@tai42/studio-sdk 16.0.0 → 16.0.1
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/dist/components/explorer-body.d.ts +49 -0
- package/dist/components/explorer-body.d.ts.map +1 -0
- package/dist/components/explorer-body.js +67 -0
- package/dist/components/explorer-body.js.map +1 -0
- package/dist/components/explorer-page-size.d.ts +4 -0
- package/dist/components/explorer-page-size.d.ts.map +1 -0
- package/dist/components/explorer-page-size.js +36 -0
- package/dist/components/explorer-page-size.js.map +1 -0
- package/dist/components/explorer-tag-filter.d.ts +17 -0
- package/dist/components/explorer-tag-filter.d.ts.map +1 -0
- package/dist/components/explorer-tag-filter.js +29 -0
- package/dist/components/explorer-tag-filter.js.map +1 -0
- package/dist/components/explorer-tags.d.ts +22 -0
- package/dist/components/explorer-tags.d.ts.map +1 -0
- package/dist/components/explorer-tags.js +51 -0
- package/dist/components/explorer-tags.js.map +1 -0
- package/dist/components/explorer-view.d.ts +3 -17
- package/dist/components/explorer-view.d.ts.map +1 -1
- package/dist/components/explorer-view.js +53 -170
- package/dist/components/explorer-view.js.map +1 -1
- package/dist/components/extension-combo-builder.d.ts +0 -5
- package/dist/components/extension-combo-builder.d.ts.map +1 -1
- package/dist/components/extension-combo-builder.js +4 -42
- package/dist/components/extension-combo-builder.js.map +1 -1
- package/dist/components/extension-combo-parts.d.ts +44 -0
- package/dist/components/extension-combo-parts.d.ts.map +1 -0
- package/dist/components/extension-combo-parts.js +57 -0
- package/dist/components/extension-combo-parts.js.map +1 -0
- package/dist/components/fleet-report.d.ts.map +1 -1
- package/dist/components/fleet-report.js +51 -37
- package/dist/components/fleet-report.js.map +1 -1
- package/dist/components/icons/chrome-marks.d.ts +50 -0
- package/dist/components/icons/chrome-marks.d.ts.map +1 -0
- package/dist/components/icons/chrome-marks.js +51 -0
- package/dist/components/icons/chrome-marks.js.map +1 -0
- package/dist/components/icons/icon-frame.d.ts +29 -0
- package/dist/components/icons/icon-frame.d.ts.map +1 -0
- package/dist/components/icons/icon-frame.js +19 -0
- package/dist/components/icons/icon-frame.js.map +1 -0
- package/dist/components/icons/index.d.ts +40 -0
- package/dist/components/icons/index.d.ts.map +1 -0
- package/dist/components/icons/index.js +6 -0
- package/dist/components/icons/index.js.map +1 -0
- package/dist/components/icons/nav-map.d.ts +14 -0
- package/dist/components/icons/nav-map.d.ts.map +1 -0
- package/dist/components/icons/nav-map.js +28 -0
- package/dist/components/icons/nav-map.js.map +1 -0
- package/dist/components/icons/nav-marks.d.ts +44 -0
- package/dist/components/icons/nav-marks.d.ts.map +1 -0
- package/dist/components/icons/nav-marks.js +45 -0
- package/dist/components/icons/nav-marks.js.map +1 -0
- package/dist/components/icons/status-marks.d.ts +21 -0
- package/dist/components/icons/status-marks.d.ts.map +1 -0
- package/dist/components/icons/status-marks.js +22 -0
- package/dist/components/icons/status-marks.js.map +1 -0
- package/dist/components/icons/theme-marks.d.ts +9 -0
- package/dist/components/icons/theme-marks.d.ts.map +1 -0
- package/dist/components/icons/theme-marks.js +10 -0
- package/dist/components/icons/theme-marks.js.map +1 -0
- package/dist/components/json-diff.d.ts.map +1 -1
- package/dist/components/json-diff.js +1 -3
- package/dist/components/json-diff.js.map +1 -1
- package/dist/components/json-tree-context.d.ts +13 -0
- package/dist/components/json-tree-context.d.ts.map +1 -0
- package/dist/components/json-tree-context.js +15 -0
- package/dist/components/json-tree-context.js.map +1 -0
- package/dist/components/json-tree-model.d.ts +56 -0
- package/dist/components/json-tree-model.d.ts.map +1 -0
- package/dist/components/json-tree-model.js +125 -0
- package/dist/components/json-tree-model.js.map +1 -0
- package/dist/components/json-tree-nodes.d.ts +30 -0
- package/dist/components/json-tree-nodes.d.ts.map +1 -0
- package/dist/components/json-tree-nodes.js +118 -0
- package/dist/components/json-tree-nodes.js.map +1 -0
- package/dist/components/json-tree.d.ts.map +1 -1
- package/dist/components/json-tree.js +7 -295
- package/dist/components/json-tree.js.map +1 -1
- package/dist/components/json-value-format.d.ts +20 -0
- package/dist/components/json-value-format.d.ts.map +1 -0
- package/dist/components/json-value-format.js +63 -0
- package/dist/components/json-value-format.js.map +1 -0
- package/dist/components/markdown-blocks.d.ts +38 -0
- package/dist/components/markdown-blocks.d.ts.map +1 -0
- package/dist/components/markdown-blocks.js +168 -0
- package/dist/components/markdown-blocks.js.map +1 -0
- package/dist/components/markdown-inline.d.ts +24 -0
- package/dist/components/markdown-inline.d.ts.map +1 -0
- package/dist/components/markdown-inline.js +257 -0
- package/dist/components/markdown-inline.js.map +1 -0
- package/dist/components/markdown.d.ts +3 -52
- package/dist/components/markdown.d.ts.map +1 -1
- package/dist/components/markdown.js +4 -376
- package/dist/components/markdown.js.map +1 -1
- package/dist/components/observer-registry.d.ts +38 -0
- package/dist/components/observer-registry.d.ts.map +1 -0
- package/dist/components/observer-registry.js +131 -0
- package/dist/components/observer-registry.js.map +1 -0
- package/dist/components/overflow-measure.d.ts +38 -0
- package/dist/components/overflow-measure.d.ts.map +1 -0
- package/dist/components/overflow-measure.js +54 -0
- package/dist/components/overflow-measure.js.map +1 -0
- package/dist/components/prose-regions.d.ts +81 -0
- package/dist/components/prose-regions.d.ts.map +1 -0
- package/dist/components/prose-regions.js +128 -0
- package/dist/components/prose-regions.js.map +1 -0
- package/dist/components/scroll-region.d.ts.map +1 -1
- package/dist/components/scroll-region.js +3 -298
- package/dist/components/scroll-region.js.map +1 -1
- package/dist/components/tab-stop-parse.d.ts +53 -0
- package/dist/components/tab-stop-parse.d.ts.map +1 -0
- package/dist/components/tab-stop-parse.js +202 -0
- package/dist/components/tab-stop-parse.js.map +1 -0
- package/dist/components/templated-text-field.d.ts.map +1 -1
- package/dist/components/templated-text-field.js +10 -148
- package/dist/components/templated-text-field.js.map +1 -1
- package/dist/components/templated-text-kwargs-editor.d.ts +24 -0
- package/dist/components/templated-text-kwargs-editor.d.ts.map +1 -0
- package/dist/components/templated-text-kwargs-editor.js +48 -0
- package/dist/components/templated-text-kwargs-editor.js.map +1 -0
- package/dist/components/templated-text-kwargs.d.ts +37 -0
- package/dist/components/templated-text-kwargs.d.ts.map +1 -0
- package/dist/components/templated-text-kwargs.js +66 -0
- package/dist/components/templated-text-kwargs.js.map +1 -0
- package/dist/components/templated-text-source.d.ts +33 -0
- package/dist/components/templated-text-source.d.ts.map +1 -0
- package/dist/components/templated-text-source.js +53 -0
- package/dist/components/templated-text-source.js.map +1 -0
- package/dist/components/tool-picker-parts.d.ts +39 -0
- package/dist/components/tool-picker-parts.d.ts.map +1 -0
- package/dist/components/tool-picker-parts.js +87 -0
- package/dist/components/tool-picker-parts.js.map +1 -0
- package/dist/components/tool-picker.d.ts +1 -2
- package/dist/components/tool-picker.d.ts.map +1 -1
- package/dist/components/tool-picker.js +4 -66
- package/dist/components/tool-picker.js.map +1 -1
- package/dist/components/version-history-panel.d.ts.map +1 -1
- package/dist/components/version-history-panel.js +7 -34
- package/dist/components/version-history-panel.js.map +1 -1
- package/dist/components/version-history-rows.d.ts +48 -0
- package/dist/components/version-history-rows.d.ts.map +1 -0
- package/dist/components/version-history-rows.js +51 -0
- package/dist/components/version-history-rows.js.map +1 -0
- package/dist/guards.d.ts +6 -0
- package/dist/guards.d.ts.map +1 -0
- package/dist/guards.js +8 -0
- package/dist/guards.js.map +1 -0
- package/dist/hooks/interactions-connection.d.ts +43 -0
- package/dist/hooks/interactions-connection.d.ts.map +1 -0
- package/dist/hooks/interactions-connection.js +171 -0
- package/dist/hooks/interactions-connection.js.map +1 -0
- package/dist/hooks/interactions-stream.d.ts +80 -0
- package/dist/hooks/interactions-stream.d.ts.map +1 -0
- package/dist/hooks/interactions-stream.js +233 -0
- package/dist/hooks/interactions-stream.js.map +1 -0
- package/dist/hooks/useApi.d.ts +2192 -2192
- package/dist/hooks/useApi.d.ts.map +1 -1
- package/dist/hooks/useSse.d.ts +3 -6
- package/dist/hooks/useSse.d.ts.map +1 -1
- package/dist/hooks/useSse.js +38 -451
- package/dist/hooks/useSse.js.map +1 -1
- package/dist/navigation/app-link.d.ts +25 -0
- package/dist/navigation/app-link.d.ts.map +1 -0
- package/dist/navigation/app-link.js +33 -0
- package/dist/navigation/app-link.js.map +1 -0
- package/dist/navigation/context.d.ts +3 -20
- package/dist/navigation/context.d.ts.map +1 -1
- package/dist/navigation/context.js +7 -267
- package/dist/navigation/context.js.map +1 -1
- package/dist/navigation/entry-state.d.ts +23 -0
- package/dist/navigation/entry-state.d.ts.map +1 -0
- package/dist/navigation/entry-state.js +39 -0
- package/dist/navigation/entry-state.js.map +1 -0
- package/dist/navigation/guard-registry.d.ts +30 -0
- package/dist/navigation/guard-registry.d.ts.map +1 -0
- package/dist/navigation/guard-registry.js +46 -0
- package/dist/navigation/guard-registry.js.map +1 -0
- package/dist/navigation/guarded-navigation.d.ts +24 -0
- package/dist/navigation/guarded-navigation.d.ts.map +1 -0
- package/dist/navigation/guarded-navigation.js +102 -0
- package/dist/navigation/guarded-navigation.js.map +1 -0
- package/dist/navigation/index.d.ts +3 -2
- package/dist/navigation/index.d.ts.map +1 -1
- package/dist/navigation/index.js +2 -1
- package/dist/navigation/index.js.map +1 -1
- package/dist/navigation/types.d.ts +8 -11
- package/dist/navigation/types.d.ts.map +1 -1
- package/dist/navigation/use-back-forward-guard.d.ts +5 -0
- package/dist/navigation/use-back-forward-guard.d.ts.map +1 -0
- package/dist/navigation/use-back-forward-guard.js +82 -0
- package/dist/navigation/use-back-forward-guard.js.map +1 -0
- package/dist/plugin/registry.d.ts +6 -15
- package/dist/plugin/registry.d.ts.map +1 -1
- package/dist/plugin/registry.js +83 -75
- package/dist/plugin/registry.js.map +1 -1
- package/dist/schema-form/SecretRefField.d.ts +2 -3
- package/dist/schema-form/SecretRefField.d.ts.map +1 -1
- package/dist/schema-form/SecretRefField.js +23 -12
- package/dist/schema-form/SecretRefField.js.map +1 -1
- package/dist/schema-form/classify.d.ts +1 -122
- package/dist/schema-form/classify.d.ts.map +1 -1
- package/dist/schema-form/classify.js +174 -237
- package/dist/schema-form/classify.js.map +1 -1
- package/dist/schema-form/default-value.d.ts.map +1 -1
- package/dist/schema-form/default-value.js +42 -19
- package/dist/schema-form/default-value.js.map +1 -1
- package/dist/schema-form/enum-field.d.ts +1 -1
- package/dist/schema-form/enum-field.d.ts.map +1 -1
- package/dist/schema-form/field-model.d.ts +121 -0
- package/dist/schema-form/field-model.d.ts.map +1 -0
- package/dist/schema-form/field-model.js +2 -0
- package/dist/schema-form/field-model.js.map +1 -0
- package/dist/schema-form/field-node.d.ts.map +1 -1
- package/dist/schema-form/field-node.js +25 -14
- package/dist/schema-form/field-node.js.map +1 -1
- package/dist/schema-form/media-field.d.ts +1 -1
- package/dist/schema-form/media-field.d.ts.map +1 -1
- package/dist/schema-form/media-field.js +47 -28
- package/dist/schema-form/media-field.js.map +1 -1
- package/dist/schema-form/string-annotations.d.ts +20 -0
- package/dist/schema-form/string-annotations.d.ts.map +1 -0
- package/dist/schema-form/string-annotations.js +105 -0
- package/dist/schema-form/string-annotations.js.map +1 -0
- package/dist/schema-form/string-field.d.ts +1 -1
- package/dist/schema-form/string-field.d.ts.map +1 -1
- package/dist/schema-form/union-field.d.ts +1 -1
- package/dist/schema-form/union-field.d.ts.map +1 -1
- package/dist/schema-form/union-field.js.map +1 -1
- package/dist/schema-form/union.d.ts +1 -1
- package/dist/schema-form/union.d.ts.map +1 -1
- package/dist/schema-form/validate.d.ts.map +1 -1
- package/dist/schema-form/validate.js +126 -105
- package/dist/schema-form/validate.js.map +1 -1
- package/dist/state-binding/AdapterMapping.d.ts.map +1 -1
- package/dist/state-binding/AdapterMapping.js +0 -0
- package/dist/state-binding/AdapterMapping.js.map +1 -1
- package/dist/state-binding/StateAttachRow.d.ts.map +1 -1
- package/dist/state-binding/StateAttachRow.js +52 -36
- package/dist/state-binding/StateAttachRow.js.map +1 -1
- package/dist/state-binding/adapter-mapping-row.d.ts +17 -0
- package/dist/state-binding/adapter-mapping-row.d.ts.map +1 -0
- package/dist/state-binding/adapter-mapping-row.js +87 -0
- package/dist/state-binding/adapter-mapping-row.js.map +1 -0
- package/dist/structured-output/StructuredOutput.d.ts.map +1 -1
- package/dist/structured-output/StructuredOutput.js +1 -3
- package/dist/structured-output/StructuredOutput.js.map +1 -1
- package/package.json +2 -2
- package/src/components/explorer-body.tsx +239 -0
- package/src/components/explorer-page-size.ts +39 -0
- package/src/components/explorer-tag-filter.tsx +80 -0
- package/src/components/explorer-tags.ts +64 -0
- package/src/components/explorer-view.tsx +114 -366
- package/src/components/extension-combo-builder.tsx +35 -152
- package/src/components/extension-combo-parts.tsx +237 -0
- package/src/components/fleet-report.tsx +97 -54
- package/src/components/icons/chrome-marks.tsx +195 -0
- package/src/components/icons/icon-frame.tsx +50 -0
- package/src/components/icons/index.ts +84 -0
- package/src/components/icons/nav-map.ts +55 -0
- package/src/components/icons/nav-marks.tsx +205 -0
- package/src/components/icons/status-marks.tsx +46 -0
- package/src/components/icons/theme-marks.tsx +33 -0
- package/src/components/json-diff.tsx +1 -4
- package/src/components/json-tree-context.ts +26 -0
- package/src/components/json-tree-model.ts +139 -0
- package/src/components/json-tree-nodes.tsx +224 -0
- package/src/components/json-tree.tsx +15 -434
- package/src/components/json-value-format.ts +64 -0
- package/src/components/markdown-blocks.ts +198 -0
- package/src/components/markdown-inline.tsx +365 -0
- package/src/components/markdown.tsx +5 -471
- package/src/components/observer-registry.ts +139 -0
- package/src/components/overflow-measure.ts +69 -0
- package/src/components/prose-regions.ts +153 -0
- package/src/components/scroll-region.tsx +20 -360
- package/src/components/tab-stop-parse.ts +217 -0
- package/src/components/templated-text-field.tsx +38 -330
- package/src/components/templated-text-kwargs-editor.tsx +147 -0
- package/src/components/templated-text-kwargs.ts +92 -0
- package/src/components/templated-text-source.tsx +226 -0
- package/src/components/tool-picker-parts.tsx +184 -0
- package/src/components/tool-picker.tsx +22 -114
- package/src/components/version-history-panel.tsx +38 -142
- package/src/components/version-history-rows.tsx +220 -0
- package/src/guards.ts +7 -0
- package/src/hooks/interactions-connection.ts +227 -0
- package/src/hooks/interactions-stream.ts +274 -0
- package/src/hooks/useSse.ts +50 -477
- package/src/navigation/app-link.tsx +70 -0
- package/src/navigation/context.tsx +11 -330
- package/src/navigation/entry-state.ts +42 -0
- package/src/navigation/guard-registry.ts +65 -0
- package/src/navigation/guarded-navigation.ts +147 -0
- package/src/navigation/index.ts +2 -2
- package/src/navigation/types.ts +8 -11
- package/src/navigation/use-back-forward-guard.ts +90 -0
- package/src/plugin/registry.ts +111 -90
- package/src/schema-form/SecretRefField.tsx +121 -71
- package/src/schema-form/classify.ts +244 -395
- package/src/schema-form/default-value.ts +48 -17
- package/src/schema-form/enum-field.tsx +1 -1
- package/src/schema-form/field-model.ts +133 -0
- package/src/schema-form/field-node.tsx +94 -38
- package/src/schema-form/media-field.tsx +134 -65
- package/src/schema-form/string-annotations.ts +111 -0
- package/src/schema-form/string-field.tsx +1 -1
- package/src/schema-form/union-field.tsx +2 -1
- package/src/schema-form/union.ts +1 -1
- package/src/schema-form/validate.ts +184 -103
- package/src/state-binding/AdapterMapping.tsx +0 -0
- package/src/state-binding/StateAttachRow.tsx +222 -147
- package/src/state-binding/adapter-mapping-row.tsx +232 -0
- package/src/structured-output/StructuredOutput.tsx +1 -4
- package/dist/components/icons.d.ts +0 -160
- package/dist/components/icons.d.ts.map +0 -1
- package/dist/components/icons.js +0 -160
- package/dist/components/icons.js.map +0 -1
- package/src/components/icons.tsx +0 -586
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The render-parameters UI for `TemplatedTextField`: the add/remove editor
|
|
3
|
+
* (`KwargsEditor`) for editable values, and the read-only listing
|
|
4
|
+
* (`ReadOnlyKwargs`) shown beside a read-only stored id.
|
|
5
|
+
*/
|
|
6
|
+
import { useState, type ReactNode } from 'react';
|
|
7
|
+
|
|
8
|
+
import { Button } from './primitives';
|
|
9
|
+
import { TextInput } from './inputs';
|
|
10
|
+
import { CloseIcon } from './icons';
|
|
11
|
+
import type { KwargRow } from './templated-text-kwargs';
|
|
12
|
+
|
|
13
|
+
/** The add/remove kwargs editor, revealed on demand and open when kwargs already exist. */
|
|
14
|
+
export function KwargsEditor({
|
|
15
|
+
label,
|
|
16
|
+
rows,
|
|
17
|
+
disabled,
|
|
18
|
+
onChange,
|
|
19
|
+
}: {
|
|
20
|
+
readonly label: string;
|
|
21
|
+
readonly rows: readonly KwargRow[];
|
|
22
|
+
readonly disabled: boolean;
|
|
23
|
+
readonly onChange: (rows: KwargRow[]) => void;
|
|
24
|
+
}): ReactNode {
|
|
25
|
+
const [open, setOpen] = useState(rows.length > 0);
|
|
26
|
+
const groupLabel = `${label} render parameters`;
|
|
27
|
+
|
|
28
|
+
if (!open) {
|
|
29
|
+
// Wrapped so the ghost button keeps its content width and stays left-aligned
|
|
30
|
+
// in the field's column, marking it as this field's own disclosure rather than
|
|
31
|
+
// a full-width control floating between two fields.
|
|
32
|
+
return (
|
|
33
|
+
<div>
|
|
34
|
+
<Button
|
|
35
|
+
type="button"
|
|
36
|
+
variant="ghost"
|
|
37
|
+
disabled={disabled}
|
|
38
|
+
onClick={() => {
|
|
39
|
+
setOpen(true);
|
|
40
|
+
onChange([...rows, { key: '', value: '' }]);
|
|
41
|
+
}}
|
|
42
|
+
>
|
|
43
|
+
Add render parameters
|
|
44
|
+
</Button>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div
|
|
51
|
+
role="group"
|
|
52
|
+
aria-label={groupLabel}
|
|
53
|
+
style={{ display: 'flex', flexDirection: 'column', gap: 'var(--tai-space-2)' }}
|
|
54
|
+
>
|
|
55
|
+
<span style={{ fontSize: 'var(--tai-text-sm)', color: 'var(--tai-color-text-muted)' }}>
|
|
56
|
+
Render parameters
|
|
57
|
+
</span>
|
|
58
|
+
{rows.map((row, index) => (
|
|
59
|
+
<div
|
|
60
|
+
key={index}
|
|
61
|
+
style={{ display: 'flex', gap: 'var(--tai-space-2)', alignItems: 'center' }}
|
|
62
|
+
>
|
|
63
|
+
<TextInput
|
|
64
|
+
aria-label={`${groupLabel} key ${String(index + 1)}`}
|
|
65
|
+
placeholder="name"
|
|
66
|
+
value={row.key}
|
|
67
|
+
autoComplete="off"
|
|
68
|
+
disabled={disabled}
|
|
69
|
+
onChange={(event) => {
|
|
70
|
+
const next = [...rows];
|
|
71
|
+
next[index] = { ...row, key: event.target.value };
|
|
72
|
+
onChange(next);
|
|
73
|
+
}}
|
|
74
|
+
/>
|
|
75
|
+
<TextInput
|
|
76
|
+
aria-label={`${groupLabel} value ${String(index + 1)}`}
|
|
77
|
+
placeholder="value"
|
|
78
|
+
value={row.value}
|
|
79
|
+
autoComplete="off"
|
|
80
|
+
disabled={disabled}
|
|
81
|
+
onChange={(event) => {
|
|
82
|
+
const next = [...rows];
|
|
83
|
+
next[index] = { ...row, value: event.target.value };
|
|
84
|
+
onChange(next);
|
|
85
|
+
}}
|
|
86
|
+
/>
|
|
87
|
+
<Button
|
|
88
|
+
type="button"
|
|
89
|
+
variant="ghost"
|
|
90
|
+
aria-label={`Remove ${groupLabel} ${String(index + 1)}`}
|
|
91
|
+
disabled={disabled}
|
|
92
|
+
onClick={() => {
|
|
93
|
+
onChange(rows.filter((_, position) => position !== index));
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
<CloseIcon aria-hidden="true" />
|
|
97
|
+
</Button>
|
|
98
|
+
</div>
|
|
99
|
+
))}
|
|
100
|
+
<div>
|
|
101
|
+
<Button
|
|
102
|
+
type="button"
|
|
103
|
+
variant="ghost"
|
|
104
|
+
disabled={disabled}
|
|
105
|
+
onClick={() => {
|
|
106
|
+
onChange([...rows, { key: '', value: '' }]);
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
Add parameter
|
|
110
|
+
</Button>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The render parameters of a read-only stored value, as read-only text — the
|
|
118
|
+
* values in normal weight, NOT the muted disabled inputs a reader could mistake
|
|
119
|
+
* for empty placeholders. Renders nothing when there are no kwargs.
|
|
120
|
+
*/
|
|
121
|
+
export function ReadOnlyKwargs({
|
|
122
|
+
label,
|
|
123
|
+
rows,
|
|
124
|
+
}: {
|
|
125
|
+
readonly label: string;
|
|
126
|
+
readonly rows: readonly KwargRow[];
|
|
127
|
+
}): ReactNode {
|
|
128
|
+
if (rows.length === 0) return null;
|
|
129
|
+
return (
|
|
130
|
+
<div
|
|
131
|
+
role="group"
|
|
132
|
+
aria-label={`${label} render parameters`}
|
|
133
|
+
style={{ display: 'flex', flexDirection: 'column', gap: 'var(--tai-space-1)' }}
|
|
134
|
+
>
|
|
135
|
+
<span style={{ fontSize: 'var(--tai-text-sm)', color: 'var(--tai-color-text-muted)' }}>
|
|
136
|
+
Render parameters
|
|
137
|
+
</span>
|
|
138
|
+
{rows.map((row) => (
|
|
139
|
+
<div key={row.key} style={{ fontFamily: 'var(--tai-font-mono)', wordBreak: 'break-all' }}>
|
|
140
|
+
<code>{row.key}</code>
|
|
141
|
+
<span style={{ color: 'var(--tai-color-text-muted)' }}>: </span>
|
|
142
|
+
<code>{row.value}</code>
|
|
143
|
+
</div>
|
|
144
|
+
))}
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The render-kwargs model behind `TemplatedTextField`: the row shape, the
|
|
3
|
+
* round-trip between stored kwargs and editable rows, and the normalized
|
|
4
|
+
* `TemplatedText` value the field emits. Pure — no React.
|
|
5
|
+
*/
|
|
6
|
+
import type { TemplatedText } from '@tai42/api-client';
|
|
7
|
+
|
|
8
|
+
/** Whether the value's source is inline authored text or a stored template id. */
|
|
9
|
+
export type Mode = 'inline' | 'stored';
|
|
10
|
+
|
|
11
|
+
export interface KwargRow {
|
|
12
|
+
readonly key: string;
|
|
13
|
+
readonly value: string;
|
|
14
|
+
/**
|
|
15
|
+
* The stored value this row was seeded from, kept so an UNTOUCHED row re-emits it
|
|
16
|
+
* BYTE-FOR-BYTE: the row form cannot tell a stored string `"7"` from the number `7`,
|
|
17
|
+
* so re-parsing an untouched row would silently coerce the stored type. `undefined`
|
|
18
|
+
* for a row the author added.
|
|
19
|
+
*/
|
|
20
|
+
readonly original?: unknown;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The row's canonical string form, matched against `value` to detect an untouched row. */
|
|
24
|
+
export function stringifyRowValue(raw: unknown): string {
|
|
25
|
+
return typeof raw === 'string' ? raw : JSON.stringify(raw);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Parse a cell as JSON when it can be (numbers/bools/objects), else keep the string. */
|
|
29
|
+
export function parseCellValue(raw: string): unknown {
|
|
30
|
+
const trimmed = raw.trim();
|
|
31
|
+
if (trimmed.length === 0) return '';
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(trimmed);
|
|
34
|
+
} catch {
|
|
35
|
+
return raw;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Collapse the kwargs rows into an object, dropping rows with a blank key. */
|
|
40
|
+
export function rowsToObject(rows: readonly KwargRow[]): Record<string, unknown> {
|
|
41
|
+
const out: Record<string, unknown> = {};
|
|
42
|
+
for (const row of rows) {
|
|
43
|
+
const key = row.key.trim();
|
|
44
|
+
if (key.length === 0) continue;
|
|
45
|
+
// An untouched seeded row re-emits its stored value verbatim (no JSON-type coerce);
|
|
46
|
+
// an edited or added row is parsed from the cell text.
|
|
47
|
+
out[key] =
|
|
48
|
+
row.original !== undefined && stringifyRowValue(row.original) === row.value
|
|
49
|
+
? row.original
|
|
50
|
+
: parseCellValue(row.value);
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Seed rows from a stored kwargs object (stringifying non-string values). */
|
|
56
|
+
export function objectToRows(kwargs: Record<string, unknown> | undefined): KwargRow[] {
|
|
57
|
+
if (kwargs === undefined) return [];
|
|
58
|
+
return Object.entries(kwargs).map(([key, raw]) => ({
|
|
59
|
+
key,
|
|
60
|
+
value: stringifyRowValue(raw),
|
|
61
|
+
original: raw,
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The initial mode: stored when the seed carries an `id`, inline otherwise. */
|
|
66
|
+
export function initialMode(value: TemplatedText | null): Mode {
|
|
67
|
+
return value?.id !== undefined ? 'stored' : 'inline';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The normalized value for a given source/mode and kwargs. The two sources are
|
|
72
|
+
* mutually exclusive by construction (`content` XOR `id`); an empty source emits
|
|
73
|
+
* `null` unless `required`, in which case an empty source is emitted so the
|
|
74
|
+
* schema guard refuses the save.
|
|
75
|
+
*/
|
|
76
|
+
export function templatedValueFrom(
|
|
77
|
+
mode: Mode,
|
|
78
|
+
inline: string,
|
|
79
|
+
stored: string,
|
|
80
|
+
rows: readonly KwargRow[],
|
|
81
|
+
required: boolean,
|
|
82
|
+
): TemplatedText | null {
|
|
83
|
+
const kwargs = rowsToObject(rows);
|
|
84
|
+
const kw = Object.keys(kwargs).length > 0 ? { kwargs } : {};
|
|
85
|
+
if (mode === 'stored') {
|
|
86
|
+
const id = stored.trim();
|
|
87
|
+
if (id === '') return required ? { id: '', ...kw } : null;
|
|
88
|
+
return { id, ...kw };
|
|
89
|
+
}
|
|
90
|
+
if (inline.trim() === '') return required ? { content: '', ...kw } : null;
|
|
91
|
+
return { content: inline, ...kw };
|
|
92
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The source region of `TemplatedTextField`: the inline/stored toggle and its
|
|
3
|
+
* editors while storage is present, a stand-in while presence is unknown, and the
|
|
4
|
+
* read-only stored reference (never converted, dropped, or blanked) when storage
|
|
5
|
+
* is absent.
|
|
6
|
+
*/
|
|
7
|
+
import type { ReactNode } from 'react';
|
|
8
|
+
|
|
9
|
+
import { ErrorState, Skeleton } from './primitives';
|
|
10
|
+
import { Field } from './field';
|
|
11
|
+
import { RadioGroup } from './radio-group';
|
|
12
|
+
import { Select } from './select';
|
|
13
|
+
import { Textarea } from './inputs';
|
|
14
|
+
import type { Mode } from './templated-text-kwargs';
|
|
15
|
+
import type { TemplatedTextInlineProps, TemplatedTextTemplateOption } from './templated-text-field';
|
|
16
|
+
|
|
17
|
+
/** A labeled read-only reference to a stored template id, plus the note that
|
|
18
|
+
* stored templates are unavailable here — NOT an editable field. */
|
|
19
|
+
function ReadOnlyStoredReference({ id }: { readonly id: string }): ReactNode {
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--tai-space-1)' }}>
|
|
23
|
+
<span style={{ fontSize: 'var(--tai-text-sm)', color: 'var(--tai-color-text-muted)' }}>
|
|
24
|
+
Stored template
|
|
25
|
+
</span>
|
|
26
|
+
<code style={{ fontFamily: 'var(--tai-font-mono)', wordBreak: 'break-all' }}>{id}</code>
|
|
27
|
+
</div>
|
|
28
|
+
<p
|
|
29
|
+
style={{ margin: 0, fontSize: 'var(--tai-text-sm)', color: 'var(--tai-color-text-muted)' }}
|
|
30
|
+
>
|
|
31
|
+
Stored templates are unavailable — this deployment has no storage backend.
|
|
32
|
+
</p>
|
|
33
|
+
</>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface PresentStorageSourceProps {
|
|
38
|
+
readonly label: string;
|
|
39
|
+
readonly mode: Mode;
|
|
40
|
+
readonly disabled: boolean;
|
|
41
|
+
readonly inlineEditor: ReactNode;
|
|
42
|
+
readonly error?: string;
|
|
43
|
+
readonly storedDraft: string;
|
|
44
|
+
readonly templates: readonly TemplatedTextTemplateOption[];
|
|
45
|
+
readonly templateOptions: readonly { value: string; label: string }[];
|
|
46
|
+
readonly templatesLoading: boolean;
|
|
47
|
+
readonly templatesError?: string;
|
|
48
|
+
readonly onTemplatesRetry?: () => void;
|
|
49
|
+
readonly unresolvedId?: string;
|
|
50
|
+
readonly resolveError?: string;
|
|
51
|
+
readonly onModeChange: (mode: Mode) => void;
|
|
52
|
+
readonly onStoredChange: (stored: string) => void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The inline/stored toggle, the mode's editor, and the unresolved-id note, for
|
|
56
|
+
* the storage-present configuration. */
|
|
57
|
+
function PresentStorageSource({
|
|
58
|
+
label,
|
|
59
|
+
mode,
|
|
60
|
+
disabled,
|
|
61
|
+
inlineEditor,
|
|
62
|
+
error,
|
|
63
|
+
storedDraft,
|
|
64
|
+
templates,
|
|
65
|
+
templateOptions,
|
|
66
|
+
templatesLoading,
|
|
67
|
+
templatesError,
|
|
68
|
+
onTemplatesRetry,
|
|
69
|
+
unresolvedId,
|
|
70
|
+
resolveError,
|
|
71
|
+
onModeChange,
|
|
72
|
+
onStoredChange,
|
|
73
|
+
}: PresentStorageSourceProps): ReactNode {
|
|
74
|
+
return (
|
|
75
|
+
<>
|
|
76
|
+
<RadioGroup
|
|
77
|
+
aria-label={`${label} source`}
|
|
78
|
+
variant="segmented"
|
|
79
|
+
value={mode}
|
|
80
|
+
disabled={disabled}
|
|
81
|
+
options={[
|
|
82
|
+
{ value: 'inline', label: 'Inline text' },
|
|
83
|
+
{ value: 'stored', label: 'Stored template' },
|
|
84
|
+
]}
|
|
85
|
+
onValueChange={(next) => {
|
|
86
|
+
onModeChange(next as Mode);
|
|
87
|
+
}}
|
|
88
|
+
/>
|
|
89
|
+
|
|
90
|
+
{mode === 'inline' ? (
|
|
91
|
+
inlineEditor
|
|
92
|
+
) : templatesError !== undefined ? (
|
|
93
|
+
<ErrorState message={templatesError} onRetry={onTemplatesRetry} />
|
|
94
|
+
) : templatesLoading ? (
|
|
95
|
+
<Skeleton height={36} />
|
|
96
|
+
) : (
|
|
97
|
+
<Field label={label} hideLabel error={error}>
|
|
98
|
+
<Select
|
|
99
|
+
placeholder={templates.length === 0 ? 'No templates available' : 'Select a template'}
|
|
100
|
+
value={storedDraft}
|
|
101
|
+
disabled={disabled || templates.length === 0}
|
|
102
|
+
options={templateOptions}
|
|
103
|
+
onValueChange={onStoredChange}
|
|
104
|
+
/>
|
|
105
|
+
</Field>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
{unresolvedId !== undefined ? (
|
|
109
|
+
<p role="alert" style={{ margin: 0, color: 'var(--tai-color-err-text)' }}>
|
|
110
|
+
{resolveError ?? `The stored template "${unresolvedId}" is not available.`}
|
|
111
|
+
</p>
|
|
112
|
+
) : null}
|
|
113
|
+
</>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface TemplatedTextSourceProps {
|
|
118
|
+
readonly label: string;
|
|
119
|
+
readonly mode: Mode;
|
|
120
|
+
readonly inlineDraft: string;
|
|
121
|
+
readonly storedDraft: string;
|
|
122
|
+
readonly error?: string;
|
|
123
|
+
readonly disabled: boolean;
|
|
124
|
+
readonly placeholder?: string;
|
|
125
|
+
readonly renderInline?: (props: TemplatedTextInlineProps) => ReactNode;
|
|
126
|
+
readonly templates: readonly TemplatedTextTemplateOption[];
|
|
127
|
+
readonly templatesLoading: boolean;
|
|
128
|
+
readonly templatesError?: string;
|
|
129
|
+
readonly onTemplatesRetry?: () => void;
|
|
130
|
+
readonly resolveError?: string;
|
|
131
|
+
readonly storageAbsent: boolean;
|
|
132
|
+
readonly storagePresenceLoading: boolean;
|
|
133
|
+
/** The `id` already authored on the value, shown read-only when storage is absent. */
|
|
134
|
+
readonly authoredStoredId?: string;
|
|
135
|
+
readonly onModeChange: (mode: Mode) => void;
|
|
136
|
+
readonly onInlineChange: (value: string) => void;
|
|
137
|
+
readonly onStoredChange: (stored: string) => void;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function TemplatedTextSource({
|
|
141
|
+
label,
|
|
142
|
+
mode,
|
|
143
|
+
inlineDraft,
|
|
144
|
+
storedDraft,
|
|
145
|
+
error,
|
|
146
|
+
disabled,
|
|
147
|
+
placeholder,
|
|
148
|
+
renderInline,
|
|
149
|
+
templates,
|
|
150
|
+
templatesLoading,
|
|
151
|
+
templatesError,
|
|
152
|
+
onTemplatesRetry,
|
|
153
|
+
resolveError,
|
|
154
|
+
storageAbsent,
|
|
155
|
+
storagePresenceLoading,
|
|
156
|
+
authoredStoredId,
|
|
157
|
+
onModeChange,
|
|
158
|
+
onInlineChange,
|
|
159
|
+
onStoredChange,
|
|
160
|
+
}: TemplatedTextSourceProps): ReactNode {
|
|
161
|
+
const templateOptions = templates.map((template) => ({
|
|
162
|
+
value: template.id,
|
|
163
|
+
label: template.label ?? template.id,
|
|
164
|
+
}));
|
|
165
|
+
// A stored id the catalog does not offer cannot be picked from the list; it is
|
|
166
|
+
// shown as an unresolved note rather than silently blanked.
|
|
167
|
+
const unresolvedId =
|
|
168
|
+
mode === 'stored' &&
|
|
169
|
+
storedDraft.trim() !== '' &&
|
|
170
|
+
!templatesLoading &&
|
|
171
|
+
templatesError === undefined &&
|
|
172
|
+
!templates.some((template) => template.id === storedDraft.trim())
|
|
173
|
+
? storedDraft.trim()
|
|
174
|
+
: undefined;
|
|
175
|
+
|
|
176
|
+
// The inline editor, shared by the present-storage inline mode and the
|
|
177
|
+
// absent-storage inline-only rendering: the caller's rich editor when supplied,
|
|
178
|
+
// else a plain textarea.
|
|
179
|
+
const inlineEditor =
|
|
180
|
+
renderInline !== undefined ? (
|
|
181
|
+
renderInline({ label, value: inlineDraft, onChange: onInlineChange, error, hideLabel: true })
|
|
182
|
+
) : (
|
|
183
|
+
<Field label={label} hideLabel error={error}>
|
|
184
|
+
<Textarea
|
|
185
|
+
value={inlineDraft}
|
|
186
|
+
placeholder={placeholder}
|
|
187
|
+
disabled={disabled}
|
|
188
|
+
onChange={(event) => {
|
|
189
|
+
onInlineChange(event.target.value);
|
|
190
|
+
}}
|
|
191
|
+
/>
|
|
192
|
+
</Field>
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// While presence is unknown a placeholder stands in, so the toggle is never
|
|
196
|
+
// shown and then removed. With storage absent the stored source cannot work and
|
|
197
|
+
// is not offered: an inline or empty value edits inline; a value carrying an
|
|
198
|
+
// `id` is shown read-only. With storage present the full toggle and picker render.
|
|
199
|
+
if (storagePresenceLoading) return <Skeleton height={36} />;
|
|
200
|
+
if (storageAbsent) {
|
|
201
|
+
return authoredStoredId !== undefined ? (
|
|
202
|
+
<ReadOnlyStoredReference id={authoredStoredId} />
|
|
203
|
+
) : (
|
|
204
|
+
inlineEditor
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
return (
|
|
208
|
+
<PresentStorageSource
|
|
209
|
+
label={label}
|
|
210
|
+
mode={mode}
|
|
211
|
+
disabled={disabled}
|
|
212
|
+
inlineEditor={inlineEditor}
|
|
213
|
+
error={error}
|
|
214
|
+
storedDraft={storedDraft}
|
|
215
|
+
templates={templates}
|
|
216
|
+
templateOptions={templateOptions}
|
|
217
|
+
templatesLoading={templatesLoading}
|
|
218
|
+
templatesError={templatesError}
|
|
219
|
+
onTemplatesRetry={onTemplatesRetry}
|
|
220
|
+
unresolvedId={unresolvedId}
|
|
221
|
+
resolveError={resolveError}
|
|
222
|
+
onModeChange={onModeChange}
|
|
223
|
+
onStoredChange={onStoredChange}
|
|
224
|
+
/>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pieces of `ToolPicker`: the tool `Select` itself (grouped or flat), the tag
|
|
3
|
+
* filter field, and the selected tool's capability badges. The picker owns the
|
|
4
|
+
* state and composes these; the option-label and grouping helpers live here too.
|
|
5
|
+
*/
|
|
6
|
+
import type { ReactNode } from 'react';
|
|
7
|
+
|
|
8
|
+
import { toolDisplayLabel } from '../hooks/useToolDisplayNames';
|
|
9
|
+
import { Badge } from './badge';
|
|
10
|
+
import { Field } from './field';
|
|
11
|
+
import { Select, type SelectGroup } from './select';
|
|
12
|
+
import { Tooltip } from './tooltip';
|
|
13
|
+
|
|
14
|
+
/** The shared informational note every badge surface carries: declared, never enforced. */
|
|
15
|
+
export const BADGES_NOTE =
|
|
16
|
+
'Declared capability labels — informational only. They describe what the tool touches; the server never enforces them.';
|
|
17
|
+
|
|
18
|
+
const NO_DISPLAY_NAMES: Readonly<Record<string, string>> = {};
|
|
19
|
+
const UNTAGGED = 'Untagged';
|
|
20
|
+
// Radix `Select.Item` forbids an empty-string value, so the "no filter" choice
|
|
21
|
+
// uses a sentinel rather than ''. A tool literally tagged this string would just
|
|
22
|
+
// never be filtered out — harmless for a UI convenience whose authority is the server.
|
|
23
|
+
export const ALL_TAGS = '__all_tags__';
|
|
24
|
+
|
|
25
|
+
/** The group a tool sorts into: its alphabetically-first tag, else "Untagged". */
|
|
26
|
+
function groupFor(name: string, tagsByTool: Readonly<Record<string, readonly string[]>>): string {
|
|
27
|
+
const tags = [...(tagsByTool[name] ?? [])].sort((a, b) => a.localeCompare(b));
|
|
28
|
+
return tags[0] ?? UNTAGGED;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The option label: `Display Name (raw)` when a distinct non-empty display name maps
|
|
33
|
+
* the name, else the bare name; either way suffixed " (agent)" for an agent run tool.
|
|
34
|
+
*/
|
|
35
|
+
function optionLabel(
|
|
36
|
+
name: string,
|
|
37
|
+
displayNames: Readonly<Record<string, string>> | undefined,
|
|
38
|
+
agentToolNames: ReadonlySet<string> | undefined,
|
|
39
|
+
): string {
|
|
40
|
+
const base = toolDisplayLabel(displayNames ?? NO_DISPLAY_NAMES, name);
|
|
41
|
+
return agentToolNames?.has(name) === true ? `${base} (agent)` : base;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Build the grouped option clusters (sorted group labels, "Untagged" last). */
|
|
45
|
+
function buildGroups(
|
|
46
|
+
names: readonly string[],
|
|
47
|
+
tagsByTool: Readonly<Record<string, readonly string[]>>,
|
|
48
|
+
displayNames: Readonly<Record<string, string>> | undefined,
|
|
49
|
+
agentToolNames: ReadonlySet<string> | undefined,
|
|
50
|
+
): SelectGroup[] {
|
|
51
|
+
const byGroup = new Map<string, string[]>();
|
|
52
|
+
for (const name of names) {
|
|
53
|
+
const group = groupFor(name, tagsByTool);
|
|
54
|
+
const bucket = byGroup.get(group) ?? [];
|
|
55
|
+
bucket.push(name);
|
|
56
|
+
byGroup.set(group, bucket);
|
|
57
|
+
}
|
|
58
|
+
return [...byGroup.entries()]
|
|
59
|
+
.sort(([a], [b]) => {
|
|
60
|
+
if (a === UNTAGGED) return 1;
|
|
61
|
+
if (b === UNTAGGED) return -1;
|
|
62
|
+
return a.localeCompare(b);
|
|
63
|
+
})
|
|
64
|
+
.map(([label, groupNames]) => ({
|
|
65
|
+
label,
|
|
66
|
+
options: groupNames.map((name) => ({
|
|
67
|
+
value: name,
|
|
68
|
+
label: optionLabel(name, displayNames, agentToolNames),
|
|
69
|
+
})),
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface ToolSelectProps {
|
|
74
|
+
readonly tagsByTool: Readonly<Record<string, readonly string[]>> | undefined;
|
|
75
|
+
readonly filtered: readonly string[];
|
|
76
|
+
readonly value: string | null;
|
|
77
|
+
readonly onChange: (toolName: string) => void;
|
|
78
|
+
readonly placeholder: string;
|
|
79
|
+
readonly ariaLabel: string | undefined;
|
|
80
|
+
readonly disabled: boolean | undefined;
|
|
81
|
+
readonly displayNames: Readonly<Record<string, string>> | undefined;
|
|
82
|
+
readonly agentToolNames: ReadonlySet<string> | undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The tool select: grouped by tag when `tagsByTool` is present, else a flat list.
|
|
86
|
+
* Either branch is a `Select`, which claims the enclosing `Field`'s control id. */
|
|
87
|
+
export function ToolSelect({
|
|
88
|
+
tagsByTool,
|
|
89
|
+
filtered,
|
|
90
|
+
value,
|
|
91
|
+
onChange,
|
|
92
|
+
placeholder,
|
|
93
|
+
ariaLabel,
|
|
94
|
+
disabled,
|
|
95
|
+
displayNames,
|
|
96
|
+
agentToolNames,
|
|
97
|
+
}: ToolSelectProps): ReactNode {
|
|
98
|
+
if (tagsByTool !== undefined) {
|
|
99
|
+
return (
|
|
100
|
+
<Select
|
|
101
|
+
groups={buildGroups(filtered, tagsByTool, displayNames, agentToolNames)}
|
|
102
|
+
value={value ?? ''}
|
|
103
|
+
onValueChange={onChange}
|
|
104
|
+
placeholder={placeholder}
|
|
105
|
+
aria-label={ariaLabel}
|
|
106
|
+
disabled={disabled}
|
|
107
|
+
/>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return (
|
|
111
|
+
<Select
|
|
112
|
+
// Keep Radix controlled even when the caller's value is null: `''` matches no
|
|
113
|
+
// item (placeholder still shows) and avoids the uncontrolled→controlled warning
|
|
114
|
+
// a subsequent selection would otherwise trigger.
|
|
115
|
+
options={filtered.map((name) => ({
|
|
116
|
+
value: name,
|
|
117
|
+
label: optionLabel(name, displayNames, agentToolNames),
|
|
118
|
+
}))}
|
|
119
|
+
value={value ?? ''}
|
|
120
|
+
onValueChange={onChange}
|
|
121
|
+
placeholder={placeholder}
|
|
122
|
+
aria-label={ariaLabel}
|
|
123
|
+
disabled={disabled}
|
|
124
|
+
/>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The tag filter: an "All tags" sentinel plus one option per distinct tag. */
|
|
129
|
+
export function TagFilterField({
|
|
130
|
+
allTags,
|
|
131
|
+
tagFilter,
|
|
132
|
+
onTagFilterChange,
|
|
133
|
+
disabled,
|
|
134
|
+
}: {
|
|
135
|
+
readonly allTags: readonly string[];
|
|
136
|
+
readonly tagFilter: string;
|
|
137
|
+
readonly onTagFilterChange: (value: string) => void;
|
|
138
|
+
readonly disabled: boolean | undefined;
|
|
139
|
+
}): ReactNode {
|
|
140
|
+
return (
|
|
141
|
+
<Field label="Filter by tag">
|
|
142
|
+
<Select
|
|
143
|
+
options={[
|
|
144
|
+
{ value: ALL_TAGS, label: 'All tags' },
|
|
145
|
+
...allTags.map((tag) => ({ value: tag, label: tag })),
|
|
146
|
+
]}
|
|
147
|
+
value={tagFilter}
|
|
148
|
+
onValueChange={onTagFilterChange}
|
|
149
|
+
disabled={disabled}
|
|
150
|
+
/>
|
|
151
|
+
</Field>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The selected tool's DECLARED capability badges, read-only and informational.
|
|
156
|
+
* Renders nothing unless the selected tool carries badges. */
|
|
157
|
+
export function SelectedToolBadges({
|
|
158
|
+
value,
|
|
159
|
+
badgesByTool,
|
|
160
|
+
idPrefix,
|
|
161
|
+
}: {
|
|
162
|
+
readonly value: string | null;
|
|
163
|
+
readonly badgesByTool: Readonly<Record<string, readonly string[]>> | undefined;
|
|
164
|
+
readonly idPrefix: string;
|
|
165
|
+
}): ReactNode {
|
|
166
|
+
if (value === null || badgesByTool === undefined || (badgesByTool[value]?.length ?? 0) === 0) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
return (
|
|
170
|
+
<Tooltip content={BADGES_NOTE}>
|
|
171
|
+
<span
|
|
172
|
+
className="tai-row"
|
|
173
|
+
data-testid={`${idPrefix}-badges`}
|
|
174
|
+
style={{ gap: 'var(--tai-space-1)' }}
|
|
175
|
+
>
|
|
176
|
+
{(badgesByTool[value] ?? []).map((badge) => (
|
|
177
|
+
<Badge key={badge} variant="neutral">
|
|
178
|
+
{badge}
|
|
179
|
+
</Badge>
|
|
180
|
+
))}
|
|
181
|
+
</span>
|
|
182
|
+
</Tooltip>
|
|
183
|
+
);
|
|
184
|
+
}
|