writer 0.8.3rc4__py3-none-any.whl → 1.25.1rc1__py3-none-any.whl

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 (378) hide show
  1. writer/__init__.py +1 -1
  2. writer/abstract.py +1 -1
  3. writer/{ai.py → ai/__init__.py} +867 -163
  4. writer/app_runner.py +596 -241
  5. writer/app_templates/default/.wf/components-blueprints_blueprint-0-0decp3w5erhvl0nw.jsonl +11 -0
  6. writer/app_templates/default/.wf/components-blueprints_root.jsonl +1 -0
  7. writer/app_templates/default/.wf/components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl +27 -0
  8. writer/app_templates/default/.wf/components-root.jsonl +1 -0
  9. writer/app_templates/default/.wf/components-workflows_root.jsonl +1 -0
  10. writer/app_templates/default/.wf/components-workflows_workflow-0-lfltcky7l1fsm6j2.jsonl +1 -0
  11. writer/app_templates/default/.wf/metadata.json +3 -0
  12. writer/app_templates/default/README.md +3 -0
  13. writer/app_templates/default/main.py +16 -0
  14. writer/app_templates/default/requirements.txt +1 -0
  15. writer/app_templates/default/static/README.md +8 -0
  16. writer/app_templates/default/static/agent_builder_demo.png +0 -0
  17. writer/app_templates/default/static/favicon.png +0 -0
  18. writer/app_templates/hello/.wf/components-blueprints_blueprint-0-t84xyhxau9ej3823.jsonl +18 -0
  19. writer/app_templates/hello/.wf/components-blueprints_root.jsonl +1 -0
  20. writer/app_templates/hello/.wf/components-page-0-c0f99a9e-5004-4e75-a6c6-36f17490b134.jsonl +15 -0
  21. writer/app_templates/hello/.wf/components-root.jsonl +1 -0
  22. writer/app_templates/hello/.wf/metadata.json +3 -0
  23. writer/app_templates/hello/main.py +16 -0
  24. writer/app_templates/hello/static/README.md +8 -0
  25. writer/app_templates/hello/static/favicon.png +0 -0
  26. writer/app_templates/hello/static/welcome.svg +40 -0
  27. writer/auth.py +7 -2
  28. writer/autogen.py +352 -0
  29. writer/blocks/__init__.py +51 -17
  30. writer/blocks/addtostatelist.py +10 -9
  31. writer/blocks/apitrigger.py +45 -0
  32. writer/blocks/base_block.py +332 -21
  33. writer/blocks/base_trigger.py +14 -0
  34. writer/blocks/calleventhandler.py +39 -35
  35. writer/blocks/changepage.py +48 -0
  36. writer/blocks/code.py +102 -0
  37. writer/blocks/crontrigger.py +49 -0
  38. writer/blocks/foreach.py +70 -53
  39. writer/blocks/httprequest.py +112 -99
  40. writer/blocks/ifelse.py +71 -0
  41. writer/blocks/logmessage.py +34 -39
  42. writer/blocks/parsejson.py +30 -29
  43. writer/blocks/returnvalue.py +7 -7
  44. writer/blocks/runblueprint.py +63 -0
  45. writer/blocks/setstate.py +43 -33
  46. writer/blocks/sharedblueprint.py +86 -0
  47. writer/blocks/uieventtrigger.py +49 -0
  48. writer/blocks/writeraddchatmessage.py +50 -12
  49. writer/blocks/writeraddtokg.py +38 -11
  50. writer/blocks/writeraskkg.py +123 -0
  51. writer/blocks/writerchat.py +80 -61
  52. writer/blocks/writerchatreply.py +279 -0
  53. writer/blocks/writerchatreplywithtoolconfig.py +393 -0
  54. writer/blocks/writerclassification.py +78 -39
  55. writer/blocks/writercompletion.py +49 -44
  56. writer/blocks/writerfileapi.py +85 -0
  57. writer/blocks/writerinitchat.py +24 -12
  58. writer/blocks/writerkeyvaluestorage.py +106 -0
  59. writer/blocks/writernocodeapp.py +35 -37
  60. writer/blocks/writerparsepdf.py +73 -0
  61. writer/blocks/writerstructuredoutput.py +105 -0
  62. writer/blocks/writertoolcalling.py +251 -0
  63. writer/blocks/writervision.py +141 -0
  64. writer/blocks/writerwebsearch.py +175 -0
  65. writer/blueprints.py +839 -0
  66. writer/command_line.py +52 -16
  67. writer/core.py +562 -290
  68. writer/core_ui.py +6 -2
  69. writer/evaluator.py +98 -46
  70. writer/journal.py +227 -0
  71. writer/keyvalue_storage.py +93 -0
  72. writer/logs.py +277 -0
  73. writer/serve.py +625 -327
  74. writer/ss_types.py +101 -12
  75. writer/static/assets/Arrow.dom-GBJpMYQS.js +1 -0
  76. writer/static/assets/BaseMarkdown-Wrvby5J8.js +1 -0
  77. writer/static/assets/BlueprintToolbar-BuXNRxWT.js +1 -0
  78. writer/static/assets/BlueprintToolbar-wpfX0jo_.css +1 -0
  79. writer/static/assets/BuilderApp-PTOI76jZ.js +8 -0
  80. writer/static/assets/BuilderApp-WimUfNZr.css +1 -0
  81. writer/static/assets/BuilderApplicationSelect-DXzy4e_h.js +7 -0
  82. writer/static/assets/BuilderApplicationSelect-XaM1D5fv.css +1 -0
  83. writer/static/assets/BuilderBlueprintLibraryPanel-Ckrhknlh.css +1 -0
  84. writer/static/assets/BuilderBlueprintLibraryPanel-DBDzhTlc.js +1 -0
  85. writer/static/assets/BuilderEmbeddedCodeEditor-B0bcjlhk.css +1 -0
  86. writer/static/assets/BuilderEmbeddedCodeEditor-Dn7eDICN.js +726 -0
  87. writer/static/assets/BuilderGraphSelect-C-LRsO8W.js +7 -0
  88. writer/static/assets/BuilderGraphSelect-D7B61d5s.css +1 -0
  89. writer/static/assets/BuilderInsertionLabel-BhyL9wgn.js +1 -0
  90. writer/static/assets/BuilderInsertionLabel-_YS5WPfq.css +1 -0
  91. writer/static/assets/BuilderInsertionOverlay-D2XS0ij9.css +1 -0
  92. writer/static/assets/BuilderInsertionOverlay-MkAIVruY.js +1 -0
  93. writer/static/assets/BuilderJournal-A0LcEwGI.js +7 -0
  94. writer/static/assets/BuilderJournal-DHv3Pvvm.css +1 -0
  95. writer/static/assets/BuilderModelSelect-CdSo_sih.js +7 -0
  96. writer/static/assets/BuilderModelSelect-Dc4IPLp2.css +1 -0
  97. writer/static/assets/BuilderSettings-BDwZBveu.js +16 -0
  98. writer/static/assets/BuilderSettings-lZkOXEYw.css +1 -0
  99. writer/static/assets/BuilderSettingsArtifactAPITriggerDetails-3O6jKBXD.js +4 -0
  100. writer/static/assets/BuilderSettingsArtifactAPITriggerDetails-DnX66iRg.css +1 -0
  101. writer/static/assets/BuilderSettingsDeploySharedBlueprint-BR_3ptsd.js +1 -0
  102. writer/static/assets/BuilderSettingsDeploySharedBlueprint-KJTl8gxP.css +1 -0
  103. writer/static/assets/BuilderSettingsHandlers-CBtEQFSo.js +1 -0
  104. writer/static/assets/BuilderSettingsHandlers-DJPeASfz.css +1 -0
  105. writer/static/assets/BuilderSidebarComponentTree-DLltgas5.js +1 -0
  106. writer/static/assets/BuilderSidebarComponentTree-DYu1F793.css +1 -0
  107. writer/static/assets/BuilderSidebarToolkit-CApZNTAq.js +7 -0
  108. writer/static/assets/BuilderSidebarToolkit-CwqbjRv8.css +1 -0
  109. writer/static/assets/BuilderTemplateEditor-CYSDeWgV.css +1 -0
  110. writer/static/assets/BuilderTemplateEditor-DnRDRcA0.js +87 -0
  111. writer/static/assets/BuilderVault-2vGoV0sx.js +1 -0
  112. writer/static/assets/BuilderVault-Cx6oQSES.css +1 -0
  113. writer/static/assets/ComponentRenderer-72hqvEvI.css +1 -0
  114. writer/static/assets/ComponentRenderer-D4Pj1i3s.js +1 -0
  115. writer/static/assets/SharedCopyClipboardButton-BipJKGtz.css +1 -0
  116. writer/static/assets/SharedCopyClipboardButton-DNI9kLe6.js +1 -0
  117. writer/static/assets/WdsCheckbox-DKvpPA4D.css +1 -0
  118. writer/static/assets/WdsCheckbox-edQcn1cf.js +1 -0
  119. writer/static/assets/WdsDropdownMenu-CzzPN9Wg.css +1 -0
  120. writer/static/assets/WdsDropdownMenu-DQnrRBNV.js +1 -0
  121. writer/static/assets/WdsFieldWrapper-Cmufx5Nj.js +1 -0
  122. writer/static/assets/WdsFieldWrapper-CsemOh8D.css +1 -0
  123. writer/static/assets/WdsTabs-DKj7BqI0.css +1 -0
  124. writer/static/assets/WdsTabs-DcfY_zn5.js +1 -0
  125. writer/static/assets/abap-D8nrxEjS.js +6 -0
  126. writer/static/assets/apex-BrXDlLUW.js +6 -0
  127. writer/static/assets/art-paper-D70v1WMA.svg +180 -0
  128. writer/static/assets/azcli-CElzELwZ.js +6 -0
  129. writer/static/assets/bat-CUsyEhik.js +6 -0
  130. writer/static/assets/bicep-BtxyJn6H.js +7 -0
  131. writer/static/assets/cameligo-ClBCoF8h.js +6 -0
  132. writer/static/assets/clojure-B9TqLHAk.js +6 -0
  133. writer/static/assets/codicon-BA2IlpFX.ttf +0 -0
  134. writer/static/assets/coffee-DYsfeylR.js +6 -0
  135. writer/static/assets/cpp-VVGvvgir.js +6 -0
  136. writer/static/assets/csharp-Z6z2stHy.js +6 -0
  137. writer/static/assets/csp-DgZoLDI1.js +6 -0
  138. writer/static/assets/css-KqQ96-gC.js +8 -0
  139. writer/static/assets/css.worker-DvNUQFd1.js +84 -0
  140. writer/static/assets/cssMode-BYq4oZGq.js +9 -0
  141. writer/static/assets/cypher-CYoSlgTu.js +6 -0
  142. writer/static/assets/dart-BGDl7St1.js +6 -0
  143. writer/static/assets/dockerfile-CuCtxA7T.js +6 -0
  144. writer/static/assets/ecl-BCTFAUpS.js +6 -0
  145. writer/static/assets/editor.worker-BVwmgLrR.js +11 -0
  146. writer/static/assets/elixir-C7hRTYZ9.js +6 -0
  147. writer/static/assets/flow9-Bi_qi707.js +6 -0
  148. writer/static/assets/freemarker2-CnNourkO.js +8 -0
  149. writer/static/assets/fsharp-CxaaEKKi.js +6 -0
  150. writer/static/assets/go-DUImKuGY.js +6 -0
  151. writer/static/assets/graphql-D5sGVkLV.js +6 -0
  152. writer/static/assets/handlebars-Bm22yapJ.js +6 -0
  153. writer/static/assets/hcl-zD_CCkZ1.js +6 -0
  154. writer/static/assets/html-CAKAfoZF.js +6 -0
  155. writer/static/assets/html.worker-BJMlcbMU.js +458 -0
  156. writer/static/assets/htmlMode-BGZ97n-V.js +9 -0
  157. writer/static/assets/index-5u5REPT4.js +16 -0
  158. writer/static/assets/index-BKNuk68o.css +1 -0
  159. writer/static/assets/index-BQNXU3IR.js +17 -0
  160. writer/static/assets/index-BQr1pfrb.js +1 -0
  161. writer/static/assets/index-DHXAd5Yn.js +4 -0
  162. writer/static/assets/index-Zki-pfO-.js +8525 -0
  163. writer/static/assets/index.esm-B1ZQtduY.js +17 -0
  164. writer/static/assets/ini-8kKHd4ZL.js +6 -0
  165. writer/static/assets/java-De1axCfe.js +6 -0
  166. writer/static/assets/javascript-X1f02eyK.js +6 -0
  167. writer/static/assets/json.worker-BwvX8PuZ.js +42 -0
  168. writer/static/assets/jsonMode-hT0bNgT8.js +11 -0
  169. writer/static/assets/julia-D3ApGBxz.js +6 -0
  170. writer/static/assets/kotlin-GbSrCElU.js +6 -0
  171. writer/static/assets/less-DNUaDNdz.js +7 -0
  172. writer/static/assets/lexon-Bg9QKxBu.js +6 -0
  173. writer/static/assets/liquid-KmCCiJw2.js +6 -0
  174. writer/static/assets/lua-Crkvc3mc.js +6 -0
  175. writer/static/assets/m3-DsrzVyM1.js +6 -0
  176. writer/static/assets/mapbox-gl-C0cyFYYW.js +2329 -0
  177. writer/static/assets/markdown-CY5IOZuu.js +6 -0
  178. writer/static/assets/marked.esm-273vDTCT.js +45 -0
  179. writer/static/assets/mdx-DtRFauUw.js +6 -0
  180. writer/static/assets/mips-BE8RsGBA.js +6 -0
  181. writer/static/assets/msdax-N5ajIiFQ.js +6 -0
  182. writer/static/assets/mysql-DRxbB97D.js +6 -0
  183. writer/static/assets/objective-c-BHUZy23s.js +6 -0
  184. writer/static/assets/pascal-BemVzBTY.js +6 -0
  185. writer/static/assets/pascaligo-BACCcnx_.js +6 -0
  186. writer/static/assets/pdf-B6-yWJ-Y.js +12 -0
  187. writer/static/assets/pdf.worker.min-CyUfim15.mjs +21 -0
  188. writer/static/assets/perl-CuU66Ptk.js +6 -0
  189. writer/static/assets/pgsql-CQ6TMH2r.js +6 -0
  190. writer/static/assets/php-BvyzZa65.js +6 -0
  191. writer/static/assets/pla-DrIuu9u1.js +6 -0
  192. writer/static/assets/plotly.min-DutuuatZ.js +4030 -0
  193. writer/static/assets/poppins-latin-300-italic-4WBEAciR.woff +0 -0
  194. writer/static/assets/poppins-latin-300-italic-EWCPeN2Y.woff2 +0 -0
  195. writer/static/assets/poppins-latin-300-normal-DCNuMXUj.woff +0 -0
  196. writer/static/assets/poppins-latin-300-normal-Dku2WoCh.woff2 +0 -0
  197. writer/static/assets/poppins-latin-400-italic-B4GYq972.woff2 +0 -0
  198. writer/static/assets/poppins-latin-400-italic-BPejoDS-.woff +0 -0
  199. writer/static/assets/poppins-latin-400-normal-BOb3E3N0.woff +0 -0
  200. writer/static/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
  201. writer/static/assets/poppins-latin-500-italic-Ce_qjtl5.woff +0 -0
  202. writer/static/assets/poppins-latin-500-italic-o28Otv0U.woff2 +0 -0
  203. writer/static/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
  204. writer/static/assets/poppins-latin-500-normal-DGXqpDMm.woff +0 -0
  205. writer/static/assets/poppins-latin-600-italic-BhOZippK.woff +0 -0
  206. writer/static/assets/poppins-latin-600-italic-CZ4wqKBi.woff2 +0 -0
  207. writer/static/assets/poppins-latin-600-normal-BJdTmd5m.woff +0 -0
  208. writer/static/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
  209. writer/static/assets/poppins-latin-700-italic-CW91C-LJ.woff +0 -0
  210. writer/static/assets/poppins-latin-700-italic-RKf6esGj.woff2 +0 -0
  211. writer/static/assets/poppins-latin-700-normal-BVuQR_eA.woff +0 -0
  212. writer/static/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
  213. writer/static/assets/poppins-latin-ext-300-italic-CBzyU4Pf.woff +0 -0
  214. writer/static/assets/poppins-latin-ext-300-italic-DdDvTq5-.woff2 +0 -0
  215. writer/static/assets/poppins-latin-ext-300-normal-7Zg2msWE.woff2 +0 -0
  216. writer/static/assets/poppins-latin-ext-300-normal-C9p7gvmA.woff +0 -0
  217. writer/static/assets/poppins-latin-ext-400-italic-BiCGV3eO.woff2 +0 -0
  218. writer/static/assets/poppins-latin-ext-400-italic-gsPYOGqV.woff +0 -0
  219. writer/static/assets/poppins-latin-ext-400-normal-CIpeJEZw.woff2 +0 -0
  220. writer/static/assets/poppins-latin-ext-400-normal-Ce_uWq1Z.woff +0 -0
  221. writer/static/assets/poppins-latin-ext-500-italic-CwrTHwbn.woff2 +0 -0
  222. writer/static/assets/poppins-latin-ext-500-italic-jdc8Bv4M.woff +0 -0
  223. writer/static/assets/poppins-latin-ext-500-normal-Bl1-S02S.woff +0 -0
  224. writer/static/assets/poppins-latin-ext-500-normal-H4Q0z8D2.woff2 +0 -0
  225. writer/static/assets/poppins-latin-ext-600-italic-BqeDa496.woff2 +0 -0
  226. writer/static/assets/poppins-latin-ext-600-italic-C7MQPb_A.woff +0 -0
  227. writer/static/assets/poppins-latin-ext-600-normal-Cn4C8475.woff2 +0 -0
  228. writer/static/assets/poppins-latin-ext-600-normal-DB6FJURc.woff +0 -0
  229. writer/static/assets/poppins-latin-ext-700-italic-BAdhB_WS.woff2 +0 -0
  230. writer/static/assets/poppins-latin-ext-700-italic-WKTwQMp8.woff +0 -0
  231. writer/static/assets/poppins-latin-ext-700-normal-CE2WFKmF.woff +0 -0
  232. writer/static/assets/poppins-latin-ext-700-normal-DDaViAzG.woff2 +0 -0
  233. writer/static/assets/postiats-BR_hrfni.js +6 -0
  234. writer/static/assets/powerquery-CKDUeRmd.js +6 -0
  235. writer/static/assets/powershell-Dsa4rhA_.js +6 -0
  236. writer/static/assets/protobuf-CGsvhooB.js +7 -0
  237. writer/static/assets/pug-D2p3uOX2.js +6 -0
  238. writer/static/assets/python-DVhxg746.js +6 -0
  239. writer/static/assets/qsharp-B7F3HtPF.js +6 -0
  240. writer/static/assets/r-3aLoi2fs.js +6 -0
  241. writer/static/assets/razor-DR5Ns_BC.js +6 -0
  242. writer/static/assets/redis-jqFeRM5s.js +6 -0
  243. writer/static/assets/redshift-BriwQgXR.js +6 -0
  244. writer/static/assets/restructuredtext-hbBFZ0w9.js +6 -0
  245. writer/static/assets/ruby-ByThyB2Q.js +6 -0
  246. writer/static/assets/rust-DIEZMp5R.js +6 -0
  247. writer/static/assets/sb-C6Gjjw_x.js +6 -0
  248. writer/static/assets/scala-DZNw3jJB.js +6 -0
  249. writer/static/assets/scheme-55eqh71t.js +6 -0
  250. writer/static/assets/scss-D-OVkc4F.js +8 -0
  251. writer/static/assets/serialization-DJC7NP0N.js +20 -0
  252. writer/static/assets/shell-DSpi8_qN.js +6 -0
  253. writer/static/assets/solidity-BHddiNFS.js +6 -0
  254. writer/static/assets/sophia-D6taVZFb.js +6 -0
  255. writer/static/assets/sparql-LA0C7mUc.js +6 -0
  256. writer/static/assets/sql-C3-3IcFM.js +6 -0
  257. writer/static/assets/st-C4g7059C.js +6 -0
  258. writer/static/assets/swift-DNI1vH3h.js +8 -0
  259. writer/static/assets/systemverilog-DL_FVbcQ.js +6 -0
  260. writer/static/assets/tcl-DVJXmIwd.js +6 -0
  261. writer/static/assets/ts.worker-CwG1rUES.js +37021 -0
  262. writer/static/assets/tsMode-BNUEZzir.js +16 -0
  263. writer/static/assets/twig-BVWDLtw5.js +6 -0
  264. writer/static/assets/typescript-CRVt7Hx0.js +6 -0
  265. writer/static/assets/useBlueprintRun-C00bCxh-.js +1 -0
  266. writer/static/assets/useKeyValueEditor-nDmI7cTJ.js +1 -0
  267. writer/static/assets/useListResources-DLkZhRSJ.js +1 -0
  268. writer/static/assets/vb-Btz91-7U.js +6 -0
  269. writer/static/assets/vega-embed.module-SNP5iNdJ.js +201 -0
  270. writer/static/assets/wgsl-D8V_buCG.js +303 -0
  271. writer/static/assets/xml-C_6-t1tb.js +6 -0
  272. writer/static/assets/yaml-DIw8G7jk.js +6 -0
  273. writer/static/components/annotatedtext.svg +4 -0
  274. writer/static/components/avatar.svg +4 -0
  275. writer/static/components/blueprints_addtostatelist.svg +4 -0
  276. writer/static/components/blueprints_apitrigger.svg +4 -0
  277. writer/static/components/blueprints_calleventhandler.svg +9 -0
  278. writer/static/components/blueprints_category_Logic.svg +4 -0
  279. writer/static/components/blueprints_category_Other.svg +4 -0
  280. writer/static/components/blueprints_category_Triggers.svg +4 -0
  281. writer/static/components/blueprints_category_Writer.svg +25 -0
  282. writer/static/components/blueprints_code.svg +9 -0
  283. writer/static/components/blueprints_crontrigger.svg +6 -0
  284. writer/static/components/blueprints_foreach.svg +4 -0
  285. writer/static/components/blueprints_httprequest.svg +11 -0
  286. writer/static/components/blueprints_logmessage.svg +11 -0
  287. writer/static/components/blueprints_parsejson.svg +4 -0
  288. writer/static/components/blueprints_returnvalue.svg +4 -0
  289. writer/static/components/blueprints_runblueprint.svg +4 -0
  290. writer/static/components/blueprints_setstate.svg +4 -0
  291. writer/static/components/blueprints_uieventtrigger.svg +4 -0
  292. writer/static/components/blueprints_writeraddchatmessage.svg +19 -0
  293. writer/static/components/blueprints_writeraddtokg.svg +19 -0
  294. writer/static/components/blueprints_writerchat.svg +11 -0
  295. writer/static/components/blueprints_writerchatreply.svg +19 -0
  296. writer/static/components/blueprints_writerclassification.svg +24 -0
  297. writer/static/components/blueprints_writercompletion.svg +14 -0
  298. writer/static/components/blueprints_writerinitchat.svg +11 -0
  299. writer/static/components/blueprints_writernocodeapp.svg +14 -0
  300. writer/static/components/button.svg +4 -0
  301. writer/static/components/category_Content.svg +4 -0
  302. writer/static/components/category_Embed.svg +4 -0
  303. writer/static/components/category_Input.svg +5 -0
  304. writer/static/components/category_Layout.svg +9 -0
  305. writer/static/components/category_Other.svg +4 -0
  306. writer/static/components/chatbot.svg +4 -0
  307. writer/static/components/checkboxinput.svg +4 -0
  308. writer/static/components/colorinput.svg +11 -0
  309. writer/static/components/column.svg +4 -0
  310. writer/static/components/columns.svg +4 -0
  311. writer/static/components/dataframe.svg +4 -0
  312. writer/static/components/dateinput.svg +4 -0
  313. writer/static/components/dropdowninput.svg +5 -0
  314. writer/static/components/fileinput.svg +4 -0
  315. writer/static/components/googlemaps.svg +4 -0
  316. writer/static/components/header.svg +4 -0
  317. writer/static/components/heading.svg +9 -0
  318. writer/static/components/horizontalstack.svg +4 -0
  319. writer/static/components/html.svg +9 -0
  320. writer/static/components/icon.svg +4 -0
  321. writer/static/components/iframe.svg +4 -0
  322. writer/static/components/image.svg +11 -0
  323. writer/static/components/jsonviewer.svg +4 -0
  324. writer/static/components/link.svg +12 -0
  325. writer/static/components/mapbox.svg +4 -0
  326. writer/static/components/message.svg +4 -0
  327. writer/static/components/metric.svg +4 -0
  328. writer/static/components/multiselectinput.svg +4 -0
  329. writer/static/components/numberinput.svg +4 -0
  330. writer/static/components/page.svg +50 -0
  331. writer/static/components/pagination.svg +4 -0
  332. writer/static/components/pdf.svg +4 -0
  333. writer/static/components/plotlygraph.svg +7 -0
  334. writer/static/components/progressbar.svg +5 -0
  335. writer/static/components/radioinput.svg +4 -0
  336. writer/static/components/rangeinput.svg +4 -0
  337. writer/static/components/ratinginput.svg +4 -0
  338. writer/static/components/repeater.svg +4 -0
  339. writer/static/components/reuse.svg +4 -0
  340. writer/static/components/section.svg +4 -0
  341. writer/static/components/selectinput.svg +5 -0
  342. writer/static/components/separator.svg +4 -0
  343. writer/static/components/sidebar.svg +4 -0
  344. writer/static/components/sliderinput.svg +4 -0
  345. writer/static/components/step.svg +4 -0
  346. writer/static/components/steps.svg +4 -0
  347. writer/static/components/switchinput.svg +4 -0
  348. writer/static/components/tab.svg +4 -0
  349. writer/static/components/tabs.svg +4 -0
  350. writer/static/components/tags.svg +11 -0
  351. writer/static/components/text.svg +4 -0
  352. writer/static/components/textareainput.svg +11 -0
  353. writer/static/components/textinput.svg +4 -0
  354. writer/static/components/timeinput.svg +4 -0
  355. writer/static/components/timer.svg +4 -0
  356. writer/static/components/vegalitechart.svg +7 -0
  357. writer/static/components/videoplayer.svg +11 -0
  358. writer/static/components/webcamcapture.svg +4 -0
  359. writer/static/favicon.png +0 -0
  360. writer/static/index.html +84 -0
  361. writer/static/status/cancelled.svg +5 -0
  362. writer/static/status/error.svg +5 -0
  363. writer/static/status/skipped.svg +4 -0
  364. writer/static/status/stopped.svg +4 -0
  365. writer/static/status/success.svg +4 -0
  366. writer/sync.py +431 -0
  367. writer/ui.py +2268 -0
  368. writer/vault.py +48 -0
  369. writer/wf_project.py +90 -66
  370. writer-1.25.1rc1.dist-info/METADATA +92 -0
  371. writer-1.25.1rc1.dist-info/RECORD +382 -0
  372. {writer-0.8.3rc4.dist-info → writer-1.25.1rc1.dist-info}/WHEEL +1 -1
  373. writer/blocks/runworkflow.py +0 -59
  374. writer/workflows.py +0 -183
  375. writer-0.8.3rc4.dist-info/METADATA +0 -117
  376. writer-0.8.3rc4.dist-info/RECORD +0 -44
  377. {writer-0.8.3rc4.dist-info → writer-1.25.1rc1.dist-info}/entry_points.txt +0 -0
  378. {writer-0.8.3rc4.dist-info → writer-1.25.1rc1.dist-info/licenses}/LICENSE.txt +0 -0
