// dashboard/src/features/agent/AskUserQuestionCard.tsx import { useState, useEffect } from 'react'; import axios from 'axios'; import { MessageCircle, Send, Loader, CheckSquare, Square } from 'lucide-react'; import type { PendingQuestion } from '../../types'; interface AskUserQuestionCardProps { onAnswered?: () => void; onQuestionCountChange?: (count: number) => void; } export function AskUserQuestionCard({ onAnswered, onQuestionCountChange, }: AskUserQuestionCardProps) { const [pendingQuestions, setPendingQuestions] = useState( [] ); const [answers, setAnswers] = useState>({}); const [freeText, setFreeText] = useState>({}); const [submitting, setSubmitting] = useState>({}); const [expanded, setExpanded] = useState>({}); const [error, setError] = useState>({}); // 轮询待回答问题 useEffect(() => { let cancelled = false; const poll = async () => { try { const res = await axios.get('/api/chat/questions'); if (!cancelled) { const data = Array.isArray(res.data) ? res.data : []; setPendingQuestions(data); // 自动展开新问题 setExpanded((prev) => { const next = { ...prev }; for (const q of data) { if (q && q.question_id && !(q.question_id in next)) { next[q.question_id] = true; } } return next; }); } } catch (e) { console.error('获取待回答问题失败:', e); } }; poll(); const interval = setInterval(poll, 3000); // 每3秒轮询 return () => { cancelled = true; clearInterval(interval); }; }, []); // 当待处理问题数量改变时,报告给父容器 useEffect(() => { onQuestionCountChange?.(pendingQuestions.length); }, [pendingQuestions.length, onQuestionCountChange]); const toggleOption = ( questionId: string, optionLabel: string, multiSelect: boolean ) => { setAnswers((prev) => { const current = prev[questionId] || []; if (multiSelect) { return { ...prev, [questionId]: current.includes(optionLabel) ? current.filter((o) => o !== optionLabel) : [...current, optionLabel], }; } else { return { ...prev, [questionId]: [optionLabel] }; } }); }; const handleSubmit = async (questionId: string) => { setSubmitting((prev) => ({ ...prev, [questionId]: true })); setError((prev) => ({ ...prev, [questionId]: null })); try { await axios.post('/api/chat/answer', { question_id: questionId, answers: answers[questionId] || [], free_text: freeText[questionId] || null, }); // 移除已回答的问题 setPendingQuestions((prev) => prev.filter((q) => q.question_id !== questionId) ); // 清理状态 setAnswers((prev) => { const next = { ...prev }; delete next[questionId]; return next; }); setFreeText((prev) => { const next = { ...prev }; delete next[questionId]; return next; }); setError((prev) => { const next = { ...prev }; delete next[questionId]; return next; }); onAnswered?.(); } catch (e: unknown) { console.error('提交答案失败:', e); const axiosError = e as { response?: { status?: number } }; const msg = axiosError.response?.status === 410 ? '该问题已超时或已被回答' : axiosError.response?.status === 404 ? '未找到该问题' : '提交失败,请稍后重试'; setError((prev) => ({ ...prev, [questionId]: msg })); } finally { setSubmitting((prev) => ({ ...prev, [questionId]: false })); } }; const dismissQuestion = (questionId: string) => { setPendingQuestions((prev) => prev.filter((q) => q.question_id !== questionId) ); setExpanded((prev) => ({ ...prev, [questionId]: false })); }; if (pendingQuestions.length === 0) return null; return (
{pendingQuestions.map((q) => { if (!q || !q.question_id) return null; const isExpanded = expanded[q.question_id] !== false; const isSubmitting = submitting[q.question_id] || false; const qError = error[q.question_id] || null; const options = Array.isArray(q.options) ? q.options : []; const multiSelect = q.multi_select === true; return (
{/* 标题栏 - 头部与问题放在同一行,可省略独立的问题文本框 */}
{q.header || '提问'} {q.question || ''}
{options.length > 0 && ( )}
{/* 内容区 */} {isExpanded && (
{/* 选项列表 */} {options.length > 0 && (
{multiSelect ? '可多选' : '请选择一项'}
{options.map((option, idx) => { const label = typeof option?.label === 'string' ? option.label : String(option); const desc = typeof option?.description === 'string' ? option.description : ''; const selected = ( answers[q.question_id] || [] ).includes(label); return ( ); })}
)} {/* 自由文本 */}