botmux 2.33.0 → 2.33.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +12 -1
- package/README.md +45 -1
- package/dist/adapters/cli/claude-code.d.ts.map +1 -1
- package/dist/adapters/cli/claude-code.js +11 -0
- package/dist/adapters/cli/claude-code.js.map +1 -1
- package/dist/cli/bots-list-output.d.ts +21 -0
- package/dist/cli/bots-list-output.d.ts.map +1 -0
- package/dist/cli/bots-list-output.js +23 -0
- package/dist/cli/bots-list-output.js.map +1 -0
- package/dist/cli/workflow.d.ts +13 -0
- package/dist/cli/workflow.d.ts.map +1 -0
- package/dist/cli/workflow.js +781 -0
- package/dist/cli/workflow.js.map +1 -0
- package/dist/cli.js +69 -14
- package/dist/cli.js.map +1 -1
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +211 -4
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/session-manager.d.ts +6 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +22 -12
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/worker-pool.d.ts +13 -0
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +100 -6
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +884 -3
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/auth.d.ts +36 -0
- package/dist/dashboard/auth.d.ts.map +1 -1
- package/dist/dashboard/auth.js +22 -0
- package/dist/dashboard/auth.js.map +1 -1
- package/dist/dashboard/web/app.js +20 -1
- package/dist/dashboard/web/app.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +356 -0
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/workflow-catalog.d.ts +2 -0
- package/dist/dashboard/web/workflow-catalog.d.ts.map +1 -0
- package/dist/dashboard/web/workflow-catalog.js +323 -0
- package/dist/dashboard/web/workflow-catalog.js.map +1 -0
- package/dist/dashboard/web/workflows.d.ts +2 -0
- package/dist/dashboard/web/workflows.d.ts.map +1 -0
- package/dist/dashboard/web/workflows.js +1618 -0
- package/dist/dashboard/web/workflows.js.map +1 -0
- package/dist/dashboard/workflow-api.d.ts +23 -0
- package/dist/dashboard/workflow-api.d.ts.map +1 -0
- package/dist/dashboard/workflow-api.js +463 -0
- package/dist/dashboard/workflow-api.js.map +1 -0
- package/dist/dashboard-web/app.js +494 -199
- package/dist/dashboard-web/index.html +1 -0
- package/dist/dashboard-web/style.css +160 -6
- package/dist/dashboard-web/terminal-replay.html +227 -0
- package/dist/dashboard.js +29 -12
- package/dist/dashboard.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +12 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +12 -0
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts +3 -0
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +27 -1
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/client.d.ts +19 -2
- package/dist/im/lark/client.d.ts.map +1 -1
- package/dist/im/lark/client.js +21 -2
- package/dist/im/lark/client.js.map +1 -1
- package/dist/im/lark/workflow-card-handler.d.ts +50 -0
- package/dist/im/lark/workflow-card-handler.d.ts.map +1 -0
- package/dist/im/lark/workflow-card-handler.js +152 -0
- package/dist/im/lark/workflow-card-handler.js.map +1 -0
- package/dist/im/lark/workflow-cards.d.ts +46 -0
- package/dist/im/lark/workflow-cards.d.ts.map +1 -0
- package/dist/im/lark/workflow-cards.js +226 -0
- package/dist/im/lark/workflow-cards.js.map +1 -0
- package/dist/im/lark/workflow-progress-card.d.ts +76 -0
- package/dist/im/lark/workflow-progress-card.d.ts.map +1 -0
- package/dist/im/lark/workflow-progress-card.js +279 -0
- package/dist/im/lark/workflow-progress-card.js.map +1 -0
- package/dist/im/lark/workflow-slash-command.d.ts +92 -0
- package/dist/im/lark/workflow-slash-command.d.ts.map +1 -0
- package/dist/im/lark/workflow-slash-command.js +185 -0
- package/dist/im/lark/workflow-slash-command.js.map +1 -0
- package/dist/services/group-creator.d.ts.map +1 -1
- package/dist/services/group-creator.js +17 -4
- package/dist/services/group-creator.js.map +1 -1
- package/dist/services/groups-store.d.ts +11 -0
- package/dist/services/groups-store.d.ts.map +1 -1
- package/dist/services/groups-store.js +26 -0
- package/dist/services/groups-store.js.map +1 -1
- package/dist/services/jsonl-cursor.d.ts +12 -0
- package/dist/services/jsonl-cursor.d.ts.map +1 -0
- package/dist/services/jsonl-cursor.js +45 -0
- package/dist/services/jsonl-cursor.js.map +1 -0
- package/dist/services/schedule-store.d.ts +35 -0
- package/dist/services/schedule-store.d.ts.map +1 -1
- package/dist/services/schedule-store.js +108 -1
- package/dist/services/schedule-store.js.map +1 -1
- package/dist/skills/definitions.d.ts.map +1 -1
- package/dist/skills/definitions.js +399 -0
- package/dist/skills/definitions.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/cli-usage-limit.d.ts.map +1 -1
- package/dist/utils/cli-usage-limit.js +4 -0
- package/dist/utils/cli-usage-limit.js.map +1 -1
- package/dist/worker.js +118 -14
- package/dist/worker.js.map +1 -1
- package/dist/workflows/attempt-resume.d.ts +114 -0
- package/dist/workflows/attempt-resume.d.ts.map +1 -0
- package/dist/workflows/attempt-resume.js +385 -0
- package/dist/workflows/attempt-resume.js.map +1 -0
- package/dist/workflows/attempt-terminal.d.ts +21 -0
- package/dist/workflows/attempt-terminal.d.ts.map +1 -0
- package/dist/workflows/attempt-terminal.js +7 -0
- package/dist/workflows/attempt-terminal.js.map +1 -0
- package/dist/workflows/blob.d.ts +27 -0
- package/dist/workflows/blob.d.ts.map +1 -0
- package/dist/workflows/blob.js +39 -0
- package/dist/workflows/blob.js.map +1 -0
- package/dist/workflows/cancel-run.d.ts +45 -0
- package/dist/workflows/cancel-run.d.ts.map +1 -0
- package/dist/workflows/cancel-run.js +99 -0
- package/dist/workflows/cancel-run.js.map +1 -0
- package/dist/workflows/cancel.d.ts +111 -0
- package/dist/workflows/cancel.d.ts.map +1 -0
- package/dist/workflows/cancel.js +120 -0
- package/dist/workflows/cancel.js.map +1 -0
- package/dist/workflows/catalog.d.ts +60 -0
- package/dist/workflows/catalog.d.ts.map +1 -0
- package/dist/workflows/catalog.js +119 -0
- package/dist/workflows/catalog.js.map +1 -0
- package/dist/workflows/cold-attach.d.ts +30 -0
- package/dist/workflows/cold-attach.d.ts.map +1 -0
- package/dist/workflows/cold-attach.js +40 -0
- package/dist/workflows/cold-attach.js.map +1 -0
- package/dist/workflows/cold-scan.d.ts +21 -0
- package/dist/workflows/cold-scan.d.ts.map +1 -0
- package/dist/workflows/cold-scan.js +70 -0
- package/dist/workflows/cold-scan.js.map +1 -0
- package/dist/workflows/daemon-spawn.d.ts +117 -0
- package/dist/workflows/daemon-spawn.d.ts.map +1 -0
- package/dist/workflows/daemon-spawn.js +551 -0
- package/dist/workflows/daemon-spawn.js.map +1 -0
- package/dist/workflows/definition.d.ts +1309 -0
- package/dist/workflows/definition.d.ts.map +1 -0
- package/dist/workflows/definition.js +334 -0
- package/dist/workflows/definition.js.map +1 -0
- package/dist/workflows/effect-input.d.ts +4 -0
- package/dist/workflows/effect-input.d.ts.map +1 -0
- package/dist/workflows/effect-input.js +18 -0
- package/dist/workflows/effect-input.js.map +1 -0
- package/dist/workflows/events/append.d.ts +77 -0
- package/dist/workflows/events/append.d.ts.map +1 -0
- package/dist/workflows/events/append.js +214 -0
- package/dist/workflows/events/append.js.map +1 -0
- package/dist/workflows/events/idempotency.d.ts +77 -0
- package/dist/workflows/events/idempotency.d.ts.map +1 -0
- package/dist/workflows/events/idempotency.js +116 -0
- package/dist/workflows/events/idempotency.js.map +1 -0
- package/dist/workflows/events/index.d.ts +7 -0
- package/dist/workflows/events/index.d.ts.map +1 -0
- package/dist/workflows/events/index.js +7 -0
- package/dist/workflows/events/index.js.map +1 -0
- package/dist/workflows/events/payloads.d.ts +917 -0
- package/dist/workflows/events/payloads.d.ts.map +1 -0
- package/dist/workflows/events/payloads.js +337 -0
- package/dist/workflows/events/payloads.js.map +1 -0
- package/dist/workflows/events/replay.d.ts +238 -0
- package/dist/workflows/events/replay.d.ts.map +1 -0
- package/dist/workflows/events/replay.js +608 -0
- package/dist/workflows/events/replay.js.map +1 -0
- package/dist/workflows/events/schema.d.ts +5242 -0
- package/dist/workflows/events/schema.d.ts.map +1 -0
- package/dist/workflows/events/schema.js +295 -0
- package/dist/workflows/events/schema.js.map +1 -0
- package/dist/workflows/events/types.d.ts +34 -0
- package/dist/workflows/events/types.d.ts.map +1 -0
- package/dist/workflows/events/types.js +2 -0
- package/dist/workflows/events/types.js.map +1 -0
- package/dist/workflows/fanout.d.ts +36 -0
- package/dist/workflows/fanout.d.ts.map +1 -0
- package/dist/workflows/fanout.js +114 -0
- package/dist/workflows/fanout.js.map +1 -0
- package/dist/workflows/hostExecutors/botmux-schedule.d.ts +41 -0
- package/dist/workflows/hostExecutors/botmux-schedule.d.ts.map +1 -0
- package/dist/workflows/hostExecutors/botmux-schedule.js +121 -0
- package/dist/workflows/hostExecutors/botmux-schedule.js.map +1 -0
- package/dist/workflows/hostExecutors/feishu-im.d.ts +12 -0
- package/dist/workflows/hostExecutors/feishu-im.d.ts.map +1 -0
- package/dist/workflows/hostExecutors/feishu-im.js +49 -0
- package/dist/workflows/hostExecutors/feishu-im.js.map +1 -0
- package/dist/workflows/hostExecutors/feishu-reply.d.ts +24 -0
- package/dist/workflows/hostExecutors/feishu-reply.d.ts.map +1 -0
- package/dist/workflows/hostExecutors/feishu-reply.js +88 -0
- package/dist/workflows/hostExecutors/feishu-reply.js.map +1 -0
- package/dist/workflows/hostExecutors/feishu-send.d.ts +23 -0
- package/dist/workflows/hostExecutors/feishu-send.d.ts.map +1 -0
- package/dist/workflows/hostExecutors/feishu-send.js +124 -0
- package/dist/workflows/hostExecutors/feishu-send.js.map +1 -0
- package/dist/workflows/hostExecutors/index.d.ts +8 -0
- package/dist/workflows/hostExecutors/index.d.ts.map +1 -0
- package/dist/workflows/hostExecutors/index.js +8 -0
- package/dist/workflows/hostExecutors/index.js.map +1 -0
- package/dist/workflows/hostExecutors/protocol.d.ts +42 -0
- package/dist/workflows/hostExecutors/protocol.d.ts.map +1 -0
- package/dist/workflows/hostExecutors/protocol.js +181 -0
- package/dist/workflows/hostExecutors/protocol.js.map +1 -0
- package/dist/workflows/hostExecutors/registry.d.ts +10 -0
- package/dist/workflows/hostExecutors/registry.d.ts.map +1 -0
- package/dist/workflows/hostExecutors/registry.js +36 -0
- package/dist/workflows/hostExecutors/registry.js.map +1 -0
- package/dist/workflows/hostExecutors/types.d.ts +78 -0
- package/dist/workflows/hostExecutors/types.d.ts.map +1 -0
- package/dist/workflows/hostExecutors/types.js +2 -0
- package/dist/workflows/hostExecutors/types.js.map +1 -0
- package/dist/workflows/loader.d.ts +16 -0
- package/dist/workflows/loader.d.ts.map +1 -0
- package/dist/workflows/loader.js +56 -0
- package/dist/workflows/loader.js.map +1 -0
- package/dist/workflows/loop.d.ts +50 -0
- package/dist/workflows/loop.d.ts.map +1 -0
- package/dist/workflows/loop.js +350 -0
- package/dist/workflows/loop.js.map +1 -0
- package/dist/workflows/ops-projection.d.ts +168 -0
- package/dist/workflows/ops-projection.d.ts.map +1 -0
- package/dist/workflows/ops-projection.js +707 -0
- package/dist/workflows/ops-projection.js.map +1 -0
- package/dist/workflows/orchestrator.d.ts +107 -0
- package/dist/workflows/orchestrator.d.ts.map +1 -0
- package/dist/workflows/orchestrator.js +197 -0
- package/dist/workflows/orchestrator.js.map +1 -0
- package/dist/workflows/output-binding.d.ts +70 -0
- package/dist/workflows/output-binding.d.ts.map +1 -0
- package/dist/workflows/output-binding.js +265 -0
- package/dist/workflows/output-binding.js.map +1 -0
- package/dist/workflows/params.d.ts +61 -0
- package/dist/workflows/params.d.ts.map +1 -0
- package/dist/workflows/params.js +195 -0
- package/dist/workflows/params.js.map +1 -0
- package/dist/workflows/resume.d.ts +263 -0
- package/dist/workflows/resume.d.ts.map +1 -0
- package/dist/workflows/resume.js +808 -0
- package/dist/workflows/resume.js.map +1 -0
- package/dist/workflows/run-id.d.ts +2 -0
- package/dist/workflows/run-id.d.ts.map +1 -0
- package/dist/workflows/run-id.js +7 -0
- package/dist/workflows/run-id.js.map +1 -0
- package/dist/workflows/run-init.d.ts +48 -0
- package/dist/workflows/run-init.d.ts.map +1 -0
- package/dist/workflows/run-init.js +99 -0
- package/dist/workflows/run-init.js.map +1 -0
- package/dist/workflows/runs-dir.d.ts +4 -0
- package/dist/workflows/runs-dir.d.ts.map +1 -0
- package/dist/workflows/runs-dir.js +15 -0
- package/dist/workflows/runs-dir.js.map +1 -0
- package/dist/workflows/runtime.d.ts +211 -0
- package/dist/workflows/runtime.d.ts.map +1 -0
- package/dist/workflows/runtime.js +594 -0
- package/dist/workflows/runtime.js.map +1 -0
- package/dist/workflows/spawn-bot.d.ts +165 -0
- package/dist/workflows/spawn-bot.d.ts.map +1 -0
- package/dist/workflows/spawn-bot.js +215 -0
- package/dist/workflows/spawn-bot.js.map +1 -0
- package/dist/workflows/system.d.ts +49 -0
- package/dist/workflows/system.d.ts.map +1 -0
- package/dist/workflows/system.js +48 -0
- package/dist/workflows/system.js.map +1 -0
- package/dist/workflows/trigger-run.d.ts +70 -0
- package/dist/workflows/trigger-run.d.ts.map +1 -0
- package/dist/workflows/trigger-run.js +88 -0
- package/dist/workflows/trigger-run.js.map +1 -0
- package/dist/workflows/wait.d.ts +120 -0
- package/dist/workflows/wait.d.ts.map +1 -0
- package/dist/workflows/wait.js +181 -0
- package/dist/workflows/wait.js.map +1 -0
- package/package.json +3 -3
|
@@ -1,21 +1,31 @@
|
|
|
1
|
-
"use strict";(()=>{var z=class{sessions=new Map;schedules=new Map;online=!0;listeners=new Set;upsertSessions(s){for(let r of s)this.sessions.set(r.sessionId,r);this.emit()}upsertSchedules(s){for(let r of s)this.schedules.set(r.id,r);this.emit()}applySse(s,r){if(s==="session.spawned")this.sessions.set(r.session.sessionId,r.session);else if(s==="session.update"){let m=this.sessions.get(r.sessionId);m&&this.sessions.set(r.sessionId,{...m,...r.patch})}else if(s==="session.exited"){let m=this.sessions.get(r.sessionId);m&&this.sessions.set(r.sessionId,{...m,status:"closed"})}else if(s==="schedule.created")this.schedules.set(r.schedule.id,r.schedule);else if(s==="schedule.updated"){let m=this.schedules.get(r.id);m&&this.schedules.set(r.id,{...m,...r.patch})}else if(s==="schedule.deleted")this.schedules.delete(r.id);else return;this.emit()}setOnline(s){this.online!==s&&(this.online=s,this.emit())}on(s){return this.listeners.add(s),()=>this.listeners.delete(s)}emit(){for(let s of this.listeners)s()}},C=new z;async function Z(){let[t,s]=await Promise.all([fetch("/api/sessions").then(v=>v.json()),fetch("/api/schedules").then(v=>v.json())]);C.upsertSessions(t.sessions??[]),C.upsertSchedules(s.schedules??[]);let r=new EventSource("/events"),m=["session.spawned","session.update","session.exited","schedule.created","schedule.updated","schedule.deleted","schedule.fired","heartbeat"];for(let v of m)r.addEventListener(v,c=>{try{let T=JSON.parse(c.data);C.applySse(v,T.body??T)}catch{}});r.onerror=()=>C.setOnline(!1),r.onopen=()=>C.setOnline(!0)}var J="botmux.dashboard.locale",we={"app.name":"botmux","app.subtitle":"\u98DE\u4E66 AI CLI \u63A7\u5236\u53F0","nav.overview":"\u603B\u89C8","nav.sessions":"\u4F1A\u8BDD","nav.groups":"\u7FA4\u7EC4","nav.schedules":"\u5B9A\u65F6","nav.botDefaults":"\u9ED8\u8BA4 Bot","status.live":"\u5B9E\u65F6\u8FDE\u63A5","status.disconnected":"\u8FDE\u63A5\u65AD\u5F00","status.system":"\u7CFB\u7EDF","status.light":"\u6D45\u8272","status.dark":"\u6697\u9ED1","status.language":"\u8BED\u8A00","status.theme":"\u4E3B\u9898","botOnboarding.add":"\u6DFB\u52A0\u673A\u5668\u4EBA","botOnboarding.title":"\u626B\u7801\u6DFB\u52A0\u673A\u5668\u4EBA","botOnboarding.intro":"\u7528\u98DE\u4E66 App \u626B\u7801\u521B\u5EFA PersonalAgent \u5E94\u7528\uFF0C\u6210\u529F\u540E\u4F1A\u5199\u5165\u672C\u673A bots.json\u3002","botOnboarding.starting":"\u6B63\u5728\u751F\u6210\u4E8C\u7EF4\u7801...","botOnboarding.waiting":"\u8BF7\u7528\u98DE\u4E66 App \u626B\u7801\u786E\u8BA4\u3002","botOnboarding.verifying":"\u626B\u7801\u6210\u529F\uFF0C\u6B63\u5728\u6821\u9A8C\u51ED\u8BC1...","botOnboarding.completed":"\u673A\u5668\u4EBA\u5DF2\u6DFB\u52A0\u3002","botOnboarding.failed":"\u6DFB\u52A0\u5931\u8D25","botOnboarding.openLink":"\u6253\u4E0D\u5F00\u626B\u7801\uFF1F\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00","botOnboarding.close":"\u5173\u95ED","botOnboarding.restartHint":"\u5DF2\u5199\u5165 bots.json\u3002\u6267\u884C pnpm daemon:restart \u540E\u65B0\u673A\u5668\u4EBA\u751F\u6548\u3002","botOnboarding.qrAlt":"\u98DE\u4E66\u626B\u7801\u6DFB\u52A0\u673A\u5668\u4EBA\u4E8C\u7EF4\u7801","overview.title":"\u63A7\u5236\u53F0\u603B\u89C8","overview.subtitle":"\u8DE8 bot\u3001\u7FA4\u804A\u3001\u4F1A\u8BDD\u548C\u5B9A\u65F6\u4EFB\u52A1\u7684\u5B9E\u65F6\u7BA1\u63A7\u9762\u3002","overview.openSessions":"\u6D3B\u8DC3\u4F1A\u8BDD","overview.workingSessions":"\u5DE5\u4F5C\u4E2D","overview.onlineBots":"\u5728\u7EBF Bot","overview.schedules":"\u5B9A\u65F6\u4EFB\u52A1","overview.groups":"\u7FA4\u804A\u8986\u76D6","overview.enabledSchedules":"\u5DF2\u542F\u7528","overview.total":"\u603B\u8BA1","overview.active":"\u6D3B\u8DC3","overview.daemonRegistry":"daemon \u6CE8\u518C\u8868","overview.chatMatrix":"\u7FA4\u804A\u77E9\u9635","overview.recentSessions":"\u6700\u8FD1\u4F1A\u8BDD","overview.nextSchedules":"\u5373\u5C06\u6267\u884C","overview.noSessions":"\u6682\u65E0\u4F1A\u8BDD\u3002","overview.noSchedules":"\u6682\u65E0\u5B9A\u65F6\u4EFB\u52A1\u3002","sessions.title":"\u4F1A\u8BDD\u63A7\u5236","sessions.subtitle":"\u5B9A\u4F4D\u98DE\u4E66\u8BDD\u9898\u3001\u6253\u5F00 Web Terminal\u3001\u5173\u95ED\u6216\u6062\u590D CLI \u4F1A\u8BDD\u3002","sessions.search":"\u641C\u7D22\u5DE5\u4F5C\u76EE\u5F55 / \u6807\u9898 / ID","sessions.anyStatus":"\u5168\u90E8\u72B6\u6001","sessions.adoptAny":"adopt: \u5168\u90E8","sessions.adoptYes":"adopt: \u662F","sessions.adoptNo":"adopt: \u5426","sessions.activeOnly":"\u4EC5\u6D3B\u8DC3","sessions.closeSelected":"\u5173\u95ED\u9009\u4E2D","sessions.clearSelection":"\u53D6\u6D88\u9009\u62E9","sessions.selectedCount":"\u5DF2\u9009 {count} \u4E2A\u4F1A\u8BDD","sessions.bot":"bot","sessions.cli":"CLI","sessions.status":"\u72B6\u6001","sessions.titleCol":"\u6807\u9898","sessions.workingDir":"\u5DE5\u4F5C\u76EE\u5F55","sessions.created":"\u521B\u5EFA","sessions.last":"\u6700\u8FD1","sessions.adopt":"\u63A5\u5165","sessions.actions":"\u64CD\u4F5C","sessions.details":"\u8BE6\u60C5","sessions.copy":"\u590D\u5236","sessions.copied":"\u5DF2\u590D\u5236","sessions.locate":"\u5B9A\u4F4D\u8BDD\u9898","sessions.locating":"\u53D1\u9001\u4E2D...","sessions.cooldown":"\u51B7\u5374 {seconds}s","sessions.openTerminal":"\u7EC8\u7AEF","sessions.close":"\u5173\u95ED\u4F1A\u8BDD","sessions.resume":"\u6062\u590D\u4F1A\u8BDD","sessions.dismiss":"\u5173\u95ED","sessions.closeConfirm":"\u5173\u95ED\u8FD9\u4E2A\u4F1A\u8BDD\uFF1F","sessions.resumeFailed":"\u6062\u590D\u5931\u8D25","sessions.closeBulkConfirm":"\u5173\u95ED\u9009\u4E2D\u7684 {count} \u4E2A\u4F1A\u8BDD\uFF1F","sessions.empty":"\u6CA1\u6709\u7B26\u5408\u6761\u4EF6\u7684\u4F1A\u8BDD\u3002","groups.title":"\u7FA4\u7EC4\u4E0E Bot","groups.subtitle":"\u67E5\u770B chat x bot \u8986\u76D6\u77E9\u9635\uFF0C\u7BA1\u7406\u62C9\u7FA4\u3001oncall\u3001\u9000\u7FA4\u548C\u89E3\u6563\u3002","groups.search":"\u641C\u7D22\u7FA4\u540D / ID / owner","groups.missingOnly":"\u4EC5\u7F3A bot","groups.refresh":"\u5237\u65B0","groups.create":"\u65B0\u5EFA\u7FA4","groups.chat":"\u7FA4\u804A","groups.actions":"\u64CD\u4F5C","groups.addBots":"\u6DFB\u52A0 bot","groups.manage":"\u7BA1\u7406","groups.empty":"\u6CA1\u6709\u7B26\u5408\u6761\u4EF6\u7684\u7FA4\u804A\u3002","groups.createTitle":"\u65B0\u5EFA\u7FA4\u804A","groups.createHelp":"\u9009\u62E9\u8981\u9080\u8BF7\u7684 bot\u3002dashboard \u4F1A\u81EA\u52A8\u9009\u62E9\u5728\u7EBF daemon \u4F5C\u4E3A\u521B\u5EFA\u8005\u3002","groups.name":"\u7FA4\u540D","groups.namePlaceholder":"\u4F8B\u5982 AI ChangeLog","groups.bindDir":"\u7ED1\u5B9A\u76EE\u5F55","groups.bindDirHelp":"\u65B0\u8BDD\u9898\u76F4\u63A5\u7528\u8BE5\u76EE\u5F55\u542F\u52A8 CLI\uFF0C\u8DF3\u8FC7 repo \u9009\u62E9\u3002","groups.botPicker":"Bot","groups.createSubmit":"\u521B\u5EFA","groups.cancel":"\u53D6\u6D88","groups.successTitle":"\u7FA4\u521B\u5EFA\u6210\u529F","groups.openGroup":"\u6253\u5F00\u65B0\u7FA4","groups.manageTitle":"\u7BA1\u7406 {name}","groups.owner":"\u7FA4\u4E3B","groups.oncall":"Oncall \u6A21\u5F0F","groups.oncallHelp":"\u5F00\u542F\u540E\uFF0C\u7FA4\u5185\u6210\u5458\u53EF @ \u673A\u5668\u4EBA\uFF1B\u65B0\u8BDD\u9898\u76F4\u63A5\u4F7F\u7528\u7ED1\u5B9A\u76EE\u5F55\u3002","groups.leaveTitle":"\u9009\u62E9\u673A\u5668\u4EBA\u9000\u51FA\u7FA4\u804A","groups.leaveSelected":"\u9009\u4E2D\u673A\u5668\u4EBA\u9000\u51FA\u7FA4\u804A","groups.disband":"\u89E3\u6563\u7FA4\u804A","groups.dangerHint":"\u89E3\u6563\u4EC5\u5F53\u5728\u7FA4\u673A\u5668\u4EBA\u662F\u7FA4\u4E3B\u65F6\u624D\u4F1A\u6210\u529F\uFF0C\u5426\u5219\u5EFA\u8BAE\u4F7F\u7528\u9000\u51FA\u7FA4\u804A\u3002","groups.save":"\u4FDD\u5B58","groups.needWorkingDir":"\u8BF7\u586B\u5DE5\u4F5C\u76EE\u5F55","groups.noBotsOnline":"\u6CA1\u6709\u5728\u7EBF bot\u3002\u8BF7\u5148\u91CD\u542F daemon\u3002","schedules.title":"\u5B9A\u65F6\u4EFB\u52A1","schedules.subtitle":"\u8DE8 daemon \u67E5\u770B\u3001\u6682\u505C\u3001\u6062\u590D\u548C\u7ACB\u5373\u89E6\u53D1\u5B9A\u65F6\u4EFB\u52A1\u3002","schedules.search":"\u641C\u7D22\u540D\u79F0 / prompt / \u5DE5\u4F5C\u76EE\u5F55","schedules.anyKind":"\u5168\u90E8\u7C7B\u578B","schedules.enabledOnly":"\u4EC5\u542F\u7528","schedules.name":"\u540D\u79F0","schedules.bot":"bot","schedules.schedule":"\u89C4\u5219","schedules.next":"\u4E0B\u6B21","schedules.last":"\u4E0A\u6B21","schedules.repeat":"\u91CD\u590D","schedules.enabled":"\u542F\u7528","schedules.actions":"\u64CD\u4F5C","schedules.runNow":"\u7ACB\u5373\u8FD0\u884C","schedules.pause":"\u6682\u505C","schedules.resume":"\u6062\u590D","schedules.empty":"\u6682\u65E0\u5B9A\u65F6\u4EFB\u52A1\u3002","botDefaults.title":"Bot \u9ED8\u8BA4 Oncall","botDefaults.subtitle":"\u914D\u7F6E\u6BCF\u4E2A bot \u5728\u65B0\u7FA4\u91CC\u7684\u9ED8\u8BA4 oncall \u884C\u4E3A\u3002","botDefaults.search":"\u641C\u7D22 bot \u540D / app id","botDefaults.refresh":"\u5237\u65B0","botDefaults.warning":"\u5F00\u542F\u540E\uFF0C\u6CA1\u6709 oncall binding \u7684\u7FA4\u4F1A\u5728\u4E0B\u6B21\u5F00\u65B0\u8BDD\u9898\u65F6\u81EA\u52A8\u7ED1\u5B9A\u5230\u8BE5\u76EE\u5F55\uFF1B\u624B\u52A8\u7ED1\u5B9A\u6216\u624B\u52A8\u89E3\u7ED1\u8FC7\u7684\u7FA4\u4E0D\u4F1A\u88AB\u8986\u76D6\u3002","botDefaults.empty":"\u6CA1\u6709\u5728\u7EBF bot\u3002\u5148 botmux restart \u8BA9 daemon \u4E0A\u7EBF\u3002","botDefaults.defaultOncall":"\u9ED8\u8BA4\u8FDB\u5165 oncall \u6A21\u5F0F","botDefaults.defaultOncallHelp":"\u6240\u6709\u672A\u7ED1\u5B9A\u7684\u7FA4\u4E0B\u6B21\u5F00\u8BDD\u9898\u81EA\u52A8\u7ED1\u5B9A","botDefaults.workingDir":"\u9ED8\u8BA4\u5DE5\u4F5C\u76EE\u5F55","botDefaults.lastEnabled":"\u4E0A\u6B21\u542F\u7528\u65F6\u95F4","botDefaults.autobound":"\u5DF2\u81EA\u52A8\u7ED1\u5B9A {count} \u4E2A\u7FA4","botDefaults.save":"\u4FDD\u5B58","botDefaults.required":"\u5F00\u542F\u65F6\u5FC5\u987B\u586B\u5DE5\u4F5C\u76EE\u5F55","common.none":"\u65E0","common.unknown":"\u672A\u77E5","common.now":"\u521A\u521A","common.never":"\u4ECE\u672A"},$e={"app.name":"botmux","app.subtitle":"Feishu AI CLI Control","nav.overview":"Overview","nav.sessions":"Sessions","nav.groups":"Groups","nav.schedules":"Schedules","nav.botDefaults":"Bot Defaults","status.live":"Live","status.disconnected":"Disconnected","status.system":"System","status.light":"Light","status.dark":"Dark","status.language":"Language","status.theme":"Theme","botOnboarding.add":"Add Bot","botOnboarding.title":"Scan to Add Bot","botOnboarding.intro":"Scan with the Feishu app to create a PersonalAgent app. The dashboard writes it to local bots.json after success.","botOnboarding.starting":"Generating QR code...","botOnboarding.waiting":"Scan with the Feishu app to continue.","botOnboarding.verifying":"Scan accepted. Verifying credentials...","botOnboarding.completed":"Bot added.","botOnboarding.failed":"Add failed","botOnboarding.openLink":"Open scan link in browser","botOnboarding.close":"Close","botOnboarding.restartHint":"bots.json has been updated. Run pnpm daemon:restart for the new bot to take effect.","botOnboarding.qrAlt":"Feishu bot onboarding QR code","overview.title":"Control Overview","overview.subtitle":"A realtime control plane across bots, chats, CLI sessions, and schedules.","overview.openSessions":"Active Sessions","overview.workingSessions":"Working","overview.onlineBots":"Online Bots","overview.schedules":"Schedules","overview.groups":"Groups Seen","overview.enabledSchedules":"Enabled","overview.total":"total","overview.active":"active","overview.daemonRegistry":"daemon registry","overview.chatMatrix":"chat matrix","overview.recentSessions":"Recent Sessions","overview.nextSchedules":"Next Runs","overview.noSessions":"No sessions yet.","overview.noSchedules":"No schedules yet.","sessions.title":"Session Control","sessions.subtitle":"Locate Feishu topics, open Web Terminal, close or resume CLI sessions.","sessions.search":"Search working dir / title / IDs","sessions.anyStatus":"Any status","sessions.adoptAny":"adopt: any","sessions.adoptYes":"adopt: yes","sessions.adoptNo":"adopt: no","sessions.activeOnly":"Active only","sessions.closeSelected":"Close selected","sessions.clearSelection":"Clear","sessions.selectedCount":"{count} sessions selected","sessions.bot":"Bot","sessions.cli":"CLI","sessions.status":"Status","sessions.titleCol":"Title","sessions.workingDir":"Working Dir","sessions.created":"Created","sessions.last":"Last","sessions.adopt":"Adopt","sessions.actions":"Actions","sessions.details":"Details","sessions.copy":"Copy","sessions.copied":"Copied","sessions.locate":"Locate Topic","sessions.locating":"Sending...","sessions.cooldown":"Cooldown {seconds}s","sessions.openTerminal":"Terminal","sessions.close":"Close Session","sessions.resume":"Resume Session","sessions.dismiss":"Close","sessions.closeConfirm":"Close this session?","sessions.resumeFailed":"Resume failed","sessions.closeBulkConfirm":"Close {count} selected sessions?","sessions.empty":"No sessions match the filters.","groups.title":"Groups & Bots","groups.subtitle":"Inspect the chat x bot matrix and manage group creation, oncall, leave, and disband flows.","groups.search":"Search chat name / ID / owner","groups.missingOnly":"Missing bot only","groups.refresh":"Refresh","groups.create":"New Group","groups.chat":"Chat","groups.actions":"Actions","groups.addBots":"Add Bots","groups.manage":"Manage","groups.empty":"No chats match the filters.","groups.createTitle":"Create New Group","groups.createHelp":"Pick bots to invite. The dashboard chooses an online daemon as creator.","groups.name":"Group Name","groups.namePlaceholder":"e.g. AI ChangeLog","groups.bindDir":"Bind Directory","groups.bindDirHelp":"New topics start the CLI here and skip repo selection.","groups.botPicker":"Bots","groups.createSubmit":"Create","groups.cancel":"Cancel","groups.successTitle":"Group Created","groups.openGroup":"Open Group","groups.manageTitle":"Manage {name}","groups.owner":"Owner","groups.oncall":"Oncall Mode","groups.oncallHelp":"When enabled, group members can @ the bot; new topics use the bound directory.","groups.leaveTitle":"Select Bots to Leave","groups.leaveSelected":"Selected Bots Leave","groups.disband":"Disband Group","groups.dangerHint":"Disband only works when an in-chat bot is the owner. Otherwise prefer leaving the chat.","groups.save":"Save","groups.needWorkingDir":"Working directory is required","groups.noBotsOnline":"No bots online. Restart the daemon first.","schedules.title":"Schedules","schedules.subtitle":"View, pause, resume, and run scheduled tasks across daemons.","schedules.search":"Search name / prompt / working dir","schedules.anyKind":"Any kind","schedules.enabledOnly":"Enabled only","schedules.name":"Name","schedules.bot":"Bot","schedules.schedule":"Schedule","schedules.next":"Next","schedules.last":"Last","schedules.repeat":"Repeat","schedules.enabled":"Enabled","schedules.actions":"Actions","schedules.runNow":"Run Now","schedules.pause":"Pause","schedules.resume":"Resume","schedules.empty":"No schedules.","botDefaults.title":"Bot Default Oncall","botDefaults.subtitle":"Configure each bot's default oncall behavior in new chats.","botDefaults.search":"Search bot name / app id","botDefaults.refresh":"Refresh","botDefaults.warning":"When enabled, chats without an oncall binding auto-bind to this directory on their next new topic. Manually bound or unbound chats are preserved.","botDefaults.empty":"No bots online. Run botmux restart first.","botDefaults.defaultOncall":"Default to oncall mode","botDefaults.defaultOncallHelp":"Unbound chats auto-bind on the next new topic","botDefaults.workingDir":"Default Working Directory","botDefaults.lastEnabled":"Last Enabled","botDefaults.autobound":"{count} chats auto-bound","botDefaults.save":"Save","botDefaults.required":"Working directory is required when enabled","common.none":"None","common.unknown":"Unknown","common.now":"now","common.never":"never"},ee={zh:we,en:$e};function te(t){if(typeof t!="string")return null;let s=t.trim().toLowerCase();return s==="zh"||s.startsWith("zh-")?"zh":s==="en"||s.startsWith("en-")?"en":null}function ke(t=[]){for(let s of t){let r=te(s);if(r)return r}return"zh"}function U(t){return(s,r)=>{let m=ee[t][s]??ee.zh[s]??s;return r?m.replace(/\{(\w+)\}/g,(v,c)=>{let T=r[c];return T==null?`{${c}}`:String(T)}):m}}function se(t,s){return(t?te(t.getItem(J)):null)??ke(s)}var K="botmux.dashboard.theme";function Le(t){return t==="system"||t==="light"||t==="dark"?t:null}function ne(t,s){return t==="system"?s?"dark":"light":t}function oe(t){return Le(t?.getItem(K))??"system"}var Q=class{locale="zh";themeMode="system";resolvedTheme="light";listeners=new Set;translate=U(this.locale);mediaQuery=null;init(){let s=typeof window<"u"?window:void 0;this.locale=se(s?.localStorage,Se()),this.translate=U(this.locale),this.themeMode=oe(s?.localStorage),this.mediaQuery=s?.matchMedia?.("(prefers-color-scheme: dark)")??null,this.mediaQuery?.addEventListener("change",()=>{this.applyTheme(),this.emit()}),this.applyTheme(),this.applyLocale()}t(s,r){return this.translate(s,r)}setLocale(s){this.locale!==s&&(this.locale=s,this.translate=U(s),window.localStorage.setItem(J,s),this.applyLocale(),this.emit())}setThemeMode(s){this.themeMode!==s&&(this.themeMode=s,window.localStorage.setItem(K,s),this.applyTheme(),this.emit())}on(s){return this.listeners.add(s),()=>this.listeners.delete(s)}emit(){for(let s of this.listeners)s()}applyTheme(){this.resolvedTheme=ne(this.themeMode,!!this.mediaQuery?.matches),document.documentElement.dataset.theme=this.resolvedTheme,document.documentElement.dataset.themeMode=this.themeMode}applyLocale(){document.documentElement.lang=this.locale==="zh"?"zh-CN":"en"}};function Se(){return typeof navigator>"u"?[]:navigator.languages?.length?navigator.languages:[navigator.language].filter(Boolean)}var O=new Q;function e(t,s){return O.t(t,s)}function o(t){return t.replace(/[&<>"']/g,s=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[s])}function j(t){if(!t)return"-";let s=Date.now()-t;return s<6e4?e("common.now"):s<36e5?Math.floor(s/6e4)+"m":s<864e5?Math.floor(s/36e5)+"h":Math.floor(s/864e5)+"d"}var Y={chats:[],bots:[]};async function Te(){try{let t=await fetch("/api/groups");if(!t.ok)return;Y=await t.json()}catch{}}function Ie(t){return`status status-${o(t||"unknown")}`}function Me(t){return`<li class="overview-list-row">
|
|
1
|
+
"use strict";(()=>{var ye=class{sessions=new Map;schedules=new Map;online=!0;listeners=new Set;upsertSessions(n){for(let o of n)this.sessions.set(o.sessionId,o);this.emit()}upsertSchedules(n){for(let o of n)this.schedules.set(o.id,o);this.emit()}applySse(n,o){if(n==="session.spawned")this.sessions.set(o.session.sessionId,o.session);else if(n==="session.update"){let r=this.sessions.get(o.sessionId);r&&this.sessions.set(o.sessionId,{...r,...o.patch})}else if(n==="session.exited"){let r=this.sessions.get(o.sessionId);r&&this.sessions.set(o.sessionId,{...r,status:"closed"})}else if(n==="schedule.created")this.schedules.set(o.schedule.id,o.schedule);else if(n==="schedule.updated"){let r=this.schedules.get(o.id);r&&this.schedules.set(o.id,{...r,...o.patch})}else if(n==="schedule.deleted")this.schedules.delete(o.id);else return;this.emit()}setOnline(n){this.online!==n&&(this.online=n,this.emit())}on(n){return this.listeners.add(n),()=>this.listeners.delete(n)}emit(){for(let n of this.listeners)n()}},D=new ye;async function De(){let[e,n]=await Promise.all([fetch("/api/sessions").then(s=>s.json()),fetch("/api/schedules").then(s=>s.json())]);D.upsertSessions(e.sessions??[]),D.upsertSchedules(n.schedules??[]);let o=new EventSource("/events"),r=["session.spawned","session.update","session.exited","schedule.created","schedule.updated","schedule.deleted","schedule.fired","heartbeat"];for(let s of r)o.addEventListener(s,a=>{try{let c=JSON.parse(a.data);D.applySse(s,c.body??c)}catch{}});o.onerror=()=>D.setOnline(!1),o.onopen=()=>D.setOnline(!0)}var $e="botmux.dashboard.locale",St={"app.name":"botmux","app.subtitle":"\u98DE\u4E66 AI CLI \u63A7\u5236\u53F0","time.secondsAgo":"{value} \u79D2\u524D","time.minutesAgo":"{value} \u5206\u949F\u524D","time.hoursAgo":"{value} \u5C0F\u65F6\u524D","nav.overview":"\u603B\u89C8","nav.sessions":"\u4F1A\u8BDD","nav.groups":"\u7FA4\u7EC4","nav.schedules":"\u5B9A\u65F6","nav.botDefaults":"\u9ED8\u8BA4 Bot","status.live":"\u5B9E\u65F6\u8FDE\u63A5","status.disconnected":"\u8FDE\u63A5\u65AD\u5F00","status.system":"\u7CFB\u7EDF","status.light":"\u6D45\u8272","status.dark":"\u6697\u9ED1","status.language":"\u8BED\u8A00","status.theme":"\u4E3B\u9898","botOnboarding.add":"\u6DFB\u52A0\u673A\u5668\u4EBA","botOnboarding.title":"\u626B\u7801\u6DFB\u52A0\u673A\u5668\u4EBA","botOnboarding.intro":"\u7528\u98DE\u4E66 App \u626B\u7801\u521B\u5EFA PersonalAgent \u5E94\u7528\uFF0C\u6210\u529F\u540E\u4F1A\u5199\u5165\u672C\u673A bots.json\u3002","botOnboarding.starting":"\u6B63\u5728\u751F\u6210\u4E8C\u7EF4\u7801...","botOnboarding.waiting":"\u8BF7\u7528\u98DE\u4E66 App \u626B\u7801\u786E\u8BA4\u3002","botOnboarding.verifying":"\u626B\u7801\u6210\u529F\uFF0C\u6B63\u5728\u6821\u9A8C\u51ED\u8BC1...","botOnboarding.completed":"\u673A\u5668\u4EBA\u5DF2\u6DFB\u52A0\u3002","botOnboarding.failed":"\u6DFB\u52A0\u5931\u8D25","botOnboarding.openLink":"\u6253\u4E0D\u5F00\u626B\u7801\uFF1F\u5728\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00","botOnboarding.close":"\u5173\u95ED","botOnboarding.restartHint":"\u5DF2\u5199\u5165 bots.json\u3002\u6267\u884C pnpm daemon:restart \u540E\u65B0\u673A\u5668\u4EBA\u751F\u6548\u3002","botOnboarding.qrAlt":"\u98DE\u4E66\u626B\u7801\u6DFB\u52A0\u673A\u5668\u4EBA\u4E8C\u7EF4\u7801","overview.title":"\u63A7\u5236\u53F0\u603B\u89C8","overview.subtitle":"\u8DE8 bot\u3001\u7FA4\u804A\u3001\u4F1A\u8BDD\u548C\u5B9A\u65F6\u4EFB\u52A1\u7684\u5B9E\u65F6\u7BA1\u63A7\u9762\u3002","overview.openSessions":"\u6D3B\u8DC3\u4F1A\u8BDD","overview.workingSessions":"\u5DE5\u4F5C\u4E2D","overview.onlineBots":"\u5728\u7EBF Bot","overview.schedules":"\u5B9A\u65F6\u4EFB\u52A1","overview.groups":"\u7FA4\u804A\u8986\u76D6","overview.enabledSchedules":"\u5DF2\u542F\u7528","overview.total":"\u603B\u8BA1","overview.active":"\u6D3B\u8DC3","overview.daemonRegistry":"daemon \u6CE8\u518C\u8868","overview.chatMatrix":"\u7FA4\u804A\u77E9\u9635","overview.recentSessions":"\u6700\u8FD1\u4F1A\u8BDD","overview.nextSchedules":"\u5373\u5C06\u6267\u884C","overview.noSessions":"\u6682\u65E0\u4F1A\u8BDD\u3002","overview.noSchedules":"\u6682\u65E0\u5B9A\u65F6\u4EFB\u52A1\u3002","sessions.title":"\u4F1A\u8BDD\u63A7\u5236","sessions.subtitle":"\u5B9A\u4F4D\u98DE\u4E66\u8BDD\u9898\u3001\u6253\u5F00 Web Terminal\u3001\u5173\u95ED\u6216\u6062\u590D CLI \u4F1A\u8BDD\u3002","sessions.search":"\u641C\u7D22\u5DE5\u4F5C\u76EE\u5F55 / \u6807\u9898 / ID","sessions.anyStatus":"\u5168\u90E8\u72B6\u6001","sessions.adoptAny":"adopt: \u5168\u90E8","sessions.adoptYes":"adopt: \u662F","sessions.adoptNo":"adopt: \u5426","sessions.activeOnly":"\u4EC5\u6D3B\u8DC3","sessions.closeSelected":"\u5173\u95ED\u9009\u4E2D","sessions.clearSelection":"\u53D6\u6D88\u9009\u62E9","sessions.selectedCount":"\u5DF2\u9009 {count} \u4E2A\u4F1A\u8BDD","sessions.bot":"bot","sessions.cli":"CLI","sessions.status":"\u72B6\u6001","sessions.titleCol":"\u6807\u9898","sessions.workingDir":"\u5DE5\u4F5C\u76EE\u5F55","sessions.created":"\u521B\u5EFA","sessions.last":"\u6700\u8FD1","sessions.adopt":"\u63A5\u5165","sessions.actions":"\u64CD\u4F5C","sessions.details":"\u8BE6\u60C5","sessions.copy":"\u590D\u5236","sessions.copied":"\u5DF2\u590D\u5236","sessions.locate":"\u5B9A\u4F4D\u8BDD\u9898","sessions.locating":"\u53D1\u9001\u4E2D...","sessions.cooldown":"\u51B7\u5374 {seconds}s","sessions.openTerminal":"\u7EC8\u7AEF","sessions.close":"\u5173\u95ED\u4F1A\u8BDD","sessions.resume":"\u6062\u590D\u4F1A\u8BDD","sessions.dismiss":"\u5173\u95ED","sessions.closeConfirm":"\u5173\u95ED\u8FD9\u4E2A\u4F1A\u8BDD\uFF1F","sessions.resumeFailed":"\u6062\u590D\u5931\u8D25","sessions.closeBulkConfirm":"\u5173\u95ED\u9009\u4E2D\u7684 {count} \u4E2A\u4F1A\u8BDD\uFF1F","sessions.empty":"\u6CA1\u6709\u7B26\u5408\u6761\u4EF6\u7684\u4F1A\u8BDD\u3002","groups.title":"\u7FA4\u7EC4\u4E0E Bot","groups.subtitle":"\u67E5\u770B chat x bot \u8986\u76D6\u77E9\u9635\uFF0C\u7BA1\u7406\u62C9\u7FA4\u3001oncall\u3001\u9000\u7FA4\u548C\u89E3\u6563\u3002","groups.search":"\u641C\u7D22\u7FA4\u540D / ID / owner","groups.missingOnly":"\u4EC5\u7F3A bot","groups.refresh":"\u5237\u65B0","groups.create":"\u65B0\u5EFA\u7FA4","groups.chat":"\u7FA4\u804A","groups.actions":"\u64CD\u4F5C","groups.addBots":"\u6DFB\u52A0 bot","groups.manage":"\u7BA1\u7406","groups.empty":"\u6CA1\u6709\u7B26\u5408\u6761\u4EF6\u7684\u7FA4\u804A\u3002","groups.createTitle":"\u65B0\u5EFA\u7FA4\u804A","groups.createHelp":"\u9009\u62E9\u8981\u9080\u8BF7\u7684 bot\u3002dashboard \u4F1A\u81EA\u52A8\u9009\u62E9\u5728\u7EBF daemon \u4F5C\u4E3A\u521B\u5EFA\u8005\u3002","groups.name":"\u7FA4\u540D","groups.namePlaceholder":"\u4F8B\u5982 AI ChangeLog","groups.bindDir":"\u7ED1\u5B9A\u76EE\u5F55","groups.bindDirHelp":"\u65B0\u8BDD\u9898\u76F4\u63A5\u7528\u8BE5\u76EE\u5F55\u542F\u52A8 CLI\uFF0C\u8DF3\u8FC7 repo \u9009\u62E9\u3002","groups.botPicker":"Bot","groups.createSubmit":"\u521B\u5EFA","groups.cancel":"\u53D6\u6D88","groups.successTitle":"\u7FA4\u521B\u5EFA\u6210\u529F","groups.openGroup":"\u6253\u5F00\u65B0\u7FA4","groups.manageTitle":"\u7BA1\u7406 {name}","groups.owner":"\u7FA4\u4E3B","groups.oncall":"Oncall \u6A21\u5F0F","groups.oncallHelp":"\u5F00\u542F\u540E\uFF0C\u7FA4\u5185\u6210\u5458\u53EF @ \u673A\u5668\u4EBA\uFF1B\u65B0\u8BDD\u9898\u76F4\u63A5\u4F7F\u7528\u7ED1\u5B9A\u76EE\u5F55\u3002","groups.leaveTitle":"\u9009\u62E9\u673A\u5668\u4EBA\u9000\u51FA\u7FA4\u804A","groups.leaveSelected":"\u9009\u4E2D\u673A\u5668\u4EBA\u9000\u51FA\u7FA4\u804A","groups.disband":"\u89E3\u6563\u7FA4\u804A","groups.dangerHint":"\u89E3\u6563\u4EC5\u5F53\u5728\u7FA4\u673A\u5668\u4EBA\u662F\u7FA4\u4E3B\u65F6\u624D\u4F1A\u6210\u529F\uFF0C\u5426\u5219\u5EFA\u8BAE\u4F7F\u7528\u9000\u51FA\u7FA4\u804A\u3002","groups.save":"\u4FDD\u5B58","groups.needWorkingDir":"\u8BF7\u586B\u5DE5\u4F5C\u76EE\u5F55","groups.noBotsOnline":"\u6CA1\u6709\u5728\u7EBF bot\u3002\u8BF7\u5148\u91CD\u542F daemon\u3002","schedules.title":"\u5B9A\u65F6\u4EFB\u52A1","schedules.subtitle":"\u8DE8 daemon \u67E5\u770B\u3001\u6682\u505C\u3001\u6062\u590D\u548C\u7ACB\u5373\u89E6\u53D1\u5B9A\u65F6\u4EFB\u52A1\u3002","schedules.search":"\u641C\u7D22\u540D\u79F0 / prompt / \u5DE5\u4F5C\u76EE\u5F55","schedules.anyKind":"\u5168\u90E8\u7C7B\u578B","schedules.enabledOnly":"\u4EC5\u542F\u7528","schedules.name":"\u540D\u79F0","schedules.bot":"bot","schedules.schedule":"\u89C4\u5219","schedules.next":"\u4E0B\u6B21","schedules.last":"\u4E0A\u6B21","schedules.repeat":"\u91CD\u590D","schedules.enabled":"\u542F\u7528","schedules.actions":"\u64CD\u4F5C","schedules.runNow":"\u7ACB\u5373\u8FD0\u884C","schedules.pause":"\u6682\u505C","schedules.resume":"\u6062\u590D","schedules.empty":"\u6682\u65E0\u5B9A\u65F6\u4EFB\u52A1\u3002","botDefaults.title":"Bot \u9ED8\u8BA4 Oncall","botDefaults.subtitle":"\u914D\u7F6E\u6BCF\u4E2A bot \u5728\u65B0\u7FA4\u91CC\u7684\u9ED8\u8BA4 oncall \u884C\u4E3A\u3002","botDefaults.search":"\u641C\u7D22 bot \u540D / app id","botDefaults.refresh":"\u5237\u65B0","botDefaults.warning":"\u5F00\u542F\u540E\uFF0C\u6CA1\u6709 oncall binding \u7684\u7FA4\u4F1A\u5728\u4E0B\u6B21\u5F00\u65B0\u8BDD\u9898\u65F6\u81EA\u52A8\u7ED1\u5B9A\u5230\u8BE5\u76EE\u5F55\uFF1B\u624B\u52A8\u7ED1\u5B9A\u6216\u624B\u52A8\u89E3\u7ED1\u8FC7\u7684\u7FA4\u4E0D\u4F1A\u88AB\u8986\u76D6\u3002","botDefaults.empty":"\u6CA1\u6709\u5728\u7EBF bot\u3002\u5148 botmux restart \u8BA9 daemon \u4E0A\u7EBF\u3002","botDefaults.defaultOncall":"\u9ED8\u8BA4\u8FDB\u5165 oncall \u6A21\u5F0F","botDefaults.defaultOncallHelp":"\u6240\u6709\u672A\u7ED1\u5B9A\u7684\u7FA4\u4E0B\u6B21\u5F00\u8BDD\u9898\u81EA\u52A8\u7ED1\u5B9A","botDefaults.workingDir":"\u9ED8\u8BA4\u5DE5\u4F5C\u76EE\u5F55","botDefaults.lastEnabled":"\u4E0A\u6B21\u542F\u7528\u65F6\u95F4","botDefaults.autobound":"\u5DF2\u81EA\u52A8\u7ED1\u5B9A {count} \u4E2A\u7FA4","botDefaults.save":"\u4FDD\u5B58","botDefaults.required":"\u5F00\u542F\u65F6\u5FC5\u987B\u586B\u5DE5\u4F5C\u76EE\u5F55","common.none":"\u65E0","common.unknown":"\u672A\u77E5","common.now":"\u521A\u521A","common.never":"\u4ECE\u672A","nav.workflows":"\u5DE5\u4F5C\u6D41(beta)","nav.workflowCatalog":"\u76EE\u5F55","workflow.subnav.runs":"\u8FD0\u884C","workflow.subnav.catalog":"\u76EE\u5F55","workflow.searchPlaceholder":"\u641C\u7D22 runId / workflowId / chatId","workflow.filter.nonTerminal":"\u975E\u7EC8\u6001","workflow.filter.all":"\u5168\u90E8","workflow.status.pending":"\u5F85\u5F00\u59CB","workflow.status.running":"\u8FD0\u884C\u4E2D","workflow.status.waiting":"\u7B49\u5F85\u4E2D","workflow.status.effectAttempting":"\u526F\u4F5C\u7528\u4E2D","workflow.status.timedOut":"\u5DF2\u8D85\u65F6","workflow.status.succeeded":"\u6210\u529F","workflow.status.failed":"\u5931\u8D25","workflow.status.cancelled":"\u5DF2\u53D6\u6D88","workflow.table.run":"\u8FD0\u884C","workflow.table.workflow":"\u5DE5\u4F5C\u6D41","workflow.table.status":"\u72B6\u6001","workflow.table.lastSeq":"\u6700\u540E\u5E8F\u53F7","workflow.table.dangling":"\u60AC\u6302 dEf/dAct/dWait","workflow.table.updated":"\u66F4\u65B0\u65F6\u95F4","workflow.table.chatApp":"\u7FA4\u804A / \u5E94\u7528","workflow.list.failedLoad":"\u52A0\u8F7D\u5931\u8D25\uFF1A{error}","workflow.list.noRuns":"\u6CA1\u6709\u5339\u914D\u7684\u8FD0\u884C\u3002","workflow.list.noFilterMatch":"\u6CA1\u6709\u7B26\u5408\u7B5B\u9009\u6761\u4EF6\u7684\u8FD0\u884C\u3002","workflow.list.loaded":"{count} \u4E2A\u8FD0\u884C \xB7 \u5237\u65B0\u4E8E {time}","workflow.list.error":"\u9519\u8BEF\uFF1A{error}","workflow.detail.back":"\u8FD4\u56DE","workflow.detail.loading":"\u52A0\u8F7D\u4E2D...","workflow.detail.loadFailed":"\u52A0\u8F7D\u5931\u8D25","workflow.detail.cancel":"\u53D6\u6D88","workflow.detail.cliCancelOnly":"\u4EC5 CLI \u53EF\u53D6\u6D88","workflow.detail.cancelTitle":"\u53D6\u6D88\u8FD9\u4E2A\u5DE5\u4F5C\u6D41\u8FD0\u884C","workflow.detail.cliCancelTitle":"\u65E0\u6CD5\u5728\u9875\u9762\u53D6\u6D88\uFF1A\u8BF7\u4F7F\u7528 botmux workflow cancel {runId}","workflow.detail.nodes":"\u8282\u70B9 / Activity","workflow.detail.parallel":"\u5E76\u53D1\u6267\u884C","workflow.detail.parallelMeta":"{count} \u6B21\u5C1D\u8BD5 \xB7 \u6700\u9AD8\u5E76\u53D1 {max} \xB7 \u8FD0\u884C\u4E2D {running}","workflow.detail.noParallelData":"\u8FD8\u6CA1\u6709 attempt \u65F6\u95F4\u6570\u636E\u3002","workflow.detail.parallelNow":"\u73B0\u5728","workflow.detail.node":"\u8282\u70B9","workflow.detail.nodeStatus":"\u8282\u70B9\u72B6\u6001","workflow.detail.activity":"Activity","workflow.detail.activityStatus":"Activity \u72B6\u6001","workflow.detail.attempts":"\u5C1D\u8BD5\u6B21\u6570","workflow.detail.current":"\u5F53\u524D\u5C1D\u8BD5","workflow.detail.detail":"\u8BE6\u60C5","workflow.detail.nodeIO":"\u8282\u70B9\u8F93\u5165\u8F93\u51FA","workflow.detail.timeline":"\u65F6\u95F4\u7EBF","workflow.detail.loadOlder":"\u52A0\u8F7D\u66F4\u65E9\u4E8B\u4EF6","workflow.detail.seq":"\u5E8F\u53F7","workflow.detail.actor":"\u6267\u884C\u8005","workflow.detail.error":"\u9519\u8BEF","workflow.detail.event":"\u4E8B\u4EF6","workflow.detail.time":"\u65F6\u95F4","workflow.detail.refreshed":"\u5237\u65B0\u4E8E {time}","workflow.detail.unknownRun":"\u672A\u77E5\u8FD0\u884C","workflow.detail.snapshotHttp":"snapshot HTTP {status}","workflow.detail.eventsHttp":"events HTTP {status}","workflow.detail.cancelUnavailable":"\u65E0\u6CD5\u53D6\u6D88\uFF1A\u8BF7\u4F7F\u7528 botmux workflow cancel {runId}","workflow.detail.cancelConfirm":`\u786E\u8BA4\u53D6\u6D88\u5DE5\u4F5C\u6D41\u8FD0\u884C {runId}\uFF1F
|
|
2
|
+
|
|
3
|
+
{total} \u4E2A\u60AC\u6302\u9879\u4F1A\u7531 cancel recovery \u5904\u7406\u3002
|
|
4
|
+
effects={effects}, activities={activities}, waits={waits}, cancels={cancels}`,"workflow.detail.writeAccessCancel":"\u9700\u8981\u5199\u6743\u9650\uFF1A\u8BF7\u5728\u7EC8\u7AEF\u8FD0\u884C `botmux dashboard` \u83B7\u53D6\u4E00\u6B21\u6027 URL\uFF0C\u6253\u5F00\u540E\u5199\u5165 cookie\uFF0C\u518D\u56DE\u6765\u70B9\u51FB\u53D6\u6D88\u3002","workflow.detail.cancelHttp":"cancel HTTP {status}","workflow.detail.cancelPending":"\u53D6\u6D88\u5DF2\u63D0\u4EA4\uFF1B\u7B49\u5F85\u8FD0\u884C\u4E2D\u7684 activity \u6536\u655B","workflow.detail.writeAccessApproval":"\u9700\u8981\u5199\u6743\u9650\uFF1A\u8BF7\u5728\u7EC8\u7AEF\u8FD0\u884C `botmux dashboard` \u83B7\u53D6\u4E00\u6B21\u6027 URL\uFF0C\u6253\u5F00\u540E\u5199\u5165 cookie\uFF0C\u518D\u56DE\u6765\u5BA1\u6279\u3002","workflow.detail.actionHttp":"{action} HTTP {status}","workflow.detail.approved":"\u5DF2\u901A\u8FC7","workflow.detail.rejected":"\u5DF2\u62D2\u7EDD","workflow.detail.alreadyTerminal":"\u8FD0\u884C\u5DF2\u7EC8\u6001\uFF1B\u672A\u5E94\u7528\u201C{label}\u201D\u3002","workflow.detail.workflowContinue":"{label}\uFF1B\u7B49\u5F85\u5DE5\u4F5C\u6D41\u7EE7\u7EED\u6267\u884C\u3002","workflow.detail.workflowRefreshing":"{label}\uFF1B\u6B63\u5728\u5237\u65B0\u5DE5\u4F5C\u6D41\u72B6\u6001\u3002","workflow.detail.eventsLoaded":"\u5DF2\u52A0\u8F7D {loaded}/{total} \u4E2A\u4E8B\u4EF6","workflow.detail.dangling":"\u60AC\u6302\u9879","workflow.detail.noDangling":"\u6CA1\u6709\u60AC\u6302\u5DE5\u4F5C\u3002","workflow.detail.none":"\u65E0","workflow.detail.noNodes":"\u8FD8\u6CA1\u6709\u8282\u70B9\u3002","workflow.detail.idle":"\u7A7A\u95F2","workflow.detail.noNodeIO":"\u8FD8\u6CA1\u6709\u8282\u70B9\u8F93\u5165\u8F93\u51FA\u3002","workflow.detail.notDispatched":"\u5C1A\u672A\u6D3E\u53D1","workflow.detail.noAttempt":"\u8FD8\u6CA1\u6709\u5C1D\u8BD5","workflow.detail.attempt":"\u5C1D\u8BD5","workflow.detail.authoredInput":"\u539F\u59CB\u8F93\u5165","workflow.detail.resolvedInput":"\u89E3\u6790\u540E\u8F93\u5165","workflow.detail.output":"\u8F93\u51FA","workflow.detail.executionLog":"\u6267\u884C\u65E5\u5FD7","workflow.detail.liveTerminal":"\u5B9E\u65F6\u7EC8\u7AEF","workflow.detail.terminalLive":"\u5728\u7EBF","workflow.detail.terminalClosedShort":"\u5DF2\u5173\u95ED","workflow.detail.terminalClosed":"\u7EC8\u7AEF\u5DF2\u5173\u95ED\u3002\u8BF7\u67E5\u770B\u4E0B\u65B9\u6267\u884C\u65E5\u5FD7\u83B7\u53D6\u6700\u7EC8\u8BB0\u5F55\u3002","workflow.detail.openTerminalNewTab":"\u5728\u65B0\u6807\u7B7E\u9875\u6253\u5F00\u7EC8\u7AEF","workflow.detail.terminalReplay":"\u7EC8\u7AEF\u56DE\u653E","workflow.detail.openReplayNewTab":"\u5728\u65B0\u6807\u7B7E\u9875\u6253\u5F00\u56DE\u653E","workflow.detail.downloadFullLog":"\u4E0B\u8F7D\u5B8C\u6574\u65E5\u5FD7","workflow.detail.terminalResume":"\u8C03\u8BD5\u4F1A\u8BDD","workflow.detail.openResumeNewTab":"\u5728\u65B0\u6807\u7B7E\u9875\u6253\u5F00\u8C03\u8BD5\u4F1A\u8BDD","workflow.detail.resumeSession":"\u7EE7\u7EED\u4F1A\u8BDD","workflow.detail.resumeStarting":"\u6B63\u5728\u542F\u52A8\u2026","workflow.detail.endResumeSession":"\u7ED3\u675F\u8C03\u8BD5\u4F1A\u8BDD","workflow.detail.resumeEnding":"\u7ED3\u675F\u4E2D\u2026","workflow.detail.resumeUnsupportedCli":"{cliId} CLI \u4E0D\u652F\u6301 resume","workflow.detail.resumeMissingCliSession":"\u7F3A\u5C11 cliSessionId\uFF0C\u65E0\u6CD5 resume","workflow.detail.resumeStartFailed":"\u542F\u52A8\u8C03\u8BD5\u4F1A\u8BDD\u5931\u8D25 (HTTP {status})","workflow.detail.resumeEndFailed":"\u7ED3\u675F\u8C03\u8BD5\u4F1A\u8BDD\u5931\u8D25 (HTTP {status})","workflow.detail.writeAccessResume":"\u9700\u8981\u5199\u5165\u6743\u9650\u624D\u80FD resume \u4F1A\u8BDD\u3002","workflow.detail.waitPrompt":"\u7B49\u5F85\u63D0\u793A","workflow.detail.approvalComment":"\u5BA1\u6279\u5907\u6CE8","workflow.detail.optionalComment":"\u53EF\u9009\u5907\u6CE8","workflow.detail.approve":"\u901A\u8FC7","workflow.detail.reject":"\u62D2\u7EDD","workflow.detail.submitting":"\u63D0\u4EA4\u4E2D...","workflow.detail.empty":"\u7A7A","workflow.detail.truncated":"\u5DF2\u622A\u65AD","workflow.detail.noData":"\u6CA1\u6709\u6570\u636E\u3002","workflow.detail.noPreview":"\u6CA1\u6709\u9884\u89C8\u3002","workflow.detail.open":"\u6253\u5F00","workflow.detail.deadline":"\u622A\u6B62","workflow.detail.effect":"\u526F\u4F5C\u7528","workflow.detail.wait":"\u7B49\u5F85","workflow.detail.noEvents":"\u8FD8\u6CA1\u6709\u4E8B\u4EF6\u3002","workflow.summary.workflow":"\u5DE5\u4F5C\u6D41","workflow.summary.status":"\u72B6\u6001","workflow.summary.lastSeq":"\u6700\u540E\u5E8F\u53F7","workflow.summary.updated":"\u66F4\u65B0\u65F6\u95F4","workflow.summary.revision":"\u4FEE\u8BA2","workflow.summary.initiator":"\u53D1\u8D77\u4EBA","workflow.summary.failedNode":"\u5931\u8D25\u8282\u70B9","workflow.summary.cancelOrigin":"\u53D6\u6D88\u6765\u6E90","workflow.summary.chat":"\u7FA4\u804A","workflow.summary.app":"\u5E94\u7528","workflow.dangling.activities":"Activities","workflow.dangling.effects":"Effects","workflow.dangling.waits":"Waits","workflow.dangling.cancels":"Cancels","catalog.title":"\u5DE5\u4F5C\u6D41\u76EE\u5F55","catalog.subtitle":"\u4ECE\u5DF2\u4FDD\u5B58\u7684 workflow \u5B9A\u4E49\u521B\u5EFA\u4E00\u6B21\u8FD0\u884C\u3002","catalog.searchPlaceholder":"\u641C\u7D22 workflowId / \u8DEF\u5F84","catalog.refresh":"\u5237\u65B0","catalog.loading":"\u6B63\u5728\u52A0\u8F7D\u76EE\u5F55...","catalog.loadFailed":"\u76EE\u5F55\u52A0\u8F7D\u5931\u8D25\uFF1A{error}","catalog.noDefinitions":"\u6CA1\u6709\u627E\u5230 workflow \u5B9A\u4E49\u3002","catalog.noFilterMatch":"\u6CA1\u6709\u7B26\u5408\u7B5B\u9009\u6761\u4EF6\u7684\u5B9A\u4E49\u3002","catalog.table.workflow":"\u5DE5\u4F5C\u6D41","catalog.table.version":"\u7248\u672C","catalog.table.params":"\u53C2\u6570","catalog.table.nodes":"\u8282\u70B9","catalog.table.revision":"\u4FEE\u8BA2","catalog.table.path":"\u8DEF\u5F84","catalog.paramSummary":"{required}/{total} \u5FC5\u586B","catalog.back":"\u8FD4\u56DE\u76EE\u5F55","catalog.detailTitle":"\u5DE5\u4F5C\u6D41\u5B9A\u4E49","catalog.definitionLoadFailed":"\u5B9A\u4E49\u52A0\u8F7D\u5931\u8D25\uFF1A{error}","catalog.summary":"\u6458\u8981","catalog.paramsSchema":"\u53C2\u6570 Schema","catalog.definitionJson":"\u5B9A\u4E49 JSON","catalog.runPanel":"\u8FD0\u884C\u5DE5\u4F5C\u6D41","catalog.paramsJson":"\u53C2\u6570 JSON","catalog.paramsPlaceholder":`{
|
|
5
|
+
"city": "\u5317\u4EAC"
|
|
6
|
+
}`,"catalog.chatId":"\u7FA4\u804A ID","catalog.larkAppId":"\u98DE\u4E66\u5E94\u7528 ID","catalog.chatBindingHint":"\u5FC5\u586B\uFF0C\u7528\u4E8E\u786E\u5B9A humanGate \u5361\u7247\u53D1\u9001\u5230\u54EA\u4E2A\u98DE\u4E66\u7FA4\uFF0C\u4EE5\u53CA\u53D6\u6D88\u8DEF\u7531\u5F52\u5C5E\u3002","catalog.run":"\u8FD0\u884C","catalog.running":"\u542F\u52A8\u4E2D...","catalog.badParamsJson":"\u53C2\u6570\u5FC5\u987B\u662F JSON object\u3002","catalog.writeAccess":"\u9700\u8981\u5199\u6743\u9650\uFF1A\u8BF7\u5728\u7EC8\u7AEF\u8FD0\u884C `botmux dashboard` \u83B7\u53D6\u4E00\u6B21\u6027 URL\uFF0C\u6253\u5F00\u540E\u5199\u5165 cookie\uFF0C\u518D\u56DE\u6765\u8FD0\u884C\u3002","catalog.runHttp":"run HTTP {status}","catalog.runStarted":"\u8FD0\u884C\u5DF2\u542F\u52A8\uFF1B\u6B63\u5728\u6253\u5F00\u8BE6\u60C5\u9875...","catalog.invalidParams":"\u53C2\u6570\u65E0\u6548","catalog.issue":"{path}: {message}","catalog.noParams":"\u6CA1\u6709\u58F0\u660E\u53C2\u6570\u3002","catalog.required":"\u5FC5\u586B","catalog.optional":"\u53EF\u9009","catalog.default":"\u9ED8\u8BA4\u503C","catalog.description":"\u8BF4\u660E","catalog.path":"\u8DEF\u5F84","catalog.revision":"\u4FEE\u8BA2","catalog.nodeCount":"\u8282\u70B9\u6570"},It={"app.name":"botmux","app.subtitle":"Feishu AI CLI Control","time.secondsAgo":"{value}s ago","time.minutesAgo":"{value}m ago","time.hoursAgo":"{value}h ago","nav.overview":"Overview","nav.sessions":"Sessions","nav.groups":"Groups","nav.schedules":"Schedules","nav.botDefaults":"Bot Defaults","status.live":"Live","status.disconnected":"Disconnected","status.system":"System","status.light":"Light","status.dark":"Dark","status.language":"Language","status.theme":"Theme","botOnboarding.add":"Add Bot","botOnboarding.title":"Scan to Add Bot","botOnboarding.intro":"Scan with the Feishu app to create a PersonalAgent app. The dashboard writes it to local bots.json after success.","botOnboarding.starting":"Generating QR code...","botOnboarding.waiting":"Scan with the Feishu app to continue.","botOnboarding.verifying":"Scan accepted. Verifying credentials...","botOnboarding.completed":"Bot added.","botOnboarding.failed":"Add failed","botOnboarding.openLink":"Open scan link in browser","botOnboarding.close":"Close","botOnboarding.restartHint":"bots.json has been updated. Run pnpm daemon:restart for the new bot to take effect.","botOnboarding.qrAlt":"Feishu bot onboarding QR code","overview.title":"Control Overview","overview.subtitle":"A realtime control plane across bots, chats, CLI sessions, and schedules.","overview.openSessions":"Active Sessions","overview.workingSessions":"Working","overview.onlineBots":"Online Bots","overview.schedules":"Schedules","overview.groups":"Groups Seen","overview.enabledSchedules":"Enabled","overview.total":"total","overview.active":"active","overview.daemonRegistry":"daemon registry","overview.chatMatrix":"chat matrix","overview.recentSessions":"Recent Sessions","overview.nextSchedules":"Next Runs","overview.noSessions":"No sessions yet.","overview.noSchedules":"No schedules yet.","sessions.title":"Session Control","sessions.subtitle":"Locate Feishu topics, open Web Terminal, close or resume CLI sessions.","sessions.search":"Search working dir / title / IDs","sessions.anyStatus":"Any status","sessions.adoptAny":"adopt: any","sessions.adoptYes":"adopt: yes","sessions.adoptNo":"adopt: no","sessions.activeOnly":"Active only","sessions.closeSelected":"Close selected","sessions.clearSelection":"Clear","sessions.selectedCount":"{count} sessions selected","sessions.bot":"Bot","sessions.cli":"CLI","sessions.status":"Status","sessions.titleCol":"Title","sessions.workingDir":"Working Dir","sessions.created":"Created","sessions.last":"Last","sessions.adopt":"Adopt","sessions.actions":"Actions","sessions.details":"Details","sessions.copy":"Copy","sessions.copied":"Copied","sessions.locate":"Locate Topic","sessions.locating":"Sending...","sessions.cooldown":"Cooldown {seconds}s","sessions.openTerminal":"Terminal","sessions.close":"Close Session","sessions.resume":"Resume Session","sessions.dismiss":"Close","sessions.closeConfirm":"Close this session?","sessions.resumeFailed":"Resume failed","sessions.closeBulkConfirm":"Close {count} selected sessions?","sessions.empty":"No sessions match the filters.","groups.title":"Groups & Bots","groups.subtitle":"Inspect the chat x bot matrix and manage group creation, oncall, leave, and disband flows.","groups.search":"Search chat name / ID / owner","groups.missingOnly":"Missing bot only","groups.refresh":"Refresh","groups.create":"New Group","groups.chat":"Chat","groups.actions":"Actions","groups.addBots":"Add Bots","groups.manage":"Manage","groups.empty":"No chats match the filters.","groups.createTitle":"Create New Group","groups.createHelp":"Pick bots to invite. The dashboard chooses an online daemon as creator.","groups.name":"Group Name","groups.namePlaceholder":"e.g. AI ChangeLog","groups.bindDir":"Bind Directory","groups.bindDirHelp":"New topics start the CLI here and skip repo selection.","groups.botPicker":"Bots","groups.createSubmit":"Create","groups.cancel":"Cancel","groups.successTitle":"Group Created","groups.openGroup":"Open Group","groups.manageTitle":"Manage {name}","groups.owner":"Owner","groups.oncall":"Oncall Mode","groups.oncallHelp":"When enabled, group members can @ the bot; new topics use the bound directory.","groups.leaveTitle":"Select Bots to Leave","groups.leaveSelected":"Selected Bots Leave","groups.disband":"Disband Group","groups.dangerHint":"Disband only works when an in-chat bot is the owner. Otherwise prefer leaving the chat.","groups.save":"Save","groups.needWorkingDir":"Working directory is required","groups.noBotsOnline":"No bots online. Restart the daemon first.","schedules.title":"Schedules","schedules.subtitle":"View, pause, resume, and run scheduled tasks across daemons.","schedules.search":"Search name / prompt / working dir","schedules.anyKind":"Any kind","schedules.enabledOnly":"Enabled only","schedules.name":"Name","schedules.bot":"Bot","schedules.schedule":"Schedule","schedules.next":"Next","schedules.last":"Last","schedules.repeat":"Repeat","schedules.enabled":"Enabled","schedules.actions":"Actions","schedules.runNow":"Run Now","schedules.pause":"Pause","schedules.resume":"Resume","schedules.empty":"No schedules.","botDefaults.title":"Bot Default Oncall","botDefaults.subtitle":"Configure each bot's default oncall behavior in new chats.","botDefaults.search":"Search bot name / app id","botDefaults.refresh":"Refresh","botDefaults.warning":"When enabled, chats without an oncall binding auto-bind to this directory on their next new topic. Manually bound or unbound chats are preserved.","botDefaults.empty":"No bots online. Run botmux restart first.","botDefaults.defaultOncall":"Default to oncall mode","botDefaults.defaultOncallHelp":"Unbound chats auto-bind on the next new topic","botDefaults.workingDir":"Default Working Directory","botDefaults.lastEnabled":"Last Enabled","botDefaults.autobound":"{count} chats auto-bound","botDefaults.save":"Save","botDefaults.required":"Working directory is required when enabled","common.none":"None","common.unknown":"Unknown","common.now":"now","common.never":"never","nav.workflows":"Workflows (beta)","nav.workflowCatalog":"Catalog","workflow.subnav.runs":"Runs","workflow.subnav.catalog":"Catalog","workflow.searchPlaceholder":"search runId / workflowId / chatId","workflow.filter.nonTerminal":"non-terminal","workflow.filter.all":"all","workflow.status.pending":"pending","workflow.status.running":"running","workflow.status.waiting":"waiting","workflow.status.effectAttempting":"effect","workflow.status.timedOut":"timed out","workflow.status.succeeded":"succeeded","workflow.status.failed":"failed","workflow.status.cancelled":"cancelled","workflow.table.run":"run","workflow.table.workflow":"workflow","workflow.table.status":"status","workflow.table.lastSeq":"lastSeq","workflow.table.dangling":"dEf/dAct/dWait","workflow.table.updated":"updated","workflow.table.chatApp":"chat / app","workflow.list.failedLoad":"Failed to load: {error}","workflow.list.noRuns":"No runs match.","workflow.list.noFilterMatch":"No runs match this filter.","workflow.list.loaded":"{count} runs \xB7 refreshed {time}","workflow.list.error":"error: {error}","workflow.detail.back":"Back","workflow.detail.loading":"Loading...","workflow.detail.loadFailed":"Load failed","workflow.detail.cancel":"Cancel","workflow.detail.cliCancelOnly":"CLI cancel only","workflow.detail.cancelTitle":"Cancel this workflow run","workflow.detail.cliCancelTitle":"Cancel unavailable: use botmux workflow cancel {runId}","workflow.detail.nodes":"Nodes / Activities","workflow.detail.parallel":"Parallel execution","workflow.detail.parallelMeta":"{count} attempt(s) \xB7 max parallel {max} \xB7 running {running}","workflow.detail.noParallelData":"No attempt timing data yet.","workflow.detail.parallelNow":"now","workflow.detail.node":"node","workflow.detail.nodeStatus":"node status","workflow.detail.activity":"activity","workflow.detail.activityStatus":"activity status","workflow.detail.attempts":"attempts","workflow.detail.current":"current","workflow.detail.detail":"detail","workflow.detail.nodeIO":"Node I/O","workflow.detail.timeline":"Timeline","workflow.detail.loadOlder":"Load older","workflow.detail.seq":"seq","workflow.detail.actor":"actor","workflow.detail.error":"error","workflow.detail.event":"event","workflow.detail.time":"time","workflow.detail.refreshed":"refreshed {time}","workflow.detail.unknownRun":"unknown run","workflow.detail.snapshotHttp":"snapshot HTTP {status}","workflow.detail.eventsHttp":"events HTTP {status}","workflow.detail.cancelUnavailable":"cancel unavailable: use botmux workflow cancel {runId}","workflow.detail.cancelConfirm":`Cancel workflow run {runId}?
|
|
7
|
+
|
|
8
|
+
{total} dangling item(s) will be handled by cancel-driven recovery.
|
|
9
|
+
effects={effects}, activities={activities}, waits={waits}, cancels={cancels}`,"workflow.detail.writeAccessCancel":"write access required: run `botmux dashboard` in the terminal to get a one-time URL, open it once to set the cookie, then come back and click cancel again.","workflow.detail.cancelHttp":"cancel HTTP {status}","workflow.detail.cancelPending":"cancel pending; waiting for running activity to drain","workflow.detail.writeAccessApproval":"write access required: run `botmux dashboard` in the terminal to get a one-time URL, open it once to set the cookie, then come back and approve/reject again.","workflow.detail.actionHttp":"{action} HTTP {status}","workflow.detail.approved":"approved","workflow.detail.rejected":"rejected","workflow.detail.alreadyTerminal":"Run already terminal; {label} was not applied.","workflow.detail.workflowContinue":"{label}; waiting for workflow to continue.","workflow.detail.workflowRefreshing":"{label}; refreshing workflow state.","workflow.detail.eventsLoaded":"{loaded}/{total} events loaded","workflow.detail.dangling":"Dangling","workflow.detail.noDangling":"No dangling work.","workflow.detail.none":"none","workflow.detail.noNodes":"No nodes yet.","workflow.detail.idle":"idle","workflow.detail.noNodeIO":"No node I/O yet.","workflow.detail.notDispatched":"not dispatched","workflow.detail.noAttempt":"No attempt yet","workflow.detail.attempt":"attempt","workflow.detail.authoredInput":"Authored input","workflow.detail.resolvedInput":"Resolved input","workflow.detail.output":"Output","workflow.detail.executionLog":"Execution log","workflow.detail.liveTerminal":"Live terminal","workflow.detail.terminalLive":"live","workflow.detail.terminalClosedShort":"closed","workflow.detail.terminalClosed":"Terminal is closed. Use the execution log below for the final transcript.","workflow.detail.openTerminalNewTab":"Open terminal in new tab","workflow.detail.terminalReplay":"Terminal replay","workflow.detail.openReplayNewTab":"Open replay in new tab","workflow.detail.downloadFullLog":"Download full log","workflow.detail.terminalResume":"Debug session","workflow.detail.openResumeNewTab":"Open debug session in new tab","workflow.detail.resumeSession":"Resume session","workflow.detail.resumeStarting":"Starting\u2026","workflow.detail.endResumeSession":"End debug session","workflow.detail.resumeEnding":"Ending\u2026","workflow.detail.resumeUnsupportedCli":'CLI "{cliId}" does not support resume',"workflow.detail.resumeMissingCliSession":"Missing cliSessionId \u2014 cannot resume","workflow.detail.resumeStartFailed":"Failed to start debug session (HTTP {status})","workflow.detail.resumeEndFailed":"Failed to end debug session (HTTP {status})","workflow.detail.writeAccessResume":"Resume requires dashboard write access.","workflow.detail.waitPrompt":"Wait prompt","workflow.detail.approvalComment":"Approval comment","workflow.detail.optionalComment":"Optional comment","workflow.detail.approve":"Approve","workflow.detail.reject":"Reject","workflow.detail.submitting":"Submitting...","workflow.detail.empty":"empty","workflow.detail.truncated":"truncated","workflow.detail.noData":"No data.","workflow.detail.noPreview":"No preview.","workflow.detail.open":"open","workflow.detail.deadline":"deadline","workflow.detail.effect":"effect","workflow.detail.wait":"wait","workflow.detail.noEvents":"No events.","workflow.summary.workflow":"workflow","workflow.summary.status":"status","workflow.summary.lastSeq":"lastSeq","workflow.summary.updated":"updated","workflow.summary.revision":"revision","workflow.summary.initiator":"initiator","workflow.summary.failedNode":"failedNode","workflow.summary.cancelOrigin":"cancelOrigin","workflow.summary.chat":"chat","workflow.summary.app":"app","workflow.dangling.activities":"activities","workflow.dangling.effects":"effects","workflow.dangling.waits":"waits","workflow.dangling.cancels":"cancels","catalog.title":"Workflow catalog","catalog.subtitle":"Create a workflow run from a saved workflow definition.","catalog.searchPlaceholder":"search workflowId / path","catalog.refresh":"Refresh","catalog.loading":"Loading catalog...","catalog.loadFailed":"Failed to load catalog: {error}","catalog.noDefinitions":"No workflow definitions found.","catalog.noFilterMatch":"No definitions match this filter.","catalog.table.workflow":"workflow","catalog.table.version":"version","catalog.table.params":"params","catalog.table.nodes":"nodes","catalog.table.revision":"revision","catalog.table.path":"path","catalog.paramSummary":"{required}/{total} required","catalog.back":"Back to catalog","catalog.detailTitle":"Workflow definition","catalog.definitionLoadFailed":"Failed to load definition: {error}","catalog.summary":"Summary","catalog.paramsSchema":"Params schema","catalog.definitionJson":"Definition JSON","catalog.runPanel":"Run workflow","catalog.paramsJson":"Params JSON","catalog.paramsPlaceholder":`{
|
|
10
|
+
"city": "\u5317\u4EAC"
|
|
11
|
+
}`,"catalog.chatId":"Chat ID","catalog.larkAppId":"Lark app ID","catalog.chatBindingHint":"Required so humanGate cards and cancel routing know which Lark chat owns the run.","catalog.run":"Run","catalog.running":"Starting...","catalog.badParamsJson":"Params must be a JSON object.","catalog.writeAccess":"write access required: run `botmux dashboard` in the terminal to get a one-time URL, open it once to set the cookie, then come back and run again.","catalog.runHttp":"run HTTP {status}","catalog.runStarted":"Run started; opening detail page...","catalog.invalidParams":"Invalid params","catalog.issue":"{path}: {message}","catalog.noParams":"No params declared.","catalog.required":"required","catalog.optional":"optional","catalog.default":"default","catalog.description":"description","catalog.path":"path","catalog.revision":"revision","catalog.nodeCount":"nodes"},qe={zh:St,en:It};function Pe(e){if(typeof e!="string")return null;let n=e.trim().toLowerCase();return n==="zh"||n.startsWith("zh-")?"zh":n==="en"||n.startsWith("en-")?"en":null}function Tt(e=[]){for(let n of e){let o=Pe(n);if(o)return o}return"zh"}function fe(e){return(n,o)=>{let r=qe[e][n]??qe.zh[n]??n;return o?r.replace(/\{(\w+)\}/g,(s,a)=>{let c=o[a];return c==null?`{${a}}`:String(c)}):r}}function Ne(e,n){return(e?Pe(e.getItem($e)):null)??Tt(n)}var Se="botmux.dashboard.theme";function Lt(e){return e==="system"||e==="light"||e==="dark"?e:null}function Be(e,n){return e==="system"?n?"dark":"light":e}function je(e){return Lt(e?.getItem(Se))??"system"}var Ie=class{locale="zh";themeMode="system";resolvedTheme="light";listeners=new Set;translate=fe(this.locale);mediaQuery=null;init(){let n=typeof window<"u"?window:void 0;this.locale=Ne(n?.localStorage,Et()),this.translate=fe(this.locale),this.themeMode=je(n?.localStorage),this.mediaQuery=n?.matchMedia?.("(prefers-color-scheme: dark)")??null,this.mediaQuery?.addEventListener("change",()=>{this.applyTheme(),this.emit()}),this.applyTheme(),this.applyLocale()}t(n,o){return this.translate(n,o)}setLocale(n){this.locale!==n&&(this.locale=n,this.translate=fe(n),window.localStorage.setItem($e,n),this.applyLocale(),this.emit())}setThemeMode(n){this.themeMode!==n&&(this.themeMode=n,window.localStorage.setItem(Se,n),this.applyTheme(),this.emit())}on(n){return this.listeners.add(n),()=>this.listeners.delete(n)}emit(){for(let n of this.listeners)n()}applyTheme(){this.resolvedTheme=Be(this.themeMode,!!this.mediaQuery?.matches),document.documentElement.dataset.theme=this.resolvedTheme,document.documentElement.dataset.themeMode=this.themeMode}applyLocale(){document.documentElement.lang=this.locale==="zh"?"zh-CN":"en"}};function Et(){return typeof navigator>"u"?[]:navigator.languages?.length?navigator.languages:[navigator.language].filter(Boolean)}var J=new Ie;function t(e,n){return J.t(e,n)}function w(e){return e.replace(/[&<>"']/g,n=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[n])}function te(e){if(!e)return"-";let n=Date.now()-e;return n<6e4?t("common.now"):n<36e5?Math.floor(n/6e4)+"m":n<864e5?Math.floor(n/36e5)+"h":Math.floor(n/864e5)+"d"}var Te={chats:[],bots:[]};async function Mt(){try{let e=await fetch("/api/groups");if(!e.ok)return;Te=await e.json()}catch{}}function At(e){return`status status-${w(e||"unknown")}`}function Ct(e){return`<li class="overview-list-row">
|
|
2
12
|
<div>
|
|
3
|
-
<strong>${
|
|
4
|
-
<span>${
|
|
13
|
+
<strong>${w(e.title??e.sessionId)}</strong>
|
|
14
|
+
<span>${w(e.botName??"")} \xB7 ${w(e.cliId??"unknown")}</span>
|
|
5
15
|
</div>
|
|
6
|
-
<span class="${
|
|
7
|
-
</li>`}function
|
|
16
|
+
<span class="${At(e.status)}">${w(e.status??"unknown")}</span>
|
|
17
|
+
</li>`}function Ht(e){let n=e.nextRunAt?new Date(e.nextRunAt).toLocaleString():"-";return`<li class="overview-list-row">
|
|
8
18
|
<div>
|
|
9
|
-
<strong>${
|
|
10
|
-
<span>${
|
|
19
|
+
<strong>${w(e.name??e.id)}</strong>
|
|
20
|
+
<span>${w(e.botName??e.larkAppId??"")} \xB7 ${w(e.parsed?.display??"")}</span>
|
|
11
21
|
</div>
|
|
12
|
-
<span>${
|
|
13
|
-
</li>`}async function
|
|
22
|
+
<span>${w(n)}</span>
|
|
23
|
+
</li>`}async function Ue(e){e.innerHTML=`<section class="page hero-page">
|
|
14
24
|
<div class="page-heading">
|
|
15
25
|
<div>
|
|
16
|
-
<p class="eyebrow">${
|
|
17
|
-
<h1>${
|
|
18
|
-
<p>${
|
|
26
|
+
<p class="eyebrow">${t("app.subtitle")}</p>
|
|
27
|
+
<h1>${t("overview.title")}</h1>
|
|
28
|
+
<p>${t("overview.subtitle")}</p>
|
|
19
29
|
</div>
|
|
20
30
|
</div>
|
|
21
31
|
<div class="metric-grid" id="overview-metrics"></div>
|
|
@@ -23,251 +33,251 @@
|
|
|
23
33
|
<section class="panel">
|
|
24
34
|
<header class="panel-header">
|
|
25
35
|
<div>
|
|
26
|
-
<h2>${
|
|
27
|
-
<p>${
|
|
36
|
+
<h2>${t("overview.recentSessions")}</h2>
|
|
37
|
+
<p>${t("sessions.subtitle")}</p>
|
|
28
38
|
</div>
|
|
29
|
-
<a class="btn-link" href="#/sessions">${
|
|
39
|
+
<a class="btn-link" href="#/sessions">${t("nav.sessions")}</a>
|
|
30
40
|
</header>
|
|
31
41
|
<ul class="overview-list" id="recent-sessions"></ul>
|
|
32
42
|
</section>
|
|
33
43
|
<section class="panel">
|
|
34
44
|
<header class="panel-header">
|
|
35
45
|
<div>
|
|
36
|
-
<h2>${
|
|
37
|
-
<p>${
|
|
46
|
+
<h2>${t("overview.nextSchedules")}</h2>
|
|
47
|
+
<p>${t("schedules.subtitle")}</p>
|
|
38
48
|
</div>
|
|
39
|
-
<a class="btn-link" href="#/schedules">${
|
|
49
|
+
<a class="btn-link" href="#/schedules">${t("nav.schedules")}</a>
|
|
40
50
|
</header>
|
|
41
51
|
<ul class="overview-list" id="next-schedules"></ul>
|
|
42
52
|
</section>
|
|
43
53
|
</div>
|
|
44
|
-
</section>`;let
|
|
45
|
-
<span>${
|
|
46
|
-
<strong>${
|
|
47
|
-
<small>${
|
|
48
|
-
</article>`).join("");let
|
|
49
|
-
<span class="filter-check-label">${
|
|
50
|
-
${
|
|
54
|
+
</section>`;let n=e.querySelector("#overview-metrics"),o=e.querySelector("#recent-sessions"),r=e.querySelector("#next-schedules");function s(){let a=[...D.sessions.values()],c=[...D.schedules.values()],p=a.filter(L=>L.status!=="closed"),y=a.filter(L=>L.status==="working"||L.status==="analyzing"||L.status==="starting"),S=c.filter(L=>L.enabled),v=Te.bots?.length||new Set(a.map(L=>L.larkAppId).filter(Boolean)).size,T=[{label:t("overview.openSessions"),value:p.length,meta:`${a.length} ${t("overview.total")}`},{label:t("overview.workingSessions"),value:y.length,meta:`${p.length} ${t("overview.active")}`},{label:t("overview.onlineBots"),value:v,meta:t("overview.daemonRegistry")},{label:t("overview.schedules"),value:c.length,meta:`${S.length} ${t("overview.enabledSchedules")}`},{label:t("overview.groups"),value:Te.chats?.length??0,meta:t("overview.chatMatrix")}];n.innerHTML=T.map(L=>`<article class="metric-card">
|
|
55
|
+
<span>${w(L.label)}</span>
|
|
56
|
+
<strong>${L.value}</strong>
|
|
57
|
+
<small>${w(L.meta)}</small>
|
|
58
|
+
</article>`).join("");let l=a.sort((L,g)=>Number(g.lastMessageAt??0)-Number(L.lastMessageAt??0)).slice(0,6);o.innerHTML=l.length?l.map(L=>Ct({...L,title:L.title??`${te(L.lastMessageAt)} \xB7 ${L.sessionId}`})).join(""):`<li class="empty">${t("overview.noSessions")}</li>`;let I=c.filter(L=>L.nextRunAt).sort((L,g)=>Date.parse(L.nextRunAt)-Date.parse(g.nextRunAt)).slice(0,6);r.innerHTML=I.length?I.map(Ht).join(""):`<li class="empty">${t("overview.noSchedules")}</li>`}D.on(s),s(),Mt().then(s)}function G(e,n){return`<th data-sort="${e}" data-label="${w(n)}">${w(n)}</th>`}var Fe=["claude-code","codex","cursor","gemini","opencode","aiden","coco","unknown"];function Rt(){return`<div class="filter-check-group" role="group" aria-label="${t("sessions.cli")}">
|
|
59
|
+
<span class="filter-check-label">${t("sessions.cli")}</span>
|
|
60
|
+
${Fe.map(e=>`
|
|
51
61
|
<label class="filter-check">
|
|
52
|
-
<input type="checkbox" name="cli" value="${
|
|
53
|
-
<span>${
|
|
62
|
+
<input type="checkbox" name="cli" value="${w(e)}" checked>
|
|
63
|
+
<span>${w(e)}</span>
|
|
54
64
|
</label>
|
|
55
65
|
`).join("")}
|
|
56
|
-
</div>`}function
|
|
66
|
+
</div>`}function Ot(){return`<section class="page">
|
|
57
67
|
<div class="page-heading">
|
|
58
68
|
<div>
|
|
59
|
-
<p class="eyebrow">${
|
|
60
|
-
<h1>${
|
|
61
|
-
<p>${
|
|
69
|
+
<p class="eyebrow">${t("nav.sessions")}</p>
|
|
70
|
+
<h1>${t("sessions.title")}</h1>
|
|
71
|
+
<p>${t("sessions.subtitle")}</p>
|
|
62
72
|
</div>
|
|
63
73
|
</div>
|
|
64
74
|
<form id="filters" class="filters sessions-filters">
|
|
65
|
-
<input type="search" name="q" placeholder="${
|
|
75
|
+
<input type="search" name="q" placeholder="${t("sessions.search")}" />
|
|
66
76
|
<select name="status">
|
|
67
|
-
<option value="">${
|
|
77
|
+
<option value="">${t("sessions.anyStatus")}</option>
|
|
68
78
|
<option>starting</option><option>working</option><option>idle</option>
|
|
69
79
|
<option>analyzing</option><option>active</option><option>closed</option>
|
|
70
80
|
</select>
|
|
71
81
|
<select name="adopt">
|
|
72
|
-
<option value="">${
|
|
73
|
-
<option value="yes">${
|
|
74
|
-
<option value="no">${
|
|
82
|
+
<option value="">${t("sessions.adoptAny")}</option>
|
|
83
|
+
<option value="yes">${t("sessions.adoptYes")}</option>
|
|
84
|
+
<option value="no">${t("sessions.adoptNo")}</option>
|
|
75
85
|
</select>
|
|
76
|
-
<label class="filter-toggle"><input type="checkbox" name="active" checked> ${
|
|
77
|
-
${
|
|
86
|
+
<label class="filter-toggle"><input type="checkbox" name="active" checked> ${t("sessions.activeOnly")}</label>
|
|
87
|
+
${Rt()}
|
|
78
88
|
</form>
|
|
79
89
|
<div id="bulk-bar" class="bulk-bar" hidden>
|
|
80
90
|
<span id="bulk-count"></span>
|
|
81
|
-
<button type="button" id="bulk-close" class="contrast">${
|
|
82
|
-
<button type="button" id="bulk-clear">${
|
|
91
|
+
<button type="button" id="bulk-close" class="contrast">${t("sessions.closeSelected")}</button>
|
|
92
|
+
<button type="button" id="bulk-clear">${t("sessions.clearSelection")}</button>
|
|
83
93
|
</div>
|
|
84
94
|
<table id="sessions-table">
|
|
85
95
|
<thead><tr>
|
|
86
|
-
<th><input type="checkbox" id="select-all" title="${
|
|
87
|
-
${
|
|
88
|
-
${
|
|
89
|
-
${
|
|
90
|
-
${
|
|
91
|
-
${
|
|
92
|
-
${
|
|
93
|
-
${
|
|
94
|
-
${
|
|
95
|
-
<th>${
|
|
96
|
+
<th><input type="checkbox" id="select-all" title="${t("sessions.activeOnly")}"></th>
|
|
97
|
+
${G("botName",t("sessions.bot"))}
|
|
98
|
+
${G("cliId",t("sessions.cli"))}
|
|
99
|
+
${G("status",t("sessions.status"))}
|
|
100
|
+
${G("title",t("sessions.titleCol"))}
|
|
101
|
+
${G("workingDir",t("sessions.workingDir"))}
|
|
102
|
+
${G("spawnedAt",t("sessions.created"))}
|
|
103
|
+
${G("lastMessageAt",t("sessions.last"))}
|
|
104
|
+
${G("adopt",t("sessions.adopt"))}
|
|
105
|
+
<th>${t("sessions.actions")}</th>
|
|
96
106
|
</tr></thead>
|
|
97
107
|
<tbody></tbody>
|
|
98
108
|
</table>
|
|
99
109
|
<dialog id="drawer"></dialog>
|
|
100
|
-
</section>`}function
|
|
101
|
-
<td><input type="checkbox" class="row-select" ${
|
|
102
|
-
<td>${
|
|
103
|
-
<td><span class="badge cli-${
|
|
104
|
-
<td><span class="status status-${
|
|
105
|
-
<td>${
|
|
106
|
-
<td title="${
|
|
107
|
-
<td>${
|
|
108
|
-
<td>${
|
|
109
|
-
<td>${
|
|
110
|
-
<td><button class="open" type="button">${
|
|
111
|
-
</tr>`}function
|
|
110
|
+
</section>`}function We(e){e.innerHTML=Ot();let n=e.querySelector("#sessions-table tbody"),o=e.querySelector("#filters"),r=e.querySelector("#drawer"),s=e.querySelector("#select-all"),a=e.querySelector("#bulk-bar"),c=e.querySelector("#bulk-count"),p=e.querySelector("#bulk-close"),y=e.querySelector("#bulk-clear"),S=e.querySelector("#sessions-table"),v=new Set,T="lastMessageAt",l="desc";function I(d){let f=d.status==="closed",h=v.has(d.sessionId)?"checked":"";return`<tr data-id="${w(d.sessionId)}">
|
|
111
|
+
<td><input type="checkbox" class="row-select" ${h} ${f?"disabled":""}></td>
|
|
112
|
+
<td>${w(d.botName??"")}</td>
|
|
113
|
+
<td><span class="badge cli-${w(d.cliId??"unknown")}">${w(d.cliId??"unknown")}</span></td>
|
|
114
|
+
<td><span class="status status-${w(d.status??"unknown")}">${w(d.status??"unknown")}</span></td>
|
|
115
|
+
<td>${w((d.title??"").slice(0,48))}</td>
|
|
116
|
+
<td title="${w(d.workingDir??"")}">${w((d.workingDir??"").slice(-34))}</td>
|
|
117
|
+
<td>${te(d.spawnedAt)}</td>
|
|
118
|
+
<td>${te(d.lastMessageAt)}</td>
|
|
119
|
+
<td>${d.adopt?'<span class="badge">adopt</span>':""}</td>
|
|
120
|
+
<td><button class="open" type="button">${t("sessions.details")}</button></td>
|
|
121
|
+
</tr>`}function L(){let d=new FormData(o),f=(d.get("q")??"").toLowerCase(),h=d.getAll("cli"),E=h.length>0&&h.length<Fe.length,C=d.get("status"),R=d.get("adopt"),q=!!d.get("active"),P=[...D.sessions.values()].filter(x=>!E||h.includes(x.cliId??"unknown")).filter(x=>!C||x.status===C).filter(x=>!R||R==="yes"==!!x.adopt).filter(x=>!q||x.status!=="closed").filter(x=>!f||JSON.stringify(x).toLowerCase().includes(f));return P.sort(u),P}function g(d,f){return f==="spawnedAt"||f==="lastMessageAt"?Number(d[f]??0):f==="adopt"?!!d.adopt:String(d[f]??"").toLowerCase()}function u(d,f){let h=g(d,T),E=g(f,T),C=0;return typeof h=="number"&&typeof E=="number"?C=h-E:typeof h=="boolean"&&typeof E=="boolean"?C=Number(h)-Number(E):C=String(h).localeCompare(String(E)),C===0&&(C=Number(d.lastMessageAt??0)-Number(f.lastMessageAt??0)),l==="asc"?C:-C}function $(){S.querySelectorAll("th[data-sort]").forEach(d=>{let f=d.dataset.sort===T;d.classList.toggle("sorted",f),d.setAttribute("aria-sort",f?l==="asc"?"ascending":"descending":"none");let h=d.dataset.label??d.textContent?.trim()??"";d.textContent=f?`${h} ${l==="asc"?"\u25B2":"\u25BC"}`:h})}function k(d){a.hidden=v.size===0,c.textContent=t("sessions.selectedCount",{count:v.size});let f=d.filter(E=>E.status!=="closed");if(f.length===0){s.checked=!1,s.indeterminate=!1,s.disabled=!0;return}s.disabled=!1;let h=f.filter(E=>v.has(E.sessionId)).length;s.checked=h===f.length,s.indeterminate=h>0&&h<f.length}function b(){let d=L();for(let f of[...v]){let h=D.sessions.get(f);(!h||h.status==="closed")&&v.delete(f)}n.innerHTML=d.length?d.map(I).join(""):`<tr><td colspan="10" class="empty">${t("sessions.empty")}</td></tr>`,$(),k(d)}function A(d){let f=d.status==="closed";r.innerHTML=`<article>
|
|
112
122
|
<header>
|
|
113
|
-
<h3>${
|
|
114
|
-
<span class="status status-${
|
|
115
|
-
<p><code>${
|
|
123
|
+
<h3>${w(d.title??d.sessionId)}</h3>
|
|
124
|
+
<span class="status status-${w(d.status??"unknown")}">${w(d.status??"unknown")}</span>
|
|
125
|
+
<p><code>${w(d.sessionId)}</code> <button data-copy="${w(d.sessionId)}">${t("sessions.copy")}</button></p>
|
|
116
126
|
</header>
|
|
117
|
-
<p><b>${
|
|
118
|
-
<p><b>chatId:</b> <code>${
|
|
119
|
-
<p><b>rootMessageId:</b> <code>${
|
|
120
|
-
${
|
|
121
|
-
<p><b>${
|
|
127
|
+
<p><b>${t("sessions.bot")}:</b> ${w(d.botName??"-")} \xB7 <b>${t("sessions.cli")}:</b> ${w(d.cliId??"?")}</p>
|
|
128
|
+
<p><b>chatId:</b> <code>${w(d.chatId??"")}</code> <button data-copy="${w(d.chatId??"")}">${t("sessions.copy")}</button></p>
|
|
129
|
+
<p><b>rootMessageId:</b> <code>${w(d.rootMessageId??"")}</code> <button data-copy="${w(d.rootMessageId??"")}">${t("sessions.copy")}</button></p>
|
|
130
|
+
${d.threadId?`<p><b>threadId:</b> <code>${w(d.threadId)}</code></p>`:""}
|
|
131
|
+
<p><b>${t("sessions.workingDir")}:</b> ${w(d.workingDir??"-")}</p>
|
|
122
132
|
<div class="actions">
|
|
123
|
-
<button id="locate-btn" type="button">${
|
|
124
|
-
${
|
|
125
|
-
${
|
|
126
|
-
${
|
|
133
|
+
<button id="locate-btn" type="button">${t("sessions.locate")}</button>
|
|
134
|
+
${d.webPort?`<a class="btn-link primary" href="http://${w(location.hostname)}:${d.webPort}" target="_blank" rel="noopener">${t("sessions.openTerminal")}</a>`:""}
|
|
135
|
+
${f?`<button id="resume-btn" type="button" class="primary">${t("sessions.resume")}</button>`:""}
|
|
136
|
+
${f?"":`<button id="close-btn" type="button" class="contrast">${t("sessions.close")}</button>`}
|
|
127
137
|
</div>
|
|
128
|
-
<form method="dialog"><button>${
|
|
129
|
-
</article>`,
|
|
138
|
+
<form method="dialog"><button>${t("sessions.dismiss")}</button></form>
|
|
139
|
+
</article>`,r.querySelectorAll("[data-copy]").forEach(R=>{R.onclick=()=>{navigator.clipboard.writeText(R.dataset.copy??""),R.textContent=t("sessions.copied"),setTimeout(()=>{R.textContent=t("sessions.copy")},800)}});let h=r.querySelector("#locate-btn");h&&(h.onclick=async()=>{h.disabled=!0,h.textContent=t("sessions.locating");try{let R=await fetch(`/api/sessions/${encodeURIComponent(d.sessionId)}/locate`,{method:"POST"}),q=await R.json();if(q.ok){let P=30;h.textContent=t("sessions.cooldown",{seconds:P});let x=setInterval(()=>{P-=1,P<=0?(clearInterval(x),h.disabled=!1,h.textContent=t("sessions.locate")):h.textContent=t("sessions.cooldown",{seconds:P})},1e3)}else alert(`Locate failed: ${q.error??R.status}`),h.disabled=!1,h.textContent=t("sessions.locate")}catch(R){alert(`Locate error: ${R}`),h.disabled=!1,h.textContent=t("sessions.locate")}});let E=r.querySelector("#resume-btn");E&&(E.onclick=async()=>{E.disabled=!0;try{let R=await fetch(`/api/sessions/${encodeURIComponent(d.sessionId)}/resume`,{method:"POST"}),q=await R.json().catch(()=>({}));if(!R.ok||q.ok===!1){alert(`${t("sessions.resumeFailed")}: ${q?.error??R.status}`),E.disabled=!1;return}r.close()}catch(R){alert(`${t("sessions.resumeFailed")}: ${R}`),E.disabled=!1}});let C=r.querySelector("#close-btn");C&&(C.onclick=async()=>{if(confirm(t("sessions.closeConfirm"))){C.disabled=!0;try{await fetch(`/api/sessions/${encodeURIComponent(d.sessionId)}/close`,{method:"POST"})}finally{r.close()}}}),r.showModal()}n.addEventListener("click",d=>{let f=d.target;if(f.classList.contains("row-select")){let R=f.closest("tr[data-id]");if(!R)return;f.checked?v.add(R.dataset.id):v.delete(R.dataset.id),k(L());return}let h=f.closest("td");if(h&&h.querySelector(".row-select"))return;let E=f.closest("tr[data-id]");if(!E)return;let C=D.sessions.get(E.dataset.id);C&&A(C)}),s.addEventListener("change",()=>{let d=L().filter(f=>f.status!=="closed");for(let f of d)s.checked?v.add(f.sessionId):v.delete(f.sessionId);b()}),y.addEventListener("click",()=>{v.clear(),b()}),p.addEventListener("click",async()=>{let d=[...v];if(d.length===0||!confirm(t("sessions.closeBulkConfirm",{count:d.length})))return;p.disabled=!0,y.disabled=!0;let f=p.textContent,h=0,E=0,C=[...d];p.textContent=`0/${d.length}`;async function R(){for(;C.length;){let q=C.shift();try{let P=await fetch(`/api/sessions/${encodeURIComponent(q)}/close`,{method:"POST"}),x=await P.json().catch(()=>({}));(!P.ok||x?.ok===!1)&&(E+=1)}catch{E+=1}finally{h+=1,p.textContent=`${h}/${d.length}`}}}await Promise.all(Array.from({length:Math.min(6,d.length)},()=>R())),p.textContent=f,p.disabled=!1,y.disabled=!1,v.clear(),b(),E>0&&alert(`Failed: ${E}/${d.length}`)}),S.querySelectorAll("th[data-sort]").forEach(d=>{d.addEventListener("click",()=>{let f=d.dataset.sort;T===f?l=l==="asc"?"desc":"asc":(T=f,l=f==="spawnedAt"||f==="lastMessageAt"?"desc":"asc"),b()})}),o.addEventListener("input",b),D.on(b),b()}function xt(){return`<section class="page">
|
|
130
140
|
<div class="page-heading">
|
|
131
141
|
<div>
|
|
132
|
-
<p class="eyebrow">${
|
|
133
|
-
<h1>${
|
|
134
|
-
<p>${
|
|
142
|
+
<p class="eyebrow">${t("nav.schedules")}</p>
|
|
143
|
+
<h1>${t("schedules.title")}</h1>
|
|
144
|
+
<p>${t("schedules.subtitle")}</p>
|
|
135
145
|
</div>
|
|
136
146
|
</div>
|
|
137
147
|
<form id="sched-filters" class="filters">
|
|
138
|
-
<input type="search" name="q" placeholder="${
|
|
148
|
+
<input type="search" name="q" placeholder="${t("schedules.search")}" />
|
|
139
149
|
<select name="kind">
|
|
140
|
-
<option value="">${
|
|
150
|
+
<option value="">${t("schedules.anyKind")}</option>
|
|
141
151
|
<option>cron</option>
|
|
142
152
|
<option>interval</option>
|
|
143
153
|
<option>once</option>
|
|
144
154
|
</select>
|
|
145
|
-
<label><input type="checkbox" name="enabled"> ${
|
|
155
|
+
<label><input type="checkbox" name="enabled"> ${t("schedules.enabledOnly")}</label>
|
|
146
156
|
</form>
|
|
147
157
|
<table>
|
|
148
158
|
<thead><tr>
|
|
149
|
-
<th>${
|
|
150
|
-
<th>${
|
|
159
|
+
<th>${t("schedules.name")}</th><th>${t("schedules.bot")}</th><th>${t("schedules.schedule")}</th><th>${t("schedules.next")}</th><th>${t("schedules.last")}</th>
|
|
160
|
+
<th>${t("schedules.repeat")}</th><th>${t("schedules.enabled")}</th><th>${t("schedules.actions")}</th>
|
|
151
161
|
</tr></thead>
|
|
152
162
|
<tbody id="schedules-tbody"></tbody>
|
|
153
163
|
</table>
|
|
154
|
-
</section>`}function
|
|
155
|
-
<td>${
|
|
156
|
-
<td>${
|
|
157
|
-
<td><code>${
|
|
158
|
-
<td>${
|
|
159
|
-
<td>${
|
|
160
|
-
<td>${
|
|
161
|
-
<td>${
|
|
164
|
+
</section>`}function _e(e){if(!e)return"\u2014";try{return new Date(e).toLocaleString()}catch{return e}}function Je(e){e.innerHTML=xt();let n=e.querySelector("#schedules-tbody"),o=e.querySelector("#sched-filters");function r(){let a=new FormData(o),c=(a.get("q")??"").toLowerCase(),p=a.get("kind"),y=!!a.get("enabled");return[...D.schedules.values()].filter(S=>!p||S.parsed?.kind===p).filter(S=>!y||S.enabled).filter(S=>!c||JSON.stringify(S).toLowerCase().includes(c)).sort((S,v)=>{if(S.enabled!==v.enabled)return S.enabled?-1:1;let T=S.nextRunAt?Date.parse(S.nextRunAt):1/0,l=v.nextRunAt?Date.parse(v.nextRunAt):1/0;return T-l})}function s(){n.innerHTML=r().map(a=>`<tr data-id="${w(a.id)}">
|
|
165
|
+
<td>${w(a.name??a.id)}</td>
|
|
166
|
+
<td>${w(a.botName??a.larkAppId??"-")}</td>
|
|
167
|
+
<td><code>${w(a.parsed?.display??"?")}</code></td>
|
|
168
|
+
<td>${_e(a.nextRunAt)}</td>
|
|
169
|
+
<td>${_e(a.lastRunAt)} ${a.lastStatus==="error"?"\u26A0\uFE0F":""}</td>
|
|
170
|
+
<td>${a.repeat?`${a.repeat.completed}/${a.repeat.times??"\u221E"}`:"\u2014"}</td>
|
|
171
|
+
<td>${a.enabled?"\u2713":"\u2717"}</td>
|
|
162
172
|
<td class="actions-cell">
|
|
163
|
-
<button data-op="run" type="button">${
|
|
164
|
-
${
|
|
173
|
+
<button data-op="run" type="button">${t("schedules.runNow")}</button>
|
|
174
|
+
${a.enabled?`<button data-op="pause" type="button">${t("schedules.pause")}</button>`:`<button data-op="resume" type="button">${t("schedules.resume")}</button>`}
|
|
165
175
|
</td>
|
|
166
|
-
</tr>`).join("")||`<tr><td colspan="8" class="empty">${
|
|
176
|
+
</tr>`).join("")||`<tr><td colspan="8" class="empty">${t("schedules.empty")}</td></tr>`}n.addEventListener("click",async a=>{let c=a.target.closest("button[data-op]");if(!c)return;let p=c.closest("tr[data-id]");if(!p)return;let y=p.dataset.id,S=c.dataset.op;c.disabled=!0;let v=c.textContent;c.textContent="...";try{let T=await fetch(`/api/schedules/${encodeURIComponent(y)}/${S}`,{method:"POST"}),l=await T.json().catch(()=>({}));(!T.ok||l.ok===!1)&&alert(`Failed: ${T.status} ${l?.error??""}`.trim())}catch(T){alert("Network error: "+T)}finally{c.disabled=!1,c.textContent=v}}),o.addEventListener("input",s),D.on(s),s()}var B={chats:[],bots:[]};function Dt(){return`<section class="page">
|
|
167
177
|
<div class="page-heading">
|
|
168
178
|
<div>
|
|
169
|
-
<p class="eyebrow">${
|
|
170
|
-
<h1>${
|
|
171
|
-
<p>${
|
|
179
|
+
<p class="eyebrow">${t("nav.groups")}</p>
|
|
180
|
+
<h1>${t("groups.title")}</h1>
|
|
181
|
+
<p>${t("groups.subtitle")}</p>
|
|
172
182
|
</div>
|
|
173
183
|
</div>
|
|
174
184
|
<form id="g-filters" class="filters">
|
|
175
|
-
<input type="search" name="q" placeholder="${
|
|
176
|
-
<label><input type="checkbox" name="missing"> ${
|
|
177
|
-
<button type="button" id="g-refresh">${
|
|
178
|
-
<button type="button" id="g-create" class="primary">${
|
|
185
|
+
<input type="search" name="q" placeholder="${t("groups.search")}" />
|
|
186
|
+
<label><input type="checkbox" name="missing"> ${t("groups.missingOnly")}</label>
|
|
187
|
+
<button type="button" id="g-refresh">${t("groups.refresh")}</button>
|
|
188
|
+
<button type="button" id="g-create" class="primary">${t("groups.create")}</button>
|
|
179
189
|
</form>
|
|
180
190
|
<table>
|
|
181
191
|
<thead id="g-head"></thead>
|
|
182
192
|
<tbody id="g-body"></tbody>
|
|
183
193
|
</table>
|
|
184
194
|
<dialog id="g-drawer"></dialog>
|
|
185
|
-
</section>`}async function
|
|
195
|
+
</section>`}async function Y(){B=await(await fetch("/api/groups")).json()}async function qt(){return(await fetch("/api/groups")).json()}function Pt(e,n){if(n.size===0)return!0;let o=e?.memberBots??[];for(let r of n)if(!o.some(s=>s.larkAppId===r&&s.inChat))return!1;return!0}function Ge(e,n){return e.filter(o=>!n||!n.has(o.larkAppId)).map(o=>`
|
|
186
196
|
<label class="checkbox-row">
|
|
187
|
-
<input type="checkbox" name="bot" value="${o
|
|
188
|
-
${o
|
|
197
|
+
<input type="checkbox" name="bot" value="${w(o.larkAppId)}">
|
|
198
|
+
${w(o.botName??o.larkAppId)} <small>(${w(o.larkAppId)})</small>
|
|
189
199
|
</label>
|
|
190
|
-
`).join("")}async function
|
|
200
|
+
`).join("")}async function ze(e){e.innerHTML=Dt();let n=e.querySelector("#g-head"),o=e.querySelector("#g-body"),r=e.querySelector("#g-filters"),s=e.querySelector("#g-refresh"),a=e.querySelector("#g-drawer");s.onclick=async()=>{s.disabled=!0;try{await Y(),v()}finally{s.disabled=!1}};let c=e.querySelector("#g-create");c.onclick=()=>p(),await Y();function p(){let l=B.bots;if(l.length===0){alert(t("groups.noBotsOnline"));return}a.innerHTML=`
|
|
191
201
|
<article>
|
|
192
|
-
<header><h3>${
|
|
193
|
-
<p>${
|
|
202
|
+
<header><h3>${t("groups.createTitle")}</h3></header>
|
|
203
|
+
<p>${t("groups.createHelp")}</p>
|
|
194
204
|
<form id="g-createform">
|
|
195
205
|
<label class="form-row">
|
|
196
|
-
<span>${
|
|
197
|
-
<input type="text" name="name" placeholder="${
|
|
206
|
+
<span>${t("groups.name")}</span>
|
|
207
|
+
<input type="text" name="name" placeholder="${t("groups.namePlaceholder")}" maxlength="60">
|
|
198
208
|
</label>
|
|
199
209
|
<label class="form-row">
|
|
200
|
-
<span>${
|
|
210
|
+
<span>${t("groups.bindDir")}</span>
|
|
201
211
|
<input type="text" name="bindWorkingDir" placeholder="e.g. ~/projects/botmux">
|
|
202
|
-
<small>${
|
|
212
|
+
<small>${t("groups.bindDirHelp")}</small>
|
|
203
213
|
</label>
|
|
204
214
|
<fieldset>
|
|
205
|
-
<legend>${
|
|
206
|
-
${
|
|
215
|
+
<legend>${t("groups.botPicker")}</legend>
|
|
216
|
+
${Ge(l)}
|
|
207
217
|
</fieldset>
|
|
208
218
|
<div class="actions">
|
|
209
|
-
<button type="submit" class="primary">${
|
|
210
|
-
<button type="button" id="g-create-cancel">${
|
|
219
|
+
<button type="submit" class="primary">${t("groups.createSubmit")}</button>
|
|
220
|
+
<button type="button" id="g-create-cancel">${t("groups.cancel")}</button>
|
|
211
221
|
</div>
|
|
212
222
|
</form>
|
|
213
|
-
</article>`,
|
|
223
|
+
</article>`,a.showModal(),a.querySelector("#g-create-cancel").onclick=()=>a.close(),a.querySelector("#g-createform").onsubmit=async g=>{g.preventDefault();let u=new FormData(g.target),$=(u.get("name")??"").trim(),k=(u.get("bindWorkingDir")??"").trim(),b=u.getAll("bot");if(b.length===0){alert("Pick at least one bot.");return}let A=g.target.querySelector("button[type=submit]");A&&(A.disabled=!0,A.textContent="Creating...");try{let d=await fetch("/api/groups/create",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({name:$||void 0,larkAppIds:b,bindWorkingDir:k||void 0})}),f=await d.json();if(f.ok&&f.chatId){y(f);let h=Array.isArray(f.invalidBotIds)?f.invalidBotIds:[],E=b.filter(R=>!h.includes(R)),C=new Set(E);typeof f.creator=="string"&&f.creator&&C.add(f.creator),I(f.chatId,$||f.chatId,E,f.creator),v(),L(f.chatId,C).catch(()=>{})}else alert(`Failed: ${f.error??d.status}`),a.close()}catch(d){alert("Network error: "+d),a.close()}};function I(g,u,$,k){let b=new Set($);k&&b.add(k);let A=B.bots.map(f=>({larkAppId:f.larkAppId,botName:f.botName,inChat:b.has(f.larkAppId),oncallChat:null})),d={chatId:g,name:u,ownerId:k??null,memberBots:A};B.chats=[d,...B.chats.filter(f=>f.chatId!==g)]}async function L(g,u){let $=[600,1200,1200,1200,1200,1200];for(let k of $){await new Promise(d=>setTimeout(d,k));let b;try{b=await qt()}catch{continue}let A=(b.chats??[]).find(d=>d.chatId===g);if(A&&Pt(A,u)){B=b,v();return}}}}function y(l){let I=String(l.chatId),L=`https://applink.feishu.cn/client/chat/open?openChatId=${encodeURIComponent(I)}`,g=l.invalidBotIds??[],u=l.invalidUserIds??[],$=l.autoInvitedOpenId,k=!!l.autoInviteRejected,b=l.ownerTransferredTo,A=l.transferError,d=l.notifyMessageId,f=l.notifyError,h=Array.isArray(l.oncallBindings)?l.oncallBindings:[],E=h.filter(x=>x?.ok).length,C=h.filter(x=>!x?.ok),R=h.length>0?C.length===0?`<p class="hint-ok">\u5DF2\u7ED1\u5B9A\u76EE\u5F55\uFF1A<code>${w(l.bindResolvedPath??"")}</code>\uFF08${E}/${h.length} bots\uFF09</p>`:`<p class="hint-warn">\u76EE\u5F55\u7ED1\u5B9A\u90E8\u5206\u5931\u8D25\uFF1A\u6210\u529F ${E}/${h.length}\u3002${C.map(x=>`<br><code>${w(x.larkAppId??"?")}</code>: ${w(x.error??"unknown")}`).join("")}</p>`:"",q;if($){let x=b?"<br><small>\u7FA4\u4E3B\u5DF2\u4ECE\u673A\u5668\u4EBA\u8F6C\u8BA9\u7ED9\u4F60\u3002</small>":A?`<br><small class="hint-warn-inline">\u26A0 \u81EA\u52A8\u8F6C\u8BA9\u7FA4\u4E3B\u5931\u8D25\uFF08${w(A)}\uFF09\uFF0C\u4F60\u73B0\u5728\u662F\u6210\u5458\u4F46\u7FA4\u4E3B\u4ECD\u662F\u673A\u5668\u4EBA\u3002</small>`:"",be=d?`<br><small>\u673A\u5668\u4EBA\u5DF2\u5728\u7FA4\u91CC @ \u4E86\u4F60\uFF08\u6D88\u606F id <code>${w(d)}</code>\uFF09\uFF0C\u770B\u98DE\u4E66\u901A\u77E5\u5C31\u80FD\u8FDB\u7FA4\u3002</small>`:f?`<br><small class="hint-warn-inline">\u26A0 \u81EA\u52A8 @ \u901A\u77E5\u5931\u8D25\uFF08${w(f)}\uFF09\uFF0C\u65B0\u7FA4\u53EF\u80FD\u4E0D\u4F1A\u4E3B\u52A8\u51FA\u73B0\u5728\u4F60\u4FA7\u8FB9\u680F\uFF0C\u5EFA\u8BAE\u4ECE\u4E0B\u9762\u6309\u94AE\u8DF3\u8FDB\u53BB\u3002</small>`:"";q=`<p class="hint-ok">\u5DF2\u81EA\u52A8\u9080\u8BF7\u4F60\uFF08<code>${w($)}</code>\uFF09\u4F5C\u4E3A\u6210\u5458\u3002${x}${be}</p>`}else k?q='<p class="hint-warn">\u98DE\u4E66\u62D2\u7EDD\u4E86\u81EA\u52A8\u9080\u8BF7\uFF08\u4F60\u7684 open_id \u5728\u521B\u5EFA\u8005 bot \u7684 scope \u4E0B\u4E0D\u53EF\u7528\uFF09\u3002<strong>\u4F60\u76EE\u524D\u4E0D\u662F\u65B0\u7FA4\u6210\u5458</strong>\uFF0C\u9700\u8981\u8BA9\u7FA4\u91CC\u7684\u67D0\u4E2A\u673A\u5668\u4EBA\u624B\u52A8\u628A\u4F60\u52A0\u8FDB\u6765\u3002</p>':q='<p class="hint-warn">\u6CA1\u5728 dashboard \u7F13\u5B58\u91CC\u627E\u5230 ownerOpenId\uFF0C<strong>\u6CA1\u6709\u81EA\u52A8\u9080\u8BF7\u4F60</strong>\u3002\u70B9\u5F00\u4E0B\u9762\u94FE\u63A5\u524D\uFF0C\u5148\u8BA9\u7FA4\u91CC\u4EFB\u4E00\u673A\u5668\u4EBA\u624B\u52A8\u628A\u4F60\u52A0\u8FDB\u53BB\u3002</p>';let P=[g.length?`<li>\u65E0\u6548 bot id: <code>${g.map(w).join(", ")}</code></li>`:"",u.length?`<li>\u65E0\u6548\u7528\u6237 open_id: <code>${u.map(w).join(", ")}</code></li>`:""].filter(Boolean).join("");a.innerHTML=`
|
|
214
224
|
<article>
|
|
215
|
-
<header><h3>${
|
|
216
|
-
<p><b>chatId:</b> <code>${
|
|
217
|
-
<p><b>\u521B\u5EFA\u8005:</b> <code>${
|
|
218
|
-
${
|
|
219
|
-
${
|
|
220
|
-
${
|
|
225
|
+
<header><h3>${t("groups.successTitle")}</h3></header>
|
|
226
|
+
<p><b>chatId:</b> <code>${w(I)}</code> <button type="button" data-copy="${w(I)}">${t("sessions.copy")}</button></p>
|
|
227
|
+
<p><b>\u521B\u5EFA\u8005:</b> <code>${w(l.creator??"?")}</code></p>
|
|
228
|
+
${q}
|
|
229
|
+
${R}
|
|
230
|
+
${P?`<ul>${P}</ul>`:""}
|
|
221
231
|
<div class="actions">
|
|
222
|
-
<a class="btn-link primary" href="${
|
|
223
|
-
<button type="button" id="g-create-close">${
|
|
232
|
+
<a class="btn-link primary" href="${L}" target="_blank" rel="noopener">${t("groups.openGroup")}</a>
|
|
233
|
+
<button type="button" id="g-create-close">${t("sessions.dismiss")}</button>
|
|
224
234
|
</div>
|
|
225
|
-
</article>`,
|
|
226
|
-
<th>${
|
|
227
|
-
${
|
|
228
|
-
<th>${
|
|
229
|
-
</tr>`}function
|
|
235
|
+
</article>`,a.querySelectorAll("[data-copy]").forEach(x=>{x.onclick=()=>{navigator.clipboard.writeText(x.dataset.copy??""),x.textContent=t("sessions.copied"),setTimeout(()=>{x.textContent=t("sessions.copy")},800)}}),a.querySelector("#g-create-close").onclick=()=>a.close()}function S(){n.innerHTML=`<tr>
|
|
236
|
+
<th>${t("groups.chat")}</th>
|
|
237
|
+
${B.bots.map(l=>`<th>${w(l.botName??l.larkAppId)}</th>`).join("")}
|
|
238
|
+
<th>${t("groups.actions")}</th>
|
|
239
|
+
</tr>`}function v(){S();let l=new FormData(r),I=(l.get("q")??"").toLowerCase(),L=!!l.get("missing"),g=B.chats.filter(u=>!I||(u.name??"").toLowerCase().includes(I)||u.chatId.toLowerCase().includes(I)||(u.ownerId??"").toLowerCase().includes(I)).filter(u=>!L||u.memberBots.some($=>!$.inChat));if(g.length===0){o.innerHTML=`<tr><td colspan="${B.bots.length+2}" class="empty">${t("groups.empty")}</td></tr>`;return}o.innerHTML=g.map(u=>`<tr data-chat="${w(u.chatId)}">
|
|
230
240
|
<td>
|
|
231
|
-
<strong>${
|
|
232
|
-
<small><code>${
|
|
241
|
+
<strong>${w(u.name??u.chatId)}</strong><br>
|
|
242
|
+
<small><code>${w(u.chatId)}</code></small>
|
|
233
243
|
</td>
|
|
234
|
-
${
|
|
244
|
+
${B.bots.map($=>{let k=u.memberBots.find(d=>d.larkAppId===$.larkAppId),b=k?k.error?"!":k.inChat?"\u2713":"\u2717":"?";return`<td class="${k?k.error?"cell-error":k.inChat?"cell-in":"cell-out":"cell-unknown"}" title="${w(k?.error??"")}">${b}</td>`}).join("")}
|
|
235
245
|
<td>
|
|
236
|
-
<button class="add-bots" type="button">${
|
|
237
|
-
<button class="manage-chat" type="button">${
|
|
246
|
+
<button class="add-bots" type="button">${t("groups.addBots")}</button>
|
|
247
|
+
<button class="manage-chat" type="button">${t("groups.manage")}</button>
|
|
238
248
|
</td>
|
|
239
|
-
</tr>`).join("")}
|
|
249
|
+
</tr>`).join("")}v(),o.addEventListener("click",async l=>{let I=l.target.closest("button.add-bots");if(!I)return;let g=I.closest("tr[data-chat]").dataset.chat,u=B.chats.find(b=>b.chatId===g);if(!u)return;let $=new Set(u.memberBots.filter(b=>b.inChat).map(b=>b.larkAppId));if(!B.bots.filter(b=>!$.has(b.larkAppId)).length){alert("All configured bots are already in this chat.");return}a.innerHTML=`
|
|
240
250
|
<article>
|
|
241
|
-
<header><h3>${
|
|
242
|
-
<p>${
|
|
251
|
+
<header><h3>${t("groups.addBots")} \xB7 ${w(u.name??u.chatId)}</h3></header>
|
|
252
|
+
<p>${t("groups.createHelp")}</p>
|
|
243
253
|
<form id="g-addform">
|
|
244
|
-
${
|
|
254
|
+
${Ge(B.bots,$)}
|
|
245
255
|
<div class="actions">
|
|
246
|
-
<button type="submit" class="primary">${
|
|
247
|
-
<button type="button" id="g-cancel">${
|
|
256
|
+
<button type="submit" class="primary">${t("groups.addBots")}</button>
|
|
257
|
+
<button type="button" id="g-cancel">${t("groups.cancel")}</button>
|
|
248
258
|
</div>
|
|
249
259
|
</form>
|
|
250
|
-
</article>`,
|
|
251
|
-
`);alert(
|
|
260
|
+
</article>`,a.showModal(),a.querySelector("#g-cancel").onclick=()=>a.close(),a.querySelector("#g-addform").onsubmit=async b=>{b.preventDefault();let d=new FormData(b.target).getAll("bot");if(d.length===0){alert("Pick at least one bot.");return}try{let h=await(await fetch(`/api/groups/${encodeURIComponent(g)}/add-bots`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({larkAppIds:d})})).json();if(h.error==="no_proxy_bot")alert("No bot is currently in this chat \u2014 add one manually in Feishu first, then retry.");else if(h.result){let E=h.result.map(C=>`${C.id}: ${C.ok?"OK":`failed (${C.error??"unknown"})`}`).join(`
|
|
261
|
+
`);alert(E),await Y(),v()}else alert(`Unexpected response: ${JSON.stringify(h)}`)}catch(f){alert("Network error: "+f)}finally{a.close()}}}),o.addEventListener("click",async l=>{let I=l.target.closest("button.manage-chat");if(!I)return;let g=I.closest("tr[data-chat]").dataset.chat,u=B.chats.find($=>$.chatId===g);u&&T(u)});function T(l){let I=l.memberBots.filter(g=>g.inChat),L=typeof l.ownerId=="string"?l.ownerId:"";a.innerHTML=`
|
|
252
262
|
<article>
|
|
253
|
-
<header><h3>${
|
|
254
|
-
<p><b>chatId:</b> <code>${
|
|
255
|
-
<p><b>${
|
|
263
|
+
<header><h3>${t("groups.manageTitle",{name:l.name??l.chatId})}</h3></header>
|
|
264
|
+
<p><b>chatId:</b> <code>${w(l.chatId)}</code></p>
|
|
265
|
+
<p><b>${t("groups.owner")}:</b> <code>${w(l.ownerId??t("common.unknown"))}</code></p>
|
|
256
266
|
|
|
257
267
|
<fieldset>
|
|
258
|
-
<legend>${
|
|
259
|
-
<p><small>${
|
|
260
|
-
${
|
|
261
|
-
<div class="oncall-row" data-bot="${
|
|
268
|
+
<legend>${t("groups.oncall")}</legend>
|
|
269
|
+
<p><small>${t("groups.oncallHelp")}</small></p>
|
|
270
|
+
${I.length===0?'<p class="empty">\u6CA1\u6709\u673A\u5668\u4EBA\u5728\u7FA4\u91CC</p>':I.map(g=>{let u=!!g.oncallChat,$=g.oncallChat?.workingDir??"";return`
|
|
271
|
+
<div class="oncall-row" data-bot="${w(g.larkAppId)}">
|
|
262
272
|
<label class="checkbox-row">
|
|
263
273
|
<input type="checkbox" data-action="toggle" ${u?"checked":""}>
|
|
264
|
-
<strong>${
|
|
265
|
-
<small>(${
|
|
274
|
+
<strong>${w(g.botName??g.larkAppId)}</strong>
|
|
275
|
+
<small>(${w(g.larkAppId)})</small>
|
|
266
276
|
</label>
|
|
267
277
|
<div class="oncall-row-body">
|
|
268
278
|
<input type="text" data-input="workingDir" placeholder="e.g. /root/iserver/botmux"
|
|
269
|
-
value="${
|
|
270
|
-
<button type="button" data-action="save">${
|
|
279
|
+
value="${w($)}" ${u?"":"disabled"}>
|
|
280
|
+
<button type="button" data-action="save">${t("groups.save")}</button>
|
|
271
281
|
<span class="oncall-status" data-status></span>
|
|
272
282
|
</div>
|
|
273
283
|
</div>
|
|
@@ -275,87 +285,372 @@
|
|
|
275
285
|
</fieldset>
|
|
276
286
|
|
|
277
287
|
<fieldset>
|
|
278
|
-
<legend>${
|
|
279
|
-
${
|
|
288
|
+
<legend>${t("groups.leaveTitle")}</legend>
|
|
289
|
+
${I.length===0?'<p class="empty">\u6CA1\u6709\u673A\u5668\u4EBA\u5728\u7FA4\u91CC</p>':I.map(g=>`
|
|
280
290
|
<label class="checkbox-row">
|
|
281
|
-
<input type="checkbox" name="leave-bot" value="${
|
|
282
|
-
${
|
|
283
|
-
<small>${
|
|
291
|
+
<input type="checkbox" name="leave-bot" value="${w(g.larkAppId)}">
|
|
292
|
+
${w(g.botName??g.larkAppId)}
|
|
293
|
+
<small>${g.larkAppId===L?"\xB7 \u7FA4\u4E3B":""}</small>
|
|
284
294
|
</label>
|
|
285
295
|
`).join("")}
|
|
286
296
|
</fieldset>
|
|
287
297
|
|
|
288
298
|
<div class="actions">
|
|
289
|
-
<button id="g-leave-btn" type="button" ${
|
|
290
|
-
<button id="g-disband-btn" type="button" class="contrast" ${
|
|
299
|
+
<button id="g-leave-btn" type="button" ${I.length===0?"disabled":""}>${t("groups.leaveSelected")}</button>
|
|
300
|
+
<button id="g-disband-btn" type="button" class="contrast" ${I.length===0?"disabled":""}>${t("groups.disband")}</button>
|
|
291
301
|
</div>
|
|
292
|
-
<p class="hint-warn"><small>${
|
|
293
|
-
<form method="dialog"><button>${
|
|
294
|
-
</article>`,
|
|
295
|
-
`);alert(
|
|
296
|
-
\u5173\u95ED\u4E86 ${
|
|
297
|
-
\u5173\u95ED\u4E86 ${
|
|
302
|
+
<p class="hint-warn"><small>${t("groups.dangerHint")}</small></p>
|
|
303
|
+
<form method="dialog"><button>${t("sessions.dismiss")}</button></form>
|
|
304
|
+
</article>`,a.showModal(),a.querySelectorAll(".oncall-row").forEach(g=>{let u=g.dataset.bot,$=g.querySelector("input[data-action=toggle]"),k=g.querySelector("input[data-input=workingDir]"),b=g.querySelector("button[data-action=save]"),A=g.querySelector("[data-status]");$.addEventListener("change",()=>{k.disabled=!$.checked,$.checked&&k.focus()}),b.addEventListener("click",async()=>{A.textContent="",A.className="oncall-status";let d=$.checked,f=k.value.trim();if(d&&!f){A.textContent=t("groups.needWorkingDir"),A.classList.add("hint-warn-inline");return}b.disabled=!0;try{let h=`/api/groups/${encodeURIComponent(l.chatId)}/oncall/${encodeURIComponent(u)}`,E=d?await fetch(h,{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({workingDir:f})}):await fetch(h,{method:"DELETE"}),C=await E.json().catch(()=>({}));if(E.ok&&C.ok){A.textContent=d?`\u2713 \u5DF2\u7ED1\u5B9A \u2192 ${C.resolvedPath??f}`:"\u2713 \u5DF2\u89E3\u7ED1",A.classList.add("hint-ok");try{await Y(),v()}catch{}}else A.textContent=`\u2717 ${C.error??E.status}`,A.classList.add("hint-warn-inline")}catch(h){A.textContent=`\u2717 ${h?.message??h}`,A.classList.add("hint-warn-inline")}finally{b.disabled=!1}})}),a.querySelector("#g-leave-btn").onclick=async()=>{let g=[...a.querySelectorAll("input[name=leave-bot]:checked")].map(u=>u.value);if(g.length===0){alert("\u81F3\u5C11\u9009\u4E00\u4E2A\u673A\u5668\u4EBA");return}if(confirm(`\u786E\u5B9A\u8BA9 ${g.length} \u4E2A\u673A\u5668\u4EBA\u9000\u51FA\u7FA4\u804A\uFF1F\u8BE5 bot \u5728\u6B64\u7FA4\u7684\u4F1A\u8BDD\u4F1A\u4E00\u5E76\u5173\u95ED\u3002`))try{let $=await(await fetch(`/api/groups/${encodeURIComponent(l.chatId)}/leave`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({larkAppIds:g})})).json(),k=($.result??[]).map(b=>{if(!b.ok)return`${b.larkAppId}: \u5931\u8D25 (${b.error??"unknown"})`;let A=b.closedSessions??[],d=A.filter(E=>!E.ok).length,f=A.length-d,h=A.length===0?"":d===0?`\uFF08\u5173\u95ED ${f} \u4E2A\u4F1A\u8BDD\uFF09`:`\uFF08\u5173\u95ED ${f} \u4E2A\uFF0C${d} \u4E2A\u5931\u8D25\uFF09`;return`${b.larkAppId}: OK${h}`}).join(`
|
|
305
|
+
`);alert(k||`Unexpected: ${JSON.stringify($)}`),await Y(),v()}catch(u){alert("Network error: "+u)}finally{a.close()}},a.querySelector("#g-disband-btn").onclick=async()=>{if(I.length===0||!confirm(`\u786E\u5B9A\u89E3\u6563\u7FA4\u804A\u300C${l.name??l.chatId}\u300D\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\uFF0C\u672C\u7FA4\u6240\u6709\u673A\u5668\u4EBA\u4F1A\u8BDD\u4E5F\u4F1A\u4E00\u5E76\u5173\u95ED\u3002`))return;let g=[...I].sort(($,k)=>(k.larkAppId===L?1:0)-($.larkAppId===L?1:0)),u=[];for(let $ of g)try{let k=await fetch(`/api/groups/${encodeURIComponent(l.chatId)}/disband`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({larkAppId:$.larkAppId})}),b=await k.json();if(b.ok){let A=b.closedSessions??[],d=A.filter(E=>!E.ok).length,f=A.length-d,h=A.length===0?"":d===0?`
|
|
306
|
+
\u5173\u95ED\u4E86 ${f} \u4E2A\u4F1A\u8BDD\u3002`:`
|
|
307
|
+
\u5173\u95ED\u4E86 ${f} \u4E2A\u4F1A\u8BDD\uFF0C${d} \u4E2A\u4F1A\u8BDD\u5173\u95ED\u5931\u8D25\u3002`;alert(`\u5DF2\u89E3\u6563\uFF08\u7531 ${$.botName??$.larkAppId} \u6267\u884C\uFF09${h}`),await Y(),v(),a.close();return}u.push(`${$.botName??$.larkAppId}: ${b.error??k.status}`)}catch(k){u.push(`${$.botName??$.larkAppId}: ${k}`)}alert(`\u6240\u6709\u5728\u7FA4\u673A\u5668\u4EBA\u5747\u65E0\u6CD5\u89E3\u6563\uFF1A
|
|
298
308
|
${u.join(`
|
|
299
309
|
`)}
|
|
300
310
|
|
|
301
|
-
\u5EFA\u8BAE\u6539\u7528\u300C\u9000\u51FA\u7FA4\u804A\u300D\u3002`)}}
|
|
311
|
+
\u5EFA\u8BAE\u6539\u7528\u300C\u9000\u51FA\u7FA4\u804A\u300D\u3002`)}}r.addEventListener("input",v)}var X={bots:[]},Z=null;function Nt(){return`<section class="page">
|
|
302
312
|
<div class="page-heading">
|
|
303
313
|
<div>
|
|
304
|
-
<p class="eyebrow">${
|
|
305
|
-
<h1>${
|
|
306
|
-
<p>${
|
|
314
|
+
<p class="eyebrow">${t("nav.botDefaults")}</p>
|
|
315
|
+
<h1>${t("botDefaults.title")}</h1>
|
|
316
|
+
<p>${t("botDefaults.subtitle")}</p>
|
|
307
317
|
</div>
|
|
308
318
|
</div>
|
|
309
319
|
<form id="bd-filters" class="filters">
|
|
310
|
-
<input type="search" name="q" placeholder="${
|
|
311
|
-
<button type="button" id="bd-refresh">${
|
|
320
|
+
<input type="search" name="q" placeholder="${t("botDefaults.search")}" />
|
|
321
|
+
<button type="button" id="bd-refresh">${t("botDefaults.refresh")}</button>
|
|
312
322
|
</form>
|
|
313
323
|
<p class="hint-warn" style="max-width:760px">
|
|
314
|
-
${
|
|
324
|
+
${t("botDefaults.warning")}
|
|
315
325
|
</p>
|
|
316
326
|
<div id="bd-list"></div>
|
|
317
|
-
</section>`}async function
|
|
318
|
-
<header><strong>${
|
|
319
|
-
<small>${
|
|
320
|
-
<p class="hint-warn-inline">\u67E5\u8BE2\u5931\u8D25\uFF1A${
|
|
321
|
-
</article>`;let
|
|
327
|
+
</section>`}async function Ke(){try{let e=await fetch("/api/bots"),n=await e.json().catch(()=>({}));if(!e.ok){Z=n?.error?`HTTP ${e.status}: ${n.error}${n.path?` (${n.path})`:""}`:`HTTP ${e.status}`,X={bots:[]};return}if(!n||!Array.isArray(n.bots)){Z="unexpected response shape (no `bots` array)",X={bots:[]};return}Z=null,X=n}catch(e){Z=e?.message??String(e),X={bots:[]}}}function Ve(e){if(!e)return"\u2014";let n=new Date(e);return Number.isNaN(n.getTime())?"\u2014":n.toLocaleString()}async function Qe(e){e.innerHTML=Nt();let n=e.querySelector("#bd-list"),o=e.querySelector("#bd-filters"),r=e.querySelector("#bd-refresh");r.onclick=async()=>{r.disabled=!0;try{await Ke(),s()}finally{r.disabled=!1}},await Ke();function s(){let y=(new FormData(o).get("q")??"").toLowerCase(),S=X.bots.filter(v=>!y||(v.botName??"").toLowerCase().includes(y)||(v.larkAppId??"").toLowerCase().includes(y));if(Z){n.innerHTML=`<p class="hint-warn">\u65E0\u6CD5\u52A0\u8F7D bot \u5217\u8868\uFF1A${w(Z)}<br>\u5E38\u89C1\u539F\u56E0\uFF1Adashboard / daemon \u8FDB\u7A0B\u8FD8\u5728\u8DD1\u65E7\u4EE3\u7801\uFF0C\u6267\u884C <code>botmux restart</code> \u540E\u5237\u65B0\u3002</p>`;return}if(S.length===0){n.innerHTML=`<p class="empty">${t("botDefaults.empty")}</p>`;return}n.innerHTML=S.map(a).join(""),c()}function a(p){if(p.error)return`<article class="bd-card" data-appid="${w(p.larkAppId)}">
|
|
328
|
+
<header><strong>${w(p.botName??p.larkAppId)}</strong>
|
|
329
|
+
<small>${w(p.larkAppId)}</small></header>
|
|
330
|
+
<p class="hint-warn-inline">\u67E5\u8BE2\u5931\u8D25\uFF1A${w(p.error)}</p>
|
|
331
|
+
</article>`;let y=p.defaultOncall??{enabled:!1,workingDir:"",since:0},S=!!y.enabled;return`<article class="bd-card" data-appid="${w(p.larkAppId)}">
|
|
322
332
|
<header>
|
|
323
|
-
<strong>${
|
|
324
|
-
<small>${
|
|
333
|
+
<strong>${w(p.botName??p.larkAppId)}</strong>
|
|
334
|
+
<small>${w(p.larkAppId)}</small>
|
|
325
335
|
</header>
|
|
326
336
|
<div class="bd-body">
|
|
327
337
|
<label class="checkbox-row">
|
|
328
338
|
<input type="checkbox" data-action="toggle" ${S?"checked":""}>
|
|
329
|
-
<strong>${
|
|
330
|
-
<small>${
|
|
339
|
+
<strong>${t("botDefaults.defaultOncall")}</strong>
|
|
340
|
+
<small>${t("botDefaults.defaultOncallHelp")}</small>
|
|
331
341
|
</label>
|
|
332
342
|
<div class="bd-row">
|
|
333
343
|
<label>
|
|
334
|
-
<span>${
|
|
344
|
+
<span>${t("botDefaults.workingDir")}</span>
|
|
335
345
|
<input type="text" data-input="workingDir" placeholder="e.g. /root/iserver/botmux"
|
|
336
|
-
value="${
|
|
346
|
+
value="${w(y.workingDir??"")}" ${S?"":"disabled"}>
|
|
337
347
|
</label>
|
|
338
348
|
</div>
|
|
339
349
|
<div class="bd-meta">
|
|
340
|
-
<small>${
|
|
341
|
-
<small>${
|
|
350
|
+
<small>${t("botDefaults.lastEnabled")}: ${w(Ve(y.since??0))}</small>
|
|
351
|
+
<small>${t("botDefaults.autobound",{count:p.autoboundChatCount??0})}</small>
|
|
342
352
|
</div>
|
|
343
353
|
<div class="actions">
|
|
344
|
-
<button type="button" data-action="save">${
|
|
354
|
+
<button type="button" data-action="save">${t("botDefaults.save")}</button>
|
|
345
355
|
<span class="oncall-status" data-status></span>
|
|
346
356
|
</div>
|
|
347
357
|
</div>
|
|
348
|
-
</article>`}function
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
358
|
+
</article>`}function c(){n.querySelectorAll(".bd-card").forEach(p=>{let y=p.dataset.appid,S=p.querySelector("input[data-action=toggle]"),v=p.querySelector("input[data-input=workingDir]"),T=p.querySelector("button[data-action=save]"),l=p.querySelector("[data-status]");!S||!v||!T||!l||(S.addEventListener("change",()=>{v.disabled=!S.checked,S.checked&&v.focus()}),T.addEventListener("click",async()=>{l.textContent="",l.className="oncall-status";let I=S.checked,L=v.value.trim();if(I&&!L){l.textContent=t("botDefaults.required"),l.classList.add("hint-warn-inline");return}T.disabled=!0;try{let g=await fetch(`/api/bots/${encodeURIComponent(y)}/default-oncall`,{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({enabled:I,workingDir:L})}),u=await g.json().catch(()=>({}));if(g.ok&&u.ok){let $=u.resolvedPath?` \u2192 ${u.resolvedPath}`:"";l.textContent=I?`\u2713 \u5DF2\u5F00\u542F${$}\uFF08\u672A\u7ED1\u5B9A\u7684\u7FA4\u4E0B\u6B21\u5F00\u8BDD\u9898\u81EA\u52A8 oncall\uFF09`:"\u2713 \u5DF2\u5173\u95ED\uFF08\u5DF2\u7ED1\u5B9A\u7684\u7FA4\u4E0D\u52A8\uFF09",l.classList.add("hint-ok");let k=X.bots.find(A=>A.larkAppId===y);k&&u.defaultOncall&&(k.defaultOncall=u.defaultOncall);let b=p.querySelector(".bd-meta small:first-child");b&&u.defaultOncall?.since!=null&&(b.textContent=`\u4E0A\u6B21\u542F\u7528\u65F6\u95F4\uFF1A${Ve(u.defaultOncall.since)}`)}else l.textContent=`\u2717 ${u.error??g.status}`,l.classList.add("hint-warn-inline")}catch(g){l.textContent=`\u2717 ${g?.message??g}`,l.classList.add("hint-warn-inline")}finally{T.disabled=!1}}))})}s(),o.addEventListener("input",s)}function Bt(){let e=[["",t("workflow.filter.nonTerminal")],["all",t("workflow.filter.all")],["pending",z("pending")],["running",z("running")],["waiting",z("waiting")],["succeeded",z("succeeded")],["failed",z("failed")],["cancelled",z("cancelled")]];return`
|
|
359
|
+
<nav class="wf-subnav">
|
|
360
|
+
<a href="#/workflows" class="active" data-i18n="workflow.subnav.runs">${i(t("workflow.subnav.runs"))}</a>
|
|
361
|
+
<a href="#/workflows/catalog" data-i18n="workflow.subnav.catalog">${i(t("workflow.subnav.catalog"))}</a>
|
|
362
|
+
</nav>
|
|
363
|
+
<form id="wf-filters" class="filters">
|
|
364
|
+
<input type="search" name="q" placeholder="${i(t("workflow.searchPlaceholder"))}" />
|
|
365
|
+
<select name="status">
|
|
366
|
+
${e.map(([n,o])=>`<option value="${i(n)}">${i(o)}</option>`).join("")}
|
|
367
|
+
</select>
|
|
368
|
+
<span id="wf-last-load" class="muted"></span>
|
|
369
|
+
</form>
|
|
370
|
+
<table>
|
|
371
|
+
<thead><tr>
|
|
372
|
+
<th>${i(t("workflow.table.run"))}</th><th>${i(t("workflow.table.workflow"))}</th><th>${i(t("workflow.table.status"))}</th>
|
|
373
|
+
<th>${i(t("workflow.table.lastSeq"))}</th><th>${i(t("workflow.table.dangling"))}</th><th>${i(t("workflow.table.updated"))}</th>
|
|
374
|
+
<th>${i(t("workflow.table.chatApp"))}</th>
|
|
375
|
+
</tr></thead>
|
|
376
|
+
<tbody id="wf-tbody"></tbody>
|
|
377
|
+
</table>
|
|
378
|
+
`}var jt=5e3,Ut=2e3,ne=new Set(["succeeded","failed","cancelled"]);function i(e){return e.replace(/[&<>"']/g,n=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[n])}function Ft(e){let n=new Date(e),r=Date.now()-e;return r<6e4?t("time.secondsAgo",{value:Math.max(1,Math.floor(r/1e3))}):r<36e5?t("time.minutesAgo",{value:Math.floor(r/6e4)}):r<864e5?t("time.hoursAgo",{value:Math.floor(r/36e5)}):n.toISOString().slice(0,19).replace("T"," ")}function W(e){return`<span class="${ne.has(e)?"wf-status terminal":"wf-status live"} wf-status-${i(e)}">${i(z(e))}</span>`}function z(e){let n=`workflow.status.${e}`,o=t(n);return o===n?e:o}function et(e){let n=location.hash.match(/^#\/workflows\/([^?#]+)(?:\?([^#]*))?$/);if(n){let o=new URLSearchParams(n[2]??"");return _t(e,decodeURIComponent(n[1]),{focusAttemptId:o.get("attempt")??void 0})}return Wt(e)}function Wt(e){e.innerHTML=Bt();let n=e.querySelector("#wf-tbody"),o=e.querySelector("#wf-filters"),r=e.querySelector("#wf-last-load"),s=[],a=null,c=!1,p=null,y=!1;function S(g){let $=(new FormData(o).get("q")??"").trim().toLowerCase();return $?g.filter(k=>k.runId.toLowerCase().includes($)||k.workflowId.toLowerCase().includes($)||(k.chatId??"").toLowerCase().includes($)):g}function v(){let g=S(s);if(g.length===0){n.innerHTML=`<tr><td colspan="7" class="empty">${p?i(t("workflow.list.failedLoad",{error:p})):s.length===0?i(t("workflow.list.noRuns")):i(t("workflow.list.noFilterMatch"))}</td></tr>`;return}n.innerHTML=g.map(u=>{let $=`${u.dEf}/${u.dAct}/${u.dWait}`,k=u.dEf+u.dAct+u.dWait>0?"wf-dangling has":"wf-dangling none",b=[];u.chatId&&b.push(i(u.chatId)),u.larkAppId&&b.push(`<span class="muted">${i(u.larkAppId)}</span>`);let A=b.length>0?b.join("<br/>"):"\u2014",d=Gt(u);return`<tr data-runid="${i(u.runId)}">
|
|
379
|
+
<td><a href="#/workflows/${encodeURIComponent(u.runId)}"><code>${i(u.runId)}</code></a></td>
|
|
380
|
+
<td>${i(u.workflowId)}</td>
|
|
381
|
+
<td>${W(u.status)}${u.failedNodeId?` <span class="muted">(${i(u.failedNodeId)})</span>`:""}${d}</td>
|
|
382
|
+
<td>${u.lastSeq}</td>
|
|
383
|
+
<td class="${k}">${$}</td>
|
|
384
|
+
<td title="${i(new Date(u.updatedAt).toISOString())}">${Ft(u.updatedAt)}</td>
|
|
385
|
+
<td>${A}</td>
|
|
386
|
+
</tr>`}).join("")}function T(){p?(r.textContent=t("workflow.list.error",{error:p}),r.classList.add("error")):(r.textContent=t("workflow.list.loaded",{count:s.length,time:new Date().toLocaleTimeString()}),r.classList.remove("error"))}async function l(){if(!(y||c)&&!document.hidden){c=!0;try{let g=o.elements.namedItem("status")?.value??"",u=new URLSearchParams;g==="all"?u.set("all","1"):g&&u.set("status",g);let $="/api/workflows/runs"+(u.toString()?`?${u}`:""),k=await fetch($);k.ok?(s=(await k.json()).runs??[],p=null):(p=`HTTP ${k.status}`,s=[])}catch(g){p=g?.message??String(g),s=[]}finally{c=!1,y||(v(),T())}}}function I(){a!==null&&window.clearTimeout(a),a=window.setTimeout(async()=>{await l(),y||I()},jt)}function L(){document.hidden||l()}return o.addEventListener("input",()=>{v()}),o.addEventListener("change",g=>{g.target.getAttribute("name")==="status"&&l()}),document.addEventListener("visibilitychange",L),l().then(()=>{y||I()}),()=>{y=!0,a!==null&&window.clearTimeout(a),document.removeEventListener("visibilitychange",L)}}function _t(e,n,o={}){e.innerHTML=`
|
|
387
|
+
<div class="wf-detail-head">
|
|
388
|
+
<a class="btn-link" href="#/workflows">${i(t("workflow.detail.back"))}</a>
|
|
389
|
+
<div>
|
|
390
|
+
<h2><code>${i(n)}</code></h2>
|
|
391
|
+
<div id="wf-detail-subtitle" class="muted">${i(t("workflow.detail.loading"))}</div>
|
|
392
|
+
</div>
|
|
393
|
+
<button id="wf-cancel-run" type="button" class="contrast" hidden>${i(t("workflow.detail.cancel"))}</button>
|
|
394
|
+
<span id="wf-detail-refresh" class="muted"></span>
|
|
395
|
+
</div>
|
|
396
|
+
<section id="wf-detail-error" class="hint-warn" hidden></section>
|
|
397
|
+
<section id="wf-cancel-status" class="hint-ok" hidden></section>
|
|
398
|
+
<section id="wf-summary" class="wf-summary-grid"></section>
|
|
399
|
+
<section id="wf-dangling-panel"></section>
|
|
400
|
+
<section class="wf-panel">
|
|
401
|
+
<div class="wf-panel-title">
|
|
402
|
+
<h3>${i(t("workflow.detail.parallel"))}</h3>
|
|
403
|
+
<span id="wf-parallel-meta" class="muted"></span>
|
|
404
|
+
</div>
|
|
405
|
+
<div id="wf-parallel-view"></div>
|
|
406
|
+
</section>
|
|
407
|
+
<section class="wf-panel">
|
|
408
|
+
<div class="wf-panel-title">
|
|
409
|
+
<h3>${i(t("workflow.detail.nodes"))}</h3>
|
|
410
|
+
</div>
|
|
411
|
+
<div class="wf-table-scroll">
|
|
412
|
+
<table>
|
|
413
|
+
<thead><tr>
|
|
414
|
+
<th>${i(t("workflow.detail.node"))}</th><th>${i(t("workflow.detail.nodeStatus"))}</th><th>${i(t("workflow.detail.activity"))}</th><th>${i(t("workflow.detail.activityStatus"))}</th>
|
|
415
|
+
<th>${i(t("workflow.detail.attempts"))}</th><th>${i(t("workflow.detail.current"))}</th><th>${i(t("workflow.detail.detail"))}</th>
|
|
416
|
+
</tr></thead>
|
|
417
|
+
<tbody id="wf-node-tbody"></tbody>
|
|
418
|
+
</table>
|
|
419
|
+
</div>
|
|
420
|
+
</section>
|
|
421
|
+
<section class="wf-panel">
|
|
422
|
+
<div class="wf-panel-title">
|
|
423
|
+
<h3>${i(t("workflow.detail.nodeIO"))}</h3>
|
|
424
|
+
</div>
|
|
425
|
+
<div id="wf-io-list" class="wf-io-list"></div>
|
|
426
|
+
</section>
|
|
427
|
+
<section class="wf-panel">
|
|
428
|
+
<div class="wf-panel-title">
|
|
429
|
+
<h3>${i(t("workflow.detail.timeline"))}</h3>
|
|
430
|
+
<button id="wf-load-older" type="button" hidden>${i(t("workflow.detail.loadOlder"))}</button>
|
|
431
|
+
</div>
|
|
432
|
+
<div class="wf-table-scroll wf-timeline-scroll">
|
|
433
|
+
<table>
|
|
434
|
+
<thead><tr>
|
|
435
|
+
<th>${i(t("workflow.detail.seq"))}</th><th>${i(t("workflow.detail.event"))}</th><th>${i(t("workflow.detail.actor"))}</th><th>${i(t("workflow.detail.node"))}</th><th>${i(t("workflow.detail.activity"))}</th><th>${i(t("workflow.detail.error"))}</th><th>${i(t("workflow.detail.time"))}</th>
|
|
436
|
+
</tr></thead>
|
|
437
|
+
<tbody id="wf-event-tbody"></tbody>
|
|
438
|
+
</table>
|
|
439
|
+
</div>
|
|
440
|
+
<div id="wf-event-meta" class="muted"></div>
|
|
441
|
+
</section>
|
|
442
|
+
`;let r=e.querySelector("#wf-detail-subtitle"),s=e.querySelector("#wf-detail-refresh"),a=e.querySelector("#wf-detail-error"),c=e.querySelector("#wf-cancel-status"),p=e.querySelector("#wf-summary"),y=e.querySelector("#wf-dangling-panel"),S=e.querySelector("#wf-parallel-view"),v=e.querySelector("#wf-parallel-meta"),T=e.querySelector("#wf-node-tbody"),l=e.querySelector("#wf-io-list"),I=e.querySelector(".wf-timeline-scroll"),L=e.querySelector("#wf-event-tbody"),g=e.querySelector("#wf-event-meta"),u=e.querySelector("#wf-cancel-run"),$=e.querySelector("#wf-load-older"),k=null,b=[],A=new Set,d=null,f=null,h=!1,E=0,C=null,R=!1,q=!1,P=!1,x=new Set,be=new Map,Ae=new Map,re=new Map,ie=new Set,le=new Map,K=new Set,ee=new Map,gt=new Map,Ce=0,He=o.focusAttemptId;function j(m){if(!m){a.hidden=!0,a.textContent="";return}a.hidden=!1,a.textContent=m}function Re(m){if(!m){c.hidden=!0,c.textContent="";return}c.hidden=!1,c.textContent=m}async function Oe(){let m=await fetch(`/api/workflows/runs/${encodeURIComponent(n)}/snapshot`);if(m.status===404)throw new Error(t("workflow.detail.unknownRun"));if(!m.ok)throw new Error(t("workflow.detail.snapshotHttp",{status:m.status}));k=await m.json()}async function de(m){let N=await fetch(`/api/workflows/runs/${encodeURIComponent(n)}/events?${m}`);if(N.status===404)throw new Error(t("workflow.detail.unknownRun"));if(!N.ok)throw new Error(t("workflow.detail.eventsHttp",{status:N.status}));return await N.json()}function ce(m,N){let O=m.filter(H=>A.has(H.eventId)?!1:(A.add(H.eventId),!0));O.length!==0&&(b=N==="prepend"?[...O,...b]:[...b,...O],b.sort((H,_)=>oe(H.eventId)-oe(_.eventId)))}async function ht(){await Oe();let m=await de(new URLSearchParams({tail:"100"}));b=[],A=new Set,ce(m.events,"append"),d=m.oldestSeq,f=m.newestSeq,h=m.hasOlder,E=m.totalCount,U()}async function ue(){if(!(R||q||document.hidden)){q=!0;try{if(await Oe(),f!==null){let m=await de(new URLSearchParams({afterSeq:String(f),limit:"200"}));ce(m.events,"append"),m.newestSeq!==null&&(f=m.newestSeq),d===null&&m.oldestSeq!==null&&(d=m.oldestSeq),E=m.totalCount}else{let m=await de(new URLSearchParams({tail:"1"}));ce(m.events,"append"),d=m.oldestSeq,f=m.newestSeq,h=m.hasOlder,E=m.totalCount}j(null),U()}catch(m){j(m?.message??String(m))}finally{q=!1}}}async function bt(){if(!(d===null||!h)){$.disabled=!0;try{let m=await de(new URLSearchParams({beforeSeq:String(d),limit:"100"}));ce(m.events,"prepend"),m.oldestSeq!==null&&(d=m.oldestSeq),h=m.hasOlder,E=m.totalCount,j(null),U()}catch(m){j(m?.message??String(m))}finally{$.disabled=!1}}}async function vt(){if(!k||ne.has(k.run.status)||P)return;if(!k.chatBinding?.larkAppId){j(t("workflow.detail.cancelUnavailable",{runId:n}));return}let m=zt(k),N=t("workflow.detail.cancelConfirm",{runId:n,...m});if(window.confirm(N)){P=!0,u.disabled=!0;try{let O=await fetch(`/api/workflows/runs/${encodeURIComponent(n)}/cancel`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({reason:"cancelled via dashboard"})});if(O.status===401)throw new Error(t("workflow.detail.writeAccessCancel"));let H=await O.json().catch(()=>({}));if(!O.ok||!H.ok)throw new Error(H.hint??H.error??t("workflow.detail.cancelHttp",{status:O.status}));Re(H.pending?t("workflow.detail.cancelPending"):null),j(null),await ue()}catch(O){j(O?.message??String(O))}finally{P=!1,u.disabled=!1,U()}}}async function kt(m,N){if(!K.has(m)){K.add(m),ee.delete(m),U();try{let O=await fetch(`/api/workflows/runs/${encodeURIComponent(n)}/attempts/${encodeURIComponent(N)}/${encodeURIComponent(m)}/resume`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({})});if(O.status===401)throw new Error(t("workflow.detail.writeAccessResume"));let H=await O.json().catch(()=>({}));if(!O.ok||!H.ok||!H.resumeId||!H.url)throw new Error(H.hint??H.message??H.error??t("workflow.detail.resumeStartFailed",{status:O.status}));le.set(m,{resumeId:H.resumeId,url:H.url})}catch(O){let H=O?.message??String(O);ee.set(m,H)}finally{K.delete(m),U()}}}async function yt(m,N){if(!K.has(m)){K.add(m),ee.delete(m),U();try{let O=await fetch(`/api/workflows/runs/${encodeURIComponent(n)}/attempts/${encodeURIComponent(N)}/${encodeURIComponent(m)}/resume/end`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({reason:"ended_by_dashboard"})});if(O.status===401)throw new Error(t("workflow.detail.writeAccessResume"));let H=await O.json().catch(()=>({}));if(!O.ok||!H.ok)if(H.error==="resume_not_running")le.delete(m);else throw new Error(H.hint??H.message??H.error??t("workflow.detail.resumeEndFailed",{status:O.status}));else le.delete(m)}catch(O){let H=O?.message??String(O);ee.set(m,H)}finally{K.delete(m),U()}}}async function $t(m,N){if(!ie.has(m)){ie.add(m),re.delete(m),U();try{let O=Ae.get(m)?.trim()||void 0,H=await fetch(`/api/workflows/runs/${encodeURIComponent(n)}/${N}`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({comment:O})});if(H.status===401)throw new Error(t("workflow.detail.writeAccessApproval"));let _=await H.json().catch(()=>({}));if(!H.ok||!_.ok)throw new Error(_.hint??_.message??_.error??t("workflow.detail.actionHttp",{action:N,status:H.status}));let ke=N==="approve"?t("workflow.detail.approved"):t("workflow.detail.rejected");re.set(m,{kind:"ok",text:_.alreadyTerminal?t("workflow.detail.alreadyTerminal",{label:ke}):_.pending?t("workflow.detail.workflowContinue",{label:ke}):t("workflow.detail.workflowRefreshing",{label:ke})}),j(null),await ue()}catch(O){let H=O?.message??String(O);re.set(m,{kind:"error",text:H}),j(H)}finally{ie.delete(m),U()}}}function U(){if(!k)return;Ce=I.scrollTop;let m=k.run;ne.has(m.status)&&Re(null),r.innerHTML=`${i(m.workflowId??"?")} \xB7 ${W(m.status)} \xB7 lastSeq ${k.lastSeq}`,s.textContent=t("workflow.detail.refreshed",{time:new Date().toLocaleTimeString()}),u.hidden=ne.has(m.status),u.disabled=P||!k.chatBinding?.larkAppId,u.textContent=k.chatBinding?.larkAppId?t("workflow.detail.cancel"):t("workflow.detail.cliCancelOnly"),u.title=k.chatBinding?.larkAppId?t("workflow.detail.cancelTitle"):t("workflow.detail.cliCancelTitle",{runId:n}),Jt(p,k),Kt(y,k),Vt(S,v,k,b),tn(T,k),nn(l,k,x,be,{comments:Ae,statuses:re,resolving:ie,onResolve:$t},{sessions:le,pending:K,errors:ee,onStart:kt,onEnd:yt},He,gt)&&(He=void 0),An(L,b),I.scrollTop=Ce,$.hidden=!h,g.textContent=t("workflow.detail.eventsLoaded",{loaded:b.length,total:E})}function ve(){if(C!==null&&window.clearTimeout(C),k&&ne.has(k.run.status)){C=null;return}C=window.setTimeout(async()=>{await ue(),R||ve()},Ut)}function xe(){document.hidden||ue().then(()=>{!R&&C===null&&ve()})}return $.addEventListener("click",()=>{bt()}),u.addEventListener("click",()=>{vt()}),document.addEventListener("visibilitychange",xe),ht().then(()=>{j(null),R||ve()}).catch(m=>{j(m?.message??String(m)),r.textContent=t("workflow.detail.loadFailed")}),()=>{R=!0,C!==null&&window.clearTimeout(C),document.removeEventListener("visibilitychange",xe)}}function Jt(e,n){let o=n.run,r=[[t("workflow.summary.workflow"),i(o.workflowId??"?")],[t("workflow.summary.status"),W(o.status)],[t("workflow.summary.lastSeq"),String(n.lastSeq)],[t("workflow.summary.updated"),i(new Date(n.updatedAt).toLocaleString())],[t("workflow.summary.revision"),i(me(o.revisionId))],[t("workflow.summary.initiator"),i(o.initiator??"-")]];o.failedNodeId&&r.push([t("workflow.summary.failedNode"),i(o.failedNodeId)]),o.cancelOriginEventId&&r.push([t("workflow.summary.cancelOrigin"),i(o.cancelOriginEventId)]),n.chatBinding&&(r.push([t("workflow.summary.chat"),`<code>${i(n.chatBinding.chatId)}</code>`]),r.push([t("workflow.summary.app"),`<code>${i(n.chatBinding.larkAppId)}</code>`])),e.innerHTML=r.map(([s,a])=>`<div class="wf-summary-item"><span>${s}</span><strong>${a}</strong></div>`).join("")}function Gt(e){if(!e.errorCode)return"";let n=e.errorMessage?` \u2014 ${On(e.errorMessage,96)}`:"";return`<div class="wf-run-error">
|
|
443
|
+
<span class="muted error">${i(e.errorCode)}</span>${i(n)}
|
|
444
|
+
</div>`}function zt(e){let n=e.dangling;return{total:new Set([...n.activities,...n.effectAttempted,...n.waits,...n.cancels]).size,effects:n.effectAttempted.length,activities:n.activities.length,waits:n.waits.length,cancels:n.cancels.length}}function Kt(e,n){let o=n.dangling,r=[[t("workflow.dangling.activities"),o.activities],[t("workflow.dangling.effects"),o.effectAttempted],[t("workflow.dangling.waits"),o.waits],[t("workflow.dangling.cancels"),o.cancels]],s=new Set(r.flatMap(([,a])=>a)).size;if(e.className=s>0?"wf-panel wf-dangling-panel has":"wf-panel wf-dangling-panel",s===0){e.innerHTML=`<div class="wf-panel-title"><h3>${i(t("workflow.detail.dangling"))}</h3></div><div class="muted">${i(t("workflow.detail.noDangling"))}</div>`;return}e.innerHTML=`<div class="wf-panel-title"><h3>${i(t("workflow.detail.dangling"))}</h3><span class="wf-dangling has">${s}</span></div>
|
|
445
|
+
<div class="wf-dangling-grid">
|
|
446
|
+
${r.map(([a,c])=>`<div><strong>${a}</strong>${c.length===0?`<div class="muted">${i(t("workflow.detail.none"))}</div>`:`<ul>${c.map(p=>`<li><code>${i(p)}</code></li>`).join("")}</ul>`}</div>`).join("")}
|
|
447
|
+
</div>`}function Vt(e,n,o,r){let s=Qt(r,o);if(s.length===0){n.textContent="",e.innerHTML=`<div class="empty">${i(t("workflow.detail.noParallelData"))}</div>`;return}let a=Date.now(),c=Math.min(...s.map(l=>l.startedAt)),p=Math.max(...s.map(l=>l.endedAt??a),c+1e3),y=Math.max(1,p-c),S=Xt(s,a),v=s.filter(l=>!l.endedAt&&(l.status==="running"||l.status==="effectAttempting")).length;n.textContent=t("workflow.detail.parallelMeta",{count:s.length,max:S,running:v});let T=s.sort((l,I)=>l.startedAt-I.startedAt||l.activityId.localeCompare(I.activityId)).map(l=>Yt(l,c,y,a)).join("");e.innerHTML=`<div class="wf-parallel-axis">
|
|
448
|
+
<span title="${i(new Date(c).toISOString())}">${i(we(c))}</span>
|
|
449
|
+
<span title="${i(new Date(p).toISOString())}">${i(we(p))}</span>
|
|
450
|
+
</div>
|
|
451
|
+
<div class="wf-parallel-list">${T}</div>`}function Qt(e,n){let o=new Map,r=new Map(n.activities.map(s=>[s.activityId,s.ownerNodeId]));for(let s of[...e].sort((a,c)=>oe(a.eventId)-oe(c.eventId))){let a=Rn(s);if(!a)continue;let c=typeof a.activityId=="string"?a.activityId:void 0,p=typeof a.attemptId=="string"?a.attemptId:void 0;if(!c||!p)continue;let y=o.get(p);if(s.type==="attemptCreated"){let S=typeof a.attemptNumber=="number"?a.attemptNumber:void 0;y={nodeId:typeof a.nodeId=="string"?a.nodeId:r.get(c),activityId:c,attemptId:p,attemptNumber:S,status:"pending",startedAt:s.timestamp},o.set(p,y);continue}y||(y={nodeId:r.get(c),activityId:c,attemptId:p,status:"pending",startedAt:s.timestamp},o.set(p,y)),s.type==="activityRunning"?(y.status="running",y.runningAt=s.timestamp):s.type==="effectAttempted"?y.status="effectAttempting":s.type==="activityWaiting"||s.type==="waitCreated"?y.status="waiting":Zt(s.type)&&(y.status=en(s.type),y.endedAt=s.timestamp,y.endType=s.type)}return[...o.values()]}function Yt(e,n,o,r){let s=e.endedAt??r,a=Ze((e.startedAt-n)/o*100,0,100),c=Ze((Math.max(s,e.startedAt+1)-e.startedAt)/o*100,.7,100-a),p=e.nodeId??e.activityId,y=e.attemptNumber!==void 0?`#${e.attemptNumber}`:me(e.attemptId),S=[`${p} ${e.status}`,`${new Date(e.startedAt).toISOString()} \u2192 ${e.endedAt?new Date(e.endedAt).toISOString():t("workflow.detail.parallelNow")}`,e.endType?`end: ${e.endType}`:void 0].filter(Boolean).join(`
|
|
452
|
+
`);return`<div class="wf-parallel-row">
|
|
453
|
+
<div class="wf-parallel-label">
|
|
454
|
+
<code>${i(p)}</code>
|
|
455
|
+
<span class="muted">${i(e.activityId)} \xB7 ${i(y)}</span>
|
|
456
|
+
</div>
|
|
457
|
+
<div class="wf-parallel-track">
|
|
458
|
+
<div class="wf-parallel-bar wf-parallel-${i(e.status)}" style="left:${a.toFixed(3)}%;width:${c.toFixed(3)}%;" title="${i(S)}">
|
|
459
|
+
<span>${i(z(e.status))}</span>
|
|
460
|
+
</div>
|
|
461
|
+
</div>
|
|
462
|
+
</div>`}function Xt(e,n){let o=[];for(let a of e)o.push({time:a.startedAt,delta:1}),o.push({time:a.endedAt??n,delta:-1});o.sort((a,c)=>a.time-c.time||c.delta-a.delta);let r=0,s=0;for(let a of o)r+=a.delta,s=Math.max(s,r);return s}function Zt(e){return e==="activitySucceeded"||e==="activityFailed"||e==="activityTimedOut"||e==="activityCanceled"}function en(e){return e==="activitySucceeded"?"succeeded":e==="activityCanceled"?"cancelled":e==="activityTimedOut"?"timedOut":"failed"}function tn(e,n){let o=new Map(n.activities.map(a=>[a.activityId,a])),r=new Set,s=[];for(let a of n.nodes){let c=(a.activityId?o.get(a.activityId):void 0)??n.activities.find(p=>p.ownerNodeId===a.nodeId);c&&r.add(c.activityId),s.push(Ye(a,c))}for(let a of n.activities)r.has(a.activityId)||s.push(Ye(void 0,a));e.innerHTML=s.length>0?s.join(""):`<tr><td colspan="7" class="empty">${i(t("workflow.detail.noNodes"))}</td></tr>`}function Ye(e,n){let o=n?.attempts[n.attempts.length-1];return`<tr>
|
|
463
|
+
<td>${e?`<code>${i(e.nodeId)}</code>`:'<span class="muted">-</span>'}</td>
|
|
464
|
+
<td>${e?W(e.status):'<span class="muted">-</span>'}</td>
|
|
465
|
+
<td>${n?`<code>${i(n.activityId)}</code>`:'<span class="muted">-</span>'}</td>
|
|
466
|
+
<td>${n?W(n.status):'<span class="muted">-</span>'}</td>
|
|
467
|
+
<td>${n?.attempts.length??0}</td>
|
|
468
|
+
<td>${o?`<code>${i(o.attemptId)}</code>`:'<span class="muted">-</span>'}</td>
|
|
469
|
+
<td>${o?Mn(o):`<span class="muted">${i(t("workflow.detail.idle"))}</span>`}</td>
|
|
470
|
+
</tr>`}function nn(e,n,o,r,s,a,c,p){$n(e,o,r),bn(e,s.comments);let y=!!(c&&n.attemptIO?.[c]?.terminal);y&&c&&o.add(Le(c,t("workflow.detail.liveTerminal")));let S=on(n),v=new Set;if(p){for(let l of S){v.add(l.key);let I=p.get(l.key);I||(I=an(l.key),p.set(l.key,I),e.appendChild(I.article)),sn(I,l,o,s,a,c)}for(let[l,I]of Array.from(p))v.has(l)||(I.article.remove(),p.delete(l));if(S.length===0){if(!e.querySelector(".wf-io-empty-placeholder")){let l=document.createElement("div");l.className="empty wf-io-empty-placeholder",l.textContent=t("workflow.detail.noNodeIO"),e.appendChild(l)}}else e.querySelector(".wf-io-empty-placeholder")?.remove()}else{let l=[];for(let I of S)l.push(un(I,o,s,a,c));e.innerHTML=l.length>0?l.join(""):`<div class="empty">${i(t("workflow.detail.noNodeIO"))}</div>`}In(e,r);let T=yn(e,c);return Sn(e,o),Tn(e,r),kn(e,s),dt(e,a),T&&y}function on(e){let n=new Map(e.activities.map(s=>[s.activityId,s])),o=new Set,r=[];for(let s of e.nodes){let a=(s.activityId?n.get(s.activityId):void 0)??e.activities.find(c=>c.ownerNodeId===s.nodeId);if(!a){r.push({key:`node:${s.nodeId}`,node:s});continue}o.add(a.activityId),r.push({key:`activity:${a.activityId}`,node:s,activity:a,io:e.attemptIO?.[pe(a)?.attemptId??""]})}for(let s of e.activities)o.has(s.activityId)||r.push({key:`activity:${s.activityId}`,activity:s,io:e.attemptIO?.[pe(s)?.attemptId??""]});return r}function an(e){let n=document.createElement("article");n.className="wf-io-card",n.dataset.wfCardKey=e;let o=document.createElement("div");o.className="wf-io-card-head";let r=document.createElement("div");r.className="wf-io-terminal-slot";let s=document.createElement("div");return s.className="wf-io-grid",n.appendChild(o),n.appendChild(r),n.appendChild(s),{article:n,head:o,terminalSlot:r,grid:s,currentTerminalUrl:null}}function sn(e,n,o,r,s,a){let c=pe(n.activity),p=n.node?.nodeId??n.activity?.ownerNodeId??n.activity?.activityId??"unknown",y=!!(c&&c.attemptId===a);e.article.classList.toggle("is-focused",y),c?e.article.dataset.wfAttemptCard=c.attemptId:delete e.article.dataset.wfAttemptCard;let S=lt(c,r);e.head.innerHTML=`
|
|
471
|
+
<header>
|
|
472
|
+
<div>
|
|
473
|
+
<strong><code>${i(p)}</code></strong>
|
|
474
|
+
<span class="muted">${n.activity?i(n.activity.activityId):i(t("workflow.detail.notDispatched"))}</span>
|
|
475
|
+
</div>
|
|
476
|
+
<div>${n.node?W(n.node.status):""} ${n.activity?W(n.activity.status):""}</div>
|
|
477
|
+
</header>
|
|
478
|
+
<div class="wf-io-meta">
|
|
479
|
+
${c?`${i(t("workflow.detail.attempt"))} <code>${i(c.attemptId)}</code>`:i(t("workflow.detail.noAttempt"))}
|
|
480
|
+
</div>
|
|
481
|
+
${S}
|
|
482
|
+
`;let v=tt(c,n.activity,n.io?.terminal,s),T=v?.url??null;if(T!==e.currentTerminalUrl)v===null?e.terminalSlot.innerHTML="":e.terminalSlot.innerHTML=nt(n.key,c,n.activity,n.io?.terminal,v,o,s),e.currentTerminalUrl=T;else if(v!==null&&n.io?.terminal){let I=e.terminalSlot.querySelector("details.wf-terminal-block > summary");if(I){let L=ot(v.kind);I.innerHTML=`${i(L)} ${it(c,n.io.terminal)}`}c&&vn(e.terminalSlot,c,n.activity,n.io.terminal,v,s)}let l=c?.attemptId??n.activity?.activityId??n.node?.nodeId??"unknown";e.grid.innerHTML=`
|
|
483
|
+
${F(l,t("workflow.detail.authoredInput"),n.io?.input,o)}
|
|
484
|
+
${F(l,t("workflow.detail.resolvedInput"),n.io?.resolvedInput,o)}
|
|
485
|
+
${F(l,t("workflow.detail.output"),n.io?.output,o)}
|
|
486
|
+
${F(l,t("workflow.detail.executionLog"),n.io?.log,o)}
|
|
487
|
+
${n.io?.waitPrompt?F(l,t("workflow.detail.waitPrompt"),n.io.waitPrompt,o):""}
|
|
488
|
+
`}function tt(e,n,o,r){if(!o||o.error)return null;if(fn(e,o))return{kind:"live",url:wn(o)};if(!e||!n||!pn(e,o))return null;let s=gn();if(!s)return null;let a=r?.sessions.get(e.attemptId);return a?{kind:"resume",url:a.url,resumeId:a.resumeId,downloadUrl:Xe(s,n.activityId,e.attemptId)}:{kind:"replay",url:mn(s,n.activityId,e.attemptId,!!o.hasPtyLog),downloadUrl:Xe(s,n.activityId,e.attemptId)}}function nt(e,n,o,r,s,a,c){if(!r)return"";let p=ot(s.kind),y=Le(e,p),S=it(n,r),v=rn(s.kind),T=s.kind==="replay"||s.kind==="resume"?`<a class="btn-link" href="${i(s.downloadUrl)}" download>${i(t("workflow.detail.downloadFullLog"))}</a>`:"",l=n?st(n,o,r,s,c):"",I=n?rt(n.attemptId,c):"";return`<details class="wf-io-block wf-terminal-block" data-io-key="${i(y)}"${a.has(y)?" open":""}>
|
|
489
|
+
<summary>${i(p)} ${S}</summary>
|
|
490
|
+
<div class="wf-terminal-actions">
|
|
491
|
+
<a class="btn-link" href="${i(s.url)}" target="_blank" rel="noreferrer">${i(v)}</a>
|
|
492
|
+
${T}
|
|
493
|
+
${l}
|
|
494
|
+
</div>
|
|
495
|
+
${I}
|
|
496
|
+
<iframe class="wf-terminal-frame" src="${i(s.url)}" title="${i(p)}" loading="lazy"></iframe>
|
|
497
|
+
</details>`}function ot(e){return e==="live"?t("workflow.detail.liveTerminal"):e==="resume"?t("workflow.detail.terminalResume"):t("workflow.detail.terminalReplay")}function rn(e){return e==="live"?t("workflow.detail.openTerminalNewTab"):e==="resume"?t("workflow.detail.openResumeNewTab"):t("workflow.detail.openReplayNewTab")}var at=new Set(["antigravity","cursor"]),ln=new Set(["aiden","coco","claude-code","codex"]);function dn(e){return!!e&&(ln.has(e)||at.has(e))}function cn(e){return!!e&&at.has(e)}function st(e,n,o,r,s){if(!s||r.kind==="live"||!n)return"";let a=r.kind==="resume",c=s.pending.has(e.attemptId),p=`data-wf-resume-attempt="${i(e.attemptId)}" data-wf-resume-activity="${i(n.activityId)}"`;return a?`<button type="button" class="btn-link" data-wf-resume-button="1" data-wf-resume-action="end" ${p}${c?" disabled":""}>${i(c?t("workflow.detail.resumeEnding"):t("workflow.detail.endResumeSession"))}</button>`:dn(o.cliId)?cn(o.cliId)&&!o.cliSessionId?`<button type="button" class="btn-link" data-wf-resume-button="1" disabled title="${i(t("workflow.detail.resumeMissingCliSession"))}">${i(t("workflow.detail.resumeSession"))}</button>`:`<button type="button" class="btn-link" data-wf-resume-button="1" data-wf-resume-action="start" ${p}${c?" disabled":""}>${i(c?t("workflow.detail.resumeStarting"):t("workflow.detail.resumeSession"))}</button>`:`<button type="button" class="btn-link" data-wf-resume-button="1" disabled title="${i(t("workflow.detail.resumeUnsupportedCli",{cliId:o.cliId??"?"}))}">${i(t("workflow.detail.resumeSession"))}</button>`}function rt(e,n){if(!n)return"";let o=n.errors.get(e);return o?`<div class="hint-warn wf-resume-status" data-wf-resume-status="${i(e)}">${i(o)}</div>`:""}function un(e,n,o,r,s){let a=pe(e.activity),c=e.node?.nodeId??e.activity?.ownerNodeId??e.activity?.activityId??"unknown",p=a?.attemptId??e.activity?.activityId??e.node?.nodeId??"unknown",y=lt(a,o),S=a?.attemptId===s?" is-focused":"",v=a?` data-wf-attempt-card="${i(a.attemptId)}"`:"",T=tt(a,e.activity,e.io?.terminal,r),l=T?nt(p,a,e.activity,e.io?.terminal,T,n,r):"";return`<article class="wf-io-card${S}" data-wf-card-key="${i(e.key)}"${v}>
|
|
498
|
+
<div class="wf-io-card-head">
|
|
499
|
+
<header>
|
|
500
|
+
<div>
|
|
501
|
+
<strong><code>${i(c)}</code></strong>
|
|
502
|
+
<span class="muted">${e.activity?i(e.activity.activityId):i(t("workflow.detail.notDispatched"))}</span>
|
|
503
|
+
</div>
|
|
504
|
+
<div>${e.node?W(e.node.status):""} ${e.activity?W(e.activity.status):""}</div>
|
|
505
|
+
</header>
|
|
506
|
+
<div class="wf-io-meta">
|
|
507
|
+
${a?`${i(t("workflow.detail.attempt"))} <code>${i(a.attemptId)}</code>`:i(t("workflow.detail.noAttempt"))}
|
|
508
|
+
</div>
|
|
509
|
+
${y}
|
|
510
|
+
</div>
|
|
511
|
+
<div class="wf-io-terminal-slot">${l}</div>
|
|
512
|
+
<div class="wf-io-grid">
|
|
513
|
+
${F(p,t("workflow.detail.authoredInput"),e.io?.input,n)}
|
|
514
|
+
${F(p,t("workflow.detail.resolvedInput"),e.io?.resolvedInput,n)}
|
|
515
|
+
${F(p,t("workflow.detail.output"),e.io?.output,n)}
|
|
516
|
+
${F(p,t("workflow.detail.executionLog"),e.io?.log,n)}
|
|
517
|
+
${e.io?.waitPrompt?F(p,t("workflow.detail.waitPrompt"),e.io.waitPrompt,n):""}
|
|
518
|
+
</div>
|
|
519
|
+
</article>`}function pe(e){return e?.attempts[e.attempts.length-1]}function it(e,n){let o=[];return n.error?o.push(t("workflow.detail.error")):o.push(n.status==="live"?t("workflow.detail.terminalLive"):t("workflow.detail.terminalClosedShort")),e?.status&&o.push(e.status),n.webPort>0&&o.push(`:${n.webPort}`),`<span class="muted">${i(o.join(" \xB7 "))}</span>`}function fn(e,n){return n.status==="live"&&n.webPort>0&&(e?.status==="pending"||e?.status==="running"||e?.status==="effectAttempting")}function pn(e,n){return e.status==="succeeded"||e.status==="failed"||e.status==="cancelled"||e.status==="timedOut"?!!(n.sessionId||n.startedAt):!1}function wn(e){return`http://${window.location.hostname||"127.0.0.1"}:${e.webPort}`}function mn(e,n,o,r){let s=new URLSearchParams({runId:e,activityId:n,attemptId:o});return r&&s.set("hasPtyLog","1"),`/assets/terminal-replay.html?${s.toString()}`}function Xe(e,n,o){return`/api/workflows/runs/${encodeURIComponent(e)}/attempts/${encodeURIComponent(n)}/${encodeURIComponent(o)}/terminal-log/raw?download=1`}function gn(){let e=window.location.hash.match(/^#\/workflows\/([^/?#]+)/);if(!e)return null;try{return decodeURIComponent(e[1])}catch{return null}}function lt(e,n){if(!hn(e))return"";let o=e.attemptId,r=n.comments.get(o)??"",s=n.resolving.has(o),a=n.statuses.get(o),c=a?.kind==="error"?"hint-warn":"hint-ok";return`<div class="wf-approval-box" data-wf-approval="${i(o)}">
|
|
520
|
+
<label>
|
|
521
|
+
<span>${i(t("workflow.detail.approvalComment"))}</span>
|
|
522
|
+
<textarea class="wf-approval-comment" data-wf-approval-comment="${i(o)}" rows="2" placeholder="${i(t("workflow.detail.optionalComment"))}"${s?" disabled":""}>${i(r)}</textarea>
|
|
523
|
+
</label>
|
|
524
|
+
<div class="wf-approval-actions">
|
|
525
|
+
<button type="button" class="primary" data-wf-approval-action="approve" data-wf-attempt-id="${i(o)}"${s?" disabled":""}>${i(t("workflow.detail.approve"))}</button>
|
|
526
|
+
<button type="button" data-wf-approval-action="reject" data-wf-attempt-id="${i(o)}"${s?" disabled":""}>${i(t("workflow.detail.reject"))}</button>
|
|
527
|
+
${s?`<span class="muted">${i(t("workflow.detail.submitting"))}</span>`:""}
|
|
528
|
+
</div>
|
|
529
|
+
${a?`<div class="${c} wf-approval-status">${i(a.text)}</div>`:""}
|
|
530
|
+
</div>`}function hn(e){return!!e&&e.status==="waiting"&&e.wait?.waitKind==="human-gate"&&!e.wait.resolution}function bn(e,n){e.querySelectorAll("textarea[data-wf-approval-comment]").forEach(o=>{let r=o.dataset.wfApprovalComment;r&&n.set(r,o.value)})}function dt(e,n){e.querySelectorAll("button[data-wf-resume-action][data-wf-resume-attempt][data-wf-resume-activity]").forEach(o=>{o.dataset.wfResumeBound!=="1"&&(o.dataset.wfResumeBound="1",o.addEventListener("click",()=>{let r=o.dataset.wfResumeAttempt,s=o.dataset.wfResumeActivity,a=o.dataset.wfResumeAction;!r||!s||(a==="start"?n.onStart(r,s):a==="end"&&n.onEnd(r,s))}))})}function vn(e,n,o,r,s,a){let c=e.querySelector(".wf-terminal-actions");if(!c)return;let p=c.querySelector('button[data-wf-resume-button="1"]'),y=st(n,o,r,s,a);p?p.outerHTML=y:y&&c.insertAdjacentHTML("beforeend",y);let S=e.querySelector("details.wf-terminal-block");if(S){let v=S.querySelector(".wf-resume-status"),T=rt(n.attemptId,a);v?v.outerHTML=T:T&&c.insertAdjacentHTML("afterend",T)}dt(e,a)}function kn(e,n){e.querySelectorAll("textarea[data-wf-approval-comment]").forEach(o=>{let r=o.dataset.wfApprovalComment;r&&o.addEventListener("input",()=>{n.comments.set(r,o.value)})}),e.querySelectorAll("button[data-wf-approval-action][data-wf-attempt-id]").forEach(o=>{o.addEventListener("click",()=>{let r=o.dataset.wfAttemptId,s=o.dataset.wfApprovalAction;!r||s!=="approve"&&s!=="reject"||n.onResolve(r,s)})})}function F(e,n,o,r){let s=Le(e,n);return`<details class="wf-io-block" data-io-key="${i(s)}"${r.has(s)?" open":""}>
|
|
531
|
+
<summary>${i(n)} ${Ln(o)}</summary>
|
|
532
|
+
${En(o)}
|
|
533
|
+
</details>`}function Le(e,n){return`${e}:${n}`}function yn(e,n){if(!n)return!1;for(let o of e.querySelectorAll("[data-wf-attempt-card]"))if(o.dataset.wfAttemptCard===n)return o.scrollIntoView({block:"center"}),!0;return!1}function $n(e,n,o){e.querySelectorAll("details.wf-io-block[data-io-key]").forEach(r=>{let s=r.dataset.ioKey;if(!s)return;r.open?n.add(s):n.delete(s);let a=r.querySelector(".wf-io-pre");a&&o.set(s,a.scrollTop)})}function Sn(e,n){e.querySelectorAll("details.wf-io-block[data-io-key]").forEach(o=>{o.dataset.ioToggleBound!=="1"&&(o.dataset.ioToggleBound="1",o.addEventListener("toggle",()=>{let r=o.dataset.ioKey;r&&(o.open?n.add(r):n.delete(r))}))})}function In(e,n){e.querySelectorAll("details.wf-io-block[data-io-key]").forEach(o=>{let r=o.dataset.ioKey;if(!r)return;let s=n.get(r);if(s===void 0)return;let a=o.querySelector(".wf-io-pre");a&&(a.scrollTop=s)})}function Tn(e,n){e.querySelectorAll("details.wf-io-block[data-io-key]").forEach(o=>{let r=o.dataset.ioKey;if(!r)return;let s=o.querySelector(".wf-io-pre");s&&s.dataset.ioScrollBound!=="1"&&(s.dataset.ioScrollBound="1",s.addEventListener("scroll",()=>{n.set(r,s.scrollTop)}))})}function Ln(e){if(!e)return`<span class="muted">${i(t("workflow.detail.empty"))}</span>`;let n=[];return e.outputBytes!==void 0&&n.push(`${e.outputBytes}B`),e.truncated&&n.push(t("workflow.detail.truncated")),e.error&&n.push(t("workflow.detail.error")),e.outputHash&&n.push(me(e.outputHash)),n.length?`<span class="muted">${i(n.join(" \xB7 "))}</span>`:""}function En(e){if(!e)return`<div class="muted wf-io-empty">${i(t("workflow.detail.noData"))}</div>`;let n=e.value!==void 0?JSON.stringify(e.value,null,2):e.text??"",o=e.error?`<div class="muted error">${i(e.error)}</div>`:"";return n?`${o}<pre class="wf-io-pre">${i(n)}</pre>`:`${o}<div class="muted wf-io-empty">${i(t("workflow.detail.noPreview"))}</div>`}function Mn(e){let n=[];if(e.effectAttempted&&n.push(`${i(t("workflow.detail.effect"))} ${i(e.effectAttempted.provider)}`),e.wait){let o=e.wait.resolution?`${e.wait.resolution.kind}${e.wait.resolution.resolution?":"+e.wait.resolution.resolution:""}`:t("workflow.detail.open");n.push(`${i(t("workflow.detail.wait"))} ${i(e.wait.waitKind)} ${i(o)}`),e.wait.deadlineAt!==void 0&&n.push(`${i(t("workflow.detail.deadline"))} ${i(we(e.wait.deadlineAt))}`)}if(e.error){let o=`${e.error.errorCode}${e.error.errorClass?` \xB7 ${e.error.errorClass}`:""}`;n.push(`<span class="muted error">${i(o)}</span>`),e.error.errorMessage&&n.push(`<span class="error wf-error-msg">${i(e.error.errorMessage)}</span>`)}return e.output&&n.push(`${i(t("workflow.detail.output"))} ${i(me(e.output.outputHash))}`),e.runningMs!==void 0&&n.push(`${e.runningMs}ms`),n.length>0?n.join("<br/>"):'<span class="muted">-</span>'}function An(e,n){e.innerHTML=n.length>0?n.map(Cn).join(""):`<tr><td colspan="7" class="empty">${i(t("workflow.detail.noEvents"))}</td></tr>`}function Cn(e){let n=Hn(e.payload);return`<tr>
|
|
534
|
+
<td>${oe(e.eventId)}</td>
|
|
535
|
+
<td><code>${i(e.type)}</code></td>
|
|
536
|
+
<td>${i(e.actor)}</td>
|
|
537
|
+
<td>${n.nodeId?`<code>${i(n.nodeId)}</code>`:"-"}</td>
|
|
538
|
+
<td>${n.activityId?`<code>${i(n.activityId)}</code>`:"-"}</td>
|
|
539
|
+
<td>${n.errorCode?`<span class="muted error">${i(n.errorCode)}</span>`:"-"}</td>
|
|
540
|
+
<td title="${i(new Date(e.timestamp).toISOString())}">${i(we(e.timestamp))}</td>
|
|
541
|
+
</tr>`}function oe(e){let n=e.lastIndexOf("-");if(n<0)return 0;let o=Number(e.slice(n+1));return Number.isFinite(o)?o:0}function Hn(e){if(!e||typeof e!="object"||"ref"in e)return{};let n=e,o={};typeof n.nodeId=="string"&&(o.nodeId=n.nodeId),typeof n.activityId=="string"&&(o.activityId=n.activityId),typeof n.failedNodeId=="string"&&(o.nodeId=n.failedNodeId);let r=n.error;return r&&typeof r=="object"&&"errorCode"in r&&(o.errorCode=String(r.errorCode)),o}function Rn(e){return!e.payload||typeof e.payload!="object"||"ref"in e.payload?null:e.payload}function Ze(e,n,o){return Math.min(o,Math.max(n,e))}function me(e){return e?e.length>18?e.slice(0,10)+"..."+e.slice(-6):e:"-"}function On(e,n){return e.length>n?e.slice(0,n-1)+"\u2026":e}function we(e){return new Date(e).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",second:"2-digit"})}function M(e){return e.replace(/[&<>"']/g,n=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[n])}function ct(e){return e?e.length>18?e.slice(0,10)+"..."+e.slice(-6):e:"-"}function ut(e){let n=location.hash.match(/^#\/(?:workflows\/catalog|workflows-catalog)\/([^/?#]+)$/);return n?Dn(e,decodeURIComponent(n[1])):xn(e)}function xn(e){e.innerHTML=`
|
|
542
|
+
<nav class="wf-subnav">
|
|
543
|
+
<a href="#/workflows" data-i18n="workflow.subnav.runs">${M(t("workflow.subnav.runs"))}</a>
|
|
544
|
+
<a href="#/workflows/catalog" class="active" data-i18n="workflow.subnav.catalog">${M(t("workflow.subnav.catalog"))}</a>
|
|
545
|
+
</nav>
|
|
546
|
+
<section class="catalog-head">
|
|
547
|
+
<div>
|
|
548
|
+
<h2>${M(t("catalog.title"))}</h2>
|
|
549
|
+
<p class="muted">${M(t("catalog.subtitle"))}</p>
|
|
550
|
+
</div>
|
|
551
|
+
<button id="catalog-refresh" type="button">${M(t("catalog.refresh"))}</button>
|
|
552
|
+
</section>
|
|
553
|
+
<form id="catalog-filters" class="filters">
|
|
554
|
+
<input type="search" name="q" placeholder="${M(t("catalog.searchPlaceholder"))}" />
|
|
555
|
+
<span id="catalog-status" class="muted"></span>
|
|
556
|
+
</form>
|
|
557
|
+
<div class="wf-table-scroll">
|
|
558
|
+
<table>
|
|
559
|
+
<thead><tr>
|
|
560
|
+
<th>${M(t("catalog.table.workflow"))}</th>
|
|
561
|
+
<th>${M(t("catalog.table.version"))}</th>
|
|
562
|
+
<th>${M(t("catalog.table.params"))}</th>
|
|
563
|
+
<th>${M(t("catalog.table.nodes"))}</th>
|
|
564
|
+
<th>${M(t("catalog.table.revision"))}</th>
|
|
565
|
+
<th>${M(t("catalog.table.path"))}</th>
|
|
566
|
+
</tr></thead>
|
|
567
|
+
<tbody id="catalog-tbody"></tbody>
|
|
568
|
+
</table>
|
|
569
|
+
</div>
|
|
570
|
+
`;let n=e.querySelector("#catalog-tbody"),o=e.querySelector("#catalog-status"),r=e.querySelector("#catalog-filters"),s=e.querySelector("#catalog-refresh"),a=[],c=null,p=!1;function y(){let T=(new FormData(r).get("q")??"").trim().toLowerCase();return T?a.filter(l=>l.workflowId.toLowerCase().includes(T)||l.path.toLowerCase().includes(T)):a}function S(){c?(o.textContent=t("catalog.loadFailed",{error:c}),o.classList.add("error")):(o.textContent=`${a.length}`,o.classList.remove("error"));let T=y();if(T.length===0){n.innerHTML=`<tr><td colspan="6" class="empty">${a.length===0?M(t("catalog.noDefinitions")):M(t("catalog.noFilterMatch"))}</td></tr>`;return}n.innerHTML=T.map(l=>`
|
|
571
|
+
<tr>
|
|
572
|
+
<td><a href="#/workflows/catalog/${encodeURIComponent(l.workflowId)}"><code>${M(l.workflowId)}</code></a></td>
|
|
573
|
+
<td>${l.version}</td>
|
|
574
|
+
<td>${M(t("catalog.paramSummary",{required:l.requiredParamCount,total:l.paramCount}))}</td>
|
|
575
|
+
<td>${l.nodeCount}</td>
|
|
576
|
+
<td><code>${M(ct(l.revisionId))}</code></td>
|
|
577
|
+
<td><code>${M(l.path)}</code></td>
|
|
578
|
+
</tr>
|
|
579
|
+
`).join("")}async function v(){s.disabled=!0,o.textContent=t("catalog.loading");try{let T=await fetch("/api/workflows/definitions");if(!T.ok)throw new Error(`HTTP ${T.status}`);a=(await T.json()).definitions??[],c=null}catch(T){c=T?.message??String(T),a=[]}finally{s.disabled=!1,p||S()}}return r.addEventListener("input",S),s.addEventListener("click",()=>{v()}),v(),()=>{p=!0}}function Dn(e,n){e.innerHTML=`
|
|
580
|
+
<div class="catalog-detail-head">
|
|
581
|
+
<a class="btn-link" href="#/workflows/catalog">${M(t("catalog.back"))}</a>
|
|
582
|
+
<div>
|
|
583
|
+
<h2><code>${M(n)}</code></h2>
|
|
584
|
+
<div id="catalog-detail-subtitle" class="muted">${M(t("workflow.detail.loading"))}</div>
|
|
585
|
+
</div>
|
|
586
|
+
</div>
|
|
587
|
+
<section id="catalog-error" class="hint-warn" hidden></section>
|
|
588
|
+
<section id="catalog-run-status" class="hint-ok" hidden></section>
|
|
589
|
+
<div id="catalog-detail-body"></div>
|
|
590
|
+
`;let o=e.querySelector("#catalog-detail-subtitle"),r=e.querySelector("#catalog-error"),s=e.querySelector("#catalog-run-status"),a=e.querySelector("#catalog-detail-body"),c=null,p=!1,y=!1;function S(u){r.hidden=!u,r.textContent=u??""}function v(u){s.hidden=!u,s.textContent=u??""}function T(u){let $={};for(let[k,b]of Object.entries(u??{}))"default"in b&&($[k]=b.default);return $}function l(){if(!c)return;let u=c.definition;o.textContent=`${t("catalog.revision")} ${ct(c.revisionId)} \xB7 ${c.path}`;let $=JSON.stringify(T(u.params),null,2);a.innerHTML=`
|
|
591
|
+
<section class="wf-panel">
|
|
592
|
+
<div class="wf-panel-title"><h3>${M(t("catalog.summary"))}</h3></div>
|
|
593
|
+
<div class="wf-summary-grid">
|
|
594
|
+
<div class="wf-summary-item"><span>${M(t("catalog.table.workflow"))}</span><strong><code>${M(u.workflowId)}</code></strong></div>
|
|
595
|
+
<div class="wf-summary-item"><span>${M(t("catalog.table.version"))}</span><strong>${u.version}</strong></div>
|
|
596
|
+
<div class="wf-summary-item"><span>${M(t("catalog.nodeCount"))}</span><strong>${Object.keys(u.nodes).length}</strong></div>
|
|
597
|
+
<div class="wf-summary-item"><span>${M(t("catalog.path"))}</span><strong><code>${M(c.path)}</code></strong></div>
|
|
598
|
+
</div>
|
|
599
|
+
</section>
|
|
600
|
+
|
|
601
|
+
<section class="wf-panel">
|
|
602
|
+
<div class="wf-panel-title"><h3>${M(t("catalog.runPanel"))}</h3></div>
|
|
603
|
+
<form id="catalog-run-form" class="catalog-run-form">
|
|
604
|
+
<label>
|
|
605
|
+
<span>${M(t("catalog.paramsJson"))}</span>
|
|
606
|
+
<textarea id="catalog-params" rows="8" spellcheck="false" placeholder="${M(t("catalog.paramsPlaceholder"))}">${M($)}</textarea>
|
|
607
|
+
</label>
|
|
608
|
+
<div class="catalog-chat-grid">
|
|
609
|
+
<label>
|
|
610
|
+
<span>${M(t("catalog.chatId"))}</span>
|
|
611
|
+
<input id="catalog-chat-id" type="text" autocomplete="off" />
|
|
612
|
+
</label>
|
|
613
|
+
<label>
|
|
614
|
+
<span>${M(t("catalog.larkAppId"))}</span>
|
|
615
|
+
<input id="catalog-lark-app-id" type="text" autocomplete="off" />
|
|
616
|
+
</label>
|
|
617
|
+
</div>
|
|
618
|
+
<div class="muted">${M(t("catalog.chatBindingHint"))}</div>
|
|
619
|
+
<div id="catalog-param-errors" class="catalog-param-errors" hidden></div>
|
|
620
|
+
<button id="catalog-run-btn" type="submit" class="primary">${M(t("catalog.run"))}</button>
|
|
621
|
+
</form>
|
|
622
|
+
</section>
|
|
623
|
+
|
|
624
|
+
<section class="wf-panel">
|
|
625
|
+
<div class="wf-panel-title"><h3>${M(t("catalog.paramsSchema"))}</h3></div>
|
|
626
|
+
${qn(u.params)}
|
|
627
|
+
</section>
|
|
628
|
+
|
|
629
|
+
<section class="wf-panel">
|
|
630
|
+
<div class="wf-panel-title"><h3>${M(t("catalog.definitionJson"))}</h3></div>
|
|
631
|
+
<pre class="wf-io-pre">${M(JSON.stringify(u,null,2))}</pre>
|
|
632
|
+
</section>
|
|
633
|
+
`,L()}async function I(){if(!c||y)return;let u=a.querySelector("#catalog-params"),$=a.querySelector("#catalog-chat-id"),k=a.querySelector("#catalog-lark-app-id"),b=a.querySelector("#catalog-run-btn"),A=a.querySelector("#catalog-param-errors"),d;try{if(d=JSON.parse(u.value||"{}"),!d||typeof d!="object"||Array.isArray(d))throw new Error(t("catalog.badParamsJson"))}catch(f){A.hidden=!1,A.innerHTML=`<div class="muted error">${M(f?.message??String(f))}</div>`;return}y=!0,b.disabled=!0,b.textContent=t("catalog.running"),A.hidden=!0,S(null),v(null);try{let f=await fetch(`/api/workflows/definitions/${encodeURIComponent(c.definition.workflowId)}/run`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({params:d,chatBinding:{chatId:$.value.trim(),larkAppId:k.value.trim()}})});if(f.status===401)throw new Error(t("catalog.writeAccess"));let h=await f.json().catch(()=>({}));if(!f.ok||!h.ok)throw h.issues?.length&&(A.hidden=!1,A.innerHTML=`<strong>${M(t("catalog.invalidParams"))}</strong><ul>${h.issues.map(E=>`<li>${M(t("catalog.issue",{path:E.path.length?E.path.join("."):"(root)",message:E.message}))}</li>`).join("")}</ul>`),new Error(h.hint??h.message??h.error??t("catalog.runHttp",{status:f.status}));v(t("catalog.runStarted")),h.runId&&(location.hash=`#/workflows/${encodeURIComponent(h.runId)}`)}catch(f){S(f?.message??String(f))}finally{y=!1,b.disabled=!1,b.textContent=t("catalog.run")}}function L(){a.querySelector("#catalog-run-form")?.addEventListener("submit",$=>{$.preventDefault(),I()})}async function g(){try{let u=await fetch(`/api/workflows/definitions/${encodeURIComponent(n)}`);if(u.status===404)throw new Error("unknown_workflow");if(!u.ok)throw new Error(`HTTP ${u.status}`);c=await u.json(),S(null),l()}catch(u){S(t("catalog.definitionLoadFailed",{error:u?.message??String(u)})),o.textContent=t("workflow.detail.loadFailed")}}return g().then(()=>{}),()=>{p=!0}}function qn(e){let n=Object.entries(e??{});return n.length===0?`<div class="muted">${M(t("catalog.noParams"))}</div>`:`<div class="catalog-param-list">${n.map(([o,r])=>`
|
|
634
|
+
<article class="catalog-param">
|
|
635
|
+
<header>
|
|
636
|
+
<code>${M(o)}</code>
|
|
637
|
+
<span class="wf-status">${M(r.required?t("catalog.required"):t("catalog.optional"))}</span>
|
|
638
|
+
<span class="muted">${M(r.type)}${r.format?` \xB7 ${M(r.format)}`:""}</span>
|
|
639
|
+
</header>
|
|
640
|
+
${r.description?`<div class="muted">${M(t("catalog.description"))}: ${M(r.description)}</div>`:""}
|
|
641
|
+
${"default"in r?`<pre class="wf-io-pre">${M(`${t("catalog.default")}: ${JSON.stringify(r.default,null,2)}`)}</pre>`:""}
|
|
642
|
+
</article>
|
|
643
|
+
`).join("")}</div>`}var V=null,ge=null;function he(){ge!==null&&(window.clearInterval(ge),ge=null)}function ft(){return V||(V=document.createElement("dialog"),V.className="onboarding-dialog",document.body.appendChild(V),V.addEventListener("close",he),V)}function Pn(e){return e.status==="waiting_for_scan"?t("botOnboarding.waiting"):e.status==="verifying"?t("botOnboarding.verifying"):e.status==="completed"?t("botOnboarding.completed"):e.status==="failed"?`${t("botOnboarding.failed")}: ${e.message??e.error??"unknown"}`:t("botOnboarding.starting")}function ae(e){let n=ft(),o=e.qrDataUrl?`<div class="qr-card">
|
|
644
|
+
<img class="qr-image" src="${e.qrDataUrl}" alt="${t("botOnboarding.qrAlt")}">
|
|
645
|
+
${e.qrUrl?`<a class="onboarding-link" href="${e.qrUrl}" target="_blank" rel="noopener">${t("botOnboarding.openLink")}</a>`:""}
|
|
646
|
+
</div>`:"",r=e.appId?`<p><b>App ID:</b> <code>${e.appId}</code></p>`:"",s=e.status==="completed"?`<p class="hint-ok">${t("botOnboarding.restartHint")}</p>`:"";n.innerHTML=`<article>
|
|
352
647
|
<header>
|
|
353
|
-
<h3>${
|
|
354
|
-
<p>${
|
|
648
|
+
<h3>${t("botOnboarding.title")}</h3>
|
|
649
|
+
<p>${t("botOnboarding.intro")}</p>
|
|
355
650
|
</header>
|
|
356
|
-
<p class="onboarding-status status-${
|
|
651
|
+
<p class="onboarding-status status-${e.status}">${Pn(e)}</p>
|
|
652
|
+
${o}
|
|
357
653
|
${r}
|
|
358
|
-
${
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
</article>`}async function Ne(t){let s=await fetch(`/api/bot-onboarding/${encodeURIComponent(t)}`),r=await s.json();if(!s.ok||!r?.job)throw new Error(r?.error??`http_${s.status}`);_(r.job),(r.job.status==="completed"||r.job.status==="failed")&&W()}async function Re(){W(),_({id:"",status:"starting"});let t=me();t.open||t.showModal();try{let s=await fetch("/api/bot-onboarding/start",{method:"POST"}),r=await s.json();if(!s.ok||!r?.job?.id)throw new Error(r?.error??`http_${s.status}`);_(r.job),G=window.setInterval(()=>{Ne(r.job.id).catch(m=>{W(),_({id:r.job.id,status:"failed",message:m instanceof Error?m.message:String(m)})})},1200)}catch(s){_({id:"",status:"failed",message:s instanceof Error?s.message:String(s)})}}function be(){let t=document.getElementById("add-bot-btn");t&&(t.onclick=()=>{Re()})}var F=document.getElementById("root");function V(){let t=location.hash||"#/";t.startsWith("#/groups")?ue(F):t.startsWith("#/bot-defaults")?ge(F):t.startsWith("#/schedules")?ce(F):t.startsWith("#/sessions")?ie(F):ae(F);for(let s of document.querySelectorAll(".sidebar-nav a")){let r=s.getAttribute("href")??"#/";s.classList.toggle("active",r===(t||"#/"))}}var X=document.getElementById("status");function ye(){X&&(X.textContent=C.online?e("status.live"):e("status.disconnected"),X.className="connection-status "+(C.online?"online":"offline"))}C.on(ye);function fe(){document.querySelectorAll("[data-i18n]").forEach(t=>{t.textContent=e(t.dataset.i18n??"")}),document.querySelectorAll("[data-locale]").forEach(t=>{t.classList.toggle("active",t.dataset.locale===O.locale)}),document.querySelectorAll("[data-theme-mode]").forEach(t=>{t.classList.toggle("active",t.dataset.themeMode===O.themeMode)}),ye()}function Pe(){document.querySelectorAll("[data-locale]").forEach(t=>{t.onclick=()=>O.setLocale(t.dataset.locale)}),document.querySelectorAll("[data-theme-mode]").forEach(t=>{t.onclick=()=>O.setThemeMode(t.dataset.themeMode)})}(async()=>{O.init(),Pe(),be(),O.on(()=>{fe(),V()}),fe();try{await Z()}catch(t){console.error("botmux dashboard bootstrap failed",t),C.setOnline(!1)}window.addEventListener("hashchange",V),V()})();})();
|
|
654
|
+
${s}
|
|
655
|
+
<form method="dialog"><button>${t("botOnboarding.close")}</button></form>
|
|
656
|
+
</article>`}async function Nn(e){let n=await fetch(`/api/bot-onboarding/${encodeURIComponent(e)}`),o=await n.json();if(!n.ok||!o?.job)throw new Error(o?.error??`http_${n.status}`);ae(o.job),(o.job.status==="completed"||o.job.status==="failed")&&he()}async function Bn(){he(),ae({id:"",status:"starting"});let e=ft();e.open||e.showModal();try{let n=await fetch("/api/bot-onboarding/start",{method:"POST"}),o=await n.json();if(!n.ok||!o?.job?.id)throw new Error(o?.error??`http_${n.status}`);ae(o.job),ge=window.setInterval(()=>{Nn(o.job.id).catch(r=>{he(),ae({id:o.job.id,status:"failed",message:r instanceof Error?r.message:String(r)})})},1200)}catch(n){ae({id:"",status:"failed",message:n instanceof Error?n.message:String(n)})}}function pt(){let e=document.getElementById("add-bot-btn");e&&(e.onclick=()=>{Bn()})}var Q=document.getElementById("root"),se=null;function Ee(){se&&(se(),se=null);let e=location.hash||"#/";e.startsWith("#/workflows/catalog")||e.startsWith("#/workflows-catalog")?se=ut(Q):e.startsWith("#/workflows")?se=et(Q):e.startsWith("#/groups")?ze(Q):e.startsWith("#/bot-defaults")?Qe(Q):e.startsWith("#/schedules")?Je(Q):e.startsWith("#/sessions")?We(Q):Ue(Q);for(let n of document.querySelectorAll(".sidebar-nav a")){let o=n.getAttribute("href")??"#/";n.classList.toggle("active",o===(e||"#/"))}}var Me=document.getElementById("status");function mt(){Me&&(Me.textContent=D.online?t("status.live"):t("status.disconnected"),Me.className="connection-status "+(D.online?"online":"offline"))}D.on(mt);function wt(){document.querySelectorAll("[data-i18n]").forEach(e=>{e.textContent=t(e.dataset.i18n??"")}),document.querySelectorAll("[data-locale]").forEach(e=>{e.classList.toggle("active",e.dataset.locale===J.locale)}),document.querySelectorAll("[data-theme-mode]").forEach(e=>{e.classList.toggle("active",e.dataset.themeMode===J.themeMode)}),mt()}function jn(){document.querySelectorAll("[data-locale]").forEach(e=>{e.onclick=()=>J.setLocale(e.dataset.locale)}),document.querySelectorAll("[data-theme-mode]").forEach(e=>{e.onclick=()=>J.setThemeMode(e.dataset.themeMode)})}(async()=>{J.init(),jn(),pt(),J.on(()=>{wt(),Ee()}),wt();try{await De()}catch(e){console.error("botmux dashboard bootstrap failed",e),D.setOnline(!1)}window.addEventListener("hashchange",Ee),Ee()})();})();
|