import type { AssistantChangePreview, TrekCardKind } from '@trek/shared'; import { Check, Undo2 } from 'lucide-react'; import { Fragment, type ReactNode } from 'react'; import { useTranslation } from '../../../i18n'; import { Tooltip } from '../../shared/Tooltip'; import AssistantChangeFields from '../AssistantChangeFields'; import type { ChangeItem } from '../assistantParts'; import { useAssistantView } from '../assistantViewContext'; import { useChangeHover } from '../useChangeHover'; import { TOOL_LABELS, verbLookOf } from './cardLooks'; import type { CardView } from './cardTypes'; import { viewOf } from './cardViews'; import EntityActions from './EntityActions'; import { ICON_BUTTON, RECEIPT } from './parts/assistantTones'; import CardBoundary from './parts/CardBoundary'; import KindTile from './parts/KindTile'; /** * The rows of the change card: a change waiting for its decision, and the * receipt it turns into. A change that carries a card is drawn through its * kind's view, slot by slot (`Lead` in front, `Aside` at the end of the * subject line, `Facts` on the faint line, `Body` under it); a change * without one reads as it always has: the tile of its verb, its subject, and * the few fields that tell it apart. A slot that throws draws what the row * shows without a card, so one broken view never takes the chat down. */ type View = CardView; /** What the change does: the tool's own action where TREK names it, else add, change or remove. */ function verbLabel(preview: AssistantChangePreview, t: (key: string) => string): string { // Read as a string: a tool named like an object's own member (toString) has no label. const own = preview.tool ? TOOL_LABELS[preview.tool]?.labelKey : undefined; return t(typeof own === 'string' ? own : verbLookOf(preview.verbKey).labelKey); } /** The tile in front: the kind's own lead, else the verb's tile. */ function Lead({ item, view, dimmed }: { item: ChangeItem; view: View | null; dimmed: boolean }) { const look = item.preview ? verbLookOf(item.preview.verbKey) : null; const tile = ; const Own = view?.Lead; if (!Own || !item.card) return tile; return ( ); } /** A slot of the kind's view, or nothing; `fallback` where it throws, until the change moves on to its next state. */ function Slot({ item, view, name, fallback = null, }: { item: ChangeItem; view: View | null; name: 'Aside' | 'Facts' | 'Body'; fallback?: ReactNode; }) { const Own = view?.[name]; if (!Own || !item.card) return null; return ( ); } /** The subject in bold, with the kind's aside at its end; only the aside where the kind draws the subject itself. */ function SubjectLine({ subject, struck, aside, block = false, }: { subject: string; struck: boolean; aside: ReactNode; block?: boolean; }) { const text = ( {subject} ); if (!aside) return text; return ( {text} {aside} ); } /** Where in TREK a change outside the trip plan lands: the vacation planner, the journal. */ function DomainChip({ domain }: { domain: string | undefined }) { const { t } = useTranslation(); if (!domain) return null; return ( {t(`assistant.domain.${domain}`)} ); } function ChangeHead({ item, view, struck }: { item: ChangeItem; view: View | null; struck: boolean }) { const { t } = useTranslation(); const preview = item.preview; if (!preview) return