Fix consistentie benamingen.

This commit is contained in:
2026-09-11 22:14:50 +02:00
parent deba8463b8
commit 38701bf06a
12 changed files with 1746 additions and 162 deletions
+132 -26
View File
@@ -799,8 +799,8 @@ require_once __DIR__ . '/includes/header.php';
<?= htmlspecialchars($usr['email']) ?>
</td>
<td class="py-3 px-4">
<span class="px-2 py-0.5 rounded-full text-[10px] font-bold <?= $usr['role'] === 'ADMIN' ? 'bg-purple-100 text-purple-800 dark:bg-purple-950/60 dark:text-purple-300' : 'bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300' ?>">
<?= $usr['role'] ?>
<span class="px-2 py-0.5 rounded text-[10px] font-bold <?= $usr['role'] === 'ADMIN' ? 'bg-indigo-50 dark:bg-indigo-950/40 text-indigo-700 dark:text-indigo-300 border border-indigo-200 dark:border-indigo-800/60' : 'bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300' ?>">
<?= $usr['role'] === 'ADMIN' ? 'Beheerder' : 'Collega' ?>
</span>
</td>
<td class="py-3 px-4">
@@ -1029,51 +1029,93 @@ require_once __DIR__ . '/includes/header.php';
<i data-lucide="sparkles" class="w-6 h-6"></i>
</div>
<div>
<h2 class="text-lg font-bold text-slate-900 dark:text-white tracking-tight">AI & Brainstorm Co-Pilot</h2>
<p class="text-xs text-slate-500 dark:text-slate-400">Intelligente assistentie voor post-it categorisatie, ideegeneratie en slimme planning</p>
<h2 class="text-lg font-bold text-slate-900 dark:text-white tracking-tight">AI & Brainstorm Co-Pilot (Google Gemini Pro)</h2>
<p class="text-xs text-slate-500 dark:text-slate-400">Intelligente assistentie voor post-it ontleding, categorisatie, ideegeneratie en slimme planning</p>
</div>
</div>
<span class="px-2.5 py-1 rounded-full <?= $aiSettings['has_key'] ? 'bg-teal-50 dark:bg-teal-950/40 text-teal-700 dark:text-teal-400 border border-teal-200 dark:border-teal-800/60' : 'bg-amber-50 dark:bg-amber-950/40 text-amber-700 dark:text-amber-400 border border-amber-200 dark:border-amber-800/60' ?> text-xs font-bold">
<?= $aiSettings['has_key'] ? 'AI Actief (v1.5)' : 'Release v1.5 Sleutel Vereist' ?>
</span>
<div class="flex items-center space-x-2">
<span class="px-2.5 py-1 rounded-full bg-emerald-50 dark:bg-emerald-950/40 text-emerald-700 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-800/60 text-xs font-bold flex items-center space-x-1">
<i data-lucide="shield-check" class="w-3.5 h-3.5"></i>
<span>Zero-Data Leakage</span>
</span>
<span class="px-2.5 py-1 rounded-full <?= $aiSettings['has_key'] ? 'bg-teal-50 dark:bg-teal-950/40 text-teal-700 dark:text-teal-400 border border-teal-200 dark:border-teal-800/60' : 'bg-amber-50 dark:bg-amber-950/40 text-amber-700 dark:text-amber-400 border border-amber-200 dark:border-amber-800/60' ?> text-xs font-bold">
<?= $aiSettings['has_key'] ? 'AI Actief (' . htmlspecialchars($aiSettings['ai_model']) . ')' : 'Sleutel Vereist' ?>
</span>
</div>
</div>
<form method="POST" action="/admin.php" class="space-y-4">
<!-- ZERO-DATA LEAKAGE PRIVACY FIREWALL BANNER -->
<div class="p-3.5 rounded-2xl bg-emerald-50/80 dark:bg-emerald-950/20 border border-emerald-200/80 dark:border-emerald-800/40 flex items-start space-x-3">
<div class="w-8 h-8 rounded-xl bg-emerald-100 dark:bg-emerald-900/40 text-emerald-700 dark:text-emerald-400 flex items-center justify-center flex-shrink-0 mt-0.5">
<i data-lucide="shield-check" class="w-4 h-4"></i>
</div>
<div class="text-xs space-y-1">
<strong class="text-emerald-950 dark:text-emerald-200 font-bold block text-xs">Strikte Zero-Data Leakage Privacy Garantie</strong>
<p class="text-emerald-800/90 dark:text-emerald-300/90 text-[11px] leading-relaxed">
Er wordt <strong>onder géén enkele voorwaarde data uit Jira tickets, Outlook e-mails of Microsoft Teams</strong> naar de AI verzonden. De ingebouwde privacy firewall onderschept en filtert automatisch ticketnummers, Atlassian-links, mailheaders en communicatietraces vóór verzending.
</p>
</div>
</div>
<form method="POST" action="/admin.php" id="adminAiForm" class="space-y-4">
<input type="hidden" name="action" value="save_ai_settings">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(get_csrf_token()) ?>">
<div class="p-3.5 rounded-2xl bg-amber-50/70 dark:bg-amber-950/20 border border-amber-200/70 dark:border-amber-800/40">
<label class="flex items-center space-x-2 text-xs text-amber-900 dark:text-amber-200 cursor-pointer select-none">
<div class="p-3.5 rounded-2xl bg-indigo-50/50 dark:bg-indigo-950/20 border border-indigo-200/60 dark:border-indigo-800/40">
<label class="flex items-center space-x-2.5 text-xs text-indigo-950 dark:text-indigo-200 cursor-pointer select-none">
<input type="checkbox" name="ai_enabled" value="1" <?= $aiSettings['ai_enabled'] ? 'checked' : '' ?> class="w-4 h-4 rounded text-indigo-600 focus:ring-indigo-500 border-slate-300 dark:border-slate-700">
<span class="font-bold">AI Co-Pilot inschakelen voor alle teamleden</span>
<span class="font-bold">AI Co-Pilot inschakelen voor alle gebruikers in de organisatie</span>
</label>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1.5">AI Provider & Model</label>
<select name="ai_model" class="w-full bg-slate-50 dark:bg-[#0c0f17] border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500">
<option value="gpt-4o-mini" <?= $aiSettings['ai_model'] === 'gpt-4o-mini' ? 'selected' : '' ?>>OpenAI GPT-4o-mini (Aanbevolen & Snel)</option>
<option value="gpt-4o" <?= $aiSettings['ai_model'] === 'gpt-4o' ? 'selected' : '' ?>>OpenAI GPT-4o (Krachtig & Uitgebreid)</option>
<option value="claude-3-5-sonnet-20241022" <?= $aiSettings['ai_model'] === 'claude-3-5-sonnet-20241022' ? 'selected' : '' ?>>Anthropic Claude 3.5 Sonnet</option>
<select name="ai_model" id="admin_ai_model" class="w-full bg-slate-50 dark:bg-[#0c0f17] border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-slate-900 dark:text-white text-xs focus:outline-none focus:ring-1 focus:ring-indigo-500">
<optgroup label="Google Gemini (Aanbevolen)">
<option value="gemini-1.5-pro" <?= ($aiSettings['ai_model'] === 'gemini-1.5-pro' || empty($aiSettings['ai_model']) || $aiSettings['ai_model'] === 'gemini') ? 'selected' : '' ?>>Google Gemini 1.5 Pro (Aanbevolen & Slim)</option>
<option value="gemini-2.0-flash" <?= $aiSettings['ai_model'] === 'gemini-2.0-flash' ? 'selected' : '' ?>>Google Gemini 2.0 Flash (Snel & Modern)</option>
<option value="gemini-1.5-flash" <?= $aiSettings['ai_model'] === 'gemini-1.5-flash' ? 'selected' : '' ?>>Google Gemini 1.5 Flash (Lichtgewicht)</option>
</optgroup>
<optgroup label="OpenAI">
<option value="gpt-4o-mini" <?= $aiSettings['ai_model'] === 'gpt-4o-mini' ? 'selected' : '' ?>>OpenAI GPT-4o-mini</option>
<option value="gpt-4o" <?= $aiSettings['ai_model'] === 'gpt-4o' ? 'selected' : '' ?>>OpenAI GPT-4o</option>
</optgroup>
</select>
<input type="hidden" name="ai_provider" id="admin_ai_provider" value="<?= strpos($aiSettings['ai_model'], 'gpt') !== false ? 'openai' : 'gemini' ?>">
</div>
<div>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1.5">OpenAI / Provider API Sleutel</label>
<input
type="password"
name="ai_api_key"
value="<?= $aiSettings['has_key'] ? '••••••••••••••••' : '' ?>"
placeholder="sk-proj-..."
class="w-full bg-slate-50 dark:bg-[#0c0f17] border border-slate-300 dark:border-slate-800 rounded-xl px-3 py-2 text-slate-900 dark:text-white text-xs font-mono placeholder-slate-400 focus:outline-none focus:ring-1 focus:ring-indigo-500"
>
<label class="block text-xs font-semibold text-slate-700 dark:text-slate-300 mb-1.5">Google Gemini / AI API Sleutel</label>
<div class="relative">
<input
type="password"
name="ai_api_key"
id="admin_ai_api_key"
value="<?= $aiSettings['has_key'] ? '••••••••••••••••' : '' ?>"
placeholder="AIzaSy... (Google) of sk-... (OpenAI)"
class="w-full bg-slate-50 dark:bg-[#0c0f17] border border-slate-300 dark:border-slate-800 rounded-xl pl-3 pr-9 py-2 text-slate-900 dark:text-white text-xs font-mono placeholder-slate-400 focus:outline-none focus:ring-1 focus:ring-indigo-500"
>
<button type="button" onclick="togglePasswordVisibility('admin_ai_api_key', this)" class="absolute right-2.5 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 dark:hover:text-slate-200">
<i data-lucide="eye" class="w-3.5 h-3.5"></i>
</button>
</div>
<span class="block text-[11px] text-slate-500 dark:text-slate-400 mt-1">Haal een gratis of betaalde Gemini API sleutel via <a href="https://aistudio.google.com/app/apikey" target="_blank" class="text-indigo-600 dark:text-indigo-400 hover:underline">Google AI Studio &rarr;</a></span>
</div>
</div>
<div class="flex justify-end pt-2">
<button type="submit" class="px-5 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold transition shadow-xs cursor-pointer">
AI Instellingen Opslaan
<!-- Live Test Resultaat Alert -->
<div id="adminAiTestResult" class="hidden p-3 rounded-xl text-xs"></div>
<div class="flex items-center justify-between pt-2 border-t border-slate-100 dark:border-slate-800/80">
<button type="button" onclick="testAdminAiConnection()" id="adminAiTestBtn" class="px-4 py-2 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-200 text-xs font-semibold transition cursor-pointer flex items-center space-x-2">
<i data-lucide="activity" class="w-3.5 h-3.5 text-indigo-500"></i>
<span id="adminAiTestBtnText">Test Verbinding</span>
</button>
<button type="submit" class="px-5 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-bold transition shadow-xs cursor-pointer flex items-center space-x-2">
<i data-lucide="save" class="w-3.5 h-3.5"></i>
<span>AI Instellingen Opslaan</span>
</button>
</div>
</form>
@@ -1530,6 +1572,70 @@ function closeJiraFilterModal() {
modal.classList.remove('flex');
}
document.addEventListener('DOMContentLoaded', function() {
const modelSelect = document.getElementById('admin_ai_model');
const providerInput = document.getElementById('admin_ai_provider');
if (modelSelect && providerInput) {
modelSelect.addEventListener('change', function() {
if (this.value.indexOf('gemini') !== -1) {
providerInput.value = 'gemini';
} else {
providerInput.value = 'openai';
}
});
}
});
async function testAdminAiConnection() {
const btn = document.getElementById('adminAiTestBtn');
const btnText = document.getElementById('adminAiTestBtnText');
const resultDiv = document.getElementById('adminAiTestResult');
const apiKey = document.getElementById('admin_ai_api_key').value.trim();
const model = document.getElementById('admin_ai_model').value;
const provider = document.getElementById('admin_ai_provider').value;
resultDiv.classList.add('hidden');
resultDiv.className = 'hidden p-3 rounded-xl text-xs';
btn.disabled = true;
btnText.innerText = 'Testen...';
try {
const payload = {
provider: provider,
model: model,
};
// Stuur alleen de ingevulde sleutel mee als het geen placeholder bolletjes zijn
if (apiKey && apiKey !== '••••••••••••••••') {
payload.api_key = apiKey;
}
const res = await fetch('/api/ai.php?action=test_connection', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
const data = await res.json();
resultDiv.classList.remove('hidden');
if (data.success) {
resultDiv.className = 'p-3 rounded-xl text-xs bg-emerald-50 dark:bg-emerald-950/30 border border-emerald-200 dark:border-emerald-800 text-emerald-800 dark:text-emerald-200 flex items-center space-x-2';
resultDiv.innerHTML = `<i data-lucide="check-circle" class="w-4 h-4 text-emerald-600 dark:text-emerald-400 flex-shrink-0"></i><span><strong>Verbinding Geslaagd!</strong> ${data.message}</span>`;
} else {
resultDiv.className = 'p-3 rounded-xl text-xs bg-rose-50 dark:bg-rose-950/30 border border-rose-200 dark:border-rose-800 text-rose-800 dark:text-rose-200 flex items-center space-x-2';
resultDiv.innerHTML = `<i data-lucide="alert-circle" class="w-4 h-4 text-rose-600 dark:text-rose-400 flex-shrink-0"></i><span><strong>Verbinding Mislukt:</strong> ${data.error || 'Onbekende fout opgetreden'}</span>`;
}
if (window.lucide) lucide.createIcons();
} catch (err) {
resultDiv.classList.remove('hidden');
resultDiv.className = 'p-3 rounded-xl text-xs bg-rose-50 dark:bg-rose-950/30 border border-rose-200 dark:border-rose-800 text-rose-800 dark:text-rose-200 flex items-center space-x-2';
resultDiv.innerHTML = `<i data-lucide="alert-circle" class="w-4 h-4 text-rose-600 dark:text-rose-400 flex-shrink-0"></i><span><strong>Netwerkfout:</strong> ${err.message}</span>`;
if (window.lucide) lucide.createIcons();
} finally {
btn.disabled = false;
btnText.innerText = 'Test Verbinding';
}
}
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeJiraFilterModal();