Some checks failed
Deploy to Production / Build and Deploy (push) Failing after 1m7s
Replaced ~100+ hardcoded French and English text strings across 30+ components with proper i18n t() calls. Added 57 new translation keys to all 15 locale files (ar, de, en, es, fa, fr, hi, it, ja, ko, nl, pl, pt, ru, zh). Key changes: - contextual-ai-chat.tsx: 30 French strings → t() (actions, toasts, labels, placeholders) - ai-chat.tsx: 15 French/English strings → t() (header, tabs, welcome, insights, history) - note-inline-editor.tsx: 20 French fallbacks removed (toolbar, save status, checklist) - lab-skeleton.tsx: French loading text → t() - admin-header.tsx, header.tsx, editor-connections-section.tsx: French fallbacks removed - New AI chat component, agent cards, sidebar, settings panel i18n cleanup Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
269 lines
8.1 KiB
TypeScript
269 lines
8.1 KiB
TypeScript
'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'
|
|
languageDetection?: boolean
|
|
autoLabeling?: boolean
|
|
}
|
|
|
|
/** 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',
|
|
'languageDetection',
|
|
'autoLabeling',
|
|
] as const
|
|
|
|
type UserAISettingsPrismaKey = (typeof USER_AI_SETTINGS_PRISMA_KEYS)[number]
|
|
|
|
function pickUserAISettingsForDb(input: UserAISettingsData): Partial<Record<UserAISettingsPrismaKey, unknown>> {
|
|
const out: Partial<Record<UserAISettingsPrismaKey, unknown>> = {}
|
|
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<string, string | boolean | undefined>
|
|
|
|
// 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 memento-note, 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,
|
|
languageDetection: true,
|
|
autoLabeling: true,
|
|
}
|
|
}
|
|
|
|
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',
|
|
languageDetection: settings.languageDetection ?? true,
|
|
autoLabeling: settings.autoLabeling ?? true,
|
|
}
|
|
} 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<boolean> {
|
|
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
|
|
}
|
|
}
|