dsh: model selector (per-session, tiered), thinking indicator, copy-per-message, download .md

This commit is contained in:
2026-08-26 14:00:02 +10:00
parent b868dd5f84
commit e24a216128
2 changed files with 91 additions and 52 deletions

View File

@@ -93,16 +93,20 @@ def load_session(sid):
d = json.load(fh)
msgs = [m for m in d.get("messages", []) if m.get("role") in ("user", "assistant")]
return {"id": d.get("id", sid or "main"), "name": d.get("name", "Conversation"),
"messages": msgs[-HISTORY_LIMIT:]}
"messages": msgs[-HISTORY_LIMIT:],
"model": d.get("model") or LLM_MODEL}
except Exception:
return {"id": sid or "main", "name": "Conversation", "messages": []}
return {"id": sid or "main", "name": "Conversation", "messages": [],
"model": LLM_MODEL}
def save_session(sid, msgs):
def save_session(sid, msgs, model=None):
_sdir()
sid = (sid or "main")
with _history_lock:
d = load_session(sid)
if model:
d["model"] = model
d["messages"] = msgs[-HISTORY_LIMIT:]
d["updated"] = datetime.now(timezone.utc).isoformat()
if not d.get("name") or d["name"] in ("Conversation", "New conversation"):
@@ -213,7 +217,8 @@ async def index(request: Request) -> HTMLResponse:
@app.post("/api/chat")
async def chat(message: Annotated[str, Form()],
image: Annotated[str, Form()] = "",
session_id: Annotated[str, Form()] = "") -> StreamingResponse:
session_id: Annotated[str, Form()] = "",
model: Annotated[str, Form()] = "") -> StreamingResponse:
"""Stream an answer as SSE. When `image` is a data:image URI the vision
model (OpenRouter) is used instead of the text LLM (image ingest)."""
use_vision = bool(image and image.startswith("data:image/"))
@@ -221,6 +226,8 @@ async def chat(message: Annotated[str, Form()],
async def event_stream():
sid = session_id or "main"
session = load_session(sid)
if model and model in MODEL_CHOICES:
session["model"] = model
hist = session["messages"]
hist.append({"role": "user", "content": message})
if use_vision:
@@ -242,7 +249,7 @@ async def chat(message: Annotated[str, Form()],
base = LLM_BASE
key = LLM_KEY
payload = {
"model": LLM_MODEL,
"model": session.get("model") or LLM_MODEL,
"messages": [{"role": "system", "content": SYSTEM_PROMPT}] + hist,
"stream": True,
"temperature": 0.7,
@@ -276,7 +283,7 @@ async def chat(message: Annotated[str, Form()],
return
if full:
hist.append({"role": "assistant", "content": "".join(full)})
save_session(sid, hist)
save_session(sid, hist, session.get("model")) # always persist user turn + chosen model
return StreamingResponse(
event_stream(),
@@ -314,6 +321,16 @@ async def generate_image(prompt: Annotated[str, Form()]) -> dict:
return {"ok": False, "error": f"{type(exc).__name__}: {exc}"}
MODEL_CHOICES = ["auto/best-chat", "auto/best-fast", "auto/best-reasoning",
"auto/best-coding", "auto/best-vision"]
@app.get("/api/models")
async def models_list() -> dict:
return {"ok": True, "default": LLM_MODEL, "current": MODEL_CHOICES}
@app.get("/api/sessions")
async def sessions_list() -> dict:
return {"ok": True, "sessions": list_sessions()}
@@ -336,7 +353,8 @@ async def sessions_new() -> dict:
@app.get("/api/sessions/{sid}")
async def sessions_get(sid: str) -> dict:
s = load_session(sid)
return {"ok": True, "id": s["id"], "name": s["name"], "messages": s["messages"]}
return {"ok": True, "id": s["id"], "name": s["name"], "messages": s["messages"],
"model": s.get("model") or LLM_MODEL}
@app.post("/api/sessions/{sid}/delete")