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

12 KiB
Raw Blame History

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] 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] A2. Create backend/app/models/chat.py: Chat model per schema. Relationships: messages, user. Add chats relationship 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). Update backend/app/models/__init__.py. Create Alembic migration 002_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 68)

  • **- [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] 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] 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] 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] D10. Create backend/app/api/v1/admin.py: GET /admin/context, PUT /admin/context. Protected by require_admin.

  • **- [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] E12. Create frontend/src/api/chats.ts: typed API functions + sendMessage using fetch() + ReadableStream for 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 1520)

  • **- [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 to package.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: /chat and /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 2122)

  • **- [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 2324)

  • **- [x] H23. Update frontend/src/routes.tsx: add /chat, /chat/:chatId, /admin/context routes.

  • **- [x] H24. Update frontend/src/components/layout/sidebar.tsx: enable Chats and Admin nav items.

I. i18n (Task 25)

  • **- [x] I25. Update en/translation.json and ru/translation.json with 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

  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).