@mcp-b/react-components 0.9.0 → 0.11.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.
@@ -3,7 +3,7 @@ import { A as SearchIcon, o as ChevronDownIcon } from "./icons-D2Q5n5C1.js";
3
3
  import { Button } from "./components/Button.js";
4
4
  import { Dialog, DialogBackdrop, DialogPopup, DialogPortal, DialogTitle, DialogTrigger } from "./components/Dialog.js";
5
5
  import * as React from "react";
6
- import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
7
  //#region src/components/_internal/provider-logos.tsx
8
8
  function Svg({ children, size = 16, ...props }) {
9
9
  return /* @__PURE__ */ jsx("svg", {
@@ -77,6 +77,128 @@ function ProviderLogo({ provider, ...props }) {
77
77
  //#endregion
78
78
  //#region src/components/ModelSelector.tsx
79
79
  const ModelSelectorContext = React.createContext(null);
80
+ const cloudflareModelProviderLabels = {
81
+ ai4bharat: "AI4Bharat",
82
+ aisingapore: "AI Singapore",
83
+ alibaba: "Alibaba",
84
+ anthropic: "Anthropic",
85
+ baai: "BAAI",
86
+ cohere: "Cohere",
87
+ deepseek: "DeepSeek",
88
+ "deepseek-ai": "DeepSeek",
89
+ google: "Google",
90
+ "google-vertex": "Google Vertex",
91
+ groq: "Groq",
92
+ ibm: "IBM",
93
+ "ibm-granite": "IBM",
94
+ meta: "Meta",
95
+ "meta-llama": "Meta",
96
+ microsoft: "Microsoft",
97
+ mistral: "Mistral AI",
98
+ mistralai: "Mistral AI",
99
+ minimax: "MiniMax",
100
+ moonshotai: "Moonshot AI",
101
+ nvidia: "NVIDIA",
102
+ openai: "OpenAI",
103
+ openrouter: "OpenRouter",
104
+ perplexity: "Perplexity",
105
+ qwen: "Qwen",
106
+ replicate: "Replicate",
107
+ together: "Together AI",
108
+ xai: "xAI",
109
+ "zai-org": "Zhipu AI"
110
+ };
111
+ function isCloudflareRunnableModelId(value) {
112
+ const modelId = value.trim();
113
+ return /^@(?:cf|hf)\/[^/\s]+\/\S+$/.test(modelId) || /^[a-z0-9][a-z0-9._-]*\/\S+$/i.test(modelId);
114
+ }
115
+ function readCatalogString(value) {
116
+ if (typeof value !== "string") return null;
117
+ const trimmed = value.trim();
118
+ return trimmed.length > 0 ? trimmed : null;
119
+ }
120
+ function readCatalogField(entry, field) {
121
+ if (typeof entry !== "object" || entry === null || !Object.hasOwn(entry, field)) return null;
122
+ return readCatalogString(entry[field]);
123
+ }
124
+ function readCatalogEntries(entry) {
125
+ if (Array.isArray(entry)) return entry;
126
+ if (typeof entry !== "object" || entry === null || !Object.hasOwn(entry, "data")) return null;
127
+ const data = entry.data;
128
+ return Array.isArray(data) ? data : null;
129
+ }
130
+ function readCloudflareModelId(entry) {
131
+ if (typeof entry === "string") {
132
+ const modelId = readCatalogString(entry);
133
+ return modelId && isCloudflareRunnableModelId(modelId) ? modelId : null;
134
+ }
135
+ for (const field of ["name", "id"]) {
136
+ const modelId = readCatalogField(entry, field);
137
+ if (modelId && isCloudflareRunnableModelId(modelId)) return modelId;
138
+ }
139
+ return null;
140
+ }
141
+ function getCloudflareModelProvider(modelId) {
142
+ const [, hostedProvider] = /^@(?:cf|hf)\/([^/]+)/.exec(modelId) ?? [];
143
+ if (hostedProvider) return hostedProvider;
144
+ return modelId.split("/", 1)[0] || "other";
145
+ }
146
+ function formatCloudflareModelProvider(provider) {
147
+ const normalized = provider.trim().toLowerCase();
148
+ if (cloudflareModelProviderLabels[normalized]) return cloudflareModelProviderLabels[normalized];
149
+ return normalized.split(/[-_]/).filter(Boolean).map((word) => word[0].toUpperCase() + word.slice(1)).join(" ");
150
+ }
151
+ function normalizeCloudflareModelSelectorModels(models) {
152
+ const seen = /* @__PURE__ */ new Set();
153
+ const options = [];
154
+ const appendOption = (entry) => {
155
+ const entries = readCatalogEntries(entry);
156
+ if (entries) {
157
+ for (const nestedEntry of entries) appendOption(nestedEntry);
158
+ return;
159
+ }
160
+ const id = readCloudflareModelId(entry);
161
+ if (!id || seen.has(id)) return;
162
+ seen.add(id);
163
+ const provider = getCloudflareModelProvider(id);
164
+ options.push({
165
+ id,
166
+ provider,
167
+ providerLabel: formatCloudflareModelProvider(provider),
168
+ source: id.startsWith("@") ? "workers-ai" : "ai-gateway",
169
+ description: readCatalogField(entry, "description")
170
+ });
171
+ };
172
+ for (const entry of models) appendOption(entry);
173
+ return options;
174
+ }
175
+ async function listCloudflareModelSelectorOptions(client, params) {
176
+ const models = [];
177
+ for await (const model of client.ai.models.list(params)) models.push(model);
178
+ if (params.format !== "openrouter") for await (const model of client.ai.models.list({
179
+ ...params,
180
+ format: "openrouter"
181
+ })) models.push(model);
182
+ return normalizeCloudflareModelSelectorModels(models);
183
+ }
184
+ function groupCloudflareModelOptions(options) {
185
+ const groups = [];
186
+ const groupsByLabel = /* @__PURE__ */ new Map();
187
+ for (const option of options) {
188
+ const group = groupsByLabel.get(option.providerLabel);
189
+ if (group) {
190
+ group.push(option);
191
+ continue;
192
+ }
193
+ const nextGroup = [option];
194
+ groupsByLabel.set(option.providerLabel, nextGroup);
195
+ groups.push({
196
+ providerLabel: option.providerLabel,
197
+ options: nextGroup
198
+ });
199
+ }
200
+ return groups;
201
+ }
80
202
  function useModelSelectorContext() {
81
203
  const ctx = React.useContext(ModelSelectorContext);
82
204
  if (!ctx) throw new Error("ModelSelector subcomponents must be used inside <ModelSelector>.");
@@ -154,6 +276,67 @@ function ModelSelector({ open, defaultOpen = false, onOpenChange, children, clas
154
276
  })
155
277
  });
156
278
  }
279
+ function CloudflareModelSelector({ models, selectedModelId = null, onSelectModel, disabled = false, placeholder = "Search or paste an AI Gateway model id...", triggerLabel = "Select a model", emptyLabel = "No models found", allowTypedModel = true, onOpenChange, ...props }) {
280
+ const [query, setQuery] = React.useState("");
281
+ const options = React.useMemo(() => normalizeCloudflareModelSelectorModels(models), [models]);
282
+ const groups = React.useMemo(() => groupCloudflareModelOptions(options), [options]);
283
+ const optionIds = React.useMemo(() => new Set(options.map((option) => option.id)), [options]);
284
+ const selectedOption = selectedModelId ? options.find((option) => option.id === selectedModelId) ?? {
285
+ id: selectedModelId,
286
+ provider: getCloudflareModelProvider(selectedModelId),
287
+ providerLabel: formatCloudflareModelProvider(getCloudflareModelProvider(selectedModelId)),
288
+ source: selectedModelId.startsWith("@") ? "workers-ai" : "ai-gateway",
289
+ description: null
290
+ } : null;
291
+ const typedModelId = query.trim();
292
+ const showTypedModel = allowTypedModel && isCloudflareRunnableModelId(typedModelId) && typedModelId !== selectedModelId && !optionIds.has(typedModelId);
293
+ return /* @__PURE__ */ jsxs(ModelSelector, {
294
+ onOpenChange: React.useCallback((nextOpen) => {
295
+ if (!nextOpen) setQuery("");
296
+ onOpenChange?.(nextOpen);
297
+ }, [onOpenChange]),
298
+ ...props,
299
+ children: [/* @__PURE__ */ jsx(ModelSelectorTrigger, {
300
+ disabled,
301
+ children: selectedOption ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ModelSelectorLogo, { provider: selectedOption.provider }), /* @__PURE__ */ jsx(ModelSelectorName, { children: selectedOption.id })] }) : /* @__PURE__ */ jsx(ModelSelectorName, { children: triggerLabel })
302
+ }), /* @__PURE__ */ jsx(ModelSelectorContent, {
303
+ title: "Cloudflare model selector",
304
+ children: /* @__PURE__ */ jsxs(ModelSelectorDialog, { children: [/* @__PURE__ */ jsx(ModelSelectorInput, {
305
+ "aria-label": "Search Cloudflare AI models",
306
+ placeholder,
307
+ onInput: (event) => setQuery(event.currentTarget.value)
308
+ }), /* @__PURE__ */ jsxs(ModelSelectorList, { children: [
309
+ showTypedModel ? /* @__PURE__ */ jsx(ModelSelectorGroup, {
310
+ label: "AI Gateway",
311
+ children: /* @__PURE__ */ jsxs(ModelSelectorItem, {
312
+ value: typedModelId,
313
+ onSelect: onSelectModel,
314
+ children: [
315
+ /* @__PURE__ */ jsx(ModelSelectorLogo, { provider: getCloudflareModelProvider(typedModelId) }),
316
+ /* @__PURE__ */ jsx(ModelSelectorName, { children: typedModelId }),
317
+ /* @__PURE__ */ jsx(ModelSelectorShortcut, { children: "Use" })
318
+ ]
319
+ })
320
+ }) : null,
321
+ groups.map((group) => /* @__PURE__ */ jsx(ModelSelectorGroup, {
322
+ label: group.providerLabel,
323
+ children: group.options.map((option) => /* @__PURE__ */ jsxs(ModelSelectorItem, {
324
+ value: option.id,
325
+ keywords: `${option.provider} ${option.providerLabel} ${option.source} ${option.description ?? ""}`,
326
+ selected: option.id === selectedModelId,
327
+ onSelect: onSelectModel,
328
+ children: [
329
+ /* @__PURE__ */ jsx(ModelSelectorLogo, { provider: option.provider }),
330
+ /* @__PURE__ */ jsx(ModelSelectorName, { children: option.id }),
331
+ /* @__PURE__ */ jsx(ModelSelectorShortcut, { children: option.source === "workers-ai" ? "Workers AI" : "Gateway" })
332
+ ]
333
+ }, option.id))
334
+ }, group.providerLabel)),
335
+ /* @__PURE__ */ jsx(ModelSelectorEmpty, { children: emptyLabel })
336
+ ] })] })
337
+ })]
338
+ });
339
+ }
157
340
  function ModelSelectorTrigger({ className, children, ...props }) {
158
341
  return /* @__PURE__ */ jsx(DialogTrigger, { render: /* @__PURE__ */ jsxs(Button, {
159
342
  variant: "outline",
@@ -303,4 +486,4 @@ function ModelSelectorShortcut({ className, children, ref, ...props }) {
303
486
  });
304
487
  }
305
488
  //#endregion
306
- export { ModelSelectorGroup as a, ModelSelectorList as c, ModelSelectorName as d, ModelSelectorSeparator as f, ModelSelectorEmpty as i, ModelSelectorLogo as l, ModelSelectorTrigger as m, ModelSelectorContent as n, ModelSelectorInput as o, ModelSelectorShortcut as p, ModelSelectorDialog as r, ModelSelectorItem as s, ModelSelector as t, ModelSelectorLogoGroup as u };
489
+ export { getCloudflareModelProvider as _, ModelSelectorEmpty as a, normalizeCloudflareModelSelectorModels as b, ModelSelectorItem as c, ModelSelectorLogoGroup as d, ModelSelectorName as f, formatCloudflareModelProvider as g, ModelSelectorTrigger as h, ModelSelectorDialog as i, ModelSelectorList as l, ModelSelectorShortcut as m, ModelSelector as n, ModelSelectorGroup as o, ModelSelectorSeparator as p, ModelSelectorContent as r, ModelSelectorInput as s, CloudflareModelSelector as t, ModelSelectorLogo as u, isCloudflareRunnableModelId as v, readCloudflareModelId as x, listCloudflareModelSelectorOptions as y };
@@ -1,4 +1,4 @@
1
- import { McpServerCard, McpServerManualSetup, McpServerSetupAuth } from "../utils/mcp-server-catalog.js";
1
+ import { McpServerSetupAuth } from "../utils/mcp-server-catalog.js";
2
2
  import * as React from "react";
3
3
 
4
4
  //#region src/components/AgentConnection.d.ts
@@ -25,19 +25,14 @@ type NanitesAgentConnectionOptions = {
25
25
  type AgentConnectionPanelProps = {
26
26
  readonly className?: string;
27
27
  readonly commands?: readonly AgentConnectionCommand[];
28
- readonly dcrMcpServers?: readonly McpServerCard[];
29
28
  readonly defaultTarget?: AgentConnectionTarget;
30
29
  readonly headingLevel?: 2 | 3;
31
- readonly manualMcpServers?: readonly McpServerCard[];
32
30
  readonly origin?: string;
33
31
  readonly section?: boolean;
34
32
  };
35
33
  type AgentConnectionPopoverProps = Omit<AgentConnectionPanelProps, "section"> & {
36
34
  readonly className?: string;
37
35
  };
38
- type McpServerSetupProps = {
39
- readonly setup: McpServerManualSetup;
40
- };
41
36
  type McpConnectorFormValues = {
42
37
  readonly bearerToken?: string;
43
38
  readonly clientId?: string;
@@ -46,11 +41,10 @@ type McpConnectorFormValues = {
46
41
  readonly scope?: string;
47
42
  readonly url: string;
48
43
  };
49
- type McpConnectorFormDefaults = Partial<Pick<McpConnectorFormValues, "name" | "url">>;
50
44
  type McpConnectorFormProps = Omit<React.FormHTMLAttributes<HTMLFormElement>, "onSubmit" | "status"> & {
51
45
  /** Authentication metadata that decides which credential fields render. */readonly auth?: McpServerSetupAuth; /** Label for the OAuth authorization button. */
52
46
  readonly authorizeLabel?: string; /** Initial MCP name and URL values. */
53
- readonly defaults?: McpConnectorFormDefaults; /** Pattern used to validate the MCP name field. */
47
+ readonly defaults?: Partial<Pick<McpConnectorFormValues, "name" | "url">>; /** Pattern used to validate the MCP name field. */
54
48
  readonly namePattern?: string; /** OAuth URL waiting for the user to authorize, when available. */
55
49
  readonly pendingAuthUrl?: string | null; /** Read-only redirect URL shown for manual OAuth client setup. */
56
50
  readonly redirectUrl?: string; /** Inline connection status announced politely to assistive tech. */
@@ -81,16 +75,11 @@ declare function buildNanitesAgentConnectionCommands(origin: string, {
81
75
  declare function AgentConnectionPanel({
82
76
  className,
83
77
  commands: commandsProp,
84
- dcrMcpServers,
85
78
  defaultTarget,
86
79
  headingLevel,
87
- manualMcpServers,
88
80
  origin,
89
81
  section
90
82
  }: AgentConnectionPanelProps): React.JSX.Element | null;
91
- declare function McpServerSetup({
92
- setup
93
- }: McpServerSetupProps): React.JSX.Element;
94
83
  /** Collects MCP connector settings and optional credentials with native form validation. */
95
84
  declare function McpConnectorForm({
96
85
  auth,
@@ -118,4 +107,4 @@ declare function AgentConnectionPopover({
118
107
  ...props
119
108
  }: AgentConnectionPopoverProps): React.JSX.Element;
120
109
  //#endregion
121
- export { AgentConnectionCommand, AgentConnectionPanel, AgentConnectionPanelProps, AgentConnectionPopover, AgentConnectionPopoverProps, AgentConnectionTarget, Connection, ConnectionProps, McpConnectorForm, McpConnectorFormDefaults, McpConnectorFormProps, McpConnectorFormValues, McpServerSetup, McpServerSetupProps, NanitesAgentConnectionOptions, buildNanitesAgentConnectionCommands };
110
+ export { AgentConnectionCommand, AgentConnectionPanel, AgentConnectionPanelProps, AgentConnectionPopover, AgentConnectionPopoverProps, AgentConnectionTarget, Connection, ConnectionProps, McpConnectorForm, McpConnectorFormProps, McpConnectorFormValues, NanitesAgentConnectionOptions, buildNanitesAgentConnectionCommands };
@@ -5,7 +5,6 @@ import { Button } from "./Button.js";
5
5
  import { a as CodeBlockCopyButton, i as CodeBlockContent, n as CodeBlockActions, p as CodeBlockTitle, r as CodeBlockContainer, s as CodeBlockHeader, t as CodeBlock } from "../CodeBlock-XNQOupQ3.js";
6
6
  import { Field } from "./Field.js";
7
7
  import { Form } from "./Form.js";
8
- import { DCR_MCP_SERVER_CARDS, MANUAL_MCP_SERVER_CARDS, getMcpServerIconSrc } from "../utils/mcp-server-catalog.js";
9
8
  import * as React from "react";
10
9
  import { jsx, jsxs } from "react/jsx-runtime";
11
10
  //#region src/components/AgentConnection.tsx
@@ -104,7 +103,7 @@ function buildNanitesAgentConnectionCommands(origin, { mcpName = "nanites", plug
104
103
  * Use inside setup screens where users need Codex, Claude Code, skill-only,
105
104
  * or raw MCP JSON connection instructions.
106
105
  */
107
- function AgentConnectionPanel({ className, commands: commandsProp, dcrMcpServers = DCR_MCP_SERVER_CARDS, defaultTarget = "codex", headingLevel = 2, manualMcpServers = MANUAL_MCP_SERVER_CARDS, origin, section = true }) {
106
+ function AgentConnectionPanel({ className, commands: commandsProp, defaultTarget = "codex", headingLevel = 2, origin, section = true }) {
108
107
  const commands = React.useMemo(() => commandsProp ?? buildNanitesAgentConnectionCommands(origin ?? getBrowserOrigin()), [commandsProp, origin]);
109
108
  const [target, setTarget] = React.useState(defaultTarget);
110
109
  const selected = commands.find((command) => command.target === target) ?? commands[0];
@@ -150,72 +149,7 @@ function AgentConnectionPanel({ className, commands: commandsProp, dcrMcpServers
150
149
  code: selected.code,
151
150
  language: selected.language,
152
151
  children: [/* @__PURE__ */ jsxs(CodeBlockHeader, { children: [/* @__PURE__ */ jsx(CodeBlockTitle, { children: selected.label }), /* @__PURE__ */ jsx(CodeBlockActions, { children: /* @__PURE__ */ jsx(CodeBlockCopyButton, {}) })] }), /* @__PURE__ */ jsx(CodeBlockContainer, { children: /* @__PURE__ */ jsx(CodeBlockContent, {}) })]
153
- }),
154
- dcrMcpServers.length > 0 ? /* @__PURE__ */ jsx(McpServerList, {
155
- heading: "Auto",
156
- servers: dcrMcpServers
157
- }) : null,
158
- manualMcpServers.length > 0 ? /* @__PURE__ */ jsx(McpServerList, {
159
- heading: "Manual",
160
- servers: manualMcpServers
161
- }) : null
162
- ]
163
- });
164
- }
165
- function McpServerList({ heading, servers }) {
166
- return /* @__PURE__ */ jsxs("div", {
167
- className: "agent-connect__mcp",
168
- children: [/* @__PURE__ */ jsx("p", {
169
- className: "agent-connect__mcp-heading",
170
- children: heading
171
- }), /* @__PURE__ */ jsx("ul", {
172
- className: "agent-connect__mcp-list",
173
- role: "list",
174
- children: servers.map((server) => /* @__PURE__ */ jsxs("li", {
175
- className: "agent-connect__mcp-item",
176
- children: [/* @__PURE__ */ jsx("span", {
177
- className: "agent-connect__mcp-icon",
178
- children: renderMcpServerIcon(server)
179
- }), /* @__PURE__ */ jsxs("div", {
180
- className: "agent-connect__mcp-copy",
181
- children: [
182
- /* @__PURE__ */ jsx("a", {
183
- className: "agent-connect__mcp-name",
184
- href: server.url,
185
- target: "_blank",
186
- rel: "noreferrer",
187
- children: server.name
188
- }),
189
- server.description ? /* @__PURE__ */ jsx("span", {
190
- className: "agent-connect__mcp-description",
191
- children: server.description
192
- }) : null,
193
- server.manualSetup ? /* @__PURE__ */ jsx(McpServerSetup, { setup: server.manualSetup }) : null
194
- ]
195
- })]
196
- }, server.id))
197
- })]
198
- });
199
- }
200
- function McpServerSetup({ setup }) {
201
- return /* @__PURE__ */ jsxs("div", {
202
- className: "agent-connect__mcp-setup",
203
- children: [
204
- /* @__PURE__ */ jsx("p", { children: setup.description }),
205
- /* @__PURE__ */ jsxs("p", {
206
- className: "agent-connect__mcp-setup-auth",
207
- children: [/* @__PURE__ */ jsx("span", { children: setup.auth.label }), setup.auth.fields.length > 0 ? /* @__PURE__ */ jsx("span", { children: setup.auth.fields.map((field) => field.label).join(", ") }) : null]
208
- }),
209
- setup.links.length > 0 ? /* @__PURE__ */ jsx("ul", {
210
- className: "agent-connect__mcp-setup-links",
211
- role: "list",
212
- children: setup.links.map((link) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("a", {
213
- href: link.url,
214
- target: "_blank",
215
- rel: "noreferrer",
216
- children: link.label
217
- }) }, `${link.label}:${link.url}`))
218
- }) : null
152
+ })
219
153
  ]
220
154
  });
221
155
  }
@@ -362,28 +296,6 @@ function readFormString(data, name) {
362
296
  function isCredentialAuthField(field) {
363
297
  return field.name !== "redirectUrl";
364
298
  }
365
- function renderMcpServerIcon(server) {
366
- const src = getMcpServerIconSrc(server);
367
- if (src) return /* @__PURE__ */ jsx(McpServerIconImage, {
368
- src,
369
- name: server.name
370
- });
371
- return server.name.slice(0, 1);
372
- }
373
- function McpServerIconImage({ src, name }) {
374
- const [failed, setFailed] = React.useState(false);
375
- if (failed) return name.slice(0, 1);
376
- return /* @__PURE__ */ jsx("img", {
377
- src,
378
- alt: "",
379
- width: 16,
380
- height: 16,
381
- loading: "lazy",
382
- decoding: "async",
383
- referrerPolicy: "no-referrer",
384
- onError: () => setFailed(true)
385
- });
386
- }
387
299
  /**
388
300
  * Compact popover trigger for showing AgentConnectionPanel inline.
389
301
  */
@@ -412,4 +324,4 @@ function AgentConnectionPopover({ className, ...props }) {
412
324
  }) })] });
413
325
  }
