mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-14 05:39:08 +02:00
State management - Pending task / live timeline are now Query-cache single source; Zustand mirror removed (fixes duplicate assistant render caused by the invalidate→refetch race window) - WS subscriptions moved from ChatWindow to global useRealtimeSync so pending state survives minimize and refresh - New GET /chat/sessions/:id/pending-task to recover live state on mount - Drafts persisted per-session (was per-workspace) Unread tracking - Migration 040: chat_session.unread_since (event-driven; old chats stay clean — no mass backfill) - POST /chat/sessions/:id/read clears unread; broadcasts chat:session_read so other devices sync - New GET /chat/pending-tasks aggregate for the FAB - ChatFab: brand-color impulse animation while running, brand-dot badge of unread session count - ChatWindow auto-marks read when user is viewing the session Header redesign - Two independent dropdowns: agent (avatar + name + My/Others grouping) at the input bottom-left; session (title + agent avatar) in the header - ⊕ new-chat button replaces the old + and history buttons - Session dropdown lists all sessions across agents with avatars - Empty state: 3 clickable starter prompts that send immediately - Mention link renderer falls through to default span on null — fixes @member/@agent/@all silently disappearing app-wide - User messages render through Markdown - Enter submits in chat input only (with IME guard + codeBlock skip); bubble menu hidden in chat Misc - Partial index on agent_task_queue for fast pending-task lookup - 2 new storage keys added to clearWorkspaceStorage - useMarkChatSessionRead has onError rollback - chat.* namespace logs across store, mutations, components, realtime Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
104 lines
3.4 KiB
SQL
104 lines
3.4 KiB
SQL
-- name: CreateChatSession :one
|
|
INSERT INTO chat_session (workspace_id, agent_id, creator_id, title)
|
|
VALUES ($1, $2, $3, $4)
|
|
RETURNING *;
|
|
|
|
-- name: GetChatSession :one
|
|
SELECT * FROM chat_session
|
|
WHERE id = $1;
|
|
|
|
-- name: GetChatSessionInWorkspace :one
|
|
SELECT * FROM chat_session
|
|
WHERE id = $1 AND workspace_id = $2;
|
|
|
|
-- name: ListChatSessionsByCreator :many
|
|
-- Returns active sessions with a boolean unread flag. Unread is strictly
|
|
-- per-session: either the user has uncleared assistant replies in this
|
|
-- session or they don't. Counting messages would be misleading.
|
|
SELECT cs.*,
|
|
(cs.unread_since IS NOT NULL)::bool AS has_unread
|
|
FROM chat_session cs
|
|
WHERE cs.workspace_id = $1 AND cs.creator_id = $2 AND cs.status = 'active'
|
|
ORDER BY cs.updated_at DESC;
|
|
|
|
-- name: ListAllChatSessionsByCreator :many
|
|
SELECT cs.*,
|
|
(cs.unread_since IS NOT NULL)::bool AS has_unread
|
|
FROM chat_session cs
|
|
WHERE cs.workspace_id = $1 AND cs.creator_id = $2
|
|
ORDER BY cs.updated_at DESC;
|
|
|
|
-- name: UpdateChatSessionTitle :one
|
|
UPDATE chat_session SET title = $2, updated_at = now()
|
|
WHERE id = $1
|
|
RETURNING *;
|
|
|
|
-- name: UpdateChatSessionSession :exec
|
|
UPDATE chat_session SET session_id = $2, work_dir = $3, updated_at = now()
|
|
WHERE id = $1;
|
|
|
|
-- name: ArchiveChatSession :exec
|
|
UPDATE chat_session SET status = 'archived', updated_at = now()
|
|
WHERE id = $1;
|
|
|
|
-- name: TouchChatSession :exec
|
|
UPDATE chat_session SET updated_at = now()
|
|
WHERE id = $1;
|
|
|
|
-- name: CreateChatMessage :one
|
|
INSERT INTO chat_message (chat_session_id, role, content, task_id)
|
|
VALUES ($1, $2, $3, sqlc.narg(task_id))
|
|
RETURNING *;
|
|
|
|
-- name: ListChatMessages :many
|
|
SELECT * FROM chat_message
|
|
WHERE chat_session_id = $1
|
|
ORDER BY created_at ASC;
|
|
|
|
-- name: GetChatMessage :one
|
|
SELECT * FROM chat_message
|
|
WHERE id = $1;
|
|
|
|
-- name: CreateChatTask :one
|
|
INSERT INTO agent_task_queue (agent_id, runtime_id, issue_id, status, priority, chat_session_id)
|
|
VALUES ($1, $2, NULL, 'queued', $3, $4)
|
|
RETURNING *;
|
|
|
|
-- name: GetLastChatTaskSession :one
|
|
SELECT session_id, work_dir FROM agent_task_queue
|
|
WHERE chat_session_id = $1 AND status = 'completed' AND session_id IS NOT NULL
|
|
ORDER BY completed_at DESC
|
|
LIMIT 1;
|
|
|
|
-- name: GetPendingChatTask :one
|
|
-- Returns the most recent in-flight task for a chat session, if any.
|
|
-- Used by the frontend to recover pending state after refresh / reopen.
|
|
SELECT id, status FROM agent_task_queue
|
|
WHERE chat_session_id = $1 AND status IN ('queued', 'dispatched', 'running')
|
|
ORDER BY created_at DESC
|
|
LIMIT 1;
|
|
|
|
-- name: ListPendingChatTasksByCreator :many
|
|
-- Aggregate view of all in-flight chat tasks owned by a given creator in a
|
|
-- workspace. Drives the FAB's "running" indicator when the chat window is
|
|
-- closed and no single session's query is active.
|
|
SELECT atq.id AS task_id, atq.status, atq.chat_session_id
|
|
FROM agent_task_queue atq
|
|
JOIN chat_session cs ON cs.id = atq.chat_session_id
|
|
WHERE cs.workspace_id = $1
|
|
AND cs.creator_id = $2
|
|
AND atq.status IN ('queued', 'dispatched', 'running')
|
|
ORDER BY atq.created_at DESC;
|
|
|
|
-- name: MarkChatSessionRead :exec
|
|
-- Clears unread_since, dropping the session's unread count to 0.
|
|
UPDATE chat_session SET unread_since = NULL
|
|
WHERE id = $1;
|
|
|
|
-- name: SetUnreadSinceIfNull :exec
|
|
-- Atomically stamps the first unread assistant message's arrival time.
|
|
-- No-op if the session is already in "has unread" state — keeps the earliest
|
|
-- unread boundary stable across multiple incoming replies.
|
|
UPDATE chat_session SET unread_since = now()
|
|
WHERE id = $1 AND unread_since IS NULL;
|