@neuronection/assistant-ui 0.56.0 → 0.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @neuronection/assistant-ui
2
2
 
3
+ ## 0.58.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`255c074`](https://github.com/neuronection/assistant-ui/commit/255c074c16bdbc7cc4d919d9ffea0d0334e0462e) Thanks [@constLiakos](https://github.com/constLiakos)! - `WebSearchProvidersPanel` — the family web-search settings surface
8
+ (ADR-0035 / plan 28): a controlled, presentational ordered multi-provider
9
+ list (enable, reorder, probe-test, edit, delete) with a catalog-driven
10
+ add/edit form. Data contracts mirror the family `providers[]` shape and
11
+ the vendored `search-providers.json` catalog; persistence, key sealing and
12
+ probe calls stay app-side. Study-assistant is the first consumer (its
13
+ local panel is deleted same-commit on adoption); desktop and career follow
14
+ in plan 28 Phases 3–4.
15
+
16
+ ## 0.57.0
17
+
18
+ ### Minor Changes
19
+
20
+ - [`087968c`](https://github.com/neuronection/assistant-ui/commit/087968cbc8b8b2dd477a897c0221ac001550d2db) Thanks [@constLiakos](https://github.com/constLiakos)! - **ModelRegistry: provider context, modern manual toggle, capability implications.** Three additive refinements to the add/edit modal. A discreet **provider context banner** (configured name + base URL, sr-only `providerContextLabel` prefix) sits at the top so the form always answers "model of _what_?". The **manual-id toggle** becomes a labelled ghost control with a pen icon, right-aligned, and swaps direction copy when manual entry is active (`useCatalogLabel`, default 'Pick from the catalog', with a library icon). New `capImplications` prop expresses **selection implications between combinable caps** — e.g. `{ vision: ['text'], tools: ['text'] }`: selecting a modifier auto-enables its text base, and deselecting the base drops everything that implies it (catalog picks normalize the same way). All optional; without them the form behaves exactly as before. Docs updated in the same change.
21
+
3
22
  ## 0.56.0
4
23
 
5
24
  ### Minor Changes
@@ -0,0 +1,270 @@
1
+ import { ConnectionTestRow } from './chunk-O6BRNGXV.js';
2
+ import { Button } from './chunk-4WBBF5LH.js';
3
+ import { cn } from './chunk-RVVXVTH3.js';
4
+ import * as React from 'react';
5
+ import { Plus, ChevronUp, ChevronDown, Pencil, Trash2 } from 'lucide-react';
6
+ import { jsxs, jsx } from 'react/jsx-runtime';
7
+
8
+ var inputClass = "min-w-0 flex-1 rounded-[var(--as-radius-sm)] border border-[var(--as-border)] bg-[var(--as-surface)] px-2 py-1.5 text-sm text-[var(--as-fg)] placeholder:text-[var(--as-muted-fg)] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]";
9
+ var WebSearchProvidersPanel = React.forwardRef(function WebSearchProvidersPanel2({
10
+ providers,
11
+ catalog,
12
+ probes = {},
13
+ testingId = null,
14
+ busy = false,
15
+ labels,
16
+ onSave,
17
+ onDelete,
18
+ onMove,
19
+ onToggle,
20
+ onTest,
21
+ className
22
+ }, ref) {
23
+ const t = React.useMemo(
24
+ () => ({
25
+ title: labels?.title ?? "Web search providers",
26
+ hint: labels?.hint,
27
+ addLabel: labels?.addLabel ?? "Add provider",
28
+ emptyLabel: labels?.emptyLabel ?? "No web search providers yet.",
29
+ nameLabel: labels?.nameLabel ?? "Name (optional)",
30
+ typeLabel: labels?.typeLabel ?? "Provider type",
31
+ baseUrlLabel: labels?.baseUrlLabel ?? "Base URL (optional)",
32
+ baseUrlRequiredLabel: labels?.baseUrlRequiredLabel ?? "Base URL (required)",
33
+ keyLabel: labels?.keyLabel ?? "API key",
34
+ keyOptionalLabel: labels?.keyOptionalLabel ?? "API key (optional)",
35
+ keyStoredLabel: labels?.keyStoredLabel ?? "API key saved",
36
+ noKeyLabel: labels?.noKeyLabel ?? "no key",
37
+ saveLabel: labels?.saveLabel ?? "Save provider",
38
+ cancelLabel: labels?.cancelLabel ?? "Cancel",
39
+ editLabel: labels?.editLabel ?? "Edit provider",
40
+ deleteLabel: labels?.deleteLabel ?? "Delete provider",
41
+ moveUpLabel: labels?.moveUpLabel ?? "Move up",
42
+ moveDownLabel: labels?.moveDownLabel ?? "Move down",
43
+ enabledAriaLabel: labels?.enabledAriaLabel ?? "Enable provider",
44
+ disabledBadge: labels?.disabledBadge ?? "disabled",
45
+ testLabel: labels?.testLabel ?? "Test",
46
+ probeLabels: { ...labels?.probeLabels }
47
+ }),
48
+ [labels]
49
+ );
50
+ const [draft, setDraft] = React.useState(null);
51
+ const openAdd = () => {
52
+ const first = catalog.order[0];
53
+ if (first) setDraft({ type: first, name: "", baseUrl: "", googleCx: "", apiKey: null });
54
+ };
55
+ const openEdit = (provider) => setDraft({
56
+ id: provider.id,
57
+ type: provider.type,
58
+ name: provider.name,
59
+ baseUrl: provider.baseUrl ?? "",
60
+ googleCx: provider.googleCx ?? "",
61
+ apiKey: null
62
+ });
63
+ const draftEntry = draft ? catalog.entries[draft.type] : void 0;
64
+ const draftCxField = draftEntry?.fields?.some((field) => field.id === "google_cx") ?? false;
65
+ const saveDisabled = busy || draftEntry?.baseUrlRequired && !(draft?.baseUrl ?? "").trim() || draft?.id == null && (draftEntry?.requiresKey ?? false) && !(draft?.apiKey ?? "").trim();
66
+ return /* @__PURE__ */ jsxs(
67
+ "div",
68
+ {
69
+ ref,
70
+ "data-as": "web-search-providers",
71
+ className: cn(
72
+ "rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] p-4",
73
+ className
74
+ ),
75
+ children: [
76
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
77
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
78
+ /* @__PURE__ */ jsx("p", { className: "text-base font-medium text-[var(--as-fg)]", children: t.title }),
79
+ t.hint ? /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-[var(--as-muted-fg)]", children: t.hint }) : null
80
+ ] }),
81
+ draft === null ? /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: openAdd, children: [
82
+ /* @__PURE__ */ jsx(Plus, { className: "size-4", "aria-hidden": true }),
83
+ t.addLabel
84
+ ] }) : null
85
+ ] }),
86
+ /* @__PURE__ */ jsx("ul", { className: "mt-3 space-y-2", "data-testid": "wsp-rows", children: providers.map((provider, index) => {
87
+ const probe = probes[provider.id];
88
+ const isTesting = testingId === provider.id;
89
+ const showProbeRow = probe != null || isTesting;
90
+ const probeStatus = !showProbeRow ? null : isTesting ? "testing" : probe.status === "ok" ? "ok" : "fail";
91
+ const probeText = probe && probe.status !== "ok" && probe.error ? probe.error : probe ? t.probeLabels[probe.status] ?? probe.status : null;
92
+ return /* @__PURE__ */ jsxs(
93
+ "li",
94
+ {
95
+ "data-testid": `wsp-row-${provider.id}`,
96
+ className: "border-border border-[var(--as-border)] space-y-1 rounded-[var(--as-radius-sm)] border p-3",
97
+ children: [
98
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
99
+ /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm font-medium text-[var(--as-fg)]", children: [
100
+ /* @__PURE__ */ jsx(
101
+ "input",
102
+ {
103
+ type: "checkbox",
104
+ checked: provider.enabled,
105
+ "aria-label": `${t.enabledAriaLabel}: ${provider.name}`,
106
+ onChange: (event) => onToggle(provider.id, event.target.checked)
107
+ }
108
+ ),
109
+ provider.name
110
+ ] }),
111
+ /* @__PURE__ */ jsx("span", { className: "rounded-full bg-[var(--as-secondary)] px-2 py-0.5 text-[11px] text-[var(--as-muted-fg)]", children: catalog.entries[provider.type]?.label ?? provider.type }),
112
+ provider.baseUrl ? /* @__PURE__ */ jsx("span", { className: "truncate font-mono text-xs text-[var(--as-muted-fg)]", children: provider.baseUrl }) : null,
113
+ /* @__PURE__ */ jsx("span", { className: "ml-auto text-xs text-[var(--as-muted-fg)]", children: provider.keySet ? provider.keyHint ?? t.keyStoredLabel : t.noKeyLabel })
114
+ ] }),
115
+ showProbeRow ? /* @__PURE__ */ jsx(
116
+ ConnectionTestRow,
117
+ {
118
+ variant: "inline",
119
+ status: probeStatus ?? "idle",
120
+ latencyMs: probe?.latencyMs ?? null,
121
+ label: catalog.entries[provider.type]?.label ?? provider.type,
122
+ testLabel: t.testLabel,
123
+ okLabel: t.probeLabels.ok ?? probe?.status ?? "ok",
124
+ failLabel: probeText ?? t.probeLabels.error ?? probe?.status ?? "failed",
125
+ errorMessage: probe && probe.status === "ok" ? null : probe?.error ?? null
126
+ }
127
+ ) : null,
128
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [
129
+ /* @__PURE__ */ jsx(
130
+ Button,
131
+ {
132
+ variant: "outline",
133
+ size: "sm",
134
+ disabled: busy || testingId === provider.id,
135
+ onClick: () => onTest(provider.id),
136
+ children: t.testLabel
137
+ }
138
+ ),
139
+ /* @__PURE__ */ jsx(
140
+ Button,
141
+ {
142
+ variant: "ghost",
143
+ size: "icon",
144
+ "aria-label": `${t.moveUpLabel}: ${provider.name}`,
145
+ disabled: index === 0 || busy,
146
+ onClick: () => onMove(provider.id, "up"),
147
+ children: /* @__PURE__ */ jsx(ChevronUp, { className: "size-4", "aria-hidden": true })
148
+ }
149
+ ),
150
+ /* @__PURE__ */ jsx(
151
+ Button,
152
+ {
153
+ variant: "ghost",
154
+ size: "icon",
155
+ "aria-label": `${t.moveDownLabel}: ${provider.name}`,
156
+ disabled: index === providers.length - 1 || busy,
157
+ onClick: () => onMove(provider.id, "down"),
158
+ children: /* @__PURE__ */ jsx(ChevronDown, { className: "size-4", "aria-hidden": true })
159
+ }
160
+ ),
161
+ /* @__PURE__ */ jsx(
162
+ Button,
163
+ {
164
+ variant: "ghost",
165
+ size: "icon",
166
+ "aria-label": `${t.editLabel}: ${provider.name}`,
167
+ disabled: busy,
168
+ onClick: () => openEdit(provider),
169
+ children: /* @__PURE__ */ jsx(Pencil, { className: "size-4", "aria-hidden": true })
170
+ }
171
+ ),
172
+ /* @__PURE__ */ jsx(
173
+ Button,
174
+ {
175
+ variant: "ghost",
176
+ size: "icon",
177
+ "aria-label": `${t.deleteLabel}: ${provider.name}`,
178
+ disabled: busy,
179
+ onClick: () => onDelete(provider.id),
180
+ children: /* @__PURE__ */ jsx(Trash2, { className: "size-4", "aria-hidden": true })
181
+ }
182
+ ),
183
+ !provider.enabled ? /* @__PURE__ */ jsx("span", { className: "text-xs text-[var(--as-muted-fg)]", children: t.disabledBadge }) : null
184
+ ] })
185
+ ]
186
+ },
187
+ provider.id
188
+ );
189
+ }) }),
190
+ providers.length === 0 && draft === null ? /* @__PURE__ */ jsx("p", { className: "py-4 text-center text-sm text-[var(--as-muted-fg)]", "data-testid": "wsp-empty", children: t.emptyLabel }) : null,
191
+ draft !== null ? /* @__PURE__ */ jsxs(
192
+ "form",
193
+ {
194
+ "data-testid": "wsp-form",
195
+ className: "mt-3 space-y-2 rounded-[var(--as-radius-sm)] border border-[var(--as-border)] p-3",
196
+ onSubmit: (event) => {
197
+ event.preventDefault();
198
+ onSave(draft);
199
+ },
200
+ children: [
201
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
202
+ /* @__PURE__ */ jsx(
203
+ "select",
204
+ {
205
+ className: inputClass,
206
+ value: draft.type,
207
+ disabled: draft.id != null || busy,
208
+ "aria-label": t.typeLabel,
209
+ onChange: (event) => setDraft({ ...draft, type: event.target.value, googleCx: "" }),
210
+ children: catalog.order.map((type) => /* @__PURE__ */ jsx("option", { value: type, children: catalog.entries[type]?.label ?? type }, type))
211
+ }
212
+ ),
213
+ /* @__PURE__ */ jsx(
214
+ "input",
215
+ {
216
+ className: inputClass,
217
+ placeholder: t.nameLabel,
218
+ value: draft.name ?? "",
219
+ onChange: (event) => setDraft({ ...draft, name: event.target.value })
220
+ }
221
+ )
222
+ ] }),
223
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
224
+ /* @__PURE__ */ jsx(
225
+ "input",
226
+ {
227
+ className: inputClass,
228
+ placeholder: draftEntry?.baseUrlRequired ? t.baseUrlRequiredLabel : draftEntry?.baseUrlHint ?? t.baseUrlLabel,
229
+ value: draft.baseUrl ?? "",
230
+ onChange: (event) => setDraft({ ...draft, baseUrl: event.target.value })
231
+ }
232
+ ),
233
+ draftCxField ? /* @__PURE__ */ jsx(
234
+ "input",
235
+ {
236
+ className: cn(inputClass, "w-48 flex-none"),
237
+ placeholder: draftEntry?.fields?.find((f) => f.id === "google_cx")?.placeholder ?? "",
238
+ "aria-label": draftEntry?.fields?.find((f) => f.id === "google_cx")?.label ?? "Engine id",
239
+ value: draft.googleCx ?? "",
240
+ onChange: (event) => setDraft({ ...draft, googleCx: event.target.value })
241
+ }
242
+ ) : null,
243
+ /* @__PURE__ */ jsx(
244
+ "input",
245
+ {
246
+ type: "password",
247
+ className: cn(inputClass, "w-48 flex-none"),
248
+ placeholder: draft.id != null ? t.keyStoredLabel : draftEntry?.requiresKey ? t.keyLabel : t.keyOptionalLabel,
249
+ "aria-label": t.keyLabel,
250
+ value: draft.apiKey ?? "",
251
+ onChange: (event) => setDraft({ ...draft, apiKey: event.target.value })
252
+ }
253
+ )
254
+ ] }),
255
+ draftEntry?.notes ? /* @__PURE__ */ jsx("p", { className: "text-[11px] text-[var(--as-muted-fg)]", children: draftEntry.notes }) : null,
256
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
257
+ /* @__PURE__ */ jsx(Button, { size: "sm", type: "submit", disabled: saveDisabled, children: t.saveLabel }),
258
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", type: "button", onClick: () => setDraft(null), children: t.cancelLabel })
259
+ ] })
260
+ ]
261
+ }
262
+ ) : null
263
+ ]
264
+ }
265
+ );
266
+ });
267
+
268
+ export { WebSearchProvidersPanel };
269
+ //# sourceMappingURL=chunk-3Y6QOTVA.js.map
270
+ //# sourceMappingURL=chunk-3Y6QOTVA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/web-search-providers/WebSearchProvidersPanel.tsx"],"names":["WebSearchProvidersPanel"],"mappings":"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","file":"chunk-3Y6QOTVA.js","sourcesContent":["import * as React from 'react'\nimport { ChevronDown, ChevronUp, Pencil, Plus, Trash2 } from 'lucide-react'\nimport { cn } from '../../lib/utils'\nimport { Button } from '../button/Button'\nimport { ConnectionTestRow, type ConnectionTestStatus } from '../connection-test-row/ConnectionTestRow'\n\nexport type WebSearchProbeStatus =\n | 'ok'\n | 'unreachable'\n | 'auth'\n | 'rate_limit'\n | 'json_disabled'\n | 'error'\n\nexport interface WebSearchProviderView {\n id: string\n name: string\n type: string\n enabled: boolean\n baseUrl?: string | null\n googleCx?: string | null\n keyHint?: string | null\n keySet: boolean\n}\n\nexport interface WebSearchProviderCatalogField {\n id: string\n label: string\n required: boolean\n placeholder?: string\n}\n\nexport interface WebSearchProviderCatalogEntry {\n label: string\n requiresKey: boolean\n keyUrl?: string | null\n baseUrl?: string | null\n baseUrlRequired: boolean\n baseUrlHint?: string | null\n notes?: string | null\n fields?: WebSearchProviderCatalogField[]\n}\n\nexport interface WebSearchProviderCatalog {\n /** Rendering + form order for provider types. */\n order: string[]\n entries: Record<string, WebSearchProviderCatalogEntry>\n}\n\nexport interface WebSearchProviderSaveInput {\n /** Absent = create. */\n id?: string\n type: string\n name?: string | null\n baseUrl?: string | null\n googleCx?: string | null\n /** `null` keeps the stored key, a value stores it. */\n apiKey?: string | null\n}\n\nexport interface WebSearchProviderProbeView {\n status: WebSearchProbeStatus\n latencyMs?: number | null\n error?: string | null\n}\n\nexport interface WebSearchProvidersPanelLabels {\n title?: string\n hint?: string\n addLabel?: string\n emptyLabel?: string\n nameLabel?: string\n typeLabel?: string\n baseUrlLabel?: string\n baseUrlRequiredLabel?: string\n keyLabel?: string\n keyOptionalLabel?: string\n keyStoredLabel?: string\n noKeyLabel?: string\n saveLabel?: string\n cancelLabel?: string\n editLabel?: string\n deleteLabel?: string\n moveUpLabel?: string\n moveDownLabel?: string\n enabledAriaLabel?: string\n disabledBadge?: string\n testLabel?: string\n probeLabels?: Partial<Record<WebSearchProbeStatus, string>>\n}\n\nexport interface WebSearchProvidersPanelProps {\n providers: WebSearchProviderView[]\n catalog: WebSearchProviderCatalog\n /** Connection probes keyed by provider id. */\n probes?: Record<string, WebSearchProviderProbeView>\n /** Id currently mid-test (shows the testing spinner). */\n testingId?: string | null\n busy?: boolean\n labels?: WebSearchProvidersPanelLabels\n onSave: (input: WebSearchProviderSaveInput) => void\n onDelete: (id: string) => void\n onMove: (id: string, direction: 'up' | 'down') => void\n onToggle: (id: string, enabled: boolean) => void\n onTest: (id: string) => void\n className?: string\n}\n\nconst inputClass =\n 'min-w-0 flex-1 rounded-[var(--as-radius-sm)] border border-[var(--as-border)] bg-[var(--as-surface)] px-2 py-1.5 text-sm text-[var(--as-fg)] placeholder:text-[var(--as-muted-fg)] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]'\n\n/**\n * Presentational ordered web-search provider list (ADR-0035 family shape):\n * enable/reorder/test/edit/delete rows plus a catalog-driven add/edit form.\n * Fully controlled — persistence, keys and probes are app-side; the panel\n * renders and emits. The first enabled provider that answers wins is the\n * family's fallback semantics, surfaced here by row order.\n */\nexport const WebSearchProvidersPanel = React.forwardRef<\n HTMLDivElement,\n WebSearchProvidersPanelProps\n>(function WebSearchProvidersPanel(\n {\n providers,\n catalog,\n probes = {},\n testingId = null,\n busy = false,\n labels,\n onSave,\n onDelete,\n onMove,\n onToggle,\n onTest,\n className,\n },\n ref,\n) {\n const t = React.useMemo(\n () => ({\n title: labels?.title ?? 'Web search providers',\n hint: labels?.hint,\n addLabel: labels?.addLabel ?? 'Add provider',\n emptyLabel: labels?.emptyLabel ?? 'No web search providers yet.',\n nameLabel: labels?.nameLabel ?? 'Name (optional)',\n typeLabel: labels?.typeLabel ?? 'Provider type',\n baseUrlLabel: labels?.baseUrlLabel ?? 'Base URL (optional)',\n baseUrlRequiredLabel: labels?.baseUrlRequiredLabel ?? 'Base URL (required)',\n keyLabel: labels?.keyLabel ?? 'API key',\n keyOptionalLabel: labels?.keyOptionalLabel ?? 'API key (optional)',\n keyStoredLabel: labels?.keyStoredLabel ?? 'API key saved',\n noKeyLabel: labels?.noKeyLabel ?? 'no key',\n saveLabel: labels?.saveLabel ?? 'Save provider',\n cancelLabel: labels?.cancelLabel ?? 'Cancel',\n editLabel: labels?.editLabel ?? 'Edit provider',\n deleteLabel: labels?.deleteLabel ?? 'Delete provider',\n moveUpLabel: labels?.moveUpLabel ?? 'Move up',\n moveDownLabel: labels?.moveDownLabel ?? 'Move down',\n enabledAriaLabel: labels?.enabledAriaLabel ?? 'Enable provider',\n disabledBadge: labels?.disabledBadge ?? 'disabled',\n testLabel: labels?.testLabel ?? 'Test',\n probeLabels: { ...labels?.probeLabels },\n }),\n [labels],\n )\n\n const [draft, setDraft] = React.useState<WebSearchProviderSaveInput | null>(null)\n\n const openAdd = () => {\n const first = catalog.order[0]\n if (first) setDraft({ type: first, name: '', baseUrl: '', googleCx: '', apiKey: null })\n }\n\n const openEdit = (provider: WebSearchProviderView) =>\n setDraft({\n id: provider.id,\n type: provider.type,\n name: provider.name,\n baseUrl: provider.baseUrl ?? '',\n googleCx: provider.googleCx ?? '',\n apiKey: null,\n })\n\n const draftEntry = draft ? catalog.entries[draft.type] : undefined\n const draftCxField = draftEntry?.fields?.some((field) => field.id === 'google_cx') ?? false\n const saveDisabled =\n busy ||\n (draftEntry?.baseUrlRequired && !(draft?.baseUrl ?? '').trim()) ||\n (draft?.id == null && (draftEntry?.requiresKey ?? false) && !(draft?.apiKey ?? '').trim())\n\n return (\n <div\n ref={ref}\n data-as=\"web-search-providers\"\n className={cn(\n 'rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] p-4',\n className,\n )}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <p className=\"text-base font-medium text-[var(--as-fg)]\">{t.title}</p>\n {t.hint ? (\n <p className=\"mt-0.5 text-xs text-[var(--as-muted-fg)]\">{t.hint}</p>\n ) : null}\n </div>\n {draft === null ? (\n <Button variant=\"outline\" size=\"sm\" onClick={openAdd}>\n <Plus className=\"size-4\" aria-hidden />\n {t.addLabel}\n </Button>\n ) : null}\n </div>\n\n <ul className=\"mt-3 space-y-2\" data-testid=\"wsp-rows\">\n {providers.map((provider, index) => {\n const probe = probes[provider.id]\n const isTesting = testingId === provider.id\n const showProbeRow = probe != null || isTesting\n const probeStatus: ConnectionTestStatus | null = !showProbeRow\n ? null\n : isTesting\n ? 'testing'\n : probe!.status === 'ok'\n ? 'ok'\n : 'fail'\n const probeText =\n probe && probe.status !== 'ok' && probe.error\n ? probe.error\n : probe\n ? (t.probeLabels[probe.status] ?? probe.status)\n : null\n return (\n <li\n key={provider.id}\n data-testid={`wsp-row-${provider.id}`}\n className=\"border-border border-[var(--as-border)] space-y-1 rounded-[var(--as-radius-sm)] border p-3\"\n >\n <div className=\"flex flex-wrap items-center gap-2\">\n <label className=\"flex cursor-pointer items-center gap-2 text-sm font-medium text-[var(--as-fg)]\">\n <input\n type=\"checkbox\"\n checked={provider.enabled}\n aria-label={`${t.enabledAriaLabel}: ${provider.name}`}\n onChange={(event) => onToggle(provider.id, event.target.checked)}\n />\n {provider.name}\n </label>\n <span className=\"rounded-full bg-[var(--as-secondary)] px-2 py-0.5 text-[11px] text-[var(--as-muted-fg)]\">\n {catalog.entries[provider.type]?.label ?? provider.type}\n </span>\n {provider.baseUrl ? (\n <span className=\"truncate font-mono text-xs text-[var(--as-muted-fg)]\">\n {provider.baseUrl}\n </span>\n ) : null}\n <span className=\"ml-auto text-xs text-[var(--as-muted-fg)]\">\n {provider.keySet\n ? (provider.keyHint ?? t.keyStoredLabel)\n : t.noKeyLabel}\n </span>\n </div>\n {showProbeRow ? (\n <ConnectionTestRow\n variant=\"inline\"\n status={probeStatus ?? 'idle'}\n latencyMs={probe?.latencyMs ?? null}\n label={catalog.entries[provider.type]?.label ?? provider.type}\n testLabel={t.testLabel}\n okLabel={t.probeLabels.ok ?? probe?.status ?? 'ok'}\n failLabel={probeText ?? t.probeLabels.error ?? probe?.status ?? 'failed'}\n errorMessage={probe && probe.status === 'ok' ? null : (probe?.error ?? null)}\n />\n ) : null}\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={busy || testingId === provider.id}\n onClick={() => onTest(provider.id)}\n >\n {t.testLabel}\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label={`${t.moveUpLabel}: ${provider.name}`}\n disabled={index === 0 || busy}\n onClick={() => onMove(provider.id, 'up')}\n >\n <ChevronUp className=\"size-4\" aria-hidden />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label={`${t.moveDownLabel}: ${provider.name}`}\n disabled={index === providers.length - 1 || busy}\n onClick={() => onMove(provider.id, 'down')}\n >\n <ChevronDown className=\"size-4\" aria-hidden />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label={`${t.editLabel}: ${provider.name}`}\n disabled={busy}\n onClick={() => openEdit(provider)}\n >\n <Pencil className=\"size-4\" aria-hidden />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n aria-label={`${t.deleteLabel}: ${provider.name}`}\n disabled={busy}\n onClick={() => onDelete(provider.id)}\n >\n <Trash2 className=\"size-4\" aria-hidden />\n </Button>\n {!provider.enabled ? (\n <span className=\"text-xs text-[var(--as-muted-fg)]\">\n {t.disabledBadge}\n </span>\n ) : null}\n </div>\n </li>\n )\n })}\n </ul>\n\n {providers.length === 0 && draft === null ? (\n <p className=\"py-4 text-center text-sm text-[var(--as-muted-fg)]\" data-testid=\"wsp-empty\">\n {t.emptyLabel}\n </p>\n ) : null}\n\n {draft !== null ? (\n <form\n data-testid=\"wsp-form\"\n className=\"mt-3 space-y-2 rounded-[var(--as-radius-sm)] border border-[var(--as-border)] p-3\"\n onSubmit={(event) => {\n event.preventDefault()\n onSave(draft)\n }}\n >\n <div className=\"flex flex-wrap gap-2\">\n <select\n className={inputClass}\n value={draft.type}\n disabled={draft.id != null || busy}\n aria-label={t.typeLabel}\n onChange={(event) =>\n setDraft({ ...draft, type: event.target.value, googleCx: '' })\n }\n >\n {catalog.order.map((type) => (\n <option key={type} value={type}>\n {catalog.entries[type]?.label ?? type}\n </option>\n ))}\n </select>\n <input\n className={inputClass}\n placeholder={t.nameLabel}\n value={draft.name ?? ''}\n onChange={(event) => setDraft({ ...draft, name: event.target.value })}\n />\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <input\n className={inputClass}\n placeholder={\n draftEntry?.baseUrlRequired\n ? t.baseUrlRequiredLabel\n : (draftEntry?.baseUrlHint ?? t.baseUrlLabel)\n }\n value={draft.baseUrl ?? ''}\n onChange={(event) => setDraft({ ...draft, baseUrl: event.target.value })}\n />\n {draftCxField ? (\n <input\n className={cn(inputClass, 'w-48 flex-none')}\n placeholder={draftEntry?.fields?.find((f) => f.id === 'google_cx')?.placeholder ?? ''}\n aria-label={draftEntry?.fields?.find((f) => f.id === 'google_cx')?.label ?? 'Engine id'}\n value={draft.googleCx ?? ''}\n onChange={(event) => setDraft({ ...draft, googleCx: event.target.value })}\n />\n ) : null}\n <input\n type=\"password\"\n className={cn(inputClass, 'w-48 flex-none')}\n placeholder={\n draft.id != null\n ? t.keyStoredLabel\n : draftEntry?.requiresKey\n ? t.keyLabel\n : t.keyOptionalLabel\n }\n aria-label={t.keyLabel}\n value={draft.apiKey ?? ''}\n onChange={(event) => setDraft({ ...draft, apiKey: event.target.value })}\n />\n </div>\n {draftEntry?.notes ? (\n <p className=\"text-[11px] text-[var(--as-muted-fg)]\">{draftEntry.notes}</p>\n ) : null}\n <div className=\"flex items-center gap-2\">\n <Button size=\"sm\" type=\"submit\" disabled={saveDisabled}>\n {t.saveLabel}\n </Button>\n <Button size=\"sm\" variant=\"ghost\" type=\"button\" onClick={() => setDraft(null)}>\n {t.cancelLabel}\n </Button>\n </div>\n </form>\n ) : null}\n </div>\n )\n})\n"]}
@@ -6,7 +6,7 @@ import { Button } from './chunk-4WBBF5LH.js';
6
6
  import { Spinner } from './chunk-SQHW7TYF.js';
