ARCHITECTURE:
- Replace SSE stream (breaks on page reload) with DB-backed background sessions
- Go Gateway runs orchestrator in detached goroutine using context.Background()
(survives HTTP disconnect, page reload, and laptop sleep/shutdown)
- Every SSE event (thinking/tool_call/delta/done/error) is persisted to chatEvents table
- Session lifecycle stored in chatSessions table (running→done/error)
- Frontend polls GET /api/orchestrator/getEvents every 1.5 s until status=done
DB CHANGES:
- Migration 0005_chat_sessions.sql: chatSessions + chatEvents tables
- schema.ts: TypeScript types for chatSessions and chatEvents
- db.go: ChatSessionRow and ChatEventRow structs with proper json tags (camelCase)
- db.go: CreateSession, AppendEvent, MarkSessionDone, GetSession, GetEvents, GetRecentSessions
GO GATEWAY:
- handlers.go: StartChatSession — creates DB session, launches goroutine, returns {sessionId} immediately
- handlers.go: GetChatSession, GetChatEvents, ListChatSessions handlers
- main.go: routes POST /api/chat/session, GET /api/chat/session/{id}, GET /api/chat/session/{id}/events, GET /api/chat/sessions
- JSON tags added to ChatSessionRow/ChatEventRow so Go returns camelCase to frontend
NODE.JS SERVER:
- gateway-proxy.ts: startChatSession, getChatSession, getChatEvents, listChatSessions functions
- routers.ts: orchestrator.startSession, .getSession, .getEvents, .listSessions tRPC procedures
FRONTEND:
- chatStore.ts: completely rewritten — uses background sessions + localStorage-based polling resume
* send() calls orchestrator.startSession via tRPC (returns immediately)
* Stores sessionId in localStorage (goclaw-pending-sessions)
* Polls getEvents every 1.5 s, applies events to UI incrementally
* On page reload: _resumePendingSessions() checks pending sessions and resumes polling
* cancel() stops all active polls
- chatStore.ts: conversations persisted to localStorage (v3 key, survives page reload)
- Chat.tsx: updated status texts to 'Фоновая обработка…', 'Обработка в фоне…'
VERIFIED:
- POST /api/chat/session → {sessionId, status:'running'} in <100ms
- Poll events → thinking, delta('Привет!'), done after ~2s
- chatSessions table has rows with status=done, model, totalTokens
- Cyrillic stored correctly in UTF-8
- JSON fields are camelCase: id, sessionId, seq, eventType, content, toolName...
37 lines
1.3 KiB
SQL
37 lines
1.3 KiB
SQL
-- chatSessions: one row per chat request, survives page reloads
|
|
CREATE TABLE IF NOT EXISTS chatSessions (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
sessionId VARCHAR(64) NOT NULL UNIQUE,
|
|
agentId INT NOT NULL DEFAULT 1,
|
|
status ENUM('running','done','error') NOT NULL DEFAULT 'running',
|
|
userMessage TEXT NOT NULL,
|
|
finalResponse TEXT,
|
|
model VARCHAR(128),
|
|
totalTokens INT DEFAULT 0,
|
|
processingTimeMs INT DEFAULT 0,
|
|
errorMessage TEXT,
|
|
createdAt TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
updatedAt TIMESTAMP NOT NULL DEFAULT NOW() ON UPDATE NOW(),
|
|
INDEX chatSessions_status_idx (status),
|
|
INDEX chatSessions_createdAt_idx (createdAt)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
|
|
-- chatEvents: one row per SSE event within a session
|
|
CREATE TABLE IF NOT EXISTS chatEvents (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
sessionId VARCHAR(64) NOT NULL,
|
|
seq INT NOT NULL DEFAULT 0,
|
|
eventType ENUM('thinking','tool_call','delta','done','error') NOT NULL,
|
|
content TEXT,
|
|
toolName VARCHAR(128),
|
|
toolArgs JSON,
|
|
toolResult TEXT,
|
|
toolSuccess TINYINT(1),
|
|
durationMs INT,
|
|
model VARCHAR(128),
|
|
usageJson JSON,
|
|
errorMsg TEXT,
|
|
createdAt TIMESTAMP(3) NOT NULL DEFAULT NOW(3),
|
|
INDEX chatEvents_sessionId_seq_idx (sessionId, seq)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|