Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 72 additions & 10 deletions desktop/renderer/src/components/ModelSetupDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
type="button"
class="model-family-card"
:class="{ active: selectedFamilyId === family.id }"
@click="selectedFamilyId = family.id"
@click="handleFamilyClick(family)"
>
<span class="family-icon" aria-hidden="true">
<img :src="family.logo" :alt="family.label" />
Expand All @@ -40,7 +40,15 @@

<el-form label-position="top" class="model-key-form">
<el-form-item :label="t('modelSetup.modelSelect')">
<el-select v-model="selectedModelName" style="width: 100%">
<el-select
v-model="selectedModelName"
style="width: 100%"
filterable
allow-create
default-first-option
:reserve-keyword="false"
:placeholder="t('modelSetup.modelPlaceholder')"
>
<el-option
v-for="model in selectedFamily.models"
:key="model"
Expand All @@ -49,6 +57,13 @@
/>
</el-select>
</el-form-item>
<el-form-item :label="t('modelSetup.baseUrl')">
<el-input
v-model="baseUrl"
placeholder="https://dashscope.aliyuncs.com/compatible-mode/v1"
@keydown.enter.prevent="saveAndStart"
/>
</el-form-item>
<el-form-item :label="t('modelSetup.apiKey')">
<el-input
v-model="apiKey"
Expand Down Expand Up @@ -91,6 +106,7 @@ interface ModelFamilyPreset {
apiFormat: ApiFormat;
models: string[];
apiKeyPlaceholder: string;
apiKeyUrl?: string;
logo: string;
}

Expand All @@ -100,15 +116,18 @@ const emit = defineEmits<{
configured: [];
}>();

const ALIYUN_OPENAI_COMPATIBLE_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1";

