diff --git a/src/components/pages/ComponentsModelsPage.tsx b/src/components/pages/ComponentsModelsPage.tsx index d4561a1..e13f073 100644 --- a/src/components/pages/ComponentsModelsPage.tsx +++ b/src/components/pages/ComponentsModelsPage.tsx @@ -1,11 +1,612 @@ -import { PlaceholderPage } from "./PlaceholderPage"; +"use client"; + +import { + Brain, + ChevronLeft, + ChevronRight, + Eye, + EyeOff, + MoreHorizontal, + Pencil, + Plus, + Search, + Trash2, +} from "lucide-react"; + +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { Input } from "@/components/ui/input"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { useState } from "react"; + +type ModelType = "LLM" | "ASR" | "TTS" | "Realtime" | "Embedding"; + +type InterfaceType = "openai" | "xfyun" | "dashscope" | "gemini"; + +/** 各资源类型可选的接口类型 */ +const interfaceOptionsByType: Record = { + LLM: ["openai"], + ASR: ["openai", "xfyun", "dashscope"], + TTS: ["openai", "xfyun", "dashscope"], + Realtime: ["openai", "gemini"], + Embedding: ["openai"], +}; + +const modelTypes: ModelType[] = ["LLM", "ASR", "TTS", "Realtime", "Embedding"]; + +type ModelResource = { + id: string; + name: string; + type: ModelType; + interfaceType: InterfaceType; + apiUrl: string; + apiKey: string; +}; + +const mockModels: ModelResource[] = [ + { + id: "model_001", + name: "DeepSeek-V3", + type: "LLM", + interfaceType: "openai", + apiUrl: "https://api.deepseek.com/v1", + apiKey: "sk-deepseek-7f3a9c2e1b", + }, + { + id: "model_002", + name: "Qwen-Max", + type: "LLM", + interfaceType: "openai", + apiUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1", + apiKey: "sk-qwen-4d8e2a6f0c", + }, + { + id: "model_003", + name: "讯飞语音识别", + type: "ASR", + interfaceType: "xfyun", + apiUrl: "https://iat-api.xfyun.cn/v2/iat", + apiKey: "xf-asr-9b1c3d5e7a", + }, + { + id: "model_004", + name: "Paraformer 识别", + type: "ASR", + interfaceType: "dashscope", + apiUrl: "https://dashscope.aliyuncs.com/api/v1/services/audio/asr", + apiKey: "sk-paraformer-2e4f6a", + }, + { + id: "model_005", + name: "讯飞语音合成", + type: "TTS", + interfaceType: "xfyun", + apiUrl: "https://tts-api.xfyun.cn/v2/tts", + apiKey: "xf-tts-6c8a0b2d4f", + }, + { + id: "model_006", + name: "CosyVoice 合成", + type: "TTS", + interfaceType: "dashscope", + apiUrl: "https://dashscope.aliyuncs.com/api/v1/services/audio/tts", + apiKey: "sk-cosyvoice-1a3c5e", + }, + { + id: "model_007", + name: "OpenAI TTS", + type: "TTS", + interfaceType: "openai", + apiUrl: "https://api.openai.com/v1/audio/speech", + apiKey: "sk-openai-tts-8f0a2c", + }, + { + id: "model_008", + name: "GPT Realtime", + type: "Realtime", + interfaceType: "openai", + apiUrl: "https://api.openai.com/v1/realtime", + apiKey: "sk-realtime-3b5d7f9a1c", + }, + { + id: "model_009", + name: "Gemini Live", + type: "Realtime", + interfaceType: "gemini", + apiUrl: "https://generativelanguage.googleapis.com/v1beta", + apiKey: "gm-live-5e7a9c1b3d", + }, + { + id: "model_010", + name: "text-embedding-3", + type: "Embedding", + interfaceType: "openai", + apiUrl: "https://api.openai.com/v1/embeddings", + apiKey: "sk-embed-0c2e4a6f8b", + }, + { + id: "model_011", + name: "Kimi-K2", + type: "LLM", + interfaceType: "openai", + apiUrl: "https://api.moonshot.cn/v1", + apiKey: "sk-kimi-7a9c1e3b5d", + }, + { + id: "model_012", + name: "BGE Embedding", + type: "Embedding", + interfaceType: "openai", + apiUrl: "https://api.siliconflow.cn/v1/embeddings", + apiKey: "sk-bge-2d4f6a8c0e", + }, +]; + +type ModelForm = { + name: string; + type: ModelType; + interfaceType: InterfaceType; + apiUrl: string; + apiKey: string; +}; + +const emptyForm: ModelForm = { + name: "", + type: "LLM", + interfaceType: "openai", + apiUrl: "", + apiKey: "", +}; + +type TypeFilter = "全部" | ModelType; + +const typeFilters: TypeFilter[] = ["全部", ...modelTypes]; export function ComponentsModelsPage() { + const [searchQuery, setSearchQuery] = useState(""); + const [typeFilter, setTypeFilter] = useState("全部"); + const [currentPage, setCurrentPage] = useState(1); + + const [dialogOpen, setDialogOpen] = useState(false); + const [editingId, setEditingId] = useState(null); + const [form, setForm] = useState(emptyForm); + const [showKey, setShowKey] = useState(false); + + function updateForm(key: K, value: ModelForm[K]) { + setForm((prev) => ({ ...prev, [key]: value })); + } + + function handleTypeChange(type: ModelType) { + const options = interfaceOptionsByType[type]; + setForm((prev) => ({ + ...prev, + type, + // 资源类型变化时,若当前接口类型不在新类型的可选项内则重置为首项 + interfaceType: options.includes(prev.interfaceType) + ? prev.interfaceType + : options[0], + })); + } + + function openCreate() { + setEditingId(null); + setForm(emptyForm); + setShowKey(false); + setDialogOpen(true); + } + + function openEdit(model: ModelResource) { + setEditingId(model.id); + setForm({ + name: model.name, + type: model.type, + interfaceType: model.interfaceType, + apiUrl: model.apiUrl, + apiKey: model.apiKey, + }); + setShowKey(false); + setDialogOpen(true); + } + + const filteredModels = mockModels.filter((model) => { + if (typeFilter !== "全部" && model.type !== typeFilter) { + return false; + } + + const keyword = searchQuery.trim().toLowerCase(); + if (!keyword) { + return true; + } + + return [model.name, model.id, model.type, model.interfaceType, model.apiUrl] + .join(" ") + .toLowerCase() + .includes(keyword); + }); + + const pageSize = 5; + const totalPages = Math.max(1, Math.ceil(filteredModels.length / pageSize)); + const safeCurrentPage = Math.min(currentPage, totalPages); + const pageStart = (safeCurrentPage - 1) * pageSize; + const pageEnd = pageStart + pageSize; + const paginatedModels = filteredModels.slice(pageStart, pageEnd); + + function handleSearchChange(value: string) { + setSearchQuery(value); + setCurrentPage(1); + } + + function handleFilterChange(filter: TypeFilter) { + setTypeFilter(filter); + setCurrentPage(1); + } + + const interfaceOptions = interfaceOptionsByType[form.type]; + const canSave = form.name.trim() && form.apiUrl.trim(); + return ( - +
+
+
+
资源管理
+

模型资源

+

+ 统一管理 LLM、ASR、TTS、Realtime 与 Embedding 模型,配置各自的接口类型与访问凭证。 +

+
+ + +
+ +
+
+
+
+ 模型列表 +
+
+ 共 {mockModels.length} 个模型 + {(searchQuery.trim() || typeFilter !== "全部") && + `,已筛选 ${filteredModels.length} 个`} +
+
+ +
+ + handleSearchChange(event.target.value)} + className="h-10 border-hairline-strong bg-background pl-9 text-sm text-foreground placeholder:text-muted-soft" + placeholder="搜索模型名称、接口类型或 URL..." + /> +
+
+ +
+ {typeFilters.map((filter) => ( + + ))} +
+ +
+
+
模型名称
+
+ 资源类型 +
+
+ 接口类型 +
+
+ API URL +
+
+ 操作 +
+
+ +
+ {paginatedModels.map((model) => ( +
+
+
+ {model.name} +
+
{model.id}
+
+ +
+ + {model.type} + +
+ +
+ + {model.interfaceType} + +
+ +
+ {model.apiUrl} +
+ +
+ + + + + + + + + + 删除 + + + +
+
+ ))} + + {filteredModels.length === 0 && ( +
+
+ 未找到匹配的模型 +
+
+ 请调整关键词或筛选条件后再试。 +
+
+ )} +
+
+ +
+
+ {filteredModels.length === 0 + ? "没有数据" + : `显示 ${pageStart + 1}-${Math.min(pageEnd, filteredModels.length)} / 共 ${filteredModels.length} 个模型`} +
+ +
+ + + {Array.from({ length: totalPages }, (_, index) => index + 1).map( + (page) => ( + + ), + )} + + +
+
+
+ + + + + {editingId ? "编辑模型" : "添加模型"} + + 填写模型名称、资源类型、接口类型与访问凭证。 + + + +
+ + +
+
+
+ 资源类型 +
+ +
+ +
+
+ 接口类型 +
+ +
+
+ + + + +
+ + + + + +
+
+
); }