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,24 +1,1057 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useRef } from 'react';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
3
4
|
import { useT } from './react-hooks.js';
|
|
4
5
|
import { mountReactPage } from './react-mount.js';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import { botDisplayName, loadNameMaps, relTime, t } from './ui.js';
|
|
7
|
+
import { DropdownMenu, LoadingState, RefreshIconButton } from './dashboard-components.js';
|
|
8
|
+
import { BAD_RESULTS, CONVO_ROLES, CONVO_TAGS, FLOW_PHASES, INSIGHT_FILTERS, INSIGHT_TABS, SESS_SORT_KEYS, SESS_SORTS, SEVERITY_RANK, SPAN_TAGS, TIME_WINDOWS, agentMsOf, aggregateRecords, buildInsightScope, buildInsightsHash, cleanPromptText, cliIdOf, cliCounts, fetchInsightConversation, fetchInsightDetail, fetchInsightsSummary, fetchTurnPrompt, filterRecords, fmtInt, fmtMs, focusSets, groupConvo, idText, initialInsightTab, intentPhrase, intentText, intentTextOf, median, newConvoState, okReports, parseInsightsHash, phaseClass, phaseLabel, phaseSlug, projectOf, projectOptions, promptMarkdownHtml, reportNeedsReview, resultLabel, safeStatus, sessionMetaLine, sessionTitle, severityLabel, sortRecordsBy, spanFailed, statusIcon, structLabel, suggestionTitle, tagLabel, toRecord, turnAdvice, turnHeadline, turnMainPhase, turnSenderKind, } from './insights.js';
|
|
9
|
+
const CLI_FILTER_ALL = '__all__';
|
|
10
|
+
function clamp(n, min, max) {
|
|
11
|
+
return Math.min(Math.max(n, min), max);
|
|
12
|
+
}
|
|
13
|
+
function paletteAnchorFromButton(button) {
|
|
14
|
+
const rect = button.getBoundingClientRect();
|
|
15
|
+
const margin = 12;
|
|
16
|
+
const width = Math.min(420, Math.max(280, window.innerWidth - margin * 2));
|
|
17
|
+
const left = clamp(Math.round(rect.left + rect.width / 2 - width / 2), margin, Math.max(margin, window.innerWidth - width - margin));
|
|
18
|
+
const naturalTop = Math.round(rect.bottom + 8);
|
|
19
|
+
const top = clamp(naturalTop, margin, Math.max(margin, window.innerHeight - 132));
|
|
20
|
+
const maxHeight = Math.max(140, Math.floor(window.innerHeight - top - margin));
|
|
21
|
+
return { top, left, width, maxHeight };
|
|
22
|
+
}
|
|
23
|
+
function toggleSet(set, value) {
|
|
24
|
+
const next = new Set(set);
|
|
25
|
+
if (next.has(value))
|
|
26
|
+
next.delete(value);
|
|
27
|
+
else
|
|
28
|
+
next.add(value);
|
|
29
|
+
return next;
|
|
30
|
+
}
|
|
31
|
+
function addToSet(set, value) {
|
|
32
|
+
const next = new Set(set);
|
|
33
|
+
next.add(value);
|
|
34
|
+
return next;
|
|
35
|
+
}
|
|
36
|
+
function MarkdownBody({ text, className = 'md-body' }) {
|
|
37
|
+
return _jsx("div", { className: className, dangerouslySetInnerHTML: { __html: promptMarkdownHtml(text) } });
|
|
38
|
+
}
|
|
39
|
+
function Tooltip({ tip }) {
|
|
40
|
+
return (_jsx("div", { id: "insight-tip", className: "ins-tip", role: "tooltip", hidden: !tip.visible, style: { left: tip.x, top: tip.y }, children: tip.text }));
|
|
41
|
+
}
|
|
42
|
+
function MetricCard({ label, value, sub }) {
|
|
43
|
+
return (_jsxs("div", { className: "card", children: [_jsx("div", { className: "cv", children: value }), _jsx("div", { className: "cl", children: label }), sub ? _jsx("div", { className: "cs", children: sub }) : null] }));
|
|
44
|
+
}
|
|
45
|
+
function DisclosureNote() {
|
|
46
|
+
return _jsxs("p", { className: "insight-disclosure", children: ["\u2696\uFE0E ", t('insights.disclosure')] });
|
|
47
|
+
}
|
|
48
|
+
function PhaseMixBar({ phase }) {
|
|
49
|
+
const entries = Object.entries(phase ?? {}).filter(([, v]) => v.count > 0 || v.ms > 0);
|
|
50
|
+
if (!entries.length)
|
|
51
|
+
return null;
|
|
52
|
+
return (_jsx("div", { className: "mph", children: entries.map(([ph, v]) => {
|
|
53
|
+
const weight = Math.max(1, v.ms || v.count);
|
|
54
|
+
return (_jsx("i", { className: phaseClass(ph), style: { flex: weight }, title: `${phaseLabel(ph)} · ${v.count} · ${fmtMs(v.ms)}` }, ph));
|
|
55
|
+
}) }));
|
|
56
|
+
}
|
|
57
|
+
function InsightTabs({ active, onChange }) {
|
|
58
|
+
return (_jsx("div", { className: "insight-tabs", role: "tablist", children: INSIGHT_TABS.map(tb => (_jsx("button", { type: "button", className: `itab${tb.key === active ? ' on' : ''}`, role: "tab", "aria-selected": tb.key === active, onClick: () => onChange(tb.key), children: t(tb.label) }, tb.key))) }));
|
|
59
|
+
}
|
|
60
|
+
function OverviewContent({ data }) {
|
|
61
|
+
const a = data.agg;
|
|
62
|
+
const rw = a.readWriteRatio === null ? '-' : a.readWriteRatio.toFixed(1);
|
|
63
|
+
const topTools = data.topFailedTools.slice(0, 5);
|
|
64
|
+
const topSuggestions = [...data.suggestions]
|
|
65
|
+
.sort((x, y) => SEVERITY_RANK[x.severity] - SEVERITY_RANK[y.severity] || y.count - x.count)
|
|
66
|
+
.slice(0, 6);
|
|
67
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "cards insights-metrics", children: [_jsx(MetricCard, { label: t('insights.metricSessions'), value: fmtInt(data.totalCount), sub: t('insights.metricAnalyzed', { count: data.analyzedCount }) }), _jsx(MetricCard, { label: t('insights.metricSpans'), value: fmtInt(a.totalSpans), sub: t('insights.metricSafe') }), _jsx(MetricCard, { label: t('insights.metricFailed'), value: fmtInt(a.failedSpans), sub: topTools[0] ? `${topTools[0].tool} ×${topTools[0].count}` : '' }), _jsx(MetricCard, { label: t('insights.metricSlow'), value: fmtInt(a.slowSpans) }), _jsx(MetricCard, { label: t('insights.metricRw'), value: rw, sub: t('insights.metricCompactions', { count: a.compactions }) })] }), _jsxs("div", { className: "insights-overview-grid", children: [_jsxs("section", { className: "block recblock", children: [_jsx("h3", { children: t('insights.recommendations') }), _jsx("div", { className: "reclist", children: topSuggestions.length ? topSuggestions.map(item => (_jsxs("div", { className: `rec ${item.severity}`, children: [_jsxs("div", { className: "rectop", children: [_jsx("b", { children: suggestionTitle(item) }), _jsx("span", { children: severityLabel(item.severity) })] }), _jsx("div", { className: "recev", children: t('insights.seenInSessions', { count: item.count }) })] }, item.id))) : _jsx("p", { className: "mut", children: t('insights.noRecommendations') }) })] }), _jsxs("section", { className: "block", children: [_jsx("h3", { children: t('insights.toolFailures') }), _jsx(HBarList, { rows: topTools.map(tt => ({ key: tt.tool, label: tt.tool, count: tt.count })), empty: t('insights.noFailures') })] })] }), _jsx(DisclosureNote, {})] }));
|
|
68
|
+
}
|
|
69
|
+
function HBarList({ rows, empty, renderValue, onRowClick }) {
|
|
70
|
+
if (!rows.length)
|
|
71
|
+
return _jsx("div", { className: "hbars", children: _jsx("p", { className: "mut", children: empty }) });
|
|
72
|
+
const max = Math.max(1, ...rows.map(r => r.count));
|
|
73
|
+
return (_jsx("div", { className: "hbars", children: rows.map(row => {
|
|
74
|
+
const pct = Math.max(row.count ? 4 : 0, Math.round((row.count / max) * 100));
|
|
75
|
+
const content = (_jsxs(_Fragment, { children: [_jsx("div", { className: "hblabel", title: row.label, children: row.label }), _jsx("div", { className: "hbtrack", children: _jsx("div", { className: "hbfill", style: { width: `${pct}%` } }) }), _jsxs("div", { className: "hbval", children: [renderValue ? renderValue(row) : fmtInt(row.count), row.sub] })] }));
|
|
76
|
+
return onRowClick ? (_jsx("button", { type: "button", className: "hbrow hbrow-click", onClick: () => onRowClick(row), children: content }, row.key)) : (_jsx("div", { className: "hbrow", children: content }, row.key));
|
|
77
|
+
}) }));
|
|
78
|
+
}
|
|
79
|
+
function Trend({ records }) {
|
|
80
|
+
const days = 28;
|
|
81
|
+
const dayMs = 86400000;
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
const counts = new Array(days).fill(0);
|
|
84
|
+
for (const rec of records) {
|
|
85
|
+
const ts = Number(rec.session.lastMessageAt ?? 0);
|
|
86
|
+
if (!ts)
|
|
87
|
+
continue;
|
|
88
|
+
const age = Math.floor((now - ts) / dayMs);
|
|
89
|
+
if (age >= 0 && age < days)
|
|
90
|
+
counts[days - 1 - age] += 1;
|
|
91
|
+
}
|
|
92
|
+
const total = counts.reduce((a, b) => a + b, 0);
|
|
93
|
+
if (!total)
|
|
94
|
+
return null;
|
|
95
|
+
const max = Math.max(1, ...counts);
|
|
96
|
+
return (_jsxs("section", { className: "block trend-block", children: [_jsxs("div", { className: "ihist-head", children: [_jsx("h3", { children: t('insights.distTrend') }), _jsxs("span", { className: "mut", children: [t('insights.distTrendSub'), " \u00B7 ", total] })] }), _jsx("div", { className: "trend", children: counts.map((c, j) => {
|
|
97
|
+
const d = new Date(now - (days - 1 - j) * dayMs);
|
|
98
|
+
const label = `${d.getMonth() + 1}-${d.getDate()} · ${c}`;
|
|
99
|
+
return _jsx("i", { className: "trendbar", style: { height: `${c ? Math.max(8, Math.round((c / max) * 100)) : 2}%` }, title: label }, j);
|
|
100
|
+
}) })] }));
|
|
101
|
+
}
|
|
102
|
+
function Histogram({ title, values, bins, fmtMedian = fmtInt, sortKey, onSort }) {
|
|
103
|
+
const counts = bins.map(b => ({ label: b.label, count: values.filter(b.test).length }));
|
|
104
|
+
const max = Math.max(1, ...counts.map(c => c.count));
|
|
105
|
+
const total = Math.max(1, values.length);
|
|
106
|
+
return (_jsxs("section", { className: "block ihist", children: [_jsxs("div", { className: "ihist-head", children: [_jsx("h3", { children: title }), _jsx("span", { className: "mut", children: t('insights.distMedian', { v: fmtMedian(median(values)) }) }), sortKey ? _jsxs("button", { type: "button", className: "ihist-jump", onClick: () => onSort?.(sortKey), children: [t('insights.viewSessions'), " \u203A"] }) : null] }), _jsx("div", { className: "hbars", children: counts.map(c => {
|
|
107
|
+
const pct = Math.max(c.count ? 4 : 0, Math.round((c.count / max) * 100));
|
|
108
|
+
const share = Math.round((c.count / total) * 100);
|
|
109
|
+
return (_jsxs("div", { className: "hbrow", children: [_jsx("div", { className: "hblabel", children: c.label }), _jsx("div", { className: "hbtrack", children: _jsx("div", { className: "hbfill", style: { width: `${pct}%` } }) }), _jsxs("div", { className: "hbval", children: [fmtInt(c.count), _jsxs("small", { children: [share, "%"] })] })] }, c.label));
|
|
110
|
+
}) })] }));
|
|
111
|
+
}
|
|
112
|
+
function Distribution({ records, onSort }) {
|
|
113
|
+
const reports = okReports(records);
|
|
114
|
+
if (!reports.length)
|
|
115
|
+
return _jsx("div", { className: "insight-empty", children: t('insights.distEmpty') });
|
|
116
|
+
const churn = reports.map(r => (r.hot?.files ?? []).reduce((s, f) => s + (f.added ?? 0) + (f.removed ?? 0), 0));
|
|
117
|
+
const spans = reports.map(r => r.agg.totalSpans);
|
|
118
|
+
const failed = reports.map(r => r.agg.failedSpans);
|
|
119
|
+
const slow = reports.map(r => r.agg.slowSpans);
|
|
120
|
+
const agentMin = reports.map(r => agentMsOf(r) / 60000);
|
|
121
|
+
const rw = reports.map(r => r.agg.readWriteRatio).filter((v) => v !== null && Number.isFinite(v));
|
|
122
|
+
return (_jsxs(_Fragment, { children: [_jsx("p", { className: "mut ins-hint", children: t('insights.distHint') }), _jsxs("div", { className: "ihist-grid", children: [_jsx(Trend, { records: records }), _jsx(Histogram, { title: t('insights.distSpans'), values: spans, onSort: onSort, sortKey: "spans", bins: [
|
|
123
|
+
{ label: '0-10', test: v => v <= 10 },
|
|
124
|
+
{ label: '11-50', test: v => v > 10 && v <= 50 },
|
|
125
|
+
{ label: '51-200', test: v => v > 50 && v <= 200 },
|
|
126
|
+
{ label: '201-500', test: v => v > 200 && v <= 500 },
|
|
127
|
+
{ label: '500+', test: v => v > 500 },
|
|
128
|
+
] }), _jsx(Histogram, { title: t('insights.distFailed'), values: failed, onSort: onSort, sortKey: "fails", bins: [
|
|
129
|
+
{ label: '0', test: v => v === 0 },
|
|
130
|
+
{ label: '1-2', test: v => v >= 1 && v <= 2 },
|
|
131
|
+
{ label: '3-5', test: v => v >= 3 && v <= 5 },
|
|
132
|
+
{ label: '6-10', test: v => v >= 6 && v <= 10 },
|
|
133
|
+
{ label: '10+', test: v => v > 10 },
|
|
134
|
+
] }), _jsx(Histogram, { title: t('insights.distSlow'), values: slow, onSort: onSort, sortKey: "slow", bins: [
|
|
135
|
+
{ label: '0', test: v => v === 0 },
|
|
136
|
+
{ label: '1-2', test: v => v >= 1 && v <= 2 },
|
|
137
|
+
{ label: '3-5', test: v => v >= 3 && v <= 5 },
|
|
138
|
+
{ label: '5+', test: v => v > 5 },
|
|
139
|
+
] }), _jsx(Histogram, { title: t('insights.distAgentTime'), values: agentMin, onSort: onSort, sortKey: "agent", fmtMedian: n => `${Math.round(n)}m`, bins: [
|
|
140
|
+
{ label: '<1m', test: v => v < 1 },
|
|
141
|
+
{ label: '1-5m', test: v => v >= 1 && v < 5 },
|
|
142
|
+
{ label: '5-30m', test: v => v >= 5 && v < 30 },
|
|
143
|
+
{ label: '30m-2h', test: v => v >= 30 && v < 120 },
|
|
144
|
+
{ label: '2h+', test: v => v >= 120 },
|
|
145
|
+
] }), rw.length ? (_jsx(Histogram, { title: t('insights.distRw'), values: rw, fmtMedian: n => n.toFixed(1), bins: [
|
|
146
|
+
{ label: '0', test: v => v === 0 },
|
|
147
|
+
{ label: '0-1', test: v => v > 0 && v < 1 },
|
|
148
|
+
{ label: '1-3', test: v => v >= 1 && v < 3 },
|
|
149
|
+
{ label: '3+', test: v => v >= 3 },
|
|
150
|
+
] })) : null, _jsx(Histogram, { title: t('insights.distChurn'), values: churn, bins: [
|
|
151
|
+
{ label: '0', test: v => v === 0 },
|
|
152
|
+
{ label: '1-100', test: v => v > 0 && v <= 100 },
|
|
153
|
+
{ label: '100-1k', test: v => v > 100 && v <= 1000 },
|
|
154
|
+
{ label: '1k-10k', test: v => v > 1000 && v <= 10000 },
|
|
155
|
+
{ label: '10k+', test: v => v > 10000 },
|
|
156
|
+
] })] }), _jsx(DisclosureNote, {})] }));
|
|
157
|
+
}
|
|
158
|
+
function Flow({ records, onSession }) {
|
|
159
|
+
const ok = records.filter(r => r.report && r.report.status === 'ok' && r.report.agg);
|
|
160
|
+
if (!ok.length)
|
|
161
|
+
return _jsx("div", { className: "insight-empty", children: t('insights.distEmpty') });
|
|
162
|
+
const tot = {};
|
|
163
|
+
for (const p of FLOW_PHASES)
|
|
164
|
+
tot[p] = { count: 0, ms: 0 };
|
|
165
|
+
for (const rec of ok)
|
|
166
|
+
for (const p of FLOW_PHASES) {
|
|
167
|
+
const v = rec.report.agg.phase?.[p];
|
|
168
|
+
if (v) {
|
|
169
|
+
tot[p].count += v.count;
|
|
170
|
+
tot[p].ms += v.ms;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const totCount = FLOW_PHASES.reduce((s, p) => s + tot[p].count, 0) || 1;
|
|
174
|
+
const totMs = FLOW_PHASES.reduce((s, p) => s + tot[p].ms, 0) || 1;
|
|
175
|
+
const rhythm = [...ok].sort((a, b) => agentMsOf(b.report) - agentMsOf(a.report)).slice(0, 40);
|
|
176
|
+
return (_jsxs(_Fragment, { children: [_jsx("p", { className: "mut ins-hint", children: t('insights.flowHint') }), _jsxs("section", { className: "block flow-pipe-block", children: [_jsx("h3", { children: t('insights.flowPipeline') }), _jsx("p", { className: "mut flow-sub", children: t('insights.flowPipeSub') }), _jsx("div", { className: "flow-pipe", children: FLOW_PHASES.map((p, i) => {
|
|
177
|
+
const v = tot[p];
|
|
178
|
+
const cPct = Math.round((v.count / totCount) * 100);
|
|
179
|
+
const tPct = Math.round((v.ms / totMs) * 100);
|
|
180
|
+
return (_jsxs("span", { style: { display: 'contents' }, children: [_jsxs("div", { className: "flow-node", children: [_jsx("i", { className: `flow-dot ${phaseClass(p)}` }), _jsx("strong", { children: phaseLabel(p) }), _jsxs("span", { className: "flow-n", children: [fmtInt(v.count), _jsxs("em", { children: [" \u00B7 ", cPct, "%"] })] }), _jsxs("span", { className: "flow-t", children: [fmtMs(v.ms), _jsxs("em", { children: [" \u00B7 ", tPct, "%"] })] })] }), i < FLOW_PHASES.length - 1 ? _jsx("span", { className: "flow-arrow", children: "\u2192" }) : null] }, p));
|
|
181
|
+
}) })] }), _jsxs("div", { className: "insights-overview-grid", children: [_jsxs("section", { className: "block", children: [_jsx("h3", { children: t('insights.flowShares') }), _jsxs("div", { className: "flow-bhead", children: [_jsx("span", {}), _jsx("span", { children: t('insights.flowActShare') }), _jsx("span", { children: t('insights.flowTimeShare') })] }), _jsx("div", { className: "flow-bars", children: FLOW_PHASES.map(p => {
|
|
182
|
+
const v = tot[p];
|
|
183
|
+
const cPct = (v.count / totCount) * 100;
|
|
184
|
+
const tPct = (v.ms / totMs) * 100;
|
|
185
|
+
return (_jsxs("div", { className: "flow-brow", children: [_jsxs("span", { className: "flow-blabel", children: [_jsx("i", { className: phaseClass(p) }), phaseLabel(p)] }), _jsxs("span", { className: "flow-btrack", children: [_jsx("span", { className: `flow-bfill ${phaseClass(p)}`, style: { width: `${Math.max(2, cPct).toFixed(1)}%` } }), _jsxs("em", { children: [Math.round(cPct), "%"] })] }), _jsxs("span", { className: "flow-btrack", children: [_jsx("span", { className: `flow-bfill ${phaseClass(p)}`, style: { width: `${Math.max(2, tPct).toFixed(1)}%` } }), _jsxs("em", { children: [Math.round(tPct), "%"] })] })] }, p));
|
|
186
|
+
}) })] }), _jsxs("section", { className: "block", children: [_jsx("h3", { children: t('insights.flowRhythm') }), _jsx("div", { className: "flow-rhythm", children: rhythm.map(rec => (_jsxs("button", { type: "button", className: "flow-sess", onClick: () => onSession(String(rec.session.sessionId)), children: [_jsx("span", { className: "flow-stitle", children: sessionTitle(rec.session) }), _jsx(PhaseMixBar, { phase: rec.report.agg.phase }), _jsx("span", { className: "flow-stime", children: fmtMs(agentMsOf(rec.report)) })] }, String(rec.session.sessionId)))) }), _jsx("div", { className: "rl-legend", children: FLOW_PHASES.map(p => _jsxs("span", { className: "rl-item", children: [_jsx("i", { className: phaseClass(p) }), phaseLabel(p)] }, p)) })] })] }), _jsx(DisclosureNote, {})] }));
|
|
187
|
+
}
|
|
188
|
+
function Hotspots({ records, openHot, onToggleHot, onProject, onSession }) {
|
|
189
|
+
const reports = records.filter(r => !!r.report && r.report.status === 'ok');
|
|
190
|
+
if (!reports.length)
|
|
191
|
+
return _jsx("div", { className: "insight-empty", children: t('insights.distEmpty') });
|
|
192
|
+
const fileAgg = new Map();
|
|
193
|
+
const cmdAgg = new Map();
|
|
194
|
+
const errAgg = new Map();
|
|
195
|
+
const bump = (m, key, label, sid, title) => {
|
|
196
|
+
let h = m.get(key);
|
|
197
|
+
if (!h) {
|
|
198
|
+
h = { key, label, sessions: [], reads: 0, edits: 0, runs: 0, fails: 0, count: 0 };
|
|
199
|
+
m.set(key, h);
|
|
200
|
+
}
|
|
201
|
+
if (!h.sessions.some(s => s.id === sid))
|
|
202
|
+
h.sessions.push({ id: sid, title });
|
|
203
|
+
return h;
|
|
204
|
+
};
|
|
205
|
+
for (const rec of reports) {
|
|
206
|
+
const sid = String(rec.session.sessionId);
|
|
207
|
+
const title = sessionTitle(rec.session);
|
|
208
|
+
const hot = rec.report.hot;
|
|
209
|
+
for (const f of hot?.files ?? []) {
|
|
210
|
+
const h = bump(fileAgg, `file:${f.path}`, f.path, sid, title);
|
|
211
|
+
h.reads += f.reads;
|
|
212
|
+
h.edits += f.edits;
|
|
213
|
+
}
|
|
214
|
+
for (const c of hot?.cmds ?? []) {
|
|
215
|
+
const h = bump(cmdAgg, `cmd:${c.cmd}`, c.cmd, sid, title);
|
|
216
|
+
h.runs += c.runs;
|
|
217
|
+
h.fails += c.fails;
|
|
218
|
+
}
|
|
219
|
+
for (const e of hot?.errs ?? []) {
|
|
220
|
+
const h = bump(errAgg, `err:${e.tool} ${e.result}`, `${e.tool} · ${resultLabel(e.result)}`, sid, title);
|
|
221
|
+
h.count += e.count;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const recur = (a, b) => b.sessions.length - a.sessions.length;
|
|
225
|
+
const files = [...fileAgg.values()].sort((a, b) => recur(a, b) || (b.edits + b.reads) - (a.edits + a.reads)).slice(0, 12);
|
|
226
|
+
const cmds = [...cmdAgg.values()].sort((a, b) => recur(a, b) || b.fails - a.fails || b.runs - a.runs).slice(0, 12);
|
|
227
|
+
const errs = [...errAgg.values()].sort((a, b) => recur(a, b) || b.count - a.count).slice(0, 10);
|
|
228
|
+
const projMap = new Map();
|
|
229
|
+
for (const rec of reports) {
|
|
230
|
+
const p = projectOf(rec);
|
|
231
|
+
if (!p)
|
|
232
|
+
continue;
|
|
233
|
+
const e = projMap.get(p) ?? { sessions: 0, fails: 0 };
|
|
234
|
+
e.sessions += 1;
|
|
235
|
+
e.fails += rec.report.agg.failedSpans;
|
|
236
|
+
projMap.set(p, e);
|
|
237
|
+
}
|
|
238
|
+
const projects = [...projMap.entries()].map(([id, v]) => ({ id, ...v })).sort((a, b) => b.fails - a.fails || b.sessions - a.sessions).slice(0, 10);
|
|
239
|
+
const projRows = projects.map(x => ({ key: x.id, label: x.id, count: x.sessions, sub: _jsxs("small", { children: [x.fails, " ", t('insights.hotFailsCol')] }) }));
|
|
240
|
+
const slowSessions = [...reports].filter(r => r.report.agg.slowSpans > 0).sort((a, b) => b.report.agg.slowSpans - a.report.agg.slowSpans).slice(0, 8);
|
|
241
|
+
return (_jsxs(_Fragment, { children: [_jsx("p", { className: "mut ins-hint", children: t('insights.hotHint') }), _jsxs("div", { className: "hot-grid", children: [_jsx(HotBlock, { title: t('insights.hotFiles'), rows: files, openHot: openHot, onToggle: onToggleHot, onSession: onSession, meta: h => `${t('insights.readsShort')}${h.reads} · ${t('insights.editsShort')}${h.edits}` }), _jsx(HotBlock, { title: t('insights.hotCommands'), rows: cmds, openHot: openHot, onToggle: onToggleHot, onSession: onSession, meta: h => _jsxs(_Fragment, { children: [h.runs, "\u00D7", h.fails ? _jsxs(_Fragment, { children: [" \u00B7 ", _jsxs("span", { className: "bad", children: [h.fails, " ", t('insights.hotFailsCol')] })] }) : null] }) }), _jsx(HotBlock, { title: t('insights.hotErrors'), rows: errs, openHot: openHot, onToggle: onToggleHot, onSession: onSession, meta: h => `${h.count}×` }), _jsxs("section", { className: "block", children: [_jsx("h3", { children: t('insights.hotProjects') }), _jsx(HBarList, { rows: projRows, empty: "-", onRowClick: row => onProject(row.key), renderValue: row => fmtInt(row.count) })] }), _jsxs("section", { className: "block hot-sessions", children: [_jsx("h3", { children: t('insights.hotSlowSessions') }), _jsx("div", { className: "slist", children: slowSessions.length ? slowSessions.map(rec => {
|
|
242
|
+
const a = rec.report.agg;
|
|
243
|
+
return (_jsxs("button", { type: "button", className: "srow", onClick: () => onSession(String(rec.session.sessionId)), children: [_jsxs("div", { className: "srmain", children: [_jsx("strong", { children: sessionTitle(rec.session) }), _jsxs("small", { children: [botDisplayName(rec.session), " \u00B7 ", String(rec.session.cliId ?? '-')] })] }), _jsxs("div", { className: "srstats", children: [_jsxs("b", { children: [t('insights.hotSlowCol'), _jsx("em", { children: fmtInt(a.slowSpans) })] }), _jsxs("b", { children: ["span", _jsx("em", { children: fmtInt(a.totalSpans) })] })] })] }, String(rec.session.sessionId)));
|
|
244
|
+
}) : _jsx("p", { className: "mut", children: "-" }) })] })] })] }));
|
|
245
|
+
}
|
|
246
|
+
function HotBlock({ title, rows, openHot, onToggle, onSession, meta }) {
|
|
247
|
+
return (_jsxs("section", { className: "block", children: [_jsx("h3", { children: title }), _jsx("div", { className: "hotlist", children: rows.length ? rows.map(h => {
|
|
248
|
+
const open = openHot.has(h.key);
|
|
249
|
+
return (_jsxs("div", { className: `hotitem${open ? ' open' : ''}`, children: [_jsxs("button", { type: "button", className: "hotrow", "aria-expanded": open, onClick: () => onToggle(h.key), children: [_jsx("span", { className: "hotlabel", title: h.label, children: h.label }), _jsx("span", { className: "hotmeta", children: meta(h) }), _jsxs("span", { className: "hotses", children: [h.sessions.length, " ", t('insights.hotSessionsCol')] })] }), open ? (_jsx("div", { className: "hot-sess", children: h.sessions.map(s => _jsx("button", { type: "button", className: "hot-sesslink", onClick: () => onSession(s.id), children: s.title || s.id }, s.id)) })) : null] }, h.key));
|
|
250
|
+
}) : _jsx("p", { className: "mut", children: "-" }) })] }));
|
|
251
|
+
}
|
|
252
|
+
function SortBar({ sort, layout, onSort, onLayout }) {
|
|
253
|
+
const sortOptions = SESS_SORTS.map(s => ({ value: s.key, label: t(s.label) }));
|
|
254
|
+
const sortLabel = sortOptions.find(option => option.value === sort)?.label ?? t('insights.sortLabel');
|
|
255
|
+
return (_jsxs(_Fragment, { children: [_jsx(DropdownMenu, { id: "insight-session-sort", className: "insight-sort-menu", ariaLabel: t('insights.sortLabel'), label: sortLabel, value: sort, options: sortOptions, onChange: onSort }), _jsx("div", { className: "insight-layout-switch", role: "group", "aria-label": `${t('insights.layoutCard')} / ${t('insights.layoutTable')}`, children: [
|
|
256
|
+
['card', 'insights.layoutCard'],
|
|
257
|
+
['table', 'insights.layoutTable'],
|
|
258
|
+
].map(([k, lbl]) => (_jsx("button", { type: "button", className: layout === k ? 'active' : '', "aria-pressed": layout === k, onClick: () => onLayout(k), children: t(lbl) }, k))) })] }));
|
|
259
|
+
}
|
|
260
|
+
function SessionRows({ records, selectedId, wide, onSelect, emptyHint }) {
|
|
261
|
+
if (!records.length) {
|
|
262
|
+
return (_jsxs("div", { className: "insight-empty", children: [t('insights.empty'), emptyHint ? _jsxs(_Fragment, { children: [_jsx("br", {}), _jsx("span", { className: "mut", children: emptyHint })] }) : null] }));
|
|
263
|
+
}
|
|
264
|
+
const stat = (label, val, bad = false) => _jsxs("b", { className: bad ? 'bad' : undefined, children: [label, _jsx("em", { children: val })] });
|
|
265
|
+
return (_jsx("div", { className: `slist${wide ? ' wide' : ''}`, children: records.map(rec => {
|
|
266
|
+
const s = rec.session;
|
|
267
|
+
const r = rec.report;
|
|
268
|
+
const ok = r?.status === 'ok';
|
|
269
|
+
const agg = r?.agg;
|
|
270
|
+
const reads = agg?.phase?.research?.count ?? 0;
|
|
271
|
+
const edits = agg?.phase?.edit?.count ?? 0;
|
|
272
|
+
return (_jsxs("button", { type: "button", className: `srow${s.sessionId === selectedId ? ' on' : ''}${reportNeedsReview(r) ? ' review' : ''}`, onClick: () => onSelect(String(s.sessionId)), children: [_jsxs("div", { className: "srmain", children: [_jsx("strong", { children: sessionTitle(s) }), _jsx("small", { children: sessionMetaLine(rec) }), ok ? _jsx(PhaseMixBar, { phase: r.agg.phase }) : null] }), ok ? (_jsxs("div", { className: "srstats", children: [stat(t('insights.spansShort'), fmtInt(agg.totalSpans)), stat(t('insights.failedShort'), fmtInt(agg.failedSpans), agg.failedSpans > 0), stat(t('insights.slowShort'), fmtInt(agg.slowSpans)), wide ? _jsxs(_Fragment, { children: [stat(t('insights.readsShort'), fmtInt(reads)), stat(t('insights.editsShort'), fmtInt(edits)), stat(t('insights.durShort'), fmtMs(agentMsOf(r)))] }) : null, stat(t('insights.rwShort'), agg.readWriteRatio !== null ? agg.readWriteRatio.toFixed(1) : '-')] })) : _jsx("div", { className: "srmsg", children: safeStatus(r, rec.error) })] }, String(s.sessionId)));
|
|
273
|
+
}) }));
|
|
274
|
+
}
|
|
275
|
+
function SessionTable({ records, selectedId, onSelect, emptyHint }) {
|
|
276
|
+
if (!records.length) {
|
|
277
|
+
return (_jsxs("div", { className: "insight-empty", children: [t('insights.empty'), emptyHint ? _jsxs(_Fragment, { children: [_jsx("br", {}), _jsx("span", { className: "mut", children: emptyHint })] }) : null] }));
|
|
278
|
+
}
|
|
279
|
+
return (_jsxs("div", { className: "stable", children: [_jsxs("div", { className: "strow sthead", children: [_jsx("span", { className: "stc-title", children: t('insights.colTitle') }), _jsx("span", { className: "stc-proj", children: t('insights.colProject') }), _jsx("span", { className: "stc-num", children: t('insights.spansShort') }), _jsx("span", { className: "stc-num", children: t('insights.failedShort') }), _jsx("span", { className: "stc-num", children: t('insights.slowShort') }), _jsx("span", { className: "stc-num", children: t('insights.rwShort') }), _jsx("span", { className: "stc-num", children: t('insights.durShort') }), _jsx("span", { className: "stc-num", children: t('insights.colTime') })] }), records.map(rec => {
|
|
280
|
+
const s = rec.session;
|
|
281
|
+
const r = rec.report;
|
|
282
|
+
const ok = r?.status === 'ok';
|
|
283
|
+
const agg = r?.agg;
|
|
284
|
+
return (_jsxs("button", { type: "button", className: `strow${s.sessionId === selectedId ? ' on' : ''}${reportNeedsReview(r) ? ' review' : ''}${ok ? '' : ' nostat'}`, onClick: () => onSelect(String(s.sessionId)), children: [_jsxs("span", { className: "stc-title", children: [_jsx("strong", { children: sessionTitle(s) }), _jsxs("small", { children: [botDisplayName(s), " \u00B7 ", String(s.cliId ?? '-')] })] }), _jsx("span", { className: "stc-proj", children: s.workingDir ? projectOf(rec) : '-' }), ok ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "stc-num", children: fmtInt(agg.totalSpans) }), _jsx("span", { className: `stc-num${agg.failedSpans ? ' bad' : ''}`, children: fmtInt(agg.failedSpans) }), _jsx("span", { className: "stc-num", children: fmtInt(agg.slowSpans) }), _jsx("span", { className: "stc-num", children: agg.readWriteRatio !== null ? agg.readWriteRatio.toFixed(1) : '-' }), _jsx("span", { className: "stc-num", children: fmtMs(agentMsOf(r)) })] })) : _jsx("span", { className: "stc-msg", children: safeStatus(r, rec.error) }), _jsx("span", { className: "stc-num stc-time", children: relTime(s.lastMessageAt ?? s.spawnedAt ?? 0) })] }, String(s.sessionId)));
|
|
285
|
+
})] }));
|
|
286
|
+
}
|
|
287
|
+
function TextPreview({ label, preview }) {
|
|
288
|
+
if (!preview?.text)
|
|
289
|
+
return null;
|
|
290
|
+
return (_jsxs("div", { className: "span-io", children: [_jsx("span", { className: "span-io-label", children: label }), _jsxs("pre", { className: "span-io-text", children: [preview.text, preview.truncated ? '\n…' : ''] })] }));
|
|
291
|
+
}
|
|
292
|
+
function OpGlyph({ span, current }) {
|
|
293
|
+
const title = `${intentText(span)}${span.intent?.subject ? ` ${span.intent.subject}` : ''}${span.result ? ` → ${resultLabel(span.result.category)}` : ''} · ${fmtMs(span.durationMs)}`;
|
|
294
|
+
return _jsx("i", { className: `op ph-${phaseSlug(span.phase)}${spanFailed(span) ? ' bad' : ''}${current ? ' cur' : ''}`, title: title });
|
|
295
|
+
}
|
|
296
|
+
function SpanDetail({ spans, idx }) {
|
|
297
|
+
const s = spans[idx];
|
|
298
|
+
if (!s)
|
|
299
|
+
return null;
|
|
300
|
+
const d = s.detail;
|
|
301
|
+
const ev = d?.evidence ?? s.evidence;
|
|
302
|
+
const kv = [
|
|
303
|
+
[t('insights.dPhase'), phaseLabel(s.phase)],
|
|
304
|
+
[t('insights.dStart'), fmtMs(s.relStartMs)],
|
|
305
|
+
[t('insights.dDur'), fmtMs(s.durationMs)],
|
|
306
|
+
];
|
|
307
|
+
const intent = intentPhrase(s.intent);
|
|
308
|
+
if (intent)
|
|
309
|
+
kv.push([t('insights.dIntent'), intent]);
|
|
310
|
+
if (s.result)
|
|
311
|
+
kv.push([t('insights.dResult'), `${resultLabel(s.result.category)}${s.result.exitCode !== undefined ? ` · exit ${s.result.exitCode}` : ''}`]);
|
|
312
|
+
if (s.inputSummary)
|
|
313
|
+
kv.push([t('insights.dIn'), structLabel(s.inputSummary)]);
|
|
314
|
+
if (s.outputSummary)
|
|
315
|
+
kv.push([t('insights.dOut'), structLabel(s.outputSummary)]);
|
|
316
|
+
const tags = (s.tags ?? []).filter(tg => tg !== 'normal');
|
|
317
|
+
if (tags.length)
|
|
318
|
+
kv.push([t('insights.dTags'), tags.map(tagLabel).join('、')]);
|
|
319
|
+
const prev = d?.context?.previousIntent ? intentPhrase(d.context.previousIntent) : '';
|
|
320
|
+
const next = d?.context?.nextIntent ? intentPhrase(d.context.nextIntent) : '';
|
|
321
|
+
const sibs = spans.map((sp, i) => ({ sp, i })).filter(x => x.sp.turnIndex === s.turnIndex).sort((a, b) => (a.sp.relStartMs ?? 0) - (b.sp.relStartMs ?? 0));
|
|
322
|
+
return (_jsxs("div", { className: "spandetail", children: [_jsx("dl", { className: "span-kv", children: kv.map(([k, v]) => _jsxs("div", { children: [_jsx("dt", { children: k }), _jsx("dd", { children: v })] }, k)) }), _jsx(TextPreview, { label: t('insights.dCommand'), preview: ev?.command }), _jsx(TextPreview, { label: t('insights.dCmdOutput'), preview: ev?.output }), prev || next ? _jsxs("div", { className: "span-flank", children: [prev ? _jsxs("span", { className: "sf-prev", children: ["\u2191 ", prev] }) : null, next ? _jsxs("span", { className: "sf-next", children: ["\u2193 ", next] }) : null] }) : null, _jsxs("div", { className: "span-ctx", children: [_jsx("span", { className: "span-ctx-label", children: t('insights.dTurnContext', { turn: s.turnIndex }) }), _jsx("div", { className: "opstrip", children: sibs.map(x => _jsx(OpGlyph, { span: x.sp, current: x.i === idx }, x.i)) })] })] }));
|
|
323
|
+
}
|
|
324
|
+
function SpanRow({ spans, idx, hot, open, detailable = true, onToggle, scrollTarget, onScrolled }) {
|
|
325
|
+
const ref = useRef(null);
|
|
326
|
+
const s = spans[idx];
|
|
327
|
+
useEffect(() => {
|
|
328
|
+
if (!ref.current || scrollTarget?.kind !== 'span' || scrollTarget.index !== idx)
|
|
329
|
+
return;
|
|
330
|
+
ref.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
331
|
+
onScrolled?.();
|
|
332
|
+
}, [idx, onScrolled, scrollTarget]);
|
|
333
|
+
if (!s)
|
|
334
|
+
return null;
|
|
335
|
+
const subject = s.intent?.subject ? _jsx("code", { className: "span-subj", children: s.intent.subject }) : null;
|
|
336
|
+
const res = s.result;
|
|
337
|
+
const tags = (s.tags ?? []).filter(tg => tg !== 'normal' && tg !== 'diagnostic');
|
|
338
|
+
const toggle = () => detailable && onToggle?.(idx);
|
|
339
|
+
return (_jsxs("div", { ref: ref, className: `spanrow ph-${phaseSlug(s.phase)}${s.status === 'error' ? ' error' : ''}${hot ? ' hot' : ''}${open ? ' open' : ''}`, children: [_jsxs("div", { className: `sprow-line${detailable ? ' clickable' : ''}`, role: detailable ? 'button' : undefined, tabIndex: detailable ? 0 : undefined, "aria-expanded": detailable ? open : undefined, onClick: toggle, onKeyDown: event => {
|
|
340
|
+
if (!detailable)
|
|
341
|
+
return;
|
|
342
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
343
|
+
event.preventDefault();
|
|
344
|
+
toggle();
|
|
345
|
+
}
|
|
346
|
+
}, children: [_jsxs("span", { className: "span-turn", title: `${t('insights.dStart')} ${fmtMs(s.relStartMs)}`, children: ["#", String(s.turnIndex ?? 0)] }), _jsx("span", { className: `spanst ${s.status}`, children: statusIcon(s.status) }), _jsx("b", { className: "span-what", children: intentText(s) }), subject, res && BAD_RESULTS.has(res.category) ? _jsxs("span", { className: "span-res rc-bad", children: [resultLabel(res.category), res.exitCode !== undefined ? ` · exit ${res.exitCode}` : ''] }) : null, _jsx("span", { className: "span-tags", children: tags.map(tg => _jsx("span", { className: `span-tag tg-${tg}`, children: tagLabel(tg) }, tg)) }), _jsx("span", { className: "span-dur", children: fmtMs(s.durationMs) }), detailable ? _jsx("span", { className: "span-detail-btn", "aria-hidden": "true", children: open ? t('insights.dCollapse') : t('insights.dDetail') }) : null] }), detailable && open ? _jsx(SpanDetail, { spans: spans, idx: idx }) : null] }));
|
|
347
|
+
}
|
|
348
|
+
function WorkSummary({ report }) {
|
|
349
|
+
const ws = report.workSummary;
|
|
350
|
+
if (!ws || (!ws.fileChanges?.length && !ws.commandsRun?.length))
|
|
351
|
+
return null;
|
|
352
|
+
const files = ws.fileChanges ?? [];
|
|
353
|
+
const cmds = ws.commandsRun ?? [];
|
|
354
|
+
return (_jsxs("div", { className: "worksum", children: [_jsxs("section", { className: "ws-panel", children: [_jsx("h4", { children: t('insights.wsFiles', { n: files.length }) }), _jsx("div", { className: "ws-list", children: files.length ? files.map((f, i) => (_jsxs("div", { className: "ws-row", children: [_jsx("code", { className: "ws-path", title: f.path, children: f.path }), _jsx("span", { className: "ws-meta", children: t('insights.wsReads', { n: f.reads }) }), (f.added || f.removed) ? _jsxs("span", { className: "ws-stat", children: [_jsxs("span", { className: "ws-add", children: ["+", f.added ?? 0] }), _jsxs("span", { className: "ws-del", children: ["-", f.removed ?? 0] })] }) : f.edits ? _jsx("span", { className: "ws-stat ws-stat-edits", children: t('insights.wsEdits', { n: f.edits }) }) : null] }, `${f.path}:${i}`))) : _jsx("p", { className: "mut", children: t('insights.wsNoFiles') }) })] }), _jsxs("section", { className: "ws-panel", children: [_jsx("h4", { children: t('insights.wsCmds', { n: cmds.length }) }), _jsx("div", { className: "ws-list", children: cmds.length ? cmds.map((c, i) => {
|
|
355
|
+
const bad = c.failures > 0;
|
|
356
|
+
return (_jsxs("div", { className: `ws-row${bad ? ' bad' : ''}`, children: [_jsxs("code", { className: "ws-cmd", title: c.command.text, children: [c.command.text, c.command.truncated ? '…' : ''] }), _jsxs("span", { className: "ws-meta", children: [c.count > 1 ? _jsxs("span", { className: "ws-x", children: ["\u00D7", c.count] }) : null, bad ? _jsx("span", { className: "ws-fail", children: t('insights.wsFail', { n: c.failures }) }) : null] })] }, `${c.command.text}:${i}`));
|
|
357
|
+
}) : _jsx("p", { className: "mut", children: t('insights.wsNoCmds') }) })] })] }));
|
|
358
|
+
}
|
|
359
|
+
function Evidence({ report, focus, view, onSpanFilter, onToggleSpan, scrollTarget, onScrolled }) {
|
|
360
|
+
const spans = report.spans ?? [];
|
|
361
|
+
if (!spans.length) {
|
|
362
|
+
return _jsxs(_Fragment, { children: [_jsx(WorkSummary, { report: report }), _jsx("p", { className: "mut", children: t('insights.noSpans') })] });
|
|
363
|
+
}
|
|
364
|
+
const order = [...spans.keys()].sort((a, b) => (spans[a].relStartMs ?? 0) - (spans[b].relStartMs ?? 0));
|
|
365
|
+
const counts = new Map();
|
|
366
|
+
for (const i of order)
|
|
367
|
+
for (const tg of spans[i].tags ?? [])
|
|
368
|
+
if (SPAN_TAGS.includes(tg))
|
|
369
|
+
counts.set(tg, (counts.get(tg) ?? 0) + 1);
|
|
370
|
+
const visible = view.spanFilter === 'all' ? order : order.filter(i => spans[i].tags?.includes(view.spanFilter));
|
|
371
|
+
return (_jsxs("div", { className: "evidence", children: [_jsx(WorkSummary, { report: report }), focus.rec ? _jsx("div", { className: "ev-reason", children: idText('why', focus.rec.why) }) : null, _jsxs("div", { className: "spanfilter", children: [_jsxs("button", { type: "button", className: `spanchip${view.spanFilter === 'all' ? ' on' : ''}`, onClick: () => onSpanFilter('all'), children: [t('insights.spanAll'), " ", _jsx("b", { children: order.length })] }), SPAN_TAGS.filter(tg => counts.has(tg)).map(tg => (_jsxs("button", { type: "button", className: `spanchip tg-${tg}${view.spanFilter === tg ? ' on' : ''}`, onClick: () => onSpanFilter(tg), children: [tagLabel(tg), " ", _jsx("b", { children: counts.get(tg) })] }, tg)))] }), _jsx("div", { className: "spantable", children: visible.length ? visible.map(i => (_jsx(SpanRow, { spans: spans, idx: i, hot: focus.spanIdx.has(i), open: view.openSpans.has(i), onToggle: onToggleSpan, scrollTarget: scrollTarget, onScrolled: onScrolled }, i))) : _jsx("p", { className: "mut", children: t('insights.evNoFlags') }) })] }));
|
|
372
|
+
}
|
|
373
|
+
function PromptSourceChip({ src, fallback }) {
|
|
374
|
+
if (!src?.kind)
|
|
375
|
+
return _jsx("span", { className: "tp-label", children: fallback ?? t('insights.turnPrompt') });
|
|
376
|
+
if (src.kind === 'a2a_agent') {
|
|
377
|
+
const name = src.agentName || src.senderName;
|
|
378
|
+
return _jsxs("span", { className: "tp-label tp-src tp-src-a2a", children: ["\uD83E\uDD1D ", name ? `${name} · a2a` : 'a2a'] });
|
|
379
|
+
}
|
|
380
|
+
if (src.kind === 'system')
|
|
381
|
+
return _jsxs("span", { className: "tp-label tp-src tp-src-system", children: ["\u2699\uFE0F ", t('insights.senderSystem')] });
|
|
382
|
+
return _jsxs("span", { className: "tp-label tp-src tp-src-user", children: ["\uD83D\uDC64 ", src.senderName || fallback || t('insights.turnPrompt')] });
|
|
383
|
+
}
|
|
384
|
+
function PromptMentions({ src }) {
|
|
385
|
+
const mentions = (src?.mentionedNames ?? []).filter(Boolean);
|
|
386
|
+
if (!mentions.length)
|
|
387
|
+
return null;
|
|
388
|
+
return _jsxs("span", { className: "tp-mentions", children: [t('insights.srcMentions'), " ", mentions.map(n => `@${n}`).join(' ')] });
|
|
389
|
+
}
|
|
390
|
+
function TurnEventGlyph({ event }) {
|
|
391
|
+
const bad = event.status === 'error' || (!!event.result && BAD_RESULTS.has(event.result.category));
|
|
392
|
+
const what = intentTextOf(event.intent, String(event.label.params.tool ?? event.kind));
|
|
393
|
+
const title = `${what}${event.intent?.subject ? ` ${event.intent.subject}` : ''}${event.result ? ` → ${resultLabel(event.result.category)}` : ''} · ${fmtMs(event.durationMs)}`;
|
|
394
|
+
return _jsx("i", { className: `op ph-${phaseSlug(event.phase)}${bad ? ' bad' : ''}`, title: title });
|
|
395
|
+
}
|
|
396
|
+
function TurnCard({ report, turn, focus, view, onToggleTurn, onToggleSpan, onTogglePrompt, onTogglePromptRaw, onOpenPrompt, scrollTarget, onScrolled }) {
|
|
397
|
+
const ref = useRef(null);
|
|
398
|
+
const spans = report.spans ?? [];
|
|
399
|
+
const open = view.openTurns.has(turn.turnIndex);
|
|
400
|
+
const hot = focus.turnIdx.has(turn.turnIndex);
|
|
401
|
+
const m = turn.metrics;
|
|
402
|
+
const advice = turn.severity !== 'info' ? turnAdvice(turn.tags) : '';
|
|
403
|
+
const ptext = turn.prompt?.text ? (cleanPromptText(turn.prompt.text) || turn.prompt.text) : '';
|
|
404
|
+
const promptExpanded = view.openPrompts.has(turn.turnIndex);
|
|
405
|
+
const promptRaw = view.rawPrompts.has(turn.turnIndex);
|
|
406
|
+
const promptBody = promptRaw
|
|
407
|
+
? _jsxs("pre", { className: "tp-raw", children: [ptext, turn.prompt?.truncated ? '\n…' : ''] })
|
|
408
|
+
: _jsx(MarkdownBody, { text: ptext + (turn.prompt?.truncated ? ' …' : '') });
|
|
409
|
+
const mini = `${t('insights.mEdits')}${m.edits} ${t('insights.mRuns')}${m.runs}${m.failures ? ` · ${t('insights.mFailures')}${m.failures}` : ''} · ${fmtMs(m.durationMs)}`;
|
|
410
|
+
useEffect(() => {
|
|
411
|
+
if (!ref.current || scrollTarget?.kind !== 'turn' || scrollTarget.index !== turn.turnIndex)
|
|
412
|
+
return;
|
|
413
|
+
ref.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
414
|
+
onScrolled();
|
|
415
|
+
}, [onScrolled, scrollTarget, turn.turnIndex]);
|
|
416
|
+
const pill = (label, val, bad = false) => _jsxs("span", { className: `tm${bad ? ' bad' : ''}`, children: [_jsx("i", { children: label }), _jsx("b", { children: val })] });
|
|
417
|
+
return (_jsxs("div", { ref: ref, className: `turnrow sev-${turn.severity}${hot ? ' hot' : ''}${turn.severity !== 'info' ? ' flagged' : ''}${open ? ' open' : ''}`, children: [_jsxs("div", { className: "turnline", children: [_jsxs("span", { className: "turn-no", children: ["#", String(turn.turnIndex)] }), _jsx("b", { className: "turn-headline", children: turnHeadline(turn.headline) }), _jsx("div", { className: "opstrip turn-strip", children: turn.events.map((e, i) => _jsx(TurnEventGlyph, { event: e }, `${e.spanIndex}:${i}`)) }), _jsx("span", { className: "turn-mini", children: mini }), _jsx("button", { type: "button", className: "turn-expand-btn", "aria-expanded": open, onClick: () => onToggleTurn(turn.turnIndex), children: open ? t('insights.turnCollapse') : t('insights.turnExpand', { count: turn.events.length }) })] }), ptext ? (_jsxs("div", { className: "turn-prompt", children: [_jsx(PromptSourceChip, { src: turn.prompt?.source }), _jsxs("div", { className: "tp-body", children: [_jsx("div", { className: `tp-md${promptExpanded ? ' expanded' : ''}`, children: promptBody }), _jsxs("div", { className: "tp-actions", children: [_jsx(PromptMentions, { src: turn.prompt?.source }), _jsx("button", { type: "button", className: "tp-toggle", onClick: () => onTogglePrompt(turn.turnIndex), children: promptExpanded ? t('insights.turnPromptCollapse') : t('insights.turnPromptExpand') }), _jsx("button", { type: "button", className: "tp-toggle", onClick: () => onTogglePromptRaw(turn.turnIndex), children: promptRaw ? t('insights.turnPromptRendered') : t('insights.turnPromptRaw') }), _jsx("button", { type: "button", className: "tp-toggle", onClick: () => onOpenPrompt(turn.turnIndex), children: t('insights.turnPromptFull') })] })] })] })) : null, advice ? _jsx("div", { className: "turn-advice", children: advice }) : null, open ? (_jsxs("div", { className: "turn-detail", children: [_jsxs("div", { className: "turn-metrics", children: [pill(t('insights.mReads'), String(m.reads)), pill(t('insights.mEdits'), String(m.edits)), pill(t('insights.mRuns'), String(m.runs)), m.failures ? pill(t('insights.mFailures'), String(m.failures), true) : null, pill(t('insights.mDur'), fmtMs(m.durationMs))] }), _jsx("div", { className: "spantable", children: turn.events.map(e => (_jsx(SpanRow, { spans: spans, idx: e.spanIndex, hot: false, open: view.openSpans.has(e.spanIndex), onToggle: onToggleSpan, scrollTarget: scrollTarget, onScrolled: onScrolled }, e.spanIndex))) })] })) : null] }));
|
|
418
|
+
}
|
|
419
|
+
function TurnEfficiency({ report, focus, view, onLedgerSort, onLedgerSender, onToggleTurn, onToggleSpan, onTogglePrompt, onTogglePromptRaw, onOpenPrompt, scrollTarget, onScrolled }) {
|
|
420
|
+
const spans = report.spans ?? [];
|
|
421
|
+
const allTurns = report.turnTimeline ?? [];
|
|
422
|
+
if (!allTurns.length)
|
|
423
|
+
return _jsx("p", { className: "mut", children: t('insights.noSpans') });
|
|
424
|
+
const recs = report.recommendations ?? [];
|
|
425
|
+
const canGroup = recs.some(r => (r.evidence?.turnIndexes?.length ?? 0) > 0);
|
|
426
|
+
const senderCount = { user: 0, a2a_agent: 0, system: 0 };
|
|
427
|
+
for (const tn of allTurns)
|
|
428
|
+
senderCount[turnSenderKind(tn)] += 1;
|
|
429
|
+
const turns = view.ledgerSender === 'all'
|
|
430
|
+
? allTurns.filter(tn => turnSenderKind(tn) !== 'system')
|
|
431
|
+
: allTurns.filter(tn => turnSenderKind(tn) === view.ledgerSender);
|
|
432
|
+
const flagged = turns.filter(tn => tn.severity !== 'info').length;
|
|
433
|
+
const card = (tn) => (_jsx(TurnCard, { report: report, turn: tn, focus: focus, view: view, onToggleTurn: onToggleTurn, onToggleSpan: onToggleSpan, onTogglePrompt: onTogglePrompt, onTogglePromptRaw: onTogglePromptRaw, onOpenPrompt: onOpenPrompt, scrollTarget: scrollTarget, onScrolled: onScrolled }, tn.turnIndex));
|
|
434
|
+
const controls = (_jsxs(_Fragment, { children: [_jsxs("div", { className: "spanfilter ledgersender", children: [_jsxs("button", { type: "button", className: `spanchip${view.ledgerSender === 'all' ? ' on' : ''}`, onClick: () => onLedgerSender('all'), children: [t('insights.spanAll'), " ", _jsx("b", { children: senderCount.user + senderCount.a2a_agent })] }), senderCount.user ? _jsxs("button", { type: "button", className: `spanchip${view.ledgerSender === 'user' ? ' on' : ''}`, onClick: () => onLedgerSender('user'), children: ["\uD83D\uDC64 ", t('insights.senderHuman'), " ", _jsx("b", { children: senderCount.user })] }) : null, senderCount.a2a_agent ? _jsxs("button", { type: "button", className: `spanchip${view.ledgerSender === 'a2a_agent' ? ' on' : ''}`, onClick: () => onLedgerSender('a2a_agent'), children: ["\uD83E\uDD1D ", t('insights.senderA2A'), " ", _jsx("b", { children: senderCount.a2a_agent })] }) : null, senderCount.system ? _jsxs("button", { type: "button", className: `spanchip spanchip-sys${view.ledgerSender === 'system' ? ' on' : ''}`, onClick: () => onLedgerSender('system'), children: ["\u2699\uFE0F ", t('insights.senderSystem'), " ", _jsx("b", { children: senderCount.system })] }) : null] }), canGroup ? (_jsxs("div", { className: "spanfilter ledgersort", children: [_jsx("button", { type: "button", className: `spanchip${view.ledgerSort === 'normal' ? ' on' : ''}`, onClick: () => onLedgerSort('normal'), children: t('insights.ledgerNormal') }), _jsx("button", { type: "button", className: `spanchip${view.ledgerSort === 'grouped' ? ' on' : ''}`, onClick: () => onLedgerSort('grouped'), children: t('insights.ledgerGrouped') })] })) : null] }));
|
|
435
|
+
const note = report.meta?.capped ? _jsx("p", { className: "turn-hidden mut", children: t('insights.turnsCapped', { shown: String(report.meta.spansReturned ?? spans.length), total: String(report.meta.spansTotal ?? spans.length) }) }) : null;
|
|
436
|
+
if (!turns.length)
|
|
437
|
+
return _jsxs("div", { className: "turnlist", children: [controls, _jsx("p", { className: "mut", children: t('insights.evNoFlags') })] });
|
|
438
|
+
if (view.ledgerSort === 'grouped' && canGroup) {
|
|
439
|
+
const sortedRecs = [...recs].sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity]);
|
|
440
|
+
const byIndex = new Map(turns.map(tn => [tn.turnIndex, tn]));
|
|
441
|
+
const assigned = new Map();
|
|
442
|
+
for (const r of sortedRecs)
|
|
443
|
+
for (const ti of r.evidence?.turnIndexes ?? [])
|
|
444
|
+
if (byIndex.has(ti) && !assigned.has(ti))
|
|
445
|
+
assigned.set(ti, r.id);
|
|
446
|
+
const blocks = [];
|
|
447
|
+
for (const r of sortedRecs) {
|
|
448
|
+
const ts = turns.filter(tn => assigned.get(tn.turnIndex) === r.id).sort((a, b) => a.turnIndex - b.turnIndex);
|
|
449
|
+
if (!ts.length)
|
|
450
|
+
continue;
|
|
451
|
+
blocks.push(_jsxs("div", { className: "turn-group", children: [_jsxs("div", { className: `turn-group-head sev-${r.severity}`, children: [_jsx("b", { children: idText('rec', { id: r.id, params: {} }) }), _jsx("span", { children: t('insights.ledgerGroupCount', { count: ts.length }) })] }), ts.map(card)] }, r.id));
|
|
452
|
+
}
|
|
453
|
+
const other = turns.filter(tn => !assigned.has(tn.turnIndex)).sort((a, b) => a.turnIndex - b.turnIndex);
|
|
454
|
+
if (other.length) {
|
|
455
|
+
blocks.push(_jsxs("div", { className: "turn-group", children: [_jsxs("div", { className: "turn-group-head sev-info", children: [_jsx("b", { children: t('insights.ledgerOther') }), _jsx("span", { children: t('insights.ledgerGroupCount', { count: other.length }) })] }), other.map(card)] }, "other"));
|
|
456
|
+
}
|
|
457
|
+
return _jsxs("div", { className: "turnlist", children: [_jsx("p", { className: "turn-sum mut", children: t('insights.turnSummary', { total: turns.length, flagged }) }), controls, blocks, note] });
|
|
458
|
+
}
|
|
459
|
+
return _jsxs("div", { className: "turnlist", children: [_jsx("p", { className: "turn-sum mut", children: t('insights.turnSummary', { total: turns.length, flagged }) }), controls, [...turns].sort((a, b) => a.turnIndex - b.turnIndex).map(card), note] });
|
|
460
|
+
}
|
|
461
|
+
function ConvoRecBadges({ turnIndex, recByTurn }) {
|
|
462
|
+
const ids = recByTurn.get(turnIndex);
|
|
463
|
+
if (!ids?.length)
|
|
464
|
+
return null;
|
|
465
|
+
return _jsx(_Fragment, { children: ids.map(id => _jsxs("span", { className: "cbub-rec", title: idText('rec', { id, params: {} }), children: ["\uD83D\uDCA1 ", idText('rec', { id, params: {} })] }, id)) });
|
|
466
|
+
}
|
|
467
|
+
function ConvoPrompt({ msg, recByTurn, onOpenPrompt }) {
|
|
468
|
+
const side = msg.role === 'user' ? 'right' : 'left';
|
|
469
|
+
const text = msg.text ? (cleanPromptText(msg.text) || msg.text) : '';
|
|
470
|
+
const sevCls = msg.severity && msg.severity !== 'info' ? ` sev-${msg.severity}` : '';
|
|
471
|
+
return (_jsxs("div", { className: `cbub cbub-${side} role-${msg.role}${sevCls}`, children: [_jsxs("div", { className: "cbub-head", children: [_jsx(PromptSourceChip, { src: msg.source }), _jsx(PromptMentions, { src: msg.source }), _jsxs("span", { className: "cbub-turn", children: ["#", String(msg.turnIndex)] }), _jsx(ConvoRecBadges, { turnIndex: msg.turnIndex, recByTurn: recByTurn })] }), _jsx("div", { className: "cbub-body", children: text ? _jsx(MarkdownBody, { text: text + (msg.truncated ? ' …' : '') }) : _jsx("p", { className: "mut", children: t('insights.replayNoText') }) }), msg.truncated ? _jsx("div", { className: "cbub-foot", children: _jsx("button", { type: "button", className: "tp-toggle", onClick: () => onOpenPrompt(msg.turnIndex), children: t('insights.turnPromptFull') }) }) : null] }));
|
|
472
|
+
}
|
|
473
|
+
function ConvoOpRow({ msg, open, onToggle }) {
|
|
474
|
+
const e = msg.event;
|
|
475
|
+
if (!e)
|
|
476
|
+
return null;
|
|
477
|
+
const bad = e.status === 'error' || (!!e.result && BAD_RESULTS.has(e.result.category));
|
|
478
|
+
const subj = e.intent?.subject ? _jsx("code", { className: "span-subj", children: e.intent.subject }) : null;
|
|
479
|
+
const what = intentTextOf(e.intent, String(e.label?.params?.tool ?? e.kind));
|
|
480
|
+
const ev = e.evidence;
|
|
481
|
+
const expandable = !!(ev?.command?.text || ev?.output?.text);
|
|
482
|
+
const tags = (msg.tags ?? []).filter(tg => tg !== 'normal' && tg !== 'diagnostic');
|
|
483
|
+
const toggle = () => expandable && onToggle(msg.id);
|
|
484
|
+
return (_jsxs("div", { className: `cop${bad ? ' bad' : ''}${open ? ' open' : ''}`, children: [_jsxs("div", { className: `cop-line${expandable ? ' clickable' : ''}`, role: expandable ? 'button' : undefined, tabIndex: expandable ? 0 : undefined, onClick: toggle, onKeyDown: event => {
|
|
485
|
+
if (!expandable)
|
|
486
|
+
return;
|
|
487
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
488
|
+
event.preventDefault();
|
|
489
|
+
toggle();
|
|
490
|
+
}
|
|
491
|
+
}, children: [_jsx("i", { className: `op ph-${phaseSlug(e.phase)}${bad ? ' bad' : ''}` }), _jsx("b", { className: "span-what", children: what }), subj, e.result && BAD_RESULTS.has(e.result.category) ? _jsxs("span", { className: "span-res rc-bad", children: [resultLabel(e.result.category), e.result.exitCode !== undefined ? ` · exit ${e.result.exitCode}` : ''] }) : null, _jsx("span", { className: "span-tags", children: tags.map(tg => _jsx("span", { className: `span-tag tg-${tg}`, children: tagLabel(tg) }, tg)) }), _jsx("span", { className: "span-dur", children: fmtMs(e.durationMs) }), expandable ? _jsx("span", { className: "span-detail-btn", "aria-hidden": "true", children: open ? t('insights.dCollapse') : t('insights.dDetail') }) : null] }), open && ev ? _jsxs("div", { className: "spandetail", children: [_jsx(TextPreview, { label: t('insights.dCommand'), preview: ev.command }), _jsx(TextPreview, { label: t('insights.dCmdOutput'), preview: ev.output })] }) : null] }));
|
|
492
|
+
}
|
|
493
|
+
function ConvoOps({ unit, convo, recByTurn, onToggleOp }) {
|
|
494
|
+
const sayMsgs = unit.msgs.filter(m => m.text && !m.event);
|
|
495
|
+
const opMsgs = unit.msgs.filter(m => m.event);
|
|
496
|
+
const worst = unit.msgs.some(m => m.severity === 'bad') ? ' sev-bad' : unit.msgs.some(m => m.severity === 'warn') ? ' sev-warn' : '';
|
|
497
|
+
return (_jsxs("div", { className: `cbub cbub-left role-agent cbub-ops${worst}`, children: [_jsxs("div", { className: "cbub-head", children: [_jsxs("span", { className: "tp-label tp-src tp-src-system", children: ["\uD83E\uDD16 ", t('insights.replayAgent')] }), _jsxs("span", { className: "cbub-turn", children: ["#", String(unit.turnIndex)] }), opMsgs.length ? _jsx("span", { className: "cbub-opcount", children: t('insights.replayOps', { count: opMsgs.length }) }) : null, _jsx(ConvoRecBadges, { turnIndex: unit.turnIndex, recByTurn: recByTurn })] }), sayMsgs.length ? _jsx("div", { className: "cbub-saywrap", children: sayMsgs.map(m => _jsx("div", { className: "cbub-say", children: _jsx(MarkdownBody, { text: m.text }) }, m.id)) }) : null, opMsgs.length ? _jsx("div", { className: "cbub-ops-list", children: opMsgs.map(m => _jsx(ConvoOpRow, { msg: m, open: convo.openOps.has(m.id), onToggle: onToggleOp }, m.id)) }) : null] }));
|
|
498
|
+
}
|
|
499
|
+
function Conversation({ convo, recByTurn, onQuery, onRole, onTag, onLoadMore, onToggleOp, onOpenPrompt }) {
|
|
500
|
+
const [draft, setDraft] = useState(convo.q);
|
|
501
|
+
useEffect(() => setDraft(convo.q), [convo.q]);
|
|
502
|
+
const units = groupConvo(convo.messages);
|
|
503
|
+
return (_jsxs("div", { className: "convo", children: [_jsxs("div", { className: "convo-controls", children: [_jsx("input", { type: "search", className: "convo-search", placeholder: t('insights.replaySearch'), value: draft, onChange: event => setDraft(event.currentTarget.value), onKeyDown: event => {
|
|
504
|
+
if (event.key !== 'Enter')
|
|
505
|
+
return;
|
|
506
|
+
event.preventDefault();
|
|
507
|
+
const next = draft.trim();
|
|
508
|
+
if (next !== convo.q)
|
|
509
|
+
onQuery(next);
|
|
510
|
+
} }), _jsxs("div", { className: "convo-filters", children: [_jsxs("div", { className: "spanfilter convo-rolefilter", children: [_jsx("span", { className: "convo-flabel", children: t('insights.replayBy') }), CONVO_ROLES.map(r => _jsx("button", { type: "button", className: `spanchip${convo.role === r.key ? ' on' : ''}`, onClick: () => onRole(r.key), children: t(`insights.${r.label}`) }, r.key))] }), _jsxs("div", { className: "spanfilter convo-tagfilter", children: [_jsx("span", { className: "convo-flabel", children: t('insights.replayState') }), CONVO_TAGS.map(tg => _jsx("button", { type: "button", className: `spanchip${convo.tag === tg.key ? ' on' : ''}`, onClick: () => onTag(tg.key), children: tg.label.startsWith('tag.') ? tagLabel(tg.label.slice(4)) : t(`insights.${tg.label}`) }, tg.key))] })] })] }), _jsx("div", { className: "convothread", children: !convo.messages.length ? (convo.loading ? _jsx(LoadingState, { label: t('insights.detailLoading'), compact: true }) : _jsx("p", { className: "mut", children: t('insights.replayEmpty') })) : (_jsxs(_Fragment, { children: [units.map((u, i) => u.kind === 'prompt'
|
|
511
|
+
? _jsx(ConvoPrompt, { msg: u.msg, recByTurn: recByTurn, onOpenPrompt: onOpenPrompt }, u.msg.id)
|
|
512
|
+
: _jsx(ConvoOps, { unit: u, convo: convo, recByTurn: recByTurn, onToggleOp: onToggleOp }, `${u.turnIndex}:${i}`)), convo.hasMore ? (_jsx("div", { className: "convo-more", children: _jsx("button", { type: "button", className: "primary convo-loadmore", disabled: convo.loading, onClick: onLoadMore, children: convo.loading ? t('insights.detailLoading') : t('insights.replayLoadMore', { shown: convo.messages.length, total: convo.total }) }) })) : _jsx("p", { className: "convo-more mut", children: t('insights.replayAllLoaded', { total: convo.total }) })] })) })] }));
|
|
513
|
+
}
|
|
514
|
+
function Recommendations({ report, activeId, onActive }) {
|
|
515
|
+
const recs = report.recommendations ?? [];
|
|
516
|
+
if (!recs.length)
|
|
517
|
+
return _jsx("p", { className: "mut", children: t('insights.noRecommendations') });
|
|
518
|
+
return (_jsx("div", { className: "reclist", children: [...recs].sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity]).map(r => {
|
|
519
|
+
const active = r.id === activeId;
|
|
520
|
+
const targeted = (r.evidence?.spanIndexes?.length ?? 0) > 0 || (r.evidence?.turnIndexes?.length ?? 0) > 0;
|
|
521
|
+
const impact = idText('impact', r.impact);
|
|
522
|
+
const why = idText('why', r.why);
|
|
523
|
+
return (_jsxs("button", { type: "button", className: `rec ${r.severity}${targeted ? ' rec-clickable' : ''}${active ? ' active' : ''}`, onClick: () => onActive(active ? null : r.id), children: [_jsxs("div", { className: "rectop", children: [_jsx("b", { children: idText('rec', { id: r.id, params: {} }) }), _jsx("span", { children: severityLabel(r.severity) })] }), impact ? _jsx("div", { className: "rec-impact", children: impact }) : null, r.nextActions.length ? _jsx("ul", { className: "rec-actions", children: r.nextActions.map(a => _jsx("li", { children: idText('action', a) }, `${a.id}:${JSON.stringify(a.params)}`)) }) : null, why ? _jsx("div", { className: "rec-why", children: why }) : null, targeted ? _jsx("span", { className: "rec-cta", children: active ? t('insights.diagActive') : t('insights.diagShow') }) : null] }, r.id));
|
|
524
|
+
}) }));
|
|
525
|
+
}
|
|
526
|
+
function Subagents({ report }) {
|
|
527
|
+
const lanes = report.subagents ?? [];
|
|
528
|
+
if (!lanes.length)
|
|
529
|
+
return null;
|
|
530
|
+
const totMs = lanes.reduce((s, l) => s + l.durationMs, 0);
|
|
531
|
+
return (_jsxs("section", { className: "block subagent-block", children: [_jsxs("h3", { children: [t('insights.subagents'), " ", _jsxs("span", { className: "mut", children: ["\u00B7 ", lanes.length, " \u00B7 ", fmtMs(totMs)] })] }), _jsx("p", { className: "mut ins-hint", children: t('insights.subagentsHint') }), _jsx("div", { className: "sublanes", children: lanes.map((l, i) => (_jsxs("div", { className: `sublane${l.failures ? ' bad' : ''}`, children: [_jsxs("div", { className: "sublane-head", children: [_jsx("span", { className: "sublane-type", children: l.agentType }), _jsxs("span", { className: "sublane-task", children: [l.task?.text ?? '', l.task?.truncated ? '…' : ''] })] }), _jsx(PhaseMixBar, { phase: l.phase }), _jsxs("div", { className: "sublane-stats", children: [_jsxs("span", { children: [fmtInt(l.spans), " ", t('insights.spansShort')] }), _jsx("span", { children: fmtMs(l.durationMs) }), l.failures ? _jsxs("span", { className: "bad", children: [fmtInt(l.failures), " ", t('insights.failedShort')] }) : null] })] }, `${l.agentType}:${i}`))) })] }));
|
|
532
|
+
}
|
|
533
|
+
function TurnRail({ report, focus, recByTurn, onJumpTurn, onTip, onTipHide }) {
|
|
534
|
+
const turns = [...(report.turnTimeline ?? [])].sort((a, b) => a.turnIndex - b.turnIndex);
|
|
535
|
+
if (turns.length < 2)
|
|
536
|
+
return null;
|
|
537
|
+
const focused = focus.turnIdx.size > 0;
|
|
538
|
+
const legend = ['research', 'edit', 'run', 'delegate', 'discuss'];
|
|
539
|
+
return (_jsxs("section", { className: "block turnrail-block", children: [_jsxs("div", { className: "turnrail-head", children: [_jsx("h3", { children: t('insights.turnRail') }), _jsxs("span", { className: "turnrail-legend", children: [legend.map(p => _jsxs("span", { className: "rl-item", children: [_jsx("i", { className: phaseClass(p) }), phaseLabel(p)] }, p)), _jsxs("span", { className: "rl-item rl-sep", children: [_jsx("i", { className: "rbr-sub" }), t('insights.railSubagent')] })] })] }), _jsx("div", { className: "turnrail", onMouseLeave: onTipHide, children: turns.map(tn => {
|
|
540
|
+
const m = tn.metrics;
|
|
541
|
+
const phase = turnMainPhase(tn);
|
|
542
|
+
const fail = (tn.tags ?? []).includes('failure') || (tn.events ?? []).some(e => e.status === 'error');
|
|
543
|
+
const slow = (tn.tags ?? []).includes('slow');
|
|
544
|
+
const recHit = recByTurn.has(tn.turnIndex);
|
|
545
|
+
const hot = focus.turnIdx.has(tn.turnIndex);
|
|
546
|
+
const cls = ['railnode', phaseClass(phase), hot ? 'hot' : '', (focused && !hot) ? 'dim' : ''].filter(Boolean).join(' ');
|
|
547
|
+
const tip = `#${tn.turnIndex} · ${phaseLabel(phase)} · ${t('insights.mReads')}${m.reads} ${t('insights.mEdits')}${m.edits} ${t('insights.mRuns')}${m.runs}${m.failures ? ` · ${t('insights.mFailures')}${m.failures}` : ''} · ${fmtMs(m.durationMs)}`;
|
|
548
|
+
const subs = (tn.events ?? []).filter(e => e.kind === 'delegate').length;
|
|
549
|
+
return (_jsxs("span", { style: { display: 'contents' }, children: [_jsxs("button", { type: "button", className: cls, onMouseMove: event => onTip(event, tip), onBlur: onTipHide, onClick: () => onJumpTurn(tn), children: [_jsx("span", { children: String(tn.turnIndex) }), fail ? _jsx("i", { className: "rb rb-fail" }) : null, slow ? _jsx("i", { className: "rb rb-slow" }) : null, recHit ? _jsx("i", { className: "rb rb-rec" }) : null] }), subs ? _jsx("span", { className: "railbranch", onMouseMove: event => onTip(event, `#${tn.turnIndex} · ${t('insights.railSubagents', { n: subs })}`), children: Array.from({ length: Math.min(subs, 4) }).map((_, i) => _jsx("i", { className: "rbr-sub" }, i)) }) : null] }, tn.turnIndex));
|
|
550
|
+
}) })] }));
|
|
551
|
+
}
|
|
552
|
+
function WorkGantt({ report, onOpenSpan, onTip, onTipHide }) {
|
|
553
|
+
const timed = (report.spans ?? []).map((s, i) => ({ s, i, start: s.relStartMs ?? 0, dur: Math.max(s.durationMs ?? 0, 0) }))
|
|
554
|
+
.filter(x => Number.isFinite(x.start)).sort((a, b) => a.start - b.start);
|
|
555
|
+
if (timed.length < 2)
|
|
556
|
+
return null;
|
|
557
|
+
const active = Math.max(timed.reduce((sum, x) => sum + x.dur, 0), 1);
|
|
558
|
+
let cursor = 0;
|
|
559
|
+
const realSpan = (timed[timed.length - 1].start + timed[timed.length - 1].dur) - timed[0].start;
|
|
560
|
+
return (_jsxs("section", { className: "block gantt-block", children: [_jsxs("div", { className: "turnrail-head", children: [_jsx("h3", { children: t('insights.gantt') }), _jsx("span", { className: "gantt-cap", children: t('insights.ganttCaption', { span: timed.length, dur: fmtMs(realSpan), active: fmtMs(active) }) })] }), _jsx("div", { className: "gantt", children: _jsx("div", { className: "gtrack", onMouseLeave: onTipHide, children: timed.map(x => {
|
|
561
|
+
const left = cursor / active * 100;
|
|
562
|
+
const width = Math.max(x.dur / active * 100, 0.3);
|
|
563
|
+
cursor += x.dur;
|
|
564
|
+
const fail = x.s.status === 'error' || (x.s.tags ?? []).includes('failure');
|
|
565
|
+
const slow = (x.s.tags ?? []).includes('slow');
|
|
566
|
+
const cls = ['gbar', phaseClass(x.s.phase), fail ? 'gbar-fail' : '', slow ? 'gbar-slow' : ''].filter(Boolean).join(' ');
|
|
567
|
+
const st = x.s.status === 'error' ? ` · ${tagLabel('failure')}` : '';
|
|
568
|
+
const tip = `#${x.i} · ${x.s.tool} · ${phaseLabel(x.s.phase)} · ${fmtMs(x.s.durationMs)}${st}`;
|
|
569
|
+
return _jsx("button", { type: "button", className: cls, style: { left: `${left.toFixed(3)}%`, width: `${width.toFixed(3)}%` }, onMouseMove: event => onTip(event, tip), onClick: () => onOpenSpan(x.i) }, x.i);
|
|
570
|
+
}) }) })] }));
|
|
571
|
+
}
|
|
572
|
+
function ContextCurve({ report, onTip, onTipHide }) {
|
|
573
|
+
const pts = (report.turnTimeline ?? [])
|
|
574
|
+
.map(tn => (tn.context && Number.isFinite(tn.context.contextTokens)) ? { turn: tn.turnIndex, v: tn.context.contextTokens } : null)
|
|
575
|
+
.filter((p) => p !== null)
|
|
576
|
+
.sort((a, b) => a.turn - b.turn);
|
|
577
|
+
if (pts.length < 2)
|
|
578
|
+
return null;
|
|
579
|
+
const max = Math.max(...pts.map(p => p.v), 1);
|
|
580
|
+
const w = 100;
|
|
581
|
+
const h = 40;
|
|
582
|
+
const xs = (i) => (i / (pts.length - 1)) * w;
|
|
583
|
+
const ys = (v) => h - 1 - (v / max) * (h - 2);
|
|
584
|
+
const line = pts.map((p, i) => `${xs(i).toFixed(2)},${ys(p.v).toFixed(2)}`).join(' ');
|
|
585
|
+
const area = `0,${h} ${line} ${w},${h}`;
|
|
586
|
+
const band = w / pts.length;
|
|
587
|
+
const mid = fmtInt(Math.round(max / 2));
|
|
588
|
+
return (_jsxs("section", { className: "block ctxcurve-block", children: [_jsxs("div", { className: "turnrail-head", children: [_jsx("h3", { children: t('insights.ctxCurve') }), _jsx("span", { className: "gantt-cap", children: t('insights.ctxCaption', { peak: fmtInt(max), turns: pts.length }) })] }), _jsxs("div", { className: "ctxchart", children: [_jsxs("div", { className: "ctxyaxis", children: [_jsx("span", { children: fmtInt(max) }), _jsx("span", { children: mid }), _jsx("span", { children: "0 tok" })] }), _jsxs("div", { className: "ctxplot", children: [_jsxs("svg", { className: "ctxcurve", viewBox: `0 0 ${w} ${h}`, preserveAspectRatio: "none", role: "img", "aria-label": t('insights.ctxCurve'), onMouseLeave: onTipHide, children: [_jsx("polygon", { className: "ctxarea", points: area }), _jsx("polyline", { className: "ctxline", points: line }), pts.map((p, i) => (_jsx("rect", { className: "ctxhit", x: Math.max(0, xs(i) - band / 2), y: 0, width: band, height: h, onMouseMove: event => onTip(event, `${t('insights.ctxTurn', { n: p.turn })} · ${fmtInt(p.v)} tok`) }, p.turn)))] }), _jsxs("div", { className: "ctxxaxis", children: [_jsx("span", { children: t('insights.ctxTurn', { n: pts[0].turn }) }), _jsx("span", { children: t('insights.ctxTurn', { n: pts[pts.length - 1].turn }) })] })] })] })] }));
|
|
589
|
+
}
|
|
590
|
+
function DetailBody({ report, view, onViewChange, onOpenPrompt, onJumpTurn, onOpenSpan, scrollTarget, onScrolled, onTip, onTipHide, onLoadConvo }) {
|
|
591
|
+
if (report.status !== 'ok')
|
|
592
|
+
return _jsx("p", { className: "mut", children: safeStatus(report) });
|
|
593
|
+
const a = report.agg;
|
|
594
|
+
const focus = focusSets(report, view.activeId);
|
|
595
|
+
const recByTurn = new Map();
|
|
596
|
+
for (const r of report.recommendations ?? [])
|
|
597
|
+
for (const ti of r.evidence?.turnIndexes ?? []) {
|
|
598
|
+
const arr = recByTurn.get(ti) ?? [];
|
|
599
|
+
if (!arr.includes(r.id))
|
|
600
|
+
arr.push(r.id);
|
|
601
|
+
recByTurn.set(ti, arr);
|
|
602
|
+
}
|
|
603
|
+
const meta = [
|
|
604
|
+
report.meta?.asOf ? t('sessions.insightAsOf', { asOf: String(report.meta.asOf) }) : '',
|
|
605
|
+
report.meta?.partial ? t('sessions.insightPartial') : '',
|
|
606
|
+
report.meta?.capped ? t('sessions.insightCapped', { shown: String(report.meta.spansReturned ?? report.spans?.length ?? 0), total: String(report.meta.spansTotal ?? report.spans?.length ?? 0) }) : '',
|
|
607
|
+
].filter(Boolean).join(' · ');
|
|
608
|
+
const spanCount = report.spans?.length ?? 0;
|
|
609
|
+
const turnTotal = report.turnTimeline?.length ?? 0;
|
|
610
|
+
const setTab = (tab) => {
|
|
611
|
+
onViewChange({ tab });
|
|
612
|
+
if (tab === 'convo' && !view.convo.messages.length && !view.convo.loading)
|
|
613
|
+
onLoadConvo(true);
|
|
614
|
+
};
|
|
615
|
+
const toggleSpan = (idx) => onViewChange({ openSpans: toggleSet(view.openSpans, idx) });
|
|
616
|
+
const toggleTurn = (idx) => onViewChange({ openTurns: toggleSet(view.openTurns, idx) });
|
|
617
|
+
const togglePrompt = (idx) => onViewChange({ openPrompts: toggleSet(view.openPrompts, idx) });
|
|
618
|
+
const togglePromptRaw = (idx) => onViewChange({ rawPrompts: toggleSet(view.rawPrompts, idx) });
|
|
619
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "cards insight-detail-metrics", children: [_jsx(MetricCard, { label: t('insights.metricSpans'), value: fmtInt(a.totalSpans) }), _jsx(MetricCard, { label: t('insights.metricFailed'), value: fmtInt(a.failedSpans) }), _jsx(MetricCard, { label: t('insights.metricSlow'), value: fmtInt(a.slowSpans) }), _jsx(MetricCard, { label: t('insights.metricRw'), value: a.readWriteRatio === null ? '-' : a.readWriteRatio.toFixed(1) })] }), meta ? _jsx("p", { className: "insight-meta", children: meta }) : null, _jsxs("section", { className: "block recblock", children: [_jsx("h3", { children: t('insights.recommendations') }), _jsx(Recommendations, { report: report, activeId: view.activeId, onActive: activeId => onViewChange({ activeId }) })] }), _jsx(TurnRail, { report: report, focus: focus, recByTurn: recByTurn, onJumpTurn: onJumpTurn, onTip: onTip, onTipHide: onTipHide }), _jsx(WorkGantt, { report: report, onOpenSpan: onOpenSpan, onTip: onTip, onTipHide: onTipHide }), _jsx(Subagents, { report: report }), _jsx(ContextCurve, { report: report, onTip: onTip, onTipHide: onTipHide }), _jsxs("div", { className: "detailtabs", children: [_jsxs("div", { className: "detailtabbar", role: "tablist", "aria-label": t('insights.detailTabs'), children: [_jsxs("button", { type: "button", role: "tab", className: view.tab === 'spans' ? 'on' : '', onClick: () => setTab('spans'), children: [t('insights.trace'), " ", _jsx("b", { children: spanCount })] }), _jsxs("button", { type: "button", role: "tab", className: view.tab === 'ledger' ? 'on' : '', onClick: () => setTab('ledger'), children: [t('insights.ledger'), " ", _jsx("b", { children: turnTotal })] }), _jsx("button", { type: "button", role: "tab", className: view.tab === 'convo' ? 'on' : '', onClick: () => setTab('convo'), children: t('insights.replay') })] }), _jsxs("div", { className: "detailtabbody", children: [_jsx("div", { className: "insight-tab-panel", "data-panel": "spans", hidden: view.tab !== 'spans', children: _jsx(Evidence, { report: report, focus: focus, view: view, onSpanFilter: spanFilter => onViewChange({ spanFilter }), onToggleSpan: toggleSpan, scrollTarget: scrollTarget, onScrolled: onScrolled }) }), _jsx("div", { className: "insight-tab-panel", "data-panel": "ledger", hidden: view.tab !== 'ledger', children: _jsx(TurnEfficiency, { report: report, focus: focus, view: view, onLedgerSort: ledgerSort => onViewChange({ ledgerSort }), onLedgerSender: ledgerSender => onViewChange({ ledgerSender }), onToggleTurn: toggleTurn, onToggleSpan: toggleSpan, onTogglePrompt: togglePrompt, onTogglePromptRaw: togglePromptRaw, onOpenPrompt: onOpenPrompt, scrollTarget: scrollTarget, onScrolled: onScrolled }) }), _jsx("div", { className: "insight-tab-panel", "data-panel": "convo", hidden: view.tab !== 'convo', children: _jsx(Conversation, { convo: view.convo, recByTurn: recByTurn, onQuery: q => onLoadConvo(true, { q }), onRole: role => { if (role !== view.convo.role)
|
|
620
|
+
onLoadConvo(true, { role }); }, onTag: tag => { if (tag !== view.convo.tag)
|
|
621
|
+
onLoadConvo(true, { tag }); }, onLoadMore: () => onLoadConvo(false), onToggleOp: id => onViewChange({ convo: { ...view.convo, openOps: toggleSet(view.convo.openOps, id) } }), onOpenPrompt: onOpenPrompt }) })] })] })] }));
|
|
622
|
+
}
|
|
623
|
+
function PromptModal({ modal, onClose, onToggleRaw }) {
|
|
624
|
+
if (modal.turnIndex === null)
|
|
625
|
+
return null;
|
|
626
|
+
const prompt = modal.prompt;
|
|
627
|
+
const src = prompt?.source;
|
|
628
|
+
const cleaned = prompt?.text ? (cleanPromptText(prompt.text) || prompt.text) : '';
|
|
629
|
+
const trunc = prompt?.truncated;
|
|
630
|
+
const body = prompt
|
|
631
|
+
? modal.raw
|
|
632
|
+
? _jsxs("pre", { className: "tp-raw modal-raw", children: [cleaned, trunc ? '\n…' : ''] })
|
|
633
|
+
: _jsx(MarkdownBody, { className: "md-body modal-md", text: cleaned + (trunc ? ' …' : '') })
|
|
634
|
+
: _jsx(LoadingState, { label: t('insights.detailLoading'), compact: true });
|
|
635
|
+
return (_jsxs("div", { id: "insight-modal", className: "insight-modal", children: [_jsx("div", { className: "modal-backdrop", onClick: onClose }), _jsx("div", { className: "modal-panel", role: "dialog", "aria-modal": "true", "aria-label": t('insights.turnPrompt'), children: prompt ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "modal-head", children: [_jsxs("div", { className: "modal-who", children: [_jsx(PromptSourceChip, { src: src }), _jsx(PromptMentions, { src: src }), _jsxs("span", { className: "modal-turnno", children: ["#", modal.turnIndex] })] }), _jsxs("div", { className: "modal-acts", children: [_jsx("button", { type: "button", className: "tp-toggle", onClick: onToggleRaw, children: modal.raw ? t('insights.turnPromptRendered') : t('insights.turnPromptRaw') }), _jsx("button", { type: "button", className: "modal-close", onClick: onClose, "aria-label": t('insights.modalClose'), children: "\u00D7" })] })] }), _jsxs("div", { className: "modal-body", children: [body, trunc ? _jsx("p", { className: "modal-trunc mut", children: t('insights.promptTruncated') }) : null] })] })) : _jsx("div", { className: "modal-body", children: body }) })] }));
|
|
636
|
+
}
|
|
637
|
+
function CommandPalette({ palette, items, onClose, onInput, onChoose }) {
|
|
638
|
+
if (!palette.open)
|
|
639
|
+
return null;
|
|
640
|
+
const anchored = !!palette.anchor;
|
|
641
|
+
const panelStyle = anchored
|
|
642
|
+
? {
|
|
643
|
+
top: palette.anchor.top,
|
|
644
|
+
left: palette.anchor.left,
|
|
645
|
+
width: palette.anchor.width,
|
|
646
|
+
'--palette-panel-max-height': `${palette.anchor.maxHeight}px`,
|
|
647
|
+
'--palette-list-max-height': `${Math.max(80, palette.anchor.maxHeight - 54)}px`,
|
|
648
|
+
}
|
|
649
|
+
: undefined;
|
|
650
|
+
const node = (_jsxs("div", { id: "insight-palette", className: `insights-page insight-palette ${anchored ? 'palette-anchored' : 'palette-centered'}`, children: [_jsx("div", { className: "modal-backdrop", onClick: onClose }), _jsxs("div", { className: `palette-panel${anchored ? ' anchored' : ''}`, role: "dialog", "aria-modal": "false", style: panelStyle, children: [_jsx("input", { type: "search", className: "palette-input", placeholder: t('insights.palettePlaceholder'), value: palette.q, autoFocus: true, onChange: event => onInput(event.currentTarget.value) }), _jsx("div", { className: "palette-list", children: items.length ? items.map((it, i) => (_jsxs("button", { type: "button", className: `palette-item${i === palette.idx ? ' on' : ''}`, onClick: () => onChoose(it.type, it.key), children: [_jsx("span", { className: "pal-label", children: it.label }), _jsx("span", { className: "pal-sub", children: it.sub })] }, `${it.type}:${it.key}`))) : _jsx("p", { className: "mut palette-empty", children: t('insights.paletteEmpty') }) })] })] }));
|
|
651
|
+
return createPortal(node, document.body);
|
|
652
|
+
}
|
|
653
|
+
function readInitialState() {
|
|
654
|
+
const hp = parseInsightsHash();
|
|
655
|
+
return {
|
|
656
|
+
tab: initialInsightTab(),
|
|
657
|
+
filter: INSIGHT_FILTERS.includes(hp.filter) ? hp.filter : 'all',
|
|
658
|
+
cli: new Set((hp.cli ?? '').split(',').filter(Boolean)),
|
|
659
|
+
q: hp.q ?? '',
|
|
660
|
+
sess: hp.sess ?? null,
|
|
661
|
+
project: hp.project ?? '',
|
|
662
|
+
time: hp.time ?? 'all',
|
|
663
|
+
showNoise: hp.noise === '1',
|
|
664
|
+
sort: SESS_SORT_KEYS.includes(hp.sort) ? hp.sort : 'recent',
|
|
665
|
+
layout: hp.layout === 'table' ? 'table' : 'card',
|
|
666
|
+
};
|
|
8
667
|
}
|
|
9
|
-
// Render-once scaffold: React owns the translated page shell and unmount
|
|
10
|
-
// lifecycle; wireInsightsPage owns the tab/detail/modal controller until the
|
|
11
|
-
// insight workbench is split into smaller reactive components.
|
|
12
668
|
function InsightsPage() {
|
|
13
669
|
const tr = useT();
|
|
14
|
-
const
|
|
15
|
-
const
|
|
670
|
+
const initial = useRef(readInitialState()).current;
|
|
671
|
+
const selectedIdRef = useRef(null);
|
|
672
|
+
const initialSessRef = useRef(initial.sess);
|
|
673
|
+
const detailReqRef = useRef(0);
|
|
674
|
+
const convoReqRef = useRef(0);
|
|
675
|
+
const modalReqRef = useRef(0);
|
|
676
|
+
const [namesVersion, setNamesVersion] = useState(0);
|
|
677
|
+
const [overviewData, setOverviewData] = useState(null);
|
|
678
|
+
const [records, setRecords] = useState([]);
|
|
679
|
+
const [statusText, setStatusText] = useState('');
|
|
680
|
+
const [refreshing, setRefreshing] = useState(false);
|
|
681
|
+
const [tab, setTab] = useState(initial.tab);
|
|
682
|
+
const [filter, setFilter] = useState(initial.filter);
|
|
683
|
+
const [cliFilter, setCliFilter] = useState(
|
|
684
|
+
// Single-select model: a legacy multi-CLI deep-link (?cli=a,b) collapses to its first CLI
|
|
685
|
+
// so the dropdown label always matches the active filter (was: showed "all" while filtering ≥2).
|
|
686
|
+
initial.cli.size > 1 ? new Set([[...initial.cli][0]]) : initial.cli);
|
|
687
|
+
const [q, setQ] = useState(initial.q);
|
|
688
|
+
const [project, setProject] = useState(initial.project);
|
|
689
|
+
const [timeWin, setTimeWin] = useState(initial.time);
|
|
690
|
+
const [showNoise, setShowNoise] = useState(initial.showNoise);
|
|
691
|
+
const [sessSort, setSessSort] = useState(initial.sort);
|
|
692
|
+
const [sessLayout, setSessLayout] = useState(initial.layout);
|
|
693
|
+
const [selectedId, setSelectedId] = useState(null);
|
|
694
|
+
const [detailReport, setDetailReport] = useState(null);
|
|
695
|
+
const [detailLoading, setDetailLoading] = useState(false);
|
|
696
|
+
const [detailError, setDetailError] = useState('');
|
|
697
|
+
const [detailView, setDetailView] = useState({
|
|
698
|
+
activeId: null,
|
|
699
|
+
tab: 'spans',
|
|
700
|
+
spanFilter: 'all',
|
|
701
|
+
openSpans: new Set(),
|
|
702
|
+
openTurns: new Set(),
|
|
703
|
+
ledgerSort: 'normal',
|
|
704
|
+
openPrompts: new Set(),
|
|
705
|
+
rawPrompts: new Set(),
|
|
706
|
+
ledgerSender: 'all',
|
|
707
|
+
convo: newConvoState(),
|
|
708
|
+
});
|
|
709
|
+
const [openHot, setOpenHot] = useState(new Set());
|
|
710
|
+
const [palette, setPalette] = useState({ open: false, q: '', idx: 0 });
|
|
711
|
+
const [modal, setModal] = useState({ turnIndex: null, raw: false, prompt: null, loading: false });
|
|
712
|
+
const [scrollTarget, setScrollTarget] = useState(null);
|
|
713
|
+
const [tip, setTip] = useState({ text: '', x: 0, y: 0, visible: false });
|
|
714
|
+
const scope = useMemo(() => buildInsightScope(timeWin, project, showNoise), [project, showNoise, timeWin]);
|
|
715
|
+
const cliBase = useMemo(() => filterRecords(records, filter, q, new Set(), scope), [filter, q, records, scope, namesVersion]);
|
|
716
|
+
const presentCliKey = useMemo(() => [...new Set(cliBase.map(cliIdOf))].sort().join(','), [cliBase]);
|
|
717
|
+
const projectBase = useMemo(() => filterRecords(records, filter, q, cliFilter, { ...scope, project: undefined }), [cliFilter, filter, q, records, scope, namesVersion]);
|
|
718
|
+
const projectOpts = useMemo(() => projectOptions(projectBase), [projectBase]);
|
|
719
|
+
const projectDropdownOptions = useMemo(() => [
|
|
720
|
+
{ value: '', label: tr('insights.projectAll') },
|
|
721
|
+
...projectOpts.map(o => ({ value: o.id, label: `${o.id} (${o.count})` })),
|
|
722
|
+
], [projectOpts, tr]);
|
|
723
|
+
const timeDropdownOptions = useMemo(() => TIME_WINDOWS.map(w => ({ value: w.key, label: tr(w.label) })), [tr]);
|
|
724
|
+
const reviewDropdownOptions = useMemo(() => INSIGHT_FILTERS.map(f => ({
|
|
725
|
+
value: f,
|
|
726
|
+
label: f === 'all'
|
|
727
|
+
? tr('insights.filterAllSessions')
|
|
728
|
+
: f === 'review'
|
|
729
|
+
? tr('insights.filterReview')
|
|
730
|
+
: f === 'failed'
|
|
731
|
+
? tr('insights.filterFailed')
|
|
732
|
+
: tr('insights.filterSlow'),
|
|
733
|
+
})), [tr]);
|
|
734
|
+
const cliDropdownOptions = useMemo(() => [
|
|
735
|
+
{ value: CLI_FILTER_ALL, label: `${tr('insights.filterAllCli')} (${cliBase.length})` },
|
|
736
|
+
...cliCounts(cliBase).map(c => ({ value: c.id, label: `${c.id} (${c.count})` })),
|
|
737
|
+
], [cliBase, tr]);
|
|
738
|
+
const projectLabel = projectDropdownOptions.find(option => option.value === project)?.label ?? tr('insights.projectAll');
|
|
739
|
+
const timeLabel = timeDropdownOptions.find(option => option.value === timeWin)?.label ?? tr('insights.timeAll');
|
|
740
|
+
const reviewLabel = reviewDropdownOptions.find(option => option.value === filter)?.label ?? tr('insights.filterAllSessions');
|
|
741
|
+
const cliFilterValue = cliFilter.size === 1 ? [...cliFilter][0] : CLI_FILTER_ALL;
|
|
742
|
+
const cliFilterLabel = cliDropdownOptions.find(option => option.value === cliFilterValue)?.label ?? tr('insights.filterAllCli');
|
|
743
|
+
const rows = useMemo(() => sortRecordsBy(filterRecords(records, filter, q, cliFilter, scope), sessSort), [cliFilter, filter, q, records, scope, sessSort, namesVersion]);
|
|
744
|
+
const overview = useMemo(() => overviewData ? aggregateRecords(rows) : null, [overviewData, rows]);
|
|
745
|
+
const selectedRecord = useMemo(() => selectedId ? rows.find(r => r.session.sessionId === selectedId) ?? records.find(r => r.session.sessionId === selectedId) : undefined, [records, rows, selectedId]);
|
|
746
|
+
const paletteItems = useMemo(() => {
|
|
747
|
+
const ql = palette.q.trim().toLowerCase();
|
|
748
|
+
const tabs = INSIGHT_TABS
|
|
749
|
+
.map(tb => ({ type: 'tab', key: tb.key, label: t(tb.label), sub: t('insights.paletteTabs') }))
|
|
750
|
+
.filter(it => !ql || it.label.toLowerCase().includes(ql));
|
|
751
|
+
const sess = records
|
|
752
|
+
.filter(r => {
|
|
753
|
+
const s = r.session;
|
|
754
|
+
return !ql || `${sessionTitle(s)} ${botDisplayName(s)} ${s.cliId ?? ''}`.toLowerCase().includes(ql);
|
|
755
|
+
})
|
|
756
|
+
.slice(0, 20)
|
|
757
|
+
.map(r => ({ type: 'session', key: String(r.session.sessionId), label: sessionTitle(r.session), sub: `${botDisplayName(r.session)} · ${r.session.cliId ?? '-'}` }));
|
|
758
|
+
return [...tabs, ...sess];
|
|
759
|
+
}, [palette.q, records, namesVersion]);
|
|
760
|
+
const showTip = useCallback((event, text) => {
|
|
761
|
+
const pad = 14;
|
|
762
|
+
setTip({ text, x: event.clientX + pad, y: event.clientY + pad, visible: true });
|
|
763
|
+
}, []);
|
|
764
|
+
const hideTip = useCallback(() => setTip(v => ({ ...v, visible: false })), []);
|
|
765
|
+
const patchDetailView = useCallback((patch) => {
|
|
766
|
+
setDetailView(prev => ({ ...prev, ...patch }));
|
|
767
|
+
}, []);
|
|
768
|
+
const closeModal = useCallback(() => {
|
|
769
|
+
setModal({ turnIndex: null, raw: false, prompt: null, loading: false });
|
|
770
|
+
}, []);
|
|
771
|
+
const resetDetailState = useCallback(() => {
|
|
772
|
+
setDetailReport(null);
|
|
773
|
+
setDetailError('');
|
|
774
|
+
setDetailLoading(false);
|
|
775
|
+
setDetailView({
|
|
776
|
+
activeId: null,
|
|
777
|
+
tab: 'spans',
|
|
778
|
+
spanFilter: 'all',
|
|
779
|
+
openSpans: new Set(),
|
|
780
|
+
openTurns: new Set(),
|
|
781
|
+
ledgerSort: 'normal',
|
|
782
|
+
openPrompts: new Set(),
|
|
783
|
+
rawPrompts: new Set(),
|
|
784
|
+
ledgerSender: 'all',
|
|
785
|
+
convo: newConvoState(),
|
|
786
|
+
});
|
|
787
|
+
setScrollTarget(null);
|
|
788
|
+
closeModal();
|
|
789
|
+
}, [closeModal]);
|
|
790
|
+
const selectSession = useCallback((sessionId) => {
|
|
791
|
+
selectedIdRef.current = sessionId;
|
|
792
|
+
setSelectedId(sessionId);
|
|
793
|
+
resetDetailState();
|
|
794
|
+
setDetailLoading(true);
|
|
795
|
+
const req = ++detailReqRef.current;
|
|
796
|
+
void (async () => {
|
|
797
|
+
try {
|
|
798
|
+
const report = await fetchInsightDetail(sessionId);
|
|
799
|
+
if (req !== detailReqRef.current || selectedIdRef.current !== sessionId)
|
|
800
|
+
return;
|
|
801
|
+
setDetailReport(report);
|
|
802
|
+
}
|
|
803
|
+
catch (e) {
|
|
804
|
+
if (req !== detailReqRef.current || selectedIdRef.current !== sessionId)
|
|
805
|
+
return;
|
|
806
|
+
setDetailError(String(e));
|
|
807
|
+
}
|
|
808
|
+
finally {
|
|
809
|
+
if (req === detailReqRef.current && selectedIdRef.current === sessionId)
|
|
810
|
+
setDetailLoading(false);
|
|
811
|
+
}
|
|
812
|
+
})();
|
|
813
|
+
}, [resetDetailState]);
|
|
814
|
+
const loadConvo = useCallback((reset, overrides = {}) => {
|
|
815
|
+
const sid = selectedIdRef.current;
|
|
816
|
+
if (!sid || detailView.convo.loading)
|
|
817
|
+
return;
|
|
818
|
+
const nextQ = overrides.q ?? detailView.convo.q;
|
|
819
|
+
const nextRole = overrides.role ?? detailView.convo.role;
|
|
820
|
+
const nextTag = overrides.tag ?? detailView.convo.tag;
|
|
821
|
+
const offset = reset ? 0 : detailView.convo.nextOffset;
|
|
822
|
+
setDetailView(prev => ({
|
|
823
|
+
...prev,
|
|
824
|
+
convo: {
|
|
825
|
+
...prev.convo,
|
|
826
|
+
...overrides,
|
|
827
|
+
messages: reset ? [] : prev.convo.messages,
|
|
828
|
+
nextOffset: reset ? 0 : prev.convo.nextOffset,
|
|
829
|
+
hasMore: reset ? false : prev.convo.hasMore,
|
|
830
|
+
loading: true,
|
|
831
|
+
},
|
|
832
|
+
}));
|
|
833
|
+
const req = ++convoReqRef.current;
|
|
834
|
+
void (async () => {
|
|
835
|
+
try {
|
|
836
|
+
const c = await fetchInsightConversation(sid, { offset, limit: 40, q: nextQ, role: nextRole, tag: nextTag });
|
|
837
|
+
if (req !== convoReqRef.current || selectedIdRef.current !== sid)
|
|
838
|
+
return;
|
|
839
|
+
setDetailView(prev => ({
|
|
840
|
+
...prev,
|
|
841
|
+
convo: {
|
|
842
|
+
...prev.convo,
|
|
843
|
+
messages: reset ? c.messages : [...prev.convo.messages, ...c.messages],
|
|
844
|
+
total: c.total,
|
|
845
|
+
hasMore: c.hasMore,
|
|
846
|
+
nextOffset: c.nextOffset,
|
|
847
|
+
loading: false,
|
|
848
|
+
},
|
|
849
|
+
}));
|
|
850
|
+
}
|
|
851
|
+
catch {
|
|
852
|
+
if (req !== convoReqRef.current || selectedIdRef.current !== sid)
|
|
853
|
+
return;
|
|
854
|
+
setDetailView(prev => ({ ...prev, convo: { ...prev.convo, loading: false } }));
|
|
855
|
+
}
|
|
856
|
+
})();
|
|
857
|
+
}, [detailView.convo]);
|
|
858
|
+
const refresh = useCallback(async () => {
|
|
859
|
+
setRefreshing(true);
|
|
860
|
+
setStatusText(tr('insights.loading'));
|
|
861
|
+
try {
|
|
862
|
+
const overviewNext = await fetchInsightsSummary();
|
|
863
|
+
setOverviewData(overviewNext);
|
|
864
|
+
setRecords(overviewNext.sessions.map(toRecord));
|
|
865
|
+
setStatusText(tr('insights.loaded', { count: overviewNext.meta.analyzedSessions }));
|
|
866
|
+
}
|
|
867
|
+
catch (e) {
|
|
868
|
+
setOverviewData(null);
|
|
869
|
+
setRecords([]);
|
|
870
|
+
setStatusText(`${tr('insights.unavailable')}: ${String(e)}`);
|
|
871
|
+
}
|
|
872
|
+
finally {
|
|
873
|
+
setRefreshing(false);
|
|
874
|
+
}
|
|
875
|
+
}, [tr]);
|
|
876
|
+
const openPromptModal = useCallback((turnIndex) => {
|
|
877
|
+
const sid = selectedIdRef.current;
|
|
878
|
+
if (!sid)
|
|
879
|
+
return;
|
|
880
|
+
const req = ++modalReqRef.current;
|
|
881
|
+
setModal({ turnIndex, raw: false, prompt: null, loading: true });
|
|
882
|
+
void (async () => {
|
|
883
|
+
try {
|
|
884
|
+
const prompt = await fetchTurnPrompt(sid, turnIndex);
|
|
885
|
+
if (req !== modalReqRef.current || selectedIdRef.current !== sid)
|
|
886
|
+
return;
|
|
887
|
+
setModal({ turnIndex, raw: false, prompt, loading: false });
|
|
888
|
+
}
|
|
889
|
+
catch {
|
|
890
|
+
if (req !== modalReqRef.current || selectedIdRef.current !== sid)
|
|
891
|
+
return;
|
|
892
|
+
setModal({ turnIndex, raw: false, prompt: { text: tr('insights.unavailable'), truncated: false }, loading: false });
|
|
893
|
+
}
|
|
894
|
+
})();
|
|
895
|
+
}, [tr]);
|
|
896
|
+
const jumpToSession = useCallback((sessionId) => {
|
|
897
|
+
setTab('sessions');
|
|
898
|
+
selectSession(sessionId);
|
|
899
|
+
}, [selectSession]);
|
|
900
|
+
const openSpanFromVisual = useCallback((idx) => {
|
|
901
|
+
patchDetailView({ tab: 'spans', spanFilter: 'all', openSpans: addToSet(detailView.openSpans, idx) });
|
|
902
|
+
setScrollTarget({ kind: 'span', index: idx });
|
|
903
|
+
}, [detailView.openSpans, patchDetailView]);
|
|
904
|
+
const jumpTurnFromRail = useCallback((turn) => {
|
|
905
|
+
patchDetailView({
|
|
906
|
+
tab: 'ledger',
|
|
907
|
+
ledgerSort: 'normal',
|
|
908
|
+
ledgerSender: turnSenderKind(turn) === 'system' ? 'system' : detailView.ledgerSender,
|
|
909
|
+
openTurns: addToSet(detailView.openTurns, turn.turnIndex),
|
|
910
|
+
});
|
|
911
|
+
setScrollTarget({ kind: 'turn', index: turn.turnIndex });
|
|
912
|
+
}, [detailView.ledgerSender, detailView.openTurns, patchDetailView]);
|
|
913
|
+
const choosePalette = useCallback((type, key) => {
|
|
914
|
+
setPalette({ open: false, q: '', idx: 0 });
|
|
915
|
+
if (type === 'tab') {
|
|
916
|
+
setTab(key);
|
|
917
|
+
selectedIdRef.current = null;
|
|
918
|
+
setSelectedId(null);
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
jumpToSession(key);
|
|
922
|
+
}, [jumpToSession]);
|
|
16
923
|
useEffect(() => {
|
|
17
|
-
|
|
18
|
-
return undefined;
|
|
19
|
-
return wireInsightsPage(rootRef.current);
|
|
924
|
+
void loadNameMaps().then(() => setNamesVersion(v => v + 1));
|
|
20
925
|
}, []);
|
|
21
|
-
|
|
926
|
+
useEffect(() => {
|
|
927
|
+
void refresh();
|
|
928
|
+
}, [refresh]);
|
|
929
|
+
useEffect(() => {
|
|
930
|
+
selectedIdRef.current = selectedId;
|
|
931
|
+
}, [selectedId]);
|
|
932
|
+
useEffect(() => {
|
|
933
|
+
const present = new Set(presentCliKey.split(',').filter(Boolean));
|
|
934
|
+
setCliFilter(prev => {
|
|
935
|
+
let changed = false;
|
|
936
|
+
const next = new Set();
|
|
937
|
+
for (const id of prev) {
|
|
938
|
+
if (present.has(id))
|
|
939
|
+
next.add(id);
|
|
940
|
+
else
|
|
941
|
+
changed = true;
|
|
942
|
+
}
|
|
943
|
+
return changed ? next : prev;
|
|
944
|
+
});
|
|
945
|
+
}, [presentCliKey]);
|
|
946
|
+
useEffect(() => {
|
|
947
|
+
if (project && !projectOpts.some(o => o.id === project))
|
|
948
|
+
setProject('');
|
|
949
|
+
}, [project, projectOpts]);
|
|
950
|
+
useEffect(() => {
|
|
951
|
+
if (selectedId && records.length && !records.some(r => r.session.sessionId === selectedId)) {
|
|
952
|
+
selectedIdRef.current = null;
|
|
953
|
+
setSelectedId(null);
|
|
954
|
+
resetDetailState();
|
|
955
|
+
}
|
|
956
|
+
}, [records, resetDetailState, selectedId]);
|
|
957
|
+
useEffect(() => {
|
|
958
|
+
const want = initialSessRef.current;
|
|
959
|
+
if (!want || selectedId || !records.some(r => r.session.sessionId === want))
|
|
960
|
+
return;
|
|
961
|
+
initialSessRef.current = null;
|
|
962
|
+
selectSession(want);
|
|
963
|
+
}, [records, selectSession, selectedId]);
|
|
964
|
+
useEffect(() => {
|
|
965
|
+
if (detailView.tab === 'convo' && !detailView.convo.messages.length && !detailView.convo.loading)
|
|
966
|
+
loadConvo(true);
|
|
967
|
+
}, [detailView.convo.loading, detailView.convo.messages.length, detailView.tab, loadConvo]);
|
|
968
|
+
useEffect(() => {
|
|
969
|
+
const p = {};
|
|
970
|
+
if (tab !== 'overview')
|
|
971
|
+
p.tab = tab;
|
|
972
|
+
if (filter !== 'all')
|
|
973
|
+
p.filter = filter;
|
|
974
|
+
if (q.trim())
|
|
975
|
+
p.q = q.trim();
|
|
976
|
+
if (project)
|
|
977
|
+
p.project = project;
|
|
978
|
+
if (timeWin !== 'all')
|
|
979
|
+
p.time = timeWin;
|
|
980
|
+
if (cliFilter.size)
|
|
981
|
+
p.cli = [...cliFilter].join(',');
|
|
982
|
+
if (sessSort !== 'recent')
|
|
983
|
+
p.sort = sessSort;
|
|
984
|
+
if (sessLayout !== 'card')
|
|
985
|
+
p.layout = sessLayout;
|
|
986
|
+
if (showNoise)
|
|
987
|
+
p.noise = '1';
|
|
988
|
+
if (selectedId)
|
|
989
|
+
p.sess = selectedId;
|
|
990
|
+
try {
|
|
991
|
+
history.replaceState(null, '', buildInsightsHash(p));
|
|
992
|
+
}
|
|
993
|
+
catch { /* ignore */ }
|
|
994
|
+
}, [cliFilter, filter, project, q, selectedId, sessLayout, sessSort, showNoise, tab, timeWin]);
|
|
995
|
+
useEffect(() => {
|
|
996
|
+
const active = palette.open || modal.turnIndex !== null;
|
|
997
|
+
document.body.classList.toggle('insight-modal-open', active);
|
|
998
|
+
return () => document.body.classList.remove('insight-modal-open');
|
|
999
|
+
}, [modal.turnIndex, palette.open]);
|
|
1000
|
+
useEffect(() => {
|
|
1001
|
+
const onKey = (event) => {
|
|
1002
|
+
if ((event.key === 'k' || event.key === 'K') && (event.metaKey || event.ctrlKey)) {
|
|
1003
|
+
event.preventDefault();
|
|
1004
|
+
setPalette(p => p.open ? { open: false, q: '', idx: 0 } : { open: true, q: '', idx: 0, anchor: undefined });
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
if (palette.open) {
|
|
1008
|
+
if (event.isComposing || event.keyCode === 229)
|
|
1009
|
+
return;
|
|
1010
|
+
if (event.key === 'Escape') {
|
|
1011
|
+
event.preventDefault();
|
|
1012
|
+
setPalette({ open: false, q: '', idx: 0 });
|
|
1013
|
+
}
|
|
1014
|
+
else if (event.key === 'ArrowDown') {
|
|
1015
|
+
event.preventDefault();
|
|
1016
|
+
setPalette(p => ({ ...p, idx: Math.min(Math.max(0, paletteItems.length - 1), p.idx + 1) }));
|
|
1017
|
+
}
|
|
1018
|
+
else if (event.key === 'ArrowUp') {
|
|
1019
|
+
event.preventDefault();
|
|
1020
|
+
setPalette(p => ({ ...p, idx: Math.max(0, p.idx - 1) }));
|
|
1021
|
+
}
|
|
1022
|
+
else if (event.key === 'Enter') {
|
|
1023
|
+
event.preventDefault();
|
|
1024
|
+
const it = paletteItems[palette.idx];
|
|
1025
|
+
if (it)
|
|
1026
|
+
choosePalette(it.type, it.key);
|
|
1027
|
+
}
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
if (event.key === 'Escape' && modal.turnIndex !== null)
|
|
1031
|
+
closeModal();
|
|
1032
|
+
};
|
|
1033
|
+
document.addEventListener('keydown', onKey);
|
|
1034
|
+
return () => document.removeEventListener('keydown', onKey);
|
|
1035
|
+
}, [choosePalette, closeModal, modal.turnIndex, palette.idx, palette.open, paletteItems]);
|
|
1036
|
+
const clearFilters = () => {
|
|
1037
|
+
setQ('');
|
|
1038
|
+
setFilter('all');
|
|
1039
|
+
setCliFilter(new Set());
|
|
1040
|
+
setProject('');
|
|
1041
|
+
setTimeWin('all');
|
|
1042
|
+
setShowNoise(false);
|
|
1043
|
+
};
|
|
1044
|
+
const emptyHint = !rows.length && !showNoise ? t('insights.emptyAnalyzableHint') : undefined;
|
|
1045
|
+
return (_jsxs("section", { className: "page insights-page", children: [_jsxs("div", { className: "page-heading", children: [_jsxs("div", { children: [_jsx("p", { className: "eyebrow", children: tr('nav.insights') }), _jsxs("div", { className: "insight-title-line", children: [_jsx("h1", { children: tr('insights.title') }), _jsx(InsightTabs, { active: tab, onChange: next => setTab(next) }), _jsx("div", { id: "insight-status", className: "insight-page-status", children: statusText })] })] }), _jsx("div", { className: "page-heading-actions insight-heading-actions", children: _jsx("button", { type: "button", id: "insight-palette-open", className: "insight-palette-trigger", onClick: event => {
|
|
1046
|
+
setPalette({
|
|
1047
|
+
open: true,
|
|
1048
|
+
q: '',
|
|
1049
|
+
idx: 0,
|
|
1050
|
+
anchor: paletteAnchorFromButton(event.currentTarget),
|
|
1051
|
+
});
|
|
1052
|
+
}, children: tr('insights.paletteOpen') }) })] }), _jsxs("form", { id: "insight-filters", className: "filters dashboard-toolbar insights-filters", onSubmit: event => event.preventDefault(), children: [_jsx("input", { type: "search", name: "q", placeholder: tr('insights.search'), value: q, onChange: event => setQ(event.currentTarget.value) }), _jsx(DropdownMenu, { id: "insight-project", className: "insight-filter-menu", ariaLabel: tr('insights.projectAll'), label: projectLabel, value: project, options: projectDropdownOptions, onChange: setProject }), _jsx(DropdownMenu, { id: "insight-time", className: "insight-filter-menu", ariaLabel: tr('insights.timeAll'), label: timeLabel, value: timeWin, options: timeDropdownOptions, onChange: setTimeWin }), _jsx(DropdownMenu, { id: "insight-review-filter", className: "insight-filter-menu insight-review-menu", ariaLabel: tr('insights.filter'), label: reviewLabel, value: filter, options: reviewDropdownOptions, onChange: setFilter }), _jsx(DropdownMenu, { id: "insight-cli-filter", className: "insight-filter-menu insight-cli-menu", ariaLabel: tr('insights.filter'), label: cliFilterLabel, value: cliFilterValue, options: cliDropdownOptions, onChange: value => setCliFilter(value === CLI_FILTER_ALL ? new Set() : new Set([value])) }), _jsxs("label", { className: "filter-toggle ins-toggle", children: [_jsx("input", { type: "checkbox", id: "insight-noise", checked: showNoise, onChange: event => setShowNoise(event.currentTarget.checked) }), _jsx("span", { className: "filter-toggle-label", children: tr('insights.showAll') }), _jsx("span", { className: "filter-toggle-switch", "aria-hidden": "true" })] }), _jsx("button", { type: "button", id: "insight-clear", className: "ins-clear", onClick: clearFilters, children: tr('insights.clear') }), _jsx("div", { className: "insight-head-acts", children: _jsx(RefreshIconButton, { id: "insight-refresh", label: tr('insights.refresh'), busy: refreshing, disabled: refreshing, onClick: () => void refresh() }) })] }), _jsx("div", { className: "insight-panel", role: "tabpanel", "data-tabpanel": "overview", hidden: tab !== 'overview', children: _jsx("div", { id: "insight-overview", children: overview ? _jsx(OverviewContent, { data: overview }) : null }) }), _jsx("div", { className: "insight-panel", role: "tabpanel", "data-tabpanel": "sessions", hidden: tab !== 'sessions', children: !selectedId ? (_jsxs("div", { id: "insight-list-view", children: [_jsxs("div", { className: "insight-list-head", children: [_jsx("span", { id: "insight-list-subtitle", children: tr('insights.listCount', { shown: rows.length, total: records.length }) }), _jsx("div", { className: "sesssort", id: "insight-sort", children: _jsx(SortBar, { sort: sessSort, layout: sessLayout, onSort: setSessSort, onLayout: setSessLayout }) })] }), _jsx("div", { id: "insight-list", children: sessLayout === 'table'
|
|
1053
|
+
? _jsx(SessionTable, { records: rows, selectedId: selectedId, onSelect: selectSession, emptyHint: emptyHint })
|
|
1054
|
+
: _jsx(SessionRows, { records: rows, selectedId: selectedId, wide: true, onSelect: selectSession, emptyHint: emptyHint }) })] })) : (_jsxs("div", { id: "insight-detail-view", children: [_jsxs("button", { type: "button", id: "insight-back", className: "ins-back", onClick: () => { selectedIdRef.current = null; setSelectedId(null); resetDetailState(); }, children: ["\u2190 ", tr('insights.backToList')] }), _jsx("section", { id: "insight-detail", className: "insight-detail", children: selectedRecord ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "shead", children: [_jsx("h2", { children: sessionTitle(selectedRecord.session) }), _jsxs("div", { className: "smeta", children: [botDisplayName(selectedRecord.session), " \u00B7 ", String(selectedRecord.session.cliId ?? '-'), " \u00B7 ", _jsx("code", { children: String(selectedRecord.session.sessionId ?? '') })] })] }), _jsx("div", { id: "insight-detail-body", children: detailLoading ? _jsx(LoadingState, { label: tr('insights.detailLoading'), compact: true }) : detailError ? _jsx("p", { className: "mut", children: detailError }) : detailReport ? (_jsx(DetailBody, { report: detailReport, view: detailView, onViewChange: patchDetailView, onOpenPrompt: openPromptModal, onJumpTurn: jumpTurnFromRail, onOpenSpan: openSpanFromVisual, scrollTarget: scrollTarget, onScrolled: () => setScrollTarget(null), onTip: showTip, onTipHide: hideTip, onLoadConvo: loadConvo })) : _jsx("p", { className: "mut", children: tr('insights.selectSession') }) })] })) : _jsx("p", { className: "mut", children: tr('insights.selectSession') }) })] })) }), _jsx("div", { className: "insight-panel", role: "tabpanel", "data-tabpanel": "flow", hidden: tab !== 'flow', children: _jsx("div", { id: "insight-flow", children: overviewData ? _jsx(Flow, { records: rows, onSession: jumpToSession }) : null }) }), _jsx("div", { className: "insight-panel", role: "tabpanel", "data-tabpanel": "dist", hidden: tab !== 'dist', children: _jsx("div", { id: "insight-dist", children: overviewData ? _jsx(Distribution, { records: rows, onSort: sort => { setSessSort(sort); setTab('sessions'); selectedIdRef.current = null; setSelectedId(null); } }) : null }) }), _jsx("div", { className: "insight-panel", role: "tabpanel", "data-tabpanel": "hot", hidden: tab !== 'hot', children: _jsx("div", { id: "insight-hot", children: overviewData ? (_jsx(Hotspots, { records: rows, openHot: openHot, onToggleHot: key => setOpenHot(prev => toggleSet(prev, key)), onProject: p => { setProject(p); setTab('sessions'); selectedIdRef.current = null; setSelectedId(null); }, onSession: jumpToSession })) : null }) }), _jsx(PromptModal, { modal: modal, onClose: closeModal, onToggleRaw: () => setModal(m => ({ ...m, raw: !m.raw })) }), _jsx(CommandPalette, { palette: palette, items: paletteItems, onClose: () => setPalette({ open: false, q: '', idx: 0 }), onInput: next => setPalette(prev => ({ ...prev, open: true, q: next, idx: 0 })), onChoose: choosePalette }), _jsx(Tooltip, { tip: tip })] }));
|
|
22
1055
|
}
|
|
23
1056
|
export function renderInsightsPage(root) {
|
|
24
1057
|
return mountReactPage(root, _jsx(InsightsPage, {}));
|