diff options
| -rw-r--r-- | AGENTS.md | 2 | ||||
| -rw-r--r-- | assistant.py | 44 |
2 files changed, 44 insertions, 2 deletions
@@ -833,7 +833,7 @@ kokoro-onnx GitHub release `model-files-v1.0`. still applies; its tasks 1-4 target this daemon, the voice-specific bits of task 4 aside. - **llamachat ports**, if the canvas replaces llamachat for good: - attachments, skills, a model picker, model-written titles. + attachments, skills, a model picker (model-written titles done). - **Phase 2 tools:** tools that write or modify (events, messages, desktop), with a review step, own spec. Desktop modes done (`mode`, no review step: one bit); the settings window's other pages next, diff --git a/assistant.py b/assistant.py index f246101..cf92c16 100644 --- a/assistant.py +++ b/assistant.py @@ -244,6 +244,8 @@ PAGE_PX = 1600 # longest side of a page render MAX_AUDIO_S = 60 HEAR = ("Transcribe this audio exactly, in the language spoken (Italian or English). " "Output only the transcript.") +TITLE = ("Write a title of at most six words for this conversation, in the language " + "it is in. Output only the title.") class Refused(Exception): @@ -1027,13 +1029,38 @@ def answer(mine, text, files=()): if turn != mine: log("interrupted") return + fresh = cur["id"] is None cur["id"] = save_turn(DB, cur["id"], text, said, used, entry, chips) + conv = cur["id"] cur["history"].append(entry) cur["turns"].append({"user": text, "reply": said, "tools": used, "files": chips}) cur["pending"] = None emit({"type": "done", "id": cur["id"]}) emit({"type": "state", "state": "idle"}) log("assistant:", said) + if fresh: + retitle(mine, conv, text, said) + + +def retitle(mine, conv, user, said): + # The first six words stand until the model names the conversation. Asked + # after done, under the reply's turn: the next ask, stop, new or open + # hangs it up, so it never holds a reply back, and the six words stay. + try: + msgs = [{"role": "user", + "content": f"{TITLE}\n\nUser: {user[:2000]}\n\nAssistant: {said[:2000]}"}] + text = "".join(p for k, p in ask(msgs, {}, None, mine, think=False, meter=False) + if k == "token") + line = next((l for l in text.splitlines() if l.strip()), "") + title = line.strip().strip("\"'*#").strip()[:80] + with lock: + if turn != mine or not title: + return + with DB: + DB.execute("UPDATE conversations SET title = ? WHERE id = ?", (title, conv)) + log(f"titled: {title}") + except Exception: # the reply is saved; a title is not worth an error + log("title failed:\n" + traceback.format_exc().rstrip()) def main(): @@ -1290,7 +1317,15 @@ def test(): self.wfile.write(json.dumps(body).encode()) def do_POST(self): - script["body"] = json.loads(self.rfile.read(int(self.headers["Content-Length"]))) + body = json.loads(self.rfile.read(int(self.headers["Content-Length"]))) + if str(body["messages"][0]["content"]).startswith(TITLE): + # Answered apart, so a title never takes a reply meant for an ask. + self.send_response(200) + self.end_headers() + self.wfile.write(b"data: " + json.dumps({"choices": [{"delta": { + "content": script.get("title", "")}}]}).encode() + b"\n\ndata: [DONE]\n\n") + return + script["body"] = body script.get("bodies", []).append(script["body"]) if script.get("hold"): # A long prompt pass: nothing is sent, so only the client @@ -1354,6 +1389,7 @@ def test(): {"tool_calls": [{"index": 0, "id": "c1", "function": {"name": "now", "arguments": "{}"}}]}], [{"content": "It is "}, {"content": "noon."}]] + script["title"] = '"Ora attuale"\n' send(type="ask", text="what time is it") got = [(e["type"], e.get("delta") or e.get("name") or e.get("state") or e.get("text")) for e in until("done") if e["type"] != "ctx"] @@ -1364,6 +1400,12 @@ def test(): assert until("state")[-1]["state"] == "idle" assert script["body"]["chat_template_kwargs"]["enable_thinking"] is True conv = cur["id"] + for _ in range(40): # the title comes after done + if list_convs(DB)[0]["title"] == "Ora attuale": + break + time.sleep(0.05) + assert list_convs(DB)[0]["title"] == "Ora attuale", list_convs(DB) + script["title"] = "" assert load_conv(DB, conv)[1] == [{"user": "what time is it", "reply": "It is noon.", "tools": ["now"], "files": []}] # The tool result went to the model but history keeps a placeholder. |
