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

172 lines
5.1 KiB
TypeScript
Raw Normal View History

import { ResearchList, ResearchDetail } from "./ResearchList";
import { useEffect, useState } from "react";
import { Banner, Button, Pagination, Table } 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(false);
const [detailOpen, setDetailOpen] = useState(false);
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) {
setRun(null);
setDetailOpen(true);
setSelected(id);
localStorage.setItem("research-selected-flow", id);
setCreating(false);
}
return (
<section className="research-workspace research-list-page">
{error && !detailOpen && <Banner type="danger" description={error} />}
<ResearchList
resetKey={JSON.stringify([page])}
label="研究流水线"
actions={
<Button
theme="solid"
onClick={() => {
setCreating(true);
setDetailOpen(true);
}}
>
新建自动研究
</Button>
}
pagination={
<>
<span>共 {total} 次运行</span>
{total > 0 && (
<Pagination
size="small"
total={total}
currentPage={page}
pageSize={25}
onPageChange={setPage}
/>
)}
</>
}
>
<Table<FlowRun>
className="research-table"
dataSource={items}
rowKey="id"
pagination={false}
size="small"
sticky
style={{ minWidth: 640 }}
empty="暂无研究运行,请新建自动研究。"
columns={[
{
title: "研究名称",
key: "name",
render: (_, item) => (
<Button theme="borderless" onClick={() => select(item.id)}>
{item.name}
</Button>
),
},
{
title: "状态",
key: "status",
width: 140,
render: (_, item) => flowStatus[item.status],
},
{
title: "轮次",
key: "round",
width: 80,
render: (_, item) => item.round,
},
{
title: "创建时间",
key: "created_at",
width: 190,
render: (_, item) => formatTime(item.created_at),
},
]}
/>
</ResearchList>
<ResearchDetail
title={creating ? "新建自动研究" : "研究运行详情"}
visible={active && detailOpen}
onClose={() => setDetailOpen(false)}
>
{error && <Banner type="danger" description={error} />}
<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>
</ResearchDetail>
</section>
);
}