import * as React from 'react'; import { Maximize2, Minimize2, RotateCcw, Sparkles, X } from 'lucide-react'; import { cn } from '@/lib/utils'; import { Surface } from '@/components/ds/Surface'; import { IconButton } from '@/components/ds/IconButton'; import { Alert } from '@/components/ds/Alert'; import { KROW_LOGO_URL } from '@/assets/brand'; import { useAssistantFacts, useConversation } from './useAssistant'; import { buildIntro, buildPrompts } from './dynamic'; import { Message, ThinkingIndicator, TurnDivider } from './AssistantMessage'; import { PromptInput } from './PromptInput'; import { PromptChips } from './PromptChips'; /** * Krow AI Assistant — the dashboard's contextual assistant. * * One component, reused on every supported page; only `context` differs. * * The frame is fixed at three parts — header, body, composer — and only the body * scrolls. That is what lets the panel stay pinned beside a long dashboard * without ever growing the page or scrolling its own input out of reach. * * Two states share that frame: * * - **Landing.** One centred block: mark, contextual label, greeting, * description, input, suggestions. Deliberately light — it is an invitation, * not a page, so nothing in it competes with the dashboard beside it. * - **Conversation.** The thread takes the body and the composer returns to the * bottom, because from then on the thread is what the user is reading. * * `expanded` widens the same panel into an analysis workspace. `data-wide` lets * individual response blocks use the extra room (a KPI row goes to three * columns) without any of them needing to know the panel's pixel width. * * Independent of Owliver. Answers come from a provider (see provider.js) that * can later be pointed at an external service without changing anything here. */ export default function KrowAssistant({ context, className, expanded = false, onClose, onExpand, onRestore, }) { const facts = useAssistantFacts({ needsWorkerProfile: context.needsWorkerProfile }); const { messages, pending, error, busy, send, stop, reset } = useConversation({ contextId: context.id, facts, }); const [input, setInput] = React.useState(''); const scrollRef = React.useRef(null); const isEmpty = messages.length === 0 && !pending; /* Greeting and suggestions come from live data, so they recompute only when the data or the page actually changes. */ const intro = React.useMemo( () => buildIntro(context.id, facts, facts.profile?.full_name), [context.id, facts] ); const prompts = React.useMemo(() => buildPrompts(context.id, facts), [context.id, facts]); /* Follow the newest content. Direct `scrollTop` rather than smooth scrolling: at streaming frequency a smooth scroll never catches up and the thread visibly lags the text. */ React.useEffect(() => { const el = scrollRef.current; if (el) el.scrollTop = el.scrollHeight; }, [messages, pending]); const submit = React.useCallback((text) => { const question = String(text).trim(); if (!question || busy) return; send({ question }); setInput(''); }, [busy, send]); /* A chip is already a complete question, so it runs on click. */ const runPrompt = React.useCallback((prompt) => { if (busy) return; setInput(''); send({ question: prompt.prompt, capability: prompt.capability }); }, [busy, send]); const composer = ( ); return ( {/* Header */}

Krow AI

{context.page}

{/* Window controls */}
{messages.length > 0 && ( )} {expanded ? onRestore && ( ) : onExpand && ( )} {onClose && ( )}
{isEmpty ? ( /* ── Landing: one compact centred block ───────────────────────── */
{/* Capped so the block stays a tight composition when expanded. */}

{intro.title}

{intro.greeting}

{intro.description}

{composer}
) : ( /* ── Conversation ─────────────────────────────────────────────── */ <>
{messages.map((message, i) => ( {i > 0 && message.role === 'user' && } ))} {pending?.thinking && } {pending && !pending.thinking && ( )} {error && {error}}
{/* Capped in conversation: the vertical space belongs to the thread. Expanded fits more per line, so it can afford one more. */} {!busy && ( )} {composer}

Krow AI reads this page's data. Check anything you act on.

)}
); }