240 lines
8.9 KiB
TypeScript
240 lines
8.9 KiB
TypeScript
"use client";
|
||
|
||
import { useState, useEffect } from "react";
|
||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||
import { Button } from "@/components/ui/button";
|
||
import { Textarea } from "@/components/ui/textarea";
|
||
import { Badge } from "@/components/ui/badge";
|
||
import { useTranslationStore } from "@/lib/store";
|
||
import { Save, Loader2, Brain, BookOpen, Sparkles, Trash2 } from "lucide-react";
|
||
|
||
export default function ContextGlossaryPage() {
|
||
const { settings, updateSettings, applyPreset, clearContext } = useTranslationStore();
|
||
const [isSaving, setIsSaving] = useState(false);
|
||
|
||
const [localSettings, setLocalSettings] = useState({
|
||
systemPrompt: settings.systemPrompt,
|
||
glossary: settings.glossary,
|
||
});
|
||
|
||
useEffect(() => {
|
||
setLocalSettings({
|
||
systemPrompt: settings.systemPrompt,
|
||
glossary: settings.glossary,
|
||
});
|
||
}, [settings]);
|
||
|
||
const handleSave = async () => {
|
||
setIsSaving(true);
|
||
try {
|
||
updateSettings(localSettings);
|
||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||
} finally {
|
||
setIsSaving(false);
|
||
}
|
||
};
|
||
|
||
const handleApplyPreset = (preset: 'hvac' | 'it' | 'legal' | 'medical') => {
|
||
applyPreset(preset);
|
||
// Need to get the updated values from the store after applying preset
|
||
setTimeout(() => {
|
||
setLocalSettings({
|
||
systemPrompt: useTranslationStore.getState().settings.systemPrompt,
|
||
glossary: useTranslationStore.getState().settings.glossary,
|
||
});
|
||
}, 0);
|
||
};
|
||
|
||
const handleClear = () => {
|
||
clearContext();
|
||
setLocalSettings({
|
||
systemPrompt: "",
|
||
glossary: "",
|
||
});
|
||
};
|
||
|
||
// Check which LLM providers are configured
|
||
const isOllamaConfigured = settings.ollamaUrl && settings.ollamaModel;
|
||
const isOpenAIConfigured = !!settings.openaiApiKey;
|
||
const isWebLLMAvailable = typeof window !== 'undefined' && 'gpu' in navigator;
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<div>
|
||
<h1 className="text-3xl font-bold text-white">Context & Glossary</h1>
|
||
<p className="text-zinc-400 mt-1">
|
||
Configure translation context and glossary for LLM-based providers.
|
||
</p>
|
||
|
||
{/* LLM Provider Status */}
|
||
<div className="flex flex-wrap gap-2 mt-3">
|
||
<Badge
|
||
variant="outline"
|
||
className={`${isOllamaConfigured ? 'border-green-500 text-green-400' : 'border-zinc-600 text-zinc-500'}`}
|
||
>
|
||
🤖 Ollama {isOllamaConfigured ? '✓' : '○'}
|
||
</Badge>
|
||
<Badge
|
||
variant="outline"
|
||
className={`${isOpenAIConfigured ? 'border-green-500 text-green-400' : 'border-zinc-600 text-zinc-500'}`}
|
||
>
|
||
🧠 OpenAI {isOpenAIConfigured ? '✓' : '○'}
|
||
</Badge>
|
||
<Badge
|
||
variant="outline"
|
||
className={`${isWebLLMAvailable ? 'border-green-500 text-green-400' : 'border-zinc-600 text-zinc-500'}`}
|
||
>
|
||
💻 WebLLM {isWebLLMAvailable ? '✓' : '○'}
|
||
</Badge>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Info Banner */}
|
||
<div className="p-4 rounded-lg bg-teal-500/10 border border-teal-500/30">
|
||
<p className="text-teal-400 text-sm flex items-center gap-2">
|
||
<Sparkles className="h-4 w-4" />
|
||
<span>
|
||
<strong>Context & Glossary</strong> settings apply to all LLM providers:
|
||
<strong> Ollama</strong>, <strong>OpenAI</strong>, and <strong>WebLLM</strong>.
|
||
Use them to improve translation quality with domain-specific instructions.
|
||
</span>
|
||
</p>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||
{/* Left Column */}
|
||
<div className="space-y-6">
|
||
{/* System Prompt */}
|
||
<Card className="border-zinc-800 bg-zinc-900/50">
|
||
<CardHeader>
|
||
<CardTitle className="text-white flex items-center gap-2">
|
||
<Brain className="h-5 w-5 text-teal-400" />
|
||
System Prompt
|
||
</CardTitle>
|
||
<CardDescription>
|
||
Instructions for the LLM to follow during translation.
|
||
Works with Ollama, OpenAI, and WebLLM.
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<Textarea
|
||
id="system-prompt"
|
||
value={localSettings.systemPrompt}
|
||
onChange={(e) =>
|
||
setLocalSettings({ ...localSettings, systemPrompt: e.target.value })
|
||
}
|
||
placeholder="Example: You are translating technical HVAC documents. Use precise engineering terminology. Maintain consistency with industry standards..."
|
||
className="bg-zinc-800 border-zinc-700 text-white placeholder:text-zinc-500 min-h-[200px] resize-y"
|
||
/>
|
||
<p className="text-xs text-zinc-500">
|
||
💡 Tip: Include domain context, tone preferences, or specific terminology rules.
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Presets */}
|
||
<Card className="border-zinc-800 bg-zinc-900/50">
|
||
<CardHeader>
|
||
<CardTitle className="text-white">Quick Presets</CardTitle>
|
||
<CardDescription>
|
||
Load pre-configured prompts & glossaries for common domains.
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<div className="grid grid-cols-2 gap-2">
|
||
<Button
|
||
variant="outline"
|
||
onClick={() => handleApplyPreset("hvac")}
|
||
className="border-zinc-700 text-zinc-300 hover:bg-zinc-800 hover:text-teal-400 justify-start"
|
||
>
|
||
🔧 HVAC / Engineering
|
||
</Button>
|
||
<Button
|
||
variant="outline"
|
||
onClick={() => handleApplyPreset("it")}
|
||
className="border-zinc-700 text-zinc-300 hover:bg-zinc-800 hover:text-teal-400 justify-start"
|
||
>
|
||
💻 IT / Software
|
||
</Button>
|
||
<Button
|
||
variant="outline"
|
||
onClick={() => handleApplyPreset("legal")}
|
||
className="border-zinc-700 text-zinc-300 hover:bg-zinc-800 hover:text-teal-400 justify-start"
|
||
>
|
||
⚖️ Legal / Contracts
|
||
</Button>
|
||
<Button
|
||
variant="outline"
|
||
onClick={() => handleApplyPreset("medical")}
|
||
className="border-zinc-700 text-zinc-300 hover:bg-zinc-800 hover:text-teal-400 justify-start"
|
||
>
|
||
🏥 Medical / Healthcare
|
||
</Button>
|
||
</div>
|
||
<Button
|
||
variant="ghost"
|
||
onClick={handleClear}
|
||
className="w-full mt-3 text-red-400 hover:text-red-300 hover:bg-red-500/10"
|
||
>
|
||
<Trash2 className="h-4 w-4 mr-2" />
|
||
Clear All
|
||
</Button>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* Right Column */}
|
||
<div className="space-y-6">
|
||
{/* Glossary */}
|
||
<Card className="border-zinc-800 bg-zinc-900/50">
|
||
<CardHeader>
|
||
<CardTitle className="text-white flex items-center gap-2">
|
||
<BookOpen className="h-5 w-5 text-teal-400" />
|
||
Technical Glossary
|
||
</CardTitle>
|
||
<CardDescription>
|
||
Define specific term translations. Format: source=target (one per line).
|
||
</CardDescription>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<Textarea
|
||
id="glossary"
|
||
value={localSettings.glossary}
|
||
onChange={(e) =>
|
||
setLocalSettings({ ...localSettings, glossary: e.target.value })
|
||
}
|
||
placeholder="pression statique=static pressure récupérateur=heat recovery unit ventilo-connecteur=fan coil unit gaine=duct diffuseur=diffuser"
|
||
className="bg-zinc-800 border-zinc-700 text-white placeholder:text-zinc-500 min-h-[280px] resize-y font-mono text-sm"
|
||
/>
|
||
<p className="text-xs text-zinc-500">
|
||
💡 The glossary is included in the system prompt to guide translations.
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Save Button */}
|
||
<div className="flex justify-end">
|
||
<Button
|
||
onClick={handleSave}
|
||
disabled={isSaving}
|
||
className="bg-teal-600 hover:bg-teal-700 text-white px-8"
|
||
>
|
||
{isSaving ? (
|
||
<>
|
||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||
Saving...
|
||
</>
|
||
) : (
|
||
<>
|
||
<Save className="mr-2 h-4 w-4" />
|
||
Save Settings
|
||
</>
|
||
)}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|