feat: listen mode + volume knob in chat; briefing discuss auto-send; fix LLM proactive note search

- ChatView: listen mode toggle (auto-reads new responses via TTS), volume popup
  with range slider persisted per-device in localStorage via GainNode
- useVoiceAudio: shared module-level _volume ref with localStorage persistence,
  GainNode for volume control, exported setVoiceVolume()
- tts.py: pre-warm all Kokoro voices at pipeline load to eliminate HuggingFace
  HEAD requests at synthesis time (reduces TTS latency)
- BriefingView: discuss article button now auto-sends instead of just filling input;
  prompt capped to 15 sentences; send() accepts optional overrideText
- llm.py: instruct LLM not to proactively search notes or comment on note absence

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-31 16:52:11 -04:00
parent ab397e78f3
commit baeb0b14e5
5 changed files with 169 additions and 22 deletions
+2 -1
View File
@@ -507,7 +507,8 @@ async def build_context(
"Use search_notes for conceptual/semantic queries — e.g. 'what notes do I have about X' or "
"'find notes related to Y' — it uses semantic understanding to find thematically related content "
"even when exact words don't match. Pass project= to scope the search to a specific project. "
"Use delete_note / delete_task only when explicitly asked to delete — these require confirmation."
"Use delete_note / delete_task only when explicitly asked to delete — these require confirmation. "
"Never proactively search notes or comment on the absence of notes/context unless the user explicitly asks about their notes."
)
tool_guidance = "\n".join(tool_lines)
+12 -5
View File
@@ -46,11 +46,18 @@ async def load_tts_model() -> None:
logger.info("Loading Kokoro TTS pipeline...")
loop = asyncio.get_running_loop()
_pipeline = await loop.run_in_executor(
None,
lambda: KPipeline(lang_code="a"), # "a" = American English
)
logger.info("Kokoro TTS pipeline loaded")
def _load_and_warm():
p = KPipeline(lang_code="a") # "a" = American English
# Pre-load all voice tensors so synthesis never hits HuggingFace at request time
for v in _VOICES:
try:
p.load_voice(v["id"])
except Exception:
pass
return p
_pipeline = await loop.run_in_executor(None, _load_and_warm)
logger.info("Kokoro TTS pipeline loaded and voices pre-warmed")
except Exception:
_load_error = "Failed to load Kokoro TTS pipeline"
logger.exception(_load_error)