dsh: rename conversations, live model in header, swap to OmniRoute auto/* routes (opencode-go dropped - no creds/burn); direct deepseek routes dead

This commit is contained in:
2026-09-15 14:21:28 +10:00
parent 9d38d79abb
commit 882ffdb631
3 changed files with 58 additions and 15 deletions

View File

@@ -36,7 +36,7 @@ BASE_DIR = Path(__file__).resolve().parent
USER_NAME = os.getenv("DSH_USER", "user")
DEFAULT_SLOT = os.getenv("DSH_SLOT", "Welcome to DeepSeek Harness")
LLM_BASE = os.getenv("DSH_LLM_BASE", "http://192.168.20.13:20129/v1")
LLM_MODEL = os.getenv("DSH_LLM_MODEL", "opencode-go/deepseek-v4-flash")
LLM_MODEL = os.getenv("DSH_LLM_MODEL", "auto/best-chat")
LLM_KEY = os.getenv("DSH_LLM_KEY", "")
SYSTEM_PROMPT = os.getenv(
"DSH_SYSTEM_PROMPT",
@@ -574,17 +574,25 @@ async def generate_image(prompt: Annotated[str, Form()]) -> dict:
MODEL_CHOICES = ["opencode-go/deepseek-v4-flash",
"deepseek/deepseek-v4-flash",
"deepseek/deepseek-v4-pro",
"z-ai/glm-5v-turbo"]
MODEL_CHOICES = ["auto/best-chat",
"auto/best-fast",
"auto/cheap",
"auto/best-reasoning",
"auto/best-coding",
"auto/best-vision",
"openrouter/deepseek/deepseek-v4-flash",
"openrouter/deepseek/deepseek-v4-pro"]
MODEL_LABELS = {
"opencode-go/deepseek-v4-flash": "Chat · opencode-go flash",
"deepseek/deepseek-v4-flash": "Reasoning · deepseek flash",
"deepseek/deepseek-v4-pro": "Coding · deepseek pro",
"z-ai/glm-5v-turbo": "Multimodal · GLM vision",
"auto/best-chat": "Chat · OmniRoute best",
"auto/best-fast": "Fast · OmniRoute best-fast",
"auto/cheap": "Budget · OmniRoute cheap",
"auto/best-reasoning": "Reasoning · OmniRoute best",
"auto/best-coding": "Coding · OmniRoute best",
"auto/best-vision": "Multimodal · OmniRoute vision",
"openrouter/deepseek/deepseek-v4-flash": "DeepSeek v4 flash (OpenRouter)",
"openrouter/deepseek/deepseek-v4-pro": "DeepSeek v4 pro (OpenRouter)",
}
VISION_OVERRIDE_MODELS = {"z-ai/glm-5v-turbo"}
VISION_OVERRIDE_MODELS = {"auto/best-vision"}
@app.get("/api/models")
@@ -619,6 +627,22 @@ async def sessions_get(sid: str) -> dict:
"model": s.get("model") or LLM_MODEL}
@app.post("/api/sessions/{sid}/rename")
async def sessions_rename(sid: str, name: Annotated[str, Form()]) -> dict:
"""Rename a conversation (shake off the auto-generated first-message title)."""
name = (name or "").strip()[:80] or "Conversation"
try:
d = load_session(sid)
d["name"] = name
d["updated"] = datetime.now(timezone.utc).isoformat()
_sdir()
with open(_spath(sid), "w", encoding="utf-8") as fh:
json.dump(d, fh, ensure_ascii=False)
except Exception as exc: # noqa: BLE001
return {"ok": False, "error": str(exc)}
return {"ok": True}
@app.post("/api/sessions/{sid}/delete")
async def sessions_delete(sid: str) -> dict:
try:

View File

@@ -22,7 +22,7 @@ x-dsh-common: &common
- /tmp
environment: &env-base
DSH_LLM_BASE: http://192.168.20.13:20129/v1
DSH_LLM_MODEL: opencode-go/deepseek-v4-flash
DSH_LLM_MODEL: auto/best-chat
DSH_HISTORY_DIR: /data
DSH_VISION_KEY: ${DSH_VISION_KEY:-}
DSH_VISION_MODEL: openai/gpt-5

View File

@@ -41,7 +41,7 @@
#sessions li.sel { background: var(--primary, #0075de); color: #fff; }
#sessions li .t { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#sessions li .n { font-size: 11px; opacity: .7; }
#sessions li .del { background: none; border: none; cursor: pointer; color: inherit; opacity: .5; font-size: 13px; }
#sessions li .del, #sessions li .ren { background: none; border: none; cursor: pointer; color: inherit; opacity: .5; font-size: 13px; }
.chat-col { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.log { flex: 1; overflow: auto; }
.bubble { position: relative; white-space: pre-wrap; }
@@ -103,7 +103,7 @@
<button id="menuBtn" type="button" aria-label="Conversations" title="Conversations" style="display:none;align-items:center;justify-content:center;background:none;border:1px solid var(--hairline, #e6e6e6);border-radius:8px;padding:4px 9px;font-size:17px;cursor:pointer;line-height:1">☰</button>
<button id="themeBtn" type="button" title="Toggle dark mode" style="background:none;border:1px solid var(--hairline, #e6e6e6);border-radius:8px;padding:4px 8px;font-size:16px;cursor:pointer;color:var(--ink, #111)">🌙</button>
<div class="logo">DeepSeek Harness</div>
<div class="who" style="margin-left:auto">{{ DSH_SLOT }} · <span class="model">{{ DSH_MODEL }}</span></div>
<div class="who" style="margin-left:auto">{{ DSH_SLOT }} · <span class="model" id="whoModel">{{ DSH_MODEL }}</span></div>
</header>
<div class="app">
@@ -206,20 +206,39 @@
const n = document.createElement('span'); n.className = 'n'; n.textContent = s.count;
const d = document.createElement('button'); d.className = 'del'; d.textContent = '✕'; d.title = 'Delete';
d.addEventListener('click', (e) => { e.stopPropagation(); deleteSession(s.id); });
li.append(t, n, d);
const r = document.createElement('button'); r.className = 'ren'; r.textContent = '✎'; r.title = 'Rename';
r.addEventListener('click', (e) => { e.stopPropagation(); renameSession(s.id); });
li.append(t, n, r, d);
li.addEventListener('click', () => openSession(s.id));
listEl.appendChild(li);
});
}
function whoModel() {
const m = modelSel.value || '';
const label = [...modelSel.options].find(o => o.value === m);
const el = document.getElementById('whoModel');
if (el) el.textContent = label ? label.textContent : m;
}
async function renameSession(id) {
const s = await (await fetch('/api/sessions/' + id)).json();
const name = prompt('Rename conversation:', s.name || '');
if (name === null) return;
const fd = new URLSearchParams({ name });
const d = await (await fetch('/api/sessions/' + id + '/rename', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: fd })).json();
if (d.ok) loadSessions(); else alert('Rename failed: ' + (d.error || 'unknown'));
}
async function fetchModels() {
try {
const d = await (await fetch('/api/models')).json();
(d.current || []).forEach(m => {
const o = document.createElement('option'); o.value = m; o.textContent = (d.labels && d.labels[m]) || m;
if (m === d.default) o.selected = true;
if (m === (d.default || (d.current && d.current[0]))) o.selected = true;
modelSel.appendChild(o);
});
modelSel.addEventListener('change', () => { whoModel(); });
whoModel();
} catch (_) {}
}