@_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,200 @@
|
|
|
1
|
+
"""Edit images with Gemini or OpenAI image models."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from openai import OpenAI
|
|
7
|
+
from pydantic import Field, field_validator, model_validator
|
|
8
|
+
|
|
9
|
+
from agency_swarm import BaseTool
|
|
10
|
+
|
|
11
|
+
from .utils.image_io import (
|
|
12
|
+
get_images_dir,
|
|
13
|
+
build_variant_output_name,
|
|
14
|
+
resolve_image_reference,
|
|
15
|
+
save_image,
|
|
16
|
+
image_to_base64_jpeg,
|
|
17
|
+
build_multimodal_outputs,
|
|
18
|
+
extract_gemini_image_and_usage,
|
|
19
|
+
extract_openai_images_and_usage,
|
|
20
|
+
run_parallel_variants_sync,
|
|
21
|
+
validate_aspect_ratio_for_model,
|
|
22
|
+
get_openai_size_for_aspect_ratio,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class EditImages(BaseTool):
|
|
27
|
+
"""
|
|
28
|
+
Edit an existing image using a text instruction.
|
|
29
|
+
|
|
30
|
+
Supports local path, URL, or generated image name references.
|
|
31
|
+
Outputs are saved to: mnt/{product_name}/generated_images/
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
product_name: str = Field(..., description="Product namespace for output files.")
|
|
35
|
+
input_image_ref: str = Field(
|
|
36
|
+
...,
|
|
37
|
+
description="Input image reference (URL, absolute path, or generated image name).",
|
|
38
|
+
)
|
|
39
|
+
edit_prompt: str = Field(..., description="Instruction describing how to edit the image.")
|
|
40
|
+
output_file_name: str = Field(
|
|
41
|
+
...,
|
|
42
|
+
description=(
|
|
43
|
+
"Output image name (without extension) or output path. "
|
|
44
|
+
"If a path is provided, the image is saved at that path."
|
|
45
|
+
),
|
|
46
|
+
)
|
|
47
|
+
model: Literal["gemini-2.5-flash-image", "gemini-3-pro-image-preview", "gpt-image-1.5"] = Field(
|
|
48
|
+
default="gemini-2.5-flash-image",
|
|
49
|
+
description="Image model to use.",
|
|
50
|
+
)
|
|
51
|
+
num_variants: int = Field(default=1, description="Number of variants to generate (1-4).")
|
|
52
|
+
aspect_ratio: Literal["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"] = Field(
|
|
53
|
+
default="1:1",
|
|
54
|
+
description="Target aspect ratio. Model compatibility is validated automatically.",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
@field_validator("product_name", "input_image_ref", "edit_prompt", "output_file_name")
|
|
58
|
+
@classmethod
|
|
59
|
+
def _not_blank(cls, value: str) -> str:
|
|
60
|
+
if not value.strip():
|
|
61
|
+
raise ValueError("value must not be empty")
|
|
62
|
+
return value
|
|
63
|
+
|
|
64
|
+
@field_validator("num_variants")
|
|
65
|
+
@classmethod
|
|
66
|
+
def _validate_variants(cls, value: int) -> int:
|
|
67
|
+
if value < 1 or value > 4:
|
|
68
|
+
raise ValueError("num_variants must be between 1 and 4")
|
|
69
|
+
return value
|
|
70
|
+
|
|
71
|
+
@model_validator(mode="after")
|
|
72
|
+
def _validate_model_aspect_ratio(self) -> "EditImages":
|
|
73
|
+
validate_aspect_ratio_for_model(self.model, self.aspect_ratio)
|
|
74
|
+
return self
|
|
75
|
+
|
|
76
|
+
def run(self) -> list:
|
|
77
|
+
images_dir = get_images_dir(self.product_name)
|
|
78
|
+
input_image, source = resolve_image_reference(self.product_name, self.input_image_ref)
|
|
79
|
+
|
|
80
|
+
if self.model.startswith("gemini-"):
|
|
81
|
+
results, usage_metadata = self._run_gemini(images_dir, input_image)
|
|
82
|
+
return build_multimodal_outputs(results, "Image editing complete")
|
|
83
|
+
|
|
84
|
+
results, usage_metadata = self._run_openai(images_dir, input_image)
|
|
85
|
+
return build_multimodal_outputs(results, "Image editing complete")
|
|
86
|
+
|
|
87
|
+
def _run_gemini(self, images_dir, input_image):
|
|
88
|
+
from google import genai
|
|
89
|
+
from google.genai.types import GenerateContentConfig, ImageConfig
|
|
90
|
+
|
|
91
|
+
api_key = os.getenv("GOOGLE_API_KEY")
|
|
92
|
+
if not api_key:
|
|
93
|
+
raise ValueError("GOOGLE_API_KEY is not set. Add it to your .env to use image editing.")
|
|
94
|
+
|
|
95
|
+
client = genai.Client(api_key=api_key)
|
|
96
|
+
results: list[dict] = []
|
|
97
|
+
total_prompt_tokens = 0.0
|
|
98
|
+
total_candidate_tokens = 0.0
|
|
99
|
+
|
|
100
|
+
def edit_single_variant(idx: int):
|
|
101
|
+
response = client.models.generate_content(
|
|
102
|
+
model=self.model,
|
|
103
|
+
contents=[self.edit_prompt, input_image],
|
|
104
|
+
config=GenerateContentConfig(
|
|
105
|
+
image_config=ImageConfig(aspect_ratio=self.aspect_ratio),
|
|
106
|
+
),
|
|
107
|
+
)
|
|
108
|
+
image, usage = extract_gemini_image_and_usage(response)
|
|
109
|
+
if image is None:
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
variant_name = build_variant_output_name(self.output_file_name, idx, self.num_variants)
|
|
113
|
+
image_name, file_path = save_image(image, variant_name, images_dir)
|
|
114
|
+
return {
|
|
115
|
+
"image_name": image_name,
|
|
116
|
+
"file_path": file_path,
|
|
117
|
+
"preview_b64": image_to_base64_jpeg(image),
|
|
118
|
+
"prompt_tokens": float(usage.get("prompt_token_count") or 0),
|
|
119
|
+
"candidate_tokens": float(usage.get("candidates_token_count") or 0),
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
raw_results = run_parallel_variants_sync(edit_single_variant, self.num_variants)
|
|
123
|
+
if not raw_results:
|
|
124
|
+
raise RuntimeError("Gemini did not return any edited images.")
|
|
125
|
+
|
|
126
|
+
for item in raw_results:
|
|
127
|
+
total_prompt_tokens += item.pop("prompt_tokens")
|
|
128
|
+
total_candidate_tokens += item.pop("candidate_tokens")
|
|
129
|
+
results.append(item)
|
|
130
|
+
|
|
131
|
+
usage_metadata = {
|
|
132
|
+
"prompt_token_count": total_prompt_tokens,
|
|
133
|
+
"candidates_token_count": total_candidate_tokens,
|
|
134
|
+
}
|
|
135
|
+
return results, usage_metadata
|
|
136
|
+
|
|
137
|
+
def _run_openai(self, images_dir, input_image):
|
|
138
|
+
api_key = os.getenv("OPENAI_API_KEY")
|
|
139
|
+
if not api_key:
|
|
140
|
+
raise RuntimeError("OPENAI_API_KEY is required for OpenAI image editing.")
|
|
141
|
+
|
|
142
|
+
size = get_openai_size_for_aspect_ratio(self.aspect_ratio)
|
|
143
|
+
|
|
144
|
+
from io import BytesIO
|
|
145
|
+
|
|
146
|
+
buffer = BytesIO()
|
|
147
|
+
input_image.save(buffer, format="PNG")
|
|
148
|
+
buffer.seek(0)
|
|
149
|
+
buffer.name = "input.png"
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
client = OpenAI(api_key=api_key)
|
|
153
|
+
response = client.images.edit(
|
|
154
|
+
model=self.model,
|
|
155
|
+
image=buffer,
|
|
156
|
+
prompt=self.edit_prompt,
|
|
157
|
+
size=size,
|
|
158
|
+
n=self.num_variants,
|
|
159
|
+
)
|
|
160
|
+
finally:
|
|
161
|
+
buffer.close()
|
|
162
|
+
|
|
163
|
+
images, usage_metadata = extract_openai_images_and_usage(response)
|
|
164
|
+
if not images:
|
|
165
|
+
raise RuntimeError("OpenAI image edit API did not return images.")
|
|
166
|
+
|
|
167
|
+
results: list[dict] = []
|
|
168
|
+
for idx, image in enumerate(images, start=1):
|
|
169
|
+
variant_name = build_variant_output_name(self.output_file_name, idx, len(images))
|
|
170
|
+
image_name, file_path = save_image(image, variant_name, images_dir)
|
|
171
|
+
results.append(
|
|
172
|
+
{
|
|
173
|
+
"image_name": image_name,
|
|
174
|
+
"file_path": file_path,
|
|
175
|
+
"preview_b64": image_to_base64_jpeg(image),
|
|
176
|
+
}
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
return results, usage_metadata
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
if __name__ == "__main__":
|
|
183
|
+
# Example test scenario
|
|
184
|
+
tool = EditImages(
|
|
185
|
+
product_name="Test_Product",
|
|
186
|
+
input_image_ref="hero_image_example_oai_hero",
|
|
187
|
+
edit_prompt=(
|
|
188
|
+
"Replace the background with a black wall, slightly illuminated with a white neon light"
|
|
189
|
+
),
|
|
190
|
+
output_file_name="edited_image_example",
|
|
191
|
+
model="gpt-image-1.5",
|
|
192
|
+
aspect_ratio="1:1",
|
|
193
|
+
num_variants=1,
|
|
194
|
+
)
|
|
195
|
+
try:
|
|
196
|
+
result = tool.run()
|
|
197
|
+
print(result)
|
|
198
|
+
except Exception as exc:
|
|
199
|
+
print(f"Image editing failed: {exc}")
|
|
200
|
+
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Generate images with Gemini or OpenAI image models."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from openai import OpenAI
|
|
7
|
+
from pydantic import Field, field_validator, model_validator
|
|
8
|
+
|
|
9
|
+
from agency_swarm import BaseTool
|
|
10
|
+
|
|
11
|
+
from .utils.image_io import (
|
|
12
|
+
get_images_dir,
|
|
13
|
+
build_variant_output_name,
|
|
14
|
+
save_image,
|
|
15
|
+
image_to_base64_jpeg,
|
|
16
|
+
build_multimodal_outputs,
|
|
17
|
+
extract_gemini_image_and_usage,
|
|
18
|
+
extract_openai_images_and_usage,
|
|
19
|
+
run_parallel_variants_sync,
|
|
20
|
+
validate_aspect_ratio_for_model,
|
|
21
|
+
get_openai_size_for_aspect_ratio,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class GenerateImages(BaseTool):
|
|
26
|
+
"""
|
|
27
|
+
Generate one or more images from a prompt using Gemini or OpenAI image models.
|
|
28
|
+
|
|
29
|
+
Outputs are saved to: mnt/{product_name}/generated_images/
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
product_name: str = Field(..., description="Product namespace for output files.")
|
|
33
|
+
prompt: str = Field(..., description="Detailed prompt describing the desired image.")
|
|
34
|
+
file_name: str = Field(
|
|
35
|
+
...,
|
|
36
|
+
description=(
|
|
37
|
+
"Output file name (without extension) or output path. "
|
|
38
|
+
"If a path is provided, the image is saved at that path."
|
|
39
|
+
),
|
|
40
|
+
)
|
|
41
|
+
model: Literal["gemini-2.5-flash-image", "gemini-3-pro-image-preview", "gpt-image-1.5"] = Field(
|
|
42
|
+
default="gemini-2.5-flash-image",
|
|
43
|
+
description="Image model to use.",
|
|
44
|
+
)
|
|
45
|
+
num_variants: int = Field(default=1, description="Number of variants to generate (1-4).")
|
|
46
|
+
aspect_ratio: Literal["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"] = Field(
|
|
47
|
+
default="1:1",
|
|
48
|
+
description="Target aspect ratio.",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@field_validator("prompt", "product_name", "file_name")
|
|
52
|
+
@classmethod
|
|
53
|
+
def _not_blank(cls, value: str) -> str:
|
|
54
|
+
if not value.strip():
|
|
55
|
+
raise ValueError("value must not be empty")
|
|
56
|
+
return value
|
|
57
|
+
|
|
58
|
+
@field_validator("num_variants")
|
|
59
|
+
@classmethod
|
|
60
|
+
def _validate_variants(cls, value: int) -> int:
|
|
61
|
+
if value < 1 or value > 4:
|
|
62
|
+
raise ValueError("num_variants must be between 1 and 4")
|
|
63
|
+
return value
|
|
64
|
+
|
|
65
|
+
@model_validator(mode="after")
|
|
66
|
+
def _validate_model_aspect_ratio(self) -> "GenerateImages":
|
|
67
|
+
validate_aspect_ratio_for_model(self.model, self.aspect_ratio)
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
def run(self) -> list:
|
|
71
|
+
images_dir = get_images_dir(self.product_name)
|
|
72
|
+
|
|
73
|
+
if self.model.startswith("gemini-"):
|
|
74
|
+
results, usage_metadata = self._run_gemini(images_dir)
|
|
75
|
+
return build_multimodal_outputs(results, "Image generation complete")
|
|
76
|
+
|
|
77
|
+
results, usage_metadata = self._run_openai(images_dir)
|
|
78
|
+
return build_multimodal_outputs(results, "Image generation complete")
|
|
79
|
+
|
|
80
|
+
def _run_gemini(self, images_dir):
|
|
81
|
+
from google import genai
|
|
82
|
+
from google.genai.types import GenerateContentConfig, ImageConfig
|
|
83
|
+
|
|
84
|
+
api_key = os.getenv("GOOGLE_API_KEY")
|
|
85
|
+
if not api_key:
|
|
86
|
+
raise ValueError("GOOGLE_API_KEY is not set. Add it to your .env to use image generation.")
|
|
87
|
+
|
|
88
|
+
client = genai.Client(api_key=api_key)
|
|
89
|
+
results: list[dict] = []
|
|
90
|
+
total_prompt_tokens = 0.0
|
|
91
|
+
total_candidate_tokens = 0.0
|
|
92
|
+
|
|
93
|
+
def generate_single_variant(idx: int):
|
|
94
|
+
response = client.models.generate_content(
|
|
95
|
+
model=self.model,
|
|
96
|
+
contents=self.prompt,
|
|
97
|
+
config=GenerateContentConfig(
|
|
98
|
+
image_config=ImageConfig(aspect_ratio=self.aspect_ratio),
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
image, usage = extract_gemini_image_and_usage(response)
|
|
102
|
+
if image is None:
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
variant_name = build_variant_output_name(self.file_name, idx, self.num_variants)
|
|
106
|
+
image_name, file_path = save_image(image, variant_name, images_dir)
|
|
107
|
+
return {
|
|
108
|
+
"image_name": image_name,
|
|
109
|
+
"file_path": file_path,
|
|
110
|
+
"preview_b64": image_to_base64_jpeg(image),
|
|
111
|
+
"prompt_tokens": float(usage.get("prompt_token_count") or 0),
|
|
112
|
+
"candidate_tokens": float(usage.get("candidates_token_count") or 0),
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
raw_results = run_parallel_variants_sync(generate_single_variant, self.num_variants)
|
|
116
|
+
if not raw_results:
|
|
117
|
+
raise RuntimeError("Gemini did not return any images.")
|
|
118
|
+
|
|
119
|
+
for item in raw_results:
|
|
120
|
+
total_prompt_tokens += item.pop("prompt_tokens")
|
|
121
|
+
total_candidate_tokens += item.pop("candidate_tokens")
|
|
122
|
+
results.append(item)
|
|
123
|
+
|
|
124
|
+
usage_metadata = {
|
|
125
|
+
"prompt_token_count": total_prompt_tokens,
|
|
126
|
+
"candidates_token_count": total_candidate_tokens,
|
|
127
|
+
}
|
|
128
|
+
return results, usage_metadata
|
|
129
|
+
|
|
130
|
+
def _run_openai(self, images_dir):
|
|
131
|
+
api_key = os.getenv("OPENAI_API_KEY")
|
|
132
|
+
if not api_key:
|
|
133
|
+
raise RuntimeError("OPENAI_API_KEY is required for OpenAI image generation.")
|
|
134
|
+
|
|
135
|
+
size = get_openai_size_for_aspect_ratio(self.aspect_ratio)
|
|
136
|
+
|
|
137
|
+
client = OpenAI(api_key=api_key)
|
|
138
|
+
response = client.images.generate(
|
|
139
|
+
model=self.model,
|
|
140
|
+
prompt=self.prompt,
|
|
141
|
+
size=size,
|
|
142
|
+
n=self.num_variants,
|
|
143
|
+
)
|
|
144
|
+
images, usage_metadata = extract_openai_images_and_usage(response)
|
|
145
|
+
if not images:
|
|
146
|
+
raise RuntimeError("OpenAI image API did not return images.")
|
|
147
|
+
|
|
148
|
+
results: list[dict] = []
|
|
149
|
+
for idx, image in enumerate(images, start=1):
|
|
150
|
+
variant_name = build_variant_output_name(self.file_name, idx, len(images))
|
|
151
|
+
image_name, file_path = save_image(image, variant_name, images_dir)
|
|
152
|
+
results.append(
|
|
153
|
+
{
|
|
154
|
+
"image_name": image_name,
|
|
155
|
+
"file_path": file_path,
|
|
156
|
+
"preview_b64": image_to_base64_jpeg(image),
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
return results, usage_metadata
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
if __name__ == "__main__":
|
|
163
|
+
# Example test scenario
|
|
164
|
+
tool = GenerateImages(
|
|
165
|
+
product_name="Test_Product",
|
|
166
|
+
prompt=(
|
|
167
|
+
"A premium product hero shot of a minimalist glass skincare bottle on a "
|
|
168
|
+
"clean marble surface, soft studio lighting, subtle reflections, "
|
|
169
|
+
"high-end commercial photography style."
|
|
170
|
+
),
|
|
171
|
+
# prompt=(
|
|
172
|
+
# "Create a logo for a skincare product that includes a leaf and a drop of water"
|
|
173
|
+
# ),
|
|
174
|
+
file_name="hero_image_example_oai_hero",
|
|
175
|
+
model="gpt-image-1.5",
|
|
176
|
+
aspect_ratio="1:1",
|
|
177
|
+
num_variants=1,
|
|
178
|
+
)
|
|
179
|
+
try:
|
|
180
|
+
result = tool.run()
|
|
181
|
+
print(result)
|
|
182
|
+
except Exception as exc:
|
|
183
|
+
print(f"Image generation failed: {exc}")
|
|
184
|
+
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Remove image backgrounds using the Pixelcut model via fal.ai."""
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
8
|
+
import fal_client
|
|
9
|
+
import requests
|
|
10
|
+
from PIL import Image
|
|
11
|
+
from pydantic import Field, field_validator
|
|
12
|
+
|
|
13
|
+
from agency_swarm import BaseTool
|
|
14
|
+
|
|
15
|
+
from .utils.image_io import (
|
|
16
|
+
find_image_path_from_name,
|
|
17
|
+
get_images_dir,
|
|
18
|
+
save_image,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
FAL_ENDPOINT = "pixelcut/background-removal"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RemoveBackground(BaseTool):
|
|
25
|
+
"""
|
|
26
|
+
Remove the background from an image using Pixelcut via fal.ai.
|
|
27
|
+
|
|
28
|
+
The output is a transparent PNG saved to: mnt/{product_name}/generated_images/
|
|
29
|
+
Supports local path, URL, or generated image name as input.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
product_name: str = Field(..., description="Product namespace for output files.")
|
|
33
|
+
input_image_ref: str = Field(
|
|
34
|
+
...,
|
|
35
|
+
description="Input image reference (URL, absolute path, or generated image name).",
|
|
36
|
+
)
|
|
37
|
+
output_file_name: str = Field(
|
|
38
|
+
...,
|
|
39
|
+
description=(
|
|
40
|
+
"Output image name (without extension) or output path. "
|
|
41
|
+
"If a path is provided, the image is saved at that path."
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
@field_validator("product_name", "input_image_ref", "output_file_name")
|
|
46
|
+
@classmethod
|
|
47
|
+
def _not_blank(cls, value: str) -> str:
|
|
48
|
+
if not value.strip():
|
|
49
|
+
raise ValueError("value must not be empty")
|
|
50
|
+
return value
|
|
51
|
+
|
|
52
|
+
def run(self) -> list:
|
|
53
|
+
api_key = os.getenv("FAL_KEY")
|
|
54
|
+
if not api_key:
|
|
55
|
+
raise ValueError("FAL_KEY is not set. Add it to your .env to use background removal.")
|
|
56
|
+
fal = fal_client.SyncClient(key=api_key)
|
|
57
|
+
|
|
58
|
+
images_dir = get_images_dir(self.product_name)
|
|
59
|
+
image_url = self._resolve_to_upload_url(images_dir, fal)
|
|
60
|
+
result = fal.subscribe(
|
|
61
|
+
FAL_ENDPOINT,
|
|
62
|
+
arguments={"image_url": image_url, "output_format": "rgba", "sync_mode": False},
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
result_url = (result.get("image") or {}).get("url")
|
|
66
|
+
if not result_url:
|
|
67
|
+
raise RuntimeError("fal.ai background removal returned no image URL.")
|
|
68
|
+
|
|
69
|
+
rgba_image = self._download_rgba(result_url)
|
|
70
|
+
image_name, file_path = save_image(rgba_image, self.output_file_name, images_dir)
|
|
71
|
+
|
|
72
|
+
return f"Background removal complete.\nImage name: {image_name}\nPath: {file_path}"
|
|
73
|
+
|
|
74
|
+
def _resolve_to_upload_url(self, images_dir: Path, fal: fal_client.SyncClient) -> str:
|
|
75
|
+
ref = self.input_image_ref.strip()
|
|
76
|
+
|
|
77
|
+
parsed = urlparse(ref)
|
|
78
|
+
if parsed.scheme in ("http", "https"):
|
|
79
|
+
return ref
|
|
80
|
+
|
|
81
|
+
candidate = Path(ref).expanduser().resolve()
|
|
82
|
+
if candidate.exists():
|
|
83
|
+
return fal.upload_file(str(candidate))
|
|
84
|
+
|
|
85
|
+
by_name = find_image_path_from_name(images_dir, ref)
|
|
86
|
+
if by_name is not None:
|
|
87
|
+
return fal.upload_file(str(by_name))
|
|
88
|
+
|
|
89
|
+
raise FileNotFoundError(
|
|
90
|
+
f"Could not resolve image reference '{ref}' as URL, path, or name in {images_dir}."
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def _download_rgba(self, url: str) -> Image.Image:
|
|
94
|
+
response = requests.get(url, timeout=30)
|
|
95
|
+
response.raise_for_status()
|
|
96
|
+
return Image.open(io.BytesIO(response.content)).convert("RGBA")
|
|
97
|
+
|
|
98
|
+
if __name__ == "__main__":
|
|
99
|
+
tool = RemoveBackground(
|
|
100
|
+
product_name="Test_Product",
|
|
101
|
+
input_image_ref="test_image.jpg",
|
|
102
|
+
output_file_name="hero_no_bg",
|
|
103
|
+
)
|
|
104
|
+
try:
|
|
105
|
+
result = tool.run()
|
|
106
|
+
print(result)
|
|
107
|
+
except Exception as exc:
|
|
108
|
+
print(f"Background removal failed: {exc}")
|