@_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,221 @@
|
|
|
1
|
+
"""Check for content overflowing the slide canvas boundaries."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import tempfile
|
|
7
|
+
from copy import deepcopy
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
|
|
11
|
+
from agency_swarm.tools import BaseTool
|
|
12
|
+
from pydantic import Field
|
|
13
|
+
|
|
14
|
+
# Add slides directory to path for render_slides module
|
|
15
|
+
SLIDES_DIR = Path(__file__).parent.parent.parent / "slides"
|
|
16
|
+
sys.path.insert(0, str(SLIDES_DIR))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CheckSlideCanvasOverflow(BaseTool):
|
|
20
|
+
"""
|
|
21
|
+
Detect slides with content overflowing the original canvas boundaries.
|
|
22
|
+
|
|
23
|
+
This tool works by:
|
|
24
|
+
1. Adding grey padding around each slide
|
|
25
|
+
2. Rendering the padded presentation to images
|
|
26
|
+
3. Inspecting the padding margins for non-grey pixels
|
|
27
|
+
|
|
28
|
+
If content extends beyond the original slide boundaries, it will appear
|
|
29
|
+
in the padding area and be detected as overflow.
|
|
30
|
+
|
|
31
|
+
Returns a list of failing slide indices (1-based) and paths to debug
|
|
32
|
+
images showing the overflow.
|
|
33
|
+
|
|
34
|
+
Requires: LibreOffice (soffice) and pdf2image Python package.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
input_pptx: str = Field(
|
|
38
|
+
...,
|
|
39
|
+
description="Path to the input PowerPoint file (.pptx)",
|
|
40
|
+
)
|
|
41
|
+
max_width_px: int = Field(
|
|
42
|
+
default=1600,
|
|
43
|
+
description="Maximum width in pixels for rendered images (default 1600)",
|
|
44
|
+
)
|
|
45
|
+
max_height_px: int = Field(
|
|
46
|
+
default=900,
|
|
47
|
+
description="Maximum height in pixels for rendered images (default 900)",
|
|
48
|
+
)
|
|
49
|
+
pad_px: int = Field(
|
|
50
|
+
default=100,
|
|
51
|
+
description="Padding in pixels to add on each side (default 100)",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def run(self) -> str:
|
|
55
|
+
"""Check for canvas overflow and return results."""
|
|
56
|
+
input_path = Path(self.input_pptx)
|
|
57
|
+
|
|
58
|
+
# Validate input
|
|
59
|
+
if not input_path.exists():
|
|
60
|
+
return f"Error: Input file not found: {self.input_pptx}"
|
|
61
|
+
if input_path.suffix.lower() != ".pptx":
|
|
62
|
+
return f"Error: Input must be a PowerPoint file (.pptx), got: {input_path.suffix}"
|
|
63
|
+
|
|
64
|
+
# Check for required external tools
|
|
65
|
+
if not self._check_soffice():
|
|
66
|
+
return "Error: LibreOffice (soffice) not found. Please install LibreOffice."
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
import numpy as np
|
|
70
|
+
from pdf2image import convert_from_path
|
|
71
|
+
from PIL import Image
|
|
72
|
+
from pptx import Presentation
|
|
73
|
+
from pptx.dml.color import RGBColor
|
|
74
|
+
from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE
|
|
75
|
+
from pptx.util import Emu
|
|
76
|
+
except ImportError as e:
|
|
77
|
+
return f"Error: Missing required package: {e}"
|
|
78
|
+
|
|
79
|
+
# Import render_slides for DPI calculation
|
|
80
|
+
try:
|
|
81
|
+
import render_slides
|
|
82
|
+
except ImportError:
|
|
83
|
+
return "Error: Could not import render_slides module"
|
|
84
|
+
|
|
85
|
+
# Constants
|
|
86
|
+
EMU_PER_INCH = 914_400
|
|
87
|
+
PAD_RGB = (200, 200, 200)
|
|
88
|
+
|
|
89
|
+
def px_to_emu(px: int, dpi: int) -> Emu:
|
|
90
|
+
return Emu(int(px * EMU_PER_INCH // dpi))
|
|
91
|
+
|
|
92
|
+
# Calculate DPI
|
|
93
|
+
try:
|
|
94
|
+
dpi = render_slides.calc_dpi_via_ooxml(
|
|
95
|
+
str(input_path), self.max_width_px, self.max_height_px
|
|
96
|
+
)
|
|
97
|
+
except Exception as e:
|
|
98
|
+
return f"Error calculating DPI: {e}"
|
|
99
|
+
|
|
100
|
+
# Create temporary directory for work
|
|
101
|
+
tmpdir = tempfile.mkdtemp(prefix="overflow_check_")
|
|
102
|
+
enlarged_pptx = Path(tmpdir) / "enlarged.pptx"
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
# Enlarge the deck with padding
|
|
106
|
+
prs = Presentation(str(input_path))
|
|
107
|
+
w0 = prs.slide_width
|
|
108
|
+
h0 = prs.slide_height
|
|
109
|
+
pad_emu = px_to_emu(self.pad_px, dpi)
|
|
110
|
+
w1 = Emu(w0 + 2 * pad_emu)
|
|
111
|
+
h1 = Emu(h0 + 2 * pad_emu)
|
|
112
|
+
prs.slide_width = w1
|
|
113
|
+
prs.slide_height = h1
|
|
114
|
+
|
|
115
|
+
for slide in prs.slides:
|
|
116
|
+
# Shift all shapes
|
|
117
|
+
for shp in list(slide.shapes):
|
|
118
|
+
shp.left = Emu(int(shp.left) + pad_emu)
|
|
119
|
+
shp.top = Emu(int(shp.top) + pad_emu)
|
|
120
|
+
|
|
121
|
+
# Add padding rectangles
|
|
122
|
+
pads = (
|
|
123
|
+
(Emu(0), Emu(0), pad_emu, h1), # left
|
|
124
|
+
(Emu(int(w1) - int(pad_emu)), Emu(0), pad_emu, h1), # right
|
|
125
|
+
(Emu(0), Emu(0), w1, pad_emu), # top
|
|
126
|
+
(Emu(0), Emu(int(h1) - int(pad_emu)), w1, pad_emu), # bottom
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
sp_tree = slide.shapes._spTree
|
|
130
|
+
|
|
131
|
+
for left, top, width, height in pads:
|
|
132
|
+
pad_shape = slide.shapes.add_shape(
|
|
133
|
+
MSO_AUTO_SHAPE_TYPE.RECTANGLE, left, top, width, height
|
|
134
|
+
)
|
|
135
|
+
pad_shape.fill.solid()
|
|
136
|
+
pad_shape.fill.fore_color.rgb = RGBColor(*PAD_RGB)
|
|
137
|
+
pad_shape.line.fill.background()
|
|
138
|
+
sp_tree.remove(pad_shape._element)
|
|
139
|
+
sp_tree.insert(2, pad_shape._element)
|
|
140
|
+
|
|
141
|
+
prs.save(str(enlarged_pptx))
|
|
142
|
+
|
|
143
|
+
# Render to images
|
|
144
|
+
img_paths = render_slides.rasterize(str(enlarged_pptx), str(Path(tmpdir) / "imgs"), dpi)
|
|
145
|
+
|
|
146
|
+
# Calculate padding ratios
|
|
147
|
+
pad_ratio_w = pad_emu / w1
|
|
148
|
+
pad_ratio_h = pad_emu / h1
|
|
149
|
+
|
|
150
|
+
# Inspect images for overflow
|
|
151
|
+
tol = max(1, round((300 - dpi) / 25)) if dpi < 300 else 0
|
|
152
|
+
tol = min(tol, 10)
|
|
153
|
+
pad_colour = np.array(PAD_RGB, dtype=np.uint8)
|
|
154
|
+
failures = []
|
|
155
|
+
|
|
156
|
+
for idx, img_path in enumerate(img_paths, start=1):
|
|
157
|
+
with Image.open(img_path) as img:
|
|
158
|
+
rgb = img.convert("RGB")
|
|
159
|
+
arr = np.asarray(rgb)
|
|
160
|
+
|
|
161
|
+
h, w, _ = arr.shape
|
|
162
|
+
pad_x = int(w * pad_ratio_w) - 1
|
|
163
|
+
pad_y = int(h * pad_ratio_h) - 1
|
|
164
|
+
|
|
165
|
+
margins = [
|
|
166
|
+
arr[:, :pad_x, :], # left
|
|
167
|
+
arr[:, w - pad_x:, :], # right
|
|
168
|
+
arr[:pad_y, :, :], # top
|
|
169
|
+
arr[h - pad_y:, :, :], # bottom
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
def is_clean(margin):
|
|
173
|
+
diff = np.abs(margin.astype(np.int16) - pad_colour)
|
|
174
|
+
matches = np.all(diff <= tol, axis=-1)
|
|
175
|
+
mismatch_fraction = 1.0 - (np.count_nonzero(matches) / matches.size)
|
|
176
|
+
max_mismatch = 0.01 if dpi >= 300 else (0.02 if dpi >= 200 else 0.03)
|
|
177
|
+
return mismatch_fraction <= max_mismatch
|
|
178
|
+
|
|
179
|
+
if not all(is_clean(m) for m in margins):
|
|
180
|
+
failures.append((idx, img_path))
|
|
181
|
+
|
|
182
|
+
if failures:
|
|
183
|
+
result = f"OVERFLOW DETECTED on {len(failures)} slide(s):\n"
|
|
184
|
+
for idx, img_path in failures:
|
|
185
|
+
result += f" - Slide {idx}: {img_path}\n"
|
|
186
|
+
result += "\nDebug images show grey padding with overflow content visible."
|
|
187
|
+
return result
|
|
188
|
+
else:
|
|
189
|
+
return "No overflow detected. All slides are within canvas boundaries."
|
|
190
|
+
|
|
191
|
+
except Exception as e:
|
|
192
|
+
return f"Error checking overflow: {e}"
|
|
193
|
+
|
|
194
|
+
def _check_soffice(self) -> bool:
|
|
195
|
+
"""Check if LibreOffice is available."""
|
|
196
|
+
try:
|
|
197
|
+
kwargs = {
|
|
198
|
+
"capture_output": True,
|
|
199
|
+
"timeout": 15,
|
|
200
|
+
"text": True,
|
|
201
|
+
"input": "\n",
|
|
202
|
+
}
|
|
203
|
+
if os.name == "nt":
|
|
204
|
+
kwargs["creationflags"] = 0x08000000 # CREATE_NO_WINDOW
|
|
205
|
+
|
|
206
|
+
soffice_bin = "soffice.com" if os.name == "nt" else "soffice"
|
|
207
|
+
subprocess.run([soffice_bin, "--version"], **kwargs)
|
|
208
|
+
return True
|
|
209
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
if __name__ == "__main__":
|
|
214
|
+
# Test with a sample file if available
|
|
215
|
+
test_pptx = Path(__file__).parent.parent / "files" / "test.pptx"
|
|
216
|
+
if test_pptx.exists():
|
|
217
|
+
tool = CheckSlideCanvasOverflow(input_pptx=str(test_pptx))
|
|
218
|
+
print(tool.run())
|
|
219
|
+
else:
|
|
220
|
+
print(f"Test file not found: {test_pptx}")
|
|
221
|
+
print("Tool definition is valid.")
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""Create labeled montage images from a collection of images."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import tempfile
|
|
5
|
+
from math import ceil
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING, List, Literal, Optional
|
|
8
|
+
|
|
9
|
+
from agency_swarm.tools import BaseTool
|
|
10
|
+
from pydantic import Field
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from PIL import Image
|
|
14
|
+
|
|
15
|
+
# Supported image extensions (same as EnsureRasterImage)
|
|
16
|
+
RASTER_EXTS = {".png", ".jpg", ".jpeg", ".bmp", ".gif", ".tif", ".tiff", ".webp"}
|
|
17
|
+
CONVERTIBLE_EXTS = {
|
|
18
|
+
".emf", ".wmf", ".emz", ".wmz",
|
|
19
|
+
".svg", ".svgz",
|
|
20
|
+
".wdp", ".jxr",
|
|
21
|
+
".heic", ".heif",
|
|
22
|
+
".pdf", ".eps", ".ps",
|
|
23
|
+
}
|
|
24
|
+
SUPPORTED_EXTS = RASTER_EXTS | CONVERTIBLE_EXTS
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CreateImageMontage(BaseTool):
|
|
28
|
+
"""
|
|
29
|
+
Create a labeled montage image from a collection of images.
|
|
30
|
+
|
|
31
|
+
Arranges images in a grid layout with optional labels (numbers or filenames).
|
|
32
|
+
Useful for:
|
|
33
|
+
- Reviewing slide images extracted from presentations
|
|
34
|
+
- Comparing multiple versions of assets
|
|
35
|
+
- Creating visual indexes of image collections
|
|
36
|
+
|
|
37
|
+
Non-raster formats (SVG, EMF, etc.) are automatically converted to PNG
|
|
38
|
+
before inclusion in the montage.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
input_files: Optional[List[str]] = Field(
|
|
42
|
+
default=None,
|
|
43
|
+
description="List of image file paths (mutually exclusive with input_dir)",
|
|
44
|
+
)
|
|
45
|
+
input_dir: Optional[str] = Field(
|
|
46
|
+
default=None,
|
|
47
|
+
description="Directory containing images (mutually exclusive with input_files)",
|
|
48
|
+
)
|
|
49
|
+
output_file: str = Field(
|
|
50
|
+
...,
|
|
51
|
+
description="Path for the output montage image",
|
|
52
|
+
)
|
|
53
|
+
num_col: int = Field(
|
|
54
|
+
default=5,
|
|
55
|
+
description="Number of columns in the grid (default 5)",
|
|
56
|
+
)
|
|
57
|
+
cell_width: int = Field(
|
|
58
|
+
default=400,
|
|
59
|
+
description="Width of each cell in pixels (default 400)",
|
|
60
|
+
)
|
|
61
|
+
cell_height: int = Field(
|
|
62
|
+
default=225,
|
|
63
|
+
description="Height of each cell in pixels (default 225)",
|
|
64
|
+
)
|
|
65
|
+
gap: int = Field(
|
|
66
|
+
default=16,
|
|
67
|
+
description="Gap between cells in pixels (default 16)",
|
|
68
|
+
)
|
|
69
|
+
label_mode: Literal["number", "filename", "none"] = Field(
|
|
70
|
+
default="number",
|
|
71
|
+
description="Label mode: 'number' (1-based index), 'filename', or 'none'",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def run(self) -> str:
|
|
75
|
+
"""Create the montage and return the output path."""
|
|
76
|
+
try:
|
|
77
|
+
from PIL import Image, ImageDraw, ImageFont, ImageOps
|
|
78
|
+
except ImportError:
|
|
79
|
+
return "Error: PIL/Pillow not installed"
|
|
80
|
+
|
|
81
|
+
# Validate inputs
|
|
82
|
+
if self.input_files and self.input_dir:
|
|
83
|
+
return "Error: Specify either input_files or input_dir, not both"
|
|
84
|
+
if not self.input_files and not self.input_dir:
|
|
85
|
+
return "Error: Must specify either input_files or input_dir"
|
|
86
|
+
|
|
87
|
+
# Gather input files
|
|
88
|
+
if self.input_files:
|
|
89
|
+
input_paths = [Path(f) for f in self.input_files]
|
|
90
|
+
for p in input_paths:
|
|
91
|
+
if not p.exists():
|
|
92
|
+
return f"Error: File not found: {p}"
|
|
93
|
+
else:
|
|
94
|
+
input_dir = Path(self.input_dir)
|
|
95
|
+
if not input_dir.is_dir():
|
|
96
|
+
return f"Error: Directory not found: {self.input_dir}"
|
|
97
|
+
# Natural sort for proper ordering (slide-1, slide-2, ..., slide-10)
|
|
98
|
+
input_paths = sorted(
|
|
99
|
+
[p for p in input_dir.iterdir() if p.suffix.lower() in SUPPORTED_EXTS],
|
|
100
|
+
key=lambda p: self._natural_key(p.name)
|
|
101
|
+
)
|
|
102
|
+
if not input_paths:
|
|
103
|
+
return f"Error: No supported images found in {self.input_dir}"
|
|
104
|
+
|
|
105
|
+
# Validate parameters
|
|
106
|
+
if self.num_col <= 0:
|
|
107
|
+
return "Error: num_col must be positive"
|
|
108
|
+
if self.cell_width <= 0 or self.cell_height <= 0:
|
|
109
|
+
return "Error: cell_width and cell_height must be positive"
|
|
110
|
+
|
|
111
|
+
# Load images (converting non-raster formats as needed)
|
|
112
|
+
labels = [p.name for p in input_paths]
|
|
113
|
+
images = []
|
|
114
|
+
placeholder = None
|
|
115
|
+
|
|
116
|
+
with tempfile.TemporaryDirectory(prefix="montage_convert_") as tmp_dir:
|
|
117
|
+
for p in input_paths:
|
|
118
|
+
try:
|
|
119
|
+
img_path = self._ensure_raster(p, tmp_dir)
|
|
120
|
+
images.append(Image.open(img_path))
|
|
121
|
+
except Exception:
|
|
122
|
+
pass
|
|
123
|
+
images.append(None)
|
|
124
|
+
|
|
125
|
+
# Check we have at least one valid image
|
|
126
|
+
valid_count = sum(1 for img in images if img is not None)
|
|
127
|
+
if valid_count == 0:
|
|
128
|
+
return "Error: No valid images to render"
|
|
129
|
+
|
|
130
|
+
# Create placeholder for failed images
|
|
131
|
+
if valid_count < len(images):
|
|
132
|
+
placeholder = self._make_placeholder(
|
|
133
|
+
int(min(self.cell_width, self.cell_height) * 0.6)
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Calculate grid dimensions
|
|
137
|
+
cols = self.num_col
|
|
138
|
+
rows = ceil(len(images) / cols)
|
|
139
|
+
|
|
140
|
+
# Set up font
|
|
141
|
+
try:
|
|
142
|
+
font_size = max(12, min(36, int(self.cell_height * 0.12)))
|
|
143
|
+
font = ImageFont.truetype("arial.ttf", font_size)
|
|
144
|
+
except Exception:
|
|
145
|
+
font = ImageFont.load_default()
|
|
146
|
+
font_size = 12
|
|
147
|
+
|
|
148
|
+
# Calculate label height
|
|
149
|
+
draw_labels = self.label_mode != "none"
|
|
150
|
+
label_height = 0
|
|
151
|
+
if draw_labels:
|
|
152
|
+
temp_img = Image.new("RGB", (10, 10))
|
|
153
|
+
temp_draw = ImageDraw.Draw(temp_img)
|
|
154
|
+
sample = "1" if self.label_mode == "number" else "Ag"
|
|
155
|
+
bbox = temp_draw.textbbox((0, 0), sample, font=font)
|
|
156
|
+
label_height = ceil(bbox[3] - bbox[1]) + 6
|
|
157
|
+
|
|
158
|
+
row_h = self.cell_height + label_height
|
|
159
|
+
canvas_w = cols * self.cell_width + (cols + 1) * self.gap
|
|
160
|
+
canvas_h = rows * row_h + (rows + 1) * self.gap
|
|
161
|
+
|
|
162
|
+
# Create canvas
|
|
163
|
+
canvas = Image.new("RGB", (canvas_w, canvas_h), (242, 242, 242))
|
|
164
|
+
draw = ImageDraw.Draw(canvas)
|
|
165
|
+
|
|
166
|
+
# Place images
|
|
167
|
+
for idx, img in enumerate(images):
|
|
168
|
+
col = idx % cols
|
|
169
|
+
row = idx // cols
|
|
170
|
+
x0 = self.gap + col * (self.cell_width + self.gap)
|
|
171
|
+
y0 = self.gap + row * (row_h + self.gap)
|
|
172
|
+
|
|
173
|
+
# Prepare label
|
|
174
|
+
if self.label_mode == "number":
|
|
175
|
+
label = str(idx + 1)
|
|
176
|
+
elif self.label_mode == "filename":
|
|
177
|
+
label = labels[idx]
|
|
178
|
+
else:
|
|
179
|
+
label = ""
|
|
180
|
+
|
|
181
|
+
# Get image to display
|
|
182
|
+
if img is not None:
|
|
183
|
+
resized = ImageOps.contain(
|
|
184
|
+
img.convert("RGBA"),
|
|
185
|
+
(self.cell_width, self.cell_height),
|
|
186
|
+
method=Image.Resampling.LANCZOS,
|
|
187
|
+
)
|
|
188
|
+
else:
|
|
189
|
+
resized = placeholder
|
|
190
|
+
|
|
191
|
+
# Calculate position
|
|
192
|
+
paste_x = x0 + (self.cell_width - resized.width) // 2
|
|
193
|
+
paste_y = y0 + (self.cell_height - resized.height) // 2
|
|
194
|
+
|
|
195
|
+
# Paste image
|
|
196
|
+
canvas.paste(
|
|
197
|
+
resized,
|
|
198
|
+
(paste_x, paste_y),
|
|
199
|
+
mask=resized.split()[3] if resized.mode == "RGBA" else None,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# Draw border
|
|
203
|
+
draw.rectangle(
|
|
204
|
+
[paste_x - 1, paste_y - 1, paste_x + resized.width, paste_y + resized.height],
|
|
205
|
+
outline=(160, 160, 160),
|
|
206
|
+
width=1,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Draw label
|
|
210
|
+
if draw_labels and label:
|
|
211
|
+
bbox = draw.textbbox((0, 0), label, font=font)
|
|
212
|
+
text_w = bbox[2] - bbox[0]
|
|
213
|
+
tx = x0 + (self.cell_width - text_w) // 2
|
|
214
|
+
ty = y0 + self.cell_height + 3
|
|
215
|
+
draw.text((tx, ty), label, font=font, fill=(0, 0, 0))
|
|
216
|
+
|
|
217
|
+
# Save output
|
|
218
|
+
output_path = Path(self.output_file)
|
|
219
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
220
|
+
canvas.save(str(output_path))
|
|
221
|
+
|
|
222
|
+
return f"Montage saved to: {output_path}\n{len(images)} images in {rows}x{cols} grid"
|
|
223
|
+
|
|
224
|
+
def _natural_key(self, s: str) -> list:
|
|
225
|
+
"""Key function for natural sorting (e.g., slide2 before slide10)."""
|
|
226
|
+
return [int(part) if part.isdigit() else part for part in re.split(r"(\d+)", s)]
|
|
227
|
+
|
|
228
|
+
def _make_placeholder(self, size: int) -> "Image.Image":
|
|
229
|
+
"""Create a placeholder image for failed loads."""
|
|
230
|
+
from PIL import Image, ImageDraw
|
|
231
|
+
|
|
232
|
+
ph = Image.new("RGBA", (size, size), (220, 220, 220, 255))
|
|
233
|
+
draw = ImageDraw.Draw(ph)
|
|
234
|
+
draw.line([(0, 0), (size - 1, size - 1)], fill=(180, 0, 0, 255), width=3)
|
|
235
|
+
draw.line([(size - 1, 0), (0, size - 1)], fill=(180, 0, 0, 255), width=3)
|
|
236
|
+
return ph
|
|
237
|
+
|
|
238
|
+
def _ensure_raster(self, path: Path, tmp_dir: str) -> str:
|
|
239
|
+
"""Ensure the image is in a raster format, converting if needed."""
|
|
240
|
+
ext = path.suffix.lower()
|
|
241
|
+
if ext in RASTER_EXTS:
|
|
242
|
+
return str(path)
|
|
243
|
+
|
|
244
|
+
# Use EnsureRasterImage tool logic for conversion
|
|
245
|
+
from .EnsureRasterImage import EnsureRasterImage
|
|
246
|
+
|
|
247
|
+
tool = EnsureRasterImage(input_path=str(path), output_dir=tmp_dir)
|
|
248
|
+
result = tool.run()
|
|
249
|
+
|
|
250
|
+
if result.startswith("Error"):
|
|
251
|
+
raise RuntimeError(result)
|
|
252
|
+
|
|
253
|
+
# Extract the output path from the result
|
|
254
|
+
if result.startswith("Converted to:"):
|
|
255
|
+
return result.replace("Converted to:", "").strip()
|
|
256
|
+
return str(path)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
if __name__ == "__main__":
|
|
260
|
+
print("CreateImageMontage tool definition is valid.")
|
|
261
|
+
print(f"Supported formats: {', '.join(sorted(SUPPORTED_EXTS))}")
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Create thumbnail grids from PowerPoint presentation slides."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import tempfile
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from agency_swarm.tools import BaseTool
|
|
9
|
+
from pydantic import Field
|
|
10
|
+
|
|
11
|
+
# Add pptx/scripts to path for thumbnail module
|
|
12
|
+
PPTX_SCRIPTS_DIR = Path(__file__).parent.parent / "pptx" / "scripts"
|
|
13
|
+
sys.path.insert(0, str(PPTX_SCRIPTS_DIR))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CreatePptxThumbnailGrid(BaseTool):
|
|
17
|
+
"""
|
|
18
|
+
Create visual thumbnail grids from PowerPoint presentation slides.
|
|
19
|
+
|
|
20
|
+
Converts all slides to images and arranges them in a large, unlabeled grid layout.
|
|
21
|
+
Useful for:
|
|
22
|
+
- Template analysis: quickly understand slide layouts and design patterns
|
|
23
|
+
- Content review: visual overview of entire presentation
|
|
24
|
+
- Quality check: verify all slides are properly formatted
|
|
25
|
+
- Navigation reference: find specific slides by visual appearance
|
|
26
|
+
|
|
27
|
+
Grid limits by column count:
|
|
28
|
+
- 3 cols: max 12 slides per grid (3×4)
|
|
29
|
+
- 4 cols: max 20 slides per grid (4×5)
|
|
30
|
+
- 5 cols: max 30 slides per grid (5×6) [default]
|
|
31
|
+
- 6 cols: max 42 slides per grid (6×7)
|
|
32
|
+
|
|
33
|
+
For large presentations, multiple numbered grid files are created automatically.
|
|
34
|
+
|
|
35
|
+
Requires: LibreOffice (soffice) and Poppler (pdftoppm) to be installed.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
input_pptx: str = Field(
|
|
39
|
+
...,
|
|
40
|
+
description="Path to the input PowerPoint file (.pptx)",
|
|
41
|
+
)
|
|
42
|
+
output_prefix: str = Field(
|
|
43
|
+
default="thumbnails",
|
|
44
|
+
description="Output prefix for image files (creates prefix.jpg or prefix-N.jpg for multiple grids)",
|
|
45
|
+
)
|
|
46
|
+
cols: int = Field(
|
|
47
|
+
default=5,
|
|
48
|
+
description="Number of columns in the grid (1-6, default 5). Auto-reduces for small decks.",
|
|
49
|
+
)
|
|
50
|
+
outline_placeholders: bool = Field(
|
|
51
|
+
default=False,
|
|
52
|
+
description="If True, outline text placeholders with red borders for visibility",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def run(self) -> str:
|
|
56
|
+
"""Create thumbnail grids and return list of generated files."""
|
|
57
|
+
input_path = Path(self.input_pptx)
|
|
58
|
+
|
|
59
|
+
# Validate input
|
|
60
|
+
if not input_path.exists():
|
|
61
|
+
return f"Error: Input file not found: {self.input_pptx}"
|
|
62
|
+
if input_path.suffix.lower() != ".pptx":
|
|
63
|
+
return f"Error: Input must be a PowerPoint file (.pptx), got: {input_path.suffix}"
|
|
64
|
+
|
|
65
|
+
# Check for required external tools
|
|
66
|
+
if not self._check_soffice():
|
|
67
|
+
return "Error: LibreOffice (soffice) not found. Please install LibreOffice."
|
|
68
|
+
if not self._check_pdftoppm():
|
|
69
|
+
return "Error: pdftoppm not found. Please install Poppler (poppler-utils)."
|
|
70
|
+
|
|
71
|
+
# Import and run thumbnail creation
|
|
72
|
+
from thumbnail import ( # type: ignore[import-not-found]
|
|
73
|
+
convert_to_images,
|
|
74
|
+
create_grids,
|
|
75
|
+
get_placeholder_regions,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
prefix_path = Path(self.output_prefix)
|
|
79
|
+
if prefix_path.parent == Path("."):
|
|
80
|
+
prefix_path = input_path.parent / prefix_path.name
|
|
81
|
+
output_path = prefix_path.with_suffix(".jpg")
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
85
|
+
# Get placeholder regions if outlining is enabled
|
|
86
|
+
placeholder_regions = None
|
|
87
|
+
slide_dimensions = None
|
|
88
|
+
if self.outline_placeholders:
|
|
89
|
+
placeholder_regions, slide_dimensions = get_placeholder_regions(
|
|
90
|
+
input_path
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Convert slides to images
|
|
94
|
+
slide_images = convert_to_images(input_path, Path(temp_dir), 100)
|
|
95
|
+
if not slide_images:
|
|
96
|
+
return "Error: No slides found in presentation"
|
|
97
|
+
|
|
98
|
+
# Validate columns after slide count is known
|
|
99
|
+
cols = max(1, min(6, self.cols))
|
|
100
|
+
cols = min(cols, len(slide_images))
|
|
101
|
+
if cols != self.cols:
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
# Create grids
|
|
105
|
+
grid_files = create_grids(
|
|
106
|
+
slide_images,
|
|
107
|
+
cols,
|
|
108
|
+
420, # thumbnail width
|
|
109
|
+
output_path,
|
|
110
|
+
placeholder_regions,
|
|
111
|
+
slide_dimensions,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
f"Created {len(grid_files)} thumbnail grid(s):\n"
|
|
116
|
+
+ "\n".join(f" - {f}" for f in grid_files)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
except Exception as e:
|
|
120
|
+
return f"Error creating thumbnails: {e}"
|
|
121
|
+
|
|
122
|
+
def _check_soffice(self) -> bool:
|
|
123
|
+
"""Check if LibreOffice is available."""
|
|
124
|
+
try:
|
|
125
|
+
kwargs = {
|
|
126
|
+
"capture_output": True,
|
|
127
|
+
"timeout": 15,
|
|
128
|
+
"text": True,
|
|
129
|
+
"input": "\n",
|
|
130
|
+
}
|
|
131
|
+
if os.name == "nt":
|
|
132
|
+
kwargs["creationflags"] = 0x08000000 # CREATE_NO_WINDOW
|
|
133
|
+
|
|
134
|
+
soffice_bin = "soffice.com" if os.name == "nt" else "soffice"
|
|
135
|
+
subprocess.run([soffice_bin, "--version"], **kwargs)
|
|
136
|
+
return True
|
|
137
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
def _check_pdftoppm(self) -> bool:
|
|
141
|
+
"""Check if pdftoppm is available."""
|
|
142
|
+
try:
|
|
143
|
+
kwargs = {
|
|
144
|
+
"capture_output": True,
|
|
145
|
+
"timeout": 5,
|
|
146
|
+
"stdin": subprocess.DEVNULL,
|
|
147
|
+
}
|
|
148
|
+
if os.name == "nt":
|
|
149
|
+
kwargs["creationflags"] = 0x08000000 # CREATE_NO_WINDOW
|
|
150
|
+
|
|
151
|
+
subprocess.run(["pdftoppm", "-v"], **kwargs)
|
|
152
|
+
return True
|
|
153
|
+
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
if __name__ == "__main__":
|
|
158
|
+
# Test with a sample file if available
|
|
159
|
+
test_pptx = Path(__file__).parent.parent / "files" / "test.pptx"
|
|
160
|
+
if test_pptx.exists():
|
|
161
|
+
tool = CreatePptxThumbnailGrid(
|
|
162
|
+
input_pptx=str(test_pptx),
|
|
163
|
+
output_prefix="/tmp/test_thumbnails",
|
|
164
|
+
)
|
|
165
|
+
print(tool.run())
|
|
166
|
+
else:
|
|
167
|
+
print(f"Test file not found: {test_pptx}")
|
|
168
|
+
print("Tool definition is valid.")
|