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

  1. Read the backend detail in the message; for provider errors, fix the key/endpoint in server config
  2. Retry after a short wait if 429 rate-limited
  3. Reduce document size or fill fields in smaller batches for context-length errors
  4. 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

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


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/813589b7cd909a81. Report an issue: GitHub.