init datamate

This commit is contained in:
Dallas98
2025-10-21 23:00:48 +08:00
commit 1c97afed7d
692 changed files with 135442 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
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,365 @@
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,86 @@
import { Card, Divider, Input, Select, Switch, Button, Form, App } from "antd";
import { useState } from "react";
export default function SystemConfig() {
const { message } = App.useApp();
// System Settings State
const [systemConfig, setSystemConfig] = useState({
siteName: "ML Dataset Tool",
maxFileSize: "100",
autoBackup: true,
logLevel: "info",
sessionTimeout: "30",
enableNotifications: true,
});
const logLevelOptions = [
{ value: "debug", label: "Debug" },
{ value: "info", label: "Info" },
{ value: "warn", label: "Warning" },
{ value: "error", label: "Error" },
];
const handleSaveSystemSettings = () => {
// Save system settings logic
console.log("Saving system settings:", systemConfig);
message.success("系统设置已保存");
};
return (
<Card>
<Form
onValuesChange={(changedValues) => {
setSystemConfig((prevConfig) => ({
...prevConfig,
...changedValues,
}));
}}
layout="vertical"
>
<div className="grid grid-cols-2 gap-6">
<Form.Item name="siteName" label="站点名称">
<Input />
</Form.Item>
<Form.Item name="maxFileSize" label="最大文件大小 (MB)">
<Input type="number" />
</Form.Item>
<Form.Item name="logLevel" label="日志级别">
<Select options={logLevelOptions}></Select>
</Form.Item>
<Form.Item name="sessionTimeout" label="会话超时 (分钟)">
<Input type="number" />
</Form.Item>
</div>
<Divider />
<div className="space-y-4">
<h4 className="font-medium"></h4>
<div className="space-y-3">
<div className="flex items-center justify-between">
<div>
<span></span>
<p className="text-sm text-gray-500"></p>
</div>
<Form.Item name="autoBackup" valuePropName="checked">
<Switch />
</Form.Item>
</div>
<div className="flex items-center justify-between">
<div>
<span></span>
<p className="text-sm text-gray-500"></p>
</div>
<Form.Item name="enableNotifications" valuePropName="checked">
<Switch />
</Form.Item>
</div>
</div>
</div>
<div className="flex justify-end mt-6">
<Button type="primary" onClick={handleSaveSystemSettings}>
</Button>
</div>
</Form>
</Card>
);
}

View File

