Fix Generate Now: HF 503 retry + 120s countdown timer in UI
Browse filesai_analyst.py:
- HF API: retry up to 3x on 503 model-loading, waiting estimated_time
- Increase HF timeout 45s → 60s
dashboard template:
- Button shows live elapsed-seconds counter while waiting
- Hard timeout 120s (was 10s): shows "no response — try again"
- SSE ai_insight event clears the countdown via stored interval handle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ai_analyst/ai_analyst.py +31 -23
- dashboard/templates/index.html +21 -6
ai_analyst/ai_analyst.py
CHANGED
|
@@ -49,30 +49,38 @@ def call_llm(prompt: str) -> str | None:
|
|
| 49 |
except Exception as e:
|
| 50 |
print(f"[AI-Analyst] Ollama unreachable: {e}")
|
| 51 |
|
| 52 |
-
# 2. HuggingFace Inference API
|
| 53 |
if HF_TOKEN:
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
return None
|
| 78 |
|
|
|
|
| 49 |
except Exception as e:
|
| 50 |
print(f"[AI-Analyst] Ollama unreachable: {e}")
|
| 51 |
|
| 52 |
+
# 2. HuggingFace Inference API (with retry on 503 model-loading)
|
| 53 |
if HF_TOKEN:
|
| 54 |
+
url = f"https://api-inference.huggingface.co/models/{HF_MODEL}/v1/chat/completions"
|
| 55 |
+
for attempt in range(3):
|
| 56 |
+
try:
|
| 57 |
+
resp = requests.post(
|
| 58 |
+
url,
|
| 59 |
+
headers={"Authorization": f"Bearer {HF_TOKEN}"},
|
| 60 |
+
json={
|
| 61 |
+
"model": HF_MODEL,
|
| 62 |
+
"messages": [{"role": "user", "content": prompt}],
|
| 63 |
+
"max_tokens": 220,
|
| 64 |
+
"temperature": 0.7,
|
| 65 |
+
},
|
| 66 |
+
timeout=60,
|
| 67 |
+
)
|
| 68 |
+
if resp.status_code == 200:
|
| 69 |
+
text = resp.json()["choices"][0]["message"]["content"].strip()
|
| 70 |
+
if text:
|
| 71 |
+
print(f"[AI-Analyst] Insight via HuggingFace ({HF_MODEL})")
|
| 72 |
+
return text
|
| 73 |
+
elif resp.status_code == 503:
|
| 74 |
+
body = resp.json() if resp.content else {}
|
| 75 |
+
wait = body.get("estimated_time", 20)
|
| 76 |
+
print(f"[AI-Analyst] HF model loading, waiting {wait:.0f}s (attempt {attempt+1}/3)")
|
| 77 |
+
time.sleep(min(float(wait), 30))
|
| 78 |
+
else:
|
| 79 |
+
print(f"[AI-Analyst] HF HTTP {resp.status_code}: {resp.text[:300]}")
|
| 80 |
+
break
|
| 81 |
+
except Exception as e:
|
| 82 |
+
print(f"[AI-Analyst] HF API error (attempt {attempt+1}/3): {e}")
|
| 83 |
+
break
|
| 84 |
|
| 85 |
return None
|
| 86 |
|
dashboard/templates/index.html
CHANGED
|
@@ -434,18 +434,32 @@
|
|
| 434 |
let selectedOrder = null;
|
| 435 |
|
| 436 |
async function triggerAIInsight() {
|
| 437 |
-
const btn
|
|
|
|
| 438 |
btn.disabled = true;
|
| 439 |
btn.textContent = "Generating…";
|
| 440 |
-
|
| 441 |
try {
|
| 442 |
await fetch("/session/ai_insight", { method: "POST" });
|
| 443 |
} catch(e) {}
|
| 444 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 445 |
setTimeout(() => {
|
|
|
|
| 446 |
btn.disabled = false;
|
| 447 |
-
btn.textContent = "Generate Now";
|
| 448 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 449 |
}
|
| 450 |
|
| 451 |
function addInsight(insight) {
|
|
@@ -1117,8 +1131,9 @@
|
|
| 1117 |
document.getElementById("ai-status").textContent =
|
| 1118 |
"Last update: " + new Date().toLocaleTimeString();
|
| 1119 |
const btn = document.getElementById("ai-generate-btn");
|
|
|
|
| 1120 |
btn.disabled = false;
|
| 1121 |
-
btn.textContent = "Generate Now";
|
| 1122 |
});
|
| 1123 |
|
| 1124 |
eventSource.onerror = () => {
|
|
|
|
| 434 |
let selectedOrder = null;
|
| 435 |
|
| 436 |
async function triggerAIInsight() {
|
| 437 |
+
const btn = document.getElementById("ai-generate-btn");
|
| 438 |
+
const status = document.getElementById("ai-status");
|
| 439 |
btn.disabled = true;
|
| 440 |
btn.textContent = "Generating…";
|
| 441 |
+
status.textContent = "contacting LLM…";
|
| 442 |
try {
|
| 443 |
await fetch("/session/ai_insight", { method: "POST" });
|
| 444 |
} catch(e) {}
|
| 445 |
+
|
| 446 |
+
// Countdown timer — LLM (especially HF cold-start) can take up to 2 min
|
| 447 |
+
let elapsed = 0;
|
| 448 |
+
const tick = setInterval(() => {
|
| 449 |
+
elapsed += 1;
|
| 450 |
+
status.textContent = `generating… ${elapsed}s`;
|
| 451 |
+
}, 1000);
|
| 452 |
+
|
| 453 |
+
// Hard cap: re-enable after 120s with failure message
|
| 454 |
setTimeout(() => {
|
| 455 |
+
clearInterval(tick);
|
| 456 |
btn.disabled = false;
|
| 457 |
+
btn.textContent = "✨ Generate Now";
|
| 458 |
+
status.textContent = "no response — check logs or try again";
|
| 459 |
+
}, 120000);
|
| 460 |
+
|
| 461 |
+
// Store tick handle so SSE success can clear it
|
| 462 |
+
btn._tick = tick;
|
| 463 |
}
|
| 464 |
|
| 465 |
function addInsight(insight) {
|
|
|
|
| 1131 |
document.getElementById("ai-status").textContent =
|
| 1132 |
"Last update: " + new Date().toLocaleTimeString();
|
| 1133 |
const btn = document.getElementById("ai-generate-btn");
|
| 1134 |
+
if (btn._tick) { clearInterval(btn._tick); btn._tick = null; }
|
| 1135 |
btn.disabled = false;
|
| 1136 |
+
btn.textContent = "✨ Generate Now";
|
| 1137 |
});
|
| 1138 |
|
| 1139 |
eventSource.onerror = () => {
|