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