import { type ChangeEvent, type FormEvent, type HTMLAttributes, type Ref, } from "react"; import { Check, Loader2, Send, Wrench, X } from "lucide-react"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import { buildReasoningSteps, getToolTraceSummary, type AgentChatMessage, type AgentCitation, type AgentReasoningStep, type AgentSource, type AgentToolTrace, } from "./agentChatModel"; export type { AgentChatMessage, AgentToolTrace } from "./agentChatModel"; type AgentConversationProps = HTMLAttributes & { messages: AgentChatMessage[]; isSubmitting?: boolean; scrollRef?: Ref; }; type AgentMessageProps = HTMLAttributes & { message: AgentChatMessage; }; type AgentPromptInputProps = { value: string; placeholder: string; disabled: boolean; isSubmitting: boolean; onChange: (value: string) => void; onSubmit: (event: FormEvent) => void; }; export type AgentActionPlanPreviewRow = { date?: string; description: string; amount?: number; currentCategoryName?: string; targetCategoryName?: string; action?: string; }; export type AgentActionPlan = { _id: string; kind: "bulk_recategory" | "category_changes"; summary: string; affectedCount: number; expiresAt: number; previewRows: AgentActionPlanPreviewRow[]; }; export function AgentConversation({ messages, isSubmitting = false, scrollRef, className, ...props }: AgentConversationProps) { return (
{messages.map((message) => ( ))} {isSubmitting && }
); } export function AgentActionPlanList({ plans, isApplying, onApply, onDismiss, }: { plans: AgentActionPlan[]; isApplying: boolean; onApply: (planId: string) => void; onDismiss: (planId: string) => void; }) { if (plans.length === 0) return null; return (

Vorschläge zur Bestätigung

Änderungen werden erst nach deiner Bestätigung angewendet.

{plans.map((plan) => (

{plan.summary}

{plan.affectedCount} {plan.affectedCount === 1 ? "Änderung" : "Änderungen"} · läuft ab{" "} {formatActionPlanExpiry(plan.expiresAt)}

{plan.previewRows.length > 0 && (
{plan.previewRows.slice(0, 5).map((row, index) => (

{row.date ? `${row.date} · ` : ""} {row.description}

{row.action ?? [row.currentCategoryName, row.targetCategoryName].filter(Boolean).join(" → ")}

{row.amount !== undefined && (

{formatActionPlanAmount(row.amount)}

)}
))}
)}
))}
); } export function AgentMessage({ message, className, ...props }: AgentMessageProps) { const isUser = message.role === "user"; return (

{isUser ? "User" : "Assistant"}

{!isUser && message.toolTrace && message.toolTrace.length > 0 && ( )} {!isUser && (!message.toolTrace || message.toolTrace.length === 0) && message.sources && message.sources.length > 0 && ( )}
); } function MessageContentWithCitations({ content, citations, }: { content: string; citations?: AgentCitation[]; }) { if (!citations || citations.length === 0) return content; const citationByMarker = new Map(citations.map((citation) => [citation.marker, citation])); const parts = content.split(/(\[\d+\])/g); return ( <> {parts.map((part, index) => { const marker = part.match(/^\[(\d+)\]$/)?.[1]; const citation = marker ? citationByMarker.get(marker) : undefined; if (!citation) return {part}; return ( {part} ); })} ); } function formatActionPlanAmount(amount: number) { return new Intl.NumberFormat("de-DE", { style: "currency", currency: "EUR", }).format(amount); } function formatActionPlanExpiry(expiresAt: number) { return new Intl.DateTimeFormat("de-DE", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit", }).format(new Date(expiresAt)); } function AgentSources({ sources }: { sources: AgentSource[] }) { return (

Quellen

{sources.map((source) => (

{source.title}

{source.description && (

{source.description}

)}
))}
); } function AgentEvidencePanel({ sources, toolTrace, }: { sources?: AgentSource[]; toolTrace: AgentToolTrace[]; }) { const steps = buildReasoningSteps(toolTrace); return (
Nachweis & Arbeitsweg ({getToolTraceSummary(toolTrace)})
{steps.map((step, stepIndex) => ( ))}
); } function ReasoningStep({ sourceId, step, }: { sourceId?: string; step: AgentReasoningStep; }) { return (

{step.label}

{step.description}

); } function AgentThinkingIndicator() { return (
Denk mit der KI nach...

Antwort wird vorbereitet

Der Agent prueft den aktuellen Finanzkontext.

); } export function AgentPromptInput({ value, placeholder, disabled, isSubmitting, onChange, onSubmit, }: AgentPromptInputProps) { const submitDisabled = disabled || value.trim().length === 0; const handleChange = (event: ChangeEvent) => onChange(event.target.value); return (