import * as React from 'react';
import { Maximize2, Minimize2, PanelRightClose, RotateCcw, Sparkles } 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 { useAssistantFacts, useConversation, useCurrentUserName } from './useAssistant';
import { buildIntro, buildPrompts } from './dynamic';
import { Message, ThinkingIndicator, TurnDivider } from './AssistantMessage';
import { PromptInput } from './PromptInput';
import { PromptChips } from './PromptChips';
/**
* Owliver — the dashboard's contextual panel.
*
* 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.
*
* The frame does not change between states, only the body's content does: an
* empty thread shows the greeting and this page's most relevant fact, and once a
* question is asked the same region holds the conversation. Keeping the composer
* anchored either way means the primary action never moves under the user, and
* the panel never reads as two different components.
*
* `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();
const userName = useCurrentUserName();
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, userName),
[context.id, facts, userName]
);
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 */}
{/* ── Body: the only region that scrolls ─────────────────────────── */}
{isEmpty ? (
/* Greeting and the single most relevant fact about this page. Top
aligned rather than centred: it is the first thing in a
conversation, not a splash screen, so it belongs where the first
message would be. */
{/* ── Composer: fixed to the bottom in both states ───────────────── */}
{/* All suggestions on the landing screen, where they teach what can be
asked. Capped once a thread exists, because from then on the vertical
space belongs to the conversation. Expanded fits more per line, so it
can afford one more. */}
{!busy && (
)}
{composer}
Owliver reads this page's data. Check anything you act on.