From e13e523f00423f158648b9082d973d41ab5739a7 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Thu, 24 Sep 2026 05:50:39 +0200 Subject: [PATCH] feat: bundle python-pptx and python-docx for the Pyodide code interpreter (#30382) Asked for a PowerPoint or Word file, the model had no library for it, so it followed the prompt's "use an alternative approach" and wrote the OOXML zip by hand. The sandbox reported success and Office refused to open the result. python-pptx and python-docx are now vendored the same way as openpyxl: their wheels (plus xlsxwriter) go through the PyPI wheel path, lxml joins the Pyodide distribution list so its wasm wheel is cached, and importing pptx or docx installs them from the bundled wheels. No prompt change is needed, since the app installs on import. static/pyodide grows by about 3 MB (58 to 61 MB) and verifyBundledWheels() passes. Verified in headless Chromium with pypi.org, files.pythonhosted.org and the jsDelivr CDN blocked: both packages install from the local wheels only, and a deck and a document built in the sandbox reopen with the native libraries. openpyxl, seaborn, black, pandas, matplotlib and requests still install offline. Without the change both installs fail offline. Fixes #30361 --- scripts/prepare-pyodide.js | 12 +++-- .../components/chat/Messages/CodeBlock.svelte | 4 +- src/routes/+layout.svelte | 4 +- static/pyodide/pyodide-lock.json | 44 +++++++++++++++++++ 4 files changed, 59 insertions(+), 5 deletions(-) diff --git a/scripts/prepare-pyodide.js b/scripts/prepare-pyodide.js index 8ec57426cb..cb043f7312 100644 --- a/scripts/prepare-pyodide.js +++ b/scripts/prepare-pyodide.js @@ -13,7 +13,8 @@ const packages = [ 'tiktoken', 'pytz', 'black', - 'openai' + 'openai', + 'lxml' ]; // Pure-Python packages whose wheels must be downloaded from PyPI and saved into @@ -28,13 +29,18 @@ const pypiPackages = [ 'pytokens', 'openpyxl', 'et-xmlfile', - 'seaborn' + 'seaborn', + 'python-pptx', + 'python-docx', + 'xlsxwriter' ]; const pypiDepends = { black: ['click', 'mypy-extensions', 'packaging', 'pathspec', 'platformdirs', 'pytokens'], openpyxl: ['et-xmlfile'], - seaborn: ['matplotlib', 'numpy', 'pandas'] + seaborn: ['matplotlib', 'numpy', 'pandas'], + 'python-pptx': ['lxml', 'pillow', 'xlsxwriter', 'typing-extensions'], + 'python-docx': ['lxml', 'typing-extensions'] }; import { loadPyodide } from 'pyodide'; diff --git a/src/lib/components/chat/Messages/CodeBlock.svelte b/src/lib/components/chat/Messages/CodeBlock.svelte index 4ae1a202b9..b52700494c 100644 --- a/src/lib/components/chat/Messages/CodeBlock.svelte +++ b/src/lib/components/chat/Messages/CodeBlock.svelte @@ -235,7 +235,9 @@ /\bimport\s+tiktoken\b|\bfrom\s+tiktoken\b/.test(code) ? 'tiktoken' : null, /\bimport\s+pytz\b|\bfrom\s+pytz\b/.test(code) ? 'pytz' : null, /\bimport\s+openpyxl\b|\bfrom\s+openpyxl\b/.test(code) ? 'openpyxl' : null, - /\.(read|to)_excel\(|\.Excel(Writer|File)\(/.test(code) ? 'openpyxl' : null + /\.(read|to)_excel\(|\.Excel(Writer|File)\(/.test(code) ? 'openpyxl' : null, + /\bimport\s+pptx\b|\bfrom\s+pptx\b/.test(code) ? 'python-pptx' : null, + /\bimport\s+docx\b|\bfrom\s+docx\b/.test(code) ? 'python-docx' : null ].filter(Boolean); console.log(packages); diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 69bc84986d..889c00a206 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -313,7 +313,9 @@ /\bimport\s+tiktoken\b|\bfrom\s+tiktoken\b/.test(code) ? 'tiktoken' : null, /\bimport\s+pytz\b|\bfrom\s+pytz\b/.test(code) ? 'pytz' : null, /\bimport\s+openpyxl\b|\bfrom\s+openpyxl\b/.test(code) ? 'openpyxl' : null, - /\.(read|to)_excel\(|\.Excel(Writer|File)\(/.test(code) ? 'openpyxl' : null + /\.(read|to)_excel\(|\.Excel(Writer|File)\(/.test(code) ? 'openpyxl' : null, + /\bimport\s+pptx\b|\bfrom\s+pptx\b/.test(code) ? 'python-pptx' : null, + /\bimport\s+docx\b|\bfrom\s+docx\b/.test(code) ? 'python-docx' : null ].filter(Boolean); const worker = getOrCreateWorker(); diff --git a/static/pyodide/pyodide-lock.json b/static/pyodide/pyodide-lock.json index 25dc86aa67..4c363247c1 100644 --- a/static/pyodide/pyodide-lock.json +++ b/static/pyodide/pyodide-lock.json @@ -5203,6 +5203,50 @@ "numpy", "pandas" ] + }, + "python-pptx": { + "name": "python-pptx", + "version": "1.0.2", + "file_name": "python_pptx-1.0.2-py3-none-any.whl", + "install_dir": "site", + "sha256": "160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", + "package_type": "package", + "imports": [ + "python_pptx" + ], + "depends": [ + "lxml", + "pillow", + "xlsxwriter", + "typing-extensions" + ] + }, + "python-docx": { + "name": "python-docx", + "version": "1.2.0", + "file_name": "python_docx-1.2.0-py3-none-any.whl", + "install_dir": "site", + "sha256": "3fd478f3250fbbbfd3b94fe1e985955737c145627498896a8a6bf81f4baf66c7", + "package_type": "package", + "imports": [ + "python_docx" + ], + "depends": [ + "lxml", + "typing-extensions" + ] + }, + "xlsxwriter": { + "name": "xlsxwriter", + "version": "3.2.9", + "file_name": "xlsxwriter-3.2.9-py3-none-any.whl", + "install_dir": "site", + "sha256": "9a5db42bc5dff014806c58a20b9eae7322a134abb6fce3c92c181bfb275ec5b3", + "package_type": "package", + "imports": [ + "xlsxwriter" + ], + "depends": [] } } } \ No newline at end of file