Files

396 lines
13 KiB
Plaintext

{
"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<Bookmark> | 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
}