import { useCallback, useEffect, useState } from "react"; import { Avatar, Badge, Banner, Button, Input, LocaleProvider, Spin, Toast, } from "@douyinfe/semi-ui-19"; import { IconGridView, IconUser, IconBell, IconExit, IconArrowRight, IconPulse, } from "@douyinfe/semi-icons"; import zhCN from "@douyinfe/semi-ui-19/lib/es/locale/source/zh_CN"; import { api, post } from "./api"; import type { Account, Job } from "./types"; import { AccountPage } from "./pages/AccountPage"; import { AlphaPage } from "./pages/AlphaPage"; import { JobPanel } from "./components/JobPanel"; import { ChatPanel } from "./ai/ChatPanel"; import type { PageContext, UIAction } from "./ai/types"; export default function App() { const [authenticated, setAuthenticated] = useState(null); const [account, setAccount] = useState(null); const [jobs, setJobs] = useState([]); const [page, setPage] = useState( location.hash === "#account" ? "account" : "alphas", ); const [showJobs, setShowJobs] = useState(false); const [refreshKey, setRefreshKey] = useState(0); const [pollError, setPollError] = useState(""); const [chatOpen, setChatOpen] = useState(false); const [chatWidth, setChatWidth] = useState(420); const [viewport, setViewport] = useState(window.innerWidth); const [alphaContext, setAlphaContext] = useState({ page: "alphas", }); const [aiAction, setAIAction] = useState(null); const chatOffset = viewport >= 1440 && chatOpen ? chatWidth : 0; const focusBusiness = useCallback(() => { if (window.innerWidth < 1440) setChatOpen(false); }, []); useEffect(() => { const resize = () => setViewport(window.innerWidth); window.addEventListener("resize", resize); return () => window.removeEventListener("resize", resize); }, []); useEffect(() => { document.documentElement.style.setProperty( "--chat-space", `${chatOffset}px`, ); document.documentElement.style.setProperty( "--chat-width", `${chatWidth}px`, ); return () => { document.documentElement.style.setProperty("--chat-space", "0px"); }; }, [chatOffset, chatWidth]); const refresh = useCallback(async () => { try { const [nextAccount, nextJobs] = await Promise.all([ api("/account"), api("/sync-jobs"), ]); setAccount(nextAccount); setJobs(nextJobs); setPollError(""); } catch (error) { setPollError((error as Error).message); } }, []); useEffect(() => { api("/auth/me") .then(() => setAuthenticated(true)) .catch(() => setAuthenticated(false)); const expired = () => { setAuthenticated(false); setAccount(null); setJobs([]); setChatOpen(false); setShowJobs(false); }; window.addEventListener("session-expired", expired); const hash = () => setPage(location.hash === "#account" ? "account" : "alphas"); window.addEventListener("hashchange", hash); return () => { window.removeEventListener("session-expired", expired); window.removeEventListener("hashchange", hash); }; }, []); useEffect(() => { if (!authenticated) return; void refresh(); const timer = window.setInterval(() => void refresh(), 3000); return () => clearInterval(timer); }, [authenticated, refresh]); useEffect(() => { document.body.setAttribute("theme-mode", account?.theme ?? "light"); }, [account?.theme]); const completedVersion = jobs .filter( (j) => j.status === "completed" || j.status === "completed_with_errors", ) .map((j) => `${j.id}:${j.updated_at}`) .join("|"); const pending = jobs.filter((j) => ["queued", "running", "waiting_auth", "waiting_connection"].includes( j.status, ), ).length; const actionDone = () => { void refresh(); setRefreshKey((k) => k + 1); }; const taskCreated = () => { actionDone(); setShowJobs(true); }; const changePage = (next: string) => { location.hash = next; setPage(next); }; const logout = async () => { try { await post("/auth/logout"); setAuthenticated(false); setChatOpen(false); setShowJobs(false); setAccount(null); setJobs([]); setAIAction(null); setAlphaContext({ page: "alphas" }); } catch (e) { Toast.error((e as Error).message); } }; return ( {authenticated === null ? (
) : !authenticated ? ( { setAuthenticated(true); void refresh(); }} /> ) : (
工作空间 /{" "} {page === "alphas" ? "Alpha 管理" : "个人信息"}
{account?.display_name.slice(0, 1) || "研"} {account?.display_name ?? "研究员"}
{pollError && ( )}
setShowJobs(false)} onChange={actionDone} onAccount={() => { setShowJobs(false); changePage("account"); }} /> {!chatOpen && ( )} setChatOpen(false)} context={page === "alphas" ? alphaContext : { page: "account" }} timezone={account?.timezone} onSettings={() => { focusBusiness(); changePage("account"); }} onChanged={actionDone} onAction={(action) => { focusBusiness(); changePage("alphas"); setAIAction(action); }} />
)}
); } function Login({ onLogin }: { onLogin: () => void }) { const [username, setUsername] = useState("admin"); const [password, setPassword] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); async function submit(event: React.FormEvent) { event.preventDefault(); setBusy(true); setError(""); try { await post("/auth/login", { username, password }); setPassword(""); onLogin(); } catch (e) { setError((e as Error).message); } finally { setBusy(false); } } return (
α
ALPHARESEARCH WORKSPACE
WORLDQUANT RESEARCH

让每一次研究,
都有迹可循。

从想法到验证,从信号到洞察。
在一个工作空间里,管理你的 Alpha 研究。

观察 · 记录 · 迭代
你的个人 Alpha 研究工作空间
欢迎回来

登录研究工作空间

使用部署时设置的系统账户登录。

{error && }
WorldQuant 账户在登录后单独连接。
); }