import React, { useState, useEffect, useRef } from 'react'; import { Plus, Search, Play, Copy, Trash2, Edit2, Mic, MessageSquare, Save, Video, PhoneOff, Camera, ArrowLeftRight, Send, Phone, MoreHorizontal, Rocket, AlertTriangle, PhoneCall, CameraOff, Image, Images, CloudSun, Calendar, TrendingUp, Coins, Wrench, Globe, Terminal, X, ClipboardCheck, Sparkles, Volume2, Timer, ChevronDown, Link as LinkIcon, Database, Server, Zap, ExternalLink, Key, BrainCircuit, Ear, Book, Filter } from 'lucide-react'; import { Button, Input, Card, Badge, Drawer, Dialog } from '../components/UI'; import { mockLLMModels, mockASRModels } from '../services/mockData'; import { Assistant, KnowledgeBase, TabValue, Voice } from '../types'; import { createAssistant, deleteAssistant, fetchAssistantRuntimeConfig, fetchAssistants, fetchKnowledgeBases, fetchVoices, updateAssistant as updateAssistantApi } from '../services/backendApi'; interface ToolItem { id: string; name: string; icon: React.ReactNode; desc: string; category: 'system' | 'query'; isCustom?: boolean; } export const AssistantsPage: React.FC = () => { const [assistants, setAssistants] = useState([]); const [voices, setVoices] = useState([]); const [knowledgeBases, setKnowledgeBases] = useState([]); const [searchTerm, setSearchTerm] = useState(''); const [selectedId, setSelectedId] = useState(null); const [activeTab, setActiveTab] = useState(TabValue.GLOBAL); const [debugOpen, setDebugOpen] = useState(false); const [hotwordInput, setHotwordInput] = useState(''); // Publish Modal State const [isPublishModalOpen, setIsPublishModalOpen] = useState(false); const [publishTab, setPublishTab] = useState<'web' | 'api'>('web'); // Custom Tools State const [customTools, setCustomTools] = useState([]); const [hiddenToolIds, setHiddenToolIds] = useState([]); const [isAddToolModalOpen, setIsAddToolModalOpen] = useState(false); const [addingToCategory, setAddingToCategory] = useState<'system' | 'query'>('system'); // New Tool Form State const [newToolName, setNewToolName] = useState(''); const [newToolDesc, setNewToolDesc] = useState(''); const [deleteId, setDeleteId] = useState(null); const [copySuccess, setCopySuccess] = useState(false); const [saveLoading, setSaveLoading] = useState(false); const [isLoading, setIsLoading] = useState(true); const selectedAssistant = assistants.find(a => a.id === selectedId) || null; const filteredAssistants = assistants.filter(a => a.name.toLowerCase().includes(searchTerm.toLowerCase()) ); useEffect(() => { const loadInitialData = async () => { setIsLoading(true); try { const [assistantList, voiceList, kbList] = await Promise.all([ fetchAssistants(), fetchVoices(), fetchKnowledgeBases(), ]); setAssistants(assistantList); setVoices(voiceList); setKnowledgeBases(kbList); if (assistantList.length > 0) { setSelectedId(assistantList[0].id); } } catch (error) { console.error(error); alert('加载助手数据失败,请检查后端服务是否启动。'); } finally { setIsLoading(false); } }; loadInitialData(); }, []); const handleCreate = async () => { const newAssistantPayload: Partial = { name: 'New Assistant', opener: '', prompt: '', knowledgeBaseId: '', language: 'zh', voice: voices[0]?.id || '', speed: 1, hotwords: [], tools: [], interruptionSensitivity: 500, configMode: 'platform', }; try { const created = await createAssistant(newAssistantPayload); setAssistants((prev) => [created, ...prev]); setSelectedId(created.id); setActiveTab(TabValue.GLOBAL); } catch (error) { console.error(error); alert('创建助手失败。'); } }; const handleSave = async () => { if (!selectedAssistant) return; setSaveLoading(true); try { const updated = await updateAssistantApi(selectedAssistant.id, selectedAssistant); setAssistants((prev) => prev.map((item) => (item.id === updated.id ? { ...item, ...updated } : item))); } catch (error) { console.error(error); alert('保存失败,请稍后重试。'); } finally { setSaveLoading(false); } }; const handleCopyId = (id: string, text?: string) => { navigator.clipboard.writeText(text || id); setCopySuccess(true); setTimeout(() => setCopySuccess(false), 2000); }; const handleCopy = async (e: React.MouseEvent, assistant: Assistant) => { e.stopPropagation(); try { const copied = await createAssistant({ ...assistant, name: `${assistant.name} (Copy)`, }); setAssistants((prev) => [copied, ...prev]); } catch (error) { console.error(error); alert('复制助手失败。'); } }; const handleDeleteClick = (e: React.MouseEvent, id: string) => { e.stopPropagation(); setDeleteId(id); }; const confirmDelete = async () => { if (deleteId) { try { await deleteAssistant(deleteId); setAssistants(prev => prev.filter(a => a.id !== deleteId)); if (selectedId === deleteId) setSelectedId(null); setDeleteId(null); } catch (error) { console.error(error); alert('删除失败,请稍后重试。'); } } }; const updateAssistant = (field: keyof Assistant, value: any) => { if (!selectedId) return; setAssistants(prev => prev.map(a => a.id === selectedId ? { ...a, [field]: value } : a)); if (field === 'configMode') { if (value === 'platform') { setActiveTab(TabValue.GLOBAL); } else if (value === 'dify' || value === 'fastgpt') { setActiveTab(TabValue.LINK); } } }; const toggleTool = (toolId: string) => { if (!selectedAssistant) return; const currentTools = selectedAssistant.tools || []; const newTools = currentTools.includes(toolId) ? currentTools.filter(id => id !== toolId) : [...currentTools, toolId]; updateAssistant('tools', newTools); }; const deleteTool = (e: React.MouseEvent, toolId: string) => { e.stopPropagation(); setAssistants(prev => prev.map(a => ({ ...a, tools: a.tools?.filter(id => id !== toolId) || [] }))); if (customTools.some(t => t.id === toolId)) { setCustomTools(prev => prev.filter(t => t.id !== toolId)); } else { setHiddenToolIds(prev => [...prev, toolId]); } }; const addHotword = () => { if (hotwordInput.trim() && selectedAssistant) { updateAssistant('hotwords', [...selectedAssistant.hotwords, hotwordInput.trim()]); setHotwordInput(''); } }; const removeHotword = (word: string) => { if (selectedAssistant) { updateAssistant('hotwords', selectedAssistant.hotwords.filter(w => w !== word)); } }; const handleAddCustomTool = () => { if (!newToolName.trim()) return; const newTool: ToolItem = { id: `custom_${Date.now()}`, name: newToolName, desc: newToolDesc, category: addingToCategory, icon: addingToCategory === 'system' ? : , isCustom: true }; setCustomTools([...customTools, newTool]); setIsAddToolModalOpen(false); setNewToolName(''); setNewToolDesc(''); }; const openAddToolModal = (e: React.MouseEvent, cat: 'system' | 'query') => { e.stopPropagation(); setAddingToCategory(cat); setIsAddToolModalOpen(true); }; const baseSystemTools: ToolItem[] = [ { id: 'cam_open', name: '打开相机', icon: , desc: '允许 AI 开启摄像头流', category: 'system' }, { id: 'cam_close', name: '关闭相机', icon: , desc: '允许 AI 停止摄像头流', category: 'system' }, { id: 'take_photo', name: '拍照', icon: , desc: 'AI 触发单张拍摄', category: 'system' }, { id: 'burst_3', name: '连拍三张', icon: , desc: 'AI 触发快速连拍', category: 'system' }, ]; const baseQueryTools: ToolItem[] = [ { id: 'q_weather', name: '天气查询', icon: , desc: '查询实时及未来天气', category: 'query' }, { id: 'q_calendar', name: '日历查询', icon: , desc: '查询日程及节假日信息', category: 'query' }, { id: 'q_stock', name: '股价查询', icon: , desc: '查询股票实时行情', category: 'query' }, { id: 'q_exchange', name: '汇率查询', icon: , desc: '查询多国货币汇率', category: 'query' }, ]; const systemTools = [...baseSystemTools, ...customTools.filter(t => t.category === 'system')].filter(t => !hiddenToolIds.includes(t.id)); const queryTools = [...baseQueryTools, ...customTools.filter(t => t.category === 'query')].filter(t => !hiddenToolIds.includes(t.id)); const isExternalConfig = selectedAssistant?.configMode === 'dify' || selectedAssistant?.configMode === 'fastgpt'; const isNoneConfig = selectedAssistant?.configMode === 'none' || !selectedAssistant?.configMode; return (
{/* LEFT COLUMN: List */}

小助手列表

setSearchTerm(e.target.value)} />
{!isLoading && filteredAssistants.map(assistant => (
setSelectedId(assistant.id)} className={`group relative flex flex-col p-4 rounded-xl border transition-all cursor-pointer ${ selectedId === assistant.id ? 'bg-primary/10 border-primary/40 shadow-[0_0_15px_rgba(6,182,212,0.15)]' : 'bg-card/30 border-white/5 hover:bg-white/5 hover:border-white/10' }`} >
{assistant.name} {assistant.configMode && assistant.configMode !== 'none' && (
{assistant.configMode === 'platform' ? '内置' : assistant.configMode}
)}
{assistant.callCount} 次通话
))} {!isLoading && filteredAssistants.length === 0 && (
未找到小助手
)} {isLoading && (
加载中...
)}
{/* RIGHT COLUMN: Config Panel */}
{selectedAssistant ? ( <> {/* Header Area */}
UUID: {selectedAssistant.id}
updateAssistant('name', e.target.value)} className="font-bold bg-white/5 border-white/10 focus:border-primary/50 text-base" />
{!isNoneConfig && (
{selectedAssistant.configMode === 'platform' ? ( <> ) : ( <> )}
)}
{isNoneConfig ? (

请先选择配置方式以展开详细设置

) : (
{activeTab === TabValue.LINK && isExternalConfig && (

接入 {selectedAssistant.configMode === 'dify' ? 'Dify' : 'FastGPT'} 引擎

配置后,视频通话过程中的对话逻辑、知识库检索以及工作流将由外部引擎托管。

updateAssistant('apiUrl', e.target.value)} placeholder={selectedAssistant.configMode === 'dify' ? "https://api.dify.ai/v1" : "https://api.fastgpt.in/api/v1"} className="bg-white/5 border-white/10 focus:border-primary/50 font-mono text-xs" />
updateAssistant('apiKey', e.target.value)} placeholder="请输入应用 API 密钥..." className="bg-white/5 border-white/10 focus:border-primary/50 font-mono text-xs" />
)} {activeTab === TabValue.GLOBAL && selectedAssistant.configMode === 'platform' && (

选择用于驱动该助手对话的大语言模型。

updateAssistant('opener', e.target.value)} placeholder="例如:您好,我是您的专属AI助手..." className="bg-white/5 border-white/10 focus:border-primary/50" />

接通通话后的第一句话。