Files
worldquant-alpha-system/frontend/src/ai/ToolCard.tsx
T

94 lines
3.0 KiB
TypeScript
Raw Normal View History

import type { ComponentType } from "react";
import { Button, Tag } from "@douyinfe/semi-ui-19";
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>> = {
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>
);
}