@nextclaw/ui 0.17.1 → 0.17.2
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/CHANGELOG.md +20 -0
- package/dist/assets/{abnfDiagram-VRR7QNED-BcFJp_1X.js → abnfDiagram-VRR7QNED-DvTjgVMf.js} +1 -1
- package/dist/assets/{aiden0z-pptx-renderer.es-Dp2ezlQn.js → aiden0z-pptx-renderer.es-C1nfBAXR.js} +1 -1
- package/dist/assets/{api-DJ6ZKWFY.js → api-CuPWhNDM.js} +3 -3
- package/dist/assets/{appearance-settings-page-B-juoJ6F.js → appearance-settings-page-DRjg6UP3.js} +1 -1
- package/dist/assets/{arc-CkgyPMyW.js → arc-BvhODBXv.js} +1 -1
- package/dist/assets/architecture-TIHT7OUA-Bjp9jf0h.js +1 -0
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-CMd4cIjI.js → architectureDiagram-ZJ3FMSHR-BKO9RgNS.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-Bz3tbhet.js → blockDiagram-677ZJIJ3-DHpDXy7D.js} +1 -1
- package/dist/assets/book-open-B4ovWX14.js +1 -0
- package/dist/assets/{c4Diagram-LMCZKHZV-D9hFNAGv.js → c4Diagram-LMCZKHZV-BG_iQnDr.js} +1 -1
- package/dist/assets/channel-5T3pdR-s.js +1 -0
- package/dist/assets/{channels-list-page-mVHYa7xW.js → channels-list-page-B26GXq63.js} +2 -2
- package/dist/assets/{chat-page-DsMYGNee.js → chat-page-QA0dpNyU.js} +1 -1
- package/dist/assets/chunk-2Q5K7J3B-DTZ3tGfE.js +1 -0
- package/dist/assets/{chunk-32BRIVSS-7NrjEITq.js → chunk-32BRIVSS-DR9M0LLx.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-viIcqj9q.js → chunk-52WLFC77-698iC3BB.js} +1 -1
- package/dist/assets/{chunk-5FCAYU7R-Vsu4J-VM.js → chunk-5FCAYU7R-DikTj9Ey.js} +1 -1
- package/dist/assets/{chunk-5HE753X5-C-w6CQZK.js → chunk-5HE753X5-ko0o7_q0.js} +1 -1
- package/dist/assets/{chunk-5JV3BV7I-CuKgJQ4W.js → chunk-5JV3BV7I-CEHufhgo.js} +1 -1
- package/dist/assets/{chunk-5TONJI2A-CYeiWZKb.js → chunk-5TONJI2A-CGgno1cu.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-DRCbfwIn.js → chunk-5VM5RSS4-DkiB4cUQ.js} +1 -1
- package/dist/assets/{chunk-7BUUIJ7U-BvydOcm3.js → chunk-7BUUIJ7U-DzptcytQ.js} +1 -1
- package/dist/assets/{chunk-BIQX33UG-RlppOXKo.js → chunk-BIQX33UG-DMRSQJvT.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-DAizzYwr.js → chunk-C7G6YPKG-Bw1BwwuY.js} +1 -1
- package/dist/assets/{chunk-CQNSW5MT-5eChJHca.js → chunk-CQNSW5MT-C0z6eHBk.js} +1 -1
- package/dist/assets/{chunk-CYSBUYHQ-YHPr53fv.js → chunk-CYSBUYHQ-ByPyAouO.js} +1 -1
- package/dist/assets/{chunk-EMLP6XTP-CK4D7Hz5.js → chunk-EMLP6XTP-CJlAXWZg.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-BmlDFIRW.js → chunk-EX3LRPZG-Dqq4zKJu.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-D0ksHZZ9.js → chunk-FWX5IMBZ-B0SNoCmi.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-DPkq8FCm.js → chunk-HOUHSVGY-DG_GJih_.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-FAatv5fC.js → chunk-ICXQ74PX-Drxk_6bE.js} +1 -1
- package/dist/assets/{chunk-JG7HCLWE-DLkIi4F7.js → chunk-JG7HCLWE-DdLFPpnd.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DawOjNE3.js → chunk-JWPE2WC7-B8M4G76F.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-Di52TorW.js → chunk-MOJQB5TN-XP6FJHY_.js} +1 -1
- package/dist/assets/{chunk-MOZMSUNE-PfyK9w1-.js → chunk-MOZMSUNE-C_6x18Qw.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-C3jdH3Ty.js → chunk-OGEWGWER-N25BzCmG.js} +1 -1
- package/dist/assets/{chunk-OSBZ3O6U-CecHo-f0.js → chunk-OSBZ3O6U-ASDEm7y0.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-f3IJh3sN.js → chunk-PUDLZKDR-CTlvAcn7.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-DQFeUPxZ.js → chunk-Q4XR5HBZ-Da9BWfB6.js} +1 -1
- package/dist/assets/{chunk-QBLGF6JB-BhUeG0y2.js → chunk-QBLGF6JB-Bi6RpTJO.js} +1 -1
- package/dist/assets/{chunk-R7FJI6CG-C3XpZ-2u.js → chunk-R7FJI6CG-DkMsSPvK.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-CAqa5klQ.js → chunk-RYQCIY6F-DZp0eU-j.js} +1 -1
- package/dist/assets/{chunk-U6XO7XAA-D_WXezeD.js → chunk-U6XO7XAA-vfinDSrj.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-CZ70oOtp.js → chunk-V7JOEXUC-C9Nksccs.js} +1 -1
- package/dist/assets/chunk-VAUOI2AC-HLGIHQ6k.js +1 -0
- package/dist/assets/{chunk-VR4S4FIN-B7AHEYYk.js → chunk-VR4S4FIN-Xv6zfwXs.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-Dn9To_OI.js → chunk-WYO6CB5R-mOJNzbWl.js} +2 -2
- package/dist/assets/chunk-XXDRQBXY-Dl0m6Ni-.js +1 -0
- package/dist/assets/{chunk-YOTPTUD7-BYEgRBk3.js → chunk-YOTPTUD7-1-aKSu4o.js} +1 -1
- package/dist/assets/{chunk-ZGVPDNZ5-C6YtZDU0.js → chunk-ZGVPDNZ5-OJ8veMpK.js} +1 -1
- package/dist/assets/{chunk-ZIRB5QZD-DMuYtnHF.js → chunk-ZIRB5QZD-SR_Nq_PP.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-DcDnzGNX.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-BKXKMzKM.js +1 -0
- package/dist/assets/{config-split-page-BaqLcRxk.js → config-split-page-Box_-aBK.js} +1 -1
- package/dist/assets/{confirm-dialog-DasFoaCT.js → confirm-dialog-uqcCxomz.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-Bc2Ex5Y3.js → cose-bilkent-JH36ORCC-NjVgz2Bw.js} +1 -1
- package/dist/assets/cynefin-VYW2F7L2-BWg7U2UD.js +1 -0
- package/dist/assets/{cynefinDiagram-TSTJHNR4-DVysQoGz.js → cynefinDiagram-TSTJHNR4-B_17LDqD.js} +1 -1
- package/dist/assets/{dagre-CMhQ1cAG.js → dagre-CMe101gk.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB-RYyWpPJZ.js → dagre-VKFMJZFB-qo8nLb3B.js} +1 -1
- package/dist/assets/{desktop-update-config-CoEvKrpB.js → desktop-update-config-DkDor159.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-BD6KONdF.js → diagram-FQU43EPY-Ba3XIklC.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-CGB2ptRR.js → diagram-G47NLZAW-CG45TcUx.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-DnkOI6cU.js → diagram-NH7WQ7WH-2F2HrwV-.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-DF82RZ7P.js → diagram-OA4YK3LP-CXfWH9NZ.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-Be2v7Ck9.js → diagram-WEI45ONY-CH_4yepc.js} +1 -1
- package/dist/assets/{dist-C741xUwt.js → dist-CZLIZntJ.js} +1 -1
- package/dist/assets/{dist-zOKNc7QQ.js → dist-Co2G-YjN.js} +1 -1
- package/dist/assets/doc-browser-CbCBDdap.js +1 -0
- package/dist/assets/doc-browser-DUqckL2E.js +1 -0
- package/dist/assets/{doc-browser-context-BT8NWQyM.js → doc-browser-context-aeZu0FHR.js} +1 -1
- package/dist/assets/{docx-preview-BqwAjrj3.js → docx-preview-CoM4JK7_.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-CP98OYk8.js → ebnfDiagram-CCIWWBDH-BwiG4Ea1.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-ZLlx8xfs.js → erDiagram-Q63AITRT-BML3hpnQ.js} +1 -1
- package/dist/assets/eventmodeling-45OFAUF4-SB-J4tpq.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-BQ_u-eLM.js +1 -0
- package/dist/assets/{form-actions-BcukaMyR.js → form-actions-C3Pa1HQb.js} +1 -1
- package/dist/assets/{ganttDiagram-NO4QXBWP-DXCDjHR2.js → ganttDiagram-NO4QXBWP-80enfWbD.js} +1 -1
- package/dist/assets/gitGraph-TEB2WS4Q-DPxNwsFd.js +1 -0
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-l2HWrTJp.js → gitGraphDiagram-IHSO6WYX-CVLqXr-b.js} +1 -1
- package/dist/assets/index-Bsoscjk7.css +1 -0
- package/dist/assets/index-CCWgeWoR.js +109 -0
- package/dist/assets/info-DKCQHKI2-DNlNGH90.js +1 -0
- package/dist/assets/{infoDiagram-FWYZ7A6U-D-DjX-bn.js → infoDiagram-FWYZ7A6U-Dyvs2t0i.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-y_Xy128F.js → ishikawaDiagram-FXEZZL3T-sSnxDKfh.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-DJn5xYcQ.js → journeyDiagram-5HDEW3XC-D22V71Mf.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-DJVIOR21.js → kanban-definition-HUTT4EX6-D3ds0p-g.js} +1 -1
- package/dist/assets/{line-Cos7PIOj.js → line-CbyFs065.js} +1 -1
- package/dist/assets/{linear-BtDLqtzF.js → linear-4DKtnZBS.js} +1 -1
- package/dist/assets/loader-circle-DfbWUPdI.js +1 -0
- package/dist/assets/{map-BKnQyvd0.js → map-CcZzEoz6.js} +1 -1
- package/dist/assets/mcp-marketplace-page-B2D7ciJL.js +1 -0
- package/dist/assets/mcp-marketplace-page-BmqbIQQQ.js +9 -0
- package/dist/assets/{mermaid-parser.core-XUNZhr2W.js → mermaid-parser.core-DPmMOjXD.js} +2 -2
- package/dist/assets/{mermaid.core-ySrvolIe.js → mermaid.core-BSf-ddRa.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-BypL9tei.js → mindmap-definition-LN4V7U3C-BUTwWPyx.js} +1 -1
- package/dist/assets/model-config-page-BXUoZEEZ.js +1 -0
- package/dist/assets/{navigation-link-BoMNAKyw.js → navigation-link-DngetDmN.js} +1 -1
- package/dist/assets/{ordinal-CxxVWxYU.js → ordinal-BgLYtOZE.js} +1 -1
- package/dist/assets/packet-7NZHBO7P-BpeiqkBZ.js +1 -0
- package/dist/assets/{pegDiagram-2B236MQR-D-U2KBGv.js → pegDiagram-2B236MQR-BEu_ZBjc.js} +1 -1
- package/dist/assets/pie-RZYD4A2V-Cy1yxsPd.js +1 -0
- package/dist/assets/{pieDiagram-ENE6RG2P-CBi3GusJ.js → pieDiagram-ENE6RG2P-CAwAwuBd.js} +1 -1
- package/dist/assets/plus-wNpOxBfP.js +1 -0
- package/dist/assets/{provider-scoped-model-input-CB-vojwC.js → provider-scoped-model-input-C4a2TuV7.js} +1 -1
- package/dist/assets/{providers-config-page-C80nti5H.js → providers-config-page-CHSka1kQ.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-H0ZuvnZL.js → quadrantDiagram-ABIIQ3AL-CwE6F-_2.js} +1 -1
- package/dist/assets/radar-I7S5WNFK-CEcPKCj8.js +1 -0
- package/dist/assets/railroad-3IZDKUUU-CW38eNDs.js +1 -0
- package/dist/assets/railroad-abnf-AHOZXSZD-ByrylcXf.js +1 -0
- package/dist/assets/railroad-ebnf-EBAXGLYW-CNB6GwP-.js +1 -0
- package/dist/assets/railroad-peg-LSFZ7HO6-x6hnqlBG.js +1 -0
- package/dist/assets/{railroadDiagram-RFXS5EU6-D7Y2EqSd.js → railroadDiagram-RFXS5EU6-xgLekU3B.js} +1 -1
- package/dist/assets/react-CPH0zySO.js +8 -0
- package/dist/assets/{refresh-cw-DZ_Y89ld.js → refresh-cw-vTYZqC0b.js} +1 -1
- package/dist/assets/remote-DgfRmokf.js +1 -0
- package/dist/assets/{requirementDiagram-TGXJPOKE-BiXNX1NN.js → requirementDiagram-TGXJPOKE-CyVUzEX5.js} +1 -1
- package/dist/assets/rotate-cw-L_seGUwq.js +1 -0
- package/dist/assets/runtime-config-page-V75_zqXa.js +1 -0
- package/dist/assets/{sankeyDiagram-HTMAVEWB-qzDeLztW.js → sankeyDiagram-HTMAVEWB-JK6bFmYB.js} +1 -1
- package/dist/assets/{save-BgMfxs5u.js → save-lWb6TI69.js} +1 -1
- package/dist/assets/{search-C7ruoV7u.js → search-BuUX_0KY.js} +1 -1
- package/dist/assets/{search-config-page-BTjfOZ6q.js → search-config-page-rlkRJZCF.js} +1 -1
- package/dist/assets/{secrets-config-page-BFjiSz73.js → secrets-config-page-BxCEc8r5.js} +2 -2
- package/dist/assets/security-config-Cqxe13za.js +1 -0
- package/dist/assets/{select-BMX2ooE-.js → select-oalsltZV.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-BtRbPQtL.js → sequenceDiagram-DBY2YBRQ-BR5IokXV.js} +1 -1
- package/dist/assets/{settings-2-BNQF6hed.js → settings-2-Bd9xiNAY.js} +1 -1
- package/dist/assets/{sizeCapture-X5ZJPWSS-CMYvM3SL.js → sizeCapture-X5ZJPWSS-SxG_YnMA.js} +1 -1
- package/dist/assets/skeleton-D72EdBNa.js +1 -0
- package/dist/assets/{src-Bw-mzbQb.js → src-vgz8E_-O.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-BJ-mFeOz.js → stateDiagram-2N3HPSRC-DD4OoYhn.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-ZC6dFORv.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-BsucVOO2.js → swimlanes-5IMT3BWC-BWkFeyCT.js} +2 -2
- package/dist/assets/swimlanesDiagram-G3AALYLV-CRkh51Im.js +8 -0
- package/dist/assets/{tag-chip-BL-UXlVO.js → tag-chip-B324d_Xk.js} +1 -1
- package/dist/assets/{timeline-definition-FHXFAJF6-Bicewlzo.js → timeline-definition-FHXFAJF6-VTRPy6KA.js} +1 -1
- package/dist/assets/treeView-QDETBFTQ-Bw0DSCH7.js +1 -0
- package/dist/assets/treemap-6X3UGDF4-DFWBFcfh.js +1 -0
- package/dist/assets/{vennDiagram-L72KCM5P-BxOOnO9x.js → vennDiagram-L72KCM5P-Bvk42Pek.js} +1 -1
- package/dist/assets/wardley-OPB4EBWU-CoWturk0.js +1 -0
- package/dist/assets/{wardleyDiagram-EHGQE667-Cl7NltMv.js → wardleyDiagram-EHGQE667-DOFjx2s3.js} +1 -1
- package/dist/assets/x-Cps-je1K.js +1 -0
- package/dist/assets/{xychartDiagram-FW5EYKEG-DiMIxQ7N.js → xychartDiagram-FW5EYKEG-DY5yRTUc.js} +1 -1
- package/dist/index.html +22 -21
- package/package.json +9 -9
- package/src/features/chat/features/message/utils/__tests__/chat-message-show-content-tool-card.utils.test.ts +46 -0
- package/src/features/chat/features/message/utils/chat-message-part.utils.ts +2 -0
- package/src/features/chat/features/message/utils/chat-message-tool-card.utils.ts +52 -2
- package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +97 -10
- package/src/features/chat/features/ncp/hooks/use-ncp-session-conversation.ts +4 -0
- package/src/features/chat/features/ncp/hooks/use-ncp-session-queries.ts +10 -0
- package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts +27 -0
- package/src/features/chat/features/session/utils/ncp-session-adapter.utils.ts +2 -1
- package/src/features/chat/features/workspace/components/__tests__/chat-session-child-sessions.test.tsx +51 -0
- package/src/features/chat/features/workspace/components/__tests__/chat-session-token-usage.test.tsx +90 -0
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +9 -17
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +4 -45
- package/src/features/chat/features/workspace/components/child-sessions/chat-session-child-sessions.tsx +66 -0
- package/src/features/chat/features/workspace/components/overview/chat-session-token-usage.tsx +171 -0
- package/src/features/chat/types/chat-message.types.ts +5 -0
- package/src/shared/components/doc-browser/__tests__/doc-browser-tab-strip.test.tsx +78 -0
- package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +71 -3
- package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +78 -1
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +97 -44
- package/src/shared/lib/api/__tests__/ncp-session-query-cache.utils.test.ts +2 -0
- package/src/shared/lib/api/ncp-session-query-cache.utils.ts +4 -0
- package/src/shared/lib/api/ncp-session.types.ts +3 -0
- package/src/shared/lib/api/types.ts +1 -0
- package/src/shared/lib/api/utils/ncp-session.utils.ts +8 -0
- package/src/shared/lib/i18n/locales/en-US/chat.json +19 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -1
- package/dist/assets/architecture-TIHT7OUA-DO2x53FK.js +0 -1
- package/dist/assets/channel-nBvMaVyP.js +0 -1
- package/dist/assets/chunk-2Q5K7J3B-CJgqFDS4.js +0 -1
- package/dist/assets/chunk-VAUOI2AC-BGvh3yIx.js +0 -1
- package/dist/assets/chunk-XXDRQBXY-DaBxG5gs.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-P6N03G2R.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-B1_uLvMv.js +0 -1
- package/dist/assets/cynefin-VYW2F7L2-B-H8Qp-n.js +0 -1
- package/dist/assets/doc-browser-BbirIYbj.js +0 -1
- package/dist/assets/doc-browser-Crqog5cn.js +0 -1
- package/dist/assets/eventmodeling-45OFAUF4-YVtShyFA.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-BeKXabVO.js +0 -1
- package/dist/assets/gitGraph-TEB2WS4Q-jTSiBj-p.js +0 -1
- package/dist/assets/index-1f4Pv2q3.css +0 -1
- package/dist/assets/index-BYWLjYFQ.js +0 -109
- package/dist/assets/info-DKCQHKI2-BIporNWb.js +0 -1
- package/dist/assets/loader-circle-LVHz84Xj.js +0 -1
- package/dist/assets/mcp-marketplace-page-2SsrbaiQ.js +0 -1
- package/dist/assets/mcp-marketplace-page-XgYLOAkK.js +0 -9
- package/dist/assets/model-config-page-DL8ljJK-.js +0 -1
- package/dist/assets/packet-7NZHBO7P-CuFMADV0.js +0 -1
- package/dist/assets/pie-RZYD4A2V-XUeIXhM9.js +0 -1
- package/dist/assets/plus-BeGZHC3f.js +0 -1
- package/dist/assets/radar-I7S5WNFK-D-NNGh1k.js +0 -1
- package/dist/assets/railroad-3IZDKUUU-C67_uDaM.js +0 -1
- package/dist/assets/railroad-abnf-AHOZXSZD-BZ8Yflnd.js +0 -1
- package/dist/assets/railroad-ebnf-EBAXGLYW-B7zO7RoK.js +0 -1
- package/dist/assets/railroad-peg-LSFZ7HO6-R63PTgLz.js +0 -1
- package/dist/assets/react-DND3vEAM.js +0 -8
- package/dist/assets/remote-Codkr9SX.js +0 -1
- package/dist/assets/rotate-cw-CP3pQ4R8.js +0 -1
- package/dist/assets/runtime-config-page-Um1XDqXJ.js +0 -1
- package/dist/assets/security-config-Dx-wMb4q.js +0 -1
- package/dist/assets/skeleton-CWZ0IO2D.js +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-BE-HqxOK.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-Dr1H9LJH.js +0 -8
- package/dist/assets/treeView-QDETBFTQ-npJ5dbqS.js +0 -1
- package/dist/assets/treemap-6X3UGDF4-pYrBx6oa.js +0 -1
- package/dist/assets/wardley-OPB4EBWU-CmSRKLR8.js +0 -1
- package/dist/assets/x-BSu1xwPK.js +0 -1
- /package/dist/assets/{array-BQVzeGRt.js → array-NADKd9aU.js} +0 -0
- /package/dist/assets/{chunk-KEIR6QF5-f9psyCXK.js → chunk-KEIR6QF5-BuyuzJJL.js} +0 -0
- /package/dist/assets/{chunk-Y2CYZVJY-D1mAU_Po.js → chunk-Y2CYZVJY-D_B2ZRNt.js} +0 -0
- /package/dist/assets/{config-hints-nV5HPOvq.js → config-hints-BcfVUMij.js} +0 -0
- /package/dist/assets/{cytoscape.esm-0uDtdD0l.js → cytoscape.esm-BANTlgza.js} +0 -0
- /package/dist/assets/{defaultLocale-DSWXGgt0.js → defaultLocale-C4_L0aEs.js} +0 -0
- /package/dist/assets/{dist--ta5rX12.js → dist-DAPEIJbz.js} +0 -0
- /package/dist/assets/{graphlib-CQ6QV4yP.js → graphlib-ChqXg0dI.js} +0 -0
- /package/dist/assets/{host-capabilities-Tk038i04.js → host-capabilities-D9C4CBK5.js} +0 -0
- /package/dist/assets/{init-Y6h9jGtZ.js → init-D8wZg9xq.js} +0 -0
- /package/dist/assets/{jszip.min-8hyJpgpx.js → jszip.min-CkbBfwAQ.js} +0 -0
- /package/dist/assets/{katex-ARhY9PS1.js → katex-TZJFa0J_.js} +0 -0
- /package/dist/assets/{middleware-DXsbf9xI.js → middleware-B3k-TSmZ.js} +0 -0
- /package/dist/assets/{path-10BjdyLh.js → path-DodfxcX7.js} +0 -0
- /package/dist/assets/{rough.esm-DxgC_14r.js → rough.esm-CUaa1Rbe.js} +0 -0
- /package/dist/assets/{xlsx-DrDuDNAa.js → xlsx-CxByi7bR.js} +0 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { ChartNoAxesColumn } from 'lucide-react';
|
|
2
|
+
|
|
3
|
+
import { useNcpSessionTokenUsage } from '@/features/chat/features/ncp/hooks/use-ncp-session-queries';
|
|
4
|
+
import { t } from '@/shared/lib/i18n';
|
|
5
|
+
import { cn } from '@/shared/lib/utils';
|
|
6
|
+
|
|
7
|
+
function formatTokenCount(value: number | null): string {
|
|
8
|
+
return value === null ? '—' : new Intl.NumberFormat().format(value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function formatCacheHitRate(value: number | null): string {
|
|
12
|
+
return value === null
|
|
13
|
+
? '—'
|
|
14
|
+
: new Intl.NumberFormat(undefined, {
|
|
15
|
+
style: 'percent',
|
|
16
|
+
maximumFractionDigits: 1,
|
|
17
|
+
}).format(value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function TokenUsageStatusBadge({ status }: { status: 'reported' | 'partial' | 'unavailable' }) {
|
|
21
|
+
return (
|
|
22
|
+
<span
|
|
23
|
+
className={cn(
|
|
24
|
+
'inline-flex shrink-0 items-center rounded-full px-2 py-0.5 text-[10px] font-semibold',
|
|
25
|
+
status === 'reported'
|
|
26
|
+
? 'bg-emerald-50 text-emerald-700'
|
|
27
|
+
: status === 'partial'
|
|
28
|
+
? 'bg-amber-50 text-amber-700'
|
|
29
|
+
: 'bg-gray-100 text-gray-500',
|
|
30
|
+
)}
|
|
31
|
+
>
|
|
32
|
+
{t(
|
|
33
|
+
status === 'reported'
|
|
34
|
+
? 'chatWorkspaceTokenUsageReported'
|
|
35
|
+
: status === 'partial'
|
|
36
|
+
? 'chatWorkspaceTokenUsagePartial'
|
|
37
|
+
: 'chatWorkspaceTokenUsageUnavailable',
|
|
38
|
+
)}
|
|
39
|
+
</span>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function TokenUsageMetric({ label, value }: { label: string; value: string }) {
|
|
44
|
+
return (
|
|
45
|
+
<div className="min-w-0 rounded-md bg-gray-50 px-2.5 py-2">
|
|
46
|
+
<div className="truncate text-[10px] font-medium text-gray-500">{label}</div>
|
|
47
|
+
<div className="mt-0.5 truncate text-sm font-semibold tabular-nums text-gray-900">
|
|
48
|
+
{value}
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function ChatSessionTokenUsage({ sessionKey }: { sessionKey: string | null }) {
|
|
55
|
+
const usageQuery = useNcpSessionTokenUsage(sessionKey);
|
|
56
|
+
const usage = usageQuery.data;
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<section
|
|
60
|
+
aria-labelledby="chat-workspace-token-usage-title"
|
|
61
|
+
className="mt-4 rounded-lg border border-gray-200/80 bg-white p-3"
|
|
62
|
+
>
|
|
63
|
+
<div className="flex items-start gap-3">
|
|
64
|
+
<span className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gray-100 text-gray-600">
|
|
65
|
+
<ChartNoAxesColumn className="h-4 w-4" />
|
|
66
|
+
</span>
|
|
67
|
+
<div className="min-w-0 flex-1">
|
|
68
|
+
<div className="flex items-center justify-between gap-2">
|
|
69
|
+
<h3 id="chat-workspace-token-usage-title" className="text-sm font-semibold text-gray-900">
|
|
70
|
+
{t('chatWorkspaceTokenUsage')}
|
|
71
|
+
</h3>
|
|
72
|
+
{usage ? <TokenUsageStatusBadge status={usage.status} /> : null}
|
|
73
|
+
</div>
|
|
74
|
+
<p className="mt-0.5 text-xs leading-5 text-gray-500">
|
|
75
|
+
{t('chatWorkspaceTokenUsageDescription')}
|
|
76
|
+
</p>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
{usageQuery.isLoading ? (
|
|
81
|
+
<p className="py-5 text-center text-xs text-gray-500" aria-live="polite">
|
|
82
|
+
{t('chatWorkspaceTokenUsageLoading')}
|
|
83
|
+
</p>
|
|
84
|
+
) : usageQuery.isError ? (
|
|
85
|
+
<div className="flex items-center justify-between gap-3 py-4 text-xs text-rose-600" role="alert">
|
|
86
|
+
<span>{t('chatWorkspaceTokenUsageLoadFailed')}</span>
|
|
87
|
+
<button
|
|
88
|
+
type="button"
|
|
89
|
+
className="shrink-0 rounded px-1.5 py-1 font-semibold text-gray-700 hover:bg-gray-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/25"
|
|
90
|
+
onClick={() => void usageQuery.refetch()}
|
|
91
|
+
>
|
|
92
|
+
{t('chatWorkspaceTokenUsageRetry')}
|
|
93
|
+
</button>
|
|
94
|
+
</div>
|
|
95
|
+
) : usage && usage.runCount > 0 ? (
|
|
96
|
+
<div className="mt-3 space-y-3">
|
|
97
|
+
<div className="grid grid-cols-2 gap-1.5">
|
|
98
|
+
<TokenUsageMetric
|
|
99
|
+
label={t('chatWorkspaceTokenUsageTotal')}
|
|
100
|
+
value={formatTokenCount(usage.totals.totalTokens)}
|
|
101
|
+
/>
|
|
102
|
+
<TokenUsageMetric
|
|
103
|
+
label={t('chatWorkspaceTokenUsageInput')}
|
|
104
|
+
value={formatTokenCount(usage.totals.inputTokens)}
|
|
105
|
+
/>
|
|
106
|
+
<TokenUsageMetric
|
|
107
|
+
label={t('chatWorkspaceTokenUsageOutput')}
|
|
108
|
+
value={formatTokenCount(usage.totals.outputTokens)}
|
|
109
|
+
/>
|
|
110
|
+
<TokenUsageMetric
|
|
111
|
+
label={t('chatWorkspaceTokenUsageCachedInput')}
|
|
112
|
+
value={formatTokenCount(usage.totals.cachedInputTokens)}
|
|
113
|
+
/>
|
|
114
|
+
<TokenUsageMetric
|
|
115
|
+
label={t('chatWorkspaceTokenUsageCacheHitRate')}
|
|
116
|
+
value={formatCacheHitRate(usage.totals.cacheHitRate)}
|
|
117
|
+
/>
|
|
118
|
+
<TokenUsageMetric
|
|
119
|
+
label={t('chatWorkspaceTokenUsageRuns')}
|
|
120
|
+
value={String(usage.runCount)}
|
|
121
|
+
/>
|
|
122
|
+
</div>
|
|
123
|
+
<div>
|
|
124
|
+
<div className="mb-1.5 text-[11px] font-medium text-gray-700">
|
|
125
|
+
{t('chatWorkspaceTokenUsageByModel')}
|
|
126
|
+
</div>
|
|
127
|
+
<div className="space-y-1.5">
|
|
128
|
+
{usage.models.map((modelUsage) => (
|
|
129
|
+
<div key={modelUsage.model} className="rounded-md border border-gray-200/70 px-2.5 py-2">
|
|
130
|
+
<div className="flex min-w-0 items-center justify-between gap-2">
|
|
131
|
+
<span className="min-w-0 truncate text-xs font-semibold text-gray-900" title={modelUsage.model}>
|
|
132
|
+
{modelUsage.model}
|
|
133
|
+
</span>
|
|
134
|
+
<TokenUsageStatusBadge status={modelUsage.status} />
|
|
135
|
+
</div>
|
|
136
|
+
<div className="mt-1 text-[10px] text-gray-500">
|
|
137
|
+
{t('chatWorkspaceTokenUsageModelRunCount').replace('{count}', String(modelUsage.runCount))}
|
|
138
|
+
{' · '}
|
|
139
|
+
{t('chatWorkspaceTokenUsageModelCacheHitRate').replace(
|
|
140
|
+
'{rate}',
|
|
141
|
+
formatCacheHitRate(modelUsage.cacheHitRate),
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
<dl className="mt-2 grid grid-cols-2 gap-x-3 gap-y-1 text-[10px] sm:grid-cols-4">
|
|
145
|
+
{[
|
|
146
|
+
[t('chatWorkspaceTokenUsageInput'), modelUsage.inputTokens],
|
|
147
|
+
[t('chatWorkspaceTokenUsageOutput'), modelUsage.outputTokens],
|
|
148
|
+
[t('chatWorkspaceTokenUsageCachedInput'), modelUsage.cachedInputTokens],
|
|
149
|
+
[t('chatWorkspaceTokenUsageTotal'), modelUsage.totalTokens],
|
|
150
|
+
].map(([label, value]) => (
|
|
151
|
+
<div key={String(label)} className="flex min-w-0 items-center justify-between gap-1 sm:block">
|
|
152
|
+
<dt className="truncate text-gray-500">{label}</dt>
|
|
153
|
+
<dd className="shrink-0 font-semibold tabular-nums text-gray-800 sm:mt-0.5">
|
|
154
|
+
{formatTokenCount(value as number | null)}
|
|
155
|
+
</dd>
|
|
156
|
+
</div>
|
|
157
|
+
))}
|
|
158
|
+
</dl>
|
|
159
|
+
</div>
|
|
160
|
+
))}
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
) : (
|
|
165
|
+
<p className="py-5 text-center text-xs leading-5 text-gray-500">
|
|
166
|
+
{t('chatWorkspaceTokenUsageEmpty')}
|
|
167
|
+
</p>
|
|
168
|
+
)}
|
|
169
|
+
</section>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { DocBrowserTabStrip } from "@/shared/components/doc-browser/doc-browser-tab-strip";
|
|
4
|
+
import type { DocBrowserTab } from "@/shared/components/doc-browser/doc-browser-context";
|
|
5
|
+
|
|
6
|
+
const docsTab: DocBrowserTab = {
|
|
7
|
+
id: "docs",
|
|
8
|
+
kind: "docs",
|
|
9
|
+
title: "Docs",
|
|
10
|
+
currentUrl: "nextclaw://docs",
|
|
11
|
+
history: ["nextclaw://docs"],
|
|
12
|
+
historyIndex: 0,
|
|
13
|
+
navVersion: 0,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function renderTabStrip(tabs: DocBrowserTab[]) {
|
|
17
|
+
return render(
|
|
18
|
+
<DocBrowserTabStrip
|
|
19
|
+
tabs={tabs}
|
|
20
|
+
activeTabId={tabs[0]?.id ?? ""}
|
|
21
|
+
canGoBack={false}
|
|
22
|
+
canGoForward={false}
|
|
23
|
+
isDocked={true}
|
|
24
|
+
isFullscreen={false}
|
|
25
|
+
onGoBack={vi.fn()}
|
|
26
|
+
onGoForward={vi.fn()}
|
|
27
|
+
onOpenNewTab={vi.fn()}
|
|
28
|
+
onSetActiveTab={vi.fn()}
|
|
29
|
+
onCloseTab={vi.fn()}
|
|
30
|
+
onClose={vi.fn()}
|
|
31
|
+
onDragStart={vi.fn()}
|
|
32
|
+
onToggleMode={vi.fn()}
|
|
33
|
+
/>,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe("DocBrowserTabStrip", () => {
|
|
38
|
+
it("keeps content-sized tabs and renders each tab's own icon", () => {
|
|
39
|
+
renderTabStrip([
|
|
40
|
+
docsTab,
|
|
41
|
+
{
|
|
42
|
+
...docsTab,
|
|
43
|
+
id: "marketplace",
|
|
44
|
+
kind: "content",
|
|
45
|
+
title: "Marketplace",
|
|
46
|
+
currentUrl: "nextclaw://marketplace",
|
|
47
|
+
dockIcon: { type: "url", url: "/marketplace-icon.png" },
|
|
48
|
+
},
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
expect(
|
|
52
|
+
screen.getByRole("button", { name: "Docs" })
|
|
53
|
+
.querySelector(".lucide-book-open"),
|
|
54
|
+
).toBeTruthy();
|
|
55
|
+
expect(
|
|
56
|
+
screen.getByRole("button", { name: "Marketplace" })
|
|
57
|
+
.querySelector('img[src="/marketplace-icon.png"]'),
|
|
58
|
+
).toBeTruthy();
|
|
59
|
+
|
|
60
|
+
const marketplaceTab = screen.getByRole("button", { name: "Marketplace" })
|
|
61
|
+
.closest("[data-compact-tab-item]");
|
|
62
|
+
expect(marketplaceTab?.className).toContain("max-w-[220px]");
|
|
63
|
+
expect(marketplaceTab?.className).not.toContain("min-w-[");
|
|
64
|
+
|
|
65
|
+
fireEvent.error(
|
|
66
|
+
screen.getByRole("button", { name: "Marketplace" })
|
|
67
|
+
.querySelector('img[src="/marketplace-icon.png"]')!,
|
|
68
|
+
);
|
|
69
|
+
expect(
|
|
70
|
+
screen.getByRole("button", { name: "Marketplace" })
|
|
71
|
+
.querySelector(".lucide-app-window"),
|
|
72
|
+
).toBeTruthy();
|
|
73
|
+
expect(
|
|
74
|
+
screen.getByRole("button", { name: "Marketplace" })
|
|
75
|
+
.querySelector("img"),
|
|
76
|
+
).toBeNull();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -1,13 +1,18 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { useState, type PointerEvent } from "react";
|
|
2
2
|
import {
|
|
3
|
+
AppWindow,
|
|
3
4
|
ArrowLeft,
|
|
4
5
|
ArrowRight,
|
|
6
|
+
BookOpen,
|
|
7
|
+
Boxes,
|
|
8
|
+
Github,
|
|
5
9
|
PanelRightOpen,
|
|
6
10
|
PictureInPicture2,
|
|
7
11
|
Pin,
|
|
8
12
|
PinOff,
|
|
9
13
|
Plus,
|
|
10
14
|
X,
|
|
15
|
+
type LucideIcon,
|
|
11
16
|
} from "lucide-react";
|
|
12
17
|
import type { DocBrowserDockState, DocBrowserTab } from "./doc-browser-context";
|
|
13
18
|
import type { ContextMenuGroup } from "@/shared/components/ui/context-menu/context-menu";
|
|
@@ -39,6 +44,68 @@ type DocBrowserTabStripProps = {
|
|
|
39
44
|
getTabMenuGroups?: (tab: DocBrowserTab) => readonly ContextMenuGroup[] | undefined;
|
|
40
45
|
};
|
|
41
46
|
|
|
47
|
+
const DOC_BROWSER_BUILTIN_TAB_ICONS: Record<string, LucideIcon> = {
|
|
48
|
+
apps: Boxes,
|
|
49
|
+
docs: BookOpen,
|
|
50
|
+
github: Github,
|
|
51
|
+
"new-tab": Plus,
|
|
52
|
+
"panel-app": AppWindow,
|
|
53
|
+
"service-apps": AppWindow,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function DocBrowserUrlTabIcon({
|
|
57
|
+
fallbackIcon: FallbackIcon,
|
|
58
|
+
url,
|
|
59
|
+
}: {
|
|
60
|
+
fallbackIcon: LucideIcon;
|
|
61
|
+
url: string;
|
|
62
|
+
}) {
|
|
63
|
+
const [loadFailed, setLoadFailed] = useState(false);
|
|
64
|
+
return loadFailed ? (
|
|
65
|
+
<FallbackIcon className="h-3.5 w-3.5 text-muted-foreground" aria-hidden="true" />
|
|
66
|
+
) : (
|
|
67
|
+
<img
|
|
68
|
+
src={url}
|
|
69
|
+
alt=""
|
|
70
|
+
className="h-3.5 w-3.5 rounded-sm object-cover"
|
|
71
|
+
onError={() => setLoadFailed(true)}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function DocBrowserTabIcon({ tab }: { tab: DocBrowserTab }) {
|
|
77
|
+
const { dockIcon } = tab;
|
|
78
|
+
const fallbackIcon = tab.kind === "docs"
|
|
79
|
+
? BookOpen
|
|
80
|
+
: tab.kind === "home"
|
|
81
|
+
? Plus
|
|
82
|
+
: tab.kind === "apps"
|
|
83
|
+
? Boxes
|
|
84
|
+
: AppWindow;
|
|
85
|
+
const Icon = dockIcon?.type === "builtin"
|
|
86
|
+
? (DOC_BROWSER_BUILTIN_TAB_ICONS[dockIcon.name] ?? fallbackIcon)
|
|
87
|
+
: fallbackIcon;
|
|
88
|
+
|
|
89
|
+
if (dockIcon?.type === "url") {
|
|
90
|
+
return (
|
|
91
|
+
<DocBrowserUrlTabIcon
|
|
92
|
+
key={dockIcon.url}
|
|
93
|
+
fallbackIcon={fallbackIcon}
|
|
94
|
+
url={dockIcon.url}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
if (dockIcon?.type === "text") {
|
|
99
|
+
return (
|
|
100
|
+
<span className="max-w-3.5 truncate text-[10px] font-semibold leading-none" aria-hidden="true">
|
|
101
|
+
{dockIcon.value}
|
|
102
|
+
</span>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return <Icon className="h-3.5 w-3.5 text-muted-foreground" aria-hidden="true" />;
|
|
107
|
+
}
|
|
108
|
+
|
|
42
109
|
function shouldBlockHeaderDrag(target: EventTarget | null): boolean {
|
|
43
110
|
return target instanceof Element && Boolean(
|
|
44
111
|
target.closest("button, a, input, textarea, select, [data-compact-tab-item]"),
|
|
@@ -80,8 +147,9 @@ export function DocBrowserTabStrip({
|
|
|
80
147
|
label: tab.title || t("docBrowserTabUntitled"),
|
|
81
148
|
active: tab.id === activeTabId,
|
|
82
149
|
tooltip: tab.title,
|
|
150
|
+
leadingIcon: <DocBrowserTabIcon tab={tab} />,
|
|
83
151
|
closeLabel: closeTabLabel,
|
|
84
|
-
closePlacement: "
|
|
152
|
+
closePlacement: "leading-hover",
|
|
85
153
|
onSelect: () => onSetActiveTab(tab.id),
|
|
86
154
|
onClose: () => onCloseTab(tab.id),
|
|
87
155
|
menuLabel: t("docBrowserTabMoreActions"),
|
|
@@ -132,7 +200,7 @@ export function DocBrowserTabStrip({
|
|
|
132
200
|
tabsClassName="items-center gap-0.5"
|
|
133
201
|
actionsClassName="h-full items-center gap-0.5"
|
|
134
202
|
actionButtonClassName="rounded-md p-1.5 text-muted-foreground hover:bg-muted/60 hover:text-foreground disabled:opacity-50"
|
|
135
|
-
tabBaseClassName="
|
|
203
|
+
tabBaseClassName="inline-flex cursor-pointer items-center gap-0.5 h-7 px-2 rounded-md text-xs max-w-[220px] shrink-0 transition-colors"
|
|
136
204
|
activeTabClassName="bg-muted/80 text-foreground"
|
|
137
205
|
inactiveTabClassName="text-muted-foreground hover:bg-muted/45 hover:text-foreground"
|
|
138
206
|
labelClassName="px-0.5 text-xs font-normal"
|
|
@@ -113,7 +113,7 @@ describe("CompactTabStrip", () => {
|
|
|
113
113
|
active: false,
|
|
114
114
|
closeLabel: "Close preview",
|
|
115
115
|
closePlacement: "leading-hover",
|
|
116
|
-
leadingIcon: <span />,
|
|
116
|
+
leadingIcon: <span data-testid="preview-tab-icon" />,
|
|
117
117
|
onClose,
|
|
118
118
|
onSelect,
|
|
119
119
|
},
|
|
@@ -126,6 +126,83 @@ describe("CompactTabStrip", () => {
|
|
|
126
126
|
|
|
127
127
|
expect(onClose).toHaveBeenCalledTimes(1);
|
|
128
128
|
expect(onSelect).not.toHaveBeenCalled();
|
|
129
|
+
expect(
|
|
130
|
+
screen.getByTestId("preview-tab-icon").parentElement?.className,
|
|
131
|
+
).toContain("group-hover:opacity-0");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("overlays the close action at the start without changing the tab width on hover", () => {
|
|
135
|
+
render(
|
|
136
|
+
<CompactTabStrip
|
|
137
|
+
tabs={[
|
|
138
|
+
{
|
|
139
|
+
key: "marketplace",
|
|
140
|
+
label: "NextClaw Marketplace",
|
|
141
|
+
active: true,
|
|
142
|
+
closeLabel: "Close tab",
|
|
143
|
+
closePlacement: "leading-hover",
|
|
144
|
+
menuLabel: "More tab actions",
|
|
145
|
+
menuGroups: [{ key: "tab", items: [{ key: "close", label: "Close", onSelect: vi.fn() }] }],
|
|
146
|
+
onClose: vi.fn(),
|
|
147
|
+
onSelect: vi.fn(),
|
|
148
|
+
},
|
|
149
|
+
]}
|
|
150
|
+
actions={[]}
|
|
151
|
+
/>,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const tabItem = screen.getByRole("button", { name: "NextClaw Marketplace" })
|
|
155
|
+
.closest("[data-compact-tab-item]");
|
|
156
|
+
const leadingActions = tabItem?.querySelector(
|
|
157
|
+
'[data-compact-tab-leading-actions=""]',
|
|
158
|
+
);
|
|
159
|
+
const trailingActions = tabItem?.querySelector(
|
|
160
|
+
'[data-compact-tab-trailing-actions=""]',
|
|
161
|
+
);
|
|
162
|
+
const trailingSpacer = tabItem?.querySelector(
|
|
163
|
+
'[data-compact-tab-trailing-spacer=""]',
|
|
164
|
+
);
|
|
165
|
+
const leadingSlot = tabItem?.querySelector(
|
|
166
|
+
'[data-compact-tab-leading-slot=""]',
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
expect(tabItem?.className).toContain("relative");
|
|
170
|
+
expect(tabItem?.className).toContain("max-w-[180px]");
|
|
171
|
+
expect(tabItem?.className).not.toContain("min-w-[");
|
|
172
|
+
expect(leadingSlot?.className).toContain("w-3.5");
|
|
173
|
+
expect(leadingSlot?.className).toContain("shrink-0");
|
|
174
|
+
expect(leadingActions?.className).toContain("absolute");
|
|
175
|
+
expect(leadingActions?.className).toContain("left-1");
|
|
176
|
+
expect(leadingActions?.className).toContain("opacity-0");
|
|
177
|
+
expect(leadingActions?.className).toContain("pointer-events-none");
|
|
178
|
+
expect(leadingActions?.className).not.toContain("bg-inherit");
|
|
179
|
+
expect(leadingActions?.className).toContain("group-hover:opacity-100");
|
|
180
|
+
expect(leadingActions?.className).toContain("group-focus-within:opacity-100");
|
|
181
|
+
expect(trailingActions?.className).toContain("absolute");
|
|
182
|
+
expect(trailingActions?.className).toContain("opacity-0");
|
|
183
|
+
expect(trailingActions?.className).toContain("pointer-events-none");
|
|
184
|
+
expect(trailingActions?.className).not.toContain("bg-inherit");
|
|
185
|
+
expect(trailingActions?.className).toContain("group-hover:opacity-100");
|
|
186
|
+
expect(trailingActions?.className).toContain("group-focus-within:opacity-100");
|
|
187
|
+
expect(trailingActions?.className).not.toContain("w-0");
|
|
188
|
+
expect(trailingActions?.className).not.toContain("transition-[width");
|
|
189
|
+
expect(trailingSpacer?.className).toContain("w-6");
|
|
190
|
+
expect(trailingSpacer?.className).toContain("shrink-0");
|
|
191
|
+
const closeButton = leadingActions?.querySelector<HTMLButtonElement>(
|
|
192
|
+
'[aria-label="Close tab"]',
|
|
193
|
+
);
|
|
194
|
+
const menuButton = trailingActions?.querySelector<HTMLButtonElement>(
|
|
195
|
+
'[aria-label="More tab actions"]',
|
|
196
|
+
);
|
|
197
|
+
expect(closeButton).toBeTruthy();
|
|
198
|
+
expect(menuButton).toBeTruthy();
|
|
199
|
+
expect(closeButton?.className).toBe(menuButton?.className);
|
|
200
|
+
expect(closeButton?.className).toContain("h-6");
|
|
201
|
+
expect(closeButton?.className).toContain("w-6");
|
|
202
|
+
expect(closeButton?.className).toContain("rounded-md");
|
|
203
|
+
expect(
|
|
204
|
+
trailingActions?.querySelector('[aria-label="Close tab"]'),
|
|
205
|
+
).toBeNull();
|
|
129
206
|
});
|
|
130
207
|
|
|
131
208
|
it("opens a tab action menu without selecting the tab or restoring stale focus after an action", async () => {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { MoreVertical, X } from "lucide-react";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
IconActionButton,
|
|
5
|
+
type IconActionButtonProps,
|
|
6
|
+
} from "@/shared/components/ui/actions/icon-action-button";
|
|
4
7
|
import {
|
|
5
8
|
ContextMenu,
|
|
6
9
|
ContextMenuTrigger,
|
|
@@ -63,28 +66,94 @@ function closeCompactTab(event: React.MouseEvent<HTMLButtonElement>, onClose: ()
|
|
|
63
66
|
onClose();
|
|
64
67
|
}
|
|
65
68
|
|
|
69
|
+
function CompactTabActionButton(props: IconActionButtonProps) {
|
|
70
|
+
return <IconActionButton {...props} size="sm" tone="strong" />;
|
|
71
|
+
}
|
|
72
|
+
|
|
66
73
|
function CompactTabMenu({ tab }: { tab: CompactTabStripTab }) {
|
|
67
74
|
if (!tab.menuGroups?.some((group) => group.items.length > 0)) {
|
|
68
75
|
return null;
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
const menuLabel = tab.menuLabel ?? tab.label;
|
|
79
|
+
return (
|
|
80
|
+
<div className="flex shrink-0">
|
|
81
|
+
<ContextMenuTrigger>
|
|
82
|
+
<CompactTabActionButton
|
|
83
|
+
icon={<MoreVertical className="h-3.5 w-3.5" />}
|
|
84
|
+
label={menuLabel}
|
|
85
|
+
/>
|
|
86
|
+
</ContextMenuTrigger>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function CompactTabTrailingActions({ tab }: { tab: CompactTabStripTab }) {
|
|
92
|
+
const hasMenu = tab.menuGroups?.some((group) => group.items.length > 0) ?? false;
|
|
93
|
+
const hasTrailingClose = Boolean(
|
|
94
|
+
tab.onClose && tab.closePlacement !== "leading-hover",
|
|
95
|
+
);
|
|
96
|
+
const close = hasTrailingClose ? (
|
|
97
|
+
<CompactTabActionButton
|
|
98
|
+
icon={<X className="h-3.5 w-3.5" />}
|
|
99
|
+
label={tab.closeLabel ?? ""}
|
|
100
|
+
tooltip={tab.closeLabel ?? null}
|
|
101
|
+
onClick={(event) => closeCompactTab(event, tab.onClose!)}
|
|
102
|
+
/>
|
|
103
|
+
) : null;
|
|
104
|
+
|
|
105
|
+
if (!hasMenu && !hasTrailingClose) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<>
|
|
111
|
+
<span
|
|
112
|
+
aria-hidden="true"
|
|
113
|
+
data-compact-tab-trailing-spacer=""
|
|
114
|
+
className={cn(
|
|
115
|
+
"shrink-0",
|
|
116
|
+
hasMenu && hasTrailingClose
|
|
117
|
+
? "w-[50px]"
|
|
118
|
+
: hasMenu
|
|
119
|
+
? "w-6"
|
|
120
|
+
: "w-6",
|
|
121
|
+
)}
|
|
122
|
+
/>
|
|
123
|
+
<div
|
|
124
|
+
data-compact-tab-trailing-actions=""
|
|
125
|
+
className={cn(
|
|
126
|
+
"pointer-events-none absolute inset-y-0 right-1 z-10 flex items-center gap-0.5 opacity-0",
|
|
127
|
+
"group-hover:pointer-events-auto group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:opacity-100",
|
|
128
|
+
"group-data-[context-menu-open]:pointer-events-auto group-data-[context-menu-open]:opacity-100",
|
|
129
|
+
)}
|
|
130
|
+
>
|
|
131
|
+
{hasMenu ? <CompactTabMenu tab={tab} /> : null}
|
|
132
|
+
{close}
|
|
133
|
+
</div>
|
|
134
|
+
</>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function CompactTabLeadingCloseOverlay({ tab }: { tab: CompactTabStripTab }) {
|
|
139
|
+
if (!tab.onClose || tab.closePlacement !== "leading-hover") {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
72
143
|
return (
|
|
73
144
|
<div
|
|
145
|
+
data-compact-tab-leading-actions=""
|
|
74
146
|
className={cn(
|
|
75
|
-
"-
|
|
76
|
-
"group-
|
|
147
|
+
"pointer-events-none absolute inset-y-0 left-1 z-10 flex items-center opacity-0",
|
|
148
|
+
"group-hover:pointer-events-auto group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:opacity-100",
|
|
77
149
|
)}
|
|
78
150
|
>
|
|
79
|
-
<
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
<MoreVertical className="h-3.5 w-3.5" />
|
|
86
|
-
</button>
|
|
87
|
-
</ContextMenuTrigger>
|
|
151
|
+
<CompactTabActionButton
|
|
152
|
+
icon={<X className="h-3.5 w-3.5" />}
|
|
153
|
+
label={tab.closeLabel ?? ""}
|
|
154
|
+
tooltip={tab.closeLabel ?? null}
|
|
155
|
+
onClick={(event) => closeCompactTab(event, tab.onClose!)}
|
|
156
|
+
/>
|
|
88
157
|
</div>
|
|
89
158
|
);
|
|
90
159
|
}
|
|
@@ -96,9 +165,9 @@ function CompactTabLabelButton({
|
|
|
96
165
|
labelClassName?: string;
|
|
97
166
|
tab: CompactTabStripTab;
|
|
98
167
|
}) {
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
168
|
+
const hasLeadingClose = Boolean(
|
|
169
|
+
tab.onClose && tab.closePlacement === "leading-hover",
|
|
170
|
+
);
|
|
102
171
|
const button = (
|
|
103
172
|
<button
|
|
104
173
|
type="button"
|
|
@@ -106,9 +175,15 @@ function CompactTabLabelButton({
|
|
|
106
175
|
aria-label={tab.label}
|
|
107
176
|
className="flex min-w-0 flex-1 items-center gap-1.5 text-left"
|
|
108
177
|
>
|
|
109
|
-
{leadingIcon ? (
|
|
110
|
-
<span
|
|
111
|
-
|
|
178
|
+
{tab.leadingIcon || hasLeadingClose ? (
|
|
179
|
+
<span
|
|
180
|
+
data-compact-tab-leading-slot=""
|
|
181
|
+
className={cn(
|
|
182
|
+
"inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center",
|
|
183
|
+
hasLeadingClose && "group-hover:opacity-0 group-focus-within:opacity-0",
|
|
184
|
+
)}
|
|
185
|
+
>
|
|
186
|
+
{tab.leadingIcon}
|
|
112
187
|
</span>
|
|
113
188
|
) : null}
|
|
114
189
|
<span className={cn("min-w-0 truncate text-[12px] font-medium", labelClassName, tab.labelClassName)}>
|
|
@@ -150,7 +225,6 @@ function CompactTabItem({
|
|
|
150
225
|
| "labelClassName"
|
|
151
226
|
| "tabBaseClassName"
|
|
152
227
|
> & { itemRef?: React.Ref<HTMLDivElement>; tab: CompactTabStripTab }) {
|
|
153
|
-
const leadingClose = tab.onClose && tab.closePlacement === "leading-hover";
|
|
154
228
|
const item = (
|
|
155
229
|
<div
|
|
156
230
|
ref={itemRef}
|
|
@@ -158,38 +232,17 @@ function CompactTabItem({
|
|
|
158
232
|
onClick={(event) => event.target === event.currentTarget && tab.onSelect()}
|
|
159
233
|
className={cn(
|
|
160
234
|
tabBaseClassName ??
|
|
161
|
-
"
|
|
235
|
+
"flex max-w-[180px] cursor-pointer items-center gap-1.5 border-r border-gray-200/70 border-b-2 px-2.5 py-2 transition-colors",
|
|
236
|
+
"group relative",
|
|
162
237
|
tab.active
|
|
163
238
|
? (activeTabClassName ?? "border-b-primary bg-white text-gray-900")
|
|
164
239
|
: (inactiveTabClassName ??
|
|
165
240
|
"border-b-transparent bg-gray-50/85 text-gray-500 hover:bg-gray-100"),
|
|
166
241
|
)}
|
|
167
242
|
>
|
|
168
|
-
{leadingClose ? (
|
|
169
|
-
<button
|
|
170
|
-
type="button"
|
|
171
|
-
onClick={(event) => closeCompactTab(event, tab.onClose!)}
|
|
172
|
-
className="relative inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-md text-gray-400 transition-colors hover:bg-gray-200 hover:text-gray-700"
|
|
173
|
-
aria-label={tab.closeLabel}
|
|
174
|
-
>
|
|
175
|
-
<span className="flex items-center justify-center group-hover:hidden">
|
|
176
|
-
{tab.leadingIcon}
|
|
177
|
-
</span>
|
|
178
|
-
<X className="hidden h-3.5 w-3.5 group-hover:block" />
|
|
179
|
-
</button>
|
|
180
|
-
) : null}
|
|
181
243
|
<CompactTabLabelButton tab={tab} labelClassName={labelClassName} />
|
|
182
|
-
<
|
|
183
|
-
{tab
|
|
184
|
-
<IconActionButton
|
|
185
|
-
size="sm"
|
|
186
|
-
tone="strong"
|
|
187
|
-
icon={<X className="h-3 w-3" />}
|
|
188
|
-
label={tab.closeLabel ?? ""}
|
|
189
|
-
tooltip={tab.closeLabel ?? null}
|
|
190
|
-
onClick={(event) => closeCompactTab(event, tab.onClose!)}
|
|
191
|
-
/>
|
|
192
|
-
) : null}
|
|
244
|
+
<CompactTabLeadingCloseOverlay tab={tab} />
|
|
245
|
+
<CompactTabTrailingActions tab={tab} />
|
|
193
246
|
</div>
|
|
194
247
|
);
|
|
195
248
|
|
|
@@ -183,6 +183,7 @@ describe('ncp-session-query-cache', () => {
|
|
|
183
183
|
it('applies realtime upsert/delete events to every ncp-sessions query cache entry', () => {
|
|
184
184
|
const queryClient = new QueryClient();
|
|
185
185
|
queryClient.setQueryData(['ncp-sessions', 200], createSessionsList());
|
|
186
|
+
queryClient.setQueryData(['ncp-session-token-usage', 'session-3'], { runCount: 1 });
|
|
186
187
|
|
|
187
188
|
applyNcpSessionRealtimeEvent(queryClient, {
|
|
188
189
|
type: 'session.run-status',
|
|
@@ -212,6 +213,7 @@ describe('ncp-session-query-cache', () => {
|
|
|
212
213
|
expect(
|
|
213
214
|
queryClient.getQueryData<NcpSessionsListView>(['ncp-sessions', 200])?.sessions.map((session) => session.sessionId)
|
|
214
215
|
).toEqual(['session-3', 'session-1', 'session-2']);
|
|
216
|
+
expect(queryClient.getQueryState(['ncp-session-token-usage', 'session-3'])?.isInvalidated).toBe(true);
|
|
215
217
|
|
|
216
218
|
applyNcpSessionRealtimeEvent(queryClient, {
|
|
217
219
|
type: 'session.summary.delete',
|