@cardstack/boxel-cli 0.11.0-unstable.1 → 0.11.0-unstable.3
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/bundled-realms/pretui/components/agent-mark.gts +98 -0
- package/bundled-realms/pretui/components/agent-question.gts +340 -37
- package/bundled-realms/pretui/components/answer-footer.gts +249 -0
- package/bundled-realms/pretui/components/card-embed.gts +523 -0
- package/bundled-realms/pretui/components/card-pills.gts +725 -0
- package/bundled-realms/pretui/components/code-block.gts +87 -45
- package/bundled-realms/pretui/components/collapsed-turn.gts +133 -12
- package/bundled-realms/pretui/components/compaction-chip.gts +81 -67
- package/bundled-realms/pretui/components/composer-cap.gts +516 -0
- package/bundled-realms/pretui/components/context-ring.gts +108 -0
- package/bundled-realms/pretui/components/diff-block.gts +1002 -31
- package/bundled-realms/pretui/components/diff-stat.gts +102 -0
- package/bundled-realms/pretui/components/jump-chip.gts +86 -53
- package/bundled-realms/pretui/components/markdown-stream.gts +799 -0
- package/bundled-realms/pretui/components/markdown-surface.gts +71 -0
- package/bundled-realms/pretui/components/plan-mark.gts +147 -0
- package/bundled-realms/pretui/components/prose.gts +185 -8
- package/bundled-realms/pretui/components/prose.md +6 -8
- package/bundled-realms/pretui/components/quick-look.gts +377 -0
- package/bundled-realms/pretui/components/reactions.gts +136 -0
- package/bundled-realms/pretui/components/recommendation.gts +67 -36
- package/bundled-realms/pretui/components/result-card.gts +58 -22
- package/bundled-realms/pretui/components/segmented-control.gts +13 -8
- package/bundled-realms/pretui/components/session-prep.gts +12 -11
- package/bundled-realms/pretui/components/sliding-highlight.gts +8 -3
- package/bundled-realms/pretui/components/speaker-label.gts +132 -0
- package/bundled-realms/pretui/components/state-glyph.gts +121 -0
- package/bundled-realms/pretui/components/step-list.gts +62 -60
- package/bundled-realms/pretui/components/stick-to-bottom.gts +1150 -0
- package/bundled-realms/pretui/components/system-line.gts +143 -0
- package/bundled-realms/pretui/components/task-checklist.gts +184 -0
- package/bundled-realms/pretui/components/thinking.gts +63 -94
- package/bundled-realms/pretui/components/tool-change-table.gts +285 -0
- package/bundled-realms/pretui/components/tool-gate.gts +462 -0
- package/bundled-realms/pretui/components/tool-group.gts +920 -0
- package/bundled-realms/pretui/components/tool-icon.ts +64 -0
- package/bundled-realms/pretui/components/tool-meta.gts +82 -0
- package/bundled-realms/pretui/components/tool-plan.gts +197 -0
- package/bundled-realms/pretui/components/tool-row.gts +2087 -0
- package/bundled-realms/pretui/components/turn-changes.gts +227 -0
- package/bundled-realms/pretui/components/user-bubble.gts +575 -0
- package/bundled-realms/pretui/components/work-item.gts +185 -48
- package/bundled-realms/pretui/docs/scroll-anchoring-protocol.md +136 -0
- package/bundled-realms/pretui/internal/markdown-projection.ts +438 -0
- package/bundled-realms/pretui/internal/mount-queue.ts +79 -0
- package/bundled-realms/pretui/internal/tool-phrases.ts +168 -0
- package/bundled-realms/pretui/package.json +2 -1
- package/bundled-test-harness/assets/{ai-module-creation-test-beRNwH-e.js → ai-module-creation-test-B6FQKqIl.js} +1 -1
- package/bundled-test-harness/assets/{app-QAuw0l8v.js → app-KpPqdyX3.js} +4 -4
- package/bundled-test-harness/assets/{binary-upload-dedupe-test-bI8T0QLl.js → binary-upload-dedupe-test-DN00y1tM.js} +1 -1
- package/bundled-test-harness/assets/{can-invoke-test-DVaU_yRT.js → can-invoke-test-Bl-EUFM3.js} +1 -1
- package/bundled-test-harness/assets/{card-chooser-paging-test-DnxMnXKR.js → card-chooser-paging-test-BNuZRHX3.js} +1 -1
- package/bundled-test-harness/assets/{card-chooser-test-DUSnLr-u.js → card-chooser-test-wKfPOOT4.js} +1 -1
- package/bundled-test-harness/assets/{card-def-field-def-relationships-test-GBUr3tFK.js → card-def-field-def-relationships-test-CduQ6zan.js} +1 -1
- package/bundled-test-harness/assets/{card-delete-test-CQveO1VC.js → card-delete-test-DKxolj5o.js} +1 -1
- package/bundled-test-harness/assets/{check-correctness-test-D2M2p66N.js → check-correctness-test-B-kgHztp.js} +1 -1
- package/bundled-test-harness/assets/choreo-BEHW-NOc.js +2 -0
- package/bundled-test-harness/assets/{choreo-CJfBb_EY.js → choreo-C2cpCfhA.js} +164 -13
- package/bundled-test-harness/assets/{choreo-feature-reel-test-D2OrygZv.js → choreo-feature-reel-test-CPI_6nkT.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-w07xqtO-.js → choreo-gallery-BxA9cyzF.js} +3 -3
- package/bundled-test-harness/assets/{choreo-gallery-build-order-test-yQg4JER6.js → choreo-gallery-build-order-test-CLVoseAJ.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-compositor-test-C2THCXJN.js → choreo-gallery-compositor-test-BpT5Ia6B.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-cover-test-D4T_Tk4s.js → choreo-gallery-cover-test-DL_fdSsh.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-crossing-stage-test-tHUPJ-Hy.js → choreo-gallery-crossing-stage-test-BcKHhpOK.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-demos-alive-test-BOtBOnEZ.js → choreo-gallery-demos-alive-test-KufLhXG8.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-drift-test-D_6U1Z2c.js → choreo-gallery-drift-test-Cx58rYIA.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-escort-test-DDBU0n4C.js → choreo-gallery-escort-test-Wz-emmID.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-films-test-CgW1NSdr.js → choreo-gallery-films-test-BFrVs5WW.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-filter-test-DUIsNwdO.js → choreo-gallery-filter-test-DCKQkhwe.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-fold-test-Chzlxvdr.js → choreo-gallery-fold-test-UtpDgpG9.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-guide-pages-test-DOWnqALT.js → choreo-gallery-guide-pages-test-rXDU9JLM.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-guides-test-Dra2J4LJ.js → choreo-gallery-guides-test-DfVFdIuU.js} +3 -3
- package/bundled-test-harness/assets/{choreo-gallery-hang-test-zzn1eiB-.js → choreo-gallery-hang-test-Cj7MbkFf.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-highlight-test-CW99hncQ.js → choreo-gallery-highlight-test-D6CF3IYZ.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-inline-edit-test-CYFm1kUz.js → choreo-gallery-inline-edit-test-LfHWsbqc.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-interruption-test-CYIBrKV9.js → choreo-gallery-interruption-test-CvQQAgTp.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-lone-stack-test-DL8R3SG7.js → choreo-gallery-lone-stack-test-D7Zci7fm.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-navigation-test-Dcv04kDx.js → choreo-gallery-navigation-test-DB1EnBEW.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-playhead-transport-test-Cfz7GsXl.js → choreo-gallery-playhead-transport-test-CtBlc6AJ.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-poplayout-subtree-test-CwoklBkW.js → choreo-gallery-poplayout-subtree-test-BQbmjsvI.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-presence-modes-test-BDCIZzJ0.js → choreo-gallery-presence-modes-test-CIdXVG1K.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-presentation-test-DlyiHwFf.js → choreo-gallery-presentation-test-C_rPFp29.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-reduced-motion-test-DVz5SKNR.js → choreo-gallery-reduced-motion-test-CH5w8Lw-.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-sheet-standalone-test-CpD_-BvS.js → choreo-gallery-sheet-standalone-test-CDgI4mGX.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-sheet-test-BjvAKHDL.js → choreo-gallery-sheet-test-5lsFX7Rk.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-site-test-oq_7y_PN.js → choreo-gallery-site-test-DbMa2xeC.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-stage-Bzr4_8Xd.js → choreo-gallery-stage-b3Sk86hp.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-stage-keyboard-test-a83d2euJ.js → choreo-gallery-stage-keyboard-test-ds6fT5Eg.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-stages-test-C9Sl1kH6.js → choreo-gallery-stages-test-ahioKCct.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-subdivision-test-BVVnkOwk.js → choreo-gallery-subdivision-test-g7anTDhT.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-table-plan-test-B0YdYEA0.js → choreo-gallery-table-plan-test-Bozc4uUf.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-theater-test-BLA147xw.js → choreo-gallery-theater-test-kE535d-b.js} +3 -3
- package/bundled-test-harness/assets/{choreo-gallery-tuning-test-DSXiNzCh.js → choreo-gallery-tuning-test-6rHkQBnZ.js} +1 -1
- package/bundled-test-harness/assets/{choreo-gallery-widget-room-test-BI574PdO.js → choreo-gallery-widget-room-test-0Vq-uTo1.js} +2 -2
- package/bundled-test-harness/assets/{choreo-gallery-wires-test-B6Mz2WZl.js → choreo-gallery-wires-test-CdtnAWYO.js} +1 -1
- package/bundled-test-harness/assets/clip-BLfuHi8g.js +2 -0
- package/bundled-test-harness/assets/{clip-C_pXjVvY.js → clip-q1QE5f73.js} +1 -1
- package/bundled-test-harness/assets/{codeblocks-test-AdolhdCA.js → codeblocks-test-BDD7qK_V.js} +1 -1
- package/bundled-test-harness/assets/{compaction-test-DvyAQq56.js → compaction-test-BweolkhG.js} +1 -1
- package/bundled-test-harness/assets/{container-DIF_9lZ9.css → container-B5B6u-xm.css} +29 -22
- package/bundled-test-harness/assets/{container-DvvqmNpg.js → container-BnJjIM6J.js} +9 -6
- package/bundled-test-harness/assets/{create-listing-modal-test-D95JfQab.js → create-listing-modal-test-FvcAKpIa.js} +1 -1
- package/bundled-test-harness/assets/{debug-message-test-BcNtj7Wr.js → debug-message-test-CJ18Evt7.js} +1 -1
- package/bundled-test-harness/assets/{externals-6ZWftYIb.js → externals-BWjhmCD4.js} +12 -12
- package/bundled-test-harness/assets/{fallback-banner-live-delete-test-5lnN1pv2.js → fallback-banner-live-delete-test-C2H3BrT-.js} +1 -1
- package/bundled-test-harness/assets/{fallback-banner-test-DFb_m_Kq.js → fallback-banner-test-C10aYjkj.js} +1 -1
- package/bundled-test-harness/assets/{fallback-models-test-p3YIwj1L.js → fallback-models-test-BxoPiaGF.js} +1 -1
- package/bundled-test-harness/assets/{file-attachment-test-CmyjNWRR.js → file-attachment-test-DeKyzYaz.js} +1 -1
- package/bundled-test-harness/assets/film-B68PjRlr.js +2 -0
- package/bundled-test-harness/assets/{film-D7vtJO5q.js → film-CeUpnHN_.js} +6 -6
- package/bundled-test-harness/assets/{film-DN0A8JHx.js → film-DBZqyqW4.js} +6 -6
- package/bundled-test-harness/assets/{general-test-CX-TFDuX.js → general-test-BewzrpoB.js} +1 -1
- package/bundled-test-harness/assets/{glimmer-motion-and-choreo-in-cards-test-BHte2t9G.js → glimmer-motion-and-choreo-in-cards-test-HA-INN3d.js} +14 -14
- package/bundled-test-harness/assets/{glimmer-motion-and-choreo-test-BPNVeIio.js → glimmer-motion-and-choreo-test-BZ0ZuuAw.js} +3 -3
- package/bundled-test-harness/assets/{index-query-engine-test-Dz7Q4Xmk.js → index-query-engine-test-BtI8Y2LR.js} +1 -1
- package/bundled-test-harness/assets/{instance-filter-matcher-test-BoLU3bVg.js → instance-filter-matcher-test-BHXHGHjE.js} +1 -1
- package/bundled-test-harness/assets/{main-Dlgp8c7v.js → main-CqAHZ2kK.js} +1 -1
- package/bundled-test-harness/assets/{message-tool-test-CfjRSJvt.js → message-tool-test-NMaPJFsd.js} +1 -1
- package/bundled-test-harness/assets/{model-cost-tier-test-mZG84Uso.js → model-cost-tier-test-BgbfN2LH.js} +1 -1
- package/bundled-test-harness/assets/{operation-lowering-test-B0KpI1xu.js → operation-lowering-test-C4IaOjsA.js} +1 -1
- package/bundled-test-harness/assets/{operator-mode-card-chooser-test-KWS8lq16.js → operator-mode-card-chooser-test-Bwe47Rn7.js} +1 -1
- package/bundled-test-harness/assets/{operator-mode-links-test-CViFt0Ws.js → operator-mode-links-test-BU4jkC-B.js} +1 -1
- package/bundled-test-harness/assets/{operator-mode-test-D0gZM2yw.js → operator-mode-test-Bh0rGM9z.js} +1 -1
- package/bundled-test-harness/assets/{operator-mode-ui-test-CEvHJ9DS.js → operator-mode-ui-test-whgUw0ju.js} +1 -1
- package/bundled-test-harness/assets/{overlay-menu-items-test-Pw80RXsr.js → overlay-menu-items-test-Cwn1vr8q.js} +1 -1
- package/bundled-test-harness/assets/{overlays-BnOM360u.js → overlays-BOzLbVDK.js} +1 -1
- package/bundled-test-harness/assets/{overlays-kBVfa4i8.js → overlays-C_PuGDGh.js} +1 -1
- package/bundled-test-harness/assets/{past-sessions-test-CW0-Bjav.js → past-sessions-test-Bh24N-Kz.js} +1 -1
- package/bundled-test-harness/assets/{permission-checking-realm-test-D5uXQypS.js → permission-checking-realm-test-DKnnAy0f.js} +1 -1
- package/bundled-test-harness/assets/{plate-CFvCDv2t.js → plate-BL7UtyKE.js} +1 -1
- package/bundled-test-harness/assets/plate-CqG3igCJ.js +2 -0
- package/bundled-test-harness/assets/player-BJfsgdSI.js +2 -0
- package/bundled-test-harness/assets/{player-DzuYjrTU.js → player-Da_CIsp_.js} +1 -1
- package/bundled-test-harness/assets/{realm-config-policy-standing-test-Btatheni.js → realm-config-policy-standing-test-DngdYypq.js} +1 -1
- package/bundled-test-harness/assets/{realm-config-routing-rule-edit-test-Dh5zW13o.js → realm-config-routing-rule-edit-test-_rkFU8SN.js} +1 -1
- package/bundled-test-harness/assets/{realm-config-settings-test-BJa8gv_C.js → realm-config-settings-test-CR9NH0Zn.js} +1 -1
- package/bundled-test-harness/assets/{reasoning-test-2Zp37cQB.js → reasoning-test-0lH10kRl.js} +1 -1
- package/bundled-test-harness/assets/{room-message-test-BsrON13e.js → room-message-test-C-PddE4Y.js} +1 -1
- package/bundled-test-harness/assets/{run-BMyvn_UL.js → run-D4n4LPcK.js} +96 -6
- package/bundled-test-harness/assets/{run-realm-code-BxktQf6R.js → run-realm-code-C8N7DtN-.js} +1 -1
- package/bundled-test-harness/assets/{run-realm-code-test-7grpTos-.js → run-realm-code-test-DBdp147q.js} +1 -1
- package/bundled-test-harness/assets/{schema-generation-test-CM9ggQji.js → schema-generation-test-CB1Hb7GQ.js} +1 -1
- package/bundled-test-harness/assets/{scrolling-test-BER00htB.js → scrolling-test-3PhVin8h.js} +1 -1
- package/bundled-test-harness/assets/{searchable-option-test-BBr5BPow.js → searchable-option-test-BAdOiL4U.js} +1 -1
- package/bundled-test-harness/assets/{sending-test-BJGobs2E.js → sending-test-KSmu1tSJ.js} +1 -1
- package/bundled-test-harness/assets/{skills-test-GPOsHRN6.js → skills-test-D-dlcFpd.js} +1 -1
- package/bundled-test-harness/assets/{src-Df148w_b.js → src-B_DEhPze.js} +4 -4
- package/bundled-test-harness/assets/{src-Cf2wv-Le.js → src-BoTgwqUA.js} +2 -2
- package/bundled-test-harness/assets/{store-test-QvvHimzH.js → store-test-CEW7xQjZ.js} +1 -1
- package/bundled-test-harness/assets/{test-support-rNHuieEc.js → test-support-FXDPpIl3.js} +1 -1
- package/bundled-test-harness/assets/{test-support-C6N_2X-l.js → test-support-VX6ukQNR.js} +1 -1
- package/bundled-test-harness/assets/{tests-CmsQ-Z_3.js → tests-QYqqa6nv.js} +84 -84
- package/bundled-test-harness/assets/{text-input-validator-test-CYYH2N0L.js → text-input-validator-test-D8iNOWep.js} +1 -1
- package/bundled-test-harness/assets/{titles-B1pTaIfP.js → titles-DFJj5O0E.js} +1 -1
- package/bundled-test-harness/assets/titles-xGlgu7ys.js +2 -0
- package/bundled-test-harness/assets/{to-device-streaming-test-Ctqa0bVp.js → to-device-streaming-test-trCX-505.js} +1 -1
- package/bundled-test-harness/assets/{token-usage-test-BQVbK3X0.js → token-usage-test-C6sULooH.js} +1 -1
- package/bundled-test-harness/assets/{tools-DU8Rc15s.js → tools-UJfD-_al.js} +1 -1
- package/bundled-test-harness/assets/{tools-test-B13yvEKE.js → tools-test-Brav31AV.js} +1 -1
- package/bundled-test-harness/assets/{worker-DHBEBihX.js → worker-9ph9_Lpv.js} +31 -26
- package/bundled-test-harness/index.html +8 -8
- package/bundled-test-harness/tests/index.html +8 -8
- package/bundled-types/choreo/beacons.ts +16 -4
- package/bundled-types/choreo/changeset.ts +25 -5
- package/bundled-types/choreo/choreo.gts +245 -3
- package/bundled-types/choreo/far.ts +39 -0
- package/bundled-types/choreo/index.ts +2 -0
- package/bundled-types/choreo/run.ts +104 -1
- package/bundled-types/host-app/lib/realm-runner/worker.ts +8 -51
- package/bundled-types/runtime-common/realm-runner-script.d.ts +3 -0
- package/package.json +1 -1
- package/bundled-test-harness/assets/choreo-B3Z9QhDH.js +0 -2
- package/bundled-test-harness/assets/clip-BjcXuTwN.js +0 -2
- package/bundled-test-harness/assets/film-DMDPP7ma.js +0 -2
- package/bundled-test-harness/assets/plate-66qnQ-M9.js +0 -2
- package/bundled-test-harness/assets/player-pygg1PwA.js +0 -2
- package/bundled-test-harness/assets/titles-Ccpcl-jq.js +0 -2
|
@@ -0,0 +1,920 @@
|
|
|
1
|
+
// Pretui — ToolGroup: a run of consecutive tool calls folded into one receipt line ("Used 12 tools · Read, Patch fields and Search"), with a rolling count while the run grows, a header that stays in view while you read inside it, and merging of repetitive runs.
|
|
2
|
+
import Component from '@glimmer/component';
|
|
3
|
+
import { tracked } from '@glimmer/tracking';
|
|
4
|
+
import { on } from '@ember/modifier';
|
|
5
|
+
import { guidFor } from '@ember/object/internals';
|
|
6
|
+
import { cssNumber } from '../pretui-css';
|
|
7
|
+
import { htmlSafe } from '@ember/template';
|
|
8
|
+
import { modifier } from 'ember-modifier';
|
|
9
|
+
import type { SafeString } from '@ember/template';
|
|
10
|
+
import { Collapse } from '../internal/agentic-chat';
|
|
11
|
+
import { Odometer } from './odometer';
|
|
12
|
+
import { PlanMark } from './plan-mark';
|
|
13
|
+
import PointIcon from '@cardstack/boxel-icons/point';
|
|
14
|
+
import StackIcon from '@cardstack/boxel-icons/stack-2';
|
|
15
|
+
import { ToolRow, humanizeToolName } from './tool-row';
|
|
16
|
+
import type { ToolRenderers, ToolSource } from './tool-row';
|
|
17
|
+
import {
|
|
18
|
+
KIND_PHRASES,
|
|
19
|
+
joinPhrases,
|
|
20
|
+
listSeparators,
|
|
21
|
+
phraseRun,
|
|
22
|
+
} from '../internal/tool-phrases';
|
|
23
|
+
import type {
|
|
24
|
+
PhraseItem,
|
|
25
|
+
ToolKind,
|
|
26
|
+
ToolPhrase,
|
|
27
|
+
} from '../internal/tool-phrases';
|
|
28
|
+
import { workTone } from './work-item';
|
|
29
|
+
import type { WorkTone } from './work-item';
|
|
30
|
+
|
|
31
|
+
export interface ToolPart {
|
|
32
|
+
/** stable: the tool call id */
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
state: string;
|
|
36
|
+
subtitle?: string;
|
|
37
|
+
source?: ToolSource;
|
|
38
|
+
/** seconds */
|
|
39
|
+
duration?: number;
|
|
40
|
+
error?: string;
|
|
41
|
+
params?: unknown;
|
|
42
|
+
result?: unknown;
|
|
43
|
+
/** drives phrasing and merging. Default: the renderer's kind, else
|
|
44
|
+
* 'other' */
|
|
45
|
+
kind?: ToolKind;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** How the receipt reads: what the calls did ('kind', the default: "Read 3
|
|
49
|
+
* files and edited 1 file"), or which tools they used ('names': "Used 4
|
|
50
|
+
* tools · Read and Patch fields"). */
|
|
51
|
+
export type ToolGroupPhrasing = 'kind' | 'names';
|
|
52
|
+
|
|
53
|
+
export type ToolGroupMerge = 'none' | 'reads' | 'all';
|
|
54
|
+
|
|
55
|
+
/** One rendered row: a single call, or a merged run of reads or writes. */
|
|
56
|
+
export interface ToolGroupEntry {
|
|
57
|
+
/** the first call's id, so a run that grows keeps its key */
|
|
58
|
+
id: string;
|
|
59
|
+
merged: boolean;
|
|
60
|
+
kind: ToolKind;
|
|
61
|
+
parts: ToolPart[];
|
|
62
|
+
title: string;
|
|
63
|
+
subtitle?: string;
|
|
64
|
+
state: string;
|
|
65
|
+
duration?: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ToolGroupSummary {
|
|
69
|
+
/** what the calls did, grouped by phrase in order of first use */
|
|
70
|
+
phrases: PhraseItem[];
|
|
71
|
+
/** the phrases as one list, in English */
|
|
72
|
+
text: string;
|
|
73
|
+
count: number;
|
|
74
|
+
names: string[];
|
|
75
|
+
/** the names as one list, in English, truncated after @maxNames */
|
|
76
|
+
list: string;
|
|
77
|
+
failed: number;
|
|
78
|
+
tone: WorkTone;
|
|
79
|
+
pending: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface ToolGroupSignature {
|
|
83
|
+
Args: {
|
|
84
|
+
/** the calls in this group, in order */
|
|
85
|
+
tools: ToolPart[];
|
|
86
|
+
renderers?: ToolRenderers;
|
|
87
|
+
/** controlled disclosure */
|
|
88
|
+
open?: boolean;
|
|
89
|
+
/** uncontrolled initial disclosure. Default false */
|
|
90
|
+
defaultOpen?: boolean;
|
|
91
|
+
/** fires when the reader toggles the group */
|
|
92
|
+
onOpenChange?: (open: boolean) => void;
|
|
93
|
+
/** more calls may still arrive: the count rolls and a spinner shows */
|
|
94
|
+
pending?: boolean;
|
|
95
|
+
/** override the derived receipt text */
|
|
96
|
+
summary?: string;
|
|
97
|
+
/** 'kind' (default): what the calls did. 'names': which tools they used */
|
|
98
|
+
phrasing?: ToolGroupPhrasing;
|
|
99
|
+
/** whether a settled, closed group recedes. Default: when it is done and
|
|
100
|
+
* not pending. It only shows inside a container that sets
|
|
101
|
+
* --pretui-recede-opacity */
|
|
102
|
+
recede?: boolean;
|
|
103
|
+
/** while open, the header sticks inside the scroll container. Default true */
|
|
104
|
+
sticky?: boolean;
|
|
105
|
+
/** CSS length below which the header sticks. Default 0 */
|
|
106
|
+
stickyOffset?: string;
|
|
107
|
+
/** an open group shows about this many rows and scrolls the rest inside
|
|
108
|
+
* itself, so forty calls never push the transcript forty rows down.
|
|
109
|
+
* Default 8; 0 for no limit */
|
|
110
|
+
maxRows?: number;
|
|
111
|
+
/** merge consecutive reads ('reads'), and writes to distinct targets too
|
|
112
|
+
* ('all', the default), into one row */
|
|
113
|
+
merge?: ToolGroupMerge;
|
|
114
|
+
/** tool names listed before "N more". Default 4 */
|
|
115
|
+
maxNames?: number;
|
|
116
|
+
};
|
|
117
|
+
Blocks: {
|
|
118
|
+
tool: [ToolGroupEntry];
|
|
119
|
+
summary: [ToolGroupSummary];
|
|
120
|
+
};
|
|
121
|
+
Element: HTMLDivElement;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const TONE_RANK: Record<WorkTone, number> = {
|
|
125
|
+
attention: 4,
|
|
126
|
+
failed: 3,
|
|
127
|
+
running: 2,
|
|
128
|
+
idle: 1,
|
|
129
|
+
done: 0,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
function toneOf(state: string): WorkTone {
|
|
133
|
+
return state === 'streaming' ? 'running' : workTone(state);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function scrollParent(el: HTMLElement): HTMLElement | undefined {
|
|
137
|
+
let node = el.parentElement;
|
|
138
|
+
while (node) {
|
|
139
|
+
let overflowY = getComputedStyle(node).overflowY;
|
|
140
|
+
if (
|
|
141
|
+
(overflowY === 'auto' || overflowY === 'scroll') &&
|
|
142
|
+
node.scrollHeight > node.clientHeight
|
|
143
|
+
) {
|
|
144
|
+
return node;
|
|
145
|
+
}
|
|
146
|
+
node = node.parentElement;
|
|
147
|
+
}
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function firstPart(entry: ToolGroupEntry): ToolPart {
|
|
152
|
+
return entry.parts[0]!;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const registerElement = modifier(
|
|
156
|
+
(el: HTMLElement, [register]: [(el: HTMLElement) => void]) => {
|
|
157
|
+
register(el);
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
/** Reports the open state on install and on change, so the rows render
|
|
162
|
+
* after the first open even when a call needing the reader opened it. */
|
|
163
|
+
const noteOpen = modifier(
|
|
164
|
+
(_el: HTMLElement, [callback, open]: [(open: boolean) => void, boolean]) => {
|
|
165
|
+
callback(open);
|
|
166
|
+
},
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* A run of tool calls as one receipt.
|
|
171
|
+
*
|
|
172
|
+
* ```hbs
|
|
173
|
+
* <ToolGroup @tools={{group.tools}} @renderers={{this.toolRenderers}}
|
|
174
|
+
* @pending={{group.isLive}} @stickyOffset='var(--session-header-height)' />
|
|
175
|
+
* ```
|
|
176
|
+
*
|
|
177
|
+
* The attention state is part of the group's tone, so a call that needs
|
|
178
|
+
* the reader opens a collapsed group instead of hiding inside it; merging
|
|
179
|
+
* is a knob; and the rows inside a group that has never been opened are not
|
|
180
|
+
* rendered at all.
|
|
181
|
+
*/
|
|
182
|
+
export class ToolGroup extends Component<ToolGroupSignature> {
|
|
183
|
+
@tracked private userOpen: boolean | undefined;
|
|
184
|
+
/** rows render only once the group has been open: a closed group of forty
|
|
185
|
+
* calls costs one header */
|
|
186
|
+
@tracked private everOpened = false;
|
|
187
|
+
readonly bodyId = guidFor(this) + '-rows';
|
|
188
|
+
private root: HTMLElement | undefined;
|
|
189
|
+
|
|
190
|
+
get tools(): ToolPart[] {
|
|
191
|
+
return this.args.tools ?? [];
|
|
192
|
+
}
|
|
193
|
+
get maxNames(): number {
|
|
194
|
+
return Math.max(1, this.args.maxNames ?? 4);
|
|
195
|
+
}
|
|
196
|
+
get merge(): ToolGroupMerge {
|
|
197
|
+
return this.args.merge ?? 'all';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private titleOf(part: ToolPart): string {
|
|
201
|
+
return (
|
|
202
|
+
this.args.renderers?.[part.name]?.title ?? humanizeToolName(part.name)
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
private kindOf(part: ToolPart): ToolKind {
|
|
206
|
+
return part.kind ?? this.args.renderers?.[part.name]?.kind ?? 'other';
|
|
207
|
+
}
|
|
208
|
+
private phraseOf = (part: ToolPart): ToolPhrase =>
|
|
209
|
+
this.args.renderers?.[part.name]?.phrase ?? KIND_PHRASES[this.kindOf(part)];
|
|
210
|
+
|
|
211
|
+
get phrasing(): ToolGroupPhrasing {
|
|
212
|
+
return this.args.phrasing ?? 'kind';
|
|
213
|
+
}
|
|
214
|
+
/** The call the run is on now, whose phrase reads in the present tense:
|
|
215
|
+
* the newest call while the group is pending or it is still running. */
|
|
216
|
+
private get activeIndex(): number | undefined {
|
|
217
|
+
let last = this.tools.length - 1;
|
|
218
|
+
if (last < 0) {
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
let lastRunning = toneOf(this.tools[last]!.state) === 'running';
|
|
222
|
+
return this.args.pending || lastRunning ? last : undefined;
|
|
223
|
+
}
|
|
224
|
+
get phrases(): PhraseItem[] {
|
|
225
|
+
return phraseRun(this.tools, this.phraseOf, this.activeIndex);
|
|
226
|
+
}
|
|
227
|
+
/** each phrase with the literal separator that goes before it, so the
|
|
228
|
+
* template can give every count its own Odometer and still read as one
|
|
229
|
+
* grammatical list */
|
|
230
|
+
get phraseParts(): { item: PhraseItem; before: string }[] {
|
|
231
|
+
let phrases = this.phrases;
|
|
232
|
+
let separators = listSeparators(phrases.length);
|
|
233
|
+
return phrases.map((item, i) => ({ item, before: separators[i] ?? '' }));
|
|
234
|
+
}
|
|
235
|
+
get phraseText(): string {
|
|
236
|
+
return joinPhrases(this.phrases.map((p) => p.text));
|
|
237
|
+
}
|
|
238
|
+
get byKind(): boolean {
|
|
239
|
+
return this.phrasing === 'kind';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ── summary ────────────────────────────────────────────────────────────
|
|
243
|
+
|
|
244
|
+
get names(): string[] {
|
|
245
|
+
let seen = new Set<string>();
|
|
246
|
+
let names: string[] = [];
|
|
247
|
+
for (let part of this.tools) {
|
|
248
|
+
let title = this.titleOf(part);
|
|
249
|
+
if (!seen.has(title)) {
|
|
250
|
+
seen.add(title);
|
|
251
|
+
names.push(title);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return names;
|
|
255
|
+
}
|
|
256
|
+
get nameList(): string {
|
|
257
|
+
let names = this.names;
|
|
258
|
+
if (names.length <= this.maxNames) {
|
|
259
|
+
return joinPhrases(names);
|
|
260
|
+
}
|
|
261
|
+
let shown = names.slice(0, this.maxNames);
|
|
262
|
+
return joinPhrases([...shown, `${names.length - shown.length} more`]);
|
|
263
|
+
}
|
|
264
|
+
get failedCount(): number {
|
|
265
|
+
return this.tools.filter((part) => toneOf(part.state) === 'failed').length;
|
|
266
|
+
}
|
|
267
|
+
get tone(): WorkTone {
|
|
268
|
+
let worst: WorkTone = 'done';
|
|
269
|
+
for (let part of this.tools) {
|
|
270
|
+
let tone = toneOf(part.state);
|
|
271
|
+
if (TONE_RANK[tone] > TONE_RANK[worst]) {
|
|
272
|
+
worst = tone;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (this.args.pending && TONE_RANK[worst] < TONE_RANK.running) {
|
|
276
|
+
worst = 'running';
|
|
277
|
+
}
|
|
278
|
+
if (worst === 'idle') {
|
|
279
|
+
worst = 'done';
|
|
280
|
+
}
|
|
281
|
+
return worst;
|
|
282
|
+
}
|
|
283
|
+
get isRunning(): boolean {
|
|
284
|
+
return this.tone === 'running';
|
|
285
|
+
}
|
|
286
|
+
get isAttention(): boolean {
|
|
287
|
+
return this.tone === 'attention';
|
|
288
|
+
}
|
|
289
|
+
get countNoun(): string {
|
|
290
|
+
return this.tools.length === 1 ? 'tool' : 'tools';
|
|
291
|
+
}
|
|
292
|
+
get statusText(): string | undefined {
|
|
293
|
+
if (this.isAttention) {
|
|
294
|
+
return 'needs you';
|
|
295
|
+
}
|
|
296
|
+
if (this.isRunning) {
|
|
297
|
+
return 'running…';
|
|
298
|
+
}
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
/** The whole receipt in words: the header's accessible name, which does
|
|
302
|
+
* not depend on the rolling digits or the glyph. */
|
|
303
|
+
get receipt(): string {
|
|
304
|
+
if (this.args.summary) {
|
|
305
|
+
return this.args.summary;
|
|
306
|
+
}
|
|
307
|
+
let parts: string[] = [];
|
|
308
|
+
if (this.byKind) {
|
|
309
|
+
if (this.phraseText) {
|
|
310
|
+
parts.push(this.phraseText);
|
|
311
|
+
}
|
|
312
|
+
} else {
|
|
313
|
+
parts.push(`Used ${this.tools.length} ${this.countNoun}`);
|
|
314
|
+
if (this.nameList) {
|
|
315
|
+
parts.push(this.nameList);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (this.failedCount) {
|
|
319
|
+
parts.push(`${this.failedCount} failed`);
|
|
320
|
+
}
|
|
321
|
+
if (this.statusText) {
|
|
322
|
+
parts.push(this.isAttention ? 'needs you' : 'running');
|
|
323
|
+
}
|
|
324
|
+
return parts.join(' · ');
|
|
325
|
+
}
|
|
326
|
+
get summary(): ToolGroupSummary {
|
|
327
|
+
return {
|
|
328
|
+
phrases: this.phrases,
|
|
329
|
+
text: this.phraseText,
|
|
330
|
+
count: this.tools.length,
|
|
331
|
+
names: this.names,
|
|
332
|
+
list: this.nameList,
|
|
333
|
+
failed: this.failedCount,
|
|
334
|
+
tone: this.tone,
|
|
335
|
+
pending: Boolean(this.args.pending),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ── entries (merging) ──────────────────────────────────────────────────
|
|
340
|
+
|
|
341
|
+
get entries(): ToolGroupEntry[] {
|
|
342
|
+
let entries: ToolGroupEntry[] = [];
|
|
343
|
+
let run: ToolPart[] = [];
|
|
344
|
+
let runKind: 'read' | 'write' | undefined;
|
|
345
|
+
|
|
346
|
+
let flush = () => {
|
|
347
|
+
if (run.length === 1) {
|
|
348
|
+
entries.push(this.single(run[0]!));
|
|
349
|
+
} else if (run.length > 1 && runKind) {
|
|
350
|
+
entries.push(this.merged(run, runKind));
|
|
351
|
+
}
|
|
352
|
+
run = [];
|
|
353
|
+
runKind = undefined;
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
for (let part of this.tools) {
|
|
357
|
+
let kind = this.kindOf(part);
|
|
358
|
+
// a failed call keeps its own row for its error, and a call that
|
|
359
|
+
// needs the reader keeps its own for its gate and body: a merged row
|
|
360
|
+
// is a plain list with nothing to act on
|
|
361
|
+
let tone = toneOf(part.state);
|
|
362
|
+
let mergeable =
|
|
363
|
+
tone !== 'failed' &&
|
|
364
|
+
tone !== 'attention' &&
|
|
365
|
+
((kind === 'read' && this.merge !== 'none') ||
|
|
366
|
+
(kind === 'write' && this.merge === 'all'));
|
|
367
|
+
if (!mergeable) {
|
|
368
|
+
flush();
|
|
369
|
+
entries.push(this.single(part));
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
let k = kind as 'read' | 'write';
|
|
373
|
+
let repeatsTarget =
|
|
374
|
+
k === 'write' &&
|
|
375
|
+
part.subtitle !== undefined &&
|
|
376
|
+
run.some((p) => p.subtitle === part.subtitle);
|
|
377
|
+
if (runKind !== k || repeatsTarget) {
|
|
378
|
+
flush();
|
|
379
|
+
runKind = k;
|
|
380
|
+
}
|
|
381
|
+
run.push(part);
|
|
382
|
+
}
|
|
383
|
+
flush();
|
|
384
|
+
return entries;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private single(part: ToolPart): ToolGroupEntry {
|
|
388
|
+
return {
|
|
389
|
+
id: part.id,
|
|
390
|
+
merged: false,
|
|
391
|
+
kind: this.kindOf(part),
|
|
392
|
+
parts: [part],
|
|
393
|
+
title: this.titleOf(part),
|
|
394
|
+
subtitle: part.subtitle,
|
|
395
|
+
state: part.state,
|
|
396
|
+
duration: part.duration,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** "Read 3 files", "Editing 2 files": the run's own phrase when its
|
|
401
|
+
* calls share one, else its kind's. */
|
|
402
|
+
private mergedTitle(
|
|
403
|
+
parts: ToolPart[],
|
|
404
|
+
kind: ToolKind,
|
|
405
|
+
running: boolean,
|
|
406
|
+
): string {
|
|
407
|
+
let phrases = new Set(parts.map((p) => this.phraseOf(p)));
|
|
408
|
+
let phrase = phrases.size === 1 ? [...phrases][0]! : KIND_PHRASES[kind];
|
|
409
|
+
let verb = running ? phrase.active : phrase.done;
|
|
410
|
+
let noun = parts.length === 1 ? phrase.one : phrase.many;
|
|
411
|
+
return `${verb} ${parts.length} ${noun}`;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private merged(parts: ToolPart[], kind: 'read' | 'write'): ToolGroupEntry {
|
|
415
|
+
let worst = parts[0]!;
|
|
416
|
+
for (let part of parts) {
|
|
417
|
+
if (TONE_RANK[toneOf(part.state)] > TONE_RANK[toneOf(worst.state)]) {
|
|
418
|
+
worst = part;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
let durations = parts.map((p) => p.duration);
|
|
422
|
+
let duration = durations.every((d) => d !== undefined)
|
|
423
|
+
? (durations as number[]).reduce((a, b) => a + b, 0)
|
|
424
|
+
: undefined;
|
|
425
|
+
let targets = parts
|
|
426
|
+
.map((p) => p.subtitle)
|
|
427
|
+
.filter((s): s is string => Boolean(s));
|
|
428
|
+
return {
|
|
429
|
+
id: parts[0]!.id,
|
|
430
|
+
merged: true,
|
|
431
|
+
kind,
|
|
432
|
+
parts,
|
|
433
|
+
title: this.mergedTitle(parts, kind, toneOf(worst.state) === 'running'),
|
|
434
|
+
subtitle: targets.length ? targets.join(', ') : undefined,
|
|
435
|
+
state: worst.state,
|
|
436
|
+
duration,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// ── disclosure ─────────────────────────────────────────────────────────
|
|
441
|
+
|
|
442
|
+
get isOpen(): boolean {
|
|
443
|
+
if (this.args.open !== undefined) {
|
|
444
|
+
return this.args.open;
|
|
445
|
+
}
|
|
446
|
+
// a call that needs the reader opens the group, whatever they chose
|
|
447
|
+
if (this.isAttention) {
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
450
|
+
return this.userOpen ?? this.args.defaultOpen ?? false;
|
|
451
|
+
}
|
|
452
|
+
get recedes(): boolean {
|
|
453
|
+
if (this.args.recede !== undefined) {
|
|
454
|
+
return this.args.recede;
|
|
455
|
+
}
|
|
456
|
+
return this.tone === 'done' && !this.isOpen;
|
|
457
|
+
}
|
|
458
|
+
get renderRows(): boolean {
|
|
459
|
+
return this.isOpen || this.everOpened;
|
|
460
|
+
}
|
|
461
|
+
get sticky(): boolean {
|
|
462
|
+
return (this.args.sticky ?? true) && this.isOpen;
|
|
463
|
+
}
|
|
464
|
+
get headerStyle(): SafeString | undefined {
|
|
465
|
+
// inline rather than in the stylesheet: the offset is the caller's
|
|
466
|
+
// value, and position and offset belong together
|
|
467
|
+
if (!this.sticky) {
|
|
468
|
+
return undefined;
|
|
469
|
+
}
|
|
470
|
+
let top = this.args.stickyOffset ?? '0px';
|
|
471
|
+
return htmlSafe(`position: sticky; top: ${top}; z-index: 1`);
|
|
472
|
+
}
|
|
473
|
+
get rootStyle(): SafeString {
|
|
474
|
+
// what a nested sticky header (a diff's file header) sticks below
|
|
475
|
+
let top = this.args.stickyOffset ?? '0px';
|
|
476
|
+
let rows = cssNumber(this.args.maxRows ?? 8, 0, 1000) ?? 8;
|
|
477
|
+
let max = rows > 0 ? `calc(${rows} * 1.875rem + 0.375rem)` : 'none';
|
|
478
|
+
return htmlSafe(
|
|
479
|
+
`--pretui-tool-group-header: calc(${top} + 2rem); --pretui-tool-group-max: ${max}`,
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
noteOpen = (open: boolean) => {
|
|
484
|
+
if (open && !this.everOpened) {
|
|
485
|
+
// after this render, not during it: renderRows already read the flag
|
|
486
|
+
queueMicrotask(() => {
|
|
487
|
+
if (!this.isDestroying && !this.isDestroyed) {
|
|
488
|
+
this.everOpened = true;
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
toggle = (event: Event) => {
|
|
495
|
+
let next = !this.isOpen;
|
|
496
|
+
if (!next) {
|
|
497
|
+
this.keepHeaderInPlace(event.currentTarget as HTMLElement);
|
|
498
|
+
}
|
|
499
|
+
if (this.args.open === undefined) {
|
|
500
|
+
this.userOpen = next;
|
|
501
|
+
}
|
|
502
|
+
if (next) {
|
|
503
|
+
this.everOpened = true;
|
|
504
|
+
}
|
|
505
|
+
this.args.onOpenChange?.(next);
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
/** Collapsing from the stuck position would leave the reader far below
|
|
509
|
+
* the group: the shorter content clamps scrollTop. So first scroll up by
|
|
510
|
+
* how far the header is stuck, which puts it back at its natural place in
|
|
511
|
+
* the same spot on screen, then collapse. */
|
|
512
|
+
private keepHeaderInPlace(header: HTMLElement) {
|
|
513
|
+
let root = this.root;
|
|
514
|
+
if (!root) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
let stuck =
|
|
518
|
+
header.getBoundingClientRect().top - root.getBoundingClientRect().top;
|
|
519
|
+
if (stuck <= 0.5) {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
let scroller = scrollParent(root);
|
|
523
|
+
if (!scroller) {
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
// rects are transformed by any scaled ancestor; scrollTop is not
|
|
527
|
+
let rendered = scroller.getBoundingClientRect().height;
|
|
528
|
+
let scale =
|
|
529
|
+
scroller.offsetHeight > 0 && rendered > 0
|
|
530
|
+
? rendered / scroller.offsetHeight
|
|
531
|
+
: 1;
|
|
532
|
+
scroller.scrollTop -= stuck / scale;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
registerRoot = (el: HTMLElement) => {
|
|
536
|
+
this.root = el;
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
<template>
|
|
540
|
+
<div
|
|
541
|
+
class='pretui-toolgroup'
|
|
542
|
+
role='group'
|
|
543
|
+
aria-label={{this.receipt}}
|
|
544
|
+
data-tone={{this.tone}}
|
|
545
|
+
data-open={{if this.isOpen 'true' 'false'}}
|
|
546
|
+
data-recede={{if this.recedes 'true'}}
|
|
547
|
+
style={{this.rootStyle}}
|
|
548
|
+
{{registerElement this.registerRoot}}
|
|
549
|
+
data-test-pretui-tool-group
|
|
550
|
+
...attributes
|
|
551
|
+
>
|
|
552
|
+
<button
|
|
553
|
+
type='button'
|
|
554
|
+
class='pretui-toolgroup-header'
|
|
555
|
+
aria-expanded={{if this.isOpen 'true' 'false'}}
|
|
556
|
+
aria-controls={{this.bodyId}}
|
|
557
|
+
aria-label={{this.receipt}}
|
|
558
|
+
style={{this.headerStyle}}
|
|
559
|
+
data-sticky={{if this.sticky 'true'}}
|
|
560
|
+
{{on 'click' this.toggle}}
|
|
561
|
+
data-test-pretui-tool-group-header
|
|
562
|
+
>
|
|
563
|
+
<span
|
|
564
|
+
class='pretui-toolgroup-disc'
|
|
565
|
+
data-tone={{this.tone}}
|
|
566
|
+
aria-hidden='true'
|
|
567
|
+
>
|
|
568
|
+
{{#if this.isRunning}}<PlanMark
|
|
569
|
+
@state='current'
|
|
570
|
+
@size={{14}}
|
|
571
|
+
/>{{else}}<StackIcon
|
|
572
|
+
class='pretui-toolgroup-icon'
|
|
573
|
+
width='14'
|
|
574
|
+
height='14'
|
|
575
|
+
/>{{/if}}
|
|
576
|
+
</span>
|
|
577
|
+
<span
|
|
578
|
+
class='pretui-toolgroup-receipt'
|
|
579
|
+
aria-hidden='true'
|
|
580
|
+
data-test-pretui-tool-group-receipt
|
|
581
|
+
>
|
|
582
|
+
{{#if (has-block 'summary')}}
|
|
583
|
+
{{yield this.summary to='summary'}}
|
|
584
|
+
{{else if @summary}}
|
|
585
|
+
{{@summary}}
|
|
586
|
+
{{else if this.byKind}}
|
|
587
|
+
<span
|
|
588
|
+
class='pretui-toolgroup-phrases'
|
|
589
|
+
data-test-pretui-tool-group-phrases
|
|
590
|
+
>
|
|
591
|
+
{{#each
|
|
592
|
+
this.phraseParts key='item.key'
|
|
593
|
+
as |part|
|
|
594
|
+
}}{{part.before}}<span
|
|
595
|
+
class='pretui-toolgroup-phrase'
|
|
596
|
+
data-active={{if part.item.active 'true'}}
|
|
597
|
+
data-test-pretui-tool-group-phrase
|
|
598
|
+
>{{part.item.verb}}
|
|
599
|
+
<Odometer @value={{part.item.count}} />
|
|
600
|
+
{{part.item.noun}}</span>{{/each}}
|
|
601
|
+
</span>
|
|
602
|
+
{{#if this.failedCount}}
|
|
603
|
+
<PointIcon
|
|
604
|
+
class='pretui-toolgroup-sep'
|
|
605
|
+
width='10'
|
|
606
|
+
height='10'
|
|
607
|
+
aria-hidden='true'
|
|
608
|
+
/>
|
|
609
|
+
<span
|
|
610
|
+
class='pretui-toolgroup-failed'
|
|
611
|
+
data-test-pretui-tool-group-failed
|
|
612
|
+
>{{this.failedCount}}
|
|
613
|
+
failed</span>
|
|
614
|
+
{{/if}}
|
|
615
|
+
{{else}}
|
|
616
|
+
<span class='pretui-toolgroup-used'>Used
|
|
617
|
+
<Odometer
|
|
618
|
+
@value={{this.tools.length}}
|
|
619
|
+
data-test-pretui-tool-group-count
|
|
620
|
+
/>
|
|
621
|
+
{{this.countNoun}}</span>
|
|
622
|
+
{{#if this.nameList}}
|
|
623
|
+
<PointIcon
|
|
624
|
+
class='pretui-toolgroup-sep'
|
|
625
|
+
width='10'
|
|
626
|
+
height='10'
|
|
627
|
+
aria-hidden='true'
|
|
628
|
+
/>
|
|
629
|
+
<span
|
|
630
|
+
class='pretui-toolgroup-names'
|
|
631
|
+
data-test-pretui-tool-group-names
|
|
632
|
+
>{{this.nameList}}</span>
|
|
633
|
+
{{/if}}
|
|
634
|
+
{{#if this.failedCount}}
|
|
635
|
+
<PointIcon
|
|
636
|
+
class='pretui-toolgroup-sep'
|
|
637
|
+
width='10'
|
|
638
|
+
height='10'
|
|
639
|
+
aria-hidden='true'
|
|
640
|
+
/>
|
|
641
|
+
<span
|
|
642
|
+
class='pretui-toolgroup-failed'
|
|
643
|
+
data-test-pretui-tool-group-failed
|
|
644
|
+
>{{this.failedCount}}
|
|
645
|
+
failed</span>
|
|
646
|
+
{{/if}}
|
|
647
|
+
{{/if}}
|
|
648
|
+
</span>
|
|
649
|
+
{{#if this.statusText}}
|
|
650
|
+
<span
|
|
651
|
+
class='pretui-toolgroup-status'
|
|
652
|
+
data-tone={{this.tone}}
|
|
653
|
+
aria-hidden='true'
|
|
654
|
+
data-test-pretui-tool-group-status
|
|
655
|
+
>{{this.statusText}}</span>
|
|
656
|
+
{{/if}}
|
|
657
|
+
<span
|
|
658
|
+
class='pretui-toolgroup-chevron'
|
|
659
|
+
data-open={{if this.isOpen 'true'}}
|
|
660
|
+
aria-hidden='true'
|
|
661
|
+
></span>
|
|
662
|
+
</button>
|
|
663
|
+
<Collapse
|
|
664
|
+
@open={{this.isOpen}}
|
|
665
|
+
id={{this.bodyId}}
|
|
666
|
+
{{noteOpen this.noteOpen this.isOpen}}
|
|
667
|
+
>
|
|
668
|
+
<div class='pretui-toolgroup-rows' data-test-pretui-tool-group-rows>
|
|
669
|
+
{{#if this.renderRows}}
|
|
670
|
+
{{#each this.entries key='id' as |entry|}}
|
|
671
|
+
{{#if (has-block 'tool')}}
|
|
672
|
+
{{yield entry to='tool'}}
|
|
673
|
+
{{else if entry.merged}}
|
|
674
|
+
<ToolRow
|
|
675
|
+
@name={{entry.kind}}
|
|
676
|
+
@title={{entry.title}}
|
|
677
|
+
@subtitle={{entry.subtitle}}
|
|
678
|
+
@state={{entry.state}}
|
|
679
|
+
@duration={{entry.duration}}
|
|
680
|
+
@recede={{false}}
|
|
681
|
+
data-merged='true'
|
|
682
|
+
data-transcript-id={{entry.id}}
|
|
683
|
+
>
|
|
684
|
+
<:body>
|
|
685
|
+
<ul
|
|
686
|
+
class='pretui-toolgroup-merged'
|
|
687
|
+
data-test-pretui-tool-group-merged
|
|
688
|
+
>
|
|
689
|
+
{{#each entry.parts key='id' as |part|}}
|
|
690
|
+
<li>{{if part.subtitle part.subtitle part.name}}</li>
|
|
691
|
+
{{/each}}
|
|
692
|
+
</ul>
|
|
693
|
+
</:body>
|
|
694
|
+
</ToolRow>
|
|
695
|
+
{{else}}
|
|
696
|
+
{{#let (firstPart entry) as |part|}}
|
|
697
|
+
<ToolRow
|
|
698
|
+
@name={{part.name}}
|
|
699
|
+
@subtitle={{part.subtitle}}
|
|
700
|
+
@state={{part.state}}
|
|
701
|
+
@source={{part.source}}
|
|
702
|
+
@duration={{part.duration}}
|
|
703
|
+
@error={{part.error}}
|
|
704
|
+
@params={{part.params}}
|
|
705
|
+
@result={{part.result}}
|
|
706
|
+
@renderers={{@renderers}}
|
|
707
|
+
@recede={{false}}
|
|
708
|
+
data-transcript-id={{entry.id}}
|
|
709
|
+
/>
|
|
710
|
+
{{/let}}
|
|
711
|
+
{{/if}}
|
|
712
|
+
{{/each}}
|
|
713
|
+
{{/if}}
|
|
714
|
+
</div>
|
|
715
|
+
</Collapse>
|
|
716
|
+
</div>
|
|
717
|
+
<style scoped>
|
|
718
|
+
@layer PretComponent, PretComposite;
|
|
719
|
+
@layer PretComposite {
|
|
720
|
+
.pretui-toolgroup {
|
|
721
|
+
display: flex;
|
|
722
|
+
flex-direction: column;
|
|
723
|
+
font-size: var(--boxel-ui-label-font-size);
|
|
724
|
+
line-height: var(--boxel-ui-label-line-height);
|
|
725
|
+
}
|
|
726
|
+
.pretui-toolgroup-header {
|
|
727
|
+
display: flex;
|
|
728
|
+
align-items: center;
|
|
729
|
+
gap: var(--boxel-sp-xs);
|
|
730
|
+
min-height: 2rem;
|
|
731
|
+
padding: var(--boxel-sp-4xs) var(--boxel-sp-xs);
|
|
732
|
+
border: 0;
|
|
733
|
+
border-radius: var(--boxel-border-radius-sm);
|
|
734
|
+
background-color: transparent;
|
|
735
|
+
color: inherit;
|
|
736
|
+
font-family: inherit;
|
|
737
|
+
font-size: inherit;
|
|
738
|
+
font-weight: inherit;
|
|
739
|
+
line-height: inherit;
|
|
740
|
+
text-align: start;
|
|
741
|
+
cursor: pointer;
|
|
742
|
+
min-width: 0;
|
|
743
|
+
}
|
|
744
|
+
.pretui-toolgroup-header {
|
|
745
|
+
transition: opacity 200ms linear;
|
|
746
|
+
}
|
|
747
|
+
.pretui-toolgroup[data-recede] > .pretui-toolgroup-header {
|
|
748
|
+
opacity: var(--pretui-recede-opacity, 1);
|
|
749
|
+
}
|
|
750
|
+
.pretui-toolgroup[data-recede]
|
|
751
|
+
> .pretui-toolgroup-header:is(:hover, :focus-visible) {
|
|
752
|
+
opacity: 1;
|
|
753
|
+
}
|
|
754
|
+
.pretui-toolgroup-header:hover {
|
|
755
|
+
background-color: var(--inset);
|
|
756
|
+
}
|
|
757
|
+
.pretui-toolgroup-header:focus-visible {
|
|
758
|
+
outline: 2px solid var(--ring);
|
|
759
|
+
outline-offset: -2px;
|
|
760
|
+
}
|
|
761
|
+
.pretui-toolgroup-header[data-sticky] {
|
|
762
|
+
background-color: var(--card);
|
|
763
|
+
box-shadow: 0 1px 0 var(--border);
|
|
764
|
+
border-radius: 0;
|
|
765
|
+
}
|
|
766
|
+
/* the group's mark is a stack of calls in the quiet ink, as each
|
|
767
|
+
row's is its tool's icon; a failure or a question tints it */
|
|
768
|
+
.pretui-toolgroup-disc {
|
|
769
|
+
width: var(--boxel-sp);
|
|
770
|
+
height: var(--boxel-sp);
|
|
771
|
+
display: grid;
|
|
772
|
+
place-items: center;
|
|
773
|
+
flex: none;
|
|
774
|
+
color: var(--subtle-foreground);
|
|
775
|
+
}
|
|
776
|
+
.pretui-toolgroup-icon {
|
|
777
|
+
display: block;
|
|
778
|
+
stroke-width: 1.75;
|
|
779
|
+
}
|
|
780
|
+
.pretui-toolgroup-disc[data-tone='failed'] {
|
|
781
|
+
color: var(--destructive-ink);
|
|
782
|
+
}
|
|
783
|
+
.pretui-toolgroup-disc[data-tone='attention'] {
|
|
784
|
+
color: var(--attention-ink);
|
|
785
|
+
}
|
|
786
|
+
.pretui-toolgroup-receipt {
|
|
787
|
+
display: flex;
|
|
788
|
+
align-items: baseline;
|
|
789
|
+
gap: var(--boxel-sp-xs);
|
|
790
|
+
min-width: 0;
|
|
791
|
+
flex: 1;
|
|
792
|
+
color: var(--muted-foreground);
|
|
793
|
+
}
|
|
794
|
+
.pretui-toolgroup-phrases {
|
|
795
|
+
min-width: 0;
|
|
796
|
+
white-space: nowrap;
|
|
797
|
+
overflow: hidden;
|
|
798
|
+
text-overflow: ellipsis;
|
|
799
|
+
color: var(--foreground);
|
|
800
|
+
font-variant-numeric: tabular-nums;
|
|
801
|
+
}
|
|
802
|
+
/* what the run is doing now shimmers; what it did is still */
|
|
803
|
+
.pretui-toolgroup-phrase[data-active] {
|
|
804
|
+
font-weight: 600;
|
|
805
|
+
background: linear-gradient(
|
|
806
|
+
90deg,
|
|
807
|
+
var(--foreground) 0%,
|
|
808
|
+
var(--muted-foreground) 50%,
|
|
809
|
+
var(--foreground) 100%
|
|
810
|
+
);
|
|
811
|
+
background-size: 200% 100%;
|
|
812
|
+
background-clip: text;
|
|
813
|
+
-webkit-background-clip: text;
|
|
814
|
+
color: transparent;
|
|
815
|
+
animation: pretui-toolgroup-shimmer 1.6s linear infinite;
|
|
816
|
+
}
|
|
817
|
+
@keyframes pretui-toolgroup-shimmer {
|
|
818
|
+
to {
|
|
819
|
+
background-position: -200% 0;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
/* the dot between the summary's parts, as a boxel-icons mark */
|
|
823
|
+
.pretui-toolgroup-sep {
|
|
824
|
+
flex: none;
|
|
825
|
+
align-self: center;
|
|
826
|
+
color: var(--subtle-foreground);
|
|
827
|
+
}
|
|
828
|
+
.pretui-toolgroup-used {
|
|
829
|
+
flex: none;
|
|
830
|
+
color: var(--foreground);
|
|
831
|
+
font-weight: 600;
|
|
832
|
+
font-variant-numeric: tabular-nums;
|
|
833
|
+
}
|
|
834
|
+
.pretui-toolgroup-names {
|
|
835
|
+
min-width: 0;
|
|
836
|
+
white-space: nowrap;
|
|
837
|
+
overflow: hidden;
|
|
838
|
+
text-overflow: ellipsis;
|
|
839
|
+
}
|
|
840
|
+
/* a state colours the word ("2 failed"), never the row */
|
|
841
|
+
.pretui-toolgroup-failed {
|
|
842
|
+
flex: none;
|
|
843
|
+
font-variant-numeric: tabular-nums;
|
|
844
|
+
color: var(--destructive-ink);
|
|
845
|
+
}
|
|
846
|
+
.pretui-toolgroup-status {
|
|
847
|
+
flex: none;
|
|
848
|
+
font-size: var(--boxel-caption-font-size);
|
|
849
|
+
line-height: var(--boxel-caption-line-height);
|
|
850
|
+
font-variant-numeric: tabular-nums;
|
|
851
|
+
color: var(--subtle-foreground);
|
|
852
|
+
}
|
|
853
|
+
.pretui-toolgroup-status[data-tone='attention'] {
|
|
854
|
+
color: var(--attention-ink);
|
|
855
|
+
font-weight: 600;
|
|
856
|
+
}
|
|
857
|
+
.pretui-toolgroup-chevron {
|
|
858
|
+
flex: none;
|
|
859
|
+
width: 0.375rem;
|
|
860
|
+
height: 0.375rem;
|
|
861
|
+
border-inline-end: 1.5px solid var(--subtle-foreground);
|
|
862
|
+
border-block-end: 1.5px solid var(--subtle-foreground);
|
|
863
|
+
transform: rotate(-45deg);
|
|
864
|
+
transition: transform 160ms
|
|
865
|
+
var(--pretui-ease-enter, cubic-bezier(0.22, 0.61, 0.25, 1));
|
|
866
|
+
}
|
|
867
|
+
.pretui-toolgroup-chevron[data-open] {
|
|
868
|
+
transform: rotate(45deg);
|
|
869
|
+
}
|
|
870
|
+
/* the group's rows hang on ToolRow's grid: the rail runs under the
|
|
871
|
+
group's disc centre (the inset plus half the disc), and the
|
|
872
|
+
padding after it plus each row's own inset puts every child's
|
|
873
|
+
disc on the group's text edge. A group nested in a group repeats
|
|
874
|
+
the same step. */
|
|
875
|
+
.pretui-toolgroup-rows {
|
|
876
|
+
display: flex;
|
|
877
|
+
flex-direction: column;
|
|
878
|
+
/* past @maxRows the rows scroll inside the group; the header
|
|
879
|
+
above them stays put */
|
|
880
|
+
max-height: var(--pretui-tool-group-max, none);
|
|
881
|
+
overflow-y: auto;
|
|
882
|
+
overscroll-behavior: contain;
|
|
883
|
+
scrollbar-width: thin;
|
|
884
|
+
gap: 1px;
|
|
885
|
+
padding: var(--boxel-sp-6xs) 0 var(--boxel-sp-4xs) var(--boxel-sp-xs);
|
|
886
|
+
margin-inline-start: var(--boxel-sp);
|
|
887
|
+
box-shadow: inset 1px 0 0 var(--border);
|
|
888
|
+
}
|
|
889
|
+
/* rows inside a group are one step quieter than rows on their own */
|
|
890
|
+
.pretui-toolgroup-rows > :deep(.pretui-toolrow .pretui-toolrow-header) {
|
|
891
|
+
min-height: 1.75rem;
|
|
892
|
+
padding-block: 0.3125rem;
|
|
893
|
+
}
|
|
894
|
+
.pretui-toolgroup-merged {
|
|
895
|
+
margin: 0;
|
|
896
|
+
padding: 0 0 0 var(--boxel-sp);
|
|
897
|
+
/* each line is a call's subtitle (a path, a command) or its name:
|
|
898
|
+
a pasteable string, so the mono voice */
|
|
899
|
+
font-family: var(--font-mono);
|
|
900
|
+
font-size: var(--boxel-font-size-xs);
|
|
901
|
+
line-height: var(--boxel-line-height-xs);
|
|
902
|
+
color: var(--muted-foreground);
|
|
903
|
+
display: flex;
|
|
904
|
+
flex-direction: column;
|
|
905
|
+
gap: var(--boxel-sp-6xs);
|
|
906
|
+
}
|
|
907
|
+
@media (prefers-reduced-motion: reduce) {
|
|
908
|
+
.pretui-toolgroup-chevron {
|
|
909
|
+
transition: none;
|
|
910
|
+
}
|
|
911
|
+
.pretui-toolgroup-phrase[data-active] {
|
|
912
|
+
animation: none;
|
|
913
|
+
color: var(--foreground);
|
|
914
|
+
background: none;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
</style>
|
|
919
|
+
</template>
|
|
920
|
+
}
|