Document & data assistant
onLanguage(e.target.value)}
>
English
Hindi
Read a PDF or examine a CSV, then ask a question or draft an office
note.
{error && (
{error}
)}
{busy
? "Extracting and checking the file…"
: file
? "Replace the uploaded file"
: "Choose a PDF or CSV"}
PDF: up to 50 MB / 100 pages · CSV: up to 25 MB
{
upload(e.target.files[0]);
e.target.value = "";
}}
/>
{file && (
<>
{file.name}
{file.kind === "pdf"
? `${file.page_count} pages · ${file.scanned_page_count} pages checked for OCR`
: `${file.profile.row_count.toLocaleString()} rows · ${file.profile.column_count} columns`}
exportFile("original")}
>
Original
exportFile("docx")}>
Word
exportFile("text")}>
Text
{(file.warnings || []).map((warning, i) => (
{warning}
))}
{file.kind === "pdf" ? (
Read extracted text and physical page markers
{file.text}
) : (
<>
{rows && (
{rows.total_matches.toLocaleString()} matching rows. Showing
up to the first 100.
)}
{file.profile.columns.map((c) => (
{c}
))}
{(rows?.rows || file.preview).map((r, i) => (
{file.profile.columns.map((c) => (
{r[c] === null ? "—" : String(r[c])}
))}
))}
Schema, missing values & summary statistics
Column
Type
Missing values
{file.profile.columns.map((c) => (
{c}
{file.profile.dtypes[c]}
{file.profile.null_counts[c]}
))}
{JSON.stringify(file.profile.summary_stats, null, 2)}
{rows?.distribution &&
Object.keys(rows.distribution).length > 0 && (
{column} · largest categories
{Object.entries(rows.distribution).map(([key, value]) => (
{key}
{value}
))}
)}
>
)}
Ask about this file
exportFile("transcript")}
>
Word transcript
Uploads expire after 30 minutes and are isolated to this browser
session. Expired files are removed from memory automatically.
Download anything you want to keep.
{messages.map((m, i) => (
{m.role === "user"
? "YOUR QUESTION"
: "FILE-GROUNDED RESPONSE"}
{m.content ||
(running
? "Preparing the response…"
: "No response received.")}
))}
>
)}
);
}
const OPERATIONS = [
["merge", "Merge PDFs"],
["extract", "Extract selected pages"],
["split", "Split into individual PDFs"],
["rotate", "Rotate pages"],
["compress", "Compress PDF"],
["images", "Images to PDF"],
["render", "PDF to images"],
["ocr", "OCR scanned PDF"],
];
export function PdfTools() {
const [operation, setOperation] = useState("merge"),
[files, setFiles] = useState([]),
[pages, setPages] = useState(""),
[rotation, setRotation] = useState("90"),
[dpi, setDpi] = useState("150"),
[language, setLanguage] = useState("eng+hin"),
[busy, setBusy] = useState(false),
[error, setError] = useState(""),
[done, setDone] = useState("");
async function run(e) {
e.preventDefault();
setBusy(true);
setError("");
setDone("");
try {
if (
files.some((f) => f.size > 50 * 1024 * 1024) ||
files.reduce((n, f) => n + f.size, 0) > 99 * 1024 * 1024
)
throw Error(
"Each file may be up to 50 MB; the combined upload must be under 100 MB.",
);
const form = new FormData();
for (const f of files) form.append("files", f);
form.append("operation", operation);
form.append("pages", pages);
form.append("rotation", rotation);
form.append("dpi", dpi);
form.append("language", language);
await saveResponse(
await request("/api/pdf-tools", { method: "POST", body: form }),
"processed-document",
);
setDone("Your processed document has been downloaded.");
} catch (e) {
setError(e.message);
} finally {
setBusy(false);
}
}
return (
Office PDF utilities
Prepare, organise and extract documents. These operations do not use
the language model.
{OPERATIONS.map(([id, label], i) => (
{
setOperation(id);
setFiles([]);
setError("");
setDone("");
}}
>
{String(i + 1).padStart(2, "0")}
{label}
))}
{error && (
{error}
)}
{done && (
{done}
)}
{files.length
? `${files.length} file(s) selected`
: operation === "images"
? "Choose image files"
: "Choose PDF files"}
50 MB per file · 100 MB per operation · 20 files maximum
setFiles(Array.from(e.target.files))}
/>
{files.length > 0 && (
{files.map((f, i) => (
{f.name}
{(f.size / 1024).toFixed(0)} KB
{["merge", "images"].includes(operation) && (
setFiles((v) => {
const a = [...v];
[a[i - 1], a[i]] = [a[i], a[i - 1]];
return a;
})
}
>
Move up
setFiles((v) => {
const a = [...v];
[a[i + 1], a[i]] = [a[i], a[i + 1]];
return a;
})
}
>
Move down
)}
))}
)}
{["extract", "rotate"].includes(operation) && (
PAGE NUMBERS / RANGES
setPages(e.target.value)}
placeholder={
operation === "extract"
? "Required, e.g. 1,3-5"
: "All pages, or e.g. 1,3-5"
}
required={operation === "extract"}
/>
)}
{operation === "rotate" && (
ROTATION
setRotation(e.target.value)}
>
90° clockwise
180°
270° clockwise
)}
{["render", "ocr"].includes(operation) && (
RESOLUTION
setDpi(e.target.value)}
>
{[100, 150, 200].map((d) => (
{d} DPI
))}
)}
{operation === "ocr" && (
OCR LANGUAGE
setLanguage(e.target.value)}
>
English + Hindi
English
Hindi
)}
{operation === "ocr"
? "OCR supports up to 30 pages and downloads a searchable PDF, Word document and extracted text in a ZIP."
: operation === "render"
? "Rendering supports up to 50 pages and downloads page images in a ZIP."
: operation === "compress"
? "Compression is lossless; the size reduction depends on the input document."
: "PDF operations support up to 250 pages per file and 300 pages combined."}
{busy ? (
) : (
)}{" "}
{busy ? "Processing…" : "Process & download"}
);
}
export function SessionSettings({ hasToken, onToken }) {
const [value, setValue] = useState(""),
[saved, setSaved] = useState(false);
return (
Session settings
Optional inference credentials for this browser visit.
The hosted app uses its configured Hugging Face token. You can provide
your own token when the shared service is unavailable or rate limited.
The model and provider remain configured on the server.
{
e.preventDefault();
setToken(value);
onToken(Boolean(value.trim()));
setValue("");
setSaved(true);
}}
>
HUGGING FACE TOKEN
{
setValue(e.target.value);
setSaved(false);
}}
placeholder="hf_…"
maxLength={300}
/>
Use for this visit
{
setToken("");
onToken(false);
setValue("");
setSaved(false);
}}
>
Clear token
The token stays in JavaScript memory and is sent only in request headers
to this app over HTTPS. It is not saved to local storage or included in
exported files. Reloading the page clears it.
{saved && (
{hasToken
? "Your session token is configured."
: "Your session token was cleared."}
)}
);
}