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

@@ -16,7 +16,7 @@ async function checkAdmin() {
export async function testSMTP() {
const session = await checkAdmin()
const email = session.user?.email
if (!email) throw new Error("No admin email found")
const result = await sendEmail({
@@ -46,7 +46,7 @@ export async function updateSystemConfig(data: Record<string, string>) {
Object.entries(data).filter(([key, value]) => value !== '' && value !== null && value !== undefined)
)
console.log('Updating system config:', filteredData)
const operations = Object.entries(filteredData).map(([key, value]) =>
prisma.systemConfig.upsert({

View File

@@ -2,7 +2,7 @@
import { auth } from '@/auth'
import { prisma } from '@/lib/prisma'
import { revalidatePath } from 'next/cache'
import { revalidatePath, revalidateTag } from 'next/cache'
export type UserAISettingsData = {
titleSuggestions?: boolean
@@ -25,9 +25,9 @@ export type UserAISettingsData = {
* Update AI settings for the current user
*/
export async function updateAISettings(settings: UserAISettingsData) {
console.log('[updateAISettings] Started with:', JSON.stringify(settings, null, 2))
const session = await auth()
console.log('[updateAISettings] Session User ID:', session?.user?.id)
if (!session?.user?.id) {
console.error('[updateAISettings] Unauthorized: No session or user ID')
@@ -44,10 +44,11 @@ export async function updateAISettings(settings: UserAISettingsData) {
},
update: settings
})
console.log('[updateAISettings] Database upsert successful:', result)
revalidatePath('/settings/ai')
revalidatePath('/')
revalidatePath('/settings/ai', 'page')
revalidatePath('/', 'layout')
revalidateTag('ai-settings')
return { success: true }
} catch (error) {
@@ -57,8 +58,78 @@ export async function updateAISettings(settings: UserAISettingsData) {
}
/**
* Get AI settings for the current user
* Get AI settings for the current user (Cached)
*/
import { unstable_cache } from 'next/cache'
// Internal cached function to fetch settings from DB
const getCachedAISettings = unstable_cache(
async (userId: string) => {
try {
const settings = await prisma.userAISettings.findUnique({
where: { userId }
})
if (!settings) {
return {
titleSuggestions: true,
semanticSearch: true,
paragraphRefactor: true,
memoryEcho: true,
memoryEchoFrequency: 'daily' as const,
aiProvider: 'auto' as const,
preferredLanguage: 'auto' as const,
demoMode: false,
showRecentNotes: false,
emailNotifications: false,
desktopNotifications: false,
anonymousAnalytics: false,
theme: 'light' as const,
fontSize: 'medium' as const
}
}
return {
titleSuggestions: settings.titleSuggestions,
semanticSearch: settings.semanticSearch,
paragraphRefactor: settings.paragraphRefactor,
memoryEcho: settings.memoryEcho,
memoryEchoFrequency: (settings.memoryEchoFrequency || 'daily') as 'daily' | 'weekly' | 'custom',
aiProvider: (settings.aiProvider || 'auto') as 'auto' | 'openai' | 'ollama',
preferredLanguage: (settings.preferredLanguage || 'auto') as 'auto' | 'en' | 'fr' | 'es' | 'de' | 'fa' | 'it' | 'pt' | 'ru' | 'zh' | 'ja' | 'ko' | 'ar' | 'hi' | 'nl' | 'pl',
demoMode: settings.demoMode,
showRecentNotes: settings.showRecentNotes,
emailNotifications: settings.emailNotifications,
desktopNotifications: settings.desktopNotifications,
anonymousAnalytics: settings.anonymousAnalytics,
// theme: 'light' as const, // REMOVED: Should not be handled here or hardcoded
fontSize: (settings.fontSize || 'medium') as 'small' | 'medium' | 'large'
}
} catch (error) {
console.error('Error getting AI settings:', error)
// Return defaults on error
return {
titleSuggestions: true,
semanticSearch: true,
paragraphRefactor: true,
memoryEcho: true,
memoryEchoFrequency: 'daily' as const,
aiProvider: 'auto' as const,
preferredLanguage: 'auto' as const,
demoMode: false,
showRecentNotes: false,
emailNotifications: false,
desktopNotifications: false,
anonymousAnalytics: false,
theme: 'light' as const,
fontSize: 'medium' as const
}
}
},
['user-ai-settings'],
{ tags: ['ai-settings'] }
)
export async function getAISettings(userId?: string) {
let id = userId
@@ -87,66 +158,7 @@ export async function getAISettings(userId?: string) {
}
}
try {
const settings = await prisma.userAISettings.findUnique({
where: { userId: id }
})
if (!settings) {
return {
titleSuggestions: true,
semanticSearch: true,
paragraphRefactor: true,
memoryEcho: true,
memoryEchoFrequency: 'daily' as const,
aiProvider: 'auto' as const,
preferredLanguage: 'auto' as const,
demoMode: false,
showRecentNotes: false,
emailNotifications: false,
desktopNotifications: false,
anonymousAnalytics: false,
theme: 'light' as const,
fontSize: 'medium' as const
}
}
return {
titleSuggestions: settings.titleSuggestions,
semanticSearch: settings.semanticSearch,
paragraphRefactor: settings.paragraphRefactor,
memoryEcho: settings.memoryEcho,
memoryEchoFrequency: (settings.memoryEchoFrequency || 'daily') as 'daily' | 'weekly' | 'custom',
aiProvider: (settings.aiProvider || 'auto') as 'auto' | 'openai' | 'ollama',
preferredLanguage: (settings.preferredLanguage || 'auto') as 'auto' | 'en' | 'fr' | 'es' | 'de' | 'fa' | 'it' | 'pt' | 'ru' | 'zh' | 'ja' | 'ko' | 'ar' | 'hi' | 'nl' | 'pl',
demoMode: settings.demoMode,
showRecentNotes: settings.showRecentNotes,
emailNotifications: settings.emailNotifications,
desktopNotifications: settings.desktopNotifications,
anonymousAnalytics: settings.anonymousAnalytics,
// theme: 'light' as const, // REMOVED: Should not be handled here or hardcoded
fontSize: (settings.fontSize || 'medium') as 'small' | 'medium' | 'large'
}
} catch (error) {
console.error('Error getting AI settings:', error)
// Return defaults on error
return {
titleSuggestions: true,
semanticSearch: true,
paragraphRefactor: true,
memoryEcho: true,
memoryEchoFrequency: 'daily' as const,
aiProvider: 'auto' as const,
preferredLanguage: 'auto' as const,
demoMode: false,
showRecentNotes: false,
emailNotifications: false,
desktopNotifications: false,
anonymousAnalytics: false,
theme: 'light' as const,
fontSize: 'medium' as const
}
}
return getCachedAISettings(id)
}
/**

View File

@@ -346,7 +346,7 @@ export async function createNote(data: {
const autoLabelingConfidence = await getConfigNumber('AUTO_LABELING_CONFIDENCE_THRESHOLD', 70);
if (autoLabelingEnabled) {
console.log('[AUTO-LABELING] Generating suggestions for new note in notebook:', data.notebookId);
const suggestions = await contextualAutoTagService.suggestLabels(
data.content,
data.notebookId,
@@ -360,12 +360,12 @@ export async function createNote(data: {
if (appliedLabels.length > 0) {
labelsToUse = appliedLabels;
console.log(`[AUTO-LABELING] Applied ${appliedLabels.length} labels:`, appliedLabels);
} else {
console.log('[AUTO-LABELING] No suggestions met confidence threshold');
}
} else {
console.log('[AUTO-LABELING] Disabled in config');
}
} catch (error) {
console.error('[AUTO-LABELING] Failed to suggest labels:', error);

View File

@@ -0,0 +1,57 @@
'use server'
interface OllamaModel {
name: string
modified_at: string
size: number
digest: string
details: {
format: string
family: string
families: string[]
parameter_size: string
quantization_level: string
}
}
interface OllamaTagsResponse {
models: OllamaModel[]
}
export async function getOllamaModels(baseUrl: string): Promise<{ success: boolean; models: string[]; error?: string }> {
if (!baseUrl) {
return { success: false, models: [], error: 'Base URL is required' }
}
// Ensure URL doesn't end with slash
const cleanUrl = baseUrl.replace(/\/$/, '')
try {
const response = await fetch(`${cleanUrl}/api/tags`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
// Set a reasonable timeout
signal: AbortSignal.timeout(5000)
})
if (!response.ok) {
throw new Error(`Ollama API returned ${response.status}: ${response.statusText}`)
}
const data = await response.json() as OllamaTagsResponse
// Extract model names
const modelNames = data.models?.map(m => m.name) || []
return { success: true, models: modelNames }
} catch (error: any) {
console.error('Failed to fetch Ollama models:', error)
return {
success: false,
models: [],
error: error.message || 'Failed to connect to Ollama'
}
}
}

View File

@@ -2,7 +2,7 @@
import { auth } from '@/auth'
import { prisma } from '@/lib/prisma'
import { revalidatePath } from 'next/cache'
import { revalidatePath, revalidateTag } from 'next/cache'
export type UserSettingsData = {
theme?: 'light' | 'dark' | 'auto' | 'sepia' | 'midnight' | 'blue'
@@ -12,7 +12,7 @@ export type UserSettingsData = {
* Update user settings (theme, etc.)
*/
export async function updateUserSettings(settings: UserSettingsData) {
console.log('[updateUserSettings] Started with:', settings)
const session = await auth()
if (!session?.user?.id) {
@@ -25,9 +25,10 @@ export async function updateUserSettings(settings: UserSettingsData) {
where: { id: session.user.id },
data: settings
})
console.log('[updateUserSettings] Success:', result)
revalidatePath('/', 'layout')
revalidateTag('user-settings')
return { success: true }
} catch (error) {
@@ -37,8 +38,33 @@ export async function updateUserSettings(settings: UserSettingsData) {
}
/**
* Get user settings for current user
* Get user settings for current user (Cached)
*/
import { unstable_cache } from 'next/cache'
// Internal cached function
const getCachedUserSettings = unstable_cache(
async (userId: string) => {
try {
const user = await prisma.user.findUnique({
where: { id: userId },
select: { theme: true }
})
return {
theme: (user?.theme || 'light') as 'light' | 'dark' | 'auto' | 'sepia' | 'midnight' | 'blue'
}
} catch (error) {
console.error('Error getting user settings:', error)
return {
theme: 'light' as const
}
}
},
['user-settings'],
{ tags: ['user-settings'] }
)
export async function getUserSettings(userId?: string) {
let id = userId
@@ -53,19 +79,5 @@ export async function getUserSettings(userId?: string) {
}
}
try {
const user = await prisma.user.findUnique({
where: { id },
select: { theme: true }
})
return {
theme: (user?.theme || 'light') as 'light' | 'dark' | 'auto' | 'sepia' | 'midnight' | 'blue'
}
} catch (error) {
console.error('Error getting user settings:', error)
return {
theme: 'light' as const
}
}
return getCachedUserSettings(id)
}