diff --git a/src/app/me/ai-accounts/page.tsx b/src/app/me/ai-accounts/page.tsx index 6354e5c..5d40f9e 100644 --- a/src/app/me/ai-accounts/page.tsx +++ b/src/app/me/ai-accounts/page.tsx @@ -13,7 +13,7 @@ export default async function AiAccountsPage() { - + >({}); - const [newDraft, setNewDraft] = useState(emptyDraft()); const [busyKey, setBusyKey] = useState(null); const [message, setMessage] = useState(""); const [guideAccountId, setGuideAccountId] = useState(null); + const [onboardingMode, setOnboardingMode] = useState(null); + const [openAiOnboardDraft, setOpenAiOnboardDraft] = useState(defaultOpenAiOnboardDraft()); + const [masterNodeOnboardDraft, setMasterNodeOnboardDraft] = useState( + defaultMasterNodeOnboardDraft(), + ); const accountDrafts = useMemo( () => @@ -175,35 +203,23 @@ export function AiAccountsClient({ })); } - async function saveAccount(accountId?: string) { - const isNew = !accountId; - const draft = isNew ? newDraft : accountDrafts[accountId]; + async function saveAccount(accountId: string) { + const draft = accountDrafts[accountId]; if (!draft.displayName.trim()) { setMessage("AI 账号名称不能为空。"); return; } - setBusyKey(`${isNew ? "create" : "save"}:${accountId ?? "new"}`); - const response = await fetch(isNew ? "/api/v1/accounts" : `/api/v1/accounts/${accountId}`, { - method: isNew ? "POST" : "PATCH", + setBusyKey(`save:${accountId}`); + const response = await fetch(`/api/v1/accounts/${accountId}`, { + method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(draft), }); const result = (await response.json()) as { ok: boolean; message?: string }; setBusyKey(null); - setMessage( - result.ok - ? isNew - ? "AI 账号已创建。" - : "AI 账号已更新。" - : result.message || "AI 账号保存失败。", - ); - if (result.ok) { - if (isNew) { - setNewDraft(emptyDraft()); - } - router.refresh(); - } + setMessage(result.ok ? "AI 账号已更新。" : result.message || "AI 账号保存失败。"); + if (result.ok) router.refresh(); } async function activateAccount(accountId: string) { @@ -245,6 +261,77 @@ export function AiAccountsClient({ } } + function closeOnboarding() { + setOnboardingMode(null); + } + + async function submitOpenAiOnboarding() { + const draft = openAiOnboardDraft; + if (!draft.displayName.trim() || !draft.apiKey.trim()) { + setMessage("请先填写 OpenAI 平台账号名称和 API Key。"); + return; + } + + setBusyKey("onboard:openai_api"); + const response = await fetch("/api/v1/accounts/onboard/openai-api", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + label: draft.label.trim() || "主 GPT", + displayName: draft.displayName.trim(), + accountIdentifier: draft.accountIdentifier.trim(), + model: draft.model.trim() || "gpt-5.4", + apiKey: draft.apiKey.trim(), + }), + }); + const result = (await response.json()) as { ok: boolean; message?: string }; + setBusyKey(null); + if (result.ok) { + setMessage(result.message || "OpenAI 平台账号已登录,并设为当前主控。"); + setOpenAiOnboardDraft(defaultOpenAiOnboardDraft()); + closeOnboarding(); + router.refresh(); + return; + } + setMessage(result.message || "OpenAI 平台账号登录失败。"); + } + + async function submitMasterNodeOnboarding() { + const draft = masterNodeOnboardDraft; + if (!draft.displayName.trim() || !draft.nodeId.trim()) { + setMessage("请先填写 Master Codex Node 的名称和节点 ID。"); + return; + } + + setBusyKey("onboard:master_codex_node"); + const response = await fetch("/api/v1/accounts/onboard/master-node", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + label: draft.label.trim() || "主 GPT", + displayName: draft.displayName.trim(), + accountIdentifier: draft.accountIdentifier.trim(), + nodeId: draft.nodeId.trim(), + nodeLabel: draft.nodeLabel.trim(), + model: draft.model.trim() || "gpt-5.4", + }), + }); + const result = (await response.json()) as { ok: boolean; message?: string }; + setBusyKey(null); + if (result.ok) { + setMessage(result.message || "Master Codex Node 已绑定。"); + setMasterNodeOnboardDraft(defaultMasterNodeOnboardDraft()); + closeOnboarding(); + router.refresh(); + return; + } + setMessage(result.message || "Master Codex Node 绑定失败。"); + } + + function onboardingTitle() { + return onboardingMode === "openai_api" ? "登录 OpenAI 平台账号" : "绑定电脑上的 Codex 节点"; + } + return (
@@ -279,20 +366,62 @@ export function AiAccountsClient({
-
-
设计约束
-
- 原始设计里的“主 GPT / 备用 GPT”不是纯 API 概念,主链路优先走单独在线的 Master - Codex Node,也就是已经在对应电脑上登录了 ChatGPT Plus / Codex 的执行节点。当前生产链路已经是 - - Boss Web -> task queue -> local-agent -> codex exec -> 回写项目账本 - - ,OpenAI API 只作为用户可配置的容灾补位。 +
+
+
+
接入入口
+
先选接入方式,再进入账号列表管理。
+
-
- 如果你要新增一个“主 GPT / 备用 GPT”,这里直接新增多个 - Master Codex Node - 账号即可;真正的登录动作发生在那台绑定电脑上的 Codex / ChatGPT 会话里,APP 负责展示、切换和回退。 +
+ +
@@ -508,128 +637,9 @@ export function AiAccountsClient({ {buildMasterNodeLoginGuide(account)}
) : null} -
- ); - })} - -
-
新增 AI 账号
-
- setNewDraft((current) => ({ ...current, label: value }))} - placeholder="例如:API 容灾" - /> - setNewDraft((current) => ({ ...current, displayName: value }))} - placeholder="例如:OpenAI 生产主控" - /> - - - setNewDraft((current) => ({ ...current, accountIdentifier: value }))} - placeholder="手机号 / 邮箱 / 账号别名" - /> - setNewDraft((current) => ({ ...current, nodeLabel: value }))} - placeholder="例如:备用 Mac mini" - /> - setNewDraft((current) => ({ ...current, nodeId: value }))} - placeholder="例如:mac-mini-02" - /> - setNewDraft((current) => ({ ...current, model: value }))} - placeholder="例如:gpt-5.4" - /> - {newDraft.provider === "openai_api" ? ( -
- setNewDraft((current) => ({ ...current, apiKey: value }))} - placeholder="输入 OpenAI API Key" - type="password" - />
- ) : null} -
- -