This commit is contained in:
2026-09-12 18:12:30 +02:00
parent 5f15eb67fe
commit f63ea740bf
4 changed files with 426 additions and 164 deletions
+75 -30
View File
@@ -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',
];
}