feat: Go backend, enhanced search, new widgets, Docker deploy
Major changes: - Add Go backend (backend/) with microservices architecture - Enhanced master-agents-svc: reranker, content-classifier, stealth-crawler, proxy-manager, media-search, fastClassifier, language detection - New web-svc widgets: KnowledgeCard, ProductCard, ProfileCard, VideoCard, UnifiedCard, CardGallery, InlineImageGallery, SourcesPanel, RelatedQuestions - Improved discover-svc with discover-db integration - Docker deployment improvements (Caddyfile, vendor.sh, BUILD.md) - Library-svc: project_id schema migration - Remove deprecated finance-svc and travel-svc - Localization improvements across services Made-with: Cursor
This commit is contained in:
172
backend/webui/src/app/(main)/settings/page.tsx
Normal file
172
backend/webui/src/app/(main)/settings/page.tsx
Normal file
@@ -0,0 +1,172 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Shield, Zap, Scale, Sparkles, Download, Trash2, Bell, Eye, Languages } from 'lucide-react';
|
||||
import * as Switch from '@radix-ui/react-switch';
|
||||
|
||||
export default function SettingsPage() {
|
||||
const [mode, setMode] = useState('balanced');
|
||||
const [saveHistory, setSaveHistory] = useState(true);
|
||||
const [personalization, setPersonalization] = useState(true);
|
||||
const [notifications, setNotifications] = useState(false);
|
||||
const [language, setLanguage] = useState('ru');
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-y-auto">
|
||||
<div className="max-w-xl mx-auto px-4 sm:px-6 py-6 sm:py-8">
|
||||
{/* Header */}
|
||||
<div className="mb-6 sm:mb-8">
|
||||
<h1 className="text-xl sm:text-2xl font-semibold text-primary mb-1 sm:mb-2">Настройки</h1>
|
||||
<p className="text-sm text-secondary">Персонализируйте ваш опыт</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6 sm:space-y-8">
|
||||
{/* Default Mode */}
|
||||
<Section title="Режим по умолчанию" icon={Zap}>
|
||||
<div className="grid grid-cols-3 gap-2 sm:gap-3">
|
||||
{[
|
||||
{ id: 'speed', label: 'Быстрый', icon: Zap, desc: '~10 сек' },
|
||||
{ id: 'balanced', label: 'Баланс', icon: Scale, desc: '~30 сек' },
|
||||
{ id: 'quality', label: 'Качество', icon: Sparkles, desc: '~60 сек' },
|
||||
].map((m) => (
|
||||
<button
|
||||
key={m.id}
|
||||
onClick={() => setMode(m.id)}
|
||||
className={`flex flex-col items-center gap-1 sm:gap-2 p-3 sm:p-4 rounded-xl border transition-all ${
|
||||
mode === m.id
|
||||
? 'bg-accent/10 border-accent/30 text-primary'
|
||||
: 'bg-surface/30 border-border/30 text-muted hover:border-border hover:text-secondary'
|
||||
}`}
|
||||
>
|
||||
<m.icon className={`w-4 h-4 sm:w-5 sm:h-5 ${mode === m.id ? 'text-accent' : ''}`} />
|
||||
<span className="text-xs sm:text-sm font-medium">{m.label}</span>
|
||||
<span className="text-[10px] sm:text-xs text-faint">{m.desc}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* Privacy & Data */}
|
||||
<Section title="Приватность" icon={Shield}>
|
||||
<div className="space-y-3">
|
||||
<ToggleRow
|
||||
icon={Eye}
|
||||
label="Сохранять историю"
|
||||
description="Автоматическое сохранение сессий"
|
||||
checked={saveHistory}
|
||||
onChange={setSaveHistory}
|
||||
/>
|
||||
<ToggleRow
|
||||
icon={Sparkles}
|
||||
label="Персонализация"
|
||||
description="Улучшение на основе истории"
|
||||
checked={personalization}
|
||||
onChange={setPersonalization}
|
||||
/>
|
||||
<ToggleRow
|
||||
icon={Bell}
|
||||
label="Уведомления"
|
||||
description="Уведомления об обновлениях"
|
||||
checked={notifications}
|
||||
onChange={setNotifications}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* Language */}
|
||||
<Section title="Язык интерфейса" icon={Languages}>
|
||||
<div className="grid grid-cols-2 gap-2 sm:gap-3">
|
||||
{[
|
||||
{ id: 'ru', label: 'Русский', flag: '🇷🇺' },
|
||||
{ id: 'en', label: 'English', flag: '🇺🇸' },
|
||||
].map((lang) => (
|
||||
<button
|
||||
key={lang.id}
|
||||
onClick={() => setLanguage(lang.id)}
|
||||
className={`flex items-center gap-3 p-3 sm:p-4 rounded-xl border transition-all ${
|
||||
language === lang.id
|
||||
? 'bg-accent/10 border-accent/30 text-primary'
|
||||
: 'bg-surface/30 border-border/30 text-muted hover:border-border hover:text-secondary'
|
||||
}`}
|
||||
>
|
||||
<span className="text-lg">{lang.flag}</span>
|
||||
<span className="text-sm font-medium">{lang.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* Data Management */}
|
||||
<Section title="Управление данными" icon={Download}>
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
<button className="flex-1 flex items-center justify-center gap-2 px-4 py-3 text-sm bg-surface/40 border border-border/50 text-secondary rounded-xl hover:bg-surface/60 hover:border-border hover:text-primary transition-all">
|
||||
<Download className="w-4 h-4" />
|
||||
Экспорт данных
|
||||
</button>
|
||||
<button className="flex-1 flex items-center justify-center gap-2 px-4 py-3 text-sm bg-error/5 border border-error/20 text-error rounded-xl hover:bg-error/10 hover:border-error/30 transition-all">
|
||||
<Trash2 className="w-4 h-4" />
|
||||
Удалить всё
|
||||
</button>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* About */}
|
||||
<div className="pt-6 border-t border-border/30">
|
||||
<div className="text-center text-faint text-xs">
|
||||
<p className="mb-1">GooSeek v1.0.0</p>
|
||||
<p>AI-поиск нового поколения</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Section({ title, icon: Icon, children }: { title: string; icon: React.ElementType; children: React.ReactNode }) {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 8 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="space-y-3 sm:space-y-4"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Icon className="w-4 h-4 text-muted" />
|
||||
<h2 className="text-xs font-semibold text-muted uppercase tracking-wider">
|
||||
{title}
|
||||
</h2>
|
||||
</div>
|
||||
{children}
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
interface ToggleRowProps {
|
||||
icon: React.ElementType;
|
||||
label: string;
|
||||
description: string;
|
||||
checked: boolean;
|
||||
onChange: (v: boolean) => void;
|
||||
}
|
||||
|
||||
function ToggleRow({ icon: Icon, label, description, checked, onChange }: ToggleRowProps) {
|
||||
return (
|
||||
<div className="flex items-center gap-3 sm:gap-4 p-3 sm:p-4 bg-elevated/40 border border-border/40 rounded-xl">
|
||||
<div className="w-9 h-9 sm:w-10 sm:h-10 rounded-xl bg-surface/60 flex items-center justify-center flex-shrink-0">
|
||||
<Icon className="w-4 h-4 text-secondary" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm text-primary truncate">{label}</p>
|
||||
<p className="text-xs text-muted mt-0.5 truncate">{description}</p>
|
||||
</div>
|
||||
<Switch.Root
|
||||
checked={checked}
|
||||
onCheckedChange={onChange}
|
||||
className="w-10 h-[22px] sm:w-11 sm:h-6 bg-surface/80 rounded-full relative transition-colors data-[state=checked]:bg-accent/20 border border-border data-[state=checked]:border-accent/30 flex-shrink-0"
|
||||
>
|
||||
<Switch.Thumb className="block w-[18px] h-[18px] sm:w-5 sm:h-5 bg-secondary rounded-full transition-transform translate-x-0.5 data-[state=checked]:translate-x-[18px] sm:data-[state=checked]:translate-x-[22px] data-[state=checked]:bg-accent" />
|
||||
</Switch.Root>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user