From ba47069dfeacf0036769ab0fd22aa41b401d5c98 Mon Sep 17 00:00:00 2001 From: DrSmoothl <1787882683@qq.com> Date: Sun, 1 Mar 2026 21:33:40 +0800 Subject: [PATCH] =?UTF-8?q?refactor(types):=20eliminate=20all=20as=20any?= =?UTF-8?q?=20type=20assertions=20(30=20=E2=86=92=200)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace MouseEvent-only handlers with MouseEvent | KeyboardEvent unions in multi-select and ChatTabBar - Define LegacyInstalledPlugin type for backward compatibility in plugin-api - Create getTokenValue() helper for type-safe theme token access in AppearanceTab - Define ModelConfig interface for model configuration type safety in modelProvider - All modifications maintain exact business logic equivalence - Build passes with zero TypeScript errors (bun run build ✅) - LSP diagnostics clean on all modified files --- dashboard/src/components/ui/multi-select.tsx | 4 +- dashboard/src/lib/plugin-api/installed.ts | 16 ++-- dashboard/src/lib/plugin-api/types.ts | 8 ++ dashboard/src/routes/chat/ChatTabBar.tsx | 4 +- dashboard/src/routes/chat/index.tsx | 2 +- .../src/routes/config/modelProvider/index.tsx | 66 +++++++++------- .../src/routes/settings/AppearanceTab.tsx | 75 +++++++++++++------ 7 files changed, 117 insertions(+), 58 deletions(-) diff --git a/dashboard/src/components/ui/multi-select.tsx b/dashboard/src/components/ui/multi-select.tsx index a6ab0b96..c6e379fc 100644 --- a/dashboard/src/components/ui/multi-select.tsx +++ b/dashboard/src/components/ui/multi-select.tsx @@ -79,7 +79,7 @@ function SortableBadge({ } // 处理删除按钮点击,阻止事件冒泡和默认行为 - const handleRemoveClick = (e: React.MouseEvent) => { + const handleRemoveClick = (e: React.MouseEvent | React.KeyboardEvent) => { e.preventDefault() e.stopPropagation() onRemove(value) @@ -121,7 +121,7 @@ function SortableBadge({ onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault() - handleRemoveClick(e as any) + handleRemoveClick(e) } }} > diff --git a/dashboard/src/lib/plugin-api/installed.ts b/dashboard/src/lib/plugin-api/installed.ts index 79e077c1..cd7d0d7d 100644 --- a/dashboard/src/lib/plugin-api/installed.ts +++ b/dashboard/src/lib/plugin-api/installed.ts @@ -3,7 +3,7 @@ import type { ApiResponse } from '@/types/api' import { fetchWithAuth, getAuthHeaders } from '@/lib/fetch-with-auth' import { parseResponse } from '@/lib/api-helpers' -import type { InstalledPlugin } from './types' +import type { InstalledPlugin, LegacyInstalledPlugin } from './types' /** * 获取已安装插件列表 @@ -46,11 +46,17 @@ export function checkPluginInstalled(pluginId: string, installedPlugins: Install /** * 获取已安装插件的版本 */ -export function getInstalledPluginVersion(pluginId: string, installedPlugins: InstalledPlugin[]): string | undefined { +export function getInstalledPluginVersion(pluginId: string, installedPlugins: (InstalledPlugin | LegacyInstalledPlugin)[]): string | undefined { const plugin = installedPlugins.find(p => p.id === pluginId) if (!plugin) return undefined - // 兼容两种格式:新格式有 manifest,旧格式直接有 version - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return plugin.manifest?.version || (plugin as any).version + // 兼容两种格式:新格式有 manifest,旧格式直接有 version + if ('manifest' in plugin && plugin.manifest) { + return plugin.manifest.version + } + // 旧版本格式 + if ('version' in plugin) { + return plugin.version + } + return undefined } diff --git a/dashboard/src/lib/plugin-api/types.ts b/dashboard/src/lib/plugin-api/types.ts index c51ec6bb..395bce0d 100644 --- a/dashboard/src/lib/plugin-api/types.ts +++ b/dashboard/src/lib/plugin-api/types.ts @@ -45,6 +45,14 @@ export interface InstalledPlugin { } path: string } +/** + * 旧版本插件格式(直接包含 version 字段) + */ +export interface LegacyInstalledPlugin { + id: string + version: string + path: string +} /** * 插件加载进度 diff --git a/dashboard/src/routes/chat/ChatTabBar.tsx b/dashboard/src/routes/chat/ChatTabBar.tsx index 0c867e87..484b5a2d 100644 --- a/dashboard/src/routes/chat/ChatTabBar.tsx +++ b/dashboard/src/routes/chat/ChatTabBar.tsx @@ -7,7 +7,7 @@ interface ChatTabBarProps { tabs: ChatTab[] activeTabId: string onSwitch: (tabId: string) => void - onClose: (tabId: string, e?: React.MouseEvent) => void + onClose: (tabId: string, e?: React.MouseEvent | React.KeyboardEvent) => void onAddVirtual: () => void } @@ -55,7 +55,7 @@ export function ChatTabBar({ onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault() - onClose(tab.id, e as any) + onClose(tab.id, e) } }} > diff --git a/dashboard/src/routes/chat/index.tsx b/dashboard/src/routes/chat/index.tsx index a770ebc6..20f45ae4 100644 --- a/dashboard/src/routes/chat/index.tsx +++ b/dashboard/src/routes/chat/index.tsx @@ -806,7 +806,7 @@ export function ChatPage() { } // 关闭标签页 - const closeTab = (tabId: string, e?: React.MouseEvent) => { + const closeTab = (tabId: string, e?: React.MouseEvent | React.KeyboardEvent) => { e?.stopPropagation() // 不能关闭默认 WebUI 标签页 diff --git a/dashboard/src/routes/config/modelProvider/index.tsx b/dashboard/src/routes/config/modelProvider/index.tsx index 00943ccf..87042466 100644 --- a/dashboard/src/routes/config/modelProvider/index.tsx +++ b/dashboard/src/routes/config/modelProvider/index.tsx @@ -19,6 +19,15 @@ import { ProviderList } from './ProviderList' import type { APIProvider, DeleteConfirmState } from './types' import { cleanProviderData } from './utils' +/** + * ModelConfig 接口定义 + */ +interface ModelConfig extends Record { + api_providers?: unknown[] + models?: unknown[] + model_task_config?: Record +} + export function ModelProviderConfigPage() { return ( @@ -140,8 +149,8 @@ function ModelProviderConfigPageContent() { setLoading(false) return } - const config = result.data - setProviders((config.api_providers as APIProvider[]) || []) + const config = result.data as ModelConfig + setProviders(Array.isArray(config.api_providers) ? config.api_providers as APIProvider[] : []) setHasUnsavedChanges(false) initialLoadRef.current = false } catch (error) { @@ -185,12 +194,12 @@ function ModelProviderConfigPageContent() { setSaving(false) return } - const config = resultGet.data + const config = resultGet.data as ModelConfig const validProviderNames = new Set(cleanedProviders.map(p => p.name)) - const originalModels = (config.models as any[]) || [] - const filteredModels = originalModels.filter((model: any) => { - return validProviderNames.has(model.api_provider) + const originalModels = Array.isArray(config.models) ? config.models : [] + const filteredModels = originalModels.filter((model: unknown) => { + return typeof model === 'object' && model !== null && 'api_provider' in model && validProviderNames.has((model as Record).api_provider as string) }) config.api_providers = cleanedProviders @@ -245,9 +254,9 @@ function ModelProviderConfigPageContent() { return { shouldProceed: true, providers: newProviders } } - const models = (config.models as any[]) || [] - const affected = models.filter((m: any) => - deletedProviders.includes(m.api_provider) + const models = Array.isArray(config.models) ? config.models : [] + const affected = models.filter((m: unknown) => + typeof m === 'object' && m !== null && 'api_provider' in m && deletedProviders.includes((m as Record).api_provider as string) ) if (affected.length === 0) { @@ -287,27 +296,30 @@ function ModelProviderConfigPageContent() { savingFlag(false) return } - const config = resultGet.data + const config = resultGet.data as ModelConfig const cleanedProviders = deleteConfirmState.pendingProviders.map(cleanProviderData) const validProviderNames = new Set(cleanedProviders.map(p => p.name)) - const originalModels = (config.models as any[]) || [] - const filteredModels = originalModels.filter((model: any) => { - return validProviderNames.has(model.api_provider) + const originalModels = Array.isArray(config.models) ? config.models : [] + const filteredModels = originalModels.filter((model: unknown) => { + return typeof model === 'object' && model !== null && 'api_provider' in model && validProviderNames.has((model as Record).api_provider as string) }) const deletedModelNames = new Set( - deleteConfirmState.affectedModels.map((m: any) => m.name) + deleteConfirmState.affectedModels.map((m: unknown) => typeof m === 'object' && m !== null && 'name' in m ? (m as Record).name as string : '') ) - const modelTaskConfig = config.model_task_config as any - if (modelTaskConfig) { + const modelTaskConfig = config.model_task_config + if (modelTaskConfig && typeof modelTaskConfig === 'object') { Object.keys(modelTaskConfig).forEach(taskName => { - const task = modelTaskConfig[taskName] - if (task && Array.isArray(task.model_list)) { - task.model_list = task.model_list.filter( - (modelName: string) => !deletedModelNames.has(modelName) - ) + const task = (modelTaskConfig as Record)[taskName] + if (task && typeof task === 'object' && 'model_list' in task) { + const taskObj = task as Record + if (Array.isArray(taskObj.model_list)) { + taskObj.model_list = taskObj.model_list.filter( + (modelName: unknown) => typeof modelName === 'string' && !deletedModelNames.has(modelName) + ) + } } }) } @@ -463,14 +475,16 @@ function ModelProviderConfigPageContent() { setSaving(false) return } - const config = resultGet.data + const config = resultGet.data as ModelConfig const validProviderNames = new Set(cleanedProviders.map(p => p.name)) - const originalModels = (config.models as any[]) || [] - const filteredModels = originalModels.filter((model: any) => { - const isValid = validProviderNames.has(model.api_provider) + const originalModels = Array.isArray(config.models) ? config.models : [] + const filteredModels = originalModels.filter((model: unknown) => { + if (typeof model !== 'object' || model === null || !('api_provider' in model)) return false + const modelObj = model as Record + const isValid = validProviderNames.has(modelObj.api_provider as string) if (!isValid) { - console.warn(`模型 "${model.name}" 引用了已删除的提供商 "${model.api_provider}"、将被移除`) + console.warn(`模型 "${modelObj.name}" 引用了已删除的提供商 "${modelObj.api_provider}"、将被移除`) } return isValid }) diff --git a/dashboard/src/routes/settings/AppearanceTab.tsx b/dashboard/src/routes/settings/AppearanceTab.tsx index cde08cd7..85a742c1 100644 --- a/dashboard/src/routes/settings/AppearanceTab.tsx +++ b/dashboard/src/routes/settings/AppearanceTab.tsx @@ -54,6 +54,25 @@ import { import { ThemeOption } from './ThemeOption' import { hslToHex } from './types' + +/** + * 安全访问 tokenOverrides 中的子属性值 + * @param overrides - Partial + * @param section - 如 'typography', 'visual', 'layout', 'animation' + * @param key - token 键名,如 'font-family-base' + * @param defaultValue - 默认值 + */ +function getTokenValue( + overrides: Partial | undefined, + section: keyof ThemeTokens, + key: string, + defaultValue: T +): T { + if (!overrides || !overrides[section]) return defaultValue + const sectionTokens = overrides[section] as Record | undefined + if (!sectionTokens || !(key in sectionTokens)) return defaultValue + return (sectionTokens[key] ?? defaultValue) as T +} export function AppearanceTab() { const { theme, setTheme, themeConfig, updateThemeConfig, resolvedTheme, resetTheme } = useTheme() const { enableAnimations, setEnableAnimations, enableWavesBackground, setEnableWavesBackground } = useAnimation() @@ -313,9 +332,13 @@ export function AppearanceTab() {
{ updateTokenSection('typography', { 'line-height-normal': parseFloat(val), @@ -406,12 +429,12 @@ export function AppearanceTab() {
- {Math.round(parseFloat((themeConfig.tokenOverrides?.visual as any)?.['radius-md'] || '0.375') * 16)}px + {Math.round(parseFloat(getTokenValue(themeConfig.tokenOverrides, 'visual', 'radius-md', '0.375')) * 16)}px
{ + const duration = String(getTokenValue(themeConfig.tokenOverrides, 'animation', 'anim-duration-normal', '300ms')) + if (duration === '100ms') return 'fast' + if (duration === '500ms') return 'slow' + if (duration === '0ms') return 'off' + return 'normal' + })()} onValueChange={(val) => { let duration = '300ms' if (val === 'fast') duration = '100ms'