mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-11 17:26:45 +02:00
fix(ia): afficher la cause réelle d'un échec de génération de texte
La route /api/ai/generate connaissait la cause exacte de l'échec (configuration absente, clé invalide, crédits épuisés, modèle inconnu) mais la remplaçait par un « Failed to generate text » en 500, et l'interface affichait de son côté un message figé. Sans accès aux logs du serveur, l'utilisateur n'avait donc aucun moyen de savoir quoi corriger. Le motif renvoyé par OpenRouter est désormais extrait, tronqué et propagé jusqu'au bandeau d'erreur, avec un code HTTP qui distingue un problème de configuration (400) d'un refus du service tiers (502). La clé API n'apparaît à aucun moment dans ces messages. Même traitement pour /api/reels/generate-text, qui souffrait du même masquage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018uUPZ1GNpEkYB1dpXHYPek
This commit is contained in:
9 files changed
+92
-18
No files matched your search
@@ -15,6 +15,49 @@ interface GeneratedText {
|
||||
import crypto from 'crypto';
|
||||
import { storage } from '../storage';
|
||||
|
||||
/**
|
||||
* Extrait le message d'erreur d'une réponse OpenRouter, qu'elle soit au format
|
||||
* JSON ({ error: { message } }) ou en texte brut. Tronqué pour éviter de
|
||||
* déverser une page HTML entière dans l'interface.
|
||||
*/
|
||||
function extractUpstreamMessage(body: string): string {
|
||||
const trimmed = (body || '').trim();
|
||||
if (!trimmed) return 'aucun détail fourni';
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(trimmed);
|
||||
const message = parsed?.error?.message || parsed?.message || parsed?.error;
|
||||
if (typeof message === 'string' && message.trim()) {
|
||||
return message.trim().slice(0, 300);
|
||||
}
|
||||
} catch {
|
||||
// Réponse non-JSON (page d'erreur HTML d'un proxy, par exemple)
|
||||
}
|
||||
|
||||
return trimmed.replace(/\s+/g, ' ').slice(0, 300);
|
||||
}
|
||||
|
||||
/**
|
||||
* Traduit une erreur de génération en réponse HTTP exploitable côté interface.
|
||||
* Sans cela, l'utilisateur ne voit qu'un 500 générique alors que la cause
|
||||
* (configuration absente, clé invalide, crédits épuisés) est connue du serveur.
|
||||
*/
|
||||
export function describeGenerationError(error: unknown): { status: number; message: string } {
|
||||
const message = error instanceof Error ? error.message : 'Erreur inconnue lors de la génération';
|
||||
|
||||
// Problème de configuration : l'administrateur peut le corriger lui-même
|
||||
if (/Configuration OpenRouter non trouvée|Clé API OpenRouter manquante/.test(message)) {
|
||||
return { status: 400, message };
|
||||
}
|
||||
|
||||
// Refus du service tiers
|
||||
if (/OpenRouter a refusé la requête|Réponse invalide de l'API OpenRouter/.test(message)) {
|
||||
return { status: 502, message };
|
||||
}
|
||||
|
||||
return { status: 500, message };
|
||||
}
|
||||
|
||||
export class OpenRouterService {
|
||||
private baseUrl = "https://openrouter.ai/api/v1/chat/completions";
|
||||
|
||||
@@ -64,7 +107,13 @@ export class OpenRouterService {
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('OpenRouter API Error Response:', errorText);
|
||||
throw new Error(`OpenRouter API error: ${response.status} ${response.statusText}`);
|
||||
// Le motif renvoyé par OpenRouter (clé invalide, crédits épuisés, modèle
|
||||
// inconnu…) est la seule information exploitable : on le fait remonter
|
||||
// jusqu'à l'utilisateur, qui n'a pas accès aux logs du serveur.
|
||||
throw new Error(
|
||||
`OpenRouter a refusé la requête (${response.status} ${response.statusText}) ` +
|
||||
`avec le modèle "${modelToUse}" : ${extractUpstreamMessage(errorText)}`
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
Reference in new issue
Block a user