Files
Fociz/includes/ai.php
T
NiekRabelink 9262b09c4d Add roadmap management, service worker, and user invitation features
- Implemented roadmap.php for managing roadmap items with filtering options by quarter and status.
- Added a service worker (sw.js) to handle push notifications and click events for calendar appointments.
- Created users.php for admin to manage team members and invitations, including user creation and deletion functionalities.
2026-09-09 15:54:24 +02:00

198 lines
8.4 KiB
PHP

<?php
/**
* Fociz AI Co-Pilot Engine (Release v1.5)
* Beheert intelligente assistentie voor Brainstorming en Dagplanning.
*/
require_once __DIR__ . '/http.php';
require_once __DIR__ . '/settings.php';
/**
* Voert een gestructureerde chat completion uit met de geconfigureerde AI provider
*
* @param array $messages [['role' => 'system'|'user'|'assistant', 'content' => string]]
* @param array $options ['temperature' => float, 'json_mode' => bool, 'max_tokens' => int]
* @return array ['success' => bool, 'content' => ?string, 'json' => ?array, 'error' => ?string]
*/
function fociz_ai_chat(array $messages, array $options = []): array {
$aiSettings = get_ai_settings();
if (empty($aiSettings['ai_api_key'])) {
return [
'success' => false,
'error' => 'Geen AI API-sleutel geconfigureerd. Stel deze in via Systeembeheer (admin.php).',
];
}
$apiKey = $aiSettings['ai_api_key'];
$model = !empty($aiSettings['ai_model']) ? $aiSettings['ai_model'] : 'gpt-4o-mini';
$temperature = $options['temperature'] ?? 0.3;
$maxTokens = $options['max_tokens'] ?? 1500;
$jsonMode = !empty($options['json_mode']);
$payload = [
'model' => $model,
'messages' => $messages,
'temperature' => $temperature,
'max_tokens' => $maxTokens,
];
if ($jsonMode) {
$payload['response_format'] = ['type' => 'json_object'];
}
$response = fociz_http_request('https://api.openai.com/v1/chat/completions', [
'method' => 'POST',
'headers' => [
'Authorization' => "Bearer {$apiKey}",
'Content-Type' => 'application/json',
],
'body' => $payload,
'timeout' => 35,
]);
if ($response['status'] !== 200 || empty($response['json']['choices'][0]['message']['content'])) {
$errMsg = $response['json']['error']['message'] ?? ($response['error'] ?? "AI aanroep mislukt (HTTP {$response['status']})");
error_log("fociz_ai_chat fout: {$errMsg}");
return [
'success' => false,
'error' => $errMsg,
];
}
$rawContent = trim($response['json']['choices'][0]['message']['content']);
$parsedJson = null;
if ($jsonMode) {
$parsedJson = json_decode($rawContent, true);
}
return [
'success' => true,
'content' => $rawContent,
'json' => $parsedJson,
'model' => $response['json']['model'] ?? $model,
];
}
/**
* Categoriseert sticky notes automatisch in:
* - 'Ideeën'
* - 'Knelpunten'
* - 'Oplossingen'
* - 'Quick Wins'
*/
function fociz_ai_categorize_cards(string $sessionTitle, string $theme, array $cards): array {
if (empty($cards)) {
return ['success' => true, 'categorized' => []];
}
$cardItems = [];
foreach ($cards as $c) {
$cardItems[] = [
'id' => $c['id'],
'content' => $c['content'],
'category' => $c['category'] ?? 'Ideeën',
];
}
$systemPrompt = "Je bent een deskundige agile facilitator en brainstorm co-pilot voor software- en productontwikkeling.\n"
. "Je ontvangt een lijst met brainstorm kaartjes voor de sessie: '{$sessionTitle}' (Thema: '{$theme}').\n"
. "Je taak is om elk kaartje in te delen in exact één van deze 4 categorieën:\n"
. "1. 'Ideeën' (nieuwe concepten, functies, wensen)\n"
. "2. 'Knelpunten' (problemen, blokkades, risico's, frustraties)\n"
. "3. 'Oplossingen' (concrete antwoorden op knelpunten, architectuurkeuzes)\n"
. "4. 'Quick Wins' (kleine, makkelijk uitvoerbare verbeteringen met hoge impact)\n\n"
. "Retourneer uitsluitend een JSON-object met het veld 'categorized' dat een array is van objecten met 'id' en 'category'.";
$userPrompt = "Hier zijn de kaartjes:\n" . json_encode($cardItems, JSON_UNESCAPED_UNICODE);
$aiRes = fociz_ai_chat([
['role' => 'system', 'content' => $systemPrompt],
['role' => 'user', 'content' => $userPrompt],
], ['json_mode' => true]);
if (!$aiRes['success'] || empty($aiRes['json']['categorized'])) {
return ['success' => false, 'error' => $aiRes['error'] ?? 'Kon kaartjes niet categoriseren'];
}
return ['success' => true, 'categorized' => $aiRes['json']['categorized']];
}
/**
* Genereert nieuwe relevante brainstorm ideeën voor een sessie
*/
function fociz_ai_generate_ideas(string $sessionTitle, string $theme, int $count = 4): array {
$systemPrompt = "Je bent een innovatieve productstrateeg en brainstorm co-pilot.\n"
. "Genereer {$count} creatieve, scherpe en direct toepasbare ideeën voor een brainstormsessie getiteld: '{$sessionTitle}' (Thema: '{$theme}').\n"
. "Houd de teksten beknopt en krachtig (1-2 zinnen per idee, alsof ze op een gele post-it staan).\n"
. "Retourneer uitsluitend een JSON-object met het veld 'ideas' dat een array van strings is.";
$aiRes = fociz_ai_chat([
['role' => 'system', 'content' => $systemPrompt],
['role' => 'user', 'content' => "Genereer {$count} vernieuwende ideeën."],
], ['json_mode' => true]);
if (!$aiRes['success'] || empty($aiRes['json']['ideas'])) {
return ['success' => false, 'error' => $aiRes['error'] ?? 'Kon geen ideeën genereren'];
}
return ['success' => true, 'ideas' => $aiRes['json']['ideas']];
}
/**
* Zet brainstorm kaartjes om in een scherp Roadmap Item
*/
function fociz_ai_summarize_to_roadmap(string $content, string $sessionTitle = ''): array {
$systemPrompt = "Je bent een senior product manager. Zet onderstaand brainstorm idee om in een helder, professioneel Roadmap Item.\n"
. "Retourneer een JSON-object met:\n"
. "- 'title': Een beknopte, actiegerichte titel (max 8 woorden)\n"
. "- 'description': Een duidelijke beschrijving van wat er gebouwd moet worden inclusief gebruikerswaarde en 2-3 acceptatiecriteria\n"
. "- 'priority': 'HIGH', 'MEDIUM' of 'LOW'\n"
. "- 'type': 'FEATURE', 'IMPROVEMENT' of 'TECH_DEBT'\n"
. "- 'quarter': 'Q3' of 'Q4'";
$aiRes = fociz_ai_chat([
['role' => 'system', 'content' => $systemPrompt],
['role' => 'user', 'content' => "Sessie: {$sessionTitle}\nIdee:\n{$content}"],
], ['json_mode' => true]);
if (!$aiRes['success'] || empty($aiRes['json']['title'])) {
return ['success' => false, 'error' => $aiRes['error'] ?? 'Kon roadmap item niet formuleren'];
}
return ['success' => true, 'item' => $aiRes['json']];
}
/**
* Berekent een slimme dagindeling voor open taken rondom bestaande afspraken
*/
function fociz_ai_smart_schedule(array $tasks, array $events, array $workdaySettings): array {
$startH = (int)explode(':', $workdaySettings['workday_start'] ?? '08:30')[0];
$endH = (int)explode(':', $workdaySettings['workday_end'] ?? '17:30')[0];
$lunchStart = $workdaySettings['lunch_start'] ?? '12:30';
$lunchEnd = $workdaySettings['lunch_end'] ?? '13:00';
$systemPrompt = "Je bent een productiviteits- en time-blocking assistent.\n"
. "Maak een optimale dagplanning door openstaande taken in te plannen tussen de werkdaguren ({$workdaySettings['workday_start']} tot {$workdaySettings['workday_end']}).\n"
. "Regels:\n"
. "1. Plan GEEN taken tijdens bestaande afspraken of tijdens de lunchpauze ({$lunchStart} - {$lunchEnd}).\n"
. "2. Geef prioriteit aan P1 (brandend/focus) vroeg op de dag, daarna P2 en P3.\n"
. "3. Zorg voor realistische tijdsblokken (15, 30, 45 of 60 minuten) met format 'HH:mm'.\n"
. "Retourneer een JSON-object met het veld 'scheduled' als array van objecten: {'id': int, 'time_block_start': 'HH:mm', 'duration_minutes': int}.";
$userPrompt = "Bestaande afspraken:\n" . json_encode($events, JSON_UNESCAPED_UNICODE) . "\n\n"
. "In te plannen taken:\n" . json_encode($tasks, JSON_UNESCAPED_UNICODE);
$aiRes = fociz_ai_chat([
['role' => 'system', 'content' => $systemPrompt],
['role' => 'user', 'content' => $userPrompt],
], ['json_mode' => true]);
if (!$aiRes['success'] || empty($aiRes['json']['scheduled'])) {
return ['success' => false, 'error' => $aiRes['error'] ?? 'Kon geen slimme indeling berekenen'];
}
return ['success' => true, 'scheduled' => $aiRes['json']['scheduled']];
}