Improve web ui
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
|
||||
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 { 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 { mockAssistants, mockKnowledgeBases, mockVoices } from '../services/mockData';
|
||||
import { mockAssistants, mockKnowledgeBases, mockVoices, mockLLMModels, mockASRModels } from '../services/mockData';
|
||||
import { Assistant, TabValue } from '../types';
|
||||
import { GoogleGenAI } from "@google/genai";
|
||||
|
||||
@@ -63,6 +63,10 @@ export const AssistantsPage: React.FC = () => {
|
||||
tools: [],
|
||||
interruptionSensitivity: 500,
|
||||
configMode: 'platform',
|
||||
llmModelId: '',
|
||||
asrModelId: '',
|
||||
embeddingModelId: '',
|
||||
rerankModelId: '',
|
||||
};
|
||||
setAssistants([...assistants, newAssistant]);
|
||||
setSelectedId(newId);
|
||||
@@ -363,6 +367,12 @@ export const AssistantsPage: React.FC = () => {
|
||||
>
|
||||
工具配置
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab(TabValue.KNOWLEDGE)}
|
||||
className={`px-6 py-1.5 text-sm font-medium rounded-md transition-all ${activeTab === TabValue.KNOWLEDGE ? 'bg-primary text-primary-foreground shadow-sm' : 'text-muted-foreground hover:text-foreground hover:bg-white/5'}`}
|
||||
>
|
||||
知识库配置
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
@@ -435,6 +445,26 @@ export const AssistantsPage: React.FC = () => {
|
||||
|
||||
{activeTab === TabValue.GLOBAL && selectedAssistant.configMode === 'platform' && (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-white flex items-center">
|
||||
<BrainCircuit className="w-4 h-4 mr-2 text-primary"/> 大模型 (LLM Model)
|
||||
</label>
|
||||
<div className="relative group">
|
||||
<select
|
||||
className="flex h-10 w-full rounded-md border border-white/10 bg-white/5 px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 [&>option]:bg-card text-foreground appearance-none cursor-pointer"
|
||||
value={selectedAssistant.llmModelId || ''}
|
||||
onChange={(e) => updateAssistant('llmModelId', e.target.value)}
|
||||
>
|
||||
<option value="">使用系统默认模型</option>
|
||||
{mockLLMModels.filter(m => m.type === 'text').map(model => (
|
||||
<option key={model.id} value={model.id}>{model.name} ({model.vendor})</option>
|
||||
))}
|
||||
</select>
|
||||
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none group-hover:text-primary transition-colors" />
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">选择用于驱动该助手对话的大语言模型。</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-white flex items-center">
|
||||
<MessageSquare className="w-4 h-4 mr-2 text-primary"/> 开场白 (Opener)
|
||||
@@ -459,25 +489,99 @@ export const AssistantsPage: React.FC = () => {
|
||||
placeholder="设定小助手的人设、语气、行为规范以及业务逻辑..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === TabValue.KNOWLEDGE && selectedAssistant.configMode === 'platform' && (
|
||||
<div className="space-y-8 animate-in fade-in">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-white flex items-center">
|
||||
<BrainCircuit className="w-4 h-4 mr-2 text-primary"/> 嵌入模型 (Embedding Model)
|
||||
</label>
|
||||
<div className="relative group">
|
||||
<select
|
||||
className="flex h-10 w-full rounded-md border border-white/10 bg-white/5 px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 [&>option]:bg-card text-foreground appearance-none cursor-pointer"
|
||||
value={selectedAssistant.embeddingModelId || ''}
|
||||
onChange={(e) => updateAssistant('embeddingModelId', e.target.value)}
|
||||
>
|
||||
<option value="">使用系统默认</option>
|
||||
{mockLLMModels.filter(m => m.type === 'embedding').map(model => (
|
||||
<option key={model.id} value={model.id}>{model.name} ({model.vendor})</option>
|
||||
))}
|
||||
</select>
|
||||
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none group-hover:text-primary transition-colors" />
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">用于将文本转换为向量的嵌入模型。</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-white">知识库绑定</label>
|
||||
<select
|
||||
className="flex h-10 w-full rounded-md border border-white/10 bg-white/5 px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 [&>option]:bg-card text-foreground"
|
||||
value={selectedAssistant.knowledgeBaseId}
|
||||
onChange={(e) => updateAssistant('knowledgeBaseId', e.target.value)}
|
||||
>
|
||||
<option value="">如果不选择,则使用通用大模型知识</option>
|
||||
{mockKnowledgeBases.map(kb => (
|
||||
<option key={kb.id} value={kb.id}>{kb.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<label className="text-sm font-medium text-white flex items-center">
|
||||
<Filter className="w-4 h-4 mr-2 text-primary"/> 重排模型 (Rerank Model)
|
||||
</label>
|
||||
<div className="relative group">
|
||||
<select
|
||||
className="flex h-10 w-full rounded-md border border-white/10 bg-white/5 px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 [&>option]:bg-card text-foreground appearance-none cursor-pointer"
|
||||
value={selectedAssistant.rerankModelId || ''}
|
||||
onChange={(e) => updateAssistant('rerankModelId', e.target.value)}
|
||||
>
|
||||
<option value="">不使用重排</option>
|
||||
{mockLLMModels.filter(m => m.type === 'rerank').map(model => (
|
||||
<option key={model.id} value={model.id}>{model.name} ({model.vendor})</option>
|
||||
))}
|
||||
</select>
|
||||
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none group-hover:text-primary transition-colors" />
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">可选配置。重排模型可优化检索结果的相关性排序。</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-white flex items-center">
|
||||
<Book className="w-4 h-4 mr-2 text-primary"/> 知识库挂载 (Knowledge Base)
|
||||
</label>
|
||||
<div className="relative group">
|
||||
<select
|
||||
className="flex h-10 w-full rounded-md border border-white/10 bg-white/5 px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 [&>option]:bg-card text-foreground appearance-none cursor-pointer"
|
||||
value={selectedAssistant.knowledgeBaseId}
|
||||
onChange={(e) => updateAssistant('knowledgeBaseId', e.target.value)}
|
||||
>
|
||||
<option value="">如果不选择,则使用通用大模型知识</option>
|
||||
{mockKnowledgeBases.map(kb => (
|
||||
<option key={kb.id} value={kb.id}>{kb.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none group-hover:text-primary transition-colors" />
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">选择助手回答问题时参考的私有知识库。</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === TabValue.VOICE && !isNoneConfig && (
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-white flex items-center">
|
||||
<Ear className="w-4 h-4 mr-2 text-primary"/> 语音识别 (ASR Model)
|
||||
</label>
|
||||
<div className="relative group">
|
||||
<select
|
||||
className="flex h-12 w-full rounded-xl border border-white/10 bg-white/5 px-4 py-1 text-sm shadow-sm transition-all focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 [&>option]:bg-card text-foreground appearance-none cursor-pointer"
|
||||
value={selectedAssistant.asrModelId || ''}
|
||||
onChange={(e) => updateAssistant('asrModelId', e.target.value)}
|
||||
>
|
||||
<option value="">使用系统默认模型</option>
|
||||
{mockASRModels.map(model => (
|
||||
<option key={model.id} value={model.id}>
|
||||
{model.name} ({model.vendor})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<ChevronDown className="absolute right-4 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none group-hover:text-primary transition-colors" />
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground flex items-center mt-1">
|
||||
选择用于识别用户语音输入的模型。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-white flex items-center">
|
||||
<Volume2 className="w-4 h-4 mr-2 text-primary"/> 选择音色 (From Voice Library)
|
||||
|
||||
@@ -41,7 +41,7 @@ export const CallLogsPage: React.FC = () => {
|
||||
return (
|
||||
<div className="space-y-6 animate-in fade-in py-2 pb-10">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-2xl font-bold tracking-tight">视频通话记录</h1>
|
||||
<h1 className="text-2xl font-bold tracking-tight text-white">历史记录</h1>
|
||||
<Button variant="outline" onClick={handleExport}>
|
||||
<Download className="mr-2 h-4 w-4" /> 导出记录
|
||||
</Button>
|
||||
@@ -117,12 +117,7 @@ export const CallLogsPage: React.FC = () => {
|
||||
))}
|
||||
{filteredLogs.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell className="text-center py-6 text-muted-foreground">暂无记录</TableCell>
|
||||
<TableCell> </TableCell>
|
||||
<TableCell> </TableCell>
|
||||
<TableCell> </TableCell>
|
||||
<TableCell> </TableCell>
|
||||
<TableCell> </TableCell>
|
||||
<TableCell colSpan={6} className="text-center py-6 text-muted-foreground">暂无记录</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</tbody>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Download, Search, Calendar, Filter, MessageSquare, Mic, Video, Eye, X, Play } from 'lucide-react';
|
||||
import { Download, Search, Calendar, Filter, MessageSquare, Mic, Video, Eye, X, Play, User, Bot, Clock } from 'lucide-react';
|
||||
import { Button, Input, TableHeader, TableRow, TableHead, TableCell, Badge, Drawer } from '../components/UI';
|
||||
import { mockCallLogs } from '../services/mockData';
|
||||
import { CallLog, InteractionType } from '../types';
|
||||
@@ -66,7 +66,7 @@ export const HistoryPage: React.FC = () => {
|
||||
<div className="flex items-center space-x-2">
|
||||
<Filter className="h-4 w-4 text-muted-foreground" />
|
||||
<select
|
||||
className="flex h-9 w-full rounded-md border-0 bg-white/5 px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 [&>option]:bg-card"
|
||||
className="flex h-9 w-full rounded-md border-0 bg-white/5 px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 [&>option]:bg-card text-foreground"
|
||||
value={sourceFilter}
|
||||
onChange={(e) => setSourceFilter(e.target.value as any)}
|
||||
>
|
||||
@@ -77,7 +77,7 @@ export const HistoryPage: React.FC = () => {
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<select
|
||||
className="flex h-9 w-full rounded-md border-0 bg-white/5 px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 [&>option]:bg-card"
|
||||
className="flex h-9 w-full rounded-md border-0 bg-white/5 px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 [&>option]:bg-card text-foreground"
|
||||
value={typeFilter}
|
||||
onChange={(e) => setTypeFilter(e.target.value as any)}
|
||||
>
|
||||
@@ -89,7 +89,7 @@ export const HistoryPage: React.FC = () => {
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<select
|
||||
className="flex h-9 w-full rounded-md border-0 bg-white/5 px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 [&>option]:bg-card"
|
||||
className="flex h-9 w-full rounded-md border-0 bg-white/5 px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/50 [&>option]:bg-card text-foreground"
|
||||
value={statusFilter}
|
||||
onChange={(e) => setStatusFilter(e.target.value as any)}
|
||||
>
|
||||
@@ -157,7 +157,7 @@ export const HistoryPage: React.FC = () => {
|
||||
<Drawer
|
||||
isOpen={!!selectedLog}
|
||||
onClose={() => setSelectedLog(null)}
|
||||
title="历史记录详情"
|
||||
title="通话详情与对话记录"
|
||||
>
|
||||
<div className="flex flex-col h-full overflow-hidden">
|
||||
<div className="shrink-0 mb-4 p-4 bg-white/5 rounded-xl border border-white/10 space-y-3">
|
||||
@@ -173,68 +173,62 @@ export const HistoryPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto space-y-4 pr-1 custom-scrollbar pb-6">
|
||||
<div className="flex-1 overflow-y-auto space-y-6 pr-2 custom-scrollbar pb-6 px-1">
|
||||
{(selectedLog.details && selectedLog.details.length > 0) ? (
|
||||
selectedLog.details.map((detail, index) => (
|
||||
<div key={index} className={`flex flex-col gap-1 ${detail.role === 'user' ? 'items-end' : 'items-start'}`}>
|
||||
<div className={`max-w-[85%] rounded-2xl p-4 shadow-sm border ${
|
||||
detail.role === 'user'
|
||||
? 'bg-primary/10 border-primary/20 rounded-tr-none'
|
||||
: 'bg-card border-white/10 rounded-tl-none'
|
||||
}`}>
|
||||
<div className="flex items-center gap-2 mb-2 opacity-70">
|
||||
<span className="text-[10px] font-bold uppercase tracking-wider text-primary">
|
||||
{detail.role === 'user' ? 'User' : 'AI Assistant'}
|
||||
</span>
|
||||
<span className="text-[10px] text-muted-foreground">{detail.timestamp}</span>
|
||||
<div key={index} className={`flex gap-3 ${detail.role === 'user' ? 'flex-row-reverse' : 'flex-row'}`}>
|
||||
<div className={`w-8 h-8 rounded-full flex items-center justify-center shrink-0 border border-white/10 ${detail.role === 'user' ? 'bg-primary/20 text-primary' : 'bg-white/5 text-muted-foreground'}`}>
|
||||
{detail.role === 'user' ? <User className="w-4 h-4" /> : <Bot className="w-4 h-4" />}
|
||||
</div>
|
||||
|
||||
<div className={`flex flex-col max-w-[85%] ${detail.role === 'user' ? 'items-end' : 'items-start'}`}>
|
||||
<div className="flex items-center gap-2 mb-1 opacity-60">
|
||||
<span className="text-[10px] uppercase font-bold tracking-wider">{detail.role === 'user' ? 'User' : 'Assistant'}</span>
|
||||
<span className="text-[10px] font-mono">{detail.timestamp}</span>
|
||||
</div>
|
||||
|
||||
{/* Video Frames */}
|
||||
{selectedLog.type === 'video' && detail.role === 'user' && detail.imageUrls && detail.imageUrls.length > 0 && (
|
||||
<div className="flex gap-2 overflow-x-auto mb-3 pb-2">
|
||||
{detail.imageUrls.map((url, i) => (
|
||||
<div key={i} className="relative h-20 w-32 rounded-lg overflow-hidden border border-white/10 bg-black/50 shrink-0 group">
|
||||
<img src={url} alt={`Frame ${i}`} className="h-full w-full object-cover" />
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<Eye className="w-5 h-5 text-white" />
|
||||
<div className={`rounded-2xl p-3 shadow-sm text-sm leading-relaxed border ${
|
||||
detail.role === 'user'
|
||||
? 'bg-primary/10 border-primary/20 text-white rounded-tr-none'
|
||||
: 'bg-card border-white/10 text-white/90 rounded-tl-none'
|
||||
}`}>
|
||||
{/* Video Frames */}
|
||||
{selectedLog.type === 'video' && detail.role === 'user' && detail.imageUrls && detail.imageUrls.length > 0 && (
|
||||
<div className="flex gap-2 overflow-x-auto mb-2 pb-1 custom-scrollbar">
|
||||
{detail.imageUrls.map((url, i) => (
|
||||
<div key={i} className="relative h-16 w-24 rounded-lg overflow-hidden border border-white/10 bg-black/50 shrink-0 group">
|
||||
<img src={url} alt={`Frame ${i}`} className="h-full w-full object-cover" />
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<Eye className="w-4 h-4 text-white" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Content / Transcript */}
|
||||
<div className="text-sm leading-relaxed text-white/90">
|
||||
{selectedLog.type !== 'text' && (
|
||||
<div className="flex items-center gap-2 mb-1.5">
|
||||
<div className={`p-1.5 rounded-full ${detail.role === 'user' ? 'bg-primary/20' : 'bg-white/10'}`}>
|
||||
{selectedLog.type === 'audio' ? <Mic size={12} /> : <Video size={12} />}
|
||||
</div>
|
||||
<span className="text-[10px] uppercase font-mono text-muted-foreground">Transcript</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{detail.content}
|
||||
</div>
|
||||
|
||||
{/* Audio Player Placeholder for Audio/Video types */}
|
||||
{selectedLog.type !== 'text' && (
|
||||
<div className="mt-3 flex items-center gap-2 p-2 rounded-lg bg-black/20 border border-white/5">
|
||||
<button className="w-6 h-6 rounded-full bg-white/10 hover:bg-primary hover:text-white flex items-center justify-center transition-colors">
|
||||
<Play size={10} className="ml-0.5" />
|
||||
</button>
|
||||
<div className="h-1 flex-1 bg-white/10 rounded-full overflow-hidden">
|
||||
<div className="h-full w-1/3 bg-primary/50"></div>
|
||||
{/* Content */}
|
||||
<div>{detail.content}</div>
|
||||
|
||||
{/* Audio Player Placeholder */}
|
||||
{selectedLog.type !== 'text' && (
|
||||
<div className="mt-2 flex items-center gap-2 p-1.5 rounded-lg bg-black/20 border border-white/5 w-full min-w-[160px]">
|
||||
<button className="w-5 h-5 rounded-full bg-white/10 hover:bg-primary hover:text-white flex items-center justify-center transition-colors shrink-0">
|
||||
<Play size={8} className="ml-0.5" />
|
||||
</button>
|
||||
<div className="h-1 flex-1 bg-white/10 rounded-full overflow-hidden">
|
||||
<div className="h-full w-1/3 bg-primary/50"></div>
|
||||
</div>
|
||||
<span className="text-[9px] font-mono text-muted-foreground">00:05</span>
|
||||
</div>
|
||||
<span className="text-[10px] font-mono text-muted-foreground">00:05</span>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="h-full flex flex-col items-center justify-center text-muted-foreground opacity-50 space-y-2">
|
||||
<MessageSquare className="w-10 h-10" />
|
||||
<p className="text-sm">暂无对话详情数据</p>
|
||||
<div className="h-full flex flex-col items-center justify-center text-muted-foreground opacity-40 space-y-3">
|
||||
<MessageSquare className="w-12 h-12 stroke-1" />
|
||||
<p className="text-sm font-medium">暂无对话记录</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -56,7 +56,7 @@ export const LLMLibraryPage: React.FC = () => {
|
||||
return (
|
||||
<div className="space-y-6 animate-in fade-in py-4 pb-10">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-2xl font-bold tracking-tight text-white">大模型库</h1>
|
||||
<h1 className="text-2xl font-bold tracking-tight text-white">模型接入</h1>
|
||||
<Button onClick={() => setIsAddModalOpen(true)} className="shadow-[0_0_15px_rgba(6,182,212,0.4)]">
|
||||
<Plus className="mr-2 h-4 w-4" /> 添加模型
|
||||
</Button>
|
||||
|
||||
@@ -63,7 +63,7 @@ export const ToolLibraryPage: React.FC = () => {
|
||||
return (
|
||||
<div className="space-y-6 animate-in fade-in py-4 pb-10">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-2xl font-bold tracking-tight text-white">工具库</h1>
|
||||
<h1 className="text-2xl font-bold tracking-tight text-white">工具与插件</h1>
|
||||
<Button onClick={() => setIsAddModalOpen(true)} className="shadow-[0_0_15px_rgba(6,182,212,0.4)]">
|
||||
<Plus className="mr-2 h-4 w-4" /> 添加工具
|
||||
</Button>
|
||||
@@ -188,4 +188,4 @@ export const ToolLibraryPage: React.FC = () => {
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -44,7 +44,7 @@ export const VoiceLibraryPage: React.FC = () => {
|
||||
return (
|
||||
<div className="space-y-6 animate-in fade-in py-4 pb-10">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-2xl font-bold tracking-tight text-white">声音库</h1>
|
||||
<h1 className="text-2xl font-bold tracking-tight text-white">声音资源</h1>
|
||||
<div className="flex space-x-3">
|
||||
<Button variant="primary" onClick={() => setIsAddModalOpen(true)} className="shadow-[0_0_15px_rgba(6,182,212,0.4)]">
|
||||
<Plus className="mr-2 h-4 w-4" /> 添加声音
|
||||
|
||||
Reference in New Issue
Block a user