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 } from 'lucide-react'; import { Button, Input, Card, Badge, Drawer, Dialog } from '../components/UI'; import { mockAssistants, mockKnowledgeBases, mockVoices } from '../services/mockData'; import { Assistant, TabValue } from '../types'; import { GoogleGenAI } from "@google/genai"; 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(mockAssistants); 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 selectedAssistant = assistants.find(a => a.id === selectedId) || null; const filteredAssistants = assistants.filter(a => a.name.toLowerCase().includes(searchTerm.toLowerCase()) ); const handleCreate = () => { const newId = Math.floor(Math.random() * 1000000).toString().padStart(6, '0'); const newAssistant: Assistant = { id: newId, name: 'New Assistant', callCount: 0, opener: '', prompt: '', knowledgeBaseId: '', language: 'zh', voice: mockVoices[0]?.id || '', speed: 1, hotwords: [], tools: [], interruptionSensitivity: 500, configMode: 'platform', }; setAssistants([...assistants, newAssistant]); setSelectedId(newId); setActiveTab(TabValue.GLOBAL); }; const handleSave = () => { setSaveLoading(true); // Simulate API call setTimeout(() => { setSaveLoading(false); // In a real app, logic to persist selectedAssistant would go here }, 800); }; const handleCopyId = (id: string, text?: string) => { navigator.clipboard.writeText(text || id); setCopySuccess(true); setTimeout(() => setCopySuccess(false), 2000); }; const handleCopy = (e: React.MouseEvent, assistant: Assistant) => { e.stopPropagation(); const newId = Math.floor(Math.random() * 1000000).toString().padStart(6, '0'); const newAssistant = { ...assistant, id: newId, name: `${assistant.name} (Copy)` }; setAssistants([...assistants, newAssistant]); }; const handleDeleteClick = (e: React.MouseEvent, id: string) => { e.stopPropagation(); setDeleteId(id); }; const confirmDelete = () => { if (deleteId) { setAssistants(prev => prev.filter(a => a.id !== deleteId)); if (selectedId === deleteId) setSelectedId(null); setDeleteId(null); } }; 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)} />
{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} 次通话
))} {filteredAssistants.length === 0 && (
未找到小助手
)}
{/* 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" />

接通通话后的第一句话。