Frontend start to use backend CRUD api

This commit is contained in:
Xin Wang
2026-02-08 15:08:18 +08:00
parent b9a315177a
commit 86744f0842
12 changed files with 768 additions and 154 deletions

View File

@@ -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>