@openhands/agent-canvas 1.17.0 → 1.18.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-DrjNsPOH.js → acp-providers-DcY_mKYa.js} +1 -1
- package/build/assets/active-backend-context-BnH4-L8h.js +2 -0
- package/build/assets/{agent-context-settings-CJUrcBut.js → agent-context-settings-D3peZkjX.js} +1 -1
- package/build/assets/{agent-profiles-service.api-Biieml2L.js → agent-profiles-service.api-DkeZaqIl.js} +1 -1
- package/build/assets/agent-profiles-settings-7yMWYjGF.js +1 -0
- package/build/assets/agent-server-client-options-EHB23gyx.js +1 -0
- package/build/assets/agent-server-config-C_x8Otd9.js +1 -0
- package/build/assets/agent-server-conversation-service.api-jiCopc2E.js +88 -0
- package/build/assets/{agent-settings-CpY3zObk.js → agent-settings-BF8hM6wb.js} +1 -1
- package/build/assets/agent-settings-YPutBAeR.js +1 -0
- package/build/assets/alert-banner-eYyqvHL3.js +1 -0
- package/build/assets/{api-error-message-Ve79qDcN.js → api-error-message-BXo0lCI5.js} +1 -1
- package/build/assets/api-key-entry-screen-CCBvBD8u.js +1 -0
- package/build/assets/app-settings-BtYDs-Be.js +1 -0
- package/build/assets/automation-detail-BxXhb_m2.js +3 -0
- package/build/assets/{automation-export-DxY47m3T.js → automation-export-BJsfi7n2.js} +1 -1
- package/build/assets/automation-git-sync-B7o_Nbl-.js +1 -0
- package/build/assets/automation-setup-route-qaJ6lLCf.js +3 -0
- package/build/assets/{automation-templates-4C4PM8K7.js → automation-templates-Bb98r-kx.js} +1 -1
- package/build/assets/automations-list-DEPVNud3.js +1 -0
- package/build/assets/{back-nav-button-B8tH0BEp.js → back-nav-button-B2yBiVMB.js} +1 -1
- package/build/assets/backend-form-modal-DxisQV_6.js +1 -0
- package/build/assets/backend-synced-settings-badge-DLEp6cq7.js +1 -0
- package/build/assets/badge-ehL-kgeO.js +1 -0
- package/build/assets/{base-modal-DTULovNc.js → base-modal-C1a0TsSx.js} +1 -1
- package/build/assets/{bot-CLMxGFaq.js → bot-ByolqPEr.js} +1 -1
- package/build/assets/{brand-button-DxPO4Lf8.js → brand-button-CUMfjb99.js} +1 -1
- package/build/assets/{browser-C-_qBasq.js → browser-SE24Tk8g.js} +6 -6
- package/build/assets/browser-tab-HjJxHA0V.js +1 -0
- package/build/assets/canvas-extension-page-BsUGBb1T.js +1 -0
- package/build/assets/canvas-extensions-C9Tm6s_E.js +1 -0
- package/build/assets/{canvas-extensions-runtime-DdnkgJXV.js → canvas-extensions-runtime-B8kxM4rO.js} +1 -1
- package/build/assets/{chat-send-button-B3SFSIg5.js → chat-send-button-CGvc2wpE.js} +1 -1
- package/build/assets/check-Bo8s5Ylg.js +1 -0
- package/build/assets/chevron-down-Cr6Fn_wS.js +1 -0
- package/build/assets/chevron-right-CfdfRn-8.js +1 -0
- package/build/assets/circle-check-5pH3qCji.js +1 -0
- package/build/assets/circle-plus-check-toggle-BDX_ctNs.js +1 -0
- package/build/assets/{cloud-funnel-analytics-BQanUEEC.js → cloud-funnel-analytics-D5uJ_4JE.js} +1 -1
- package/build/assets/{cog-VBlRo6iw.js → cog-CAEugqtV.js} +1 -1
- package/build/assets/command-menu-CQxs4mtX.js +1 -0
- package/build/assets/commits-tab-D0xgjipP.js +1 -0
- package/build/assets/{condenser-settings-eVKFMvap.js → condenser-settings-D63NJRBT.js} +1 -1
- package/build/assets/confirmation-modal-78sd9Vny.js +1 -0
- package/build/assets/{context-menu-list-item-DDROe12Z.js → context-menu-list-item-Cm7-7Y-7.js} +1 -1
- package/build/assets/{conversation-BnjJSFf5.js → conversation-B_nweu5p.js} +8 -8
- package/build/assets/conversation-edjzrv5w.js +1 -0
- package/build/assets/conversation-panel-edjzrv5w.js +1 -0
- package/build/assets/conversation-state-store-9D_3P7q4.js +1 -0
- package/build/assets/conversation-store-DC-JlJuq.js +6 -0
- package/build/assets/{conversation-tab-empty-state-DKlzAz5c.js → conversation-tab-empty-state-B3dpJXX0.js} +1 -1
- package/build/assets/copy-to-clipboard-button-B1ljgNxO.js +1 -0
- package/build/assets/device-verify-D5QRIS_x.js +1 -0
- package/build/assets/{dropdown-classes-B9kJVDIk.js → dropdown-classes-BGvOvW2_.js} +1 -1
- package/build/assets/edit-automation-modal-D_JWSmxT.js +1 -0
- package/build/assets/empty-state-FK_s1B60.js +1 -0
- package/build/assets/{entry.client-Ds8LVp-E.js → entry.client-BMafTpjO.js} +2 -2
- package/build/assets/enum-filter-dropdown-UNaom12H.js +1 -0
- package/build/assets/environment-switch-overlay-COibRUA-.js +1 -0
- package/build/assets/error-state-Cf8LRuuY.js +1 -0
- package/build/assets/extensions-hub-C_6ahIua.js +1 -0
- package/build/assets/extensions-navigation-C5GfpzFe.js +1 -0
- package/build/assets/{external-link-DQtv7eTh.js → external-link-Bd7Tga-1.js} +1 -1
- package/build/assets/files-tab-D5PPIWI1.js +1 -0
- package/build/assets/{form-control-classes-B-z85REi.js → form-control-classes-Dbq-7YP_.js} +1 -1
- package/build/assets/format-model-name-Dfh9Udrx.js +1 -0
- package/build/assets/get-git-path-jU2zjqIB.js +1 -0
- package/build/assets/git-repo-dropdown-BoDDWXfX.js +1 -0
- package/build/assets/git-repo-dropdown-uxlFw2gn.js +1 -0
- package/build/assets/highlighted-source-view-C7pAXrT0.js +1 -0
- package/build/assets/home-BM5_Wz_R.js +1 -0
- package/build/assets/home-Ivfm7KC4.js +1 -0
- package/build/assets/home-automation-run-tooltip-B1N2uO9p.js +1 -0
- package/build/assets/home-store-DCbei3DX.js +1 -0
- package/build/assets/hooks-service-Dnmhqc0Z.js +1 -0
- package/build/assets/index-home-td9BN2T3.js +1 -0
- package/build/assets/info-BcajzHn1.js +1 -0
- package/build/assets/install-server-modal-B81I-5OE.js +1 -0
- package/build/assets/kebab-menu-item-content-SCJBwZnp.js +1 -0
- package/build/assets/{key-CfXbJa8w.js → key-O84CMFHb.js} +1 -1
- package/build/assets/launch-ZLxI4opd.js +1 -0
- package/build/assets/list-filter-B8PQk3RA.js +1 -0
- package/build/assets/{list-todo-ByXMdY4W.js → list-todo-Caa8E1X_.js} +1 -1
- package/build/assets/llm-not-configured-banner-Dh_ajcM8.js +27 -0
- package/build/assets/llm-settings-CODhjQzy.js +1 -0
- package/build/assets/llm-settings-CZaXYgtw.js +1 -0
- package/build/assets/{llm-subscription-service-aDNBQ7Gq.js → llm-subscription-service-CgIDc38n.js} +1 -1
- package/build/assets/loader-circle-BxJgk73D.js +1 -0
- package/build/assets/{loading-spinner-DfHI77BA.js → loading-spinner-BeLV0IXf.js} +1 -1
- package/build/assets/manage-backends-modal-cMXqCb-B.js +1 -0
- package/build/assets/manifest-bfeccb98.js +1 -0
- package/build/assets/manifest-subpage-layout-CVZgVKoY.js +1 -0
- package/build/assets/{map-provider-DXvssjw9.js → map-provider-iRs80q_6.js} +1 -1
- package/build/assets/markdown-renderer-DyFl9-mL.js +8 -0
- package/build/assets/mcp-CraxoXQv.js +1 -0
- package/build/assets/{mcp-marketplace-utils-Cn_NIMWM.js → mcp-marketplace-utils-C7PYr293.js} +1 -1
- package/build/assets/mcp-page-CteH_K0N.js +9 -0
- package/build/assets/messages-Bk1xhbU2.js +41 -0
- package/build/assets/{modal-body-DJw4xBki.js → modal-body-C24WsdFb.js} +1 -1
- package/build/assets/{modal-classes-Dg0ngBPO.js → modal-classes-B0b9eiL8.js} +1 -1
- package/build/assets/modal-close-button-DfqfB2Nj.js +1 -0
- package/build/assets/{mouse-pointer-click-DEhsSUpu.js → mouse-pointer-click-BXlRN92t.js} +1 -1
- package/build/assets/onboarding-DXFBRqEy.js +1 -0
- package/build/assets/onboarding-modal-BT3Gx_sZ.js +1 -0
- package/build/assets/{organization-service.api-Cyg1Tn03.js → organization-service.api-B5CvmnYQ.js} +1 -1
- package/build/assets/{pencil-D5c47kyN.js → pencil-q-ihqDYv.js} +1 -1
- package/build/assets/{pin-C7vMagvf.js → pin-BdroVQdN.js} +1 -1
- package/build/assets/planner-tab-CstoOlAB.js +1 -0
- package/build/assets/{plug-CeaqeF6u.js → plug-7ACOEbMs.js} +1 -1
- package/build/assets/{plugins-management-service-Bh_pBuf-.js → plugins-management-service-5rqVPEGN.js} +1 -1
- package/build/assets/plus-C2e21TFC.js +1 -0
- package/build/assets/{profiles-service.api-BbOK0EkF.js → profiles-service.api-D1XKMgr0.js} +1 -1
- package/build/assets/providers-XmZ9HM4Q.js +1 -0
- package/build/assets/proxy-XP6U1Dhv.js +1 -0
- package/build/assets/{puzzle-DdAqbUrb.js → puzzle-LJYnuxKe.js} +1 -1
- package/build/assets/react-BX7IV650.js +1 -0
- package/build/assets/recommended-automations-launcher-DEHRKIAg.js +1 -0
- package/build/assets/{refresh-cw-DEQdPCpH.js → refresh-cw-BtdC3T8P.js} +1 -1
- package/build/assets/root-CM6IZHlg.css +1 -0
- package/build/assets/root-DdFkodKW.js +2 -0
- package/build/assets/root-layout-DUquNZTU.js +2 -0
- package/build/assets/schema-field-Y3F8JvXd.js +1 -0
- package/build/assets/sdk-section-page-CNZTLwH9.js +1 -0
- package/build/assets/secret-form-9-BoGA40.js +1 -0
- package/build/assets/{secrets-service-Cr8g8xuv.js → secrets-service-BOIsfD9j.js} +1 -1
- package/build/assets/secrets-settings-CBJgFr42.js +1 -0
- package/build/assets/settings-D4hdToD4.js +1 -0
- package/build/assets/settings-dropdown-input-DBvsZ7-Q.js +1 -0
- package/build/assets/settings-index-DeQphyBK.js +1 -0
- package/build/assets/settings-input-dVuzapBA.js +1 -0
- package/build/assets/{settings-list-classes-Ds4jPBHK.js → settings-list-classes-DhAPuXm6.js} +1 -1
- package/build/assets/settings-modal-tHiJ8iYV.js +1 -0
- package/build/assets/settings-nav-GAQjW1a-.js +16 -0
- package/build/assets/{settings-service.api-9_xdv_mh.js → settings-service.api-C1vZH9Vh.js} +1 -1
- package/build/assets/settings-switch-DDd1cQ8t.js +1 -0
- package/build/assets/{settings-utils-8GIIgOQL.js → settings-utils-BTaaRhLZ.js} +1 -1
- package/build/assets/shared-conversation-Cyg7m7ns.js +1 -0
- package/build/assets/{shield-check-DO7wa_eu.js → shield-check-C3Rl0NF8.js} +1 -1
- package/build/assets/{sidebar-layout-EPfWxk11.js → sidebar-layout-DH0E-5KW.js} +1 -1
- package/build/assets/sidebar-mobile-menu-toggle-CQ4iky__.js +1 -0
- package/build/assets/{sidebar-nav-link-_t4_Ef-9.js → sidebar-nav-link-oLQiKUBu.js} +1 -1
- package/build/assets/{sidebar-onboarding-checklist-storage-DbDX-Wvj.js → sidebar-onboarding-checklist-storage-TfvNNnnr.js} +1 -1
- package/build/assets/sidebar-store-DAZVH-DR.js +1 -0
- package/build/assets/skill-card-pill-row-DfjIfj0K.js +1 -0
- package/build/assets/{skill-filter-BBeZ4JEP.js → skill-filter-DsNLmn76.js} +2 -2
- package/build/assets/{skill-icon-badge-BDhQ-tQ0.js → skill-icon-badge-0MK_68Nc.js} +1 -1
- package/build/assets/skill-scope-BE-H9TSZ.js +1 -0
- package/build/assets/skills-plugins-vzSdxSk_.js +1 -0
- package/build/assets/skills-settings-BiPOH9H9.js +1 -0
- package/build/assets/{styled-tooltip-Pd22BIvr.js → styled-tooltip-CejG0rqv.js} +1 -1
- package/build/assets/task-list-tab-OkZSqnTp.js +1 -0
- package/build/assets/{terminal-onXx0_Qz.js → terminal-Dph60VFZ.js} +1 -1
- package/build/assets/{toggle-switch-lwQtxDcm.js → toggle-switch-B2uXaAUS.js} +1 -1
- package/build/assets/{trash-2-ZhKuTLiz.js → trash-2-Bb40zQzs.js} +1 -1
- package/build/assets/trash-Ckh7yCaB.js +1 -0
- package/build/assets/{triangle-alert-BajLPpQg.js → triangle-alert-DEdu-b_L.js} +1 -1
- package/build/assets/{typography-HLLg_FAn.js → typography-DgcLGdHk.js} +1 -1
- package/build/assets/usage-tab-MCmNRAnR.js +1 -0
- package/build/assets/use-acp-credential-form-ychoGqjC.js +1 -0
- package/build/assets/{use-activate-agent-profile-BLKML6vQ.js → use-activate-agent-profile-8L8Xdghy.js} +1 -1
- package/build/assets/use-agent-profiles-Bi8hSkze.js +1 -0
- package/build/assets/{use-automation-health-BZkQNEA8.js → use-automation-health-U99vvHxx.js} +1 -1
- package/build/assets/{use-automation-permissions-B_RpvL-s.js → use-automation-permissions-Buus0R6h.js} +1 -1
- package/build/assets/{use-automations-Cg0PDIZr.js → use-automations-Bq5hVuv0.js} +1 -1
- package/build/assets/{use-can-manage-org-profiles-BbN6KfDr.js → use-can-manage-org-profiles-MzIeEV8F.js} +1 -1
- package/build/assets/{use-canvas-extensions-CFUpHat6.js → use-canvas-extensions-Ca9KoVEm.js} +1 -1
- package/build/assets/{use-cloud-current-user-id-Dk0rjr63.js → use-cloud-current-user-id-B4ZLUZSa.js} +1 -1
- package/build/assets/{use-config-DpafU_QP.js → use-config-l5tkDHyD.js} +1 -1
- package/build/assets/{use-create-conversation-1YgmAHt-.js → use-create-conversation-i_PdFdIs.js} +1 -1
- package/build/assets/use-create-secret-CjWq0J4O.js +1 -0
- package/build/assets/use-free-models-gCsJ5BV0.js +1 -0
- package/build/assets/{use-get-secrets-CQ4b0qK8.js → use-get-secrets-CMTuWEp_.js} +1 -1
- package/build/assets/use-is-authed-A2Yinlt0.js +1 -0
- package/build/assets/{use-launch-skill-in-chat-DeYa1tFM.js → use-launch-skill-in-chat-QbXDhiHr.js} +1 -1
- package/build/assets/use-llm-configured-Dl4zg8ht.js +1 -0
- package/build/assets/use-llm-profiles-SWsuFmFu.js +1 -0
- package/build/assets/{use-manifest-capabilities-AOWGcu0j.js → use-manifest-capabilities-CdqYfUAC.js} +1 -1
- package/build/assets/{use-onboarding-completion-CoINurkm.js → use-onboarding-completion-Xl63ZvzQ.js} +1 -1
- package/build/assets/{use-pinned-home-route-D6krwXjy.js → use-pinned-home-route-BXDIUaxQ.js} +1 -1
- package/build/assets/{use-plugins-marketplace-mLsIDhBv.js → use-plugins-marketplace-BIIaPiaB.js} +1 -1
- package/build/assets/use-save-agent-profile-D-ZHHE94.js +1 -0
- package/build/assets/{use-save-settings-3U5K9-8x.js → use-save-settings-Dapkf4cC.js} +1 -1
- package/build/assets/{use-settings-Dn00UISA.js → use-settings-C2KKQKVC.js} +1 -1
- package/build/assets/use-settings-nav-items-TOc0Rsfd.js +1 -0
- package/build/assets/{use-tracking-B9prBsrq.js → use-tracking-j4uo9VEW.js} +1 -1
- package/build/assets/{use-user-conversation-ZWlXtbZb.js → use-user-conversation-5UFqMoO4.js} +1 -1
- package/build/assets/{useInfiniteQuery-BzrRqjLg.js → useInfiniteQuery-CtSRG7uW.js} +1 -1
- package/build/assets/{useMutation-CJuK90oc.js → useMutation-Cszf8mKy.js} +1 -1
- package/build/assets/useQueries-pUohFMSP.js +1 -0
- package/build/assets/useQuery-DAAacS7O.js +1 -0
- package/build/assets/useTranslation-CCgUjPJF.js +1 -0
- package/build/assets/utils-BgLSZdyy.js +1 -0
- package/build/assets/{vendor~conversation-panel~conversation-4eJ0tml3.js → vendor~conversation-panel~conversation-C22sJuWZ.js} +1 -1
- package/build/assets/vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~czjee3a3-BXXGJNDU.js +185 -0
- package/build/assets/vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~ew9ih1s8-40UR63Gt.js +1 -0
- package/build/assets/{vendor~index-home~home~conversation-panel~conversation-CSFLzmTN.js → vendor~index-home~home~conversation-panel~conversation-UTl7gUo9.js} +1 -1
- package/build/assets/{vendor~root-layout-n8XCEXG8.js → vendor~root-layout-CcdIqs-C.js} +1 -1
- package/build/assets/{vendor~root-layout~conversation-panel~conversation-3Idg1_U3.js → vendor~root-layout~conversation-panel~conversation-C3EBV3kp.js} +1 -1
- package/build/assets/vendor~root-layout~conversation-panel~conversation~skills-settings~automations-list~shared-~bovqqk8e-BCe0JJVy.js +1 -0
- package/build/assets/{vendor~root-layout~index-home~home~conversation-panel~conversation-DgQ564nC.js → vendor~root-layout~index-home~home~conversation-panel~conversation-BGGbPcYh.js} +1 -1
- package/build/assets/{vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~skills-setti~eo2nf80n-BPjI-atc.js → vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~skills-setti~eo2nf80n-3eJ4ExtV.js} +1 -1
- package/build/assets/{verification-settings-u2POabjH.js → verification-settings-DdBpaVPz.js} +1 -1
- package/build/assets/{wrench-CkgI4rxA.js → wrench-B8Wg_nbp.js} +1 -1
- package/build/assets/{x-By6qCmtY.js → x-CX8lVGwm.js} +1 -1
- package/build/index.html +4 -4
- package/build/locales/ar/openhands.json +6 -2
- package/build/locales/ca/openhands.json +6 -2
- package/build/locales/de/openhands.json +6 -2
- package/build/locales/en/openhands.json +6 -2
- package/build/locales/es/openhands.json +6 -2
- package/build/locales/fr/openhands.json +6 -2
- package/build/locales/it/openhands.json +6 -2
- package/build/locales/ja/openhands.json +6 -2
- package/build/locales/ko-KR/openhands.json +6 -2
- package/build/locales/no/openhands.json +6 -2
- package/build/locales/pt/openhands.json +6 -2
- package/build/locales/tr/openhands.json +6 -2
- package/build/locales/uk/openhands.json +6 -2
- package/build/locales/zh-CN/openhands.json +6 -2
- package/build/locales/zh-TW/openhands.json +6 -2
- package/config/defaults.json +1 -1
- package/dist/api/cloud/organization-service.api.cjs.map +1 -1
- package/dist/api/cloud/organization-service.api.d.ts +9 -1
- package/dist/api/cloud/organization-service.api.js.map +1 -1
- package/dist/api/config-service/config-service.api.cjs +1 -1
- package/dist/api/config-service/config-service.api.cjs.map +1 -1
- package/dist/api/config-service/config-service.api.js +6 -2
- package/dist/api/config-service/config-service.api.js.map +1 -1
- package/dist/api/config-service/config-service.types.d.ts +13 -0
- package/dist/components/features/automations/automation-card.cjs +1 -1
- package/dist/components/features/automations/automation-card.cjs.map +1 -1
- package/dist/components/features/automations/automation-card.js +39 -38
- package/dist/components/features/automations/automation-card.js.map +1 -1
- package/dist/components/features/automations/automation-list-row.cjs +1 -1
- package/dist/components/features/automations/automation-list-row.cjs.map +1 -1
- package/dist/components/features/automations/automation-list-row.js +73 -72
- package/dist/components/features/automations/automation-list-row.js.map +1 -1
- package/dist/components/features/automations/build-automation-menu-items.cjs +1 -1
- package/dist/components/features/automations/build-automation-menu-items.cjs.map +1 -1
- package/dist/components/features/automations/build-automation-menu-items.d.ts +3 -1
- package/dist/components/features/automations/build-automation-menu-items.js +12 -11
- package/dist/components/features/automations/build-automation-menu-items.js.map +1 -1
- package/dist/components/features/automations/detail/configuration-section.d.ts +7 -1
- package/dist/components/features/automations/detail/detail-header.d.ts +8 -5
- package/dist/components/features/backends/backend-selector.cjs +1 -1
- package/dist/components/features/backends/backend-selector.cjs.map +1 -1
- package/dist/components/features/backends/backend-selector.js +112 -112
- package/dist/components/features/backends/backend-selector.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 +62 -61
- package/dist/components/features/chat/components/chat-input-llm-profile-picker.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 +27 -26
- package/dist/components/features/chat/model-messages.js.map +1 -1
- 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.d.ts +1 -2
- package/dist/components/features/conversation-panel/budget-display.js +12 -19
- package/dist/components/features/conversation-panel/budget-display.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.js +45 -44
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-footer.js.map +1 -1
- package/dist/components/features/home/featured-automations/build-home-automation-menu-items.d.ts +1 -2
- package/dist/components/features/home/featured-automations/build-pinned-automation-menu-items.d.ts +0 -1
- package/dist/components/features/images/image-lightbox.cjs +2 -0
- package/dist/components/features/images/image-lightbox.cjs.map +1 -0
- package/dist/components/features/images/image-lightbox.d.ts +11 -0
- package/dist/components/features/images/image-lightbox.js +37 -0
- package/dist/components/features/images/image-lightbox.js.map +1 -0
- package/dist/components/features/images/image-preview.cjs +1 -1
- package/dist/components/features/images/image-preview.cjs.map +1 -1
- package/dist/components/features/images/image-preview.d.ts +2 -1
- package/dist/components/features/images/image-preview.js +33 -13
- package/dist/components/features/images/image-preview.js.map +1 -1
- package/dist/components/features/onboarding/steps/setup-llm-step.d.ts +3 -4
- package/dist/components/features/settings/cloud-settings-link.cjs +1 -1
- package/dist/components/features/settings/cloud-settings-link.cjs.map +1 -1
- package/dist/components/features/settings/cloud-settings-link.d.ts +3 -1
- package/dist/components/features/settings/cloud-settings-link.js +22 -21
- package/dist/components/features/settings/cloud-settings-link.js.map +1 -1
- package/dist/components/features/settings/llm-profiles/llm-settings-local-view.cjs +1 -1
- package/dist/components/features/settings/llm-profiles/llm-settings-local-view.js +2 -1
- package/dist/components/features/settings/llm-profiles/profile-row.cjs +1 -1
- package/dist/components/features/settings/llm-profiles/profile-row.js +1 -1
- package/dist/components/features/sidebar/sidebar-rail-body.cjs +1 -1
- package/dist/components/features/sidebar/sidebar-rail-body.cjs.map +1 -1
- package/dist/components/features/sidebar/sidebar-rail-body.d.ts +2 -1
- package/dist/components/features/sidebar/sidebar-rail-body.js +143 -142
- package/dist/components/features/sidebar/sidebar-rail-body.js.map +1 -1
- package/dist/components/features/sidebar/sidebar.cjs +1 -1
- package/dist/components/features/sidebar/sidebar.cjs.map +1 -1
- package/dist/components/features/sidebar/sidebar.js +28 -27
- package/dist/components/features/sidebar/sidebar.js.map +1 -1
- package/dist/components/providers/agent-server-ui-providers.cjs +1 -1
- package/dist/components/providers/agent-server-ui-providers.cjs.map +1 -1
- package/dist/components/providers/agent-server-ui-providers.js +33 -29
- package/dist/components/providers/agent-server-ui-providers.js.map +1 -1
- package/dist/components/shared/buttons/remove-button.cjs +1 -1
- package/dist/components/shared/buttons/remove-button.cjs.map +1 -1
- package/dist/components/shared/buttons/remove-button.d.ts +2 -1
- package/dist/components/shared/buttons/remove-button.js +2 -1
- package/dist/components/shared/buttons/remove-button.js.map +1 -1
- package/dist/components/shared/free-models-note.cjs +2 -0
- package/dist/components/shared/free-models-note.cjs.map +1 -0
- package/dist/components/shared/free-models-note.d.ts +3 -0
- package/dist/components/shared/free-models-note.js +20 -0
- package/dist/components/shared/free-models-note.js.map +1 -0
- 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 +102 -112
- 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 +1 -1
- package/dist/config/defaults.js.map +1 -1
- package/dist/constants/acp-providers.cjs.map +1 -1
- package/dist/constants/acp-providers.d.ts +12 -0
- package/dist/constants/acp-providers.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 +63 -62
- package/dist/hooks/chat/use-slash-command.js.map +1 -1
- package/dist/hooks/query/use-free-models.cjs +2 -0
- package/dist/hooks/query/use-free-models.cjs.map +1 -0
- package/dist/hooks/query/use-free-models.d.ts +23 -0
- package/dist/hooks/query/use-free-models.js +67 -0
- package/dist/hooks/query/use-free-models.js.map +1 -0
- package/dist/hooks/query/use-local-git-info.cjs.map +1 -1
- package/dist/hooks/query/use-local-git-info.js.map +1 -1
- package/dist/hooks/query/use-provider-models.cjs +1 -1
- package/dist/hooks/query/use-provider-models.cjs.map +1 -1
- package/dist/hooks/query/use-provider-models.js +34 -25
- package/dist/hooks/query/use-provider-models.js.map +1 -1
- 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 +31 -19
- package/dist/hooks/query/use-search-providers.js.map +1 -1
- package/dist/hooks/use-home-automation-actions.cjs +1 -1
- package/dist/hooks/use-home-automation-actions.cjs.map +1 -1
- package/dist/hooks/use-home-automation-actions.d.ts +1 -2
- package/dist/hooks/use-home-automation-actions.js +0 -1
- package/dist/hooks/use-home-automation-actions.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 +6 -2
- 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 +85 -17
- package/dist/i18n/translation.js.map +1 -1
- package/dist/locales/ar/openhands.json +6 -2
- package/dist/locales/ca/openhands.json +6 -2
- package/dist/locales/de/openhands.json +6 -2
- package/dist/locales/en/openhands.json +6 -2
- package/dist/locales/es/openhands.json +6 -2
- package/dist/locales/fr/openhands.json +6 -2
- package/dist/locales/it/openhands.json +6 -2
- package/dist/locales/ja/openhands.json +6 -2
- package/dist/locales/ko-KR/openhands.json +6 -2
- package/dist/locales/no/openhands.json +6 -2
- package/dist/locales/pt/openhands.json +6 -2
- package/dist/locales/tr/openhands.json +6 -2
- package/dist/locales/uk/openhands.json +6 -2
- package/dist/locales/zh-CN/openhands.json +6 -2
- package/dist/locales/zh-TW/openhands.json +6 -2
- package/dist/package.cjs +1 -1
- package/dist/package.cjs.map +1 -1
- package/dist/package.js +1 -1
- 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 +52 -47
- 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 +137 -145
- package/dist/routes/llm-settings.js.map +1 -1
- package/dist/stores/free-models-store.cjs +2 -0
- package/dist/stores/free-models-store.cjs.map +1 -0
- package/dist/stores/free-models-store.d.ts +30 -0
- package/dist/stores/free-models-store.js +22 -0
- package/dist/stores/free-models-store.js.map +1 -0
- 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 +27 -6
- package/dist/utils/format-model-name.js +14 -6
- package/dist/utils/format-model-name.js.map +1 -1
- package/package.json +1 -1
- package/build/assets/active-backend-context-rZiLJRUc.js +0 -2
- package/build/assets/agent-profiles-settings-x6RvPsq7.js +0 -1
- package/build/assets/agent-server-client-options-BikiNwTa.js +0 -1
- package/build/assets/agent-server-conversation-service.api-DYFsNHKf.js +0 -88
- package/build/assets/agent-settings-BJyovpJ_.js +0 -1
- package/build/assets/alert-banner-vARz1idS.js +0 -1
- package/build/assets/api-key-entry-screen-CsMuBhoP.js +0 -1
- package/build/assets/app-settings-BaJ7nfHt.js +0 -1
- package/build/assets/automation-detail-CVoGDrKq.js +0 -3
- package/build/assets/automation-git-sync-CRMP9Bbm.js +0 -1
- package/build/assets/automation-setup-route-072beLz1.js +0 -3
- package/build/assets/automations-list-xuqTSQL4.js +0 -1
- package/build/assets/backend-form-modal-0aJmWdsa.js +0 -1
- package/build/assets/backend-synced-settings-badge-B7rKWBir.js +0 -1
- package/build/assets/badge-CcGx2TaV.js +0 -1
- package/build/assets/browser-tab-DZanIw5m.js +0 -1
- package/build/assets/canvas-extension-page-o_cWgE9B.js +0 -1
- package/build/assets/canvas-extensions-DSgNlCha.js +0 -1
- package/build/assets/check-fDEfgSzj.js +0 -1
- package/build/assets/chevron-down-CFrWHqu8.js +0 -1
- package/build/assets/chevron-right-C5roOrJu.js +0 -1
- package/build/assets/circle-check-ClTzJTdb.js +0 -1
- package/build/assets/circle-plus-check-toggle-Fr-h-mbL.js +0 -1
- package/build/assets/command-menu-BT3Ia5Ru.js +0 -1
- package/build/assets/commits-tab-2jhtaohl.js +0 -1
- package/build/assets/confirmation-modal-gIFHeZt6.js +0 -1
- package/build/assets/conversation-DZ578rOX.js +0 -1
- package/build/assets/conversation-panel-DZ578rOX.js +0 -1
- package/build/assets/conversation-state-store-D1_G_9I-.js +0 -1
- package/build/assets/conversation-store-DVVfkCsk.js +0 -6
- package/build/assets/copy-to-clipboard-button-B3w0X7WH.js +0 -1
- package/build/assets/device-verify-BfkCzyFB.js +0 -1
- package/build/assets/edit-automation-modal-BwhJ39JT.js +0 -1
- package/build/assets/empty-state-RZz0OfnY.js +0 -1
- package/build/assets/enum-filter-dropdown-C4pW0ORd.js +0 -1
- package/build/assets/environment-switch-overlay-P2ZAuzov.js +0 -1
- package/build/assets/error-state-B_5cgNPJ.js +0 -1
- package/build/assets/extensions-hub-DsaUr4ca.js +0 -1
- package/build/assets/extensions-navigation-CRMZd-FE.js +0 -1
- package/build/assets/files-tab-CCgAcVdc.js +0 -1
- package/build/assets/format-model-name-CasAP3nd.js +0 -1
- package/build/assets/get-git-path-EHrSDxTM.js +0 -1
- package/build/assets/git-repo-dropdown-D2WEQ97X.js +0 -1
- package/build/assets/git-repo-dropdown-DVeCUUXG.js +0 -1
- package/build/assets/highlighted-source-view-DjHO3SdW.js +0 -1
- package/build/assets/home-BorS_vdS.js +0 -1
- package/build/assets/home-aIMbHw5M.js +0 -1
- package/build/assets/home-automation-run-tooltip-CpHNFiFl.js +0 -1
- package/build/assets/home-store-DyXz0w6d.js +0 -1
- package/build/assets/hooks-service-C6dpRtZM.js +0 -1
- package/build/assets/index-home-zKFuKK2B.js +0 -1
- package/build/assets/info-CTIdjttz.js +0 -1
- package/build/assets/install-server-modal-DUiZMc8u.js +0 -1
- package/build/assets/kebab-menu-item-content-mm04SBD3.js +0 -1
- package/build/assets/launch-BSIf9KEg.js +0 -1
- package/build/assets/list-filter-CNpqTWVY.js +0 -1
- package/build/assets/llm-not-configured-banner-z_QRSeV0.js +0 -27
- package/build/assets/llm-settings-B-5AHMiX.js +0 -1
- package/build/assets/llm-settings-B3SjHDmA.js +0 -1
- package/build/assets/loader-circle-Dex47vVW.js +0 -1
- package/build/assets/manage-backends-modal-q1NdFisr.js +0 -1
- package/build/assets/manifest-8920b615.js +0 -1
- package/build/assets/manifest-subpage-layout-BQQy5ipR.js +0 -1
- package/build/assets/markdown-renderer-rivLvYh-.js +0 -8
- package/build/assets/mcp-CEXDhQWn.js +0 -1
- package/build/assets/mcp-page-LGYIICMG.js +0 -9
- package/build/assets/messages-DUSeANAh.js +0 -41
- package/build/assets/modal-close-button-B9j4nasm.js +0 -1
- package/build/assets/onboarding-BjFebfs5.js +0 -1
- package/build/assets/onboarding-modal-DDTjOpyd.js +0 -1
- package/build/assets/planner-tab-D8f2Y3rn.js +0 -1
- package/build/assets/plus-B3wvU06-.js +0 -1
- package/build/assets/providers-2gIIkz2N.js +0 -1
- package/build/assets/proxy-CBgMd2zf.js +0 -1
- package/build/assets/recommended-automations-launcher-YLAQTp4s.js +0 -1
- package/build/assets/root-Bye_XmEL.js +0 -2
- package/build/assets/root-Cnwwzgo0.css +0 -1
- package/build/assets/root-layout-CSAwxBGM.js +0 -2
- package/build/assets/schema-field-CK0xYNbO.js +0 -1
- package/build/assets/sdk-section-page-Da73Rs_o.js +0 -1
- package/build/assets/secret-form-CB2VwYYg.js +0 -1
- package/build/assets/secrets-settings-Bh0udeKv.js +0 -1
- package/build/assets/settings-DmyIgRxO.js +0 -1
- package/build/assets/settings-dropdown-input-DVpyD87C.js +0 -1
- package/build/assets/settings-index-BSJUFmjE.js +0 -1
- package/build/assets/settings-input-C0dd8Utu.js +0 -1
- package/build/assets/settings-modal-yStgEh2o.js +0 -1
- package/build/assets/settings-switch-B78Khmyz.js +0 -1
- package/build/assets/shared-conversation-DM96X4KV.js +0 -1
- package/build/assets/sidebar-mobile-menu-toggle-D5GR5xT9.js +0 -1
- package/build/assets/sidebar-store-CSDFK0W5.js +0 -1
- package/build/assets/skill-card-pill-row-989y15jV.js +0 -1
- package/build/assets/skill-scope-Dz3kPH3P.js +0 -1
- package/build/assets/skills-plugins-e3CYGdUJ.js +0 -1
- package/build/assets/skills-settings-3e-FuHw_.js +0 -1
- package/build/assets/task-list-tab-3Jg8ywch.js +0 -1
- package/build/assets/trash-B6OIe4yX.js +0 -1
- package/build/assets/usage-tab-Cm-9j3UK.js +0 -1
- package/build/assets/use-acp-credential-form-BARcvjYE.js +0 -1
- package/build/assets/use-agent-profiles-1P91KSXz.js +0 -1
- package/build/assets/use-create-secret-D8p9ybQj.js +0 -1
- package/build/assets/use-is-authed-BRPjCRKM.js +0 -1
- package/build/assets/use-llm-configured-DGm8FiRR.js +0 -1
- package/build/assets/use-llm-profiles-BTTtpW4z.js +0 -1
- package/build/assets/use-save-agent-profile-CxyO9YG-.js +0 -1
- package/build/assets/use-settings-nav-items-vrjxTqcu.js +0 -16
- package/build/assets/useQueries-CykmpT-a.js +0 -1
- package/build/assets/useQuery-S6j94er1.js +0 -1
- package/build/assets/utils-BOco8mxR.js +0 -1
- package/build/assets/vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~czjee3a3-BfkNGovw.js +0 -185
- package/build/assets/vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~ew9ih1s8-DnDoaYNi.js +0 -1
- package/build/assets/vendor~root-layout~conversation-panel~conversation~skills-settings~automations-list~shared-~bovqqk8e-DkPlXWl9.js +0 -1
- package/build/assets/vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~iyia17eu-CV850oio.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-slash-command.js","names":[],"sources":["../../../src/hooks/chat/use-slash-command.ts"],"sourcesContent":["import { useState, useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { useConversationSkills } from \"#/hooks/query/use-conversation-skills\";\nimport { SkillInfo } from \"#/types/settings\";\nimport { BUILT_IN_COMMANDS, MODEL_COMMAND } from \"#/utils/constants\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport { useLlmProfiles } from \"#/hooks/query/use-llm-profiles\";\nimport { formatModelNameForDisplay } from \"#/utils/format-model-name\";\n\nexport type SlashCommandSkill = SkillInfo;\n\nexport interface SlashCommandItem {\n skill: SlashCommandSkill;\n /** The slash command string, e.g. \"/random-number\" */\n command: string;\n}\n\ntype SlashCompletionKind = \"command\" | \"model-profile\";\n\n/** Get the cursor's character offset within a contentEditable element. */\nfunction getCursorOffset(element: HTMLElement): number {\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0) return -1;\n const range = selection.getRangeAt(0);\n const preRange = range.cloneRange();\n preRange.selectNodeContents(element);\n preRange.setEnd(range.startContainer, range.startOffset);\n return preRange.toString().length;\n}\n\n/**\n * Hook for managing slash command autocomplete in the chat input.\n * Detects when user types \"/\" and provides filtered skill suggestions.\n * Only skills with explicit \"/\" triggers (TaskTrigger) appear in the menu.\n */\nexport const useSlashCommand = (\n chatInputRef: React.RefObject<HTMLDivElement | null>,\n) => {\n // Scope the skill catalog to this conversation's attached workspace so the\n // slash menu lists the same project skills that were loaded into it.\n const { data: skills, isLoading: isSkillsLoading } = useConversationSkills();\n const isCloud = useActiveBackend().backend.kind === \"cloud\";\n const { data: profilesData, isLoading: isProfilesLoading } = useLlmProfiles();\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const [filterText, setFilterText] = useState(\"\");\n const [completionKind, setCompletionKind] =\n useState<SlashCompletionKind>(\"command\");\n const [selectedIndex, setSelectedIndex] = useState(0);\n\n // Build slash command items from built-in commands + skills:\n // - Built-in commands (like /new) are included for V1 conversations\n // - /new is cloud-only — local backends don't surface it\n // - /model lists/switches LLM profiles; both local and cloud support them\n // - Skills with explicit \"/\" triggers use those triggers\n // - AgentSkills without \"/\" triggers get a derived \"/<name>\" command\n const slashItems = useMemo(() => {\n const items: SlashCommandItem[] = BUILT_IN_COMMANDS.filter((cmd) => {\n if (cmd.command === \"/new\") return isCloud;\n return true;\n });\n\n // Wait for skills to finish initial load so all commands appear together\n if (isSkillsLoading) return items;\n\n if (!skills) return items;\n skills.forEach((skill) => {\n const triggers = skill.triggers || [];\n const slashTriggers = triggers.filter((t) => t.startsWith(\"/\"));\n\n if (slashTriggers.length > 0) {\n // Skill has explicit slash triggers\n slashTriggers.forEach((trigger) => {\n items.push({ skill, command: trigger });\n });\n } else if (skill.type === \"agentskills\") {\n // AgentSkills without slash triggers get a derived command\n items.push({ skill, command: `/${skill.name}` });\n }\n });\n return items;\n }, [skills, isSkillsLoading, isCloud]);\n\n const modelProfileItems = useMemo<SlashCommandItem[]>(() => {\n return (profilesData?.profiles ?? []).map((profile) => {\n const command = `${MODEL_COMMAND} ${profile.name}`;\n return {\n command,\n skill: {\n name: profile.name,\n type: \"agentskills\",\n source: null,\n content: profile.model\n ? `Switch to ${formatModelNameForDisplay(profile.model)}`\n : \"Switch to this LLM profile\",\n triggers: [command],\n },\n };\n });\n }, [profilesData?.profiles]);\n\n // Filter items based on user input after \"/\"\n const filteredItems = useMemo(() => {\n const sourceItems =\n completionKind === \"model-profile\" ? modelProfileItems : slashItems;\n if (!filterText) return sourceItems;\n const lower = filterText.toLowerCase();\n return sourceItems.filter(\n (item) =>\n item.command.toLowerCase().includes(lower) ||\n item.skill.name.toLowerCase().includes(lower) ||\n item.skill.content?.toLowerCase().includes(lower),\n );\n }, [completionKind, modelProfileItems, slashItems, filterText]);\n\n // Keep refs in sync so handleSlashKeyDown always reads the latest values,\n // avoiding stale closures from React's batched state updates.\n const isMenuOpenRef = useRef(isMenuOpen);\n isMenuOpenRef.current = isMenuOpen;\n const filteredItemsRef = useRef(filteredItems);\n filteredItemsRef.current = filteredItems;\n const selectedIndexRef = useRef(selectedIndex);\n selectedIndexRef.current = selectedIndex;\n\n // Reset selected index when the filter text changes\n useEffect(() => {\n setSelectedIndex(0);\n }, [filterText]);\n\n // Track the character range of the current slash word so selectItem can\n // replace only that portion instead of wiping the entire input.\n const slashRangeRef = useRef<{ start: number; end: number } | null>(null);\n\n // Detect a slash word at the cursor position.\n // Returns the filter text (characters after \"/\") and the range of the\n // slash word within the full input text, or null if no slash word found.\n const getSlashText = useCallback((): {\n kind: SlashCompletionKind;\n text: string;\n start: number;\n end: number;\n } | null => {\n const element = chatInputRef.current;\n if (!element) return null;\n\n // Strip trailing newlines that contentEditable can produce, but preserve\n // spaces so \"/command \" (after selection) won't re-trigger the menu.\n const text = (element.innerText || \"\").replace(/[\\n\\r]+$/, \"\");\n const cursor = getCursorOffset(element);\n if (cursor < 0) return null;\n\n const textBeforeCursor = text.slice(0, cursor);\n\n const modelMatch = textBeforeCursor.match(/(^|\\s)(\\/model(?:\\s+\\S*)?)$/);\n if (modelMatch) {\n const modelCommand = modelMatch[2];\n const start = textBeforeCursor.length - modelCommand.length;\n const afterCursor = text.slice(cursor);\n const trailing = afterCursor.match(/^\\S*/);\n const end = cursor + (trailing ? trailing[0].length : 0);\n\n return {\n kind: \"model-profile\",\n text: modelCommand.replace(/^\\/model(?:\\s+)?/, \"\"),\n start,\n end,\n };\n }\n\n // Match a \"/\" preceded by whitespace or at position 0, followed by\n // non-whitespace characters, ending right at the cursor.\n const match = textBeforeCursor.match(/(^|\\s)(\\/\\S*)$/);\n if (!match) return null;\n\n const slashWord = match[2]; // e.g. \"/hel\"\n const start = textBeforeCursor.length - slashWord.length;\n // The end of the slash word extends past the cursor to include any\n // contiguous non-whitespace characters (covers the case where the\n // cursor sits in the middle of a word).\n const afterCursor = text.slice(cursor);\n const trailing = afterCursor.match(/^\\S*/);\n const end = cursor + (trailing ? trailing[0].length : 0);\n\n return { kind: \"command\", text: slashWord.slice(1), start, end }; // strip leading \"/\"\n }, [chatInputRef]);\n\n // Update the menu state based on current input\n const updateSlashMenu = useCallback(() => {\n const result = getSlashText();\n const hasItems =\n result?.kind === \"model-profile\"\n ? modelProfileItems.length > 0 || isProfilesLoading\n : slashItems.length > 0;\n\n if (result !== null && hasItems) {\n setCompletionKind(result.kind);\n setFilterText(result.text);\n slashRangeRef.current = { start: result.start, end: result.end };\n setIsMenuOpen(true);\n } else {\n setIsMenuOpen(false);\n setFilterText(\"\");\n setCompletionKind(\"command\");\n slashRangeRef.current = null;\n }\n }, [\n getSlashText,\n isProfilesLoading,\n modelProfileItems.length,\n slashItems.length,\n ]);\n\n // Select an item and replace only the slash word with the command\n const selectItem = useCallback(\n (item: SlashCommandItem) => {\n const element = chatInputRef.current;\n if (!element) return;\n\n const slashRange = slashRangeRef.current;\n const currentText = (element.innerText || \"\").replace(/[\\n\\r]+$/, \"\");\n const replacement = `${item.command} `;\n\n if (slashRange) {\n // Splice the command into the text, replacing only the slash word\n element.textContent =\n currentText.slice(0, slashRange.start) +\n replacement +\n currentText.slice(slashRange.end);\n\n // Position cursor right after the inserted command + space\n const cursorPos = slashRange.start + replacement.length;\n const textNode = element.firstChild;\n if (textNode) {\n const range = document.createRange();\n const sel = window.getSelection();\n const offset = Math.min(cursorPos, textNode.textContent!.length);\n range.setStart(textNode, offset);\n range.collapse(true);\n sel?.removeAllRanges();\n sel?.addRange(range);\n }\n } else {\n // Fallback: replace everything (e.g. if range tracking failed)\n element.textContent = replacement;\n const range = document.createRange();\n const sel = window.getSelection();\n range.selectNodeContents(element);\n range.collapse(false);\n sel?.removeAllRanges();\n sel?.addRange(range);\n }\n\n setIsMenuOpen(false);\n setFilterText(\"\");\n setCompletionKind(\"command\");\n setSelectedIndex(0);\n slashRangeRef.current = null;\n\n // Trigger a native InputEvent so React's onInput fires (for smartResize etc.)\n element.dispatchEvent(new InputEvent(\"input\", { bubbles: true }));\n\n // Restore focus so keyboard events (Enter to submit) work after selection\n element.focus();\n },\n [chatInputRef],\n );\n\n // Handle keyboard navigation in the menu.\n // Uses refs to always read the latest state, avoiding stale closures.\n const handleSlashKeyDown = useCallback(\n (e: React.KeyboardEvent): boolean => {\n const items = filteredItemsRef.current;\n if (!isMenuOpenRef.current || items.length === 0) return false;\n\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n setSelectedIndex((prev) => (prev < items.length - 1 ? prev + 1 : 0));\n return true;\n case \"ArrowUp\":\n e.preventDefault();\n setSelectedIndex((prev) => (prev > 0 ? prev - 1 : items.length - 1));\n return true;\n case \"Enter\":\n case \"Tab\": {\n const item = items[selectedIndexRef.current];\n if (!item) return false;\n e.preventDefault();\n selectItem(item);\n return true;\n }\n case \"Escape\":\n e.preventDefault();\n setIsMenuOpen(false);\n return true;\n // Cursor-movement keys: close the menu to avoid acting on a stale\n // slash-word range, but don't consume the event so the cursor moves.\n case \"ArrowLeft\":\n case \"ArrowRight\":\n case \"Home\":\n case \"End\":\n setIsMenuOpen(false);\n return false;\n default:\n return false;\n }\n },\n [selectItem],\n );\n\n const closeMenu = useCallback(() => setIsMenuOpen(false), []);\n\n return {\n isMenuOpen,\n filteredItems,\n selectedIndex,\n updateSlashMenu,\n selectItem,\n handleSlashKeyDown,\n closeMenu,\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-slash-command.js","names":[],"sources":["../../../src/hooks/chat/use-slash-command.ts"],"sourcesContent":["import { useState, useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { useConversationSkills } from \"#/hooks/query/use-conversation-skills\";\nimport { SkillInfo } from \"#/types/settings\";\nimport { BUILT_IN_COMMANDS, MODEL_COMMAND } from \"#/utils/constants\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport { useLlmProfiles } from \"#/hooks/query/use-llm-profiles\";\nimport { useFreeModels } from \"#/hooks/query/use-free-models\";\nimport { formatModelNameForDisplay } from \"#/utils/format-model-name\";\n\nexport type SlashCommandSkill = SkillInfo;\n\nexport interface SlashCommandItem {\n skill: SlashCommandSkill;\n /** The slash command string, e.g. \"/random-number\" */\n command: string;\n}\n\ntype SlashCompletionKind = \"command\" | \"model-profile\";\n\n/** Get the cursor's character offset within a contentEditable element. */\nfunction getCursorOffset(element: HTMLElement): number {\n const selection = window.getSelection();\n if (!selection || selection.rangeCount === 0) return -1;\n const range = selection.getRangeAt(0);\n const preRange = range.cloneRange();\n preRange.selectNodeContents(element);\n preRange.setEnd(range.startContainer, range.startOffset);\n return preRange.toString().length;\n}\n\n/**\n * Hook for managing slash command autocomplete in the chat input.\n * Detects when user types \"/\" and provides filtered skill suggestions.\n * Only skills with explicit \"/\" triggers (TaskTrigger) appear in the menu.\n */\nexport const useSlashCommand = (\n chatInputRef: React.RefObject<HTMLDivElement | null>,\n) => {\n // Scope the skill catalog to this conversation's attached workspace so the\n // slash menu lists the same project skills that were loaded into it.\n const { data: skills, isLoading: isSkillsLoading } = useConversationSkills();\n const isCloud = useActiveBackend().backend.kind === \"cloud\";\n const { data: profilesData, isLoading: isProfilesLoading } = useLlmProfiles();\n const freeModels = useFreeModels();\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const [filterText, setFilterText] = useState(\"\");\n const [completionKind, setCompletionKind] =\n useState<SlashCompletionKind>(\"command\");\n const [selectedIndex, setSelectedIndex] = useState(0);\n\n // Build slash command items from built-in commands + skills:\n // - Built-in commands (like /new) are included for V1 conversations\n // - /new is cloud-only — local backends don't surface it\n // - /model lists/switches LLM profiles; both local and cloud support them\n // - Skills with explicit \"/\" triggers use those triggers\n // - AgentSkills without \"/\" triggers get a derived \"/<name>\" command\n const slashItems = useMemo(() => {\n const items: SlashCommandItem[] = BUILT_IN_COMMANDS.filter((cmd) => {\n if (cmd.command === \"/new\") return isCloud;\n return true;\n });\n\n // Wait for skills to finish initial load so all commands appear together\n if (isSkillsLoading) return items;\n\n if (!skills) return items;\n skills.forEach((skill) => {\n const triggers = skill.triggers || [];\n const slashTriggers = triggers.filter((t) => t.startsWith(\"/\"));\n\n if (slashTriggers.length > 0) {\n // Skill has explicit slash triggers\n slashTriggers.forEach((trigger) => {\n items.push({ skill, command: trigger });\n });\n } else if (skill.type === \"agentskills\") {\n // AgentSkills without slash triggers get a derived command\n items.push({ skill, command: `/${skill.name}` });\n }\n });\n return items;\n }, [skills, isSkillsLoading, isCloud]);\n\n const modelProfileItems = useMemo<SlashCommandItem[]>(() => {\n return (profilesData?.profiles ?? []).map((profile) => {\n const command = `${MODEL_COMMAND} ${profile.name}`;\n return {\n command,\n skill: {\n name: profile.name,\n type: \"agentskills\",\n source: null,\n content: profile.model\n ? `Switch to ${formatModelNameForDisplay(profile.model, freeModels)}`\n : \"Switch to this LLM profile\",\n triggers: [command],\n },\n };\n });\n }, [profilesData?.profiles, freeModels]);\n\n // Filter items based on user input after \"/\"\n const filteredItems = useMemo(() => {\n const sourceItems =\n completionKind === \"model-profile\" ? modelProfileItems : slashItems;\n if (!filterText) return sourceItems;\n const lower = filterText.toLowerCase();\n return sourceItems.filter(\n (item) =>\n item.command.toLowerCase().includes(lower) ||\n item.skill.name.toLowerCase().includes(lower) ||\n item.skill.content?.toLowerCase().includes(lower),\n );\n }, [completionKind, modelProfileItems, slashItems, filterText]);\n\n // Keep refs in sync so handleSlashKeyDown always reads the latest values,\n // avoiding stale closures from React's batched state updates.\n const isMenuOpenRef = useRef(isMenuOpen);\n isMenuOpenRef.current = isMenuOpen;\n const filteredItemsRef = useRef(filteredItems);\n filteredItemsRef.current = filteredItems;\n const selectedIndexRef = useRef(selectedIndex);\n selectedIndexRef.current = selectedIndex;\n\n // Reset selected index when the filter text changes\n useEffect(() => {\n setSelectedIndex(0);\n }, [filterText]);\n\n // Track the character range of the current slash word so selectItem can\n // replace only that portion instead of wiping the entire input.\n const slashRangeRef = useRef<{ start: number; end: number } | null>(null);\n\n // Detect a slash word at the cursor position.\n // Returns the filter text (characters after \"/\") and the range of the\n // slash word within the full input text, or null if no slash word found.\n const getSlashText = useCallback((): {\n kind: SlashCompletionKind;\n text: string;\n start: number;\n end: number;\n } | null => {\n const element = chatInputRef.current;\n if (!element) return null;\n\n // Strip trailing newlines that contentEditable can produce, but preserve\n // spaces so \"/command \" (after selection) won't re-trigger the menu.\n const text = (element.innerText || \"\").replace(/[\\n\\r]+$/, \"\");\n const cursor = getCursorOffset(element);\n if (cursor < 0) return null;\n\n const textBeforeCursor = text.slice(0, cursor);\n\n const modelMatch = textBeforeCursor.match(/(^|\\s)(\\/model(?:\\s+\\S*)?)$/);\n if (modelMatch) {\n const modelCommand = modelMatch[2];\n const start = textBeforeCursor.length - modelCommand.length;\n const afterCursor = text.slice(cursor);\n const trailing = afterCursor.match(/^\\S*/);\n const end = cursor + (trailing ? trailing[0].length : 0);\n\n return {\n kind: \"model-profile\",\n text: modelCommand.replace(/^\\/model(?:\\s+)?/, \"\"),\n start,\n end,\n };\n }\n\n // Match a \"/\" preceded by whitespace or at position 0, followed by\n // non-whitespace characters, ending right at the cursor.\n const match = textBeforeCursor.match(/(^|\\s)(\\/\\S*)$/);\n if (!match) return null;\n\n const slashWord = match[2]; // e.g. \"/hel\"\n const start = textBeforeCursor.length - slashWord.length;\n // The end of the slash word extends past the cursor to include any\n // contiguous non-whitespace characters (covers the case where the\n // cursor sits in the middle of a word).\n const afterCursor = text.slice(cursor);\n const trailing = afterCursor.match(/^\\S*/);\n const end = cursor + (trailing ? trailing[0].length : 0);\n\n return { kind: \"command\", text: slashWord.slice(1), start, end }; // strip leading \"/\"\n }, [chatInputRef]);\n\n // Update the menu state based on current input\n const updateSlashMenu = useCallback(() => {\n const result = getSlashText();\n const hasItems =\n result?.kind === \"model-profile\"\n ? modelProfileItems.length > 0 || isProfilesLoading\n : slashItems.length > 0;\n\n if (result !== null && hasItems) {\n setCompletionKind(result.kind);\n setFilterText(result.text);\n slashRangeRef.current = { start: result.start, end: result.end };\n setIsMenuOpen(true);\n } else {\n setIsMenuOpen(false);\n setFilterText(\"\");\n setCompletionKind(\"command\");\n slashRangeRef.current = null;\n }\n }, [\n getSlashText,\n isProfilesLoading,\n modelProfileItems.length,\n slashItems.length,\n ]);\n\n // Select an item and replace only the slash word with the command\n const selectItem = useCallback(\n (item: SlashCommandItem) => {\n const element = chatInputRef.current;\n if (!element) return;\n\n const slashRange = slashRangeRef.current;\n const currentText = (element.innerText || \"\").replace(/[\\n\\r]+$/, \"\");\n const replacement = `${item.command} `;\n\n if (slashRange) {\n // Splice the command into the text, replacing only the slash word\n element.textContent =\n currentText.slice(0, slashRange.start) +\n replacement +\n currentText.slice(slashRange.end);\n\n // Position cursor right after the inserted command + space\n const cursorPos = slashRange.start + replacement.length;\n const textNode = element.firstChild;\n if (textNode) {\n const range = document.createRange();\n const sel = window.getSelection();\n const offset = Math.min(cursorPos, textNode.textContent!.length);\n range.setStart(textNode, offset);\n range.collapse(true);\n sel?.removeAllRanges();\n sel?.addRange(range);\n }\n } else {\n // Fallback: replace everything (e.g. if range tracking failed)\n element.textContent = replacement;\n const range = document.createRange();\n const sel = window.getSelection();\n range.selectNodeContents(element);\n range.collapse(false);\n sel?.removeAllRanges();\n sel?.addRange(range);\n }\n\n setIsMenuOpen(false);\n setFilterText(\"\");\n setCompletionKind(\"command\");\n setSelectedIndex(0);\n slashRangeRef.current = null;\n\n // Trigger a native InputEvent so React's onInput fires (for smartResize etc.)\n element.dispatchEvent(new InputEvent(\"input\", { bubbles: true }));\n\n // Restore focus so keyboard events (Enter to submit) work after selection\n element.focus();\n },\n [chatInputRef],\n );\n\n // Handle keyboard navigation in the menu.\n // Uses refs to always read the latest state, avoiding stale closures.\n const handleSlashKeyDown = useCallback(\n (e: React.KeyboardEvent): boolean => {\n const items = filteredItemsRef.current;\n if (!isMenuOpenRef.current || items.length === 0) return false;\n\n switch (e.key) {\n case \"ArrowDown\":\n e.preventDefault();\n setSelectedIndex((prev) => (prev < items.length - 1 ? prev + 1 : 0));\n return true;\n case \"ArrowUp\":\n e.preventDefault();\n setSelectedIndex((prev) => (prev > 0 ? prev - 1 : items.length - 1));\n return true;\n case \"Enter\":\n case \"Tab\": {\n const item = items[selectedIndexRef.current];\n if (!item) return false;\n e.preventDefault();\n selectItem(item);\n return true;\n }\n case \"Escape\":\n e.preventDefault();\n setIsMenuOpen(false);\n return true;\n // Cursor-movement keys: close the menu to avoid acting on a stale\n // slash-word range, but don't consume the event so the cursor moves.\n case \"ArrowLeft\":\n case \"ArrowRight\":\n case \"Home\":\n case \"End\":\n setIsMenuOpen(false);\n return false;\n default:\n return false;\n }\n },\n [selectItem],\n );\n\n const closeMenu = useCallback(() => setIsMenuOpen(false), []);\n\n return {\n isMenuOpen,\n filteredItems,\n selectedIndex,\n updateSlashMenu,\n selectItem,\n handleSlashKeyDown,\n closeMenu,\n };\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../node_modules/@tanstack/react-query/build/modern/useQuery.cjs"),n=require("../../contexts/active-backend-context.cjs"),r=require("../../api/config-service/config-service.api.cjs"),i=require("../../stores/free-models-store.cjs"),a=require("./use-verified-models.cjs");let o=require("react");o=e.__toESM(o,1);var s=`openhands`,c=100,l=10;async function u(e,t,n,i){if(i>=l)throw Error(`Too many pagination requests while fetching OpenHands models (depth=${i})`);let a=await r.default.searchModels({provider__eq:s,limit:c,...t?{page_id:t}:{}},e);if(!a.next_page_id)return a.items;if(n.has(a.next_page_id))throw Error(`Repeated page id while fetching OpenHands models: ${a.next_page_id}`);n.add(a.next_page_id);let o=await u(e,a.next_page_id,n,i+1);return[...a.items,...o]}var d=()=>{let{backend:e,orgId:r}=n.useActiveBackend(),i=[e.id,e.connectionRevision??0,r];return t.useQuery({queryKey:[`config`,`models`,s,`flags`,...i],queryFn:async({client:e})=>u(await e.fetchQuery({queryKey:[...a.VERIFIED_MODELS_QUERY_KEY,...i],queryFn:a.fetchVerifiedModelsByProvider,staleTime:a.VERIFIED_MODELS_STALE_TIME}),null,new Set,0),staleTime:a.VERIFIED_MODELS_STALE_TIME,gcTime:a.VERIFIED_MODELS_GC_TIME})},f=()=>{let{backend:e,orgId:t}=n.useActiveBackend(),{data:r,isError:a}=d(),c=i.useFreeModelsStore(e=>e.setFlags),l=i.useFreeModelsStore(e=>e.markDefaultModelReady),u=i.useFreeModelsStore(e=>e.resetFlags);o.default.useEffect(()=>{u()},[e.id,e.connectionRevision,t,u]),o.default.useEffect(()=>{if(!r)return;let e=new Set(r.filter(e=>e.free).map(e=>`${s}/${e.name}`)),t=r.find(e=>e.default);c({freeModels:e,defaultModel:t?`${s}/${t.name}`:null})},[r,c]),o.default.useEffect(()=>{a&&l()},[a,l])},p=()=>i.useFreeModelsStore(e=>e.freeModels),m=()=>i.useFreeModelsStore(e=>e.defaultModel);exports.useDefaultModel=m,exports.useFreeModels=p,exports.useHydrateFreeModels=f;
|
|
2
|
+
//# sourceMappingURL=use-free-models.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-free-models.cjs","names":[],"sources":["../../../src/hooks/query/use-free-models.ts"],"sourcesContent":["import React from \"react\";\nimport { useQuery } from \"@tanstack/react-query\";\nimport ConfigService from \"#/api/config-service/config-service.api\";\nimport type { LLMModel } from \"#/api/config-service/config-service.types\";\nimport type { FreeModelSet } from \"#/utils/format-model-name\";\nimport { useFreeModelsStore } from \"#/stores/free-models-store\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport {\n VERIFIED_MODELS_GC_TIME,\n VERIFIED_MODELS_QUERY_KEY,\n VERIFIED_MODELS_STALE_TIME,\n fetchVerifiedModelsByProvider,\n} from \"./use-verified-models\";\n\n/**\n * Provider whose models carry free / default metadata. Both are\n * OpenHands-managed concepts, so they are scoped to the `openhands` provider.\n */\nconst OPENHANDS_PROVIDER = \"openhands\";\nconst CLOUD_MODEL_SEARCH_PAGE_LIMIT = 100;\nconst MAX_PAGINATION_DEPTH = 10;\n\nasync function fetchAllOpenHandsModels(\n verifiedByProvider: Record<string, string[]>,\n pageId: string | null | undefined,\n seenPageIds: Set<string>,\n depth: number,\n): Promise<LLMModel[]> {\n if (depth >= MAX_PAGINATION_DEPTH) {\n throw new Error(\n `Too many pagination requests while fetching OpenHands models (depth=${depth})`,\n );\n }\n\n const page = await ConfigService.searchModels(\n {\n provider__eq: OPENHANDS_PROVIDER,\n limit: CLOUD_MODEL_SEARCH_PAGE_LIMIT,\n ...(pageId ? { page_id: pageId } : {}),\n },\n verifiedByProvider,\n );\n\n if (!page.next_page_id) {\n return page.items;\n }\n\n if (seenPageIds.has(page.next_page_id)) {\n throw new Error(\n `Repeated page id while fetching OpenHands models: ${page.next_page_id}`,\n );\n }\n seenPageIds.add(page.next_page_id);\n\n const rest = await fetchAllOpenHandsModels(\n verifiedByProvider,\n page.next_page_id,\n seenPageIds,\n depth + 1,\n );\n return [...page.items, ...rest];\n}\n\n/**\n * Fetches the `openhands` provider's models with their DB-driven `free` /\n * `default` flags (the same channel that carries `verified`).\n */\nconst useOpenHandsModels = () => {\n const { backend, orgId } = useActiveBackend();\n const backendScope = [\n backend.id,\n backend.connectionRevision ?? 0,\n orgId,\n ] as const;\n\n return useQuery({\n queryKey: [\n \"config\",\n \"models\",\n OPENHANDS_PROVIDER,\n \"flags\",\n ...backendScope,\n ],\n queryFn: async ({ client }): Promise<LLMModel[]> => {\n const verifiedByProvider = await client.fetchQuery({\n queryKey: [...VERIFIED_MODELS_QUERY_KEY, ...backendScope],\n queryFn: fetchVerifiedModelsByProvider,\n staleTime: VERIFIED_MODELS_STALE_TIME,\n });\n return fetchAllOpenHandsModels(verifiedByProvider, null, new Set(), 0);\n },\n staleTime: VERIFIED_MODELS_STALE_TIME,\n gcTime: VERIFIED_MODELS_GC_TIME,\n });\n};\n\n/**\n * Fetches the DB-driven free / default flags once and mirrors them into the\n * {@link useFreeModelsStore}. Mount this high in the tree (inside the query\n * provider). Leaf display components then read the flags synchronously via the\n * {@link useFreeModels} / {@link useDefaultModel} zustand selectors, so they\n * stay renderable in isolation without a QueryClientProvider in scope.\n */\nexport const useHydrateFreeModels = (): void => {\n const { backend, orgId } = useActiveBackend();\n const { data, isError } = useOpenHandsModels();\n const setFlags = useFreeModelsStore((state) => state.setFlags);\n const markDefaultModelReady = useFreeModelsStore(\n (state) => state.markDefaultModelReady,\n );\n const resetFlags = useFreeModelsStore((state) => state.resetFlags);\n\n React.useEffect(() => {\n resetFlags();\n }, [backend.id, backend.connectionRevision, orgId, resetFlags]);\n\n React.useEffect(() => {\n if (!data) return;\n const freeModels = new Set(\n data\n .filter((model) => model.free)\n .map((model) => `${OPENHANDS_PROVIDER}/${model.name}`),\n );\n const defaultEntry = data.find((model) => model.default);\n setFlags({\n freeModels,\n defaultModel: defaultEntry\n ? `${OPENHANDS_PROVIDER}/${defaultEntry.name}`\n : null,\n });\n }, [data, setFlags]);\n\n React.useEffect(() => {\n if (isError) markDefaultModelReady();\n }, [isError, markDefaultModelReady]);\n};\n\n/**\n * Set of free ``openhands/<model>`` ids, sourced from the backend model list\n * (DB-driven on cloud via the `free` flag). Returns an empty set on backends\n * without free metadata (e.g. the local agent-server), so callers uniformly\n * treat \"not in set\" as paid and the frontend keeps no hardcoded free list.\n */\nexport const useFreeModels = (): FreeModelSet =>\n useFreeModelsStore((state) => state.freeModels);\n\n/**\n * DB-driven default OpenHands model id (``openhands/<model>``), used to\n * preselect a model on onboarding and when creating a new OpenHands model.\n * Returns `null` on backends without default metadata.\n */\nexport const useDefaultModel = (): string | null =>\n useFreeModelsStore((state) => state.defaultModel);\n\nexport const useDefaultModelReady = (): boolean =>\n useFreeModelsStore((state) => state.defaultModelReady);\n"],"mappings":"2XAkBA,IAAM,EAAqB,YACrB,EAAgC,IAChC,EAAuB,GAE7B,eAAe,EACb,EACA,EACA,EACA,EACqB,CACrB,GAAI,GAAS,EACX,MAAU,MACR,uEAAuE,EAAM,EAC/E,EAGF,IAAM,EAAO,MAAM,EAAA,QAAc,aAC/B,CACE,aAAc,EACd,MAAO,EACP,GAAI,EAAS,CAAE,QAAS,CAAO,EAAI,CAAC,CACtC,EACA,CACF,EAEA,GAAI,CAAC,EAAK,aACR,OAAO,EAAK,MAGd,GAAI,EAAY,IAAI,EAAK,YAAY,EACnC,MAAU,MACR,qDAAqD,EAAK,cAC5D,EAEF,EAAY,IAAI,EAAK,YAAY,EAEjC,IAAM,EAAO,MAAM,EACjB,EACA,EAAK,aACL,EACA,EAAQ,CACV,EACA,MAAO,CAAC,GAAG,EAAK,MAAO,GAAG,CAAI,CAChC,CAMA,IAAM,MAA2B,CAC/B,GAAM,CAAE,UAAS,SAAU,EAAA,iBAAiB,EACtC,EAAe,CACnB,EAAQ,GACR,EAAQ,oBAAsB,EAC9B,CACF,EAEA,OAAO,EAAA,SAAS,CACd,SAAU,CACR,SACA,SACA,EACA,QACA,GAAG,CACL,EACA,QAAS,MAAO,CAAE,YAMT,EAAwB,MALE,EAAO,WAAW,CACjD,SAAU,CAAC,GAAG,EAAA,0BAA2B,GAAG,CAAY,EACxD,QAAS,EAAA,8BACT,UAAW,EAAA,0BACb,CAAC,EACkD,KAAM,IAAI,IAAO,CAAC,EAEvE,UAAW,EAAA,2BACX,OAAQ,EAAA,uBACV,CAAC,CACH,EASa,MAAmC,CAC9C,GAAM,CAAE,UAAS,SAAU,EAAA,iBAAiB,EACtC,CAAE,OAAM,WAAY,EAAmB,EACvC,EAAW,EAAA,mBAAoB,GAAU,EAAM,QAAQ,EACvD,EAAwB,EAAA,mBAC3B,GAAU,EAAM,qBACnB,EACM,EAAa,EAAA,mBAAoB,GAAU,EAAM,UAAU,EAEjE,EAAA,QAAM,cAAgB,CACpB,EAAW,CACb,EAAG,CAAC,EAAQ,GAAI,EAAQ,mBAAoB,EAAO,CAAU,CAAC,EAE9D,EAAA,QAAM,cAAgB,CACpB,GAAI,CAAC,EAAM,OACX,IAAM,EAAa,IAAI,IACrB,EACG,OAAQ,GAAU,EAAM,IAAI,EAC5B,IAAK,GAAU,GAAG,EAAmB,GAAG,EAAM,MAAM,CACzD,EACM,EAAe,EAAK,KAAM,GAAU,EAAM,OAAO,EACvD,EAAS,CACP,aACA,aAAc,EACV,GAAG,EAAmB,GAAG,EAAa,OACtC,IACN,CAAC,CACH,EAAG,CAAC,EAAM,CAAQ,CAAC,EAEnB,EAAA,QAAM,cAAgB,CAChB,GAAS,EAAsB,CACrC,EAAG,CAAC,EAAS,CAAqB,CAAC,CACrC,EAQa,MACX,EAAA,mBAAoB,GAAU,EAAM,UAAU,EAOnC,MACX,EAAA,mBAAoB,GAAU,EAAM,YAAY"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { FreeModelSet } from "#/utils/format-model-name";
|
|
2
|
+
/**
|
|
3
|
+
* Fetches the DB-driven free / default flags once and mirrors them into the
|
|
4
|
+
* {@link useFreeModelsStore}. Mount this high in the tree (inside the query
|
|
5
|
+
* provider). Leaf display components then read the flags synchronously via the
|
|
6
|
+
* {@link useFreeModels} / {@link useDefaultModel} zustand selectors, so they
|
|
7
|
+
* stay renderable in isolation without a QueryClientProvider in scope.
|
|
8
|
+
*/
|
|
9
|
+
export declare const useHydrateFreeModels: () => void;
|
|
10
|
+
/**
|
|
11
|
+
* Set of free ``openhands/<model>`` ids, sourced from the backend model list
|
|
12
|
+
* (DB-driven on cloud via the `free` flag). Returns an empty set on backends
|
|
13
|
+
* without free metadata (e.g. the local agent-server), so callers uniformly
|
|
14
|
+
* treat "not in set" as paid and the frontend keeps no hardcoded free list.
|
|
15
|
+
*/
|
|
16
|
+
export declare const useFreeModels: () => FreeModelSet;
|
|
17
|
+
/**
|
|
18
|
+
* DB-driven default OpenHands model id (``openhands/<model>``), used to
|
|
19
|
+
* preselect a model on onboarding and when creating a new OpenHands model.
|
|
20
|
+
* Returns `null` on backends without default metadata.
|
|
21
|
+
*/
|
|
22
|
+
export declare const useDefaultModel: () => string | null;
|
|
23
|
+
export declare const useDefaultModelReady: () => boolean;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useQuery as e } from "../../node_modules/@tanstack/react-query/build/modern/useQuery.js";
|
|
2
|
+
import { useActiveBackend as t } from "../../contexts/active-backend-context.js";
|
|
3
|
+
import n from "../../api/config-service/config-service.api.js";
|
|
4
|
+
import { useFreeModelsStore as r } from "../../stores/free-models-store.js";
|
|
5
|
+
import { VERIFIED_MODELS_GC_TIME as i, VERIFIED_MODELS_QUERY_KEY as a, VERIFIED_MODELS_STALE_TIME as o, fetchVerifiedModelsByProvider as s } from "./use-verified-models.js";
|
|
6
|
+
import c from "react";
|
|
7
|
+
//#region src/hooks/query/use-free-models.ts
|
|
8
|
+
var l = "openhands", u = 100, d = 10;
|
|
9
|
+
async function f(e, t, r, i) {
|
|
10
|
+
if (i >= d) throw Error(`Too many pagination requests while fetching OpenHands models (depth=${i})`);
|
|
11
|
+
let a = await n.searchModels({
|
|
12
|
+
provider__eq: l,
|
|
13
|
+
limit: u,
|
|
14
|
+
...t ? { page_id: t } : {}
|
|
15
|
+
}, e);
|
|
16
|
+
if (!a.next_page_id) return a.items;
|
|
17
|
+
if (r.has(a.next_page_id)) throw Error(`Repeated page id while fetching OpenHands models: ${a.next_page_id}`);
|
|
18
|
+
r.add(a.next_page_id);
|
|
19
|
+
let o = await f(e, a.next_page_id, r, i + 1);
|
|
20
|
+
return [...a.items, ...o];
|
|
21
|
+
}
|
|
22
|
+
var p = () => {
|
|
23
|
+
let { backend: n, orgId: r } = t(), c = [
|
|
24
|
+
n.id,
|
|
25
|
+
n.connectionRevision ?? 0,
|
|
26
|
+
r
|
|
27
|
+
];
|
|
28
|
+
return e({
|
|
29
|
+
queryKey: [
|
|
30
|
+
"config",
|
|
31
|
+
"models",
|
|
32
|
+
l,
|
|
33
|
+
"flags",
|
|
34
|
+
...c
|
|
35
|
+
],
|
|
36
|
+
queryFn: async ({ client: e }) => f(await e.fetchQuery({
|
|
37
|
+
queryKey: [...a, ...c],
|
|
38
|
+
queryFn: s,
|
|
39
|
+
staleTime: o
|
|
40
|
+
}), null, /* @__PURE__ */ new Set(), 0),
|
|
41
|
+
staleTime: o,
|
|
42
|
+
gcTime: i
|
|
43
|
+
});
|
|
44
|
+
}, m = () => {
|
|
45
|
+
let { backend: e, orgId: n } = t(), { data: i, isError: a } = p(), o = r((e) => e.setFlags), s = r((e) => e.markDefaultModelReady), u = r((e) => e.resetFlags);
|
|
46
|
+
c.useEffect(() => {
|
|
47
|
+
u();
|
|
48
|
+
}, [
|
|
49
|
+
e.id,
|
|
50
|
+
e.connectionRevision,
|
|
51
|
+
n,
|
|
52
|
+
u
|
|
53
|
+
]), c.useEffect(() => {
|
|
54
|
+
if (!i) return;
|
|
55
|
+
let e = new Set(i.filter((e) => e.free).map((e) => `${l}/${e.name}`)), t = i.find((e) => e.default);
|
|
56
|
+
o({
|
|
57
|
+
freeModels: e,
|
|
58
|
+
defaultModel: t ? `${l}/${t.name}` : null
|
|
59
|
+
});
|
|
60
|
+
}, [i, o]), c.useEffect(() => {
|
|
61
|
+
a && s();
|
|
62
|
+
}, [a, s]);
|
|
63
|
+
}, h = () => r((e) => e.freeModels), g = () => r((e) => e.defaultModel);
|
|
64
|
+
//#endregion
|
|
65
|
+
export { g as useDefaultModel, h as useFreeModels, m as useHydrateFreeModels };
|
|
66
|
+
|
|
67
|
+
//# sourceMappingURL=use-free-models.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-free-models.js","names":[],"sources":["../../../src/hooks/query/use-free-models.ts"],"sourcesContent":["import React from \"react\";\nimport { useQuery } from \"@tanstack/react-query\";\nimport ConfigService from \"#/api/config-service/config-service.api\";\nimport type { LLMModel } from \"#/api/config-service/config-service.types\";\nimport type { FreeModelSet } from \"#/utils/format-model-name\";\nimport { useFreeModelsStore } from \"#/stores/free-models-store\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport {\n VERIFIED_MODELS_GC_TIME,\n VERIFIED_MODELS_QUERY_KEY,\n VERIFIED_MODELS_STALE_TIME,\n fetchVerifiedModelsByProvider,\n} from \"./use-verified-models\";\n\n/**\n * Provider whose models carry free / default metadata. Both are\n * OpenHands-managed concepts, so they are scoped to the `openhands` provider.\n */\nconst OPENHANDS_PROVIDER = \"openhands\";\nconst CLOUD_MODEL_SEARCH_PAGE_LIMIT = 100;\nconst MAX_PAGINATION_DEPTH = 10;\n\nasync function fetchAllOpenHandsModels(\n verifiedByProvider: Record<string, string[]>,\n pageId: string | null | undefined,\n seenPageIds: Set<string>,\n depth: number,\n): Promise<LLMModel[]> {\n if (depth >= MAX_PAGINATION_DEPTH) {\n throw new Error(\n `Too many pagination requests while fetching OpenHands models (depth=${depth})`,\n );\n }\n\n const page = await ConfigService.searchModels(\n {\n provider__eq: OPENHANDS_PROVIDER,\n limit: CLOUD_MODEL_SEARCH_PAGE_LIMIT,\n ...(pageId ? { page_id: pageId } : {}),\n },\n verifiedByProvider,\n );\n\n if (!page.next_page_id) {\n return page.items;\n }\n\n if (seenPageIds.has(page.next_page_id)) {\n throw new Error(\n `Repeated page id while fetching OpenHands models: ${page.next_page_id}`,\n );\n }\n seenPageIds.add(page.next_page_id);\n\n const rest = await fetchAllOpenHandsModels(\n verifiedByProvider,\n page.next_page_id,\n seenPageIds,\n depth + 1,\n );\n return [...page.items, ...rest];\n}\n\n/**\n * Fetches the `openhands` provider's models with their DB-driven `free` /\n * `default` flags (the same channel that carries `verified`).\n */\nconst useOpenHandsModels = () => {\n const { backend, orgId } = useActiveBackend();\n const backendScope = [\n backend.id,\n backend.connectionRevision ?? 0,\n orgId,\n ] as const;\n\n return useQuery({\n queryKey: [\n \"config\",\n \"models\",\n OPENHANDS_PROVIDER,\n \"flags\",\n ...backendScope,\n ],\n queryFn: async ({ client }): Promise<LLMModel[]> => {\n const verifiedByProvider = await client.fetchQuery({\n queryKey: [...VERIFIED_MODELS_QUERY_KEY, ...backendScope],\n queryFn: fetchVerifiedModelsByProvider,\n staleTime: VERIFIED_MODELS_STALE_TIME,\n });\n return fetchAllOpenHandsModels(verifiedByProvider, null, new Set(), 0);\n },\n staleTime: VERIFIED_MODELS_STALE_TIME,\n gcTime: VERIFIED_MODELS_GC_TIME,\n });\n};\n\n/**\n * Fetches the DB-driven free / default flags once and mirrors them into the\n * {@link useFreeModelsStore}. Mount this high in the tree (inside the query\n * provider). Leaf display components then read the flags synchronously via the\n * {@link useFreeModels} / {@link useDefaultModel} zustand selectors, so they\n * stay renderable in isolation without a QueryClientProvider in scope.\n */\nexport const useHydrateFreeModels = (): void => {\n const { backend, orgId } = useActiveBackend();\n const { data, isError } = useOpenHandsModels();\n const setFlags = useFreeModelsStore((state) => state.setFlags);\n const markDefaultModelReady = useFreeModelsStore(\n (state) => state.markDefaultModelReady,\n );\n const resetFlags = useFreeModelsStore((state) => state.resetFlags);\n\n React.useEffect(() => {\n resetFlags();\n }, [backend.id, backend.connectionRevision, orgId, resetFlags]);\n\n React.useEffect(() => {\n if (!data) return;\n const freeModels = new Set(\n data\n .filter((model) => model.free)\n .map((model) => `${OPENHANDS_PROVIDER}/${model.name}`),\n );\n const defaultEntry = data.find((model) => model.default);\n setFlags({\n freeModels,\n defaultModel: defaultEntry\n ? `${OPENHANDS_PROVIDER}/${defaultEntry.name}`\n : null,\n });\n }, [data, setFlags]);\n\n React.useEffect(() => {\n if (isError) markDefaultModelReady();\n }, [isError, markDefaultModelReady]);\n};\n\n/**\n * Set of free ``openhands/<model>`` ids, sourced from the backend model list\n * (DB-driven on cloud via the `free` flag). Returns an empty set on backends\n * without free metadata (e.g. the local agent-server), so callers uniformly\n * treat \"not in set\" as paid and the frontend keeps no hardcoded free list.\n */\nexport const useFreeModels = (): FreeModelSet =>\n useFreeModelsStore((state) => state.freeModels);\n\n/**\n * DB-driven default OpenHands model id (``openhands/<model>``), used to\n * preselect a model on onboarding and when creating a new OpenHands model.\n * Returns `null` on backends without default metadata.\n */\nexport const useDefaultModel = (): string | null =>\n useFreeModelsStore((state) => state.defaultModel);\n\nexport const useDefaultModelReady = (): boolean =>\n useFreeModelsStore((state) => state.defaultModelReady);\n"],"mappings":";;;;;;;AAkBA,IAAM,IAAqB,aACrB,IAAgC,KAChC,IAAuB;AAE7B,eAAe,EACb,GACA,GACA,GACA,GACqB;CACrB,IAAI,KAAS,GACX,MAAU,MACR,uEAAuE,EAAM,EAC/E;CAGF,IAAM,IAAO,MAAM,EAAc,aAC/B;EACE,cAAc;EACd,OAAO;EACP,GAAI,IAAS,EAAE,SAAS,EAAO,IAAI,CAAC;CACtC,GACA,CACF;CAEA,IAAI,CAAC,EAAK,cACR,OAAO,EAAK;CAGd,IAAI,EAAY,IAAI,EAAK,YAAY,GACnC,MAAU,MACR,qDAAqD,EAAK,cAC5D;CAEF,EAAY,IAAI,EAAK,YAAY;CAEjC,IAAM,IAAO,MAAM,EACjB,GACA,EAAK,cACL,GACA,IAAQ,CACV;CACA,OAAO,CAAC,GAAG,EAAK,OAAO,GAAG,CAAI;AAChC;AAMA,IAAM,UAA2B;CAC/B,IAAM,EAAE,YAAS,aAAU,EAAiB,GACtC,IAAe;EACnB,EAAQ;EACR,EAAQ,sBAAsB;EAC9B;CACF;CAEA,OAAO,EAAS;EACd,UAAU;GACR;GACA;GACA;GACA;GACA,GAAG;EACL;EACA,SAAS,OAAO,EAAE,gBAMT,EAAwB,MALE,EAAO,WAAW;GACjD,UAAU,CAAC,GAAG,GAA2B,GAAG,CAAY;GACxD,SAAS;GACT,WAAW;EACb,CAAC,GACkD,sBAAM,IAAI,IAAI,GAAG,CAAC;EAEvE,WAAW;EACX,QAAQ;CACV,CAAC;AACH,GASa,UAAmC;CAC9C,IAAM,EAAE,YAAS,aAAU,EAAiB,GACtC,EAAE,SAAM,eAAY,EAAmB,GACvC,IAAW,GAAoB,MAAU,EAAM,QAAQ,GACvD,IAAwB,GAC3B,MAAU,EAAM,qBACnB,GACM,IAAa,GAAoB,MAAU,EAAM,UAAU;CAsBjE,AApBA,EAAM,gBAAgB;EACpB,EAAW;CACb,GAAG;EAAC,EAAQ;EAAI,EAAQ;EAAoB;EAAO;CAAU,CAAC,GAE9D,EAAM,gBAAgB;EACpB,IAAI,CAAC,GAAM;EACX,IAAM,IAAa,IAAI,IACrB,EACG,QAAQ,MAAU,EAAM,IAAI,EAC5B,KAAK,MAAU,GAAG,EAAmB,GAAG,EAAM,MAAM,CACzD,GACM,IAAe,EAAK,MAAM,MAAU,EAAM,OAAO;EACvD,EAAS;GACP;GACA,cAAc,IACV,GAAG,EAAmB,GAAG,EAAa,SACtC;EACN,CAAC;CACH,GAAG,CAAC,GAAM,CAAQ,CAAC,GAEnB,EAAM,gBAAgB;EACpB,AAAI,KAAS,EAAsB;CACrC,GAAG,CAAC,GAAS,CAAqB,CAAC;AACrC,GAQa,UACX,GAAoB,MAAU,EAAM,UAAU,GAOnC,UACX,GAAoB,MAAU,EAAM,YAAY"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-local-git-info.cjs","names":[],"sources":["../../../src/hooks/query/use-local-git-info.ts"],"sourcesContent":["import { useQuery } from \"@tanstack/react-query\";\nimport { useRef } from \"react\";\n\nimport type { CommandResult } from \"#/api/runtime-service/agent-server-runtime-service\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport { useActiveConversation } from \"#/hooks/query/use-active-conversation\";\nimport { useRuntimeIsReady } from \"#/hooks/use-runtime-is-ready\";\nimport { useBashCommandRunner } from \"#/hooks/use-bash-command-runner\";\nimport { Provider } from \"#/types/settings\";\nimport { parseGitRemoteUrl } from \"#/utils/parse-git-remote-url\";\n\nexport interface LocalGitInfo {\n repository: string | null;\n branch: string | null;\n provider: Provider | null;\n remoteUrl: string | null;\n}\n\nconst EMPTY_LOCAL_GIT_INFO: LocalGitInfo = {\n repository: null,\n branch: null,\n provider: null,\n remoteUrl: null,\n};\n\ntype RunCommand = (\n command: string,\n cwd: string,\n timeout: number,\n) => Promise<CommandResult>;\n\n// Single shell script that replaces the former probeGitInfoAtDir +\n// probeNestedRepoInDir pair. It runs as one bash WebSocket round-trip:\n// 1. Read the origin remote URL and current branch at the workspace root.\n// 2. If neither is set, search for exactly one nested git repo up to 4\n// levels deep and repeat the probe there.\n// Output: two lines — <remote-url>\\n<branch> — either may be empty.\nconst GIT_INFO_COMMAND = [\n \"r=$(git remote get-url origin 2>/dev/null)\",\n \"b=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)\",\n 'if [ -z \"$r$b\" ]; then',\n \"n=$(find . -mindepth 2 -maxdepth 4 -name .git 2>/dev/null | cut -c3- | sed 's|/.git$||' | sort -u)\",\n \"c=$(printf '%s\\\\n' \\\"$n\\\" | grep -c '[^[:space:]]')\",\n 'if [ \"$c\" = \"1\" ] && [ -n \"$n\" ]; then',\n 'r=$(git -C \"$n\" remote get-url origin 2>/dev/null)',\n 'b=$(git -C \"$n\" rev-parse --abbrev-ref HEAD 2>/dev/null)',\n \"fi\",\n \"fi\",\n 'printf \\'%s\\\\n%s\\' \"$r\" \"$b\"',\n].join(\"\\n\");\n\nasync function probeGitInfo(\n run: RunCommand,\n directory: string,\n): Promise<LocalGitInfo> {\n const result = await run(GIT_INFO_COMMAND, directory, 10);\n if (result.exit_code !== 0) return EMPTY_LOCAL_GIT_INFO;\n\n const nl = result.stdout.indexOf(\"\\n\");\n const remoteUrl = (\n nl >= 0 ? result.stdout.slice(0, nl) : result.stdout\n ).trim();\n const rawBranch = (nl >= 0 ? result.stdout.slice(nl + 1) : \"\").trim();\n const branch = rawBranch && rawBranch !== \"HEAD\" ? rawBranch : null;\n\n if (!remoteUrl && !branch) return EMPTY_LOCAL_GIT_INFO;\n\n const parsedRemote = parseGitRemoteUrl(remoteUrl);\n return {\n repository: parsedRemote?.repository ?? null,\n provider: parsedRemote?.provider ?? null,\n remoteUrl: remoteUrl || null,\n branch,\n };\n}\n\n/**\n * Probe git metadata for a **local** backend's workspace checkout by\n * shelling out via the agent server using a single consolidated bash\n * script (see `GIT_INFO_COMMAND`).\n *\n * Local-only by design. On cloud backends the conversation metadata\n * (`selected_repository`, `git_provider`, `selected_branch`) is the\n * source of truth, and probing via `/api/bash/execute_bash_command`\n * would (a) leak the user's local `getAgentServerWorkingDir()` path to\n * the cloud runtime when `workspace.working_dir` is missing, and\n * (b) hit a bash endpoint we don't want the frontend driving on cloud.\n *\n * On local, we keep the probe enabled until the active conversation\n * has a complete repo tuple so the control bar can recover from\n * partial metadata hydration after connect/clone flows.\n *\n * Returns `null` fields when the working dir is not a git checkout —\n * callers should treat that the same as \"no repo detected\".\n */\nexport const useLocalGitInfo = () => {\n const { data: conversation } = useActiveConversation();\n const runtimeIsReady = useRuntimeIsReady();\n const { backend } = useActiveBackend();\n const isLocalBackend = backend.kind === \"local\";\n\n const conversationId = conversation?.id;\n const conversationUrl = conversation?.conversation_url;\n const sessionApiKey = conversation?.session_api_key;\n const workingDir = conversation?.workspace?.working_dir?.trim();\n const hasConversationRepo = !!conversation?.selected_repository;\n const hasConversationProvider = !!conversation?.git_provider;\n const hasConversationBranch = !!conversation?.selected_branch;\n\n const queryEnabled =\n isLocalBackend &&\n runtimeIsReady &&\n !!conversationId &&\n !!workingDir &&\n (!hasConversationRepo ||\n !hasConversationProvider ||\n !hasConversationBranch);\n\n // Persistent WebSocket connection to the bash-events endpoint. The\n // connection is opened when the query is enabled and closed on unmount or\n // when the conversation changes.\n const runCommand = useBashCommandRunner(\n conversationUrl,\n sessionApiKey,\n queryEnabled,\n );\n\n // Keep a ref so queryFn can call the latest runner without capturing it\n // as a queryKey dependency (runCommand is stable but the linter can't\n // infer that).\n const runCommandRef = useRef(runCommand);\n runCommandRef.current = runCommand;\n\n // runCommandRef is a ref (always stable); the linter cannot infer this so\n // we disable the exhaustive-deps check here.\n
|
|
1
|
+
{"version":3,"file":"use-local-git-info.cjs","names":[],"sources":["../../../src/hooks/query/use-local-git-info.ts"],"sourcesContent":["import { useQuery } from \"@tanstack/react-query\";\nimport { useRef } from \"react\";\n\nimport type { CommandResult } from \"#/api/runtime-service/agent-server-runtime-service\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport { useActiveConversation } from \"#/hooks/query/use-active-conversation\";\nimport { useRuntimeIsReady } from \"#/hooks/use-runtime-is-ready\";\nimport { useBashCommandRunner } from \"#/hooks/use-bash-command-runner\";\nimport { Provider } from \"#/types/settings\";\nimport { parseGitRemoteUrl } from \"#/utils/parse-git-remote-url\";\n\nexport interface LocalGitInfo {\n repository: string | null;\n branch: string | null;\n provider: Provider | null;\n remoteUrl: string | null;\n}\n\nconst EMPTY_LOCAL_GIT_INFO: LocalGitInfo = {\n repository: null,\n branch: null,\n provider: null,\n remoteUrl: null,\n};\n\ntype RunCommand = (\n command: string,\n cwd: string,\n timeout: number,\n) => Promise<CommandResult>;\n\n// Single shell script that replaces the former probeGitInfoAtDir +\n// probeNestedRepoInDir pair. It runs as one bash WebSocket round-trip:\n// 1. Read the origin remote URL and current branch at the workspace root.\n// 2. If neither is set, search for exactly one nested git repo up to 4\n// levels deep and repeat the probe there.\n// Output: two lines — <remote-url>\\n<branch> — either may be empty.\nconst GIT_INFO_COMMAND = [\n \"r=$(git remote get-url origin 2>/dev/null)\",\n \"b=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)\",\n 'if [ -z \"$r$b\" ]; then',\n \"n=$(find . -mindepth 2 -maxdepth 4 -name .git 2>/dev/null | cut -c3- | sed 's|/.git$||' | sort -u)\",\n \"c=$(printf '%s\\\\n' \\\"$n\\\" | grep -c '[^[:space:]]')\",\n 'if [ \"$c\" = \"1\" ] && [ -n \"$n\" ]; then',\n 'r=$(git -C \"$n\" remote get-url origin 2>/dev/null)',\n 'b=$(git -C \"$n\" rev-parse --abbrev-ref HEAD 2>/dev/null)',\n \"fi\",\n \"fi\",\n 'printf \\'%s\\\\n%s\\' \"$r\" \"$b\"',\n].join(\"\\n\");\n\nasync function probeGitInfo(\n run: RunCommand,\n directory: string,\n): Promise<LocalGitInfo> {\n const result = await run(GIT_INFO_COMMAND, directory, 10);\n if (result.exit_code !== 0) return EMPTY_LOCAL_GIT_INFO;\n\n const nl = result.stdout.indexOf(\"\\n\");\n const remoteUrl = (\n nl >= 0 ? result.stdout.slice(0, nl) : result.stdout\n ).trim();\n const rawBranch = (nl >= 0 ? result.stdout.slice(nl + 1) : \"\").trim();\n const branch = rawBranch && rawBranch !== \"HEAD\" ? rawBranch : null;\n\n if (!remoteUrl && !branch) return EMPTY_LOCAL_GIT_INFO;\n\n const parsedRemote = parseGitRemoteUrl(remoteUrl);\n return {\n repository: parsedRemote?.repository ?? null,\n provider: parsedRemote?.provider ?? null,\n remoteUrl: remoteUrl || null,\n branch,\n };\n}\n\n/**\n * Probe git metadata for a **local** backend's workspace checkout by\n * shelling out via the agent server using a single consolidated bash\n * script (see `GIT_INFO_COMMAND`).\n *\n * Local-only by design. On cloud backends the conversation metadata\n * (`selected_repository`, `git_provider`, `selected_branch`) is the\n * source of truth, and probing via `/api/bash/execute_bash_command`\n * would (a) leak the user's local `getAgentServerWorkingDir()` path to\n * the cloud runtime when `workspace.working_dir` is missing, and\n * (b) hit a bash endpoint we don't want the frontend driving on cloud.\n *\n * On local, we keep the probe enabled until the active conversation\n * has a complete repo tuple so the control bar can recover from\n * partial metadata hydration after connect/clone flows.\n *\n * Returns `null` fields when the working dir is not a git checkout —\n * callers should treat that the same as \"no repo detected\".\n */\nexport const useLocalGitInfo = () => {\n const { data: conversation } = useActiveConversation();\n const runtimeIsReady = useRuntimeIsReady();\n const { backend } = useActiveBackend();\n const isLocalBackend = backend.kind === \"local\";\n\n const conversationId = conversation?.id;\n const conversationUrl = conversation?.conversation_url;\n const sessionApiKey = conversation?.session_api_key;\n const workingDir = conversation?.workspace?.working_dir?.trim();\n const hasConversationRepo = !!conversation?.selected_repository;\n const hasConversationProvider = !!conversation?.git_provider;\n const hasConversationBranch = !!conversation?.selected_branch;\n\n const queryEnabled =\n isLocalBackend &&\n runtimeIsReady &&\n !!conversationId &&\n !!workingDir &&\n (!hasConversationRepo ||\n !hasConversationProvider ||\n !hasConversationBranch);\n\n // Persistent WebSocket connection to the bash-events endpoint. The\n // connection is opened when the query is enabled and closed on unmount or\n // when the conversation changes.\n const runCommand = useBashCommandRunner(\n conversationUrl,\n sessionApiKey,\n queryEnabled,\n );\n\n // Keep a ref so queryFn can call the latest runner without capturing it\n // as a queryKey dependency (runCommand is stable but the linter can't\n // infer that).\n const runCommandRef = useRef(runCommand);\n runCommandRef.current = runCommand;\n\n // runCommandRef is a ref (always stable); the linter cannot infer this so\n // we disable the exhaustive-deps check here.\n\n return useQuery<LocalGitInfo>({\n queryKey: [\n \"local-git-info\",\n conversationId,\n conversationUrl,\n sessionApiKey,\n workingDir,\n ],\n queryFn: async () => {\n const run: RunCommand = (command, cwd, timeout) =>\n runCommandRef.current(command, cwd, timeout);\n // workingDir is guaranteed non-empty by the queryEnabled guard above.\n return probeGitInfo(run, workingDir!);\n },\n enabled: queryEnabled,\n retry: false,\n // Re-probe the workspace every 10s so the UI reflects branch/repo\n // changes (e.g. `git checkout`, adding a remote) without requiring a\n // manual refresh when there is no `selected_repository` recorded on\n // the conversation. Commands now run over the persistent WebSocket\n // connection rather than individual REST calls.\n staleTime: 10_000,\n refetchInterval: 10_000,\n gcTime: 1000 * 60 * 5,\n meta: { disableToast: true },\n });\n};\n"],"mappings":"sYAkBA,IAAM,EAAqC,CACzC,WAAY,KACZ,OAAQ,KACR,SAAU,KACV,UAAW,IACb,EAcM,EAAmB,CACvB,6CACA,mDACA,yBACA,qGACA,oDACA,yCACA,qDACA,2DACA,KACA,KACA,4BACF,EAAE,KAAK;CAAI,EAEX,eAAe,EACb,EACA,EACuB,CACvB,IAAM,EAAS,MAAM,EAAI,EAAkB,EAAW,EAAE,EACxD,GAAI,EAAO,YAAc,EAAG,OAAO,EAEnC,IAAM,EAAK,EAAO,OAAO,QAAQ;CAAI,EAC/B,GACJ,GAAM,EAAI,EAAO,OAAO,MAAM,EAAG,CAAE,EAAI,EAAO,QAC9C,KAAK,EACD,GAAa,GAAM,EAAI,EAAO,OAAO,MAAM,EAAK,CAAC,EAAI,IAAI,KAAK,EAC9D,EAAS,GAAa,IAAc,OAAS,EAAY,KAE/D,GAAI,CAAC,GAAa,CAAC,EAAQ,OAAO,EAElC,IAAM,EAAe,EAAA,kBAAkB,CAAS,EAChD,MAAO,CACL,WAAY,GAAc,YAAc,KACxC,SAAU,GAAc,UAAY,KACpC,UAAW,GAAa,KACxB,QACF,CACF,CAqBA,IAAa,MAAwB,CACnC,GAAM,CAAE,KAAM,GAAiB,EAAA,sBAAsB,EAC/C,EAAiB,EAAA,kBAAkB,EACnC,CAAE,WAAY,EAAA,iBAAiB,EAC/B,EAAiB,EAAQ,OAAS,QAElC,EAAiB,GAAc,GAC/B,EAAkB,GAAc,iBAChC,EAAgB,GAAc,gBAC9B,EAAa,GAAc,WAAW,aAAa,KAAK,EACxD,EAAsB,CAAC,CAAC,GAAc,oBACtC,EAA0B,CAAC,CAAC,GAAc,aAC1C,EAAwB,CAAC,CAAC,GAAc,gBAExC,EACJ,GACA,GACA,CAAC,CAAC,GACF,CAAC,CAAC,IACD,CAAC,GACA,CAAC,GACD,CAAC,GAKC,EAAa,EAAA,qBACjB,EACA,EACA,CACF,EAKM,GAAA,EAAA,EAAA,QAAuB,CAAU,EAMvC,MALA,GAAc,QAAU,EAKjB,EAAA,SAAuB,CAC5B,SAAU,CACR,iBACA,EACA,EACA,EACA,CACF,EACA,QAAS,SAIA,GAHkB,EAAS,EAAK,IACrC,EAAc,QAAQ,EAAS,EAAK,CAAO,EAEpB,CAAW,EAEtC,QAAS,EACT,MAAO,GAMP,UAAW,IACX,gBAAiB,IACjB,OAAQ,IAAO,GAAK,EACpB,KAAM,CAAE,aAAc,EAAK,CAC7B,CAAC,CACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-local-git-info.js","names":[],"sources":["../../../src/hooks/query/use-local-git-info.ts"],"sourcesContent":["import { useQuery } from \"@tanstack/react-query\";\nimport { useRef } from \"react\";\n\nimport type { CommandResult } from \"#/api/runtime-service/agent-server-runtime-service\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport { useActiveConversation } from \"#/hooks/query/use-active-conversation\";\nimport { useRuntimeIsReady } from \"#/hooks/use-runtime-is-ready\";\nimport { useBashCommandRunner } from \"#/hooks/use-bash-command-runner\";\nimport { Provider } from \"#/types/settings\";\nimport { parseGitRemoteUrl } from \"#/utils/parse-git-remote-url\";\n\nexport interface LocalGitInfo {\n repository: string | null;\n branch: string | null;\n provider: Provider | null;\n remoteUrl: string | null;\n}\n\nconst EMPTY_LOCAL_GIT_INFO: LocalGitInfo = {\n repository: null,\n branch: null,\n provider: null,\n remoteUrl: null,\n};\n\ntype RunCommand = (\n command: string,\n cwd: string,\n timeout: number,\n) => Promise<CommandResult>;\n\n// Single shell script that replaces the former probeGitInfoAtDir +\n// probeNestedRepoInDir pair. It runs as one bash WebSocket round-trip:\n// 1. Read the origin remote URL and current branch at the workspace root.\n// 2. If neither is set, search for exactly one nested git repo up to 4\n// levels deep and repeat the probe there.\n// Output: two lines — <remote-url>\\n<branch> — either may be empty.\nconst GIT_INFO_COMMAND = [\n \"r=$(git remote get-url origin 2>/dev/null)\",\n \"b=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)\",\n 'if [ -z \"$r$b\" ]; then',\n \"n=$(find . -mindepth 2 -maxdepth 4 -name .git 2>/dev/null | cut -c3- | sed 's|/.git$||' | sort -u)\",\n \"c=$(printf '%s\\\\n' \\\"$n\\\" | grep -c '[^[:space:]]')\",\n 'if [ \"$c\" = \"1\" ] && [ -n \"$n\" ]; then',\n 'r=$(git -C \"$n\" remote get-url origin 2>/dev/null)',\n 'b=$(git -C \"$n\" rev-parse --abbrev-ref HEAD 2>/dev/null)',\n \"fi\",\n \"fi\",\n 'printf \\'%s\\\\n%s\\' \"$r\" \"$b\"',\n].join(\"\\n\");\n\nasync function probeGitInfo(\n run: RunCommand,\n directory: string,\n): Promise<LocalGitInfo> {\n const result = await run(GIT_INFO_COMMAND, directory, 10);\n if (result.exit_code !== 0) return EMPTY_LOCAL_GIT_INFO;\n\n const nl = result.stdout.indexOf(\"\\n\");\n const remoteUrl = (\n nl >= 0 ? result.stdout.slice(0, nl) : result.stdout\n ).trim();\n const rawBranch = (nl >= 0 ? result.stdout.slice(nl + 1) : \"\").trim();\n const branch = rawBranch && rawBranch !== \"HEAD\" ? rawBranch : null;\n\n if (!remoteUrl && !branch) return EMPTY_LOCAL_GIT_INFO;\n\n const parsedRemote = parseGitRemoteUrl(remoteUrl);\n return {\n repository: parsedRemote?.repository ?? null,\n provider: parsedRemote?.provider ?? null,\n remoteUrl: remoteUrl || null,\n branch,\n };\n}\n\n/**\n * Probe git metadata for a **local** backend's workspace checkout by\n * shelling out via the agent server using a single consolidated bash\n * script (see `GIT_INFO_COMMAND`).\n *\n * Local-only by design. On cloud backends the conversation metadata\n * (`selected_repository`, `git_provider`, `selected_branch`) is the\n * source of truth, and probing via `/api/bash/execute_bash_command`\n * would (a) leak the user's local `getAgentServerWorkingDir()` path to\n * the cloud runtime when `workspace.working_dir` is missing, and\n * (b) hit a bash endpoint we don't want the frontend driving on cloud.\n *\n * On local, we keep the probe enabled until the active conversation\n * has a complete repo tuple so the control bar can recover from\n * partial metadata hydration after connect/clone flows.\n *\n * Returns `null` fields when the working dir is not a git checkout —\n * callers should treat that the same as \"no repo detected\".\n */\nexport const useLocalGitInfo = () => {\n const { data: conversation } = useActiveConversation();\n const runtimeIsReady = useRuntimeIsReady();\n const { backend } = useActiveBackend();\n const isLocalBackend = backend.kind === \"local\";\n\n const conversationId = conversation?.id;\n const conversationUrl = conversation?.conversation_url;\n const sessionApiKey = conversation?.session_api_key;\n const workingDir = conversation?.workspace?.working_dir?.trim();\n const hasConversationRepo = !!conversation?.selected_repository;\n const hasConversationProvider = !!conversation?.git_provider;\n const hasConversationBranch = !!conversation?.selected_branch;\n\n const queryEnabled =\n isLocalBackend &&\n runtimeIsReady &&\n !!conversationId &&\n !!workingDir &&\n (!hasConversationRepo ||\n !hasConversationProvider ||\n !hasConversationBranch);\n\n // Persistent WebSocket connection to the bash-events endpoint. The\n // connection is opened when the query is enabled and closed on unmount or\n // when the conversation changes.\n const runCommand = useBashCommandRunner(\n conversationUrl,\n sessionApiKey,\n queryEnabled,\n );\n\n // Keep a ref so queryFn can call the latest runner without capturing it\n // as a queryKey dependency (runCommand is stable but the linter can't\n // infer that).\n const runCommandRef = useRef(runCommand);\n runCommandRef.current = runCommand;\n\n // runCommandRef is a ref (always stable); the linter cannot infer this so\n // we disable the exhaustive-deps check here.\n
|
|
1
|
+
{"version":3,"file":"use-local-git-info.js","names":[],"sources":["../../../src/hooks/query/use-local-git-info.ts"],"sourcesContent":["import { useQuery } from \"@tanstack/react-query\";\nimport { useRef } from \"react\";\n\nimport type { CommandResult } from \"#/api/runtime-service/agent-server-runtime-service\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport { useActiveConversation } from \"#/hooks/query/use-active-conversation\";\nimport { useRuntimeIsReady } from \"#/hooks/use-runtime-is-ready\";\nimport { useBashCommandRunner } from \"#/hooks/use-bash-command-runner\";\nimport { Provider } from \"#/types/settings\";\nimport { parseGitRemoteUrl } from \"#/utils/parse-git-remote-url\";\n\nexport interface LocalGitInfo {\n repository: string | null;\n branch: string | null;\n provider: Provider | null;\n remoteUrl: string | null;\n}\n\nconst EMPTY_LOCAL_GIT_INFO: LocalGitInfo = {\n repository: null,\n branch: null,\n provider: null,\n remoteUrl: null,\n};\n\ntype RunCommand = (\n command: string,\n cwd: string,\n timeout: number,\n) => Promise<CommandResult>;\n\n// Single shell script that replaces the former probeGitInfoAtDir +\n// probeNestedRepoInDir pair. It runs as one bash WebSocket round-trip:\n// 1. Read the origin remote URL and current branch at the workspace root.\n// 2. If neither is set, search for exactly one nested git repo up to 4\n// levels deep and repeat the probe there.\n// Output: two lines — <remote-url>\\n<branch> — either may be empty.\nconst GIT_INFO_COMMAND = [\n \"r=$(git remote get-url origin 2>/dev/null)\",\n \"b=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)\",\n 'if [ -z \"$r$b\" ]; then',\n \"n=$(find . -mindepth 2 -maxdepth 4 -name .git 2>/dev/null | cut -c3- | sed 's|/.git$||' | sort -u)\",\n \"c=$(printf '%s\\\\n' \\\"$n\\\" | grep -c '[^[:space:]]')\",\n 'if [ \"$c\" = \"1\" ] && [ -n \"$n\" ]; then',\n 'r=$(git -C \"$n\" remote get-url origin 2>/dev/null)',\n 'b=$(git -C \"$n\" rev-parse --abbrev-ref HEAD 2>/dev/null)',\n \"fi\",\n \"fi\",\n 'printf \\'%s\\\\n%s\\' \"$r\" \"$b\"',\n].join(\"\\n\");\n\nasync function probeGitInfo(\n run: RunCommand,\n directory: string,\n): Promise<LocalGitInfo> {\n const result = await run(GIT_INFO_COMMAND, directory, 10);\n if (result.exit_code !== 0) return EMPTY_LOCAL_GIT_INFO;\n\n const nl = result.stdout.indexOf(\"\\n\");\n const remoteUrl = (\n nl >= 0 ? result.stdout.slice(0, nl) : result.stdout\n ).trim();\n const rawBranch = (nl >= 0 ? result.stdout.slice(nl + 1) : \"\").trim();\n const branch = rawBranch && rawBranch !== \"HEAD\" ? rawBranch : null;\n\n if (!remoteUrl && !branch) return EMPTY_LOCAL_GIT_INFO;\n\n const parsedRemote = parseGitRemoteUrl(remoteUrl);\n return {\n repository: parsedRemote?.repository ?? null,\n provider: parsedRemote?.provider ?? null,\n remoteUrl: remoteUrl || null,\n branch,\n };\n}\n\n/**\n * Probe git metadata for a **local** backend's workspace checkout by\n * shelling out via the agent server using a single consolidated bash\n * script (see `GIT_INFO_COMMAND`).\n *\n * Local-only by design. On cloud backends the conversation metadata\n * (`selected_repository`, `git_provider`, `selected_branch`) is the\n * source of truth, and probing via `/api/bash/execute_bash_command`\n * would (a) leak the user's local `getAgentServerWorkingDir()` path to\n * the cloud runtime when `workspace.working_dir` is missing, and\n * (b) hit a bash endpoint we don't want the frontend driving on cloud.\n *\n * On local, we keep the probe enabled until the active conversation\n * has a complete repo tuple so the control bar can recover from\n * partial metadata hydration after connect/clone flows.\n *\n * Returns `null` fields when the working dir is not a git checkout —\n * callers should treat that the same as \"no repo detected\".\n */\nexport const useLocalGitInfo = () => {\n const { data: conversation } = useActiveConversation();\n const runtimeIsReady = useRuntimeIsReady();\n const { backend } = useActiveBackend();\n const isLocalBackend = backend.kind === \"local\";\n\n const conversationId = conversation?.id;\n const conversationUrl = conversation?.conversation_url;\n const sessionApiKey = conversation?.session_api_key;\n const workingDir = conversation?.workspace?.working_dir?.trim();\n const hasConversationRepo = !!conversation?.selected_repository;\n const hasConversationProvider = !!conversation?.git_provider;\n const hasConversationBranch = !!conversation?.selected_branch;\n\n const queryEnabled =\n isLocalBackend &&\n runtimeIsReady &&\n !!conversationId &&\n !!workingDir &&\n (!hasConversationRepo ||\n !hasConversationProvider ||\n !hasConversationBranch);\n\n // Persistent WebSocket connection to the bash-events endpoint. The\n // connection is opened when the query is enabled and closed on unmount or\n // when the conversation changes.\n const runCommand = useBashCommandRunner(\n conversationUrl,\n sessionApiKey,\n queryEnabled,\n );\n\n // Keep a ref so queryFn can call the latest runner without capturing it\n // as a queryKey dependency (runCommand is stable but the linter can't\n // infer that).\n const runCommandRef = useRef(runCommand);\n runCommandRef.current = runCommand;\n\n // runCommandRef is a ref (always stable); the linter cannot infer this so\n // we disable the exhaustive-deps check here.\n\n return useQuery<LocalGitInfo>({\n queryKey: [\n \"local-git-info\",\n conversationId,\n conversationUrl,\n sessionApiKey,\n workingDir,\n ],\n queryFn: async () => {\n const run: RunCommand = (command, cwd, timeout) =>\n runCommandRef.current(command, cwd, timeout);\n // workingDir is guaranteed non-empty by the queryEnabled guard above.\n return probeGitInfo(run, workingDir!);\n },\n enabled: queryEnabled,\n retry: false,\n // Re-probe the workspace every 10s so the UI reflects branch/repo\n // changes (e.g. `git checkout`, adding a remote) without requiring a\n // manual refresh when there is no `selected_repository` recorded on\n // the conversation. Commands now run over the persistent WebSocket\n // connection rather than individual REST calls.\n staleTime: 10_000,\n refetchInterval: 10_000,\n gcTime: 1000 * 60 * 5,\n meta: { disableToast: true },\n });\n};\n"],"mappings":";;;;;;;;AAkBA,IAAM,IAAqC;CACzC,YAAY;CACZ,QAAQ;CACR,UAAU;CACV,WAAW;AACb,GAcM,IAAmB;CACvB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,EAAE,KAAK,IAAI;AAEX,eAAe,EACb,GACA,GACuB;CACvB,IAAM,IAAS,MAAM,EAAI,GAAkB,GAAW,EAAE;CACxD,IAAI,EAAO,cAAc,GAAG,OAAO;CAEnC,IAAM,IAAK,EAAO,OAAO,QAAQ,IAAI,GAC/B,KACJ,KAAM,IAAI,EAAO,OAAO,MAAM,GAAG,CAAE,IAAI,EAAO,QAC9C,KAAK,GACD,KAAa,KAAM,IAAI,EAAO,OAAO,MAAM,IAAK,CAAC,IAAI,IAAI,KAAK,GAC9D,IAAS,KAAa,MAAc,SAAS,IAAY;CAE/D,IAAI,CAAC,KAAa,CAAC,GAAQ,OAAO;CAElC,IAAM,IAAe,EAAkB,CAAS;CAChD,OAAO;EACL,YAAY,GAAc,cAAc;EACxC,UAAU,GAAc,YAAY;EACpC,WAAW,KAAa;EACxB;CACF;AACF;AAqBA,IAAa,UAAwB;CACnC,IAAM,EAAE,MAAM,MAAiB,EAAsB,GAC/C,IAAiB,EAAkB,GACnC,EAAE,eAAY,EAAiB,GAC/B,IAAiB,EAAQ,SAAS,SAElC,IAAiB,GAAc,IAC/B,IAAkB,GAAc,kBAChC,IAAgB,GAAc,iBAC9B,IAAa,GAAc,WAAW,aAAa,KAAK,GACxD,IAAsB,CAAC,CAAC,GAAc,qBACtC,IAA0B,CAAC,CAAC,GAAc,cAC1C,IAAwB,CAAC,CAAC,GAAc,iBAExC,IACJ,KACA,KACA,CAAC,CAAC,KACF,CAAC,CAAC,MACD,CAAC,KACA,CAAC,KACD,CAAC,IAKC,IAAa,EACjB,GACA,GACA,CACF,GAKM,IAAgB,EAAO,CAAU;CAMvC,OALA,EAAc,UAAU,GAKjB,EAAuB;EAC5B,UAAU;GACR;GACA;GACA;GACA;GACA;EACF;EACA,SAAS,YAIA,GAHkB,GAAS,GAAK,MACrC,EAAc,QAAQ,GAAS,GAAK,CAAO,GAEpB,CAAW;EAEtC,SAAS;EACT,OAAO;EAMP,WAAW;EACX,iBAAiB;EACjB,QAAQ,MAAO,KAAK;EACpB,MAAM,EAAE,cAAc,GAAK;CAC7B,CAAC;AACH"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require("../../_virtual/_rolldown/runtime.cjs");const e=require("../../node_modules/@tanstack/react-query/build/modern/useQuery.cjs"),t=require("../../api/config-service/config-service.api.cjs"),
|
|
1
|
+
require("../../_virtual/_rolldown/runtime.cjs");const e=require("../../node_modules/@tanstack/react-query/build/modern/useQuery.cjs"),t=require("../../contexts/active-backend-context.cjs"),n=require("../../api/config-service/config-service.api.cjs"),r=require("./use-verified-models.cjs");var i=10;async function a(e,t,r,o=0){if(o>=i)throw Error(`Too many pagination requests for provider ${e}`);let s=await n.default.searchModels({provider__eq:e,limit:100,page_id:r},t);if(s.next_page_id){let n=await a(e,t,s.next_page_id,o+1);return[...s.items,...n]}return s.items}var o=n=>{let{backend:i,orgId:o}=t.useActiveBackend(),s=[i.id,i.connectionRevision??0,o];return e.useQuery({queryKey:[`config`,`models`,n,...s],queryFn:async({client:e})=>a(n,await e.fetchQuery({queryKey:[...r.VERIFIED_MODELS_QUERY_KEY,...s],queryFn:r.fetchVerifiedModelsByProvider,staleTime:r.VERIFIED_MODELS_STALE_TIME})),enabled:!!n,staleTime:r.VERIFIED_MODELS_STALE_TIME,gcTime:r.VERIFIED_MODELS_GC_TIME})};exports.useProviderModels=o;
|
|
2
2
|
//# sourceMappingURL=use-provider-models.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-provider-models.cjs","names":[],"sources":["../../../src/hooks/query/use-provider-models.ts"],"sourcesContent":["import { useQuery } from \"@tanstack/react-query\";\nimport ConfigService from \"#/api/config-service/config-service.api\";\nimport type { LLMModel } from \"#/api/config-service/config-service.types\";\nimport {\n VERIFIED_MODELS_GC_TIME,\n VERIFIED_MODELS_QUERY_KEY,\n VERIFIED_MODELS_STALE_TIME,\n fetchVerifiedModelsByProvider,\n} from \"./use-verified-models\";\n\nconst MAX_PAGINATION_DEPTH = 10;\n\nasync function fetchPage(\n provider: string,\n verifiedByProvider: Record<string, string[]>,\n pageId?: string,\n depth = 0,\n): Promise<LLMModel[]> {\n if (depth >= MAX_PAGINATION_DEPTH) {\n throw new Error(`Too many pagination requests for provider ${provider}`);\n }\n\n const page = await ConfigService.searchModels(\n {\n provider__eq: provider,\n limit: 100,\n page_id: pageId,\n },\n verifiedByProvider,\n );\n\n if (page.next_page_id) {\n const rest = await fetchPage(\n provider,\n verifiedByProvider,\n page.next_page_id,\n depth + 1,\n );\n return [...page.items, ...rest];\n }\n return page.items;\n}\n\nexport const useProviderModels = (provider: string | null)
|
|
1
|
+
{"version":3,"file":"use-provider-models.cjs","names":[],"sources":["../../../src/hooks/query/use-provider-models.ts"],"sourcesContent":["import { useQuery } from \"@tanstack/react-query\";\nimport ConfigService from \"#/api/config-service/config-service.api\";\nimport type { LLMModel } from \"#/api/config-service/config-service.types\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport {\n VERIFIED_MODELS_GC_TIME,\n VERIFIED_MODELS_QUERY_KEY,\n VERIFIED_MODELS_STALE_TIME,\n fetchVerifiedModelsByProvider,\n} from \"./use-verified-models\";\n\nconst MAX_PAGINATION_DEPTH = 10;\n\nasync function fetchPage(\n provider: string,\n verifiedByProvider: Record<string, string[]>,\n pageId?: string,\n depth = 0,\n): Promise<LLMModel[]> {\n if (depth >= MAX_PAGINATION_DEPTH) {\n throw new Error(`Too many pagination requests for provider ${provider}`);\n }\n\n const page = await ConfigService.searchModels(\n {\n provider__eq: provider,\n limit: 100,\n page_id: pageId,\n },\n verifiedByProvider,\n );\n\n if (page.next_page_id) {\n const rest = await fetchPage(\n provider,\n verifiedByProvider,\n page.next_page_id,\n depth + 1,\n );\n return [...page.items, ...rest];\n }\n return page.items;\n}\n\nexport const useProviderModels = (provider: string | null) => {\n // `ActiveBackendProvider` deliberately does not blanket-invalidate on\n // backend/org switches, so the query key must carry the active backend\n // identity (id, connection revision, org id). Otherwise React Query serves\n // the previous backend/org's cached page — including DB-driven `free`/\n // `default`/`verified` flags — for the full stale window after a switch.\n const { backend, orgId } = useActiveBackend();\n const backendScope = [\n backend.id,\n backend.connectionRevision ?? 0,\n orgId,\n ] as const;\n\n return useQuery({\n queryKey: [\"config\", \"models\", provider, ...backendScope],\n queryFn: async ({ client }) => {\n const verifiedByProvider = await client.fetchQuery({\n queryKey: [...VERIFIED_MODELS_QUERY_KEY, ...backendScope],\n queryFn: fetchVerifiedModelsByProvider,\n staleTime: VERIFIED_MODELS_STALE_TIME,\n });\n return fetchPage(provider!, verifiedByProvider);\n },\n enabled: !!provider,\n staleTime: VERIFIED_MODELS_STALE_TIME,\n gcTime: VERIFIED_MODELS_GC_TIME,\n });\n};\n"],"mappings":"iSAWA,IAAM,EAAuB,GAE7B,eAAe,EACb,EACA,EACA,EACA,EAAQ,EACa,CACrB,GAAI,GAAS,EACX,MAAU,MAAM,6CAA6C,GAAU,EAGzE,IAAM,EAAO,MAAM,EAAA,QAAc,aAC/B,CACE,aAAc,EACd,MAAO,IACP,QAAS,CACX,EACA,CACF,EAEA,GAAI,EAAK,aAAc,CACrB,IAAM,EAAO,MAAM,EACjB,EACA,EACA,EAAK,aACL,EAAQ,CACV,EACA,MAAO,CAAC,GAAG,EAAK,MAAO,GAAG,CAAI,CAChC,CACA,OAAO,EAAK,KACd,CAEA,IAAa,EAAqB,GAA4B,CAM5D,GAAM,CAAE,UAAS,SAAU,EAAA,iBAAiB,EACtC,EAAe,CACnB,EAAQ,GACR,EAAQ,oBAAsB,EAC9B,CACF,EAEA,OAAO,EAAA,SAAS,CACd,SAAU,CAAC,SAAU,SAAU,EAAU,GAAG,CAAY,EACxD,QAAS,MAAO,CAAE,YAMT,EAAU,EAAW,MALK,EAAO,WAAW,CACjD,SAAU,CAAC,GAAG,EAAA,0BAA2B,GAAG,CAAY,EACxD,QAAS,EAAA,8BACT,UAAW,EAAA,0BACb,CAAC,CAC6C,EAEhD,QAAS,CAAC,CAAC,EACX,UAAW,EAAA,2BACX,OAAQ,EAAA,uBACV,CAAC,CACH"}
|
|
@@ -1,37 +1,46 @@
|
|
|
1
1
|
import { useQuery as e } from "../../node_modules/@tanstack/react-query/build/modern/useQuery.js";
|
|
2
|
-
import t from "../../
|
|
3
|
-
import
|
|
2
|
+
import { useActiveBackend as t } from "../../contexts/active-backend-context.js";
|
|
3
|
+
import n from "../../api/config-service/config-service.api.js";
|
|
4
|
+
import { VERIFIED_MODELS_GC_TIME as r, VERIFIED_MODELS_QUERY_KEY as i, VERIFIED_MODELS_STALE_TIME as a, fetchVerifiedModelsByProvider as o } from "./use-verified-models.js";
|
|
4
5
|
//#region src/hooks/query/use-provider-models.ts
|
|
5
|
-
var
|
|
6
|
-
async function
|
|
7
|
-
if (i >=
|
|
8
|
-
let a = await
|
|
6
|
+
var s = 10;
|
|
7
|
+
async function c(e, t, r, i = 0) {
|
|
8
|
+
if (i >= s) throw Error(`Too many pagination requests for provider ${e}`);
|
|
9
|
+
let a = await n.searchModels({
|
|
9
10
|
provider__eq: e,
|
|
10
11
|
limit: 100,
|
|
11
12
|
page_id: r
|
|
12
|
-
},
|
|
13
|
+
}, t);
|
|
13
14
|
if (a.next_page_id) {
|
|
14
|
-
let
|
|
15
|
-
return [...a.items, ...
|
|
15
|
+
let n = await c(e, t, a.next_page_id, i + 1);
|
|
16
|
+
return [...a.items, ...n];
|
|
16
17
|
}
|
|
17
18
|
return a.items;
|
|
18
19
|
}
|
|
19
|
-
var
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
]
|
|
25
|
-
|
|
26
|
-
queryKey:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
var l = (n) => {
|
|
21
|
+
let { backend: s, orgId: l } = t(), u = [
|
|
22
|
+
s.id,
|
|
23
|
+
s.connectionRevision ?? 0,
|
|
24
|
+
l
|
|
25
|
+
];
|
|
26
|
+
return e({
|
|
27
|
+
queryKey: [
|
|
28
|
+
"config",
|
|
29
|
+
"models",
|
|
30
|
+
n,
|
|
31
|
+
...u
|
|
32
|
+
],
|
|
33
|
+
queryFn: async ({ client: e }) => c(n, await e.fetchQuery({
|
|
34
|
+
queryKey: [...i, ...u],
|
|
35
|
+
queryFn: o,
|
|
36
|
+
staleTime: a
|
|
37
|
+
})),
|
|
38
|
+
enabled: !!n,
|
|
39
|
+
staleTime: a,
|
|
40
|
+
gcTime: r
|
|
41
|
+
});
|
|
42
|
+
};
|
|
34
43
|
//#endregion
|
|
35
|
-
export {
|
|
44
|
+
export { l as useProviderModels };
|
|
36
45
|
|
|
37
46
|
//# sourceMappingURL=use-provider-models.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-provider-models.js","names":[],"sources":["../../../src/hooks/query/use-provider-models.ts"],"sourcesContent":["import { useQuery } from \"@tanstack/react-query\";\nimport ConfigService from \"#/api/config-service/config-service.api\";\nimport type { LLMModel } from \"#/api/config-service/config-service.types\";\nimport {\n VERIFIED_MODELS_GC_TIME,\n VERIFIED_MODELS_QUERY_KEY,\n VERIFIED_MODELS_STALE_TIME,\n fetchVerifiedModelsByProvider,\n} from \"./use-verified-models\";\n\nconst MAX_PAGINATION_DEPTH = 10;\n\nasync function fetchPage(\n provider: string,\n verifiedByProvider: Record<string, string[]>,\n pageId?: string,\n depth = 0,\n): Promise<LLMModel[]> {\n if (depth >= MAX_PAGINATION_DEPTH) {\n throw new Error(`Too many pagination requests for provider ${provider}`);\n }\n\n const page = await ConfigService.searchModels(\n {\n provider__eq: provider,\n limit: 100,\n page_id: pageId,\n },\n verifiedByProvider,\n );\n\n if (page.next_page_id) {\n const rest = await fetchPage(\n provider,\n verifiedByProvider,\n page.next_page_id,\n depth + 1,\n );\n return [...page.items, ...rest];\n }\n return page.items;\n}\n\nexport const useProviderModels = (provider: string | null)
|
|
1
|
+
{"version":3,"file":"use-provider-models.js","names":[],"sources":["../../../src/hooks/query/use-provider-models.ts"],"sourcesContent":["import { useQuery } from \"@tanstack/react-query\";\nimport ConfigService from \"#/api/config-service/config-service.api\";\nimport type { LLMModel } from \"#/api/config-service/config-service.types\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport {\n VERIFIED_MODELS_GC_TIME,\n VERIFIED_MODELS_QUERY_KEY,\n VERIFIED_MODELS_STALE_TIME,\n fetchVerifiedModelsByProvider,\n} from \"./use-verified-models\";\n\nconst MAX_PAGINATION_DEPTH = 10;\n\nasync function fetchPage(\n provider: string,\n verifiedByProvider: Record<string, string[]>,\n pageId?: string,\n depth = 0,\n): Promise<LLMModel[]> {\n if (depth >= MAX_PAGINATION_DEPTH) {\n throw new Error(`Too many pagination requests for provider ${provider}`);\n }\n\n const page = await ConfigService.searchModels(\n {\n provider__eq: provider,\n limit: 100,\n page_id: pageId,\n },\n verifiedByProvider,\n );\n\n if (page.next_page_id) {\n const rest = await fetchPage(\n provider,\n verifiedByProvider,\n page.next_page_id,\n depth + 1,\n );\n return [...page.items, ...rest];\n }\n return page.items;\n}\n\nexport const useProviderModels = (provider: string | null) => {\n // `ActiveBackendProvider` deliberately does not blanket-invalidate on\n // backend/org switches, so the query key must carry the active backend\n // identity (id, connection revision, org id). Otherwise React Query serves\n // the previous backend/org's cached page — including DB-driven `free`/\n // `default`/`verified` flags — for the full stale window after a switch.\n const { backend, orgId } = useActiveBackend();\n const backendScope = [\n backend.id,\n backend.connectionRevision ?? 0,\n orgId,\n ] as const;\n\n return useQuery({\n queryKey: [\"config\", \"models\", provider, ...backendScope],\n queryFn: async ({ client }) => {\n const verifiedByProvider = await client.fetchQuery({\n queryKey: [...VERIFIED_MODELS_QUERY_KEY, ...backendScope],\n queryFn: fetchVerifiedModelsByProvider,\n staleTime: VERIFIED_MODELS_STALE_TIME,\n });\n return fetchPage(provider!, verifiedByProvider);\n },\n enabled: !!provider,\n staleTime: VERIFIED_MODELS_STALE_TIME,\n gcTime: VERIFIED_MODELS_GC_TIME,\n });\n};\n"],"mappings":";;;;;AAWA,IAAM,IAAuB;AAE7B,eAAe,EACb,GACA,GACA,GACA,IAAQ,GACa;CACrB,IAAI,KAAS,GACX,MAAU,MAAM,6CAA6C,GAAU;CAGzE,IAAM,IAAO,MAAM,EAAc,aAC/B;EACE,cAAc;EACd,OAAO;EACP,SAAS;CACX,GACA,CACF;CAEA,IAAI,EAAK,cAAc;EACrB,IAAM,IAAO,MAAM,EACjB,GACA,GACA,EAAK,cACL,IAAQ,CACV;EACA,OAAO,CAAC,GAAG,EAAK,OAAO,GAAG,CAAI;CAChC;CACA,OAAO,EAAK;AACd;AAEA,IAAa,KAAqB,MAA4B;CAM5D,IAAM,EAAE,YAAS,aAAU,EAAiB,GACtC,IAAe;EACnB,EAAQ;EACR,EAAQ,sBAAsB;EAC9B;CACF;CAEA,OAAO,EAAS;EACd,UAAU;GAAC;GAAU;GAAU;GAAU,GAAG;EAAY;EACxD,SAAS,OAAO,EAAE,gBAMT,EAAU,GAAW,MALK,EAAO,WAAW;GACjD,UAAU,CAAC,GAAG,GAA2B,GAAG,CAAY;GACxD,SAAS;GACT,WAAW;EACb,CAAC,CAC6C;EAEhD,SAAS,CAAC,CAAC;EACX,WAAW;EACX,QAAQ;CACV,CAAC;AACH"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require("../../_virtual/_rolldown/runtime.cjs");const e=require("../../node_modules/@tanstack/react-query/build/modern/useQuery.cjs"),t=require("../../api/config-service/config-service.api.cjs"),
|
|
1
|
+
require("../../_virtual/_rolldown/runtime.cjs");const e=require("../../node_modules/@tanstack/react-query/build/modern/useQuery.cjs"),t=require("../../contexts/active-backend-context.cjs"),n=require("../../api/config-service/config-service.api.cjs"),r=require("./use-verified-models.cjs");var i=10;async function a(e,t,r,o){if(o>=i)throw Error(`Too many pagination requests while fetching providers (depth=${o})`);let s=await n.default.searchProviders(t?{page_id:t}:{},e);if(!s.next_page_id)return s.items;if(r.has(s.next_page_id))throw Error(`Repeated page id while fetching providers: ${s.next_page_id}`);r.add(s.next_page_id);let c=await a(e,s.next_page_id,r,o+1);return[...s.items,...c]}var o=()=>{let{backend:n,orgId:i}=t.useActiveBackend(),o=[n.id,n.connectionRevision??0,i];return e.useQuery({queryKey:[`config`,`providers`,...o],queryFn:async({client:e})=>a(await e.fetchQuery({queryKey:[...r.VERIFIED_MODELS_QUERY_KEY,...o],queryFn:r.fetchVerifiedModelsByProvider,staleTime:r.VERIFIED_MODELS_STALE_TIME}),null,new Set,0),staleTime:r.VERIFIED_MODELS_STALE_TIME,gcTime:r.VERIFIED_MODELS_GC_TIME})};exports.useSearchProviders=o;
|
|
2
2
|
//# sourceMappingURL=use-search-providers.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-search-providers.cjs","names":[],"sources":["../../../src/hooks/query/use-search-providers.ts"],"sourcesContent":["import { useQuery } from \"@tanstack/react-query\";\nimport ConfigService from \"#/api/config-service/config-service.api\";\nimport type { LLMProvider } from \"#/api/config-service/config-service.types\";\nimport {\n VERIFIED_MODELS_GC_TIME,\n VERIFIED_MODELS_QUERY_KEY,\n VERIFIED_MODELS_STALE_TIME,\n fetchVerifiedModelsByProvider,\n} from \"./use-verified-models\";\n\n// Cloud backends paginate `/api/v1/config/providers/search` with a default\n// page size smaller than the provider list (~150 entries from litellm), so a\n// single-page fetch silently drops everything past the cut. Cap the walk\n// in case the cursor ever drifts (e.g. the cloud service returns an unstable\n// `next_page_id` that loops back).\nconst MAX_PAGINATION_DEPTH = 10;\n\nasync function fetchAllProviders(\n verifiedByProvider: Record<string, string[]>,\n pageId: string | null | undefined,\n seenPageIds: Set<string>,\n depth: number,\n): Promise<LLMProvider[]> {\n if (depth >= MAX_PAGINATION_DEPTH) {\n throw new Error(\n `Too many pagination requests while fetching providers (depth=${depth})`,\n );\n }\n\n const page = await ConfigService.searchProviders(\n pageId ? { page_id: pageId } : {},\n verifiedByProvider,\n );\n\n if (!page.next_page_id) {\n return page.items;\n }\n\n // Cycle guard: the cloud backend is out of our control — if `next_page_id`\n // repeats, refuse to hang the settings page. Mirrors `use-search-subdirs`.\n if (seenPageIds.has(page.next_page_id)) {\n throw new Error(\n `Repeated page id while fetching providers: ${page.next_page_id}`,\n );\n }\n seenPageIds.add(page.next_page_id);\n\n const rest = await fetchAllProviders(\n verifiedByProvider,\n page.next_page_id,\n seenPageIds,\n depth + 1,\n );\n return [...page.items, ...rest];\n}\n\nexport const useSearchProviders = ()
|
|
1
|
+
{"version":3,"file":"use-search-providers.cjs","names":[],"sources":["../../../src/hooks/query/use-search-providers.ts"],"sourcesContent":["import { useQuery } from \"@tanstack/react-query\";\nimport ConfigService from \"#/api/config-service/config-service.api\";\nimport type { LLMProvider } from \"#/api/config-service/config-service.types\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport {\n VERIFIED_MODELS_GC_TIME,\n VERIFIED_MODELS_QUERY_KEY,\n VERIFIED_MODELS_STALE_TIME,\n fetchVerifiedModelsByProvider,\n} from \"./use-verified-models\";\n\n// Cloud backends paginate `/api/v1/config/providers/search` with a default\n// page size smaller than the provider list (~150 entries from litellm), so a\n// single-page fetch silently drops everything past the cut. Cap the walk\n// in case the cursor ever drifts (e.g. the cloud service returns an unstable\n// `next_page_id` that loops back).\nconst MAX_PAGINATION_DEPTH = 10;\n\nasync function fetchAllProviders(\n verifiedByProvider: Record<string, string[]>,\n pageId: string | null | undefined,\n seenPageIds: Set<string>,\n depth: number,\n): Promise<LLMProvider[]> {\n if (depth >= MAX_PAGINATION_DEPTH) {\n throw new Error(\n `Too many pagination requests while fetching providers (depth=${depth})`,\n );\n }\n\n const page = await ConfigService.searchProviders(\n pageId ? { page_id: pageId } : {},\n verifiedByProvider,\n );\n\n if (!page.next_page_id) {\n return page.items;\n }\n\n // Cycle guard: the cloud backend is out of our control — if `next_page_id`\n // repeats, refuse to hang the settings page. Mirrors `use-search-subdirs`.\n if (seenPageIds.has(page.next_page_id)) {\n throw new Error(\n `Repeated page id while fetching providers: ${page.next_page_id}`,\n );\n }\n seenPageIds.add(page.next_page_id);\n\n const rest = await fetchAllProviders(\n verifiedByProvider,\n page.next_page_id,\n seenPageIds,\n depth + 1,\n );\n return [...page.items, ...rest];\n}\n\nexport const useSearchProviders = () => {\n // `ActiveBackendProvider` deliberately does not blanket-invalidate on\n // backend/org switches, so the query key must carry the active backend\n // identity (id, connection revision, org id). Otherwise React Query serves\n // the previous backend/org's cached provider list — including DB-driven\n // `verified` flags — for the full stale window after a switch.\n const { backend, orgId } = useActiveBackend();\n const backendScope = [\n backend.id,\n backend.connectionRevision ?? 0,\n orgId,\n ] as const;\n\n return useQuery({\n queryKey: [\"config\", \"providers\", ...backendScope],\n queryFn: async ({ client }): Promise<LLMProvider[]> => {\n const verifiedByProvider = await client.fetchQuery({\n queryKey: [...VERIFIED_MODELS_QUERY_KEY, ...backendScope],\n queryFn: fetchVerifiedModelsByProvider,\n staleTime: VERIFIED_MODELS_STALE_TIME,\n });\n // The local backend returns a single page with `next_page_id: null`\n // so this loop is a no-op there; on the cloud backend it walks the\n // cursor to exhaustion.\n return fetchAllProviders(verifiedByProvider, null, new Set(), 0);\n },\n staleTime: VERIFIED_MODELS_STALE_TIME,\n gcTime: VERIFIED_MODELS_GC_TIME,\n });\n};\n"],"mappings":"iSAgBA,IAAM,EAAuB,GAE7B,eAAe,EACb,EACA,EACA,EACA,EACwB,CACxB,GAAI,GAAS,EACX,MAAU,MACR,gEAAgE,EAAM,EACxE,EAGF,IAAM,EAAO,MAAM,EAAA,QAAc,gBAC/B,EAAS,CAAE,QAAS,CAAO,EAAI,CAAC,EAChC,CACF,EAEA,GAAI,CAAC,EAAK,aACR,OAAO,EAAK,MAKd,GAAI,EAAY,IAAI,EAAK,YAAY,EACnC,MAAU,MACR,8CAA8C,EAAK,cACrD,EAEF,EAAY,IAAI,EAAK,YAAY,EAEjC,IAAM,EAAO,MAAM,EACjB,EACA,EAAK,aACL,EACA,EAAQ,CACV,EACA,MAAO,CAAC,GAAG,EAAK,MAAO,GAAG,CAAI,CAChC,CAEA,IAAa,MAA2B,CAMtC,GAAM,CAAE,UAAS,SAAU,EAAA,iBAAiB,EACtC,EAAe,CACnB,EAAQ,GACR,EAAQ,oBAAsB,EAC9B,CACF,EAEA,OAAO,EAAA,SAAS,CACd,SAAU,CAAC,SAAU,YAAa,GAAG,CAAY,EACjD,QAAS,MAAO,CAAE,YAST,EAAkB,MARQ,EAAO,WAAW,CACjD,SAAU,CAAC,GAAG,EAAA,0BAA2B,GAAG,CAAY,EACxD,QAAS,EAAA,8BACT,UAAW,EAAA,0BACb,CAAC,EAI4C,KAAM,IAAI,IAAO,CAAC,EAEjE,UAAW,EAAA,2BACX,OAAQ,EAAA,uBACV,CAAC,CACH"}
|
|
@@ -1,28 +1,40 @@
|
|
|
1
1
|
import { useQuery as e } from "../../node_modules/@tanstack/react-query/build/modern/useQuery.js";
|
|
2
|
-
import t from "../../
|
|
3
|
-
import
|
|
2
|
+
import { useActiveBackend as t } from "../../contexts/active-backend-context.js";
|
|
3
|
+
import n from "../../api/config-service/config-service.api.js";
|
|
4
|
+
import { VERIFIED_MODELS_GC_TIME as r, VERIFIED_MODELS_QUERY_KEY as i, VERIFIED_MODELS_STALE_TIME as a, fetchVerifiedModelsByProvider as o } from "./use-verified-models.js";
|
|
4
5
|
//#region src/hooks/query/use-search-providers.ts
|
|
5
|
-
var
|
|
6
|
-
async function
|
|
7
|
-
if (i >=
|
|
8
|
-
let a = await
|
|
6
|
+
var s = 10;
|
|
7
|
+
async function c(e, t, r, i) {
|
|
8
|
+
if (i >= s) throw Error(`Too many pagination requests while fetching providers (depth=${i})`);
|
|
9
|
+
let a = await n.searchProviders(t ? { page_id: t } : {}, e);
|
|
9
10
|
if (!a.next_page_id) return a.items;
|
|
10
11
|
if (r.has(a.next_page_id)) throw Error(`Repeated page id while fetching providers: ${a.next_page_id}`);
|
|
11
12
|
r.add(a.next_page_id);
|
|
12
|
-
let
|
|
13
|
-
return [...a.items, ...
|
|
13
|
+
let o = await c(e, a.next_page_id, r, i + 1);
|
|
14
|
+
return [...a.items, ...o];
|
|
14
15
|
}
|
|
15
|
-
var
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
var l = () => {
|
|
17
|
+
let { backend: n, orgId: s } = t(), l = [
|
|
18
|
+
n.id,
|
|
19
|
+
n.connectionRevision ?? 0,
|
|
20
|
+
s
|
|
21
|
+
];
|
|
22
|
+
return e({
|
|
23
|
+
queryKey: [
|
|
24
|
+
"config",
|
|
25
|
+
"providers",
|
|
26
|
+
...l
|
|
27
|
+
],
|
|
28
|
+
queryFn: async ({ client: e }) => c(await e.fetchQuery({
|
|
29
|
+
queryKey: [...i, ...l],
|
|
30
|
+
queryFn: o,
|
|
31
|
+
staleTime: a
|
|
32
|
+
}), null, /* @__PURE__ */ new Set(), 0),
|
|
33
|
+
staleTime: a,
|
|
34
|
+
gcTime: r
|
|
35
|
+
});
|
|
36
|
+
};
|
|
25
37
|
//#endregion
|
|
26
|
-
export {
|
|
38
|
+
export { l as useSearchProviders };
|
|
27
39
|
|
|
28
40
|
//# sourceMappingURL=use-search-providers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-search-providers.js","names":[],"sources":["../../../src/hooks/query/use-search-providers.ts"],"sourcesContent":["import { useQuery } from \"@tanstack/react-query\";\nimport ConfigService from \"#/api/config-service/config-service.api\";\nimport type { LLMProvider } from \"#/api/config-service/config-service.types\";\nimport {\n VERIFIED_MODELS_GC_TIME,\n VERIFIED_MODELS_QUERY_KEY,\n VERIFIED_MODELS_STALE_TIME,\n fetchVerifiedModelsByProvider,\n} from \"./use-verified-models\";\n\n// Cloud backends paginate `/api/v1/config/providers/search` with a default\n// page size smaller than the provider list (~150 entries from litellm), so a\n// single-page fetch silently drops everything past the cut. Cap the walk\n// in case the cursor ever drifts (e.g. the cloud service returns an unstable\n// `next_page_id` that loops back).\nconst MAX_PAGINATION_DEPTH = 10;\n\nasync function fetchAllProviders(\n verifiedByProvider: Record<string, string[]>,\n pageId: string | null | undefined,\n seenPageIds: Set<string>,\n depth: number,\n): Promise<LLMProvider[]> {\n if (depth >= MAX_PAGINATION_DEPTH) {\n throw new Error(\n `Too many pagination requests while fetching providers (depth=${depth})`,\n );\n }\n\n const page = await ConfigService.searchProviders(\n pageId ? { page_id: pageId } : {},\n verifiedByProvider,\n );\n\n if (!page.next_page_id) {\n return page.items;\n }\n\n // Cycle guard: the cloud backend is out of our control — if `next_page_id`\n // repeats, refuse to hang the settings page. Mirrors `use-search-subdirs`.\n if (seenPageIds.has(page.next_page_id)) {\n throw new Error(\n `Repeated page id while fetching providers: ${page.next_page_id}`,\n );\n }\n seenPageIds.add(page.next_page_id);\n\n const rest = await fetchAllProviders(\n verifiedByProvider,\n page.next_page_id,\n seenPageIds,\n depth + 1,\n );\n return [...page.items, ...rest];\n}\n\nexport const useSearchProviders = ()
|
|
1
|
+
{"version":3,"file":"use-search-providers.js","names":[],"sources":["../../../src/hooks/query/use-search-providers.ts"],"sourcesContent":["import { useQuery } from \"@tanstack/react-query\";\nimport ConfigService from \"#/api/config-service/config-service.api\";\nimport type { LLMProvider } from \"#/api/config-service/config-service.types\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport {\n VERIFIED_MODELS_GC_TIME,\n VERIFIED_MODELS_QUERY_KEY,\n VERIFIED_MODELS_STALE_TIME,\n fetchVerifiedModelsByProvider,\n} from \"./use-verified-models\";\n\n// Cloud backends paginate `/api/v1/config/providers/search` with a default\n// page size smaller than the provider list (~150 entries from litellm), so a\n// single-page fetch silently drops everything past the cut. Cap the walk\n// in case the cursor ever drifts (e.g. the cloud service returns an unstable\n// `next_page_id` that loops back).\nconst MAX_PAGINATION_DEPTH = 10;\n\nasync function fetchAllProviders(\n verifiedByProvider: Record<string, string[]>,\n pageId: string | null | undefined,\n seenPageIds: Set<string>,\n depth: number,\n): Promise<LLMProvider[]> {\n if (depth >= MAX_PAGINATION_DEPTH) {\n throw new Error(\n `Too many pagination requests while fetching providers (depth=${depth})`,\n );\n }\n\n const page = await ConfigService.searchProviders(\n pageId ? { page_id: pageId } : {},\n verifiedByProvider,\n );\n\n if (!page.next_page_id) {\n return page.items;\n }\n\n // Cycle guard: the cloud backend is out of our control — if `next_page_id`\n // repeats, refuse to hang the settings page. Mirrors `use-search-subdirs`.\n if (seenPageIds.has(page.next_page_id)) {\n throw new Error(\n `Repeated page id while fetching providers: ${page.next_page_id}`,\n );\n }\n seenPageIds.add(page.next_page_id);\n\n const rest = await fetchAllProviders(\n verifiedByProvider,\n page.next_page_id,\n seenPageIds,\n depth + 1,\n );\n return [...page.items, ...rest];\n}\n\nexport const useSearchProviders = () => {\n // `ActiveBackendProvider` deliberately does not blanket-invalidate on\n // backend/org switches, so the query key must carry the active backend\n // identity (id, connection revision, org id). Otherwise React Query serves\n // the previous backend/org's cached provider list — including DB-driven\n // `verified` flags — for the full stale window after a switch.\n const { backend, orgId } = useActiveBackend();\n const backendScope = [\n backend.id,\n backend.connectionRevision ?? 0,\n orgId,\n ] as const;\n\n return useQuery({\n queryKey: [\"config\", \"providers\", ...backendScope],\n queryFn: async ({ client }): Promise<LLMProvider[]> => {\n const verifiedByProvider = await client.fetchQuery({\n queryKey: [...VERIFIED_MODELS_QUERY_KEY, ...backendScope],\n queryFn: fetchVerifiedModelsByProvider,\n staleTime: VERIFIED_MODELS_STALE_TIME,\n });\n // The local backend returns a single page with `next_page_id: null`\n // so this loop is a no-op there; on the cloud backend it walks the\n // cursor to exhaustion.\n return fetchAllProviders(verifiedByProvider, null, new Set(), 0);\n },\n staleTime: VERIFIED_MODELS_STALE_TIME,\n gcTime: VERIFIED_MODELS_GC_TIME,\n });\n};\n"],"mappings":";;;;;AAgBA,IAAM,IAAuB;AAE7B,eAAe,EACb,GACA,GACA,GACA,GACwB;CACxB,IAAI,KAAS,GACX,MAAU,MACR,gEAAgE,EAAM,EACxE;CAGF,IAAM,IAAO,MAAM,EAAc,gBAC/B,IAAS,EAAE,SAAS,EAAO,IAAI,CAAC,GAChC,CACF;CAEA,IAAI,CAAC,EAAK,cACR,OAAO,EAAK;CAKd,IAAI,EAAY,IAAI,EAAK,YAAY,GACnC,MAAU,MACR,8CAA8C,EAAK,cACrD;CAEF,EAAY,IAAI,EAAK,YAAY;CAEjC,IAAM,IAAO,MAAM,EACjB,GACA,EAAK,cACL,GACA,IAAQ,CACV;CACA,OAAO,CAAC,GAAG,EAAK,OAAO,GAAG,CAAI;AAChC;AAEA,IAAa,UAA2B;CAMtC,IAAM,EAAE,YAAS,aAAU,EAAiB,GACtC,IAAe;EACnB,EAAQ;EACR,EAAQ,sBAAsB;EAC9B;CACF;CAEA,OAAO,EAAS;EACd,UAAU;GAAC;GAAU;GAAa,GAAG;EAAY;EACjD,SAAS,OAAO,EAAE,gBAST,EAAkB,MARQ,EAAO,WAAW;GACjD,UAAU,CAAC,GAAG,GAA2B,GAAG,CAAY;GACxD,SAAS;GACT,WAAW;EACb,CAAC,GAI4C,sBAAM,IAAI,IAAI,GAAG,CAAC;EAEjE,WAAW;EACX,QAAQ;CACV,CAAC;AACH"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require("../_virtual/_rolldown/runtime.cjs"),require("../context/navigation-context.cjs"),require("../utils/custom-toast-handlers.cjs")
|
|
1
|
+
require("../_virtual/_rolldown/runtime.cjs"),require("../context/navigation-context.cjs"),require("../utils/custom-toast-handlers.cjs");const e=require("../types/automation.cjs");require("./use-automation-permissions.cjs"),require("./query/use-automations.cjs"),require("../fixtures/home-automations-demo.cjs"),require("react");function t(t){return t?.status===e.AutomationRunStatus.PENDING||t?.status===e.AutomationRunStatus.RUNNING}exports.isInFlightAutomationRun=t;
|
|
2
2
|
//# sourceMappingURL=use-home-automation-actions.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-home-automation-actions.cjs","names":[],"sources":["../../src/hooks/use-home-automation-actions.ts"],"sourcesContent":["import { useCallback, useState } from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport {
|
|
1
|
+
{"version":3,"file":"use-home-automation-actions.cjs","names":[],"sources":["../../src/hooks/use-home-automation-actions.ts"],"sourcesContent":["import { useCallback, useState } from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { useNavigation } from \"#/context/navigation-context\";\nimport {\n useCancelAutomationRun,\n useDispatchAutomation,\n useToggleAutomation,\n} from \"#/hooks/query/use-automations\";\nimport {\n useAutomationPermissions,\n useIsAutomationOwner,\n} from \"#/hooks/use-automation-permissions\";\nimport { isHomeAutomationsDemoEnabled } from \"#/fixtures/home-automations-demo\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport {\n AutomationRunStatus,\n type Automation,\n type AutomationRun,\n} from \"#/types/automation\";\nimport { getApiErrorMessage } from \"#/utils/api-error-message\";\nimport {\n displayErrorToast,\n displaySuccessToast,\n} from \"#/utils/custom-toast-handlers\";\n\nexport function isInFlightAutomationRun(\n run: AutomationRun | null | undefined,\n): boolean {\n return (\n run?.status === AutomationRunStatus.PENDING ||\n run?.status === AutomationRunStatus.RUNNING\n );\n}\n\n/**\n * Shared home-surface actions for pinned cards and activity rows:\n * run now, view, edit, turn off (with confirm), cancel in-flight.\n */\nexport function useHomeAutomationActions(\n automation: Automation,\n latestRun: AutomationRun | null,\n) {\n const { t } = useTranslation(\"openhands\");\n const { navigate } = useNavigation();\n const { canManage: hasManagePermission } = useAutomationPermissions();\n const isOwner = useIsAutomationOwner(automation);\n // Write actions on a specific automation are allowed when the user has\n // manage permission OR is the automation's creator (creator escape hatch).\n const canManage = hasManagePermission || isOwner;\n const isDemo = isHomeAutomationsDemoEnabled();\n\n const dispatchMutation = useDispatchAutomation();\n const toggleMutation = useToggleAutomation();\n const cancelMutation = useCancelAutomationRun();\n\n const [editOpen, setEditOpen] = useState(false);\n const [turnOffConfirmOpen, setTurnOffConfirmOpen] = useState(false);\n\n const isRunPending =\n dispatchMutation.isPending && dispatchMutation.variables === automation.id;\n const isCancelPending =\n cancelMutation.isPending &&\n cancelMutation.variables?.runId === latestRun?.id;\n const canCancel = canManage && isInFlightAutomationRun(latestRun);\n\n const runNow = useCallback(() => {\n if (isDemo) {\n displaySuccessToast(t(I18nKey.AUTOMATIONS$RUN_NOW_SUCCESS));\n return;\n }\n dispatchMutation.mutate(automation.id, {\n onSuccess: () => {\n displaySuccessToast(t(I18nKey.AUTOMATIONS$RUN_NOW_SUCCESS));\n },\n onError: (error) => {\n displayErrorToast(\n getApiErrorMessage(error, t(I18nKey.AUTOMATIONS$RUN_NOW_ERROR)),\n );\n },\n });\n }, [automation.id, dispatchMutation, isDemo, t]);\n\n const viewDetails = useCallback(() => {\n navigate?.(`/automations/${automation.id}`);\n }, [automation.id, navigate]);\n\n const openEdit = useCallback(() => {\n setEditOpen(true);\n }, []);\n\n const requestTurnOff = useCallback(() => {\n setTurnOffConfirmOpen(true);\n }, []);\n\n const confirmTurnOff = useCallback(() => {\n setTurnOffConfirmOpen(false);\n if (isDemo) return;\n toggleMutation.mutate(\n { id: automation.id, enabled: false },\n {\n onError: (error) => {\n displayErrorToast(\n getApiErrorMessage(error, t(I18nKey.AUTOMATIONS$TURN_OFF_ERROR)),\n );\n },\n },\n );\n }, [automation.id, isDemo, t, toggleMutation]);\n\n const cancelTurnOff = useCallback(() => {\n setTurnOffConfirmOpen(false);\n }, []);\n\n const cancelRun = useCallback(() => {\n if (!latestRun || !isInFlightAutomationRun(latestRun)) return;\n if (isDemo) {\n displaySuccessToast(t(I18nKey.AUTOMATIONS$CANCEL_RUN_SUCCESS));\n return;\n }\n cancelMutation.mutate(\n { automationId: automation.id, runId: latestRun.id },\n {\n onSuccess: () => {\n displaySuccessToast(t(I18nKey.AUTOMATIONS$CANCEL_RUN_SUCCESS));\n },\n onError: (error) => {\n displayErrorToast(\n getApiErrorMessage(error, t(I18nKey.AUTOMATIONS$CANCEL_RUN_ERROR)),\n );\n },\n },\n );\n }, [automation.id, cancelMutation, isDemo, latestRun, t]);\n\n return {\n canManage,\n isRunPending,\n isCancelPending,\n canCancel,\n editOpen,\n setEditOpen,\n turnOffConfirmOpen,\n runNow,\n viewDetails,\n openEdit,\n requestTurnOff,\n confirmTurnOff,\n cancelTurnOff,\n cancelRun,\n };\n}\n"],"mappings":"wUAyBA,SAAgB,EACd,EACS,CACT,OACE,GAAK,SAAW,EAAA,oBAAoB,SACpC,GAAK,SAAW,EAAA,oBAAoB,OAExC"}
|
|
@@ -2,11 +2,10 @@ import { type Automation, type AutomationRun } from "#/types/automation";
|
|
|
2
2
|
export declare function isInFlightAutomationRun(run: AutomationRun | null | undefined): boolean;
|
|
3
3
|
/**
|
|
4
4
|
* Shared home-surface actions for pinned cards and activity rows:
|
|
5
|
-
* run now, view, edit
|
|
5
|
+
* run now, view, edit, turn off (with confirm), cancel in-flight.
|
|
6
6
|
*/
|
|
7
7
|
export declare function useHomeAutomationActions(automation: Automation, latestRun: AutomationRun | null): {
|
|
8
8
|
canManage: boolean;
|
|
9
|
-
canEdit: boolean;
|
|
10
9
|
isRunPending: boolean;
|
|
11
10
|
isCancelPending: boolean;
|
|
12
11
|
canCancel: boolean;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import "../context/navigation-context.js";
|
|
2
2
|
import "../utils/custom-toast-handlers.js";
|
|
3
|
-
import "../contexts/active-backend-context.js";
|
|
4
3
|
import { AutomationRunStatus as e } from "../types/automation.js";
|
|
5
4
|
import "./use-automation-permissions.js";
|
|
6
5
|
import "./query/use-automations.js";
|