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:
99
src/web/features/chat/parts/ToolPart.tsx
Normal file
99
src/web/features/chat/parts/ToolPart.tsx
Normal 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"
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user