Add context windows, web search, streamed answers, overlay move/resize; overhaul docs

- Context windows (contexts/*.yaml) scope answers to a defined domain
- Optional DuckDuckGo web search behind ai.web_search.enabled (default off)
- Stream partial answers into the overlay at first-token time
- Default Whisper to local base.en (~9x faster); offline model loading
- Priority-ordered loopback detection (BlackHole > Teams device)
- Overlay: drag interior to move, edges to resize
- Stop tracking model binaries (models/ is gitignored)
- README/CLAUDE.md overhaul + tracked config.example.yaml

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
spiro-alvin-nyasimi
2026-07-03 11:22:34 +03:00
parent 138580475c
commit 08118cc650
13 changed files with 1095 additions and 97 deletions

27
main.py
View File

@@ -5,6 +5,7 @@ Meeting Assistant - Real-time AI Copilot for Meetings
import sys
import os
import time
import yaml
import threading
import logging
@@ -255,16 +256,29 @@ class MeetingAssistant:
try:
context = self.context_manager.get_context()
result = self.ai_engine.answer_question(question, context, source)
# Stream the answer into the overlay as it generates: first words
# appear at first-token time instead of after the full generation.
asked_by = speaker or ("You" if source == "microphone" else "Them")
shown_q = f"({asked_by}) {question}"
suggested_early = self.ai_engine.is_suggested(question)
t_start = time.time()
first_shown = [False]
def on_partial(text_so_far):
if not first_shown[0]:
first_shown[0] = True
print(f" ⚡ first words on screen after {time.time() - t_start:.1f}s")
self.overlay.show_answer(text_so_far + "", shown_q, suggested_early)
result = self.ai_engine.answer_question(
question, context, source, on_partial=on_partial)
if not result or not result.get("text"):
return # interrupted or empty
answer = result["text"]
suggested = result.get("suggested", False)
asked_by = speaker or ("You" if source == "microphone" else "Them")
shown_q = f"({asked_by}) {question}"
self.overlay.show_answer(answer, shown_q, suggested)
self.logger.info(f"Q [{asked_by}]: {question}")
self.logger.info(f"A: {answer}")
@@ -305,7 +319,10 @@ class MeetingAssistant:
self.ai_engine.interrupt()
self.answering = True
try:
answer = self.ai_engine.answer_from_image(image_path)
answer = self.ai_engine.answer_from_image(
image_path,
on_partial=lambda t: self.overlay.show_answer(
t + "", "Screen question", suggested=False))
if not answer:
return
self.overlay.show_answer(answer, "Screen question", suggested=False)