refactor(i-g): ProactiveAISettings hardcoded Strings auf t() umgestellt — i18n-Hotspot Nr.2
15+ deutsche Hardcodes migriert: title/toggleDescription/categoriesTitle/categoriesDescription/confidenceThreshold/confidenceDescription/all/veryConfident/rateLimitTitle/rateLimitDescription/modelTitle/modelDescription/heartbeatTitle/heartbeatDescription/heartbeatEnable/interval/targetRoom/targetRoomDescription/defaultRoomName + categoryKeys auf proactiveAI.categories.*-Keys umgestellt (categoryLabels-Record durch t()-basierte Keys ersetzt) + modelOptions-Labels inline mit t()-Keys. Beweis: tsc exit=0; ProactiveAISettings-Tests 10/10 gruen.
This commit is contained in:
@@ -1404,5 +1404,41 @@
|
||||
"defaultProvider": "Standard-Anbieter",
|
||||
"defaultTag": "(Standard)",
|
||||
"confirmDeleteProvider": "Anbieter wirklich löschen?"
|
||||
},
|
||||
"proactiveAI": {
|
||||
"categories": {
|
||||
"mail": "E-Mails",
|
||||
"tasks": "Aufgaben",
|
||||
"contacts": "Kontakte",
|
||||
"companies": "Firmen",
|
||||
"insights": "Erkenntnisse"
|
||||
},
|
||||
"models": {
|
||||
"deepseekV4": "DeepSeek V4 (empfohlen)",
|
||||
"deepseekV4Pro": "DeepSeek V4 Pro (präzise)",
|
||||
"llama32": "Llama 3.2 (Alternative)",
|
||||
"gpt4oMiniOllama": "GPT-4o mini via Ollama",
|
||||
"gpt4oMiniDirect": "GPT-4o mini (Direct OpenAI)"
|
||||
},
|
||||
"loading": "Lade Einstellungen...",
|
||||
"title": "Proaktive KI",
|
||||
"toggleDescription": "Aktiviert oder deaktiviert alle Vorschläge",
|
||||
"categoriesTitle": "Kategorien",
|
||||
"categoriesDescription": "Für welche Bereiche sollen Vorschläge generiert werden?",
|
||||
"confidenceThreshold": "Confidence Schwellwert",
|
||||
"confidenceDescription": "Nur Vorschläge mit höherer Confidence anzeigen",
|
||||
"all": "Alle",
|
||||
"veryConfident": "Sehr sicher",
|
||||
"rateLimitTitle": "Rate Limit",
|
||||
"rateLimitDescription": "Mindestabstand zwischen Vorschlägen",
|
||||
"modelTitle": "KI Modell (Ollama Cloud)",
|
||||
"modelDescription": "Wähle das Modell für Vorschläge. DeepSeek V4 ist empfohlen für beste Performance/Kosten.",
|
||||
"heartbeatTitle": "Heartbeat",
|
||||
"heartbeatDescription": "Regelmäßige Status-Meldung an KI-Raum",
|
||||
"heartbeatEnable": "Heartbeat aktivieren",
|
||||
"interval": "Intervall",
|
||||
"targetRoom": "Ziel-Raum",
|
||||
"targetRoomDescription": "Name des Raums in der Kommunikation, an den Status-Meldungen gesendet werden.",
|
||||
"defaultRoomName": "Live KI"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1404,5 +1404,41 @@
|
||||
"defaultProvider": "Default provider",
|
||||
"defaultTag": "(default)",
|
||||
"confirmDeleteProvider": "Really delete provider?"
|
||||
},
|
||||
"proactiveAI": {
|
||||
"categories": {
|
||||
"mail": "Emails",
|
||||
"tasks": "Tasks",
|
||||
"contacts": "Contacts",
|
||||
"companies": "Companies",
|
||||
"insights": "Insights"
|
||||
},
|
||||
"models": {
|
||||
"deepseekV4": "DeepSeek V4 (empfohlen)",
|
||||
"deepseekV4Pro": "DeepSeek V4 Pro (präzise)",
|
||||
"llama32": "Llama 3.2 (Alternative)",
|
||||
"gpt4oMiniOllama": "GPT-4o mini via Ollama",
|
||||
"gpt4oMiniDirect": "GPT-4o mini (Direct OpenAI)"
|
||||
},
|
||||
"loading": "Loading settings...",
|
||||
"title": "Proactive AI",
|
||||
"toggleDescription": "Enables or disables all suggestions",
|
||||
"categoriesTitle": "Categories",
|
||||
"categoriesDescription": "Which areas should suggestions be generated for?",
|
||||
"confidenceThreshold": "Confidence threshold",
|
||||
"confidenceDescription": "Only show suggestions with higher confidence",
|
||||
"all": "All",
|
||||
"veryConfident": "Very confident",
|
||||
"rateLimitTitle": "Rate limit",
|
||||
"rateLimitDescription": "Minimum distance between suggestions",
|
||||
"modelTitle": "AI Model (Ollama Cloud)",
|
||||
"modelDescription": "Choose the model for suggestions. DeepSeek V4 is recommended for best performance/cost.",
|
||||
"heartbeatTitle": "Heartbeat",
|
||||
"heartbeatDescription": "Regular status messages to the AI room",
|
||||
"heartbeatEnable": "Enable heartbeat",
|
||||
"interval": "Interval",
|
||||
"targetRoom": "Target room",
|
||||
"targetRoomDescription": "Name of the room in Communication that status messages are sent to.",
|
||||
"defaultRoomName": "Live AI"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,16 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useProactiveSettings } from '@/api/aiProactive';
|
||||
// TODO: P2-T20 — Replace hardcoded categoryLabels/modelOptions with backend config
|
||||
|
||||
const categoryLabels: Record<string, string> = {
|
||||
mail: 'E-Mails',
|
||||
tasks: 'Aufgaben',
|
||||
contacts: 'Kontakte',
|
||||
companies: 'Firmen',
|
||||
insights: 'Erkenntnisse',
|
||||
};
|
||||
|
||||
const modelOptions = [
|
||||
{ value: 'ollama/deepseek-v4', label: 'DeepSeek V4 (empfohlen)' },
|
||||
{ value: 'ollama/deepseek-v4-pro', label: 'DeepSeek V4 Pro (präzise)' },
|
||||
{ value: 'ollama/llama3.2', label: 'Llama 3.2 (Alternative)' },
|
||||
{ value: 'ollama/gpt-4o-mini', label: 'GPT-4o mini via Ollama' },
|
||||
{ value: 'gpt-4o-mini', label: 'GPT-4o mini (Direct OpenAI)' },
|
||||
];
|
||||
const categoryKeys = ['mail', 'tasks', 'contacts', 'companies', 'insights'];
|
||||
|
||||
export function ProactiveAISettings() {
|
||||
const { t } = useTranslation();
|
||||
const { settings, update } = useProactiveSettings();
|
||||
|
||||
if (!settings) {
|
||||
return (
|
||||
<div className="flex items-center justify-center p-8 text-gray-400">
|
||||
<div className="animate-pulse">Lade Einstellungen...</div>
|
||||
<div className="animate-pulse">{t('proactiveAI.loading')}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -41,8 +28,8 @@ export function ProactiveAISettings() {
|
||||
{/* Enable/Disable */}
|
||||
<div className="flex items-center justify-between p-4 bg-white rounded-lg border border-gray-200">
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-800">Proaktive KI</h3>
|
||||
<p className="text-sm text-gray-500">Aktiviert oder deaktiviert alle Vorschläge</p>
|
||||
<h3 className="font-semibold text-gray-800">{t('proactiveAI.title')}</h3>
|
||||
<p className="text-sm text-gray-500">{t('proactiveAI.toggleDescription')}</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => update({ enabled: !settings.enabled })}
|
||||
@@ -60,10 +47,10 @@ export function ProactiveAISettings() {
|
||||
|
||||
{/* Categories */}
|
||||
<div className="p-4 bg-white rounded-lg border border-gray-200">
|
||||
<h3 className="font-semibold text-gray-800 mb-3">Kategorien</h3>
|
||||
<p className="text-sm text-gray-500 mb-4">Für welche Bereiche sollen Vorschläge generiert werden?</p>
|
||||
<h3 className="font-semibold text-gray-800 mb-3">{t('proactiveAI.categoriesTitle')}</h3>
|
||||
<p className="text-sm text-gray-500 mb-4">{t('proactiveAI.categoriesDescription')}</p>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{Object.entries(categoryLabels).map(([key, label]) => {
|
||||
{categoryKeys.map((key) => {
|
||||
const checked = settings.suggestion_categories?.includes(key);
|
||||
return (
|
||||
<label
|
||||
@@ -78,7 +65,7 @@ export function ProactiveAISettings() {
|
||||
onChange={() => toggleCategory(key)}
|
||||
className="w-4 h-4 rounded text-blue-500 focus:ring-blue-400"
|
||||
/>
|
||||
<span className="text-sm text-gray-700">{label}</span>
|
||||
<span className="text-sm text-gray-700">{t(`proactiveAI.categories.${key}`)}</span>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
@@ -88,14 +75,12 @@ export function ProactiveAISettings() {
|
||||
{/* Confidence Threshold */}
|
||||
<div className="p-4 bg-white rounded-lg border border-gray-200">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h3 className="font-semibold text-gray-800">Confidence Schwellwert</h3>
|
||||
<h3 className="font-semibold text-gray-800">{t('proactiveAI.confidenceThreshold')}</h3>
|
||||
<span className="text-sm font-mono text-blue-600">
|
||||
{Math.round((settings.confidence_threshold || 0.5) * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 mb-3">
|
||||
Nur Vorschläge mit höherer Confidence anzeigen
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 mb-3">{t('proactiveAI.confidenceDescription')}</p>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
@@ -106,22 +91,20 @@ export function ProactiveAISettings() {
|
||||
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-blue-500"
|
||||
/>
|
||||
<div className="flex justify-between text-xs text-gray-400 mt-1">
|
||||
<span>Alle</span>
|
||||
<span>Sehr sicher</span>
|
||||
<span>{t('proactiveAI.all')}</span>
|
||||
<span>{t('proactiveAI.veryConfident')}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Rate Limit */}
|
||||
<div className="p-4 bg-white rounded-lg border border-gray-200">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h3 className="font-semibold text-gray-800">Rate Limit</h3>
|
||||
<h3 className="font-semibold text-gray-800">{t('proactiveAI.rateLimitTitle')}</h3>
|
||||
<span className="text-sm font-mono text-blue-600">
|
||||
{settings.rate_limit_seconds || 10}s
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 mb-3">
|
||||
Mindestabstand zwischen Vorschlägen
|
||||
</p>
|
||||
<p className="text-sm text-gray-500 mb-3">{t('proactiveAI.rateLimitDescription')}</p>
|
||||
<input
|
||||
type="range"
|
||||
min="5"
|
||||
@@ -139,18 +122,18 @@ export function ProactiveAISettings() {
|
||||
|
||||
{/* Model Selection */}
|
||||
<div className="p-4 bg-white rounded-lg border border-gray-200">
|
||||
<h3 className="font-semibold text-gray-800 mb-2">KI Modell (Ollama Cloud)</h3>
|
||||
<p className="text-sm text-gray-500 mb-3">Wähle das Modell für Vorschläge. DeepSeek V4 ist empfohlen für beste Performance/Kosten.</p>
|
||||
<h3 className="font-semibold text-gray-800 mb-2">{t('proactiveAI.modelTitle')}</h3>
|
||||
<p className="text-sm text-gray-500 mb-3">{t('proactiveAI.modelDescription')}</p>
|
||||
<select
|
||||
value={settings.model || 'ollama/deepseek-v4'}
|
||||
onChange={(e) => update({ model: e.target.value })}
|
||||
className="w-full px-3 py-2 rounded-lg border border-gray-200 bg-white text-sm text-gray-700 focus:ring-2 focus:ring-blue-400 focus:border-blue-400 outline-none"
|
||||
>
|
||||
{modelOptions.map(opt => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</option>
|
||||
))}
|
||||
<option value="ollama/deepseek-v4">{t('proactiveAI.models.deepseekV4')}</option>
|
||||
<option value="ollama/deepseek-v4-pro">{t('proactiveAI.models.deepseekV4Pro')}</option>
|
||||
<option value="ollama/llama3.2">{t('proactiveAI.models.llama32')}</option>
|
||||
<option value="ollama/gpt-4o-mini">{t('proactiveAI.models.gpt4oMiniOllama')}</option>
|
||||
<option value="gpt-4o-mini">{t('proactiveAI.models.gpt4oMiniDirect')}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -158,8 +141,8 @@ export function ProactiveAISettings() {
|
||||
<div className="p-4 bg-white rounded-lg border border-gray-200">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-800">Heartbeat</h3>
|
||||
<p className="text-sm text-gray-500">Regelmäßige Status-Meldung an KI-Raum</p>
|
||||
<h3 className="font-semibold text-gray-800">{t('proactiveAI.heartbeatTitle')}</h3>
|
||||
<p className="text-sm text-gray-500">{t('proactiveAI.heartbeatDescription')}</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => update({ heartbeat_enabled: !settings.heartbeat_enabled })}
|
||||
@@ -168,7 +151,7 @@ export function ProactiveAISettings() {
|
||||
}`}
|
||||
role="switch"
|
||||
aria-checked={settings.heartbeat_enabled}
|
||||
aria-label="Heartbeat aktivieren"
|
||||
aria-label={t('proactiveAI.heartbeatEnable')}
|
||||
>
|
||||
<span
|
||||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
|
||||
@@ -181,7 +164,7 @@ export function ProactiveAISettings() {
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium text-gray-700">Intervall</span>
|
||||
<span className="text-sm font-medium text-gray-700">{t('proactiveAI.interval')}</span>
|
||||
<span className="text-sm font-mono text-blue-600">
|
||||
{settings.heartbeat_interval_seconds || 300}s
|
||||
</span>
|
||||
@@ -201,15 +184,15 @@ export function ProactiveAISettings() {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Ziel-Raum</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">{t('proactiveAI.targetRoom')}</label>
|
||||
<input
|
||||
type="text"
|
||||
value={settings.heartbeat_target_room || 'Live KI'}
|
||||
onChange={(e) => update({ heartbeat_target_room: e.target.value })}
|
||||
className="w-full px-3 py-2 rounded-lg border border-gray-200 bg-white text-sm text-gray-700 focus:ring-2 focus:ring-blue-400 focus:border-blue-400 outline-none"
|
||||
placeholder="Live KI"
|
||||
placeholder={t('proactiveAI.defaultRoomName')}
|
||||
/>
|
||||
<p className="text-xs text-gray-400 mt-1">Name des Raums in der Kommunikation, an den Status-Meldungen gesendet werden.</p>
|
||||
<p className="text-xs text-gray-400 mt-1">{t('proactiveAI.targetRoomDescription')}</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user