9262b09c4d
- 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.
198 lines
8.4 KiB
PHP
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']];
|
|
}
|
|
|