414
326
  //#endregion
415
- export { AgentConnectionPanel, AgentConnectionPopover, Connection, McpConnectorForm, McpServerSetup, buildNanitesAgentConnectionCommands };
327
+ export { AgentConnectionPanel, AgentConnectionPopover, Connection, McpConnectorForm, buildNanitesAgentConnectionCommands };
@@ -0,0 +1,46 @@
1
+ import * as React from "react";
2
+ import { BrowserLiveView } from "agents/browser";
3
+
4
+ //#region src/components/BrowserRunPanel.d.ts
5
+ interface BrowserRunPanelProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
6
+ /** Host-side Browser Run Live View data from `connector.liveView()`. */
7
+ liveView?: BrowserLiveView;
8
+ /** Refreshes the Live View URL before it expires. */
9
+ onRefresh?: () => void | Promise<void>;
10
+ /** Closes the app-owned shared browser session. */
11
+ onCloseSession?: () => void | Promise<void>;
12
+ /** True while the app is fetching Live View data. */
13
+ loading?: boolean;
14
+ /** Error from the app-owned browser session fetch/refresh path. */
15
+ error?: React.ReactNode;
16
+ /** Panel heading. */
17
+ label?: React.ReactNode;
18
+ /** Empty state shown when there is no shared browser session. */
19
+ empty?: React.ReactNode;
20
+ /** Fallback passed through to `BrowserLiveView` after URL expiry/failure. */
21
+ fallback?: React.ReactNode;
22
+ }
23
+ /**
24
+ * Right-side Browser Run panel for `ThinkFullApp.workbenchPanels`.
25
+ *
26
+ * The app owns `createBrowserRuntime()`, `connector.liveView()`, polling, and
27
+ * session cleanup. This component only renders upstream `BrowserLiveView`
28
+ * data and calls app-provided refresh/close callbacks.
29
+ */
30
+ declare function BrowserRunPanel({
31
+ liveView,
32
+ onRefresh,
33
+ onCloseSession,
34
+ loading,
35
+ error,
36
+ label,
37
+ empty,
38
+ fallback,
39
+ className,
40
+ ref,
41
+ ...props
42
+ }: BrowserRunPanelProps & {
43
+ ref?: React.Ref<HTMLDivElement>;
44
+ }): React.JSX.Element;
45
+ //#endregion
46
+ export { BrowserRunPanel, BrowserRunPanelProps };
@@ -0,0 +1,123 @@
1
+ import { t as cx } from "../class-names-9UtZwb0J.js";
2
+ import { C as MonitorIcon, L as XIcon, M as SpinnerIcon, k as RefreshIcon } from "../icons-D2Q5n5C1.js";
3
+ import { Button } from "./Button.js";
4
+ import { BrowserLiveView } from "./BrowserLiveView.js";
5
+ import * as React from "react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/components/BrowserRunPanel.tsx
8
+ function isInspectablePage(target) {
9
+ const pageUrl = target.pageUrl;
10
+ return Boolean(pageUrl && !pageUrl.startsWith("about:") && !pageUrl.startsWith("chrome:") && !pageUrl.startsWith("devtools:"));
11
+ }
12
+ function targetLabel(target) {
13
+ return target.title || target.pageUrl || target.targetId;
14
+ }
15
+ function pickTarget(targets, targetId) {
16
+ return (targetId ? targets.find((target) => target.targetId === targetId) : void 0) ?? targets.find(isInspectablePage) ?? targets[0];
17
+ }
18
+ function BrowserRunPanelState({ children, role, tone = "neutral" }) {
19
+ return /* @__PURE__ */ jsx("div", {
20
+ className: "browser-run-panel__state",
21
+ "data-tone": tone,
22
+ role: role ?? (tone === "danger" ? "alert" : void 0),
23
+ children
24
+ });
25
+ }
26
+ /**
27
+ * Right-side Browser Run panel for `ThinkFullApp.workbenchPanels`.
28
+ *
29
+ * The app owns `createBrowserRuntime()`, `connector.liveView()`, polling, and
30
+ * session cleanup. This component only renders upstream `BrowserLiveView`
31
+ * data and calls app-provided refresh/close callbacks.
32
+ */
33
+ function BrowserRunPanel({ liveView, onRefresh, onCloseSession, loading, error, label = "Browser", empty = "No live browser session", fallback, className, ref, ...props }) {
34
+ const [selectedTargetId, setSelectedTargetId] = React.useState();
35
+ const [receivedAt, setReceivedAt] = React.useState(() => Date.now());
36
+ React.useEffect(() => {
37
+ if (liveView) setReceivedAt(Date.now());
38
+ }, [liveView]);
39
+ const targets = liveView?.targets ?? [];
40
+ const target = pickTarget(targets, selectedTargetId);
41
+ const pageLabel = target ? targetLabel(target) : "Waiting for a shared browser session";
42
+ return /* @__PURE__ */ jsxs("section", {
43
+ ref,
44
+ className: cx("browser-run-panel", className),
45
+ "aria-label": typeof label === "string" ? label : "Browser",
46
+ ...props,
47
+ children: [
48
+ /* @__PURE__ */ jsxs("header", {
49
+ className: "browser-run-panel__header",
50
+ children: [/* @__PURE__ */ jsxs("div", {
51
+ className: "browser-run-panel__identity",
52
+ children: [/* @__PURE__ */ jsx(MonitorIcon, { className: "browser-run-panel__icon" }), /* @__PURE__ */ jsxs("div", {
53
+ className: "browser-run-panel__title-block",
54
+ children: [/* @__PURE__ */ jsx("span", {
55
+ className: "browser-run-panel__title",
56
+ children: label
57
+ }), /* @__PURE__ */ jsx("span", {
58
+ className: "browser-run-panel__url",
59
+ title: pageLabel,
60
+ children: pageLabel
61
+ })]
62
+ })]
63
+ }), /* @__PURE__ */ jsxs("div", {
64
+ className: "browser-run-panel__actions",
65
+ children: [onRefresh ? /* @__PURE__ */ jsx(Button, {
66
+ type: "button",
67
+ variant: "ghost",
68
+ color: "neutral",
69
+ size: "icon-sm",
70
+ "aria-label": "Refresh browser view",
71
+ disabled: loading,
72
+ onClick: () => void onRefresh(),
73
+ children: /* @__PURE__ */ jsx(RefreshIcon, {})
74
+ }) : null, onCloseSession ? /* @__PURE__ */ jsx(Button, {
75
+ type: "button",
76
+ variant: "ghost",
77
+ color: "neutral",
78
+ size: "icon-sm",
79
+ "aria-label": "Close browser session",
80
+ onClick: () => void onCloseSession(),
81
+ children: /* @__PURE__ */ jsx(XIcon, {})
82
+ }) : null]
83
+ })]
84
+ }),
85
+ targets.length > 1 ? /* @__PURE__ */ jsx("div", {
86
+ className: "browser-run-panel__targets",
87
+ "aria-label": "Browser tabs",
88
+ children: targets.map((candidate) => /* @__PURE__ */ jsxs("button", {
89
+ type: "button",
90
+ className: "browser-run-panel__target",
91
+ "aria-pressed": candidate.targetId === target?.targetId,
92
+ onClick: () => setSelectedTargetId(candidate.targetId),
93
+ children: [/* @__PURE__ */ jsx("span", {
94
+ className: "browser-run-panel__target-title",
95
+ children: targetLabel(candidate)
96
+ }), candidate.pageUrl ? /* @__PURE__ */ jsx("span", {
97
+ className: "browser-run-panel__target-url",
98
+ children: candidate.pageUrl
99
+ }) : null]
100
+ }, candidate.targetId))
101
+ }) : null,
102
+ error ? /* @__PURE__ */ jsx(BrowserRunPanelState, {
103
+ tone: "danger",
104
+ children: error
105
+ }) : null,
106
+ /* @__PURE__ */ jsx("div", {
107
+ className: "browser-run-panel__body",
108
+ "aria-busy": loading || void 0,
109
+ children: target && liveView ? /* @__PURE__ */ jsx(BrowserLiveView, {
110
+ url: target.url,
111
+ expiresAt: receivedAt + liveView.expiresInMs,
112
+ pageTitle: target.title ?? target.pageUrl,
113
+ fallback
114
+ }) : loading ? /* @__PURE__ */ jsxs(BrowserRunPanelState, {
115
+ role: "status",
116
+ children: [/* @__PURE__ */ jsx(SpinnerIcon, { className: "browser-run-panel__spinner" }), "Loading browser session"]
117
+ }) : /* @__PURE__ */ jsx(BrowserRunPanelState, { children: empty })
118
+ })
119
+ ]
120
+ });
121
+ }
122
+ //#endregion
123
+ export { BrowserRunPanel };
@@ -1,7 +1,7 @@
1
1
  import { t as cx } from "../class-names-9UtZwb0J.js";
