From db89aaeee3442f60b4a426e3d788f494170e800d Mon Sep 17 00:00:00 2001 From: Mark Xian Date: Wed, 19 Aug 2026 17:27:41 +0800 Subject: [PATCH] fix(stage-ui): incorrect DeepSeek provider instance normalized (#2287) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - show the DeepSeek thinking-mode control for configured provider instances whose id is generated - compare provider configs after filling omitted schema defaults so new defaults like `thinkingMode: auto` do not mark old empty DeepSeek configs as configured ## Tests - `pnpm -rF @proj-airi/stage-ui run test:run src/stores/providers/config-defaults.test.ts src/libs/providers/providers/deepseek/index.test.ts` - `pnpm exec moeru-lint packages/stage-ui/src/stores/providers/config-defaults.ts packages/stage-ui/src/stores/providers/config-defaults.test.ts packages/stage-ui/src/stores/providers/provider.ts packages/stage-pages/src/pages/settings/providers/chat/[providerId].vue` - `git diff --check` Co-authored-by: 冼健聪 --- .../settings/providers/chat/[providerId].vue | 11 +++++--- .../stores/providers/config-defaults.test.ts | 25 +++++++++++++++++++ .../src/stores/providers/config-defaults.ts | 9 +++++++ .../stage-ui/src/stores/providers/provider.ts | 3 ++- 4 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 packages/stage-ui/src/stores/providers/config-defaults.test.ts create mode 100644 packages/stage-ui/src/stores/providers/config-defaults.ts diff --git a/packages/stage-pages/src/pages/settings/providers/chat/[providerId].vue b/packages/stage-pages/src/pages/settings/providers/chat/[providerId].vue index 271b3478b..705f6d20c 100644 --- a/packages/stage-pages/src/pages/settings/providers/chat/[providerId].vue +++ b/packages/stage-pages/src/pages/settings/providers/chat/[providerId].vue @@ -14,6 +14,7 @@ import { useProviderValidation } from '@proj-airi/stage-ui/composables/use-provi import { getDefinedProvider } from '@proj-airi/stage-ui/libs' import { useConsciousnessStore } from '@proj-airi/stage-ui/stores/modules/consciousness' import { useProviderConfigStore } from '@proj-airi/stage-ui/stores/providers/config' +import { useProviderStore } from '@proj-airi/stage-ui/stores/providers/provider' import { FieldCombobox } from '@proj-airi/ui' import { storeToRefs } from 'pinia' import { computed } from 'vue' @@ -21,10 +22,12 @@ import { useRoute } from 'vue-router' const route = useRoute() const providerId = route.params.providerId as string -const providerStore = useProviderConfigStore() +const providerConfigStore = useProviderConfigStore() +const providersStore = useProviderStore() const consciousnessStore = useConsciousnessStore() -const { configs: providers } = storeToRefs(providerStore) as { configs: RemovableRef> } +const { configs: providers } = storeToRefs(providerConfigStore) as { configs: RemovableRef> } const { activeProvider } = storeToRefs(consciousnessStore) +const providerDefinition = computed(() => providersStore.findProviderDefinition(providerId)) // Define computed properties for credentials const apiKey = computed({ @@ -54,7 +57,7 @@ const thinkingMode = computed({ }, }) -const supportsDeepSeekThinkingMode = computed(() => providerId === 'deepseek') +const supportsDeepSeekThinkingMode = computed(() => providerDefinition.value?.id === 'deepseek') // Use the composable to get validation logic and state const { @@ -74,7 +77,7 @@ const { } = useProviderValidation(providerId) const apiKeyPlaceholder = computed(() => { - const definition = getDefinedProvider(providerId) + const definition = providerDefinition.value ?? getDefinedProvider(providerId) if (!definition?.createProviderConfig) return 'sk-...' diff --git a/packages/stage-ui/src/stores/providers/config-defaults.test.ts b/packages/stage-ui/src/stores/providers/config-defaults.test.ts new file mode 100644 index 000000000..0a7d9d9fd --- /dev/null +++ b/packages/stage-ui/src/stores/providers/config-defaults.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest' + +import { normalizeProviderConfigDefaults } from './config-defaults' + +describe('normalizeProviderConfigDefaults', () => { + it('treats omitted schema defaults as unchanged provider config', () => { + expect(normalizeProviderConfigDefaults( + { baseUrl: 'https://api.deepseek.com/' }, + { baseUrl: 'https://api.deepseek.com/', thinkingMode: 'auto' }, + )).toEqual({ + baseUrl: 'https://api.deepseek.com/', + thinkingMode: 'auto', + }) + }) + + it('keeps explicit non-default values dirty', () => { + expect(normalizeProviderConfigDefaults( + { baseUrl: 'https://api.deepseek.com/', thinkingMode: 'disable' }, + { baseUrl: 'https://api.deepseek.com/', thinkingMode: 'auto' }, + )).toEqual({ + baseUrl: 'https://api.deepseek.com/', + thinkingMode: 'disable', + }) + }) +}) diff --git a/packages/stage-ui/src/stores/providers/config-defaults.ts b/packages/stage-ui/src/stores/providers/config-defaults.ts new file mode 100644 index 000000000..04a21f9de --- /dev/null +++ b/packages/stage-ui/src/stores/providers/config-defaults.ts @@ -0,0 +1,9 @@ +export function normalizeProviderConfigDefaults(config: Record, defaultOptions: Record) { + const keys = [...new Set([...Object.keys(defaultOptions), ...Object.keys(config)])] + return Object.fromEntries(keys.map((key) => { + if (Object.hasOwn(config, key)) + return [key, config[key]] + + return [key, defaultOptions[key]] + })) +} diff --git a/packages/stage-ui/src/stores/providers/provider.ts b/packages/stage-ui/src/stores/providers/provider.ts index 3284f4309..778b7bc71 100644 --- a/packages/stage-ui/src/stores/providers/provider.ts +++ b/packages/stage-ui/src/stores/providers/provider.ts @@ -32,6 +32,7 @@ import { } from '../../libs/providers' import { selectProviderMetadata, selectProvidersMetadata } from '../../libs/providers/metadata' import { useProviderConfigStore } from './config' +import { normalizeProviderConfigDefaults } from './config-defaults' export type { ModelInfo, VoiceInfo } from '../../libs/providers/types' @@ -836,7 +837,7 @@ export const useProviderStore = defineStore('provider', () => { return false const defaultOptions = getDefaultProviderConfig(providerId) - return JSON.stringify(config) !== JSON.stringify(defaultOptions) + return JSON.stringify(normalizeProviderConfigDefaults(config, defaultOptions)) !== JSON.stringify(defaultOptions) } function shouldListProvider(providerId: string) {