修复 build 问题

pull/1496/head
DrSmoothl 2026-02-19 18:56:49 +08:00
parent 79871100be
commit 6a54a796f1
11 changed files with 9857 additions and 209 deletions

9791
dashboard/package-lock.json generated 100644

File diff suppressed because it is too large Load Diff

View File

@ -78,6 +78,7 @@
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.39.1", "@eslint/js": "^9.39.1",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2", "@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1", "@testing-library/user-event": "^14.6.1",

View File

@ -1,5 +1,6 @@
import { describe, it, expect, vi } from 'vitest' import { describe, it, expect, vi } from 'vitest'
import { render, screen } from '@testing-library/react' import { screen } from '@testing-library/dom'
import { render } from '@testing-library/react'
import userEvent from '@testing-library/user-event' import userEvent from '@testing-library/user-event'
import { DynamicConfigForm } from '../DynamicConfigForm' import { DynamicConfigForm } from '../DynamicConfigForm'

View File

@ -1,5 +1,6 @@
import { describe, it, expect, vi } from 'vitest' import { describe, it, expect, vi } from 'vitest'
import { render, screen } from '@testing-library/react' import { screen } from '@testing-library/dom'
import { render } from '@testing-library/react'
import userEvent from '@testing-library/user-event' import userEvent from '@testing-library/user-event'
import { DynamicField } from '../DynamicField' import { DynamicField } from '../DynamicField'

View File

