Claude commited on
Fix silent API error handling in chat
Browse filesFrontend now properly detects HTTP errors and displays meaningful
error messages instead of showing a stuck typing indicator. Backend
logs API key presence and request details for easier debugging.
https://claude.ai/code/session_015z3yZxNNfXF63JuQDuPbEG
- app/main.py +12 -0
- static/app.js +31 -5
app/main.py
CHANGED
|
@@ -80,7 +80,18 @@ def _detect_phase(reply: str, current_phase: int) -> int:
|
|
| 80 |
|
| 81 |
@app.post("/api/chat", response_model=ChatResponse)
|
| 82 |
async def api_chat(req: ChatRequest):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
try:
|
|
|
|
| 84 |
rag_chunks = retrieve(req.message)
|
| 85 |
system_prompt = build_system_prompt(req.mode, req.topic, req.phase, rag_chunks)
|
| 86 |
|
|
@@ -88,6 +99,7 @@ async def api_chat(req: ChatRequest):
|
|
| 88 |
messages.append({"role": "user", "content": req.message})
|
| 89 |
|
| 90 |
reply = await chat(system_prompt, messages)
|
|
|
|
| 91 |
detected_phase = _detect_phase(reply, req.phase)
|
| 92 |
|
| 93 |
return ChatResponse(reply=reply, phase=detected_phase)
|
|
|
|
| 80 |
|
| 81 |
@app.post("/api/chat", response_model=ChatResponse)
|
| 82 |
async def api_chat(req: ChatRequest):
|
| 83 |
+
import os
|
| 84 |
+
api_key = os.environ.get("OPENROUTER_API_KEY", "")
|
| 85 |
+
base_url = os.environ.get("LLM_BASE_URL", "")
|
| 86 |
+
model = os.environ.get("LLM_MODEL", "")
|
| 87 |
+
if not api_key:
|
| 88 |
+
logger.error("OPENROUTER_API_KEY is not set!")
|
| 89 |
+
return JSONResponse(status_code=500, content={"error": "Cle API non configuree (OPENROUTER_API_KEY manquant)"})
|
| 90 |
+
if not base_url:
|
| 91 |
+
logger.warning("LLM_BASE_URL is not set, will use OpenAI default")
|
| 92 |
+
|
| 93 |
try:
|
| 94 |
+
logger.info(f"Chat request: mode={req.mode}, topic={req.topic[:50]}, phase={req.phase}, model={model}")
|
| 95 |
rag_chunks = retrieve(req.message)
|
| 96 |
system_prompt = build_system_prompt(req.mode, req.topic, req.phase, rag_chunks)
|
| 97 |
|
|
|
|
| 99 |
messages.append({"role": "user", "content": req.message})
|
| 100 |
|
| 101 |
reply = await chat(system_prompt, messages)
|
| 102 |
+
logger.info(f"LLM reply received ({len(reply)} chars)")
|
| 103 |
detected_phase = _detect_phase(reply, req.phase)
|
| 104 |
|
| 105 |
return ChatResponse(reply=reply, phase=detected_phase)
|
static/app.js
CHANGED
|
@@ -255,9 +255,21 @@
|
|
| 255 |
history: state.history.slice(0, -1) // send history before this message
|
| 256 |
})
|
| 257 |
})
|
| 258 |
-
.then(function (res) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
.then(function (data) {
|
| 260 |
setTyping(false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
state.phase = data.phase;
|
| 262 |
state.history.push({ role: "assistant", content: data.reply });
|
| 263 |
state.timestamps.push(Date.now());
|
|
@@ -268,7 +280,8 @@
|
|
| 268 |
})
|
| 269 |
.catch(function (err) {
|
| 270 |
setTyping(false);
|
| 271 |
-
|
|
|
|
| 272 |
btnSend.disabled = false;
|
| 273 |
});
|
| 274 |
}
|
|
@@ -458,9 +471,21 @@
|
|
| 458 |
history: []
|
| 459 |
})
|
| 460 |
})
|
| 461 |
-
.then(function (res) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 462 |
.then(function (data) {
|
| 463 |
setTyping(false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 464 |
state.phase = data.phase;
|
| 465 |
state.history.push({ role: "assistant", content: data.reply });
|
| 466 |
state.timestamps.push(Date.now());
|
|
@@ -469,9 +494,10 @@
|
|
| 469 |
btnSend.disabled = false;
|
| 470 |
chatInput.focus();
|
| 471 |
})
|
| 472 |
-
.catch(function () {
|
| 473 |
setTyping(false);
|
| 474 |
-
|
|
|
|
| 475 |
btnSend.disabled = false;
|
| 476 |
});
|
| 477 |
}
|
|
|
|
| 255 |
history: state.history.slice(0, -1) // send history before this message
|
| 256 |
})
|
| 257 |
})
|
| 258 |
+
.then(function (res) {
|
| 259 |
+
if (!res.ok) {
|
| 260 |
+
return res.json().then(function (err) {
|
| 261 |
+
throw new Error(err.error || "Erreur serveur " + res.status);
|
| 262 |
+
});
|
| 263 |
+
}
|
| 264 |
+
return res.json();
|
| 265 |
+
})
|
| 266 |
.then(function (data) {
|
| 267 |
setTyping(false);
|
| 268 |
+
if (!data.reply) {
|
| 269 |
+
addMessage("assistant", "Reponse vide du serveur. Verifiez la configuration API.");
|
| 270 |
+
btnSend.disabled = false;
|
| 271 |
+
return;
|
| 272 |
+
}
|
| 273 |
state.phase = data.phase;
|
| 274 |
state.history.push({ role: "assistant", content: data.reply });
|
| 275 |
state.timestamps.push(Date.now());
|
|
|
|
| 280 |
})
|
| 281 |
.catch(function (err) {
|
| 282 |
setTyping(false);
|
| 283 |
+
console.error("sendMessage error:", err);
|
| 284 |
+
addMessage("assistant", "Erreur: " + (err.message || "Connexion impossible. Veuillez reessayer."));
|
| 285 |
btnSend.disabled = false;
|
| 286 |
});
|
| 287 |
}
|
|
|
|
| 471 |
history: []
|
| 472 |
})
|
| 473 |
})
|
| 474 |
+
.then(function (res) {
|
| 475 |
+
if (!res.ok) {
|
| 476 |
+
return res.json().then(function (err) {
|
| 477 |
+
throw new Error(err.error || "Erreur serveur " + res.status);
|
| 478 |
+
});
|
| 479 |
+
}
|
| 480 |
+
return res.json();
|
| 481 |
+
})
|
| 482 |
.then(function (data) {
|
| 483 |
setTyping(false);
|
| 484 |
+
if (!data.reply) {
|
| 485 |
+
addMessage("assistant", "Reponse vide du serveur. Verifiez la configuration API.");
|
| 486 |
+
btnSend.disabled = false;
|
| 487 |
+
return;
|
| 488 |
+
}
|
| 489 |
state.phase = data.phase;
|
| 490 |
state.history.push({ role: "assistant", content: data.reply });
|
| 491 |
state.timestamps.push(Date.now());
|
|
|
|
| 494 |
btnSend.disabled = false;
|
| 495 |
chatInput.focus();
|
| 496 |
})
|
| 497 |
+
.catch(function (err) {
|
| 498 |
setTyping(false);
|
| 499 |
+
console.error("startSession error:", err);
|
| 500 |
+
addMessage("assistant", "Erreur: " + (err.message || "Connexion impossible. Veuillez reessayer."));
|
| 501 |
btnSend.disabled = false;
|
| 502 |
});
|
| 503 |
}
|