Files
worldquant-alpha-system/frontend/src/research/LineagePanel.tsx
T

107 lines
3.4 KiB
TypeScript

import { useEffect, useState } from "react";
import { Banner, Button, Empty, Toast } from "@douyinfe/semi-ui-19";
import { api } from "../api";
import type { UIAction } from "../ai/types";
import type { Experiment } from "./workspaceTypes";
type Graph = {
items: Experiment[];
edges: { child_id: string; parent_kind: string; parent_id: string }[];
truncated: boolean;
unresolved_experiment_ids: string[];
};
export function LineagePanel({
alphaId,
experimentId,
onAction,
}: {
alphaId?: string;
experimentId?: string;
onAction: (a: UIAction) => void;
}) {
const [graph, setGraph] = useState<Graph | null>(null);
const [focus, setFocus] = useState(experimentId);
useEffect(() => {
const c = new AbortController();
api<Graph>(
`/research/lineage?${focus ? `experiment_id=${encodeURIComponent(focus)}` : `alpha_id=${encodeURIComponent(alphaId || "")}`}`,
{ signal: c.signal },
)
.then(setGraph)
.catch((e) => {
if (!c.signal.aborted) Toast.error(e.message);
});
return () => c.abort();
}, [alphaId, focus]);
return (
<div className="detail-section">
<h3>变体关系</h3>
<p className="muted">
每个实验保留所有父来源;每次回测来源另见「研究来源」。选择实验可继续查看输入、设置差异、候选及回测。
</p>
{graph?.truncated && (
<Banner
type="info"
description="关系图按 100 个实验、8 层展示;可从边界实验继续展开。"
/>
)}
{graph?.items.length === 0 && <Empty description="尚无本地变体关系" />}
{graph?.items.map((e) => (
<div className="research-card" key={e.id}>
<h4>{e.name}</h4>
<p>{e.hypothesis}</p>
<p>
方法:{e.evidence.method || e.kind} · 模板{" "}
{e.evidence.template?.name || "—"}{" "}
{e.evidence.template?.version
? `v${e.evidence.template.version}`
: ""}
</p>
<div className="inline-actions">
{e.parents.map((p) => (
<Button
key={`${p.kind}:${p.id}`}
size="small"
onClick={() =>
p.kind === "alpha"
? onAction({
type: "open_alpha",
alpha_id: p.id,
nonce: Date.now(),
})
: setFocus(p.id)
}
>
父{p.kind === "alpha" ? " Alpha" : "实验"} {p.id.slice(0, 12)}
</Button>
))}
</div>
<p className="muted">
{e.candidates.length} 个候选 · {e.backtest_run_ids.length} 次回测
</p>
<div className="inline-actions">
<Button
onClick={() =>
onAction({
type: "open_experiment",
experiment_id: e.id,
nonce: Date.now(),
})
}
>
查看候选与来源
</Button>
<Button theme="borderless" onClick={() => setFocus(e.id)}>
以此实验展开关系
</Button>
</div>
</div>
))}
{graph?.unresolved_experiment_ids.map((id) => (
<Button key={id} onClick={() => setFocus(id)}>
继续展开 {id}
</Button>
))}
</div>
);
}