Download desktop/src/stores/settingsStore.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 20.7 kB
-
https://huggingface.co/chenbhao/codev/resolve/e40073cc959cd4729bf4009662bca374c5c4e266/desktop/src/stores/settingsStore.ts
- Command line
-
hf download hf://chenbhao/codev@e40073cc959cd4729bf4009662bca374c5c4e266/desktop/src/stores/settingsStore.ts
-
curl -L -o settingsStore.ts https://huggingface.co/chenbhao/codev/resolve/e40073cc959cd4729bf4009662bca374c5c4e266/desktop/src/stores/settingsStore.ts
20.7 kB
| import { create } from 'zustand' | |
| import { ApiError } from '../api/client' | |
| import { settingsApi } from '../api/settings' | |
| import { h5AccessApi } from '../api/h5Access' | |
| import { fetchProviderModels, clearProviderModelCache } from '../api/providerModels' | |
| import { getTuiConfig, saveTuiConfigPatch, clearConfigCache } from '../api/config' | |
| import { | |
| isThemeMode, | |
| type AppMode, | |
| type AppModeConfig, | |
| type DesktopTerminalSettings, | |
| type DesktopTerminalStartupShell, | |
| type H5AccessSettings, | |
| type NetworkSettings, | |
| type PermissionMode, | |
| type EffortLevel, | |
| type ModelInfo, | |
| type ThemeMode, | |
| type UpdateProxyMode, | |
| type UpdateProxySettings, | |
| type WebSearchSettings, | |
| } from '../types/settings' | |
| import { isTauriRuntime } from '../lib/desktopRuntime' | |
| import type { Locale } from '../i18n' | |
| import { | |
| APP_ZOOM_CONTROL_STEP, | |
| DEFAULT_APP_ZOOM, | |
| MAX_APP_ZOOM, | |
| MIN_APP_ZOOM, | |
| applyAppZoomLevel, | |
| normalizeAppZoomLevel, | |
| readStoredAppZoomLevel, | |
| } from '../lib/appZoom' | |
| import { useUIStore } from './uiStore' | |
| const LOCALE_STORAGE_KEY = 'cc-haha-locale' | |
| export const UI_ZOOM_MIN = MIN_APP_ZOOM | |
| export const UI_ZOOM_MAX = MAX_APP_ZOOM | |
| export const UI_ZOOM_STEP = APP_ZOOM_CONTROL_STEP | |
| export const UI_ZOOM_DEFAULT = DEFAULT_APP_ZOOM | |
| let desktopNotificationsSaveQueue: Promise<void> = Promise.resolve() | |
| function getStoredLocale(): Locale { | |
| try { | |
| const stored = localStorage.getItem(LOCALE_STORAGE_KEY) | |
| if (stored === 'en' || stored === 'zh') return stored | |
| } catch { /* localStorage unavailable */ } | |
| return 'zh' | |
| } | |
| type SettingsStore = { | |
| permissionMode: PermissionMode | |
| currentModel: ModelInfo | null | |
| effortLevel: EffortLevel | |
| thinkingEnabled: boolean | |
| availableModels: ModelInfo[] | |
| activeProviderId: string | null | |
| activeProviderName: string | null | |
| locale: Locale | |
| theme: ThemeMode | |
| skipWebFetchPreflight: boolean | |
| desktopNotificationsEnabled: boolean | |
| desktopTerminal: DesktopTerminalSettings | |
| webSearch: WebSearchSettings | |
| updateProxy: UpdateProxySettings | |
| network: NetworkSettings | |
| h5Access: H5AccessSettings | |
| h5AccessError: string | null | |
| responseLanguage: string | |
| uiZoom: number | |
| isLoading: boolean | |
| error: string | null | |
| appMode: AppModeConfig | |
| appModeRequiresRestart: boolean | |
| fetchAll: () => Promise<void> | |
| fetchH5Access: () => Promise<void> | |
| setPermissionMode: (mode: PermissionMode) => Promise<void> | |
| setModel: (modelId: string) => Promise<void> | |
| setActiveProvider: (providerId: string | null, providerName: string | null, models: ModelInfo[]) => void | |
| syncFromConfig: () => Promise<void> | |
| setEffort: (level: EffortLevel) => Promise<void> | |
| setThinkingEnabled: (enabled: boolean) => Promise<void> | |
| setLocale: (locale: Locale) => void | |
| setTheme: (theme: ThemeMode) => Promise<void> | |
| setSkipWebFetchPreflight: (enabled: boolean) => Promise<void> | |
| setDesktopNotificationsEnabled: (enabled: boolean) => Promise<void> | |
| setDesktopTerminal: (settings: DesktopTerminalSettings) => Promise<void> | |
| setWebSearch: (settings: WebSearchSettings) => Promise<void> | |
| setUpdateProxy: (settings: UpdateProxySettings) => Promise<void> | |
| setNetwork: (settings: NetworkSettings) => Promise<void> | |
| enableH5Access: () => Promise<string> | |
| disableH5Access: () => Promise<void> | |
| regenerateH5AccessToken: () => Promise<string> | |
| updateH5AccessSettings: (input: { | |
| allowedOrigins?: string[] | |
| publicBaseUrl?: string | null | |
| }) => Promise<void> | |
| setResponseLanguage: (language: string) => Promise<void> | |
| fetchAppMode: () => Promise<void> | |
| setAppMode: (mode: AppMode, portableDir?: string | null) => Promise<void> | |
| setUiZoom: (zoom: number) => void | |
| } | |
| type NetworkSettingsInput = Partial<Omit<NetworkSettings, 'proxy'>> & { | |
| proxy?: Partial<NetworkSettings['proxy']> | |
| } | |
| const DEFAULT_H5_ACCESS_SETTINGS: H5AccessSettings = { | |
| enabled: false, | |
| tokenPreview: null, | |
| allowedOrigins: [], | |
| publicBaseUrl: null, | |
| } | |
| const DEFAULT_DESKTOP_TERMINAL_SETTINGS: DesktopTerminalSettings = { | |
| startupShell: 'system', | |
| customShellPath: '', | |
| } | |
| const DEFAULT_UPDATE_PROXY_SETTINGS: UpdateProxySettings = { | |
| mode: 'system', | |
| url: '', | |
| } | |
| const DEFAULT_NETWORK_SETTINGS: NetworkSettings = { | |
| aiRequestTimeoutMs: 120_000, | |
| proxy: { | |
| mode: 'system', | |
| url: '', | |
| }, | |
| } | |
| export const useSettingsStore = create<SettingsStore>((set, get) => ({ | |
| permissionMode: 'default', | |
| currentModel: null, | |
| effortLevel: 'medium', | |
| thinkingEnabled: true, | |
| availableModels: [], | |
| activeProviderId: null, | |
| activeProviderName: null, | |
| locale: getStoredLocale(), | |
| theme: useUIStore.getState().theme, | |
| skipWebFetchPreflight: true, | |
| desktopNotificationsEnabled: false, | |
| desktopTerminal: DEFAULT_DESKTOP_TERMINAL_SETTINGS, | |
| webSearch: { mode: 'auto', tavilyApiKey: '', braveApiKey: '' }, | |
| updateProxy: DEFAULT_UPDATE_PROXY_SETTINGS, | |
| network: DEFAULT_NETWORK_SETTINGS, | |
| h5Access: DEFAULT_H5_ACCESS_SETTINGS, | |
| h5AccessError: null, | |
| responseLanguage: '', | |
| uiZoom: readStoredAppZoomLevel(), | |
| isLoading: false, | |
| error: null, | |
| appMode: { | |
| mode: 'default', | |
| portableDir: null, | |
| defaultPortableDir: null, | |
| activeConfigDir: null, | |
| configDirSource: 'system', | |
| }, | |
| appModeRequiresRestart: false, | |
| setUiZoom: (zoom: number) => { | |
| const level = normalizeAppZoomLevel(zoom) | |
| set({ uiZoom: level }) | |
| void applyAppZoomLevel(level) | |
| }, | |
| fetchAll: async () => { | |
| set({ isLoading: true, error: null }) | |
| try { | |
| const previousH5Access = get().h5Access | |
| const [directModels, { mode }, config, userSettings, h5AccessResult] = await Promise.all([ | |
| // Fetch models directly from provider APIs (TUI-style) | |
| fetchProviderModels().catch(() => ({ models: [] as ModelInfo[], provider: null as { id: string; name: string } | null })), | |
| settingsApi.getPermissionMode(), | |
| // Read model + effort from ~/.claude.json (TUI-style), bypassing sidecar | |
| getTuiConfig(), | |
| settingsApi.getUser(), | |
| loadH5AccessSettings(previousH5Access), | |
| ]) | |
| // Use direct models if available (TUI-style), otherwise fall back to sidecar | |
| let availableModels: ModelInfo[] | |
| let activeProviderId: string | null | |
| let activeProviderName: string | null | |
| if (directModels.models.length > 0) { | |
| availableModels = directModels.models | |
| activeProviderId = directModels.provider?.id ?? null | |
| activeProviderName = directModels.provider?.name ?? null | |
| } else { | |
| // No direct models — always derive from ~/.claude.json authProvider. | |
| // Never use sidecar's provider name: it may be stale and corrupted | |
| // (e.g. "TUI: NVIDIA NIM" instead of "NVIDIA"). | |
| const authProvider = config.authProvider as string | undefined | |
| if (authProvider) { | |
| const CLI_PROVIDER_NAMES: Record<string, string> = { | |
| nvidia: 'NVIDIA', | |
| openrouter: 'OpenRouter', | |
| opencode: 'OpenCode Zen', | |
| openai: 'OpenAI', | |
| local: 'Local', | |
| anthropic: 'Anthropic', | |
| } | |
| availableModels = [] | |
| activeProviderId = `cli-${authProvider}` | |
| activeProviderName = CLI_PROVIDER_NAMES[authProvider] ?? authProvider | |
| } else { | |
| availableModels = [] | |
| activeProviderId = null | |
| activeProviderName = null | |
| } | |
| } | |
| // Derive currentModel from config's model field, matched against availableModels | |
| const configModelId = config.model ?? null | |
| const currentModel: ModelInfo | null = configModelId | |
| ? availableModels.find((m) => m.id === configModelId) ?? { | |
| id: configModelId, | |
| name: configModelId, | |
| description: '', | |
| context: '', | |
| } | |
| : null | |
| const effortLevel: EffortLevel = | |
| (config.effortLevel as EffortLevel) ?? 'medium' | |
| const theme = isThemeMode(userSettings.theme) ? userSettings.theme : 'white' | |
| useUIStore.getState().setTheme(theme) | |
| set({ | |
| permissionMode: mode, | |
| availableModels, | |
| activeProviderId, | |
| activeProviderName, | |
| currentModel, | |
| effortLevel, | |
| thinkingEnabled: userSettings.alwaysThinkingEnabled !== false, | |
| theme, | |
| skipWebFetchPreflight: userSettings.skipWebFetchPreflight !== false, | |
| desktopNotificationsEnabled: userSettings.desktopNotificationsEnabled === true, | |
| desktopTerminal: normalizeDesktopTerminalSettings(userSettings.desktopTerminal), | |
| webSearch: normalizeWebSearchSettings(userSettings.webSearch), | |
| updateProxy: normalizeUpdateProxySettings(userSettings.updateProxy), | |
| network: normalizeNetworkSettings(userSettings.network), | |
| h5Access: h5AccessResult.settings, | |
| h5AccessError: h5AccessResult.error, | |
| responseLanguage: typeof userSettings.language === 'string' ? userSettings.language : '', | |
| isLoading: false, | |
| error: null, | |
| }) | |
| } catch (error) { | |
| const message = | |
| error instanceof Error ? error.message : 'Failed to load desktop settings' | |
| set({ isLoading: false, error: message }) | |
| throw error | |
| } | |
| }, | |
| fetchH5Access: async () => { | |
| const result = await loadH5AccessSettings(get().h5Access) | |
| set({ h5Access: result.settings, h5AccessError: result.error }) | |
| }, | |
| setPermissionMode: async (mode) => { | |
| const prev = get().permissionMode | |
| set({ permissionMode: mode }) | |
| try { | |
| await settingsApi.setPermissionMode(mode) | |
| } catch { | |
| set({ permissionMode: prev }) | |
| } | |
| }, | |
| setModel: async (modelId) => { | |
| await saveTuiConfigPatch({ model: modelId }) | |
| const availableModels = get().availableModels | |
| const currentModel: ModelInfo | null = availableModels.find((m) => m.id === modelId) ?? { | |
| id: modelId, | |
| name: modelId, | |
| description: '', | |
| context: '', | |
| } | |
| set({ currentModel }) | |
| }, | |
| setActiveProvider: (providerId, providerName, models) => { | |
| set({ | |
| activeProviderId: providerId, | |
| activeProviderName: providerName, | |
| availableModels: models, | |
| currentModel: models.length > 0 | |
| ? models[0] | |
| : null, | |
| }) | |
| }, | |
| syncFromConfig: async () => { | |
| // Re-read ~/.claude.json and re-fetch the model list for the new provider. | |
| // This keeps settingsStore in sync when cliAuthStore writes to the config file. | |
| clearProviderModelCache() | |
| clearConfigCache() | |
| const config = await getTuiConfig().catch(() => ({})) | |
| const authProvider = config?.authProvider as string | undefined | |
| if (!authProvider) { | |
| set({ activeProviderId: null, activeProviderName: null, availableModels: [], currentModel: null }) | |
| return | |
| } | |
| const CLI_NAMES: Record<string, string> = { | |
| nvidia: 'NVIDIA', | |
| openrouter: 'OpenRouter', | |
| opencode: 'OpenCode Zen', | |
| openai: 'OpenAI', | |
| local: 'Local', | |
| anthropic: 'Anthropic', | |
| } | |
| const cliProviderId = `cli-${authProvider}` | |
| const cliProviderName = CLI_NAMES[authProvider] ?? authProvider | |
| // Fetch fresh models for the new provider | |
| const { models } = await fetchProviderModels().catch(() => ({ models: [] as ModelInfo[], provider: null })) | |
| set({ | |
| activeProviderId: cliProviderId, | |
| activeProviderName: cliProviderName, | |
| availableModels: models, | |
| currentModel: models.length > 0 ? models[0] : null, | |
| }) | |
| }, | |
| setEffort: async (level) => { | |
| const prev = get().effortLevel | |
| set({ effortLevel: level }) | |
| try { | |
| await saveTuiConfigPatch({ effortLevel: level }) | |
| } catch { | |
| set({ effortLevel: prev }) | |
| } | |
| }, | |
| setThinkingEnabled: async (enabled) => { | |
| const prev = get().thinkingEnabled | |
| set({ thinkingEnabled: enabled }) | |
| try { | |
| await settingsApi.updateUser({ alwaysThinkingEnabled: enabled }) | |
| } catch { | |
| set({ thinkingEnabled: prev }) | |
| } | |
| }, | |
| setLocale: (locale) => { | |
| set({ locale }) | |
| try { localStorage.setItem(LOCALE_STORAGE_KEY, locale) } catch { /* noop */ } | |
| }, | |
| setTheme: async (theme) => { | |
| const prev = get().theme | |
| set({ theme }) | |
| useUIStore.getState().setTheme(theme) | |
| try { | |
| await settingsApi.updateUser({ theme }) | |
| } catch { | |
| set({ theme: prev }) | |
| useUIStore.getState().setTheme(prev) | |
| } | |
| }, | |
| setSkipWebFetchPreflight: async (enabled) => { | |
| const prev = get().skipWebFetchPreflight | |
| set({ skipWebFetchPreflight: enabled }) | |
| try { | |
| await settingsApi.updateUser({ skipWebFetchPreflight: enabled }) | |
| } catch { | |
| set({ skipWebFetchPreflight: prev }) | |
| } | |
| }, | |
| setDesktopNotificationsEnabled: async (enabled) => { | |
| const prev = get().desktopNotificationsEnabled | |
| set({ desktopNotificationsEnabled: enabled }) | |
| const save = desktopNotificationsSaveQueue | |
| .catch(() => undefined) | |
| .then(async () => { | |
| if (get().desktopNotificationsEnabled !== enabled) return | |
| await settingsApi.updateUser({ desktopNotificationsEnabled: enabled }) | |
| }) | |
| desktopNotificationsSaveQueue = save | |
| try { | |
| await save | |
| } catch { | |
| if (get().desktopNotificationsEnabled === enabled) { | |
| set({ desktopNotificationsEnabled: prev }) | |
| } | |
| } | |
| }, | |
| setDesktopTerminal: async (settings) => { | |
| const prev = get().desktopTerminal | |
| const next = normalizeDesktopTerminalSettings(settings) | |
| set({ desktopTerminal: next }) | |
| try { | |
| await settingsApi.updateUser({ desktopTerminal: next }) | |
| } catch (error) { | |
| set({ desktopTerminal: prev }) | |
| throw error | |
| } | |
| }, | |
| setWebSearch: async (webSearch) => { | |
| const prev = get().webSearch | |
| const next = normalizeWebSearchSettings(webSearch) | |
| set({ webSearch: next }) | |
| try { | |
| await settingsApi.updateUser({ webSearch: next }) | |
| } catch { | |
| set({ webSearch: prev }) | |
| } | |
| }, | |
| setUpdateProxy: async (settings) => { | |
| const prev = get().updateProxy | |
| const next = normalizeUpdateProxySettings(settings) | |
| set({ updateProxy: next }) | |
| try { | |
| await settingsApi.updateUser({ updateProxy: next }) | |
| } catch (error) { | |
| set({ updateProxy: prev }) | |
| throw error | |
| } | |
| }, | |
| setNetwork: async (settings) => { | |
| const prev = get().network | |
| const next = normalizeNetworkSettings(settings) | |
| set({ network: next }) | |
| try { | |
| await settingsApi.updateUser({ network: next }) | |
| } catch (error) { | |
| set({ network: prev }) | |
| throw error | |
| } | |
| }, | |
| enableH5Access: async () => { | |
| set({ h5AccessError: null }) | |
| try { | |
| const { settings, token } = await h5AccessApi.enable() | |
| set({ | |
| h5Access: normalizeH5AccessSettings(settings), | |
| h5AccessError: null, | |
| }) | |
| return token | |
| } catch (error) { | |
| set({ h5AccessError: getErrorMessage(error, 'Failed to enable H5 access.') }) | |
| throw error | |
| } | |
| }, | |
| disableH5Access: async () => { | |
| set({ h5AccessError: null }) | |
| try { | |
| const { settings } = await h5AccessApi.disable() | |
| set({ | |
| h5Access: normalizeH5AccessSettings(settings), | |
| h5AccessError: null, | |
| }) | |
| } catch (error) { | |
| set({ h5AccessError: getErrorMessage(error, 'Failed to disable H5 access.') }) | |
| throw error | |
| } | |
| }, | |
| regenerateH5AccessToken: async () => { | |
| set({ h5AccessError: null }) | |
| try { | |
| const { settings, token } = await h5AccessApi.regenerate() | |
| set({ | |
| h5Access: normalizeH5AccessSettings(settings), | |
| h5AccessError: null, | |
| }) | |
| return token | |
| } catch (error) { | |
| set({ h5AccessError: getErrorMessage(error, 'Failed to regenerate the H5 token.') }) | |
| throw error | |
| } | |
| }, | |
| updateH5AccessSettings: async (input) => { | |
| set({ h5AccessError: null }) | |
| try { | |
| const { settings } = await h5AccessApi.update(input) | |
| set({ | |
| h5Access: normalizeH5AccessSettings(settings), | |
| h5AccessError: null, | |
| }) | |
| } catch (error) { | |
| set({ h5AccessError: getErrorMessage(error, 'Failed to update H5 access settings.') }) | |
| throw error | |
| } | |
| }, | |
| setResponseLanguage: async (language) => { | |
| const prev = get().responseLanguage | |
| set({ responseLanguage: language }) | |
| try { | |
| await settingsApi.updateUser({ language: language || undefined }) | |
| } catch { | |
| set({ responseLanguage: prev }) | |
| } | |
| }, | |
| fetchAppMode: async () => { | |
| if (!isTauriRuntime()) return | |
| try { | |
| const { invoke } = await import('@tauri-apps/api/core') | |
| const result: AppModeConfig = await invoke('get_app_mode') | |
| set({ appMode: result }) | |
| } catch { /* silently ignore - not in Tauri or command unavailable */ } | |
| }, | |
| setAppMode: async (mode, portableDir) => { | |
| if (!isTauriRuntime()) return | |
| const prev = get().appMode | |
| const newMode: AppModeConfig = { | |
| ...prev, | |
| mode, | |
| portableDir: mode === 'portable' | |
| ? portableDir ?? prev.defaultPortableDir ?? prev.portableDir | |
| : null, | |
| activeConfigDir: mode === 'portable' | |
| ? portableDir ?? prev.defaultPortableDir ?? prev.portableDir | |
| : null, | |
| configDirSource: mode === 'portable' ? 'portable' : 'system', | |
| } | |
| set({ appMode: newMode, appModeRequiresRestart: true }) | |
| try { | |
| const { invoke } = await import('@tauri-apps/api/core') | |
| await invoke('set_app_mode', { | |
| mode, | |
| portableDir: newMode.portableDir || null, | |
| }) | |
| } catch { | |
| set({ appMode: prev, appModeRequiresRestart: false }) | |
| } | |
| }, | |
| })) | |
| function normalizeWebSearchSettings(settings: WebSearchSettings | undefined): WebSearchSettings { | |
| return { | |
| mode: settings?.mode ?? 'auto', | |
| tavilyApiKey: settings?.tavilyApiKey ?? '', | |
| braveApiKey: settings?.braveApiKey ?? '', | |
| } | |
| } | |
| function isUpdateProxyMode(value: unknown): value is UpdateProxyMode { | |
| return value === 'system' || value === 'manual' | |
| } | |
| function normalizeUpdateProxySettings( | |
| settings: Partial<UpdateProxySettings> | undefined, | |
| ): UpdateProxySettings { | |
| const mode = isUpdateProxyMode(settings?.mode) | |
| ? settings.mode | |
| : DEFAULT_UPDATE_PROXY_SETTINGS.mode | |
| return { | |
| mode, | |
| url: typeof settings?.url === 'string' ? settings.url.trim() : '', | |
| } | |
| } | |
| function normalizeNetworkSettings( | |
| settings: NetworkSettingsInput | undefined, | |
| ): NetworkSettings { | |
| const timeout = typeof settings?.aiRequestTimeoutMs === 'number' && Number.isFinite(settings.aiRequestTimeoutMs) | |
| ? Math.min(Math.max(Math.round(settings.aiRequestTimeoutMs), 5_000), 600_000) | |
| : DEFAULT_NETWORK_SETTINGS.aiRequestTimeoutMs | |
| const proxyMode = settings?.proxy?.mode === 'manual' ? 'manual' : 'system' | |
| return { | |
| aiRequestTimeoutMs: timeout, | |
| proxy: { | |
| mode: proxyMode, | |
| url: typeof settings?.proxy?.url === 'string' ? settings.proxy.url.trim() : '', | |
| }, | |
| } | |
| } | |
| function normalizeDesktopTerminalSettings( | |
| settings: Partial<DesktopTerminalSettings> | undefined, | |
| ): DesktopTerminalSettings { | |
| const startupShell = isDesktopTerminalStartupShell(settings?.startupShell) | |
| ? settings.startupShell | |
| : DEFAULT_DESKTOP_TERMINAL_SETTINGS.startupShell | |
| return { | |
| startupShell, | |
| customShellPath: typeof settings?.customShellPath === 'string' | |
| ? settings.customShellPath | |
| : DEFAULT_DESKTOP_TERMINAL_SETTINGS.customShellPath, | |
| } | |
| } | |
| function normalizeH5AccessSettings(settings: H5AccessSettings | undefined): H5AccessSettings { | |
| return { | |
| enabled: settings?.enabled === true, | |
| tokenPreview: settings?.tokenPreview ?? null, | |
| allowedOrigins: Array.isArray(settings?.allowedOrigins) ? settings.allowedOrigins : [], | |
| publicBaseUrl: settings?.publicBaseUrl ?? null, | |
| } | |
| } | |
| async function loadH5AccessSettings(previousH5Access: H5AccessSettings): Promise<{ | |
| settings: H5AccessSettings | |
| error: string | null | |
| }> { | |
| try { | |
| const { settings } = await h5AccessApi.get() | |
| return { | |
| settings: normalizeH5AccessSettings(settings), | |
| error: null, | |
| } | |
| } catch (error) { | |
| if (isLegacyH5EndpointError(error)) { | |
| return { | |
| settings: DEFAULT_H5_ACCESS_SETTINGS, | |
| error: null, | |
| } | |
| } | |
| return { | |
| settings: previousH5Access, | |
| error: getErrorMessage(error, 'Failed to load H5 access settings.'), | |
| } | |
| } | |
| } | |
| function isLegacyH5EndpointError(error: unknown) { | |
| const status = error instanceof ApiError | |
| ? error.status | |
| : typeof error === 'object' && error !== null && 'status' in error && typeof error.status === 'number' | |
| ? error.status | |
| : null | |
| return status === 404 || status === 405 | |
| } | |
| function getErrorMessage(error: unknown, fallback: string) { | |
| return error instanceof Error && error.message.trim().length > 0 ? error.message : fallback | |
| } | |
| function isDesktopTerminalStartupShell(value: unknown): value is DesktopTerminalStartupShell { | |
| return value === 'system' | |
| || value === 'pwsh' | |
| || value === 'powershell' | |
| || value === 'cmd' | |
| || value === 'custom' | |
| } | |