- log_thread.py: thread-safe ContextVar bridge so executor threads can log
individual LLM calls and archive searches back to the event loop
- ai_log.py: init_thread_logging(), notify_entity_update(); WS now pushes
entity_update messages when book data changes after any plugin or batch run
- batch.py: replace batch_pending.json with batch_queue SQLite table;
run_batch_consumer() reads queue dynamically so new books can be added
while batch is running; add_to_queue() deduplicates
- migrate.py: fix _migrate_v1 (clear-on-startup bug); add _migrate_v2 for
batch_queue table
- _client.py / archive.py / identification.py: wrap each LLM API call and
archive search with log_thread start/finish entries
- api.py: POST /api/batch returns {already_running, added}; notify_entity_update
after identify pipeline
- models.default.yaml: strengthen ai_identify confidence-scoring instructions;
warn against placeholder data
- detail-render.js: book log entries show clickable ID + spine thumbnail;
book spine/title images open full-screen popup
- events.js: batch-start handles already_running+added; open-img-popup action
- init.js: entity_update WS handler; image popup close listeners
- overlays.css / index.html: full-screen image popup overlay
- eslint.config.js: add new globals; fix no-redeclare/no-unused-vars for
multi-file global architecture; all lint errors resolved
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
30 lines
939 B
JavaScript
30 lines
939 B
JavaScript
/*
|
|
* api.js
|
|
* Single fetch wrapper used for all server communication.
|
|
* Throws an Error with the server's detail message on non-2xx responses.
|
|
*
|
|
* Provides: req(method, url, body?, isForm?)
|
|
* Depends on: nothing
|
|
*/
|
|
|
|
/* exported req */
|
|
|
|
// ── API ──────────────────────────────────────────────────────────────────────
|
|
async function req(method, url, body = null, isForm = false) {
|
|
const opts = { method };
|
|
if (body) {
|
|
if (isForm) {
|
|
opts.body = body;
|
|
} else {
|
|
opts.headers = { 'Content-Type': 'application/json' };
|
|
opts.body = JSON.stringify(body);
|
|
}
|
|
}
|
|
const r = await fetch(url, opts);
|
|
if (!r.ok) {
|
|
const e = await r.json().catch(() => ({ detail: 'Request failed' }));
|
|
throw new Error(e.detail || 'Request failed');
|
|
}
|
|
return r.json();
|
|
}
|