flow-frame-core 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/README.md +64 -0
- package/dist/Dockerfile +86 -0
- package/dist/GPU_DEPLOYMENT_README.md +324 -0
- package/dist/OPS_AGENT_README.md +174 -0
- package/dist/README-H100-VM.md +192 -0
- package/dist/README-worker-pools.md +231 -0
- package/dist/README.md +8 -0
- package/dist/WEB-ELEMENT-REQUESTS-README.md +302 -0
- package/dist/append.d.ts +3 -0
- package/dist/append.d.ts.map +1 -0
- package/dist/append.js +42 -0
- package/dist/append.js.map +1 -0
- package/dist/audioRoutes.d.ts +2 -0
- package/dist/audioRoutes.d.ts.map +1 -0
- package/dist/audioRoutes.js +97 -0
- package/dist/audioRoutes.js.map +1 -0
- package/dist/augment-parallel.d.ts +6 -0
- package/dist/augment-parallel.d.ts.map +1 -0
- package/dist/augment-parallel.js +128 -0
- package/dist/augment-parallel.js.map +1 -0
- package/dist/augment-worker.d.ts +2 -0
- package/dist/augment-worker.d.ts.map +1 -0
- package/dist/augment-worker.js +100 -0
- package/dist/augment-worker.js.map +1 -0
- package/dist/browerRoutes.d.ts +2 -0
- package/dist/browerRoutes.d.ts.map +1 -0
- package/dist/browerRoutes.js +323 -0
- package/dist/browerRoutes.js.map +1 -0
- package/dist/browser-utils/utils.d.ts +6 -0
- package/dist/browser-utils/utils.d.ts.map +1 -0
- package/dist/browser-utils/utils.js +133 -0
- package/dist/browser-utils/utils.js.map +1 -0
- package/dist/capture_training_data_endpoints.d.ts +158 -0
- package/dist/capture_training_data_endpoints.d.ts.map +1 -0
- package/dist/capture_training_data_endpoints.js +1812 -0
- package/dist/capture_training_data_endpoints.js.map +1 -0
- package/dist/config.json +28 -0
- package/dist/configEndpoints.d.ts +2 -0
- package/dist/configEndpoints.d.ts.map +1 -0
- package/dist/configEndpoints.js +459 -0
- package/dist/configEndpoints.js.map +1 -0
- package/dist/constants.d.ts +109 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +110 -0
- package/dist/constants.js.map +1 -0
- package/dist/docs/workflow_nodes.md +257 -0
- package/dist/download.d.ts +11 -0
- package/dist/download.d.ts.map +1 -0
- package/dist/download.js +31 -0
- package/dist/download.js.map +1 -0
- package/dist/download.py +61 -0
- package/dist/ecosystem.config.json +63 -0
- package/dist/email-body-extractor.d.ts +20 -0
- package/dist/email-body-extractor.d.ts.map +1 -0
- package/dist/email-body-extractor.js +103 -0
- package/dist/email-body-extractor.js.map +1 -0
- package/dist/express_util.d.ts +2 -0
- package/dist/express_util.d.ts.map +1 -0
- package/dist/express_util.js +30 -0
- package/dist/express_util.js.map +1 -0
- package/dist/extension/background.d.ts +2 -0
- package/dist/extension/background.d.ts.map +1 -0
- package/dist/extension/background.js +268 -0
- package/dist/extension/background.js.map +1 -0
- package/dist/extension/manifest.json +19 -0
- package/dist/extensionUtils.d.ts +2 -0
- package/dist/extensionUtils.d.ts.map +1 -0
- package/dist/extensionUtils.js +48 -0
- package/dist/extensionUtils.js.map +1 -0
- package/dist/filter-gmail-poller/README.md +320 -0
- package/dist/filter-gmail-poller/demo.d.ts +2 -0
- package/dist/filter-gmail-poller/demo.d.ts.map +1 -0
- package/dist/filter-gmail-poller/demo.js +79 -0
- package/dist/filter-gmail-poller/demo.js.map +1 -0
- package/dist/filter-gmail-poller/example-existing-app.d.ts +2 -0
- package/dist/filter-gmail-poller/example-existing-app.d.ts.map +1 -0
- package/dist/filter-gmail-poller/example-existing-app.js +72 -0
- package/dist/filter-gmail-poller/example-existing-app.js.map +1 -0
- package/dist/filter-gmail-poller/filter-gmail-poller.d.ts +160 -0
- package/dist/filter-gmail-poller/filter-gmail-poller.d.ts.map +1 -0
- package/dist/filter-gmail-poller/filter-gmail-poller.js +1048 -0
- package/dist/filter-gmail-poller/filter-gmail-poller.js.map +1 -0
- package/dist/filter-gmail-poller/index.d.ts +3 -0
- package/dist/filter-gmail-poller/index.d.ts.map +1 -0
- package/dist/filter-gmail-poller/index.js +18 -0
- package/dist/filter-gmail-poller/index.js.map +1 -0
- package/dist/filter-gmail-poller/manual-test.d.ts +2 -0
- package/dist/filter-gmail-poller/manual-test.d.ts.map +1 -0
- package/dist/filter-gmail-poller/manual-test.js +70 -0
- package/dist/filter-gmail-poller/manual-test.js.map +1 -0
- package/dist/filter-gmail-poller/poller-prompts.d.ts +12 -0
- package/dist/filter-gmail-poller/poller-prompts.d.ts.map +1 -0
- package/dist/filter-gmail-poller/poller-prompts.js +330 -0
- package/dist/filter-gmail-poller/poller-prompts.js.map +1 -0
- package/dist/filter-gmail-poller/test.js +69 -0
- package/dist/flowframe-auto-firebase-adminsdk.json +13 -0
- package/dist/gmail-poller/README-microsoft-email-poller.md +203 -0
- package/dist/gmail-poller/README.md +129 -0
- package/dist/gmail-poller/example.d.ts +5 -0
- package/dist/gmail-poller/example.d.ts.map +1 -0
- package/dist/gmail-poller/example.js +83 -0
- package/dist/gmail-poller/example.js.map +1 -0
- package/dist/gmail-poller/gmail-poller.d.ts +82 -0
- package/dist/gmail-poller/gmail-poller.d.ts.map +1 -0
- package/dist/gmail-poller/gmail-poller.js +455 -0
- package/dist/gmail-poller/gmail-poller.js.map +1 -0
- package/dist/gmail-poller/manual-test.d.ts +2 -0
- package/dist/gmail-poller/manual-test.d.ts.map +1 -0
- package/dist/gmail-poller/manual-test.js +37 -0
- package/dist/gmail-poller/manual-test.js.map +1 -0
- package/dist/gmail-poller/microsoft-email-example.d.ts +8 -0
- package/dist/gmail-poller/microsoft-email-example.d.ts.map +1 -0
- package/dist/gmail-poller/microsoft-email-example.js +58 -0
- package/dist/gmail-poller/microsoft-email-example.js.map +1 -0
- package/dist/gmail-poller/microsoft-email-poller.d.ts +73 -0
- package/dist/gmail-poller/microsoft-email-poller.d.ts.map +1 -0
- package/dist/gmail-poller/microsoft-email-poller.js +346 -0
- package/dist/gmail-poller/microsoft-email-poller.js.map +1 -0
- package/dist/gmail-poller/setup-auth.d.ts +3 -0
- package/dist/gmail-poller/setup-auth.d.ts.map +1 -0
- package/dist/gmail-poller/setup-auth.js +36 -0
- package/dist/gmail-poller/setup-auth.js.map +1 -0
- package/dist/gmail-poller/test.js +36 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/inference/augment_levels.d.ts +2 -0
- package/dist/inference/augment_levels.d.ts.map +1 -0
- package/dist/inference/augment_levels.js +1 -0
- package/dist/inference/augment_levels.js.map +1 -0
- package/dist/inference/capture-overlay.d.ts +13 -0
- package/dist/inference/capture-overlay.d.ts.map +1 -0
- package/dist/inference/capture-overlay.js +355 -0
- package/dist/inference/capture-overlay.js.map +1 -0
- package/dist/inference/capturescreenshot.d.ts +12 -0
- package/dist/inference/capturescreenshot.d.ts.map +1 -0
- package/dist/inference/capturescreenshot.js +157 -0
- package/dist/inference/capturescreenshot.js.map +1 -0
- package/dist/jsonHandler.d.ts +37 -0
- package/dist/jsonHandler.d.ts.map +1 -0
- package/dist/jsonHandler.js +191 -0
- package/dist/jsonHandler.js.map +1 -0
- package/dist/localStorage.json +11 -0
- package/dist/media_data_endpoints.d.ts +2 -0
- package/dist/media_data_endpoints.d.ts.map +1 -0
- package/dist/media_data_endpoints.js +102 -0
- package/dist/media_data_endpoints.js.map +1 -0
- package/dist/operations/blender-ops.d.ts +4 -0
- package/dist/operations/blender-ops.d.ts.map +1 -0
- package/dist/operations/blender-ops.js +55 -0
- package/dist/operations/blender-ops.js.map +1 -0
- package/dist/operations.d.ts +34 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +1514 -0
- package/dist/operations.js.map +1 -0
- package/dist/pdfRoutes.d.ts +2 -0
- package/dist/pdfRoutes.d.ts.map +1 -0
- package/dist/pdfRoutes.js +56 -0
- package/dist/pdfRoutes.js.map +1 -0
- package/dist/peers.d.ts +9 -0
- package/dist/peers.d.ts.map +1 -0
- package/dist/peers.js +70 -0
- package/dist/peers.js.map +1 -0
- package/dist/playparser.d.ts +2 -0
- package/dist/playparser.d.ts.map +1 -0
- package/dist/playparser.js +281 -0
- package/dist/playparser.js.map +1 -0
- package/dist/process.d.ts +4 -0
- package/dist/process.d.ts.map +1 -0
- package/dist/process.js +375 -0
- package/dist/process.js.map +1 -0
- package/dist/promptRoutes.d.ts +7 -0
- package/dist/promptRoutes.d.ts.map +1 -0
- package/dist/promptRoutes.js +68 -0
- package/dist/promptRoutes.js.map +1 -0
- package/dist/queueManager.d.ts +23 -0
- package/dist/queueManager.d.ts.map +1 -0
- package/dist/queueManager.js +96 -0
- package/dist/queueManager.js.map +1 -0
- package/dist/run-flow.d.ts +8 -0
- package/dist/run-flow.d.ts.map +1 -0
- package/dist/run-flow.js +220 -0
- package/dist/run-flow.js.map +1 -0
- package/dist/scraper.d.ts +2 -0
- package/dist/scraper.d.ts.map +1 -0
- package/dist/scraper.js +75 -0
- package/dist/scraper.js.map +1 -0
- package/dist/scraper_endpoints.d.ts +2 -0
- package/dist/scraper_endpoints.d.ts.map +1 -0
- package/dist/scraper_endpoints.js +40 -0
- package/dist/scraper_endpoints.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +528 -0
- package/dist/server.js.map +1 -0
- package/dist/services/ModelContext.d.ts +7 -0
- package/dist/services/ModelContext.d.ts.map +1 -0
- package/dist/services/ModelContext.js +7 -0
- package/dist/services/ModelContext.js.map +1 -0
- package/dist/services/agenticUiPlanner.d.ts +27 -0
- package/dist/services/agenticUiPlanner.d.ts.map +1 -0
- package/dist/services/agenticUiPlanner.js +161 -0
- package/dist/services/agenticUiPlanner.js.map +1 -0
- package/dist/services/apiKeyService.d.ts +3 -0
- package/dist/services/apiKeyService.d.ts.map +1 -0
- package/dist/services/apiKeyService.js +7 -0
- package/dist/services/apiKeyService.js.map +1 -0
- package/dist/services/audioService.d.ts +10 -0
- package/dist/services/audioService.d.ts.map +1 -0
- package/dist/services/audioService.js +140 -0
- package/dist/services/audioService.js.map +1 -0
- package/dist/services/autoPromptOptimizer.d.ts +44 -0
- package/dist/services/autoPromptOptimizer.d.ts.map +1 -0
- package/dist/services/autoPromptOptimizer.js +344 -0
- package/dist/services/autoPromptOptimizer.js.map +1 -0
- package/dist/services/autoPromptOptimizer.manual-test.d.ts +2 -0
- package/dist/services/autoPromptOptimizer.manual-test.d.ts.map +1 -0
- package/dist/services/autoPromptOptimizer.manual-test.js +27 -0
- package/dist/services/autoPromptOptimizer.manual-test.js.map +1 -0
- package/dist/services/chainExecutor.d.ts +26 -0
- package/dist/services/chainExecutor.d.ts.map +1 -0
- package/dist/services/chainExecutor.js +399 -0
- package/dist/services/chainExecutor.js.map +1 -0
- package/dist/services/classifyImageQuestion.d.ts +55 -0
- package/dist/services/classifyImageQuestion.d.ts.map +1 -0
- package/dist/services/classifyImageQuestion.js +428 -0
- package/dist/services/classifyImageQuestion.js.map +1 -0
- package/dist/services/configuration/executor.d.ts +3 -0
- package/dist/services/configuration/executor.d.ts.map +1 -0
- package/dist/services/configuration/executor.js +795 -0
- package/dist/services/configuration/executor.js.map +1 -0
- package/dist/services/error.d.ts +13 -0
- package/dist/services/error.d.ts.map +1 -0
- package/dist/services/error.js +34 -0
- package/dist/services/error.js.map +1 -0
- package/dist/services/executor.d.ts +11 -0
- package/dist/services/executor.d.ts.map +1 -0
- package/dist/services/executor.js +1587 -0
- package/dist/services/executor.js.map +1 -0
- package/dist/services/extractPdf.d.ts +26 -0
- package/dist/services/extractPdf.d.ts.map +1 -0
- package/dist/services/extractPdf.js +256 -0
- package/dist/services/extractPdf.js.map +1 -0
- package/dist/services/generateJsTransformFromPrompt.d.ts +11 -0
- package/dist/services/generateJsTransformFromPrompt.d.ts.map +1 -0
- package/dist/services/generateJsTransformFromPrompt.js +328 -0
- package/dist/services/generateJsTransformFromPrompt.js.map +1 -0
- package/dist/services/localizeFirebaseMedia.d.ts +20 -0
- package/dist/services/localizeFirebaseMedia.d.ts.map +1 -0
- package/dist/services/localizeFirebaseMedia.js +135 -0
- package/dist/services/localizeFirebaseMedia.js.map +1 -0
- package/dist/services/polyfill_canvas.d.ts +2 -0
- package/dist/services/polyfill_canvas.d.ts.map +1 -0
- package/dist/services/polyfill_canvas.js +19 -0
- package/dist/services/polyfill_canvas.js.map +1 -0
- package/dist/services/promptRoutes.d.ts +7 -0
- package/dist/services/promptRoutes.d.ts.map +1 -0
- package/dist/services/promptRoutes.js +70 -0
- package/dist/services/promptRoutes.js.map +1 -0
- package/dist/services/runPrompt.d.ts +29 -0
- package/dist/services/runPrompt.d.ts.map +1 -0
- package/dist/services/runPrompt.js +232 -0
- package/dist/services/runPrompt.js.map +1 -0
- package/dist/services/schemaInference.d.ts +2 -0
- package/dist/services/schemaInference.d.ts.map +1 -0
- package/dist/services/schemaInference.js +17 -0
- package/dist/services/schemaInference.js.map +1 -0
- package/dist/services/self-learning/api.d.ts +2 -0
- package/dist/services/self-learning/api.d.ts.map +1 -0
- package/dist/services/self-learning/api.js +84 -0
- package/dist/services/self-learning/api.js.map +1 -0
- package/dist/services/self-learning/autolearn.d.ts +23 -0
- package/dist/services/self-learning/autolearn.d.ts.map +1 -0
- package/dist/services/self-learning/autolearn.js +308 -0
- package/dist/services/self-learning/autolearn.js.map +1 -0
- package/dist/services/self-learning/discover.d.ts +11 -0
- package/dist/services/self-learning/discover.d.ts.map +1 -0
- package/dist/services/self-learning/discover.js +446 -0
- package/dist/services/self-learning/discover.js.map +1 -0
- package/dist/services/self-learning/image.d.ts +10 -0
- package/dist/services/self-learning/image.d.ts.map +1 -0
- package/dist/services/self-learning/image.js +38 -0
- package/dist/services/self-learning/image.js.map +1 -0
- package/dist/services/self-learning/injest.d.ts +25 -0
- package/dist/services/self-learning/injest.d.ts.map +1 -0
- package/dist/services/self-learning/injest.js +110 -0
- package/dist/services/self-learning/injest.js.map +1 -0
- package/dist/services/self-learning/learn.d.ts +2 -0
- package/dist/services/self-learning/learn.d.ts.map +1 -0
- package/dist/services/self-learning/learn.js +145 -0
- package/dist/services/self-learning/learn.js.map +1 -0
- package/dist/services/self-learning/matcher.d.ts +2 -0
- package/dist/services/self-learning/matcher.d.ts.map +1 -0
- package/dist/services/self-learning/matcher.js +38 -0
- package/dist/services/self-learning/matcher.js.map +1 -0
- package/dist/services/self-learning/openai.d.ts +8 -0
- package/dist/services/self-learning/openai.d.ts.map +1 -0
- package/dist/services/self-learning/openai.js +97 -0
- package/dist/services/self-learning/openai.js.map +1 -0
- package/dist/services/self-learning/phash.d.ts +5 -0
- package/dist/services/self-learning/phash.d.ts.map +1 -0
- package/dist/services/self-learning/phash.js +68 -0
- package/dist/services/self-learning/phash.js.map +1 -0
- package/dist/services/self-learning/recognize.d.ts +17 -0
- package/dist/services/self-learning/recognize.d.ts.map +1 -0
- package/dist/services/self-learning/recognize.js +116 -0
- package/dist/services/self-learning/recognize.js.map +1 -0
- package/dist/services/self-learning/record_transition.d.ts +8 -0
- package/dist/services/self-learning/record_transition.d.ts.map +1 -0
- package/dist/services/self-learning/record_transition.js +20 -0
- package/dist/services/self-learning/record_transition.js.map +1 -0
- package/dist/services/self-learning/registry.d.ts +4 -0
- package/dist/services/self-learning/registry.d.ts.map +1 -0
- package/dist/services/self-learning/registry.js +19 -0
- package/dist/services/self-learning/registry.js.map +1 -0
- package/dist/services/self-learning/schema.d.ts +114 -0
- package/dist/services/self-learning/schema.d.ts.map +1 -0
- package/dist/services/self-learning/schema.js +70 -0
- package/dist/services/self-learning/schema.js.map +1 -0
- package/dist/services/self-learning/schemaStrictify.d.ts +2 -0
- package/dist/services/self-learning/schemaStrictify.d.ts.map +1 -0
- package/dist/services/self-learning/schemaStrictify.js +34 -0
- package/dist/services/self-learning/schemaStrictify.js.map +1 -0
- package/dist/services/self-learning/transition_graph.d.ts +6 -0
- package/dist/services/self-learning/transition_graph.d.ts.map +1 -0
- package/dist/services/self-learning/transition_graph.js +83 -0
- package/dist/services/self-learning/transition_graph.js.map +1 -0
- package/dist/services/self-learning/transition_log.d.ts +3 -0
- package/dist/services/self-learning/transition_log.d.ts.map +1 -0
- package/dist/services/self-learning/transition_log.js +42 -0
- package/dist/services/self-learning/transition_log.js.map +1 -0
- package/dist/services/self-learning/util.d.ts +3 -0
- package/dist/services/self-learning/util.d.ts.map +1 -0
- package/dist/services/self-learning/util.js +11 -0
- package/dist/services/self-learning/util.js.map +1 -0
- package/dist/services/stepByStepAiPlanner.d.ts +39 -0
- package/dist/services/stepByStepAiPlanner.d.ts.map +1 -0
- package/dist/services/stepByStepAiPlanner.js +379 -0
- package/dist/services/stepByStepAiPlanner.js.map +1 -0
- package/dist/services/test-genjs.js +39 -0
- package/dist/services/test-genjs.manual-test.d.ts +2 -0
- package/dist/services/test-genjs.manual-test.d.ts.map +1 -0
- package/dist/services/test-genjs.manual-test.js +40 -0
- package/dist/services/test-genjs.manual-test.js.map +1 -0
- package/dist/services/uiMapPathFinder.d.ts +13 -0
- package/dist/services/uiMapPathFinder.d.ts.map +1 -0
- package/dist/services/uiMapPathFinder.js +79 -0
- package/dist/services/uiMapPathFinder.js.map +1 -0
- package/dist/services/uiMapService.d.ts +26 -0
- package/dist/services/uiMapService.d.ts.map +1 -0
- package/dist/services/uiMapService.js +275 -0
- package/dist/services/uiMapService.js.map +1 -0
- package/dist/services/uiPlanner.d.ts +54 -0
- package/dist/services/uiPlanner.d.ts.map +1 -0
- package/dist/services/uiPlanner.js +558 -0
- package/dist/services/uiPlanner.js.map +1 -0
- package/dist/services/utilityFunctions.d.ts +80 -0
- package/dist/services/utilityFunctions.d.ts.map +1 -0
- package/dist/services/utilityFunctions.js +352 -0
- package/dist/services/utilityFunctions.js.map +1 -0
- package/dist/services/variableGenerator.d.ts +39 -0
- package/dist/services/variableGenerator.d.ts.map +1 -0
- package/dist/services/variableGenerator.js +157 -0
- package/dist/services/variableGenerator.js.map +1 -0
- package/dist/services/workflow/build-workflow.d.ts +49 -0
- package/dist/services/workflow/build-workflow.d.ts.map +1 -0
- package/dist/services/workflow/build-workflow.js +119 -0
- package/dist/services/workflow/build-workflow.js.map +1 -0
- package/dist/standardRoutes.d.ts +2 -0
- package/dist/standardRoutes.d.ts.map +1 -0
- package/dist/standardRoutes.js +1495 -0
- package/dist/standardRoutes.js.map +1 -0
- package/dist/stepWorkflowRoutes.d.ts +2 -0
- package/dist/stepWorkflowRoutes.d.ts.map +1 -0
- package/dist/stepWorkflowRoutes.js +1007 -0
- package/dist/stepWorkflowRoutes.js.map +1 -0
- package/dist/storage.d.ts +19 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.docker.json +61 -0
- package/dist/storage.js +131 -0
- package/dist/storage.js.map +1 -0
- package/dist/storage.json +78 -0
- package/dist/storage_cache/boxes.json +48 -0
- package/dist/storage_cache/suno_state.json +3 -0
- package/dist/suno_download.d.ts +11 -0
- package/dist/suno_download.d.ts.map +1 -0
- package/dist/suno_download.js +33 -0
- package/dist/suno_download.js.map +1 -0
- package/dist/suno_download.py +119 -0
- package/dist/test-web-element-requests.d.ts +6 -0
- package/dist/test-web-element-requests.d.ts.map +1 -0
- package/dist/test-web-element-requests.js +114 -0
- package/dist/test-web-element-requests.js.map +1 -0
- package/dist/test_pdf_render.d.ts +2 -0
- package/dist/test_pdf_render.d.ts.map +1 -0
- package/dist/test_pdf_render.js +50 -0
- package/dist/test_pdf_render.js.map +1 -0
- package/dist/training_data_viewer_endpoints.d.ts +2 -0
- package/dist/training_data_viewer_endpoints.d.ts.map +1 -0
- package/dist/training_data_viewer_endpoints.js +141 -0
- package/dist/training_data_viewer_endpoints.js.map +1 -0
- package/dist/utils.d.ts +353 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +1517 -0
- package/dist/utils.js.map +1 -0
- package/dist/vm-h100.env.template +55 -0
- package/dist/web-element-requests.d.ts +102 -0
- package/dist/web-element-requests.d.ts.map +1 -0
- package/dist/web-element-requests.js +278 -0
- package/dist/web-element-requests.js.map +1 -0
- package/dist/workflowRoutes.d.ts +2 -0
- package/dist/workflowRoutes.d.ts.map +1 -0
- package/dist/workflowRoutes.js +441 -0
- package/dist/workflowRoutes.js.map +1 -0
- package/package.json +109 -0
package/dist/utils.js
ADDED
|
@@ -0,0 +1,1517 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { windowManager } from 'node-window-manager';
|
|
3
|
+
import http from 'http';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import os from 'os';
|
|
6
|
+
import extract from 'extract-zip';
|
|
7
|
+
import https from 'https';
|
|
8
|
+
import { URL } from 'url';
|
|
9
|
+
import { imageSize } from 'image-size';
|
|
10
|
+
import progressStream from 'progress-stream';
|
|
11
|
+
import ffmpeg from 'fluent-ffmpeg';
|
|
12
|
+
import robot from 'robotjs';
|
|
13
|
+
import { getItem, isDocker, setItem } from './storage.js';
|
|
14
|
+
import { KEYS, MODES } from './constants.js';
|
|
15
|
+
import archiver from 'archiver';
|
|
16
|
+
import { execSync } from 'child_process';
|
|
17
|
+
const console_context = {};
|
|
18
|
+
export function console_log_it(text, cat) {
|
|
19
|
+
console_context[cat || 'general'] = text;
|
|
20
|
+
if (true) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (isDocker()) {
|
|
24
|
+
console.log(`${cat}: ${text}`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
console.clear();
|
|
28
|
+
Object.entries(console_context).map(([key, value]) => {
|
|
29
|
+
if (console_context[`mode-filter`]) {
|
|
30
|
+
if (key.includes(console_context[`mode-filter`])) {
|
|
31
|
+
if (typeof value === 'object') {
|
|
32
|
+
return console.log(`${key}: ${JSON.stringify(value, null, 3)}`);
|
|
33
|
+
}
|
|
34
|
+
return console.log(`${key}: ${value}`);
|
|
35
|
+
}
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
return console.log(`${key}: ${value}`);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export function getConsoleContext() {
|
|
42
|
+
return console_context;
|
|
43
|
+
}
|
|
44
|
+
const input_bar = '[id="desktop_input_bar"]';
|
|
45
|
+
const MIDJOURNEY_SELECTOR = {
|
|
46
|
+
queued: 'span||Queued',
|
|
47
|
+
[input_bar]: input_bar,
|
|
48
|
+
addImageBtn: '[id="AddImageIcon"]',
|
|
49
|
+
promptImage: '[src*="https://cdn.midjourney.com/u/',
|
|
50
|
+
chooseAnImage: 'button span||Choose a file',
|
|
51
|
+
starting: 'div[id="pageScroll"] span.relative||Starting...',
|
|
52
|
+
completing: 'div[id="pageScroll"] span.relative||!Complete',
|
|
53
|
+
firstImage: 'div[id="pageScroll"] a',
|
|
54
|
+
buttonSearchBtn: 'div button||Search',
|
|
55
|
+
searchInput: '[id="search-input"]',
|
|
56
|
+
downloadButton: 'button[title="Download Video for Social Media"] svg',
|
|
57
|
+
lightboxPrompt: '[id="lightboxPrompt"] p',
|
|
58
|
+
firstImageCell: '[id="pageScroll"] div div:first-child div.relative.flex-center',
|
|
59
|
+
ilikeRadiobutton: 'button span||I like this video',
|
|
60
|
+
iNeutralRadiobutton: `button span||I'm neutral on this video`,
|
|
61
|
+
buttonAnimate: 'button||Animate',
|
|
62
|
+
videoButton: 'div a video',
|
|
63
|
+
videoRatio: 'button[title="Image aspect ratio"] span||ar 91:51',
|
|
64
|
+
videoRatio2: 'button[title="Image aspect ratio"] span||ar 143:256',
|
|
65
|
+
videoRatio3: 'button[title="Image aspect ratio"] span||ar 51:91',
|
|
66
|
+
videoRatio4: 'button[title="Image aspect ratio"] span||ar 77:58',
|
|
67
|
+
videoRatio5: 'button[title="Image aspect ratio"] span||ar 3:2',
|
|
68
|
+
videoRatio6: 'button[title="Image aspect ratio"] span||ar 2:3',
|
|
69
|
+
optionsButton: 'button[title="Options"]',
|
|
70
|
+
optionsDownloadButton: 'button div span||Download for Social',
|
|
71
|
+
useText: 'h2||Use'
|
|
72
|
+
};
|
|
73
|
+
export function processSunoState(suno_state, project_song_states, project, all_project_songs_ready, song_states) {
|
|
74
|
+
suno_state = get(KEYS.suno_state) || {};
|
|
75
|
+
const sunoStateValues = Object.values(suno_state);
|
|
76
|
+
project_song_states = sunoStateValues.filter(x => x.name === project.name);
|
|
77
|
+
let unready_songs = project_song_states.filter(x => !x.ready || !x.url || !x.lyric);
|
|
78
|
+
all_project_songs_ready = !project_song_states.some(x => !x.ready || !x.url || !x.lyric);
|
|
79
|
+
console_log_it(`unready songs ${all_project_songs_ready} ${project_song_states.filter(x => !x.ready || !x.url || !x.lyric).length}`, 'unready-songs');
|
|
80
|
+
song_states = project_song_states.filter(x => x.name === project.name && x.url && x.lyric);
|
|
81
|
+
project.lyrics = project.lyrics || [];
|
|
82
|
+
project.lyrics = unique([...project.lyrics, ...song_states.map(v => v.lyric)], v => v.songId);
|
|
83
|
+
project.song_sources = unique([...project.song_sources, ...song_states.map(v => v.url)], v => v);
|
|
84
|
+
console_log_it(`${project.song_sources.length}`, 'soung_source_count');
|
|
85
|
+
console_log_it(`${song_states.length}/${project_song_states.length}`, 'song_progress');
|
|
86
|
+
console_log_it(buildProgressBar(song_states.length, project_song_states.length), 'song_progress_bar');
|
|
87
|
+
let skip = false;
|
|
88
|
+
if (unready_songs.length > 3) {
|
|
89
|
+
skip = true;
|
|
90
|
+
// Do something if there are more than 3 unready songs
|
|
91
|
+
console_log_it(`There are more than 3 unready songs: ${unready_songs.length}`, 'unready-songs');
|
|
92
|
+
}
|
|
93
|
+
return { suno_state, project_song_states, all_project_songs_ready, song_states, skip };
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Detect whether an image is portrait, landscape, or square.
|
|
97
|
+
*
|
|
98
|
+
* @param {string|Buffer} input – either a path to the image file, or a Buffer
|
|
99
|
+
* @returns {'portrait'|'landscape'|'square'}
|
|
100
|
+
*/
|
|
101
|
+
const doCache = {};
|
|
102
|
+
function getDetectOrientation({ width, height }) {
|
|
103
|
+
if (width > height) {
|
|
104
|
+
return 'landscape';
|
|
105
|
+
}
|
|
106
|
+
if (height > width) {
|
|
107
|
+
return 'portrait';
|
|
108
|
+
}
|
|
109
|
+
return 'square';
|
|
110
|
+
}
|
|
111
|
+
function detectOrientation(input) {
|
|
112
|
+
// image-size will accept a file path or a Buffer
|
|
113
|
+
// image-size will accept a file path or a Buffer
|
|
114
|
+
if (doCache[input]) {
|
|
115
|
+
return doCache[input];
|
|
116
|
+
}
|
|
117
|
+
const { width, height } = imageSize(fs.readFileSync(input));
|
|
118
|
+
if (width > height) {
|
|
119
|
+
console.log('landscape');
|
|
120
|
+
doCache[input] = 'landscape';
|
|
121
|
+
return doCache[input];
|
|
122
|
+
}
|
|
123
|
+
if (height > width) {
|
|
124
|
+
console.log('portrait');
|
|
125
|
+
doCache[input] = 'portrait';
|
|
126
|
+
return doCache[input];
|
|
127
|
+
}
|
|
128
|
+
console.log('square');
|
|
129
|
+
doCache[input] = 'square';
|
|
130
|
+
return doCache[input];
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Returns one or more random element(s) from the given array.
|
|
134
|
+
* @param {any[]} arr — the array to pick from
|
|
135
|
+
* @param {number} [count=1] — how many random elements to select
|
|
136
|
+
* @returns {any|any[]|undefined} a single element if count is 1 (or undefined if empty),
|
|
137
|
+
* otherwise an array of up to count elements
|
|
138
|
+
*/
|
|
139
|
+
function selectRandom(arr, count = 1) {
|
|
140
|
+
if (!Array.isArray(arr)) {
|
|
141
|
+
throw new TypeError('selectRandom expects an array');
|
|
142
|
+
}
|
|
143
|
+
if (!Number.isInteger(count) || count < 1) {
|
|
144
|
+
throw new TypeError('selectRandom expects count as a positive integer');
|
|
145
|
+
}
|
|
146
|
+
const len = arr.length;
|
|
147
|
+
if (len === 0) {
|
|
148
|
+
// empty array: undefined for single, empty array for multiple
|
|
149
|
+
return count === 1 ? undefined : [];
|
|
150
|
+
}
|
|
151
|
+
if (count === 1) {
|
|
152
|
+
// single pick
|
|
153
|
+
const idx = Math.floor(Math.random() * len);
|
|
154
|
+
return [arr[idx]];
|
|
155
|
+
}
|
|
156
|
+
// multiple picks — shuffle a copy of the array
|
|
157
|
+
const shuffled = arr.slice();
|
|
158
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
159
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
160
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
161
|
+
}
|
|
162
|
+
// return up to count items
|
|
163
|
+
return shuffled.slice(0, Math.min(count, len));
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Node.js script to extract all zip files from a source folder to a destination folder.
|
|
167
|
+
*
|
|
168
|
+
* Usage: node unzip-all.js <source_folder> <destination_folder>
|
|
169
|
+
* If not provided, defaults are:
|
|
170
|
+
* source_folder: './zips'
|
|
171
|
+
* destination_folder: './unzipped'
|
|
172
|
+
*
|
|
173
|
+
* Required dependency: extract-zip (install via: npm install extract-zip)
|
|
174
|
+
*/
|
|
175
|
+
async function unzip(sourceFolder, destinationFolder, subfolder = '') {
|
|
176
|
+
// const sourceFolder = process.argv[2] || 'F:\\ai\\historical figures\\zips';
|
|
177
|
+
// const destinationFolder = process.argv[3] || 'F:\\ai\\historical figures\\images';
|
|
178
|
+
// const subfolder = process.argv[4] || '';
|
|
179
|
+
// Read all files from the source folder
|
|
180
|
+
console.log(`unziping....`);
|
|
181
|
+
console.log(`sourceFolder: ${sourceFolder}`);
|
|
182
|
+
console.log(`destinationFolder: ${destinationFolder}`);
|
|
183
|
+
console.log(`subfolder: ${subfolder}`);
|
|
184
|
+
let files = fs.readdirSync(sourceFolder);
|
|
185
|
+
// Filter for .zip files
|
|
186
|
+
const zipFiles = files.filter(file => path.extname(file).toLowerCase() === '.zip' && file.startsWith("midjourney_session_"));
|
|
187
|
+
if (zipFiles.length === 0) {
|
|
188
|
+
console.log('No zip files found in', sourceFolder);
|
|
189
|
+
return Promise.resolve([]);
|
|
190
|
+
}
|
|
191
|
+
// Process each zip file
|
|
192
|
+
let result = Promise.resolve();
|
|
193
|
+
(zipFiles.map(file => {
|
|
194
|
+
result = result.then(() => {
|
|
195
|
+
const zipPath = path.join(sourceFolder, file);
|
|
196
|
+
// Create a destination folder for the current zip file (based on its name)
|
|
197
|
+
const extractTo = path.join(destinationFolder);
|
|
198
|
+
console.log(`Extracting to: ${extractTo}`);
|
|
199
|
+
// Ensure the destination subfolder exists
|
|
200
|
+
fs.mkdirSync(extractTo, { recursive: true });
|
|
201
|
+
// Extract the zip file
|
|
202
|
+
return extract(zipPath, { dir: extractTo })
|
|
203
|
+
.then(() => {
|
|
204
|
+
console.log(`Extracted ${file} to ${extractTo}`);
|
|
205
|
+
fs.unlinkSync(zipPath);
|
|
206
|
+
})
|
|
207
|
+
.catch(err => {
|
|
208
|
+
console.error(`Error extracting ${file}:`, err);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}));
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
async function unzipFile(zipPath, extractTo) {
|
|
215
|
+
await ensureDir(extractTo);
|
|
216
|
+
return extract(zipPath, { dir: extractTo })
|
|
217
|
+
.then(() => {
|
|
218
|
+
console.log(`Extracted ${zipPath} to ${extractTo}`);
|
|
219
|
+
fs.unlinkSync(zipPath);
|
|
220
|
+
})
|
|
221
|
+
.catch(err => {
|
|
222
|
+
console.error(`Error extracting ${zipPath}:`, err);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Remove characters not allowed in filenames and replace whitespace with underscores.
|
|
227
|
+
* @param {string} name
|
|
228
|
+
* @returns {string}
|
|
229
|
+
*/
|
|
230
|
+
function sanitizeFileName(name) {
|
|
231
|
+
// remove reserved Windows filename characters: < > : " / \ | ? *
|
|
232
|
+
const illegalRe = /[<>:,.\'"\/\\|?*]/g;
|
|
233
|
+
// collapse one or more whitespace characters into a single underscore
|
|
234
|
+
const whitespaceRe = /\s+/g;
|
|
235
|
+
return name
|
|
236
|
+
.replace(illegalRe, '')
|
|
237
|
+
.replace(whitespaceRe, '_');
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Finds the screen position and size (bounds) of an Explorer window
|
|
241
|
+
* that appears to be displaying a specific folder address.
|
|
242
|
+
*
|
|
243
|
+
* @param {string} folderAddress - The full path of the folder to find (e.g., "C:\\Users\\YourUser\\Documents").
|
|
244
|
+
* @returns {Object|null} The window bounds { x, y, width, height } if found, or null if not.
|
|
245
|
+
*/
|
|
246
|
+
function findExplorerWindowPosition(folderName) {
|
|
247
|
+
// Extract the folder name from the provided address.
|
|
248
|
+
// Retrieve all open windows.
|
|
249
|
+
const windows = windowManager.getWindows();
|
|
250
|
+
for (const win of windows) {
|
|
251
|
+
// Check if the window belongs to explorer.exe.
|
|
252
|
+
const procPath = win.process?.path;
|
|
253
|
+
const title = win.getTitle().toLowerCase();
|
|
254
|
+
if (title === folderName) {
|
|
255
|
+
console.log(`title: ${title}`);
|
|
256
|
+
console.log(`procPath: ${procPath}`);
|
|
257
|
+
return (win.getBounds());
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// If no matching Explorer window is found, return null.
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
async function locateExtensionIcon(filePath) {
|
|
264
|
+
// const filePath = path.resolve(__dirname, './chromeExtensionIconImage.png');
|
|
265
|
+
const fileName = path.basename(filePath);
|
|
266
|
+
// Get file size
|
|
267
|
+
const stats = await fs.promises.stat(filePath);
|
|
268
|
+
// Generate a unique multipart boundary
|
|
269
|
+
const boundary = '----NodeMultipartBoundary' + Date.now();
|
|
270
|
+
const lb = '\r\n';
|
|
271
|
+
// Build the multipart header and footer
|
|
272
|
+
const partHeader = `--${boundary}${lb}` +
|
|
273
|
+
`Content-Disposition: form-data; name="template"; filename="${fileName}"${lb}` +
|
|
274
|
+
`Content-Type: application/octet-stream${lb}${lb}`;
|
|
275
|
+
const partFooter = `${lb}--${boundary}--${lb}`;
|
|
276
|
+
// Calculate total content length
|
|
277
|
+
const contentLength = Buffer.byteLength(partHeader) +
|
|
278
|
+
stats.size +
|
|
279
|
+
Buffer.byteLength(partFooter);
|
|
280
|
+
const options = {
|
|
281
|
+
hostname: '127.0.0.1',
|
|
282
|
+
port: 5020,
|
|
283
|
+
path: '/find-image',
|
|
284
|
+
method: 'POST',
|
|
285
|
+
headers: {
|
|
286
|
+
'Content-Type': `multipart/form-data; boundary=${boundary}`,
|
|
287
|
+
'Content-Length': contentLength
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
return new Promise((resolve, reject) => {
|
|
291
|
+
const req = http.request(options, (res) => {
|
|
292
|
+
let body = '';
|
|
293
|
+
res.setEncoding('utf8');
|
|
294
|
+
res.on('data', (chunk) => {
|
|
295
|
+
body += chunk;
|
|
296
|
+
});
|
|
297
|
+
res.on('end', () => {
|
|
298
|
+
try {
|
|
299
|
+
const json = JSON.parse(body);
|
|
300
|
+
resolve(json);
|
|
301
|
+
}
|
|
302
|
+
catch (err) {
|
|
303
|
+
reject(new Error('Invalid JSON response'));
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
req.on('error', reject);
|
|
308
|
+
// write header
|
|
309
|
+
req.write(partHeader);
|
|
310
|
+
// pipe the file
|
|
311
|
+
const fileStream = fs.createReadStream(filePath);
|
|
312
|
+
fileStream.pipe(req, { end: false });
|
|
313
|
+
fileStream.on('end', () => {
|
|
314
|
+
// write footer and end request
|
|
315
|
+
req.end(partFooter);
|
|
316
|
+
});
|
|
317
|
+
fileStream.on('error', reject);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* saveProjectFile
|
|
322
|
+
* ----------------
|
|
323
|
+
* Saves an array of objects to `filePath` as newline-delimited JSON.
|
|
324
|
+
* Each element in `array` is stringified and written on its own line.
|
|
325
|
+
*
|
|
326
|
+
* @param {string} filePath - Path to the file where data will be written.
|
|
327
|
+
* @param {any[]} array - Array of serializable objects.
|
|
328
|
+
*/
|
|
329
|
+
function saveProjectFile(filePath, array) {
|
|
330
|
+
// Serialize each object to a JSON string, join by newline
|
|
331
|
+
const data = array.map(obj => JSON.stringify(obj)).join('\n');
|
|
332
|
+
fs.writeFileSync(filePath, data, 'utf8');
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* readProjectFile
|
|
336
|
+
* ----------------
|
|
337
|
+
* Reads `filePath` synchronously and returns an array of objects.
|
|
338
|
+
* - If the file content (after trimming) starts with '[', parse it as a JSON array.
|
|
339
|
+
* - Otherwise, split on '\n', parse each non-empty line as a JSON object, and return an array of those objects.
|
|
340
|
+
*
|
|
341
|
+
* @param {string} filePath - Path to the JSON file to read.
|
|
342
|
+
* @returns {any[]} - Array of objects read from the file.
|
|
343
|
+
*/
|
|
344
|
+
function readProjectFile(filePath) {
|
|
345
|
+
// Read whole file as UTF-8 text
|
|
346
|
+
const content = fs.readFileSync(filePath, 'utf8').trim();
|
|
347
|
+
if (!content) {
|
|
348
|
+
// Empty file: return empty array
|
|
349
|
+
return [];
|
|
350
|
+
}
|
|
351
|
+
if (content.startsWith('[')) {
|
|
352
|
+
// File is a JSON array
|
|
353
|
+
return JSON.parse(content);
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
// Treat as newline-delimited JSON objects
|
|
357
|
+
return content
|
|
358
|
+
.split('\n')
|
|
359
|
+
.filter(line => line.trim() !== '')
|
|
360
|
+
.map(line => JSON.parse(line));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Counts the number of files (not directories) in the Downloads folder.
|
|
365
|
+
* @returns {number} The count of files.
|
|
366
|
+
*/
|
|
367
|
+
function countFilesInDownloads() {
|
|
368
|
+
const downloadsPath = getDownloadsFolder();
|
|
369
|
+
try {
|
|
370
|
+
const entries = fs.readdirSync(downloadsPath);
|
|
371
|
+
let fileCount = 0;
|
|
372
|
+
for (const entry of entries) {
|
|
373
|
+
const fullPath = path.join(downloadsPath, entry);
|
|
374
|
+
if (fs.statSync(fullPath).isFile()) {
|
|
375
|
+
fileCount++;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return fileCount;
|
|
379
|
+
}
|
|
380
|
+
catch (err) {
|
|
381
|
+
console.error(`Failed to read ${downloadsPath}:`, err);
|
|
382
|
+
return 0;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Schedule `callback` to run in `timeoutMs` milliseconds,
|
|
387
|
+
* unless you call the returned cancel function first.
|
|
388
|
+
*
|
|
389
|
+
* @param {() => void} callback
|
|
390
|
+
* @param {number} timeoutMs
|
|
391
|
+
* @returns {() => void} cancel function
|
|
392
|
+
*/
|
|
393
|
+
function ifYouDontFinish(callback, timeoutMs) {
|
|
394
|
+
const timerId = setTimeout(callback, timeoutMs);
|
|
395
|
+
return () => clearTimeout(timerId);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Find all files in the Downloads folder that start with
|
|
399
|
+
* "social_aporter_" and end with ".mp4".
|
|
400
|
+
*
|
|
401
|
+
* @returns {Promise<string[]>} Array of matching file names
|
|
402
|
+
*/
|
|
403
|
+
async function findSocialPorterVideos() {
|
|
404
|
+
const downloadsDir = getDownloadsFolder();
|
|
405
|
+
try {
|
|
406
|
+
const entries = fs.readdirSync(downloadsDir);
|
|
407
|
+
return entries.filter(name => name.startsWith('social_') &&
|
|
408
|
+
name.toLowerCase().endsWith('.mp4'));
|
|
409
|
+
}
|
|
410
|
+
catch (err) {
|
|
411
|
+
console_log_it(`Failed to read directory ${downloadsDir}:`, 'findSocialPorterVideos');
|
|
412
|
+
return [];
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Move an array of filenames (from findSocialPorterVideos)
|
|
417
|
+
* from the Downloads folder into the given target directory.
|
|
418
|
+
*
|
|
419
|
+
* @param {string[]} files - array of filenames (e.g. ['social_aporter_1.mp4', ...])
|
|
420
|
+
* @param {string} targetDir - absolute or relative path to destination directory
|
|
421
|
+
*/
|
|
422
|
+
async function moveSocialPorterVideos(files, targetDir) {
|
|
423
|
+
try {
|
|
424
|
+
console_log_it(`moveSocialPorterVideos`, 'social_move');
|
|
425
|
+
const downloadsDir = getDownloadsFolder();
|
|
426
|
+
console_log_it(downloadsDir, 'social_move');
|
|
427
|
+
// ensure the target directory exists
|
|
428
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
429
|
+
console_log_it(targetDir, 'social_move');
|
|
430
|
+
console_log_it(`files.length : ${files.length}`, 'social_move_length');
|
|
431
|
+
for (const file of files) {
|
|
432
|
+
const srcPath = path.join(downloadsDir, file);
|
|
433
|
+
const destPath = path.join(targetDir, file);
|
|
434
|
+
try {
|
|
435
|
+
// first try a fast rename
|
|
436
|
+
fs.renameSync(srcPath, destPath);
|
|
437
|
+
console_log_it(`✔ Renamed: ${file}`, 'social_move');
|
|
438
|
+
}
|
|
439
|
+
catch (err) {
|
|
440
|
+
if (err.code === 'EXDEV') {
|
|
441
|
+
// cross-device, so copy then delete
|
|
442
|
+
try {
|
|
443
|
+
fs.copyFileSync(srcPath, destPath);
|
|
444
|
+
fs.unlinkSync(srcPath);
|
|
445
|
+
console_log_it(`✔ Copied+Deleted across devices: ${file}`, 'social_move');
|
|
446
|
+
}
|
|
447
|
+
catch (copyErr) {
|
|
448
|
+
console_log_it(`✖ Failed to copy+delete ${file}: ${copyErr}`, 'social_move');
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
console_log_it(`✖ Failed to rename ${file}: ${err}`, 'social_move');
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
catch (e) {
|
|
458
|
+
console_log_it(`✖ Failed to move social_porter videos: ${e}`, 'social_move');
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function getDownloadsFolder() {
|
|
462
|
+
const home = os.homedir();
|
|
463
|
+
if (process.platform === 'win32') {
|
|
464
|
+
// Windows: check registry for the Downloads known folder
|
|
465
|
+
try {
|
|
466
|
+
const raw = execSync('reg query "HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders" /v "{374DE290-123F-4565-9164-39C4925E467B}"').toString();
|
|
467
|
+
// parse the REG_SZ value and expand %USERPROFILE%
|
|
468
|
+
const match = raw.match(/REG_SZ\s+(.*)/);
|
|
469
|
+
if (match) {
|
|
470
|
+
return match[1].replace('%USERPROFILE%', home);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
catch (e) {
|
|
474
|
+
// fall back if registry read fails
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// macOS & Linux: check XDG spec first
|
|
478
|
+
if (process.platform !== 'win32') {
|
|
479
|
+
const cfg = path.join(home, '.config', 'user-dirs.dirs');
|
|
480
|
+
if (fs.existsSync(cfg)) {
|
|
481
|
+
const content = fs.readFileSync(cfg, 'utf8');
|
|
482
|
+
const line = content.match(/^XDG_DOWNLOAD_DIR=(.*)$/m);
|
|
483
|
+
if (line) {
|
|
484
|
+
// strip quotes and ${HOME}
|
|
485
|
+
return line[1]
|
|
486
|
+
.replace(/^"(.*)"$/, '$1')
|
|
487
|
+
.replace('$HOME', home);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// default fallback
|
|
492
|
+
return path.join(home, 'Downloads');
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Reads all entries in `dirPath` and returns those whose names start with `prefix`.
|
|
496
|
+
* @param {string} dirPath - The directory to scan.
|
|
497
|
+
* @param {string} prefix - The filename prefix to match.
|
|
498
|
+
* @returns {Promise<string[]>} - Resolves to an array of matching filenames.
|
|
499
|
+
*/
|
|
500
|
+
async function getFilesStartingWith(dirPath, prefix) {
|
|
501
|
+
try {
|
|
502
|
+
// Read all entries in the directory
|
|
503
|
+
const entries = await fs.promises.readdir(dirPath);
|
|
504
|
+
// Filter for names that start with the prefix
|
|
505
|
+
const matches = entries.filter(name => name.startsWith(prefix));
|
|
506
|
+
return matches;
|
|
507
|
+
}
|
|
508
|
+
catch (err) {
|
|
509
|
+
// You might want to handle ENOENT (directory not found) specially
|
|
510
|
+
console.error(`Error reading directory ${dirPath}:`, err);
|
|
511
|
+
throw err;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Reads all entries in `dirPath` and returns those whose names start with `prefix`.
|
|
516
|
+
* @param {string} dirPath - The directory to scan.
|
|
517
|
+
* @returns {Promise<string[]>} - Resolves to an array of matching filenames.
|
|
518
|
+
*/
|
|
519
|
+
async function getFiles(dirPath) {
|
|
520
|
+
try {
|
|
521
|
+
// Read all entries in the directory
|
|
522
|
+
const entries = await fs.promises.readdir(dirPath);
|
|
523
|
+
// Filter for names that start with the prefix
|
|
524
|
+
const matches = entries;
|
|
525
|
+
return matches;
|
|
526
|
+
}
|
|
527
|
+
catch (err) {
|
|
528
|
+
return [];
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Synchronously deletes the file at `filePath`.
|
|
533
|
+
* @param {string} filePath - The path to the file to delete.
|
|
534
|
+
*/
|
|
535
|
+
function deleteFileSync(filePath) {
|
|
536
|
+
try {
|
|
537
|
+
fs.unlinkSync(filePath);
|
|
538
|
+
console.log(`Deleted: ${filePath}`);
|
|
539
|
+
}
|
|
540
|
+
catch (err) {
|
|
541
|
+
if (err.code === 'ENOENT') {
|
|
542
|
+
console.warn(`File not found, could not delete: ${filePath}`);
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
console.error(`Error deleting file ${filePath}:`, err);
|
|
546
|
+
throw err; // rethrow if it's a different error
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Download a single file to the given destination path.
|
|
552
|
+
* @param {string} fileUrl - The URL of the file to download.
|
|
553
|
+
* @param {string} destPath - The full path (including filename) to save the file.
|
|
554
|
+
* @returns {Promise<void>}
|
|
555
|
+
*/
|
|
556
|
+
function downloadFile(fileUrl, destPath, onProgress) {
|
|
557
|
+
return new Promise((resolve, reject) => {
|
|
558
|
+
const urlObj = new URL(fileUrl);
|
|
559
|
+
const client = urlObj.protocol === 'https:' ? https : http;
|
|
560
|
+
client.get(urlObj, response => {
|
|
561
|
+
if (response.statusCode !== 200) {
|
|
562
|
+
return reject(new Error(`Failed to get '${fileUrl}' (status: ${response.statusCode})`));
|
|
563
|
+
}
|
|
564
|
+
// Make sure the destination folder exists
|
|
565
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
566
|
+
// Read total size from headers
|
|
567
|
+
const total = parseInt(response.headers['content-length'] || '0', 10);
|
|
568
|
+
// Create the progress stream
|
|
569
|
+
const prog = progressStream({
|
|
570
|
+
length: total,
|
|
571
|
+
time: 100 // emit 'progress' events every 100ms
|
|
572
|
+
});
|
|
573
|
+
prog.on('progress', progress => {
|
|
574
|
+
if (onProgress) {
|
|
575
|
+
onProgress(`Downloading ${fileUrl}… ${progress.percentage.toFixed(2)}%`, progress.percentage);
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
// Pipe response → prog → fileStream
|
|
579
|
+
const fileStream = fs.createWriteStream(destPath);
|
|
580
|
+
response.pipe(prog).pipe(fileStream);
|
|
581
|
+
fileStream.on('finish', () => {
|
|
582
|
+
fileStream.close(resolve);
|
|
583
|
+
});
|
|
584
|
+
fileStream.on('error', err => {
|
|
585
|
+
fs.unlink(destPath, () => reject(err));
|
|
586
|
+
});
|
|
587
|
+
})
|
|
588
|
+
.on('error', err => {
|
|
589
|
+
reject(err);
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Read and parse all .json files in a directory, including newline-delimited JSON.
|
|
595
|
+
*
|
|
596
|
+
* @param {string} dirPath - Path to folder containing your .json files
|
|
597
|
+
* @returns {Promise<Object[]>} - Resolves to an array of all parsed objects
|
|
598
|
+
*/
|
|
599
|
+
async function readJsonFiles(dirPath) {
|
|
600
|
+
return new Promise((resolve, reject) => {
|
|
601
|
+
let files;
|
|
602
|
+
try {
|
|
603
|
+
files = fs.readdirSync(dirPath);
|
|
604
|
+
}
|
|
605
|
+
catch (err) {
|
|
606
|
+
return reject(err);
|
|
607
|
+
}
|
|
608
|
+
// Filter to .json files
|
|
609
|
+
const jsonFiles = files.filter(f => path.extname(f).toLowerCase() === '.json');
|
|
610
|
+
if (jsonFiles.length === 0) {
|
|
611
|
+
return resolve([]);
|
|
612
|
+
}
|
|
613
|
+
const results = [];
|
|
614
|
+
jsonFiles.forEach(filename => {
|
|
615
|
+
const fullPath = path.join(dirPath, filename);
|
|
616
|
+
results.push(fullPath);
|
|
617
|
+
});
|
|
618
|
+
resolve(results);
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Get the last‑modified time of a file as a comparable number.
|
|
623
|
+
*
|
|
624
|
+
* @param {string} filePath - Path to the file
|
|
625
|
+
* @returns {number} - milliseconds since UNIX epoch
|
|
626
|
+
*/
|
|
627
|
+
function getLastModifiedTimestampSync(filePath) {
|
|
628
|
+
// resolve to an absolute path (optional but avoids surprises)
|
|
629
|
+
const fullPath = path.resolve(filePath);
|
|
630
|
+
// statSync will throw if the file doesn't exist or is inaccessible
|
|
631
|
+
const stats = fs.statSync(fullPath);
|
|
632
|
+
// use getTime() for maximum compatibility across Node versions
|
|
633
|
+
return stats.mtime.getTime();
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Download multiple files into a target folder.
|
|
637
|
+
* @param {string[]} urls - Array of file URLs to download.
|
|
638
|
+
* @param {string} destFolder - Path to the folder where files should be saved.
|
|
639
|
+
* @returns {Promise<string[]>} - Resolves to an array of the downloaded file paths.
|
|
640
|
+
*/
|
|
641
|
+
async function downloadFiles(urls, destFolder) {
|
|
642
|
+
// Ensure destination folder exists
|
|
643
|
+
fs.mkdirSync(destFolder, { recursive: true });
|
|
644
|
+
const downloadPromises = urls.map(async (fileUrl) => {
|
|
645
|
+
// Derive a filename from the URL
|
|
646
|
+
const pathname = new URL(fileUrl).pathname;
|
|
647
|
+
let filename = path.basename(pathname);
|
|
648
|
+
if (!filename) {
|
|
649
|
+
// Fallback if URL ends with a slash
|
|
650
|
+
filename = `file-${Date.now()}`;
|
|
651
|
+
}
|
|
652
|
+
const destPath = path.join(destFolder, filename);
|
|
653
|
+
if (!fs.existsSync(destPath))
|
|
654
|
+
await downloadFile(fileUrl, destPath, null).catch(e => {
|
|
655
|
+
console.error(e);
|
|
656
|
+
});
|
|
657
|
+
return destPath;
|
|
658
|
+
});
|
|
659
|
+
// Wait for all downloads to complete
|
|
660
|
+
return Promise.all(downloadPromises);
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Ensures that the directory at `dirPath` exists.
|
|
664
|
+
* Creates it (and any missing parent directories) if it doesn’t.
|
|
665
|
+
* @param {string} dirPath
|
|
666
|
+
* @returns {Promise<void>}
|
|
667
|
+
*/
|
|
668
|
+
async function ensureDir(dirPath) {
|
|
669
|
+
return fs.promises.mkdir(dirPath, { recursive: true });
|
|
670
|
+
}
|
|
671
|
+
function unique(array, selector = x => x) {
|
|
672
|
+
const seen = new Set();
|
|
673
|
+
return array.filter(item => {
|
|
674
|
+
const key = selector(item);
|
|
675
|
+
if (seen.has(key)) {
|
|
676
|
+
return false;
|
|
677
|
+
}
|
|
678
|
+
seen.add(key);
|
|
679
|
+
return true;
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Strip out punctuation and lowercase for comparison.
|
|
684
|
+
*/
|
|
685
|
+
function normalize(str) {
|
|
686
|
+
return str.replace(/[^a-z0-9]/gi, '').toLowerCase();
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Align every lyric line to its timed words, treating any number of
|
|
690
|
+
* trailing '\n' on a word as exactly one line break.
|
|
691
|
+
*
|
|
692
|
+
* @param {string[]} lines
|
|
693
|
+
* @param {{ word: string, start_s: number, end_s: number }[]} alignedWords
|
|
694
|
+
* @returns {{
|
|
695
|
+
* line: string,
|
|
696
|
+
* start_s: number|null,
|
|
697
|
+
* end_s: number|null,
|
|
698
|
+
* words: { text: string, start_s: number|null, end_s: number|null }[]
|
|
699
|
+
* }[]}
|
|
700
|
+
*/
|
|
701
|
+
function alignLinesToWords(alignedWords) {
|
|
702
|
+
// 1) Bucket the alignedWords into per-line arrays by '\n+' marker:
|
|
703
|
+
const wordsByLine = [];
|
|
704
|
+
let lineIdx = 0;
|
|
705
|
+
wordsByLine[lineIdx] = [];
|
|
706
|
+
alignedWords.forEach(({ word, start_s, end_s }) => {
|
|
707
|
+
// detect one-or-more trailing newlines
|
|
708
|
+
const isLineEnd = /\n+\"?$/.test(word);
|
|
709
|
+
// strip ALL trailing newlines for matching
|
|
710
|
+
const cleanWord = word.replace(/\n+$/, '');
|
|
711
|
+
wordsByLine[lineIdx].push({ text: cleanWord, start_s, end_s });
|
|
712
|
+
// if there was at least one '\n', advance to next bucket
|
|
713
|
+
if (isLineEnd) {
|
|
714
|
+
lineIdx++;
|
|
715
|
+
wordsByLine[lineIdx] = [];
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
// 2) Now align each lyric line to its bucket of timed words
|
|
719
|
+
return wordsByLine.map((line, i) => {
|
|
720
|
+
// const tokens = line.split(/\s+/).filter(Boolean);
|
|
721
|
+
const timed = wordsByLine[i] || [];
|
|
722
|
+
const aligned = timed || [];
|
|
723
|
+
// compute line start/end as min/max of word timings
|
|
724
|
+
const starts = timed.map(w => w.start_s).filter(x => x != null);
|
|
725
|
+
const ends = timed.map(w => w.end_s).filter(x => x != null);
|
|
726
|
+
return {
|
|
727
|
+
line: line.map(v => v.text.trim()).join(' '),
|
|
728
|
+
start_s: starts.length ? Math.min(...starts) : null,
|
|
729
|
+
end_s: ends.length ? Math.max(...ends) : null,
|
|
730
|
+
start: starts.length ? Math.min(...starts) : null,
|
|
731
|
+
end: ends.length ? Math.max(...ends) : null,
|
|
732
|
+
words: aligned
|
|
733
|
+
};
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
function extractFileNameFromUrl(url) {
|
|
737
|
+
try {
|
|
738
|
+
const urlObj = new URL(url);
|
|
739
|
+
return path.basename(urlObj.pathname);
|
|
740
|
+
}
|
|
741
|
+
catch (error) {
|
|
742
|
+
console.error('Invalid URL:', url, error);
|
|
743
|
+
return null;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
function findFileInDirectoryHasInWithAndEndingWith(dirPath, start, end) {
|
|
747
|
+
try {
|
|
748
|
+
const files = fs.readdirSync(dirPath);
|
|
749
|
+
return files.find(file => file.includes(start) && file.endsWith(end)) || null;
|
|
750
|
+
}
|
|
751
|
+
catch (err) {
|
|
752
|
+
console.error(`Error reading directory ${dirPath}:`, err);
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
export function getFilesToProcess(name, filesToProcess, root_folder) {
|
|
757
|
+
switch (get(KEYS.procedure)) {
|
|
758
|
+
}
|
|
759
|
+
return filesToProcess;
|
|
760
|
+
}
|
|
761
|
+
export function getMediaFileDuration(filePath) {
|
|
762
|
+
return new Promise((resolve, reject) => {
|
|
763
|
+
ffmpeg.ffprobe(filePath, (err, metadata) => {
|
|
764
|
+
if (err)
|
|
765
|
+
return reject(err);
|
|
766
|
+
if (metadata && metadata.format && typeof metadata.format.duration === 'number') {
|
|
767
|
+
resolve(metadata.format.duration); // duration in seconds
|
|
768
|
+
}
|
|
769
|
+
else if (metadata && metadata.streams) {
|
|
770
|
+
// If duration is not in format, try to find it in any stream (audio or video)
|
|
771
|
+
const stream = metadata.streams.find(s => s.codec_type === 'audio' || s.codec_type === 'video');
|
|
772
|
+
if (stream && typeof stream.duration === 'number') {
|
|
773
|
+
resolve(Math.round(stream.duration));
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
reject(new Error('Could not determine media duration.'));
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
reject(new Error('Could not determine media duration.'));
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
// Returns the duration of a video file in seconds using ffmpeg
|
|
786
|
+
function getVideoDuration(filePath) {
|
|
787
|
+
return new Promise((resolve, reject) => {
|
|
788
|
+
ffmpeg.ffprobe(filePath, (err, metadata) => {
|
|
789
|
+
if (err)
|
|
790
|
+
return reject(err);
|
|
791
|
+
if (metadata && metadata.format && typeof metadata.format.duration === 'number') {
|
|
792
|
+
// Find the first video stream for dimensions
|
|
793
|
+
const videoStream = (metadata.streams || []).find(s => s.codec_type === 'video');
|
|
794
|
+
const width = videoStream ? videoStream.width : undefined;
|
|
795
|
+
const height = videoStream ? videoStream.height : undefined;
|
|
796
|
+
resolve({
|
|
797
|
+
duration: metadata.format.duration,
|
|
798
|
+
width,
|
|
799
|
+
height
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
else {
|
|
803
|
+
reject(new Error('Could not determine video duration.'));
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
export function getVideoDimensions(filePath) {
|
|
809
|
+
return new Promise((resolve, reject) => {
|
|
810
|
+
ffmpeg.ffprobe(filePath, (err, metadata) => {
|
|
811
|
+
if (err)
|
|
812
|
+
return reject(err);
|
|
813
|
+
const videoStream = (metadata.streams || []).find(s => s.codec_type === 'video');
|
|
814
|
+
if (videoStream) {
|
|
815
|
+
resolve({
|
|
816
|
+
width: videoStream.width,
|
|
817
|
+
height: videoStream.height
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
else {
|
|
821
|
+
reject(new Error('No video stream found.'));
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Sends the “refresh” key combo:
|
|
828
|
+
* - macOS → ⌘+R
|
|
829
|
+
* - Windows/Linux → F5
|
|
830
|
+
*/
|
|
831
|
+
function refreshScreen(robot) {
|
|
832
|
+
const platform = process.platform;
|
|
833
|
+
if (platform === 'darwin') {
|
|
834
|
+
// macOS: Command+R
|
|
835
|
+
robot.keyTap('r', 'command');
|
|
836
|
+
}
|
|
837
|
+
else {
|
|
838
|
+
// Windows & most Linux desktops: F5
|
|
839
|
+
robot.keyTap('f5');
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Reads files in a directory and sorts them by updated_at (most recent first).
|
|
844
|
+
* @param {string} dirPath - The path to the directory.
|
|
845
|
+
* @returns {Promise<string[]>} - A Promise that resolves to an array of filenames sorted by updated_at.
|
|
846
|
+
*/
|
|
847
|
+
async function getSortedFilesByUpdatedAt(dirPath) {
|
|
848
|
+
return new Promise((resolve, reject) => {
|
|
849
|
+
fs.readdir(dirPath, (err, files) => {
|
|
850
|
+
if (err)
|
|
851
|
+
return reject(err);
|
|
852
|
+
const filePaths = files.filter(x => x.endsWith('.png')).map(file => path.join(dirPath, file));
|
|
853
|
+
Promise.all(filePaths.map(filePath => fs.promises.stat(filePath).then(stats => ({
|
|
854
|
+
file: path.basename(filePath),
|
|
855
|
+
updatedAt: stats.mtime.getTime()
|
|
856
|
+
})))).then(fileStats => {
|
|
857
|
+
const sorted = fileStats
|
|
858
|
+
.sort((a, b) => b.updatedAt - a.updatedAt)
|
|
859
|
+
.map(f => f.file);
|
|
860
|
+
resolve(sorted);
|
|
861
|
+
}).catch(reject);
|
|
862
|
+
});
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Asynchronously writes text to the clipboard, waits for a short delay,
|
|
867
|
+
* and then simulates a paste operation.
|
|
868
|
+
*
|
|
869
|
+
* @param {string} text - The text to copy to the clipboard and paste.
|
|
870
|
+
*/
|
|
871
|
+
async function pasteText(text) {
|
|
872
|
+
const clipboardy = (await import('clipboardy')).default;
|
|
873
|
+
// Asynchronously write the text to the clipboard.
|
|
874
|
+
await clipboardy.writeSync(`${text}`);
|
|
875
|
+
// Wait 100ms to ensure the clipboard is updated.
|
|
876
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
877
|
+
// Determine the correct modifier key based on your platform.
|
|
878
|
+
const modifier = process.platform === 'darwin' ? 'command' : 'control';
|
|
879
|
+
// Simulate the paste operation (Ctrl+V on Windows/Linux, Command+V on macOS).
|
|
880
|
+
robot.keyTap('v', modifier);
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Waits for a condition to become true within a specified time window.
|
|
884
|
+
* Polls every 10 seconds by default.
|
|
885
|
+
*
|
|
886
|
+
* @param {() => boolean} condition - The condition to check.
|
|
887
|
+
* @param {number} maxWaitMs - Maximum time to wait (default: 60 seconds).
|
|
888
|
+
* @param {number} intervalMs - Polling interval (default: 10 seconds).
|
|
889
|
+
* @returns {Promise<void>} Resolves when condition is met, rejects if time runs out.
|
|
890
|
+
*/
|
|
891
|
+
async function waitFor(condition, maxWaitMs = 120000, intervalMs = 100) {
|
|
892
|
+
const deadline = Date.now() + maxWaitMs;
|
|
893
|
+
const sleep = (ms) => new Promise(res => setTimeout(res, ms));
|
|
894
|
+
console_log_it(condition.toString(), 'wait_for_condition');
|
|
895
|
+
while (Date.now() < deadline) {
|
|
896
|
+
try {
|
|
897
|
+
if (await condition())
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
catch (e) { }
|
|
901
|
+
await sleep(intervalMs);
|
|
902
|
+
}
|
|
903
|
+
console_log_it('condition not met within timeout', 'wait_for');
|
|
904
|
+
throw new Error('Condition not met within timeout: ' + condition.toString());
|
|
905
|
+
}
|
|
906
|
+
async function workflowOp(testCondition, moveMouse, action, checkCondition, attemps = 10, interval = 1000, console_context = 'workflowOp') {
|
|
907
|
+
let attempts = 0;
|
|
908
|
+
while (attempts < attemps) {
|
|
909
|
+
try {
|
|
910
|
+
// Check the test condition
|
|
911
|
+
const currentElement = await testCondition();
|
|
912
|
+
if (currentElement) {
|
|
913
|
+
// Move the mouse to the element
|
|
914
|
+
await moveMouse();
|
|
915
|
+
// Perform the action (e.g., click)
|
|
916
|
+
await action();
|
|
917
|
+
// Check the condition after the action
|
|
918
|
+
const conditionMet = await checkCondition();
|
|
919
|
+
if (conditionMet) {
|
|
920
|
+
console_log_it('Condition met after action', console_context);
|
|
921
|
+
return true; // Exit if the condition is met
|
|
922
|
+
}
|
|
923
|
+
else {
|
|
924
|
+
console_log_it('Condition not met after action, retrying...', console_context);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
console_log_it('Test condition not met, retrying...', console_context);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
catch (error) {
|
|
932
|
+
console_log_it(`Error in workflowOp: ${error.message}`, console_context);
|
|
933
|
+
}
|
|
934
|
+
// Wait before the next attempt
|
|
935
|
+
await new Promise(resolve => setTimeout(resolve, interval));
|
|
936
|
+
attempts++;
|
|
937
|
+
}
|
|
938
|
+
console_log_it('Max attempts reached, exiting workflowOp', console_context);
|
|
939
|
+
return false; // Return false if the condition was never met
|
|
940
|
+
// Example usage:
|
|
941
|
+
// const MODES = getContext().mode;
|
|
942
|
+
}
|
|
943
|
+
async function isThere(condition, maxWaitMs = 6000, intervalMs = 100) {
|
|
944
|
+
try {
|
|
945
|
+
await waitFor(condition, maxWaitMs, intervalMs);
|
|
946
|
+
}
|
|
947
|
+
catch (e) {
|
|
948
|
+
console_log_it('is there failed ', 'isThere');
|
|
949
|
+
console_log_it(e, 'isThere-error');
|
|
950
|
+
return false;
|
|
951
|
+
}
|
|
952
|
+
return true;
|
|
953
|
+
}
|
|
954
|
+
function pressReturn() {
|
|
955
|
+
// You can also use 'return' on some platforms, but 'enter' is most common
|
|
956
|
+
robot.keyTap('enter');
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Pause execution for a specified delay and then execute a callback.
|
|
960
|
+
* @param {Function} callback - The function to execute after the delay.
|
|
961
|
+
* @param {number} delay - The delay in milliseconds.
|
|
962
|
+
* @returns {Promise<void>}
|
|
963
|
+
*/
|
|
964
|
+
function pause(callback, delay) {
|
|
965
|
+
return new Promise((resolve) => {
|
|
966
|
+
setTimeout(async () => {
|
|
967
|
+
await callback();
|
|
968
|
+
resolve(undefined);
|
|
969
|
+
}, delay);
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Selects all text in the current focused field and deletes it.
|
|
974
|
+
*/
|
|
975
|
+
export function clearAllText() {
|
|
976
|
+
// On macOS use 'command', otherwise 'control'
|
|
977
|
+
const modifier = process.platform === 'darwin' ? 'command' : 'control';
|
|
978
|
+
// Give a tiny pause to ensure focus is correct (optional)
|
|
979
|
+
robot.setKeyboardDelay(50);
|
|
980
|
+
// Select all
|
|
981
|
+
robot.keyTap('a', modifier);
|
|
982
|
+
// Delete selected text
|
|
983
|
+
// On some apps you may prefer 'backspace' instead of 'delete'
|
|
984
|
+
robot.keyTap('delete');
|
|
985
|
+
}
|
|
986
|
+
async function mouseClick() {
|
|
987
|
+
return new Promise((resolve) => {
|
|
988
|
+
robot.mouseToggle('down');
|
|
989
|
+
setTimeout(() => {
|
|
990
|
+
robot.mouseToggle('up');
|
|
991
|
+
resolve(undefined);
|
|
992
|
+
}, 100);
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
async function waitForQueuedRenders(waitedAmount, SPEED) {
|
|
996
|
+
while (getModePosition()?.[MIDJOURNEY_SELECTOR.queued]) {
|
|
997
|
+
console_log_it(`'waited amount ${waitedAmount}'`, 'waited_amount');
|
|
998
|
+
waitedAmount++;
|
|
999
|
+
await pause(() => { }, SPEED);
|
|
1000
|
+
}
|
|
1001
|
+
return waitedAmount;
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Extracts the version number from a string containing '_version_${number}'.
|
|
1005
|
+
* Returns the number as an integer, or null if not found.
|
|
1006
|
+
* @param {string} str
|
|
1007
|
+
* @returns {number|null}
|
|
1008
|
+
*/
|
|
1009
|
+
function extractVersionNumber(str) {
|
|
1010
|
+
const match = str.match(/_version_(\d+)/);
|
|
1011
|
+
return match ? parseInt(match[1], 10) : null;
|
|
1012
|
+
}
|
|
1013
|
+
const context = {
|
|
1014
|
+
positions: null,
|
|
1015
|
+
position: {},
|
|
1016
|
+
last_updated: 0,
|
|
1017
|
+
last_used: 0
|
|
1018
|
+
};
|
|
1019
|
+
function getContext() {
|
|
1020
|
+
return context;
|
|
1021
|
+
}
|
|
1022
|
+
function setContext(key, value) {
|
|
1023
|
+
context[key] = value;
|
|
1024
|
+
context.last_updated = Date.now();
|
|
1025
|
+
context.last_used = Date.now();
|
|
1026
|
+
}
|
|
1027
|
+
;
|
|
1028
|
+
/// ─── UTIL ──────────────────────────────────────────────────────────────────────
|
|
1029
|
+
/**
|
|
1030
|
+
* Replace placeholders in a template string with values from a data object.
|
|
1031
|
+
* Placeholders are in the form {key}.
|
|
1032
|
+
*
|
|
1033
|
+
* @param {string} template — the template string, e.g. "Hello, {name}!"
|
|
1034
|
+
* @param {Object} data — an object mapping keys to values, e.g. { name: "Alice" }
|
|
1035
|
+
* @returns {string} — the interpolated string
|
|
1036
|
+
*/
|
|
1037
|
+
function applyTemplate(template, data) {
|
|
1038
|
+
return template.replace(/\{([^}]+)\}/g, (_, key) => {
|
|
1039
|
+
// If the key exists in data, replace it; otherwise leave it blank (or you could throw/error)
|
|
1040
|
+
return data[key] != null ? data[key] : "";
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
// grab the first non-internal IPv4 on your machine
|
|
1044
|
+
function getLocalIp() {
|
|
1045
|
+
const ifaces = os.networkInterfaces();
|
|
1046
|
+
for (const list of Object.values(ifaces)) {
|
|
1047
|
+
for (const iface of list) {
|
|
1048
|
+
if (iface.family === 'IPv4' && !iface.internal) {
|
|
1049
|
+
return iface.address;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
throw new Error('No LAN IPv4 address found');
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Build a text progress bar.
|
|
1057
|
+
*
|
|
1058
|
+
* @param {number} index - Current progress (0 … length)
|
|
1059
|
+
* @param {number} length - Total units of work
|
|
1060
|
+
* @param {number} charLength - Total characters in the bar
|
|
1061
|
+
* @returns {string} - Progress bar string
|
|
1062
|
+
*/
|
|
1063
|
+
function buildProgressBar(index, length, charLength = 20) {
|
|
1064
|
+
if (length <= 0 || charLength <= 0) {
|
|
1065
|
+
console_log_it(`Invalid progress bar parameters`, 'progress_bar');
|
|
1066
|
+
return '';
|
|
1067
|
+
}
|
|
1068
|
+
// Clamp index between 0 and length
|
|
1069
|
+
const clampedIndex = Math.max(0, Math.min(index, length));
|
|
1070
|
+
// How many characters should be "filled"?
|
|
1071
|
+
const filledCount = Math.floor((clampedIndex / length) * charLength);
|
|
1072
|
+
// Build the bar
|
|
1073
|
+
const filledPart = '█'.repeat(filledCount);
|
|
1074
|
+
const emptyPart = '_'.repeat(charLength - filledCount);
|
|
1075
|
+
return filledPart + emptyPart + `[${index}/${length}]`;
|
|
1076
|
+
}
|
|
1077
|
+
function getMode() {
|
|
1078
|
+
if (!getContext().mode) {
|
|
1079
|
+
getContext().mode = getItem('mode');
|
|
1080
|
+
}
|
|
1081
|
+
if (getContext().mode) {
|
|
1082
|
+
return getContext().mode;
|
|
1083
|
+
}
|
|
1084
|
+
return MODES.MIDJOURNEY;
|
|
1085
|
+
}
|
|
1086
|
+
function getModePosition() {
|
|
1087
|
+
if (getContext()?.position && getContext().position[getMode()]) {
|
|
1088
|
+
return getContext().position[getMode()];
|
|
1089
|
+
}
|
|
1090
|
+
return null;
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Recursively searches for files containing a specific string in their name.
|
|
1094
|
+
* @param dir The root directory to search.
|
|
1095
|
+
* @param searchString The string to search for in file names.
|
|
1096
|
+
* @returns An array of relative file paths.
|
|
1097
|
+
*/
|
|
1098
|
+
function findFilesWithString(dir, searchString) {
|
|
1099
|
+
const results = [];
|
|
1100
|
+
function search(currentPath) {
|
|
1101
|
+
const entries = fs.readdirSync(currentPath, { withFileTypes: true });
|
|
1102
|
+
for (const entry of entries) {
|
|
1103
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
1104
|
+
if (entry.isDirectory()) {
|
|
1105
|
+
search(fullPath); // Recurse into subdirectory
|
|
1106
|
+
}
|
|
1107
|
+
else if (entry.name.includes(searchString)) {
|
|
1108
|
+
const relativePath = path.relative(dir, fullPath);
|
|
1109
|
+
results.push(relativePath);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
search(dir);
|
|
1114
|
+
return results;
|
|
1115
|
+
}
|
|
1116
|
+
function getModePositionKey(key) {
|
|
1117
|
+
return getModePosition()?.[key];
|
|
1118
|
+
}
|
|
1119
|
+
function lerp(start, end, t) {
|
|
1120
|
+
return start + (end - start) * t;
|
|
1121
|
+
}
|
|
1122
|
+
function get(key) {
|
|
1123
|
+
if (!getContext()?.[key]) {
|
|
1124
|
+
getContext()[key] = getItem(key);
|
|
1125
|
+
}
|
|
1126
|
+
console_log_it(`${getItem(key)}`.substring(0, 200), key);
|
|
1127
|
+
return getItem(key);
|
|
1128
|
+
}
|
|
1129
|
+
function set(key, value) {
|
|
1130
|
+
setItem(key, value);
|
|
1131
|
+
console_log_it(`${value}`.substring(0, 200), key);
|
|
1132
|
+
setContext(key, getItem(key));
|
|
1133
|
+
}
|
|
1134
|
+
function get_jobs_path() {
|
|
1135
|
+
if (!getContext().jobsPath) {
|
|
1136
|
+
getContext().jobsPath = get(KEYS.jobs_folder);
|
|
1137
|
+
}
|
|
1138
|
+
if (getContext().jobsPath) {
|
|
1139
|
+
return path.join(get(KEYS.jobs_folder), 'source.jsonl');
|
|
1140
|
+
}
|
|
1141
|
+
throw 'jobs_path not set';
|
|
1142
|
+
}
|
|
1143
|
+
const jobs_context = {
|
|
1144
|
+
jobs: []
|
|
1145
|
+
};
|
|
1146
|
+
function getJobsContext() {
|
|
1147
|
+
return jobs_context;
|
|
1148
|
+
}
|
|
1149
|
+
function findJobs(prompt) {
|
|
1150
|
+
console_log_it(`running: read jobs: ${get_jobs_path()}`, 'jobs');
|
|
1151
|
+
jobs_context.jobs = fs.readFileSync(get_jobs_path(), 'utf-8').split('\n').map(v => JSON.parse(v));
|
|
1152
|
+
let jobs = jobs_context.jobs.filter(f => `${(f.full_command)}`.includes((prompt)));
|
|
1153
|
+
return jobs;
|
|
1154
|
+
}
|
|
1155
|
+
function findVideoJobs(prompt) {
|
|
1156
|
+
console_log_it(`running: read jobs: ${get_jobs_path()}`, 'jobs');
|
|
1157
|
+
let jobs = findJobs(prompt);
|
|
1158
|
+
return jobs.filter(job => {
|
|
1159
|
+
return job.event_type === "video_diffusion" || job.event_type === "video_extended";
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
;
|
|
1163
|
+
/**
|
|
1164
|
+
* Create a zip file containing specified folders
|
|
1165
|
+
* @param {string} rootFolder - Root directory path
|
|
1166
|
+
* @param {string[]} folderNames - Array of folder names relative to root folder
|
|
1167
|
+
* @param {string} zipFileName - Name of the zip file to create (without extension)
|
|
1168
|
+
* @param {Object} options - Additional options
|
|
1169
|
+
* @returns {Promise<string>} - Path to the created zip file
|
|
1170
|
+
*/
|
|
1171
|
+
async function createZipFromFolders(rootFolder, folderNames, zipFileName, options = {}) {
|
|
1172
|
+
const { compressionLevel = 1, // 0-9, higher = more compression but slower
|
|
1173
|
+
onProgress = null, // Callback for progress updates
|
|
1174
|
+
includeEmptyFolders = true, filter = null // Function to filter files: (filePath, stat) => boolean
|
|
1175
|
+
} = options;
|
|
1176
|
+
return new Promise((resolve, reject) => {
|
|
1177
|
+
try {
|
|
1178
|
+
console_log_it(`Creating zip file: ${zipFileName}.zip`, 'create_zip_folders');
|
|
1179
|
+
console_log_it(`Root folder: ${rootFolder}`, 'create_zip_folders');
|
|
1180
|
+
console_log_it(`Folders to zip: ${folderNames.join(', ')}`, 'create_zip_folders');
|
|
1181
|
+
// Ensure root folder exists
|
|
1182
|
+
if (!fs.existsSync(rootFolder)) {
|
|
1183
|
+
throw new Error(`Root folder does not exist: ${rootFolder}`);
|
|
1184
|
+
}
|
|
1185
|
+
// Create zip file path
|
|
1186
|
+
// make sure zipFileName doesn't double up on extensions
|
|
1187
|
+
const zipFilePath = path.join(rootFolder, zipFileName.endsWith('.zip') ? zipFileName : `${zipFileName}.zip`);
|
|
1188
|
+
// Create write stream for the zip file
|
|
1189
|
+
const output = fs.createWriteStream(zipFilePath);
|
|
1190
|
+
const archive = archiver('zip', {
|
|
1191
|
+
zlib: { level: compressionLevel }
|
|
1192
|
+
});
|
|
1193
|
+
// Track progress
|
|
1194
|
+
let totalBytes = 0;
|
|
1195
|
+
const folderStats = [];
|
|
1196
|
+
// Handle stream events
|
|
1197
|
+
output.on('close', () => {
|
|
1198
|
+
console_log_it(`Zip file created successfully: ${zipFilePath}`, 'create_zip_folders');
|
|
1199
|
+
console_log_it(`Total size: ${(archive.pointer() / 1024 / 1024).toFixed(2)} MB`, 'create_zip_folders');
|
|
1200
|
+
resolve(zipFilePath);
|
|
1201
|
+
});
|
|
1202
|
+
output.on('error', (err) => {
|
|
1203
|
+
console_log_it(`Error writing zip file: ${err.message}`, 'create_zip_folders');
|
|
1204
|
+
reject(err);
|
|
1205
|
+
});
|
|
1206
|
+
archive.on('error', (err) => {
|
|
1207
|
+
console_log_it(`Error creating archive: ${err.message}`, 'create_zip_folders');
|
|
1208
|
+
reject(err);
|
|
1209
|
+
});
|
|
1210
|
+
archive.on('progress', (progress) => {
|
|
1211
|
+
if (onProgress) {
|
|
1212
|
+
const progressPercent = totalBytes > 0 ? Math.round((progress.fs.processedBytes / totalBytes) * 100) : 0;
|
|
1213
|
+
onProgress({
|
|
1214
|
+
type: 'compress',
|
|
1215
|
+
processedBytes: progress.fs.processedBytes,
|
|
1216
|
+
totalBytes: totalBytes,
|
|
1217
|
+
progress: progressPercent,
|
|
1218
|
+
entriesProcessed: progress.entries.processed,
|
|
1219
|
+
entriesTotal: progress.entries.total
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
});
|
|
1223
|
+
// Pipe archive data to the file
|
|
1224
|
+
archive.pipe(output);
|
|
1225
|
+
// Validate and add each folder
|
|
1226
|
+
const validFolders = [];
|
|
1227
|
+
for (const folderName of folderNames) {
|
|
1228
|
+
const folderPath = path.join(rootFolder, folderName);
|
|
1229
|
+
if (!fs.existsSync(folderPath)) {
|
|
1230
|
+
console_log_it(`Warning: Folder does not exist, skipping: ${folderName}`, 'create_zip_folders');
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
const stat = fs.statSync(folderPath);
|
|
1234
|
+
if (!stat.isDirectory()) {
|
|
1235
|
+
console_log_it(`Warning: Path is not a directory, skipping: ${folderName}`, 'create_zip_folders');
|
|
1236
|
+
continue;
|
|
1237
|
+
}
|
|
1238
|
+
validFolders.push({ name: folderName, path: folderPath });
|
|
1239
|
+
// Calculate folder size for progress tracking
|
|
1240
|
+
const folderSize = calculateFolderSize(folderPath);
|
|
1241
|
+
folderStats.push({ name: folderName, size: folderSize });
|
|
1242
|
+
totalBytes += folderSize;
|
|
1243
|
+
}
|
|
1244
|
+
if (validFolders.length === 0) {
|
|
1245
|
+
reject(new Error('No valid folders found to zip'));
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
console_log_it(`Total estimated size: ${(totalBytes / 1024 / 1024).toFixed(2)} MB`, 'create_zip_folders');
|
|
1249
|
+
// Add each folder to the archive using a more reliable method
|
|
1250
|
+
const addFolderRecursive = (folderPath, zipPath) => {
|
|
1251
|
+
try {
|
|
1252
|
+
const files = fs.readdirSync(folderPath, { withFileTypes: true });
|
|
1253
|
+
for (const file of files) {
|
|
1254
|
+
const fullPath = path.join(folderPath, file.name);
|
|
1255
|
+
const zipFilePath = path.join(zipPath, file.name).replace(/\\/g, '/'); // Normalize path separators
|
|
1256
|
+
if (file.isDirectory()) {
|
|
1257
|
+
// Recursively add subdirectory
|
|
1258
|
+
addFolderRecursive(fullPath, zipFilePath);
|
|
1259
|
+
}
|
|
1260
|
+
else if (file.isFile()) {
|
|
1261
|
+
// Apply filter if provided
|
|
1262
|
+
if (filter) {
|
|
1263
|
+
const stat = fs.statSync(fullPath);
|
|
1264
|
+
if (!filter(fullPath, stat)) {
|
|
1265
|
+
continue;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
// Add individual file
|
|
1269
|
+
console_log_it(`Adding file: ${zipFilePath}`, 'create_zip_folders');
|
|
1270
|
+
archive.file(fullPath, { name: zipFilePath });
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
catch (error) {
|
|
1275
|
+
console_log_it(`Error processing folder ${folderPath}: ${error.message}`, 'create_zip_folders');
|
|
1276
|
+
throw error;
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
// Add each valid folder
|
|
1280
|
+
for (const { name, path: folderPath } of validFolders) {
|
|
1281
|
+
console_log_it(`Adding folder to zip: ${name}`, 'create_zip_folders');
|
|
1282
|
+
if (onProgress) {
|
|
1283
|
+
onProgress({
|
|
1284
|
+
type: 'folder_start',
|
|
1285
|
+
folder: name,
|
|
1286
|
+
message: `Adding folder: ${name}`
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
// Use our custom recursive function instead of archive.directory
|
|
1290
|
+
addFolderRecursive(folderPath, name);
|
|
1291
|
+
}
|
|
1292
|
+
// Finalize the archive
|
|
1293
|
+
console_log_it('Finalizing archive...', 'create_zip_folders');
|
|
1294
|
+
archive.finalize();
|
|
1295
|
+
}
|
|
1296
|
+
catch (error) {
|
|
1297
|
+
console_log_it(`Error in createZipFromFolders: ${error.message}`, 'create_zip_folders');
|
|
1298
|
+
reject(error);
|
|
1299
|
+
}
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Calculate the total size of a folder (recursive)
|
|
1304
|
+
* @param {string} folderPath - Path to the folder
|
|
1305
|
+
* @returns {number} - Total size in bytes
|
|
1306
|
+
*/
|
|
1307
|
+
function calculateFolderSize(folderPath) {
|
|
1308
|
+
let totalSize = 0;
|
|
1309
|
+
try {
|
|
1310
|
+
const items = fs.readdirSync(folderPath, { withFileTypes: true });
|
|
1311
|
+
for (const item of items) {
|
|
1312
|
+
const itemPath = path.join(folderPath, item.name);
|
|
1313
|
+
if (item.isDirectory()) {
|
|
1314
|
+
totalSize += calculateFolderSize(itemPath);
|
|
1315
|
+
}
|
|
1316
|
+
else if (item.isFile()) {
|
|
1317
|
+
const stat = fs.statSync(itemPath);
|
|
1318
|
+
totalSize += stat.size;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
catch (error) {
|
|
1323
|
+
console_log_it(`Error calculating folder size for ${folderPath}: ${error.message}`, 'calculate_folder_size');
|
|
1324
|
+
}
|
|
1325
|
+
return totalSize;
|
|
1326
|
+
}
|
|
1327
|
+
function sanitizeFolderName(folderName) {
|
|
1328
|
+
// Remove file extension first
|
|
1329
|
+
const nameWithoutExtension = folderName.replace(/\.[^/.]+$/, '');
|
|
1330
|
+
// Then replace non-alphanumeric characters with underscores
|
|
1331
|
+
return nameWithoutExtension.replace(/[^a-zA-Z0-9]/g, '_');
|
|
1332
|
+
}
|
|
1333
|
+
/**
|
|
1334
|
+
* Create a zip file with training data from workflow steps
|
|
1335
|
+
* @param {string} rootFolder - Root annotations folder
|
|
1336
|
+
* @param {string} workflowId - Workflow ID
|
|
1337
|
+
* @param {string[]} stepIds - Array of step IDs to include
|
|
1338
|
+
* @param {Object} options - Additional options
|
|
1339
|
+
* @returns {Promise<string>} - Path to the created zip file
|
|
1340
|
+
*/
|
|
1341
|
+
async function createTrainingDataZip(rootFolder, workflowId, stepIds, options = {}) {
|
|
1342
|
+
const { includeMetadata = true, onProgress = null, outputFileName = null } = options;
|
|
1343
|
+
try {
|
|
1344
|
+
console.log(`Creating training data zip for workflow: ${workflowId}`, 'create_training_zip');
|
|
1345
|
+
const sanitizedWorkflowId = sanitizeFolderName(workflowId);
|
|
1346
|
+
const workflowFolder = path.join(rootFolder, sanitizedWorkflowId);
|
|
1347
|
+
if (!fs.existsSync(workflowFolder)) {
|
|
1348
|
+
throw new Error(`Workflow folder does not exist: ${workflowFolder}`);
|
|
1349
|
+
}
|
|
1350
|
+
// Build list of step folders
|
|
1351
|
+
const stepFolders = stepIds.map(stepId => {
|
|
1352
|
+
const sanitizedStepId = sanitizeFolderName(stepId);
|
|
1353
|
+
return path.join(sanitizedWorkflowId, sanitizedStepId);
|
|
1354
|
+
});
|
|
1355
|
+
// Validate step folders exist
|
|
1356
|
+
const existingStepFolders = stepFolders.filter(stepFolder => {
|
|
1357
|
+
const fullPath = path.join(rootFolder, stepFolder);
|
|
1358
|
+
return fs.existsSync(fullPath);
|
|
1359
|
+
});
|
|
1360
|
+
if (existingStepFolders.length === 0) {
|
|
1361
|
+
throw new Error('No valid step folders found');
|
|
1362
|
+
}
|
|
1363
|
+
console_log_it(`Including ${existingStepFolders.length} step folders in zip`, 'create_training_zip');
|
|
1364
|
+
// Create zip file name with timestamp
|
|
1365
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
|
|
1366
|
+
const zipFileName = outputFileName || `training_data_${sanitizedWorkflowId}_${timestamp}`;
|
|
1367
|
+
// Create metadata file in temp location
|
|
1368
|
+
const tempMetadataPath = path.join(rootFolder, 'temp_metadata.json');
|
|
1369
|
+
fs.writeFileSync(tempMetadataPath, JSON.stringify({
|
|
1370
|
+
workflowId,
|
|
1371
|
+
stepIds,
|
|
1372
|
+
createdAt: new Date().toISOString(),
|
|
1373
|
+
stepCount: existingStepFolders.length
|
|
1374
|
+
}, null, 2));
|
|
1375
|
+
// Add metadata file to the folders list
|
|
1376
|
+
existingStepFolders.push('temp_metadata.json');
|
|
1377
|
+
// Create the zip
|
|
1378
|
+
const zipPath = await createZipFromFolders(rootFolder, existingStepFolders, zipFileName, {
|
|
1379
|
+
onProgress: (progress) => {
|
|
1380
|
+
if (onProgress) {
|
|
1381
|
+
onProgress({
|
|
1382
|
+
...progress,
|
|
1383
|
+
workflowId,
|
|
1384
|
+
stepCount: existingStepFolders.length
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
},
|
|
1388
|
+
filter: (filePath, stat) => {
|
|
1389
|
+
// Exclude temporary files and backups
|
|
1390
|
+
const fileName = path.basename(filePath);
|
|
1391
|
+
return !fileName.startsWith('temp_') &&
|
|
1392
|
+
!fileName.startsWith('.') &&
|
|
1393
|
+
!fileName.includes('_backup');
|
|
1394
|
+
}
|
|
1395
|
+
});
|
|
1396
|
+
// Clean up temp metadata file
|
|
1397
|
+
if (fs.existsSync(tempMetadataPath)) {
|
|
1398
|
+
fs.unlinkSync(tempMetadataPath);
|
|
1399
|
+
}
|
|
1400
|
+
return zipPath;
|
|
1401
|
+
}
|
|
1402
|
+
catch (error) {
|
|
1403
|
+
console_log_it(`Error creating training data zip: ${error.message}`, 'create_training_zip');
|
|
1404
|
+
throw error;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Add metadata file to an existing zip
|
|
1409
|
+
* @param {string} zipPath - Path to the zip file
|
|
1410
|
+
* @param {Object} metadata - Metadata object to add
|
|
1411
|
+
*/
|
|
1412
|
+
async function addMetadataToZip(zipPath, metadata) {
|
|
1413
|
+
return new Promise((resolve, reject) => {
|
|
1414
|
+
try {
|
|
1415
|
+
const tempMetadataPath = path.join(path.dirname(zipPath), 'temp_metadata.json');
|
|
1416
|
+
fs.writeFileSync(tempMetadataPath, JSON.stringify(metadata, null, 2));
|
|
1417
|
+
const output = fs.createWriteStream(zipPath + '.tmp');
|
|
1418
|
+
const archive = archiver('zip');
|
|
1419
|
+
// Extract existing zip and add metadata
|
|
1420
|
+
const extract = require('extract-zip');
|
|
1421
|
+
const tempDir = path.join(path.dirname(zipPath), 'temp_extract');
|
|
1422
|
+
// This is simplified - you might want to use a proper zip library
|
|
1423
|
+
// that supports adding files to existing archives
|
|
1424
|
+
console_log_it('Adding metadata to zip (simplified implementation)', 'add_metadata_zip');
|
|
1425
|
+
// Clean up temp files
|
|
1426
|
+
if (fs.existsSync(tempMetadataPath)) {
|
|
1427
|
+
fs.unlinkSync(tempMetadataPath);
|
|
1428
|
+
}
|
|
1429
|
+
resolve(undefined);
|
|
1430
|
+
}
|
|
1431
|
+
catch (error) {
|
|
1432
|
+
reject(error);
|
|
1433
|
+
}
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* Given { classes: string[], parents: Record<string, string|null> },
|
|
1438
|
+
* return the list of class names from root -> deepest child.
|
|
1439
|
+
*/
|
|
1440
|
+
function getRootToDeepestPath(data, depth = 10) {
|
|
1441
|
+
const { classes = [], parents = {} } = data ?? {};
|
|
1442
|
+
if (!Array.isArray(classes) || parents == null || typeof parents !== 'object') {
|
|
1443
|
+
throw new TypeError('Expected shape: { classes: string[], parents: Record<string, string|null> }');
|
|
1444
|
+
}
|
|
1445
|
+
// Ensure every class has an entry in parents
|
|
1446
|
+
for (const c of classes) {
|
|
1447
|
+
if (!(c in parents)) {
|
|
1448
|
+
throw new Error(`Missing parent mapping for "${c}"`);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
// The deepest child is the class that is never listed as anyone's parent
|
|
1452
|
+
const allParents = new Set(Object.values(parents).filter(Boolean));
|
|
1453
|
+
const leaves = classes.filter(c => !allParents.has(c));
|
|
1454
|
+
if (leaves.length !== 1) {
|
|
1455
|
+
throw new Error(`Expected exactly one deepest child, found ${leaves.length}: [${leaves.join(', ')}]`);
|
|
1456
|
+
}
|
|
1457
|
+
// Walk backwards from the leaf up to the root (null), then reverse.
|
|
1458
|
+
const path = [];
|
|
1459
|
+
const seen = new Set();
|
|
1460
|
+
let cur = leaves[0];
|
|
1461
|
+
while (cur) {
|
|
1462
|
+
if (seen.has(cur))
|
|
1463
|
+
throw new Error('Cycle detected in parents map');
|
|
1464
|
+
seen.add(cur);
|
|
1465
|
+
path.push(cur);
|
|
1466
|
+
cur = parents[cur] ?? null;
|
|
1467
|
+
}
|
|
1468
|
+
let res = path.reverse();
|
|
1469
|
+
res = res.slice(0, depth); // limit to max depth
|
|
1470
|
+
return res;
|
|
1471
|
+
}
|
|
1472
|
+
export function getFlowFrameSelectors(url, word) {
|
|
1473
|
+
for (let key in flow_frame_selectors.selectors) {
|
|
1474
|
+
if (key.startsWith(url)) {
|
|
1475
|
+
return flow_frame_selectors.selectors[key]?.[word]?.positions[0];
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
export function hasFlowFrameKey(url, word) {
|
|
1480
|
+
console.log(flow_frame_selectors);
|
|
1481
|
+
for (let key in flow_frame_selectors.selectors) {
|
|
1482
|
+
if (key.startsWith(url)) {
|
|
1483
|
+
url = key;
|
|
1484
|
+
break;
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
return !!flow_frame_selectors?.selectors?.[url]?.[word]?.positions?.length;
|
|
1488
|
+
}
|
|
1489
|
+
export function setFlowFrameText(url, dict) {
|
|
1490
|
+
flow_frame_text[url] = dict;
|
|
1491
|
+
}
|
|
1492
|
+
export function getFlowFrameText(url) {
|
|
1493
|
+
let result = {};
|
|
1494
|
+
for (let url_regex of Object.keys(flow_frame_text)) {
|
|
1495
|
+
console.log('testing', url_regex, url);
|
|
1496
|
+
if (url_regex.startsWith(url)) {
|
|
1497
|
+
result = flow_frame_text[url_regex];
|
|
1498
|
+
break;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
return result;
|
|
1502
|
+
}
|
|
1503
|
+
export function setFlowFrameSelector(location, selector) {
|
|
1504
|
+
for (let key in flow_frame_text) {
|
|
1505
|
+
if (key.startsWith(location)) {
|
|
1506
|
+
location = key;
|
|
1507
|
+
break;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
flow_frame_selectors.selectors[location] = selector;
|
|
1511
|
+
}
|
|
1512
|
+
const flow_frame_selectors = {
|
|
1513
|
+
selectors: {}
|
|
1514
|
+
};
|
|
1515
|
+
const flow_frame_text = {};
|
|
1516
|
+
export { get_jobs_path, findJobs, findVideoJobs, unique, set, get, setContext, getContext, refreshScreen, getModePositionKey, getModePosition, saveProjectFile, waitForQueuedRenders, readProjectFile, extractFileNameFromUrl, findExplorerWindowPosition, locateExtensionIcon, getDownloadsFolder, findSocialPorterVideos, ifYouDontFinish, getRootToDeepestPath, moveSocialPorterVideos, countFilesInDownloads, getFilesStartingWith, pressReturn, getFiles, getLastModifiedTimestampSync, findFileInDirectoryHasInWithAndEndingWith, deleteFileSync, downloadFiles, downloadFile, ensureDir, isThere, workflowOp, selectRandom, detectOrientation, getDetectOrientation, getMode, readJsonFiles, unzip, unzipFile, sanitizeFileName, alignLinesToWords, getVideoDuration, extractVersionNumber, waitFor, pasteText, getJobsContext, getItem, mouseClick, lerp, pause, getSortedFilesByUpdatedAt, createZipFromFolders, createTrainingDataZip, MIDJOURNEY_SELECTOR, input_bar, buildProgressBar, getLocalIp, applyTemplate, findFilesWithString, sanitizeFolderName };
|
|
1517
|
+
//# sourceMappingURL=utils.js.map
|