AuthorBot commited on
Commit
1c77285
Β·
1 Parent(s): 9eb7e65

Fix: book readiness check uses chunk_count+chroma_id as real truth; add stuck-book detection + Re-upload button in admin

Browse files
app/admin/templates/admin.html CHANGED
@@ -1041,16 +1041,21 @@ function renderBooks(books) {
1041
  const coverBg = b.cover_thumbnail
1042
  ? `background-image:url('/covers/${b.cover_thumbnail?.split('/data/covers/')[1] || ''}');background-size:cover;background-position:center;`
1043
  : `background:linear-gradient(135deg,hsl(${Math.abs(hashCode(b.title))%360},65%,72%),hsl(${(Math.abs(hashCode(b.title))+60)%360},55%,62%));display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:700;color:#fff;`;
1044
- return `<tr>
 
 
1045
  <td><div style="width:40px;height:56px;border-radius:6px;overflow:hidden;flex-shrink:0;${coverBg}">${b.cover_thumbnail ? '' : esc(b.title.charAt(0))}</div></td>
1046
- <td><div class="fw-600">${esc(b.title)}</div></td>
1047
  <td><span class="text-muted text-sm">${esc(b.genre || 'β€”')}</span></td>
1048
- <td><span class="badge badge-${b.status}">${b.status}</span></td>
1049
  <td>${b.chunk_count || 0}</td>
1050
  <td style="max-width:200px;" class="truncate text-xs text-muted">${esc(b.ai_summary || 'β€”')}</td>
1051
- <td style="display:flex;gap:4px;">
1052
- <label class="btn btn-secondary btn-sm" style="cursor:pointer;margin:0;"><input type="file" accept="image/*" style="display:none;" onchange="uploadCover('${b.id}',this)">πŸ“·</label>
1053
- <button class="btn btn-danger btn-sm" onclick="confirmDeleteBook('${b.id}','${esc(b.title)}')">Delete</button>
 
 
 
1054
  </td>
1055
  </tr>`;
1056
  }).join('');
@@ -1086,6 +1091,15 @@ async function executeDeleteBook(id, title) {
1086
  try { await apiDelete(`/admin/${authorSlug}/books/${id}`); closeModal(); toast('Book deleted', 'success'); loadBooks(); } catch(e) { toast(e.message, 'error'); }
1087
  }
1088
 
 
 
 
 
 
 
 
 
 
1089
  /* ── UPLOAD ── */
1090
  const uploadZone = document.getElementById('upload-zone');
1091
  const fileInput = document.getElementById('file-input');
 
1041
  const coverBg = b.cover_thumbnail
1042
  ? `background-image:url('/covers/${b.cover_thumbnail?.split('/data/covers/')[1] || ''}');background-size:cover;background-position:center;`
1043
  : `background:linear-gradient(135deg,hsl(${Math.abs(hashCode(b.title))%360},65%,72%),hsl(${(Math.abs(hashCode(b.title))+60)%360},55%,62%));display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:700;color:#fff;`;
1044
+ const stuck = !['ready','active'].includes(b.status) && !(b.chunk_count > 0 && b.chroma_collection_id);
1045
+ const stuckBadge = stuck ? `<span title="Indexing stalled β€” delete and re-upload" style="cursor:help;">⚠️</span>` : '';
1046
+ return `<tr${stuck ? ' style="opacity:0.75;"' : ''}>
1047
  <td><div style="width:40px;height:56px;border-radius:6px;overflow:hidden;flex-shrink:0;${coverBg}">${b.cover_thumbnail ? '' : esc(b.title.charAt(0))}</div></td>
1048
+ <td><div class="fw-600">${esc(b.title)}</div>${stuck ? '<div class="text-xs" style="color:var(--yellow-dark);">Indexing stalled</div>' : ''}</td>
1049
  <td><span class="text-muted text-sm">${esc(b.genre || 'β€”')}</span></td>
1050
+ <td><span class="badge badge-${b.status}">${b.status}</span> ${stuckBadge}</td>
1051
  <td>${b.chunk_count || 0}</td>
1052
  <td style="max-width:200px;" class="truncate text-xs text-muted">${esc(b.ai_summary || 'β€”')}</td>
1053
+ <td style="display:flex;gap:4px;flex-wrap:wrap;">
1054
+ ${stuck
1055
+ ? `<button class="btn btn-primary btn-sm" onclick="repairBook('${b.id}','${esc(b.title)}')">πŸ”„ Re-upload</button>`
1056
+ : `<label class="btn btn-secondary btn-sm" style="cursor:pointer;margin:0;"><input type="file" accept="image/*" style="display:none;" onchange="uploadCover('${b.id}',this)">πŸ“·</label>`
1057
+ }
1058
+ <button class="btn btn-danger btn-sm" onclick="confirmDeleteBook('${b.id}','${esc(b.title)}')">πŸ—‘</button>
1059
  </td>
1060
  </tr>`;
1061
  }).join('');
 
1091
  try { await apiDelete(`/admin/${authorSlug}/books/${id}`); closeModal(); toast('Book deleted', 'success'); loadBooks(); } catch(e) { toast(e.message, 'error'); }
1092
  }
1093
 
1094
+ async function repairBook(id, title) {
1095
+ showModal('πŸ”„ Re-upload Required',
1096
+ `<div class="modal-sub">The book <strong>${esc(title)}</strong> got stuck during indexing (the server restarted mid-process).<br><br>
1097
+ The fix is simple: <strong>delete it and re-upload the file</strong>. Indexing now runs in the background and will complete reliably.</div>`,
1098
+ `<button class="btn btn-secondary" onclick="closeModal()">Cancel</button>
1099
+ <button class="btn btn-danger" onclick="closeModal(); executeDeleteBook('${id}','${esc(title)}'); setTimeout(()=>nav('upload'),500);">πŸ—‘ Delete &amp; Go to Upload</button>`
1100
+ );
1101
+ }
1102
+
1103
  /* ── UPLOAD ── */
1104
  const uploadZone = document.getElementById('upload-zone');
1105
  const fileInput = document.getElementById('file-input');
app/services/rag_pipeline.py CHANGED
@@ -764,7 +764,12 @@ async def _no_context_response(
764
 
765
  if len(books) == 1:
766
  book = books[0]
767
- if book.status not in ("ready", "active"):
 
 
 
 
 
768
  text = (
769
  f"{book.title} is still being indexed β€” give it a minute and ask again. "
770
  "I'll be able to answer detailed questions once processing finishes."
 
764
 
765
  if len(books) == 1:
766
  book = books[0]
767
+ # Book is answerable if embeddings exist in ChromaDB β€” status field may be stale
768
+ book_is_ready = (
769
+ book.status in ("ready", "active")
770
+ or (book.chunk_count and book.chunk_count > 0 and book.chroma_collection_id)
771
+ )
772
+ if not book_is_ready:
773
  text = (
774
  f"{book.title} is still being indexed β€” give it a minute and ask again. "
775
  "I'll be able to answer detailed questions once processing finishes."