@jini-ai/ui 0.5.5 → 0.5.6

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 (263) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/core/theme/validation.js +40 -38
  3. package/dist/features/a2ui/renderer.js +15 -13
  4. package/dist/features/about/react/components/AboutTab.js +64 -34
  5. package/dist/features/about/rules.d.ts +2 -2
  6. package/dist/features/about/rules.js +156 -129
  7. package/dist/features/admin-widgets/components/ImagePreviewModal.d.ts +4 -3
  8. package/dist/features/admin-widgets/components/ImagePreviewModal.js +6 -10
  9. package/dist/features/admin-widgets/components/InfoTip.d.ts +3 -2
  10. package/dist/features/admin-widgets/components/InfoTip.hooks.js +2 -0
  11. package/dist/features/admin-widgets/components/InfoTip.js +34 -7
  12. package/dist/features/admin-widgets/components/InfoTip.rules.d.ts +13 -0
  13. package/dist/features/admin-widgets/components/InfoTip.rules.js +43 -10
  14. package/dist/features/admin-widgets/components/Lightbox/Lightbox.d.ts +30 -0
  15. package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.d.ts +40 -0
  16. package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.js +128 -0
  17. package/dist/features/admin-widgets/components/Lightbox/Lightbox.js +18 -0
  18. package/dist/features/admin-widgets/components/MiddleTruncate.d.ts +14 -0
  19. package/dist/features/admin-widgets/components/MiddleTruncate.js +12 -0
  20. package/dist/features/admin-widgets/components/MiddleTruncate.rules.d.ts +20 -0
  21. package/dist/features/admin-widgets/components/MiddleTruncate.rules.js +21 -0
  22. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.d.ts +18 -0
  23. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.d.ts +16 -0
  24. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.js +45 -0
  25. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.js +18 -0
  26. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.d.ts +32 -0
  27. package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.js +26 -0
  28. package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.hooks.js +29 -14
  29. package/dist/features/admin-widgets/index.d.ts +14 -2
  30. package/dist/features/admin-widgets/index.js +7 -1
  31. package/dist/features/admin-widgets/styles/admin-widgets.css +1 -0
  32. package/dist/features/asset-grid/react/components/AssetCard.js +7 -1
  33. package/dist/features/asset-grid/react/components/AssetGrid.d.ts +1 -1
  34. package/dist/features/asset-grid/react/components/AssetGrid.js +38 -18
  35. package/dist/features/asset-grid/react/components/useAssetGridFilters.d.ts +21 -0
  36. package/dist/features/asset-grid/react/components/useAssetGridFilters.js +19 -0
  37. package/dist/features/asset-grid/react/hooks/useAssetGridKeyboardShortcuts.js +16 -11
  38. package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.d.ts +2 -0
  39. package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.js +35 -11
  40. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.d.ts +6 -6
  41. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.js +10 -129
  42. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.d.ts +50 -0
  43. package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.js +95 -0
  44. package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.d.ts +56 -0
  45. package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.js +80 -0
  46. package/dist/features/asset-tree-browser/rules.js +40 -22
  47. package/dist/features/browser-chrome/rules.js +74 -70
  48. package/dist/features/charts/browser.js +11 -5
  49. package/dist/features/charts/react.js +2 -2
  50. package/dist/features/charts/table.js +2 -2
  51. package/dist/features/command-palette/react/hooks/useCommandPalette.js +3 -9
  52. package/dist/features/connectors/ConnectorsBrowser.d.ts +4 -4
  53. package/dist/features/connectors/ConnectorsBrowser.js +26 -14
  54. package/dist/features/connectors/components/ConnectorCard.js +67 -38
  55. package/dist/features/connectors/components/ConnectorDetailDrawer.js +62 -31
  56. package/dist/features/connectors/components/ConnectorGrid.js +11 -6
  57. package/dist/features/connectors/hooks/useConnectorAuthorization.js +6 -0
  58. package/dist/features/connectors/hooks/useConnectorCatalog.d.ts +2 -0
  59. package/dist/features/connectors/hooks/useConnectorCatalog.js +32 -13
  60. package/dist/features/connectors/hooks/useWiredConnectorsBrowser.d.ts +3 -3
  61. package/dist/features/connectors/rules.d.ts +8 -1
  62. package/dist/features/connectors/rules.js +42 -20
  63. package/dist/features/connectors/types.d.ts +11 -0
  64. package/dist/features/execution/constants.d.ts +4 -10
  65. package/dist/features/execution/constants.js +9 -94
  66. package/dist/features/execution/react/components/ByokProviderForm.d.ts +2 -4
  67. package/dist/features/execution/react/components/ByokProviderForm.js +140 -43
  68. package/dist/features/execution/react/components/ExecutionTab.d.ts +1 -2
  69. package/dist/features/execution/react/components/ExecutionTab.js +52 -30
  70. package/dist/features/execution/react/components/LocalCliAgentCard.js +12 -3
  71. package/dist/features/execution/react/components/LocalCliAgentList.js +14 -3
  72. package/dist/features/execution/react/components/LocalCliAgentSettings.js +14 -3
  73. package/dist/features/execution/react/hooks/useLocalCliAgentCard.js +54 -36
  74. package/dist/features/execution/react/hooks/useReasoningControl.js +114 -78
  75. package/dist/features/execution/rules.d.ts +2 -2
  76. package/dist/features/execution/rules.js +31 -15
  77. package/dist/features/execution/types.d.ts +4 -42
  78. package/dist/features/execution/types.js +0 -17
  79. package/dist/features/external-mcp/react/components/ExternalMcpTab.d.ts +6 -2
  80. package/dist/features/external-mcp/react/components/ExternalMcpTab.js +9 -2
  81. package/dist/features/file-dropzone/react/components/FileDropzone.js +24 -25
  82. package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.d.ts +2 -2
  83. package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.js +35 -30
  84. package/dist/features/file-dropzone/react/components/FileDropzoneThumbnailGrid.js +30 -24
  85. package/dist/features/file-dropzone/react/components/FileDropzoneZone.d.ts +14 -0
  86. package/dist/features/file-dropzone/react/components/FileDropzoneZone.js +26 -0
  87. package/dist/features/html-editor/canvas-content-wrapper.js +13 -13
  88. package/dist/features/html-editor/canvas-embed-placeholders.js +46 -37
  89. package/dist/features/html-editor/css.js +76 -69
  90. package/dist/features/html-editor/editor-blocks.js +47 -33
  91. package/dist/features/html-editor/editor-serialization.js +6 -5
  92. package/dist/features/html-editor/protected-elements.js +39 -28
  93. package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.js +4 -4
  94. package/dist/features/html-editor/source-splice.js +51 -30
  95. package/dist/features/i18n/dictionaries/settings-dialog.ja.js +1 -0
  96. package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +1 -0
  97. package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +1 -0
  98. package/dist/features/i18n/locale.js +20 -16
  99. package/dist/features/iframe-pool/react/dom-sync.js +8 -6
  100. package/dist/features/integrations/react/components/CodexInstallToggleButton.js +19 -4
  101. package/dist/features/integrations/react/components/IntegrationsTab.js +60 -14
  102. package/dist/features/interactive-ui/interactive-ui.css +1 -1
  103. package/dist/features/interactive-ui/providers/shadcn/data-table.js +10 -7
  104. package/dist/features/lexical-rich-text-editor/mention-parser.js +60 -57
  105. package/dist/features/lexical-rich-text-editor/react/hooks/useKeyboardCommands.js +33 -38
  106. package/dist/features/lexical-rich-text-editor/rules.js +87 -74
  107. package/dist/features/lexical-rich-text-editor/serialize.js +27 -25
  108. package/dist/features/mcp-ui/secret-card.js +111 -61
  109. package/dist/features/mcp-ui/surfaces/confirmation.js +39 -21
  110. package/dist/features/mcp-ui/surfaces/document.d.ts +1 -1
  111. package/dist/features/mcp-ui/surfaces/document.js +10 -2
  112. package/dist/features/mcp-ui/surfaces/fields.js +30 -50
  113. package/dist/features/mcp-ui/surfaces/form.d.ts +6 -0
  114. package/dist/features/mcp-ui/surfaces/form.js +43 -21
  115. package/dist/features/mcp-ui/surfaces/outcome.js +30 -20
  116. package/dist/features/mcp-ui/surfaces/text-input.js +33 -21
  117. package/dist/features/mcp-ui/surfaces/tokens.d.ts +6 -1
  118. package/dist/features/mcp-ui/surfaces/tokens.js +5 -2
  119. package/dist/features/media-providers/constants.d.ts +1 -0
  120. package/dist/features/media-providers/constants.js +2 -81
  121. package/dist/features/media-providers/index.d.ts +1 -1
  122. package/dist/features/media-providers/ports.d.ts +2 -0
  123. package/dist/features/media-providers/react/components/MediaProviderCard.d.ts +22 -0
  124. package/dist/features/media-providers/react/components/MediaProviderCard.js +50 -0
  125. package/dist/features/media-providers/react/components/MediaProvidersTab.d.ts +5 -6
  126. package/dist/features/media-providers/react/components/MediaProvidersTab.js +40 -72
  127. package/dist/features/media-providers/react/components/media-providers-tab-labels.d.ts +11 -0
  128. package/dist/features/media-providers/react/components/media-providers-tab-labels.js +57 -0
  129. package/dist/features/media-providers/react/hooks/useMediaProvidersTab.d.ts +4 -2
  130. package/dist/features/media-providers/react/hooks/useMediaProvidersTab.js +37 -23
  131. package/dist/features/media-providers/rules.js +17 -10
  132. package/dist/features/media-providers/types.d.ts +4 -22
  133. package/dist/features/media-providers/types.js +3 -8
  134. package/dist/features/memory/formatters.js +127 -92
  135. package/dist/features/memory/react/components/MemoryConnectedPanel.js +92 -36
  136. package/dist/features/memory/react/components/MemoryList.js +37 -18
  137. package/dist/features/memory/react/hooks/useMemoryExtractions.store.js +42 -36
  138. package/dist/features/notifications/react/components/NotificationsTab.js +28 -28
  139. package/dist/features/observability/long-task.d.ts +0 -12
  140. package/dist/features/observability/long-task.js +15 -19
  141. package/dist/features/observability/resource-error.d.ts +0 -10
  142. package/dist/features/observability/resource-error.js +18 -13
  143. package/dist/features/observability/stuck-run.js +10 -6
  144. package/dist/features/observability/telemetry-url.d.ts +6 -0
  145. package/dist/features/observability/telemetry-url.js +16 -0
  146. package/dist/features/observability/white-screen.js +12 -24
  147. package/dist/features/panel-kit/fetch-query/adapter.react.d.ts +2 -2
  148. package/dist/features/panel-kit/fetch-query/adapter.react.js +4 -2
  149. package/dist/features/panel-kit/fetch-query/adapter.tanstack.js +46 -33
  150. package/dist/features/panel-kit/helpers/standing-draft-local-backup.js +3 -1
  151. package/dist/features/panel-kit/hooks/use-settings-slice.hooks.js +3 -1
  152. package/dist/features/privacy/react/components/PrivacyTab.js +24 -13
  153. package/dist/features/progress-card/components/ProgressCard.js +7 -3
  154. package/dist/features/progress-card/reference-adapters.js +59 -54
  155. package/dist/features/project-locations/react/components/ProjectLocationsTab.js +24 -15
  156. package/dist/features/project-locations/react/hooks/useProjectLocationsTab.d.ts +8 -0
  157. package/dist/features/project-locations/react/hooks/useProjectLocationsTab.js +31 -23
  158. package/dist/features/resource-dashboard/react/components/ResourceBoard.js +6 -47
  159. package/dist/features/resource-dashboard/react/components/ResourceBoardView.d.ts +2 -2
  160. package/dist/features/resource-dashboard/react/components/ResourceBoardView.js +3 -6
  161. package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.d.ts +15 -0
  162. package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.js +35 -0
  163. package/dist/features/resource-dashboard/react/components/ResourceCard.js +5 -16
  164. package/dist/features/resource-dashboard/react/components/ResourceCardParts.d.ts +34 -0
  165. package/dist/features/resource-dashboard/react/components/ResourceCardParts.js +34 -0
  166. package/dist/features/resource-dashboard/react/components/ResourceRowList.js +3 -1
  167. package/dist/features/resource-dashboard/react/components/ResourceRowListItem.js +2 -2
  168. package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.d.ts +19 -0
  169. package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.js +11 -0
  170. package/dist/features/resource-dashboard/react/components/ResourceRowListView.d.ts +1 -1
  171. package/dist/features/resource-dashboard/react/components/ResourceRowListView.js +4 -4
  172. package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.d.ts +10 -0
  173. package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.js +20 -0
  174. package/dist/features/resource-dashboard/react/components/resourceBoardProps.d.ts +48 -0
  175. package/dist/features/resource-dashboard/react/components/resourceBoardProps.js +70 -0
  176. package/dist/features/resource-dashboard/react/components/resourceRowListProps.d.ts +19 -0
  177. package/dist/features/resource-dashboard/react/components/resourceRowListProps.js +14 -0
  178. package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.d.ts +16 -0
  179. package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.js +13 -0
  180. package/dist/features/resource-dashboard/rules.js +2 -8
  181. package/dist/features/sketch-editor/dom.js +150 -99
  182. package/dist/features/sketch-editor/react/components/SketchEditor.js +26 -15
  183. package/dist/features/skills/react/components/SkillDraftForm.js +13 -19
  184. package/dist/features/skills/react/components/SkillDraftForm.labels.d.ts +31 -0
  185. package/dist/features/skills/react/components/SkillDraftForm.labels.js +54 -0
  186. package/dist/features/skills/react/components/SkillRow.js +38 -18
  187. package/dist/features/skills/react/components/SkillRow.labels.d.ts +7 -0
  188. package/dist/features/skills/react/components/SkillRow.labels.js +18 -0
  189. package/dist/features/skills/react/components/SkillsTab.js +24 -14
  190. package/dist/features/skills/react/components/SkillsTab.labels.d.ts +7 -0
  191. package/dist/features/skills/react/components/SkillsTab.labels.js +11 -0
  192. package/dist/features/skills/rules.js +32 -23
  193. package/dist/features/source-config-list/agent-handles.js +2 -1
  194. package/dist/features/source-config-list/dependencies.js +53 -49
  195. package/dist/features/source-config-list/index.d.ts +3 -3
  196. package/dist/features/source-config-list/react/components/SourceConfigAddForm.d.ts +6 -5
  197. package/dist/features/source-config-list/react/components/SourceConfigAddForm.js +38 -7
  198. package/dist/features/source-config-list/react/components/SourceConfigField.js +69 -25
  199. package/dist/features/source-config-list/react/components/SourceConfigItemCard.d.ts +6 -5
  200. package/dist/features/source-config-list/react/components/SourceConfigItemCard.js +76 -20
  201. package/dist/features/source-config-list/react/components/SourceConfigListView.d.ts +5 -3
  202. package/dist/features/source-config-list/react/components/SourceConfigListView.js +33 -3
  203. package/dist/features/source-config-list/react/components/SourceConfigTestControl.js +17 -1
  204. package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.d.ts +11 -0
  205. package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.js +12 -0
  206. package/dist/features/source-config-list/types.d.ts +14 -0
  207. package/dist/features/tab-launcher-menu/react/components/TabLauncherMenu.js +25 -1
  208. package/dist/features/tab-launcher-menu/react/hooks/useTabLauncherMenu.js +15 -16
  209. package/dist/features/tab-strip/react/components/TabBar.js +8 -1
  210. package/dist/features/tabbed-dialog/react/components/TabbedDialog.js +8 -30
  211. package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.d.ts +15 -0
  212. package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.js +8 -0
  213. package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.d.ts +12 -0
  214. package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.js +6 -0
  215. package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.d.ts +27 -0
  216. package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.js +12 -0
  217. package/dist/features/tabbed-dialog/react/components/tabbedDialogView.d.ts +43 -0
  218. package/dist/features/tabbed-dialog/react/components/tabbedDialogView.js +59 -0
  219. package/dist/features/version-manager/dependencies.d.ts +3 -1
  220. package/dist/features/version-manager/dependencies.js +3 -2
  221. package/dist/features/version-manager/react/components/VersionManagerModal.js +10 -6
  222. package/dist/features/version-manager/react/hooks/useVersionManager.js +7 -4
  223. package/dist/features/viewer-shell/react/components/CommentSidePanel.js +103 -50
  224. package/dist/features/viewer-shell/react/components/MarkdownSplitPane.js +28 -8
  225. package/dist/features/viewer-shell/react/hooks/useCommentReorder.js +22 -16
  226. package/dist/features/viewer-shell/rules.js +19 -23
  227. package/dist/react/components/CustomSelect.d.ts +38 -1
  228. package/dist/react/components/CustomSelect.js +94 -38
  229. package/dist/react/components/OnboardingDropdown.js +20 -8
  230. package/dist/react/components/RemixIcon.stylesheet.js +3 -1
  231. package/dist/react/components/Toast.js +13 -2
  232. package/dist/react/components/TooltipLayer.js +5 -14
  233. package/dist/react/components/WorkingDirPicker.js +78 -50
  234. package/dist/react/components/remixicon-font-recovery.js +38 -30
  235. package/dist/react/components/select-popover.d.ts +8 -6
  236. package/dist/react/components/select-popover.js +33 -29
  237. package/dist/react/mcp-ui/McpUiHost.d.ts +1 -1
  238. package/dist/react/mcp-ui/McpUiHost.js +10 -11
  239. package/dist/react/mcp-ui/host-message-source.d.ts +14 -2
  240. package/dist/react/mcp-ui/host-message-source.js +15 -5
  241. package/dist/react/mcp-ui/mcp-ui-host-frame.d.ts +41 -0
  242. package/dist/react/mcp-ui/mcp-ui-host-frame.js +39 -0
  243. package/dist/renderers/annotation-canvas/react/components/AnnotationCanvas.js +97 -69
  244. package/dist/renderers/preview-modal-shell/react/components/PreviewModalShell.js +59 -30
  245. package/dist/renderers/react/components/ArtifactView.js +9 -6
  246. package/dist/renderers/srcdoc/build.d.ts +4 -1
  247. package/dist/renderers/srcdoc/build.js +62 -48
  248. package/dist/styles/admin.css +29 -14
  249. package/dist/styles/lightbox.css +142 -0
  250. package/dist/styles/variables.css +128 -0
  251. package/dist/theme/apply.js +9 -5
  252. package/dist/utils/auto-open-file.d.ts +2 -1
  253. package/dist/utils/auto-open-file.js +69 -85
  254. package/dist/utils/markdown-scroll-sync.js +108 -87
  255. package/dist/utils/polygon-selection.js +25 -26
  256. package/dist/utils/smooth-scroll-to-top.d.ts +1 -1
  257. package/dist/utils/smooth-scroll-to-top.js +2 -32
  258. package/dist/utils/sse.js +26 -26
  259. package/dist/utils/unit-bezier.d.ts +7 -0
  260. package/dist/utils/unit-bezier.js +38 -0
  261. package/package.json +7 -10
  262. package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.d.ts +0 -13
  263. package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.js +0 -46
