diff --git a/design/model-setup-dialog/v1/README.md b/design/model-setup-dialog/v1/README.md new file mode 100644 index 0000000..0de897d --- /dev/null +++ b/design/model-setup-dialog/v1/README.md @@ -0,0 +1,23 @@ +# Model Setup Dialog Design v1 + +## Scope +- Improve selected-state clarity for provider cards. +- Unify selected ring and check colors with primary action color. +- Replace hardcoded red error/close-hover colors with MAI danger tokens. + +## Decisions +- Selected card keeps a single visible selected border layer. +- Selected fill matches hover fill to avoid a blue tint bias. +- Error text and close-hover danger visuals use semantic tokens: + - --smtc-status-danger-foreground + - --smtc-status-danger-background + +## Files Updated +- desktop/renderer/src/components/ModelSetupDialog.vue +- desktop/renderer/src/components/GatewayLoading.vue +- desktop/renderer/src/App.vue +- desktop/renderer/src/styles/global.css +- desktop/renderer/src/views/ChatView.vue + +## Notes +- This version is intended for design iteration and review on branch design/model-setup-dialog/v1. diff --git a/desktop/renderer/src/App.vue b/desktop/renderer/src/App.vue index 9b4eb14..e5ec171 100644 --- a/desktop/renderer/src/App.vue +++ b/desktop/renderer/src/App.vue @@ -359,12 +359,17 @@ onMounted(async () => { } catch {} // Listen for mid-session integrity alerts (file watcher) - unsubIntegrityAlert = window.openclaw.skills.onIntegrityAlert((result) => { - if (!result.valid) { - integrityResult.value = result; - integrityDialogVisible.value = true; + // Some preview environments do not expose this API yet. + try { + if (typeof window.openclaw.skills.onIntegrityAlert === "function") { + unsubIntegrityAlert = window.openclaw.skills.onIntegrityAlert((result) => { + if (!result.valid) { + integrityResult.value = result; + integrityDialogVisible.value = true; + } + }); } - }); + } catch {} // Listen for sandbox permission requests unsubPermission = window.openclaw.sandbox.onPermissionRequest((data: PermissionRequestData) => { @@ -547,28 +552,40 @@ onUnmounted(() => { diff --git a/desktop/renderer/src/assets/modelprovider/Qwen.png b/desktop/renderer/src/assets/modelprovider/Qwen.png index 2ad8ff2..2389a67 100644 Binary files a/desktop/renderer/src/assets/modelprovider/Qwen.png and b/desktop/renderer/src/assets/modelprovider/Qwen.png differ diff --git a/desktop/renderer/src/assets/modelprovider/minimax.png b/desktop/renderer/src/assets/modelprovider/minimax.png index c0d114b..9ec8d00 100644 Binary files a/desktop/renderer/src/assets/modelprovider/minimax.png and b/desktop/renderer/src/assets/modelprovider/minimax.png differ diff --git a/desktop/renderer/src/components/GatewayLoading.vue b/desktop/renderer/src/components/GatewayLoading.vue index 4f711a3..2fc13a9 100644 --- a/desktop/renderer/src/components/GatewayLoading.vue +++ b/desktop/renderer/src/components/GatewayLoading.vue @@ -403,15 +403,15 @@ watch( } .win-ctrl--close:hover { - background: #fee2e2; + background: var(--smtc-status-danger-background); } .win-ctrl--close:hover svg { - stroke: #dc2626; + stroke: var(--smtc-status-danger-foreground); } :global(html.dark) .win-ctrl--close:hover { - background: rgba(239, 68, 68, 0.15); + background: color-mix(in srgb, var(--smtc-status-danger-background) 28%, transparent); } .loading-center { diff --git a/desktop/renderer/src/components/ModelSetupDialog.vue b/desktop/renderer/src/components/ModelSetupDialog.vue index 47b6498..64dcb38 100644 --- a/desktop/renderer/src/components/ModelSetupDialog.vue +++ b/desktop/renderer/src/components/ModelSetupDialog.vue @@ -2,11 +2,23 @@
@@ -106,7 +139,6 @@ interface ModelFamilyPreset { apiFormat: ApiFormat; models: string[]; apiKeyPlaceholder: string; - apiKeyUrl?: string; logo: string; } @@ -125,24 +157,23 @@ const modelFamilies: ModelFamilyPreset[] = [ providerKey: "qwen", baseUrl: ALIYUN_OPENAI_COMPATIBLE_BASE_URL, apiFormat: "openai-chat", - models: ["Qwen3.6-Plus", "Qwen3.5-Plus"], + models: ["qwen", "Qwen3.6-Plus", "Qwen3.5-Plus"], apiKeyPlaceholder: "sk-...", - apiKeyUrl: "https://platform.qianwenai.com/home/api-keys", logo: qwenLogo, }, { id: "minimax", label: "MiniMax", providerKey: "minimax", - baseUrl: "https://api.minimaxi.com/anthropic", + baseUrl: "", apiFormat: "anthropic", - models: ["MiniMax-M1"], + models: ["minimaxm3", "MiniMax-M1"], apiKeyPlaceholder: "sk-cp-...", logo: minimaxLogo, }, ]; -const step = ref<"select" | "key">("select"); +const isKeyStep = ref(false); const selectedFamilyId = ref("qwen"); const selectedModelName = ref(modelFamilies[0].models[0]); const baseUrl = ref(modelFamilies[0].baseUrl); @@ -155,21 +186,16 @@ const selectedFamily = computed( ); watch(selectedFamilyId, () => { - applyFamilyDefaults(); -}); - -watch(selectedModelName, (modelName) => { - if (isAliyunModel(modelName)) { - baseUrl.value = ALIYUN_OPENAI_COMPATIBLE_BASE_URL; - } + selectedModelName.value = selectedFamily.value.models[0]; + baseUrl.value = selectedFamily.value.baseUrl; + errorMsg.value = ""; }); watch( () => props.modelValue, (visible) => { if (!visible) return; - step.value = "select"; - applyFamilyDefaults(); + isKeyStep.value = false; errorMsg.value = ""; }, ); @@ -178,50 +204,39 @@ function resolveApiValue(apiFormat: ApiFormat): string { return apiFormat === "anthropic" ? "anthropic-messages" : "openai-completions"; } -function isAliyunModel(modelName: string): boolean { - return modelName.trim().toLowerCase().startsWith("qwen"); +function close() { + emit("update:modelValue", false); } -function applyFamilyDefaults() { +function goToKeyForm() { selectedModelName.value = selectedFamily.value.models[0]; baseUrl.value = selectedFamily.value.baseUrl; + isKeyStep.value = true; errorMsg.value = ""; } -function close() { - emit("update:modelValue", false); -} - function handleGetApiKey() { - openApiKeyPage(selectedFamily.value); - goToKeyForm(); -} - -function handleFamilyClick(family: ModelFamilyPreset) { - selectedFamilyId.value = family.id; - if (family.id === "qwen") { - openApiKeyPage(family); - goToKeyForm(); + const signupUrl = + selectedFamily.value.id === "qwen" + ? "https://bailian.console.aliyun.com/?tab=model#/api-key" + : "https://platform.minimaxi.com/user-center/basic-information/interface-key"; + try { + window.openclaw?.shell?.openExternal?.(signupUrl); + } catch { + window.open(signupUrl, "_blank", "noopener,noreferrer"); } + 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() { - applyFamilyDefaults(); - step.value = "key"; +function goToSelectStep() { + isKeyStep.value = false; errorMsg.value = ""; } async function saveAndStart() { const trimmedModelName = selectedModelName.value.trim(); - const trimmedKey = apiKey.value.trim(); const trimmedBaseUrl = baseUrl.value.trim(); + const trimmedKey = apiKey.value.trim(); if (!trimmedModelName) { errorMsg.value = t("modelSetup.enterModelName"); return; @@ -230,10 +245,6 @@ async function saveAndStart() { 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; @@ -247,7 +258,7 @@ async function saveAndStart() { const modelRef = `${family.providerKey}/${modelName}`; const existing = (await window.openclaw.config.read()) || {}; const providerEntry: Record = { - baseUrl: trimmedBaseUrl, + ...(trimmedBaseUrl ? { baseUrl: trimmedBaseUrl } : {}), apiKey: trimmedKey, api: resolveApiValue(family.apiFormat), models: [ @@ -290,13 +301,57 @@ async function saveAndStart() { diff --git a/desktop/renderer/src/styles/global.css b/desktop/renderer/src/styles/global.css index 3908d10..f4e0ca0 100644 --- a/desktop/renderer/src/styles/global.css +++ b/desktop/renderer/src/styles/global.css @@ -24,7 +24,7 @@ --msg-user-bg: #1e293b; --msg-user-text: #ffffff; --msg-ai-bg: transparent; - --danger: #ef4444; + --danger: var(--smtc-status-danger-foreground); --success: #10b981; --warning: #f59e0b; --sidebar-width: 280px; @@ -131,7 +131,7 @@ html.dark { --border-light: #3f3f46; --msg-user-bg: #334155; --msg-ai-bg: transparent; - --danger: #ef4444; + --danger: var(--smtc-status-danger-foreground); --success: #22c55e; --warning: #f59e0b; --card-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); diff --git a/desktop/renderer/src/views/ChatView.vue b/desktop/renderer/src/views/ChatView.vue index b2b16c9..687d1c8 100644 --- a/desktop/renderer/src/views/ChatView.vue +++ b/desktop/renderer/src/views/ChatView.vue @@ -262,7 +262,11 @@ onMounted(() => { if (chatStore.messages.length) { nextTick(scrollToBottom); } - studioStore.initialize(); + try { + studioStore.initialize(); + } catch { + // Studio APIs may be absent in lightweight preview mode. + } }); onUnmounted(() => { @@ -447,11 +451,35 @@ function handleThreadClick(e: MouseEvent) {