Backend: - Chat, Message, ContextFile models + Alembic migration - Chat CRUD with per-user limit enforcement (max_chats) - SSE streaming endpoint: saves user message, streams Claude response, saves assistant message with token usage metadata - Context assembly: primary context file + conversation history - Admin context CRUD (GET/PUT with version tracking) - Anthropic SDK integration with async streaming - Chat ownership isolation (users can't access each other's chats) Frontend: - Chat page with sidebar chat list + main chat window - Real-time SSE streaming via fetch + ReadableStream - Message bubbles with Markdown rendering (react-markdown) - Auto-growing message input (Enter to send, Shift+Enter newline) - Zustand chat store for streaming state management - Admin primary context editor with unsaved changes warning - Updated routing: /chat, /chat/:chatId, /admin/context - Enabled Chat and Admin sidebar navigation - English + Russian translations for all new UI Infrastructure: - nginx: disabled proxy buffering for SSE support - Added ANTHROPIC_API_KEY and CLAUDE_MODEL to config Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
12 KiB
Phase 2: Chat & AI Core — Subplan
Goal
Deliver a working AI chat system where users can create conversations, send messages, and receive streamed Claude API responses via SSE, with admin-editable primary context and per-user chat limits enforced.
Prerequisites
- Phase 1 completed: auth endpoints, frontend shell with login/register/dashboard
- Anthropic API key available (
ANTHROPIC_API_KEYenv var)
Database Schema (Phase 2)
chats table
| Column | Type | Constraints |
|---|---|---|
| id | UUID | PK, default uuid4 (inherited from Base) |
| user_id | UUID | FK -> users.id ON DELETE CASCADE, NOT NULL, indexed |
| title | VARCHAR(255) | NOT NULL, default 'New Chat' |
| skill_id | UUID | NULL (unused until Phase 3, added now for schema stability) |
| is_archived | BOOLEAN | NOT NULL, default false |
| created_at | TIMESTAMPTZ | inherited from Base |
| updated_at | TIMESTAMPTZ | NOT NULL, default now(), onupdate now() |
messages table
| Column | Type | Constraints |
|---|---|---|
| id | UUID | PK, default uuid4 (inherited from Base) |
| chat_id | UUID | FK -> chats.id ON DELETE CASCADE, NOT NULL, indexed |
| role | VARCHAR(20) | NOT NULL, CHECK IN ('user','assistant','system','tool') |
| content | TEXT | NOT NULL |
| metadata | JSONB | NULL (token counts, model info) |
| created_at | TIMESTAMPTZ | inherited from Base |
context_files table
| Column | Type | Constraints |
|---|---|---|
| id | UUID | PK, default uuid4 (inherited from Base) |
| type | VARCHAR(20) | NOT NULL, CHECK IN ('primary','personal') |
| user_id | UUID | FK -> users.id ON DELETE CASCADE, NULL (NULL for primary) |
| content | TEXT | NOT NULL, default '' |
| version | INTEGER | NOT NULL, default 1 |
| updated_by | UUID | FK -> users.id ON DELETE SET NULL, NULL |
| created_at | TIMESTAMPTZ | inherited from Base |
| updated_at | TIMESTAMPTZ | NOT NULL, default now(), onupdate now() |
UNIQUE constraint: (type, user_id)
API Endpoints (Phase 2)
Chat CRUD — /api/v1/chats/
| Method | Path | Request | Response | Notes |
|---|---|---|---|---|
| POST | / |
{ title?: string } |
ChatResponse 201 |
Enforces user.max_chats on non-archived chats |
| GET | / |
Query: archived?: bool |
ChatListResponse |
User's chats, ordered by updated_at desc |
| GET | /{chat_id} |
— | ChatResponse |
404 if not owned |
| PATCH | /{chat_id} |
{ title?, is_archived? } |
ChatResponse |
Ownership check |
| DELETE | /{chat_id} |
— | 204 | Cascades messages |
Messages — /api/v1/chats/{chat_id}/messages
| Method | Path | Request | Response | Notes |
|---|---|---|---|---|
| GET | / |
Query: limit?=50, before?: UUID |
MessageListResponse |
Cursor-paginated |
| POST | / |
{ content: string } |
SSE stream | Saves user msg, streams Claude response, saves assistant msg |
Admin Context — /api/v1/admin/context
| Method | Path | Request | Response | Notes |
|---|---|---|---|---|
| GET | / |
— | ContextFileResponse |
Primary context content + version |
| PUT | / |
{ content: string } |
ContextFileResponse |
Upsert, increments version |
SSE Event Format
event: message_start
data: {"message_id": "<uuid>"}
event: content_delta
data: {"delta": "partial text..."}
event: message_end
data: {"message_id": "<uuid>", "metadata": {"model": "...", "input_tokens": N, "output_tokens": N}}
event: error
data: {"detail": "Error description"}
Context Assembly (Phase 2)
- Primary context file (admin-edited) → Claude
systemparameter - Conversation history → Claude
messagesarray - Current user message → appended as final user message
Hardcoded default system prompt used when no primary context file exists.
Tasks
A. Backend Database & Models (Tasks 1–4)
-
**- [x] A1. Add
ANTHROPIC_API_KEYandCLAUDE_MODEL(defaultclaude-sonnet-4-20250514) tobackend/app/config.py. Addanthropictobackend/pyproject.toml. Add vars to.env.example. -
**- [x] A2. Create
backend/app/models/chat.py: Chat model per schema. Relationships:messages,user. Addchatsrelationship on User model. -
**- [x] A3. Create
backend/app/models/message.py: Message model per schema. Relationship:chat. -
**- [x] A4. Create
backend/app/models/context_file.py: ContextFile model per schema. UNIQUE(type, user_id). Updatebackend/app/models/__init__.py. Create Alembic migration002_create_chats_messages_context_files.py.
B. Backend Schemas (Task 5)
- **- [x] B5. Create
backend/app/schemas/chat.py:CreateChatRequest,UpdateChatRequest,SendMessageRequest,ChatResponse,ChatListResponse,MessageResponse,MessageListResponse,ContextFileResponse,UpdateContextRequest.
C. Backend Services (Tasks 6–8)
-
**- [x] C6. Create
backend/app/services/chat_service.py:create_chat,get_user_chats,get_chat,update_chat,delete_chat,get_messages,save_message. Chat limit enforcement: count non-archived chats vsuser.max_chats. -
**- [x] C7. Create
backend/app/services/ai_service.py:assemble_context(loads primary context + history),stream_ai_response(async generator: saves user msg → calls Claude streaming API → yields SSE events → saves assistant msg). Usesanthropic.AsyncAnthropic().messages.stream(). -
**- [x] C8. Create
backend/app/services/context_service.py:get_primary_context,upsert_primary_context. Default system prompt constant.
D. Backend API Endpoints (Tasks 9–11)
-
**- [x] D9. Create
backend/app/api/v1/chats.py: Chat CRUD + message list + SSE streaming endpoint. ReturnsStreamingResponse(media_type="text/event-stream")with no-cache headers. -
**- [x] D10. Create
backend/app/api/v1/admin.py:GET /admin/context,PUT /admin/context. Protected byrequire_admin. -
**- [x] D11. Register routers in
backend/app/api/v1/router.py. Addproxy_buffering off;to nginx/api/location for SSE.
E. Frontend API & Store (Tasks 12–14)
-
**- [x] E12. Create
frontend/src/api/chats.ts: typed API functions +sendMessageusingfetch()+ReadableStreamfor SSE parsing. -
**- [x] E13. Create
frontend/src/api/admin.ts:getPrimaryContext(),updatePrimaryContext(content). -
**- [x] E14. Create
frontend/src/stores/chat-store.ts(Zustand, not persisted): chats, currentChatId, messages, isStreaming, streamingContent, actions.
F. Frontend Chat Components (Tasks 15–20)
-
**- [x] F15. Create
frontend/src/components/chat/chat-list.tsx: chat sidebar with list, new chat button, archive/delete actions. TanStack Query for fetching. -
**- [x] F16. Create
frontend/src/components/chat/message-bubble.tsx: role-based styling (user right/blue, assistant left/gray). Markdown rendering for assistant messages (react-markdown+remark-gfm). Add deps topackage.json. -
**- [x] F17. Create
frontend/src/components/chat/message-input.tsx: auto-growing textarea, Enter to send (Shift+Enter newline), disabled while streaming, send icon button. -
**- [x] F18. Create
frontend/src/components/chat/chat-window.tsx: message list (auto-scroll) + input. Loads messages via TanStack Query. Handles streaming flow. Empty state. -
**- [x] F19. Create
frontend/src/pages/chat.tsx: chat list panel (left) + chat window (right). Routes:/chatand/chat/:chatId. -
**- [x] F20. Create
frontend/src/hooks/use-chat.ts: encapsulates TanStack queries, mutations, SSE send flow with store updates, abort on unmount.
G. Frontend Admin Context Editor (Tasks 21–22)
-
**- [x] G21. Create
frontend/src/components/admin/context-editor.tsx: textarea editor, load/save, version display, unsaved changes warning. -
**- [x] G22. Create
frontend/src/pages/admin/context.tsx: page wrapper, admin role protected.
H. Frontend Routing & Navigation (Tasks 23–24)
-
**- [x] H23. Update
frontend/src/routes.tsx: add/chat,/chat/:chatId,/admin/contextroutes. -
**- [x] H24. Update
frontend/src/components/layout/sidebar.tsx: enable Chats and Admin nav items.
I. i18n (Task 25)
- **- [x] I25. Update
en/translation.jsonandru/translation.jsonwith chat and admin keys.
J. Backend Tests (Task 26)
- **- [x] J26. Create
backend/tests/test_chats.py: chat CRUD, limit enforcement, message pagination, SSE streaming format, admin context CRUD, ownership isolation.
Files to Create
| File | Purpose |
|---|---|
backend/app/models/chat.py |
Chat ORM model |
backend/app/models/message.py |
Message ORM model |
backend/app/models/context_file.py |
ContextFile ORM model |
backend/alembic/versions/002_create_chats_messages_context_files.py |
Migration |
backend/app/schemas/chat.py |
Request/response schemas |
backend/app/services/chat_service.py |
Chat + message business logic |
backend/app/services/ai_service.py |
Claude API integration + SSE streaming |
backend/app/services/context_service.py |
Primary context CRUD |
backend/app/api/v1/chats.py |
Chat + message endpoints |
backend/app/api/v1/admin.py |
Admin context endpoints |
backend/tests/test_chats.py |
Tests |
frontend/src/api/chats.ts |
Chat API client + SSE consumer |
frontend/src/api/admin.ts |
Admin API client |
frontend/src/stores/chat-store.ts |
Chat UI state |
frontend/src/hooks/use-chat.ts |
Chat data + streaming hook |
frontend/src/components/chat/chat-list.tsx |
Chat list sidebar |
frontend/src/components/chat/message-bubble.tsx |
Message display |
frontend/src/components/chat/message-input.tsx |
Message input |
frontend/src/components/chat/chat-window.tsx |
Main chat area |
frontend/src/pages/chat.tsx |
Chat page |
frontend/src/components/admin/context-editor.tsx |
Context editor |
frontend/src/pages/admin/context.tsx |
Admin context page |
Files to Modify
| File | Change |
|---|---|
backend/pyproject.toml |
Add anthropic dependency |
backend/app/config.py |
Add ANTHROPIC_API_KEY, CLAUDE_MODEL |
backend/app/models/__init__.py |
Import Chat, Message, ContextFile |
backend/app/models/user.py |
Add chats relationship |
backend/app/api/v1/router.py |
Include chats + admin routers |
nginx/nginx.conf |
Add proxy_buffering off; for SSE |
.env.example |
Add ANTHROPIC_API_KEY, CLAUDE_MODEL |
frontend/package.json |
Add react-markdown, remark-gfm |
frontend/src/routes.tsx |
Add chat + admin routes |
frontend/src/components/layout/sidebar.tsx |
Enable chat + admin nav |
frontend/public/locales/en/translation.json |
Chat + admin keys |
frontend/public/locales/ru/translation.json |
Chat + admin keys |
Acceptance Criteria
- Migration creates
chats,messages,context_filestables correctly POST /chats/enforces max_chats limit (403 when exceeded)- Chat CRUD works with ownership isolation (user A can't access user B's chats)
POST /chats/{id}/messagesreturns SSE stream withmessage_start,content_delta,message_endevents- Both user and assistant messages are persisted after streaming completes
- Context assembly includes primary context file as system prompt
- Admin context GET/PUT works with version incrementing, protected by admin role
- Frontend:
/chatshows chat list, create/select/archive/delete chats - Frontend: sending a message streams the AI response character-by-character
- Frontend: admin can edit the primary context at
/admin/context - Sidebar shows enabled Chat and Admin navigation
- All new UI text works in both English and Russian
- All backend tests pass
Status
COMPLETED — All code written. TypeScript compiles clean. Vite builds successfully. Docker smoke test deferred (Docker not installed).