@@ -2,20 +2,22 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useEffect, useId, useState } from 'react';
3
3
  import { agentHandleProps, agentSubHandle } from '@jini-ai/agentic';
4
4
  import { useT } from '../../../i18n/index.js';
5
- import { CUSTOM_MODEL_SENTINEL, DEFAULT_PROVIDER_PRESETS } from '../../constants.js';
5
+ import { CUSTOM_MODEL_SENTINEL } from '../../constants.js';
6
6
  import { apiKeyFormatWarning, parseMaxTokens, presetRequiresApiKey, shouldShowCustomModelInput, showsBaseUrlField, } from '../../rules.js';
7
7
  import { SearchableModelSelect } from './SearchableModelSelect.js';
8
8
  import { useByokFieldValidation } from '../hooks/use-byok-field-validation.hooks.js';
9
+ /** `' is-missing'` when `field` is a required field the operator still has to fill. */
10
+ function missingClass(validation, field) {
11
+ return validation.isMissing({ field }) ? ' is-missing' : '';
12
+ }
9
13
  /**
10
14
  * The BYOK credential card: API key, base URL, optional token cap, and model.
11
15
  * Origin: `components/byok/*` (`ByokKeyField`, `ByokProviderBaseUrl`,
12
16
  * `ByokModelField`, `ByokConnectionTestControl`) — already factored out
13
17
  * upstream, so this is assembly plus the origin's required-field marking.
14
18
  */
