refactor(web): 前端目录重构 — consoles/pages → layouts/features + shared

- consoles/admin/ → layouts/admin-layout/
- consoles/workbench/ → layouts/workbench-layout/ + features/chat/
- pages/ → features/ (dashboard, models, projects, not-found)
- components/ → shared/components/
- hooks/ → shared/hooks/
- utils/ → shared/utils/
- 更新所有 import 路径 (src/web/ + tests/web/)
- 更新开发文档 (README.md, frontend.md, architecture.md)
This commit is contained in:
2026-06-02 23:17:28 +08:00
parent 1f05f259d0
commit b1dec691e9
76 changed files with 249 additions and 111 deletions

View File

@@ -0,0 +1,124 @@
import type { ColumnsType } from "antd/es/table";
import { DeleteOutlined, EditOutlined } from "@ant-design/icons";
import { App as AntApp, Button, Popconfirm, Space, Table, Tag } from "antd";
import type { Model, ModelListResponse, ProviderOption } from "../../../../shared/api";
interface ModelTableProps {
data: ModelListResponse | undefined;
loading: boolean;
onDelete: (id: string) => Promise<unknown>;
onEdit: (model: Model) => void;
onPageChange: (page: number, pageSize: number) => void;
page: number;
pageSize: number;
providers: ProviderOption[];
}
const CAPABILITY_LABELS: Record<string, string> = {
"audio-generation": "音频生成",
"audio-recognition": "音频识别",
"image-generation": "图片生成",
"image-recognition": "图片识别",
reasoning: "推理",
text: "文本",
"video-generation": "视频生成",
"video-recognition": "视频识别",
};
function getProviderName(providerId: string, providers: ProviderOption[]): string {
return providers.find((p) => p.id === providerId)?.name ?? providerId;
}
const COLUMNS: ColumnsType<Model> = [
{ dataIndex: "name", ellipsis: true, title: "名称", width: 180 },
{
dataIndex: "providerId",
ellipsis: true,
title: "供应商",
width: 120,
},
{
dataIndex: "capabilities",
render: (value: string[]) =>
value.length > 0 ? (
<Space size={[4, 4]} wrap>
{value.map((c) => (
<Tag key={c}>{CAPABILITY_LABELS[c] ?? c}</Tag>
))}
</Space>
) : null,
title: "能力",
},
];
export function ModelTable({
data,
loading,
onDelete,
onEdit,
onPageChange,
page,
pageSize,
providers,
}: ModelTableProps) {
const { message } = AntApp.useApp();
const handleDelete = async (id: string) => {
try {
await onDelete(id);
message.success("模型已删除");
} catch (err: unknown) {
message.error((err as Error).message);
}
};
const columnsWithProvider: ColumnsType<Model> = COLUMNS.map((col) =>
"dataIndex" in col && col.dataIndex === "providerId"
? {
...col,
render: (_value: unknown, record: Model) => getProviderName(record.providerId, providers),
}
: col,
);
const operationColumn: ColumnsType<Model>[number] = {
dataIndex: "op",
render: (_value: unknown, record: Model) => (
<Space size="small">
<Button icon={<EditOutlined />} onClick={() => onEdit(record)} size="small" type="link">
</Button>
<Popconfirm
description="此操作不可恢复。"
onConfirm={() => void handleDelete(record.id)}
title="确认删除此模型?"
>
<Button danger icon={<DeleteOutlined />} size="small" type="link">
</Button>
</Popconfirm>
</Space>
),
title: "操作",
width: 180,
};
return (
<Table
columns={[...columnsWithProvider, operationColumn]}
dataSource={data?.items ?? []}
loading={loading}
pagination={{
current: page,
hideOnSinglePage: false,
onChange: onPageChange,
pageSize,
showSizeChanger: true,
total: data?.total ?? 0,
}}
rowKey="id"
/>
);
}