@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.
Files changed (161) hide show
  1. package/README.md +1 -1
  2. package/README.windows.md +2 -2
  3. package/build/assets/{active-backend-context-BBvP-bI2.js → active-backend-context-B_bg5h9O.js} +1 -1
  4. package/build/assets/{agent-context-settings-Bno1nwPL.js → agent-context-settings-CzWS1DgR.js} +1 -1
  5. package/build/assets/{agent-profiles-settings-DqoS7RL3.js → agent-profiles-settings-hd2cAIZM.js} +1 -1
  6. package/build/assets/{agent-server-conversation-service.api-0RztSdJJ.js → agent-server-conversation-service.api-BheU4HX3.js} +3 -3
  7. package/build/assets/{agent-settings-h8hQz-IU.js → agent-settings-D2J1dKVd.js} +1 -1
  8. package/build/assets/agent-settings-D_SVrWhI.js +1 -0
  9. package/build/assets/{api-key-entry-screen-Qsb83VX1.js → api-key-entry-screen-D70oJj2c.js} +1 -1
  10. package/build/assets/app-settings-D0OKHB3M.js +1 -0
  11. package/build/assets/{automation-detail-7LkL91j9.js → automation-detail-C0SxWTnQ.js} +1 -1
  12. package/build/assets/{automation-export-U2QNyNy6.js → automation-export-BjQa_fT6.js} +1 -1
  13. package/build/assets/{automation-setup-route-Bel9_Lue.js → automation-setup-route-BzCNpljP.js} +1 -1
  14. package/build/assets/{automation-templates-CRLDssaI.js → automation-templates-D9gs6ihu.js} +1 -1
  15. package/build/assets/{automations-list-B2iV3ZKW.js → automations-list-dZg8zOCl.js} +1 -1
  16. package/build/assets/backend-form-modal-HwclKrUS.js +1 -0
  17. package/build/assets/{backend-synced-settings-badge-DB4717Iu.js → backend-synced-settings-badge-YI3k09Wr.js} +1 -1
  18. package/build/assets/{badge-yR53enbU.js → badge-Dpn3-LTV.js} +1 -1
  19. package/build/assets/{browser-tab-A94o33Vn.js → browser-tab-4CPdj3J3.js} +1 -1
  20. package/build/assets/{cloud-funnel-analytics-CBQdPZl_.js → cloud-funnel-analytics-CcSez21r.js} +1 -1
  21. package/build/assets/{command-menu-PsRXVBz2.js → command-menu-BKOLj-TC.js} +1 -1
  22. package/build/assets/{condenser-settings-w7L3EFvm.js → condenser-settings-DLQogFnB.js} +1 -1
  23. package/build/assets/{conversation-BUzBZAc-.js → conversation-Cmyw9bFh.js} +3 -3
  24. package/build/assets/conversation-CvoGZ5vC.js +1 -0
  25. package/build/assets/conversation-panel-CvoGZ5vC.js +1 -0
  26. package/build/assets/{conversation-state-store-Bs8gA263.js → conversation-state-store-CtgILxWF.js} +1 -1
  27. package/build/assets/{entry.client-DzbDyLkV.js → entry.client-EBn3ZtNP.js} +1 -1
  28. package/build/assets/{extensions-hub-DOmHPGTA.js → extensions-hub-BZ3HYztk.js} +1 -1
  29. package/build/assets/{extensions-navigation-6zU5M1pa.js → extensions-navigation-Bnx32WAb.js} +1 -1
  30. package/build/assets/{files-tab-BzofBmF4.js → files-tab-D5zuU5TY.js} +1 -1
  31. package/build/assets/format-model-name-BN_rx1Y3.js +1 -0
  32. package/build/assets/{format-relative-time-DGPQlPx0.js → format-relative-time-BerVamGl.js} +1 -1
  33. package/build/assets/{git-provider-icon-BLMlwy02.js → git-provider-icon-zuKz2cwo.js} +1 -1
  34. package/build/assets/{git-repo-dropdown-B7RvO70z.js → git-repo-dropdown-B5VnMftu.js} +1 -1
  35. package/build/assets/git-repo-dropdown-Bo7ciSNX.js +1 -0
  36. package/build/assets/home-DqyeeFA_.js +1 -0
  37. package/build/assets/{home-DEZaZqYv.js → home-Y0aDou-a.js} +1 -1
  38. package/build/assets/{home-store-D7CjMN_-.js → home-store-Cubdq0uP.js} +1 -1
  39. package/build/assets/index-home-DqyeeFA_.js +1 -0
  40. package/build/assets/info-C9lpK5pw.js +1 -0
  41. package/build/assets/{install-server-modal-BTwK5lbi.js → install-server-modal-DW6dSyGr.js} +1 -1
  42. package/build/assets/{kebab-menu-item-content-C4CkeiJO.js → kebab-menu-item-content-CKydZrbH.js} +1 -1
  43. package/build/assets/{launch-DVlx4v9o.js → launch-c4GEP8oo.js} +1 -1
  44. package/build/assets/llm-not-configured-banner-DCE_0B4I.js +27 -0
  45. package/build/assets/llm-settings-8eWBF3Ij.js +1 -0
  46. package/build/assets/llm-settings-D9ko42Pi.js +1 -0
  47. package/build/assets/{llm-subscription-service-7eVfDIE1.js → llm-subscription-service-B3K-97lP.js} +1 -1
  48. package/build/assets/{manage-backends-modal-HNWKLYhq.js → manage-backends-modal-DTCME_Pu.js} +1 -1
  49. package/build/assets/manifest-e1c3bc3b.js +1 -0
  50. package/build/assets/{manifest-subpage-layout-D-m-h8_t.js → manifest-subpage-layout-Ds7G5GZ8.js} +1 -1
  51. package/build/assets/{markdown-renderer-BiiiIznl.js → markdown-renderer-DzvgdZey.js} +1 -1
  52. package/build/assets/{mcp-gLgPP7AS.js → mcp-Da7wFU7A.js} +1 -1
  53. package/build/assets/{mcp-marketplace-utils-Dr_HG_ol.js → mcp-marketplace-utils-DSLou4n7.js} +1 -1
  54. package/build/assets/messages-B2l0qLVk.js +40 -0
  55. package/build/assets/{npm-DRzJF5Zn.js → npm-Cswluf6-.js} +1 -1
  56. package/build/assets/{onboarding-DSr8hOpk.js → onboarding-BKbTYlW1.js} +1 -1
  57. package/build/assets/{onboarding-modal-C6Euf7N4.js → onboarding-modal-DrrGlEQq.js} +1 -1
  58. package/build/assets/{organization-service.api-C4GYWwj9.js → organization-service.api-DK8csGvK.js} +1 -1
  59. package/build/assets/{planner-tab-CZcguxTK.js → planner-tab-D-mZ1I7n.js} +1 -1
  60. package/build/assets/{plugins-management-service-BmDzOQsd.js → plugins-management-service-CywqyfLK.js} +1 -1
  61. package/build/assets/{profiles-service.api-CoLTwjc1.js → profiles-service.api-vcE_jJCe.js} +1 -1
  62. package/build/assets/{providers-tBzTpz1w.js → providers-CdrPPm0z.js} +1 -1
  63. package/build/assets/{proxy-DCI9S-LA.js → proxy-b4aqL4SX.js} +1 -1
  64. package/build/assets/{recommended-automations-launcher-BbJAJVED.js → recommended-automations-launcher-DIYgUdMm.js} +1 -1
  65. package/build/assets/root-CBBi6IAe.js +2 -0
  66. package/build/assets/root-CRK9fOFd.css +1 -0
  67. package/build/assets/root-layout-BFN2rCOy.js +2 -0
  68. package/build/assets/{schema-field-zhsVDllR.js → schema-field-Z-rMg9ti.js} +1 -1
  69. package/build/assets/{sdk-section-page-CS4GWn0y.js → sdk-section-page-m_cjygxR.js} +1 -1
  70. package/build/assets/{secrets-service-DjgGduXk.js → secrets-service-BHF4fxML.js} +1 -1
  71. package/build/assets/{secrets-settings-f-qahfbi.js → secrets-settings-aBGXtQhX.js} +1 -1
  72. package/build/assets/{settings-DQd0xLEw.js → settings-CZPGNens.js} +1 -1
  73. package/build/assets/{settings-index-BVwy9JEp.js → settings-index-Dtlegd6s.js} +1 -1
  74. package/build/assets/{settings-modal-D6D5ngET.js → settings-modal-CalzJP2l.js} +1 -1
  75. package/build/assets/{settings-service.api-DZp7E7A9.js → settings-service.api-DfPYaxZJ.js} +1 -1
  76. package/build/assets/{shared-conversation-DIxZjElr.js → shared-conversation-BIAcuORk.js} +1 -1
  77. package/build/assets/{sidebar-mobile-menu-toggle-OoZBHxaT.js → sidebar-mobile-menu-toggle-C6HpQ55l.js} +1 -1
  78. package/build/assets/{skill-scope-B1Per5XI.js → skill-scope-Dt1V5X2p.js} +1 -1
  79. package/build/assets/{skills-plugins-B3AO6sMz.js → skills-plugins-B-H0cCOP.js} +1 -1
  80. package/build/assets/{skills-settings-DmmgJ6CT.js → skills-settings-DthdIDUb.js} +1 -1
  81. package/build/assets/{task-list-tab-C19Q_JNf.js → task-list-tab-BTde81Iu.js} +1 -1
  82. package/build/assets/{terminal-CbuMRYal.js → terminal-DRrJo-cR.js} +1 -1
  83. package/build/assets/{use-acp-credential-form-DJmmJKMo.js → use-acp-credential-form-DGyo0QvL.js} +1 -1
  84. package/build/assets/{use-activate-agent-profile-CeFfYwu_.js → use-activate-agent-profile-DrPzIKJF.js} +1 -1
  85. package/build/assets/{use-agent-profiles-BHLkLbRR.js → use-agent-profiles-B37XWDLq.js} +1 -1
  86. package/build/assets/{use-can-manage-org-profiles-Bu4y8gSx.js → use-can-manage-org-profiles-BzE28H3x.js} +1 -1
  87. package/build/assets/{use-cloud-current-user-id-QD9x3ySO.js → use-cloud-current-user-id-U6HPjy50.js} +1 -1
  88. package/build/assets/{use-config-CPwqciXN.js → use-config-DapX3pjC.js} +1 -1
  89. package/build/assets/{use-create-conversation-DWaSVUOB.js → use-create-conversation-CRf1dCm5.js} +1 -1
  90. package/build/assets/{use-create-secret-BsOfOLvg.js → use-create-secret-DcpT3RPw.js} +1 -1
  91. package/build/assets/{use-llm-profiles-CU8ah1pz.js → use-llm-profiles-CHxqoHiN.js} +1 -1
  92. package/build/assets/{use-manifest-capabilities-DBYPjyV6.js → use-manifest-capabilities-zihHDbMW.js} +1 -1
  93. package/build/assets/{use-onboarding-completion-CtNJxkXG.js → use-onboarding-completion-6kWOW3ul.js} +1 -1
  94. package/build/assets/{use-plugins-marketplace-ZEA-gyM9.js → use-plugins-marketplace-CqkR8kLE.js} +1 -1
  95. package/build/assets/{use-save-agent-profile-DgI_Uz9a.js → use-save-agent-profile-CH9_EXQG.js} +1 -1
  96. package/build/assets/{use-save-settings-pSjRhD_m.js → use-save-settings-DIEurJ3D.js} +1 -1
  97. package/build/assets/{use-settings-BHWsOcIG.js → use-settings-BmLzQjnC.js} +1 -1
  98. package/build/assets/{use-settings-nav-items-CQgkxUbV.js → use-settings-nav-items-DV9qgeu7.js} +1 -1
  99. package/build/assets/{use-tracking-CGCsBWhg.js → use-tracking-salbPJxT.js} +1 -1
  100. package/build/assets/{use-user-conversation-DRfEU8x0.js → use-user-conversation-Dt-W6uNw.js} +1 -1
  101. package/build/assets/{verification-settings-BItwt998.js → verification-settings-oi54wEc1.js} +1 -1
  102. package/build/index.html +4 -4
  103. package/config/defaults.json +1 -1
  104. package/dist/components/features/chat/components/chat-input-llm-profile-picker.cjs +1 -1
  105. package/dist/components/features/chat/components/chat-input-llm-profile-picker.cjs.map +1 -1
  106. package/dist/components/features/chat/components/chat-input-llm-profile-picker.js +61 -60
  107. package/dist/components/features/chat/components/chat-input-llm-profile-picker.js.map +1 -1
  108. package/dist/components/features/chat/model-messages.cjs +1 -1
  109. package/dist/components/features/chat/model-messages.cjs.map +1 -1
  110. package/dist/components/features/chat/model-messages.js +26 -25
  111. package/dist/components/features/chat/model-messages.js.map +1 -1
  112. package/dist/components/features/conversation-panel/conversation-card/conversation-card-footer.cjs +1 -1
  113. package/dist/components/features/conversation-panel/conversation-card/conversation-card-footer.js +4 -4
  114. package/dist/components/features/settings/llm-profiles/profile-row.cjs +1 -1
  115. package/dist/components/features/settings/llm-profiles/profile-row.js +1 -0
  116. package/dist/components/shared/modals/settings/model-selector.cjs +1 -1
  117. package/dist/components/shared/modals/settings/model-selector.cjs.map +1 -1
  118. package/dist/components/shared/modals/settings/model-selector.js +154 -98
  119. package/dist/components/shared/modals/settings/model-selector.js.map +1 -1
  120. package/dist/config/defaults.cjs +1 -1
  121. package/dist/config/defaults.cjs.map +1 -1
  122. package/dist/config/defaults.js +1 -1
  123. package/dist/config/defaults.js.map +1 -1
  124. package/dist/hooks/chat/use-slash-command.cjs +1 -1
  125. package/dist/hooks/chat/use-slash-command.cjs.map +1 -1
  126. package/dist/hooks/chat/use-slash-command.js +61 -60
  127. package/dist/hooks/chat/use-slash-command.js.map +1 -1
  128. package/dist/package.cjs +1 -1
  129. package/dist/package.cjs.map +1 -1
  130. package/dist/package.js +1 -1
  131. package/dist/package.js.map +1 -1
  132. package/dist/routes/app-settings.cjs +1 -1
  133. package/dist/routes/app-settings.cjs.map +1 -1
  134. package/dist/routes/app-settings.js +94 -93
  135. package/dist/routes/app-settings.js.map +1 -1
  136. package/dist/routes/llm-settings.cjs +1 -1
  137. package/dist/routes/llm-settings.cjs.map +1 -1
  138. package/dist/routes/llm-settings.js +131 -119
  139. package/dist/routes/llm-settings.js.map +1 -1
  140. package/dist/utils/format-model-name.cjs +1 -1
  141. package/dist/utils/format-model-name.cjs.map +1 -1
  142. package/dist/utils/format-model-name.d.ts +11 -0
  143. package/dist/utils/format-model-name.js +11 -3
  144. package/dist/utils/format-model-name.js.map +1 -1
  145. package/package.json +1 -1
  146. package/build/assets/agent-settings-BJHryp65.js +0 -1
  147. package/build/assets/app-settings-r1RUbgGi.js +0 -1
  148. package/build/assets/backend-form-modal-OroTqnAv.js +0 -1
  149. package/build/assets/conversation-panel-pzAKsSDb.js +0 -1
  150. package/build/assets/conversation-pzAKsSDb.js +0 -1
  151. package/build/assets/git-repo-dropdown-B_PTq4ol.js +0 -1
  152. package/build/assets/home-r89Vj2td.js +0 -1
  153. package/build/assets/index-home-r89Vj2td.js +0 -1
  154. package/build/assets/llm-not-configured-banner-CwKu8C6g.js +0 -27
  155. package/build/assets/llm-settings-BJotLQND.js +0 -1
  156. package/build/assets/llm-settings-CDM-5H2A.js +0 -1
  157. package/build/assets/manifest-2a36a8f8.js +0 -1
  158. package/build/assets/messages-D1cbVhRK.js +0 -40
  159. package/build/assets/root-BfM_kC_d.css +0 -1
  160. package/build/assets/root-gstBEjty.js +0 -2
  161. package/build/assets/root-layout-BIAFLP9p.js +0 -2
