import { useState, useEffect, useCallback } from "react"; import { ActionIcon, Popover, Tooltip, UnstyledButton } from "@mantine/core"; import { IconPlus, IconChevronDown, IconArrowsDiagonal, IconX, IconSparkles, IconFileText, IconLanguage, IconSearch, } from "@tabler/icons-react"; import { useAtom } from "jotai"; import { useNavigate, useParams } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { asideStateAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom"; import { usePageQuery } from "@/features/page/queries/page-query"; import { extractPageSlugId } from "@/lib"; import { useChatStream } from "../hooks/use-chat-stream"; import { useChatInfoQuery } from "../queries/ai-chat-query"; import ChatMessageList from "./chat-message-list"; import ChatInput from "./chat-input"; import AsideChatHistory from "./aside-chat-history"; import type { ChatAttachment, PageMention } from "../types/ai-chat.types"; import classes from "../styles/aside-chat-panel.module.css"; type QuickAction = { icon: React.ReactNode; label: string; prompt: string; }; export default function AsideChatPanel() { const { t } = useTranslation(); const navigate = useNavigate(); const [, setAsideState] = useAtom(asideStateAtom); const [chatId, setChatId] = useState(undefined); const [historyOpen, setHistoryOpen] = useState(false); const [contextPages, setContextPages] = useState([]); const { pageSlug } = useParams(); const slugId = extractPageSlugId(pageSlug); const { data: page } = usePageQuery({ pageId: slugId }); const chatInfoQuery = useChatInfoQuery(chatId); const { messages, streamingContent, streamingToolCalls, isStreaming, error, sendMessage, stopGeneration, hydrateFromServer, } = useChatStream(chatId, { onChatCreated: (newChatId) => { setChatId(newChatId); }, }); useEffect(() => { if (page && !chatId) { setContextPages([{ id: page.id, title: page.title || "", slugId: page.slugId }]); } }, [page, chatId]); const handleRemoveContextPage = useCallback((pageId: string) => { setContextPages((prev) => prev.filter((p) => p.id !== pageId)); }, []); useEffect(() => { if (chatInfoQuery.data?.messages) { hydrateFromServer(chatInfoQuery.data.messages); } }, [chatInfoQuery.data, hydrateFromServer]); // Drop the open chatId if the current user lost access to it (404/403 on // the info fetch). Reverts the panel to a fresh chat instead of presenting // an input tied to a chat the user does not own. useEffect(() => { if (chatId && chatInfoQuery.isError) { setChatId(undefined); } }, [chatId, chatInfoQuery.isError]); const handleNewChat = useCallback( (event: React.MouseEvent) => { if ( event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey ) { return; } event.preventDefault(); setChatId(undefined); if (page) { setContextPages([ { id: page.id, title: page.title || "", slugId: page.slugId }, ]); } }, [page], ); const handleSelectChat = useCallback((selectedChatId: string) => { setChatId(selectedChatId); setHistoryOpen(false); }, []); const handleExpand = useCallback(() => { if (chatId) { navigate(`/ai/chat/${chatId}`); } else { navigate("/ai"); } setAsideState({ tab: "", isAsideOpen: false }); }, [chatId, navigate, setAsideState]); const handleClose = useCallback(() => { setAsideState({ tab: "", isAsideOpen: false }); }, [setAsideState]); const handleSend = useCallback( (content: string, mentions: PageMention[], attachments: ChatAttachment[]) => { const contextPageId = contextPages.length > 0 ? contextPages[0].id : undefined; sendMessage(content, mentions, attachments, contextPageId); }, [sendMessage, contextPages], ); const handleQuickAction = useCallback( (prompt: string) => { handleSend(prompt, [], []); }, [handleSend], ); const hasMessages = messages.length > 0 || isStreaming; const quickActions: QuickAction[] = [ { icon: , label: t("Summarize this page"), prompt: "Summarize this page" }, { icon: , label: t("Translate this page"), prompt: "Translate this page" }, { icon: , label: t("Analyze for insights"), prompt: "Analyze this page for insights" }, ]; return (
setHistoryOpen((o) => !o)} > {chatInfoQuery.data?.chat?.title || t("New chat")}
{error && (
{error}
)} {hasMessages ? ( <>
) : (
{t("How can I help you today?")}
{quickActions.map((action) => ( ))}
)}
); }