@mcp-b/react-components 0.6.5 → 0.7.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.
Files changed (38) hide show
  1. package/dist/PromptInput-D6-G24p7.d.ts +528 -0
  2. package/dist/ThinkChatToolParts-DnUiZgCC.d.ts +83 -0
  3. package/dist/components/AgentChat.d.ts +11 -14
  4. package/dist/components/AgentChat.js +76 -57
  5. package/dist/components/Context.d.ts +6 -10
  6. package/dist/components/Context.js +4 -2
  7. package/dist/components/Conversation.d.ts +28 -28
  8. package/dist/components/Conversation.js +35 -54
  9. package/dist/components/Marker.d.ts +57 -0
  10. package/dist/components/Marker.js +89 -0
  11. package/dist/components/MessageScroller.d.ts +52 -0
  12. package/dist/components/MessageScroller.js +62 -0
  13. package/dist/components/PromptInput.d.ts +2 -534
  14. package/dist/components/PromptInput.js +17 -11
  15. package/dist/components/ThinkChat.d.ts +10 -77
  16. package/dist/components/ThinkChat.js +59 -856
  17. package/dist/components/ThinkChatActivity.d.ts +73 -0
  18. package/dist/components/ThinkChatActivity.js +429 -0
  19. package/dist/components/ThinkChatContext.d.ts +28 -0
  20. package/dist/components/ThinkChatContext.js +10 -0
  21. package/dist/components/ThinkChatToolPanels.d.ts +39 -0
  22. package/dist/components/ThinkChatToolPanels.js +355 -0
  23. package/dist/components/ThinkChatToolParts.d.ts +2 -0
  24. package/dist/components/ThinkChatToolParts.js +66 -0
  25. package/dist/components/WorkspaceExplorer.d.ts +1 -1
  26. package/dist/components/useStickToBottom.d.ts +5 -2
  27. package/dist/components/useStickToBottom.js +5 -2
  28. package/dist/styles/agent-chat.css +15 -3
  29. package/dist/styles/agent-connection.css +1 -0
  30. package/dist/styles/code-block.css +4 -4
  31. package/dist/styles/conversation.css +10 -0
  32. package/dist/styles/index.css +2 -0
  33. package/dist/styles/marker.css +95 -0
  34. package/dist/styles/message-scroller.css +90 -0
  35. package/dist/styles/message.css +2 -1
  36. package/dist/styles/scrollbars.css +10 -2
  37. package/dist/styles/streamdown.css +2 -0
  38. package/package.json +3 -2
