feat: add versioned research templates and alpha variants
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
import { ResearchSelect } from "./ResearchSelect";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
Banner,
|
||||
Button,
|
||||
Checkbox,
|
||||
Input,
|
||||
TextArea,
|
||||
} from "@douyinfe/semi-ui-19";
|
||||
import { api, patch, post, formatTime } from "../api";
|
||||
import "./workspace.css";
|
||||
type Operator = {
|
||||
name: string;
|
||||
category: string;
|
||||
description: string;
|
||||
definition: string;
|
||||
example: string;
|
||||
parameters: unknown;
|
||||
scope: string[];
|
||||
local: { note: string; favorite: boolean; version: number };
|
||||
};
|
||||
type Results = {
|
||||
items: Operator[];
|
||||
total: number;
|
||||
categories: string[];
|
||||
fetched_at: string | null;
|
||||
};
|
||||
export function OperatorsPage({ active }: { active: boolean }) {
|
||||
const [query, setQuery] = useState("");
|
||||
const [category, setCategory] = useState("");
|
||||
const [favorite, setFavorite] = useState(false);
|
||||
const [offset, setOffset] = useState(0);
|
||||
const [result, setResult] = useState<Results>({
|
||||
items: [],
|
||||
total: 0,
|
||||
categories: [],
|
||||
fetched_at: null,
|
||||
});
|
||||
const [selected, setSelected] = useState<Operator | null>(null);
|
||||
const [error, setError] = useState("");
|
||||
const [busy, setBusy] = useState(false);
|
||||
async function load() {
|
||||
setResult(
|
||||
await api(
|
||||
`/catalog/operators?${new URLSearchParams({ q: query, category, favorite: String(favorite), limit: "25", offset: String(offset) })}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
async function task(operation: () => Promise<void>) {
|
||||
setBusy(true);
|
||||
setError("");
|
||||
try {
|
||||
await operation();
|
||||
} catch (error) {
|
||||
setError((error as Error).message);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
if (active) void task(load);
|
||||
}, [active, query, category, favorite, offset]);
|
||||
return (
|
||||
<div className="research-workspace">
|
||||
<header className="research-page-heading">
|
||||
<div>
|
||||
<h2>算子库</h2>
|
||||
<p>
|
||||
平台定义只读,本地收藏和研究备注独立保存。同步时间:
|
||||
{formatTime(result.fetched_at)}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
theme="solid"
|
||||
loading={busy}
|
||||
onClick={() =>
|
||||
void task(async () => {
|
||||
await post("/catalog/operators/refresh");
|
||||
await load();
|
||||
})
|
||||
}
|
||||
>
|
||||
同步算子目录
|
||||
</Button>
|
||||
</header>
|
||||
{error && <Banner type="danger" description={error} />}
|
||||
<div className="research-toolbar">
|
||||
<Input
|
||||
aria-label="搜索算子"
|
||||
placeholder="名称、参数或描述"
|
||||
value={query}
|
||||
onChange={(value) => {
|
||||
setQuery(value);
|
||||
setOffset(0);
|
||||
}}
|
||||
/>
|
||||
<ResearchSelect
|
||||
label="算子分类"
|
||||
value={category}
|
||||
optionList={[
|
||||
{ value: "", label: "全部分类" },
|
||||
...result.categories.map((value) => ({ value, label: value })),
|
||||
]}
|
||||
onChange={(value) => {
|
||||
setCategory(value as string);
|
||||
setOffset(0);
|
||||
}}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={favorite}
|
||||
onChange={(event) => {
|
||||
setFavorite(!!event.target.checked);
|
||||
setOffset(0);
|
||||
}}
|
||||
>
|
||||
只看收藏
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div className="research-operator-layout">
|
||||
<section className="research-card">
|
||||
<table className="research-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>算子</th>
|
||||
<th>分类</th>
|
||||
<th>定义</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{result.items.map((item) => (
|
||||
<tr key={item.name}>
|
||||
<td>
|
||||
<Button
|
||||
theme="borderless"
|
||||
onClick={() => setSelected(item)}
|
||||
>
|
||||
{item.local.favorite ? "已收藏 · " : ""}
|
||||
{item.name}
|
||||
</Button>
|
||||
</td>
|
||||
<td>{item.category}</td>
|
||||
<td>
|
||||
<code>{item.definition}</code>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
{!result.items.length && (
|
||||
<p>暂无算子。连接平台并同步目录,或调整筛选条件。</p>
|
||||
)}
|
||||
<div className="research-toolbar">
|
||||
<span>共 {result.total} 个</span>
|
||||
<Button
|
||||
disabled={!offset}
|
||||
onClick={() => setOffset(Math.max(0, offset - 25))}
|
||||
>
|
||||
上一页
|
||||
</Button>
|
||||
<Button
|
||||
disabled={offset + 25 >= result.total}
|
||||
onClick={() => setOffset(offset + 25)}
|
||||
>
|
||||
下一页
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
{selected && (
|
||||
<section className="research-card">
|
||||
<h3>{selected.name}</h3>
|
||||
<p>{selected.description}</p>
|
||||
<code>{selected.definition}</code>
|
||||
{selected.example && <pre>{selected.example}</pre>}
|
||||
{!!selected.parameters && (
|
||||
<pre>{JSON.stringify(selected.parameters, null, 2)}</pre>
|
||||
)}
|
||||
<p>适用:{selected.scope?.join("、") || "平台未提供"}</p>
|
||||
<label>
|
||||
研究备注
|
||||
<TextArea
|
||||
aria-label="算子研究备注"
|
||||
value={selected.local.note}
|
||||
onChange={(note) =>
|
||||
setSelected({
|
||||
...selected,
|
||||
local: { ...selected.local, note },
|
||||
})
|
||||
}
|
||||
autosize={{ minRows: 4, maxRows: 8 }}
|
||||
/>
|
||||
</label>
|
||||
<Checkbox
|
||||
checked={selected.local.favorite}
|
||||
onChange={(event) =>
|
||||
setSelected({
|
||||
...selected,
|
||||
local: {
|
||||
...selected.local,
|
||||
favorite: !!event.target.checked,
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
收藏算子
|
||||
</Checkbox>
|
||||
<div className="research-toolbar">
|
||||
<Button
|
||||
disabled={busy}
|
||||
onClick={() =>
|
||||
void task(async () => {
|
||||
const result = await patch<{ version: number }>(
|
||||
`/catalog/operators/${encodeURIComponent(selected.name)}/research`,
|
||||
selected.local,
|
||||
);
|
||||
setSelected({
|
||||
...selected,
|
||||
local: { ...selected.local, version: result.version },
|
||||
});
|
||||
await load();
|
||||
})
|
||||
}
|
||||
>
|
||||
保存本地备注
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user