diff --git a/apps/macos/Sources/OpenClaw/OnboardingAISetupSupport.swift b/apps/macos/Sources/OpenClaw/OnboardingAISetupSupport.swift index af4a53a41975..7aa2817fdb16 100644 --- a/apps/macos/Sources/OpenClaw/OnboardingAISetupSupport.swift +++ b/apps/macos/Sources/OpenClaw/OnboardingAISetupSupport.swift @@ -89,6 +89,7 @@ extension OnboardingAISetupModel { let id: String let label: String let hint: String? + let actionLabel: String? let brandId: String? let icon: String? let website: String? @@ -117,6 +118,7 @@ extension OnboardingAISetupModel { id: "ollama", label: "Ollama", hint: "Download a tools-capable model from your Ollama server", + actionLabel: nil, brandId: "ollama", icon: nil, website: nil), @@ -124,6 +126,7 @@ extension OnboardingAISetupModel { id: "llama-cpp", label: "Local model (llama.cpp)", hint: "Download an approximately 5.0 GB local model; requires 16 GB RAM", + actionLabel: nil, brandId: "llama-cpp", icon: nil, website: nil), diff --git a/apps/macos/Sources/OpenClaw/OnboardingAISetupView.swift b/apps/macos/Sources/OpenClaw/OnboardingAISetupView.swift index 25d7a30643d5..99d59f2e4f28 100644 --- a/apps/macos/Sources/OpenClaw/OnboardingAISetupView.swift +++ b/apps/macos/Sources/OpenClaw/OnboardingAISetupView.swift @@ -534,7 +534,7 @@ struct OnboardingAISetupView: View { } } Spacer(minLength: 0) - Text("Connect / Set up") + Text(option.actionLabel ?? "Connect / Set up") .font(.caption.weight(.semibold)) .foregroundStyle(Color.accentColor) } diff --git a/apps/macos/Tests/OpenClawIPCTests/OnboardingAISetupTests.swift b/apps/macos/Tests/OpenClawIPCTests/OnboardingAISetupTests.swift index ee1b14cf0fbe..dfccb186c4f9 100644 --- a/apps/macos/Tests/OpenClawIPCTests/OnboardingAISetupTests.swift +++ b/apps/macos/Tests/OpenClawIPCTests/OnboardingAISetupTests.swift @@ -254,17 +254,20 @@ private func detectedSetupResponse( "id": "ollama", "brandId": "ollama", "label": "Ollama", - "hint": "Connect to an Ollama server and select a cloud or local model" + "hint": "Connect to an Ollama server and select a cloud or local model", + "actionLabel": "Choose connection" }, { "id": "llama-cpp", "brandId": "llama-cpp", "label": "Local model (llama.cpp)", - "hint": "Download and run a private GGUF model" + "hint": "Download and run a private GGUF model", + "actionLabel": "Review download" }, { "id": "lmstudio", "brandId": "lmstudio", "label": "LM Studio", "hint": "Connect to a running LM Studio server and use an already loaded model", + "actionLabel": "Connect server", "icon": "https://cdn.simpleicons.org/lmstudio", "website": "https://lmstudio.ai/download" }], @@ -681,6 +684,7 @@ struct OnboardingAISetupTests { id: "ollama", label: "Wire Ollama", hint: "Wire hint", + actionLabel: "Choose connection", brandId: "ollama", icon: "https://cdn.simpleicons.org/ollama", website: "https://ollama.com/download"), @@ -688,6 +692,7 @@ struct OnboardingAISetupTests { id: "llama-cpp", label: "Local model (llama.cpp)", hint: "Private GGUF model", + actionLabel: "Review download", brandId: "llama-cpp", icon: nil, website: nil), @@ -695,6 +700,7 @@ struct OnboardingAISetupTests { id: "lmstudio-local", label: "LM Studio", hint: "Running local service", + actionLabel: "Connect server", brandId: "lmstudio", icon: "https://cdn.simpleicons.org/lmstudio", website: "https://lmstudio.ai/download"), @@ -706,6 +712,7 @@ struct OnboardingAISetupTests { #expect(options.map(\.id) == ["llama-cpp"]) #expect(options.first?.label == "Local model (llama.cpp)") + #expect(options.first?.actionLabel == "Review download") #expect(OnboardingAISetupModel.ProviderWizardKind.prepare.startMethod == "openclaw.setup.prepare.start") } diff --git a/docs/plugins/manifest.md b/docs/plugins/manifest.md index f4543b5fe6c3..6f79895ff4ca 100644 --- a/docs/plugins/manifest.md +++ b/docs/plugins/manifest.md @@ -385,30 +385,31 @@ If a tool has no `toolMetadata`, OpenClaw preserves the existing behavior and lo Each `providerAuthChoices` entry describes one onboarding or auth choice. OpenClaw reads this before provider runtime loads. Provider setup lists use these manifest choices, descriptor-derived setup choices, and install-catalog metadata without loading provider runtime. -| Field | Required | Type | What it means | -| --------------------- | -------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | -| `provider` | Yes | `string` | Provider id this choice belongs to. | -| `method` | Yes | `string` | Auth method id to dispatch to. | -| `choiceId` | Yes | `string` | Stable auth-choice id used by onboarding and CLI flows. | -| `choiceLabel` | No | `string` | User-facing label. If omitted, OpenClaw falls back to `choiceId`. | -| `choiceHint` | No | `string` | Short helper text for the picker. | -| `icon` | No | HTTPS URL | Artwork shown beside this choice in supported onboarding clients. | -| `website` | No | HTTPS URL | Product, sign-in, or installation page shown by supported onboarding clients. | -| `assistantPriority` | No | `number` | Lower values sort earlier in assistant-driven interactive pickers. | -| `assistantVisibility` | No | `"visible"` \| `"manual-only"` | Hide the choice from assistant pickers while still allowing manual CLI selection. | -| `deprecatedChoiceIds` | No | `string[]` | Legacy choice ids that should redirect users to this replacement choice. | -| `groupId` | No | `string` | Optional group id for grouping related choices. | -| `groupLabel` | No | `string` | User-facing label for that group. | -| `groupHint` | No | `string` | Short helper text for the group. | -| `onboardingFeatured` | No | `boolean` | Surface this group in the featured tier of the interactive onboarding picker, before the "More..." entry. | -| `optionKey` | No | `string` | Internal option key for simple one-flag auth flows. | -| `cliFlag` | No | `string` | CLI flag name, such as `--openrouter-api-key`. | -| `cliOption` | No | `string` | Full CLI option shape, such as `--openrouter-api-key `. | -| `cliDescription` | No | `string` | Description used in CLI help. | -| `appGuidedSecret` | No | `boolean` | One pasted secret plus provider defaults is sufficient for app-guided setup. | -| `appGuidedDiscovery` | No | `boolean` | The matching runtime auth method owns read-only local discovery through `appGuidedSetup`. | -| `appGuidedAuth` | No | `"oauth"` \| `"device-code"` | Provider-owned interactive login that native setup clients can render generically. | -| `onboardingScopes` | No | `Array<"text-inference" \| "image-generation" \| "music-generation">` | Which onboarding surfaces this choice should appear in. If omitted, it defaults to `["text-inference"]`. | +| Field | Required | Type | What it means | +| ---------------------- | -------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | +| `provider` | Yes | `string` | Provider id this choice belongs to. | +| `method` | Yes | `string` | Auth method id to dispatch to. | +| `choiceId` | Yes | `string` | Stable auth-choice id used by onboarding and CLI flows. | +| `choiceLabel` | No | `string` | User-facing label. If omitted, OpenClaw falls back to `choiceId`. | +| `choiceHint` | No | `string` | Short helper text for the picker. | +| `icon` | No | HTTPS URL | Artwork shown beside this choice in supported onboarding clients. | +| `website` | No | HTTPS URL | Product, sign-in, or installation page shown by supported onboarding clients. | +| `assistantPriority` | No | `number` | Lower values sort earlier in assistant-driven interactive pickers. | +| `assistantVisibility` | No | `"visible"` \| `"manual-only"` | Hide the choice from assistant pickers while still allowing manual CLI selection. | +| `deprecatedChoiceIds` | No | `string[]` | Legacy choice ids that should redirect users to this replacement choice. | +| `groupId` | No | `string` | Optional group id for grouping related choices. | +| `groupLabel` | No | `string` | User-facing label for that group. | +| `groupHint` | No | `string` | Short helper text for the group. | +| `onboardingFeatured` | No | `boolean` | Surface this group in the featured tier of the interactive onboarding picker, before the "More..." entry. | +| `optionKey` | No | `string` | Internal option key for simple one-flag auth flows. | +| `cliFlag` | No | `string` | CLI flag name, such as `--openrouter-api-key`. | +| `cliOption` | No | `string` | Full CLI option shape, such as `--openrouter-api-key `. | +| `cliDescription` | No | `string` | Description used in CLI help. | +| `appGuidedSecret` | No | `boolean` | One pasted secret plus provider defaults is sufficient for app-guided setup. | +| `appGuidedActionLabel` | No | `string` | Short command label shown when starting provider-owned app-guided setup. | +| `appGuidedDiscovery` | No | `boolean` | The matching runtime auth method owns read-only local discovery through `appGuidedSetup`. | +| `appGuidedAuth` | No | `"oauth"` \| `"device-code"` | Provider-owned interactive login that native setup clients can render generically. | +| `onboardingScopes` | No | `Array<"text-inference" \| "image-generation" \| "music-generation">` | Which onboarding surfaces this choice should appear in. If omitted, it defaults to `["text-inference"]`. | When `appGuidedDiscovery` is true, the matching provider auth method must expose `appGuidedSetup.detect` and `appGuidedSetup.prepare`. Detection must be diff --git a/extensions/llama-cpp/openclaw.plugin.json b/extensions/llama-cpp/openclaw.plugin.json index 86b208113382..224016d19ca4 100644 --- a/extensions/llama-cpp/openclaw.plugin.json +++ b/extensions/llama-cpp/openclaw.plugin.json @@ -29,6 +29,7 @@ "method": "local", "choiceId": "llama-cpp", "appGuidedDiscovery": true, + "appGuidedActionLabel": "Review download", "choiceLabel": "Local model (llama.cpp)", "choiceHint": "Downloads an approximately 5.0 GB local model; requires 16 GB RAM", "groupId": "llama-cpp", diff --git a/extensions/lmstudio/openclaw.plugin.json b/extensions/lmstudio/openclaw.plugin.json index 15d4d0b65d2f..af97f44d83f7 100644 --- a/extensions/lmstudio/openclaw.plugin.json +++ b/extensions/lmstudio/openclaw.plugin.json @@ -44,6 +44,7 @@ "method": "custom", "choiceId": "lmstudio", "appGuidedDiscovery": true, + "appGuidedActionLabel": "Connect server", "appGuidedSecret": true, "choiceLabel": "LM Studio", "choiceHint": "Connect to a running LM Studio server and use an already loaded model", diff --git a/extensions/ollama/openclaw.plugin.json b/extensions/ollama/openclaw.plugin.json index de8959efa007..6afbf523e5de 100644 --- a/extensions/ollama/openclaw.plugin.json +++ b/extensions/ollama/openclaw.plugin.json @@ -58,6 +58,7 @@ "method": "local", "choiceId": "ollama", "appGuidedDiscovery": true, + "appGuidedActionLabel": "Choose connection", "choiceLabel": "Ollama", "choiceHint": "Connect to an Ollama server and select a cloud or local model", "icon": "https://cdn.simpleicons.org/ollama", diff --git a/packages/gateway-protocol/src/schema/openclaw.test.ts b/packages/gateway-protocol/src/schema/openclaw.test.ts index e30e7092be76..ca0cd76d790e 100644 --- a/packages/gateway-protocol/src/schema/openclaw.test.ts +++ b/packages/gateway-protocol/src/schema/openclaw.test.ts @@ -127,6 +127,7 @@ describe("OpenClaw setup detection protocol", () => { brandId: "lmstudio", label: "LM Studio", hint: "Local/self-hosted LM Studio server", + actionLabel: "Connect server", icon: "https://cdn.simpleicons.org/lmstudio", website: "https://lmstudio.ai/download", }, diff --git a/packages/gateway-protocol/src/schema/openclaw.ts b/packages/gateway-protocol/src/schema/openclaw.ts index 8f2db9d75016..b5dc01ebb8b3 100644 --- a/packages/gateway-protocol/src/schema/openclaw.ts +++ b/packages/gateway-protocol/src/schema/openclaw.ts @@ -266,6 +266,7 @@ export const SystemAgentSetupDetectResultSchema = closedObject({ brandId: Type.Optional(NonEmptyString), label: NonEmptyString, hint: Type.Optional(Type.String()), + actionLabel: Type.Optional(NonEmptyString), icon: Type.Optional(SetupInferenceHttpsUrl), website: Type.Optional(SetupInferenceHttpsUrl), }), diff --git a/src/plugins/manifest-registry.test.ts b/src/plugins/manifest-registry.test.ts index 6726df0683dd..68204813073e 100644 --- a/src/plugins/manifest-registry.test.ts +++ b/src/plugins/manifest-registry.test.ts @@ -1258,6 +1258,7 @@ describe("loadPluginManifestRegistry", () => { assistantPriority: 10, assistantVisibility: "visible", appGuidedSecret: true, + appGuidedActionLabel: "Connect account", appGuidedDiscovery: true, }, ], @@ -1326,6 +1327,7 @@ describe("loadPluginManifestRegistry", () => { assistantPriority: 10, assistantVisibility: "visible", appGuidedSecret: true, + appGuidedActionLabel: "Connect account", appGuidedDiscovery: true, }, ]); diff --git a/src/plugins/manifest-setup-normalizers.ts b/src/plugins/manifest-setup-normalizers.ts index f42e04b49dcc..a224e0575f42 100644 --- a/src/plugins/manifest-setup-normalizers.ts +++ b/src/plugins/manifest-setup-normalizers.ts @@ -321,6 +321,7 @@ export function normalizeProviderAuthChoices( const cliOption = normalizeOptionalString(entry.cliOption) ?? ""; const cliDescription = normalizeOptionalString(entry.cliDescription) ?? ""; const appGuidedSecret = entry.appGuidedSecret === true; + const appGuidedActionLabel = normalizeOptionalString(entry.appGuidedActionLabel) ?? ""; const appGuidedAuth = entry.appGuidedAuth === "oauth" || entry.appGuidedAuth === "device-code" ? entry.appGuidedAuth @@ -351,6 +352,7 @@ export function normalizeProviderAuthChoices( ...(cliOption ? { cliOption } : {}), ...(cliDescription ? { cliDescription } : {}), ...(appGuidedSecret ? { appGuidedSecret: true } : {}), + ...(appGuidedActionLabel ? { appGuidedActionLabel } : {}), ...(appGuidedAuth ? { appGuidedAuth } : {}), ...(onboardingScopes.length > 0 ? { onboardingScopes } : {}), }); diff --git a/src/plugins/manifest-types.ts b/src/plugins/manifest-types.ts index 1c65598e41d5..4e739707d27c 100644 --- a/src/plugins/manifest-types.ts +++ b/src/plugins/manifest-types.ts @@ -550,6 +550,8 @@ export type PluginManifestProviderAuthChoice = { cliDescription?: string; /** One pasted secret plus provider defaults is sufficient for app-guided setup. */ appGuidedSecret?: boolean; + /** Short provider-owned command label for starting app-guided setup. */ + appGuidedActionLabel?: string; /** Provider-owned interactive login that native setup clients can render generically. */ appGuidedAuth?: "oauth" | "device-code"; /** diff --git a/src/plugins/provider-auth-choices.test.ts b/src/plugins/provider-auth-choices.test.ts index 9397d6b30f8c..2038a6af7820 100644 --- a/src/plugins/provider-auth-choices.test.ts +++ b/src/plugins/provider-auth-choices.test.ts @@ -322,6 +322,7 @@ describe("provider auth choice manifest helpers", () => { cliFlag: "--openai-api-key", cliOption: "--openai-api-key ", appGuidedSecret: true, + appGuidedActionLabel: "Connect account", appGuidedDiscovery: true, }, ], @@ -368,6 +369,7 @@ describe("provider auth choice manifest helpers", () => { cliFlag: "--openai-api-key", cliOption: "--openai-api-key ", appGuidedSecret: true, + appGuidedActionLabel: "Connect account", appGuidedDiscovery: true, }, ]); diff --git a/src/plugins/provider-auth-choices.ts b/src/plugins/provider-auth-choices.ts index b92af77d0520..71a11e82a6e2 100644 --- a/src/plugins/provider-auth-choices.ts +++ b/src/plugins/provider-auth-choices.ts @@ -31,6 +31,7 @@ export type ProviderAuthChoiceMetadata = { cliOption?: string; cliDescription?: string; appGuidedSecret?: boolean; + appGuidedActionLabel?: string; appGuidedDiscovery?: boolean; appGuidedAuth?: "oauth" | "device-code"; onboardingScopes?: ("text-inference" | "image-generation" | "music-generation")[]; @@ -113,6 +114,7 @@ function toProviderAuthChoiceCandidate(params: { ...(choice.cliOption ? { cliOption: choice.cliOption } : {}), ...(choice.cliDescription ? { cliDescription: choice.cliDescription } : {}), ...(choice.appGuidedSecret ? { appGuidedSecret: true } : {}), + ...(choice.appGuidedActionLabel ? { appGuidedActionLabel: choice.appGuidedActionLabel } : {}), ...(choice.appGuidedDiscovery ? { appGuidedDiscovery: true } : {}), ...(choice.appGuidedAuth ? { appGuidedAuth: choice.appGuidedAuth } : {}), ...(choice.onboardingScopes ? { onboardingScopes: choice.onboardingScopes } : {}), diff --git a/src/system-agent/setup-inference-auth-options.ts b/src/system-agent/setup-inference-auth-options.ts index 219ef4a34eab..0a7f511cd785 100644 --- a/src/system-agent/setup-inference-auth-options.ts +++ b/src/system-agent/setup-inference-auth-options.ts @@ -35,6 +35,7 @@ export type SetupInferencePrepareOption = { brandId?: string; label: string; hint?: string; + actionLabel?: string; icon?: string; website?: string; }; @@ -158,6 +159,9 @@ export function listSetupInferencePrepareOptions( brandId: choice.providerId, label: choice.choiceLabel, ...(choice.choiceHint?.trim() ? { hint: choice.choiceHint.trim() } : {}), + ...(choice.appGuidedActionLabel?.trim() + ? { actionLabel: choice.appGuidedActionLabel.trim() } + : {}), ...(choice.icon ? { icon: choice.icon } : {}), ...(choice.website ? { website: choice.website } : {}), }, diff --git a/src/system-agent/setup-inference.test.ts b/src/system-agent/setup-inference.test.ts index a79721917b72..e4f422b52037 100644 --- a/src/system-agent/setup-inference.test.ts +++ b/src/system-agent/setup-inference.test.ts @@ -540,6 +540,7 @@ describe("detectSetupInference", () => { methodId: "ambient", choiceId: "local-model", choiceLabel: "Local Server", + appGuidedActionLabel: "Connect server", appGuidedDiscovery: true, icon: "https://cdn.example.com/local.svg", website: "https://local.example.com/download", @@ -569,6 +570,14 @@ describe("detectSetupInference", () => { ]); expect(detect).toHaveBeenCalledOnce(); expect(prepare).not.toHaveBeenCalled(); + expect(detection.prepareOptions).toEqual([ + expect.objectContaining({ + id: "local-model", + brandId: "local", + label: "Local Server", + actionLabel: "Connect server", + }), + ]); }); it("surfaces an invalid existing config instead of treating it as fresh", async () => { diff --git a/ui/src/e2e/model-setup-llamacpp.e2e.test.ts b/ui/src/e2e/model-setup-llamacpp.e2e.test.ts index 45ca579e64b4..77fe550b9496 100644 --- a/ui/src/e2e/model-setup-llamacpp.e2e.test.ts +++ b/ui/src/e2e/model-setup-llamacpp.e2e.test.ts @@ -22,18 +22,21 @@ const prepareOptions = [ brandId: "ollama", label: "Ollama", hint: "Connect to an Ollama server and select a cloud or local model", + actionLabel: "Choose connection", }, { id: "llama-cpp", brandId: "llama-cpp", label: "Local model (llama.cpp)", hint: "Download and run a private GGUF model", + actionLabel: "Review download", }, { id: "lmstudio", brandId: "lmstudio", label: "LM Studio", hint: "Connect to a running LM Studio server and use an already loaded model", + actionLabel: "Connect server", icon: "https://cdn.simpleicons.org/lmstudio", website: "https://lmstudio.ai/download", }, @@ -137,7 +140,7 @@ describeControlUiE2e("Control UI llama.cpp setup mocked Gateway E2E", () => { const response = await page.goto(`${server.baseUrl}settings/model-setup`); expect(response?.status()).toBe(200); const llamaCppRow = page.locator('[data-prepare-choice="llama-cpp"]'); - await llamaCppRow.getByRole("button", { name: "Check & set up" }).waitFor(); + await llamaCppRow.getByRole("button", { name: "Review download" }).waitFor(); await expect .poll(() => llamaCppRow.locator('[data-provider-icon="llamacpp"]').count()) .toBe(1); @@ -154,7 +157,7 @@ describeControlUiE2e("Control UI llama.cpp setup mocked Gateway E2E", () => { }); } - await llamaCppRow.getByRole("button", { name: "Check & set up" }).click(); + await llamaCppRow.getByRole("button", { name: "Review download" }).click(); const start = await gateway.waitForRequest("openclaw.setup.prepare.start"); expect(start.params).toMatchObject({ authChoice: "llama-cpp" }); await page.getByRole("heading", { name: "Set up a local model" }).waitFor(); diff --git a/ui/src/e2e/model-setup-lmstudio.e2e.test.ts b/ui/src/e2e/model-setup-lmstudio.e2e.test.ts index e9a1744052d9..a57ace5678ed 100644 --- a/ui/src/e2e/model-setup-lmstudio.e2e.test.ts +++ b/ui/src/e2e/model-setup-lmstudio.e2e.test.ts @@ -22,6 +22,7 @@ const prepareOptions = [ brandId: "lmstudio", label: "LM Studio", hint: "Connect to a running LM Studio server and use an already loaded model", + actionLabel: "Connect server", icon: "https://cdn.simpleicons.org/lmstudio", website: "https://lmstudio.ai/download", }, @@ -138,7 +139,7 @@ describeControlUiE2e("Control UI LM Studio setup mocked Gateway E2E", () => { const response = await page.goto(`${server.baseUrl}settings/model-setup`); expect(response?.status()).toBe(200); const lmStudioRow = page.locator('[data-prepare-choice="lmstudio"]'); - await lmStudioRow.getByRole("button", { name: "Check & set up" }).waitFor(); + await lmStudioRow.getByRole("button", { name: "Connect server" }).waitFor(); await expect .poll(() => lmStudioRow.locator('[data-provider-icon="lmstudio"]').count()) .toBe(1); @@ -152,7 +153,7 @@ describeControlUiE2e("Control UI LM Studio setup mocked Gateway E2E", () => { }); } - await lmStudioRow.getByRole("button", { name: "Check & set up" }).click(); + await lmStudioRow.getByRole("button", { name: "Connect server" }).click(); const start = await gateway.waitForRequest("openclaw.setup.prepare.start"); expect(start.params).toMatchObject({ authChoice: "lmstudio" }); await expect diff --git a/ui/src/e2e/model-setup.e2e.test.ts b/ui/src/e2e/model-setup.e2e.test.ts index b39a14c51ad0..b7dff44f6a1a 100644 --- a/ui/src/e2e/model-setup.e2e.test.ts +++ b/ui/src/e2e/model-setup.e2e.test.ts @@ -22,18 +22,21 @@ const localPrepareOptions = [ brandId: "ollama", label: "Ollama", hint: "Connect to an Ollama server and select a cloud or local model", + actionLabel: "Choose connection", }, { id: "llama-cpp", brandId: "llama-cpp", label: "Local model (llama.cpp)", hint: "Download and run a private GGUF model", + actionLabel: "Review download", }, { id: "lmstudio", brandId: "lmstudio", label: "LM Studio", hint: "Connect to a running LM Studio server and use an already loaded model", + actionLabel: "Connect server", icon: "https://cdn.simpleicons.org/lmstudio", website: "https://lmstudio.ai/download", }, @@ -383,7 +386,7 @@ describeControlUiE2e("Control UI Model Setup mocked Gateway E2E", () => { expect(new Set(localProviderIconColors).size).toBe(1); await page .locator('[data-prepare-choice="ollama"]') - .getByRole("button", { name: "Check & set up" }) + .getByRole("button", { name: "Choose connection" }) .click(); const start = await gateway.waitForRequest("openclaw.setup.prepare.start"); diff --git a/ui/src/pages/model-setup/prepare-options.ts b/ui/src/pages/model-setup/prepare-options.ts index 5b271ecfd6e2..6494673ebff9 100644 --- a/ui/src/pages/model-setup/prepare-options.ts +++ b/ui/src/pages/model-setup/prepare-options.ts @@ -6,6 +6,7 @@ export type ModelSetupPrepareOption = { brandId?: string; label: string; hint?: string; + actionLabel?: string; icon?: string; website?: string; }; diff --git a/ui/src/pages/model-setup/view.test.ts b/ui/src/pages/model-setup/view.test.ts index 1c3836d383dc..01345d78019e 100644 --- a/ui/src/pages/model-setup/view.test.ts +++ b/ui/src/pages/model-setup/view.test.ts @@ -69,6 +69,7 @@ const detected: SystemAgentSetupDetectResult = { brandId: "ollama", label: "Ollama", hint: "Connect to an Ollama server and select a cloud or local model", + actionLabel: "Choose connection", icon: "https://cdn.simpleicons.org/ollama", website: "https://ollama.com/download", }, @@ -77,6 +78,7 @@ const detected: SystemAgentSetupDetectResult = { brandId: "lmstudio", label: "LM Studio", hint: "Connect to a running LM Studio server and use an already loaded model", + actionLabel: "Connect server", icon: "https://cdn.simpleicons.org/lmstudio", website: "https://lmstudio.ai/download", }, @@ -85,6 +87,7 @@ const detected: SystemAgentSetupDetectResult = { brandId: "llama-cpp", label: "Local model (llama.cpp)", hint: "Download and run a private GGUF model", + actionLabel: "Review download", }, ], recommendedInstalls: [ @@ -498,8 +501,12 @@ describe("renderModelSetup", () => { const llamaCpp = container.querySelector( '[data-prepare-choice="llama-cpp"] button', ); - expect(ollama?.textContent).toContain("Check & set up"); - expect(llamaCpp?.textContent).toContain("Check & set up"); + expect(ollama?.textContent).toContain("Choose connection"); + expect(llamaCpp?.textContent).toContain("Review download"); + expect( + container.querySelector('[data-prepare-choice="lmstudio"] button') + ?.textContent, + ).toContain("Connect server"); const llamaCppRow = container.querySelector('[data-prepare-choice="llama-cpp"]'); expect(llamaCppRow?.querySelector('[data-provider-icon="llamacpp"]')).not.toBeNull(); expect(text(llamaCppRow!)).toContain("llama.cpp"); diff --git a/ui/src/pages/model-setup/view.ts b/ui/src/pages/model-setup/view.ts index 734718173e54..06898a68dc41 100644 --- a/ui/src/pages/model-setup/view.ts +++ b/ui/src/pages/model-setup/view.ts @@ -436,7 +436,7 @@ function renderPrepare(props: ModelSetupViewProps, result: SystemAgentSetupDetec ?disabled=${props.actionsDisabled} @click=${() => props.onStartPrepare(option)} > - ${t("modelSetup.prepare.ollamaButton")} + ${option.actionLabel ?? t("modelSetup.prepare.ollamaButton")} `,