import { useRef, useState, type ReactNode } from 'react'; import { ArrowRight, Check, ChevronDown, ClipboardList, Download, FileText, Link, Pencil, Search, ShieldCheck } from 'lucide-react'; import type { BusinessOutcomeEvent, BusinessOutcomeValue, CandidateBadges, CandidateGroup, Clarification, DeliveryStatus, EvidenceBadge, ExportFile, GoalProgress, ModelUsage, PhaseState, ProspectLead, RecoveryRoute, Task } from '../types'; import { displayTime, statusLabel } from '../taskView'; import { ConversationParticles } from './ConversationParticles'; import '../scenario-cards.css'; export function safeSourceUrl(value: unknown): string | null { if (typeof value !== 'string') return null; try { const url = new URL(value); return ['https:', 'http:'].includes(url.protocol) ? url.href : null; } catch { return null; } } function text(value: unknown): string { return typeof value === 'string' ? value : ''; } function records(value: unknown): Record[] { return Array.isArray(value) ? value.filter((row): row is Record => Boolean(row && typeof row === 'object')) : []; } function profilePersonKey(value: unknown): string | null { if (typeof value !== 'string') return null; try { const url = new URL(value); if (url.protocol !== 'https:' || !['www.xiaohongshu.com', 'xiaohongshu.com'].includes(url.hostname) || url.username || url.password || (url.port && url.port !== '443')) return null; const match = /^\/user\/profile\/([a-f0-9]{24})\/?$/i.exec(url.pathname); return match ? `xhs:${match[1].toLowerCase()}` : null; } catch { return null; } } export function ClarificationCard({ card, busy, onSubmit, onUseConversation }: { card: Clarification; busy: boolean; onSubmit: (answers: Record) => Promise; onUseConversation: () => void; }) { const [answers, setAnswers] = useState>(() => Object.fromEntries(card.fields.map(field => [field.id, field.prefill || '']))); const [errors, setErrors] = useState>({}); const [sending, setSending] = useState(false); const [failed, setFailed] = useState(false); const composing = useRef(false); async function submit(): Promise { if (busy || sending) return; const nextErrors: Record = {}; for (const field of card.fields) { const value = answers[field.id]?.trim() || ''; if (field.required && !value) nextErrors[field.id] = '请补充这一项,或在对话里说明。'; else if (field.input_kind === 'number' && value && (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(Number(value)) || Number(value) < (field.min ?? 1))) nextErrors[field.id] = '请输入大于或等于1的整数,或在对话中说明本次范围。'; } setErrors(nextErrors); setFailed(false); if (Object.keys(nextErrors).length) return; setSending(true); try { setFailed(!(await onSubmit(Object.fromEntries(card.fields.map(field => [field.id, answers[field.id]?.trim() || '']))))); } catch { setFailed(true); } finally { setSending(false); } } return
补充一点信息等你补充

补齐后,接着这个任务继续

已有条件会沿用。你可以填写这里,也可以直接在下方回复。

{ composing.current = true; }} onCompositionEnd={() => { composing.current = false; }} onKeyDown={event => { if (event.key === 'Enter' && (composing.current || event.nativeEvent.isComposing)) event.preventDefault(); }} onSubmit={event => { event.preventDefault(); if (!composing.current) void submit(); }}>
{card.fields.map(field => { const id = `clarification-${card.id}-${field.id}`; return
{field.input_kind === 'textarea' ?