From 50d5a8d22f0d86c838256f4546e038a743834772 Mon Sep 17 00:00:00 2001 From: sepehr Date: Sat, 16 May 2026 15:54:04 +0200 Subject: [PATCH] feat: multilingual glossary UI, translate selector, context fusion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TermEditor: rewritten with expandable multilingual translation grid (13 languages), editorial styling, source/target + translations JSON - GlossarySelector: new component in translate page config panel, fetches user glossaries, shows flag + term count, Pro+LLM only - useTranslationConfig: added glossaryId state - useTranslationSubmit: sends glossary_id to backend - Context page: removed textarea glossary, presets now create API glossaries via template import, added link to Glossaries page - i18n: added 12 keys × 13 locales for glossary/translate/context Co-Authored-By: Claude Opus 4.7 --- frontend/src/app/dashboard/context/page.tsx | 168 ++++++++----- .../app/dashboard/glossaries/TermEditor.tsx | 234 +++++++++++++----- .../dashboard/translate/GlossarySelector.tsx | 125 ++++++++++ frontend/src/app/dashboard/translate/page.tsx | 9 + frontend/src/app/dashboard/translate/types.ts | 3 + .../translate/useTranslationConfig.ts | 6 +- .../translate/useTranslationSubmit.ts | 4 + frontend/src/lib/i18n.tsx | 143 +++++++++++ 8 files changed, 563 insertions(+), 129 deletions(-) create mode 100644 frontend/src/app/dashboard/translate/GlossarySelector.tsx diff --git a/frontend/src/app/dashboard/context/page.tsx b/frontend/src/app/dashboard/context/page.tsx index 09458cc..b25091d 100644 --- a/frontend/src/app/dashboard/context/page.tsx +++ b/frontend/src/app/dashboard/context/page.tsx @@ -2,40 +2,40 @@ import { useState, useEffect } from 'react'; import { - Zap, Save, Brain, BookOpen, Trash2, Crown, Loader2, + Zap, Save, Crown, Loader2, Trash2, Wrench, HardHat, Monitor, Scale, Stethoscope, BarChart3, - Megaphone, Car, MessageSquare, Database, + Megaphone, Car, MessageSquare, BookOpen, CheckCircle2, } from 'lucide-react'; import { useTranslationStore } from '@/lib/store'; import { API_BASE } from '@/lib/config'; import { useI18n } from '@/lib/i18n'; +import { useToast } from '@/components/ui/toast'; import Link from 'next/link'; import { cn } from '@/lib/utils'; const PRESETS = [ - { key: 'hvac', title: 'HVAC / Génie climatique', desc: 'Thermique, ventilation, climatisation', icon: Wrench }, - { key: 'construction', title: 'BTP / Construction', desc: 'Gros œuvre, second œuvre, normes', icon: HardHat }, - { key: 'it', title: 'IT / Logiciel', desc: 'Développement, infrastructure, DevOps', icon: Monitor }, - { key: 'legal', title: 'Juridique / Contrats', desc: 'Droit des affaires, contentieux', icon: Scale }, - { key: 'medical', title: 'Médical / Santé', desc: 'Pharmacologie, chirurgie, diagnostic', icon: Stethoscope }, - { key: 'finance', title: 'Finance / Comptabilité', desc: 'IFRS, bilans, fiscalité', icon: BarChart3 }, - { key: 'marketing', title: 'Marketing / Publicité', desc: 'Digital, branding, analytics', icon: Megaphone }, - { key: 'automotive', title: 'Automobile', desc: 'Motorisation, ADAS, homologation', icon: Car }, + { key: 'hvac', title: 'HVAC / Génie climatique', desc: 'Thermique, ventilation, climatisation', icon: Wrench, templateId: 'hvac' }, + { key: 'construction', title: 'BTP / Construction', desc: 'Gros œuvre, second œuvre, normes', icon: HardHat, templateId: 'construction' }, + { 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: 'automotive', title: 'Automobile', desc: 'Motorisation, ADAS, homologation', icon: Car, templateId: 'automotive' }, ]; export default function ContextGlossaryPage() { const { t } = useI18n(); - const { settings, updateSettings, applyPreset, clearContext } = useTranslationStore(); + const { settings, updateSettings } = useTranslationStore(); + const { toast } = useToast(); const [isSaving, setIsSaving] = useState(false); const [isPro, setIsPro] = useState(false); + const [creatingPreset, setCreatingPreset] = useState(null); - const [localSettings, setLocalSettings] = useState({ - systemPrompt: settings.systemPrompt, - glossary: settings.glossary, - }); + const [systemPrompt, setSystemPrompt] = useState(settings.systemPrompt); useEffect(() => { - setLocalSettings({ systemPrompt: settings.systemPrompt, glossary: settings.glossary }); + setSystemPrompt(settings.systemPrompt); }, [settings]); useEffect(() => { @@ -66,24 +66,60 @@ export default function ContextGlossaryPage() { const handleSave = async () => { setIsSaving(true); try { - updateSettings(localSettings); + updateSettings({ systemPrompt }); await new Promise(resolve => setTimeout(resolve, 500)); + toast({ title: t('context.saved'), description: t('context.savedDesc') }); } finally { setIsSaving(false); } }; - const handleApplyPreset = (key: string) => { - applyPreset(key); - setTimeout(() => { - setLocalSettings({ - systemPrompt: useTranslationStore.getState().settings.systemPrompt, - glossary: useTranslationStore.getState().settings.glossary, - }); - }, 0); + const handleClear = () => { + updateSettings({ systemPrompt: '' }); + setSystemPrompt(''); }; - const handleClear = () => { - clearContext(); - setLocalSettings({ systemPrompt: '', glossary: '' }); + 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}`, + }; + + // Import the template as a new glossary via the API + 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); + } }; if (!isPro) { @@ -130,14 +166,20 @@ export default function ContextGlossaryPage() {
{PRESETS.map((p) => { const Icon = p.icon; + const isCreating = creatingPreset === p.key; return ( ); })}
+ +

+ {t('context.presets.hint')} +

{/* ── Context Instructions ──────────────────────────────── */} @@ -161,8 +211,8 @@ export default function ContextGlossaryPage() {

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