15
- export function ByokProviderForm({ config, onConfigChange, preset, presets = DEFAULT_PROVIDER_PRESETS, modelDiscovery, connectionTest, onTestConnection, canTestConnection = true, apiKeyFooter, formFooter, modelFooter, apiKeyStoredExternally = false, apiKeyPlaceholder, agentHandle, apiKeyActions, collapseAdvanced = false, deferMissingFeedback = false, }) {
19
+ export function ByokProviderForm({ config, onConfigChange, preset, presets = [], modelDiscovery, connectionTest, onTestConnection, canTestConnection = true, apiKeyFooter, formFooter, modelFooter, apiKeyStoredExternally = false, apiKeyPlaceholder, agentHandle, apiKeyActions, collapseAdvanced = false, deferMissingFeedback = false, }) {
16
20
  const t = useT();
17
- const keyInputId = useId();
18
- const KeyField = apiKeyActions ? 'div' : 'label';
19
21
  const [revealKey, setRevealKey] = useState(false);
20
22
  /** Whether the operator explicitly picked "Custom…" in the model picker — distinct from "the
21
23
  * saved model simply isn't in the live list". Same UI-local toggle, for the same reason, as
@@ -36,12 +38,53 @@ export function ByokProviderForm({ config, onConfigChange, preset, presets = DEF
36
38
  useEffect(() => {
37
39
  setRevealKey(false);
38
40
  }, [config.providerId]);
39
- const { missing, baseUrlInvalid, isMissing, markTouched } = useByokFieldValidation({ config, preset, apiKeyStoredExternally }, { deferMissingFeedback });
41
+ const validation = useByokFieldValidation({ config, preset, apiKeyStoredExternally }, { deferMissingFeedback });
42
+ const patch = (next) => onConfigChange({ ...config, ...next });
43
+ const part = { config, patch, preset, agentHandle, validation };
44
+ const connectionFields = _jsx(ByokConnectionFields, { ...part });
45
+ return (_jsxs("div", { className: "jini-byok-card", children: [_jsx("div", { className: "jini-byok-card-head", children: _jsx("h4", { className: "jini-byok-card-title", children: preset ? preset.title : t('Custom endpoint') }) }), presetRequiresApiKey(preset) ? (_jsx(ByokApiKeyField, { ...part, presets: presets, revealKey: revealKey, onToggleReveal: () => setRevealKey((shown) => !shown), apiKeyFooter: apiKeyFooter, apiKeyPlaceholder: apiKeyPlaceholder, apiKeyActions: apiKeyActions })) : null, collapseAdvanced ? null : connectionFields, _jsx(ByokModelRow, { ...part, modelDiscovery: modelDiscovery, explicitCustomModel: explicitCustomModel, setExplicitCustomModel: setExplicitCustomModel, connectionTest: connectionTest, onTestConnection: onTestConnection, canTestConnection: canTestConnection }), _jsx(ByokDiscoveryError, { modelDiscovery: modelDiscovery }), _jsx(ByokConnectionResult, { connectionTest: connectionTest, canTestConnection: canTestConnection }), _jsx(ByokSlot, { className: "jini-byok-model-footer", children: modelFooter }), collapseAdvanced ? _jsx(ByokAdvanced, { validation: validation, children: connectionFields }) : null, _jsx(ByokSlot, { className: "jini-byok-form-footer", children: formFooter })] }));
46
+ }
47
+ /** Base URL (unless the preset has a fixed origin) and Max tokens, on one wrapping row. */
48
+ function ByokConnectionFields({ config, patch, preset, agentHandle, validation }) {
49
+ const t = useT();
50
+ return (_jsx(_Fragment, { children: _jsxs("div", { className: "jini-byok-field-row", children: [showsBaseUrlField(preset) ? (_jsxs("label", { className: "jini-field", onBlurCapture: () => validation.markTouched({ field: 'baseUrl' }), children: [_jsxs("span", { className: "jini-field-label", children: [t('Base URL'), preset?.baseUrl ? null : _jsx("span", { className: "jini-field-required", "aria-hidden": "true", children: "*" })] }), _jsx("input", { className: 'jini-input' + missingClass(validation, 'baseUrl'), type: "url", inputMode: "url", spellCheck: false, value: config.baseUrl, onChange: (event) => patch({ baseUrl: event.target.value }), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'base-url', role: 'field', label: t('Base URL') }) }) }), _jsx("span", { className: 'jini-field-hint' + (validation.baseUrlInvalid ? ' is-error' : ''), children: validation.baseUrlInvalid
51
+ ? t('Enter an absolute http(s) URL.')
52
+ : t('Default endpoint. Usually no need to change this.') })] })) : null, _jsxs("label", { className: "jini-field", children: [_jsx("span", { className: "jini-field-label", children: t('Max tokens (optional)') }), _jsx("input", { className: "jini-input", type: "number", min: 1, step: 1, value: config.maxTokens ?? '', onChange: (event) => patch({ maxTokens: parseMaxTokens(event.target.value) }), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'max-tokens', role: 'field', label: t('Max tokens (optional)') }) }) }), _jsx("span", { className: "jini-field-hint", children: t('Cap on the response length. Leave blank to use the model default.') })] })] }) }));
53
+ }
54
+ /** The API-key field (with Show/Hide, host actions and the advisory format warning) plus the
55
+ * host's `apiKeyFooter` slot beneath it. */
56
+ function ByokApiKeyField({ config, patch, preset, agentHandle, validation, presets, revealKey, onToggleReveal, apiKeyFooter, apiKeyPlaceholder, apiKeyActions, }) {
57
+ const t = useT();
58
+ const keyInputId = useId();
59
+ const KeyField = apiKeyActions ? 'div' : 'label';
40
60
  /** Deliberately NOT folded into `missing` above: that set drives `is-missing` styling and the
41
61
  * Test-connection disable, and a wrong-SHAPED key must never reach either. */
42
62
  const keyFormatWarning = apiKeyFormatWarning(config, preset, presets);
43
- const suggestions = modelDiscovery.status === 'ok' ? modelDiscovery.models : (preset?.preferredModels ?? []);
44
- const modelListId = 'jini-byok-model-options';
63
+ return (_jsxs(_Fragment, { children: [_jsxs(KeyField, { className: "jini-field", onBlurCapture: () => validation.markTouched({ field: 'apiKey' }), children: [_jsxs("span", { className: "jini-field-label", children: [_jsx(ByokApiKeyLabel, { keyInputId: apiKeyActions ? keyInputId : undefined }), preset?.apiKeyConsoleUrl ? (_jsx("a", { className: "jini-field-link", href: preset.apiKeyConsoleUrl, target: "_blank", rel: "noreferrer noopener", children: t('Get key') })) : null] }), _jsxs("span", { className: "jini-field-input-row", children: [_jsx("input", { ...(apiKeyActions ? { id: keyInputId } : {}), className: 'jini-input' + missingClass(validation, 'apiKey'), type: revealKey ? 'text' : 'password',
64
+ /* `new-password`, NOT `"off"` — Chrome deliberately ignores `off` on credential-shaped
65
+ fields (a long-standing intentional decision, not a bug). `off` here is what let the
66
+ reported autofill through: Chrome filled this field with the admin's own saved
67
+ PASSWORD, the form sent it, and the provider answered with its own "API key not
68
+ valid" — a true message about a string the operator never typed. `new-password` is
69
+ the value Chrome/Safari/Firefox actually honor for "this is not a saved-login
70
+ field", which suppresses both the credential dropdown and the silent fill.
71
+
72
+ Known trade-off, not fixed here: Chrome may now offer to GENERATE a password on
73
+ this field — a suggestion popup, not a silently wrong value. */
74
+ autoComplete: "new-password", spellCheck: false, placeholder: apiKeyPlaceholder, value: config.apiKey, onChange: (event) => patch({ apiKey: event.target.value }), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'api-key', role: 'field', label: t('API key') }) }) }), _jsx("button", { type: "button", className: "jini-input-affix-btn", "aria-pressed": revealKey, onClick: onToggleReveal, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'reveal-key', role: 'button', label: t('Reveal API key') }) }), children: revealKey ? t('Hide') : t('Show') })] }), apiKeyActions ? _jsx("span", { className: "jini-byok-key-actions", children: apiKeyActions }) : null, _jsx("span", { className: "jini-field-hint", children: t('Stored only by this host.') }), keyFormatWarning ? (_jsx("span", { className: "jini-field-hint is-error", role: "status", children: t(keyFormatWarning.message, keyFormatWarning.vars) })) : null] }), _jsx(ByokSlot, { className: "jini-byok-key-footer", children: apiKeyFooter })] }));
75
+ }
76
+ /** "API key *". With host key actions the field is a `<div>`, so the name gets its own `<label>`
77
+ * pointing at the input by id; otherwise the whole field is already the label. */
78
+ function ByokApiKeyLabel({ keyInputId }) {
79
+ const t = useT();
80
+ if (keyInputId) {
81
+ return (_jsxs("label", { className: "jini-field-label", htmlFor: keyInputId, children: [_jsx("span", { children: t('API key') }), _jsx("span", { className: "jini-field-required", "aria-hidden": "true", children: "*" })] }));
82
+ }
83
+ return (_jsxs(_Fragment, { children: [t('API key'), _jsx("span", { className: "jini-field-required", "aria-hidden": "true", children: "*" })] }));
84
+ }
85
+ /** The Model field (live picker, free-text box, or both) with "Test connection" beside it. */
86
+ function ByokModelRow({ config, patch, preset, agentHandle, validation, modelDiscovery, explicitCustomModel, setExplicitCustomModel, connectionTest, onTestConnection, canTestConnection, }) {
87
+ const t = useT();
45
88
  /**
46
89
  * Live discovery turns the Model field from a text box into a real picker.
47
90
  *
@@ -66,41 +109,95 @@ export function ByokProviderForm({ config, onConfigChange, preset, presets = DEF
66
109
  // opens the box for an explicit "Custom…" pick AND for a value that simply isn't in the list, so
67
110
  // a hydrated or hand-typed id is never silently replaced by whatever sorts first.
68
111
  const customModelActive = showModelPicker && shouldShowCustomModelInput(config.model, liveModels, explicitCustomModel);
69
- const patch = (next) => onConfigChange({ ...config, ...next });
70
- const connectionFields = (_jsx(_Fragment, { children: _jsxs("div", { className: "jini-byok-field-row", children: [showsBaseUrlField(preset) ? (_jsxs("label", { className: "jini-field", onBlurCapture: () => markTouched({ field: 'baseUrl' }), children: [_jsxs("span", { className: "jini-field-label", children: [t('Base URL'), preset?.baseUrl ? null : _jsx("span", { className: "jini-field-required", "aria-hidden": "true", children: "*" })] }), _jsx("input", { className: 'jini-input' + (isMissing({ field: 'baseUrl' }) ? ' is-missing' : ''), type: "url", inputMode: "url", spellCheck: false, value: config.baseUrl, onChange: (event) => patch({ baseUrl: event.target.value }), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'base-url', role: 'field', label: t('Base URL') }) }) }), _jsx("span", { className: 'jini-field-hint' + (baseUrlInvalid ? ' is-error' : ''), children: baseUrlInvalid
71
- ? t('Enter an absolute http(s) URL.')
72
- : t('Default endpoint. Usually no need to change this.') })] })) : null, _jsxs("label", { className: "jini-field", children: [_jsx("span", { className: "jini-field-label", children: t('Max tokens (optional)') }), _jsx("input", { className: "jini-input", type: "number", min: 1, step: 1, value: config.maxTokens ?? '', onChange: (event) => patch({ maxTokens: parseMaxTokens(event.target.value) }), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'max-tokens', role: 'field', label: t('Max tokens (optional)') }) }) }), _jsx("span", { className: "jini-field-hint", children: t('Cap on the response length. Leave blank to use the model default.') })] })] }) }));
73
- return (_jsxs("div", { className: "jini-byok-card", children: [_jsx("div", { className: "jini-byok-card-head", children: _jsx("h4", { className: "jini-byok-card-title", children: preset ? preset.title : t('Custom endpoint') }) }), presetRequiresApiKey(preset) ? (_jsxs(_Fragment, { children: [_jsxs(KeyField, { className: "jini-field", onBlurCapture: () => markTouched({ field: 'apiKey' }), children: [_jsxs("span", { className: "jini-field-label", children: [apiKeyActions ? (_jsxs("label", { className: "jini-field-label", htmlFor: keyInputId, children: [_jsx("span", { children: t('API key') }), _jsx("span", { className: "jini-field-required", "aria-hidden": "true", children: "*" })] })) : (_jsxs(_Fragment, { children: [t('API key'), _jsx("span", { className: "jini-field-required", "aria-hidden": "true", children: "*" })] })), preset?.apiKeyConsoleUrl ? (_jsx("a", { className: "jini-field-link", href: preset.apiKeyConsoleUrl, target: "_blank", rel: "noreferrer noopener", children: t('Get key') })) : null] }), _jsxs("span", { className: "jini-field-input-row", children: [_jsx("input", { ...(apiKeyActions ? { id: keyInputId } : {}), className: 'jini-input' + (isMissing({ field: 'apiKey' }) ? ' is-missing' : ''), type: revealKey ? 'text' : 'password',
74
- /* `new-password`, NOT `"off"` — Chrome deliberately ignores `off` on credential-shaped
75
- fields (a long-standing intentional decision, not a bug). `off` here is what let the
76
- reported autofill through: Chrome filled this field with the admin's own saved
77
- PASSWORD, the form sent it, and the provider answered with its own "API key not
78
- valid" — a true message about a string the operator never typed. `new-password` is
79
- the value Chrome/Safari/Firefox actually honor for "this is not a saved-login
80
- field", which suppresses both the credential dropdown and the silent fill.
81
-
82
- Known trade-off, not fixed here: Chrome may now offer to GENERATE a password on
83
- this field — a suggestion popup, not a silently wrong value. */
84
- autoComplete: "new-password", spellCheck: false, placeholder: apiKeyPlaceholder, value: config.apiKey, onChange: (event) => patch({ apiKey: event.target.value }), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'api-key', role: 'field', label: t('API key') }) }) }), _jsx("button", { type: "button", className: "jini-input-affix-btn", "aria-pressed": revealKey, onClick: () => setRevealKey((shown) => !shown), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'reveal-key', role: 'button', label: t('Reveal API key') }) }), children: revealKey ? t('Hide') : t('Show') })] }), apiKeyActions ? _jsx("span", { className: "jini-byok-key-actions", children: apiKeyActions }) : null, _jsx("span", { className: "jini-field-hint", children: t('Stored only by this host.') }), keyFormatWarning ? (_jsx("span", { className: "jini-field-hint is-error", role: "status", children: t(keyFormatWarning.message, keyFormatWarning.vars) })) : null] }), apiKeyFooter ? _jsx("div", { className: "jini-byok-key-footer", children: apiKeyFooter }) : null] })) : null, collapseAdvanced ? null : connectionFields, _jsxs("div", { className: "jini-byok-model-row", children: [_jsxs("label", { className: "jini-field", onBlurCapture: () => markTouched({ field: 'model' }), children: [_jsxs("span", { className: "jini-field-label", children: [t('Model'), _jsx("span", { className: "jini-field-required", "aria-hidden": "true", children: "*" })] }), showModelPicker ? (_jsx(SearchableModelSelect, { className: 'jini-input' + (isMissing({ field: 'model' }) ? ' is-missing' : ''), ariaLabel: t('Model'), searchPlaceholder: t('Search models'), testId: "jini-byok-model-select", searchInputTestId: "jini-byok-model-search", ...(agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'model' }) } : {}), value: customModelActive ? CUSTOM_MODEL_SENTINEL : config.model, models: liveModels.map((model) => ({ id: model, label: model })), additionalOptions: [{ value: CUSTOM_MODEL_SENTINEL, label: t('Custom…') }], onChange: (next) => {
85
- if (next === CUSTOM_MODEL_SENTINEL) {
86
- // Open the free-text box WITHOUT clearing `config.model`. Blanking it here would
87
- // discard a working model the moment someone opened the picker to look at it.
88
- setExplicitCustomModel(true);
89
- return;
90
- }
91
- setExplicitCustomModel(false);
92
- patch({ model: next });
93
- } })) : null, !showModelPicker || customModelActive ? (_jsx("input", { className: 'jini-input' + (isMissing({ field: 'model' }) ? ' is-missing' : ''), list: !showModelPicker && suggestions.length > 0 ? modelListId : undefined, spellCheck: false, value: config.model, onChange: (event) => patch({ model: event.target.value }), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'model', role: 'field', label: t('Model') }) }) })) : null, !showModelPicker && suggestions.length > 0 ? (_jsx("datalist", { id: modelListId, children: suggestions.map((model) => (_jsx("option", { value: model }, model))) })) : null] }), canTestConnection ? (_jsx("button", { type: "button", className: "jini-btn jini-byok-test-btn", disabled: connectionTest.status === 'testing' || missing.size > 0, onClick: onTestConnection, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'test-connection', role: 'button', label: t('Test connection') }) }), children: connectionTest.status === 'testing' ? t('Testing…') : t('Test connection') })) : null] }), modelDiscovery.status === 'error' ? (
94
- // Non-blocking (the field above stays editable, with the preset's static
95
- // suggestions) but never silent — a discovery failure is an operator-actionable
96
- // fact (bad key, wrong base URL, unreachable endpoint), not "no models exist".
97
- //
98
- // Moved out of the Model `<label>` when the probe button moved up beside it: as a label
99
- // child it grew the field and pushed the bottom-aligned button off the input's line every
100
- // time discovery failed. Same element, same `.jini-field-hint.is-error[role="status"]`
101
- // identity host suites locate it by — only its parent changed.
102
- _jsx("span", { className: "jini-field-hint is-error", role: "status", children: t('Could not load live models: {message}', { message: t(modelDiscovery.message) }) })) : null, canTestConnection && (connectionTest.status === 'ok' || connectionTest.status === 'error') ? (_jsx("div", { className: "jini-byok-test-row", children: _jsx("span", { className: `jini-byok-test-status is-${connectionTest.status}`, role: connectionTest.status === 'error' ? 'alert' : 'status', children: connectionTest.status === 'ok'
103
- ? (connectionTest.message !== undefined ? t(connectionTest.message) : t('Connection succeeded'))
104
- : t(connectionTest.message) }) })) : null, modelFooter ? _jsx("div", { className: "jini-byok-model-footer", children: modelFooter }) : null, collapseAdvanced ? (_jsxs("details", { className: "jini-byok-advanced", open: baseUrlInvalid || missing.has('baseUrl') || undefined, children: [_jsx("summary", { children: t('Advanced') }), connectionFields] })) : null, formFooter ? _jsx("div", { className: "jini-byok-form-footer", children: formFooter }) : null] }));
112
+ const modelClassName = 'jini-input' + missingClass(validation, 'model');
113
+ return (
114
+ // "Test connection" sits BESIDE the Model control (owner ruling, 2026-09-02), not on a row of
115
+ // its own underneath it. It probes the endpoint the Model field belongs to, and a row's
116
+ // distance made it read as unrelated to any particular field.
117
+ //
118
+ // A SIBLING of the label, never a child: a `<button>` inside a `<label>` makes every click on
119
+ // the button also focus and activate the labelled control — here, opening the model picker —
120
+ // and nesting interactive controls in a label is an accessibility defect regardless. Same rule
121
+ // `apiKeyFooter` documents.
122
+ //
123
+ // Wrapping, and `align-items: flex-end`, so the button sits on the input's own line at a
124
+ // comfortable width and drops below the field at a narrow one. Both status lines
125
+ // (discovery error, connection result) are deliberately OUTSIDE this row: inside it they would
126
+ // either sit beside the button or, as label children, push it out of line the moment either
127
+ // appeared.
128
+ _jsxs("div", { className: "jini-byok-model-row", children: [_jsxs("label", { className: "jini-field", onBlurCapture: () => validation.markTouched({ field: 'model' }), children: [_jsxs("span", { className: "jini-field-label", children: [t('Model'), _jsx("span", { className: "jini-field-required", "aria-hidden": "true", children: "*" })] }), showModelPicker ? (_jsx(ByokLiveModelSelect, { className: modelClassName, agentHandle: agentHandle, value: customModelActive ? CUSTOM_MODEL_SENTINEL : config.model, liveModels: liveModels, patch: patch, setExplicitCustomModel: setExplicitCustomModel })) : null, !showModelPicker || customModelActive ? (_jsx(ByokModelTextInput, { className: modelClassName, value: config.model, patch: patch, agentHandle: agentHandle, suggestions: showModelPicker ? [] : modelSuggestions(modelDiscovery, preset) })) : null] }), _jsx(ByokTestConnectionButton, { agentHandle: agentHandle, validation: validation, connectionTest: connectionTest, onTestConnection: onTestConnection, canTestConnection: canTestConnection })] }));
129
+ }
130
+ /** The probe's RESULT keeps its own full-width line. Beside the button it would have made the
131
+ * Model row's width depend on the length of a provider's error message. Renders nothing until
132
+ * the probe has finished. */
133
+ function ByokConnectionResult({ connectionTest, canTestConnection }) {
134
+ const t = useT();
135
+ if (!canTestConnection)
136
+ return null;
137
+ if (connectionTest.status !== 'ok' && connectionTest.status !== 'error')
138
+ return null;
139
+ return (_jsx("div", { className: "jini-byok-test-row", children: _jsx("span", { className: `jini-byok-test-status is-${connectionTest.status}`, role: connectionTest.status === 'error' ? 'alert' : 'status', children: connectionTest.status === 'ok'
140
+ ? (connectionTest.message !== undefined ? t(connectionTest.message) : t('Connection succeeded'))
141
+ : t(connectionTest.message) }) }));
142
+ }
143
+ /** Discovery failure, as an inline hint below the Model row. Renders nothing otherwise. */
144
+ function ByokDiscoveryError({ modelDiscovery }) {
145
+ const t = useT();
146
+ if (modelDiscovery.status !== 'error')
147
+ return null;
148
+ return (
149
+ // Non-blocking (the field above stays editable, with the preset's static
150
+ // suggestions) but never silent — a discovery failure is an operator-actionable
151
+ // fact (bad key, wrong base URL, unreachable endpoint), not "no models exist".
152
+ //
153
+ // Moved out of the Model `<label>` when the probe button moved up beside it: as a label
154
+ // child it grew the field and pushed the bottom-aligned button off the input's line every
155
+ // time discovery failed. Same element, same `.jini-field-hint.is-error[role="status"]`
156
+ // identity host suites locate it by — only its parent changed.
157
+ _jsx("span", { className: "jini-field-hint is-error", role: "status", children: t('Could not load live models: {message}', { message: t(modelDiscovery.message) }) }));
158
+ }
159
+ /** A host slot's wrapper `<div>`, rendered only when the host supplied content for it. */
160
+ function ByokSlot({ className, children }) {
161
+ return children ? _jsx("div", { className: className, children: children }) : null;
162
+ }
163
+ /** The collapsed "Advanced" disclosure for Base URL and Max tokens. Opens on its own while Base URL
164
+ * is invalid or missing, so a required-field problem is never hidden. */
165
+ function ByokAdvanced({ validation, children }) {
166
+ const t = useT();
167
+ return (_jsxs("details", { className: "jini-byok-advanced", open: validation.baseUrlInvalid || validation.missing.has('baseUrl') || undefined, children: [_jsx("summary", { children: t('Advanced') }), children] }));
168
+ }
169
+ /** The live-model picker. "Custom…" opens the free-text box beside it; any real pick closes it. */
170
+ function ByokLiveModelSelect({ className, agentHandle, value, liveModels, patch, setExplicitCustomModel, }) {
171
+ const t = useT();
172
+ return (_jsx(SearchableModelSelect, { className: className, ariaLabel: t('Model'), searchPlaceholder: t('Search models'), testId: "jini-byok-model-select", searchInputTestId: "jini-byok-model-search", ...(agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'model' }) } : {}), value: value, models: liveModels.map((model) => ({ id: model, label: model })), additionalOptions: [{ value: CUSTOM_MODEL_SENTINEL, label: t('Custom…') }], onChange: (next) => {
173
+ if (next === CUSTOM_MODEL_SENTINEL) {
174
+ // Open the free-text box WITHOUT clearing `config.model`. Blanking it here would
175
+ // discard a working model the moment someone opened the picker to look at it.
176
+ setExplicitCustomModel(true);
177
+ return;
178
+ }
179
+ setExplicitCustomModel(false);
180
+ patch({ model: next });
181
+ } }));
182
+ }
183
+ /** "Test connection", beside the Model field. Disabled while a probe runs or a required field is
184
+ * blank; absent for hosts with no probe endpoint. */
185
+ function ByokTestConnectionButton({ agentHandle, validation, connectionTest, onTestConnection, canTestConnection, }) {
186
+ const t = useT();
187
+ if (!canTestConnection)
188
+ return null;
189
+ return (_jsx("button", { type: "button", className: "jini-btn jini-byok-test-btn", disabled: connectionTest.status === 'testing' || validation.missing.size > 0, onClick: onTestConnection, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'test-connection', role: 'button', label: t('Test connection') }) }), children: connectionTest.status === 'testing' ? t('Testing…') : t('Test connection') }));
190
+ }
191
+ /** Autocomplete hints for the free-text Model box: the live list when discovery succeeded, else the
192
+ * preset's static `preferredModels`. */
193
+ function modelSuggestions(modelDiscovery, preset) {
194
+ return modelDiscovery.status === 'ok' ? modelDiscovery.models : (preset?.preferredModels ?? []);
195
+ }
196
+ /** The free-text Model box, with a `<datalist>` autocomplete when there are suggestions. */
197
+ function ByokModelTextInput({ className, value, patch, agentHandle, suggestions, }) {
198
+ const t = useT();
199
+ const modelListId = 'jini-byok-model-options';
200
+ const showDatalist = suggestions.length > 0;
201
+ return (_jsxs(_Fragment, { children: [_jsx("input", { className: className, list: showDatalist ? modelListId : undefined, spellCheck: false, value: value, onChange: (event) => patch({ model: event.target.value }), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'model', role: 'field', label: t('Model') }) }) }), showDatalist ? (_jsx("datalist", { id: modelListId, children: suggestions.map((model) => (_jsx("option", { value: model }, model))) })) : null] }));
105
202
  }
