@openhands/agent-canvas 1.11.0 → 1.12.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/{active-backend-context-BBvP-bI2.js → active-backend-context-B_bg5h9O.js} +1 -1
- package/build/assets/{agent-context-settings-Bno1nwPL.js → agent-context-settings-CzWS1DgR.js} +1 -1
- package/build/assets/{agent-profiles-settings-DqoS7RL3.js → agent-profiles-settings-hd2cAIZM.js} +1 -1
- package/build/assets/{agent-server-conversation-service.api-0RztSdJJ.js → agent-server-conversation-service.api-BheU4HX3.js} +3 -3
- package/build/assets/{agent-settings-h8hQz-IU.js → agent-settings-D2J1dKVd.js} +1 -1
- package/build/assets/agent-settings-D_SVrWhI.js +1 -0
- package/build/assets/{api-key-entry-screen-Qsb83VX1.js → api-key-entry-screen-D70oJj2c.js} +1 -1
- package/build/assets/app-settings-D0OKHB3M.js +1 -0
- package/build/assets/{automation-detail-7LkL91j9.js → automation-detail-C0SxWTnQ.js} +1 -1
- package/build/assets/{automation-export-U2QNyNy6.js → automation-export-BjQa_fT6.js} +1 -1
- package/build/assets/{automation-setup-route-Bel9_Lue.js → automation-setup-route-BzCNpljP.js} +1 -1
- package/build/assets/{automation-templates-CRLDssaI.js → automation-templates-D9gs6ihu.js} +1 -1
- package/build/assets/{automations-list-B2iV3ZKW.js → automations-list-dZg8zOCl.js} +1 -1
- package/build/assets/backend-form-modal-HwclKrUS.js +1 -0
- package/build/assets/{backend-synced-settings-badge-DB4717Iu.js → backend-synced-settings-badge-YI3k09Wr.js} +1 -1
- package/build/assets/{badge-yR53enbU.js → badge-Dpn3-LTV.js} +1 -1
- package/build/assets/{browser-tab-A94o33Vn.js → browser-tab-4CPdj3J3.js} +1 -1
- package/build/assets/{cloud-funnel-analytics-CBQdPZl_.js → cloud-funnel-analytics-CcSez21r.js} +1 -1
- package/build/assets/{command-menu-PsRXVBz2.js → command-menu-BKOLj-TC.js} +1 -1
- package/build/assets/{condenser-settings-w7L3EFvm.js → condenser-settings-DLQogFnB.js} +1 -1
- package/build/assets/{conversation-BUzBZAc-.js → conversation-Cmyw9bFh.js} +3 -3
- package/build/assets/conversation-CvoGZ5vC.js +1 -0
- package/build/assets/conversation-panel-CvoGZ5vC.js +1 -0
- package/build/assets/{conversation-state-store-Bs8gA263.js → conversation-state-store-CtgILxWF.js} +1 -1
- package/build/assets/{entry.client-DzbDyLkV.js → entry.client-EBn3ZtNP.js} +1 -1
- package/build/assets/{extensions-hub-DOmHPGTA.js → extensions-hub-BZ3HYztk.js} +1 -1
- package/build/assets/{extensions-navigation-6zU5M1pa.js → extensions-navigation-Bnx32WAb.js} +1 -1
- package/build/assets/{files-tab-BzofBmF4.js → files-tab-D5zuU5TY.js} +1 -1
- package/build/assets/format-model-name-BN_rx1Y3.js +1 -0
- package/build/assets/{format-relative-time-DGPQlPx0.js → format-relative-time-BerVamGl.js} +1 -1
- package/build/assets/{git-provider-icon-BLMlwy02.js → git-provider-icon-zuKz2cwo.js} +1 -1
- package/build/assets/{git-repo-dropdown-B7RvO70z.js → git-repo-dropdown-B5VnMftu.js} +1 -1
- package/build/assets/git-repo-dropdown-Bo7ciSNX.js +1 -0
- package/build/assets/home-DqyeeFA_.js +1 -0
- package/build/assets/{home-DEZaZqYv.js → home-Y0aDou-a.js} +1 -1
- package/build/assets/{home-store-D7CjMN_-.js → home-store-Cubdq0uP.js} +1 -1
- package/build/assets/index-home-DqyeeFA_.js +1 -0
- package/build/assets/info-C9lpK5pw.js +1 -0
- package/build/assets/{install-server-modal-BTwK5lbi.js → install-server-modal-DW6dSyGr.js} +1 -1
- package/build/assets/{kebab-menu-item-content-C4CkeiJO.js → kebab-menu-item-content-CKydZrbH.js} +1 -1
- package/build/assets/{launch-DVlx4v9o.js → launch-c4GEP8oo.js} +1 -1
- package/build/assets/llm-not-configured-banner-DCE_0B4I.js +27 -0
- package/build/assets/llm-settings-8eWBF3Ij.js +1 -0
- package/build/assets/llm-settings-D9ko42Pi.js +1 -0
- package/build/assets/{llm-subscription-service-7eVfDIE1.js → llm-subscription-service-B3K-97lP.js} +1 -1
- package/build/assets/{manage-backends-modal-HNWKLYhq.js → manage-backends-modal-DTCME_Pu.js} +1 -1
- package/build/assets/manifest-e1c3bc3b.js +1 -0
- package/build/assets/{manifest-subpage-layout-D-m-h8_t.js → manifest-subpage-layout-Ds7G5GZ8.js} +1 -1
- package/build/assets/{markdown-renderer-BiiiIznl.js → markdown-renderer-DzvgdZey.js} +1 -1
- package/build/assets/{mcp-gLgPP7AS.js → mcp-Da7wFU7A.js} +1 -1
- package/build/assets/{mcp-marketplace-utils-Dr_HG_ol.js → mcp-marketplace-utils-DSLou4n7.js} +1 -1
- package/build/assets/messages-B2l0qLVk.js +40 -0
- package/build/assets/{npm-DRzJF5Zn.js → npm-Cswluf6-.js} +1 -1
- package/build/assets/{onboarding-DSr8hOpk.js → onboarding-BKbTYlW1.js} +1 -1
- package/build/assets/{onboarding-modal-C6Euf7N4.js → onboarding-modal-DrrGlEQq.js} +1 -1
- package/build/assets/{organization-service.api-C4GYWwj9.js → organization-service.api-DK8csGvK.js} +1 -1
- package/build/assets/{planner-tab-CZcguxTK.js → planner-tab-D-mZ1I7n.js} +1 -1
- package/build/assets/{plugins-management-service-BmDzOQsd.js → plugins-management-service-CywqyfLK.js} +1 -1
- package/build/assets/{profiles-service.api-CoLTwjc1.js → profiles-service.api-vcE_jJCe.js} +1 -1
- package/build/assets/{providers-tBzTpz1w.js → providers-CdrPPm0z.js} +1 -1
- package/build/assets/{proxy-DCI9S-LA.js → proxy-b4aqL4SX.js} +1 -1
- package/build/assets/{recommended-automations-launcher-BbJAJVED.js → recommended-automations-launcher-DIYgUdMm.js} +1 -1
- package/build/assets/root-CBBi6IAe.js +2 -0
- package/build/assets/root-CRK9fOFd.css +1 -0
- package/build/assets/root-layout-BFN2rCOy.js +2 -0
- package/build/assets/{schema-field-zhsVDllR.js → schema-field-Z-rMg9ti.js} +1 -1
- package/build/assets/{sdk-section-page-CS4GWn0y.js → sdk-section-page-m_cjygxR.js} +1 -1
- package/build/assets/{secrets-service-DjgGduXk.js → secrets-service-BHF4fxML.js} +1 -1
- package/build/assets/{secrets-settings-f-qahfbi.js → secrets-settings-aBGXtQhX.js} +1 -1
- package/build/assets/{settings-DQd0xLEw.js → settings-CZPGNens.js} +1 -1
- package/build/assets/{settings-index-BVwy9JEp.js → settings-index-Dtlegd6s.js} +1 -1
- package/build/assets/{settings-modal-D6D5ngET.js → settings-modal-CalzJP2l.js} +1 -1
- package/build/assets/{settings-service.api-DZp7E7A9.js → settings-service.api-DfPYaxZJ.js} +1 -1
- package/build/assets/{shared-conversation-DIxZjElr.js → shared-conversation-BIAcuORk.js} +1 -1
- package/build/assets/{sidebar-mobile-menu-toggle-OoZBHxaT.js → sidebar-mobile-menu-toggle-C6HpQ55l.js} +1 -1
- package/build/assets/{skill-scope-B1Per5XI.js → skill-scope-Dt1V5X2p.js} +1 -1
- package/build/assets/{skills-plugins-B3AO6sMz.js → skills-plugins-B-H0cCOP.js} +1 -1
- package/build/assets/{skills-settings-DmmgJ6CT.js → skills-settings-DthdIDUb.js} +1 -1
- package/build/assets/{task-list-tab-C19Q_JNf.js → task-list-tab-BTde81Iu.js} +1 -1
- package/build/assets/{terminal-CbuMRYal.js → terminal-DRrJo-cR.js} +1 -1
- package/build/assets/{use-acp-credential-form-DJmmJKMo.js → use-acp-credential-form-DGyo0QvL.js} +1 -1
- package/build/assets/{use-activate-agent-profile-CeFfYwu_.js → use-activate-agent-profile-DrPzIKJF.js} +1 -1
- package/build/assets/{use-agent-profiles-BHLkLbRR.js → use-agent-profiles-B37XWDLq.js} +1 -1
- package/build/assets/{use-can-manage-org-profiles-Bu4y8gSx.js → use-can-manage-org-profiles-BzE28H3x.js} +1 -1
- package/build/assets/{use-cloud-current-user-id-QD9x3ySO.js → use-cloud-current-user-id-U6HPjy50.js} +1 -1
- package/build/assets/{use-config-CPwqciXN.js → use-config-DapX3pjC.js} +1 -1
- package/build/assets/{use-create-conversation-DWaSVUOB.js → use-create-conversation-CRf1dCm5.js} +1 -1
- package/build/assets/{use-create-secret-BsOfOLvg.js → use-create-secret-DcpT3RPw.js} +1 -1
- package/build/assets/{use-llm-profiles-CU8ah1pz.js → use-llm-profiles-CHxqoHiN.js} +1 -1
- package/build/assets/{use-manifest-capabilities-DBYPjyV6.js → use-manifest-capabilities-zihHDbMW.js} +1 -1
- package/build/assets/{use-onboarding-completion-CtNJxkXG.js → use-onboarding-completion-6kWOW3ul.js} +1 -1
- package/build/assets/{use-plugins-marketplace-ZEA-gyM9.js → use-plugins-marketplace-CqkR8kLE.js} +1 -1
- package/build/assets/{use-save-agent-profile-DgI_Uz9a.js → use-save-agent-profile-CH9_EXQG.js} +1 -1
- package/build/assets/{use-save-settings-pSjRhD_m.js → use-save-settings-DIEurJ3D.js} +1 -1
- package/build/assets/{use-settings-BHWsOcIG.js → use-settings-BmLzQjnC.js} +1 -1
- package/build/assets/{use-settings-nav-items-CQgkxUbV.js → use-settings-nav-items-DV9qgeu7.js} +1 -1
- package/build/assets/{use-tracking-CGCsBWhg.js → use-tracking-salbPJxT.js} +1 -1
- package/build/assets/{use-user-conversation-DRfEU8x0.js → use-user-conversation-Dt-W6uNw.js} +1 -1
- package/build/assets/{verification-settings-BItwt998.js → verification-settings-oi54wEc1.js} +1 -1
- package/build/index.html +4 -4
- package/config/defaults.json +1 -1
- package/dist/components/features/chat/components/chat-input-llm-profile-picker.cjs +1 -1
- package/dist/components/features/chat/components/chat-input-llm-profile-picker.cjs.map +1 -1
- package/dist/components/features/chat/components/chat-input-llm-profile-picker.js +61 -60
- package/dist/components/features/chat/components/chat-input-llm-profile-picker.js.map +1 -1
- package/dist/components/features/chat/model-messages.cjs +1 -1
- package/dist/components/features/chat/model-messages.cjs.map +1 -1
- package/dist/components/features/chat/model-messages.js +26 -25
- package/dist/components/features/chat/model-messages.js.map +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-footer.cjs +1 -1
- package/dist/components/features/conversation-panel/conversation-card/conversation-card-footer.js +4 -4
- package/dist/components/features/settings/llm-profiles/profile-row.cjs +1 -1
- package/dist/components/features/settings/llm-profiles/profile-row.js +1 -0
- package/dist/components/shared/modals/settings/model-selector.cjs +1 -1
- package/dist/components/shared/modals/settings/model-selector.cjs.map +1 -1
- package/dist/components/shared/modals/settings/model-selector.js +154 -98
- package/dist/components/shared/modals/settings/model-selector.js.map +1 -1
- package/dist/config/defaults.cjs +1 -1
- package/dist/config/defaults.cjs.map +1 -1
- package/dist/config/defaults.js +1 -1
- package/dist/config/defaults.js.map +1 -1
- package/dist/hooks/chat/use-slash-command.cjs +1 -1
- package/dist/hooks/chat/use-slash-command.cjs.map +1 -1
- package/dist/hooks/chat/use-slash-command.js +61 -60
- package/dist/hooks/chat/use-slash-command.js.map +1 -1
- package/dist/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 +94 -93
- package/dist/routes/app-settings.js.map +1 -1
- package/dist/routes/llm-settings.cjs +1 -1
- package/dist/routes/llm-settings.cjs.map +1 -1
- package/dist/routes/llm-settings.js +131 -119
- package/dist/routes/llm-settings.js.map +1 -1
- package/dist/utils/format-model-name.cjs +1 -1
- package/dist/utils/format-model-name.cjs.map +1 -1
- package/dist/utils/format-model-name.d.ts +11 -0
- package/dist/utils/format-model-name.js +11 -3
- package/dist/utils/format-model-name.js.map +1 -1
- package/package.json +1 -1
- package/build/assets/agent-settings-BJHryp65.js +0 -1
- package/build/assets/app-settings-r1RUbgGi.js +0 -1
- package/build/assets/backend-form-modal-OroTqnAv.js +0 -1
- package/build/assets/conversation-panel-pzAKsSDb.js +0 -1
- package/build/assets/conversation-pzAKsSDb.js +0 -1
- package/build/assets/git-repo-dropdown-B_PTq4ol.js +0 -1
- package/build/assets/home-r89Vj2td.js +0 -1
- package/build/assets/index-home-r89Vj2td.js +0 -1
- package/build/assets/llm-not-configured-banner-CwKu8C6g.js +0 -27
- package/build/assets/llm-settings-BJotLQND.js +0 -1
- package/build/assets/llm-settings-CDM-5H2A.js +0 -1
- package/build/assets/manifest-2a36a8f8.js +0 -1
- package/build/assets/messages-D1cbVhRK.js +0 -40
- package/build/assets/root-BfM_kC_d.css +0 -1
- package/build/assets/root-gstBEjty.js +0 -2
- package/build/assets/root-layout-BIAFLP9p.js +0 -2
|
@@ -1,137 +1,193 @@
|
|
|
1
1
|
import { useTranslation as e } from "../../../../node_modules/react-i18next/dist/es/useTranslation.js";
|
|
2
2
|
import { I18nKey as t } from "../../../../i18n/declaration.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
3
|
+
import { Info as n } from "../../../../node_modules/lucide-react/dist/esm/icons/info.js";
|
|
4
|
+
import { PRODUCT_URL as r } from "../../../../utils/constants.js";
|
|
5
|
+
import { cn as i } from "../../../../utils/utils.js";
|
|
6
|
+
import { formControlSettingsFieldClassName as a } from "../../../../utils/form-control-classes.js";
|
|
7
|
+
import { FREE_MODEL_BADGE_LABEL as o, FREE_OPENHANDS_MODEL_NOTE as s, isFreeOpenHandsModel as c } from "../../../../utils/format-model-name.js";
|
|
8
|
+
import { listbox_item_base_default as l } from "../../../../node_modules/@heroui/listbox/dist/chunk-BJFJ4DRR.js";
|
|
9
|
+
import { listbox_section_base_default as u } from "../../../../node_modules/@heroui/listbox/dist/chunk-ANTZPR32.js";
|
|
10
|
+
import { autocomplete_default as d } from "../../../../node_modules/@heroui/autocomplete/dist/chunk-3QKDXXDY.js";
|
|
11
|
+
import { heroUiAutocompleteSelectorButtonClassName as f } from "../../../../ui/combobox-caret.js";
|
|
12
|
+
import { mapProvider as p } from "../../../../utils/map-provider.js";
|
|
13
|
+
import { extractModelAndProvider as m } from "../../../../utils/extract-model-and-provider.js";
|
|
14
|
+
import { HelpLink as h } from "../../../../ui/help-link.js";
|
|
15
|
+
import { useSearchProviders as g } from "../../../../hooks/query/use-search-providers.js";
|
|
16
|
+
import { useProviderModels as _ } from "../../../../hooks/query/use-provider-models.js";
|
|
17
|
+
import v from "react";
|
|
18
|
+
import { Fragment as y, jsx as b, jsxs as x } from "react/jsx-runtime";
|
|
17
19
|
//#region src/components/shared/modals/settings/model-selector.tsx
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
var S = "shrink-0 rounded-full border border-warning/40 bg-warning/10 px-1.5 py-0.5 text-[10px] leading-none text-warning";
|
|
21
|
+
function C() {
|
|
22
|
+
return /* @__PURE__ */ x("p", {
|
|
23
|
+
"data-testid": "openhands-free-models-note",
|
|
24
|
+
className: "flex items-start gap-2 text-xs text-warning",
|
|
25
|
+
children: [/* @__PURE__ */ b(n, {
|
|
26
|
+
className: "mt-0.5 size-4 shrink-0 text-warning",
|
|
27
|
+
"aria-hidden": !0
|
|
28
|
+
}), /* @__PURE__ */ b("span", { children: s })]
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function w({ isDisabled: n, currentModel: s, onChange: w, onDefaultValuesChanged: T, wrapperClassName: E, labelClassName: D }) {
|
|
32
|
+
let [, O] = v.useState(null), [k, A] = v.useState(null), [j, M] = v.useState(null), { data: N = [] } = g(), { data: P = [], isLoading: F, error: I } = _(k), L = v.useMemo(() => N.filter((e) => e.verified), [N]), R = v.useMemo(() => N.filter((e) => !e.verified), [N]), z = v.useMemo(() => P.filter((e) => e.verified), [P]), B = v.useMemo(() => P.filter((e) => !e.verified), [P]);
|
|
33
|
+
v.useEffect(() => {
|
|
34
|
+
if (s) {
|
|
35
|
+
let { provider: e, model: t } = m(s);
|
|
36
|
+
O(s), A(e || null), M(t), T?.(e || null, t);
|
|
24
37
|
}
|
|
25
|
-
}, [
|
|
26
|
-
let
|
|
27
|
-
|
|
28
|
-
},
|
|
29
|
-
let t = `${
|
|
30
|
-
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
}, {
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
}, [s]);
|
|
39
|
+
let V = (e) => {
|
|
40
|
+
A(e), M(null), O(`${e}/`), w?.(e, null);
|
|
41
|
+
}, H = (e) => {
|
|
42
|
+
let t = `${k}/${e}`;
|
|
43
|
+
k === "openai" && (t = e), O(t), M(e), w?.(k, e);
|
|
44
|
+
}, U = () => {
|
|
45
|
+
A(null), O(null);
|
|
46
|
+
}, W = c(k && j ? `${k}/${j}` : null), G = v.useRef(null), [K, q] = v.useState(0);
|
|
47
|
+
v.useLayoutEffect(() => {
|
|
48
|
+
if (!W || !G.current) {
|
|
49
|
+
q(0);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
let e = () => {
|
|
53
|
+
q(Math.ceil(G.current?.getBoundingClientRect().width ?? 0));
|
|
54
|
+
};
|
|
55
|
+
if (e(), typeof ResizeObserver > "u") return;
|
|
56
|
+
let t = new ResizeObserver(e);
|
|
57
|
+
return t.observe(G.current), () => t.disconnect();
|
|
58
|
+
}, [W, j]);
|
|
59
|
+
let { t: J } = e("openhands");
|
|
60
|
+
return /* @__PURE__ */ x("div", {
|
|
61
|
+
className: i("flex flex-col md:flex-row w-full min-w-0 justify-between gap-4 md:gap-[46px]", E),
|
|
36
62
|
children: [
|
|
37
|
-
/* @__PURE__ */
|
|
63
|
+
/* @__PURE__ */ x("fieldset", {
|
|
38
64
|
className: "flex flex-col gap-2.5 w-full",
|
|
39
|
-
children: [/* @__PURE__ */
|
|
40
|
-
className:
|
|
41
|
-
children:
|
|
42
|
-
}), /* @__PURE__ */
|
|
65
|
+
children: [/* @__PURE__ */ b("label", {
|
|
66
|
+
className: i("text-sm", D),
|
|
67
|
+
children: J(t.LLM$PROVIDER)
|
|
68
|
+
}), /* @__PURE__ */ x(d, {
|
|
43
69
|
"data-testid": "llm-provider-input",
|
|
44
70
|
isRequired: !0,
|
|
45
71
|
isVirtualized: !1,
|
|
46
72
|
name: "llm-provider-input",
|
|
47
|
-
isDisabled:
|
|
48
|
-
"aria-label":
|
|
73
|
+
isDisabled: n,
|
|
74
|
+
"aria-label": J(t.LLM$PROVIDER),
|
|
49
75
|
isClearable: !1,
|
|
50
76
|
onSelectionChange: (e) => {
|
|
51
|
-
e?.toString() &&
|
|
77
|
+
e?.toString() && V(e.toString());
|
|
52
78
|
},
|
|
53
|
-
onInputChange: (e) => !e &&
|
|
54
|
-
defaultSelectedKey:
|
|
55
|
-
selectedKey:
|
|
79
|
+
onInputChange: (e) => !e && U(),
|
|
80
|
+
defaultSelectedKey: k ?? void 0,
|
|
81
|
+
selectedKey: k,
|
|
56
82
|
classNames: {
|
|
57
83
|
popoverContent: "bg-content1 rounded-xl border border-[var(--oh-border)]",
|
|
58
|
-
selectorButton:
|
|
84
|
+
selectorButton: f
|
|
59
85
|
},
|
|
60
86
|
selectorButtonProps: { disableRipple: !0 },
|
|
61
|
-
inputProps: { classNames: { inputWrapper:
|
|
62
|
-
children: [/* @__PURE__ */
|
|
63
|
-
title:
|
|
87
|
+
inputProps: { classNames: { inputWrapper: a } },
|
|
88
|
+
children: [/* @__PURE__ */ b(u, {
|
|
89
|
+
title: J(t.MODEL_SELECTOR$VERIFIED),
|
|
64
90
|
classNames: { heading: "text-[var(--oh-muted)]" },
|
|
65
|
-
children:
|
|
91
|
+
children: L.map((e) => /* @__PURE__ */ b(l, {
|
|
66
92
|
"data-testid": `provider-item-${e.name}`,
|
|
67
|
-
children:
|
|
93
|
+
children: p(e.name)
|
|
68
94
|
}, e.name))
|
|
69
|
-
}),
|
|
70
|
-
title:
|
|
95
|
+
}), R.length > 0 ? /* @__PURE__ */ b(u, {
|
|
96
|
+
title: J(t.MODEL_SELECTOR$OTHERS),
|
|
71
97
|
classNames: { heading: "text-[var(--oh-muted)]" },
|
|
72
|
-
children:
|
|
98
|
+
children: R.map((e) => /* @__PURE__ */ b(l, { children: p(e.name) }, e.name))
|
|
73
99
|
}) : null]
|
|
74
100
|
})]
|
|
75
101
|
}),
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
102
|
+
k === "openhands" && /* @__PURE__ */ b("div", {
|
|
103
|
+
className: "flex flex-col gap-2",
|
|
104
|
+
children: /* @__PURE__ */ b(h, {
|
|
105
|
+
testId: "openhands-account-help",
|
|
106
|
+
text: J(t.SETTINGS$NEED_OPENHANDS_ACCOUNT),
|
|
107
|
+
linkText: J(t.SETTINGS$CLICK_HERE),
|
|
108
|
+
href: r.PRODUCTION,
|
|
109
|
+
size: "settings",
|
|
110
|
+
linkColor: "white"
|
|
111
|
+
})
|
|
83
112
|
}),
|
|
84
|
-
/* @__PURE__ */
|
|
113
|
+
/* @__PURE__ */ x("fieldset", {
|
|
85
114
|
className: "flex flex-col gap-2.5 w-full",
|
|
86
115
|
children: [
|
|
87
|
-
/* @__PURE__ */
|
|
88
|
-
className:
|
|
89
|
-
children:
|
|
116
|
+
/* @__PURE__ */ b("label", {
|
|
117
|
+
className: i("text-sm", D),
|
|
118
|
+
children: J(t.LLM$MODEL)
|
|
90
119
|
}),
|
|
91
|
-
/* @__PURE__ */
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
/* @__PURE__ */ x("div", {
|
|
121
|
+
className: "relative",
|
|
122
|
+
children: [/* @__PURE__ */ x(d, {
|
|
123
|
+
"data-testid": "llm-model-input",
|
|
124
|
+
isRequired: !0,
|
|
125
|
+
isVirtualized: !1,
|
|
126
|
+
isLoading: F,
|
|
127
|
+
name: "llm-model-input",
|
|
128
|
+
"aria-label": J(t.LLM$MODEL),
|
|
129
|
+
isClearable: !1,
|
|
130
|
+
onSelectionChange: (e) => {
|
|
131
|
+
e?.toString() && H(e.toString());
|
|
132
|
+
},
|
|
133
|
+
isDisabled: n || !k,
|
|
134
|
+
selectedKey: j,
|
|
135
|
+
defaultSelectedKey: j ?? void 0,
|
|
136
|
+
classNames: {
|
|
137
|
+
popoverContent: "bg-content1 rounded-xl border border-[var(--oh-border)]",
|
|
138
|
+
selectorButton: f
|
|
139
|
+
},
|
|
140
|
+
selectorButtonProps: { disableRipple: !0 },
|
|
141
|
+
inputProps: { classNames: { inputWrapper: a } },
|
|
142
|
+
children: [/* @__PURE__ */ b(u, {
|
|
143
|
+
title: J(t.MODEL_SELECTOR$VERIFIED),
|
|
144
|
+
classNames: { heading: "text-[var(--oh-muted)]" },
|
|
145
|
+
children: z.map((e) => /* @__PURE__ */ b(l, {
|
|
146
|
+
textValue: e.name,
|
|
147
|
+
children: /* @__PURE__ */ x("span", {
|
|
148
|
+
className: "flex min-w-0 items-center gap-2",
|
|
149
|
+
children: [/* @__PURE__ */ b("span", {
|
|
150
|
+
className: "truncate",
|
|
151
|
+
children: e.name
|
|
152
|
+
}), c(`${k}/${e.name}`) ? /* @__PURE__ */ b("span", {
|
|
153
|
+
className: S,
|
|
154
|
+
children: o
|
|
155
|
+
}) : null]
|
|
156
|
+
})
|
|
157
|
+
}, e.name))
|
|
158
|
+
}), B.length > 0 ? /* @__PURE__ */ b(u, {
|
|
159
|
+
title: J(t.MODEL_SELECTOR$OTHERS),
|
|
160
|
+
classNames: { heading: "text-[var(--oh-muted)]" },
|
|
161
|
+
children: B.map((e) => /* @__PURE__ */ b(l, {
|
|
162
|
+
"data-testid": `model-item-${e.name}`,
|
|
163
|
+
textValue: e.name,
|
|
164
|
+
children: e.name
|
|
165
|
+
}, e.name))
|
|
166
|
+
}) : null]
|
|
167
|
+
}), W && j ? /* @__PURE__ */ x(y, { children: [/* @__PURE__ */ b("span", {
|
|
168
|
+
ref: G,
|
|
169
|
+
className: "pointer-events-none absolute left-3 top-1/2 whitespace-pre text-sm opacity-0",
|
|
170
|
+
"aria-hidden": !0,
|
|
171
|
+
children: j
|
|
172
|
+
}), /* @__PURE__ */ b("span", {
|
|
173
|
+
"data-testid": "selected-free-model-badge",
|
|
174
|
+
className: i(S, "pointer-events-none absolute top-1/2 z-10 -translate-y-1/2"),
|
|
175
|
+
style: { left: `calc(0.75rem + ${K}px + 0.5rem)` },
|
|
176
|
+
children: o
|
|
177
|
+
})] }) : null]
|
|
123
178
|
}),
|
|
124
|
-
|
|
179
|
+
I && /* @__PURE__ */ b("p", {
|
|
125
180
|
"data-testid": "models-error",
|
|
126
181
|
className: "text-danger text-xs",
|
|
127
|
-
children:
|
|
128
|
-
})
|
|
182
|
+
children: J(t.CONFIGURATION$ERROR_FETCH_MODELS)
|
|
183
|
+
}),
|
|
184
|
+
k === "openhands" ? /* @__PURE__ */ b(C, {}) : null
|
|
129
185
|
]
|
|
130
186
|
})
|
|
131
187
|
]
|
|
132
188
|
});
|
|
133
189
|
}
|
|
134
190
|
//#endregion
|
|
135
|
-
export {
|
|
191
|
+
export { w as ModelSelector };
|
|
136
192
|
|
|
137
193
|
//# sourceMappingURL=model-selector.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-selector.js","names":[],"sources":["../../../../../src/components/shared/modals/settings/model-selector.tsx"],"sourcesContent":["import {\n Autocomplete,\n AutocompleteItem,\n AutocompleteSection,\n} from \"@heroui/react\";\nimport React from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { mapProvider } from \"#/utils/map-provider\";\nimport { extractModelAndProvider } from \"#/utils/extract-model-and-provider\";\nimport { cn } from \"#/utils/utils\";\nimport { formControlSettingsFieldClassName } from \"#/utils/form-control-classes\";\nimport { heroUiAutocompleteSelectorButtonClassName } from \"#/ui/combobox-caret\";\nimport { HelpLink } from \"#/ui/help-link\";\nimport { PRODUCT_URL } from \"#/utils/constants\";\nimport { useSearchProviders } from \"#/hooks/query/use-search-providers\";\nimport { useProviderModels } from \"#/hooks/query/use-provider-models\";\n\ninterface ModelSelectorProps {\n isDisabled?: boolean;\n currentModel?: string;\n onChange?: (provider: string | null, model: string | null) => void;\n onDefaultValuesChanged?: (\n provider: string | null,\n model: string | null,\n ) => void;\n wrapperClassName?: string;\n labelClassName?: string;\n}\n\nexport function ModelSelector({\n isDisabled,\n currentModel,\n onChange,\n onDefaultValuesChanged,\n wrapperClassName,\n labelClassName,\n}: ModelSelectorProps) {\n const [, setLitellmId] = React.useState<string | null>(null);\n const [selectedProvider, setSelectedProvider] = React.useState<string | null>(\n null,\n );\n const [selectedModel, setSelectedModel] = React.useState<string | null>(null);\n\n const { data: providers = [] } = useSearchProviders();\n const {\n data: providerModels = [],\n isLoading: isLoadingModels,\n error: modelsError,\n } = useProviderModels(selectedProvider);\n\n const verifiedProviders = React.useMemo(\n () => providers.filter((p) => p.verified),\n [providers],\n );\n const unverifiedProviders = React.useMemo(\n () => providers.filter((p) => !p.verified),\n [providers],\n );\n\n const verifiedModels = React.useMemo(\n () => providerModels.filter((m) => m.verified),\n [providerModels],\n );\n const unverifiedModels = React.useMemo(\n () => providerModels.filter((m) => !m.verified),\n [providerModels],\n );\n\n React.useEffect(() => {\n if (currentModel) {\n const { provider, model } = extractModelAndProvider(currentModel);\n\n setLitellmId(currentModel);\n setSelectedProvider(provider || null);\n setSelectedModel(model);\n onDefaultValuesChanged?.(provider || null, model);\n }\n }, [currentModel]);\n\n const handleChangeProvider = (provider: string) => {\n setSelectedProvider(provider);\n setSelectedModel(null);\n setLitellmId(`${provider}/`);\n onChange?.(provider, null);\n };\n\n const handleChangeModel = (model: string) => {\n let fullModel = `${selectedProvider}/${model}`;\n if (selectedProvider === \"openai\") {\n fullModel = model;\n }\n setLitellmId(fullModel);\n setSelectedModel(model);\n onChange?.(selectedProvider, model);\n };\n\n const clear = () => {\n setSelectedProvider(null);\n setLitellmId(null);\n };\n\n const { t } = useTranslation(\"openhands\");\n\n return (\n <div\n className={cn(\n \"flex flex-col md:flex-row w-full min-w-0 justify-between gap-4 md:gap-[46px]\",\n wrapperClassName,\n )}\n >\n <fieldset className=\"flex flex-col gap-2.5 w-full\">\n <label className={cn(\"text-sm\", labelClassName)}>\n {t(I18nKey.LLM$PROVIDER)}\n </label>\n <Autocomplete\n data-testid=\"llm-provider-input\"\n isRequired\n isVirtualized={false}\n name=\"llm-provider-input\"\n isDisabled={isDisabled}\n aria-label={t(I18nKey.LLM$PROVIDER)}\n isClearable={false}\n onSelectionChange={(e) => {\n if (e?.toString()) handleChangeProvider(e.toString());\n }}\n onInputChange={(value) => !value && clear()}\n defaultSelectedKey={selectedProvider ?? undefined}\n selectedKey={selectedProvider}\n classNames={{\n popoverContent:\n \"bg-content1 rounded-xl border border-[var(--oh-border)]\",\n selectorButton: heroUiAutocompleteSelectorButtonClassName,\n }}\n selectorButtonProps={{ disableRipple: true }}\n inputProps={{\n classNames: {\n inputWrapper: formControlSettingsFieldClassName,\n },\n }}\n >\n <AutocompleteSection\n title={t(I18nKey.MODEL_SELECTOR$VERIFIED)}\n classNames={{ heading: \"text-[var(--oh-muted)]\" }}\n >\n {verifiedProviders.map((provider) => (\n <AutocompleteItem\n data-testid={`provider-item-${provider.name}`}\n key={provider.name}\n >\n {mapProvider(provider.name)}\n </AutocompleteItem>\n ))}\n </AutocompleteSection>\n {unverifiedProviders.length > 0 ? (\n <AutocompleteSection\n title={t(I18nKey.MODEL_SELECTOR$OTHERS)}\n classNames={{ heading: \"text-[var(--oh-muted)]\" }}\n >\n {unverifiedProviders.map((provider) => (\n <AutocompleteItem key={provider.name}>\n {mapProvider(provider.name)}\n </AutocompleteItem>\n ))}\n </AutocompleteSection>\n ) : null}\n </Autocomplete>\n </fieldset>\n\n {selectedProvider === \"openhands\" && (\n <HelpLink\n testId=\"openhands-account-help\"\n text={t(I18nKey.SETTINGS$NEED_OPENHANDS_ACCOUNT)}\n linkText={t(I18nKey.SETTINGS$CLICK_HERE)}\n href={PRODUCT_URL.PRODUCTION}\n size=\"settings\"\n linkColor=\"white\"\n />\n )}\n\n <fieldset className=\"flex flex-col gap-2.5 w-full\">\n <label className={cn(\"text-sm\", labelClassName)}>\n {t(I18nKey.LLM$MODEL)}\n </label>\n <Autocomplete\n data-testid=\"llm-model-input\"\n isRequired\n isVirtualized={false}\n isLoading={isLoadingModels}\n name=\"llm-model-input\"\n aria-label={t(I18nKey.LLM$MODEL)}\n isClearable={false}\n onSelectionChange={(e) => {\n if (e?.toString()) handleChangeModel(e.toString());\n }}\n isDisabled={isDisabled || !selectedProvider}\n selectedKey={selectedModel}\n defaultSelectedKey={selectedModel ?? undefined}\n classNames={{\n popoverContent:\n \"bg-content1 rounded-xl border border-[var(--oh-border)]\",\n selectorButton: heroUiAutocompleteSelectorButtonClassName,\n }}\n selectorButtonProps={{ disableRipple: true }}\n inputProps={{\n classNames: {\n inputWrapper: formControlSettingsFieldClassName,\n },\n }}\n >\n <AutocompleteSection\n title={t(I18nKey.MODEL_SELECTOR$VERIFIED)}\n classNames={{ heading: \"text-[var(--oh-muted)]\" }}\n >\n {verifiedModels.map((model) => (\n <AutocompleteItem key={model.name}>{model.name}</AutocompleteItem>\n ))}\n </AutocompleteSection>\n {unverifiedModels.length > 0 ? (\n <AutocompleteSection\n title={t(I18nKey.MODEL_SELECTOR$OTHERS)}\n classNames={{ heading: \"text-[var(--oh-muted)]\" }}\n >\n {unverifiedModels.map((model) => (\n <AutocompleteItem\n data-testid={`model-item-${model.name}`}\n key={model.name}\n >\n {model.name}\n </AutocompleteItem>\n ))}\n </AutocompleteSection>\n ) : null}\n </Autocomplete>\n {modelsError && (\n <p data-testid=\"models-error\" className=\"text-danger text-xs\">\n {t(I18nKey.CONFIGURATION$ERROR_FETCH_MODELS)}\n </p>\n )}\n </fieldset>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA8BA,SAAgB,EAAc,EAC5B,eACA,iBACA,aACA,2BACA,qBACA,qBACqB;CACrB,IAAM,GAAG,KAAgB,EAAM,SAAwB,IAAI,GACrD,CAAC,GAAkB,KAAuB,EAAM,SACpD,IACF,GACM,CAAC,GAAe,KAAoB,EAAM,SAAwB,IAAI,GAEtE,EAAE,MAAM,IAAY,CAAC,MAAM,EAAmB,GAC9C,EACJ,MAAM,IAAiB,CAAC,GACxB,WAAW,GACX,OAAO,MACL,EAAkB,CAAgB,GAEhC,IAAoB,EAAM,cACxB,EAAU,QAAQ,MAAM,EAAE,QAAQ,GACxC,CAAC,CAAS,CACZ,GACM,IAAsB,EAAM,cAC1B,EAAU,QAAQ,MAAM,CAAC,EAAE,QAAQ,GACzC,CAAC,CAAS,CACZ,GAEM,IAAiB,EAAM,cACrB,EAAe,QAAQ,MAAM,EAAE,QAAQ,GAC7C,CAAC,CAAc,CACjB,GACM,IAAmB,EAAM,cACvB,EAAe,QAAQ,MAAM,CAAC,EAAE,QAAQ,GAC9C,CAAC,CAAc,CACjB;CAEA,EAAM,gBAAgB;EACpB,IAAI,GAAc;GAChB,IAAM,EAAE,aAAU,aAAU,EAAwB,CAAY;GAKhE,AAHA,EAAa,CAAY,GACzB,EAAoB,KAAY,IAAI,GACpC,EAAiB,CAAK,GACtB,IAAyB,KAAY,MAAM,CAAK;EAClD;CACF,GAAG,CAAC,CAAY,CAAC;CAEjB,IAAM,KAAwB,MAAqB;EAIjD,AAHA,EAAoB,CAAQ,GAC5B,EAAiB,IAAI,GACrB,EAAa,GAAG,EAAS,EAAE,GAC3B,IAAW,GAAU,IAAI;CAC3B,GAEM,KAAqB,MAAkB;EAC3C,IAAI,IAAY,GAAG,EAAiB,GAAG;EAMvC,AALI,MAAqB,aACvB,IAAY,IAEd,EAAa,CAAS,GACtB,EAAiB,CAAK,GACtB,IAAW,GAAkB,CAAK;CACpC,GAEM,UAAc;EAElB,AADA,EAAoB,IAAI,GACxB,EAAa,IAAI;CACnB,GAEM,EAAE,SAAM,EAAe,WAAW;CAExC,OACE,kBAAC,OAAD;EACE,WAAW,EACT,gFACA,CACF;YAJF;GAME,kBAAC,YAAD;IAAU,WAAU;cAApB,CACE,kBAAC,SAAD;KAAO,WAAW,EAAG,WAAW,CAAc;eAC3C,EAAE,EAAQ,YAAY;IAClB,CAAA,GACP,kBAAC,GAAD;KACE,eAAY;KACZ,YAAA;KACA,eAAe;KACf,MAAK;KACO;KACZ,cAAY,EAAE,EAAQ,YAAY;KAClC,aAAa;KACb,oBAAoB,MAAM;MACxB,AAAI,GAAG,SAAS,KAAG,EAAqB,EAAE,SAAS,CAAC;KACtD;KACA,gBAAgB,MAAU,CAAC,KAAS,EAAM;KAC1C,oBAAoB,KAAoB,KAAA;KACxC,aAAa;KACb,YAAY;MACV,gBACE;MACF,gBAAgB;KAClB;KACA,qBAAqB,EAAE,eAAe,GAAK;KAC3C,YAAY,EACV,YAAY,EACV,cAAc,EAChB,EACF;eAxBF,CA0BE,kBAAC,GAAD;MACE,OAAO,EAAE,EAAQ,uBAAuB;MACxC,YAAY,EAAE,SAAS,yBAAyB;gBAE/C,EAAkB,KAAK,MACtB,kBAAC,GAAD;OACE,eAAa,iBAAiB,EAAS;iBAGtC,EAAY,EAAS,IAAI;MACV,GAHX,EAAS,IAGE,CACnB;KACkB,CAAA,GACpB,EAAoB,SAAS,IAC5B,kBAAC,GAAD;MACE,OAAO,EAAE,EAAQ,qBAAqB;MACtC,YAAY,EAAE,SAAS,yBAAyB;gBAE/C,EAAoB,KAAK,MACxB,kBAAC,GAAD,EAAA,UACG,EAAY,EAAS,IAAI,EACV,GAFK,EAAS,IAEd,CACnB;KACkB,CAAA,IACnB,IACQ;MACN;;GAET,MAAqB,eACpB,kBAAC,GAAD;IACE,QAAO;IACP,MAAM,EAAE,EAAQ,+BAA+B;IAC/C,UAAU,EAAE,EAAQ,mBAAmB;IACvC,MAAM,EAAY;IAClB,MAAK;IACL,WAAU;GACX,CAAA;GAGH,kBAAC,YAAD;IAAU,WAAU;cAApB;KACE,kBAAC,SAAD;MAAO,WAAW,EAAG,WAAW,CAAc;gBAC3C,EAAE,EAAQ,SAAS;KACf,CAAA;KACP,kBAAC,GAAD;MACE,eAAY;MACZ,YAAA;MACA,eAAe;MACf,WAAW;MACX,MAAK;MACL,cAAY,EAAE,EAAQ,SAAS;MAC/B,aAAa;MACb,oBAAoB,MAAM;OACxB,AAAI,GAAG,SAAS,KAAG,EAAkB,EAAE,SAAS,CAAC;MACnD;MACA,YAAY,KAAc,CAAC;MAC3B,aAAa;MACb,oBAAoB,KAAiB,KAAA;MACrC,YAAY;OACV,gBACE;OACF,gBAAgB;MAClB;MACA,qBAAqB,EAAE,eAAe,GAAK;MAC3C,YAAY,EACV,YAAY,EACV,cAAc,EAChB,EACF;gBAxBF,CA0BE,kBAAC,GAAD;OACE,OAAO,EAAE,EAAQ,uBAAuB;OACxC,YAAY,EAAE,SAAS,yBAAyB;iBAE/C,EAAe,KAAK,MACnB,kBAAC,GAAD,EAAA,UAAoC,EAAM,KAAuB,GAA1C,EAAM,IAAoC,CAClE;MACkB,CAAA,GACpB,EAAiB,SAAS,IACzB,kBAAC,GAAD;OACE,OAAO,EAAE,EAAQ,qBAAqB;OACtC,YAAY,EAAE,SAAS,yBAAyB;iBAE/C,EAAiB,KAAK,MACrB,kBAAC,GAAD;QACE,eAAa,cAAc,EAAM;kBAGhC,EAAM;OACS,GAHX,EAAM,IAGK,CACnB;MACkB,CAAA,IACnB,IACQ;;KACb,KACC,kBAAC,KAAD;MAAG,eAAY;MAAe,WAAU;gBACrC,EAAE,EAAQ,gCAAgC;KAC1C,CAAA;IAEG;;EACP;;AAET"}
|
|
1
|
+
{"version":3,"file":"model-selector.js","names":[],"sources":["../../../../../src/components/shared/modals/settings/model-selector.tsx"],"sourcesContent":["import {\n Autocomplete,\n AutocompleteItem,\n AutocompleteSection,\n} from \"@heroui/react\";\nimport { Info } from \"lucide-react\";\nimport React from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { mapProvider } from \"#/utils/map-provider\";\nimport { extractModelAndProvider } from \"#/utils/extract-model-and-provider\";\nimport { cn } from \"#/utils/utils\";\nimport { formControlSettingsFieldClassName } from \"#/utils/form-control-classes\";\nimport { heroUiAutocompleteSelectorButtonClassName } from \"#/ui/combobox-caret\";\nimport { HelpLink } from \"#/ui/help-link\";\nimport { PRODUCT_URL } from \"#/utils/constants\";\nimport { useSearchProviders } from \"#/hooks/query/use-search-providers\";\nimport { useProviderModels } from \"#/hooks/query/use-provider-models\";\nimport {\n FREE_MODEL_BADGE_LABEL,\n FREE_OPENHANDS_MODEL_NOTE,\n isFreeOpenHandsModel,\n} from \"#/utils/format-model-name\";\n\nconst freeModelBadgeClassName =\n \"shrink-0 rounded-full border border-warning/40 bg-warning/10 px-1.5 py-0.5 text-[10px] leading-none text-warning\";\n\nfunction FreeOpenHandsModelsNote() {\n return (\n <p\n data-testid=\"openhands-free-models-note\"\n className=\"flex items-start gap-2 text-xs text-warning\"\n >\n <Info className=\"mt-0.5 size-4 shrink-0 text-warning\" aria-hidden />\n <span>{FREE_OPENHANDS_MODEL_NOTE}</span>\n </p>\n );\n}\n\ninterface ModelSelectorProps {\n isDisabled?: boolean;\n currentModel?: string;\n onChange?: (provider: string | null, model: string | null) => void;\n onDefaultValuesChanged?: (\n provider: string | null,\n model: string | null,\n ) => void;\n wrapperClassName?: string;\n labelClassName?: string;\n}\n\nexport function ModelSelector({\n isDisabled,\n currentModel,\n onChange,\n onDefaultValuesChanged,\n wrapperClassName,\n labelClassName,\n}: ModelSelectorProps) {\n const [, setLitellmId] = React.useState<string | null>(null);\n const [selectedProvider, setSelectedProvider] = React.useState<string | null>(\n null,\n );\n const [selectedModel, setSelectedModel] = React.useState<string | null>(null);\n\n const { data: providers = [] } = useSearchProviders();\n const {\n data: providerModels = [],\n isLoading: isLoadingModels,\n error: modelsError,\n } = useProviderModels(selectedProvider);\n\n const verifiedProviders = React.useMemo(\n () => providers.filter((p) => p.verified),\n [providers],\n );\n const unverifiedProviders = React.useMemo(\n () => providers.filter((p) => !p.verified),\n [providers],\n );\n\n const verifiedModels = React.useMemo(\n () => providerModels.filter((m) => m.verified),\n [providerModels],\n );\n const unverifiedModels = React.useMemo(\n () => providerModels.filter((m) => !m.verified),\n [providerModels],\n );\n\n React.useEffect(() => {\n if (currentModel) {\n const { provider, model } = extractModelAndProvider(currentModel);\n\n setLitellmId(currentModel);\n setSelectedProvider(provider || null);\n setSelectedModel(model);\n onDefaultValuesChanged?.(provider || null, model);\n }\n }, [currentModel]);\n\n const handleChangeProvider = (provider: string) => {\n setSelectedProvider(provider);\n setSelectedModel(null);\n setLitellmId(`${provider}/`);\n onChange?.(provider, null);\n };\n\n const handleChangeModel = (model: string) => {\n let fullModel = `${selectedProvider}/${model}`;\n if (selectedProvider === \"openai\") {\n fullModel = model;\n }\n setLitellmId(fullModel);\n setSelectedModel(model);\n onChange?.(selectedProvider, model);\n };\n\n const clear = () => {\n setSelectedProvider(null);\n setLitellmId(null);\n };\n\n const selectedFullModel =\n selectedProvider && selectedModel\n ? `${selectedProvider}/${selectedModel}`\n : null;\n const isSelectedModelFree = isFreeOpenHandsModel(selectedFullModel);\n const selectedModelMeasureRef = React.useRef<HTMLSpanElement>(null);\n const [selectedModelTextWidth, setSelectedModelTextWidth] = React.useState(0);\n\n React.useLayoutEffect(() => {\n if (!isSelectedModelFree || !selectedModelMeasureRef.current) {\n setSelectedModelTextWidth(0);\n return undefined;\n }\n\n const measureSelectedModel = () => {\n setSelectedModelTextWidth(\n Math.ceil(\n selectedModelMeasureRef.current?.getBoundingClientRect().width ?? 0,\n ),\n );\n };\n measureSelectedModel();\n\n if (typeof ResizeObserver === \"undefined\") {\n return undefined;\n }\n\n const observer = new ResizeObserver(measureSelectedModel);\n observer.observe(selectedModelMeasureRef.current);\n return () => observer.disconnect();\n }, [isSelectedModelFree, selectedModel]);\n\n const { t } = useTranslation(\"openhands\");\n\n return (\n <div\n className={cn(\n \"flex flex-col md:flex-row w-full min-w-0 justify-between gap-4 md:gap-[46px]\",\n wrapperClassName,\n )}\n >\n <fieldset className=\"flex flex-col gap-2.5 w-full\">\n <label className={cn(\"text-sm\", labelClassName)}>\n {t(I18nKey.LLM$PROVIDER)}\n </label>\n <Autocomplete\n data-testid=\"llm-provider-input\"\n isRequired\n isVirtualized={false}\n name=\"llm-provider-input\"\n isDisabled={isDisabled}\n aria-label={t(I18nKey.LLM$PROVIDER)}\n isClearable={false}\n onSelectionChange={(e) => {\n if (e?.toString()) handleChangeProvider(e.toString());\n }}\n onInputChange={(value) => !value && clear()}\n defaultSelectedKey={selectedProvider ?? undefined}\n selectedKey={selectedProvider}\n classNames={{\n popoverContent:\n \"bg-content1 rounded-xl border border-[var(--oh-border)]\",\n selectorButton: heroUiAutocompleteSelectorButtonClassName,\n }}\n selectorButtonProps={{ disableRipple: true }}\n inputProps={{\n classNames: {\n inputWrapper: formControlSettingsFieldClassName,\n },\n }}\n >\n <AutocompleteSection\n title={t(I18nKey.MODEL_SELECTOR$VERIFIED)}\n classNames={{ heading: \"text-[var(--oh-muted)]\" }}\n >\n {verifiedProviders.map((provider) => (\n <AutocompleteItem\n data-testid={`provider-item-${provider.name}`}\n key={provider.name}\n >\n {mapProvider(provider.name)}\n </AutocompleteItem>\n ))}\n </AutocompleteSection>\n {unverifiedProviders.length > 0 ? (\n <AutocompleteSection\n title={t(I18nKey.MODEL_SELECTOR$OTHERS)}\n classNames={{ heading: \"text-[var(--oh-muted)]\" }}\n >\n {unverifiedProviders.map((provider) => (\n <AutocompleteItem key={provider.name}>\n {mapProvider(provider.name)}\n </AutocompleteItem>\n ))}\n </AutocompleteSection>\n ) : null}\n </Autocomplete>\n </fieldset>\n\n {selectedProvider === \"openhands\" && (\n <div className=\"flex flex-col gap-2\">\n <HelpLink\n testId=\"openhands-account-help\"\n text={t(I18nKey.SETTINGS$NEED_OPENHANDS_ACCOUNT)}\n linkText={t(I18nKey.SETTINGS$CLICK_HERE)}\n href={PRODUCT_URL.PRODUCTION}\n size=\"settings\"\n linkColor=\"white\"\n />\n </div>\n )}\n\n <fieldset className=\"flex flex-col gap-2.5 w-full\">\n <label className={cn(\"text-sm\", labelClassName)}>\n {t(I18nKey.LLM$MODEL)}\n </label>\n <div className=\"relative\">\n <Autocomplete\n data-testid=\"llm-model-input\"\n isRequired\n isVirtualized={false}\n isLoading={isLoadingModels}\n name=\"llm-model-input\"\n aria-label={t(I18nKey.LLM$MODEL)}\n isClearable={false}\n onSelectionChange={(e) => {\n if (e?.toString()) handleChangeModel(e.toString());\n }}\n isDisabled={isDisabled || !selectedProvider}\n selectedKey={selectedModel}\n defaultSelectedKey={selectedModel ?? undefined}\n classNames={{\n popoverContent:\n \"bg-content1 rounded-xl border border-[var(--oh-border)]\",\n selectorButton: heroUiAutocompleteSelectorButtonClassName,\n }}\n selectorButtonProps={{ disableRipple: true }}\n inputProps={{\n classNames: {\n inputWrapper: formControlSettingsFieldClassName,\n },\n }}\n >\n <AutocompleteSection\n title={t(I18nKey.MODEL_SELECTOR$VERIFIED)}\n classNames={{ heading: \"text-[var(--oh-muted)]\" }}\n >\n {verifiedModels.map((model) => (\n <AutocompleteItem key={model.name} textValue={model.name}>\n <span className=\"flex min-w-0 items-center gap-2\">\n <span className=\"truncate\">{model.name}</span>\n {isFreeOpenHandsModel(\n `${selectedProvider}/${model.name}`,\n ) ? (\n <span className={freeModelBadgeClassName}>\n {FREE_MODEL_BADGE_LABEL}\n </span>\n ) : null}\n </span>\n </AutocompleteItem>\n ))}\n </AutocompleteSection>\n {unverifiedModels.length > 0 ? (\n <AutocompleteSection\n title={t(I18nKey.MODEL_SELECTOR$OTHERS)}\n classNames={{ heading: \"text-[var(--oh-muted)]\" }}\n >\n {unverifiedModels.map((model) => (\n <AutocompleteItem\n data-testid={`model-item-${model.name}`}\n key={model.name}\n textValue={model.name}\n >\n {model.name}\n </AutocompleteItem>\n ))}\n </AutocompleteSection>\n ) : null}\n </Autocomplete>\n {isSelectedModelFree && selectedModel ? (\n <>\n <span\n ref={selectedModelMeasureRef}\n className=\"pointer-events-none absolute left-3 top-1/2 whitespace-pre text-sm opacity-0\"\n aria-hidden\n >\n {selectedModel}\n </span>\n <span\n data-testid=\"selected-free-model-badge\"\n className={cn(\n freeModelBadgeClassName,\n \"pointer-events-none absolute top-1/2 z-10 -translate-y-1/2\",\n )}\n style={{\n left: `calc(0.75rem + ${selectedModelTextWidth}px + 0.5rem)`,\n }}\n >\n {FREE_MODEL_BADGE_LABEL}\n </span>\n </>\n ) : null}\n </div>\n {modelsError && (\n <p data-testid=\"models-error\" className=\"text-danger text-xs\">\n {t(I18nKey.CONFIGURATION$ERROR_FETCH_MODELS)}\n </p>\n )}\n {selectedProvider === \"openhands\" ? <FreeOpenHandsModelsNote /> : null}\n </fieldset>\n </div>\n );\n}\n"],"mappings":"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"}
|
package/dist/config/defaults.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require("../_virtual/_rolldown/runtime.cjs");var e={_comment:`Single source of truth for version pins, ports, paths, and defaults shared across the npm and Docker install paths. Read by scripts/dev-safe.mjs, scripts/dev-with-automation.mjs, docker/entrypoint.sh (via generated defaults.env), and .github/workflows/docker.yml.`,versions:{agentServer:`1.40.1`,agentCanvas:`1.
|
|
1
|
+
require("../_virtual/_rolldown/runtime.cjs");var e={_comment:`Single source of truth for version pins, ports, paths, and defaults shared across the npm and Docker install paths. Read by scripts/dev-safe.mjs, scripts/dev-with-automation.mjs, docker/entrypoint.sh (via generated defaults.env), and .github/workflows/docker.yml.`,versions:{agentServer:`1.40.1`,agentCanvas:`1.12.0`,automation:`1.6.0`},compatibility:{minimumAgentServer:`1.28.0`},constraints:{_comment:`Temporary upper bound on the agent-client-protocol (acp) transitive dependency. openhands-sdk 1.35.0 requires agent-client-protocol>=0.10.1 with no upper bound, but acp 0.11.0 reordered the ACP prompt() arguments and breaks the SDK's ACP client (a PromptRequest validation error surfaces as 'ACP error: 2 validation errors for PromptRequest'). Pin acp <0.11 until a fixed openhands-sdk ships. Consumed by scripts/dev-safe.mjs (agent-server uvx install) and .github/workflows/mock-llm-e2e.yml (mock ACP server venv).`,agentClientProtocol:`agent-client-protocol<0.11`},images:{agentServer:`ghcr.io/openhands/agent-server`,agentCanvas:`ghcr.io/openhands/agent-canvas`},ports:{agentServer:18e3,automation:18001,proxy:8e3},paths:{stateSubdir:`agent-canvas`,conversations:`agent-canvas/conversations`,bashEvents:`agent-canvas/bash_events`,automationDb:`automation/automations.db`,canvasBasePath:`/canvas`},packages:{agentServer:`openhands-agent-server`,automation:`openhands-automation`,tools:`openhands-tools`,workspace:`openhands-workspace`},telemetry:{posthogApiKey:`phc_kBtz5nKmxVRRQ7HtPwr2QX9eMC5j65zE86QKocVNwb4U`,posthogHost:`https://us.i.posthog.com`}};exports.default=e;
|
|
2
2
|
//# sourceMappingURL=defaults.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.cjs","names":[],"sources":["../../config/defaults.json"],"sourcesContent":["{\n \"_comment\": \"Single source of truth for version pins, ports, paths, and defaults shared across the npm and Docker install paths. Read by scripts/dev-safe.mjs, scripts/dev-with-automation.mjs, docker/entrypoint.sh (via generated defaults.env), and .github/workflows/docker.yml.\",\n \"versions\": {\n \"agentServer\": \"1.40.1\",\n \"agentCanvas\": \"1.
|
|
1
|
+
{"version":3,"file":"defaults.cjs","names":[],"sources":["../../config/defaults.json"],"sourcesContent":["{\n \"_comment\": \"Single source of truth for version pins, ports, paths, and defaults shared across the npm and Docker install paths. Read by scripts/dev-safe.mjs, scripts/dev-with-automation.mjs, docker/entrypoint.sh (via generated defaults.env), and .github/workflows/docker.yml.\",\n \"versions\": {\n \"agentServer\": \"1.40.1\",\n \"agentCanvas\": \"1.12.0\",\n \"automation\": \"1.6.0\"\n },\n \"compatibility\": {\n \"minimumAgentServer\": \"1.28.0\"\n },\n \"constraints\": {\n \"_comment\": \"Temporary upper bound on the agent-client-protocol (acp) transitive dependency. openhands-sdk 1.35.0 requires agent-client-protocol>=0.10.1 with no upper bound, but acp 0.11.0 reordered the ACP prompt() arguments and breaks the SDK's ACP client (a PromptRequest validation error surfaces as 'ACP error: 2 validation errors for PromptRequest'). Pin acp <0.11 until a fixed openhands-sdk ships. Consumed by scripts/dev-safe.mjs (agent-server uvx install) and .github/workflows/mock-llm-e2e.yml (mock ACP server venv).\",\n \"agentClientProtocol\": \"agent-client-protocol<0.11\"\n },\n \"images\": {\n \"agentServer\": \"ghcr.io/openhands/agent-server\",\n \"agentCanvas\": \"ghcr.io/openhands/agent-canvas\"\n },\n \"ports\": {\n \"agentServer\": 18000,\n \"automation\": 18001,\n \"proxy\": 8000\n },\n \"paths\": {\n \"stateSubdir\": \"agent-canvas\",\n \"conversations\": \"agent-canvas/conversations\",\n \"bashEvents\": \"agent-canvas/bash_events\",\n \"automationDb\": \"automation/automations.db\",\n \"canvasBasePath\": \"/canvas\"\n },\n \"packages\": {\n \"agentServer\": \"openhands-agent-server\",\n \"automation\": \"openhands-automation\",\n \"tools\": \"openhands-tools\",\n \"workspace\": \"openhands-workspace\"\n },\n \"telemetry\": {\n \"posthogApiKey\": \"phc_kBtz5nKmxVRRQ7HtPwr2QX9eMC5j65zE86QKocVNwb4U\",\n \"posthogHost\": \"https://us.i.posthog.com\"\n }\n}\n"],"mappings":""}
|
package/dist/config/defaults.js
CHANGED
|
@@ -2,7 +2,7 @@ var e = {
|
|
|
2
2
|
_comment: "Single source of truth for version pins, ports, paths, and defaults shared across the npm and Docker install paths. Read by scripts/dev-safe.mjs, scripts/dev-with-automation.mjs, docker/entrypoint.sh (via generated defaults.env), and .github/workflows/docker.yml.",
|
|
3
3
|
versions: {
|
|
4
4
|
agentServer: "1.40.1",
|
|
5
|
-
agentCanvas: "1.
|
|
5
|
+
agentCanvas: "1.12.0",
|
|
6
6
|
automation: "1.6.0"
|
|
7
7
|
},
|
|
8
8
|
compatibility: { minimumAgentServer: "1.28.0" },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.js","names":[],"sources":["../../config/defaults.json"],"sourcesContent":["{\n \"_comment\": \"Single source of truth for version pins, ports, paths, and defaults shared across the npm and Docker install paths. Read by scripts/dev-safe.mjs, scripts/dev-with-automation.mjs, docker/entrypoint.sh (via generated defaults.env), and .github/workflows/docker.yml.\",\n \"versions\": {\n \"agentServer\": \"1.40.1\",\n \"agentCanvas\": \"1.
|
|
1
|
+
{"version":3,"file":"defaults.js","names":[],"sources":["../../config/defaults.json"],"sourcesContent":["{\n \"_comment\": \"Single source of truth for version pins, ports, paths, and defaults shared across the npm and Docker install paths. Read by scripts/dev-safe.mjs, scripts/dev-with-automation.mjs, docker/entrypoint.sh (via generated defaults.env), and .github/workflows/docker.yml.\",\n \"versions\": {\n \"agentServer\": \"1.40.1\",\n \"agentCanvas\": \"1.12.0\",\n \"automation\": \"1.6.0\"\n },\n \"compatibility\": {\n \"minimumAgentServer\": \"1.28.0\"\n },\n \"constraints\": {\n \"_comment\": \"Temporary upper bound on the agent-client-protocol (acp) transitive dependency. openhands-sdk 1.35.0 requires agent-client-protocol>=0.10.1 with no upper bound, but acp 0.11.0 reordered the ACP prompt() arguments and breaks the SDK's ACP client (a PromptRequest validation error surfaces as 'ACP error: 2 validation errors for PromptRequest'). Pin acp <0.11 until a fixed openhands-sdk ships. Consumed by scripts/dev-safe.mjs (agent-server uvx install) and .github/workflows/mock-llm-e2e.yml (mock ACP server venv).\",\n \"agentClientProtocol\": \"agent-client-protocol<0.11\"\n },\n \"images\": {\n \"agentServer\": \"ghcr.io/openhands/agent-server\",\n \"agentCanvas\": \"ghcr.io/openhands/agent-canvas\"\n },\n \"ports\": {\n \"agentServer\": 18000,\n \"automation\": 18001,\n \"proxy\": 8000\n },\n \"paths\": {\n \"stateSubdir\": \"agent-canvas\",\n \"conversations\": \"agent-canvas/conversations\",\n \"bashEvents\": \"agent-canvas/bash_events\",\n \"automationDb\": \"automation/automations.db\",\n \"canvasBasePath\": \"/canvas\"\n },\n \"packages\": {\n \"agentServer\": \"openhands-agent-server\",\n \"automation\": \"openhands-automation\",\n \"tools\": \"openhands-tools\",\n \"workspace\": \"openhands-workspace\"\n },\n \"telemetry\": {\n \"posthogApiKey\": \"phc_kBtz5nKmxVRRQ7HtPwr2QX9eMC5j65zE86QKocVNwb4U\",\n \"posthogHost\": \"https://us.i.posthog.com\"\n }\n}\n"],"mappings":""}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require("../../_virtual/_rolldown/runtime.cjs");const e=require("../../utils/constants.cjs"),t=require("../../contexts/active-backend-context.cjs"),n=require("../query/use-conversation-skills.cjs"),
|
|
1
|
+
require("../../_virtual/_rolldown/runtime.cjs");const e=require("../../utils/constants.cjs"),t=require("../../contexts/active-backend-context.cjs"),n=require("../../utils/format-model-name.cjs"),r=require("../query/use-conversation-skills.cjs"),i=require("../query/use-llm-profiles.cjs");let a=require("react");function o(e){let t=window.getSelection();if(!t||t.rangeCount===0)return-1;let n=t.getRangeAt(0),r=n.cloneRange();return r.selectNodeContents(e),r.setEnd(n.startContainer,n.startOffset),r.toString().length}var s=s=>{let{data:c,isLoading:l}=r.useConversationSkills(),u=t.useActiveBackend().backend.kind===`cloud`,{data:d,isLoading:f}=i.useLlmProfiles(),[p,m]=(0,a.useState)(!1),[h,g]=(0,a.useState)(``),[_,v]=(0,a.useState)(`command`),[y,b]=(0,a.useState)(0),x=(0,a.useMemo)(()=>{let t=e.BUILT_IN_COMMANDS.filter(e=>e.command===`/new`?u:!0);return l||!c||c.forEach(e=>{let n=(e.triggers||[]).filter(e=>e.startsWith(`/`));n.length>0?n.forEach(n=>{t.push({skill:e,command:n})}):e.type===`agentskills`&&t.push({skill:e,command:`/${e.name}`})}),t},[c,l,u]),S=(0,a.useMemo)(()=>(d?.profiles??[]).map(t=>{let r=`${e.MODEL_COMMAND} ${t.name}`;return{command:r,skill:{name:t.name,type:`agentskills`,content:t.model?`Switch to ${n.formatModelNameForDisplay(t.model)}`:`Switch to this LLM profile`,triggers:[r]}}}),[d?.profiles]),C=(0,a.useMemo)(()=>{let e=_===`model-profile`?S:x;if(!h)return e;let t=h.toLowerCase();return e.filter(e=>e.command.toLowerCase().includes(t)||e.skill.name.toLowerCase().includes(t)||e.skill.content?.toLowerCase().includes(t))},[_,S,x,h]),w=(0,a.useRef)(p);w.current=p;let T=(0,a.useRef)(C);T.current=C;let E=(0,a.useRef)(y);E.current=y,(0,a.useEffect)(()=>{b(0)},[h]);let D=(0,a.useRef)(null),O=(0,a.useCallback)(()=>{let e=s.current;if(!e)return null;let t=(e.innerText||``).replace(/[\n\r]+$/,``),n=o(e);if(n<0)return null;let r=t.slice(0,n),i=r.match(/(^|\s)(\/model(?:\s+\S*)?)$/);if(i){let e=i[2],a=r.length-e.length,o=t.slice(n).match(/^\S*/),s=n+(o?o[0].length:0);return{kind:`model-profile`,text:e.replace(/^\/model(?:\s+)?/,``),start:a,end:s}}let a=r.match(/(^|\s)(\/\S*)$/);if(!a)return null;let c=a[2],l=r.length-c.length,u=t.slice(n).match(/^\S*/),d=n+(u?u[0].length:0);return{kind:`command`,text:c.slice(1),start:l,end:d}},[s]),k=(0,a.useCallback)(()=>{let e=O(),t=e?.kind===`model-profile`?S.length>0||f:x.length>0;e!==null&&t?(v(e.kind),g(e.text),D.current={start:e.start,end:e.end},m(!0)):(m(!1),g(``),v(`command`),D.current=null)},[O,f,S.length,x.length]),A=(0,a.useCallback)(e=>{let t=s.current;if(!t)return;let n=D.current,r=(t.innerText||``).replace(/[\n\r]+$/,``),i=`${e.command} `;if(n){t.textContent=r.slice(0,n.start)+i+r.slice(n.end);let e=n.start+i.length,a=t.firstChild;if(a){let t=document.createRange(),n=window.getSelection(),r=Math.min(e,a.textContent.length);t.setStart(a,r),t.collapse(!0),n?.removeAllRanges(),n?.addRange(t)}}else{t.textContent=i;let e=document.createRange(),n=window.getSelection();e.selectNodeContents(t),e.collapse(!1),n?.removeAllRanges(),n?.addRange(e)}m(!1),g(``),v(`command`),b(0),D.current=null,t.dispatchEvent(new InputEvent(`input`,{bubbles:!0})),t.focus()},[s]);return{isMenuOpen:p,filteredItems:C,selectedIndex:y,updateSlashMenu:k,selectItem:A,handleSlashKeyDown:(0,a.useCallback)(e=>{let t=T.current;if(!w.current||t.length===0)return!1;switch(e.key){case`ArrowDown`:return e.preventDefault(),b(e=>e<t.length-1?e+1:0),!0;case`ArrowUp`:return e.preventDefault(),b(e=>e>0?e-1:t.length-1),!0;case`Enter`:case`Tab`:{let n=t[E.current];return n?(e.preventDefault(),A(n),!0):!1}case`Escape`:return e.preventDefault(),m(!1),!0;case`ArrowLeft`:case`ArrowRight`:case`Home`:case`End`:return m(!1),!1;default:return!1}},[A]),closeMenu:(0,a.useCallback)(()=>m(!1),[])}};exports.useSlashCommand=s;
|
|
2
2
|
//# sourceMappingURL=use-slash-command.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-slash-command.cjs","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 { Microagent } from \"#/api/open-hands.types\";\nimport { BUILT_IN_COMMANDS, MODEL_COMMAND } from \"#/utils/constants\";\nimport { useActiveBackend } from \"#/contexts/active-backend-context\";\nimport { useLlmProfiles } from \"#/hooks/query/use-llm-profiles\";\n\nexport type SlashCommandSkill = SkillInfo | Microagent;\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 content: profile.model\n ? `Switch to ${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.cjs","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 { Microagent } from \"#/api/open-hands.types\";\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 | Microagent;\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 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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"}
|