Spaces:
Sleeping
Sleeping
| import React, { useState, useRef, useEffect } from 'react'; | |
| import { Send, Loader2, MessageSquare, X } from 'lucide-react'; | |
| import { useStore } from '../../store/useStore'; | |
| import { generateFlow } from '../../lib/ai'; | |
| import { cn } from '../../lib/utils'; | |
| const ChatBox: React.FC<{ isMobile?: boolean; onClose?: () => void }> = ({ isMobile, onClose }) => { | |
| const [input, setInput] = useState(''); | |
| const { messages, addMessage, isLoading, setLoading, nodes, edges, updateFlow } = useStore(); | |
| const messagesEndRef = useRef<HTMLDivElement>(null); | |
| const scrollToBottom = () => { | |
| messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); | |
| }; | |
| useEffect(() => { | |
| scrollToBottom(); | |
| }, [messages]); | |
| const handleSend = async (customInput?: string) => { | |
| const text = customInput || input; | |
| if (!text.trim() || isLoading) return; | |
| const userMsg = { | |
| id: Date.now().toString(), | |
| role: 'user' as const, | |
| content: text, | |
| createdAt: Date.now(), | |
| }; | |
| addMessage(userMsg); | |
| if (!customInput) setInput(''); | |
| setLoading(true); | |
| try { | |
| const { nodes: newNodes, edges: newEdges, reply } = await generateFlow(text, nodes, edges); | |
| updateFlow(newNodes, newEdges); | |
| addMessage({ | |
| id: (Date.now() + 1).toString(), | |
| role: 'assistant', | |
| content: reply, | |
| createdAt: Date.now(), | |
| }); | |
| } catch (error: any) { | |
| console.error('Chat error:', error); | |
| let errorMessage = `抱歉,生成工作流时出现错误:${error.message || '未知错误'}`; | |
| if (typeof error.message === 'string' && (error.message.includes('403') || error.message.includes('insufficient'))) { | |
| errorMessage = "API 余额不足或权限受限。已为您展示演示流程。"; | |
| // 演示模式:如果真失败了,手动模拟一个成功响应,让用户先看到效果 | |
| setTimeout(() => { | |
| const demoNodes = [ | |
| { id: 'd1', type: 'input', position: { x: 250, y: 0 }, data: { label: '收到订单' } }, | |
| { id: 'd2', type: 'default', position: { x: 250, y: 150 }, data: { label: '检查库存' } }, | |
| { id: 'd3', type: 'default', position: { x: 450, y: 300 }, data: { label: '库存不足' } }, | |
| { id: 'd4', type: 'default', position: { x: 50, y: 300 }, data: { label: '扣减库存' } }, | |
| { id: 'd5', type: 'output', position: { x: 250, y: 450 }, data: { label: '发货' } }, | |
| ]; | |
| const demoEdges = [ | |
| { id: 'e1-2', source: 'd1', target: 'd2', animated: true }, | |
| { id: 'e2-3', source: 'd2', target: 'd3', label: '否' }, | |
| { id: 'e2-4', source: 'd2', target: 'd4', label: '是' }, | |
| { id: 'e4-5', source: 'd4', target: 'd5', animated: true }, | |
| ]; | |
| updateFlow(demoNodes, demoEdges); | |
| }, 100); | |
| } | |
| addMessage({ | |
| id: (Date.now() + 1).toString(), | |
| role: 'assistant', | |
| content: errorMessage, | |
| createdAt: Date.now(), | |
| }); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| const examples = [ | |
| "创建一个简单的请假审批流程", | |
| "帮我做一个电商下单到发货的流程", | |
| "设计一个 AI 图像生成的处理管道", | |
| "增加一个'经理二审'的节点在现有流程中" | |
| ]; | |
| return ( | |
| <div className={cn("flex flex-col h-full bg-white border-l border-gray-200 shadow-xl", isMobile ? "w-full" : "w-96")}> | |
| <div className="p-4 border-b bg-gray-50 flex justify-between items-center"> | |
| <div className="flex items-center gap-2"> | |
| <div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" /> | |
| <div className="flex flex-col"> | |
| <h2 className="font-bold text-gray-800 text-sm">对话工作流助手</h2> | |
| <span className="text-[10px] text-gray-400">Powered by Qwen2.5-7B</span> | |
| </div> | |
| </div> | |
| {isMobile && ( | |
| <button onClick={onClose} className="p-1 hover:bg-gray-200 rounded"><X className="w-5 h-5" /></button> | |
| )} | |
| </div> | |
| <div className="flex-1 overflow-y-auto p-4 space-y-6 bg-white"> | |
| {messages.length === 0 && ( | |
| <div className="text-center py-10 space-y-4"> | |
| <div className="bg-blue-100 w-16 h-16 rounded-full flex items-center justify-center mx-auto"> | |
| <MessageSquare className="w-8 h-8 text-blue-600" /> | |
| </div> | |
| <p className="text-gray-500 text-sm">你好!我是你的工作流助手。<br/>试着告诉我你想创建什么样的流程。</p> | |
| <div className="flex flex-wrap gap-2 justify-center"> | |
| {examples.map((ex, i) => ( | |
| <button | |
| key={i} | |
| onClick={() => handleSend(ex)} | |
| className="text-xs bg-gray-100 hover:bg-blue-100 text-gray-600 hover:text-blue-700 px-3 py-1.5 rounded-full transition-colors border border-gray-200" | |
| > | |
| {ex} | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| {messages.map((msg) => ( | |
| <div | |
| key={msg.id} | |
| className={cn( | |
| "flex flex-col gap-1 max-w-[90%]", | |
| msg.role === 'user' ? "ml-auto items-end" : "mr-auto items-start" | |
| )} | |
| > | |
| <div | |
| className={cn( | |
| "rounded-2xl px-4 py-2 text-sm shadow-sm", | |
| msg.role === 'user' | |
| ? "bg-blue-600 text-white rounded-tr-none" | |
| : "bg-gray-100 text-gray-800 rounded-tl-none border border-gray-200" | |
| )} | |
| > | |
| {msg.content} | |
| </div> | |
| <span className="text-[10px] text-gray-400 px-1"> | |
| {new Date(msg.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} | |
| </span> | |
| </div> | |
| ))} | |
| <div ref={messagesEndRef} /> | |
| </div> | |
| <div className="p-4 border-t bg-gray-50 space-y-3"> | |
| {isLoading && ( | |
| <div className="flex items-center gap-2 text-xs text-blue-600 animate-pulse"> | |
| <Loader2 className="w-3 h-3 animate-spin" /> | |
| <span>AI 正在思考并生成节点...</span> | |
| </div> | |
| )} | |
| <div className="flex gap-2"> | |
| <input | |
| className="flex-1 px-4 py-2.5 bg-white border border-gray-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all" | |
| placeholder="描述你想创建的流程..." | |
| value={input} | |
| onChange={(e) => setInput(e.target.value)} | |
| onKeyDown={(e) => e.key === 'Enter' && handleSend()} | |
| disabled={isLoading} | |
| /> | |
| <button | |
| className="p-2.5 bg-blue-600 text-white rounded-xl hover:bg-blue-700 disabled:opacity-50 shadow-md hover:shadow-lg transition-all active:scale-95" | |
| onClick={() => handleSend()} | |
| disabled={isLoading} | |
| > | |
| <Send className="w-5 h-5" /> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default ChatBox; | |