240 lines
8.9 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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&#10;récupérateur=heat recovery unit&#10;ventilo-connecteur=fan coil unit&#10;gaine=duct&#10;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>
);
}