@web-auto/webauto 0.1.1 → 0.1.2
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/apps/desktop-console/default-settings.json +1 -0
- package/apps/desktop-console/dist/main/index.mjs +1618 -0
- package/apps/desktop-console/{src → dist}/main/preload.mjs +10 -0
- package/apps/desktop-console/dist/renderer/index.js +3063 -0
- package/apps/desktop-console/entry/ui-console.mjs +299 -0
- package/apps/webauto/entry/account.mjs +356 -0
- package/apps/webauto/entry/lib/account-detect.mjs +160 -0
- package/apps/webauto/entry/lib/account-store.mjs +587 -0
- package/apps/webauto/entry/lib/profilepool.mjs +1 -1
- package/apps/webauto/entry/xhs-install.mjs +27 -3
- package/apps/webauto/entry/xhs-status.mjs +152 -0
- package/apps/webauto/entry/xhs-unified.mjs +595 -17
- package/bin/webauto.mjs +247 -12
- package/dist/apps/webauto/server.js +66 -0
- package/dist/modules/camo-backend/src/index.js +575 -0
- package/dist/modules/camo-backend/src/internal/BrowserSession.js +817 -0
- package/dist/modules/camo-backend/src/internal/ElementRegistry.js +61 -0
- package/dist/modules/camo-backend/src/internal/ProfileLock.js +85 -0
- package/dist/modules/camo-backend/src/internal/SessionManager.js +172 -0
- package/dist/modules/camo-backend/src/internal/container-matcher.js +852 -0
- package/dist/modules/camo-backend/src/internal/engine-manager.js +258 -0
- package/dist/modules/camo-backend/src/internal/fingerprint.js +203 -0
- package/dist/modules/camo-backend/src/internal/pageRuntime.js +29 -0
- package/dist/modules/camo-backend/src/internal/runtimeInjector.js +30 -0
- package/dist/modules/camo-backend/src/internal/state-bus.js +46 -0
- package/dist/modules/camo-backend/src/internal/storage-paths.js +36 -0
- package/dist/modules/camo-backend/src/internal/ws-server.js +1202 -0
- package/dist/modules/camo-runtime/src/utils/browser-service.mjs +423 -0
- package/dist/modules/camo-runtime/src/utils/config.mjs +77 -0
- package/dist/modules/container-registry/src/index.js +184 -0
- package/dist/modules/logging/src/index.js +92 -0
- package/dist/modules/operations/src/builtin.js +27 -0
- package/dist/modules/operations/src/container-binding.js +75 -0
- package/dist/modules/operations/src/executor.js +146 -0
- package/dist/modules/operations/src/operations/click.js +167 -0
- package/dist/modules/operations/src/operations/extract.js +204 -0
- package/dist/modules/operations/src/operations/find-child.js +17 -0
- package/dist/modules/operations/src/operations/highlight.js +138 -0
- package/dist/modules/operations/src/operations/key.js +61 -0
- package/dist/modules/operations/src/operations/navigate.js +148 -0
- package/dist/modules/operations/src/operations/scroll.js +126 -0
- package/dist/modules/operations/src/operations/type.js +190 -0
- package/dist/modules/operations/src/queue.js +100 -0
- package/dist/modules/operations/src/registry.js +11 -0
- package/dist/modules/operations/src/system/mouse.js +33 -0
- package/dist/modules/state/src/atomic-json.js +33 -0
- package/dist/modules/workflow/blocks/AnchorVerificationBlock.js +71 -0
- package/dist/modules/workflow/blocks/BehaviorRandomizer.js +26 -0
- package/dist/modules/workflow/blocks/CallWorkflowBlock.js +38 -0
- package/dist/modules/workflow/blocks/CloseDetailBlock.js +209 -0
- package/dist/modules/workflow/blocks/CollectBatch.js +137 -0
- package/dist/modules/workflow/blocks/CollectCommentsBlock.js +415 -0
- package/dist/modules/workflow/blocks/CollectSearchListBlock.js +599 -0
- package/dist/modules/workflow/blocks/CollectWeiboPosts.js +229 -0
- package/dist/modules/workflow/blocks/DetectPageStateBlock.js +259 -0
- package/dist/modules/workflow/blocks/EnsureLoginBlock.js +162 -0
- package/dist/modules/workflow/blocks/EnsureSession.js +426 -0
- package/dist/modules/workflow/blocks/ErrorClassifier.js +164 -0
- package/dist/modules/workflow/blocks/ErrorRecoveryBlock.js +319 -0
- package/dist/modules/workflow/blocks/ExpandCommentsBlock.js +1032 -0
- package/dist/modules/workflow/blocks/ExtractDetailBlock.js +310 -0
- package/dist/modules/workflow/blocks/ExtractPostFields.js +88 -0
- package/dist/modules/workflow/blocks/GenerateSmartReplyBlock.js +68 -0
- package/dist/modules/workflow/blocks/GoToSearchBlock.js +497 -0
- package/dist/modules/workflow/blocks/GracefulFallbackBlock.js +104 -0
- package/dist/modules/workflow/blocks/HighlightBlock.js +66 -0
- package/dist/modules/workflow/blocks/InitAutoScroll.js +65 -0
- package/dist/modules/workflow/blocks/LoadContainerDefinition.js +50 -0
- package/dist/modules/workflow/blocks/LoadContainerIndex.js +43 -0
- package/dist/modules/workflow/blocks/LocateAndGuardBlock.js +176 -0
- package/dist/modules/workflow/blocks/LoginRecoveryBlock.js +242 -0
- package/dist/modules/workflow/blocks/MatchContainers.js +64 -0
- package/dist/modules/workflow/blocks/MonitoringBlock.js +190 -0
- package/dist/modules/workflow/blocks/OpenDetailBlock.js +1240 -0
- package/dist/modules/workflow/blocks/OrganizeXhsNotesBlock.js +117 -0
- package/dist/modules/workflow/blocks/PersistXhsNoteBlock.js +270 -0
- package/dist/modules/workflow/blocks/PickSinglePost.js +69 -0
- package/dist/modules/workflow/blocks/ProgressTracker.js +125 -0
- package/dist/modules/workflow/blocks/RecordFixtureBlock.js +44 -0
- package/dist/modules/workflow/blocks/RenderMarkdown.js +48 -0
- package/dist/modules/workflow/blocks/SaveFile.js +54 -0
- package/dist/modules/workflow/blocks/ScrollNextBatch.js +72 -0
- package/dist/modules/workflow/blocks/SessionHealthBlock.js +73 -0
- package/dist/modules/workflow/blocks/StartBrowserService.js +45 -0
- package/dist/modules/workflow/blocks/ValidateContainerDefinition.js +67 -0
- package/dist/modules/workflow/blocks/ValidateExtract.js +35 -0
- package/dist/modules/workflow/blocks/WaitSearchPermitBlock.js +162 -0
- package/dist/modules/workflow/blocks/WaitStable.js +74 -0
- package/dist/modules/workflow/blocks/WarmupCommentsBlock.js +120 -0
- package/dist/modules/workflow/blocks/WorkflowExecutor.js +156 -0
- package/dist/modules/workflow/blocks/XiaohongshuCollectFromLinksBlock.js +1004 -0
- package/dist/modules/workflow/blocks/XiaohongshuCollectLinksBlock.js +1049 -0
- package/dist/modules/workflow/blocks/XiaohongshuFullCollectBlock.js +782 -0
- package/dist/modules/workflow/blocks/helpers/anchorVerify.js +198 -0
- package/dist/modules/workflow/blocks/helpers/asyncWorkQueue.js +53 -0
- package/dist/modules/workflow/blocks/helpers/commentScroller.js +334 -0
- package/dist/modules/workflow/blocks/helpers/commentSectionLocator.js +126 -0
- package/dist/modules/workflow/blocks/helpers/containerAnchors.js +301 -0
- package/dist/modules/workflow/blocks/helpers/debugArtifacts.js +6 -0
- package/dist/modules/workflow/blocks/helpers/downloadPaths.js +29 -0
- package/dist/modules/workflow/blocks/helpers/expandCommentsController.js +53 -0
- package/dist/modules/workflow/blocks/helpers/expandCommentsExtractor.js +129 -0
- package/dist/modules/workflow/blocks/helpers/macosVisionOcrPlugin.js +116 -0
- package/dist/modules/workflow/blocks/helpers/mergeXhsMarkdown.js +109 -0
- package/dist/modules/workflow/blocks/helpers/openDetailController.js +56 -0
- package/dist/modules/workflow/blocks/helpers/openDetailTypes.js +7 -0
- package/dist/modules/workflow/blocks/helpers/openDetailViewport.js +474 -0
- package/dist/modules/workflow/blocks/helpers/openDetailWaiter.js +104 -0
- package/dist/modules/workflow/blocks/helpers/operationLogger.js +195 -0
- package/dist/modules/workflow/blocks/helpers/persistedNotes.js +107 -0
- package/dist/modules/workflow/blocks/helpers/replyExpander.js +260 -0
- package/dist/modules/workflow/blocks/helpers/scrollIntoView.js +138 -0
- package/dist/modules/workflow/blocks/helpers/searchExecutor.js +328 -0
- package/dist/modules/workflow/blocks/helpers/searchGate.js +46 -0
- package/dist/modules/workflow/blocks/helpers/searchPageState.js +164 -0
- package/dist/modules/workflow/blocks/helpers/searchResultWaiter.js +64 -0
- package/dist/modules/workflow/blocks/helpers/simpleAnchor.js +134 -0
- package/dist/modules/workflow/blocks/helpers/smartReply.js +40 -0
- package/dist/modules/workflow/blocks/helpers/systemInput.js +635 -0
- package/dist/modules/workflow/blocks/helpers/targetCountMode.js +9 -0
- package/dist/modules/workflow/blocks/helpers/xhsCliArgs.js +80 -0
- package/dist/modules/workflow/blocks/helpers/xhsCommentDom.js +805 -0
- package/dist/modules/workflow/blocks/helpers/xhsNoteOrganizer.js +140 -0
- package/dist/modules/workflow/blocks/restore/RestorePhaseBlock.js +204 -0
- package/dist/modules/workflow/config/workflowRegistry.js +32 -0
- package/dist/modules/workflow/definitions/batch-collect-workflow.js +63 -0
- package/dist/modules/workflow/definitions/scroll-extract-workflow.js +74 -0
- package/dist/modules/workflow/definitions/xiaohongshu-collect-workflow-v2.js +81 -0
- package/dist/modules/workflow/definitions/xiaohongshu-collect-workflow.js +57 -0
- package/dist/modules/workflow/definitions/xiaohongshu-full-collect-workflow-v3.js +68 -0
- package/dist/modules/workflow/definitions/xiaohongshu-note-collect.js +49 -0
- package/dist/modules/workflow/definitions/xiaohongshu-phase1-workflow-v3.js +30 -0
- package/dist/modules/workflow/definitions/xiaohongshu-phase2-links-workflow-v3.js +40 -0
- package/dist/modules/workflow/definitions/xiaohongshu-phase3-collect-workflow-v1.js +54 -0
- package/dist/modules/workflow/definitions/xiaohongshu-phase34-from-links-workflow-v3.js +25 -0
- package/dist/modules/workflow/src/WeiboEventDrivenWorkflowRunner.js +308 -0
- package/dist/modules/workflow/src/context.js +70 -0
- package/dist/modules/workflow/src/index.js +5 -0
- package/dist/modules/workflow/src/orchestrator.js +230 -0
- package/dist/modules/workflow/src/runner.js +55 -0
- package/dist/modules/workflow/src/runtime.js +70 -0
- package/dist/modules/workflow/workflows/WeiboFeedExtractionWorkflow.js +359 -0
- package/dist/modules/workflow/workflows/XiaohongshuLoginWorkflow.js +110 -0
- package/dist/modules/xiaohongshu/app/src/blocks/MatchCommentsBlock.js +139 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.js +36 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.js +213 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.js +121 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.js +1249 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase2SearchBlock.js +703 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.js +41 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.js +44 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.js +150 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.js +117 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.js +102 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.js +109 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +117 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.js +114 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.js +90 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +1009 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.js +233 -0
- package/dist/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +291 -0
- package/dist/modules/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.js +240 -0
- package/dist/modules/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.js +126 -0
- package/dist/modules/xiaohongshu/app/src/blocks/helpers/commentMatcher.js +99 -0
- package/dist/modules/xiaohongshu/app/src/blocks/helpers/evidence.js +27 -0
- package/dist/modules/xiaohongshu/app/src/blocks/helpers/sharding.js +42 -0
- package/dist/modules/xiaohongshu/app/src/blocks/helpers/xhsComments.js +270 -0
- package/dist/modules/xiaohongshu/app/src/index.js +9 -0
- package/dist/modules/xiaohongshu/app/src/utils/checkpoints.js +222 -0
- package/dist/modules/xiaohongshu/app/src/utils/controllerAction.js +43 -0
- package/dist/services/controller/src/controller.js +1476 -0
- package/dist/services/controller/src/index.js +2 -0
- package/dist/services/controller/src/payload-normalizer.js +129 -0
- package/dist/services/shared/heartbeat.js +120 -0
- package/dist/services/shared/lib/errorHandler.js +2 -0
- package/dist/services/shared/serviceProcessLogger.js +139 -0
- package/dist/services/unified-api/RemoteBrowserSession.js +176 -0
- package/dist/services/unified-api/RemoteSessionManager.js +148 -0
- package/dist/services/unified-api/container-operations-handler.js +115 -0
- package/dist/services/unified-api/server.js +652 -0
- package/dist/services/unified-api/state-registry.js +274 -0
- package/dist/services/unified-api/task-persistence.js +66 -0
- package/dist/services/unified-api/task-state.js +130 -0
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +12 -5
- package/modules/xiaohongshu/app/pnpm-lock.yaml +24 -0
- package/package.json +37 -9
- package/.beads/README.md +0 -81
- package/.beads/config.yaml +0 -67
- package/.beads/interactions.jsonl +0 -0
- package/.beads/issues.jsonl +0 -180
- package/.beads/metadata.json +0 -4
- package/.claude/settings.local.json +0 -10
- package/.github/workflows/ci.yml +0 -55
- package/AGENTS.md +0 -253
- package/apps/desktop-console/README.md +0 -27
- package/apps/desktop-console/package-lock.json +0 -897
- package/apps/desktop-console/package.json +0 -20
- package/apps/desktop-console/scripts/build-and-install.mjs +0 -19
- package/apps/desktop-console/scripts/build.mjs +0 -45
- package/apps/desktop-console/scripts/test-preload.mjs +0 -13
- package/apps/desktop-console/src/main/config.mts +0 -26
- package/apps/desktop-console/src/main/core-daemon-manager.mts +0 -131
- package/apps/desktop-console/src/main/desktop-settings.mts +0 -267
- package/apps/desktop-console/src/main/heartbeat-watchdog.mts +0 -50
- package/apps/desktop-console/src/main/heartbeat-watchdog.test.mts +0 -68
- package/apps/desktop-console/src/main/index-streaming.test.mts +0 -20
- package/apps/desktop-console/src/main/index.mts +0 -980
- package/apps/desktop-console/src/main/profile-store.mts +0 -239
- package/apps/desktop-console/src/main/profile-store.test.mts +0 -54
- package/apps/desktop-console/src/main/state-bridge.mts +0 -114
- package/apps/desktop-console/src/main/task-state-types.ts +0 -32
- package/apps/desktop-console/src/renderer/hooks/use-task-state.mts +0 -120
- package/apps/desktop-console/src/renderer/index.mts +0 -133
- package/apps/desktop-console/src/renderer/index.test.mts +0 -34
- package/apps/desktop-console/src/renderer/path-helpers.mts +0 -46
- package/apps/desktop-console/src/renderer/path-helpers.test.mts +0 -14
- package/apps/desktop-console/src/renderer/tabs/debug.mts +0 -48
- package/apps/desktop-console/src/renderer/tabs/debug.test.mts +0 -22
- package/apps/desktop-console/src/renderer/tabs/logs.mts +0 -421
- package/apps/desktop-console/src/renderer/tabs/logs.test.mts +0 -27
- package/apps/desktop-console/src/renderer/tabs/preflight.mts +0 -486
- package/apps/desktop-console/src/renderer/tabs/preflight.test.mts +0 -33
- package/apps/desktop-console/src/renderer/tabs/profile-pool.mts +0 -213
- package/apps/desktop-console/src/renderer/tabs/results.mts +0 -171
- package/apps/desktop-console/src/renderer/tabs/run.test.mts +0 -63
- package/apps/desktop-console/src/renderer/tabs/runtime.mts +0 -151
- package/apps/desktop-console/src/renderer/tabs/settings.mts +0 -146
- package/apps/desktop-console/src/renderer/tabs/xiaohongshu/account-flow.mts +0 -486
- package/apps/desktop-console/src/renderer/tabs/xiaohongshu/guide-browser-check.mts +0 -56
- package/apps/desktop-console/src/renderer/tabs/xiaohongshu/helpers.mts +0 -262
- package/apps/desktop-console/src/renderer/tabs/xiaohongshu/layout-block.mts +0 -430
- package/apps/desktop-console/src/renderer/tabs/xiaohongshu/live-stats.mts +0 -847
- package/apps/desktop-console/src/renderer/tabs/xiaohongshu/run-flow.mts +0 -443
- package/apps/desktop-console/src/renderer/tabs/xiaohongshu-state.mts +0 -425
- package/apps/desktop-console/src/renderer/tabs/xiaohongshu.mts +0 -497
- package/apps/desktop-console/src/renderer/tabs/xiaohongshu.test.mts +0 -291
- package/apps/desktop-console/src/renderer/ui-components.mts +0 -31
- package/docs/README_camoufox_chinese.md +0 -141
- package/docs/USAGE_V3.md +0 -163
- package/docs/arch/OCR_MACOS_PLUGIN.md +0 -39
- package/docs/arch/PORTS.md +0 -40
- package/docs/arch/REGRESSION_CHECKLIST.md +0 -121
- package/docs/arch/SEARCH_GATE.md +0 -224
- package/docs/arch/VIEWPORT_SAFETY.md +0 -182
- package/docs/arch/XIAOHONGSHU_OFFLINE_MOCK_DESIGN.md +0 -267
- package/docs/xiaohongshu-container-driven-summary.md +0 -221
- package/docs/xiaohongshu-full-collect-runbook.md +0 -134
- package/docs/xiaohongshu-next-steps.md +0 -228
- package/docs/xiaohongshu-quickstart.md +0 -73
- package/docs/xiaohongshu-workflow-summary.md +0 -227
- package/modules/container-registry/tests/container-registry.test.ts +0 -16
- package/modules/logging/tests/logging.test.ts +0 -38
- package/modules/operations/tests/operations.test.ts +0 -22
- package/modules/operations/tests/viewport-filter.test.ts +0 -161
- package/modules/operations/tests/visible-only.test.ts +0 -250
- package/modules/session-manager/tests/session-manager.test.ts +0 -23
- package/modules/state/src/atomic-json.test.ts +0 -30
- package/modules/state/src/paths.test.ts +0 -59
- package/modules/state/src/xiaohongshu-collect-state.test.ts +0 -259
- package/modules/workflow/blocks/AnchorVerificationBlock.d.ts.map +0 -1
- package/modules/workflow/blocks/AnchorVerificationBlock.js.map +0 -1
- package/modules/workflow/blocks/DetectPageStateBlock.d.ts.map +0 -1
- package/modules/workflow/blocks/DetectPageStateBlock.js.map +0 -1
- package/modules/workflow/blocks/ErrorRecoveryBlock.d.ts.map +0 -1
- package/modules/workflow/blocks/ErrorRecoveryBlock.js.map +0 -1
- package/modules/workflow/blocks/WaitSearchPermitBlock.d.ts.map +0 -1
- package/modules/workflow/blocks/WaitSearchPermitBlock.js.map +0 -1
- package/modules/workflow/blocks/helpers/containerAnchors.d.ts.map +0 -1
- package/modules/workflow/blocks/helpers/containerAnchors.js.map +0 -1
- package/modules/workflow/blocks/helpers/downloadPaths.test.ts +0 -62
- package/modules/workflow/blocks/helpers/mergeXhsMarkdown.test.ts +0 -121
- package/modules/workflow/blocks/helpers/operationLogger.d.ts.map +0 -1
- package/modules/workflow/blocks/helpers/operationLogger.js.map +0 -1
- package/modules/workflow/blocks/helpers/persistedNotes.test.ts +0 -268
- package/modules/workflow/blocks/helpers/searchPageState.d.ts.map +0 -1
- package/modules/workflow/blocks/helpers/searchPageState.js.map +0 -1
- package/modules/workflow/blocks/helpers/targetCountMode.test.ts +0 -29
- package/modules/workflow/blocks/helpers/xhsCliArgs.test.ts +0 -75
- package/modules/workflow/tests/smartReply.test.ts +0 -32
- package/modules/xiaohongshu/app/src/blocks/Phase3Interact.matcher.test.ts +0 -33
- package/modules/xiaohongshu/app/src/utils/__tests__/checkpoints.test.ts +0 -141
- package/modules/xiaohongshu/app/tests/commentMatchDsl.test.ts +0 -50
- package/modules/xiaohongshu/app/tests/commentMatcher.test.ts +0 -46
- package/modules/xiaohongshu/app/tests/sharding.test.ts +0 -31
- package/package-scripts.json +0 -8
- package/runtime/infra/utils/README.md +0 -13
- package/runtime/infra/utils/scripts/README.md +0 -0
- package/runtime/infra/utils/scripts/development/eval-in-session.mjs +0 -40
- package/runtime/infra/utils/scripts/development/highlight-search-containers.mjs +0 -35
- package/runtime/infra/utils/scripts/service/kill-port.mjs +0 -24
- package/runtime/infra/utils/scripts/service/start-api.mjs +0 -39
- package/runtime/infra/utils/scripts/service/start-browser-service.mjs +0 -106
- package/runtime/infra/utils/scripts/service/stop-api.mjs +0 -18
- package/runtime/infra/utils/scripts/service/stop-browser-service.mjs +0 -104
- package/runtime/infra/utils/scripts/test-services.mjs +0 -94
- package/services/shared/heartbeat.test.ts +0 -102
- package/services/unified-api/__tests__/task-state.test.ts +0 -95
- package/sitecustomize.py +0 -19
- package/tests/README.md +0 -194
- package/tests/e2e/workflows/weibo-feed-extraction.test.ts +0 -171
- package/tests/fixtures/data/container-definitions.json +0 -67
- package/tests/fixtures/pages/simple-page.html +0 -69
- package/tests/integration/01-test-container-match.mjs +0 -188
- package/tests/integration/02-test-dom-branch.mjs +0 -161
- package/tests/integration/03-test-container-operation-system.mjs +0 -91
- package/tests/integration/05-test-container-lifecycle-events.mjs +0 -224
- package/tests/integration/05-test-container-lifecycle-with-events.mjs +0 -250
- package/tests/integration/06-test-container-dom-tree-drawing.mjs +0 -256
- package/tests/integration/07-test-weibo-container-lifecycle.mjs +0 -355
- package/tests/integration/08-test-weibo-feed-workflow.test.mjs +0 -164
- package/tests/integration/10-test-visual-analyzer.mjs +0 -312
- package/tests/integration/11-test-visual-loop.mjs +0 -284
- package/tests/integration/12-test-simple-visual-loop.mjs +0 -242
- package/tests/integration/13-test-visual-robust.mjs +0 -185
- package/tests/integration/14-test-visual-highlight-loop.mjs +0 -271
- package/tests/integration/inspect-page.mjs +0 -50
- package/tests/integration/run-all-tests.mjs +0 -95
- package/tests/patch_verification/CODEX_PATCH_TEST.md +0 -103
- package/tests/patch_verification/PHASE2_ANALYSIS.md +0 -179
- package/tests/patch_verification/PHASE2_OPTIMIZATION_REPORT.md +0 -55
- package/tests/patch_verification/PHASE2_TO_PHASE4_SUMMARY.md +0 -126
- package/tests/patch_verification/QUICK_TEST_SEQUENCE.md +0 -262
- package/tests/patch_verification/README.md +0 -143
- package/tests/patch_verification/RUN_TESTS.md +0 -60
- package/tests/patch_verification/TEST_EXECUTION.md +0 -99
- package/tests/patch_verification/TEST_PLAN.md +0 -328
- package/tests/patch_verification/TEST_RESULTS.md +0 -34
- package/tests/patch_verification/TOOL_TEST_PLAN.md +0 -48
- package/tests/patch_verification/run-tool-test.mjs +0 -121
- package/tests/patch_verification/temp_test_files/test01.txt +0 -1
- package/tests/patch_verification/temp_test_files/test02.txt +0 -3
- package/tests/patch_verification/temp_test_files/test02_gnu.txt +0 -3
- package/tests/patch_verification/temp_test_files/test03.txt +0 -1
- package/tests/patch_verification/temp_test_files/test03_multiline.txt +0 -5
- package/tests/patch_verification/temp_test_files/test04_function.ts +0 -5
- package/tests/patch_verification/temp_test_files/test05_import.ts +0 -4
- package/tests/patch_verification/temp_test_files/test06_special_chars.txt +0 -4
- package/tests/patch_verification/temp_test_files/test07_indentation.ts +0 -5
- package/tests/patch_verification/temp_test_files/test08_mismatch.txt +0 -1
- package/tests/patch_verification/temp_test_files/test_add_02.txt +0 -3
- package/tests/patch_verification/temp_test_files/test_simple.txt +0 -1
- package/tests/runner/TestReporter.mjs +0 -57
- package/tests/runner/TestRunner.mjs +0 -244
- package/tests/unit/commands/profile.test.mjs +0 -10
- package/tests/unit/container/change-notifier.test.mjs +0 -181
- package/tests/unit/lifecycle/session-registry.test.mjs +0 -135
- package/tests/unit/operations/registry.test.ts +0 -73
- package/tests/unit/utils/browser-service.test.mjs +0 -153
- package/tests/unit/utils/config.test.mjs +0 -166
- package/tests/unit/utils/fingerprint.test.mjs +0 -166
- package/tsconfig.json +0 -31
- package/tsconfig.services.json +0 -26
- /package/apps/desktop-console/{src → dist}/renderer/index.html +0 -0
- /package/apps/desktop-console/{src/renderer/tabs → dist/renderer}/run.mts +0 -0
|
@@ -0,0 +1,3063 @@
|
|
|
1
|
+
// src/renderer/ui-components.mts
|
|
2
|
+
function createEl(tag, props = {}, children = []) {
|
|
3
|
+
const el = document.createElement(tag);
|
|
4
|
+
for (const [k, v] of Object.entries(props)) {
|
|
5
|
+
el[k] = v;
|
|
6
|
+
}
|
|
7
|
+
for (const c of children) {
|
|
8
|
+
if (typeof c === "string") el.appendChild(document.createTextNode(c));
|
|
9
|
+
else el.appendChild(c);
|
|
10
|
+
}
|
|
11
|
+
return el;
|
|
12
|
+
}
|
|
13
|
+
function labeledInput(label, input) {
|
|
14
|
+
const wrap = createEl("div", { style: "display:flex; flex-direction:column; gap:6px; min-width:140px; max-width:260px;" });
|
|
15
|
+
wrap.appendChild(createEl("label", {}, [label]));
|
|
16
|
+
wrap.appendChild(input);
|
|
17
|
+
return wrap;
|
|
18
|
+
}
|
|
19
|
+
function section(title, children) {
|
|
20
|
+
const card = createEl("div", { className: "card" });
|
|
21
|
+
card.appendChild(createEl("div", { style: "font-weight:700; margin-bottom:10px;" }, [title]));
|
|
22
|
+
children.forEach((c) => card.appendChild(c));
|
|
23
|
+
return card;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/renderer/path-helpers.mts
|
|
27
|
+
function normalizePath(p, api) {
|
|
28
|
+
if (!p) return "";
|
|
29
|
+
if (api?.pathNormalize) return String(api.pathNormalize(p));
|
|
30
|
+
return String(p);
|
|
31
|
+
}
|
|
32
|
+
function joinPath(api, ...parts) {
|
|
33
|
+
if (api?.pathJoin) return String(api.pathJoin(...parts));
|
|
34
|
+
const sep = api?.pathSep || "/";
|
|
35
|
+
return parts.filter(Boolean).join(sep);
|
|
36
|
+
}
|
|
37
|
+
function resolveWebautoRoot(downloadRoot, api) {
|
|
38
|
+
const raw = String(downloadRoot || "").trim();
|
|
39
|
+
if (!raw) {
|
|
40
|
+
return api?.pathSep === "\\" ? "%USERPROFILE%\\.webauto" : "~/.webauto";
|
|
41
|
+
}
|
|
42
|
+
const normalized = normalizePath(raw, api);
|
|
43
|
+
const sep = api?.pathSep || "/";
|
|
44
|
+
const suffix = `${sep}download`;
|
|
45
|
+
if (normalized.toLowerCase().endsWith(suffix.toLowerCase())) {
|
|
46
|
+
return normalized.slice(0, -suffix.length);
|
|
47
|
+
}
|
|
48
|
+
return normalized;
|
|
49
|
+
}
|
|
50
|
+
function resolveConfigPath(downloadRoot, api) {
|
|
51
|
+
const root = resolveWebautoRoot(downloadRoot, api);
|
|
52
|
+
return joinPath(api, root, "config.json");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/renderer/tabs/preflight.mts
|
|
56
|
+
function buildArgs(parts) {
|
|
57
|
+
return parts.filter((x) => x != null && String(x).trim() !== "");
|
|
58
|
+
}
|
|
59
|
+
var XHS_NAV_TARGET_KEY = "webauto.xhs.navTarget.v1";
|
|
60
|
+
function renderPreflight(root, ctx2) {
|
|
61
|
+
const filterInput = createEl("input", { value: "", placeholder: "\u8FC7\u6EE4\uFF1AprofileId / alias / platform" });
|
|
62
|
+
const selectAllBox = createEl("input", { type: "checkbox" });
|
|
63
|
+
const selectionHint = createEl("div", { className: "muted" }, ["selected=0"]);
|
|
64
|
+
const onlyMissingFp = createEl("input", { type: "checkbox" });
|
|
65
|
+
const regenPlatform = createEl("select");
|
|
66
|
+
[
|
|
67
|
+
{ value: "random", label: "random(win/mac)" },
|
|
68
|
+
{ value: "windows", label: "windows" },
|
|
69
|
+
{ value: "macos", label: "macos" }
|
|
70
|
+
].forEach((x) => regenPlatform.appendChild(createEl("option", { value: x.value }, [x.label])));
|
|
71
|
+
regenPlatform.value = "random";
|
|
72
|
+
const statusBox = createEl("div", { className: "muted" }, [""]);
|
|
73
|
+
const listBox = createEl("div", { className: "list" });
|
|
74
|
+
const batchDeleteBtn = createEl("button", { className: "danger" }, ["\u6279\u91CF\u5220\u9664\u9009\u4E2D profile"]);
|
|
75
|
+
const batchDeleteFp = createEl("input", { type: "checkbox" });
|
|
76
|
+
const onboardingSummary = createEl("div", { className: "muted" }, ["\u6B63\u5728\u52A0\u8F7D profile \u4FE1\u606F..."]);
|
|
77
|
+
const onboardingTips = createEl("div", { className: "muted", style: "font-size:12px; margin-top:6px;" }, [
|
|
78
|
+
"\u9996\u6B21\u4F7F\u7528\u5EFA\u8BAE\uFF1A\u8F93\u5165\u8D26\u53F7\u540D\u540E\uFF0C\u7CFB\u7EDF\u81EA\u52A8\u6309 <\u8D26\u53F7\u540D>-batch-1/2/3 \u547D\u540D\uFF1B\u7559\u7A7A\u9ED8\u8BA4 xiaohongshu-batch-1/2/3\u3002\u767B\u5F55\u540E\u53EF\u8BBE\u7F6E alias\uFF08\u8D26\u53F7\u540D\uFF09\u7528\u4E8E\u533A\u5206\uFF0C\u9ED8\u8BA4\u4F1A\u81EA\u52A8\u83B7\u53D6\u7528\u6237\u540D\u3002"
|
|
79
|
+
]);
|
|
80
|
+
const gotoXhsBtn = createEl("button", { className: "secondary", type: "button" }, ["\u53BB\u5C0F\u7EA2\u4E66\u9996\u9875"]);
|
|
81
|
+
const browserStatus = createEl("div", { className: "muted" }, ["\u6D4F\u89C8\u5668\u72B6\u6001\uFF1A\u672A\u68C0\u67E5"]);
|
|
82
|
+
const browserCheckBtn = createEl("button", { className: "secondary", type: "button" }, ["\u68C0\u67E5\u6D4F\u89C8\u5668/\u4F9D\u8D56"]);
|
|
83
|
+
const browserDownloadBtn = createEl("button", { type: "button" }, ["\u4E0B\u8F7D Camoufox"]);
|
|
84
|
+
let cachedScan = null;
|
|
85
|
+
const webautoRoot = resolveWebautoRoot(ctx2.settings?.downloadRoot || "", window.api);
|
|
86
|
+
function getAliasMap() {
|
|
87
|
+
const m = ctx2.settings?.profileAliases;
|
|
88
|
+
return m && typeof m === "object" ? m : {};
|
|
89
|
+
}
|
|
90
|
+
async function saveAlias(profileId, alias) {
|
|
91
|
+
const nextAliases = { ...getAliasMap() };
|
|
92
|
+
const key = String(profileId || "").trim();
|
|
93
|
+
const val = String(alias || "").trim();
|
|
94
|
+
if (!key) return;
|
|
95
|
+
if (!val) delete nextAliases[key];
|
|
96
|
+
else nextAliases[key] = val;
|
|
97
|
+
const next = await window.api.settingsSet({ profileAliases: nextAliases });
|
|
98
|
+
ctx2.settings = next;
|
|
99
|
+
}
|
|
100
|
+
function toLower(x) {
|
|
101
|
+
return String(x || "").toLowerCase();
|
|
102
|
+
}
|
|
103
|
+
function renderList() {
|
|
104
|
+
listBox.textContent = "";
|
|
105
|
+
const entries = cachedScan?.entries || [];
|
|
106
|
+
const q = toLower(filterInput.value.trim());
|
|
107
|
+
const aliases = getAliasMap();
|
|
108
|
+
const filtered = entries.filter((e) => {
|
|
109
|
+
if (onlyMissingFp.checked && e.fingerprint) return false;
|
|
110
|
+
if (!q) return true;
|
|
111
|
+
const hay = [
|
|
112
|
+
e.profileId,
|
|
113
|
+
aliases[e.profileId] || "",
|
|
114
|
+
e.fingerprint?.platform || "",
|
|
115
|
+
e.fingerprint?.originalPlatform || "",
|
|
116
|
+
e.fingerprint?.osVersion || "",
|
|
117
|
+
e.fingerprint?.userAgent || ""
|
|
118
|
+
].map(toLower).join(" ");
|
|
119
|
+
return hay.includes(q);
|
|
120
|
+
});
|
|
121
|
+
const header = createEl("div", {
|
|
122
|
+
className: "item",
|
|
123
|
+
// One-row-per-profile, keep actions on a single line; avoid horizontal scroll.
|
|
124
|
+
style: "display:grid; grid-template-columns: 32px 180px 180px 120px minmax(0,1fr) 360px; gap:8px; font-weight:700; align-items:center; min-width:0;"
|
|
125
|
+
});
|
|
126
|
+
header.appendChild(createEl("div", {}, ["sel"]));
|
|
127
|
+
header.appendChild(createEl("div", {}, ["profileId"]));
|
|
128
|
+
header.appendChild(createEl("div", {}, ["alias"]));
|
|
129
|
+
header.appendChild(createEl("div", {}, ["fingerprint"]));
|
|
130
|
+
header.appendChild(createEl("div", {}, ["userAgent"]));
|
|
131
|
+
header.appendChild(createEl("div", {}, ["actions"]));
|
|
132
|
+
listBox.appendChild(header);
|
|
133
|
+
for (const e of filtered) {
|
|
134
|
+
const fp = e.fingerprint;
|
|
135
|
+
const fpLabel = fp ? `${fp.originalPlatform || fp.platform || "unknown"}${fp.osVersion ? ` ${fp.osVersion}` : ""}` : "(missing)";
|
|
136
|
+
const aliasInput = createEl("input", {
|
|
137
|
+
value: String(aliases[e.profileId] || ""),
|
|
138
|
+
placeholder: "\u8D26\u53F7\u540D\uFF08alias\uFF0C\u7528\u4E8E\u533A\u5206\u8D26\u53F7\uFF0C\u9ED8\u8BA4\u767B\u5F55\u540E\u83B7\u53D6\u7528\u6237\u540D\uFF09"
|
|
139
|
+
});
|
|
140
|
+
const row = createEl("div", {
|
|
141
|
+
className: "item",
|
|
142
|
+
style: "display:grid; grid-template-columns: 32px 180px 180px 120px minmax(0,1fr) 360px; gap:8px; align-items:center; min-width:0;"
|
|
143
|
+
});
|
|
144
|
+
const rowSelect = createEl("input", { type: "checkbox" });
|
|
145
|
+
rowSelect.dataset.profileId = String(e.profileId || "");
|
|
146
|
+
rowSelect.onchange = () => updateSelectionHint();
|
|
147
|
+
row.appendChild(rowSelect);
|
|
148
|
+
row.appendChild(createEl("div", {}, [String(e.profileId)]));
|
|
149
|
+
row.appendChild(createEl("div", { style: "min-width:0;" }, [aliasInput]));
|
|
150
|
+
row.appendChild(createEl("div", { className: "muted" }, [fpLabel]));
|
|
151
|
+
row.appendChild(
|
|
152
|
+
createEl(
|
|
153
|
+
"div",
|
|
154
|
+
{ className: "muted", style: "overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0;", title: String(fp?.userAgent || "") },
|
|
155
|
+
[String(fp?.userAgent || "")]
|
|
156
|
+
)
|
|
157
|
+
);
|
|
158
|
+
const actions = createEl("div", { style: "display:flex; gap:6px; flex-wrap:nowrap; align-items:center; overflow:hidden; justify-content:flex-end;" });
|
|
159
|
+
const btnOpenProfile = createEl("button", { className: "secondary" }, ["\u6253\u5F00"]);
|
|
160
|
+
const btnOpenFp = createEl("button", { className: "secondary" }, ["\u6307\u7EB9"]);
|
|
161
|
+
const btnSaveAlias = createEl("button", { className: "secondary" }, ["\u4FDD\u5B58"]);
|
|
162
|
+
const btnRegenFp = createEl("button", {}, ["\u91CD\u751F"]);
|
|
163
|
+
const btnDelFp = createEl("button", { className: "secondary" }, ["\u5220\u6307"]);
|
|
164
|
+
const btnDelProfile = createEl("button", { className: "danger" }, ["\u5220\u6863"]);
|
|
165
|
+
btnOpenProfile.onclick = () => void window.api.osOpenPath(e.profileDir);
|
|
166
|
+
btnOpenFp.onclick = () => void window.api.osOpenPath(e.fingerprintPath);
|
|
167
|
+
let lastSavedAlias = String(aliases[e.profileId] || "").trim();
|
|
168
|
+
const commitAlias = async () => {
|
|
169
|
+
const nextAlias = String(aliasInput.value || "").trim();
|
|
170
|
+
if (nextAlias === lastSavedAlias) return;
|
|
171
|
+
await saveAlias(e.profileId, nextAlias);
|
|
172
|
+
lastSavedAlias = nextAlias;
|
|
173
|
+
aliasInput.style.borderColor = "";
|
|
174
|
+
};
|
|
175
|
+
aliasInput.oninput = () => {
|
|
176
|
+
const dirty = String(aliasInput.value || "").trim() !== lastSavedAlias;
|
|
177
|
+
aliasInput.style.borderColor = dirty ? "#2b67ff" : "";
|
|
178
|
+
};
|
|
179
|
+
btnSaveAlias.onclick = () => void commitAlias();
|
|
180
|
+
aliasInput.onkeydown = (ev) => {
|
|
181
|
+
if (ev.key === "Enter") void commitAlias();
|
|
182
|
+
};
|
|
183
|
+
aliasInput.onblur = () => void commitAlias();
|
|
184
|
+
btnRegenFp.onclick = async () => {
|
|
185
|
+
ctx2.clearLog();
|
|
186
|
+
const platform = String(regenPlatform.value || "random");
|
|
187
|
+
const out = await window.api.fingerprintRegenerate({ profileId: e.profileId, platform });
|
|
188
|
+
ctx2.appendLog(JSON.stringify(out, null, 2));
|
|
189
|
+
await refreshScan();
|
|
190
|
+
};
|
|
191
|
+
btnDelFp.onclick = async () => {
|
|
192
|
+
if (!confirm(`\u5220\u9664 fingerprint: ${e.profileId}?`)) return;
|
|
193
|
+
ctx2.clearLog();
|
|
194
|
+
const out = await window.api.fingerprintDelete({ profileId: e.profileId });
|
|
195
|
+
ctx2.appendLog(JSON.stringify(out, null, 2));
|
|
196
|
+
await refreshScan();
|
|
197
|
+
};
|
|
198
|
+
btnDelProfile.onclick = async () => {
|
|
199
|
+
if (!confirm(`\u5220\u9664 profile \u76EE\u5F55: ${e.profileId}?
|
|
200
|
+
(\u53EF\u9009\uFF1A\u540C\u65F6\u5220\u9664\u6307\u7EB9\u6587\u4EF6)`)) return;
|
|
201
|
+
const alsoFp = confirm(`\u540C\u65F6\u5220\u9664 ${e.profileId} \u7684 fingerprint \u6587\u4EF6\uFF1F`);
|
|
202
|
+
ctx2.clearLog();
|
|
203
|
+
const out = await window.api.profileDelete({ profileId: e.profileId, deleteFingerprint: alsoFp });
|
|
204
|
+
ctx2.appendLog(JSON.stringify(out, null, 2));
|
|
205
|
+
await refreshScan();
|
|
206
|
+
};
|
|
207
|
+
actions.appendChild(btnOpenProfile);
|
|
208
|
+
actions.appendChild(btnOpenFp);
|
|
209
|
+
actions.appendChild(btnSaveAlias);
|
|
210
|
+
actions.appendChild(btnRegenFp);
|
|
211
|
+
actions.appendChild(btnDelFp);
|
|
212
|
+
actions.appendChild(btnDelProfile);
|
|
213
|
+
row.appendChild(actions);
|
|
214
|
+
listBox.appendChild(row);
|
|
215
|
+
}
|
|
216
|
+
updateSelectionHint();
|
|
217
|
+
}
|
|
218
|
+
function updateSelectionHint() {
|
|
219
|
+
const selected = listBox.querySelectorAll('input[type="checkbox"][data-profile-id]:checked').length;
|
|
220
|
+
selectionHint.textContent = `selected=${selected}`;
|
|
221
|
+
}
|
|
222
|
+
async function batchDeleteSelectedProfiles() {
|
|
223
|
+
const selected = [];
|
|
224
|
+
listBox.querySelectorAll('input[type="checkbox"][data-profile-id]').forEach((el) => {
|
|
225
|
+
const cb = el;
|
|
226
|
+
if (!cb.checked) return;
|
|
227
|
+
const id = String(cb.dataset.profileId || "").trim();
|
|
228
|
+
if (id) selected.push(id);
|
|
229
|
+
});
|
|
230
|
+
if (selected.length === 0) return;
|
|
231
|
+
if (!confirm(`\u6279\u91CF\u5220\u9664 ${selected.length} \u4E2A profile\uFF1F`)) return;
|
|
232
|
+
const deleteFp = batchDeleteFp.checked && confirm(`\u540C\u65F6\u5220\u9664\u8FD9 ${selected.length} \u4E2A profile \u7684 fingerprint \u6587\u4EF6\uFF1F`);
|
|
233
|
+
ctx2.clearLog();
|
|
234
|
+
for (const profileId of selected) {
|
|
235
|
+
try {
|
|
236
|
+
const out = await window.api.profileDelete({ profileId, deleteFingerprint: deleteFp });
|
|
237
|
+
ctx2.appendLog(JSON.stringify(out, null, 2));
|
|
238
|
+
} catch (err) {
|
|
239
|
+
ctx2.appendLog(`[batch-delete] ${profileId} failed: ${err?.message || String(err)}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
await refreshScan();
|
|
243
|
+
}
|
|
244
|
+
async function refreshScan() {
|
|
245
|
+
const out = await window.api.profilesScan();
|
|
246
|
+
cachedScan = out;
|
|
247
|
+
statusBox.textContent = `profiles=${out?.entries?.length || 0} profilesRoot=${out?.profilesRoot || ""} fingerprintsRoot=${out?.fingerprintsRoot || ""}`;
|
|
248
|
+
const entries = out?.entries || [];
|
|
249
|
+
const aliases = getAliasMap();
|
|
250
|
+
const aliasedCount = entries.filter((e) => String(aliases[String(e?.profileId || "")] || "").trim()).length;
|
|
251
|
+
onboardingSummary.textContent = `profile=${entries.length}\uFF0C\u5DF2\u8BBE\u7F6E\u8D26\u53F7\u540D=${aliasedCount}`;
|
|
252
|
+
if (entries.length === 0) {
|
|
253
|
+
onboardingTips.textContent = "\u5F53\u524D\u6CA1\u6709\u53EF\u7528 profile\uFF1A\u8BF7\u5148\u5728\u4E0B\u65B9\u201C\u6279\u91CF\u8D26\u53F7\u6C60\u201D\u91CC\u65B0\u589E\u8D26\u53F7\u3002";
|
|
254
|
+
} else if (aliasedCount < entries.length) {
|
|
255
|
+
onboardingTips.textContent = `\u4ECD\u6709 ${entries.length - aliasedCount} \u4E2A profile \u672A\u8BBE\u7F6E\u8D26\u53F7\u540D\uFF08alias\uFF09\u3002alias \u7528\u4E8E\u533A\u5206\u8D26\u53F7\uFF0C\u9ED8\u8BA4\u767B\u5F55\u540E\u4F1A\u81EA\u52A8\u83B7\u53D6\u7528\u6237\u540D\u3002`;
|
|
256
|
+
} else {
|
|
257
|
+
onboardingTips.textContent = "\u5F88\u597D\uFF1A\u6240\u6709 profile \u90FD\u6709\u8D26\u53F7\u540D\uFF08alias\uFF09\uFF0C\u5728\u5C0F\u7EA2\u4E66\u9996\u9875\u4F1A\u6309\u201C\u8D26\u53F7\u540D (profileId)\u201D\u663E\u793A\u3002";
|
|
258
|
+
}
|
|
259
|
+
renderList();
|
|
260
|
+
}
|
|
261
|
+
const toolbar = createEl("div", { className: "row" }, [
|
|
262
|
+
labeledInput("filter", filterInput),
|
|
263
|
+
createEl("div", { style: "display:flex; flex-direction:column; gap:6px;" }, [
|
|
264
|
+
createEl("label", {}, ["select all"]),
|
|
265
|
+
selectAllBox
|
|
266
|
+
]),
|
|
267
|
+
createEl("div", { style: "display:flex; flex-direction:column; gap:6px;" }, [
|
|
268
|
+
createEl("label", {}, ["del fingerprint"]),
|
|
269
|
+
batchDeleteFp
|
|
270
|
+
]),
|
|
271
|
+
createEl("div", { style: "display:flex; flex-direction:column; gap:6px;" }, [
|
|
272
|
+
createEl("label", {}, ["only missing fingerprint"]),
|
|
273
|
+
onlyMissingFp
|
|
274
|
+
]),
|
|
275
|
+
labeledInput("regen platform", regenPlatform),
|
|
276
|
+
createEl("button", { className: "secondary" }, ["\u5237\u65B0\u5217\u8868"]),
|
|
277
|
+
createEl("button", {}, ["\u6279\u91CF\u8865\u9F50\u7F3A\u5931\u6307\u7EB9\uFF08\u811A\u672C\uFF09"]),
|
|
278
|
+
batchDeleteBtn
|
|
279
|
+
]);
|
|
280
|
+
toolbar.children[5].onclick = () => void refreshScan();
|
|
281
|
+
toolbar.children[6].onclick = async () => {
|
|
282
|
+
ctx2.clearLog();
|
|
283
|
+
const args = buildArgs([window.api.pathJoin("apps", "webauto", "entry", "profilepool.mjs"), "migrate-fingerprints"]);
|
|
284
|
+
await window.api.cmdSpawn({ title: "migrate fingerprints", cwd: "", args, groupKey: "profilepool" });
|
|
285
|
+
setTimeout(() => void refreshScan(), 1e3);
|
|
286
|
+
};
|
|
287
|
+
batchDeleteBtn.onclick = () => void batchDeleteSelectedProfiles();
|
|
288
|
+
filterInput.oninput = () => renderList();
|
|
289
|
+
onlyMissingFp.onchange = () => renderList();
|
|
290
|
+
selectAllBox.onchange = () => {
|
|
291
|
+
const checked = selectAllBox.checked;
|
|
292
|
+
listBox.querySelectorAll('input[type="checkbox"][data-profile-id]').forEach((el) => {
|
|
293
|
+
el.checked = checked;
|
|
294
|
+
});
|
|
295
|
+
updateSelectionHint();
|
|
296
|
+
};
|
|
297
|
+
const runBrowserCheck = async (opts = {}) => {
|
|
298
|
+
const download = opts.download === true;
|
|
299
|
+
const source = opts.source || "manual";
|
|
300
|
+
const script = window.api.pathJoin("apps", "webauto", "entry", "xhs-install.mjs");
|
|
301
|
+
const args = buildArgs([script, "--check-browser-only", ...download ? ["--download-browser"] : []]);
|
|
302
|
+
browserStatus.textContent = download ? "\u6D4F\u89C8\u5668\u72B6\u6001\uFF1A\u4E0B\u8F7D+\u68C0\u67E5\u4E2D..." : "\u6D4F\u89C8\u5668\u72B6\u6001\uFF1A\u68C0\u67E5\u4E2D...";
|
|
303
|
+
browserStatus.style.color = "#8b93a6";
|
|
304
|
+
if (typeof window.api?.cmdRunJson === "function") {
|
|
305
|
+
const out = await window.api.cmdRunJson({
|
|
306
|
+
title: download ? "xhs install download" : "xhs install check",
|
|
307
|
+
cwd: "",
|
|
308
|
+
args,
|
|
309
|
+
timeoutMs: download ? 24e4 : 12e4
|
|
310
|
+
}).catch((err) => ({ ok: false, error: err?.message || String(err) }));
|
|
311
|
+
const mergedOutput = String(out?.stdout || out?.stderr || out?.error || "").replace(/\x1b\[[0-9;]*m/g, "");
|
|
312
|
+
if (out?.ok) {
|
|
313
|
+
browserStatus.textContent = download ? "\u6D4F\u89C8\u5668\u72B6\u6001\uFF1A\u4E0B\u8F7D\u5E76\u68C0\u67E5\u901A\u8FC7" : "\u6D4F\u89C8\u5668\u72B6\u6001\uFF1A\u68C0\u67E5\u901A\u8FC7";
|
|
314
|
+
browserStatus.style.color = "#22c55e";
|
|
315
|
+
} else if (/Camoufox 未安装/i.test(mergedOutput)) {
|
|
316
|
+
browserStatus.textContent = "\u6D4F\u89C8\u5668\u72B6\u6001\uFF1A\u672A\u5B89\u88C5 Camoufox\uFF08\u53EF\u70B9\u201C\u4E0B\u8F7D Camoufox\u201D\uFF09";
|
|
317
|
+
browserStatus.style.color = "#f59e0b";
|
|
318
|
+
} else {
|
|
319
|
+
browserStatus.textContent = `\u6D4F\u89C8\u5668\u72B6\u6001\uFF1A\u68C0\u67E5\u5931\u8D25\uFF08code=${out?.code ?? "n/a"}\uFF09`;
|
|
320
|
+
browserStatus.style.color = "#ef4444";
|
|
321
|
+
}
|
|
322
|
+
if (source === "manual" && mergedOutput) {
|
|
323
|
+
ctx2.appendLog(`[preflight] install output
|
|
324
|
+
${mergedOutput}`);
|
|
325
|
+
}
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (typeof window.api?.cmdSpawn === "function") {
|
|
329
|
+
await window.api.cmdSpawn({ title: download ? "xhs install download" : "xhs install check", cwd: "", args, groupKey: "install" });
|
|
330
|
+
browserStatus.textContent = "\u6D4F\u89C8\u5668\u72B6\u6001\uFF1A\u5DF2\u89E6\u53D1\u68C0\u67E5\uFF08\u67E5\u770B\u65E5\u5FD7\uFF09";
|
|
331
|
+
browserStatus.style.color = "#22c55e";
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
browserStatus.textContent = "\u6D4F\u89C8\u5668\u72B6\u6001\uFF1A\u68C0\u67E5\u80FD\u529B\u4E0D\u53EF\u7528";
|
|
335
|
+
browserStatus.style.color = "#ef4444";
|
|
336
|
+
};
|
|
337
|
+
gotoXhsBtn.onclick = () => {
|
|
338
|
+
try {
|
|
339
|
+
window.localStorage.setItem(XHS_NAV_TARGET_KEY, "account");
|
|
340
|
+
} catch {
|
|
341
|
+
}
|
|
342
|
+
if (typeof ctx2?.setActiveTab === "function") ctx2.setActiveTab("xiaohongshu");
|
|
343
|
+
};
|
|
344
|
+
browserCheckBtn.onclick = async () => {
|
|
345
|
+
ctx2.clearLog();
|
|
346
|
+
await runBrowserCheck({ source: "manual" });
|
|
347
|
+
};
|
|
348
|
+
browserDownloadBtn.onclick = async () => {
|
|
349
|
+
ctx2.clearLog();
|
|
350
|
+
await runBrowserCheck({ download: true, source: "manual" });
|
|
351
|
+
};
|
|
352
|
+
root.appendChild(
|
|
353
|
+
section("\u9996\u6B21\u5F15\u5BFC\uFF08\u8D26\u53F7\u89C6\u89D2\uFF09", [
|
|
354
|
+
onboardingSummary,
|
|
355
|
+
onboardingTips,
|
|
356
|
+
createEl("div", { className: "row" }, [gotoXhsBtn])
|
|
357
|
+
])
|
|
358
|
+
);
|
|
359
|
+
root.appendChild(
|
|
360
|
+
section("\u6D4F\u89C8\u5668\u68C0\u67E5\u4E0E\u4E0B\u8F7D", [
|
|
361
|
+
browserStatus,
|
|
362
|
+
createEl("div", { className: "row" }, [browserCheckBtn, browserDownloadBtn]),
|
|
363
|
+
createEl("div", { className: "muted", style: "font-size:12px;" }, ["\u8BF4\u660E\uFF1A\u68C0\u67E5/\u4E0B\u8F7D\u7684\u8BE6\u7EC6\u8F93\u51FA\u8BF7\u67E5\u770B\u65E5\u5FD7\u9875\u3002"])
|
|
364
|
+
])
|
|
365
|
+
);
|
|
366
|
+
root.appendChild(
|
|
367
|
+
section("Profiles + Fingerprints (CRUD)", [
|
|
368
|
+
toolbar,
|
|
369
|
+
statusBox,
|
|
370
|
+
selectionHint,
|
|
371
|
+
listBox,
|
|
372
|
+
createEl("div", { className: "muted" }, [`\u63D0\u793A\uFF1Aprofile \u4E0E fingerprint \u7684\u771F\u5B9E\u8DEF\u5F84\u5747\u5728 ${webautoRoot} \u4E0B\uFF1Balias \u53EA\u5F71\u54CD UI \u663E\u793A\uFF0C\u4E0D\u5F71\u54CD profileId\u3002`])
|
|
373
|
+
])
|
|
374
|
+
);
|
|
375
|
+
const keywordInput = createEl("input", { value: "xiaohongshu", placeholder: "\u8D26\u53F7\u540D\uFF08\u53EF\u9009\uFF09\uFF0C\u7CFB\u7EDF\u81EA\u52A8\u62FC\u63A5\u4E3A <\u8D26\u53F7\u540D>-batch-1/2/3\uFF1B\u7559\u7A7A\u9ED8\u8BA4 xiaohongshu-batch" });
|
|
376
|
+
const ensureCountInput = createEl("input", { value: "0", type: "number", min: "0" });
|
|
377
|
+
const timeoutInput = createEl("input", { value: String(ctx2.settings?.timeouts?.loginTimeoutSec || 900), type: "number", min: "30" });
|
|
378
|
+
const keepSession = createEl("input", { type: "checkbox" });
|
|
379
|
+
const poolListBox = createEl("div", { className: "list" });
|
|
380
|
+
const poolStatus = createEl("div", { className: "muted" }, [""]);
|
|
381
|
+
function resolveBatchPrefix() {
|
|
382
|
+
const base = String(keywordInput.value || "").trim() || "xiaohongshu";
|
|
383
|
+
return `${base}-batch`;
|
|
384
|
+
}
|
|
385
|
+
async function poolList() {
|
|
386
|
+
ctx2.clearLog();
|
|
387
|
+
const kw = resolveBatchPrefix();
|
|
388
|
+
const out = await window.api.cmdRunJson({
|
|
389
|
+
title: "profilepool list",
|
|
390
|
+
cwd: "",
|
|
391
|
+
args: buildArgs([window.api.pathJoin("apps", "webauto", "entry", "profilepool.mjs"), "list", kw, "--json"])
|
|
392
|
+
});
|
|
393
|
+
poolListBox.textContent = "";
|
|
394
|
+
if (!out?.ok || !out?.json) {
|
|
395
|
+
poolListBox.appendChild(createEl("div", { className: "item" }, ["(failed)"]));
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const profiles = out.json.profiles || [];
|
|
399
|
+
profiles.forEach((p) => poolListBox.appendChild(createEl("div", { className: "item" }, [p])));
|
|
400
|
+
poolStatus.textContent = `count=${profiles.length} root=${out.json.root}`;
|
|
401
|
+
await refreshScan();
|
|
402
|
+
}
|
|
403
|
+
async function poolAdd() {
|
|
404
|
+
ctx2.clearLog();
|
|
405
|
+
const kw = resolveBatchPrefix();
|
|
406
|
+
const out = await window.api.cmdRunJson({
|
|
407
|
+
title: "profilepool add",
|
|
408
|
+
cwd: "",
|
|
409
|
+
args: buildArgs([window.api.pathJoin("apps", "webauto", "entry", "profilepool.mjs"), "add", kw, "--json"])
|
|
410
|
+
});
|
|
411
|
+
ctx2.appendLog(JSON.stringify(out?.json || out, null, 2));
|
|
412
|
+
const createdProfileId = String(out?.json?.profileId || "").trim();
|
|
413
|
+
await poolList();
|
|
414
|
+
if (!createdProfileId) return;
|
|
415
|
+
if (typeof window.api?.cmdSpawn !== "function") return;
|
|
416
|
+
const timeoutSec = Math.max(30, Math.floor(Number(timeoutInput.value || "900")));
|
|
417
|
+
const loginArgs = buildArgs([
|
|
418
|
+
window.api.pathJoin("apps", "webauto", "entry", "profilepool.mjs"),
|
|
419
|
+
"login-profile",
|
|
420
|
+
createdProfileId,
|
|
421
|
+
"--timeout-sec",
|
|
422
|
+
String(timeoutSec),
|
|
423
|
+
"--check-interval-sec",
|
|
424
|
+
"2",
|
|
425
|
+
"--keep-session",
|
|
426
|
+
...ctx2.settings?.unifiedApiUrl ? ["--unified-api", String(ctx2.settings.unifiedApiUrl)] : [],
|
|
427
|
+
...ctx2.settings?.browserServiceUrl ? ["--browser-service", String(ctx2.settings.browserServiceUrl)] : []
|
|
428
|
+
]);
|
|
429
|
+
await window.api.cmdSpawn({
|
|
430
|
+
title: `profilepool login-profile ${createdProfileId}`,
|
|
431
|
+
cwd: "",
|
|
432
|
+
args: loginArgs,
|
|
433
|
+
groupKey: "profilepool",
|
|
434
|
+
env: { WEBAUTO_DAEMON: "1" }
|
|
435
|
+
});
|
|
436
|
+
ctx2.appendLog(`[preflight] \u5DF2\u521B\u5EFA\u5E76\u542F\u52A8\u767B\u5F55: ${createdProfileId}`);
|
|
437
|
+
}
|
|
438
|
+
async function poolLogin() {
|
|
439
|
+
ctx2.clearLog();
|
|
440
|
+
const kw = resolveBatchPrefix();
|
|
441
|
+
const ensureCount = Math.max(0, Math.floor(Number(ensureCountInput.value || "0")));
|
|
442
|
+
const timeoutSec = Math.max(30, Math.floor(Number(timeoutInput.value || "900")));
|
|
443
|
+
const args = buildArgs([
|
|
444
|
+
window.api.pathJoin("apps", "webauto", "entry", "profilepool.mjs"),
|
|
445
|
+
"login",
|
|
446
|
+
kw,
|
|
447
|
+
...ctx2.settings?.unifiedApiUrl ? ["--unified-api", String(ctx2.settings.unifiedApiUrl)] : [],
|
|
448
|
+
...ctx2.settings?.browserServiceUrl ? ["--browser-service", String(ctx2.settings.browserServiceUrl)] : [],
|
|
449
|
+
"--timeout-sec",
|
|
450
|
+
String(timeoutSec),
|
|
451
|
+
...ensureCount > 0 ? ["--ensure-count", String(ensureCount)] : [],
|
|
452
|
+
...keepSession.checked ? ["--keep-session"] : []
|
|
453
|
+
]);
|
|
454
|
+
await window.api.cmdSpawn({ title: `profilepool login ${kw}`, cwd: "", args, groupKey: "profilepool" });
|
|
455
|
+
}
|
|
456
|
+
const poolActions = createEl("div", { className: "row" }, [
|
|
457
|
+
createEl("button", { className: "secondary" }, ["\u626B\u63CF\u6C60"]),
|
|
458
|
+
createEl("button", { className: "secondary" }, ["\u65B0\u589E\u4E00\u4E2A\u5E76\u767B\u5F55"]),
|
|
459
|
+
createEl("button", {}, ["\u6279\u91CF\u767B\u5F55/\u8865\u767B\u5F55"])
|
|
460
|
+
]);
|
|
461
|
+
poolActions.children[0].onclick = () => void poolList();
|
|
462
|
+
poolActions.children[1].onclick = () => void poolAdd();
|
|
463
|
+
poolActions.children[2].onclick = () => void poolLogin();
|
|
464
|
+
root.appendChild(
|
|
465
|
+
section("\u6279\u91CF\u8D26\u53F7\u6C60\uFF08\u81EA\u52A8\u5E8F\u53F7\uFF09", [
|
|
466
|
+
createEl("div", { className: "row" }, [
|
|
467
|
+
labeledInput("\u8D26\u53F7\u540D\uFF08\u9ED8\u8BA4 xiaohongshu\uFF09", keywordInput),
|
|
468
|
+
labeledInput("ensure-count (\u53EF\u9009)", ensureCountInput),
|
|
469
|
+
labeledInput("login timeout (sec)", timeoutInput),
|
|
470
|
+
createEl("div", { style: "display:flex; flex-direction:column; gap:6px;" }, [
|
|
471
|
+
createEl("label", {}, ["keep-session"]),
|
|
472
|
+
keepSession
|
|
473
|
+
])
|
|
474
|
+
]),
|
|
475
|
+
poolActions,
|
|
476
|
+
poolStatus,
|
|
477
|
+
poolListBox
|
|
478
|
+
])
|
|
479
|
+
);
|
|
480
|
+
void refreshScan();
|
|
481
|
+
void runBrowserCheck({ source: "auto" });
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// src/renderer/tabs/run.mts
|
|
485
|
+
function buildArgs2(parts) {
|
|
486
|
+
return parts.filter((x) => x != null && String(x).trim() !== "");
|
|
487
|
+
}
|
|
488
|
+
function renderRun(root, ctx2) {
|
|
489
|
+
const templateSel = createEl("select");
|
|
490
|
+
const templates = [
|
|
491
|
+
{ id: "fullCollect", label: "\u8C03\u8BD5\uFF1AUnified Harvest" },
|
|
492
|
+
{ id: "smartReply", label: "\u8C03\u8BD5\uFF1ASmart Reply DEV E2E" },
|
|
493
|
+
{ id: "virtualLike", label: "\u8C03\u8BD5\uFF1AVirtual Like E2E" }
|
|
494
|
+
];
|
|
495
|
+
templates.forEach((t) => templateSel.appendChild(createEl("option", { value: t.id }, [t.label])));
|
|
496
|
+
const keywordInput = createEl("input", { value: ctx2.settings?.defaultKeyword || "", placeholder: "keyword" });
|
|
497
|
+
const targetInput = createEl("input", { value: String(ctx2.settings?.defaultTarget || ""), placeholder: "target", type: "number", min: "1" });
|
|
498
|
+
const envSel = createEl("select");
|
|
499
|
+
["debug", "prod"].forEach((x) => envSel.appendChild(createEl("option", { value: x }, [x])));
|
|
500
|
+
envSel.value = ctx2.settings?.defaultEnv || "debug";
|
|
501
|
+
const dryRun = createEl("input", { type: "checkbox" });
|
|
502
|
+
dryRun.checked = ctx2.settings?.defaultDryRun === true;
|
|
503
|
+
const profileModeSel = createEl("select");
|
|
504
|
+
const profilePickSel = createEl("select");
|
|
505
|
+
const runtimePickSel = createEl("select");
|
|
506
|
+
const profileRefreshBtn = createEl("button", { className: "secondary" }, ["\u5237\u65B0 profiles"]);
|
|
507
|
+
const poolPickSel = createEl("select");
|
|
508
|
+
const profilesBox = createEl("div", { className: "list" });
|
|
509
|
+
const profilesHint = createEl("div", { className: "muted" }, [""]);
|
|
510
|
+
const resolvedHint = createEl("div", { className: "muted" }, [""]);
|
|
511
|
+
const extraInput = createEl("input", { placeholder: "extra args (raw)" });
|
|
512
|
+
let fullCollectScripts = [];
|
|
513
|
+
function maybeFlag(flag, value) {
|
|
514
|
+
const v = String(value || "").trim();
|
|
515
|
+
if (!v) return [];
|
|
516
|
+
return [flag, v];
|
|
517
|
+
}
|
|
518
|
+
function setProfileModes(templateId) {
|
|
519
|
+
const supportsMultiProfile = templateId === "fullCollect";
|
|
520
|
+
profileModeSel.textContent = "";
|
|
521
|
+
const modes = supportsMultiProfile ? [
|
|
522
|
+
{ v: "profile", l: "profile\uFF08\u5355\u4E2A\uFF09" },
|
|
523
|
+
{ v: "profilepool", l: "profilepool\uFF08\u6309 keyword \u524D\u7F00\u626B\u63CF + \u81EA\u52A8\u5206\u7247\uFF09" },
|
|
524
|
+
{ v: "profiles", l: "profiles\uFF08\u624B\u52A8\u5217\u8868 + \u81EA\u52A8\u5206\u7247\uFF09" }
|
|
525
|
+
] : [{ v: "profile", l: "profile\uFF08\u5355\u4E2A\uFF09" }];
|
|
526
|
+
modes.forEach((m) => profileModeSel.appendChild(createEl("option", { value: m.v }, [m.l])));
|
|
527
|
+
profileModeSel.value = "profile";
|
|
528
|
+
}
|
|
529
|
+
function derivePoolKeys(profiles) {
|
|
530
|
+
const keys = /* @__PURE__ */ new Set();
|
|
531
|
+
for (const p of profiles) {
|
|
532
|
+
const m = /^(.+)-\d+$/.exec(p);
|
|
533
|
+
if (m && m[1]) keys.add(m[1]);
|
|
534
|
+
}
|
|
535
|
+
return Array.from(keys).sort((a, b) => a.localeCompare(b));
|
|
536
|
+
}
|
|
537
|
+
async function refreshProfiles() {
|
|
538
|
+
profilePickSel.textContent = "";
|
|
539
|
+
profilePickSel.appendChild(createEl("option", { value: "" }, ["(\u9009\u62E9\u5DF2\u6709 profile\uFF0C\u586B\u5145\u5230\u8F93\u5165\u6846)"]));
|
|
540
|
+
const profilesRes = await window.api.profilesList().catch(() => null);
|
|
541
|
+
const profiles = profilesRes?.profiles || [];
|
|
542
|
+
const aliases = ctx2.settings?.profileAliases && typeof ctx2.settings.profileAliases === "object" ? ctx2.settings.profileAliases : {};
|
|
543
|
+
profiles.forEach((p) => {
|
|
544
|
+
const alias = String(aliases[p] || "").trim();
|
|
545
|
+
const label = alias ? `${alias} (${p})` : p;
|
|
546
|
+
profilePickSel.appendChild(createEl("option", { value: p }, [label]));
|
|
547
|
+
});
|
|
548
|
+
poolPickSel.textContent = "";
|
|
549
|
+
poolPickSel.appendChild(createEl("option", { value: "" }, ["(\u9009\u62E9 pool keyword)"]));
|
|
550
|
+
const poolKeys = derivePoolKeys(profiles);
|
|
551
|
+
poolKeys.forEach((k) => poolPickSel.appendChild(createEl("option", { value: k }, [String(k)])));
|
|
552
|
+
if (poolKeys.length > 0 && profileModeSel.value === "profilepool" && !poolPickSel.value) {
|
|
553
|
+
poolPickSel.value = poolKeys[0];
|
|
554
|
+
syncProfileValueFromUI();
|
|
555
|
+
}
|
|
556
|
+
profilesBox.textContent = "";
|
|
557
|
+
profilesHint.textContent = "";
|
|
558
|
+
const selected = /* @__PURE__ */ new Set();
|
|
559
|
+
const preferSingle = profiles[0] || "";
|
|
560
|
+
if (preferSingle) {
|
|
561
|
+
profilePickSel.value = preferSingle;
|
|
562
|
+
selected.add(preferSingle);
|
|
563
|
+
}
|
|
564
|
+
profiles.forEach((p) => {
|
|
565
|
+
const id = `p_${p}`;
|
|
566
|
+
const cb = createEl("input", { type: "checkbox", id });
|
|
567
|
+
cb.dataset.profile = p;
|
|
568
|
+
cb.checked = selected.has(p);
|
|
569
|
+
cb.onchange = () => syncProfileValueFromUI();
|
|
570
|
+
const alias = String(aliases[p] || "").trim();
|
|
571
|
+
const labelText = alias ? `${alias} (${p})` : p;
|
|
572
|
+
const label = createEl("label", { for: id, style: "cursor:pointer;" }, [labelText]);
|
|
573
|
+
const row = createEl("div", { className: "row", style: "align-items:center;" }, [cb, label]);
|
|
574
|
+
profilesBox.appendChild(row);
|
|
575
|
+
});
|
|
576
|
+
syncProfileValueFromUI();
|
|
577
|
+
}
|
|
578
|
+
async function refreshFullCollectScripts() {
|
|
579
|
+
const res = await window.api.scriptsXhsFullCollect().catch(() => null);
|
|
580
|
+
fullCollectScripts = Array.isArray(res?.scripts) ? res.scripts : [];
|
|
581
|
+
if (fullCollectScripts.length > 0) {
|
|
582
|
+
const first = fullCollectScripts[0];
|
|
583
|
+
const label = first?.label || "Full Collect (Phase1-4)";
|
|
584
|
+
const option = templateSel.querySelector('option[value="fullCollect"]');
|
|
585
|
+
if (option) option.textContent = label;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
async function refreshRuntimes() {
|
|
589
|
+
runtimePickSel.textContent = "";
|
|
590
|
+
runtimePickSel.appendChild(createEl("option", { value: "" }, ["(\u9009\u62E9\u8FD0\u884C\u4E2D\u7684 runtime)"]));
|
|
591
|
+
const sessions = await window.api.runtimeListSessions().catch(() => []);
|
|
592
|
+
const aliases = ctx2.settings?.profileAliases && typeof ctx2.settings.profileAliases === "object" ? ctx2.settings.profileAliases : {};
|
|
593
|
+
(Array.isArray(sessions) ? sessions : []).forEach((s) => {
|
|
594
|
+
const profileId = String(s?.profileId || s?.sessionId || "").trim();
|
|
595
|
+
if (!profileId) return;
|
|
596
|
+
const alias = String(aliases[profileId] || "").trim();
|
|
597
|
+
const label = alias ? `${alias} (${profileId})` : profileId;
|
|
598
|
+
runtimePickSel.appendChild(createEl("option", { value: profileId }, [label]));
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
window.api.onSettingsChanged((next) => {
|
|
602
|
+
ctx2.settings = next;
|
|
603
|
+
void refreshProfiles();
|
|
604
|
+
void refreshRuntimes();
|
|
605
|
+
});
|
|
606
|
+
function getSelectedProfiles() {
|
|
607
|
+
const selected = [];
|
|
608
|
+
profilesBox.querySelectorAll('input[type="checkbox"]').forEach((el) => {
|
|
609
|
+
const cb = el;
|
|
610
|
+
if (!cb.checked) return;
|
|
611
|
+
const id = String(cb.dataset.profile || "").trim();
|
|
612
|
+
if (id) selected.push(id);
|
|
613
|
+
});
|
|
614
|
+
return selected;
|
|
615
|
+
}
|
|
616
|
+
function syncProfileValueFromUI() {
|
|
617
|
+
const mode = profileModeSel.value;
|
|
618
|
+
const useRuntimeForSingle = templateSel.value !== "fullCollect";
|
|
619
|
+
profilePickSel.style.display = mode === "profile" && !useRuntimeForSingle ? "" : "none";
|
|
620
|
+
runtimePickSel.style.display = mode === "profile" && useRuntimeForSingle ? "" : "none";
|
|
621
|
+
poolPickSel.style.display = mode === "profilepool" ? "" : "none";
|
|
622
|
+
profilesBox.style.display = mode === "profiles" || mode === "profilepool" ? "" : "none";
|
|
623
|
+
if (mode === "profile") {
|
|
624
|
+
const v = String(useRuntimeForSingle ? runtimePickSel.value : profilePickSel.value || "").trim();
|
|
625
|
+
profilesHint.textContent = v ? "" : "\u8BF7\u9009\u62E9\u4E00\u4E2A runtime/profile";
|
|
626
|
+
resolvedHint.textContent = v ? `resolved: --profile ${v}` : "";
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
if (mode === "profilepool") {
|
|
630
|
+
const v = String(poolPickSel.value || "").trim();
|
|
631
|
+
profilesHint.textContent = v ? "" : "\u8BF7\u9009\u62E9\u4E00\u4E2A pool keyword";
|
|
632
|
+
resolvedHint.textContent = v ? `resolved: --profilepool ${v}` : "";
|
|
633
|
+
if (v) {
|
|
634
|
+
profilesBox.querySelectorAll('input[type="checkbox"]').forEach((el) => {
|
|
635
|
+
const cb = el;
|
|
636
|
+
const pid = String(cb.dataset.profile || "");
|
|
637
|
+
cb.checked = pid.startsWith(v);
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
if (mode === "profiles") {
|
|
643
|
+
const list = getSelectedProfiles();
|
|
644
|
+
profilesHint.textContent = `selected=${list.length}`;
|
|
645
|
+
resolvedHint.textContent = list.length ? `resolved: --profiles ${list.join(",")}` : "";
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
function resolveProfileArgsForRun(t) {
|
|
650
|
+
const supportsMultiProfile = t === "fullCollect";
|
|
651
|
+
const mode = profileModeSel.value;
|
|
652
|
+
const useRuntimeForSingle = t !== "fullCollect";
|
|
653
|
+
if (!supportsMultiProfile) {
|
|
654
|
+
const v = String(useRuntimeForSingle ? runtimePickSel.value : profilePickSel.value || "").trim();
|
|
655
|
+
if (!v) return { ok: false, error: "\u8BF7\u9009\u62E9\u4E00\u4E2A profile", args: [] };
|
|
656
|
+
return { ok: true, args: ["--profile", v], mode: "profile", value: v };
|
|
657
|
+
}
|
|
658
|
+
if (mode === "profile") {
|
|
659
|
+
const v = String(useRuntimeForSingle ? runtimePickSel.value : profilePickSel.value || "").trim();
|
|
660
|
+
if (!v) return { ok: false, error: "\u8BF7\u9009\u62E9\u4E00\u4E2A profile", args: [] };
|
|
661
|
+
return { ok: true, args: ["--profile", v], mode: "profile", value: v };
|
|
662
|
+
}
|
|
663
|
+
if (mode === "profilepool") {
|
|
664
|
+
const v = String(poolPickSel.value || "").trim();
|
|
665
|
+
if (!v) return { ok: false, error: "\u8BF7\u9009\u62E9\u4E00\u4E2A pool keyword", args: [] };
|
|
666
|
+
return { ok: true, args: ["--profilepool", v], mode: "profilepool", value: v };
|
|
667
|
+
}
|
|
668
|
+
const list = getSelectedProfiles();
|
|
669
|
+
if (list.length === 0) return { ok: false, error: "\u8BF7\u52FE\u9009\u81F3\u5C11\u4E00\u4E2A profile", args: [] };
|
|
670
|
+
return { ok: true, args: ["--profiles", list.join(",")], mode: "profiles", value: list.join(",") };
|
|
671
|
+
}
|
|
672
|
+
function persistRunInputs(next) {
|
|
673
|
+
const curr = ctx2.settings || {};
|
|
674
|
+
const payload = {};
|
|
675
|
+
if (next.keyword && next.keyword !== curr.defaultKeyword) payload.defaultKeyword = next.keyword;
|
|
676
|
+
if (typeof next.target === "number" && next.target > 0 && next.target !== curr.defaultTarget) payload.defaultTarget = next.target;
|
|
677
|
+
if (next.env && next.env !== curr.defaultEnv) payload.defaultEnv = next.env;
|
|
678
|
+
if (typeof next.dryRun === "boolean" && next.dryRun !== curr.defaultDryRun) payload.defaultDryRun = next.dryRun;
|
|
679
|
+
if (Object.keys(payload).length === 0) return;
|
|
680
|
+
window.api.settingsSet(payload).then((updated) => {
|
|
681
|
+
if (updated) ctx2.settings = updated;
|
|
682
|
+
}).catch(() => {
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
async function run() {
|
|
686
|
+
ctx2.clearLog();
|
|
687
|
+
const t = templateSel.value;
|
|
688
|
+
const keyword = keywordInput.value.trim();
|
|
689
|
+
const env = envSel.value.trim();
|
|
690
|
+
const extra = extraInput.value.trim();
|
|
691
|
+
const target = targetInput.value.trim();
|
|
692
|
+
if (t === "fullCollect") {
|
|
693
|
+
if (!keyword) {
|
|
694
|
+
profilesHint.textContent = "fullCollect: \u5FC5\u987B\u586B\u5199 keyword";
|
|
695
|
+
alert("Full Collect: \u5FC5\u987B\u586B\u5199 keyword");
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
const n = Number(target);
|
|
699
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
700
|
+
profilesHint.textContent = "fullCollect: \u5FC5\u987B\u586B\u5199 target\uFF08\u6B63\u6574\u6570\uFF09";
|
|
701
|
+
alert("Full Collect: \u5FC5\u987B\u586B\u5199 target\uFF08\u6B63\u6574\u6570\uFF09");
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
const targetNum = Number(target);
|
|
706
|
+
persistRunInputs({ keyword, target: Number.isFinite(targetNum) ? targetNum : void 0, env, dryRun: dryRun.checked });
|
|
707
|
+
const common = buildArgs2([
|
|
708
|
+
...keyword ? ["--keyword", keyword] : [],
|
|
709
|
+
...target ? ["--target", target] : [],
|
|
710
|
+
...env ? ["--env", env] : [],
|
|
711
|
+
...dryRun.checked ? ["--dry-run"] : []
|
|
712
|
+
]);
|
|
713
|
+
const resolved = resolveProfileArgsForRun(t);
|
|
714
|
+
if (!resolved.ok) {
|
|
715
|
+
profilesHint.textContent = resolved.error;
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
const profileArgs = resolved.args;
|
|
719
|
+
const extraArgs = extra ? extra.split(" ").filter(Boolean) : [];
|
|
720
|
+
let script = "";
|
|
721
|
+
let args = [];
|
|
722
|
+
if (t === "fullCollect") {
|
|
723
|
+
const chosen = fullCollectScripts[0];
|
|
724
|
+
const scriptPath = chosen?.path || window.api.pathJoin("apps", "webauto", "entry", "xhs-unified.mjs");
|
|
725
|
+
script = scriptPath;
|
|
726
|
+
args = buildArgs2([script, ...profileArgs, ...common, ...extraArgs]);
|
|
727
|
+
} else if (t === "smartReply") {
|
|
728
|
+
script = window.api.pathJoin("apps", "webauto", "entry", "xhs-unified.mjs");
|
|
729
|
+
args = buildArgs2([
|
|
730
|
+
script,
|
|
731
|
+
...profileArgs,
|
|
732
|
+
...common,
|
|
733
|
+
"--do-comments",
|
|
734
|
+
"true",
|
|
735
|
+
"--do-reply",
|
|
736
|
+
"true",
|
|
737
|
+
"--do-likes",
|
|
738
|
+
"false",
|
|
739
|
+
...extraArgs
|
|
740
|
+
]);
|
|
741
|
+
} else if (t === "virtualLike") {
|
|
742
|
+
script = window.api.pathJoin("apps", "webauto", "entry", "xhs-unified.mjs");
|
|
743
|
+
args = buildArgs2([
|
|
744
|
+
script,
|
|
745
|
+
...profileArgs,
|
|
746
|
+
...common,
|
|
747
|
+
"--do-comments",
|
|
748
|
+
"true",
|
|
749
|
+
"--do-likes",
|
|
750
|
+
"true",
|
|
751
|
+
...extraArgs
|
|
752
|
+
]);
|
|
753
|
+
}
|
|
754
|
+
const ok = await window.api.cmdSpawn({
|
|
755
|
+
title: `${t} ${keyword}`,
|
|
756
|
+
cwd: "",
|
|
757
|
+
args,
|
|
758
|
+
groupKey: "xiaohongshu"
|
|
759
|
+
});
|
|
760
|
+
if (!ok || ok.runId == null) {
|
|
761
|
+
alert("\u8FD0\u884C\u5931\u8D25\uFF1A\u547D\u4EE4\u672A\u542F\u52A8\uFF08\u8BF7\u68C0\u67E5\u8F93\u5165\u53C2\u6570\u6216\u65E5\u5FD7\uFF09");
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
async function stop() {
|
|
765
|
+
if (!ctx2.activeRunId) return;
|
|
766
|
+
await window.api.cmdKill(ctx2.activeRunId);
|
|
767
|
+
}
|
|
768
|
+
const actions = createEl("div", { className: "row" }, [
|
|
769
|
+
createEl("button", {}, ["\u8FD0\u884C"]),
|
|
770
|
+
createEl("button", { className: "danger" }, ["\u505C\u6B62"])
|
|
771
|
+
]);
|
|
772
|
+
actions.children[0].onclick = () => void run();
|
|
773
|
+
actions.children[1].onclick = () => void stop();
|
|
774
|
+
root.appendChild(
|
|
775
|
+
section("\u8C03\u7528", [
|
|
776
|
+
createEl("div", { className: "muted" }, [
|
|
777
|
+
"\u8BF4\u660E\uFF1A\u5C0F\u7EA2\u4E66\u5B8C\u6574\u7F16\u6392\uFF08Phase1/2/Unified + \u5206\u7247\uFF09\u5DF2\u8FC1\u79FB\u5230\u201C\u5C0F\u7EA2\u4E66\u201DTab\uFF1B\u8FD9\u91CC\u4EC5\u4FDD\u7559\u8C03\u8BD5\u5165\u53E3\u3002"
|
|
778
|
+
]),
|
|
779
|
+
createEl("div", { className: "row" }, [
|
|
780
|
+
labeledInput("template", templateSel),
|
|
781
|
+
labeledInput("keyword", keywordInput),
|
|
782
|
+
labeledInput("target", targetInput),
|
|
783
|
+
labeledInput("env", envSel),
|
|
784
|
+
createEl("div", { style: "display:flex; flex-direction:column; gap:6px;" }, [
|
|
785
|
+
createEl("label", {}, ["dry-run"]),
|
|
786
|
+
dryRun
|
|
787
|
+
])
|
|
788
|
+
]),
|
|
789
|
+
createEl("div", { className: "row" }, [
|
|
790
|
+
labeledInput("profile mode", profileModeSel),
|
|
791
|
+
createEl("div", { style: "display:flex; gap:8px; align-items:center;" }, [profilePickSel, runtimePickSel, poolPickSel, profileRefreshBtn]),
|
|
792
|
+
labeledInput("extra", extraInput)
|
|
793
|
+
]),
|
|
794
|
+
profilesHint,
|
|
795
|
+
resolvedHint,
|
|
796
|
+
profilesBox,
|
|
797
|
+
actions,
|
|
798
|
+
createEl("div", { className: "muted" }, [
|
|
799
|
+
"profile\uFF1A\u5355 profile\uFF1Bprofilepool\uFF1A\u6309 keyword \u524D\u7F00\u626B\u63CF pool \u81EA\u52A8\u5206\u7247\uFF1Bprofiles\uFF1A\u624B\u52A8 a,b,c \u81EA\u52A8\u5206\u7247\u3002"
|
|
800
|
+
])
|
|
801
|
+
])
|
|
802
|
+
);
|
|
803
|
+
templateSel.onchange = () => {
|
|
804
|
+
setProfileModes(templateSel.value);
|
|
805
|
+
syncProfileValueFromUI();
|
|
806
|
+
void refreshRuntimes();
|
|
807
|
+
};
|
|
808
|
+
profileModeSel.onchange = async () => {
|
|
809
|
+
syncProfileValueFromUI();
|
|
810
|
+
if (profileModeSel.value === "profilepool") {
|
|
811
|
+
const res = await window.api.profilesList().catch(() => null);
|
|
812
|
+
const profiles = res?.profiles || [];
|
|
813
|
+
const poolKeys = derivePoolKeys(profiles);
|
|
814
|
+
if (poolKeys.length > 0 && !poolPickSel.value) {
|
|
815
|
+
poolPickSel.value = poolKeys[0];
|
|
816
|
+
syncProfileValueFromUI();
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
profilePickSel.onchange = () => {
|
|
821
|
+
syncProfileValueFromUI();
|
|
822
|
+
};
|
|
823
|
+
runtimePickSel.onchange = () => {
|
|
824
|
+
syncProfileValueFromUI();
|
|
825
|
+
};
|
|
826
|
+
poolPickSel.onchange = () => syncProfileValueFromUI();
|
|
827
|
+
profileRefreshBtn.onclick = () => void refreshProfiles();
|
|
828
|
+
setProfileModes(templateSel.value);
|
|
829
|
+
void refreshProfiles();
|
|
830
|
+
void refreshRuntimes();
|
|
831
|
+
void refreshFullCollectScripts();
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// src/renderer/tabs/results.mts
|
|
835
|
+
function renderResults(root, ctx2) {
|
|
836
|
+
const entryList = createEl("div", { className: "list" });
|
|
837
|
+
const fileList = createEl("div", { className: "list" });
|
|
838
|
+
const rightTop = createEl("div", {});
|
|
839
|
+
const preview = createEl("div", {});
|
|
840
|
+
const filterInput = createEl("input", { placeholder: "filter filename (contains)", value: "" });
|
|
841
|
+
const onlyImages = createEl("input", { type: "checkbox" });
|
|
842
|
+
const onlyText = createEl("input", { type: "checkbox" });
|
|
843
|
+
const statusLine = createEl("div", { className: "muted" }, [""]);
|
|
844
|
+
let currentEntry = null;
|
|
845
|
+
let currentFiles = [];
|
|
846
|
+
async function refresh() {
|
|
847
|
+
entryList.textContent = "";
|
|
848
|
+
fileList.textContent = "";
|
|
849
|
+
rightTop.textContent = "";
|
|
850
|
+
preview.textContent = "";
|
|
851
|
+
statusLine.textContent = "";
|
|
852
|
+
currentEntry = null;
|
|
853
|
+
currentFiles = [];
|
|
854
|
+
const res = await window.api.resultsScan({ downloadRoot: ctx2.settings?.downloadRoot });
|
|
855
|
+
if (!res?.ok) {
|
|
856
|
+
entryList.appendChild(createEl("div", { className: "item" }, [String(res?.error || "scan failed")]));
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
const entries = res.entries || [];
|
|
860
|
+
entries.forEach((e) => {
|
|
861
|
+
const s = e.state || null;
|
|
862
|
+
const badge = s && s.status ? ` [${s.status}] links=${s.links || 0}/${s.target || 0} ok=${s.completed || 0} fail=${s.failed || 0}` : " [no-state]";
|
|
863
|
+
const item = createEl("div", { className: "item" }, [`${e.env}/${e.keyword}${badge}`]);
|
|
864
|
+
item.onclick = () => void openEntry(e);
|
|
865
|
+
entryList.appendChild(item);
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
async function openEntry(e) {
|
|
869
|
+
currentEntry = e;
|
|
870
|
+
currentFiles = [];
|
|
871
|
+
rightTop.textContent = "";
|
|
872
|
+
preview.textContent = "";
|
|
873
|
+
fileList.textContent = "";
|
|
874
|
+
const openDirBtn = createEl("button", { className: "secondary" }, ["\u6253\u5F00\u76EE\u5F55"]);
|
|
875
|
+
openDirBtn.onclick = () => void window.api.osOpenPath(e.path);
|
|
876
|
+
rightTop.appendChild(createEl("div", { className: "row" }, [
|
|
877
|
+
openDirBtn,
|
|
878
|
+
createEl("div", { className: "muted" }, [String(e.path || "")])
|
|
879
|
+
]));
|
|
880
|
+
const res = await window.api.fsListDir({ root: e.path, recursive: true, maxEntries: 2e3 });
|
|
881
|
+
if (!res?.ok) {
|
|
882
|
+
statusLine.textContent = `list failed: ${String(res?.error || "")}`;
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
const files = (res.entries || []).filter((x) => x && x.isDir === false);
|
|
886
|
+
currentFiles = files;
|
|
887
|
+
statusLine.textContent = `files=${files.length}${res.truncated ? " (truncated)" : ""}`;
|
|
888
|
+
renderFileList();
|
|
889
|
+
}
|
|
890
|
+
function isImagePath(p) {
|
|
891
|
+
const ext = p.toLowerCase().split(".").pop() || "";
|
|
892
|
+
return ext === "png" || ext === "jpg" || ext === "jpeg" || ext === "webp";
|
|
893
|
+
}
|
|
894
|
+
function isTextPath(p) {
|
|
895
|
+
const ext = p.toLowerCase().split(".").pop() || "";
|
|
896
|
+
return ext === "jsonl" || ext === "json" || ext === "txt" || ext === "log" || ext === "md";
|
|
897
|
+
}
|
|
898
|
+
function renderFileList() {
|
|
899
|
+
fileList.textContent = "";
|
|
900
|
+
preview.textContent = "";
|
|
901
|
+
const needle = filterInput.value.trim().toLowerCase();
|
|
902
|
+
const list = currentFiles.filter((x) => {
|
|
903
|
+
const rel = String(x.rel || x.name || "").toLowerCase();
|
|
904
|
+
if (needle && !rel.includes(needle)) return false;
|
|
905
|
+
if (onlyImages.checked && !isImagePath(rel)) return false;
|
|
906
|
+
if (onlyText.checked && !isTextPath(rel)) return false;
|
|
907
|
+
return true;
|
|
908
|
+
}).slice(0, 600);
|
|
909
|
+
list.forEach((f) => {
|
|
910
|
+
const label = String(f.rel || f.name || "");
|
|
911
|
+
const item = createEl("div", { className: "item" }, [label]);
|
|
912
|
+
item.onclick = () => void previewFile(f);
|
|
913
|
+
fileList.appendChild(item);
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
async function previewFile(f) {
|
|
917
|
+
preview.textContent = "";
|
|
918
|
+
const p = String(f.path || "");
|
|
919
|
+
if (!p) return;
|
|
920
|
+
const actions = createEl("div", { className: "row" }, [
|
|
921
|
+
createEl("button", { className: "secondary" }, ["\u6253\u5F00\u6587\u4EF6"])
|
|
922
|
+
]);
|
|
923
|
+
actions.querySelector("button").onclick = () => void window.api.osOpenPath(p);
|
|
924
|
+
preview.appendChild(actions);
|
|
925
|
+
if (isImagePath(p)) {
|
|
926
|
+
const res = await window.api.fsReadFileBase64({ path: p, maxBytes: 8e6 });
|
|
927
|
+
if (!res?.ok) {
|
|
928
|
+
preview.appendChild(createEl("div", { className: "muted" }, [String(res?.error || "read image failed")]));
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
const ext = p.toLowerCase().endsWith(".png") ? "png" : p.toLowerCase().endsWith(".webp") ? "webp" : "jpeg";
|
|
932
|
+
const img = createEl("img", { style: "max-width:100%; border:1px solid #ddd; border-radius:6px;" });
|
|
933
|
+
img.src = `data:image/${ext};base64,${res.data}`;
|
|
934
|
+
preview.appendChild(img);
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
if (isTextPath(p)) {
|
|
938
|
+
await previewText(p);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
preview.appendChild(createEl("div", { className: "muted" }, ["(no preview)"]));
|
|
942
|
+
}
|
|
943
|
+
async function previewText(filePath) {
|
|
944
|
+
const res = await window.api.fsReadTextPreview({ path: filePath, maxBytes: 5e4, maxLines: 200 });
|
|
945
|
+
if (!res?.ok) {
|
|
946
|
+
preview.appendChild(createEl("div", { className: "muted" }, [String(res?.error || "read failed")]));
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
preview.appendChild(createEl("pre", { style: "white-space:pre-wrap; margin:0;" }, [res.text]));
|
|
950
|
+
}
|
|
951
|
+
root.appendChild(
|
|
952
|
+
section("\u7ED3\u679C\u76EE\u5F55", [
|
|
953
|
+
createEl("div", { className: "row" }, [
|
|
954
|
+
createEl("button", { className: "secondary" }, ["\u5237\u65B0"]),
|
|
955
|
+
createEl("div", { className: "muted" }, [`root=${ctx2.settings?.downloadRoot || ""}`])
|
|
956
|
+
]),
|
|
957
|
+
createEl("div", { className: "row" }, [
|
|
958
|
+
createEl("div", { style: "display:flex; flex-direction:column; gap:6px;" }, [
|
|
959
|
+
createEl("label", {}, ["only images"]),
|
|
960
|
+
onlyImages
|
|
961
|
+
]),
|
|
962
|
+
createEl("div", { style: "display:flex; flex-direction:column; gap:6px;" }, [
|
|
963
|
+
createEl("label", {}, ["only text"]),
|
|
964
|
+
onlyText
|
|
965
|
+
]),
|
|
966
|
+
filterInput,
|
|
967
|
+
statusLine
|
|
968
|
+
]),
|
|
969
|
+
createEl("div", { className: "split" }, [
|
|
970
|
+
entryList,
|
|
971
|
+
createEl("div", {}, [rightTop, fileList, preview])
|
|
972
|
+
])
|
|
973
|
+
])
|
|
974
|
+
);
|
|
975
|
+
root.querySelector("button").onclick = () => void refresh();
|
|
976
|
+
filterInput.oninput = () => renderFileList();
|
|
977
|
+
onlyImages.onchange = () => renderFileList();
|
|
978
|
+
onlyText.onchange = () => renderFileList();
|
|
979
|
+
void refresh();
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// src/renderer/tabs/debug.mts
|
|
983
|
+
var activeDebugView = "run";
|
|
984
|
+
function renderDebug(root, ctx2) {
|
|
985
|
+
root.textContent = "";
|
|
986
|
+
const title = createEl("div", { style: "font-weight:700; margin-bottom:8px;" }, ["Debug \xB7 \u8C03\u7528\u4E0E\u7ED3\u679C"]);
|
|
987
|
+
const sub = createEl("div", { className: "muted", style: "margin-bottom:10px; font-size:12px;" }, [
|
|
988
|
+
"\u201C\u8C03\u7528\u201D\u548C\u201C\u7ED3\u679C\u201D\u5DF2\u6574\u5408\u5230 Debug \u9875\uFF1BRuntime \u5DF2\u4ECE\u4E3B\u5BFC\u822A\u79FB\u9664\u3002"
|
|
989
|
+
]);
|
|
990
|
+
const switcher = createEl("div", { className: "row", style: "margin-bottom:8px;" });
|
|
991
|
+
const runBtn = createEl("button", { type: "button" }, ["\u8C03\u7528"]);
|
|
992
|
+
const resultsBtn = createEl("button", { type: "button", className: "secondary" }, ["\u7ED3\u679C"]);
|
|
993
|
+
const panel = createEl("div");
|
|
994
|
+
const setView = (id) => {
|
|
995
|
+
activeDebugView = id;
|
|
996
|
+
runBtn.className = id === "run" ? "" : "secondary";
|
|
997
|
+
resultsBtn.className = id === "results" ? "" : "secondary";
|
|
998
|
+
panel.textContent = "";
|
|
999
|
+
if (id === "run") {
|
|
1000
|
+
renderRun(panel, ctx2);
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
renderResults(panel, ctx2);
|
|
1004
|
+
};
|
|
1005
|
+
runBtn.onclick = () => setView("run");
|
|
1006
|
+
resultsBtn.onclick = () => setView("results");
|
|
1007
|
+
switcher.appendChild(runBtn);
|
|
1008
|
+
switcher.appendChild(resultsBtn);
|
|
1009
|
+
root.appendChild(title);
|
|
1010
|
+
root.appendChild(sub);
|
|
1011
|
+
root.appendChild(switcher);
|
|
1012
|
+
root.appendChild(panel);
|
|
1013
|
+
setView(activeDebugView);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// src/renderer/tabs/settings.mts
|
|
1017
|
+
function renderSettings(root, ctx2) {
|
|
1018
|
+
const coreDaemon = createEl("input", { value: ctx2.settings?.coreDaemonUrl || "http://127.0.0.1:7700" });
|
|
1019
|
+
const download = createEl("input", { value: ctx2.settings?.downloadRoot || "" });
|
|
1020
|
+
const env = createEl("select");
|
|
1021
|
+
["debug", "prod"].forEach((x) => env.appendChild(createEl("option", { value: x }, [x])));
|
|
1022
|
+
env.value = ctx2.settings?.defaultEnv || "debug";
|
|
1023
|
+
const keyword = createEl("input", { value: ctx2.settings?.defaultKeyword || "" });
|
|
1024
|
+
const loginTimeout = createEl("input", { value: String(ctx2.settings?.timeouts?.loginTimeoutSec || 900), type: "number", min: "30" });
|
|
1025
|
+
const cmdTimeout = createEl("input", { value: String(ctx2.settings?.timeouts?.cmdTimeoutSec || 0), type: "number", min: "0" });
|
|
1026
|
+
const aiEnabled = createEl("input", { type: "checkbox", checked: ctx2.settings?.aiReply?.enabled ?? false });
|
|
1027
|
+
const aiBaseUrl = createEl("input", { value: ctx2.settings?.aiReply?.baseUrl || "http://127.0.0.1:5520", placeholder: "http://127.0.0.1:5520" });
|
|
1028
|
+
const aiApiKey = createEl("input", { value: ctx2.settings?.aiReply?.apiKey || "", type: "password", placeholder: "sk-..." });
|
|
1029
|
+
const aiModel = createEl("input", { value: ctx2.settings?.aiReply?.model || "iflow.glm-5", placeholder: "iflow.glm-5" });
|
|
1030
|
+
const aiTemperature = createEl("input", { value: String(ctx2.settings?.aiReply?.temperature ?? 0.7), type: "number", min: "0", max: "2", step: "0.1" });
|
|
1031
|
+
const aiMaxChars = createEl("input", { value: String(ctx2.settings?.aiReply?.maxChars ?? 20), type: "number", min: "5", max: "500" });
|
|
1032
|
+
const aiTimeout = createEl("input", { value: String(ctx2.settings?.aiReply?.timeoutMs ?? 25e3), type: "number", min: "5000", step: "1000" });
|
|
1033
|
+
const aiStyle = createEl("select");
|
|
1034
|
+
["friendly", "professional", "humorous", "concise", "custom"].forEach((x) => aiStyle.appendChild(createEl("option", { value: x }, [x])));
|
|
1035
|
+
aiStyle.value = ctx2.settings?.aiReply?.stylePreset || "friendly";
|
|
1036
|
+
const aiStyleCustom = createEl("input", { value: ctx2.settings?.aiReply?.styleCustom || "", placeholder: "\u81EA\u5B9A\u4E49\u98CE\u683C\u63CF\u8FF0\uFF08\u53EF\u9009\uFF09" });
|
|
1037
|
+
const aiTestResult = createEl("div", { className: "muted", style: "min-height:1.5em;" });
|
|
1038
|
+
async function fetchModels() {
|
|
1039
|
+
try {
|
|
1040
|
+
aiTestResult.textContent = "\u83B7\u53D6\u6A21\u578B\u5217\u8868\u4E2D...";
|
|
1041
|
+
const result = await window.api.invoke("ai:listModels", {
|
|
1042
|
+
baseUrl: aiBaseUrl.value.trim(),
|
|
1043
|
+
apiKey: aiApiKey.value.trim()
|
|
1044
|
+
});
|
|
1045
|
+
if (result.ok && result.models?.length > 0) {
|
|
1046
|
+
aiTestResult.textContent = "\u627E\u5230 " + result.models.length + " \u4E2A\u6A21\u578B: " + result.models.slice(0, 3).join(", ") + (result.models.length > 3 ? "..." : "");
|
|
1047
|
+
if (!aiModel.value && result.models[0]) {
|
|
1048
|
+
aiModel.value = result.models[0];
|
|
1049
|
+
}
|
|
1050
|
+
} else {
|
|
1051
|
+
aiTestResult.textContent = result.error ? "\u9519\u8BEF: " + result.error : "\u672A\u627E\u5230\u6A21\u578B";
|
|
1052
|
+
}
|
|
1053
|
+
} catch (e) {
|
|
1054
|
+
aiTestResult.textContent = "\u9519\u8BEF: " + (e?.message || String(e));
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
async function testConnection() {
|
|
1058
|
+
try {
|
|
1059
|
+
aiTestResult.textContent = "\u6D4B\u8BD5\u8FDE\u901A\u6027...";
|
|
1060
|
+
const result = await window.api.invoke("ai:testChatCompletion", {
|
|
1061
|
+
baseUrl: aiBaseUrl.value.trim(),
|
|
1062
|
+
apiKey: aiApiKey.value.trim(),
|
|
1063
|
+
model: aiModel.value.trim() || "iflow.glm-5",
|
|
1064
|
+
timeoutMs: Number(aiTimeout.value) || 25e3
|
|
1065
|
+
});
|
|
1066
|
+
if (result.ok) {
|
|
1067
|
+
aiTestResult.textContent = "\u8FDE\u901A\u6210\u529F (" + result.latencyMs + "ms)";
|
|
1068
|
+
} else {
|
|
1069
|
+
aiTestResult.textContent = result.error ? "\u9519\u8BEF: " + result.error : "\u6D4B\u8BD5\u5931\u8D25";
|
|
1070
|
+
}
|
|
1071
|
+
} catch (e) {
|
|
1072
|
+
aiTestResult.textContent = "\u9519\u8BEF: " + (e?.message || String(e));
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
const configPath = resolveConfigPath(ctx2.settings?.downloadRoot || "", window.api);
|
|
1076
|
+
const debugHost = createEl("div");
|
|
1077
|
+
async function save() {
|
|
1078
|
+
const next = await window.api.settingsSet({
|
|
1079
|
+
coreDaemonUrl: coreDaemon.value.trim(),
|
|
1080
|
+
downloadRoot: download.value.trim(),
|
|
1081
|
+
defaultEnv: env.value,
|
|
1082
|
+
defaultKeyword: keyword.value,
|
|
1083
|
+
timeouts: {
|
|
1084
|
+
loginTimeoutSec: Number(loginTimeout.value || "900"),
|
|
1085
|
+
cmdTimeoutSec: Number(cmdTimeout.value || "0")
|
|
1086
|
+
},
|
|
1087
|
+
aiReply: {
|
|
1088
|
+
enabled: aiEnabled.checked,
|
|
1089
|
+
baseUrl: aiBaseUrl.value.trim(),
|
|
1090
|
+
apiKey: aiApiKey.value.trim(),
|
|
1091
|
+
model: aiModel.value.trim(),
|
|
1092
|
+
temperature: Number(aiTemperature.value) || 0.7,
|
|
1093
|
+
maxChars: Number(aiMaxChars.value) || 20,
|
|
1094
|
+
timeoutMs: Number(aiTimeout.value) || 25e3,
|
|
1095
|
+
stylePreset: aiStyle.value,
|
|
1096
|
+
styleCustom: aiStyleCustom.value.trim()
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
ctx2.settings = next;
|
|
1100
|
+
ctx2.appendLog("[settings] saved");
|
|
1101
|
+
}
|
|
1102
|
+
root.appendChild(
|
|
1103
|
+
section("\u8BBE\u7F6E", [
|
|
1104
|
+
createEl("div", { className: "row" }, [
|
|
1105
|
+
labeledInput("Core Daemon", coreDaemon)
|
|
1106
|
+
]),
|
|
1107
|
+
createEl("div", { className: "row" }, [
|
|
1108
|
+
labeledInput("downloadRoot", download),
|
|
1109
|
+
labeledInput("defaultEnv", env),
|
|
1110
|
+
labeledInput("defaultKeyword", keyword)
|
|
1111
|
+
]),
|
|
1112
|
+
createEl("div", { className: "row" }, [
|
|
1113
|
+
labeledInput("loginTimeoutSec", loginTimeout),
|
|
1114
|
+
labeledInput("cmdTimeoutSec", cmdTimeout)
|
|
1115
|
+
]),
|
|
1116
|
+
createEl("div", { className: "row" }, [
|
|
1117
|
+
createEl("button", {}, ["\u4FDD\u5B58"])
|
|
1118
|
+
]),
|
|
1119
|
+
createEl("div", { className: "muted" }, [`\u4FDD\u5B58\u5230 ${configPath} \u7684 desktopConsole \u914D\u7F6E\u5757\uFF08\u82E5 dist/modules/config \u4E0D\u53EF\u7528\u5219 fallback \u5230 legacy settings \u6587\u4EF6\uFF09`]),
|
|
1120
|
+
section("AI \u667A\u80FD\u56DE\u590D", [
|
|
1121
|
+
createEl("div", { className: "row" }, [
|
|
1122
|
+
labeledInput("\u542F\u7528\u667A\u80FD\u56DE\u590D", aiEnabled)
|
|
1123
|
+
]),
|
|
1124
|
+
createEl("div", { className: "row" }, [
|
|
1125
|
+
labeledInput("API Base URL", aiBaseUrl),
|
|
1126
|
+
labeledInput("API Key", aiApiKey)
|
|
1127
|
+
]),
|
|
1128
|
+
createEl("div", { className: "row" }, [
|
|
1129
|
+
labeledInput("\u6A21\u578B", aiModel),
|
|
1130
|
+
createEl("button", { style: "margin-left:8px;" }, ["\u83B7\u53D6\u6A21\u578B\u5217\u8868"]),
|
|
1131
|
+
createEl("button", { style: "margin-left:8px;" }, ["\u6D4B\u8BD5\u8FDE\u901A"])
|
|
1132
|
+
]),
|
|
1133
|
+
createEl("div", { className: "row" }, [
|
|
1134
|
+
labeledInput("Temperature", aiTemperature),
|
|
1135
|
+
labeledInput("\u6700\u5927\u5B57\u6570", aiMaxChars),
|
|
1136
|
+
labeledInput("\u8D85\u65F6(ms)", aiTimeout)
|
|
1137
|
+
]),
|
|
1138
|
+
createEl("div", { className: "row" }, [
|
|
1139
|
+
labeledInput("\u56DE\u590D\u98CE\u683C", aiStyle),
|
|
1140
|
+
labeledInput("\u81EA\u5B9A\u4E49\u98CE\u683C", aiStyleCustom)
|
|
1141
|
+
]),
|
|
1142
|
+
aiTestResult
|
|
1143
|
+
]),
|
|
1144
|
+
section("\u8C03\u8BD5\uFF08\u5DF2\u5E76\u5165\u8BBE\u7F6E\uFF09", [debugHost])
|
|
1145
|
+
])
|
|
1146
|
+
);
|
|
1147
|
+
root.querySelector("button").onclick = () => void save();
|
|
1148
|
+
const buttons = root.querySelectorAll("button");
|
|
1149
|
+
if (buttons[1]) buttons[1].onclick = () => void fetchModels();
|
|
1150
|
+
if (buttons[2]) buttons[2].onclick = () => void testConnection();
|
|
1151
|
+
renderDebug(debugHost, ctx2);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// src/renderer/tabs/logs.mts
|
|
1155
|
+
function renderLogs(root, ctx2) {
|
|
1156
|
+
root.textContent = "";
|
|
1157
|
+
const title = createEl("div", { style: "font-weight:700; margin-bottom:8px;" }, ["\u65E5\u5FD7 \xB7 Logs"]);
|
|
1158
|
+
const sub = createEl("div", { className: "muted", style: "margin-bottom:10px; font-size:12px;" }, [
|
|
1159
|
+
"\u547D\u4EE4\u4E8B\u4EF6\u7684\u8FD0\u884C\u65E5\u5FD7\uFF08\u4ECE\u4E3B\u58F3\u4E0A\u4E0B\u6587\u6536\u96C6\uFF09\u3002"
|
|
1160
|
+
]);
|
|
1161
|
+
const toolbar = createEl("div", { className: "row", style: "margin-bottom:8px;" });
|
|
1162
|
+
const clearBtn = createEl("button", { type: "button", className: "secondary" }, ["\u6E05\u7A7A\u65E5\u5FD7"]);
|
|
1163
|
+
const copyGlobalBtn = createEl("button", { type: "button", className: "secondary", title: "\u590D\u5236\u516C\u5171\u65E5\u5FD7\uFF08Ctrl/Cmd+Shift+1\uFF09" }, ["\u590D\u5236\u516C\u5171\u65E5\u5FD7"]);
|
|
1164
|
+
const copyShardBtn = createEl("button", { type: "button", className: "secondary", title: "\u590D\u5236\u5206\u7247\u65E5\u5FD7\uFF08Ctrl/Cmd+Shift+2\uFF09" }, ["\u590D\u5236\u5206\u7247\u65E5\u5FD7"]);
|
|
1165
|
+
const activeOnlyCheckbox = createEl("input", { type: "checkbox", id: "logs-active-only" });
|
|
1166
|
+
activeOnlyCheckbox.checked = true;
|
|
1167
|
+
const activeOnlyLabel = createEl("label", { htmlFor: "logs-active-only", style: "cursor:pointer; user-select:none;" }, ["\u4EC5\u663E\u793A\u6D3B\u8DC3\u5206\u7247"]);
|
|
1168
|
+
const showGlobalCheckbox = createEl("input", { type: "checkbox", id: "logs-show-global" });
|
|
1169
|
+
showGlobalCheckbox.checked = false;
|
|
1170
|
+
const showGlobalLabel = createEl("label", { htmlFor: "logs-show-global", style: "cursor:pointer; user-select:none;" }, ["\u663E\u793A\u516C\u5171\u65E5\u5FD7"]);
|
|
1171
|
+
toolbar.appendChild(clearBtn);
|
|
1172
|
+
toolbar.appendChild(copyGlobalBtn);
|
|
1173
|
+
toolbar.appendChild(copyShardBtn);
|
|
1174
|
+
toolbar.appendChild(activeOnlyCheckbox);
|
|
1175
|
+
toolbar.appendChild(activeOnlyLabel);
|
|
1176
|
+
toolbar.appendChild(showGlobalCheckbox);
|
|
1177
|
+
toolbar.appendChild(showGlobalLabel);
|
|
1178
|
+
const globalContainer = createEl("div", {
|
|
1179
|
+
style: "display:flex; flex-direction:column; gap:10px; margin-bottom:10px;"
|
|
1180
|
+
});
|
|
1181
|
+
const shardContainer = createEl("div", {
|
|
1182
|
+
style: 'display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px; align-items:start; font-family:"Cascadia Mono", Consolas, ui-monospace, SFMono-Regular, Menlo, monospace; font-size:12px;'
|
|
1183
|
+
});
|
|
1184
|
+
const sectionMap = /* @__PURE__ */ new Map();
|
|
1185
|
+
const sectionCardMap = /* @__PURE__ */ new Map();
|
|
1186
|
+
const sectionRunIds = /* @__PURE__ */ new Map();
|
|
1187
|
+
const sectionHeaderMap = /* @__PURE__ */ new Map();
|
|
1188
|
+
const sectionTypeMap = /* @__PURE__ */ new Map();
|
|
1189
|
+
const logActiveRunIds = /* @__PURE__ */ new Set();
|
|
1190
|
+
const parentRunIds = /* @__PURE__ */ new Set();
|
|
1191
|
+
const runIdToSection = /* @__PURE__ */ new Map();
|
|
1192
|
+
const parentRunCurrentSection = /* @__PURE__ */ new Map();
|
|
1193
|
+
const shardRunIds = /* @__PURE__ */ new Set();
|
|
1194
|
+
let selectedSectionKey = "global";
|
|
1195
|
+
let shardProfileQueue = [];
|
|
1196
|
+
const parseShardHint = (line) => {
|
|
1197
|
+
const text = String(line || "");
|
|
1198
|
+
const hinted = text.match(/\[shard-hint\]\s*profiles=([A-Za-z0-9_,-]+)/i);
|
|
1199
|
+
const orchestrate = text.match(/\[orchestrate\][^\n]*\bprofiles=([A-Za-z0-9_,-]+)/i);
|
|
1200
|
+
const raw = hinted?.[1] || orchestrate?.[1] || "";
|
|
1201
|
+
if (!raw) return [];
|
|
1202
|
+
return String(raw).split(",").map((x) => x.trim()).filter(Boolean);
|
|
1203
|
+
};
|
|
1204
|
+
const isShardSection = (sectionKey) => String(sectionKey || "").startsWith("shard:");
|
|
1205
|
+
const extractRunId = (line) => {
|
|
1206
|
+
const text = String(line || "");
|
|
1207
|
+
const byRunTag = text.match(/^\[(?:run:|rid:)([A-Za-z0-9_-]+)\]\s*(.*)$/);
|
|
1208
|
+
const prefixedRunId = byRunTag?.[1] ? String(byRunTag[1]) : "";
|
|
1209
|
+
const tailText = byRunTag?.[2] ? String(byRunTag[2]) : text;
|
|
1210
|
+
const byRunIdField = tailText.match(/runId=([A-Za-z0-9_-]+)/);
|
|
1211
|
+
if (byRunIdField?.[1]) return String(byRunIdField[1]);
|
|
1212
|
+
const byRunIdTag = tailText.match(/\[runId:([A-Za-z0-9_-]+)\]/);
|
|
1213
|
+
if (byRunIdTag?.[1]) return String(byRunIdTag[1]);
|
|
1214
|
+
if (prefixedRunId) return prefixedRunId;
|
|
1215
|
+
const byRunTagAnyPos = text.match(/\[(?:run:|rid:)([A-Za-z0-9_-]+)\]/);
|
|
1216
|
+
if (byRunTagAnyPos?.[1]) return String(byRunTagAnyPos[1]);
|
|
1217
|
+
return "global";
|
|
1218
|
+
};
|
|
1219
|
+
const extractPrefixedRunId = (line) => {
|
|
1220
|
+
const text = String(line || "");
|
|
1221
|
+
const byRunTag = text.match(/^\[(?:run:|rid:)([A-Za-z0-9_-]+)\]/);
|
|
1222
|
+
return byRunTag?.[1] ? String(byRunTag[1]) : "";
|
|
1223
|
+
};
|
|
1224
|
+
const ensureSection = (sectionKey, headerLabel) => {
|
|
1225
|
+
const normalized = String(sectionKey || "global").trim() || "global";
|
|
1226
|
+
const existing = sectionMap.get(normalized);
|
|
1227
|
+
if (existing) {
|
|
1228
|
+
if (headerLabel && sectionHeaderMap.has(normalized)) {
|
|
1229
|
+
sectionHeaderMap.get(normalized).textContent = headerLabel;
|
|
1230
|
+
}
|
|
1231
|
+
return existing;
|
|
1232
|
+
}
|
|
1233
|
+
const card = createEl("div", {
|
|
1234
|
+
style: "border:1px solid #23262f; background:#0b0d12; border-radius:10px; overflow:hidden; min-width:0;"
|
|
1235
|
+
});
|
|
1236
|
+
const head = createEl("div", {
|
|
1237
|
+
style: "padding:8px 12px; border-bottom:1px solid #23262f; background:#121622; font-weight:600; color:#9aa4bd;"
|
|
1238
|
+
}, [headerLabel || (normalized === "global" ? "\u516C\u5171\u65E5\u5FD7" : `runId: ${normalized}`)]);
|
|
1239
|
+
const body = createEl("div", {
|
|
1240
|
+
style: "padding:10px 12px; white-space:pre-wrap; word-break:break-all; line-height:1.5; height:calc(100vh - 220px); overflow:auto;"
|
|
1241
|
+
});
|
|
1242
|
+
card.tabIndex = 0;
|
|
1243
|
+
card.appendChild(head);
|
|
1244
|
+
card.appendChild(body);
|
|
1245
|
+
if (normalized === "global" || !isShardSection(normalized)) {
|
|
1246
|
+
globalContainer.appendChild(card);
|
|
1247
|
+
sectionTypeMap.set(normalized, "global");
|
|
1248
|
+
} else {
|
|
1249
|
+
shardContainer.appendChild(card);
|
|
1250
|
+
sectionTypeMap.set(normalized, "shard");
|
|
1251
|
+
}
|
|
1252
|
+
sectionMap.set(normalized, body);
|
|
1253
|
+
sectionCardMap.set(normalized, card);
|
|
1254
|
+
sectionRunIds.set(normalized, /* @__PURE__ */ new Set());
|
|
1255
|
+
sectionHeaderMap.set(normalized, head);
|
|
1256
|
+
const activateCard = () => {
|
|
1257
|
+
selectedSectionKey = normalized;
|
|
1258
|
+
sectionCardMap.forEach((item, key) => {
|
|
1259
|
+
const isSelected = key === selectedSectionKey;
|
|
1260
|
+
item.style.outline = isSelected ? "2px solid #4f86ff" : "none";
|
|
1261
|
+
item.style.outlineOffset = isSelected ? "-2px" : "0";
|
|
1262
|
+
});
|
|
1263
|
+
};
|
|
1264
|
+
card.addEventListener("click", activateCard);
|
|
1265
|
+
card.addEventListener("focus", activateCard);
|
|
1266
|
+
return body;
|
|
1267
|
+
};
|
|
1268
|
+
const getSectionText = (sectionKey) => {
|
|
1269
|
+
const body = sectionMap.get(sectionKey);
|
|
1270
|
+
if (!body) return "";
|
|
1271
|
+
return Array.from(body.children).map((el) => String(el.innerText || el.textContent || "").trim()).filter(Boolean).join("\n");
|
|
1272
|
+
};
|
|
1273
|
+
const collectSectionText = (type) => {
|
|
1274
|
+
return Array.from(sectionMap.keys()).filter((sectionKey) => sectionTypeMap.get(sectionKey) === type).filter((sectionKey) => {
|
|
1275
|
+
const card = sectionCardMap.get(sectionKey);
|
|
1276
|
+
if (!card) return false;
|
|
1277
|
+
return card.style.display !== "none";
|
|
1278
|
+
}).map((sectionKey) => getSectionText(sectionKey)).filter(Boolean).join("\n");
|
|
1279
|
+
};
|
|
1280
|
+
const writeClipboard = async (text) => {
|
|
1281
|
+
if (!text) return { ok: false, error: "empty" };
|
|
1282
|
+
if (typeof window.api?.clipboardWriteText === "function") {
|
|
1283
|
+
return window.api.clipboardWriteText(text);
|
|
1284
|
+
}
|
|
1285
|
+
if (typeof navigator?.clipboard?.writeText === "function") {
|
|
1286
|
+
await navigator.clipboard.writeText(text);
|
|
1287
|
+
return { ok: true };
|
|
1288
|
+
}
|
|
1289
|
+
return { ok: false, error: "clipboard_unavailable" };
|
|
1290
|
+
};
|
|
1291
|
+
const copyByType = async (type) => {
|
|
1292
|
+
const text = collectSectionText(type);
|
|
1293
|
+
if (!text) {
|
|
1294
|
+
ctx2.setStatus?.(`\u65E5\u5FD7\u590D\u5236\u5931\u8D25\uFF1A${type === "global" ? "\u516C\u5171\u65E5\u5FD7\u4E3A\u7A7A" : "\u5206\u7247\u65E5\u5FD7\u4E3A\u7A7A"}`);
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
const ret = await writeClipboard(text);
|
|
1298
|
+
ctx2.setStatus?.(ret?.ok ? `\u5DF2\u590D\u5236${type === "global" ? "\u516C\u5171\u65E5\u5FD7" : "\u5206\u7247\u65E5\u5FD7"}\uFF08${text.split("\n").length}\u884C\uFF09` : `\u65E5\u5FD7\u590D\u5236\u5931\u8D25\uFF1A${ret?.error || "unknown"}`);
|
|
1299
|
+
};
|
|
1300
|
+
const copySelected = async () => {
|
|
1301
|
+
const sectionType = sectionTypeMap.get(selectedSectionKey) || "global";
|
|
1302
|
+
const text = selectedSectionKey ? getSectionText(selectedSectionKey) : "";
|
|
1303
|
+
if (text) {
|
|
1304
|
+
const ret = await writeClipboard(text);
|
|
1305
|
+
ctx2.setStatus?.(
|
|
1306
|
+
ret?.ok ? `\u5DF2\u590D\u5236\u5F53\u524D\u65E5\u5FD7\u5361\u7247\uFF08${selectedSectionKey}\uFF09` : `\u65E5\u5FD7\u590D\u5236\u5931\u8D25\uFF1A${ret?.error || "unknown"}`
|
|
1307
|
+
);
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
await copyByType(sectionType);
|
|
1311
|
+
};
|
|
1312
|
+
const resolveSectionKey = (text, rawRunId, prefixedRunId) => {
|
|
1313
|
+
if (rawRunId === "global") return "global";
|
|
1314
|
+
if (text.includes("[started]") && text.includes("xiaohongshu orchestrate")) {
|
|
1315
|
+
parentRunIds.add(rawRunId);
|
|
1316
|
+
}
|
|
1317
|
+
const mapped = runIdToSection.get(rawRunId);
|
|
1318
|
+
if (mapped) return mapped;
|
|
1319
|
+
if (prefixedRunId && rawRunId === prefixedRunId) {
|
|
1320
|
+
const currentSection = parentRunCurrentSection.get(prefixedRunId);
|
|
1321
|
+
if (currentSection) return currentSection;
|
|
1322
|
+
}
|
|
1323
|
+
if (!parentRunIds.has(rawRunId) && shardProfileQueue.length > 0) {
|
|
1324
|
+
const profile = String(shardProfileQueue.shift() || "").trim();
|
|
1325
|
+
if (profile) {
|
|
1326
|
+
const sectionKey = `shard:${profile}`;
|
|
1327
|
+
runIdToSection.set(rawRunId, sectionKey);
|
|
1328
|
+
shardRunIds.add(rawRunId);
|
|
1329
|
+
ensureSection(sectionKey, `\u5206\u7247: ${profile}`);
|
|
1330
|
+
return sectionKey;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
return rawRunId;
|
|
1334
|
+
};
|
|
1335
|
+
const updateSectionVisibility = () => {
|
|
1336
|
+
const activeOnly = activeOnlyCheckbox.checked;
|
|
1337
|
+
const showGlobal = showGlobalCheckbox.checked;
|
|
1338
|
+
const activeRunIds = ctx2._activeRunIds instanceof Set ? ctx2._activeRunIds : /* @__PURE__ */ new Set();
|
|
1339
|
+
const effectiveActiveRunIds = /* @__PURE__ */ new Set([...activeRunIds, ...logActiveRunIds]);
|
|
1340
|
+
sectionCardMap.forEach((card, sectionKey) => {
|
|
1341
|
+
if (sectionKey === "global") {
|
|
1342
|
+
card.style.display = showGlobal ? "" : "none";
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
if (!isShardSection(sectionKey)) {
|
|
1346
|
+
card.style.display = showGlobal ? "" : "none";
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
if (!activeOnly) {
|
|
1350
|
+
card.style.display = "";
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
const relatedRunIds = sectionRunIds.get(sectionKey) || /* @__PURE__ */ new Set();
|
|
1354
|
+
const visible = effectiveActiveRunIds.has(sectionKey) || shardRunIds.has(sectionKey) || Array.from(relatedRunIds).some((runId) => effectiveActiveRunIds.has(runId));
|
|
1355
|
+
card.style.display = visible ? "" : "none";
|
|
1356
|
+
});
|
|
1357
|
+
};
|
|
1358
|
+
const appendLine = (line) => {
|
|
1359
|
+
const text = String(line || "").trim();
|
|
1360
|
+
if (!text) return;
|
|
1361
|
+
const prefixedRunId = extractPrefixedRunId(text);
|
|
1362
|
+
const rawRunId = extractRunId(text);
|
|
1363
|
+
const hintedProfiles = parseShardHint(text);
|
|
1364
|
+
if (hintedProfiles.length > 0) {
|
|
1365
|
+
shardProfileQueue = hintedProfiles.slice();
|
|
1366
|
+
hintedProfiles.forEach((profile) => {
|
|
1367
|
+
const sectionKey2 = `shard:${profile}`;
|
|
1368
|
+
ensureSection(sectionKey2, `\u5206\u7247: ${profile}`);
|
|
1369
|
+
if (prefixedRunId && rawRunId !== "global") {
|
|
1370
|
+
parentRunCurrentSection.set(prefixedRunId, sectionKey2);
|
|
1371
|
+
runIdToSection.set(rawRunId, sectionKey2);
|
|
1372
|
+
shardRunIds.add(rawRunId);
|
|
1373
|
+
if (!sectionRunIds.has(sectionKey2)) {
|
|
1374
|
+
sectionRunIds.set(sectionKey2, /* @__PURE__ */ new Set());
|
|
1375
|
+
}
|
|
1376
|
+
sectionRunIds.get(sectionKey2).add(rawRunId);
|
|
1377
|
+
}
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
const profileMatch = text.match(/\b[Pp]rofile\s*[::]\s*([A-Za-z0-9_-]+)/);
|
|
1381
|
+
const profileEqMatch = text.match(/\bprofile=([A-Za-z0-9_-]+)/);
|
|
1382
|
+
const resolvedProfile = String(profileMatch?.[1] || profileEqMatch?.[1] || "").trim();
|
|
1383
|
+
if (resolvedProfile) {
|
|
1384
|
+
const profileId = resolvedProfile;
|
|
1385
|
+
if (profileId) {
|
|
1386
|
+
const sectionKey2 = `shard:${profileId}`;
|
|
1387
|
+
ensureSection(sectionKey2, `\u5206\u7247: ${profileId}`);
|
|
1388
|
+
if (prefixedRunId && rawRunId && rawRunId !== prefixedRunId) {
|
|
1389
|
+
runIdToSection.set(rawRunId, sectionKey2);
|
|
1390
|
+
if (!sectionRunIds.has(sectionKey2)) {
|
|
1391
|
+
sectionRunIds.set(sectionKey2, /* @__PURE__ */ new Set());
|
|
1392
|
+
}
|
|
1393
|
+
sectionRunIds.get(sectionKey2).add(rawRunId);
|
|
1394
|
+
}
|
|
1395
|
+
if (prefixedRunId) {
|
|
1396
|
+
parentRunCurrentSection.set(prefixedRunId, sectionKey2);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
const loggerChildRunId = text.match(/\[Logger\]\s+runId=([A-Za-z0-9_-]+)/);
|
|
1401
|
+
if (loggerChildRunId?.[1] && prefixedRunId) {
|
|
1402
|
+
const childRunId = String(loggerChildRunId[1]).trim();
|
|
1403
|
+
const parentSection = parentRunCurrentSection.get(prefixedRunId);
|
|
1404
|
+
if (parentSection) {
|
|
1405
|
+
shardRunIds.add(childRunId);
|
|
1406
|
+
runIdToSection.set(childRunId, parentSection);
|
|
1407
|
+
if (!sectionRunIds.has(parentSection)) {
|
|
1408
|
+
sectionRunIds.set(parentSection, /* @__PURE__ */ new Set());
|
|
1409
|
+
}
|
|
1410
|
+
sectionRunIds.get(parentSection).add(childRunId);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
if (rawRunId !== "global") {
|
|
1414
|
+
logActiveRunIds.add(rawRunId);
|
|
1415
|
+
if (text.includes("[exit]")) {
|
|
1416
|
+
logActiveRunIds.delete(rawRunId);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
const sectionKey = resolveSectionKey(text, rawRunId, prefixedRunId);
|
|
1420
|
+
const body = ensureSection(sectionKey);
|
|
1421
|
+
if (isShardSection(sectionKey) && rawRunId !== "global") {
|
|
1422
|
+
shardRunIds.add(rawRunId);
|
|
1423
|
+
}
|
|
1424
|
+
if (rawRunId !== "global") {
|
|
1425
|
+
if (!sectionRunIds.has(sectionKey)) {
|
|
1426
|
+
sectionRunIds.set(sectionKey, /* @__PURE__ */ new Set());
|
|
1427
|
+
}
|
|
1428
|
+
sectionRunIds.get(sectionKey)?.add(rawRunId);
|
|
1429
|
+
}
|
|
1430
|
+
const div = createEl("div", { className: "muted" }, [text]);
|
|
1431
|
+
body.appendChild(div);
|
|
1432
|
+
};
|
|
1433
|
+
clearBtn.onclick = () => {
|
|
1434
|
+
ctx2.clearLog();
|
|
1435
|
+
sectionMap.clear();
|
|
1436
|
+
sectionCardMap.clear();
|
|
1437
|
+
sectionRunIds.clear();
|
|
1438
|
+
sectionHeaderMap.clear();
|
|
1439
|
+
sectionTypeMap.clear();
|
|
1440
|
+
logActiveRunIds.clear();
|
|
1441
|
+
parentRunIds.clear();
|
|
1442
|
+
runIdToSection.clear();
|
|
1443
|
+
parentRunCurrentSection.clear();
|
|
1444
|
+
shardRunIds.clear();
|
|
1445
|
+
shardProfileQueue = [];
|
|
1446
|
+
selectedSectionKey = "global";
|
|
1447
|
+
globalContainer.textContent = "";
|
|
1448
|
+
shardContainer.textContent = "";
|
|
1449
|
+
};
|
|
1450
|
+
copyGlobalBtn.onclick = () => {
|
|
1451
|
+
void copyByType("global");
|
|
1452
|
+
};
|
|
1453
|
+
copyShardBtn.onclick = () => {
|
|
1454
|
+
void copyByType("shard");
|
|
1455
|
+
};
|
|
1456
|
+
activeOnlyCheckbox.onchange = () => {
|
|
1457
|
+
updateSectionVisibility();
|
|
1458
|
+
};
|
|
1459
|
+
showGlobalCheckbox.onchange = () => {
|
|
1460
|
+
updateSectionVisibility();
|
|
1461
|
+
};
|
|
1462
|
+
const existingLines = ctx2._logLines || [];
|
|
1463
|
+
if (Array.isArray(existingLines)) {
|
|
1464
|
+
existingLines.forEach((line) => {
|
|
1465
|
+
appendLine(line);
|
|
1466
|
+
});
|
|
1467
|
+
updateSectionVisibility();
|
|
1468
|
+
}
|
|
1469
|
+
const originalAppendLog = typeof ctx2._appendLogBase === "function" ? ctx2._appendLogBase : ctx2.appendLog;
|
|
1470
|
+
ctx2._appendLogBase = originalAppendLog;
|
|
1471
|
+
ctx2.appendLog = (line) => {
|
|
1472
|
+
appendLine(line);
|
|
1473
|
+
updateSectionVisibility();
|
|
1474
|
+
if (typeof originalAppendLog === "function") originalAppendLog.call(ctx2, line);
|
|
1475
|
+
};
|
|
1476
|
+
const unsubscribeActiveRuns = typeof ctx2.onActiveRunsChanged === "function" ? ctx2.onActiveRunsChanged(() => updateSectionVisibility()) : null;
|
|
1477
|
+
updateSectionVisibility();
|
|
1478
|
+
root.appendChild(title);
|
|
1479
|
+
root.appendChild(sub);
|
|
1480
|
+
root.appendChild(toolbar);
|
|
1481
|
+
root.appendChild(globalContainer);
|
|
1482
|
+
root.appendChild(shardContainer);
|
|
1483
|
+
const onKeydown = (evt) => {
|
|
1484
|
+
if (!(evt.ctrlKey || evt.metaKey) || !evt.shiftKey) return;
|
|
1485
|
+
const key = String(evt.key || "").toLowerCase();
|
|
1486
|
+
const code = String(evt.code || "");
|
|
1487
|
+
if (code === "Digit1" || key === "1") {
|
|
1488
|
+
evt.preventDefault();
|
|
1489
|
+
void copyByType("global");
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
if (code === "Digit2" || key === "2") {
|
|
1493
|
+
evt.preventDefault();
|
|
1494
|
+
void copyByType("shard");
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
if (code === "KeyC" || key === "c") {
|
|
1498
|
+
evt.preventDefault();
|
|
1499
|
+
void copySelected();
|
|
1500
|
+
}
|
|
1501
|
+
};
|
|
1502
|
+
root.addEventListener("keydown", onKeydown);
|
|
1503
|
+
ensureSection("global", "\u516C\u5171\u65E5\u5FD7");
|
|
1504
|
+
root.addEventListener("DOMNodeRemoved", () => {
|
|
1505
|
+
if (typeof unsubscribeActiveRuns === "function") unsubscribeActiveRuns();
|
|
1506
|
+
root.removeEventListener("keydown", onKeydown);
|
|
1507
|
+
}, { once: true });
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
// src/renderer/account-source.mts
|
|
1511
|
+
function asText(value) {
|
|
1512
|
+
const text = String(value ?? "").trim();
|
|
1513
|
+
return text || null;
|
|
1514
|
+
}
|
|
1515
|
+
function normalizeRow(row) {
|
|
1516
|
+
const profileId = asText(row?.profileId);
|
|
1517
|
+
if (!profileId) return null;
|
|
1518
|
+
return {
|
|
1519
|
+
profileId,
|
|
1520
|
+
accountRecordId: asText(row?.accountRecordId),
|
|
1521
|
+
accountId: asText(row?.accountId),
|
|
1522
|
+
alias: asText(row?.alias),
|
|
1523
|
+
name: asText(row?.name),
|
|
1524
|
+
status: asText(row?.status) || "invalid",
|
|
1525
|
+
valid: row?.valid === true && Boolean(asText(row?.accountId)),
|
|
1526
|
+
reason: asText(row?.reason),
|
|
1527
|
+
updatedAt: asText(row?.updatedAt)
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
async function listAccountProfiles(api) {
|
|
1531
|
+
const script = api.pathJoin("apps", "webauto", "entry", "account.mjs");
|
|
1532
|
+
const out = await api.cmdRunJson({
|
|
1533
|
+
title: "account list",
|
|
1534
|
+
cwd: "",
|
|
1535
|
+
args: [script, "list", "--json"],
|
|
1536
|
+
timeoutMs: 2e4
|
|
1537
|
+
});
|
|
1538
|
+
const rows = Array.isArray(out?.json?.profiles) ? out.json.profiles : [];
|
|
1539
|
+
return rows.map(normalizeRow).filter(Boolean);
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// src/renderer/tabs-new/setup-wizard.mts
|
|
1543
|
+
function renderSetupWizard(root, ctx2) {
|
|
1544
|
+
root.innerHTML = "";
|
|
1545
|
+
const header = createEl("div", { style: "margin-bottom:20px;" }, [
|
|
1546
|
+
createEl("h2", { style: "margin:0 0 8px 0; font-size:20px; color:#dbeafe;" }, ["\u73AF\u5883\u4E0E\u8D26\u6237\u521D\u59CB\u5316"]),
|
|
1547
|
+
createEl("div", { className: "muted", style: "font-size:13px;" }, ['\u9996\u6B21\u4F7F\u7528\u5FC5\u987B\u5B8C\u6210\u73AF\u5883\u68C0\u67E5\u4E0E\u8D26\u6237\u767B\u5F55\uFF0C\u4E4B\u540E\u53EF\u5728"\u8D26\u6237\u7BA1\u7406"Tab\u4E2D\u7EF4\u62A4'])
|
|
1548
|
+
]);
|
|
1549
|
+
root.appendChild(header);
|
|
1550
|
+
const bentoGrid = createEl("div", { className: "bento-grid bento-sidebar" });
|
|
1551
|
+
const envCard = createEl("div", { className: "bento-cell" });
|
|
1552
|
+
envCard.innerHTML = `
|
|
1553
|
+
<div class="bento-title"><span style="color: var(--warning);">\u25CF</span> \u73AF\u5883\u68C0\u67E5</div>
|
|
1554
|
+
<div class="env-status-grid">
|
|
1555
|
+
<div class="env-item" id="env-camo">
|
|
1556
|
+
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
1557
|
+
<span>Camoufox CLI</span>
|
|
1558
|
+
</div>
|
|
1559
|
+
<div class="env-item" id="env-unified">
|
|
1560
|
+
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
1561
|
+
<span>Unified API (7701)</span>
|
|
1562
|
+
</div>
|
|
1563
|
+
<div class="env-item" id="env-browser">
|
|
1564
|
+
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
1565
|
+
<span>Browser Service (7704)</span>
|
|
1566
|
+
</div>
|
|
1567
|
+
<div class="env-item" id="env-firefox">
|
|
1568
|
+
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
1569
|
+
<span>Camoufox Runtime</span>
|
|
1570
|
+
</div>
|
|
1571
|
+
<div class="env-item" id="env-geoip">
|
|
1572
|
+
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
1573
|
+
<span>GeoIP Database</span>
|
|
1574
|
+
</div>
|
|
1575
|
+
</div>
|
|
1576
|
+
<div style="margin-top: var(--gap);">
|
|
1577
|
+
<button id="env-check-btn" class="secondary" style="width: 100%;">\u68C0\u67E5\u73AF\u5883</button>
|
|
1578
|
+
</div>
|
|
1579
|
+
`;
|
|
1580
|
+
bentoGrid.appendChild(envCard);
|
|
1581
|
+
const accountCard = createEl("div", { className: "bento-cell" });
|
|
1582
|
+
accountCard.innerHTML = `
|
|
1583
|
+
<div class="bento-title">\u8D26\u6237\u8BBE\u7F6E</div>
|
|
1584
|
+
<div class="account-list" id="account-list" style="margin-bottom: var(--gap); max-height: 200px; overflow: auto;">
|
|
1585
|
+
<div style="padding:12px; text-align:center; color:#8b93a6;">\u6682\u65E0\u8D26\u6237\uFF0C\u8BF7\u70B9\u51FB\u4E0B\u65B9\u6309\u94AE\u6DFB\u52A0</div>
|
|
1586
|
+
</div>
|
|
1587
|
+
<div class="row">
|
|
1588
|
+
<div>
|
|
1589
|
+
<label>\u65B0\u8D26\u6237\u522B\u540D</label>
|
|
1590
|
+
<input id="new-alias-input" placeholder="\u4F8B\u5982: \u7F8E\u98DF\u63A2\u5E97\u8D26\u53F7" style="width: 200px;" />
|
|
1591
|
+
</div>
|
|
1592
|
+
<button id="add-account-btn" style="flex: 0 0 auto; min-width: 120px;">\u6DFB\u52A0\u8D26\u6237</button>
|
|
1593
|
+
</div>
|
|
1594
|
+
`;
|
|
1595
|
+
bentoGrid.appendChild(accountCard);
|
|
1596
|
+
root.appendChild(bentoGrid);
|
|
1597
|
+
const statusRow = createEl("div", { className: "bento-grid", style: "margin-top: var(--gap);" });
|
|
1598
|
+
const statusCard = createEl("div", { className: "bento-cell highlight" });
|
|
1599
|
+
statusCard.id = "setup-status-card";
|
|
1600
|
+
statusCard.innerHTML = `
|
|
1601
|
+
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
1602
|
+
<div>
|
|
1603
|
+
<div style="font-size: 14px; font-weight: 600; color: var(--text-1); margin-bottom: 4px;">
|
|
1604
|
+
\u8BF7\u5B8C\u6210\u4E0A\u8FF0\u6B65\u9AA4
|
|
1605
|
+
</div>
|
|
1606
|
+
<div style="font-size: 12px; color: var(--text-3);" id="setup-status-text">
|
|
1607
|
+
\u73AF\u5883\u68C0\u67E5\u548C\u8D26\u6237\u767B\u5F55\u540E\u624D\u80FD\u5F00\u59CB\u4F7F\u7528
|
|
1608
|
+
</div>
|
|
1609
|
+
</div>
|
|
1610
|
+
<button id="enter-main-btn" class="secondary" disabled style="padding: 12px 32px; font-size: 14px;">\u8FDB\u5165\u4E3B\u754C\u9762</button>
|
|
1611
|
+
</div>
|
|
1612
|
+
`;
|
|
1613
|
+
statusRow.appendChild(statusCard);
|
|
1614
|
+
root.appendChild(statusRow);
|
|
1615
|
+
const envCheckBtn = root.querySelector("#env-check-btn");
|
|
1616
|
+
const addAccountBtn = root.querySelector("#add-account-btn");
|
|
1617
|
+
const newAliasInput = root.querySelector("#new-alias-input");
|
|
1618
|
+
const enterMainBtn = root.querySelector("#enter-main-btn");
|
|
1619
|
+
const accountListEl = root.querySelector("#account-list");
|
|
1620
|
+
const setupStatusText = root.querySelector("#setup-status-text");
|
|
1621
|
+
let envReady = false;
|
|
1622
|
+
let accounts = [];
|
|
1623
|
+
const isEnvReady = (snapshot) => Boolean(
|
|
1624
|
+
snapshot?.camo?.installed && snapshot?.services?.unifiedApi && snapshot?.services?.browserService && snapshot?.firefox?.installed && snapshot?.geoip?.installed
|
|
1625
|
+
);
|
|
1626
|
+
async function collectEnvironment() {
|
|
1627
|
+
const [camo, services, firefox, geoip] = await Promise.all([
|
|
1628
|
+
ctx2.api.envCheckCamo(),
|
|
1629
|
+
ctx2.api.envCheckServices(),
|
|
1630
|
+
ctx2.api.envCheckFirefox(),
|
|
1631
|
+
ctx2.api.envCheckGeoIP()
|
|
1632
|
+
]);
|
|
1633
|
+
return { camo, services, firefox, geoip };
|
|
1634
|
+
}
|
|
1635
|
+
function applyEnvironment(snapshot) {
|
|
1636
|
+
updateEnvItem("env-camo", snapshot.camo?.installed, snapshot.camo?.version || (snapshot.camo?.installed ? "\u5DF2\u5B89\u88C5" : "\u672A\u5B89\u88C5"));
|
|
1637
|
+
updateEnvItem("env-unified", snapshot.services?.unifiedApi, "7701");
|
|
1638
|
+
updateEnvItem("env-browser", snapshot.services?.browserService, "7704");
|
|
1639
|
+
updateEnvItem("env-firefox", snapshot.firefox?.installed, snapshot.firefox?.path ? "\u5DF2\u5B89\u88C5" : "\u672A\u5B89\u88C5");
|
|
1640
|
+
updateEnvItem("env-geoip", snapshot.geoip?.installed, snapshot.geoip?.installed ? "\u5DF2\u5B89\u88C5" : "\u672A\u5B89\u88C5");
|
|
1641
|
+
envReady = isEnvReady(snapshot);
|
|
1642
|
+
}
|
|
1643
|
+
async function autoRepairEnvironment(snapshot) {
|
|
1644
|
+
const missingCore = !snapshot.services?.unifiedApi || !snapshot.services?.browserService;
|
|
1645
|
+
const missingCamo = !snapshot.camo?.installed;
|
|
1646
|
+
const missingRuntime = !snapshot.firefox?.installed;
|
|
1647
|
+
const missingGeoIP = !snapshot.geoip?.installed;
|
|
1648
|
+
if (!missingCore && !missingCamo && !missingRuntime && !missingGeoIP) return;
|
|
1649
|
+
setupStatusText.textContent = "\u68C0\u6D4B\u5230\u4F9D\u8D56\u7F3A\u5931\uFF0C\u6B63\u5728\u81EA\u52A8\u4FEE\u590D...";
|
|
1650
|
+
if (missingCore && typeof ctx2.api?.envRepairCore === "function") {
|
|
1651
|
+
setupStatusText.textContent = "\u6B63\u5728\u62C9\u8D77\u6838\u5FC3\u670D\u52A1...";
|
|
1652
|
+
await ctx2.api.envRepairCore().catch(() => null);
|
|
1653
|
+
}
|
|
1654
|
+
if ((missingCamo || missingRuntime || missingGeoIP) && typeof ctx2.api?.cmdRunJson === "function") {
|
|
1655
|
+
setupStatusText.textContent = "\u6B63\u5728\u5B89\u88C5\u4F9D\u8D56\uFF08Camoufox/GeoIP\uFF09...";
|
|
1656
|
+
const script = ctx2.api.pathJoin("apps", "webauto", "entry", "xhs-install.mjs");
|
|
1657
|
+
const args = [script];
|
|
1658
|
+
if (missingRuntime || missingCamo) args.push("--download-browser");
|
|
1659
|
+
if (missingGeoIP) args.push("--download-geoip");
|
|
1660
|
+
args.push("--ensure-backend");
|
|
1661
|
+
await ctx2.api.cmdRunJson({
|
|
1662
|
+
title: "setup auto repair",
|
|
1663
|
+
cwd: "",
|
|
1664
|
+
args,
|
|
1665
|
+
timeoutMs: 3e5
|
|
1666
|
+
}).catch(() => null);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
async function checkEnvironment() {
|
|
1670
|
+
envCheckBtn.disabled = true;
|
|
1671
|
+
envCheckBtn.textContent = "\u68C0\u67E5/\u4FEE\u590D\u4E2D...";
|
|
1672
|
+
try {
|
|
1673
|
+
const before = await collectEnvironment();
|
|
1674
|
+
applyEnvironment(before);
|
|
1675
|
+
if (!isEnvReady(before)) {
|
|
1676
|
+
await autoRepairEnvironment(before);
|
|
1677
|
+
}
|
|
1678
|
+
const after = await collectEnvironment();
|
|
1679
|
+
applyEnvironment(after);
|
|
1680
|
+
updateCompleteStatus();
|
|
1681
|
+
if (!envReady) {
|
|
1682
|
+
const missing = [];
|
|
1683
|
+
if (!after?.camo?.installed) missing.push("camo");
|
|
1684
|
+
if (!after?.services?.unifiedApi) missing.push("unified-api");
|
|
1685
|
+
if (!after?.services?.browserService) missing.push("browser-service");
|
|
1686
|
+
if (!after?.firefox?.installed) missing.push("camoufox-runtime");
|
|
1687
|
+
if (!after?.geoip?.installed) missing.push("geoip");
|
|
1688
|
+
setupStatusText.textContent = `\u81EA\u52A8\u4FEE\u590D\u540E\u4ECD\u7F3A\u5931: ${missing.join(", ")}`;
|
|
1689
|
+
}
|
|
1690
|
+
} catch (err) {
|
|
1691
|
+
console.error("Environment check failed:", err);
|
|
1692
|
+
setupStatusText.textContent = "\u73AF\u5883\u68C0\u67E5\u5931\u8D25\uFF0C\u8BF7\u67E5\u770B\u65E5\u5FD7\u5E76\u91CD\u8BD5";
|
|
1693
|
+
}
|
|
1694
|
+
envCheckBtn.disabled = false;
|
|
1695
|
+
envCheckBtn.textContent = "\u91CD\u65B0\u68C0\u67E5";
|
|
1696
|
+
}
|
|
1697
|
+
function updateEnvItem(id, ok, detail) {
|
|
1698
|
+
const el = root.querySelector(`#${id}`);
|
|
1699
|
+
if (!el) return;
|
|
1700
|
+
const icon = el.querySelector(".icon");
|
|
1701
|
+
const text = el.querySelector("span:last-child");
|
|
1702
|
+
const baseLabel = el.dataset.label || text.textContent || "";
|
|
1703
|
+
el.dataset.label = baseLabel;
|
|
1704
|
+
icon.textContent = ok ? "\u2713" : "\u2717";
|
|
1705
|
+
icon.style.color = ok ? "var(--success)" : "var(--danger)";
|
|
1706
|
+
text.textContent = detail ? `${baseLabel} \xB7 ${detail}` : baseLabel;
|
|
1707
|
+
}
|
|
1708
|
+
async function refreshAccounts() {
|
|
1709
|
+
try {
|
|
1710
|
+
accounts = await listAccountProfiles(ctx2.api);
|
|
1711
|
+
renderAccountList();
|
|
1712
|
+
updateCompleteStatus();
|
|
1713
|
+
} catch (err) {
|
|
1714
|
+
console.error("Failed to refresh accounts:", err);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
function renderAccountList() {
|
|
1718
|
+
accountListEl.innerHTML = "";
|
|
1719
|
+
if (accounts.length === 0) {
|
|
1720
|
+
accountListEl.innerHTML = '<div style="padding:12px; text-align:center; color:#8b93a6;">\u6682\u65E0\u8D26\u6237\uFF0C\u8BF7\u70B9\u51FB\u4E0B\u65B9\u6309\u94AE\u6DFB\u52A0</div>';
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1723
|
+
accounts.forEach((acc) => {
|
|
1724
|
+
const statusLabel = acc.valid ? "\u2713 \u6709\u6548" : acc.status === "pending" ? "\u23F3 \u5F85\u767B\u5F55" : "\u2717 \u5931\u6548";
|
|
1725
|
+
const statusClass = acc.valid ? "status-valid" : acc.status === "pending" ? "status-pending" : "status-expired";
|
|
1726
|
+
const row = createEl("div", {
|
|
1727
|
+
style: "display:flex; justify-content:space-between; align-items:center; padding:8px 12px; border-bottom:1px solid var(--border);"
|
|
1728
|
+
}, [
|
|
1729
|
+
createEl("div", {}, [
|
|
1730
|
+
createEl("div", { style: "font-weight:600; margin-bottom:2px;" }, [acc.alias || acc.name || acc.profileId]),
|
|
1731
|
+
createEl("div", { className: "muted", style: "font-size:11px;" }, [acc.profileId])
|
|
1732
|
+
]),
|
|
1733
|
+
createEl("span", {
|
|
1734
|
+
className: `status-badge ${statusClass}`
|
|
1735
|
+
}, [statusLabel])
|
|
1736
|
+
]);
|
|
1737
|
+
accountListEl.appendChild(row);
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
async function addAccount() {
|
|
1741
|
+
const alias = newAliasInput.value.trim();
|
|
1742
|
+
if (!alias) {
|
|
1743
|
+
alert("\u8BF7\u8F93\u5165\u8D26\u6237\u522B\u540D");
|
|
1744
|
+
return;
|
|
1745
|
+
}
|
|
1746
|
+
addAccountBtn.disabled = true;
|
|
1747
|
+
addAccountBtn.textContent = "\u521B\u5EFA\u4E2D...";
|
|
1748
|
+
try {
|
|
1749
|
+
const batchKey = "xiaohongshu";
|
|
1750
|
+
const out = await ctx2.api.cmdRunJson({
|
|
1751
|
+
title: "profilepool add",
|
|
1752
|
+
cwd: "",
|
|
1753
|
+
args: [ctx2.api.pathJoin("apps", "webauto", "entry", "profilepool.mjs"), "add", batchKey, "--json"]
|
|
1754
|
+
});
|
|
1755
|
+
if (!out?.ok || !out?.json?.profileId) {
|
|
1756
|
+
alert("\u521B\u5EFA\u8D26\u53F7\u5931\u8D25: " + (out?.error || "\u672A\u77E5\u9519\u8BEF"));
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1759
|
+
const profileId = out.json.profileId;
|
|
1760
|
+
const aliases = { ...ctx2.api.settings?.profileAliases, [profileId]: alias };
|
|
1761
|
+
await ctx2.api.settingsSet({ profileAliases: aliases });
|
|
1762
|
+
if (typeof ctx2.refreshSettings === "function") {
|
|
1763
|
+
await ctx2.refreshSettings();
|
|
1764
|
+
}
|
|
1765
|
+
const timeoutSec = ctx2.api.settings?.timeouts?.loginTimeoutSec || 900;
|
|
1766
|
+
const loginArgs = [
|
|
1767
|
+
ctx2.api.pathJoin("apps", "webauto", "entry", "profilepool.mjs"),
|
|
1768
|
+
"login-profile",
|
|
1769
|
+
profileId,
|
|
1770
|
+
"--timeout-sec",
|
|
1771
|
+
String(timeoutSec),
|
|
1772
|
+
"--keep-session"
|
|
1773
|
+
];
|
|
1774
|
+
await ctx2.api.cmdSpawn({
|
|
1775
|
+
title: `\u767B\u5F55 ${alias}`,
|
|
1776
|
+
cwd: "",
|
|
1777
|
+
args: loginArgs,
|
|
1778
|
+
groupKey: "profilepool"
|
|
1779
|
+
});
|
|
1780
|
+
newAliasInput.value = "";
|
|
1781
|
+
await refreshAccounts();
|
|
1782
|
+
} catch (err) {
|
|
1783
|
+
alert("\u6DFB\u52A0\u8D26\u53F7\u5931\u8D25: " + (err?.message || String(err)));
|
|
1784
|
+
} finally {
|
|
1785
|
+
addAccountBtn.disabled = false;
|
|
1786
|
+
addAccountBtn.textContent = "\u6DFB\u52A0\u8D26\u6237";
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
function updateCompleteStatus() {
|
|
1790
|
+
const hasValidAccount = accounts.some((a) => a.valid);
|
|
1791
|
+
const canProceed = envReady && hasValidAccount;
|
|
1792
|
+
enterMainBtn.disabled = !canProceed;
|
|
1793
|
+
if (canProceed) {
|
|
1794
|
+
setupStatusText.textContent = `\u73AF\u5883\u5C31\u7EEA\uFF0C${accounts.length} \u4E2A\u8D26\u6237\u914D\u7F6E\u5B8C\u6210`;
|
|
1795
|
+
enterMainBtn.className = "";
|
|
1796
|
+
} else {
|
|
1797
|
+
const missing = [];
|
|
1798
|
+
if (!envReady) missing.push("\u73AF\u5883\u68C0\u67E5");
|
|
1799
|
+
if (!hasValidAccount) missing.push("\u81F3\u5C11\u4E00\u4E2A\u8D26\u6237");
|
|
1800
|
+
setupStatusText.textContent = `\u5C1A\u672A\u5B8C\u6210: ${missing.join("\u3001")}`;
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
envCheckBtn.onclick = checkEnvironment;
|
|
1804
|
+
addAccountBtn.onclick = addAccount;
|
|
1805
|
+
enterMainBtn.onclick = () => {
|
|
1806
|
+
if (typeof ctx2.setActiveTab === "function") {
|
|
1807
|
+
ctx2.setActiveTab("config");
|
|
1808
|
+
}
|
|
1809
|
+
};
|
|
1810
|
+
void checkEnvironment();
|
|
1811
|
+
void refreshAccounts();
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
// src/renderer/tabs-new/config-panel.mts
|
|
1815
|
+
function renderConfigPanel(root, ctx2) {
|
|
1816
|
+
root.innerHTML = "";
|
|
1817
|
+
const pageIndicator = createEl("div", { className: "page-indicator" }, [
|
|
1818
|
+
"\u5F53\u524D: ",
|
|
1819
|
+
createEl("span", {}, ["\u914D\u7F6E\u9875"]),
|
|
1820
|
+
" \u2192 \u5B8C\u6210\u540E\u8DF3\u8F6C ",
|
|
1821
|
+
createEl("span", {}, ["\u770B\u677F\u9875"])
|
|
1822
|
+
]);
|
|
1823
|
+
root.appendChild(pageIndicator);
|
|
1824
|
+
const bentoGrid = createEl("div", { className: "bento-grid bento-sidebar" });
|
|
1825
|
+
const targetCard = createEl("div", { className: "bento-cell" });
|
|
1826
|
+
targetCard.innerHTML = `
|
|
1827
|
+
<div class="bento-title">\u76EE\u6807\u8BBE\u5B9A</div>
|
|
1828
|
+
|
|
1829
|
+
<div class="row">
|
|
1830
|
+
<div>
|
|
1831
|
+
<label>\u641C\u7D22\u5173\u952E\u8BCD</label>
|
|
1832
|
+
<input id="keyword-input" placeholder="\u8F93\u5165\u5173\u952E\u8BCD" style="width: 200px;" />
|
|
1833
|
+
</div>
|
|
1834
|
+
</div>
|
|
1835
|
+
|
|
1836
|
+
<div class="row">
|
|
1837
|
+
<div>
|
|
1838
|
+
<label>\u76EE\u6807\u6570\u91CF</label>
|
|
1839
|
+
<input id="target-input" type="number" value="50" min="1" style="width: 100px;" />
|
|
1840
|
+
</div>
|
|
1841
|
+
<div>
|
|
1842
|
+
<label>\u8FD0\u884C\u73AF\u5883</label>
|
|
1843
|
+
<select id="env-select" style="width: 120px;">
|
|
1844
|
+
<option value="debug">\u8C03\u8BD5\u6A21\u5F0F</option>
|
|
1845
|
+
<option value="prod">\u751F\u4EA7\u6A21\u5F0F</option>
|
|
1846
|
+
</select>
|
|
1847
|
+
</div>
|
|
1848
|
+
</div>
|
|
1849
|
+
|
|
1850
|
+
<div>
|
|
1851
|
+
<label>\u9009\u62E9\u8D26\u6237</label>
|
|
1852
|
+
<select id="account-select" style="min-width: 200px;">
|
|
1853
|
+
<option value="">\u8BF7\u9009\u62E9\u8D26\u6237...</option>
|
|
1854
|
+
</select>
|
|
1855
|
+
</div>
|
|
1856
|
+
|
|
1857
|
+
<div style="margin-top: var(--gap); padding-top: var(--gap); border-top: 1px solid var(--border);">
|
|
1858
|
+
<div class="bento-title" style="font-size: 13px;">\u914D\u7F6E\u9884\u8BBE</div>
|
|
1859
|
+
<div class="row">
|
|
1860
|
+
<select id="preset-select" style="width: 200px;">
|
|
1861
|
+
<option value="last">\u4E0A\u6B21\u914D\u7F6E</option>
|
|
1862
|
+
<option value="full">\u9884\u8BBE1\uFF1A\u5168\u91CF\u722C\u53D6</option>
|
|
1863
|
+
<option value="body-only">\u9884\u8BBE2\uFF1A\u4EC5\u6B63\u6587</option>
|
|
1864
|
+
<option value="quick">\u9884\u8BBE3\uFF1A\u5FEB\u901F\u91C7\u96C6</option>
|
|
1865
|
+
</select>
|
|
1866
|
+
</div>
|
|
1867
|
+
<div class="btn-group">
|
|
1868
|
+
<button id="import-btn" class="secondary" style="flex: 1;">\u5BFC\u5165\u914D\u7F6E</button>
|
|
1869
|
+
<button id="export-btn" class="secondary" style="flex: 1;">\u5BFC\u51FA\u914D\u7F6E</button>
|
|
1870
|
+
</div>
|
|
1871
|
+
</div>
|
|
1872
|
+
`;
|
|
1873
|
+
bentoGrid.appendChild(targetCard);
|
|
1874
|
+
const optionsCard = createEl("div", { className: "bento-cell" });
|
|
1875
|
+
optionsCard.innerHTML = `
|
|
1876
|
+
<div class="bento-title">\u722C\u53D6\u9009\u9879</div>
|
|
1877
|
+
|
|
1878
|
+
<div style="display: flex; gap: var(--gap); margin-bottom: var(--gap);">
|
|
1879
|
+
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
|
1880
|
+
<input id="fetch-body-cb" type="checkbox" checked />
|
|
1881
|
+
<span>\u722C\u53D6\u6B63\u6587</span>
|
|
1882
|
+
</label>
|
|
1883
|
+
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
|
1884
|
+
<input id="fetch-comments-cb" type="checkbox" checked />
|
|
1885
|
+
<span>\u722C\u53D6\u8BC4\u8BBA</span>
|
|
1886
|
+
</label>
|
|
1887
|
+
</div>
|
|
1888
|
+
|
|
1889
|
+
<div class="row">
|
|
1890
|
+
<div>
|
|
1891
|
+
<label>\u6700\u591A\u8BC4\u8BBA\u6570</label>
|
|
1892
|
+
<input id="max-comments-input" type="number" value="100" min="0" style="width: 100px;" />
|
|
1893
|
+
</div>
|
|
1894
|
+
</div>
|
|
1895
|
+
|
|
1896
|
+
<div style="margin-top: var(--gap); padding-top: var(--gap); border-top: 1px solid var(--border);">
|
|
1897
|
+
<div class="bento-title" style="font-size: 13px;">\u70B9\u8D5E\u8BBE\u7F6E</div>
|
|
1898
|
+
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer; margin-bottom: var(--gap-sm);">
|
|
1899
|
+
<input id="auto-like-cb" type="checkbox" />
|
|
1900
|
+
<span>\u81EA\u52A8\u70B9\u8D5E</span>
|
|
1901
|
+
</label>
|
|
1902
|
+
<div>
|
|
1903
|
+
<label>\u70B9\u8D5E\u5173\u952E\u8BCD (\u9017\u53F7\u5206\u9694)</label>
|
|
1904
|
+
<input id="like-keywords-input" placeholder="\u4F8B\u5982: \u7F8E\u98DF,\u65C5\u6E38,\u6444\u5F71" disabled />
|
|
1905
|
+
</div>
|
|
1906
|
+
</div>
|
|
1907
|
+
|
|
1908
|
+
<div style="margin-top: var(--gap); padding-top: var(--gap); border-top: 1px solid var(--border);">
|
|
1909
|
+
<div class="bento-title" style="font-size: 13px;">\u9AD8\u7EA7\u9009\u9879</div>
|
|
1910
|
+
<div style="display: flex; gap: var(--gap);">
|
|
1911
|
+
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
|
1912
|
+
<input id="headless-cb" type="checkbox" />
|
|
1913
|
+
<span>Headless</span>
|
|
1914
|
+
</label>
|
|
1915
|
+
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
|
1916
|
+
<input id="dry-run-cb" type="checkbox" />
|
|
1917
|
+
<span>Dry Run</span>
|
|
1918
|
+
</label>
|
|
1919
|
+
</div>
|
|
1920
|
+
</div>
|
|
1921
|
+
`;
|
|
1922
|
+
bentoGrid.appendChild(optionsCard);
|
|
1923
|
+
root.appendChild(bentoGrid);
|
|
1924
|
+
const actionRow = createEl("div", { className: "bento-grid", style: "margin-top: var(--gap);" });
|
|
1925
|
+
const actionCard = createEl("div", { className: "bento-cell highlight" });
|
|
1926
|
+
actionCard.innerHTML = `
|
|
1927
|
+
<div style="text-align: center;">
|
|
1928
|
+
<button id="start-btn" style="padding: 14px 64px; font-size: 15px;">\u5F00\u59CB\u722C\u53D6</button>
|
|
1929
|
+
</div>
|
|
1930
|
+
`;
|
|
1931
|
+
actionRow.appendChild(actionCard);
|
|
1932
|
+
root.appendChild(actionRow);
|
|
1933
|
+
const keywordInput = root.querySelector("#keyword-input");
|
|
1934
|
+
const targetInput = root.querySelector("#target-input");
|
|
1935
|
+
const envSelect = root.querySelector("#env-select");
|
|
1936
|
+
const accountSelect = root.querySelector("#account-select");
|
|
1937
|
+
const presetSelect = root.querySelector("#preset-select");
|
|
1938
|
+
const fetchBodyCb = root.querySelector("#fetch-body-cb");
|
|
1939
|
+
const fetchCommentsCb = root.querySelector("#fetch-comments-cb");
|
|
1940
|
+
const maxCommentsInput = root.querySelector("#max-comments-input");
|
|
1941
|
+
const autoLikeCb = root.querySelector("#auto-like-cb");
|
|
1942
|
+
const likeKeywordsInput = root.querySelector("#like-keywords-input");
|
|
1943
|
+
const headlessCb = root.querySelector("#headless-cb");
|
|
1944
|
+
const dryRunCb = root.querySelector("#dry-run-cb");
|
|
1945
|
+
const startBtn = root.querySelector("#start-btn");
|
|
1946
|
+
const importBtn = root.querySelector("#import-btn");
|
|
1947
|
+
const exportBtn = root.querySelector("#export-btn");
|
|
1948
|
+
let saveTimeout = null;
|
|
1949
|
+
let accountRows = [];
|
|
1950
|
+
let preferredProfileId = "";
|
|
1951
|
+
function buildConfigPayload() {
|
|
1952
|
+
return {
|
|
1953
|
+
keyword: keywordInput.value.trim(),
|
|
1954
|
+
target: parseInt(targetInput.value) || 50,
|
|
1955
|
+
env: envSelect.value,
|
|
1956
|
+
fetchBody: fetchBodyCb.checked,
|
|
1957
|
+
fetchComments: fetchCommentsCb.checked,
|
|
1958
|
+
maxComments: parseInt(maxCommentsInput.value) || 100,
|
|
1959
|
+
autoLike: autoLikeCb.checked,
|
|
1960
|
+
likeKeywords: likeKeywordsInput.value.trim(),
|
|
1961
|
+
headless: headlessCb.checked,
|
|
1962
|
+
dryRun: dryRunCb.checked,
|
|
1963
|
+
lastProfileId: accountSelect.value || void 0
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1966
|
+
async function loadConfig() {
|
|
1967
|
+
try {
|
|
1968
|
+
const config = await ctx2.api.configLoadLast();
|
|
1969
|
+
if (config) {
|
|
1970
|
+
keywordInput.value = config.keyword || "";
|
|
1971
|
+
targetInput.value = String(config.target || 50);
|
|
1972
|
+
envSelect.value = config.env || "debug";
|
|
1973
|
+
fetchBodyCb.checked = config.fetchBody !== false;
|
|
1974
|
+
fetchCommentsCb.checked = config.fetchComments !== false;
|
|
1975
|
+
maxCommentsInput.value = String(config.maxComments || 100);
|
|
1976
|
+
autoLikeCb.checked = config.autoLike === true;
|
|
1977
|
+
likeKeywordsInput.value = config.likeKeywords || "";
|
|
1978
|
+
headlessCb.checked = config.headless === true;
|
|
1979
|
+
dryRunCb.checked = config.dryRun === true;
|
|
1980
|
+
preferredProfileId = String(config.lastProfileId || "").trim();
|
|
1981
|
+
updateLikeKeywordsState();
|
|
1982
|
+
}
|
|
1983
|
+
} catch (err) {
|
|
1984
|
+
console.error("Failed to load config:", err);
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
function saveConfig() {
|
|
1988
|
+
if (saveTimeout) clearTimeout(saveTimeout);
|
|
1989
|
+
saveTimeout = setTimeout(async () => {
|
|
1990
|
+
const config = buildConfigPayload();
|
|
1991
|
+
try {
|
|
1992
|
+
await ctx2.api.configSaveLast(config);
|
|
1993
|
+
} catch (err) {
|
|
1994
|
+
console.error("Failed to save config:", err);
|
|
1995
|
+
}
|
|
1996
|
+
}, 1e3);
|
|
1997
|
+
}
|
|
1998
|
+
async function loadAccounts() {
|
|
1999
|
+
try {
|
|
2000
|
+
accountRows = await listAccountProfiles(ctx2.api);
|
|
2001
|
+
const validRows = accountRows.filter((row) => row.valid);
|
|
2002
|
+
accountSelect.innerHTML = '<option value="">\u8BF7\u9009\u62E9\u8D26\u6237...</option>';
|
|
2003
|
+
validRows.forEach((row) => {
|
|
2004
|
+
const profileId = String(row.profileId || "");
|
|
2005
|
+
const label = row.alias ? `${row.alias} (${profileId})` : row.name || profileId;
|
|
2006
|
+
const opt = createEl("option", { value: profileId }, [label]);
|
|
2007
|
+
accountSelect.appendChild(opt);
|
|
2008
|
+
});
|
|
2009
|
+
if (preferredProfileId && validRows.some((row) => row.profileId === preferredProfileId)) {
|
|
2010
|
+
accountSelect.value = preferredProfileId;
|
|
2011
|
+
}
|
|
2012
|
+
} catch (err) {
|
|
2013
|
+
console.error("Failed to load accounts:", err);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
async function exportConfig() {
|
|
2017
|
+
try {
|
|
2018
|
+
const config = buildConfigPayload();
|
|
2019
|
+
const home = ctx2.api.osHomedir();
|
|
2020
|
+
const downloadsPath = ctx2.api.pathJoin(home, "Downloads");
|
|
2021
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
2022
|
+
const filePath = ctx2.api.pathJoin(downloadsPath, `webauto-config-${timestamp}.json`);
|
|
2023
|
+
const result = await ctx2.api.configExport({ filePath, config });
|
|
2024
|
+
if (result.ok) {
|
|
2025
|
+
alert(`\u914D\u7F6E\u5DF2\u5BFC\u51FA\u5230: ${result.path}`);
|
|
2026
|
+
}
|
|
2027
|
+
} catch (err) {
|
|
2028
|
+
alert("\u5BFC\u51FA\u5931\u8D25: " + (err?.message || String(err)));
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
async function importConfig() {
|
|
2032
|
+
const input = document.createElement("input");
|
|
2033
|
+
input.type = "file";
|
|
2034
|
+
input.accept = ".json";
|
|
2035
|
+
input.onchange = async (e) => {
|
|
2036
|
+
const file = e.target.files?.[0];
|
|
2037
|
+
if (!file) return;
|
|
2038
|
+
try {
|
|
2039
|
+
const text = await file.text();
|
|
2040
|
+
const config = JSON.parse(text.replace(/^\uFEFF/, ""));
|
|
2041
|
+
keywordInput.value = config.keyword || "";
|
|
2042
|
+
targetInput.value = String(config.target || 50);
|
|
2043
|
+
envSelect.value = config.env || "debug";
|
|
2044
|
+
fetchBodyCb.checked = config.fetchBody !== false;
|
|
2045
|
+
fetchCommentsCb.checked = config.fetchComments !== false;
|
|
2046
|
+
maxCommentsInput.value = String(config.maxComments || 100);
|
|
2047
|
+
autoLikeCb.checked = config.autoLike === true;
|
|
2048
|
+
likeKeywordsInput.value = config.likeKeywords || "";
|
|
2049
|
+
headlessCb.checked = config.headless === true;
|
|
2050
|
+
dryRunCb.checked = config.dryRun === true;
|
|
2051
|
+
updateLikeKeywordsState();
|
|
2052
|
+
saveConfig();
|
|
2053
|
+
alert("\u914D\u7F6E\u5DF2\u5BFC\u5165");
|
|
2054
|
+
} catch (err) {
|
|
2055
|
+
alert("\u5BFC\u5165\u5931\u8D25: " + (err?.message || String(err)));
|
|
2056
|
+
}
|
|
2057
|
+
};
|
|
2058
|
+
input.click();
|
|
2059
|
+
}
|
|
2060
|
+
function updateLikeKeywordsState() {
|
|
2061
|
+
likeKeywordsInput.disabled = !autoLikeCb.checked;
|
|
2062
|
+
likeKeywordsInput.style.opacity = autoLikeCb.checked ? "1" : "0.5";
|
|
2063
|
+
}
|
|
2064
|
+
async function startCrawl() {
|
|
2065
|
+
const keyword = keywordInput.value.trim();
|
|
2066
|
+
if (!keyword) {
|
|
2067
|
+
alert("\u8BF7\u8F93\u5165\u5173\u952E\u8BCD");
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2070
|
+
const profileId = accountSelect.value;
|
|
2071
|
+
if (!profileId) {
|
|
2072
|
+
alert("\u8BF7\u9009\u62E9\u8D26\u6237");
|
|
2073
|
+
return;
|
|
2074
|
+
}
|
|
2075
|
+
const account = accountRows.find((row) => row.profileId === profileId);
|
|
2076
|
+
if (!account || !account.valid) {
|
|
2077
|
+
alert("\u5F53\u524D\u8D26\u6237\u65E0\u6548\uFF0C\u8BF7\u5148\u5230\u201C\u8D26\u6237\u7BA1\u7406\u201D\u5B8C\u6210\u767B\u5F55\u5E76\u6821\u9A8C");
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
const config = buildConfigPayload();
|
|
2081
|
+
try {
|
|
2082
|
+
await ctx2.api.configSaveLast(config);
|
|
2083
|
+
} catch {
|
|
2084
|
+
}
|
|
2085
|
+
const script = ctx2.api.pathJoin("apps", "webauto", "entry", "xhs-unified.mjs");
|
|
2086
|
+
const outputRoot = String(ctx2?.settings?.downloadRoot || "").trim();
|
|
2087
|
+
const args = [
|
|
2088
|
+
script,
|
|
2089
|
+
"--profile",
|
|
2090
|
+
profileId,
|
|
2091
|
+
"--keyword",
|
|
2092
|
+
config.keyword,
|
|
2093
|
+
"--max-notes",
|
|
2094
|
+
String(config.target),
|
|
2095
|
+
"--env",
|
|
2096
|
+
config.env,
|
|
2097
|
+
"--do-comments",
|
|
2098
|
+
config.fetchComments ? "true" : "false",
|
|
2099
|
+
"--persist-comments",
|
|
2100
|
+
config.fetchComments ? "true" : "false",
|
|
2101
|
+
"--do-likes",
|
|
2102
|
+
config.autoLike ? "true" : "false",
|
|
2103
|
+
"--like-keywords",
|
|
2104
|
+
config.likeKeywords || "",
|
|
2105
|
+
"--headless",
|
|
2106
|
+
config.headless ? "true" : "false"
|
|
2107
|
+
];
|
|
2108
|
+
if (outputRoot) args.push("--output-root", outputRoot);
|
|
2109
|
+
if (config.dryRun) args.push("--dry-run");
|
|
2110
|
+
else args.push("--no-dry-run");
|
|
2111
|
+
startBtn.disabled = true;
|
|
2112
|
+
const prevText = startBtn.textContent;
|
|
2113
|
+
startBtn.textContent = "\u542F\u52A8\u4E2D...";
|
|
2114
|
+
try {
|
|
2115
|
+
const ret = await ctx2.api.cmdSpawn({
|
|
2116
|
+
title: `xhs unified ${config.keyword}`.trim(),
|
|
2117
|
+
cwd: "",
|
|
2118
|
+
args,
|
|
2119
|
+
groupKey: "xiaohongshu"
|
|
2120
|
+
});
|
|
2121
|
+
const runId = String(ret?.runId || "").trim();
|
|
2122
|
+
if (!runId) {
|
|
2123
|
+
throw new Error("runId \u4E3A\u7A7A");
|
|
2124
|
+
}
|
|
2125
|
+
ctx2.xhsCurrentRun = {
|
|
2126
|
+
runId,
|
|
2127
|
+
profileId,
|
|
2128
|
+
keyword: config.keyword,
|
|
2129
|
+
target: config.target,
|
|
2130
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2131
|
+
};
|
|
2132
|
+
if (typeof ctx2.appendLog === "function") {
|
|
2133
|
+
ctx2.appendLog(`[ui] started xhs-unified runId=${runId} profile=${profileId} keyword=${config.keyword}`);
|
|
2134
|
+
}
|
|
2135
|
+
if (typeof ctx2.setStatus === "function") {
|
|
2136
|
+
ctx2.setStatus(`running: xhs-unified ${config.keyword}`);
|
|
2137
|
+
}
|
|
2138
|
+
} catch (err) {
|
|
2139
|
+
alert(`\u542F\u52A8\u5931\u8D25: ${err?.message || String(err)}`);
|
|
2140
|
+
if (typeof ctx2.appendLog === "function") {
|
|
2141
|
+
ctx2.appendLog(`[ui][error] xhs-unified \u542F\u52A8\u5931\u8D25: ${err?.message || String(err)}`);
|
|
2142
|
+
}
|
|
2143
|
+
return;
|
|
2144
|
+
} finally {
|
|
2145
|
+
startBtn.disabled = false;
|
|
2146
|
+
startBtn.textContent = prevText || "\u5F00\u59CB\u722C\u53D6";
|
|
2147
|
+
}
|
|
2148
|
+
if (typeof ctx2.setActiveTab === "function") {
|
|
2149
|
+
ctx2.setActiveTab("dashboard");
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
autoLikeCb.onchange = updateLikeKeywordsState;
|
|
2153
|
+
importBtn.onclick = importConfig;
|
|
2154
|
+
exportBtn.onclick = exportConfig;
|
|
2155
|
+
startBtn.onclick = startCrawl;
|
|
2156
|
+
[
|
|
2157
|
+
keywordInput,
|
|
2158
|
+
targetInput,
|
|
2159
|
+
envSelect,
|
|
2160
|
+
accountSelect,
|
|
2161
|
+
fetchBodyCb,
|
|
2162
|
+
fetchCommentsCb,
|
|
2163
|
+
maxCommentsInput,
|
|
2164
|
+
autoLikeCb,
|
|
2165
|
+
likeKeywordsInput,
|
|
2166
|
+
headlessCb,
|
|
2167
|
+
dryRunCb
|
|
2168
|
+
].forEach((el) => {
|
|
2169
|
+
el.onchange = saveConfig;
|
|
2170
|
+
if (el.tagName === "INPUT" && el.type !== "checkbox") {
|
|
2171
|
+
el.oninput = saveConfig;
|
|
2172
|
+
}
|
|
2173
|
+
});
|
|
2174
|
+
void loadConfig();
|
|
2175
|
+
void loadAccounts();
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
// src/renderer/tabs-new/dashboard.mts
|
|
2179
|
+
function renderDashboard(root, ctx2) {
|
|
2180
|
+
root.innerHTML = "";
|
|
2181
|
+
const pageIndicator = createEl("div", { className: "page-indicator" }, [
|
|
2182
|
+
"\u5F53\u524D: ",
|
|
2183
|
+
createEl("span", {}, ["\u770B\u677F\u9875"]),
|
|
2184
|
+
" \u2190 \u4ECE\u914D\u7F6E\u9875\u8DF3\u5165 | \u5B8C\u6210\u540E\u8FD4\u56DE ",
|
|
2185
|
+
createEl("span", {}, ["\u914D\u7F6E\u9875"])
|
|
2186
|
+
]);
|
|
2187
|
+
root.appendChild(pageIndicator);
|
|
2188
|
+
const statsGrid = createEl("div", { className: "bento-grid bento-4", style: "margin-bottom: var(--gap);" });
|
|
2189
|
+
statsGrid.innerHTML = `
|
|
2190
|
+
<div class="stat-card info">
|
|
2191
|
+
<div class="stat-value" id="stat-collected">0</div>
|
|
2192
|
+
<div class="stat-label">\u5DF2\u91C7\u96C6</div>
|
|
2193
|
+
</div>
|
|
2194
|
+
<div class="stat-card success">
|
|
2195
|
+
<div class="stat-value" id="stat-success">0</div>
|
|
2196
|
+
<div class="stat-label">\u6210\u529F</div>
|
|
2197
|
+
</div>
|
|
2198
|
+
<div class="stat-card danger">
|
|
2199
|
+
<div class="stat-value" id="stat-failed">0</div>
|
|
2200
|
+
<div class="stat-label">\u5931\u8D25</div>
|
|
2201
|
+
</div>
|
|
2202
|
+
<div class="stat-card warning">
|
|
2203
|
+
<div class="stat-value" id="stat-remaining">0</div>
|
|
2204
|
+
<div class="stat-label">\u5269\u4F59</div>
|
|
2205
|
+
</div>
|
|
2206
|
+
`;
|
|
2207
|
+
root.appendChild(statsGrid);
|
|
2208
|
+
const runSummaryGrid = createEl("div", { className: "bento-grid", style: "margin-bottom: var(--gap);" });
|
|
2209
|
+
const runSummaryCard = createEl("div", { className: "bento-cell highlight" });
|
|
2210
|
+
runSummaryCard.innerHTML = `
|
|
2211
|
+
<div class="bento-title">\u8FD0\u884C\u6458\u8981</div>
|
|
2212
|
+
<div style="display:grid; grid-template-columns: minmax(200px, 1fr) 160px; gap: var(--gap); margin-bottom: var(--gap-sm);">
|
|
2213
|
+
<div>
|
|
2214
|
+
<label>\u5F53\u524D Run ID</label>
|
|
2215
|
+
<div id="run-id-text" style="font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--text-1); word-break: break-all;">-</div>
|
|
2216
|
+
</div>
|
|
2217
|
+
<div>
|
|
2218
|
+
<label>\u7D2F\u8BA1\u9519\u8BEF</label>
|
|
2219
|
+
<div id="error-count-text" style="font-weight:700; color: var(--danger); font-size: 18px;">0</div>
|
|
2220
|
+
</div>
|
|
2221
|
+
</div>
|
|
2222
|
+
<div>
|
|
2223
|
+
<label>\u6700\u8FD1\u9519\u8BEF\uFF08\u6700\u591A 8 \u6761\uFF09</label>
|
|
2224
|
+
<div id="recent-errors-empty" class="muted" style="font-size: 12px;">\u6682\u65E0\u9519\u8BEF</div>
|
|
2225
|
+
<ul id="recent-errors-list" style="margin: 6px 0 0 16px; padding: 0; font-size: 12px; line-height: 1.5; display:none;"></ul>
|
|
2226
|
+
</div>
|
|
2227
|
+
`;
|
|
2228
|
+
runSummaryGrid.appendChild(runSummaryCard);
|
|
2229
|
+
root.appendChild(runSummaryGrid);
|
|
2230
|
+
const mainGrid = createEl("div", { className: "bento-grid bento-aside" });
|
|
2231
|
+
const taskCard = createEl("div", { className: "bento-cell" });
|
|
2232
|
+
taskCard.innerHTML = `
|
|
2233
|
+
<div class="bento-title">\u5F53\u524D\u4EFB\u52A1</div>
|
|
2234
|
+
|
|
2235
|
+
<div class="row" style="margin-bottom: var(--gap);">
|
|
2236
|
+
<div>
|
|
2237
|
+
<label>\u5173\u952E\u8BCD</label>
|
|
2238
|
+
<div id="task-keyword" style="font-weight: 600; color: var(--text-1);">-</div>
|
|
2239
|
+
</div>
|
|
2240
|
+
<div>
|
|
2241
|
+
<label>\u76EE\u6807\u6570\u91CF</label>
|
|
2242
|
+
<div id="task-target" style="font-weight: 600; color: var(--text-1);">-</div>
|
|
2243
|
+
</div>
|
|
2244
|
+
<div>
|
|
2245
|
+
<label>\u4F7F\u7528\u8D26\u6237</label>
|
|
2246
|
+
<div id="task-account" style="font-weight: 600; color: var(--text-1);">-</div>
|
|
2247
|
+
</div>
|
|
2248
|
+
</div>
|
|
2249
|
+
|
|
2250
|
+
<div class="phase-indicator" style="margin-bottom: var(--gap);">
|
|
2251
|
+
<div style="display: flex; justify-content: space-between; margin-bottom: 6px;">
|
|
2252
|
+
<span style="color: var(--text-3); font-size: 12px;">\u5F53\u524D\u9636\u6BB5</span>
|
|
2253
|
+
<span id="current-phase" style="font-weight: 600; color: var(--accent-light);">\u5F85\u542F\u52A8</span>
|
|
2254
|
+
</div>
|
|
2255
|
+
<div style="display: flex; justify-content: space-between;">
|
|
2256
|
+
<span style="color: var(--text-3); font-size: 12px;">\u5F53\u524D\u64CD\u4F5C</span>
|
|
2257
|
+
<span id="current-action" style="color: var(--text-1);">-</span>
|
|
2258
|
+
</div>
|
|
2259
|
+
</div>
|
|
2260
|
+
|
|
2261
|
+
<div style="margin-bottom: var(--gap);">
|
|
2262
|
+
<div style="display: flex; justify-content: space-between; margin-bottom: 6px;">
|
|
2263
|
+
<span style="font-size: 12px; color: var(--text-3);">\u6574\u4F53\u8FDB\u5EA6</span>
|
|
2264
|
+
<span id="progress-percent" style="font-size: 12px; color: var(--text-1);">0%</span>
|
|
2265
|
+
</div>
|
|
2266
|
+
<div class="progress-bar-container">
|
|
2267
|
+
<div id="progress-bar" class="progress-bar" style="width: 0%;"></div>
|
|
2268
|
+
</div>
|
|
2269
|
+
</div>
|
|
2270
|
+
|
|
2271
|
+
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--gap-sm);">
|
|
2272
|
+
<div style="font-size: 12px;"><span style="color: var(--text-4);">\u8BC4\u8BBA\u91C7\u96C6\uFF1A</span><span id="stat-comments">0\u6761</span></div>
|
|
2273
|
+
<div style="font-size: 12px;"><span style="color: var(--text-4);">\u70B9\u8D5E\u64CD\u4F5C\uFF1A</span><span id="stat-likes">0\u6B21</span></div>
|
|
2274
|
+
<div style="font-size: 12px;"><span style="color: var(--text-4);">\u9650\u6D41\u6B21\u6570\uFF1A</span><span id="stat-ratelimit" style="color: var(--warning);">0\u6B21</span></div>
|
|
2275
|
+
<div style="font-size: 12px;"><span style="color: var(--text-4);">\u8FD0\u884C\u65F6\u95F4\uFF1A</span><span id="stat-elapsed">00:00:00</span></div>
|
|
2276
|
+
</div>
|
|
2277
|
+
`;
|
|
2278
|
+
mainGrid.appendChild(taskCard);
|
|
2279
|
+
const logsCard = createEl("div", { className: "bento-cell" });
|
|
2280
|
+
logsCard.innerHTML = `
|
|
2281
|
+
<div class="bento-title">
|
|
2282
|
+
\u5B9E\u65F6\u65E5\u5FD7
|
|
2283
|
+
<button id="toggle-logs-btn" class="secondary" style="margin-left: auto; padding: 4px 10px; font-size: 11px;">\u5C55\u5F00</button>
|
|
2284
|
+
</div>
|
|
2285
|
+
<div id="logs-container" class="log-container" style="display: none; max-height: 300px;"></div>
|
|
2286
|
+
|
|
2287
|
+
<div style="margin-top: var(--gap);">
|
|
2288
|
+
<div class="btn-group">
|
|
2289
|
+
<button id="pause-btn" class="secondary" style="flex: 1;">\u6682\u505C</button>
|
|
2290
|
+
<button id="stop-btn" class="danger" style="flex: 1;">\u505C\u6B62</button>
|
|
2291
|
+
</div>
|
|
2292
|
+
</div>
|
|
2293
|
+
`;
|
|
2294
|
+
mainGrid.appendChild(logsCard);
|
|
2295
|
+
root.appendChild(mainGrid);
|
|
2296
|
+
const statCollected = root.querySelector("#stat-collected");
|
|
2297
|
+
const statSuccess = root.querySelector("#stat-success");
|
|
2298
|
+
const statFailed = root.querySelector("#stat-failed");
|
|
2299
|
+
const statRemaining = root.querySelector("#stat-remaining");
|
|
2300
|
+
const taskKeyword = root.querySelector("#task-keyword");
|
|
2301
|
+
const taskTarget = root.querySelector("#task-target");
|
|
2302
|
+
const taskAccount = root.querySelector("#task-account");
|
|
2303
|
+
const currentPhase = root.querySelector("#current-phase");
|
|
2304
|
+
const currentAction = root.querySelector("#current-action");
|
|
2305
|
+
const progressPercent = root.querySelector("#progress-percent");
|
|
2306
|
+
const progressBar = root.querySelector("#progress-bar");
|
|
2307
|
+
const statComments = root.querySelector("#stat-comments");
|
|
2308
|
+
const statLikes = root.querySelector("#stat-likes");
|
|
2309
|
+
const statRatelimit = root.querySelector("#stat-ratelimit");
|
|
2310
|
+
const statElapsed = root.querySelector("#stat-elapsed");
|
|
2311
|
+
const runIdText = root.querySelector("#run-id-text");
|
|
2312
|
+
const errorCountText = root.querySelector("#error-count-text");
|
|
2313
|
+
const recentErrorsEmpty = root.querySelector("#recent-errors-empty");
|
|
2314
|
+
const recentErrorsList = root.querySelector("#recent-errors-list");
|
|
2315
|
+
const logsContainer = root.querySelector("#logs-container");
|
|
2316
|
+
const toggleLogsBtn = root.querySelector("#toggle-logs-btn");
|
|
2317
|
+
const pauseBtn = root.querySelector("#pause-btn");
|
|
2318
|
+
const stopBtn = root.querySelector("#stop-btn");
|
|
2319
|
+
let logsExpanded = false;
|
|
2320
|
+
let paused = false;
|
|
2321
|
+
let startTime = Date.now();
|
|
2322
|
+
let elapsedTimer = null;
|
|
2323
|
+
let unsubscribeState = null;
|
|
2324
|
+
let unsubscribeCmd = null;
|
|
2325
|
+
let activeRunId = String(ctx2?.xhsCurrentRun?.runId || "").trim();
|
|
2326
|
+
let errorCountTotal = 0;
|
|
2327
|
+
const recentErrors = [];
|
|
2328
|
+
const maxLogs = 500;
|
|
2329
|
+
const maxRecentErrors = 8;
|
|
2330
|
+
function renderRunSummary() {
|
|
2331
|
+
runIdText.textContent = activeRunId || "-";
|
|
2332
|
+
errorCountText.textContent = String(errorCountTotal);
|
|
2333
|
+
recentErrorsList.innerHTML = "";
|
|
2334
|
+
if (recentErrors.length === 0) {
|
|
2335
|
+
recentErrorsEmpty.style.display = "block";
|
|
2336
|
+
recentErrorsList.style.display = "none";
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
recentErrorsEmpty.style.display = "none";
|
|
2340
|
+
recentErrorsList.style.display = "block";
|
|
2341
|
+
recentErrors.forEach((item) => {
|
|
2342
|
+
const li = document.createElement("li");
|
|
2343
|
+
li.textContent = `[${item.ts}] ${item.source}: ${item.message}`;
|
|
2344
|
+
recentErrorsList.appendChild(li);
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
function pushRecentError(message, source = "runtime") {
|
|
2348
|
+
const msg = String(message || "").trim();
|
|
2349
|
+
if (!msg) return;
|
|
2350
|
+
errorCountTotal += 1;
|
|
2351
|
+
recentErrors.push({
|
|
2352
|
+
ts: (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false }),
|
|
2353
|
+
source: String(source || "runtime").trim() || "runtime",
|
|
2354
|
+
message: msg
|
|
2355
|
+
});
|
|
2356
|
+
while (recentErrors.length > maxRecentErrors) recentErrors.shift();
|
|
2357
|
+
renderRunSummary();
|
|
2358
|
+
}
|
|
2359
|
+
function updateElapsed() {
|
|
2360
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1e3);
|
|
2361
|
+
const h = Math.floor(elapsed / 3600);
|
|
2362
|
+
const m = Math.floor(elapsed % 3600 / 60);
|
|
2363
|
+
const s = elapsed % 60;
|
|
2364
|
+
statElapsed.textContent = `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
2365
|
+
}
|
|
2366
|
+
function addLog(line, type = "info") {
|
|
2367
|
+
const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false });
|
|
2368
|
+
const logLine = createEl("div", { className: "log-line" });
|
|
2369
|
+
logLine.innerHTML = `<span class="log-time">[${ts}]</span> <span class="log-${type}">${line}</span>`;
|
|
2370
|
+
logsContainer.appendChild(logLine);
|
|
2371
|
+
while (logsContainer.children.length > maxLogs) {
|
|
2372
|
+
logsContainer.removeChild(logsContainer.firstChild);
|
|
2373
|
+
}
|
|
2374
|
+
if (logsExpanded) {
|
|
2375
|
+
logsContainer.scrollTop = logsContainer.scrollHeight;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
function updateFromTaskState(state) {
|
|
2379
|
+
if (!state) return;
|
|
2380
|
+
const collected = state.collected || state.progress || 0;
|
|
2381
|
+
const target = state.target || 50;
|
|
2382
|
+
const success = state.success || collected;
|
|
2383
|
+
const failed = state.failed || state.errors || 0;
|
|
2384
|
+
const remaining = Math.max(0, target - collected);
|
|
2385
|
+
statCollected.textContent = String(collected);
|
|
2386
|
+
statSuccess.textContent = String(success);
|
|
2387
|
+
statFailed.textContent = String(failed);
|
|
2388
|
+
statRemaining.textContent = String(remaining);
|
|
2389
|
+
const percent = target > 0 ? Math.round(collected / target * 100) : 0;
|
|
2390
|
+
progressPercent.textContent = `${percent}%`;
|
|
2391
|
+
progressBar.style.width = `${percent}%`;
|
|
2392
|
+
if (state.phase) {
|
|
2393
|
+
currentPhase.textContent = state.phase;
|
|
2394
|
+
}
|
|
2395
|
+
if (state.action) {
|
|
2396
|
+
currentAction.textContent = state.action;
|
|
2397
|
+
}
|
|
2398
|
+
if (state.comments) {
|
|
2399
|
+
statComments.textContent = `${state.comments}\u6761`;
|
|
2400
|
+
}
|
|
2401
|
+
if (state.likes) {
|
|
2402
|
+
statLikes.textContent = `${state.likes}\u6B21`;
|
|
2403
|
+
}
|
|
2404
|
+
if (state.ratelimits) {
|
|
2405
|
+
statRatelimit.textContent = `${state.ratelimits}\u6B21`;
|
|
2406
|
+
}
|
|
2407
|
+
if (state.keyword) {
|
|
2408
|
+
taskKeyword.textContent = state.keyword;
|
|
2409
|
+
}
|
|
2410
|
+
if (state.target) {
|
|
2411
|
+
taskTarget.textContent = String(state.target);
|
|
2412
|
+
}
|
|
2413
|
+
if (state.profileId) {
|
|
2414
|
+
const aliases = ctx2.api?.settings?.profileAliases || {};
|
|
2415
|
+
taskAccount.textContent = aliases[state.profileId] || state.profileId;
|
|
2416
|
+
}
|
|
2417
|
+
if (state.runId) {
|
|
2418
|
+
activeRunId = String(state.runId);
|
|
2419
|
+
renderRunSummary();
|
|
2420
|
+
}
|
|
2421
|
+
if (state.error) {
|
|
2422
|
+
pushRecentError(String(state.error), "state");
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
function pickTaskFromList(tasks) {
|
|
2426
|
+
const target = activeRunId;
|
|
2427
|
+
if (target) {
|
|
2428
|
+
const matched = tasks.find((item) => String(item?.runId || "").trim() === target);
|
|
2429
|
+
if (matched) return matched;
|
|
2430
|
+
}
|
|
2431
|
+
const running = tasks.find((item) => ["running", "queued", "pending", "starting"].includes(String(item?.status || "").toLowerCase()));
|
|
2432
|
+
return running || tasks[0] || null;
|
|
2433
|
+
}
|
|
2434
|
+
function updateFromEventPayload(payload) {
|
|
2435
|
+
const event = String(payload?.event || "").trim();
|
|
2436
|
+
if (!event) return;
|
|
2437
|
+
if (event === "xhs.unified.start") {
|
|
2438
|
+
currentPhase.textContent = "\u8FD0\u884C\u4E2D";
|
|
2439
|
+
currentAction.textContent = "\u542F\u52A8 autoscript";
|
|
2440
|
+
if (payload.runId) {
|
|
2441
|
+
activeRunId = String(payload.runId || "").trim() || activeRunId;
|
|
2442
|
+
}
|
|
2443
|
+
if (payload.keyword) taskKeyword.textContent = String(payload.keyword);
|
|
2444
|
+
if (payload.maxNotes) taskTarget.textContent = String(payload.maxNotes);
|
|
2445
|
+
renderRunSummary();
|
|
2446
|
+
return;
|
|
2447
|
+
}
|
|
2448
|
+
if (event === "autoscript:operation_done") {
|
|
2449
|
+
const opId = String(payload.operationId || "").trim();
|
|
2450
|
+
currentAction.textContent = opId || currentAction.textContent;
|
|
2451
|
+
const result = payload.result && typeof payload.result === "object" ? payload.result : {};
|
|
2452
|
+
if (opId === "comments_harvest") {
|
|
2453
|
+
const nowComments = Number((statComments.textContent || "0").replace(/[^\d]/g, "")) || 0;
|
|
2454
|
+
const added = Number(result.collected || 0);
|
|
2455
|
+
statComments.textContent = `${Math.max(0, nowComments + added)}\u6761`;
|
|
2456
|
+
}
|
|
2457
|
+
if (opId === "comment_like") {
|
|
2458
|
+
const nowLikes = Number((statLikes.textContent || "0").replace(/[^\d]/g, "")) || 0;
|
|
2459
|
+
const added = Number(result.likedCount || 0);
|
|
2460
|
+
statLikes.textContent = `${Math.max(0, nowLikes + added)}\u6B21`;
|
|
2461
|
+
}
|
|
2462
|
+
return;
|
|
2463
|
+
}
|
|
2464
|
+
if (event === "autoscript:operation_error" || event === "autoscript:operation_recovery_failed" || event === "xhs.unified.profile_failed") {
|
|
2465
|
+
const failed = Number(statFailed.textContent || "0") || 0;
|
|
2466
|
+
statFailed.textContent = String(failed + 1);
|
|
2467
|
+
const opId = String(payload?.operationId || "").trim();
|
|
2468
|
+
const err = String(payload?.error || payload?.message || payload?.code || event).trim();
|
|
2469
|
+
pushRecentError(opId ? `${opId}: ${err}` : err, event);
|
|
2470
|
+
return;
|
|
2471
|
+
}
|
|
2472
|
+
if (event === "xhs.unified.merged") {
|
|
2473
|
+
currentPhase.textContent = "\u5DF2\u5B8C\u6210";
|
|
2474
|
+
currentAction.textContent = "\u7ED3\u679C\u5408\u5E76\u5B8C\u6210";
|
|
2475
|
+
if (payload.profilesFailed) {
|
|
2476
|
+
statFailed.textContent = String(Number(payload.profilesFailed) || 0);
|
|
2477
|
+
}
|
|
2478
|
+
return;
|
|
2479
|
+
}
|
|
2480
|
+
if (event === "xhs.unified.stop") {
|
|
2481
|
+
const reason = String(payload.reason || "").trim();
|
|
2482
|
+
currentPhase.textContent = reason && reason !== "script_failure" ? "\u5DF2\u7ED3\u675F" : "\u5931\u8D25";
|
|
2483
|
+
currentAction.textContent = reason || "stop";
|
|
2484
|
+
if (reason && reason !== "completed") {
|
|
2485
|
+
pushRecentError(`stop reason=${reason}`, event);
|
|
2486
|
+
}
|
|
2487
|
+
renderRunSummary();
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
function parseLineEvent(line) {
|
|
2491
|
+
const text = String(line || "").trim();
|
|
2492
|
+
if (!text.startsWith("{") || !text.endsWith("}")) return;
|
|
2493
|
+
try {
|
|
2494
|
+
const payload = JSON.parse(text);
|
|
2495
|
+
updateFromEventPayload(payload);
|
|
2496
|
+
} catch {
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
function subscribeToUpdates() {
|
|
2500
|
+
if (typeof ctx2.api?.onStateUpdate === "function") {
|
|
2501
|
+
unsubscribeState = ctx2.api.onStateUpdate((update) => {
|
|
2502
|
+
if (paused) return;
|
|
2503
|
+
const runId = String(update?.runId || "").trim();
|
|
2504
|
+
if (activeRunId && runId && runId !== activeRunId) return;
|
|
2505
|
+
if (!activeRunId && runId) {
|
|
2506
|
+
activeRunId = runId;
|
|
2507
|
+
renderRunSummary();
|
|
2508
|
+
}
|
|
2509
|
+
if (update?.data && typeof update.data === "object") {
|
|
2510
|
+
const payload = { ...update.data || {}, runId };
|
|
2511
|
+
updateFromTaskState(payload);
|
|
2512
|
+
if (payload.action) addLog(String(payload.action), "info");
|
|
2513
|
+
if (payload.error) {
|
|
2514
|
+
addLog(String(payload.error), "error");
|
|
2515
|
+
pushRecentError(String(payload.error), "state");
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2520
|
+
if (typeof ctx2.api?.onCmdEvent === "function") {
|
|
2521
|
+
unsubscribeCmd = ctx2.api.onCmdEvent((evt) => {
|
|
2522
|
+
if (paused) return;
|
|
2523
|
+
const runId = String(evt?.runId || "").trim();
|
|
2524
|
+
if (!activeRunId && evt?.type === "started" && String(evt?.title || "").includes("xhs unified")) {
|
|
2525
|
+
activeRunId = runId;
|
|
2526
|
+
renderRunSummary();
|
|
2527
|
+
}
|
|
2528
|
+
if (activeRunId && runId && runId !== activeRunId) return;
|
|
2529
|
+
if (evt.type === "stdout") {
|
|
2530
|
+
addLog(evt.line, "info");
|
|
2531
|
+
parseLineEvent(String(evt.line || "").trim());
|
|
2532
|
+
} else if (evt.type === "stderr") {
|
|
2533
|
+
addLog(evt.line, "error");
|
|
2534
|
+
pushRecentError(String(evt.line || ""), "stderr");
|
|
2535
|
+
const failed = Number(statFailed.textContent || "0") || 0;
|
|
2536
|
+
statFailed.textContent = String(failed + 1);
|
|
2537
|
+
} else if (evt.type === "exit") {
|
|
2538
|
+
currentPhase.textContent = Number(evt.exitCode || 0) === 0 ? "\u5DF2\u7ED3\u675F" : "\u5931\u8D25";
|
|
2539
|
+
currentAction.textContent = `exit(${evt.exitCode ?? "null"})`;
|
|
2540
|
+
addLog(`\u8FDB\u7A0B\u9000\u51FA: code=${evt.exitCode}`, evt.exitCode === 0 ? "success" : "error");
|
|
2541
|
+
if (Number(evt.exitCode || 0) !== 0) {
|
|
2542
|
+
pushRecentError(`\u8FDB\u7A0B\u9000\u51FA code=${evt.exitCode ?? "null"}`, "exit");
|
|
2543
|
+
}
|
|
2544
|
+
renderRunSummary();
|
|
2545
|
+
}
|
|
2546
|
+
});
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
async function loadTaskInfo() {
|
|
2550
|
+
try {
|
|
2551
|
+
const config = await ctx2.api.configLoadLast();
|
|
2552
|
+
if (config) {
|
|
2553
|
+
taskKeyword.textContent = config.keyword || "-";
|
|
2554
|
+
taskTarget.textContent = String(config.target || 50);
|
|
2555
|
+
if (config.lastProfileId) {
|
|
2556
|
+
const aliases = ctx2.api?.settings?.profileAliases || {};
|
|
2557
|
+
taskAccount.textContent = aliases[config.lastProfileId] || config.lastProfileId;
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
} catch (err) {
|
|
2561
|
+
console.error("Failed to load task info:", err);
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
async function fetchCurrentState() {
|
|
2565
|
+
try {
|
|
2566
|
+
const tasks = await ctx2.api.stateGetTasks();
|
|
2567
|
+
if (Array.isArray(tasks) && tasks.length > 0) {
|
|
2568
|
+
const picked = pickTaskFromList(tasks);
|
|
2569
|
+
if (picked) {
|
|
2570
|
+
const runId = String(picked?.runId || "").trim();
|
|
2571
|
+
if (runId) {
|
|
2572
|
+
activeRunId = runId;
|
|
2573
|
+
renderRunSummary();
|
|
2574
|
+
}
|
|
2575
|
+
updateFromTaskState(picked);
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
} catch (err) {
|
|
2579
|
+
console.error("Failed to fetch state:", err);
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
toggleLogsBtn.onclick = () => {
|
|
2583
|
+
logsExpanded = !logsExpanded;
|
|
2584
|
+
logsContainer.style.display = logsExpanded ? "block" : "none";
|
|
2585
|
+
toggleLogsBtn.textContent = logsExpanded ? "\u6536\u8D77" : "\u5C55\u5F00";
|
|
2586
|
+
};
|
|
2587
|
+
pauseBtn.onclick = () => {
|
|
2588
|
+
paused = !paused;
|
|
2589
|
+
pauseBtn.textContent = paused ? "\u7EE7\u7EED" : "\u6682\u505C";
|
|
2590
|
+
if (paused) {
|
|
2591
|
+
addLog("\u4EFB\u52A1\u5DF2\u6682\u505C", "warn");
|
|
2592
|
+
} else {
|
|
2593
|
+
addLog("\u4EFB\u52A1\u7EE7\u7EED\u6267\u884C", "info");
|
|
2594
|
+
}
|
|
2595
|
+
};
|
|
2596
|
+
stopBtn.onclick = async () => {
|
|
2597
|
+
if (confirm("\u786E\u5B9A\u8981\u505C\u6B62\u5F53\u524D\u4EFB\u52A1\u5417\uFF1F")) {
|
|
2598
|
+
try {
|
|
2599
|
+
const tasks = await ctx2.api.stateGetTasks();
|
|
2600
|
+
if (Array.isArray(tasks) && tasks.length > 0 && tasks[0].runId) {
|
|
2601
|
+
await ctx2.api.cmdKill(tasks[0].runId);
|
|
2602
|
+
addLog("\u4EFB\u52A1\u5DF2\u505C\u6B62", "warn");
|
|
2603
|
+
}
|
|
2604
|
+
} catch (err) {
|
|
2605
|
+
console.error("Failed to stop task:", err);
|
|
2606
|
+
}
|
|
2607
|
+
setTimeout(() => {
|
|
2608
|
+
if (typeof ctx2.setActiveTab === "function") {
|
|
2609
|
+
ctx2.setActiveTab("config");
|
|
2610
|
+
}
|
|
2611
|
+
}, 1500);
|
|
2612
|
+
}
|
|
2613
|
+
};
|
|
2614
|
+
renderRunSummary();
|
|
2615
|
+
loadTaskInfo();
|
|
2616
|
+
subscribeToUpdates();
|
|
2617
|
+
fetchCurrentState();
|
|
2618
|
+
elapsedTimer = setInterval(updateElapsed, 1e3);
|
|
2619
|
+
return () => {
|
|
2620
|
+
if (elapsedTimer) clearInterval(elapsedTimer);
|
|
2621
|
+
if (unsubscribeState) unsubscribeState();
|
|
2622
|
+
if (unsubscribeCmd) unsubscribeCmd();
|
|
2623
|
+
};
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
// src/renderer/tabs-new/account-manager.mts
|
|
2627
|
+
function renderAccountManager(root, ctx2) {
|
|
2628
|
+
root.innerHTML = "";
|
|
2629
|
+
const bentoGrid = createEl("div", { className: "bento-grid bento-sidebar" });
|
|
2630
|
+
const envCard = createEl("div", { className: "bento-cell" });
|
|
2631
|
+
envCard.innerHTML = `
|
|
2632
|
+
<div class="bento-title"><span style="color: var(--success);">\u25CF</span> \u73AF\u5883\u72B6\u6001</div>
|
|
2633
|
+
<div class="env-status-grid">
|
|
2634
|
+
<div class="env-item" id="env-camo">
|
|
2635
|
+
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
2636
|
+
<span>Camoufox CLI</span>
|
|
2637
|
+
</div>
|
|
2638
|
+
<div class="env-item" id="env-unified">
|
|
2639
|
+
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
2640
|
+
<span>Unified API</span>
|
|
2641
|
+
</div>
|
|
2642
|
+
<div class="env-item" id="env-browser">
|
|
2643
|
+
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
2644
|
+
<span>Browser Service</span>
|
|
2645
|
+
</div>
|
|
2646
|
+
<div class="env-item" id="env-firefox">
|
|
2647
|
+
<span class="icon" style="color: var(--text-4);">\u25CB</span>
|
|
2648
|
+
<span>Firefox</span>
|
|
2649
|
+
</div>
|
|
2650
|
+
</div>
|
|
2651
|
+
<div style="margin-top: var(--gap);">
|
|
2652
|
+
<button id="recheck-env-btn" class="secondary" style="width: 100%;">\u91CD\u65B0\u68C0\u67E5</button>
|
|
2653
|
+
</div>
|
|
2654
|
+
`;
|
|
2655
|
+
bentoGrid.appendChild(envCard);
|
|
2656
|
+
const accountCard = createEl("div", { className: "bento-cell" });
|
|
2657
|
+
accountCard.innerHTML = `
|
|
2658
|
+
<div class="bento-title">
|
|
2659
|
+
\u8D26\u6237\u5217\u8868
|
|
2660
|
+
<button id="add-account-btn" style="margin-left: auto; padding: 6px 12px; font-size: 12px;">\u6DFB\u52A0\u8D26\u6237</button>
|
|
2661
|
+
</div>
|
|
2662
|
+
<div id="account-list" class="account-list" style="margin-bottom: var(--gap); max-height: 300px; overflow: auto;"></div>
|
|
2663
|
+
<div style="margin-top: var(--gap);">
|
|
2664
|
+
<div class="btn-group">
|
|
2665
|
+
<button id="check-all-btn" class="secondary" style="flex: 1;">\u68C0\u67E5\u6240\u6709</button>
|
|
2666
|
+
<button id="refresh-expired-btn" class="secondary" style="flex: 1;">\u5237\u65B0\u5931\u6548</button>
|
|
2667
|
+
</div>
|
|
2668
|
+
</div>
|
|
2669
|
+
`;
|
|
2670
|
+
bentoGrid.appendChild(accountCard);
|
|
2671
|
+
root.appendChild(bentoGrid);
|
|
2672
|
+
const recheckEnvBtn = root.querySelector("#recheck-env-btn");
|
|
2673
|
+
const addAccountBtn = root.querySelector("#add-account-btn");
|
|
2674
|
+
const checkAllBtn = root.querySelector("#check-all-btn");
|
|
2675
|
+
const refreshExpiredBtn = root.querySelector("#refresh-expired-btn");
|
|
2676
|
+
const accountListEl = root.querySelector("#account-list");
|
|
2677
|
+
let accounts = [];
|
|
2678
|
+
async function checkEnvironment() {
|
|
2679
|
+
try {
|
|
2680
|
+
const [camo, services, firefox] = await Promise.all([
|
|
2681
|
+
ctx2.api.envCheckCamo(),
|
|
2682
|
+
ctx2.api.envCheckServices(),
|
|
2683
|
+
ctx2.api.envCheckFirefox()
|
|
2684
|
+
]);
|
|
2685
|
+
updateEnvItem("env-camo", camo.installed);
|
|
2686
|
+
updateEnvItem("env-unified", services.unifiedApi);
|
|
2687
|
+
updateEnvItem("env-browser", services.browserService);
|
|
2688
|
+
updateEnvItem("env-firefox", firefox.installed);
|
|
2689
|
+
} catch (err) {
|
|
2690
|
+
console.error("Environment check failed:", err);
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
function updateEnvItem(id, ok) {
|
|
2694
|
+
const el = root.querySelector(`#${id}`);
|
|
2695
|
+
if (!el) return;
|
|
2696
|
+
const icon = el.querySelector(".icon");
|
|
2697
|
+
icon.textContent = ok ? "\u2713" : "\u2717";
|
|
2698
|
+
icon.style.color = ok ? "var(--success)" : "var(--danger)";
|
|
2699
|
+
}
|
|
2700
|
+
async function loadAccounts() {
|
|
2701
|
+
try {
|
|
2702
|
+
const rows = await listAccountProfiles(ctx2.api);
|
|
2703
|
+
accounts = rows.map((row) => ({
|
|
2704
|
+
...row,
|
|
2705
|
+
statusView: row.valid ? "valid" : row.status === "pending" ? "pending" : "expired"
|
|
2706
|
+
}));
|
|
2707
|
+
renderAccountList();
|
|
2708
|
+
} catch (err) {
|
|
2709
|
+
console.error("Failed to load accounts:", err);
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
function renderAccountList() {
|
|
2713
|
+
accountListEl.innerHTML = "";
|
|
2714
|
+
if (accounts.length === 0) {
|
|
2715
|
+
accountListEl.innerHTML = '<div style="padding: 12px; text-align: center; color: var(--text-4);">\u6682\u65E0\u8D26\u6237</div>';
|
|
2716
|
+
return;
|
|
2717
|
+
}
|
|
2718
|
+
accounts.forEach((acc) => {
|
|
2719
|
+
const row = createEl("div", {
|
|
2720
|
+
className: "account-item",
|
|
2721
|
+
style: "display: grid; grid-template-columns: 1fr 120px 100px 100px; gap: var(--gap-sm); padding: var(--gap-sm); align-items: center; border-bottom: 1px solid var(--border);"
|
|
2722
|
+
});
|
|
2723
|
+
const nameDiv = createEl("div", {}, [
|
|
2724
|
+
createEl("div", { className: "account-name" }, [acc.alias || acc.name || acc.profileId]),
|
|
2725
|
+
createEl("div", { className: "account-alias" }, [acc.profileId])
|
|
2726
|
+
]);
|
|
2727
|
+
const statusBadge = createEl("span", {
|
|
2728
|
+
className: `status-badge ${acc.statusView === "valid" ? "status-valid" : acc.statusView === "expired" ? "status-expired" : "status-pending"}`
|
|
2729
|
+
}, [
|
|
2730
|
+
acc.statusView === "valid" ? "\u2713 \u6709\u6548" : acc.statusView === "expired" ? "\u2717 \u5931\u6548" : acc.statusView === "checking" ? "\u23F3 \u68C0\u67E5\u4E2D" : "\u23F3 \u5F85\u68C0\u67E5"
|
|
2731
|
+
]);
|
|
2732
|
+
const checkBtn = createEl("button", {
|
|
2733
|
+
className: "secondary",
|
|
2734
|
+
style: "padding: 6px 10px; font-size: 11px;"
|
|
2735
|
+
}, ["\u68C0\u67E5"]);
|
|
2736
|
+
const actionsDiv = createEl("div", { className: "btn-group", style: "flex: 0;" });
|
|
2737
|
+
const detailBtn = createEl("button", {
|
|
2738
|
+
className: "secondary",
|
|
2739
|
+
style: "padding: 6px 8px; font-size: 10px;"
|
|
2740
|
+
}, ["\u8BE6\u60C5"]);
|
|
2741
|
+
const deleteBtn = createEl("button", {
|
|
2742
|
+
className: "danger",
|
|
2743
|
+
style: "padding: 6px 8px; font-size: 10px;"
|
|
2744
|
+
}, ["\u5220\u9664"]);
|
|
2745
|
+
actionsDiv.appendChild(detailBtn);
|
|
2746
|
+
actionsDiv.appendChild(deleteBtn);
|
|
2747
|
+
row.appendChild(nameDiv);
|
|
2748
|
+
row.appendChild(statusBadge);
|
|
2749
|
+
row.appendChild(checkBtn);
|
|
2750
|
+
row.appendChild(actionsDiv);
|
|
2751
|
+
checkBtn.onclick = () => checkAccountStatus(acc.profileId);
|
|
2752
|
+
detailBtn.onclick = () => {
|
|
2753
|
+
alert(`\u8D26\u6237\u8BE6\u60C5:
|
|
2754
|
+
|
|
2755
|
+
Profile ID: ${acc.profileId}
|
|
2756
|
+
\u8D26\u53F7ID: ${acc.accountId || "\u672A\u8BC6\u522B"}
|
|
2757
|
+
\u522B\u540D: ${acc.alias || "\u672A\u8BBE\u7F6E"}
|
|
2758
|
+
\u72B6\u6001: ${acc.status}
|
|
2759
|
+
\u539F\u56E0: ${acc.reason || "-"}
|
|
2760
|
+
\u6700\u540E\u68C0\u67E5: ${acc.lastCheck || "\u672A\u68C0\u67E5"}`);
|
|
2761
|
+
};
|
|
2762
|
+
deleteBtn.onclick = async () => {
|
|
2763
|
+
if (confirm(`\u786E\u5B9A\u5220\u9664\u8D26\u6237 "${acc.alias || acc.profileId}" \u5417\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002`)) {
|
|
2764
|
+
try {
|
|
2765
|
+
if (acc.accountRecordId) {
|
|
2766
|
+
await ctx2.api.cmdRunJson({
|
|
2767
|
+
title: `account delete ${acc.accountRecordId}`,
|
|
2768
|
+
cwd: "",
|
|
2769
|
+
args: [
|
|
2770
|
+
ctx2.api.pathJoin("apps", "webauto", "entry", "account.mjs"),
|
|
2771
|
+
"delete",
|
|
2772
|
+
acc.accountRecordId,
|
|
2773
|
+
"--delete-profile",
|
|
2774
|
+
"--delete-fingerprint",
|
|
2775
|
+
"--json"
|
|
2776
|
+
]
|
|
2777
|
+
});
|
|
2778
|
+
} else {
|
|
2779
|
+
await ctx2.api.profileDelete({ profileId: acc.profileId, deleteFingerprint: true });
|
|
2780
|
+
}
|
|
2781
|
+
await loadAccounts();
|
|
2782
|
+
} catch (err) {
|
|
2783
|
+
alert("\u5220\u9664\u5931\u8D25: " + (err?.message || String(err)));
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
};
|
|
2787
|
+
accountListEl.appendChild(row);
|
|
2788
|
+
});
|
|
2789
|
+
}
|
|
2790
|
+
async function checkAccountStatus(profileId) {
|
|
2791
|
+
const account = accounts.find((a) => a.profileId === profileId);
|
|
2792
|
+
if (!account) return;
|
|
2793
|
+
account.statusView = "checking";
|
|
2794
|
+
renderAccountList();
|
|
2795
|
+
try {
|
|
2796
|
+
const result = await ctx2.api.cmdRunJson({
|
|
2797
|
+
title: `account sync ${profileId}`,
|
|
2798
|
+
cwd: "",
|
|
2799
|
+
args: [
|
|
2800
|
+
ctx2.api.pathJoin("apps", "webauto", "entry", "account.mjs"),
|
|
2801
|
+
"sync",
|
|
2802
|
+
profileId,
|
|
2803
|
+
"--json"
|
|
2804
|
+
]
|
|
2805
|
+
});
|
|
2806
|
+
const profile = result?.json?.profile;
|
|
2807
|
+
if (profile && String(profile.profileId || "").trim() === profileId) {
|
|
2808
|
+
account.accountId = String(profile.accountId || "").trim() || null;
|
|
2809
|
+
account.alias = String(profile.alias || "").trim() || account.alias;
|
|
2810
|
+
account.status = String(profile.status || "").trim() || account.status;
|
|
2811
|
+
account.valid = profile.valid === true && Boolean(account.accountId);
|
|
2812
|
+
account.reason = String(profile.reason || "").trim() || null;
|
|
2813
|
+
}
|
|
2814
|
+
account.statusView = account.valid ? "valid" : "expired";
|
|
2815
|
+
account.lastCheck = (/* @__PURE__ */ new Date()).toLocaleString("zh-CN");
|
|
2816
|
+
} catch (err) {
|
|
2817
|
+
account.statusView = "expired";
|
|
2818
|
+
}
|
|
2819
|
+
renderAccountList();
|
|
2820
|
+
}
|
|
2821
|
+
async function addAccount() {
|
|
2822
|
+
const alias = prompt("\u8BF7\u8F93\u5165\u65B0\u8D26\u6237\u522B\u540D:");
|
|
2823
|
+
if (!alias?.trim()) return;
|
|
2824
|
+
try {
|
|
2825
|
+
const out = await ctx2.api.cmdRunJson({
|
|
2826
|
+
title: "profilepool add",
|
|
2827
|
+
cwd: "",
|
|
2828
|
+
args: [ctx2.api.pathJoin("apps", "webauto", "entry", "profilepool.mjs"), "add", "xiaohongshu", "--json"]
|
|
2829
|
+
});
|
|
2830
|
+
if (!out?.ok || !out?.json?.profileId) {
|
|
2831
|
+
alert("\u521B\u5EFA\u8D26\u53F7\u5931\u8D25: " + (out?.error || "\u672A\u77E5\u9519\u8BEF"));
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2834
|
+
const profileId = out.json.profileId;
|
|
2835
|
+
const aliases = { ...ctx2.api.settings?.profileAliases, [profileId]: alias.trim() };
|
|
2836
|
+
await ctx2.api.settingsSet({ profileAliases: aliases });
|
|
2837
|
+
if (typeof ctx2.refreshSettings === "function") {
|
|
2838
|
+
await ctx2.refreshSettings();
|
|
2839
|
+
}
|
|
2840
|
+
const timeoutSec = ctx2.api.settings?.timeouts?.loginTimeoutSec || 900;
|
|
2841
|
+
await ctx2.api.cmdSpawn({
|
|
2842
|
+
title: `\u767B\u5F55 ${alias}`,
|
|
2843
|
+
cwd: "",
|
|
2844
|
+
args: [
|
|
2845
|
+
ctx2.api.pathJoin("apps", "webauto", "entry", "profilepool.mjs"),
|
|
2846
|
+
"login-profile",
|
|
2847
|
+
profileId,
|
|
2848
|
+
"--timeout-sec",
|
|
2849
|
+
String(timeoutSec),
|
|
2850
|
+
"--keep-session"
|
|
2851
|
+
],
|
|
2852
|
+
groupKey: "profilepool"
|
|
2853
|
+
});
|
|
2854
|
+
await loadAccounts();
|
|
2855
|
+
} catch (err) {
|
|
2856
|
+
alert("\u6DFB\u52A0\u8D26\u53F7\u5931\u8D25: " + (err?.message || String(err)));
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
async function checkAllAccounts() {
|
|
2860
|
+
checkAllBtn.disabled = true;
|
|
2861
|
+
checkAllBtn.textContent = "\u68C0\u67E5\u4E2D...";
|
|
2862
|
+
for (const acc of accounts) {
|
|
2863
|
+
await checkAccountStatus(acc.profileId);
|
|
2864
|
+
}
|
|
2865
|
+
checkAllBtn.disabled = false;
|
|
2866
|
+
checkAllBtn.textContent = "\u68C0\u67E5\u6240\u6709";
|
|
2867
|
+
}
|
|
2868
|
+
async function refreshExpiredAccounts() {
|
|
2869
|
+
const expired = accounts.filter((a) => !a.valid);
|
|
2870
|
+
if (expired.length === 0) {
|
|
2871
|
+
alert("\u6CA1\u6709\u5931\u6548\u7684\u8D26\u6237\u9700\u8981\u5237\u65B0");
|
|
2872
|
+
return;
|
|
2873
|
+
}
|
|
2874
|
+
refreshExpiredBtn.disabled = true;
|
|
2875
|
+
refreshExpiredBtn.textContent = "\u5237\u65B0\u4E2D...";
|
|
2876
|
+
for (const acc of expired) {
|
|
2877
|
+
try {
|
|
2878
|
+
const accountKey = acc.accountRecordId || acc.profileId;
|
|
2879
|
+
await ctx2.api.cmdSpawn({
|
|
2880
|
+
title: `\u91CD\u65B0\u767B\u5F55 ${acc.alias || acc.profileId}`,
|
|
2881
|
+
cwd: "",
|
|
2882
|
+
args: [
|
|
2883
|
+
ctx2.api.pathJoin("apps", "webauto", "entry", "account.mjs"),
|
|
2884
|
+
"login",
|
|
2885
|
+
accountKey,
|
|
2886
|
+
"--url",
|
|
2887
|
+
"https://www.xiaohongshu.com",
|
|
2888
|
+
"--json"
|
|
2889
|
+
],
|
|
2890
|
+
groupKey: "profilepool"
|
|
2891
|
+
});
|
|
2892
|
+
} catch (err) {
|
|
2893
|
+
console.error(`Failed to refresh ${acc.profileId}:`, err);
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
refreshExpiredBtn.disabled = false;
|
|
2897
|
+
refreshExpiredBtn.textContent = "\u5237\u65B0\u5931\u6548";
|
|
2898
|
+
}
|
|
2899
|
+
recheckEnvBtn.onclick = checkEnvironment;
|
|
2900
|
+
addAccountBtn.onclick = addAccount;
|
|
2901
|
+
checkAllBtn.onclick = checkAllAccounts;
|
|
2902
|
+
refreshExpiredBtn.onclick = refreshExpiredAccounts;
|
|
2903
|
+
void checkEnvironment();
|
|
2904
|
+
void loadAccounts();
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
// src/renderer/index.mts
|
|
2908
|
+
var tabs = [
|
|
2909
|
+
{ id: "setup-wizard", label: "\u521D\u59CB\u5316", render: renderSetupWizard },
|
|
2910
|
+
{ id: "config", label: "\u914D\u7F6E", render: renderConfigPanel },
|
|
2911
|
+
{ id: "dashboard", label: "\u770B\u677F", render: renderDashboard },
|
|
2912
|
+
{ id: "account-manager", label: "\u8D26\u6237\u7BA1\u7406", render: renderAccountManager },
|
|
2913
|
+
{ id: "preflight", label: "\u65E7\u9884\u5904\u7406", render: renderPreflight, hidden: true },
|
|
2914
|
+
{ id: "logs", label: "\u65E5\u5FD7", render: renderLogs },
|
|
2915
|
+
{ id: "settings", label: "\u8BBE\u7F6E", render: renderSettings }
|
|
2916
|
+
];
|
|
2917
|
+
var tabsEl = document.getElementById("tabs");
|
|
2918
|
+
var contentEl = document.getElementById("content");
|
|
2919
|
+
var statusEl = document.getElementById("status");
|
|
2920
|
+
var activeTabCleanup = null;
|
|
2921
|
+
var ctx = {
|
|
2922
|
+
api: window.api,
|
|
2923
|
+
settings: null,
|
|
2924
|
+
xhsCurrentRun: null,
|
|
2925
|
+
activeRunId: null,
|
|
2926
|
+
_activeRunIds: /* @__PURE__ */ new Set(),
|
|
2927
|
+
_activeRunsListeners: [],
|
|
2928
|
+
_logLines: [],
|
|
2929
|
+
appendLog(line) {
|
|
2930
|
+
const l = String(line || "").trim();
|
|
2931
|
+
if (!l) return;
|
|
2932
|
+
this._logLines.push(l);
|
|
2933
|
+
},
|
|
2934
|
+
clearLog() {
|
|
2935
|
+
this._logLines = [];
|
|
2936
|
+
},
|
|
2937
|
+
onActiveRunsChanged(listener) {
|
|
2938
|
+
this._activeRunsListeners.push(listener);
|
|
2939
|
+
return () => {
|
|
2940
|
+
this._activeRunsListeners = this._activeRunsListeners.filter((x) => x !== listener);
|
|
2941
|
+
};
|
|
2942
|
+
},
|
|
2943
|
+
notifyActiveRunsChanged() {
|
|
2944
|
+
this._activeRunsListeners.forEach((listener) => {
|
|
2945
|
+
try {
|
|
2946
|
+
listener();
|
|
2947
|
+
} catch {
|
|
2948
|
+
}
|
|
2949
|
+
});
|
|
2950
|
+
},
|
|
2951
|
+
setStatus(s) {
|
|
2952
|
+
statusEl.textContent = s;
|
|
2953
|
+
},
|
|
2954
|
+
async refreshSettings() {
|
|
2955
|
+
const latest = await window.api.settingsGet();
|
|
2956
|
+
this.settings = latest;
|
|
2957
|
+
if (this.api && typeof this.api === "object") {
|
|
2958
|
+
this.api.settings = latest;
|
|
2959
|
+
}
|
|
2960
|
+
return latest;
|
|
2961
|
+
},
|
|
2962
|
+
setActiveTab(id) {
|
|
2963
|
+
setActiveTab(id);
|
|
2964
|
+
}
|
|
2965
|
+
};
|
|
2966
|
+
function startDesktopHeartbeat() {
|
|
2967
|
+
const sendHeartbeat = async () => {
|
|
2968
|
+
try {
|
|
2969
|
+
if (typeof window.api?.desktopHeartbeat === "function") {
|
|
2970
|
+
await window.api.desktopHeartbeat();
|
|
2971
|
+
}
|
|
2972
|
+
} catch {
|
|
2973
|
+
}
|
|
2974
|
+
};
|
|
2975
|
+
void sendHeartbeat();
|
|
2976
|
+
const timer = setInterval(() => {
|
|
2977
|
+
void sendHeartbeat();
|
|
2978
|
+
}, 1e4);
|
|
2979
|
+
window.addEventListener("beforeunload", () => clearInterval(timer));
|
|
2980
|
+
}
|
|
2981
|
+
async function loadSettings() {
|
|
2982
|
+
await ctx.refreshSettings();
|
|
2983
|
+
}
|
|
2984
|
+
function setActiveTab(id) {
|
|
2985
|
+
if (activeTabCleanup) {
|
|
2986
|
+
try {
|
|
2987
|
+
activeTabCleanup();
|
|
2988
|
+
} catch {
|
|
2989
|
+
}
|
|
2990
|
+
activeTabCleanup = null;
|
|
2991
|
+
}
|
|
2992
|
+
tabsEl.textContent = "";
|
|
2993
|
+
for (const t of tabs.filter((x) => !x.hidden)) {
|
|
2994
|
+
const el = createEl("div", { className: `tab ${t.id === id ? "active" : ""}` }, [t.label]);
|
|
2995
|
+
el.addEventListener("click", () => setActiveTab(t.id));
|
|
2996
|
+
tabsEl.appendChild(el);
|
|
2997
|
+
}
|
|
2998
|
+
contentEl.textContent = "";
|
|
2999
|
+
const tab = tabs.find((x) => x.id === id);
|
|
3000
|
+
const dispose = tab.render(contentEl, ctx);
|
|
3001
|
+
if (typeof dispose === "function") activeTabCleanup = dispose;
|
|
3002
|
+
}
|
|
3003
|
+
function installCmdEvents() {
|
|
3004
|
+
window.api.onCmdEvent((evt) => {
|
|
3005
|
+
const runTag = evt?.runId ? `[rid:${evt.runId}] ` : "";
|
|
3006
|
+
const runId = String(evt?.runId || "").trim();
|
|
3007
|
+
if (evt?.type === "started") {
|
|
3008
|
+
ctx.activeRunId = evt.runId;
|
|
3009
|
+
if (runId) {
|
|
3010
|
+
ctx._activeRunIds.add(runId);
|
|
3011
|
+
ctx.notifyActiveRunsChanged();
|
|
3012
|
+
}
|
|
3013
|
+
ctx.appendLog(`${runTag}[started] ${evt.title} pid=${evt.pid} runId=${evt.runId}`);
|
|
3014
|
+
ctx.setStatus(`running: ${evt.title}`);
|
|
3015
|
+
return;
|
|
3016
|
+
}
|
|
3017
|
+
if (evt?.type === "stdout") {
|
|
3018
|
+
ctx.appendLog(`${runTag}${evt.line}`);
|
|
3019
|
+
return;
|
|
3020
|
+
}
|
|
3021
|
+
if (evt?.type === "stderr") {
|
|
3022
|
+
ctx.appendLog(`${runTag}[stderr] ${evt.line}`);
|
|
3023
|
+
return;
|
|
3024
|
+
}
|
|
3025
|
+
if (evt?.type === "exit") {
|
|
3026
|
+
if (runId) {
|
|
3027
|
+
ctx._activeRunIds.delete(runId);
|
|
3028
|
+
ctx.notifyActiveRunsChanged();
|
|
3029
|
+
}
|
|
3030
|
+
ctx.appendLog(`${runTag}[exit] code=${evt.exitCode ?? "null"} signal=${evt.signal ?? "null"}`);
|
|
3031
|
+
ctx.setStatus(ctx._activeRunIds.size > 0 ? `running: ${ctx._activeRunIds.size} tasks` : "idle");
|
|
3032
|
+
}
|
|
3033
|
+
});
|
|
3034
|
+
}
|
|
3035
|
+
async function detectStartupTab() {
|
|
3036
|
+
try {
|
|
3037
|
+
const env = typeof window.api?.envCheckAll === "function" ? await window.api.envCheckAll() : null;
|
|
3038
|
+
const rows = await listAccountProfiles(window.api).catch(() => []);
|
|
3039
|
+
const hasAccount = rows.some((row) => row.valid);
|
|
3040
|
+
const envReady = Boolean(env?.allReady);
|
|
3041
|
+
if (envReady && hasAccount) return "config";
|
|
3042
|
+
} catch {
|
|
3043
|
+
}
|
|
3044
|
+
return "setup-wizard";
|
|
3045
|
+
}
|
|
3046
|
+
async function main() {
|
|
3047
|
+
startDesktopHeartbeat();
|
|
3048
|
+
await loadSettings();
|
|
3049
|
+
installCmdEvents();
|
|
3050
|
+
const startupTab = await detectStartupTab();
|
|
3051
|
+
setActiveTab(startupTab);
|
|
3052
|
+
ctx.setStatus("idle");
|
|
3053
|
+
}
|
|
3054
|
+
window.addEventListener("beforeunload", () => {
|
|
3055
|
+
if (!activeTabCleanup) return;
|
|
3056
|
+
try {
|
|
3057
|
+
activeTabCleanup();
|
|
3058
|
+
} catch {
|
|
3059
|
+
}
|
|
3060
|
+
activeTabCleanup = null;
|
|
3061
|
+
});
|
|
3062
|
+
void main();
|
|
3063
|
+
//# sourceMappingURL=index.js.map
|