From b608c395c72e2ccb5c44c8cc35838f8040395a3a Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Wed, 4 Feb 2026 18:36:40 +0800 Subject: [PATCH] Better UX --- App.tsx | 18 +- index.html | 5 +- metadata.json | 5 +- package.json | 2 +- pages/Assistants.tsx | 438 +++++++++++++++++++++++++++++++--------- pages/AutoTest.tsx | 380 +++++++++++++++++----------------- pages/CallLogs.tsx | 5 +- pages/Dashboard.tsx | 113 ++++++++--- pages/History.tsx | 128 ++++++++++++ pages/KnowledgeBase.tsx | 106 +++++++--- pages/VoiceLibrary.tsx | 30 +-- pages/Workflows.tsx | 19 +- services/mockData.ts | 22 +- types.ts | 9 +- 14 files changed, 877 insertions(+), 403 deletions(-) create mode 100644 pages/History.tsx diff --git a/App.tsx b/App.tsx index 007e71d..a3d2910 100644 --- a/App.tsx +++ b/App.tsx @@ -1,11 +1,11 @@ import React, { useState } from 'react'; import { HashRouter as Router, Routes, Route, Link, useLocation } from 'react-router-dom'; -import { Bot, Phone, Book, User, LayoutDashboard, Mic2, Video, GitBranch, Zap, PanelLeftClose, PanelLeftOpen } from 'lucide-react'; +import { Bot, Phone, Book, User, LayoutDashboard, Mic2, Video, GitBranch, Zap, PanelLeftClose, PanelLeftOpen, History as HistoryIcon } from 'lucide-react'; import { AssistantsPage } from './pages/Assistants'; import { KnowledgeBasePage } from './pages/KnowledgeBase'; -import { CallLogsPage } from './pages/CallLogs'; +import { HistoryPage } from './pages/History'; import { ProfilePage } from './pages/Profile'; import { DashboardPage } from './pages/Dashboard'; import { VoiceLibraryPage } from './pages/VoiceLibrary'; @@ -32,10 +32,10 @@ const AppLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => { { path: '/', label: '首页', icon: }, { path: '/assistants', label: '小助手', icon: }, { path: '/voices', label: '声音库', icon: }, - { path: '/call-logs', label: '视频通话记录', icon: }, + { path: '/history', label: '历史记录', icon: }, { path: '/knowledge', label: '知识库', icon: }, { path: '/workflows', label: '工作流', icon: }, - { path: '/auto-test', label: '自动测试', icon: }, + { path: '/auto-test', label: '测试助手', icon: }, { path: '/profile', label: '个人中心', icon: }, ]; @@ -51,7 +51,7 @@ const AppLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => { {!isCollapsed && ( - AI VideoAssistant + AI视频助手 )} @@ -73,7 +73,7 @@ const AppLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
{!isCollapsed && ( - SYSTEM v2.0 + SYSTEM v1.0 )} @@ -232,18 +277,29 @@ export const AssistantsPage: React.FC = () => { {selectedAssistant ? ( <> {/* Header Area */} -
- {/* Row 1: Name and Actions */} -
+
+
- +
+ +
+ UUID: {selectedAssistant.id} + +
+
updateAssistant('name', e.target.value)} className="font-bold bg-white/5 border-white/10 focus:border-primary/50 text-base" />
-
+
+
- {/* Row 2: Tabs */} -
- - - +
+ +
+ + +
+ + {!isNoneConfig && ( +
+ {selectedAssistant.configMode === 'platform' ? ( + <> + + + + + ) : ( + <> + + + + )} +
+ )}
- {/* Content Scroll Area */}
-
- {activeTab === TabValue.GLOBAL && ( + {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' && (
)} - {activeTab === TabValue.VOICE && ( + {activeTab === TabValue.VOICE && !isNoneConfig && (
-
-
- +
+ +
-
-
- - +
+

+ + 音色配置同步自声音库。如需添加更多音色,请前往“声音库”模块。 +

-
-
- - {selectedAssistant.speed}x +
+
+ +
+
+ updateAssistant('interruptionSensitivity', parseInt(e.target.value) || 0)} + className="w-20 h-8 text-right pr-7 text-xs font-mono bg-black/40 border-white/5" + /> + ms +
+
- updateAssistant('speed', parseFloat(e.target.value))} - className="w-full h-2 bg-secondary rounded-lg appearance-none cursor-pointer accent-primary" - /> -
- 0.5x (Slow) - 1.0x (Normal) - 2.0x (Fast) +
+ updateAssistant('interruptionSensitivity', parseInt(e.target.value))} + className="flex-1 h-1.5 bg-secondary rounded-lg appearance-none cursor-pointer accent-primary" + />
+
+ 0ms (Extreme) + 1000ms + 2000ms (Lazy) +
+

+ * 定义用户说话多长时间后 AI 应当停止当前的发言并响应。数值越小响应越快,但也更容易被噪音误导打断。 +

@@ -394,16 +553,16 @@ export const AssistantsPage: React.FC = () => { onKeyDown={(e) => e.key === 'Enter' && addHotword()} className="bg-white/5 border-white/10" /> - +
{selectedAssistant.hotwords.length === 0 && ( - 暂无热词 + 暂无热词 )} {selectedAssistant.hotwords.map((word, idx) => ( - + {word} - + ))}
@@ -412,7 +571,7 @@ export const AssistantsPage: React.FC = () => {
)} - {activeTab === TabValue.TOOLS && ( + {activeTab === TabValue.TOOLS && selectedAssistant.configMode === 'platform' && (
@@ -445,7 +604,6 @@ export const AssistantsPage: React.FC = () => {

{tool.desc}

- {/* Delete Button */}

{tool.desc}

- {/* Delete Button */}
)} -
+
+ )}
) : ( @@ -521,6 +679,96 @@ export const AssistantsPage: React.FC = () => { )}
+ {/* Publish Modal */} + setIsPublishModalOpen(false)} + title="发布小助手" + footer={ + + } + > +
+
+ + +
+ + {publishTab === 'web' ? ( +
+
+
+ +

交互体验站

+
+

+ 该链接允许用户通过独立的浏览器页面与您的智能体进行交互。支持:文本对话、实时音频通话以及双向视频通话。 +

+
+
+ +
+ + +
+
+
+ ) : ( +
+
+ +
+ + +
+
+
+
+ + PRIVATE +
+
+ + +
+

+ API Key 仅用于身份鉴权,请务必妥善保存。 +

+
+
+ )} +
+
+ {selectedAssistant && ( {
-

自定义工具将通过其名称和描述告知 AI 它的用途。您可以在后续的工作流中进一步定义 these 工具的具体行为逻辑。

+

自定义工具将通过其名称 and 描述告知 AI 它的用途。您可以在后续的工作流中进一步定义 these 工具的具体行为逻辑。

diff --git a/pages/AutoTest.tsx b/pages/AutoTest.tsx index 3d4fb9d..3dfd694 100644 --- a/pages/AutoTest.tsx +++ b/pages/AutoTest.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; -import { Plus, Search, Play, Copy, Trash2, Zap, MessageSquare, Mic, AlertTriangle, ListFilter, Braces, Rocket } from 'lucide-react'; +import { Plus, Search, Play, Copy, Trash2, Zap, MessageSquare, Mic, AlertTriangle, ClipboardCheck, X } from 'lucide-react'; import { Button, Input, Card, Badge, Dialog } from '../components/UI'; import { mockAutoTestAssistants, mockAssistants } from '../services/mockData'; import { AutoTestAssistant, TestType, TestMethod } from '../types'; @@ -9,93 +9,87 @@ export const AutoTestPage: React.FC = () => { const [testAssistants, setTestAssistants] = useState(mockAutoTestAssistants); const [searchTerm, setSearchTerm] = useState(''); const [selectedId, setSelectedId] = useState(null); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [deleteId, setDeleteId] = useState(null); + const [copySuccess, setCopySuccess] = useState(false); - const selectedTestAssistant = testAssistants.find(a => a.id === selectedId) || null; - - const filteredTests = testAssistants.filter(a => - a.name.toLowerCase().includes(searchTerm.toLowerCase()) + const filteredTests = testAssistants.filter(t => + t.name.toLowerCase().includes(searchTerm.toLowerCase()) ); + const selectedTest = testAssistants.find(t => t.id === selectedId) || null; + const handleCreate = () => { - const newId = `at_${Date.now()}`; - const newAssistant: AutoTestAssistant = { + const newId = crypto.randomUUID(); + const newTest: AutoTestAssistant = { id: newId, - name: '新测试助手', - type: TestType.INTELLIGENT, + name: '新测试任务', + type: TestType.FIXED, method: TestMethod.TEXT, targetAssistantId: mockAssistants[0]?.id || '', fixedWorkflowSteps: [], - intelligentPrompt: '你是一个普通的测试用户,试图了解产品信息。', - createdAt: new Date().toISOString().replace('T', ' ').substring(0, 16) + intelligentPrompt: '', + createdAt: new Date().toISOString().split('T')[0], }; - setTestAssistants([...testAssistants, newAssistant]); + setTestAssistants([newTest, ...testAssistants]); setSelectedId(newId); }; - const handleCopy = (e: React.MouseEvent, assistant: AutoTestAssistant) => { + const handleCopy = (e: React.MouseEvent, test: AutoTestAssistant) => { e.stopPropagation(); - const newAssistant = { ...assistant, id: `at_${Date.now()}`, name: `${assistant.name} (Copy)` }; - setTestAssistants([...testAssistants, newAssistant]); + const newId = crypto.randomUUID(); + const newTest: AutoTestAssistant = { + ...test, + id: newId, + name: `${test.name} (复制)`, + createdAt: new Date().toISOString().split('T')[0], + }; + setTestAssistants([newTest, ...testAssistants]); + setSelectedId(newId); + }; + + const updateTest = (field: keyof AutoTestAssistant, value: any) => { + if (!selectedId) return; + setTestAssistants(prev => prev.map(t => t.id === selectedId ? { ...t, [field]: value } : t)); }; const handleDeleteClick = (e: React.MouseEvent, id: string) => { e.stopPropagation(); setDeleteId(id); + setIsDeleteModalOpen(true); }; const confirmDelete = () => { if (deleteId) { - setTestAssistants(prev => prev.filter(a => a.id !== deleteId)); + setTestAssistants(prev => prev.filter(t => t.id !== deleteId)); if (selectedId === deleteId) setSelectedId(null); + setIsDeleteModalOpen(false); setDeleteId(null); } }; - const updateAssistant = (field: keyof AutoTestAssistant, value: any) => { - if (!selectedId) return; - setTestAssistants(prev => prev.map(a => a.id === selectedId ? { ...a, [field]: value } : a)); - }; - - const handleAddStep = () => { - if (selectedTestAssistant) { - updateAssistant('fixedWorkflowSteps', [...selectedTestAssistant.fixedWorkflowSteps, '']); - } - }; - - const updateStep = (idx: number, val: string) => { - if (selectedTestAssistant) { - const newSteps = [...selectedTestAssistant.fixedWorkflowSteps]; - newSteps[idx] = val; - updateAssistant('fixedWorkflowSteps', newSteps); - } - }; - - const removeStep = (idx: number) => { - if (selectedTestAssistant) { - updateAssistant('fixedWorkflowSteps', selectedTestAssistant.fixedWorkflowSteps.filter((_, i) => i !== idx)); - } + const handleCopyId = (id: string) => { + navigator.clipboard.writeText(id); + setCopySuccess(true); + setTimeout(() => setCopySuccess(false), 2000); }; return ( -
- {/* LEFT COLUMN: Test Assistants List */} +
+ {/* Left List */}
-
-

测试助手列表

-
- +

测试助手列表

setSearchTerm(e.target.value)} />
-
@@ -111,214 +105,208 @@ export const AutoTestPage: React.FC = () => { : 'bg-card/30 border-white/5 hover:bg-white/5 hover:border-white/10' }`} > -
- +
+ {test.name} -
- -
- - {test.type === TestType.FIXED ? '固定流程' : '智能测试'} - -
- {test.method === TestMethod.TEXT ? : } - {test.method === TestMethod.TEXT ? '文本' : '音频'} +
+ + {test.type === TestType.FIXED ? '固定' : '智能'} +
- - {/* Hover Actions */} -
- -
))} {filteredTests.length === 0 && ( -
- [ NO TESTERS FOUND ] +
+ 未找到测试助手
)}
- {/* RIGHT COLUMN: Config Panel */} + {/* Right Config Panel */}
- {selectedTestAssistant ? ( - <> - {/* Header Area */} -
+ {selectedTest ? ( +
+
- +
+ +
updateAssistant('name', e.target.value)} + value={selectedTest.name} + onChange={(e) => updateTest('name', e.target.value)} className="font-bold bg-white/5 border-white/10 focus:border-primary/50 text-base" />
-
- {/* Scroll Area */}
-
- - {/* Basic Config Grid */} -
-
- -
- - -
+
+ {/* Basic Config */} +
+
+ +
- -
- -
- -
-
- - + {/* Test Logic Type */} +
+ +
+ updateTest('type', TestType.FIXED)} + > +
+ 固定流程 +
+

按照预设的消息序列依次发送给目标小助手,验证其回复是否符合预期。

+
+ updateTest('type', TestType.INTELLIGENT)} + > +
+ 智能提示词 +
+

由 AI 扮演用户,根据设定的角色和场景与目标小助手进行动态对话。

+
+
- {/* Conditional Settings */} -
- {selectedTestAssistant.type === TestType.FIXED ? ( -
-
- - -
-
- {selectedTestAssistant.fixedWorkflowSteps.map((step, idx) => ( -
-
- {idx + 1} -
- updateStep(idx, e.target.value)} - placeholder={`步骤 ${idx + 1} 的测试输入...`} - className="bg-white/5" - /> - -
- ))} - {selectedTestAssistant.fixedWorkflowSteps.length === 0 && ( -
- 点击右上角按钮添加测试步骤 -
- )} -
+ {/* Content Config */} + {selectedTest.type === TestType.FIXED ? ( +
+ +
+ {selectedTest.fixedWorkflowSteps.map((step, idx) => ( +
+
{idx + 1}
+ { + const newSteps = [...selectedTest.fixedWorkflowSteps]; + newSteps[idx] = e.target.value; + updateTest('fixedWorkflowSteps', newSteps); + }} + placeholder="输入用户消息..." + /> + +
+ ))} +
- ) : ( -
- -