Files
leocrm/frontend/src/pages/ProactiveAISettings.tsx
T
Agent Zero 26b5ae9a0d 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.
2026-08-26 07:12:27 +02:00

203 lines
8.8 KiB
TypeScript

import { useTranslation } from 'react-i18next';
import { useProactiveSettings } from '@/api/aiProactive';
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">{t('proactiveAI.loading')}</div>
</div>
);
}
const toggleCategory = async (cat: string) => {
const current = settings.suggestion_categories || [];
const newCats = current.includes(cat)
? current.filter((c: string) => c !== cat)
: [...current, cat];
await update({ suggestion_categories: newCats });
};
return (
<div className="max-w-2xl space-y-6">
{/* 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">{t('proactiveAI.title')}</h3>
<p className="text-sm text-gray-500">{t('proactiveAI.toggleDescription')}</p>
</div>
<button
onClick={() => update({ enabled: !settings.enabled })}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
settings.enabled ? 'bg-blue-500' : 'bg-gray-300'
}`}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
settings.enabled ? 'translate-x-6' : 'translate-x-1'
}`}
/>
</button>
</div>
{/* Categories */}
<div className="p-4 bg-white rounded-lg border border-gray-200">
<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">
{categoryKeys.map((key) => {
const checked = settings.suggestion_categories?.includes(key);
return (
<label
key={key}
className={`flex items-center gap-2 p-3 rounded-lg border cursor-pointer transition-colors ${
checked ? 'bg-blue-50 border-blue-300' : 'bg-gray-50 border-gray-200 hover:bg-gray-100'
}`}
>
<input
type="checkbox"
checked={checked || false}
onChange={() => toggleCategory(key)}
className="w-4 h-4 rounded text-blue-500 focus:ring-blue-400"
/>
<span className="text-sm text-gray-700">{t(`proactiveAI.categories.${key}`)}</span>
</label>
);
})}
</div>
</div>
{/* 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">{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">{t('proactiveAI.confidenceDescription')}</p>
<input
type="range"
min="0"
max="1"
step="0.05"
value={settings.confidence_threshold || 0.5}
onChange={(e) => update({ confidence_threshold: parseFloat(e.target.value) })}
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>{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">{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">{t('proactiveAI.rateLimitDescription')}</p>
<input
type="range"
min="5"
max="60"
step="5"
value={settings.rate_limit_seconds || 10}
onChange={(e) => update({ rate_limit_seconds: parseInt(e.target.value) })}
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>5s</span>
<span>60s</span>
</div>
</div>
{/* Model Selection */}
<div className="p-4 bg-white rounded-lg border border-gray-200">
<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"
>
<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>
{/* Heartbeat Configuration */}
<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">{t('proactiveAI.heartbeatTitle')}</h3>
<p className="text-sm text-gray-500">{t('proactiveAI.heartbeatDescription')}</p>
</div>
<button
onClick={() => update({ heartbeat_enabled: !settings.heartbeat_enabled })}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
settings.heartbeat_enabled ? 'bg-blue-500' : 'bg-gray-300'
}`}
role="switch"
aria-checked={settings.heartbeat_enabled}
aria-label={t('proactiveAI.heartbeatEnable')}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
settings.heartbeat_enabled ? 'translate-x-6' : 'translate-x-1'
}`}
/>
</button>
</div>
{settings.heartbeat_enabled && (
<>
<div className="mb-4">
<div className="flex items-center justify-between mb-2">
<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>
</div>
<input
type="range"
min="60"
max="1800"
step="60"
value={settings.heartbeat_interval_seconds || 300}
onChange={(e) => update({ heartbeat_interval_seconds: parseInt(e.target.value) })}
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>1min</span>
<span>30min</span>
</div>
</div>
<div>
<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={t('proactiveAI.defaultRoomName')}
/>
<p className="text-xs text-gray-400 mt-1">{t('proactiveAI.targetRoomDescription')}</p>
</div>
</>
)}
</div>
</div>
);
}