feat(ai): localize AI features

This commit is contained in:
Sepehr Ramezani
2026-02-15 17:38:16 +01:00
parent 8f9031f076
commit 9eb3bd912a
72 changed files with 17098 additions and 7759 deletions

View File

@@ -1,127 +1,129 @@
'use client'
import { SettingsNav, SettingsSection } from '@/components/settings'
import { SettingsSection } from '@/components/settings'
import { Card, CardContent } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { useLanguage } from '@/lib/i18n'
export default function AboutSettingsPage() {
const { t } = useLanguage()
const version = '1.0.0'
const buildDate = '2026-01-17'
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold mb-2">About</h1>
<h1 className="text-3xl font-bold mb-2">{t('about.title')}</h1>
<p className="text-gray-600 dark:text-gray-400">
Information about the application
{t('about.description')}
</p>
</div>
<SettingsSection
title="Keep Notes"
title={t('about.appName')}
icon={<span className="text-2xl">📝</span>}
description="A powerful note-taking application with AI-powered features"
description={t('about.appDescription')}
>
<Card>
<CardContent className="pt-6 space-y-4">
<div className="flex justify-between items-center">
<span className="font-medium">Version</span>
<span className="font-medium">{t('about.version')}</span>
<Badge variant="secondary">{version}</Badge>
</div>
<div className="flex justify-between items-center">
<span className="font-medium">Build Date</span>
<span className="font-medium">{t('about.buildDate')}</span>
<Badge variant="outline">{buildDate}</Badge>
</div>
<div className="flex justify-between items-center">
<span className="font-medium">Platform</span>
<Badge variant="outline">Web</Badge>
<span className="font-medium">{t('about.platform')}</span>
<Badge variant="outline">{t('about.platformWeb')}</Badge>
</div>
</CardContent>
</Card>
</SettingsSection>
<SettingsSection
title="Features"
title={t('about.features.title')}
icon={<span className="text-2xl"></span>}
description="AI-powered capabilities"
description={t('about.features.description')}
>
<Card>
<CardContent className="pt-6 space-y-2">
<div className="flex items-center gap-2">
<span className="text-green-500"></span>
<span>AI-powered title suggestions</span>
<span>{t('about.features.titleSuggestions')}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-green-500"></span>
<span>Semantic search with embeddings</span>
<span>{t('about.features.semanticSearch')}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-green-500"></span>
<span>Paragraph reformulation</span>
<span>{t('about.features.paragraphReformulation')}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-green-500"></span>
<span>Memory Echo daily insights</span>
<span>{t('about.features.memoryEcho')}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-green-500"></span>
<span>Notebook organization</span>
<span>{t('about.features.notebookOrganization')}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-green-500"></span>
<span>Drag & drop note management</span>
<span>{t('about.features.dragDrop')}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-green-500"></span>
<span>Label system</span>
<span>{t('about.features.labelSystem')}</span>
</div>
<div className="flex items-center gap-2">
<span className="text-green-500"></span>
<span>Multiple AI providers (OpenAI, Ollama)</span>
<span>{t('about.features.multipleProviders')}</span>
</div>
</CardContent>
</Card>
</SettingsSection>
<SettingsSection
title="Technology Stack"
title={t('about.technology.title')}
icon={<span className="text-2xl"></span>}
description="Built with modern technologies"
description={t('about.technology.description')}
>
<Card>
<CardContent className="pt-6 space-y-2 text-sm">
<div><strong>Frontend:</strong> Next.js 16, React 19, TypeScript</div>
<div><strong>Backend:</strong> Next.js API Routes, Server Actions</div>
<div><strong>Database:</strong> SQLite (Prisma ORM)</div>
<div><strong>Authentication:</strong> NextAuth 5</div>
<div><strong>AI:</strong> Vercel AI SDK, OpenAI, Ollama</div>
<div><strong>UI:</strong> Radix UI, Tailwind CSS, Lucide Icons</div>
<div><strong>Testing:</strong> Playwright (E2E)</div>
<div><strong>{t('about.technology.frontend')}:</strong> Next.js 16, React 19, TypeScript</div>
<div><strong>{t('about.technology.backend')}:</strong> Next.js API Routes, Server Actions</div>
<div><strong>{t('about.technology.database')}:</strong> SQLite (Prisma ORM)</div>
<div><strong>{t('about.technology.authentication')}:</strong> NextAuth 5</div>
<div><strong>{t('about.technology.ai')}:</strong> Vercel AI SDK, OpenAI, Ollama</div>
<div><strong>{t('about.technology.ui')}:</strong> Radix UI, Tailwind CSS, Lucide Icons</div>
<div><strong>{t('about.technology.testing')}:</strong> Playwright (E2E)</div>
</CardContent>
</Card>
</SettingsSection>
<SettingsSection
title="Support"
title={t('about.support.title')}
icon={<span className="text-2xl">💬</span>}
description="Get help and feedback"
description={t('about.support.description')}
>
<Card>
<CardContent className="pt-6 space-y-4">
<div>
<p className="font-medium mb-2">Documentation</p>
<p className="font-medium mb-2">{t('about.support.documentation')}</p>
<p className="text-sm text-gray-600 dark:text-gray-400">
Check the documentation for detailed guides and tutorials.
</p>
</div>
<div>
<p className="font-medium mb-2">Report Issues</p>
<p className="font-medium mb-2">{t('about.support.reportIssues')}</p>
<p className="text-sm text-gray-600 dark:text-gray-400">
Found a bug? Report it in the issue tracker.
</p>
</div>
<div>
<p className="font-medium mb-2">Feedback</p>
<p className="font-medium mb-2">{t('about.support.feedback')}</p>
<p className="text-sm text-gray-600 dark:text-gray-400">
We value your feedback! Share your thoughts and suggestions.
</p>

View File

@@ -27,36 +27,33 @@ export default function AISettingsPage() {
try {
await updateAISettings({ [feature]: value })
} catch (error) {
console.error('Error updating setting:', error)
toast.error('Failed to save setting')
toast.error(t('aiSettings.error'))
setSettings(settings) // Revert on error
}
}
const handleFrequencyChange = async (value: 'daily' | 'weekly' | 'custom') => {
setSettings(prev => ({ ...prev, memoryEchoFrequency: value }))
const handleFrequencyChange = async (value: string) => {
setSettings(prev => ({ ...prev, memoryEchoFrequency: value as any }))
try {
await updateAISettings({ memoryEchoFrequency: value })
await updateAISettings({ memoryEchoFrequency: value as any })
} catch (error) {
console.error('Error updating frequency:', error)
toast.error('Failed to save setting')
toast.error(t('aiSettings.error'))
}
}
const handleProviderChange = async (value: 'auto' | 'openai' | 'ollama') => {
setSettings(prev => ({ ...prev, aiProvider: value }))
const handleProviderChange = async (value: string) => {
setSettings(prev => ({ ...prev, aiProvider: value as any }))
try {
await updateAISettings({ aiProvider: value })
await updateAISettings({ aiProvider: value as any })
} catch (error) {
console.error('Error updating provider:', error)
toast.error('Failed to save setting')
toast.error(t('aiSettings.error'))
}
}
const handleApiKeyChange = async (value: string) => {
setApiKey(value)
// TODO: Implement API key persistence
console.log('API Key:', value)
}
return (
@@ -70,37 +67,37 @@ export default function AISettingsPage() {
{/* Main Content */}
<main className="lg:col-span-3 space-y-6">
<div>
<h1 className="text-3xl font-bold mb-2">AI Settings</h1>
<h1 className="text-3xl font-bold mb-2">{t('aiSettings.title')}</h1>
<p className="text-gray-600 dark:text-gray-400">
Configure AI-powered features and preferences
{t('aiSettings.description')}
</p>
</div>
{/* AI Provider */}
<SettingsSection
title="AI Provider"
title={t('aiSettings.provider')}
icon={<span className="text-2xl">🤖</span>}
description="Choose your preferred AI service provider"
description={t('aiSettings.providerDesc')}
>
<SettingSelect
label="Provider"
description="Select which AI service to use"
label={t('aiSettings.provider')}
description={t('aiSettings.providerDesc')}
value={settings.aiProvider}
options={[
{
value: 'auto',
label: 'Auto-detect',
description: 'Ollama when available, OpenAI fallback'
label: t('aiSettings.providerAuto'),
description: t('aiSettings.providerAutoDesc')
},
{
value: 'ollama',
label: 'Ollama (Local)',
description: '100% private, runs locally on your machine'
label: t('aiSettings.providerOllama'),
description: t('aiSettings.providerOllamaDesc')
},
{
value: 'openai',
label: 'OpenAI',
description: 'Most accurate, requires API key'
label: t('aiSettings.providerOpenAI'),
description: t('aiSettings.providerOpenAIDesc')
},
]}
onChange={handleProviderChange}
@@ -108,8 +105,8 @@ export default function AISettingsPage() {
{settings.aiProvider === 'openai' && (
<SettingInput
label="API Key"
description="Your OpenAI API key (stored securely)"
label={t('admin.ai.apiKey')}
description={t('admin.ai.openAIKeyDescription')}
value={apiKey}
type="password"
placeholder="sk-..."
@@ -120,46 +117,46 @@ export default function AISettingsPage() {
{/* Feature Toggles */}
<SettingsSection
title="AI Features"
title={t('aiSettings.features')}
icon={<span className="text-2xl"></span>}
description="Enable or disable AI-powered features"
description={t('aiSettings.description')}
>
<SettingToggle
label="Title Suggestions"
description="Suggest titles for untitled notes after 50+ words"
label={t('titleSuggestions.available').replace('💡 ', '')}
description={t('aiSettings.titleSuggestionsDesc')}
checked={settings.titleSuggestions}
onChange={(checked) => handleToggle('titleSuggestions', checked)}
/>
<SettingToggle
label="Semantic Search"
description="Search by meaning, not just keywords"
label={t('semanticSearch.exactMatch')}
description={t('semanticSearch.searching')}
checked={settings.semanticSearch}
onChange={(checked) => handleToggle('semanticSearch', checked)}
/>
<SettingToggle
label="Paragraph Reformulation"
description="AI-powered text improvement options"
label={t('paragraphRefactor.title')}
description={t('aiSettings.paragraphRefactorDesc')}
checked={settings.paragraphRefactor}
onChange={(checked) => handleToggle('paragraphRefactor', checked)}
/>
<SettingToggle
label="Memory Echo"
description="Daily proactive note connections and insights"
label={t('memoryEcho.title')}
description={t('memoryEcho.dailyInsight')}
checked={settings.memoryEcho}
onChange={(checked) => handleToggle('memoryEcho', checked)}
/>
{settings.memoryEcho && (
<SettingSelect
label="Memory Echo Frequency"
description="How often to analyze note connections"
label={t('aiSettings.frequency')}
description={t('aiSettings.frequencyDesc')}
value={settings.memoryEchoFrequency}
options={[
{ value: 'daily', label: 'Daily' },
{ value: 'weekly', label: 'Weekly' },
{ value: 'daily', label: t('aiSettings.frequencyDaily') },
{ value: 'weekly', label: t('aiSettings.frequencyWeekly') },
{ value: 'custom', label: 'Custom' },
]}
onChange={handleFrequencyChange}
@@ -169,13 +166,13 @@ export default function AISettingsPage() {
{/* Demo Mode */}
<SettingsSection
title="Demo Mode"
title={t('demoMode.title')}
icon={<span className="text-2xl">🎭</span>}
description="Test AI features without using real AI calls"
description={t('demoMode.description')}
>
<SettingToggle
label="Enable Demo Mode"
description="Use mock AI responses for testing and demonstrations"
label={t('demoMode.title')}
description={t('demoMode.description')}
checked={settings.demoMode}
onChange={(checked) => handleToggle('demoMode', checked)}
/>

View File

@@ -3,9 +3,9 @@
import { useRouter } from 'next/navigation'
import { useState } from 'react'
import { SettingsSection, SettingSelect } from '@/components/settings'
// Import actions directly
import { updateAISettings as updateAI } from '@/app/actions/ai-settings'
import { updateUserSettings as updateUser } from '@/app/actions/user-settings'
import { useLanguage } from '@/lib/i18n'
interface AppearanceSettingsFormProps {
initialTheme: string
@@ -16,6 +16,7 @@ export function AppearanceSettingsForm({ initialTheme, initialFontSize }: Appear
const router = useRouter()
const [theme, setTheme] = useState(initialTheme)
const [fontSize, setFontSize] = useState(initialFontSize)
const { t } = useLanguage()
const handleThemeChange = async (value: string) => {
setTheme(value)
@@ -57,46 +58,46 @@ export function AppearanceSettingsForm({ initialTheme, initialFontSize }: Appear
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold mb-2">Appearance</h1>
<h1 className="text-3xl font-bold mb-2">{t('appearance.title')}</h1>
<p className="text-gray-600 dark:text-gray-400">
Customize look and feel of application
{t('appearance.description')}
</p>
</div>
<SettingsSection
title="Theme"
title={t('settings.theme')}
icon={<span className="text-2xl">🎨</span>}
description="Choose your preferred color scheme"
description={t('settings.themeLight') + ' / ' + t('settings.themeDark')}
>
<SettingSelect
label="Color Scheme"
description="Select app's visual theme"
label={t('settings.theme')}
description={t('settings.selectLanguage')}
value={theme}
options={[
{ value: 'slate', label: 'Light' },
{ value: 'dark', label: 'Dark' },
{ value: 'slate', label: t('settings.themeLight') },
{ value: 'dark', label: t('settings.themeDark') },
{ value: 'sepia', label: 'Sepia' },
{ value: 'midnight', label: 'Midnight' },
{ value: 'blue', label: 'Blue' },
{ value: 'auto', label: 'Auto (system)' },
{ value: 'auto', label: t('settings.themeSystem') },
]}
onChange={handleThemeChange}
/>
</SettingsSection>
<SettingsSection
title="Typography"
title={t('profile.fontSize')}
icon={<span className="text-2xl">📝</span>}
description="Adjust text size for better readability"
description={t('profile.fontSizeDescription')}
>
<SettingSelect
label="Font Size"
description="Adjust size of text throughout app"
label={t('profile.fontSize')}
description={t('profile.selectFontSize')}
value={fontSize}
options={[
{ value: 'small', label: 'Small' },
{ value: 'medium', label: 'Medium' },
{ value: 'large', label: 'Large' },
{ value: 'small', label: t('profile.fontSizeSmall') },
{ value: 'medium', label: t('profile.fontSizeMedium') },
{ value: 'large', label: t('profile.fontSizeLarge') },
]}
onChange={handleFontSizeChange}
/>

View File

@@ -4,8 +4,10 @@ import { useState, useEffect } from 'react'
import { SettingsNav, SettingsSection, SettingSelect } from '@/components/settings'
import { updateAISettings, getAISettings } from '@/app/actions/ai-settings'
import { updateUserSettings, getUserSettings } from '@/app/actions/user-settings'
import { useLanguage } from '@/lib/i18n'
export default function AppearanceSettingsPage() {
const { t } = useLanguage()
const [theme, setTheme] = useState('auto')
const [fontSize, setFontSize] = useState('medium')
@@ -63,45 +65,45 @@ export default function AppearanceSettingsPage() {
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold mb-2">Appearance</h1>
<h1 className="text-3xl font-bold mb-2">{t('appearance.title')}</h1>
<p className="text-gray-600 dark:text-gray-400">
Customize look and feel of application
{t('appearance.description')}
</p>
</div>
<SettingsSection
title="Theme"
title={t('settings.theme')}
icon={<span className="text-2xl">🎨</span>}
description="Choose your preferred color scheme"
description={t('settings.themeLight') + ' / ' + t('settings.themeDark')}
>
<SettingSelect
label="Color Scheme"
description="Select app's visual theme"
label={t('settings.theme')}
description={t('settings.selectLanguage')}
value={theme}
options={[
{ value: 'light', label: 'Light' },
{ value: 'dark', label: 'Dark' },
{ value: 'light', label: t('settings.themeLight') },
{ value: 'dark', label: t('settings.themeDark') },
{ value: 'sepia', label: 'Sepia' },
{ value: 'midnight', label: 'Midnight' },
{ value: 'auto', label: 'Auto (system)' },
{ value: 'auto', label: t('settings.themeSystem') },
]}
onChange={handleThemeChange}
/>
</SettingsSection>
<SettingsSection
title="Typography"
title={t('profile.fontSize')}
icon={<span className="text-2xl">📝</span>}
description="Adjust text size for better readability"
description={t('profile.fontSizeDescription')}
>
<SettingSelect
label="Font Size"
description="Adjust size of text throughout app"
label={t('profile.fontSize')}
description={t('profile.selectFontSize')}
value={fontSize}
options={[
{ value: 'small', label: 'Small' },
{ value: 'medium', label: 'Medium' },
{ value: 'large', label: 'Large' },
{ value: 'small', label: t('profile.fontSizeSmall') },
{ value: 'medium', label: t('profile.fontSizeMedium') },
{ value: 'large', label: t('profile.fontSizeLarge') },
]}
onChange={handleFontSizeChange}
/>

View File

@@ -1,16 +1,17 @@
'use client'
import { useState } from 'react'
import { SettingsNav, SettingsSection, SettingToggle, SettingInput } from '@/components/settings'
import { SettingsSection } from '@/components/settings'
import { Button } from '@/components/ui/button'
import { Download, Upload, Trash2, Loader2, Check } from 'lucide-react'
import { Download, Upload, Trash2, Loader2 } from 'lucide-react'
import { toast } from 'sonner'
import { useLanguage } from '@/lib/i18n'
export default function DataSettingsPage() {
const { t } = useLanguage()
const [isExporting, setIsExporting] = useState(false)
const [isImporting, setIsImporting] = useState(false)
const [isDeleting, setIsDeleting] = useState(false)
const [exportUrl, setExportUrl] = useState('')
const handleExport = async () => {
setIsExporting(true)
@@ -26,11 +27,11 @@ export default function DataSettingsPage() {
a.click()
document.body.removeChild(a)
window.URL.revokeObjectURL(url)
toast.success('Notes exported successfully')
toast.success(t('dataManagement.export.success'))
}
} catch (error) {
console.error('Export error:', error)
toast.error('Failed to export notes')
toast.error(t('dataManagement.export.failed'))
} finally {
setIsExporting(false)
}
@@ -52,24 +53,22 @@ export default function DataSettingsPage() {
if (response.ok) {
const result = await response.json()
toast.success(`Imported ${result.count} notes`)
// Refresh the page to show imported notes
toast.success(t('dataManagement.import.success', { count: result.count }))
window.location.reload()
} else {
throw new Error('Import failed')
}
} catch (error) {
console.error('Import error:', error)
toast.error('Failed to import notes')
toast.error(t('dataManagement.import.failed'))
} finally {
setIsImporting(false)
// Reset input
event.target.value = ''
}
}
const handleDeleteAll = async () => {
if (!confirm('Are you sure you want to delete all notes? This action cannot be undone.')) {
if (!confirm(t('dataManagement.delete.confirm'))) {
return
}
@@ -77,12 +76,12 @@ export default function DataSettingsPage() {
try {
const response = await fetch('/api/notes/delete-all', { method: 'POST' })
if (response.ok) {
toast.success('All notes deleted')
toast.success(t('dataManagement.delete.success'))
window.location.reload()
}
} catch (error) {
console.error('Delete error:', error)
toast.error('Failed to delete notes')
toast.error(t('dataManagement.delete.failed'))
} finally {
setIsDeleting(false)
}
@@ -91,22 +90,22 @@ export default function DataSettingsPage() {
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold mb-2">Data Management</h1>
<h1 className="text-3xl font-bold mb-2">{t('dataManagement.title')}</h1>
<p className="text-gray-600 dark:text-gray-400">
Export, import, or manage your data
{t('dataManagement.toolsDescription')}
</p>
</div>
<SettingsSection
title="Export Data"
title={`💾 ${t('dataManagement.export.title')}`}
icon={<span className="text-2xl">💾</span>}
description="Download your notes as a JSON file"
description={t('dataManagement.export.description')}
>
<div className="flex items-center justify-between py-4">
<div>
<p className="font-medium">Export All Notes</p>
<p className="font-medium">{t('dataManagement.export.title')}</p>
<p className="text-sm text-gray-600 dark:text-gray-400">
Download all your notes in JSON format
{t('dataManagement.export.description')}
</p>
</div>
<Button
@@ -118,21 +117,21 @@ export default function DataSettingsPage() {
) : (
<Download className="h-4 w-4 mr-2" />
)}
{isExporting ? 'Exporting...' : 'Export'}
{isExporting ? t('dataManagement.exporting') : t('dataManagement.export.button')}
</Button>
</div>
</SettingsSection>
<SettingsSection
title="Import Data"
title={`📥 ${t('dataManagement.import.title')}`}
icon={<span className="text-2xl">📥</span>}
description="Import notes from a JSON file"
description={t('dataManagement.import.description')}
>
<div className="flex items-center justify-between py-4">
<div>
<p className="font-medium">Import Notes</p>
<p className="font-medium">{t('dataManagement.import.title')}</p>
<p className="text-sm text-gray-600 dark:text-gray-400">
Upload a JSON file to import notes
{t('dataManagement.import.description')}
</p>
</div>
<div>
@@ -153,22 +152,22 @@ export default function DataSettingsPage() {
) : (
<Upload className="h-4 w-4 mr-2" />
)}
{isImporting ? 'Importing...' : 'Import'}
{isImporting ? t('dataManagement.importing') : t('dataManagement.import.button')}
</Button>
</div>
</div>
</SettingsSection>
<SettingsSection
title="Danger Zone"
title={`⚠️ ${t('dataManagement.dangerZone')}`}
icon={<span className="text-2xl"></span>}
description="Permanently delete your data"
description={t('dataManagement.dangerZoneDescription')}
>
<div className="flex items-center justify-between py-4 border-t border-red-200 dark:border-red-900">
<div>
<p className="font-medium text-red-600 dark:text-red-400">Delete All Notes</p>
<p className="font-medium text-red-600 dark:text-red-400">{t('dataManagement.delete.title')}</p>
<p className="text-sm text-gray-600 dark:text-gray-400">
This action cannot be undone
{t('dataManagement.delete.description')}
</p>
</div>
<Button
@@ -181,7 +180,7 @@ export default function DataSettingsPage() {
) : (
<Trash2 className="h-4 w-4 mr-2" />
)}
{isDeleting ? 'Deleting...' : 'Delete All'}
{isDeleting ? t('dataManagement.deleting') : t('dataManagement.delete.button')}
</Button>
</div>
</SettingsSection>

View File

@@ -4,9 +4,10 @@ import { useState, useEffect } from 'react'
import { SettingsNav, SettingsSection, SettingToggle, SettingSelect } from '@/components/settings'
import { useLanguage } from '@/lib/i18n'
import { updateAISettings, getAISettings } from '@/app/actions/ai-settings'
import { toast } from 'sonner'
export default function GeneralSettingsPage() {
const { t } = useLanguage()
const { t, setLanguage: setContextLanguage } = useLanguage()
const [language, setLanguage] = useState('auto')
const [emailNotifications, setEmailNotifications] = useState(false)
const [desktopNotifications, setDesktopNotifications] = useState(false)
@@ -30,7 +31,22 @@ export default function GeneralSettingsPage() {
const handleLanguageChange = async (value: string) => {
setLanguage(value)
// 1. Update database settings
await updateAISettings({ preferredLanguage: value as any })
// 2. Update local storage and application state
if (value === 'auto') {
localStorage.removeItem('user-language')
toast.success("Language set to Auto")
} else {
localStorage.setItem('user-language', value)
setContextLanguage(value as any)
toast.success(t('profile.languageUpdateSuccess') || "Language updated")
}
// 3. Force reload to ensure all components update (server components, metadata, etc.)
setTimeout(() => window.location.reload(), 500)
}
const handleEmailNotificationsChange = async (enabled: boolean) => {
@@ -51,23 +67,23 @@ export default function GeneralSettingsPage() {
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold mb-2">General Settings</h1>
<h1 className="text-3xl font-bold mb-2">{t('generalSettings.title')}</h1>
<p className="text-gray-600 dark:text-gray-400">
Configure basic application preferences
{t('generalSettings.description')}
</p>
</div>
<SettingsSection
title="Language & Region"
title={t('settings.language')}
icon={<span className="text-2xl">🌍</span>}
description="Choose your preferred language and regional settings"
description={t('profile.languagePreferencesDescription')}
>
<SettingSelect
label="Language"
description="Select interface language"
label={t('settings.language')}
description={t('settings.selectLanguage')}
value={language}
options={[
{ value: 'auto', label: 'Auto-detect' },
{ value: 'auto', label: t('profile.autoDetect') },
{ value: 'en', label: 'English' },
{ value: 'fr', label: 'Français' },
{ value: 'es', label: 'Español' },
@@ -89,32 +105,32 @@ export default function GeneralSettingsPage() {
</SettingsSection>
<SettingsSection
title="Notifications"
title={t('settings.notifications')}
icon={<span className="text-2xl">🔔</span>}
description="Manage how and when you receive notifications"
description={t('settings.notifications')}
>
<SettingToggle
label="Email Notifications"
description="Receive email updates about your notes"
label={t('settings.notifications')}
description={t('settings.notifications')}
checked={emailNotifications}
onChange={handleEmailNotificationsChange}
/>
<SettingToggle
label="Desktop Notifications"
description="Show notifications in your browser"
label={t('settings.notifications')}
description={t('settings.notifications')}
checked={desktopNotifications}
onChange={handleDesktopNotificationsChange}
/>
</SettingsSection>
<SettingsSection
title="Privacy"
title={t('settings.privacy')}
icon={<span className="text-2xl">🔒</span>}
description="Control your privacy settings"
description={t('settings.privacy')}
>
<SettingToggle
label="Anonymous Analytics"
description="Help improve app with anonymous usage data"
label={t('settings.privacy')}
description={t('settings.privacy')}
checked={anonymousAnalytics}
onChange={handleAnonymousAnalyticsChange}
/>

View File

@@ -81,9 +81,9 @@ export default function SettingsPage() {
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold mb-2">Settings</h1>
<h1 className="text-3xl font-bold mb-2">{t('settings.title')}</h1>
<p className="text-gray-600 dark:text-gray-400">
Configure your application settings
{t('settings.description')}
</p>
</div>
@@ -92,18 +92,18 @@ export default function SettingsPage() {
<Link href="/settings/ai">
<div className="p-4 border rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors cursor-pointer">
<BrainCircuit className="h-6 w-6 text-purple-500 mb-2" />
<h3 className="font-semibold">AI Settings</h3>
<h3 className="font-semibold">{t('aiSettings.title')}</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
Configure AI features and provider
{t('aiSettings.description')}
</p>
</div>
</Link>
<Link href="/settings/profile">
<div className="p-4 border rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors cursor-pointer">
<RefreshCw className="h-6 w-6 text-primary mb-2" />
<h3 className="font-semibold">Profile Settings</h3>
<h3 className="font-semibold">{t('profile.title')}</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
Manage your account and preferences
{t('profile.description')}
</p>
</div>
</Link>
@@ -111,17 +111,17 @@ export default function SettingsPage() {
{/* AI Diagnostics */}
<SettingsSection
title="AI Diagnostics"
title={t('diagnostics.title')}
icon={<span className="text-2xl">🔍</span>}
description="Check your AI provider connection status"
description={t('diagnostics.description')}
>
<div className="grid grid-cols-2 gap-4 py-4">
<div className="p-4 rounded-lg bg-secondary/50">
<p className="text-sm font-medium text-muted-foreground mb-1">Configured Provider</p>
<p className="text-sm font-medium text-muted-foreground mb-1">{t('diagnostics.configuredProvider')}</p>
<p className="text-lg font-mono">{config?.provider || '...'}</p>
</div>
<div className="p-4 rounded-lg bg-secondary/50">
<p className="text-sm font-medium text-muted-foreground mb-1">API Status</p>
<p className="text-sm font-medium text-muted-foreground mb-1">{t('diagnostics.apiStatus')}</p>
<div className="flex items-center gap-2">
{status === 'success' && <CheckCircle className="text-green-500 w-5 h-5" />}
{status === 'error' && <XCircle className="text-red-500 w-5 h-5" />}
@@ -129,9 +129,9 @@ export default function SettingsPage() {
status === 'error' ? 'text-red-600 dark:text-red-400' :
'text-gray-600'
}`}>
{status === 'success' ? 'Operational' :
status === 'error' ? 'Error' :
'Checking...'}
{status === 'success' ? t('diagnostics.operational') :
status === 'error' ? t('diagnostics.errorStatus') :
t('diagnostics.checking')}
</span>
</div>
</div>
@@ -139,7 +139,7 @@ export default function SettingsPage() {
{result && (
<div className="space-y-2 mt-4">
<h3 className="text-sm font-medium">Test Details:</h3>
<h3 className="text-sm font-medium">{t('diagnostics.testDetails')}</h3>
<div className={`p-4 rounded-md font-mono text-xs overflow-x-auto ${status === 'error'
? 'bg-red-50 text-red-900 border border-red-200 dark:bg-red-950 dark:text-red-100 dark:border-red-900'
: 'bg-slate-950 text-slate-50'
@@ -149,12 +149,12 @@ export default function SettingsPage() {
{status === 'error' && (
<div className="text-sm text-red-600 dark:text-red-400 mt-2">
<p className="font-bold">Troubleshooting Tips:</p>
<p className="font-bold">{t('diagnostics.troubleshootingTitle')}</p>
<ul className="list-disc list-inside mt-1 space-y-1">
<li>Check that Ollama is running (<code className="bg-red-100 dark:bg-red-900 px-1 rounded">ollama list</code>)</li>
<li>Check URL (http://localhost:11434)</li>
<li>Verify model (e.g., granite4:latest) is downloaded</li>
<li>Check Next.js server terminal for more logs</li>
<li>{t('diagnostics.tip1')}</li>
<li>{t('diagnostics.tip2')}</li>
<li>{t('diagnostics.tip3')}</li>
<li>{t('diagnostics.tip4')}</li>
</ul>
</div>
)}
@@ -164,45 +164,45 @@ export default function SettingsPage() {
<div className="mt-4 flex justify-end">
<Button variant="outline" size="sm" onClick={checkConnection} disabled={loading}>
{loading ? <Loader2 className="w-4 h-4 animate-spin mr-2" /> : <RefreshCw className="w-4 h-4 mr-2" />}
Test Connection
{t('general.testConnection')}
</Button>
</div>
</SettingsSection>
{/* Maintenance */}
<SettingsSection
title="Maintenance"
title={t('settings.maintenance')}
icon={<span className="text-2xl">🔧</span>}
description="Tools to maintain your database health"
description={t('settings.maintenanceDescription')}
>
<div className="space-y-4 py-4">
<div className="flex items-center justify-between p-4 border rounded-lg">
<div>
<h3 className="font-medium flex items-center gap-2">
Clean Orphan Tags
{t('settings.cleanTags')}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
Remove tags that are no longer used by any notes
{t('settings.cleanTagsDescription')}
</p>
</div>
<Button variant="secondary" onClick={handleCleanup} disabled={cleanupLoading}>
{cleanupLoading ? <Loader2 className="w-4 h-4 animate-spin mr-2" /> : <Database className="w-4 h-4 mr-2" />}
Clean
{t('general.clean')}
</Button>
</div>
<div className="flex items-center justify-between p-4 border rounded-lg">
<div>
<h3 className="font-medium flex items-center gap-2">
Semantic Indexing
{t('settings.semanticIndexing')}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
Generate vectors for all notes to enable intent-based search
{t('settings.semanticIndexingDescription')}
</p>
</div>
<Button variant="secondary" onClick={handleSync} disabled={syncLoading}>
{syncLoading ? <Loader2 className="w-4 h-4 animate-spin mr-2" /> : <BrainCircuit className="w-4 h-4 mr-2" />}
Index All
{t('general.indexAll')}
</Button>
</div>
</div>

View File

@@ -21,13 +21,13 @@ export default function ProfileSettingsPage() {
const handleNameChange = async (value: string) => {
setUser(prev => ({ ...prev, name: value }))
// TODO: Implement profile update
console.log('Name:', value)
}
const handleEmailChange = async (value: string) => {
setUser(prev => ({ ...prev, email: value }))
// TODO: Implement email update
console.log('Email:', value)
}
const handleLanguageChange = async (value: string) => {
@@ -36,7 +36,7 @@ export default function ProfileSettingsPage() {
await updateAISettings({ preferredLanguage: value as any })
} catch (error) {
console.error('Error updating language:', error)
toast.error('Failed to save language')
toast.error(t('aiSettings.error'))
}
}
@@ -46,7 +46,7 @@ export default function ProfileSettingsPage() {
await updateAISettings({ showRecentNotes: enabled })
} catch (error) {
console.error('Error updating recent notes setting:', error)
toast.error('Failed to save setting')
toast.error(t('aiSettings.error'))
}
}
@@ -61,48 +61,48 @@ export default function ProfileSettingsPage() {
{/* Main Content */}
<main className="lg:col-span-3 space-y-6">
<div>
<h1 className="text-3xl font-bold mb-2">Profile</h1>
<h1 className="text-3xl font-bold mb-2">{t('profile.title')}</h1>
<p className="text-gray-600 dark:text-gray-400">
Manage your account and personal information
{t('profile.description')}
</p>
</div>
{/* Profile Information */}
<SettingsSection
title="Profile Information"
title={t('profile.accountSettings')}
icon={<span className="text-2xl">👤</span>}
description="Update your personal details"
description={t('profile.description')}
>
<SettingInput
label="Name"
description="Your display name"
label={t('profile.displayName')}
description={t('profile.displayName')}
value={user.name}
onChange={handleNameChange}
placeholder="Enter your name"
placeholder={t('auth.namePlaceholder')}
/>
<SettingInput
label="Email"
description="Your email address"
label={t('profile.email')}
description={t('profile.email')}
value={user.email}
type="email"
onChange={handleEmailChange}
placeholder="Enter your email"
placeholder={t('auth.emailPlaceholder')}
/>
</SettingsSection>
{/* Preferences */}
<SettingsSection
title="Preferences"
title={t('settings.language')}
icon={<span className="text-2xl"></span>}
description="Customize your experience"
description={t('profile.languagePreferencesDescription')}
>
<SettingSelect
label="Language"
description="Choose your preferred language"
label={t('profile.preferredLanguage')}
description={t('profile.languageDescription')}
value={language}
options={[
{ value: 'auto', label: 'Auto-detect' },
{ value: 'auto', label: t('profile.autoDetect') },
{ value: 'en', label: 'English' },
{ value: 'fr', label: 'Français' },
{ value: 'es', label: 'Español' },
@@ -123,8 +123,8 @@ export default function ProfileSettingsPage() {
/>
<SettingToggle
label="Show Recent Notes"
description="Display recently viewed notes in sidebar"
label={t('profile.showRecentNotes')}
description={t('profile.showRecentNotesDescription')}
checked={showRecentNotes}
onChange={handleRecentNotesChange}
/>
@@ -135,16 +135,16 @@ export default function ProfileSettingsPage() {
<div className="flex items-center gap-4">
<div className="text-4xl"></div>
<div className="flex-1">
<h3 className="font-semibold text-lg mb-1">AI Settings</h3>
<h3 className="font-semibold text-lg mb-1">{t('aiSettings.title')}</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
Configure AI-powered features, provider selection, and preferences
{t('aiSettings.description')}
</p>
</div>
<button
onClick={() => window.location.href = '/settings/ai'}
className="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
>
Configure
{t('nav.configureAI')}
</button>
</div>
</div>

View File

@@ -7,40 +7,16 @@ import { Input } from '@/components/ui/input'
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
import { Label } from '@/components/ui/label'
import { Switch } from '@/components/ui/switch'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { updateProfile, changePassword, updateLanguage, updateFontSize, updateShowRecentNotes } from '@/app/actions/profile'
import { updateProfile, changePassword, updateFontSize, updateShowRecentNotes } from '@/app/actions/profile'
import { toast } from 'sonner'
import { useLanguage } from '@/lib/i18n'
const LANGUAGES = [
{ value: 'auto', label: 'Auto-detect', flag: '🌐' },
{ value: 'en', label: 'English', flag: '🇬🇧' },
{ value: 'fr', label: 'Français', flag: '🇫🇷' },
{ value: 'es', label: 'Español', flag: '🇪🇸' },
{ value: 'de', label: 'Deutsch', flag: '🇩🇪' },
{ value: 'it', label: 'Italiano', flag: '🇮🇹' },
{ value: 'pt', label: 'Português', flag: '🇵🇹' },
{ value: 'ru', label: 'Русский', flag: '🇷🇺' },
{ value: 'zh', label: '中文', flag: '🇨🇳' },
{ value: 'ja', label: '日本語', flag: '🇯🇵' },
{ value: 'ko', label: '한국어', flag: '🇰🇷' },
{ value: 'ar', label: 'العربية', flag: '🇸🇦' },
{ value: 'hi', label: 'हिन्दी', flag: '🇮🇳' },
{ value: 'nl', label: 'Nederlands', flag: '🇳🇱' },
{ value: 'pl', label: 'Polski', flag: '🇵🇱' },
{ value: 'fa', label: 'فارسی (Persian)', flag: '🇮🇷' },
]
export function ProfileForm({ user, userAISettings }: { user: any; userAISettings?: any }) {
const router = useRouter()
const [selectedLanguage, setSelectedLanguage] = useState(userAISettings?.preferredLanguage || 'auto')
const [isUpdatingLanguage, setIsUpdatingLanguage] = useState(false)
const [fontSize, setFontSize] = useState(userAISettings?.fontSize || 'medium')
const [isUpdatingFontSize, setIsUpdatingFontSize] = useState(false)
const [showRecentNotes, setShowRecentNotes] = useState(userAISettings?.showRecentNotes ?? false)
@@ -101,26 +77,7 @@ export function ProfileForm({ user, userAISettings }: { user: any; userAISetting
applyFontSize(savedFontSize as string)
}, [])
const handleLanguageChange = async (language: string) => {
setIsUpdatingLanguage(true)
try {
const result = await updateLanguage(language)
if (result?.error) {
toast.error(t('profile.languageUpdateFailed'))
} else {
setSelectedLanguage(language)
// Update localStorage and reload to apply new language
localStorage.setItem('user-language', language)
toast.success(t('profile.languageUpdateSuccess'))
// Reload page to apply new language
setTimeout(() => window.location.reload(), 500)
}
} catch (error) {
toast.error(t('profile.languageUpdateFailed'))
} finally {
setIsUpdatingLanguage(false)
}
}
const handleShowRecentNotesChange = async (enabled: boolean) => {
setIsUpdatingRecentNotes(true)
@@ -175,39 +132,7 @@ export function ProfileForm({ user, userAISettings }: { user: any; userAISetting
</form>
</Card>
<Card>
<CardHeader>
<CardTitle>{t('profile.languagePreferences')}</CardTitle>
<CardDescription>{t('profile.languagePreferencesDescription')}</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="language">{t('profile.preferredLanguage')}</Label>
<Select
value={selectedLanguage}
onValueChange={handleLanguageChange}
disabled={isUpdatingLanguage}
>
<SelectTrigger id="language">
<SelectValue placeholder={t('profile.selectLanguage')} />
</SelectTrigger>
<SelectContent>
{LANGUAGES.map((lang) => (
<SelectItem key={lang.value} value={lang.value}>
<span className="flex items-center gap-2">
<span>{lang.flag}</span>
<span>{lang.label}</span>
</span>
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-sm text-muted-foreground">
{t('profile.languageDescription')}
</p>
</div>
</CardContent>
</Card>