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
This commit is contained in:
+26
@@ -1,5 +1,7 @@
|
||||
"""LLM completion via litellm — supports any provider with a single call."""
|
||||
|
||||
from collections.abc import Generator
|
||||
|
||||
import litellm
|
||||
|
||||
|
||||
@@ -20,3 +22,27 @@ def complete(model: str, system: str, messages: list) -> str:
|
||||
api_messages += [{"role": m.role, "content": m.content} for m in messages]
|
||||
response = litellm.completion(model=model, messages=api_messages, max_tokens=2048)
|
||||
return response.choices[0].message.content
|
||||
|
||||
|
||||
def stream_complete(
|
||||
model: str, system: str, messages: list
|
||||
) -> Generator[str, None, None]:
|
||||
"""Call any LLM with streaming and yield text chunks as they arrive.
|
||||
|
||||
Args:
|
||||
model: litellm model string (same format as ``complete``).
|
||||
system: System prompt text.
|
||||
messages: List of objects with .role and .content attributes.
|
||||
|
||||
Yields:
|
||||
Non-empty text chunks from the model's streamed response.
|
||||
"""
|
||||
api_messages = [{"role": "system", "content": system}]
|
||||
api_messages += [{"role": m.role, "content": m.content} for m in messages]
|
||||
response = litellm.completion(
|
||||
model=model, messages=api_messages, max_tokens=2048, stream=True
|
||||
)
|
||||
for chunk in response:
|
||||
delta = chunk.choices[0].delta.content
|
||||
if delta:
|
||||
yield delta
|
||||
|
||||
Reference in New Issue
Block a user