Download web/src/lib/core/queries.ts from reversely/lifekit: direct link, hf CLI and curl.
- Browser
- Download file 8.06 kB
-
https://huggingface.co/spaces/reversely/lifekit/resolve/main/web/src/lib/core/queries.ts
- Command line
-
hf download hf://spaces/reversely/lifekit/web/src/lib/core/queries.ts
-
curl -L -o queries.ts https://huggingface.co/spaces/reversely/lifekit/resolve/main/web/src/lib/core/queries.ts
8.06 kB
| // Every screen is one of these queries. No screen touches SQL directly. | |
| import type { Database } from './db'; | |
| import { rows } from './db'; | |
| import type { Hit, Shelf, SortKey, Tag, TagCount, Volume } from './types'; | |
| import { sections } from './sections'; | |
| const SORT_SQL: Record<SortKey, string> = { | |
| latest: 'v.retrieved DESC', | |
| oldest: 'v.retrieved ASC', | |
| title: 'v.title ASC', | |
| pages: 'v.page_count DESC', | |
| }; | |
| export function shelves(db: Database): Shelf[] { | |
| const counts = new Map( | |
| rows<{ tag: string; n: number }>( | |
| db, | |
| "SELECT t.tag AS tag, count(*) AS n FROM volume_tag t JOIN volume v ON v.id = t.volume_id WHERE v.carry = 'carry' AND v.page_count > 0 AND v.admission != 'rejected' AND t.tag LIKE 'section:%' GROUP BY t.tag", | |
| ).map((r) => [r.tag, r.n]), | |
| ); | |
| return sections(db).map((s) => ({ | |
| id: s.id, | |
| order: s.order, | |
| title: s.title, | |
| scopeLine: s.scope, | |
| icon: s.icon, | |
| count: counts.get(`section:${s.id}`) ?? 0, | |
| })); | |
| } | |
| function tagsOf(db: Database, volumeId: string): Tag[] { | |
| return rows<{ tag: string; source: Tag['source'] }>( | |
| db, | |
| 'SELECT tag, source FROM volume_tag WHERE volume_id = ? ORDER BY tag', | |
| [volumeId], | |
| ).map((r) => { | |
| const [namespace, value] = r.tag.split(/:(.+)/); | |
| return { tag: r.tag, namespace, value, source: r.source }; | |
| }); | |
| } | |
| export function volumes(db: Database, opts: { tag?: string; sort?: SortKey } = {}): Volume[] { | |
| const where = opts.tag ? ' AND v.id IN (SELECT volume_id FROM volume_tag WHERE tag = ?)' : ''; | |
| const list = rows<any>( | |
| db, | |
| `SELECT v.id, v.title, v.publisher, v.edition, v.format, v.licence, v.page_count, v.retrieved, v.admission, c.text AS cover, c.digest_line | |
| FROM volume v LEFT JOIN cover c ON c.volume_id = v.id WHERE v.carry = 'carry' AND v.page_count > 0 AND v.admission != 'rejected'${where} ORDER BY ${SORT_SQL[opts.sort ?? 'latest']}`, | |
| opts.tag ? [opts.tag] : [], | |
| ); | |
| return list.map((r) => ({ | |
| id: r.id, | |
| title: r.title, | |
| publisher: r.publisher ?? '', | |
| edition: r.edition ?? '', | |
| format: r.format, | |
| licence: r.licence ?? '', | |
| pageCount: r.page_count ?? 0, | |
| retrieved: r.retrieved ?? '', | |
| cover: r.cover ?? '', | |
| digestLine: r.digest_line ?? '', | |
| admission: r.admission ?? 'stock', | |
| tags: tagsOf(db, r.id), | |
| })); | |
| } | |
| /** Tag counts for the filter groups, optionally within one section. */ | |
| export function tagCounts(db: Database, within?: string): TagCount[] { | |
| const scope = within ? ' AND v.id IN (SELECT volume_id FROM volume_tag WHERE tag = ?)' : ''; | |
| const counts = rows<{ tag: string; n: number }>( | |
| db, | |
| `SELECT t.tag AS tag, count(*) AS n FROM volume_tag t JOIN volume v ON v.id = t.volume_id WHERE v.carry = 'carry' AND v.page_count > 0 AND v.admission != 'rejected'${scope} GROUP BY t.tag ORDER BY n DESC, t.tag`, | |
| within ? [`section:${within}`] : [], | |
| ); | |
| const byTag = new Map(counts.map((c) => [c.tag, c.n])); | |
| const out: TagCount[] = []; | |
| // every section, in order, empty ones included | |
| for (const s of sections(db)) { | |
| const tag = `section:${s.id}`; | |
| out.push({ tag, namespace: 'section', value: s.id, label: s.title, count: byTag.get(tag) ?? 0 }); | |
| } | |
| for (const c of counts) { | |
| if (c.tag.startsWith('section:')) continue; | |
| const [namespace, value] = c.tag.split(/:(.+)/); | |
| out.push({ tag: c.tag, namespace, value, label: value, count: c.n }); | |
| } | |
| return out; | |
| } | |
| const STOP = new Set(['the', 'and', 'for', 'how', 'can', 'what', 'with', 'you', 'are', 'should', 'who', 'someone', 'very', 'not', 'when', 'does', 'did', 'put', 'get', 'into', 'from', 'this', 'that', 'there', 'they', 'them', 'their', 'your', 'our', 'his', 'her', 'its', 'much', 'many']); | |
| const ROLE_RANK: Record<string, number> = { primary: 0, reference: 0.5, depth: 0.7, figures: 1, archive: 2 }; | |
| function words(question: string): string[] { | |
| const all = question.toLowerCase().match(/[a-z0-9]+/g) ?? []; | |
| const kept = all.filter((w) => w.length > 2 && !STOP.has(w)); | |
| return kept.length ? kept : all; | |
| } | |
| function ftsQueries(question: string): string[] { | |
| const w = words(question).map((x) => `"${x}"`); | |
| const queries = [w.join(' AND ')]; | |
| if (w.length > 2) queries.push(w.map((drop) => `(${w.filter((x) => x !== drop).join(' AND ')})`).join(' OR ')); | |
| queries.push(w.join(' OR ')); | |
| return queries; | |
| } | |
| export function search(db: Database, question: string, opts: { tag?: string; limit?: number } = {}): Hit[] { | |
| const limit = opts.limit ?? 8; | |
| for (const query of ftsQueries(question)) { | |
| const hits = rows<any>( | |
| db, | |
| `SELECT f.passage_id AS passage, bm25(passage_fts) AS score, s.text, s.kind, p.number AS page, p.anchor, | |
| v.id AS volume, v.title, v.edition, sv.role | |
| FROM passage_fts f JOIN passage s ON s.id = f.passage_id JOIN page p ON p.id = s.page_id | |
| JOIN volume v ON v.id = p.volume_id JOIN shelf_volume sv ON sv.volume_id = v.id | |
| WHERE passage_fts MATCH ? AND v.admission != 'rejected' ORDER BY score LIMIT 60`, | |
| [query], | |
| ) | |
| .filter((h) => !opts.tag || tagsOf(db, h.volume).some((t) => t.tag === opts.tag)) | |
| .map((h) => ({ | |
| ...h, | |
| score: h.score + (ROLE_RANK[h.role] ?? 1) - (['procedure_step', 'checklist', 'warning'].includes(h.kind) ? 1 : 0), | |
| })) | |
| .sort((a, b) => a.score - b.score) | |
| .slice(0, limit); | |
| if (hits.length >= Math.min(limit, 3)) return hits; | |
| if (query === ftsQueries(question).at(-1)) return hits; | |
| } | |
| return []; | |
| } | |
| /** Volumes whose title or cover matches the question's words. */ | |
| export function matchingVolumes(db: Database, question: string, limit = 3): Volume[] { | |
| const w = words(question); | |
| if (!w.length) return []; | |
| const like = w.map(() => '(lower(v.title) LIKE ? OR lower(c.text) LIKE ?)').join(' OR '); | |
| const params = w.flatMap((x) => [`%${x}%`, `%${x}%`]); | |
| const ids = rows<{ id: string }>( | |
| db, | |
| `SELECT v.id FROM volume v LEFT JOIN cover c ON c.volume_id = v.id WHERE v.carry = 'carry' AND v.page_count > 0 AND v.admission != 'rejected' AND (${like}) LIMIT ${limit}`, | |
| params, | |
| ).map((r) => r.id); | |
| return volumes(db).filter((v) => ids.includes(v.id)); | |
| } | |
| export interface Page { | |
| id: string; | |
| number: number; | |
| anchor: string; | |
| text: string; | |
| } | |
| export function volume(db: Database, id: string): Volume | undefined { | |
| return volumes(db).find((v) => v.id === id); | |
| } | |
| export function volumeMeta(db: Database, id: string): { url: string; licence: string; statement: string; sha256: string; retrieved: string; history: string | null } | undefined { | |
| const r = rows<any>(db, 'SELECT url, licence, licence_statement, licence_read_at, sha256, retrieved FROM volume WHERE id = ?', [id])[0]; | |
| const history = r && /action=history/.test(r.licence_read_at ?? '') ? r.licence_read_at : null; | |
| return r && { url: r.url, licence: r.licence, statement: r.licence_statement, sha256: r.sha256, retrieved: r.retrieved, history }; | |
| } | |
| export function pageCount(db: Database, volumeId: string): number { | |
| return rows<{ n: number }>(db, 'SELECT count(*) AS n FROM page WHERE volume_id = ?', [volumeId])[0]?.n ?? 0; | |
| } | |
| export function page(db: Database, volumeId: string, number: number): Page | undefined { | |
| const r = rows<any>(db, 'SELECT id, number, anchor, text FROM page WHERE volume_id = ? AND number = ?', [volumeId, number])[0]; | |
| return r && { id: r.id, number: r.number, anchor: r.anchor, text: r.text }; | |
| } | |
| export function passagesOf(db: Database, pageId: string): { id: string; start: number; end: number; kind: string }[] { | |
| return rows<any>(db, 'SELECT id, start, "end" AS end, kind FROM passage WHERE page_id = ? ORDER BY start', [pageId]); | |
| } | |
| /** First-line headings of each page, for a contents outline. */ | |
| export function outline(db: Database, volumeId: string): { number: number; heading: string }[] { | |
| return rows<{ number: number; text: string }>(db, 'SELECT number, text FROM page WHERE volume_id = ? ORDER BY number', [volumeId]).map((r) => ({ | |
| number: r.number, | |
| heading: (r.text.split('\n').find((l) => l.trim().length > 3) ?? '').slice(0, 80), | |
| })); | |
| } | |