@ -2,7 +2,6 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { import {
BotInfoSection, BotInfoSection,
PersonalitySection, PersonalitySection,
ChatSection,
DreamSection, DreamSection,
LPMMSection, LPMMSection,
LogSection, LogSection,

View File

@ -79,7 +79,6 @@ function ModelConfigPageContent() {
const [models, setModels] = useState<ModelInfo[]>([]) const [models, setModels] = useState<ModelInfo[]>([])
const [providers, setProviders] = useState<string[]>([]) const [providers, setProviders] = useState<string[]>([])
const [providerConfigs, setProviderConfigs] = useState<ProviderConfig[]>([]) const [providerConfigs, setProviderConfigs] = useState<ProviderConfig[]>([])
const [modelNames, setModelNames] = useState<string[]>([])
const [taskConfig, setTaskConfig] = useState<ModelTaskConfig | null>(null) const [taskConfig, setTaskConfig] = useState<ModelTaskConfig | null>(null)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false) const [saving, setSaving] = useState(false)
@ -183,7 +182,6 @@ function ModelConfigPageContent() {
const config = await getModelConfig() const config = await getModelConfig()
const modelList = (config.models as ModelInfo[]) || [] const modelList = (config.models as ModelInfo[]) || []
setModels(modelList) setModels(modelList)
setModelNames(modelList.map((m) => m.name))
const providerList = (config.api_providers as ProviderConfig[]) || [] const providerList = (config.api_providers as ProviderConfig[]) || []
setProviders(providerList.map((p) => p.name)) setProviders(providerList.map((p) => p.name))
@ -433,8 +431,6 @@ function ModelConfigPageContent() {
} }
setModels(newModels) setModels(newModels)
// 立即更新模型名称列表
setModelNames(newModels.map((m) => m.name))
// 如果模型名称发生变化,更新任务配置中对该模型的引用 // 如果模型名称发生变化,更新任务配置中对该模型的引用
if (oldModelName && oldModelName !== modelToSave.name && taskConfig) { if (oldModelName && oldModelName !== modelToSave.name && taskConfig) {
@ -492,8 +488,6 @@ function ModelConfigPageContent() {
if (deletingIndex !== null) { if (deletingIndex !== null) {
const newModels = models.filter((_, i) => i !== deletingIndex) const newModels = models.filter((_, i) => i !== deletingIndex)
setModels(newModels) setModels(newModels)
// 立即更新模型名称列表
setModelNames(newModels.map((m) => m.name))
// 重新检查任务配置问题 // 重新检查任务配置问题
checkTaskConfigIssues(taskConfig, newModels) checkTaskConfigIssues(taskConfig, newModels)
toast({ toast({
@ -546,8 +540,6 @@ function ModelConfigPageContent() {
const deletedCount = selectedModels.size const deletedCount = selectedModels.size
const newModels = models.filter((_, index) => !selectedModels.has(index)) const newModels = models.filter((_, index) => !selectedModels.has(index))
setModels(newModels) setModels(newModels)
// 立即更新模型名称列表
setModelNames(newModels.map((m) => m.name))
// 重新检查任务配置问题 // 重新检查任务配置问题
checkTaskConfigIssues(taskConfig, newModels) checkTaskConfigIssues(taskConfig, newModels)
setSelectedModels(new Set()) setSelectedModels(new Set())
@ -558,53 +550,6 @@ function ModelConfigPageContent() {
}) })
} }
// 更新任务配置
const updateTaskConfig = (
taskName: keyof ModelTaskConfig,
field: keyof TaskConfig,
value: string[] | number | string
) => {
if (!taskConfig) return
// 检测 embedding 模型列表变化
if (taskName === 'embedding' && field === 'model_list' && Array.isArray(value)) {
const previousModels = previousEmbeddingModelsRef.current
const newModels = value as string[]
// 判断是否有变化(添加、删除或替换)
const hasChanges =
previousModels.length !== newModels.length ||
previousModels.some(model => !newModels.includes(model)) ||
newModels.some(model => !previousModels.includes(model))
if (hasChanges && previousModels.length > 0) {
// 存储待更新的配置
pendingEmbeddingUpdateRef.current = { field, value }
// 显示警告对话框
setEmbeddingWarningOpen(true)
return
}
}
// 正常更新配置
const newTaskConfig = {
...taskConfig,
[taskName]: {
...taskConfig[taskName],
[field]: value,
},
}
setTaskConfig(newTaskConfig)
// 重新检查任务配置问题
checkTaskConfigIssues(newTaskConfig, models)
// 如果是 embedding 模型列表,更新 ref
if (taskName === 'embedding' && field === 'model_list' && Array.isArray(value)) {
previousEmbeddingModelsRef.current = [...(value as string[])]
}
}
// 确认更新嵌入模型 // 确认更新嵌入模型
const handleConfirmEmbeddingChange = () => { const handleConfirmEmbeddingChange = () => {
if (!taskConfig || !pendingEmbeddingUpdateRef.current) return if (!taskConfig || !pendingEmbeddingUpdateRef.current) return

View File

@ -1,7 +1,7 @@
import { Palette, Info, Shield, Eye, EyeOff, Copy, RefreshCw, Check, CheckCircle2, XCircle, AlertTriangle, Settings, RotateCcw, Database, Download, Upload, Trash2, HardDrive } from 'lucide-react' import { Palette, Info, Shield, Eye, EyeOff, Copy, RefreshCw, Check, CheckCircle2, XCircle, AlertTriangle, Settings, RotateCcw, Database, Download, Upload, Trash2, HardDrive } from 'lucide-react'
import { useTheme } from '@/components/use-theme' import { useTheme } from '@/components/use-theme'
import { useAnimation } from '@/hooks/use-animation' import { useAnimation } from '@/hooks/use-animation'
import { useState, useMemo, useRef, useCallback, useEffect } from 'react' import { useState, useMemo, useRef, useCallback } from 'react'
import { useNavigate } from '@tanstack/react-router' import { useNavigate } from '@tanstack/react-router'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { fetchWithAuth } from '@/lib/fetch-with-auth' import { fetchWithAuth } from '@/lib/fetch-with-auth'
@ -143,12 +143,12 @@ function hslToHex(hsl: string): string {
let r = 0, g = 0, b = 0 let r = 0, g = 0, b = 0
if (0 <= h && h < 60) { r = c; g = x; b = 0 } if (h >= 0 && h < 60) { r = c; g = x; b = 0 }
else if (60 <= h && h < 120) { r = x; g = c; b = 0 } else if (h >= 60 && h < 120) { r = x; g = c; b = 0 }
else if (120 <= h && h < 180) { r = 0; g = c; b = x } else if (h >= 120 && h < 180) { r = 0; g = c; b = x }
else if (180 <= h && h < 240) { r = 0; g = x; b = c } else if (h >= 180 && h < 240) { r = 0; g = x; b = c }
else if (240 <= h && h < 300) { r = x; g = 0; b = c } else if (h >= 240 && h < 300) { r = x; g = 0; b = c }
else if (300 <= h && h < 360) { r = c; g = 0; b = x } else if (h >= 300 && h < 360) { r = c; g = 0; b = x }
const toHex = (n: number) => { const toHex = (n: number) => {
const hex = Math.round((n + m) * 255).toString(16) const hex = Math.round((n + m) * 255).toString(16)
@ -169,9 +169,30 @@ function AppearanceTab() {
const cssDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null) const cssDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const fileInputRef = useRef<HTMLInputElement>(null) const fileInputRef = useRef<HTMLInputElement>(null)
useEffect(() => { const updateTokenSection = useCallback(
setLocalCSS(themeConfig.customCSS || '') <K extends keyof ThemeTokens>(section: K, partial: Partial<ThemeTokens[K]>) => {
}, [themeConfig.customCSS]) updateThemeConfig({
tokenOverrides: {
...themeConfig.tokenOverrides,
[section]: {
...defaultLightTokens[section],
...themeConfig.tokenOverrides?.[section],
...partial,
} as ThemeTokens[K],
},
})
},
[themeConfig.tokenOverrides, updateThemeConfig]
)
const resetTokenSection = useCallback(
(section: keyof ThemeTokens) => {
const newOverrides: Partial<ThemeTokens> = { ...themeConfig.tokenOverrides }
delete newOverrides[section]
updateThemeConfig({ tokenOverrides: newOverrides })
},
[themeConfig.tokenOverrides, updateThemeConfig]
)
const handleCSSChange = useCallback((val: string) => { const handleCSSChange = useCallback((val: string) => {
setLocalCSS(val) setLocalCSS(val)
@ -234,6 +255,8 @@ function AppearanceTab() {
const handleResetTheme = () => { const handleResetTheme = () => {
resetTheme() resetTheme()
setLocalCSS('')
setCssWarnings([])
toast({ title: '重置成功', description: '主题已重置为默认值' }) toast({ title: '重置成功', description: '主题已重置为默认值' })
} }
@ -347,11 +370,7 @@ function AppearanceTab() {
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => { onClick={() => resetTokenSection('typography')}
const newOverrides = { ...themeConfig.tokenOverrides }
delete newOverrides.typography
updateThemeConfig({ tokenOverrides: newOverrides })
}}
disabled={!themeConfig.tokenOverrides?.typography} disabled={!themeConfig.tokenOverrides?.typography}
className="h-8 text-xs" className="h-8 text-xs"
> >
@ -372,14 +391,8 @@ function AppearanceTab() {
else if (val === 'mono') fontVal = 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace' else if (val === 'mono') fontVal = 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
else if (val === 'sans') fontVal = 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif' else if (val === 'sans') fontVal = 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
updateThemeConfig({ updateTokenSection('typography', {
tokenOverrides: { 'font-family-base': fontVal,
...themeConfig.tokenOverrides,
typography: {
...themeConfig.tokenOverrides?.typography,
'font-family-base': fontVal
}
}
}) })
}} }}
> >
@ -409,14 +422,8 @@ function AppearanceTab() {
max={20} max={20}
step={1} step={1}
onValueChange={(vals) => { onValueChange={(vals) => {
updateThemeConfig({ updateTokenSection('typography', {
tokenOverrides: { 'font-size-base': `${vals[0] / 16}rem`,
...themeConfig.tokenOverrides,
typography: {
...themeConfig.tokenOverrides?.typography,
'font-size-base': `${vals[0] / 16}rem`
}
}
}) })
}} }}
/> />
@ -427,14 +434,8 @@ function AppearanceTab() {
<Select <Select
value={String((themeConfig.tokenOverrides?.typography as any)?.['line-height-normal'] || '1.5')} value={String((themeConfig.tokenOverrides?.typography as any)?.['line-height-normal'] || '1.5')}
onValueChange={(val) => { onValueChange={(val) => {
updateThemeConfig({ updateTokenSection('typography', {
tokenOverrides: { 'line-height-normal': parseFloat(val),
...themeConfig.tokenOverrides,
typography: {
...themeConfig.tokenOverrides?.typography,
'line-height-normal': parseFloat(val)
}
}
}) })
}} }}
> >
@ -461,11 +462,7 @@ function AppearanceTab() {
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => { onClick={() => resetTokenSection('visual')}
const newOverrides = { ...themeConfig.tokenOverrides }
delete newOverrides.visual
updateThemeConfig({ tokenOverrides: newOverrides })
}}
disabled={!themeConfig.tokenOverrides?.visual} disabled={!themeConfig.tokenOverrides?.visual}
className="h-8 text-xs" className="h-8 text-xs"
> >
@ -488,14 +485,8 @@ function AppearanceTab() {
max={24} max={24}
step={1} step={1}
onValueChange={(vals) => { onValueChange={(vals) => {
updateThemeConfig({ updateTokenSection('visual', {
tokenOverrides: { 'radius-md': `${vals[0] / 16}rem`,
...themeConfig.tokenOverrides,
visual: {
...themeConfig.tokenOverrides?.visual,
'radius-md': `${vals[0] / 16}rem`
}
}
}) })
}} }}
/> />
@ -515,14 +506,8 @@ function AppearanceTab() {
else if (val === 'lg') shadowVal = defaultLightTokens.visual['shadow-lg'] else if (val === 'lg') shadowVal = defaultLightTokens.visual['shadow-lg']
else if (val === 'xl') shadowVal = defaultLightTokens.visual['shadow-xl'] else if (val === 'xl') shadowVal = defaultLightTokens.visual['shadow-xl']
updateThemeConfig({ updateTokenSection('visual', {
tokenOverrides: { 'shadow-md': shadowVal,
...themeConfig.tokenOverrides,
visual: {
...themeConfig.tokenOverrides?.visual,
'shadow-md': shadowVal
}
}
}) })
}} }}
> >
@ -545,14 +530,8 @@ function AppearanceTab() {
id="blur-switch" id="blur-switch"
checked={(themeConfig.tokenOverrides?.visual as any)?.['blur-md'] !== '0px'} checked={(themeConfig.tokenOverrides?.visual as any)?.['blur-md'] !== '0px'}
onCheckedChange={(checked) => { onCheckedChange={(checked) => {
updateThemeConfig({ updateTokenSection('visual', {
tokenOverrides: { 'blur-md': checked ? defaultLightTokens.visual['blur-md'] : '0px',
...themeConfig.tokenOverrides,
visual: {
...themeConfig.tokenOverrides?.visual,
'blur-md': checked ? defaultLightTokens.visual['blur-md'] : '0px'
}
}
}) })
}} }}
/> />
@ -570,11 +549,7 @@ function AppearanceTab() {
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => { onClick={() => resetTokenSection('layout')}
const newOverrides = { ...themeConfig.tokenOverrides }
delete newOverrides.layout
updateThemeConfig({ tokenOverrides: newOverrides })
}}
disabled={!themeConfig.tokenOverrides?.layout} disabled={!themeConfig.tokenOverrides?.layout}
className="h-8 text-xs" className="h-8 text-xs"
> >
@ -597,14 +572,8 @@ function AppearanceTab() {
max={24} max={24}
step={0.5} step={0.5}
onValueChange={(vals) => { onValueChange={(vals) => {
updateThemeConfig({ updateTokenSection('layout', {
tokenOverrides: { 'sidebar-width': `${vals[0]}rem`,
...themeConfig.tokenOverrides,
layout: {
...themeConfig.tokenOverrides?.layout,
'sidebar-width': `${vals[0]}rem`
}
}
}) })
}} }}
/> />
@ -624,14 +593,8 @@ function AppearanceTab() {
max={1600} max={1600}
step={10} step={10}
onValueChange={(vals) => { onValueChange={(vals) => {
updateThemeConfig({ updateTokenSection('layout', {
tokenOverrides: { 'max-content-width': `${vals[0]}px`,
...themeConfig.tokenOverrides,
layout: {
...themeConfig.tokenOverrides?.layout,
'max-content-width': `${vals[0]}px`
}
}
}) })
}} }}
/> />
@ -651,14 +614,8 @@ function AppearanceTab() {
max={0.4} max={0.4}
step={0.01} step={0.01}
onValueChange={(vals) => { onValueChange={(vals) => {
updateThemeConfig({ updateTokenSection('layout', {
tokenOverrides: { 'space-unit': `${vals[0]}rem`,
...themeConfig.tokenOverrides,
layout: {
...themeConfig.tokenOverrides?.layout,
'space-unit': `${vals[0]}rem`
}
}
}) })
}} }}
/> />
@ -676,11 +633,7 @@ function AppearanceTab() {
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
onClick={() => { onClick={() => resetTokenSection('animation')}
const newOverrides = { ...themeConfig.tokenOverrides }
delete newOverrides.animation
updateThemeConfig({ tokenOverrides: newOverrides })
}}
disabled={!themeConfig.tokenOverrides?.animation} disabled={!themeConfig.tokenOverrides?.animation}
className="h-8 text-xs" className="h-8 text-xs"
> >
@ -708,14 +661,8 @@ function AppearanceTab() {
setEnableAnimations(true) setEnableAnimations(true)
} }
updateThemeConfig({ updateTokenSection('animation', {
tokenOverrides: { 'anim-duration-normal': duration,
...themeConfig.tokenOverrides,
animation: {
...themeConfig.tokenOverrides?.animation,
'anim-duration-normal': duration
}
}
}) })
}} }}
> >
@ -748,6 +695,7 @@ function AppearanceTab() {
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => { onClick={() => {
setLocalCSS('')
updateThemeConfig({ customCSS: '' }) updateThemeConfig({ customCSS: '' })
setCssWarnings([]) setCssWarnings([])
}} }}
@ -2190,36 +2138,3 @@ function ThemeOption({ value, current, onChange, label, description }: ThemeOpti
</button> </button>
) )
} }
type ColorPresetOptionProps = {
value: string
current: string
onChange: (color: string) => void
label: string
colorClass: string
}
function ColorPresetOption({ value, current, onChange, label, colorClass }: ColorPresetOptionProps) {
const isSelected = current === value
return (
<button
onClick={() => onChange(value)}
className={cn(
'relative rounded-lg border-2 p-2 sm:p-3 text-left transition-all',
'hover:border-primary/50 hover:bg-accent/50',
isSelected ? 'border-primary bg-accent' : 'border-border'
)}
>
{/* 选中指示器 */}
{isSelected && (
<div className="absolute top-1.5 right-1.5 sm:top-2 sm:right-2 h-1.5 w-1.5 sm:h-2 sm:w-2 rounded-full bg-primary" />
)}
<div className="flex flex-col items-center gap-1.5 sm:gap-2">
<div className={cn('h-8 w-8 sm:h-10 sm:w-10 rounded-full', colorClass)} />
<div className="text-[10px] sm:text-xs font-medium text-center">{label}</div>
</div>
</button>
)
}

View File

@ -1,6 +1,6 @@
import '@testing-library/jest-dom/vitest' import '@testing-library/jest-dom/vitest'
global.ResizeObserver = class ResizeObserver { globalThis.ResizeObserver = class ResizeObserver {
observe() {} observe() {}
unobserve() {} unobserve() {}
disconnect() {} disconnect() {}

View File

@ -0,0 +1 @@
declare module '*.css'

View File

@ -1,7 +1,7 @@
{ {
"extends": "./tsconfig.app.json", "extends": "./tsconfig.app.json",
"compilerOptions": { "compilerOptions": {
"types": ["vitest/globals", "@testing-library/jest-dom"] "types": ["vite/client", "vitest/globals", "@testing-library/jest-dom"]
}, },
"include": ["src"] "include": ["src"]
} }

View File

@ -1,4 +1,3 @@
/// <reference types="vitest" />
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
import path from 'path' import path from 'path'
@ -6,11 +5,6 @@ import path from 'path'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/test/setup.ts',
},
server: { server: {
port: 7999, port: 7999,
proxy: { proxy: {