8000 feat(chat): title conversations after their first answer by adeelahmad · Pull Request #531 · adeelahmad/lense · GitHub
[go: up one dir, main page]

Skip to content

feat(chat): title conversations after their first answer - #531

Merged
adeelahmad merged 1 commit into
mainfrom
claude/project-thread-aqomyd
Oct 18, 2026
Merged

adeelahmad merged 1 commit into
mainfrom
claude/project-thread-aqomyd

Conversation

@adeelahmad
Copy link
Copy Markdown
Owner

Requested by Adeel · project thread

Description

Before: a new conversation was called "New conversation" until the first question, then its first 80 characters, for good. Long questions made long, lookalike titles in the list.

After: a conversation nobody named still gets its first question's words the moment it's asked, then, once the first answer is in, the model gives it a short title of a few words from the question and the answer. The stream ends with a title event, so the list updates without a reload. A conversation you name when you start it or rename later is yours, and Lens leaves it alone; title_by on a conversation says which it is. Editing the first question of a conversation Lens titled titles it again. POST /api/v1/chats/{cid}/title and the MCP tool rename_chat (with a title, or none for Lens's) rename or retitle it. Settings → AI assistant → Title conversations (on) turns the model's titles off, and the first question's words stay.

How: chat.auto_title makes one small call (max_tokens 200, the reply cleaned of labels, quotes and Markdown) with the model for summaries (ai.compact_model) when set, else the chat's model, inside activity.scope("chat:<id>") so the ledger row names the conversation and never the answer (whose record keeps one total per answer). It runs after the answer's stream, on a thread the stream waits on for at most 10 seconds; a slower title is saved when it comes. The save is conditional (WHERE title_by = 'assistant'), so a rename during the call wins. title_by is a new nullable field on chat: null means from before this change and reads as before (untitled is Lens's, anything else is kept), so no upgrade step.

Motivation and Context

Adeel asked for chats to be titled automatically (project thread). Picks made on the way: the title waits for the first answer rather than the first question, since the answer says what the question was really about and the question's words already fill the gap; chat rooms, setup and automation conversations keep the names Lens gives them.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change; ! in the title)

Checklist:

  • The title is a Conventional Commit.
  • My code follows the code style of this project (make hooks checks it on commit).
  • I added or updated tests.
  • My change requires documentation updates, and I have updated docs/ accordingly.
  • Users need more than the title to understand it, so I wrote it under ## Unreleased in CHANGELOG.md.

Done questions (AGENTS.md)

  • Goal: delivers what the user wanted; merged on green CI.
  • Agents: title_by on GET /chats and GET /chats/{cid}, POST /chats/{cid}/title, PATCH /chats/{cid} with title; MCP rename_chat, and list_chats/read_chat show title_by.
  • Zero setup: on by default with the configured model; nothing new to install.
  • Fewest clicks: no clicks at all; the title appears on its own.
  • Configurable: Settings → AI assistant → Title conversations (ai.auto_title, on); not in the wizard.
  • Light: one short cloud call per conversation, with the cheaper model when set.
  • Private and secure: n/a, no new surface; the title call reads only the person's own question and answer, with the untrusted.neutral marking summaries use, and the prompt says not to follow instructions in quoted text.
  • Cost: one call of about 200 output tokens, model.chat row for chat:<id> in the ledger; activity.Call refuses it at the overall budget's hard limit, and the conversation keeps the first question's words.
  • User decides: a name you give is never overwritten; the default (on) only replaces the placeholder Lens already wrote.
  • Upgrades: new nullable field, read as before when null; no upgrade step.
  • Honest: changelog, README built list, docs; "refine later": the room/bridge conversations keep their names, and the title event isn't yet shown live in the web app (the list refetches on done, which is after the title is saved for a fast model; a slow one shows on the next refetch).
  • Tests, docs and generated files: test_chat_titles.py (7 tests), settings-web-search.test.tsx, docs/assistant.md#titles, docs/api.md, docs/mcp.md, docs/requirements.md; make openapi regenerated the schema and client.
  • Open standards; dependencies keep Lens MIT: n/a, no new dependency.
  • Reported with evidence in the thread.
  • Reuse: llm.chat, activity.scope, the compaction model setting and _said formatting, notebook.refine's cleaning idea.

Generated by Claude Code

A conversation nobody named gets its first question's words at once, then
a short title from the model after the first answer, written by the model
for summaries (ai.compact_model) when set, in one call counted for the
conversation in the activity ledger. A title someone gives is kept
(title_by), POST /chats/{cid}/title and the MCP tool rename_chat have Lens
title it again, and Settings › AI assistant › Title conversations turns it
off.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdR7CS12bmc7dcQFbdV4vn
@adeelahmad adeelahmad self-assigned this Oct 17, 2026
@adeelahmad
adeelahmad merged commit 17e3d21 into main Oct 18, 2026
11 checks passed
8000 @adeelahmad
adeelahmad deleted the claude/project-thread-aqomyd branch October 18, 2026 01:02
adeelahmad pushed a commit that referenced this pull request Oct 18, 2026
…and-in model titles conversations

Since #531 Lens asks the model for a conversation's title after its first
answer, and the stand-in model's one reply became every conversation's
title in the list beside Chat. chat-ask waits for the reply's words, found
them at once in that list, and ended before its own answer was saved, so
chat-regenerate and the journeys after it found a conversation with a
question and no answer.

The stand-in now answers the title prompt with the first question's words,
as a model would, and first-question and chat-ask wait for the reply inside
the newest answer, not anywhere on the page. The page survey counts a
page's controls again for a few seconds when it finds none, so a settings
page still loading isn't recorded with none.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B
adeelahmad added a commit that referenced this pull request Oct 18, 2026
…ing tools in a chat (#541)

* fix(chat): copy an answer in one click

An answer's Copy button opened a menu, so copying took two clicks. A click on Copy now copies it as Markdown (its
citations as footnotes, as before); the arrow beside it, as Regenerate has, holds Copy as Markdown, Copy text and
Export as Markdown, PDF or JSON.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B

* fix(chat): keep each tool change made while the one before is being saved

In a conversation, the Tools chip saved each change and then read the conversation back, and the next change was
made from what the page still held. Switching a group off and then turning on the limit before the first save came
back sent the old tools (Auto) with the limit, and the group came back on. Now each change shows at once, the
changes are saved in the order they were made, and the conversation is read back after the last one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B

* test(clicks): journeys for regenerating, copying, exporting and choosing tools in a chat

chat-regenerate (from Home: the conversation, Regenerate, back to the first version), chat-copy, chat-export-pdf (the
server's PDF downloaded, or the browser's print dialog counted outside the browser) and chat-tools (a group off and a
limit), on a computer and a phone. They run before cloud-model, which stays last since it points Lens away from the
stand-in model; the stand-in model now answers a regenerated question with "..., version 2." so the versions differ.
Both baselines are from full runs, so they also carry entity-merge and graph-merge, which they lacked.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B

* fix(clicks): the chat journeys wait for the answer itself, and the stand-in model titles conversations

Since #531 Lens asks the model for a conversation's title after its first
answer, and the stand-in model's one reply became every conversation's
title in the list beside Chat. chat-ask waits for the reply's words, found
them at once in that list, and ended before its own answer was saved, so
chat-regenerate and the journeys after it found a conversation with a
question and no answer.

The stand-in now answers the title prompt with the first question's words,
as a model would, and first-question and chat-ask wait for the reply inside
the newest answer, not anywhere on the page. The page survey counts a
page's controls again for a few seconds when it finds none, so a settings
page still loading isn't recorded with none.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B

* test(clicks): baselines from full runs with main merged in

Both devices, 37 of 37 journeys ok, every journey's count as before. The
per-page control counts move with main: Settings › AI assistant has the
auto title switch, Settings › Sensors two more controls.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HXPWXcSCQb9hWPr7xkZu7B

---------

Co-authored-by: Adeel Ahmad <noreply@anthropic.com>
adeelahmad added a commit that referenced this pull request Oct 18, 2026
…ine (#538)

* feat(voice): read answers and podcasts aloud with Kokoro on this machine

Kokoro v1.0 (hexgrad's Kokoro-82M, Apache-2.0) as a text-to-speech provider, voice.tts_provider "kokoro": spoken
answers and podcast hosts are read on the server's own CPU through kokoro-onnx on ONNX Runtime, in any of its 54
voices across nine languages, with nothing sent anywhere and nothing billed. It's opt-in and fetched once it's picked:
the `kokoro` extra (espeak-ng and phonemizer are GPL-3.0, so they stay out of the default install), and the model
(voice.kokoro_model: fp32, fp16 or int8) and voices files from kokoro-onnx's release, checked against their SHA-256.
Settings → AI assistant → Voice offers it with the model size and the voice picker; a podcast's second host gets a
voice in the first host's language. kokoro.yml fetches and reads with each model on x64 and arm64.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzm4YHdHn2qZy7aXer3KeH

* test(settings): carry ai.auto_title in the Kokoro settings fixture

main's conversation titles (#531) added ai.auto_title to the section the Kokoro settings test renders, so its fixture
names it too; without it Save sent an ai update beside the voice one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzm4YHdHn2qZy7aXer3KeH

* docs(threat-model): count the Kokoro row in the JSON companion

The threat model workflow's validate job wants a change to threat-model.md to change a companion too (#507): the Kokoro row is documented, so the JSON's documented count goes up by one, as #518 did for Moondream.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzm4YHdHn2qZy7aXer3KeH

* fix(voice): offer Kokoro in full precision and 8-bit only

On GitHub's arm64 runner, ONNX Runtime's CPU provider returned an empty waveform for kokoro-onnx's fp16 export, and kokoro-onnx's pause insertion then failed on a zero-size array. On x64 it was no faster than fp32, so voice.kokoro_model is now fp32 (the default) or int8, and the fp16 file is no longer pinned.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzm4YHdHn2qZy7aXer3KeH

* test(settings): carry the research section in the Kokoro settings fixture

Main's deep research (#517) added a research section to Settings › AI assistant, so the fixture carries its defaults and Save sends only the voice change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzm4YHdHn2qZy7aXer3KeH

* test(settings): carry ai.compact_at in the Kokoro settings fixture

Main added ai.compact_at (summarise long conversations early), so the fixture carries its default and Save sends only the voice change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzm4YHdHn2qZy7aXer3KeH

* docs(readme): list answers about Lens itself as built, now that #400 is released

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzm4YHdHn2qZy7aXer3KeH

* docs(changelog): name the Kokoro fragment after its pull request

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzm4YHdHn2qZy7aXer3KeH

* docs(threat-model): count the Kokoro entries against main's newest model

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzm4YHdHn2qZy7aXer3KeH

* docs(readme): take Kokoro out of In progress, now that it's built

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dzm4YHdHn2qZy7aXer3KeH

---------

Co-authored-by: Adeel Ahmad <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0