Fix consistentie benamingen.
This commit is contained in:
+245
-1
@@ -132,6 +132,48 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
exit;
|
||||
}
|
||||
|
||||
// 4b. Persoonlijke Google Gemini / AI instellingen opslaan
|
||||
elseif ($action === 'save_personal_ai') {
|
||||
$aiEnabled = isset($_POST['ai_enabled']) ? 1 : 0;
|
||||
$aiProvider = trim($_POST['ai_provider'] ?? 'gemini');
|
||||
$aiModel = trim($_POST['ai_model'] ?? 'gemini-1.5-pro');
|
||||
$apiKeyIn = trim($_POST['ai_api_key'] ?? '');
|
||||
|
||||
// Haal eventueel bestaande integratie op
|
||||
$stmtEx = $db->prepare("SELECT id, access_token, settings FROM integrations WHERE user_id = ? AND provider IN ('GEMINI', 'AI') LIMIT 1");
|
||||
$stmtEx->execute([$userId]);
|
||||
$existing = $stmtEx->fetch();
|
||||
|
||||
$tokenToSave = $existing['access_token'] ?? null;
|
||||
if ($apiKeyIn !== '' && $apiKeyIn !== '••••••••••••••••') {
|
||||
$tokenToSave = fociz_encrypt($apiKeyIn);
|
||||
}
|
||||
|
||||
$settingsJson = json_encode([
|
||||
'provider' => $aiProvider,
|
||||
'model' => $aiModel,
|
||||
]);
|
||||
|
||||
if ($existing) {
|
||||
$stmtUp = $db->prepare("UPDATE integrations SET provider = 'GEMINI', is_enabled = ?, access_token = ?, settings = ?, last_sync_at = NOW() WHERE id = ?");
|
||||
$stmtUp->execute([$aiEnabled, $tokenToSave, $settingsJson, $existing['id']]);
|
||||
} else {
|
||||
$stmtIn = $db->prepare("INSERT INTO integrations (user_id, provider, is_enabled, access_token, settings, last_sync_at) VALUES (?, 'GEMINI', ?, ?, ?, NOW())");
|
||||
$stmtIn->execute([$userId, $aiEnabled, $tokenToSave, $settingsJson]);
|
||||
}
|
||||
|
||||
$message = "Uw persoonlijke AI instellingen zijn succesvol opgeslagen.";
|
||||
$messageType = 'success';
|
||||
}
|
||||
|
||||
// 4c. Persoonlijke AI sleutel ontkoppelen / verwijderen
|
||||
elseif ($action === 'disconnect_personal_ai') {
|
||||
$stmtDel = $db->prepare("DELETE FROM integrations WHERE user_id = ? AND provider IN ('GEMINI', 'AI')");
|
||||
$stmtDel->execute([$userId]);
|
||||
$message = "Uw persoonlijke AI sleutel is ontkoppeld. Fociz gebruikt nu de centrale instellingen van de organisatie.";
|
||||
$messageType = 'success';
|
||||
}
|
||||
|
||||
// 5. Notificatievoorkeuren opslaan
|
||||
elseif ($action === 'save_notifications') {
|
||||
$settings = [
|
||||
@@ -278,6 +320,14 @@ $appConfig = microsoft_get_app_config();
|
||||
// Notificatie-instellingen
|
||||
$notifSettings = get_user_notification_settings($userId);
|
||||
|
||||
// AI Co-Pilot & Google Gemini instellingen voor gebruiker
|
||||
$userAiSettings = get_ai_settings($userId);
|
||||
$stmtAiInt = $db->prepare("SELECT id, access_token, is_enabled, settings FROM integrations WHERE user_id = ? AND provider IN ('GEMINI', 'AI') LIMIT 1");
|
||||
$stmtAiInt->execute([$userId]);
|
||||
$userAiIntRow = $stmtAiInt->fetch();
|
||||
$hasPersonalAiKey = !empty($userAiIntRow['access_token']);
|
||||
$userAiRowSettings = json_decode($userAiIntRow['settings'] ?? '{}', true) ?: [];
|
||||
|
||||
// Thema ophalen
|
||||
$userTheme = get_user_theme($userId);
|
||||
|
||||
@@ -849,6 +899,138 @@ require_once __DIR__ . '/includes/header.php';
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Sectie 2b: Mijn AI Co-Pilot & Google Gemini Pro -->
|
||||
<div class="fociz-card p-6 sm:p-7">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pb-6 border-b border-slate-100 dark:border-slate-800/80">
|
||||
<div class="flex items-center space-x-3.5">
|
||||
<div class="w-12 h-12 rounded-2xl bg-indigo-50 dark:bg-indigo-500/10 border border-indigo-200 dark:border-indigo-500/20 flex items-center justify-center text-indigo-600 dark:text-indigo-400 font-bold shadow-xs">
|
||||
<i data-lucide="sparkles" class="w-6 h-6"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<h2 class="text-base sm:text-lg font-bold text-slate-900 dark:text-white tracking-tight">AI Co-Pilot & Google Gemini Pro</h2>
|
||||
<?php if ($hasPersonalAiKey): ?>
|
||||
<span class="px-2.5 py-0.5 rounded-full bg-indigo-50 dark:bg-indigo-500/15 border border-indigo-200 dark:border-indigo-500/30 text-indigo-700 dark:text-indigo-400 text-[10px] font-semibold">
|
||||
Eigen Sleutel
|
||||
</span>
|
||||
<?php elseif (!empty($userAiSettings['has_key'])): ?>
|
||||
<span class="px-2.5 py-0.5 rounded-full bg-teal-50 dark:bg-teal-500/15 border border-teal-200 dark:border-teal-500/30 text-teal-700 dark:text-teal-400 text-[10px] font-semibold">
|
||||
Organisatiesleutel Actief
|
||||
</span>
|
||||
<?php else: ?>
|
||||
<span class="px-2.5 py-0.5 rounded-full bg-amber-50 dark:bg-amber-500/15 border border-amber-200 dark:border-amber-500/30 text-amber-700 dark:text-amber-400 text-[10px] font-semibold">
|
||||
Geen Sleutel
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<p class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">Koppel uw eigen Google Gemini Pro account of gebruik de centrale organisatieconfiguratie</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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 Actief</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Privacy Shield Banner -->
|
||||
<div class="mt-6 p-3.5 rounded-2xl bg-emerald-50/70 dark:bg-emerald-950/20 border border-emerald-200/70 dark:border-emerald-800/40 flex items-start space-x-3">
|
||||
<div class="w-7 h-7 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">
|
||||
<strong class="text-emerald-900 dark:text-emerald-200 font-bold block">100% Zero-Data Leakage Garantie</strong>
|
||||
<p class="text-emerald-800/80 dark:text-emerald-300/80 text-[11px] mt-0.5">
|
||||
Uw gevoelige gegevens uit <strong>Jira tickets</strong>, <strong>Outlook e-mails</strong> en <strong>Microsoft Teams</strong> worden strikt afgeschermd en <strong>NOOIT</strong> naar Google Gemini of OpenAI verstuurd.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="/profile.php" class="mt-6 space-y-4">
|
||||
<input type="hidden" name="action" value="save_personal_ai">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(get_csrf_token()) ?>">
|
||||
|
||||
<div class="p-3.5 rounded-2xl bg-slate-50/70 dark:bg-slate-900/60 border border-slate-200/80 dark:border-slate-800">
|
||||
<label class="flex items-center space-x-2.5 text-xs text-slate-900 dark:text-white cursor-pointer select-none">
|
||||
<input type="checkbox" name="ai_enabled" value="1" <?= (!isset($userAiIntRow['is_enabled']) || !empty($userAiIntRow['is_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-semibold">AI Co-Pilot assistentie inschakelen voor mijn account</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" id="profile_ai_model" class="w-full bg-white dark:bg-slate-900/60 border border-slate-200 dark:border-slate-800 rounded-xl px-3.5 py-2.5 text-xs text-slate-900 dark:text-white focus:outline-none focus:ring-1 focus:ring-indigo-500">
|
||||
<optgroup label="Google Gemini (Aanbevolen)">
|
||||
<option value="gemini-1.5-pro" <?= ($userAiSettings['ai_model'] === 'gemini-1.5-pro' || empty($userAiSettings['ai_model'])) ? 'selected' : '' ?>>Google Gemini 1.5 Pro (Aanbevolen)</option>
|
||||
<option value="gemini-2.0-flash" <?= $userAiSettings['ai_model'] === 'gemini-2.0-flash' ? 'selected' : '' ?>>Google Gemini 2.0 Flash (Razendsnel)</option>
|
||||
<option value="gemini-1.5-flash" <?= $userAiSettings['ai_model'] === 'gemini-1.5-flash' ? 'selected' : '' ?>>Google Gemini 1.5 Flash</option>
|
||||
</optgroup>
|
||||
<optgroup label="OpenAI">
|
||||
<option value="gpt-4o-mini" <?= $userAiSettings['ai_model'] === 'gpt-4o-mini' ? 'selected' : '' ?>>OpenAI GPT-4o-mini</option>
|
||||
<option value="gpt-4o" <?= $userAiSettings['ai_model'] === 'gpt-4o' ? 'selected' : '' ?>>OpenAI GPT-4o</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<input type="hidden" name="ai_provider" id="profile_ai_provider" value="<?= strpos($userAiSettings['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">
|
||||
Persoonlijke Google Gemini API Sleutel
|
||||
<span class="text-[11px] font-normal text-slate-500 dark:text-slate-400">(Optioneel)</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
type="password"
|
||||
name="ai_api_key"
|
||||
id="profile_ai_api_key"
|
||||
value="<?= $hasPersonalAiKey ? '••••••••••••••••' : '' ?>"
|
||||
placeholder="<?= $userAiSettings['has_key'] ? 'Gebruikt organisatie-sleutel (leeg laten)' : 'AIzaSy... (Google AI Studio)' ?>"
|
||||
class="w-full bg-white dark:bg-slate-900/60 border border-slate-200 dark:border-slate-800 rounded-xl pl-3.5 pr-9 py-2.5 text-xs text-slate-900 dark:text-white font-mono placeholder-slate-400 focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
||||
>
|
||||
<button type="button" onclick="togglePasswordVisibility('profile_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">
|
||||
Gratis sleutel aanmaken? <a href="https://aistudio.google.com/app/apikey" target="_blank" class="text-indigo-600 dark:text-indigo-400 hover:underline">Google AI Studio →</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Live Test Resultaat Alert in Profiel -->
|
||||
<div id="profileAiTestResult" 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">
|
||||
<div class="flex items-center space-x-2">
|
||||
<button type="button" onclick="testPersonalAiConnection()" id="profileAiTestBtn" 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="profileAiTestBtnText">Test Verbinding</span>
|
||||
</button>
|
||||
|
||||
<?php if ($hasPersonalAiKey): ?>
|
||||
<button type="submit" form="disconnectPersonalAiForm" class="px-3 py-2 rounded-xl text-xs text-rose-600 dark:text-rose-400 hover:bg-rose-50 dark:hover:bg-rose-950/30 transition cursor-pointer font-medium">
|
||||
Persoonlijke Sleutel Verwijderen
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="px-5 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-semibold transition shadow-xs cursor-pointer">
|
||||
AI Instellingen Opslaan
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php if ($hasPersonalAiKey): ?>
|
||||
<form method="POST" id="disconnectPersonalAiForm" class="hidden">
|
||||
<input type="hidden" name="action" value="disconnect_personal_ai">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(get_csrf_token()) ?>">
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Sectie 3: Mijn Meldingen & Agenda Push Notificaties -->
|
||||
<div class="fociz-card p-6 sm:p-7">
|
||||
<div class="flex items-center space-x-3.5 pb-6 border-b border-slate-100 dark:border-slate-800/80">
|
||||
@@ -1173,8 +1355,20 @@ function filterAvatarCategory(category) {
|
||||
}
|
||||
}
|
||||
|
||||
// Sync banner status met browser permissie & activeer actuele thema kaart
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Model en provider sync
|
||||
const profileModelSelect = document.getElementById('profile_ai_model');
|
||||
const profileProviderInput = document.getElementById('profile_ai_provider');
|
||||
if (profileModelSelect && profileProviderInput) {
|
||||
profileModelSelect.addEventListener('change', function() {
|
||||
if (this.value.indexOf('gemini') !== -1) {
|
||||
profileProviderInput.value = 'gemini';
|
||||
} else {
|
||||
profileProviderInput.value = 'openai';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialiseer geselecteerde thema-kaart
|
||||
const activeTheme = localStorage.getItem('fociz_theme') || '<?= $userTheme ?>' || 'dark';
|
||||
updateProfileThemeUI(activeTheme);
|
||||
@@ -1199,6 +1393,56 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (deniedEl) deniedEl.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
async function testPersonalAiConnection() {
|
||||
const btn = document.getElementById('profileAiTestBtn');
|
||||
const btnText = document.getElementById('profileAiTestBtnText');
|
||||
const resultDiv = document.getElementById('profileAiTestResult');
|
||||
const apiKey = document.getElementById('profile_ai_api_key').value.trim();
|
||||
const model = document.getElementById('profile_ai_model').value;
|
||||
const provider = document.getElementById('profile_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'}</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';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
|
||||
Reference in New Issue
Block a user