@_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,149 @@
|
|
|
1
|
+
You generate slide HTML. Return ONLY the complete HTML document — no markdown fences, no explanations, no tool calls.
|
|
2
|
+
|
|
3
|
+
## Derive layout from content structure
|
|
4
|
+
|
|
5
|
+
Do not default to the same layout for every slide. Examine what the content is communicating and choose the layout that fits it best:
|
|
6
|
+
|
|
7
|
+
- **Two opposing or complementary concepts** (problem vs solution, before vs after, risk vs benefit) → split panel: two halves with a vertical divider and a transitional icon at the midpoint
|
|
8
|
+
- **Five or more items of equal weight** → multi-row grid (2×3, 2×4); a single row is only appropriate for ≤4 items
|
|
9
|
+
- **Single dominant message, metric, or statement** → hero layout: the key element large and centered, with supporting context arranged around or below it
|
|
10
|
+
- **Sequential or temporal content** (steps, timeline, process) → numbered steps or a horizontal/vertical timeline
|
|
11
|
+
- **Closing or action-oriented slide** → include a visually distinct CTA block (different background, border, or glow) with a clear call to action, separate from the value content above it
|
|
12
|
+
- **Comparative content across two or more subjects** → side-by-side columns or a comparison table
|
|
13
|
+
|
|
14
|
+
Use color-coded sections intentionally: when content has distinct categories or sides, give each a unique accent color (background tint, icon color, top accent bar) so the distinction is visually immediate.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Density requirements
|
|
19
|
+
|
|
20
|
+
Every slide must feel fully utilized. Dead space is a design failure:
|
|
21
|
+
|
|
22
|
+
- **No slide should have more than 35% empty vertical space** below the last content element. If content is sparse, add a supporting element — a statistic, a pull quote, a contextual diagram, or an additional content item.
|
|
23
|
+
- **Every card or item must have**: a unique specific title (not a generic label) + at least 2 substantive sentences of description.
|
|
24
|
+
- **Cards must never have a fixed `height`**. Use `min-height` only as a floor, never a ceiling — the card expands with its content. If a grid makes short cards look hollow (large empty space below the text), either: add more content to fill them, reduce the number of columns, or switch to `align-items: start` so cards don't stretch to match their tallest sibling.
|
|
25
|
+
- **Fewer than 4 content items?** Add a complementary secondary section — a key metric row, a quote block, a supporting visual, or expand existing items with more detail.
|
|
26
|
+
- **Never use a 3-item single row** when 5–6 items would better represent the topic. If the task_brief only has 3 items but the subject clearly has more facets, design room to expand — or make the 3 items visually heavier with sub-points or icons.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Design vocabulary
|
|
31
|
+
|
|
32
|
+
These are primitives you can freely compose in any layout. Use them to add depth, polish, and visual hierarchy:
|
|
33
|
+
|
|
34
|
+
- **Accent bars**: thin 2–4 px horizontal or vertical colored bars at the top or left edge of a card, column, or section — signal category or importance at a glance
|
|
35
|
+
- **Kicker labels**: small all-caps badge or pill above a heading to name the section or category (e.g. `THE CHALLENGE`, `CAPABILITIES`, `02 // SOLUTION`) — use monospace or a condensed font
|
|
36
|
+
- **Grid-div backgrounds**: 1 px `<div>` elements placed absolutely to form a grid pattern (vertical + horizontal lines, e.g. `background-color: #1f1f1f; width: 1px`) — renders perfectly in PPTX unlike CSS `background-image` grid tricks
|
|
37
|
+
- **Glowing orbs**: solid colored divs with `filter: blur(60px–120px)` and low opacity (`0.10–0.20`) positioned behind content — creates ambient depth without gradients
|
|
38
|
+
- **Per-item color coding**: each card or item in a group gets its own distinct accent color for its icon background, top bar, or border highlight — avoids the monotony of one uniform accent across all cards
|
|
39
|
+
- **Monospace footer/labels**: page numbers, slide identifiers, or section tags in monospace font at the edge of the slide (e.g. `03 // PLATFORM_FEATURES`, `CONFIDENTIAL`) — adds professional framing
|
|
40
|
+
- **Corner bracket decorations**: L-shaped border fragments (two sides of a border) at card corners using absolute-positioned elements — creates a technical or editorial frame feel
|
|
41
|
+
- **Vertical section dividers**: a 1 px line running vertically between two content regions, optionally with a small icon or arrow centered on it — reinforces a split layout visually
|
|
42
|
+
- **Top section accent stripes**: a full-width 2–4 px stripe spanning the entire top of a column or panel, colored per section — immediately communicates the section's identity
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Create visually appealing and impactful slides
|
|
47
|
+
|
|
48
|
+
- Prioritize strong typography, proper layout, and appropriate charts/diagrams to maximize visual impact. Avoid walls of text.
|
|
49
|
+
- When tackling complex tasks, consider which frontend libraries could help you work more efficiently. Use jsdelivr as the CDN:
|
|
50
|
+
|
|
51
|
+
- Use Tailwind CSS for styling: `<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">`
|
|
52
|
+
|
|
53
|
+
- **Charts & Visualizations:**
|
|
54
|
+
- Statistical charts (bar, line, pie, scatter, radar, heatmap, treemap): use Chart.js `<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>` or ECharts `<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>`
|
|
55
|
+
- Static diagrams (timelines, Venn, matrix): use the Canvas 2D API directly.
|
|
56
|
+
- Geographic maps: use ECharts geo or Leaflet `<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.css" />`
|
|
57
|
+
|
|
58
|
+
- **Icons**: two valid approaches — choose the one that fits the design:
|
|
59
|
+
- **Inline SVG** — zero dependencies, always safe, best for bespoke or branded shapes.
|
|
60
|
+
- **Font Awesome 6 Free** — use the cdnjs CDN link:
|
|
61
|
+
```html
|
|
62
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" crossorigin="anonymous" />
|
|
63
|
+
```
|
|
64
|
+
Use the FA 6 class syntax: `<i class="fa-solid fa-rocket"></i>`, `<i class="fa-brands fa-github"></i>`, etc.
|
|
65
|
+
Do **not** use Font Awesome Kit `<script>` tags — only CDN `<link>` stylesheet tags are supported.
|
|
66
|
+
|
|
67
|
+
- **Google Fonts** for typography (fonts are embedded in the final PPTX export):
|
|
68
|
+
```html
|
|
69
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
70
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Merriweather:wght@300;400;700&family=Roboto+Mono:wght@400;500&display=swap">
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- Use only local project assets for images; reference them as `./assets/{filename}`. Avoid remote image URLs.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Design a perfect layout for 1280 × 720
|
|
78
|
+
|
|
79
|
+
- **Always use Flexbox**:
|
|
80
|
+
- Set `display: flex;` on the outermost slide container.
|
|
81
|
+
- Set `flex: 1;` on the main content wrapper inside the container.
|
|
82
|
+
|
|
83
|
+
- **Container dimensions** — the root slide container must be exactly:
|
|
84
|
+
```css
|
|
85
|
+
width: 1280px;
|
|
86
|
+
height: 720px; /* fixed — never use min-height or auto here */
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- Set explicit height constraints on chart containers (e.g. `height: 300px`) so Chart.js / ECharts can render at the correct size.
|
|
90
|
+
|
|
91
|
+
- Ensure no element overflows horizontally or vertically — content must fit within 1280 × 720 without scrollbars or clipping.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Technical requirements
|
|
96
|
+
|
|
97
|
+
- **No base64-encoded images** — use `./assets/{filename}` local paths instead.
|
|
98
|
+
|
|
99
|
+
- **Background images must use `<img>` tags, not CSS `background-image`**. CSS `background-image` is not extracted by the PPTX converter. For full-slide backgrounds use an absolutely-positioned `<img>` with `object-fit: cover`:
|
|
100
|
+
```html
|
|
101
|
+
<img src="./assets/hero.jpg"
|
|
102
|
+
style="position:absolute; top:0; left:0; width:1280px; height:720px; object-fit:cover; z-index:0;" />
|
|
103
|
+
```
|
|
104
|
+
Layer overlays and content above it with higher `z-index` values.
|
|
105
|
+
|
|
106
|
+
- **CSS gradients are fully supported and encouraged** (`linear-gradient`, `radial-gradient`, `conic-gradient`). Use them freely for backgrounds, overlays, and visual depth — they export faithfully to PPTX.
|
|
107
|
+
|
|
108
|
+
- **Minimize animations** — prefer static, high-impact graphic design. Animations do not export to PPTX.
|
|
109
|
+
|
|
110
|
+
- **Google Fonts only** for typography. Available families (all embedded in PPTX export):
|
|
111
|
+
Roboto, Open Sans, Lato, Montserrat, Poppins, Raleway, Inter, Work Sans, Urbanist,
|
|
112
|
+
Space Grotesk, Lora, Merriweather, Playfair Display, Libre Baskerville,
|
|
113
|
+
Roboto Mono, Inconsolata, IBM Plex Mono, Oswald, Roboto Condensed.
|
|
114
|
+
|
|
115
|
+
- **Text wrapping rule** — always wrap text inside `<p>` tags, never as naked text nodes inside `<div>`:
|
|
116
|
+
- ❌ `<div>Some text <span class="accent">highlighted</span></div>`
|
|
117
|
+
- ✅ `<div><p>Some text <span class="accent">highlighted</span></p></div>`
|
|
118
|
+
|
|
119
|
+
- **Use at least 8px gap between pill/badge groups** (`gap: 8px` on the flex container). CSS `border-radius` makes the HTML visual gap appear larger than the physical value; PPTX renders shapes at exact box coordinates, so gaps smaller than 8px look cramped in PPTX.
|
|
120
|
+
|
|
121
|
+
- **Never place styled badges or pills inline within flowing sentence text.** The PPTX converter treats any inline element with a background color as a standalone shape, which splits the surrounding sentence into separate disconnected text boxes. Badges and pills must live on their own line or inside their own container — never mid-sentence.
|
|
122
|
+
- ❌ `<p>For example, <span class="badge">@BotName</span> can respond.</p>`
|
|
123
|
+
- ✅ `<p>For example, <code>@BotName</code> (plain monospace, no background) can respond.</p>`
|
|
124
|
+
- ✅ A pill/badge on its own line or as a list item with a label above it
|
|
125
|
+
|
|
126
|
+
- **Be factual** — use placeholders like `{Insert metric here}` instead of fabricating data.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Validation rules (every generated slide must pass all of these)
|
|
131
|
+
|
|
132
|
+
- Return ONLY HTML (no markdown, no explanations).
|
|
133
|
+
- Keep slide canvas exactly 1280 × 720.
|
|
134
|
+
- Include `<link rel="stylesheet" href="./_theme.css" />` in every full HTML document.
|
|
135
|
+
- Reuse CURRENT_THEME_CSS variables/classes; do not invent conflicting design tokens.
|
|
136
|
+
- Icon fonts: if using Font Awesome, load it via the cdnjs CDN `<link>` stylesheet (not a Kit `<script>` tag). Inline SVG is equally valid.
|
|
137
|
+
- Do NOT use emoji or Unicode symbols as icons; use inline SVG or image assets.
|
|
138
|
+
- Do NOT use empty bullet markers like `<span class="dot"></span>`; use SVG or image circles.
|
|
139
|
+
- Do NOT place styled badges/pills (inline elements with a background color) inside flowing sentence text (`<p>` or `<li>`). They must be on their own line or in their own container.
|
|
140
|
+
- NEVER try to create logos or complex images with svg, that looks cheap.
|
|
141
|
+
- All visible text must be wrapped in semantic tags (`<p>`, `<h1>`–`<h6>`, `<ul>`, `<ol>`, `<li>`, `<span>`).
|
|
142
|
+
- Do not leave naked text nodes directly inside `<div>`.
|
|
143
|
+
- No overflow: content must not overflow horizontally or vertically.
|
|
144
|
+
- Keep text safely above the bottom edge to avoid descender clipping (reserve at least 5–10 px).
|
|
145
|
+
- For every local image reference (`img src` or CSS `url(...)`):
|
|
146
|
+
- path must stay inside the project folder,
|
|
147
|
+
- file must exist,
|
|
148
|
+
- extension must be one of `.png`, `.jpg`, `.jpeg`, `.gif`, `.bmp`, `.tiff`, `.tif`, `.webp`,
|
|
149
|
+
- file must be a real image (not HTML masquerading as an image).
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Utilities for managing HTML slide files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import re
|
|
8
|
+
import uuid
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_SENTINEL_RE = re.compile(
|
|
12
|
+
r'<!-- css-snapshot:([^:\n]+):start -->\s*<style>(.*?)</style>\s*<!-- css-snapshot:\1:end -->',
|
|
13
|
+
re.DOTALL | re.IGNORECASE,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def restore_snapshot_html(html: str) -> tuple[str, dict[str, str]]:
|
|
18
|
+
"""Reverse the sentinel blocks written by _inline_theme_css.
|
|
19
|
+
|
|
20
|
+
Each ``<!-- css-snapshot:<filename>:start --> <style>…</style>
|
|
21
|
+
<!-- css-snapshot:<filename>:end -->`` block is replaced with
|
|
22
|
+
``<link rel="stylesheet" href="./<filename>" />`` and the CSS content is
|
|
23
|
+
returned so the caller can write it back to disk.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
(restored_html, {filename: css_content, …})
|
|
27
|
+
"""
|
|
28
|
+
extracted: dict[str, str] = {}
|
|
29
|
+
|
|
30
|
+
def _replace(match: re.Match) -> str:
|
|
31
|
+
filename = match.group(1)
|
|
32
|
+
css = match.group(2).strip()
|
|
33
|
+
extracted[filename] = css
|
|
34
|
+
return f'<link rel="stylesheet" href="./{filename}" />'
|
|
35
|
+
|
|
36
|
+
restored = _SENTINEL_RE.sub(_replace, html)
|
|
37
|
+
return restored, extracted
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class SlideFile:
|
|
42
|
+
index: int
|
|
43
|
+
suffix: str
|
|
44
|
+
path: Path
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_mnt_dir() -> Path:
|
|
48
|
+
return Path("/app/mnt") if Path("/.dockerenv").is_file() else Path(__file__).parents[2] / "mnt"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_project_dir(project_name: str) -> Path:
|
|
52
|
+
return get_mnt_dir() / project_name / "presentations"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def list_slide_files(project_dir: Path, file_prefix: str = "slide") -> list[SlideFile]:
|
|
56
|
+
pattern = re.compile(rf"^{re.escape(file_prefix)}_(\d+)(.*)\.html$", re.IGNORECASE)
|
|
57
|
+
slides: list[SlideFile] = []
|
|
58
|
+
for path in project_dir.glob("*.html"):
|
|
59
|
+
match = pattern.match(path.name)
|
|
60
|
+
if not match:
|
|
61
|
+
continue
|
|
62
|
+
index = int(match.group(1))
|
|
63
|
+
suffix = match.group(2) or ""
|
|
64
|
+
slides.append(SlideFile(index=index, suffix=suffix, path=path))
|
|
65
|
+
return sorted(slides, key=lambda s: s.index)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def compute_pad_width(slides: list[SlideFile], min_width: int = 2, extra_count: int = 0) -> int:
|
|
69
|
+
max_index = max([s.index for s in slides], default=0) + max(0, extra_count)
|
|
70
|
+
return max(min_width, len(str(max_index or 1)))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def build_slide_name(file_prefix: str, index: int, pad_width: int, suffix: str) -> str:
|
|
74
|
+
return f"{file_prefix}_{index:0{pad_width}d}{suffix}.html"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def next_pptx_version(desired: Path) -> Path:
|
|
78
|
+
"""Return *desired* if it doesn't exist, otherwise the next free _vN path.
|
|
79
|
+
|
|
80
|
+
Strips any existing ``_vN`` suffix before searching so that passing
|
|
81
|
+
``deck_v2.pptx`` when that file already exists yields ``deck_v3.pptx``
|
|
82
|
+
rather than ``deck_v2_v2.pptx``.
|
|
83
|
+
"""
|
|
84
|
+
if not desired.exists():
|
|
85
|
+
return desired
|
|
86
|
+
base = re.sub(r"_v\d+$", "", desired.stem)
|
|
87
|
+
n = 2
|
|
88
|
+
while True:
|
|
89
|
+
candidate = desired.parent / f"{base}_v{n}{desired.suffix}"
|
|
90
|
+
if not candidate.exists():
|
|
91
|
+
return candidate
|
|
92
|
+
n += 1
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def apply_renames(rename_map: dict[Path, Path]) -> None:
|
|
96
|
+
temp_map: dict[Path, Path] = {}
|
|
97
|
+
for src, dest in rename_map.items():
|
|
98
|
+
if src == dest:
|
|
99
|
+
continue
|
|
100
|
+
temp_name = f".__tmp__{uuid.uuid4().hex}{src.suffix}"
|
|
101
|
+
tmp_path = src.with_name(temp_name)
|
|
102
|
+
src.rename(tmp_path)
|
|
103
|
+
temp_map[tmp_path] = dest
|
|
104
|
+
|
|
105
|
+
for tmp, dest in temp_map.items():
|
|
106
|
+
if dest.exists():
|
|
107
|
+
dest.unlink()
|
|
108
|
+
tmp.rename(dest)
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"""Shared HTML scaffold and validation helpers for slides."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import tempfile
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from playwright.sync_api import sync_playwright
|
|
9
|
+
|
|
10
|
+
# Extensions we consider valid for slide images (PPTX-friendly)
|
|
11
|
+
VALID_IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tiff", ".tif", ".webp"}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def ensure_full_html(html_content: str) -> tuple[str, bool]:
|
|
15
|
+
"""Ensure the slide has a full HTML scaffold."""
|
|
16
|
+
html_lower = html_content.lower()
|
|
17
|
+
is_full_doc = "<html" in html_lower or "<!doctype" in html_lower
|
|
18
|
+
if is_full_doc:
|
|
19
|
+
return html_content, False
|
|
20
|
+
|
|
21
|
+
base_css = """
|
|
22
|
+
* { box-sizing: border-box; }
|
|
23
|
+
html, body {
|
|
24
|
+
width: 1280px;
|
|
25
|
+
height: 720px;
|
|
26
|
+
margin: 0;
|
|
27
|
+
padding: 0;
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
background-color: #0f0f0f;
|
|
30
|
+
}
|
|
31
|
+
body {
|
|
32
|
+
position: relative;
|
|
33
|
+
}
|
|
34
|
+
.slide-wrapper {
|
|
35
|
+
width: 1280px;
|
|
36
|
+
height: 720px;
|
|
37
|
+
margin: 0;
|
|
38
|
+
padding: 0;
|
|
39
|
+
position: relative;
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
background-color: #151517;
|
|
44
|
+
color: #ffffff;
|
|
45
|
+
}
|
|
46
|
+
.bg-grid {
|
|
47
|
+
background-image: url("data:image/svg+xml,%3Csvg width='40' height='40' viewBox='0 0 40 40' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23ffffff' fill-opacity='0.03' fill-rule='evenodd'%3E%3Cpath d='M0 40L40 0H20L0 20M40 40V20L20 40'/%3E%3C/g%3E%3C/svg%3E");
|
|
48
|
+
}
|
|
49
|
+
.glass-panel {
|
|
50
|
+
background: rgba(255, 255, 255, 0.03);
|
|
51
|
+
backdrop-filter: blur(10px);
|
|
52
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
53
|
+
border-radius: 24px;
|
|
54
|
+
}
|
|
55
|
+
.title-font {
|
|
56
|
+
font-family: 'Space Grotesk', sans-serif;
|
|
57
|
+
}
|
|
58
|
+
.slide {
|
|
59
|
+
width: 100%;
|
|
60
|
+
height: 100%;
|
|
61
|
+
padding: 0;
|
|
62
|
+
position: relative;
|
|
63
|
+
z-index: 10;
|
|
64
|
+
display: flex;
|
|
65
|
+
flex-direction: column;
|
|
66
|
+
}
|
|
67
|
+
.content-safe-area {
|
|
68
|
+
width: 100%;
|
|
69
|
+
max-width: 1100px;
|
|
70
|
+
margin: 0 auto;
|
|
71
|
+
padding: 54px 0;
|
|
72
|
+
flex: 1;
|
|
73
|
+
display: flex;
|
|
74
|
+
flex-direction: column;
|
|
75
|
+
}
|
|
76
|
+
.canvas {
|
|
77
|
+
width: 100%;
|
|
78
|
+
height: 100%;
|
|
79
|
+
}
|
|
80
|
+
"""
|
|
81
|
+
full_html = f"""<!DOCTYPE html>
|
|
82
|
+
<html>
|
|
83
|
+
<head>
|
|
84
|
+
<meta charset="utf-8" />
|
|
85
|
+
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet" />
|
|
86
|
+
<link href="https://fonts.googleapis.com/css2?family=Merriweather:wght@300;400;700&family=Space+Grotesk:wght@500;700&family=Roboto+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
|
87
|
+
<link rel="stylesheet" href="./_theme.css" />
|
|
88
|
+
<style>
|
|
89
|
+
{base_css}
|
|
90
|
+
</style>
|
|
91
|
+
</head>
|
|
92
|
+
<body>
|
|
93
|
+
<div class="slide-wrapper bg-grid">
|
|
94
|
+
<div class="slide">
|
|
95
|
+
<div class="content-safe-area">
|
|
96
|
+
{html_content}
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</body>
|
|
101
|
+
</html>"""
|
|
102
|
+
return full_html, True
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _collect_local_image_refs(html_content: str) -> list[str]:
|
|
106
|
+
"""Collect img src and url() in style that look like local paths (./ or no scheme)."""
|
|
107
|
+
refs = []
|
|
108
|
+
# <img src="..."> and <img src='...'>
|
|
109
|
+
for m in re.finditer(r'<img[^>]+src\s*=\s*["\']([^"\']+)["\']', html_content, re.IGNORECASE):
|
|
110
|
+
refs.append(m.group(1).strip())
|
|
111
|
+
# url(...) in style or style attribute
|
|
112
|
+
for m in re.finditer(r'url\s*\(\s*["\']?([^"\')\s]+)["\']?\s*\)', html_content, re.IGNORECASE):
|
|
113
|
+
refs.append(m.group(1).strip())
|
|
114
|
+
# Keep only local-looking refs (no http/https/data:)
|
|
115
|
+
local = []
|
|
116
|
+
for r in refs:
|
|
117
|
+
r_lower = r.lower()
|
|
118
|
+
if r_lower.startswith(("http://", "https://", "data:")):
|
|
119
|
+
continue
|
|
120
|
+
local.append(r)
|
|
121
|
+
return local
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _validate_image_refs(project_dir: Path, html_content: str) -> list[str]:
|
|
125
|
+
"""Check that every local image reference in HTML exists under project_dir and is a valid image."""
|
|
126
|
+
errors = []
|
|
127
|
+
project_dir = project_dir.resolve()
|
|
128
|
+
seen = set()
|
|
129
|
+
for ref in _collect_local_image_refs(html_content):
|
|
130
|
+
if not ref or ref in seen:
|
|
131
|
+
continue
|
|
132
|
+
seen.add(ref)
|
|
133
|
+
# Resolve relative to project_dir (ref may be ./assets/x or assets/x)
|
|
134
|
+
normalized = ref.lstrip("/").replace("\\", "/")
|
|
135
|
+
if normalized.startswith("./"):
|
|
136
|
+
normalized = normalized[2:]
|
|
137
|
+
full_path = (project_dir / normalized).resolve()
|
|
138
|
+
try:
|
|
139
|
+
full_path.relative_to(project_dir)
|
|
140
|
+
except (ValueError, TypeError):
|
|
141
|
+
errors.append(f"Image path escapes project: {ref}")
|
|
142
|
+
continue
|
|
143
|
+
if not full_path.exists():
|
|
144
|
+
errors.append(f"Image file not found: {ref} (resolved to {full_path})")
|
|
145
|
+
continue
|
|
146
|
+
if full_path.suffix.lower() not in VALID_IMAGE_EXTENSIONS:
|
|
147
|
+
errors.append(
|
|
148
|
+
f"Image '{ref}' has unsupported extension '{full_path.suffix}'. "
|
|
149
|
+
f"Use one of: {', '.join(sorted(VALID_IMAGE_EXTENSIONS))}"
|
|
150
|
+
)
|
|
151
|
+
continue
|
|
152
|
+
# Optional: verify file is readable as image (avoid corrupt or HTML-masquerading files)
|
|
153
|
+
try:
|
|
154
|
+
with open(full_path, "rb") as f:
|
|
155
|
+
header = f.read(32)
|
|
156
|
+
if header.startswith(b"<") or header.startswith(b"<!") or b"<html" in header[:50].lower():
|
|
157
|
+
errors.append(f"Image '{ref}' is not a valid image file (looks like HTML). Re-download with DownloadImage.")
|
|
158
|
+
except Exception:
|
|
159
|
+
pass
|
|
160
|
+
return errors
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def validate_html(html_content: str, project_dir: Path, used_scaffold: bool) -> dict:
|
|
164
|
+
"""Validate HTML layout using Playwright."""
|
|
165
|
+
errors = []
|
|
166
|
+
|
|
167
|
+
# Validate local image references (photos) exist and are valid
|
|
168
|
+
errors.extend(_validate_image_refs(project_dir, html_content))
|
|
169
|
+
|
|
170
|
+
if re.search(r"[\U0001F300-\U0001FAFF]", html_content):
|
|
171
|
+
errors.append(
|
|
172
|
+
"Emoji/Unicode symbols detected. Use image icons (PNG) instead of emoji."
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if re.search(r"<span[^>]*class=[\"'][^\"']*\\bdot\\b[^\"']*[\"'][^>]*>\\s*</span>", html_content, flags=re.IGNORECASE):
|
|
176
|
+
errors.append(
|
|
177
|
+
"Detected empty .dot spans used as colored bullets. Replace with inline SVG circles or image assets to ensure PPTX rendering."
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Detect inline badges/pills inside flowing text (<p> or <li> containing sibling text nodes AND styled spans)
|
|
181
|
+
# Pattern: <p ...> or <li ...> that contains both plain text AND a <span> or <code> with inline style containing background
|
|
182
|
+
_inline_badge_in_text = re.compile(
|
|
183
|
+
# Requires at least one text character before the badge ([^<]+) to avoid flagging badge-only paragraphs.
|
|
184
|
+
r'<(?:p|li)[^>]*>[^<]+<(?:span|code|a)[^>]+style=["\'][^"\']*background[^"\']*["\'][^>]*>[^<]+</(?:span|code|a)>',
|
|
185
|
+
re.IGNORECASE | re.DOTALL,
|
|
186
|
+
)
|
|
187
|
+
if _inline_badge_in_text.search(html_content):
|
|
188
|
+
errors.append(
|
|
189
|
+
"Styled badge/pill detected inline inside <p> or <li> text. "
|
|
190
|
+
"Inline elements with background-color split the surrounding sentence into separate PPTX text boxes. "
|
|
191
|
+
"Move the badge to its own line or container, or use plain monospace text (no background) for inline code references."
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
with tempfile.NamedTemporaryFile(
|
|
195
|
+
mode="w",
|
|
196
|
+
suffix=".html",
|
|
197
|
+
delete=False,
|
|
198
|
+
encoding="utf-8",
|
|
199
|
+
dir=project_dir,
|
|
200
|
+
) as f:
|
|
201
|
+
f.write(html_content)
|
|
202
|
+
temp_path = f.name
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
with sync_playwright() as p:
|
|
206
|
+
browser = p.chromium.launch()
|
|
207
|
+
page = browser.new_page(viewport={"width": 1280, "height": 720})
|
|
208
|
+
page.goto(f"file://{temp_path}", wait_until="load")
|
|
209
|
+
|
|
210
|
+
if not used_scaffold and "_theme.css" not in html_content:
|
|
211
|
+
errors.append("Missing theme link: include <link rel=\"stylesheet\" href=\"./_theme.css\" />")
|
|
212
|
+
|
|
213
|
+
body_dims = page.evaluate("""() => {
|
|
214
|
+
const body = document.body;
|
|
215
|
+
const slide = document.querySelector('.content-safe-area') || document.querySelector('.slide') || document.querySelector('.slide-wrapper') || body;
|
|
216
|
+
const style = window.getComputedStyle(body);
|
|
217
|
+
return {
|
|
218
|
+
width: parseFloat(style.width),
|
|
219
|
+
height: parseFloat(style.height),
|
|
220
|
+
scrollWidth: Math.max(body.scrollWidth, slide.scrollWidth),
|
|
221
|
+
scrollHeight: Math.max(body.scrollHeight, slide.scrollHeight)
|
|
222
|
+
};
|
|
223
|
+
}""")
|
|
224
|
+
|
|
225
|
+
expected_width = 1280
|
|
226
|
+
expected_height = 720
|
|
227
|
+
|
|
228
|
+
if abs(body_dims["width"] - expected_width) > 2:
|
|
229
|
+
errors.append(f"Body width must be 1280px, got {body_dims['width']:.0f}px")
|
|
230
|
+
if abs(body_dims["height"] - expected_height) > 2:
|
|
231
|
+
errors.append(f"Body height must be 720px, got {body_dims['height']:.0f}px")
|
|
232
|
+
|
|
233
|
+
width_overflow = max(0, body_dims["scrollWidth"] - body_dims["width"] - 1)
|
|
234
|
+
height_overflow = max(0, body_dims["scrollHeight"] - body_dims["height"] - 1)
|
|
235
|
+
|
|
236
|
+
if width_overflow > 0:
|
|
237
|
+
errors.append(f"Content overflows horizontally by {width_overflow:.0f}px")
|
|
238
|
+
errors.append(" 💡 Hint: Reduce content width or increase right margin")
|
|
239
|
+
|
|
240
|
+
if height_overflow > 0:
|
|
241
|
+
errors.append(f"Content overflows vertically by {height_overflow:.0f}px")
|
|
242
|
+
errors.append(" 💡 Hint: Reduce content height, use smaller font, or move elements up from the bottom edge.")
|
|
243
|
+
|
|
244
|
+
# Check for descender clipping (elements too close to the bottom edge)
|
|
245
|
+
descender_issues = page.evaluate("""() => {
|
|
246
|
+
const body = document.body;
|
|
247
|
+
const bodyRect = body.getBoundingClientRect();
|
|
248
|
+
const textElements = Array.from(body.querySelectorAll('p, h1, h2, h3, h4, h5, h6, li, span'));
|
|
249
|
+
const issues = [];
|
|
250
|
+
for (const el of textElements) {
|
|
251
|
+
const r = el.getBoundingClientRect();
|
|
252
|
+
const distToBottom = bodyRect.bottom - r.bottom;
|
|
253
|
+
// If text is within 3px of the bottom, it's likely clipping descenders
|
|
254
|
+
if (distToBottom >= -1 && distToBottom < 3 && el.textContent.trim()) {
|
|
255
|
+
issues.push({
|
|
256
|
+
text: el.textContent.trim().substring(0, 30),
|
|
257
|
+
dist: distToBottom
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return issues;
|
|
262
|
+
}""")
|
|
263
|
+
|
|
264
|
+
if descender_issues:
|
|
265
|
+
for issue in descender_issues[:2]:
|
|
266
|
+
errors.append(f"Text \"{issue['text']}...\" is too close to the bottom edge ({issue['dist']:.1f}px). Descenders (like 'g', 'y', 'p') may be clipped.")
|
|
267
|
+
errors.append(" 💡 Hint: Move the element up by at least 5-10px for safety.")
|
|
268
|
+
|
|
269
|
+
if width_overflow > 0 or height_overflow > 0:
|
|
270
|
+
offenders = page.evaluate("""() => {
|
|
271
|
+
const body = document.body;
|
|
272
|
+
const bodyRect = body.getBoundingClientRect();
|
|
273
|
+
const nodes = Array.from(body.querySelectorAll("*"));
|
|
274
|
+
const over = [];
|
|
275
|
+
for (const el of nodes) {
|
|
276
|
+
const r = el.getBoundingClientRect();
|
|
277
|
+
const overflowRight = Math.max(0, r.right - bodyRect.right);
|
|
278
|
+
const overflowBottom = Math.max(0, r.bottom - bodyRect.bottom);
|
|
279
|
+
const overflowLeft = Math.max(0, bodyRect.left - r.left);
|
|
280
|
+
const overflowTop = Math.max(0, bodyRect.top - r.top);
|
|
281
|
+
if (overflowRight > 0 || overflowBottom > 0 || overflowLeft > 0 || overflowTop > 0) {
|
|
282
|
+
const area = Math.max(0, r.width) * Math.max(0, r.height);
|
|
283
|
+
over.push({
|
|
284
|
+
tag: el.tagName.toLowerCase(),
|
|
285
|
+
id: el.id || "",
|
|
286
|
+
className: el.className || "",
|
|
287
|
+
overflowRight,
|
|
288
|
+
overflowBottom,
|
|
289
|
+
overflowLeft,
|
|
290
|
+
overflowTop,
|
|
291
|
+
area,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
over.sort((a, b) => b.area - a.area);
|
|
296
|
+
return over.slice(0, 3);
|
|
297
|
+
}""")
|
|
298
|
+
if offenders:
|
|
299
|
+
errors.append("Top overflow offenders:")
|
|
300
|
+
for off in offenders:
|
|
301
|
+
ident = off["tag"]
|
|
302
|
+
if off["id"]:
|
|
303
|
+
ident += f"#{off['id']}"
|
|
304
|
+
if off["className"]:
|
|
305
|
+
ident += f".{str(off['className']).strip().replace(' ', '.')}"
|
|
306
|
+
errors.append(
|
|
307
|
+
f" - {ident} (R:{off['overflowRight']:.0f}px, B:{off['overflowBottom']:.0f}px, "
|
|
308
|
+
f"L:{off['overflowLeft']:.0f}px, T:{off['overflowTop']:.0f}px)"
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
unwrapped = page.evaluate("""() => {
|
|
312
|
+
const divs = document.querySelectorAll('div');
|
|
313
|
+
const issues = [];
|
|
314
|
+
divs.forEach(div => {
|
|
315
|
+
for (const node of div.childNodes) {
|
|
316
|
+
if (node.nodeType === Node.TEXT_NODE && node.textContent.trim()) {
|
|
317
|
+
const text = node.textContent.trim().substring(0, 50);
|
|
318
|
+
issues.push(text + (node.textContent.trim().length > 50 ? '...' : ''));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
return issues;
|
|
323
|
+
}""")
|
|
324
|
+
|
|
325
|
+
if unwrapped:
|
|
326
|
+
errors.append("Found unwrapped text in DIV elements:")
|
|
327
|
+
for text in unwrapped[:3]:
|
|
328
|
+
errors.append(f" - \"{text}\"")
|
|
329
|
+
errors.append(" 💡 Hint: Wrap all text in <p>, <h1>-<h6>, <ul>, or <ol> tags")
|
|
330
|
+
|
|
331
|
+
browser.close()
|
|
332
|
+
except Exception as e:
|
|
333
|
+
errors.append(f"Validation error: {e}")
|
|
334
|
+
finally:
|
|
335
|
+
os.unlink(temp_path)
|
|
336
|
+
|
|
337
|
+
if errors:
|
|
338
|
+
return {"valid": False, "error": "\n".join(errors)}
|
|
339
|
+
return {"valid": True, "error": ""}
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
MAIN_TEXT_MAX_CHARS = 100
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def list_slide_filenames(project_dir: Path) -> list[str]:
|
|
346
|
+
if not project_dir.exists():
|
|
347
|
+
return []
|
|
348
|
+
return sorted(p.name for p in project_dir.iterdir() if p.suffix.lower() == ".html")
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _strip_html_to_text(html: str, max_chars: int = MAIN_TEXT_MAX_CHARS) -> str:
|
|
352
|
+
text = re.sub(r"<[^>]+>", " ", html)
|
|
353
|
+
text = re.sub(r"\s+", " ", text).strip()
|
|
354
|
+
return (text[:max_chars] + "…") if len(text) > max_chars else text
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Per-project template registry utilities for slides_agent_v2."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
TEMPLATE_DIRNAME = "_templates"
|
|
10
|
+
TEMPLATE_INDEX_FILENAME = "index.json"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _templates_dir(project_dir: Path) -> Path:
|
|
14
|
+
return project_dir / TEMPLATE_DIRNAME
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _template_index_path(project_dir: Path) -> Path:
|
|
18
|
+
return _templates_dir(project_dir) / TEMPLATE_INDEX_FILENAME
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def ensure_template_dir(project_dir: Path) -> Path:
|
|
22
|
+
path = _templates_dir(project_dir)
|
|
23
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
24
|
+
return path
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def load_template_index(project_dir: Path) -> dict[str, dict[str, Any]]:
|
|
28
|
+
path = _template_index_path(project_dir)
|
|
29
|
+
if not path.exists():
|
|
30
|
+
return {}
|
|
31
|
+
try:
|
|
32
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
33
|
+
except Exception:
|
|
34
|
+
return {}
|
|
35
|
+
if not isinstance(data, dict):
|
|
36
|
+
return {}
|
|
37
|
+
clean: dict[str, dict[str, Any]] = {}
|
|
38
|
+
for key, value in data.items():
|
|
39
|
+
if isinstance(key, str) and isinstance(value, dict):
|
|
40
|
+
clean[key] = value
|
|
41
|
+
return clean
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def save_template_index(project_dir: Path, index_data: dict[str, dict[str, Any]]) -> None:
|
|
45
|
+
ensure_template_dir(project_dir)
|
|
46
|
+
_template_index_path(project_dir).write_text(
|
|
47
|
+
json.dumps(index_data, ensure_ascii=False, indent=2),
|
|
48
|
+
encoding="utf-8",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def template_path(project_dir: Path, template_key: str) -> Path:
|
|
53
|
+
ensure_template_dir(project_dir)
|
|
54
|
+
return _templates_dir(project_dir) / f"{template_key}.html"
|
|
55
|
+
|