106
203
  //# sourceMappingURL=ByokProviderForm.js.map
@@ -5,8 +5,7 @@ export interface ExecutionTabProps {
5
5
  config: ExecutionConfig;
6
6
  onConfigChange: (config: ExecutionConfig) => void;
7
7
  port: ExecutionPort;
8
- /** Endpoint catalog. Defaults to this package's small starter set — a host
9
- * with its own provider list passes it here rather than editing ours. */
8
+ /** Host-owned endpoint catalog. Omitted means manual endpoint entry. */
10
9
  presets?: readonly ProviderPreset[];
11
10
  /** Per-agent CLI env-var catalog (proxy URLs, custom config dirs, a
12
11
  * binary-path override). Defaults to this package's starter set for the
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect } from 'react';
3
3
  import { agentHandleProps, agentSubHandle } from '@jini-ai/agentic';
4
4
  import { useT } from '../../../i18n/index.js';
5
- import { DEFAULT_AGENT_CLI_ENV_FIELDS, DEFAULT_PROVIDER_PRESETS } from '../../constants.js';
5
+ import { DEFAULT_AGENT_CLI_ENV_FIELDS } from '../../constants.js';
6
6
  import { groupPresets, isProviderConfigured, nextConfigForAgentCliEnvChange, nextConfigForAgentModel, nextConfigForAgentReasoning, nextConfigForAgentSelect, nextConfigForModeChange, nextConfigForPresetSelect, resolveSelectedPreset, selectedAgentModel, } from '../../rules.js';
7
7
  import { useExecutionTab } from '../hooks/useExecutionTab.js';
8
8
  import { ByokProviderForm } from './ByokProviderForm.js';
@@ -20,13 +20,14 @@ import { ProviderChipGroup } from './ProviderChipGroup.js';
20
20
  * wallet — balance polling, top-up, sign-in coachmarks — is deliberately not
21
21
  * ported; it is product-bound, not generic.
22
22
  */
23
- export function ExecutionTab({ config, onConfigChange, port, presets = DEFAULT_PROVIDER_PRESETS, cliEnvFields = DEFAULT_AGENT_CLI_ENV_FIELDS, localCliUnavailableReason, renderAgentIcon, localCliScopeLabel, autoDetect = true, ariaLabel, apiKeyFooter, formFooter, apiKeyStoredExternally, apiKeyPlaceholder, canDiscoverModels = true, describeProbeError, agentHandle, }) {
23
+ export function ExecutionTab({ config, onConfigChange, port, presets = [], cliEnvFields = DEFAULT_AGENT_CLI_ENV_FIELDS, localCliUnavailableReason, renderAgentIcon, localCliScopeLabel, autoDetect = true, ariaLabel, apiKeyFooter, formFooter, apiKeyStoredExternally, apiKeyPlaceholder, canDiscoverModels = true, describeProbeError, agentHandle, }) {
24
24
  const t = useT();
25
- const { agents, scan, connectionTest, modelDiscovery, agentTest, rescan, testConnection, testAgent, loadModels, resetModelDiscovery, canRescan, canTestAgent, } = useExecutionTab({
25
+ const tab = useExecutionTab({
26
26
  port,
27
27
  autoDetect: autoDetect && config.mode === 'local-cli',
28
28
  ...(describeProbeError ? { describeProbeError } : {}),
29
29
  });
30
+ const { loadModels, resetModelDiscovery } = tab;
30
31
  const selectedPreset = resolveSelectedPreset(presets, config.byok);
31
32
  /**
32
33
  * Whether a key EXISTS, never what it is. Keyed on the boolean specifically so the effect below
@@ -68,39 +69,60 @@ export function ExecutionTab({ config, onConfigChange, port, presets = DEFAULT_P
68
69
  if (!canDiscoverModels)
69
70
  resetModelDiscovery();
70
71
  }, [canDiscoverModels, resetModelDiscovery]);
71
- const { protocols, gateways } = groupPresets(presets);
72
- const configuredPresetIds = new Set(presets.filter((preset) => isProviderConfigured(config.byok, preset)).map((preset) => preset.id));
73
- const localCliDisabled = Boolean(localCliUnavailableReason);
72
+ // No "local-cli while unavailable" guard here: `ExecutionModeSwitch` renders that tab `disabled`
73
+ // whenever `localCliUnavailableReason` is set, and React never dispatches `onClick` for a disabled
74
+ // button, so `setMode('local-cli')` cannot be reached in that state.
74
75
  const setMode = (mode) => {
75
- if (mode === 'local-cli' && localCliDisabled)
76
- return;
77
76
  onConfigChange(nextConfigForModeChange(config, mode));
78
77
  };
79
- const selectPreset = (preset) => onConfigChange({ ...config, byok: nextConfigForPresetSelect(config.byok, preset) });
80
78
  const modes = [
81
79
  { id: 'local-cli', title: t('Local CLI'), meta: t('Run an agent installed on this machine') },
82
80
  { id: 'byok', title: t('BYOK'), meta: t('Use your own API credentials') },
83
81
  ];
84
- return (_jsxs("div", { className: "jini-settings-execution", "aria-label": ariaLabel ?? t('Execution mode'), children: [_jsx("div", { className: "jini-seg-control", role: "tablist", "aria-label": t('Execution mode'), style: { '--seg-cols': modes.length }, children: modes.map((mode) => {
85
- const disabled = mode.id === 'local-cli' && localCliDisabled;
86
- return (_jsxs("button", { type: "button", role: "tab", "aria-selected": config.mode === mode.id, disabled: disabled, title: disabled ? localCliUnavailableReason : undefined, className: 'jini-seg-btn' + (config.mode === mode.id ? ' active' : ''), onClick: () => setMode(mode.id), ...agentHandleProps({}, { base: agentHandle, ...({ action: `mode-${mode.id}`, role: 'button', label: mode.title }) }), children: [_jsx("span", { className: "jini-seg-title", children: mode.title }), _jsx("span", { className: "jini-seg-meta", children: disabled ? localCliUnavailableReason : mode.meta })] }, mode.id));
87
- }) }), config.mode === 'local-cli' ? (_jsx(LocalCliAgentList, { agents: agents, config: config.localCli, scan: scan, agentTest: agentTest, onSelect: (agentId) => onConfigChange(nextConfigForAgentSelect(config, agentId)), onModelChange: (agentId, model) => onConfigChange(nextConfigForAgentModel(config, agentId, model)), onReasoningChange: (agentId, reasoning) => onConfigChange(nextConfigForAgentReasoning(config, agentId, reasoning)), onEnvChange: (agentId, envKey, value) => onConfigChange(nextConfigForAgentCliEnvChange(config, agentId, envKey, value)), cliEnvFields: cliEnvFields, onRescan: canRescan ? rescan : undefined, onTest: canTestAgent
88
- ? (agent) => testAgent(agent.id, selectedAgentModel(config.localCli, agent) || undefined)
89
- : undefined, renderAgentIcon: renderAgentIcon, scopeLabel: localCliScopeLabel, ...(agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'local-cli' }) } : {}) })) : (_jsxs("section", { className: "jini-settings-section jini-settings-byok", children: [_jsx(ProviderChipGroup, { label: t('Protocols'), presets: protocols, selectedPresetId: selectedPreset?.id ?? null, configuredPresetIds: configuredPresetIds, onSelect: selectPreset, configuredLabel: t('Configured'), unsetLabel: t('Not configured'), ...(agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'protocol' }) } : {}) }), _jsx(ProviderChipGroup, { label: t('Gateways'), presets: gateways, selectedPresetId: selectedPreset?.id ?? null, configuredPresetIds: configuredPresetIds, onSelect: selectPreset, configuredLabel: t('Configured'), unsetLabel: t('Not configured'), ...(agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'gateway' }) } : {}) }), _jsx(ByokProviderForm, { config: config.byok, onConfigChange: (byok) => onConfigChange({ ...config, byok }), preset: selectedPreset, ...(agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'byok' }) } : {}),
90
- // The WHOLE catalog, not the `protocols`/`gateways` halves the chip rows take: the card
91
- // only reads it to recognise a pasted key as some other row's, and a key pasted from a
92
- // gateway into a protocol field is exactly the mistake worth naming.
93
- presets: presets, modelDiscovery: modelDiscovery, connectionTest: connectionTest,
94
- // Also re-runs discovery with the CURRENT byok config, not just the
95
- // connectivity probe — see this file's model-discovery effect above
96
- // for why that matters: a stale "could not load live models" error
97
- // from before this key existed (or from any earlier transient
98
- // failure) would otherwise never clear, even once Test Connection
99
- // itself goes green with the same config.
100
- onTestConnection: () => {
101
- testConnection(config.byok);
102
- if (canDiscoverModels && typeof port.listModels === 'function')
103
- loadModels(config.byok);
104
- }, ...(apiKeyFooter !== undefined ? { apiKeyFooter } : {}), ...(formFooter !== undefined ? { formFooter } : {}), ...(apiKeyStoredExternally !== undefined ? { apiKeyStoredExternally } : {}), ...(apiKeyPlaceholder !== undefined ? { apiKeyPlaceholder } : {}) })] }))] }));
82
+ return (_jsxs("div", { className: "jini-settings-execution", "aria-label": ariaLabel ?? t('Execution mode'), children: [_jsx(ExecutionModeSwitch, { modes: modes, activeMode: config.mode, localCliUnavailableReason: localCliUnavailableReason, onSelect: setMode, agentHandle: agentHandle }), config.mode === 'local-cli' ? (_jsx(LocalCliPane, { config: config, onConfigChange: onConfigChange, tab: tab, cliEnvFields: cliEnvFields, renderAgentIcon: renderAgentIcon, localCliScopeLabel: localCliScopeLabel, agentHandle: agentHandle })) : (_jsx(ByokPane, { config: config, onConfigChange: onConfigChange, presets: presets, selectedPreset: selectedPreset, tab: tab, canDiscoverModels: canDiscoverModels, canListModels: typeof port.listModels === 'function', formProps: { apiKeyFooter, formFooter, apiKeyStoredExternally, apiKeyPlaceholder }, agentHandle: agentHandle }))] }));
83
+ }
84
+ /** The Local CLI ↔ BYOK segmented control. Local CLI is disabled, with the host's reason as its
85
+ * tooltip and caption, while the host says it is unavailable. */
86
+ function ExecutionModeSwitch({ modes, activeMode, localCliUnavailableReason, onSelect, agentHandle }) {
87
+ const t = useT();
88
+ const localCliDisabled = Boolean(localCliUnavailableReason);
89
+ return (_jsx("div", { className: "jini-seg-control", role: "tablist", "aria-label": t('Execution mode'), style: { '--seg-cols': modes.length }, children: modes.map((mode) => {
90
+ const disabled = mode.id === 'local-cli' && localCliDisabled;
91
+ return (_jsxs("button", { type: "button", role: "tab", "aria-selected": activeMode === mode.id, disabled: disabled, title: disabled ? localCliUnavailableReason : undefined, className: 'jini-seg-btn' + (activeMode === mode.id ? ' active' : ''), onClick: () => onSelect(mode.id), ...agentHandleProps({}, { base: agentHandle, ...({ action: `mode-${mode.id}`, role: 'button', label: mode.title }) }), children: [_jsx("span", { className: "jini-seg-title", children: mode.title }), _jsx("span", { className: "jini-seg-meta", children: disabled ? localCliUnavailableReason : mode.meta })] }, mode.id));
92
+ }) }));
93
+ }
94
+ /** The BYOK half of the tab: protocol and gateway chip rows over the credential card. */
95
+ function ByokPane({ config, onConfigChange, presets, selectedPreset, tab, canDiscoverModels, canListModels, formProps, agentHandle, }) {
96
+ const t = useT();
97
+ const { protocols, gateways } = groupPresets(presets);
98
+ const configuredPresetIds = new Set(presets.filter((preset) => isProviderConfigured(config.byok, preset)).map((preset) => preset.id));
99
+ const selectPreset = (preset) => onConfigChange({ ...config, byok: nextConfigForPresetSelect(config.byok, preset) });
100
+ const selectedPresetId = selectedPreset?.id ?? null;
101
+ return (_jsxs("section", { className: "jini-settings-section jini-settings-byok", children: [_jsx(ProviderChipGroup, { label: t('Protocols'), presets: protocols, selectedPresetId: selectedPresetId, configuredPresetIds: configuredPresetIds, onSelect: selectPreset, configuredLabel: t('Configured'), unsetLabel: t('Not configured'), ...subHandleProps(agentHandle, 'protocol') }), _jsx(ProviderChipGroup, { label: t('Gateways'), presets: gateways, selectedPresetId: selectedPresetId, configuredPresetIds: configuredPresetIds, onSelect: selectPreset, configuredLabel: t('Configured'), unsetLabel: t('Not configured'), ...subHandleProps(agentHandle, 'gateway') }), _jsx(ByokProviderForm, { config: config.byok, onConfigChange: (byok) => onConfigChange({ ...config, byok }), preset: selectedPreset, ...subHandleProps(agentHandle, 'byok'),
102
+ // The WHOLE catalog, not the `protocols`/`gateways` halves the chip rows take: the card
103
+ // only reads it to recognise a pasted key as some other row's, and a key pasted from a
104
+ // gateway into a protocol field is exactly the mistake worth naming.
105
+ presets: presets, modelDiscovery: tab.modelDiscovery, connectionTest: tab.connectionTest,
106
+ // Also re-runs discovery with the CURRENT byok config, not just the
107
+ // connectivity probe — see this file's model-discovery effect above
108
+ // for why that matters: a stale "could not load live models" error
109
+ // from before this key existed (or from any earlier transient
110
+ // failure) would otherwise never clear, even once Test Connection
111
+ // itself goes green with the same config.
112
+ onTestConnection: () => {
113
+ tab.testConnection(config.byok);
114
+ if (canDiscoverModels && canListModels)
115
+ tab.loadModels(config.byok);
116
+ }, ...(formProps.apiKeyFooter !== undefined ? { apiKeyFooter: formProps.apiKeyFooter } : {}), ...(formProps.formFooter !== undefined ? { formFooter: formProps.formFooter } : {}), ...(formProps.apiKeyStoredExternally !== undefined ? { apiKeyStoredExternally: formProps.apiKeyStoredExternally } : {}), ...(formProps.apiKeyPlaceholder !== undefined ? { apiKeyPlaceholder: formProps.apiKeyPlaceholder } : {}) })] }));
117
+ }
118
+ /** The Local CLI half of the tab: detected agents, wired to the config's next-state rules. */
119
+ function LocalCliPane({ config, onConfigChange, tab, cliEnvFields, renderAgentIcon, localCliScopeLabel, agentHandle }) {
120
+ return (_jsx(LocalCliAgentList, { agents: tab.agents, config: config.localCli, scan: tab.scan, agentTest: tab.agentTest, onSelect: (agentId) => onConfigChange(nextConfigForAgentSelect(config, agentId)), onModelChange: (agentId, model) => onConfigChange(nextConfigForAgentModel(config, agentId, model)), onReasoningChange: (agentId, reasoning) => onConfigChange(nextConfigForAgentReasoning(config, agentId, reasoning)), onEnvChange: (agentId, envKey, value) => onConfigChange(nextConfigForAgentCliEnvChange(config, agentId, envKey, value)), cliEnvFields: cliEnvFields, onRescan: tab.canRescan ? tab.rescan : undefined, onTest: tab.canTestAgent
121
+ ? (agent) => tab.testAgent(agent.id, selectedAgentModel(config.localCli, agent) || undefined)
122
+ : undefined, renderAgentIcon: renderAgentIcon, scopeLabel: localCliScopeLabel, ...subHandleProps(agentHandle, 'local-cli') }));
123
+ }
124
+ /** `{ agentHandle }` for a child's sub-handle under this tab's base, or nothing when no base is set. */
125
+ function subHandleProps(agentHandle, action) {
126
+ return agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action }) } : {};
105
127
  }
106
128
  //# sourceMappingURL=ExecutionTab.js.map
@@ -27,6 +27,17 @@ import { LocalCliAgentSettings } from './LocalCliAgentSettings.js';
27
27
  export function LocalCliAgentCard({ agent, config, selected, onSelect, onModelChange, onReasoningChange, onEnvChange, cliEnvFields, renderIcon, onTest, agentTest, onRescan, agentHandle, }) {
28
28
  const t = useT();
29
29
  const card = useLocalCliAgentCard({ agent, config, agentTest, cliEnvFields, onModelChange, onReasoningChange, onRescan, onTest, t });
30
+ return (_jsxs("div", { className: 'jini-agent-card' +
31
+ (selected ? ' is-selected' : '') +
32
+ (agent.installed ? ' is-installed' : ' is-missing'), "data-testid": `jini-agent-card-${agent.id}`, children: [_jsxs("div", { className: "jini-agent-card-main", children: [_jsx(LocalCliAgentSelection, { agent: agent, config: config, selected: selected, onSelect: onSelect, renderIcon: renderIcon, agentHandle: agentHandle }), selected && onTest ? (_jsx("button", { type: "button", className: 'jini-btn jini-agent-card-test' + (card.testing ? ' is-loading' : ''), "data-testid": `jini-agent-test-${agent.id}`, disabled: card.testing, onClick: () => onTest(agent), ...agentHandleProps({}, { base: agentHandle, action: 'test', role: 'button', label: t('Test') }), children: card.testing ? t('Testing…') : t('Test') })) : null] }), (agent.diagnostics ?? []).map((diagnostic, index) => (_jsx(AgentDiagnosticRow, { diagnostic: diagnostic, handlers: card.diagnosticHandlers }, `${diagnostic.reason}-${index}`))), selected && card.hasSettings ? (_jsx(LocalCliAgentSettings, { agent: agent, config: config, card: card, cliEnvFields: cliEnvFields, onModelChange: onModelChange, onEnvChange: onEnvChange, agentHandle: agentHandle })) : null] }));
33
+ }
34
+ /** The selectable identity stays independent of the header's Test action. */
35
+ function LocalCliAgentSelection({ agent, config, selected, onSelect, renderIcon, agentHandle }) {
36
+ return (_jsxs("button", { type: "button", className: "jini-agent-card-select", "data-testid": `jini-agent-select-${agent.id}`, "aria-pressed": selected, disabled: !agent.installed, onClick: () => onSelect(agent.id), ...agentHandleProps({}, { base: agentHandle, action: 'select', role: 'button', label: agent.label }), children: [_jsx("span", { className: "jini-agent-card-icon", "aria-hidden": "true", children: renderIcon ? renderIcon(agent) : agent.label.slice(0, 1).toUpperCase() }), _jsx(LocalCliAgentSummary, { agent: agent, config: config, selected: selected }), selected ? (_jsx("span", { className: "jini-agent-card-check", "aria-hidden": "true", children: "\u2713" })) : null] }));
37
+ }
38
+ /** Detection metadata and the compact model summary for the card's identity. */
39
+ function LocalCliAgentSummary({ agent, config, selected }) {
40
+ const t = useT();
30
41
  const description = agent.description ?? DEFAULT_AGENT_DESCRIPTIONS[agent.id];
31
42
  const meta = agentMetaLabel(agent, {
32
43
  authRequired: t('Authentication required'),
@@ -35,8 +46,6 @@ export function LocalCliAgentCard({ agent, config, selected, onSelect, onModelCh
35
46
  notInstalled: t('Not installed'),
36
47
  });
37
48
  const summary = agentModelSummary(config, agent);
38
- return (_jsxs("div", { className: 'jini-agent-card' +
39
- (selected ? ' is-selected' : '') +
40
- (agent.installed ? ' is-installed' : ' is-missing'), "data-testid": `jini-agent-card-${agent.id}`, children: [_jsxs("div", { className: "jini-agent-card-main", children: [_jsxs("button", { type: "button", className: "jini-agent-card-select", "data-testid": `jini-agent-select-${agent.id}`, "aria-pressed": selected, disabled: !agent.installed, onClick: () => onSelect(agent.id), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'select', role: 'button', label: agent.label }) }), children: [_jsx("span", { className: "jini-agent-card-icon", "aria-hidden": "true", children: renderIcon ? renderIcon(agent) : agent.label.slice(0, 1).toUpperCase() }), _jsxs("span", { className: "jini-agent-card-body", children: [_jsxs("span", { className: "jini-agent-card-name", children: [_jsx("span", { className: "jini-agent-card-title", children: agent.label }), description ? (_jsx("span", { className: "jini-agent-card-tagline", children: description })) : null] }), agent.badges?.length ? (_jsx("span", { className: "jini-agent-card-badges", children: agent.badges.map((badge) => (_jsx("span", { className: "jini-agent-card-badge", children: badge }, badge))) })) : null, meta.text ? (_jsx("span", { className: "jini-agent-card-meta", children: _jsx("span", { title: meta.title || undefined, children: meta.text }) })) : null, !selected && summary ? (_jsxs("span", { className: "jini-agent-card-model-summary", children: [_jsx("span", { children: t('Model') }), _jsx("strong", { children: summary })] })) : null] }), selected ? (_jsx("span", { className: "jini-agent-card-check", "aria-hidden": "true", children: "\u2713" })) : null] }), selected && onTest ? (_jsx("button", { type: "button", className: 'jini-btn jini-agent-card-test' + (card.testing ? ' is-loading' : ''), "data-testid": `jini-agent-test-${agent.id}`, disabled: card.testing, onClick: () => onTest(agent), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'test', role: 'button', label: t('Test') }) }), children: card.testing ? t('Testing…') : t('Test') })) : null] }), (agent.diagnostics ?? []).map((diagnostic, index) => (_jsx(AgentDiagnosticRow, { diagnostic: diagnostic, handlers: card.diagnosticHandlers }, `${diagnostic.reason}-${index}`))), selected && card.hasSettings ? (_jsx(LocalCliAgentSettings, { agent: agent, config: config, card: card, cliEnvFields: cliEnvFields, onModelChange: onModelChange, onEnvChange: onEnvChange, agentHandle: agentHandle })) : null] }));
49
+ return (_jsxs("span", { className: "jini-agent-card-body", children: [_jsxs("span", { className: "jini-agent-card-name", children: [_jsx("span", { className: "jini-agent-card-title", children: agent.label }), description ? (_jsx("span", { className: "jini-agent-card-tagline", children: description })) : null] }), agent.badges?.length ? (_jsx("span", { className: "jini-agent-card-badges", children: agent.badges.map((badge) => (_jsx("span", { className: "jini-agent-card-badge", children: badge }, badge))) })) : null, meta.text ? (_jsx("span", { className: "jini-agent-card-meta", children: _jsx("span", { title: meta.title || undefined, children: meta.text }) })) : null, !selected && summary ? (_jsxs("span", { className: "jini-agent-card-model-summary", children: [_jsx("span", { children: t('Model') }), _jsx("strong", { children: summary })] })) : null] }));
41
50
  }