7
7
  import { cn } from './chunk-RVVXVTH3.js';
8
8
  import * as React from 'react';
9
- import { ChevronDown, ChevronRight, Pencil, X, Plus, Check, ChevronUp } from 'lucide-react';
9
+ import { ChevronDown, ChevronRight, Pencil, X, Plus, PenLine, Library, Check, ChevronUp } from 'lucide-react';
10
10
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
11
11
 
12
12
  var PARAMETRIC_CAPS = /* @__PURE__ */ new Set(["text", "vision", "tools"]);
@@ -15,6 +15,22 @@ var normalizeCaps = (next, standalone) => {
15
15
  const standaloneHit = next.find((cap) => standalone.has(cap));
16
16
  return standaloneHit ? [standaloneHit] : next;
17
17
  };
18
+ var applyImplications = (next, implications, toggled, selected) => {
19
+ if (Object.keys(implications).length === 0) return next;
20
+ if (selected) {
21
+ const implied = implications[toggled] ?? [];
22
+ return [...next, ...implied.filter((cap) => !next.includes(cap))];
23
+ }
24
+ const dependents = Object.entries(implications).filter(([, values]) => values.includes(toggled)).map(([cap]) => cap);
25
+ return dependents.length === 0 ? next : next.filter((cap) => cap !== toggled && !dependents.includes(cap));
26
+ };
27
+ var withImplications = (caps, implications) => {
28
+ let out = [...caps];
29
+ for (const cap of caps) {
30
+ out = applyImplications(out, implications, cap, true);
31
+ }
32
+ return out;
33
+ };
18
34
  var CUSTOM = "__custom__";
19
35
  function NumberField({
20
36
  label,
@@ -108,6 +124,8 @@ var ModelRegistry = React.forwardRef(
108
124
  editTitle = "Edit model",
109
125
  selectModelLabel = "Model",
110
126
  manualIdToggleLabel = "Enter the model id manually",
127
+ useCatalogLabel = "Pick from the catalog",
128
+ providerContextLabel = "Provider",
111
129
  editLabel = "Edit",
112
130
  removeLabel = "Remove",
113
131
  missingLabel = "Missing",
@@ -115,6 +133,7 @@ var ModelRegistry = React.forwardRef(
115
133
  capsHint,
116
134
  standaloneCaps,
117
135
  standaloneLabel = "Standalone",
136
+ capImplications,
118
137
  searchPlaceholder = "Search models\u2026",
119
138
  searchLabel = "Search models",
120
139
  emptyProviderLabel = "No models yet \u2014 use Add model to pick from the live catalog.",
@@ -210,7 +229,8 @@ var ModelRegistry = React.forwardRef(
210
229
  const remote = (remoteModels ?? []).find((entry) => entry.id === remoteId);
211
230
  setModal((current) => {
212
231
  if (!current) return current;
213
- const nextCaps = remote ? normalizeCaps([...remote.caps], standaloneSet) : current.caps;
232
+ const normalized = remote ? normalizeCaps([...remote.caps], standaloneSet) : current.caps;
233
+ const nextCaps = withImplications(normalized, capImplications ?? {});
214
234
  return {
215
235
  ...current,
216
236
  externalId: remoteId,
@@ -224,10 +244,11 @@ var ModelRegistry = React.forwardRef(
224
244
  setModal((current) => {
225
245
  if (!current) return current;
226
246
  const isStandalone = standaloneSet.has(value);
227
- const nextCaps = isStandalone ? (
247
+ const raw = isStandalone ? (
228
248
  // a standalone kind replaces everything (or clears when toggled off)
229
249
  current.caps.includes(value) ? current.caps.filter((entry) => entry !== value) : [value]
230
250
  ) : current.caps.includes(value) ? current.caps.filter((entry) => entry !== value) : [...current.caps.filter((entry) => !standaloneSet.has(entry)), value];
251
+ const nextCaps = isStandalone ? raw : applyImplications(raw, capImplications ?? {}, value, !current.caps.includes(value));
231
252
  return {
232
253
  ...current,
233
254
  caps: nextCaps,
@@ -305,6 +326,24 @@ var ModelRegistry = React.forwardRef(
305
326
  return /* @__PURE__ */ jsx(Modal, { open: true, onOpenChange: (open) => !open ? resetModal() : void 0, children: /* @__PURE__ */ jsxs(ModalContent, { className: "max-h-[85vh] w-full max-w-lg overflow-y-auto", children: [
306
327
  /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(ModalTitle, { className: "text-base", children: modal.mode === "add" ? addTitle : `${editTitle} \u2014 ${modal.externalId}` }) }),
307
328
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 p-4", children: [
329
+ provider ? /* @__PURE__ */ jsxs(
330
+ "div",
331
+ {
332
+ "data-as": "model-registry-provider-context",
333
+ className: "flex min-w-0 items-center gap-2 rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-muted)]/50 px-3 py-2",
334
+ children: [
335
+ /* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
336
+ providerContextLabel,
337
+ ":"
338
+ ] }),
339
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs font-medium text-[var(--as-fg)]", children: provider.name }),
340
+ provider.baseUrl ? /* @__PURE__ */ jsxs(Fragment, { children: [
341
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "text-[var(--as-border)]", children: "\xB7" }),
342
+ /* @__PURE__ */ jsx("span", { className: "truncate font-mono text-[10px] text-[var(--as-muted-fg)]", children: provider.baseUrl })
343
+ ] }) : null
344
+ ]
345
+ }
346
+ ) : null,
308
347
  modal.mode === "add" ? remoteState === "loading" ? /* @__PURE__ */ jsxs("p", { className: "flex items-center justify-center gap-2 py-4 text-xs text-[var(--as-muted-fg)]", children: [
309
348
  /* @__PURE__ */ jsx(Spinner, { size: "sm" }),
310
349
  remoteLoadingLabel
@@ -324,13 +363,17 @@ var ModelRegistry = React.forwardRef(
324
363
  className: "w-full rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] px-3 py-2 font-mono text-sm text-[var(--as-fg)]"
325
364
  }
326
365
  )
327
- ] }) : /* @__PURE__ */ jsx(
366
+ ] }) : /* @__PURE__ */ jsxs(
328
367
  "button",
329
368
  {
330
369
  type: "button",
331
- className: "cursor-pointer self-start text-xs text-[var(--as-muted-fg)] underline-offset-2 hover:text-[var(--as-fg)] hover:underline",
370
+ "data-as": "model-registry-manual-toggle",
371
+ className: "inline-flex cursor-pointer items-center gap-1.5 self-end rounded-[var(--as-radius)] border border-[var(--as-border)] bg-transparent px-2.5 py-1 text-xs text-[var(--as-muted-fg)] transition-colors hover:bg-[var(--as-muted)] hover:text-[var(--as-fg)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--as-focus-ring)]",
332
372
  onClick: () => setModal((c) => c ? { ...c, manual: true } : c),
333
- children: manualIdToggleLabel
373
+ children: [
374
+ /* @__PURE__ */ jsx(PenLine, { className: "size-3.5", "aria-hidden": true }),
375
+ manualIdToggleLabel
376
+ ]
334
377
  }
335
378
  )
336
379
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -361,9 +404,16 @@ var ModelRegistry = React.forwardRef(
361
404
  "button",
362
405
  {
363
406
  type: "button",
364
- className: "cursor-pointer self-start text-xs text-[var(--as-muted-fg)] underline-offset-2 hover:text-[var(--as-fg)] hover:underline",
407
+ "data-as": "model-registry-manual-toggle",
408
+ className: "inline-flex cursor-pointer items-center gap-1.5 self-end rounded-[var(--as-radius)] border border-[var(--as-border)] bg-transparent px-2.5 py-1 text-xs text-[var(--as-muted-fg)] transition-colors hover:bg-[var(--as-muted)] hover:text-[var(--as-fg)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--as-focus-ring)]",
365
409
  onClick: () => setModal((c) => c ? { ...c, manual: !c.manual } : c),
366
- children: manualIdToggleLabel
410
+ children: modal.manual ? /* @__PURE__ */ jsxs(Fragment, { children: [
411
+ /* @__PURE__ */ jsx(Library, { className: "size-3.5", "aria-hidden": true }),
412
+ useCatalogLabel
413
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
414
+ /* @__PURE__ */ jsx(PenLine, { className: "size-3.5", "aria-hidden": true }),
415
+ manualIdToggleLabel
416
+ ] })
367
417
  }
368
418
  )
369
419
  ] }) : /* @__PURE__ */ jsx("p", { className: "font-mono text-sm text-[var(--as-fg)]", children: modal.externalId }),
@@ -692,5 +742,5 @@ var ModelRegistry = React.forwardRef(
692
742
  );
693
743
 
694
744
  export { ModelRegistry };
695
- //# sourceMappingURL=chunk-AP56DLCQ.js.map
696
- //# sourceMappingURL=chunk-AP56DLCQ.js.map
745
+ //# sourceMappingURL=chunk-FPX3AI3Z.js.map
746
+ //# sourceMappingURL=chunk-FPX3AI3Z.js.map