"use client"; import { useChat, useChatActions, useDataPart } from "@ai-sdk-tools/store"; import type { UIChatMessage } from "@midday/api/ai/types"; import { createClient } from "@midday/supabase/client"; import { cn } from "@midday/ui/cn"; import { Conversation, ConversationContent } from "@midday/ui/conversation"; import { DefaultChatTransport, generateId } from "ai"; import dynamic from "next/dynamic"; import { parseAsString, useQueryState } from "nuqs"; import { useEffect, useMemo, useRef } from "react"; import { Portal } from "@/components/portal"; import { useChatInterface } from "@/hooks/use-chat-interface"; import { useChatStatus } from "@/hooks/use-chat-status"; import { useMetricsFilter } from "@/hooks/use-metrics-filter"; import type { Geo } from "@/utils/geo"; import { ChatHeader, ChatInput, type ChatInputMessage, ChatMessages, ChatStatusIndicators, } from "./"; import { SuggestedPrompts } from "./suggested-prompts"; // Dynamically load Canvas (15 chart components) - only loads when user opens an artifact const Canvas = dynamic( () => import("@/components/canvas").then((mod) => mod.Canvas), { ssr: false }, ); type Props = { geo?: Geo; }; export function ChatInterface({ geo }: Props) { const { chatId: routeChatId, isHome } = useChatInterface(); const chatId = useMemo(() => routeChatId ?? generateId(), [routeChatId]); const { reset } = useChatActions(); const prevChatIdRef = useRef(routeChatId); const [, clearSuggestions] = useDataPart<{ prompts: string[] }>( "suggestions", ); // Get current dashboard metrics filter state (source of truth for AI tool defaults) const { period, from, to, currency, revenueType } = useMetricsFilter(); // Reset chat state when navigating away from a chat (sidebar, browser back, etc.) useEffect(() => { const prevChatId = prevChatIdRef.current; const currentChatId = routeChatId; // If we had a chatId before and now we don't (navigated away), reset // Or if we're switching to a different chatId, reset if (prevChatId && prevChatId !== currentChatId) { reset(); clearSuggestions(); } // Update the ref for next comparison prevChatIdRef.current = currentChatId; }, [routeChatId, reset, clearSuggestions]); const authenticatedFetch = useMemo( () => Object.assign( async (url: RequestInfo | URL, requestOptions?: RequestInit) => { const supabase = createClient(); const { data: { session }, } = await supabase.auth.getSession(); return fetch(url, { ...requestOptions, headers: { ...requestOptions?.headers, Authorization: `Bearer ${session?.access_token}`, "Content-Type": "application/json", }, }); }, ), [], ); const { messages, status } = useChat({ id: chatId, transport: new DefaultChatTransport({ api: `${process.env.NEXT_PUBLIC_API_URL}/chat`, fetch: authenticatedFetch, prepareSendMessagesRequest({ messages, id }) { const lastMessage = messages[messages.length - 1] as ChatInputMessage; const agentChoice = lastMessage.metadata?.agentChoice; const toolChoice = lastMessage.metadata?.toolChoice; return { body: { id, country: geo?.country, city: geo?.city, message: lastMessage, agentChoice, toolChoice, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, // Dashboard metrics filter state - source of truth for AI tool defaults metricsFilter: { period, from, to, currency, revenueType }, }, }; }, }), }); const { agentStatus, currentToolCall, artifactStage, artifactType, currentSection, bankAccountRequired, hasTextContent, hasInsightData, } = useChatStatus(messages, status); const [selectedType] = useQueryState("artifact-type", parseAsString); const hasMessages = messages.length > 0; const showCanvas = Boolean(selectedType); return (
{/* Canvas slides in from right when artifacts are present */}
{/* Main chat area - container that slides left when canvas opens */}
{hasMessages && ( <> {/* Conversation view - messages with absolute positioning for proper height */}
)}
); }