Add AI Elements-inspired chat primitives

This commit is contained in:
Matthias
2026-06-16 10:42:19 +02:00
parent 76b73ddb76
commit 28d0f4f852
5 changed files with 367 additions and 58 deletions

View File

@@ -1,14 +1,17 @@
import { type FormEvent, useEffect, useMemo, useRef, useState } from "react";
import { useAction, useMutation, usePaginatedQuery, useQuery } from "convex/react";
import { MessageCircle, Send, Loader2 } from "lucide-react";
import { MessageCircle } from "lucide-react";
import { api } from "../../convex/_generated/api";
import type { Id } from "../../convex/_generated/dataModel";
import { useAccountFilterId } from "@/components/layout/AccountFilter";
import { useFilters } from "@/context/FilterContext";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import {
AgentConversation,
AgentPromptInput,
type AgentChatMessage,
} from "@/components/chat/AgentChat";
import { ChatHistory, type ChatHistoryItem } from "@/components/chat/ChatHistory";
import { toast } from "sonner";
@@ -163,6 +166,18 @@ export function SavingsChatPage() {
[messagesQuery.results],
);
const displayMessages = activeSessionId && messages.length > 0 ? messages : fallbackMessages;
const agentMessages: AgentChatMessage[] = useMemo(
() =>
displayMessages.map((message) => ({
id: message._id,
role: message.role,
content: message.content,
...(message.role === "assistant" && message.toolTrace
? { toolTrace: message.toolTrace }
: {}),
})),
[displayMessages],
);
const context = useQuery(api.savingsChat.getContext, {
from,
@@ -186,8 +201,6 @@ export function SavingsChatPage() {
? legacyImportResult.importedCount
: 0;
const buttonDisabled = isSubmitting || draft.trim().length === 0 || !activeSessionId;
const formatAmount = (amount: number) =>
new Intl.NumberFormat("de-DE", {
style: "currency",
@@ -353,60 +366,20 @@ export function SavingsChatPage() {
</CardContent>
</Card>
<Card>
<CardContent className="p-4">
<div className="h-[52vh] overflow-y-auto" ref={listRef}>
<div className="space-y-3">
{displayMessages.map((message) => (
<div
key={message._id}
className={`rounded-lg border p-3 ${
message.role === "user" ? "bg-muted/50" : "bg-background"
}`}
>
<p className="text-xs uppercase text-muted-foreground">{message.role}</p>
<p className="whitespace-pre-wrap text-sm">{message.content}</p>
{message.role === "assistant" && message.toolTrace && message.toolTrace.length > 0 && (
<div className="mt-3 rounded-md border bg-muted/30 p-2">
<p className="text-xs font-medium text-muted-foreground">
Verwendete Werkzeuge
</p>
<div className="mt-2 space-y-2">
{message.toolTrace.map((tool, toolIndex) => (
<div key={`${tool.name}-${toolIndex}`} className="text-xs">
<p className="font-medium">{tool.name}</p>
<p className="text-muted-foreground">{tool.resultSummary}</p>
</div>
))}
</div>
</div>
)}
</div>
))}
{isSubmitting && (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Loader2 className="h-4 w-4 animate-spin" />
Denk mit der KI nach
</div>
)}
</div>
</div>
</CardContent>
</Card>
<AgentConversation
messages={agentMessages}
isSubmitting={isSubmitting}
scrollRef={listRef}
/>
<form className="flex gap-2" onSubmit={submit}>
<Input
value={draft}
onChange={(event) => setDraft(event.target.value)}
placeholder={activeSession ? "Welche Auswertung soll ich machen?" : "Chat wird vorbereitet"}
disabled={isSubmitting || !activeSessionId}
autoFocus
/>
<Button type="submit" disabled={buttonDisabled}>
<Send className="h-4 w-4" />
Senden
</Button>
</form>
<AgentPromptInput
value={draft}
onChange={setDraft}
onSubmit={submit}
placeholder={activeSession ? "Welche Auswertung soll ich machen?" : "Chat wird vorbereitet..."}
disabled={isSubmitting || !activeSessionId}
isSubmitting={isSubmitting}
/>
<Separator />
<p className="text-xs text-muted-foreground">