42
51
  //# sourceMappingURL=LocalCliAgentCard.js.map
@@ -22,8 +22,19 @@ export function LocalCliAgentList({ agents, config, scan, onSelect, onModelChang
22
22
  const handle = cardHandles.get(agent.id);
23
23
  return (_jsx(LocalCliAgentCard, { agent: agent, config: config, selected: config.agentId === agent.id, onSelect: onSelect, onModelChange: onModelChange, onReasoningChange: onReasoningChange, onEnvChange: onEnvChange, cliEnvFields: cliEnvFields, renderIcon: renderAgentIcon, onTest: onTest, agentTest: agentTest, onRescan: onRescan, ...(handle ? { agentHandle: handle } : {}) }, agent.id));
24
24
  };
25
- return (_jsxs("section", { className: "jini-settings-section jini-local-cli", children: [_jsxs("div", { className: "jini-section-head", children: [_jsxs("div", { className: "jini-section-head-text", children: [_jsxs("h4", { className: "jini-agent-group-head", children: [t('Your CLIs'), " (", installed.length, ")"] }), _jsx("p", { className: "jini-field-hint", children: t('Pick the CLI that runs your prompts.') })] }), onRescan ? (_jsx("button", { type: "button", className: "jini-btn jini-local-cli-rescan", disabled: scan.status === 'scanning', onClick: onRescan, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'rescan', role: 'button', label: t('Rescan') }) }), children: scan.status === 'scanning' ? t('Scanning…') : t('↻ Rescan') })) : null] }), _jsx("p", { className: "jini-field-hint jini-local-cli-scope", children: scope }), scan.status === 'error' ? (_jsx("p", { className: "jini-field-hint is-error", role: "alert", children: scan.message })) : null, scan.status === 'scanning' ? (_jsxs("div", { className: "jini-agent-scan-card", role: "status", "aria-live": "polite", children: [_jsx("span", { className: "jini-agent-scan-ring", "aria-hidden": "true" }), _jsx("strong", { children: t('Scanning for installed agents…') })] })) : agents.length === 0 ? (scan.status === 'error' ? null : (_jsx("div", { className: "jini-empty-card", children: t('No agent CLIs detected.') }))) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "jini-agent-grid", children: list.visible.map(renderCard) }), list.collapsed.length > 0 ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "jini-btn jini-local-cli-more", "aria-expanded": list.showCollapsed, onClick: list.toggleCollapsed, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'toggle-not-installed', role: 'button', label: t('Show not-installed CLIs') }) }), children: list.showCollapsed
26
- ? t('Hide not installed')
27
- : t('Show {count} not installed').replace('{count}', String(list.collapsed.length)) }), list.showCollapsed ? _jsx("div", { className: "jini-agent-grid", children: list.collapsed.map(renderCard) }) : null] })) : null] }))] }));
25
+ return (_jsxs("section", { className: "jini-settings-section jini-local-cli", children: [_jsxs("div", { className: "jini-section-head", children: [_jsxs("div", { className: "jini-section-head-text", children: [_jsxs("h4", { className: "jini-agent-group-head", children: [t('Your CLIs'), " (", installed.length, ")"] }), _jsx("p", { className: "jini-field-hint", children: t('Pick the CLI that runs your prompts.') })] }), onRescan ? (_jsx("button", { type: "button", className: "jini-btn jini-local-cli-rescan", disabled: scan.status === 'scanning', onClick: onRescan, ...agentHandleProps({}, { base: agentHandle, action: 'rescan', role: 'button', label: t('Rescan') }), children: scan.status === 'scanning' ? t('Scanning…') : t('↻ Rescan') })) : null] }), _jsx("p", { className: "jini-field-hint jini-local-cli-scope", children: scope }), scan.status === 'error' ? (_jsx("p", { className: "jini-field-hint is-error", role: "alert", children: scan.message })) : null, _jsx(LocalCliAgentGrid, { agents: agents, scan: scan, list: list, renderCard: renderCard, agentHandle: agentHandle })] }));
26
+ }
27
+ /** Scan-state precedence and the not-installed disclosure belong to one grid. */
28
+ function LocalCliAgentGrid({ agents, scan, list, renderCard, agentHandle }) {
29
+ const t = useT();
30
+ if (scan.status === 'scanning') {
31
+ return (_jsxs("div", { className: "jini-agent-scan-card", role: "status", "aria-live": "polite", children: [_jsx("span", { className: "jini-agent-scan-ring", "aria-hidden": "true" }), _jsx("strong", { children: t('Scanning for installed agents…') })] }));
32
+ }
33
+ if (agents.length === 0) {
34
+ return scan.status === 'error' ? null : _jsx("div", { className: "jini-empty-card", children: t('No agent CLIs detected.') });
35
+ }
36
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "jini-agent-grid", children: list.visible.map(renderCard) }), list.collapsed.length > 0 ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "jini-btn jini-local-cli-more", "aria-expanded": list.showCollapsed, onClick: () => list.toggleCollapsed(), ...agentHandleProps({}, { base: agentHandle, action: 'toggle-not-installed', role: 'button', label: t('Show not-installed CLIs') }), children: list.showCollapsed
37
+ ? t('Hide not installed')
38
+ : t('Show {count} not installed').replace('{count}', String(list.collapsed.length)) }), list.showCollapsed ? _jsx("div", { className: "jini-agent-grid", children: list.collapsed.map(renderCard) }) : null] })) : null] }));
28
39
  }
