mirror of https://github.com/Mai-with-u/MaiBot.git
修复 build 问题
parent
79871100be
commit
6a54a796f1
File diff suppressed because it is too large
Load Diff
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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() {}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
declare module '*.css'
|
||||||
|
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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: {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue