@jini-ai/admin 0.4.12 → 0.4.13

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 (151) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/agent-plugins/controllers/agent-plugin-files.controller.js +14 -9
  3. package/dist/agent-plugins/controllers/agent-plugins.controller.js +12 -9
  4. package/dist/agent-plugins/package-file-tree.js +54 -51
  5. package/dist/agent-plugins/react/components/AgentPluginRow.d.ts +1 -1
  6. package/dist/agent-plugins/react/components/AgentPluginRow.js +9 -2
  7. package/dist/agent-plugins/react/hooks/AgentPluginInspector.hooks.d.ts +5 -5
  8. package/dist/agent-plugins/react/hooks/AgentPluginInspector.hooks.js +14 -5
  9. package/dist/agent-plugins/react/hooks/AgentPluginPanel.hooks.d.ts +15 -13
  10. package/dist/agent-plugins/react/hooks/AgentPluginPanel.hooks.js +54 -40
  11. package/dist/agent-plugins/react/hooks/AgentPluginsPage.hooks.d.ts +13 -13
  12. package/dist/agent-plugins/react/hooks/AgentPluginsPage.hooks.js +23 -11
  13. package/dist/browser/navigation.js +44 -27
  14. package/dist/comments/adapters/memory.js +30 -12
  15. package/dist/comments/conformance/comments-api.conformance.js +55 -33
  16. package/dist/comments/controllers/comment-queue.controller.js +13 -4
  17. package/dist/comments/react/hooks/use-comment-queue.hooks.d.ts +17 -2
  18. package/dist/comments/react/hooks/use-comment-queue.hooks.js +79 -30
  19. package/dist/comments/react/hooks/use-comment-settings.hooks.js +9 -5
  20. package/dist/comments/react/index.d.ts +2 -1
  21. package/dist/comments/react/index.js +1 -1
  22. package/dist/consent/react/ConsentFields.d.ts +35 -0
  23. package/dist/consent/react/ConsentFields.js +74 -0
  24. package/dist/consent/react/ConsentPreviewFrame.d.ts +14 -0
  25. package/dist/consent/react/ConsentPreviewFrame.js +14 -0
  26. package/dist/consent/react/ConsentSettingsPanel.d.ts +27 -4
  27. package/dist/consent/react/ConsentSettingsPanel.js +6 -17
  28. package/dist/consent/react/index.d.ts +6 -1
  29. package/dist/consent/react/index.js +2 -0
  30. package/dist/consent/react/settings-panel.hooks.d.ts +28 -1
  31. package/dist/consent/react/settings-panel.hooks.js +26 -0
  32. package/dist/core/entities/rules.js +126 -81
  33. package/dist/core/manifest/rules.js +10 -10
  34. package/dist/core/module/create-admin.js +195 -117
  35. package/dist/core/ports/redirects.d.ts +8 -6
  36. package/dist/core/ports/redirects.js +8 -6
  37. package/dist/core/routing/rules.js +9 -4
  38. package/dist/core/transport/http.js +37 -20
  39. package/dist/forms/adapters/memory.js +22 -11
  40. package/dist/forms/conformance/forms-api.conformance.d.ts +6 -4
  41. package/dist/forms/conformance/forms-api.conformance.js +42 -46
  42. package/dist/forms/field-settings.js +150 -115
  43. package/dist/forms/html-rules.js +45 -28
  44. package/dist/forms/react/components/FieldSettingsRows.d.ts +22 -0
  45. package/dist/forms/react/components/FieldSettingsRows.js +29 -0
  46. package/dist/forms/react/components/FieldSettingsSection.d.ts +1 -1
  47. package/dist/forms/react/components/FieldSettingsSection.js +5 -6
  48. package/dist/forms/react/components/FormEditorFieldsBody.d.ts +1 -1
  49. package/dist/forms/react/components/FormEditorFieldsBody.js +4 -15
  50. package/dist/forms/react/components/FormEditorFieldsBodyParts.d.ts +18 -0
  51. package/dist/forms/react/components/FormEditorFieldsBodyParts.js +32 -0
  52. package/dist/forms/react/components/FormSubmissions.d.ts +1 -1
  53. package/dist/forms/react/components/FormSubmissions.js +4 -20
  54. package/dist/forms/react/components/FormSubmissionsTable.d.ts +19 -0
  55. package/dist/forms/react/components/FormSubmissionsTable.js +22 -0
  56. package/dist/media/adapters/memory.js +31 -26
  57. package/dist/media/controllers/edit-media.controller.js +2 -1
  58. package/dist/media/controllers/library.controller.js +48 -35
  59. package/dist/media/controllers/providers.controller.js +2 -1
  60. package/dist/media/html-attributes.js +29 -15
  61. package/dist/media/index.d.ts +1 -1
  62. package/dist/media/index.js +1 -1
  63. package/dist/media/react/components/MediaIcons.d.ts +0 -3
  64. package/dist/media/react/components/MediaIcons.js +0 -9
  65. package/dist/media/react/components/MediaLightbox.d.ts +2 -1
  66. package/dist/media/react/components/MediaLightbox.js +11 -5
  67. package/dist/media/react/hooks/LibraryTab.hooks.d.ts +55 -45
  68. package/dist/media/react/hooks/LibraryTab.hooks.js +107 -70
  69. package/dist/media/react/hooks/MediaCard.hooks.d.ts +27 -31
  70. package/dist/media/react/hooks/MediaCard.hooks.js +52 -26
  71. package/dist/media/react/hooks/MediaGrid.hooks.d.ts +1 -1
  72. package/dist/media/react/hooks/MediaLightbox.hooks.d.ts +10 -14
  73. package/dist/media/react/hooks/MediaLightbox.hooks.js +9 -56
  74. package/dist/media/react/hooks/MediaPage.hooks.d.ts +6 -0
  75. package/dist/media/react/hooks/MediaPage.hooks.js +29 -12
  76. package/dist/media/react/hooks/ProvidersTab.hooks.d.ts +33 -33
  77. package/dist/media/react/hooks/ProvidersTab.hooks.js +119 -79
  78. package/dist/media/react/hooks/UploadButton.hooks.d.ts +7 -5
  79. package/dist/media/react/hooks/UploadButton.hooks.js +36 -24
  80. package/dist/media/react/index.js +4 -0
  81. package/dist/media/react/tabs/LibraryTab.d.ts +1 -1
  82. package/dist/media/react/tabs/LibraryTab.js +2 -2
  83. package/dist/media/rules.d.ts +7 -0
  84. package/dist/media/rules.js +25 -2
  85. package/dist/react/components/ConfirmDialog/ConfirmDialog.js +5 -8
  86. package/dist/react/components/ConfirmDialog/confirm-dialog-attrs.d.ts +20 -0
  87. package/dist/react/components/ConfirmDialog/confirm-dialog-attrs.js +24 -0
  88. package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.js +16 -7
  89. package/dist/react/components/RowMenu/RowMenu.js +16 -10
  90. package/dist/react/components/Sidebar.js +32 -26
  91. package/dist/react/entities/EntityEdit.js +17 -14
  92. package/dist/react/entities/EntityField.d.ts +5 -2
  93. package/dist/react/entities/EntityField.js +38 -23
  94. package/dist/react/entities/EntityList.js +6 -5
  95. package/dist/react/entities/RelationTruncationNotice.d.ts +9 -0
  96. package/dist/react/entities/RelationTruncationNotice.js +9 -0
  97. package/dist/react/entities/use-entity-edit.d.ts +4 -2
  98. package/dist/react/entities/use-entity-edit.js +52 -33
  99. package/dist/react/shell/AdminShell.js +40 -23
  100. package/dist/react/shell/model.d.ts +4 -1
  101. package/dist/react/shell/model.js +3 -2
  102. package/dist/react/shell/use-admin-shell-session.js +25 -26
  103. package/dist/redirects/adapters/memory.js +9 -2
  104. package/dist/redirects/conformance/redirects-api.conformance.js +66 -36
  105. package/dist/redirects/react/hooks/use-import-redirects-form.hooks.d.ts +1 -1
  106. package/dist/redirects/react/hooks/use-import-redirects-form.hooks.js +10 -3
  107. package/dist/redirects/react/hooks/wired.hooks.d.ts +1 -1
  108. package/dist/security/adapters/http.js +25 -10
  109. package/dist/security/adapters/memory.js +19 -5
  110. package/dist/security/controllers/access-tokens.controller.js +27 -26
  111. package/dist/security/controllers/other-credentials.controller.js +4 -2
  112. package/dist/security/controllers/root-key.controller.js +17 -9
  113. package/dist/security/react/hooks/AccessTokensTab.hooks.d.ts +40 -38
  114. package/dist/security/react/hooks/AccessTokensTab.hooks.js +36 -9
  115. package/dist/security/react/hooks/RootKeyTab.hooks.d.ts +14 -14
  116. package/dist/security/react/hooks/RootKeyTab.hooks.js +12 -3
  117. package/dist/security/react/tabs/RootKeyTab.d.ts +1 -1
  118. package/dist/security/react/tabs/RootKeyTab.js +12 -3
  119. package/dist/security/rules.js +45 -25
  120. package/dist/seo/controllers/seo.controller.js +4 -2
  121. package/dist/seo/react/components/MediaRefField.d.ts +1 -1
  122. package/dist/seo/react/components/MediaRefField.js +19 -2
  123. package/dist/seo/react/hooks/MediaRefField.hooks.js +3 -1
  124. package/dist/seo/react/hooks/SeoPage.hooks.d.ts +1 -1
  125. package/dist/seo/react/hooks/use-share-image-warning.hooks.js +48 -34
  126. package/dist/seo/react/pages/SeoPage.d.ts +4 -4
  127. package/dist/seo/react/pages/SeoPage.js +26 -6
  128. package/dist/seo/rules.js +39 -10
  129. package/dist/source-control/conformance/source-control-api.conformance.js +68 -38
  130. package/dist/source-control/controllers/source-control.controller.js +20 -8
  131. package/dist/source-control/react/hooks/ProvidersTab.hooks.d.ts +2 -1
  132. package/dist/source-control/react/hooks/ProvidersTab.hooks.js +18 -11
  133. package/dist/widgets/conformance/widgets-api.conformance.js +15 -1
  134. package/dist/widgets/controllers/instance-editor.controller.js +33 -24
  135. package/dist/widgets/controllers/library.controller.js +18 -12
  136. package/dist/widgets/controllers/region-editor.controller.js +6 -5
  137. package/dist/widgets/react/hooks/page-views.hooks.d.ts +1 -0
  138. package/dist/widgets/react/hooks/page-views.hooks.js +3 -2
  139. package/dist/widgets/react/hooks/use-widget-regions.hooks.d.ts +1 -1
  140. package/dist/widgets/react/hooks/use-widget-regions.hooks.js +9 -3
  141. package/dist/widgets/react/hooks/use-widgets-library.hooks.js +9 -2
  142. package/dist/widgets/react/pages/WidgetInstanceEditor.d.ts +1 -1
  143. package/dist/widgets/react/pages/WidgetInstanceEditor.js +7 -8
  144. package/dist/widgets/rules.d.ts +14 -2
  145. package/dist/widgets/rules.js +9 -6
  146. package/package.json +17 -8
  147. package/src/themes/default/admin.css +51 -0
  148. package/src/themes/default/chat.css +21 -0
  149. package/src/themes/default/manifest.json +29 -0
  150. package/dist/media/styles.d.ts +0 -4
  151. package/dist/media/styles.js +0 -5
@@ -1,9 +1,36 @@
1
1
  import type { ChangeEvent } from 'react';
2
2
  export declare function useProvidersTab(_required?: Record<string, never>, _optional?: Record<string, never>): {
3
+ reload: () => void;
4
+ reloadDisabled: boolean;
5
+ reloadLabel: string;
6
+ unreachable: boolean;
7
+ empty: boolean;
8
+ save: () => void;
9
+ saveLabel: string;
10
+ saveDisabled: boolean;
11
+ saved: boolean;
12
+ saveError: boolean;
13
+ disabled: boolean;
14
+ settingsDisabled: boolean;
3
15
  rows: {
4
- divider: boolean;
5
- statusLabel: string | null;
6
- statusClassName: string;
16
+ invalid: boolean;
17
+ hint: string | null;
18
+ hintClassName: string;
19
+ hintRole: "alert" | undefined;
20
+ ariaInvalid: true | undefined;
21
+ clearLabel: string;
22
+ clearDisabled: boolean;
23
+ clear: () => void;
24
+ baseUrl: string;
25
+ baseUrlPlaceholder: string;
26
+ baseUrlLabel: string;
27
+ onBaseUrl: (event: ChangeEvent<HTMLInputElement>) => void;
28
+ model: string;
29
+ modelLabel: string;
30
+ modelsId: string;
31
+ hasModels: boolean;
32
+ onModel: (event: ChangeEvent<HTMLInputElement>) => void;
33
+ toggle: () => void;
7
34
  credential: string;
8
35
  credentialType: "text" | "password";
9
36
  credentialPlaceholder: string;
@@ -15,41 +42,14 @@ export declare function useProvidersTab(_required?: Record<string, never>, _opti
15
42
  };
16
43
  toggleLabel: string;
17
44
  visible: boolean;
18
- toggle(): void;
19
45
  onCredential: (event: ChangeEvent<HTMLInputElement>) => void;
20
- baseUrl: string;
21
- baseUrlPlaceholder: string;
22
- baseUrlLabel: string;
23
- onBaseUrl: (event: ChangeEvent<HTMLInputElement>) => void;
24
- model: string;
25
- modelLabel: string;
26
- modelsId: string;
27
- hasModels: boolean;
28
- onModel: (event: ChangeEvent<HTMLInputElement>) => void;
29
- clearLabel: string;
30
- clearDisabled: boolean;
31
- clear: () => void;
32
- invalid: boolean;
33
- hint: string | null;
34
- hintClassName: string;
35
- hintRole: "alert" | undefined;
36
- ariaInvalid: true | undefined;
46
+ statusLabel: string | null;
47
+ statusClassName: string;
48
+ divider: boolean;
37
49
  id: string;
38
50
  label: string;
39
51
  defaultBaseUrl?: string;
40
52
  models?: readonly string[];
41
53
  }[];
42
- reload: () => void;
43
- reloadDisabled: boolean;
44
- reloadLabel: string;
45
- unreachable: boolean;
46
- empty: boolean;
47
- save: () => void;
48
- saveLabel: string;
49
- saveDisabled: boolean;
50
- saved: boolean;
51
- saveError: boolean;
52
- disabled: boolean;
53
- settingsDisabled: boolean;
54
54
  };
55
55
  //# sourceMappingURL=ProvidersTab.hooks.d.ts.map
@@ -3,6 +3,12 @@ import { MEDIA_QUERY_KEYS, useMediaProvidersQuery } from './MediaQuery.hooks.js'
3
3
  import { useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { useMediaProvidersTab, sortProvidersByConfigured, isEntryPresent, isMarkerOnlyEntry, maskedKeyLabel, isProviderBaseUrlInvalid, invalidBaseUrlProviderIds } from '@jini-ai/ui';
5
5
  import { useMediaPorts } from './MediaPorts.hooks.js';
6
+ /** Host adapters supply canonical IDs (media/PORT.md); keep them literal in the editor.
7
+ * Pure operations preserve values and copy maps in O(n) time/space; ID lookup is O(1). */
8
+ const catalogPolicy = {
9
+ canonicalMediaProviderId: ({ id }, _options = {}) => id,
10
+ normalizeMediaProviderMap: ({ providers }, _options = {}) => ({ ...providers }),
11
+ };
6
12
  function configuration({ items }, _optional = {}) {
7
13
  return Object.fromEntries(items.filter(item => item.configured || item.apiKeyTail || item.baseUrl?.trim() || item.model?.trim())
8
14
  .map(item => [item.id, { apiKeyConfigured: item.configured,
@@ -10,6 +16,108 @@ function configuration({ items }, _optional = {}) {
10
16
  ...(item.baseUrl === undefined ? {} : { baseUrl: item.baseUrl }),
11
17
  ...(item.model === undefined ? {} : { model: item.model }) }]));
12
18
  }
19
+ /** Drops a cleared provider's stored state; one that never held any needs no call. */
20
+ async function removeIfStored({ port, item, signal }, _optional = {}) {
21
+ if (item.configured || item.baseUrl || item.model)
22
+ await port.removeCredential({ id: item.id }, { signal });
23
+ }
24
+ /** Absent endpoint and model compare and send as blank, the stored "use the default". */
25
+ function settingsOf({ source }, _optional = {}) {
26
+ return { baseUrl: source.baseUrl ?? '', model: source.model ?? '' };
27
+ }
28
+ async function saveProviderSettings({ port, item, entry, signal }, _optional = {}) {
29
+ const next = settingsOf({ source: entry }), stored = settingsOf({ source: item });
30
+ if (next.baseUrl === stored.baseUrl && next.model === stored.model)
31
+ return;
32
+ if (!port.saveSettings)
33
+ throw new Error('Provider settings unavailable');
34
+ await port.saveSettings({ id: item.id, ...next }, { signal });
35
+ }
36
+ async function saveProvider({ port, item, signal }, { entry } = {}) {
37
+ if (!entry)
38
+ return removeIfStored({ port, item, signal });
39
+ await saveProviderSettings({ port, item, entry, signal });
40
+ if (entry.apiKey?.trim())
41
+ await port.saveCredential({ id: item.id, credential: entry.apiKey }, { signal });
42
+ }
43
+ async function saveSequentially({ port, providers, lastRead, signal }, _optional = {}) {
44
+ // Older hosts retain the one-button editor; writes remain sequential and failures
45
+ // retain local edits. Atomic whole-set hosts should implement saveChanges instead.
46
+ // Submitted ids the last read never returned (catalog-only providers getting their
47
+ // first key or settings) start from an empty stored state, or their writes are lost.
48
+ const known = new Set(lastRead.map(item => item.id));
49
+ const unread = Object.keys(providers).filter(id => !known.has(id)).map(id => ({ id, configured: false }));
50
+ for (const item of [...lastRead, ...unread])
51
+ await saveProvider({ port, item, signal }, { entry: providers[item.id] });
52
+ const items = await port.list({}, { signal });
53
+ if (!items)
54
+ throw new Error('Provider service unavailable');
55
+ return items;
56
+ }
57
+ function rowStatus({ entry, pending }, _optional = {}) {
58
+ const saved = isMarkerOnlyEntry(entry);
59
+ const mask = saved ? maskedKeyLabel(entry) : null;
60
+ return {
61
+ mask,
62
+ statusLabel: saved ? `Saved (${mask})` : pending ? 'Unsaved' : null,
63
+ statusClassName: saved ? 'jini-field-status-badge jini-field-status-badge-success' : 'jini-field-status-badge',
64
+ };
65
+ }
66
+ function baseUrlHint({ entry, item }, _optional = {}) {
67
+ const invalid = isProviderBaseUrlInvalid(entry);
68
+ return {
69
+ invalid, hint: invalid ? 'Enter an absolute http:// or https:// URL that is not a private or internal address.'
70
+ : !entry.baseUrl?.trim() && item.defaultBaseUrl ? `Uses ${item.defaultBaseUrl} by default.` : null,
71
+ hintClassName: invalid ? 'jini-field-hint jini-hint-error' : 'jini-field-hint',
72
+ hintRole: invalid ? 'alert' : undefined,
73
+ ariaInvalid: invalid || undefined,
74
+ };
75
+ }
76
+ function credentialFields({ item, entry, editor, visible }, { mask = null } = {}) {
77
+ return {
78
+ credential: entry.apiKey ?? '', credentialType: visible ? 'text' : 'password',
79
+ credentialPlaceholder: mask ?? 'Paste your API key',
80
+ credentialAttrs: { 'data-jini-part': 'media.provider.credential', 'data-agent-private': true,
81
+ 'data-agent-element': `media-provider-${item.id}-api-key`, 'aria-label': `${item.label} credential` },
82
+ toggleLabel: `${item.label} ${visible ? 'Hide' : 'Show'}`, visible,
83
+ onCredential: (event) => editor.updateProvider(item.id, { apiKey: event.currentTarget.value }),
84
+ };
85
+ }
86
+ function settingsFields({ item, entry, editor }, _optional = {}) {
87
+ return {
88
+ baseUrl: entry.baseUrl ?? '', baseUrlPlaceholder: item.defaultBaseUrl || 'https://api.example.com',
89
+ baseUrlLabel: `${item.label} base URL`,
90
+ onBaseUrl: (event) => editor.updateProvider(item.id, { baseUrl: event.currentTarget.value }),
91
+ model: entry.model ?? '', modelLabel: `${item.label} model`, modelsId: `jini-media-provider-models-${item.id}`, hasModels: !!item.models?.length,
92
+ onModel: (event) => editor.updateProvider(item.id, { model: event.currentTarget.value }),
93
+ };
94
+ }
95
+ function providerRow({ item, entry, editor, visible, toggle }, { divider = false, disabled = false } = {}) {
96
+ const { mask, ...status } = rowStatus({ entry, pending: editor.pendingProviderIds.has(item.id) });
97
+ return {
98
+ ...item,
99
+ divider,
100
+ ...status,
101
+ ...credentialFields({ item, entry, editor, visible }, { mask }),
102
+ toggle,
103
+ ...settingsFields({ item, entry, editor }),
104
+ clearLabel: `${item.label} Clear`, clearDisabled: !isEntryPresent(entry) || disabled,
105
+ clear: () => editor.clearProvider(item.id),
106
+ ...baseUrlHint({ entry, item }),
107
+ };
108
+ }
109
+ function editorView({ editor, port }, _optional = {}) {
110
+ const loading = editor.load.status === 'loading', saving = editor.save.status === 'saving';
111
+ return { reload: editor.reload, reloadDisabled: loading,
112
+ reloadLabel: loading ? 'Reloading…' : 'Reload',
113
+ unreachable: editor.load.status === 'unreachable', empty: !editor.hasAnyConfigured,
114
+ save: editor.saveChanges, saveLabel: saving ? 'Saving…' : 'Save changes',
115
+ saveDisabled: !port || saving || !editor.pendingProviderIds.size || !!invalidBaseUrlProviderIds(editor.providers).length,
116
+ saved: editor.save.status === 'saved', saveError: editor.save.status === 'save-error', disabled: !port,
117
+ // Fail closed: a host that can persist neither the whole set nor per-provider settings
118
+ // must not accept endpoint/model edits that would only surface as a failed save.
119
+ settingsDisabled: !port || (!port.saveChanges && !port.saveSettings) };
120
+ }
13
121
  export function useProvidersTab(_required = {}, _optional = {}) {
14
122
  const { mediaProviders } = useMediaPorts();
15
123
  const cached = useMediaProvidersQuery({ port: mediaProviders });
@@ -46,44 +154,15 @@ export function useProvidersTab(_required = {}, _optional = {}) {
46
154
  if (!mediaProviders)
47
155
  throw new Error('Provider service unavailable');
48
156
  const signal = lifetime.current.signal;
49
- if (mediaProviders.saveChanges) {
50
- const items = await mediaProviders.saveChanges({ providers }, { signal });
51
- lastRead = items;
52
- return configuration({ items });
53
- }
54
- // Older hosts retain the one-button editor; writes remain sequential and failures
55
- // retain local edits. Atomic whole-set hosts should implement saveChanges instead.
56
- // Submitted ids the last read never returned (catalog-only providers getting their
57
- // first key or settings) start from an empty stored state, or their writes are lost.
58
- const known = new Set(lastRead.map(item => item.id));
59
- const unread = Object.keys(providers).filter(id => !known.has(id)).map(id => ({ id, configured: false }));
60
- for (const item of [...lastRead, ...unread]) {
61
- const entry = providers[item.id];
62
- if (!entry) {
63
- if (item.configured || item.baseUrl || item.model)
64
- await mediaProviders.removeCredential({ id: item.id }, { signal });
65
- continue;
66
- }
67
- if ((entry.baseUrl ?? '') !== (item.baseUrl ?? '') || (entry.model ?? '') !== (item.model ?? '')) {
68
- if (!mediaProviders.saveSettings)
69
- throw new Error('Provider settings unavailable');
70
- await mediaProviders.saveSettings({ id: item.id, baseUrl: entry.baseUrl ?? '', model: entry.model ?? '' }, { signal });
71
- }
72
- if (entry.apiKey?.trim())
73
- await mediaProviders.saveCredential({ id: item.id, credential: entry.apiKey }, { signal });
74
- }
75
- const items = await mediaProviders.list({}, { signal });
76
- if (!items)
77
- throw new Error('Provider service unavailable');
157
+ const items = mediaProviders.saveChanges
158
+ ? await mediaProviders.saveChanges({ providers }, { signal })
159
+ : await saveSequentially({ port: mediaProviders, providers, lastRead, signal });
78
160
  lastRead = items;
79
161
  return configuration({ items });
80
162
  },
81
163
  };
82
164
  }, [mediaProviders]);
83
- const save = useFetchMutation({ run: async ({ input }) => {
84
- const result = await rawEditorPort.saveMediaProviders(input);
85
- return result;
86
- } }, { invalidates: [MEDIA_QUERY_KEYS.providers] });
165
+ const save = useFetchMutation({ run: ({ input }) => rawEditorPort.saveMediaProviders(input) }, { invalidates: [MEDIA_QUERY_KEYS.providers] });
87
166
  const saves = useRef(save);
88
167
  saves.current = save;
89
168
  const editorPort = useMemo(() => ({
@@ -94,54 +173,15 @@ export function useProvidersTab(_required = {}, _optional = {}) {
94
173
  },
95
174
  saveMediaProviders: (providers) => saves.current.mutate({ input: providers }),
96
175
  }), [rawEditorPort]);
97
- const editor = useMediaProvidersTab({ port: editorPort });
176
+ const editor = useMediaProvidersTab({ port: editorPort, catalogPolicy });
98
177
  const catalog = mediaProviders?.catalog ?? discoveredCatalog;
99
178
  const pinnedIds = mediaProviders?.pinnedProviderIds ?? [];
100
179
  const ordered = sortProvidersByConfigured(catalog, editor.providers, pinnedIds);
101
- const rows = ordered.map((item, index) => {
102
- const entry = editor.providers[item.id] ?? {};
103
- const saved = isMarkerOnlyEntry(entry);
104
- const mask = saved ? maskedKeyLabel(entry) : null;
105
- const visible = visibleKeys.has(item.id);
106
- const invalid = isProviderBaseUrlInvalid(entry);
107
- const modelsId = `jini-media-provider-models-${item.id}`;
108
- return {
109
- ...item,
110
- divider: index > 0 && !pinnedIds.includes(item.id) && pinnedIds.includes(ordered[index - 1].id),
111
- statusLabel: saved ? `Saved (${mask})` : editor.pendingProviderIds.has(item.id) ? 'Unsaved' : null,
112
- statusClassName: saved ? 'jini-field-status-badge jini-field-status-badge-success' : 'jini-field-status-badge',
113
- credential: entry.apiKey ?? '', credentialType: visible ? 'text' : 'password',
114
- credentialPlaceholder: mask ?? 'Paste your API key',
115
- credentialAttrs: { 'data-jini-part': 'media.provider.credential', 'data-agent-private': true,
116
- 'data-agent-element': `media-provider-${item.id}-api-key`, 'aria-label': `${item.label} credential` },
117
- toggleLabel: `${item.label} ${visible ? 'Hide' : 'Show'}`, visible,
118
- toggle() { setVisibleKeys(current => { const next = new Set(current); if (next.has(item.id))
119
- next.delete(item.id);
120
- else
121
- next.add(item.id); return next; }); },
122
- onCredential: (event) => editor.updateProvider(item.id, { apiKey: event.currentTarget.value }),
123
- baseUrl: entry.baseUrl ?? '', baseUrlPlaceholder: item.defaultBaseUrl || 'https://api.example.com',
124
- baseUrlLabel: `${item.label} base URL`,
125
- onBaseUrl: (event) => editor.updateProvider(item.id, { baseUrl: event.currentTarget.value }),
126
- model: entry.model ?? '', modelLabel: `${item.label} model`, modelsId, hasModels: !!item.models?.length,
127
- onModel: (event) => editor.updateProvider(item.id, { model: event.currentTarget.value }),
128
- clearLabel: `${item.label} Clear`, clearDisabled: !isEntryPresent(entry) || !mediaProviders,
129
- clear: () => editor.clearProvider(item.id),
130
- invalid, hint: invalid ? 'Enter an absolute http:// or https:// URL that is not a private or internal address.'
131
- : !entry.baseUrl?.trim() && item.defaultBaseUrl ? `Uses ${item.defaultBaseUrl} by default.` : null,
132
- hintClassName: invalid ? 'jini-field-hint jini-hint-error' : 'jini-field-hint',
133
- hintRole: invalid ? 'alert' : undefined,
134
- ariaInvalid: invalid || undefined,
135
- };
136
- });
137
- return { rows, reload: editor.reload, reloadDisabled: editor.load.status === 'loading',
138
- reloadLabel: editor.load.status === 'loading' ? 'Reloading…' : 'Reload',
139
- unreachable: editor.load.status === 'unreachable', empty: !editor.hasAnyConfigured,
140
- save: editor.saveChanges, saveLabel: editor.save.status === 'saving' ? 'Saving…' : 'Save changes',
141
- saveDisabled: !mediaProviders || editor.save.status === 'saving' || !editor.pendingProviderIds.size || !!invalidBaseUrlProviderIds(editor.providers).length,
142
- saved: editor.save.status === 'saved', saveError: editor.save.status === 'save-error', disabled: !mediaProviders,
143
- // Fail closed: a host that can persist neither the whole set nor per-provider settings
144
- // must not accept endpoint/model edits that would only surface as a failed save.
145
- settingsDisabled: !mediaProviders || (!mediaProviders.saveChanges && !mediaProviders.saveSettings) };
180
+ const toggleKey = (id) => setVisibleKeys(current => { const next = new Set(current); if (next.has(id))
181
+ next.delete(id);
182
+ else
183
+ next.add(id); return next; });
184
+ const rows = ordered.map((item, index) => providerRow({ item, entry: editor.providers[item.id] ?? {}, editor, visible: visibleKeys.has(item.id), toggle: () => toggleKey(item.id) }, { divider: index > 0 && !pinnedIds.includes(item.id) && pinnedIds.includes(ordered[index - 1].id), disabled: !mediaProviders }));
185
+ return { rows, ...editorView({ editor, port: mediaProviders }) };
146
186
  }
147
187
  //# sourceMappingURL=ProvidersTab.hooks.js.map
@@ -18,12 +18,13 @@ export declare function readMediaFile({ file }: {
18
18
  signal?: AbortSignal;
19
19
  createReader?: () => FileReader;
20
20
  }): Promise<UploadInput>;
21
+ type ReadFile = (required: {
22
+ file: File;
23
+ }, optional?: {
24
+ signal?: AbortSignal;
25
+ }) => Promise<UploadInput>;
21
26
  export declare function useUploadButton(props: UploadButtonProps, { readFile }?: {
22
- readFile?: (required: {
23
- file: File;
24
- }, optional?: {
25
- signal?: AbortSignal;
26
- }) => Promise<UploadInput>;
27
+ readFile?: ReadFile;
27
28
  }): {
28
29
  alt: string;
29
30
  withAlt: boolean;
@@ -52,4 +53,5 @@ export declare function useUploadButton(props: UploadButtonProps, { readFile }?:
52
53
  onPress: () => void | undefined;
53
54
  label: string;
54
55
  };
56
+ export {};
55
57
  //# sourceMappingURL=UploadButton.hooks.d.ts.map
@@ -26,6 +26,22 @@ export function readMediaFile({ file }, { signal, createReader = () => new FileR
26
26
  reader.readAsDataURL(file);
27
27
  });
28
28
  }
29
+ /** Uploads files in order, recording each landed name in `uploaded` so a mid-batch failure
30
+ * still reports what succeeded. Resolves false when the toolbar unmounted mid-batch. */
31
+ async function sendFiles({ files, signal, uploaded, alt, upload, readFile }, _optional = {}) {
32
+ const trimmed = alt.trim();
33
+ for (const file of files) {
34
+ const input = await readFile({ file }, { signal });
35
+ if (signal.aborted)
36
+ return false;
37
+ if (!await upload({ input, ...(trimmed ? { alt: trimmed } : {}) }))
38
+ throw new Error(`Upload failed for ${file.name}`);
39
+ if (signal.aborted)
40
+ return false;
41
+ uploaded.push(file.name);
42
+ }
43
+ return true;
44
+ }
29
45
  export function useUploadButton(props, { readFile = readMediaFile } = {}) {
30
46
  // Advisory picker formats mirror the host's standard ceiling, including video. The
31
47
  // injected server still validates/sniffs bytes; a file picker is never a security boundary.
@@ -40,13 +56,27 @@ export function useUploadButton(props, { readFile = readMediaFile } = {}) {
40
56
  lifetime.current = abort;
41
57
  return () => abort.abort();
42
58
  }, []);