2
2
  import { M as SpinnerIcon } from "../icons-D2Q5n5C1.js";
3
3
  import { Button } from "./Button.js";
4
- import { l as ModelSelectorLogo } from "../ModelSelector-9G_-GooI.js";
4
+ import { u as ModelSelectorLogo } from "../ModelSelector-DXtpcRmT.js";
5
5
  import "react";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
7
  //#region src/components/ChatGptSignInScreen.tsx
@@ -0,0 +1,41 @@
1
+ import * as React from "react";
2
+ import { MCPServer, MCPServersState } from "agents";
3
+
4
+ //#region src/components/McpServerConnections.d.ts
5
+ type McpServerConnectionAction = (serverId: string, server: MCPServer) => void | Promise<void>;
6
+ interface McpServerConnectionsProps extends React.HTMLAttributes<HTMLElement> {
7
+ /** Upstream Agents SDK MCP state from `getMcpServers()` or `useAgent({ onMcpUpdate })`. */
8
+ mcp: MCPServersState;
9
+ /** Optional heading for the connected-server list. */
10
+ heading?: React.ReactNode;
11
+ /** Called when an OAuth server needs user authorization. */
12
+ onAuthorize?: McpServerConnectionAction;
13
+ /** Called when a failed server should retry through an app-owned Agent RPC. */
14
+ onRetry?: McpServerConnectionAction;
15
+ /** Called when a ready/connected server should refresh discovery through an app-owned Agent RPC. */
16
+ onRefresh?: McpServerConnectionAction;
17
+ /** Called when a server transport should close but the stored config should remain. */
18
+ onDisconnect?: McpServerConnectionAction;
19
+ /** Should call `removeMcpServer(serverId)`; Agents SDK disconnects and removes storage. */
20
+ onRemove?: McpServerConnectionAction;
21
+ }
22
+ /**
23
+ * Runtime MCP server list. Add servers with `McpServerPicker`; render connected
24
+ * state here from the upstream Agents SDK `MCPServersState`.
25
+ */
26
+ declare function McpServerConnections({
27
+ className,
28
+ heading,
29
+ mcp,
30
+ onAuthorize,
31
+ onDisconnect,
32
+ onRefresh,
33
+ onRemove,
34
+ onRetry,
35
+ ref,
36
+ ...props
37
+ }: McpServerConnectionsProps & {
38
+ ref?: React.Ref<HTMLElement>;
39
+ }): React.JSX.Element;
40
+ //#endregion
41
+ export { McpServerConnections, McpServerConnectionsProps };