Spaces:
Running
Running
Add qwen/qwen3.7-flash and inclusionai/ling-2.6-flash models
#2
by bep40 - opened
- Dockerfile +1 -1
- README.md +7 -19
- fix_space_error_report.json +14 -21
- patch_frontend.py +4 -67
- patch_models.py +6 -141
Dockerfile
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# Rebuild
|
| 2 |
|
| 3 |
# Stage 1: Clone upstream + patch frontend ChatInput + build
|
| 4 |
FROM node:20-alpine AS patcher
|
|
|
|
| 1 |
+
# Rebuild v9: Fix model routing - Hy3 Preview (OpenRouter via openai/tencent/), cohere/north-mini-code, deepseek/deepseek-v4-flash OR route, gpt-oss-120b no double-prefix
|
| 2 |
|
| 3 |
# Stage 1: Clone upstream + patch frontend ChatInput + build
|
| 4 |
FROM node:20-alpine AS patcher
|
README.md
CHANGED
|
@@ -25,9 +25,9 @@ secrets:
|
|
| 25 |
|
| 26 |
# Space Multiverse — ML Intern V2 (FREE & SAVE MODEL)
|
| 27 |
|
| 28 |
-
<!-- rebuild: 2026-
|
| 29 |
|
| 30 |
-
**
|
| 31 |
|
| 32 |
## Models
|
| 33 |
|
|
@@ -42,24 +42,12 @@ secrets:
|
|
| 42 |
| 7 | **DeepSeek V4 Flash (OR)** | OpenRouter | `deepseek` | ✅ |
|
| 43 |
| 8 | **Gemma 4 9B** | OpenRouter | `google` | ✅ |
|
| 44 |
| 9 | **Gemma 4 31B** | OpenRouter | `google` | ✅ |
|
| 45 |
-
| 10 | **
|
| 46 |
-
| 11 | **
|
| 47 |
-
| 12 | **
|
| 48 |
-
| 13 | **
|
| 49 |
-
| 14 | **Laguna M.1** | OpenRouter | `poolside` | ✅ |
|
| 50 |
-
| 15 | **Laguna XS.2** | OpenRouter | `poolside` | ✅ |
|
| 51 |
-
| 16 | **Qwen3.7 Flash** | OpenRouter | `qwen` | ✅ |
|
| 52 |
-
| 17 | **Ling 2.6 Flash** | OpenRouter | `inclusionai` | ✅ |
|
| 53 |
-
| 18 | **Ling 3.0 Flash** | OpenRouter | `inclusionai` | ✅ |
|
| 54 |
-
| 19 | **Ling 3.0 Tiny** | OpenRouter | `inclusionai` | ✅ |
|
| 55 |
-
| 20 | **Ling 3.0 Flash VL** | OpenRouter | `inclusionai` | ✅ |
|
| 56 |
-
| 21 | **Nex-N2.5-Mini** | OpenRouter | `nex-agi` | ✅ |
|
| 57 |
-
| 22 | **Nex-N2.5-Pro** | OpenRouter | `nex-agi` | ✅ |
|
| 58 |
-
| 23 | **GLM 5.3 Flash** | OpenRouter | `z-ai` | ✅ |
|
| 59 |
-
| 24 | **Owl Alpha** | OpenRouter | `openrouter` | ✅ |
|
| 60 |
-
| 25 | **DeepSeek V4 Flash 0731** | HF Inference | `deepseek` | ✅ |
|
| 61 |
|
| 62 |
> Owl Alpha REMOVED — its free provider "Stealth" on OpenRouter is broken (returns 400 error).
|
| 63 |
|
| 64 |
## Required Secrets
|
| 65 |
-
- `OPENROUTER_API_KEY` — required for OpenRouter-routed models
|
|
|
|
| 25 |
|
| 26 |
# Space Multiverse — ML Intern V2 (FREE & SAVE MODEL)
|
| 27 |
|
| 28 |
+
<!-- rebuild: 2026-06-06-owl-alpha-removed-stealth-broken -->
|
| 29 |
|
| 30 |
+
**13 models — mix of HF Inference + OpenRouter.**
|
| 31 |
|
| 32 |
## Models
|
| 33 |
|
|
|
|
| 42 |
| 7 | **DeepSeek V4 Flash (OR)** | OpenRouter | `deepseek` | ✅ |
|
| 43 |
| 8 | **Gemma 4 9B** | OpenRouter | `google` | ✅ |
|
| 44 |
| 9 | **Gemma 4 31B** | OpenRouter | `google` | ✅ |
|
| 45 |
+
| 10 | **Nemotron 3 Super 120B** | OpenRouter | `nvidia` | ✅ |
|
| 46 |
+
| 11 | **Nemotron 3 Ultra 550B** | OpenRouter | `nvidia` | ✅ |
|
| 47 |
+
| 12 | **Laguna M.1** | OpenRouter | `poolside` | ✅ |
|
| 48 |
+
| 13 | **Laguna XS.2** | OpenRouter | `poolside` | ✅ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
> Owl Alpha REMOVED — its free provider "Stealth" on OpenRouter is broken (returns 400 error).
|
| 51 |
|
| 52 |
## Required Secrets
|
| 53 |
+
- `OPENROUTER_API_KEY` — required for OpenRouter-routed models
|
fix_space_error_report.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
{
|
| 2 |
-
"status": "
|
| 3 |
"space": "bep40/Space-Mutiverse-Control",
|
| 4 |
"space_url": "https://huggingface.co/spaces/bep40/Space-Mutiverse-Control",
|
| 5 |
"files_changed": ["patch_frontend.py", "patch_models.py"],
|
|
@@ -7,32 +7,25 @@
|
|
| 7 |
{"row":1, "name":"Owl Alpha", "path":"openai/openrouter/owl-alpha"},
|
| 8 |
{"row":2, "name":"DeepSeek V4 Pro", "path":"deepseek-ai/DeepSeek-V4-Pro"},
|
| 9 |
{"row":3, "name":"DeepSeek V4 Flash", "path":"deepseek-ai/DeepSeek-V4-Flash"},
|
| 10 |
-
{"row":4, "name":"
|
| 11 |
{"row":5, "name":"Qwen3 Coder Next", "path":"Qwen/Qwen3-Coder-Next"},
|
| 12 |
{"row":6, "name":"Gemma 3 1B", "path":"google/gemma-3-1b-it"},
|
| 13 |
-
{"row":7, "name":"
|
| 14 |
-
{"row":8, "name":"
|
| 15 |
-
{"row":9, "name":"
|
| 16 |
-
{"row":10, "name":"Gemma 4
|
| 17 |
-
{"row":11, "name":"
|
| 18 |
-
{"row":12, "name":"
|
| 19 |
-
{"row":13, "name":"
|
| 20 |
-
{"row":14, "name":"Nemotron 3
|
| 21 |
-
{"row":15, "name":"
|
| 22 |
-
{"row":16, "name":"Laguna
|
| 23 |
-
{"row":17, "name":"Laguna XS.2", "path":"openai/poolside/laguna-xs.2:free"},
|
| 24 |
-
{"row":18, "name":"Ling 3.0 Flash", "path":"openai/inclusionai/ling-3.0-flash"},
|
| 25 |
-
{"row":19, "name":"Ling 3.0 Tiny", "path":"openai/inclusionai/ling-3.0-tiny:free"},
|
| 26 |
-
{"row":20, "name":"DeepSeek V4 Flash 0731", "path":"deepseek/deepseek-v4-flash-0731"}
|
| 27 |
],
|
| 28 |
"changes": {
|
| 29 |
-
"stealth/ox-alpha": "-> z-ai/glm-5.3-flash (OpenRouter routing, path preserved as openai/z-ai/glm-5.3-flash)",
|
| 30 |
-
"GLM 5.2": "-> GLM 5.3 Flash (model.ts: GLM_52_MODEL_PATH -> zai-org/GLM-5.3-Flash:novita)",
|
| 31 |
"Riverflow V2.5 Fast": "→ Hy3 Preview (tencent/hy3-preview)",
|
| 32 |
"Riverflow V2.5 Pro": "prefix changed to cohere/north-mini-code:free",
|
| 33 |
"DeepSeek V4 Flash (OR)": "→ deepseek/deepseek-v4-flash (now HF direct)",
|
| 34 |
"GPT-5.5": "→ openai/gpt-oss-120b"
|
| 35 |
},
|
| 36 |
-
"default_free_model": "Owl Alpha"
|
| 37 |
-
|
| 38 |
-
}
|
|
|
|
| 1 |
{
|
| 2 |
+
"status": "updated_with_16_models_v2",
|
| 3 |
"space": "bep40/Space-Mutiverse-Control",
|
| 4 |
"space_url": "https://huggingface.co/spaces/bep40/Space-Mutiverse-Control",
|
| 5 |
"files_changed": ["patch_frontend.py", "patch_models.py"],
|
|
|
|
| 7 |
{"row":1, "name":"Owl Alpha", "path":"openai/openrouter/owl-alpha"},
|
| 8 |
{"row":2, "name":"DeepSeek V4 Pro", "path":"deepseek-ai/DeepSeek-V4-Pro"},
|
| 9 |
{"row":3, "name":"DeepSeek V4 Flash", "path":"deepseek-ai/DeepSeek-V4-Flash"},
|
| 10 |
+
{"row":4, "name":"GPT-5.5", "path":"openai/gpt-oss-120b"},
|
| 11 |
{"row":5, "name":"Qwen3 Coder Next", "path":"Qwen/Qwen3-Coder-Next"},
|
| 12 |
{"row":6, "name":"Gemma 3 1B", "path":"google/gemma-3-1b-it"},
|
| 13 |
+
{"row":7, "name":"Gemini 2.0 Flash", "path":"openai/google/gemini-2.0-flash-001"},
|
| 14 |
+
{"row":8, "name":"DeepSeek V4 Flash (OR)", "path":"deepseek/deepseek-v4-flash"},
|
| 15 |
+
{"row":9, "name":"Gemma 4 9B", "path":"openai/google/gemma-4-9b-it:free"},
|
| 16 |
+
{"row":10, "name":"Gemma 4 31B", "path":"openai/google/gemma-4-31b-it:free"},
|
| 17 |
+
{"row":11, "name":"Hy3 Preview", "path":"tencent/hy3-preview"},
|
| 18 |
+
{"row":12, "name":"Riverflow V2.5 Pro", "path":"cohere/north-mini-code:free"},
|
| 19 |
+
{"row":13, "name":"Nemotron 3 Super 120B", "path":"openai/nvidia/nemotron-3-super-120b-a12b:free"},
|
| 20 |
+
{"row":14, "name":"Nemotron 3 Ultra 550B", "path":"openai/nvidia/nemotron-3-ultra-550b-a55b:free"},
|
| 21 |
+
{"row":15, "name":"Laguna M.1", "path":"openai/poolside/laguna-m.1:free"},
|
| 22 |
+
{"row":16, "name":"Laguna XS.2", "path":"openai/poolside/laguna-xs.2:free"}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
],
|
| 24 |
"changes": {
|
|
|
|
|
|
|
| 25 |
"Riverflow V2.5 Fast": "→ Hy3 Preview (tencent/hy3-preview)",
|
| 26 |
"Riverflow V2.5 Pro": "prefix changed to cohere/north-mini-code:free",
|
| 27 |
"DeepSeek V4 Flash (OR)": "→ deepseek/deepseek-v4-flash (now HF direct)",
|
| 28 |
"GPT-5.5": "→ openai/gpt-oss-120b"
|
| 29 |
},
|
| 30 |
+
"default_free_model": "Owl Alpha"
|
| 31 |
+
}
|
|
|
patch_frontend.py
CHANGED
|
@@ -1,23 +1,21 @@
|
|
| 1 |
-
"""Patch frontend ChatInput.tsx: Replace DEFAULT_MODEL_OPTIONS array + fix imports
|
| 2 |
|
| 3 |
import re
|
| 4 |
import sys
|
| 5 |
|
| 6 |
FILE = "/source/frontend/src/components/Chat/ChatInput.tsx"
|
| 7 |
-
MODEL_TS_FILE = "/source/frontend/src/utils/model.ts"
|
| 8 |
|
| 9 |
with open(FILE, "r", encoding="utf-8") as f:
|
| 10 |
content = f.read()
|
| 11 |
|
| 12 |
# === Step 1: Fix imports — replace any model-constants import block ===
|
| 13 |
-
# Restore GLM_52_MODEL_PATH import so we can use it in the model options
|
| 14 |
import_pattern = re.compile(
|
| 15 |
r'import\s*\{[^}]+\}\s*from\s*[\'"]@/utils/model[\'"];'
|
| 16 |
)
|
| 17 |
match = import_pattern.search(content)
|
| 18 |
if match:
|
| 19 |
-
content = content.replace(match.group(0), "import {
|
| 20 |
-
print("OK: Replaced imports with
|
| 21 |
else:
|
| 22 |
print("OK: Imports already clean or not found")
|
| 23 |
|
|
@@ -72,13 +70,6 @@ new_array = """const DEFAULT_MODEL_OPTIONS: ModelOption[] = [
|
|
| 72 |
avatarUrl: 'https://huggingface.co/api/avatars/google',
|
| 73 |
recommended: true,
|
| 74 |
},
|
| 75 |
-
{
|
| 76 |
-
id: 'glm-5.3-flash',
|
| 77 |
-
name: 'GLM 5.3 Flash',
|
| 78 |
-
modelPath: GLM_52_MODEL_PATH,
|
| 79 |
-
avatarUrl: 'https://huggingface.co/api/avatars/zai-org',
|
| 80 |
-
recommended: true,
|
| 81 |
-
},
|
| 82 |
{
|
| 83 |
id: 'gemini-2.0-flash',
|
| 84 |
name: 'Gemini 2.0 Flash',
|
|
@@ -145,45 +136,6 @@ new_array = """const DEFAULT_MODEL_OPTIONS: ModelOption[] = [
|
|
| 145 |
modelPath: 'openai/poolside/laguna-xs.2:free',
|
| 146 |
avatarUrl: 'https://huggingface.co/api/avatars/poolside',
|
| 147 |
},
|
| 148 |
-
{
|
| 149 |
-
id: 'ling-3.0-flash',
|
| 150 |
-
name: 'Ling 3.0 Flash',
|
| 151 |
-
modelPath: 'openai/inclusionai/ling-3.0-flash',
|
| 152 |
-
avatarUrl: 'https://huggingface.co/api/avatars/inclusionai',
|
| 153 |
-
},
|
| 154 |
-
{
|
| 155 |
-
id: 'ling-3.0-tiny',
|
| 156 |
-
name: 'Ling 3.0 Tiny',
|
| 157 |
-
modelPath: 'openai/inclusionai/ling-3.0-tiny:free',
|
| 158 |
-
avatarUrl: 'https://huggingface.co/api/avatars/inclusionai',
|
| 159 |
-
},
|
| 160 |
-
{
|
| 161 |
-
id: 'ling-3.0-flash-vl',
|
| 162 |
-
name: 'Ling 3.0 Flash VL',
|
| 163 |
-
modelPath: 'openai/inclusionai/ling-3.0-flash-vl:free',
|
| 164 |
-
avatarUrl: 'https://huggingface.co/api/avatars/inclusionai',
|
| 165 |
-
recommended: true,
|
| 166 |
-
},
|
| 167 |
-
{
|
| 168 |
-
id: 'nex-n2.5-mini',
|
| 169 |
-
name: 'Nex-N2.5-Mini',
|
| 170 |
-
modelPath: 'openai/nex-agi/nex-n2.5-mini:free',
|
| 171 |
-
avatarUrl: 'https://huggingface.co/api/avatars/nex-agi',
|
| 172 |
-
recommended: true,
|
| 173 |
-
},
|
| 174 |
-
{
|
| 175 |
-
id: 'nex-n2.5-pro',
|
| 176 |
-
name: 'Nex-N2.5-Pro',
|
| 177 |
-
modelPath: 'openai/nex-agi/nex-n2.5-pro:free',
|
| 178 |
-
avatarUrl: 'https://huggingface.co/api/avatars/nex-agi',
|
| 179 |
-
recommended: true,
|
| 180 |
-
},
|
| 181 |
-
{
|
| 182 |
-
id: 'deepseek-v4-flash-0731',
|
| 183 |
-
name: 'DeepSeek V4 Flash 0731',
|
| 184 |
-
modelPath: 'deepseek/deepseek-v4-flash-0731',
|
| 185 |
-
avatarUrl: 'https://huggingface.co/api/avatars/deepseek-ai',
|
| 186 |
-
},
|
| 187 |
];"""
|
| 188 |
|
| 189 |
content = content[:match.start()] + new_array + content[match.end():]
|
|
@@ -199,19 +151,4 @@ else:
|
|
| 199 |
with open(FILE, "w", encoding="utf-8") as f:
|
| 200 |
f.write(content)
|
| 201 |
|
| 202 |
-
print("OK: Frontend patched -
|
| 203 |
-
|
| 204 |
-
# === Step 4: Patch model.ts — GLM_52_MODEL_PATH -> zai-org/GLM-5.3-Flash:novita ===
|
| 205 |
-
with open(MODEL_TS_FILE, "r", encoding="utf-8") as f:
|
| 206 |
-
model_content = f.read()
|
| 207 |
-
|
| 208 |
-
model_content = model_content.replace(
|
| 209 |
-
"export const GLM_52_MODEL_PATH = 'zai-org/GLM-5.2:novita';",
|
| 210 |
-
"export const GLM_52_MODEL_PATH = 'zai-org/GLM-5.3-Flash:novita';"
|
| 211 |
-
)
|
| 212 |
-
print("OK: Patched GLM_52_MODEL_PATH -> zai-org/GLM-5.3-Flash:novita in model.ts")
|
| 213 |
-
|
| 214 |
-
with open(MODEL_TS_FILE, "w", encoding="utf-8") as f:
|
| 215 |
-
f.write(model_content)
|
| 216 |
-
|
| 217 |
-
print("OK: Frontend fully patched")
|
|
|
|
| 1 |
+
"""Patch frontend ChatInput.tsx: Replace DEFAULT_MODEL_OPTIONS array + fix imports."""
|
| 2 |
|
| 3 |
import re
|
| 4 |
import sys
|
| 5 |
|
| 6 |
FILE = "/source/frontend/src/components/Chat/ChatInput.tsx"
|
|
|
|
| 7 |
|
| 8 |
with open(FILE, "r", encoding="utf-8") as f:
|
| 9 |
content = f.read()
|
| 10 |
|
| 11 |
# === Step 1: Fix imports — replace any model-constants import block ===
|
|
|
|
| 12 |
import_pattern = re.compile(
|
| 13 |
r'import\s*\{[^}]+\}\s*from\s*[\'"]@/utils/model[\'"];'
|
| 14 |
)
|
| 15 |
match = import_pattern.search(content)
|
| 16 |
if match:
|
| 17 |
+
content = content.replace(match.group(0), "import { isClaudePath } from '@/utils/model';")
|
| 18 |
+
print("OK: Replaced imports with just isClaudePath")
|
| 19 |
else:
|
| 20 |
print("OK: Imports already clean or not found")
|
| 21 |
|
|
|
|
| 70 |
avatarUrl: 'https://huggingface.co/api/avatars/google',
|
| 71 |
recommended: true,
|
| 72 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
{
|
| 74 |
id: 'gemini-2.0-flash',
|
| 75 |
name: 'Gemini 2.0 Flash',
|
|
|
|
| 136 |
modelPath: 'openai/poolside/laguna-xs.2:free',
|
| 137 |
avatarUrl: 'https://huggingface.co/api/avatars/poolside',
|
| 138 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
];"""
|
| 140 |
|
| 141 |
content = content[:match.start()] + new_array + content[match.end():]
|
|
|
|
| 151 |
with open(FILE, "w", encoding="utf-8") as f:
|
| 152 |
f.write(content)
|
| 153 |
|
| 154 |
+
print("OK: Frontend patched - 16 models, deepseek-ai/DeepSeek-V4-Pro:novita default")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
patch_models.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
"""Patch backend: replace AVAILABLE_MODELS (remove old 6, add
|
| 2 |
|
| 3 |
import ast
|
| 4 |
import os
|
|
@@ -148,58 +148,13 @@ new_func = '''def _available_models() -> list[dict[str, Any]]:
|
|
| 148 |
"provider": "openrouter",
|
| 149 |
"tier": "free",
|
| 150 |
},
|
| 151 |
-
{
|
| 152 |
-
"id": "openai/inclusionai/ling-3.0-flash",
|
| 153 |
-
"label": "Ling 3.0 Flash",
|
| 154 |
-
"provider": "openrouter",
|
| 155 |
-
"tier": "free",
|
| 156 |
-
},
|
| 157 |
-
{
|
| 158 |
-
"id": "openai/inclusionai/ling-3.0-tiny:free",
|
| 159 |
-
"label": "Ling 3.0 Tiny",
|
| 160 |
-
"provider": "openrouter",
|
| 161 |
-
"tier": "free",
|
| 162 |
-
},
|
| 163 |
-
{
|
| 164 |
-
"id": "openai/inclusionai/ling-3.0-flash-vl:free",
|
| 165 |
-
"label": "Ling 3.0 Flash VL",
|
| 166 |
-
"provider": "openrouter",
|
| 167 |
-
"tier": "free",
|
| 168 |
-
"recommended": True,
|
| 169 |
-
},
|
| 170 |
-
{
|
| 171 |
-
"id": "openai/nex-agi/nex-n2.5-mini:free",
|
| 172 |
-
"label": "Nex-N2.5-Mini",
|
| 173 |
-
"provider": "openrouter",
|
| 174 |
-
"tier": "free",
|
| 175 |
-
"recommended": True,
|
| 176 |
-
},
|
| 177 |
-
{
|
| 178 |
-
"id": "openai/nex-agi/nex-n2.5-pro:free",
|
| 179 |
-
"label": "Nex-N2.5-Pro",
|
| 180 |
-
"provider": "openrouter",
|
| 181 |
-
"tier": "free",
|
| 182 |
-
"recommended": True,
|
| 183 |
-
},
|
| 184 |
-
{
|
| 185 |
-
"id": "z-ai/glm-5.3-flash",
|
| 186 |
-
"label": "GLM 5.3 Flash",
|
| 187 |
-
"provider": "openrouter",
|
| 188 |
-
"tier": "paid",
|
| 189 |
-
},
|
| 190 |
-
{
|
| 191 |
-
"id": "deepseek/deepseek-v4-flash-0731",
|
| 192 |
-
"label": "DeepSeek V4 Flash 0731",
|
| 193 |
-
"provider": "openrouter",
|
| 194 |
-
"tier": "free",
|
| 195 |
-
},
|
| 196 |
]
|
| 197 |
return models'''
|
| 198 |
|
| 199 |
func_match = func_pattern.search(content)
|
| 200 |
if func_match:
|
| 201 |
content = content[:func_match.start()] + new_func + content[func_match.end():]
|
| 202 |
-
print("OK: Replaced _available_models() with
|
| 203 |
else:
|
| 204 |
print("WARN: Could not find _available_models() via regex, trying extend approach...")
|
| 205 |
target = "AVAILABLE_MODELS = _available_models()\n"
|
|
@@ -226,13 +181,6 @@ else:
|
|
| 226 |
' {"id": "openai/poolside/laguna-xs.2:free", "label": "Laguna XS.2", "provider": "openrouter", "tier": "free"},\n'
|
| 227 |
' {"id": "openai/qwen/qwen3.7-flash", "label": "Qwen3.7 Flash", "provider": "openrouter", "tier": "free"},\n'
|
| 228 |
' {"id": "openai/inclusionai/ling-2.6-flash", "label": "Ling 2.6 Flash", "provider": "openrouter", "tier": "free"},\n'
|
| 229 |
-
' {"id": "openai/inclusionai/ling-3.0-flash", "label": "Ling 3.0 Flash", "provider": "openrouter", "tier": "free"},\n'
|
| 230 |
-
' {"id": "openai/inclusionai/ling-3.0-tiny:free", "label": "Ling 3.0 Tiny", "provider": "openrouter", "tier": "free"},\n'
|
| 231 |
-
' {"id": "openai/inclusionai/ling-3.0-flash-vl:free", "label": "Ling 3.0 Flash VL", "provider": "openrouter", "tier": "free", "recommended": True},\n'
|
| 232 |
-
' {"id": "openai/nex-agi/nex-n2.5-mini:free", "label": "Nex-N2.5-Mini", "provider": "openrouter", "tier": "free", "recommended": True},\n'
|
| 233 |
-
' {"id": "openai/nex-agi/nex-n2.5-pro:free", "label": "Nex-N2.5-Pro", "provider": "openrouter", "tier": "free", "recommended": True},\n'
|
| 234 |
-
' {"id": "z-ai/glm-5.3-flash", "label": "GLM 5.3 Flash", "provider": "openrouter", "tier": "paid"},\n'
|
| 235 |
-
' {"id": "deepseek/deepseek-v4-flash-0731", "label": "DeepSeek V4 Flash 0731", "provider": "openrouter", "tier": "free"},\n'
|
| 236 |
"])\n"
|
| 237 |
'DEFAULT_FREE_MODEL_ID = "openai/openrouter/owl-alpha"\n'
|
| 238 |
)
|
|
@@ -261,34 +209,10 @@ api_key_match = re.search(r'api_key\s*=\s*_resolve_hf_router_token\(session_hf_t
|
|
| 261 |
if api_key_match:
|
| 262 |
line_end = llm_content.find('\n', api_key_match.end()) + 1
|
| 263 |
if line_end > 0:
|
| 264 |
-
routing_insert = ''' # === PATCH: Route
|
| 265 |
-
# FIX: Add explicit deepseek/ route so "deepseek/deepseek-v4-flash-0731" goes
|
| 266 |
-
# to OpenRouter (HF Router does NOT have this id -> "model does not exist").
|
| 267 |
-
if normalized_model.startswith("deepseek/"):
|
| 268 |
-
return {
|
| 269 |
-
"model": normalized_model,
|
| 270 |
-
"api_base": "https://openrouter.ai/api/v1",
|
| 271 |
-
"api_key": os.environ.get("OPENROUTER_API_KEY") or api_key or "",
|
| 272 |
-
}
|
| 273 |
-
# FIX: Route z-ai/glm-5.3-flash to OpenRouter with the provider prefix so litellm
|
| 274 |
-
# recognizes the provider. Strip any trailing ":free" suffix.
|
| 275 |
-
if normalized_model.startswith("z-ai/"):
|
| 276 |
-
or_model = normalized_model
|
| 277 |
-
if or_model.endswith(":free"):
|
| 278 |
-
or_model = or_model[: -len(":free")]
|
| 279 |
-
return {
|
| 280 |
-
"model": "openrouter/" + or_model,
|
| 281 |
-
"api_base": "https://openrouter.ai/api/v1",
|
| 282 |
-
"api_key": os.environ.get("OPENROUTER_API_KEY") or api_key or "",
|
| 283 |
-
}
|
| 284 |
-
# FIX: Strip the "openai/" prefix before sending to OpenRouter.
|
| 285 |
-
# OpenRouter model ids do NOT include the "openai/" prefix, so sending the
|
| 286 |
-
# prefixed name causes a 404 (model does not exist) on OpenRouter. Prefix
|
| 287 |
-
# with "openrouter/" so litellm recognizes the provider.
|
| 288 |
if normalized_model.startswith("openai/"):
|
| 289 |
-
or_model = "openrouter/" + normalized_model[len("openai/"):]
|
| 290 |
return {
|
| 291 |
-
"model":
|
| 292 |
"api_base": "https://openrouter.ai/api/v1",
|
| 293 |
"api_key": os.environ.get("OPENROUTER_API_KEY") or api_key or "",
|
| 294 |
}
|
|
@@ -296,7 +220,7 @@ if api_key_match:
|
|
| 296 |
|
| 297 |
'''
|
| 298 |
llm_content = llm_content[:line_end] + routing_insert + llm_content[line_end:]
|
| 299 |
-
print("OK: Patched _resolve_llm_params (
|
| 300 |
else:
|
| 301 |
print("OK: Could not find end of api_key line")
|
| 302 |
else:
|
|
@@ -312,63 +236,4 @@ except SyntaxError as e:
|
|
| 312 |
with open(LLM_PARAMS_FILE, "w") as f:
|
| 313 |
f.write(llm_content)
|
| 314 |
|
| 315 |
-
print("OK: Backend patched -
|
| 316 |
-
|
| 317 |
-
# === Step 3: Patch backend/main.py — safe static cache headers ===
|
| 318 |
-
# FIX: Stale-cache blank page. HF Spaces serves the static build with NO
|
| 319 |
-
# Cache-Control headers. After every rebuild the hashed asset filename
|
| 320 |
-
# changes but the browser keeps caching index.html which then references a
|
| 321 |
-
# bundle hash that no longer exists -> 404 on /assets/index-*.js -> blank
|
| 322 |
-
# page. Fix: 1yr immutable cache for hashed assets, no-cache for index.html.
|
| 323 |
-
MAIN_FILE = "/app/backend/main.py"
|
| 324 |
-
with open(MAIN_FILE) as f:
|
| 325 |
-
main_content = f.read()
|
| 326 |
-
|
| 327 |
-
static_import_old = "from fastapi.staticfiles import StaticFiles"
|
| 328 |
-
static_import_new = """from fastapi.staticfiles import StaticFiles
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
class _CachedStaticFiles(StaticFiles):
|
| 332 |
-
\"\"\"StaticFiles with safe cache headers.
|
| 333 |
-
|
| 334 |
-
- Hashed build assets (vite emits index-<hash>.js) get a 1-year
|
| 335 |
-
immutable Cache-Control so repeat visits skip the download.
|
| 336 |
-
- index.html and everything else get no-cache so the browser always
|
| 337 |
-
revalidates and never pins a stale HTML that references a bundle
|
| 338 |
-
hash that no longer exists after a rebuild (blank-page 404 bug).
|
| 339 |
-
\"\"\"
|
| 340 |
-
|
| 341 |
-
async def get_response(self, path: str, scope):
|
| 342 |
-
resp = await super().get_response(path, scope)
|
| 343 |
-
if scope["method"] == "GET" and resp.status_code == 200:
|
| 344 |
-
if path.startswith("assets/") or path.endswith(
|
| 345 |
-
(".js", ".css", ".woff2", ".png", ".webp", ".svg", ".ico", ".jpg", ".jpeg", ".ttf")
|
| 346 |
-
):
|
| 347 |
-
resp.headers["Cache-Control"] = "public, max-age=31536000, immutable"
|
| 348 |
-
else:
|
| 349 |
-
resp.headers["Cache-Control"] = "no-cache"
|
| 350 |
-
return resp"""
|
| 351 |
-
|
| 352 |
-
if static_import_old in main_content:
|
| 353 |
-
main_content = main_content.replace(static_import_old, static_import_new)
|
| 354 |
-
else:
|
| 355 |
-
print("WARN: StaticFiles import not found for cache patch, may already be applied")
|
| 356 |
-
|
| 357 |
-
static_mount_old = 'app.mount("/", StaticFiles(directory=str(static_path), html=True), name="static")'
|
| 358 |
-
static_mount_new = 'app.mount("/", _CachedStaticFiles(directory=str(static_path), html=True), name="static")'
|
| 359 |
-
if static_mount_old in main_content:
|
| 360 |
-
main_content = main_content.replace(static_mount_old, static_mount_new)
|
| 361 |
-
else:
|
| 362 |
-
print("WARN: StaticFiles mount not found for cache patch, may already be applied")
|
| 363 |
-
|
| 364 |
-
try:
|
| 365 |
-
ast.parse(main_content)
|
| 366 |
-
print("OK: main.py syntax OK")
|
| 367 |
-
except SyntaxError as e:
|
| 368 |
-
print(f"FAIL: Syntax error in main.py: {e}")
|
| 369 |
-
raise
|
| 370 |
-
|
| 371 |
-
with open(MAIN_FILE, "w") as f:
|
| 372 |
-
f.write(main_content)
|
| 373 |
-
|
| 374 |
-
print("OK: main.py patched - immutable asset cache + no-cache index.html (fixes stale-hash blank page)")
|
|
|
|
| 1 |
+
"""Patch backend: replace AVAILABLE_MODELS (remove old 6, add 18 new) + OpenRouter routing."""
|
| 2 |
|
| 3 |
import ast
|
| 4 |
import os
|
|
|
|
| 148 |
"provider": "openrouter",
|
| 149 |
"tier": "free",
|
| 150 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
]
|
| 152 |
return models'''
|
| 153 |
|
| 154 |
func_match = func_pattern.search(content)
|
| 155 |
if func_match:
|
| 156 |
content = content[:func_match.start()] + new_func + content[func_match.end():]
|
| 157 |
+
print("OK: Replaced _available_models() with 18 new models via regex")
|
| 158 |
else:
|
| 159 |
print("WARN: Could not find _available_models() via regex, trying extend approach...")
|
| 160 |
target = "AVAILABLE_MODELS = _available_models()\n"
|
|
|
|
| 181 |
' {"id": "openai/poolside/laguna-xs.2:free", "label": "Laguna XS.2", "provider": "openrouter", "tier": "free"},\n'
|
| 182 |
' {"id": "openai/qwen/qwen3.7-flash", "label": "Qwen3.7 Flash", "provider": "openrouter", "tier": "free"},\n'
|
| 183 |
' {"id": "openai/inclusionai/ling-2.6-flash", "label": "Ling 2.6 Flash", "provider": "openrouter", "tier": "free"},\n'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
"])\n"
|
| 185 |
'DEFAULT_FREE_MODEL_ID = "openai/openrouter/owl-alpha"\n'
|
| 186 |
)
|
|
|
|
| 209 |
if api_key_match:
|
| 210 |
line_end = llm_content.find('\n', api_key_match.end()) + 1
|
| 211 |
if line_end > 0:
|
| 212 |
+
routing_insert = ''' # === PATCH: Route ALL openai/-prefixed models to OpenRouter ===
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
if normalized_model.startswith("openai/"):
|
|
|
|
| 214 |
return {
|
| 215 |
+
"model": normalized_model,
|
| 216 |
"api_base": "https://openrouter.ai/api/v1",
|
| 217 |
"api_key": os.environ.get("OPENROUTER_API_KEY") or api_key or "",
|
| 218 |
}
|
|
|
|
| 220 |
|
| 221 |
'''
|
| 222 |
llm_content = llm_content[:line_end] + routing_insert + llm_content[line_end:]
|
| 223 |
+
print("OK: Patched _resolve_llm_params (catch-all openai/ -> OR)")
|
| 224 |
else:
|
| 225 |
print("OK: Could not find end of api_key line")
|
| 226 |
else:
|
|
|
|
| 236 |
with open(LLM_PARAMS_FILE, "w") as f:
|
| 237 |
f.write(llm_content)
|
| 238 |
|
| 239 |
+
print("OK: Backend patched - 18 models, ALL openai/ -> OR")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|