import * as React from 'react'; import { ThumbsDown, ThumbsUp } from 'lucide-react'; import { cn } from '@/lib/utils'; import { ResponseDocument } from './ResponseBlocks'; import OwliverAvatar from '@/components/krow/OwliverAvatar'; /** * The thinking state, shown between sending and the first block arriving. * * Labelled as well as animated: three moving dots mean nothing to a screen * reader, and the visually-hidden text is what tells one that a reply is coming. */ export function ThinkingIndicator() { return (
{[0, 1, 2].map((i) => ( ))} Owliver is preparing a response
); } /** * One turn. * * The user's words sit in a filled bubble; the assistant's reply runs the full * width of the panel. That asymmetry is deliberate — these replies are reports * with tables and KPI tiles, and a chat bubble would waste a third of a 380px * column on padding around them. */ /** * How this conversation was rated. * * Offered on the latest answer only, because a rating belongs to the * conversation rather than to a paragraph of it — thumbs on every turn would * ask a reader to score an answer out of the exchange that produced it, and * each one would set the same value anyway. * * Hidden until the turn is hovered or focused, the way History's delete control * already is: a thread of reports should not carry two permanent buttons under * every answer. */ function FeedbackControls({ feedback, onFeedback }) { const rate = (rating) => onFeedback(feedback?.rating === rating ? null : rating); return (
{[ { rating: 'up', Icon: ThumbsUp, label: 'This answer was useful' }, { rating: 'down', Icon: ThumbsDown, label: 'This answer was not useful' }, ].map(({ rating, Icon, label }) => ( ))}
); } export const Message = React.memo( /** @param {any} props */ ({ role, text, blocks, streaming, stopped, onPrompt, feedback = null, onFeedback = null }) => { if (role === 'user') { return (
{text}
); } return (
Owliver
{stopped &&

Stopped early.

} {/* Only where a handler was passed — a turn nobody is collecting feedback for renders exactly as it did before this existed. */} {onFeedback && !streaming && ( )}
); }); Message.displayName = 'Message'; /** A subtle divider between turns, so a long thread stays readable. */ export function TurnDivider() { return
; } export { cn };