'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:00')[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']]; } /** * Genereert een persoonlijke AI dagbriefing op basis van taken, afspraken en Jira tickets * * @param string $userName Naam van de gebruiker * @param string $date Datum (Y-m-d) * @param array $tasks Openstaande taken van vandaag * @param array $meetings Outlook meetings van vandaag * @param array $jiraIssues Jira tickets (P1/blokkades) * @param array $unfinishedYest Gisteren niet voltooide taken * @return array ['success' => bool, 'briefing' => string, 'focus_tip' => string, 'warnings' => array] */ function fociz_ai_daily_briefing( string $userName, string $date, array $tasks, array $meetings, array $jiraIssues = [], array $unfinishedYest = [] ): array { $dutchDays = ['', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag', 'zondag']; $dayName = $dutchDays[(int)date('N', strtotime($date))] ?? 'vandaag'; $dateLabel = $dayName . ' ' . date('j', strtotime($date)) . ' ' . date('F', strtotime($date)); $taskSummary = array_map(fn($t) => [ 'title' => $t['title'], 'priority' => $t['priority'] ?? 'P2', 'time' => $t['time_block_start'] ?? null, 'duration' => (int)($t['duration_minutes'] ?? 30), ], array_slice($tasks, 0, 10)); $meetingSummary = array_map(fn($e) => [ 'subject' => $e['subject'] ?? 'Afspraak', 'start' => $e['start'] ?? null, 'end' => $e['end'] ?? null, ], array_slice($meetings, 0, 8)); $jiraSummary = array_map(fn($j) => [ 'key' => $j['key'] ?? '', 'title' => $j['summary'] ?? '', 'priority' => $j['priority'] ?? 'Medium', 'status' => $j['status'] ?? 'Open', ], array_slice(array_filter($jiraIssues, fn($j) => in_array($j['priority'] ?? '', ['Highest', 'High'])), 0, 5)); $yesterdaySummary = array_map(fn($t) => $t['title'], array_slice($unfinishedYest, 0, 5)); $systemPrompt = "Je bent de persoonlijke AI Co-Pilot van {$userName} in Fociz. " . "Schrijf een korte, energieke en persoonlijke dagbriefing in het Nederlands voor {$dateLabel}. " . "Wees direct, concreet en vriendelijk. Gebruik geen markdown headers, geen bullet symbolen zoals *, -, •. " . "Geef 1-2 zinnen dagopening + 1 zin over de grootste prioriteit + 1 praktisch focustip. " . "Maximaal 60 woorden totaal. " . "Retourneer een JSON-object met:\n" . "- 'greeting': openingszin (max 20 woorden)\n" . "- 'focus_task': de meest urgente taak of meeting vandaag (max 15 woorden)\n" . "- 'tip': één concrete focustip voor vandaag (max 20 woorden)\n" . "- 'warnings': array van strings, max 2 items — alleen als er iets opvalt (gisteren niet afgerond, urgent Jira, overvolle dag)"; $userPrompt = json_encode([ 'date' => $dateLabel, 'tasks_today' => $taskSummary, 'meetings_today' => $meetingSummary, 'urgent_jira' => $jiraSummary, 'unfinished_yesterday' => $yesterdaySummary, 'total_tasks' => count($tasks), 'total_meetings' => count($meetings), ], JSON_UNESCAPED_UNICODE); $aiRes = fociz_ai_chat([ ['role' => 'system', 'content' => $systemPrompt], ['role' => 'user', 'content' => $userPrompt], ], ['json_mode' => true, 'temperature' => 0.6, 'max_tokens' => 300]); if (!$aiRes['success'] || empty($aiRes['json'])) { return ['success' => false, 'error' => $aiRes['error'] ?? 'Kon geen dagbriefing genereren']; } return [ 'success' => true, 'greeting' => $aiRes['json']['greeting'] ?? "Goedemorgen {$userName}, een nieuwe dag!", 'focus_task'=> $aiRes['json']['focus_task'] ?? '', 'tip' => $aiRes['json']['tip'] ?? '', 'warnings' => $aiRes['json']['warnings'] ?? [], ]; }