feat: add AI research chatbot with confirmed business tools

This commit is contained in:
yuxuanhui
2026-09-07 23:02:55 +08:00
parent 3cd280d068
commit 79ab20b4ea
44 changed files with 4647 additions and 197 deletions
+2
View File
@@ -16,6 +16,7 @@ import {
} from "@douyinfe/semi-icons";
import { api, displayValue, formatTime, patch, post } from "../api";
import type { Account } from "../types";
import { ModelSettingsPanel } from "../ai/ModelSettingsPanel";
const connectionLabels: Record<string, string> = {
connected: "已连接",
@@ -137,6 +138,7 @@ export function AccountPage({
</div>
)}
<div className="account-grid">
<ModelSettingsPanel />
<section className="panel">
<div className="panel-title">
<IconLink />
+84 -7
View File
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
Banner,
Button,
@@ -35,6 +35,7 @@ import {
} from "../api";
import type { Account, Alpha, AlphaPage as Page, Facets } from "../types";
import { AlphaDetail } from "../components/AlphaDetail";
import type { PageContext, UIAction } from "../ai/types";
const metricLabels = {
sharpe: "Sharpe",
@@ -77,12 +78,24 @@ export function AlphaPage({
version,
onTask,
onAccount,
active,
overlaySuspended,
chatOffset,
onContext,
action,
onOverlay,
}: {
account: Account | null;
taskPanelOpen: boolean;
version: string;
onTask: () => void;
onAccount: () => void;
active: boolean;
overlaySuspended: boolean;
chatOffset: number;
onContext: (context: PageContext) => void;
action: UIAction | null;
onOverlay: () => void;
}) {
const [data, setData] = useState<Page>({
items: [],
@@ -124,6 +137,8 @@ export function AlphaPage({
const [idText, setIdText] = useState("");
const [busy, setBusy] = useState("");
const [bulkOpen, setBulkOpen] = useState(false);
const [bulkVersions, setBulkVersions] = useState<Record<string, number>>({});
const actionNonce = useRef(0);
const [bulk, setBulk] = useState({
add_tags: "",
remove_tags: "",
@@ -147,6 +162,46 @@ export function AlphaPage({
[filters, sort, direction, pageSize, page],
);
const query = queryString(params);
useEffect(() => {
if (!active) return;
onContext({
page: "alphas",
alpha_id: detailId,
selected_ids: selected,
filters: Object.fromEntries(
Object.entries(params).filter(
([, value]) => value !== "" && value !== undefined && value !== null,
),
),
});
}, [active, detailId, selected, params, onContext]);
useEffect(() => {
if (!action || actionNonce.current === action.nonce) return;
actionNonce.current = action.nonce;
if (action.type === "open_alpha") {
setDetailId(action.alpha_id);
onOverlay();
} else {
const {
sort: nextSort,
direction: nextDirection,
limit: _limit,
offset: _offset,
...values
} = action.filters;
const next = Object.fromEntries(
Object.entries(values)
.filter(([, v]) => v !== null && v !== undefined)
.map(([k, v]) => [k, String(v)]),
);
setDraft(next);
setFilters(next);
setPage(1);
setSelected([]);
if (nextSort) setSort(String(nextSort));
if (nextDirection) setDirection(String(nextDirection));
}
}, [action, onOverlay]);
const refresh = useCallback(() => setLocalVersion((n) => n + 1), []);
useEffect(() => {
const controller = new AbortController();
@@ -210,7 +265,8 @@ export function AlphaPage({
.filter(Boolean);
try {
await patch("/alphas/research/bulk", {
alpha_ids: selected,
alpha_ids: Object.keys(bulkVersions),
versions: bulkVersions,
add_tags: split(bulk.add_tags),
remove_tags: split(bulk.remove_tags),
state: bulk.state || null,
@@ -233,7 +289,13 @@ export function AlphaPage({
fixed: "left",
width: 190,
render: (_, row) => (
<button className="alpha-link" onClick={() => setDetailId(row!.id)}>
<button
className="alpha-link"
onClick={() => {
setDetailId(row!.id);
onOverlay();
}}
>
<span>
{row!.research.favorite && <IconStar className="favorite-icon" />}
{row!.name || row!.id}
@@ -569,7 +631,20 @@ export function AlphaPage({
<div className="inline-actions">
{selected.length > 0 && (
<>
<Button size="small" onClick={() => setBulkOpen(true)}>
<Button
size="small"
onClick={() => {
setBulkVersions(
Object.fromEntries(
data.items
.filter((row) => selected.includes(row.id))
.map((row) => [row.id, row.research.version]),
),
);
setBulkOpen(true);
onOverlay();
}}
>
批量编辑
</Button>
<Button
@@ -705,7 +780,7 @@ export function AlphaPage({
</p>
<Modal
title="导入 Alpha ID"
visible={importing}
visible={importing && !overlaySuspended}
onCancel={() => setImporting(false)}
confirmLoading={busy === "alpha_refresh"}
okText="开始导入"
@@ -730,8 +805,8 @@ export function AlphaPage({
/>
</Modal>
<Modal
title={`批量编辑 ${selected.length} 条研究记录`}
visible={bulkOpen}
title={`批量编辑 ${Object.keys(bulkVersions).length} 条研究记录`}
visible={bulkOpen && !overlaySuspended}
onCancel={() => setBulkOpen(false)}
confirmLoading={busy === "bulk"}
onOk={() => void bulkSave()}
@@ -768,6 +843,8 @@ export function AlphaPage({
</label>
</Modal>
<AlphaDetail
suspended={overlaySuspended}
chatOffset={chatOffset}
taskPanelOpen={taskPanelOpen}
id={detailId}
version={`${version}:${localVersion}`}