feat: add ModelAccess to settings page (#29)

* feat: Update site name to DataMate and refine text for AI data processing

* feat: Refactor settings page and implement model access functionality

- Created a new ModelAccess component for managing model configurations.
- Removed the old Settings component and replaced it with a new SettingsPage component that integrates ModelAccess, SystemConfig, and WebhookConfig.
- Added SystemConfig component for managing system settings.
- Implemented WebhookConfig component for managing webhook configurations.
- Updated API functions for model management in settings.apis.ts.
- Adjusted routing to point to the new SettingsPage component.
This commit is contained in:
chenghh-9609
2025-10-28 16:02:18 +08:00
committed by GitHub
parent a4b5238621
commit acafe70d90
14 changed files with 662 additions and 444 deletions

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/huawei-logo.webp" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ML Dataset Tool</title>
<title>DataMate</title>
</head>
<body>
<div id="root"></div>

View File

@@ -2,10 +2,11 @@ const { addMockPrefix } = require("./mock-core/util.cjs");
const MockAPI = {
// 数据归集接口
queryTasksUsingPost: "/data-collection/tasks", // 获取数据源任务列表
queryTasksUsingGet: "/data-collection/tasks", // 获取数据源任务列表
createTaskUsingPost: "/data-collection/tasks/create", // 创建数据源任务
queryTaskByIdUsingGet: "/data-collection/tasks/:id", // 根据ID获取数据源任务详情
updateTaskByIdUsingPut: "/data-collection/tasks/:id", // 更新数据源任务
queryDataXTemplatesUsingGet: "/data-collection/templates", // 获取DataX数据源模板列表
deleteTaskByIdUsingDelete: "/data-collection/tasks/:id", // 删除数据源任务
executeTaskByIdUsingPost: "/data-collection/tasks/:id/execute", // 执行数据源任务
stopTaskByIdUsingPost: "/data-collection/tasks/:id/stop", // 停止数据源任务
@@ -91,7 +92,6 @@ const MockAPI = {
deleteInstructionTemplateByIdUsingDelete: "/synthesis/templates/:templateId", // 删除指令模板
instructionTuningUsingPost: "/synthesis/instruction-tuning", // 指令微调
cotDistillationUsingPost: "/synthesis/cot-distillation", // Cot蒸馏
queryOperatorsUsingPost: "/synthesis/operators", // 获取操作列表
// 数据评测接口
queryEvaluationTasksUsingPost: "/evaluation/tasks", // 获取评测任务列表
@@ -144,6 +144,14 @@ const MockAPI = {
deleteOperatorByIdUsingDelete: "/operators/:operatorId", // 删除算子
publishOperatorUsingPost: "/operators/:operatorId/publish", // 发布算子
unpublishOperatorUsingPost: "/operators/:operatorId/unpublish", // 下架算子
// 设置接口
queryModelsUsingGet: "/models/list", // 获取模型列表
queryProvidersUsingGet: "/models/providers", // 获取模型提供商列表
createModelUsingPost: "/models/create", // 创建模型
updateModelUsingPut: "/models/:id", // 更新模型
deleteModelUsingDelete: "/models/:id", // 删除模型
};
module.exports = addMockPrefix("/api", MockAPI);

View File

@@ -16,8 +16,8 @@ function datasetItem() {
return {
id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
name: Mock.Random.ctitle(5, 20),
type: Mock.Random.pick(["TEXT", "IMAGE", "AUDIO", "VIDEO"]),
status: Mock.Random.pick(["ACTIVE", "INACTIVE", "PROCESSING"]),
datasetType: Mock.Random.pick(["TEXT", "IMAGE", "AUDIO", "VIDEO"]),
status: Mock.Random.pick(["DRAFT","ACTIVE", "INACTIVE", "PROCESSING"]),
tags: Mock.Random.shuffle(tagList).slice(0, Mock.Random.integer(1, 3)),
totalSize: Mock.Random.integer(1024, 1024 * 1024 * 1024), // in bytes
description: Mock.Random.cparagraph(1, 3),
@@ -164,7 +164,7 @@ module.exports = function (router) {
console.log("filter type:", type);
filteredDatasets = filteredDatasets.filter(
(dataset) => dataset.type === type
(dataset) => dataset.datasetType === type
);
}
if (status) {

View File

@@ -0,0 +1,177 @@
const Mock = require("mockjs");
const API = require("../mock-apis.cjs");
// 算子标签数据
function ModelItem() {
return {
id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
modelName: Mock.Random.pick([
"数据清洗",
"特征选择",
"分类算法",
"聚类算法",
"回归分析",
"深度神经网络",
"卷积神经网络",
"循环神经网络",
"注意力机制",
"文本分析",
"图像处理",
"语音识别",
"推荐算法",
"异常检测",
"优化算法",
"集成学习",
"迁移学习",
"强化学习",
"联邦学习",
]),
provider: Mock.Random.pick([
"OpenAI",
"Anthropic",
"Cohere",
"AI21 Labs",
"Hugging Face",
"Google Cloud AI",
"Microsoft Azure AI",
"Amazon Web Services AI",
"IBM Watson",
"Alibaba Cloud AI",
]),
type: Mock.Random.pick(["CHAT", "EMBEDDING"]),
usageCount: Mock.Random.integer(1, 500),
createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
updatedAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
};
}
const modelList = new Array(50).fill(null).map(ModelItem);
function ProviderItem(provider) {
return {
id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
provider,
baseUrl: Mock.Random.url("https") + "/v1/models",
createdAt: Mock.Random.datetime("yyyy-MM-dd HH:mm:ss"),
};
}
const ProviderList = [
"OpenAI",
"Anthropic",
"Cohere",
"AI21 Labs",
"Hugging Face",
"Google Cloud AI",
"Microsoft Azure AI",
"Amazon Web Services AI",
"IBM Watson",
"Alibaba Cloud AI",
].map(ProviderItem);
module.exports = function (router) {
// 获取模型列表
router.get(API.queryModelsUsingGet, (req, res) => {
const {
page = 0,
size = 20,
keyword = "",
provider = "",
type = "",
} = req.query;
let filteredModels = modelList;
if (keyword) {
filteredModels = modelList.filter((model) =>
model.modelName.toLowerCase().includes(keyword.toLowerCase())
);
}
if (provider && provider !== "all") {
filteredModels = filteredModels.filter(
(model) => model.provider === provider
);
}
if (type && type !== "all") {
filteredModels = filteredModels.filter((model) => model.type === type);
}
const startIndex = page * size;
const endIndex = startIndex + parseInt(size);
const pageData = filteredModels.slice(startIndex, endIndex);
res.status(201).send({
code: "0",
msg: "Success",
data: {
content: pageData,
totalElements: filteredModels.length,
totalPages: Math.ceil(filteredModels.length / size),
size: parseInt(size),
number: parseInt(page),
},
});
});
// 获取模型提供商列表
router.get(API.queryProvidersUsingGet, (req, res) => {
res.status(201).send({
code: "0",
msg: "success",
data: ProviderList,
});
});
// 创建模型
router.post(API.createModelUsingPost, (req, res) => {
const { ...modelData } = req.body;
const newModel = {
id: Mock.Random.guid().replace(/[^a-zA-Z0-9]/g, ""),
...modelData,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
modelList.unshift(newModel);
res.status(201).send({
code: "0",
msg: "success",
data: newModel,
});
});
// 删除模型
router.delete(API.deleteModelUsingDelete, (req, res) => {
const { id } = req.params;
const index = modelList.findIndex((model) => model.id === id);
if (index !== -1) {
modelList.splice(index, 1);
}
res.status(204).send({
code: "0",
msg: "success",
data: null,
});
});
// 更新模型
router.put(API.updateModelUsingPut, (req, res) => {
const { id, ...update } = req.params;
const index = modelList.findIndex((model) => model.id === id);
if (index !== -1) {
modelList[index] = {
...modelList[index],
...update,
updatedAt: new Date().toISOString(),
};
}
res.status(201).send({
code: "0",
msg: "success",
data: null,
});
});
};

View File

@@ -51,11 +51,11 @@ export default function DatasetManagementPage() {
},
{
title: "文件总数",
value: data?.totalFiles || "0 MB",
value: data?.totalFiles || 0,
},
{
title: "总大小",
value: formatBytes(data?.totalSize) || 0,
value: formatBytes(data?.totalSize) || '0 B',
},
],
count: [

View File

@@ -29,8 +29,7 @@ export default function WelcomePage() {
<span className="text-blue-600"> AI数据集</span>
</h1>
<p className="text-xl text-gray-600 max-w-3xl mx-auto mb-8">
RAG知识库构建
AI数据处理的场景问题
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<span

View File

@@ -0,0 +1,375 @@
import {
Card,
Button,
Form,
Input,
Modal,
Select,
Table,
Tooltip,
Popconfirm,
message,
} from "antd";
import {
EditOutlined,
DeleteOutlined,
ReloadOutlined,
PlusOutlined,
} from "@ant-design/icons";
import { useEffect, useState } from "react";
import { SearchControls } from "@/components/SearchControls";
import useFetchData from "@/hooks/useFetchData";
import {
createModelUsingPost,
deleteModelByIdUsingDelete,
queryModelListUsingGet,
queryModelProvidersUsingGet,
updateModelByIdUsingPut,
} from "./settings.apis";
interface ModelI {
id: string;
name: string;
provider: string;
model: string;
apiKey: string;
isEnabled: boolean;
createdAt: string;
updatedAt: string;
createdBy: string;
updatedBy: string;
}
interface ProviderI {
id: string;
modelName: string;
value: string;
label: string;
baseUrl: string;
provider: string;
apiKey: string;
type: string;
isEnabled: boolean;
}
export default function EnvironmentAccess() {
const [form] = Form.useForm();
const [showModelDialog, setShowModelDialog] = useState(false);
const [isEditMode, setIsEditMode] = useState(false);
const [newModel, setNewModel] = useState({
name: "",
provider: "openai",
model: "",
apiKey: "",
endpoint: "",
});
const [typeOptions] = useState([
{ value: "CHAT", label: "CHAT" },
{ value: "EMBEDDING", label: "EMBEDDING" },
]);
const {
loading,
tableData,
pagination,
searchParams,
setSearchParams,
fetchData,
handleFiltersChange,
} = useFetchData(queryModelListUsingGet);
const handleAddModel = async () => {
const formValues = await form.validateFields();
const fn = isEditMode
? () => updateModelByIdUsingPut(newModel.id, formValues)
: () => createModelUsingPost(formValues);
await fn();
setShowModelDialog(false);
fetchData();
message.success("模型添加成功");
};
const [providerOptions, setProviderOptions] = useState<ProviderI[]>([]);
const fetchProviderOptions = async () => {
const { data } = await queryModelProvidersUsingGet();
setProviderOptions(
data.map((provider: ProviderI) => ({
...provider,
value: provider.provider,
label: provider.provider,
}))
);
};
const generateApiKey = () => {
const chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "sk-";
for (let i = 0; i < 48; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
};
const handleDeleteModel = async (modelId: string) => {
await deleteModelByIdUsingDelete(modelId);
fetchData();
};
useEffect(() => {
fetchProviderOptions();
}, []);
const columns = [
{
title: "模型名称",
dataIndex: "modelName",
key: "modelName",
fixed: "left",
width: 200,
ellipsis: true,
},
{
title: "创建时间",
dataIndex: "createdAt",
key: "createdAt",
ellipsis: true,
},
{
title: "模型提供商",
dataIndex: "provider",
key: "provider",
ellipsis: true,
},
{
title: "模型类型",
dataIndex: "type",
key: "type",
ellipsis: true,
},
{
title: "更新时间",
dataIndex: "updatedAt",
key: "updatedAt",
ellipsis: true,
},
{
title: "操作",
key: "action",
fixed: "right" as const,
ellipsis: true,
render: (_: any, record: ModelI) => {
return [
{
key: "edit",
label: "编辑",
icon: <EditOutlined />,
onClick: () => {
setIsEditMode(true);
setNewModel(record);
form.setFieldsValue(record);
setShowModelDialog(true);
},
},
{
key: "delete",
label: "删除",
danger: true,
icon: <DeleteOutlined />,
confirm: {
title: "确定要删除该任务吗?此操作不可撤销。",
okText: "删除",
cancelText: "取消",
okType: "danger",
},
onClick: () => handleDeleteModel(record.id),
},
].map((op) => {
const button = (
<Tooltip key={op.key} title={op.label}>
<Button
type="text"
icon={op.icon}
danger={op?.danger}
onClick={() => op.onClick(record)}
/>
</Tooltip>
);
if (op.confirm) {
return (
<Popconfirm
key={op.key}
title={op.confirm.title}
okText={op.confirm.okText}
cancelText={op.confirm.cancelText}
okType={op.danger ? "danger" : "primary"}
onConfirm={() => op.onClick(record)}
>
<Tooltip key={op.key} title={op.label}>
<Button type="text" icon={op.icon} danger={op?.danger} />
</Tooltip>
</Popconfirm>
);
}
return button;
});
},
},
];
return (
<>
<div className="flex items-top justify-between">
<h2 className="text-lg font-medium mb-4"></h2>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => {
setIsEditMode(false);
form.resetFields();
setNewModel({
name: "",
provider: "",
model: "",
apiKey: "",
endpoint: "",
});
setShowModelDialog(true);
}}
>
</Button>
</div>
<SearchControls
searchTerm={searchParams.keyword}
onSearchChange={(newSearchTerm) =>
setSearchParams((prev) => ({
...prev,
keyword: newSearchTerm,
current: 1,
}))
}
searchPlaceholder="搜索模型描述..."
filters={[
{
key: "provider",
label: "模型提供商",
options: [{ value: "all", label: "全部" }, ...providerOptions],
},
{
key: "type",
label: "模型类型",
options: [{ value: "all", label: "全部" }, ...typeOptions],
},
]}
onFiltersChange={handleFiltersChange}
showViewToggle={false}
onReload={fetchData}
onClearFilters={() =>
setSearchParams((prev) => ({
...prev,
filters: {},
}))
}
className="mb-4"
/>
<Card>
<Table
rowKey="id"
columns={columns}
dataSource={tableData}
loading={loading}
pagination={pagination}
scroll={{ x: "max-content", y: "calc(100vh - 26rem)" }}
/>
</Card>
<Modal
open={showModelDialog}
onCancel={() => setShowModelDialog(false)}
title={isEditMode ? "编辑模型" : "添加模型"}
footer={[
<Button key="cancel" onClick={() => setShowModelDialog(false)}>
</Button>,
<Button key="ok" type="primary" onClick={handleAddModel}>
</Button>,
]}
>
<Form
form={form}
onValuesChange={(changedValues) => {
setNewModel({ ...newModel, ...changedValues });
}}
layout="vertical"
>
<Form.Item
name="modelName"
label="模型名称"
required
rules={[{ required: true, message: "请输入模型名称" }]}
>
<Input placeholder="输入模型名称" />
</Form.Item>
<Form.Item
name="provider"
label="服务提供商"
required
rules={[{ required: true, message: "请选择服务提供商" }]}
>
<Select
placeholder="选择服务提供商"
options={providerOptions}
onChange={(value) => {
const selectedProvider = providerOptions.find(
(p) => p.value === value
);
form.setFieldsValue({ baseUrl: selectedProvider?.baseUrl });
}}
></Select>
</Form.Item>
<Form.Item
name="baseUrl"
label="接口地址"
required
rules={[
{ required: true, message: "请输入接口地址" },
{
pattern: /^https?:\/\/.+/,
message: "请输入有效的URL地址,必须以http://或https://开头",
},
]}
>
<Input placeholder="输入接口地址,如:https://api.openai.com" />
</Form.Item>
<Form.Item
name="apiKey"
label="API密钥"
required
rules={[{ required: true, message: "请输入API密钥" }]}
>
<Input
placeholder="输入或生成API密钥"
addonAfter={
<ReloadOutlined
onClick={() => {
form.setFieldsValue({ apiKey: generateApiKey() });
setNewModel({ ...newModel, apiKey: generateApiKey() });
}}
/>
}
/>
</Form.Item>
<Form.Item
name="type"
label="模型类型"
required
rules={[{ required: true, message: "请选择模型类型" }]}
>
<Select options={typeOptions} placeholder="选择模型类型"></Select>
</Form.Item>
</Form>
</Modal>
</>
);
}

View File

@@ -1,66 +0,0 @@
import { useState } from "react";
import { Tabs } from "antd";
import {
SettingOutlined,
DatabaseOutlined,
ApiOutlined,
} from "@ant-design/icons";
import DevelopmentInProgress from "@/components/DevelopmentInProgress";
import WebhookConfig from "./components/WebhookConfig";
import EnvironmentAccess from "./components/EnvironmentAccess";
import SystemConfig from "./components/SystemConfig";
export default function SettingsPage() {
return <DevelopmentInProgress />;
const [activeTab, setActiveTab] = useState("system");
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-xl font-bold text-gray-900"></h1>
</div>
</div>
{/* Settings Tabs */}
<Tabs
activeKey={activeTab}
onChange={setActiveTab}
className="space-y-6"
items={[
{
key: "system",
label: (
<span>
<SettingOutlined className="mr-1" />
</span>
),
children: <SystemConfig />,
},
{
key: "environment",
label: (
<span>
<DatabaseOutlined className="mr-1" />
</span>
),
children: <EnvironmentAccess />,
},
{
key: "webhook",
label: (
<span>
<ApiOutlined className="mr-1" />
Webhook
</span>
),
children: <WebhookConfig />,
},
]}
/>
</div>
);
}

View File

@@ -0,0 +1,60 @@
import { useState } from "react";
import { Tabs } from "antd";
import { SettingOutlined, ApiOutlined } from "@ant-design/icons";
import WebhookConfig from "./WebhookConfig";
import ModelAccess from "./ModelAccess";
import SystemConfig from "./SystemConfig";
import { Component } from "lucide-react";
export default function SettingsPage() {
const [activeTab, setActiveTab] = useState("modelAccess");
return (
<div className="h-full flex flex-col gap-4">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-xl font-bold text-gray-900"></h1>
</div>
</div>
{/* Settings Tabs */}
<Tabs
activeKey={activeTab}
onChange={setActiveTab}
items={[
// {
// key: "system",
// label: (
// <span>
// <SettingOutlined className="mr-1" />
// 系统设置
// </span>
// ),
// children: <SystemConfig />,
// },
{
key: "modelAccess",
label: (
<span className="flex items-center">
<Component className="w-4 h-4 mr-1" />
</span>
),
children: <ModelAccess key="modelAccess" />,
},
// {
// key: "webhook",
// label: (
// <span>
// <ApiOutlined className="mr-1" />
// Webhook
// </span>
// ),
// children: <WebhookConfig />,
// },
]}
/>
</div>
);
}

View File

@@ -5,7 +5,7 @@ export default function SystemConfig() {
const { message } = App.useApp();
// System Settings State
const [systemConfig, setSystemConfig] = useState({
siteName: "ML Dataset Tool",
siteName: "DataMate",
maxFileSize: "100",
autoBackup: true,
logLevel: "info",

View File

@@ -1,365 +0,0 @@
import { Card, Button, Form, Input, Modal, Select, Badge } from "antd";
import {
EditOutlined,
DeleteOutlined,
ReloadOutlined,
ExperimentOutlined,
EyeOutlined,
EyeInvisibleOutlined,
CopyOutlined,
} from "@ant-design/icons";
import { useState } from "react";
interface VectorDBConfig {
id: string;
name: string;
type: "pinecone" | "weaviate" | "qdrant" | "milvus" | "chroma";
url: string;
apiKey: string;
dimension: number;
metric: string;
status: "connected" | "disconnected" | "error";
}
interface ModelConfig {
id: string;
name: string;
provider: "openai" | "anthropic" | "google" | "azure" | "local";
model: string;
apiKey: string;
endpoint?: string;
status: "active" | "inactive";
usage: number;
}
export default function EnvironmentAccess() {
const [form] = Form.useForm();
const [showApiKey, setShowApiKey] = useState<{ [key: string]: boolean }>({});
const [showVectorDBDialog, setShowVectorDBDialog] = useState(false);
const [showModelDialog, setShowModelDialog] = useState(false);
const [vectorDBs, setVectorDBs] = useState<VectorDBConfig[]>([
{
id: "1",
name: "Pinecone Production",
type: "pinecone",
url: "https://your-index.svc.us-east1-gcp.pinecone.io",
apiKey: "pc-****-****-****",
dimension: 1536,
metric: "cosine",
status: "connected",
},
{
id: "2",
name: "Weaviate Local",
type: "weaviate",
url: "http://localhost:8080",
apiKey: "",
dimension: 768,
metric: "cosine",
status: "disconnected",
},
]);
const [providerOptions] = useState([
{ value: "openai", label: "OpenAI" },
{ value: "anthropic", label: "Anthropic" },
{ value: "google", label: "Google" },
{ value: "azure", label: "Azure" },
{ value: "local", label: "本地部署" },
]);
const [models, setModels] = useState<ModelConfig[]>([
{
id: "1",
name: "GPT-4 Turbo",
provider: "openai",
model: "gpt-4-turbo-preview",
apiKey: "sk-****-****-****",
status: "active",
usage: 85,
},
{
id: "2",
name: "Claude 3 Sonnet",
provider: "anthropic",
model: "claude-3-sonnet-20240229",
apiKey: "sk-ant-****-****",
status: "active",
usage: 42,
},
]);
const [dbOptions] = useState([
{ value: "pinecone", label: "Pinecone" },
{ value: "weaviate", label: "Weaviate" },
{ value: "qdrant", label: "Qdrant" },
{ value: "milvus", label: "Milvus" },
{ value: "chroma", label: "Chroma" },
]);
const [metricOptions] = useState([
{ value: "cosine", label: "Cosine" },
{ value: "euclidean", label: "Euclidean" },
{ value: "dotproduct", label: "Dot Product" },
]);
const [newVectorDB, setNewVectorDB] = useState({
name: "",
type: "pinecone",
url: "",
apiKey: "",
dimension: 1536,
metric: "cosine",
});
const [newModel, setNewModel] = useState({
name: "",
provider: "openai",
model: "",
apiKey: "",
endpoint: "",
});
const handleAddVectorDB = () => {
setNewVectorDB({
name: "",
type: "pinecone",
url: "",
apiKey: "",
dimension: 1536,
metric: "cosine",
});
setShowVectorDBDialog(true);
};
const handleAddModel = () => {
setNewModel({
name: "",
provider: "openai",
model: "",
apiKey: generateApiKey(),
endpoint: "",
});
setShowModelDialog(true);
};
const toggleApiKeyVisibility = (id: string) => {
setShowApiKey((prev) => ({ ...prev, [id]: !prev[id] }));
};
const generateApiKey = () => {
const chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "sk-";
for (let i = 0; i < 48; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
};
return (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Card>
<div className="flex items-top justify-between">
<h2 className="text-base font-medium mb-4"></h2>
<Button type="link" onClick={handleAddVectorDB}>
</Button>
</div>
<div className="flex flex-col gap-3">
{vectorDBs.map((db) => (
<Card key={db.id} className="border rounded-lg p-4 space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="font-medium">{db.name}</span>
<Badge
status={
db.status === "connected"
? "success"
: db.status === "error"
? "error"
: "default"
}
text={
db.status === "connected"
? "已连接"
: db.status === "error"
? "异常"
: "未连接"
}
/>
</div>
<div className="flex items-center gap-1">
<Button icon={<ExperimentOutlined />} size="small" />
<Button icon={<EditOutlined />} size="small" />
<Button icon={<DeleteOutlined />} size="small" danger />
</div>
</div>
<div className="text-sm text-gray-600 space-y-1">
<p>: {db.type}</p>
<p>: {db.url}</p>
<p>
: {db.dimension} | : {db.metric}
</p>
</div>
</Card>
))}
</div>
</Card>
<Card>
<div className="flex items-top justify-between">
<h2 className="text-base font-medium mb-4"></h2>
<Button type="link" onClick={handleAddModel}>
</Button>
</div>
<div className="flex flex-col gap-3">
{models.map((model) => (
<Card key={model.id} className="border rounded-lg p-4 space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="font-medium">{model.name}</span>
<Badge
status={model.status === "active" ? "success" : "default"}
text={model.status === "active" ? "启用" : "禁用"}
/>
</div>
<div className="flex items-center gap-1">
<Button
icon={
showApiKey[model.id] ? (
<EyeInvisibleOutlined />
) : (
<EyeOutlined />
)
}
size="small"
onClick={() => toggleApiKeyVisibility(model.id)}
/>
<Button icon={<ReloadOutlined />} size="small" />
<Button icon={<EditOutlined />} size="small" />
<Button icon={<DeleteOutlined />} size="small" danger />
</div>
</div>
<div className="text-sm text-gray-600 space-y-1">
<p>: {model.provider}</p>
<p>: {model.model}</p>
<div className="flex items-center gap-2">
<span>API Key:</span>
<code className="bg-gray-100 px-2 py-1 rounded text-xs">
{showApiKey[model.id] ? model.apiKey : "sk-****-****-****"}
</code>
<Button
icon={<CopyOutlined />}
size="small"
onClick={() => navigator.clipboard.writeText(model.apiKey)}
/>
</div>
<div className="flex items-center gap-2">
<span>使:</span>
<div className="flex-1 bg-gray-200 rounded-full h-2">
<div
className="bg-blue-500 h-2 rounded-full"
style={{ width: `${model.usage}%` }}
/>
</div>
<span className="text-xs">{model.usage}%</span>
</div>
</div>
</Card>
))}
</div>
</Card>
{/* VectorDB Modal */}
<Modal
open={showVectorDBDialog}
onCancel={() => setShowVectorDBDialog(false)}
title="添加向量数据库"
footer={[
<Button key="cancel" onClick={() => setShowVectorDBDialog(false)}>
</Button>,
<Button
key="ok"
type="primary"
onClick={() => setShowVectorDBDialog(false)}
>
</Button>,
]}
>
<Form layout="vertical">
<Form.Item name="name" label="数据库名称">
<Input placeholder="输入数据库名称" />
</Form.Item>
<Form.Item name="type" label="数据库类型">
<Select options={dbOptions}></Select>
</Form.Item>
<Form.Item name="url" label="连接地址">
<Input placeholder="https://your-index.svc.region.pinecone.io" />
</Form.Item>
<Form.Item name="apiKey" label="API Key">
<Input type="password" placeholder="输入API密钥" />
</Form.Item>
<Form.Item name="dimension" label="向量维度">
<Input type="number" />
</Form.Item>
<Form.Item name="metric" label="距离度量">
<Select options={metricOptions}></Select>
</Form.Item>
</Form>
</Modal>
{/* Model Modal */}
<Modal
open={showModelDialog}
onCancel={() => setShowModelDialog(false)}
title="添加AI模型"
footer={[
<Button key="cancel" onClick={() => setShowModelDialog(false)}>
</Button>,
<Button
key="ok"
type="primary"
onClick={() => setShowModelDialog(false)}
>
</Button>,
]}
>
<Form
form={form}
onValuesChange={(changedValues) => {
setNewModel({ ...newModel, ...changedValues });
}}
layout="vertical"
>
<Form.Item name="name" label="模型名称">
<Input placeholder="输入模型名称" />
</Form.Item>
<Form.Item name="provider" label="服务提供商">
<Select options={providerOptions}></Select>
</Form.Item>
<Form.Item name="model" label="模型标识">
<Input placeholder="gpt-4-turbo-preview" />
</Form.Item>
<Form.Item name="apiKey" label="API Key">
<Input
placeholder="输入或生成API密钥"
addonAfter={
<ReloadOutlined
onClick={() => {
form.setFieldsValue({ apiKey: generateApiKey() });
setNewModel({ ...newModel, apiKey: generateApiKey() });
}}
/>
}
/>
</Form.Item>
{newModel.provider === "local" && (
<Form.Item name="endpoint" label="自定义端点">
<Input placeholder="http://localhost:8000/v1" />
</Form.Item>
)}
</Form>
</Modal>
</div>
);
}

View File

@@ -0,0 +1,30 @@
import { get, post, put, del } from "@/utils/request";
// 模型相关接口
export function queryModelProvidersUsingGet(params?: any) {
return get("/api/models/providers", params);
}
export function queryModelListUsingGet(data: any) {
return get("/api/models/list", data);
}
export function queryModelDetailByIdUsingGet(id: string | number) {
return get(`/api/models/${id}`);
}
export function updateModelByIdUsingPut(
id: string | number,
data: any
) {
return put(`/api/models/${id}`, data);
}
export function createModelUsingPost(data: any) {
return post("/api/models/create", data);
}
export function deleteModelByIdUsingDelete(id: string | number) {
return del(`/api/models/${id}`);
}

View File

@@ -2,7 +2,7 @@ import { createBrowserRouter } from "react-router";
import Home from "../pages/Home/Home";
import MainLayout from "../pages/Layout/MainLayout";
import DataCollection from "@/pages/DataCollection/Home/DataCollection";
import DataCollection from "@/pages/DataCollection/Home/DataCollectionPage";
import CollectionTaskCreate from "@/pages/DataCollection/Create/CreateTask";
import DatasetManagement from "@/pages/DataManagement/Home/DataManagement";
@@ -44,7 +44,7 @@ import CreateRatioTask from "@/pages/RatioTask/CreateRatioTask";
import OrchestrationPage from "@/pages/Orchestration/Orchestration";
import WorkflowEditor from "@/pages/Orchestration/WorkflowEditor";
import AgentPage from "@/pages/Agent/Agent";
import SettingsPage from "@/pages/SettingsPage/Settings";
import SettingsPage from "@/pages/SettingsPage/SettingsPage";
import { withErrorBoundary } from "@/components/ErrorBoundary";
const router = createBrowserRouter([