Frontend start to use backend CRUD api
This commit is contained in:
@@ -2,9 +2,10 @@
|
||||
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 { mockAssistants, mockKnowledgeBases, mockVoices, mockLLMModels, mockASRModels } from '../services/mockData';
|
||||
import { Assistant, TabValue } from '../types';
|
||||
import { mockLLMModels, mockASRModels } from '../services/mockData';
|
||||
import { Assistant, KnowledgeBase, TabValue, Voice } from '../types';
|
||||
import { GoogleGenAI } from "@google/genai";
|
||||
import { createAssistant, deleteAssistant, fetchAssistants, fetchKnowledgeBases, fetchVoices, updateAssistant as updateAssistantApi } from '../services/backendApi';
|
||||
|
||||
interface ToolItem {
|
||||
id: string;
|
||||
@@ -16,7 +17,9 @@ interface ToolItem {
|
||||
}
|
||||
|
||||
export const AssistantsPage: React.FC = () => {
|
||||
const [assistants, setAssistants] = useState<Assistant[]>(mockAssistants);
|
||||
const [assistants, setAssistants] = useState<Assistant[]>([]);
|
||||
const [voices, setVoices] = useState<Voice[]>([]);
|
||||
const [knowledgeBases, setKnowledgeBases] = useState<KnowledgeBase[]>([]);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const [activeTab, setActiveTab] = useState<TabValue>(TabValue.GLOBAL);
|
||||
@@ -40,6 +43,7 @@ export const AssistantsPage: React.FC = () => {
|
||||
const [deleteId, setDeleteId] = useState<string | null>(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;
|
||||
|
||||
@@ -47,39 +51,69 @@ export const AssistantsPage: React.FC = () => {
|
||||
a.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
|
||||
const handleCreate = () => {
|
||||
const newId = Math.floor(Math.random() * 1000000).toString().padStart(6, '0');
|
||||
const newAssistant: Assistant = {
|
||||
id: newId,
|
||||
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<Assistant> = {
|
||||
name: 'New Assistant',
|
||||
callCount: 0,
|
||||
opener: '',
|
||||
prompt: '',
|
||||
knowledgeBaseId: '',
|
||||
language: 'zh',
|
||||
voice: mockVoices[0]?.id || '',
|
||||
voice: voices[0]?.id || '',
|
||||
speed: 1,
|
||||
hotwords: [],
|
||||
tools: [],
|
||||
interruptionSensitivity: 500,
|
||||
configMode: 'platform',
|
||||
llmModelId: '',
|
||||
asrModelId: '',
|
||||
embeddingModelId: '',
|
||||
rerankModelId: '',
|
||||
};
|
||||
setAssistants([...assistants, newAssistant]);
|
||||
setSelectedId(newId);
|
||||
setActiveTab(TabValue.GLOBAL);
|
||||
try {
|
||||
const created = await createAssistant(newAssistantPayload);
|
||||
setAssistants((prev) => [created, ...prev]);
|
||||
setSelectedId(created.id);
|
||||
setActiveTab(TabValue.GLOBAL);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert('创建助手失败。');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
const handleSave = async () => {
|
||||
if (!selectedAssistant) return;
|
||||
setSaveLoading(true);
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
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);
|
||||
// In a real app, logic to persist selectedAssistant would go here
|
||||
}, 800);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopyId = (id: string, text?: string) => {
|
||||
@@ -88,11 +122,18 @@ export const AssistantsPage: React.FC = () => {
|
||||
setTimeout(() => setCopySuccess(false), 2000);
|
||||
};
|
||||
|
||||
const handleCopy = (e: React.MouseEvent, assistant: Assistant) => {
|
||||
const handleCopy = async (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]);
|
||||
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) => {
|
||||
@@ -100,11 +141,17 @@ export const AssistantsPage: React.FC = () => {
|
||||
setDeleteId(id);
|
||||
};
|
||||
|
||||
const confirmDelete = () => {
|
||||
const confirmDelete = async () => {
|
||||
if (deleteId) {
|
||||
setAssistants(prev => prev.filter(a => a.id !== deleteId));
|
||||
if (selectedId === deleteId) setSelectedId(null);
|
||||
setDeleteId(null);
|
||||
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('删除失败,请稍后重试。');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -223,7 +270,7 @@ export const AssistantsPage: React.FC = () => {
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto space-y-2 pr-1 custom-scrollbar">
|
||||
{filteredAssistants.map(assistant => (
|
||||
{!isLoading && filteredAssistants.map(assistant => (
|
||||
<div
|
||||
key={assistant.id}
|
||||
onClick={() => setSelectedId(assistant.id)}
|
||||
@@ -268,11 +315,16 @@ export const AssistantsPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{filteredAssistants.length === 0 && (
|
||||
{!isLoading && filteredAssistants.length === 0 && (
|
||||
<div className="text-center py-10 text-muted-foreground text-sm">
|
||||
未找到小助手
|
||||
</div>
|
||||
)}
|
||||
{isLoading && (
|
||||
<div className="text-center py-10 text-muted-foreground text-sm">
|
||||
加载中...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -545,7 +597,7 @@ export const AssistantsPage: React.FC = () => {
|
||||
onChange={(e) => updateAssistant('knowledgeBaseId', e.target.value)}
|
||||
>
|
||||
<option value="">如果不选择,则使用通用大模型知识</option>
|
||||
{mockKnowledgeBases.map(kb => (
|
||||
{knowledgeBases.map(kb => (
|
||||
<option key={kb.id} value={kb.id}>{kb.name}</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -593,7 +645,7 @@ export const AssistantsPage: React.FC = () => {
|
||||
onChange={(e) => updateAssistant('voice', e.target.value)}
|
||||
>
|
||||
<option value="" disabled>请选择声音库中的声音...</option>
|
||||
{mockVoices.map(voice => (
|
||||
{voices.map(voice => (
|
||||
<option key={voice.id} value={voice.id}>
|
||||
{voice.name} ({voice.vendor} - {voice.gender === 'Male' ? '男' : '女'})
|
||||
</option>
|
||||
|
||||
Reference in New Issue
Block a user