botmux 2.107.0 → 2.108.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +17 -14
- package/README.md +19 -14
- package/dist/adapters/backend/sandbox.d.ts +3 -1
- package/dist/adapters/backend/sandbox.d.ts.map +1 -1
- package/dist/adapters/backend/sandbox.js +19 -3
- package/dist/adapters/backend/sandbox.js.map +1 -1
- package/dist/adapters/backend/tmux-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-backend.js +16 -1
- package/dist/adapters/backend/tmux-backend.js.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.d.ts +24 -15
- package/dist/adapters/backend/tmux-pipe-backend.d.ts.map +1 -1
- package/dist/adapters/backend/tmux-pipe-backend.js +75 -43
- package/dist/adapters/backend/tmux-pipe-backend.js.map +1 -1
- package/dist/adapters/cli/genius.d.ts.map +1 -1
- package/dist/adapters/cli/genius.js +9 -15
- package/dist/adapters/cli/genius.js.map +1 -1
- package/dist/adapters/cli/grok.d.ts +73 -0
- package/dist/adapters/cli/grok.d.ts.map +1 -0
- package/dist/adapters/cli/grok.js +298 -0
- package/dist/adapters/cli/grok.js.map +1 -0
- package/dist/adapters/cli/oh-my-pi.d.ts.map +1 -1
- package/dist/adapters/cli/oh-my-pi.js +133 -12
- package/dist/adapters/cli/oh-my-pi.js.map +1 -1
- package/dist/adapters/cli/registry.d.ts +2 -1
- package/dist/adapters/cli/registry.d.ts.map +1 -1
- package/dist/adapters/cli/registry.js +3 -1
- package/dist/adapters/cli/registry.js.map +1 -1
- package/dist/adapters/cli/shared-hints.d.ts +8 -4
- package/dist/adapters/cli/shared-hints.d.ts.map +1 -1
- package/dist/adapters/cli/shared-hints.js +8 -4
- package/dist/adapters/cli/shared-hints.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +18 -16
- package/dist/adapters/cli/types.d.ts.map +1 -1
- package/dist/adapters/hook-installer.d.ts +5 -4
- package/dist/adapters/hook-installer.d.ts.map +1 -1
- package/dist/adapters/hook-installer.js +42 -0
- package/dist/adapters/hook-installer.js.map +1 -1
- package/dist/cli/send-dispatch.d.ts +21 -0
- package/dist/cli/send-dispatch.d.ts.map +1 -1
- package/dist/cli/send-dispatch.js +159 -0
- package/dist/cli/send-dispatch.js.map +1 -1
- package/dist/cli/session-list-liveness.d.ts +22 -0
- package/dist/cli/session-list-liveness.d.ts.map +1 -0
- package/dist/cli/session-list-liveness.js +22 -0
- package/dist/cli/session-list-liveness.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +182 -33
- package/dist/cli.js.map +1 -1
- package/dist/core/ask-types.d.ts +4 -0
- package/dist/core/ask-types.d.ts.map +1 -1
- package/dist/core/ask-types.js +10 -0
- package/dist/core/ask-types.js.map +1 -1
- package/dist/core/cost-calculator.d.ts.map +1 -1
- package/dist/core/cost-calculator.js +35 -13
- package/dist/core/cost-calculator.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +89 -16
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/dashboard-rows.d.ts.map +1 -1
- package/dist/core/dashboard-rows.js +6 -2
- package/dist/core/dashboard-rows.js.map +1 -1
- package/dist/core/dashboard-url.d.ts +6 -5
- package/dist/core/dashboard-url.d.ts.map +1 -1
- package/dist/core/dashboard-url.js +11 -8
- package/dist/core/dashboard-url.js.map +1 -1
- package/dist/core/maintenance.d.ts +11 -8
- package/dist/core/maintenance.d.ts.map +1 -1
- package/dist/core/maintenance.js +45 -20
- package/dist/core/maintenance.js.map +1 -1
- package/dist/core/per-bot-env.d.ts.map +1 -1
- package/dist/core/per-bot-env.js +4 -0
- package/dist/core/per-bot-env.js.map +1 -1
- package/dist/core/session-discovery.d.ts.map +1 -1
- package/dist/core/session-discovery.js +4 -0
- package/dist/core/session-discovery.js.map +1 -1
- package/dist/core/skills/cli-admin-command.d.ts.map +1 -1
- package/dist/core/skills/cli-admin-command.js +8 -2
- package/dist/core/skills/cli-admin-command.js.map +1 -1
- package/dist/core/skills/im-command.d.ts.map +1 -1
- package/dist/core/skills/im-command.js +3 -1
- package/dist/core/skills/im-command.js.map +1 -1
- package/dist/core/skills/sources.d.ts +33 -1
- package/dist/core/skills/sources.d.ts.map +1 -1
- package/dist/core/skills/sources.js +97 -0
- package/dist/core/skills/sources.js.map +1 -1
- package/dist/core/skills/types.d.ts +8 -0
- package/dist/core/skills/types.d.ts.map +1 -1
- package/dist/core/terminal-url.d.ts.map +1 -1
- package/dist/core/terminal-url.js +12 -2
- package/dist/core/terminal-url.js.map +1 -1
- package/dist/core/terminal-write-auth.d.ts +34 -0
- package/dist/core/terminal-write-auth.d.ts.map +1 -0
- package/dist/core/terminal-write-auth.js +76 -0
- package/dist/core/terminal-write-auth.js.map +1 -0
- package/dist/core/tmux-recovery.d.ts +11 -0
- package/dist/core/tmux-recovery.d.ts.map +1 -0
- package/dist/core/tmux-recovery.js +19 -0
- package/dist/core/tmux-recovery.js.map +1 -0
- package/dist/core/types.d.ts +10 -4
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/update-check.d.ts +2 -2
- package/dist/core/update-check.js +2 -2
- package/dist/core/worker-pool.d.ts +3 -0
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +70 -3
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +15 -8
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/auth.d.ts +1 -1
- package/dist/dashboard/auth.d.ts.map +1 -1
- package/dist/dashboard/auth.js +7 -2
- package/dist/dashboard/auth.js.map +1 -1
- package/dist/dashboard/bot-payload.d.ts +3 -0
- package/dist/dashboard/bot-payload.d.ts.map +1 -1
- package/dist/dashboard/bot-payload.js +3 -0
- package/dist/dashboard/bot-payload.js.map +1 -1
- package/dist/dashboard/connector-api.d.ts.map +1 -1
- package/dist/dashboard/connector-api.js +39 -13
- package/dist/dashboard/connector-api.js.map +1 -1
- package/dist/dashboard/history-senders.d.ts +37 -0
- package/dist/dashboard/history-senders.d.ts.map +1 -0
- package/dist/dashboard/history-senders.js +60 -0
- package/dist/dashboard/history-senders.js.map +1 -0
- package/dist/dashboard/roles-batch.d.ts +31 -0
- package/dist/dashboard/roles-batch.d.ts.map +1 -0
- package/dist/dashboard/roles-batch.js +87 -0
- package/dist/dashboard/roles-batch.js.map +1 -0
- package/dist/dashboard/settings-card-model.d.ts +4 -2
- package/dist/dashboard/settings-card-model.d.ts.map +1 -1
- package/dist/dashboard/settings-card-model.js +13 -5
- package/dist/dashboard/settings-card-model.js.map +1 -1
- package/dist/dashboard/settings-write-applier.d.ts +4 -1
- package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
- package/dist/dashboard/settings-write-applier.js +6 -1
- package/dist/dashboard/settings-write-applier.js.map +1 -1
- package/dist/dashboard/skill-install-request.d.ts +4 -0
- package/dist/dashboard/skill-install-request.d.ts.map +1 -1
- package/dist/dashboard/skill-install-request.js +11 -1
- package/dist/dashboard/skill-install-request.js.map +1 -1
- package/dist/dashboard/trigger-api.d.ts +9 -1
- package/dist/dashboard/trigger-api.d.ts.map +1 -1
- package/dist/dashboard/trigger-api.js +48 -10
- package/dist/dashboard/trigger-api.js.map +1 -1
- package/dist/dashboard/web/app.d.ts.map +1 -1
- package/dist/dashboard/web/app.js +286 -271
- package/dist/dashboard/web/app.js.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.d.ts +26 -0
- package/dist/dashboard/web/bot-defaults-page.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults-page.js +1408 -12
- package/dist/dashboard/web/bot-defaults-page.js.map +1 -1
- package/dist/dashboard/web/bot-defaults.d.ts +91 -4
- package/dist/dashboard/web/bot-defaults.d.ts.map +1 -1
- package/dist/dashboard/web/bot-defaults.js +43 -2141
- package/dist/dashboard/web/bot-defaults.js.map +1 -1
- package/dist/dashboard/web/bot-onboarding.d.ts +6 -1
- package/dist/dashboard/web/bot-onboarding.d.ts.map +1 -1
- package/dist/dashboard/web/bot-onboarding.js +261 -306
- package/dist/dashboard/web/bot-onboarding.js.map +1 -1
- package/dist/dashboard/web/connectors-page.d.ts +4 -0
- package/dist/dashboard/web/connectors-page.d.ts.map +1 -1
- package/dist/dashboard/web/connectors-page.js +228 -48
- package/dist/dashboard/web/connectors-page.js.map +1 -1
- package/dist/dashboard/web/dashboard-api.d.ts +9 -0
- package/dist/dashboard/web/dashboard-api.d.ts.map +1 -0
- package/dist/dashboard/web/dashboard-api.js +18 -0
- package/dist/dashboard/web/dashboard-api.js.map +1 -0
- package/dist/dashboard/web/dashboard-components.d.ts +92 -0
- package/dist/dashboard/web/dashboard-components.d.ts.map +1 -0
- package/dist/dashboard/web/dashboard-components.js +250 -0
- package/dist/dashboard/web/dashboard-components.js.map +1 -0
- package/dist/dashboard/web/dashboard-routes.d.ts.map +1 -1
- package/dist/dashboard/web/dashboard-routes.js +30 -183
- package/dist/dashboard/web/dashboard-routes.js.map +1 -1
- package/dist/dashboard/web/floating-scrollbars.d.ts +2 -0
- package/dist/dashboard/web/floating-scrollbars.d.ts.map +1 -0
- package/dist/dashboard/web/floating-scrollbars.js +264 -0
- package/dist/dashboard/web/floating-scrollbars.js.map +1 -0
- package/dist/dashboard/web/groups-api.d.ts +36 -0
- package/dist/dashboard/web/groups-api.d.ts.map +1 -0
- package/dist/dashboard/web/groups-api.js +44 -0
- package/dist/dashboard/web/groups-api.js.map +1 -0
- package/dist/dashboard/web/groups-page.d.ts.map +1 -1
- package/dist/dashboard/web/groups-page.js +746 -15
- package/dist/dashboard/web/groups-page.js.map +1 -1
- package/dist/dashboard/web/groups.d.ts +51 -18
- package/dist/dashboard/web/groups.d.ts.map +1 -1
- package/dist/dashboard/web/groups.js +145 -1096
- package/dist/dashboard/web/groups.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +545 -255
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/insights-page.d.ts.map +1 -1
- package/dist/dashboard/web/insights-page.js +1047 -14
- package/dist/dashboard/web/insights-page.js.map +1 -1
- package/dist/dashboard/web/insights.d.ts +177 -7
- package/dist/dashboard/web/insights.d.ts.map +1 -1
- package/dist/dashboard/web/insights.js +187 -1926
- package/dist/dashboard/web/insights.js.map +1 -1
- package/dist/dashboard/web/legacy-workflow-page.d.ts +3 -0
- package/dist/dashboard/web/legacy-workflow-page.d.ts.map +1 -0
- package/dist/dashboard/web/legacy-workflow-page.js +717 -0
- package/dist/dashboard/web/legacy-workflow-page.js.map +1 -0
- package/dist/dashboard/web/monitor-room.d.ts.map +1 -1
- package/dist/dashboard/web/monitor-room.js +10 -8
- package/dist/dashboard/web/monitor-room.js.map +1 -1
- package/dist/dashboard/web/monitoring-page.d.ts.map +1 -1
- package/dist/dashboard/web/monitoring-page.js +188 -14
- package/dist/dashboard/web/monitoring-page.js.map +1 -1
- package/dist/dashboard/web/office-page.d.ts.map +1 -1
- package/dist/dashboard/web/office-page.js +11 -23
- package/dist/dashboard/web/office-page.js.map +1 -1
- package/dist/dashboard/web/overview-page.d.ts.map +1 -1
- package/dist/dashboard/web/overview-page.js +141 -147
- package/dist/dashboard/web/overview-page.js.map +1 -1
- package/dist/dashboard/web/overview.d.ts +0 -1
- package/dist/dashboard/web/overview.d.ts.map +1 -1
- package/dist/dashboard/web/overview.js +3 -275
- package/dist/dashboard/web/overview.js.map +1 -1
- package/dist/dashboard/web/preferences.d.ts +1 -6
- package/dist/dashboard/web/preferences.d.ts.map +1 -1
- package/dist/dashboard/web/preferences.js +0 -21
- package/dist/dashboard/web/preferences.js.map +1 -1
- package/dist/dashboard/web/role-batch.d.ts +9 -0
- package/dist/dashboard/web/role-batch.d.ts.map +1 -0
- package/dist/dashboard/web/role-batch.js +42 -0
- package/dist/dashboard/web/role-batch.js.map +1 -0
- package/dist/dashboard/web/roles-page.d.ts.map +1 -1
- package/dist/dashboard/web/roles-page.js +538 -13
- package/dist/dashboard/web/roles-page.js.map +1 -1
- package/dist/dashboard/web/roles.d.ts +98 -1
- package/dist/dashboard/web/roles.d.ts.map +1 -1
- package/dist/dashboard/web/roles.js +117 -903
- package/dist/dashboard/web/roles.js.map +1 -1
- package/dist/dashboard/web/schedules-page.d.ts.map +1 -1
- package/dist/dashboard/web/schedules-page.js +82 -24
- package/dist/dashboard/web/schedules-page.js.map +1 -1
- package/dist/dashboard/web/sessions-kanban.d.ts +67 -0
- package/dist/dashboard/web/sessions-kanban.d.ts.map +1 -0
- package/dist/dashboard/web/sessions-kanban.js +481 -0
- package/dist/dashboard/web/sessions-kanban.js.map +1 -0
- package/dist/dashboard/web/sessions-page.d.ts +4 -0
- package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
- package/dist/dashboard/web/sessions-page.js +1633 -18
- package/dist/dashboard/web/sessions-page.js.map +1 -1
- package/dist/dashboard/web/sessions.d.ts +45 -4
- package/dist/dashboard/web/sessions.d.ts.map +1 -1
- package/dist/dashboard/web/sessions.js +53 -2535
- package/dist/dashboard/web/sessions.js.map +1 -1
- package/dist/dashboard/web/settings-page.d.ts +0 -2
- package/dist/dashboard/web/settings-page.d.ts.map +1 -1
- package/dist/dashboard/web/settings-page.js +127 -117
- package/dist/dashboard/web/settings-page.js.map +1 -1
- package/dist/dashboard/web/skills-page.d.ts +10 -1
- package/dist/dashboard/web/skills-page.d.ts.map +1 -1
- package/dist/dashboard/web/skills-page.js +235 -93
- package/dist/dashboard/web/skills-page.js.map +1 -1
- package/dist/dashboard/web/team-federation-page.d.ts.map +1 -1
- package/dist/dashboard/web/team-federation-page.js +446 -28
- package/dist/dashboard/web/team-federation-page.js.map +1 -1
- package/dist/dashboard/web/team-federation.d.ts +91 -2
- package/dist/dashboard/web/team-federation.d.ts.map +1 -1
- package/dist/dashboard/web/team-federation.js +87 -474
- package/dist/dashboard/web/team-federation.js.map +1 -1
- package/dist/dashboard/web/theme-menu.d.ts +1 -0
- package/dist/dashboard/web/theme-menu.d.ts.map +1 -1
- package/dist/dashboard/web/theme-menu.js +79 -20
- package/dist/dashboard/web/theme-menu.js.map +1 -1
- package/dist/dashboard/web/ui.d.ts +1 -2
- package/dist/dashboard/web/ui.d.ts.map +1 -1
- package/dist/dashboard/web/ui.js +8 -20
- package/dist/dashboard/web/ui.js.map +1 -1
- package/dist/dashboard/web/v3-api.d.ts +16 -0
- package/dist/dashboard/web/v3-api.d.ts.map +1 -0
- package/dist/dashboard/web/v3-api.js +21 -0
- package/dist/dashboard/web/v3-api.js.map +1 -0
- package/dist/dashboard/web/v3-components.d.ts +2 -0
- package/dist/dashboard/web/v3-components.d.ts.map +1 -0
- package/dist/dashboard/web/v3-components.js +290 -0
- package/dist/dashboard/web/v3-components.js.map +1 -0
- package/dist/dashboard/web/v3-model.d.ts +80 -0
- package/dist/dashboard/web/v3-model.d.ts.map +1 -0
- package/dist/dashboard/web/v3-model.js +240 -0
- package/dist/dashboard/web/v3-model.js.map +1 -0
- package/dist/dashboard/web/v3-page.d.ts.map +1 -1
- package/dist/dashboard/web/v3-page.js +2 -22
- package/dist/dashboard/web/v3-page.js.map +1 -1
- package/dist/dashboard/web/v3-terminal.d.ts +14 -0
- package/dist/dashboard/web/v3-terminal.d.ts.map +1 -1
- package/dist/dashboard/web/v3-terminal.js +31 -19
- package/dist/dashboard/web/v3-terminal.js.map +1 -1
- package/dist/dashboard/web/webhook-logs-page.d.ts +16 -0
- package/dist/dashboard/web/webhook-logs-page.d.ts.map +1 -0
- package/dist/dashboard/web/webhook-logs-page.js +195 -0
- package/dist/dashboard/web/webhook-logs-page.js.map +1 -0
- package/dist/dashboard/web/whiteboards-page.d.ts.map +1 -1
- package/dist/dashboard/web/whiteboards-page.js +18 -23
- package/dist/dashboard/web/whiteboards-page.js.map +1 -1
- package/dist/dashboard/web/workflow-version-switch.d.ts +8 -0
- package/dist/dashboard/web/workflow-version-switch.d.ts.map +1 -0
- package/dist/dashboard/web/workflow-version-switch.js +9 -0
- package/dist/dashboard/web/workflow-version-switch.js.map +1 -0
- package/dist/dashboard/web/workflows.d.ts +272 -1
- package/dist/dashboard/web/workflows.d.ts.map +1 -1
- package/dist/dashboard/web/workflows.js +211 -1397
- package/dist/dashboard/web/workflows.js.map +1 -1
- package/dist/dashboard/webhook-routes.d.ts.map +1 -1
- package/dist/dashboard/webhook-routes.js +92 -39
- package/dist/dashboard/webhook-routes.js.map +1 -1
- package/dist/dashboard-web/app.js +1 -65
- package/dist/dashboard-web/brand-logo.png +0 -0
- package/dist/dashboard-web/chunks/bot-defaults-page-HSQ6HR3R.js +1 -0
- package/dist/dashboard-web/chunks/chunk-6TPODDDC.js +1 -0
- package/dist/dashboard-web/chunks/chunk-73EJP2MO.js +1 -0
- package/dist/dashboard-web/chunks/chunk-BRTZFELS.js +1 -0
- package/dist/dashboard-web/chunks/chunk-EVK77ASG.js +1 -0
- package/dist/dashboard-web/chunks/chunk-R3FXBFQH.js +1 -0
- package/dist/dashboard-web/chunks/chunk-VRLL7SNU.js +1 -0
- package/dist/dashboard-web/chunks/chunk-XCPSHD3G.js +37 -0
- package/dist/dashboard-web/chunks/chunk-XJ7BGPJH.js +1 -0
- package/dist/dashboard-web/chunks/chunk-YVRWYGXR.js +1 -0
- package/dist/dashboard-web/chunks/chunk-ZJING6FY.js +66 -0
- package/dist/dashboard-web/chunks/connectors-page-ODUA5UCW.js +1 -0
- package/dist/dashboard-web/chunks/groups-page-OYX5M6NK.js +8 -0
- package/dist/dashboard-web/chunks/insights-page-YCQO6OCR.js +22 -0
- package/dist/dashboard-web/chunks/legacy-workflow-page-XJAUNONP.js +2 -0
- package/dist/dashboard-web/chunks/{monitor-room-RFX4HPS5.js → monitor-room-WTZNYAFV.js} +17 -15
- package/dist/dashboard-web/chunks/monitoring-page-R6LJUK7G.js +2 -0
- package/dist/dashboard-web/chunks/office-page-4OXK3GNR.js +1 -0
- package/dist/dashboard-web/chunks/overview-page-4MVEJY6U.js +1 -0
- package/dist/dashboard-web/chunks/roles-page-NQDW27VV.js +1 -0
- package/dist/dashboard-web/chunks/schedules-page-JRNKYHOX.js +1 -0
- package/dist/dashboard-web/chunks/sessions-page-LF7UVI7B.js +3 -0
- package/dist/dashboard-web/chunks/settings-page-WGW7KQQS.js +2 -0
- package/dist/dashboard-web/chunks/skills-page-AMD4XP3T.js +3 -0
- package/dist/dashboard-web/chunks/team-federation-page-SYG3K4GG.js +1 -0
- package/dist/dashboard-web/chunks/v3-page-6NFFWNUK.js +94 -0
- package/dist/dashboard-web/chunks/whiteboards-page-FBRUK2AS.js +1 -0
- package/dist/dashboard-web/favicon.png +0 -0
- package/dist/dashboard-web/index.html +5 -69
- package/dist/dashboard-web/style.css +18835 -7211
- package/dist/dashboard.js +150 -32
- package/dist/dashboard.js.map +1 -1
- package/dist/global-config.d.ts +2 -2
- package/dist/global-config.d.ts.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +3 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +3 -0
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/card-builder.d.ts +7 -5
- package/dist/im/lark/card-builder.d.ts.map +1 -1
- package/dist/im/lark/card-builder.js +10 -7
- package/dist/im/lark/card-builder.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +25 -1
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +84 -0
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/platform/binding.d.ts +9 -0
- package/dist/platform/binding.d.ts.map +1 -1
- package/dist/platform/binding.js +14 -0
- package/dist/platform/binding.js.map +1 -1
- package/dist/platform/tunnel-client.d.ts.map +1 -1
- package/dist/platform/tunnel-client.js +9 -0
- package/dist/platform/tunnel-client.js.map +1 -1
- package/dist/services/bot-config-store.d.ts +1 -1
- package/dist/services/bot-config-store.d.ts.map +1 -1
- package/dist/services/bot-config-store.js +11 -5
- package/dist/services/bot-config-store.js.map +1 -1
- package/dist/services/file-bridge-path.d.ts +8 -0
- package/dist/services/file-bridge-path.d.ts.map +1 -0
- package/dist/services/file-bridge-path.js +75 -0
- package/dist/services/file-bridge-path.js.map +1 -0
- package/dist/services/grok-paths.d.ts +27 -0
- package/dist/services/grok-paths.d.ts.map +1 -0
- package/dist/services/grok-paths.js +106 -0
- package/dist/services/grok-paths.js.map +1 -0
- package/dist/services/grok-transcript.d.ts +67 -0
- package/dist/services/grok-transcript.d.ts.map +1 -0
- package/dist/services/grok-transcript.js +630 -0
- package/dist/services/grok-transcript.js.map +1 -0
- package/dist/services/hermes-session-filter.d.ts +28 -0
- package/dist/services/hermes-session-filter.d.ts.map +1 -0
- package/dist/services/hermes-session-filter.js +53 -0
- package/dist/services/hermes-session-filter.js.map +1 -0
- package/dist/services/hermes-transcript.d.ts +3 -0
- package/dist/services/hermes-transcript.d.ts.map +1 -1
- package/dist/services/hermes-transcript.js +20 -2
- package/dist/services/hermes-transcript.js.map +1 -1
- package/dist/services/resumable-session-discovery.d.ts +3 -0
- package/dist/services/resumable-session-discovery.d.ts.map +1 -1
- package/dist/services/resumable-session-discovery.js +6 -1
- package/dist/services/resumable-session-discovery.js.map +1 -1
- package/dist/services/skill-registry-store.d.ts +9 -0
- package/dist/services/skill-registry-store.d.ts.map +1 -1
- package/dist/services/skill-registry-store.js +304 -1
- package/dist/services/skill-registry-store.js.map +1 -1
- package/dist/services/structured-bridge-clis.d.ts +29 -0
- package/dist/services/structured-bridge-clis.d.ts.map +1 -0
- package/dist/services/structured-bridge-clis.js +76 -0
- package/dist/services/structured-bridge-clis.js.map +1 -0
- package/dist/services/trigger-log-store.d.ts +59 -0
- package/dist/services/trigger-log-store.d.ts.map +1 -1
- package/dist/services/trigger-log-store.js +81 -10
- package/dist/services/trigger-log-store.js.map +1 -1
- package/dist/services/usage-ledger.d.ts +4 -1
- package/dist/services/usage-ledger.d.ts.map +1 -1
- package/dist/services/usage-ledger.js +48 -10
- package/dist/services/usage-ledger.js.map +1 -1
- package/dist/services/webhook-audit.d.ts +10 -0
- package/dist/services/webhook-audit.d.ts.map +1 -0
- package/dist/services/webhook-audit.js +116 -0
- package/dist/services/webhook-audit.js.map +1 -0
- package/dist/setup/bot-config-editor.d.ts.map +1 -1
- package/dist/setup/bot-config-editor.js +2 -0
- package/dist/setup/bot-config-editor.js.map +1 -1
- package/dist/setup/ensure-tmux.d.ts +26 -12
- package/dist/setup/ensure-tmux.d.ts.map +1 -1
- package/dist/setup/ensure-tmux.js +85 -18
- package/dist/setup/ensure-tmux.js.map +1 -1
- package/dist/skills/definitions.d.ts.map +1 -1
- package/dist/skills/definitions.js +14 -1
- package/dist/skills/definitions.js.map +1 -1
- package/dist/skills/injection-mode.d.ts +16 -0
- package/dist/skills/injection-mode.d.ts.map +1 -1
- package/dist/skills/injection-mode.js +25 -0
- package/dist/skills/injection-mode.js.map +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/global-install.d.ts +22 -0
- package/dist/utils/global-install.d.ts.map +1 -0
- package/dist/utils/global-install.js +106 -0
- package/dist/utils/global-install.js.map +1 -0
- package/dist/utils/install-diagnostics.d.ts +1 -1
- package/dist/utils/install-diagnostics.d.ts.map +1 -1
- package/dist/utils/install-diagnostics.js +6 -4
- package/dist/utils/install-diagnostics.js.map +1 -1
- package/dist/utils/install-info.d.ts +2 -0
- package/dist/utils/install-info.d.ts.map +1 -1
- package/dist/utils/install-info.js +10 -4
- package/dist/utils/install-info.js.map +1 -1
- package/dist/worker.js +293 -172
- package/dist/worker.js.map +1 -1
- package/dist/workflows/events/payloads.d.ts +10 -10
- package/dist/workflows/events/schema.d.ts +28 -28
- package/package.json +8 -7
- package/dist/dashboard/web/connectors.d.ts +0 -2
- package/dist/dashboard/web/connectors.d.ts.map +0 -1
- package/dist/dashboard/web/connectors.js +0 -2
- package/dist/dashboard/web/connectors.js.map +0 -1
- package/dist/dashboard/web/office.d.ts +0 -2
- package/dist/dashboard/web/office.d.ts.map +0 -1
- package/dist/dashboard/web/office.js +0 -2
- package/dist/dashboard/web/office.js.map +0 -1
- package/dist/dashboard/web/schedules.d.ts +0 -2
- package/dist/dashboard/web/schedules.d.ts.map +0 -1
- package/dist/dashboard/web/schedules.js +0 -2
- package/dist/dashboard/web/schedules.js.map +0 -1
- package/dist/dashboard/web/settings.d.ts +0 -2
- package/dist/dashboard/web/settings.d.ts.map +0 -1
- package/dist/dashboard/web/settings.js +0 -2
- package/dist/dashboard/web/settings.js.map +0 -1
- package/dist/dashboard/web/skills.d.ts +0 -2
- package/dist/dashboard/web/skills.d.ts.map +0 -1
- package/dist/dashboard/web/skills.js +0 -2
- package/dist/dashboard/web/skills.js.map +0 -1
- package/dist/dashboard/web/skin-intro.d.ts +0 -4
- package/dist/dashboard/web/skin-intro.d.ts.map +0 -1
- package/dist/dashboard/web/skin-intro.js +0 -49
- package/dist/dashboard/web/skin-intro.js.map +0 -1
- package/dist/dashboard/web/v3.d.ts +0 -3
- package/dist/dashboard/web/v3.d.ts.map +0 -1
- package/dist/dashboard/web/v3.js +0 -539
- package/dist/dashboard/web/v3.js.map +0 -1
- package/dist/dashboard/web/whiteboards.d.ts +0 -2
- package/dist/dashboard/web/whiteboards.d.ts.map +0 -1
- package/dist/dashboard/web/whiteboards.js +0 -2
- package/dist/dashboard/web/whiteboards.js.map +0 -1
- package/dist/dashboard-web/chunks/bot-defaults-page-OCIHPGEU.js +0 -438
- package/dist/dashboard-web/chunks/chunk-3N2PPQVD.js +0 -1
- package/dist/dashboard-web/chunks/chunk-3UJYRR2Q.js +0 -37
- package/dist/dashboard-web/chunks/chunk-5E3MJJL4.js +0 -1
- package/dist/dashboard-web/chunks/chunk-7SZRL6FY.js +0 -1
- package/dist/dashboard-web/chunks/chunk-JTSE5VP6.js +0 -54
- package/dist/dashboard-web/chunks/chunk-LXIK7OUJ.js +0 -1
- package/dist/dashboard-web/chunks/chunk-PE6W3C2C.js +0 -1
- package/dist/dashboard-web/chunks/chunk-QW54DWU3.js +0 -325
- package/dist/dashboard-web/chunks/connectors-IGETYWCP.js +0 -1
- package/dist/dashboard-web/chunks/groups-page-MYBS4ZYJ.js +0 -207
- package/dist/dashboard-web/chunks/insights-page-UPA32V2S.js +0 -309
- package/dist/dashboard-web/chunks/monitoring-page-7T3BESZ2.js +0 -1
- package/dist/dashboard-web/chunks/office-page-6DXYOG55.js +0 -1
- package/dist/dashboard-web/chunks/overview-page-LJSBCQW4.js +0 -1
- package/dist/dashboard-web/chunks/roles-page-ESURTVQH.js +0 -98
- package/dist/dashboard-web/chunks/schedules-LML2ISOT.js +0 -1
- package/dist/dashboard-web/chunks/sessions-YB73XOTU.js +0 -1
- package/dist/dashboard-web/chunks/sessions-page-MCO5QR7Z.js +0 -1
- package/dist/dashboard-web/chunks/settings-page-OSDKNVS3.js +0 -2
- package/dist/dashboard-web/chunks/skills-page-PXBZJ6P6.js +0 -2
- package/dist/dashboard-web/chunks/team-federation-page-TTPVHFPX.js +0 -10
- package/dist/dashboard-web/chunks/v3-page-FVPUL665.js +0 -149
- package/dist/dashboard-web/chunks/whiteboards-page-HLCTYSSV.js +0 -1
- package/dist/dashboard-web/chunks/workflows-FCPM4S4D.js +0 -184
- package/dist/dashboard-web/skins/bluearchive-hero.webp +0 -0
- package/dist/dashboard-web/skins/dragonball-goku.webp +0 -0
- package/dist/dashboard-web/skins/dragonball-wukong.webp +0 -0
- package/dist/dashboard-web/skins/genshin-breeze.webp +0 -0
- package/dist/dashboard-web/skins/ikun-hero.webp +0 -0
- package/dist/dashboard-web/skins/prts-priestess.webp +0 -0
- package/dist/dashboard-web/skins/zzz-hero.webp +0 -0
- package/dist/dashboard-web/skins/zzz-pattern.svg +0 -25
|
@@ -1,17 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import { chatAvatarHtml, escapeHtml, t } from './ui.js';
|
|
6
|
-
import { hasExplicitChatRole, summarizeGroupProfileMatches, } from './role-profile-match.js';
|
|
7
|
-
let cache = { chats: [], bots: [] };
|
|
1
|
+
import { t } from './ui.js';
|
|
2
|
+
import { emptyGroupsSnapshot, fetchGroupsSnapshot, } from './groups-api.js';
|
|
3
|
+
import { effectiveRoleKey, loadEffectiveRoleMap } from './role-batch.js';
|
|
4
|
+
export { emptyGroupsSnapshot, fetchGroupsSnapshot, };
|
|
8
5
|
const PROFILE_ID_RE = /^[A-Za-z0-9._-]{1,64}$/;
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
const GROUP_ROLE_CONTEXT_CONCURRENCY = 6;
|
|
7
|
+
export const GROUPS_PAGE_SIZE = 30;
|
|
8
|
+
export function roleKey(larkAppId, chatId) {
|
|
9
|
+
return effectiveRoleKey(larkAppId, chatId);
|
|
10
|
+
}
|
|
11
|
+
/** Keep the expensive group coverage matrix bounded to one client-side page. */
|
|
12
|
+
export function paginateGroupRows(rows, requestedPage, pageSize = GROUPS_PAGE_SIZE) {
|
|
13
|
+
const safePageSize = Number.isFinite(pageSize) ? Math.max(1, Math.floor(pageSize)) : GROUPS_PAGE_SIZE;
|
|
14
|
+
const total = rows.length;
|
|
15
|
+
const totalPages = Math.max(1, Math.ceil(total / safePageSize));
|
|
16
|
+
const normalizedPage = Number.isFinite(requestedPage) ? Math.floor(requestedPage) : 1;
|
|
17
|
+
const page = Math.min(totalPages, Math.max(1, normalizedPage));
|
|
18
|
+
const start = (page - 1) * safePageSize;
|
|
19
|
+
const to = Math.min(total, start + safePageSize);
|
|
20
|
+
return {
|
|
21
|
+
rows: rows.slice(start, to),
|
|
22
|
+
page,
|
|
23
|
+
totalPages,
|
|
24
|
+
from: total === 0 ? 0 : start + 1,
|
|
25
|
+
to,
|
|
26
|
+
total,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
async function mapWithConcurrency(items, limit, worker) {
|
|
30
|
+
if (items.length === 0)
|
|
31
|
+
return [];
|
|
32
|
+
const results = new Array(items.length);
|
|
33
|
+
let nextIndex = 0;
|
|
34
|
+
const workerCount = Math.min(Math.max(1, limit), items.length);
|
|
35
|
+
await Promise.all(Array.from({ length: workerCount }, async () => {
|
|
36
|
+
while (nextIndex < items.length) {
|
|
37
|
+
const index = nextIndex++;
|
|
38
|
+
results[index] = await worker(items[index]);
|
|
39
|
+
}
|
|
40
|
+
}));
|
|
41
|
+
return results;
|
|
42
|
+
}
|
|
43
|
+
export function isValidProfileId(profileId) {
|
|
15
44
|
return PROFILE_ID_RE.test(profileId) && profileId !== '.' && profileId !== '..';
|
|
16
45
|
}
|
|
17
46
|
export function suggestRoleProfileIdFromChat(value) {
|
|
@@ -23,21 +52,14 @@ export function suggestRoleProfileIdFromChat(value) {
|
|
|
23
52
|
.slice(0, 64);
|
|
24
53
|
return isValidProfileId(cleaned) ? cleaned : 'profile';
|
|
25
54
|
}
|
|
26
|
-
async function
|
|
27
|
-
const r = await fetch('/api/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
/** Fetch /api/groups once and return the parsed payload without mutating
|
|
31
|
-
* `cache` — used by refreshUntilSeen so we can decide whether to commit. */
|
|
32
|
-
async function fetchGroups() {
|
|
33
|
-
const r = await fetch('/api/groups');
|
|
34
|
-
return r.json();
|
|
55
|
+
export async function fetchRoleProfileSummaries() {
|
|
56
|
+
const r = await fetch('/api/role-profiles');
|
|
57
|
+
const body = await r.json().catch(() => ({}));
|
|
58
|
+
return Array.isArray(body.profiles) ? body.profiles : [];
|
|
35
59
|
}
|
|
36
|
-
async function loadGroupRoleProfileContext() {
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const nextProfiles = Array.isArray(profileBody.profiles) ? profileBody.profiles : [];
|
|
40
|
-
const detailPairs = await Promise.all(nextProfiles.map(async (profile) => {
|
|
60
|
+
export async function loadGroupRoleProfileContext(snapshot) {
|
|
61
|
+
const nextProfiles = await fetchRoleProfileSummaries();
|
|
62
|
+
const detailPairs = await mapWithConcurrency(nextProfiles, GROUP_ROLE_CONTEXT_CONCURRENCY, async (profile) => {
|
|
41
63
|
try {
|
|
42
64
|
const r = await fetch(`/api/role-profiles/${encodeURIComponent(profile.profileId)}`);
|
|
43
65
|
const body = await r.json().catch(() => ({}));
|
|
@@ -46,43 +68,75 @@ async function loadGroupRoleProfileContext() {
|
|
|
46
68
|
catch {
|
|
47
69
|
return [profile.profileId, []];
|
|
48
70
|
}
|
|
49
|
-
})
|
|
50
|
-
const nextGroupRoles = new Map();
|
|
71
|
+
});
|
|
51
72
|
const seenRoleKeys = new Set();
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
73
|
+
const roleTargets = [];
|
|
74
|
+
for (const chat of snapshot.chats ?? []) {
|
|
75
|
+
for (const bot of chat.memberBots ?? []) {
|
|
76
|
+
// Profile matching intentionally considers explicit chat roles only.
|
|
77
|
+
// /api/groups already tells us whether one exists, so skip every
|
|
78
|
+
// unconfigured membership instead of reading its effective team role.
|
|
79
|
+
if (!bot?.inChat || !bot?.hasRole || !bot?.larkAppId)
|
|
80
|
+
continue;
|
|
81
|
+
const key = roleKey(bot.larkAppId, chat.chatId);
|
|
82
|
+
if (seenRoleKeys.has(key))
|
|
83
|
+
continue;
|
|
84
|
+
seenRoleKeys.add(key);
|
|
85
|
+
roleTargets.push({ chatId: chat.chatId, larkAppId: bot.larkAppId });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const nextGroupRoles = await loadEffectiveRoleMap(roleTargets);
|
|
89
|
+
return {
|
|
90
|
+
profiles: nextProfiles,
|
|
91
|
+
entriesById: new Map(detailPairs),
|
|
92
|
+
groupRoleContentByBot: nextGroupRoles,
|
|
93
|
+
loaded: true,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export async function collectGroupProfileEntries(chat) {
|
|
97
|
+
const inChat = (chat.memberBots ?? []).filter(bot => bot?.inChat && bot?.larkAppId);
|
|
98
|
+
return mapWithConcurrency(inChat, GROUP_ROLE_CONTEXT_CONCURRENCY, async (bot) => {
|
|
59
99
|
try {
|
|
60
100
|
const r = await fetch(`/api/roles/${encodeURIComponent(bot.larkAppId)}/${encodeURIComponent(chat.chatId)}`);
|
|
61
101
|
const body = await r.json().catch(() => ({}));
|
|
62
102
|
const hasEffectiveRole = body?.hasEffectiveRole ?? body?.hasRole;
|
|
63
103
|
const effectiveContent = 'effectiveContent' in body ? body.effectiveContent : body.content;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
104
|
+
const content = hasEffectiveRole ? String(effectiveContent ?? '').trim() : '';
|
|
105
|
+
const source = body?.effectiveSource === 'chat' || body?.effectiveSource === 'team'
|
|
106
|
+
? body.effectiveSource
|
|
107
|
+
: null;
|
|
108
|
+
return {
|
|
109
|
+
larkAppId: bot.larkAppId,
|
|
110
|
+
botName: bot.botName,
|
|
111
|
+
content,
|
|
112
|
+
status: content ? (source ?? 'chat') : 'empty',
|
|
113
|
+
};
|
|
68
114
|
}
|
|
69
115
|
catch {
|
|
70
|
-
|
|
116
|
+
return {
|
|
117
|
+
larkAppId: bot.larkAppId,
|
|
118
|
+
botName: bot.botName,
|
|
119
|
+
content: '',
|
|
120
|
+
status: 'error',
|
|
121
|
+
};
|
|
71
122
|
}
|
|
72
|
-
})
|
|
73
|
-
roleProfiles = nextProfiles;
|
|
74
|
-
roleProfileEntriesById = new Map(detailPairs);
|
|
75
|
-
groupRoleContentByBot = nextGroupRoles;
|
|
123
|
+
});
|
|
76
124
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
125
|
+
export function availableBotsForPicker(bots, excludeIds) {
|
|
126
|
+
return bots.filter(bot => !excludeIds || !excludeIds.has(bot.larkAppId));
|
|
127
|
+
}
|
|
128
|
+
export function filterGroupChats(chats, filters) {
|
|
129
|
+
const q = filters.q.trim().toLowerCase();
|
|
130
|
+
return chats
|
|
131
|
+
.filter(chat => !q ||
|
|
132
|
+
(chat.name ?? '').toLowerCase().includes(q) ||
|
|
133
|
+
chat.chatId.toLowerCase().includes(q) ||
|
|
134
|
+
(chat.ownerId ?? '').toLowerCase().includes(q))
|
|
135
|
+
.filter(chat => !filters.missingOnly || (chat.memberBots ?? []).some(member => !member.inChat));
|
|
81
136
|
}
|
|
82
137
|
/** True iff every expected bot id appears in the row's memberBots with
|
|
83
138
|
* inChat:true. Used by refreshUntilSeen to defer committing a canonical
|
|
84
|
-
* snapshot until all invited bots have caught up Lark-side.
|
|
85
|
-
* tests can exercise the predicate without spinning up jsdom. */
|
|
139
|
+
* snapshot until all invited bots have caught up Lark-side. */
|
|
86
140
|
export function allExpectedInChat(row, expectedBotIds) {
|
|
87
141
|
if (expectedBotIds.size === 0)
|
|
88
142
|
return true;
|
|
@@ -93,1062 +147,57 @@ export function allExpectedInChat(row, expectedBotIds) {
|
|
|
93
147
|
}
|
|
94
148
|
return true;
|
|
95
149
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
* for the Add-bots flow. Exported for tests. */
|
|
101
|
-
export function renderBotCheckboxes(bots, excludeIds) {
|
|
102
|
-
return bots
|
|
103
|
-
.filter(b => !excludeIds || !excludeIds.has(b.larkAppId))
|
|
104
|
-
.map(b => `
|
|
105
|
-
<label class="checkbox-row">
|
|
106
|
-
<input type="checkbox" name="bot" value="${escapeHtml(b.larkAppId)}">
|
|
107
|
-
${escapeHtml(b.botName ?? b.larkAppId)} <small>(${escapeHtml(b.larkAppId)})</small>
|
|
108
|
-
</label>
|
|
109
|
-
`).join('');
|
|
150
|
+
export function summarizeAddBotsResult(result) {
|
|
151
|
+
const rows = Array.isArray(result) ? result : [];
|
|
152
|
+
const okCount = rows.filter(row => !!row?.ok).length;
|
|
153
|
+
return { rows, okCount, failed: rows.length - okCount };
|
|
110
154
|
}
|
|
111
|
-
export function
|
|
155
|
+
export function roleProfileBootstrapStatus(profileId, messageId, error) {
|
|
112
156
|
const cleanProfileId = String(profileId ?? '').trim();
|
|
113
157
|
if (!cleanProfileId)
|
|
114
|
-
return
|
|
158
|
+
return null;
|
|
115
159
|
if (error) {
|
|
116
|
-
return
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
160
|
+
return {
|
|
161
|
+
kind: 'warn',
|
|
162
|
+
text: t('groups.roleProfileBootstrapFailed', {
|
|
163
|
+
name: cleanProfileId,
|
|
164
|
+
reason: String(error),
|
|
165
|
+
}),
|
|
166
|
+
};
|
|
120
167
|
}
|
|
121
168
|
const cleanMessageId = typeof messageId === 'string' && messageId.trim() ? messageId.trim() : '';
|
|
122
169
|
if (cleanMessageId) {
|
|
123
|
-
return
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
export function renderAddBotsResultSummary(result) {
|
|
131
|
-
const rows = Array.isArray(result) ? result : [];
|
|
132
|
-
if (!rows.length) {
|
|
133
|
-
return `<p class="hint-warn">没有返回添加结果。</p>`;
|
|
134
|
-
}
|
|
135
|
-
const okCount = rows.filter(x => !!x?.ok).length;
|
|
136
|
-
const failed = rows.length - okCount;
|
|
137
|
-
const cls = failed ? 'hint-warn' : 'hint-ok';
|
|
138
|
-
const items = rows.map(x => {
|
|
139
|
-
const id = String(x?.id ?? '?');
|
|
140
|
-
return x?.ok
|
|
141
|
-
? `<li><code>${escapeHtml(id)}</code>: OK</li>`
|
|
142
|
-
: `<li><code>${escapeHtml(id)}</code>: failed (${escapeHtml(String(x?.error ?? 'unknown'))})</li>`;
|
|
143
|
-
}).join('');
|
|
144
|
-
return `<div class="${cls}">
|
|
145
|
-
<strong>添加结果:成功 ${okCount}/${rows.length}${failed ? `,失败 ${failed}` : ''}</strong>
|
|
146
|
-
<ul>${items}</ul>
|
|
147
|
-
</div>`;
|
|
148
|
-
}
|
|
149
|
-
export function wireGroupsPage(root) {
|
|
150
|
-
let disposed = false;
|
|
151
|
-
const timers = new Set();
|
|
152
|
-
const delayResolvers = new Map();
|
|
153
|
-
const setTimer = (fn, ms) => {
|
|
154
|
-
const id = window.setTimeout(() => {
|
|
155
|
-
timers.delete(id);
|
|
156
|
-
if (!disposed)
|
|
157
|
-
fn();
|
|
158
|
-
}, ms);
|
|
159
|
-
timers.add(id);
|
|
160
|
-
return id;
|
|
161
|
-
};
|
|
162
|
-
const delay = (ms) => new Promise(resolve => {
|
|
163
|
-
const id = window.setTimeout(() => {
|
|
164
|
-
timers.delete(id);
|
|
165
|
-
delayResolvers.delete(id);
|
|
166
|
-
resolve();
|
|
167
|
-
}, ms);
|
|
168
|
-
timers.add(id);
|
|
169
|
-
delayResolvers.set(id, resolve);
|
|
170
|
-
});
|
|
171
|
-
const head = root.querySelector('#g-head');
|
|
172
|
-
const body = root.querySelector('#g-body');
|
|
173
|
-
const form = root.querySelector('#g-filters');
|
|
174
|
-
const refreshBtn = root.querySelector('#g-refresh');
|
|
175
|
-
const drawer = root.querySelector('#g-drawer');
|
|
176
|
-
refreshBtn.onclick = async () => {
|
|
177
|
-
refreshBtn.disabled = true;
|
|
178
|
-
try {
|
|
179
|
-
await loadGroups();
|
|
180
|
-
if (disposed)
|
|
181
|
-
return;
|
|
182
|
-
rerender();
|
|
183
|
-
void refreshRoleProfileContext();
|
|
184
|
-
}
|
|
185
|
-
finally {
|
|
186
|
-
if (!disposed)
|
|
187
|
-
refreshBtn.disabled = false;
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
const createBtn = root.querySelector('#g-create');
|
|
191
|
-
createBtn.onclick = () => { void openCreateModal(); };
|
|
192
|
-
// /api/groups 要扇出到所有 daemon、逐群查成员,慢——先亮 loading,回来再换表格。
|
|
193
|
-
const loadingEl = root.querySelector('#g-loading');
|
|
194
|
-
const tableWrap = root.querySelector('#g-table-wrap');
|
|
195
|
-
void (async () => {
|
|
196
|
-
try {
|
|
197
|
-
await loadGroups();
|
|
198
|
-
}
|
|
199
|
-
finally {
|
|
200
|
-
if (disposed)
|
|
201
|
-
return;
|
|
202
|
-
loadingEl.remove();
|
|
203
|
-
tableWrap.hidden = false;
|
|
204
|
-
rerender();
|
|
205
|
-
void refreshRoleProfileContext();
|
|
206
|
-
}
|
|
207
|
-
})();
|
|
208
|
-
async function refreshRoleProfileContext() {
|
|
209
|
-
try {
|
|
210
|
-
await loadGroupRoleProfileContext();
|
|
211
|
-
}
|
|
212
|
-
catch {
|
|
213
|
-
roleProfiles = [];
|
|
214
|
-
roleProfileEntriesById = new Map();
|
|
215
|
-
groupRoleContentByBot = new Map();
|
|
216
|
-
}
|
|
217
|
-
finally {
|
|
218
|
-
if (disposed)
|
|
219
|
-
return;
|
|
220
|
-
roleProfileContextLoaded = true;
|
|
221
|
-
rerender();
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
function setDialogStatus(el, html) {
|
|
225
|
-
el.innerHTML = html;
|
|
226
|
-
}
|
|
227
|
-
function renderDialogError(title, reason) {
|
|
228
|
-
return `<p class="hint-warn"><strong>${escapeHtml(title)}</strong><br><small>${escapeHtml(String(reason ?? 'unknown'))}</small></p>`;
|
|
229
|
-
}
|
|
230
|
-
function restoreSubmit(btn, label) {
|
|
231
|
-
if (!btn)
|
|
232
|
-
return;
|
|
233
|
-
btn.disabled = false;
|
|
234
|
-
btn.textContent = label;
|
|
235
|
-
}
|
|
236
|
-
async function openCreateModal() {
|
|
237
|
-
const allBots = cache.bots;
|
|
238
|
-
if (allBots.length === 0) {
|
|
239
|
-
alert(t('groups.noBotsOnline'));
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
let roleProfiles = [];
|
|
243
|
-
try {
|
|
244
|
-
const r = await fetch('/api/role-profiles');
|
|
245
|
-
const data = await r.json();
|
|
246
|
-
roleProfiles = data.profiles ?? [];
|
|
247
|
-
}
|
|
248
|
-
catch { /* profile selector is optional */ }
|
|
249
|
-
if (disposed)
|
|
250
|
-
return;
|
|
251
|
-
drawer.innerHTML = `
|
|
252
|
-
<article>
|
|
253
|
-
<header><h3>${t('groups.createTitle')}</h3></header>
|
|
254
|
-
<p>${t('groups.createHelp')}</p>
|
|
255
|
-
<form id="g-createform">
|
|
256
|
-
<label class="form-row">
|
|
257
|
-
<span>${t('groups.name')}</span>
|
|
258
|
-
<input type="text" name="name" placeholder="${t('groups.namePlaceholder')}" maxlength="60">
|
|
259
|
-
</label>
|
|
260
|
-
<label class="form-row">
|
|
261
|
-
<span>${t('groups.bindDir')}</span>
|
|
262
|
-
<input type="text" name="bindWorkingDir" placeholder="e.g. ~/projects/botmux">
|
|
263
|
-
<small>${t('groups.bindDirHelp')}</small>
|
|
264
|
-
</label>
|
|
265
|
-
<label class="form-row">
|
|
266
|
-
<span>${t('groups.roleProfile')}</span>
|
|
267
|
-
<select name="roleProfileId">
|
|
268
|
-
<option value="">${t('groups.roleProfileNone')}</option>
|
|
269
|
-
${roleProfiles.map(p => `<option value="${escapeHtml(p.profileId)}">${escapeHtml(p.profileId)}</option>`).join('')}
|
|
270
|
-
</select>
|
|
271
|
-
<small>${t('groups.roleProfileHelp')}</small>
|
|
272
|
-
</label>
|
|
273
|
-
<fieldset>
|
|
274
|
-
<legend>${t('groups.botPicker')}</legend>
|
|
275
|
-
${renderBotCheckboxes(allBots)}
|
|
276
|
-
</fieldset>
|
|
277
|
-
<div data-create-status aria-live="polite"></div>
|
|
278
|
-
<div class="actions">
|
|
279
|
-
<button type="submit" class="primary">${t('groups.createSubmit')}</button>
|
|
280
|
-
<button type="button" id="g-create-cancel">${t('groups.cancel')}</button>
|
|
281
|
-
</div>
|
|
282
|
-
</form>
|
|
283
|
-
</article>`;
|
|
284
|
-
drawer.showModal();
|
|
285
|
-
drawer.querySelector('#g-create-cancel').onclick = () => drawer.close();
|
|
286
|
-
drawer.querySelector('#g-createform').onsubmit = async (ev) => {
|
|
287
|
-
ev.preventDefault();
|
|
288
|
-
const fd = new FormData(ev.target);
|
|
289
|
-
const name = (fd.get('name') ?? '').trim();
|
|
290
|
-
const bindWorkingDir = (fd.get('bindWorkingDir') ?? '').trim();
|
|
291
|
-
const roleProfileId = (fd.get('roleProfileId') ?? '').trim();
|
|
292
|
-
const ids = fd.getAll('bot');
|
|
293
|
-
const statusEl = drawer.querySelector('[data-create-status]');
|
|
294
|
-
if (ids.length === 0) {
|
|
295
|
-
setDialogStatus(statusEl, renderDialogError('请选择 bot', '至少选择一个 bot 后再创建群聊。'));
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
const submitBtn = ev.target.querySelector('button[type=submit]');
|
|
299
|
-
if (submitBtn) {
|
|
300
|
-
submitBtn.disabled = true;
|
|
301
|
-
submitBtn.textContent = 'Creating...';
|
|
302
|
-
}
|
|
303
|
-
setDialogStatus(statusEl, '');
|
|
304
|
-
try {
|
|
305
|
-
const r = await fetch('/api/groups/create', {
|
|
306
|
-
method: 'POST',
|
|
307
|
-
headers: { 'content-type': 'application/json' },
|
|
308
|
-
body: JSON.stringify({
|
|
309
|
-
name: name || undefined,
|
|
310
|
-
larkAppIds: ids,
|
|
311
|
-
bindWorkingDir: bindWorkingDir || undefined,
|
|
312
|
-
roleProfileId: roleProfileId || undefined,
|
|
313
|
-
}),
|
|
314
|
-
});
|
|
315
|
-
// 兜底:平台反代抖动时可能回非 JSON 体(旧平台)或 502。别再让 r.json() 抛
|
|
316
|
-
// SyntaxError 弹「网络错误」——解析失败就当作一次失败,走下面的「创建失败」分支。
|
|
317
|
-
const respBody = await r.json().catch(() => ({ ok: false, error: `HTTP ${r.status}` }));
|
|
318
|
-
if (disposed)
|
|
319
|
-
return;
|
|
320
|
-
if (respBody.ok && respBody.chatId) {
|
|
321
|
-
renderCreateSuccess(respBody);
|
|
322
|
-
// Lark's chat.list has eventual consistency: the chat we just
|
|
323
|
-
// created via /api/groups/create often doesn't appear in the next
|
|
324
|
-
// /api/groups response for a few seconds. Without intervention the
|
|
325
|
-
// background refresh would overwrite cache with a stale snapshot
|
|
326
|
-
// and the new group would silently vanish from the matrix until
|
|
327
|
-
// the user hit Refresh manually.
|
|
328
|
-
//
|
|
329
|
-
// Optimistic insert: render the new group instantly using the
|
|
330
|
-
// creator's larkAppId + the form's selected bot ids minus any
|
|
331
|
-
// invalidBotIds Lark rejected. The refresh poll below keeps
|
|
332
|
-
// re-fetching until Lark confirms ALL expected bots show inChat,
|
|
333
|
-
// then commits the canonical response; until then we keep the
|
|
334
|
-
// optimistic row. Stopping on first sight of just `chatId` would
|
|
335
|
-
// commit a partial-membership snapshot — see Codex review.
|
|
336
|
-
const invalidBotIds = Array.isArray(respBody.invalidBotIds) ? respBody.invalidBotIds : [];
|
|
337
|
-
const validIds = ids.filter(id => !invalidBotIds.includes(id));
|
|
338
|
-
const expectedBotIds = new Set(validIds);
|
|
339
|
-
if (typeof respBody.creator === 'string' && respBody.creator)
|
|
340
|
-
expectedBotIds.add(respBody.creator);
|
|
341
|
-
injectOptimisticChat(respBody.chatId, name || respBody.chatId, validIds, respBody.creator);
|
|
342
|
-
rerender();
|
|
343
|
-
void refreshRoleProfileContext();
|
|
344
|
-
void refreshUntilSeen(respBody.chatId, expectedBotIds).catch(() => { });
|
|
345
|
-
}
|
|
346
|
-
else {
|
|
347
|
-
setDialogStatus(statusEl, renderDialogError('创建失败', respBody.error ?? `HTTP ${r.status}`));
|
|
348
|
-
restoreSubmit(submitBtn, t('groups.createSubmit'));
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
catch (e) {
|
|
352
|
-
setDialogStatus(statusEl, renderDialogError('网络错误', e));
|
|
353
|
-
restoreSubmit(submitBtn, t('groups.createSubmit'));
|
|
354
|
-
}
|
|
170
|
+
return {
|
|
171
|
+
kind: 'ok',
|
|
172
|
+
text: t('groups.roleProfileBootstrapSent', {
|
|
173
|
+
name: cleanProfileId,
|
|
174
|
+
messageId: cleanMessageId,
|
|
175
|
+
}),
|
|
355
176
|
};
|
|
356
|
-
function injectOptimisticChat(chatId, displayName, memberIds, creator) {
|
|
357
|
-
const inChatSet = new Set(memberIds);
|
|
358
|
-
if (creator)
|
|
359
|
-
inChatSet.add(creator);
|
|
360
|
-
const memberBots = cache.bots.map((b) => ({
|
|
361
|
-
larkAppId: b.larkAppId,
|
|
362
|
-
botName: b.botName,
|
|
363
|
-
inChat: inChatSet.has(b.larkAppId),
|
|
364
|
-
oncallChat: null,
|
|
365
|
-
}));
|
|
366
|
-
const optimistic = {
|
|
367
|
-
chatId,
|
|
368
|
-
name: displayName,
|
|
369
|
-
ownerId: creator ?? null,
|
|
370
|
-
memberBots,
|
|
371
|
-
};
|
|
372
|
-
// Drop any duplicate (defensive — shouldn't fire, but cheap) and
|
|
373
|
-
// prepend so the new row lands at the top of the matrix.
|
|
374
|
-
cache.chats = [optimistic, ...cache.chats.filter((c) => c.chatId !== chatId)];
|
|
375
|
-
}
|
|
376
|
-
async function refreshUntilSeen(chatId, expectedBotIds) {
|
|
377
|
-
// Total budget ≈ 0.6 + 5*1.2 = ~6.6s. Commit only when the row is
|
|
378
|
-
// present AND every expected bot reports inChat:true — otherwise we'd
|
|
379
|
-
// overwrite our optimistic ✓ marks with a partial canonical snapshot
|
|
380
|
-
// (creator daemon often lags vs. invitee daemons on Lark's side).
|
|
381
|
-
// If the budget runs out the optimistic row stays put; next manual
|
|
382
|
-
// Refresh will reconcile.
|
|
383
|
-
const delays = [600, 1200, 1200, 1200, 1200, 1200];
|
|
384
|
-
for (const d of delays) {
|
|
385
|
-
await delay(d);
|
|
386
|
-
if (disposed)
|
|
387
|
-
return;
|
|
388
|
-
let next;
|
|
389
|
-
try {
|
|
390
|
-
next = await fetchGroups();
|
|
391
|
-
}
|
|
392
|
-
catch {
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
if (disposed)
|
|
396
|
-
return;
|
|
397
|
-
const row = (next.chats ?? []).find((c) => c.chatId === chatId);
|
|
398
|
-
if (row && allExpectedInChat(row, expectedBotIds)) {
|
|
399
|
-
cache = next;
|
|
400
|
-
rerender();
|
|
401
|
-
void refreshRoleProfileContext();
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
177
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
// 仅当缺失(share-link API 失败)时回退到 applink。浏览器侧拿不到 brand,这个
|
|
411
|
-
// 回退默认 feishu host——属极少触发的兜底,不影响 Lark 正常建群(有 shareLink)。
|
|
412
|
-
const appLink = typeof resp.shareLink === 'string' && resp.shareLink
|
|
413
|
-
? resp.shareLink
|
|
414
|
-
: `https://applink.feishu.cn/client/chat/open?openChatId=${encodeURIComponent(chatId)}`;
|
|
415
|
-
const invalidBots = (resp.invalidBotIds ?? []);
|
|
416
|
-
const invalidUsers = (resp.invalidUserIds ?? []);
|
|
417
|
-
const auto = resp.autoInvitedOpenId;
|
|
418
|
-
const rejected = !!resp.autoInviteRejected;
|
|
419
|
-
const ownerTo = resp.ownerTransferredTo;
|
|
420
|
-
const transferErr = resp.transferError;
|
|
421
|
-
const notifyMsgId = resp.notifyMessageId;
|
|
422
|
-
const notifyErr = resp.notifyError;
|
|
423
|
-
const binds = Array.isArray(resp.oncallBindings) ? resp.oncallBindings : [];
|
|
424
|
-
const bindOk = binds.filter(b => b?.ok).length;
|
|
425
|
-
const bindFailed = binds.filter(b => !b?.ok);
|
|
426
|
-
const bindNote = binds.length > 0
|
|
427
|
-
? bindFailed.length === 0
|
|
428
|
-
? `<p class="hint-ok">已绑定目录:<code>${escapeHtml(resp.bindResolvedPath ?? '')}</code>(${bindOk}/${binds.length} bots)</p>`
|
|
429
|
-
: `<p class="hint-warn">目录绑定部分失败:成功 ${bindOk}/${binds.length}。${bindFailed.map(b => `<br><code>${escapeHtml(b.larkAppId ?? '?')}</code>: ${escapeHtml(b.error ?? 'unknown')}`).join('')}</p>`
|
|
430
|
-
: '';
|
|
431
|
-
let inviteNote;
|
|
432
|
-
if (auto) {
|
|
433
|
-
const transferLine = ownerTo
|
|
434
|
-
? `<br><small>群主已从机器人转让给你。</small>`
|
|
435
|
-
: transferErr
|
|
436
|
-
? `<br><small class="hint-warn-inline">⚠ 自动转让群主失败(${escapeHtml(transferErr)}),你现在是成员但群主仍是机器人。</small>`
|
|
437
|
-
: '';
|
|
438
|
-
const notifyLine = notifyMsgId
|
|
439
|
-
? `<br><small>机器人已在群里 @ 了你(消息 id <code>${escapeHtml(notifyMsgId)}</code>),看飞书通知就能进群。</small>`
|
|
440
|
-
: notifyErr
|
|
441
|
-
? `<br><small class="hint-warn-inline">⚠ 自动 @ 通知失败(${escapeHtml(notifyErr)}),新群可能不会主动出现在你侧边栏,建议从下面按钮跳进去。</small>`
|
|
442
|
-
: '';
|
|
443
|
-
inviteNote = `<p class="hint-ok">已自动邀请你(<code>${escapeHtml(auto)}</code>)作为成员。${transferLine}${notifyLine}</p>`;
|
|
444
|
-
}
|
|
445
|
-
else if (rejected) {
|
|
446
|
-
inviteNote = `<p class="hint-warn">飞书拒绝了自动邀请(你的 open_id 在创建者 bot 的 scope 下不可用)。<strong>你目前不是新群成员</strong>,需要让群里的某个机器人手动把你加进来。</p>`;
|
|
447
|
-
}
|
|
448
|
-
else {
|
|
449
|
-
inviteNote = `<p class="hint-warn">没在 dashboard 缓存里找到 ownerOpenId,<strong>没有自动邀请你</strong>。点开下面链接前,先让群里任一机器人手动把你加进去。</p>`;
|
|
450
|
-
}
|
|
451
|
-
const invalidNote = [
|
|
452
|
-
invalidBots.length ? `<li>无效 bot id: <code>${invalidBots.map(escapeHtml).join(', ')}</code></li>` : '',
|
|
453
|
-
invalidUsers.length ? `<li>无效用户 open_id: <code>${invalidUsers.map(escapeHtml).join(', ')}</code></li>` : '',
|
|
454
|
-
].filter(Boolean).join('');
|
|
455
|
-
const profileNote = renderRoleProfileBootstrapSummary(typeof resp.roleProfileId === 'string' ? resp.roleProfileId : '', resp.roleProfileBootstrapMessageId, resp.roleProfileBootstrapError);
|
|
456
|
-
drawer.innerHTML = `
|
|
457
|
-
<article>
|
|
458
|
-
<header><h3>${t('groups.successTitle')}</h3></header>
|
|
459
|
-
<p><b>chatId:</b> <code>${escapeHtml(chatId)}</code> <button type="button" data-copy="${escapeHtml(chatId)}">${t('sessions.copy')}</button></p>
|
|
460
|
-
<p><b>创建者:</b> <code>${escapeHtml(resp.creator ?? '?')}</code></p>
|
|
461
|
-
${inviteNote}
|
|
462
|
-
${bindNote}
|
|
463
|
-
${profileNote}
|
|
464
|
-
${invalidNote ? `<ul>${invalidNote}</ul>` : ''}
|
|
465
|
-
<div class="actions">
|
|
466
|
-
<a class="btn-link primary" href="${appLink}" target="_blank" rel="noopener">${t('groups.openGroup')}</a>
|
|
467
|
-
<button type="button" id="g-create-close">${t('sessions.dismiss')}</button>
|
|
468
|
-
</div>
|
|
469
|
-
</article>`;
|
|
470
|
-
drawer.querySelectorAll('[data-copy]').forEach(b => {
|
|
471
|
-
b.onclick = () => {
|
|
472
|
-
navigator.clipboard.writeText(b.dataset.copy ?? '');
|
|
473
|
-
b.textContent = t('sessions.copied');
|
|
474
|
-
setTimer(() => { b.textContent = t('sessions.copy'); }, 800);
|
|
475
|
-
};
|
|
476
|
-
});
|
|
477
|
-
drawer.querySelector('#g-create-close').onclick = () => drawer.close();
|
|
478
|
-
}
|
|
479
|
-
function renderGroupProfileStatus(chat) {
|
|
480
|
-
if (!roleProfiles.length || !roleProfileContextLoaded)
|
|
481
|
-
return '';
|
|
482
|
-
const rolesByBot = new Map();
|
|
483
|
-
for (const bot of chat.memberBots ?? []) {
|
|
484
|
-
if (!bot?.inChat)
|
|
485
|
-
continue;
|
|
486
|
-
rolesByBot.set(bot.larkAppId, groupRoleContentByBot.get(roleKey(bot.larkAppId, chat.chatId)) ?? null);
|
|
487
|
-
}
|
|
488
|
-
if (!hasExplicitChatRole(rolesByBot))
|
|
489
|
-
return '';
|
|
490
|
-
const matches = summarizeGroupProfileMatches(chat.memberBots ?? [], roleProfiles, roleProfileEntriesById, rolesByBot);
|
|
491
|
-
const best = matches[0];
|
|
492
|
-
if (!best) {
|
|
493
|
-
return `<div class="g-profile-status muted">${t('groups.profileStatusUnmatched')}</div>`;
|
|
494
|
-
}
|
|
495
|
-
const key = best.kind === 'full' ? 'groups.profileStatusFullChat' : 'groups.profileStatusPartial';
|
|
496
|
-
return `<div class="g-profile-status ${best.kind}">
|
|
497
|
-
${escapeHtml(t(key, {
|
|
498
|
-
name: best.profileId,
|
|
499
|
-
matched: best.matched,
|
|
500
|
-
total: best.total,
|
|
501
|
-
chat: best.chatMatched,
|
|
502
|
-
}))}
|
|
503
|
-
</div>`;
|
|
504
|
-
}
|
|
505
|
-
function renderHead() {
|
|
506
|
-
head.innerHTML = `<tr>
|
|
507
|
-
<th>${t('groups.chat')}</th>
|
|
508
|
-
${cache.bots.map(b => `<th>${escapeHtml(b.botName ?? b.larkAppId)}</th>`).join('')}
|
|
509
|
-
<th>${t('groups.actions')}</th>
|
|
510
|
-
</tr>`;
|
|
511
|
-
}
|
|
512
|
-
function rerender() {
|
|
513
|
-
renderHead();
|
|
514
|
-
const f = new FormData(form);
|
|
515
|
-
const q = (f.get('q') ?? '').toLowerCase();
|
|
516
|
-
const onlyMissing = !!f.get('missing');
|
|
517
|
-
const filtered = cache.chats
|
|
518
|
-
.filter(c => !q ||
|
|
519
|
-
(c.name ?? '').toLowerCase().includes(q) ||
|
|
520
|
-
c.chatId.toLowerCase().includes(q) ||
|
|
521
|
-
(c.ownerId ?? '').toLowerCase().includes(q))
|
|
522
|
-
.filter(c => !onlyMissing || c.memberBots.some((m) => !m.inChat));
|
|
523
|
-
if (filtered.length === 0) {
|
|
524
|
-
body.innerHTML = `<tr><td colspan="${cache.bots.length + 2}" class="empty">${t('groups.empty')}</td></tr>`;
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
body.innerHTML = filtered.map(c => `<tr data-chat="${escapeHtml(c.chatId)}">
|
|
528
|
-
<td>
|
|
529
|
-
<div class="g-chat-cell">
|
|
530
|
-
${chatAvatarHtml({ chatId: c.chatId, name: c.name, avatarUrl: c.avatar, size: 'sm' })}
|
|
531
|
-
<div class="g-chat-meta">
|
|
532
|
-
<strong>${escapeHtml(c.name ?? c.chatId)}</strong><br>
|
|
533
|
-
<small><code>${escapeHtml(c.chatId)}</code></small>
|
|
534
|
-
${renderGroupProfileStatus(c)}
|
|
535
|
-
</div>
|
|
536
|
-
</div>
|
|
537
|
-
</td>
|
|
538
|
-
${cache.bots.map(b => {
|
|
539
|
-
const m = c.memberBots.find((m) => m.larkAppId === b.larkAppId);
|
|
540
|
-
const cell = !m ? '?' : m.error ? '!' : m.inChat ? '✓' : '✗';
|
|
541
|
-
const cls = !m ? 'cell-unknown' : m.error ? 'cell-error' : m.inChat ? 'cell-in' : 'cell-out';
|
|
542
|
-
return `<td class="${cls}" title="${escapeHtml(m?.error ?? '')}">${cell}</td>`;
|
|
543
|
-
}).join('')}
|
|
544
|
-
<td>
|
|
545
|
-
<button class="add-bots" type="button">${t('groups.addBots')}</button>
|
|
546
|
-
<button class="save-profile" type="button">${t('groups.saveAsProfile')}</button>
|
|
547
|
-
<button class="manage-chat" type="button">${t('groups.manage')}</button>
|
|
548
|
-
</td>
|
|
549
|
-
</tr>`).join('');
|
|
550
|
-
}
|
|
551
|
-
const onAddBotsClick = async (e) => {
|
|
552
|
-
const btn = e.target.closest('button.add-bots');
|
|
553
|
-
if (!btn)
|
|
554
|
-
return;
|
|
555
|
-
const tr = btn.closest('tr[data-chat]');
|
|
556
|
-
const chatId = tr.dataset.chat;
|
|
557
|
-
const chat = cache.chats.find(c => c.chatId === chatId);
|
|
558
|
-
if (!chat)
|
|
559
|
-
return;
|
|
560
|
-
// Iterate cache.bots (botIndex-sorted by the aggregator) so this modal's
|
|
561
|
-
// order matches "Create new group" exactly. memberBots is only used to
|
|
562
|
-
// compute which bots to hide as already-in-chat.
|
|
563
|
-
const inChatSet = new Set(chat.memberBots.filter((m) => m.inChat).map((m) => m.larkAppId));
|
|
564
|
-
const missing = cache.bots.filter((b) => !inChatSet.has(b.larkAppId));
|
|
565
|
-
if (!missing.length) {
|
|
566
|
-
alert('All configured bots are already in this chat.');
|
|
567
|
-
return;
|
|
568
|
-
}
|
|
569
|
-
drawer.innerHTML = `
|
|
570
|
-
<article>
|
|
571
|
-
<header><h3>${t('groups.addBots')} · ${escapeHtml(chat.name ?? chat.chatId)}</h3></header>
|
|
572
|
-
<p>${t('groups.createHelp')}</p>
|
|
573
|
-
<form id="g-addform">
|
|
574
|
-
${renderBotCheckboxes(cache.bots, inChatSet)}
|
|
575
|
-
<div data-add-status aria-live="polite"></div>
|
|
576
|
-
<div class="actions">
|
|
577
|
-
<button type="submit" class="primary">${t('groups.addBots')}</button>
|
|
578
|
-
<button type="button" id="g-cancel">${t('groups.cancel')}</button>
|
|
579
|
-
</div>
|
|
580
|
-
</form>
|
|
581
|
-
</article>`;
|
|
582
|
-
drawer.showModal();
|
|
583
|
-
drawer.querySelector('#g-cancel').onclick = () => drawer.close();
|
|
584
|
-
drawer.querySelector('#g-addform').onsubmit = async (ev) => {
|
|
585
|
-
ev.preventDefault();
|
|
586
|
-
const fd = new FormData(ev.target);
|
|
587
|
-
const ids = fd.getAll('bot');
|
|
588
|
-
const statusEl = drawer.querySelector('[data-add-status]');
|
|
589
|
-
if (ids.length === 0) {
|
|
590
|
-
setDialogStatus(statusEl, renderDialogError('请选择 bot', '至少选择一个 bot 后再添加。'));
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
const submitBtn = ev.target.querySelector('button[type=submit]');
|
|
594
|
-
if (submitBtn) {
|
|
595
|
-
submitBtn.disabled = true;
|
|
596
|
-
submitBtn.textContent = 'Adding...';
|
|
597
|
-
}
|
|
598
|
-
setDialogStatus(statusEl, '');
|
|
599
|
-
try {
|
|
600
|
-
const r = await fetch(`/api/groups/${encodeURIComponent(chatId)}/add-bots`, {
|
|
601
|
-
method: 'POST',
|
|
602
|
-
headers: { 'content-type': 'application/json' },
|
|
603
|
-
body: JSON.stringify({ larkAppIds: ids }),
|
|
604
|
-
});
|
|
605
|
-
const respBody = await r.json();
|
|
606
|
-
if (disposed)
|
|
607
|
-
return;
|
|
608
|
-
if (respBody.error === 'no_proxy_bot') {
|
|
609
|
-
setDialogStatus(statusEl, renderDialogError('无法添加 bot', '当前群里没有可代理操作的 bot。请先在飞书里手动拉入一个 bot,然后重试。'));
|
|
610
|
-
}
|
|
611
|
-
else if (respBody.result) {
|
|
612
|
-
const resultHtml = renderAddBotsResultSummary(respBody.result);
|
|
613
|
-
setDialogStatus(statusEl, resultHtml);
|
|
614
|
-
try {
|
|
615
|
-
await loadGroups();
|
|
616
|
-
if (disposed)
|
|
617
|
-
return;
|
|
618
|
-
rerender();
|
|
619
|
-
void refreshRoleProfileContext();
|
|
620
|
-
}
|
|
621
|
-
catch (e) {
|
|
622
|
-
if (!disposed) {
|
|
623
|
-
setDialogStatus(statusEl, `${resultHtml}${renderDialogError('刷新失败', `添加结果已返回,但刷新群组列表失败:${e}`)}`);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
else {
|
|
628
|
-
setDialogStatus(statusEl, renderDialogError('响应异常', JSON.stringify(respBody)));
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
catch (e) {
|
|
632
|
-
setDialogStatus(statusEl, renderDialogError('网络错误', e));
|
|
633
|
-
}
|
|
634
|
-
finally {
|
|
635
|
-
if (!disposed)
|
|
636
|
-
restoreSubmit(submitBtn, t('groups.addBots'));
|
|
637
|
-
}
|
|
638
|
-
};
|
|
639
|
-
};
|
|
640
|
-
body.addEventListener('click', onAddBotsClick);
|
|
641
|
-
const onSaveProfileClick = async (e) => {
|
|
642
|
-
const btn = e.target.closest('button.save-profile');
|
|
643
|
-
if (!btn)
|
|
644
|
-
return;
|
|
645
|
-
const tr = btn.closest('tr[data-chat]');
|
|
646
|
-
const chatId = tr.dataset.chat;
|
|
647
|
-
const chat = cache.chats.find(c => c.chatId === chatId);
|
|
648
|
-
if (!chat)
|
|
649
|
-
return;
|
|
650
|
-
await saveGroupRolesAsProfile(chat, btn);
|
|
178
|
+
return {
|
|
179
|
+
kind: 'ok',
|
|
180
|
+
text: t('groups.roleProfileBootstrapDone', { name: cleanProfileId }),
|
|
651
181
|
};
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
return;
|
|
668
|
-
openSaveProfileDialog(chat, suggested, entries, profiles);
|
|
669
|
-
}
|
|
670
|
-
finally {
|
|
671
|
-
if (!disposed) {
|
|
672
|
-
btn.disabled = false;
|
|
673
|
-
btn.textContent = originalText;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
async function collectGroupProfileEntries(chat) {
|
|
678
|
-
const inChat = (chat.memberBots ?? []).filter((bot) => bot?.inChat && bot?.larkAppId);
|
|
679
|
-
const roleRows = await Promise.all(inChat.map(async (bot) => {
|
|
680
|
-
try {
|
|
681
|
-
const r = await fetch(`/api/roles/${encodeURIComponent(bot.larkAppId)}/${encodeURIComponent(chat.chatId)}`);
|
|
682
|
-
const body = await r.json().catch(() => ({}));
|
|
683
|
-
const hasEffectiveRole = body?.hasEffectiveRole ?? body?.hasRole;
|
|
684
|
-
const effectiveContent = 'effectiveContent' in body ? body.effectiveContent : body.content;
|
|
685
|
-
const content = hasEffectiveRole ? String(effectiveContent ?? '').trim() : '';
|
|
686
|
-
const source = body?.effectiveSource === 'chat' || body?.effectiveSource === 'team'
|
|
687
|
-
? body.effectiveSource
|
|
688
|
-
: null;
|
|
689
|
-
return {
|
|
690
|
-
larkAppId: bot.larkAppId,
|
|
691
|
-
botName: bot.botName,
|
|
692
|
-
content,
|
|
693
|
-
status: content ? (source ?? 'chat') : 'empty',
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
catch {
|
|
697
|
-
return {
|
|
698
|
-
larkAppId: bot.larkAppId,
|
|
699
|
-
botName: bot.botName,
|
|
700
|
-
content: '',
|
|
701
|
-
status: 'error',
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
}));
|
|
705
|
-
return roleRows;
|
|
706
|
-
}
|
|
707
|
-
function openSaveProfileDialog(chat, suggestedProfileId, entries, profiles) {
|
|
708
|
-
const sortedProfiles = [...profiles].sort((a, b) => a.profileId.localeCompare(b.profileId));
|
|
709
|
-
const profileButtons = sortedProfiles
|
|
710
|
-
.map((profile, index) => `
|
|
711
|
-
<button type="button"
|
|
712
|
-
class="g-save-profile-pick ${index === 0 ? 'selected' : ''}"
|
|
713
|
-
data-profile-id="${escapeHtml(profile.profileId)}"
|
|
714
|
-
aria-pressed="${index === 0 ? 'true' : 'false'}">
|
|
715
|
-
<span>${escapeHtml(profile.profileId)}</span>
|
|
716
|
-
<small>${escapeHtml(t('groups.saveProfileExistingMeta', { count: profile.entryCount ?? 0 }))}</small>
|
|
717
|
-
</button>`)
|
|
718
|
-
.join('');
|
|
719
|
-
const hasExistingProfiles = sortedProfiles.length > 0;
|
|
720
|
-
const emptyCount = entries.filter(entry => entry.status === 'empty').length;
|
|
721
|
-
const failedCount = entries.filter(entry => entry.status === 'error').length;
|
|
722
|
-
const canSubmitSnapshot = entries.length > 0 && failedCount === 0;
|
|
723
|
-
const entryRows = entries.map(entry => `
|
|
724
|
-
<div class="g-save-profile-entry ${entry.status === 'error' ? 'error' : ''}">
|
|
725
|
-
<div>
|
|
726
|
-
<strong>${escapeHtml(entry.botName ?? entry.larkAppId)}</strong>
|
|
727
|
-
<code>${escapeHtml(entry.larkAppId)}</code>
|
|
728
|
-
</div>
|
|
729
|
-
<span class="g-save-profile-entry-status ${entry.status === 'error' ? 'error' : 'ok'}">
|
|
730
|
-
${t(entry.status === 'error' ? 'groups.saveProfileStatus.error' : 'groups.saveProfileStatus.entry')}
|
|
731
|
-
</span>
|
|
732
|
-
</div>`).join('');
|
|
733
|
-
drawer.innerHTML = `
|
|
734
|
-
<article class="g-save-profile-dialog">
|
|
735
|
-
<header>
|
|
736
|
-
<h3>${t('groups.saveProfileTitle')}</h3>
|
|
737
|
-
<p>${escapeHtml(t('groups.saveProfileIntro', {
|
|
738
|
-
name: chat.name ?? chat.chatId,
|
|
739
|
-
count: entries.length,
|
|
740
|
-
}))}</p>
|
|
741
|
-
</header>
|
|
742
|
-
<form id="g-save-profile-form">
|
|
743
|
-
<section class="g-save-profile-panel">
|
|
744
|
-
<div class="g-save-profile-section-head">
|
|
745
|
-
<span>${t('groups.saveProfileScope')}</span>
|
|
746
|
-
<small>${t('groups.saveProfileScopeHelp')}</small>
|
|
747
|
-
</div>
|
|
748
|
-
<div class="g-save-profile-stats">
|
|
749
|
-
<span>${t('groups.saveProfileBotCount')} <strong>${entries.length}</strong></span>
|
|
750
|
-
${failedCount ? `<span class="warn">${t('groups.saveProfileLoadFailed')} <strong>${failedCount}</strong></span>` : ''}
|
|
751
|
-
</div>
|
|
752
|
-
<div class="g-save-profile-entry-list">
|
|
753
|
-
${entryRows || `<div class="g-save-profile-empty">${t('groups.saveProfileNoRoles')}</div>`}
|
|
754
|
-
</div>
|
|
755
|
-
</section>
|
|
756
|
-
|
|
757
|
-
<div class="form-row">
|
|
758
|
-
<span>${t('groups.saveProfileMode')}</span>
|
|
759
|
-
<div class="g-save-profile-switch" role="tablist" aria-label="${t('groups.saveProfileMode')}">
|
|
760
|
-
<button type="button" class="active" data-save-profile-mode="new">${t('groups.saveProfileNew')}</button>
|
|
761
|
-
<button type="button" data-save-profile-mode="overwrite" ${hasExistingProfiles ? '' : 'disabled'}>${t('groups.saveProfileOverwrite')}</button>
|
|
762
|
-
</div>
|
|
763
|
-
</div>
|
|
764
|
-
<label class="form-row" data-profile-mode-row="new">
|
|
765
|
-
<span>${t('groups.saveProfileIdLabel')}</span>
|
|
766
|
-
<input type="text" name="profileId" value="${escapeHtml(suggestedProfileId)}" maxlength="64" autocomplete="off">
|
|
767
|
-
<small>${t('groups.saveProfileInvalid')}</small>
|
|
768
|
-
</label>
|
|
769
|
-
<div class="form-row" data-profile-mode-row="overwrite" hidden>
|
|
770
|
-
<span>${t('groups.saveProfileExistingLabel')}</span>
|
|
771
|
-
${hasExistingProfiles
|
|
772
|
-
? `<div class="g-save-profile-picker">${profileButtons}</div>`
|
|
773
|
-
: `<div class="g-save-profile-summary warn">${t('groups.saveProfileExistingEmpty')}</div>`}
|
|
774
|
-
<small>${t('groups.saveProfileOverwriteHelp')}</small>
|
|
775
|
-
</div>
|
|
776
|
-
<div class="g-save-profile-target">
|
|
777
|
-
<span>${t('groups.saveProfileTarget')}</span>
|
|
778
|
-
<code data-save-profile-target>${escapeHtml(suggestedProfileId)}</code>
|
|
779
|
-
<small data-save-profile-target-mode>${t('groups.saveProfileTargetNew')}</small>
|
|
780
|
-
</div>
|
|
781
|
-
<div class="g-save-profile-summary ${canSubmitSnapshot ? '' : 'warn'}">
|
|
782
|
-
${failedCount
|
|
783
|
-
? escapeHtml(t('groups.saveProfileFailedLoadSummary', { count: failedCount }))
|
|
784
|
-
: entries.length
|
|
785
|
-
? escapeHtml(emptyCount
|
|
786
|
-
? t('groups.saveProfileEntrySummaryWithEmpty', { count: entries.length, emptyCount })
|
|
787
|
-
: t('groups.saveProfileEntrySummary', { count: entries.length }))
|
|
788
|
-
: escapeHtml(t('groups.saveProfileNoRoles'))}
|
|
789
|
-
</div>
|
|
790
|
-
<div class="g-save-profile-status" data-save-profile-status></div>
|
|
791
|
-
<div class="actions">
|
|
792
|
-
<button type="submit" class="primary" ${canSubmitSnapshot ? '' : 'disabled'}>${t('groups.saveProfileSubmit')}</button>
|
|
793
|
-
<button type="button" id="g-save-profile-cancel">${t('groups.cancel')}</button>
|
|
794
|
-
</div>
|
|
795
|
-
</form>
|
|
796
|
-
</article>`;
|
|
797
|
-
drawer.showModal();
|
|
798
|
-
const formEl = drawer.querySelector('#g-save-profile-form');
|
|
799
|
-
const modeRows = [...drawer.querySelectorAll('[data-profile-mode-row]')];
|
|
800
|
-
const statusEl = drawer.querySelector('[data-save-profile-status]');
|
|
801
|
-
const submitBtn = formEl.querySelector('button[type=submit]');
|
|
802
|
-
const modeButtons = [...formEl.querySelectorAll('[data-save-profile-mode]')];
|
|
803
|
-
const profilePickButtons = [...formEl.querySelectorAll('[data-profile-id]')];
|
|
804
|
-
const profileIdInput = formEl.querySelector('input[name=profileId]');
|
|
805
|
-
const targetEl = formEl.querySelector('[data-save-profile-target]');
|
|
806
|
-
const targetModeEl = formEl.querySelector('[data-save-profile-target-mode]');
|
|
807
|
-
let selectedMode = 'new';
|
|
808
|
-
let selectedExistingProfileId = sortedProfiles[0]?.profileId ?? '';
|
|
809
|
-
function currentProfileId() {
|
|
810
|
-
return selectedMode === 'overwrite'
|
|
811
|
-
? selectedExistingProfileId
|
|
812
|
-
: profileIdInput.value.trim();
|
|
813
|
-
}
|
|
814
|
-
function updateMode() {
|
|
815
|
-
for (const row of modeRows) {
|
|
816
|
-
row.hidden = row.dataset.profileModeRow !== selectedMode;
|
|
817
|
-
}
|
|
818
|
-
for (const button of modeButtons) {
|
|
819
|
-
const isActive = button.dataset.saveProfileMode === selectedMode;
|
|
820
|
-
button.classList.toggle('active', isActive);
|
|
821
|
-
button.setAttribute('aria-pressed', String(isActive));
|
|
822
|
-
}
|
|
823
|
-
for (const button of profilePickButtons) {
|
|
824
|
-
const isSelected = button.dataset.profileId === selectedExistingProfileId;
|
|
825
|
-
button.classList.toggle('selected', isSelected);
|
|
826
|
-
button.setAttribute('aria-pressed', String(isSelected));
|
|
827
|
-
}
|
|
828
|
-
submitBtn.textContent = selectedMode === 'overwrite'
|
|
829
|
-
? t('groups.saveProfileOverwriteSubmit')
|
|
830
|
-
: t('groups.saveProfileSubmit');
|
|
831
|
-
submitBtn.disabled = !canSubmitSnapshot || (selectedMode === 'overwrite' && !selectedExistingProfileId);
|
|
832
|
-
targetEl.textContent = currentProfileId() || '-';
|
|
833
|
-
targetModeEl.textContent = selectedMode === 'overwrite'
|
|
834
|
-
? t('groups.saveProfileTargetOverwrite')
|
|
835
|
-
: t('groups.saveProfileTargetNew');
|
|
836
|
-
statusEl.textContent = '';
|
|
837
|
-
statusEl.className = 'g-save-profile-status';
|
|
838
|
-
}
|
|
839
|
-
modeButtons.forEach(button => {
|
|
840
|
-
button.addEventListener('click', () => {
|
|
841
|
-
const mode = button.dataset.saveProfileMode === 'overwrite' ? 'overwrite' : 'new';
|
|
842
|
-
if (mode === 'overwrite' && !hasExistingProfiles)
|
|
843
|
-
return;
|
|
844
|
-
selectedMode = mode;
|
|
845
|
-
updateMode();
|
|
846
|
-
});
|
|
847
|
-
});
|
|
848
|
-
profilePickButtons.forEach(button => {
|
|
849
|
-
button.addEventListener('click', () => {
|
|
850
|
-
selectedExistingProfileId = button.dataset.profileId ?? '';
|
|
851
|
-
selectedMode = 'overwrite';
|
|
852
|
-
updateMode();
|
|
853
|
-
});
|
|
854
|
-
});
|
|
855
|
-
profileIdInput.addEventListener('input', updateMode);
|
|
856
|
-
updateMode();
|
|
857
|
-
drawer.querySelector('#g-save-profile-cancel').onclick = () => drawer.close();
|
|
858
|
-
formEl.onsubmit = async (ev) => {
|
|
859
|
-
ev.preventDefault();
|
|
860
|
-
if (!canSubmitSnapshot)
|
|
861
|
-
return;
|
|
862
|
-
const profileId = currentProfileId();
|
|
863
|
-
if (!isValidProfileId(profileId)) {
|
|
864
|
-
statusEl.textContent = t('groups.saveProfileInvalid');
|
|
865
|
-
statusEl.className = 'g-save-profile-status error';
|
|
866
|
-
return;
|
|
867
|
-
}
|
|
868
|
-
submitBtn.disabled = true;
|
|
869
|
-
submitBtn.textContent = t('groups.saveProfileSaving');
|
|
870
|
-
statusEl.textContent = t('groups.saveProfileSaving');
|
|
871
|
-
statusEl.className = 'g-save-profile-status';
|
|
872
|
-
try {
|
|
873
|
-
const results = await Promise.all(entries.map(async (entry) => {
|
|
874
|
-
const r = await fetch(`/api/role-profiles/${encodeURIComponent(profileId)}/${encodeURIComponent(entry.larkAppId)}`, {
|
|
875
|
-
method: 'PUT',
|
|
876
|
-
headers: { 'content-type': 'application/json' },
|
|
877
|
-
body: JSON.stringify({ content: entry.content, allowEmpty: true }),
|
|
878
|
-
});
|
|
879
|
-
return r.ok;
|
|
880
|
-
}));
|
|
881
|
-
if (disposed)
|
|
882
|
-
return;
|
|
883
|
-
const saved = results.filter(Boolean).length;
|
|
884
|
-
if (saved !== entries.length) {
|
|
885
|
-
statusEl.textContent = t('groups.saveProfileFailed', { saved, total: entries.length });
|
|
886
|
-
statusEl.className = 'g-save-profile-status error';
|
|
887
|
-
submitBtn.disabled = false;
|
|
888
|
-
submitBtn.textContent = selectedMode === 'overwrite'
|
|
889
|
-
? t('groups.saveProfileOverwriteSubmit')
|
|
890
|
-
: t('groups.saveProfileSubmit');
|
|
891
|
-
return;
|
|
892
|
-
}
|
|
893
|
-
statusEl.textContent = t('groups.saveProfileDone', { name: profileId, count: saved });
|
|
894
|
-
statusEl.className = 'g-save-profile-status ok';
|
|
895
|
-
await refreshRoleProfileContext();
|
|
896
|
-
if (disposed)
|
|
897
|
-
return;
|
|
898
|
-
setTimer(() => drawer.close(), 700);
|
|
899
|
-
}
|
|
900
|
-
catch (err) {
|
|
901
|
-
statusEl.textContent = String(err);
|
|
902
|
-
statusEl.className = 'g-save-profile-status error';
|
|
903
|
-
submitBtn.disabled = false;
|
|
904
|
-
submitBtn.textContent = selectedMode === 'overwrite'
|
|
905
|
-
? t('groups.saveProfileOverwriteSubmit')
|
|
906
|
-
: t('groups.saveProfileSubmit');
|
|
907
|
-
}
|
|
908
|
-
};
|
|
909
|
-
}
|
|
910
|
-
const onManageChatClick = async (e) => {
|
|
911
|
-
const btn = e.target.closest('button.manage-chat');
|
|
912
|
-
if (!btn)
|
|
913
|
-
return;
|
|
914
|
-
const tr = btn.closest('tr[data-chat]');
|
|
915
|
-
const chatId = tr.dataset.chat;
|
|
916
|
-
const chat = cache.chats.find(c => c.chatId === chatId);
|
|
917
|
-
if (!chat)
|
|
918
|
-
return;
|
|
919
|
-
openManageDrawer(chat);
|
|
182
|
+
}
|
|
183
|
+
export function injectOptimisticChat(snapshot, chatId, displayName, memberIds, creator) {
|
|
184
|
+
const inChatSet = new Set(memberIds);
|
|
185
|
+
if (creator)
|
|
186
|
+
inChatSet.add(creator);
|
|
187
|
+
const optimistic = {
|
|
188
|
+
chatId,
|
|
189
|
+
name: displayName,
|
|
190
|
+
ownerId: creator ?? null,
|
|
191
|
+
memberBots: snapshot.bots.map(bot => ({
|
|
192
|
+
larkAppId: bot.larkAppId,
|
|
193
|
+
botName: bot.botName,
|
|
194
|
+
inChat: inChatSet.has(bot.larkAppId),
|
|
195
|
+
oncallChat: null,
|
|
196
|
+
})),
|
|
920
197
|
};
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
// Lark `owner_id` returns app_id format for bot owners (string match works).
|
|
925
|
-
// For user owners it'll be an open_id which won't match any larkAppId.
|
|
926
|
-
const ownerAppId = typeof chat.ownerId === 'string' ? chat.ownerId : '';
|
|
927
|
-
drawer.innerHTML = `
|
|
928
|
-
<article>
|
|
929
|
-
<header><h3>${t('groups.manageTitle', { name: chat.name ?? chat.chatId })}</h3></header>
|
|
930
|
-
<p><b>chatId:</b> <code>${escapeHtml(chat.chatId)}</code></p>
|
|
931
|
-
<p><b>${t('groups.owner')}:</b> <code>${escapeHtml(chat.ownerId ?? t('common.unknown'))}</code></p>
|
|
932
|
-
|
|
933
|
-
<fieldset>
|
|
934
|
-
<legend>${t('groups.oncall')}</legend>
|
|
935
|
-
<p><small>${t('groups.oncallHelp')}</small></p>
|
|
936
|
-
${inChat.length === 0
|
|
937
|
-
? `<p class="empty">没有机器人在群里</p>`
|
|
938
|
-
: inChat.map((m) => {
|
|
939
|
-
const enabled = !!m.oncallChat;
|
|
940
|
-
const wd = m.oncallChat?.workingDir ?? '';
|
|
941
|
-
return `
|
|
942
|
-
<div class="oncall-row" data-bot="${escapeHtml(m.larkAppId)}">
|
|
943
|
-
<label class="checkbox-row">
|
|
944
|
-
<input type="checkbox" data-action="toggle" ${enabled ? 'checked' : ''}>
|
|
945
|
-
<strong>${escapeHtml(m.botName ?? m.larkAppId)}</strong>
|
|
946
|
-
<small>(${escapeHtml(m.larkAppId)})</small>
|
|
947
|
-
</label>
|
|
948
|
-
<div class="oncall-row-body">
|
|
949
|
-
<input type="text" data-input="workingDir" placeholder="e.g. /root/iserver/botmux"
|
|
950
|
-
value="${escapeHtml(wd)}" ${enabled ? '' : 'disabled'}>
|
|
951
|
-
<button type="button" data-action="save">${t('groups.save')}</button>
|
|
952
|
-
<span class="oncall-status" data-status></span>
|
|
953
|
-
</div>
|
|
954
|
-
</div>
|
|
955
|
-
`;
|
|
956
|
-
}).join('')}
|
|
957
|
-
</fieldset>
|
|
958
|
-
|
|
959
|
-
<fieldset>
|
|
960
|
-
<legend>${t('groups.leaveTitle')}</legend>
|
|
961
|
-
${inChat.length === 0
|
|
962
|
-
? `<p class="empty">没有机器人在群里</p>`
|
|
963
|
-
: inChat.map((m) => `
|
|
964
|
-
<label class="checkbox-row">
|
|
965
|
-
<input type="checkbox" name="leave-bot" value="${escapeHtml(m.larkAppId)}">
|
|
966
|
-
${escapeHtml(m.botName ?? m.larkAppId)}
|
|
967
|
-
<small>${m.larkAppId === ownerAppId ? '· 群主' : ''}</small>
|
|
968
|
-
</label>
|
|
969
|
-
`).join('')}
|
|
970
|
-
</fieldset>
|
|
971
|
-
|
|
972
|
-
<div class="actions">
|
|
973
|
-
<button id="g-leave-btn" type="button" ${inChat.length === 0 ? 'disabled' : ''}>${t('groups.leaveSelected')}</button>
|
|
974
|
-
<button id="g-disband-btn" type="button" class="contrast" ${inChat.length === 0 ? 'disabled' : ''}>${t('groups.disband')}</button>
|
|
975
|
-
</div>
|
|
976
|
-
<p class="hint-warn"><small>${t('groups.dangerHint')}</small></p>
|
|
977
|
-
<form method="dialog"><button>${t('sessions.dismiss')}</button></form>
|
|
978
|
-
</article>`;
|
|
979
|
-
drawer.showModal();
|
|
980
|
-
// Oncall row interactions: toggle enables/disables the input; Save commits.
|
|
981
|
-
drawer.querySelectorAll('.oncall-row').forEach(row => {
|
|
982
|
-
const appId = row.dataset.bot;
|
|
983
|
-
const cb = row.querySelector('input[data-action=toggle]');
|
|
984
|
-
const input = row.querySelector('input[data-input=workingDir]');
|
|
985
|
-
const saveBtn = row.querySelector('button[data-action=save]');
|
|
986
|
-
const statusEl = row.querySelector('[data-status]');
|
|
987
|
-
cb.addEventListener('change', () => {
|
|
988
|
-
input.disabled = !cb.checked;
|
|
989
|
-
if (cb.checked)
|
|
990
|
-
input.focus();
|
|
991
|
-
});
|
|
992
|
-
saveBtn.addEventListener('click', async () => {
|
|
993
|
-
statusEl.textContent = '';
|
|
994
|
-
statusEl.className = 'oncall-status';
|
|
995
|
-
const want = cb.checked;
|
|
996
|
-
const wd = input.value.trim();
|
|
997
|
-
if (want && !wd) {
|
|
998
|
-
statusEl.textContent = t('groups.needWorkingDir');
|
|
999
|
-
statusEl.classList.add('hint-warn-inline');
|
|
1000
|
-
return;
|
|
1001
|
-
}
|
|
1002
|
-
saveBtn.disabled = true;
|
|
1003
|
-
try {
|
|
1004
|
-
const url = `/api/groups/${encodeURIComponent(chat.chatId)}/oncall/${encodeURIComponent(appId)}`;
|
|
1005
|
-
const r = want
|
|
1006
|
-
? await fetch(url, {
|
|
1007
|
-
method: 'PUT',
|
|
1008
|
-
headers: { 'content-type': 'application/json' },
|
|
1009
|
-
body: JSON.stringify({ workingDir: wd }),
|
|
1010
|
-
})
|
|
1011
|
-
: await fetch(url, { method: 'DELETE' });
|
|
1012
|
-
const body = await r.json().catch(() => ({}));
|
|
1013
|
-
if (disposed)
|
|
1014
|
-
return;
|
|
1015
|
-
if (r.ok && body.ok) {
|
|
1016
|
-
statusEl.textContent = want
|
|
1017
|
-
? `✓ 已绑定 → ${body.resolvedPath ?? wd}`
|
|
1018
|
-
: '✓ 已解绑';
|
|
1019
|
-
statusEl.classList.add('hint-ok');
|
|
1020
|
-
// Refresh aggregator cache + matrix; drawer state stays as-is
|
|
1021
|
-
// (current row reflects the just-saved values).
|
|
1022
|
-
try {
|
|
1023
|
-
await loadGroups();
|
|
1024
|
-
if (disposed)
|
|
1025
|
-
return;
|
|
1026
|
-
rerender();
|
|
1027
|
-
void refreshRoleProfileContext();
|
|
1028
|
-
}
|
|
1029
|
-
catch { /* tolerate */ }
|
|
1030
|
-
}
|
|
1031
|
-
else {
|
|
1032
|
-
statusEl.textContent = `✗ ${body.error ?? r.status}`;
|
|
1033
|
-
statusEl.classList.add('hint-warn-inline');
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
catch (e) {
|
|
1037
|
-
statusEl.textContent = `✗ ${e?.message ?? e}`;
|
|
1038
|
-
statusEl.classList.add('hint-warn-inline');
|
|
1039
|
-
}
|
|
1040
|
-
finally {
|
|
1041
|
-
saveBtn.disabled = false;
|
|
1042
|
-
}
|
|
1043
|
-
});
|
|
1044
|
-
});
|
|
1045
|
-
drawer.querySelector('#g-leave-btn').onclick = async () => {
|
|
1046
|
-
const checked = [...drawer.querySelectorAll('input[name=leave-bot]:checked')]
|
|
1047
|
-
.map(i => i.value);
|
|
1048
|
-
if (checked.length === 0) {
|
|
1049
|
-
alert('至少选一个机器人');
|
|
1050
|
-
return;
|
|
1051
|
-
}
|
|
1052
|
-
if (!confirm(`确定让 ${checked.length} 个机器人退出群聊?该 bot 在此群的会话会一并关闭。`))
|
|
1053
|
-
return;
|
|
1054
|
-
try {
|
|
1055
|
-
const r = await fetch(`/api/groups/${encodeURIComponent(chat.chatId)}/leave`, {
|
|
1056
|
-
method: 'POST',
|
|
1057
|
-
headers: { 'content-type': 'application/json' },
|
|
1058
|
-
body: JSON.stringify({ larkAppIds: checked }),
|
|
1059
|
-
});
|
|
1060
|
-
const respBody = await r.json();
|
|
1061
|
-
if (disposed)
|
|
1062
|
-
return;
|
|
1063
|
-
const lines = (respBody.result ?? []).map((x) => {
|
|
1064
|
-
if (!x.ok)
|
|
1065
|
-
return `${x.larkAppId}: 失败 (${x.error ?? 'unknown'})`;
|
|
1066
|
-
const closed = (x.closedSessions ?? []);
|
|
1067
|
-
const failed = closed.filter(c => !c.ok).length;
|
|
1068
|
-
const ok = closed.length - failed;
|
|
1069
|
-
const note = closed.length === 0
|
|
1070
|
-
? ''
|
|
1071
|
-
: failed === 0 ? `(关闭 ${ok} 个会话)` : `(关闭 ${ok} 个,${failed} 个失败)`;
|
|
1072
|
-
return `${x.larkAppId}: OK${note}`;
|
|
1073
|
-
}).join('\n');
|
|
1074
|
-
alert(lines || `Unexpected: ${JSON.stringify(respBody)}`);
|
|
1075
|
-
await loadGroups();
|
|
1076
|
-
if (disposed)
|
|
1077
|
-
return;
|
|
1078
|
-
rerender();
|
|
1079
|
-
void refreshRoleProfileContext();
|
|
1080
|
-
}
|
|
1081
|
-
catch (e) {
|
|
1082
|
-
alert('Network error: ' + e);
|
|
1083
|
-
}
|
|
1084
|
-
finally {
|
|
1085
|
-
if (!disposed)
|
|
1086
|
-
drawer.close();
|
|
1087
|
-
}
|
|
1088
|
-
};
|
|
1089
|
-
drawer.querySelector('#g-disband-btn').onclick = async () => {
|
|
1090
|
-
if (inChat.length === 0)
|
|
1091
|
-
return;
|
|
1092
|
-
if (!confirm(`确定解散群聊「${chat.name ?? chat.chatId}」?此操作不可恢复,本群所有机器人会话也会一并关闭。`))
|
|
1093
|
-
return;
|
|
1094
|
-
// Try the owner bot first (highest success probability), then fall back
|
|
1095
|
-
// to other in-chat bots in case our ownerId match was wrong (e.g. owner
|
|
1096
|
-
// is a user, but a creator-bot with operate_as_owner scope is in chat).
|
|
1097
|
-
const ordered = [...inChat].sort((a, b) => (b.larkAppId === ownerAppId ? 1 : 0) - (a.larkAppId === ownerAppId ? 1 : 0));
|
|
1098
|
-
const errs = [];
|
|
1099
|
-
for (const m of ordered) {
|
|
1100
|
-
try {
|
|
1101
|
-
const r = await fetch(`/api/groups/${encodeURIComponent(chat.chatId)}/disband`, {
|
|
1102
|
-
method: 'POST',
|
|
1103
|
-
headers: { 'content-type': 'application/json' },
|
|
1104
|
-
body: JSON.stringify({ larkAppId: m.larkAppId }),
|
|
1105
|
-
});
|
|
1106
|
-
const respBody = await r.json();
|
|
1107
|
-
if (disposed)
|
|
1108
|
-
return;
|
|
1109
|
-
if (respBody.ok) {
|
|
1110
|
-
const closed = (respBody.closedSessions ?? []);
|
|
1111
|
-
const failed = closed.filter(c => !c.ok).length;
|
|
1112
|
-
const ok = closed.length - failed;
|
|
1113
|
-
const closedNote = closed.length === 0
|
|
1114
|
-
? ''
|
|
1115
|
-
: failed === 0 ? `\n关闭了 ${ok} 个会话。` : `\n关闭了 ${ok} 个会话,${failed} 个会话关闭失败。`;
|
|
1116
|
-
alert(`已解散(由 ${m.botName ?? m.larkAppId} 执行)${closedNote}`);
|
|
1117
|
-
await loadGroups();
|
|
1118
|
-
if (disposed)
|
|
1119
|
-
return;
|
|
1120
|
-
rerender();
|
|
1121
|
-
void refreshRoleProfileContext();
|
|
1122
|
-
drawer.close();
|
|
1123
|
-
return;
|
|
1124
|
-
}
|
|
1125
|
-
errs.push(`${m.botName ?? m.larkAppId}: ${respBody.error ?? r.status}`);
|
|
1126
|
-
}
|
|
1127
|
-
catch (e) {
|
|
1128
|
-
errs.push(`${m.botName ?? m.larkAppId}: ${e}`);
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
alert(`所有在群机器人均无法解散:\n${errs.join('\n')}\n\n建议改用「退出群聊」。`);
|
|
1132
|
-
};
|
|
1133
|
-
}
|
|
1134
|
-
form.addEventListener('input', rerender);
|
|
1135
|
-
return () => {
|
|
1136
|
-
disposed = true;
|
|
1137
|
-
for (const id of timers)
|
|
1138
|
-
window.clearTimeout(id);
|
|
1139
|
-
timers.clear();
|
|
1140
|
-
for (const resolve of delayResolvers.values())
|
|
1141
|
-
resolve();
|
|
1142
|
-
delayResolvers.clear();
|
|
1143
|
-
refreshBtn.onclick = null;
|
|
1144
|
-
createBtn.onclick = null;
|
|
1145
|
-
form.removeEventListener('input', rerender);
|
|
1146
|
-
body.removeEventListener('click', onAddBotsClick);
|
|
1147
|
-
body.removeEventListener('click', onSaveProfileClick);
|
|
1148
|
-
body.removeEventListener('click', onManageChatClick);
|
|
1149
|
-
if (drawer.open)
|
|
1150
|
-
drawer.close();
|
|
1151
|
-
drawer.replaceChildren();
|
|
198
|
+
return {
|
|
199
|
+
bots: snapshot.bots,
|
|
200
|
+
chats: [optimistic, ...snapshot.chats.filter(chat => chat.chatId !== chatId)],
|
|
1152
201
|
};
|
|
1153
202
|
}
|
|
1154
203
|
//# sourceMappingURL=groups.js.map
|