diff --git a/admin-web/src/AiDesignerChat.tsx b/admin-web/src/AiDesignerChat.tsx new file mode 100644 index 0000000..7d34996 --- /dev/null +++ b/admin-web/src/AiDesignerChat.tsx @@ -0,0 +1,150 @@ +import { useState, useRef, useEffect } from 'react'; +import { api } from './api'; +import type { Field, ControlType } from './schema'; + +export type DesignerStage = 'UNDERSTANDING' | 'CLARIFYING' | 'GENERATING' | 'VALIDATING' | 'CONFIRMING'; + +export type DesignerSuggestion = { + stage: DesignerStage; + formTitle?: string; + formKey?: string; + fields: { + key: string; label: string; control: string; required: boolean; + placeholder?: string; options?: string[]; helperText?: string; + }[]; + process?: { + mode: string; + steps: { name: string; assigneeVariable: string }[]; + conditionThresholdDays?: number | null; + }; + summary: string; + understanding: string; + assumptions: string[]; + needsClarification: string[]; + validationIssues?: { field: string; issue: string; severity: string }[]; + schemaReady: boolean; +}; + +type ChatMessage = { + role: 'user' | 'assistant'; + content: string; + suggestion?: DesignerSuggestion; +}; + +export type ApplySuggestion = (s: DesignerSuggestion) => void; + +const stageLabels: Record = { + UNDERSTANDING: '🧠 理解需求', + CLARIFYING: '❓ 澄清确认', + GENERATING: '📋 生成 Schema', + VALIDATING: '✅ 校验 Schema', + CONFIRMING: '🎯 等待确认', +}; + +export function AiDesignerChat({ onApply, contextLabel, getCurrentSchema }: { onApply: ApplySuggestion; contextLabel: string; getCurrentSchema?: () => string }) { + const [messages, setMessages] = useState([ + { role: 'assistant', content: '你好!我是 AI 设计助手。用自然语言描述你想要的表单或审批流程,我来帮你生成。\n\n例如:\n• "报销申请表单,包含报销人、金额、日期、事由"\n• "报销审批:先部门主管审批,超过5000元加财务复核,最后OA管理员审批"' }, + ]); + const [input, setInput] = useState(''); + const [loading, setLoading] = useState(false); + const [currentStage, setCurrentStage] = useState('UNDERSTANDING'); + const scrollRef = useRef(null); + + useEffect(() => { + scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth' }); + }, [messages, loading]); + + function buildHistory(msgs: ChatMessage[]) { + return msgs.slice(1).map(m => ({ role: m.role, content: m.content })); + } + + async function send(presetText?: string) { + const text = (presetText ?? input).trim(); + if (!text || loading) return; + setInput(''); + const next = [...messages, { role: 'user' as const, content: text }]; + setMessages(next); + setLoading(true); + try { + const result = await api<{ suggestion: DesignerSuggestion; model: string }>('/ai/designer-suggestions', { + method: 'POST', + body: JSON.stringify({ + message: text, + history: buildHistory(next.slice(0, -1)), + currentSchema: getCurrentSchema?.() ?? null, + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'Asia/Shanghai', + }), + }); + const s = result.suggestion; + setCurrentStage(s.stage); + const parts = formatSuggestionMessage(s); + setMessages([...next, { role: 'assistant', content: parts, suggestion: s }]); + } catch (e) { + setMessages([...next, { role: 'assistant', content: `⚠️ ${e instanceof Error ? e.message : 'AI 服务暂时不可用'}` }]); + } finally { + setLoading(false); + } + } + + return
+
+ ✨ AI 设计助手 + {contextLabel} + {stageLabels[currentStage]} +
+
+ {messages.map((m, i) =>
+
{m.content}
+ {m.suggestion && m.suggestion.fields.length > 0 && (m.suggestion.stage === 'GENERATING' || m.suggestion.stage === 'VALIDATING' || m.suggestion.stage === 'CONFIRMING') && ( + + )} + {m.suggestion && m.suggestion.needsClarification.length > 0 && m.suggestion.stage === 'CLARIFYING' && ( +
+ {m.suggestion.needsClarification.map((q, qi) => )} +
+ )} + {m.suggestion && m.suggestion.schemaReady && ( +
✅ Schema 已确认,可应用到设计器
+ )} +
)} + {loading &&
思考中…
} +
+
+