diff --git a/surfsense_backend/alembic/versions/66_add_chat_comments_table.py b/surfsense_backend/alembic/versions/66_add_chat_comments_table.py index 332ad3ca3..0f31bcfdc 100644 --- a/surfsense_backend/alembic/versions/66_add_chat_comments_table.py +++ b/surfsense_backend/alembic/versions/66_add_chat_comments_table.py @@ -26,15 +26,21 @@ def upgrade() -> None: content TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() - ); - - -- Create indexes - CREATE INDEX IF NOT EXISTS idx_chat_comments_message_id ON chat_comments(message_id); - CREATE INDEX IF NOT EXISTS idx_chat_comments_parent_id ON chat_comments(parent_id); - CREATE INDEX IF NOT EXISTS idx_chat_comments_author_id ON chat_comments(author_id); - CREATE INDEX IF NOT EXISTS idx_chat_comments_created_at ON chat_comments(created_at); + ) """ ) + op.execute( + "CREATE INDEX IF NOT EXISTS idx_chat_comments_message_id ON chat_comments(message_id)" + ) + op.execute( + "CREATE INDEX IF NOT EXISTS idx_chat_comments_parent_id ON chat_comments(parent_id)" + ) + op.execute( + "CREATE INDEX IF NOT EXISTS idx_chat_comments_author_id ON chat_comments(author_id)" + ) + op.execute( + "CREATE INDEX IF NOT EXISTS idx_chat_comments_created_at ON chat_comments(created_at)" + ) def downgrade() -> None: diff --git a/surfsense_backend/alembic/versions/67_add_chat_comment_mentions_table.py b/surfsense_backend/alembic/versions/67_add_chat_comment_mentions_table.py index ceb8669ae..02f1740c3 100644 --- a/surfsense_backend/alembic/versions/67_add_chat_comment_mentions_table.py +++ b/surfsense_backend/alembic/versions/67_add_chat_comment_mentions_table.py @@ -25,15 +25,15 @@ def upgrade() -> None: read BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (comment_id, mentioned_user_id) - ); - - -- Create indexes - CREATE INDEX IF NOT EXISTS idx_chat_comment_mentions_comment_id - ON chat_comment_mentions(comment_id); - CREATE INDEX IF NOT EXISTS idx_chat_comment_mentions_user_unread - ON chat_comment_mentions(mentioned_user_id) WHERE read = FALSE; + ) """ ) + op.execute( + "CREATE INDEX IF NOT EXISTS idx_chat_comment_mentions_comment_id ON chat_comment_mentions(comment_id)" + ) + op.execute( + "CREATE INDEX IF NOT EXISTS idx_chat_comment_mentions_user_unread ON chat_comment_mentions(mentioned_user_id) WHERE read = FALSE" + ) def downgrade() -> None: diff --git a/surfsense_backend/app/schemas/chat_comments.py b/surfsense_backend/app/schemas/chat_comments.py index 67509cb90..a8abba978 100644 --- a/surfsense_backend/app/schemas/chat_comments.py +++ b/surfsense_backend/app/schemas/chat_comments.py @@ -55,6 +55,8 @@ class CommentReplyResponse(BaseModel): created_at: datetime updated_at: datetime is_edited: bool + can_edit: bool = False + can_delete: bool = False model_config = ConfigDict(from_attributes=True) @@ -70,6 +72,8 @@ class CommentResponse(BaseModel): created_at: datetime updated_at: datetime is_edited: bool + can_edit: bool = False + can_delete: bool = False reply_count: int replies: list[CommentReplyResponse] = [] diff --git a/surfsense_web/components/assistant-ui/assistant-message.tsx b/surfsense_web/components/assistant-ui/assistant-message.tsx index 83c573e2f..63a8ba39b 100644 --- a/surfsense_web/components/assistant-ui/assistant-message.tsx +++ b/surfsense_web/components/assistant-ui/assistant-message.tsx @@ -5,9 +5,11 @@ import { MessagePrimitive, useAssistantState, } from "@assistant-ui/react"; +import { useAtomValue } from "jotai"; import { CheckIcon, CopyIcon, DownloadIcon, RefreshCwIcon } from "lucide-react"; import type { FC } from "react"; -import { useContext } from "react"; +import { useContext, useState } from "react"; +import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; import { BranchPicker } from "@/components/assistant-ui/branch-picker"; import { MarkdownText } from "@/components/assistant-ui/markdown-text"; import { @@ -16,6 +18,9 @@ import { } from "@/components/assistant-ui/thinking-steps"; import { ToolFallback } from "@/components/assistant-ui/tool-fallback"; import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; +import { CommentPanelContainer } from "@/components/chat-comments/comment-panel-container/comment-panel-container"; +import { CommentTrigger } from "@/components/chat-comments/comment-trigger/comment-trigger"; +import { useComments } from "@/hooks/use-comments"; export const MessageError: FC = () => { return ( @@ -76,13 +81,53 @@ const AssistantMessageInner: FC = () => { ); }; +function parseMessageId(assistantUiMessageId: string | undefined): number | null { + if (!assistantUiMessageId) return null; + const match = assistantUiMessageId.match(/^msg-(\d+)$/); + return match ? Number.parseInt(match[1], 10) : null; +} + export const AssistantMessage: FC = () => { + const [isCommentPanelOpen, setIsCommentPanelOpen] = useState(false); + const messageId = useAssistantState(({ message }) => message?.id); + const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); + const dbMessageId = parseMessageId(messageId); + + const { data: commentsData } = useComments({ + messageId: dbMessageId ?? 0, + enabled: !!dbMessageId, + }); + + const commentCount = commentsData?.total_count ?? 0; + return ( + + {/* Comment trigger and floating panel */} + {dbMessageId && searchSpaceId && ( +
+ setIsCommentPanelOpen(!isCommentPanelOpen)} + /> + {isCommentPanelOpen && ( +
+ setIsCommentPanelOpen(false)} + maxHeight={400} + /> +
+ )} +
+ )}
); }; diff --git a/surfsense_web/components/assistant-ui/thread.tsx b/surfsense_web/components/assistant-ui/thread.tsx index 9f844ba2b..51f9c0d33 100644 --- a/surfsense_web/components/assistant-ui/thread.tsx +++ b/surfsense_web/components/assistant-ui/thread.tsx @@ -36,6 +36,7 @@ import { newLLMConfigsAtom, } from "@/atoms/new-llm-config/new-llm-config-query.atoms"; import { currentUserAtom } from "@/atoms/user/user-query.atoms"; +import { AssistantMessage } from "@/components/assistant-ui/assistant-message"; import { ComposerAddAttachment, ComposerAttachments } from "@/components/assistant-ui/attachment"; import { ConnectorIndicator } from "@/components/assistant-ui/connector-popup"; import { @@ -590,17 +591,6 @@ const AssistantMessageInner: FC = () => { ); }; -const AssistantMessage: FC = () => { - return ( - - - - ); -}; - const AssistantActionBar: FC = () => { return ( - !open && closeMentionPicker()}> + !open && closeMentionPicker()} modal={false}>