@@ -0,0 +1,355 @@
1
+ import { ActivityOutput, ActivityPanel } from "./Activity.js";
2
+ import { Tab, TabPanel, Tabs, TabsList } from "./Tabs.js";
3
+ import { t as CodeBlock } from "../CodeBlock-BK0sOAbV.js";
4
+ import { BrowserLiveView } from "./BrowserLiveView.js";
5
+ import { displayUrl } from "./ThinkChatToolParts.js";
6
+ import * as React from "react";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ import { getToolName } from "ai";
9
+ //#region src/components/ThinkChatToolPanels.tsx
10
+ function renderDeniedPanel(label, reason) {
11
+ return /* @__PURE__ */ jsx(ActivityPanel, {
12
+ label,
13
+ text: reason,
14
+ status: "denied",
15
+ error: true
16
+ });
17
+ }
18
+ function numberLines(content, from = 1) {
19
+ const lines = content.split("\n");
20
+ const width = String(from + lines.length - 1).length;
21
+ return lines.map((line, i) => `${String(from + i).padStart(width)} ${line}`).join("\n");
22
+ }
23
+ function prefixLines(text, prefix) {
24
+ return text.split("\n").map((line) => `${prefix}${line}`).join("\n");
25
+ }
26
+ /** Default panels for the Think workspace tools, Claude Code-style: read/write
27
+ show the file with line numbers, edit shows a mini diff, list/find/grep
28
+ show their result lists, bash gets the shell panel. Everything is typed by
29
+ the upstream tool contract. */
30
+ function ThinkChatWorkspacePanel({ part }) {
31
+ if (part.type === "tool-bash") return /* @__PURE__ */ jsx(ThinkChatShellPanel, { part });
32
+ if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
33
+ label: getToolName(part),
34
+ text: part.errorText,
35
+ status: "error",
36
+ error: true
37
+ });
38
+ if (part.state === "output-denied") return renderDeniedPanel(getToolName(part), part.approval.reason);
39
+ switch (part.type) {
40
+ case "tool-read": {
41
+ const label = part.input?.path ?? "file";
42
+ const output = part.state === "output-available" ? part.output : void 0;
43
+ if (output && "error" in output) return /* @__PURE__ */ jsx(ActivityPanel, {
44
+ label,
45
+ text: output.error,
46
+ status: "error",
47
+ error: true
48
+ });
49
+ if (output && "content" in output) return /* @__PURE__ */ jsx(ActivityPanel, {
50
+ label,
51
+ text: output.content,
52
+ status: `${output.totalLines} lines`
53
+ });
54
+ if (output) return /* @__PURE__ */ jsx(ActivityPanel, {
55
+ label,
56
+ text: `${output.name} · ${output.mediaType} · ${output.sizeBytes} bytes`
57
+ });
58
+ return /* @__PURE__ */ jsx(ActivityPanel, { label });
59
+ }
60
+ case "tool-write": {
61
+ const output = part.state === "output-available" ? part.output : void 0;
62
+ return /* @__PURE__ */ jsx(ActivityPanel, {
63
+ label: part.input?.path ?? "file",
64
+ text: part.input?.content ? numberLines(part.input.content) : void 0,
65
+ status: output ? `${output.lines} lines written` : void 0
66
+ });
67
+ }
68
+ case "tool-edit": {
69
+ const input = part.input;
70
+ const output = part.state === "output-available" ? part.output : void 0;
71
+ const status = !output ? void 0 : output.error ? output.error : output.created ? "created" : output.fuzzyMatch ? "replaced (fuzzy match)" : "replaced";
72
+ return /* @__PURE__ */ jsx(ActivityPanel, {
73
+ label: input?.path ?? "file",
74
+ status,
75
+ error: Boolean(output?.error),
76
+ children: input?.old_string !== void 0 && input.new_string !== void 0 ? /* @__PURE__ */ jsxs(ActivityOutput, { children: [
77
+ /* @__PURE__ */ jsx("span", {
78
+ className: "activity__diff-del",
79
+ children: prefixLines(input.old_string, "- ")
80
+ }),
81
+ "\n",
82
+ /* @__PURE__ */ jsx("span", {
83
+ className: "activity__diff-add",
84
+ children: prefixLines(input.new_string, "+ ")
85
+ })
86
+ ] }) : null
87
+ });
88
+ }
89
+ case "tool-list": {
90
+ const output = part.state === "output-available" ? part.output : void 0;
91
+ return /* @__PURE__ */ jsx(ActivityPanel, {
92
+ label: part.input?.path ?? "/",
93
+ text: output?.entries.join("\n"),
94
+ status: output ? `${output.count} entries` : void 0
95
+ });
96
+ }
97
+ case "tool-find": {
98
+ const output = part.state === "output-available" ? part.output : void 0;
99
+ const files = Array.isArray(output?.files) ? output.files.map(String) : [];
100
+ const count = typeof output?.count === "number" ? output.count : files.length;
101
+ return /* @__PURE__ */ jsx(ActivityPanel, {
102
+ label: part.input?.pattern ?? "files",
103
+ text: files.join("\n"),
104
+ status: output ? `${count} files${output.truncated ? " (truncated)" : ""}` : void 0
105
+ });
106
+ }
107
+ case "tool-grep": {
108
+ const output = part.state === "output-available" ? part.output : void 0;
109
+ const matches = Array.isArray(output?.matches) ? output.matches.map((m) => typeof m === "string" ? m : m.context ?? JSON.stringify(m)) : [];
110
+ const status = output && typeof output.totalMatches === "number" ? `${output.totalMatches} matches in ${String(output.filesWithMatches)} files` : void 0;
111
+ return /* @__PURE__ */ jsx(ActivityPanel, {
112
+ label: part.input?.query ?? "search",
113
+ text: matches.join("\n"),
114
+ status
115
+ });
116
+ }
117
+ default: return null;
118
+ }
119
+ }
120
+ /** Skill panels show what the model actually received: the activated skill's
121
+ body, the resource text, or the script result. */
122
+ function ThinkChatSkillPanel({ part }) {
123
+ if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
124
+ label: getToolName(part),
125
+ text: part.errorText,
126
+ status: "error",
127
+ error: true
128
+ });
129
+ if (part.state === "output-denied") return renderDeniedPanel(getToolName(part), part.approval.reason);
130
+ switch (part.type) {
131
+ case "tool-activate_skill": {
132
+ const output = part.state === "output-available" ? part.output : void 0;
133
+ return /* @__PURE__ */ jsx(ActivityPanel, {
134
+ label: part.input?.name ?? "skill",
135
+ text: output
136
+ });
137
+ }
138
+ case "tool-read_skill_resource": {
139
+ const output = part.state === "output-available" ? part.output : void 0;
140
+ return /* @__PURE__ */ jsx(ActivityPanel, {
141
+ label: part.input?.path ?? "resource",
142
+ text: output
143
+ });
144
+ }
145
+ case "tool-run_skill_script": {
146
+ const output = part.state === "output-available" ? part.output : void 0;
147
+ const text = output === void 0 ? void 0 : typeof output === "string" ? output : JSON.stringify(output, null, 2);
148
+ return /* @__PURE__ */ jsx(ActivityPanel, {
149
+ label: part.input?.path ?? "script",
150
+ text
151
+ });
152
+ }
153
+ }
154
+ }
155
+ function ThinkChatFetchPanel({ part }) {
156
+ const label = part.input?.url ?? part.input?.path ?? "fetch";
157
+ if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
158
+ label,
159
+ text: part.errorText,
160
+ status: "error",
161
+ error: true
162
+ });
163
+ if (part.state === "output-denied") return renderDeniedPanel(label, part.approval.reason);
164
+ const output = part.state === "output-available" ? part.output : void 0;
165
+ if (!output) return /* @__PURE__ */ jsx(ActivityPanel, { label });
166
+ if (!output.ok) return /* @__PURE__ */ jsx(ActivityPanel, {
167
+ label: output.finalUrl ?? label,
168
+ text: output.message,
169
+ status: output.code,
170
+ error: true
171
+ });
172
+ const body = output.body ?? (output.json !== void 0 ? JSON.stringify(output.json, null, 2) : output.path ? `saved to ${output.path}` : "");
173
+ return /* @__PURE__ */ jsx(ActivityPanel, {
174
+ label: output.finalUrl,
175
+ text: body || void 0,
176
+ status: `${output.status} · ${output.bytes} bytes${output.truncated ? " (truncated)" : ""}`
177
+ });
178
+ }
179
+ function findBrowserResultMedia(value, depth = 0) {
180
+ const media = {};
181
+ const visit = (node, level) => {
182
+ if (level > 4 || node === null) return;
183
+ if (typeof node === "string") {
184
+ if (!media.liveViewUrl && node.includes("live.browser.run")) media.liveViewUrl = node;
185
+ else if (!media.screenshot && node.startsWith("data:image/")) media.screenshot = node;
186
+ return;
187
+ }
188
+ if (Array.isArray(node)) {
189
+ for (const item of node) visit(item, level + 1);
190
+ return;
191
+ }
192
+ if (typeof node === "object") {
193
+ const record = node;
194
+ if (media.expiresInMs === void 0 && typeof record.url === "string" && typeof record.expiresInMs === "number") media.expiresInMs = record.expiresInMs;
195
+ for (const item of Object.values(record)) visit(item, level + 1);
196
+ }
197
+ };
198
+ visit(value, depth);
199
+ return media;
200
+ }
201
+ function ExecuteCodeView({ code, output, hideResult }) {
202
+ const sections = [
203
+ output && "logs" in output && output.logs?.length ? output.logs.join("\n") : "",
204
+ output?.status === "error" ? output.error : "",
205
+ output?.status === "paused" ? formatPendingActions(output.pending) : "",
206
+ output?.status === "completed" && output.result !== void 0 && !hideResult ? typeof output.result === "string" ? output.result : JSON.stringify(output.result, null, 2) : ""
207
+ ].filter(Boolean);
208
+ return /* @__PURE__ */ jsxs(Fragment, { children: [code ? /* @__PURE__ */ jsx(CodeBlock, {
209
+ className: "activity__code-block",
210
+ code,
211
+ language: "ts"
212
+ }) : null, sections.length ? /* @__PURE__ */ jsx(ActivityOutput, { children: sections.join("\n\n") }) : null] });
213
+ }
214
+ function formatPendingActions(pending) {
215
+ return pending.map((action) => {
216
+ const args = action.args === void 0 ? "" : JSON.stringify(action.args);
217
+ return `awaiting approval: ${action.connector}.${action.method}(${args})`;
218
+ }).join("\n");
219
+ }
220
+ function executeStatusText(output) {
221
+ if (!output) return void 0;
222
+ return output.status === "paused" ? "paused · needs approval" : output.status;
223
+ }
224
+ /** The codemode execute tool as an activity panel: highlighted TypeScript
225
+ plus logs, result, and the paused pending-approval list. */
226
+ function ThinkChatExecutePanel({ part }) {
227
+ if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
228
+ label: "Code",
229
+ text: part.errorText,
230
+ status: "error",
231
+ error: true
232
+ });
233
+ if (part.state === "output-denied") return renderDeniedPanel("Code", part.approval.reason);
234
+ const output = part.state === "output-available" ? part.output : void 0;
235
+ return /* @__PURE__ */ jsx(ActivityPanel, {
236
+ label: "Code",
237
+ status: executeStatusText(output),
238
+ error: output?.status === "error",
239
+ children: /* @__PURE__ */ jsx(ExecuteCodeView, {
240
+ code: part.input?.code ?? "",
241
+ output
242
+ })
243
+ });
244
+ }
245
+ function ThinkChatBrowserPanel({ part }) {
246
+ const [seenAt] = React.useState(() => Date.now());
247
+ if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
248
+ label: "Browser",
249
+ text: part.errorText,
250
+ status: "error",
251
+ error: true
252
+ });
253
+ if (part.state === "output-denied") return renderDeniedPanel("Browser", part.approval.reason);
254
+ if (part.type === "tool-browser_execute") {
255
+ const output = part.state === "output-available" ? part.output : void 0;
256
+ const code = part.input?.code ?? "";
257
+ const media = output?.status === "completed" ? findBrowserResultMedia(output.result) : {};
258
+ const hasMedia = Boolean(media.liveViewUrl || media.screenshot);
259
+ const browserView = media.liveViewUrl ? /* @__PURE__ */ jsx(BrowserLiveView, {
260
+ url: media.liveViewUrl,
261
+ expiresAt: seenAt + (media.expiresInMs ?? 300 * 1e3),
262
+ fallback: media.screenshot ? /* @__PURE__ */ jsx("img", {
263
+ src: media.screenshot,
264
+ alt: "Last browser state"
265
+ }) : void 0
266
+ }) : media.screenshot ? /* @__PURE__ */ jsx("img", {
267
+ className: "activity__browser-screenshot",
268
+ src: media.screenshot,
269
+ alt: "Browser screenshot"
270
+ }) : null;
271
+ const codeView = /* @__PURE__ */ jsx(ExecuteCodeView, {
272
+ code,
273
+ output,
274
+ hideResult: hasMedia
275
+ });
276
+ return /* @__PURE__ */ jsx(ActivityPanel, {
277
+ label: "Browser",
278
+ status: executeStatusText(output),
279
+ error: output?.status === "error",
280
+ children: browserView && code ? /* @__PURE__ */ jsxs(Tabs, {
281
+ className: "activity__browser-tabs",
282
+ defaultValue: "browser",
283
+ children: [
284
+ /* @__PURE__ */ jsxs(TabsList, {
285
+ variant: "line",
286
+ children: [/* @__PURE__ */ jsx(Tab, {
287
+ value: "browser",
288
+ children: "Preview"
289
+ }), /* @__PURE__ */ jsx(Tab, {
290
+ value: "code",
291
+ children: "Code"
292
+ })]
293
+ }),
294
+ /* @__PURE__ */ jsx(TabPanel, {
295
+ value: "browser",
296
+ children: browserView
297
+ }),
298
+ /* @__PURE__ */ jsx(TabPanel, {
299
+ value: "code",
300
+ children: codeView
301
+ })
302
+ ]
303
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [browserView, codeView] })
304
+ });
305
+ }
306
+ const output = part.state === "output-available" ? part.output : void 0;
307
+ return /* @__PURE__ */ jsx(ActivityPanel, {
308
+ label: displayUrl(part.input?.url) || "page",
309
+ text: output === void 0 ? void 0 : typeof output === "string" ? output : Array.isArray(output) ? output.map((item) => typeof item === "string" ? item : JSON.stringify(item)).join("\n") : JSON.stringify(output, null, 2),
310
+ status: part.type === "tool-browser_links" && Array.isArray(output) ? `${output.length} links` : void 0
311
+ });
312
+ }
313
+ function ThinkChatShellPanel({ part }) {
314
+ const output = part.state === "output-available" ? part.output : void 0;
315
+ const errorText = part.state === "output-error" ? part.errorText : "";
316
+ const deniedReason = part.state === "output-denied" ? part.approval.reason ?? "denied" : "";
317
+ const failed = Boolean(errorText) || Boolean(deniedReason) || output !== void 0 && (output.exitCode !== 0 || Boolean(output.errors?.length));
318
+ return /* @__PURE__ */ jsx(ActivityPanel, {
319
+ label: "Shell",
320
+ text: formatBashPanelText(part.input, output, errorText || deniedReason),
321
+ status: output !== void 0 || errorText || deniedReason ? getBashStatusText(output, errorText, deniedReason) : void 0,
322
+ error: failed
323
+ });
324
+ }
325
+ function formatBashPanelText(input, output, errorText) {
326
+ return [
327
+ input?.script?.trim() ? `$ bash\n${input.script}` : "",
328
+ output?.stdout,
329
+ output?.stderr,
330
+ errorText,
331
+ output?.skippedFiles?.length ? ["skipped files", ...output.skippedFiles].join("\n") : "",
332
+ output?.errors?.length ? ["sync errors", ...output.errors].join("\n") : "",
333
+ output ? formatChangedFiles(output.changedFiles) : ""
334
+ ].filter(Boolean).join("\n\n");
335
+ }
336
+ function formatChangedFiles(changedFiles) {
337
+ if (!changedFiles) return "";
338
+ const lines = [
339
+ ...changedFiles.created.map((path) => `created ${path}`),
340
+ ...changedFiles.updated.map((path) => `updated ${path}`),
341
+ ...changedFiles.deleted.map((path) => `deleted ${path}`),
342
+ ...changedFiles.directoriesCreated.map((path) => `created directory ${path}`),
343
+ ...changedFiles.directoriesDeleted.map((path) => `deleted directory ${path}`)
344
+ ];
345
+ return lines.length ? ["workspace changes", ...lines].join("\n") : "";
346
+ }
347
+ function getBashStatusText(output, errorText, deniedReason) {
348
+ if (errorText) return "error";
349
+ if (deniedReason) return "denied";
350
+ if (output?.errors?.length) return "sync errors";
351
+ if (output && output.exitCode !== 0) return `exit ${output.exitCode}`;
352
+ return "success";
353
+ }
354
+ //#endregion
355
+ export { ThinkChatBrowserPanel, ThinkChatExecutePanel, ThinkChatFetchPanel, ThinkChatSkillPanel, ThinkChatWorkspacePanel };
@@ -0,0 +1,2 @@
1
+ import { _ as isThinkChatActivityPart, a as ThinkBashToolOutput, c as ThinkWorkspaceToolName, d as isBrowserToolPart, f as isExecuteToolPart, g as isSkillToolPart, h as isPartWorking, i as SkillToolPart, l as WorkspaceToolPart, m as isPartFailed, n as ExecuteToolPart, o as ThinkBashToolPart, p as isFetchToolPart, r as FetchToolPart, s as ThinkChatPart, t as BrowserToolPart, u as displayUrl, v as isWorkspaceToolPart } from "../ThinkChatToolParts-DnUiZgCC.js";
2
+ export { BrowserToolPart, ExecuteToolPart, FetchToolPart, SkillToolPart, ThinkBashToolOutput, ThinkBashToolPart, ThinkChatPart, ThinkWorkspaceToolName, WorkspaceToolPart, displayUrl, isBrowserToolPart, isExecuteToolPart, isFetchToolPart, isPartFailed, isPartWorking, isSkillToolPart, isThinkChatActivityPart, isWorkspaceToolPart };
@@ -0,0 +1,66 @@
1
+ import { getStaticToolName, getToolName, isReasoningUIPart, isStaticToolUIPart, isToolUIPart } from "ai";
2
+ import { getToolPartState } from "@cloudflare/think/react";
3
+ //#region src/components/ThinkChatToolParts.ts
4
+ const WORKSPACE_TOOL_NAMES = new Set([
5
+ "read",
6
+ "write",
7
+ "edit",
8
+ "list",
9
+ "find",
10
+ "grep",
11
+ "delete",
12
+ "bash"
13
+ ]);
14
+ function isWorkspaceToolPart(part) {
15
+ return isStaticToolUIPart(part) && WORKSPACE_TOOL_NAMES.has(getStaticToolName(part));
16
+ }
17
+ const SKILL_TOOL_NAMES = new Set([
18
+ "activate_skill",
19
+ "read_skill_resource",
20
+ "run_skill_script"
21
+ ]);
22
+ function isSkillToolPart(part) {
23
+ return isStaticToolUIPart(part) && SKILL_TOOL_NAMES.has(getStaticToolName(part));
24
+ }
25
+ function isFetchToolPart(part) {
26
+ return isStaticToolUIPart(part) && getStaticToolName(part).startsWith("fetch_");
27
+ }
28
+ const BROWSER_TOOL_NAMES = new Set([
29
+ "browser_execute",
30
+ "browser_markdown",
31
+ "browser_extract",
32
+ "browser_links",
33
+ "browser_scrape",
34
+ "browser_content"
35
+ ]);
36
+ function isBrowserToolPart(part) {
37
+ return isStaticToolUIPart(part) && BROWSER_TOOL_NAMES.has(getStaticToolName(part));
38
+ }
39
+ function isExecuteToolPart(part) {
40
+ return isStaticToolUIPart(part) && getStaticToolName(part) === "execute";
41
+ }
42
+ /** Agent work (reasoning, tool calls, step boundaries) vs. content shown to the user. */
43
+ function isThinkChatActivityPart(part, excludeTools) {
44
+ if (part === void 0) return false;
45
+ if (isToolUIPart(part) && excludeTools?.has(getToolName(part))) return false;
46
+ return part.type === "step-start" || isReasoningUIPart(part) || isToolUIPart(part);
47
+ }
48
+ /** In-flight: the stream opened this part but hasn't delivered its result yet. */
49
+ function isPartWorking(part) {
50
+ if (isReasoningUIPart(part)) return part.state === "streaming";
51
+ if (isToolUIPart(part)) {
52
+ const state = getToolPartState(part);
53
+ return state === "loading" || state === "streaming" || state === "waiting-approval";
54
+ }
55
+ return false;
56
+ }
57
+ /** Errored or denied — upstream's simplified tool-part state vocabulary. */
58
+ function isPartFailed(part) {
59
+ const state = getToolPartState(part);
60
+ return state === "error" || state === "denied";
61
+ }
62
+ function displayUrl(url) {
63
+ return url ? url.replace(/^https?:\/\//, "") : "";
64
+ }
65
+ //#endregion
66
+ export { displayUrl, isBrowserToolPart, isExecuteToolPart, isFetchToolPart, isPartFailed, isPartWorking, isSkillToolPart, isThinkChatActivityPart, isWorkspaceToolPart };
@@ -1,7 +1,7 @@
1
1
  import { A as CodeBlockLanguage } from "../CodeBlock-CxQXv3C4.js";
2
2
  import * as React from "react";
3
- import { FileInfo, Workspace } from "@cloudflare/shell";
4
3
  import { WorkspaceLike } from "@cloudflare/think/tools/workspace";
4
+ import { FileInfo, Workspace } from "@cloudflare/shell";
5
5
 
6
6
  //#region src/components/WorkspaceExplorer.d.ts
7
7
  /** Workspace summary, as returned by `Workspace.getWorkspaceInfo()` upstream. */
@@ -22,8 +22,11 @@ interface UseStickToBottomResult {
22
22
  }
23
23
  /**
24
24
  * Pin a scrollable viewport to the bottom as its content grows, unless the
25
- * user has scrolled up. Used by the conversation log and the workspace code
26
- * inspector to keep streaming output in view without fighting user intent.
25
+ * user has scrolled up.
26
+ *
27
+ * @deprecated Use MessageScroller for chat transcripts. Keep this only for
28
+ * standalone streaming panes that do not need transcript row anchoring,
29
+ * history prepending, visibility tracking, or jump-to-message behavior.
27
30
  */
28
31
  declare function useStickToBottom(options?: UseStickToBottomOptions): UseStickToBottomResult;
29
32
  //#endregion
@@ -3,8 +3,11 @@ import * as React from "react";
3
3
  const DEFAULT_STICK_SLACK_PX = 160;
4
4
  /**
5
5
  * Pin a scrollable viewport to the bottom as its content grows, unless the
6
- * user has scrolled up. Used by the conversation log and the workspace code
7
- * inspector to keep streaming output in view without fighting user intent.
6
+ * user has scrolled up.
7
+ *
8
+ * @deprecated Use MessageScroller for chat transcripts. Keep this only for
9
+ * standalone streaming panes that do not need transcript row anchoring,
10
+ * history prepending, visibility tracking, or jump-to-message behavior.
8
11
  */
9
12
  function useStickToBottom(options = {}) {
10
13
  const slackPx = options.slackPx ?? DEFAULT_STICK_SLACK_PX;
@@ -15,11 +15,23 @@
15
15
 
16
16
  /* Room for the last message to scroll clear of the floating composer.
17
17
  --agent-chat-composer-size is set from a ResizeObserver in AgentChat. */
18
- .agent-chat .conversation__content {
19
- padding-block-end: calc(var(--agent-chat-composer-size, 5rem) + 0.5rem);
18
+ .agent-chat__transcript-content {
19
+ gap: 0.875rem;
20
+ padding-block: 1rem calc(var(--agent-chat-composer-size, 5rem) + 0.5rem);
21
+ padding-inline: 1rem;
20
22
  }
21
23
 
22
- .agent-chat .conversation__scroll-button {
24
+ .agent-chat__empty-row {
25
+ display: flex;
26
+ flex: 1;
27
+ min-block-size: 0;
28
+ }
29
+
30
+ .agent-chat__empty-row > .conversation__empty {
31
+ flex: 1;
32
+ }
33
+
34
+ .agent-chat__scroll-button {
23
35
  inset-block-end: calc(var(--agent-chat-composer-size, 5rem) + 0.5rem);
24
36
  }
25
37
 
@@ -137,6 +137,7 @@
137
137
 
138
138
  .agent-connect .code-block__container {
139
139
  max-block-size: 13.5rem;
140
+ overflow-y: auto;
140
141
  }
141
142
 
142
143
  .agent-connect .code-block__content,
@@ -47,8 +47,8 @@
47
47
 
48
48
  .code-block__container {
49
49
  position: relative;
50
- overflow: auto;
51
- max-height: 30rem;
50
+ overflow-x: auto;
51
+ overflow-y: clip;
52
52
  }
53
53
 
54
54
  .code-block__content {
@@ -59,7 +59,7 @@
59
59
  font-size: 0.8125rem;
60
60
  line-height: 1.55;
61
61
  color: var(--sigvelo-color-text);
62
- overflow-x: auto;
62
+ overflow: visible;
63
63
  }
64
64
 
65
65
  .code-block__content--highlighted {
@@ -74,7 +74,7 @@
74
74
  font-family: var(--sigvelo-font-mono);
75
75
  font-size: 0.8125rem;
76
76
  line-height: 1.55;
77
- overflow-x: auto;
77
+ overflow: visible;
78
78
  }
79
79
 
80
80
  .code-block__content--numbered:is(pre) > code,
@@ -27,6 +27,16 @@
27
27
  min-height: 100%;
28
28
  }
29
29
 
30
+ .conversation__empty-row {
31
+ display: flex;
32
+ flex: 1;
33
+ min-block-size: 0;
34
+ }
35
+
36
+ .conversation__empty-row > .conversation__empty {
37
+ flex: 1;
38
+ }
39
+
30
40
  .conversation__empty {
31
41
  display: flex;
32
42
  flex: 1;
@@ -30,8 +30,10 @@
30
30
  @import "./github-motion-mark.css" layer(components);
31
31
  @import "./input.css" layer(components);
32
32
  @import "./label.css" layer(components);
33
+ @import "./marker.css" layer(components);
33
34
  @import "./menu.css" layer(components);
34
35
  @import "./menubar.css" layer(components);
36
+ @import "./message-scroller.css" layer(components);
35
37
  @import "./meter.css" layer(components);
36
38
  @import "./navigation-menu.css" layer(components);
37
39
  @import "./nanite-scene.css" layer(components);
@@ -0,0 +1,95 @@
1
+ .marker {
2
+ position: relative;
3
+ display: flex;
4
+ align-items: center;
5
+ gap: 0.5rem;
6
+ width: 100%;
7
+ min-height: 1rem;
8
+ font-family: var(--sigvelo-font-sans);
9
+ font-size: var(--sigvelo-text-sm);
10
+ line-height: 1.5;
11
+ color: var(--sigvelo-color-text-muted);
12
+ text-align: start;
13
+ }
14
+
15
+ .marker:where(a, button) {
16
+ padding: 0;
17
+ font: inherit;
18
+ color: var(--sigvelo-color-text-muted);
19
+ text-decoration: none;
20
+ background: transparent;
21
+ border: 0;
22
+ }
23
+
24
+ .marker:where(button) {
25
+ cursor: pointer;
26
+ }
27
+
28
+ .marker:where(a, button):hover {
29
+ color: var(--sigvelo-color-text);
30
+ }
31
+
32
+ .marker:where(a, button):focus-visible {
33
+ outline: none;
34
+ border-radius: var(--sigvelo-radius-sm);
35
+ box-shadow:
36
+ 0 0 0 2px var(--sigvelo-color-canvas),
37
+ 0 0 0 4px var(--sigvelo-color-focus);
38
+ }
39
+
40
+ .marker--separator::before,
41
+ .marker--separator::after {
42
+ content: "";
43
+ flex: 1 1 0;
44
+ min-width: 0;
45
+ height: var(--sigvelo-border-width);
46
+ background-color: var(--sigvelo-color-neutral-border-subtle);
47
+ }
48
+
49
+ .marker--separator::before {
50
+ margin-inline-end: 0.25rem;
51
+ }
52
+
53
+ .marker--separator::after {
54
+ margin-inline-start: 0.25rem;
55
+ }
56
+
57
+ .marker--border {
58
+ padding-block-end: 0.5rem;
59
+ border-block-end: var(--sigvelo-border-width) var(--sigvelo-border-style)
60
+ var(--sigvelo-color-neutral-border-subtle);
61
+ }
62
+
63
+ .marker__icon {
64
+ display: inline-flex;
65
+ flex-shrink: 0;
66
+ width: 1rem;
67
+ height: 1rem;
68
+ align-items: center;
69
+ justify-content: center;
70
+ }
71
+
72
+ .marker__icon svg {
73
+ width: 1rem;
74
+ height: 1rem;
75
+ }
76
+
77
+ .marker__content {
78
+ min-width: 0;
79
+ overflow-wrap: anywhere;
80
+ }
81
+
82
+ .marker--separator .marker__content {
83
+ flex: 0 1 auto;
84
+ text-align: center;
85
+ }
86
+
87
+ .marker a {
88
+ color: inherit;
89
+ text-decoration: underline;
90
+ text-underline-offset: 0.1875rem;
91
+ }
92
+
93
+ .marker a:hover {
94
+ color: var(--sigvelo-color-text);
95
+ }