perf: memo GridCard, fuse save fns, fix slash tab active color
This commit is contained in:
@@ -4,7 +4,7 @@ import { useState } from 'react'
|
||||
import { updateAISettings } from '@/app/actions/ai-settings'
|
||||
import { DemoModeToggle } from '@/components/demo-mode-toggle'
|
||||
import { toast } from 'sonner'
|
||||
import { Loader2, Sparkles, Brain, Languages, Tag, History, Wand2 } from 'lucide-react'
|
||||
import { Loader2, Sparkles, Brain, Languages, Tag, History, Wand2, ImageIcon } from 'lucide-react'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
import { motion } from 'motion/react'
|
||||
import { cn } from '@/lib/utils'
|
||||
@@ -23,6 +23,7 @@ interface AISettingsPanelProps {
|
||||
autoLabeling: boolean
|
||||
noteHistory: boolean
|
||||
noteHistoryMode: 'manual' | 'auto'
|
||||
svgComplexity?: 'simple' | 'illustrated' | 'rich'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +74,20 @@ export function AISettingsPanel({ initialSettings }: AISettingsPanelProps) {
|
||||
}
|
||||
}
|
||||
|
||||
const handleSvgComplexityChange = async (value: 'simple' | 'illustrated' | 'rich') => {
|
||||
setSettings(prev => ({ ...prev, svgComplexity: value }))
|
||||
try {
|
||||
setIsPending(true)
|
||||
await updateAISettings({ svgComplexity: value })
|
||||
toast.success(t('aiSettings.saved'))
|
||||
} catch {
|
||||
toast.error(t('aiSettings.error'))
|
||||
setSettings(initialSettings)
|
||||
} finally {
|
||||
setIsPending(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDemoModeToggle = async (enabled: boolean) => {
|
||||
setSettings(prev => ({ ...prev, demoMode: enabled }))
|
||||
try {
|
||||
@@ -255,6 +270,117 @@ export function AISettingsPanel({ initialSettings }: AISettingsPanelProps) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* SVG Complexity selector */}
|
||||
<div className="bg-white/40 dark:bg-white/5 border border-border rounded-2xl p-8 space-y-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 bg-paper dark:bg-brand-accent/10 rounded-2xl border border-brand-accent/20 text-brand-accent">
|
||||
<ImageIcon size={18} />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<h4 className="text-[13px] font-bold text-ink">{t('aiSettings.svgComplexity')}</h4>
|
||||
<p className="text-[10px] text-concrete leading-relaxed">{t('aiSettings.svgComplexityDesc')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
{([
|
||||
{
|
||||
value: 'simple' as const,
|
||||
label: t('aiSettings.svgComplexitySimple'),
|
||||
desc: t('aiSettings.svgComplexitySimpleDesc'),
|
||||
preview: (
|
||||
<svg viewBox="0 0 80 60" className="w-full h-full">
|
||||
<rect width="80" height="60" fill="#F5F0E8" rx="4"/>
|
||||
<circle cx="40" cy="28" r="14" fill="none" stroke="#C4A882" strokeWidth="2.5"/>
|
||||
<line x1="40" y1="14" x2="40" y2="42" stroke="#C4A882" strokeWidth="2"/>
|
||||
<line x1="26" y1="28" x2="54" y2="28" stroke="#C4A882" strokeWidth="2"/>
|
||||
<circle cx="40" cy="28" r="3" fill="#8B4513"/>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'illustrated' as const,
|
||||
label: t('aiSettings.svgComplexityIllustrated'),
|
||||
desc: t('aiSettings.svgComplexityIllustratedDesc'),
|
||||
preview: (
|
||||
<svg viewBox="0 0 80 60" className="w-full h-full">
|
||||
<defs>
|
||||
<linearGradient id="bg-grad" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stopColor="#1a1a2e"/>
|
||||
<stop offset="100%" stopColor="#16213e"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="glow" cx="50%" cy="40%">
|
||||
<stop offset="0%" stopColor="#5F9EA0" stopOpacity="0.6"/>
|
||||
<stop offset="100%" stopColor="#5F9EA0" stopOpacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<rect width="80" height="60" fill="url(#bg-grad)" rx="4"/>
|
||||
<ellipse cx="40" cy="24" rx="28" ry="18" fill="url(#glow)"/>
|
||||
<rect x="18" y="30" width="44" height="22" fill="#2a2a4a" rx="3"/>
|
||||
<rect x="24" y="35" width="8" height="12" fill="#5F9EA0" rx="2" opacity="0.8"/>
|
||||
<rect x="36" y="32" width="8" height="15" fill="#C4A882" rx="2" opacity="0.8"/>
|
||||
<rect x="48" y="37" width="8" height="10" fill="#C9A9A6" rx="2" opacity="0.8"/>
|
||||
<circle cx="40" cy="16" r="8" fill="none" stroke="#5F9EA0" strokeWidth="1.5" opacity="0.7"/>
|
||||
<circle cx="40" cy="16" r="3" fill="#5F9EA0" opacity="0.9"/>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 'rich' as const,
|
||||
label: t('aiSettings.svgComplexityRich'),
|
||||
desc: t('aiSettings.svgComplexityRichDesc'),
|
||||
preview: (
|
||||
<svg viewBox="0 0 80 60" className="w-full h-full">
|
||||
<rect width="80" height="60" fill="#0F172A" rx="4"/>
|
||||
<circle cx="40" cy="30" r="8" fill="#5F9EA0" opacity="0.9"/>
|
||||
<circle cx="16" cy="18" r="6" fill="#C4A882" opacity="0.8"/>
|
||||
<circle cx="64" cy="18" r="6" fill="#C9A9A6" opacity="0.8"/>
|
||||
<circle cx="16" cy="42" r="6" fill="#A8B5A0" opacity="0.8"/>
|
||||
<circle cx="64" cy="42" r="6" fill="#8B4513" opacity="0.8"/>
|
||||
<line x1="40" y1="30" x2="16" y2="18" stroke="#5F9EA0" strokeWidth="1" opacity="0.5"/>
|
||||
<line x1="40" y1="30" x2="64" y2="18" stroke="#C4A882" strokeWidth="1" opacity="0.5"/>
|
||||
<line x1="40" y1="30" x2="16" y2="42" stroke="#A8B5A0" strokeWidth="1" opacity="0.5"/>
|
||||
<line x1="40" y1="30" x2="64" y2="42" stroke="#C9A9A6" strokeWidth="1" opacity="0.5"/>
|
||||
<text x="40" y="33" textAnchor="middle" fontSize="4" fill="white" fontFamily="sans-serif">MAIN</text>
|
||||
<text x="16" y="21" textAnchor="middle" fontSize="3.5" fill="white" fontFamily="sans-serif">idea 1</text>
|
||||
<text x="64" y="21" textAnchor="middle" fontSize="3.5" fill="white" fontFamily="sans-serif">idea 2</text>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
] as const).map((opt) => {
|
||||
const isActive = (settings.svgComplexity ?? 'simple') === opt.value
|
||||
return (
|
||||
<button
|
||||
key={opt.value}
|
||||
type="button"
|
||||
onClick={() => handleSvgComplexityChange(opt.value)}
|
||||
className={cn(
|
||||
'relative flex flex-col rounded-2xl border-2 overflow-hidden transition-all duration-200 text-left cursor-pointer',
|
||||
isActive
|
||||
? 'border-brand-accent shadow-md shadow-brand-accent/10'
|
||||
: 'border-border hover:border-brand-accent/40 hover:shadow-sm',
|
||||
)}
|
||||
>
|
||||
<div className="aspect-[4/3] w-full bg-muted/30">
|
||||
{opt.preview}
|
||||
</div>
|
||||
<div className={cn(
|
||||
'p-3 space-y-0.5 transition-colors',
|
||||
isActive ? 'bg-brand-accent/5' : 'bg-transparent',
|
||||
)}>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-[11px] font-bold text-ink">{opt.label}</span>
|
||||
{isActive && (
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-brand-accent"/>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-[9px] text-concrete leading-relaxed">{opt.desc}</p>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DemoModeToggle demoMode={settings.demoMode} onToggle={handleDemoModeToggle} />
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
Reference in New Issue
Block a user