feat: persist feature research and versioned alpha assessments
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user