@jini-ai/ui 0.5.4 → 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.
- package/CHANGELOG.md +10 -0
- package/dist/core/theme/validation.js +40 -38
- package/dist/features/a2ui/renderer.js +15 -13
- package/dist/features/about/react/components/AboutTab.js +64 -34
- package/dist/features/about/rules.d.ts +2 -2
- package/dist/features/about/rules.js +156 -129
- package/dist/features/admin-widgets/components/ImagePreviewModal.d.ts +4 -3
- package/dist/features/admin-widgets/components/ImagePreviewModal.js +6 -10
- package/dist/features/admin-widgets/components/InfoTip.d.ts +3 -2
- package/dist/features/admin-widgets/components/InfoTip.hooks.js +2 -0
- package/dist/features/admin-widgets/components/InfoTip.js +34 -7
- package/dist/features/admin-widgets/components/InfoTip.rules.d.ts +13 -0
- package/dist/features/admin-widgets/components/InfoTip.rules.js +43 -10
- package/dist/features/admin-widgets/components/Lightbox/Lightbox.d.ts +30 -0
- package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.d.ts +40 -0
- package/dist/features/admin-widgets/components/Lightbox/Lightbox.hooks.js +128 -0
- package/dist/features/admin-widgets/components/Lightbox/Lightbox.js +18 -0
- package/dist/features/admin-widgets/components/MiddleTruncate.d.ts +14 -0
- package/dist/features/admin-widgets/components/MiddleTruncate.js +12 -0
- package/dist/features/admin-widgets/components/MiddleTruncate.rules.d.ts +20 -0
- package/dist/features/admin-widgets/components/MiddleTruncate.rules.js +21 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.d.ts +18 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.d.ts +16 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.hooks.js +45 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.js +18 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.d.ts +32 -0
- package/dist/features/admin-widgets/components/ScrollJump/ScrollJump.rules.js +26 -0
- package/dist/features/admin-widgets/components/Select/Select.hooks.d.ts +21 -0
- package/dist/features/admin-widgets/components/Select/Select.hooks.js +24 -2
- package/dist/features/admin-widgets/components/Select/Select.js +4 -3
- package/dist/features/admin-widgets/components/SuggestInput/SuggestInput.hooks.js +29 -14
- package/dist/features/admin-widgets/index.d.ts +14 -2
- package/dist/features/admin-widgets/index.js +7 -1
- package/dist/features/admin-widgets/styles/admin-widgets.css +1 -0
- package/dist/features/asset-grid/react/components/AssetCard.js +7 -1
- package/dist/features/asset-grid/react/components/AssetGrid.d.ts +1 -1
- package/dist/features/asset-grid/react/components/AssetGrid.js +38 -18
- package/dist/features/asset-grid/react/components/useAssetGridFilters.d.ts +21 -0
- package/dist/features/asset-grid/react/components/useAssetGridFilters.js +19 -0
- package/dist/features/asset-grid/react/hooks/useAssetGridKeyboardShortcuts.js +16 -11
- package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.d.ts +2 -0
- package/dist/features/asset-grid/react/hooks/useAssetGridLiveUpdates.js +35 -11
- package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.d.ts +6 -6
- package/dist/features/asset-tree-browser/react/components/AssetTreeBrowser.js +10 -129
- package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.d.ts +50 -0
- package/dist/features/asset-tree-browser/react/components/AssetTreeBrowserParts.js +95 -0
- package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.d.ts +56 -0
- package/dist/features/asset-tree-browser/react/components/useAssetTreeBrowserController.js +80 -0
- package/dist/features/asset-tree-browser/rules.js +40 -22
- package/dist/features/browser-chrome/rules.js +74 -70
- package/dist/features/charts/browser.js +11 -5
- package/dist/features/charts/react.js +2 -2
- package/dist/features/charts/table.js +2 -2
- package/dist/features/command-palette/react/hooks/useCommandPalette.js +3 -9
- package/dist/features/connectors/ConnectorsBrowser.d.ts +4 -4
- package/dist/features/connectors/ConnectorsBrowser.js +26 -14
- package/dist/features/connectors/components/ConnectorCard.js +67 -38
- package/dist/features/connectors/components/ConnectorDetailDrawer.js +62 -31
- package/dist/features/connectors/components/ConnectorGrid.js +11 -6
- package/dist/features/connectors/hooks/useConnectorAuthorization.js +6 -0
- package/dist/features/connectors/hooks/useConnectorCatalog.d.ts +2 -0
- package/dist/features/connectors/hooks/useConnectorCatalog.js +32 -13
- package/dist/features/connectors/hooks/useWiredConnectorsBrowser.d.ts +3 -3
- package/dist/features/connectors/rules.d.ts +8 -1
- package/dist/features/connectors/rules.js +42 -20
- package/dist/features/connectors/types.d.ts +11 -0
- package/dist/features/execution/constants.d.ts +4 -10
- package/dist/features/execution/constants.js +9 -94
- package/dist/features/execution/react/components/ByokProviderForm.d.ts +2 -4
- package/dist/features/execution/react/components/ByokProviderForm.js +140 -43
- package/dist/features/execution/react/components/ExecutionTab.d.ts +1 -2
- package/dist/features/execution/react/components/ExecutionTab.js +52 -30
- package/dist/features/execution/react/components/LocalCliAgentCard.js +12 -3
- package/dist/features/execution/react/components/LocalCliAgentList.js +14 -3
- package/dist/features/execution/react/components/LocalCliAgentSettings.js +14 -3
- package/dist/features/execution/react/hooks/useLocalCliAgentCard.js +54 -36
- package/dist/features/execution/react/hooks/useReasoningControl.d.ts +1 -1
- package/dist/features/execution/react/hooks/useReasoningControl.js +116 -75
- package/dist/features/execution/rules.d.ts +13 -2
- package/dist/features/execution/rules.js +51 -11
- package/dist/features/execution/types.d.ts +6 -42
- package/dist/features/execution/types.js +0 -17
- package/dist/features/external-mcp/react/components/ExternalMcpTab.d.ts +6 -2
- package/dist/features/external-mcp/react/components/ExternalMcpTab.js +9 -2
- package/dist/features/file-dropzone/react/components/FileDropzone.js +24 -25
- package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.d.ts +2 -2
- package/dist/features/file-dropzone/react/components/FileDropzoneLightbox.js +35 -30
- package/dist/features/file-dropzone/react/components/FileDropzoneThumbnailGrid.js +30 -24
- package/dist/features/file-dropzone/react/components/FileDropzoneZone.d.ts +14 -0
- package/dist/features/file-dropzone/react/components/FileDropzoneZone.js +26 -0
- package/dist/features/html-editor/canvas-content-wrapper.js +13 -13
- package/dist/features/html-editor/canvas-embed-placeholders.js +46 -37
- package/dist/features/html-editor/css.js +76 -69
- package/dist/features/html-editor/editor-blocks.js +47 -33
- package/dist/features/html-editor/editor-serialization.js +6 -5
- package/dist/features/html-editor/protected-elements.js +39 -28
- package/dist/features/html-editor/react/hooks/useInteractiveHtmlEditor.js +4 -4
- package/dist/features/html-editor/source-splice.js +51 -30
- package/dist/features/i18n/dictionaries/settings-dialog.ja.js +1 -0
- package/dist/features/i18n/dictionaries/settings-dialog.zh-CN.js +1 -0
- package/dist/features/i18n/dictionaries/settings-dialog.zh-TW.js +1 -0
- package/dist/features/i18n/locale.js +20 -16
- package/dist/features/iframe-pool/react/dom-sync.js +8 -6
- package/dist/features/integrations/react/components/CodexInstallToggleButton.js +19 -4
- package/dist/features/integrations/react/components/IntegrationsTab.js +60 -14
- package/dist/features/interactive-ui/interactive-ui.css +1 -1
- package/dist/features/interactive-ui/providers/native/data-table.manifest.d.ts +6 -6
- package/dist/features/interactive-ui/providers/shadcn/data-table.js +10 -7
- package/dist/features/interactive-ui/providers/shadcn/data-table.manifest.d.ts +6 -6
- package/dist/features/lexical-rich-text-editor/mention-parser.js +60 -57
- package/dist/features/lexical-rich-text-editor/react/hooks/useKeyboardCommands.js +33 -38
- package/dist/features/lexical-rich-text-editor/rules.js +87 -74
- package/dist/features/lexical-rich-text-editor/serialize.js +27 -25
- package/dist/features/mcp-ui/secret-card.js +111 -61
- package/dist/features/mcp-ui/surfaces/confirmation.js +39 -21
- package/dist/features/mcp-ui/surfaces/document.d.ts +1 -1
- package/dist/features/mcp-ui/surfaces/document.js +10 -2
- package/dist/features/mcp-ui/surfaces/fields.js +30 -50
- package/dist/features/mcp-ui/surfaces/form.d.ts +6 -0
- package/dist/features/mcp-ui/surfaces/form.js +43 -21
- package/dist/features/mcp-ui/surfaces/outcome.js +30 -20
- package/dist/features/mcp-ui/surfaces/text-input.js +33 -21
- package/dist/features/mcp-ui/surfaces/tokens.d.ts +6 -1
- package/dist/features/mcp-ui/surfaces/tokens.js +5 -2
- package/dist/features/media-providers/constants.d.ts +1 -0
- package/dist/features/media-providers/constants.js +2 -81
- package/dist/features/media-providers/index.d.ts +1 -1
- package/dist/features/media-providers/ports.d.ts +2 -0
- package/dist/features/media-providers/react/components/MediaProviderCard.d.ts +22 -0
- package/dist/features/media-providers/react/components/MediaProviderCard.js +50 -0
- package/dist/features/media-providers/react/components/MediaProvidersTab.d.ts +5 -6
- package/dist/features/media-providers/react/components/MediaProvidersTab.js +40 -72
- package/dist/features/media-providers/react/components/media-providers-tab-labels.d.ts +11 -0
- package/dist/features/media-providers/react/components/media-providers-tab-labels.js +57 -0
- package/dist/features/media-providers/react/hooks/useMediaProvidersTab.d.ts +4 -2
- package/dist/features/media-providers/react/hooks/useMediaProvidersTab.js +37 -23
- package/dist/features/media-providers/rules.js +17 -10
- package/dist/features/media-providers/types.d.ts +4 -22
- package/dist/features/media-providers/types.js +3 -8
- package/dist/features/memory/formatters.js +127 -92
- package/dist/features/memory/react/components/MemoryConnectedPanel.js +92 -36
- package/dist/features/memory/react/components/MemoryList.js +37 -18
- package/dist/features/memory/react/hooks/useMemoryExtractions.store.js +42 -36
- package/dist/features/notifications/react/components/NotificationsTab.js +28 -28
- package/dist/features/observability/long-task.d.ts +0 -12
- package/dist/features/observability/long-task.js +15 -19
- package/dist/features/observability/resource-error.d.ts +0 -10
- package/dist/features/observability/resource-error.js +18 -13
- package/dist/features/observability/stuck-run.js +10 -6
- package/dist/features/observability/telemetry-url.d.ts +6 -0
- package/dist/features/observability/telemetry-url.js +16 -0
- package/dist/features/observability/white-screen.js +12 -24
- package/dist/features/panel-kit/fetch-query/adapter.react.d.ts +2 -2
- package/dist/features/panel-kit/fetch-query/adapter.react.js +4 -2
- package/dist/features/panel-kit/fetch-query/adapter.tanstack.js +46 -33
- package/dist/features/panel-kit/helpers/standing-draft-local-backup.js +3 -1
- package/dist/features/panel-kit/hooks/use-settings-slice.hooks.js +3 -1
- package/dist/features/privacy/react/components/PrivacyTab.js +24 -13
- package/dist/features/progress-card/components/ProgressCard.js +7 -3
- package/dist/features/progress-card/reference-adapters.js +59 -54
- package/dist/features/project-locations/react/components/ProjectLocationsTab.js +24 -15
- package/dist/features/project-locations/react/hooks/useProjectLocationsTab.d.ts +8 -0
- package/dist/features/project-locations/react/hooks/useProjectLocationsTab.js +31 -23
- package/dist/features/resource-dashboard/react/components/ResourceBoard.js +6 -47
- package/dist/features/resource-dashboard/react/components/ResourceBoardView.d.ts +2 -2
- package/dist/features/resource-dashboard/react/components/ResourceBoardView.js +3 -6
- package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.d.ts +15 -0
- package/dist/features/resource-dashboard/react/components/ResourceBoardViewParts.js +35 -0
- package/dist/features/resource-dashboard/react/components/ResourceCard.js +5 -16
- package/dist/features/resource-dashboard/react/components/ResourceCardParts.d.ts +34 -0
- package/dist/features/resource-dashboard/react/components/ResourceCardParts.js +34 -0
- package/dist/features/resource-dashboard/react/components/ResourceRowList.js +3 -1
- package/dist/features/resource-dashboard/react/components/ResourceRowListItem.js +2 -2
- package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.d.ts +19 -0
- package/dist/features/resource-dashboard/react/components/ResourceRowListItemParts.js +11 -0
- package/dist/features/resource-dashboard/react/components/ResourceRowListView.d.ts +1 -1
- package/dist/features/resource-dashboard/react/components/ResourceRowListView.js +4 -4
- package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.d.ts +10 -0
- package/dist/features/resource-dashboard/react/components/ResourceRowListViewParts.js +20 -0
- package/dist/features/resource-dashboard/react/components/resourceBoardProps.d.ts +48 -0
- package/dist/features/resource-dashboard/react/components/resourceBoardProps.js +70 -0
- package/dist/features/resource-dashboard/react/components/resourceRowListProps.d.ts +19 -0
- package/dist/features/resource-dashboard/react/components/resourceRowListProps.js +14 -0
- package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.d.ts +16 -0
- package/dist/features/resource-dashboard/react/components/translatedErrorLabelProps.js +13 -0
- package/dist/features/resource-dashboard/rules.js +2 -8
- package/dist/features/sketch-editor/dom.js +150 -99
- package/dist/features/sketch-editor/react/components/SketchEditor.js +26 -15
- package/dist/features/skills/react/components/SkillDraftForm.js +13 -19
- package/dist/features/skills/react/components/SkillDraftForm.labels.d.ts +31 -0
- package/dist/features/skills/react/components/SkillDraftForm.labels.js +54 -0
- package/dist/features/skills/react/components/SkillRow.js +38 -18
- package/dist/features/skills/react/components/SkillRow.labels.d.ts +7 -0
- package/dist/features/skills/react/components/SkillRow.labels.js +18 -0
- package/dist/features/skills/react/components/SkillsTab.js +24 -14
- package/dist/features/skills/react/components/SkillsTab.labels.d.ts +7 -0
- package/dist/features/skills/react/components/SkillsTab.labels.js +11 -0
- package/dist/features/skills/rules.js +32 -23
- package/dist/features/source-config-list/agent-handles.js +2 -1
- package/dist/features/source-config-list/dependencies.js +53 -49
- package/dist/features/source-config-list/index.d.ts +3 -3
- package/dist/features/source-config-list/react/components/SourceConfigAddForm.d.ts +6 -5
- package/dist/features/source-config-list/react/components/SourceConfigAddForm.js +38 -7
- package/dist/features/source-config-list/react/components/SourceConfigField.js +69 -25
- package/dist/features/source-config-list/react/components/SourceConfigItemCard.d.ts +6 -5
- package/dist/features/source-config-list/react/components/SourceConfigItemCard.js +76 -20
- package/dist/features/source-config-list/react/components/SourceConfigListView.d.ts +5 -3
- package/dist/features/source-config-list/react/components/SourceConfigListView.js +33 -3
- package/dist/features/source-config-list/react/components/SourceConfigTestControl.js +17 -1
- package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.d.ts +11 -0
- package/dist/features/source-config-list/react/components/SourceConfigTrustOptions.js +12 -0
- package/dist/features/source-config-list/types.d.ts +14 -0
- package/dist/features/tab-launcher-menu/react/components/TabLauncherMenu.js +25 -1
- package/dist/features/tab-launcher-menu/react/hooks/useTabLauncherMenu.js +15 -16
- package/dist/features/tab-strip/react/components/TabBar.js +8 -1
- package/dist/features/tabbed-dialog/react/components/TabbedDialog.js +8 -30
- package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.d.ts +15 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogChrome.js +8 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.d.ts +12 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogHeader.js +6 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.d.ts +27 -0
- package/dist/features/tabbed-dialog/react/components/TabbedDialogSidebar.js +12 -0
- package/dist/features/tabbed-dialog/react/components/tabbedDialogView.d.ts +43 -0
- package/dist/features/tabbed-dialog/react/components/tabbedDialogView.js +59 -0
- package/dist/features/version-manager/dependencies.d.ts +3 -1
- package/dist/features/version-manager/dependencies.js +3 -2
- package/dist/features/version-manager/react/components/VersionManagerModal.js +10 -6
- package/dist/features/version-manager/react/hooks/useVersionManager.js +7 -4
- package/dist/features/viewer-shell/react/components/CommentSidePanel.js +103 -50
- package/dist/features/viewer-shell/react/components/MarkdownSplitPane.js +28 -8
- package/dist/features/viewer-shell/react/hooks/useCommentReorder.js +22 -16
- package/dist/features/viewer-shell/rules.js +19 -23
- package/dist/react/components/CustomSelect.d.ts +38 -1
- package/dist/react/components/CustomSelect.js +94 -38
- package/dist/react/components/OnboardingDropdown.js +20 -8
- package/dist/react/components/RemixIcon.stylesheet.js +3 -1
- package/dist/react/components/Toast.js +13 -2
- package/dist/react/components/TooltipLayer.js +5 -14
- package/dist/react/components/WorkingDirPicker.js +78 -50
- package/dist/react/components/remixicon-font-recovery.js +38 -30
- package/dist/react/components/select-popover.d.ts +8 -6
- package/dist/react/components/select-popover.js +33 -29
- package/dist/react/mcp-ui/McpUiHost.d.ts +1 -1
- package/dist/react/mcp-ui/McpUiHost.js +10 -11
- package/dist/react/mcp-ui/host-message-source.d.ts +14 -2
- package/dist/react/mcp-ui/host-message-source.js +15 -5
- package/dist/react/mcp-ui/mcp-ui-host-frame.d.ts +41 -0
- package/dist/react/mcp-ui/mcp-ui-host-frame.js +39 -0
- package/dist/renderers/annotation-canvas/react/components/AnnotationCanvas.js +97 -69
- package/dist/renderers/preview-modal-shell/react/components/PreviewModalShell.js +59 -30
- package/dist/renderers/preview-modal-shell/react/components/icons.js +4 -1
- package/dist/renderers/react/components/ArtifactView.js +9 -6
- package/dist/renderers/srcdoc/build.d.ts +4 -1
- package/dist/renderers/srcdoc/build.js +62 -48
- package/dist/styles/admin.css +29 -14
- package/dist/styles/lightbox.css +142 -0
- package/dist/styles/variables.css +128 -0
- package/dist/theme/apply.js +9 -5
- package/dist/utils/auto-open-file.d.ts +2 -1
- package/dist/utils/auto-open-file.js +69 -85
- package/dist/utils/markdown-scroll-sync.js +108 -87
- package/dist/utils/polygon-selection.js +25 -26
- package/dist/utils/smooth-scroll-to-top.d.ts +1 -1
- package/dist/utils/smooth-scroll-to-top.js +2 -32
- package/dist/utils/sse.js +26 -26
- package/dist/utils/unit-bezier.d.ts +7 -0
- package/dist/utils/unit-bezier.js +38 -0
- package/package.json +7 -10
- package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.d.ts +0 -13
- package/dist/features/admin-widgets/components/ImagePreviewModal.hooks.js +0 -46
|
@@ -1,82 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* A small, vendor-shaped starting catalog — same convention as `execution/constants.ts`'s
|
|
4
|
-
* `DEFAULT_PROVIDER_PRESETS`. Sourced by driving Open Design's own Settings → Media providers
|
|
5
|
-
* panel directly (Playwright) and transcribing its real provider list, base URLs, and default
|
|
6
|
-
* models. Pass your own `catalog` to `MediaProvidersTab` to replace this wholesale; Zana or any
|
|
7
|
-
* other host is expected to do so once it has its own provider roster.
|
|
8
|
-
*/
|
|
9
|
-
const starterCatalog = [
|
|
10
|
-
{
|
|
11
|
-
id: 'nano-banana',
|
|
12
|
-
label: 'Nano Banana',
|
|
13
|
-
models: ['gemini-3.1-flash-image-preview'],
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
id: 'aihubmix',
|
|
17
|
-
label: 'AIHubMix',
|
|
18
|
-
models: ['gemini-3.1-flash-image-preview'],
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
id: 'custom-image-api',
|
|
22
|
-
label: 'Custom Media API',
|
|
23
|
-
models: ['gemini-3.1-flash-image-preview'],
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
id: 'elevenlabs',
|
|
27
|
-
label: 'ElevenLabs',
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
id: 'fal-ai',
|
|
31
|
-
label: 'Fal.ai',
|
|
32
|
-
models: ['gemini-3.1-flash-image-preview'],
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
id: 'fishaudio',
|
|
36
|
-
label: 'FishAudio',
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
id: 'imagerouter',
|
|
40
|
-
label: 'ImageRouter',
|
|
41
|
-
models: ['gemini-3.1-flash-image-preview'],
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
id: 'leonardo-ai',
|
|
45
|
-
label: 'Leonardo.ai',
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
id: 'minimax',
|
|
49
|
-
label: 'MiniMax',
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
id: 'openai',
|
|
53
|
-
label: 'OpenAI',
|
|
54
|
-
models: ['gpt-image-2', 'dall-e-3'],
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: 'openrouter',
|
|
58
|
-
label: 'OpenRouter',
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
id: 'senseaudio',
|
|
62
|
-
label: 'SenseAudio',
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
id: 'tavily-search',
|
|
66
|
-
label: 'Tavily Search',
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
id: 'volcengine-ark',
|
|
70
|
-
label: 'Volcengine Ark (Doubao)',
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
id: 'xai-grok-imagine',
|
|
74
|
-
label: 'xAI Grok Imagine',
|
|
75
|
-
},
|
|
76
|
-
];
|
|
77
|
-
export const DEFAULT_MEDIA_PROVIDER_CATALOG = starterCatalog.map((entry) => {
|
|
78
|
-
const id = canonicalMediaProviderId({ id: entry.id });
|
|
79
|
-
const provider = findProvider({ id });
|
|
80
|
-
return { ...entry, id, label: provider.label, ...(provider.defaultBaseUrl ? { defaultBaseUrl: provider.defaultBaseUrl } : {}) };
|
|
81
|
-
});
|
|
1
|
+
/** @deprecated Supply a host catalog. The former starter set is exported by integrations/media-providers/catalog. */
|
|
2
|
+
export const DEFAULT_MEDIA_PROVIDER_CATALOG = [];
|
|
82
3
|
//# sourceMappingURL=constants.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { DEFAULT_MEDIA_PROVIDER_CATALOG } from './constants.js';
|
|
2
2
|
export { createFakeMediaProvidersPort, type FakeMediaProvidersPortOptions } from './dependencies.js';
|
|
3
|
-
export type { MediaProvidersPort } from './ports.js';
|
|
3
|
+
export type { MediaProvidersPort, MediaProviderCatalogPolicy } from './ports.js';
|
|
4
4
|
export { hasAnyConfiguredProvider, hasRecoverableFields, isEntryEmpty, isEntryPresent, isMarkerOnlyEntry, maskedKeyLabel, mergeDaemonProviders, resolveProviderBaseUrl, shouldSyncLocalProvidersToDaemon, sortProvidersByConfigured, isProviderBaseUrlInvalid, invalidBaseUrlProviderIds, } from './rules.js';
|
|
5
5
|
export type { MediaProviderCredentials, MediaProviderMap, MediaProviderOption, MediaProvidersLoadState, MediaProvidersSaveState, } from './types.js';
|
|
6
6
|
export { useMediaProvidersTab } from './react/hooks/useMediaProvidersTab.js';
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Catalog policy is UI-owned; the shared contract lives once in core. */
|
|
2
|
+
export type { MediaProviderCatalogPolicy } from '@jini-ai/core';
|
|
1
3
|
import type { MediaProviderMap } from './types.js';
|
|
2
4
|
/**
|
|
3
5
|
* The daemon-side transport this tab needs: reading the server's current
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { I18nContextValue } from '../../../i18n/index.js';
|
|
2
|
+
import type { MediaProviderCredentials, MediaProviderOption } from '../../types.js';
|
|
3
|
+
import type { MediaProviderEditPatch } from '../hooks/useMediaProvidersTab.js';
|
|
4
|
+
import type { ResolvedMediaProvidersTabLabels } from './media-providers-tab-labels.js';
|
|
5
|
+
type Translate = I18nContextValue['t'];
|
|
6
|
+
export interface MediaProviderCardProps {
|
|
7
|
+
option: MediaProviderOption;
|
|
8
|
+
entry: MediaProviderCredentials;
|
|
9
|
+
labels: ResolvedMediaProvidersTabLabels;
|
|
10
|
+
t: Translate;
|
|
11
|
+
pending: boolean;
|
|
12
|
+
keyVisible: boolean;
|
|
13
|
+
showDividerBefore: boolean;
|
|
14
|
+
providerHandle: string | undefined;
|
|
15
|
+
onUpdate: (patch: MediaProviderEditPatch) => void;
|
|
16
|
+
onClear: () => void;
|
|
17
|
+
onToggleKeyVisible: () => void;
|
|
18
|
+
}
|
|
19
|
+
/** One provider's credential card: API key, base URL, optional model, clear, and the base-URL hint. */
|
|
20
|
+
export declare function MediaProviderCard(props: MediaProviderCardProps): import("react").JSX.Element;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=MediaProviderCard.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment } from 'react';
|
|
3
|
+
import { agentHandleProps } from '@jini-ai/agentic';
|
|
4
|
+
import { Icon } from '../../../../react/components/Icon.js';
|
|
5
|
+
import { isEntryPresent, isMarkerOnlyEntry, isProviderBaseUrlInvalid, maskedKeyLabel, resolveProviderBaseUrl, } from '../../rules.js';
|
|
6
|
+
/** One provider's credential card: API key, base URL, optional model, clear, and the base-URL hint. */
|
|
7
|
+
export function MediaProviderCard(props) {
|
|
8
|
+
const { option, entry, labels, t, providerHandle, onUpdate } = props;
|
|
9
|
+
const saved = isMarkerOnlyEntry(entry);
|
|
10
|
+
// Guaranteed non-null: `isMarkerOnlyEntry` only holds when a server
|
|
11
|
+
// marker is present, and `maskedKeyLabel` always resolves a string
|
|
12
|
+
// for exactly that case — see both functions' doc comments.
|
|
13
|
+
const maskedLabel = saved ? maskedKeyLabel(entry) : null;
|
|
14
|
+
const fields = { option, entry, labels, providerHandle, onUpdate };
|
|
15
|
+
return (_jsxs(Fragment, { children: [props.showDividerBefore ? _jsx("hr", { className: "jini-media-provider-divider" }) : null, _jsxs("div", { className: "jini-media-provider-card", children: [_jsx(CardHead, { option: option, labels: labels, t: t, maskedLabel: maskedLabel, pending: props.pending }), _jsxs("div", { className: "jini-media-provider-fields", children: [_jsx(ApiKeyField, { ...fields, maskedLabel: maskedLabel, keyVisible: props.keyVisible, onToggleKeyVisible: props.onToggleKeyVisible }), _jsx(BaseUrlField, { ...fields }), option.models && option.models.length > 0 ? _jsx(ModelField, { ...fields, models: option.models }) : null, _jsx("button", { type: "button", className: "jini-button jini-button-ghost", disabled: !isEntryPresent(entry), "aria-label": `${option.label} ${labels.clearLabel}`, onClick: props.onClear, ...agentHandleProps({}, { base: providerHandle, ...({ action: 'clear', role: 'button', label: `${option.label} ${labels.clearLabel}` }) }), children: labels.clearLabel })] }), _jsx(BaseUrlHint, { entry: entry, option: option, labels: labels, t: t })] })] }));
|
|
16
|
+
}
|
|
17
|
+
function CardHead({ option, labels, t, maskedLabel, pending, }) {
|
|
18
|
+
// `maskedLabel` is non-null exactly when the entry is marker-only ("saved").
|
|
19
|
+
const saved = maskedLabel !== null;
|
|
20
|
+
return (_jsxs("div", { className: "jini-media-provider-card-head", children: [_jsx("strong", { children: option.label }), saved ? (
|
|
21
|
+
// `maskedLabel` is guaranteed non-null here — see its
|
|
22
|
+
// definition above — so no fallback is reachable to test.
|
|
23
|
+
_jsx("span", { className: "jini-field-status-badge jini-field-status-badge-success", children: t(labels.savedWithMaskTemplate, { mask: maskedLabel }) })) : null, !saved && pending ? _jsx("span", { className: "jini-field-status-badge", children: labels.unsavedLabel }) : null] }));
|
|
24
|
+
}
|
|
25
|
+
function ApiKeyField({ option, entry, labels, providerHandle, onUpdate, maskedLabel, keyVisible, onToggleKeyVisible, }) {
|
|
26
|
+
return (_jsxs("label", { className: "jini-media-provider-field jini-media-provider-field--secret", children: [_jsx("span", { className: "jini-field-label jini-sr-only", children: labels.apiKeyLabel }), _jsx("input", { className: "jini-input", type: keyVisible ? 'text' : 'password',
|
|
27
|
+
/* `new-password`, NOT `"off"` — Chrome deliberately ignores `off` on
|
|
28
|
+
credential-shaped fields, which is how a saved password gets autofilled into
|
|
29
|
+
an API-key box. Same defect, same fix, same reasoning as
|
|
30
|
+
`ByokProviderForm`'s own API-key input; see its comment. */
|
|
31
|
+
autoComplete: "new-password", spellCheck: false, placeholder: maskedLabel ?? labels.apiKeyPlaceholder, "aria-label": `${option.label} ${labels.apiKeyLabel}`, value: entry.apiKey ?? '', onChange: (event) => onUpdate({ apiKey: event.target.value }), ...agentHandleProps({}, { base: providerHandle, ...({ action: 'api-key', role: 'field', label: `${option.label} ${labels.apiKeyLabel}` }) }) }), _jsx("button", { type: "button", className: "jini-input-affix-btn", "aria-pressed": keyVisible, "aria-label": `${option.label} ${keyVisible ? labels.hideKeyLabel : labels.showKeyLabel}`, onClick: onToggleKeyVisible, ...agentHandleProps({}, { base: providerHandle, ...({ action: 'reveal-key', role: 'button', label: `${option.label} ${labels.showKeyLabel}` }) }), children: _jsx(Icon, { name: keyVisible ? 'eye-off' : 'eye', size: 14 }) })] }));
|
|
32
|
+
}
|
|
33
|
+
function BaseUrlField({ option, entry, labels, providerHandle, onUpdate }) {
|
|
34
|
+
return (_jsxs("label", { className: "jini-media-provider-field", children: [_jsx("span", { className: "jini-field-label jini-sr-only", children: labels.baseUrlLabel }), _jsx("input", { className: "jini-input", type: "url", inputMode: "url", spellCheck: false, placeholder: option.defaultBaseUrl || labels.baseUrlPlaceholder, "aria-label": `${option.label} ${labels.baseUrlLabel}`, value: entry.baseUrl ?? '', "aria-invalid": isProviderBaseUrlInvalid(entry) || undefined, onChange: (event) => onUpdate({ baseUrl: event.target.value }), ...agentHandleProps({}, { base: providerHandle, ...({ action: 'base-url', role: 'field', label: `${option.label} ${labels.baseUrlLabel}` }) }) })] }));
|
|
35
|
+
}
|
|
36
|
+
function ModelField({ option, entry, labels, providerHandle, onUpdate, models }) {
|
|
37
|
+
const modelListId = `jini-media-provider-models-${option.id}`;
|
|
38
|
+
return (_jsxs("label", { className: "jini-media-provider-field", children: [_jsx("span", { className: "jini-field-label jini-sr-only", children: labels.modelLabel }), _jsx("input", { className: "jini-input", list: modelListId, spellCheck: false, placeholder: labels.modelPlaceholder, "aria-label": `${option.label} ${labels.modelLabel}`, value: entry.model ?? '', onChange: (event) => onUpdate({ model: event.target.value }), ...agentHandleProps({}, { base: providerHandle, ...({ action: 'model', role: 'field', label: `${option.label} ${labels.modelLabel}` }) }) }), _jsx("datalist", { id: modelListId, children: models.map((model) => (_jsx("option", { value: model }, model))) })] }));
|
|
39
|
+
}
|
|
40
|
+
/** The invalid-URL alert, else the "uses X by default" hint while the field is blank, else nothing. */
|
|
41
|
+
function BaseUrlHint({ entry, option, labels, t, }) {
|
|
42
|
+
if (isProviderBaseUrlInvalid(entry)) {
|
|
43
|
+
return (_jsx("span", { className: "jini-field-hint jini-hint-error", role: "alert", children: labels.baseUrlInvalidLabel }));
|
|
44
|
+
}
|
|
45
|
+
const effectiveBaseUrl = resolveProviderBaseUrl(entry, option.defaultBaseUrl);
|
|
46
|
+
if ((entry.baseUrl ?? '').trim() || !effectiveBaseUrl)
|
|
47
|
+
return null;
|
|
48
|
+
return _jsx("span", { className: "jini-field-hint", children: t(labels.baseUrlDefaultHintTemplate, { url: effectiveBaseUrl }) });
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=MediaProviderCard.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MediaProvidersPort } from '../../ports.js';
|
|
1
|
+
import type { MediaProviderCatalogPolicy, MediaProvidersPort } from '../../ports.js';
|
|
2
2
|
import type { MediaProviderMap, MediaProviderOption } from '../../types.js';
|
|
3
3
|
export interface MediaProvidersTabLabels {
|
|
4
4
|
title?: string;
|
|
@@ -32,10 +32,9 @@ export interface MediaProvidersTabLabels {
|
|
|
32
32
|
}
|
|
33
33
|
export interface MediaProvidersTabProps {
|
|
34
34
|
port: MediaProvidersPort;
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
catalog?: readonly MediaProviderOption[];
|
|
35
|
+
/** Host-owned provider catalog and its matching identity policy. */
|
|
36
|
+
catalog: readonly MediaProviderOption[];
|
|
37
|
+
catalogPolicy: MediaProviderCatalogPolicy;
|
|
39
38
|
/** Host-persisted local edits from before this tab mounted. See
|
|
40
39
|
* `useMediaProvidersTab`'s doc for how this feeds the first-load merge. */
|
|
41
40
|
initialProviders?: MediaProviderMap;
|
|
@@ -58,5 +57,5 @@ export interface MediaProvidersTabProps {
|
|
|
58
57
|
* decorations outside this package's boundary; see `MediaProviderOption`'s
|
|
59
58
|
* doc and this tab's `ports.ts` header for the full provenance note.
|
|
60
59
|
*/
|
|
61
|
-
export declare function MediaProvidersTab({ port, catalog, initialProviders, pinnedProviderIds, labels, agentHandle, }: MediaProvidersTabProps): import("react").JSX.Element;
|
|
60
|
+
export declare function MediaProvidersTab({ port, catalog, catalogPolicy, initialProviders, pinnedProviderIds, labels, agentHandle, }: MediaProvidersTabProps): import("react").JSX.Element;
|
|
62
61
|
//# sourceMappingURL=MediaProvidersTab.d.ts.map
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import { Fragment, useState } from 'react';
|
|
2
|
+
import { useState } from 'react';
|
|
4
3
|
import { agentHandleProps, buildAgentListHandles } from '@jini-ai/agentic';
|
|
5
4
|
import { useT } from '../../../i18n/index.js';
|
|
6
5
|
import { Icon } from '../../../../react/components/Icon.js';
|
|
7
|
-
import { invalidBaseUrlProviderIds,
|
|
8
|
-
import { DEFAULT_MEDIA_PROVIDER_CATALOG } from '../../constants.js';
|
|
6
|
+
import { invalidBaseUrlProviderIds, sortProvidersByConfigured } from '../../rules.js';
|
|
9
7
|
import { useMediaProvidersTab } from '../hooks/useMediaProvidersTab.js';
|
|
8
|
+
import { MediaProviderCard } from './MediaProviderCard.js';
|
|
9
|
+
import { resolveMediaProvidersTabLabels } from './media-providers-tab-labels.js';
|
|
10
10
|
/**
|
|
11
11
|
* Per-provider credential cards: API key, base URL, and (when the catalog
|
|
12
12
|
* entry advertises any) a model picker, each independently editable and
|
|
@@ -16,47 +16,17 @@ import { useMediaProvidersTab } from '../hooks/useMediaProvidersTab.js';
|
|
|
16
16
|
* decorations outside this package's boundary; see `MediaProviderOption`'s
|
|
17
17
|
* doc and this tab's `ports.ts` header for the full provenance note.
|
|
18
18
|
*/
|
|
19
|
-
export function MediaProvidersTab({ port, catalog
|
|
19
|
+
export function MediaProvidersTab({ port, catalog, catalogPolicy, initialProviders, pinnedProviderIds, labels, agentHandle, }) {
|
|
20
20
|
const t = useT();
|
|
21
|
-
const { providers, load, save, hasAnyConfigured, pendingProviderIds, updateProvider, clearProvider, saveChanges, reload } = useMediaProvidersTab({ port, initialProviders });
|
|
21
|
+
const { providers, load, save, hasAnyConfigured, pendingProviderIds, updateProvider, clearProvider, saveChanges, reload } = useMediaProvidersTab({ port, catalogPolicy, initialProviders });
|
|
22
|
+
const { canonicalMediaProviderId } = catalogPolicy;
|
|
22
23
|
const [visibleApiKeys, setVisibleApiKeys] = useState(() => new Set());
|
|
23
|
-
const
|
|
24
|
-
const description = labels?.description ?? t('Credentials this host uses to generate images and video.');
|
|
25
|
-
const reloadLabel = labels?.reloadLabel ?? t('Reload');
|
|
26
|
-
const reloadingLabel = labels?.reloadingLabel ?? t('Reloading…');
|
|
27
|
-
const unreachableLabel = labels?.unreachableLabel ?? t('Could not reach the server. Showing local changes only.');
|
|
28
|
-
const emptyStateLabel = labels?.emptyStateLabel ?? t('No media providers configured yet.');
|
|
29
|
-
const apiKeyLabel = labels?.apiKeyLabel ?? t('API key');
|
|
30
|
-
const apiKeyPlaceholder = labels?.apiKeyPlaceholder ?? t('Paste your API key');
|
|
31
|
-
const showKeyLabel = labels?.showKeyLabel ?? t('Show');
|
|
32
|
-
const hideKeyLabel = labels?.hideKeyLabel ?? t('Hide');
|
|
33
|
-
const baseUrlLabel = labels?.baseUrlLabel ?? t('Base URL');
|
|
34
|
-
const baseUrlPlaceholder = labels?.baseUrlPlaceholder ?? t('https://api.example.com');
|
|
35
|
-
const baseUrlDefaultHintTemplate = labels?.baseUrlDefaultHintTemplate ?? t('Uses {url} by default.');
|
|
36
|
-
const baseUrlInvalidLabel = labels?.baseUrlInvalidLabel ??
|
|
37
|
-
t('Enter an absolute http:// or https:// URL that is not a private or internal address.');
|
|
38
|
-
const modelLabel = labels?.modelLabel ?? t('Model');
|
|
39
|
-
const modelPlaceholder = labels?.modelPlaceholder ?? t('Default model');
|
|
40
|
-
const savedWithMaskTemplate = labels?.savedWithMaskTemplate ?? t('Saved ({mask})');
|
|
41
|
-
const unsavedLabel = labels?.unsavedLabel ?? t('Unsaved');
|
|
42
|
-
const clearLabel = labels?.clearLabel ?? t('Clear');
|
|
43
|
-
const saveChangesLabel = labels?.saveChangesLabel ?? t('Save changes');
|
|
44
|
-
const savingLabel = labels?.savingLabel ?? t('Saving…');
|
|
45
|
-
const savedNoticeLabel = labels?.savedNoticeLabel ?? t('Saved.');
|
|
46
|
-
const saveErrorLabel = labels?.saveErrorLabel ?? t('Could not save media providers.');
|
|
24
|
+
const resolved = resolveMediaProvidersTabLabels({ t }, { labels });
|
|
47
25
|
const toggleKeyVisible = (providerId) => {
|
|
48
|
-
setVisibleApiKeys((current) => {
|
|
49
|
-
const next = new Set(current);
|
|
50
|
-
if (next.has(providerId)) {
|
|
51
|
-
next.delete(providerId);
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
next.add(providerId);
|
|
55
|
-
}
|
|
56
|
-
return next;
|
|
57
|
-
});
|
|
26
|
+
setVisibleApiKeys((current) => toggledMember({ set: current, member: providerId }));
|
|
58
27
|
};
|
|
59
|
-
const
|
|
28
|
+
const canonicalPinnedIds = pinnedProviderIds?.map((id) => canonicalMediaProviderId({ id }));
|
|
29
|
+
const orderedCatalog = sortProvidersByConfigured(catalog.map((entry) => ({ ...entry, id: canonicalMediaProviderId({ id: entry.id }) })), providers, canonicalPinnedIds);
|
|
60
30
|
const providerHandles = agentHandle
|
|
61
31
|
? buildAgentListHandles({ prefix: agentHandle, ids: orderedCatalog.map((option) => option.id) })
|
|
62
32
|
: undefined;
|
|
@@ -64,40 +34,38 @@ export function MediaProvidersTab({ port, catalog = DEFAULT_MEDIA_PROVIDER_CATAL
|
|
|
64
34
|
// pinned id absent from `catalog` never appears in `orderedCatalog` either (see
|
|
65
35
|
// `sortProvidersByConfigured`'s own "silently skipped" doc), so this stays correct without
|
|
66
36
|
// re-deriving which pinned ids actually resolved to a real card.
|
|
67
|
-
const pinnedIdSet = new Set(
|
|
37
|
+
const pinnedIdSet = new Set(canonicalPinnedIds ?? []);
|
|
68
38
|
const hasPendingChanges = pendingProviderIds.size > 0;
|
|
69
39
|
// Save writes every provider at once, so ONE unacceptable endpoint blocks the
|
|
70
40
|
// whole button rather than being silently persisted alongside the good ones.
|
|
71
41
|
const blockedByInvalidBaseUrl = invalidBaseUrlProviderIds(providers).length > 0;
|
|
72
|
-
return (_jsxs("section", { className: "jini-settings-section jini-settings-media-providers", children: [
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
autoComplete: "new-password", spellCheck: false, placeholder: saved ? maskedLabel : apiKeyPlaceholder, "aria-label": `${option.label} ${apiKeyLabel}`, value: entry.apiKey ?? '', onChange: (event) => updateProvider(option.id, { apiKey: event.target.value }), ...agentHandleProps({}, { base: providerHandle, ...({ action: 'api-key', role: 'field', label: `${option.label} ${apiKeyLabel}` }) }) }), _jsx("button", { type: "button", className: "jini-input-affix-btn", "aria-pressed": keyVisible, "aria-label": `${option.label} ${keyVisible ? hideKeyLabel : showKeyLabel}`, onClick: () => toggleKeyVisible(option.id), ...agentHandleProps({}, { base: providerHandle, ...({ action: 'reveal-key', role: 'button', label: `${option.label} ${showKeyLabel}` }) }), children: _jsx(Icon, { name: keyVisible ? 'eye-off' : 'eye', size: 14 }) })] }), _jsxs("label", { className: "jini-media-provider-field", children: [_jsx("span", { className: "jini-field-label jini-sr-only", children: baseUrlLabel }), _jsx("input", { className: "jini-input", type: "url", inputMode: "url", spellCheck: false, placeholder: option.defaultBaseUrl || baseUrlPlaceholder, "aria-label": `${option.label} ${baseUrlLabel}`, value: rawBaseUrl, "aria-invalid": baseUrlInvalid || undefined, onChange: (event) => updateProvider(option.id, { baseUrl: event.target.value }), ...agentHandleProps({}, { base: providerHandle, ...({ action: 'base-url', role: 'field', label: `${option.label} ${baseUrlLabel}` }) }) })] }), option.models && option.models.length > 0 ? (_jsxs("label", { className: "jini-media-provider-field", children: [_jsx("span", { className: "jini-field-label jini-sr-only", children: modelLabel }), _jsx("input", { className: "jini-input", list: modelListId, spellCheck: false, placeholder: modelPlaceholder, "aria-label": `${option.label} ${modelLabel}`, value: entry.model ?? '', onChange: (event) => updateProvider(option.id, { model: event.target.value }), ...agentHandleProps({}, { base: providerHandle, ...({ action: 'model', role: 'field', label: `${option.label} ${modelLabel}` }) }) }), _jsx("datalist", { id: modelListId, children: option.models.map((model) => (_jsx("option", { value: model }, model))) })] })) : null, _jsx("button", { type: "button", className: "jini-button jini-button-ghost", disabled: !clearable, "aria-label": `${option.label} ${clearLabel}`, onClick: () => clearProvider(option.id), ...agentHandleProps({}, { base: providerHandle, ...({ action: 'clear', role: 'button', label: `${option.label} ${clearLabel}` }) }), children: clearLabel })] }), baseUrlInvalid ? (_jsx("span", { className: "jini-field-hint jini-hint-error", role: "alert", children: baseUrlInvalidLabel })) : !rawBaseUrl.trim() && effectiveBaseUrl ? (_jsx("span", { className: "jini-field-hint", children: t(baseUrlDefaultHintTemplate, { url: effectiveBaseUrl }) })) : null] })] }, option.id));
|
|
101
|
-
}) }), _jsxs("div", { className: "jini-media-provider-save-row", children: [_jsx("button", { type: "button", className: "jini-button", onClick: saveChanges, disabled: save.status === 'saving' || !hasPendingChanges || blockedByInvalidBaseUrl, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'save', role: 'button', label: saveChangesLabel }) }), children: save.status === 'saving' ? savingLabel : saveChangesLabel }), save.status === 'saved' ? (_jsx("span", { className: "jini-hint", role: "status", children: savedNoticeLabel })) : null, save.status === 'save-error' ? (_jsx("span", { className: "jini-hint jini-hint-error", role: "alert", children: saveErrorLabel })) : null] })] }));
|
|
42
|
+
return (_jsxs("section", { className: "jini-settings-section jini-settings-media-providers", children: [_jsx(TabHead, { labels: resolved, loading: load.status === 'loading', onReload: reload, agentHandle: agentHandle }), load.status === 'unreachable' ? (_jsx("p", { className: "jini-hint jini-hint-error", role: "alert", children: resolved.unreachableLabel })) : null, !hasAnyConfigured ? _jsx("p", { className: "jini-hint", children: resolved.emptyStateLabel }) : null, _jsx("div", { className: "jini-media-provider-list", children: orderedCatalog.map((option, index) => (_jsx(MediaProviderCard, { option: option, entry: providers[option.id] ?? {}, labels: resolved, t: t, pending: pendingProviderIds.has(option.id), keyVisible: visibleApiKeys.has(option.id), showDividerBefore: isDividerBefore({ option, previousOption: orderedCatalog[index - 1], pinnedIdSet }), providerHandle: providerHandles?.[index], onUpdate: (patch) => updateProvider(option.id, patch), onClear: () => clearProvider(option.id), onToggleKeyVisible: () => toggleKeyVisible(option.id) }, option.id))) }), _jsx(SaveRow, { labels: resolved, save: save, disabled: !hasPendingChanges || blockedByInvalidBaseUrl, onSave: saveChanges, agentHandle: agentHandle })] }));
|
|
43
|
+
}
|
|
44
|
+
/** A copy of `set` with `member` added if absent, removed if present. */
|
|
45
|
+
function toggledMember({ set, member }) {
|
|
46
|
+
const next = new Set(set);
|
|
47
|
+
if (next.has(member)) {
|
|
48
|
+
next.delete(member);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
next.add(member);
|
|
52
|
+
}
|
|
53
|
+
return next;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A divider renders once, directly above the first UNpinned card — i.e. exactly at the
|
|
57
|
+
* pinned/rest boundary `sortProvidersByConfigured` produced. Never renders above index 0
|
|
58
|
+
* (nothing pinned, or the pinned card IS index 0 with no boundary yet) and never when
|
|
59
|
+
* every card is pinned (no "rest" to separate from).
|
|
60
|
+
*/
|
|
61
|
+
function isDividerBefore({ option, previousOption, pinnedIdSet, }) {
|
|
62
|
+
return !pinnedIdSet.has(option.id) && previousOption !== undefined && pinnedIdSet.has(previousOption.id);
|
|
63
|
+
}
|
|
64
|
+
function TabHead({ labels, loading, onReload, agentHandle, }) {
|
|
65
|
+
return (_jsxs("div", { className: "jini-section-head", children: [_jsxs("div", { children: [_jsx("h4", { children: labels.title }), _jsx("p", { className: "jini-hint", children: labels.description })] }), _jsxs("button", { type: "button", className: "jini-button jini-button-ghost", onClick: onReload, disabled: loading, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'reload', role: 'button', label: labels.reloadLabel }) }), children: [_jsx(Icon, { name: "refresh", size: 13 }), _jsx("span", { children: loading ? labels.reloadingLabel : labels.reloadLabel })] })] }));
|
|
66
|
+
}
|
|
67
|
+
function SaveRow({ labels, save, disabled, onSave, agentHandle, }) {
|
|
68
|
+
const saving = save.status === 'saving';
|
|
69
|
+
return (_jsxs("div", { className: "jini-media-provider-save-row", children: [_jsx("button", { type: "button", className: "jini-button", onClick: onSave, disabled: saving || disabled, ...agentHandleProps({}, { base: agentHandle, ...({ action: 'save', role: 'button', label: labels.saveChangesLabel }) }), children: saving ? labels.savingLabel : labels.saveChangesLabel }), save.status === 'saved' ? (_jsx("span", { className: "jini-hint", role: "status", children: labels.savedNoticeLabel })) : null, save.status === 'save-error' ? (_jsx("span", { className: "jini-hint jini-hint-error", role: "alert", children: labels.saveErrorLabel })) : null] }));
|
|
102
70
|
}
|
|
103
71
|
//# sourceMappingURL=MediaProvidersTab.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { I18nContextValue } from '../../../i18n/index.js';
|
|
2
|
+
import type { MediaProvidersTabLabels } from './MediaProvidersTab.js';
|
|
3
|
+
/** Every `MediaProvidersTabLabels` field, resolved to the string actually rendered. */
|
|
4
|
+
export type ResolvedMediaProvidersTabLabels = Required<MediaProvidersTabLabels>;
|
|
5
|
+
/** Host-supplied label where given, else the translated English default. */
|
|
6
|
+
export declare function resolveMediaProvidersTabLabels({ t }: {
|
|
7
|
+
t: I18nContextValue['t'];
|
|
8
|
+
}, { labels }?: {
|
|
9
|
+
labels?: MediaProvidersTabLabels | undefined;
|
|
10
|
+
}): ResolvedMediaProvidersTabLabels;
|
|
11
|
+
//# sourceMappingURL=media-providers-tab-labels.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** English defaults, keyed exactly like `MediaProvidersTabLabels`; each is passed through `t`
|
|
2
|
+
* only when the host did not supply that label (the dictionaries are keyed by this English text). */
|
|
3
|
+
const DEFAULT_LABELS = {
|
|
4
|
+
title: 'Media providers',
|
|
5
|
+
description: 'Credentials this host uses to generate images and video.',
|
|
6
|
+
reloadLabel: 'Reload',
|
|
7
|
+
reloadingLabel: 'Reloading…',
|
|
8
|
+
unreachableLabel: 'Could not reach the server. Showing local changes only.',
|
|
9
|
+
emptyStateLabel: 'No media providers configured yet.',
|
|
10
|
+
apiKeyLabel: 'API key',
|
|
11
|
+
apiKeyPlaceholder: 'Paste your API key',
|
|
12
|
+
showKeyLabel: 'Show',
|
|
13
|
+
hideKeyLabel: 'Hide',
|
|
14
|
+
baseUrlLabel: 'Base URL',
|
|
15
|
+
baseUrlPlaceholder: 'https://api.example.com',
|
|
16
|
+
baseUrlDefaultHintTemplate: 'Uses {url} by default.',
|
|
17
|
+
baseUrlInvalidLabel: 'Enter an absolute http:// or https:// URL that is not a private or internal address.',
|
|
18
|
+
modelLabel: 'Model',
|
|
19
|
+
modelPlaceholder: 'Default model',
|
|
20
|
+
savedWithMaskTemplate: 'Saved ({mask})',
|
|
21
|
+
unsavedLabel: 'Unsaved',
|
|
22
|
+
clearLabel: 'Clear',
|
|
23
|
+
saveChangesLabel: 'Save changes',
|
|
24
|
+
savingLabel: 'Saving…',
|
|
25
|
+
savedNoticeLabel: 'Saved.',
|
|
26
|
+
saveErrorLabel: 'Could not save media providers.',
|
|
27
|
+
};
|
|
28
|
+
/** Host-supplied label where given, else the translated English default. */
|
|
29
|
+
export function resolveMediaProvidersTabLabels({ t }, { labels } = {}) {
|
|
30
|
+
const pick = (key) => labels?.[key] ?? t(DEFAULT_LABELS[key]);
|
|
31
|
+
return {
|
|
32
|
+
title: pick('title'),
|
|
33
|
+
description: pick('description'),
|
|
34
|
+
reloadLabel: pick('reloadLabel'),
|
|
35
|
+
reloadingLabel: pick('reloadingLabel'),
|
|
36
|
+
unreachableLabel: pick('unreachableLabel'),
|
|
37
|
+
emptyStateLabel: pick('emptyStateLabel'),
|
|
38
|
+
apiKeyLabel: pick('apiKeyLabel'),
|
|
39
|
+
apiKeyPlaceholder: pick('apiKeyPlaceholder'),
|
|
40
|
+
showKeyLabel: pick('showKeyLabel'),
|
|
41
|
+
hideKeyLabel: pick('hideKeyLabel'),
|
|
42
|
+
baseUrlLabel: pick('baseUrlLabel'),
|
|
43
|
+
baseUrlPlaceholder: pick('baseUrlPlaceholder'),
|
|
44
|
+
baseUrlDefaultHintTemplate: pick('baseUrlDefaultHintTemplate'),
|
|
45
|
+
baseUrlInvalidLabel: pick('baseUrlInvalidLabel'),
|
|
46
|
+
modelLabel: pick('modelLabel'),
|
|
47
|
+
modelPlaceholder: pick('modelPlaceholder'),
|
|
48
|
+
savedWithMaskTemplate: pick('savedWithMaskTemplate'),
|
|
49
|
+
unsavedLabel: pick('unsavedLabel'),
|
|
50
|
+
clearLabel: pick('clearLabel'),
|
|
51
|
+
saveChangesLabel: pick('saveChangesLabel'),
|
|
52
|
+
savingLabel: pick('savingLabel'),
|
|
53
|
+
savedNoticeLabel: pick('savedNoticeLabel'),
|
|
54
|
+
saveErrorLabel: pick('saveErrorLabel'),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=media-providers-tab-labels.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MediaProvidersPort } from '../../ports.js';
|
|
1
|
+
import type { MediaProviderCatalogPolicy, MediaProvidersPort } from '../../ports.js';
|
|
2
2
|
import type { MediaProviderCredentials, MediaProviderMap, MediaProvidersLoadState, MediaProvidersSaveState } from '../../types.js';
|
|
3
3
|
/** The operator-editable fields of a provider entry. Server markers
|
|
4
4
|
* (`apiKeyConfigured`/`apiKeyTail`) are deliberately excluded — they only
|
|
@@ -8,6 +8,8 @@ import type { MediaProviderCredentials, MediaProviderMap, MediaProvidersLoadStat
|
|
|
8
8
|
export type MediaProviderEditPatch = Pick<MediaProviderCredentials, 'apiKey' | 'baseUrl' | 'model'>;
|
|
9
9
|
export interface UseMediaProvidersTabOptions {
|
|
10
10
|
port: MediaProvidersPort;
|
|
11
|
+
/** Identity rules shared by catalog rows, local edits and daemon responses. Stable while mounted. */
|
|
12
|
+
catalogPolicy: MediaProviderCatalogPolicy;
|
|
11
13
|
/** Host-persisted local edits from before this tab mounted (e.g. the
|
|
12
14
|
* host's own locally-cached config, read before the daemon round-trip
|
|
13
15
|
* completes). Defaults to none. Threaded into the initial
|
|
@@ -54,5 +56,5 @@ export interface UseMediaProvidersTabResult {
|
|
|
54
56
|
* `{}` before merging — would make a transient daemon blip read as "the
|
|
55
57
|
* server manages nothing" and silently wipe local edits.
|
|
56
58
|
*/
|
|
57
|
-
export declare function useMediaProvidersTab({ port, initialProviders }: UseMediaProvidersTabOptions): UseMediaProvidersTabResult;
|
|
59
|
+
export declare function useMediaProvidersTab({ port, catalogPolicy, initialProviders }: UseMediaProvidersTabOptions): UseMediaProvidersTabResult;
|
|
58
60
|
//# sourceMappingURL=useMediaProvidersTab.d.ts.map
|
|
@@ -1,6 +1,33 @@
|
|
|
1
|
-
import { normalizeMediaProviderMap, canonicalMediaProviderId } from '@jini-ai/integrations/media-providers/catalog';
|
|
2
1
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
2
|
import { hasAnyConfiguredProvider, isEntryEmpty, mergeDaemonProviders, shouldSyncLocalProvidersToDaemon, } from '../../rules.js';
|
|
3
|
+
/**
|
|
4
|
+
* Releases the tombstones a successful write carried. The server has now accepted a map without
|
|
5
|
+
* these, so the local absence is no longer a claim waiting to be defended — the daemon agrees.
|
|
6
|
+
* Re-touched ids keep their tombstone: the operator changed their mind mid-flight and this
|
|
7
|
+
* response does not describe that decision.
|
|
8
|
+
*/
|
|
9
|
+
function releaseAcknowledgedClears({ cleared, clearsInFlight, touched, }) {
|
|
10
|
+
for (const providerId of clearsInFlight) {
|
|
11
|
+
if (!touched.has(providerId))
|
|
12
|
+
cleared.delete(providerId);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** The server's saved map with every provider touched during the flight reset to local truth —
|
|
16
|
+
* including local deletion (see `flushNow`'s doc comment). Mutates and returns `saved`. */
|
|
17
|
+
function overlayTouchedProviders({ saved, local, touched, }) {
|
|
18
|
+
for (const providerId of touched) {
|
|
19
|
+
const entry = local[providerId];
|
|
20
|
+
if (entry === undefined)
|
|
21
|
+
delete saved[providerId];
|
|
22
|
+
else
|
|
23
|
+
saved[providerId] = entry;
|
|
24
|
+
}
|
|
25
|
+
return saved;
|
|
26
|
+
}
|
|
27
|
+
/** A save rejection as display text; a non-`Error` rejection is stringified rather than read as `.message`. */
|
|
28
|
+
function saveErrorMessage({ error }) {
|
|
29
|
+
return error instanceof Error ? error.message : String(error);
|
|
30
|
+
}
|
|
4
31
|
/**
|
|
5
32
|
* Owns the tab's async edges: the initial daemon fetch (plus the one-time
|
|
6
33
|
* first-upload migration `shouldSyncLocalProvidersToDaemon` guards), manual
|
|
@@ -18,7 +45,8 @@ import { hasAnyConfiguredProvider, isEntryEmpty, mergeDaemonProviders, shouldSyn
|
|
|
18
45
|
* `{}` before merging — would make a transient daemon blip read as "the
|
|
19
46
|
* server manages nothing" and silently wipe local edits.
|
|
20
47
|
*/
|
|
21
|
-
export function useMediaProvidersTab({ port, initialProviders }) {
|
|
48
|
+
export function useMediaProvidersTab({ port, catalogPolicy, initialProviders }) {
|
|
49
|
+
const { normalizeMediaProviderMap, canonicalMediaProviderId } = catalogPolicy;
|
|
22
50
|
const [providers, setProviders] = useState(() => normalizeMediaProviderMap({ providers: initialProviders ?? {} }));
|
|
23
51
|
const [pendingProviderIds, setPendingProviderIds] = useState(() => new Set());
|
|
24
52
|
const [load, setLoad] = useState({ status: 'loading' });
|
|
@@ -107,22 +135,8 @@ export function useMediaProvidersTab({ port, initialProviders }) {
|
|
|
107
135
|
if (!alive.current)
|
|
108
136
|
return { status: 'abandoned' };
|
|
109
137
|
const touched = mutatedSinceSend.current;
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// operator changed their mind mid-flight and this response does not describe that decision.
|
|
113
|
-
for (const providerId of clearsInFlight) {
|
|
114
|
-
if (!touched.has(providerId))
|
|
115
|
-
clearedProviderIds.current.delete(providerId);
|
|
116
|
-
}
|
|
117
|
-
const next = normalizeMediaProviderMap({ providers: saved });
|
|
118
|
-
for (const providerId of touched) {
|
|
119
|
-
const local = providersRef.current[providerId];
|
|
120
|
-
if (local === undefined)
|
|
121
|
-
delete next[providerId];
|
|
122
|
-
else
|
|
123
|
-
next[providerId] = local;
|
|
124
|
-
}
|
|
125
|
-
applyProviders(next);
|
|
138
|
+
releaseAcknowledgedClears({ cleared: clearedProviderIds.current, clearsInFlight, touched });
|
|
139
|
+
applyProviders(overlayTouchedProviders({ saved: normalizeMediaProviderMap({ providers: saved }), local: providersRef.current, touched }));
|
|
126
140
|
// Everything this write sent is now persisted; only edits made during
|
|
127
141
|
// the flight are still unflushed.
|
|
128
142
|
applyPending(new Set([...pendingRef.current].filter((providerId) => touched.has(providerId))));
|
|
@@ -132,10 +146,10 @@ export function useMediaProvidersTab({ port, initialProviders }) {
|
|
|
132
146
|
catch (error) {
|
|
133
147
|
if (!alive.current)
|
|
134
148
|
return { status: 'abandoned' };
|
|
135
|
-
setSave({ status: 'save-error', message:
|
|
149
|
+
setSave({ status: 'save-error', message: saveErrorMessage({ error }) });
|
|
136
150
|
return { status: 'failed' };
|
|
137
151
|
}
|
|
138
|
-
}, [applyProviders, applyPending]);
|
|
152
|
+
}, [applyProviders, applyPending, normalizeMediaProviderMap]);
|
|
139
153
|
/** True while a write is at the port. */
|
|
140
154
|
const writing = useRef(false);
|
|
141
155
|
/** The single coalesced follow-up write requested during that one. */
|
|
@@ -216,7 +230,7 @@ export function useMediaProvidersTab({ port, initialProviders }) {
|
|
|
216
230
|
void persist();
|
|
217
231
|
}
|
|
218
232
|
});
|
|
219
|
-
}, [persist, applyProviders]);
|
|
233
|
+
}, [persist, applyProviders, normalizeMediaProviderMap]);
|
|
220
234
|
useEffect(() => {
|
|
221
235
|
// Mount (or host-supplied port swap): migration is a one-time boot
|
|
222
236
|
// check, matching the origin's App.tsx boot sequence — a manual
|
|
@@ -242,7 +256,7 @@ export function useMediaProvidersTab({ port, initialProviders }) {
|
|
|
242
256
|
if (!currentPending.has(providerId))
|
|
243
257
|
applyPending(new Set(currentPending).add(providerId));
|
|
244
258
|
mutatedSinceSend.current.add(providerId);
|
|
245
|
-
}, [applyProviders, applyPending]);
|
|
259
|
+
}, [applyProviders, applyPending, canonicalMediaProviderId]);
|
|
246
260
|
const clearProvider = useCallback((providerId) => {
|
|
247
261
|
providerId = canonicalMediaProviderId({ id: providerId });
|
|
248
262
|
void (async () => {
|
|
@@ -278,7 +292,7 @@ export function useMediaProvidersTab({ port, initialProviders }) {
|
|
|
278
292
|
return;
|
|
279
293
|
applyProviders({ ...providersRef.current, [providerId]: previousEntry });
|
|
280
294
|
})();
|
|
281
|
-
}, [persist, applyProviders]);
|
|
295
|
+
}, [persist, applyProviders, canonicalMediaProviderId]);
|
|
282
296
|
const saveChanges = useCallback(() => {
|
|
283
297
|
void persist();
|
|
284
298
|
}, [persist]);
|
|
@@ -89,27 +89,34 @@ export function hasAnyConfiguredProvider(providers) {
|
|
|
89
89
|
*/
|
|
90
90
|
export function mergeDaemonProviders(localProviders, daemonProviders, options) {
|
|
91
91
|
const dropped = options?.dropProviderIds;
|
|
92
|
-
const
|
|
93
|
-
? map
|
|
94
|
-
: Object.fromEntries(Object.entries(map).filter(([providerId]) => !dropped.has(providerId)));
|
|
95
|
-
const local = withoutDropped({ ...(localProviders ?? {}) });
|
|
92
|
+
const local = withoutProviderIds({ map: { ...(localProviders ?? {}) } }, { dropped });
|
|
96
93
|
if (daemonProviders == null)
|
|
97
94
|
return local;
|
|
98
95
|
if (!hasAnyConfiguredProvider(daemonProviders)) {
|
|
99
96
|
return Object.fromEntries(Object.entries(local).filter(([, entry]) => !isMarkerOnlyEntry(entry)));
|
|
100
97
|
}
|
|
98
|
+
const preserveIds = options?.preserveLocalProviderIds;
|
|
101
99
|
const merged = { ...local };
|
|
102
100
|
for (const [providerId, daemonEntry] of Object.entries(daemonProviders)) {
|
|
103
|
-
if (!isEntryPresent(daemonEntry))
|
|
101
|
+
if (!isEntryPresent(daemonEntry) || dropped?.has(providerId))
|
|
104
102
|
continue;
|
|
105
|
-
|
|
106
|
-
continue;
|
|
107
|
-
const localEntry = merged[providerId];
|
|
108
|
-
const preserveLocalEdit = Boolean(options?.preserveLocalProviderIds?.has(providerId)) && hasRecoverableFields(localEntry);
|
|
109
|
-
merged[providerId] = preserveLocalEdit ? { ...daemonEntry, ...localEntry } : { ...daemonEntry };
|
|
103
|
+
merged[providerId] = mergeDaemonEntry({ providerId, daemonEntry, localEntry: merged[providerId] }, { preserveIds });
|
|
110
104
|
}
|
|
111
105
|
return merged;
|
|
112
106
|
}
|
|
107
|
+
/** `map` minus every id in `dropped` (see `mergeDaemonProviders`'s `dropProviderIds`). */
|
|
108
|
+
function withoutProviderIds({ map }, { dropped }) {
|
|
109
|
+
if (dropped === undefined || dropped.size === 0)
|
|
110
|
+
return map;
|
|
111
|
+
return Object.fromEntries(Object.entries(map).filter(([providerId]) => !dropped.has(providerId)));
|
|
112
|
+
}
|
|
113
|
+
/** Case 3 of `mergeDaemonProviders` for one provider: the server entry wins
|
|
114
|
+
* unless the caller asked to preserve a local edit that has recoverable
|
|
115
|
+
* fields, which is then layered ON TOP so server-only markers survive. */
|
|
116
|
+
function mergeDaemonEntry({ providerId, daemonEntry, localEntry, }, { preserveIds }) {
|
|
117
|
+
const preserveLocalEdit = Boolean(preserveIds?.has(providerId)) && hasRecoverableFields(localEntry);
|
|
118
|
+
return preserveLocalEdit ? { ...daemonEntry, ...localEntry } : { ...daemonEntry };
|
|
119
|
+
}
|
|
113
120
|
/**
|
|
114
121
|
* Whether local credentials should be pushed to the server.
|
|
115
122
|
*
|