96 lines
3.1 KiB
TypeScript
96 lines
3.1 KiB
TypeScript
import type { ComponentType } from "react";
|
|
import { Button, Tag } from "@douyinfe/semi-ui-19";
|
|
import { ResearchToolCard } from "../research/ResearchToolCard";
|
|
import { BacktestToolCard } from "../backtests/BacktestToolCard";
|
|
import { CatalogToolCard } from "../research/CatalogToolCard";
|
|
import { AlphaToolCard } from "./AlphaToolCard";
|
|
import { JobToolCard } from "./JobToolCard";
|
|
import type { ToolCardProps } from "./types";
|
|
|
|
// The server owns labels, effects and refresh targets. This is the only place
|
|
// where a new presentation shape joins the workspace; tool names are domain-local.
|
|
const renderers: Record<string, ComponentType<ToolCardProps>> = {
|
|
research: ResearchToolCard,
|
|
alpha: AlphaToolCard,
|
|
jobs: JobToolCard,
|
|
catalog: CatalogToolCard,
|
|
backtest: BacktestToolCard,
|
|
};
|
|
const labels: Record<string, string> = {
|
|
pending: "待确认",
|
|
completed: "已完成",
|
|
failed: "失败",
|
|
denied: "已拒绝",
|
|
cancelled: "已取消",
|
|
};
|
|
|
|
export function BusinessCard(
|
|
props: ToolCardProps & {
|
|
onDecision: (id: string, approved: boolean) => Promise<void>;
|
|
disabled: boolean;
|
|
},
|
|
) {
|
|
const { call, onDecision, disabled } = props;
|
|
const renderer = call.presentation?.renderer ?? "generic";
|
|
const Renderer = Object.hasOwn(renderers, renderer)
|
|
? renderers[renderer]
|
|
: undefined;
|
|
const pending = call.status === "pending";
|
|
const result = call.result ?? {};
|
|
return (
|
|
<section className="ai-tool-card" data-status={call.status}>
|
|
<div className="ai-card-title">
|
|
<strong>{call.presentation?.label ?? call.name}</strong>
|
|
<Tag
|
|
color={pending ? "orange" : call.status === "failed" ? "red" : "grey"}
|
|
>
|
|
{labels[call.status] ?? call.status}
|
|
</Tag>
|
|
</div>
|
|
{Renderer ? (
|
|
<Renderer {...props} />
|
|
) : (
|
|
<>
|
|
<p>此记录暂不支持专用展示,原始结果仍可查看。</p>
|
|
<details>
|
|
<summary>查看业务记录</summary>
|
|
<pre>
|
|
{JSON.stringify(pending ? call.preview : result, null, 2)}
|
|
</pre>
|
|
</details>
|
|
</>
|
|
)}
|
|
{pending && (
|
|
<div className="ai-approval">
|
|
<p className="muted">确认后执行以上预览中的固定操作。</p>
|
|
<div className="inline-actions">
|
|
<Button
|
|
theme="solid"
|
|
disabled={
|
|
disabled || !Renderer || call.presentation?.effect !== "confirm"
|
|
}
|
|
onClick={() => void onDecision(call.id, true)}
|
|
>
|
|
确认执行
|
|
</Button>
|
|
<Button
|
|
type="tertiary"
|
|
disabled={disabled}
|
|
onClick={() => void onDecision(call.id, false)}
|
|
>
|
|
拒绝
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{typeof result.error === "string" && (
|
|
<p className="error-text">{result.error}</p>
|
|
)}
|
|
{typeof result._warning === "string" && (
|
|
<p role="status">{result._warning}</p>
|
|
)}
|
|
{result.denied === true && <p>已拒绝,未执行此操作。</p>}
|
|
</section>
|
|
);
|
|
}
|