9 Commits
Author SHA1 Message Date
gurixandClaude Sonnet 4.6 91ba940951 update language hint to reflect any-language support
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-23 22:52:57 +01:00
gurixandClaude Sonnet 4.6 602b8eddc3 introduce separate SIMPLE_MODEL config for lightweight tasks
Add a distinct simple_model field alongside ai_model so operators can
route cheap, simple tasks (e.g. email-label translation) to a low-cost
model while keeping the strong model for parent conversations. The two
models can be from different providers (e.g. Gemini + Haiku). If
SIMPLE_MODEL is unset, falls back to AI_MODEL with a warning.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-23 22:18:28 +01:00
Claude b2e04fa07b implement registration-confirmation-email
- Add qrbill and pillow dependencies for Swiss QR-bill PNG generation
- Add _STRINGS_DE / _STRINGS_EN bilingual string tables to AdminNotifier
- Add _generate_qr_bill_png(): Swiss QR code with cross overlay (PNG bytes)
- Add _build_parent_html(): HTML confirmation with inline cid:qrbill image
- Add _build_parent_text(): plain-text fallback with IBAN in full
- Add notify_parent(): multipart/mixed MIME email to parent on completion
- Persist metadata.language in _build_record() in json_store.py
- Wire notify_parent() into src/agent/core.py and chat_app.py completion events
- Add 9 tests for notify_parent, _generate_qr_bill_png, language fallback
- Update tasks.md: tasks 1–5 complete; task 6 (smoke test) remains manual

112 tests passing.

https://claude.ai/code/session_01LjjK7RjKVnC8bETtccfgna
2026-02-22 21:23:32 +00:00
Claude 1bd11e6acd feat: inject today's date into system prompt and add extended thinking support
Fixes age validation errors caused by the LLM not knowing the current date.

Changes:
- prompts.py: inject date.today() at the top of both system prompts so the
  LLM can accurately calculate a child's age from their date of birth
- llm.py: add optional thinking_budget parameter to complete(); when set,
  passes thinking={"type": "enabled", "budget_tokens": N} to litellm and
  raises max_tokens to thinking_budget + 4096 (Anthropic models only)
- config.py: add thinking_budget field, read from THINKING_BUDGET env var
- .env.example: document the THINKING_BUDGET option
- core.py: pass thinking_budget through to llm.complete()
- main.py: pass thinking_budget when constructing EmailAgent
- chat_app.py: switch from stream_complete to asyncio.to_thread(complete)
  so extended thinking works and so only the reply field is shown to
  the parent (not the raw JSON wrapper)

To enable extended thinking set THINKING_BUDGET=8000 in .env.

https://claude.ai/code/session_01SUWzMzFvSfWiHXA2p6rPg9
2026-02-22 19:57:32 +00:00
gurixandClaude Sonnet 4.6 13cb35111d fix(chat): simplify user label to German; expose host via env var
- Change user message author label from "Du / You" to "Du" (German-first)
- Add CHAINLIT_HOST env var to .env.example so the server listens on all
  interfaces and is reachable from outside localhost

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 20:45:33 +01:00
Claude 72189d2b7b fix(chat): use native async LLM call to prevent session reset on message submit
The previous implementation used asyncio.to_thread(llm.complete) to avoid
blocking the event loop, but Chainlit's contextvars context is not reliably
propagated across thread boundaries, causing the session to reset and clear
the message history on each user submission.

Changes:
- Add llm.acomplete() using litellm.acompletion() (native coroutine)
- Replace asyncio.to_thread() in on_message with await llm.acomplete()
- Store the welcome message in state.messages so it is replayed on reconnect
- Persist state to cl.user_session immediately after appending the user's
  message (before the LLM call) so reconnect detection has the latest history
- Add pytest-asyncio dev dependency and asyncio_mode = "auto" config
- Add 6 async tests for acomplete() in tests/test_llm.py

https://claude.ai/code/session_01SUWzMzFvSfWiHXA2p6rPg9
2026-02-22 12:38:20 +00:00
Claude 981948c106 fix(chat): restore message history on reconnect; fix event loop blocking
Two related issues caused the screen to clear after each answer:

1. Blocking event loop: the synchronous llm.stream_complete() for-loop
   was running directly in the async on_message handler, blocking the
   event loop for the full LLM response duration. This caused the
   WebSocket to time out and Chainlit to reconnect after each message.

   Fix: replace stream_complete() with asyncio.to_thread(llm.complete)
   so the network-bound LLM call runs in a thread pool and the event
   loop (and WebSocket) stay alive throughout.

2. Reconnect resets history: on_chat_start always created a fresh empty
   state and sent the welcome message, even on WebSocket reconnections
   where cl.user_session still held the existing conversation.

   Fix: if cl.user_session["state"] is already present, replay the
   stored message history into the new thread instead of starting fresh.

https://claude.ai/code/session_01SUWzMzFvSfWiHXA2p6rPg9
2026-02-22 08:30:52 +00:00
Claude 808be0185f fix(chat): show only reply text to user, not raw JSON
The LLM returns a structured JSON object. Previously, raw tokens were
streamed directly to the user via msg.stream_token(), causing the full
JSON blob to appear in the chat.

Fix: collect all chunks silently, parse the JSON, then send only the
reply field with cl.Message(content=reply_text).send(). The JSON fields
(updates, next_step, registration_complete, language, intent) are still
processed in the background — parents never see them.

https://claude.ai/code/session_01SUWzMzFvSfWiHXA2p6rPg9
2026-02-22 08:16:12 +00:00
Claude 9fdbe341be feat(chat): implement web chat interface with accessibility
Core implementation:
- chat_app.py: Chainlit entry point with @cl.on_chat_start,
  @cl.on_message (streaming via llm.stream_complete), @cl.on_chat_end
  Reuses Config, KnowledgeBase, ConversationStore, AdminNotifier from src/
  Handles registration completion, post-completion updates, new-child flow

- src/llm.py: add stream_complete() generator (litellm stream=True)
  alongside existing complete(); tests added in tests/test_llm.py

- src/agent/response_parser.py: extract parse_llm_response(),
  apply_updates(), fallback_message() from EmailAgent into shared module
  EmailAgent now delegates to these functions (no logic change)

Chainlit configuration:
- chainlit.toml: telemetry off, German default, custom CSS + JS paths
- chainlit.md: German welcome page with playgroup info

Accessibility (WCAG 2.1 AA):
- public/custom.css: contrast overrides (≥4.5:1), prefers-reduced-motion
  (static "…" replaces animated dots), skip link styles, 100dvh fix
- public/accessibility.js: MutationObserver injects aria-live="polite"
  on message list, focus management after agent replies, skip link element

Other:
- .gitignore: add .chainlit/ (Chainlit runtime, auto-generated)
- openspec/config.yaml: populate context field with tech stack
- openspec/changes/implement-web-chat/tasks.md: mark completed tasks

95 tests pass.

https://claude.ai/code/session_01SUWzMzFvSfWiHXA2p6rPg9
2026-02-22 07:42:29 +00:00