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,99 @@
import { CheckCircleFilled, CloseCircleFilled, LoadingOutlined } from "@ant-design/icons";
import { Collapse, Flex, Typography } from "antd";
import type { PartProps } from "./types";
interface ToolPartData {
errorText?: string;
input?: unknown;
output?: unknown;
toolCallId?: string;
toolMetadata?: Record<string, unknown>;
toolName?: string;
type?: string;
}
function getToolState(part: ToolPartData) {
if ("errorText" in part && part.errorText) return "output-error" as const;
if ("output" in part) return "output-available" as const;
if ("input" in part) return "input-available" as const;
return "input-streaming" as const;
}
const FORMAT_JSON = (v: unknown) => JSON.stringify(v, null, 2);
export function ToolPart({ part }: PartProps) {
const toolPart = part as unknown as ToolPartData;
const state = getToolState(toolPart);
const rawToolName = toolPart.toolName ?? (toolPart.type ?? "unknown").replace(/^tool-/, "");
const toolName =
typeof toolPart.toolMetadata?.["displayName"] === "string" ? toolPart.toolMetadata["displayName"] : rawToolName;
const isStreaming = state === "input-streaming" || state === "input-available";
if (state === "output-error") {
return (
<Collapse
ghost
items={[
{
children: <Typography.Text type="danger">{toolPart.errorText}</Typography.Text>,
key: toolPart.toolCallId ?? toolName,
label: (
<Flex align="center" component="span" gap={4}>
<CloseCircleFilled className="icon-error" />
<Typography.Text type="danger">{toolName} </Typography.Text>
</Flex>
),
},
]}
size="small"
/>
);
}
return (
<Collapse
defaultActiveKey={isStreaming ? [toolPart.toolCallId ?? toolName] : undefined}
ghost
items={[
{
children: (
<Flex gap={4} vertical>
{toolPart.input != null && (
<>
<Typography.Text type="secondary"></Typography.Text>
<pre className="tool-result-pre">{FORMAT_JSON(toolPart.input)}</pre>
</>
)}
{"output" in toolPart && toolPart.output != null && (
<>
<Typography.Text type="secondary"></Typography.Text>
<pre className="tool-result-pre">{FORMAT_JSON(toolPart.output)}</pre>
</>
)}
{!toolPart.input && !("output" in toolPart) && (
<Typography.Text type="secondary">...</Typography.Text>
)}
</Flex>
),
key: toolPart.toolCallId ?? toolName,
label: isStreaming ? (
<Flex align="center" component="span" gap={4}>
<LoadingOutlined className="icon-primary" />
<Typography.Text type="secondary">
{state === "input-streaming" ? "生成参数" : `调用 ${toolName}`}
</Typography.Text>
</Flex>
) : (
<Flex align="center" component="span" gap={4}>
<CheckCircleFilled className="icon-success" />
<Typography.Text type="secondary">{toolName}</Typography.Text>
</Flex>
),
},
]}
size="small"
/>
);
}