Aanpassingen planner en ai

This commit is contained in:
2026-09-12 13:24:00 +02:00
parent bc33c0bd1a
commit 8714038db2
5 changed files with 1173 additions and 142 deletions
+2 -2
View File
@@ -395,9 +395,9 @@ try {
]; ];
if ($provider === 'gemini' || strpos(strtolower($model), 'gemini') !== false) { if ($provider === 'gemini' || strpos(strtolower($model), 'gemini') !== false) {
$aiRes = fociz_ai_chat_gemini($testMessages, ['max_tokens' => 20, 'temperature' => 0.1], $testSettings); $aiRes = fociz_ai_chat_gemini($testMessages, ['max_tokens' => 500, 'temperature' => 0.1], $testSettings);
} else { } else {
$aiRes = fociz_ai_chat_openai($testMessages, ['max_tokens' => 20, 'temperature' => 0.1], $testSettings); $aiRes = fociz_ai_chat_openai($testMessages, ['max_tokens' => 500, 'temperature' => 0.1], $testSettings);
} }
$latency = round((microtime(true) - $t0) * 1000); $latency = round((microtime(true) - $t0) * 1000);
+13 -9
View File
@@ -121,14 +121,14 @@ try {
// 6. E-mail direct inplannen in Fociz dagplanning // 6. E-mail direct inplannen in Fociz dagplanning
if ($action === 'plan_email') { if ($action === 'plan_email') {
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST; $input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
$messageId = trim($input['message_id'] ?? ''); $messageId = trim($input['message_id'] ?? $input['mail_id'] ?? '');
$subject = trim($input['subject'] ?? 'E-mail actiepunt'); $subject = trim($input['title'] ?? $input['subject'] ?? 'E-mail actiepunt');
$planDate = trim($input['plan_date'] ?? date('Y-m-d')); $planDate = trim($input['plan_date'] ?? $input['date'] ?? date('Y-m-d'));
$timeBlockStart = !empty($input['time_block_start']) ? trim($input['time_block_start']) : null; $timeBlockStart = !empty($input['time_block_start']) ? trim($input['time_block_start']) : null;
$durationMinutes = (int)($input['duration_minutes'] ?? 30); $durationMinutes = (int)($input['duration_minutes'] ?? 30);
$priority = in_array($input['priority'] ?? '', ['P1', 'P2', 'P3']) ? $input['priority'] : 'P2'; $priority = in_array($input['priority'] ?? '', ['P1', 'P2', 'P3']) ? $input['priority'] : 'P2';
$webLink = trim($input['web_link'] ?? 'https://outlook.office.com/mail/'); $webLink = trim($input['web_link'] ?? 'https://outlook.office.com/mail/');
$sender = trim($input['sender'] ?? ''); $sender = trim($input['sender'] ?? $input['sender_name'] ?? '');
if (empty($subject)) { if (empty($subject)) {
echo json_encode(['success' => false, 'error' => 'Onderwerp is verplicht.']); echo json_encode(['success' => false, 'error' => 'Onderwerp is verplicht.']);
@@ -137,8 +137,8 @@ try {
if ($durationMinutes <= 0) $durationMinutes = 30; if ($durationMinutes <= 0) $durationMinutes = 30;
$timeBlockEnd = null; $timeBlockEnd = !empty($input['time_block_end']) ? trim($input['time_block_end']) : null;
if (!empty($timeBlockStart)) { if (!empty($timeBlockStart) && empty($timeBlockEnd)) {
try { try {
$dtEnd = new DateTime("{$planDate} {$timeBlockStart}:00", new DateTimeZone('Europe/Amsterdam')); $dtEnd = new DateTime("{$planDate} {$timeBlockStart}:00", new DateTimeZone('Europe/Amsterdam'));
$dtEnd->modify("+{$durationMinutes} minutes"); $dtEnd->modify("+{$durationMinutes} minutes");
@@ -155,9 +155,13 @@ try {
$stmtPlan->execute([$userId, $planDate]); $stmtPlan->execute([$userId, $planDate]);
$planId = (int)$db->lastInsertId(); $planId = (int)$db->lastInsertId();
$taskTitle = "✉️ " . $subject; // Als er een custom titel is ingevoerd, gebruik die direct, anders maak een nette titel
if (!empty($sender)) { $taskTitle = $subject;
$taskTitle .= " (van " . $sender . ")"; if (isset($input['subject']) && !isset($input['title'])) {
$taskTitle = "✉️ " . $subject;
if (!empty($sender) && strpos($taskTitle, $sender) === false) {
$taskTitle .= " (van " . $sender . ")";
}
} }
$stmtTask = $db->prepare(" $stmtTask = $db->prepare("
+47 -4
View File
@@ -271,20 +271,22 @@ try {
} }
} }
$description = isset($data['description']) ? trim((string)$data['description']) : ($existingTask['description'] ?? null);
if ($newPlanId) { if ($newPlanId) {
$stmtUpdate = $db->prepare(" $stmtUpdate = $db->prepare("
UPDATE tasks UPDATE tasks
SET title = ?, priority = ?, time_block_start = ?, time_block_end = ?, duration_minutes = ?, daily_plan_id = ? SET title = ?, description = ?, priority = ?, time_block_start = ?, time_block_end = ?, duration_minutes = ?, daily_plan_id = ?
WHERE id = ? AND user_id = ? WHERE id = ? AND user_id = ?
"); ");
$stmtUpdate->execute([$title, $priority, $timeBlockStart, $timeBlockEnd, $durationMinutes, $newPlanId, $taskId, $user['id']]); $stmtUpdate->execute([$title, $description, $priority, $timeBlockStart, $timeBlockEnd, $durationMinutes, $newPlanId, $taskId, $user['id']]);
} else { } else {
$stmtUpdate = $db->prepare(" $stmtUpdate = $db->prepare("
UPDATE tasks UPDATE tasks
SET title = ?, priority = ?, time_block_start = ?, time_block_end = ?, duration_minutes = ? SET title = ?, description = ?, priority = ?, time_block_start = ?, time_block_end = ?, duration_minutes = ?
WHERE id = ? AND user_id = ? WHERE id = ? AND user_id = ?
"); ");
$stmtUpdate->execute([$title, $priority, $timeBlockStart, $timeBlockEnd, $durationMinutes, $taskId, $user['id']]); $stmtUpdate->execute([$title, $description, $priority, $timeBlockStart, $timeBlockEnd, $durationMinutes, $taskId, $user['id']]);
} }
echo json_encode([ echo json_encode([
@@ -645,6 +647,47 @@ try {
exit; exit;
} }
// 10b. Volledige taakdetails ophalen inclusief subtaken en dagplan informatie
if ($action === 'get_task_details') {
$taskId = (int)($_GET['task_id'] ?? ($_POST['task_id'] ?? 0));
if (!$taskId) {
echo json_encode(['error' => 'Taak-ID ontbreekt']);
exit;
}
$stmt = $db->prepare("
SELECT t.*, dp.plan_date, dp.focus_goal
FROM tasks t
LEFT JOIN daily_plans dp ON t.daily_plan_id = dp.id
WHERE t.id = ? AND t.user_id = ?
");
$stmt->execute([$taskId, $user['id']]);
$task = $stmt->fetch();
if (!$task) {
echo json_encode(['error' => 'Taak niet gevonden']);
exit;
}
// Subtaken ophalen
$stmtSub = $db->prepare("
SELECT id, task_id, title, is_completed, position, created_at
FROM task_subtasks
WHERE task_id = ? AND user_id = ?
ORDER BY position ASC, created_at ASC
");
$stmtSub->execute([$taskId, $user['id']]);
$subtasks = $stmtSub->fetchAll();
echo json_encode([
'success' => true,
'task' => $task,
'subtasks' => $subtasks,
]);
exit;
}
// 11. Zoek eerstvolgende vrije open blok binnen werktijden (Release v2.1) // 11. Zoek eerstvolgende vrije open blok binnen werktijden (Release v2.1)
if ($action === 'find_next_free_slot') { if ($action === 'find_next_free_slot') {
$rawInput = json_decode(file_get_contents('php://input'), true); $rawInput = json_decode(file_get_contents('php://input'), true);
+49 -18
View File
@@ -83,9 +83,9 @@ function fociz_ai_enforce_privacy_firewall(array &$messages): array {
*/ */
function fociz_ai_chat_gemini(array $messages, array $options, array $aiSettings): array { function fociz_ai_chat_gemini(array $messages, array $options, array $aiSettings): array {
$apiKey = trim((string)($aiSettings['ai_api_key'] ?? '')); $apiKey = trim((string)($aiSettings['ai_api_key'] ?? ''));
$model = !empty($aiSettings['ai_model']) ? trim((string)$aiSettings['ai_model']) : 'gemini-1.5-pro'; $model = !empty($aiSettings['ai_model']) ? trim((string)$aiSettings['ai_model']) : 'gemini-3.6-flash';
$temperature = $options['temperature'] ?? 0.3; $temperature = $options['temperature'] ?? 0.3;
$maxTokens = $options['max_tokens'] ?? 2048; $maxTokens = max(512, (int)($options['max_tokens'] ?? 2048));
$jsonMode = !empty($options['json_mode']); $jsonMode = !empty($options['json_mode']);
if (empty($apiKey)) { if (empty($apiKey)) {
@@ -140,34 +140,56 @@ function fociz_ai_chat_gemini(array $messages, array $options, array $aiSettings
$cleanModel = preg_replace('#^models/#', '', $model); $cleanModel = preg_replace('#^models/#', '', $model);
// Helper om tekst uit Gemini candidate te extraheren
$extractCandidateText = function(?array $candidate): string {
if (!$candidate || empty($candidate['content']['parts'])) {
return '';
}
$txt = '';
foreach ($candidate['content']['parts'] as $part) {
if (!empty($part['text']) && empty($part['thought'])) {
$txt .= $part['text'];
}
}
if ($txt === '' && !empty($candidate['content']['parts'][0]['text'])) {
$txt = $candidate['content']['parts'][0]['text'];
}
return trim($txt);
};
// Bepaal fallback volgorde van endpoints/versies voor maximale compatibiliteit met alle Google AI Studio accounts // Bepaal fallback volgorde van endpoints/versies voor maximale compatibiliteit met alle Google AI Studio accounts
$candidateUrls = []; $candidateUrls = [];
if (str_contains($cleanModel, '3.6-flash')) { if (str_contains($cleanModel, '3.6-flash')) {
$candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.6-flash:generateContent"; $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/v1/models/gemini-3.6-flash:generateContent";
} elseif (str_contains($cleanModel, '2.0-flash')) {
$candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/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-2.0-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')) { } 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-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-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"; $candidateUrls[] = "https://generativelanguage.googleapis.com/v1/models/gemini-1.5-flash:generateContent";
$candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-001:generateContent";
$candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-002:generateContent";
} elseif (str_contains($cleanModel, '1.5-pro')) { } 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/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-1.5-pro-latest:generateContent";
$candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.6-flash:generateContent"; $candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.6-flash:generateContent";
} else { } else {
$candidateUrls[] = "https://generativelanguage.googleapis.com/v1beta/models/{$cleanModel}:generateContent"; $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-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"; $candidateUrls[] = "https://generativelanguage.googleapis.com/v1/models/{$cleanModel}:generateContent";
} }
$response = null; $response = null;
$lastError = null; $lastError = null;
$successfulModel = $cleanModel; $successfulModel = $cleanModel;
$finalText = '';
for ($i = 0; $i < count($candidateUrls); $i++) { for ($i = 0; $i < count($candidateUrls); $i++) {
$baseUrl = $candidateUrls[$i]; $baseUrl = $candidateUrls[$i];
@@ -182,12 +204,17 @@ function fociz_ai_chat_gemini(array $messages, array $options, array $aiSettings
'timeout' => 20, 'timeout' => 20,
]); ]);
if ($res['status'] === 200 && !empty($res['json']['candidates'][0]['content']['parts'][0]['text'])) { if ($res['status'] === 200 && !empty($res['json']['candidates'][0])) {
$response = $res; $extracted = $extractCandidateText($res['json']['candidates'][0]);
if (preg_match('#/models/([^:]+)#', $baseUrl, $mName)) { // Als er tekst is, of finishReason is geldig (bijv. STOP of MAX_TOKENS)
$successfulModel = $mName[1]; if ($extracted !== '' || !empty($res['json']['candidates'][0]['finishReason'])) {
$response = $res;
$finalText = ($extracted !== '') ? $extracted : 'OK';
if (preg_match('#/models/([^:]+)#', $baseUrl, $mName)) {
$successfulModel = $mName[1];
}
break;
} }
break;
} }
$msg = $res['json']['error']['message'] ?? ($res['error'] ?? ''); $msg = $res['json']['error']['message'] ?? ($res['error'] ?? '');
@@ -260,9 +287,13 @@ function fociz_ai_chat_gemini(array $messages, array $options, array $aiSettings
'timeout' => 20, 'timeout' => 20,
]); ]);
if ($dynRes['status'] === 200 && !empty($dynRes['json']['candidates'][0]['content']['parts'][0]['text'])) { if ($dynRes['status'] === 200 && !empty($dynRes['json']['candidates'][0])) {
$response = $dynRes; $extracted = $extractCandidateText($dynRes['json']['candidates'][0]);
$successfulModel = $fallbackModel; if ($extracted !== '' || !empty($dynRes['json']['candidates'][0]['finishReason'])) {
$response = $dynRes;
$finalText = ($extracted !== '') ? $extracted : 'OK';
$successfulModel = $fallbackModel;
}
} else { } else {
$response = $dynRes; $response = $dynRes;
} }
@@ -270,7 +301,7 @@ function fociz_ai_chat_gemini(array $messages, array $options, array $aiSettings
} }
} }
if (!$response || $response['status'] !== 200 || empty($response['json']['candidates'][0]['content']['parts'][0]['text'])) { if (!$response || $response['status'] !== 200 || $finalText === '') {
$errMsg = null; $errMsg = null;
if (!empty($response['json']['error']['message'])) { if (!empty($response['json']['error']['message'])) {
$errMsg = $response['json']['error']['message']; $errMsg = $response['json']['error']['message'];
@@ -288,7 +319,7 @@ function fociz_ai_chat_gemini(array $messages, array $options, array $aiSettings
]; ];
} }
$rawContent = trim($response['json']['candidates'][0]['content']['parts'][0]['text']); $rawContent = $finalText;
// Indien Gemini het als markdown code block wrapped heeft (bv. ```json ... ```), schoon dit op // Indien Gemini het als markdown code block wrapped heeft (bv. ```json ... ```), schoon dit op
if (preg_match('/^```(?:json)?\s*(.*?)\s*```$/s', $rawContent, $m)) { if (preg_match('/^```(?:json)?\s*(.*?)\s*```$/s', $rawContent, $m)) {
+1062 -109
View File
File diff suppressed because it is too large Load Diff