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
21 changes: 21 additions & 0 deletions docs/issues/provider-validation-disabled/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Provider Validation Disabled State Plan

## Scope

The regression is limited to provider settings verification controls in renderer components.

## Implementation

- Gate verify-key entry points on `provider.enable` in the affected settings components.
- Disable the visible verify buttons so the UI matches the runtime behavior.
- Add a focused renderer regression test around `ProviderApiConfig`, which is the shared verify
entry point for generic providers.

## Test Strategy

- Run the focused renderer test for `ProviderApiConfig`.
- Run repository-required formatting, i18n, and lint checks when dependencies are available.

## Risks

- Low. The change only blocks verification while a provider is explicitly disabled.
22 changes: 22 additions & 0 deletions docs/issues/provider-validation-disabled/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Provider Validation Disabled State

## User Story

As a user editing provider settings, I want the verify-key action to stay unavailable while the
provider is disabled so that I do not see a misleading `Provider not initialized` error.

## Acceptance Criteria

- Disabled providers do not open the model check flow from the generic verify-key action.
- Disabled providers do not run inline verification handlers that would surface the initialization
error.
- Enabled providers keep the existing verification behavior.

## Non-goals

- Redesigning the provider settings layout.
- Changing provider initialization behavior in the main process.

## Open Questions

None.
7 changes: 7 additions & 0 deletions docs/issues/provider-validation-disabled/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Provider Validation Disabled State Tasks

- [x] Confirm the verify-key error is triggered from disabled providers.
- [x] Guard verification actions behind the provider enabled state.
- [x] Add focused renderer regression coverage for the shared verify button.
- [ ] Run `pnpm run format`, `pnpm run i18n`, and `pnpm run lint` (blocked locally: `pnpm install`
cannot fetch `https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz` in this sandbox).
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
variant="outline"
size="sm"
class="text-xs text-normal rounded-lg"
:disabled="!provider.enable"
@click="
handleVerifyCredential({ credential: { accessKeyId, secretAccessKey, region } })
"
Expand Down Expand Up @@ -258,6 +259,10 @@ const handleRegionChange = (value: string) => {
}

const validateCredential = async () => {
if (!props.provider.enable) {
return
}

try {
const resp = await providerStore.checkProvider(props.provider.id)
if (resp.isOk) {
Expand Down
5 changes: 5 additions & 0 deletions src/renderer/settings/components/GitHubCopilotOAuth.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
variant="outline"
size="sm"
class="text-xs text-normal rounded-lg"
:disabled="!provider.enable"
@click="openModelCheckDialog"
>
<Icon icon="lucide:check-check" class="w-4 h-4 text-muted-foreground" />
Expand Down Expand Up @@ -261,6 +262,10 @@ const startOAuthLogin = async () => {
}

const openModelCheckDialog = () => {
if (!props.provider.enable) {
return
}

modelCheckStore.openDialog(props.provider.id)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ const customModelsSource = computed(
)

const validateApiKey = async () => {
if (!props.provider.enable) {
return
}

try {
const resp = await providerStore.checkProvider(props.provider.id)
if (resp.isOk) {
Expand Down Expand Up @@ -432,6 +436,10 @@ const handleConfigChanged = () => {
}

const openModelCheckDialog = () => {
if (!props.provider.enable) {
return
}

modelCheckStore.openDialog(props.provider.id)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
variant="outline"
size="sm"
class="text-xs text-normal rounded-lg"
:disabled="!provider.enable"
@click="openModelCheckDialog"
>
<Icon icon="lucide:check-check" class="w-4 h-4 text-muted-foreground" />
Expand Down Expand Up @@ -672,6 +673,10 @@ const handleApiKeyEnter = async (value: string) => {
}

const validateApiKey = async () => {
if (!props.provider.enable) {
return
}

try {
const resp = await providerStore.checkProvider(props.provider.id)
if (resp.isOk) {
Expand All @@ -693,6 +698,10 @@ const validateApiKey = async () => {
}

const openModelCheckDialog = () => {
if (!props.provider.enable) {
return
}

modelCheckStore.openDialog(props.provider.id)
}

Expand Down
16 changes: 15 additions & 1 deletion src/renderer/settings/components/ProviderApiConfig.vue
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
:placeholder="t('settings.provider.keyPlaceholder')"
style="padding-right: 2.5rem !important"
@blur="handleApiKeyBlur"
@keyup.enter="$emit('validate-key', apiKey)"
@keyup.enter="handleValidateKey"
@update:model-value="apiKey = String($event)"
/>
<Button
Expand Down Expand Up @@ -143,6 +143,7 @@
variant="outline"
size="sm"
class="text-xs text-normal rounded-lg"
:disabled="!canVerifyProvider"
@click="openModelCheckDialog"
>
<Icon icon="lucide:check-check" class="w-4 h-4 text-muted-foreground" />{{
Expand Down Expand Up @@ -271,6 +272,7 @@ const providerApiKeyUrl = computed(() => {
return props.providerWebsites?.apiKey || ''
}
})
const canVerifyProvider = computed(() => props.provider.enable)

watch(
() => props.provider,
Expand Down Expand Up @@ -321,7 +323,19 @@ const handleOAuthError = (error: string) => {
emit('oauth-error', error)
}

const handleValidateKey = () => {
if (!canVerifyProvider.value) {
return
}

emit('validate-key', apiKey.value)
}

const openModelCheckDialog = () => {
if (!canVerifyProvider.value) {
return
}

modelCheckStore.openDialog(props.provider.id)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
variant="outline"
size="sm"
class="text-xs text-normal rounded-lg"
:disabled="!provider.enable"
@click="emit('validate-provider')"
>
<Icon icon="lucide:check-check" class="w-4 h-4 text-muted-foreground" />{{
Expand Down
30 changes: 30 additions & 0 deletions test/renderer/components/ProviderApiConfig.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,36 @@ describe('ProviderApiConfig', () => {
})
})

it('disables provider verification when the provider is not enabled', async () => {
const { wrapper, modelCheckStore } = await setup({
provider: createProvider({
enable: false
})
})

const verifyButton = wrapper.get('[data-testid="provider-verify-button"]')

expect(verifyButton.attributes('disabled')).toBeDefined()

await verifyButton.trigger('click')
await flushPromises()

expect(modelCheckStore.openDialog).not.toHaveBeenCalled()
})

it('does not emit validation from the API key enter shortcut when the provider is disabled', async () => {
const { wrapper } = await setup({
provider: createProvider({
enable: false
})
})

await wrapper.get('input#deepseek-apikey').trigger('keyup.enter')
await flushPromises()

expect(wrapper.emitted('validate-key')).toBeUndefined()
})

it('shows a destructive toast when metadata-backed refresh fails', async () => {
const { wrapper, toast, llmproviderPresenter } = await setup({
provider: createProvider({
Expand Down