'use server' import { auth } from '@/auth' import { prisma } from '@/lib/prisma' import { revalidatePath, updateTag } from 'next/cache' export type UserAISettingsData = { titleSuggestions?: boolean semanticSearch?: boolean paragraphRefactor?: boolean memoryEcho?: boolean memoryEchoFrequency?: 'daily' | 'weekly' | 'custom' aiProvider?: 'auto' | 'openai' | 'ollama' preferredLanguage?: 'auto' | 'en' | 'fr' | 'es' | 'de' | 'fa' | 'it' | 'pt' | 'ru' | 'zh' | 'ja' | 'ko' | 'ar' | 'hi' | 'nl' | 'pl' demoMode?: boolean showRecentNotes?: boolean notesViewMode?: 'masonry' | 'tabs' | 'list' emailNotifications?: boolean desktopNotifications?: boolean anonymousAnalytics?: boolean fontSize?: 'small' | 'medium' | 'large' } /** Only fields that exist on `UserAISettings` in Prisma (excludes e.g. `theme`, which lives on `User`). */ const USER_AI_SETTINGS_PRISMA_KEYS = [ 'titleSuggestions', 'semanticSearch', 'paragraphRefactor', 'memoryEcho', 'memoryEchoFrequency', 'aiProvider', 'preferredLanguage', 'fontSize', 'demoMode', 'showRecentNotes', 'notesViewMode', 'emailNotifications', 'desktopNotifications', 'anonymousAnalytics', ] as const type UserAISettingsPrismaKey = (typeof USER_AI_SETTINGS_PRISMA_KEYS)[number] function pickUserAISettingsForDb(input: UserAISettingsData): Partial> { const out: Partial> = {} for (const key of USER_AI_SETTINGS_PRISMA_KEYS) { const v = input[key] if (v !== undefined) { out[key] = v } } if (out.notesViewMode === 'list') { out.notesViewMode = 'tabs' } if ( out.notesViewMode != null && out.notesViewMode !== 'masonry' && out.notesViewMode !== 'tabs' ) { delete out.notesViewMode } return out } /** * Update AI settings for the current user */ export async function updateAISettings(settings: UserAISettingsData) { const session = await auth() if (!session?.user?.id) { console.error('[updateAISettings] Unauthorized: No session or user ID') throw new Error('Unauthorized') } try { const data = pickUserAISettingsForDb(settings) if (Object.keys(data).length === 0) { return { success: true } } // Valeurs scalaires uniquement (pickUserAISettingsForDb) — cast pour éviter UpdateOperations vs create. const payload = data as Record // Upsert settings (create if not exists, update if exists) await prisma.userAISettings.upsert({ where: { userId: session.user.id }, create: { userId: session.user.id, ...payload, }, update: payload, }) revalidatePath('/settings/ai', 'page') revalidatePath('/settings/appearance', 'page') revalidatePath('/', 'layout') updateTag('ai-settings') return { success: true } } catch (error) { console.error('Error updating AI settings:', error) const raw = error instanceof Error ? error.message : String(error) const isSchema = /no such column|notesViewMode|Unknown column|does not exist/i.test(raw) || (typeof raw === 'string' && raw.includes('UserAISettings') && raw.includes('column')) if (isSchema) { throw new Error( 'Schéma base de données obsolète : colonne notesViewMode manquante. Dans le dossier keep-notes, exécutez : npx prisma db push (ou appliquez les migrations Prisma).' ) } throw new Error('Failed to update AI settings') } } /** * 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, notesViewMode: 'masonry' as const, emailNotifications: false, desktopNotifications: false, anonymousAnalytics: false, theme: 'light' as const, fontSize: 'medium' as const } } const raw = settings.notesViewMode const viewMode = raw === 'masonry' ? ('masonry' as const) : raw === 'list' || raw === 'tabs' ? ('tabs' as const) : ('masonry' 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, notesViewMode: viewMode, 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, notesViewMode: 'masonry' as const, 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 if (!id) { const session = await auth() id = session?.user?.id } // Return defaults for non-logged-in users if (!id) { 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, notesViewMode: 'masonry' as const, emailNotifications: false, desktopNotifications: false, anonymousAnalytics: false, theme: 'light' as const, fontSize: 'medium' as const } } return getCachedAISettings(id) } /** * Get user's preferred AI provider */ export async function getUserAIPreference(): Promise<'auto' | 'openai' | 'ollama'> { const settings = await getAISettings() return settings.aiProvider } /** * Check if a specific AI feature is enabled for the user */ export async function isAIFeatureEnabled(feature: keyof UserAISettingsData): Promise { const settings = await getAISettings() switch (feature) { case 'titleSuggestions': return settings.titleSuggestions case 'semanticSearch': return settings.semanticSearch case 'paragraphRefactor': return settings.paragraphRefactor case 'memoryEcho': return settings.memoryEcho default: return true } }