feat: integrate chatbox research with datasets and backtests

This commit is contained in:
yuxuanhui
2026-09-08 12:43:00 +08:00
parent 43336ad960
commit aef8e1d310
37 changed files with 1421 additions and 42 deletions
+38 -1
View File
@@ -38,6 +38,8 @@ import type {
import { AlphaDetail } from "../components/AlphaDetail";
import { AlphaSyncDialog } from "../components/AlphaSyncDialog";
import type { PageContext, AlphaUIAction as UIAction } from "../ai/types";
import type { UIAction as WorkspaceAction } from "../ai/types";
import { sourceLabel } from "../research/SourceDetails";
const metricLabels = {
sharpe: "Sharpe",
@@ -57,8 +59,10 @@ const initialColumns = [
"turnover",
"correlation",
"research",
"source",
];
const columnLabels: Record<string, string> = {
source: "研究来源",
name: "Alpha",
expression: "表达式",
region: "地区 / Universe",
@@ -89,6 +93,7 @@ export function AlphaPage({
onContext,
action,
onOverlay,
onAction,
}: {
account: Account | null;
taskPanelOpen: boolean;
@@ -101,6 +106,7 @@ export function AlphaPage({
onContext: (context: PageContext) => void;
action: UIAction | null;
onOverlay: () => void;
onAction: (action: WorkspaceAction) => void;
}) {
const [data, setData] = useState<Page>({
items: [],
@@ -225,6 +231,7 @@ export function AlphaPage({
}, [action, onOverlay]);
const refresh = useCallback(() => setLocalVersion((n) => n + 1), []);
useEffect(() => {
if (!active) return;
const controller = new AbortController();
setLoading(true);
setError("");
@@ -245,7 +252,7 @@ export function AlphaPage({
if (!controller.signal.aborted) setLoading(false);
});
return () => controller.abort();
}, [query, version, localVersion]);
}, [active, query, version, localVersion]);
useEffect(() => {
setSelected([]);
tablePanel.current?.querySelector(".semi-table-body")?.scrollTo({ top: 0 });
@@ -348,6 +355,13 @@ export function AlphaPage({
</code>
),
},
{
key: "source",
title: "研究来源",
width: 150,
render: (_, row) =>
row!.source_kinds?.map(sourceLabel).join("、") || "暂无本地来源",
},
{
key: "region",
title: "地区 / Universe",
@@ -594,6 +608,28 @@ export function AlphaPage({
{activeFilterCount ? `· ${activeFilterCount} 项已应用` : ""}
</summary>
<div className="advanced-grid">
<label>
研究来源
<Select
aria-label="研究来源筛选"
showClear
value={draft.source || undefined}
optionList={options("source").map((option) => ({
...option,
label: sourceLabel(option.value),
}))}
placeholder="全部来源"
onChange={(v) => updateDraft("source", v)}
/>
</label>
<label>
研究编号
<Input
aria-label="研究编号筛选"
value={draft.research_id || ""}
onChange={(v) => updateDraft("research_id", v)}
/>
</label>
{[
["universe", "Universe"],
["alpha_type", "Alpha 类型"],
@@ -938,6 +974,7 @@ export function AlphaPage({
/>
)}
<AlphaDetail
onAction={onAction}
suspended={overlaySuspended}
chatOffset={chatOffset}
taskPanelOpen={taskPanelOpen}
+83 -2
View File
@@ -26,6 +26,7 @@ import {
queryString,
} from "../api";
import type { Account, Job } from "../types";
import type { PageContext, UIAction } from "../ai/types";
import "./dataset.css";
type Scope = {
@@ -66,6 +67,7 @@ type Draft = {
dataset_id: string;
scope: Scope;
field_ids: string[];
field_types: Record<string, string | null>;
collection_version: string;
selection: string;
created_at: string;
@@ -114,6 +116,8 @@ export function DatasetPage({
onTask,
onModal,
onChat,
onContext,
action,
}: {
account: Account | null;
version: string;
@@ -123,6 +127,8 @@ export function DatasetPage({
onTask: () => void;
onModal: (value: boolean) => void;
onChat: () => void;
onContext: (context: PageContext) => void;
action: UIAction | null;
}) {
const [scope, setScope] = useState<Scope>({
instrument_type: "EQUITY",
@@ -185,6 +191,61 @@ export function DatasetPage({
);
const total = selected?.complete_count ?? 0;
const chosen = Math.max(0, total - excluded.size);
const contextDraft = inputOpen ? draft : null;
useEffect(() => {
if (!active) return;
onContext({
page: "datasets",
catalog_scope: contextDraft?.scope ?? scope,
dataset_id: contextDraft?.dataset_id ?? selected?.id,
field_id: contextDraft ? undefined : detail?.field,
collection_version:
contextDraft?.collection_version ??
selected?.collection_version ??
undefined,
template_input_id: contextDraft?.id,
unsaved_field_selection: !contextDraft && excluded.size > 0,
});
}, [
active,
scopeQuery,
contextDraft,
selected?.id,
selected?.collection_version,
detail?.field,
excluded.size,
onContext,
]);
useEffect(() => {
if (action?.type !== "open_research_input") return;
let live = true;
void api<Draft>(`/catalog/inputs/${encode(action.input_id)}`)
.then((saved) => {
if (!live) return;
changeScope(saved.scope);
setDraft(saved);
setInputOpen(true);
})
.catch((e) => {
if (live) setError(e.message);
});
return () => {
live = false;
};
}, [action]);
const researchInput = (saved: Draft) => {
setDraft(saved);
setHistoryOpen(false);
setInputOpen(true);
onContext({
page: "datasets",
catalog_scope: saved.scope,
dataset_id: saved.dataset_id,
collection_version: saved.collection_version,
template_input_id: saved.id,
});
onChat();
};
const top = historyOpen
? "history"
: inputOpen
@@ -1131,6 +1192,11 @@ export function DatasetPage({
保存输入草稿
</Button>
)}
{draft && (
<Button theme="solid" onClick={() => researchInput(draft)}>
用此输入研究
</Button>
)}
</div>
<div className="catalog-details-body">
<h2>{draft ? "输入草稿已保存" : selected?.name}</h2>
@@ -1140,10 +1206,24 @@ export function DatasetPage({
: `${preparation.current?.count} 个字段`}
</p>
<p>
{scope.region} · {scope.universe} · Delay {scope.delay}
{(draft?.scope ?? scope).region} ·{" "}
{(draft?.scope ?? scope).universe} · Delay{" "}
{(draft?.scope ?? scope).delay}
</p>
<p>模板消费方尚未接入</p>
<p>保存后可交给 Chatbox 研究,构建候选并预览回测。</p>
{draft && <p>草稿 ID:{draft.id}</p>}
{draft && (
<details>
<summary>查看固定字段集合</summary>
<p>
{draft.field_ids
.map(
(id) => `${id} (${draft.field_types[id] ?? "类型未知"})`,
)
.join(", ")}
</p>
</details>
)}
<p>
集合版本:
{draft?.collection_version ?? preparation.current?.version}
@@ -1177,6 +1257,7 @@ export function DatasetPage({
{d.scope.delay}
</p>
<p>{formatTime(d.created_at, account?.timezone)}</p>
<Button onClick={() => researchInput(d)}>用此输入研究</Button>
<details>
<summary>查看固定字段集合</summary>
<p>{d.field_ids.join(", ")}</p>