@_vrsen/openswarm 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +152 -0
- package/bin/openswarm.js +38 -0
- package/config.py +34 -0
- package/data_analyst_agent/.cursor/rules/data_analyst.mdc +43 -0
- package/data_analyst_agent/__init__.py +3 -0
- package/data_analyst_agent/__pycache__/__init__.cpython-312.pyc +0 -0
- package/data_analyst_agent/__pycache__/data_analyst_agent.cpython-312.pyc +0 -0
- package/data_analyst_agent/data_analyst_agent.py +45 -0
- package/data_analyst_agent/instructions.md +173 -0
- package/data_analyst_agent/test_files/test_file.csv +21 -0
- package/data_analyst_agent/tools/__init__.py +6 -0
- package/deep_research/__init__.py +1 -0
- package/deep_research/__pycache__/__init__.cpython-312.pyc +0 -0
- package/deep_research/__pycache__/deep_research.cpython-312.pyc +0 -0
- package/deep_research/deep_research.py +27 -0
- package/deep_research/instructions.md +104 -0
- package/deep_research/tools/__init__.py +1 -0
- package/docs_agent/__init__.py +3 -0
- package/docs_agent/__pycache__/__init__.cpython-312.pyc +0 -0
- package/docs_agent/__pycache__/docs_agent.cpython-312.pyc +0 -0
- package/docs_agent/docs_agent.py +61 -0
- package/docs_agent/instructions.md +418 -0
- package/docs_agent/tools/ConvertDocument.py +323 -0
- package/docs_agent/tools/CreateDocument.py +287 -0
- package/docs_agent/tools/ListDocuments.py +134 -0
- package/docs_agent/tools/ModifyDocument.py +247 -0
- package/docs_agent/tools/RestoreDocument.py +79 -0
- package/docs_agent/tools/ViewDocument.py +153 -0
- package/docs_agent/tools/__init__.py +1 -0
- package/docs_agent/tools/__pycache__/ConvertDocument.cpython-312.pyc +0 -0
- package/docs_agent/tools/__pycache__/CreateDocument.cpython-312.pyc +0 -0
- package/docs_agent/tools/__pycache__/ListDocuments.cpython-312.pyc +0 -0
- package/docs_agent/tools/__pycache__/ModifyDocument.cpython-312.pyc +0 -0
- package/docs_agent/tools/__pycache__/RestoreDocument.cpython-312.pyc +0 -0
- package/docs_agent/tools/__pycache__/ViewDocument.cpython-312.pyc +0 -0
- package/docs_agent/tools/__pycache__/__init__.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__init__.py +1 -0
- package/docs_agent/tools/utils/__pycache__/__init__.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/doc_file_utils.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_docx_blocks.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_docx_constants.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_docx_core.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_docx_css.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_docx_images.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_docx_page.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_docx_paragraphs.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_docx_playwright.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_docx_selectors.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_docx_shared.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/__pycache__/html_validation.cpython-312.pyc +0 -0
- package/docs_agent/tools/utils/doc_file_utils.py +29 -0
- package/docs_agent/tools/utils/html_docx_blocks.py +262 -0
- package/docs_agent/tools/utils/html_docx_constants.py +78 -0
- package/docs_agent/tools/utils/html_docx_core.py +138 -0
- package/docs_agent/tools/utils/html_docx_css.py +262 -0
- package/docs_agent/tools/utils/html_docx_images.py +293 -0
- package/docs_agent/tools/utils/html_docx_page.py +185 -0
- package/docs_agent/tools/utils/html_docx_paragraphs.py +342 -0
- package/docs_agent/tools/utils/html_docx_playwright.py +184 -0
- package/docs_agent/tools/utils/html_docx_selectors.py +196 -0
- package/docs_agent/tools/utils/html_docx_shared.py +23 -0
- package/docs_agent/tools/utils/html_docx_tables.py +942 -0
- package/docs_agent/tools/utils/html_validation.py +102 -0
- package/helpers.py +59 -0
- package/image_generation_agent/__init__.py +1 -0
- package/image_generation_agent/__pycache__/__init__.cpython-312.pyc +0 -0
- package/image_generation_agent/__pycache__/image_generation_agent.cpython-312.pyc +0 -0
- package/image_generation_agent/image_generation_agent.py +31 -0
- package/image_generation_agent/instructions.md +80 -0
- package/image_generation_agent/tools/CombineImages.py +211 -0
- package/image_generation_agent/tools/EditImages.py +200 -0
- package/image_generation_agent/tools/GenerateImages.py +184 -0
- package/image_generation_agent/tools/RemoveBackground.py +108 -0
- package/image_generation_agent/tools/__init__.py +2 -0
- package/image_generation_agent/tools/__pycache__/CombineImages.cpython-312.pyc +0 -0
- package/image_generation_agent/tools/__pycache__/EditImages.cpython-312.pyc +0 -0
- package/image_generation_agent/tools/__pycache__/GenerateImages.cpython-312.pyc +0 -0
- package/image_generation_agent/tools/__pycache__/RemoveBackground.cpython-312.pyc +0 -0
- package/image_generation_agent/tools/utils/__init__.py +2 -0
- package/image_generation_agent/tools/utils/__pycache__/__init__.cpython-312.pyc +0 -0
- package/image_generation_agent/tools/utils/__pycache__/image_io.cpython-312.pyc +0 -0
- package/image_generation_agent/tools/utils/image_io.py +308 -0
- package/onboard.py +325 -0
- package/orchestrator/__init__.py +3 -0
- package/orchestrator/__pycache__/__init__.cpython-312.pyc +0 -0
- package/orchestrator/__pycache__/orchestrator.cpython-312.pyc +0 -0
- package/orchestrator/instructions.md +90 -0
- package/orchestrator/orchestrator.py +33 -0
- package/package.json +49 -0
- package/patches/__init__.py +1 -0
- package/patches/__pycache__/__init__.cpython-312.pyc +0 -0
- package/patches/__pycache__/patch_agency_swarm_dual_comms.cpython-312.pyc +0 -0
- package/patches/__pycache__/patch_file_attachment_refs.cpython-312.pyc +0 -0
- package/patches/__pycache__/patch_ipython_interpreter_composio.cpython-312.pyc +0 -0
- package/patches/dom-to-pptx+1.1.5.patch +133440 -0
- package/patches/patch_agency_swarm_dual_comms.py +199 -0
- package/patches/patch_file_attachment_refs.py +74 -0
- package/patches/patch_ipython_interpreter_composio.py +54 -0
- package/pyproject.toml +67 -0
- package/run.py +343 -0
- package/server.py +26 -0
- package/shared_instructions.md +119 -0
- package/shared_tools/CopyFile.py +68 -0
- package/shared_tools/ExecuteTool.py +184 -0
- package/shared_tools/FindTools.py +101 -0
- package/shared_tools/ManageConnections.py +43 -0
- package/shared_tools/SearchTools.py +44 -0
- package/shared_tools/__init__.py +7 -0
- package/shared_tools/__pycache__/CopyFile.cpython-312.pyc +0 -0
- package/shared_tools/__pycache__/ExecuteTool.cpython-312.pyc +0 -0
- package/shared_tools/__pycache__/FindTools.cpython-312.pyc +0 -0
- package/shared_tools/__pycache__/ManageConnections.cpython-312.pyc +0 -0
- package/shared_tools/__pycache__/SearchTools.cpython-312.pyc +0 -0
- package/shared_tools/__pycache__/__init__.cpython-312.pyc +0 -0
- package/slides_agent/.cursor/rules/slides-agent-workflow.mdc +9 -0
- package/slides_agent/__init__.py +1 -0
- package/slides_agent/__pycache__/__init__.cpython-312.pyc +0 -0
- package/slides_agent/__pycache__/slides_agent.cpython-312.pyc +0 -0
- package/slides_agent/instructions.md +298 -0
- package/slides_agent/pptx/SKILL.md +528 -0
- package/slides_agent/pptx/html2pptx.md +625 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/slides_agent/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/slides_agent/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/slides_agent/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/slides_agent/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/slides_agent/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/slides_agent/pptx/ooxml/schemas/mce/mc.xsd +75 -0
- package/slides_agent/pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
- package/slides_agent/pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
- package/slides_agent/pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
- package/slides_agent/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/slides_agent/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/slides_agent/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/slides_agent/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/slides_agent/pptx/ooxml/scripts/pack.py +169 -0
- package/slides_agent/pptx/ooxml/scripts/unpack.py +29 -0
- package/slides_agent/pptx/ooxml/scripts/validate.py +69 -0
- package/slides_agent/pptx/ooxml/scripts/validation/__init__.py +15 -0
- package/slides_agent/pptx/ooxml/scripts/validation/base.py +951 -0
- package/slides_agent/pptx/ooxml/scripts/validation/docx.py +274 -0
- package/slides_agent/pptx/ooxml/scripts/validation/pptx.py +315 -0
- package/slides_agent/pptx/ooxml/scripts/validation/redlining.py +279 -0
- package/slides_agent/pptx/ooxml.md +427 -0
- package/slides_agent/pptx/scripts/html2pptx.js +1092 -0
- package/slides_agent/pptx/scripts/inventory.py +1020 -0
- package/slides_agent/pptx/scripts/rearrange.py +231 -0
- package/slides_agent/pptx/scripts/replace.py +385 -0
- package/slides_agent/pptx/scripts/thumbnail.py +451 -0
- package/slides_agent/slides_agent.py +109 -0
- package/slides_agent/test_deck/_theme.css +92 -0
- package/slides_agent/test_deck/assets/placeholder.svg +11 -0
- package/slides_agent/test_deck/slide_01_title.html +10 -0
- package/slides_agent/test_deck/slide_02_image_split.html +23 -0
- package/slides_agent/test_deck/slide_03_kpi.html +21 -0
- package/slides_agent/tools/ApplyPptxTextReplacements.py +91 -0
- package/slides_agent/tools/BuildPptxFromHtmlSlides.py +221 -0
- package/slides_agent/tools/CheckSlide.py +218 -0
- package/slides_agent/tools/CheckSlideCanvasOverflow.py +221 -0
- package/slides_agent/tools/CreateImageMontage.py +261 -0
- package/slides_agent/tools/CreatePptxThumbnailGrid.py +168 -0
- package/slides_agent/tools/DeleteSlide.py +78 -0
- package/slides_agent/tools/DownloadImage.py +79 -0
- package/slides_agent/tools/EnsureRasterImage.py +157 -0
- package/slides_agent/tools/ExtractPptxTextInventory.py +104 -0
- package/slides_agent/tools/GenerateImage.py +189 -0
- package/slides_agent/tools/ImageSearch.py +127 -0
- package/slides_agent/tools/InsertNewSlides.py +393 -0
- package/slides_agent/tools/ManageTheme.py +112 -0
- package/slides_agent/tools/ModifySlide.py +563 -0
- package/slides_agent/tools/ReadSlide.py +26 -0
- package/slides_agent/tools/RearrangePptxSlidesFromTemplate.py +114 -0
- package/slides_agent/tools/RestoreSnapshot.py +89 -0
- package/slides_agent/tools/SlideScreenshot.py +66 -0
- package/slides_agent/tools/__init__.py +54 -0
- package/slides_agent/tools/__pycache__/ApplyPptxTextReplacements.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/BuildPptxFromHtmlSlides.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/CheckSlide.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/CheckSlideCanvasOverflow.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/CreateImageMontage.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/CreatePptxThumbnailGrid.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/DeleteSlide.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/DownloadImage.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/EnsureRasterImage.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/ExtractPptxTextInventory.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/GenerateImage.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/ImageSearch.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/InsertNewSlides.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/ManageTheme.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/ModifySlide.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/ReadSlide.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/RearrangePptxSlidesFromTemplate.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/RestoreSnapshot.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/SlideScreenshot.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/__init__.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/slide_file_utils.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/slide_html_utils.cpython-312.pyc +0 -0
- package/slides_agent/tools/__pycache__/template_registry.cpython-312.pyc +0 -0
- package/slides_agent/tools/deck_utils.py +31 -0
- package/slides_agent/tools/html2pptx_runner.js +1183 -0
- package/slides_agent/tools/html_writer_instructions.md +149 -0
- package/slides_agent/tools/slide_file_utils.py +108 -0
- package/slides_agent/tools/slide_html_utils.py +354 -0
- package/slides_agent/tools/template_registry.py +55 -0
- package/swarm.py +82 -0
- package/video_generation_agent/__init__.py +1 -0
- package/video_generation_agent/__pycache__/__init__.cpython-312.pyc +0 -0
- package/video_generation_agent/__pycache__/video_generation_agent.cpython-312.pyc +0 -0
- package/video_generation_agent/instructions.md +178 -0
- package/video_generation_agent/tools/AddSubtitles.py +425 -0
- package/video_generation_agent/tools/CombineImages.py +166 -0
- package/video_generation_agent/tools/CombineVideos.py +113 -0
- package/video_generation_agent/tools/EditAudio.py +297 -0
- package/video_generation_agent/tools/EditImage.py +144 -0
- package/video_generation_agent/tools/EditVideoContent.py +369 -0
- package/video_generation_agent/tools/GenerateImage.py +133 -0
- package/video_generation_agent/tools/GenerateVideo.py +556 -0
- package/video_generation_agent/tools/TrimVideo.py +233 -0
- package/video_generation_agent/tools/__init__.py +1 -0
- package/video_generation_agent/tools/__pycache__/AddSubtitles.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/__pycache__/CombineImages.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/__pycache__/CombineVideos.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/__pycache__/EditAudio.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/__pycache__/EditImage.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/__pycache__/EditVideoContent.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/__pycache__/GenerateImage.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/__pycache__/GenerateVideo.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/__pycache__/TrimVideo.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/utils/__init__.py +1 -0
- package/video_generation_agent/tools/utils/__pycache__/__init__.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/utils/__pycache__/image_utils.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/utils/__pycache__/video_utils.cpython-312.pyc +0 -0
- package/video_generation_agent/tools/utils/image_utils.py +174 -0
- package/video_generation_agent/tools/utils/video_utils.py +522 -0
- package/video_generation_agent/video_generation_agent.py +26 -0
- package/virtual_assistant/__init__.py +1 -0
- package/virtual_assistant/__pycache__/__init__.cpython-312.pyc +0 -0
- package/virtual_assistant/__pycache__/virtual_assistant.cpython-312.pyc +0 -0
- package/virtual_assistant/instructions.md +206 -0
- package/virtual_assistant/tools/AddLabelToEmail.py +154 -0
- package/virtual_assistant/tools/CheckEventsForDate.py +218 -0
- package/virtual_assistant/tools/CheckUnreadSlackMessages.py +216 -0
- package/virtual_assistant/tools/CreateCalendarEvent.py +261 -0
- package/virtual_assistant/tools/DeleteCalendarEvent.py +137 -0
- package/virtual_assistant/tools/DeleteDraft.py +95 -0
- package/virtual_assistant/tools/DraftEmail.py +239 -0
- package/virtual_assistant/tools/EditFile.py +113 -0
- package/virtual_assistant/tools/FindEmails.py +330 -0
- package/virtual_assistant/tools/GetCurrentTime.py +69 -0
- package/virtual_assistant/tools/GetSlackUserInfo.py +117 -0
- package/virtual_assistant/tools/ListDirectory.py +113 -0
- package/virtual_assistant/tools/ListSkills.py +94 -0
- package/virtual_assistant/tools/ManageLabels.py +295 -0
- package/virtual_assistant/tools/ProductSearch.py +254 -0
- package/virtual_assistant/tools/ReadEmail.py +251 -0
- package/virtual_assistant/tools/ReadFile.py +108 -0
- package/virtual_assistant/tools/ReadSlackMessages.py +191 -0
- package/virtual_assistant/tools/RemoveLabelFromEmail.py +137 -0
- package/virtual_assistant/tools/RescheduleCalendarEvent.py +227 -0
- package/virtual_assistant/tools/ScholarSearch.py +216 -0
- package/virtual_assistant/tools/SendDraft.py +101 -0
- package/virtual_assistant/tools/SendSlackMessage.py +148 -0
- package/virtual_assistant/tools/WriteFile.py +95 -0
- package/virtual_assistant/tools/__init__.py +1 -0
- package/virtual_assistant/tools/__pycache__/AddLabelToEmail.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/CheckEventsForDate.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/CheckUnreadSlackMessages.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/CreateCalendarEvent.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/DeleteCalendarEvent.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/DeleteDraft.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/DraftEmail.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/EditFile.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/FindEmails.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/GetCurrentTime.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/GetSlackUserInfo.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/ListDirectory.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/ListSkills.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/ManageLabels.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/ProductSearch.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/ReadEmail.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/ReadFile.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/ReadSlackMessages.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/RemoveLabelFromEmail.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/RescheduleCalendarEvent.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/ScholarSearch.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/SendDraft.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/SendSlackMessage.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/WriteFile.cpython-312.pyc +0 -0
- package/virtual_assistant/tools/__pycache__/__init__.cpython-312.pyc +0 -0
- package/virtual_assistant/virtual_assistant.py +52 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
from typing import Dict, Optional, Tuple
|
|
2
|
+
|
|
3
|
+
from docx.shared import RGBColor
|
|
4
|
+
|
|
5
|
+
from .html_docx_constants import _BORDER_SCALE, _NAMED_COLORS
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _normalize_font_family(font_family: str) -> str:
|
|
9
|
+
font_family = font_family.strip().strip('"').strip("'")
|
|
10
|
+
if "," in font_family:
|
|
11
|
+
return font_family.split(",", 1)[0].strip().strip('"').strip("'")
|
|
12
|
+
return font_family
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _parse_font_size_pt(font_size: str) -> Optional[float]:
|
|
16
|
+
size = font_size.strip().lower()
|
|
17
|
+
if size.endswith("pt"):
|
|
18
|
+
return _parse_float(size[:-2])
|
|
19
|
+
if size.endswith("px"):
|
|
20
|
+
px = _parse_float(size[:-2])
|
|
21
|
+
if px is not None:
|
|
22
|
+
return px * 0.75
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _parse_color(value: str) -> Optional[RGBColor]:
|
|
27
|
+
color = value.strip().lower()
|
|
28
|
+
if color in _NAMED_COLORS:
|
|
29
|
+
r, g, b = _NAMED_COLORS[color]
|
|
30
|
+
return RGBColor(r, g, b)
|
|
31
|
+
if color.startswith("#"):
|
|
32
|
+
hex_value = color[1:]
|
|
33
|
+
if len(hex_value) == 3:
|
|
34
|
+
hex_value = "".join([c * 2 for c in hex_value])
|
|
35
|
+
if len(hex_value) == 6:
|
|
36
|
+
try:
|
|
37
|
+
r = int(hex_value[0:2], 16)
|
|
38
|
+
g = int(hex_value[2:4], 16)
|
|
39
|
+
b = int(hex_value[4:6], 16)
|
|
40
|
+
return RGBColor(r, g, b)
|
|
41
|
+
except ValueError:
|
|
42
|
+
return None
|
|
43
|
+
if color.startswith("rgb(") and color.endswith(")"):
|
|
44
|
+
values = color[4:-1].split(",")
|
|
45
|
+
if len(values) == 3:
|
|
46
|
+
try:
|
|
47
|
+
r, g, b = [int(v.strip()) for v in values]
|
|
48
|
+
return RGBColor(r, g, b)
|
|
49
|
+
except ValueError:
|
|
50
|
+
return None
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _parse_float(value: str) -> Optional[float]:
|
|
55
|
+
try:
|
|
56
|
+
return float(value)
|
|
57
|
+
except ValueError:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _parse_background_color(style_map: Dict[str, str]) -> Optional[str]:
|
|
62
|
+
bg = style_map.get("background", "") or style_map.get("background-color", "")
|
|
63
|
+
bg = bg.strip().lower()
|
|
64
|
+
if not bg:
|
|
65
|
+
return None
|
|
66
|
+
if bg in _NAMED_COLORS:
|
|
67
|
+
r, g, b = _NAMED_COLORS[bg]
|
|
68
|
+
return f"{r:02X}{g:02X}{b:02X}"
|
|
69
|
+
if "linear-gradient" in bg and "#" in bg:
|
|
70
|
+
start = bg.find("#")
|
|
71
|
+
return bg[start + 1:start + 7].upper()
|
|
72
|
+
if bg.startswith("#"):
|
|
73
|
+
hex_value = bg[1:]
|
|
74
|
+
if len(hex_value) == 3:
|
|
75
|
+
hex_value = "".join([c * 2 for c in hex_value])
|
|
76
|
+
if len(hex_value) == 6:
|
|
77
|
+
return hex_value.upper()
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _parse_border_left(style_map: Dict[str, str]) -> Optional[Tuple[float, str]]:
|
|
82
|
+
border = style_map.get("border-left", "")
|
|
83
|
+
if border:
|
|
84
|
+
parts = border.split()
|
|
85
|
+
width_pt = _parse_px_to_pt(parts[0]) if parts else None
|
|
86
|
+
color = None
|
|
87
|
+
for part in parts:
|
|
88
|
+
if part.startswith("#"):
|
|
89
|
+
color = part[1:].upper()
|
|
90
|
+
break
|
|
91
|
+
if width_pt and color:
|
|
92
|
+
return width_pt, color
|
|
93
|
+
|
|
94
|
+
width = style_map.get("border-left-width", "")
|
|
95
|
+
color = style_map.get("border-left-color", "")
|
|
96
|
+
if width and color:
|
|
97
|
+
width_pt = _parse_px_to_pt(width)
|
|
98
|
+
color = color[1:].upper() if color.startswith("#") else None
|
|
99
|
+
if width_pt and color:
|
|
100
|
+
return width_pt, color
|
|
101
|
+
|
|
102
|
+
pseudo_width = style_map.get("pseudo-before-width", "")
|
|
103
|
+
pseudo_bg = style_map.get("pseudo-before-background", "") or style_map.get(
|
|
104
|
+
"pseudo-before-background-color", ""
|
|
105
|
+
)
|
|
106
|
+
if pseudo_width and pseudo_bg:
|
|
107
|
+
width_pt = _parse_px_to_pt(pseudo_width)
|
|
108
|
+
color = _parse_color_hex(pseudo_bg)
|
|
109
|
+
if width_pt and color:
|
|
110
|
+
return width_pt, color
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _parse_border(border_value: str) -> Optional[Tuple[float, str]]:
|
|
115
|
+
border = border_value.strip()
|
|
116
|
+
if not border:
|
|
117
|
+
return None
|
|
118
|
+
parts = border.split()
|
|
119
|
+
width_pt = _parse_px_to_pt(parts[0]) if parts else None
|
|
120
|
+
color = None
|
|
121
|
+
for part in parts:
|
|
122
|
+
if part.startswith("#"):
|
|
123
|
+
color = part[1:].upper()
|
|
124
|
+
break
|
|
125
|
+
if part.lower() in _NAMED_COLORS:
|
|
126
|
+
r, g, b = _NAMED_COLORS[part.lower()]
|
|
127
|
+
color = f"{r:02X}{g:02X}{b:02X}"
|
|
128
|
+
break
|
|
129
|
+
if width_pt and color:
|
|
130
|
+
return width_pt, color
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _parse_padding(padding_value: str) -> Optional[Tuple[float, float, float, float]]:
|
|
135
|
+
if not padding_value:
|
|
136
|
+
return None
|
|
137
|
+
parts = [p for p in padding_value.replace(",", " ").split() if p]
|
|
138
|
+
if not parts:
|
|
139
|
+
return None
|
|
140
|
+
values = [_parse_px_to_pt(p) for p in parts]
|
|
141
|
+
values = [v for v in values if v is not None]
|
|
142
|
+
if not values:
|
|
143
|
+
return None
|
|
144
|
+
if len(values) == 1:
|
|
145
|
+
return values[0], values[0], values[0], values[0]
|
|
146
|
+
if len(values) == 2:
|
|
147
|
+
return values[0], values[1], values[0], values[1]
|
|
148
|
+
if len(values) == 3:
|
|
149
|
+
return values[0], values[1], values[2], values[1]
|
|
150
|
+
return values[0], values[1], values[2], values[3]
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _resolve_padding(
|
|
154
|
+
style_map: Dict[str, str],
|
|
155
|
+
) -> Optional[Tuple[Optional[float], Optional[float], Optional[float], Optional[float]]]:
|
|
156
|
+
padding = _parse_padding(style_map.get("padding", ""))
|
|
157
|
+
top, right, bottom, left = padding if padding else (None, None, None, None)
|
|
158
|
+
padding_top = _parse_length_to_pt(style_map.get("padding-top", ""))
|
|
159
|
+
padding_right = _parse_length_to_pt(style_map.get("padding-right", ""))
|
|
160
|
+
padding_bottom = _parse_length_to_pt(style_map.get("padding-bottom", ""))
|
|
161
|
+
padding_left = _parse_length_to_pt(style_map.get("padding-left", ""))
|
|
162
|
+
if padding_top is not None:
|
|
163
|
+
top = padding_top
|
|
164
|
+
if padding_right is not None:
|
|
165
|
+
right = padding_right
|
|
166
|
+
if padding_bottom is not None:
|
|
167
|
+
bottom = padding_bottom
|
|
168
|
+
if padding_left is not None:
|
|
169
|
+
left = padding_left
|
|
170
|
+
if all(value is None for value in (top, right, bottom, left)):
|
|
171
|
+
return None
|
|
172
|
+
return top, right, bottom, left
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _normalize_padding(
|
|
176
|
+
padding: Tuple[Optional[float], Optional[float], Optional[float], Optional[float]],
|
|
177
|
+
) -> Tuple[float, float, float, float]:
|
|
178
|
+
top, right, bottom, left = padding
|
|
179
|
+
return (
|
|
180
|
+
top or 0.0,
|
|
181
|
+
right or 0.0,
|
|
182
|
+
bottom or 0.0,
|
|
183
|
+
left or 0.0,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _parse_px_to_pt(value: str) -> Optional[float]:
|
|
188
|
+
val = value.strip().lower()
|
|
189
|
+
if val.endswith("px"):
|
|
190
|
+
px = _parse_float(val[:-2])
|
|
191
|
+
if px is not None:
|
|
192
|
+
return px * 0.75
|
|
193
|
+
if val.endswith("pt"):
|
|
194
|
+
return _parse_float(val[:-2])
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _parse_length_to_pt(value: str) -> Optional[float]:
|
|
199
|
+
val = value.strip().lower()
|
|
200
|
+
if not val:
|
|
201
|
+
return None
|
|
202
|
+
if val.endswith("pt") or val.endswith("px"):
|
|
203
|
+
return _parse_px_to_pt(val)
|
|
204
|
+
if val.endswith("%"):
|
|
205
|
+
return None
|
|
206
|
+
if val.isdigit():
|
|
207
|
+
return _parse_px_to_pt(f"{val}px")
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _parse_percentage(value: str) -> Optional[float]:
|
|
212
|
+
val = value.strip().replace("%", "")
|
|
213
|
+
try:
|
|
214
|
+
return float(val) / 100.0
|
|
215
|
+
except ValueError:
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _parse_color_hex(value: str) -> Optional[str]:
|
|
220
|
+
color = value.strip().lower()
|
|
221
|
+
if color in _NAMED_COLORS:
|
|
222
|
+
r, g, b = _NAMED_COLORS[color]
|
|
223
|
+
return f"{r:02X}{g:02X}{b:02X}"
|
|
224
|
+
if color.startswith("#"):
|
|
225
|
+
hex_value = color[1:]
|
|
226
|
+
if len(hex_value) == 3:
|
|
227
|
+
hex_value = "".join([c * 2 for c in hex_value])
|
|
228
|
+
if len(hex_value) == 6:
|
|
229
|
+
return hex_value.upper()
|
|
230
|
+
if color.startswith("rgb(") and color.endswith(")"):
|
|
231
|
+
values = color[4:-1].split(",")
|
|
232
|
+
if len(values) == 3:
|
|
233
|
+
try:
|
|
234
|
+
r, g, b = [int(v.strip()) for v in values]
|
|
235
|
+
return f"{r:02X}{g:02X}{b:02X}"
|
|
236
|
+
except ValueError:
|
|
237
|
+
return None
|
|
238
|
+
return None
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _parse_box_values(
|
|
242
|
+
value: str,
|
|
243
|
+
) -> Optional[Tuple[Optional[float], Optional[float], Optional[float], Optional[float]]]:
|
|
244
|
+
if not value:
|
|
245
|
+
return None
|
|
246
|
+
parts = [p for p in value.replace(",", " ").split() if p]
|
|
247
|
+
if not parts:
|
|
248
|
+
return None
|
|
249
|
+
values = [_parse_px_to_pt(p) for p in parts]
|
|
250
|
+
if not values:
|
|
251
|
+
return None
|
|
252
|
+
if len(values) == 1:
|
|
253
|
+
return values[0], values[0], values[0], values[0]
|
|
254
|
+
if len(values) == 2:
|
|
255
|
+
return values[0], values[1], values[0], values[1]
|
|
256
|
+
if len(values) == 3:
|
|
257
|
+
return values[0], values[1], values[2], values[1]
|
|
258
|
+
return values[0], values[1], values[2], values[3]
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _border_sz(width_pt: float) -> str:
|
|
262
|
+
return str(int(width_pt * 8 * _BORDER_SCALE))
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import binascii
|
|
3
|
+
import mimetypes
|
|
4
|
+
import re
|
|
5
|
+
from io import BytesIO
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Dict, Optional
|
|
8
|
+
from urllib.request import Request, urlopen
|
|
9
|
+
|
|
10
|
+
from bs4.element import Tag
|
|
11
|
+
from cairosvg import svg2png
|
|
12
|
+
from docx.shared import Pt
|
|
13
|
+
|
|
14
|
+
from .html_docx_css import _parse_length_to_pt
|
|
15
|
+
from .html_docx_selectors import _compute_style_map
|
|
16
|
+
|
|
17
|
+
_IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".ico", ".bmp", ".avif"}
|
|
18
|
+
|
|
19
|
+
# Fallback max image width when no cell/container constraint is available.
|
|
20
|
+
# ~5.5 inches — fits safely within any standard A4/Letter body.
|
|
21
|
+
_DEFAULT_MAX_IMAGE_WIDTH_PT = 400.0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _is_image_path(src: str) -> bool:
|
|
25
|
+
return Path(src.split("?")[0]).suffix.lower() in _IMAGE_EXTENSIONS
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def embed_local_images(html: str, base_dir: Path) -> str:
|
|
29
|
+
"""Rewrite all image references to inline base64 data URIs.
|
|
30
|
+
|
|
31
|
+
Handles:
|
|
32
|
+
- HTML src= attributes (<img>, <image>)
|
|
33
|
+
- CSS url() background images
|
|
34
|
+
- SVG href= / xlink:href= on <image> elements
|
|
35
|
+
- <object data=> embeds
|
|
36
|
+
|
|
37
|
+
Local paths are resolved relative to base_dir.
|
|
38
|
+
HTTP/HTTPS URLs are fetched and embedded so documents work offline.
|
|
39
|
+
data: URIs, file:// URLs, fragment-only (#) refs, non-image paths, and
|
|
40
|
+
unreachable resources are left unchanged (no crash).
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def _encode(src: str) -> str | None:
|
|
44
|
+
if src.startswith("data:") or src.startswith("file://"):
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
if src.startswith("http://") or src.startswith("https://"):
|
|
48
|
+
try:
|
|
49
|
+
req = Request(src, headers={"User-Agent": "docs-agent"})
|
|
50
|
+
with urlopen(req, timeout=20) as response:
|
|
51
|
+
data = response.read()
|
|
52
|
+
mime, _ = mimetypes.guess_type(src.split("?")[0])
|
|
53
|
+
mime = mime or "image/png"
|
|
54
|
+
return f"data:{mime};base64,{base64.b64encode(data).decode('ascii')}"
|
|
55
|
+
except Exception:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
if not _is_image_path(src):
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
img_path = (base_dir / src).resolve()
|
|
62
|
+
if not img_path.exists():
|
|
63
|
+
return None
|
|
64
|
+
mime, _ = mimetypes.guess_type(str(img_path))
|
|
65
|
+
mime = mime or "image/png"
|
|
66
|
+
return f"data:{mime};base64,{base64.b64encode(img_path.read_bytes()).decode('ascii')}"
|
|
67
|
+
|
|
68
|
+
def replace_src(match: re.Match) -> str:
|
|
69
|
+
quote, src = match.group(1), match.group(2)
|
|
70
|
+
data_uri = _encode(src)
|
|
71
|
+
return f"src={quote}{data_uri}{quote}" if data_uri else match.group(0)
|
|
72
|
+
|
|
73
|
+
def replace_css_url(match: re.Match) -> str:
|
|
74
|
+
quote, src = match.group(1), match.group(2)
|
|
75
|
+
data_uri = _encode(src)
|
|
76
|
+
return f"url({quote}{data_uri}{quote})" if data_uri else match.group(0)
|
|
77
|
+
|
|
78
|
+
def replace_href(match: re.Match) -> str:
|
|
79
|
+
attr, quote, src = match.group(1), match.group(2), match.group(3)
|
|
80
|
+
data_uri = _encode(src)
|
|
81
|
+
return f"{attr}={quote}{data_uri}{quote}" if data_uri else match.group(0)
|
|
82
|
+
|
|
83
|
+
html = re.sub(r'src=(["\'])((?!data:|https?://|file://)[^"\']+)\1', replace_src, html)
|
|
84
|
+
html = re.sub(r'src=(["\'])(https?://[^"\']+)\1', replace_src, html)
|
|
85
|
+
html = re.sub(r"url\(([\"']?)((?!data:|file://)[^\"')>\s]+)\1\)", replace_css_url, html)
|
|
86
|
+
html = re.sub(
|
|
87
|
+
r'(href|xlink:href|data)=(["\'])((?!data:|https?://|file://|#)[^"\']+)\2',
|
|
88
|
+
replace_href,
|
|
89
|
+
html,
|
|
90
|
+
)
|
|
91
|
+
return html
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _add_svg_run(paragraph, node: Tag, parent_style: Dict[str, str]) -> None:
|
|
95
|
+
"""Rasterize an inline <svg> node to PNG and add it as a picture run.
|
|
96
|
+
|
|
97
|
+
BeautifulSoup's html.parser has two SVG serialization bugs:
|
|
98
|
+
- Lowercases attribute names (viewBox → viewbox), breaking cairosvg.
|
|
99
|
+
- Serializes SVG-specific attrs like 'fill' as valueless booleans, making
|
|
100
|
+
paths transparent.
|
|
101
|
+
|
|
102
|
+
We avoid both by reconstructing the SVG XML from the parsed .attrs dicts
|
|
103
|
+
directly rather than using str(node). No HTML string manipulation.
|
|
104
|
+
"""
|
|
105
|
+
style = node.get("style", "") or ""
|
|
106
|
+
m = re.search(r"width\s*:\s*([\d.]+\s*(?:pt|px|em|rem)?)", style, re.IGNORECASE)
|
|
107
|
+
width_pt = _parse_length_to_pt(m.group(1)) if m else None
|
|
108
|
+
output_width = max(100, int((width_pt or 54) * (96 / 72) * 2)) # 2× for retina
|
|
109
|
+
|
|
110
|
+
svg_xml = _svg_node_to_xml(node, output_width)
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
png_bytes = svg2png(bytestring=svg_xml.encode("utf-8"))
|
|
114
|
+
except Exception:
|
|
115
|
+
return # silently skip if conversion fails
|
|
116
|
+
|
|
117
|
+
run = paragraph.add_run()
|
|
118
|
+
_add_picture_safe(run, BytesIO(png_bytes), width_pt, None)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _svg_node_to_xml(node: Tag, output_width: int) -> str:
|
|
122
|
+
"""Reconstruct SVG XML from a BeautifulSoup node, bypassing html.parser serialization bugs.
|
|
123
|
+
|
|
124
|
+
html.parser drops 'fill' values and lowercases 'viewBox'. We build the XML
|
|
125
|
+
directly from node.attrs (which are always correct) and recurse into children.
|
|
126
|
+
Explicit width/height are injected on the root <svg> so cairosvg can render.
|
|
127
|
+
"""
|
|
128
|
+
vb = node.get("viewbox", "") # BeautifulSoup lowercases to 'viewbox'
|
|
129
|
+
parts = re.split(r"[\s,]+", vb.strip())
|
|
130
|
+
if len(parts) == 4:
|
|
131
|
+
try:
|
|
132
|
+
vb_w, vb_h = float(parts[2]), float(parts[3])
|
|
133
|
+
aspect = vb_h / vb_w if vb_w else 1.0
|
|
134
|
+
except ValueError:
|
|
135
|
+
aspect = 1.0
|
|
136
|
+
else:
|
|
137
|
+
aspect = 1.0
|
|
138
|
+
output_height = max(1, int(output_width * aspect))
|
|
139
|
+
|
|
140
|
+
ns = node.get("xmlns", "http://www.w3.org/2000/svg")
|
|
141
|
+
children_xml = "".join(_svg_child_to_xml(c) for c in node.children)
|
|
142
|
+
return (
|
|
143
|
+
f'<svg xmlns="{ns}" viewBox="{vb}" '
|
|
144
|
+
f'width="{output_width}" height="{output_height}">'
|
|
145
|
+
f"{children_xml}</svg>"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _svg_child_to_xml(node) -> str:
|
|
150
|
+
"""Recursively serialize an SVG child node to XML, preserving all attribute values."""
|
|
151
|
+
from bs4.element import NavigableString
|
|
152
|
+
if isinstance(node, NavigableString):
|
|
153
|
+
return str(node)
|
|
154
|
+
if not isinstance(node, Tag):
|
|
155
|
+
return ""
|
|
156
|
+
attrs = " ".join(f'{k}="{v}"' for k, v in node.attrs.items())
|
|
157
|
+
inner = "".join(_svg_child_to_xml(c) for c in node.children)
|
|
158
|
+
if inner:
|
|
159
|
+
return f"<{node.name} {attrs}>{inner}</{node.name}>" if attrs else f"<{node.name}>{inner}</{node.name}>"
|
|
160
|
+
return f"<{node.name} {attrs}/>" if attrs else f"<{node.name}/>"
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _add_image_run(paragraph, node: Tag, parent_style: Dict[str, str]) -> None:
|
|
164
|
+
src = node.get("src", "") or ""
|
|
165
|
+
if not src:
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
width_pt, height_pt = _extract_image_dimensions(node, parent_style)
|
|
169
|
+
image_stream = _load_image_stream(src)
|
|
170
|
+
if image_stream is None:
|
|
171
|
+
alt_text = (node.get("alt", "") or "").strip()
|
|
172
|
+
if alt_text:
|
|
173
|
+
paragraph.add_run(alt_text)
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
run = paragraph.add_run()
|
|
177
|
+
if width_pt and height_pt:
|
|
178
|
+
_add_picture_safe(run, image_stream, width_pt, height_pt)
|
|
179
|
+
elif width_pt:
|
|
180
|
+
_add_picture_safe(run, image_stream, width_pt, None)
|
|
181
|
+
elif height_pt:
|
|
182
|
+
_add_picture_safe(run, image_stream, None, height_pt)
|
|
183
|
+
else:
|
|
184
|
+
_add_picture_safe(run, image_stream, None, None)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _extract_image_dimensions(
|
|
188
|
+
node: Tag, parent_style: Dict[str, str]
|
|
189
|
+
) -> tuple[Optional[float], Optional[float]]:
|
|
190
|
+
style_map = _compute_style_map(node, [])
|
|
191
|
+
width_value = style_map.get("width", "") or node.get("width", "")
|
|
192
|
+
height_value = style_map.get("height", "") or node.get("height", "")
|
|
193
|
+
|
|
194
|
+
width_pt = _parse_length_to_pt(str(width_value)) if width_value else None
|
|
195
|
+
height_pt = _parse_length_to_pt(str(height_value)) if height_value else None
|
|
196
|
+
|
|
197
|
+
if width_pt is None:
|
|
198
|
+
width_pt = (
|
|
199
|
+
_parse_length_to_pt(str(parent_style.get("width", "")))
|
|
200
|
+
if parent_style.get("width")
|
|
201
|
+
else None
|
|
202
|
+
)
|
|
203
|
+
if height_pt is None:
|
|
204
|
+
height_pt = (
|
|
205
|
+
_parse_length_to_pt(str(parent_style.get("height", "")))
|
|
206
|
+
if parent_style.get("height")
|
|
207
|
+
else None
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Determine the available width cap: prefer the cell width injected by the
|
|
211
|
+
# table processor; fall back to a page-body constant for images outside tables.
|
|
212
|
+
cell_width_str = parent_style.get("_cell_width_pt", "")
|
|
213
|
+
max_width_pt = float(cell_width_str) if cell_width_str else _DEFAULT_MAX_IMAGE_WIDTH_PT
|
|
214
|
+
|
|
215
|
+
if width_pt is None:
|
|
216
|
+
# No explicit width (e.g. width:100%) — fill available space and let
|
|
217
|
+
# aspect ratio determine height.
|
|
218
|
+
width_pt = max_width_pt
|
|
219
|
+
height_pt = None
|
|
220
|
+
elif width_pt > max_width_pt:
|
|
221
|
+
# Explicit width exceeds the available space — cap and preserve aspect ratio.
|
|
222
|
+
width_pt = max_width_pt
|
|
223
|
+
height_pt = None
|
|
224
|
+
|
|
225
|
+
return width_pt, height_pt
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _load_image_stream(src: str) -> BytesIO | Path | None:
|
|
229
|
+
if src.startswith("data:image/"):
|
|
230
|
+
return _load_data_image(src)
|
|
231
|
+
|
|
232
|
+
if src.startswith("http://") or src.startswith("https://"):
|
|
233
|
+
try:
|
|
234
|
+
request = Request(src, headers={"User-Agent": "docs-agent"})
|
|
235
|
+
with urlopen(request, timeout=20) as response:
|
|
236
|
+
return BytesIO(response.read())
|
|
237
|
+
except Exception:
|
|
238
|
+
return None
|
|
239
|
+
|
|
240
|
+
path = Path(src)
|
|
241
|
+
if path.exists():
|
|
242
|
+
if path.suffix.lower() == ".svg":
|
|
243
|
+
return _convert_svg_to_png(path.read_bytes())
|
|
244
|
+
return path
|
|
245
|
+
return None
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _load_data_image(src: str) -> BytesIO | None:
|
|
249
|
+
try:
|
|
250
|
+
header, encoded = src.split(",", 1)
|
|
251
|
+
except ValueError:
|
|
252
|
+
return None
|
|
253
|
+
is_base64 = "base64" in header
|
|
254
|
+
if "image/svg+xml" in header:
|
|
255
|
+
svg_bytes = _decode_data_uri(encoded, is_base64)
|
|
256
|
+
return _convert_svg_to_png(svg_bytes)
|
|
257
|
+
data = _decode_data_uri(encoded, is_base64)
|
|
258
|
+
return BytesIO(data) if data else None
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _decode_data_uri(encoded: str, is_base64: bool) -> Optional[bytes]:
|
|
262
|
+
try:
|
|
263
|
+
if is_base64:
|
|
264
|
+
return base64.b64decode(encoded)
|
|
265
|
+
return encoded.encode("utf-8")
|
|
266
|
+
except (binascii.Error, UnicodeEncodeError):
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _convert_svg_to_png(svg_bytes: Optional[bytes]) -> BytesIO | None:
|
|
271
|
+
if not svg_bytes:
|
|
272
|
+
return None
|
|
273
|
+
try:
|
|
274
|
+
png_bytes = svg2png(bytestring=svg_bytes)
|
|
275
|
+
return BytesIO(png_bytes)
|
|
276
|
+
except Exception:
|
|
277
|
+
return None
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _add_picture_safe(
|
|
281
|
+
run, image_stream, width_pt: Optional[float], height_pt: Optional[float]
|
|
282
|
+
) -> None:
|
|
283
|
+
try:
|
|
284
|
+
if width_pt is not None and height_pt is not None:
|
|
285
|
+
run.add_picture(image_stream, width=Pt(width_pt), height=Pt(height_pt))
|
|
286
|
+
elif width_pt is not None:
|
|
287
|
+
run.add_picture(image_stream, width=Pt(width_pt))
|
|
288
|
+
elif height_pt is not None:
|
|
289
|
+
run.add_picture(image_stream, height=Pt(height_pt))
|
|
290
|
+
else:
|
|
291
|
+
run.add_picture(image_stream)
|
|
292
|
+
except Exception:
|
|
293
|
+
run.add_text("[image]")
|