Compare commits
2 Commits
8444e3055e
...
20f6d0fc51
| Author | SHA1 | Date | |
|---|---|---|---|
| 20f6d0fc51 | |||
| db449f0915 |
@@ -13,9 +13,12 @@ import {
|
||||
Input,
|
||||
InputNumber,
|
||||
Table,
|
||||
Tabs,
|
||||
TextArea,
|
||||
Toast,
|
||||
Typography,
|
||||
} from "@douyinfe/semi-ui-19";
|
||||
import { WorkspaceTable } from "../components/WorkspaceTable";
|
||||
import { ResearchInput } from "../ai/ResearchInput";
|
||||
import { IconAIEditLevel3 } from "@douyinfe/semi-icons";
|
||||
import { api, post, formatTime } from "../api";
|
||||
@@ -54,6 +57,7 @@ export function ResearchWorkspace({
|
||||
onAction: (action: UIAction) => void;
|
||||
onContext: (context: PageContext) => void;
|
||||
}) {
|
||||
const [detailTab, setDetailTab] = useState("editor");
|
||||
const [detailOpen, setDetailOpen] = useState(false);
|
||||
const [detailMode, setDetailMode] = useState<"editor" | "experiment">(
|
||||
"editor",
|
||||
@@ -150,6 +154,7 @@ export function ResearchWorkspace({
|
||||
["open_experiment", "open_template", "open_variant"].includes(action.type)
|
||||
) {
|
||||
setDetailOpen(true);
|
||||
setDetailTab("editor");
|
||||
setDetailMode(
|
||||
action.type === "open_experiment" ? "experiment" : "editor",
|
||||
);
|
||||
@@ -261,6 +266,7 @@ export function ResearchWorkspace({
|
||||
parent_alpha_ids: parent ? parent.split(/[,,\s]+/).filter(Boolean) : [],
|
||||
});
|
||||
setExperiment(next);
|
||||
setDetailTab("prepare");
|
||||
await refresh();
|
||||
}
|
||||
const combination = Object.values(template.variables)
|
||||
@@ -308,6 +314,7 @@ export function ResearchWorkspace({
|
||||
setAsset(null);
|
||||
setTemplate(blankTemplate());
|
||||
setExperiment(null);
|
||||
setDetailTab("editor");
|
||||
setDetailMode("editor");
|
||||
setDetailOpen(true);
|
||||
}}
|
||||
@@ -354,20 +361,20 @@ export function ResearchWorkspace({
|
||||
}
|
||||
>
|
||||
{listMode === "assets" ? (
|
||||
<>
|
||||
<Table<Asset>
|
||||
className="research-table"
|
||||
<div className="research-template-table-container">
|
||||
<WorkspaceTable<Asset>
|
||||
key={JSON.stringify([search, assetPage])}
|
||||
fill
|
||||
dataSource={assets}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
size="small"
|
||||
sticky
|
||||
style={{ minWidth: 720 }}
|
||||
scroll={{ x: 800, y: "100%" }}
|
||||
empty="还没有模板。新建或导入一个模板开始研究。"
|
||||
columns={[
|
||||
{
|
||||
title: "模板名称",
|
||||
key: "name",
|
||||
width: 280,
|
||||
ellipsis: { showTitle: false },
|
||||
render: (_, item) => (
|
||||
<Button
|
||||
theme="borderless"
|
||||
@@ -379,12 +386,19 @@ export function ResearchWorkspace({
|
||||
setAsset(next);
|
||||
setTemplate(next.content);
|
||||
setExperiment(null);
|
||||
setDetailTab("editor");
|
||||
setDetailMode("editor");
|
||||
setDetailOpen(true);
|
||||
})
|
||||
}
|
||||
>
|
||||
<Typography.Text
|
||||
size="inherit"
|
||||
style={{ width: "100%", color: "inherit" }}
|
||||
ellipsis={{ showTooltip: true }}
|
||||
>
|
||||
{item.name}
|
||||
</Typography.Text>
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
@@ -397,8 +411,17 @@ export function ResearchWorkspace({
|
||||
{
|
||||
title: "表达式",
|
||||
key: "expression",
|
||||
width: 260,
|
||||
render: (_, item) => <code>{item.content.expression}</code>,
|
||||
width: undefined,
|
||||
ellipsis: { showTitle: false },
|
||||
render: (_, item) => (
|
||||
<Typography.Text
|
||||
className="research-template-expression"
|
||||
style={{ width: "100%" }}
|
||||
ellipsis={{ showTooltip: true }}
|
||||
>
|
||||
{item.content.expression}
|
||||
</Typography.Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "分类",
|
||||
@@ -412,6 +435,7 @@ export function ResearchWorkspace({
|
||||
key: "actions",
|
||||
width: 80,
|
||||
render: (_, item) => (
|
||||
<div className="workspace-cell-actions">
|
||||
<DeleteResearchButton
|
||||
name={item.name}
|
||||
label="模板"
|
||||
@@ -420,11 +444,12 @@ export function ResearchWorkspace({
|
||||
task("删除模板", () => removeAsset(item))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Table<(typeof history)[number]>
|
||||
@@ -522,6 +547,19 @@ export function ResearchWorkspace({
|
||||
description="此模板已删除,当前显示保留的历史版本;可另存新模板继续编辑。"
|
||||
/>
|
||||
)}
|
||||
{page === "templates" && (
|
||||
<Tabs
|
||||
type="card"
|
||||
activeKey={detailTab}
|
||||
onChange={setDetailTab}
|
||||
tabPaneMotion={false}
|
||||
tabList={[
|
||||
{ itemKey: "editor", tab: "模板信息编辑" },
|
||||
{ itemKey: "prepare", tab: "回测准备" },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<div hidden={page === "templates" && detailTab !== "editor"}>
|
||||
<section className="research-card">
|
||||
<h3>研究输入与假设</h3>
|
||||
{page === "variants" && (
|
||||
@@ -639,11 +677,14 @@ export function ResearchWorkspace({
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
{page !== "variants" || method === "structure" ? (
|
||||
<>
|
||||
<div hidden={page === "templates" && detailTab !== "editor"}>
|
||||
<section className="research-card">
|
||||
<div className="research-section-heading">
|
||||
<h3>
|
||||
编辑与展开{asset && ` · v${asset.version}`}
|
||||
模板信息{asset && ` · v${asset.version}`}
|
||||
{dirty && " · 未保存"}
|
||||
</h3>
|
||||
</div>
|
||||
@@ -708,6 +749,68 @@ export function ResearchWorkspace({
|
||||
)}
|
||||
<span>组合规模:{combination}</span>
|
||||
</div>
|
||||
{page === "templates" && (
|
||||
<Button
|
||||
theme="solid"
|
||||
disabled={!!busy || dirty}
|
||||
onClick={() => setDetailTab("prepare")}
|
||||
>
|
||||
下一步:回测准备
|
||||
</Button>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
<div hidden={page === "templates" && detailTab !== "prepare"}>
|
||||
<section className="research-card">
|
||||
<h3>回测准备</h3>
|
||||
<p className="research-hint">
|
||||
配置展开方式与模拟设置 → 生成候选研究集合 →
|
||||
生成回测确认预览 → 确认后开始回测。
|
||||
</p>
|
||||
{page === "templates" && (
|
||||
<>
|
||||
<label>
|
||||
固定研究输入
|
||||
<ResearchDataInput
|
||||
label="选择回测数据准备"
|
||||
ids={inputIds}
|
||||
inputs={inputs}
|
||||
onChange={(ids, rows) => {
|
||||
setInputs(rows);
|
||||
setInputIds(ids);
|
||||
const first = rows.find((r) => r.id === ids[0]);
|
||||
if (first)
|
||||
setSettings((old) => ({
|
||||
...old,
|
||||
region: first.scope.region,
|
||||
universe: first.scope.universe,
|
||||
delay: first.scope.delay,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
本次研究假设
|
||||
<TextArea
|
||||
aria-label="回测研究假设"
|
||||
value={hypothesis}
|
||||
onChange={setHypothesis}
|
||||
autosize={{ minRows: 2, maxRows: 5 }}
|
||||
/>
|
||||
</label>
|
||||
<p>
|
||||
模板:{template.name || "未命名"} ·{" "}
|
||||
{asset ? `v${asset.version}` : "未保存"} · 组合规模:
|
||||
{combination}
|
||||
</p>
|
||||
{dirty && (
|
||||
<Banner
|
||||
type="warning"
|
||||
description="请返回模板信息编辑,保存模板版本后再生成候选。"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<div className="research-form-grid">
|
||||
<label>
|
||||
展开方式
|
||||
@@ -760,9 +863,23 @@ export function ResearchWorkspace({
|
||||
}
|
||||
onClick={() => void task("生成候选", expand)}
|
||||
>
|
||||
保存候选研究记录
|
||||
生成候选研究集合
|
||||
</Button>
|
||||
{(!inputIds.length || !hypothesis || !settingsValid) && (
|
||||
<p className="research-hint">
|
||||
待完成:
|
||||
{[
|
||||
!inputIds.length && "选择固定研究输入",
|
||||
!hypothesis && "填写研究假设",
|
||||
!settingsValid && "检查模拟设置",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("、")}
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<section className="research-card">
|
||||
<h3>目标范围检查</h3>
|
||||
@@ -789,6 +906,7 @@ export function ResearchWorkspace({
|
||||
</Button>
|
||||
</section>
|
||||
)}
|
||||
<div hidden={page === "templates" && detailTab !== "prepare"}>
|
||||
{experiment && (
|
||||
<ExperimentView
|
||||
key={experiment.id}
|
||||
@@ -800,7 +918,8 @@ export function ResearchWorkspace({
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{page === "templates" && (
|
||||
</div>
|
||||
{page === "templates" && detailTab === "editor" && (
|
||||
<details className="research-card">
|
||||
<summary>导入旧模板</summary>
|
||||
<p>
|
||||
|
||||
@@ -104,6 +104,18 @@
|
||||
.research-table-scroll {
|
||||
overflow-x: auto;
|
||||
}
|
||||
.research-template-table-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.research-template-expression {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
.research-table .semi-table-row-cell {
|
||||
vertical-align: top;
|
||||
overflow-wrap: anywhere;
|
||||
|
||||
@@ -86,6 +86,7 @@ test("feature conversion, saved views and immutable evaluations", async ({
|
||||
await expect(
|
||||
page.getByRole("textbox", { name: "模板名称", exact: true }),
|
||||
).toHaveValue("特征转换模板");
|
||||
await page.getByRole("tab", { name: "回测准备", exact: true }).click();
|
||||
const settingsEditor = page.getByRole("region", {
|
||||
name: "回测参数",
|
||||
exact: true,
|
||||
|
||||
@@ -79,7 +79,7 @@ for (const mode of ["templates", "variants", "features"] as const) {
|
||||
const hypothesis = "检验 <收益> 与规模\n第二轮保留原始假设";
|
||||
await input.fill(hypothesis);
|
||||
await expect(
|
||||
sheet.locator("p").filter({ hasText: /Semi 准备集合 ·/ }),
|
||||
sheet.locator("p:visible").filter({ hasText: /Semi 准备集合 ·/ }),
|
||||
).toBeVisible();
|
||||
let release!: () => void;
|
||||
const pending = new Promise<void>((resolve) => {
|
||||
@@ -148,6 +148,22 @@ for (const mode of ["templates", "variants", "features"] as const) {
|
||||
sheet.getByRole("textbox", { name: "模板名称", exact: true }),
|
||||
).toHaveValue("AI 生成验收");
|
||||
await expect(generate).toBeEnabled();
|
||||
if (mode === "templates") {
|
||||
await sheet.getByRole("button", { name: "下一步:回测准备" }).click();
|
||||
await expect(
|
||||
sheet.getByRole("textbox", { name: "回测研究假设", exact: true }),
|
||||
).toHaveValue(hypothesis);
|
||||
await expect(
|
||||
sheet.getByRole("button", { name: "生成候选研究集合", exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(generate).toBeHidden();
|
||||
await sheet
|
||||
.getByRole("tab", { name: "模板信息编辑", exact: true })
|
||||
.click();
|
||||
await expect(
|
||||
sheet.getByRole("textbox", { name: "模板名称", exact: true }),
|
||||
).toHaveValue("AI 生成验收");
|
||||
}
|
||||
await expect(input).toContainText("检验 <收益> 与规模");
|
||||
await page.screenshot({ path: `../output/playwright/semi-ai-${mode}.png` });
|
||||
await page.setViewportSize({ width: 390, height: 900 });
|
||||
|
||||
Reference in New Issue
Block a user