diff --git a/dsh/app.py b/dsh/app.py index d6d3d38..1866ba3 100644 --- a/dsh/app.py +++ b/dsh/app.py @@ -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: diff --git a/dsh/docker-compose.yml b/dsh/docker-compose.yml index e003182..17a2e7f 100644 --- a/dsh/docker-compose.yml +++ b/dsh/docker-compose.yml @@ -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 diff --git a/dsh/templates/chat.html b/dsh/templates/chat.html index 75241e6..ea1921d 100644 --- a/dsh/templates/chat.html +++ b/dsh/templates/chat.html @@ -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 @@ -
{{ DSH_SLOT }} · {{ DSH_MODEL }}
+
{{ DSH_SLOT }} · {{ DSH_MODEL }}
@@ -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 (_) {} }