diff --git a/ui/src/components/ChatHandoffIndicator.tsx b/ui/src/components/ChatHandoffIndicator.tsx new file mode 100644 index 00000000..ff6b4c8d --- /dev/null +++ b/ui/src/components/ChatHandoffIndicator.tsx @@ -0,0 +1,21 @@ +import { cn } from "../lib/utils"; + +interface ChatHandoffIndicatorProps { + content: string; +} + +export function ChatHandoffIndicator({ content }: ChatHandoffIndicatorProps) { + return ( +
+ + {content} + +
+ ); +} diff --git a/ui/src/components/ChatSpecCard.tsx b/ui/src/components/ChatSpecCard.tsx new file mode 100644 index 00000000..0a329e1c --- /dev/null +++ b/ui/src/components/ChatSpecCard.tsx @@ -0,0 +1,233 @@ +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { cn } from "../lib/utils"; +import { chatApi } from "../api/chat"; + +interface SpecContent { + what: string; + why: string; + constraints: string; + success: string; +} + +interface ChatSpecCardProps { + content: string; + messageId?: string; + conversationId?: string; + onHandoff?: (spec: SpecContent) => void; +} + +export function ChatSpecCard({ content, messageId, conversationId, onHandoff }: ChatSpecCardProps) { + let parsedSpec: SpecContent | null = null; + try { + parsedSpec = JSON.parse(content) as SpecContent; + } catch { + return ( +
Could not render spec.
+ ); + } + + return ; +} + +function ChatSpecCardInner({ + spec: initialSpec, + messageId, + conversationId, + onHandoff, +}: { + spec: SpecContent; + messageId?: string; + conversationId?: string; + onHandoff?: (spec: SpecContent) => void; +}) { + const [isEditing, setIsEditing] = useState(false); + const [isDraft, setIsDraft] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + const [spec, setSpec] = useState(initialSpec); + const [editedSpec, setEditedSpec] = useState(initialSpec); + + const allFieldsEmpty = + !editedSpec.what.trim() && + !editedSpec.why.trim() && + !editedSpec.constraints.trim() && + !editedSpec.success.trim(); + + function handleDiscard() { + setEditedSpec(spec); + setIsEditing(false); + } + + async function handleSaveChanges() { + setSpec(editedSpec); + setIsEditing(false); + if (conversationId && messageId) { + await chatApi.editMessage(conversationId, messageId, JSON.stringify(editedSpec)); + } + } + + async function handleSendToPM() { + setIsSubmitting(true); + try { + onHandoff?.(spec); + } finally { + setIsSubmitting(false); + } + } + + if (isEditing) { + return ( +
+
+
+

What

+