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
+49 -2
View File
@@ -17,6 +17,7 @@ import {
} from "@douyinfe/semi-ui-19";
import {
api,
ApiError,
displayValue,
formatTime,
patch,
@@ -39,6 +40,8 @@ export function AlphaDetail({
onClose,
onSaved,
onTask,
suspended = false,
chatOffset = 0,
}: {
id: string | null;
version: string;
@@ -47,6 +50,8 @@ export function AlphaDetail({
onClose: () => void;
onSaved: () => void;
onTask: () => void;
suspended?: boolean;
chatOffset?: number;
}) {
const [detail, setDetail] = useState<Detail | null>(null);
const [pnl, setPnl] = useState<Pnl | null>(null);
@@ -54,10 +59,12 @@ export function AlphaDetail({
const [tagText, setTagText] = useState("");
const [error, setError] = useState("");
const [saving, setSaving] = useState(false);
const [conflict, setConflict] = useState(false);
const [tab, setTab] = useState("overview");
const dirty = useRef(false);
useEffect(() => {
dirty.current = false;
setConflict(false);
setDetail(null);
setResearch(null);
setPnl(null);
@@ -75,6 +82,11 @@ export function AlphaDetail({
if (!dirty.current) {
setResearch(value.research);
setTagText(value.research.tags.join(", "));
} else if (
research &&
value.research.version !== research.version
) {
setConflict(true);
}
}
})
@@ -120,10 +132,19 @@ export function AlphaDetail({
.filter(Boolean),
});
dirty.current = false;
setConflict(false);
Toast.success("研究记录已保存");
onSaved();
} catch (e) {
Toast.error((e as Error).message);
if (e instanceof ApiError && e.status === 409) {
setConflict(true);
try {
setDetail(await api<Detail>(`/alphas/${id}`));
} catch {
/* Preserve the draft if refresh fails. */
}
}
} finally {
setSaving(false);
}
@@ -132,8 +153,10 @@ export function AlphaDetail({
<SideSheet
closeOnEsc={!taskPanelOpen}
className="alpha-detail"
visible={Boolean(id)}
width={800}
visible={Boolean(id) && !suspended}
width={Math.min(800, window.innerWidth - chatOffset)}
style={{ right: chatOffset }}
maskStyle={{ right: chatOffset }}
title={
<span>
Alpha 详情 <code className="muted">{id}</code>
@@ -147,6 +170,30 @@ export function AlphaDetail({
<Spin />
) : (
<>
{conflict && (
<Banner
type="warning"
description={
<span>
研究记录已被其他操作更新,你的草稿已保留。请先复制需要保留的内容,再
<button
className="text-link"
onClick={() => {
if (detail) {
dirty.current = false;
setResearch(detail.research);
setTagText(detail.research.tags.join(", "));
setConflict(false);
}
}}
>
载入最新记录
</button>
。
</span>
}
/>
)}
<div className="detail-title">
<div>
<h2>{detail.name || detail.id}</h2>