const modelFamilies: ModelFamilyPreset[] = [
{
id: "qwen",
label: "千问",
providerKey: "qwen",
baseUrl: "https://dashscope.aliyuncs.com/compatible-mode",
baseUrl: ALIYUN_OPENAI_COMPATIBLE_BASE_URL,
apiFormat: "openai-chat",
models: ["Qwen3.6-Plus"],
models: ["Qwen3.6-Plus", "Qwen3.5-Plus"],
apiKeyPlaceholder: "sk-...",
apiKeyUrl: "https://platform.qianwenai.com/home/api-keys",
logo: qwenLogo,
},
{
Expand All @@ -126,6 +145,7 @@ const modelFamilies: ModelFamilyPreset[] = [
const step = ref<"select" | "key">("select");
const selectedFamilyId = ref<ModelFamilyId>("qwen");
const selectedModelName = ref(modelFamilies[0].models[0]);
const baseUrl = ref(modelFamilies[0].baseUrl);
const apiKey = ref("");
const errorMsg = ref("");
const saving = ref(false);
Expand All @@ -135,15 +155,21 @@ const selectedFamily = computed(
);

watch(selectedFamilyId, () => {
selectedModelName.value = selectedFamily.value.models[0];
errorMsg.value = "";
applyFamilyDefaults();
});

watch(selectedModelName, (modelName) => {
if (isAliyunModel(modelName)) {
baseUrl.value = ALIYUN_OPENAI_COMPATIBLE_BASE_URL;
}
});

watch(
() => props.modelValue,
(visible) => {
if (!visible) return;
step.value = "select";
applyFamilyDefaults();
errorMsg.value = "";
},
);
Expand All @@ -152,26 +178,62 @@ function resolveApiValue(apiFormat: ApiFormat): string {
return apiFormat === "anthropic" ? "anthropic-messages" : "openai-completions";
}

function isAliyunModel(modelName: string): boolean {
return modelName.trim().toLowerCase().startsWith("qwen");
}

function applyFamilyDefaults() {
selectedModelName.value = selectedFamily.value.models[0];
baseUrl.value = selectedFamily.value.baseUrl;
errorMsg.value = "";
}

function close() {
emit("update:modelValue", false);
}

function handleGetApiKey() {
// Reserved for the future BYOK/register-url flow.
openApiKeyPage(selectedFamily.value);
goToKeyForm();
}

function handleFamilyClick(family: ModelFamilyPreset) {
selectedFamilyId.value = family.id;
if (family.id === "qwen") {
openApiKeyPage(family);
goToKeyForm();
}
}

function openApiKeyPage(family: ModelFamilyPreset) {
if (!family.apiKeyUrl) return;
window.openclaw.shell.openExternal(family.apiKeyUrl).catch((err) => {
console.error("Failed to open API key page:", err);
});
}

function goToKeyForm() {
selectedModelName.value = selectedFamily.value.models[0];
applyFamilyDefaults();
step.value = "key";
errorMsg.value = "";
}

async function saveAndStart() {
const trimmedModelName = selectedModelName.value.trim();
const trimmedKey = apiKey.value.trim();
const trimmedBaseUrl = baseUrl.value.trim();
if (!trimmedModelName) {
errorMsg.value = t("modelSetup.enterModelName");
return;
}
if (!trimmedKey) {
errorMsg.value = t("modelSetup.enterApiKey");
return;
}
if (!trimmedBaseUrl) {
errorMsg.value = t("modelSetup.enterBaseUrl");
return;
}
if (selectedFamily.value.id === "minimax" && !trimmedKey.startsWith("sk-cp-")) {
errorMsg.value = t("modelSetup.invalidMiniMaxKey");
return;
Expand All @@ -181,11 +243,11 @@ async function saveAndStart() {
errorMsg.value = "";
try {
const family = selectedFamily.value;
const modelName = selectedModelName.value || family.models[0];
const modelName = trimmedModelName;
const modelRef = `${family.providerKey}/${modelName}`;
const existing = (await window.openclaw.config.read()) || {};
const providerEntry: Record<string, unknown> = {
baseUrl: family.baseUrl,
baseUrl: trimmedBaseUrl,
apiKey: trimmedKey,
api: resolveApiValue(family.apiFormat),
models: [
Expand Down
4 changes: 4 additions & 0 deletions desktop/renderer/src/i18n/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,9 +434,13 @@ export default {
"modelSetup.getApiKey": "Get API Key",
"modelSetup.haveApiKey": "I already have an API Key",
"modelSetup.modelSelect": "Model",
"modelSetup.modelPlaceholder": "Select or enter a model name",
"modelSetup.baseUrl": "Base URL",
"modelSetup.apiKey": "API KEY",
"modelSetup.start": "Start",
"modelSetup.saving": "Saving…",
"modelSetup.enterModelName": "Please enter a model name",
"modelSetup.enterBaseUrl": "Please enter a Base URL",
"modelSetup.enterApiKey": "Please enter an API key",
"modelSetup.invalidMiniMaxKey": "MiniMax Subscription Key should start with sk-cp-",
"modelSetup.saveFailed": "Save failed: {error}",
Expand Down
4 changes: 4 additions & 0 deletions desktop/renderer/src/i18n/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,9 +415,13 @@ export default {
"modelSetup.getApiKey": "获取 API 密钥",
"modelSetup.haveApiKey": "已获取 API 密钥",
"modelSetup.modelSelect": "模型选择",
"modelSetup.modelPlaceholder": "选择或输入模型名称",
"modelSetup.baseUrl": "Base URL",
"modelSetup.apiKey": "API KEY",
"modelSetup.start": "开始体验",
"modelSetup.saving": "正在保存…",
"modelSetup.enterModelName": "请输入模型名称",
"modelSetup.enterBaseUrl": "请输入 Base URL",
"modelSetup.enterApiKey": "请输入 API KEY",
"modelSetup.invalidMiniMaxKey": "MiniMax Subscription Key 应以 sk-cp- 开头",
"modelSetup.saveFailed": "保存失败: {error}",
Expand Down
Loading