fix(web): skip unavailable DeepSeek onboarding modal

This commit is contained in:
Yichen Jiang
2026-07-31 00:44:24 +08:00
parent c580b1b634
commit 15636fbb8e
12 changed files with 44 additions and 109 deletions
@@ -2,13 +2,6 @@
width: min(420px, 100%);
}
.diagnostic {
margin: 0;
font-size: 13px;
line-height: 20px;
color: var(--dsw-alias-label-secondary);
}
.primary {
width: 100%;
}
@@ -9,7 +9,7 @@ import type { ReactNode } from 'react'
import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
import { Button, Modal } from '@deepseek-ai/dsh-client-ui-primitives'
import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-web-react'
import type { DeepSeekReadiness, ModelsSettingsState, ModelsSettingsStore } from './store.ts'
import type { ModelsSettingsState, ModelsSettingsStore } from './store.ts'
import { deepSeekReadiness } from './store.ts'
import type { en } from './locales.ts'
import styles from './DeepSeekOnboardingDialog.module.css'
@@ -28,35 +28,11 @@ export interface DeepSeekOnboardingInjected {
export type DeepSeekOnboardingDialogProps =
PropsRuntime<'settings.onboarding'> & DeepSeekOnboardingInjected
type UnavailableReason = Extract<DeepSeekReadiness, { kind: 'unavailable' }>['reason']
/* v8 ignore next 3 -- closed-union defaults only defend future source widening */
function assertNever(_value: never): never {
throw new Error('unexpected DeepSeek onboarding state')
}
function unavailableDiagnostic(
reason: UnavailableReason,
t: DeepSeekOnboardingInjected['t'],
): string {
switch (reason) {
case 'load-failed':
return t('onboardingLoadFailed')
case 'credentials-unavailable':
return t('onboardingCredentialsUnavailable')
case 'settings-read-only':
case 'credential-read-only':
return t('onboardingReadOnly')
case 'provider-inactive':
case 'settings-unavailable':
case 'credential-ref-unavailable':
return t('onboardingConfigurationUnavailable')
/* v8 ignore next -- every current unavailable reason is handled above */
default:
return assertNever(reason)
}
}
/**
* Prompt a first-run user to open Models while the official adapter exists
* and its effective credential is not configured.
@@ -84,34 +60,26 @@ export function DeepSeekOnboardingDialog(props: DeepSeekOnboardingDialogProps):
if (!active || dismissed) return null
let unavailableReason: UnavailableReason | undefined
switch (readiness.kind) {
case 'loading':
case 'adapter-absent':
case 'configured':
case 'unavailable':
return null
case 'credential-missing':
unavailableReason = undefined
break
case 'unavailable':
unavailableReason = readiness.reason
break
/* v8 ignore next -- every current readiness variant is handled above */
default:
return assertNever(readiness)
}
const unavailable = unavailableReason !== undefined
const diagnostic = unavailableReason === undefined
? undefined
: unavailableDiagnostic(unavailableReason, t)
return (
<Modal
open
onClose={close}
title={unavailable ? t('onboardingUnavailableTitle') : t('onboardingTitle')}
title={t('onboardingTitle')}
closeLabel={t('onboardingLater')}
{...(unavailable ? {} : { description: t('onboardingDescription') })}
description={t('onboardingDescription')}
className={styles['dialog'] as string}
footer={(
<Button
@@ -123,8 +91,6 @@ export function DeepSeekOnboardingDialog(props: DeepSeekOnboardingDialogProps):
{t('onboardingGoToSettings')}
</Button>
)}
>
{diagnostic === undefined ? undefined : <p className={styles['diagnostic']}>{diagnostic}</p>}
</Modal>
/>
)
}
@@ -32,11 +32,6 @@ export const en = {
onboardingDescription: 'Configure the official DeepSeek provider to start building.',
onboardingGoToSettings: 'Go to settings',
onboardingLater: 'Configure later',
onboardingUnavailableTitle: 'DeepSeek setup is unavailable',
onboardingLoadFailed: 'DeepSeek configuration could not be loaded. Check the connection and try again in Models.',
onboardingCredentialsUnavailable: 'Credential storage is unavailable in this deployment. Check the deployment configuration.',
onboardingReadOnly: 'This deployment does not allow the DeepSeek API key to be changed here. Ask an administrator to provide the credential.',
onboardingConfigurationUnavailable: 'DeepSeek configuration is unavailable in this deployment. Check the deployment composition.',
}
/** Chinese strings (same keys as {@link en}). */
@@ -71,9 +66,4 @@ export const zh: typeof en = {
onboardingDescription: '配置 DeepSeek 官方模型,即可开始使用。',
onboardingGoToSettings: '前往配置',
onboardingLater: '稍后配置',
onboardingUnavailableTitle: '无法在此配置 DeepSeek',
onboardingLoadFailed: '无法加载 DeepSeek 配置。请检查连接,然后在模型设置中重试。',
onboardingCredentialsUnavailable: '当前部署无法使用凭据存储。请检查部署配置。',
onboardingReadOnly: '当前部署不允许在此修改 DeepSeek API 密钥。请联系管理员提供凭据。',
onboardingConfigurationUnavailable: '当前部署无法使用 DeepSeek 配置。请检查部署组合。',
}
@@ -215,8 +215,8 @@ export type DeepSeekReadiness =
/**
* Project official-DeepSeek readiness from the provider/settings/credential
* join used by the Models page. A missing directory entry means the adapter
* is not mounted and therefore cannot be repaired by navigating to Models.
* join used by the Models page. A missing official configurable-provider
* declaration means the adapter is not repairable by navigating to Models.
* @param state - current shared Models join snapshot.
* @returns the onboarding state without reading a parallel fact source.
*/
@@ -230,7 +230,10 @@ export function deepSeekReadiness(state: ModelsSettingsState): DeepSeekReadiness
reason: 'load-failed',
}
}
const row = state.rows.find(candidate => candidate.entry.provider === 'deepseek-official')
const row = state.rows.find(candidate =>
candidate.entry.provider === 'deepseek-official'
&& candidate.entry.settingsNs === 'llm-deepseek'
&& candidate.entry.settingsPath.length === 0)
if (row === undefined) return { kind: 'adapter-absent' }
if (!row.entry.active) {
return {