-
+
+
+
Kon taken niet genereren
-
+
+
+
+
diff --git a/includes/ai.php b/includes/ai.php
index 0e0a810..abcc93f 100644
--- a/includes/ai.php
+++ b/includes/ai.php
@@ -83,7 +83,7 @@ function fociz_ai_enforce_privacy_firewall(array &$messages): array {
*/
function fociz_ai_chat_gemini(array $messages, array $options, array $aiSettings): array {
$apiKey = trim((string)($aiSettings['ai_api_key'] ?? ''));
- $model = !empty($aiSettings['ai_model']) ? trim((string)$aiSettings['ai_model']) : 'gemini-3.6-flash';
+ $model = !empty($aiSettings['ai_model']) ? trim((string)$aiSettings['ai_model']) : 'gemini-2.0-flash';
$temperature = $options['temperature'] ?? 0.3;
$maxTokens = max(512, (int)($options['max_tokens'] ?? 2048));
$jsonMode = !empty($options['json_mode']);
@@ -157,35 +157,34 @@ function fociz_ai_chat_gemini(array $messages, array $options, array $aiSettings
return trim($txt);
};
- // Bepaal fallback volgorde van endpoints/versies voor maximale compatibiliteit met alle Google AI Studio accounts
+ // Normaliseer modelnaam en stel geldige kandidatenlijst op
$candidateUrls = [];
- if (str_contains($cleanModel, '3.6-flash')) {
- $candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.6-flash:generateContent";
- $candidateUrls[] = "https://generativelanguage.googleapis.com/v1/models/gemini-3.6-flash:generateContent";
- $candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent";
- $candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent";
- } elseif (str_contains($cleanModel, '2.0-flash')) {
- $candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent";
- $candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.6-flash:generateContent";
- $candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent";
- $candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent";
- } elseif (str_contains($cleanModel, '1.5-flash')) {
- $candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent";
- $candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent";
- $candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.6-flash:generateContent";
- $candidateUrls[] = "https://generativelanguage.googleapis.com/v1/models/gemini-1.5-flash:generateContent";
- } elseif (str_contains($cleanModel, '1.5-pro')) {
- $candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro:generateContent";
- $candidateUrls[] = "https://generativelanguage.googleapis.com/v1/models/gemini-1.5-pro:generateContent";
- $candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-latest:generateContent";
- $candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.6-flash:generateContent";
- } else {
- $candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/{$cleanModel}:generateContent";
- $candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.6-flash:generateContent";
- $candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent";
- $candidateUrls[] = "https://generativelanguage.googleapis.com/v1/models/{$cleanModel}:generateContent";
+
+ // Helper om endpoints op te bouwen voor v1beta en v1
+ $addCandidatesFor = function(string $m) use (&$candidateUrls) {
+ $m = preg_replace('#^models/#', '', trim($m));
+ if ($m === '') return;
+ $v1beta = "https://generativelanguage.googleapis.com/v1beta/models/{$m}:generateContent";
+ $v1 = "https://generativelanguage.googleapis.com/v1/models/{$m}:generateContent";
+ if (!in_array($v1beta, $candidateUrls, true)) {
+ $candidateUrls[] = $v1beta;
+ }
+ if (!in_array($v1, $candidateUrls, true)) {
+ $candidateUrls[] = $v1;
+ }
+ };
+
+ // Voeg het geconfigureerde model toe als eerste kandidaat (tenzij het een niet-bestaand/fictief model is)
+ if (!empty($cleanModel) && !str_contains($cleanModel, '3.6-flash')) {
+ $addCandidatesFor($cleanModel);
}
+ // Voeg altijd de stabiele, snelle Google AI modellen toe als betrouwbare fallbacks/standaarden
+ $addCandidatesFor('gemini-2.0-flash');
+ $addCandidatesFor('gemini-1.5-flash');
+ $addCandidatesFor('gemini-1.5-pro');
+ $addCandidatesFor('gemini-2.5-flash');
+
$response = null;
$lastError = null;
$successfulModel = $cleanModel;
@@ -335,6 +334,14 @@ function fociz_ai_chat_gemini(array $messages, array $options, array $aiSettings
// Poging 2: json decode met raw
$parsedJson = json_decode($rawContent, true);
}
+ // Poging 3: extraheer JSON substring {...} of [...] indien AI conversatietekst heeft toegevoegd
+ if ($parsedJson === null) {
+ if (preg_match('/\{(?:[^{}]|(?R))*\}/s', $cleanContent, $matchJson)) {
+ $parsedJson = json_decode($matchJson[0], true);
+ } elseif (preg_match('/\[(?:[^\[\]]|(?R))*\]/s', $cleanContent, $matchJson)) {
+ $parsedJson = json_decode($matchJson[0], true);
+ }
+ }
}
$promptTokens = (int)($response['json']['usageMetadata']['promptTokenCount'] ?? 0);
@@ -540,17 +547,55 @@ function fociz_ai_breakdown_idea(string $ideaContent, string $sessionTitle = '',
'user_id' => $userId,
]);
- if (!$aiRes['success'] || empty($aiRes['json']['items'])) {
+ if (!$aiRes['success']) {
+ error_log("[fociz_ai_breakdown_idea] AI request failed: " . ($aiRes['error'] ?? 'Unknown error'));
return [
'success' => false,
'error' => $aiRes['error'] ?? 'Kon het idee niet ontleden in taken via AI.',
];
}
+ $parsed = $aiRes['json'] ?? null;
+ $items = [];
+ $summary = '';
+
+ if (is_array($parsed)) {
+ if (!empty($parsed['items']) && is_array($parsed['items'])) {
+ $items = $parsed['items'];
+ } elseif (!empty($parsed['tasks']) && is_array($parsed['tasks'])) {
+ $items = $parsed['tasks'];
+ } elseif (!empty($parsed['taken']) && is_array($parsed['taken'])) {
+ $items = $parsed['taken'];
+ } elseif (!empty($parsed['cards']) && is_array($parsed['cards'])) {
+ $items = $parsed['cards'];
+ } elseif (array_is_list($parsed)) {
+ $items = $parsed;
+ }
+ $summary = (string)($parsed['summary'] ?? '');
+ }
+
+ // Als JSON parsing toch leeg was, probeer regex fallback op ruwe content
+ if (empty($items) && !empty($aiRes['content'])) {
+ if (preg_match('/\[\s*\{.*\}\s*\]/s', $aiRes['content'], $mArr)) {
+ $decodedArr = json_decode($mArr[0], true);
+ if (is_array($decodedArr)) {
+ $items = $decodedArr;
+ }
+ }
+ }
+
+ if (empty($items)) {
+ error_log("[fociz_ai_breakdown_idea] Geen geldige items gevonden in response: " . substr((string)($aiRes['content'] ?? ''), 0, 500));
+ return [
+ 'success' => false,
+ 'error' => 'De AI gaf een antwoord maar kon de taken niet structureren. Probeer het nogmaals.',
+ ];
+ }
+
return [
'success' => true,
- 'summary' => $aiRes['json']['summary'] ?? '',
- 'items' => $aiRes['json']['items'],
+ 'summary' => $summary ?: 'Dit idee is opgesplitst in behapbare deeltaken.',
+ 'items' => $items,
'model' => $aiRes['model'] ?? 'Gemini Pro',
];
}
diff --git a/planner.php b/planner.php
index e01ba04..9ac107d 100644
--- a/planner.php
+++ b/planner.php
@@ -186,16 +186,31 @@ try {
error_log("Fout bij ophalen taken: " . $e->getMessage());
}
-// 3b. Haal alle reeds ingeplande externe items op (Jira keys & Outlook Mail IDs)
+// 3b. Haal alle reeds ingeplande externe items op (Jira keys & Outlook Mail IDs) inclusief taakgegevens voor herplannen
$plannedExternalIds = [];
try {
$stmtPlannedExt = $db->prepare("
- SELECT external_id FROM tasks
- WHERE user_id = ? AND external_id IS NOT NULL AND external_id != ''
+ SELECT t.id as task_id, t.external_id, t.title, t.priority, t.time_block_start, t.time_block_end,
+ t.duration_minutes, dp.plan_date
+ FROM tasks t
+ LEFT JOIN daily_plans dp ON dp.id = t.daily_plan_id
+ WHERE t.user_id = ? AND t.external_id IS NOT NULL AND t.external_id != ''
+ ORDER BY t.id DESC
");
$stmtPlannedExt->execute([$userId]);
foreach ($stmtPlannedExt->fetchAll() as $row) {
- $plannedExternalIds[$row['external_id']] = true;
+ $extId = $row['external_id'];
+ if (!isset($plannedExternalIds[$extId])) {
+ $plannedExternalIds[$extId] = [
+ 'task_id' => (int)$row['task_id'],
+ 'title' => $row['title'],
+ 'priority' => $row['priority'],
+ 'time_block_start' => $row['time_block_start'] ? substr($row['time_block_start'], 0, 5) : '',
+ 'time_block_end' => $row['time_block_end'] ? substr($row['time_block_end'], 0, 5) : '',
+ 'duration_minutes' => (int)($row['duration_minutes'] ?: 30),
+ 'date' => $row['plan_date'] ?: date('Y-m-d')
+ ];
+ }
}
} catch (Throwable $e) {
error_log("Fout bij ophalen planned external IDs: " . $e->getMessage());
@@ -330,7 +345,7 @@ $availableWidgets = [
'timeline' => ['id' => 'timeline', 'title' => 'Tijdlijn & Dag/Weekoverzicht', 'category' => 'Agenda', 'icon' => 'calendar', 'enabled' => true, 'col_span' => 12, 'order' => 2, 'desc' => 'Gecombineerd weekoverzicht, dagselectie en uren-tijdlijn met time-blocking en AI'],
'tasks' => ['id' => 'tasks', 'title' => 'Dagtaken & Prioriteiten', 'category' => 'Planning', 'icon' => 'check-square', 'enabled' => true, 'col_span' => 7, 'order' => 3, 'desc' => 'Volledige takenlijst met P1/P2/P3, tijdvakken en Outlook synchronisatie'],
'outlook_mail' => ['id' => 'outlook_mail', 'title' => 'Outlook E-mail Widget', 'category' => 'Communicatie', 'icon' => 'mail', 'enabled' => true, 'col_span' => 6, 'order' => 4, 'desc' => 'Inkomende e-mails direct inzien en met 1 klik inplannen als taak'],
- 'jira_queue' => ['id' => 'jira_queue', 'title' => 'Jira Tickets & Wachtrijen', 'category' => 'Ontwikkeling', 'icon' => 'layers', 'enabled' => true, 'col_span' => 6, 'order' => 5, 'desc' => 'Openstaande Jira tickets (DX/SD wachtrijen) met directe importknop'],
+ 'jira_queue' => ['id' => 'jira_queue', 'title' => 'Jira Tickets', 'category' => 'Ontwikkeling', 'icon' => 'layers', 'enabled' => true, 'col_span' => 6, 'order' => 5, 'desc' => 'Openstaande Jira tickets met directe importknop'],
'calendar_meetings' => ['id' => 'calendar_meetings', 'title' => 'Agenda & Teams Meetings', 'category' => 'Agenda', 'icon' => 'video', 'enabled' => true, 'col_span' => 6, 'order' => 6, 'desc' => 'Vandaag geplande afspraken met 1-klik Teams deelnemen knop'],
'scratchpad' => ['id' => 'scratchpad', 'title' => 'Dagnotities & Kladblok', 'category' => 'Productiviteit', 'icon' => 'edit-3', 'enabled' => true, 'col_span' => 6, 'order' => 7, 'desc' => 'Vrij notitieblok met automatische achtergrond-opslag'],
'stats' => ['id' => 'stats', 'title' => 'Productiviteit & KPI\'s', 'category' => 'Inzicht', 'icon' => 'bar-chart-2', 'enabled' => true, 'col_span' => 12, 'order' => 8, 'desc' => 'Voortgangsbalk, voltooide taken, focus uren en meeting totalen'],
@@ -1478,7 +1493,7 @@ for ($h = $startHour; $h <= $endHour; $h++) {
-
+