13 KiB
13 KiB
const ordinals = [
"first",
"second",
"third",
"fourth",
"fifth",
"sixth",
"seventh",
"eighth",
"ninth",
"tenth",
"eleventh",
"twelfth",
"thirteenth",
"fourteenth",
"fifteenth",
"sixteenth",
"seventeenth",
"eighteenth",
"nineteenth",
"twentieth",
];
import { load } from "jsr:@std/dotenv";
import OpenAI from "jsr:@openai/openai";
// flush all previous env vars
/* for (const key of Object.keys(Deno.env.toObject())) {
Deno.env.delete(key);
} */
const _ = await load({ export: true });
const openai = new OpenAI();
const safeName = (s: string) => s.replace(/[<>:"/\\|?*\x00-\x1F]/g, "_").trim();
const bookName = Deno.env.get("BOOK_NAME");
if (!bookName) {
throw new Error("BOOK_NAME environment variable is not set");
}
console.log(bookName);
// BOOKMARK_LEVEL=1
// BOOK_FROM=10
// BOOK_TO=-1
Deno.env.set("BOOKMARK_LEVEL", "1");
Deno.env.set("BOOK_FROM", "10");
Deno.env.set("BOOK_TO", "-1");
import { PDFDocument } from "https://cdn.skypack.dev/pdf-lib@^1.11.1?dts";
const INPUT_PDF_PATH = `./books/${bookName}.pdf`;
type Bookmark = { title: string; page: number; level: number; endpage: number };
const bookmarks = await new Deno.Command("pdftk", {
args: [
INPUT_PDF_PATH,
"dump_data",
"output",
"-",
],
}).output().then(
(res) => new TextDecoder().decode(res.stdout),
).then((data) => {
const lines = data.split("\n");
let bookmarks: Bookmark[] = [];
let currentBookmark: Partial<Bookmark> | null = null;
for (const line of lines) {
if (line.startsWith("BookmarkBegin")) {
if (currentBookmark) {
bookmarks.push(currentBookmark as Bookmark);
}
currentBookmark = {};
} else if (line.startsWith("BookmarkTitle:")) {
if (currentBookmark) {
currentBookmark.title = line.replace("BookmarkTitle: ", "").trim();
}
} else if (line.startsWith("BookmarkLevel:")) {
if (currentBookmark) {
currentBookmark.level = parseInt(
line.replace("BookmarkLevel: ", "").trim(),
);
}
} else if (line.startsWith("BookmarkPageNumber:")) {
if (currentBookmark) {
currentBookmark.page = parseInt(
line.replace("BookmarkPageNumber: ", "").trim(),
);
}
}
}
if (currentBookmark) {
bookmarks.push(currentBookmark as Bookmark);
}
bookmarks = bookmarks.filter((b) =>
b.level === parseInt(Deno.env.get("BOOKMARK_LEVEL") ?? "1")
).slice(
parseInt(Deno.env.get("BOOK_FROM") ?? "0"),
parseInt(Deno.env.get("BOOK_TO") ?? String(bookmarks.length)),
);
for (let i = 0; i < bookmarks.length; i++) {
const current = bookmarks[i] as Bookmark;
const next = bookmarks[i + 1] as Bookmark | undefined;
const currentPage = current.page ?? 0;
const nextPage = next?.page ?? 0;
current.endpage = nextPage
? Math.max(currentPage, nextPage - 1)
: currentPage;
}
return bookmarks;
});
if (!bookmarks || bookmarks.length === 0) {
throw new Error("No bookmarks found in the PDF.");
} /* else if (bookmarks.length > ordinals.length) {
throw new Error(
`Not enough ordinals for the number of chapters: ${bookmarks.length} chapters but only ${ordinals.length} ordinals.`,
);
} */
console.log(bookmarks);
const promises = [];
for (const [idx, ch] of bookmarks.entries()) {
async function processChapter(idx: number, title: string, file: File) {
const upload = await openai.files.create({
file,
purpose: "user_data",
});
const response = await openai.responses.create({
model: "gpt-5.2",
reasoning: { effort: "xhigh" },
input: [{
role: "user",
content: [
{
type: "input_text",
text:
`A complete, exhaustive and very detailed mind map in markmap syntax of only and only this ${
ordinals[idx]
} chapter`,
},
{ type: "input_file", file_id: upload.id },
],
}],
metadata: {
chapter: title,
ordinal: ordinals[idx],
},
});
console.log(`Processing chapter: ${title} at ${response.id}`);
await openai.files.delete(upload.id);
const mindMapContent = response.output_text;
await Deno.writeTextFile(
`./mindmap/${safeName(title)}.md`,
mindMapContent,
);
console.log(`Finished processing chapter: ${title}`);
}
{
// Split the chapter into a separate PDF file
const pdfBytes = await Deno.readFile(INPUT_PDF_PATH);
const pdfDoc = await PDFDocument.load(pdfBytes);
const chapterPdf = await PDFDocument.create();
const startPage = ch.page - 1; // zero-based index
const endPage = ch.endpage - 1; // zero-based index
const pagesToCopy = await chapterPdf.copyPages(
pdfDoc,
Array.from(
{ length: endPage - startPage + 1 },
(_, i) => i + startPage,
),
);
type PdfPage =
import("https://cdn.skypack.dev/pdf-lib@^1.11.1?dts").PDFPage;
pagesToCopy.forEach((page: PdfPage): void => {
chapterPdf.addPage(page);
});
const chapterPdfBytes = await chapterPdf.save();
const chapterFile = new File(
[chapterPdfBytes],
`${safeName(ch.title)}.pdf`,
{ type: "application/pdf" },
);
promises.push(processChapter(idx, ch.title, chapterFile));
}
}
await Promise.all(promises);
// delete all files from openai file storage
const userFiles = await openai.files.list({ purpose: "user_data" });
for (const file of userFiles.data) {
await openai.files.delete(file.id);
}