@openhands/agent-canvas 1.11.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.windows.md +2 -2
- package/build/assets/{acp-providers-BkL3HWa3.js → acp-providers-D1PORKc0.js} +1 -1
- package/build/assets/active-backend-context-IqOjobNK.js +2 -0
- package/build/assets/{agent-context-settings-Bno1nwPL.js → agent-context-settings-DMrObcIS.js} +1 -1
- package/build/assets/agent-profiles-settings-DororXN5.js +1 -0
- package/build/assets/agent-server-conversation-service.api-DWBe0rbX.js +86 -0
- package/build/assets/{agent-settings-h8hQz-IU.js → agent-settings-BJVk92PU.js} +1 -1
- package/build/assets/agent-settings-COfJsQ1d.js +1 -0
- package/build/assets/{alert-banner-lrK-2_0Y.js → alert-banner-CzZsm7ms.js} +1 -1
- package/build/assets/{api-key-entry-screen-Qsb83VX1.js → api-key-entry-screen-D8Fade_-.js} +1 -1
- package/build/assets/app-settings-CpyGB3AT.js +1 -0
- package/build/assets/{automation-detail-7LkL91j9.js → automation-detail-B-jQ39Mm.js} +1 -1
- package/build/assets/{automation-export-U2QNyNy6.js → automation-export-B1dywoqk.js} +1 -1
- package/build/assets/{automation-setup-route-Bel9_Lue.js → automation-setup-route-C-bWUZBi.js} +1 -1
- package/build/assets/{automation-templates-CRLDssaI.js → automation-templates-DgpCGOJV.js} +1 -1
- package/build/assets/{automations-list-B2iV3ZKW.js → automations-list-DnMxlxsL.js} +1 -1
- package/build/assets/{back-nav-button-B3pYwmUJ.js → back-nav-button-HHEneWMw.js} +1 -1
- package/build/assets/backend-form-modal-DiVrD4Cm.js +1 -0
- package/build/assets/{backend-synced-settings-badge-DB4717Iu.js → backend-synced-settings-badge-7MIjpr8M.js} +1 -1
- package/build/assets/{badge-yR53enbU.js → badge-CpYmcVZV.js} +1 -1
- package/build/assets/{base-modal-3NmIOKDm.js → base-modal-Fnh6Uln9.js} +1 -1
- package/build/assets/bot-BQZx8e2i.js +1 -0
- package/build/assets/{brand-button-CJcwjnfb.js → brand-button-swT1RyO0.js} +1 -1
- package/build/assets/browser-B9AbV6kG.js +7 -0
- package/build/assets/{browser-tab-A94o33Vn.js → browser-tab-zAY12m_i.js} +1 -1
- package/build/assets/{chat-send-button-B8n6-DQA.js → chat-send-button-DsJtivmL.js} +1 -1
- package/build/assets/check-WAFXrrs_.js +1 -0
- package/build/assets/chevron-down-CifNWzme.js +1 -0
- package/build/assets/circle-check-CtnimJa8.js +1 -0
- package/build/assets/{circle-plus-check-toggle-BVzv5EkZ.js → circle-plus-check-toggle-Cuzx55bo.js} +1 -1
- package/build/assets/{cloud-funnel-analytics-CBQdPZl_.js → cloud-funnel-analytics-86CwNdSh.js} +1 -1
- package/build/assets/{combobox-caret-BlmRwiM8.js → combobox-caret-D0fRotFi.js} +1 -1
- package/build/assets/{command-menu-PsRXVBz2.js → command-menu-BXy03FBl.js} +1 -1
- package/build/assets/{condenser-settings-w7L3EFvm.js → condenser-settings-BmZa0ui_.js} +1 -1
- package/build/assets/{confirmation-modal-DAH0XtOp.js → confirmation-modal-BgoN6lbN.js} +1 -1
- package/build/assets/{context-menu-list-item-BQhmFVEK.js → context-menu-list-item-Bq02tc-P.js} +1 -1
- package/build/assets/conversation-BPo8SvLN.js +1 -0
- package/build/assets/conversation-CPCVsCUY.js +90 -0
- package/build/assets/conversation-panel-BPo8SvLN.js +1 -0
- package/build/assets/{conversation-state-store-Bs8gA263.js → conversation-state-store-C5YpjMNy.js} +1 -1
- package/build/assets/conversation-store-Blc5xysY.js +6 -0
- package/build/assets/{conversation-tab-empty-state-C9sfDOlt.js → conversation-tab-empty-state-CMqo_0Bd.js} +1 -1
- package/build/assets/{copy-to-clipboard-button-DatoElj8.js → copy-to-clipboard-button-Cg1pF0xn.js} +1 -1
- package/build/assets/{device-verify-BIjXeW9N.js → device-verify-BfQLgNyq.js} +1 -1
- package/build/assets/{dropdown-classes-zFW7qPSK.js → dropdown-classes-BIqQr5mx.js} +1 -1
- package/build/assets/{entry.client-DzbDyLkV.js → entry.client-CxFjRYqH.js} +2 -2
- package/build/assets/{enum-filter-dropdown-CctILg5t.js → enum-filter-dropdown-C_yV4_bo.js} +1 -1
- package/build/assets/{environment-switch-overlay-Cvmo6clk.js → environment-switch-overlay-Bi4qbh2g.js} +1 -1
- package/build/assets/{extensions-hub-DOmHPGTA.js → extensions-hub-BqUINAhp.js} +1 -1
- package/build/assets/{extensions-navigation-6zU5M1pa.js → extensions-navigation-lraj88Wk.js} +1 -1
- package/build/assets/external-link-CgtpA_a1.js +1 -0
- package/build/assets/{file-text-RecuaId_.js → file-text-DEK4LSVw.js} +1 -1
- package/build/assets/files-tab-DV0L-lFE.js +1 -0
- package/build/assets/{form-control-classes-DLN3Qgub.js → form-control-classes-7yFSQJq_.js} +1 -1
- package/build/assets/format-model-name-BN_rx1Y3.js +1 -0
- package/build/assets/{format-relative-time-DGPQlPx0.js → format-relative-time-DSErAuVr.js} +1 -1
- package/build/assets/git-provider-icon-BvlLAche.js +1 -0
- package/build/assets/{git-repo-dropdown-B7RvO70z.js → git-repo-dropdown-ByMzpNko.js} +1 -1
- package/build/assets/git-repo-dropdown-C6R7hgM8.js +1 -0
- package/build/assets/{highlighted-source-view-CgJfD5VT.js → highlighted-source-view-C_sfncLZ.js} +1 -1
- package/build/assets/{home-DEZaZqYv.js → home-BOrlwLqG.js} +1 -1
- package/build/assets/home-BvMNC6E-.js +1 -0
- package/build/assets/{home-store-D7CjMN_-.js → home-store-CxqsY9Fi.js} +1 -1
- package/build/assets/index-home-BvMNC6E-.js +1 -0
- package/build/assets/info-BRVcJAUg.js +1 -0
- package/build/assets/{install-server-modal-BTwK5lbi.js → install-server-modal-Bl4G1zF4.js} +1 -1
- package/build/assets/{kebab-menu-item-content-C4CkeiJO.js → kebab-menu-item-content-BGoGFEAg.js} +1 -1
- package/build/assets/{launch-DVlx4v9o.js → launch-CkqiSCqi.js} +1 -1
- package/build/assets/{list-todo-CheUtB7F.js → list-todo-DNWlzfaM.js} +1 -1
- package/build/assets/llm-not-configured-banner-DLw_tu-G.js +27 -0
- package/build/assets/llm-settings-B-pkVCrt.js +1 -0
- package/build/assets/llm-settings-DSC0-IQ7.js +1 -0
- package/build/assets/llm-subscription-service-BAdOl5Xx.js +1 -0
- package/build/assets/loader-circle-B47W9XqT.js +1 -0
- package/build/assets/{loading-spinner-CN3riY7k.js → loading-spinner-xhnrGP4Y.js} +1 -1
- package/build/assets/{manage-backends-modal-HNWKLYhq.js → manage-backends-modal-DhGk5mJt.js} +1 -1
- package/build/assets/manifest-653b89aa.js +1 -0
- package/build/assets/{manifest-subpage-layout-D-m-h8_t.js → manifest-subpage-layout-CKWvL2ig.js} +1 -1
- package/build/assets/{map-provider-7Bp2aih5.js → map-provider-Mx8mZuE-.js} +1 -1
- package/build/assets/markdown-renderer-DUmk7pFc.js +8 -0
- package/build/assets/{mcp-gLgPP7AS.js → mcp-DCkvl6K0.js} +2 -2
- package/build/assets/mcp-marketplace-utils-DEbMXlQr.js +1 -0
- package/build/assets/messages-DSXvyc21.js +40 -0
- package/build/assets/{modal-body-duRaxj_K.js → modal-body-gIe21fDv.js} +1 -1
- package/build/assets/{modal-classes-CLEXO6dj.js → modal-classes-D66dlgZ7.js} +1 -1
- package/build/assets/{modal-close-button-D0aT02hs.js → modal-close-button-B6iOGYpI.js} +1 -1
- package/build/assets/{mouse-pointer-click-Px-dXWcS.js → mouse-pointer-click-BYX1Pw5W.js} +1 -1
- package/build/assets/{npm-DRzJF5Zn.js → npm-DLckoeJc.js} +1 -1
- package/build/assets/{onboarding-DSr8hOpk.js → onboarding-D7WDG66N.js} +1 -1
- package/build/assets/{onboarding-modal-C6Euf7N4.js → onboarding-modal-C_xVrFNv.js} +1 -1
- package/build/assets/{organization-service.api-C4GYWwj9.js → organization-service.api-D-s9z18a.js} +1 -1
- package/build/assets/pencil-BGOCPRyu.js +1 -0
- package/build/assets/{planner-tab-CZcguxTK.js → planner-tab-AKaXIIfg.js} +1 -1
- package/build/assets/{plugins-management-service-BmDzOQsd.js → plugins-management-service-wtmY4QQw.js} +1 -1
- package/build/assets/plus-zNEL8rcX.js +1 -0
- package/build/assets/{profiles-service.api-CoLTwjc1.js → profiles-service.api-DCMYxHMi.js} +1 -1
- package/build/assets/{providers-tBzTpz1w.js → providers-BjrtjX4L.js} +1 -1
- package/build/assets/{proxy-DCI9S-LA.js → proxy-9Z_WZggZ.js} +1 -1
- package/build/assets/{puzzle-B3v361yY.js → puzzle-BWLmN8z4.js} +1 -1
- package/build/assets/recommended-automations-launcher-CFlzhu1o.js +1 -0
- package/build/assets/refresh-cw-CXtFWpwv.js +1 -0
- package/build/assets/root-CanxKlNV.css +1 -0
- package/build/assets/root-DkfG_UWR.js +2 -0
- package/build/assets/root-layout-D75LHYhh.js +2 -0
- package/build/assets/{schema-field-zhsVDllR.js → schema-field-OnM5Uob_.js} +1 -1
- package/build/assets/{sdk-section-page-CS4GWn0y.js → sdk-section-page-m5WQm2sy.js} +1 -1
- package/build/assets/search-CycosyjN.js +1 -0
- package/build/assets/{secrets-service-DjgGduXk.js → secrets-service-B26R61C1.js} +1 -1
- package/build/assets/{secrets-settings-f-qahfbi.js → secrets-settings-CbLYCKOX.js} +1 -1
- package/build/assets/{segmented-toggle-SmvsR3jW.js → segmented-toggle-DCspm2U5.js} +1 -1
- package/build/assets/{settings-DQd0xLEw.js → settings-BJQI_iBM.js} +1 -1
- package/build/assets/{settings-dropdown-input-TWIeiG02.js → settings-dropdown-input-BI8V_Nov.js} +1 -1
- package/build/assets/{settings-index-BVwy9JEp.js → settings-index-BiTqJoH7.js} +1 -1
- package/build/assets/{settings-input-D61Vzp9e.js → settings-input-Cz62hWVW.js} +1 -1
- package/build/assets/{settings-list-classes-sE0ASr1s.js → settings-list-classes-z_3FW5H3.js} +1 -1
- package/build/assets/{settings-modal-D6D5ngET.js → settings-modal-BIrIVn97.js} +1 -1
- package/build/assets/{settings-service.api-DZp7E7A9.js → settings-service.api--rJmnuLa.js} +1 -1
- package/build/assets/{settings-switch-Drt-rF5s.js → settings-switch-BRsSI8Vs.js} +1 -1
- package/build/assets/{settings-utils-gw2ppAvw.js → settings-utils-Ba7vel6T.js} +1 -1
- package/build/assets/shared-conversation-CQK_PqHx.js +1 -0
- package/build/assets/shield-check-T3JsDJiM.js +1 -0
- package/build/assets/{sidebar-layout-Dxg6zc2K.js → sidebar-layout-Bhi3hVcu.js} +1 -1
- package/build/assets/{sidebar-mobile-menu-toggle-OoZBHxaT.js → sidebar-mobile-menu-toggle-YWJNRN4p.js} +1 -1
- package/build/assets/{sidebar-nav-link-CxLoG-dG.js → sidebar-nav-link-BSUlY5Ml.js} +1 -1
- package/build/assets/{sidebar-store-BJeYY-E9.js → sidebar-store-Cl1cUv_F.js} +1 -1
- package/build/assets/{skill-card-pill-row-B_BYBONu.js → skill-card-pill-row-BXz8L8Rn.js} +1 -1
- package/build/assets/{skill-icon-badge-CN41Hy9l.js → skill-icon-badge-QgfKv31M.js} +1 -1
- package/build/assets/{skill-scope-B1Per5XI.js → skill-scope-Dc3JPW-8.js} +1 -1
- package/build/assets/{skills-plugins-B3AO6sMz.js → skills-plugins-CpTV27n3.js} +1 -1
- package/build/assets/{skills-settings-DmmgJ6CT.js → skills-settings-CSW__IW4.js} +1 -1
- package/build/assets/{sparkles-g7y3r-Ut.js → sparkles-TJ0mdECe.js} +1 -1
- package/build/assets/{styled-tooltip-C5C07RQC.js → styled-tooltip-BUMSbgrF.js} +1 -1
- package/build/assets/{task-list-tab-C19Q_JNf.js → task-list-tab-mAHMOkUz.js} +1 -1
- package/build/assets/{terminal-CbuMRYal.js → terminal-BzxBaHx2.js} +1 -1
- package/build/assets/{toggle-switch-CIioisto.js → toggle-switch-O5v1FgxW.js} +1 -1
- package/build/assets/trash-2-C0xNVp9c.js +1 -0
- package/build/assets/{typography-D9tiHct7.js → typography-NOKSk9_R.js} +1 -1
- package/build/assets/usage-tab-tEmVN0KD.js +1 -0
- package/build/assets/{use-acp-credential-form-DJmmJKMo.js → use-acp-credential-form-BPNi0Zbm.js} +1 -1
- package/build/assets/{use-activate-agent-profile-CeFfYwu_.js → use-activate-agent-profile-D9YRQPTF.js} +1 -1
- package/build/assets/{use-agent-profiles-BHLkLbRR.js → use-agent-profiles-Cb2zUW3l.js} +1 -1
- package/build/assets/{use-can-manage-org-profiles-Bu4y8gSx.js → use-can-manage-org-profiles-Dxzq2AEd.js} +1 -1
- package/build/assets/{use-cloud-current-user-id-QD9x3ySO.js → use-cloud-current-user-id-Co2eJk2A.js} +1 -1
- package/build/assets/{use-config-CPwqciXN.js → use-config-DKAMiI5R.js} +1 -1
- package/build/assets/use-create-conversation-CoHTiCbI.js +1 -0
- package/build/assets/{use-create-secret-BsOfOLvg.js → use-create-secret-2zBlCDsF.js} +1 -1
- package/build/assets/use-is-authed-C52-ivnB.js +1 -0
- package/build/assets/{use-is-creating-conversation-CVefC9Ns.js → use-is-creating-conversation-DdaAE6jV.js} +1 -1
- package/build/assets/{use-launch-skill-in-chat-BcKNjxxd.js → use-launch-skill-in-chat-COnDpxCQ.js} +1 -1
- package/build/assets/use-llm-profiles-C_BHue65.js +1 -0
- package/build/assets/{use-manifest-capabilities-DBYPjyV6.js → use-manifest-capabilities-DPrQsaPP.js} +1 -1
- package/build/assets/{use-onboarding-completion-CtNJxkXG.js → use-onboarding-completion-CCE8OQgV.js} +1 -1
- package/build/assets/{use-plugins-marketplace-ZEA-gyM9.js → use-plugins-marketplace-CEiak69h.js} +1 -1
- package/build/assets/use-save-agent-profile-DK9idD6a.js +1 -0
- package/build/assets/{use-save-settings-pSjRhD_m.js → use-save-settings-DNmnbG6f.js} +1 -1
- package/build/assets/{use-settings-nav-items-CQgkxUbV.js → use-settings-nav-items-BlFL7YhM.js} +1 -1
- package/build/assets/{use-settings-BHWsOcIG.js → use-settings-wCwPaSIv.js} +1 -1
- package/build/assets/{use-tracking-CGCsBWhg.js → use-tracking-CVwPmhlX.js} +1 -1
- package/build/assets/{use-user-conversation-DRfEU8x0.js → use-user-conversation-DVtfg0B_.js} +1 -1
- package/build/assets/{useInfiniteQuery-DbCv0Jzd.js → useInfiniteQuery-p1xq35r2.js} +1 -1
- package/build/assets/{useMutation-ChpkYxI5.js → useMutation-BFDQmgk9.js} +1 -1
- package/build/assets/{useQueries-Cjv3E8_1.js → useQueries-DF1NAAeC.js} +1 -1
- package/build/assets/{useQuery-BCnzy4LX.js → useQuery-DRv--6bK.js} +1 -1
- package/build/assets/utils-zxEjKE70.js +1 -0
- package/build/assets/{vendor~browser-DJ1pl_wn.js → vendor~browser-B-fae1JU.js} +14 -14
- package/build/assets/{vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~kv3pap4j-oGvLV0dO.js → vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~kv3pap4j-B8i84bh4.js} +3 -3
- package/build/assets/{vendor~files-tab-C87pjSpi.js → vendor~files-tab-D9wR9yM2.js} +1 -1
- package/build/assets/vendor~index-home~home~conversation-panel~conversation-Ipoe6hvY.js +13 -0
- package/build/assets/vendor~root-layout-C-IgVx6e.js +1 -0
- package/build/assets/{vendor~root-layout~conversation-panel~conversation-DEzjR4K7.js → vendor~root-layout~conversation-panel~conversation-Dhd_I4H4.js} +1 -1
- package/build/assets/{vendor~root-layout~conversation-panel~conversation~skills-settings~automations-list~shared-~bovqqk8e-U-xdsAEx.js → vendor~root-layout~conversation-panel~conversation~skills-settings~automations-list~shared-~bovqqk8e-Cc0f9kPP.js} +1 -1
- package/build/assets/{vendor~root-layout~index-home~home~conversation-panel~conversation~shared-conversation-CQR5Sa0H.js → vendor~root-layout~index-home~home~conversation-panel~conversation~shared-conversation-d7svw-BB.js} +1 -1
- package/build/assets/{vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~gxu4j2ea-Cwhh3zLe.js → vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~gxu4j2ea-D5DWZVc7.js} +1 -1
- package/build/assets/{vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~hjcjabwg-CuQhkiS7.js → vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~hjcjabwg-4WsUP-tz.js} +2 -2
- package/build/assets/{verification-settings-BItwt998.js → verification-settings-B8UMQ7MG.js} +1 -1
- package/build/assets/{wrench-B1_H-tL1.js → wrench-DIka3hCq.js} +1 -1
- package/build/assets/x-BWKA2Ver.js +1 -0
- package/build/assets/zap-DHddTPc0.js +1 -0
- package/build/index.html +4 -4
- package/build/locales/ar/openhands.json +28 -1
- package/build/locales/ca/openhands.json +28 -1
- package/build/locales/de/openhands.json +28 -1
- package/build/locales/en/openhands.json +28 -1
- package/build/locales/es/openhands.json +28 -1
- package/build/locales/fr/openhands.json +28 -1
- package/build/locales/it/openhands.json +28 -1
- package/build/locales/ja/openhands.json +28 -1
- package/build/locales/ko-KR/openhands.json +28 -1
- package/build/locales/no/openhands.json +28 -1
- package/build/locales/pt/openhands.json +28 -1
- package/build/locales/tr/openhands.json +28 -1
- package/build/locales/uk/openhands.json +28 -1
- package/build/locales/zh-CN/openhands.json +28 -1
- package/build/locales/zh-TW/openhands.json +28 -1
- package/config/defaults.json +3 -3
- package/dist/api/agent-server-compatibility.cjs +1 -1
- package/dist/api/agent-server-compatibility.cjs.map +1 -1
- package/dist/api/agent-server-compatibility.js +1 -1
- package/dist/api/agent-server-compatibility.js.map +1 -1
- package/dist/api/conversation-service/agent-server-conversation-service.api.cjs +1 -1
- package/dist/api/conversation-service/agent-server-conversation-service.api.cjs.map +1 -1
- package/dist/api/conversation-service/agent-server-conversation-service.api.d.ts +24 -1
- package/dist/api/conversation-service/agent-server-conversation-service.api.js +116 -97
- package/dist/api/conversation-service/agent-server-conversation-service.api.js.map +1 -1
- package/dist/api/llm-balance-service.cjs +2 -0
- package/dist/api/llm-balance-service.cjs.map +1 -0
- package/dist/api/llm-balance-service.d.ts +33 -0
- package/dist/api/llm-balance-service.js +53 -0
- package/dist/api/llm-balance-service.js.map +1 -0
- package/dist/api/llm-subscription-service.cjs +1 -1
- package/dist/api/llm-subscription-service.cjs.map +1 -1
- package/dist/api/llm-subscription-service.js +42 -34
- package/dist/api/llm-subscription-service.js.map +1 -1
- package/dist/components/conversation/index.cjs +1 -1
- package/dist/components/conversation/index.d.ts +0 -1
- package/dist/components/conversation/index.js +0 -1
- package/dist/components/conversation-events/chat/event-content-helpers/get-event-content.cjs +1 -1
- package/dist/components/conversation-events/chat/event-content-helpers/get-event-content.cjs.map +1 -1
- package/dist/components/conversation-events/chat/event-content-helpers/get-event-content.js +1 -1
- package/dist/components/conversation-events/chat/event-content-helpers/get-event-content.js.map +1 -1
- package/dist/components/conversation-events/chat/event-message-components/generic-event-message-wrapper.cjs +1 -1
- package/dist/components/conversation-events/chat/event-message-components/generic-event-message-wrapper.cjs.map +1 -1
- package/dist/components/conversation-events/chat/event-message-components/generic-event-message-wrapper.js +24 -23
- package/dist/components/conversation-events/chat/event-message-components/generic-event-message-wrapper.js.map +1 -1
- package/dist/components/conversation-events/chat/group-events.cjs +1 -1
- package/dist/components/conversation-events/chat/group-events.cjs.map +1 -1
- package/dist/components/conversation-events/chat/group-events.d.ts +3 -3
- package/dist/components/conversation-events/chat/group-events.js +27 -26
- package/dist/components/conversation-events/chat/group-events.js.map +1 -1
- package/dist/components/features/automations/recommended-automations-section.d.ts +7 -0
- package/dist/components/features/chat/components/chat-input-actions.cjs +1 -1
- package/dist/components/features/chat/components/chat-input-actions.cjs.map +1 -1
- package/dist/components/features/chat/components/chat-input-actions.js +123 -118
- package/dist/components/features/chat/components/chat-input-actions.js.map +1 -1
- package/dist/components/features/chat/components/chat-input-grip.cjs +1 -1
- package/dist/components/features/chat/components/chat-input-grip.cjs.map +1 -1
- package/dist/components/features/chat/components/chat-input-grip.d.ts +2 -1
- package/dist/components/features/chat/components/chat-input-grip.js +7 -7
- package/dist/components/features/chat/components/chat-input-grip.js.map +1 -1
- package/dist/components/features/chat/components/chat-input-llm-profile-picker.cjs +1 -1
- package/dist/components/features/chat/components/chat-input-llm-profile-picker.cjs.map +1 -1
- package/dist/components/features/chat/components/chat-input-llm-profile-picker.js +61 -60
- package/dist/components/features/chat/components/chat-input-llm-profile-picker.js.map +1 -1
- package/dist/components/features/chat/components/context-window-meter.cjs +2 -0
- package/dist/components/features/chat/components/context-window-meter.cjs.map +1 -0
- package/dist/components/features/chat/components/context-window-meter.d.ts +2 -0
- package/dist/components/features/chat/components/context-window-meter.js +131 -0
- package/dist/components/features/chat/components/context-window-meter.js.map +1 -0
- package/dist/components/features/chat/components/context-window-ring.cjs +2 -0
- package/dist/components/features/chat/components/context-window-ring.cjs.map +1 -0
- package/dist/components/features/chat/components/context-window-ring.d.ts +26 -0
- package/dist/components/features/chat/components/context-window-ring.js +45 -0
- package/dist/components/features/chat/components/context-window-ring.js.map +1 -0
- package/dist/components/features/chat/custom-chat-input.cjs +1 -1
- package/dist/components/features/chat/custom-chat-input.cjs.map +1 -1
- package/dist/components/features/chat/custom-chat-input.js +68 -67
- package/dist/components/features/chat/custom-chat-input.js.map +1 -1
- package/dist/components/features/chat/model-messages.cjs +1 -1
- package/dist/components/features/chat/model-messages.cjs.map +1 -1
- package/dist/components/features/chat/model-messages.js +26 -25
- package/dist/components/features/chat/model-messages.js.map +1 -1
- package/dist/components/features/chat/path-component.cjs +1 -1
- package/dist/components/features/chat/path-component.cjs.map +1 -1
- package/dist/components/features/chat/path-component.js +9 -6
- package/dist/components/features/chat/path-component.js.map +1 -1
- package/dist/components/features/chat/plan-preview.cjs +1 -1
- package/dist/components/features/chat/plan-preview.js +4 -4
- package/dist/components/features/chat/tool-visualizers/file-editor/file-editor.cjs +1 -1
- package/dist/components/features/chat/tool-visualizers/file-editor/file-editor.cjs.map +1 -1
- package/dist/components/features/chat/tool-visualizers/file-editor/file-editor.d.ts +0 -8
- package/dist/components/features/chat/tool-visualizers/file-editor/file-editor.js +92 -50
- package/dist/components/features/chat/tool-visualizers/file-editor/file-editor.js.map +1 -1
- package/dist/components/features/chat/tool-visualizers/primitives/file-path-chip.cjs +1 -1
- package/dist/components/features/chat/tool-visualizers/primitives/file-path-chip.cjs.map +1 -1
- package/dist/components/features/chat/tool-visualizers/primitives/file-path-chip.d.ts +7 -2
- package/dist/components/features/chat/tool-visualizers/primitives/file-path-chip.js +15 -9
- package/dist/components/features/chat/tool-visualizers/primitives/file-path-chip.js.map +1 -1
- package/dist/components/features/chat/tool-visualizers/primitives/markdown-file-preview.cjs +2 -0
- package/dist/components/features/chat/tool-visualizers/primitives/markdown-file-preview.cjs.map +1 -0
- package/dist/components/features/chat/tool-visualizers/primitives/markdown-file-preview.d.ts +20 -0
- package/dist/components/features/chat/tool-visualizers/primitives/markdown-file-preview.js +76 -0
- package/dist/components/features/chat/tool-visualizers/primitives/markdown-file-preview.js.map +1 -0
- package/dist/components/features/controls/server-status-context-menu.cjs +1 -1
- package/dist/components/features/controls/server-status-context-menu.cjs.map +1 -1
- package/dist/components/features/controls/server-status-context-menu.d.ts +5 -3
- package/dist/components/features/controls/server-status-context-menu.js +8 -8
- package/dist/components/features/controls/server-status-context-menu.js.map +1 -1
- package/dist/components/features/conversation/conversation-name-context-menu.cjs +1 -1
- package/dist/components/features/conversation/conversation-name-context-menu.cjs.map +1 -1
- package/dist/components/features/conversation/conversation-name-context-menu.js +58 -61
- package/dist/components/features/conversation/conversation-name-context-menu.js.map +1 -1
- package/dist/components/features/conversation/conversation-name-with-status.cjs +1 -1
- package/dist/components/features/conversation/conversation-name-with-status.cjs.map +1 -1
- package/dist/components/features/conversation/conversation-name-with-status.js +74 -46
- package/dist/components/features/conversation/conversation-name-with-status.js.map +1 -1
- package/dist/components/features/conversation/conversation-name.cjs +1 -1
- package/dist/components/features/conversation/conversation-name.cjs.map +1 -1
- package/dist/components/features/conversation/conversation-name.js +69 -71
- package/dist/components/features/conversation/conversation-name.js.map +1 -1
- package/dist/components/features/conversation/conversation-tabs/conversation-tab-content/conversation-tab-content.cjs +1 -1
- package/dist/components/features/conversation/conversation-tabs/conversation-tab-content/conversation-tab-content.cjs.map +1 -1
- package/dist/components/features/conversation/conversation-tabs/conversation-tab-content/conversation-tab-content.js +7 -6
- package/dist/components/features/conversation/conversation-tabs/conversation-tab-content/conversation-tab-content.js.map +1 -1
- package/dist/components/features/conversation/conversation-tabs/conversation-tabs-context-menu.cjs +1 -1
- package/dist/components/features/conversation/conversation-tabs/conversation-tabs-context-menu.cjs.map +1 -1
- package/dist/components/features/conversation/conversation-tabs/conversation-tabs-context-menu.js +92 -86
- package/dist/components/features/conversation/conversation-tabs/conversation-tabs-context-menu.js.map +1 -1
- package/dist/components/features/conversation/conversation-tabs/conversation-tabs.cjs +1 -1
- package/dist/components/features/conversation/conversation-tabs/conversation-tabs.cjs.map +1 -1
- package/dist/components/features/conversation/conversation-tabs/conversation-tabs.js +81 -71
- package/dist/components/features/conversation/conversation-tabs/conversation-tabs.js.map +1 -1
- package/dist/components/features/conversation/metrics-modal/cost-section.cjs +1 -1
- package/dist/components/features/conversation/metrics-modal/cost-section.cjs.map +1 -1
- package/dist/components/features/conversation/metrics-modal/cost-section.js +6 -6
- package/dist/components/features/conversation/metrics-modal/cost-section.js.map +1 -1
- package/dist/components/features/conversation/metrics-modal/metric-row.cjs +1 -1
- package/dist/components/features/conversation/metrics-modal/metric-row.cjs.map +1 -1
- package/dist/components/features/conversation/metrics-modal/metric-row.d.ts +2 -1
- package/dist/components/features/conversation/metrics-modal/metric-row.js +10 -9
- package/dist/components/features/conversation/metrics-modal/metric-row.js.map +1 -1
- package/dist/components/features/conversation/metrics-modal/usage-section.cjs +1 -1
- package/dist/components/features/conversation/metrics-modal/usage-section.cjs.map +1 -1
- package/dist/components/features/conversation/metrics-modal/usage-section.js +2 -1
- package/dist/components/features/conversation/metrics-modal/usage-section.js.map +1 -1
- package/dist/components/features/conversation/usage-panel/compact-context-button.cjs +2 -0
- package/dist/components/features/conversation/usage-panel/compact-context-button.cjs.map +1 -0
- package/dist/components/features/conversation/usage-panel/compact-context-button.d.ts +16 -0
- package/dist/components/features/conversation/usage-panel/compact-context-button.js +59 -0
- package/dist/components/features/conversation/usage-panel/compact-context-button.js.map +1 -0
- package/dist/components/features/conversation/usage-panel/context-meter.cjs +2 -0
- package/dist/components/features/conversation/usage-panel/context-meter.cjs.map +1 -0
- package/dist/components/features/conversation/usage-panel/context-meter.d.ts +19 -0
- package/dist/components/features/conversation/usage-panel/context-meter.js +46 -0
- package/dist/components/features/conversation/usage-panel/context-meter.js.map +1 -0
- package/dist/components/features/conversation/usage-panel/provider-balance-card.cjs +2 -0
- package/dist/components/features/conversation/usage-panel/provider-balance-card.cjs.map +1 -0
- package/dist/components/features/conversation/usage-panel/provider-balance-card.d.ts +7 -0
- package/dist/components/features/conversation/usage-panel/provider-balance-card.js +57 -0
- package/dist/components/features/conversation/usage-panel/provider-balance-card.js.map +1 -0
- package/dist/components/features/conversation/usage-panel/usage-panel.cjs +2 -0
- package/dist/components/features/conversation/usage-panel/usage-panel.cjs.map +1 -0
- package/dist/components/features/conversation/usage-panel/usage-panel.d.ts +9 -0
- package/dist/components/features/conversation/usage-panel/usage-panel.js +68 -0
- package/dist/components/features/conversation/usage-panel/usage-panel.js.map +1 -0
- package/dist/components/features/conversation-panel/budget-display.cjs +1 -1
- package/dist/components/features/conversation-panel/budget-display.cjs.map +1 -1
- package/dist/components/features/conversation-panel/budget-display.js +10 -13
- package/dist/components/features/conversation-panel/budget-display.js.map +1 -1
- package/dist/components/features/conversation-panel/budget-progress-bar.cjs +1 -1
- package/dist/components/features/conversation-panel/budget-progress-bar.cjs.map +1 -1
- package/dist/components/features/conversation-panel/budget-progress-bar.js +2 -2
- package/dist/components/features/conversation-panel/budget-progress-bar.js.map +1 -1
- package/dist/components/features/conversation-panel/confirm-archive-modal.cjs +2 -0
- package/dist/components/features/conversation-panel/confirm-archive-modal.cjs.map +1 -0
- package/dist/components/features/conversation-panel/confirm-archive-modal.d.ts +8 -0
- package/dist/components/features/conversation-panel/confirm-archive-modal.js +48 -0
- package/dist/components/features/conversation-panel/confirm-archive-modal.js.map +1 -0
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-actions.cjs +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-actions.cjs.map +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-actions.d.ts +3 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-actions.js +39 -34
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-actions.js.map +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-context-menu.cjs +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-context-menu.cjs.map +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-context-menu.d.ts +3 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-context-menu.js +104 -80
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-context-menu.js.map +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-footer.cjs +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-footer.cjs.map +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-footer.d.ts +7 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-footer.js +28 -20
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-footer.js.map +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card.cjs +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card.cjs.map +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card.d.ts +9 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card.js +94 -84
- package/dist/components/features/conversation-panel/conversation-card/conversation-card.js.map +1 -1
- package/dist/components/features/conversation-panel/conversation-group-folder-list.cjs +1 -1
- package/dist/components/features/conversation-panel/conversation-group-folder-list.cjs.map +1 -1
- package/dist/components/features/conversation-panel/conversation-group-folder-list.d.ts +2 -1
- package/dist/components/features/conversation-panel/conversation-group-folder-list.js +32 -31
- package/dist/components/features/conversation-panel/conversation-group-folder-list.js.map +1 -1
- package/dist/components/features/conversation-panel/conversation-group-folder-row.cjs +1 -1
- package/dist/components/features/conversation-panel/conversation-group-folder-row.cjs.map +1 -1
- package/dist/components/features/conversation-panel/conversation-group-folder-row.d.ts +2 -1
- package/dist/components/features/conversation-panel/conversation-group-folder-row.js +27 -26
- package/dist/components/features/conversation-panel/conversation-group-folder-row.js.map +1 -1
- package/dist/components/features/conversation-panel/conversation-panel-filter-menu.cjs +1 -1
- package/dist/components/features/conversation-panel/conversation-panel-filter-menu.cjs.map +1 -1
- package/dist/components/features/conversation-panel/conversation-panel-filter-menu.d.ts +3 -1
- package/dist/components/features/conversation-panel/conversation-panel-filter-menu.js +116 -106
- package/dist/components/features/conversation-panel/conversation-panel-filter-menu.js.map +1 -1
- package/dist/components/features/conversation-panel/conversation-panel-list-helpers.cjs +1 -1
- package/dist/components/features/conversation-panel/conversation-panel-list-helpers.cjs.map +1 -1
- package/dist/components/features/conversation-panel/conversation-panel-list-helpers.d.ts +27 -0
- package/dist/components/features/conversation-panel/conversation-panel-list-helpers.js +69 -46
- package/dist/components/features/conversation-panel/conversation-panel-list-helpers.js.map +1 -1
- package/dist/components/features/conversation-panel/conversation-panel.cjs +1 -1
- package/dist/components/features/conversation-panel/conversation-panel.cjs.map +1 -1
- package/dist/components/features/conversation-panel/conversation-panel.js +230 -165
- package/dist/components/features/conversation-panel/conversation-panel.js.map +1 -1
- package/dist/components/features/files-tab/file-content-viewer.cjs +1 -1
- package/dist/components/features/files-tab/file-content-viewer.cjs.map +1 -1
- package/dist/components/features/files-tab/file-content-viewer.js +22 -25
- package/dist/components/features/files-tab/file-content-viewer.js.map +1 -1
- package/dist/components/features/markdown/markdown-table-scroll.cjs +1 -1
- package/dist/components/features/markdown/markdown-table-scroll.cjs.map +1 -1
- package/dist/components/features/markdown/markdown-table-scroll.js +2 -2
- package/dist/components/features/markdown/markdown-table-scroll.js.map +1 -1
- package/dist/components/features/settings/llm-profiles/profile-row.cjs +1 -1
- package/dist/components/features/settings/llm-profiles/profile-row.js +1 -0
- package/dist/components/shared/buttons/conversation-confirmation-buttons.cjs +1 -1
- package/dist/components/shared/buttons/conversation-confirmation-buttons.cjs.map +1 -1
- package/dist/components/shared/buttons/conversation-confirmation-buttons.js +2 -2
- package/dist/components/shared/buttons/conversation-confirmation-buttons.js.map +1 -1
- package/dist/components/shared/modals/confirmation-modals/base-modal.cjs +1 -1
- package/dist/components/shared/modals/confirmation-modals/base-modal.cjs.map +1 -1
- package/dist/components/shared/modals/confirmation-modals/base-modal.js +1 -1
- package/dist/components/shared/modals/confirmation-modals/base-modal.js.map +1 -1
- package/dist/components/shared/modals/settings/model-selector.cjs +1 -1
- package/dist/components/shared/modals/settings/model-selector.cjs.map +1 -1
- package/dist/components/shared/modals/settings/model-selector.js +154 -98
- package/dist/components/shared/modals/settings/model-selector.js.map +1 -1
- package/dist/config/defaults.cjs +1 -1
- package/dist/config/defaults.cjs.map +1 -1
- package/dist/config/defaults.js +3 -3
- package/dist/config/defaults.js.map +1 -1
- package/dist/constants/llm-balance.cjs +2 -0
- package/dist/constants/llm-balance.cjs.map +1 -0
- package/dist/constants/llm-balance.d.ts +13 -0
- package/dist/constants/llm-balance.js +6 -0
- package/dist/constants/llm-balance.js.map +1 -0
- package/dist/contexts/conversation-websocket-context.cjs +2 -2
- package/dist/contexts/conversation-websocket-context.cjs.map +1 -1
- package/dist/contexts/conversation-websocket-context.js +217 -209
- package/dist/contexts/conversation-websocket-context.js.map +1 -1
- package/dist/hooks/chat/use-grip-resize.cjs +1 -1
- package/dist/hooks/chat/use-grip-resize.cjs.map +1 -1
- package/dist/hooks/chat/use-grip-resize.d.ts +1 -0
- package/dist/hooks/chat/use-grip-resize.js +39 -30
- package/dist/hooks/chat/use-grip-resize.js.map +1 -1
- package/dist/hooks/chat/use-slash-command.cjs +1 -1
- package/dist/hooks/chat/use-slash-command.cjs.map +1 -1
- package/dist/hooks/chat/use-slash-command.js +61 -60
- package/dist/hooks/chat/use-slash-command.js.map +1 -1
- package/dist/hooks/mutation/use-condense-conversation.cjs +2 -0
- package/dist/hooks/mutation/use-condense-conversation.cjs.map +1 -0
- package/dist/hooks/mutation/use-condense-conversation.d.ts +12 -0
- package/dist/hooks/mutation/use-condense-conversation.js +17 -0
- package/dist/hooks/mutation/use-condense-conversation.js.map +1 -0
- package/dist/hooks/mutation/use-create-conversation.cjs +1 -1
- package/dist/hooks/mutation/use-create-conversation.cjs.map +1 -1
- package/dist/hooks/mutation/use-create-conversation.js +30 -21
- package/dist/hooks/mutation/use-create-conversation.js.map +1 -1
- package/dist/hooks/mutation/use-new-conversation-command.cjs +1 -1
- package/dist/hooks/mutation/use-new-conversation-command.cjs.map +1 -1
- package/dist/hooks/mutation/use-new-conversation-command.js +1 -1
- package/dist/hooks/mutation/use-new-conversation-command.js.map +1 -1
- package/dist/hooks/query/use-conversation-metrics.cjs +1 -1
- package/dist/hooks/query/use-conversation-metrics.cjs.map +1 -1
- package/dist/hooks/query/use-conversation-metrics.js +1 -1
- package/dist/hooks/query/use-conversation-metrics.js.map +1 -1
- package/dist/hooks/query/use-llm-balance.cjs +2 -0
- package/dist/hooks/query/use-llm-balance.cjs.map +1 -0
- package/dist/hooks/query/use-llm-balance.d.ts +10 -0
- package/dist/hooks/query/use-llm-balance.js +24 -0
- package/dist/hooks/query/use-llm-balance.js.map +1 -0
- package/dist/hooks/query/use-search-providers.cjs +1 -1
- package/dist/hooks/query/use-search-providers.cjs.map +1 -1
- package/dist/hooks/query/use-search-providers.js +1 -1
- package/dist/hooks/query/use-search-providers.js.map +1 -1
- package/dist/hooks/use-await-context-compaction.cjs +2 -0
- package/dist/hooks/use-await-context-compaction.cjs.map +1 -0
- package/dist/hooks/use-await-context-compaction.d.ts +38 -0
- package/dist/hooks/use-await-context-compaction.js +61 -0
- package/dist/hooks/use-await-context-compaction.js.map +1 -0
- package/dist/hooks/use-compact-context-action.cjs +2 -0
- package/dist/hooks/use-compact-context-action.cjs.map +1 -0
- package/dist/hooks/use-compact-context-action.d.ts +10 -0
- package/dist/hooks/use-compact-context-action.js +61 -0
- package/dist/hooks/use-compact-context-action.js.map +1 -0
- package/dist/hooks/use-context-window-usage.cjs +2 -0
- package/dist/hooks/use-context-window-usage.cjs.map +1 -0
- package/dist/hooks/use-context-window-usage.d.ts +5 -0
- package/dist/hooks/use-context-window-usage.js +20 -0
- package/dist/hooks/use-context-window-usage.js.map +1 -0
- package/dist/hooks/use-conversation-name-context-menu.cjs +1 -1
- package/dist/hooks/use-conversation-name-context-menu.cjs.map +1 -1
- package/dist/hooks/use-conversation-name-context-menu.d.ts +0 -3
- package/dist/hooks/use-conversation-name-context-menu.js +54 -57
- package/dist/hooks/use-conversation-name-context-menu.js.map +1 -1
- package/dist/hooks/use-drag-resize.cjs +1 -1
- package/dist/hooks/use-drag-resize.cjs.map +1 -1
- package/dist/hooks/use-drag-resize.d.ts +8 -0
- package/dist/hooks/use-drag-resize.js +21 -18
- package/dist/hooks/use-drag-resize.js.map +1 -1
- package/dist/hooks/use-live-conversation-metrics.cjs +2 -0
- package/dist/hooks/use-live-conversation-metrics.cjs.map +1 -0
- package/dist/hooks/use-live-conversation-metrics.d.ts +18 -0
- package/dist/hooks/use-live-conversation-metrics.js +37 -0
- package/dist/hooks/use-live-conversation-metrics.js.map +1 -0
- package/dist/hooks/use-select-conversation-tab.cjs +1 -1
- package/dist/hooks/use-select-conversation-tab.cjs.map +1 -1
- package/dist/hooks/use-select-conversation-tab.js +2 -2
- package/dist/hooks/use-select-conversation-tab.js.map +1 -1
- package/dist/i18n/declaration.cjs +1 -1
- package/dist/i18n/declaration.cjs.map +1 -1
- package/dist/i18n/declaration.d.ts +27 -0
- package/dist/i18n/declaration.js +1 -1
- package/dist/i18n/declaration.js.map +1 -1
- package/dist/i18n/translation.cjs +2 -2
- package/dist/i18n/translation.cjs.map +1 -1
- package/dist/i18n/translation.js +474 -15
- package/dist/i18n/translation.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +28 -29
- package/dist/lib/index.cjs +1 -1
- package/dist/lib/index.js +0 -1
- package/dist/locales/ar/openhands.json +28 -1
- package/dist/locales/ca/openhands.json +28 -1
- package/dist/locales/de/openhands.json +28 -1
- package/dist/locales/en/openhands.json +28 -1
- package/dist/locales/es/openhands.json +28 -1
- package/dist/locales/fr/openhands.json +28 -1
- package/dist/locales/it/openhands.json +28 -1
- package/dist/locales/ja/openhands.json +28 -1
- package/dist/locales/ko-KR/openhands.json +28 -1
- package/dist/locales/no/openhands.json +28 -1
- package/dist/locales/pt/openhands.json +28 -1
- package/dist/locales/tr/openhands.json +28 -1
- package/dist/locales/uk/openhands.json +28 -1
- package/dist/locales/zh-CN/openhands.json +28 -1
- package/dist/locales/zh-TW/openhands.json +28 -1
- package/dist/node_modules/@microlink/react-json-view/dist/main.cjs +5 -5
- package/dist/node_modules/@microlink/react-json-view/dist/main.cjs.map +1 -1
- package/dist/node_modules/@microlink/react-json-view/dist/main.js +5194 -5187
- package/dist/node_modules/@microlink/react-json-view/dist/main.js.map +1 -1
- package/dist/node_modules/@openhands/typescript-client/dist/client/desktop-client.cjs +2 -1
- package/dist/node_modules/@openhands/typescript-client/dist/client/desktop-client.cjs.map +1 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/desktop-client.js +21 -1
- package/dist/node_modules/@openhands/typescript-client/dist/client/desktop-client.js.map +1 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/hooks-client.cjs +2 -1
- package/dist/node_modules/@openhands/typescript-client/dist/client/hooks-client.cjs.map +1 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/hooks-client.js +22 -1
- package/dist/node_modules/@openhands/typescript-client/dist/client/hooks-client.js.map +1 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/meta-profiles-client.cjs +2 -1
- package/dist/node_modules/@openhands/typescript-client/dist/client/meta-profiles-client.cjs.map +1 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/meta-profiles-client.js +33 -1
- package/dist/node_modules/@openhands/typescript-client/dist/client/meta-profiles-client.js.map +1 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/openhands-client.cjs +1 -1
- package/dist/node_modules/@openhands/typescript-client/dist/client/openhands-client.cjs.map +1 -1
- package/dist/node_modules/@openhands/typescript-client/dist/client/openhands-client.js +56 -22
- package/dist/node_modules/@openhands/typescript-client/dist/client/openhands-client.js.map +1 -1
- package/dist/node_modules/@openhands/typescript-client/dist/client/profiles-client.cjs +1 -1
- package/dist/node_modules/@openhands/typescript-client/dist/client/profiles-client.cjs.map +1 -1
- package/dist/node_modules/@openhands/typescript-client/dist/client/profiles-client.js +3 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/profiles-client.js.map +1 -1
- package/dist/node_modules/@openhands/typescript-client/dist/client/shared-client.cjs +2 -1
- package/dist/node_modules/@openhands/typescript-client/dist/client/shared-client.cjs.map +1 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/shared-client.js +31 -1
- package/dist/node_modules/@openhands/typescript-client/dist/client/shared-client.js.map +1 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/sub-agents-client.cjs +2 -1
- package/dist/node_modules/@openhands/typescript-client/dist/client/sub-agents-client.cjs.map +1 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/sub-agents-client.js +21 -1
- package/dist/node_modules/@openhands/typescript-client/dist/client/sub-agents-client.js.map +1 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/tool-client.cjs +2 -1
- package/dist/node_modules/@openhands/typescript-client/dist/client/tool-client.cjs.map +1 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/tool-client.js +21 -1
- package/dist/node_modules/@openhands/typescript-client/dist/client/tool-client.js.map +1 -0
- package/dist/node_modules/@openhands/typescript-client/dist/conversation/local-conversation.cjs.map +1 -1
- package/dist/node_modules/@openhands/typescript-client/dist/conversation/local-conversation.js.map +1 -1
- package/dist/node_modules/@openhands/typescript-client/dist/models/acp-providers.cjs +1 -1
- package/dist/node_modules/@openhands/typescript-client/dist/models/acp-providers.cjs.map +1 -1
- package/dist/node_modules/@openhands/typescript-client/dist/models/acp-providers.js +9 -5
- package/dist/node_modules/@openhands/typescript-client/dist/models/acp-providers.js.map +1 -1
- package/dist/node_modules/i18next-http-backend/esm/index.cjs +1 -1
- package/dist/node_modules/i18next-http-backend/esm/index.cjs.map +1 -1
- package/dist/node_modules/i18next-http-backend/esm/index.js +2 -2
- package/dist/node_modules/i18next-http-backend/esm/index.js.map +1 -1
- package/dist/node_modules/lucide-react/dist/esm/icons/archive-restore.cjs +2 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/archive-restore.cjs.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/archive-restore.js +31 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/archive-restore.js.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/archive.cjs +2 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/archive.cjs.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/archive.js +23 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/archive.js.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/gauge.cjs +2 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/gauge.cjs.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/gauge.js +12 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/gauge.js.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/minimize.cjs +2 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/minimize.cjs.map +1 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/minimize.js +23 -0
- package/dist/node_modules/lucide-react/dist/esm/icons/minimize.js.map +1 -0
- package/dist/node_modules/zustand/esm/middleware.cjs +3 -3
- package/dist/node_modules/zustand/esm/middleware.cjs.map +1 -1
- package/dist/node_modules/zustand/esm/middleware.js +33 -31
- package/dist/node_modules/zustand/esm/middleware.js.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.cjs.map +1 -1
- package/dist/package.js +17 -16
- package/dist/package.js.map +1 -1
- package/dist/routes/app-settings.cjs +1 -1
- package/dist/routes/app-settings.cjs.map +1 -1
- package/dist/routes/app-settings.js +94 -93
- package/dist/routes/app-settings.js.map +1 -1
- package/dist/routes/llm-settings.cjs +1 -1
- package/dist/routes/llm-settings.cjs.map +1 -1
- package/dist/routes/llm-settings.js +131 -119
- package/dist/routes/llm-settings.js.map +1 -1
- package/dist/routes/usage-tab.cjs +2 -0
- package/dist/routes/usage-tab.cjs.map +1 -0
- package/dist/routes/usage-tab.d.ts +2 -0
- package/dist/routes/usage-tab.js +10 -0
- package/dist/routes/usage-tab.js.map +1 -0
- package/dist/services/child-conversation-launch.cjs +1 -1
- package/dist/services/child-conversation-launch.cjs.map +1 -1
- package/dist/services/child-conversation-launch.js +11 -5
- package/dist/services/child-conversation-launch.js.map +1 -1
- package/dist/stores/archived-conversations-store.cjs +2 -0
- package/dist/stores/archived-conversations-store.cjs.map +1 -0
- package/dist/stores/archived-conversations-store.d.ts +24 -0
- package/dist/stores/archived-conversations-store.js +33 -0
- package/dist/stores/archived-conversations-store.js.map +1 -0
- package/dist/stores/conversation-panel-preferences-store.cjs +1 -1
- package/dist/stores/conversation-panel-preferences-store.cjs.map +1 -1
- package/dist/stores/conversation-panel-preferences-store.d.ts +3 -0
- package/dist/stores/conversation-panel-preferences-store.js +4 -0
- package/dist/stores/conversation-panel-preferences-store.js.map +1 -1
- package/dist/stores/conversation-store.cjs.map +1 -1
- package/dist/stores/conversation-store.d.ts +1 -1
- package/dist/stores/conversation-store.js.map +1 -1
- package/dist/stores/metrics-store.cjs +1 -1
- package/dist/stores/metrics-store.cjs.map +1 -1
- package/dist/stores/metrics-store.d.ts +1 -0
- package/dist/stores/metrics-store.js +7 -4
- package/dist/stores/metrics-store.js.map +1 -1
- package/dist/types/agent-server/core/events/condensation-event.d.ts +3 -0
- package/dist/types/agent-server/core/events/conversation-state-event.d.ts +4 -6
- package/dist/types/agent-server/type-guards.cjs +1 -1
- package/dist/types/agent-server/type-guards.cjs.map +1 -1
- package/dist/types/agent-server/type-guards.d.ts +5 -0
- package/dist/types/agent-server/type-guards.js +3 -3
- package/dist/types/agent-server/type-guards.js.map +1 -1
- package/dist/utils/conversation-local-storage.cjs +1 -1
- package/dist/utils/conversation-local-storage.cjs.map +1 -1
- package/dist/utils/conversation-local-storage.js +2 -1
- package/dist/utils/conversation-local-storage.js.map +1 -1
- package/dist/utils/format-model-name.cjs +1 -1
- package/dist/utils/format-model-name.cjs.map +1 -1
- package/dist/utils/format-model-name.d.ts +11 -0
- package/dist/utils/format-model-name.js +11 -3
- package/dist/utils/format-model-name.js.map +1 -1
- package/dist/utils/format-token-count.cjs +2 -0
- package/dist/utils/format-token-count.cjs.map +1 -0
- package/dist/utils/format-token-count.d.ts +3 -0
- package/dist/utils/format-token-count.js +19 -0
- package/dist/utils/format-token-count.js.map +1 -0
- package/dist/utils/hover-reveal-classes.cjs +2 -0
- package/dist/utils/hover-reveal-classes.cjs.map +1 -0
- package/dist/utils/hover-reveal-classes.d.ts +34 -0
- package/dist/utils/hover-reveal-classes.js +41 -0
- package/dist/utils/hover-reveal-classes.js.map +1 -0
- package/dist/utils/is-markdown-file-path.cjs +2 -0
- package/dist/utils/is-markdown-file-path.cjs.map +1 -0
- package/dist/utils/is-markdown-file-path.d.ts +4 -0
- package/dist/utils/is-markdown-file-path.js +14 -0
- package/dist/utils/is-markdown-file-path.js.map +1 -0
- package/dist/utils/mcp-marketplace-utils.cjs +1 -1
- package/dist/utils/mcp-marketplace-utils.cjs.map +1 -1
- package/dist/utils/mcp-marketplace-utils.d.ts +7 -0
- package/dist/utils/mcp-marketplace-utils.js +19 -16
- package/dist/utils/mcp-marketplace-utils.js.map +1 -1
- package/dist/utils/transcript-export/index.cjs +3 -3
- package/dist/utils/transcript-export/index.cjs.map +1 -1
- package/dist/utils/transcript-export/index.js +12 -12
- package/dist/utils/transcript-export/index.js.map +1 -1
- package/dist/utils/transcript-export/load-complete-events.cjs +1 -1
- package/dist/utils/transcript-export/load-complete-events.cjs.map +1 -1
- package/dist/utils/transcript-export/load-complete-events.js +4 -4
- package/dist/utils/transcript-export/load-complete-events.js.map +1 -1
- package/dist/utils/workspace-relative-path.cjs +2 -0
- package/dist/utils/workspace-relative-path.cjs.map +1 -0
- package/dist/utils/workspace-relative-path.d.ts +10 -0
- package/dist/utils/workspace-relative-path.js +22 -0
- package/dist/utils/workspace-relative-path.js.map +1 -0
- package/package.json +17 -16
- package/scripts/check-sdk-version-sync.mjs +6 -6
- package/scripts/dev-process-utils.mjs +6 -1
- package/scripts/dev-safe.mjs +57 -2
- package/build/assets/active-backend-context-BBvP-bI2.js +0 -2
- package/build/assets/agent-profiles-settings-DqoS7RL3.js +0 -1
- package/build/assets/agent-server-conversation-service.api-0RztSdJJ.js +0 -86
- package/build/assets/agent-settings-BJHryp65.js +0 -1
- package/build/assets/app-settings-r1RUbgGi.js +0 -1
- package/build/assets/backend-form-modal-OroTqnAv.js +0 -1
- package/build/assets/bot-DuN83i3p.js +0 -1
- package/build/assets/browser--eoQOo4M.js +0 -6
- package/build/assets/check-DLOKoFU9.js +0 -1
- package/build/assets/chevron-down-CnIbtM6i.js +0 -1
- package/build/assets/circle-check-CxCYJrpZ.js +0 -1
- package/build/assets/conversation-BUzBZAc-.js +0 -90
- package/build/assets/conversation-panel-pzAKsSDb.js +0 -1
- package/build/assets/conversation-pzAKsSDb.js +0 -1
- package/build/assets/conversation-store-B8Eahyeg.js +0 -6
- package/build/assets/external-link-zWbg5KXS.js +0 -1
- package/build/assets/files-tab-BzofBmF4.js +0 -1
- package/build/assets/git-provider-icon-BLMlwy02.js +0 -1
- package/build/assets/git-repo-dropdown-B_PTq4ol.js +0 -1
- package/build/assets/home-r89Vj2td.js +0 -1
- package/build/assets/index-home-r89Vj2td.js +0 -1
- package/build/assets/llm-not-configured-banner-CwKu8C6g.js +0 -27
- package/build/assets/llm-settings-BJotLQND.js +0 -1
- package/build/assets/llm-settings-CDM-5H2A.js +0 -1
- package/build/assets/llm-subscription-service-7eVfDIE1.js +0 -1
- package/build/assets/loader-circle-C5AnEN8r.js +0 -1
- package/build/assets/manifest-2a36a8f8.js +0 -1
- package/build/assets/markdown-renderer-BiiiIznl.js +0 -8
- package/build/assets/mcp-marketplace-utils-Dr_HG_ol.js +0 -1
- package/build/assets/messages-D1cbVhRK.js +0 -40
- package/build/assets/pencil-Bl3GWhgq.js +0 -1
- package/build/assets/plus-CtzlyAoF.js +0 -1
- package/build/assets/recommended-automations-launcher-BbJAJVED.js +0 -1
- package/build/assets/refresh-cw-B9GhC3QC.js +0 -1
- package/build/assets/root-BfM_kC_d.css +0 -1
- package/build/assets/root-gstBEjty.js +0 -2
- package/build/assets/root-layout-BIAFLP9p.js +0 -2
- package/build/assets/search-CYaiKHRp.js +0 -1
- package/build/assets/shared-conversation-DIxZjElr.js +0 -1
- package/build/assets/shield-check-D2cpEpv3.js +0 -1
- package/build/assets/trash-2-CzDVN-Iw.js +0 -1
- package/build/assets/use-create-conversation-DWaSVUOB.js +0 -1
- package/build/assets/use-is-authed-Bka89Xmm.js +0 -1
- package/build/assets/use-llm-profiles-CU8ah1pz.js +0 -1
- package/build/assets/use-save-agent-profile-DgI_Uz9a.js +0 -1
- package/build/assets/utils-DPQfkd-7.js +0 -1
- package/build/assets/vendor~index-home~home~conversation-panel~conversation-DiXf0dkj.js +0 -13
- package/build/assets/x-RYDBg-e2.js +0 -1
- package/build/assets/zap-tbKDzCe3.js +0 -1
- package/dist/components/features/conversation/metrics-modal/context-window-section.cjs +0 -2
- package/dist/components/features/conversation/metrics-modal/context-window-section.cjs.map +0 -1
- package/dist/components/features/conversation/metrics-modal/context-window-section.d.ts +0 -6
- package/dist/components/features/conversation/metrics-modal/context-window-section.js +0 -46
- package/dist/components/features/conversation/metrics-modal/context-window-section.js.map +0 -1
- package/dist/components/features/conversation/metrics-modal/empty-state.cjs +0 -2
- package/dist/components/features/conversation/metrics-modal/empty-state.cjs.map +0 -1
- package/dist/components/features/conversation/metrics-modal/empty-state.d.ts +0 -1
- package/dist/components/features/conversation/metrics-modal/empty-state.js +0 -18
- package/dist/components/features/conversation/metrics-modal/empty-state.js.map +0 -1
- package/dist/components/features/conversation/metrics-modal/metrics-modal-header.cjs +0 -2
- package/dist/components/features/conversation/metrics-modal/metrics-modal-header.cjs.map +0 -1
- package/dist/components/features/conversation/metrics-modal/metrics-modal-header.d.ts +0 -5
- package/dist/components/features/conversation/metrics-modal/metrics-modal-header.js +0 -20
- package/dist/components/features/conversation/metrics-modal/metrics-modal-header.js.map +0 -1
- package/dist/components/features/conversation/metrics-modal/metrics-modal.cjs +0 -2
- package/dist/components/features/conversation/metrics-modal/metrics-modal.cjs.map +0 -1
- package/dist/components/features/conversation/metrics-modal/metrics-modal.d.ts +0 -6
- package/dist/components/features/conversation/metrics-modal/metrics-modal.js +0 -57
- package/dist/components/features/conversation/metrics-modal/metrics-modal.js.map +0 -1
- package/dist/icons/u-credit-card.cjs +0 -2
- package/dist/icons/u-credit-card.cjs.map +0 -1
- package/dist/icons/u-credit-card.js +0 -21
- package/dist/icons/u-credit-card.js.map +0 -1
- /package/build/assets/{vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~cy4jtuc2-rfV8mQ9m.js → vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~dh9v73sa-rfV8mQ9m.js} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation-panel.js","names":[],"sources":["../../../../src/components/features/conversation-panel/conversation-panel.tsx"],"sourcesContent":["import React from \"react\";\nimport { Tooltip } from \"@heroui/react\";\nimport { useTranslation } from \"react-i18next\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { useNavigation } from \"#/context/navigation-context\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport { useBackendScopedPath } from \"#/hooks/use-backend-scoped-path\";\nimport { usePaginatedConversations } from \"#/hooks/query/use-paginated-conversations\";\nimport { useStartTasks } from \"#/hooks/query/use-start-tasks\";\nimport { useDeleteConversation } from \"#/hooks/mutation/use-delete-conversation\";\nimport { useUnifiedPauseConversation } from \"#/hooks/mutation/use-unified-stop-conversation\";\nimport { ConfirmDeleteModal } from \"./confirm-delete-modal\";\nimport { ConfirmStopModal } from \"./confirm-stop-modal\";\nimport { NavigationLink } from \"#/components/shared/navigation-link\";\nimport { ExitConversationModal } from \"./exit-conversation-modal\";\nimport { useClickOutsideElement } from \"#/hooks/use-click-outside-element\";\nimport { Provider } from \"#/types/settings\";\nimport { useUpdateConversation } from \"#/hooks/mutation/use-update-conversation\";\nimport {\n displayErrorToast,\n displaySuccessToast,\n} from \"#/utils/custom-toast-handlers\";\nimport { isExecutionActive } from \"#/utils/status\";\nimport { useCreateConversation } from \"#/hooks/mutation/use-create-conversation\";\nimport { useIsCreatingConversation } from \"#/hooks/use-is-creating-conversation\";\nimport { ConversationCard } from \"./conversation-card/conversation-card\";\nimport { ConversationCardPreview } from \"./conversation-card/conversation-card-preview\";\nimport { StartTaskCard } from \"./start-task-card/start-task-card\";\nimport { ConversationCardSkeleton } from \"./conversation-card/conversation-card-skeleton\";\nimport { CompactConversationRow } from \"./compact-conversation-row\";\nimport { useConversationPanelPreferencesStore } from \"#/stores/conversation-panel-preferences-store\";\nimport { cn } from \"#/utils/utils\";\nimport { ConversationPanelFilterMenu } from \"./conversation-panel-filter-menu\";\nimport { ConversationPanelNewThreadPicker } from \"./conversation-panel-new-thread-picker\";\nimport { ConversationGroupFolderList } from \"./conversation-group-folder-list\";\nimport { ConversationPanelPinnedSection } from \"./conversation-panel-pinned-section\";\nimport {\n applyAutomationConversationFilter,\n applyGroupFolderOrder,\n collectAutomationNameFacets,\n filterOutPinnedConversations,\n groupConversations,\n resolvePinnedConversations,\n sortConversationsByField,\n type ConversationGroupLaunch,\n} from \"./conversation-panel-list-helpers\";\nimport { usePinnedConversationsStore } from \"#/stores/pinned-conversations-store\";\n\ninterface ConversationPanelProps {\n onClose?: () => void;\n /**\n * Render a minimal icon-only variant of each conversation row (used by the\n * collapsed sidebar). Each row is a single status dot with a hover preview\n * containing the full card content.\n */\n compact?: boolean;\n}\n\nconst noop = () => {};\n\nconst EMPTY_PINNED_CONVERSATION_IDS: readonly string[] = [];\n\nconst ONE_HOUR_MS = 60 * 60 * 1000;\n\nconst partitionByCutoff = <T extends { updated_at: string }>(\n items: readonly T[],\n): { recent: T[]; older: T[] } => {\n // The cutoff is intentionally relative to \"now\" each time the list is\n // recomputed, so conversations naturally age into the older bucket as the\n // conversations query refreshes.\n const cutoff = Date.now() - ONE_HOUR_MS;\n const recent: T[] = [];\n const older: T[] = [];\n for (const item of items) {\n const updatedAt = item.updated_at ? Date.parse(item.updated_at) : NaN;\n // Missing or unparseable timestamps stay in the \"recent\" bucket so we\n // do not accidentally hide them behind the older-conversations toggle.\n if (Number.isFinite(updatedAt) && updatedAt < cutoff) {\n older.push(item);\n } else {\n recent.push(item);\n }\n }\n return { recent, older };\n};\n\nexport function ConversationPanel({\n onClose,\n compact = false,\n}: ConversationPanelProps) {\n const { t } = useTranslation(\"openhands\");\n const { conversationId: currentConversationId, navigate } = useNavigation();\n const { backend: activeBackend } = useActiveBackend();\n const backendScopedPath = useBackendScopedPath();\n // Click-outside is only relevant in the legacy drawer mode where an\n // onClose handler is provided. When the panel is rendered inline (e.g.\n // as the always-visible conversation list pane), clicking outside should\n // not dismiss the list, so we pass a no-op callback in that case.\n const ref = useClickOutsideElement<HTMLDivElement>(onClose ?? noop);\n\n const [confirmDeleteModalVisible, setConfirmDeleteModalVisible] =\n React.useState(false);\n const [confirmStopModalVisible, setConfirmStopModalVisible] =\n React.useState(false);\n const [\n confirmExitConversationModalVisible,\n setConfirmExitConversationModalVisible,\n ] = React.useState(false);\n const [confirmDeleteAllVisible, setConfirmDeleteAllVisible] =\n React.useState(false);\n const showOlderConversations = useConversationPanelPreferencesStore(\n (state) => state.showOlderConversations,\n );\n const toggleShowOlderConversations = useConversationPanelPreferencesStore(\n (state) => state.toggleShowOlderConversations,\n );\n const showRepoBranchMetadata = useConversationPanelPreferencesStore(\n (state) => state.showRepoBranchMetadata,\n );\n const toggleShowRepoBranchMetadata = useConversationPanelPreferencesStore(\n (state) => state.toggleShowRepoBranchMetadata,\n );\n const showLlmProfiles = useConversationPanelPreferencesStore(\n (state) => state.showLlmProfiles,\n );\n const toggleShowLlmProfiles = useConversationPanelPreferencesStore(\n (state) => state.toggleShowLlmProfiles,\n );\n const showTagsMetadata = useConversationPanelPreferencesStore(\n (state) => state.showTagsMetadata,\n );\n const toggleShowTagsMetadata = useConversationPanelPreferencesStore(\n (state) => state.toggleShowTagsMetadata,\n );\n const showHoverMetadata = useConversationPanelPreferencesStore(\n (state) => state.showHoverMetadata,\n );\n const toggleShowHoverMetadata = useConversationPanelPreferencesStore(\n (state) => state.toggleShowHoverMetadata,\n );\n const organizeMode = useConversationPanelPreferencesStore(\n (state) => state.organizeMode,\n );\n const setOrganizeMode = useConversationPanelPreferencesStore(\n (state) => state.setOrganizeMode,\n );\n const conversationSort = useConversationPanelPreferencesStore(\n (state) => state.conversationSort,\n );\n const setConversationSort = useConversationPanelPreferencesStore(\n (state) => state.setConversationSort,\n );\n const threadScope = useConversationPanelPreferencesStore(\n (state) => state.threadScope,\n );\n const setThreadScope = useConversationPanelPreferencesStore(\n (state) => state.setThreadScope,\n );\n const automationFilterMode = useConversationPanelPreferencesStore(\n (state) => state.automationFilterMode,\n );\n const setAutomationFilterMode = useConversationPanelPreferencesStore(\n (state) => state.setAutomationFilterMode,\n );\n const selectedAutomationNames = useConversationPanelPreferencesStore(\n (state) => state.selectedAutomationNames,\n );\n const toggleAutomationName = useConversationPanelPreferencesStore(\n (state) => state.toggleAutomationName,\n );\n const groupFolderOrder = useConversationPanelPreferencesStore(\n (state) => state.groupFolderOrder,\n );\n const setGroupFolderOrder = useConversationPanelPreferencesStore(\n (state) => state.setGroupFolderOrder,\n );\n const [filterMenuOpen, setFilterMenuOpen] = React.useState(false);\n const [isListScrolled, setIsListScrolled] = React.useState(false);\n const filterMenuRef = useClickOutsideElement<HTMLDivElement>(() => {\n setFilterMenuOpen(false);\n });\n const [collapsedGroupIds, setCollapsedGroupIds] = React.useState<\n ReadonlySet<string>\n >(() => new Set());\n const [expandedGroupPreviewIds, setExpandedGroupPreviewIds] = React.useState<\n ReadonlySet<string>\n >(() => new Set());\n const [expandedPinnedPreview, setExpandedPinnedPreview] =\n React.useState(false);\n\n const pinnedIds = usePinnedConversationsStore(\n (state) =>\n state.pinsByBackendId[activeBackend.id] ?? EMPTY_PINNED_CONVERSATION_IDS,\n );\n const togglePin = usePinnedConversationsStore((state) => state.togglePin);\n const pruneMissingPinnedConversations = usePinnedConversationsStore(\n (state) => state.pruneMissingConversations,\n );\n\n const toggleGroupCollapsed = React.useCallback((groupId: string) => {\n setCollapsedGroupIds((prev) => {\n const next = new Set(prev);\n if (next.has(groupId)) {\n next.delete(groupId);\n } else {\n next.add(groupId);\n }\n return next;\n });\n }, []);\n\n const toggleGroupPreviewExpanded = React.useCallback((groupId: string) => {\n setExpandedGroupPreviewIds((prev) => {\n const next = new Set(prev);\n if (next.has(groupId)) {\n next.delete(groupId);\n } else {\n next.add(groupId);\n }\n return next;\n });\n }, []);\n\n React.useEffect(() => {\n if (organizeMode !== \"grouped\") {\n setCollapsedGroupIds(new Set());\n setExpandedGroupPreviewIds(new Set());\n }\n }, [organizeMode]);\n\n const scrollContainerRef = React.useRef<HTMLDivElement>(null);\n\n const [selectedConversationId, setSelectedConversationId] = React.useState<\n string | null\n >(null);\n const [selectedConversationTitle, setSelectedConversationTitle] =\n React.useState<string | null>(null);\n const [openContextMenuId, setOpenContextMenuId] = React.useState<\n string | null\n >(null);\n\n const {\n data,\n isLoading,\n isFetched,\n hasNextPage,\n isFetching,\n isFetchingNextPage,\n fetchNextPage,\n } = usePaginatedConversations();\n\n // Fetch in-progress start tasks\n const { data: startTasks } = useStartTasks();\n\n const conversations = React.useMemo(() => {\n const all = data?.pages.flatMap((page) => page.items) ?? [];\n // The 10s background refetch re-fetches every loaded page with the\n // `UPDATED_AT_DESC` cursor. If a conversation's `updated_at` shifts between\n // page fetches, a later page can overlap an earlier one and surface the\n // same conversation twice. Dedupe by id (keeping the first/freshest copy)\n // so the rendered count reflects real growth and React keys stay unique.\n const seen = new Set<string>();\n return all.filter((conversation) => {\n if (seen.has(conversation.id)) {\n return false;\n }\n seen.add(conversation.id);\n return true;\n });\n }, [data]);\n\n // Facets derive from the unfiltered list so the automation-name rows in the\n // filter menu don't vanish while a narrowing selection is active.\n const automationNameFacets = React.useMemo(\n () => collectAutomationNameFacets(conversations),\n [conversations],\n );\n\n const automationFilteredConversations = React.useMemo(\n () =>\n applyAutomationConversationFilter(\n conversations,\n automationFilterMode,\n selectedAutomationNames,\n automationNameFacets,\n ),\n [\n automationFilterMode,\n automationNameFacets,\n conversations,\n selectedAutomationNames,\n ],\n );\n\n const pinnedConversations = React.useMemo(\n () => resolvePinnedConversations(pinnedIds, conversations),\n [conversations, pinnedIds],\n );\n\n React.useEffect(() => {\n if (!isFetched) {\n return;\n }\n pruneMissingPinnedConversations(\n activeBackend.id,\n conversations.map((conversation) => conversation.id),\n );\n }, [\n activeBackend.id,\n conversations,\n isFetched,\n pruneMissingPinnedConversations,\n ]);\n\n React.useEffect(() => {\n if (pinnedIds.length === 0) {\n setExpandedPinnedPreview(false);\n }\n }, [pinnedIds.length]);\n\n const scopedConversations = React.useMemo(() => {\n // The pinned section intentionally bypasses the automation filter (same\n // exemption the thread scope has): a pin is an explicit user override.\n const scopeFiltered =\n threadScope === \"relevant\"\n ? automationFilteredConversations.filter((c) =>\n isExecutionActive(c.execution_status),\n )\n : automationFilteredConversations;\n\n // In the expanded panel, pinned conversations should only appear inside\n // the dedicated pinned section (not duplicated in grouped/flat lists).\n if (compact) {\n return scopeFiltered;\n }\n\n return filterOutPinnedConversations(scopeFiltered, pinnedIds);\n }, [automationFilteredConversations, compact, pinnedIds, threadScope]);\n\n const { recent: recentScoped, older: olderScoped } = React.useMemo(\n () => partitionByCutoff(scopedConversations),\n [scopedConversations],\n );\n\n // Sort the full visible set as one list. The recent/older partition is\n // still computed (it gates the \"Show older\" toggle and \"Load more\"\n // visibility), but the rendering must not use it as a visual boundary —\n // when sorting by `created`, a stale-but-recently-touched conversation\n // would otherwise land in `recent` and render above an actually-newer-\n // by-`created_at` conversation sitting in `older`.\n const sortedVisibleConversations = React.useMemo(() => {\n const visible = showOlderConversations\n ? [...recentScoped, ...olderScoped]\n : recentScoped;\n return sortConversationsByField(visible, conversationSort);\n }, [recentScoped, olderScoped, showOlderConversations, conversationSort]);\n\n const groupLabels = React.useMemo(\n () => ({\n emptyWorkspace: t(I18nKey.CONVERSATION_PANEL$NO_WORKSPACE),\n emptyRepository: t(I18nKey.CONVERSATION_PANEL$NO_REPOSITORY),\n }),\n [t],\n );\n\n const conversationGroups = React.useMemo(() => {\n if (compact || organizeMode !== \"grouped\") {\n return null;\n }\n // Use the unsorted partitions: groupConversations sorts each bucket\n // internally by `sortField`, so pre-sorting the merged input is wasted\n // work in grouped mode (the per-group sort overrides any global order).\n const merged = [\n ...recentScoped,\n ...(showOlderConversations ? olderScoped : []),\n ];\n return groupConversations(\n merged,\n activeBackend.kind,\n conversationSort,\n groupLabels,\n );\n }, [\n activeBackend.kind,\n compact,\n conversationSort,\n groupLabels,\n olderScoped,\n organizeMode,\n recentScoped,\n showOlderConversations,\n ]);\n\n const orderedConversationGroups = React.useMemo(() => {\n if (!conversationGroups) {\n return null;\n }\n return applyGroupFolderOrder(conversationGroups, groupFolderOrder);\n }, [conversationGroups, groupFolderOrder]);\n\n const conversationGroupIds = React.useMemo(\n () => conversationGroups?.map((group) => group.id) ?? [],\n [conversationGroups],\n );\n\n const compactVisibleConversations = React.useMemo(\n () =>\n sortConversationsByField(\n recentScoped.filter((conversation) =>\n isExecutionActive(conversation.execution_status),\n ),\n conversationSort,\n ),\n [conversationSort, recentScoped],\n );\n\n const visibleFlatCount = sortedVisibleConversations.length;\n\n const visibleGroupedCount = React.useMemo(() => {\n if (!orderedConversationGroups) {\n return 0;\n }\n return orderedConversationGroups.reduce(\n (n, g) => n + g.conversations.length,\n 0,\n );\n }, [orderedConversationGroups]);\n\n const listIsEffectivelyEmpty =\n organizeMode === \"grouped\" && !compact\n ? visibleGroupedCount === 0\n : visibleFlatCount === 0;\n\n // Attribution is exact: the automation filter step itself produced zero\n // rows out of a non-empty loaded set (not merely threadScope/older-cutoff\n // effects). Used to keep \"Load more\" reachable and to pick the empty-state\n // message.\n const emptyDueToAutomationFilter =\n listIsEffectivelyEmpty &&\n automationFilterMode !== \"all\" &&\n conversations.length > 0 &&\n automationFilteredConversations.length === 0;\n\n // Number of conversations actually rendered in the list right now, in the\n // current organize mode. \"Load more\" succeeds only when this number grows.\n const visibleCount =\n organizeMode === \"grouped\" && !compact\n ? visibleGroupedCount\n : visibleFlatCount;\n\n // KNOWN ISSUE (unresolved as of 2026-05-29): users still report that the\n // sidebar \"Load more\" sometimes requires two clicks before new conversations\n // appear. The mitigation below (dedupe by id in `conversations`, plus the\n // floor-tracking driver that keeps fetching until the visible count grows)\n // reduced but did NOT fully eliminate the symptom in manual testing. Likely\n // remaining suspects to investigate next: the agent-server cursor pagination\n // returning an overlapping/short page under `UPDATED_AT_DESC` while the 10s\n // `refetchInterval` reorders pages (see `usePaginatedConversations`), or a\n // React Query state lag where `hasNextPage`/`isFetching` settle a render\n // after the click. If you pick this up, reproduce against a backend with\n // >40 conversations and watch the `/api/conversations/search` cursors.\n //\n // Robust \"Load more\" driver. A single click can fail to surface new rows for\n // two reasons: (1) `fetchNextPage()` is silently dropped while the 10s\n // background refetch is in flight, and (2) a fetched page can yield zero\n // *visible* rows (filtered out by the active scope, or deduped as overlap),\n // so the list does not appear to grow. We capture the visible count at click\n // time and keep fetching pages — once the query is idle — until the visible\n // count actually increases or there are no more pages.\n const [loadMoreFloor, setLoadMoreFloor] = React.useState<number | null>(null);\n const visibleCountRef = React.useRef(visibleCount);\n visibleCountRef.current = visibleCount;\n\n const requestLoadMore = React.useCallback(() => {\n if (hasNextPage) {\n setLoadMoreFloor(visibleCountRef.current);\n }\n }, [hasNextPage]);\n\n React.useEffect(() => {\n if (loadMoreFloor === null) {\n return;\n }\n // Goal met: the visible list grew past where it was when the user clicked.\n if (visibleCount > loadMoreFloor) {\n setLoadMoreFloor(null);\n return;\n }\n // Nothing more to fetch — stop waiting even if the list did not grow.\n if (!hasNextPage) {\n setLoadMoreFloor(null);\n return;\n }\n // Wait for any in-flight fetch (including the background refetch) to settle\n // before requesting the next page, otherwise the request is dropped.\n if (isFetching || isFetchingNextPage) {\n return;\n }\n fetchNextPage();\n }, [\n loadMoreFloor,\n visibleCount,\n hasNextPage,\n isFetching,\n isFetchingNextPage,\n fetchNextPage,\n ]);\n\n const isLoadingMore = loadMoreFloor !== null || isFetchingNextPage;\n\n const { mutate: deleteConversation, mutateAsync: deleteConversationAsync } =\n useDeleteConversation();\n const { mutate: pauseConversation } = useUnifiedPauseConversation();\n const { mutate: updateConversation } = useUpdateConversation();\n\n // The next page of conversations is loaded only via the explicit \"Load\n // more\" link rendered at the end of the list — there is no scroll-driven\n // pagination, which previously caused the panel to feel like it had stray\n // scrollable space at the bottom.\n const olderHidden = olderScoped.length > 0 && !showOlderConversations;\n // Compact mode also hides \"Load more\" — paginating into archived\n // conversations contradicts the \"active only\" intent of the icon rail.\n // Do not show when the visible list is empty (that state already shows\n // \"No conversations found\") — unless the automation filter alone emptied\n // it while more pages exist: then the button must stay reachable so the\n // load-more driver can keep fetching until a matching run surfaces.\n const showLoadMore =\n !!hasNextPage &&\n !olderHidden &&\n !compact &&\n (!listIsEffectivelyEmpty || emptyDueToAutomationFilter);\n\n const { mutate: createConversation } = useCreateConversation();\n const isCreatingConversationFlow = useIsCreatingConversation();\n\n const launchFromGroup = React.useCallback(\n (launch: ConversationGroupLaunch) => {\n if (isCreatingConversationFlow) return;\n createConversation(\n {\n workingDir: launch.workingDir,\n repository: launch.repository,\n entryPoint: \"sidebar_relaunch_project\",\n },\n {\n onSuccess: (data) => {\n navigate(`/conversations/${data.conversation_id}`);\n },\n },\n );\n },\n [createConversation, isCreatingConversationFlow, navigate],\n );\n\n const handleDeleteProject = React.useCallback(\n (conversationId: string, title: string) => {\n setConfirmDeleteModalVisible(true);\n setSelectedConversationId(conversationId);\n setSelectedConversationTitle(title);\n },\n [],\n );\n\n const handleStopConversation = React.useCallback((conversationId: string) => {\n setConfirmStopModalVisible(true);\n setSelectedConversationId(conversationId);\n }, []);\n\n const handleConversationTitleChange = React.useCallback(\n (conversationId: string, newTitle: string) => {\n updateConversation(\n { conversationId, newTitle },\n {\n onSuccess: () => {\n displaySuccessToast(t(I18nKey.CONVERSATION$TITLE_UPDATED));\n },\n },\n );\n },\n [t, updateConversation],\n );\n\n const handleConfirmDelete = () => {\n if (selectedConversationId) {\n deleteConversation(\n { conversationId: selectedConversationId },\n {\n onSuccess: () => {\n if (selectedConversationId === currentConversationId) {\n navigate(\"/conversations\");\n }\n },\n },\n );\n }\n };\n\n const handleConfirmStop = () => {\n if (selectedConversationId) {\n pauseConversation({\n conversationId: selectedConversationId,\n });\n }\n };\n\n const handleConfirmDeleteAll = async () => {\n const idsToDelete = conversations.map((c) => c.id);\n const results = await Promise.allSettled(\n idsToDelete.map((conversationId) =>\n deleteConversationAsync({ conversationId }),\n ),\n );\n\n const deletedIds = results.flatMap((result, index) =>\n result.status === \"fulfilled\" ? [idsToDelete[index]] : [],\n );\n const failedCount = results.length - deletedIds.length;\n\n if (\n currentConversationId !== null &&\n deletedIds.includes(currentConversationId)\n ) {\n navigate(\"/conversations\");\n }\n\n if (failedCount > 0) {\n displayErrorToast(\n `${failedCount} conversation${failedCount === 1 ? \"\" : \"s\"} could not be deleted.`,\n );\n }\n };\n\n const renderConversationCard = React.useCallback(\n (\n conversation: (typeof conversations)[number],\n options?: { inPinnedSection?: boolean },\n ) => {\n const isPinned = pinnedIds.includes(conversation.id);\n if (compact) {\n return (\n <CompactConversationRow\n key={conversation.id}\n conversationId={conversation.id}\n title={conversation.title ?? \"\"}\n selectedRepository={{\n selected_repository: conversation.selected_repository,\n selected_branch: conversation.selected_branch,\n git_provider: conversation.git_provider as Provider,\n }}\n executionStatus={conversation.execution_status}\n sandboxStatus={conversation.sandbox_status}\n lastUpdatedAt={conversation.updated_at}\n createdAt={conversation.created_at}\n workspaceWorkingDir={\n conversation.selected_workspace ??\n conversation.workspace?.working_dir\n }\n isActive={conversation.id === currentConversationId}\n onClose={onClose}\n showRepositoryMetadata={showRepoBranchMetadata}\n llmModel={conversation.llm_model}\n showLlmProfiles={showLlmProfiles}\n agentKind={conversation.agent_kind}\n acpServer={conversation.acp_server}\n tags={conversation.tags}\n showTags={showTagsMetadata}\n />\n );\n }\n return (\n <Tooltip\n key={conversation.id}\n placement=\"right-start\"\n delay={1000}\n closeDelay={100}\n isDisabled={\n !showHoverMetadata || openContextMenuId === conversation.id\n }\n disableAnimation={import.meta.env.MODE === \"test\"}\n className=\"max-w-none overflow-visible rounded-xl border border-[var(--oh-border)] bg-base-secondary p-0 text-white shadow-xl\"\n content={\n <ConversationCardPreview\n title={conversation.title ?? \"\"}\n executionStatus={conversation.execution_status}\n sandboxStatus={conversation.sandbox_status}\n selectedRepository={{\n selected_repository: conversation.selected_repository,\n selected_branch: conversation.selected_branch,\n git_provider: conversation.git_provider as Provider,\n }}\n workspaceWorkingDir={\n conversation.selected_workspace ??\n conversation.workspace?.working_dir\n }\n llmModel={conversation.llm_model}\n agentKind={conversation.agent_kind}\n acpServer={conversation.acp_server}\n createdAt={conversation.created_at}\n tags={conversation.tags}\n />\n }\n >\n <NavigationLink\n to={backendScopedPath(`/conversations/${conversation.id}`)}\n onClick={onClose}\n className={cn(\n \"block rounded-md transition-colors\",\n openContextMenuId !== conversation.id &&\n \"hover:bg-[var(--oh-surface)]\",\n (conversation.id === currentConversationId ||\n openContextMenuId === conversation.id) &&\n \"bg-[var(--oh-surface)]\",\n )}\n >\n <ConversationCard\n onDelete={() =>\n handleDeleteProject(conversation.id, conversation.title ?? \"\")\n }\n onStop={() => handleStopConversation(conversation.id)}\n onChangeTitle={(title) =>\n handleConversationTitleChange(conversation.id, title)\n }\n title={conversation.title ?? \"\"}\n selectedRepository={{\n selected_repository: conversation.selected_repository,\n selected_branch: conversation.selected_branch,\n git_provider: conversation.git_provider as Provider,\n }}\n lastUpdatedAt={conversation.updated_at}\n createdAt={conversation.created_at}\n executionStatus={conversation.execution_status}\n sandboxStatus={conversation.sandbox_status}\n conversationId={conversation.id}\n contextMenuOpen={openContextMenuId === conversation.id}\n onContextMenuToggle={(isOpen) =>\n setOpenContextMenuId(isOpen ? conversation.id : null)\n }\n isActive={conversation.id === currentConversationId}\n workspaceWorkingDir={\n conversation.selected_workspace ??\n conversation.workspace?.working_dir\n }\n showRepositoryMetadata={showRepoBranchMetadata}\n llmModel={conversation.llm_model}\n showLlmProfiles={showLlmProfiles}\n agentKind={conversation.agent_kind}\n acpServer={conversation.acp_server}\n tags={conversation.tags}\n showTags={showTagsMetadata}\n isPinned={isPinned}\n onTogglePin={() => togglePin(activeBackend.id, conversation.id)}\n alwaysShowPinIcon={isPinned && !options?.inPinnedSection}\n />\n </NavigationLink>\n </Tooltip>\n );\n },\n [\n activeBackend.id,\n compact,\n currentConversationId,\n handleConversationTitleChange,\n handleDeleteProject,\n handleStopConversation,\n onClose,\n openContextMenuId,\n pinnedIds,\n showRepoBranchMetadata,\n showLlmProfiles,\n showTagsMetadata,\n showHoverMetadata,\n togglePin,\n ],\n );\n\n // Standard layout: panel fills its slot in the sidebar; the inner scroll\n // child fills the panel and scrolls when its content overflows. Modals are\n // siblings of the scroll element and are `position: fixed`, so they don't\n // participate in the panel's scroll geometry.\n // Gate on `isLoading` / `!isFetched` (true only until the first fetch settles),\n // not `isFetching` — the latter flips back to true on every 10s background\n // refetch, causing the skeleton/empty-state to flicker when the list is empty.\n const showInitialSkeleton = isLoading || !isFetched;\n const showPinnedSection =\n !compact && !showInitialSkeleton && pinnedConversations.length > 0;\n const showEmptyState =\n isFetched &&\n !isLoading &&\n !compact &&\n listIsEffectivelyEmpty &&\n !showPinnedSection &&\n !startTasks?.length;\n\n const showConversationHeader = !compact;\n\n return (\n <div\n ref={ref}\n data-testid=\"conversation-panel\"\n className=\"flex h-full min-h-0 w-full flex-col\"\n >\n {showConversationHeader && (\n <div\n className={cn(\n // Pull flush to the sidebar edges: `-ml-2.5` matches aside `pl-2.5`;\n // width extends by that inset on the right now that aside is `pr-0`.\n \"-ml-2.5 w-[calc(100%+0.625rem)] max-w-none box-border border-b\",\n isListScrolled ? \"border-[var(--oh-border)]\" : \"border-transparent\",\n )}\n >\n <div\n data-testid=\"older-conversations-summary\"\n className=\"flex min-w-0 flex-nowrap items-center gap-x-2 py-2 pl-4 pr-2.5 text-[var(--oh-muted)]\"\n >\n <span className=\"min-w-0 truncate text-sm font-medium text-[var(--oh-muted)]\">\n {t(I18nKey.SIDEBAR$CONVERSATIONS)}\n </span>\n <div className=\"ml-auto flex shrink-0 items-center gap-0.5\">\n <ConversationPanelNewThreadPicker\n backendKind={activeBackend.kind}\n />\n <ConversationPanelFilterMenu\n filterMenuOpen={filterMenuOpen}\n setFilterMenuOpen={setFilterMenuOpen}\n menuRef={filterMenuRef}\n backendKind={activeBackend.kind}\n organizeMode={organizeMode}\n setOrganizeMode={setOrganizeMode}\n conversationSort={conversationSort}\n setConversationSort={setConversationSort}\n threadScope={threadScope}\n setThreadScope={setThreadScope}\n automationFilterMode={automationFilterMode}\n setAutomationFilterMode={setAutomationFilterMode}\n selectedAutomationNames={selectedAutomationNames}\n onToggleAutomationName={toggleAutomationName}\n automationNameFacets={automationNameFacets}\n showOlderConversations={showOlderConversations}\n toggleShowOlderConversations={toggleShowOlderConversations}\n showRepoBranchMetadata={showRepoBranchMetadata}\n toggleShowRepoBranchMetadata={toggleShowRepoBranchMetadata}\n showLlmProfiles={showLlmProfiles}\n toggleShowLlmProfiles={toggleShowLlmProfiles}\n showTagsMetadata={showTagsMetadata}\n toggleShowTagsMetadata={toggleShowTagsMetadata}\n showHoverMetadata={showHoverMetadata}\n toggleShowHoverMetadata={toggleShowHoverMetadata}\n totalConversationsCount={conversations.length}\n onRequestDeleteAll={() => setConfirmDeleteAllVisible(true)}\n />\n </div>\n </div>\n </div>\n )}\n\n <div\n ref={scrollContainerRef}\n data-testid=\"conversation-panel-list-scroll\"\n onScroll={(event) => {\n setIsListScrolled(event.currentTarget.scrollTop > 0);\n }}\n className={cn(\n \"flex min-h-0 flex-1 flex-col overflow-y-auto overflow-x-hidden overscroll-contain custom-scrollbar-always\",\n !compact && \"conversation-panel-list-scroll\",\n )}\n >\n {showInitialSkeleton && <ConversationCardSkeleton compact={compact} />}\n\n {!compact && showEmptyState && (\n <div\n data-testid=\"conversation-panel-empty-state\"\n className=\"flex min-h-0 flex-1 flex-col items-center justify-center px-4 py-8\"\n >\n <p className=\"text-xs text-[var(--oh-muted)]\">\n {t(\n emptyDueToAutomationFilter\n ? I18nKey.CONVERSATION_PANEL$NO_AUTOMATION_MATCHES\n : I18nKey.CONVERSATION$NO_CONVERSATIONS,\n )}\n </p>\n </div>\n )}\n\n {showPinnedSection ? (\n <ConversationPanelPinnedSection\n pinnedConversations={pinnedConversations}\n isPreviewExpanded={expandedPinnedPreview}\n onTogglePreviewExpanded={() =>\n setExpandedPinnedPreview((current) => !current)\n }\n activeConversationId={currentConversationId}\n showDivider={!compact && organizeMode === \"chronological\"}\n renderConversationCard={(conversation) =>\n renderConversationCard(conversation, { inPinnedSection: true })\n }\n />\n ) : null}\n\n {/* Render in-progress start tasks first (skipped in compact mode —\n their rich card layout doesn't fit in the icon rail). */}\n {!compact &&\n startTasks?.map((task) => (\n <NavigationLink\n key={task.id}\n to={backendScopedPath(`/conversations/task-${task.id}`)}\n onClick={onClose}\n className=\"block\"\n >\n <StartTaskCard task={task} />\n </NavigationLink>\n ))}\n\n {!showInitialSkeleton && compact\n ? compactVisibleConversations.map((conversation) =>\n renderConversationCard(conversation),\n )\n : null}\n\n {!showInitialSkeleton &&\n !compact &&\n organizeMode === \"grouped\" &&\n orderedConversationGroups &&\n orderedConversationGroups.length > 0 ? (\n <ConversationGroupFolderList\n groups={orderedConversationGroups}\n groupIds={conversationGroupIds}\n groupFolderOrder={groupFolderOrder}\n setGroupFolderOrder={setGroupFolderOrder}\n collapsedGroupIds={collapsedGroupIds}\n expandedGroupPreviewIds={expandedGroupPreviewIds}\n onToggleGroupCollapsed={toggleGroupCollapsed}\n onToggleGroupPreviewExpanded={toggleGroupPreviewExpanded}\n isCreatingConversationFlow={isCreatingConversationFlow}\n activeConversationId={currentConversationId}\n onLaunchFromGroup={launchFromGroup}\n renderConversationCard={(conversation) =>\n renderConversationCard(conversation)\n }\n />\n ) : null}\n\n {!showInitialSkeleton &&\n !compact &&\n organizeMode === \"chronological\" ? (\n <div className=\"space-y-0.5\">\n {sortedVisibleConversations.map((conversation) =>\n renderConversationCard(conversation),\n )}\n </div>\n ) : null}\n\n {/* Explicit \"Load more\" trigger. Only shown when more pages exist\n *and* the older list is currently visible (or there are no older\n conversations to begin with) — otherwise the next page would be\n populated mostly with conversations the user has chosen to hide. */}\n {showLoadMore &&\n (isLoadingMore ? (\n <div className=\"py-1\">\n <ConversationCardSkeleton compact={compact} />\n </div>\n ) : (\n <div className=\"flex justify-center py-4\">\n <button\n type=\"button\"\n data-testid=\"load-more-conversations\"\n onClick={requestLoadMore}\n className=\"text-xs text-[var(--oh-muted)] hover:text-white\"\n >\n {t(I18nKey.CONVERSATION$LOAD_MORE)}\n </button>\n </div>\n ))}\n </div>\n\n {confirmDeleteModalVisible && (\n <ConfirmDeleteModal\n onConfirm={() => {\n handleConfirmDelete();\n setConfirmDeleteModalVisible(false);\n setSelectedConversationTitle(null);\n }}\n onCancel={() => {\n setConfirmDeleteModalVisible(false);\n setSelectedConversationTitle(null);\n }}\n conversationTitle={selectedConversationTitle ?? undefined}\n />\n )}\n\n {confirmDeleteAllVisible && (\n <ConfirmDeleteModal\n title={t(I18nKey.CONVERSATION$CONFIRM_DELETE_ALL_TITLE)}\n description={t(I18nKey.CONVERSATION$CONFIRM_DELETE_ALL_DESC, {\n count: conversations.length,\n })}\n onConfirm={async () => {\n await handleConfirmDeleteAll();\n setConfirmDeleteAllVisible(false);\n }}\n onCancel={() => setConfirmDeleteAllVisible(false)}\n />\n )}\n\n {confirmStopModalVisible && (\n <ConfirmStopModal\n onConfirm={() => {\n handleConfirmStop();\n setConfirmStopModalVisible(false);\n }}\n onCancel={() => setConfirmStopModalVisible(false)}\n />\n )}\n\n {confirmExitConversationModalVisible && (\n <ExitConversationModal\n onConfirm={() => {\n onClose?.();\n }}\n onClose={() => setConfirmExitConversationModalVisible(false)}\n onCancel={() => setConfirmExitConversationModalVisible(false)}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"conversation-panel.js","names":[],"sources":["../../../../src/components/features/conversation-panel/conversation-panel.tsx"],"sourcesContent":["import React from \"react\";\nimport { Tooltip } from \"@heroui/react\";\nimport { useTranslation } from \"react-i18next\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { useNavigation } from \"#/context/navigation-context\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport { useBackendScopedPath } from \"#/hooks/use-backend-scoped-path\";\nimport { usePaginatedConversations } from \"#/hooks/query/use-paginated-conversations\";\nimport { useStartTasks } from \"#/hooks/query/use-start-tasks\";\nimport { useDeleteConversation } from \"#/hooks/mutation/use-delete-conversation\";\nimport { useUnifiedPauseConversation } from \"#/hooks/mutation/use-unified-stop-conversation\";\nimport { ConfirmArchiveModal } from \"./confirm-archive-modal\";\nimport { ConfirmDeleteModal } from \"./confirm-delete-modal\";\nimport { ConfirmStopModal } from \"./confirm-stop-modal\";\nimport { NavigationLink } from \"#/components/shared/navigation-link\";\nimport { ExitConversationModal } from \"./exit-conversation-modal\";\nimport { useClickOutsideElement } from \"#/hooks/use-click-outside-element\";\nimport { Provider } from \"#/types/settings\";\nimport { useUpdateConversation } from \"#/hooks/mutation/use-update-conversation\";\nimport {\n displayErrorToast,\n displaySuccessToast,\n} from \"#/utils/custom-toast-handlers\";\nimport { isExecutionActive } from \"#/utils/status\";\nimport { useCreateConversation } from \"#/hooks/mutation/use-create-conversation\";\nimport { useIsCreatingConversation } from \"#/hooks/use-is-creating-conversation\";\nimport { ConversationCard } from \"./conversation-card/conversation-card\";\nimport { ConversationCardPreview } from \"./conversation-card/conversation-card-preview\";\nimport { StartTaskCard } from \"./start-task-card/start-task-card\";\nimport { ConversationCardSkeleton } from \"./conversation-card/conversation-card-skeleton\";\nimport { CompactConversationRow } from \"./compact-conversation-row\";\nimport { useConversationPanelPreferencesStore } from \"#/stores/conversation-panel-preferences-store\";\nimport { cn } from \"#/utils/utils\";\nimport { ConversationPanelFilterMenu } from \"./conversation-panel-filter-menu\";\nimport { ConversationPanelNewThreadPicker } from \"./conversation-panel-new-thread-picker\";\nimport { ConversationGroupFolderList } from \"./conversation-group-folder-list\";\nimport { ConversationPanelPinnedSection } from \"./conversation-panel-pinned-section\";\nimport {\n applyAutomationConversationFilter,\n applyGroupFolderOrder,\n collectAutomationNameFacets,\n filterOutPinnedConversations,\n getGroupDiscoveryConversationIds,\n groupConversations,\n MAX_PAGES_PER_LOAD_MORE_CLICK,\n resolvePinnedConversations,\n sortConversationsByField,\n type ConversationGroupLaunch,\n} from \"./conversation-panel-list-helpers\";\nimport { useArchivedConversationsStore } from \"#/stores/archived-conversations-store\";\nimport { usePinnedConversationsStore } from \"#/stores/pinned-conversations-store\";\n\ninterface ConversationPanelProps {\n onClose?: () => void;\n /**\n * Render a minimal icon-only variant of each conversation row (used by the\n * collapsed sidebar). Each row is a single status dot with a hover preview\n * containing the full card content.\n */\n compact?: boolean;\n}\n\nconst noop = () => {};\n\nconst EMPTY_PINNED_CONVERSATION_IDS: readonly string[] = [];\n\nconst ONE_HOUR_MS = 60 * 60 * 1000;\n\nconst partitionByCutoff = <T extends { updated_at: string }>(\n items: readonly T[],\n): { recent: T[]; older: T[] } => {\n // The cutoff is intentionally relative to \"now\" each time the list is\n // recomputed, so conversations naturally age into the older bucket as the\n // conversations query refreshes.\n const cutoff = Date.now() - ONE_HOUR_MS;\n const recent: T[] = [];\n const older: T[] = [];\n for (const item of items) {\n const updatedAt = item.updated_at ? Date.parse(item.updated_at) : NaN;\n // Missing or unparseable timestamps stay in the \"recent\" bucket so we\n // do not accidentally hide them behind the older-conversations toggle.\n if (Number.isFinite(updatedAt) && updatedAt < cutoff) {\n older.push(item);\n } else {\n recent.push(item);\n }\n }\n return { recent, older };\n};\n\nexport function ConversationPanel({\n onClose,\n compact = false,\n}: ConversationPanelProps) {\n const { t } = useTranslation(\"openhands\");\n const { conversationId: currentConversationId, navigate } = useNavigation();\n const { backend: activeBackend } = useActiveBackend();\n const backendScopedPath = useBackendScopedPath();\n // Click-outside is only relevant in the legacy drawer mode where an\n // onClose handler is provided. When the panel is rendered inline (e.g.\n // as the always-visible conversation list pane), clicking outside should\n // not dismiss the list, so we pass a no-op callback in that case.\n const ref = useClickOutsideElement<HTMLDivElement>(onClose ?? noop);\n\n const [confirmDeleteModalVisible, setConfirmDeleteModalVisible] =\n React.useState(false);\n const [confirmArchiveModalVisible, setConfirmArchiveModalVisible] =\n React.useState(false);\n const [confirmStopModalVisible, setConfirmStopModalVisible] =\n React.useState(false);\n const [\n confirmExitConversationModalVisible,\n setConfirmExitConversationModalVisible,\n ] = React.useState(false);\n const [confirmDeleteAllVisible, setConfirmDeleteAllVisible] =\n React.useState(false);\n const showOlderConversations = useConversationPanelPreferencesStore(\n (state) => state.showOlderConversations,\n );\n const showArchivedConversations = useConversationPanelPreferencesStore(\n (state) => state.showArchivedConversations,\n );\n const toggleShowArchivedConversations = useConversationPanelPreferencesStore(\n (state) => state.toggleShowArchivedConversations,\n );\n const toggleShowOlderConversations = useConversationPanelPreferencesStore(\n (state) => state.toggleShowOlderConversations,\n );\n const showRepoBranchMetadata = useConversationPanelPreferencesStore(\n (state) => state.showRepoBranchMetadata,\n );\n const toggleShowRepoBranchMetadata = useConversationPanelPreferencesStore(\n (state) => state.toggleShowRepoBranchMetadata,\n );\n const showLlmProfiles = useConversationPanelPreferencesStore(\n (state) => state.showLlmProfiles,\n );\n const toggleShowLlmProfiles = useConversationPanelPreferencesStore(\n (state) => state.toggleShowLlmProfiles,\n );\n const showTagsMetadata = useConversationPanelPreferencesStore(\n (state) => state.showTagsMetadata,\n );\n const toggleShowTagsMetadata = useConversationPanelPreferencesStore(\n (state) => state.toggleShowTagsMetadata,\n );\n const showHoverMetadata = useConversationPanelPreferencesStore(\n (state) => state.showHoverMetadata,\n );\n const toggleShowHoverMetadata = useConversationPanelPreferencesStore(\n (state) => state.toggleShowHoverMetadata,\n );\n const organizeMode = useConversationPanelPreferencesStore(\n (state) => state.organizeMode,\n );\n const setOrganizeMode = useConversationPanelPreferencesStore(\n (state) => state.setOrganizeMode,\n );\n const conversationSort = useConversationPanelPreferencesStore(\n (state) => state.conversationSort,\n );\n const setConversationSort = useConversationPanelPreferencesStore(\n (state) => state.setConversationSort,\n );\n const threadScope = useConversationPanelPreferencesStore(\n (state) => state.threadScope,\n );\n const setThreadScope = useConversationPanelPreferencesStore(\n (state) => state.setThreadScope,\n );\n const automationFilterMode = useConversationPanelPreferencesStore(\n (state) => state.automationFilterMode,\n );\n const setAutomationFilterMode = useConversationPanelPreferencesStore(\n (state) => state.setAutomationFilterMode,\n );\n const selectedAutomationNames = useConversationPanelPreferencesStore(\n (state) => state.selectedAutomationNames,\n );\n const toggleAutomationName = useConversationPanelPreferencesStore(\n (state) => state.toggleAutomationName,\n );\n const groupFolderOrder = useConversationPanelPreferencesStore(\n (state) => state.groupFolderOrder,\n );\n const setGroupFolderOrder = useConversationPanelPreferencesStore(\n (state) => state.setGroupFolderOrder,\n );\n const [filterMenuOpen, setFilterMenuOpen] = React.useState(false);\n const [isListScrolled, setIsListScrolled] = React.useState(false);\n const filterMenuRef = useClickOutsideElement<HTMLDivElement>(() => {\n setFilterMenuOpen(false);\n });\n const [collapsedGroupIds, setCollapsedGroupIds] = React.useState<\n ReadonlySet<string>\n >(() => new Set());\n const [expandedGroupPreviewIds, setExpandedGroupPreviewIds] = React.useState<\n ReadonlySet<string>\n >(() => new Set());\n const [expandedPinnedPreview, setExpandedPinnedPreview] =\n React.useState(false);\n\n const pinnedIds = usePinnedConversationsStore(\n (state) =>\n state.pinsByBackendId[activeBackend.id] ?? EMPTY_PINNED_CONVERSATION_IDS,\n );\n const togglePin = usePinnedConversationsStore((state) => state.togglePin);\n const unpinConversation = usePinnedConversationsStore(\n (state) => state.unpinConversation,\n );\n const pruneMissingPinnedConversations = usePinnedConversationsStore(\n (state) => state.pruneMissingConversations,\n );\n const archivedIds = useArchivedConversationsStore(\n (state) =>\n state.archivesByBackendId[activeBackend.id] ??\n EMPTY_PINNED_CONVERSATION_IDS,\n );\n const archiveConversation = useArchivedConversationsStore(\n (state) => state.archiveConversation,\n );\n const archivedIdSet = React.useMemo(\n () => new Set(archivedIds),\n [archivedIds],\n );\n const removeArchivedConversation = useArchivedConversationsStore(\n (state) => state.removeArchivedConversation,\n );\n\n const toggleGroupCollapsed = React.useCallback((groupId: string) => {\n setCollapsedGroupIds((prev) => {\n const next = new Set(prev);\n if (next.has(groupId)) {\n next.delete(groupId);\n } else {\n next.add(groupId);\n }\n return next;\n });\n }, []);\n\n const toggleGroupPreviewExpanded = React.useCallback((groupId: string) => {\n setExpandedGroupPreviewIds((prev) => {\n const next = new Set(prev);\n if (next.has(groupId)) {\n next.delete(groupId);\n } else {\n next.add(groupId);\n }\n return next;\n });\n }, []);\n\n React.useEffect(() => {\n if (organizeMode !== \"grouped\") {\n setCollapsedGroupIds(new Set());\n setExpandedGroupPreviewIds(new Set());\n }\n }, [organizeMode]);\n\n const scrollContainerRef = React.useRef<HTMLDivElement>(null);\n\n const [selectedConversationId, setSelectedConversationId] = React.useState<\n string | null\n >(null);\n const [selectedConversationTitle, setSelectedConversationTitle] =\n React.useState<string | null>(null);\n const [openContextMenuId, setOpenContextMenuId] = React.useState<\n string | null\n >(null);\n\n const {\n data,\n isLoading,\n isFetched,\n hasNextPage,\n isFetching,\n isFetchingNextPage,\n fetchNextPage,\n } = usePaginatedConversations();\n\n // Fetch in-progress start tasks\n const { data: startTasks } = useStartTasks();\n\n // Deduped, archive-unaware collection of every conversation currently loaded\n // from the backend. Bulk actions like \"Delete all\" must use this list so\n // hiding archived rows from the UI never shrinks what gets deleted.\n const allLoadedConversations = React.useMemo(() => {\n const all = data?.pages.flatMap((page) => page.items) ?? [];\n // The 10s background refetch re-fetches every loaded page with the\n // `UPDATED_AT_DESC` cursor. If a conversation's `updated_at` shifts between\n // page fetches, a later page can overlap an earlier one and surface the\n // same conversation twice. Dedupe by id (keeping the first/freshest copy)\n // so the rendered count reflects real growth and React keys stay unique.\n const seen = new Set<string>();\n return all.filter((conversation) => {\n if (seen.has(conversation.id)) {\n return false;\n }\n seen.add(conversation.id);\n return true;\n });\n }, [data]);\n\n // Grouped pagination is folder-oriented. Record the first backend page for\n // every conversation so later pages can introduce new folders without\n // mutating the contents of folders that are already visible.\n //\n // Known limitation: the mapping is recomputed from `data.pages` on the 10s\n // background refetch, so a conversation whose `updated_at` shifts can move\n // to an earlier page and change its recorded discovery page. The blast\n // radius is only which rows a collapsed preview shows (expanding a folder\n // always reveals every loaded conversation); pinning discovery pages across\n // refetches is not worth the extra bookkeeping today.\n const conversationPageById = React.useMemo(() => {\n const pageById = new Map<string, number>();\n data?.pages.forEach((page, pageIndex) => {\n page.items.forEach((conversation) => {\n if (!pageById.has(conversation.id)) {\n pageById.set(conversation.id, pageIndex);\n }\n });\n });\n return pageById;\n }, [data]);\n\n // Display collection: same loaded pages, with archived rows filtered out\n // unless the user has opted into \"Show archived\".\n const conversations = React.useMemo(() => {\n if (showArchivedConversations) {\n return allLoadedConversations;\n }\n return allLoadedConversations.filter(\n (conversation) => !archivedIdSet.has(conversation.id),\n );\n }, [allLoadedConversations, archivedIdSet, showArchivedConversations]);\n\n // Facets derive from the unfiltered list so the automation-name rows in the\n // filter menu don't vanish while a narrowing selection is active.\n const automationNameFacets = React.useMemo(\n () => collectAutomationNameFacets(conversations),\n [conversations],\n );\n\n const automationFilteredConversations = React.useMemo(\n () =>\n applyAutomationConversationFilter(\n conversations,\n automationFilterMode,\n selectedAutomationNames,\n automationNameFacets,\n ),\n [\n automationFilterMode,\n automationNameFacets,\n conversations,\n selectedAutomationNames,\n ],\n );\n\n const pinnedConversations = React.useMemo(\n () => resolvePinnedConversations(pinnedIds, conversations),\n [conversations, pinnedIds],\n );\n\n React.useEffect(() => {\n if (!isFetched) {\n return;\n }\n // Prune pins against the unfiltered loaded pages so archived-but-still-\n // pinned rows are not treated as missing. Archived IDs are intentionally\n // not pruned here — pagination would otherwise drop archives that are not\n // on the currently loaded pages and let them reappear in the list.\n const loadedIds =\n data?.pages.flatMap((page) => page.items.map((item) => item.id)) ?? [];\n pruneMissingPinnedConversations(activeBackend.id, loadedIds);\n }, [activeBackend.id, data, isFetched, pruneMissingPinnedConversations]);\n\n React.useEffect(() => {\n if (pinnedIds.length === 0) {\n setExpandedPinnedPreview(false);\n }\n }, [pinnedIds.length]);\n\n const scopedConversations = React.useMemo(() => {\n // The pinned section intentionally bypasses the automation filter (same\n // exemption the thread scope has): a pin is an explicit user override.\n const scopeFiltered =\n threadScope === \"relevant\"\n ? automationFilteredConversations.filter((c) =>\n isExecutionActive(c.execution_status),\n )\n : automationFilteredConversations;\n\n // In the expanded panel, pinned conversations should only appear inside\n // the dedicated pinned section (not duplicated in grouped/flat lists).\n if (compact) {\n return scopeFiltered;\n }\n\n return filterOutPinnedConversations(scopeFiltered, pinnedIds);\n }, [automationFilteredConversations, compact, pinnedIds, threadScope]);\n\n const { recent: recentScoped, older: olderScoped } = React.useMemo(\n () => partitionByCutoff(scopedConversations),\n [scopedConversations],\n );\n\n // Sort the full visible set as one list. The recent/older partition is\n // still computed (it gates the \"Show older\" toggle and \"Load more\"\n // visibility), but the rendering must not use it as a visual boundary —\n // when sorting by `created`, a stale-but-recently-touched conversation\n // would otherwise land in `recent` and render above an actually-newer-\n // by-`created_at` conversation sitting in `older`.\n const sortedVisibleConversations = React.useMemo(() => {\n const visible = showOlderConversations\n ? [...recentScoped, ...olderScoped]\n : recentScoped;\n return sortConversationsByField(visible, conversationSort);\n }, [recentScoped, olderScoped, showOlderConversations, conversationSort]);\n\n const groupLabels = React.useMemo(\n () => ({\n emptyWorkspace: t(I18nKey.CONVERSATION_PANEL$NO_WORKSPACE),\n emptyRepository: t(I18nKey.CONVERSATION_PANEL$NO_REPOSITORY),\n }),\n [t],\n );\n\n const groupedSourceConversations = React.useMemo(() => {\n if (compact || organizeMode !== \"grouped\") {\n return null;\n }\n // Use the unsorted partitions: groupConversations sorts each bucket\n // internally by `sortField`, so pre-sorting the merged input is wasted\n // work in grouped mode (the per-group sort overrides any global order).\n return [...recentScoped, ...(showOlderConversations ? olderScoped : [])];\n }, [\n compact,\n olderScoped,\n organizeMode,\n recentScoped,\n showOlderConversations,\n ]);\n\n const conversationGroups = React.useMemo(() => {\n if (!groupedSourceConversations) {\n return null;\n }\n // Keep every loaded conversation in the group model. Folder discovery only\n // freezes the collapsed preview — expanding a folder must reach later-page\n // rows for that same workspace/repo.\n return groupConversations(\n groupedSourceConversations,\n activeBackend.kind,\n conversationSort,\n groupLabels,\n );\n }, [\n activeBackend.kind,\n conversationSort,\n groupLabels,\n groupedSourceConversations,\n ]);\n\n const groupDiscoveryConversationIds = React.useMemo(() => {\n if (!groupedSourceConversations) {\n return null;\n }\n return getGroupDiscoveryConversationIds(\n groupedSourceConversations,\n conversationPageById,\n activeBackend.kind,\n { forceIncludeConversationId: currentConversationId },\n );\n }, [\n activeBackend.kind,\n conversationPageById,\n currentConversationId,\n groupedSourceConversations,\n ]);\n\n const orderedConversationGroups = React.useMemo(() => {\n if (!conversationGroups) {\n return null;\n }\n return applyGroupFolderOrder(conversationGroups, groupFolderOrder);\n }, [conversationGroups, groupFolderOrder]);\n\n const conversationGroupIds = React.useMemo(\n () => conversationGroups?.map((group) => group.id) ?? [],\n [conversationGroups],\n );\n\n const compactVisibleConversations = React.useMemo(\n () =>\n sortConversationsByField(\n recentScoped.filter((conversation) =>\n isExecutionActive(conversation.execution_status),\n ),\n conversationSort,\n ),\n [conversationSort, recentScoped],\n );\n\n const visibleFlatCount = sortedVisibleConversations.length;\n\n const visibleGroupCount = orderedConversationGroups?.length ?? 0;\n\n const listIsEffectivelyEmpty =\n organizeMode === \"grouped\" && !compact\n ? visibleGroupCount === 0\n : visibleFlatCount === 0;\n\n // Attribution is exact: the automation filter step itself produced zero\n // rows out of a non-empty loaded set (not merely threadScope/older-cutoff\n // effects). Used to pick the empty-state message.\n const emptyDueToAutomationFilter =\n listIsEffectivelyEmpty &&\n automationFilterMode !== \"all\" &&\n conversations.length > 0 &&\n automationFilteredConversations.length === 0;\n\n // Grouped pagination prefers discovering another folder; chronological\n // pagination succeeds when another row appears. Pages that only deepen\n // already-visible folders are not success for the grouped control — the\n // driver walks past them (bounded by the per-click page cap) so a single\n // click can still reach a folder hiding behind deepen-only pages.\n const visibleCount =\n organizeMode === \"grouped\" && !compact\n ? visibleGroupCount\n : visibleFlatCount;\n const loadedPageCount = data?.pages.length ?? 0;\n\n // KNOWN ISSUE (unresolved as of 2026-05-29): users still report that the\n // sidebar \"Load more\" sometimes requires two clicks before new conversations\n // appear. The mitigation below (dedupe by id in `conversations`, plus the\n // floor-tracking driver that keeps fetching until the visible count grows)\n // reduced but did NOT fully eliminate the symptom in manual testing. Likely\n // remaining suspects to investigate next: the agent-server cursor pagination\n // returning an overlapping/short page under `UPDATED_AT_DESC` while the 10s\n // `refetchInterval` reorders pages (see `usePaginatedConversations`), or a\n // React Query state lag where `hasNextPage`/`isFetching` settle a render\n // after the click. If you pick this up, reproduce against a backend with\n // >40 conversations and watch the `/api/conversations/search` cursors.\n //\n // Robust \"Load more\" driver. A single click can fail to surface new rows for\n // two reasons: (1) `fetchNextPage()` is silently dropped while the 10s\n // background refetch is in flight, and (2) a fetched page can yield zero\n // *visible* rows (filtered out by the active scope, or deduped as overlap),\n // so the list does not appear to grow. We capture floors at click time and\n // keep fetching — once idle — until the visible count grows, the grouped\n // per-click page cap is hit, or pages run out. `loadedPageCount` keeps the\n // driver advancing when a fetched page contains only folders that were\n // already discovered.\n const [loadMoreFloor, setLoadMoreFloor] = React.useState<number | null>(null);\n const [loadMorePageFloor, setLoadMorePageFloor] = React.useState<\n number | null\n >(null);\n const visibleCountRef = React.useRef(visibleCount);\n visibleCountRef.current = visibleCount;\n const loadedPageCountRef = React.useRef(loadedPageCount);\n loadedPageCountRef.current = loadedPageCount;\n\n const clearLoadMoreRequest = React.useCallback(() => {\n setLoadMoreFloor(null);\n setLoadMorePageFloor(null);\n }, []);\n\n const requestLoadMore = React.useCallback(() => {\n if (hasNextPage) {\n setLoadMoreFloor(visibleCountRef.current);\n setLoadMorePageFloor(loadedPageCountRef.current);\n }\n }, [hasNextPage]);\n\n React.useEffect(() => {\n if (loadMoreFloor === null) {\n return;\n }\n // Goal met: the visible list grew past where it was when the user clicked.\n if (visibleCount > loadMoreFloor) {\n clearLoadMoreRequest();\n return;\n }\n // Hard cap (grouped only): pages that merely deepen already-visible\n // folders are walked past — a new folder may sit right behind them — but\n // never unbounded many, so one click cannot drain the whole cursor.\n // Chronological mode keeps its pre-existing behavior: fetch until a\n // visible row appears or pages run out.\n if (\n organizeMode === \"grouped\" &&\n !compact &&\n loadMorePageFloor != null &&\n loadedPageCount >= loadMorePageFloor + MAX_PAGES_PER_LOAD_MORE_CLICK\n ) {\n clearLoadMoreRequest();\n return;\n }\n // Wait for any in-flight fetch (including the background refetch) to settle\n // before evaluating `hasNextPage`; React Query may transiently clear that\n // flag while replacing the last page.\n if (isFetching || isFetchingNextPage) {\n return;\n }\n // Nothing more to fetch — stop waiting even if the list did not grow.\n if (!hasNextPage) {\n clearLoadMoreRequest();\n return;\n }\n fetchNextPage();\n }, [\n clearLoadMoreRequest,\n compact,\n loadMoreFloor,\n loadMorePageFloor,\n visibleCount,\n loadedPageCount,\n organizeMode,\n hasNextPage,\n isFetching,\n isFetchingNextPage,\n fetchNextPage,\n ]);\n\n const isLoadingMore = loadMoreFloor !== null || isFetchingNextPage;\n\n const { mutate: deleteConversation, mutateAsync: deleteConversationAsync } =\n useDeleteConversation();\n const { mutate: pauseConversation } = useUnifiedPauseConversation();\n const { mutate: updateConversation } = useUpdateConversation();\n\n // The next page of conversations is loaded only via the explicit \"Load\n // more\" link rendered at the end of the list — there is no scroll-driven\n // pagination, which previously caused the panel to feel like it had stray\n // scrollable space at the bottom.\n const olderHidden = olderScoped.length > 0 && !showOlderConversations;\n // Compact mode also hides \"Load more\" — paginating into stale conversations\n // contradicts the \"active only\" intent of the icon rail.\n // Availability tracks backend exhaustion, never visible emptiness: a\n // client-side filter (archiving, the automation filter) can hide every row\n // of the loaded pages, and hiding the control there would strand the\n // remaining pages behind an empty-state message with no way forward.\n // `requestLoadMore`'s floor driver keeps paging until a visible row\n // appears, so a single click walks past pages that are entirely filtered.\n const showLoadMore = !!hasNextPage && !olderHidden && !compact;\n\n const { mutate: createConversation } = useCreateConversation();\n const isCreatingConversationFlow = useIsCreatingConversation();\n\n const launchFromGroup = React.useCallback(\n (launch: ConversationGroupLaunch) => {\n if (isCreatingConversationFlow) return;\n createConversation(\n {\n workingDir: launch.workingDir,\n repository: launch.repository,\n entryPoint: \"sidebar_relaunch_project\",\n },\n {\n onSuccess: (data) => {\n navigate(`/conversations/${data.conversation_id}`);\n },\n },\n );\n },\n [createConversation, isCreatingConversationFlow, navigate],\n );\n\n const handleDeleteProject = React.useCallback(\n (conversationId: string, title: string) => {\n setConfirmDeleteModalVisible(true);\n setSelectedConversationId(conversationId);\n setSelectedConversationTitle(title);\n },\n [],\n );\n\n const handleArchiveProject = React.useCallback(\n (conversationId: string, title: string) => {\n setConfirmArchiveModalVisible(true);\n setSelectedConversationId(conversationId);\n setSelectedConversationTitle(title);\n },\n [],\n );\n\n // Unarchiving needs no confirmation: it restores a row the user can archive\n // again in one click, and nothing about the conversation itself changes.\n const handleUnarchiveProject = React.useCallback(\n (conversationId: string) => {\n removeArchivedConversation(activeBackend.id, conversationId);\n },\n [activeBackend.id, removeArchivedConversation],\n );\n\n const handleStopConversation = React.useCallback((conversationId: string) => {\n setConfirmStopModalVisible(true);\n setSelectedConversationId(conversationId);\n }, []);\n\n const handleConversationTitleChange = React.useCallback(\n (conversationId: string, newTitle: string) => {\n updateConversation(\n { conversationId, newTitle },\n {\n onSuccess: () => {\n displaySuccessToast(t(I18nKey.CONVERSATION$TITLE_UPDATED));\n },\n },\n );\n },\n [t, updateConversation],\n );\n\n const handleConfirmDelete = () => {\n if (selectedConversationId) {\n const conversationId = selectedConversationId;\n deleteConversation(\n { conversationId },\n {\n onSuccess: () => {\n removeArchivedConversation(activeBackend.id, conversationId);\n if (conversationId === currentConversationId) {\n navigate(\"/conversations\");\n }\n },\n },\n );\n }\n };\n\n const handleConfirmArchive = () => {\n if (!selectedConversationId) {\n return;\n }\n archiveConversation(activeBackend.id, selectedConversationId);\n unpinConversation(activeBackend.id, selectedConversationId);\n if (selectedConversationId === currentConversationId) {\n navigate(\"/conversations\");\n }\n };\n\n const handleConfirmStop = () => {\n if (selectedConversationId) {\n pauseConversation({\n conversationId: selectedConversationId,\n });\n }\n };\n\n const handleConfirmDeleteAll = async () => {\n // Delete against the unfiltered loaded set so archived (currently hidden)\n // conversations are still removed from the server — matching the action's\n // \"delete all conversations\" label and confirmation count.\n const idsToDelete = allLoadedConversations.map((c) => c.id);\n const results = await Promise.allSettled(\n idsToDelete.map((conversationId) =>\n deleteConversationAsync({ conversationId }),\n ),\n );\n\n const deletedIds = results.flatMap((result, index) =>\n result.status === \"fulfilled\" ? [idsToDelete[index]] : [],\n );\n const failedCount = results.length - deletedIds.length;\n\n for (const conversationId of deletedIds) {\n removeArchivedConversation(activeBackend.id, conversationId);\n }\n\n if (\n currentConversationId !== null &&\n deletedIds.includes(currentConversationId)\n ) {\n navigate(\"/conversations\");\n }\n\n if (failedCount > 0) {\n displayErrorToast(\n `${failedCount} conversation${failedCount === 1 ? \"\" : \"s\"} could not be deleted.`,\n );\n }\n };\n\n const renderConversationCard = React.useCallback(\n (\n conversation: (typeof conversations)[number],\n options?: { inPinnedSection?: boolean },\n ) => {\n const isPinned = pinnedIds.includes(conversation.id);\n const isArchived = archivedIdSet.has(conversation.id);\n if (compact) {\n return (\n <CompactConversationRow\n key={conversation.id}\n conversationId={conversation.id}\n title={conversation.title ?? \"\"}\n selectedRepository={{\n selected_repository: conversation.selected_repository,\n selected_branch: conversation.selected_branch,\n git_provider: conversation.git_provider as Provider,\n }}\n executionStatus={conversation.execution_status}\n sandboxStatus={conversation.sandbox_status}\n lastUpdatedAt={conversation.updated_at}\n createdAt={conversation.created_at}\n workspaceWorkingDir={\n conversation.selected_workspace ??\n conversation.workspace?.working_dir\n }\n isActive={conversation.id === currentConversationId}\n onClose={onClose}\n showRepositoryMetadata={showRepoBranchMetadata}\n llmModel={conversation.llm_model}\n showLlmProfiles={showLlmProfiles}\n agentKind={conversation.agent_kind}\n acpServer={conversation.acp_server}\n tags={conversation.tags}\n showTags={showTagsMetadata}\n />\n );\n }\n return (\n <Tooltip\n key={conversation.id}\n placement=\"right-start\"\n delay={1000}\n closeDelay={100}\n isDisabled={\n !showHoverMetadata || openContextMenuId === conversation.id\n }\n disableAnimation={import.meta.env.MODE === \"test\"}\n className=\"max-w-none overflow-visible rounded-xl border border-[var(--oh-border)] bg-base-secondary p-0 text-white shadow-xl\"\n content={\n <ConversationCardPreview\n title={conversation.title ?? \"\"}\n executionStatus={conversation.execution_status}\n sandboxStatus={conversation.sandbox_status}\n selectedRepository={{\n selected_repository: conversation.selected_repository,\n selected_branch: conversation.selected_branch,\n git_provider: conversation.git_provider as Provider,\n }}\n workspaceWorkingDir={\n conversation.selected_workspace ??\n conversation.workspace?.working_dir\n }\n llmModel={conversation.llm_model}\n agentKind={conversation.agent_kind}\n acpServer={conversation.acp_server}\n createdAt={conversation.created_at}\n tags={conversation.tags}\n />\n }\n >\n <NavigationLink\n to={backendScopedPath(`/conversations/${conversation.id}`)}\n onClick={onClose}\n className={cn(\n \"block rounded-md transition-colors\",\n openContextMenuId !== conversation.id &&\n \"hover:bg-[var(--oh-surface)]\",\n (conversation.id === currentConversationId ||\n openContextMenuId === conversation.id) &&\n \"bg-[var(--oh-surface)]\",\n )}\n >\n <ConversationCard\n onDelete={() =>\n handleDeleteProject(conversation.id, conversation.title ?? \"\")\n }\n // Exactly one direction is offered per row, so the menu always\n // reflects the conversation's current archived state.\n onArchive={\n isArchived\n ? undefined\n : () =>\n handleArchiveProject(\n conversation.id,\n conversation.title ?? \"\",\n )\n }\n onUnarchive={\n isArchived\n ? () => handleUnarchiveProject(conversation.id)\n : undefined\n }\n onStop={() => handleStopConversation(conversation.id)}\n onChangeTitle={(title) =>\n handleConversationTitleChange(conversation.id, title)\n }\n title={conversation.title ?? \"\"}\n selectedRepository={{\n selected_repository: conversation.selected_repository,\n selected_branch: conversation.selected_branch,\n git_provider: conversation.git_provider as Provider,\n }}\n lastUpdatedAt={conversation.updated_at}\n createdAt={conversation.created_at}\n executionStatus={conversation.execution_status}\n sandboxStatus={conversation.sandbox_status}\n conversationId={conversation.id}\n contextMenuOpen={openContextMenuId === conversation.id}\n onContextMenuToggle={(isOpen) =>\n setOpenContextMenuId(isOpen ? conversation.id : null)\n }\n isActive={conversation.id === currentConversationId}\n workspaceWorkingDir={\n conversation.selected_workspace ??\n conversation.workspace?.working_dir\n }\n showRepositoryMetadata={showRepoBranchMetadata}\n llmModel={conversation.llm_model}\n showLlmProfiles={showLlmProfiles}\n agentKind={conversation.agent_kind}\n acpServer={conversation.acp_server}\n tags={conversation.tags}\n showTags={showTagsMetadata}\n isArchived={isArchived}\n isPinned={isPinned}\n onTogglePin={() => togglePin(activeBackend.id, conversation.id)}\n alwaysShowPinIcon={isPinned && !options?.inPinnedSection}\n />\n </NavigationLink>\n </Tooltip>\n );\n },\n [\n activeBackend.id,\n archivedIdSet,\n compact,\n currentConversationId,\n handleArchiveProject,\n handleConversationTitleChange,\n handleDeleteProject,\n handleStopConversation,\n handleUnarchiveProject,\n onClose,\n openContextMenuId,\n pinnedIds,\n showRepoBranchMetadata,\n showLlmProfiles,\n showTagsMetadata,\n showHoverMetadata,\n togglePin,\n ],\n );\n\n // Standard layout: panel fills its slot in the sidebar; the inner scroll\n // child fills the panel and scrolls when its content overflows. Modals are\n // siblings of the scroll element and are `position: fixed`, so they don't\n // participate in the panel's scroll geometry.\n // Gate on `isLoading` / `!isFetched` (true only until the first fetch settles),\n // not `isFetching` — the latter flips back to true on every 10s background\n // refetch, causing the skeleton/empty-state to flicker when the list is empty.\n const showInitialSkeleton = isLoading || !isFetched;\n const showPinnedSection =\n !compact && !showInitialSkeleton && pinnedConversations.length > 0;\n const showEmptyState =\n isFetched &&\n !isLoading &&\n !compact &&\n listIsEffectivelyEmpty &&\n !showPinnedSection &&\n !startTasks?.length;\n\n const showConversationHeader = !compact;\n\n return (\n <div\n ref={ref}\n data-testid=\"conversation-panel\"\n className=\"flex h-full min-h-0 w-full flex-col\"\n >\n {showConversationHeader && (\n <div\n className={cn(\n // Pull flush to the sidebar edges: `-ml-2.5` matches aside `pl-2.5`;\n // width extends by that inset on the right now that aside is `pr-0`.\n \"-ml-2.5 w-[calc(100%+0.625rem)] max-w-none box-border border-b\",\n isListScrolled ? \"border-[var(--oh-border)]\" : \"border-transparent\",\n )}\n >\n <div\n data-testid=\"older-conversations-summary\"\n className=\"flex min-w-0 flex-nowrap items-center gap-x-2 py-2 pl-4 pr-2.5 text-[var(--oh-muted)]\"\n >\n <span className=\"min-w-0 truncate text-sm font-medium text-[var(--oh-muted)]\">\n {t(I18nKey.SIDEBAR$CONVERSATIONS)}\n </span>\n <div className=\"ml-auto flex shrink-0 items-center gap-0.5\">\n <ConversationPanelNewThreadPicker\n backendKind={activeBackend.kind}\n />\n <ConversationPanelFilterMenu\n filterMenuOpen={filterMenuOpen}\n setFilterMenuOpen={setFilterMenuOpen}\n menuRef={filterMenuRef}\n backendKind={activeBackend.kind}\n organizeMode={organizeMode}\n setOrganizeMode={setOrganizeMode}\n conversationSort={conversationSort}\n setConversationSort={setConversationSort}\n threadScope={threadScope}\n setThreadScope={setThreadScope}\n automationFilterMode={automationFilterMode}\n setAutomationFilterMode={setAutomationFilterMode}\n selectedAutomationNames={selectedAutomationNames}\n onToggleAutomationName={toggleAutomationName}\n automationNameFacets={automationNameFacets}\n showOlderConversations={showOlderConversations}\n showArchivedConversations={showArchivedConversations}\n toggleShowArchivedConversations={\n toggleShowArchivedConversations\n }\n toggleShowOlderConversations={toggleShowOlderConversations}\n showRepoBranchMetadata={showRepoBranchMetadata}\n toggleShowRepoBranchMetadata={toggleShowRepoBranchMetadata}\n showLlmProfiles={showLlmProfiles}\n toggleShowLlmProfiles={toggleShowLlmProfiles}\n showTagsMetadata={showTagsMetadata}\n toggleShowTagsMetadata={toggleShowTagsMetadata}\n showHoverMetadata={showHoverMetadata}\n toggleShowHoverMetadata={toggleShowHoverMetadata}\n totalConversationsCount={allLoadedConversations.length}\n onRequestDeleteAll={() => setConfirmDeleteAllVisible(true)}\n />\n </div>\n </div>\n </div>\n )}\n\n <div\n ref={scrollContainerRef}\n data-testid=\"conversation-panel-list-scroll\"\n onScroll={(event) => {\n setIsListScrolled(event.currentTarget.scrollTop > 0);\n }}\n className={cn(\n \"flex min-h-0 flex-1 flex-col overflow-y-auto overflow-x-hidden overscroll-contain custom-scrollbar-always\",\n !compact && \"conversation-panel-list-scroll\",\n )}\n >\n {showInitialSkeleton && <ConversationCardSkeleton compact={compact} />}\n\n {!compact && showEmptyState && (\n <div\n data-testid=\"conversation-panel-empty-state\"\n className=\"flex min-h-0 flex-1 flex-col items-center justify-center px-4 py-8\"\n >\n <p className=\"text-xs text-[var(--oh-muted)]\">\n {t(\n emptyDueToAutomationFilter\n ? I18nKey.CONVERSATION_PANEL$NO_AUTOMATION_MATCHES\n : I18nKey.CONVERSATION$NO_CONVERSATIONS,\n )}\n </p>\n </div>\n )}\n\n {showPinnedSection ? (\n <ConversationPanelPinnedSection\n pinnedConversations={pinnedConversations}\n isPreviewExpanded={expandedPinnedPreview}\n onTogglePreviewExpanded={() =>\n setExpandedPinnedPreview((current) => !current)\n }\n activeConversationId={currentConversationId}\n showDivider={!compact && organizeMode === \"chronological\"}\n renderConversationCard={(conversation) =>\n renderConversationCard(conversation, { inPinnedSection: true })\n }\n />\n ) : null}\n\n {/* Render in-progress start tasks first (skipped in compact mode —\n their rich card layout doesn't fit in the icon rail). */}\n {!compact &&\n startTasks?.map((task) => (\n <NavigationLink\n key={task.id}\n to={backendScopedPath(`/conversations/task-${task.id}`)}\n onClick={onClose}\n className=\"block\"\n >\n <StartTaskCard task={task} />\n </NavigationLink>\n ))}\n\n {!showInitialSkeleton && compact\n ? compactVisibleConversations.map((conversation) =>\n renderConversationCard(conversation),\n )\n : null}\n\n {!showInitialSkeleton &&\n !compact &&\n organizeMode === \"grouped\" &&\n orderedConversationGroups &&\n orderedConversationGroups.length > 0 ? (\n <ConversationGroupFolderList\n groups={orderedConversationGroups}\n groupIds={conversationGroupIds}\n groupFolderOrder={groupFolderOrder}\n setGroupFolderOrder={setGroupFolderOrder}\n collapsedGroupIds={collapsedGroupIds}\n expandedGroupPreviewIds={expandedGroupPreviewIds}\n discoveryConversationIds={groupDiscoveryConversationIds}\n onToggleGroupCollapsed={toggleGroupCollapsed}\n onToggleGroupPreviewExpanded={toggleGroupPreviewExpanded}\n isCreatingConversationFlow={isCreatingConversationFlow}\n activeConversationId={currentConversationId}\n onLaunchFromGroup={launchFromGroup}\n renderConversationCard={(conversation) =>\n renderConversationCard(conversation)\n }\n />\n ) : null}\n\n {!showInitialSkeleton &&\n !compact &&\n organizeMode === \"chronological\" ? (\n <div className=\"space-y-0.5\">\n {sortedVisibleConversations.map((conversation) =>\n renderConversationCard(conversation),\n )}\n </div>\n ) : null}\n\n {/* Explicit \"Load more\" trigger. Only shown when more pages exist\n *and* the older list is currently visible (or there are no older\n conversations to begin with) — otherwise the next page would be\n populated mostly with conversations the user has chosen to hide. */}\n {showLoadMore &&\n (isLoadingMore ? (\n <div className=\"py-1\">\n <ConversationCardSkeleton compact={compact} />\n </div>\n ) : (\n <div className=\"flex justify-center py-4\">\n <button\n type=\"button\"\n data-testid=\"load-more-conversations\"\n onClick={requestLoadMore}\n className=\"text-xs text-[var(--oh-muted)] hover:text-white\"\n >\n {t(I18nKey.CONVERSATION$LOAD_MORE)}\n </button>\n </div>\n ))}\n </div>\n\n {confirmDeleteModalVisible && (\n <ConfirmDeleteModal\n onConfirm={() => {\n handleConfirmDelete();\n setConfirmDeleteModalVisible(false);\n setSelectedConversationTitle(null);\n }}\n onCancel={() => {\n setConfirmDeleteModalVisible(false);\n setSelectedConversationTitle(null);\n }}\n conversationTitle={selectedConversationTitle ?? undefined}\n />\n )}\n\n {confirmArchiveModalVisible && (\n <ConfirmArchiveModal\n onConfirm={() => {\n handleConfirmArchive();\n setConfirmArchiveModalVisible(false);\n setSelectedConversationTitle(null);\n }}\n onCancel={() => {\n setConfirmArchiveModalVisible(false);\n setSelectedConversationTitle(null);\n }}\n conversationTitle={selectedConversationTitle ?? undefined}\n />\n )}\n\n {confirmDeleteAllVisible && (\n <ConfirmDeleteModal\n title={t(I18nKey.CONVERSATION$CONFIRM_DELETE_ALL_TITLE)}\n description={t(I18nKey.CONVERSATION$CONFIRM_DELETE_ALL_DESC, {\n count: allLoadedConversations.length,\n })}\n onConfirm={async () => {\n await handleConfirmDeleteAll();\n setConfirmDeleteAllVisible(false);\n }}\n onCancel={() => setConfirmDeleteAllVisible(false)}\n />\n )}\n\n {confirmStopModalVisible && (\n <ConfirmStopModal\n onConfirm={() => {\n handleConfirmStop();\n setConfirmStopModalVisible(false);\n }}\n onCancel={() => setConfirmStopModalVisible(false)}\n />\n )}\n\n {confirmExitConversationModalVisible && (\n <ExitConversationModal\n onConfirm={() => {\n onClose?.();\n }}\n onClose={() => setConfirmExitConversationModalVisible(false)}\n onCancel={() => setConfirmExitConversationModalVisible(false)}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require("../../../_virtual/_rolldown/runtime.cjs");const e=require("../../../node_modules/react-i18next/dist/es/useTranslation.cjs"),t=require("../../../i18n/declaration.cjs"),n=require("../markdown/markdown-renderer.cjs"),r=require("../../../stores/use-workspace-mutation-counter.cjs"),
|
|
1
|
+
require("../../../_virtual/_rolldown/runtime.cjs");const e=require("../../../node_modules/react-i18next/dist/es/useTranslation.cjs"),t=require("../../../i18n/declaration.cjs"),n=require("../markdown/markdown-renderer.cjs"),r=require("../../../utils/is-markdown-file-path.cjs"),i=require("../../../stores/use-workspace-mutation-counter.cjs"),a=require("../../../hooks/query/use-workspace-file-content.cjs"),o=require("./highlighted-source-view.cjs");let s=require("react/jsx-runtime");var c=new Set([`html`,`htm`,`svg`]),l={pptx:`PowerPoint`,ppt:`PowerPoint`,docx:`Word`,doc:`Word`,xlsx:`Excel`,xls:`Excel`};function u(e){let t=e.lastIndexOf(`.`);return t===-1?``:e.slice(t+1).toLowerCase()}function d({path:n}){let{t:r}=e.useTranslation(`openhands`),i=l[u(n)];return(0,s.jsx)(`div`,{className:`flex h-full w-full items-center justify-center text-sm text-[var(--oh-muted)]`,"data-testid":i?`file-content-viewer-unsupported-document`:`file-content-viewer-binary-fallback`,children:i?r(t.I18nKey.FILES$UNSUPPORTED_DOCUMENT,{type:i}):r(t.I18nKey.FILES$BINARY_FALLBACK)})}function f({path:l,viewMode:f}){let{t:p}=e.useTranslation(`openhands`),m=a.useWorkspaceFileContent(l),h=i.useWorkspaceMutationCounter(e=>e.count);if(m.isLoading)return(0,s.jsx)(`div`,{className:`flex h-full w-full items-center justify-center text-sm text-[var(--oh-muted)]`,children:p(t.I18nKey.FILES$LOADING_FILES)});if(m.isError||!m.data)return(0,s.jsx)(`div`,{className:`flex h-full w-full items-center justify-center text-sm text-[var(--oh-muted)]`,"data-testid":`file-content-viewer-error`,children:m.error?.message??p(t.I18nKey.FILES$LOAD_ERROR)});let{kind:g,text:_,staticUrl:v,mimeType:y}=m.data,b=i.withWorkspaceCacheBuster(v,h);return f===`plain`?g===`text`&&_!==null?(0,s.jsx)(o.HighlightedSourceView,{path:l,text:_,mimeType:y??void 0}):(0,s.jsx)(d,{path:l}):g===`image`?(0,s.jsx)(`div`,{className:`flex h-full w-full items-center justify-center bg-[var(--oh-surface)] p-4`,"data-testid":`file-content-viewer-image`,children:(0,s.jsx)(`img`,{src:b,alt:l,className:`max-h-full max-w-full object-contain`})}):g===`pdf`?(0,s.jsx)(`iframe`,{title:l,src:b,sandbox:`allow-same-origin`,"data-testid":`file-content-viewer-iframe`,className:`h-full w-full bg-white`}):g===`binary`?(0,s.jsx)(d,{path:l}):y===`text/html`||c.has(u(l))?(0,s.jsx)(`iframe`,{title:l,src:b,sandbox:`allow-same-origin`,"data-testid":`file-content-viewer-iframe`,className:`h-full w-full bg-white`}):g===`text`&&r.isMarkdownFilePath(l)?(0,s.jsx)(`div`,{"data-testid":`file-content-viewer-markdown`,className:`h-full w-full overflow-auto bg-[var(--oh-surface)] text-white custom-scrollbar-always [--oh-scroll-fade-from:var(--oh-surface)]`,children:(0,s.jsx)(`div`,{className:`prose prose-sm prose-invert max-w-none p-6 [--tw-prose-body:#fff] [--tw-prose-bold:#fff] [--tw-prose-headings:#fff] [--tw-prose-lead:#fff] [--tw-prose-counters:#fff] [--tw-prose-quotes:#fff] [--tw-prose-quote-borders:var(--oh-border-subtle)] [--tw-prose-bullets:var(--oh-muted)] [--tw-prose-hr:var(--oh-border-subtle)] [--tw-prose-captions:var(--oh-muted)] [--tw-prose-kbd:#fff]`,children:(0,s.jsx)(n.MarkdownRenderer,{content:_??``,includeStandard:!0,includeHeadings:!0})})}):g===`text`&&_!==null?(0,s.jsx)(o.HighlightedSourceView,{path:l,text:_,mimeType:y??void 0}):(0,s.jsx)(d,{path:l})}exports.FileContentViewer=f;
|
|
2
2
|
//# sourceMappingURL=file-content-viewer.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-content-viewer.cjs","names":[],"sources":["../../../../src/components/features/files-tab/file-content-viewer.tsx"],"sourcesContent":["import { useTranslation } from \"react-i18next\";\n\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { useWorkspaceFileContent } from \"#/hooks/query/use-workspace-file-content\";\nimport {\n useWorkspaceMutationCounter,\n withWorkspaceCacheBuster,\n} from \"#/stores/use-workspace-mutation-counter\";\nimport { MarkdownRenderer } from \"#/components/features/markdown/markdown-renderer\";\nimport { HighlightedSourceView } from \"./highlighted-source-view\";\nimport type { ViewMode } from \"./view-mode\";\n\ninterface FileContentViewerProps {\n path: string;\n viewMode: ViewMode;\n}\n\nconst HTML_LIKE_EXTS = new Set([\"html\", \"htm\", \"svg\"]);\nconst MARKDOWN_EXTS = new Set([\"md\", \"markdown\", \"mdx\"]);\n\n// Office/document formats we can't preview inline. The label doubles as the\n// allow-list (a present entry => Office doc) and feeds a clear, format-named\n// \"no preview\" message instead of the generic binary fallback.\nconst OFFICE_DOCUMENT_LABELS: Record<string, string> = {\n pptx: \"PowerPoint\",\n ppt: \"PowerPoint\",\n docx: \"Word\",\n doc: \"Word\",\n xlsx: \"Excel\",\n xls: \"Excel\",\n};\n\nfunction getExtension(path: string): string {\n const idx = path.lastIndexOf(\".\");\n return idx === -1 ? \"\" : path.slice(idx + 1).toLowerCase();\n}\n\n/**\n * Fallback shown when a file's bytes aren't previewable. Office documents\n * (.pptx / .docx / .xlsx …) get a clear, format-named message; every other\n * binary keeps the generic \"binary file\" string so the pane is never blank.\n */\nfunction UnpreviewableFallback({ path }: { path: string }) {\n const { t } = useTranslation(\"openhands\");\n const documentLabel = OFFICE_DOCUMENT_LABELS[getExtension(path)];\n return (\n <div\n className=\"flex h-full w-full items-center justify-center text-sm text-[var(--oh-muted)]\"\n data-testid={\n documentLabel\n ? \"file-content-viewer-unsupported-document\"\n : \"file-content-viewer-binary-fallback\"\n }\n >\n {documentLabel\n ? t(I18nKey.FILES$UNSUPPORTED_DOCUMENT, { type: documentLabel })\n : t(I18nKey.FILES$BINARY_FALLBACK)}\n </div>\n );\n}\n\n/**\n * Renders the contents of a single workspace file. In `rich` mode we point\n * an iframe / <img> straight at the agent server's static workspace\n * fileserver for HTML / SVG / images / PDFs, so relative asset references\n * load naturally. In `plain` mode we always show the raw bytes as text (or\n * a fallback message for binaries).\n */\nexport function FileContentViewer({ path, viewMode }: FileContentViewerProps) {\n const { t } = useTranslation(\"openhands\");\n const query = useWorkspaceFileContent(path);\n // Subscribe to the workspace mutation counter so the iframe / <img> src\n // changes after every agent-side edit, forcing a fresh fetch even when\n // the *path* hasn't moved (e.g. agent rewrote `style.css` referenced by\n // the currently-displayed `index.html`).\n const mutationCounter = useWorkspaceMutationCounter((state) => state.count);\n\n if (query.isLoading) {\n return (\n <div className=\"flex h-full w-full items-center justify-center text-sm text-[var(--oh-muted)]\">\n {t(I18nKey.FILES$LOADING_FILES)}\n </div>\n );\n }\n\n if (query.isError || !query.data) {\n // Show a load-error message rather than the binary-fallback string —\n // these are completely different failure modes (couldn't even fetch\n // the file vs. fetched fine but the bytes aren't previewable) and\n // mixing them up hides real backend failures behind a misleading\n // \"Binary file\" label. Prefer the underlying error's own message when\n // we have one; fall back to the generic translated string otherwise.\n return (\n <div\n className=\"flex h-full w-full items-center justify-center text-sm text-[var(--oh-muted)]\"\n data-testid=\"file-content-viewer-error\"\n >\n {(query.error as Error | undefined)?.message ??\n t(I18nKey.FILES$LOAD_ERROR)}\n </div>\n );\n }\n\n const { kind, text, staticUrl, mimeType } = query.data;\n const bustedStaticUrl = withWorkspaceCacheBuster(staticUrl, mutationCounter);\n\n // ----- Plain mode: raw source bytes, syntax-highlighted when we can\n // recognize the grammar (falls through to a `<pre>` otherwise). This\n // includes \"plain\" view of markdown / HTML, where the point is to see\n // the markup behind the rich preview.\n if (viewMode === \"plain\") {\n if (kind === \"text\" && text !== null) {\n return (\n <HighlightedSourceView\n path={path}\n text={text}\n mimeType={mimeType ?? undefined}\n />\n );\n }\n return <UnpreviewableFallback path={path} />;\n }\n\n // ----- Rich mode: render HTML, markdown, images, PDFs from staticUrl. ----\n if (kind === \"image\") {\n return (\n <div\n className=\"flex h-full w-full items-center justify-center bg-[var(--oh-surface)] p-4\"\n data-testid=\"file-content-viewer-image\"\n >\n <img\n src={bustedStaticUrl}\n alt={path}\n className=\"max-h-full max-w-full object-contain\"\n />\n </div>\n );\n }\n\n if (kind === \"pdf\") {\n // PDFs can carry embedded JavaScript (AcroForm, OpenAction…). Even\n // though `staticUrl` lives on the agent server origin, the PDF\n // viewer's scripting capability isn't worth the risk for a file\n // preview, so we sandbox the iframe. `allow-same-origin` lets the\n // browser's built-in PDF viewer load the underlying bytes without\n // tripping cross-origin restrictions; we omit `allow-scripts`\n // because no PDF preview we care about needs to run JS in the\n // parent's origin.\n return (\n <iframe\n title={path}\n src={bustedStaticUrl}\n sandbox=\"allow-same-origin\"\n data-testid=\"file-content-viewer-iframe\"\n className=\"h-full w-full bg-white\"\n />\n );\n }\n\n if (kind === \"binary\") {\n return <UnpreviewableFallback path={path} />;\n }\n\n // Text-like content.\n if (mimeType === \"text/html\" || HTML_LIKE_EXTS.has(getExtension(path))) {\n // Sandbox the preview iframe: `allow-same-origin` keeps the frame on\n // the workspace fileserver's origin so relative `<link href=\"…\">`,\n // `<img src=\"…\">`, etc. continue to resolve, while the absence of\n // `allow-scripts` means any `<script>` (or `onerror=…`, inline event\n // handler, …) inside the previewed file is inert. This is exactly\n // the safe-preview posture we want — users can look at their HTML\n // without it executing in the canvas's context.\n return (\n <iframe\n title={path}\n src={bustedStaticUrl}\n sandbox=\"allow-same-origin\"\n data-testid=\"file-content-viewer-iframe\"\n className=\"h-full w-full bg-white\"\n />\n );\n }\n\n if (kind === \"text\" && MARKDOWN_EXTS.has(getExtension(path))) {\n // Match the right-pane chrome color so the rich-rendered markdown\n // blends with the surrounding files tab instead of painting a stark\n // white card. We use `prose-invert` (typography plugin's dark-theme\n // variant) and then layer arbitrary CSS-variable overrides on top to\n // pin body / bold / quote text to pure white — the user specifically\n // asked for every text element (not just headings) to read as white.\n // The custom heading components in `markdown/headings.tsx` already\n // hard-code `text-white`, so headers stay white through this change.\n return (\n <div\n data-testid=\"file-content-viewer-markdown\"\n className=\"h-full w-full overflow-auto bg-[var(--oh-surface)] text-white custom-scrollbar-always\"\n >\n <div className=\"prose prose-sm prose-invert max-w-none p-6 [--tw-prose-body:#fff] [--tw-prose-bold:#fff] [--tw-prose-headings:#fff] [--tw-prose-lead:#fff] [--tw-prose-counters:#fff] [--tw-prose-quotes:#fff] [--tw-prose-quote-borders:var(--oh-border-subtle)] [--tw-prose-bullets:var(--oh-muted)] [--tw-prose-hr:var(--oh-border-subtle)] [--tw-prose-captions:var(--oh-muted)] [--tw-prose-kbd:#fff]\">\n <MarkdownRenderer\n content={text ?? \"\"}\n includeStandard\n includeHeadings\n />\n </div>\n </div>\n );\n }\n\n // Rich mode for actual source code (.ts, .py, .yaml, .css, …): there\n // is no other \"rich\" rendering to fall back to, so highlighted source\n // IS the rich view. Identical to the plain-mode treatment — keeping\n // both branches reuse `HighlightedSourceView` means the toggle has the\n // same visual identity for source files in both modes (which is the\n // honest answer: source IS rendered code).\n if (kind === \"text\" && text !== null) {\n return (\n <HighlightedSourceView\n path={path}\n text={text}\n mimeType={mimeType ?? undefined}\n />\n );\n }\n\n // Truly unknown / empty payload — show a fallback so the pane is never\n // blank.\n return <UnpreviewableFallback path={path} />;\n}\n"],"mappings":"8aAiBA,IAAM,EAAiB,IAAI,IAAI,CAAC,OAAQ,MAAO,KAAK,CAAC,EAC/C,EAAgB,IAAI,IAAI,CAAC,KAAM,WAAY,KAAK,CAAC,EAKjD,EAAiD,CACrD,KAAM,aACN,IAAK,aACL,KAAM,OACN,IAAK,OACL,KAAM,QACN,IAAK,OACP,EAEA,SAAS,EAAa,EAAsB,CAC1C,IAAM,EAAM,EAAK,YAAY,GAAG,EAChC,OAAO,IAAQ,GAAK,GAAK,EAAK,MAAM,EAAM,CAAC,EAAE,YAAY,CAC3D,CAOA,SAAS,EAAsB,CAAE,QAA0B,CACzD,GAAM,CAAE,KAAM,EAAA,eAAe,WAAW,EAClC,EAAgB,EAAuB,EAAa,CAAI,GAC9D,OACE,EAAA,EAAA,KAAC,MAAD,CACE,UAAU,gFACV,cACE,EACI,2CACA,+CAGL,EACG,EAAE,EAAA,QAAQ,2BAA4B,CAAE,KAAM,CAAc,CAAC,EAC7D,EAAE,EAAA,QAAQ,qBAAqB,CAChC,CAAA,CAET,CASA,SAAgB,EAAkB,CAAE,OAAM,YAAoC,CAC5E,GAAM,CAAE,KAAM,EAAA,eAAe,WAAW,EAClC,EAAQ,EAAA,wBAAwB,CAAI,EAKpC,EAAkB,EAAA,4BAA6B,GAAU,EAAM,KAAK,EAE1E,GAAI,EAAM,UACR,OACE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,yFACZ,EAAE,EAAA,QAAQ,mBAAmB,CAC3B,CAAA,EAIT,GAAI,EAAM,SAAW,CAAC,EAAM,KAO1B,OACE,EAAA,EAAA,KAAC,MAAD,CACE,UAAU,gFACV,cAAY,qCAEV,EAAM,OAA6B,SACnC,EAAE,EAAA,QAAQ,gBAAgB,CACzB,CAAA,EAIT,GAAM,CAAE,OAAM,OAAM,YAAW,YAAa,EAAM,KAC5C,EAAkB,EAAA,yBAAyB,EAAW,CAAe,EA0H3E,OApHI,IAAa,QACX,IAAS,QAAU,IAAS,MAE5B,EAAA,EAAA,KAAC,EAAA,sBAAD,CACQ,OACA,OACN,SAAU,GAAY,IAAA,EACvB,CAAA,GAGE,EAAA,EAAA,KAAC,EAAD,CAA6B,MAAO,CAAA,EAIzC,IAAS,SAET,EAAA,EAAA,KAAC,MAAD,CACE,UAAU,4EACV,cAAY,sCAEZ,EAAA,EAAA,KAAC,MAAD,CACE,IAAK,EACL,IAAK,EACL,UAAU,sCACX,CAAA,CACE,CAAA,EAIL,IAAS,OAUT,EAAA,EAAA,KAAC,SAAD,CACE,MAAO,EACP,IAAK,EACL,QAAQ,oBACR,cAAY,6BACZ,UAAU,wBACX,CAAA,EAID,IAAS,UACJ,EAAA,EAAA,KAAC,EAAD,CAA6B,MAAO,CAAA,EAIzC,IAAa,aAAe,EAAe,IAAI,EAAa,CAAI,CAAC,GASjE,EAAA,EAAA,KAAC,SAAD,CACE,MAAO,EACP,IAAK,EACL,QAAQ,oBACR,cAAY,6BACZ,UAAU,wBACX,CAAA,EAID,IAAS,QAAU,EAAc,IAAI,EAAa,CAAI,CAAC,GAUvD,EAAA,EAAA,KAAC,MAAD,CACE,cAAY,+BACZ,UAAU,kGAEV,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,uYACb,EAAA,EAAA,KAAC,EAAA,iBAAD,CACE,QAAS,GAAQ,GACjB,gBAAA,GACA,gBAAA,EACD,CAAA,CACE,CAAA,CACF,CAAA,EAUL,IAAS,QAAU,IAAS,MAE5B,EAAA,EAAA,KAAC,EAAA,sBAAD,CACQ,OACA,OACN,SAAU,GAAY,IAAA,EACvB,CAAA,GAME,EAAA,EAAA,KAAC,EAAD,CAA6B,MAAO,CAAA,CAC7C"}
|
|
1
|
+
{"version":3,"file":"file-content-viewer.cjs","names":[],"sources":["../../../../src/components/features/files-tab/file-content-viewer.tsx"],"sourcesContent":["import { useTranslation } from \"react-i18next\";\n\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { useWorkspaceFileContent } from \"#/hooks/query/use-workspace-file-content\";\nimport {\n useWorkspaceMutationCounter,\n withWorkspaceCacheBuster,\n} from \"#/stores/use-workspace-mutation-counter\";\nimport { MarkdownRenderer } from \"#/components/features/markdown/markdown-renderer\";\nimport { isMarkdownFilePath } from \"#/utils/is-markdown-file-path\";\nimport { HighlightedSourceView } from \"./highlighted-source-view\";\nimport type { ViewMode } from \"./view-mode\";\n\ninterface FileContentViewerProps {\n path: string;\n viewMode: ViewMode;\n}\n\nconst HTML_LIKE_EXTS = new Set([\"html\", \"htm\", \"svg\"]);\n\n// Office/document formats we can't preview inline. The label doubles as the\n// allow-list (a present entry => Office doc) and feeds a clear, format-named\n// \"no preview\" message instead of the generic binary fallback.\nconst OFFICE_DOCUMENT_LABELS: Record<string, string> = {\n pptx: \"PowerPoint\",\n ppt: \"PowerPoint\",\n docx: \"Word\",\n doc: \"Word\",\n xlsx: \"Excel\",\n xls: \"Excel\",\n};\n\nfunction getExtension(path: string): string {\n const idx = path.lastIndexOf(\".\");\n return idx === -1 ? \"\" : path.slice(idx + 1).toLowerCase();\n}\n\n/**\n * Fallback shown when a file's bytes aren't previewable. Office documents\n * (.pptx / .docx / .xlsx …) get a clear, format-named message; every other\n * binary keeps the generic \"binary file\" string so the pane is never blank.\n */\nfunction UnpreviewableFallback({ path }: { path: string }) {\n const { t } = useTranslation(\"openhands\");\n const documentLabel = OFFICE_DOCUMENT_LABELS[getExtension(path)];\n return (\n <div\n className=\"flex h-full w-full items-center justify-center text-sm text-[var(--oh-muted)]\"\n data-testid={\n documentLabel\n ? \"file-content-viewer-unsupported-document\"\n : \"file-content-viewer-binary-fallback\"\n }\n >\n {documentLabel\n ? t(I18nKey.FILES$UNSUPPORTED_DOCUMENT, { type: documentLabel })\n : t(I18nKey.FILES$BINARY_FALLBACK)}\n </div>\n );\n}\n\n/**\n * Renders the contents of a single workspace file. In `rich` mode we point\n * an iframe / <img> straight at the agent server's static workspace\n * fileserver for HTML / SVG / images / PDFs, so relative asset references\n * load naturally. In `plain` mode we always show the raw bytes as text (or\n * a fallback message for binaries).\n */\nexport function FileContentViewer({ path, viewMode }: FileContentViewerProps) {\n const { t } = useTranslation(\"openhands\");\n const query = useWorkspaceFileContent(path);\n // Subscribe to the workspace mutation counter so the iframe / <img> src\n // changes after every agent-side edit, forcing a fresh fetch even when\n // the *path* hasn't moved (e.g. agent rewrote `style.css` referenced by\n // the currently-displayed `index.html`).\n const mutationCounter = useWorkspaceMutationCounter((state) => state.count);\n\n if (query.isLoading) {\n return (\n <div className=\"flex h-full w-full items-center justify-center text-sm text-[var(--oh-muted)]\">\n {t(I18nKey.FILES$LOADING_FILES)}\n </div>\n );\n }\n\n if (query.isError || !query.data) {\n // Show a load-error message rather than the binary-fallback string —\n // these are completely different failure modes (couldn't even fetch\n // the file vs. fetched fine but the bytes aren't previewable) and\n // mixing them up hides real backend failures behind a misleading\n // \"Binary file\" label. Prefer the underlying error's own message when\n // we have one; fall back to the generic translated string otherwise.\n return (\n <div\n className=\"flex h-full w-full items-center justify-center text-sm text-[var(--oh-muted)]\"\n data-testid=\"file-content-viewer-error\"\n >\n {(query.error as Error | undefined)?.message ??\n t(I18nKey.FILES$LOAD_ERROR)}\n </div>\n );\n }\n\n const { kind, text, staticUrl, mimeType } = query.data;\n const bustedStaticUrl = withWorkspaceCacheBuster(staticUrl, mutationCounter);\n\n // ----- Plain mode: raw source bytes, syntax-highlighted when we can\n // recognize the grammar (falls through to a `<pre>` otherwise). This\n // includes \"plain\" view of markdown / HTML, where the point is to see\n // the markup behind the rich preview.\n if (viewMode === \"plain\") {\n if (kind === \"text\" && text !== null) {\n return (\n <HighlightedSourceView\n path={path}\n text={text}\n mimeType={mimeType ?? undefined}\n />\n );\n }\n return <UnpreviewableFallback path={path} />;\n }\n\n // ----- Rich mode: render HTML, markdown, images, PDFs from staticUrl. ----\n if (kind === \"image\") {\n return (\n <div\n className=\"flex h-full w-full items-center justify-center bg-[var(--oh-surface)] p-4\"\n data-testid=\"file-content-viewer-image\"\n >\n <img\n src={bustedStaticUrl}\n alt={path}\n className=\"max-h-full max-w-full object-contain\"\n />\n </div>\n );\n }\n\n if (kind === \"pdf\") {\n // PDFs can carry embedded JavaScript (AcroForm, OpenAction…). Even\n // though `staticUrl` lives on the agent server origin, the PDF\n // viewer's scripting capability isn't worth the risk for a file\n // preview, so we sandbox the iframe. `allow-same-origin` lets the\n // browser's built-in PDF viewer load the underlying bytes without\n // tripping cross-origin restrictions; we omit `allow-scripts`\n // because no PDF preview we care about needs to run JS in the\n // parent's origin.\n return (\n <iframe\n title={path}\n src={bustedStaticUrl}\n sandbox=\"allow-same-origin\"\n data-testid=\"file-content-viewer-iframe\"\n className=\"h-full w-full bg-white\"\n />\n );\n }\n\n if (kind === \"binary\") {\n return <UnpreviewableFallback path={path} />;\n }\n\n // Text-like content.\n if (mimeType === \"text/html\" || HTML_LIKE_EXTS.has(getExtension(path))) {\n // Sandbox the preview iframe: `allow-same-origin` keeps the frame on\n // the workspace fileserver's origin so relative `<link href=\"…\">`,\n // `<img src=\"…\">`, etc. continue to resolve, while the absence of\n // `allow-scripts` means any `<script>` (or `onerror=…`, inline event\n // handler, …) inside the previewed file is inert. This is exactly\n // the safe-preview posture we want — users can look at their HTML\n // without it executing in the canvas's context.\n return (\n <iframe\n title={path}\n src={bustedStaticUrl}\n sandbox=\"allow-same-origin\"\n data-testid=\"file-content-viewer-iframe\"\n className=\"h-full w-full bg-white\"\n />\n );\n }\n\n if (kind === \"text\" && isMarkdownFilePath(path)) {\n // Match the right-pane chrome color so the rich-rendered markdown\n // blends with the surrounding files tab instead of painting a stark\n // white card. `--oh-scroll-fade-from` keeps wide-table edge fades on\n // the same surface (otherwise they default to `--oh-color-base`).\n // We use `prose-invert` (typography plugin's dark-theme variant) and\n // then layer arbitrary CSS-variable overrides on top to pin body /\n // bold / quote text to pure white — the user specifically asked for\n // every text element (not just headings) to read as white. The custom\n // heading components in `markdown/headings.tsx` already hard-code\n // `text-white`, so headers stay white through this change.\n return (\n <div\n data-testid=\"file-content-viewer-markdown\"\n className=\"h-full w-full overflow-auto bg-[var(--oh-surface)] text-white custom-scrollbar-always [--oh-scroll-fade-from:var(--oh-surface)]\"\n >\n <div className=\"prose prose-sm prose-invert max-w-none p-6 [--tw-prose-body:#fff] [--tw-prose-bold:#fff] [--tw-prose-headings:#fff] [--tw-prose-lead:#fff] [--tw-prose-counters:#fff] [--tw-prose-quotes:#fff] [--tw-prose-quote-borders:var(--oh-border-subtle)] [--tw-prose-bullets:var(--oh-muted)] [--tw-prose-hr:var(--oh-border-subtle)] [--tw-prose-captions:var(--oh-muted)] [--tw-prose-kbd:#fff]\">\n <MarkdownRenderer\n content={text ?? \"\"}\n includeStandard\n includeHeadings\n />\n </div>\n </div>\n );\n }\n\n // Rich mode for actual source code (.ts, .py, .yaml, .css, …): there\n // is no other \"rich\" rendering to fall back to, so highlighted source\n // IS the rich view. Identical to the plain-mode treatment — keeping\n // both branches reuse `HighlightedSourceView` means the toggle has the\n // same visual identity for source files in both modes (which is the\n // honest answer: source IS rendered code).\n if (kind === \"text\" && text !== null) {\n return (\n <HighlightedSourceView\n path={path}\n text={text}\n mimeType={mimeType ?? undefined}\n />\n );\n }\n\n // Truly unknown / empty payload — show a fallback so the pane is never\n // blank.\n return <UnpreviewableFallback path={path} />;\n}\n"],"mappings":"oeAkBA,IAAM,EAAiB,IAAI,IAAI,CAAC,OAAQ,MAAO,KAAK,CAAC,EAK/C,EAAiD,CACrD,KAAM,aACN,IAAK,aACL,KAAM,OACN,IAAK,OACL,KAAM,QACN,IAAK,OACP,EAEA,SAAS,EAAa,EAAsB,CAC1C,IAAM,EAAM,EAAK,YAAY,GAAG,EAChC,OAAO,IAAQ,GAAK,GAAK,EAAK,MAAM,EAAM,CAAC,EAAE,YAAY,CAC3D,CAOA,SAAS,EAAsB,CAAE,QAA0B,CACzD,GAAM,CAAE,KAAM,EAAA,eAAe,WAAW,EAClC,EAAgB,EAAuB,EAAa,CAAI,GAC9D,OACE,EAAA,EAAA,KAAC,MAAD,CACE,UAAU,gFACV,cACE,EACI,2CACA,+CAGL,EACG,EAAE,EAAA,QAAQ,2BAA4B,CAAE,KAAM,CAAc,CAAC,EAC7D,EAAE,EAAA,QAAQ,qBAAqB,CAChC,CAAA,CAET,CASA,SAAgB,EAAkB,CAAE,OAAM,YAAoC,CAC5E,GAAM,CAAE,KAAM,EAAA,eAAe,WAAW,EAClC,EAAQ,EAAA,wBAAwB,CAAI,EAKpC,EAAkB,EAAA,4BAA6B,GAAU,EAAM,KAAK,EAE1E,GAAI,EAAM,UACR,OACE,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,yFACZ,EAAE,EAAA,QAAQ,mBAAmB,CAC3B,CAAA,EAIT,GAAI,EAAM,SAAW,CAAC,EAAM,KAO1B,OACE,EAAA,EAAA,KAAC,MAAD,CACE,UAAU,gFACV,cAAY,qCAEV,EAAM,OAA6B,SACnC,EAAE,EAAA,QAAQ,gBAAgB,CACzB,CAAA,EAIT,GAAM,CAAE,OAAM,OAAM,YAAW,YAAa,EAAM,KAC5C,EAAkB,EAAA,yBAAyB,EAAW,CAAe,EA4H3E,OAtHI,IAAa,QACX,IAAS,QAAU,IAAS,MAE5B,EAAA,EAAA,KAAC,EAAA,sBAAD,CACQ,OACA,OACN,SAAU,GAAY,IAAA,EACvB,CAAA,GAGE,EAAA,EAAA,KAAC,EAAD,CAA6B,MAAO,CAAA,EAIzC,IAAS,SAET,EAAA,EAAA,KAAC,MAAD,CACE,UAAU,4EACV,cAAY,sCAEZ,EAAA,EAAA,KAAC,MAAD,CACE,IAAK,EACL,IAAK,EACL,UAAU,sCACX,CAAA,CACE,CAAA,EAIL,IAAS,OAUT,EAAA,EAAA,KAAC,SAAD,CACE,MAAO,EACP,IAAK,EACL,QAAQ,oBACR,cAAY,6BACZ,UAAU,wBACX,CAAA,EAID,IAAS,UACJ,EAAA,EAAA,KAAC,EAAD,CAA6B,MAAO,CAAA,EAIzC,IAAa,aAAe,EAAe,IAAI,EAAa,CAAI,CAAC,GASjE,EAAA,EAAA,KAAC,SAAD,CACE,MAAO,EACP,IAAK,EACL,QAAQ,oBACR,cAAY,6BACZ,UAAU,wBACX,CAAA,EAID,IAAS,QAAU,EAAA,mBAAmB,CAAI,GAY1C,EAAA,EAAA,KAAC,MAAD,CACE,cAAY,+BACZ,UAAU,4IAEV,EAAA,EAAA,KAAC,MAAD,CAAK,UAAU,uYACb,EAAA,EAAA,KAAC,EAAA,iBAAD,CACE,QAAS,GAAQ,GACjB,gBAAA,GACA,gBAAA,EACD,CAAA,CACE,CAAA,CACF,CAAA,EAUL,IAAS,QAAU,IAAS,MAE5B,EAAA,EAAA,KAAC,EAAA,sBAAD,CACQ,OACA,OACN,SAAU,GAAY,IAAA,EACvB,CAAA,GAME,EAAA,EAAA,KAAC,EAAD,CAA6B,MAAO,CAAA,CAC7C"}
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
import { useTranslation as e } from "../../../node_modules/react-i18next/dist/es/useTranslation.js";
|
|
2
2
|
import { I18nKey as t } from "../../../i18n/declaration.js";
|
|
3
3
|
import { MarkdownRenderer as n } from "../markdown/markdown-renderer.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { isMarkdownFilePath as r } from "../../../utils/is-markdown-file-path.js";
|
|
5
|
+
import { useWorkspaceMutationCounter as i, withWorkspaceCacheBuster as a } from "../../../stores/use-workspace-mutation-counter.js";
|
|
6
|
+
import { useWorkspaceFileContent as o } from "../../../hooks/query/use-workspace-file-content.js";
|
|
7
|
+
import { HighlightedSourceView as s } from "./highlighted-source-view.js";
|
|
8
|
+
import { jsx as c } from "react/jsx-runtime";
|
|
8
9
|
//#region src/components/features/files-tab/file-content-viewer.tsx
|
|
9
|
-
var
|
|
10
|
+
var l = new Set([
|
|
10
11
|
"html",
|
|
11
12
|
"htm",
|
|
12
13
|
"svg"
|
|
13
|
-
]), l = new Set([
|
|
14
|
-
"md",
|
|
15
|
-
"markdown",
|
|
16
|
-
"mdx"
|
|
17
14
|
]), u = {
|
|
18
15
|
pptx: "PowerPoint",
|
|
19
16
|
ppt: "PowerPoint",
|
|
@@ -28,64 +25,64 @@ function d(e) {
|
|
|
28
25
|
}
|
|
29
26
|
function f({ path: n }) {
|
|
30
27
|
let { t: r } = e("openhands"), i = u[d(n)];
|
|
31
|
-
return /* @__PURE__ */
|
|
28
|
+
return /* @__PURE__ */ c("div", {
|
|
32
29
|
className: "flex h-full w-full items-center justify-center text-sm text-[var(--oh-muted)]",
|
|
33
30
|
"data-testid": i ? "file-content-viewer-unsupported-document" : "file-content-viewer-binary-fallback",
|
|
34
31
|
children: i ? r(t.FILES$UNSUPPORTED_DOCUMENT, { type: i }) : r(t.FILES$BINARY_FALLBACK)
|
|
35
32
|
});
|
|
36
33
|
}
|
|
37
34
|
function p({ path: u, viewMode: p }) {
|
|
38
|
-
let { t: m } = e("openhands"), h =
|
|
39
|
-
if (h.isLoading) return /* @__PURE__ */
|
|
35
|
+
let { t: m } = e("openhands"), h = o(u), g = i((e) => e.count);
|
|
36
|
+
if (h.isLoading) return /* @__PURE__ */ c("div", {
|
|
40
37
|
className: "flex h-full w-full items-center justify-center text-sm text-[var(--oh-muted)]",
|
|
41
38
|
children: m(t.FILES$LOADING_FILES)
|
|
42
39
|
});
|
|
43
|
-
if (h.isError || !h.data) return /* @__PURE__ */
|
|
40
|
+
if (h.isError || !h.data) return /* @__PURE__ */ c("div", {
|
|
44
41
|
className: "flex h-full w-full items-center justify-center text-sm text-[var(--oh-muted)]",
|
|
45
42
|
"data-testid": "file-content-viewer-error",
|
|
46
43
|
children: h.error?.message ?? m(t.FILES$LOAD_ERROR)
|
|
47
44
|
});
|
|
48
|
-
let { kind: _, text: v, staticUrl: y, mimeType: b } = h.data, x =
|
|
49
|
-
return p === "plain" ? _ === "text" && v !== null ? /* @__PURE__ */ s
|
|
45
|
+
let { kind: _, text: v, staticUrl: y, mimeType: b } = h.data, x = a(y, g);
|
|
46
|
+
return p === "plain" ? _ === "text" && v !== null ? /* @__PURE__ */ c(s, {
|
|
50
47
|
path: u,
|
|
51
48
|
text: v,
|
|
52
49
|
mimeType: b ?? void 0
|
|
53
|
-
}) : /* @__PURE__ */
|
|
50
|
+
}) : /* @__PURE__ */ c(f, { path: u }) : _ === "image" ? /* @__PURE__ */ c("div", {
|
|
54
51
|
className: "flex h-full w-full items-center justify-center bg-[var(--oh-surface)] p-4",
|
|
55
52
|
"data-testid": "file-content-viewer-image",
|
|
56
|
-
children: /* @__PURE__ */
|
|
53
|
+
children: /* @__PURE__ */ c("img", {
|
|
57
54
|
src: x,
|
|
58
55
|
alt: u,
|
|
59
56
|
className: "max-h-full max-w-full object-contain"
|
|
60
57
|
})
|
|
61
|
-
}) : _ === "pdf" ? /* @__PURE__ */
|
|
58
|
+
}) : _ === "pdf" ? /* @__PURE__ */ c("iframe", {
|
|
62
59
|
title: u,
|
|
63
60
|
src: x,
|
|
64
61
|
sandbox: "allow-same-origin",
|
|
65
62
|
"data-testid": "file-content-viewer-iframe",
|
|
66
63
|
className: "h-full w-full bg-white"
|
|
67
|
-
}) : _ === "binary" ? /* @__PURE__ */
|
|
64
|
+
}) : _ === "binary" ? /* @__PURE__ */ c(f, { path: u }) : b === "text/html" || l.has(d(u)) ? /* @__PURE__ */ c("iframe", {
|
|
68
65
|
title: u,
|
|
69
66
|
src: x,
|
|
70
67
|
sandbox: "allow-same-origin",
|
|
71
68
|
"data-testid": "file-content-viewer-iframe",
|
|
72
69
|
className: "h-full w-full bg-white"
|
|
73
|
-
}) : _ === "text" &&
|
|
70
|
+
}) : _ === "text" && r(u) ? /* @__PURE__ */ c("div", {
|
|
74
71
|
"data-testid": "file-content-viewer-markdown",
|
|
75
|
-
className: "h-full w-full overflow-auto bg-[var(--oh-surface)] text-white custom-scrollbar-always",
|
|
76
|
-
children: /* @__PURE__ */
|
|
72
|
+
className: "h-full w-full overflow-auto bg-[var(--oh-surface)] text-white custom-scrollbar-always [--oh-scroll-fade-from:var(--oh-surface)]",
|
|
73
|
+
children: /* @__PURE__ */ c("div", {
|
|
77
74
|
className: "prose prose-sm prose-invert max-w-none p-6 [--tw-prose-body:#fff] [--tw-prose-bold:#fff] [--tw-prose-headings:#fff] [--tw-prose-lead:#fff] [--tw-prose-counters:#fff] [--tw-prose-quotes:#fff] [--tw-prose-quote-borders:var(--oh-border-subtle)] [--tw-prose-bullets:var(--oh-muted)] [--tw-prose-hr:var(--oh-border-subtle)] [--tw-prose-captions:var(--oh-muted)] [--tw-prose-kbd:#fff]",
|
|
78
|
-
children: /* @__PURE__ */
|
|
75
|
+
children: /* @__PURE__ */ c(n, {
|
|
79
76
|
content: v ?? "",
|
|
80
77
|
includeStandard: !0,
|
|
81
78
|
includeHeadings: !0
|
|
82
79
|
})
|
|
83
80
|
})
|
|
84
|
-
}) : _ === "text" && v !== null ? /* @__PURE__ */ s
|
|
81
|
+
}) : _ === "text" && v !== null ? /* @__PURE__ */ c(s, {
|
|
85
82
|
path: u,
|
|
86
83
|
text: v,
|
|
87
84
|
mimeType: b ?? void 0
|
|
88
|
-
}) : /* @__PURE__ */
|
|
85
|
+
}) : /* @__PURE__ */ c(f, { path: u });
|
|
89
86
|
}
|
|
90
87
|
//#endregion
|
|
91
88
|
export { p as FileContentViewer };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-content-viewer.js","names":[],"sources":["../../../../src/components/features/files-tab/file-content-viewer.tsx"],"sourcesContent":["import { useTranslation } from \"react-i18next\";\n\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { useWorkspaceFileContent } from \"#/hooks/query/use-workspace-file-content\";\nimport {\n useWorkspaceMutationCounter,\n withWorkspaceCacheBuster,\n} from \"#/stores/use-workspace-mutation-counter\";\nimport { MarkdownRenderer } from \"#/components/features/markdown/markdown-renderer\";\nimport { HighlightedSourceView } from \"./highlighted-source-view\";\nimport type { ViewMode } from \"./view-mode\";\n\ninterface FileContentViewerProps {\n path: string;\n viewMode: ViewMode;\n}\n\nconst HTML_LIKE_EXTS = new Set([\"html\", \"htm\", \"svg\"]);\nconst MARKDOWN_EXTS = new Set([\"md\", \"markdown\", \"mdx\"]);\n\n// Office/document formats we can't preview inline. The label doubles as the\n// allow-list (a present entry => Office doc) and feeds a clear, format-named\n// \"no preview\" message instead of the generic binary fallback.\nconst OFFICE_DOCUMENT_LABELS: Record<string, string> = {\n pptx: \"PowerPoint\",\n ppt: \"PowerPoint\",\n docx: \"Word\",\n doc: \"Word\",\n xlsx: \"Excel\",\n xls: \"Excel\",\n};\n\nfunction getExtension(path: string): string {\n const idx = path.lastIndexOf(\".\");\n return idx === -1 ? \"\" : path.slice(idx + 1).toLowerCase();\n}\n\n/**\n * Fallback shown when a file's bytes aren't previewable. Office documents\n * (.pptx / .docx / .xlsx …) get a clear, format-named message; every other\n * binary keeps the generic \"binary file\" string so the pane is never blank.\n */\nfunction UnpreviewableFallback({ path }: { path: string }) {\n const { t } = useTranslation(\"openhands\");\n const documentLabel = OFFICE_DOCUMENT_LABELS[getExtension(path)];\n return (\n <div\n className=\"flex h-full w-full items-center justify-center text-sm text-[var(--oh-muted)]\"\n data-testid={\n documentLabel\n ? \"file-content-viewer-unsupported-document\"\n : \"file-content-viewer-binary-fallback\"\n }\n >\n {documentLabel\n ? t(I18nKey.FILES$UNSUPPORTED_DOCUMENT, { type: documentLabel })\n : t(I18nKey.FILES$BINARY_FALLBACK)}\n </div>\n );\n}\n\n/**\n * Renders the contents of a single workspace file. In `rich` mode we point\n * an iframe / <img> straight at the agent server's static workspace\n * fileserver for HTML / SVG / images / PDFs, so relative asset references\n * load naturally. In `plain` mode we always show the raw bytes as text (or\n * a fallback message for binaries).\n */\nexport function FileContentViewer({ path, viewMode }: FileContentViewerProps) {\n const { t } = useTranslation(\"openhands\");\n const query = useWorkspaceFileContent(path);\n // Subscribe to the workspace mutation counter so the iframe / <img> src\n // changes after every agent-side edit, forcing a fresh fetch even when\n // the *path* hasn't moved (e.g. agent rewrote `style.css` referenced by\n // the currently-displayed `index.html`).\n const mutationCounter = useWorkspaceMutationCounter((state) => state.count);\n\n if (query.isLoading) {\n return (\n <div className=\"flex h-full w-full items-center justify-center text-sm text-[var(--oh-muted)]\">\n {t(I18nKey.FILES$LOADING_FILES)}\n </div>\n );\n }\n\n if (query.isError || !query.data) {\n // Show a load-error message rather than the binary-fallback string —\n // these are completely different failure modes (couldn't even fetch\n // the file vs. fetched fine but the bytes aren't previewable) and\n // mixing them up hides real backend failures behind a misleading\n // \"Binary file\" label. Prefer the underlying error's own message when\n // we have one; fall back to the generic translated string otherwise.\n return (\n <div\n className=\"flex h-full w-full items-center justify-center text-sm text-[var(--oh-muted)]\"\n data-testid=\"file-content-viewer-error\"\n >\n {(query.error as Error | undefined)?.message ??\n t(I18nKey.FILES$LOAD_ERROR)}\n </div>\n );\n }\n\n const { kind, text, staticUrl, mimeType } = query.data;\n const bustedStaticUrl = withWorkspaceCacheBuster(staticUrl, mutationCounter);\n\n // ----- Plain mode: raw source bytes, syntax-highlighted when we can\n // recognize the grammar (falls through to a `<pre>` otherwise). This\n // includes \"plain\" view of markdown / HTML, where the point is to see\n // the markup behind the rich preview.\n if (viewMode === \"plain\") {\n if (kind === \"text\" && text !== null) {\n return (\n <HighlightedSourceView\n path={path}\n text={text}\n mimeType={mimeType ?? undefined}\n />\n );\n }\n return <UnpreviewableFallback path={path} />;\n }\n\n // ----- Rich mode: render HTML, markdown, images, PDFs from staticUrl. ----\n if (kind === \"image\") {\n return (\n <div\n className=\"flex h-full w-full items-center justify-center bg-[var(--oh-surface)] p-4\"\n data-testid=\"file-content-viewer-image\"\n >\n <img\n src={bustedStaticUrl}\n alt={path}\n className=\"max-h-full max-w-full object-contain\"\n />\n </div>\n );\n }\n\n if (kind === \"pdf\") {\n // PDFs can carry embedded JavaScript (AcroForm, OpenAction…). Even\n // though `staticUrl` lives on the agent server origin, the PDF\n // viewer's scripting capability isn't worth the risk for a file\n // preview, so we sandbox the iframe. `allow-same-origin` lets the\n // browser's built-in PDF viewer load the underlying bytes without\n // tripping cross-origin restrictions; we omit `allow-scripts`\n // because no PDF preview we care about needs to run JS in the\n // parent's origin.\n return (\n <iframe\n title={path}\n src={bustedStaticUrl}\n sandbox=\"allow-same-origin\"\n data-testid=\"file-content-viewer-iframe\"\n className=\"h-full w-full bg-white\"\n />\n );\n }\n\n if (kind === \"binary\") {\n return <UnpreviewableFallback path={path} />;\n }\n\n // Text-like content.\n if (mimeType === \"text/html\" || HTML_LIKE_EXTS.has(getExtension(path))) {\n // Sandbox the preview iframe: `allow-same-origin` keeps the frame on\n // the workspace fileserver's origin so relative `<link href=\"…\">`,\n // `<img src=\"…\">`, etc. continue to resolve, while the absence of\n // `allow-scripts` means any `<script>` (or `onerror=…`, inline event\n // handler, …) inside the previewed file is inert. This is exactly\n // the safe-preview posture we want — users can look at their HTML\n // without it executing in the canvas's context.\n return (\n <iframe\n title={path}\n src={bustedStaticUrl}\n sandbox=\"allow-same-origin\"\n data-testid=\"file-content-viewer-iframe\"\n className=\"h-full w-full bg-white\"\n />\n );\n }\n\n if (kind === \"text\" && MARKDOWN_EXTS.has(getExtension(path))) {\n // Match the right-pane chrome color so the rich-rendered markdown\n // blends with the surrounding files tab instead of painting a stark\n // white card. We use `prose-invert` (typography plugin's dark-theme\n // variant) and then layer arbitrary CSS-variable overrides on top to\n // pin body / bold / quote text to pure white — the user specifically\n // asked for every text element (not just headings) to read as white.\n // The custom heading components in `markdown/headings.tsx` already\n // hard-code `text-white`, so headers stay white through this change.\n return (\n <div\n data-testid=\"file-content-viewer-markdown\"\n className=\"h-full w-full overflow-auto bg-[var(--oh-surface)] text-white custom-scrollbar-always\"\n >\n <div className=\"prose prose-sm prose-invert max-w-none p-6 [--tw-prose-body:#fff] [--tw-prose-bold:#fff] [--tw-prose-headings:#fff] [--tw-prose-lead:#fff] [--tw-prose-counters:#fff] [--tw-prose-quotes:#fff] [--tw-prose-quote-borders:var(--oh-border-subtle)] [--tw-prose-bullets:var(--oh-muted)] [--tw-prose-hr:var(--oh-border-subtle)] [--tw-prose-captions:var(--oh-muted)] [--tw-prose-kbd:#fff]\">\n <MarkdownRenderer\n content={text ?? \"\"}\n includeStandard\n includeHeadings\n />\n </div>\n </div>\n );\n }\n\n // Rich mode for actual source code (.ts, .py, .yaml, .css, …): there\n // is no other \"rich\" rendering to fall back to, so highlighted source\n // IS the rich view. Identical to the plain-mode treatment — keeping\n // both branches reuse `HighlightedSourceView` means the toggle has the\n // same visual identity for source files in both modes (which is the\n // honest answer: source IS rendered code).\n if (kind === \"text\" && text !== null) {\n return (\n <HighlightedSourceView\n path={path}\n text={text}\n mimeType={mimeType ?? undefined}\n />\n );\n }\n\n // Truly unknown / empty payload — show a fallback so the pane is never\n // blank.\n return <UnpreviewableFallback path={path} />;\n}\n"],"mappings":";;;;;;;;AAiBA,IAAM,IAAiB,IAAI,IAAI;CAAC;CAAQ;CAAO;AAAK,CAAC,GAC/C,IAAgB,IAAI,IAAI;CAAC;CAAM;CAAY;AAAK,CAAC,GAKjD,IAAiD;CACrD,MAAM;CACN,KAAK;CACL,MAAM;CACN,KAAK;CACL,MAAM;CACN,KAAK;AACP;AAEA,SAAS,EAAa,GAAsB;CAC1C,IAAM,IAAM,EAAK,YAAY,GAAG;CAChC,OAAO,MAAQ,KAAK,KAAK,EAAK,MAAM,IAAM,CAAC,EAAE,YAAY;AAC3D;AAOA,SAAS,EAAsB,EAAE,WAA0B;CACzD,IAAM,EAAE,SAAM,EAAe,WAAW,GAClC,IAAgB,EAAuB,EAAa,CAAI;CAC9D,OACE,kBAAC,OAAD;EACE,WAAU;EACV,eACE,IACI,6CACA;YAGL,IACG,EAAE,EAAQ,4BAA4B,EAAE,MAAM,EAAc,CAAC,IAC7D,EAAE,EAAQ,qBAAqB;CAChC,CAAA;AAET;AASA,SAAgB,EAAkB,EAAE,SAAM,eAAoC;CAC5E,IAAM,EAAE,SAAM,EAAe,WAAW,GAClC,IAAQ,EAAwB,CAAI,GAKpC,IAAkB,GAA6B,MAAU,EAAM,KAAK;CAE1E,IAAI,EAAM,WACR,OACE,kBAAC,OAAD;EAAK,WAAU;YACZ,EAAE,EAAQ,mBAAmB;CAC3B,CAAA;CAIT,IAAI,EAAM,WAAW,CAAC,EAAM,MAO1B,OACE,kBAAC,OAAD;EACE,WAAU;EACV,eAAY;YAEV,EAAM,OAA6B,WACnC,EAAE,EAAQ,gBAAgB;CACzB,CAAA;CAIT,IAAM,EAAE,SAAM,SAAM,cAAW,gBAAa,EAAM,MAC5C,IAAkB,EAAyB,GAAW,CAAe;CA0H3E,OApHI,MAAa,UACX,MAAS,UAAU,MAAS,OAE5B,kBAAC,GAAD;EACQ;EACA;EACN,UAAU,KAAY,KAAA;CACvB,CAAA,IAGE,kBAAC,GAAD,EAA6B,QAAO,CAAA,IAIzC,MAAS,UAET,kBAAC,OAAD;EACE,WAAU;EACV,eAAY;YAEZ,kBAAC,OAAD;GACE,KAAK;GACL,KAAK;GACL,WAAU;EACX,CAAA;CACE,CAAA,IAIL,MAAS,QAUT,kBAAC,UAAD;EACE,OAAO;EACP,KAAK;EACL,SAAQ;EACR,eAAY;EACZ,WAAU;CACX,CAAA,IAID,MAAS,WACJ,kBAAC,GAAD,EAA6B,QAAO,CAAA,IAIzC,MAAa,eAAe,EAAe,IAAI,EAAa,CAAI,CAAC,IASjE,kBAAC,UAAD;EACE,OAAO;EACP,KAAK;EACL,SAAQ;EACR,eAAY;EACZ,WAAU;CACX,CAAA,IAID,MAAS,UAAU,EAAc,IAAI,EAAa,CAAI,CAAC,IAUvD,kBAAC,OAAD;EACE,eAAY;EACZ,WAAU;YAEV,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD;IACE,SAAS,KAAQ;IACjB,iBAAA;IACA,iBAAA;GACD,CAAA;EACE,CAAA;CACF,CAAA,IAUL,MAAS,UAAU,MAAS,OAE5B,kBAAC,GAAD;EACQ;EACA;EACN,UAAU,KAAY,KAAA;CACvB,CAAA,IAME,kBAAC,GAAD,EAA6B,QAAO,CAAA;AAC7C"}
|
|
1
|
+
{"version":3,"file":"file-content-viewer.js","names":[],"sources":["../../../../src/components/features/files-tab/file-content-viewer.tsx"],"sourcesContent":["import { useTranslation } from \"react-i18next\";\n\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { useWorkspaceFileContent } from \"#/hooks/query/use-workspace-file-content\";\nimport {\n useWorkspaceMutationCounter,\n withWorkspaceCacheBuster,\n} from \"#/stores/use-workspace-mutation-counter\";\nimport { MarkdownRenderer } from \"#/components/features/markdown/markdown-renderer\";\nimport { isMarkdownFilePath } from \"#/utils/is-markdown-file-path\";\nimport { HighlightedSourceView } from \"./highlighted-source-view\";\nimport type { ViewMode } from \"./view-mode\";\n\ninterface FileContentViewerProps {\n path: string;\n viewMode: ViewMode;\n}\n\nconst HTML_LIKE_EXTS = new Set([\"html\", \"htm\", \"svg\"]);\n\n// Office/document formats we can't preview inline. The label doubles as the\n// allow-list (a present entry => Office doc) and feeds a clear, format-named\n// \"no preview\" message instead of the generic binary fallback.\nconst OFFICE_DOCUMENT_LABELS: Record<string, string> = {\n pptx: \"PowerPoint\",\n ppt: \"PowerPoint\",\n docx: \"Word\",\n doc: \"Word\",\n xlsx: \"Excel\",\n xls: \"Excel\",\n};\n\nfunction getExtension(path: string): string {\n const idx = path.lastIndexOf(\".\");\n return idx === -1 ? \"\" : path.slice(idx + 1).toLowerCase();\n}\n\n/**\n * Fallback shown when a file's bytes aren't previewable. Office documents\n * (.pptx / .docx / .xlsx …) get a clear, format-named message; every other\n * binary keeps the generic \"binary file\" string so the pane is never blank.\n */\nfunction UnpreviewableFallback({ path }: { path: string }) {\n const { t } = useTranslation(\"openhands\");\n const documentLabel = OFFICE_DOCUMENT_LABELS[getExtension(path)];\n return (\n <div\n className=\"flex h-full w-full items-center justify-center text-sm text-[var(--oh-muted)]\"\n data-testid={\n documentLabel\n ? \"file-content-viewer-unsupported-document\"\n : \"file-content-viewer-binary-fallback\"\n }\n >\n {documentLabel\n ? t(I18nKey.FILES$UNSUPPORTED_DOCUMENT, { type: documentLabel })\n : t(I18nKey.FILES$BINARY_FALLBACK)}\n </div>\n );\n}\n\n/**\n * Renders the contents of a single workspace file. In `rich` mode we point\n * an iframe / <img> straight at the agent server's static workspace\n * fileserver for HTML / SVG / images / PDFs, so relative asset references\n * load naturally. In `plain` mode we always show the raw bytes as text (or\n * a fallback message for binaries).\n */\nexport function FileContentViewer({ path, viewMode }: FileContentViewerProps) {\n const { t } = useTranslation(\"openhands\");\n const query = useWorkspaceFileContent(path);\n // Subscribe to the workspace mutation counter so the iframe / <img> src\n // changes after every agent-side edit, forcing a fresh fetch even when\n // the *path* hasn't moved (e.g. agent rewrote `style.css` referenced by\n // the currently-displayed `index.html`).\n const mutationCounter = useWorkspaceMutationCounter((state) => state.count);\n\n if (query.isLoading) {\n return (\n <div className=\"flex h-full w-full items-center justify-center text-sm text-[var(--oh-muted)]\">\n {t(I18nKey.FILES$LOADING_FILES)}\n </div>\n );\n }\n\n if (query.isError || !query.data) {\n // Show a load-error message rather than the binary-fallback string —\n // these are completely different failure modes (couldn't even fetch\n // the file vs. fetched fine but the bytes aren't previewable) and\n // mixing them up hides real backend failures behind a misleading\n // \"Binary file\" label. Prefer the underlying error's own message when\n // we have one; fall back to the generic translated string otherwise.\n return (\n <div\n className=\"flex h-full w-full items-center justify-center text-sm text-[var(--oh-muted)]\"\n data-testid=\"file-content-viewer-error\"\n >\n {(query.error as Error | undefined)?.message ??\n t(I18nKey.FILES$LOAD_ERROR)}\n </div>\n );\n }\n\n const { kind, text, staticUrl, mimeType } = query.data;\n const bustedStaticUrl = withWorkspaceCacheBuster(staticUrl, mutationCounter);\n\n // ----- Plain mode: raw source bytes, syntax-highlighted when we can\n // recognize the grammar (falls through to a `<pre>` otherwise). This\n // includes \"plain\" view of markdown / HTML, where the point is to see\n // the markup behind the rich preview.\n if (viewMode === \"plain\") {\n if (kind === \"text\" && text !== null) {\n return (\n <HighlightedSourceView\n path={path}\n text={text}\n mimeType={mimeType ?? undefined}\n />\n );\n }\n return <UnpreviewableFallback path={path} />;\n }\n\n // ----- Rich mode: render HTML, markdown, images, PDFs from staticUrl. ----\n if (kind === \"image\") {\n return (\n <div\n className=\"flex h-full w-full items-center justify-center bg-[var(--oh-surface)] p-4\"\n data-testid=\"file-content-viewer-image\"\n >\n <img\n src={bustedStaticUrl}\n alt={path}\n className=\"max-h-full max-w-full object-contain\"\n />\n </div>\n );\n }\n\n if (kind === \"pdf\") {\n // PDFs can carry embedded JavaScript (AcroForm, OpenAction…). Even\n // though `staticUrl` lives on the agent server origin, the PDF\n // viewer's scripting capability isn't worth the risk for a file\n // preview, so we sandbox the iframe. `allow-same-origin` lets the\n // browser's built-in PDF viewer load the underlying bytes without\n // tripping cross-origin restrictions; we omit `allow-scripts`\n // because no PDF preview we care about needs to run JS in the\n // parent's origin.\n return (\n <iframe\n title={path}\n src={bustedStaticUrl}\n sandbox=\"allow-same-origin\"\n data-testid=\"file-content-viewer-iframe\"\n className=\"h-full w-full bg-white\"\n />\n );\n }\n\n if (kind === \"binary\") {\n return <UnpreviewableFallback path={path} />;\n }\n\n // Text-like content.\n if (mimeType === \"text/html\" || HTML_LIKE_EXTS.has(getExtension(path))) {\n // Sandbox the preview iframe: `allow-same-origin` keeps the frame on\n // the workspace fileserver's origin so relative `<link href=\"…\">`,\n // `<img src=\"…\">`, etc. continue to resolve, while the absence of\n // `allow-scripts` means any `<script>` (or `onerror=…`, inline event\n // handler, …) inside the previewed file is inert. This is exactly\n // the safe-preview posture we want — users can look at their HTML\n // without it executing in the canvas's context.\n return (\n <iframe\n title={path}\n src={bustedStaticUrl}\n sandbox=\"allow-same-origin\"\n data-testid=\"file-content-viewer-iframe\"\n className=\"h-full w-full bg-white\"\n />\n );\n }\n\n if (kind === \"text\" && isMarkdownFilePath(path)) {\n // Match the right-pane chrome color so the rich-rendered markdown\n // blends with the surrounding files tab instead of painting a stark\n // white card. `--oh-scroll-fade-from` keeps wide-table edge fades on\n // the same surface (otherwise they default to `--oh-color-base`).\n // We use `prose-invert` (typography plugin's dark-theme variant) and\n // then layer arbitrary CSS-variable overrides on top to pin body /\n // bold / quote text to pure white — the user specifically asked for\n // every text element (not just headings) to read as white. The custom\n // heading components in `markdown/headings.tsx` already hard-code\n // `text-white`, so headers stay white through this change.\n return (\n <div\n data-testid=\"file-content-viewer-markdown\"\n className=\"h-full w-full overflow-auto bg-[var(--oh-surface)] text-white custom-scrollbar-always [--oh-scroll-fade-from:var(--oh-surface)]\"\n >\n <div className=\"prose prose-sm prose-invert max-w-none p-6 [--tw-prose-body:#fff] [--tw-prose-bold:#fff] [--tw-prose-headings:#fff] [--tw-prose-lead:#fff] [--tw-prose-counters:#fff] [--tw-prose-quotes:#fff] [--tw-prose-quote-borders:var(--oh-border-subtle)] [--tw-prose-bullets:var(--oh-muted)] [--tw-prose-hr:var(--oh-border-subtle)] [--tw-prose-captions:var(--oh-muted)] [--tw-prose-kbd:#fff]\">\n <MarkdownRenderer\n content={text ?? \"\"}\n includeStandard\n includeHeadings\n />\n </div>\n </div>\n );\n }\n\n // Rich mode for actual source code (.ts, .py, .yaml, .css, …): there\n // is no other \"rich\" rendering to fall back to, so highlighted source\n // IS the rich view. Identical to the plain-mode treatment — keeping\n // both branches reuse `HighlightedSourceView` means the toggle has the\n // same visual identity for source files in both modes (which is the\n // honest answer: source IS rendered code).\n if (kind === \"text\" && text !== null) {\n return (\n <HighlightedSourceView\n path={path}\n text={text}\n mimeType={mimeType ?? undefined}\n />\n );\n }\n\n // Truly unknown / empty payload — show a fallback so the pane is never\n // blank.\n return <UnpreviewableFallback path={path} />;\n}\n"],"mappings":";;;;;;;;;AAkBA,IAAM,IAAiB,IAAI,IAAI;CAAC;CAAQ;CAAO;AAAK,CAAC,GAK/C,IAAiD;CACrD,MAAM;CACN,KAAK;CACL,MAAM;CACN,KAAK;CACL,MAAM;CACN,KAAK;AACP;AAEA,SAAS,EAAa,GAAsB;CAC1C,IAAM,IAAM,EAAK,YAAY,GAAG;CAChC,OAAO,MAAQ,KAAK,KAAK,EAAK,MAAM,IAAM,CAAC,EAAE,YAAY;AAC3D;AAOA,SAAS,EAAsB,EAAE,WAA0B;CACzD,IAAM,EAAE,SAAM,EAAe,WAAW,GAClC,IAAgB,EAAuB,EAAa,CAAI;CAC9D,OACE,kBAAC,OAAD;EACE,WAAU;EACV,eACE,IACI,6CACA;YAGL,IACG,EAAE,EAAQ,4BAA4B,EAAE,MAAM,EAAc,CAAC,IAC7D,EAAE,EAAQ,qBAAqB;CAChC,CAAA;AAET;AASA,SAAgB,EAAkB,EAAE,SAAM,eAAoC;CAC5E,IAAM,EAAE,SAAM,EAAe,WAAW,GAClC,IAAQ,EAAwB,CAAI,GAKpC,IAAkB,GAA6B,MAAU,EAAM,KAAK;CAE1E,IAAI,EAAM,WACR,OACE,kBAAC,OAAD;EAAK,WAAU;YACZ,EAAE,EAAQ,mBAAmB;CAC3B,CAAA;CAIT,IAAI,EAAM,WAAW,CAAC,EAAM,MAO1B,OACE,kBAAC,OAAD;EACE,WAAU;EACV,eAAY;YAEV,EAAM,OAA6B,WACnC,EAAE,EAAQ,gBAAgB;CACzB,CAAA;CAIT,IAAM,EAAE,SAAM,SAAM,cAAW,gBAAa,EAAM,MAC5C,IAAkB,EAAyB,GAAW,CAAe;CA4H3E,OAtHI,MAAa,UACX,MAAS,UAAU,MAAS,OAE5B,kBAAC,GAAD;EACQ;EACA;EACN,UAAU,KAAY,KAAA;CACvB,CAAA,IAGE,kBAAC,GAAD,EAA6B,QAAO,CAAA,IAIzC,MAAS,UAET,kBAAC,OAAD;EACE,WAAU;EACV,eAAY;YAEZ,kBAAC,OAAD;GACE,KAAK;GACL,KAAK;GACL,WAAU;EACX,CAAA;CACE,CAAA,IAIL,MAAS,QAUT,kBAAC,UAAD;EACE,OAAO;EACP,KAAK;EACL,SAAQ;EACR,eAAY;EACZ,WAAU;CACX,CAAA,IAID,MAAS,WACJ,kBAAC,GAAD,EAA6B,QAAO,CAAA,IAIzC,MAAa,eAAe,EAAe,IAAI,EAAa,CAAI,CAAC,IASjE,kBAAC,UAAD;EACE,OAAO;EACP,KAAK;EACL,SAAQ;EACR,eAAY;EACZ,WAAU;CACX,CAAA,IAID,MAAS,UAAU,EAAmB,CAAI,IAY1C,kBAAC,OAAD;EACE,eAAY;EACZ,WAAU;YAEV,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD;IACE,SAAS,KAAQ;IACjB,iBAAA;IACA,iBAAA;GACD,CAAA;EACE,CAAA;CACF,CAAA,IAUL,MAAS,UAAU,MAAS,OAE5B,kBAAC,GAAD;EACQ;EACA;EACN,UAAU,KAAY,KAAA;CACvB,CAAA,IAME,kBAAC,GAAD,EAA6B,QAAO,CAAA;AAC7C"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../../_virtual/_rolldown/runtime.cjs"),t=require("../../../utils/utils.cjs");let n=require("react");n=e.__toESM(n,1);let r=require("react/jsx-runtime");var i=1,a=`w-10`;function o(e){let{scrollLeft:t,scrollWidth:n,clientWidth:r}=e,a=n-r,o=a>i;return{left:o&&t>i,right:o&&t<a-i}}function s({children:e}){let i=n.default.useRef(null),[s,c]=n.default.useState({left:!1,right:!1}),l=n.default.useCallback(()=>{let e=i.current;e&&c(o(e))},[]);return n.default.useLayoutEffect(()=>{l();let e=i.current;if(!e)return;let t=new ResizeObserver(l);t.observe(e);let n=e.firstElementChild;return n&&t.observe(n),()=>t.disconnect()},[l,e]),(0,r.jsxs)(`div`,{className:`relative max-w-full`,children:[(0,r.jsx)(`div`,{ref:i,"data-testid":`markdown-table-scroll`,onScroll:l,className:`max-w-full overflow-x-auto custom-scrollbar-always`,children:e}),(0,r.jsx)(`div`,{"aria-hidden":!0,"data-testid":`markdown-table-scroll-fade-left`,"data-visible":s.left?`true`:`false`,className:t.cn(`pointer-events-none absolute inset-y-0 left-0 z-10`,a,`bg-gradient-to-r from-base to-transparent`,`transition-opacity duration-300 ease-out motion-reduce:transition-none`,s.left?`opacity-100`:`opacity-0`)}),(0,r.jsx)(`div`,{"aria-hidden":!0,"data-testid":`markdown-table-scroll-fade-right`,"data-visible":s.right?`true`:`false`,className:t.cn(`pointer-events-none absolute inset-y-0 right-0 z-10`,a,`bg-gradient-to-l from-base to-transparent`,`transition-opacity duration-300 ease-out motion-reduce:transition-none`,s.right?`opacity-100`:`opacity-0`)})]})}exports.MarkdownTableScroll=s;
|
|
1
|
+
const e=require("../../../_virtual/_rolldown/runtime.cjs"),t=require("../../../utils/utils.cjs");let n=require("react");n=e.__toESM(n,1);let r=require("react/jsx-runtime");var i=1,a=`w-10`;function o(e){let{scrollLeft:t,scrollWidth:n,clientWidth:r}=e,a=n-r,o=a>i;return{left:o&&t>i,right:o&&t<a-i}}function s({children:e}){let i=n.default.useRef(null),[s,c]=n.default.useState({left:!1,right:!1}),l=n.default.useCallback(()=>{let e=i.current;e&&c(o(e))},[]);return n.default.useLayoutEffect(()=>{l();let e=i.current;if(!e)return;let t=new ResizeObserver(l);t.observe(e);let n=e.firstElementChild;return n&&t.observe(n),()=>t.disconnect()},[l,e]),(0,r.jsxs)(`div`,{className:`relative max-w-full`,children:[(0,r.jsx)(`div`,{ref:i,"data-testid":`markdown-table-scroll`,onScroll:l,className:`max-w-full overflow-x-auto custom-scrollbar-always`,children:e}),(0,r.jsx)(`div`,{"aria-hidden":!0,"data-testid":`markdown-table-scroll-fade-left`,"data-visible":s.left?`true`:`false`,className:t.cn(`pointer-events-none absolute inset-y-0 left-0 z-10`,a,`bg-gradient-to-r from-[var(--oh-scroll-fade-from,var(--oh-color-base))] to-transparent`,`transition-opacity duration-300 ease-out motion-reduce:transition-none`,s.left?`opacity-100`:`opacity-0`)}),(0,r.jsx)(`div`,{"aria-hidden":!0,"data-testid":`markdown-table-scroll-fade-right`,"data-visible":s.right?`true`:`false`,className:t.cn(`pointer-events-none absolute inset-y-0 right-0 z-10`,a,`bg-gradient-to-l from-[var(--oh-scroll-fade-from,var(--oh-color-base))] to-transparent`,`transition-opacity duration-300 ease-out motion-reduce:transition-none`,s.right?`opacity-100`:`opacity-0`)})]})}exports.MarkdownTableScroll=s;
|
|
2
2
|
//# sourceMappingURL=markdown-table-scroll.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown-table-scroll.cjs","names":[],"sources":["../../../../src/components/features/markdown/markdown-table-scroll.tsx"],"sourcesContent":["import React from \"react\";\nimport { cn } from \"#/utils/utils\";\n\nconst SCROLL_EDGE_THRESHOLD_PX = 1;\nconst FADE_WIDTH_CLASS = \"w-10\";\n\ninterface MarkdownTableScrollProps {\n children: React.ReactNode;\n}\n\ninterface ScrollFadeState {\n left: boolean;\n right: boolean;\n}\n\nexport function readScrollFadeState(element: HTMLDivElement): ScrollFadeState {\n const { scrollLeft, scrollWidth, clientWidth } = element;\n const maxScroll = scrollWidth - clientWidth;\n const hasOverflow = maxScroll > SCROLL_EDGE_THRESHOLD_PX;\n\n return {\n left: hasOverflow && scrollLeft > SCROLL_EDGE_THRESHOLD_PX,\n right: hasOverflow && scrollLeft < maxScroll - SCROLL_EDGE_THRESHOLD_PX,\n };\n}\n\nexport function MarkdownTableScroll({ children }: MarkdownTableScrollProps) {\n const scrollRef = React.useRef<HTMLDivElement>(null);\n const [fadeState, setFadeState] = React.useState<ScrollFadeState>({\n left: false,\n right: false,\n });\n\n const updateFadeState = React.useCallback(() => {\n const element = scrollRef.current;\n if (!element) {\n return;\n }\n setFadeState(readScrollFadeState(element));\n }, []);\n\n React.useLayoutEffect(() => {\n updateFadeState();\n\n const element = scrollRef.current;\n if (!element) {\n return undefined;\n }\n\n const resizeObserver = new ResizeObserver(updateFadeState);\n resizeObserver.observe(element);\n\n const table = element.firstElementChild;\n if (table) {\n resizeObserver.observe(table);\n }\n\n return () => resizeObserver.disconnect();\n }, [updateFadeState, children]);\n\n return (\n <div className=\"relative max-w-full\">\n <div\n ref={scrollRef}\n data-testid=\"markdown-table-scroll\"\n onScroll={updateFadeState}\n className=\"max-w-full overflow-x-auto custom-scrollbar-always\"\n >\n {children}\n </div>\n <div\n aria-hidden\n data-testid=\"markdown-table-scroll-fade-left\"\n data-visible={fadeState.left ? \"true\" : \"false\"}\n className={cn(\n \"pointer-events-none absolute inset-y-0 left-0 z-10\",\n FADE_WIDTH_CLASS,\n \"bg-gradient-to-r from-base to-transparent\",\n \"transition-opacity duration-300 ease-out motion-reduce:transition-none\",\n fadeState.left ? \"opacity-100\" : \"opacity-0\",\n )}\n />\n <div\n aria-hidden\n data-testid=\"markdown-table-scroll-fade-right\"\n data-visible={fadeState.right ? \"true\" : \"false\"}\n className={cn(\n \"pointer-events-none absolute inset-y-0 right-0 z-10\",\n FADE_WIDTH_CLASS,\n \"bg-gradient-to-l from-base to-transparent\",\n \"transition-opacity duration-300 ease-out motion-reduce:transition-none\",\n fadeState.right ? \"opacity-100\" : \"opacity-0\",\n )}\n />\n </div>\n );\n}\n"],"mappings":"4KAGA,IAAM,EAA2B,EAC3B,EAAmB,OAWzB,SAAgB,EAAoB,EAA0C,CAC5E,GAAM,CAAE,aAAY,cAAa,eAAgB,EAC3C,EAAY,EAAc,EAC1B,EAAc,EAAY,EAEhC,MAAO,CACL,KAAM,GAAe,EAAa,EAClC,MAAO,GAAe,EAAa,EAAY,CACjD,CACF,CAEA,SAAgB,EAAoB,CAAE,YAAsC,CAC1E,IAAM,EAAY,EAAA,QAAM,OAAuB,IAAI,EAC7C,CAAC,EAAW,GAAgB,EAAA,QAAM,SAA0B,CAChE,KAAM,GACN,MAAO,EACT,CAAC,EAEK,EAAkB,EAAA,QAAM,gBAAkB,CAC9C,IAAM,EAAU,EAAU,QACrB,GAGL,EAAa,EAAoB,CAAO,CAAC,CAC3C,EAAG,CAAC,CAAC,EAqBL,OAnBA,EAAA,QAAM,oBAAsB,CAC1B,EAAgB,EAEhB,IAAM,EAAU,EAAU,QAC1B,GAAI,CAAC,EACH,OAGF,IAAM,EAAiB,IAAI,eAAe,CAAe,EACzD,EAAe,QAAQ,CAAO,EAE9B,IAAM,EAAQ,EAAQ,kBAKtB,OAJI,GACF,EAAe,QAAQ,CAAK,MAGjB,EAAe,WAAW,CACzC,EAAG,CAAC,EAAiB,CAAQ,CAAC,GAG5B,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,+BAAf,EACE,EAAA,EAAA,KAAC,MAAD,CACE,IAAK,EACL,cAAY,wBACZ,SAAU,EACV,UAAU,qDAET,UACE,CAAA,
|
|
1
|
+
{"version":3,"file":"markdown-table-scroll.cjs","names":[],"sources":["../../../../src/components/features/markdown/markdown-table-scroll.tsx"],"sourcesContent":["import React from \"react\";\nimport { cn } from \"#/utils/utils\";\n\nconst SCROLL_EDGE_THRESHOLD_PX = 1;\nconst FADE_WIDTH_CLASS = \"w-10\";\n\ninterface MarkdownTableScrollProps {\n children: React.ReactNode;\n}\n\ninterface ScrollFadeState {\n left: boolean;\n right: boolean;\n}\n\nexport function readScrollFadeState(element: HTMLDivElement): ScrollFadeState {\n const { scrollLeft, scrollWidth, clientWidth } = element;\n const maxScroll = scrollWidth - clientWidth;\n const hasOverflow = maxScroll > SCROLL_EDGE_THRESHOLD_PX;\n\n return {\n left: hasOverflow && scrollLeft > SCROLL_EDGE_THRESHOLD_PX,\n right: hasOverflow && scrollLeft < maxScroll - SCROLL_EDGE_THRESHOLD_PX,\n };\n}\n\nexport function MarkdownTableScroll({ children }: MarkdownTableScrollProps) {\n const scrollRef = React.useRef<HTMLDivElement>(null);\n const [fadeState, setFadeState] = React.useState<ScrollFadeState>({\n left: false,\n right: false,\n });\n\n const updateFadeState = React.useCallback(() => {\n const element = scrollRef.current;\n if (!element) {\n return;\n }\n setFadeState(readScrollFadeState(element));\n }, []);\n\n React.useLayoutEffect(() => {\n updateFadeState();\n\n const element = scrollRef.current;\n if (!element) {\n return undefined;\n }\n\n const resizeObserver = new ResizeObserver(updateFadeState);\n resizeObserver.observe(element);\n\n const table = element.firstElementChild;\n if (table) {\n resizeObserver.observe(table);\n }\n\n return () => resizeObserver.disconnect();\n }, [updateFadeState, children]);\n\n return (\n <div className=\"relative max-w-full\">\n <div\n ref={scrollRef}\n data-testid=\"markdown-table-scroll\"\n onScroll={updateFadeState}\n className=\"max-w-full overflow-x-auto custom-scrollbar-always\"\n >\n {children}\n </div>\n {/* The fades blend into whatever surface the table sits on, which differs\n between the chat stream and cards like the markdown artifact preview.\n Set `--oh-scroll-fade-from` on any ancestor to match another surface.\n The value is inlined at both edges because Tailwind only extracts\n classes from literal strings, not interpolated constants. */}\n <div\n aria-hidden\n data-testid=\"markdown-table-scroll-fade-left\"\n data-visible={fadeState.left ? \"true\" : \"false\"}\n className={cn(\n \"pointer-events-none absolute inset-y-0 left-0 z-10\",\n FADE_WIDTH_CLASS,\n \"bg-gradient-to-r from-[var(--oh-scroll-fade-from,var(--oh-color-base))] to-transparent\",\n \"transition-opacity duration-300 ease-out motion-reduce:transition-none\",\n fadeState.left ? \"opacity-100\" : \"opacity-0\",\n )}\n />\n <div\n aria-hidden\n data-testid=\"markdown-table-scroll-fade-right\"\n data-visible={fadeState.right ? \"true\" : \"false\"}\n className={cn(\n \"pointer-events-none absolute inset-y-0 right-0 z-10\",\n FADE_WIDTH_CLASS,\n \"bg-gradient-to-l from-[var(--oh-scroll-fade-from,var(--oh-color-base))] to-transparent\",\n \"transition-opacity duration-300 ease-out motion-reduce:transition-none\",\n fadeState.right ? \"opacity-100\" : \"opacity-0\",\n )}\n />\n </div>\n );\n}\n"],"mappings":"4KAGA,IAAM,EAA2B,EAC3B,EAAmB,OAWzB,SAAgB,EAAoB,EAA0C,CAC5E,GAAM,CAAE,aAAY,cAAa,eAAgB,EAC3C,EAAY,EAAc,EAC1B,EAAc,EAAY,EAEhC,MAAO,CACL,KAAM,GAAe,EAAa,EAClC,MAAO,GAAe,EAAa,EAAY,CACjD,CACF,CAEA,SAAgB,EAAoB,CAAE,YAAsC,CAC1E,IAAM,EAAY,EAAA,QAAM,OAAuB,IAAI,EAC7C,CAAC,EAAW,GAAgB,EAAA,QAAM,SAA0B,CAChE,KAAM,GACN,MAAO,EACT,CAAC,EAEK,EAAkB,EAAA,QAAM,gBAAkB,CAC9C,IAAM,EAAU,EAAU,QACrB,GAGL,EAAa,EAAoB,CAAO,CAAC,CAC3C,EAAG,CAAC,CAAC,EAqBL,OAnBA,EAAA,QAAM,oBAAsB,CAC1B,EAAgB,EAEhB,IAAM,EAAU,EAAU,QAC1B,GAAI,CAAC,EACH,OAGF,IAAM,EAAiB,IAAI,eAAe,CAAe,EACzD,EAAe,QAAQ,CAAO,EAE9B,IAAM,EAAQ,EAAQ,kBAKtB,OAJI,GACF,EAAe,QAAQ,CAAK,MAGjB,EAAe,WAAW,CACzC,EAAG,CAAC,EAAiB,CAAQ,CAAC,GAG5B,EAAA,EAAA,MAAC,MAAD,CAAK,UAAU,+BAAf,EACE,EAAA,EAAA,KAAC,MAAD,CACE,IAAK,EACL,cAAY,wBACZ,SAAU,EACV,UAAU,qDAET,UACE,CAAA,GAML,EAAA,EAAA,KAAC,MAAD,CACE,cAAA,GACA,cAAY,kCACZ,eAAc,EAAU,KAAO,OAAS,QACxC,UAAW,EAAA,GACT,qDACA,EACA,yFACA,yEACA,EAAU,KAAO,cAAgB,WACnC,CACD,CAAA,GACD,EAAA,EAAA,KAAC,MAAD,CACE,cAAA,GACA,cAAY,mCACZ,eAAc,EAAU,MAAQ,OAAS,QACzC,UAAW,EAAA,GACT,sDACA,EACA,yFACA,yEACA,EAAU,MAAQ,cAAgB,WACpC,CACD,CAAA,CACE,GAET"}
|
|
@@ -40,13 +40,13 @@ function s({ children: i }) {
|
|
|
40
40
|
"aria-hidden": !0,
|
|
41
41
|
"data-testid": "markdown-table-scroll-fade-left",
|
|
42
42
|
"data-visible": c.left ? "true" : "false",
|
|
43
|
-
className: e("pointer-events-none absolute inset-y-0 left-0 z-10", a, "bg-gradient-to-r from-base to-transparent", "transition-opacity duration-300 ease-out motion-reduce:transition-none", c.left ? "opacity-100" : "opacity-0")
|
|
43
|
+
className: e("pointer-events-none absolute inset-y-0 left-0 z-10", a, "bg-gradient-to-r from-[var(--oh-scroll-fade-from,var(--oh-color-base))] to-transparent", "transition-opacity duration-300 ease-out motion-reduce:transition-none", c.left ? "opacity-100" : "opacity-0")
|
|
44
44
|
}),
|
|
45
45
|
/* @__PURE__ */ n("div", {
|
|
46
46
|
"aria-hidden": !0,
|
|
47
47
|
"data-testid": "markdown-table-scroll-fade-right",
|
|
48
48
|
"data-visible": c.right ? "true" : "false",
|
|
49
|
-
className: e("pointer-events-none absolute inset-y-0 right-0 z-10", a, "bg-gradient-to-l from-base to-transparent", "transition-opacity duration-300 ease-out motion-reduce:transition-none", c.right ? "opacity-100" : "opacity-0")
|
|
49
|
+
className: e("pointer-events-none absolute inset-y-0 right-0 z-10", a, "bg-gradient-to-l from-[var(--oh-scroll-fade-from,var(--oh-color-base))] to-transparent", "transition-opacity duration-300 ease-out motion-reduce:transition-none", c.right ? "opacity-100" : "opacity-0")
|
|
50
50
|
})
|
|
51
51
|
]
|
|
52
52
|
});
|