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

126 lines
3.9 KiB
TypeScript
Raw Normal View History

import { useEffect, useState } from "react";
import { Banner, Button, Pagination } from "@douyinfe/semi-ui-19";
import { api, formatTime } from "../api";
import type { PageContext, UIAction } from "../ai/types";
import { FlowLaunchForm } from "./FlowLaunchForm";
import { FlowRunView } from "./FlowRunView";
import type { FlowRun } from "./flowTypes";
import { flowStatus } from "./flowTypes";
import "./workspace.css";
export function PipelinePage({
active,
onAction,
onContext,
}: {
active: boolean;
onAction: (a: UIAction) => void;
onContext: (c: PageContext) => void;
}) {
const [items, setItems] = useState<FlowRun[]>([]);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [selected, setSelected] = useState<string | null>(() =>
localStorage.getItem("research-selected-flow"),
);
const [run, setRun] = useState<FlowRun | null>(null);
const [creating, setCreating] = useState(!selected);
const [revision, setRevision] = useState(0);
const [error, setError] = useState("");
useEffect(() => {
if (active) onContext({ page: "pipeline", research_run_id: selected });
}, [active, selected, onContext]);
useEffect(() => {
if (!active) return;
const c = new AbortController();
let timer: ReturnType<typeof setTimeout>;
async function refresh() {
try {
const list = await api<{ items: FlowRun[]; total: number }>(
`/research/flows/runs?kind=pipeline&offset=${(page - 1) * 25}`,
{ signal: c.signal },
);
setItems(list.items);
setTotal(list.total);
if (selected)
setRun(
await api(`/research/flows/runs/${selected}`, { signal: c.signal }),
);
setError("");
} catch (e) {
if (!c.signal.aborted) setError((e as Error).message);
} finally {
if (!c.signal.aborted) timer = setTimeout(() => void refresh(), 1500);
}
}
void refresh();
return () => {
c.abort();
clearTimeout(timer);
};
}, [active, page, selected, revision]);
function select(id: string) {
setSelected(id);
localStorage.setItem("research-selected-flow", id);
setCreating(false);
}
return (
<section className="research-workspace">
<div className="section-toolbar">
<div>
<h2>研究流水线</h2>
<p className="muted">
生成 → 校验与设参 → 回测 → 评估决策 → 增强 → 重新展开
</p>
</div>
<Button onClick={() => setCreating(true)}>新建自动研究</Button>
</div>
{error && <Banner type="danger" description={error} />}
<div className="research-layout">
<aside className="research-library">
<h3>研究运行</h3>
{items.map((item) => (
<button
className={`research-library-item ${selected === item.id ? "selected" : ""}`}
key={item.id}
onClick={() => select(item.id)}
>
<strong>{item.name}</strong>
<span>
{flowStatus[item.status]} · 第 {item.round} 轮
</span>
<span>{formatTime(item.created_at)}</span>
</button>
))}
<Pagination
size="small"
total={total}
currentPage={page}
pageSize={25}
onPageChange={setPage}
/>
</aside>
<div className="research-main">
{creating ? (
<FlowLaunchForm
key="new-flow"
onStarted={(r) => {
select(r.id);
setRun(r);
setRevision((v) => v + 1);
}}
/>
) : (
run && (
<FlowRunView
run={run}
onRefresh={() => setRevision((v) => v + 1)}
onAction={onAction}
/>
)
)}
</div>
</div>
</section>
);
}