Files
leocrm/app/plugins/builtins/ai_proactive/frontend/AISettings.tsx
T
Agent Zero 8cebb4f4e9 feat: unified_search + ai_proactive plugins with Ollama Cloud DeepSeek V4
- unified_search: Hybride Suche (PostgreSQL FTS + pgvector + RRF Fusion)
  - 5 Search Providers (Contact, Company, Mail, File, Event)
  - KI Query Understanding (Fuzzy, Facetten via LiteLLM)
  - DMS Text-Extraction (PDF, DOCX, XLSX, PPTX)
  - Embedding Pipeline (ollama/nomic-embed-text, 768 Dim)
  - Background Jobs für Indexierung
  - Plugin-basierte Provider Registry

- ai_proactive: Proaktiver KI-Agent
  - Context-Tracking (Frontend → Backend → Event Bus)
  - Proactive Engine mit LLM Suggestion-Generierung
  - SSE Real-time Push an Frontend
  - 6 AI Tools für Tool Registry
  - Rate-Limiting + User Settings
  - Deep Analysis Background Jobs

- Frontend Integration:
  - useAIContext Hook, SuggestionSidebar, SuggestionBadge
  - ProactiveAISettings Page, Search API Client
  - Globale Suche auf neue API umgestellt

- Tests: test_unified_search.py + test_ai_proactive.py (alle bestanden)
- Config: Ollama Cloud DeepSeek V4 als Default, konfigurierbar
- Dependencies: PyMuPDF, python-docx, python-pptx, pgvector
- Bugfixes: notification type_key length, migration IF NOT EXISTS
2026-07-18 11:21:51 +02:00

158 lines
6.0 KiB
TypeScript

import { useProactiveSettings } from './api';
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)' },
];
export function AISettings() {
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>
);
}
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">Proaktive KI</h3>
<p className="text-sm text-gray-500">Aktiviert oder deaktiviert alle Vorschläge</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">Kategorien</h3>
<p className="text-sm text-gray-500 mb-4">Für welche Bereiche sollen Vorschläge generiert werden?</p>
<div className="grid grid-cols-2 gap-3">
{Object.entries(categoryLabels).map(([key, label]) => {
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">{label}</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">Confidence Schwellwert</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>
<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>Alle</span>
<span>Sehr sicher</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>
<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>
<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">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>
<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>
))}
</select>
</div>
</div>
);
}