{ "cells": [ { "cell_type": "code", "execution_count": 2, "id": "ae701b32", "metadata": {}, "outputs": [], "source": [ "const ordinals = [\n", " \"first\",\n", " \"second\",\n", " \"third\",\n", " \"fourth\",\n", " \"fifth\",\n", " \"sixth\",\n", " \"seventh\",\n", " \"eighth\",\n", " \"ninth\",\n", " \"tenth\",\n", " \"eleventh\",\n", " \"twelfth\",\n", " \"thirteenth\",\n", " \"fourteenth\",\n", " \"fifteenth\",\n", " \"sixteenth\",\n", " \"seventeenth\",\n", " \"eighteenth\",\n", " \"nineteenth\",\n", " \"twentieth\",\n", "];\n" ] }, { "cell_type": "code", "execution_count": 7, "id": "9f587bf1", "metadata": {}, "outputs": [], "source": [ "import { load } from \"jsr:@std/dotenv\";\n", "import OpenAI from \"jsr:@openai/openai\";\n", "\n", "// flush all previous env vars\n", "/* for (const key of Object.keys(Deno.env.toObject())) {\n", " Deno.env.delete(key);\n", "} */\n", "const _ = await load({ export: true });\n", "const openai = new OpenAI();" ] }, { "cell_type": "code", "execution_count": 8, "id": "4650126c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "progit\n" ] } ], "source": [ "const safeName = (s: string) => s.replace(/[<>:\"/\\\\|?*\\x00-\\x1F]/g, \"_\").trim();\n", "const bookName = Deno.env.get(\"BOOK_NAME\");\n", "\n", "if (!bookName) {\n", " throw new Error(\"BOOK_NAME environment variable is not set\");\n", "}\n", "\n", "console.log(bookName);\n" ] }, { "cell_type": "code", "execution_count": 10, "id": "211e589f", "metadata": {}, "outputs": [], "source": [ "// BOOKMARK_LEVEL=1\n", "// BOOK_FROM=10\n", "// BOOK_TO=-1\n", "\n", "Deno.env.set(\"BOOKMARK_LEVEL\", \"1\");\n", "Deno.env.set(\"BOOK_FROM\", \"10\");\n", "Deno.env.set(\"BOOK_TO\", \"-1\");" ] }, { "cell_type": "code", "execution_count": 11, "id": "8bee369d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[\n", " { title: \"Git Branching\", level: 1, page: 69, endpage: 110 },\n", " { title: \"Git on the Server\", level: 1, page: 111, endpage: 131 },\n", " { title: \"Distributed Git\", level: 1, page: 132, endpage: 172 },\n", " { title: \"GitHub\", level: 1, page: 173, endpage: 225 },\n", " { title: \"Git Tools\", level: 1, page: 226, endpage: 348 },\n", " { title: \"Customizing Git\", level: 1, page: 349, endpage: 380 },\n", " { title: \"Git and Other Systems\", level: 1, page: 381, endpage: 436 },\n", " { title: \"Git Internals\", level: 1, page: 437, endpage: 476 },\n", " {\n", " title: \"Appendix A: Git in Other Environments\",\n", " level: 1,\n", " page: 477,\n", " endpage: 489\n", " },\n", " {\n", " title: \"Appendix B: Embedding Git in your Applications\",\n", " level: 1,\n", " page: 490,\n", " endpage: 501\n", " },\n", " {\n", " title: \"Appendix C: Git Commands\",\n", " level: 1,\n", " page: 502,\n", " endpage: 502\n", " }\n", "]\n" ] } ], "source": [ "import { PDFDocument } from \"https://cdn.skypack.dev/pdf-lib@^1.11.1?dts\";\n", "\n", "const INPUT_PDF_PATH = `./books/${bookName}.pdf`;\n", "\n", "type Bookmark = { title: string; page: number; level: number; endpage: number };\n", "const bookmarks = await new Deno.Command(\"pdftk\", {\n", " args: [\n", " INPUT_PDF_PATH,\n", " \"dump_data\",\n", " \"output\",\n", " \"-\",\n", " ],\n", "}).output().then(\n", " (res) => new TextDecoder().decode(res.stdout),\n", ").then((data) => {\n", " const lines = data.split(\"\\n\");\n", " let bookmarks: Bookmark[] = [];\n", " let currentBookmark: Partial | null = null;\n", "\n", " for (const line of lines) {\n", " if (line.startsWith(\"BookmarkBegin\")) {\n", " if (currentBookmark) {\n", " bookmarks.push(currentBookmark as Bookmark);\n", " }\n", " currentBookmark = {};\n", " } else if (line.startsWith(\"BookmarkTitle:\")) {\n", " if (currentBookmark) {\n", " currentBookmark.title = line.replace(\"BookmarkTitle: \", \"\").trim();\n", " }\n", " } else if (line.startsWith(\"BookmarkLevel:\")) {\n", " if (currentBookmark) {\n", " currentBookmark.level = parseInt(\n", " line.replace(\"BookmarkLevel: \", \"\").trim(),\n", " );\n", " }\n", " } else if (line.startsWith(\"BookmarkPageNumber:\")) {\n", " if (currentBookmark) {\n", " currentBookmark.page = parseInt(\n", " line.replace(\"BookmarkPageNumber: \", \"\").trim(),\n", " );\n", " }\n", " }\n", " }\n", " if (currentBookmark) {\n", " bookmarks.push(currentBookmark as Bookmark);\n", " }\n", "\n", " bookmarks = bookmarks.filter((b) =>\n", " b.level === parseInt(Deno.env.get(\"BOOKMARK_LEVEL\") ?? \"1\")\n", " ).slice(\n", " parseInt(Deno.env.get(\"BOOK_FROM\") ?? \"0\"),\n", " parseInt(Deno.env.get(\"BOOK_TO\") ?? String(bookmarks.length)),\n", " );\n", "\n", " for (let i = 0; i < bookmarks.length; i++) {\n", " const current = bookmarks[i] as Bookmark;\n", " const next = bookmarks[i + 1] as Bookmark | undefined;\n", " const currentPage = current.page ?? 0;\n", " const nextPage = next?.page ?? 0;\n", " current.endpage = nextPage\n", " ? Math.max(currentPage, nextPage - 1)\n", " : currentPage;\n", " }\n", "\n", " return bookmarks;\n", "});\n", "\n", "if (!bookmarks || bookmarks.length === 0) {\n", " throw new Error(\"No bookmarks found in the PDF.\");\n", "} /* else if (bookmarks.length > ordinals.length) {\n", " throw new Error(\n", " `Not enough ordinals for the number of chapters: ${bookmarks.length} chapters but only ${ordinals.length} ordinals.`,\n", " );\n", "} */\n", "\n", "console.log(bookmarks);\n" ] }, { "cell_type": "code", "execution_count": 13, "id": "6029d4ac", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Processing chapter: Appendix C: Git Commands at resp_04d1e841c5c4dfc2006984fd06079c81949afb817229705523\n", "Finished processing chapter: Appendix C: Git Commands\n", "Processing chapter: Customizing Git at resp_0ee8a5d1ba6d78a6006984fd06313c819087c4177391a50101\n", "Finished processing chapter: Customizing Git\n", "Processing chapter: Appendix A: Git in Other Environments at resp_00ee07fd3ffdee22006984fd069c7c8193bf192277fb80f4bc\n", "Finished processing chapter: Appendix A: Git in Other Environments\n", "Processing chapter: Git Internals at resp_0d312b5627d2306a006984fd0660c081939ebc561caf071506\n", "Finished processing chapter: Git Internals\n", "Processing chapter: Git on the Server at resp_07581a2e7f9be083006984fd05d32c8190b87cf1e8d7d68a5c\n", "Finished processing chapter: Git on the Server\n", "Processing chapter: GitHub at resp_0e4a9edfb24ed6a1006984fd06722081909497d0c7e35bfb1d\n", "Finished processing chapter: GitHub\n", "Processing chapter: Appendix B: Embedding Git in your Applications at resp_079044470442fcc0006984fd05fc1881978cacbd612d6e2f30\n", "Finished processing chapter: Appendix B: Embedding Git in your Applications\n", "Processing chapter: Distributed Git at resp_0947b861b710eb8a006984fd06918881968e0e1f70b418cc88\n", "Finished processing chapter: Distributed Git\n", "Processing chapter: Git Tools at resp_01ec7c1b665f5b12006984fd06b9048196a56f1e376843b55a\n", "Finished processing chapter: Git Tools\n", "Processing chapter: Git and Other Systems at resp_0e3a4b42775ae33f006984fd061cf8819389817080ff57b78f\n", "Finished processing chapter: Git and Other Systems\n", "Processing chapter: Git Branching at resp_0bb2c9b2b1435efc006984fd0641d08194b3fd55b5e3b3b432\n", "Finished processing chapter: Git Branching\n" ] }, { "data": { "text/plain": [ "[\n", " \u001b[90mundefined\u001b[39m, \u001b[90mundefined\u001b[39m,\n", " \u001b[90mundefined\u001b[39m, \u001b[90mundefined\u001b[39m,\n", " \u001b[90mundefined\u001b[39m, \u001b[90mundefined\u001b[39m,\n", " \u001b[90mundefined\u001b[39m, \u001b[90mundefined\u001b[39m,\n", " \u001b[90mundefined\u001b[39m, \u001b[90mundefined\u001b[39m,\n", " \u001b[90mundefined\u001b[39m\n", "]" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "const promises = [];\n", "for (const [idx, ch] of bookmarks.entries()) {\n", " async function processChapter(idx: number, title: string, file: File) {\n", " const upload = await openai.files.create({\n", " file,\n", " purpose: \"user_data\",\n", " });\n", "\n", " const response = await openai.responses.create({\n", " model: \"gpt-5.2\",\n", " reasoning: { effort: \"xhigh\" },\n", " input: [{\n", " role: \"user\",\n", " content: [\n", " {\n", " type: \"input_text\",\n", " text:\n", " `A complete, exhaustive and very detailed mind map in markmap syntax of only and only this ${\n", " ordinals[idx]\n", " } chapter`,\n", " },\n", " { type: \"input_file\", file_id: upload.id },\n", " ],\n", " }],\n", " metadata: {\n", " chapter: title,\n", " ordinal: ordinals[idx],\n", " },\n", " });\n", " console.log(`Processing chapter: ${title} at ${response.id}`);\n", "\n", " await openai.files.delete(upload.id);\n", "\n", " const mindMapContent = response.output_text;\n", " await Deno.writeTextFile(\n", " `./mindmap/${safeName(title)}.md`,\n", " mindMapContent,\n", " );\n", "\n", " console.log(`Finished processing chapter: ${title}`);\n", " }\n", " {\n", " // Split the chapter into a separate PDF file\n", " const pdfBytes = await Deno.readFile(INPUT_PDF_PATH);\n", " const pdfDoc = await PDFDocument.load(pdfBytes);\n", " const chapterPdf = await PDFDocument.create();\n", "\n", " const startPage = ch.page - 1; // zero-based index\n", " const endPage = ch.endpage - 1; // zero-based index\n", "\n", " const pagesToCopy = await chapterPdf.copyPages(\n", " pdfDoc,\n", " Array.from(\n", " { length: endPage - startPage + 1 },\n", " (_, i) => i + startPage,\n", " ),\n", " );\n", "\n", " type PdfPage =\n", " import(\"https://cdn.skypack.dev/pdf-lib@^1.11.1?dts\").PDFPage;\n", "\n", " pagesToCopy.forEach((page: PdfPage): void => {\n", " chapterPdf.addPage(page);\n", " });\n", "\n", " const chapterPdfBytes = await chapterPdf.save();\n", " const chapterFile = new File(\n", " [chapterPdfBytes],\n", " `${safeName(ch.title)}.pdf`,\n", " { type: \"application/pdf\" },\n", " );\n", " promises.push(processChapter(idx, ch.title, chapterFile));\n", " }\n", "}\n", "await Promise.all(promises);\n" ] }, { "cell_type": "code", "execution_count": 5, "id": "7333f395", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{ object: \u001b[32m\"file\"\u001b[39m, deleted: \u001b[33mtrue\u001b[39m, id: \u001b[32m\"file-CxDoeyfgNLkuizajP6EHas\"\u001b[39m }" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// delete all files from openai file storage\n", "const userFiles = await openai.files.list({ purpose: \"user_data\" });\n", "for (const file of userFiles.data) {\n", " await openai.files.delete(file.id);\n", "}\n" ] }, { "cell_type": "code", "execution_count": null, "id": "b8a5dc2c", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Deno", "language": "typescript", "name": "deno" }, "language_info": { "codemirror_mode": "typescript", "file_extension": ".ts", "mimetype": "text/x.typescript", "name": "typescript", "nbconvert_exporter": "script", "pygments_lexer": "typescript", "version": "5.9.2" } }, "nbformat": 4, "nbformat_minor": 5 }