@@ -1,50 +1,52 @@
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 { DEFAULT_SETTINGS as n } from "../services/settings.js";
4
- import { LLM_AUTH_TYPE_API_KEY as r, LLM_AUTH_TYPE_KEY as i, LLM_AUTH_TYPE_SUBSCRIPTION as a, LLM_SUBSCRIPTION_VENDOR_KEY as o, OPENAI_SUBSCRIPTION_VENDOR as s, resolveLlmAuthType as c } from "../constants/llm-subscription.js";
5
- import { useSettings as l } from "../hooks/query/use-settings.js";
6
- import { SettingsInput as u } from "../components/features/settings/settings-input.js";
7
- import { SettingsDropdownInput as d } from "../components/features/settings/settings-dropdown-input.js";
8
- import { extractModelAndProvider as f } from "../utils/extract-model-and-provider.js";
9
- import { HelpLink as p } from "../ui/help-link.js";
10
- import { ModelSelector as m } from "../components/shared/modals/settings/model-selector.js";
11
- import { useAgentSettingsSchema as h } from "../hooks/query/use-agent-settings-schema.js";
12
- import { OpenAISubscriptionAuthCard as g } from "../components/features/settings/llm-settings/openai-subscription-auth-card.js";
13
- import { KeyStatusIcon as _ } from "../components/features/settings/key-status-icon.js";
14
- import { inferInitialView as v } from "../utils/sdk-settings-schema.js";
15
- import { SdkSectionPage as y } from "../components/features/settings/sdk-settings/sdk-section-page.js";
3
+ import { Info as n } from "../node_modules/lucide-react/dist/esm/icons/info.js";
4
+ import { DEFAULT_SETTINGS as r } from "../services/settings.js";
5
+ import { LLM_AUTH_TYPE_API_KEY as i, LLM_AUTH_TYPE_KEY as a, LLM_AUTH_TYPE_SUBSCRIPTION as o, LLM_SUBSCRIPTION_VENDOR_KEY as s, OPENAI_SUBSCRIPTION_VENDOR as c, resolveLlmAuthType as l } from "../constants/llm-subscription.js";
6
+ import { FREE_OPENHANDS_MODEL_NOTE as u, isFreeOpenHandsModel as d } from "../utils/format-model-name.js";
7
+ import { useSettings as f } from "../hooks/query/use-settings.js";
8
+ import { SettingsInput as p } from "../components/features/settings/settings-input.js";
9
+ import { SettingsDropdownInput as m } from "../components/features/settings/settings-dropdown-input.js";
10
+ import { extractModelAndProvider as h } from "../utils/extract-model-and-provider.js";
11
+ import { HelpLink as g } from "../ui/help-link.js";
12
+ import { ModelSelector as _ } from "../components/shared/modals/settings/model-selector.js";
13
+ import { useAgentSettingsSchema as v } from "../hooks/query/use-agent-settings-schema.js";
14
+ import { OpenAISubscriptionAuthCard as y } from "../components/features/settings/llm-settings/openai-subscription-auth-card.js";
15
+ import { KeyStatusIcon as b } from "../components/features/settings/key-status-icon.js";
16
+ import { inferInitialView as x } from "../utils/sdk-settings-schema.js";
17
+ import { SdkSectionPage as S } from "../components/features/settings/sdk-settings/sdk-section-page.js";
16
18
  import "../components/features/settings/llm-profiles/index.js";
17
- import { useOpenAISubscriptionModels as b } from "../hooks/query/use-llm-subscription-models.js";
18
- import x from "react";
19
- import { Fragment as S, jsx as C, jsxs as w } from "react/jsx-runtime";
19
+ import { useOpenAISubscriptionModels as C } from "../hooks/query/use-llm-subscription-models.js";
20
+ import w from "react";
21
+ import { Fragment as T, jsx as E, jsxs as D } from "react/jsx-runtime";
20
22
  //#region src/routes/llm-settings.tsx