29
40
  //# sourceMappingURL=LocalCliAgentList.js.map
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { agentHandleProps, agentSubHandle } from '@jini-ai/agentic';
3
3
  import { useT } from '../../../i18n/index.js';
4
4
  import { CUSTOM_MODEL_SENTINEL } from '../../constants.js';
@@ -12,9 +12,20 @@ import { SearchableModelSelect } from './SearchableModelSelect.js';
12
12
  * visible provenance sentence inside the card so only that card grows taller.
13
13
  */
14
14
  export function LocalCliAgentSettings({ agent, config, card, cliEnvFields, onModelChange, onEnvChange, agentHandle, }) {
15
+ return (_jsxs("div", { className: "jini-agent-settings", role: "group", "aria-label": agent.label, "data-testid": `jini-agent-settings-${agent.id}`, children: [_jsx(LocalCliAgentModelControls, { agent: agent, card: card, onModelChange: onModelChange, agentHandle: agentHandle }), _jsx(LocalCliAgentFeedback, { agent: agent, card: card, onEnvChange: onEnvChange }), _jsx(AgentCliEnvFields, { agentId: agent.id, fields: cliEnvFields, config: config, onChange: onEnvChange })] }));
16
+ }
17
+ /** Model, custom-id and effort controls share the hook's resolved model state. */
18
+ function LocalCliAgentModelControls({ agent, card, onModelChange, agentHandle }) {
15
19
  const t = useT();
16
- const { reasoning, source, sourceLabel, sourceHint, result, repair, repairField } = card;
20
+ const { reasoning, source, sourceLabel, sourceHint } = card;
17
21
  const { hasModels, hasReasoning, reasoningOptions, reasoningValue, reasoningDisabled, allowCustomModel, customActive, modelChoices, selectValue, customModelInputValue, } = reasoning;
18
- return (_jsxs("div", { className: "jini-agent-settings", role: "group", "aria-label": agent.label, "data-testid": `jini-agent-settings-${agent.id}`, children: [_jsxs("div", { className: "jini-agent-settings-row", children: [hasModels ? (_jsxs("div", { className: "jini-agent-settings-field jini-agent-settings-model", children: [_jsxs("div", { className: "jini-agent-settings-label", children: [_jsx("span", { className: "jini-field-label", children: t('Model') }), sourceLabel ? (_jsx("span", { className: `jini-agent-model-source ${source}`, children: sourceLabel })) : null] }), _jsx(SearchableModelSelect, { className: "jini-input jini-agent-model-select", ariaLabel: t('Model'), searchPlaceholder: t('Search models'), testId: `jini-agent-model-${agent.id}`, searchInputTestId: `jini-agent-model-search-${agent.id}`, ...(agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'model' }) } : {}), value: selectValue, models: modelChoices, additionalOptions: allowCustomModel ? [{ value: CUSTOM_MODEL_SENTINEL, label: t('Custom…') }] : undefined, onChange: card.selectModel }), sourceHint ? _jsx("p", { className: "jini-field-hint", children: sourceHint }) : null] })) : null, customActive ? (_jsxs("label", { className: "jini-agent-settings-field", children: [_jsx("span", { className: "jini-field-label", children: t('Custom model id') }), _jsx("input", { className: "jini-input jini-agent-settings-custom", type: "text", "data-testid": `jini-agent-model-custom-${agent.id}`, value: customModelInputValue, placeholder: t('e.g. my-fine-tuned-model'), spellCheck: false, onChange: (event) => onModelChange(agent.id, event.target.value.trim()), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'custom-model', role: 'field', label: t('Custom model id') }) }) })] })) : null, hasReasoning ? (_jsxs("label", { className: "jini-agent-settings-field", children: [_jsx("span", { className: "jini-field-label", children: t('Reasoning effort') }), _jsx("select", { className: "jini-input jini-agent-settings-reasoning", "data-testid": `jini-agent-reasoning-${agent.id}`, value: reasoningValue, disabled: reasoningDisabled, onChange: (event) => card.selectReasoning(event.target.value), ...agentHandleProps({}, { base: agentHandle, ...({ action: 'reasoning', role: 'field', label: t('Reasoning effort') }) }), children: reasoningOptions.map((option) => (_jsx("option", { value: option.id, children: option.label }, option.id))) })] })) : null] }), result ? (_jsx("p", { className: 'jini-agent-card-test-result is-' + result.status, role: result.status === 'error' ? 'alert' : 'status', children: result.message ?? (result.status === 'ok' ? t('Agent is ready.') : t('Agent check failed')) })) : null, repair && repairField ? (_jsxs("div", { className: "jini-agent-card-path-repair", children: [_jsx("span", { className: "jini-field-hint", children: t('A custom path override did not work, so this used the automatically detected binary instead.') }), _jsxs("div", { className: "jini-agent-card-path-repair-actions", children: [repair.canUseDetected ? (_jsx("button", { type: "button", className: "jini-btn", "data-testid": `jini-agent-path-repair-use-${agent.id}`, onClick: () => onEnvChange(agent.id, repairField.envKey, repair.detectedPath), children: t('Use detected path') })) : null, _jsx("button", { type: "button", className: "jini-btn jini-btn-ghost", "data-testid": `jini-agent-path-repair-clear-${agent.id}`, onClick: () => onEnvChange(agent.id, repairField.envKey, ''), children: t('Clear custom path') })] })] })) : null, _jsx(AgentCliEnvFields, { agentId: agent.id, fields: cliEnvFields, config: config, onChange: onEnvChange })] }));
22
+ const additionalOptions = allowCustomModel ? [{ value: CUSTOM_MODEL_SENTINEL, label: t('Custom…') }] : undefined;
23
+ return (_jsxs("div", { className: "jini-agent-settings-row", children: [hasModels ? (_jsxs("div", { className: "jini-agent-settings-field jini-agent-settings-model", children: [_jsxs("div", { className: "jini-agent-settings-label", children: [_jsx("span", { className: "jini-field-label", children: t('Model') }), sourceLabel ? (_jsx("span", { className: `jini-agent-model-source ${source}`, children: sourceLabel })) : null] }), _jsx(SearchableModelSelect, { className: "jini-input jini-agent-model-select", ariaLabel: t('Model'), searchPlaceholder: t('Search models'), testId: `jini-agent-model-${agent.id}`, searchInputTestId: `jini-agent-model-search-${agent.id}`, ...(agentHandle ? { agentHandle: agentSubHandle({ base: agentHandle, action: 'model' }) } : {}), value: selectValue, models: modelChoices, additionalOptions: additionalOptions, onChange: card.selectModel }), sourceHint ? _jsx("p", { className: "jini-field-hint", children: sourceHint }) : null] })) : null, customActive ? (_jsxs("label", { className: "jini-agent-settings-field", children: [_jsx("span", { className: "jini-field-label", children: t('Custom model id') }), _jsx("input", { className: "jini-input jini-agent-settings-custom", type: "text", "data-testid": `jini-agent-model-custom-${agent.id}`, value: customModelInputValue, placeholder: t('e.g. my-fine-tuned-model'), spellCheck: false, onChange: (event) => onModelChange(agent.id, event.target.value.trim()), ...agentHandleProps({}, { base: agentHandle, action: 'custom-model', role: 'field', label: t('Custom model id') }) })] })) : null, hasReasoning ? (_jsxs("label", { className: "jini-agent-settings-field", children: [_jsx("span", { className: "jini-field-label", children: t('Reasoning effort') }), _jsx("select", { className: "jini-input jini-agent-settings-reasoning", "data-testid": `jini-agent-reasoning-${agent.id}`, value: reasoningValue, disabled: reasoningDisabled, onChange: (event) => card.selectReasoning(event.target.value), ...agentHandleProps({}, { base: agentHandle, action: 'reasoning', role: 'field', label: t('Reasoning effort') }), children: reasoningOptions.map((option) => (_jsx("option", { value: option.id, children: option.label }, option.id))) })] })) : null] }));
24
+ }
25
+ /** Probe evidence and repair actions stay together beneath the model controls. */
26
+ function LocalCliAgentFeedback({ agent, card, onEnvChange }) {
27
+ const t = useT();
28
+ const { result, repair, repairField } = card;
29
+ return (_jsxs(_Fragment, { children: [result ? (_jsx("p", { className: 'jini-agent-card-test-result is-' + result.status, role: result.status === 'error' ? 'alert' : 'status', children: result.status === 'ok' ? (result.message ?? t('Agent is ready.')) : result.message })) : null, repair && repairField ? (_jsxs("div", { className: "jini-agent-card-path-repair", children: [_jsx("span", { className: "jini-field-hint", children: t('A custom path override did not work, so this used the automatically detected binary instead.') }), _jsxs("div", { className: "jini-agent-card-path-repair-actions", children: [repair.canUseDetected ? (_jsx("button", { type: "button", className: "jini-btn", "data-testid": `jini-agent-path-repair-use-${agent.id}`, onClick: () => onEnvChange(agent.id, repairField.envKey, repair.detectedPath), children: t('Use detected path') })) : null, _jsx("button", { type: "button", className: "jini-btn jini-btn-ghost", "data-testid": `jini-agent-path-repair-clear-${agent.id}`, onClick: () => onEnvChange(agent.id, repairField.envKey, ''), children: t('Clear custom path') })] })] })) : null] }));
19
30
  }
20
31
  //# sourceMappingURL=LocalCliAgentSettings.js.map