feat: add aliyun qwen backup provider
This commit is contained in:
@@ -26,7 +26,7 @@ type AccountDraft = {
|
||||
loginStatusNote: string;
|
||||
};
|
||||
|
||||
type OnboardingMode = "openai_api" | "master_codex_node" | null;
|
||||
type OnboardingMode = "openai_api" | "aliyun_qwen_api" | "master_codex_node" | null;
|
||||
|
||||
type OpenAiOnboardDraft = {
|
||||
label: string;
|
||||
@@ -36,6 +36,14 @@ type OpenAiOnboardDraft = {
|
||||
apiKey: string;
|
||||
};
|
||||
|
||||
type AliyunQwenOnboardDraft = {
|
||||
label: string;
|
||||
displayName: string;
|
||||
accountIdentifier: string;
|
||||
model: string;
|
||||
apiKey: string;
|
||||
};
|
||||
|
||||
type MasterNodeOnboardDraft = {
|
||||
label: string;
|
||||
displayName: string;
|
||||
@@ -56,6 +64,7 @@ function roleOptions() {
|
||||
function providerOptions() {
|
||||
return [
|
||||
{ value: "openai_api", label: "OpenAI API" },
|
||||
{ value: "aliyun_qwen_api", label: "阿里百炼 Qwen" },
|
||||
{ value: "master_codex_node", label: "Master Codex Node / ChatGPT Plus 节点" },
|
||||
] as const;
|
||||
}
|
||||
@@ -70,6 +79,16 @@ function defaultOpenAiOnboardDraft(): OpenAiOnboardDraft {
|
||||
};
|
||||
}
|
||||
|
||||
function defaultAliyunQwenOnboardDraft(): AliyunQwenOnboardDraft {
|
||||
return {
|
||||
label: "备用 GPT",
|
||||
displayName: "阿里百炼备用账号",
|
||||
accountIdentifier: "",
|
||||
model: "qwen3.5-plus",
|
||||
apiKey: "",
|
||||
};
|
||||
}
|
||||
|
||||
function defaultMasterNodeOnboardDraft(): MasterNodeOnboardDraft {
|
||||
return {
|
||||
label: "主 GPT",
|
||||
@@ -184,6 +203,8 @@ export function AiAccountsClient({
|
||||
const [guideAccountId, setGuideAccountId] = useState<string | null>(null);
|
||||
const [onboardingMode, setOnboardingMode] = useState<OnboardingMode>(null);
|
||||
const [openAiOnboardDraft, setOpenAiOnboardDraft] = useState<OpenAiOnboardDraft>(defaultOpenAiOnboardDraft());
|
||||
const [aliyunQwenOnboardDraft, setAliyunQwenOnboardDraft] =
|
||||
useState<AliyunQwenOnboardDraft>(defaultAliyunQwenOnboardDraft());
|
||||
const [masterNodeOnboardDraft, setMasterNodeOnboardDraft] = useState<MasterNodeOnboardDraft>(
|
||||
defaultMasterNodeOnboardDraft(),
|
||||
);
|
||||
@@ -296,6 +317,37 @@ export function AiAccountsClient({
|
||||
setMessage(result.message || "OpenAI 平台账号登录失败。");
|
||||
}
|
||||
|
||||
async function submitAliyunQwenOnboarding() {
|
||||
const draft = aliyunQwenOnboardDraft;
|
||||
if (!draft.displayName.trim() || !draft.apiKey.trim()) {
|
||||
setMessage("请先填写阿里百炼备用账号名称和 API Key。");
|
||||
return;
|
||||
}
|
||||
|
||||
setBusyKey("onboard:aliyun_qwen_api");
|
||||
const response = await fetch("/api/v1/accounts/onboard/aliyun-qwen", {
|
||||
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() || "qwen3.5-plus",
|
||||
apiKey: draft.apiKey.trim(),
|
||||
}),
|
||||
});
|
||||
const result = (await response.json()) as { ok: boolean; message?: string };
|
||||
setBusyKey(null);
|
||||
if (result.ok) {
|
||||
setMessage(result.message || "阿里百炼备用账号已接入。");
|
||||
setAliyunQwenOnboardDraft(defaultAliyunQwenOnboardDraft());
|
||||
closeOnboarding();
|
||||
router.refresh();
|
||||
return;
|
||||
}
|
||||
setMessage(result.message || "阿里百炼备用账号接入失败。");
|
||||
}
|
||||
|
||||
async function submitMasterNodeOnboarding() {
|
||||
const draft = masterNodeOnboardDraft;
|
||||
if (!draft.displayName.trim() || !draft.nodeId.trim()) {
|
||||
@@ -330,7 +382,9 @@ export function AiAccountsClient({
|
||||
}
|
||||
|
||||
function onboardingTitle() {
|
||||
return onboardingMode === "openai_api" ? "登录 OpenAI 平台账号" : "绑定电脑上的 Codex 节点";
|
||||
if (onboardingMode === "openai_api") return "登录 OpenAI 平台账号";
|
||||
if (onboardingMode === "aliyun_qwen_api") return "接入阿里百炼备用账号";
|
||||
return "绑定电脑上的 Codex 节点";
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -399,6 +453,30 @@ export function AiAccountsClient({
|
||||
入口字段:显示名称、账号标识、模型、API Key。
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setOnboardingMode("aliyun_qwen_api");
|
||||
setMessage("");
|
||||
}}
|
||||
disabled={!canManage}
|
||||
className="rounded-3xl border border-[#E5E5EA] bg-gradient-to-br from-[#FFF8EE] to-white p-4 text-left shadow-sm transition hover:-translate-y-0.5 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<div className="text-[16px] font-semibold text-[#111111]">接入阿里百炼备用账号</div>
|
||||
<div className="mt-2 text-[12px] leading-6 text-[#57606A]">
|
||||
作为主 Agent 的备用模型链路,默认建议用 qwen3.5-plus。
|
||||
</div>
|
||||
</div>
|
||||
<span className="rounded-full bg-[#FFF5E8] px-3 py-1 text-[11px] font-semibold text-[#B54708]">
|
||||
备用链路
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-3 rounded-2xl bg-white/80 px-3 py-3 text-[12px] leading-6 text-[#57606A]">
|
||||
入口字段:显示名称、模型、API Key。
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
@@ -545,7 +623,7 @@ export function AiAccountsClient({
|
||||
onChange={(value) => updateDraft(account.accountId, (current) => ({ ...current, model: value }))}
|
||||
placeholder="例如:gpt-5.4"
|
||||
/>
|
||||
{draft.provider === "openai_api" ? (
|
||||
{draft.provider === "openai_api" || draft.provider === "aliyun_qwen_api" ? (
|
||||
<div className="col-span-2">
|
||||
<AccountField
|
||||
label={`API Key${account.apiKeyMasked ? `(已配置 ${account.apiKeyMasked})` : ""}`}
|
||||
@@ -553,7 +631,13 @@ export function AiAccountsClient({
|
||||
onChange={(value) =>
|
||||
updateDraft(account.accountId, (current) => ({ ...current, apiKey: value }))
|
||||
}
|
||||
placeholder={account.apiKeyConfigured ? "留空则保持现有 Key" : "输入 OpenAI API Key"}
|
||||
placeholder={
|
||||
account.apiKeyConfigured
|
||||
? "留空则保持现有 Key"
|
||||
: draft.provider === "aliyun_qwen_api"
|
||||
? "输入阿里百炼 API Key"
|
||||
: "输入 OpenAI API Key"
|
||||
}
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
@@ -679,6 +763,8 @@ export function AiAccountsClient({
|
||||
<div className="mt-1 text-[12px] leading-6 text-[#8C8C8C]">
|
||||
{onboardingMode === "openai_api"
|
||||
? "填写 API Key 后会立即校验,并将这个账号设为当前主控。"
|
||||
: onboardingMode === "aliyun_qwen_api"
|
||||
? "填写阿里百炼 API Key 后会立即校验,并保存为备用模型链路。"
|
||||
: "这个入口只负责绑定电脑节点,真正登录仍发生在那台电脑上。"}
|
||||
</div>
|
||||
</div>
|
||||
@@ -746,6 +832,63 @@ export function AiAccountsClient({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : onboardingMode === "aliyun_qwen_api" ? (
|
||||
<div className="mt-4 space-y-3">
|
||||
<AccountField
|
||||
label="标签"
|
||||
value={aliyunQwenOnboardDraft.label}
|
||||
onChange={(value) => setAliyunQwenOnboardDraft((current) => ({ ...current, label: value }))}
|
||||
placeholder="例如:备用 GPT"
|
||||
/>
|
||||
<AccountField
|
||||
label="显示名称"
|
||||
value={aliyunQwenOnboardDraft.displayName}
|
||||
onChange={(value) =>
|
||||
setAliyunQwenOnboardDraft((current) => ({ ...current, displayName: value }))
|
||||
}
|
||||
placeholder="例如:阿里百炼备用账号"
|
||||
/>
|
||||
<AccountField
|
||||
label="账号标识"
|
||||
value={aliyunQwenOnboardDraft.accountIdentifier}
|
||||
onChange={(value) =>
|
||||
setAliyunQwenOnboardDraft((current) => ({ ...current, accountIdentifier: value }))
|
||||
}
|
||||
placeholder="例如:dashscope 账号备注"
|
||||
/>
|
||||
<AccountField
|
||||
label="模型"
|
||||
value={aliyunQwenOnboardDraft.model}
|
||||
onChange={(value) =>
|
||||
setAliyunQwenOnboardDraft((current) => ({ ...current, model: value }))
|
||||
}
|
||||
placeholder="例如:qwen3.5-plus"
|
||||
/>
|
||||
<AccountField
|
||||
label="API Key"
|
||||
value={aliyunQwenOnboardDraft.apiKey}
|
||||
onChange={(value) => setAliyunQwenOnboardDraft((current) => ({ ...current, apiKey: value }))}
|
||||
placeholder="输入阿里百炼 API Key"
|
||||
type="password"
|
||||
/>
|
||||
<div className="flex items-center gap-3 pt-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={submitAliyunQwenOnboarding}
|
||||
disabled={!canManage || busyKey === "onboard:aliyun_qwen_api"}
|
||||
className="flex-1 rounded-full bg-[#111111] px-4 py-3 text-[13px] font-semibold text-white disabled:opacity-50"
|
||||
>
|
||||
{busyKey === "onboard:aliyun_qwen_api" ? "接入中" : "接入备用账号"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={closeOnboarding}
|
||||
className="rounded-full border border-[#E5E5EA] px-4 py-3 text-[13px] font-semibold text-[#57606A]"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-4 space-y-3">
|
||||
<AccountField
|
||||
|
||||
Reference in New Issue
Block a user