21
- var T = new Set([
23
+ var O = new Set([
22
24
  "llm.model",
23
25
  "llm.api_key",
24
26
  "llm.base_url",
25
- i,
26
- o
27
- ]), E = (e, t) => !e || !t ? null : `${e}/${t}`, D = (e, t) => e?.sections.flatMap((e) => e.fields).find((e) => e.key === t)?.default ?? null, O = {
27
+ a,
28
+ s
29
+ ]), k = (e, t) => !e || !t ? null : `${e}/${t}`, A = (e, t) => e?.sections.flatMap((e) => e.fields).find((e) => e.key === t)?.default ?? null, j = {
28
30
  openai: new Set(["https://api.openai.com", "https://api.openai.com/v1"]),
29
31
  moonshot: new Set(["https://api.kimi.com/coding/v1"])
30
- }, k = (e) => {
32
+ }, M = (e) => {
31
33
  try {
32
34
  let t = new URL(e), n = t.pathname.replace(/\/+$/, "") || "";
33
35
  return `${t.origin}${n}`;
34
36
  } catch {
35
37
  return e.trim().replace(/\/+$/, "");
36
38
  }
37
- }, A = (e, t) => {
38
- let n = k(t), { provider: r } = f(e);
39
+ }, N = (e, t) => {
40
+ let n = M(t), { provider: r } = h(e);
39
41
  if (r) {
40
- let e = O[r];
42
+ let e = j[r];
41
43
  if (e) return e.has(n);
42
44
  }
43
- return Object.values(O).some((e) => e?.has(n));
45
+ return Object.values(j).some((e) => e?.has(n));
44
46
  };
45
- function j({ testId: n }) {
47
+ function P({ testId: n }) {
46
48
  let { t: r } = e("openhands");
47
- return /* @__PURE__ */ C(p, {
49
+ return /* @__PURE__ */ E(g, {
48
50
  testId: n,
49
51
  text: r(t.SETTINGS$OPENHANDS_API_KEY_HELP_TEXT),
50
52
  linkText: r(t.SETTINGS$NAV_API_KEYS),
@@ -52,164 +54,174 @@ function j({ testId: n }) {
52
54
  suffix: ` ${r(t.SETTINGS$OPENHANDS_API_KEY_HELP_SUFFIX)}`
53
55
  });
54
56
  }
55
- function M({ scope: f = "personal", onSaveSuccess: O, initialValueOverrides: k, embedded: M, hideSaveButton: N, suppressSuccessToast: P, onSaveControlChange: F }) {
56
- let { t: I } = e("openhands"), { data: L } = l(f), { data: R } = h(L?.agent_settings_schema), z = L?.agent_settings?.llm, B = c(k?.["llm.auth_type"] ?? z?.auth_type), [V, H] = x.useState(B === a), { data: U, isLoading: W, isFetching: G } = b({ enabled: V }), K = V && !U && (W || G), q = x.useRef(null), J = x.useRef(null);
57
- x.useEffect(() => {
58
- B === "subscription" && H(!0);
59
- }, [B]);
60
- let Y = String(n.agent_settings?.llm?.model ?? ""), X = x.useCallback((e, t) => {
61
- let n = v(e, t);
57
+ function F() {
58
+ return /* @__PURE__ */ D("p", {
59
+ "data-testid": "openhands-free-models-note",
60
+ className: "flex items-start gap-2 text-xs text-warning",
61
+ children: [/* @__PURE__ */ E(n, {
62
+ className: "mt-0.5 size-4 shrink-0 text-warning",
63
+ "aria-hidden": !0
64
+ }), /* @__PURE__ */ E("span", { children: u })]
65
+ });
66
+ }
67
+ function I({ scope: n = "personal", onSaveSuccess: u, initialValueOverrides: h, embedded: j, hideSaveButton: M, suppressSuccessToast: I, onSaveControlChange: L }) {
68
+ let { t: R } = e("openhands"), { data: z } = f(n), { data: B } = v(z?.agent_settings_schema), V = z?.agent_settings?.llm, H = l(h?.["llm.auth_type"] ?? V?.auth_type), [U, W] = w.useState(H === o), { data: G, isLoading: K, isFetching: q } = C({ enabled: U }), J = U && !G && (K || q), Y = w.useRef(null), X = w.useRef(null);
69
+ w.useEffect(() => {
70
+ H === "subscription" && W(!0);
71
+ }, [H]);
72
+ let Z = String(r.agent_settings?.llm?.model ?? ""), Q = w.useCallback((e, t) => {
73
+ let n = x(e, t);
62
74
  if (n !== "basic") return n;
63
75
  let r = e.llm_model ?? "", i = e.llm_base_url?.trim() ?? "";
64
- return i.length > 0 && !A(r, i) ? "all" : "basic";
65
- }, []), Z = x.useCallback(({ values: e, isDisabled: n, view: l, onChange: f }) => {
66
- let h = typeof e["llm.model"] == "string" ? e["llm.model"] : "", v = typeof e["llm.base_url"] == "string" ? e["llm.base_url"] : "", y = h.startsWith("openhands/"), b = c(e[i]), x = b === a, T = n || x && K, D = U?.includes(h) ? h : U?.[0] ?? "", O = typeof e["llm.api_key"] == "string" ? e["llm.api_key"] : "", k = M ? O.length > 0 : !!L?.llm_api_key_set, A = (e, r) => /* @__PURE__ */ w(S, { children: [/* @__PURE__ */ C(u, {
76
+ return i.length > 0 && !N(r, i) ? "all" : "basic";
77
+ }, []), $ = w.useCallback(({ values: e, isDisabled: n, view: r, onChange: u }) => {
78
+ let f = typeof e["llm.model"] == "string" ? e["llm.model"] : "", h = typeof e["llm.base_url"] == "string" ? e["llm.base_url"] : "", v = f.startsWith("openhands/"), x = l(e[a]), S = x === o, C = n || S && J, w = G?.includes(f) ? f : G?.[0] ?? "", O = typeof e["llm.api_key"] == "string" ? e["llm.api_key"] : "", A = j ? O.length > 0 : !!z?.llm_api_key_set, M = (e, r) => /* @__PURE__ */ D(T, { children: [/* @__PURE__ */ E(p, {
67
79
  testId: e,
68
- label: I(t.SETTINGS_FORM$API_KEY),
80
+ label: R(t.SETTINGS_FORM$API_KEY),
69
81
  type: "password",
70
82
  className: "w-full",
71
83
  value: O,
72
- placeholder: k ? "<hidden>" : "",
73
- onChange: (e) => f("llm.api_key", e),
84
+ placeholder: A ? "<hidden>" : "",
85
+ onChange: (e) => u("llm.api_key", e),
74
86
  isDisabled: n,
75
- startContent: k ? /* @__PURE__ */ C(_, { isSet: k }) : void 0
76
- }), /* @__PURE__ */ C(p, {
87
+ startContent: A ? /* @__PURE__ */ E(b, { isSet: A }) : void 0
88
+ }), /* @__PURE__ */ E(g, {
77
89
  testId: r,
78
- text: I(t.SETTINGS$DONT_KNOW_API_KEY),
79
- linkText: I(t.SETTINGS$CLICK_FOR_INSTRUCTIONS),
90
+ text: R(t.SETTINGS$DONT_KNOW_API_KEY),
91
+ linkText: R(t.SETTINGS$CLICK_FOR_INSTRUCTIONS),
80
92
  href: "https://docs.openhands.dev/usage/local-setup#getting-an-api-key"
81
93
  })] }), N = (e) => {
82
- let t = e === "subscription" ? a : r;
83
- if (f(i, t), t === "subscription") {
84
- H(!0), h && !U?.includes(h) && (q.current = h);
85
- let e = J.current && U?.includes(J.current) ? J.current : U?.[0];
86
- f(o, s), !U?.includes(h) && e && f("llm.model", e);
94
+ let t = e === "subscription" ? o : i;
95
+ if (u(a, t), t === "subscription") {
96
+ W(!0), f && !G?.includes(f) && (Y.current = f);
97
+ let e = X.current && G?.includes(X.current) ? X.current : G?.[0];
98
+ u(s, c), !G?.includes(f) && e && u("llm.model", e);
87
99
  return;
88
100
  }
89
- h && U?.includes(h) && (J.current = h, f("llm.model", q.current ?? Y));
90
- }, P = () => /* @__PURE__ */ C(d, {
101
+ f && G?.includes(f) && (X.current = f, u("llm.model", Y.current ?? Z));
102
+ }, I = () => /* @__PURE__ */ E(m, {
91
103
  testId: "llm-auth-type-input",
92
- name: i,
93
- label: I(t.SETTINGS$LLM_AUTH_TYPE),
104
+ name: a,
105
+ label: R(t.SETTINGS$LLM_AUTH_TYPE),
94
106
  items: [{
95
- key: r,
96
- label: I(t.SETTINGS$LLM_AUTH_TYPE_API_KEY)
107
+ key: i,
108
+ label: R(t.SETTINGS$LLM_AUTH_TYPE_API_KEY)
97
109
  }, {
98
- key: a,
99
- label: I(t.SETTINGS$LLM_AUTH_TYPE_SUBSCRIPTION)
110
+ key: o,
111
+ label: R(t.SETTINGS$LLM_AUTH_TYPE_SUBSCRIPTION)
100
112
  }],
101
- selectedKey: b,
113
+ selectedKey: x,
102
114
  isClearable: !1,
103
115
  required: !0,
104
- isDisabled: T,
116
+ isDisabled: C,
105
117
  onSelectionChange: N
106
- }), F = () => /* @__PURE__ */ w("div", {
118
+ }), L = () => /* @__PURE__ */ D("div", {
107
119
  className: "flex flex-col gap-6",
108
120
  "data-testid": "llm-subscription-settings",
109
- children: [/* @__PURE__ */ C(d, {
121
+ children: [/* @__PURE__ */ E(m, {
110
122
  testId: "llm-subscription-model-input",
111
123
  name: "llm.subscription_model",
112
- label: I(t.SETTINGS$SUBSCRIPTION_MODEL),
113
- items: (U ?? []).map((e) => ({
124
+ label: R(t.SETTINGS$SUBSCRIPTION_MODEL),
125
+ items: (G ?? []).map((e) => ({
114
126
  key: e,
115
127
  label: e
116
128
  })),
117
- selectedKey: D,
129
+ selectedKey: w,
118
130
  isClearable: !1,
119
131
  required: !0,
120
- isDisabled: T || !U?.length,
132
+ isDisabled: C || !G?.length,
121
133
  onSelectionChange: (e) => {
122
- let t = typeof e == "string" ? e : U?.[0];
123
- t && f("llm.model", t);
134
+ let t = typeof e == "string" ? e : G?.[0];
135
+ t && u("llm.model", t);
124
136
  }
125
- }), /* @__PURE__ */ C(g, { isDisabled: n })]
137
+ }), /* @__PURE__ */ E(y, { isDisabled: n })]
126
138
  });
127
- return /* @__PURE__ */ C("div", {
139
+ return /* @__PURE__ */ E("div", {
128
140
  className: "flex flex-col gap-6",
129
- children: l === "basic" ? /* @__PURE__ */ w("div", {
141
+ children: r === "basic" ? /* @__PURE__ */ D("div", {
130
142
  className: "flex flex-col gap-6",
131
143
  "data-testid": "llm-settings-form-basic",
132
- children: [P(), x ? F() : /* @__PURE__ */ w(S, { children: [
133
- /* @__PURE__ */ C(m, {
134
- currentModel: h || void 0,
144
+ children: [I(), S ? L() : /* @__PURE__ */ D(T, { children: [
145
+ /* @__PURE__ */ E(_, {
146
+ currentModel: f || void 0,
135
147
  onChange: (e, t) => {
136
- let n = E(e, t);
137
- n && f("llm.model", n);
148
+ let n = k(e, t);
149
+ n && u("llm.model", n);
138
150
  },
139
151
  wrapperClassName: "!flex-col !gap-6",
140
152
  isDisabled: n
141
153
  }),
142
- y ? /* @__PURE__ */ C(j, { testId: "openhands-api-key-help" }) : null,
143
- A("llm-api-key-input", "llm-api-key-help-anchor")
154
+ v ? /* @__PURE__ */ E(P, { testId: "openhands-api-key-help" }) : null,
155
+ M("llm-api-key-input", "llm-api-key-help-anchor")
144
156
  ] })]
145
- }) : /* @__PURE__ */ w("div", {
157
+ }) : /* @__PURE__ */ D("div", {
146
158
  className: "flex flex-col gap-6",
147
159
  "data-testid": "llm-settings-form-advanced",
148
- children: [P(), x ? F() : /* @__PURE__ */ w(S, { children: [
149
- /* @__PURE__ */ C(u, {
160
+ children: [I(), S ? L() : /* @__PURE__ */ D(T, { children: [
161
+ /* @__PURE__ */ E(p, {
150
162
  testId: "llm-custom-model-input",
151
- label: I(t.SETTINGS$CUSTOM_MODEL),
163
+ label: R(t.SETTINGS$CUSTOM_MODEL),
152
164
  type: "text",
153
165
  className: "w-full",
154
- value: h,
155
- placeholder: Y,
156
- onChange: (e) => f("llm.model", e),
166
+ value: f,
167
+ placeholder: Z,
168
+ onChange: (e) => u("llm.model", e),
157
169
  isDisabled: n
158
170
  }),
159
- y ? /* @__PURE__ */ C(j, { testId: "openhands-api-key-help-2" }) : null,
160
- /* @__PURE__ */ C(u, {
171
+ v ? /* @__PURE__ */ D(T, { children: [d(f) ? /* @__PURE__ */ E(F, {}) : null, /* @__PURE__ */ E(P, { testId: "openhands-api-key-help-2" })] }) : null,
172
+ /* @__PURE__ */ E(p, {
161
173
  testId: "base-url-input",
162
- label: I(t.SETTINGS$BASE_URL),
174
+ label: R(t.SETTINGS$BASE_URL),
163
175
  type: "text",
164
176
  className: "w-full",
165
- value: v,
177
+ value: h,
166
178
  placeholder: "https://api.openai.com",
167
- onChange: (e) => f("llm.base_url", e),
179
+ onChange: (e) => u("llm.base_url", e),
168
180
  isDisabled: n
169
181
  }),
170
- A("llm-api-key-input", "llm-api-key-help-anchor-advanced")
182
+ M("llm-api-key-input", "llm-api-key-help-anchor-advanced")
171
183
  ] })]
172
184
  })
173
185
  });
174
186
  }, [
175
- Y,
176
- M,
177
- K,
178
- L?.llm_api_key_set,
179
- U,
180
- I
181
- ]), Q = x.useCallback((e, t) => {
182
- let n = structuredClone(e.agent_settings_diff ?? {}), i = n.llm ?? {};
183
- if (c(t.values["llm.auth_type"]) === "subscription") {
184
- i.auth_type = a, i.subscription_vendor = s;
185
- let e = typeof i.model == "string" ? i.model : String(t.values["llm.model"] ?? ""), n = U?.[0];
186
- if (!U?.includes(e) && !n) throw Error("Subscription models are not loaded yet.");
187
- i.model = U?.includes(e) ? e : n, delete i.api_key, delete i.base_url;
188
- } else t.dirty["llm.auth_type"] && (i.auth_type = r, i.subscription_vendor = null), t.view === "basic" && i.model !== void 0 && (i.base_url = D(R, "llm.base_url"));
189
- return n.llm = i, { agent_settings_diff: n };
190
- }, [R, U]);
191
- return /* @__PURE__ */ C(y, {
192
- scope: f,
187
+ Z,
188
+ j,
189
+ J,
190
+ z?.llm_api_key_set,
191
+ G,
192
+ R
193
+ ]), ee = w.useCallback((e, t) => {
194
+ let n = structuredClone(e.agent_settings_diff ?? {}), r = n.llm ?? {};
195
+ if (l(t.values["llm.auth_type"]) === "subscription") {
196
+ r.auth_type = o, r.subscription_vendor = c;
197
+ let e = typeof r.model == "string" ? r.model : String(t.values["llm.model"] ?? ""), n = G?.[0];
198
+ if (!G?.includes(e) && !n) throw Error("Subscription models are not loaded yet.");
199
+ r.model = G?.includes(e) ? e : n, delete r.api_key, delete r.base_url;
200
+ } else t.dirty["llm.auth_type"] && (r.auth_type = i, r.subscription_vendor = null), t.view === "basic" && r.model !== void 0 && (r.base_url = A(B, "llm.base_url"));
201
+ return n.llm = r, { agent_settings_diff: n };
202
+ }, [B, G]);
203
+ return /* @__PURE__ */ E(S, {
204
+ scope: n,
193
205
  settingsSources: [{
194
206
  settingsSource: "agent_settings",
195
207
  sectionKeys: ["llm"],
196
- excludeKeys: T
208
+ excludeKeys: O
197
209
  }],
198
- header: Z,
199
- buildPayload: Q,
200
- getInitialView: X,
210
+ header: $,
211
+ buildPayload: ee,
212
+ getInitialView: Q,
201
213
  forceShowAdvancedView: !0,
202
214
  allowAllView: !0,
203
- onSaveSuccess: O,
204
- initialValueOverrides: k,
205
- embedded: M,
206
- hideSaveButton: N,
207
- suppressSuccessToast: P,
208
- onSaveControlChange: F,
215
+ onSaveSuccess: u,
216
+ initialValueOverrides: h,
217
+ embedded: j,
218
+ hideSaveButton: M,
219
+ suppressSuccessToast: I,
220
+ onSaveControlChange: L,
209
221
  testId: "llm-settings-screen"
210
222
  });
211
223
  }
212
224
  //#endregion
213
- export { M as LlmSettingsScreen };
225
+ export { I as LlmSettingsScreen };
214
226
 
215
227
  //# sourceMappingURL=llm-settings.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"llm-settings.js","names":[],"sources":["../../src/routes/llm-settings.tsx"],"sourcesContent":["import React from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { ModelSelector } from \"#/components/shared/modals/settings/model-selector\";\nimport { useAgentSettingsSchema } from \"#/hooks/query/use-agent-settings-schema\";\nimport { useSettings } from \"#/hooks/query/use-settings\";\nimport { SettingsInput } from \"#/components/features/settings/settings-input\";\nimport { SettingsDropdownInput } from \"#/components/features/settings/settings-dropdown-input\";\nimport { OpenAISubscriptionAuthCard } from \"#/components/features/settings/llm-settings/openai-subscription-auth-card\";\nimport { HelpLink } from \"#/ui/help-link\";\nimport { KeyStatusIcon } from \"#/components/features/settings/key-status-icon\";\nimport {\n SdkSectionHeaderProps,\n SdkSectionPage,\n SdkSectionSaveControl,\n} from \"#/components/features/settings/sdk-settings/sdk-section-page\";\nimport { LlmSettingsLocalView } from \"#/components/features/settings/llm-profiles\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { Settings, SettingsSchema, SettingsScope } from \"#/types/settings\";\nimport { extractModelAndProvider } from \"#/utils/extract-model-and-provider\";\nimport {\n inferInitialView,\n type SettingsFormValues,\n type SettingsView,\n} from \"#/utils/sdk-settings-schema\";\nimport { DEFAULT_SETTINGS } from \"#/services/settings\";\nimport {\n LLM_AUTH_TYPE_API_KEY,\n LLM_AUTH_TYPE_KEY,\n LLM_AUTH_TYPE_SUBSCRIPTION,\n LLM_SUBSCRIPTION_VENDOR_KEY,\n OPENAI_SUBSCRIPTION_VENDOR,\n resolveLlmAuthType,\n} from \"#/constants/llm-subscription\";\nimport { useOpenAISubscriptionModels } from \"#/hooks/query/use-llm-subscription-models\";\n\nconst LLM_EXCLUDED_KEYS = new Set([\n \"llm.model\",\n \"llm.api_key\",\n \"llm.base_url\",\n LLM_AUTH_TYPE_KEY,\n LLM_SUBSCRIPTION_VENDOR_KEY,\n]);\n\nconst buildModelId = (provider: string | null, model: string | null) => {\n if (!provider || !model) return null;\n return `${provider}/${model}`;\n};\n\nconst getSchemaFieldDefaultValue = (\n schema: SettingsSchema | null | undefined,\n fieldKey: string,\n) =>\n schema?.sections\n .flatMap((section) => section.fields)\n .find((field) => field.key === fieldKey)?.default ?? null;\n\nconst KNOWN_PROVIDER_DEFAULT_BASE_URLS: Partial<Record<string, Set<string>>> = {\n openai: new Set([\"https://api.openai.com\", \"https://api.openai.com/v1\"]),\n moonshot: new Set([\"https://api.kimi.com/coding/v1\"]),\n};\n\nconst normalizeBaseUrl = (baseUrl: string) => {\n try {\n const parsedUrl = new URL(baseUrl);\n const normalizedPath = parsedUrl.pathname.replace(/\\/+$/, \"\") || \"\";\n return `${parsedUrl.origin}${normalizedPath}`;\n } catch {\n return baseUrl.trim().replace(/\\/+$/, \"\");\n }\n};\n\nconst isProviderDefaultBaseUrl = (model: string, baseUrl: string) => {\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl);\n const { provider } = extractModelAndProvider(model);\n\n if (provider) {\n const knownDefaults = KNOWN_PROVIDER_DEFAULT_BASE_URLS[provider];\n if (knownDefaults) {\n return knownDefaults.has(normalizedBaseUrl);\n }\n }\n\n return Object.values(KNOWN_PROVIDER_DEFAULT_BASE_URLS).some((knownDefaults) =>\n knownDefaults?.has(normalizedBaseUrl),\n );\n};\n\ninterface OpenHandsApiKeyHelpProps {\n testId: string;\n}\n\nfunction OpenHandsApiKeyHelp({ testId }: OpenHandsApiKeyHelpProps) {\n const { t } = useTranslation(\"openhands\");\n\n return (\n <HelpLink\n testId={testId}\n text={t(I18nKey.SETTINGS$OPENHANDS_API_KEY_HELP_TEXT)}\n linkText={t(I18nKey.SETTINGS$NAV_API_KEYS)}\n href=\"https://app.all-hands.dev/settings/api-keys\"\n suffix={` ${t(I18nKey.SETTINGS$OPENHANDS_API_KEY_HELP_SUFFIX)}`}\n />\n );\n}\n\nexport function LlmSettingsScreen({\n scope = \"personal\",\n onSaveSuccess,\n initialValueOverrides,\n embedded,\n hideSaveButton,\n suppressSuccessToast,\n onSaveControlChange,\n}: {\n scope?: SettingsScope;\n /** Optional hook fired after a successful save (e.g. advance an onboarding step). */\n onSaveSuccess?: () => void;\n /** Forwarded to {@link SdkSectionPage}. */\n initialValueOverrides?: SettingsFormValues;\n /** Forwarded to {@link SdkSectionPage}. */\n embedded?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n hideSaveButton?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n suppressSuccessToast?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n onSaveControlChange?: (control: SdkSectionSaveControl) => void;\n}) {\n const { t } = useTranslation(\"openhands\");\n\n const { data: settings } = useSettings(scope);\n const { data: schema } = useAgentSettingsSchema(\n settings?.agent_settings_schema,\n );\n const persistedLlmSettings = settings?.agent_settings?.llm as\n | Record<string, unknown>\n | undefined;\n const initialAuthType = resolveLlmAuthType(\n initialValueOverrides?.[LLM_AUTH_TYPE_KEY] ??\n persistedLlmSettings?.auth_type,\n );\n const [enableSubscriptionModels, setEnableSubscriptionModels] =\n React.useState(initialAuthType === LLM_AUTH_TYPE_SUBSCRIPTION);\n const {\n data: subscriptionModels,\n isLoading: isSubscriptionModelsLoading,\n isFetching: isSubscriptionModelsFetching,\n } = useOpenAISubscriptionModels({ enabled: enableSubscriptionModels });\n const isWaitingForSubscriptionModels =\n enableSubscriptionModels &&\n !subscriptionModels &&\n (isSubscriptionModelsLoading || isSubscriptionModelsFetching);\n const lastApiKeyModelRef = React.useRef<string | null>(null);\n const lastSubscriptionModelRef = React.useRef<string | null>(null);\n\n React.useEffect(() => {\n if (initialAuthType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n setEnableSubscriptionModels(true);\n }\n }, [initialAuthType]);\n\n const defaultModel = String(\n (DEFAULT_SETTINGS.agent_settings?.llm as Record<string, unknown>)?.model ??\n \"\",\n );\n\n const getInitialView = React.useCallback(\n (\n currentSettings: Settings,\n filteredSchema: SettingsSchema,\n ): SettingsView => {\n const schemaView = inferInitialView(currentSettings, filteredSchema);\n if (schemaView !== \"basic\") {\n return schemaView;\n }\n\n const currentModel = currentSettings.llm_model ?? \"\";\n const trimmedBaseUrl = currentSettings.llm_base_url?.trim() ?? \"\";\n const hasCustomBaseUrl =\n trimmedBaseUrl.length > 0 &&\n !isProviderDefaultBaseUrl(currentModel, trimmedBaseUrl);\n\n return hasCustomBaseUrl ? \"all\" : \"basic\";\n },\n [],\n );\n\n const buildHeader = React.useCallback(\n ({ values, isDisabled, view, onChange }: SdkSectionHeaderProps) => {\n const modelValue =\n typeof values[\"llm.model\"] === \"string\" ? values[\"llm.model\"] : \"\";\n const baseUrlValue =\n typeof values[\"llm.base_url\"] === \"string\"\n ? values[\"llm.base_url\"]\n : \"\";\n const showOpenHandsApiKeyHelp = modelValue.startsWith(\"openhands/\");\n const authType = resolveLlmAuthType(values[LLM_AUTH_TYPE_KEY]);\n const isSubscriptionAuth = authType === LLM_AUTH_TYPE_SUBSCRIPTION;\n const shouldDisableSubscriptionControls =\n isDisabled || (isSubscriptionAuth && isWaitingForSubscriptionModels);\n const subscriptionModelValue = subscriptionModels?.includes(modelValue)\n ? modelValue\n : (subscriptionModels?.[0] ?? \"\");\n\n const apiKeyValue =\n typeof values[\"llm.api_key\"] === \"string\" ? values[\"llm.api_key\"] : \"\";\n // For embedded profile forms (create/edit) the global\n // `llm_api_key_set` flag is misleading: a brand-new profile would show a\n // \"key set\" indicator just because some other profile has a key. Reflect\n // the form's own key state instead so create mode starts visibly unset.\n const apiKeyIsSet = embedded\n ? apiKeyValue.length > 0\n : Boolean(settings?.llm_api_key_set);\n\n const renderApiKeyInput = (testId: string, helpTestId: string) => (\n <>\n <SettingsInput\n testId={testId}\n label={t(I18nKey.SETTINGS_FORM$API_KEY)}\n type=\"password\"\n className=\"w-full\"\n value={apiKeyValue}\n // eslint-disable-next-line i18next/no-literal-string -- masked-key sentinel, not translatable\n placeholder={apiKeyIsSet ? \"<hidden>\" : \"\"}\n onChange={(value) => onChange(\"llm.api_key\", value)}\n isDisabled={isDisabled}\n startContent={\n apiKeyIsSet ? <KeyStatusIcon isSet={apiKeyIsSet} /> : undefined\n }\n />\n\n <HelpLink\n testId={helpTestId}\n text={t(I18nKey.SETTINGS$DONT_KNOW_API_KEY)}\n linkText={t(I18nKey.SETTINGS$CLICK_FOR_INSTRUCTIONS)}\n href=\"https://docs.openhands.dev/usage/local-setup#getting-an-api-key\"\n />\n </>\n );\n\n const handleAuthTypeChange = (selectedKey: React.Key | null) => {\n const nextAuthType =\n selectedKey === LLM_AUTH_TYPE_SUBSCRIPTION\n ? LLM_AUTH_TYPE_SUBSCRIPTION\n : LLM_AUTH_TYPE_API_KEY;\n onChange(LLM_AUTH_TYPE_KEY, nextAuthType);\n\n if (nextAuthType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n setEnableSubscriptionModels(true);\n if (modelValue && !subscriptionModels?.includes(modelValue)) {\n lastApiKeyModelRef.current = modelValue;\n }\n const restoredSubscriptionModel =\n lastSubscriptionModelRef.current &&\n subscriptionModels?.includes(lastSubscriptionModelRef.current)\n ? lastSubscriptionModelRef.current\n : subscriptionModels?.[0];\n onChange(LLM_SUBSCRIPTION_VENDOR_KEY, OPENAI_SUBSCRIPTION_VENDOR);\n if (\n !subscriptionModels?.includes(modelValue) &&\n restoredSubscriptionModel\n ) {\n onChange(\"llm.model\", restoredSubscriptionModel);\n }\n return;\n }\n\n if (modelValue && subscriptionModels?.includes(modelValue)) {\n lastSubscriptionModelRef.current = modelValue;\n onChange(\"llm.model\", lastApiKeyModelRef.current ?? defaultModel);\n }\n };\n\n const renderAuthTypeInput = () => (\n <SettingsDropdownInput\n testId=\"llm-auth-type-input\"\n name={LLM_AUTH_TYPE_KEY}\n label={t(I18nKey.SETTINGS$LLM_AUTH_TYPE)}\n items={[\n {\n key: LLM_AUTH_TYPE_API_KEY,\n label: t(I18nKey.SETTINGS$LLM_AUTH_TYPE_API_KEY),\n },\n {\n key: LLM_AUTH_TYPE_SUBSCRIPTION,\n label: t(I18nKey.SETTINGS$LLM_AUTH_TYPE_SUBSCRIPTION),\n },\n ]}\n selectedKey={authType}\n isClearable={false}\n required\n isDisabled={shouldDisableSubscriptionControls}\n onSelectionChange={handleAuthTypeChange}\n />\n );\n\n const renderSubscriptionSettings = () => (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-subscription-settings\"\n >\n <SettingsDropdownInput\n testId=\"llm-subscription-model-input\"\n name=\"llm.subscription_model\"\n label={t(I18nKey.SETTINGS$SUBSCRIPTION_MODEL)}\n items={(subscriptionModels ?? []).map((model) => ({\n key: model,\n label: model,\n }))}\n selectedKey={subscriptionModelValue}\n isClearable={false}\n required\n isDisabled={\n shouldDisableSubscriptionControls || !subscriptionModels?.length\n }\n onSelectionChange={(selectedKey) => {\n const nextModel =\n typeof selectedKey === \"string\"\n ? selectedKey\n : subscriptionModels?.[0];\n if (nextModel) {\n onChange(\"llm.model\", nextModel);\n }\n }}\n />\n <OpenAISubscriptionAuthCard isDisabled={isDisabled} />\n </div>\n );\n\n return (\n <div className=\"flex flex-col gap-6\">\n {view === \"basic\" ? (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-settings-form-basic\"\n >\n {renderAuthTypeInput()}\n\n {isSubscriptionAuth ? (\n renderSubscriptionSettings()\n ) : (\n <>\n <ModelSelector\n currentModel={modelValue || undefined}\n onChange={(provider, model) => {\n const nextModel = buildModelId(provider, model);\n if (nextModel) {\n onChange(\"llm.model\", nextModel);\n }\n }}\n wrapperClassName=\"!flex-col !gap-6\"\n isDisabled={isDisabled}\n />\n\n {showOpenHandsApiKeyHelp ? (\n <OpenHandsApiKeyHelp testId=\"openhands-api-key-help\" />\n ) : null}\n\n {renderApiKeyInput(\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-input\",\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-help-anchor\",\n )}\n </>\n )}\n </div>\n ) : (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-settings-form-advanced\"\n >\n {renderAuthTypeInput()}\n\n {isSubscriptionAuth ? (\n renderSubscriptionSettings()\n ) : (\n <>\n <SettingsInput\n testId=\"llm-custom-model-input\"\n label={t(I18nKey.SETTINGS$CUSTOM_MODEL)}\n type=\"text\"\n className=\"w-full\"\n value={modelValue}\n placeholder={defaultModel}\n onChange={(value) => onChange(\"llm.model\", value)}\n isDisabled={isDisabled}\n />\n\n {showOpenHandsApiKeyHelp ? (\n <OpenHandsApiKeyHelp testId=\"openhands-api-key-help-2\" />\n ) : null}\n\n <SettingsInput\n testId=\"base-url-input\"\n label={t(I18nKey.SETTINGS$BASE_URL)}\n type=\"text\"\n className=\"w-full\"\n value={baseUrlValue}\n // eslint-disable-next-line i18next/no-literal-string -- example value, not translatable\n placeholder=\"https://api.openai.com\"\n onChange={(value) => onChange(\"llm.base_url\", value)}\n isDisabled={isDisabled}\n />\n\n {renderApiKeyInput(\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-input\",\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-help-anchor-advanced\",\n )}\n </>\n )}\n </div>\n )}\n </div>\n );\n },\n [\n defaultModel,\n embedded,\n isWaitingForSubscriptionModels,\n settings?.llm_api_key_set,\n subscriptionModels,\n t,\n ],\n );\n\n const buildPayload = React.useCallback(\n (\n defaultPayload: Record<string, unknown>,\n context: {\n values: Record<string, string | boolean>;\n dirty: Record<string, boolean>;\n view: SettingsView;\n },\n ) => {\n // defaultPayload is the wrapped diff (e.g.\n // `{ agent_settings_diff: { llm: { model: \"gpt-4\" } } }`); we only\n // mutate the inner `llm` object below.\n const agentSettings = structuredClone(\n (defaultPayload.agent_settings_diff as Record<string, unknown>) ?? {},\n );\n const llm = (agentSettings.llm ?? {}) as Record<string, unknown>;\n const authType = resolveLlmAuthType(context.values[LLM_AUTH_TYPE_KEY]);\n\n if (authType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n llm.auth_type = LLM_AUTH_TYPE_SUBSCRIPTION;\n llm.subscription_vendor = OPENAI_SUBSCRIPTION_VENDOR;\n const model =\n typeof llm.model === \"string\"\n ? llm.model\n : String(context.values[\"llm.model\"] ?? \"\");\n const fallbackSubscriptionModel = subscriptionModels?.[0];\n if (\n !subscriptionModels?.includes(model) &&\n !fallbackSubscriptionModel\n ) {\n throw new Error(\"Subscription models are not loaded yet.\");\n }\n llm.model = subscriptionModels?.includes(model)\n ? model\n : fallbackSubscriptionModel;\n delete llm.api_key;\n delete llm.base_url;\n } else {\n if (context.dirty[LLM_AUTH_TYPE_KEY]) {\n llm.auth_type = LLM_AUTH_TYPE_API_KEY;\n llm.subscription_vendor = null;\n }\n if (context.view === \"basic\" && llm.model !== undefined) {\n llm.base_url = getSchemaFieldDefaultValue(schema, \"llm.base_url\");\n }\n }\n\n agentSettings.llm = llm;\n return { agent_settings_diff: agentSettings };\n },\n [schema, subscriptionModels],\n );\n\n return (\n <SdkSectionPage\n scope={scope}\n settingsSources={[\n {\n settingsSource: \"agent_settings\",\n sectionKeys: [\"llm\"],\n excludeKeys: LLM_EXCLUDED_KEYS,\n },\n ]}\n header={buildHeader}\n buildPayload={buildPayload}\n getInitialView={getInitialView}\n forceShowAdvancedView\n allowAllView\n onSaveSuccess={onSaveSuccess}\n initialValueOverrides={initialValueOverrides}\n embedded={embedded}\n hideSaveButton={hideSaveButton}\n suppressSuccessToast={suppressSuccessToast}\n onSaveControlChange={onSaveControlChange}\n testId=\"llm-settings-screen\"\n />\n );\n}\n\n/**\n * Default export for the route renders the LLM-profile management view for both\n * backend types. Both manage the LLM through named profiles — local via the\n * agent-server (`/api/profiles`), cloud via the app-server\n * (`/api/v1/settings/profiles`) — and the view is backend-agnostic because it\n * goes through ProfilesService, which routes per active backend.\n *\n * The LlmSettingsScreen component is also exported for embedded use cases\n * (e.g., onboarding, the profile create/edit form).\n *\n * Note: This is a route file, only the router should import the default export.\n * Other consumers should use the named export `LlmSettingsScreen` for embedded\n * use cases.\n */\nexport default function LlmSettingsRoute() {\n return <LlmSettingsLocalView />;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"llm-settings.js","names":[],"sources":["../../src/routes/llm-settings.tsx"],"sourcesContent":["import React from \"react\";\nimport { Info } from \"lucide-react\";\nimport { useTranslation } from \"react-i18next\";\nimport { ModelSelector } from \"#/components/shared/modals/settings/model-selector\";\nimport { useAgentSettingsSchema } from \"#/hooks/query/use-agent-settings-schema\";\nimport { useSettings } from \"#/hooks/query/use-settings\";\nimport { SettingsInput } from \"#/components/features/settings/settings-input\";\nimport { SettingsDropdownInput } from \"#/components/features/settings/settings-dropdown-input\";\nimport { OpenAISubscriptionAuthCard } from \"#/components/features/settings/llm-settings/openai-subscription-auth-card\";\nimport { HelpLink } from \"#/ui/help-link\";\nimport { KeyStatusIcon } from \"#/components/features/settings/key-status-icon\";\nimport {\n SdkSectionHeaderProps,\n SdkSectionPage,\n SdkSectionSaveControl,\n} from \"#/components/features/settings/sdk-settings/sdk-section-page\";\nimport { LlmSettingsLocalView } from \"#/components/features/settings/llm-profiles\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { Settings, SettingsSchema, SettingsScope } from \"#/types/settings\";\nimport { extractModelAndProvider } from \"#/utils/extract-model-and-provider\";\nimport {\n inferInitialView,\n type SettingsFormValues,\n type SettingsView,\n} from \"#/utils/sdk-settings-schema\";\nimport { DEFAULT_SETTINGS } from \"#/services/settings\";\nimport {\n LLM_AUTH_TYPE_API_KEY,\n LLM_AUTH_TYPE_KEY,\n LLM_AUTH_TYPE_SUBSCRIPTION,\n LLM_SUBSCRIPTION_VENDOR_KEY,\n OPENAI_SUBSCRIPTION_VENDOR,\n resolveLlmAuthType,\n} from \"#/constants/llm-subscription\";\nimport { useOpenAISubscriptionModels } from \"#/hooks/query/use-llm-subscription-models\";\nimport {\n FREE_OPENHANDS_MODEL_NOTE,\n isFreeOpenHandsModel,\n} from \"#/utils/format-model-name\";\n\nconst LLM_EXCLUDED_KEYS = new Set([\n \"llm.model\",\n \"llm.api_key\",\n \"llm.base_url\",\n LLM_AUTH_TYPE_KEY,\n LLM_SUBSCRIPTION_VENDOR_KEY,\n]);\n\nconst buildModelId = (provider: string | null, model: string | null) => {\n if (!provider || !model) return null;\n return `${provider}/${model}`;\n};\n\nconst getSchemaFieldDefaultValue = (\n schema: SettingsSchema | null | undefined,\n fieldKey: string,\n) =>\n schema?.sections\n .flatMap((section) => section.fields)\n .find((field) => field.key === fieldKey)?.default ?? null;\n\nconst KNOWN_PROVIDER_DEFAULT_BASE_URLS: Partial<Record<string, Set<string>>> = {\n openai: new Set([\"https://api.openai.com\", \"https://api.openai.com/v1\"]),\n moonshot: new Set([\"https://api.kimi.com/coding/v1\"]),\n};\n\nconst normalizeBaseUrl = (baseUrl: string) => {\n try {\n const parsedUrl = new URL(baseUrl);\n const normalizedPath = parsedUrl.pathname.replace(/\\/+$/, \"\") || \"\";\n return `${parsedUrl.origin}${normalizedPath}`;\n } catch {\n return baseUrl.trim().replace(/\\/+$/, \"\");\n }\n};\n\nconst isProviderDefaultBaseUrl = (model: string, baseUrl: string) => {\n const normalizedBaseUrl = normalizeBaseUrl(baseUrl);\n const { provider } = extractModelAndProvider(model);\n\n if (provider) {\n const knownDefaults = KNOWN_PROVIDER_DEFAULT_BASE_URLS[provider];\n if (knownDefaults) {\n return knownDefaults.has(normalizedBaseUrl);\n }\n }\n\n return Object.values(KNOWN_PROVIDER_DEFAULT_BASE_URLS).some((knownDefaults) =>\n knownDefaults?.has(normalizedBaseUrl),\n );\n};\n\ninterface OpenHandsApiKeyHelpProps {\n testId: string;\n}\n\nfunction OpenHandsApiKeyHelp({ testId }: OpenHandsApiKeyHelpProps) {\n const { t } = useTranslation(\"openhands\");\n\n return (\n <HelpLink\n testId={testId}\n text={t(I18nKey.SETTINGS$OPENHANDS_API_KEY_HELP_TEXT)}\n linkText={t(I18nKey.SETTINGS$NAV_API_KEYS)}\n href=\"https://app.all-hands.dev/settings/api-keys\"\n suffix={` ${t(I18nKey.SETTINGS$OPENHANDS_API_KEY_HELP_SUFFIX)}`}\n />\n );\n}\n\nfunction OpenHandsFreeModelsNote() {\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\nexport function LlmSettingsScreen({\n scope = \"personal\",\n onSaveSuccess,\n initialValueOverrides,\n embedded,\n hideSaveButton,\n suppressSuccessToast,\n onSaveControlChange,\n}: {\n scope?: SettingsScope;\n /** Optional hook fired after a successful save (e.g. advance an onboarding step). */\n onSaveSuccess?: () => void;\n /** Forwarded to {@link SdkSectionPage}. */\n initialValueOverrides?: SettingsFormValues;\n /** Forwarded to {@link SdkSectionPage}. */\n embedded?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n hideSaveButton?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n suppressSuccessToast?: boolean;\n /** Forwarded to {@link SdkSectionPage}. */\n onSaveControlChange?: (control: SdkSectionSaveControl) => void;\n}) {\n const { t } = useTranslation(\"openhands\");\n\n const { data: settings } = useSettings(scope);\n const { data: schema } = useAgentSettingsSchema(\n settings?.agent_settings_schema,\n );\n const persistedLlmSettings = settings?.agent_settings?.llm as\n | Record<string, unknown>\n | undefined;\n const initialAuthType = resolveLlmAuthType(\n initialValueOverrides?.[LLM_AUTH_TYPE_KEY] ??\n persistedLlmSettings?.auth_type,\n );\n const [enableSubscriptionModels, setEnableSubscriptionModels] =\n React.useState(initialAuthType === LLM_AUTH_TYPE_SUBSCRIPTION);\n const {\n data: subscriptionModels,\n isLoading: isSubscriptionModelsLoading,\n isFetching: isSubscriptionModelsFetching,\n } = useOpenAISubscriptionModels({ enabled: enableSubscriptionModels });\n const isWaitingForSubscriptionModels =\n enableSubscriptionModels &&\n !subscriptionModels &&\n (isSubscriptionModelsLoading || isSubscriptionModelsFetching);\n const lastApiKeyModelRef = React.useRef<string | null>(null);\n const lastSubscriptionModelRef = React.useRef<string | null>(null);\n\n React.useEffect(() => {\n if (initialAuthType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n setEnableSubscriptionModels(true);\n }\n }, [initialAuthType]);\n\n const defaultModel = String(\n (DEFAULT_SETTINGS.agent_settings?.llm as Record<string, unknown>)?.model ??\n \"\",\n );\n\n const getInitialView = React.useCallback(\n (\n currentSettings: Settings,\n filteredSchema: SettingsSchema,\n ): SettingsView => {\n const schemaView = inferInitialView(currentSettings, filteredSchema);\n if (schemaView !== \"basic\") {\n return schemaView;\n }\n\n const currentModel = currentSettings.llm_model ?? \"\";\n const trimmedBaseUrl = currentSettings.llm_base_url?.trim() ?? \"\";\n const hasCustomBaseUrl =\n trimmedBaseUrl.length > 0 &&\n !isProviderDefaultBaseUrl(currentModel, trimmedBaseUrl);\n\n return hasCustomBaseUrl ? \"all\" : \"basic\";\n },\n [],\n );\n\n const buildHeader = React.useCallback(\n ({ values, isDisabled, view, onChange }: SdkSectionHeaderProps) => {\n const modelValue =\n typeof values[\"llm.model\"] === \"string\" ? values[\"llm.model\"] : \"\";\n const baseUrlValue =\n typeof values[\"llm.base_url\"] === \"string\"\n ? values[\"llm.base_url\"]\n : \"\";\n const showOpenHandsApiKeyHelp = modelValue.startsWith(\"openhands/\");\n const authType = resolveLlmAuthType(values[LLM_AUTH_TYPE_KEY]);\n const isSubscriptionAuth = authType === LLM_AUTH_TYPE_SUBSCRIPTION;\n const shouldDisableSubscriptionControls =\n isDisabled || (isSubscriptionAuth && isWaitingForSubscriptionModels);\n const subscriptionModelValue = subscriptionModels?.includes(modelValue)\n ? modelValue\n : (subscriptionModels?.[0] ?? \"\");\n\n const apiKeyValue =\n typeof values[\"llm.api_key\"] === \"string\" ? values[\"llm.api_key\"] : \"\";\n // For embedded profile forms (create/edit) the global\n // `llm_api_key_set` flag is misleading: a brand-new profile would show a\n // \"key set\" indicator just because some other profile has a key. Reflect\n // the form's own key state instead so create mode starts visibly unset.\n const apiKeyIsSet = embedded\n ? apiKeyValue.length > 0\n : Boolean(settings?.llm_api_key_set);\n\n const renderApiKeyInput = (testId: string, helpTestId: string) => (\n <>\n <SettingsInput\n testId={testId}\n label={t(I18nKey.SETTINGS_FORM$API_KEY)}\n type=\"password\"\n className=\"w-full\"\n value={apiKeyValue}\n // eslint-disable-next-line i18next/no-literal-string -- masked-key sentinel, not translatable\n placeholder={apiKeyIsSet ? \"<hidden>\" : \"\"}\n onChange={(value) => onChange(\"llm.api_key\", value)}\n isDisabled={isDisabled}\n startContent={\n apiKeyIsSet ? <KeyStatusIcon isSet={apiKeyIsSet} /> : undefined\n }\n />\n\n <HelpLink\n testId={helpTestId}\n text={t(I18nKey.SETTINGS$DONT_KNOW_API_KEY)}\n linkText={t(I18nKey.SETTINGS$CLICK_FOR_INSTRUCTIONS)}\n href=\"https://docs.openhands.dev/usage/local-setup#getting-an-api-key\"\n />\n </>\n );\n\n const handleAuthTypeChange = (selectedKey: React.Key | null) => {\n const nextAuthType =\n selectedKey === LLM_AUTH_TYPE_SUBSCRIPTION\n ? LLM_AUTH_TYPE_SUBSCRIPTION\n : LLM_AUTH_TYPE_API_KEY;\n onChange(LLM_AUTH_TYPE_KEY, nextAuthType);\n\n if (nextAuthType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n setEnableSubscriptionModels(true);\n if (modelValue && !subscriptionModels?.includes(modelValue)) {\n lastApiKeyModelRef.current = modelValue;\n }\n const restoredSubscriptionModel =\n lastSubscriptionModelRef.current &&\n subscriptionModels?.includes(lastSubscriptionModelRef.current)\n ? lastSubscriptionModelRef.current\n : subscriptionModels?.[0];\n onChange(LLM_SUBSCRIPTION_VENDOR_KEY, OPENAI_SUBSCRIPTION_VENDOR);\n if (\n !subscriptionModels?.includes(modelValue) &&\n restoredSubscriptionModel\n ) {\n onChange(\"llm.model\", restoredSubscriptionModel);\n }\n return;\n }\n\n if (modelValue && subscriptionModels?.includes(modelValue)) {\n lastSubscriptionModelRef.current = modelValue;\n onChange(\"llm.model\", lastApiKeyModelRef.current ?? defaultModel);\n }\n };\n\n const renderAuthTypeInput = () => (\n <SettingsDropdownInput\n testId=\"llm-auth-type-input\"\n name={LLM_AUTH_TYPE_KEY}\n label={t(I18nKey.SETTINGS$LLM_AUTH_TYPE)}\n items={[\n {\n key: LLM_AUTH_TYPE_API_KEY,\n label: t(I18nKey.SETTINGS$LLM_AUTH_TYPE_API_KEY),\n },\n {\n key: LLM_AUTH_TYPE_SUBSCRIPTION,\n label: t(I18nKey.SETTINGS$LLM_AUTH_TYPE_SUBSCRIPTION),\n },\n ]}\n selectedKey={authType}\n isClearable={false}\n required\n isDisabled={shouldDisableSubscriptionControls}\n onSelectionChange={handleAuthTypeChange}\n />\n );\n\n const renderSubscriptionSettings = () => (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-subscription-settings\"\n >\n <SettingsDropdownInput\n testId=\"llm-subscription-model-input\"\n name=\"llm.subscription_model\"\n label={t(I18nKey.SETTINGS$SUBSCRIPTION_MODEL)}\n items={(subscriptionModels ?? []).map((model) => ({\n key: model,\n label: model,\n }))}\n selectedKey={subscriptionModelValue}\n isClearable={false}\n required\n isDisabled={\n shouldDisableSubscriptionControls || !subscriptionModels?.length\n }\n onSelectionChange={(selectedKey) => {\n const nextModel =\n typeof selectedKey === \"string\"\n ? selectedKey\n : subscriptionModels?.[0];\n if (nextModel) {\n onChange(\"llm.model\", nextModel);\n }\n }}\n />\n <OpenAISubscriptionAuthCard isDisabled={isDisabled} />\n </div>\n );\n\n return (\n <div className=\"flex flex-col gap-6\">\n {view === \"basic\" ? (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-settings-form-basic\"\n >\n {renderAuthTypeInput()}\n\n {isSubscriptionAuth ? (\n renderSubscriptionSettings()\n ) : (\n <>\n <ModelSelector\n currentModel={modelValue || undefined}\n onChange={(provider, model) => {\n const nextModel = buildModelId(provider, model);\n if (nextModel) {\n onChange(\"llm.model\", nextModel);\n }\n }}\n wrapperClassName=\"!flex-col !gap-6\"\n isDisabled={isDisabled}\n />\n\n {showOpenHandsApiKeyHelp ? (\n <OpenHandsApiKeyHelp testId=\"openhands-api-key-help\" />\n ) : null}\n\n {renderApiKeyInput(\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-input\",\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-help-anchor\",\n )}\n </>\n )}\n </div>\n ) : (\n <div\n className=\"flex flex-col gap-6\"\n data-testid=\"llm-settings-form-advanced\"\n >\n {renderAuthTypeInput()}\n\n {isSubscriptionAuth ? (\n renderSubscriptionSettings()\n ) : (\n <>\n <SettingsInput\n testId=\"llm-custom-model-input\"\n label={t(I18nKey.SETTINGS$CUSTOM_MODEL)}\n type=\"text\"\n className=\"w-full\"\n value={modelValue}\n placeholder={defaultModel}\n onChange={(value) => onChange(\"llm.model\", value)}\n isDisabled={isDisabled}\n />\n\n {showOpenHandsApiKeyHelp ? (\n <>\n {isFreeOpenHandsModel(modelValue) ? (\n <OpenHandsFreeModelsNote />\n ) : null}\n <OpenHandsApiKeyHelp testId=\"openhands-api-key-help-2\" />\n </>\n ) : null}\n\n <SettingsInput\n testId=\"base-url-input\"\n label={t(I18nKey.SETTINGS$BASE_URL)}\n type=\"text\"\n className=\"w-full\"\n value={baseUrlValue}\n // eslint-disable-next-line i18next/no-literal-string -- example value, not translatable\n placeholder=\"https://api.openai.com\"\n onChange={(value) => onChange(\"llm.base_url\", value)}\n isDisabled={isDisabled}\n />\n\n {renderApiKeyInput(\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-input\",\n // eslint-disable-next-line i18next/no-literal-string -- DOM id, not user-facing\n \"llm-api-key-help-anchor-advanced\",\n )}\n </>\n )}\n </div>\n )}\n </div>\n );\n },\n [\n defaultModel,\n embedded,\n isWaitingForSubscriptionModels,\n settings?.llm_api_key_set,\n subscriptionModels,\n t,\n ],\n );\n\n const buildPayload = React.useCallback(\n (\n defaultPayload: Record<string, unknown>,\n context: {\n values: Record<string, string | boolean>;\n dirty: Record<string, boolean>;\n view: SettingsView;\n },\n ) => {\n // defaultPayload is the wrapped diff (e.g.\n // `{ agent_settings_diff: { llm: { model: \"gpt-4\" } } }`); we only\n // mutate the inner `llm` object below.\n const agentSettings = structuredClone(\n (defaultPayload.agent_settings_diff as Record<string, unknown>) ?? {},\n );\n const llm = (agentSettings.llm ?? {}) as Record<string, unknown>;\n const authType = resolveLlmAuthType(context.values[LLM_AUTH_TYPE_KEY]);\n\n if (authType === LLM_AUTH_TYPE_SUBSCRIPTION) {\n llm.auth_type = LLM_AUTH_TYPE_SUBSCRIPTION;\n llm.subscription_vendor = OPENAI_SUBSCRIPTION_VENDOR;\n const model =\n typeof llm.model === \"string\"\n ? llm.model\n : String(context.values[\"llm.model\"] ?? \"\");\n const fallbackSubscriptionModel = subscriptionModels?.[0];\n if (\n !subscriptionModels?.includes(model) &&\n !fallbackSubscriptionModel\n ) {\n throw new Error(\"Subscription models are not loaded yet.\");\n }\n llm.model = subscriptionModels?.includes(model)\n ? model\n : fallbackSubscriptionModel;\n delete llm.api_key;\n delete llm.base_url;\n } else {\n if (context.dirty[LLM_AUTH_TYPE_KEY]) {\n llm.auth_type = LLM_AUTH_TYPE_API_KEY;\n llm.subscription_vendor = null;\n }\n if (context.view === \"basic\" && llm.model !== undefined) {\n llm.base_url = getSchemaFieldDefaultValue(schema, \"llm.base_url\");\n }\n }\n\n agentSettings.llm = llm;\n return { agent_settings_diff: agentSettings };\n },\n [schema, subscriptionModels],\n );\n\n return (\n <SdkSectionPage\n scope={scope}\n settingsSources={[\n {\n settingsSource: \"agent_settings\",\n sectionKeys: [\"llm\"],\n excludeKeys: LLM_EXCLUDED_KEYS,\n },\n ]}\n header={buildHeader}\n buildPayload={buildPayload}\n getInitialView={getInitialView}\n forceShowAdvancedView\n allowAllView\n onSaveSuccess={onSaveSuccess}\n initialValueOverrides={initialValueOverrides}\n embedded={embedded}\n hideSaveButton={hideSaveButton}\n suppressSuccessToast={suppressSuccessToast}\n onSaveControlChange={onSaveControlChange}\n testId=\"llm-settings-screen\"\n />\n );\n}\n\n/**\n * Default export for the route renders the LLM-profile management view for both\n * backend types. Both manage the LLM through named profiles — local via the\n * agent-server (`/api/profiles`), cloud via the app-server\n * (`/api/v1/settings/profiles`) — and the view is backend-agnostic because it\n * goes through ProfilesService, which routes per active backend.\n *\n * The LlmSettingsScreen component is also exported for embedded use cases\n * (e.g., onboarding, the profile create/edit form).\n *\n * Note: This is a route file, only the router should import the default export.\n * Other consumers should use the named export `LlmSettingsScreen` for embedded\n * use cases.\n */\nexport default function LlmSettingsRoute() {\n return <LlmSettingsLocalView />;\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- require("../_virtual/_rolldown/runtime.cjs");function e(e){return e?e.split(`/`).pop()||e:null}exports.formatNativeModelName=e;
1
+ require("../_virtual/_rolldown/runtime.cjs");var e=`Free`,t={"openhands/glm-5.2":`OpenHands GLM-5.2 (free)`,"openhands/deepseek-v4-flash":`OpenHands DeepSeek V4 Flash (free)`,"openhands/minimax-m2.7":`OpenHands MiniMax M2.7 (free)`},n=`Free OpenHands models: ${Object.keys(t).join(`, `)}. Other provider endpoints with similar model names may require separate billing.`,r=e=>!!(e&&e in t);function i(e){return e?r(e)?t[e]:e:null}function a(e){return e?r(e)?t[e]:e.split(`/`).pop()||e:null}exports.FREE_MODEL_BADGE_LABEL=e,exports.FREE_OPENHANDS_MODEL_NOTE=n,exports.formatModelNameForDisplay=i,exports.formatNativeModelName=a,exports.isFreeOpenHandsModel=r;
2
2
  //# sourceMappingURL=format-model-name.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"format-model-name.cjs","names":[],"sources":["../../src/utils/format-model-name.ts"],"sourcesContent":["/**\n * Format a native (OpenHands-kind) routing model string for display, stripping\n * the provider route prefix (e.g. ``\"anthropic/claude-sonnet-4-5-20250929\"`` →\n * ``\"claude-sonnet-4-5-20250929\"``, ``\"litellm_proxy/openai/gpt-4o\"`` →\n * ``\"gpt-4o\"``) so a conversation chip shows a meaningful model name rather than\n * the full routing path.\n *\n * Returns ``null`` for an empty/nullish input, and falls back to the original\n * string when stripping the prefix would leave nothing (e.g. a trailing slash)\n * — never an empty string, which would collapse the chip text.\n *\n * Display-only: unlike {@link deriveProfileNameFromModel} this does not sanitize\n * to an identifier, so it keeps the real model id intact for the chip.\n */\nexport function formatNativeModelName(\n model: string | null | undefined,\n): string | null {\n if (!model) return null;\n const lastSegment = model.split(\"/\").pop();\n return lastSegment || model;\n}\n"],"mappings":"6CAcA,SAAgB,EACd,EACe,CAGf,OAFK,EACe,EAAM,MAAM,GAAG,EAAE,IAC9B,GAAe,EAFH,IAGrB"}
1
+ {"version":3,"file":"format-model-name.cjs","names":[],"sources":["../../src/utils/format-model-name.ts"],"sourcesContent":["export const FREE_MODEL_BADGE_LABEL = \"Free\";\n\nexport const FREE_OPENHANDS_MODELS = {\n \"openhands/glm-5.2\": \"OpenHands GLM-5.2 (free)\",\n \"openhands/deepseek-v4-flash\": \"OpenHands DeepSeek V4 Flash (free)\",\n \"openhands/minimax-m2.7\": \"OpenHands MiniMax M2.7 (free)\",\n} as const;\n\nexport const FREE_OPENHANDS_MODEL_IDS = Object.keys(FREE_OPENHANDS_MODELS);\nexport const FREE_OPENHANDS_MODEL_NOTE = `Free OpenHands models: ${FREE_OPENHANDS_MODEL_IDS.join(\n \", \",\n)}. Other provider endpoints with similar model names may require separate billing.`;\n\nexport const isFreeOpenHandsModel = (\n model: string | null | undefined,\n): model is keyof typeof FREE_OPENHANDS_MODELS =>\n Boolean(model && model in FREE_OPENHANDS_MODELS);\n\nexport function formatModelNameForDisplay(\n model: string | null | undefined,\n): string | null {\n if (!model) return null;\n return isFreeOpenHandsModel(model) ? FREE_OPENHANDS_MODELS[model] : model;\n}\n\nexport function formatProviderModelNameForDisplay(\n provider: string | null | undefined,\n model: string | null | undefined,\n): string | null {\n if (!model) return null;\n const fullModel = provider ? `${provider}/${model}` : model;\n return isFreeOpenHandsModel(fullModel)\n ? FREE_OPENHANDS_MODELS[fullModel]\n : model;\n}\n\n/**\n * Format a native (OpenHands-kind) routing model string for display, stripping\n * the provider route prefix (e.g. ``\"anthropic/claude-sonnet-4-5-20250929\"`` →\n * ``\"claude-sonnet-4-5-20250929\"``, ``\"litellm_proxy/openai/gpt-4o\"`` →\n * ``\"gpt-4o\"``) so a conversation chip shows a meaningful model name rather than\n * the full routing path.\n *\n * Returns ``null`` for an empty/nullish input, and falls back to the original\n * string when stripping the prefix would leave nothing (e.g. a trailing slash)\n * — never an empty string, which would collapse the chip text.\n *\n * Display-only: unlike {@link deriveProfileNameFromModel} this does not sanitize\n * to an identifier, so it keeps the real model id intact for the chip.\n */\nexport function formatNativeModelName(\n model: string | null | undefined,\n): string | null {\n if (!model) return null;\n if (isFreeOpenHandsModel(model)) return FREE_OPENHANDS_MODELS[model];\n const lastSegment = model.split(\"/\").pop();\n return lastSegment || model;\n}\n"],"mappings":"6CAAA,IAAa,EAAyB,OAEzB,EAAwB,CACnC,oBAAqB,2BACrB,8BAA+B,qCAC/B,yBAA0B,+BAC5B,EAGa,EAA4B,0BADD,OAAO,KAAK,CACe,EAAyB,KAC1F,IACF,EAAE,mFAEW,EACX,GAEA,GAAQ,GAAS,KAAS,GAE5B,SAAgB,EACd,EACe,CAEf,OADK,EACE,EAAqB,CAAK,EAAI,EAAsB,GAAS,EADjD,IAErB,CA2BA,SAAgB,EACd,EACe,CAIf,OAHK,EACD,EAAqB,CAAK,EAAU,EAAsB,GAC1C,EAAM,MAAM,GAAG,EAAE,IAC9B,GAAe,EAHH,IAIrB"}
@@ -1,3 +1,14 @@
1
+ export declare const FREE_MODEL_BADGE_LABEL = "Free";
2
+ export declare const FREE_OPENHANDS_MODELS: {
3
+ readonly "openhands/glm-5.2": "OpenHands GLM-5.2 (free)";
4
+ readonly "openhands/deepseek-v4-flash": "OpenHands DeepSeek V4 Flash (free)";
5
+ readonly "openhands/minimax-m2.7": "OpenHands MiniMax M2.7 (free)";
6
+ };
7
+ export declare const FREE_OPENHANDS_MODEL_IDS: string[];
8
+ export declare const FREE_OPENHANDS_MODEL_NOTE: string;
9
+ export declare const isFreeOpenHandsModel: (model: string | null | undefined) => model is keyof typeof FREE_OPENHANDS_MODELS;
10
+ export declare function formatModelNameForDisplay(model: string | null | undefined): string | null;
11
+ export declare function formatProviderModelNameForDisplay(provider: string | null | undefined, model: string | null | undefined): string | null;
1
12
  /**
2
13
  * Format a native (OpenHands-kind) routing model string for display, stripping
3
14
  * the provider route prefix (e.g. ``"anthropic/claude-sonnet-4-5-20250929"`` →
@@ -1,8 +1,16 @@
1
1
  //#region src/utils/format-model-name.ts
2
- function e(e) {
3
- return e ? e.split("/").pop() || e : null;
2
+ var e = "Free", t = {
3
+ "openhands/glm-5.2": "OpenHands GLM-5.2 (free)",
4
+ "openhands/deepseek-v4-flash": "OpenHands DeepSeek V4 Flash (free)",
5
+ "openhands/minimax-m2.7": "OpenHands MiniMax M2.7 (free)"
6
+ }, n = `Free OpenHands models: ${Object.keys(t).join(", ")}. Other provider endpoints with similar model names may require separate billing.`, r = (e) => !!(e && e in t);
7
+ function i(e) {
8
+ return e ? r(e) ? t[e] : e : null;
9
+ }
10
+ function a(e) {
11
+ return e ? r(e) ? t[e] : e.split("/").pop() || e : null;
4
12
  }
5
13
  //#endregion
6
- export { e as formatNativeModelName };
14
+ export { e as FREE_MODEL_BADGE_LABEL, n as FREE_OPENHANDS_MODEL_NOTE, i as formatModelNameForDisplay, a as formatNativeModelName, r as isFreeOpenHandsModel };
7
15
 
8
16
  //# sourceMappingURL=format-model-name.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"format-model-name.js","names":[],"sources":["../../src/utils/format-model-name.ts"],"sourcesContent":["/**\n * Format a native (OpenHands-kind) routing model string for display, stripping\n * the provider route prefix (e.g. ``\"anthropic/claude-sonnet-4-5-20250929\"`` →\n * ``\"claude-sonnet-4-5-20250929\"``, ``\"litellm_proxy/openai/gpt-4o\"`` →\n * ``\"gpt-4o\"``) so a conversation chip shows a meaningful model name rather than\n * the full routing path.\n *\n * Returns ``null`` for an empty/nullish input, and falls back to the original\n * string when stripping the prefix would leave nothing (e.g. a trailing slash)\n * — never an empty string, which would collapse the chip text.\n *\n * Display-only: unlike {@link deriveProfileNameFromModel} this does not sanitize\n * to an identifier, so it keeps the real model id intact for the chip.\n */\nexport function formatNativeModelName(\n model: string | null | undefined,\n): string | null {\n if (!model) return null;\n const lastSegment = model.split(\"/\").pop();\n return lastSegment || model;\n}\n"],"mappings":";AAcA,SAAgB,EACd,GACe;CAGf,OAFK,IACe,EAAM,MAAM,GAAG,EAAE,IAC9B,KAAe,IAFH;AAGrB"}
1
+ {"version":3,"file":"format-model-name.js","names":[],"sources":["../../src/utils/format-model-name.ts"],"sourcesContent":["export const FREE_MODEL_BADGE_LABEL = \"Free\";\n\nexport const FREE_OPENHANDS_MODELS = {\n \"openhands/glm-5.2\": \"OpenHands GLM-5.2 (free)\",\n \"openhands/deepseek-v4-flash\": \"OpenHands DeepSeek V4 Flash (free)\",\n \"openhands/minimax-m2.7\": \"OpenHands MiniMax M2.7 (free)\",\n} as const;\n\nexport const FREE_OPENHANDS_MODEL_IDS = Object.keys(FREE_OPENHANDS_MODELS);\nexport const FREE_OPENHANDS_MODEL_NOTE = `Free OpenHands models: ${FREE_OPENHANDS_MODEL_IDS.join(\n \", \",\n)}. Other provider endpoints with similar model names may require separate billing.`;\n\nexport const isFreeOpenHandsModel = (\n model: string | null | undefined,\n): model is keyof typeof FREE_OPENHANDS_MODELS =>\n Boolean(model && model in FREE_OPENHANDS_MODELS);\n\nexport function formatModelNameForDisplay(\n model: string | null | undefined,\n): string | null {\n if (!model) return null;\n return isFreeOpenHandsModel(model) ? FREE_OPENHANDS_MODELS[model] : model;\n}\n\nexport function formatProviderModelNameForDisplay(\n provider: string | null | undefined,\n model: string | null | undefined,\n): string | null {\n if (!model) return null;\n const fullModel = provider ? `${provider}/${model}` : model;\n return isFreeOpenHandsModel(fullModel)\n ? FREE_OPENHANDS_MODELS[fullModel]\n : model;\n}\n\n/**\n * Format a native (OpenHands-kind) routing model string for display, stripping\n * the provider route prefix (e.g. ``\"anthropic/claude-sonnet-4-5-20250929\"`` →\n * ``\"claude-sonnet-4-5-20250929\"``, ``\"litellm_proxy/openai/gpt-4o\"`` →\n * ``\"gpt-4o\"``) so a conversation chip shows a meaningful model name rather than\n * the full routing path.\n *\n * Returns ``null`` for an empty/nullish input, and falls back to the original\n * string when stripping the prefix would leave nothing (e.g. a trailing slash)\n * — never an empty string, which would collapse the chip text.\n *\n * Display-only: unlike {@link deriveProfileNameFromModel} this does not sanitize\n * to an identifier, so it keeps the real model id intact for the chip.\n */\nexport function formatNativeModelName(\n model: string | null | undefined,\n): string | null {\n if (!model) return null;\n if (isFreeOpenHandsModel(model)) return FREE_OPENHANDS_MODELS[model];\n const lastSegment = model.split(\"/\").pop();\n return lastSegment || model;\n}\n"],"mappings":";AAAA,IAAa,IAAyB,QAEzB,IAAwB;CACnC,qBAAqB;CACrB,+BAA+B;CAC/B,0BAA0B;AAC5B,GAGa,IAA4B,0BADD,OAAO,KAAK,CACe,EAAyB,KAC1F,IACF,EAAE,oFAEW,KACX,MAEA,GAAQ,KAAS,KAAS;AAE5B,SAAgB,EACd,GACe;CAEf,OADK,IACE,EAAqB,CAAK,IAAI,EAAsB,KAAS,IADjD;AAErB;AA2BA,SAAgB,EACd,GACe;CAIf,OAHK,IACD,EAAqB,CAAK,IAAU,EAAsB,KAC1C,EAAM,MAAM,GAAG,EAAE,IAC9B,KAAe,IAHH;AAIrB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openhands/agent-canvas",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "Agent Canvas UI for OpenHands - run AI coding agents with a visual interface",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -1 +0,0 @@
1
- import{n as e,r as t}from"./agent-settings-h8hQz-IU.js";export{e as default,t as handle};
@@ -1 +0,0 @@
1
- import{a as e}from"./rolldown-runtime-Cyuzqnbw.js";import{St as t,yt as n}from"./vendor-styling-Ck5D_A2Q.js";import{U as r}from"./vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~lq5hicxx-cHFhpUBq.js";import{t as i}from"./vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~gxu4j2ea-Cwhh3zLe.js";import{At as a,Pt as o,_ as s,jt as c,n as l,zt as u}from"./active-backend-context-BBvP-bI2.js";import{z as d}from"./utils-DPQfkd-7.js";import{t as f}from"./settings-CWFxymAf.js";import{n as p}from"./use-settings-BHWsOcIG.js";import{t as m}from"./use-save-settings-pSjRhD_m.js";import{t as h}from"./brand-button-CJcwjnfb.js";import{i as g,n as _,r as v,t as y}from"./color-themes-DTj5Ylbr.js";import{t as b}from"./navigation-link-Bxo3-4vo.js";import{t as x}from"./settings-input-D61Vzp9e.js";import{t as S}from"./use-llm-profiles-CU8ah1pz.js";import{t as C}from"./settings-dropdown-input-TWIeiG02.js";import{n as w,t as T}from"./switch-skeleton-DwOoCqaL.js";import{t as E}from"./settings-switch-Drt-rF5s.js";var D=e(t(),1),O=n();function k({defaultKey:e,onChange:t,name:n}){let{t:r}=i(`openhands`);return(0,O.jsx)(C,{testId:n,name:n,onInputChange:t,label:r(d.SETTINGS$LANGUAGE),items:u.map(e=>({key:e.value,label:e.label})),defaultSelectedKey:e,isClearable:!1,wrapperClassName:`w-full min-w-0`})}function A(){let{t:e}=i(`openhands`),t=D.useCallback(e=>{if(!e)return;let t=e;_(t),v(t)},[]);return(0,O.jsx)(C,{testId:`color-theme-input`,name:`color-theme-input`,label:e(d.SETTINGS$COLOR_THEME),items:y.map(e=>({key:e.key,label:e.label})),defaultSelectedKey:g(),onSelectionChange:t,isClearable:!1,wrapperClassName:`w-full min-w-0`})}function j(){return(0,O.jsxs)(`div`,{"data-testid":`app-settings-skeleton`,className:`px-11 py-9 flex flex-col gap-6`,children:[(0,O.jsx)(w,{}),(0,O.jsx)(T,{}),(0,O.jsx)(T,{})]})}var M=`__automatic__`;function N(){let{t:e}=i(`openhands`),{mutate:t,isPending:n}=m(),{data:r,isLoading:g}=p(),_=l().backend.kind===`cloud`,{data:v,isLoading:y}=S(),[w,T]=D.useState(!1),[N,P]=D.useState(!1),[F,I]=D.useState(!1),[L,R]=D.useState(!1),[z,B]=D.useState(!1),[V,H]=D.useState(void 0),U=D.useMemo(()=>{let e=r?.title_llm_profile??null;return!e||!v||v.profiles.some(t=>t.name===e)?e:null},[v,r?.title_llm_profile]),W=V===void 0?U:V,G=D.useMemo(()=>[{key:M,label:e(d.SETTINGS$TITLE_GENERATION_AUTOMATIC)},...v?.profiles.map(t=>({key:t.name,label:t.model?e(d.SETTINGS$TITLE_GENERATION_PROFILE_OPTION,{name:t.name,model:t.model}):t.name}))??[]],[v?.profiles,e]),K=n=>{let r=n.get(`language-input`)?.toString(),i=u.find(({label:e})=>e===r)?.value||f.language,l=_?!0:n.get(`enable-analytics-switch`)?.toString()===`on`,p=n.get(`enable-sound-notifications-switch`)?.toString()===`on`,m=n.get(`git-user-name-input`)?.toString()||f.git_user_name,h=n.get(`git-user-email-input`)?.toString()||f.git_user_email;t({language:i,...!_&&{user_consents_to_analytics:l},enable_sound_notifications:p,git_user_name:m,git_user_email:h,title_llm_profile:W},{onSuccess:()=>{s(l?`granted`:`denied`),c(e(d.SETTINGS$SAVED))},onError:t=>{a(o(t)||e(d.ERROR$GENERIC))},onSettled:()=>{T(!1),P(!1),I(!1),R(!1),B(!1),H(void 0)}})},q=e=>{let t=u.find(({label:t})=>t===e)?.label,n=u.find(({value:e})=>e===r?.language)?.label;T(t!==n)},J=e=>{P(e!==(r?.user_consents_to_analytics??!0))},Y=e=>{I(e!==!!r?.enable_sound_notifications)},X=e=>{let t=r?.git_user_name;R(e!==t)},Z=e=>{let t=r?.git_user_email;B(e!==t)},Q=!w&&!N&&!F&&W===U&&!L&&!z,$=!r||g||y||n;return(0,O.jsxs)(`form`,{"data-testid":`app-settings-screen`,action:K,className:`flex flex-col gap-6`,children:[$&&(0,O.jsx)(j,{}),!$&&(0,O.jsxs)(`div`,{className:`flex flex-col gap-6`,children:[(0,O.jsx)(k,{name:`language-input`,defaultKey:r.language,onChange:q}),(0,O.jsx)(A,{}),(0,O.jsx)(E,{testId:`enable-analytics-switch`,name:_?void 0:`enable-analytics-switch`,defaultIsToggled:_?!0:r.user_consents_to_analytics??!0,isToggled:_?!0:void 0,isDisabled:_,onToggle:_?void 0:J,children:e(d.ANALYTICS$SEND_ANONYMOUS_DATA)}),(0,O.jsx)(E,{testId:`enable-sound-notifications-switch`,name:`enable-sound-notifications-switch`,defaultIsToggled:!!r.enable_sound_notifications,onToggle:Y,children:e(d.SETTINGS$SOUND_NOTIFICATIONS)}),(0,O.jsxs)(`div`,{className:`border-t border-[var(--oh-border)] pt-6 mt-2`,children:[(0,O.jsx)(`h3`,{className:`text-lg font-medium mb-2`,children:e(d.SETTINGS$CONVERSATION_TITLES)}),(0,O.jsx)(`p`,{className:`mb-4 text-sm leading-5 text-tertiary-light`,children:e(d.SETTINGS$TITLE_GENERATION_DESCRIPTION)}),(0,O.jsx)(C,{testId:`title-llm-profile-input`,name:`title-llm-profile-input`,label:e(d.SETTINGS$TITLE_GENERATION_MODEL),items:G,selectedKey:W??M,onSelectionChange:e=>{let t=e?.toString();H(!t||t===M?null:t)}}),(0,O.jsx)(b,{to:`/settings/llm`,className:`mt-3 inline-block text-sm text-primary hover:underline`,children:e(d.SETTINGS$MANAGE_LLM_PROFILES)})]}),(0,O.jsxs)(`div`,{className:`border-t border-[var(--oh-border)] pt-6 mt-2`,children:[(0,O.jsx)(`h3`,{className:`text-lg font-medium mb-2`,children:e(d.SETTINGS$GIT_SETTINGS)}),(0,O.jsx)(`p`,{className:`mb-4 text-sm leading-5 text-tertiary-light`,children:e(d.SETTINGS$GIT_SETTINGS_DESCRIPTION)}),(0,O.jsxs)(`div`,{className:`flex flex-col gap-6`,children:[(0,O.jsx)(x,{testId:`git-user-name-input`,name:`git-user-name-input`,type:`text`,label:e(d.SETTINGS$GIT_USERNAME),defaultValue:r.git_user_name||``,onChange:X,placeholder:e(d.SETTINGS$GIT_USERNAME_PLACEHOLDER),className:`w-full min-w-0`}),(0,O.jsx)(x,{testId:`git-user-email-input`,name:`git-user-email-input`,type:`email`,label:e(d.SETTINGS$GIT_EMAIL),defaultValue:r.git_user_email||``,onChange:Z,placeholder:e(d.SETTINGS$GIT_EMAIL_PLACEHOLDER),className:`w-full min-w-0`})]}),(0,O.jsx)(`div`,{className:`flex justify-start pt-4`,children:(0,O.jsxs)(h,{testId:`submit-button`,variant:`primary`,type:`submit`,isDisabled:n||Q,children:[!n&&e(d.SETTINGS$SAVE_CHANGES),n&&e(d.SETTINGS$SAVING)]})})]})]})]})}var P=r(N);export{P as default};