diff --git a/surfsense_web/atoms/chat/mentioned-documents.atom.ts b/surfsense_web/atoms/chat/mentioned-documents.atom.ts index cf1bd8bcf..fb87f4794 100644 --- a/surfsense_web/atoms/chat/mentioned-documents.atom.ts +++ b/surfsense_web/atoms/chat/mentioned-documents.atom.ts @@ -28,6 +28,11 @@ export type MentionedDocumentInfo = kind: "connector"; connector_type: string; account_name: string; + } + | { + id: number; + title: string; + kind: "thread"; }; /** @@ -49,7 +54,10 @@ export function toMentionedDocumentInfo( ): MentionedDocumentInfo { if ( "kind" in input && - (input.kind === "doc" || input.kind === "folder" || input.kind === "connector") + (input.kind === "doc" || + input.kind === "folder" || + input.kind === "connector" || + input.kind === "thread") ) { return input; } @@ -72,6 +80,18 @@ export function makeFolderMention(input: { id: number; name: string }): Mentione }; } +/** + * Build a thread-mention chip from a thread row (id + title). Used to + * reference another conversation as read-only context. + */ +export function makeThreadMention(input: { id: number; title: string }): MentionedDocumentInfo { + return { + id: input.id, + title: input.title, + kind: "thread", + }; +} + /** * Atom to store the full context objects attached via @-mention chips in * the current chat composer. Persists across component remounts. @@ -79,21 +99,26 @@ export function makeFolderMention(input: { id: number; name: string }): Mentione export const mentionedDocumentsAtom = atom([]); /** - * Derived read-only atom that maps deduplicated mention chips into - * backend payload fields. Each mention kind maps to its own explicit - * payload bucket so non-document context never has to masquerade as a - * document type. + * Chips captured at submit time, so they survive the composer resetting + * the live atom on send. Consumed (and reset) by the send handler. */ -export const mentionedDocumentIdsAtom = atom((get) => { - const allMentions = get(mentionedDocumentsAtom); +export const submittedMentionsAtom = atom(null); + +/** + * Map mention chips to their backend payload buckets. Each kind gets its + * own bucket so non-document context never masquerades as a document. + */ +export function deriveMentionedPayload(mentions: ReadonlyArray) { const seen = new Set(); - const deduped = allMentions.filter((m) => { + const deduped = mentions.filter((m) => { const key = m.kind === "doc" ? `doc:${m.document_type}:${m.id}` : m.kind === "connector" ? `connector:${m.connector_type}:${m.id}` - : `folder:${m.id}`; + : m.kind === "thread" + ? `thread:${m.id}` + : `folder:${m.id}`; if (seen.has(key)) return false; seen.add(key); return true; @@ -101,10 +126,12 @@ export const mentionedDocumentIdsAtom = atom((get) => { const docs = deduped.filter((m) => m.kind === "doc"); const folders = deduped.filter((m) => m.kind === "folder"); const connectors = deduped.filter((m) => m.kind === "connector"); + const threads = deduped.filter((m) => m.kind === "thread"); return { document_ids: docs.map((doc) => doc.id), folder_ids: folders.map((f) => f.id), connector_ids: connectors.map((c) => c.id), + thread_ids: threads.map((t) => t.id), connectors: connectors.map((c) => ({ id: c.id, title: c.title, @@ -113,7 +140,7 @@ export const mentionedDocumentIdsAtom = atom((get) => { account_name: c.account_name, })), }; -}); +} /** * Atom to store mentioned chips per message ID. diff --git a/surfsense_web/lib/chat/mention-doc-key.ts b/surfsense_web/lib/chat/mention-doc-key.ts index 87676dbd6..dd5222068 100644 --- a/surfsense_web/lib/chat/mention-doc-key.ts +++ b/surfsense_web/lib/chat/mention-doc-key.ts @@ -2,19 +2,20 @@ type MentionKeyInput = { id: number; document_type?: string | null; connector_type?: string | null; - kind?: "doc" | "folder" | "connector"; + kind?: "doc" | "folder" | "connector" | "thread"; }; /** * Build a stable dedup key for a mention chip. * * Each mention kind keys off its real identity fields: - * docs by document type, folders by folder id, and connectors by - * connector type + account id. + * docs by document type, folders by folder id, connectors by + * connector type + account id, and threads by thread id. */ export function getMentionDocKey(doc: MentionKeyInput): string { const kind = doc.kind ?? "doc"; if (kind === "folder") return `folder:${doc.id}`; + if (kind === "thread") return `thread:${doc.id}`; if (kind === "connector") return `connector:${doc.connector_type ?? "UNKNOWN"}:${doc.id}`; return `doc:${doc.document_type ?? "UNKNOWN"}:${doc.id}`; }