@@ -0,0 +1,7 @@
1
+ <svg width="19" height="19" viewBox="0 0 19 19" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect x="0.516602" y="0.193359" width="18" height="18" rx="4" fill="#333333"/>
3
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M13.5163 5.19352H5.51628V13.1935H13.5163V5.19352ZM4.84961 4.52686V13.8602H14.1829V4.52686H4.84961Z" fill="white"/>
4
+ <path d="M10.8496 11.1935V9.19352H11.5163V11.1935H10.8496Z" fill="white"/>
5
+ <path d="M7.51628 11.1935V8.52686H8.18294V11.1935H7.51628Z" fill="white"/>
6
+ <path d="M9.18294 11.1935V7.19352H9.84961L9.84961 11.1935H9.18294Z" fill="white"/>
7
+ </svg>
@@ -0,0 +1,11 @@
1
+ <svg width="19" height="18" viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect x="0.815918" width="18" height="18" rx="4" fill="#F5F5F9"/>
3
+ <g clip-path="url(#clip0_4193_236611)">
4
+ <path d="M15.0659 4.5V13.5H4.56592V4.5H15.0659ZM5.23291 5.16699V12.833H14.3989V5.16699H5.23291ZM13.2749 8.97656L7.48291 11.873V6.49512L13.2749 8.97656ZM8.1499 10.7939L11.6909 9.02344L8.1499 7.50586V10.7939Z" fill="black"/>
5
+ </g>
6
+ <defs>
7
+ <clipPath id="clip0_4193_236611">
8
+ <rect width="12" height="12" fill="white" transform="translate(3.81592 3)"/>
9
+ </clipPath>
10
+ </defs>
11
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg width="19" height="18" viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect x="0.815918" width="18" height="18" rx="4" fill="#F5F5F9"/>
3
+ <path d="M11.8159 5.6665C12.184 5.66668 12.4819 5.96541 12.4819 6.3335V8.33252L14.4819 6.3335V11.6665L12.4819 9.6665V11.6665C12.4819 12.0346 12.184 12.3333 11.8159 12.3335H5.81592C5.44773 12.3335 5.14893 12.0347 5.14893 11.6665V6.3335C5.14893 5.96531 5.44773 5.6665 5.81592 5.6665H11.8159ZM5.81592 11.6665H11.8159V9.00049L11.8149 8.99951L11.8159 8.99854V6.3335H5.81592V11.6665Z" fill="black"/>
4
+ </svg>
Binary file
@@ -0,0 +1,84 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" prefix="og: https://ogp.me/ns#">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/png" href="./static/favicon.png?2" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Writer</title>
8
+ <!-- {{ meta }} -->
9
+ <!-- {{ opengraph_tags }} -->
10
+ <style>@layer wf {
11
+ * {
12
+ box-sizing: border-box;
13
+ margin: 0;
14
+ padding: 0;
15
+ font-family: inherit;
16
+ color: currentColor;
17
+ -webkit-font-smoothing: antialiased;
18
+ }
19
+
20
+ body {
21
+ font-size: 13px;
22
+ }
23
+
24
+ #app {
25
+ isolation: isolate;
26
+ }
27
+
28
+ #app > * {
29
+ min-height: 100vh;
30
+ }
31
+
32
+ #loading_L1 {
33
+ width: 100%;
34
+ display: flex;
35
+ align-items: center;
36
+ justify-content: center;
37
+ position: relative;
38
+ }
39
+
40
+ #loading_L2 {
41
+ position: absolute;
42
+ background: conic-gradient(from 180deg at 50% 50%, #FFF 0deg, #A95EF8 360deg);
43
+ width: 113px;
44
+ height: 113px;
45
+ top: calc(50% - 56.5px);
46
+ left: calc(50% - 56.5px);
47
+ border-radius: 50%;
48
+ animation: logoAnimation 1s infinite linear;
49
+ }
50
+
51
+ #loading_L1 svg {
52
+ top: calc(50% - 49.5px);
53
+ left: calc(50% - 49px);
54
+ position: absolute;
55
+ }
56
+
57
+ @keyframes logoAnimation {
58
+ 0% {
59
+ transform: rotate(0deg);
60
+ }
61
+ 100% {
62
+ transform: rotate(360deg);
63
+ }
64
+ }
65
+ }
66
+ </style>
67
+ <script type="module" crossorigin src="./assets/index-Zki-pfO-.js"></script>
68
+ <link rel="stylesheet" crossorigin href="./assets/index-BKNuk68o.css">
69
+ </head>
70
+ <body>
71
+ <div id="app">
72
+ <div id="loading_L1">
73
+ <div id="loading_L2">
74
+ </div>
75
+ <svg width="98" height="99" viewBox="0 0 98 99" fill="none" xmlns="http://www.w3.org/2000/svg">
76
+ <rect y="1" width="98" height="98" rx="49" fill="black"/>
77
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M69.4549 60.0928L76.0021 33.0802H66.897L66.897 33.0802H62.9077L69.4549 60.0928Z" fill="white"/>
78
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M51.9127 33.0802H42.8076L51.9127 70.6464H55.959L55.959 70.6464H65.0641L55.959 33.0802H51.9128L51.9127 33.0802Z" fill="white"/>
79
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M35.8587 33.0803H26.7535L26.7537 33.081H22.7073L31.8124 70.6471H40.9176L40.9174 70.6465H44.9638L35.8587 33.0803Z" fill="white"/>
80
+ </svg>
81
+ </div>
82
+ </div>
83
+ </body>
84
+ </html>
@@ -0,0 +1,5 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="24" height="24" rx="4" fill="#d2d4d7"/>
3
+ <rect x="3" y="3" width="18" height="18" rx="9" fill="#d2d4d7"/>
4
+ <path d="M9.00002 9L15 15M15 9.00004L9 15" stroke="black" stroke-width="1.08"/>
5
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="24" height="24" rx="4" fill="#FFCFC2"/>
3
+ <rect x="3" y="3" width="18" height="18" rx="9" fill="#FFCFC2"/>
4
+ <path d="M9.00002 9L15 15M15 9.00004L9 15" stroke="black" stroke-width="1.08"/>
5
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="24" height="24" rx="4" fill="#d2d4d7"/>
3
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M10.5002 14.1L8.4002 12L7.7002 12.7L10.5002 15.5L16.5002 9.49999L15.8002 8.79999L10.5002 14.1V14.1Z" fill="black"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect y="0" width="24" height="24" rx="4" fill="#E4E7ED"/>
3
+ <path d="M16.6667 6.5H7.33333C6.59695 6.5 6 7.09695 6 7.83333V17.1667C6 17.903 6.59695 18.5 7.33333 18.5H16.6667C17.403 18.5 18 17.903 18 17.1667V7.83333C18 7.09695 17.403 6.5 16.6667 6.5Z" stroke="#828282" stroke-linecap="round" stroke-linejoin="round"/>
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="24" height="24" rx="4" fill="#A9F9E1"/>
3
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M10.5002 14.1L8.4002 12L7.7002 12.7L10.5002 15.5L16.5002 9.49999L15.8002 8.79999L10.5002 14.1V14.1Z" fill="black"/>
4
+ </svg>
writer/sync.py ADDED
@@ -0,0 +1,431 @@
1
+ import argparse
2
+ import asyncio
3
+ import os
4
+ import shutil
5
+ import stat
6
+ import tempfile
7
+ import threading
8
+ import time
9
+ from collections import defaultdict
10
+ from datetime import datetime, timedelta
11
+ from pathlib import Path
12
+ from threading import Timer
13
+ from typing import Any, Dict, List, Optional, Set, Union
14
+
15
+ from watchdog.events import FileSystemEvent, FileSystemEventHandler
16
+ from watchdog.observers import Observer
17
+ from watchdog.observers.api import BaseObserver
18
+
19
+
20
+ class OperationQueues:
21
+ def __init__(self) -> None:
22
+ self.files_to_update: Dict[str, str] = {} # Maps dest paths to their source paths
23
+ self.files_to_delete: Set[str] = set()
24
+ self.dirs_to_create: Set[str] = set()
25
+ self.dirs_to_delete: Set[str] = set()
26
+ self.lock = threading.Lock()
27
+
28
+ def has_pending_operations(self) -> bool:
29
+ with self.lock:
30
+ return (
31
+ len(self.files_to_update) > 0 or
32
+ len(self.files_to_delete) > 0 or
33
+ len(self.dirs_to_create) > 0 or
34
+ len(self.dirs_to_delete) > 0
35
+ )
36
+
37
+ def clear_all(self) -> None:
38
+ with self.lock:
39
+ self.files_to_update.clear()
40
+ self.files_to_delete.clear()
41
+ self.dirs_to_create.clear()
42
+ self.dirs_to_delete.clear()
43
+
44
+
45
+ class FolderSyncHandler(FileSystemEventHandler):
46
+ def __init__(self, src_dir: str, dest_dir: str, verbose: bool, operation_queues: OperationQueues) -> None:
47
+ self.src_dir = Path(src_dir).resolve()
48
+ self.dest_dir = Path(dest_dir).resolve()
49
+ self.verbose = verbose
50
+ self.operation_queues = operation_queues
51
+
52
+ def log(self, *args) -> None:
53
+ if self.verbose:
54
+ print(*args)
55
+
56
+ def rel(self, path: str) -> str:
57
+ return str(Path(path).relative_to(self.src_dir))
58
+
59
+ def dest_path(self, src_path: str) -> str:
60
+ rel_path = self.rel(src_path)
61
+ return str(self.dest_dir / rel_path)
62
+
63
+ def on_created(self, event) -> None:
64
+ if event.is_directory:
65
+ with self.operation_queues.lock:
66
+ self.operation_queues.dirs_to_create.add(event.src_path)
67
+ self.log("Queued dir add:", self.rel(event.src_path))
68
+ else:
69
+ dest = self.dest_path(event.src_path)
70
+ with self.operation_queues.lock:
71
+ self.operation_queues.files_to_update[dest] = event.src_path
72
+ self.log("Queued add:", self.rel(event.src_path))
73
+
74
+ def on_modified(self, event) -> None:
75
+ if not event.is_directory:
76
+ dest = self.dest_path(event.src_path)
77
+ with self.operation_queues.lock:
78
+ self.operation_queues.files_to_update[dest] = event.src_path
79
+ self.log("Queued change:", self.rel(event.src_path))
80
+
81
+ def on_deleted(self, event) -> None:
82
+ if event.is_directory:
83
+ with self.operation_queues.lock:
84
+ self.operation_queues.dirs_to_delete.add(event.src_path)
85
+ self.log("Queued dir remove:", self.rel(event.src_path))
86
+ else:
87
+ dest = self.dest_path(event.src_path)
88
+ with self.operation_queues.lock:
89
+ self.operation_queues.files_to_delete.add(event.src_path)
90
+ # Remove from update queue if present
91
+ self.operation_queues.files_to_update.pop(dest, None)
92
+ self.log("Queued remove:", self.rel(event.src_path))
93
+
94
+ def on_moved(self, event) -> None:
95
+ # Handle moves as delete + create
96
+ self.on_deleted(type('MockEvent', (), {'src_path': event.src_path, 'is_directory': event.is_directory})())
97
+ self.on_created(type('MockEvent', (), {'src_path': event.dest_path, 'is_directory': event.is_directory})())
98
+
99
+
100
+ class FileBuffering:
101
+ def __init__(self, dest_dir: str, *, src_dir: Optional[str] = None, verbose: bool = False, interval: int = 10) -> None:
102
+ self.path = src_dir if src_dir is not None else tempfile.mkdtemp()
103
+ self.src_dir = Path(self.path).resolve()
104
+ self.dest_dir = Path(dest_dir).resolve()
105
+ self.verbose = verbose
106
+ self.sync_interval = interval
107
+ self.operation_queues = OperationQueues()
108
+ self.is_sync_in_progress = False
109
+ self.observer: Optional[BaseObserver] = None
110
+ self.sync_timer: Optional[Timer] = None
111
+ self.running = False
112
+ self.last_comparison_time: datetime = datetime.now()
113
+ self.check_interval = 10
114
+ self.check_timer: Optional[Timer] = None
115
+
116
+ def log(self, *args) -> None:
117
+ if self.verbose:
118
+ print(*args)
119
+
120
+ def rel(self, path: str) -> str:
121
+ return str(Path(path).relative_to(self.src_dir))
122
+
123
+ def dest_path(self, src_path: str) -> str:
124
+ rel_path = self.rel(src_path)
125
+ return str(self.dest_dir / rel_path)
126
+
127
+ def should_copy(self, src: str, dest: str) -> bool:
128
+ try:
129
+ src_path = Path(src)
130
+ dest_path = Path(dest)
131
+
132
+ if not dest_path.exists():
133
+ return True
134
+
135
+ src_stat = src_path.stat()
136
+ dest_stat = dest_path.stat()
137
+
138
+ # Compare size
139
+ if src_stat.st_size != dest_stat.st_size:
140
+ return True
141
+
142
+ # Compare modification time (with 1 second tolerance)
143
+ if src_stat.st_mtime > dest_stat.st_mtime + 1:
144
+ return True
145
+
146
+ # Compare content
147
+ try:
148
+ with open(src, 'rb') as f1, open(dest, 'rb') as f2:
149
+ return f1.read() != f2.read()
150
+ except Exception:
151
+ return True
152
+
153
+ except Exception:
154
+ return True
155
+
156
+ def ensure_dir(self, path: str) -> None:
157
+ Path(path).mkdir(parents=True, exist_ok=True)
158
+
159
+ def copy_file(self, src: str, dest: str) -> None:
160
+ dest_path = Path(dest)
161
+ dest_path.parent.mkdir(parents=True, exist_ok=True)
162
+ shutil.copyfile(src, dest)
163
+
164
+ def remove_path(self, path: str) -> None:
165
+ path_obj = Path(path)
166
+ if path_obj.exists():
167
+ if path_obj.is_dir():
168
+ shutil.rmtree(path_obj)
169
+ else:
170
+ path_obj.unlink()
171
+
172
+ def init(self) -> None:
173
+ """Initialize by copying all files from destination to source directory."""
174
+ self.log(f"Initializing: copying files from {self.dest_dir} to {self.src_dir}")
175
+
176
+ # Check if destination directory exists
177
+ if not self.dest_dir.exists():
178
+ self.log(f"Destination directory {self.dest_dir} doesn't exist yet. Nothing to copy.")
179
+ return
180
+
181
+ def copy_recursively(relative_path: str = ""):
182
+ full_dest = self.dest_dir / relative_path
183
+ full_src = self.src_dir / relative_path
184
+
185
+ # Ensure the directory exists in source
186
+ self.ensure_dir(str(full_src))
187
+
188
+ try:
189
+ for entry in full_dest.iterdir():
190
+ rel_path = str(Path(relative_path) / entry.name) if relative_path else entry.name
191
+ dest_path = self.dest_dir / rel_path
192
+ src_path = self.src_dir / rel_path
193
+
194
+ if entry.is_dir():
195
+ copy_recursively(rel_path)
196
+ elif entry.is_file():
197
+ self.copy_file(str(dest_path), str(src_path))
198
+ self.log(f"Copied to source: {rel_path}")
199
+ except PermissionError as e:
200
+ print(f"Permission error accessing {full_dest}: {e}")
201
+ except Exception as e:
202
+ print(f"Error processing {full_dest}: {e}")
203
+
204
+ # Execute the recursive copy
205
+ copy_recursively()
206
+ self.log("Initialization complete. All files copied to temporary directory.")
207
+
208
+ def initial_sync(self) -> None:
209
+ self.log("Starting initial sync...")
210
+
211
+ def sync_recursively(relative: str = ""):
212
+ full_src = self.src_dir / relative
213
+ full_dest = self.dest_dir / relative
214
+
215
+ self.ensure_dir(str(full_dest))
216
+
217
+ try:
218
+ for entry in full_src.iterdir():
219
+ rel_path = str(Path(relative) / entry.name) if relative else entry.name
220
+ src_path = self.src_dir / rel_path
221
+ dst_path = self.dest_dir / rel_path
222
+
223
+ if entry.is_dir():
224
+ sync_recursively(rel_path)
225
+ elif entry.is_file():
226
+ if self.should_copy(str(src_path), str(dst_path)):
227
+ self.copy_file(str(src_path), str(dst_path))
228
+ self.log("Copied:", rel_path)
229
+ else:
230
+ self.log("Skipped (up to date):", rel_path)
231
+ except PermissionError as e:
232
+ print(f"Permission error accessing {full_src}: {e}")
233
+ except Exception as e:
234
+ print(f"Error processing {full_src}: {e}")
235
+
236
+ # Run synchronously since it's the initial sync
237
+ sync_recursively()
238
+
239
+ def process_batched_operations(self) -> None:
240
+ if self.is_sync_in_progress:
241
+ return
242
+ if not self.operation_queues.has_pending_operations():
243
+ return
244
+
245
+ self.is_sync_in_progress = True
246
+ self.log(f"Processing batched operations at {datetime.now().isoformat()}")
247
+
248
+ try:
249
+ with self.operation_queues.lock:
250
+ # Process directory creations first
251
+ if self.operation_queues.dirs_to_create:
252
+ self.log(f"Creating {len(self.operation_queues.dirs_to_create)} directories")
253
+ for dir_path in self.operation_queues.dirs_to_create.copy():
254
+ try:
255
+ self.log("Dir add:", self.rel(dir_path))
256
+ to = self.dest_path(dir_path)
257
+ self.ensure_dir(to)
258
+ except Exception as e:
259
+ print(f"Error creating directory {dir_path}: {e}")
260
+ self.operation_queues.dirs_to_create.clear()
261
+
262
+ # Process file updates
263
+ if self.operation_queues.files_to_update:
264
+ self.log(f"Updating {len(self.operation_queues.files_to_update)} files")
265
+ for dest_path, src_path in self.operation_queues.files_to_update.copy().items():
266
+ try:
267
+ if self.should_copy(src_path, dest_path):
268
+ self.log("File update:", self.rel(src_path))
269
+ self.copy_file(src_path, dest_path)
270
+ except Exception as e:
271
+ print(f"Error updating file {src_path} to {dest_path}: {e}")
272
+ self.operation_queues.files_to_update.clear()
273
+
274
+ # Process file deletions
275
+ if self.operation_queues.files_to_delete:
276
+ self.log(f"Deleting {len(self.operation_queues.files_to_delete)} files")
277
+ for file_path in self.operation_queues.files_to_delete.copy():
278
+ try:
279
+ self.log("File remove:", self.rel(file_path))
280
+ to = self.dest_path(file_path)
281
+ self.remove_path(to)
282
+ except Exception as e:
283
+ print(f"Error deleting file {file_path}: {e}")
284
+ self.operation_queues.files_to_delete.clear()
285
+
286
+ # Process directory deletions last
287
+ if self.operation_queues.dirs_to_delete:
288
+ self.log(f"Deleting {len(self.operation_queues.dirs_to_delete)} directories")
289
+ for dir_path in self.operation_queues.dirs_to_delete.copy():
290
+ try:
291
+ self.log("Dir remove:", self.rel(dir_path))
292
+ to = self.dest_path(dir_path)
293
+ self.remove_path(to)
294
+ except Exception as e:
295
+ print(f"Error deleting directory {dir_path}: {e}")
296
+ self.operation_queues.dirs_to_delete.clear()
297
+
298
+ except Exception as error:
299
+ print(f"Error processing batch: {error}")
300
+ finally:
301
+ self.is_sync_in_progress = False
302
+
303
+ # Compare directories after processing
304
+ self.compare_directories(str(self.src_dir), str(self.dest_dir), self.verbose)
305
+
306
+ def schedule_batch_processing(self) -> None:
307
+ if not self.running:
308
+ return
309
+
310
+ self.process_batched_operations()
311
+
312
+
313
+ # Schedule next processing
314
+ self.sync_timer = threading.Timer(self.sync_interval, self.schedule_batch_processing)
315
+ if self.sync_timer: # This check is redundant but satisfies mypy
316
+ self.sync_timer.start()
317
+
318
+ def schedule_sanity_check(self) -> None:
319
+
320
+ if self.last_comparison_time < datetime.now() - timedelta(seconds=5):
321
+ self.compare_directories(str(self.src_dir), str(self.dest_dir), self.verbose)
322
+
323
+ self.check_timer = threading.Timer(self.check_interval, self.schedule_sanity_check)
324
+ if self.check_timer: # This check is redundant but satisfies mypy
325
+ self.check_timer.start()
326
+
327
+
328
+
329
+ def get_all_files(self, directory: str, base_dir: str) -> Dict[str, str]:
330
+ files = {}
331
+ dir_path = Path(directory)
332
+ base_path = Path(base_dir)
333
+
334
+ def traverse_dir(current_path: Path):
335
+ try:
336
+ for entry in current_path.iterdir():
337
+ if entry.is_dir():
338
+ traverse_dir(entry)
339
+ elif entry.is_file():
340
+ relative_path = str(entry.relative_to(base_path))
341
+ files[relative_path] = str(entry)
342
+ except PermissionError:
343
+ pass
344
+
345
+ traverse_dir(dir_path)
346
+ return files
347
+
348
+ def files_have_same_content(self, file1: str, file2: str) -> bool:
349
+ try:
350
+ with open(file1, 'rb') as f1, open(file2, 'rb') as f2:
351
+ return f1.read() == f2.read()
352
+ except Exception as e:
353
+ if self.verbose:
354
+ print(f"Error comparing files {file1} and {file2}: {e}")
355
+ return False
356
+
357
+ def compare_directories(self, dir1: str, dir2: str, verbose: bool = False) -> List[Dict[str, str]]:
358
+ divergent_files: List[Dict[str, str]] = []
359
+ self.last_comparison_time = datetime.now()
360
+
361
+ def log_verbose(*args):
362
+ if verbose:
363
+ print(*args)
364
+
365
+ # Get all files from both directories
366
+ dir1_files = self.get_all_files(dir1, dir1)
367
+ dir2_files = self.get_all_files(dir2, dir2)
368
+
369
+ # Compare files that exist in both directories
370
+ for relative_path, full_path1 in dir1_files.items():
371
+ full_path2 = dir2_files.get(relative_path)
372
+
373
+ if not full_path2:
374
+ # File exists in dir1 but not in dir2
375
+ divergent_files.append({'path': relative_path, 'reason': 'missing in target'})
376
+ dest = self.dest_path(full_path1)
377
+ with self.operation_queues.lock:
378
+ self.operation_queues.files_to_update[dest] = full_path1
379
+ log_verbose(f"File only in source: {relative_path}")
380
+ else:
381
+ # File exists in both, compare content
382
+ if not self.files_have_same_content(full_path1, full_path2):
383
+ divergent_files.append({'path': relative_path, 'reason': 'content differs'})
384
+ dest = self.dest_path(full_path1)
385
+ with self.operation_queues.lock:
386
+ self.operation_queues.files_to_update[dest] = full_path1
387
+ log_verbose(f"Content differs: {relative_path}")
388
+
389
+ # Check for files in dir2 that don't exist in dir1
390
+ for relative_path in dir2_files.keys():
391
+ if relative_path not in dir1_files:
392
+ divergent_files.append({'path': relative_path, 'reason': 'missing in source'})
393
+ with self.operation_queues.lock:
394
+ self.operation_queues.files_to_delete.add(str(self.src_dir / relative_path))
395
+ log_verbose(f"File only in target: {relative_path}")
396
+
397
+ return divergent_files
398
+
399
+ def watch_changes(self) -> None:
400
+ """
401
+ Set up file system monitoring and start the periodic sync process.
402
+ This method returns immediately after setting up the background watchers.
403
+ """
404
+ handler = FolderSyncHandler(str(self.src_dir), str(self.dest_dir), self.verbose, self.operation_queues)
405
+ self.observer = Observer()
406
+ if self.observer: # This check is redundant but satisfies mypy
407
+ self.observer.schedule(handler, str(self.src_dir), recursive=True)
408
+ self.observer.start()
409
+
410
+ print(f"Watching for changes in {self.src_dir}...")
411
+ print(f"Files will be synchronized every {self.sync_interval} seconds")
412
+
413
+ # Start the batch processing scheduler
414
+ self.running = True
415
+ self.schedule_batch_processing()
416
+ self.schedule_sanity_check()
417
+
418
+ def stop(self) -> None:
419
+ self.running = False
420
+ if self.sync_timer:
421
+ self.sync_timer.cancel()
422
+ self.sync_timer.join()
423
+ self.sync_timer = None
424
+ if self.check_timer:
425
+ self.check_timer.cancel()
426
+ self.check_timer.join()
427
+ self.check_timer = None
428
+ if self.observer:
429
+ self.observer.stop()
430
+ self.observer.join()
431
+