openclaw-multi-auto 1.3.6 → 1.3.8
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/dist/{audio-preflight-5FEeDooz.js → audio-preflight-DDBLZBdb.js} +4 -4
- package/dist/{audio-transcription-runner-B-UvoDjZ.js → audio-transcription-runner-DZbSWT9E.js} +1 -1
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/{chrome-D45SyhQL.js → chrome-CMU2WVFh.js} +8 -8
- package/dist/{deliver-B9cys0EZ.js → deliver-BXVcFIHL.js} +1 -1
- package/dist/{deliver-runtime-DhaQJ0pI.js → deliver-runtime-DTaIS-1i.js} +3 -3
- package/dist/{deps-send-whatsapp.runtime-DvTL2tzN.js → deps-send-whatsapp.runtime-CIZqFAqb.js} +7 -7
- package/dist/extensionAPI.js +6 -6
- package/dist/{image-DAOPwVXi.js → image-BCVLo0qw.js} +1 -1
- package/dist/{image-runtime-wlCLVvVv.js → image-runtime-DtCKpMPZ.js} +3 -3
- package/dist/{pi-embedded-DYU79yGe.js → pi-embedded-CgQ_W6Xs.js} +24 -24
- package/dist/{pi-embedded-helpers-uTRAmQ4n.js → pi-embedded-helpers-CwuBTKza.js} +3 -3
- package/dist/plugin-sdk/{accounts-DyFCXtHv.js → accounts-BslAlVYS.js} +2 -2
- package/dist/plugin-sdk/{accounts-BJAXxY46.js → accounts-C3m65--E.js} +2 -2
- package/dist/plugin-sdk/{accounts-C1j7HSL0.js → accounts-CNCCkdEF.js} +3 -3
- package/dist/plugin-sdk/{active-listener-CftX5jLD.js → active-listener-CkPnMUkB.js} +2 -2
- package/dist/plugin-sdk/{api-key-rotation-8nyyt1kx.js → api-key-rotation-BXnNsojA.js} +2 -2
- package/dist/plugin-sdk/{audio-preflight-C_aSAPR1.js → audio-preflight-CtO4fFvp.js} +26 -26
- package/dist/plugin-sdk/{audio-transcription-runner-CB53F7_7.js → audio-transcription-runner-DnxvOS1-.js} +11 -11
- package/dist/plugin-sdk/{audit-membership-runtime-BXndI4LG.js → audit-membership-runtime-BpfoSk8M.js} +2 -2
- package/dist/plugin-sdk/{channel-activity-C5y8AgAV.js → channel-activity-WJYxcJ3S.js} +3 -3
- package/dist/plugin-sdk/{channel-web-DBTRO03V.js → channel-web-dO5k3ubM.js} +18 -18
- package/dist/plugin-sdk/{chrome-f00sZkDX.js → chrome-CjNTuJML.js} +6 -6
- package/dist/plugin-sdk/{commands-registry-BJ_NxG2F.js → commands-registry-CdYjoI0i.js} +4 -4
- package/dist/plugin-sdk/{common-Cf27Jwxu.js → common-oYc5vPFl.js} +2 -2
- package/dist/plugin-sdk/{config-CHQrpx-Q.js → config-B1z-UxQ3.js} +7 -7
- package/dist/plugin-sdk/{deliver-DNEuetST.js → deliver-D5_6T567.js} +10 -10
- package/dist/plugin-sdk/deliver-runtime-C5dgvvga.js +32 -0
- package/dist/plugin-sdk/deps-send-discord.runtime-Dg4N7PHJ.js +23 -0
- package/dist/plugin-sdk/deps-send-imessage.runtime-0OEwzMQm.js +22 -0
- package/dist/plugin-sdk/deps-send-signal.runtime-BM1jRt3G.js +21 -0
- package/dist/plugin-sdk/deps-send-slack.runtime-1E3BYRdF.js +19 -0
- package/dist/plugin-sdk/deps-send-telegram.runtime-DNCxIflA.js +24 -0
- package/dist/plugin-sdk/deps-send-whatsapp.runtime-OLwr-9c8.js +57 -0
- package/dist/plugin-sdk/{diagnostic-LYUUmjJ5.js → diagnostic-Bxxu0ig-.js} +2 -2
- package/dist/plugin-sdk/{errors-CtMWwS2Z.js → errors-B3cHyZZA.js} +1 -1
- package/dist/plugin-sdk/{fetch-guard-CxYB5Kg6.js → fetch-guard-Dcgod0tg.js} +2 -2
- package/dist/plugin-sdk/{fs-safe-DtfhxbrI.js → fs-safe-BaKqI3G4.js} +3 -3
- package/dist/plugin-sdk/{image-BwjYjRHx.js → image-B2mQW9Rb.js} +6 -6
- package/dist/plugin-sdk/{image-ops-BnZKcbd6.js → image-ops-Cbzr4U9l.js} +2 -2
- package/dist/plugin-sdk/image-runtime-BFm45j49.js +25 -0
- package/dist/plugin-sdk/{ir-Z4hX67TJ.js → ir-ZEmrTr4J.js} +7 -7
- package/dist/plugin-sdk/{local-roots-KhjQw04O.js → local-roots-CIPRxA-4.js} +4 -4
- package/dist/plugin-sdk/{logger-DHIIvMxj.js → logger-CvPFVOgT.js} +2 -2
- package/dist/plugin-sdk/{login-C31642Ld.js → login-CCTew9bt.js} +4 -4
- package/dist/plugin-sdk/{login-qr--y2SG_Ue.js → login-qr-BI3Vi_wJ.js} +5 -5
- package/dist/plugin-sdk/{manager-2UZBMCc7.js → manager-BEoYPn7R.js} +8 -8
- package/dist/plugin-sdk/manager-runtime-DxclHQ4U.js +15 -0
- package/dist/plugin-sdk/{outbound-Ba0QUI5h.js → outbound-ByOw1K6W.js} +5 -5
- package/dist/plugin-sdk/{outbound-attachment-B1Laso-8.js → outbound-attachment-BzVhxRRw.js} +2 -2
- package/dist/plugin-sdk/{path-alias-guards-C7Vm5DZ1.js → path-alias-guards-sWayacde.js} +1 -1
- package/dist/plugin-sdk/{paths-DopV9PQG.js → paths-Dpg3qxcl.js} +1 -1
- package/dist/plugin-sdk/{pi-embedded-helpers-DnA_OCzP.js → pi-embedded-helpers-DIxXkGJf.js} +16 -16
- package/dist/plugin-sdk/{pi-model-discovery-DdPqXk8f.js → pi-model-discovery-DM_2uFtj.js} +1 -1
- package/dist/plugin-sdk/pi-model-discovery-runtime-BuzvkvNR.js +8 -0
- package/dist/plugin-sdk/{pi-tools.before-tool-call.runtime-DxFHiLUE.js → pi-tools.before-tool-call.runtime-w1dqL_ty.js} +4 -4
- package/dist/plugin-sdk/{plugins-CbCt4osF.js → plugins-C4USiH29.js} +4 -4
- package/dist/plugin-sdk/{proxy-env-C63mMdas.js → proxy-env-ET-rp8eg.js} +1 -1
- package/dist/plugin-sdk/{proxy-fetch-Ch95c_Y2.js → proxy-fetch-uDXGKG3Z.js} +1 -1
- package/dist/plugin-sdk/{pw-ai-DpJk62D4.js → pw-ai-CyOt3RDA.js} +9 -9
- package/dist/plugin-sdk/{qmd-manager-Ca-iSfEE.js → qmd-manager-BySdoVR7.js} +7 -7
- package/dist/plugin-sdk/{query-expansion-B_Xe41Ab.js → query-expansion-C6uS-7lj.js} +4 -4
- package/dist/plugin-sdk/{redact-hp9TOulW.js → redact-Bvxt1T_Q.js} +1 -1
- package/dist/plugin-sdk/{reply-CovBlFea.js → reply-CTCSeQqW.js} +73 -73
- package/dist/plugin-sdk/{resolve-outbound-target-BbrHgyUk.js → resolve-outbound-target-Bw8YNANu.js} +2 -2
- package/dist/plugin-sdk/{run-with-concurrency-BR1DXa8T.js → run-with-concurrency-C_KCHwvf.js} +1 -1
- package/dist/plugin-sdk/runtime-whatsapp-login.runtime-BxgRDkhc.js +10 -0
- package/dist/plugin-sdk/runtime-whatsapp-outbound.runtime-elOqrkfg.js +19 -0
- package/dist/plugin-sdk/{send-BvAtLLPl.js → send-BZ6nYFZr.js} +5 -5
- package/dist/plugin-sdk/{send-BTztm3D2.js → send-C0w6xP2x.js} +6 -6
- package/dist/plugin-sdk/{send-CWJUuG0i.js → send-CFf-1V89.js} +8 -8
- package/dist/plugin-sdk/{send-EcglC4cG.js → send-CY-Qfwia.js} +7 -7
- package/dist/plugin-sdk/{send-BXpXBwM_.js → send-qPyNGSe4.js} +13 -13
- package/dist/plugin-sdk/{session-k256LJZT.js → session-COrvpvUQ.js} +3 -3
- package/dist/plugin-sdk/signal.js +2 -2
- package/dist/plugin-sdk/{skill-commands-DoRqLzxm.js → skill-commands-DZqhtmiv.js} +4 -4
- package/dist/plugin-sdk/{skills-QudILG6e.js → skills-Cw_vXEJb.js} +6 -6
- package/dist/plugin-sdk/slash-commands.runtime-D67JLweo.js +13 -0
- package/dist/plugin-sdk/slash-dispatch.runtime-DvcpvCJ0.js +52 -0
- package/dist/plugin-sdk/slash-skill-commands.runtime-BM1x3azR.js +16 -0
- package/dist/plugin-sdk/{store-BbDQw3g6.js → store-CMHj6IIw.js} +2 -2
- package/dist/plugin-sdk/subagent-registry-runtime-1lbDyRzz.js +52 -0
- package/dist/plugin-sdk/{tables-BhvloMKN.js → tables-CSqrHsKL.js} +1 -1
- package/dist/plugin-sdk/{thinking-URzkT-3p.js → thinking-DOnsR_A8.js} +7 -7
- package/dist/plugin-sdk/{tokens-B1PW5Ayy.js → tokens-BDr0Z9o3.js} +1 -1
- package/dist/plugin-sdk/{tool-images-xpqbP6RR.js → tool-images-eEfOVkzf.js} +2 -2
- package/dist/plugin-sdk/web-BLyT64pW.js +56 -0
- package/dist/plugin-sdk/{whatsapp-actions-RcZ6vp61.js → whatsapp-actions-xcleMoMv.js} +17 -17
- package/dist/plugin-sdk/whatsapp.js +50 -50
- package/dist/{pw-ai-GcYO6HPE.js → pw-ai-CmphSzHx.js} +1 -1
- package/dist/{slash-dispatch.runtime-Dh053pQK.js → slash-dispatch.runtime-131yup2e.js} +6 -6
- package/dist/{subagent-registry-runtime-DSi5mnCQ.js → subagent-registry-runtime-DbSf_Je6.js} +6 -6
- package/dist/{web-1hWJDzNA.js → web-MR9d7KyB.js} +6 -6
- package/package.json +5 -2
- package/scripts/create-instance.sh +44 -19
- package/scripts/install-maca.sh +39 -28
- package/scripts/npm_publish.sh +8 -6
- package/ui/index.html +16 -0
- package/ui/node_modules/.bin/jiti +21 -0
- package/ui/node_modules/.bin/lessc +21 -0
- package/ui/node_modules/.bin/marked +21 -0
- package/ui/node_modules/.bin/playwright +21 -0
- package/ui/node_modules/.bin/sass +21 -0
- package/ui/node_modules/.bin/tsx +21 -0
- package/ui/node_modules/.bin/vite +21 -0
- package/ui/node_modules/.bin/vitest +21 -0
- package/ui/node_modules/.bin/yaml +21 -0
- package/ui/package.json +27 -0
- package/ui/public/apple-touch-icon.png +0 -0
- package/ui/public/favicon-32.png +0 -0
- package/ui/public/favicon.ico +0 -0
- package/ui/public/favicon.svg +22 -0
- package/ui/src/css.d.ts +1 -0
- package/ui/src/i18n/index.ts +3 -0
- package/ui/src/i18n/lib/lit-controller.ts +22 -0
- package/ui/src/i18n/lib/registry.ts +64 -0
- package/ui/src/i18n/lib/translate.ts +123 -0
- package/ui/src/i18n/lib/types.ts +9 -0
- package/ui/src/i18n/locales/de.ts +129 -0
- package/ui/src/i18n/locales/en.ts +337 -0
- package/ui/src/i18n/locales/pt-BR.ts +128 -0
- package/ui/src/i18n/locales/zh-CN.ts +330 -0
- package/ui/src/i18n/locales/zh-TW.ts +125 -0
- package/ui/src/i18n/test/translate.test.ts +56 -0
- package/ui/src/main.ts +2 -0
- package/ui/src/styles/base.css +385 -0
- package/ui/src/styles/chat/grouped.css +300 -0
- package/ui/src/styles/chat/layout.css +481 -0
- package/ui/src/styles/chat/sidebar.css +117 -0
- package/ui/src/styles/chat/text.css +146 -0
- package/ui/src/styles/chat/tool-cards.css +202 -0
- package/ui/src/styles/chat.css +5 -0
- package/ui/src/styles/components.css +2612 -0
- package/ui/src/styles/config.css +1658 -0
- package/ui/src/styles/layout.css +621 -0
- package/ui/src/styles/layout.mobile.css +374 -0
- package/ui/src/styles.css +5 -0
- package/ui/src/ui/__screenshots__/config-form.browser.test.ts/config-form-renderer-flags-unsupported-unions-1.png +0 -0
- package/ui/src/ui/__screenshots__/config-form.browser.test.ts/config-form-renderer-renders-inputs-and-patches-values-1.png +0 -0
- package/ui/src/ui/__screenshots__/config-form.browser.test.ts/config-form-renderer-renders-union-literals-as-select-options-1.png +0 -0
- package/ui/src/ui/__screenshots__/navigation.browser.test.ts/control-UI-routing-auto-scrolls-chat-history-to-the-latest-message-1.png +0 -0
- package/ui/src/ui/app-channels.ts +279 -0
- package/ui/src/ui/app-chat.ts +266 -0
- package/ui/src/ui/app-defaults.ts +50 -0
- package/ui/src/ui/app-events.ts +5 -0
- package/ui/src/ui/app-gateway.node.test.ts +229 -0
- package/ui/src/ui/app-gateway.ts +349 -0
- package/ui/src/ui/app-lifecycle.node.test.ts +44 -0
- package/ui/src/ui/app-lifecycle.ts +109 -0
- package/ui/src/ui/app-polling.ts +69 -0
- package/ui/src/ui/app-render-usage-tab.ts +273 -0
- package/ui/src/ui/app-render.helpers.node.test.ts +286 -0
- package/ui/src/ui/app-render.helpers.ts +574 -0
- package/ui/src/ui/app-render.ts +1168 -0
- package/ui/src/ui/app-scroll.test.ts +275 -0
- package/ui/src/ui/app-scroll.ts +179 -0
- package/ui/src/ui/app-settings.test.ts +70 -0
- package/ui/src/ui/app-settings.ts +440 -0
- package/ui/src/ui/app-tool-stream.node.test.ts +139 -0
- package/ui/src/ui/app-tool-stream.ts +455 -0
- package/ui/src/ui/app-view-state.ts +321 -0
- package/ui/src/ui/app.ts +621 -0
- package/ui/src/ui/assistant-identity.ts +23 -0
- package/ui/src/ui/chat/constants.ts +12 -0
- package/ui/src/ui/chat/copy-as-markdown.ts +97 -0
- package/ui/src/ui/chat/grouped-render.ts +287 -0
- package/ui/src/ui/chat/message-extract.test.ts +64 -0
- package/ui/src/ui/chat/message-extract.ts +122 -0
- package/ui/src/ui/chat/message-normalizer.test.ts +179 -0
- package/ui/src/ui/chat/message-normalizer.ts +101 -0
- package/ui/src/ui/chat/tool-cards.ts +156 -0
- package/ui/src/ui/chat/tool-helpers.test.ts +141 -0
- package/ui/src/ui/chat/tool-helpers.ts +37 -0
- package/ui/src/ui/chat-event-reload.test.ts +47 -0
- package/ui/src/ui/chat-event-reload.ts +16 -0
- package/ui/src/ui/chat-markdown.browser.test.ts +37 -0
- package/ui/src/ui/components/resizable-divider.ts +110 -0
- package/ui/src/ui/config-form.browser.test.ts +443 -0
- package/ui/src/ui/controllers/agent-files.ts +126 -0
- package/ui/src/ui/controllers/agent-identity.ts +59 -0
- package/ui/src/ui/controllers/agent-skills.ts +33 -0
- package/ui/src/ui/controllers/agents.test.ts +61 -0
- package/ui/src/ui/controllers/agents.ts +64 -0
- package/ui/src/ui/controllers/assistant-identity.ts +34 -0
- package/ui/src/ui/controllers/channels.ts +94 -0
- package/ui/src/ui/controllers/channels.types.ts +15 -0
- package/ui/src/ui/controllers/chat.test.ts +568 -0
- package/ui/src/ui/controllers/chat.ts +318 -0
- package/ui/src/ui/controllers/config/form-coerce.ts +160 -0
- package/ui/src/ui/controllers/config/form-utils.node.test.ts +455 -0
- package/ui/src/ui/controllers/config/form-utils.ts +90 -0
- package/ui/src/ui/controllers/config.test.ts +289 -0
- package/ui/src/ui/controllers/config.ts +219 -0
- package/ui/src/ui/controllers/control-ui-bootstrap.test.ts +82 -0
- package/ui/src/ui/controllers/control-ui-bootstrap.ts +49 -0
- package/ui/src/ui/controllers/cron-filters.test.ts +81 -0
- package/ui/src/ui/controllers/cron.test.ts +1070 -0
- package/ui/src/ui/controllers/cron.ts +921 -0
- package/ui/src/ui/controllers/debug.ts +60 -0
- package/ui/src/ui/controllers/devices.ts +159 -0
- package/ui/src/ui/controllers/exec-approval.ts +100 -0
- package/ui/src/ui/controllers/exec-approvals.ts +170 -0
- package/ui/src/ui/controllers/logs.ts +147 -0
- package/ui/src/ui/controllers/nodes.ts +32 -0
- package/ui/src/ui/controllers/presence.ts +37 -0
- package/ui/src/ui/controllers/sessions.test.ts +104 -0
- package/ui/src/ui/controllers/sessions.ts +127 -0
- package/ui/src/ui/controllers/skills.ts +157 -0
- package/ui/src/ui/controllers/usage.node.test.ts +181 -0
- package/ui/src/ui/controllers/usage.ts +315 -0
- package/ui/src/ui/data/moonshot-kimi-k2.ts +45 -0
- package/ui/src/ui/device-auth.ts +73 -0
- package/ui/src/ui/device-identity.ts +112 -0
- package/ui/src/ui/external-link.test.ts +18 -0
- package/ui/src/ui/external-link.ts +19 -0
- package/ui/src/ui/focus-mode.browser.test.ts +39 -0
- package/ui/src/ui/format.test.ts +101 -0
- package/ui/src/ui/format.ts +60 -0
- package/ui/src/ui/gateway.ts +360 -0
- package/ui/src/ui/icons.ts +256 -0
- package/ui/src/ui/markdown.test.ts +85 -0
- package/ui/src/ui/markdown.ts +139 -0
- package/ui/src/ui/navigation.browser.test.ts +188 -0
- package/ui/src/ui/navigation.test.ts +189 -0
- package/ui/src/ui/navigation.ts +165 -0
- package/ui/src/ui/open-external-url.test.ts +108 -0
- package/ui/src/ui/open-external-url.ts +73 -0
- package/ui/src/ui/presenter.ts +85 -0
- package/ui/src/ui/storage.node.test.ts +63 -0
- package/ui/src/ui/storage.ts +99 -0
- package/ui/src/ui/test-helpers/app-mount.ts +27 -0
- package/ui/src/ui/text-direction.test.ts +24 -0
- package/ui/src/ui/text-direction.ts +30 -0
- package/ui/src/ui/theme-transition.ts +109 -0
- package/ui/src/ui/theme.ts +16 -0
- package/ui/src/ui/tool-display.ts +159 -0
- package/ui/src/ui/types/chat-types.ts +44 -0
- package/ui/src/ui/types.ts +627 -0
- package/ui/src/ui/ui-types.ts +54 -0
- package/ui/src/ui/usage-helpers.node.test.ts +43 -0
- package/ui/src/ui/usage-helpers.ts +321 -0
- package/ui/src/ui/usage-types.ts +22 -0
- package/ui/src/ui/uuid.test.ts +41 -0
- package/ui/src/ui/uuid.ts +57 -0
- package/ui/src/ui/views/agents-panels-status-files.ts +461 -0
- package/ui/src/ui/views/agents-panels-tools-skills.browser.test.ts +102 -0
- package/ui/src/ui/views/agents-panels-tools-skills.ts +537 -0
- package/ui/src/ui/views/agents-utils.test.ts +100 -0
- package/ui/src/ui/views/agents-utils.ts +502 -0
- package/ui/src/ui/views/agents.ts +499 -0
- package/ui/src/ui/views/channel-config-extras.ts +49 -0
- package/ui/src/ui/views/channels.config.ts +155 -0
- package/ui/src/ui/views/channels.discord.ts +65 -0
- package/ui/src/ui/views/channels.googlechat.ts +79 -0
- package/ui/src/ui/views/channels.imessage.ts +65 -0
- package/ui/src/ui/views/channels.nostr-profile-form.ts +321 -0
- package/ui/src/ui/views/channels.nostr.ts +237 -0
- package/ui/src/ui/views/channels.shared.ts +38 -0
- package/ui/src/ui/views/channels.signal.ts +69 -0
- package/ui/src/ui/views/channels.slack.ts +65 -0
- package/ui/src/ui/views/channels.telegram.ts +120 -0
- package/ui/src/ui/views/channels.ts +325 -0
- package/ui/src/ui/views/channels.types.ts +62 -0
- package/ui/src/ui/views/channels.whatsapp.ts +118 -0
- package/ui/src/ui/views/chat-image-open.browser.test.ts +70 -0
- package/ui/src/ui/views/chat.test.ts +227 -0
- package/ui/src/ui/views/chat.ts +616 -0
- package/ui/src/ui/views/config-form.analyze.ts +267 -0
- package/ui/src/ui/views/config-form.node.ts +1073 -0
- package/ui/src/ui/views/config-form.render.ts +478 -0
- package/ui/src/ui/views/config-form.search.node.test.ts +69 -0
- package/ui/src/ui/views/config-form.shared.ts +96 -0
- package/ui/src/ui/views/config-form.ts +4 -0
- package/ui/src/ui/views/config-search.node.test.ts +50 -0
- package/ui/src/ui/views/config-search.ts +92 -0
- package/ui/src/ui/views/config.browser.test.ts +233 -0
- package/ui/src/ui/views/config.ts +820 -0
- package/ui/src/ui/views/cron.test.ts +741 -0
- package/ui/src/ui/views/cron.ts +1758 -0
- package/ui/src/ui/views/debug.ts +151 -0
- package/ui/src/ui/views/exec-approval.ts +89 -0
- package/ui/src/ui/views/gateway-url-confirmation.ts +40 -0
- package/ui/src/ui/views/instances.ts +89 -0
- package/ui/src/ui/views/logs.ts +155 -0
- package/ui/src/ui/views/markdown-sidebar.ts +40 -0
- package/ui/src/ui/views/nodes-exec-approvals.ts +617 -0
- package/ui/src/ui/views/nodes-shared.ts +67 -0
- package/ui/src/ui/views/nodes.ts +485 -0
- package/ui/src/ui/views/overview-hints.ts +16 -0
- package/ui/src/ui/views/overview.node.test.ts +39 -0
- package/ui/src/ui/views/overview.ts +361 -0
- package/ui/src/ui/views/sessions.test.ts +81 -0
- package/ui/src/ui/views/sessions.ts +321 -0
- package/ui/src/ui/views/skills-grouping.ts +40 -0
- package/ui/src/ui/views/skills-shared.ts +52 -0
- package/ui/src/ui/views/skills.ts +192 -0
- package/ui/src/ui/views/usage-metrics.ts +578 -0
- package/ui/src/ui/views/usage-query.ts +277 -0
- package/ui/src/ui/views/usage-render-details.test.ts +136 -0
- package/ui/src/ui/views/usage-render-details.ts +1083 -0
- package/ui/src/ui/views/usage-render-overview.ts +796 -0
- package/ui/src/ui/views/usage-styles/usageStyles-part1.ts +701 -0
- package/ui/src/ui/views/usage-styles/usageStyles-part2.ts +702 -0
- package/ui/src/ui/views/usage-styles/usageStyles-part3.ts +551 -0
- package/ui/src/ui/views/usage.ts +836 -0
- package/ui/src/ui/views/usageStyles.ts +5 -0
- package/ui/src/ui/views/usageTypes.ts +105 -0
- package/ui/vite.config.ts +43 -0
- package/ui/vitest.config.ts +15 -0
- package/ui/vitest.node.config.ts +10 -0
- package/dist/plugin-sdk/deliver-runtime-BFdqklJM.js +0 -32
- package/dist/plugin-sdk/deps-send-discord.runtime-DuqpYwU0.js +0 -23
- package/dist/plugin-sdk/deps-send-imessage.runtime-CZ2rS8Lb.js +0 -22
- package/dist/plugin-sdk/deps-send-signal.runtime-BdqiWhIh.js +0 -21
- package/dist/plugin-sdk/deps-send-slack.runtime-04s36qiC.js +0 -19
- package/dist/plugin-sdk/deps-send-telegram.runtime-LE5tkPvr.js +0 -24
- package/dist/plugin-sdk/deps-send-whatsapp.runtime-Bz57lobC.js +0 -57
- package/dist/plugin-sdk/image-runtime-B8twoubs.js +0 -25
- package/dist/plugin-sdk/manager-runtime-CMeLwose.js +0 -15
- package/dist/plugin-sdk/pi-model-discovery-runtime-D8CJhtJY.js +0 -8
- package/dist/plugin-sdk/runtime-whatsapp-login.runtime-SkO91TZH.js +0 -10
- package/dist/plugin-sdk/runtime-whatsapp-outbound.runtime-B0VWK5hm.js +0 -19
- package/dist/plugin-sdk/slash-commands.runtime-DS6vCNSL.js +0 -13
- package/dist/plugin-sdk/slash-dispatch.runtime-BXrxb2wd.js +0 -52
- package/dist/plugin-sdk/slash-skill-commands.runtime-Bd6qQ2oT.js +0 -16
- package/dist/plugin-sdk/subagent-registry-runtime-1uwQbuXj.js +0 -52
- package/dist/plugin-sdk/web-B74yhL2N.js +0 -56
|
@@ -0,0 +1,1083 @@
|
|
|
1
|
+
import { html, svg, nothing } from "lit";
|
|
2
|
+
import { formatDurationCompact } from "../../../../src/infra/format-time/format-duration.ts";
|
|
3
|
+
import { parseToolSummary } from "../usage-helpers.ts";
|
|
4
|
+
import { charsToTokens, formatCost, formatTokens } from "./usage-metrics.ts";
|
|
5
|
+
import { renderInsightList } from "./usage-render-overview.ts";
|
|
6
|
+
import {
|
|
7
|
+
SessionLogEntry,
|
|
8
|
+
SessionLogRole,
|
|
9
|
+
TimeSeriesPoint,
|
|
10
|
+
UsageSessionEntry,
|
|
11
|
+
} from "./usageTypes.ts";
|
|
12
|
+
|
|
13
|
+
// Chart constants
|
|
14
|
+
const CHART_BAR_WIDTH_RATIO = 0.75; // Fraction of slot used for bar (rest is gap)
|
|
15
|
+
const CHART_MAX_BAR_WIDTH = 8; // Max bar width in SVG viewBox units
|
|
16
|
+
const CHART_SELECTION_OPACITY = 0.06; // Opacity of range selection overlay
|
|
17
|
+
const HANDLE_WIDTH = 5; // Width of drag handle in SVG units
|
|
18
|
+
const HANDLE_HEIGHT = 12; // Height of drag handle
|
|
19
|
+
const HANDLE_GRIP_OFFSET = 0.7; // Offset of grip lines inside handle
|
|
20
|
+
|
|
21
|
+
function pct(part: number, total: number): number {
|
|
22
|
+
if (!total || total <= 0) {
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
return (part / total) * 100;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function renderEmptyDetailState() {
|
|
29
|
+
return nothing;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Normalize a log timestamp to milliseconds (handles seconds vs ms). */
|
|
33
|
+
function normalizeLogTimestamp(ts: number): number {
|
|
34
|
+
return ts < 1e12 ? ts * 1000 : ts;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Filter session logs by a timestamp range. */
|
|
38
|
+
function filterLogsByRange(
|
|
39
|
+
logs: SessionLogEntry[],
|
|
40
|
+
rangeStart: number,
|
|
41
|
+
rangeEnd: number,
|
|
42
|
+
): SessionLogEntry[] {
|
|
43
|
+
const lo = Math.min(rangeStart, rangeEnd);
|
|
44
|
+
const hi = Math.max(rangeStart, rangeEnd);
|
|
45
|
+
return logs.filter((log) => {
|
|
46
|
+
if (log.timestamp <= 0) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
const ts = normalizeLogTimestamp(log.timestamp);
|
|
50
|
+
return ts >= lo && ts <= hi;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function renderSessionSummary(
|
|
55
|
+
session: UsageSessionEntry,
|
|
56
|
+
filteredUsage?: UsageSessionEntry["usage"],
|
|
57
|
+
filteredLogs?: SessionLogEntry[],
|
|
58
|
+
) {
|
|
59
|
+
const usage = filteredUsage || session.usage;
|
|
60
|
+
if (!usage) {
|
|
61
|
+
return html`
|
|
62
|
+
<div class="muted">No usage data for this session.</div>
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const formatTs = (ts?: number): string => (ts ? new Date(ts).toLocaleString() : "—");
|
|
67
|
+
|
|
68
|
+
const badges: string[] = [];
|
|
69
|
+
if (session.channel) {
|
|
70
|
+
badges.push(`channel:${session.channel}`);
|
|
71
|
+
}
|
|
72
|
+
if (session.agentId) {
|
|
73
|
+
badges.push(`agent:${session.agentId}`);
|
|
74
|
+
}
|
|
75
|
+
if (session.modelProvider || session.providerOverride) {
|
|
76
|
+
badges.push(`provider:${session.modelProvider ?? session.providerOverride}`);
|
|
77
|
+
}
|
|
78
|
+
if (session.model) {
|
|
79
|
+
badges.push(`model:${session.model}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Always use the full tool list for stable layout; update counts when filtering
|
|
83
|
+
const baseTools = usage.toolUsage?.tools.slice(0, 6) ?? [];
|
|
84
|
+
let toolCallCount: number;
|
|
85
|
+
let uniqueToolCount: number;
|
|
86
|
+
let toolItems: Array<{ label: string; value: string; sub: string }>;
|
|
87
|
+
|
|
88
|
+
if (filteredLogs) {
|
|
89
|
+
const toolCounts = new Map<string, number>();
|
|
90
|
+
for (const log of filteredLogs) {
|
|
91
|
+
const { tools } = parseToolSummary(log.content);
|
|
92
|
+
for (const [name] of tools) {
|
|
93
|
+
toolCounts.set(name, (toolCounts.get(name) || 0) + 1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Keep the same tool order as the full session, just update counts
|
|
97
|
+
toolItems = baseTools.map((tool) => ({
|
|
98
|
+
label: tool.name,
|
|
99
|
+
value: `${toolCounts.get(tool.name) ?? 0}`,
|
|
100
|
+
sub: "calls",
|
|
101
|
+
}));
|
|
102
|
+
toolCallCount = [...toolCounts.values()].reduce((sum, c) => sum + c, 0);
|
|
103
|
+
uniqueToolCount = toolCounts.size;
|
|
104
|
+
} else {
|
|
105
|
+
toolItems = baseTools.map((tool) => ({
|
|
106
|
+
label: tool.name,
|
|
107
|
+
value: `${tool.count}`,
|
|
108
|
+
sub: "calls",
|
|
109
|
+
}));
|
|
110
|
+
toolCallCount = usage.toolUsage?.totalCalls ?? 0;
|
|
111
|
+
uniqueToolCount = usage.toolUsage?.uniqueTools ?? 0;
|
|
112
|
+
}
|
|
113
|
+
const modelItems =
|
|
114
|
+
usage.modelUsage?.slice(0, 6).map((entry) => ({
|
|
115
|
+
label: entry.model ?? "unknown",
|
|
116
|
+
value: formatCost(entry.totals.totalCost),
|
|
117
|
+
sub: formatTokens(entry.totals.totalTokens),
|
|
118
|
+
})) ?? [];
|
|
119
|
+
|
|
120
|
+
return html`
|
|
121
|
+
${badges.length > 0 ? html`<div class="usage-badges">${badges.map((b) => html`<span class="usage-badge">${b}</span>`)}</div>` : nothing}
|
|
122
|
+
<div class="session-summary-grid">
|
|
123
|
+
<div class="session-summary-card">
|
|
124
|
+
<div class="session-summary-title">Messages</div>
|
|
125
|
+
<div class="session-summary-value">${usage.messageCounts?.total ?? 0}</div>
|
|
126
|
+
<div class="session-summary-meta">${usage.messageCounts?.user ?? 0} user · ${usage.messageCounts?.assistant ?? 0} assistant</div>
|
|
127
|
+
</div>
|
|
128
|
+
<div class="session-summary-card">
|
|
129
|
+
<div class="session-summary-title">Tool Calls</div>
|
|
130
|
+
<div class="session-summary-value">${toolCallCount}</div>
|
|
131
|
+
<div class="session-summary-meta">${uniqueToolCount} tools</div>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="session-summary-card">
|
|
134
|
+
<div class="session-summary-title">Errors</div>
|
|
135
|
+
<div class="session-summary-value">${usage.messageCounts?.errors ?? 0}</div>
|
|
136
|
+
<div class="session-summary-meta">${usage.messageCounts?.toolResults ?? 0} tool results</div>
|
|
137
|
+
</div>
|
|
138
|
+
<div class="session-summary-card">
|
|
139
|
+
<div class="session-summary-title">Duration</div>
|
|
140
|
+
<div class="session-summary-value">${formatDurationCompact(usage.durationMs, { spaced: true }) ?? "—"}</div>
|
|
141
|
+
<div class="session-summary-meta">${formatTs(usage.firstActivity)} → ${formatTs(usage.lastActivity)}</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
<div class="usage-insights-grid" style="margin-top: 12px;">
|
|
145
|
+
${renderInsightList("Top Tools", toolItems, "No tool calls")}
|
|
146
|
+
${renderInsightList("Model Mix", modelItems, "No model data")}
|
|
147
|
+
</div>
|
|
148
|
+
`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Aggregate usage stats from time series points within a timestamp range. */
|
|
152
|
+
function computeFilteredUsage(
|
|
153
|
+
baseUsage: NonNullable<UsageSessionEntry["usage"]>,
|
|
154
|
+
points: TimeSeriesPoint[],
|
|
155
|
+
rangeStart: number,
|
|
156
|
+
rangeEnd: number,
|
|
157
|
+
): UsageSessionEntry["usage"] | undefined {
|
|
158
|
+
const lo = Math.min(rangeStart, rangeEnd);
|
|
159
|
+
const hi = Math.max(rangeStart, rangeEnd);
|
|
160
|
+
const filtered = points.filter((p) => p.timestamp >= lo && p.timestamp <= hi);
|
|
161
|
+
if (filtered.length === 0) {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let totalTokens = 0;
|
|
166
|
+
let totalCost = 0;
|
|
167
|
+
let userMessages = 0;
|
|
168
|
+
let assistantMessages = 0;
|
|
169
|
+
let totalInput = 0;
|
|
170
|
+
let totalOutput = 0;
|
|
171
|
+
let totalCacheRead = 0;
|
|
172
|
+
let totalCacheWrite = 0;
|
|
173
|
+
|
|
174
|
+
for (const p of filtered) {
|
|
175
|
+
totalTokens += p.totalTokens || 0;
|
|
176
|
+
totalCost += p.cost || 0;
|
|
177
|
+
totalInput += p.input || 0;
|
|
178
|
+
totalOutput += p.output || 0;
|
|
179
|
+
totalCacheRead += p.cacheRead || 0;
|
|
180
|
+
totalCacheWrite += p.cacheWrite || 0;
|
|
181
|
+
if (p.output > 0) {
|
|
182
|
+
assistantMessages++;
|
|
183
|
+
}
|
|
184
|
+
if (p.input > 0) {
|
|
185
|
+
userMessages++;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
...baseUsage,
|
|
191
|
+
totalTokens,
|
|
192
|
+
totalCost,
|
|
193
|
+
input: totalInput,
|
|
194
|
+
output: totalOutput,
|
|
195
|
+
cacheRead: totalCacheRead,
|
|
196
|
+
cacheWrite: totalCacheWrite,
|
|
197
|
+
durationMs: filtered[filtered.length - 1].timestamp - filtered[0].timestamp,
|
|
198
|
+
firstActivity: filtered[0].timestamp,
|
|
199
|
+
lastActivity: filtered[filtered.length - 1].timestamp,
|
|
200
|
+
messageCounts: {
|
|
201
|
+
total: filtered.length,
|
|
202
|
+
user: userMessages,
|
|
203
|
+
assistant: assistantMessages,
|
|
204
|
+
toolCalls: 0,
|
|
205
|
+
toolResults: 0,
|
|
206
|
+
errors: 0,
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function renderSessionDetailPanel(
|
|
212
|
+
session: UsageSessionEntry,
|
|
213
|
+
timeSeries: { points: TimeSeriesPoint[] } | null,
|
|
214
|
+
timeSeriesLoading: boolean,
|
|
215
|
+
timeSeriesMode: "cumulative" | "per-turn",
|
|
216
|
+
onTimeSeriesModeChange: (mode: "cumulative" | "per-turn") => void,
|
|
217
|
+
timeSeriesBreakdownMode: "total" | "by-type",
|
|
218
|
+
onTimeSeriesBreakdownChange: (mode: "total" | "by-type") => void,
|
|
219
|
+
timeSeriesCursorStart: number | null,
|
|
220
|
+
timeSeriesCursorEnd: number | null,
|
|
221
|
+
onTimeSeriesCursorRangeChange: (start: number | null, end: number | null) => void,
|
|
222
|
+
startDate: string,
|
|
223
|
+
endDate: string,
|
|
224
|
+
selectedDays: string[],
|
|
225
|
+
sessionLogs: SessionLogEntry[] | null,
|
|
226
|
+
sessionLogsLoading: boolean,
|
|
227
|
+
sessionLogsExpanded: boolean,
|
|
228
|
+
onToggleSessionLogsExpanded: () => void,
|
|
229
|
+
logFilters: {
|
|
230
|
+
roles: SessionLogRole[];
|
|
231
|
+
tools: string[];
|
|
232
|
+
hasTools: boolean;
|
|
233
|
+
query: string;
|
|
234
|
+
},
|
|
235
|
+
onLogFilterRolesChange: (next: SessionLogRole[]) => void,
|
|
236
|
+
onLogFilterToolsChange: (next: string[]) => void,
|
|
237
|
+
onLogFilterHasToolsChange: (next: boolean) => void,
|
|
238
|
+
onLogFilterQueryChange: (next: string) => void,
|
|
239
|
+
onLogFilterClear: () => void,
|
|
240
|
+
contextExpanded: boolean,
|
|
241
|
+
onToggleContextExpanded: () => void,
|
|
242
|
+
onClose: () => void,
|
|
243
|
+
) {
|
|
244
|
+
const label = session.label || session.key;
|
|
245
|
+
const displayLabel = label.length > 50 ? label.slice(0, 50) + "…" : label;
|
|
246
|
+
const usage = session.usage;
|
|
247
|
+
|
|
248
|
+
const hasRange = timeSeriesCursorStart !== null && timeSeriesCursorEnd !== null;
|
|
249
|
+
const filteredUsage =
|
|
250
|
+
timeSeriesCursorStart !== null && timeSeriesCursorEnd !== null && timeSeries?.points && usage
|
|
251
|
+
? computeFilteredUsage(usage, timeSeries.points, timeSeriesCursorStart, timeSeriesCursorEnd)
|
|
252
|
+
: undefined;
|
|
253
|
+
const headerStats = filteredUsage
|
|
254
|
+
? { totalTokens: filteredUsage.totalTokens, totalCost: filteredUsage.totalCost }
|
|
255
|
+
: { totalTokens: usage?.totalTokens ?? 0, totalCost: usage?.totalCost ?? 0 };
|
|
256
|
+
const cursorIndicator = filteredUsage ? " (filtered)" : "";
|
|
257
|
+
|
|
258
|
+
return html`
|
|
259
|
+
<div class="card session-detail-panel">
|
|
260
|
+
<div class="session-detail-header">
|
|
261
|
+
<div class="session-detail-header-left">
|
|
262
|
+
<div class="session-detail-title">
|
|
263
|
+
${displayLabel}
|
|
264
|
+
${cursorIndicator ? html`<span style="font-size: 11px; color: var(--muted); margin-left: 8px;">${cursorIndicator}</span>` : nothing}
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
<div class="session-detail-stats">
|
|
268
|
+
${
|
|
269
|
+
usage
|
|
270
|
+
? html`
|
|
271
|
+
<span><strong>${formatTokens(headerStats.totalTokens)}</strong> tokens${cursorIndicator}</span>
|
|
272
|
+
<span><strong>${formatCost(headerStats.totalCost)}</strong>${cursorIndicator}</span>
|
|
273
|
+
`
|
|
274
|
+
: nothing
|
|
275
|
+
}
|
|
276
|
+
</div>
|
|
277
|
+
<button class="session-close-btn" @click=${onClose} title="Close session details">×</button>
|
|
278
|
+
</div>
|
|
279
|
+
<div class="session-detail-content">
|
|
280
|
+
${renderSessionSummary(
|
|
281
|
+
session,
|
|
282
|
+
filteredUsage,
|
|
283
|
+
timeSeriesCursorStart != null && timeSeriesCursorEnd != null && sessionLogs
|
|
284
|
+
? filterLogsByRange(sessionLogs, timeSeriesCursorStart, timeSeriesCursorEnd)
|
|
285
|
+
: undefined,
|
|
286
|
+
)}
|
|
287
|
+
<div class="session-detail-row">
|
|
288
|
+
${renderTimeSeriesCompact(
|
|
289
|
+
timeSeries,
|
|
290
|
+
timeSeriesLoading,
|
|
291
|
+
timeSeriesMode,
|
|
292
|
+
onTimeSeriesModeChange,
|
|
293
|
+
timeSeriesBreakdownMode,
|
|
294
|
+
onTimeSeriesBreakdownChange,
|
|
295
|
+
startDate,
|
|
296
|
+
endDate,
|
|
297
|
+
selectedDays,
|
|
298
|
+
timeSeriesCursorStart,
|
|
299
|
+
timeSeriesCursorEnd,
|
|
300
|
+
onTimeSeriesCursorRangeChange,
|
|
301
|
+
)}
|
|
302
|
+
</div>
|
|
303
|
+
<div class="session-detail-bottom">
|
|
304
|
+
${renderSessionLogsCompact(
|
|
305
|
+
sessionLogs,
|
|
306
|
+
sessionLogsLoading,
|
|
307
|
+
sessionLogsExpanded,
|
|
308
|
+
onToggleSessionLogsExpanded,
|
|
309
|
+
logFilters,
|
|
310
|
+
onLogFilterRolesChange,
|
|
311
|
+
onLogFilterToolsChange,
|
|
312
|
+
onLogFilterHasToolsChange,
|
|
313
|
+
onLogFilterQueryChange,
|
|
314
|
+
onLogFilterClear,
|
|
315
|
+
hasRange ? timeSeriesCursorStart : null,
|
|
316
|
+
hasRange ? timeSeriesCursorEnd : null,
|
|
317
|
+
)}
|
|
318
|
+
${renderContextPanel(session.contextWeight, usage, contextExpanded, onToggleContextExpanded)}
|
|
319
|
+
</div>
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function renderTimeSeriesCompact(
|
|
326
|
+
timeSeries: { points: TimeSeriesPoint[] } | null,
|
|
327
|
+
loading: boolean,
|
|
328
|
+
mode: "cumulative" | "per-turn",
|
|
329
|
+
onModeChange: (mode: "cumulative" | "per-turn") => void,
|
|
330
|
+
breakdownMode: "total" | "by-type",
|
|
331
|
+
onBreakdownChange: (mode: "total" | "by-type") => void,
|
|
332
|
+
startDate?: string,
|
|
333
|
+
endDate?: string,
|
|
334
|
+
selectedDays?: string[],
|
|
335
|
+
cursorStart?: number | null,
|
|
336
|
+
cursorEnd?: number | null,
|
|
337
|
+
onCursorRangeChange?: (start: number | null, end: number | null) => void,
|
|
338
|
+
) {
|
|
339
|
+
if (loading) {
|
|
340
|
+
return html`
|
|
341
|
+
<div class="session-timeseries-compact">
|
|
342
|
+
<div class="muted" style="padding: 20px; text-align: center">Loading...</div>
|
|
343
|
+
</div>
|
|
344
|
+
`;
|
|
345
|
+
}
|
|
346
|
+
if (!timeSeries || timeSeries.points.length < 2) {
|
|
347
|
+
return html`
|
|
348
|
+
<div class="session-timeseries-compact">
|
|
349
|
+
<div class="muted" style="padding: 20px; text-align: center">No timeline data</div>
|
|
350
|
+
</div>
|
|
351
|
+
`;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Filter and recalculate (same logic as main function)
|
|
355
|
+
let points = timeSeries.points;
|
|
356
|
+
if (startDate || endDate || (selectedDays && selectedDays.length > 0)) {
|
|
357
|
+
const startTs = startDate ? new Date(startDate + "T00:00:00").getTime() : 0;
|
|
358
|
+
const endTs = endDate ? new Date(endDate + "T23:59:59").getTime() : Infinity;
|
|
359
|
+
points = timeSeries.points.filter((p) => {
|
|
360
|
+
if (p.timestamp < startTs || p.timestamp > endTs) {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
if (selectedDays && selectedDays.length > 0) {
|
|
364
|
+
const d = new Date(p.timestamp);
|
|
365
|
+
const dateStr = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
366
|
+
return selectedDays.includes(dateStr);
|
|
367
|
+
}
|
|
368
|
+
return true;
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
if (points.length < 2) {
|
|
372
|
+
return html`
|
|
373
|
+
<div class="session-timeseries-compact">
|
|
374
|
+
<div class="muted" style="padding: 20px; text-align: center">No data in range</div>
|
|
375
|
+
</div>
|
|
376
|
+
`;
|
|
377
|
+
}
|
|
378
|
+
let cumTokens = 0,
|
|
379
|
+
cumCost = 0;
|
|
380
|
+
let sumOutput = 0;
|
|
381
|
+
let sumInput = 0;
|
|
382
|
+
let sumCacheRead = 0;
|
|
383
|
+
let sumCacheWrite = 0;
|
|
384
|
+
points = points.map((p) => {
|
|
385
|
+
cumTokens += p.totalTokens;
|
|
386
|
+
cumCost += p.cost;
|
|
387
|
+
sumOutput += p.output;
|
|
388
|
+
sumInput += p.input;
|
|
389
|
+
sumCacheRead += p.cacheRead;
|
|
390
|
+
sumCacheWrite += p.cacheWrite;
|
|
391
|
+
return { ...p, cumulativeTokens: cumTokens, cumulativeCost: cumCost };
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// Compute range-filtered sums for "Tokens by Type"
|
|
395
|
+
const hasSelection = cursorStart != null && cursorEnd != null;
|
|
396
|
+
const rangeStartTs = hasSelection ? Math.min(cursorStart, cursorEnd) : 0;
|
|
397
|
+
const rangeEndTs = hasSelection ? Math.max(cursorStart, cursorEnd) : Infinity;
|
|
398
|
+
|
|
399
|
+
// Find start/end indices for dimming
|
|
400
|
+
let rangeStartIdx = 0;
|
|
401
|
+
let rangeEndIdx = points.length;
|
|
402
|
+
if (hasSelection) {
|
|
403
|
+
rangeStartIdx = points.findIndex((p) => p.timestamp >= rangeStartTs);
|
|
404
|
+
if (rangeStartIdx === -1) {
|
|
405
|
+
rangeStartIdx = points.length;
|
|
406
|
+
}
|
|
407
|
+
const endIdx = points.findIndex((p) => p.timestamp > rangeEndTs);
|
|
408
|
+
rangeEndIdx = endIdx === -1 ? points.length : endIdx;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const filteredPoints = hasSelection ? points.slice(rangeStartIdx, rangeEndIdx) : points;
|
|
412
|
+
let filteredOutput = 0,
|
|
413
|
+
filteredInput = 0,
|
|
414
|
+
filteredCacheRead = 0,
|
|
415
|
+
filteredCacheWrite = 0;
|
|
416
|
+
for (const p of filteredPoints) {
|
|
417
|
+
filteredOutput += p.output;
|
|
418
|
+
filteredInput += p.input;
|
|
419
|
+
filteredCacheRead += p.cacheRead;
|
|
420
|
+
filteredCacheWrite += p.cacheWrite;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const width = 400,
|
|
424
|
+
height = 100;
|
|
425
|
+
const padding = { top: 8, right: 4, bottom: 14, left: 30 };
|
|
426
|
+
const chartWidth = width - padding.left - padding.right;
|
|
427
|
+
const chartHeight = height - padding.top - padding.bottom;
|
|
428
|
+
const isCumulative = mode === "cumulative";
|
|
429
|
+
const breakdownByType = mode === "per-turn" && breakdownMode === "by-type";
|
|
430
|
+
|
|
431
|
+
const totalTypeTokens = filteredOutput + filteredInput + filteredCacheRead + filteredCacheWrite;
|
|
432
|
+
const barTotals = points.map((p) =>
|
|
433
|
+
isCumulative
|
|
434
|
+
? p.cumulativeTokens
|
|
435
|
+
: breakdownByType
|
|
436
|
+
? p.input + p.output + p.cacheRead + p.cacheWrite
|
|
437
|
+
: p.totalTokens,
|
|
438
|
+
);
|
|
439
|
+
const maxValue = Math.max(...barTotals, 1);
|
|
440
|
+
// Ensure bars + gaps fit exactly within chartWidth
|
|
441
|
+
const slotWidth = chartWidth / points.length; // space per bar including gap
|
|
442
|
+
const barWidth = Math.min(CHART_MAX_BAR_WIDTH, Math.max(1, slotWidth * CHART_BAR_WIDTH_RATIO));
|
|
443
|
+
const barGap = slotWidth - barWidth;
|
|
444
|
+
|
|
445
|
+
// Pre-compute handle X positions in SVG viewBox coordinates
|
|
446
|
+
const leftHandleX = padding.left + rangeStartIdx * (barWidth + barGap);
|
|
447
|
+
const rightHandleX =
|
|
448
|
+
rangeEndIdx >= points.length
|
|
449
|
+
? padding.left + (points.length - 1) * (barWidth + barGap) + barWidth // right edge of last bar
|
|
450
|
+
: padding.left + (rangeEndIdx - 1) * (barWidth + barGap) + barWidth; // right edge of last selected bar
|
|
451
|
+
|
|
452
|
+
return html`
|
|
453
|
+
<div class="session-timeseries-compact">
|
|
454
|
+
<div class="timeseries-header-row">
|
|
455
|
+
<div class="card-title" style="font-size: 12px; color: var(--text);">Usage Over Time</div>
|
|
456
|
+
<div class="timeseries-controls">
|
|
457
|
+
${
|
|
458
|
+
hasSelection
|
|
459
|
+
? html`
|
|
460
|
+
<div class="chart-toggle small">
|
|
461
|
+
<button class="toggle-btn active" @click=${() => onCursorRangeChange?.(null, null)}>Reset</button>
|
|
462
|
+
</div>
|
|
463
|
+
`
|
|
464
|
+
: nothing
|
|
465
|
+
}
|
|
466
|
+
<div class="chart-toggle small">
|
|
467
|
+
<button
|
|
468
|
+
class="toggle-btn ${!isCumulative ? "active" : ""}"
|
|
469
|
+
@click=${() => onModeChange("per-turn")}
|
|
470
|
+
>
|
|
471
|
+
Per Turn
|
|
472
|
+
</button>
|
|
473
|
+
<button
|
|
474
|
+
class="toggle-btn ${isCumulative ? "active" : ""}"
|
|
475
|
+
@click=${() => onModeChange("cumulative")}
|
|
476
|
+
>
|
|
477
|
+
Cumulative
|
|
478
|
+
</button>
|
|
479
|
+
</div>
|
|
480
|
+
${
|
|
481
|
+
!isCumulative
|
|
482
|
+
? html`
|
|
483
|
+
<div class="chart-toggle small">
|
|
484
|
+
<button
|
|
485
|
+
class="toggle-btn ${breakdownMode === "total" ? "active" : ""}"
|
|
486
|
+
@click=${() => onBreakdownChange("total")}
|
|
487
|
+
>
|
|
488
|
+
Total
|
|
489
|
+
</button>
|
|
490
|
+
<button
|
|
491
|
+
class="toggle-btn ${breakdownMode === "by-type" ? "active" : ""}"
|
|
492
|
+
@click=${() => onBreakdownChange("by-type")}
|
|
493
|
+
>
|
|
494
|
+
By Type
|
|
495
|
+
</button>
|
|
496
|
+
</div>
|
|
497
|
+
`
|
|
498
|
+
: nothing
|
|
499
|
+
}
|
|
500
|
+
</div>
|
|
501
|
+
</div>
|
|
502
|
+
<div class="timeseries-chart-wrapper" style="position: relative; cursor: crosshair;">
|
|
503
|
+
<svg
|
|
504
|
+
viewBox="0 0 ${width} ${height + 18}"
|
|
505
|
+
class="timeseries-svg"
|
|
506
|
+
style="width: 100%; height: auto; display: block;"
|
|
507
|
+
>
|
|
508
|
+
<!-- Y axis -->
|
|
509
|
+
<line x1="${padding.left}" y1="${padding.top}" x2="${padding.left}" y2="${padding.top + chartHeight}" stroke="var(--border)" />
|
|
510
|
+
<!-- X axis -->
|
|
511
|
+
<line x1="${padding.left}" y1="${padding.top + chartHeight}" x2="${width - padding.right}" y2="${padding.top + chartHeight}" stroke="var(--border)" />
|
|
512
|
+
<!-- Y axis labels -->
|
|
513
|
+
<text x="${padding.left - 4}" y="${padding.top + 5}" text-anchor="end" class="ts-axis-label">${formatTokens(maxValue)}</text>
|
|
514
|
+
<text x="${padding.left - 4}" y="${padding.top + chartHeight}" text-anchor="end" class="ts-axis-label">0</text>
|
|
515
|
+
<!-- X axis labels (first and last) -->
|
|
516
|
+
${
|
|
517
|
+
points.length > 0
|
|
518
|
+
? svg`
|
|
519
|
+
<text x="${padding.left}" y="${padding.top + chartHeight + 10}" text-anchor="start" class="ts-axis-label">${new Date(points[0].timestamp).toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" })}</text>
|
|
520
|
+
<text x="${width - padding.right}" y="${padding.top + chartHeight + 10}" text-anchor="end" class="ts-axis-label">${new Date(points[points.length - 1].timestamp).toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" })}</text>
|
|
521
|
+
`
|
|
522
|
+
: nothing
|
|
523
|
+
}
|
|
524
|
+
<!-- Bars -->
|
|
525
|
+
${points.map((p, i) => {
|
|
526
|
+
const val = barTotals[i];
|
|
527
|
+
const x = padding.left + i * (barWidth + barGap);
|
|
528
|
+
const bh = (val / maxValue) * chartHeight;
|
|
529
|
+
const y = padding.top + chartHeight - bh;
|
|
530
|
+
const date = new Date(p.timestamp);
|
|
531
|
+
const tooltipLines = [
|
|
532
|
+
date.toLocaleDateString(undefined, {
|
|
533
|
+
month: "short",
|
|
534
|
+
day: "numeric",
|
|
535
|
+
hour: "2-digit",
|
|
536
|
+
minute: "2-digit",
|
|
537
|
+
}),
|
|
538
|
+
`${formatTokens(val)} tokens`,
|
|
539
|
+
];
|
|
540
|
+
if (breakdownByType) {
|
|
541
|
+
tooltipLines.push(`Out ${formatTokens(p.output)}`);
|
|
542
|
+
tooltipLines.push(`In ${formatTokens(p.input)}`);
|
|
543
|
+
tooltipLines.push(`CW ${formatTokens(p.cacheWrite)}`);
|
|
544
|
+
tooltipLines.push(`CR ${formatTokens(p.cacheRead)}`);
|
|
545
|
+
}
|
|
546
|
+
const tooltip = tooltipLines.join(" · ");
|
|
547
|
+
const isOutside = hasSelection && (i < rangeStartIdx || i >= rangeEndIdx);
|
|
548
|
+
|
|
549
|
+
if (!breakdownByType) {
|
|
550
|
+
return svg`<rect x="${x}" y="${y}" width="${barWidth}" height="${bh}" class="ts-bar${isOutside ? " dimmed" : ""}" rx="1"><title>${tooltip}</title></rect>`;
|
|
551
|
+
}
|
|
552
|
+
const segments = [
|
|
553
|
+
{ value: p.output, cls: "output" },
|
|
554
|
+
{ value: p.input, cls: "input" },
|
|
555
|
+
{ value: p.cacheWrite, cls: "cache-write" },
|
|
556
|
+
{ value: p.cacheRead, cls: "cache-read" },
|
|
557
|
+
];
|
|
558
|
+
let yC = padding.top + chartHeight;
|
|
559
|
+
const dim = isOutside ? " dimmed" : "";
|
|
560
|
+
return svg`
|
|
561
|
+
${segments.map((seg) => {
|
|
562
|
+
if (seg.value <= 0 || val <= 0) {
|
|
563
|
+
return nothing;
|
|
564
|
+
}
|
|
565
|
+
const sh = bh * (seg.value / val);
|
|
566
|
+
yC -= sh;
|
|
567
|
+
return svg`<rect x="${x}" y="${yC}" width="${barWidth}" height="${sh}" class="ts-bar ${seg.cls}${dim}" rx="1"><title>${tooltip}</title></rect>`;
|
|
568
|
+
})}
|
|
569
|
+
`;
|
|
570
|
+
})}
|
|
571
|
+
<!-- Selection highlight overlay (always visible between handles) -->
|
|
572
|
+
${svg`
|
|
573
|
+
<rect
|
|
574
|
+
x="${leftHandleX}"
|
|
575
|
+
y="${padding.top}"
|
|
576
|
+
width="${Math.max(1, rightHandleX - leftHandleX)}"
|
|
577
|
+
height="${chartHeight}"
|
|
578
|
+
fill="var(--accent)"
|
|
579
|
+
opacity="${CHART_SELECTION_OPACITY}"
|
|
580
|
+
pointer-events="none"
|
|
581
|
+
/>
|
|
582
|
+
`}
|
|
583
|
+
<!-- Left cursor line + handle -->
|
|
584
|
+
${svg`
|
|
585
|
+
<line x1="${leftHandleX}" y1="${padding.top}" x2="${leftHandleX}" y2="${padding.top + chartHeight}" stroke="var(--accent)" stroke-width="0.8" opacity="0.7" />
|
|
586
|
+
<rect x="${leftHandleX - HANDLE_WIDTH / 2}" y="${padding.top + chartHeight / 2 - HANDLE_HEIGHT / 2}" width="${HANDLE_WIDTH}" height="${HANDLE_HEIGHT}" rx="1.5" fill="var(--accent)" class="cursor-handle" />
|
|
587
|
+
<line x1="${leftHandleX - HANDLE_GRIP_OFFSET}" y1="${padding.top + chartHeight / 2 - HANDLE_HEIGHT / 5}" x2="${leftHandleX - HANDLE_GRIP_OFFSET}" y2="${padding.top + chartHeight / 2 + HANDLE_HEIGHT / 5}" stroke="var(--bg)" stroke-width="0.4" pointer-events="none" />
|
|
588
|
+
<line x1="${leftHandleX + HANDLE_GRIP_OFFSET}" y1="${padding.top + chartHeight / 2 - HANDLE_HEIGHT / 5}" x2="${leftHandleX + HANDLE_GRIP_OFFSET}" y2="${padding.top + chartHeight / 2 + HANDLE_HEIGHT / 5}" stroke="var(--bg)" stroke-width="0.4" pointer-events="none" />
|
|
589
|
+
`}
|
|
590
|
+
<!-- Right cursor line + handle -->
|
|
591
|
+
${svg`
|
|
592
|
+
<line x1="${rightHandleX}" y1="${padding.top}" x2="${rightHandleX}" y2="${padding.top + chartHeight}" stroke="var(--accent)" stroke-width="0.8" opacity="0.7" />
|
|
593
|
+
<rect x="${rightHandleX - HANDLE_WIDTH / 2}" y="${padding.top + chartHeight / 2 - HANDLE_HEIGHT / 2}" width="${HANDLE_WIDTH}" height="${HANDLE_HEIGHT}" rx="1.5" fill="var(--accent)" class="cursor-handle" />
|
|
594
|
+
<line x1="${rightHandleX - HANDLE_GRIP_OFFSET}" y1="${padding.top + chartHeight / 2 - HANDLE_HEIGHT / 5}" x2="${rightHandleX - HANDLE_GRIP_OFFSET}" y2="${padding.top + chartHeight / 2 + HANDLE_HEIGHT / 5}" stroke="var(--bg)" stroke-width="0.4" pointer-events="none" />
|
|
595
|
+
<line x1="${rightHandleX + HANDLE_GRIP_OFFSET}" y1="${padding.top + chartHeight / 2 - HANDLE_HEIGHT / 5}" x2="${rightHandleX + HANDLE_GRIP_OFFSET}" y2="${padding.top + chartHeight / 2 + HANDLE_HEIGHT / 5}" stroke="var(--bg)" stroke-width="0.4" pointer-events="none" />
|
|
596
|
+
`}
|
|
597
|
+
</svg>
|
|
598
|
+
<!-- Handle drag zones (only on handles, not full chart) -->
|
|
599
|
+
${(() => {
|
|
600
|
+
const leftHandlePos = `${((leftHandleX / width) * 100).toFixed(1)}%`;
|
|
601
|
+
const rightHandlePos = `${((rightHandleX / width) * 100).toFixed(1)}%`;
|
|
602
|
+
|
|
603
|
+
const makeDragHandler = (side: "left" | "right") => (e: MouseEvent) => {
|
|
604
|
+
if (!onCursorRangeChange) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
e.preventDefault();
|
|
608
|
+
e.stopPropagation();
|
|
609
|
+
// Find the wrapper, then the SVG inside it
|
|
610
|
+
const wrapper = (e.currentTarget as HTMLElement).closest(".timeseries-chart-wrapper");
|
|
611
|
+
const svgEl = wrapper?.querySelector("svg") as SVGSVGElement;
|
|
612
|
+
if (!svgEl) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
// Capture rect once at mousedown to avoid re-render offset shifts
|
|
616
|
+
const rect = svgEl.getBoundingClientRect();
|
|
617
|
+
const svgWidth = rect.width;
|
|
618
|
+
const chartLeftPx = (padding.left / width) * svgWidth;
|
|
619
|
+
const chartRightPx = ((width - padding.right) / width) * svgWidth;
|
|
620
|
+
const chartW = chartRightPx - chartLeftPx;
|
|
621
|
+
|
|
622
|
+
const posToIdx = (clientX: number) => {
|
|
623
|
+
const x = Math.max(0, Math.min(1, (clientX - rect.left - chartLeftPx) / chartW));
|
|
624
|
+
return Math.min(Math.floor(x * points.length), points.length - 1);
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
// Compute click offset: where on the handle the user grabbed
|
|
628
|
+
const handleSvgX = side === "left" ? leftHandleX : rightHandleX;
|
|
629
|
+
const handleClientX = rect.left + (handleSvgX / width) * svgWidth;
|
|
630
|
+
const grabOffset = e.clientX - handleClientX;
|
|
631
|
+
|
|
632
|
+
document.body.style.cursor = "col-resize";
|
|
633
|
+
|
|
634
|
+
const handleMove = (me: MouseEvent) => {
|
|
635
|
+
const adjustedX = me.clientX - grabOffset;
|
|
636
|
+
const idx = posToIdx(adjustedX);
|
|
637
|
+
const pt = points[idx];
|
|
638
|
+
if (!pt) {
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
if (side === "left") {
|
|
642
|
+
const endTs = cursorEnd ?? points[points.length - 1].timestamp;
|
|
643
|
+
// Don't let left go past right
|
|
644
|
+
onCursorRangeChange(Math.min(pt.timestamp, endTs), endTs);
|
|
645
|
+
} else {
|
|
646
|
+
const startTs = cursorStart ?? points[0].timestamp;
|
|
647
|
+
// Don't let right go past left
|
|
648
|
+
onCursorRangeChange(startTs, Math.max(pt.timestamp, startTs));
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
const handleUp = () => {
|
|
653
|
+
document.body.style.cursor = "";
|
|
654
|
+
document.removeEventListener("mousemove", handleMove);
|
|
655
|
+
document.removeEventListener("mouseup", handleUp);
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
document.addEventListener("mousemove", handleMove);
|
|
659
|
+
document.addEventListener("mouseup", handleUp);
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
return html`
|
|
663
|
+
<div class="chart-handle-zone chart-handle-left"
|
|
664
|
+
style="left: ${leftHandlePos};"
|
|
665
|
+
@mousedown=${makeDragHandler("left")}></div>
|
|
666
|
+
<div class="chart-handle-zone chart-handle-right"
|
|
667
|
+
style="left: ${rightHandlePos};"
|
|
668
|
+
@mousedown=${makeDragHandler("right")}></div>
|
|
669
|
+
`;
|
|
670
|
+
})()}
|
|
671
|
+
</div>
|
|
672
|
+
<div class="timeseries-summary">
|
|
673
|
+
${
|
|
674
|
+
hasSelection
|
|
675
|
+
? html`
|
|
676
|
+
<span style="color: var(--accent);">▶ Turns ${rangeStartIdx + 1}–${rangeEndIdx} of ${points.length}</span> ·
|
|
677
|
+
${new Date(rangeStartTs).toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" })}–${new Date(rangeEndTs).toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" })} ·
|
|
678
|
+
${formatTokens(filteredOutput + filteredInput + filteredCacheRead + filteredCacheWrite)} ·
|
|
679
|
+
${formatCost(filteredPoints.reduce((s, p) => s + (p.cost || 0), 0))}
|
|
680
|
+
`
|
|
681
|
+
: html`${points.length} msgs · ${formatTokens(cumTokens)} · ${formatCost(cumCost)}`
|
|
682
|
+
}
|
|
683
|
+
</div>
|
|
684
|
+
${
|
|
685
|
+
breakdownByType
|
|
686
|
+
? html`
|
|
687
|
+
<div style="margin-top: 8px;">
|
|
688
|
+
<div class="card-title" style="font-size: 12px; margin-bottom: 6px; color: var(--text);">Tokens by Type</div>
|
|
689
|
+
<div class="cost-breakdown-bar" style="height: 18px;">
|
|
690
|
+
<div class="cost-segment output" style="width: ${pct(filteredOutput, totalTypeTokens).toFixed(1)}%"></div>
|
|
691
|
+
<div class="cost-segment input" style="width: ${pct(filteredInput, totalTypeTokens).toFixed(1)}%"></div>
|
|
692
|
+
<div class="cost-segment cache-write" style="width: ${pct(filteredCacheWrite, totalTypeTokens).toFixed(1)}%"></div>
|
|
693
|
+
<div class="cost-segment cache-read" style="width: ${pct(filteredCacheRead, totalTypeTokens).toFixed(1)}%"></div>
|
|
694
|
+
</div>
|
|
695
|
+
<div class="cost-breakdown-legend">
|
|
696
|
+
<div class="legend-item" title="Assistant output tokens">
|
|
697
|
+
<span class="legend-dot output"></span>Output ${formatTokens(filteredOutput)}
|
|
698
|
+
</div>
|
|
699
|
+
<div class="legend-item" title="User + tool input tokens">
|
|
700
|
+
<span class="legend-dot input"></span>Input ${formatTokens(filteredInput)}
|
|
701
|
+
</div>
|
|
702
|
+
<div class="legend-item" title="Tokens written to cache">
|
|
703
|
+
<span class="legend-dot cache-write"></span>Cache Write ${formatTokens(filteredCacheWrite)}
|
|
704
|
+
</div>
|
|
705
|
+
<div class="legend-item" title="Tokens read from cache">
|
|
706
|
+
<span class="legend-dot cache-read"></span>Cache Read ${formatTokens(filteredCacheRead)}
|
|
707
|
+
</div>
|
|
708
|
+
</div>
|
|
709
|
+
<div class="cost-breakdown-total">Total: ${formatTokens(totalTypeTokens)}</div>
|
|
710
|
+
</div>
|
|
711
|
+
`
|
|
712
|
+
: nothing
|
|
713
|
+
}
|
|
714
|
+
</div>
|
|
715
|
+
`;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function renderContextPanel(
|
|
719
|
+
contextWeight: UsageSessionEntry["contextWeight"],
|
|
720
|
+
usage: UsageSessionEntry["usage"],
|
|
721
|
+
expanded: boolean,
|
|
722
|
+
onToggleExpanded: () => void,
|
|
723
|
+
) {
|
|
724
|
+
if (!contextWeight) {
|
|
725
|
+
return html`
|
|
726
|
+
<div class="context-details-panel">
|
|
727
|
+
<div class="muted" style="padding: 20px; text-align: center">No context data</div>
|
|
728
|
+
</div>
|
|
729
|
+
`;
|
|
730
|
+
}
|
|
731
|
+
const systemTokens = charsToTokens(contextWeight.systemPrompt.chars);
|
|
732
|
+
const skillsTokens = charsToTokens(contextWeight.skills.promptChars);
|
|
733
|
+
const toolsTokens = charsToTokens(
|
|
734
|
+
contextWeight.tools.listChars + contextWeight.tools.schemaChars,
|
|
735
|
+
);
|
|
736
|
+
const filesTokens = charsToTokens(
|
|
737
|
+
contextWeight.injectedWorkspaceFiles.reduce((sum, f) => sum + f.injectedChars, 0),
|
|
738
|
+
);
|
|
739
|
+
const totalContextTokens = systemTokens + skillsTokens + toolsTokens + filesTokens;
|
|
740
|
+
|
|
741
|
+
let contextPct = "";
|
|
742
|
+
if (usage && usage.totalTokens > 0) {
|
|
743
|
+
const inputTokens = usage.input + usage.cacheRead;
|
|
744
|
+
if (inputTokens > 0) {
|
|
745
|
+
contextPct = `~${Math.min((totalContextTokens / inputTokens) * 100, 100).toFixed(0)}% of input`;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
const skillsList = contextWeight.skills.entries.toSorted((a, b) => b.blockChars - a.blockChars);
|
|
750
|
+
const toolsList = contextWeight.tools.entries.toSorted(
|
|
751
|
+
(a, b) => b.summaryChars + b.schemaChars - (a.summaryChars + a.schemaChars),
|
|
752
|
+
);
|
|
753
|
+
const filesList = contextWeight.injectedWorkspaceFiles.toSorted(
|
|
754
|
+
(a, b) => b.injectedChars - a.injectedChars,
|
|
755
|
+
);
|
|
756
|
+
const defaultLimit = 4;
|
|
757
|
+
const showAll = expanded;
|
|
758
|
+
const skillsTop = showAll ? skillsList : skillsList.slice(0, defaultLimit);
|
|
759
|
+
const toolsTop = showAll ? toolsList : toolsList.slice(0, defaultLimit);
|
|
760
|
+
const filesTop = showAll ? filesList : filesList.slice(0, defaultLimit);
|
|
761
|
+
const hasMore =
|
|
762
|
+
skillsList.length > defaultLimit ||
|
|
763
|
+
toolsList.length > defaultLimit ||
|
|
764
|
+
filesList.length > defaultLimit;
|
|
765
|
+
|
|
766
|
+
return html`
|
|
767
|
+
<div class="context-details-panel">
|
|
768
|
+
<div class="context-breakdown-header">
|
|
769
|
+
<div class="card-title" style="font-size: 12px; color: var(--text);">System Prompt Breakdown</div>
|
|
770
|
+
${
|
|
771
|
+
hasMore
|
|
772
|
+
? html`<button class="context-expand-btn" @click=${onToggleExpanded}>
|
|
773
|
+
${showAll ? "Collapse" : "Expand all"}
|
|
774
|
+
</button>`
|
|
775
|
+
: nothing
|
|
776
|
+
}
|
|
777
|
+
</div>
|
|
778
|
+
<p class="context-weight-desc">
|
|
779
|
+
${contextPct || "Base context per message"}
|
|
780
|
+
</p>
|
|
781
|
+
<div class="context-stacked-bar">
|
|
782
|
+
<div class="context-segment system" style="width: ${pct(systemTokens, totalContextTokens).toFixed(1)}%" title="System: ~${formatTokens(systemTokens)}"></div>
|
|
783
|
+
<div class="context-segment skills" style="width: ${pct(skillsTokens, totalContextTokens).toFixed(1)}%" title="Skills: ~${formatTokens(skillsTokens)}"></div>
|
|
784
|
+
<div class="context-segment tools" style="width: ${pct(toolsTokens, totalContextTokens).toFixed(1)}%" title="Tools: ~${formatTokens(toolsTokens)}"></div>
|
|
785
|
+
<div class="context-segment files" style="width: ${pct(filesTokens, totalContextTokens).toFixed(1)}%" title="Files: ~${formatTokens(filesTokens)}"></div>
|
|
786
|
+
</div>
|
|
787
|
+
<div class="context-legend">
|
|
788
|
+
<span class="legend-item"><span class="legend-dot system"></span>Sys ~${formatTokens(systemTokens)}</span>
|
|
789
|
+
<span class="legend-item"><span class="legend-dot skills"></span>Skills ~${formatTokens(skillsTokens)}</span>
|
|
790
|
+
<span class="legend-item"><span class="legend-dot tools"></span>Tools ~${formatTokens(toolsTokens)}</span>
|
|
791
|
+
<span class="legend-item"><span class="legend-dot files"></span>Files ~${formatTokens(filesTokens)}</span>
|
|
792
|
+
</div>
|
|
793
|
+
<div class="context-total">Total: ~${formatTokens(totalContextTokens)}</div>
|
|
794
|
+
<div class="context-breakdown-grid">
|
|
795
|
+
${
|
|
796
|
+
skillsList.length > 0
|
|
797
|
+
? (() => {
|
|
798
|
+
const more = skillsList.length - skillsTop.length;
|
|
799
|
+
return html`
|
|
800
|
+
<div class="context-breakdown-card">
|
|
801
|
+
<div class="context-breakdown-title">Skills (${skillsList.length})</div>
|
|
802
|
+
<div class="context-breakdown-list">
|
|
803
|
+
${skillsTop.map(
|
|
804
|
+
(s) => html`
|
|
805
|
+
<div class="context-breakdown-item">
|
|
806
|
+
<span class="mono">${s.name}</span>
|
|
807
|
+
<span class="muted">~${formatTokens(charsToTokens(s.blockChars))}</span>
|
|
808
|
+
</div>
|
|
809
|
+
`,
|
|
810
|
+
)}
|
|
811
|
+
</div>
|
|
812
|
+
${
|
|
813
|
+
more > 0
|
|
814
|
+
? html`<div class="context-breakdown-more">+${more} more</div>`
|
|
815
|
+
: nothing
|
|
816
|
+
}
|
|
817
|
+
</div>
|
|
818
|
+
`;
|
|
819
|
+
})()
|
|
820
|
+
: nothing
|
|
821
|
+
}
|
|
822
|
+
${
|
|
823
|
+
toolsList.length > 0
|
|
824
|
+
? (() => {
|
|
825
|
+
const more = toolsList.length - toolsTop.length;
|
|
826
|
+
return html`
|
|
827
|
+
<div class="context-breakdown-card">
|
|
828
|
+
<div class="context-breakdown-title">Tools (${toolsList.length})</div>
|
|
829
|
+
<div class="context-breakdown-list">
|
|
830
|
+
${toolsTop.map(
|
|
831
|
+
(t) => html`
|
|
832
|
+
<div class="context-breakdown-item">
|
|
833
|
+
<span class="mono">${t.name}</span>
|
|
834
|
+
<span class="muted">~${formatTokens(charsToTokens(t.summaryChars + t.schemaChars))}</span>
|
|
835
|
+
</div>
|
|
836
|
+
`,
|
|
837
|
+
)}
|
|
838
|
+
</div>
|
|
839
|
+
${
|
|
840
|
+
more > 0
|
|
841
|
+
? html`<div class="context-breakdown-more">+${more} more</div>`
|
|
842
|
+
: nothing
|
|
843
|
+
}
|
|
844
|
+
</div>
|
|
845
|
+
`;
|
|
846
|
+
})()
|
|
847
|
+
: nothing
|
|
848
|
+
}
|
|
849
|
+
${
|
|
850
|
+
filesList.length > 0
|
|
851
|
+
? (() => {
|
|
852
|
+
const more = filesList.length - filesTop.length;
|
|
853
|
+
return html`
|
|
854
|
+
<div class="context-breakdown-card">
|
|
855
|
+
<div class="context-breakdown-title">Files (${filesList.length})</div>
|
|
856
|
+
<div class="context-breakdown-list">
|
|
857
|
+
${filesTop.map(
|
|
858
|
+
(f) => html`
|
|
859
|
+
<div class="context-breakdown-item">
|
|
860
|
+
<span class="mono">${f.name}</span>
|
|
861
|
+
<span class="muted">~${formatTokens(charsToTokens(f.injectedChars))}</span>
|
|
862
|
+
</div>
|
|
863
|
+
`,
|
|
864
|
+
)}
|
|
865
|
+
</div>
|
|
866
|
+
${
|
|
867
|
+
more > 0
|
|
868
|
+
? html`<div class="context-breakdown-more">+${more} more</div>`
|
|
869
|
+
: nothing
|
|
870
|
+
}
|
|
871
|
+
</div>
|
|
872
|
+
`;
|
|
873
|
+
})()
|
|
874
|
+
: nothing
|
|
875
|
+
}
|
|
876
|
+
</div>
|
|
877
|
+
</div>
|
|
878
|
+
`;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function renderSessionLogsCompact(
|
|
882
|
+
logs: SessionLogEntry[] | null,
|
|
883
|
+
loading: boolean,
|
|
884
|
+
expandedAll: boolean,
|
|
885
|
+
onToggleExpandedAll: () => void,
|
|
886
|
+
filters: {
|
|
887
|
+
roles: SessionLogRole[];
|
|
888
|
+
tools: string[];
|
|
889
|
+
hasTools: boolean;
|
|
890
|
+
query: string;
|
|
891
|
+
},
|
|
892
|
+
onFilterRolesChange: (next: SessionLogRole[]) => void,
|
|
893
|
+
onFilterToolsChange: (next: string[]) => void,
|
|
894
|
+
onFilterHasToolsChange: (next: boolean) => void,
|
|
895
|
+
onFilterQueryChange: (next: string) => void,
|
|
896
|
+
onFilterClear: () => void,
|
|
897
|
+
cursorStart?: number | null,
|
|
898
|
+
cursorEnd?: number | null,
|
|
899
|
+
) {
|
|
900
|
+
if (loading) {
|
|
901
|
+
return html`
|
|
902
|
+
<div class="session-logs-compact">
|
|
903
|
+
<div class="session-logs-header">Conversation</div>
|
|
904
|
+
<div class="muted" style="padding: 20px; text-align: center">Loading...</div>
|
|
905
|
+
</div>
|
|
906
|
+
`;
|
|
907
|
+
}
|
|
908
|
+
if (!logs || logs.length === 0) {
|
|
909
|
+
return html`
|
|
910
|
+
<div class="session-logs-compact">
|
|
911
|
+
<div class="session-logs-header">Conversation</div>
|
|
912
|
+
<div class="muted" style="padding: 20px; text-align: center">No messages</div>
|
|
913
|
+
</div>
|
|
914
|
+
`;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const normalizedQuery = filters.query.trim().toLowerCase();
|
|
918
|
+
const entries = logs.map((log) => {
|
|
919
|
+
const toolInfo = parseToolSummary(log.content);
|
|
920
|
+
const cleanContent = toolInfo.cleanContent || log.content;
|
|
921
|
+
return { log, toolInfo, cleanContent };
|
|
922
|
+
});
|
|
923
|
+
const toolOptions = Array.from(
|
|
924
|
+
new Set(entries.flatMap((entry) => entry.toolInfo.tools.map(([name]) => name))),
|
|
925
|
+
).toSorted((a, b) => a.localeCompare(b));
|
|
926
|
+
const filteredEntries = entries.filter((entry) => {
|
|
927
|
+
// Filter by cursor timeline range (only if logs cover the range)
|
|
928
|
+
if (cursorStart != null && cursorEnd != null) {
|
|
929
|
+
const ts = entry.log.timestamp;
|
|
930
|
+
if (ts > 0) {
|
|
931
|
+
const lo = Math.min(cursorStart, cursorEnd);
|
|
932
|
+
const hi = Math.max(cursorStart, cursorEnd);
|
|
933
|
+
const normalizedTs = normalizeLogTimestamp(ts);
|
|
934
|
+
if (normalizedTs < lo || normalizedTs > hi) {
|
|
935
|
+
return false;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
if (filters.roles.length > 0 && !filters.roles.includes(entry.log.role)) {
|
|
940
|
+
return false;
|
|
941
|
+
}
|
|
942
|
+
if (filters.hasTools && entry.toolInfo.tools.length === 0) {
|
|
943
|
+
return false;
|
|
944
|
+
}
|
|
945
|
+
if (filters.tools.length > 0) {
|
|
946
|
+
const matchesTool = entry.toolInfo.tools.some(([name]) => filters.tools.includes(name));
|
|
947
|
+
if (!matchesTool) {
|
|
948
|
+
return false;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
if (normalizedQuery) {
|
|
952
|
+
const haystack = entry.cleanContent.toLowerCase();
|
|
953
|
+
if (!haystack.includes(normalizedQuery)) {
|
|
954
|
+
return false;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
return true;
|
|
958
|
+
});
|
|
959
|
+
const hasActiveFilters =
|
|
960
|
+
filters.roles.length > 0 || filters.tools.length > 0 || filters.hasTools || normalizedQuery;
|
|
961
|
+
const hasCursorFilter = cursorStart != null && cursorEnd != null;
|
|
962
|
+
const displayedCount =
|
|
963
|
+
hasActiveFilters || hasCursorFilter
|
|
964
|
+
? `${filteredEntries.length} of ${logs.length} ${hasCursorFilter ? "(timeline filtered)" : ""}`
|
|
965
|
+
: `${logs.length}`;
|
|
966
|
+
|
|
967
|
+
const roleSelected = new Set(filters.roles);
|
|
968
|
+
const toolSelected = new Set(filters.tools);
|
|
969
|
+
|
|
970
|
+
return html`
|
|
971
|
+
<div class="session-logs-compact">
|
|
972
|
+
<div class="session-logs-header">
|
|
973
|
+
<span>Conversation <span style="font-weight: normal; color: var(--muted);">(${displayedCount} messages)</span></span>
|
|
974
|
+
<button class="btn btn-sm usage-action-btn usage-secondary-btn" @click=${onToggleExpandedAll}>
|
|
975
|
+
${expandedAll ? "Collapse All" : "Expand All"}
|
|
976
|
+
</button>
|
|
977
|
+
</div>
|
|
978
|
+
<div class="usage-filters-inline" style="margin: 10px 12px;">
|
|
979
|
+
<select
|
|
980
|
+
multiple
|
|
981
|
+
size="4"
|
|
982
|
+
@change=${(event: Event) =>
|
|
983
|
+
onFilterRolesChange(
|
|
984
|
+
Array.from((event.target as HTMLSelectElement).selectedOptions).map(
|
|
985
|
+
(option) => option.value as SessionLogRole,
|
|
986
|
+
),
|
|
987
|
+
)}
|
|
988
|
+
>
|
|
989
|
+
<option value="user" ?selected=${roleSelected.has("user")}>User</option>
|
|
990
|
+
<option value="assistant" ?selected=${roleSelected.has("assistant")}>Assistant</option>
|
|
991
|
+
<option value="tool" ?selected=${roleSelected.has("tool")}>Tool</option>
|
|
992
|
+
<option value="toolResult" ?selected=${roleSelected.has("toolResult")}>Tool result</option>
|
|
993
|
+
</select>
|
|
994
|
+
<select
|
|
995
|
+
multiple
|
|
996
|
+
size="4"
|
|
997
|
+
@change=${(event: Event) =>
|
|
998
|
+
onFilterToolsChange(
|
|
999
|
+
Array.from((event.target as HTMLSelectElement).selectedOptions).map(
|
|
1000
|
+
(option) => option.value,
|
|
1001
|
+
),
|
|
1002
|
+
)}
|
|
1003
|
+
>
|
|
1004
|
+
${toolOptions.map(
|
|
1005
|
+
(tool) =>
|
|
1006
|
+
html`<option value=${tool} ?selected=${toolSelected.has(tool)}>${tool}</option>`,
|
|
1007
|
+
)}
|
|
1008
|
+
</select>
|
|
1009
|
+
<label class="usage-filters-inline" style="gap: 6px;">
|
|
1010
|
+
<input
|
|
1011
|
+
type="checkbox"
|
|
1012
|
+
.checked=${filters.hasTools}
|
|
1013
|
+
@change=${(event: Event) =>
|
|
1014
|
+
onFilterHasToolsChange((event.target as HTMLInputElement).checked)}
|
|
1015
|
+
/>
|
|
1016
|
+
Has tools
|
|
1017
|
+
</label>
|
|
1018
|
+
<input
|
|
1019
|
+
type="text"
|
|
1020
|
+
placeholder="Search conversation"
|
|
1021
|
+
.value=${filters.query}
|
|
1022
|
+
@input=${(event: Event) => onFilterQueryChange((event.target as HTMLInputElement).value)}
|
|
1023
|
+
/>
|
|
1024
|
+
<button class="btn btn-sm usage-action-btn usage-secondary-btn" @click=${onFilterClear}>
|
|
1025
|
+
Clear
|
|
1026
|
+
</button>
|
|
1027
|
+
</div>
|
|
1028
|
+
<div class="session-logs-list">
|
|
1029
|
+
${filteredEntries.map((entry) => {
|
|
1030
|
+
const { log, toolInfo, cleanContent } = entry;
|
|
1031
|
+
const roleClass = log.role === "user" ? "user" : "assistant";
|
|
1032
|
+
const roleLabel =
|
|
1033
|
+
log.role === "user" ? "You" : log.role === "assistant" ? "Assistant" : "Tool";
|
|
1034
|
+
return html`
|
|
1035
|
+
<div class="session-log-entry ${roleClass}">
|
|
1036
|
+
<div class="session-log-meta">
|
|
1037
|
+
<span class="session-log-role">${roleLabel}</span>
|
|
1038
|
+
<span>${new Date(log.timestamp).toLocaleString()}</span>
|
|
1039
|
+
${log.tokens ? html`<span>${formatTokens(log.tokens)}</span>` : nothing}
|
|
1040
|
+
</div>
|
|
1041
|
+
<div class="session-log-content">${cleanContent}</div>
|
|
1042
|
+
${
|
|
1043
|
+
toolInfo.tools.length > 0
|
|
1044
|
+
? html`
|
|
1045
|
+
<details class="session-log-tools" ?open=${expandedAll}>
|
|
1046
|
+
<summary>${toolInfo.summary}</summary>
|
|
1047
|
+
<div class="session-log-tools-list">
|
|
1048
|
+
${toolInfo.tools.map(
|
|
1049
|
+
([name, count]) => html`
|
|
1050
|
+
<span class="session-log-tools-pill">${name} × ${count}</span>
|
|
1051
|
+
`,
|
|
1052
|
+
)}
|
|
1053
|
+
</div>
|
|
1054
|
+
</details>
|
|
1055
|
+
`
|
|
1056
|
+
: nothing
|
|
1057
|
+
}
|
|
1058
|
+
</div>
|
|
1059
|
+
`;
|
|
1060
|
+
})}
|
|
1061
|
+
${
|
|
1062
|
+
filteredEntries.length === 0
|
|
1063
|
+
? html`
|
|
1064
|
+
<div class="muted" style="padding: 12px">No messages match the filters.</div>
|
|
1065
|
+
`
|
|
1066
|
+
: nothing
|
|
1067
|
+
}
|
|
1068
|
+
</div>
|
|
1069
|
+
</div>
|
|
1070
|
+
`;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
export {
|
|
1074
|
+
computeFilteredUsage,
|
|
1075
|
+
renderContextPanel,
|
|
1076
|
+
renderEmptyDetailState,
|
|
1077
|
+
renderSessionDetailPanel,
|
|
1078
|
+
renderSessionLogsCompact,
|
|
1079
|
+
renderSessionSummary,
|
|
1080
|
+
renderTimeSeriesCompact,
|
|
1081
|
+
CHART_BAR_WIDTH_RATIO,
|
|
1082
|
+
CHART_MAX_BAR_WIDTH,
|
|
1083
|
+
};
|