import { useContext, useId, useRef, useState } from "react"; import { ArrowUpRight, CheckCircle2, FileCheck2, Info, LoaderCircle, MapPin, TriangleAlert } from "lucide-react"; import { useAnswerMotion } from "./answer-motion-context"; import { motion } from "motion/react"; import { agentEase } from "../components/agent-motion"; import { AnswerDisclosure, AnswerDisclosureChevron } from "./answer-motion"; import { Button } from "@/shared/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/shared/ui/card"; import { DateTimeField } from "@/shared/ui/date-time-field"; import { Input } from "@/shared/ui/input"; import { Textarea } from "@/shared/ui/textarea"; import { AnswerInteractionContext as InteractionContext, type AnswerUiMapTarget } from "./answer-ui-interaction-context"; import { useAnswerNodeState } from "./answer-view-state-provider"; import { answerStatePointer, type AnswerFormState } from "./answer-view-state"; type ActionState = "idle" | "pending" | "success" | "partial" | "unconfirmed" | "error"; function ActionFeedback({ state, children }: { state: ActionState; children: string }) { const shouldAnimate = useAnswerMotion(); const animate = shouldAnimate(); const Icon = state === "pending" ? LoaderCircle : state === "success" ? CheckCircle2 : state === "error" || state === "partial" ? TriangleAlert : Info; return {children}; } export function LocateButton({ label, source, ...target }: AnswerUiMapTarget & { label: string; source: string }) { const { disabled, onLocate } = useContext(InteractionContext); const runningRef = useRef(false); const [showIds, setShowIds] = useState(false); const [state, setState] = useState("idle"); const running = state === "pending"; const [feedback, setFeedback] = useState(null); return ( ); } type FormField = { name: string; label: string; kind: "text" | "datetime" | "number"; required: boolean; }; export function FollowUpForm({ title, description, fields }: { title: string; description: string; fields: FormField[]; }) { const { disabled, onSubmit, submitMode } = useContext(InteractionContext); const prefix = useId(); const [{ values }, patch] = useAnswerNodeState("FollowUpForm", { title, description, fields }); const submittingRef = useRef(false); const [state, setState] = useState("idle"); const submitting = state === "pending"; const [invalidFields, setInvalidFields] = useState([]); const setValue = (name: string, value: string) => { patch({ values: { ...values, [name]: value } }, [`values/${answerStatePointer(name)}`]); setState("idle"); setFeedback(null); setInvalidFields((current) => current.filter((field) => field !== name)); }; const [feedback, setFeedback] = useState(null); return ( {title} {description}
{ event.preventDefault(); if (!onSubmit || disabled || submittingRef.current) return; const missing = fields.filter((field) => field.required && !values[field.name]?.trim()).map((field) => field.name); setInvalidFields(missing); if (missing.length) { setState("error"); setFeedback("请填写必填资料。"); return; } const entries = fields .filter((field) => values[field.name]?.trim()) .map( (field) => `${field.label}:${values[field.name].trim()}${field.kind === "datetime" ? "(北京时间)" : ""}` ); if (!entries.length) { setState("error"); setFeedback("请至少填写一项资料。"); return; } if (entries.join("\n").length > 9000) { setState("error"); setFeedback("补充资料过长,请分次发送。"); return; } submittingRef.current = true; setState("pending"); setFeedback(null); try { await onSubmit( `我补充以下资料,请按用户提供的信息处理,说明保存或调查的实际结果:\n${entries.join("\n")}` ); setState("success"); setFeedback(submitMode === "draft" ? "资料已带入对话,请确认后发送。" : "资料已发送,请查看 Agent 回复。"); } catch (error) { setState("error"); setFeedback(error instanceof Error ? error.message : "资料发送失败,请重试。"); } finally { submittingRef.current = false; } }} > {fields.map((field) => (
{field.kind === "datetime" ? ( setValue(field.name, value)} /> ) : (
{field.kind === "text" ? (