odysseus-dev/odysseus · error · Error
t || res.statusText
Error message
t || res.statusText
What it means
Error thrown when AI-assisted annotation filling fails at the HTTP level: POST /api/document/{docId}/ai-fill-annotations with the user instruction returned non-OK. Body text (or statusText) becomes the message; the save pill shows 'AI fill failed: ...'.
Source
Thrown at static/js/document.js:1803
if (!doc) return;
const instruction = window.prompt(
'What should the AI fill in?\n(e.g. "My name is Jane Doe, address 123 Main St, dob 1990-01-15")'
);
if (!instruction || !instruction.trim()) return;
_setPdfSaveStatus('saving');
const btn = document.getElementById('doc-pdf-ai-fill-btn');
if (btn) { btn.disabled = true; btn.textContent = 'Thinking…'; }
try {
const res = await fetch(`${API_BASE}/api/document/${docId}/ai-fill-annotations`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ instruction: instruction.trim() }),
});
if (!res.ok) {
const t = await res.text().catch(() => res.statusText);
throw new Error(t || res.statusText);
}
const data = await res.json();
const proposed = (data && data.annotations) || [];
if (!proposed.length) {
_setPdfSaveStatus('idle');
if (uiModule && uiModule.showToast) uiModule.showToast('AI found nothing to fill');
return;
}
// Merge into markdown via the same _writeAnnotations path: parse current,
// append proposed (each gets a fresh id), persist, then re-render.
const existing = _parseAnnotations(doc.content || '');
const combined = existing.slice();
for (const a of proposed) {
combined.push({
id: _newAnnotationId(),
page: parseInt(a.page, 10) || 1,
x: Math.max(0, Math.min(100, parseFloat(a.x) || 0)),
y: Math.max(0, Math.min(100, parseFloat(a.y) || 0)),View on GitHub (pinned to f9235ebbf1)
Solutions
- Read the backend detail in the message; for provider errors, fix the key/endpoint in server config
- Retry after a short wait if 429 rate-limited
- Reduce document size or fill fields in smaller batches for context-length errors
- Check backend logs for the exact provider error
Defensive patterns
Strategy: try-catch
Validate before calling
const instruction2 = instruction.trim();
if (!instruction2) { uiModule?.showToast?.('Describe what the AI should fill in'); return; } Try / catch
try {
const res = await fetch(`${API_BASE}/api/document/${docId}/ai-fill-annotations`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ instruction: instruction.trim() }) });
if (!res.ok) {
const t = await res.text().catch(() => '');
let msg = '';
try { msg = JSON.parse(t).detail || ''; } catch { msg = t; }
throw new Error(msg || res.statusText || `HTTP ${res.status}`);
}
const data = await res.json();
// ...
} catch (e) {
_setPdfSaveStatus('error', `AI fill failed: ${e.message || e}`);
} finally {
if (btn) { btn.disabled = false; btn.textContent = 'AI fill'; }
} Prevention
- Reject empty instructions client-side to avoid a guaranteed 422
- On 429, show a 'rate limited, retry shortly' message instead of the raw body
- Always restore the button in finally (already done)
When it happens
Trigger: POST ai-fill-annotations returns 500 (backend LLM call failed — no API key, provider down, context overflow with a huge PDF), 429 (provider rate limit), 404 (docId unknown), 422 (empty instruction).
Common situations: LLM provider credentials missing/expired on the server; model endpoint changed; very large documents blowing the model context; provider outage or rate limiting; user submitted whitespace-only instruction.
Related errors
- t || r2.statusText
- t || r.statusText
- err || res.statusText
- res.statusText || String(res.status)
- await _pdfResponseErrorMessage(res)
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/813589b7cd909a81.
Report an issue: GitHub.