import { useEffect, useState } from "react"; import { Avatar, Banner, Button, Input, Select, Spin, Tag, Toast, } from "@douyinfe/semi-ui-19"; import { api, displayValue, formatTime, patch, post } from "../api"; import type { Account, AccountUsage } from "../types"; import { ModelSettingsPanel } from "../ai/ModelSettingsPanel"; const connectionLabels: Record = { connected: "已连接", disconnected: "未连接", expired: "会话待恢复", connecting: "连接中", verification_required: "需要人工验证", error: "连接异常", }; const permissionLabels: Record = { BEFORE_AND_AFTER_PERFORMANCE_V2: "提交前后表现", BRAIN_LABS: "BRAIN Labs", BRAIN_LABS_JUPYTER_LAB: "Jupyter Lab", CONSULTANT: "顾问权限", MULTI_SIMULATION: "批量回测", PROD_ALPHAS: "生产 Alpha", REFERRAL: "推荐邀请", SUPER_ALPHA: "Super Alpha", VISUALIZATION: "数据可视化", WORKDAY: "Workday", }; const count = (value: unknown) => typeof value === "number" ? value.toLocaleString("zh-CN") : "未提供"; const duration = (seconds: number | null | undefined) => { if (seconds == null) return "未提供"; if (seconds <= 0) return "已到期"; const minutes = Math.ceil(seconds / 60); return minutes >= 60 ? `${Math.floor(minutes / 60)} 小时 ${minutes % 60} 分钟` : `${minutes} 分钟`; }; export function AccountPage({ account, onChange, onTask, }: { account: Account | null; onChange: () => void; onTask: () => void; }) { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [busy, setBusy] = useState(""); const [settingsOpen, setSettingsOpen] = useState(false); const [preferences, setPreferences] = useState({ display_name: "研究员", theme: "light", timezone: "Asia/Shanghai", page_size: 25, }); useEffect(() => { if (account) setEmail(account.email ?? ""); }, [account?.email]); useEffect(() => { if (account) setPreferences({ display_name: account.display_name, theme: account.theme, timezone: account.timezone, page_size: account.page_size, }); }, [ account?.display_name, account?.theme, account?.timezone, account?.page_size, ]); if (!account) return (
); async function action(key: string, fn: () => Promise, task = false) { setBusy(key); try { await fn(); if (task) onTask(); else { onChange(); Toast.success( key === "profile" ? "正在刷新资料" : key === "disconnect" ? "已断开连接" : "已保存", ); } } catch (e) { Toast.error((e as Error).message); } finally { setBusy(""); } } const profile = account.profile; const name = displayValue( profile.fullName ?? profile.name ?? profile.username ?? [profile.firstName, profile.lastName].filter(Boolean).join(" "), ); const permissions = Array.isArray(profile.permissions) ? profile.permissions.filter((p): p is string => typeof p === "string") : null; const usage = profile.usage as AccountUsage | undefined; const connected = account.connection_status === "connected" && account.session.authenticated; const connecting = account.connection_status === "connecting"; const blocked = Boolean(busy) || connecting; const profileRows: [string, unknown][] = [ ["用户 ID", account.wq_user_id], ["平台邮箱", profile.email ?? account.email], ["账户等级", profile.level ?? profile.role ?? profile.type], ["Genius 等级", profile.geniusLevel], [ "邮箱验证", typeof profile.verified === "boolean" ? profile.verified ? "已验证" : "未验证" : null, ], [ "账户审批", typeof profile.approved === "boolean" ? profile.approved ? "已通过" : "未通过" : null, ], [ "注册时间", typeof profile.dateCreated === "string" ? formatTime(profile.dateCreated, account.timezone) : null, ], [ "顾问身份", permissions ? permissions.includes("CONSULTANT") ? "顾问" : "无顾问权限" : null, ], ]; return ( <>

个人信息

{account.connection_error && ( )} {account.verification_url && (
在 WorldQuant 完成人工验证后,返回继续。
打开验证页面
)}
{name === "未提供" ? account.display_name.slice(0, 1) : name.slice(0, 1)}

{name === "未提供" ? account.display_name : name}

{account.wq_user_id ?? "尚未连接 WorldQuant"}
{account.connection_status === "connected" && !account.session.authenticated ? "会话待恢复" : (connectionLabels[account.connection_status] ?? account.connection_status)}
会话剩余{" "} {duration(account.session.remaining_seconds)} 有效期 {duration(account.session.total_seconds)}
{(settingsOpen || !account.configured) && (

WorldQuant 连接

{ e.preventDefault(); void action("save", async () => { await api("/account/credentials", { method: "PUT", body: JSON.stringify({ email, password }), }); setPassword(""); setSettingsOpen(true); }); }} >
凭据仅在服务端加密保存
)}

基本资料

{profileRows.map(([key, value]) => (
{key}
{displayValue(value)}
))}

账户权限

{permissions ? `${permissions.length} 项` : "未提供"}
{permissions?.length ? (
    {permissions.map((permission) => (
  • {permissionLabels[permission] ?? permission} 已开通
  • ))}
) : (
{permissions ? "平台未授予权限" : "连接后读取平台权限"}
)}

提交与用量

{usage ? `美东日期 ${usage.date}` : "尚未同步"}
{[ ["待提交 Alpha", usage?.alphas.unsubmitted], ["已启用 Alpha", usage?.alphas.active], ["已停用 Alpha", usage?.alphas.decommissioned], ["累计提交", usage?.submissions?.total], ].map(([label, value]) => (
{label} {count(value)}
))}
{( [ ["submissions", "Alpha 提交"], ["simulations", "回测"], ] as const ).map(([key, label]) => ( ))}
用量类型 今日 昨日 每日上限 剩余额度
{label} {usage?.[key]?.today == null ? "尚无当日记录" : count(usage[key]?.today)} {count(usage?.[key]?.yesterday)} {usage?.[key]?.limit == null ? "平台未返回" : count(usage[key]?.limit)} {key === "simulations" && usage?.simulations?.limit != null && "(本地设定)"} {count(usage?.[key]?.remaining)}

回测按本地每日额度计算剩余次数;活动统计可能延迟,当日次数未知时不估算余额。

{usage && Object.entries(usage.errors).map(([key, message]) => (

{ ( { simulations: "回测用量", submissions: "提交用量", alphas: "Alpha 概览", } as Record )[key] } :{message}

))}

工作空间偏好

更新于 {formatTime(account.last_synced_at, account.timezone)}
{ e.preventDefault(); void action("preferences", () => patch("/account/preferences", preferences), ); }} >