@@ -0,0 +1,313 @@
import { Button, Card, Checkbox, Form, Input, Modal, Badge } from "antd";
import {
EditOutlined,
DeleteOutlined,
KeyOutlined,
ReloadOutlined,
ExperimentOutlined,
ThunderboltOutlined,
} from "@ant-design/icons";
import { useState } from "react";
interface WebhookEvent {
id: string;
name: string;
description: string;
category: string;
}
interface WebhookConfig {
id: string;
name: string;
url: string;
events: string[];
status: "active" | "inactive";
secret: string;
retryCount: number;
}
const availableEvents: WebhookEvent[] = [
{
id: "project_created",
name: "项目创建",
description: "新项目被创建时触发",
category: "项目管理",
},
{
id: "project_updated",
name: "项目更新",
description: "项目信息被修改时触发",
category: "项目管理",
},
{
id: "project_deleted",
name: "项目删除",
description: "项目被删除时触发",
category: "项目管理",
},
{
id: "task_created",
name: "任务创建",
description: "新任务被创建时触发",
category: "任务管理",
},
{
id: "task_updated",
name: "任务更新",
description: "任务状态或内容被更新时触发",
category: "任务管理",
},
{
id: "task_completed",
name: "任务完成",
description: "任务被标记为完成时触发",
category: "任务管理",
},
{
id: "annotation_created",
name: "标注创建",
description: "新标注被创建时触发",
category: "标注管理",
},
{
id: "annotation_updated",
name: "标注更新",
description: "标注被修改时触发",
category: "标注管理",
},
{
id: "annotation_deleted",
name: "标注删除",
description: "标注被删除时触发",
category: "标注管理",
},
{
id: "model_trained",
name: "模型训练完成",
description: "模型训练任务完成时触发",
category: "模型管理",
},
{
id: "prediction_created",
name: "预测生成",
description: "新预测结果生成时触发",
category: "预测管理",
},
];
export default function WebhookConfig() {
const [newWebhook, setNewWebhook] = useState({
name: "",
url: "",
events: [] as string[],
secret: "",
retryCount: 3,
});
const [showWebhookDialog, setShowWebhookDialog] = useState(false);
// Webhook State
const [webhooks, setWebhooks] = useState<WebhookConfig[]>([
{
id: "1",
name: "数据同步Webhook",
url: "https://webhook.example.com/data-sync",
events: ["task_created", "task_completed", "annotation_created"],
status: "active",
secret: "wh_secret_123456",
retryCount: 3,
},
{
id: "2",
name: "任务通知Webhook",
url: "https://webhook.example.com/task-notify",
events: ["task_started", "task_completed", "task_failed"],
status: "inactive",
secret: "wh_secret_789012",
retryCount: 5,
},
]);
const handleAddWebhook = () => {
setNewWebhook({
name: "",
url: "",
events: [],
secret: generateApiKey(),
retryCount: 3,
});
setShowWebhookDialog(true);
};
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>
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-lg font-medium">Webhook </h3>
</div>
<Button onClick={handleAddWebhook}>Webhook</Button>
</div>
<div className="grid gap-4">
{webhooks.map((webhook) => (
<Card key={webhook.id}>
<div className="flex items-start justify-between p-6">
<div className="space-y-3">
<div className="flex items-center gap-3">
<span className="font-medium">{webhook.name}</span>
<Badge
status={webhook.status === "active" ? "success" : "default"}
text={webhook.status === "active" ? "启用" : "禁用"}
/>
</div>
<div className="space-y-2">
<p className="text-sm text-gray-600 flex items-center gap-2">
<ThunderboltOutlined />
{webhook.url}
</p>
<div className="flex items-center gap-2 flex-wrap">
<span className="text-sm text-gray-500">:</span>
{webhook.events.map((event) => {
const eventInfo = availableEvents.find(
(e) => e.id === event
);
return (
<Badge
key={event}
status="default"
text={eventInfo?.name || event}
/>
);
})}
</div>
<div className="flex items-center gap-4 text-sm text-gray-500">
<span className="flex items-center gap-1">
<KeyOutlined />
Secret: {webhook.secret.substring(0, 12)}...
</span>
<span className="flex items-center gap-1">
<ReloadOutlined />
: {webhook.retryCount}
</span>
</div>
</div>
</div>
<div className="flex items-center gap-2">
<Button icon={<ExperimentOutlined />} size="small" />
<Button icon={<EditOutlined />} size="small" />
<Button icon={<DeleteOutlined />} size="small" danger />
</div>
</div>
</Card>
))}
</div>
<Modal
open={showWebhookDialog}
onCancel={() => setShowWebhookDialog(false)}
title="新增 Webhook"
footer={[
<Button key="cancel" onClick={() => setShowWebhookDialog(false)}>
</Button>,
<Button
key="ok"
type="primary"
onClick={() => setShowWebhookDialog(false)}
>
Webhook
</Button>,
]}
>
<Form
layout="vertical"
initialValues={newWebhook}
onValuesChange={(changedValues) => {
setNewWebhook({ ...newWebhook, ...changedValues });
}}
>
<Form.Item name="name" label="Webhook名称">
<Input placeholder="输入Webhook名称" />
</Form.Item>
<Form.Item name="retryCount" label="重试次数">
<Input type="number" />
</Form.Item>
<Form.Item name="url" label="Webhook URL">
<Input placeholder="https://your-domain.com/webhook" />
</Form.Item>
<Form.Item name="secret" label="Secret Key">
<Input
placeholder="用于验证Webhook请求的密钥"
addonAfter={
<Button
icon={<ReloadOutlined />}
onClick={() =>
setNewWebhook({ ...newWebhook, secret: generateApiKey() })
}
/>
}
/>
</Form.Item>
<Form.Item label="选择事件">
<div className="max-h-48 overflow-y-auto border rounded-lg p-3 space-y-3">
{Object.entries(
availableEvents.reduce((acc, event) => {
if (!acc[event.category]) acc[event.category] = [];
acc[event.category].push(event);
return acc;
}, {} as Record<string, WebhookEvent[]>)
).map(([category, events]) => (
<div key={category} className="space-y-2">
<h4 className="font-medium text-sm text-gray-700">
{category}
</h4>
<div className="space-y-2 pl-4">
{events.map((event) => (
<div
key={event.id}
className="flex items-start space-x-2"
>
<Checkbox
checked={newWebhook.events.includes(event.id)}
onChange={(e) => {
const checked = e.target.checked;
if (checked) {
setNewWebhook({
...newWebhook,
events: [...newWebhook.events, event.id],
});
} else {
setNewWebhook({
...newWebhook,
events: newWebhook.events.filter(
(ev) => ev !== event.id
),
});
}
}}
>
<span className="text-sm font-medium">
{event.name}
</span>
</Checkbox>
<span className="text-xs text-gray-500">
{event.description}
</span>
</div>
))}
</div>
</div>
))}
</div>
</Form.Item>
</Form>
</Modal>
</div>
);
}