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": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
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 { DynamicConfigForm } from '../DynamicConfigForm'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
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 { DynamicField } from '../DynamicField'
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
|||
import {
|
||||
BotInfoSection,
|
||||
PersonalitySection,
|
||||
ChatSection,
|
||||
DreamSection,
|
||||
LPMMSection,
|
||||
LogSection,
|
||||
|
|
|
|||
|
|
@ -79,7 +79,6 @@ function ModelConfigPageContent() {
|
|||
const [models, setModels] = useState<ModelInfo[]>([])
|
||||
const [providers, setProviders] = useState<string[]>([])
|
||||
const [providerConfigs, setProviderConfigs] = useState<ProviderConfig[]>([])
|
||||
const [modelNames, setModelNames] = useState<string[]>([])
|
||||
const [taskConfig, setTaskConfig] = useState<ModelTaskConfig | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
|
@ -183,7 +182,6 @@ function ModelConfigPageContent() {
|
|||
const config = await getModelConfig()
|
||||
const modelList = (config.models as ModelInfo[]) || []
|
||||
setModels(modelList)
|
||||
setModelNames(modelList.map((m) => m.name))
|
||||
|
||||
const providerList = (config.api_providers as ProviderConfig[]) || []
|
||||
setProviders(providerList.map((p) => p.name))
|
||||
|
|
@ -433,8 +431,6 @@ function ModelConfigPageContent() {
|
|||
}
|
||||
|
||||
setModels(newModels)
|
||||
// 立即更新模型名称列表
|
||||
setModelNames(newModels.map((m) => m.name))
|
||||
|
||||
// 如果模型名称发生变化,更新任务配置中对该模型的引用
|
||||
if (oldModelName && oldModelName !== modelToSave.name && taskConfig) {
|
||||
|
|
@ -492,8 +488,6 @@ function ModelConfigPageContent() {
|
|||
if (deletingIndex !== null) {
|
||||
const newModels = models.filter((_, i) => i !== deletingIndex)
|
||||
setModels(newModels)
|
||||
// 立即更新模型名称列表
|
||||
setModelNames(newModels.map((m) => m.name))
|
||||
// 重新检查任务配置问题
|
||||
checkTaskConfigIssues(taskConfig, newModels)
|
||||
toast({
|
||||
|
|
@ -546,8 +540,6 @@ function ModelConfigPageContent() {
|
|||
const deletedCount = selectedModels.size
|
||||
const newModels = models.filter((_, index) => !selectedModels.has(index))
|
||||
setModels(newModels)
|
||||
// 立即更新模型名称列表
|
||||
setModelNames(newModels.map((m) => m.name))
|
||||
// 重新检查任务配置问题
|
||||
checkTaskConfigIssues(taskConfig, newModels)
|
||||
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 = () => {
|
||||
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 { useTheme } from '@/components/use-theme'
|
||||
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 { cn } from '@/lib/utils'
|
||||
import { fetchWithAuth } from '@/lib/fetch-with-auth'
|
||||
|
|
@ -143,12 +143,12 @@ function hslToHex(hsl: string): string {
|
|||
|
||||
let r = 0, g = 0, b = 0
|
||||
|
||||
if (0 <= h && h < 60) { r = c; g = x; b = 0 }
|
||||
else if (60 <= h && h < 120) { r = x; g = c; b = 0 }
|
||||
else if (120 <= h && h < 180) { r = 0; g = c; b = x }
|
||||
else if (180 <= h && h < 240) { r = 0; g = x; b = c }
|
||||
else if (240 <= h && h < 300) { r = x; g = 0; b = c }
|
||||
else if (300 <= h && h < 360) { r = c; g = 0; b = x }
|
||||
if (h >= 0 && h < 60) { r = c; g = x; b = 0 }
|
||||
else if (h >= 60 && h < 120) { r = x; g = c; b = 0 }
|
||||
else if (h >= 120 && h < 180) { r = 0; g = c; b = x }
|
||||
else if (h >= 180 && h < 240) { r = 0; g = x; b = c }
|
||||
else if (h >= 240 && h < 300) { r = x; g = 0; b = c }
|
||||
else if (h >= 300 && h < 360) { r = c; g = 0; b = x }
|
||||
|
||||
const toHex = (n: number) => {
|
||||
const hex = Math.round((n + m) * 255).toString(16)
|
||||
|
|
@ -169,9 +169,30 @@ function AppearanceTab() {
|
|||
const cssDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setLocalCSS(themeConfig.customCSS || '')
|
||||
}, [themeConfig.customCSS])
|
||||
const updateTokenSection = useCallback(
|
||||
<K extends keyof ThemeTokens>(section: K, partial: Partial<ThemeTokens[K]>) => {
|
||||
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) => {
|
||||
setLocalCSS(val)
|
||||
|
|
@ -234,6 +255,8 @@ function AppearanceTab() {
|
|||
|
||||
const handleResetTheme = () => {
|
||||
resetTheme()
|
||||
setLocalCSS('')
|
||||
setCssWarnings([])
|
||||
toast({ title: '重置成功', description: '主题已重置为默认值' })
|
||||
}
|
||||
|
||||
|
|
@ -347,11 +370,7 @@ function AppearanceTab() {
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
const newOverrides = { ...themeConfig.tokenOverrides }
|
||||
delete newOverrides.typography
|
||||
updateThemeConfig({ tokenOverrides: newOverrides })
|
||||
}}
|
||||
onClick={() => resetTokenSection('typography')}
|
||||
disabled={!themeConfig.tokenOverrides?.typography}
|
||||
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 === 'sans') fontVal = 'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
|
||||
|
||||
updateThemeConfig({
|
||||
tokenOverrides: {
|
||||
...themeConfig.tokenOverrides,
|
||||
typography: {
|
||||
...themeConfig.tokenOverrides?.typography,
|
||||
'font-family-base': fontVal
|
||||
}
|
||||
}
|
||||
updateTokenSection('typography', {
|
||||
'font-family-base': fontVal,
|
||||
})
|
||||
}}
|
||||
>
|
||||
|
|
@ -409,14 +422,8 @@ function AppearanceTab() {
|
|||
max={20}
|
||||
step={1}
|
||||
onValueChange={(vals) => {
|
||||
updateThemeConfig({
|
||||
tokenOverrides: {
|
||||
...themeConfig.tokenOverrides,
|
||||
typography: {
|
||||
...themeConfig.tokenOverrides?.typography,
|
||||
'font-size-base': `${vals[0] / 16}rem`
|
||||
}
|
||||
}
|
||||
updateTokenSection('typography', {
|
||||
'font-size-base': `${vals[0] / 16}rem`,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
|
@ -427,14 +434,8 @@ function AppearanceTab() {
|
|||
<Select
|
||||
value={String((themeConfig.tokenOverrides?.typography as any)?.['line-height-normal'] || '1.5')}
|
||||
onValueChange={(val) => {
|
||||
updateThemeConfig({
|
||||
tokenOverrides: {
|
||||
...themeConfig.tokenOverrides,
|
||||
typography: {
|
||||
...themeConfig.tokenOverrides?.typography,
|
||||
'line-height-normal': parseFloat(val)
|
||||
}
|
||||
}
|
||||
updateTokenSection('typography', {
|
||||
'line-height-normal': parseFloat(val),
|
||||
})
|
||||
}}
|
||||
>
|
||||
|
|
@ -461,11 +462,7 @@ function AppearanceTab() {
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
const newOverrides = { ...themeConfig.tokenOverrides }
|
||||
delete newOverrides.visual
|
||||
updateThemeConfig({ tokenOverrides: newOverrides })
|
||||
}}
|
||||
onClick={() => resetTokenSection('visual')}
|
||||
disabled={!themeConfig.tokenOverrides?.visual}
|
||||
className="h-8 text-xs"
|
||||
>
|
||||
|
|
@ -488,14 +485,8 @@ function AppearanceTab() {
|
|||
max={24}
|
||||
step={1}
|
||||
onValueChange={(vals) => {
|
||||
updateThemeConfig({
|
||||
tokenOverrides: {
|
||||
...themeConfig.tokenOverrides,
|
||||
visual: {
|
||||
...themeConfig.tokenOverrides?.visual,
|
||||
'radius-md': `${vals[0] / 16}rem`
|
||||
}
|
||||
}
|
||||
updateTokenSection('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 === 'xl') shadowVal = defaultLightTokens.visual['shadow-xl']
|
||||
|
||||
updateThemeConfig({
|
||||
tokenOverrides: {
|
||||
...themeConfig.tokenOverrides,
|
||||
visual: {
|
||||
...themeConfig.tokenOverrides?.visual,
|
||||
'shadow-md': shadowVal
|
||||
}
|
||||
}
|
||||
updateTokenSection('visual', {
|
||||
'shadow-md': shadowVal,
|
||||
})
|
||||
}}
|
||||
>
|
||||
|
|
@ -545,14 +530,8 @@ function AppearanceTab() {
|
|||
id="blur-switch"
|
||||
checked={(themeConfig.tokenOverrides?.visual as any)?.['blur-md'] !== '0px'}
|
||||
onCheckedChange={(checked) => {
|
||||
updateThemeConfig({
|
||||
tokenOverrides: {
|
||||
...themeConfig.tokenOverrides,
|
||||
visual: {
|
||||
...themeConfig.tokenOverrides?.visual,
|
||||
'blur-md': checked ? defaultLightTokens.visual['blur-md'] : '0px'
|
||||
}
|
||||
}
|
||||
updateTokenSection('visual', {
|
||||
'blur-md': checked ? defaultLightTokens.visual['blur-md'] : '0px',
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
|
@ -570,11 +549,7 @@ function AppearanceTab() {
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
const newOverrides = { ...themeConfig.tokenOverrides }
|
||||
delete newOverrides.layout
|
||||
updateThemeConfig({ tokenOverrides: newOverrides })
|
||||
}}
|
||||
onClick={() => resetTokenSection('layout')}
|
||||
disabled={!themeConfig.tokenOverrides?.layout}
|
||||
className="h-8 text-xs"
|
||||
>
|
||||
|
|
@ -597,14 +572,8 @@ function AppearanceTab() {
|
|||
max={24}
|
||||
step={0.5}
|
||||
onValueChange={(vals) => {
|
||||
updateThemeConfig({
|
||||
tokenOverrides: {
|
||||
...themeConfig.tokenOverrides,
|
||||
layout: {
|
||||
...themeConfig.tokenOverrides?.layout,
|
||||
'sidebar-width': `${vals[0]}rem`
|
||||
}
|
||||
}
|
||||
updateTokenSection('layout', {
|
||||
'sidebar-width': `${vals[0]}rem`,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
|
@ -624,14 +593,8 @@ function AppearanceTab() {
|
|||
max={1600}
|
||||
step={10}
|
||||
onValueChange={(vals) => {
|
||||
updateThemeConfig({
|
||||
tokenOverrides: {
|
||||
...themeConfig.tokenOverrides,
|
||||
layout: {
|
||||
...themeConfig.tokenOverrides?.layout,
|
||||
'max-content-width': `${vals[0]}px`
|
||||
}
|
||||
}
|
||||
updateTokenSection('layout', {
|
||||
'max-content-width': `${vals[0]}px`,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
|
@ -651,14 +614,8 @@ function AppearanceTab() {
|
|||
max={0.4}
|
||||
step={0.01}
|
||||
onValueChange={(vals) => {
|
||||
updateThemeConfig({
|
||||
tokenOverrides: {
|
||||
...themeConfig.tokenOverrides,
|
||||
layout: {
|
||||
...themeConfig.tokenOverrides?.layout,
|
||||
'space-unit': `${vals[0]}rem`
|
||||
}
|
||||
}
|
||||
updateTokenSection('layout', {
|
||||
'space-unit': `${vals[0]}rem`,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
|
@ -676,11 +633,7 @@ function AppearanceTab() {
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
const newOverrides = { ...themeConfig.tokenOverrides }
|
||||
delete newOverrides.animation
|
||||
updateThemeConfig({ tokenOverrides: newOverrides })
|
||||
}}
|
||||
onClick={() => resetTokenSection('animation')}
|
||||
disabled={!themeConfig.tokenOverrides?.animation}
|
||||
className="h-8 text-xs"
|
||||
>
|
||||
|
|
@ -708,14 +661,8 @@ function AppearanceTab() {
|
|||
setEnableAnimations(true)
|
||||
}
|
||||
|
||||
updateThemeConfig({
|
||||
tokenOverrides: {
|
||||
...themeConfig.tokenOverrides,
|
||||
animation: {
|
||||
...themeConfig.tokenOverrides?.animation,
|
||||
'anim-duration-normal': duration
|
||||
}
|
||||
}
|
||||
updateTokenSection('animation', {
|
||||
'anim-duration-normal': duration,
|
||||
})
|
||||
}}
|
||||
>
|
||||
|
|
@ -748,6 +695,7 @@ function AppearanceTab() {
|
|||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setLocalCSS('')
|
||||
updateThemeConfig({ customCSS: '' })
|
||||
setCssWarnings([])
|
||||
}}
|
||||
|
|
@ -2190,36 +2138,3 @@ function ThemeOption({ value, current, onChange, label, description }: ThemeOpti
|
|||
</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'
|
||||
|
||||
global.ResizeObserver = class ResizeObserver {
|
||||
globalThis.ResizeObserver = class ResizeObserver {
|
||||
observe() {}
|
||||
unobserve() {}
|
||||
disconnect() {}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
declare module '*.css'
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"extends": "./tsconfig.app.json",
|
||||
"compilerOptions": {
|
||||
"types": ["vitest/globals", "@testing-library/jest-dom"]
|
||||
"types": ["vite/client", "vitest/globals", "@testing-library/jest-dom"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/// <reference types="vitest" />
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import path from 'path'
|
||||
|
|
@ -6,11 +5,6 @@ import path from 'path'
|
|||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
setupFiles: './src/test/setup.ts',
|
||||
},
|
||||
server: {
|
||||
port: 7999,
|
||||
proxy: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue