@_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,127 @@
|
|
|
1
|
+
"""Search images across Unsplash, Pexels, and Pixabay."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from urllib.parse import urlencode
|
|
7
|
+
from urllib.request import Request, urlopen
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from agency_swarm.tools import BaseTool
|
|
11
|
+
from pydantic import Field
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ImageSearch(BaseTool):
|
|
15
|
+
"""
|
|
16
|
+
Search for existing images on the internet. Use this when the user wants to find real photos, diagrams, or illustrations of something rather than generating new images.
|
|
17
|
+
Do not use this tool to find specific logos, icons, or other brand-specific images. It only provides generic images that are not brand-specific.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
query: str = Field(
|
|
21
|
+
...,
|
|
22
|
+
description="Image search query",
|
|
23
|
+
)
|
|
24
|
+
per_page: int = Field(
|
|
25
|
+
default=6,
|
|
26
|
+
description="Number of results per provider",
|
|
27
|
+
)
|
|
28
|
+
providers: list[str] | None = Field(
|
|
29
|
+
default=None,
|
|
30
|
+
description="Providers to search: unsplash, pexels, pixabay",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def run(self) -> str:
|
|
34
|
+
providers = [p.lower() for p in (self.providers or ["unsplash", "pexels", "pixabay"])]
|
|
35
|
+
results = []
|
|
36
|
+
warnings = []
|
|
37
|
+
if "unsplash" in providers:
|
|
38
|
+
key = os.getenv("UNSPLASH_ACCESS_KEY")
|
|
39
|
+
if not key:
|
|
40
|
+
warnings.append("Unsplash skipped: UNSPLASH_ACCESS_KEY not set.")
|
|
41
|
+
else:
|
|
42
|
+
url = "https://api.unsplash.com/search/photos?" + urlencode({
|
|
43
|
+
"query": self.query,
|
|
44
|
+
"per_page": self.per_page,
|
|
45
|
+
})
|
|
46
|
+
req = Request(url, headers={"Authorization": f"Client-ID {key}"})
|
|
47
|
+
with urlopen(req, timeout=20) as response:
|
|
48
|
+
data = json.loads(response.read().decode("utf-8"))
|
|
49
|
+
for item in data.get("results", []):
|
|
50
|
+
results.append({
|
|
51
|
+
"source": "unsplash",
|
|
52
|
+
"image_url": item.get("urls", {}).get("regular"),
|
|
53
|
+
"thumbnail_url": item.get("urls", {}).get("thumb"),
|
|
54
|
+
"description": item.get("description") or item.get("alt_description") or "",
|
|
55
|
+
"photographer": (item.get("user") or {}).get("name"),
|
|
56
|
+
"width": item.get("width"),
|
|
57
|
+
"height": item.get("height"),
|
|
58
|
+
"link": item.get("links", {}).get("html"),
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
if "pexels" in providers:
|
|
62
|
+
key = os.getenv("PEXELS_API_KEY")
|
|
63
|
+
if not key:
|
|
64
|
+
warnings.append("Pexels skipped: PEXELS_API_KEY not set.")
|
|
65
|
+
else:
|
|
66
|
+
url = "https://api.pexels.com/v1/search?" + urlencode({
|
|
67
|
+
"query": self.query,
|
|
68
|
+
"per_page": self.per_page,
|
|
69
|
+
})
|
|
70
|
+
req = Request(url, headers={"Authorization": key})
|
|
71
|
+
with urlopen(req, timeout=20) as response:
|
|
72
|
+
data = json.loads(response.read().decode("utf-8"))
|
|
73
|
+
for item in data.get("photos", []):
|
|
74
|
+
results.append({
|
|
75
|
+
"source": "pexels",
|
|
76
|
+
"image_url": (item.get("src") or {}).get("large"),
|
|
77
|
+
"thumbnail_url": (item.get("src") or {}).get("tiny"),
|
|
78
|
+
"description": item.get("alt") or "",
|
|
79
|
+
"photographer": item.get("photographer"),
|
|
80
|
+
"width": item.get("width"),
|
|
81
|
+
"height": item.get("height"),
|
|
82
|
+
"link": item.get("url"),
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
if "pixabay" in providers:
|
|
86
|
+
key = os.getenv("PIXABAY_API_KEY")
|
|
87
|
+
if not key:
|
|
88
|
+
warnings.append("Pixabay skipped: PIXABAY_API_KEY not set.")
|
|
89
|
+
else:
|
|
90
|
+
url = "https://pixabay.com/api/?" + urlencode({
|
|
91
|
+
"key": key,
|
|
92
|
+
"q": self.query,
|
|
93
|
+
"per_page": self.per_page,
|
|
94
|
+
})
|
|
95
|
+
req = Request(url)
|
|
96
|
+
with urlopen(req, timeout=20) as response:
|
|
97
|
+
data = json.loads(response.read().decode("utf-8"))
|
|
98
|
+
for item in data.get("hits", []):
|
|
99
|
+
results.append({
|
|
100
|
+
"source": "pixabay",
|
|
101
|
+
"image_url": item.get("largeImageURL"),
|
|
102
|
+
"thumbnail_url": item.get("previewURL"),
|
|
103
|
+
"description": item.get("tags") or "",
|
|
104
|
+
"photographer": item.get("user"),
|
|
105
|
+
"width": item.get("imageWidth"),
|
|
106
|
+
"height": item.get("imageHeight"),
|
|
107
|
+
"link": item.get("pageURL"),
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
if not results:
|
|
111
|
+
if len(warnings) == len(providers):
|
|
112
|
+
raise ValueError(
|
|
113
|
+
"No image source keys are set. Add at least one of "
|
|
114
|
+
"UNSPLASH_ACCESS_KEY, PEXELS_API_KEY, or PIXABAY_API_KEY to your .env to use ImageSearch."
|
|
115
|
+
)
|
|
116
|
+
return f"No images found for '{self.query}'."
|
|
117
|
+
|
|
118
|
+
return json.dumps({
|
|
119
|
+
"query": self.query,
|
|
120
|
+
"results": results,
|
|
121
|
+
"warnings": warnings,
|
|
122
|
+
}, indent=2)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
if __name__ == "__main__":
|
|
126
|
+
tool = ImageSearch(query="abstract digital technology mesh background")
|
|
127
|
+
print(tool.run())
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Insert blank slide placeholders before a position.
|
|
3
|
+
|
|
4
|
+
Uses task_brief to plan outline and execution order. Slide content is generated later via ModifySlide.
|
|
5
|
+
Do not use in parallel with other tools.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
import threading
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Literal
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
from agency_swarm import Agent, ModelSettings, Reasoning
|
|
17
|
+
from agency_swarm.tools import BaseTool
|
|
18
|
+
from agents.extensions.models.litellm_model import LitellmModel
|
|
19
|
+
from pydantic import BaseModel, Field, ValidationError
|
|
20
|
+
|
|
21
|
+
from .slide_file_utils import (
|
|
22
|
+
apply_renames,
|
|
23
|
+
build_slide_name,
|
|
24
|
+
compute_pad_width,
|
|
25
|
+
get_project_dir,
|
|
26
|
+
list_slide_files,
|
|
27
|
+
)
|
|
28
|
+
from .slide_html_utils import ensure_full_html
|
|
29
|
+
from .template_registry import load_template_index
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_PLANNER_MODEL_CLAUDE = "anthropic/claude-sonnet-4-6"
|
|
33
|
+
_PLANNER_MODEL_OAI = "gpt-5.2-codex"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class _PlanSlide(BaseModel):
|
|
37
|
+
page: int
|
|
38
|
+
title: str
|
|
39
|
+
content: str
|
|
40
|
+
template_key: str | None = None
|
|
41
|
+
template_name: str | None = None
|
|
42
|
+
template_status: Literal["existing", "new"] | None = None
|
|
43
|
+
depends_on: int | None = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class _PlanResponse(BaseModel):
|
|
47
|
+
slides: list[_PlanSlide]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _make_planner_agent(caller_model=None) -> Agent:
|
|
51
|
+
"""Create a fresh, stateless agent instance for one InsertNewSlides call.
|
|
52
|
+
|
|
53
|
+
Model priority:
|
|
54
|
+
1. ANTHROPIC_API_KEY in env → Claude Sonnet 4.6 (best planning quality)
|
|
55
|
+
2. caller_model → inherit from the calling agent (covers /auth TUI flow)
|
|
56
|
+
"""
|
|
57
|
+
anthropic_key = os.getenv("ANTHROPIC_API_KEY")
|
|
58
|
+
if anthropic_key:
|
|
59
|
+
model = LitellmModel(model=_PLANNER_MODEL_CLAUDE, api_key=anthropic_key)
|
|
60
|
+
else:
|
|
61
|
+
model = caller_model or _PLANNER_MODEL_OAI
|
|
62
|
+
return Agent(
|
|
63
|
+
name="Slide Planner",
|
|
64
|
+
description="Creates structured slide outline plans.",
|
|
65
|
+
instructions=(
|
|
66
|
+
"You generate JSON plans for slide creation. "
|
|
67
|
+
"Output must be valid JSON only, no markdown fences, no extra text."
|
|
68
|
+
),
|
|
69
|
+
tools=[],
|
|
70
|
+
model=model,
|
|
71
|
+
model_settings=ModelSettings(
|
|
72
|
+
reasoning=Reasoning(effort="high", summary="auto"),
|
|
73
|
+
verbosity="medium",
|
|
74
|
+
),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _run_awaitable(awaitable):
|
|
79
|
+
box: dict[str, object] = {}
|
|
80
|
+
err: dict[str, BaseException] = {}
|
|
81
|
+
|
|
82
|
+
def _worker() -> None:
|
|
83
|
+
try:
|
|
84
|
+
box["result"] = asyncio.run(awaitable)
|
|
85
|
+
except BaseException as exc: # noqa: BLE001
|
|
86
|
+
err["error"] = exc
|
|
87
|
+
|
|
88
|
+
thread = threading.Thread(
|
|
89
|
+
target=_worker,
|
|
90
|
+
name="insert-slides-awaitable-worker",
|
|
91
|
+
daemon=True,
|
|
92
|
+
)
|
|
93
|
+
thread.start()
|
|
94
|
+
thread.join(timeout=180)
|
|
95
|
+
if thread.is_alive():
|
|
96
|
+
raise TimeoutError("InsertNewSlides planner timed out after 180s")
|
|
97
|
+
if "error" in err:
|
|
98
|
+
raise err["error"]
|
|
99
|
+
return box.get("result")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _extract_json_block(text: str) -> str:
|
|
103
|
+
raw = (text or "").strip()
|
|
104
|
+
if not raw:
|
|
105
|
+
return ""
|
|
106
|
+
match = re.search(r"```(?:json)?\s*(.*?)```", raw, flags=re.IGNORECASE | re.DOTALL)
|
|
107
|
+
return (match.group(1) if match else raw).strip()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _build_planner_prompt(
|
|
111
|
+
task_brief: str,
|
|
112
|
+
count: int,
|
|
113
|
+
insert_position: int,
|
|
114
|
+
existing_templates: dict[str, dict[str, str]],
|
|
115
|
+
) -> str:
|
|
116
|
+
template_lines = []
|
|
117
|
+
for key, value in existing_templates.items():
|
|
118
|
+
name = str(value.get("name", key))
|
|
119
|
+
template_lines.append(f'- "{key}": "{name}"')
|
|
120
|
+
template_block = "\n".join(template_lines) if template_lines else "(none)"
|
|
121
|
+
return (
|
|
122
|
+
"Create a structured plan for inserting blank slides. "
|
|
123
|
+
"Return JSON only with shape:\n"
|
|
124
|
+
'{ "slides": [ { "page": int, "title": str, "content": str, "template_key": str|null, "template_name": str|null, "template_status": "existing"|"new"|null, "depends_on": int|null } ] }\n'
|
|
125
|
+
f"Constraints:\n- exactly {count} slides\n"
|
|
126
|
+
f"- pages must be contiguous from {insert_position} to {insert_position + count - 1}\n"
|
|
127
|
+
"- concise titles; content should describe WHAT the slide covers (topic and key points), not HOW it should look — no layout instructions, no column descriptions, no visual prescriptions\n"
|
|
128
|
+
"- (CRITICAL) No inline code snippets or code blocks.\n\n"
|
|
129
|
+
"Template assignment rules (CRITICAL):\n"
|
|
130
|
+
"- A template represents a LAYOUT PATTERN, not an individual slide. By default, assign each slide its own unique template_key.\n"
|
|
131
|
+
"- Only share a template_key across slides when the layout is genuinely identical (e.g. a repeated content card format). Reuse templates only when fitting and do not reuse them often.\n"
|
|
132
|
+
"- Never reuse a template just to save keys. Distinct slides deserve distinct templates.\n"
|
|
133
|
+
"- Never use the same template for adjacent slides.\n"
|
|
134
|
+
"- When multiple slides do share a template_key, only the FIRST slide gets template_status 'new'. All subsequent slides get template_status 'existing'.\n"
|
|
135
|
+
" Example: slides 2, 5, 9 all use layout 'two_col_content'. Slide 2: template_status 'new'. Slides 5, 9: template_status 'existing'.\n\n"
|
|
136
|
+
"Sequential rule:\n"
|
|
137
|
+
"- depends_on is null by default and should almost always stay null.\n"
|
|
138
|
+
"- Only set it when this slide is a direct continuation of another slide's specific content (e.g. 'Part 2 of X' that cannot be written without knowing what Part 1 said). Narrative flow, thematic progression, or being 'related to' another slide are NOT valid reasons.\n"
|
|
139
|
+
f"Task brief:\n{task_brief.strip()}\n\n"
|
|
140
|
+
f"Existing templates:\n{template_block}\n"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _infer_template_key(title: str) -> str:
|
|
145
|
+
"""Infer a stable template key from full page title text."""
|
|
146
|
+
raw = re.sub(r"[^a-z0-9\s]+", " ", title.lower())
|
|
147
|
+
words = [
|
|
148
|
+
w for w in raw.split() if w and w not in {"and", "the", "of", "to", "for", "in"}
|
|
149
|
+
]
|
|
150
|
+
if not words:
|
|
151
|
+
return "content_default"
|
|
152
|
+
return "_".join(words)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _pretty_template_name(template_key: str) -> str:
|
|
156
|
+
return " ".join(part.capitalize() for part in template_key.split("_"))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _normalize_outline(
|
|
160
|
+
plan: _PlanResponse,
|
|
161
|
+
count: int,
|
|
162
|
+
insert_position: int,
|
|
163
|
+
existing_templates: dict[str, dict[str, str]],
|
|
164
|
+
) -> list[dict[str, str | int]]:
|
|
165
|
+
existing_keys = set(existing_templates.keys())
|
|
166
|
+
rows: list[dict[str, str | int]] = []
|
|
167
|
+
for i in range(count):
|
|
168
|
+
page = insert_position + i
|
|
169
|
+
src = (
|
|
170
|
+
plan.slides[i]
|
|
171
|
+
if i < len(plan.slides)
|
|
172
|
+
else _PlanSlide(page=page, title=f"Slide {i + 1}", content="Content")
|
|
173
|
+
)
|
|
174
|
+
title = src.title.strip() if src.title else f"Slide {i + 1}"
|
|
175
|
+
content = src.content.strip() if src.content else title
|
|
176
|
+
key = (src.template_key or "").strip() or _infer_template_key(title)
|
|
177
|
+
if key in existing_keys:
|
|
178
|
+
status = "existing"
|
|
179
|
+
name = existing_templates.get(key, {}).get(
|
|
180
|
+
"name", _pretty_template_name(key)
|
|
181
|
+
)
|
|
182
|
+
else:
|
|
183
|
+
# Respect the planner's intra-batch reuse declaration: if the planner
|
|
184
|
+
# says "existing" for a key not in the registry, it means a previous
|
|
185
|
+
# slide in this batch creates the template and this slide reuses it.
|
|
186
|
+
status = "existing" if src.template_status == "existing" else "new"
|
|
187
|
+
name = (src.template_name or "").strip() or _pretty_template_name(key)
|
|
188
|
+
rows.append(
|
|
189
|
+
{
|
|
190
|
+
"page": page,
|
|
191
|
+
"title": title,
|
|
192
|
+
"content": content,
|
|
193
|
+
"template_key": key,
|
|
194
|
+
"template_name": name,
|
|
195
|
+
"template_status": status,
|
|
196
|
+
"depends_on": src.depends_on,
|
|
197
|
+
}
|
|
198
|
+
)
|
|
199
|
+
return rows
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _build_creation_steps(outline: list[dict]) -> list[list[dict]]:
|
|
203
|
+
"""Group outline rows into parallel execution steps via DAG level assignment.
|
|
204
|
+
|
|
205
|
+
Each slide's level is the maximum level of its dependencies plus one:
|
|
206
|
+
- Content dependency (depends_on page P) → level ≥ level(P) + 1
|
|
207
|
+
- Template dependency (reuses a template created in this batch by page P)
|
|
208
|
+
→ level ≥ level(P) + 1
|
|
209
|
+
|
|
210
|
+
Slides with no dependencies go to level 0 (step 1) regardless of their
|
|
211
|
+
position in the outline. All slides at the same level can run in parallel.
|
|
212
|
+
"""
|
|
213
|
+
# Map template_key → page number of the first "new" creator in this batch
|
|
214
|
+
key_creator: dict[str, int] = {}
|
|
215
|
+
for row in outline:
|
|
216
|
+
key = row.get("template_key") or ""
|
|
217
|
+
if row.get("template_status") == "new" and key not in key_creator:
|
|
218
|
+
key_creator[key] = row["page"]
|
|
219
|
+
|
|
220
|
+
page_level: dict[int, int] = {}
|
|
221
|
+
for row in outline:
|
|
222
|
+
page = row["page"]
|
|
223
|
+
level = 0
|
|
224
|
+
|
|
225
|
+
dep = row.get("depends_on")
|
|
226
|
+
if dep is not None and dep in page_level:
|
|
227
|
+
level = max(level, page_level[dep] + 1)
|
|
228
|
+
|
|
229
|
+
key = row.get("template_key") or ""
|
|
230
|
+
if row.get("template_status") == "existing":
|
|
231
|
+
creator = key_creator.get(key)
|
|
232
|
+
if creator is not None and creator in page_level:
|
|
233
|
+
level = max(level, page_level[creator] + 1)
|
|
234
|
+
|
|
235
|
+
page_level[page] = level
|
|
236
|
+
|
|
237
|
+
max_level = max(page_level.values(), default=0)
|
|
238
|
+
return [
|
|
239
|
+
[row for row in outline if page_level[row["page"]] == lvl]
|
|
240
|
+
for lvl in range(max_level + 1)
|
|
241
|
+
if any(page_level[row["page"]] == lvl for row in outline)
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class InsertNewSlides(BaseTool):
|
|
246
|
+
"""
|
|
247
|
+
Insert new slides before a specified page position.
|
|
248
|
+
|
|
249
|
+
Uses task_brief to generate an outline/plan while creating blank placeholders.
|
|
250
|
+
For 2+ slides, the outline includes serial vs parallel guidance for ModifySlide calls.
|
|
251
|
+
Do not use this tool in parallel with other tools — call it independently.
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
class ToolConfig:
|
|
255
|
+
one_call_at_a_time: bool = True
|
|
256
|
+
|
|
257
|
+
project_name: str = Field(
|
|
258
|
+
...,
|
|
259
|
+
description="Name of the presentation project",
|
|
260
|
+
)
|
|
261
|
+
task_brief: str = Field(
|
|
262
|
+
...,
|
|
263
|
+
max_length=2000,
|
|
264
|
+
description="Brief description of what content will be created in these new pages. Used for outline/plan generation.",
|
|
265
|
+
)
|
|
266
|
+
approximate_page_count: int = Field(
|
|
267
|
+
...,
|
|
268
|
+
ge=1,
|
|
269
|
+
le=20,
|
|
270
|
+
description="Number of blank slide placeholders to insert (1-20). Content is added later with ModifySlide.",
|
|
271
|
+
)
|
|
272
|
+
insert_position: int = Field(
|
|
273
|
+
default=1,
|
|
274
|
+
ge=1,
|
|
275
|
+
description="Page number (1-based) before which to insert. insert_position=1 means at the beginning, insert_position=3 means before page 3.",
|
|
276
|
+
)
|
|
277
|
+
file_prefix: str = Field(
|
|
278
|
+
default="slide",
|
|
279
|
+
description="Prefix of the slide file names (e.g. slide_01, slide_02).",
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
def run(self):
|
|
283
|
+
"""Insert blank placeholders and return a planning-oriented response."""
|
|
284
|
+
project_dir = get_project_dir(self.project_name)
|
|
285
|
+
project_dir.mkdir(parents=True, exist_ok=True)
|
|
286
|
+
|
|
287
|
+
slides = list_slide_files(project_dir, self.file_prefix)
|
|
288
|
+
pad_width = compute_pad_width(slides, extra_count=self.approximate_page_count)
|
|
289
|
+
insert_position = self.insert_position
|
|
290
|
+
n = self.approximate_page_count
|
|
291
|
+
existing_templates = load_template_index(project_dir)
|
|
292
|
+
|
|
293
|
+
# Rename existing slides that are at or after insert position
|
|
294
|
+
rename_map: dict[Path, Path] = {}
|
|
295
|
+
for s in slides:
|
|
296
|
+
if s.index >= insert_position:
|
|
297
|
+
new_name = build_slide_name(
|
|
298
|
+
self.file_prefix, s.index + n, pad_width, s.suffix
|
|
299
|
+
)
|
|
300
|
+
rename_map[s.path] = project_dir / new_name
|
|
301
|
+
apply_renames(rename_map)
|
|
302
|
+
|
|
303
|
+
planner = _make_planner_agent(caller_model=getattr(self._caller_agent, "model", None))
|
|
304
|
+
prompt = _build_planner_prompt(
|
|
305
|
+
self.task_brief, n, insert_position, existing_templates
|
|
306
|
+
)
|
|
307
|
+
plan_result = _run_awaitable(planner.get_response(prompt))
|
|
308
|
+
plan_text = _extract_json_block(
|
|
309
|
+
str(getattr(plan_result, "final_output", "") or "")
|
|
310
|
+
)
|
|
311
|
+
if not plan_text:
|
|
312
|
+
return "❌ Outline generation failed: planner returned empty output."
|
|
313
|
+
try:
|
|
314
|
+
plan_obj = _PlanResponse.model_validate(json.loads(plan_text))
|
|
315
|
+
except (json.JSONDecodeError, ValidationError) as exc:
|
|
316
|
+
return (
|
|
317
|
+
f"❌ Outline generation failed: planner returned invalid JSON ({exc})."
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
# Write blank slide placeholders (no content generation here)
|
|
321
|
+
created: list[str] = []
|
|
322
|
+
outline = _normalize_outline(plan_obj, n, insert_position, existing_templates)
|
|
323
|
+
blank_html, _ = ensure_full_html("")
|
|
324
|
+
for i in range(n):
|
|
325
|
+
idx = insert_position + i
|
|
326
|
+
name = build_slide_name(self.file_prefix, idx, pad_width, "")
|
|
327
|
+
path = project_dir / name
|
|
328
|
+
path.write_text(blank_html, encoding="utf-8")
|
|
329
|
+
created.append(name)
|
|
330
|
+
|
|
331
|
+
total_after_insert = len(slides) + n
|
|
332
|
+
lines = [
|
|
333
|
+
"Successfully generated outline",
|
|
334
|
+
"",
|
|
335
|
+
"**Summary:**",
|
|
336
|
+
f"- {n} new blank slide placeholder(s) created",
|
|
337
|
+
f"- {len({row['template_key'] for row in outline})} template key(s) planned",
|
|
338
|
+
f"- Insert positions: Before page {insert_position} (inserted at positions: {insert_position} to {insert_position + n - 1})",
|
|
339
|
+
f"- Total slides in presentation: {total_after_insert} slides",
|
|
340
|
+
"",
|
|
341
|
+
"**Slide Outline:**",
|
|
342
|
+
]
|
|
343
|
+
for row in outline:
|
|
344
|
+
page = row["page"]
|
|
345
|
+
lines.append(f"**Page {page}:**")
|
|
346
|
+
lines.append(f"- Title: {row['title']}")
|
|
347
|
+
if row["content"] != row["title"]:
|
|
348
|
+
lines.append(f"- Content: {row['content']}")
|
|
349
|
+
lines.append(f"- Template Name: {row['template_name']}")
|
|
350
|
+
lines.append(f"- Template Key: {row['template_key']}")
|
|
351
|
+
lines.append(f"- Template Status: {row['template_status']}")
|
|
352
|
+
lines.append("")
|
|
353
|
+
|
|
354
|
+
steps = _build_creation_steps(outline)
|
|
355
|
+
|
|
356
|
+
lines.append("**Creation Order:**")
|
|
357
|
+
for step_num, step_rows in enumerate(steps, start=1):
|
|
358
|
+
prev = f" (after Step {step_num - 1} completes)" if step_num > 1 else ""
|
|
359
|
+
pages = ", ".join(str(r["page"]) for r in step_rows)
|
|
360
|
+
if len(step_rows) == 1:
|
|
361
|
+
row = step_rows[0]
|
|
362
|
+
dep = row.get("depends_on")
|
|
363
|
+
note = f" — create after slide {dep} is generated" if dep is not None else ""
|
|
364
|
+
lines.append(f"Step {step_num}: Create page {pages}{prev}{note}")
|
|
365
|
+
else:
|
|
366
|
+
lines.append(f"Step {step_num}: Create pages {pages} IN PARALLEL{prev}")
|
|
367
|
+
lines.append("These pages can be created simultaneously:")
|
|
368
|
+
for row in step_rows:
|
|
369
|
+
tag = "creates new template" if row["template_status"] == "new" else "uses existing template"
|
|
370
|
+
lines.append(f"- Page {row['page']}: '{row['template_key']}' ({tag})")
|
|
371
|
+
lines.append("")
|
|
372
|
+
|
|
373
|
+
lines.extend(
|
|
374
|
+
[
|
|
375
|
+
"",
|
|
376
|
+
"**Current Status:**",
|
|
377
|
+
f"The presentation now has {total_after_insert} page placeholders.",
|
|
378
|
+
"Use ModifySlide to generate slide HTML according to the creation order above.",
|
|
379
|
+
"Important: insert_new_slides must be called independently (not in parallel).",
|
|
380
|
+
]
|
|
381
|
+
)
|
|
382
|
+
return "\n".join(lines)
|
|
383
|
+
|
|
384
|
+
if __name__ == "__main__":
|
|
385
|
+
|
|
386
|
+
agent = InsertNewSlides(
|
|
387
|
+
project_name="test",
|
|
388
|
+
task_brief="Create a presentation about the benefits of using AI",
|
|
389
|
+
approximate_page_count=7,
|
|
390
|
+
insert_position=1,
|
|
391
|
+
file_prefix="slide",
|
|
392
|
+
)
|
|
393
|
+
print(agent.run())
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Create and edit presentation theme CSS files."""
|
|
2
|
+
|
|
3
|
+
from agency_swarm.tools import BaseTool
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
|
|
6
|
+
from .slide_file_utils import get_project_dir
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ManageTheme(BaseTool):
|
|
10
|
+
"""
|
|
11
|
+
Create or edit the theme CSS file for a presentation project.
|
|
12
|
+
|
|
13
|
+
The theme file is saved as `_theme.css` in the project folder and defines:
|
|
14
|
+
- Color palette (CSS variables)
|
|
15
|
+
- Typography (fonts, sizes, line heights)
|
|
16
|
+
- Base styles for common elements
|
|
17
|
+
- Reusable classes (cards, labels, etc.)
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
- First time: Creates new theme file
|
|
21
|
+
- Subsequent calls: Overwrites existing theme
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
project_name: str = Field(
|
|
25
|
+
...,
|
|
26
|
+
description="Name of the presentation project"
|
|
27
|
+
)
|
|
28
|
+
css_content: str = Field(
|
|
29
|
+
...,
|
|
30
|
+
description="Complete CSS content for the theme file"
|
|
31
|
+
)
|
|
32
|
+
overwrite: bool = Field(
|
|
33
|
+
default=False,
|
|
34
|
+
description=(
|
|
35
|
+
"If False, return an error when the theme already exists. "
|
|
36
|
+
"If True, overwrite the existing theme file."
|
|
37
|
+
),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def run(self):
|
|
41
|
+
"""Create or update theme file."""
|
|
42
|
+
project_dir = get_project_dir(self.project_name)
|
|
43
|
+
project_dir.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
|
|
45
|
+
theme_path = project_dir / "_theme.css"
|
|
46
|
+
if theme_path.exists() and not self.overwrite:
|
|
47
|
+
return (
|
|
48
|
+
f"❌ Theme already exists: {theme_path}. "
|
|
49
|
+
"Set overwrite=True to replace it or add a postfix to the filename."
|
|
50
|
+
)
|
|
51
|
+
operation = "updated" if theme_path.exists() else "created"
|
|
52
|
+
|
|
53
|
+
css_content, injected = self._ensure_canvas_rules(self.css_content)
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
theme_path.write_text(css_content, encoding='utf-8')
|
|
57
|
+
file_size = theme_path.stat().st_size
|
|
58
|
+
|
|
59
|
+
note = " (added base canvas rules)" if injected else ""
|
|
60
|
+
return f"✅ Successfully {operation} theme: {theme_path}{note}\nSize: {file_size} bytes"
|
|
61
|
+
|
|
62
|
+
except Exception as e:
|
|
63
|
+
return f"Error writing theme: {e}"
|
|
64
|
+
|
|
65
|
+
def _ensure_canvas_rules(self, css_content: str) -> tuple[str, bool]:
|
|
66
|
+
"""Ensure the theme defines the required slide canvas size."""
|
|
67
|
+
import re
|
|
68
|
+
|
|
69
|
+
selector_pattern = r"(html\s*,\s*body|body|html)\s*\{[^}]*\}"
|
|
70
|
+
width_pattern = r"width\s*:\s*1280px"
|
|
71
|
+
height_pattern = r"height\s*:\s*720px"
|
|
72
|
+
|
|
73
|
+
has_width = False
|
|
74
|
+
has_height = False
|
|
75
|
+
|
|
76
|
+
for match in re.finditer(selector_pattern, css_content, flags=re.IGNORECASE | re.DOTALL):
|
|
77
|
+
block = match.group(0)
|
|
78
|
+
if re.search(width_pattern, block, flags=re.IGNORECASE):
|
|
79
|
+
has_width = True
|
|
80
|
+
if re.search(height_pattern, block, flags=re.IGNORECASE):
|
|
81
|
+
has_height = True
|
|
82
|
+
|
|
83
|
+
if has_width and has_height:
|
|
84
|
+
return css_content, False
|
|
85
|
+
|
|
86
|
+
canvas_rules = (
|
|
87
|
+
"html, body {\n"
|
|
88
|
+
" width: 1280px;\n"
|
|
89
|
+
" height: 720px;\n"
|
|
90
|
+
" margin: 0;\n"
|
|
91
|
+
" padding: 0;\n"
|
|
92
|
+
" overflow: hidden;\n"
|
|
93
|
+
"}\n\n"
|
|
94
|
+
)
|
|
95
|
+
return canvas_rules + css_content, True
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == "__main__":
|
|
99
|
+
from pathlib import Path
|
|
100
|
+
import sys
|
|
101
|
+
|
|
102
|
+
tools_root = Path(__file__).resolve().parents[1]
|
|
103
|
+
if str(tools_root) not in sys.path:
|
|
104
|
+
sys.path.insert(0, str(tools_root))
|
|
105
|
+
|
|
106
|
+
from tools.deck_utils import load_theme_css
|
|
107
|
+
|
|
108
|
+
tool = ManageTheme(
|
|
109
|
+
project_name="slides_agent_test_deck",
|
|
110
|
+
css_content=load_theme_css(),
|
|
111
|
+
)
|
|
112
|
+
print(tool.run())
|