Skip to content

Commit 6ce263d

Browse files
committed
garotm - add folder features.
1 parent d4f568c commit 6ce263d

7 files changed

Lines changed: 379 additions & 92 deletions

File tree

backend/src/app.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import express from "express";
22
import cors from "cors";
3+
import fs from "node:fs";
4+
import path from "node:path";
35
import type { Database } from "better-sqlite3";
46
import { z } from "zod";
57
import { getSetting, setSetting } from "./db";
68
import { ingestFile } from "./ingest";
79
import { DEFAULT_LLM_BASE_URL, DEFAULT_LLM_MODEL } from "./llmDefaults";
810

11+
const INGESTABLE_EXTENSIONS = new Set([".json", ".hl7", ".pdf", ".txt"]);
12+
913
export function createApp(db: Database) {
1014
const app = express();
1115

@@ -122,6 +126,41 @@ export function createApp(db: Database) {
122126
});
123127
});
124128

129+
app.delete("/api/documents/:id", (req, res) => {
130+
const { changes } = db.prepare("DELETE FROM documents WHERE id = ?").run(req.params.id);
131+
if (changes === 0) {
132+
res.status(404).json({ error: "not found" });
133+
return;
134+
}
135+
res.json({ deleted: req.params.id });
136+
});
137+
138+
app.post("/api/scan", async (_req, res) => {
139+
const watchFolder = getSetting(db, "watch_folder");
140+
if (!watchFolder) {
141+
res.status(400).json({ error: "No watch folder configured" });
142+
return;
143+
}
144+
let queued = 0;
145+
try {
146+
const entries = fs.readdirSync(watchFolder);
147+
const files = entries
148+
.filter((e) => INGESTABLE_EXTENSIONS.has(path.extname(e).toLowerCase()))
149+
.map((e) => path.join(watchFolder, e));
150+
151+
for (const filePath of files) {
152+
void ingestFile(db, filePath).catch((err: unknown) => {
153+
console.error(`[Sift] scan ingest error ${filePath}:`, err);
154+
});
155+
queued++;
156+
}
157+
res.json({ queued, folder: watchFolder });
158+
} catch (e) {
159+
const msg = e instanceof Error ? e.message : String(e);
160+
res.status(500).json({ error: msg });
161+
}
162+
});
163+
125164
app.post("/ingest", (req, res) => {
126165
const fileName = typeof req.body?.fileName === "string" ? req.body.fileName : "";
127166
console.log(`[Sift] legacy ingest ping: ${fileName}`);

backend/src/ingest.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import fs from "fs";
2-
import path from "path";
1+
import fs from "node:fs";
2+
import path from "node:path";
33
import { v4 as uuidv4 } from "uuid";
44
import type Database from "better-sqlite3";
55
import { extractFromFhirJson } from "./pipelines/fhir";
@@ -18,7 +18,7 @@ function detectKind(filePath: string, rawText: string): "fhir" | "hl7" | "pdf" |
1818
const ext = path.extname(filePath).toLowerCase();
1919
if (ext === ".json" || ext === ".xml") {
2020
const t = rawText.trimStart();
21-
if (t.startsWith("{") && (t.includes('"resourceType"') || t.includes('"resourceType"'))) return "fhir";
21+
if (t.startsWith("{") && t.includes('"resourceType"')) return "fhir";
2222
}
2323
if (ext === ".hl7" || ext === ".txt") {
2424
if (looksLikeHl7(rawText)) return "hl7";
@@ -90,6 +90,13 @@ export async function ingestFile(db: Database.Database, filePath: string): Promi
9090
}
9191

9292
const kind = ext === ".pdf" ? "pdf" : detectKind(normalized, rawText);
93+
94+
// Write a 'processing' row immediately so the UI shows the file right away.
95+
db.prepare(
96+
`INSERT INTO documents (id, file_path, file_name, source_type, status)
97+
VALUES (?, ?, ?, ?, 'processing')`,
98+
).run(id, normalized, fileName, kind);
99+
93100
const ctx = await buildContext(kind, normalized, rawText);
94101

95102
const llmBase = getSetting(db, "llm_base_url") ?? process.env.SIFT_LLM_BASE_URL ?? DEFAULT_LLM_BASE_URL;
@@ -115,10 +122,10 @@ export async function ingestFile(db: Database.Database, filePath: string): Promi
115122
}
116123

117124
db.prepare(
118-
`INSERT INTO documents (
119-
id, file_path, file_name, source_type, status, raw_preview, summary_text, confidence, error_message
120-
) VALUES (?, ?, ?, ?, 'complete', ?, ?, ?, ?)`,
121-
).run(id, normalized, fileName, kind, ctx.preview.slice(0, 65000), summary, confidence, err);
125+
`UPDATE documents
126+
SET status = 'complete', raw_preview = ?, summary_text = ?, confidence = ?, error_message = ?
127+
WHERE id = ?`,
128+
).run(ctx.preview.slice(0, 65000), summary, confidence, err, id);
122129

123130
return { documentId: id, status: "complete" };
124131
}

0 commit comments

Comments
 (0)