From a4655439dd5238f2fb10939c993c46df8663d9b5 Mon Sep 17 00:00:00 2001 From: kris Date: Wed, 1 Apr 2026 08:04:31 +0800 Subject: [PATCH] feat: add preset aliyun qwen model switching --- README.md | 4 + .../com/hyzq/boss/AiAccountsActivity.java | 118 ++++++++++++++++-- .../com/hyzq/boss/AiAccountsActivityTest.java | 111 ++++++++++++++++ .../current_runtime_and_deploy_status_cn.md | 4 + src/components/ai-accounts-client.tsx | 68 ++++++++-- src/lib/ai-account-models.ts | 19 +++ tests/ai-account-models.test.ts | 24 ++++ 7 files changed, 332 insertions(+), 16 deletions(-) create mode 100644 src/lib/ai-account-models.ts create mode 100644 tests/ai-account-models.test.ts diff --git a/README.md b/README.md index f36ff19..37866f3 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,11 @@ Android APK: - 当前 `我的 > AI 账号` 已补 `登录 OpenAI 平台账号`、`接入阿里百炼备用账号` 与 `绑定 Master Codex Node` 三条显式入口;OpenAI API 登录成功后会立即设为当前主控,阿里百炼账号会作为备用链路保存 - 当前 `登录 OpenAI 平台账号` 已升级成浏览器辅助登录流:会先进入原生引导页,再自动打开 `OpenAI Platform` 登录页;用户登录后可直接跳到 `API Keys` 页面,回 APP 粘贴 key 完成接入 - 当前 `AI 账号` 页顶部会显式展示“当前主控身份”,并提供 `校验主控 / 测试主 Agent 对话` 两个动作,切换主控后可直接验证聊天通路 +- 当前阿里百炼备用链已完成一次真实线上闭环验证:手动切到 `aliyun-qwen-backup` 后,`POST /api/v1/projects/master-agent/messages` 会返回 `queued`,并已实际回流 `阿里备用链正常。` 到 `master-agent` 会话 +- 当前 `我的 > AI 账号` 已把阿里百炼备用模型切成预设选择:Web 和原生 Android 都支持直接切换 `qwen3.5-plus / qwen3.5-flash`,只有在预设不适用时才需要填自定义模型 - 当前 `我的 > 主 Agent 提示词 / 记忆` 页面已补:管理员全局主提示词只读展示、用户主提示词、当前对话附加提示词,以及用户通用记忆 / 跨项目项目记忆的新增、编辑、删除接口;当前对话设置按登录账号隔离,管理员全局主提示词不可覆盖 +- 当前 Web 端 `master-agent` 会话页右上角也已补齐微信式三点菜单,支持直接进入 `提示词 / 模型 / 推理强度 / 记忆 / 刷新` +- 当前 `approval_required` 群聊在 Web 端已统一用单一状态快照驱动:如果有新的待确认推荐,会自动折叠旧的拒绝态;如果上次推荐已拒绝,会明确展示“重新生成新的推荐”的恢复入口 - 当前 `OpenAiOnboardingActivity` 在登录成功后会直接给出 `测试主 Agent 对话` 入口,可一键跳到 `master-agent` 聊天页 - 当前主控若还是 `Master Codex Node`,但节点离线或执行立即失败,主 Agent 会优先尝试已配置的 `OpenAI API / 阿里百炼 Qwen` 备用账号,避免聊天直接掉成失败日志 - 当前原生 Android 的聊天发送已改成更短的客户端等待窗口;`master-agent` 单聊依赖服务端快速入队和消息流里的“思考中 / 超时 / 重试等待”状态,不再要求客户端长时间同步阻塞 diff --git a/android/app/src/main/java/com/hyzq/boss/AiAccountsActivity.java b/android/app/src/main/java/com/hyzq/boss/AiAccountsActivity.java index a65301d..5c88c03 100644 --- a/android/app/src/main/java/com/hyzq/boss/AiAccountsActivity.java +++ b/android/app/src/main/java/com/hyzq/boss/AiAccountsActivity.java @@ -1,8 +1,10 @@ package com.hyzq.boss; import android.os.Bundle; +import android.view.View; import android.text.InputType; import android.content.Intent; +import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; @@ -21,6 +23,9 @@ public class AiAccountsActivity extends BossScreenActivity { private static final String[] ROLE_LABELS = {"主 GPT", "备用 GPT", "API 容灾"}; private static final String[] PROVIDER_VALUES = {"master_codex_node", "openai_api", "aliyun_qwen_api"}; private static final String[] PROVIDER_LABELS = {"Master Codex Node", "OpenAI API", "阿里百炼 Qwen"}; + private static final String[] ALIYUN_QWEN_MODEL_VALUES = {"qwen3.5-plus", "qwen3.5-flash", "__custom__"}; + private static final String[] ALIYUN_QWEN_MODEL_LABELS = {"qwen3.5-plus", "qwen3.5-flash", "自定义模型"}; + private static final String ALIYUN_QWEN_PROVIDER = "aliyun_qwen_api"; private boolean refreshOnResume; @Override @@ -345,17 +350,16 @@ public class AiAccountsActivity extends BossScreenActivity { final EditText displayNameInput = BossUi.buildInput(this, "显示名称", false); displayNameInput.setText("阿里百炼备用账号"); final EditText accountIdentifierInput = BossUi.buildInput(this, "账号标识 / 备注", false); - final EditText modelInput = BossUi.buildInput(this, "模型,例如 qwen3.5-plus", false); - modelInput.setText("qwen3.5-plus"); final EditText apiKeyInput = BossUi.buildInput(this, "阿里百炼 API Key", false); apiKeyInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + final AliyunModelSelection modelSelection = buildAliyunQwenModelSelection("qwen3.5-plus"); LinearLayout form = new LinearLayout(this); form.setOrientation(LinearLayout.VERTICAL); form.addView(BossUi.buildFormCell(this, "标签", "建议使用 备用 GPT", labelInput)); form.addView(BossUi.buildFormCell(this, "显示名称", "会展示在账号列表中", displayNameInput)); form.addView(BossUi.buildFormCell(this, "账号标识", "可填账号名或自定义备注", accountIdentifierInput)); - form.addView(BossUi.buildFormCell(this, "模型", "例如 qwen3.5-plus", modelInput)); + form.addView(BossUi.buildFormCell(this, "模型", "预设 qwen3.5-plus / qwen3.5-flash,不适用时切换自定义模型。", modelSelection.container)); form.addView(BossUi.buildFormCell(this, "API Key", "填写后会保存为备用链路,不会抢占当前主控", apiKeyInput)); new AlertDialog.Builder(this) @@ -367,7 +371,7 @@ public class AiAccountsActivity extends BossScreenActivity { labelInput.getText().toString().trim(), displayNameInput.getText().toString().trim(), accountIdentifierInput.getText().toString().trim(), - modelInput.getText().toString().trim(), + modelSelection.resolveModel(), apiKeyInput.getText().toString().trim() )) .show(); @@ -482,6 +486,10 @@ public class AiAccountsActivity extends BossScreenActivity { showMessage("标签、显示名称和 API Key 不能为空"); return; } + if (model.isEmpty()) { + showMessage("模型不能为空"); + return; + } setRefreshing(true); executor.execute(() -> { try { @@ -532,12 +540,17 @@ public class AiAccountsActivity extends BossScreenActivity { final android.widget.EditText nodeIdInput = BossUi.buildInput(this, "节点 ID", false); final android.widget.EditText nodeLabelInput = BossUi.buildInput(this, "节点名称", false); final android.widget.EditText modelInput = BossUi.buildInput(this, "模型,例如 gpt-5.4", false); + final AliyunModelSelection aliyunModelSelection = buildAliyunQwenModelSelection( + existing == null ? "qwen3.5-plus" : existing.optString("model", "") + ); final android.widget.EditText apiKeyInput = BossUi.buildInput(this, "API Key", false); final android.widget.EditText loginStatusInput = BossUi.buildInput(this, "登录状态备注", true); final Spinner roleSpinner = new Spinner(this); roleSpinner.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, ROLE_LABELS)); final Spinner providerSpinner = new Spinner(this); providerSpinner.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, PROVIDER_LABELS)); + final LinearLayout modelFieldContainer = new LinearLayout(this); + modelFieldContainer.setOrientation(LinearLayout.VERTICAL); final SwitchCompat enabledSwitch = new SwitchCompat(this); enabledSwitch.setText("启用"); enabledSwitch.setChecked(existing == null || existing.optBoolean("enabled", true)); @@ -560,6 +573,27 @@ public class AiAccountsActivity extends BossScreenActivity { apiKeyInput.setText(apiKeyHint); } + final Runnable refreshModelField = () -> { + modelFieldContainer.removeAllViews(); + if (ALIYUN_QWEN_PROVIDER.equals(PROVIDER_VALUES[providerSpinner.getSelectedItemPosition()])) { + modelFieldContainer.addView(aliyunModelSelection.container); + } else { + modelFieldContainer.addView(modelInput); + } + }; + providerSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + refreshModelField.run(); + } + + @Override + public void onNothingSelected(AdapterView parent) { + refreshModelField.run(); + } + }); + refreshModelField.run(); + LinearLayout form = new LinearLayout(this); form.setOrientation(LinearLayout.VERTICAL); form.addView(BossUi.buildFormCell(this, "标签", "例如 主 GPT", labelInput)); @@ -567,7 +601,7 @@ public class AiAccountsActivity extends BossScreenActivity { form.addView(BossUi.buildFormCell(this, "账号标识", "邮箱、登录名或备注信息", accountIdentifierInput)); form.addView(BossUi.buildFormCell(this, "节点 ID", "Master Codex Node 的唯一标识", nodeIdInput)); form.addView(BossUi.buildFormCell(this, "节点名称", "用于快速识别节点", nodeLabelInput)); - form.addView(BossUi.buildFormCell(this, "模型", "例如 gpt-5.4", modelInput)); + form.addView(BossUi.buildFormCell(this, "模型", "OpenAI / Master Node 使用通用模型名,阿里百炼会自动切换到预设选择。", modelFieldContainer)); form.addView(BossUi.buildFormCell(this, "API Key", "OpenAI API / 阿里百炼 Qwen 模式需要", apiKeyInput)); form.addView(BossUi.buildFormCell(this, "登录状态备注", "可记录 Plus、有无风控等状态", loginStatusInput)); form.addView(BossUi.buildFormCell(this, "账号角色", null, roleSpinner)); @@ -586,7 +620,9 @@ public class AiAccountsActivity extends BossScreenActivity { accountIdentifierInput.getText().toString().trim(), nodeIdInput.getText().toString().trim(), nodeLabelInput.getText().toString().trim(), - modelInput.getText().toString().trim(), + ALIYUN_QWEN_PROVIDER.equals(PROVIDER_VALUES[providerSpinner.getSelectedItemPosition()]) + ? aliyunModelSelection.resolveModel() + : modelInput.getText().toString().trim(), apiKeyInput.getText().toString().trim(), loginStatusInput.getText().toString().trim(), enabledSwitch.isChecked(), @@ -613,7 +649,11 @@ public class AiAccountsActivity extends BossScreenActivity { String provider ) { if (label.isEmpty() || displayName.isEmpty()) { - showMessage("标签和显示名称不能为空"); + showMessage("标签和显示名称不能为空"); + return; + } + if (model.isEmpty()) { + showMessage("模型不能为空"); return; } setRefreshing(true); @@ -659,6 +699,15 @@ public class AiAccountsActivity extends BossScreenActivity { return 0; } + private int indexOfOrMinusOne(String[] values, String target) { + for (int i = 0; i < values.length; i++) { + if (values[i].equals(target)) { + return i; + } + } + return -1; + } + private void activateAccount(JSONObject account) { setRefreshing(true); executor.execute(() -> { @@ -739,4 +788,59 @@ public class AiAccountsActivity extends BossScreenActivity { } }); } + + private AliyunModelSelection buildAliyunQwenModelSelection(String initialModel) { + LinearLayout container = new LinearLayout(this); + container.setOrientation(LinearLayout.VERTICAL); + + final Spinner presetSpinner = new Spinner(this); + presetSpinner.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, ALIYUN_QWEN_MODEL_LABELS)); + final EditText customModelInput = BossUi.buildInput(this, "自定义模型名", false); + + int presetIndex = indexOfOrMinusOne(ALIYUN_QWEN_MODEL_VALUES, initialModel); + if (presetIndex >= 0 && presetIndex < ALIYUN_QWEN_MODEL_VALUES.length - 1) { + presetSpinner.setSelection(presetIndex); + customModelInput.setVisibility(View.GONE); + } else { + presetSpinner.setSelection(ALIYUN_QWEN_MODEL_VALUES.length - 1); + customModelInput.setText(initialModel == null ? "" : initialModel); + customModelInput.setVisibility(View.VISIBLE); + } + + presetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + customModelInput.setVisibility(position == ALIYUN_QWEN_MODEL_VALUES.length - 1 ? View.VISIBLE : View.GONE); + } + + @Override + public void onNothingSelected(AdapterView parent) { + customModelInput.setVisibility(View.GONE); + } + }); + + container.addView(BossUi.buildFormCell(this, "预设模型", "建议选择 qwen3.5-plus 或 qwen3.5-flash。", presetSpinner)); + container.addView(BossUi.buildFormCell(this, "自定义模型", "预设不适用时填写完整模型名。", customModelInput)); + return new AliyunModelSelection(container, presetSpinner, customModelInput); + } + + private static final class AliyunModelSelection { + private final LinearLayout container; + private final Spinner presetSpinner; + private final EditText customModelInput; + + AliyunModelSelection(LinearLayout container, Spinner presetSpinner, EditText customModelInput) { + this.container = container; + this.presetSpinner = presetSpinner; + this.customModelInput = customModelInput; + } + + String resolveModel() { + int selectedIndex = presetSpinner.getSelectedItemPosition(); + if (selectedIndex >= 0 && selectedIndex < ALIYUN_QWEN_MODEL_VALUES.length - 1) { + return ALIYUN_QWEN_MODEL_VALUES[selectedIndex]; + } + return customModelInput.getText().toString().trim(); + } + } } diff --git a/android/app/src/test/java/com/hyzq/boss/AiAccountsActivityTest.java b/android/app/src/test/java/com/hyzq/boss/AiAccountsActivityTest.java index fe295c8..4862651 100644 --- a/android/app/src/test/java/com/hyzq/boss/AiAccountsActivityTest.java +++ b/android/app/src/test/java/com/hyzq/boss/AiAccountsActivityTest.java @@ -8,8 +8,13 @@ import android.content.SharedPreferences; import android.os.Looper; import android.view.View; import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.SpinnerAdapter; import android.widget.TextView; +import androidx.appcompat.app.AlertDialog; + import org.json.JSONObject; import org.junit.Test; import org.junit.runner.RunWith; @@ -18,6 +23,7 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import org.robolectric.Shadows; import org.robolectric.shadows.ShadowActivity; +import org.robolectric.shadows.ShadowDialog; import org.robolectric.shadows.ShadowToast; import org.robolectric.util.ReflectionHelpers; @@ -133,6 +139,65 @@ public class AiAccountsActivityTest { assertEquals("master-agent", nextIntent.getStringExtra(ProjectDetailActivity.EXTRA_PROJECT_ID)); } + @Test + public void openAliyunQwenOnboardingDialogUsesPresetModelsWithCustomFallback() throws Exception { + TestAiAccountsActivity activity = Robolectric.buildActivity(TestAiAccountsActivity.class).setup().get(); + + ReflectionHelpers.callInstanceMethod(activity, "openAliyunQwenOnboardingDialog"); + Shadows.shadowOf(Looper.getMainLooper()).idle(); + + AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog(); + assertNotNull(dialog); + + View root = dialog.getWindow().getDecorView(); + Spinner modelSpinner = findSpinnerContainingItem(root, "qwen3.5-plus"); + assertNotNull(modelSpinner); + SpinnerAdapter adapter = modelSpinner.getAdapter(); + assertNotNull(adapter); + assertEquals(3, adapter.getCount()); + assertEquals("qwen3.5-plus", adapter.getItem(0).toString()); + assertEquals("qwen3.5-flash", adapter.getItem(1).toString()); + assertEquals("自定义模型", adapter.getItem(2).toString()); + assertEquals("qwen3.5-plus", modelSpinner.getSelectedItem().toString()); + + EditText customModelInput = findEditTextWithHint(root, "自定义模型"); + assertNotNull(customModelInput); + } + + @Test + public void openAccountEditorShowsCustomFallbackForNonPresetAliyunModel() throws Exception { + TestAiAccountsActivity activity = Robolectric.buildActivity(TestAiAccountsActivity.class).setup().get(); + JSONObject existing = new JSONObject() + .put("accountId", "acc-1") + .put("label", "备用 GPT") + .put("displayName", "阿里百炼备用账号") + .put("provider", "aliyun_qwen_api") + .put("model", "qwen-custom-x"); + + ReflectionHelpers.callInstanceMethod( + activity, + "openAccountEditor", + ReflectionHelpers.ClassParameter.from(JSONObject.class, existing), + ReflectionHelpers.ClassParameter.from(String.class, null) + ); + Shadows.shadowOf(Looper.getMainLooper()).idle(); + + AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog(); + assertNotNull(dialog); + + View root = dialog.getWindow().getDecorView(); + Spinner modelSpinner = findSpinnerContainingItem(root, "自定义模型"); + assertNotNull(modelSpinner); + SpinnerAdapter adapter = modelSpinner.getAdapter(); + assertNotNull(adapter); + assertEquals(3, adapter.getCount()); + assertEquals("自定义模型", modelSpinner.getSelectedItem().toString()); + + EditText customModelInput = findEditTextWithHint(root, "自定义模型"); + assertNotNull(customModelInput); + assertEquals("qwen-custom-x", customModelInput.getText().toString()); + } + private static final class TestAiAccountsActivity extends AiAccountsActivity { private int reloadCount = 0; @@ -418,4 +483,50 @@ public class AiAccountsActivityTest { } return false; } + + private static Spinner findSpinnerContainingItem(View root, String expectedText) { + if (root instanceof Spinner) { + Spinner spinner = (Spinner) root; + SpinnerAdapter adapter = spinner.getAdapter(); + if (adapter != null) { + for (int index = 0; index < adapter.getCount(); index += 1) { + Object item = adapter.getItem(index); + if (item != null && item.toString().contains(expectedText)) { + return spinner; + } + } + } + } + if (!(root instanceof ViewGroup)) { + return null; + } + ViewGroup group = (ViewGroup) root; + for (int index = 0; index < group.getChildCount(); index += 1) { + Spinner match = findSpinnerContainingItem(group.getChildAt(index), expectedText); + if (match != null) { + return match; + } + } + return null; + } + + private static EditText findEditTextWithHint(View root, String expectedText) { + if (root instanceof EditText) { + CharSequence hint = ((EditText) root).getHint(); + if (hint != null && hint.toString().contains(expectedText)) { + return (EditText) root; + } + } + if (!(root instanceof ViewGroup)) { + return null; + } + ViewGroup group = (ViewGroup) root; + for (int index = 0; index < group.getChildCount(); index += 1) { + EditText match = findEditTextWithHint(group.getChildAt(index), expectedText); + if (match != null) { + return match; + } + } + return null; + } } diff --git a/docs/architecture/current_runtime_and_deploy_status_cn.md b/docs/architecture/current_runtime_and_deploy_status_cn.md index 93fb703..aefbf7c 100644 --- a/docs/architecture/current_runtime_and_deploy_status_cn.md +++ b/docs/architecture/current_runtime_and_deploy_status_cn.md @@ -112,7 +112,11 @@ cd /Users/kris/code/boss - 当前 `登录 OpenAI 平台账号` 已升级成浏览器辅助登录流:原生 Android 会先进入 `OpenAiOnboardingActivity`,自动打开 `OpenAI Platform` 登录页;用户登录后可直接跳到 `API Keys` 页面,回 APP 粘贴 key 完成接入 - 当前 `OpenAiOnboardingActivity` 在登录成功后会直接弹出 `测试主 Agent 对话`,可一键进入 `master-agent` 聊天页验证主控链路 - 当前 `AI 账号` 页顶部会直接展示“当前主控身份”,并提供 `校验主控 / 测试主 Agent 对话` 两个入口,切换主控后不必再手动退回会话页验证 +- 当前阿里百炼备用链已完成一次真实线上闭环验证:手动切到 `aliyun-qwen-backup` 后,`POST /api/v1/projects/master-agent/messages` 会返回 `queued`,并已实际回流 `阿里备用链正常。` 到 `master-agent` 会话 +- 当前 `我的 > AI 账号` 已把阿里百炼备用模型切成预设选择:Web 和原生 Android 都支持直接切换 `qwen3.5-plus / qwen3.5-flash`,只有预设不适用时才需要填写自定义模型 - 当前 `我的 > 主 Agent 提示词 / 记忆` 页面已接通:管理员全局主提示词只读展示、用户主提示词、当前对话附加提示词,以及用户通用记忆 / 跨项目项目记忆都可以在 Web 端查看和编辑;当前对话设置按登录账号隔离,管理员全局主提示词不可覆盖 +- 当前 Web 端 `master-agent` 会话页右上角也已补齐微信式三点菜单,支持直接进入 `提示词 / 模型 / 推理强度 / 记忆 / 刷新` +- 当前 `approval_required` 群聊在 Web 端已统一用单一状态快照驱动:如果存在新的待确认推荐,会自动折叠旧的拒绝态;如果上次推荐已拒绝,会明确展示“重新生成新的推荐”的恢复入口 - 当前如果主控身份还是 `Master Codex Node`,但该节点离线或执行立即失败,主 Agent 会优先尝试已配置的 `OpenAI API / 阿里百炼 Qwen` 备用账号,不再把失败日志直接原样回给用户 - 当前原生 Android 的聊天发送已收短客户端等待窗口;`master-agent` 单聊依赖服务端快速入队和消息流里的“主 Agent 思考中 / 回复超时 / 重试等待”状态,不再要求客户端长时间同步阻塞 - 当前设备导入主链也已补上第一轮后端闭环:`heartbeat` 可上报真实项目候选,服务端会生成 `deviceImportDraft`;用户可提交勾选结果、生成导入决议,再把选中的线程真正落成聊天窗口 diff --git a/src/components/ai-accounts-client.tsx b/src/components/ai-accounts-client.tsx index 41ace24..a3148c7 100644 --- a/src/components/ai-accounts-client.tsx +++ b/src/components/ai-accounts-client.tsx @@ -10,6 +10,12 @@ import type { AiProvider, MasterIdentitySummary, } from "@/lib/boss-data"; +import { + ALIYUN_QWEN_CUSTOM_MODEL_VALUE, + ALIYUN_QWEN_PRESET_MODELS, + resolveAliyunQwenModelSelection, + resolveAliyunQwenModelValue, +} from "@/lib/ai-account-models"; import { formatTimestampLabel } from "@/lib/boss-projections"; type AccountDraft = { @@ -185,6 +191,45 @@ function AccountField({ ); } +function AliyunQwenModelField({ + value, + onChange, +}: { + value: string; + onChange: (value: string) => void; +}) { + const selection = resolveAliyunQwenModelSelection(value); + const isCustom = selection === ALIYUN_QWEN_CUSTOM_MODEL_VALUE; + + return ( +
+ + {isCustom ? ( + + ) : null} +
+ ); +} + export function AiAccountsClient({ accounts, activeIdentity, @@ -617,12 +662,19 @@ export function AiAccountsClient({ onChange={(value) => updateDraft(account.accountId, (current) => ({ ...current, nodeId: value }))} placeholder="例如:mac-studio" /> - updateDraft(account.accountId, (current) => ({ ...current, model: value }))} - placeholder="例如:gpt-5.4" - /> + {draft.provider === "aliyun_qwen_api" ? ( + updateDraft(account.accountId, (current) => ({ ...current, model: value }))} + /> + ) : ( + updateDraft(account.accountId, (current) => ({ ...current, model: value }))} + placeholder="例如:gpt-5.4" + /> + )} {draft.provider === "openai_api" || draft.provider === "aliyun_qwen_api" ? (
- setAliyunQwenOnboardDraft((current) => ({ ...current, model: value })) } - placeholder="例如:qwen3.5-plus" /> { + assert.equal(isAliyunQwenPresetModel("qwen3.5-plus"), true); + assert.equal(isAliyunQwenPresetModel("qwen3.5-flash"), true); + assert.equal(isAliyunQwenPresetModel(" qwen3.5-plus "), true); +}); + +test("阿里百炼非预设模型会落到自定义选项", () => { + assert.equal(isAliyunQwenPresetModel("qwen-max"), false); + assert.equal(resolveAliyunQwenModelSelection("qwen-max"), ALIYUN_QWEN_CUSTOM_MODEL_VALUE); +}); + +test("阿里百炼模型值会根据当前选择输出预设或自定义值", () => { + assert.equal(resolveAliyunQwenModelValue("qwen3.5-plus", "ignored"), "qwen3.5-plus"); + assert.equal(resolveAliyunQwenModelValue(ALIYUN_QWEN_CUSTOM_MODEL_VALUE, " qwen-max "), "qwen-max"); +});