'use client'; import { useState, useEffect } from 'react'; import { BookText, Plus, Library, Calendar, Hash, Zap, Save, Trash2, MessageSquare, Loader2, Monitor, Scale, Stethoscope, BarChart3, Megaphone, ShoppingCart, FlaskConical, Users, CheckCircle2, AlertCircle, ArrowRight, MousePointerClick, Info, ExternalLink, } from 'lucide-react'; import Link from 'next/link'; import { cn } from '@/lib/utils'; import { useUser } from '@/app/dashboard/useUser'; import { useI18n } from '@/lib/i18n'; import { useGlossaries, useGlossary } from './useGlossaries'; import type { Glossary, GlossaryTermInput, GlossaryListItem } from './types'; import { ProUpgradePrompt } from './ProUpgradePrompt'; import { CreateGlossaryDialog } from './CreateGlossaryDialog'; import { EditGlossaryDialog } from './EditGlossaryDialog'; import { DeleteGlossaryDialog } from './DeleteGlossaryDialog'; import { useToast } from '@/components/ui/toast'; import { SUPPORTED_LANGUAGES } from './types'; import { useTranslationStore } from '@/lib/store'; import { API_BASE } from '@/lib/config'; const PRESETS = [ { key: 'it', title: 'IT / Logiciel', desc: 'Développement, infrastructure, DevOps', icon: Monitor, templateId: 'technology' }, { key: 'legal', title: 'Juridique / Contrats', desc: 'Droit des affaires, contentieux', icon: Scale, templateId: 'legal' }, { key: 'medical', title: 'Médical / Santé', desc: 'Pharmacologie, chirurgie, diagnostic', icon: Stethoscope, templateId: 'medical' }, { key: 'finance', title: 'Finance / Comptabilité', desc: 'IFRS, bilans, fiscalité', icon: BarChart3, templateId: 'finance' }, { key: 'marketing', title: 'Marketing / Publicité', desc: 'Digital, branding, analytics', icon: Megaphone, templateId: 'marketing' }, { key: 'hr', title: 'RH / Ressources Humaines', desc: 'Contrats, politiques, recrutement', icon: Users, templateId: 'hr' }, { key: 'scientific', title: 'Scientifique / Recherche', desc: 'Publications, thèses, articles', icon: FlaskConical, templateId: 'scientific' }, { key: 'ecommerce', title: 'E-commerce / Vente', desc: 'Boutiques en ligne, catalogues, CRM', icon: ShoppingCart, templateId: 'ecommerce' }, ]; export default function GlossariesPage() { const { t } = useI18n(); const { data: user, isLoading: isLoadingUser } = useUser(); const { glossaries, total, isLoading: isLoadingGlossaries, isCreating, isUpdating, isDeleting, isImportingTemplate, createGlossary, updateGlossary, deleteGlossary, importTemplate, } = useGlossaries(); const { toast } = useToast(); const { settings, updateSettings } = useTranslationStore(); const [createDialogOpen, setCreateDialogOpen] = useState(false); const [editDialogOpen, setEditDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [selectedGlossary, setSelectedGlossary] = useState(null); const [glossaryToDelete, setGlossaryToDelete] = useState<{ id: string; name: string } | null>(null); const [systemPrompt, setSystemPrompt] = useState(settings.systemPrompt); const [isSavingPrompt, setIsSavingPrompt] = useState(false); const [promptSaved, setPromptSaved] = useState(false); const [creatingPreset, setCreatingPreset] = useState(null); const { glossary: fullGlossary, isLoading: isLoadingGlossaryDetail } = useGlossary( selectedGlossary?.id || null ); const isPro = user?.tier === 'pro'; const isLoading = isLoadingUser || isLoadingGlossaries; // Current translation target from store const currentTargetLang = settings.defaultTargetLanguage; const currentTargetInfo = SUPPORTED_LANGUAGES.find(l => l.code === currentTargetLang); // Track whether prompt has unsaved changes const promptHasUnsavedChanges = systemPrompt !== settings.systemPrompt; const promptIsActive = !!settings.systemPrompt?.trim(); useEffect(() => { setSystemPrompt(settings.systemPrompt); setPromptSaved(false); }, [settings.systemPrompt]); const handleSavePrompt = async () => { setIsSavingPrompt(true); try { updateSettings({ systemPrompt }); await new Promise(resolve => setTimeout(resolve, 300)); setPromptSaved(true); toast({ title: t('context.saved'), description: t('context.savedDesc') }); setTimeout(() => setPromptSaved(false), 3000); } finally { setIsSavingPrompt(false); } }; const handleClearPrompt = () => { updateSettings({ systemPrompt: '' }); setSystemPrompt(''); }; const handleCreatePresetGlossary = async (preset: typeof PRESETS[0]) => { setCreatingPreset(preset.key); try { const token = localStorage.getItem('token'); if (!token) return; const headers: Record = { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }; const params = new URLSearchParams({ template_id: preset.templateId }); const res = await fetch(`${API_BASE}/api/v1/glossaries/import?${params.toString()}`, { method: 'POST', headers, }); if (res.ok) { const result = await res.json(); const glossary = result.data; toast({ title: t('context.presets.created'), description: t('context.presets.createdDesc', { name: glossary?.name ?? preset.title, count: String(glossary?.terms?.length ?? 0), }), }); } else { toast({ variant: 'destructive', title: t('glossaries.toast.error'), description: t('glossaries.toast.errorCreate') }); } } catch { toast({ variant: 'destructive', title: t('glossaries.toast.error'), description: t('glossaries.toast.errorImport') }); } finally { setCreatingPreset(null); } }; const handleEditClick = (id: string) => { const glossary = glossaries.find((g: GlossaryListItem) => g.id === id); if (glossary) { setSelectedGlossary(glossary); setEditDialogOpen(true); } }; const handleDeleteClick = (id: string, name: string) => { setGlossaryToDelete({ id, name }); setDeleteDialogOpen(true); }; const handleCreateGlossary = async (data: { name: string; source_language: string; target_language: string; terms: GlossaryTermInput[] }) => { try { await createGlossary(data); setCreateDialogOpen(false); toast({ title: t('glossaries.toast.created'), description: t('glossaries.toast.createdDesc', { name: data.name }), }); } catch (error) { toast({ variant: 'destructive', title: t('glossaries.toast.error'), description: t('glossaries.toast.errorCreate'), }); throw error; } }; const handleImportTemplate = async (templateId: string, name?: string) => { try { await importTemplate(templateId, name); setCreateDialogOpen(false); toast({ title: t('glossaries.toast.imported'), description: name ? t('glossaries.toast.importedDesc', { name }) : t('glossaries.toast.importedDesc', { name: templateId }), }); } catch (error) { toast({ variant: 'destructive', title: t('glossaries.toast.error'), description: t('glossaries.toast.errorImport'), }); throw error; } }; const handleSaveGlossary = async (id: string, data: { name: string; terms: GlossaryTermInput[] }) => { try { await updateGlossary(id, data); setEditDialogOpen(false); setSelectedGlossary(null); toast({ title: t('glossaries.toast.updated'), description: t('glossaries.toast.updatedDesc', { name: data.name }), }); } catch (error) { toast({ variant: 'destructive', title: t('glossaries.toast.error'), description: t('glossaries.toast.errorUpdate'), }); throw error; } }; const handleDeleteConfirm = async () => { if (!glossaryToDelete) return; try { await deleteGlossary(glossaryToDelete.id); setDeleteDialogOpen(false); setGlossaryToDelete(null); toast({ title: t('glossaries.toast.deleted'), description: t('glossaries.toast.deletedDesc'), }); } catch (error) { toast({ variant: 'destructive', title: t('glossaries.toast.error'), description: t('glossaries.toast.errorDelete'), }); } }; if (isLoading) { return (

Chargement...

); } if (!isPro) { return ; } const renderTitle = (title: string) => { const lastSpaceIndex = title.lastIndexOf(' '); if (lastSpaceIndex === -1) return title; const firstPart = title.substring(0, lastSpaceIndex); const lastWord = title.substring(lastSpaceIndex + 1); return ( <> {firstPart} {lastWord} ); }; return (
{/* ── Editorial Header ───────────────────────────────────── */}
{t('glossaries.yourGlossaries') || "Vos Glossaires"}

{renderTitle(t('glossaries.title') || "Glossaires & Contexte")}

{t('glossaries.description') || "Gérez vos glossaires et instructions de contexte pour des traductions plus précises."}

{/* ── Comment ça marche ─────────────────────────────────── */}
Comment ces paramètres sont utilisés
{/* Step 1 */}
1

Configurez ici

Rédigez vos instructions de contexte ou créez/importez un glossaire de termes.

{/* Arrow */}
{/* Step 2 */}
2

Activez dans Traduire

Sur la page de traduction, dans la colonne de droite, sélectionnez votre glossaire.

⚠️ Les instructions de contexte s'appliquent automatiquement à toutes vos traductions IA une fois enregistrées. Les glossaires doivent être sélectionnés manuellement sur la page Traduire.

Aller à Traduire
{/* ── System Prompt ────────────────────────────────────── */}
{/* Header with status badge */}

{t('context.instructions.title')}

{/* Status badge */} {promptHasUnsavedChanges ? ( Non enregistré ) : promptIsActive ? ( Actif · s'applique à toutes les traductions IA ) : ( Inactif )}
{/* Explanation box */}

À quoi ça sert ? Ces instructions sont envoyées automatiquement à l'IA avant chaque traduction, sans que vous ayez besoin de faire quoi que ce soit sur la page Traduire. Utilisez-les pour guider le style, le registre ou la terminologie générale.

Exemple : « Vous traduisez des rapports financiers. Soyez formel, précis et conservez tous les chiffres. »