import { sendToAgentChat } from "@agent-native/core/client/agent-chat"; import { useT } from "@agent-native/core/client/i18n"; import { IconCommand, IconNotes, IconSend, IconWand, } from "@tabler/icons-react"; import { useCallback, useEffect, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { Sheet, SheetContent, SheetHeader, SheetTitle, } from "@/components/ui/sheet"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; const QUICK_PROMPTS: Array<{ labelKey: string; promptKey: string }> = [ { labelKey: "quickAsk.whatDidIMiss", promptKey: "quickAsk.whatDidIMissPrompt", }, { labelKey: "quickAsk.suggestQuestions", promptKey: "quickAsk.suggestQuestionsPrompt", }, { labelKey: "quickAsk.summarizeLastFive", promptKey: "quickAsk.summarizeLastFivePrompt", }, { labelKey: "quickAsk.makeMeSoundSmart", promptKey: "quickAsk.makeMeSoundSmartPrompt", }, { labelKey: "quickAsk.actionItemsForMe", promptKey: "quickAsk.actionItemsForMePrompt", }, ]; interface TranscriptSegment { startMs: number; endMs?: number; text: string; speaker?: string | null; } interface ChatTurn { id: string; role: "user" | "assistant" | "system"; text: string; ts: number; } interface QuickAskSidebarProps { meetingId: string; meetingTitle?: string; segments?: TranscriptSegment[] | null; } /** * Mounts the Cmd+J keybinding on the meeting detail page. The toggle is * idempotent: pressing Cmd+J while open closes the sheet (and vice versa). * * IMPORTANT: we register exactly one keydown handler. The `useEffect` cleanup * unsubscribes — so route changes / unmounts never leave a stale listener. */ export function QuickAskSidebar({ meetingId, meetingTitle, segments, }: QuickAskSidebarProps) { const t = useT(); const [open, setOpen] = useState(false); const [draft, setDraft] = useState(""); const [history, setHistory] = useState([]); const [pendingAsk, setPendingAsk] = useState(null); const textareaRef = useRef(null); // Single global keydown listener; toggles on Cmd/Ctrl+J. Esc is handled // natively by `Sheet` (Radix Dialog). useEffect(() => { const onKeyDown = (e: KeyboardEvent) => { const cmdOrCtrl = e.metaKey || e.ctrlKey; if (cmdOrCtrl && (e.key === "j" || e.key === "J")) { e.preventDefault(); setOpen((prev) => !prev); } }; window.addEventListener("keydown", onKeyDown); return () => window.removeEventListener("keydown", onKeyDown); }, []); // The desktop pill's "Ask anything" bar hands its question over as `?ask=` // alongside `?chat=1`, so the question the user typed on the overlay lands // in the agent chat here instead of being retyped. useEffect(() => { const params = new URLSearchParams(window.location.search); const askParam = params.get("ask")?.trim(); if (params.get("chat") !== "1" && !askParam) return; setOpen(true); if (askParam) setPendingAsk(askParam); params.delete("chat"); params.delete("ask"); const nextQuery = params.toString(); window.history.replaceState( window.history.state, "", `${window.location.pathname}${nextQuery ? `?${nextQuery}` : ""}${window.location.hash}`, ); }, []); // Focus the composer whenever the sheet opens. useEffect(() => { if (open) { const t = setTimeout(() => textareaRef.current?.focus(), 60); return () => clearTimeout(t); } }, [open]); const send = useCallback( (prompt: string) => { const trimmed = prompt.trim(); if (!trimmed) return; // Build a compact context object: meeting id + last 200 segments. // Agent chat is the single source of truth — no inline LLM calls. const tail = (segments ?? []).slice(-200); const turn: ChatTurn = { id: `t_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`, role: "user", text: trimmed, ts: Date.now(), }; setHistory((prev) => [...prev, turn]); setDraft(""); sendToAgentChat({ message: trimmed, context: JSON.stringify({ meetingId, meetingTitle: meetingTitle ?? null, transcript: tail, }), submit: true, openSidebar: false, background: false, }); // Optimistic placeholder so the user sees we received the prompt. setHistory((prev) => [ ...prev, { id: `${turn.id}-ack`, role: "system", text: t("quickAsk.sentToChat"), ts: Date.now(), }, ]); }, [meetingId, meetingTitle, segments, t], ); useEffect(() => { if (!pendingAsk) return; setPendingAsk(null); send(pendingAsk); }, [pendingAsk, send]); return ( {t("quickAsk.title")}
J {t("quickAsk.toggleHint")}

{t("quickAsk.quickPrompts")}

{QUICK_PROMPTS.map((q) => ( ))}
{history.length > 0 && (

{t("quickAsk.history")}

{history.map((t) => ( {t.text} ))}
)} {history.length === 0 && (

{t("quickAsk.emptyDescription")}

)}
{ e.preventDefault(); send(draft); }} className="border-t border-border p-3 flex items-end gap-2" >