Files
personal-ai-assistant/plans/phase-2-chat-ai.md
dolgolyov.alexei 70469beef8 Phase 2: Chat & AI Core — Claude API streaming, chat UI, admin context
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>
2026-03-19 12:38:30 +03:00

253 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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_KEY` env 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)
1. Primary context file (admin-edited) → Claude `system` parameter
2. Conversation history → Claude `messages` array
3. 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 14)
- [x] **- [x] **A1.** Add `ANTHROPIC_API_KEY` and `CLAUDE_MODEL` (default `claude-sonnet-4-20250514`) to `backend/app/config.py`. Add `anthropic` to `backend/pyproject.toml`. Add vars to `.env.example`.
- [x] **- [x] **A2.** Create `backend/app/models/chat.py`: Chat model per schema. Relationships: `messages`, `user`. Add `chats` relationship on User model.
- [x] **- [x] **A3.** Create `backend/app/models/message.py`: Message model per schema. Relationship: `chat`.
- [x] **- [x] **A4.** Create `backend/app/models/context_file.py`: ContextFile model per schema. UNIQUE(type, user_id). Update `backend/app/models/__init__.py`. Create Alembic migration `002_create_chats_messages_context_files.py`.
### B. Backend Schemas (Task 5)
- [x] **- [x] **B5.** Create `backend/app/schemas/chat.py`: `CreateChatRequest`, `UpdateChatRequest`, `SendMessageRequest`, `ChatResponse`, `ChatListResponse`, `MessageResponse`, `MessageListResponse`, `ContextFileResponse`, `UpdateContextRequest`.
### C. Backend Services (Tasks 68)
- [x] **- [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 vs `user.max_chats`.
- [x] **- [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). Uses `anthropic.AsyncAnthropic().messages.stream()`.
- [x] **- [x] **C8.** Create `backend/app/services/context_service.py`: `get_primary_context`, `upsert_primary_context`. Default system prompt constant.
### D. Backend API Endpoints (Tasks 911)
- [x] **- [x] **D9.** Create `backend/app/api/v1/chats.py`: Chat CRUD + message list + SSE streaming endpoint. Returns `StreamingResponse(media_type="text/event-stream")` with no-cache headers.
- [x] **- [x] **D10.** Create `backend/app/api/v1/admin.py`: `GET /admin/context`, `PUT /admin/context`. Protected by `require_admin`.
- [x] **- [x] **D11.** Register routers in `backend/app/api/v1/router.py`. Add `proxy_buffering off;` to nginx `/api/` location for SSE.
### E. Frontend API & Store (Tasks 1214)
- [x] **- [x] **E12.** Create `frontend/src/api/chats.ts`: typed API functions + `sendMessage` using `fetch()` + `ReadableStream` for SSE parsing.
- [x] **- [x] **E13.** Create `frontend/src/api/admin.ts`: `getPrimaryContext()`, `updatePrimaryContext(content)`.
- [x] **- [x] **E14.** Create `frontend/src/stores/chat-store.ts` (Zustand, not persisted): chats, currentChatId, messages, isStreaming, streamingContent, actions.
### F. Frontend Chat Components (Tasks 1520)
- [x] **- [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] **- [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 to `package.json`.
- [x] **- [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] **- [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] **- [x] **F19.** Create `frontend/src/pages/chat.tsx`: chat list panel (left) + chat window (right). Routes: `/chat` and `/chat/:chatId`.
- [x] **- [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 2122)
- [x] **- [x] **G21.** Create `frontend/src/components/admin/context-editor.tsx`: textarea editor, load/save, version display, unsaved changes warning.
- [x] **- [x] **G22.** Create `frontend/src/pages/admin/context.tsx`: page wrapper, admin role protected.
### H. Frontend Routing & Navigation (Tasks 2324)
- [x] **- [x] **H23.** Update `frontend/src/routes.tsx`: add `/chat`, `/chat/:chatId`, `/admin/context` routes.
- [x] **- [x] **H24.** Update `frontend/src/components/layout/sidebar.tsx`: enable Chats and Admin nav items.
### I. i18n (Task 25)
- [x] **- [x] **I25.** Update `en/translation.json` and `ru/translation.json` with chat and admin keys.
### J. Backend Tests (Task 26)
- [x] **- [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
1. Migration creates `chats`, `messages`, `context_files` tables correctly
2. `POST /chats/` enforces max_chats limit (403 when exceeded)
3. Chat CRUD works with ownership isolation (user A can't access user B's chats)
4. `POST /chats/{id}/messages` returns SSE stream with `message_start`, `content_delta`, `message_end` events
5. Both user and assistant messages are persisted after streaming completes
6. Context assembly includes primary context file as system prompt
7. Admin context GET/PUT works with version incrementing, protected by admin role
8. Frontend: `/chat` shows chat list, create/select/archive/delete chats
9. Frontend: sending a message streams the AI response character-by-character
10. Frontend: admin can edit the primary context at `/admin/context`
11. Sidebar shows enabled Chat and Admin navigation
12. All new UI text works in both English and Russian
13. All backend tests pass
---
## Status
**COMPLETED** — All code written. TypeScript compiles clean. Vite builds successfully. Docker smoke test deferred (Docker not installed).