59
+ // Multi-select hosts take the whole pick; single-file toolbars keep only the first.
60
+ const limit = (files) => files.slice(0, props.multiple === false ? 1 : undefined);
43
61
  function onFile(event) {
44
62
  if (!inFlight.current && !props.disabled) {
45
- setSelectedFiles(Array.from(event.currentTarget.files ?? []).slice(0, props.multiple === false ? 1 : undefined));
63
+ setSelectedFiles(limit(Array.from(event.currentTarget.files ?? [])));
46
64
  setError(null);
47
65
  setSuccess(null);
48
66
  }
49
67
  }
68
+ function settle({ files, uploaded }, _optional = {}) {
69
+ // Keep only failed/unattempted files: retry must not duplicate successes in a batch.
70
+ setSelectedFiles(files.slice(uploaded.length));
71
+ setBusy(false);
72
+ if (uploaded.length)
73
+ setSuccess(`Uploaded ${uploaded.join(', ')}`);
74
+ }
75
+ function clearForm() {
76
+ setAlt('');
77
+ if (inputRef.current)
78
+ inputRef.current.value = '';
79
+ }
50
80
  async function uploadFiles(files) {
51
81
  if (!files.length || inFlight.current || props.disabled)
52
82
  return;
@@ -56,22 +86,9 @@ export function useUploadButton(props, { readFile = readMediaFile } = {}) {
56
86
  setError(null);
57
87
  setSuccess(null);
58
88
  const uploaded = [];
59
- let remaining = [...files];
60
89
  try {
61
- for (const file of files) {
62
- const input = await readFile({ file }, { signal });
63
- if (signal.aborted)
64
- return;
65
- if (!await props.upload({ input, ...(alt.trim() ? { alt: alt.trim() } : {}) }))
66
- throw new Error(`Upload failed for ${file.name}`);
67
- if (signal.aborted)
68
- return;
69
- uploaded.push(file.name);
70
- remaining = remaining.slice(1);
71
- }
72
- setAlt('');
73
- if (inputRef.current)
74
- inputRef.current.value = '';
90
+ if (await sendFiles({ files, signal, uploaded, alt, upload: props.upload, readFile }))
91
+ clearForm();
75
92
  }
76
93
  catch (error) {
77
94
  if (!signal.aborted)
@@ -79,13 +96,8 @@ export function useUploadButton(props, { readFile = readMediaFile } = {}) {
79
96
  }
80
97
  finally {
81
98
  inFlight.current = false;
82
- if (!signal.aborted) {
83
- // Keep only failed/unattempted files: retry must not duplicate successes in a batch.
84
- setSelectedFiles(remaining);
85
- setBusy(false);
86
- if (uploaded.length)
87
- setSuccess(`Uploaded ${uploaded.join(', ')}`);
88
- }
99
+ if (!signal.aborted)
100
+ settle({ files, uploaded });
89
101
  }
90
102
  }
91
103
  return {
@@ -104,7 +116,7 @@ export function useUploadButton(props, { readFile = readMediaFile } = {}) {
104
116
  event.preventDefault();
105
117
  if (inFlight.current || props.disabled)
106
118
  return;
107
- const files = Array.from(event.dataTransfer.files).slice(0, props.multiple === false ? 1 : undefined);
119
+ const files = limit(Array.from(event.dataTransfer.files));
108
120
  setSelectedFiles(files);
109
121
  void uploadFiles(files);
110
122
  },
@@ -1,3 +1,7 @@
1
+ // Styling: scoped feature presentation used to travel with compiled JS (a `mediaStyles` string in
2
+ // `media/styles.ts`), which also overrode host dialog CSS that could accidentally display a closed
3
+ // dialog or shrink a large media preview. Host CSS now supplies presentation and the closed-dialog
4
+ // safeguards on jini- classes, so no style string ships from this module.
1
5
  export { useGallerySelection } from './hooks/GallerySelection.hooks.js';
2
6
  import { createElement } from 'react';
3
7
  import { bindReact } from '../../core/react/bind-react.js';
@@ -1,4 +1,4 @@
1
1
  import type { TabViewProps } from '../../../core/react/bind-react.js';
2
- export declare function LibraryTab(props: TabViewProps, _optional?: Record<string, never>): import("react").JSX.Element;
2
+ export declare function LibraryTab(props: TabViewProps): import("react").JSX.Element;
3
3
  export default LibraryTab;
4
4
  //# sourceMappingURL=LibraryTab.d.ts.map
@@ -6,9 +6,9 @@ import { UploadButton } from '../components/UploadButton.js';
6
6
  import { EditMediaPanel } from '../components/EditMediaPanel.js';
7
7
  import { MediaLightbox } from '../components/MediaLightbox.js';
8
8
  import { MediaPurgeDialog } from '../components/MediaPurgeDialog.js';
9
- export function LibraryTab(props, _optional = {}) {
9
+ export function LibraryTab(props) {
10
10
  const vm = useLibraryTab(props);
11
- return (_jsxs("section", { className: "jini-media-library", "data-jini-part": "media.library", children: [vm.canUpload && _jsx(UploadButton, { upload: vm.upload, disabled: vm.busy }), _jsx("div", { className: "jini-media-order-control", children: _jsx(Select, { label: "Order by", attrs: { 'data-jini-part': 'media.sort', 'data-agent-element': 'media-order-by' }, value: vm.orderBy, onValueChange: vm.onOrder, options: [{ value: 'created', label: 'Created (newest first)' }, { value: 'alphabetical', label: 'Alphabetical (A–Z)' }] }) }), _jsxs("div", { className: "jini-media-library-filters", hidden: true, children: [_jsx(TextField, { attrs: { 'data-jini-part': 'media.search', 'data-agent-element': 'media-search' }, label: "Search media", value: vm.search, onValueChange: vm.onSearch }), _jsx(Select, { label: "Status", value: vm.statusFilter, onValueChange: vm.onStatus, options: [{ value: 'all', label: 'All statuses' }, { value: 'active', label: 'Active' }, { value: 'trashed', label: 'Trash' }] })] }), _jsx("p", { className: "jini-media-success", role: "status", "aria-live": "polite", "aria-atomic": "true", children: vm.success }), vm.untyped && _jsx("p", { className: "jini-notice", children: vm.t.untyped }), vm.snapshot?.error && _jsxs("div", { className: "jini-notice jini-notice-error", children: [_jsx("p", { className: "jini-notice-text", role: "alert", children: vm.snapshot.error }), _jsx(Button, { onPress: vm.onRetry, children: "Retry" })] }), vm.loading ? (_jsx("p", { className: "jini-notice", role: "status", children: vm.t.loading })) : (_jsx(MediaGrid, { gridRef: vm.gridRef, emptyLabel: vm.emptyLabel, actions: vm.actions, api: vm.mediaApi })), _jsx(MediaLightbox, { items: vm.items, api: vm.mediaApi, activeIndex: vm.lightboxIndex, onNavigate: vm.onNavigate, onClose: vm.closeLightbox }), vm.editing && (_jsx(EditMediaPanel, { api: vm.mediaApi, item: vm.editing, onSaved: vm.saved, onClose: vm.closeEdit }, vm.editing.id)), _jsx(MediaPurgeDialog, { open: !!vm.snapshot?.pendingPurge, pending: vm.busy, ...(vm.snapshot?.pendingPurge ? { filename: vm.snapshot.pendingPurge.title } : {}), onCancel: vm.cancelPurge, onConfirm: vm.confirmPurge })] }));
11
+ return (_jsxs("section", { className: "jini-media-library", "data-jini-part": "media.library", children: [vm.canUpload && _jsx(UploadButton, { upload: vm.upload, disabled: vm.busy }), _jsx("div", { className: "jini-media-order-control", children: _jsx(Select, { label: "Order by", attrs: { 'data-jini-part': 'media.sort', 'data-agent-element': 'media-order-by' }, value: vm.orderBy, onValueChange: vm.onOrder, options: [{ value: 'created', label: 'Created (newest first)' }, { value: 'alphabetical', label: 'Alphabetical (A–Z)' }] }) }), _jsxs("div", { className: "jini-media-library-filters", hidden: true, children: [_jsx(TextField, { attrs: { 'data-jini-part': 'media.search', 'data-agent-element': 'media-search' }, label: "Search media", value: vm.search, onValueChange: vm.onSearch }), _jsx(Select, { label: "Status", value: vm.statusFilter, onValueChange: vm.onStatus, options: [{ value: 'all', label: 'All statuses' }, { value: 'active', label: 'Active' }, { value: 'trashed', label: 'Trash' }] })] }), _jsx("p", { className: "jini-media-success", role: "status", "aria-live": "polite", "aria-atomic": "true", children: vm.success }), vm.untyped && _jsx("p", { className: "jini-notice", children: vm.t.untyped }), vm.snapshot?.error && _jsxs("div", { className: "jini-notice jini-notice-error", children: [_jsx("p", { className: "jini-notice-text", role: "alert", children: vm.snapshot.error }), _jsx(Button, { onPress: vm.onRetry, children: "Retry" })] }), vm.loading ? (_jsx("p", { className: "jini-notice", role: "status", children: vm.t.loading })) : (_jsx(MediaGrid, { gridRef: vm.gridRef, emptyLabel: vm.emptyLabel, actions: vm.actions, api: vm.mediaApi })), _jsx(MediaLightbox, { items: vm.items, api: vm.mediaApi, activeIndex: vm.lightboxIndex, onNavigate: vm.onNavigate, onClose: vm.closeLightbox }), vm.editing && (_jsx(EditMediaPanel, { api: vm.mediaApi, item: vm.editing, onSaved: vm.saved, onClose: vm.closeEdit }, vm.editing.id)), _jsx(MediaPurgeDialog, { ...vm.purgeDialog, onCancel: vm.cancelPurge, onConfirm: vm.confirmPurge })] }));
12
12
  }
13
13
  export default LibraryTab;
14
14
  //# sourceMappingURL=LibraryTab.js.map
@@ -56,6 +56,13 @@ export declare function formatByteSize({ bytes }: {
56
56
  }, { locale }?: {
57
57
  locale?: string;
58
58
  }): string;
59
+ /** The file name a media card shows. A media record keeps only its extension-less title, so the
60
+ * extension comes from the type sniffed from its bytes; an unknown type, or a title that already
61
+ * ends with that extension, shows the title as-is. */
62
+ export declare function mediaFileName({ title, contentType }: {
63
+ title: string;
64
+ contentType: string | null;
65
+ }, _optional?: Record<string, never>): string;
59
66
  /** Full-library counts, independent of search/order/type selection; Providers has no count. */
60
67
  export declare function countMediaTabs({ media }: {
61
68
  media: readonly MediaAsset[];
@@ -75,8 +75,16 @@ export function isRetryableMediaError({ error }, _optional = {}) {
75
75
  const failure = error;
76
76
  if (typeof failure.retryable === 'boolean')
77
77
  return failure.retryable;
78
- const status = failure.status ?? failure.statusCode ?? Number(failure.message?.match(/HTTP\s+(\d{3})/i)?.[1]);
79
- return status === 408 || status === 429 || (status >= 500 && status <= 599) || error instanceof TypeError;
78
+ return isRetryableHttpStatus({ status: mediaErrorHttpStatus({ failure }) }) || error instanceof TypeError;
79
+ }
80
+ /** `status`, else `statusCode`, else the `HTTP nnn` an older transport put in its message —
81
+ * `NaN` when none is present. */
82
+ function mediaErrorHttpStatus({ failure }, _optional = {}) {
83
+ return failure.status ?? failure.statusCode ?? Number(failure.message?.match(/HTTP\s+(\d{3})/i)?.[1]);
84
+ }
85
+ /** Request timeout, rate limiting and any 5xx are temporary; `NaN` and every other status are not. */
86
+ function isRetryableHttpStatus({ status }, _optional = {}) {
87
+ return status === 408 || status === 429 || (status >= 500 && status <= 599);
80
88
  }
81
89
  /** Blank means native size; invalid values reach the server's positive-integer validator. */
82
90
  export function parseOptionalPixelSize({ value }, _optional = {}) {
@@ -94,6 +102,21 @@ export function formatByteSize({ bytes }, { locale = 'en-US' } = {}) {
94
102
  const mb = kb / 1024;
95
103
  return mb < 1024 ? `${decimal.format(mb)} MB` : `${decimal.format(mb / 1024)} GB`;
96
104
  }
105
+ /** The extension each sniffed media type is stored as; JPEG shows as the common ".jpg". */
106
+ const MEDIA_EXTENSION_BY_CONTENT_TYPE = {
107
+ 'image/png': 'png', 'image/jpeg': 'jpg', 'image/gif': 'gif', 'image/webp': 'webp', 'image/avif': 'avif',
108
+ 'image/svg+xml': 'svg', 'video/mp4': 'mp4', 'video/webm': 'webm', 'audio/wav': 'wav', 'audio/mpeg': 'mp3',
109
+ 'audio/flac': 'flac', 'audio/mp4': 'm4a', 'application/pdf': 'pdf',
110
+ };
111
+ /** The file name a media card shows. A media record keeps only its extension-less title, so the
112
+ * extension comes from the type sniffed from its bytes; an unknown type, or a title that already
113
+ * ends with that extension, shows the title as-is. */
114
+ export function mediaFileName({ title, contentType }, _optional = {}) {
115
+ const extension = contentType ? MEDIA_EXTENSION_BY_CONTENT_TYPE[contentType] : undefined;
116
+ if (!extension || title.toLowerCase().endsWith(`.${extension}`))
117
+ return title;
118
+ return `${title}.${extension}`;
119
+ }
97
120
  /** Full-library counts, independent of search/order/type selection; Providers has no count. */
98
121
  export function countMediaTabs({ media }, _optional = {}) {
99
122
  return { all: media.length, images: filterMediaByTab({ media, tab: 'images' }).length,
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { agentHandle } from '@jini-ai/agentic';
3
2
  import { useConfirmController } from '@jini-ai/ui-kit/react';
4
3
  import { resolveTone, toneClassName } from '../../types.js';
5
4
  import { useConfirmDialog, useConfirmDialogCancelLabel } from './ConfirmDialog.hooks.js';
5
+ import { confirmDialogAgentAttrs, withoutAccessibleName } from './confirm-dialog-attrs.js';
6
6
  /**
7
7
  * UI-kit's confirmation planner owns the sub-handle segments appended to the caller's base:
8
8
  * literals chosen by the mechanism, never host data. Neither needs sanitizing, unlike
@@ -39,16 +39,13 @@ export function ConfirmDialog({ useDialog = useConfirmDialog, agentHandle: baseH
39
39
  ...(props.pending === undefined ? {} : { pending: props.pending }),
40
40
  onConfirm: () => props.onConfirm(), onCancel: () => props.onCancel(),
41
41
  }, {
42
- // UI-kit names selects separately; agentic publishes them as fields and still owns handle validation.
43
- agent: ((required, optional = {}) => agentHandle(required, {
44
- ...(optional.role === undefined ? {} : { role: optional.role === 'select' ? 'field' : optional.role }),
45
- ...(optional.label === undefined ? {} : { label: optional.label }),
46
- })),
42
+ // UI-kit names selects separately; see `confirmDialogAgentAttrs`.
43
+ agent: confirmDialogAgentAttrs,
47
44
  });
48
45
  const { titleId, dialogRef, cancelRef, handleNativeCancel, handleBackdropClick } = useDialog({ open: props.open, onCancel: () => { controller.requestDismiss({ reason: 'close' }); }, document }, { pending: controller.pending });
49
46
  // Keep admin's concise accessible names; the shared planner still supplies consequence-aware agent labels.
50
- const { 'aria-label': _cancelName, ...cancelAttrs } = controller.cancel.attrs ?? {};
51
- const { 'aria-label': _confirmName, ...confirmAttrs } = controller.confirm.attrs ?? {};
47
+ const cancelAttrs = withoutAccessibleName({ attrs: controller.cancel.attrs });
48
+ const confirmAttrs = withoutAccessibleName({ attrs: controller.confirm.attrs });
52
49
  return (_jsxs("dialog", { ref: dialogRef, className: "confirm-dialog", "aria-labelledby": titleId, onCancel: handleNativeCancel, onClick: handleBackdropClick, children: [_jsx("h2", { id: titleId, children: props.title }), _jsxs("div", { className: "confirm-dialog-body", children: [props.body, controller.error] }), _jsxs("div", { className: "confirm-dialog-actions", children: [_jsx("button", { ref: cancelRef, type: "button", className: "btn-secondary", disabled: controller.pending, onClick: () => controller.cancel.onPress?.({}), ...cancelAttrs, children: cancelLabel }), _jsx("button", { type: "button", className: toneClassName({ tone }), disabled: controller.pending, onClick: () => controller.confirm.onPress?.({}), ...confirmAttrs, children: props.confirmLabel })] })] }));
53
50
  }
54
51
  //# sourceMappingURL=ConfirmDialog.js.map
@@ -0,0 +1,20 @@
1
+ import type { AgentAttrsPort, KitAttrs } from '@jini-ai/ui-kit';
2
+ /**
3
+ * The agent-attribute port `ConfirmDialog` hands UI-kit's confirm controller. UI-kit names selects
4
+ * separately; agentic publishes them as fields and still owns handle validation. Absent role/label
5
+ * options stay absent rather than being sent as `undefined`.
6
+ *
7
+ * @complexity O(1).
8
+ */
9
+ export declare const confirmDialogAgentAttrs: AgentAttrsPort;
10
+ /**
11
+ * Keep admin's concise accessible names; the shared planner still supplies consequence-aware agent
12
+ * labels. Returns the action's attributes without UI-kit's `aria-label`. UI-kit's `ButtonProps`
13
+ * types `attrs` as optional, so a missing set yields an empty one.
14
+ *
15
+ * @complexity O(k) in the number of attributes.
16
+ */
17
+ export declare function withoutAccessibleName({ attrs }: {
18
+ attrs: KitAttrs | undefined;
19
+ }, _optional?: Record<string, never>): KitAttrs;
20
+ //# sourceMappingURL=confirm-dialog-attrs.d.ts.map
@@ -0,0 +1,24 @@
1
+ import { agentHandle } from '@jini-ai/agentic';
2
+ /**
3
+ * The agent-attribute port `ConfirmDialog` hands UI-kit's confirm controller. UI-kit names selects
4
+ * separately; agentic publishes them as fields and still owns handle validation. Absent role/label
5
+ * options stay absent rather than being sent as `undefined`.
6
+ *
7
+ * @complexity O(1).
8
+ */
9
+ export const confirmDialogAgentAttrs = (required, optional = {}) => agentHandle(required, {
10
+ ...(optional.role === undefined ? {} : { role: optional.role === 'select' ? 'field' : optional.role }),
11
+ ...(optional.label === undefined ? {} : { label: optional.label }),
12
+ });
13
+ /**
14
+ * Keep admin's concise accessible names; the shared planner still supplies consequence-aware agent
15
+ * labels. Returns the action's attributes without UI-kit's `aria-label`. UI-kit's `ButtonProps`
16
+ * types `attrs` as optional, so a missing set yields an empty one.
17
+ *
18
+ * @complexity O(k) in the number of attributes.
19
+ */
20
+ export function withoutAccessibleName({ attrs }, _optional = {}) {
21
+ const { 'aria-label': _accessibleName, ...rest } = attrs ?? {};
22
+ return rest;
23
+ }
24
+ //# sourceMappingURL=confirm-dialog-attrs.js.map