feat: index the text of attached PDFs

- jz_bm_material_file_texts caches the pdftotext output per file
- the River job jz.bm.extract_material_text extracts and resyncs
- the management multipart attach queues it inside its transaction
- file_texts in the document and a file snippet with its name
This commit is contained in:
Jakub Zych
2026-11-08 20:11:19 +01:00
parent dd13795e6f
commit 412821ec56
21 changed files with 747 additions and 24 deletions

View File

@@ -157,15 +157,21 @@ func (h *Handlers) Materials(w http.ResponseWriter, r *http.Request) {
return
}
if cands != nil {
attachSnippets(out, rows, cands.Hits, viaMCP(r))
files, err := snippetFiles(db, materialIDs(rows), cands.Hits)
if err != nil {
writeOpaque500(w)
return
}
attachSnippets(out, rows, cands.Hits, files, viaMCP(r))
}
writeJSONPage(w, lagoon.Paginate(out, page, perPage, total))
}
// attachSnippets sets the snippet of each list row from its Hit. rows are
// attachSnippets sets the snippet of each list row from its Hit and, for a
// file snippet, the row's indexed files. rows are
// the rows SQL returned (they passed the re-gate), in the order of out; the
// Hit of an id SQL did not return is never read.
func attachSnippets(out []materialListJSON, rows []models.Material, hits map[uint]beachcomber.Hit, mcp bool) {
func attachSnippets(out []materialListJSON, rows []models.Material, hits map[uint]beachcomber.Hit, files map[uint][]models.IndexedFile, mcp bool) {
for i := range out {
if i >= len(rows) || rows[i].ID != out[i].ID {
continue
@@ -174,10 +180,19 @@ func attachSnippets(out []materialListJSON, rows []models.Material, hits map[uin
if !ok {
continue
}
out[i].Snippet = snippetValue(pickSnippet(hit, rows[i]), mcp)
out[i].Snippet = snippetValue(pickSnippet(hit, rows[i], files[rows[i].ID]), mcp)
}
}
// materialIDs are the ids of rows, in order.
func materialIDs(rows []models.Material) []uint {
ids := make([]uint, 0, len(rows))
for _, m := range rows {
ids = append(ids, m.ID)
}
return ids
}
// Material answers GET /materials/{id}: one published material the caller
// may see, with its body, tags and files. A draft, a future publication or
// another user's private material is a 404. can_manage tells the author (a