@coral-ai/chat 0.3.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/dist/index.js ADDED
@@ -0,0 +1,1537 @@
1
+ // src/Chat.tsx
2
+ import {
3
+ InteractionTag,
4
+ InteractionTagPrimary,
5
+ makeStyles as makeStyles8,
6
+ motionTokens
7
+ } from "@fluentui/react-components";
8
+
9
+ // src/Chat.Canvas.tsx
10
+ import { useState, useEffect, useLayoutEffect, useRef, useCallback } from "react";
11
+ import { Button, makeStyles, tokens } from "@fluentui/react-components";
12
+ import { ArrowDown } from "@coral-ai/bundleicons";
13
+
14
+ // src/Chat.Context.tsx
15
+ import { createContext, useContext } from "react";
16
+ var ChatContext = createContext({});
17
+ function useChatContext() {
18
+ return useContext(ChatContext);
19
+ }
20
+
21
+ // src/Chat.Canvas.tsx
22
+ import { jsx, jsxs } from "react/jsx-runtime";
23
+ var useStyles = makeStyles({
24
+ root: {
25
+ width: "100%",
26
+ display: "flex",
27
+ flexDirection: "column",
28
+ flex: "1",
29
+ minHeight: "0",
30
+ position: "relative"
31
+ },
32
+ scrollToBottom: {
33
+ position: "absolute",
34
+ left: "50%",
35
+ transform: "translateX(-50%)",
36
+ zIndex: 1e3,
37
+ cursor: "pointer",
38
+ boxShadow: tokens.shadow8,
39
+ marginBottom: "16px"
40
+ }
41
+ });
42
+ function isAsyncIterable(value) {
43
+ return value !== null && typeof value === "object" && Symbol.asyncIterator in value;
44
+ }
45
+ function isRateLimitError(err) {
46
+ const e = err;
47
+ const status = Number(e?.status ?? e?.statusCode ?? 0);
48
+ const msg = String(e?.message ?? "").toLowerCase();
49
+ return status === 429 || msg.includes("rate limit") || msg.includes("too many requests");
50
+ }
51
+ var ChatCanvas = ({
52
+ agent,
53
+ agents,
54
+ conversationId,
55
+ initialMessages,
56
+ systemPrompt,
57
+ onSendMessage,
58
+ onMessageComplete,
59
+ onMessagesChange,
60
+ onError,
61
+ autoSendMessage,
62
+ thinkingStatus,
63
+ onStop,
64
+ children
65
+ }) => {
66
+ const styles = useStyles();
67
+ const agentKey = agent ?? "default";
68
+ const resolvedHandler = onSendMessage ?? agents?.[agentKey]?.handler ?? agents?.["default"]?.handler;
69
+ const [messages, setMessages] = useState(initialMessages ?? []);
70
+ const [input, setInput] = useState("");
71
+ const [isTyping, setIsTyping] = useState(false);
72
+ const [isRetrying, setIsRetrying] = useState(false);
73
+ const [chatError, setChatError] = useState(null);
74
+ const [showScrollButton, setShowScrollButton] = useState(false);
75
+ const [inputHeight, setInputHeightState] = useState(0);
76
+ const [isInterrupted, setIsInterrupted] = useState(false);
77
+ const [streamingReasoning, setStreamingReasoning] = useState("");
78
+ const [streamingAgentName, setStreamingAgentName] = useState();
79
+ const [attachments, setAttachments] = useState([]);
80
+ const reasoningRef = useRef("");
81
+ const lastUserInputRef = useRef("");
82
+ const hasEverSentRef = useRef(false);
83
+ const currentConversationIdRef = useRef(conversationId);
84
+ const messagesContainerRef = useRef(null);
85
+ const [messagesEl, setMessagesEl] = useState(null);
86
+ const bottomRef = useRef(null);
87
+ const shouldStopRef = useRef(false);
88
+ const userScrolledUpRef = useRef(false);
89
+ useEffect(() => {
90
+ setMessages((current) => {
91
+ if ((initialMessages?.length ?? 0) > 0 || current.length === 0) {
92
+ return initialMessages ?? [];
93
+ }
94
+ return current;
95
+ });
96
+ }, [initialMessages]);
97
+ useEffect(() => {
98
+ currentConversationIdRef.current = conversationId;
99
+ }, [conversationId]);
100
+ const autoSentRef = useRef(false);
101
+ useEffect(() => {
102
+ if (autoSendMessage?.trim() && !autoSentRef.current) {
103
+ autoSentRef.current = true;
104
+ sendMessage(autoSendMessage);
105
+ }
106
+ }, []);
107
+ const registerMessagesContainer = useCallback((el) => {
108
+ messagesContainerRef.current = el;
109
+ setMessagesEl(el);
110
+ }, []);
111
+ useLayoutEffect(() => {
112
+ if (!userScrolledUpRef.current) {
113
+ if (messagesEl) {
114
+ messagesEl.scrollTop = messagesEl.scrollHeight;
115
+ }
116
+ setShowScrollButton(false);
117
+ }
118
+ }, [messages, messagesEl]);
119
+ useEffect(() => {
120
+ const container = messagesEl;
121
+ if (!container) return;
122
+ const handleScroll = () => {
123
+ const { scrollTop, scrollHeight, clientHeight } = container;
124
+ const atBottom = scrollTop + clientHeight >= scrollHeight - 80;
125
+ userScrolledUpRef.current = !atBottom;
126
+ setShowScrollButton(!atBottom);
127
+ };
128
+ container.addEventListener("scroll", handleScroll, { passive: true });
129
+ return () => container.removeEventListener("scroll", handleScroll);
130
+ }, [messagesEl]);
131
+ const scrollToBottom = () => {
132
+ userScrolledUpRef.current = false;
133
+ bottomRef.current?.scrollIntoView({ behavior: "smooth" });
134
+ setShowScrollButton(false);
135
+ };
136
+ const handleCopy = (content) => {
137
+ navigator.clipboard.writeText(content).catch(console.error);
138
+ };
139
+ const addAttachments = (files) => {
140
+ const newItems = files.map((file) => {
141
+ const attachment = { id: crypto.randomUUID(), file };
142
+ if (file.type.startsWith("image/")) {
143
+ const reader = new FileReader();
144
+ reader.onload = (e) => {
145
+ const preview = e.target?.result;
146
+ setAttachments(
147
+ (prev) => prev.map((a) => a.id === attachment.id ? { ...a, preview } : a)
148
+ );
149
+ };
150
+ reader.readAsDataURL(file);
151
+ }
152
+ return attachment;
153
+ });
154
+ setAttachments((prev) => [...prev, ...newItems]);
155
+ };
156
+ const removeAttachment = (id) => {
157
+ setAttachments((prev) => prev.filter((a) => a.id !== id));
158
+ };
159
+ const sendMessage = async (overrideInput) => {
160
+ const textToSend = overrideInput ?? input;
161
+ if (!textToSend.trim()) return;
162
+ hasEverSentRef.current = true;
163
+ const sessionAtSend = conversationId;
164
+ const baseMessages = overrideInput ? messages.filter((_, i) => i < messages.length - 2) : messages;
165
+ const snapshotAttachments = attachments;
166
+ const attachmentMeta = snapshotAttachments.map((a) => ({
167
+ name: a.file.name,
168
+ type: a.file.type,
169
+ preview: a.preview
170
+ }));
171
+ const updatedMessages = [
172
+ ...baseMessages,
173
+ {
174
+ id: crypto.randomUUID(),
175
+ role: "user",
176
+ content: textToSend.replace(/^[\n\r]+|[\n\r]+$/g, ""),
177
+ ...attachmentMeta.length > 0 ? { metadata: { attachments: attachmentMeta } } : {}
178
+ }
179
+ ];
180
+ lastUserInputRef.current = textToSend;
181
+ userScrolledUpRef.current = false;
182
+ setMessages(updatedMessages);
183
+ onMessagesChange?.(updatedMessages, sessionAtSend);
184
+ if (!overrideInput) setInput("");
185
+ setAttachments([]);
186
+ setIsTyping(true);
187
+ setIsInterrupted(false);
188
+ setChatError(null);
189
+ shouldStopRef.current = false;
190
+ reasoningRef.current = "";
191
+ setStreamingReasoning("");
192
+ setStreamingAgentName(void 0);
193
+ const historyForAI = systemPrompt ? [{ role: "system", content: systemPrompt }, ...updatedMessages] : updatedMessages;
194
+ const MAX_RETRIES = 3;
195
+ try {
196
+ if (resolvedHandler) {
197
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
198
+ try {
199
+ const assistantId = crypto.randomUUID();
200
+ const placeholder = [...updatedMessages, { id: assistantId, role: "assistant", content: "" }];
201
+ setMessages(placeholder);
202
+ onMessagesChange?.(placeholder, sessionAtSend);
203
+ const response = await resolvedHandler(textToSend, historyForAI, snapshotAttachments.map((a) => a.file));
204
+ if (isAsyncIterable(response)) {
205
+ let assistantContent = "";
206
+ let rafId = null;
207
+ let pendingContent = "";
208
+ for await (const chunk of response) {
209
+ if (shouldStopRef.current) break;
210
+ if (sessionAtSend !== void 0 && currentConversationIdRef.current !== sessionAtSend) break;
211
+ if (typeof chunk === "string") {
212
+ assistantContent += chunk;
213
+ pendingContent = assistantContent;
214
+ if (rafId === null) {
215
+ rafId = requestAnimationFrame(() => {
216
+ const content = pendingContent;
217
+ const nextMsg = { id: assistantId, role: "assistant", content };
218
+ setMessages((prev) => {
219
+ const updated = [...prev];
220
+ updated[updated.length - 1] = nextMsg;
221
+ return updated;
222
+ });
223
+ onMessagesChange?.([...updatedMessages, nextMsg], sessionAtSend);
224
+ rafId = null;
225
+ });
226
+ }
227
+ } else if (chunk.type === "reasoning") {
228
+ reasoningRef.current += chunk.content;
229
+ setStreamingReasoning(reasoningRef.current);
230
+ } else if (chunk.type === "agent") {
231
+ setStreamingAgentName(chunk.name);
232
+ }
233
+ }
234
+ if (rafId !== null) cancelAnimationFrame(rafId);
235
+ if (shouldStopRef.current) setIsInterrupted(true);
236
+ if (sessionAtSend !== void 0 && currentConversationIdRef.current !== sessionAtSend) {
237
+ break;
238
+ }
239
+ const finalMsg = {
240
+ id: assistantId,
241
+ role: "assistant",
242
+ content: assistantContent,
243
+ ...reasoningRef.current ? { metadata: { reasoning: reasoningRef.current } } : {}
244
+ };
245
+ const finalMessages = [...updatedMessages, finalMsg];
246
+ setMessages(finalMessages);
247
+ onMessagesChange?.(finalMessages, sessionAtSend);
248
+ onMessageComplete?.(finalMessages, sessionAtSend);
249
+ } else if (typeof response === "string") {
250
+ if (currentConversationIdRef.current !== sessionAtSend) break;
251
+ const finalMessages = [...updatedMessages, { id: assistantId, role: "assistant", content: response }];
252
+ setMessages(finalMessages);
253
+ onMessagesChange?.(finalMessages, sessionAtSend);
254
+ onMessageComplete?.(finalMessages, sessionAtSend);
255
+ } else {
256
+ if (currentConversationIdRef.current !== sessionAtSend) break;
257
+ const assistantMsg = {
258
+ id: assistantId,
259
+ role: "assistant",
260
+ content: response.content,
261
+ type: response.type,
262
+ metadata: response.metadata
263
+ };
264
+ const finalMessages = [...updatedMessages, assistantMsg];
265
+ setMessages(finalMessages);
266
+ onMessagesChange?.(finalMessages, sessionAtSend);
267
+ onMessageComplete?.(finalMessages, sessionAtSend);
268
+ }
269
+ break;
270
+ } catch (err) {
271
+ if (isRateLimitError(err) && attempt < MAX_RETRIES - 1) {
272
+ setIsRetrying(true);
273
+ await new Promise((r) => setTimeout(r, (attempt + 1) * 3e3));
274
+ continue;
275
+ }
276
+ throw err;
277
+ }
278
+ }
279
+ }
280
+ } catch (err) {
281
+ onError?.(err);
282
+ setChatError(err);
283
+ } finally {
284
+ setIsRetrying(false);
285
+ setIsTyping(false);
286
+ }
287
+ };
288
+ return /* @__PURE__ */ jsx(
289
+ ChatContext.Provider,
290
+ {
291
+ value: {
292
+ sendMessage,
293
+ scrollToBottom,
294
+ handleCopy,
295
+ messages,
296
+ setMessages,
297
+ input,
298
+ setInput,
299
+ isTyping,
300
+ isRetrying,
301
+ isInterrupted,
302
+ streamingReasoning,
303
+ streamingAgentName,
304
+ thinkingStatus,
305
+ chatError,
306
+ showScrollButton,
307
+ setInputHeight: setInputHeightState,
308
+ messagesContainerRef,
309
+ registerMessagesContainer,
310
+ bottomRef,
311
+ lastUserInputRef,
312
+ hasEverSentRef,
313
+ shouldStopRef,
314
+ onStop,
315
+ attachments,
316
+ addAttachments,
317
+ removeAttachment
318
+ },
319
+ children: /* @__PURE__ */ jsxs("div", { className: styles.root, children: [
320
+ children,
321
+ showScrollButton && /* @__PURE__ */ jsx(
322
+ Button,
323
+ {
324
+ onClick: scrollToBottom,
325
+ className: styles.scrollToBottom,
326
+ shape: "circular",
327
+ style: {
328
+ bottom: inputHeight + 16,
329
+ backgroundColor: "transparent",
330
+ border: `1px solid ${tokens.colorNeutralStroke3}`,
331
+ backdropFilter: "saturate(180%) blur(16px)"
332
+ },
333
+ icon: /* @__PURE__ */ jsx(ArrowDown, { style: { fontSize: "16px" } })
334
+ }
335
+ )
336
+ ] })
337
+ }
338
+ );
339
+ };
340
+ var Chat_Canvas_default = ChatCanvas;
341
+
342
+ // src/Chat.Messages.tsx
343
+ import React4, { useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
344
+ import { Body1 as Body14, Button as Button4, makeStyles as makeStyles6, tokens as tokens6 } from "@fluentui/react-components";
345
+ import { Copy, Heart } from "@coral-ai/bundleicons";
346
+ import { CoralMarkdown } from "@coral-ai/markdown";
347
+
348
+ // src/Chat.ThinkingIndicator.tsx
349
+ import { useState as useState2, useEffect as useEffect2, useRef as useRef2 } from "react";
350
+ import { makeStyles as makeStyles2, tokens as tokens2 } from "@fluentui/react-components";
351
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
352
+ var thinkingMessages = [
353
+ "Generating",
354
+ "Thinking",
355
+ "Optimizing",
356
+ "Processing",
357
+ "Analyzing",
358
+ "Reasoning",
359
+ "Drafting",
360
+ "Evaluating",
361
+ "Computing",
362
+ "Synthesizing",
363
+ "Reviewing",
364
+ "Refining"
365
+ ];
366
+ var retryingMessages = [
367
+ "Still working",
368
+ "Awaiting",
369
+ "Hang tight",
370
+ "One moment",
371
+ "Almost there",
372
+ "Just a moment",
373
+ "Reconnecting"
374
+ ];
375
+ var TYPING_SPEED = 80;
376
+ var HOLD_DURATION = 1300;
377
+ var SPINNER_BG = "#000000";
378
+ var useStyles2 = makeStyles2({
379
+ root: {
380
+ display: "flex",
381
+ alignItems: "center",
382
+ gap: "8px",
383
+ width: "100%",
384
+ color: tokens2.colorNeutralForeground3
385
+ // fontSize: "14px",
386
+ },
387
+ icon: {
388
+ width: "28px",
389
+ height: "28px",
390
+ flexShrink: 0,
391
+ border: `3px solid ${SPINNER_BG}`,
392
+ boxSizing: "border-box",
393
+ backgroundColor: "${SPINNER_BG}",
394
+ opacity: 0.8,
395
+ backgroundImage: [
396
+ `radial-gradient(farthest-side, ${tokens2.colorNeutralForeground1} 98%, ${SPINNER_BG})`,
397
+ `radial-gradient(farthest-side, ${tokens2.colorNeutralForeground1} 98%, ${SPINNER_BG})`,
398
+ `radial-gradient(farthest-side, ${tokens2.colorNeutralForeground1} 98%, ${SPINNER_BG})`,
399
+ `radial-gradient(farthest-side, ${tokens2.colorNeutralForeground1} 98%, ${SPINNER_BG})`,
400
+ `radial-gradient(farthest-side, ${tokens2.colorNeutralForeground1} 98%, ${SPINNER_BG})`,
401
+ `linear-gradient(${tokens2.colorNeutralForeground1} 0 0)`,
402
+ `linear-gradient(${tokens2.colorNeutralForeground1} 0 0)`
403
+ ].join(", "),
404
+ backgroundSize: "7px 7px, 7px 7px, 7px 7px, 7px 7px, 7px 7px, 100% 3px, 3px 100%",
405
+ backgroundPosition: "50%, 50%, 50%, 50%, 50%, 50%, 50%",
406
+ backgroundRepeat: "no-repeat",
407
+ filter: "blur(2px) contrast(10)",
408
+ animationName: {
409
+ "100%": {
410
+ backgroundPosition: "50% -7px, -7px 50%, 22px 50%, 50% 22px, 50%, 50%, 50%"
411
+ }
412
+ },
413
+ animationDuration: "0.8s",
414
+ animationIterationCount: "infinite",
415
+ animationTimingFunction: "ease"
416
+ },
417
+ text: {
418
+ // fontFamily: tokens.fontFamilyBase,
419
+ // fontSize: tokens.fontSizeBase200,
420
+ // color: tokens.colorNeutralForeground3,
421
+ fontFamily: "var(--fontFamilyMonospace)",
422
+ minWidth: "0"
423
+ },
424
+ cursor: {
425
+ display: "inline-block",
426
+ width: "6px",
427
+ height: "1em",
428
+ backgroundColor: tokens2.colorNeutralForeground3,
429
+ marginLeft: "1px",
430
+ verticalAlign: "text-bottom",
431
+ animationName: {
432
+ "0%, 100%": { opacity: "1" },
433
+ "50%": { opacity: "0" }
434
+ },
435
+ animationDuration: "0.9s",
436
+ animationTimingFunction: "step-end",
437
+ animationIterationCount: "infinite"
438
+ }
439
+ });
440
+ var ThinkingIndicator = ({ isRetrying = false, status }) => {
441
+ const styles = useStyles2();
442
+ const [displayText, setDisplayText] = useState2("");
443
+ const timerRef = useRef2(void 0);
444
+ const msgIdxRef = useRef2(0);
445
+ const charIdxRef = useRef2(0);
446
+ useEffect2(() => {
447
+ const pool = isRetrying ? retryingMessages : thinkingMessages;
448
+ msgIdxRef.current = 0;
449
+ charIdxRef.current = 0;
450
+ const tick = () => {
451
+ const word = pool[msgIdxRef.current];
452
+ if (charIdxRef.current < word.length) {
453
+ charIdxRef.current += 1;
454
+ setDisplayText(word.slice(0, charIdxRef.current));
455
+ timerRef.current = setTimeout(tick, TYPING_SPEED);
456
+ } else {
457
+ timerRef.current = setTimeout(() => {
458
+ msgIdxRef.current = (msgIdxRef.current + 1) % pool.length;
459
+ charIdxRef.current = 0;
460
+ tick();
461
+ }, HOLD_DURATION);
462
+ }
463
+ };
464
+ tick();
465
+ return () => clearTimeout(timerRef.current);
466
+ }, [isRetrying]);
467
+ return /* @__PURE__ */ jsxs2("div", { className: styles.root, children: [
468
+ /* @__PURE__ */ jsx2("div", { className: styles.icon }),
469
+ /* @__PURE__ */ jsxs2("span", { className: styles.text, children: [
470
+ status ?? displayText,
471
+ /* @__PURE__ */ jsx2("span", { className: styles.cursor })
472
+ ] })
473
+ ] });
474
+ };
475
+ var Chat_ThinkingIndicator_default = ThinkingIndicator;
476
+
477
+ // src/Chat.Callout.tsx
478
+ import { Body1, Button as Button2, makeStyles as makeStyles3, mergeClasses, tokens as tokens3 } from "@fluentui/react-components";
479
+ import { ChatWarning20Regular } from "@fluentui/react-icons";
480
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
481
+ var useStyles3 = makeStyles3({
482
+ root: {
483
+ display: "inline-flex",
484
+ alignItems: "center",
485
+ justifyContent: "space-between",
486
+ gap: "8px",
487
+ padding: "12px 12px",
488
+ borderRadius: tokens3.borderRadiusXLarge,
489
+ backgroundColor: tokens3.colorNeutralBackground3,
490
+ border: `1px solid ${tokens3.colorNeutralStroke2}`,
491
+ color: tokens3.colorNeutralForeground3,
492
+ width: "100%",
493
+ boxSizing: "border-box",
494
+ margin: "24px 0"
495
+ },
496
+ left: {
497
+ display: "flex",
498
+ alignItems: "center",
499
+ gap: "8px"
500
+ },
501
+ label: {
502
+ fontFamily: "var(--fontFamilyMonospace)"
503
+ },
504
+ retryButton: {
505
+ minWidth: 0
506
+ }
507
+ });
508
+ var ChatCallout = ({ message, onRetry, className }) => {
509
+ const styles = useStyles3();
510
+ return /* @__PURE__ */ jsxs3("div", { className: mergeClasses(styles.root, className), children: [
511
+ /* @__PURE__ */ jsxs3("span", { className: styles.left, children: [
512
+ /* @__PURE__ */ jsx3(ChatWarning20Regular, {}),
513
+ /* @__PURE__ */ jsx3(Body1, { className: styles.label, children: message })
514
+ ] }),
515
+ onRetry && /* @__PURE__ */ jsx3(
516
+ Button2,
517
+ {
518
+ onClick: onRetry,
519
+ className: styles.retryButton,
520
+ size: "small",
521
+ appearance: "primary",
522
+ children: "Retry"
523
+ }
524
+ )
525
+ ] });
526
+ };
527
+ var Chat_Callout_default = ChatCallout;
528
+
529
+ // src/Chat.Error.tsx
530
+ import { Body1 as Body12, Button as Button3, makeStyles as makeStyles4, tokens as tokens4 } from "@fluentui/react-components";
531
+ import {
532
+ ErrorCircle20Regular,
533
+ Clock20Regular,
534
+ LockClosed20Regular,
535
+ PlugDisconnected20Regular,
536
+ Warning20Regular
537
+ } from "@fluentui/react-icons";
538
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
539
+ function normalizeChatError(err) {
540
+ const anyErr = err;
541
+ const rawMessage = String(
542
+ anyErr?.message ?? anyErr?.error?.message ?? err ?? ""
543
+ ).toLowerCase();
544
+ const status = Number(anyErr?.status ?? anyErr?.statusCode ?? 0);
545
+ const errorType = String(
546
+ anyErr?.error?.type ?? anyErr?.type ?? anyErr?.code ?? ""
547
+ );
548
+ const errorCode = String(
549
+ anyErr?.error?.code ?? anyErr?.code ?? ""
550
+ );
551
+ if (rawMessage.includes("credit") || rawMessage.includes("billing") || rawMessage.includes("insufficient_quota") || errorCode === "insufficient_quota" || errorType === "PERMISSION_DENIED") {
552
+ return {
553
+ category: "auth_billing",
554
+ message: "API credits exhausted. Add credits in your provider's billing dashboard."
555
+ };
556
+ }
557
+ if (errorType === "rate_limit_error" || rawMessage.includes("rate limit") || rawMessage.includes("too many requests")) {
558
+ return {
559
+ category: "rate_limit",
560
+ message: "You're being rate limited. Wait a moment and try again."
561
+ };
562
+ }
563
+ if (status === 529 || status === 503 || errorType === "overloaded_error" || errorType === "RESOURCE_EXHAUSTED" || rawMessage.includes("overload") || rawMessage.includes("capacity")) {
564
+ return {
565
+ category: "overloaded",
566
+ message: "The AI service is overloaded right now. Try again in a few seconds."
567
+ };
568
+ }
569
+ if (status === 404 || errorType === "NOT_FOUND" || rawMessage.includes("model_not_found") || rawMessage.includes("not found")) {
570
+ return {
571
+ category: "not_found",
572
+ message: "Model not found. Check the model name in your configuration."
573
+ };
574
+ }
575
+ if (status === 400 || errorType === "invalid_request_error" || errorType === "INVALID_ARGUMENT") {
576
+ return {
577
+ category: "bad_input",
578
+ message: "The request was invalid. Check your message or API configuration."
579
+ };
580
+ }
581
+ if (status >= 500 || errorType === "api_error" || errorType === "INTERNAL") {
582
+ return {
583
+ category: "server_error",
584
+ message: "The AI service hit an internal error. Try again shortly."
585
+ };
586
+ }
587
+ return {
588
+ category: "unknown",
589
+ message: "Something went wrong. Check your connection and try again."
590
+ };
591
+ }
592
+ var useStyles4 = makeStyles4({
593
+ root: {
594
+ display: "flex",
595
+ alignItems: "center",
596
+ justifyContent: "space-between",
597
+ gap: "8px",
598
+ padding: "12px",
599
+ borderRadius: tokens4.borderRadiusXLarge,
600
+ backgroundColor: tokens4.colorNeutralBackground3,
601
+ border: `1px solid ${tokens4.colorNeutralStroke2}`,
602
+ color: tokens4.colorNeutralForeground3,
603
+ width: "100%",
604
+ boxSizing: "border-box",
605
+ margin: "8px 0"
606
+ },
607
+ left: {
608
+ display: "flex",
609
+ alignItems: "center",
610
+ gap: "8px",
611
+ minWidth: 0
612
+ },
613
+ message: {
614
+ fontFamily: "var(--fontFamilyMonospace)"
615
+ }
616
+ });
617
+ var categoryIcon = {
618
+ auth_billing: /* @__PURE__ */ jsx4(LockClosed20Regular, {}),
619
+ rate_limit: /* @__PURE__ */ jsx4(Clock20Regular, {}),
620
+ overloaded: /* @__PURE__ */ jsx4(PlugDisconnected20Regular, {}),
621
+ bad_input: /* @__PURE__ */ jsx4(Warning20Regular, {}),
622
+ not_found: /* @__PURE__ */ jsx4(ErrorCircle20Regular, {}),
623
+ server_error: /* @__PURE__ */ jsx4(ErrorCircle20Regular, {}),
624
+ unknown: /* @__PURE__ */ jsx4(Warning20Regular, {})
625
+ };
626
+ var ChatError = ({ error, onRetry }) => {
627
+ const styles = useStyles4();
628
+ const { category, message } = normalizeChatError(error);
629
+ return /* @__PURE__ */ jsxs4("div", { className: styles.root, children: [
630
+ /* @__PURE__ */ jsxs4("span", { className: styles.left, children: [
631
+ categoryIcon[category],
632
+ /* @__PURE__ */ jsx4(Body12, { className: styles.message, children: message })
633
+ ] }),
634
+ onRetry && /* @__PURE__ */ jsx4(
635
+ Button3,
636
+ {
637
+ size: "small",
638
+ appearance: "primary",
639
+ onClick: onRetry,
640
+ children: "Retry"
641
+ }
642
+ )
643
+ ] });
644
+ };
645
+ var Chat_Error_default = ChatError;
646
+
647
+ // src/Chat.ReasoningBlock.tsx
648
+ import { useState as useState3, useEffect as useEffect3, useRef as useRef3 } from "react";
649
+ import { Body1 as Body13, makeStyles as makeStyles5, tokens as tokens5 } from "@fluentui/react-components";
650
+ import { ChevronRight16Regular } from "@fluentui/react-icons";
651
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
652
+ var useStyles5 = makeStyles5({
653
+ root: {
654
+ display: "flex",
655
+ flexDirection: "column",
656
+ overflow: "hidden",
657
+ margin: "12px 0 40px 0"
658
+ },
659
+ header: {
660
+ display: "flex",
661
+ alignItems: "center",
662
+ padding: "0",
663
+ cursor: "pointer",
664
+ background: "none",
665
+ border: "none",
666
+ width: "100%",
667
+ textAlign: "left",
668
+ gap: "8px"
669
+ },
670
+ labelRow: {
671
+ display: "flex",
672
+ alignItems: "center",
673
+ gap: "6px",
674
+ minWidth: 0
675
+ },
676
+ pulseContainer: {
677
+ display: "flex",
678
+ alignItems: "center",
679
+ justifyContent: "center",
680
+ width: "16px",
681
+ height: "16px"
682
+ },
683
+ pulse: {
684
+ flexShrink: 0,
685
+ width: "8px",
686
+ height: "8px",
687
+ borderRadius: "50%",
688
+ backgroundColor: tokens5.colorNeutralForeground3,
689
+ animationName: {
690
+ "0%, 100%": { opacity: "1" },
691
+ "50%": { opacity: "0.2" }
692
+ },
693
+ animationDuration: "1.2s",
694
+ animationTimingFunction: "ease-in-out",
695
+ animationIterationCount: "infinite"
696
+ },
697
+ pulseDone: {
698
+ flexShrink: 0,
699
+ width: "8px",
700
+ height: "8px",
701
+ borderRadius: "50%",
702
+ backgroundColor: tokens5.colorBrandForeground1
703
+ },
704
+ label: {
705
+ color: tokens5.colorNeutralForeground3,
706
+ flexShrink: 0,
707
+ marginBottom: "2px"
708
+ },
709
+ chevron: {
710
+ color: tokens5.colorNeutralForeground3,
711
+ flexShrink: 0,
712
+ transition: "transform 0.2s ease"
713
+ },
714
+ chevronExpanded: {
715
+ color: tokens5.colorNeutralForeground3,
716
+ flexShrink: 0,
717
+ transition: "transform 0.2s ease",
718
+ transform: "rotate(90deg)"
719
+ },
720
+ expanded: {
721
+ padding: "16px 0 0 0",
722
+ marginLeft: "22px",
723
+ fontSize: tokens5.fontSizeBase300,
724
+ color: tokens5.colorNeutralForeground3,
725
+ fontFamily: "var(--fontFamilyMonospace)",
726
+ whiteSpace: "pre-wrap",
727
+ wordBreak: "break-word"
728
+ // maxHeight: "320px",
729
+ // overflowY: "auto",
730
+ }
731
+ });
732
+ var ReasoningBlock = ({
733
+ content,
734
+ isStreaming = false
735
+ }) => {
736
+ const styles = useStyles5();
737
+ const [expanded, setExpanded] = useState3(false);
738
+ const [duration, setDuration] = useState3(null);
739
+ const startTimeRef = useRef3(null);
740
+ useEffect3(() => {
741
+ if (isStreaming) {
742
+ startTimeRef.current = Date.now();
743
+ setDuration(null);
744
+ } else if (startTimeRef.current !== null) {
745
+ setDuration(Math.round((Date.now() - startTimeRef.current) / 1e3));
746
+ startTimeRef.current = null;
747
+ }
748
+ }, [isStreaming]);
749
+ const label = isStreaming ? "Thinking" : duration !== null ? `Thought for ${duration}s` : "Thought";
750
+ return /* @__PURE__ */ jsxs5("div", { className: styles.root, children: [
751
+ /* @__PURE__ */ jsx5("button", { className: styles.header, onClick: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsxs5("div", { className: styles.labelRow, children: [
752
+ /* @__PURE__ */ jsx5("div", { className: styles.pulseContainer, children: /* @__PURE__ */ jsx5("span", { className: isStreaming ? styles.pulse : styles.pulseDone }) }),
753
+ /* @__PURE__ */ jsx5(Body13, { className: styles.label, children: label }),
754
+ /* @__PURE__ */ jsx5(
755
+ ChevronRight16Regular,
756
+ {
757
+ className: expanded ? styles.chevronExpanded : styles.chevron
758
+ }
759
+ )
760
+ ] }) }),
761
+ expanded && /* @__PURE__ */ jsx5("div", { className: styles.expanded, children: content })
762
+ ] });
763
+ };
764
+ var Chat_ReasoningBlock_default = ReasoningBlock;
765
+
766
+ // src/Chat.Messages.tsx
767
+ import { CheckmarkCircle20Filled } from "@fluentui/react-icons";
768
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
769
+ var useStyles6 = makeStyles6({
770
+ messages: {
771
+ flex: "1",
772
+ overflowY: "auto",
773
+ overflowX: "hidden",
774
+ display: "flex",
775
+ flexDirection: "column",
776
+ alignItems: "center",
777
+ padding: "40px 0",
778
+ boxSizing: "border-box",
779
+ scrollbarGutter: "stable both-edges"
780
+ },
781
+ messageWrapper: {
782
+ display: "flex",
783
+ flexDirection: "column",
784
+ gap: "24px",
785
+ width: "100%",
786
+ maxWidth: "768px",
787
+ padding: "0 16px",
788
+ boxSizing: "border-box"
789
+ },
790
+ messageUser: {
791
+ alignSelf: "flex-end",
792
+ maxWidth: "80%",
793
+ backgroundColor: tokens6.colorNeutralBackground2,
794
+ color: tokens6.colorNeutralForeground2,
795
+ border: `1px solid ${tokens6.colorNeutralStroke2}`,
796
+ padding: "8px 12px",
797
+ borderRadius: tokens6.borderRadiusXLarge,
798
+ wordBreak: "break-word",
799
+ boxSizing: "border-box"
800
+ },
801
+ messageAssistant: {
802
+ alignSelf: "flex-start",
803
+ width: "100%",
804
+ color: tokens6.colorNeutralForeground2,
805
+ wordBreak: "break-word",
806
+ boxSizing: "border-box"
807
+ },
808
+ assistantFooter: {
809
+ display: "flex",
810
+ justifyContent: "space-between",
811
+ marginTop: "6px",
812
+ marginLeft: "-10px"
813
+ },
814
+ assistantActions: {
815
+ display: "flex"
816
+ }
817
+ });
818
+ var ChatMessages = ({
819
+ components,
820
+ onFeedback,
821
+ onCopy,
822
+ onNavigate,
823
+ fontSize,
824
+ fontFamily
825
+ }) => {
826
+ const styles = useStyles6();
827
+ const ctx = useChatContext();
828
+ const {
829
+ messages,
830
+ setMessages,
831
+ isTyping,
832
+ isRetrying,
833
+ isInterrupted,
834
+ streamingReasoning,
835
+ streamingAgentName,
836
+ thinkingStatus,
837
+ chatError,
838
+ sendMessage,
839
+ handleCopy,
840
+ lastUserInputRef,
841
+ registerMessagesContainer,
842
+ bottomRef
843
+ } = ctx;
844
+ const containerRef = useRef4(null);
845
+ const [copiedIndex, setCopiedIndex] = useState4(null);
846
+ const copiedTimerRef = useRef4(null);
847
+ useEffect4(() => {
848
+ registerMessagesContainer(containerRef.current);
849
+ return () => registerMessagesContainer(null);
850
+ }, []);
851
+ const onCopyMerged = (content, index) => {
852
+ handleCopy(content);
853
+ onCopy?.(content);
854
+ if (copiedTimerRef.current) clearTimeout(copiedTimerRef.current);
855
+ setCopiedIndex(index);
856
+ copiedTimerRef.current = setTimeout(() => setCopiedIndex(null), 1500);
857
+ };
858
+ const renderAssistantMessage = (msg, index) => {
859
+ const isLatest = index === messages.length - 1;
860
+ const reasoning = isLatest && isTyping ? streamingReasoning : msg.metadata?.reasoning;
861
+ if (msg.type && components?.[msg.type]) {
862
+ const CustomRenderer = components[msg.type];
863
+ return /* @__PURE__ */ jsx6(
864
+ CustomRenderer,
865
+ {
866
+ message: msg,
867
+ onReply: (content) => {
868
+ const updatedMessages = [
869
+ ...messages.slice(0, index + 1),
870
+ { role: "user", content }
871
+ ];
872
+ setMessages(updatedMessages);
873
+ sendMessage(content);
874
+ }
875
+ }
876
+ );
877
+ }
878
+ return /* @__PURE__ */ jsxs6("div", { children: [
879
+ reasoning && /* @__PURE__ */ jsx6(
880
+ Chat_ReasoningBlock_default,
881
+ {
882
+ content: reasoning,
883
+ isStreaming: isLatest && isTyping && !msg.content
884
+ }
885
+ ),
886
+ /* @__PURE__ */ jsx6(CoralMarkdown, { onNavigate, fontSize, fontFamily, children: msg.content }),
887
+ isInterrupted && !isTyping && index === messages.length - 1 && /* @__PURE__ */ jsx6(
888
+ Chat_Callout_default,
889
+ {
890
+ message: "Response was interrupted",
891
+ onRetry: () => sendMessage(lastUserInputRef.current)
892
+ }
893
+ ),
894
+ !(isTyping && isLatest) && /* @__PURE__ */ jsx6("div", { className: `${styles.assistantFooter} coral-assistant-footer`, children: /* @__PURE__ */ jsxs6("div", { className: styles.assistantActions, children: [
895
+ copiedIndex === index ? /* @__PURE__ */ jsx6(
896
+ "span",
897
+ {
898
+ style: {
899
+ display: "inline-flex",
900
+ alignItems: "center",
901
+ justifyContent: "center",
902
+ width: "32px",
903
+ height: "32px",
904
+ color: tokens6.colorPaletteSeafoamForeground2
905
+ },
906
+ children: /* @__PURE__ */ jsx6(CheckmarkCircle20Filled, {})
907
+ }
908
+ ) : /* @__PURE__ */ jsx6(
909
+ Button4,
910
+ {
911
+ onClick: () => onCopyMerged(msg.content, index),
912
+ title: "Copy Response",
913
+ appearance: "subtle",
914
+ style: { color: tokens6.colorNeutralForeground3 },
915
+ icon: /* @__PURE__ */ jsx6(Copy, {})
916
+ }
917
+ ),
918
+ /* @__PURE__ */ jsx6(
919
+ Button4,
920
+ {
921
+ onClick: () => onFeedback?.(index, "up"),
922
+ title: "Like",
923
+ appearance: "subtle",
924
+ style: { color: tokens6.colorNeutralForeground3 },
925
+ icon: /* @__PURE__ */ jsx6(Heart, {})
926
+ }
927
+ )
928
+ ] }) })
929
+ ] });
930
+ };
931
+ return /* @__PURE__ */ jsx6("div", { className: styles.messages, ref: containerRef, children: /* @__PURE__ */ jsxs6("div", { className: styles.messageWrapper, children: [
932
+ messages.map((msg, index) => /* @__PURE__ */ jsxs6(
933
+ "div",
934
+ {
935
+ className: msg.role === "user" ? styles.messageUser : `${styles.messageAssistant} coral-message-assistant${index === messages.length - 1 ? " is-latest" : ""}`,
936
+ children: [
937
+ msg.role === "user" && Array.isArray(msg.metadata?.attachments) && /* @__PURE__ */ jsx6("div", { style: { display: "flex", gap: "6px", flexWrap: "wrap", marginBottom: "6px" }, children: msg.metadata.attachments.map(
938
+ (a, i) => a.type.startsWith("image/") && a.preview ? /* @__PURE__ */ jsx6(
939
+ "img",
940
+ {
941
+ src: a.preview,
942
+ alt: a.name,
943
+ style: { width: 64, height: 64, borderRadius: "var(--borderRadiusMedium)", objectFit: "cover" }
944
+ },
945
+ i
946
+ ) : /* @__PURE__ */ jsxs6(
947
+ "span",
948
+ {
949
+ style: {
950
+ fontSize: 12,
951
+ padding: "2px 8px",
952
+ borderRadius: "var(--borderRadiusSmall)",
953
+ backgroundColor: tokens6.colorNeutralBackground3,
954
+ color: tokens6.colorNeutralForeground2
955
+ },
956
+ children: [
957
+ "\u{1F4C4} ",
958
+ a.name
959
+ ]
960
+ },
961
+ i
962
+ )
963
+ ) }),
964
+ /* @__PURE__ */ jsx6(Body14, { children: msg.role === "user" ? msg.content.split("\n").map((line, i, arr) => /* @__PURE__ */ jsxs6(React4.Fragment, { children: [
965
+ line,
966
+ i < arr.length - 1 && /* @__PURE__ */ jsx6("br", {})
967
+ ] }, i)) : renderAssistantMessage(msg, index) })
968
+ ]
969
+ },
970
+ msg.id ?? index
971
+ )),
972
+ chatError !== null && !isTyping && /* @__PURE__ */ jsx6(
973
+ Chat_Error_default,
974
+ {
975
+ error: chatError,
976
+ onRetry: () => sendMessage(lastUserInputRef.current)
977
+ }
978
+ ),
979
+ isTyping && /* @__PURE__ */ jsx6(
980
+ Chat_ThinkingIndicator_default,
981
+ {
982
+ isRetrying,
983
+ status: streamingAgentName ? `${streamingAgentName}...` : thinkingStatus
984
+ }
985
+ ),
986
+ /* @__PURE__ */ jsx6("div", { ref: bottomRef })
987
+ ] }) });
988
+ };
989
+ var Chat_Messages_default = ChatMessages;
990
+
991
+ // src/Chat.Input.tsx
992
+ import {
993
+ useRef as useRef5,
994
+ useState as useState5,
995
+ useEffect as useEffect5,
996
+ forwardRef,
997
+ useImperativeHandle
998
+ } from "react";
999
+ import { Body1 as Body15, Button as Button5, Tag, makeStyles as makeStyles7, tokens as tokens7 } from "@fluentui/react-components";
1000
+ import { Dismiss, Send, Stop } from "@coral-ai/bundleicons";
1001
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1002
+ var useStyles7 = makeStyles7({
1003
+ wrapper: {
1004
+ width: "100%",
1005
+ margin: "0 auto"
1006
+ },
1007
+ card: {
1008
+ display: "inline-flex",
1009
+ flexDirection: "column",
1010
+ gap: "8px",
1011
+ alignItems: "stretch",
1012
+ width: "100%",
1013
+ padding: "8px",
1014
+ borderRadius: "var(--borderRadiusXLarge)",
1015
+ backgroundColor: "var(--colorNeutralBackgroundAlpha2)",
1016
+ transition: "border-color 0.2s ease-in-out",
1017
+ position: "relative",
1018
+ boxSizing: "border-box",
1019
+ overflow: "hidden"
1020
+ },
1021
+ cardFocused: {
1022
+ boxShadow: tokens7.shadow28
1023
+ },
1024
+ cardDisabled: {
1025
+ opacity: "0.3",
1026
+ pointerEvents: "none"
1027
+ },
1028
+ attachmentsRow: {
1029
+ display: "flex",
1030
+ gap: "8px",
1031
+ overflowX: "auto",
1032
+ padding: "0 4px"
1033
+ },
1034
+ attachmentItem: {
1035
+ position: "relative",
1036
+ flexShrink: 0,
1037
+ width: "128px",
1038
+ height: "128px",
1039
+ borderRadius: "var(--borderRadiusMedium)",
1040
+ overflow: "hidden",
1041
+ border: `1px solid ${tokens7.colorNeutralStroke2}`,
1042
+ backgroundColor: "var(--colorNeutralBackground3)",
1043
+ display: "flex",
1044
+ flexDirection: "column"
1045
+ },
1046
+ attachmentImage: {
1047
+ width: "100%",
1048
+ height: "100%",
1049
+ objectFit: "cover"
1050
+ },
1051
+ attachmentFile: {
1052
+ padding: "12px",
1053
+ boxSizing: "border-box",
1054
+ display: "flex",
1055
+ flexDirection: "column",
1056
+ gap: "4px",
1057
+ justifyContent: "space-between"
1058
+ },
1059
+ attachmentFileName: {
1060
+ wordBreak: "break-all",
1061
+ color: "var(--colorNeutralForeground2)",
1062
+ overflow: "hidden",
1063
+ maxHeight: "36px",
1064
+ lineHeight: "1.2"
1065
+ },
1066
+ attachmentDismiss: {
1067
+ position: "absolute",
1068
+ top: "6px",
1069
+ right: "6px"
1070
+ },
1071
+ textarea: {
1072
+ resize: "none",
1073
+ overflowY: "auto",
1074
+ height: "auto",
1075
+ minHeight: "24px",
1076
+ maxHeight: "150px",
1077
+ padding: "8px",
1078
+ backgroundColor: "transparent",
1079
+ border: "none",
1080
+ outline: "none",
1081
+ fontFamily: "var(--fontFamilyBase)",
1082
+ fontSize: "var(--fontSizeBase300)",
1083
+ fontWeight: "var(--fontWeightRegular)",
1084
+ color: "var(--colorNeutralForeground1)",
1085
+ lineHeight: "1.5",
1086
+ boxSizing: "border-box",
1087
+ width: "100%"
1088
+ },
1089
+ toolbar: {
1090
+ display: "flex",
1091
+ alignItems: "center",
1092
+ justifyContent: "space-between",
1093
+ maxHeight: "32px"
1094
+ },
1095
+ toolbarSide: {
1096
+ display: "flex",
1097
+ alignItems: "center"
1098
+ },
1099
+ focusBar: {
1100
+ position: "absolute",
1101
+ bottom: "0",
1102
+ left: "0",
1103
+ width: "100%",
1104
+ height: "2px",
1105
+ backgroundColor: "var(--colorCompoundBrandStroke)",
1106
+ transition: "transform 0.2s ease-in-out",
1107
+ textAlign: "center"
1108
+ },
1109
+ focusBarVisible: {
1110
+ transform: "scaleX(1)"
1111
+ },
1112
+ focusBarHidden: {
1113
+ transform: "scaleX(0)"
1114
+ }
1115
+ });
1116
+ var ChatInput = forwardRef(
1117
+ ({
1118
+ value: valueProp,
1119
+ onChange: onChangeProp,
1120
+ onEnter: onEnterProp,
1121
+ placeholder = "Type a message...",
1122
+ disabled: disabledProp,
1123
+ style,
1124
+ actionsStart,
1125
+ actionsEnd,
1126
+ attachments: attachmentsProp,
1127
+ removeAttachment: removeAttachmentProp
1128
+ }, ref) => {
1129
+ const styles = useStyles7();
1130
+ const [isFocused, setIsFocused] = useState5(false);
1131
+ const textareaRef = useRef5(null);
1132
+ const containerRef = useRef5(null);
1133
+ useImperativeHandle(ref, () => textareaRef.current);
1134
+ const ctx = useChatContext();
1135
+ const isInsideCanvas = ctx.setInput !== void 0;
1136
+ const attachments = isInsideCanvas ? ctx.attachments : attachmentsProp ?? [];
1137
+ const removeAttachment = isInsideCanvas ? ctx.removeAttachment : removeAttachmentProp ?? (() => {
1138
+ });
1139
+ const value = valueProp ?? (isInsideCanvas ? ctx.input : "");
1140
+ const onChange = onChangeProp ?? (isInsideCanvas ? ctx.setInput : () => {
1141
+ });
1142
+ const disabled = disabledProp ?? false;
1143
+ const onEnter = onEnterProp ?? (isInsideCanvas ? () => {
1144
+ if (!ctx.isTyping) ctx.sendMessage();
1145
+ } : void 0);
1146
+ useEffect5(() => {
1147
+ if (!isInsideCanvas) return;
1148
+ const el = containerRef.current;
1149
+ if (!el) return;
1150
+ const ro = new ResizeObserver(() => {
1151
+ ctx.setInputHeight(el.offsetHeight);
1152
+ });
1153
+ ro.observe(el);
1154
+ ctx.setInputHeight(el.offsetHeight);
1155
+ return () => ro.disconnect();
1156
+ }, [isInsideCanvas]);
1157
+ useEffect5(() => {
1158
+ if (textareaRef.current) {
1159
+ textareaRef.current.style.height = "auto";
1160
+ textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
1161
+ }
1162
+ }, [value]);
1163
+ useEffect5(() => {
1164
+ const el = textareaRef.current;
1165
+ if (!el) return;
1166
+ const observer = new ResizeObserver(() => {
1167
+ el.style.height = "auto";
1168
+ el.style.height = `${el.scrollHeight}px`;
1169
+ });
1170
+ observer.observe(el);
1171
+ return () => observer.disconnect();
1172
+ }, []);
1173
+ const sendStopButton = isInsideCanvas ? ctx.isTyping ? /* @__PURE__ */ jsx7(
1174
+ Button5,
1175
+ {
1176
+ appearance: "transparent",
1177
+ onClick: () => {
1178
+ ctx.shouldStopRef.current = true;
1179
+ ctx.onStop?.();
1180
+ },
1181
+ icon: /* @__PURE__ */ jsx7(Stop, {}),
1182
+ style: { cursor: "default" }
1183
+ }
1184
+ ) : /* @__PURE__ */ jsx7(
1185
+ Button5,
1186
+ {
1187
+ appearance: "transparent",
1188
+ onClick: () => ctx.sendMessage(),
1189
+ icon: /* @__PURE__ */ jsx7(Send, {}),
1190
+ disabled: !value.trim(),
1191
+ style: { cursor: "default" }
1192
+ }
1193
+ ) : null;
1194
+ return /* @__PURE__ */ jsx7("div", { ref: containerRef, className: styles.wrapper, style, children: /* @__PURE__ */ jsxs7(
1195
+ "div",
1196
+ {
1197
+ className: `${styles.card}${isFocused ? ` ${styles.cardFocused}` : ""}${disabled ? ` ${styles.cardDisabled}` : ""}`,
1198
+ style: {
1199
+ border: isFocused ? `1px solid ${tokens7.colorNeutralStroke1}` : `1px solid ${tokens7.colorNeutralStroke2}`
1200
+ },
1201
+ children: [
1202
+ attachments.length > 0 && /* @__PURE__ */ jsx7("div", { className: styles.attachmentsRow, children: attachments.map((a) => {
1203
+ const ext = a.file.name.split(".").pop()?.toUpperCase() ?? "FILE";
1204
+ return /* @__PURE__ */ jsxs7("div", { className: styles.attachmentItem, children: [
1205
+ a.preview ? /* @__PURE__ */ jsx7(
1206
+ "img",
1207
+ {
1208
+ src: a.preview,
1209
+ alt: a.file.name,
1210
+ className: styles.attachmentImage
1211
+ }
1212
+ ) : /* @__PURE__ */ jsxs7("div", { className: styles.attachmentFile, children: [
1213
+ /* @__PURE__ */ jsx7(Body15, { className: styles.attachmentFileName, children: a.file.name }),
1214
+ /* @__PURE__ */ jsx7(Tag, { appearance: "outline", size: "small", children: ext })
1215
+ ] }),
1216
+ /* @__PURE__ */ jsx7(
1217
+ Button5,
1218
+ {
1219
+ icon: /* @__PURE__ */ jsx7(Dismiss, {}),
1220
+ size: "small",
1221
+ onClick: () => removeAttachment(a.id),
1222
+ className: styles.attachmentDismiss
1223
+ }
1224
+ )
1225
+ ] }, a.id);
1226
+ }) }),
1227
+ /* @__PURE__ */ jsx7(
1228
+ "textarea",
1229
+ {
1230
+ disabled,
1231
+ ref: textareaRef,
1232
+ value,
1233
+ onChange: (e) => onChange(e.target.value),
1234
+ onKeyDown: (e) => {
1235
+ if (e.key === "Enter" && !e.shiftKey) {
1236
+ e.preventDefault();
1237
+ onEnter?.();
1238
+ }
1239
+ },
1240
+ onFocus: () => setIsFocused(true),
1241
+ onBlur: () => setIsFocused(false),
1242
+ placeholder,
1243
+ rows: 1,
1244
+ className: styles.textarea
1245
+ }
1246
+ ),
1247
+ /* @__PURE__ */ jsxs7("div", { className: styles.toolbar, children: [
1248
+ /* @__PURE__ */ jsx7("div", { className: styles.toolbarSide, children: actionsStart }),
1249
+ /* @__PURE__ */ jsxs7("div", { className: styles.toolbarSide, children: [
1250
+ actionsEnd,
1251
+ sendStopButton
1252
+ ] })
1253
+ ] }),
1254
+ /* @__PURE__ */ jsx7(
1255
+ "span",
1256
+ {
1257
+ className: `${styles.focusBar} ${isFocused ? styles.focusBarVisible : styles.focusBarHidden}`
1258
+ }
1259
+ )
1260
+ ]
1261
+ }
1262
+ ) });
1263
+ }
1264
+ );
1265
+ var Chat_Input_default = ChatInput;
1266
+
1267
+ // src/Chat.tsx
1268
+ import { Fragment, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1269
+ var useStyles8 = makeStyles8({
1270
+ emptySlot: {
1271
+ width: "100%",
1272
+ maxWidth: "768px",
1273
+ margin: "0 auto",
1274
+ padding: "0 16px",
1275
+ boxSizing: "border-box",
1276
+ display: "flex",
1277
+ flexDirection: "column",
1278
+ alignItems: "center"
1279
+ },
1280
+ contentArea: {
1281
+ width: "100%",
1282
+ display: "flex",
1283
+ flexDirection: "column",
1284
+ minHeight: "0"
1285
+ },
1286
+ inputRow: {
1287
+ display: "flex",
1288
+ width: "100%",
1289
+ alignItems: "center",
1290
+ justifyContent: "center"
1291
+ },
1292
+ inputInner: {
1293
+ display: "flex",
1294
+ width: "100%",
1295
+ maxWidth: "768px",
1296
+ padding: "0 16px 16px 16px",
1297
+ boxSizing: "border-box"
1298
+ },
1299
+ starterRow: {
1300
+ display: "flex",
1301
+ flexWrap: "wrap",
1302
+ justifyContent: "center",
1303
+ gap: "6px",
1304
+ width: "100%",
1305
+ maxWidth: "768px",
1306
+ margin: "0 auto",
1307
+ padding: "0 16px",
1308
+ boxSizing: "border-box",
1309
+ overflow: "hidden"
1310
+ }
1311
+ });
1312
+ var ChatLayout = ({
1313
+ emptyState,
1314
+ promptStarters,
1315
+ surfaceMotion,
1316
+ placeholder,
1317
+ disabled,
1318
+ actionsStart,
1319
+ actionsEnd,
1320
+ components,
1321
+ onFeedback,
1322
+ onCopy,
1323
+ onNavigate,
1324
+ fontSize,
1325
+ fontFamily
1326
+ }) => {
1327
+ const styles = useStyles8();
1328
+ const { messages, isTyping, sendMessage } = useChatContext();
1329
+ const empty = messages.every((m) => m.role === "system") && !isTyping;
1330
+ const motionEnabled = surfaceMotion !== null;
1331
+ const curve = motionTokens.curveDecelerateMax;
1332
+ const spacerTransition = motionEnabled ? `flex-grow 300ms ${curve}` : "none";
1333
+ const gapTransition = motionEnabled ? `height 300ms ${curve}` : "none";
1334
+ const starterTransition = motionEnabled ? `opacity 300ms ${curve}, max-height 300ms ${curve}, padding-bottom 300ms ${curve}` : "none";
1335
+ const hasStarters = !!promptStarters && promptStarters.length > 0;
1336
+ return /* @__PURE__ */ jsxs8(Fragment, { children: [
1337
+ /* @__PURE__ */ jsx8(
1338
+ "div",
1339
+ {
1340
+ style: {
1341
+ flexGrow: empty ? 1 : 0,
1342
+ flexShrink: 1,
1343
+ flexBasis: 0,
1344
+ minHeight: 0,
1345
+ transition: spacerTransition
1346
+ }
1347
+ }
1348
+ ),
1349
+ /* @__PURE__ */ jsx8(
1350
+ "div",
1351
+ {
1352
+ className: styles.contentArea,
1353
+ style: {
1354
+ flex: empty ? "none" : 1,
1355
+ overflow: empty ? "visible" : "hidden"
1356
+ },
1357
+ children: empty ? /* @__PURE__ */ jsx8("div", { className: styles.emptySlot, children: emptyState }) : /* @__PURE__ */ jsx8(
1358
+ Chat_Messages_default,
1359
+ {
1360
+ components,
1361
+ onFeedback,
1362
+ onCopy,
1363
+ onNavigate,
1364
+ fontSize,
1365
+ fontFamily
1366
+ }
1367
+ )
1368
+ }
1369
+ ),
1370
+ /* @__PURE__ */ jsx8("div", { style: { height: empty ? 24 : 0, transition: gapTransition } }),
1371
+ /* @__PURE__ */ jsx8("div", { className: styles.inputRow, children: /* @__PURE__ */ jsx8("div", { className: styles.inputInner, children: /* @__PURE__ */ jsx8(
1372
+ Chat_Input_default,
1373
+ {
1374
+ placeholder,
1375
+ disabled,
1376
+ actionsStart,
1377
+ actionsEnd
1378
+ }
1379
+ ) }) }),
1380
+ hasStarters && /* @__PURE__ */ jsx8(
1381
+ "div",
1382
+ {
1383
+ className: styles.starterRow,
1384
+ style: {
1385
+ opacity: empty ? 1 : 0,
1386
+ maxHeight: empty ? 160 : 0,
1387
+ paddingBottom: empty ? 16 : 0,
1388
+ pointerEvents: empty ? "auto" : "none",
1389
+ transition: starterTransition,
1390
+ gap: "8px"
1391
+ },
1392
+ "aria-hidden": !empty,
1393
+ children: promptStarters.map((p) => /* @__PURE__ */ jsx8(
1394
+ InteractionTag,
1395
+ {
1396
+ appearance: "outline",
1397
+ onClick: () => sendMessage(p),
1398
+ tabIndex: empty ? 0 : -1,
1399
+ children: /* @__PURE__ */ jsx8(InteractionTagPrimary, { children: p })
1400
+ },
1401
+ p
1402
+ ))
1403
+ }
1404
+ ),
1405
+ /* @__PURE__ */ jsx8(
1406
+ "div",
1407
+ {
1408
+ style: {
1409
+ flexGrow: empty ? 1 : 0,
1410
+ flexShrink: 1,
1411
+ flexBasis: 0,
1412
+ minHeight: 0,
1413
+ transition: spacerTransition
1414
+ }
1415
+ }
1416
+ )
1417
+ ] });
1418
+ };
1419
+ var Chat = ({
1420
+ agent,
1421
+ agents,
1422
+ conversationId,
1423
+ initialMessages,
1424
+ systemPrompt,
1425
+ onSendMessage,
1426
+ onMessageComplete,
1427
+ onMessagesChange,
1428
+ onError,
1429
+ autoSendMessage,
1430
+ thinkingStatus,
1431
+ onStop,
1432
+ emptyState,
1433
+ promptStarters,
1434
+ surfaceMotion,
1435
+ components,
1436
+ onFeedback,
1437
+ onCopy,
1438
+ onNavigate,
1439
+ placeholder,
1440
+ disabled,
1441
+ actionsStart,
1442
+ actionsEnd,
1443
+ fontSize,
1444
+ fontFamily
1445
+ }) => {
1446
+ return /* @__PURE__ */ jsx8(
1447
+ Chat_Canvas_default,
1448
+ {
1449
+ agent,
1450
+ agents,
1451
+ conversationId,
1452
+ initialMessages,
1453
+ systemPrompt,
1454
+ onSendMessage,
1455
+ onMessageComplete,
1456
+ onMessagesChange,
1457
+ onError,
1458
+ autoSendMessage,
1459
+ thinkingStatus,
1460
+ onStop,
1461
+ children: /* @__PURE__ */ jsx8(
1462
+ ChatLayout,
1463
+ {
1464
+ emptyState,
1465
+ promptStarters,
1466
+ surfaceMotion,
1467
+ placeholder,
1468
+ disabled,
1469
+ actionsStart,
1470
+ actionsEnd,
1471
+ components,
1472
+ onFeedback,
1473
+ onCopy,
1474
+ onNavigate,
1475
+ fontSize,
1476
+ fontFamily
1477
+ }
1478
+ )
1479
+ }
1480
+ );
1481
+ };
1482
+ var Chat_default = Chat;
1483
+
1484
+ // src/createHandler.ts
1485
+ function createHandler({ endpoint, headers }) {
1486
+ return async function* (input, history, context) {
1487
+ const res = await fetch(endpoint, {
1488
+ method: "POST",
1489
+ headers: { "Content-Type": "application/json", ...headers },
1490
+ body: JSON.stringify({ input, history, context })
1491
+ });
1492
+ if (!res.ok || !res.body) {
1493
+ throw Object.assign(new Error(`${res.status} ${res.statusText}`), {
1494
+ status: res.status,
1495
+ statusText: res.statusText
1496
+ });
1497
+ }
1498
+ const reader = res.body.getReader();
1499
+ const decoder = new TextDecoder();
1500
+ let buffer = "";
1501
+ while (true) {
1502
+ const { done, value } = await reader.read();
1503
+ if (done) break;
1504
+ buffer += decoder.decode(value, { stream: true });
1505
+ const lines = buffer.split("\n");
1506
+ buffer = lines.pop() ?? "";
1507
+ for (const line of lines) {
1508
+ const text = line.startsWith("data: ") ? line.slice(6) : line;
1509
+ if (!text || text === "[DONE]") continue;
1510
+ try {
1511
+ const chunk = JSON.parse(text);
1512
+ yield chunk;
1513
+ } catch {
1514
+ yield text;
1515
+ }
1516
+ }
1517
+ }
1518
+ };
1519
+ }
1520
+
1521
+ // src/index.ts
1522
+ var Chat2 = Object.assign(Chat_default, {
1523
+ Canvas: Chat_Canvas_default,
1524
+ Messages: Chat_Messages_default,
1525
+ Input: Chat_Input_default,
1526
+ Error: Chat_Error_default,
1527
+ Callout: Chat_Callout_default
1528
+ });
1529
+ var normalizeChatError2 = normalizeChatError;
1530
+ var createHandler2 = createHandler;
1531
+ export {
1532
+ Chat2 as Chat,
1533
+ createHandler2 as createHandler,
1534
+ normalizeChatError2 as normalizeChatError,
1535
+ useChatContext
1536
+ };
1537
+ //# sourceMappingURL=index.js.map