diff --git a/surfsense_web/components/assistant-ui/user-message.tsx b/surfsense_web/components/assistant-ui/user-message.tsx index 86863a501..fb7212119 100644 --- a/surfsense_web/components/assistant-ui/user-message.tsx +++ b/surfsense_web/components/assistant-ui/user-message.tsx @@ -1,11 +1,12 @@ import { ActionBarPrimitive, AuiIf, MessagePrimitive, useAuiState } from "@assistant-ui/react"; import { useAtomValue } from "jotai"; -import { CheckIcon, CopyIcon, FileText, Pencil } from "lucide-react"; +import { CheckIcon, CopyIcon, Pencil } from "lucide-react"; import Image from "next/image"; import { type FC, useState } from "react"; import { currentThreadAtom } from "@/atoms/chat/current-thread.atom"; import { messageDocumentsMapAtom } from "@/atoms/chat/mentioned-documents.atom"; import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; +import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; interface AuthorMetadata { displayName: string | null; @@ -48,6 +49,19 @@ const UserAvatar: FC = ({ displayName, avatarUrl }) => { export const UserMessage: FC = () => { const messageId = useAuiState(({ message }) => message?.id); + const messageText = useAuiState(({ message }) => + (message?.content ?? []) + .map((part) => + typeof part === "object" && + part !== null && + "type" in part && + (part as { type?: string }).type === "text" && + "text" in part + ? String((part as { text?: string }).text ?? "") + : "" + ) + .join("") + ); const messageDocumentsMap = useAtomValue(messageDocumentsMapAtom); const mentionedDocs = messageId ? messageDocumentsMap[messageId] : undefined; const metadata = useAuiState(({ message }) => message?.metadata); @@ -63,22 +77,12 @@ export const UserMessage: FC = () => {
- {mentionedDocs && mentionedDocs.length > 0 && ( -
- {mentionedDocs?.map((doc) => ( - - - {doc.title} - - ))} -
- )}
- + {mentionedDocs && mentionedDocs.length > 0 ? ( + + ) : ( + + )}
@@ -95,6 +99,64 @@ export const UserMessage: FC = () => { ); }; +const UserMessageWithMentionChips: FC<{ + text: string; + mentionedDocs: { id: number; title: string; document_type: string }[]; +}> = ({ text, mentionedDocs }) => { + type Segment = + | { type: "text"; value: string; start: number } + | { type: "mention"; doc: { id: number; title: string; document_type: string }; start: number }; + + const tokens = mentionedDocs + .map((doc) => ({ doc, token: `@${doc.title}` })) + .sort((a, b) => b.token.length - a.token.length); + + const segments: Segment[] = []; + let i = 0; + let buffer = ""; + let bufferStart = 0; + while (i < text.length) { + const tokenMatch = tokens.find(({ token }) => text.startsWith(token, i)); + if (tokenMatch) { + if (buffer) { + segments.push({ type: "text", value: buffer, start: bufferStart }); + buffer = ""; + } + segments.push({ type: "mention", doc: tokenMatch.doc, start: i }); + i += tokenMatch.token.length; + bufferStart = i; + continue; + } + if (!buffer) bufferStart = i; + buffer += text[i]; + i += 1; + } + if (buffer) { + segments.push({ type: "text", value: buffer, start: bufferStart }); + } + + return ( + + {segments.map((segment) => + segment.type === "text" ? ( + {segment.value} + ) : ( + + + {getConnectorIcon(segment.doc.document_type ?? "UNKNOWN", "h-3 w-3")} + + {segment.doc.title} + + ) + )} + + ); +}; + const UserActionBar: FC = () => { const isThreadRunning = useAuiState(({ thread }) => thread.isRunning);