'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']]; }