@rebyteai/agent-ui 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rebyte, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # @rebyteai/agent-ui
2
+
3
+ Install the versioned package (no repository clone required):
4
+
5
+ ```sh
6
+ pnpm add @rebyteai/agent-ui@0.2.0
7
+ ```
8
+
9
+
10
+ Optional React chat components for native Agents API Sessions.
11
+
12
+ ```tsx
13
+ import { AgentChat } from '@rebyteai/agent-ui'
14
+ import '@rebyteai/agent-ui/styles.css'
15
+ import { createAgentSessionTransport } from '@rebyteai/agent-react'
16
+ const transport = createAgentSessionTransport({ url: '/api/sessions' })
17
+ // Inside your application:
18
+ <AgentChat transport={transport} initialSessionId={savedSessionId} />
19
+ ```
20
+
21
+ Use `AgentChatView` with `useAgentSession` when your application owns state and
22
+ Session-ID persistence. The UI renders text and tools in Item output order, upload
23
+ progress, Session Artifact downloads, cancellation and a native event inspector.
24
+ It does not implement application-specific function handlers.
25
+
26
+ See the [Node App Kit](../../examples/react-chat/README.md) for the complete server
27
+ and browser setup. The organization key stays on the application server.
package/dist/index.cjs ADDED
@@ -0,0 +1,392 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ AgentChat: () => AgentChat,
34
+ AgentChatView: () => AgentChatView
35
+ });
36
+ module.exports = __toCommonJS(index_exports);
37
+
38
+ // src/AgentChat.tsx
39
+ var import_react = require("react");
40
+ var import_react_markdown = __toESM(require("react-markdown"), 1);
41
+ var import_remark_gfm = __toESM(require("remark-gfm"), 1);
42
+ var import_agent_react = require("@rebyteai/agent-react");
43
+ var import_jsx_runtime = require("react/jsx-runtime");
44
+ var SendIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 19V5M5 12l7-7 7 7" }) });
45
+ var StopIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "7", y: "7", width: "10", height: "10", rx: "1" }) });
46
+ var AttachIcon = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m20.5 11.5-8.8 8.8a6 6 0 0 1-8.5-8.5l9.4-9.4a4 4 0 0 1 5.7 5.7l-9.4 9.4a2 2 0 0 1-2.8-2.8l8.7-8.7" }) });
47
+ var FileIcon = ({ image }) => image ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
48
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2" }),
49
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "8.5", cy: "9", r: "1.5" }),
50
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m21 15-5-5L5 20" })
51
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
52
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M6 2h8l4 4v16H6z" }),
53
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M14 2v5h5" })
54
+ ] });
55
+ function formatBytes(size) {
56
+ if (size < 1024) return `${size} B`;
57
+ if (size < 1024 * 1024) return `${Math.round(size / 1024)} KB`;
58
+ return `${(size / (1024 * 1024)).toFixed(1)} MB`;
59
+ }
60
+ function assistantMessages(messages) {
61
+ return messages.filter((message) => message.role === "assistant");
62
+ }
63
+ function AgentChat({ transport, initialSessionId, ...props }) {
64
+ const chat = (0, import_agent_react.useAgentSession)({
65
+ transport,
66
+ ...initialSessionId ? { initialSessionId } : {}
67
+ });
68
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AgentChatView, { chat, ...props });
69
+ }
70
+ function AgentChatView({
71
+ chat,
72
+ brand = "Rebyte Agent",
73
+ agentName = "Configured Agent",
74
+ apiLabel = "Agents API",
75
+ welcomeTitle = "Talk to your Agent.",
76
+ welcomeDescription = "Messages, tool execution, and streamed output share one event timeline.",
77
+ inspector = true,
78
+ className = "",
79
+ continuityLabel = "Session"
80
+ }) {
81
+ const [theme, setTheme] = (0, import_react.useState)("light");
82
+ const [mobilePane, setMobilePane] = (0, import_react.useState)("chat");
83
+ const [input, setInput] = (0, import_react.useState)("");
84
+ const [composerFiles, setComposerFiles] = (0, import_react.useState)([]);
85
+ const [fileError, setFileError] = (0, import_react.useState)(null);
86
+ const textarea = (0, import_react.useRef)(null);
87
+ const fileInput = (0, import_react.useRef)(null);
88
+ const end = (0, import_react.useRef)(null);
89
+ const running = chat.status === "streaming";
90
+ const uploading = composerFiles.some((file) => !file.attachment && !file.error);
91
+ const failedUpload = composerFiles.some((file) => file.error);
92
+ const readyAttachments = composerFiles.flatMap((file) => file.attachment ? [file.attachment] : []);
93
+ const assistant = (0, import_react.useMemo)(() => assistantMessages(chat.messages), [chat.messages]);
94
+ const lastText = assistant[assistant.length - 1]?.content;
95
+ (0, import_react.useEffect)(() => {
96
+ end.current?.scrollIntoView({ behavior: "smooth" });
97
+ }, [chat.messages.length, lastText]);
98
+ const submit = () => {
99
+ const text = input.trim();
100
+ if (!text && readyAttachments.length === 0 || running || uploading || failedUpload) return;
101
+ setInput("");
102
+ setComposerFiles([]);
103
+ setFileError(null);
104
+ if (textarea.current) textarea.current.style.height = "auto";
105
+ void chat.send({ text, attachments: readyAttachments }).catch(() => void 0);
106
+ };
107
+ const updateComposerFile = (localId, update) => {
108
+ setComposerFiles((current) => current.map(
109
+ (file) => file.localId === localId ? { ...file, ...update } : file
110
+ ));
111
+ };
112
+ const selectFiles = (selected) => {
113
+ if (!selected || selected.length === 0 || !chat.upload) return;
114
+ const incoming = Array.from(selected);
115
+ if (composerFiles.length + incoming.length > 8) {
116
+ setFileError("Attach up to 8 files per message.");
117
+ return;
118
+ }
119
+ setFileError(null);
120
+ const pending = incoming.map((file) => ({
121
+ localId: crypto.randomUUID(),
122
+ file,
123
+ progress: 0,
124
+ attachment: null,
125
+ error: null
126
+ }));
127
+ setComposerFiles((current) => [...current, ...pending]);
128
+ for (const item of pending) {
129
+ void chat.upload(item.file, (progress) => {
130
+ updateComposerFile(item.localId, { progress: progress.percent });
131
+ }).then((attachment) => {
132
+ updateComposerFile(item.localId, { attachment, progress: 100 });
133
+ }).catch((error) => {
134
+ const message = error instanceof Error ? error.message : String(error);
135
+ updateComposerFile(item.localId, { error: message });
136
+ });
137
+ }
138
+ };
139
+ const newConversation = () => {
140
+ setInput("");
141
+ setComposerFiles([]);
142
+ setFileError(null);
143
+ chat.reset();
144
+ };
145
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `rb-agent-chat ${inspector ? "" : "rb-no-inspector"} ${className}`.trim(), "data-theme": theme, children: [
146
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "rb-mobile-bar", children: [
147
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rb-mobile-brand", children: brand }),
148
+ inspector && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rb-pane-switch", "aria-label": "Visible pane", children: [
149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: mobilePane === "chat" ? "is-active" : "", onClick: () => setMobilePane("chat"), children: "Chat" }),
150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: mobilePane === "inspector" ? "is-active" : "", onClick: () => setMobilePane("inspector"), children: "Run" })
151
+ ] })
152
+ ] }),
153
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", { className: "rb-sidebar", children: [
154
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rb-brand-row", children: [
155
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rb-brand", children: brand }),
156
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rb-product-tag", children: "UI" })
157
+ ] }),
158
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { className: "rb-new", onClick: newConversation, disabled: running || uploading, children: [
159
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: "\uFF0B" }),
160
+ " New conversation"
161
+ ] }),
162
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rb-sidebar-label", children: "Runtime" }),
163
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rb-runtime-card", children: [
164
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `rb-runtime-dot ${running ? "is-running" : ""}` }),
165
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
166
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: agentName }),
167
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: running ? "Agent is running" : apiLabel })
168
+ ] })
169
+ ] }),
170
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rb-sidebar-spacer" }),
171
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rb-sidebar-foot", children: [
172
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: chat.sessionId ? `\u2026${chat.sessionId.slice(-10)}` : "New conversation" }),
173
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => setTheme((current) => current === "light" ? "dark" : "light"), "aria-label": "Toggle color theme", children: theme === "light" ? "\u25D0" : "\u25D1" })
174
+ ] })
175
+ ] }),
176
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", { className: `rb-chat-pane ${mobilePane === "chat" ? "is-mobile-visible" : ""}`, children: [
177
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rb-messages", children: [
178
+ chat.messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rb-welcome", children: [
179
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rb-eyebrow", children: [
180
+ apiLabel.toUpperCase(),
181
+ " \xD7 REBYTE AGENTS"
182
+ ] }),
183
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", { children: welcomeTitle }),
184
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: welcomeDescription }),
185
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rb-event-sequence", "aria-label": "Streaming event sequence", children: [
186
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "input" }),
187
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("i", {}),
188
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "tool calls" }),
189
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("i", {}),
190
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "text delta" }),
191
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("i", {}),
192
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "completed" })
193
+ ] })
194
+ ] }) : chat.messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Message, { message }, message.id)),
195
+ "artifacts" in chat && chat.artifacts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rb-message-files", "aria-label": "Session artifacts", children: chat.artifacts.map((artifact) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("a", { href: artifact.url, download: true, children: [
196
+ artifact.path.split("/").at(-1),
197
+ " \xB7 ",
198
+ formatBytes(artifact.size_bytes)
199
+ ] }, artifact.id)) }),
200
+ running && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rb-running", children: [
201
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("i", {}),
202
+ " Agent is running"
203
+ ] }),
204
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: end })
205
+ ] }),
206
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rb-composer-wrap", children: [
207
+ (chat.error || fileError) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rb-error", role: "alert", children: fileError ? fileError : chat.error?.message }),
208
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rb-composer", children: [
209
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
210
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
211
+ "input",
212
+ {
213
+ ref: fileInput,
214
+ className: "rb-file-input",
215
+ type: "file",
216
+ multiple: true,
217
+ tabIndex: -1,
218
+ onChange: (event) => {
219
+ selectFiles(event.currentTarget.files);
220
+ event.currentTarget.value = "";
221
+ }
222
+ }
223
+ ),
224
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
225
+ "button",
226
+ {
227
+ className: "rb-attach",
228
+ type: "button",
229
+ disabled: running,
230
+ onClick: () => fileInput.current?.click(),
231
+ "aria-label": "Attach files",
232
+ title: "Attach files",
233
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AttachIcon, {})
234
+ }
235
+ )
236
+ ] }),
237
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rb-composer-main", children: [
238
+ composerFiles.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rb-composer-files", "aria-label": "Attached files", "aria-live": "polite", children: composerFiles.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `rb-composer-file ${item.error ? "is-failed" : item.attachment ? "is-ready" : "is-uploading"}`, children: [
239
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rb-file-icon", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileIcon, { image: item.file.type.startsWith("image/") }) }),
240
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rb-file-copy", children: [
241
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: item.file.name }),
242
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: item.error ? item.error : item.attachment ? `${formatBytes(item.file.size)} \xB7 Ready` : `${formatBytes(item.file.size)} \xB7 Uploading ${item.progress}%` }),
243
+ !item.attachment && !item.error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("progress", { max: "100", value: item.progress })
244
+ ] }),
245
+ (item.attachment || item.error) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
246
+ "button",
247
+ {
248
+ type: "button",
249
+ onClick: () => setComposerFiles((current) => current.filter((file) => file.localId !== item.localId)),
250
+ "aria-label": `Remove ${item.file.name}`,
251
+ children: "\xD7"
252
+ }
253
+ )
254
+ ] }, item.localId)) }),
255
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
256
+ "textarea",
257
+ {
258
+ ref: textarea,
259
+ rows: 1,
260
+ value: input,
261
+ placeholder: composerFiles.length > 0 ? "Add a message\u2026" : `Message ${agentName}\u2026`,
262
+ disabled: running,
263
+ onChange: (event) => {
264
+ setInput(event.target.value);
265
+ const target = event.currentTarget;
266
+ target.style.height = "auto";
267
+ target.style.height = `${Math.min(target.scrollHeight, 180)}px`;
268
+ },
269
+ onKeyDown: (event) => {
270
+ if (event.nativeEvent.isComposing || event.keyCode === 229) return;
271
+ if (event.key === "Enter" && !event.shiftKey) {
272
+ event.preventDefault();
273
+ submit();
274
+ }
275
+ }
276
+ }
277
+ )
278
+ ] }),
279
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
280
+ "button",
281
+ {
282
+ className: running ? "rb-stop" : "rb-send",
283
+ onClick: running ? () => void chat.stop().catch(() => void 0) : submit,
284
+ disabled: !running && (uploading || failedUpload || !input.trim() && readyAttachments.length === 0),
285
+ "aria-label": running ? "Stop response" : "Send message",
286
+ children: running ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StopIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SendIcon, {})
287
+ }
288
+ )
289
+ ] }),
290
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("small", { children: [
291
+ apiLabel,
292
+ " \xB7 Enter to send \xB7 Shift+Enter for a new line"
293
+ ] })
294
+ ] })
295
+ ] }),
296
+ inspector && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("aside", { className: `rb-inspector-pane ${mobilePane === "inspector" ? "is-mobile-visible" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Inspector, { messages: assistant, apiLabel, agentName, continuityLabel }) })
297
+ ] });
298
+ }
299
+ function Message({ message }) {
300
+ if (message.role === "user") {
301
+ const attachments = message.attachments ?? [];
302
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rb-user-turn", children: [
303
+ attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rb-message-files", children: attachments.map((attachment) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
304
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FileIcon, { image: attachment.inputType === "input_image" }),
305
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
306
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: attachment.filename }),
307
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: formatBytes(attachment.size) })
308
+ ] })
309
+ ] }, attachment.fileId)) }),
310
+ message.content && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rb-message rb-user-message", children: message.content })
311
+ ] });
312
+ }
313
+ const parts = message.projection === null ? [] : [
314
+ ...message.projection.toolCalls.map((tool) => ({ kind: "tool", outputIndex: tool.outputIndex, tool })),
315
+ ...message.projection.textMessages.map((text) => ({ kind: "text", outputIndex: text.outputIndex, text }))
316
+ ].sort((a, b) => a.outputIndex - b.outputIndex);
317
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `rb-assistant-turn is-${message.status}`, children: [
318
+ parts.map((part) => part.kind === "tool" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rb-tools", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `rb-tool is-${part.tool.status}`, children: [
319
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "rb-tool-mark", children: part.tool.status === "completed" ? "\u2713" : part.tool.status === "failed" ? "!" : "\xB7" }),
320
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
321
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: part.tool.name }),
322
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("small", { children: [
323
+ part.tool.serverLabel,
324
+ " \xB7 ",
325
+ part.tool.status === "awaiting_output" ? "Awaiting client output" : part.tool.status
326
+ ] })
327
+ ] })
328
+ ] }) }, part.tool.id) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rb-message rb-assistant-message", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default], children: part.text.text }) }, part.text.id)),
329
+ parts.length === 0 && message.content && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rb-message rb-assistant-message", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_markdown.default, { remarkPlugins: [import_remark_gfm.default], children: message.content }) }),
330
+ message.status === "cancelled" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rb-cancelled", children: "Response stopped" })
331
+ ] });
332
+ }
333
+ function Inspector({ messages, apiLabel, agentName, continuityLabel }) {
334
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rb-inspector", children: [
335
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rb-inspector-head", children: [
336
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
337
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Execution" }),
338
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: apiLabel })
339
+ ] }),
340
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rb-live", children: [
341
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("i", {}),
342
+ " LIVE"
343
+ ] })
344
+ ] }),
345
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dl", { className: "rb-runtime-grid", children: [
346
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
347
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Agent" }),
348
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: agentName })
349
+ ] }),
350
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
351
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Continuity" }),
352
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: continuityLabel })
353
+ ] }),
354
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
355
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dt", { children: "Transport" }),
356
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { children: "Server-sent events" })
357
+ ] })
358
+ ] }),
359
+ messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rb-inspector-empty", children: [
360
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "{ }" }),
361
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { children: [
362
+ "Send a message to inspect the raw ",
363
+ apiLabel,
364
+ " event stream."
365
+ ] })
366
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rb-turns", children: messages.map((message, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "rb-turn", children: [
367
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { children: [
368
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
369
+ "turn ",
370
+ index + 1
371
+ ] }),
372
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { className: `is-${message.status}`, children: message.status }),
373
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("small", { children: [
374
+ message.projection?.events.length ?? 0,
375
+ " events"
376
+ ] })
377
+ ] }),
378
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rb-event-list", children: (message.projection?.events ?? []).map((event) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { children: [
379
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("summary", { children: [
380
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: event.event_id.slice(-8) }),
381
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: event.type })
382
+ ] }),
383
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { children: JSON.stringify(event, null, 2) })
384
+ ] }, event.event_id)) })
385
+ ] }, message.id)) })
386
+ ] });
387
+ }
388
+ // Annotate the CommonJS export names for ESM import in node:
389
+ 0 && (module.exports = {
390
+ AgentChat,
391
+ AgentChatView
392
+ });
@@ -0,0 +1,22 @@
1
+ import * as react from 'react';
2
+ import { AgentSessionTransport, AgentSessionChat } from '@rebyteai/agent-react';
3
+
4
+ interface AgentChatProps {
5
+ transport: AgentSessionTransport;
6
+ brand?: string;
7
+ agentName?: string;
8
+ apiLabel?: string;
9
+ welcomeTitle?: string;
10
+ welcomeDescription?: string;
11
+ inspector?: boolean;
12
+ className?: string;
13
+ initialSessionId?: string;
14
+ continuityLabel?: string;
15
+ }
16
+ interface AgentChatViewProps extends Omit<AgentChatProps, 'transport' | 'initialSessionId'> {
17
+ chat: AgentSessionChat;
18
+ }
19
+ declare function AgentChat({ transport, initialSessionId, ...props }: AgentChatProps): react.JSX.Element;
20
+ declare function AgentChatView({ chat, brand, agentName, apiLabel, welcomeTitle, welcomeDescription, inspector, className, continuityLabel, }: AgentChatViewProps): react.JSX.Element;
21
+
22
+ export { AgentChat, type AgentChatProps, AgentChatView, type AgentChatViewProps };
@@ -0,0 +1,22 @@
1
+ import * as react from 'react';
2
+ import { AgentSessionTransport, AgentSessionChat } from '@rebyteai/agent-react';
3
+
4
+ interface AgentChatProps {
5
+ transport: AgentSessionTransport;
6
+ brand?: string;
7
+ agentName?: string;
8
+ apiLabel?: string;
9
+ welcomeTitle?: string;
10
+ welcomeDescription?: string;
11
+ inspector?: boolean;
12
+ className?: string;
13
+ initialSessionId?: string;
14
+ continuityLabel?: string;
15
+ }
16
+ interface AgentChatViewProps extends Omit<AgentChatProps, 'transport' | 'initialSessionId'> {
17
+ chat: AgentSessionChat;
18
+ }
19
+ declare function AgentChat({ transport, initialSessionId, ...props }: AgentChatProps): react.JSX.Element;
20
+ declare function AgentChatView({ chat, brand, agentName, apiLabel, welcomeTitle, welcomeDescription, inspector, className, continuityLabel, }: AgentChatViewProps): react.JSX.Element;
21
+
22
+ export { AgentChat, type AgentChatProps, AgentChatView, type AgentChatViewProps };
package/dist/index.js ADDED
@@ -0,0 +1,356 @@
1
+ // src/AgentChat.tsx
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
+ import ReactMarkdown from "react-markdown";
4
+ import remarkGfm from "remark-gfm";
5
+ import {
6
+ useAgentSession
7
+ } from "@rebyteai/agent-react";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ var SendIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M12 19V5M5 12l7-7 7 7" }) });
10
+ var StopIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx("rect", { x: "7", y: "7", width: "10", height: "10", rx: "1" }) });
11
+ var AttachIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "m20.5 11.5-8.8 8.8a6 6 0 0 1-8.5-8.5l9.4-9.4a4 4 0 0 1 5.7 5.7l-9.4 9.4a2 2 0 0 1-2.8-2.8l8.7-8.7" }) });
12
+ var FileIcon = ({ image }) => image ? /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
13
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2" }),
14
+ /* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "9", r: "1.5" }),
15
+ /* @__PURE__ */ jsx("path", { d: "m21 15-5-5L5 20" })
16
+ ] }) : /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
17
+ /* @__PURE__ */ jsx("path", { d: "M6 2h8l4 4v16H6z" }),
18
+ /* @__PURE__ */ jsx("path", { d: "M14 2v5h5" })
19
+ ] });
20
+ function formatBytes(size) {
21
+ if (size < 1024) return `${size} B`;
22
+ if (size < 1024 * 1024) return `${Math.round(size / 1024)} KB`;
23
+ return `${(size / (1024 * 1024)).toFixed(1)} MB`;
24
+ }
25
+ function assistantMessages(messages) {
26
+ return messages.filter((message) => message.role === "assistant");
27
+ }
28
+ function AgentChat({ transport, initialSessionId, ...props }) {
29
+ const chat = useAgentSession({
30
+ transport,
31
+ ...initialSessionId ? { initialSessionId } : {}
32
+ });
33
+ return /* @__PURE__ */ jsx(AgentChatView, { chat, ...props });
34
+ }
35
+ function AgentChatView({
36
+ chat,
37
+ brand = "Rebyte Agent",
38
+ agentName = "Configured Agent",
39
+ apiLabel = "Agents API",
40
+ welcomeTitle = "Talk to your Agent.",
41
+ welcomeDescription = "Messages, tool execution, and streamed output share one event timeline.",
42
+ inspector = true,
43
+ className = "",
44
+ continuityLabel = "Session"
45
+ }) {
46
+ const [theme, setTheme] = useState("light");
47
+ const [mobilePane, setMobilePane] = useState("chat");
48
+ const [input, setInput] = useState("");
49
+ const [composerFiles, setComposerFiles] = useState([]);
50
+ const [fileError, setFileError] = useState(null);
51
+ const textarea = useRef(null);
52
+ const fileInput = useRef(null);
53
+ const end = useRef(null);
54
+ const running = chat.status === "streaming";
55
+ const uploading = composerFiles.some((file) => !file.attachment && !file.error);
56
+ const failedUpload = composerFiles.some((file) => file.error);
57
+ const readyAttachments = composerFiles.flatMap((file) => file.attachment ? [file.attachment] : []);
58
+ const assistant = useMemo(() => assistantMessages(chat.messages), [chat.messages]);
59
+ const lastText = assistant[assistant.length - 1]?.content;
60
+ useEffect(() => {
61
+ end.current?.scrollIntoView({ behavior: "smooth" });
62
+ }, [chat.messages.length, lastText]);
63
+ const submit = () => {
64
+ const text = input.trim();
65
+ if (!text && readyAttachments.length === 0 || running || uploading || failedUpload) return;
66
+ setInput("");
67
+ setComposerFiles([]);
68
+ setFileError(null);
69
+ if (textarea.current) textarea.current.style.height = "auto";
70
+ void chat.send({ text, attachments: readyAttachments }).catch(() => void 0);
71
+ };
72
+ const updateComposerFile = (localId, update) => {
73
+ setComposerFiles((current) => current.map(
74
+ (file) => file.localId === localId ? { ...file, ...update } : file
75
+ ));
76
+ };
77
+ const selectFiles = (selected) => {
78
+ if (!selected || selected.length === 0 || !chat.upload) return;
79
+ const incoming = Array.from(selected);
80
+ if (composerFiles.length + incoming.length > 8) {
81
+ setFileError("Attach up to 8 files per message.");
82
+ return;
83
+ }
84
+ setFileError(null);
85
+ const pending = incoming.map((file) => ({
86
+ localId: crypto.randomUUID(),
87
+ file,
88
+ progress: 0,
89
+ attachment: null,
90
+ error: null
91
+ }));
92
+ setComposerFiles((current) => [...current, ...pending]);
93
+ for (const item of pending) {
94
+ void chat.upload(item.file, (progress) => {
95
+ updateComposerFile(item.localId, { progress: progress.percent });
96
+ }).then((attachment) => {
97
+ updateComposerFile(item.localId, { attachment, progress: 100 });
98
+ }).catch((error) => {
99
+ const message = error instanceof Error ? error.message : String(error);
100
+ updateComposerFile(item.localId, { error: message });
101
+ });
102
+ }
103
+ };
104
+ const newConversation = () => {
105
+ setInput("");
106
+ setComposerFiles([]);
107
+ setFileError(null);
108
+ chat.reset();
109
+ };
110
+ return /* @__PURE__ */ jsxs("div", { className: `rb-agent-chat ${inspector ? "" : "rb-no-inspector"} ${className}`.trim(), "data-theme": theme, children: [
111
+ /* @__PURE__ */ jsxs("header", { className: "rb-mobile-bar", children: [
112
+ /* @__PURE__ */ jsx("span", { className: "rb-mobile-brand", children: brand }),
113
+ inspector && /* @__PURE__ */ jsxs("div", { className: "rb-pane-switch", "aria-label": "Visible pane", children: [
114
+ /* @__PURE__ */ jsx("button", { className: mobilePane === "chat" ? "is-active" : "", onClick: () => setMobilePane("chat"), children: "Chat" }),
115
+ /* @__PURE__ */ jsx("button", { className: mobilePane === "inspector" ? "is-active" : "", onClick: () => setMobilePane("inspector"), children: "Run" })
116
+ ] })
117
+ ] }),
118
+ /* @__PURE__ */ jsxs("aside", { className: "rb-sidebar", children: [
119
+ /* @__PURE__ */ jsxs("div", { className: "rb-brand-row", children: [
120
+ /* @__PURE__ */ jsx("span", { className: "rb-brand", children: brand }),
121
+ /* @__PURE__ */ jsx("span", { className: "rb-product-tag", children: "UI" })
122
+ ] }),
123
+ /* @__PURE__ */ jsxs("button", { className: "rb-new", onClick: newConversation, disabled: running || uploading, children: [
124
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\uFF0B" }),
125
+ " New conversation"
126
+ ] }),
127
+ /* @__PURE__ */ jsx("div", { className: "rb-sidebar-label", children: "Runtime" }),
128
+ /* @__PURE__ */ jsxs("div", { className: "rb-runtime-card", children: [
129
+ /* @__PURE__ */ jsx("span", { className: `rb-runtime-dot ${running ? "is-running" : ""}` }),
130
+ /* @__PURE__ */ jsxs("span", { children: [
131
+ /* @__PURE__ */ jsx("strong", { children: agentName }),
132
+ /* @__PURE__ */ jsx("small", { children: running ? "Agent is running" : apiLabel })
133
+ ] })
134
+ ] }),
135
+ /* @__PURE__ */ jsx("div", { className: "rb-sidebar-spacer" }),
136
+ /* @__PURE__ */ jsxs("div", { className: "rb-sidebar-foot", children: [
137
+ /* @__PURE__ */ jsx("span", { children: chat.sessionId ? `\u2026${chat.sessionId.slice(-10)}` : "New conversation" }),
138
+ /* @__PURE__ */ jsx("button", { onClick: () => setTheme((current) => current === "light" ? "dark" : "light"), "aria-label": "Toggle color theme", children: theme === "light" ? "\u25D0" : "\u25D1" })
139
+ ] })
140
+ ] }),
141
+ /* @__PURE__ */ jsxs("main", { className: `rb-chat-pane ${mobilePane === "chat" ? "is-mobile-visible" : ""}`, children: [
142
+ /* @__PURE__ */ jsxs("div", { className: "rb-messages", children: [
143
+ chat.messages.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "rb-welcome", children: [
144
+ /* @__PURE__ */ jsxs("span", { className: "rb-eyebrow", children: [
145
+ apiLabel.toUpperCase(),
146
+ " \xD7 REBYTE AGENTS"
147
+ ] }),
148
+ /* @__PURE__ */ jsx("h1", { children: welcomeTitle }),
149
+ /* @__PURE__ */ jsx("p", { children: welcomeDescription }),
150
+ /* @__PURE__ */ jsxs("div", { className: "rb-event-sequence", "aria-label": "Streaming event sequence", children: [
151
+ /* @__PURE__ */ jsx("span", { children: "input" }),
152
+ /* @__PURE__ */ jsx("i", {}),
153
+ /* @__PURE__ */ jsx("span", { children: "tool calls" }),
154
+ /* @__PURE__ */ jsx("i", {}),
155
+ /* @__PURE__ */ jsx("span", { children: "text delta" }),
156
+ /* @__PURE__ */ jsx("i", {}),
157
+ /* @__PURE__ */ jsx("span", { children: "completed" })
158
+ ] })
159
+ ] }) : chat.messages.map((message) => /* @__PURE__ */ jsx(Message, { message }, message.id)),
160
+ "artifacts" in chat && chat.artifacts.length > 0 && /* @__PURE__ */ jsx("div", { className: "rb-message-files", "aria-label": "Session artifacts", children: chat.artifacts.map((artifact) => /* @__PURE__ */ jsxs("a", { href: artifact.url, download: true, children: [
161
+ artifact.path.split("/").at(-1),
162
+ " \xB7 ",
163
+ formatBytes(artifact.size_bytes)
164
+ ] }, artifact.id)) }),
165
+ running && /* @__PURE__ */ jsxs("div", { className: "rb-running", children: [
166
+ /* @__PURE__ */ jsx("i", {}),
167
+ " Agent is running"
168
+ ] }),
169
+ /* @__PURE__ */ jsx("div", { ref: end })
170
+ ] }),
171
+ /* @__PURE__ */ jsxs("div", { className: "rb-composer-wrap", children: [
172
+ (chat.error || fileError) && /* @__PURE__ */ jsx("div", { className: "rb-error", role: "alert", children: fileError ? fileError : chat.error?.message }),
173
+ /* @__PURE__ */ jsxs("div", { className: "rb-composer", children: [
174
+ /* @__PURE__ */ jsxs(Fragment, { children: [
175
+ /* @__PURE__ */ jsx(
176
+ "input",
177
+ {
178
+ ref: fileInput,
179
+ className: "rb-file-input",
180
+ type: "file",
181
+ multiple: true,
182
+ tabIndex: -1,
183
+ onChange: (event) => {
184
+ selectFiles(event.currentTarget.files);
185
+ event.currentTarget.value = "";
186
+ }
187
+ }
188
+ ),
189
+ /* @__PURE__ */ jsx(
190
+ "button",
191
+ {
192
+ className: "rb-attach",
193
+ type: "button",
194
+ disabled: running,
195
+ onClick: () => fileInput.current?.click(),
196
+ "aria-label": "Attach files",
197
+ title: "Attach files",
198
+ children: /* @__PURE__ */ jsx(AttachIcon, {})
199
+ }
200
+ )
201
+ ] }),
202
+ /* @__PURE__ */ jsxs("div", { className: "rb-composer-main", children: [
203
+ composerFiles.length > 0 && /* @__PURE__ */ jsx("div", { className: "rb-composer-files", "aria-label": "Attached files", "aria-live": "polite", children: composerFiles.map((item) => /* @__PURE__ */ jsxs("div", { className: `rb-composer-file ${item.error ? "is-failed" : item.attachment ? "is-ready" : "is-uploading"}`, children: [
204
+ /* @__PURE__ */ jsx("span", { className: "rb-file-icon", children: /* @__PURE__ */ jsx(FileIcon, { image: item.file.type.startsWith("image/") }) }),
205
+ /* @__PURE__ */ jsxs("span", { className: "rb-file-copy", children: [
206
+ /* @__PURE__ */ jsx("strong", { children: item.file.name }),
207
+ /* @__PURE__ */ jsx("small", { children: item.error ? item.error : item.attachment ? `${formatBytes(item.file.size)} \xB7 Ready` : `${formatBytes(item.file.size)} \xB7 Uploading ${item.progress}%` }),
208
+ !item.attachment && !item.error && /* @__PURE__ */ jsx("progress", { max: "100", value: item.progress })
209
+ ] }),
210
+ (item.attachment || item.error) && /* @__PURE__ */ jsx(
211
+ "button",
212
+ {
213
+ type: "button",
214
+ onClick: () => setComposerFiles((current) => current.filter((file) => file.localId !== item.localId)),
215
+ "aria-label": `Remove ${item.file.name}`,
216
+ children: "\xD7"
217
+ }
218
+ )
219
+ ] }, item.localId)) }),
220
+ /* @__PURE__ */ jsx(
221
+ "textarea",
222
+ {
223
+ ref: textarea,
224
+ rows: 1,
225
+ value: input,
226
+ placeholder: composerFiles.length > 0 ? "Add a message\u2026" : `Message ${agentName}\u2026`,
227
+ disabled: running,
228
+ onChange: (event) => {
229
+ setInput(event.target.value);
230
+ const target = event.currentTarget;
231
+ target.style.height = "auto";
232
+ target.style.height = `${Math.min(target.scrollHeight, 180)}px`;
233
+ },
234
+ onKeyDown: (event) => {
235
+ if (event.nativeEvent.isComposing || event.keyCode === 229) return;
236
+ if (event.key === "Enter" && !event.shiftKey) {
237
+ event.preventDefault();
238
+ submit();
239
+ }
240
+ }
241
+ }
242
+ )
243
+ ] }),
244
+ /* @__PURE__ */ jsx(
245
+ "button",
246
+ {
247
+ className: running ? "rb-stop" : "rb-send",
248
+ onClick: running ? () => void chat.stop().catch(() => void 0) : submit,
249
+ disabled: !running && (uploading || failedUpload || !input.trim() && readyAttachments.length === 0),
250
+ "aria-label": running ? "Stop response" : "Send message",
251
+ children: running ? /* @__PURE__ */ jsx(StopIcon, {}) : /* @__PURE__ */ jsx(SendIcon, {})
252
+ }
253
+ )
254
+ ] }),
255
+ /* @__PURE__ */ jsxs("small", { children: [
256
+ apiLabel,
257
+ " \xB7 Enter to send \xB7 Shift+Enter for a new line"
258
+ ] })
259
+ ] })
260
+ ] }),
261
+ inspector && /* @__PURE__ */ jsx("aside", { className: `rb-inspector-pane ${mobilePane === "inspector" ? "is-mobile-visible" : ""}`, children: /* @__PURE__ */ jsx(Inspector, { messages: assistant, apiLabel, agentName, continuityLabel }) })
262
+ ] });
263
+ }
264
+ function Message({ message }) {
265
+ if (message.role === "user") {
266
+ const attachments = message.attachments ?? [];
267
+ return /* @__PURE__ */ jsxs("div", { className: "rb-user-turn", children: [
268
+ attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: "rb-message-files", children: attachments.map((attachment) => /* @__PURE__ */ jsxs("span", { children: [
269
+ /* @__PURE__ */ jsx(FileIcon, { image: attachment.inputType === "input_image" }),
270
+ /* @__PURE__ */ jsxs("span", { children: [
271
+ /* @__PURE__ */ jsx("strong", { children: attachment.filename }),
272
+ /* @__PURE__ */ jsx("small", { children: formatBytes(attachment.size) })
273
+ ] })
274
+ ] }, attachment.fileId)) }),
275
+ message.content && /* @__PURE__ */ jsx("div", { className: "rb-message rb-user-message", children: message.content })
276
+ ] });
277
+ }
278
+ const parts = message.projection === null ? [] : [
279
+ ...message.projection.toolCalls.map((tool) => ({ kind: "tool", outputIndex: tool.outputIndex, tool })),
280
+ ...message.projection.textMessages.map((text) => ({ kind: "text", outputIndex: text.outputIndex, text }))
281
+ ].sort((a, b) => a.outputIndex - b.outputIndex);
282
+ return /* @__PURE__ */ jsxs("div", { className: `rb-assistant-turn is-${message.status}`, children: [
283
+ parts.map((part) => part.kind === "tool" ? /* @__PURE__ */ jsx("div", { className: "rb-tools", children: /* @__PURE__ */ jsxs("div", { className: `rb-tool is-${part.tool.status}`, children: [
284
+ /* @__PURE__ */ jsx("span", { className: "rb-tool-mark", children: part.tool.status === "completed" ? "\u2713" : part.tool.status === "failed" ? "!" : "\xB7" }),
285
+ /* @__PURE__ */ jsxs("span", { children: [
286
+ /* @__PURE__ */ jsx("strong", { children: part.tool.name }),
287
+ /* @__PURE__ */ jsxs("small", { children: [
288
+ part.tool.serverLabel,
289
+ " \xB7 ",
290
+ part.tool.status === "awaiting_output" ? "Awaiting client output" : part.tool.status
291
+ ] })
292
+ ] })
293
+ ] }) }, part.tool.id) : /* @__PURE__ */ jsx("div", { className: "rb-message rb-assistant-message", children: /* @__PURE__ */ jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], children: part.text.text }) }, part.text.id)),
294
+ parts.length === 0 && message.content && /* @__PURE__ */ jsx("div", { className: "rb-message rb-assistant-message", children: /* @__PURE__ */ jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], children: message.content }) }),
295
+ message.status === "cancelled" && /* @__PURE__ */ jsx("div", { className: "rb-cancelled", children: "Response stopped" })
296
+ ] });
297
+ }
298
+ function Inspector({ messages, apiLabel, agentName, continuityLabel }) {
299
+ return /* @__PURE__ */ jsxs("div", { className: "rb-inspector", children: [
300
+ /* @__PURE__ */ jsxs("div", { className: "rb-inspector-head", children: [
301
+ /* @__PURE__ */ jsxs("div", { children: [
302
+ /* @__PURE__ */ jsx("span", { children: "Execution" }),
303
+ /* @__PURE__ */ jsx("strong", { children: apiLabel })
304
+ ] }),
305
+ /* @__PURE__ */ jsxs("span", { className: "rb-live", children: [
306
+ /* @__PURE__ */ jsx("i", {}),
307
+ " LIVE"
308
+ ] })
309
+ ] }),
310
+ /* @__PURE__ */ jsxs("dl", { className: "rb-runtime-grid", children: [
311
+ /* @__PURE__ */ jsxs("div", { children: [
312
+ /* @__PURE__ */ jsx("dt", { children: "Agent" }),
313
+ /* @__PURE__ */ jsx("dd", { children: agentName })
314
+ ] }),
315
+ /* @__PURE__ */ jsxs("div", { children: [
316
+ /* @__PURE__ */ jsx("dt", { children: "Continuity" }),
317
+ /* @__PURE__ */ jsx("dd", { children: continuityLabel })
318
+ ] }),
319
+ /* @__PURE__ */ jsxs("div", { children: [
320
+ /* @__PURE__ */ jsx("dt", { children: "Transport" }),
321
+ /* @__PURE__ */ jsx("dd", { children: "Server-sent events" })
322
+ ] })
323
+ ] }),
324
+ messages.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "rb-inspector-empty", children: [
325
+ /* @__PURE__ */ jsx("span", { children: "{ }" }),
326
+ /* @__PURE__ */ jsxs("p", { children: [
327
+ "Send a message to inspect the raw ",
328
+ apiLabel,
329
+ " event stream."
330
+ ] })
331
+ ] }) : /* @__PURE__ */ jsx("div", { className: "rb-turns", children: messages.map((message, index) => /* @__PURE__ */ jsxs("section", { className: "rb-turn", children: [
332
+ /* @__PURE__ */ jsxs("header", { children: [
333
+ /* @__PURE__ */ jsxs("span", { children: [
334
+ "turn ",
335
+ index + 1
336
+ ] }),
337
+ /* @__PURE__ */ jsx("strong", { className: `is-${message.status}`, children: message.status }),
338
+ /* @__PURE__ */ jsxs("small", { children: [
339
+ message.projection?.events.length ?? 0,
340
+ " events"
341
+ ] })
342
+ ] }),
343
+ /* @__PURE__ */ jsx("div", { className: "rb-event-list", children: (message.projection?.events ?? []).map((event) => /* @__PURE__ */ jsxs("details", { children: [
344
+ /* @__PURE__ */ jsxs("summary", { children: [
345
+ /* @__PURE__ */ jsx("span", { children: event.event_id.slice(-8) }),
346
+ /* @__PURE__ */ jsx("strong", { children: event.type })
347
+ ] }),
348
+ /* @__PURE__ */ jsx("pre", { children: JSON.stringify(event, null, 2) })
349
+ ] }, event.event_id)) })
350
+ ] }, message.id)) })
351
+ ] });
352
+ }
353
+ export {
354
+ AgentChat,
355
+ AgentChatView
356
+ };
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@rebyteai/agent-ui",
3
+ "version": "0.2.0",
4
+ "description": "Optional React chat UI for Rebyte Agents",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/ReByteAI/rebyte-agent-toolkit.git",
9
+ "directory": "packages/ui"
10
+ },
11
+ "homepage": "https://github.com/ReByteAI/rebyte-agent-toolkit#readme",
12
+ "bugs": "https://github.com/ReByteAI/rebyte-agent-toolkit/issues",
13
+ "engines": {
14
+ "node": ">=22"
15
+ },
16
+ "type": "module",
17
+ "sideEffects": [
18
+ "./src/styles.css"
19
+ ],
20
+ "files": [
21
+ "dist",
22
+ "src/styles.css",
23
+ "README.md"
24
+ ],
25
+ "main": "./dist/index.cjs",
26
+ "module": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js",
32
+ "require": "./dist/index.cjs"
33
+ },
34
+ "./styles.css": "./src/styles.css"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "registry": "https://registry.npmjs.org"
39
+ },
40
+ "dependencies": {
41
+ "react-markdown": "^10.1.0",
42
+ "remark-gfm": "^4.0.1",
43
+ "@rebyteai/agent-react": "0.2.0"
44
+ },
45
+ "peerDependencies": {
46
+ "react": ">=18.2.0",
47
+ "react-dom": ">=18.2.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/react": "^19.2.0",
51
+ "@types/react-dom": "^19.2.0",
52
+ "react": "^19.2.0",
53
+ "react-dom": "^19.2.0",
54
+ "tsup": "^8.5.0",
55
+ "typescript": "^5.9.3"
56
+ },
57
+ "scripts": {
58
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean --external react --external react-dom",
59
+ "typecheck": "tsc --noEmit"
60
+ }
61
+ }
package/src/styles.css ADDED
@@ -0,0 +1,203 @@
1
+ .rb-agent-chat {
2
+ --rb-paper: #f5f1e8;
3
+ --rb-panel: #fbf9f4;
4
+ --rb-panel-raised: #fffdf8;
5
+ --rb-ink: #16283c;
6
+ --rb-muted: #6d7780;
7
+ --rb-faint: #a9aea9;
8
+ --rb-line: rgba(22, 40, 60, 0.13);
9
+ --rb-accent: #225d84;
10
+ --rb-accent-soft: #dfeaf0;
11
+ --rb-positive: #247052;
12
+ --rb-danger: #ad443d;
13
+ --rb-code: #10263a;
14
+ box-sizing: border-box;
15
+ display: grid;
16
+ grid-template-columns: 240px minmax(360px, 1fr) minmax(320px, 40%);
17
+ min-height: 680px;
18
+ height: 100dvh;
19
+ overflow: hidden;
20
+ color: var(--rb-ink);
21
+ background: var(--rb-paper);
22
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
23
+ }
24
+
25
+ .rb-agent-chat[data-theme="dark"] {
26
+ --rb-paper: #101922;
27
+ --rb-panel: #14212c;
28
+ --rb-panel-raised: #192935;
29
+ --rb-ink: #edf1ed;
30
+ --rb-muted: #9eabb4;
31
+ --rb-faint: #667681;
32
+ --rb-line: rgba(229, 237, 237, 0.12);
33
+ --rb-accent: #78b7d7;
34
+ --rb-accent-soft: #213c4b;
35
+ --rb-positive: #75c9a6;
36
+ --rb-danger: #f08b82;
37
+ --rb-code: #0b131a;
38
+ }
39
+ .rb-agent-chat.rb-no-inspector { grid-template-columns: 240px minmax(360px, 1fr); }
40
+
41
+ .rb-agent-chat *, .rb-agent-chat *::before, .rb-agent-chat *::after { box-sizing: inherit; }
42
+ .rb-agent-chat button, .rb-agent-chat textarea { font: inherit; }
43
+ .rb-agent-chat button { color: inherit; }
44
+
45
+ .rb-mobile-bar { display: none; }
46
+ .rb-sidebar {
47
+ display: flex;
48
+ min-width: 0;
49
+ flex-direction: column;
50
+ padding: 24px 18px 16px;
51
+ border-right: 1px solid var(--rb-line);
52
+ background: color-mix(in srgb, var(--rb-panel) 84%, transparent);
53
+ }
54
+ .rb-brand-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; padding: 0 5px 25px; }
55
+ .rb-brand, .rb-mobile-brand { font-family: Georgia, "Times New Roman", serif; font-size: 19px; font-weight: 700; letter-spacing: -0.03em; }
56
+ .rb-product-tag { color: var(--rb-accent); font-size: 10px; font-weight: 800; letter-spacing: .16em; }
57
+ .rb-new {
58
+ display: flex; align-items: center; gap: 9px; width: 100%; padding: 10px 11px;
59
+ border: 1px solid var(--rb-line); border-radius: 8px; background: var(--rb-panel-raised);
60
+ cursor: pointer; font-size: 13px; font-weight: 650; text-align: left;
61
+ }
62
+ .rb-new:hover { border-color: color-mix(in srgb, var(--rb-accent) 40%, var(--rb-line)); }
63
+ .rb-new span { color: var(--rb-accent); font-size: 18px; font-weight: 400; line-height: 1; }
64
+ .rb-sidebar-label { margin: 26px 8px 9px; color: var(--rb-faint); font-size: 10px; font-weight: 800; letter-spacing: .13em; text-transform: uppercase; }
65
+ .rb-runtime-card { display: flex; align-items: flex-start; gap: 10px; padding: 10px 8px; border-radius: 8px; background: var(--rb-accent-soft); }
66
+ .rb-runtime-card strong, .rb-runtime-card small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
67
+ .rb-runtime-card strong { font-size: 12px; font-weight: 750; }
68
+ .rb-runtime-card small { margin-top: 3px; color: var(--rb-muted); font-size: 10px; }
69
+ .rb-runtime-dot { width: 7px; height: 7px; flex: 0 0 auto; margin-top: 4px; border-radius: 50%; background: var(--rb-positive); box-shadow: 0 0 0 3px color-mix(in srgb, var(--rb-positive) 16%, transparent); }
70
+ .rb-runtime-dot.is-running { animation: rb-pulse 1.2s ease-in-out infinite; }
71
+ .rb-sidebar-spacer { flex: 1; }
72
+ .rb-sidebar-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 12px 5px 0; border-top: 1px solid var(--rb-line); color: var(--rb-muted); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 9px; }
73
+ .rb-sidebar-foot button { width: 29px; height: 29px; border: 0; border-radius: 50%; background: transparent; cursor: pointer; }
74
+ .rb-sidebar-foot button:hover { background: var(--rb-accent-soft); }
75
+
76
+ .rb-chat-pane { display: flex; min-width: 0; flex-direction: column; overflow: hidden; background: var(--rb-panel); }
77
+ .rb-messages { flex: 1; overflow-y: auto; padding: 36px clamp(24px, 5vw, 72px) 24px; scroll-behavior: smooth; }
78
+ .rb-welcome { max-width: 620px; margin: clamp(80px, 17vh, 170px) auto 0; }
79
+ .rb-eyebrow { display: block; margin-bottom: 16px; color: var(--rb-accent); font-size: 10px; font-weight: 850; letter-spacing: .16em; }
80
+ .rb-welcome h1 { max-width: 520px; margin: 0; font-family: Georgia, "Times New Roman", serif; font-size: clamp(40px, 5vw, 62px); font-weight: 500; line-height: .98; letter-spacing: -.05em; }
81
+ .rb-welcome p { max-width: 510px; margin: 22px 0 28px; color: var(--rb-muted); font-size: 15px; line-height: 1.65; }
82
+ .rb-event-sequence { display: flex; align-items: center; gap: 8px; color: var(--rb-muted); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 9px; text-transform: uppercase; }
83
+ .rb-event-sequence i { width: 18px; height: 1px; background: var(--rb-line); }
84
+ .rb-message { max-width: 720px; font-size: 15px; line-height: 1.7; }
85
+ .rb-user-turn { display: flex; max-width: min(720px, 88%); margin: 15px 0 28px auto; align-items: flex-end; flex-direction: column; gap: 8px; }
86
+ .rb-user-message { width: fit-content; padding: 10px 14px; border-radius: 14px 14px 3px 14px; background: var(--rb-accent-soft); white-space: pre-wrap; }
87
+ .rb-message-files { display: flex; max-width: 100%; flex-wrap: wrap; justify-content: flex-end; gap: 6px; }
88
+ .rb-message-files > span { display: flex; min-width: 0; max-width: 280px; align-items: center; gap: 8px; padding: 7px 10px 7px 8px; border: 1px solid var(--rb-line); border-radius: 8px; background: var(--rb-panel-raised); }
89
+ .rb-message-files svg { width: 19px; height: 19px; flex: 0 0 auto; color: var(--rb-accent); }
90
+ .rb-message-files span span { min-width: 0; }
91
+ .rb-message-files strong, .rb-message-files small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
92
+ .rb-message-files strong { font-size: 10px; }
93
+ .rb-message-files small { margin-top: 1px; color: var(--rb-muted); font-size: 8px; }
94
+ .rb-assistant-turn { max-width: 760px; margin: 0 auto 32px 0; }
95
+ .rb-assistant-message p { margin: 0 0 1em; }
96
+ .rb-assistant-message p:last-child { margin-bottom: 0; }
97
+ .rb-assistant-message pre { overflow-x: auto; padding: 14px; border-radius: 8px; color: #eaf1f4; background: var(--rb-code); }
98
+ .rb-assistant-message code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .88em; }
99
+ .rb-assistant-message :not(pre) > code { padding: 2px 5px; border-radius: 4px; color: var(--rb-accent); background: var(--rb-accent-soft); }
100
+ .rb-tools { display: grid; gap: 7px; margin: 0 0 14px; }
101
+ .rb-tool { display: flex; width: fit-content; align-items: center; gap: 9px; padding: 7px 10px; border: 1px solid var(--rb-line); border-radius: 7px; background: var(--rb-panel-raised); }
102
+ .rb-tool-mark { display: grid; width: 19px; height: 19px; place-items: center; border-radius: 50%; color: var(--rb-panel); background: var(--rb-accent); font-size: 11px; }
103
+ .rb-tool.is-completed .rb-tool-mark { background: var(--rb-positive); }
104
+ .rb-tool.is-failed .rb-tool-mark { background: var(--rb-danger); }
105
+ .rb-tool strong, .rb-tool small { display: block; }
106
+ .rb-tool strong { font-size: 11px; }
107
+ .rb-tool small { margin-top: 1px; color: var(--rb-muted); font-size: 9px; }
108
+ .rb-running, .rb-cancelled { display: flex; align-items: center; gap: 8px; color: var(--rb-muted); font-size: 11px; }
109
+ .rb-running i { width: 6px; height: 6px; border-radius: 50%; background: var(--rb-accent); animation: rb-pulse 1.1s ease-in-out infinite; }
110
+ .rb-composer-wrap { padding: 12px clamp(20px, 5vw, 66px) 19px; background: linear-gradient(transparent, var(--rb-panel) 18%); }
111
+ .rb-composer { display: flex; max-width: 760px; margin: 0 auto; align-items: flex-end; gap: 10px; padding: 8px 8px 8px 16px; border: 1px solid var(--rb-line); border-radius: 13px; background: var(--rb-panel-raised); box-shadow: 0 10px 28px rgba(14, 32, 48, .07); }
112
+ .rb-composer:focus-within { border-color: color-mix(in srgb, var(--rb-accent) 46%, var(--rb-line)); box-shadow: 0 10px 34px rgba(14, 32, 48, .1); }
113
+ .rb-composer-main { min-width: 0; flex: 1; }
114
+ .rb-composer textarea { display: block; width: 100%; min-height: 34px; max-height: 180px; padding: 7px 0; resize: none; border: 0; outline: 0; color: var(--rb-ink); background: transparent; line-height: 1.45; }
115
+ .rb-composer textarea::placeholder { color: var(--rb-faint); }
116
+ .rb-file-input { position: fixed; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
117
+ .rb-attach, .rb-send, .rb-stop { display: grid; width: 36px; height: 36px; flex: 0 0 auto; place-items: center; border: 0; border-radius: 9px; cursor: pointer; }
118
+ .rb-attach { color: var(--rb-muted); background: transparent; }
119
+ .rb-attach:hover { color: var(--rb-accent); background: var(--rb-accent-soft); }
120
+ .rb-attach:disabled { opacity: .35; cursor: default; }
121
+ .rb-send { color: white !important; background: var(--rb-accent); }
122
+ .rb-stop { color: white !important; background: var(--rb-danger); }
123
+ .rb-send:disabled { opacity: .28; cursor: default; }
124
+ .rb-attach svg, .rb-send svg, .rb-stop svg { width: 17px; height: 17px; }
125
+ .rb-composer-files { display: flex; flex-wrap: wrap; gap: 6px; padding: 2px 0 6px; }
126
+ .rb-composer-file { display: grid; width: min(270px, 100%); min-width: 0; grid-template-columns: 28px minmax(0, 1fr) 22px; align-items: center; gap: 7px; padding: 6px 6px 6px 7px; border: 1px solid var(--rb-line); border-radius: 8px; background: var(--rb-panel); }
127
+ .rb-composer-file.is-uploading { grid-template-columns: 28px minmax(0, 1fr); }
128
+ .rb-composer-file.is-failed { border-color: color-mix(in srgb, var(--rb-danger) 38%, var(--rb-line)); }
129
+ .rb-file-icon { display: grid; width: 28px; height: 28px; place-items: center; border-radius: 6px; color: var(--rb-accent); background: var(--rb-accent-soft); }
130
+ .rb-file-icon svg { width: 16px; height: 16px; }
131
+ .rb-file-copy { min-width: 0; }
132
+ .rb-file-copy strong, .rb-file-copy small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
133
+ .rb-file-copy strong { font-size: 10px; }
134
+ .rb-file-copy small { margin-top: 2px; color: var(--rb-muted); font-size: 8px; }
135
+ .rb-composer-file.is-failed .rb-file-copy small { color: var(--rb-danger); }
136
+ .rb-file-copy progress { display: block; width: 100%; height: 2px; margin-top: 5px; overflow: hidden; border: 0; border-radius: 2px; background: var(--rb-line); appearance: none; }
137
+ .rb-file-copy progress::-webkit-progress-bar { background: var(--rb-line); }
138
+ .rb-file-copy progress::-webkit-progress-value { background: var(--rb-accent); }
139
+ .rb-file-copy progress::-moz-progress-bar { background: var(--rb-accent); }
140
+ .rb-composer-file > button { display: grid; width: 22px; height: 22px; place-items: center; padding: 0; border: 0; border-radius: 50%; color: var(--rb-muted); background: transparent; cursor: pointer; font-size: 17px; line-height: 1; }
141
+ .rb-composer-file > button:hover { color: var(--rb-danger); background: color-mix(in srgb, var(--rb-danger) 9%, transparent); }
142
+ .rb-composer-wrap > small { display: block; max-width: 760px; margin: 7px auto 0; color: var(--rb-faint); font-size: 9px; text-align: center; }
143
+ .rb-error { max-width: 760px; margin: 0 auto 8px; padding: 8px 10px; border: 1px solid color-mix(in srgb, var(--rb-danger) 35%, transparent); border-radius: 7px; color: var(--rb-danger); background: color-mix(in srgb, var(--rb-danger) 8%, transparent); font-size: 11px; }
144
+
145
+ .rb-inspector-pane { min-width: 0; overflow: hidden; border-left: 1px solid var(--rb-line); background: var(--rb-code); color: #dfe9ed; }
146
+ .rb-inspector { display: flex; height: 100%; flex-direction: column; }
147
+ .rb-inspector-head { display: flex; align-items: center; justify-content: space-between; padding: 20px 22px; border-bottom: 1px solid rgba(255,255,255,.1); }
148
+ .rb-inspector-head span, .rb-inspector-head strong { display: block; }
149
+ .rb-inspector-head span { color: #7f95a2; font-size: 9px; font-weight: 800; letter-spacing: .13em; text-transform: uppercase; }
150
+ .rb-inspector-head strong { margin-top: 3px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
151
+ .rb-live { display: flex !important; align-items: center; gap: 7px; color: #77c9a8 !important; }
152
+ .rb-live i { width: 6px; height: 6px; border-radius: 50%; background: #77c9a8; box-shadow: 0 0 0 3px rgba(119,201,168,.13); }
153
+ .rb-runtime-grid { display: grid; grid-template-columns: repeat(3, 1fr); margin: 0; padding: 14px 22px; border-bottom: 1px solid rgba(255,255,255,.08); }
154
+ .rb-runtime-grid div { min-width: 0; padding-right: 10px; }
155
+ .rb-runtime-grid dt { color: #718793; font-size: 8px; font-weight: 750; letter-spacing: .1em; text-transform: uppercase; }
156
+ .rb-runtime-grid dd { overflow: hidden; margin: 4px 0 0; color: #bfd0d7; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 8px; text-overflow: ellipsis; white-space: nowrap; }
157
+ .rb-inspector-empty { display: grid; flex: 1; place-content: center; padding: 35px; text-align: center; }
158
+ .rb-inspector-empty span { color: #48606d; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 55px; letter-spacing: -.2em; }
159
+ .rb-inspector-empty p { max-width: 240px; color: #78909c; font-size: 11px; line-height: 1.6; }
160
+ .rb-turns { flex: 1; overflow-y: auto; padding: 17px 17px 30px; }
161
+ .rb-turn { margin-bottom: 16px; border: 1px solid rgba(255,255,255,.09); border-radius: 8px; overflow: hidden; }
162
+ .rb-turn > header { display: flex; align-items: center; gap: 8px; padding: 9px 11px; background: rgba(255,255,255,.035); }
163
+ .rb-turn > header span { color: #9fb1ba; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 9px; text-transform: uppercase; }
164
+ .rb-turn > header strong { color: #77c9a8; font-size: 8px; letter-spacing: .08em; text-transform: uppercase; }
165
+ .rb-turn > header strong.is-failed { color: #ee8b82; }
166
+ .rb-turn > header small { margin-left: auto; color: #657d89; font-size: 8px; }
167
+ .rb-event-list details { border-top: 1px solid rgba(255,255,255,.065); }
168
+ .rb-event-list summary { display: flex; gap: 9px; padding: 8px 10px; cursor: pointer; list-style: none; }
169
+ .rb-event-list summary::-webkit-details-marker { display: none; }
170
+ .rb-event-list summary span { width: 25px; color: #607987; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 8px; }
171
+ .rb-event-list summary strong { color: #a8bec8; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 8px; font-weight: 500; }
172
+ .rb-event-list pre { max-height: 260px; overflow: auto; margin: 0; padding: 10px 12px 13px; border-top: 1px solid rgba(255,255,255,.05); color: #87a2ae; background: rgba(0,0,0,.16); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 8px; line-height: 1.55; white-space: pre-wrap; word-break: break-word; }
173
+
174
+ @keyframes rb-pulse { 50% { opacity: .35; transform: scale(.83); } }
175
+
176
+ @media (max-width: 1040px) {
177
+ .rb-agent-chat { grid-template-columns: 205px minmax(340px, 1fr) minmax(290px, 38%); }
178
+ .rb-agent-chat.rb-no-inspector { grid-template-columns: 205px minmax(340px, 1fr); }
179
+ .rb-runtime-grid { grid-template-columns: 1fr; gap: 8px; }
180
+ }
181
+
182
+ @media (max-width: 820px) {
183
+ .rb-agent-chat { display: grid; grid-template: 52px minmax(0, 1fr) / 1fr; min-height: 560px; }
184
+ .rb-mobile-bar { display: flex; grid-row: 1; align-items: center; justify-content: space-between; padding: 0 14px; border-bottom: 1px solid var(--rb-line); background: var(--rb-panel); }
185
+ .rb-mobile-brand { font-size: 16px; }
186
+ .rb-pane-switch { display: flex; padding: 3px; border-radius: 7px; background: var(--rb-accent-soft); }
187
+ .rb-pane-switch button { padding: 5px 10px; border: 0; border-radius: 5px; color: var(--rb-muted); background: transparent; cursor: pointer; font-size: 10px; font-weight: 700; }
188
+ .rb-pane-switch button.is-active { color: var(--rb-ink); background: var(--rb-panel-raised); box-shadow: 0 1px 3px rgba(0,0,0,.08); }
189
+ .rb-sidebar { display: none; }
190
+ .rb-chat-pane, .rb-inspector-pane { display: none; grid-row: 2; grid-column: 1; border: 0; }
191
+ .rb-chat-pane.is-mobile-visible, .rb-inspector-pane.is-mobile-visible { display: flex; }
192
+ .rb-messages { padding: 24px 18px 18px; }
193
+ .rb-welcome { margin-top: 12vh; }
194
+ .rb-welcome h1 { font-size: 42px; }
195
+ .rb-event-sequence { flex-wrap: wrap; }
196
+ .rb-composer-wrap { padding: 10px 12px 14px; }
197
+ .rb-composer { gap: 6px; padding-left: 7px; }
198
+ .rb-composer-file { width: 100%; }
199
+ }
200
+
201
+ @media (prefers-reduced-motion: reduce) {
202
+ .rb-agent-chat *, .rb-agent-chat *::before, .rb-agent-chat *::after { scroll-behavior: auto !important; animation-duration: .01ms !important; animation-iteration-count: 1 !important; }
203
+ }