Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a0d99914e6 | |||
| af80ffdb14 |
@@ -438,6 +438,7 @@ $appConfig = microsoft_get_app_config();
|
|||||||
$companyJiraDomain = jira_get_company_domain();
|
$companyJiraDomain = jira_get_company_domain();
|
||||||
$workdaySettings = get_workday_settings();
|
$workdaySettings = get_workday_settings();
|
||||||
$aiSettings = get_ai_settings();
|
$aiSettings = get_ai_settings();
|
||||||
|
$aiUsageStats = get_ai_usage_stats();
|
||||||
$sessionIdleTimeout = get_session_idle_timeout();
|
$sessionIdleTimeout = get_session_idle_timeout();
|
||||||
$calendarBlockReasons = get_calendar_block_reasons();
|
$calendarBlockReasons = get_calendar_block_reasons();
|
||||||
$brainstormCategories = get_brainstorm_categories();
|
$brainstormCategories = get_brainstorm_categories();
|
||||||
@@ -1539,8 +1540,7 @@ require_once __DIR__ . '/includes/header.php';
|
|||||||
<i data-lucide="sparkles" class="w-6 h-6"></i>
|
<i data-lucide="sparkles" class="w-6 h-6"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-lg font-bold text-slate-900 dark:text-white tracking-tight">AI & Brainstorm Co-Pilot (Google Gemini Pro)</h2>
|
<h2 class="text-lg font-bold text-slate-900 dark:text-white tracking-tight">AI-Co-pilot</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>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
@@ -1555,6 +1555,54 @@ require_once __DIR__ . '/includes/header.php';
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- AI Verbruik & Statistieken Dashboard -->
|
||||||
|
<div class="p-4 rounded-2xl bg-slate-50/80 dark:bg-[#0c0f17]/90 border border-slate-200/80 dark:border-slate-800/80 space-y-3">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<i data-lucide="bar-chart-2" class="w-4 h-4 text-indigo-500"></i>
|
||||||
|
<span class="text-xs font-bold text-slate-800 dark:text-slate-200 uppercase tracking-wider">AI Verbruik</span>
|
||||||
|
</div>
|
||||||
|
<?php if (!empty($aiUsageStats['last_used_at'])): ?>
|
||||||
|
<span class="text-[11px] text-slate-400">Laatst gebruikt: <?= htmlspecialchars(date('d-m-Y H:i', strtotime($aiUsageStats['last_used_at']))) ?></span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="text-[11px] text-slate-400 italic">Nog geen verbruik geregistreerd</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3">
|
||||||
|
<div class="p-3 rounded-xl bg-white dark:bg-slate-900 border border-slate-200/60 dark:border-slate-800 shadow-2xs">
|
||||||
|
<span class="block text-[10px] uppercase font-bold text-slate-400">Totaal Aanroepen</span>
|
||||||
|
<span class="text-lg font-black text-slate-900 dark:text-white"><?= number_format((int)($aiUsageStats['total_requests'] ?? 0), 0, ',', '.') ?></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-3 rounded-xl bg-white dark:bg-slate-900 border border-slate-200/60 dark:border-slate-800 shadow-2xs">
|
||||||
|
<span class="block text-[10px] uppercase font-bold text-slate-400">Totaal Tokens</span>
|
||||||
|
<span class="text-lg font-black text-indigo-600 dark:text-indigo-400"><?= number_format((int)($aiUsageStats['total_tokens'] ?? 0), 0, ',', '.') ?></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-3 rounded-xl bg-white dark:bg-slate-900 border border-slate-200/60 dark:border-slate-800 shadow-2xs">
|
||||||
|
<span class="block text-[10px] uppercase font-bold text-slate-400">Prompt / Invoer</span>
|
||||||
|
<span class="text-lg font-black text-slate-700 dark:text-slate-300"><?= number_format((int)($aiUsageStats['total_prompt_tokens'] ?? 0), 0, ',', '.') ?></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-3 rounded-xl bg-white dark:bg-slate-900 border border-slate-200/60 dark:border-slate-800 shadow-2xs">
|
||||||
|
<span class="block text-[10px] uppercase font-bold text-slate-400">Output / Gegenereerd</span>
|
||||||
|
<span class="text-lg font-black text-teal-600 dark:text-teal-400"><?= number_format((int)($aiUsageStats['total_completion_tokens'] ?? 0), 0, ',', '.') ?></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!empty($aiUsageStats['by_model'])): ?>
|
||||||
|
<div class="pt-2 flex flex-wrap items-center gap-2 text-[11px] border-t border-slate-200/40 dark:border-slate-800/40">
|
||||||
|
<span class="font-semibold text-slate-500 dark:text-slate-400">Per model:</span>
|
||||||
|
<?php foreach ($aiUsageStats['by_model'] as $mName => $mCount): ?>
|
||||||
|
<span class="px-2 py-0.5 rounded-md bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-300 border border-slate-200 dark:border-slate-700">
|
||||||
|
<strong><?= htmlspecialchars((string)$mName) ?></strong>: <?= (int)$mCount ?>x
|
||||||
|
</span>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ZERO-DATA LEAKAGE PRIVACY FIREWALL BANNER -->
|
<!-- 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="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">
|
<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">
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@ if ($currentUser) {
|
|||||||
$error = null;
|
$error = null;
|
||||||
$infoMessage = null;
|
$infoMessage = null;
|
||||||
if (isset($_GET['expired'])) {
|
if (isset($_GET['expired'])) {
|
||||||
$infoMessage = "Uw sessie is beeïndigd. Log opnieuw in om verder te gaan.";
|
$infoMessage = "Uw sessie is beeïndigd";
|
||||||
}
|
}
|
||||||
if (isset($_GET['revoked'])) {
|
if (isset($_GET['revoked'])) {
|
||||||
$infoMessage = "Uw sessie is op afstand beëindigd of ingetrokken. Log opnieuw in om verder te gaan.";
|
$infoMessage = "Uw sessie is op afstand beëindigd of ingetrokken. Log opnieuw in om verder te gaan.";
|
||||||
|
|||||||
+34
-2
@@ -337,12 +337,23 @@ function fociz_ai_chat_gemini(array $messages, array $options, array $aiSettings
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$promptTokens = (int)($response['json']['usageMetadata']['promptTokenCount'] ?? 0);
|
||||||
|
$completionTokens = (int)($response['json']['usageMetadata']['candidatesTokenCount'] ?? 0);
|
||||||
|
if ($promptTokens === 0 && $completionTokens === 0) {
|
||||||
|
// Schatting obv lengte indien niet geretourneerd
|
||||||
|
$promptTokens = max(1, (int)(strlen(json_encode($payload)) / 4));
|
||||||
|
$completionTokens = max(1, (int)(strlen($cleanContent) / 4));
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'content' => $cleanContent,
|
'content' => $cleanContent,
|
||||||
'json' => $parsedJson,
|
'json' => $parsedJson,
|
||||||
'model' => $successfulModel,
|
'model' => $successfulModel,
|
||||||
'provider' => 'gemini',
|
'provider' => 'gemini',
|
||||||
|
'prompt_tokens' => $promptTokens,
|
||||||
|
'completion_tokens' => $completionTokens,
|
||||||
|
'total_tokens' => ($promptTokens + $completionTokens),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,12 +425,22 @@ function fociz_ai_chat_openai(array $messages, array $options, array $aiSettings
|
|||||||
$parsedJson = json_decode($rawContent, true);
|
$parsedJson = json_decode($rawContent, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$promptTokens = (int)($response['json']['usage']['prompt_tokens'] ?? 0);
|
||||||
|
$completionTokens = (int)($response['json']['usage']['completion_tokens'] ?? 0);
|
||||||
|
if ($promptTokens === 0 && $completionTokens === 0) {
|
||||||
|
$promptTokens = max(1, (int)(strlen(json_encode($payload)) / 4));
|
||||||
|
$completionTokens = max(1, (int)(strlen($rawContent) / 4));
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'content' => $rawContent,
|
'content' => $rawContent,
|
||||||
'json' => $parsedJson,
|
'json' => $parsedJson,
|
||||||
'model' => $response['json']['model'] ?? $model,
|
'model' => $response['json']['model'] ?? $model,
|
||||||
'provider' => 'openai',
|
'provider' => 'openai',
|
||||||
|
'prompt_tokens' => $promptTokens,
|
||||||
|
'completion_tokens' => $completionTokens,
|
||||||
|
'total_tokens' => ($promptTokens + $completionTokens),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,10 +473,21 @@ function fociz_ai_chat(array $messages, array $options = []): array {
|
|||||||
|
|
||||||
// Routering: als provider gemini is of model gemini bevat, stuur naar Google Gemini
|
// Routering: als provider gemini is of model gemini bevat, stuur naar Google Gemini
|
||||||
if ($provider === 'gemini' || strpos($model, 'gemini') !== false) {
|
if ($provider === 'gemini' || strpos($model, 'gemini') !== false) {
|
||||||
return fociz_ai_chat_gemini($messages, $options, $aiSettings);
|
$res = fociz_ai_chat_gemini($messages, $options, $aiSettings);
|
||||||
|
} else {
|
||||||
|
$res = fociz_ai_chat_openai($messages, $options, $aiSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
return fociz_ai_chat_openai($messages, $options, $aiSettings);
|
// Verbruiksstatistieken registreren bij geslaagde aanroep
|
||||||
|
if (!empty($res['success'])) {
|
||||||
|
$usedProvider = $res['provider'] ?? $provider;
|
||||||
|
$usedModel = $res['model'] ?? $model;
|
||||||
|
$pTokens = (int)($res['prompt_tokens'] ?? 0);
|
||||||
|
$cTokens = (int)($res['completion_tokens'] ?? 0);
|
||||||
|
record_ai_usage($usedProvider, $usedModel, $pTokens, $cTokens, $userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -259,4 +259,79 @@ function get_brainstorm_categories(): array {
|
|||||||
return $defaultCategories;
|
return $defaultCategories;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registreert AI verbruiksstatistieken (aanroepen, tokens, provider/model)
|
||||||
|
*/
|
||||||
|
function record_ai_usage(string $provider, string $model, int $promptTokens = 0, int $completionTokens = 0, ?int $userId = null): void {
|
||||||
|
try {
|
||||||
|
$stats = get_system_setting('ai_usage_stats', [
|
||||||
|
'total_requests' => 0,
|
||||||
|
'total_prompt_tokens' => 0,
|
||||||
|
'total_completion_tokens' => 0,
|
||||||
|
'total_tokens' => 0,
|
||||||
|
'last_used_at' => null,
|
||||||
|
'by_provider' => [],
|
||||||
|
'by_model' => [],
|
||||||
|
'by_date' => [],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$stats['total_requests'] = (int)($stats['total_requests'] ?? 0) + 1;
|
||||||
|
$stats['total_prompt_tokens'] = (int)($stats['total_prompt_tokens'] ?? 0) + $promptTokens;
|
||||||
|
$stats['total_completion_tokens'] = (int)($stats['total_completion_tokens'] ?? 0) + $completionTokens;
|
||||||
|
$stats['total_tokens'] = (int)($stats['total_tokens'] ?? 0) + ($promptTokens + $completionTokens);
|
||||||
|
$stats['last_used_at'] = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
// Per provider
|
||||||
|
if (!isset($stats['by_provider'][$provider])) {
|
||||||
|
$stats['by_provider'][$provider] = 0;
|
||||||
|
}
|
||||||
|
$stats['by_provider'][$provider]++;
|
||||||
|
|
||||||
|
// Per model
|
||||||
|
if (!isset($stats['by_model'][$model])) {
|
||||||
|
$stats['by_model'][$model] = 0;
|
||||||
|
}
|
||||||
|
$stats['by_model'][$model]++;
|
||||||
|
|
||||||
|
// Per datum
|
||||||
|
$today = date('Y-m-d');
|
||||||
|
if (!isset($stats['by_date'][$today])) {
|
||||||
|
$stats['by_date'][$today] = ['requests' => 0, 'tokens' => 0];
|
||||||
|
}
|
||||||
|
$stats['by_date'][$today]['requests']++;
|
||||||
|
$stats['by_date'][$today]['tokens'] += ($promptTokens + $completionTokens);
|
||||||
|
|
||||||
|
// Maximaal 30 dagen historiek
|
||||||
|
if (count($stats['by_date']) > 30) {
|
||||||
|
ksort($stats['by_date']);
|
||||||
|
$stats['by_date'] = array_slice($stats['by_date'], -30, 30, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
set_system_setting('ai_usage_stats', $stats);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("record_ai_usage fout: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Haalt de AI verbruiksstatistieken op
|
||||||
|
*/
|
||||||
|
function get_ai_usage_stats(): array {
|
||||||
|
$stats = get_system_setting('ai_usage_stats', null);
|
||||||
|
if (!is_array($stats)) {
|
||||||
|
return [
|
||||||
|
'total_requests' => 0,
|
||||||
|
'total_prompt_tokens' => 0,
|
||||||
|
'total_completion_tokens' => 0,
|
||||||
|
'total_tokens' => 0,
|
||||||
|
'last_used_at' => null,
|
||||||
|
'by_provider' => [],
|
||||||
|
'by_model' => [],
|
||||||
|
'by_date' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user