dsh: model selector (per-session, tiered), thinking indicator, copy-per-message, download .md
This commit is contained in:
32
dsh/app.py
32
dsh/app.py
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user