@coral-ai/chat 0.3.0 → 0.4.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/README.md +7 -10
- package/dist/index.d.ts +489 -87
- package/dist/index.js +1711 -466
- package/dist/index.js.map +1 -1
- package/package.json +8 -6
package/dist/index.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
// src/Chat.tsx
|
|
2
|
+
import { useLayoutEffect as useLayoutEffect3, useRef as useRef7, useState as useState7 } from "react";
|
|
2
3
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
makeStyles as
|
|
4
|
+
Caption1 as Caption14,
|
|
5
|
+
Title3,
|
|
6
|
+
makeStyles as makeStyles10,
|
|
6
7
|
motionTokens
|
|
7
8
|
} from "@fluentui/react-components";
|
|
9
|
+
import { useCoralConfig } from "@coral-ai/react";
|
|
8
10
|
|
|
9
11
|
// src/Chat.Canvas.tsx
|
|
10
12
|
import { useState, useEffect, useLayoutEffect, useRef, useCallback } from "react";
|
|
11
|
-
import { Button, makeStyles, tokens } from "@fluentui/react-components";
|
|
13
|
+
import { Button as Button2, makeStyles as makeStyles2, tokens as tokens2 } from "@fluentui/react-components";
|
|
12
14
|
import { ArrowDown } from "@coral-ai/bundleicons";
|
|
13
15
|
|
|
14
16
|
// src/Chat.Context.tsx
|
|
@@ -18,9 +20,154 @@ function useChatContext() {
|
|
|
18
20
|
return useContext(ChatContext);
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
// src/Chat.
|
|
23
|
+
// src/Chat.Error.tsx
|
|
24
|
+
import { Body1, Button, makeStyles, tokens } from "@fluentui/react-components";
|
|
25
|
+
import {
|
|
26
|
+
ErrorCircle20Regular,
|
|
27
|
+
Clock20Regular,
|
|
28
|
+
LockClosed20Regular,
|
|
29
|
+
PlugDisconnected20Regular,
|
|
30
|
+
Warning20Regular
|
|
31
|
+
} from "@fluentui/react-icons";
|
|
22
32
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
33
|
+
var ERROR_CATEGORIES = [
|
|
34
|
+
"auth_billing",
|
|
35
|
+
"rate_limit",
|
|
36
|
+
"overloaded",
|
|
37
|
+
"bad_input",
|
|
38
|
+
"not_found",
|
|
39
|
+
"server_error",
|
|
40
|
+
"unknown"
|
|
41
|
+
];
|
|
42
|
+
function isNormalizedChatError(err) {
|
|
43
|
+
return !!err && typeof err === "object" && typeof err.message === "string" && ERROR_CATEGORIES.includes(err.category);
|
|
44
|
+
}
|
|
45
|
+
function normalizeChatError(err) {
|
|
46
|
+
if (isNormalizedChatError(err)) return err;
|
|
47
|
+
const anyErr = err;
|
|
48
|
+
const rawMessage = String(
|
|
49
|
+
anyErr?.message ?? anyErr?.error?.message ?? err ?? ""
|
|
50
|
+
).toLowerCase();
|
|
51
|
+
const status = Number(anyErr?.status ?? anyErr?.statusCode ?? 0);
|
|
52
|
+
const errorType = String(
|
|
53
|
+
anyErr?.error?.type ?? anyErr?.type ?? anyErr?.code ?? ""
|
|
54
|
+
);
|
|
55
|
+
const errorCode = String(
|
|
56
|
+
anyErr?.error?.code ?? anyErr?.code ?? ""
|
|
57
|
+
);
|
|
58
|
+
if (rawMessage.includes("credit") || rawMessage.includes("billing") || rawMessage.includes("insufficient_quota") || errorCode === "insufficient_quota" || errorType === "PERMISSION_DENIED") {
|
|
59
|
+
return {
|
|
60
|
+
category: "auth_billing",
|
|
61
|
+
message: "API credits exhausted. Add credits in your provider's billing dashboard."
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (errorType === "rate_limit_error" || rawMessage.includes("rate limit") || rawMessage.includes("too many requests")) {
|
|
65
|
+
return {
|
|
66
|
+
category: "rate_limit",
|
|
67
|
+
message: "You're being rate limited. Wait a moment and try again."
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (status === 529 || status === 503 || errorType === "overloaded_error" || errorType === "RESOURCE_EXHAUSTED" || rawMessage.includes("overload") || rawMessage.includes("capacity")) {
|
|
71
|
+
return {
|
|
72
|
+
category: "overloaded",
|
|
73
|
+
message: "The AI service is overloaded right now. Try again in a few seconds."
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (status === 404 || errorType === "NOT_FOUND" || rawMessage.includes("model_not_found") || rawMessage.includes("not found")) {
|
|
77
|
+
return {
|
|
78
|
+
category: "not_found",
|
|
79
|
+
message: "Model not found. Check the model name in your configuration."
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (status === 400 || errorType === "invalid_request_error" || errorType === "INVALID_ARGUMENT") {
|
|
83
|
+
return {
|
|
84
|
+
category: "bad_input",
|
|
85
|
+
message: "The request was invalid. Check your message or API configuration."
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (status >= 500 || errorType === "api_error" || errorType === "INTERNAL") {
|
|
89
|
+
return {
|
|
90
|
+
category: "server_error",
|
|
91
|
+
message: "The AI service hit an internal error. Try again shortly."
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
category: "unknown",
|
|
96
|
+
message: "Something went wrong. Check your connection and try again."
|
|
97
|
+
};
|
|
98
|
+
}
|
|
23
99
|
var useStyles = makeStyles({
|
|
100
|
+
root: {
|
|
101
|
+
display: "flex",
|
|
102
|
+
alignItems: "center",
|
|
103
|
+
justifyContent: "space-between",
|
|
104
|
+
gap: "8px",
|
|
105
|
+
padding: "12px",
|
|
106
|
+
borderRadius: tokens.borderRadiusXLarge,
|
|
107
|
+
backgroundColor: tokens.colorNeutralBackground3,
|
|
108
|
+
border: `1px solid ${tokens.colorNeutralStroke2}`,
|
|
109
|
+
color: tokens.colorNeutralForeground3,
|
|
110
|
+
width: "100%",
|
|
111
|
+
boxSizing: "border-box",
|
|
112
|
+
margin: "8px 0"
|
|
113
|
+
},
|
|
114
|
+
left: {
|
|
115
|
+
display: "flex",
|
|
116
|
+
alignItems: "center",
|
|
117
|
+
gap: "8px",
|
|
118
|
+
minWidth: 0
|
|
119
|
+
},
|
|
120
|
+
message: {
|
|
121
|
+
fontFamily: "var(--fontFamilyMonospace)"
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
var categoryIcon = {
|
|
125
|
+
auth_billing: /* @__PURE__ */ jsx(LockClosed20Regular, {}),
|
|
126
|
+
rate_limit: /* @__PURE__ */ jsx(Clock20Regular, {}),
|
|
127
|
+
overloaded: /* @__PURE__ */ jsx(PlugDisconnected20Regular, {}),
|
|
128
|
+
bad_input: /* @__PURE__ */ jsx(Warning20Regular, {}),
|
|
129
|
+
not_found: /* @__PURE__ */ jsx(ErrorCircle20Regular, {}),
|
|
130
|
+
server_error: /* @__PURE__ */ jsx(ErrorCircle20Regular, {}),
|
|
131
|
+
unknown: /* @__PURE__ */ jsx(Warning20Regular, {})
|
|
132
|
+
};
|
|
133
|
+
var ChatError = ({ error, onRetry }) => {
|
|
134
|
+
const styles = useStyles();
|
|
135
|
+
const { category, message } = normalizeChatError(error);
|
|
136
|
+
return /* @__PURE__ */ jsxs("div", { className: styles.root, children: [
|
|
137
|
+
/* @__PURE__ */ jsxs("span", { className: styles.left, children: [
|
|
138
|
+
categoryIcon[category],
|
|
139
|
+
/* @__PURE__ */ jsx(Body1, { className: styles.message, children: message })
|
|
140
|
+
] }),
|
|
141
|
+
onRetry && /* @__PURE__ */ jsx(
|
|
142
|
+
Button,
|
|
143
|
+
{
|
|
144
|
+
size: "small",
|
|
145
|
+
appearance: "primary",
|
|
146
|
+
onClick: onRetry,
|
|
147
|
+
children: "Retry"
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
] });
|
|
151
|
+
};
|
|
152
|
+
var Chat_Error_default = ChatError;
|
|
153
|
+
|
|
154
|
+
// src/fileToBase64.ts
|
|
155
|
+
function fileToBase64(file) {
|
|
156
|
+
return new Promise((resolve, reject) => {
|
|
157
|
+
const reader = new FileReader();
|
|
158
|
+
reader.onload = () => {
|
|
159
|
+
const dataUrl = reader.result;
|
|
160
|
+
const comma = dataUrl.indexOf(",");
|
|
161
|
+
resolve(comma === -1 ? dataUrl : dataUrl.slice(comma + 1));
|
|
162
|
+
};
|
|
163
|
+
reader.onerror = () => reject(reader.error ?? new Error("FileReader failed"));
|
|
164
|
+
reader.readAsDataURL(file);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/Chat.Canvas.tsx
|
|
169
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
170
|
+
var useStyles2 = makeStyles2({
|
|
24
171
|
root: {
|
|
25
172
|
width: "100%",
|
|
26
173
|
display: "flex",
|
|
@@ -35,8 +182,10 @@ var useStyles = makeStyles({
|
|
|
35
182
|
transform: "translateX(-50%)",
|
|
36
183
|
zIndex: 1e3,
|
|
37
184
|
cursor: "pointer",
|
|
38
|
-
boxShadow:
|
|
39
|
-
marginBottom: "
|
|
185
|
+
boxShadow: tokens2.shadow8,
|
|
186
|
+
marginBottom: "48px",
|
|
187
|
+
backgroundColor: "var(--colorNeutralBackgroundAlpha2)",
|
|
188
|
+
backdropFilter: "blur(40px) saturate(150%)"
|
|
40
189
|
}
|
|
41
190
|
});
|
|
42
191
|
function isAsyncIterable(value) {
|
|
@@ -48,9 +197,54 @@ function isRateLimitError(err) {
|
|
|
48
197
|
const msg = String(e?.message ?? "").toLowerCase();
|
|
49
198
|
return status === 429 || msg.includes("rate limit") || msg.includes("too many requests");
|
|
50
199
|
}
|
|
200
|
+
async function* wrapWithStatusChunk(taskType, reply) {
|
|
201
|
+
yield { type: "status", name: taskType };
|
|
202
|
+
if (reply) yield reply;
|
|
203
|
+
}
|
|
204
|
+
async function* parseStream(res) {
|
|
205
|
+
const reader = res.body?.getReader();
|
|
206
|
+
if (!reader) return;
|
|
207
|
+
const decoder = new TextDecoder();
|
|
208
|
+
let buffer = "";
|
|
209
|
+
while (true) {
|
|
210
|
+
const { value, done } = await reader.read();
|
|
211
|
+
if (done) break;
|
|
212
|
+
buffer += decoder.decode(value, { stream: true });
|
|
213
|
+
const lines = buffer.split("\n");
|
|
214
|
+
buffer = lines.pop() ?? "";
|
|
215
|
+
for (const raw of lines) {
|
|
216
|
+
const line = raw.startsWith("data:") ? raw.slice(5).trim() : raw.trim();
|
|
217
|
+
if (!line || line === "[DONE]") continue;
|
|
218
|
+
try {
|
|
219
|
+
const parsed = JSON.parse(line);
|
|
220
|
+
if (typeof parsed === "string") {
|
|
221
|
+
yield parsed;
|
|
222
|
+
} else if (parsed && typeof parsed === "object" && "type" in parsed) {
|
|
223
|
+
yield parsed;
|
|
224
|
+
} else if (parsed && typeof parsed === "object" && typeof parsed.reply === "string") {
|
|
225
|
+
if (typeof parsed.type === "string") {
|
|
226
|
+
yield { type: "status", name: parsed.type };
|
|
227
|
+
}
|
|
228
|
+
yield parsed.reply;
|
|
229
|
+
}
|
|
230
|
+
} catch {
|
|
231
|
+
yield line;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (buffer.trim()) {
|
|
236
|
+
try {
|
|
237
|
+
const parsed = JSON.parse(buffer);
|
|
238
|
+
if (typeof parsed === "string") yield parsed;
|
|
239
|
+
else if (parsed && typeof parsed === "object" && "type" in parsed) yield parsed;
|
|
240
|
+
} catch {
|
|
241
|
+
yield buffer;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
51
245
|
var ChatCanvas = ({
|
|
52
|
-
|
|
53
|
-
|
|
246
|
+
api,
|
|
247
|
+
allowedAttachments,
|
|
54
248
|
conversationId,
|
|
55
249
|
initialMessages,
|
|
56
250
|
systemPrompt,
|
|
@@ -63,20 +257,67 @@ var ChatCanvas = ({
|
|
|
63
257
|
onStop,
|
|
64
258
|
children
|
|
65
259
|
}) => {
|
|
66
|
-
const styles =
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
260
|
+
const styles = useStyles2();
|
|
261
|
+
const apiHandler = api ? async function(input2, history, files) {
|
|
262
|
+
const attachments2 = await Promise.all(
|
|
263
|
+
files.map(async (file) => ({
|
|
264
|
+
name: file.name,
|
|
265
|
+
mediaType: file.type,
|
|
266
|
+
data: await fileToBase64(file)
|
|
267
|
+
}))
|
|
268
|
+
);
|
|
269
|
+
const res = await fetch(api, {
|
|
270
|
+
method: "POST",
|
|
271
|
+
headers: { "Content-Type": "application/json" },
|
|
272
|
+
body: JSON.stringify({
|
|
273
|
+
text: input2,
|
|
274
|
+
history,
|
|
275
|
+
attachments: attachments2
|
|
276
|
+
})
|
|
277
|
+
});
|
|
278
|
+
if (!res.ok) {
|
|
279
|
+
const text = await res.text().catch(() => "");
|
|
280
|
+
throw Object.assign(
|
|
281
|
+
new Error(`Chat API request failed (${res.status}): ${text || res.statusText}`),
|
|
282
|
+
{ status: res.status }
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
286
|
+
if (contentType.includes("text/event-stream") || contentType.includes("application/x-ndjson")) {
|
|
287
|
+
return parseStream(res);
|
|
288
|
+
}
|
|
289
|
+
const data = await res.json();
|
|
290
|
+
if (data && typeof data === "object") {
|
|
291
|
+
if (typeof data.type === "string") {
|
|
292
|
+
return wrapWithStatusChunk(data.type, String(data.reply ?? ""));
|
|
293
|
+
}
|
|
294
|
+
if (typeof data.reply === "string") return data.reply;
|
|
295
|
+
}
|
|
296
|
+
return typeof data === "string" ? data : "";
|
|
297
|
+
} : void 0;
|
|
298
|
+
const resolvedHandler = onSendMessage ?? apiHandler;
|
|
299
|
+
const [messages, setMessages] = useState(
|
|
300
|
+
() => (initialMessages ?? []).map((m) => m.id ? m : { ...m, id: crypto.randomUUID() })
|
|
301
|
+
);
|
|
70
302
|
const [input, setInput] = useState("");
|
|
71
303
|
const [isTyping, setIsTyping] = useState(false);
|
|
72
304
|
const [isRetrying, setIsRetrying] = useState(false);
|
|
73
|
-
const [chatError, setChatError] = useState(null);
|
|
74
305
|
const [showScrollButton, setShowScrollButton] = useState(false);
|
|
75
306
|
const [inputHeight, setInputHeightState] = useState(0);
|
|
76
307
|
const [isInterrupted, setIsInterrupted] = useState(false);
|
|
77
308
|
const [streamingReasoning, setStreamingReasoning] = useState("");
|
|
78
|
-
const [
|
|
309
|
+
const [streamingStatus, setStreamingStatus] = useState();
|
|
79
310
|
const [attachments, setAttachments] = useState([]);
|
|
311
|
+
const [awaitingFreeformApproval, setAwaitingFreeformApproval] = useState(null);
|
|
312
|
+
const pendingApproval = (() => {
|
|
313
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
314
|
+
const m = messages[i];
|
|
315
|
+
if (m.role !== "assistant") continue;
|
|
316
|
+
if (m.approval && !m.approval.answer) return m.approval;
|
|
317
|
+
if (m.approval && m.approval.answer) return null;
|
|
318
|
+
}
|
|
319
|
+
return null;
|
|
320
|
+
})();
|
|
80
321
|
const reasoningRef = useRef("");
|
|
81
322
|
const lastUserInputRef = useRef("");
|
|
82
323
|
const hasEverSentRef = useRef(false);
|
|
@@ -89,7 +330,9 @@ var ChatCanvas = ({
|
|
|
89
330
|
useEffect(() => {
|
|
90
331
|
setMessages((current) => {
|
|
91
332
|
if ((initialMessages?.length ?? 0) > 0 || current.length === 0) {
|
|
92
|
-
return initialMessages ?? []
|
|
333
|
+
return (initialMessages ?? []).map(
|
|
334
|
+
(m) => m.id ? m : { ...m, id: crypto.randomUUID() }
|
|
335
|
+
);
|
|
93
336
|
}
|
|
94
337
|
return current;
|
|
95
338
|
});
|
|
@@ -156,40 +399,18 @@ var ChatCanvas = ({
|
|
|
156
399
|
const removeAttachment = (id) => {
|
|
157
400
|
setAttachments((prev) => prev.filter((a) => a.id !== id));
|
|
158
401
|
};
|
|
159
|
-
const
|
|
160
|
-
const textToSend = overrideInput ?? input;
|
|
161
|
-
if (!textToSend.trim()) return;
|
|
162
|
-
hasEverSentRef.current = true;
|
|
402
|
+
const runHandler = async (textToSend, updatedMessages, snapshotAttachments) => {
|
|
163
403
|
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
404
|
lastUserInputRef.current = textToSend;
|
|
181
405
|
userScrolledUpRef.current = false;
|
|
182
406
|
setMessages(updatedMessages);
|
|
183
407
|
onMessagesChange?.(updatedMessages, sessionAtSend);
|
|
184
|
-
if (!overrideInput) setInput("");
|
|
185
|
-
setAttachments([]);
|
|
186
408
|
setIsTyping(true);
|
|
187
409
|
setIsInterrupted(false);
|
|
188
|
-
setChatError(null);
|
|
189
410
|
shouldStopRef.current = false;
|
|
190
411
|
reasoningRef.current = "";
|
|
191
412
|
setStreamingReasoning("");
|
|
192
|
-
|
|
413
|
+
setStreamingStatus(void 0);
|
|
193
414
|
const historyForAI = systemPrompt ? [{ role: "system", content: systemPrompt }, ...updatedMessages] : updatedMessages;
|
|
194
415
|
const MAX_RETRIES = 3;
|
|
195
416
|
try {
|
|
@@ -205,6 +426,8 @@ var ChatCanvas = ({
|
|
|
205
426
|
let assistantContent = "";
|
|
206
427
|
let rafId = null;
|
|
207
428
|
let pendingContent = "";
|
|
429
|
+
let streamedApproval;
|
|
430
|
+
let streamedTodos;
|
|
208
431
|
for await (const chunk of response) {
|
|
209
432
|
if (shouldStopRef.current) break;
|
|
210
433
|
if (sessionAtSend !== void 0 && currentConversationIdRef.current !== sessionAtSend) break;
|
|
@@ -214,9 +437,13 @@ var ChatCanvas = ({
|
|
|
214
437
|
if (rafId === null) {
|
|
215
438
|
rafId = requestAnimationFrame(() => {
|
|
216
439
|
const content = pendingContent;
|
|
217
|
-
|
|
440
|
+
let nextMsg = { id: assistantId, role: "assistant", content };
|
|
218
441
|
setMessages((prev) => {
|
|
219
442
|
const updated = [...prev];
|
|
443
|
+
const last = updated[updated.length - 1];
|
|
444
|
+
if (last && last.id === assistantId) {
|
|
445
|
+
nextMsg = { ...last, content };
|
|
446
|
+
}
|
|
220
447
|
updated[updated.length - 1] = nextMsg;
|
|
221
448
|
return updated;
|
|
222
449
|
});
|
|
@@ -227,8 +454,28 @@ var ChatCanvas = ({
|
|
|
227
454
|
} else if (chunk.type === "reasoning") {
|
|
228
455
|
reasoningRef.current += chunk.content;
|
|
229
456
|
setStreamingReasoning(reasoningRef.current);
|
|
230
|
-
} else if (chunk.type === "
|
|
231
|
-
|
|
457
|
+
} else if (chunk.type === "status") {
|
|
458
|
+
setStreamingStatus(chunk.name);
|
|
459
|
+
} else if (chunk.type === "approval") {
|
|
460
|
+
streamedApproval = chunk.approval;
|
|
461
|
+
setMessages((prev) => {
|
|
462
|
+
const updated = [...prev];
|
|
463
|
+
const last = updated[updated.length - 1];
|
|
464
|
+
if (last && last.id === assistantId) {
|
|
465
|
+
updated[updated.length - 1] = { ...last, approval: chunk.approval };
|
|
466
|
+
}
|
|
467
|
+
return updated;
|
|
468
|
+
});
|
|
469
|
+
} else if (chunk.type === "todos") {
|
|
470
|
+
streamedTodos = chunk.todos;
|
|
471
|
+
setMessages((prev) => {
|
|
472
|
+
const updated = [...prev];
|
|
473
|
+
const last = updated[updated.length - 1];
|
|
474
|
+
if (last && last.id === assistantId) {
|
|
475
|
+
updated[updated.length - 1] = { ...last, todos: chunk.todos };
|
|
476
|
+
}
|
|
477
|
+
return updated;
|
|
478
|
+
});
|
|
232
479
|
}
|
|
233
480
|
}
|
|
234
481
|
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
@@ -240,7 +487,9 @@ var ChatCanvas = ({
|
|
|
240
487
|
id: assistantId,
|
|
241
488
|
role: "assistant",
|
|
242
489
|
content: assistantContent,
|
|
243
|
-
...reasoningRef.current ? { metadata: { reasoning: reasoningRef.current } } : {}
|
|
490
|
+
...reasoningRef.current ? { metadata: { reasoning: reasoningRef.current } } : {},
|
|
491
|
+
...streamedApproval ? { approval: streamedApproval } : {},
|
|
492
|
+
...streamedTodos ? { todos: streamedTodos } : {}
|
|
244
493
|
};
|
|
245
494
|
const finalMessages = [...updatedMessages, finalMsg];
|
|
246
495
|
setMessages(finalMessages);
|
|
@@ -259,7 +508,8 @@ var ChatCanvas = ({
|
|
|
259
508
|
role: "assistant",
|
|
260
509
|
content: response.content,
|
|
261
510
|
type: response.type,
|
|
262
|
-
metadata: response.metadata
|
|
511
|
+
metadata: response.metadata,
|
|
512
|
+
todos: response.todos
|
|
263
513
|
};
|
|
264
514
|
const finalMessages = [...updatedMessages, assistantMsg];
|
|
265
515
|
setMessages(finalMessages);
|
|
@@ -279,17 +529,148 @@ var ChatCanvas = ({
|
|
|
279
529
|
}
|
|
280
530
|
} catch (err) {
|
|
281
531
|
onError?.(err);
|
|
282
|
-
|
|
532
|
+
if (sessionAtSend === void 0 || currentConversationIdRef.current === sessionAtSend) {
|
|
533
|
+
const erroredMessages = [
|
|
534
|
+
...updatedMessages,
|
|
535
|
+
{
|
|
536
|
+
id: crypto.randomUUID(),
|
|
537
|
+
role: "assistant",
|
|
538
|
+
content: "",
|
|
539
|
+
error: normalizeChatError(err)
|
|
540
|
+
}
|
|
541
|
+
];
|
|
542
|
+
setMessages(erroredMessages);
|
|
543
|
+
onMessagesChange?.(erroredMessages, sessionAtSend);
|
|
544
|
+
}
|
|
283
545
|
} finally {
|
|
284
546
|
setIsRetrying(false);
|
|
285
547
|
setIsTyping(false);
|
|
286
548
|
}
|
|
287
549
|
};
|
|
288
|
-
|
|
550
|
+
const resolveApproval = (approvalId, optionId) => {
|
|
551
|
+
const target = messages.find(
|
|
552
|
+
(m) => m.role === "assistant" && m.approval?.id === approvalId
|
|
553
|
+
);
|
|
554
|
+
if (!target || !target.approval) return;
|
|
555
|
+
if (target.approval.answer) return;
|
|
556
|
+
const option = target.approval.options.find((o) => o.id === optionId);
|
|
557
|
+
if (!option) return;
|
|
558
|
+
if (option.freeform) {
|
|
559
|
+
setAwaitingFreeformApproval({ approvalId, optionId });
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
const updated = messages.map(
|
|
563
|
+
(m) => m.role === "assistant" && m.approval?.id === approvalId ? { ...m, approval: { ...m.approval, answer: { optionId } } } : m
|
|
564
|
+
);
|
|
565
|
+
const replyText = option.label;
|
|
566
|
+
const userMsg = {
|
|
567
|
+
id: crypto.randomUUID(),
|
|
568
|
+
role: "user",
|
|
569
|
+
type: "approval_response",
|
|
570
|
+
content: replyText,
|
|
571
|
+
metadata: { approvalId, optionId, label: option.label }
|
|
572
|
+
};
|
|
573
|
+
const next = [...updated, userMsg];
|
|
574
|
+
void runHandler(replyText, next, []);
|
|
575
|
+
};
|
|
576
|
+
const sendMessage = async (overrideInput) => {
|
|
577
|
+
const textToSend = overrideInput ?? input;
|
|
578
|
+
if (!textToSend.trim()) return;
|
|
579
|
+
if (awaitingFreeformApproval) {
|
|
580
|
+
const { approvalId, optionId } = awaitingFreeformApproval;
|
|
581
|
+
const target = messages.find(
|
|
582
|
+
(m) => m.role === "assistant" && m.approval?.id === approvalId
|
|
583
|
+
);
|
|
584
|
+
if (target && target.approval) {
|
|
585
|
+
const option = target.approval.options.find((o) => o.id === optionId);
|
|
586
|
+
const updated = messages.map(
|
|
587
|
+
(m) => m.role === "assistant" && m.approval?.id === approvalId ? { ...m, approval: { ...m.approval, answer: { optionId, text: textToSend } } } : m
|
|
588
|
+
);
|
|
589
|
+
const userMsg = {
|
|
590
|
+
id: crypto.randomUUID(),
|
|
591
|
+
role: "user",
|
|
592
|
+
type: "approval_response",
|
|
593
|
+
content: textToSend.replace(/^[\n\r]+|[\n\r]+$/g, ""),
|
|
594
|
+
metadata: { approvalId, optionId, label: option?.label, text: textToSend }
|
|
595
|
+
};
|
|
596
|
+
const next = [...updated, userMsg];
|
|
597
|
+
if (!overrideInput) setInput("");
|
|
598
|
+
setAwaitingFreeformApproval(null);
|
|
599
|
+
hasEverSentRef.current = true;
|
|
600
|
+
await runHandler(textToSend, next, []);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
setAwaitingFreeformApproval(null);
|
|
604
|
+
}
|
|
605
|
+
hasEverSentRef.current = true;
|
|
606
|
+
const lastMessage = messages[messages.length - 1];
|
|
607
|
+
const baseMessages = overrideInput ? messages.filter((_, i) => i < messages.length - 2) : lastMessage?.role === "assistant" && lastMessage.error ? messages.slice(0, -1) : messages;
|
|
608
|
+
const snapshotAttachments = attachments;
|
|
609
|
+
const attachmentMeta = snapshotAttachments.map((a) => ({
|
|
610
|
+
name: a.file.name,
|
|
611
|
+
type: a.file.type,
|
|
612
|
+
preview: a.preview
|
|
613
|
+
}));
|
|
614
|
+
const updatedMessages = [
|
|
615
|
+
...baseMessages,
|
|
616
|
+
{
|
|
617
|
+
id: crypto.randomUUID(),
|
|
618
|
+
role: "user",
|
|
619
|
+
content: textToSend.replace(/^[\n\r]+|[\n\r]+$/g, ""),
|
|
620
|
+
...attachmentMeta.length > 0 ? { metadata: { attachments: attachmentMeta } } : {}
|
|
621
|
+
}
|
|
622
|
+
];
|
|
623
|
+
if (!overrideInput) setInput("");
|
|
624
|
+
setAttachments([]);
|
|
625
|
+
await runHandler(textToSend, updatedMessages, snapshotAttachments);
|
|
626
|
+
};
|
|
627
|
+
const resolveRegenerateTarget = (messageId) => {
|
|
628
|
+
if (!messageId) {
|
|
629
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
630
|
+
if (messages[i].role === "user") return i;
|
|
631
|
+
}
|
|
632
|
+
return -1;
|
|
633
|
+
}
|
|
634
|
+
const idx = messages.findIndex((m) => m.id === messageId);
|
|
635
|
+
if (idx === -1) return -1;
|
|
636
|
+
if (messages[idx].role === "user") return idx;
|
|
637
|
+
if (messages[idx].role === "assistant") {
|
|
638
|
+
for (let i = idx - 1; i >= 0; i--) {
|
|
639
|
+
if (messages[i].role === "user") return i;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return -1;
|
|
643
|
+
};
|
|
644
|
+
const regenerate = async (messageId) => {
|
|
645
|
+
const userIdx = resolveRegenerateTarget(messageId);
|
|
646
|
+
if (userIdx === -1) return;
|
|
647
|
+
const userMsg = messages[userIdx];
|
|
648
|
+
const baseMessages = messages.slice(0, userIdx + 1);
|
|
649
|
+
await runHandler(userMsg.content, baseMessages, []);
|
|
650
|
+
};
|
|
651
|
+
const editMessage = async (messageId, newContent) => {
|
|
652
|
+
const idx = messages.findIndex((m) => m.id === messageId);
|
|
653
|
+
if (idx === -1) return;
|
|
654
|
+
if (messages[idx].role !== "user") return;
|
|
655
|
+
const trimmed = newContent.replace(/^[\n\r]+|[\n\r]+$/g, "");
|
|
656
|
+
if (!trimmed.trim()) return;
|
|
657
|
+
const editedMessage = {
|
|
658
|
+
...messages[idx],
|
|
659
|
+
content: trimmed
|
|
660
|
+
};
|
|
661
|
+
const baseMessages = [
|
|
662
|
+
...messages.slice(0, idx),
|
|
663
|
+
editedMessage
|
|
664
|
+
];
|
|
665
|
+
await runHandler(trimmed, baseMessages, []);
|
|
666
|
+
};
|
|
667
|
+
return /* @__PURE__ */ jsx2(
|
|
289
668
|
ChatContext.Provider,
|
|
290
669
|
{
|
|
291
670
|
value: {
|
|
292
671
|
sendMessage,
|
|
672
|
+
regenerate,
|
|
673
|
+
editMessage,
|
|
293
674
|
scrollToBottom,
|
|
294
675
|
handleCopy,
|
|
295
676
|
messages,
|
|
@@ -300,9 +681,8 @@ var ChatCanvas = ({
|
|
|
300
681
|
isRetrying,
|
|
301
682
|
isInterrupted,
|
|
302
683
|
streamingReasoning,
|
|
303
|
-
|
|
684
|
+
streamingStatus,
|
|
304
685
|
thinkingStatus,
|
|
305
|
-
chatError,
|
|
306
686
|
showScrollButton,
|
|
307
687
|
setInputHeight: setInputHeightState,
|
|
308
688
|
messagesContainerRef,
|
|
@@ -314,12 +694,16 @@ var ChatCanvas = ({
|
|
|
314
694
|
onStop,
|
|
315
695
|
attachments,
|
|
316
696
|
addAttachments,
|
|
317
|
-
removeAttachment
|
|
697
|
+
removeAttachment,
|
|
698
|
+
allowedAttachments,
|
|
699
|
+
pendingApproval,
|
|
700
|
+
awaitingFreeformApproval,
|
|
701
|
+
resolveApproval
|
|
318
702
|
},
|
|
319
|
-
children: /* @__PURE__ */
|
|
703
|
+
children: /* @__PURE__ */ jsxs2("div", { className: styles.root, children: [
|
|
320
704
|
children,
|
|
321
|
-
showScrollButton && /* @__PURE__ */
|
|
322
|
-
|
|
705
|
+
showScrollButton && /* @__PURE__ */ jsx2(
|
|
706
|
+
Button2,
|
|
323
707
|
{
|
|
324
708
|
onClick: scrollToBottom,
|
|
325
709
|
className: styles.scrollToBottom,
|
|
@@ -327,10 +711,10 @@ var ChatCanvas = ({
|
|
|
327
711
|
style: {
|
|
328
712
|
bottom: inputHeight + 16,
|
|
329
713
|
backgroundColor: "transparent",
|
|
330
|
-
border: `1px solid ${
|
|
714
|
+
border: `1px solid ${tokens2.colorNeutralStroke3}`,
|
|
331
715
|
backdropFilter: "saturate(180%) blur(16px)"
|
|
332
716
|
},
|
|
333
|
-
icon: /* @__PURE__ */
|
|
717
|
+
icon: /* @__PURE__ */ jsx2(ArrowDown, { style: { fontSize: "16px" } })
|
|
334
718
|
}
|
|
335
719
|
)
|
|
336
720
|
] })
|
|
@@ -340,22 +724,34 @@ var ChatCanvas = ({
|
|
|
340
724
|
var Chat_Canvas_default = ChatCanvas;
|
|
341
725
|
|
|
342
726
|
// src/Chat.Messages.tsx
|
|
343
|
-
import
|
|
344
|
-
import {
|
|
345
|
-
|
|
346
|
-
|
|
727
|
+
import React5, { useEffect as useEffect4, useLayoutEffect as useLayoutEffect2, useRef as useRef4, useState as useState5 } from "react";
|
|
728
|
+
import {
|
|
729
|
+
Body1 as Body15,
|
|
730
|
+
Button as Button4,
|
|
731
|
+
Caption1 as Caption12,
|
|
732
|
+
makeStyles as makeStyles7,
|
|
733
|
+
tokens as tokens7
|
|
734
|
+
} from "@fluentui/react-components";
|
|
735
|
+
import {
|
|
736
|
+
ArrowClockwise,
|
|
737
|
+
Copy,
|
|
738
|
+
Edit as EditIcon,
|
|
739
|
+
Info
|
|
740
|
+
} from "@coral-ai/bundleicons";
|
|
741
|
+
import {
|
|
742
|
+
CoralMarkdown
|
|
743
|
+
} from "@coral-ai/markdown";
|
|
347
744
|
|
|
348
745
|
// src/Chat.ThinkingIndicator.tsx
|
|
349
746
|
import { useState as useState2, useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
350
|
-
import { makeStyles as
|
|
351
|
-
import { jsx as
|
|
747
|
+
import { makeStyles as makeStyles3, Spinner, tokens as tokens3 } from "@fluentui/react-components";
|
|
748
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
352
749
|
var thinkingMessages = [
|
|
353
750
|
"Generating",
|
|
354
751
|
"Thinking",
|
|
355
752
|
"Optimizing",
|
|
356
753
|
"Processing",
|
|
357
754
|
"Analyzing",
|
|
358
|
-
"Reasoning",
|
|
359
755
|
"Drafting",
|
|
360
756
|
"Evaluating",
|
|
361
757
|
"Computing",
|
|
@@ -374,45 +770,13 @@ var retryingMessages = [
|
|
|
374
770
|
];
|
|
375
771
|
var TYPING_SPEED = 80;
|
|
376
772
|
var HOLD_DURATION = 1300;
|
|
377
|
-
var
|
|
378
|
-
var useStyles2 = makeStyles2({
|
|
773
|
+
var useStyles3 = makeStyles3({
|
|
379
774
|
root: {
|
|
380
775
|
display: "flex",
|
|
381
776
|
alignItems: "center",
|
|
382
777
|
gap: "8px",
|
|
383
778
|
width: "100%",
|
|
384
|
-
color:
|
|
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"
|
|
779
|
+
color: tokens3.colorNeutralForeground3
|
|
416
780
|
},
|
|
417
781
|
text: {
|
|
418
782
|
// fontFamily: tokens.fontFamilyBase,
|
|
@@ -425,7 +789,7 @@ var useStyles2 = makeStyles2({
|
|
|
425
789
|
display: "inline-block",
|
|
426
790
|
width: "6px",
|
|
427
791
|
height: "1em",
|
|
428
|
-
backgroundColor:
|
|
792
|
+
backgroundColor: tokens3.colorNeutralForeground3,
|
|
429
793
|
marginLeft: "1px",
|
|
430
794
|
verticalAlign: "text-bottom",
|
|
431
795
|
animationName: {
|
|
@@ -437,14 +801,14 @@ var useStyles2 = makeStyles2({
|
|
|
437
801
|
animationIterationCount: "infinite"
|
|
438
802
|
}
|
|
439
803
|
});
|
|
440
|
-
var ThinkingIndicator = ({ isRetrying = false, status }) => {
|
|
441
|
-
const styles =
|
|
804
|
+
var ThinkingIndicator = ({ isRetrying = false, status, messages }) => {
|
|
805
|
+
const styles = useStyles3();
|
|
442
806
|
const [displayText, setDisplayText] = useState2("");
|
|
443
807
|
const timerRef = useRef2(void 0);
|
|
444
808
|
const msgIdxRef = useRef2(0);
|
|
445
809
|
const charIdxRef = useRef2(0);
|
|
446
810
|
useEffect2(() => {
|
|
447
|
-
const pool = isRetrying ? retryingMessages : thinkingMessages;
|
|
811
|
+
const pool = isRetrying ? retryingMessages : messages && messages.length > 0 ? messages : thinkingMessages;
|
|
448
812
|
msgIdxRef.current = 0;
|
|
449
813
|
charIdxRef.current = 0;
|
|
450
814
|
const tick = () => {
|
|
@@ -463,32 +827,58 @@ var ThinkingIndicator = ({ isRetrying = false, status }) => {
|
|
|
463
827
|
};
|
|
464
828
|
tick();
|
|
465
829
|
return () => clearTimeout(timerRef.current);
|
|
466
|
-
}, [isRetrying]);
|
|
467
|
-
return /* @__PURE__ */
|
|
468
|
-
/* @__PURE__ */
|
|
469
|
-
/* @__PURE__ */
|
|
830
|
+
}, [isRetrying, messages]);
|
|
831
|
+
return /* @__PURE__ */ jsxs3("div", { className: styles.root, children: [
|
|
832
|
+
/* @__PURE__ */ jsx3(Spinner, { size: "tiny" }),
|
|
833
|
+
/* @__PURE__ */ jsxs3("span", { className: styles.text, children: [
|
|
470
834
|
status ?? displayText,
|
|
471
|
-
/* @__PURE__ */
|
|
835
|
+
/* @__PURE__ */ jsx3("span", { className: styles.cursor })
|
|
472
836
|
] })
|
|
473
837
|
] });
|
|
474
838
|
};
|
|
475
839
|
var Chat_ThinkingIndicator_default = ThinkingIndicator;
|
|
476
840
|
|
|
841
|
+
// src/Chat.TaskMessages.ts
|
|
842
|
+
var DEFAULT_TASK_MESSAGES = {
|
|
843
|
+
text: [
|
|
844
|
+
"Generating",
|
|
845
|
+
"Thinking",
|
|
846
|
+
"Optimizing",
|
|
847
|
+
"Processing",
|
|
848
|
+
"Analyzing",
|
|
849
|
+
"Drafting",
|
|
850
|
+
"Evaluating",
|
|
851
|
+
"Computing",
|
|
852
|
+
"Synthesizing",
|
|
853
|
+
"Reviewing",
|
|
854
|
+
"Refining"
|
|
855
|
+
],
|
|
856
|
+
photo: ["Looking", "Examining", "Reading image", "Analyzing visuals"],
|
|
857
|
+
file: ["Reading document", "Parsing", "Reviewing", "Scanning"],
|
|
858
|
+
voice: ["Listening", "Transcribing", "Processing audio"]
|
|
859
|
+
};
|
|
860
|
+
function resolveTaskMessages(name) {
|
|
861
|
+
if (name && name in DEFAULT_TASK_MESSAGES) {
|
|
862
|
+
return DEFAULT_TASK_MESSAGES[name];
|
|
863
|
+
}
|
|
864
|
+
return DEFAULT_TASK_MESSAGES.text;
|
|
865
|
+
}
|
|
866
|
+
|
|
477
867
|
// src/Chat.Callout.tsx
|
|
478
|
-
import { Body1, Button as
|
|
868
|
+
import { Body1 as Body12, Button as Button3, makeStyles as makeStyles4, mergeClasses, tokens as tokens4 } from "@fluentui/react-components";
|
|
479
869
|
import { ChatWarning20Regular } from "@fluentui/react-icons";
|
|
480
|
-
import { jsx as
|
|
481
|
-
var
|
|
870
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
871
|
+
var useStyles4 = makeStyles4({
|
|
482
872
|
root: {
|
|
483
873
|
display: "inline-flex",
|
|
484
874
|
alignItems: "center",
|
|
485
875
|
justifyContent: "space-between",
|
|
486
876
|
gap: "8px",
|
|
487
877
|
padding: "12px 12px",
|
|
488
|
-
borderRadius:
|
|
489
|
-
backgroundColor:
|
|
490
|
-
border: `1px solid ${
|
|
491
|
-
color:
|
|
878
|
+
borderRadius: tokens4.borderRadiusXLarge,
|
|
879
|
+
backgroundColor: tokens4.colorNeutralBackground3,
|
|
880
|
+
border: `1px solid ${tokens4.colorNeutralStroke2}`,
|
|
881
|
+
color: tokens4.colorNeutralForeground3,
|
|
492
882
|
width: "100%",
|
|
493
883
|
boxSizing: "border-box",
|
|
494
884
|
margin: "24px 0"
|
|
@@ -506,143 +896,25 @@ var useStyles3 = makeStyles3({
|
|
|
506
896
|
}
|
|
507
897
|
});
|
|
508
898
|
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
899
|
const styles = useStyles4();
|
|
628
|
-
|
|
629
|
-
return /* @__PURE__ */ jsxs4("div", { className: styles.root, children: [
|
|
900
|
+
return /* @__PURE__ */ jsxs4("div", { className: mergeClasses(styles.root, className), children: [
|
|
630
901
|
/* @__PURE__ */ jsxs4("span", { className: styles.left, children: [
|
|
631
|
-
|
|
632
|
-
/* @__PURE__ */ jsx4(Body12, { className: styles.
|
|
902
|
+
/* @__PURE__ */ jsx4(ChatWarning20Regular, {}),
|
|
903
|
+
/* @__PURE__ */ jsx4(Body12, { className: styles.label, children: message })
|
|
633
904
|
] }),
|
|
634
905
|
onRetry && /* @__PURE__ */ jsx4(
|
|
635
906
|
Button3,
|
|
636
907
|
{
|
|
908
|
+
onClick: onRetry,
|
|
909
|
+
className: styles.retryButton,
|
|
637
910
|
size: "small",
|
|
638
911
|
appearance: "primary",
|
|
639
|
-
onClick: onRetry,
|
|
640
912
|
children: "Retry"
|
|
641
913
|
}
|
|
642
914
|
)
|
|
643
915
|
] });
|
|
644
916
|
};
|
|
645
|
-
var
|
|
917
|
+
var Chat_Callout_default = ChatCallout;
|
|
646
918
|
|
|
647
919
|
// src/Chat.ReasoningBlock.tsx
|
|
648
920
|
import { useState as useState3, useEffect as useEffect3, useRef as useRef3 } from "react";
|
|
@@ -763,10 +1035,168 @@ var ReasoningBlock = ({
|
|
|
763
1035
|
};
|
|
764
1036
|
var Chat_ReasoningBlock_default = ReasoningBlock;
|
|
765
1037
|
|
|
766
|
-
// src/Chat.
|
|
767
|
-
import {
|
|
1038
|
+
// src/Chat.TodoList.tsx
|
|
1039
|
+
import { useState as useState4 } from "react";
|
|
1040
|
+
import {
|
|
1041
|
+
Body1 as Body14,
|
|
1042
|
+
Caption1,
|
|
1043
|
+
makeStyles as makeStyles6,
|
|
1044
|
+
Spinner as Spinner2,
|
|
1045
|
+
tokens as tokens6
|
|
1046
|
+
} from "@fluentui/react-components";
|
|
1047
|
+
import {
|
|
1048
|
+
CheckmarkCircle16Filled,
|
|
1049
|
+
ChevronRight16Regular as ChevronRight16Regular2,
|
|
1050
|
+
Circle16Regular
|
|
1051
|
+
} from "@fluentui/react-icons";
|
|
768
1052
|
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
769
1053
|
var useStyles6 = makeStyles6({
|
|
1054
|
+
root: {
|
|
1055
|
+
boxSizing: "border-box",
|
|
1056
|
+
display: "flex",
|
|
1057
|
+
flexDirection: "column",
|
|
1058
|
+
margin: "12px 0",
|
|
1059
|
+
borderRadius: "var(--borderRadiusXLarge)",
|
|
1060
|
+
backgroundColor: "var(--colorNeutralBackgroundAlpha2)",
|
|
1061
|
+
border: `1px solid ${tokens6.colorNeutralStroke2}`,
|
|
1062
|
+
overflow: "hidden"
|
|
1063
|
+
},
|
|
1064
|
+
header: {
|
|
1065
|
+
display: "flex",
|
|
1066
|
+
alignItems: "center",
|
|
1067
|
+
gap: "8px",
|
|
1068
|
+
padding: "10px 14px",
|
|
1069
|
+
cursor: "pointer",
|
|
1070
|
+
background: "none",
|
|
1071
|
+
border: "none",
|
|
1072
|
+
width: "100%",
|
|
1073
|
+
textAlign: "left"
|
|
1074
|
+
},
|
|
1075
|
+
label: {
|
|
1076
|
+
color: tokens6.colorNeutralForeground2,
|
|
1077
|
+
flexShrink: 0
|
|
1078
|
+
},
|
|
1079
|
+
count: {
|
|
1080
|
+
color: tokens6.colorNeutralForeground3,
|
|
1081
|
+
marginLeft: "auto",
|
|
1082
|
+
flexShrink: 0
|
|
1083
|
+
},
|
|
1084
|
+
chevron: {
|
|
1085
|
+
color: tokens6.colorNeutralForeground3,
|
|
1086
|
+
flexShrink: 0,
|
|
1087
|
+
transition: "transform 0.2s ease"
|
|
1088
|
+
},
|
|
1089
|
+
chevronExpanded: {
|
|
1090
|
+
transform: "rotate(90deg)"
|
|
1091
|
+
},
|
|
1092
|
+
list: {
|
|
1093
|
+
display: "flex",
|
|
1094
|
+
flexDirection: "column",
|
|
1095
|
+
gap: "8px",
|
|
1096
|
+
padding: "2px 14px 14px 14px"
|
|
1097
|
+
},
|
|
1098
|
+
item: {
|
|
1099
|
+
display: "flex",
|
|
1100
|
+
alignItems: "flex-start",
|
|
1101
|
+
gap: "8px"
|
|
1102
|
+
},
|
|
1103
|
+
iconCell: {
|
|
1104
|
+
display: "flex",
|
|
1105
|
+
alignItems: "center",
|
|
1106
|
+
justifyContent: "center",
|
|
1107
|
+
width: "16px",
|
|
1108
|
+
height: "20px",
|
|
1109
|
+
flexShrink: 0
|
|
1110
|
+
},
|
|
1111
|
+
pendingIcon: {
|
|
1112
|
+
color: tokens6.colorNeutralForeground4
|
|
1113
|
+
},
|
|
1114
|
+
doneIcon: {
|
|
1115
|
+
color: tokens6.colorStatusSuccessForeground1
|
|
1116
|
+
},
|
|
1117
|
+
text: {
|
|
1118
|
+
minWidth: 0,
|
|
1119
|
+
wordBreak: "break-word"
|
|
1120
|
+
},
|
|
1121
|
+
pendingText: {
|
|
1122
|
+
color: tokens6.colorNeutralForeground3
|
|
1123
|
+
},
|
|
1124
|
+
activeText: {
|
|
1125
|
+
color: tokens6.colorNeutralForeground1,
|
|
1126
|
+
fontWeight: tokens6.fontWeightSemibold
|
|
1127
|
+
},
|
|
1128
|
+
doneText: {
|
|
1129
|
+
color: tokens6.colorNeutralForeground3,
|
|
1130
|
+
textDecorationLine: "line-through"
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
var ChatTodoList = ({
|
|
1134
|
+
todos,
|
|
1135
|
+
className,
|
|
1136
|
+
style
|
|
1137
|
+
}) => {
|
|
1138
|
+
const styles = useStyles6();
|
|
1139
|
+
const total = todos.length;
|
|
1140
|
+
const completed = todos.filter((t) => t.status === "completed").length;
|
|
1141
|
+
const [expanded, setExpanded] = useState4(true);
|
|
1142
|
+
if (total === 0) return null;
|
|
1143
|
+
return /* @__PURE__ */ jsxs6(
|
|
1144
|
+
"div",
|
|
1145
|
+
{
|
|
1146
|
+
className: `${styles.root}${className ? ` ${className}` : ""}`,
|
|
1147
|
+
style,
|
|
1148
|
+
role: "group",
|
|
1149
|
+
"aria-label": `Task plan, ${completed} of ${total} done`,
|
|
1150
|
+
children: [
|
|
1151
|
+
/* @__PURE__ */ jsxs6(
|
|
1152
|
+
"button",
|
|
1153
|
+
{
|
|
1154
|
+
type: "button",
|
|
1155
|
+
className: styles.header,
|
|
1156
|
+
onClick: () => setExpanded((e) => !e),
|
|
1157
|
+
"aria-expanded": expanded,
|
|
1158
|
+
children: [
|
|
1159
|
+
/* @__PURE__ */ jsx6(
|
|
1160
|
+
ChevronRight16Regular2,
|
|
1161
|
+
{
|
|
1162
|
+
className: `${styles.chevron}${expanded ? ` ${styles.chevronExpanded}` : ""}`
|
|
1163
|
+
}
|
|
1164
|
+
),
|
|
1165
|
+
/* @__PURE__ */ jsx6(Body14, { className: styles.label, children: "Tasks" }),
|
|
1166
|
+
/* @__PURE__ */ jsxs6(Caption1, { className: styles.count, children: [
|
|
1167
|
+
completed,
|
|
1168
|
+
"/",
|
|
1169
|
+
total
|
|
1170
|
+
] })
|
|
1171
|
+
]
|
|
1172
|
+
}
|
|
1173
|
+
),
|
|
1174
|
+
expanded && /* @__PURE__ */ jsx6("div", { className: styles.list, role: "list", children: todos.map((todo, index) => {
|
|
1175
|
+
const text = todo.status === "in_progress" ? todo.activeForm ?? todo.content : todo.content;
|
|
1176
|
+
const textClass = todo.status === "completed" ? styles.doneText : todo.status === "in_progress" ? styles.activeText : styles.pendingText;
|
|
1177
|
+
return /* @__PURE__ */ jsxs6(
|
|
1178
|
+
"div",
|
|
1179
|
+
{
|
|
1180
|
+
className: styles.item,
|
|
1181
|
+
role: "listitem",
|
|
1182
|
+
children: [
|
|
1183
|
+
/* @__PURE__ */ jsx6("span", { className: styles.iconCell, children: todo.status === "completed" ? /* @__PURE__ */ jsx6(CheckmarkCircle16Filled, { className: styles.doneIcon }) : todo.status === "in_progress" ? /* @__PURE__ */ jsx6(Spinner2, { size: "extra-tiny" }) : /* @__PURE__ */ jsx6(Circle16Regular, { className: styles.pendingIcon }) }),
|
|
1184
|
+
/* @__PURE__ */ jsx6(Body14, { className: `${styles.text} ${textClass}`, children: text })
|
|
1185
|
+
]
|
|
1186
|
+
},
|
|
1187
|
+
todo.id ?? `todo-${index}`
|
|
1188
|
+
);
|
|
1189
|
+
}) })
|
|
1190
|
+
]
|
|
1191
|
+
}
|
|
1192
|
+
);
|
|
1193
|
+
};
|
|
1194
|
+
var Chat_TodoList_default = ChatTodoList;
|
|
1195
|
+
|
|
1196
|
+
// src/Chat.Messages.tsx
|
|
1197
|
+
import { CheckmarkCircle20Filled } from "@fluentui/react-icons";
|
|
1198
|
+
import { Fragment, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1199
|
+
var useStyles7 = makeStyles7({
|
|
770
1200
|
messages: {
|
|
771
1201
|
flex: "1",
|
|
772
1202
|
overflowY: "auto",
|
|
@@ -787,43 +1217,119 @@ var useStyles6 = makeStyles6({
|
|
|
787
1217
|
padding: "0 16px",
|
|
788
1218
|
boxSizing: "border-box"
|
|
789
1219
|
},
|
|
790
|
-
|
|
1220
|
+
// ── User message column: holds attachments + bubble + hover-only footer ──
|
|
1221
|
+
userRow: {
|
|
791
1222
|
alignSelf: "flex-end",
|
|
1223
|
+
display: "flex",
|
|
1224
|
+
flexDirection: "column",
|
|
1225
|
+
alignItems: "flex-end",
|
|
792
1226
|
maxWidth: "80%",
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1227
|
+
"&:hover .coral-user-footer, &:focus-within .coral-user-footer": {
|
|
1228
|
+
opacity: 1
|
|
1229
|
+
}
|
|
1230
|
+
},
|
|
1231
|
+
userBubble: {
|
|
1232
|
+
backgroundColor: tokens7.colorNeutralBackground2,
|
|
1233
|
+
color: tokens7.colorNeutralForeground2,
|
|
1234
|
+
border: `1px solid ${tokens7.colorNeutralStroke2}`,
|
|
796
1235
|
padding: "8px 12px",
|
|
797
|
-
borderRadius:
|
|
1236
|
+
borderRadius: tokens7.borderRadiusXLarge,
|
|
798
1237
|
wordBreak: "break-word",
|
|
799
1238
|
boxSizing: "border-box"
|
|
800
1239
|
},
|
|
1240
|
+
userFooter: {
|
|
1241
|
+
display: "flex",
|
|
1242
|
+
justifyContent: "flex-end",
|
|
1243
|
+
marginTop: "6px",
|
|
1244
|
+
marginRight: "-10px",
|
|
1245
|
+
opacity: 0,
|
|
1246
|
+
transition: "opacity 120ms ease-in-out"
|
|
1247
|
+
},
|
|
1248
|
+
userActions: {
|
|
1249
|
+
display: "flex"
|
|
1250
|
+
},
|
|
1251
|
+
// ── Inline edit mode ──
|
|
1252
|
+
userEditWrap: {
|
|
1253
|
+
display: "flex",
|
|
1254
|
+
flexDirection: "column",
|
|
1255
|
+
alignItems: "stretch",
|
|
1256
|
+
width: "min(640px, 80vw)",
|
|
1257
|
+
gap: "8px"
|
|
1258
|
+
},
|
|
1259
|
+
userEditTextarea: {
|
|
1260
|
+
backgroundColor: tokens7.colorNeutralBackground2,
|
|
1261
|
+
color: tokens7.colorNeutralForeground2,
|
|
1262
|
+
border: `1px solid ${tokens7.colorNeutralStroke1}`,
|
|
1263
|
+
padding: "8px 12px",
|
|
1264
|
+
borderRadius: tokens7.borderRadiusLarge,
|
|
1265
|
+
fontFamily: tokens7.fontFamilyBase,
|
|
1266
|
+
fontSize: tokens7.fontSizeBase300,
|
|
1267
|
+
lineHeight: tokens7.lineHeightBase300,
|
|
1268
|
+
resize: "none",
|
|
1269
|
+
outline: "none",
|
|
1270
|
+
width: "100%",
|
|
1271
|
+
boxSizing: "border-box",
|
|
1272
|
+
minHeight: "40px",
|
|
1273
|
+
"&:focus": {
|
|
1274
|
+
border: `1px solid ${tokens7.colorNeutralStroke3}`
|
|
1275
|
+
}
|
|
1276
|
+
},
|
|
1277
|
+
userEditFooter: {
|
|
1278
|
+
display: "flex",
|
|
1279
|
+
marginTop: "8px",
|
|
1280
|
+
// marginLeft: "12px",
|
|
1281
|
+
color: `${tokens7.colorNeutralForeground3}`,
|
|
1282
|
+
gap: "12px",
|
|
1283
|
+
alignItems: "center"
|
|
1284
|
+
},
|
|
1285
|
+
userEditActions: {
|
|
1286
|
+
display: "flex",
|
|
1287
|
+
justifyContent: "flex-end",
|
|
1288
|
+
gap: "8px"
|
|
1289
|
+
},
|
|
1290
|
+
// ── Assistant message ──
|
|
801
1291
|
messageAssistant: {
|
|
802
1292
|
alignSelf: "flex-start",
|
|
803
1293
|
width: "100%",
|
|
804
|
-
color:
|
|
1294
|
+
color: tokens7.colorNeutralForeground2,
|
|
805
1295
|
wordBreak: "break-word",
|
|
806
|
-
boxSizing: "border-box"
|
|
1296
|
+
boxSizing: "border-box",
|
|
1297
|
+
"&:hover .coral-assistant-footer, &:focus-within .coral-assistant-footer": {
|
|
1298
|
+
opacity: 1
|
|
1299
|
+
}
|
|
807
1300
|
},
|
|
808
1301
|
assistantFooter: {
|
|
809
1302
|
display: "flex",
|
|
810
1303
|
justifyContent: "space-between",
|
|
811
1304
|
marginTop: "6px",
|
|
812
|
-
marginLeft: "-10px"
|
|
1305
|
+
marginLeft: "-10px",
|
|
1306
|
+
// Hover-only by default; the latest message overrides this to stay visible.
|
|
1307
|
+
opacity: 0,
|
|
1308
|
+
transition: "opacity 120ms ease-in-out"
|
|
813
1309
|
},
|
|
814
1310
|
assistantActions: {
|
|
815
1311
|
display: "flex"
|
|
1312
|
+
},
|
|
1313
|
+
copiedBadge: {
|
|
1314
|
+
display: "inline-flex",
|
|
1315
|
+
alignItems: "center",
|
|
1316
|
+
justifyContent: "center",
|
|
1317
|
+
width: "32px",
|
|
1318
|
+
height: "32px",
|
|
1319
|
+
color: tokens7.colorPaletteSeafoamForeground2
|
|
1320
|
+
},
|
|
1321
|
+
footerIconButton: {
|
|
1322
|
+
color: tokens7.colorNeutralForeground3
|
|
816
1323
|
}
|
|
817
1324
|
});
|
|
818
1325
|
var ChatMessages = ({
|
|
819
1326
|
components,
|
|
820
|
-
onFeedback,
|
|
821
1327
|
onCopy,
|
|
822
1328
|
onNavigate,
|
|
823
1329
|
fontSize,
|
|
824
1330
|
fontFamily
|
|
825
1331
|
}) => {
|
|
826
|
-
const styles =
|
|
1332
|
+
const styles = useStyles7();
|
|
827
1333
|
const ctx = useChatContext();
|
|
828
1334
|
const {
|
|
829
1335
|
messages,
|
|
@@ -832,35 +1338,204 @@ var ChatMessages = ({
|
|
|
832
1338
|
isRetrying,
|
|
833
1339
|
isInterrupted,
|
|
834
1340
|
streamingReasoning,
|
|
835
|
-
|
|
1341
|
+
streamingStatus,
|
|
836
1342
|
thinkingStatus,
|
|
837
|
-
chatError,
|
|
838
1343
|
sendMessage,
|
|
1344
|
+
regenerate,
|
|
1345
|
+
editMessage,
|
|
839
1346
|
handleCopy,
|
|
840
1347
|
lastUserInputRef,
|
|
841
1348
|
registerMessagesContainer,
|
|
842
1349
|
bottomRef
|
|
843
1350
|
} = ctx;
|
|
844
1351
|
const containerRef = useRef4(null);
|
|
845
|
-
const [
|
|
1352
|
+
const [surface, setSurface] = useState5(null);
|
|
1353
|
+
useLayoutEffect2(() => {
|
|
1354
|
+
const el = containerRef.current?.closest("[data-coral-surface]");
|
|
1355
|
+
setSurface(el?.getAttribute("data-coral-surface") ?? null);
|
|
1356
|
+
}, []);
|
|
1357
|
+
const isSidecar = surface === "panel-right";
|
|
1358
|
+
const [copiedKey, setCopiedKey] = useState5(null);
|
|
846
1359
|
const copiedTimerRef = useRef4(null);
|
|
1360
|
+
const [editingId, setEditingId] = useState5(null);
|
|
1361
|
+
const [editDraft, setEditDraft] = useState5("");
|
|
1362
|
+
const editTextareaRef = useRef4(null);
|
|
847
1363
|
useEffect4(() => {
|
|
848
1364
|
registerMessagesContainer(containerRef.current);
|
|
849
1365
|
return () => registerMessagesContainer(null);
|
|
850
1366
|
}, []);
|
|
851
|
-
|
|
1367
|
+
useLayoutEffect2(() => {
|
|
1368
|
+
if (editingId && editTextareaRef.current) {
|
|
1369
|
+
const ta = editTextareaRef.current;
|
|
1370
|
+
ta.focus();
|
|
1371
|
+
ta.setSelectionRange(ta.value.length, ta.value.length);
|
|
1372
|
+
ta.style.height = "auto";
|
|
1373
|
+
ta.style.height = `${ta.scrollHeight}px`;
|
|
1374
|
+
}
|
|
1375
|
+
}, [editingId]);
|
|
1376
|
+
const startEdit = (msg) => {
|
|
1377
|
+
if (!msg.id) return;
|
|
1378
|
+
setEditingId(msg.id);
|
|
1379
|
+
setEditDraft(msg.content);
|
|
1380
|
+
};
|
|
1381
|
+
const cancelEdit = () => {
|
|
1382
|
+
setEditingId(null);
|
|
1383
|
+
setEditDraft("");
|
|
1384
|
+
};
|
|
1385
|
+
const saveEdit = async () => {
|
|
1386
|
+
if (!editingId) return;
|
|
1387
|
+
const trimmed = editDraft.trim();
|
|
1388
|
+
if (!trimmed) return;
|
|
1389
|
+
const id = editingId;
|
|
1390
|
+
setEditingId(null);
|
|
1391
|
+
setEditDraft("");
|
|
1392
|
+
await editMessage(id, trimmed);
|
|
1393
|
+
};
|
|
1394
|
+
const handleEditTextareaInput = (e) => {
|
|
1395
|
+
setEditDraft(e.target.value);
|
|
1396
|
+
const ta = e.target;
|
|
1397
|
+
ta.style.height = "auto";
|
|
1398
|
+
ta.style.height = `${ta.scrollHeight}px`;
|
|
1399
|
+
};
|
|
1400
|
+
const handleEditKeyDown = (e) => {
|
|
1401
|
+
if (e.key === "Escape") {
|
|
1402
|
+
e.preventDefault();
|
|
1403
|
+
cancelEdit();
|
|
1404
|
+
} else if (e.key === "Enter" && !e.shiftKey) {
|
|
1405
|
+
e.preventDefault();
|
|
1406
|
+
void saveEdit();
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
const onCopyMerged = (content, key) => {
|
|
852
1410
|
handleCopy(content);
|
|
853
1411
|
onCopy?.(content);
|
|
854
1412
|
if (copiedTimerRef.current) clearTimeout(copiedTimerRef.current);
|
|
855
|
-
|
|
856
|
-
copiedTimerRef.current = setTimeout(() =>
|
|
1413
|
+
setCopiedKey(key);
|
|
1414
|
+
copiedTimerRef.current = setTimeout(() => setCopiedKey(null), 1500);
|
|
1415
|
+
};
|
|
1416
|
+
const renderUserMessage = (msg, index) => {
|
|
1417
|
+
const key = msg.id ?? `idx-${index}`;
|
|
1418
|
+
const isEditing = !!msg.id && editingId === msg.id;
|
|
1419
|
+
const attachments = Array.isArray(msg.metadata?.attachments) ? msg.metadata.attachments : null;
|
|
1420
|
+
return /* @__PURE__ */ jsxs7("div", { className: styles.userRow, children: [
|
|
1421
|
+
attachments && /* @__PURE__ */ jsx7(
|
|
1422
|
+
"div",
|
|
1423
|
+
{
|
|
1424
|
+
style: {
|
|
1425
|
+
display: "flex",
|
|
1426
|
+
gap: "6px",
|
|
1427
|
+
flexWrap: "wrap",
|
|
1428
|
+
marginBottom: "6px"
|
|
1429
|
+
},
|
|
1430
|
+
children: attachments.map(
|
|
1431
|
+
(a, i) => a.type.startsWith("image/") && a.preview ? /* @__PURE__ */ jsx7(
|
|
1432
|
+
"img",
|
|
1433
|
+
{
|
|
1434
|
+
src: a.preview,
|
|
1435
|
+
alt: a.name,
|
|
1436
|
+
style: {
|
|
1437
|
+
width: 64,
|
|
1438
|
+
height: 64,
|
|
1439
|
+
borderRadius: tokens7.borderRadiusMedium,
|
|
1440
|
+
objectFit: "cover"
|
|
1441
|
+
}
|
|
1442
|
+
},
|
|
1443
|
+
i
|
|
1444
|
+
) : /* @__PURE__ */ jsxs7(
|
|
1445
|
+
"span",
|
|
1446
|
+
{
|
|
1447
|
+
style: {
|
|
1448
|
+
fontSize: 12,
|
|
1449
|
+
padding: "2px 8px",
|
|
1450
|
+
borderRadius: tokens7.borderRadiusSmall,
|
|
1451
|
+
backgroundColor: tokens7.colorNeutralBackground3,
|
|
1452
|
+
color: tokens7.colorNeutralForeground2
|
|
1453
|
+
},
|
|
1454
|
+
children: [
|
|
1455
|
+
"\u{1F4C4} ",
|
|
1456
|
+
a.name
|
|
1457
|
+
]
|
|
1458
|
+
},
|
|
1459
|
+
i
|
|
1460
|
+
)
|
|
1461
|
+
)
|
|
1462
|
+
}
|
|
1463
|
+
),
|
|
1464
|
+
isEditing ? /* @__PURE__ */ jsxs7("div", { className: styles.userEditWrap, children: [
|
|
1465
|
+
/* @__PURE__ */ jsx7(
|
|
1466
|
+
"textarea",
|
|
1467
|
+
{
|
|
1468
|
+
ref: editTextareaRef,
|
|
1469
|
+
className: styles.userEditTextarea,
|
|
1470
|
+
value: editDraft,
|
|
1471
|
+
onChange: handleEditTextareaInput,
|
|
1472
|
+
onKeyDown: handleEditKeyDown,
|
|
1473
|
+
rows: 1
|
|
1474
|
+
}
|
|
1475
|
+
),
|
|
1476
|
+
/* @__PURE__ */ jsxs7("div", { className: styles.userEditFooter, children: [
|
|
1477
|
+
/* @__PURE__ */ jsx7(
|
|
1478
|
+
Info,
|
|
1479
|
+
{
|
|
1480
|
+
style: {
|
|
1481
|
+
marginRight: "6px",
|
|
1482
|
+
color: `${tokens7.colorNeutralForeground3}`,
|
|
1483
|
+
fontSize: "20px",
|
|
1484
|
+
boxSizing: "border-box",
|
|
1485
|
+
flexShrink: 0
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
),
|
|
1489
|
+
/* @__PURE__ */ jsx7(Caption12, { children: "Editing this message will discard the current response and regenerate from here." }),
|
|
1490
|
+
/* @__PURE__ */ jsxs7("div", { className: styles.userEditActions, children: [
|
|
1491
|
+
/* @__PURE__ */ jsx7(Button4, { appearance: "subtle", onClick: cancelEdit, children: "Cancel" }),
|
|
1492
|
+
/* @__PURE__ */ jsx7(
|
|
1493
|
+
Button4,
|
|
1494
|
+
{
|
|
1495
|
+
appearance: "primary",
|
|
1496
|
+
onClick: saveEdit,
|
|
1497
|
+
disabled: !editDraft.trim(),
|
|
1498
|
+
children: "Save"
|
|
1499
|
+
}
|
|
1500
|
+
)
|
|
1501
|
+
] })
|
|
1502
|
+
] })
|
|
1503
|
+
] }) : /* @__PURE__ */ jsxs7(Fragment, { children: [
|
|
1504
|
+
/* @__PURE__ */ jsx7("div", { className: styles.userBubble, children: /* @__PURE__ */ jsx7(Body15, { children: msg.content.split("\n").map((line, i, arr) => /* @__PURE__ */ jsxs7(React5.Fragment, { children: [
|
|
1505
|
+
line,
|
|
1506
|
+
i < arr.length - 1 && /* @__PURE__ */ jsx7("br", {})
|
|
1507
|
+
] }, i)) }) }),
|
|
1508
|
+
msg.id && /* @__PURE__ */ jsx7("div", { className: `${styles.userFooter} coral-user-footer`, children: /* @__PURE__ */ jsxs7("div", { className: styles.userActions, children: [
|
|
1509
|
+
copiedKey === `user-${key}` ? /* @__PURE__ */ jsx7("span", { className: styles.copiedBadge, children: /* @__PURE__ */ jsx7(CheckmarkCircle20Filled, {}) }) : /* @__PURE__ */ jsx7(
|
|
1510
|
+
Button4,
|
|
1511
|
+
{
|
|
1512
|
+
onClick: () => onCopyMerged(msg.content, `user-${key}`),
|
|
1513
|
+
title: "Copy message",
|
|
1514
|
+
appearance: "subtle",
|
|
1515
|
+
className: styles.footerIconButton,
|
|
1516
|
+
icon: /* @__PURE__ */ jsx7(Copy, {})
|
|
1517
|
+
}
|
|
1518
|
+
),
|
|
1519
|
+
/* @__PURE__ */ jsx7(
|
|
1520
|
+
Button4,
|
|
1521
|
+
{
|
|
1522
|
+
onClick: () => startEdit(msg),
|
|
1523
|
+
title: "Edit message",
|
|
1524
|
+
appearance: "subtle",
|
|
1525
|
+
className: styles.footerIconButton,
|
|
1526
|
+
icon: /* @__PURE__ */ jsx7(EditIcon, {})
|
|
1527
|
+
}
|
|
1528
|
+
)
|
|
1529
|
+
] }) })
|
|
1530
|
+
] })
|
|
1531
|
+
] }, key);
|
|
857
1532
|
};
|
|
858
1533
|
const renderAssistantMessage = (msg, index) => {
|
|
859
1534
|
const isLatest = index === messages.length - 1;
|
|
860
1535
|
const reasoning = isLatest && isTyping ? streamingReasoning : msg.metadata?.reasoning;
|
|
861
1536
|
if (msg.type && components?.[msg.type]) {
|
|
862
1537
|
const CustomRenderer = components[msg.type];
|
|
863
|
-
return /* @__PURE__ */
|
|
1538
|
+
return /* @__PURE__ */ jsx7(
|
|
864
1539
|
CustomRenderer,
|
|
865
1540
|
{
|
|
866
1541
|
message: msg,
|
|
@@ -875,131 +1550,197 @@ var ChatMessages = ({
|
|
|
875
1550
|
}
|
|
876
1551
|
);
|
|
877
1552
|
}
|
|
878
|
-
|
|
879
|
-
|
|
1553
|
+
const key = msg.id ?? `idx-${index}`;
|
|
1554
|
+
return /* @__PURE__ */ jsxs7("div", { children: [
|
|
1555
|
+
reasoning && /* @__PURE__ */ jsx7(
|
|
880
1556
|
Chat_ReasoningBlock_default,
|
|
881
1557
|
{
|
|
882
1558
|
content: reasoning,
|
|
883
1559
|
isStreaming: isLatest && isTyping && !msg.content
|
|
884
1560
|
}
|
|
885
1561
|
),
|
|
886
|
-
/* @__PURE__ */
|
|
887
|
-
|
|
1562
|
+
msg.todos && msg.todos.length > 0 && /* @__PURE__ */ jsx7(Chat_TodoList_default, { todos: msg.todos }),
|
|
1563
|
+
/* @__PURE__ */ jsx7(
|
|
1564
|
+
CoralMarkdown,
|
|
1565
|
+
{
|
|
1566
|
+
onNavigate,
|
|
1567
|
+
fontSize,
|
|
1568
|
+
fontFamily,
|
|
1569
|
+
children: msg.content
|
|
1570
|
+
}
|
|
1571
|
+
),
|
|
1572
|
+
msg.error && /* @__PURE__ */ jsx7(
|
|
1573
|
+
Chat_Error_default,
|
|
1574
|
+
{
|
|
1575
|
+
error: msg.error,
|
|
1576
|
+
onRetry: () => {
|
|
1577
|
+
const userMsg = messages[index - 1];
|
|
1578
|
+
if (userMsg?.role === "user") sendMessage(userMsg.content);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
),
|
|
1582
|
+
isInterrupted && !isTyping && isLatest && /* @__PURE__ */ jsx7(
|
|
888
1583
|
Chat_Callout_default,
|
|
889
1584
|
{
|
|
890
1585
|
message: "Response was interrupted",
|
|
891
1586
|
onRetry: () => sendMessage(lastUserInputRef.current)
|
|
892
1587
|
}
|
|
893
1588
|
),
|
|
894
|
-
!(isTyping && isLatest) &&
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
{
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
1589
|
+
!(isTyping && isLatest) && msg.content.trim() && /* @__PURE__ */ jsx7(
|
|
1590
|
+
"div",
|
|
1591
|
+
{
|
|
1592
|
+
className: `${styles.assistantFooter} coral-assistant-footer`,
|
|
1593
|
+
style: isLatest ? { opacity: 1 } : void 0,
|
|
1594
|
+
children: /* @__PURE__ */ jsxs7("div", { className: styles.assistantActions, children: [
|
|
1595
|
+
copiedKey === `assistant-${key}` ? /* @__PURE__ */ jsx7("span", { className: styles.copiedBadge, children: /* @__PURE__ */ jsx7(CheckmarkCircle20Filled, {}) }) : /* @__PURE__ */ jsx7(
|
|
1596
|
+
Button4,
|
|
1597
|
+
{
|
|
1598
|
+
onClick: () => onCopyMerged(msg.content, `assistant-${key}`),
|
|
1599
|
+
title: "Copy response",
|
|
1600
|
+
appearance: "subtle",
|
|
1601
|
+
className: styles.footerIconButton,
|
|
1602
|
+
icon: /* @__PURE__ */ jsx7(Copy, {})
|
|
1603
|
+
}
|
|
1604
|
+
),
|
|
1605
|
+
isLatest && /* @__PURE__ */ jsx7(
|
|
1606
|
+
Button4,
|
|
1607
|
+
{
|
|
1608
|
+
onClick: () => regenerate(msg.id),
|
|
1609
|
+
title: "Regenerate response",
|
|
1610
|
+
appearance: "subtle",
|
|
1611
|
+
className: styles.footerIconButton,
|
|
1612
|
+
icon: /* @__PURE__ */ jsx7(ArrowClockwise, {})
|
|
1613
|
+
}
|
|
1614
|
+
)
|
|
1615
|
+
] })
|
|
1616
|
+
}
|
|
1617
|
+
)
|
|
1618
|
+
] });
|
|
1619
|
+
};
|
|
1620
|
+
return /* @__PURE__ */ jsx7("div", { className: styles.messages, ref: containerRef, children: /* @__PURE__ */ jsxs7(
|
|
1621
|
+
"div",
|
|
1622
|
+
{
|
|
1623
|
+
className: styles.messageWrapper,
|
|
1624
|
+
style: isSidecar ? { padding: "0 8px" } : void 0,
|
|
1625
|
+
children: [
|
|
1626
|
+
messages.map((msg, index) => {
|
|
1627
|
+
if (msg.role === "user") {
|
|
1628
|
+
return renderUserMessage(msg, index);
|
|
907
1629
|
}
|
|
908
|
-
|
|
909
|
-
|
|
1630
|
+
const key = msg.id ?? `idx-${index}`;
|
|
1631
|
+
const isLatest = index === messages.length - 1;
|
|
1632
|
+
return /* @__PURE__ */ jsx7(
|
|
1633
|
+
"div",
|
|
1634
|
+
{
|
|
1635
|
+
className: `${styles.messageAssistant} coral-message-assistant${isLatest ? " is-latest" : ""}`,
|
|
1636
|
+
children: renderAssistantMessage(msg, index)
|
|
1637
|
+
},
|
|
1638
|
+
key
|
|
1639
|
+
);
|
|
1640
|
+
}),
|
|
1641
|
+
isTyping && /* @__PURE__ */ jsx7(
|
|
1642
|
+
Chat_ThinkingIndicator_default,
|
|
910
1643
|
{
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
style: { color: tokens6.colorNeutralForeground3 },
|
|
915
|
-
icon: /* @__PURE__ */ jsx6(Copy, {})
|
|
1644
|
+
isRetrying,
|
|
1645
|
+
status: thinkingStatus,
|
|
1646
|
+
messages: resolveTaskMessages(streamingStatus)
|
|
916
1647
|
}
|
|
917
1648
|
),
|
|
918
|
-
/* @__PURE__ */
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
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
|
-
] }) });
|
|
1649
|
+
/* @__PURE__ */ jsx7("div", { ref: bottomRef })
|
|
1650
|
+
]
|
|
1651
|
+
}
|
|
1652
|
+
) });
|
|
988
1653
|
};
|
|
989
1654
|
var Chat_Messages_default = ChatMessages;
|
|
990
1655
|
|
|
991
1656
|
// src/Chat.Input.tsx
|
|
992
1657
|
import {
|
|
993
|
-
useRef as
|
|
994
|
-
useState as
|
|
1658
|
+
useRef as useRef6,
|
|
1659
|
+
useState as useState6,
|
|
995
1660
|
useEffect as useEffect5,
|
|
996
1661
|
forwardRef,
|
|
997
1662
|
useImperativeHandle
|
|
998
1663
|
} from "react";
|
|
999
|
-
import { Body1 as
|
|
1664
|
+
import { Body1 as Body16, Button as Button6, Tag, makeStyles as makeStyles8, tokens as tokens8 } from "@fluentui/react-components";
|
|
1000
1665
|
import { Dismiss, Send, Stop } from "@coral-ai/bundleicons";
|
|
1001
|
-
|
|
1002
|
-
|
|
1666
|
+
|
|
1667
|
+
// src/Chat.AttachButton.tsx
|
|
1668
|
+
import { useRef as useRef5 } from "react";
|
|
1669
|
+
import {
|
|
1670
|
+
Button as Button5,
|
|
1671
|
+
Menu,
|
|
1672
|
+
MenuTrigger,
|
|
1673
|
+
MenuPopover,
|
|
1674
|
+
MenuList,
|
|
1675
|
+
MenuItem
|
|
1676
|
+
} from "@fluentui/react-components";
|
|
1677
|
+
import { Add, DocumentAdd, ImageAdd } from "@coral-ai/bundleicons";
|
|
1678
|
+
import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1679
|
+
var ChatAttachButton = ({
|
|
1680
|
+
types = ["files", "photos"]
|
|
1681
|
+
}) => {
|
|
1682
|
+
const { addAttachments } = useChatContext();
|
|
1683
|
+
const fileRef = useRef5(null);
|
|
1684
|
+
const photoRef = useRef5(null);
|
|
1685
|
+
const showFiles = types.includes("files");
|
|
1686
|
+
const showPhotos = types.includes("photos");
|
|
1687
|
+
return /* @__PURE__ */ jsxs8(Fragment2, { children: [
|
|
1688
|
+
showFiles && /* @__PURE__ */ jsx8(
|
|
1689
|
+
"input",
|
|
1690
|
+
{
|
|
1691
|
+
ref: fileRef,
|
|
1692
|
+
type: "file",
|
|
1693
|
+
hidden: true,
|
|
1694
|
+
multiple: true,
|
|
1695
|
+
onChange: (e) => {
|
|
1696
|
+
if (e.target.files) addAttachments([...e.target.files]);
|
|
1697
|
+
e.target.value = "";
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
),
|
|
1701
|
+
showPhotos && /* @__PURE__ */ jsx8(
|
|
1702
|
+
"input",
|
|
1703
|
+
{
|
|
1704
|
+
ref: photoRef,
|
|
1705
|
+
type: "file",
|
|
1706
|
+
hidden: true,
|
|
1707
|
+
multiple: true,
|
|
1708
|
+
accept: "image/*",
|
|
1709
|
+
onChange: (e) => {
|
|
1710
|
+
if (e.target.files) addAttachments([...e.target.files]);
|
|
1711
|
+
e.target.value = "";
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
),
|
|
1715
|
+
/* @__PURE__ */ jsxs8(Menu, { children: [
|
|
1716
|
+
/* @__PURE__ */ jsx8(MenuTrigger, { children: /* @__PURE__ */ jsx8(Button5, { appearance: "subtle", icon: /* @__PURE__ */ jsx8(Add, {}) }) }),
|
|
1717
|
+
/* @__PURE__ */ jsx8(MenuPopover, { children: /* @__PURE__ */ jsx8(MenuList, { children: types.map(
|
|
1718
|
+
(t) => t === "files" ? /* @__PURE__ */ jsx8(
|
|
1719
|
+
MenuItem,
|
|
1720
|
+
{
|
|
1721
|
+
icon: /* @__PURE__ */ jsx8(DocumentAdd, {}),
|
|
1722
|
+
onClick: () => fileRef.current?.click(),
|
|
1723
|
+
children: "Add files"
|
|
1724
|
+
},
|
|
1725
|
+
"files"
|
|
1726
|
+
) : /* @__PURE__ */ jsx8(
|
|
1727
|
+
MenuItem,
|
|
1728
|
+
{
|
|
1729
|
+
icon: /* @__PURE__ */ jsx8(ImageAdd, {}),
|
|
1730
|
+
onClick: () => photoRef.current?.click(),
|
|
1731
|
+
children: "Add photos"
|
|
1732
|
+
},
|
|
1733
|
+
"photos"
|
|
1734
|
+
)
|
|
1735
|
+
) }) })
|
|
1736
|
+
] })
|
|
1737
|
+
] });
|
|
1738
|
+
};
|
|
1739
|
+
var Chat_AttachButton_default = ChatAttachButton;
|
|
1740
|
+
|
|
1741
|
+
// src/Chat.Input.tsx
|
|
1742
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1743
|
+
var useStyles8 = makeStyles8({
|
|
1003
1744
|
wrapper: {
|
|
1004
1745
|
width: "100%",
|
|
1005
1746
|
margin: "0 auto"
|
|
@@ -1019,7 +1760,7 @@ var useStyles7 = makeStyles7({
|
|
|
1019
1760
|
overflow: "hidden"
|
|
1020
1761
|
},
|
|
1021
1762
|
cardFocused: {
|
|
1022
|
-
boxShadow:
|
|
1763
|
+
boxShadow: tokens8.shadow28
|
|
1023
1764
|
},
|
|
1024
1765
|
cardDisabled: {
|
|
1025
1766
|
opacity: "0.3",
|
|
@@ -1038,7 +1779,7 @@ var useStyles7 = makeStyles7({
|
|
|
1038
1779
|
height: "128px",
|
|
1039
1780
|
borderRadius: "var(--borderRadiusMedium)",
|
|
1040
1781
|
overflow: "hidden",
|
|
1041
|
-
border: `1px solid ${
|
|
1782
|
+
border: `1px solid ${tokens8.colorNeutralStroke2}`,
|
|
1042
1783
|
backgroundColor: "var(--colorNeutralBackground3)",
|
|
1043
1784
|
display: "flex",
|
|
1044
1785
|
flexDirection: "column"
|
|
@@ -1124,18 +1865,20 @@ var ChatInput = forwardRef(
|
|
|
1124
1865
|
actionsStart,
|
|
1125
1866
|
actionsEnd,
|
|
1126
1867
|
attachments: attachmentsProp,
|
|
1127
|
-
removeAttachment: removeAttachmentProp
|
|
1868
|
+
removeAttachment: removeAttachmentProp,
|
|
1869
|
+
allowedAttachments: allowedAttachmentsProp
|
|
1128
1870
|
}, ref) => {
|
|
1129
|
-
const styles =
|
|
1130
|
-
const [isFocused, setIsFocused] =
|
|
1131
|
-
const textareaRef =
|
|
1132
|
-
const containerRef =
|
|
1871
|
+
const styles = useStyles8();
|
|
1872
|
+
const [isFocused, setIsFocused] = useState6(false);
|
|
1873
|
+
const textareaRef = useRef6(null);
|
|
1874
|
+
const containerRef = useRef6(null);
|
|
1133
1875
|
useImperativeHandle(ref, () => textareaRef.current);
|
|
1134
1876
|
const ctx = useChatContext();
|
|
1135
1877
|
const isInsideCanvas = ctx.setInput !== void 0;
|
|
1136
1878
|
const attachments = isInsideCanvas ? ctx.attachments : attachmentsProp ?? [];
|
|
1137
1879
|
const removeAttachment = isInsideCanvas ? ctx.removeAttachment : removeAttachmentProp ?? (() => {
|
|
1138
1880
|
});
|
|
1881
|
+
const allowedAttachments = allowedAttachmentsProp ?? (isInsideCanvas ? ctx.allowedAttachments : void 0);
|
|
1139
1882
|
const value = valueProp ?? (isInsideCanvas ? ctx.input : "");
|
|
1140
1883
|
const onChange = onChangeProp ?? (isInsideCanvas ? ctx.setInput : () => {
|
|
1141
1884
|
});
|
|
@@ -1170,53 +1913,53 @@ var ChatInput = forwardRef(
|
|
|
1170
1913
|
observer.observe(el);
|
|
1171
1914
|
return () => observer.disconnect();
|
|
1172
1915
|
}, []);
|
|
1173
|
-
const sendStopButton = isInsideCanvas ? ctx.isTyping ? /* @__PURE__ */
|
|
1174
|
-
|
|
1916
|
+
const sendStopButton = isInsideCanvas ? ctx.isTyping ? /* @__PURE__ */ jsx9(
|
|
1917
|
+
Button6,
|
|
1175
1918
|
{
|
|
1176
1919
|
appearance: "transparent",
|
|
1177
1920
|
onClick: () => {
|
|
1178
1921
|
ctx.shouldStopRef.current = true;
|
|
1179
1922
|
ctx.onStop?.();
|
|
1180
1923
|
},
|
|
1181
|
-
icon: /* @__PURE__ */
|
|
1924
|
+
icon: /* @__PURE__ */ jsx9(Stop, {}),
|
|
1182
1925
|
style: { cursor: "default" }
|
|
1183
1926
|
}
|
|
1184
|
-
) : /* @__PURE__ */
|
|
1185
|
-
|
|
1927
|
+
) : /* @__PURE__ */ jsx9(
|
|
1928
|
+
Button6,
|
|
1186
1929
|
{
|
|
1187
1930
|
appearance: "transparent",
|
|
1188
1931
|
onClick: () => ctx.sendMessage(),
|
|
1189
|
-
icon: /* @__PURE__ */
|
|
1932
|
+
icon: /* @__PURE__ */ jsx9(Send, {}),
|
|
1190
1933
|
disabled: !value.trim(),
|
|
1191
1934
|
style: { cursor: "default" }
|
|
1192
1935
|
}
|
|
1193
1936
|
) : null;
|
|
1194
|
-
return /* @__PURE__ */
|
|
1937
|
+
return /* @__PURE__ */ jsx9("div", { ref: containerRef, className: styles.wrapper, style, children: /* @__PURE__ */ jsxs9(
|
|
1195
1938
|
"div",
|
|
1196
1939
|
{
|
|
1197
1940
|
className: `${styles.card}${isFocused ? ` ${styles.cardFocused}` : ""}${disabled ? ` ${styles.cardDisabled}` : ""}`,
|
|
1198
1941
|
style: {
|
|
1199
|
-
border: isFocused ? `1px solid ${
|
|
1942
|
+
border: isFocused ? `1px solid ${tokens8.colorNeutralStroke1}` : `1px solid ${tokens8.colorNeutralStroke2}`
|
|
1200
1943
|
},
|
|
1201
1944
|
children: [
|
|
1202
|
-
attachments.length > 0 && /* @__PURE__ */
|
|
1945
|
+
attachments.length > 0 && /* @__PURE__ */ jsx9("div", { className: styles.attachmentsRow, children: attachments.map((a) => {
|
|
1203
1946
|
const ext = a.file.name.split(".").pop()?.toUpperCase() ?? "FILE";
|
|
1204
|
-
return /* @__PURE__ */
|
|
1205
|
-
a.preview ? /* @__PURE__ */
|
|
1947
|
+
return /* @__PURE__ */ jsxs9("div", { className: styles.attachmentItem, children: [
|
|
1948
|
+
a.preview ? /* @__PURE__ */ jsx9(
|
|
1206
1949
|
"img",
|
|
1207
1950
|
{
|
|
1208
1951
|
src: a.preview,
|
|
1209
1952
|
alt: a.file.name,
|
|
1210
1953
|
className: styles.attachmentImage
|
|
1211
1954
|
}
|
|
1212
|
-
) : /* @__PURE__ */
|
|
1213
|
-
/* @__PURE__ */
|
|
1214
|
-
/* @__PURE__ */
|
|
1955
|
+
) : /* @__PURE__ */ jsxs9("div", { className: styles.attachmentFile, children: [
|
|
1956
|
+
/* @__PURE__ */ jsx9(Body16, { className: styles.attachmentFileName, children: a.file.name }),
|
|
1957
|
+
/* @__PURE__ */ jsx9(Tag, { appearance: "outline", size: "small", children: ext })
|
|
1215
1958
|
] }),
|
|
1216
|
-
/* @__PURE__ */
|
|
1217
|
-
|
|
1959
|
+
/* @__PURE__ */ jsx9(
|
|
1960
|
+
Button6,
|
|
1218
1961
|
{
|
|
1219
|
-
icon: /* @__PURE__ */
|
|
1962
|
+
icon: /* @__PURE__ */ jsx9(Dismiss, {}),
|
|
1220
1963
|
size: "small",
|
|
1221
1964
|
onClick: () => removeAttachment(a.id),
|
|
1222
1965
|
className: styles.attachmentDismiss
|
|
@@ -1224,7 +1967,7 @@ var ChatInput = forwardRef(
|
|
|
1224
1967
|
)
|
|
1225
1968
|
] }, a.id);
|
|
1226
1969
|
}) }),
|
|
1227
|
-
/* @__PURE__ */
|
|
1970
|
+
/* @__PURE__ */ jsx9(
|
|
1228
1971
|
"textarea",
|
|
1229
1972
|
{
|
|
1230
1973
|
disabled,
|
|
@@ -1244,14 +1987,17 @@ var ChatInput = forwardRef(
|
|
|
1244
1987
|
className: styles.textarea
|
|
1245
1988
|
}
|
|
1246
1989
|
),
|
|
1247
|
-
/* @__PURE__ */
|
|
1248
|
-
/* @__PURE__ */
|
|
1249
|
-
|
|
1990
|
+
/* @__PURE__ */ jsxs9("div", { className: styles.toolbar, children: [
|
|
1991
|
+
/* @__PURE__ */ jsxs9("div", { className: styles.toolbarSide, children: [
|
|
1992
|
+
allowedAttachments && allowedAttachments.length > 0 && /* @__PURE__ */ jsx9(Chat_AttachButton_default, { types: allowedAttachments }),
|
|
1993
|
+
actionsStart
|
|
1994
|
+
] }),
|
|
1995
|
+
/* @__PURE__ */ jsxs9("div", { className: styles.toolbarSide, children: [
|
|
1250
1996
|
actionsEnd,
|
|
1251
1997
|
sendStopButton
|
|
1252
1998
|
] })
|
|
1253
1999
|
] }),
|
|
1254
|
-
/* @__PURE__ */
|
|
2000
|
+
/* @__PURE__ */ jsx9(
|
|
1255
2001
|
"span",
|
|
1256
2002
|
{
|
|
1257
2003
|
className: `${styles.focusBar} ${isFocused ? styles.focusBarVisible : styles.focusBarHidden}`
|
|
@@ -1264,9 +2010,112 @@ var ChatInput = forwardRef(
|
|
|
1264
2010
|
);
|
|
1265
2011
|
var Chat_Input_default = ChatInput;
|
|
1266
2012
|
|
|
2013
|
+
// src/Chat.Approval.tsx
|
|
2014
|
+
import { Body1 as Body17, Body1Strong, Button as Button7, Caption1 as Caption13, makeStyles as makeStyles9, tokens as tokens9 } from "@fluentui/react-components";
|
|
2015
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2016
|
+
var useStyles9 = makeStyles9({
|
|
2017
|
+
root: {
|
|
2018
|
+
width: "100%",
|
|
2019
|
+
boxSizing: "border-box",
|
|
2020
|
+
display: "flex",
|
|
2021
|
+
flexDirection: "column",
|
|
2022
|
+
gap: "24px",
|
|
2023
|
+
padding: "12px 14px",
|
|
2024
|
+
marginBottom: "8px",
|
|
2025
|
+
borderRadius: "var(--borderRadiusXLarge)",
|
|
2026
|
+
backgroundColor: "var(--colorNeutralBackgroundAlpha2)",
|
|
2027
|
+
border: `1px solid ${tokens9.colorNeutralStroke2}`
|
|
2028
|
+
},
|
|
2029
|
+
header: {
|
|
2030
|
+
display: "flex",
|
|
2031
|
+
flexDirection: "column",
|
|
2032
|
+
gap: "2px"
|
|
2033
|
+
},
|
|
2034
|
+
description: {
|
|
2035
|
+
color: tokens9.colorNeutralForeground3
|
|
2036
|
+
},
|
|
2037
|
+
optionsRow: {
|
|
2038
|
+
display: "flex",
|
|
2039
|
+
flexWrap: "wrap",
|
|
2040
|
+
gap: "6px"
|
|
2041
|
+
},
|
|
2042
|
+
optionsColumn: {
|
|
2043
|
+
display: "flex",
|
|
2044
|
+
flexDirection: "column",
|
|
2045
|
+
gap: "6px"
|
|
2046
|
+
},
|
|
2047
|
+
optionStacked: {
|
|
2048
|
+
justifyContent: "flex-start"
|
|
2049
|
+
},
|
|
2050
|
+
hint: {
|
|
2051
|
+
color: tokens9.colorNeutralForeground3
|
|
2052
|
+
// fontStyle: "italic",
|
|
2053
|
+
}
|
|
2054
|
+
});
|
|
2055
|
+
function shouldStackVertically(options) {
|
|
2056
|
+
if (options.length > 3) return true;
|
|
2057
|
+
const totalLength = options.reduce((sum, o) => sum + o.label.length, 0);
|
|
2058
|
+
if (totalLength > 40) return true;
|
|
2059
|
+
if (options.some((o) => !!o.description)) return true;
|
|
2060
|
+
return false;
|
|
2061
|
+
}
|
|
2062
|
+
var ChatApproval = ({
|
|
2063
|
+
approval: approvalProp,
|
|
2064
|
+
onSelect: onSelectProp,
|
|
2065
|
+
disabled = false,
|
|
2066
|
+
className,
|
|
2067
|
+
style
|
|
2068
|
+
}) => {
|
|
2069
|
+
const styles = useStyles9();
|
|
2070
|
+
const ctx = useChatContext();
|
|
2071
|
+
const approval = approvalProp ?? ctx.pendingApproval;
|
|
2072
|
+
if (!approval) return null;
|
|
2073
|
+
const onSelect = onSelectProp ?? ((optionId) => ctx.resolveApproval(approval.id, optionId));
|
|
2074
|
+
const answeredId = approval.answer?.optionId;
|
|
2075
|
+
const isAnswered = answeredId !== void 0;
|
|
2076
|
+
const awaitingFreeform = ctx.awaitingFreeformApproval?.approvalId === approval.id ? ctx.awaitingFreeformApproval : null;
|
|
2077
|
+
const stack = shouldStackVertically(approval.options);
|
|
2078
|
+
return /* @__PURE__ */ jsxs10(
|
|
2079
|
+
"div",
|
|
2080
|
+
{
|
|
2081
|
+
className: `${styles.root}${className ? ` ${className}` : ""}`,
|
|
2082
|
+
style,
|
|
2083
|
+
role: "group",
|
|
2084
|
+
"aria-label": approval.question,
|
|
2085
|
+
children: [
|
|
2086
|
+
/* @__PURE__ */ jsxs10("div", { className: styles.header, children: [
|
|
2087
|
+
/* @__PURE__ */ jsx10(Body1Strong, { children: approval.question }),
|
|
2088
|
+
approval.description && /* @__PURE__ */ jsx10(Body17, { className: styles.description, children: approval.description })
|
|
2089
|
+
] }),
|
|
2090
|
+
/* @__PURE__ */ jsx10("div", { className: stack ? styles.optionsColumn : styles.optionsRow, children: approval.options.map((opt) => {
|
|
2091
|
+
const selected = answeredId === opt.id || awaitingFreeform?.optionId === opt.id;
|
|
2092
|
+
const baseAppearance = opt.appearance ?? (opt.freeform ? "subtle" : "outline");
|
|
2093
|
+
const appearance = selected ? "primary" : baseAppearance;
|
|
2094
|
+
return /* @__PURE__ */ jsx10(
|
|
2095
|
+
Button7,
|
|
2096
|
+
{
|
|
2097
|
+
appearance,
|
|
2098
|
+
disabled: disabled || isAnswered,
|
|
2099
|
+
onClick: () => onSelect(opt.id),
|
|
2100
|
+
className: stack ? styles.optionStacked : void 0,
|
|
2101
|
+
style: {
|
|
2102
|
+
height: "40px"
|
|
2103
|
+
},
|
|
2104
|
+
children: opt.label
|
|
2105
|
+
},
|
|
2106
|
+
opt.id
|
|
2107
|
+
);
|
|
2108
|
+
}) }),
|
|
2109
|
+
awaitingFreeform && !isAnswered && /* @__PURE__ */ jsx10(Caption13, { className: styles.hint, children: "Type your reply in the message box below." })
|
|
2110
|
+
]
|
|
2111
|
+
}
|
|
2112
|
+
);
|
|
2113
|
+
};
|
|
2114
|
+
var Chat_Approval_default = ChatApproval;
|
|
2115
|
+
|
|
1267
2116
|
// src/Chat.tsx
|
|
1268
|
-
import { Fragment, jsx as
|
|
1269
|
-
var
|
|
2117
|
+
import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2118
|
+
var useStyles10 = makeStyles10({
|
|
1270
2119
|
emptySlot: {
|
|
1271
2120
|
width: "100%",
|
|
1272
2121
|
maxWidth: "768px",
|
|
@@ -1295,50 +2144,52 @@ var useStyles8 = makeStyles8({
|
|
|
1295
2144
|
maxWidth: "768px",
|
|
1296
2145
|
padding: "0 16px 16px 16px",
|
|
1297
2146
|
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
2147
|
}
|
|
1311
2148
|
});
|
|
2149
|
+
var remapFontSizeForSurface = (size, surface) => {
|
|
2150
|
+
if (surface === "panel-right" && (size === void 0 || size === "default")) {
|
|
2151
|
+
return "small";
|
|
2152
|
+
}
|
|
2153
|
+
return size;
|
|
2154
|
+
};
|
|
1312
2155
|
var ChatLayout = ({
|
|
1313
|
-
|
|
1314
|
-
promptStarters,
|
|
2156
|
+
welcomeMessage,
|
|
1315
2157
|
surfaceMotion,
|
|
1316
2158
|
placeholder,
|
|
1317
2159
|
disabled,
|
|
1318
2160
|
actionsStart,
|
|
1319
2161
|
actionsEnd,
|
|
1320
2162
|
components,
|
|
1321
|
-
onFeedback,
|
|
1322
2163
|
onCopy,
|
|
1323
2164
|
onNavigate,
|
|
1324
2165
|
fontSize,
|
|
1325
2166
|
fontFamily
|
|
1326
2167
|
}) => {
|
|
1327
|
-
const
|
|
1328
|
-
const
|
|
2168
|
+
const welcomeIsFill = welcomeMessage !== void 0 && typeof welcomeMessage !== "string";
|
|
2169
|
+
const welcomeNode = typeof welcomeMessage === "string" ? /* @__PURE__ */ jsx11(Title3, { children: welcomeMessage }) : welcomeMessage;
|
|
2170
|
+
const styles = useStyles10();
|
|
2171
|
+
const surfaceProbeRef = useRef7(null);
|
|
2172
|
+
const [surface, setSurface] = useState7(null);
|
|
2173
|
+
useLayoutEffect3(() => {
|
|
2174
|
+
const el = surfaceProbeRef.current?.closest("[data-coral-surface]");
|
|
2175
|
+
setSurface(el?.getAttribute("data-coral-surface") ?? null);
|
|
2176
|
+
}, []);
|
|
2177
|
+
const effectiveFontSize = remapFontSizeForSurface(fontSize, surface);
|
|
2178
|
+
const { messages, isTyping, pendingApproval, awaitingFreeformApproval } = useChatContext();
|
|
1329
2179
|
const empty = messages.every((m) => m.role === "system") && !isTyping;
|
|
2180
|
+
const approvalLocksInput = pendingApproval !== null && awaitingFreeformApproval === null;
|
|
2181
|
+
const inputDisabled = disabled || approvalLocksInput;
|
|
1330
2182
|
const motionEnabled = surfaceMotion !== null;
|
|
1331
2183
|
const curve = motionTokens.curveDecelerateMax;
|
|
1332
2184
|
const spacerTransition = motionEnabled ? `flex-grow 300ms ${curve}` : "none";
|
|
1333
2185
|
const gapTransition = motionEnabled ? `height 300ms ${curve}` : "none";
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
return /* @__PURE__ */ jsxs8(Fragment, { children: [
|
|
1337
|
-
/* @__PURE__ */ jsx8(
|
|
2186
|
+
return /* @__PURE__ */ jsxs11(Fragment3, { children: [
|
|
2187
|
+
/* @__PURE__ */ jsx11(
|
|
1338
2188
|
"div",
|
|
1339
2189
|
{
|
|
2190
|
+
ref: surfaceProbeRef,
|
|
1340
2191
|
style: {
|
|
1341
|
-
flexGrow: empty ? 1 : 0,
|
|
2192
|
+
flexGrow: empty && !welcomeIsFill ? 1 : 0,
|
|
1342
2193
|
flexShrink: 1,
|
|
1343
2194
|
flexBasis: 0,
|
|
1344
2195
|
minHeight: 0,
|
|
@@ -1346,79 +2197,80 @@ var ChatLayout = ({
|
|
|
1346
2197
|
}
|
|
1347
2198
|
}
|
|
1348
2199
|
),
|
|
1349
|
-
/* @__PURE__ */
|
|
2200
|
+
/* @__PURE__ */ jsx11(
|
|
1350
2201
|
"div",
|
|
1351
2202
|
{
|
|
1352
2203
|
className: styles.contentArea,
|
|
1353
2204
|
style: {
|
|
1354
|
-
flex: empty ? "none" : 1,
|
|
1355
|
-
overflow: empty ? "visible" : "hidden"
|
|
2205
|
+
flex: empty && !welcomeIsFill ? "none" : 1,
|
|
2206
|
+
overflow: empty && !welcomeIsFill ? "visible" : "hidden"
|
|
1356
2207
|
},
|
|
1357
|
-
children: empty ? /* @__PURE__ */
|
|
2208
|
+
children: empty ? /* @__PURE__ */ jsx11(
|
|
2209
|
+
"div",
|
|
2210
|
+
{
|
|
2211
|
+
className: styles.emptySlot,
|
|
2212
|
+
style: welcomeIsFill ? {
|
|
2213
|
+
flex: 1,
|
|
2214
|
+
justifyContent: "center",
|
|
2215
|
+
alignContent: "center"
|
|
2216
|
+
} : void 0,
|
|
2217
|
+
children: welcomeNode
|
|
2218
|
+
}
|
|
2219
|
+
) : /* @__PURE__ */ jsx11(
|
|
1358
2220
|
Chat_Messages_default,
|
|
1359
2221
|
{
|
|
1360
2222
|
components,
|
|
1361
|
-
onFeedback,
|
|
1362
2223
|
onCopy,
|
|
1363
2224
|
onNavigate,
|
|
1364
|
-
fontSize,
|
|
2225
|
+
fontSize: effectiveFontSize,
|
|
1365
2226
|
fontFamily
|
|
1366
2227
|
}
|
|
1367
2228
|
)
|
|
1368
2229
|
}
|
|
1369
2230
|
),
|
|
1370
|
-
/* @__PURE__ */
|
|
1371
|
-
/* @__PURE__ */
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
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(
|
|
2231
|
+
/* @__PURE__ */ jsx11("div", { style: { height: empty ? 24 : 0, transition: gapTransition } }),
|
|
2232
|
+
/* @__PURE__ */ jsx11("div", { className: styles.inputRow, children: /* @__PURE__ */ jsxs11("div", { className: styles.inputInner, style: { flexDirection: "column" }, children: [
|
|
2233
|
+
pendingApproval && /* @__PURE__ */ jsx11(Chat_Approval_default, {}),
|
|
2234
|
+
/* @__PURE__ */ jsx11(
|
|
2235
|
+
Chat_Input_default,
|
|
2236
|
+
{
|
|
2237
|
+
placeholder: approvalLocksInput ? "Choose an option above to continue..." : placeholder,
|
|
2238
|
+
disabled: inputDisabled,
|
|
2239
|
+
actionsStart,
|
|
2240
|
+
actionsEnd
|
|
2241
|
+
}
|
|
2242
|
+
)
|
|
2243
|
+
] }) }),
|
|
2244
|
+
/* @__PURE__ */ jsx11(
|
|
1406
2245
|
"div",
|
|
1407
2246
|
{
|
|
1408
2247
|
style: {
|
|
1409
|
-
flexGrow: empty ? 1 : 0,
|
|
2248
|
+
flexGrow: empty && !welcomeIsFill ? 1 : 0,
|
|
1410
2249
|
flexShrink: 1,
|
|
1411
2250
|
flexBasis: 0,
|
|
1412
2251
|
minHeight: 0,
|
|
1413
2252
|
transition: spacerTransition
|
|
1414
2253
|
}
|
|
1415
2254
|
}
|
|
2255
|
+
),
|
|
2256
|
+
/* @__PURE__ */ jsx11(
|
|
2257
|
+
Caption14,
|
|
2258
|
+
{
|
|
2259
|
+
style: {
|
|
2260
|
+
display: "flex",
|
|
2261
|
+
width: "100%",
|
|
2262
|
+
justifyContent: "center",
|
|
2263
|
+
height: "32px",
|
|
2264
|
+
opacity: 0.5
|
|
2265
|
+
},
|
|
2266
|
+
children: "AI makes mistakes. Check outputs."
|
|
2267
|
+
}
|
|
1416
2268
|
)
|
|
1417
2269
|
] });
|
|
1418
2270
|
};
|
|
1419
2271
|
var Chat = ({
|
|
1420
|
-
|
|
1421
|
-
|
|
2272
|
+
api,
|
|
2273
|
+
allowedAttachments,
|
|
1422
2274
|
conversationId,
|
|
1423
2275
|
initialMessages,
|
|
1424
2276
|
systemPrompt,
|
|
@@ -1429,11 +2281,9 @@ var Chat = ({
|
|
|
1429
2281
|
autoSendMessage,
|
|
1430
2282
|
thinkingStatus,
|
|
1431
2283
|
onStop,
|
|
1432
|
-
|
|
1433
|
-
promptStarters,
|
|
2284
|
+
welcomeMessage,
|
|
1434
2285
|
surfaceMotion,
|
|
1435
2286
|
components,
|
|
1436
|
-
onFeedback,
|
|
1437
2287
|
onCopy,
|
|
1438
2288
|
onNavigate,
|
|
1439
2289
|
placeholder,
|
|
@@ -1443,11 +2293,14 @@ var Chat = ({
|
|
|
1443
2293
|
fontSize,
|
|
1444
2294
|
fontFamily
|
|
1445
2295
|
}) => {
|
|
1446
|
-
|
|
2296
|
+
const chatConfig = useCoralConfig()?.chat;
|
|
2297
|
+
const resolvedApi = api ?? chatConfig?.api;
|
|
2298
|
+
const resolvedAllowedAttachments = allowedAttachments ?? chatConfig?.attachments;
|
|
2299
|
+
return /* @__PURE__ */ jsx11(
|
|
1447
2300
|
Chat_Canvas_default,
|
|
1448
2301
|
{
|
|
1449
|
-
|
|
1450
|
-
|
|
2302
|
+
api: resolvedApi,
|
|
2303
|
+
allowedAttachments: resolvedAllowedAttachments,
|
|
1451
2304
|
conversationId,
|
|
1452
2305
|
initialMessages,
|
|
1453
2306
|
systemPrompt,
|
|
@@ -1458,18 +2311,16 @@ var Chat = ({
|
|
|
1458
2311
|
autoSendMessage,
|
|
1459
2312
|
thinkingStatus,
|
|
1460
2313
|
onStop,
|
|
1461
|
-
children: /* @__PURE__ */
|
|
2314
|
+
children: /* @__PURE__ */ jsx11(
|
|
1462
2315
|
ChatLayout,
|
|
1463
2316
|
{
|
|
1464
|
-
|
|
1465
|
-
promptStarters,
|
|
2317
|
+
welcomeMessage,
|
|
1466
2318
|
surfaceMotion,
|
|
1467
2319
|
placeholder,
|
|
1468
2320
|
disabled,
|
|
1469
2321
|
actionsStart,
|
|
1470
2322
|
actionsEnd,
|
|
1471
2323
|
components,
|
|
1472
|
-
onFeedback,
|
|
1473
2324
|
onCopy,
|
|
1474
2325
|
onNavigate,
|
|
1475
2326
|
fontSize,
|
|
@@ -1483,11 +2334,18 @@ var Chat_default = Chat;
|
|
|
1483
2334
|
|
|
1484
2335
|
// src/createHandler.ts
|
|
1485
2336
|
function createHandler({ endpoint, headers }) {
|
|
1486
|
-
return async function* (input, history,
|
|
2337
|
+
return async function* (input, history, files) {
|
|
2338
|
+
const attachments = await Promise.all(
|
|
2339
|
+
files.map(async (file) => ({
|
|
2340
|
+
name: file.name,
|
|
2341
|
+
mediaType: file.type,
|
|
2342
|
+
data: await fileToBase64(file)
|
|
2343
|
+
}))
|
|
2344
|
+
);
|
|
1487
2345
|
const res = await fetch(endpoint, {
|
|
1488
2346
|
method: "POST",
|
|
1489
2347
|
headers: { "Content-Type": "application/json", ...headers },
|
|
1490
|
-
body: JSON.stringify({ input, history,
|
|
2348
|
+
body: JSON.stringify({ input, history, attachments })
|
|
1491
2349
|
});
|
|
1492
2350
|
if (!res.ok || !res.body) {
|
|
1493
2351
|
throw Object.assign(new Error(`${res.status} ${res.statusText}`), {
|
|
@@ -1518,20 +2376,407 @@ function createHandler({ endpoint, headers }) {
|
|
|
1518
2376
|
};
|
|
1519
2377
|
}
|
|
1520
2378
|
|
|
2379
|
+
// src/Chat.Agent.ts
|
|
2380
|
+
var UPDATE_TODOS_TOOL = {
|
|
2381
|
+
name: "update_todos",
|
|
2382
|
+
description: "Display or update the multi-step task plan shown to the user as an inline checklist. Call this when you begin a task that takes 3 or more distinct steps, and again every time a step changes status. Always send the COMPLETE list of todos \u2014 it replaces the previous one. Keep exactly one item 'in_progress' at a time.",
|
|
2383
|
+
parameters: {
|
|
2384
|
+
type: "object",
|
|
2385
|
+
properties: {
|
|
2386
|
+
todos: {
|
|
2387
|
+
type: "array",
|
|
2388
|
+
description: "The full task list, in order.",
|
|
2389
|
+
items: {
|
|
2390
|
+
type: "object",
|
|
2391
|
+
properties: {
|
|
2392
|
+
content: {
|
|
2393
|
+
type: "string",
|
|
2394
|
+
description: "Imperative title, e.g. 'Optimize images'."
|
|
2395
|
+
},
|
|
2396
|
+
activeForm: {
|
|
2397
|
+
type: "string",
|
|
2398
|
+
description: "Present-tense title shown while this step is in progress, e.g. 'Optimizing images'."
|
|
2399
|
+
},
|
|
2400
|
+
status: {
|
|
2401
|
+
type: "string",
|
|
2402
|
+
enum: ["pending", "in_progress", "completed"]
|
|
2403
|
+
}
|
|
2404
|
+
},
|
|
2405
|
+
required: ["content", "status"]
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
},
|
|
2409
|
+
required: ["todos"]
|
|
2410
|
+
}
|
|
2411
|
+
};
|
|
2412
|
+
var REQUEST_APPROVAL_TOOL = {
|
|
2413
|
+
name: "request_approval",
|
|
2414
|
+
description: "Pause and ask the user to approve an action or choose between options before continuing. The conversation halts until the user responds \u2014 their choice comes back as this tool's result. Use this before any consequential or irreversible action (deleting, sending, purchasing, etc).",
|
|
2415
|
+
parameters: {
|
|
2416
|
+
type: "object",
|
|
2417
|
+
properties: {
|
|
2418
|
+
question: {
|
|
2419
|
+
type: "string",
|
|
2420
|
+
description: "The question or action to confirm."
|
|
2421
|
+
},
|
|
2422
|
+
description: {
|
|
2423
|
+
type: "string",
|
|
2424
|
+
description: "Optional extra context shown below the question."
|
|
2425
|
+
},
|
|
2426
|
+
options: {
|
|
2427
|
+
type: "array",
|
|
2428
|
+
description: "The choices offered to the user.",
|
|
2429
|
+
items: {
|
|
2430
|
+
type: "object",
|
|
2431
|
+
properties: {
|
|
2432
|
+
id: { type: "string" },
|
|
2433
|
+
label: { type: "string" },
|
|
2434
|
+
description: { type: "string" },
|
|
2435
|
+
freeform: {
|
|
2436
|
+
type: "boolean",
|
|
2437
|
+
description: "If true, choosing this option lets the user type a free-text reply instead of a canned choice."
|
|
2438
|
+
}
|
|
2439
|
+
},
|
|
2440
|
+
required: ["id", "label"]
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
},
|
|
2444
|
+
required: ["question", "options"]
|
|
2445
|
+
}
|
|
2446
|
+
};
|
|
2447
|
+
function createMemoryStore() {
|
|
2448
|
+
const map = /* @__PURE__ */ new Map();
|
|
2449
|
+
return {
|
|
2450
|
+
get: (id) => map.get(id),
|
|
2451
|
+
set: (id, snap) => {
|
|
2452
|
+
map.set(id, snap);
|
|
2453
|
+
},
|
|
2454
|
+
delete: (id) => {
|
|
2455
|
+
map.delete(id);
|
|
2456
|
+
}
|
|
2457
|
+
};
|
|
2458
|
+
}
|
|
2459
|
+
function historyToAgentMessages(history) {
|
|
2460
|
+
const out = [];
|
|
2461
|
+
for (const m of history) {
|
|
2462
|
+
if (m.role === "user") out.push({ role: "user", content: m.content });
|
|
2463
|
+
else if (m.role === "assistant") out.push({ role: "assistant", content: m.content });
|
|
2464
|
+
}
|
|
2465
|
+
return out;
|
|
2466
|
+
}
|
|
2467
|
+
function extractTodos(input) {
|
|
2468
|
+
const todos = input?.todos;
|
|
2469
|
+
if (!Array.isArray(todos)) return [];
|
|
2470
|
+
return todos.map((t, i) => {
|
|
2471
|
+
const o = t ?? {};
|
|
2472
|
+
return {
|
|
2473
|
+
id: typeof o.id === "string" ? o.id : `todo-${i}`,
|
|
2474
|
+
content: String(o.content ?? ""),
|
|
2475
|
+
activeForm: typeof o.activeForm === "string" ? o.activeForm : void 0,
|
|
2476
|
+
status: o.status === "in_progress" || o.status === "completed" ? o.status : "pending"
|
|
2477
|
+
};
|
|
2478
|
+
});
|
|
2479
|
+
}
|
|
2480
|
+
function buildApproval(tc) {
|
|
2481
|
+
const input = tc.input ?? {};
|
|
2482
|
+
const rawOptions = Array.isArray(input.options) ? input.options : [];
|
|
2483
|
+
const options = rawOptions.map((o, i) => {
|
|
2484
|
+
const obj = o ?? {};
|
|
2485
|
+
return {
|
|
2486
|
+
id: typeof obj.id === "string" ? obj.id : `opt-${i}`,
|
|
2487
|
+
label: String(obj.label ?? `Option ${i + 1}`),
|
|
2488
|
+
description: typeof obj.description === "string" ? obj.description : void 0,
|
|
2489
|
+
freeform: obj.freeform === true ? true : void 0
|
|
2490
|
+
};
|
|
2491
|
+
});
|
|
2492
|
+
return {
|
|
2493
|
+
// The tool-call id is the approval id — Coral round-trips it as
|
|
2494
|
+
// metadata.approvalId, which is the session-store key on resume.
|
|
2495
|
+
id: tc.id,
|
|
2496
|
+
question: String(input.question ?? "Approve?"),
|
|
2497
|
+
description: typeof input.description === "string" ? input.description : void 0,
|
|
2498
|
+
options
|
|
2499
|
+
};
|
|
2500
|
+
}
|
|
2501
|
+
function createAgentHandler(options) {
|
|
2502
|
+
const { callModel, system, tools = [], maxSteps = 8 } = options;
|
|
2503
|
+
const store = options.sessionStore ?? createMemoryStore();
|
|
2504
|
+
const customByName = new Map(tools.map((t) => [t.spec.name, t]));
|
|
2505
|
+
const toolSpecs = [
|
|
2506
|
+
UPDATE_TODOS_TOOL,
|
|
2507
|
+
REQUEST_APPROVAL_TOOL,
|
|
2508
|
+
...tools.map((t) => t.spec)
|
|
2509
|
+
];
|
|
2510
|
+
return async function* (_input, history, attachments) {
|
|
2511
|
+
const sysFromHistory = history.filter((m) => m.role === "system").map((m) => m.content);
|
|
2512
|
+
const effectiveSystem = [system, ...sysFromHistory].filter(Boolean).join("\n\n") || void 0;
|
|
2513
|
+
const last = history[history.length - 1];
|
|
2514
|
+
let messages;
|
|
2515
|
+
let firstCallAttachments;
|
|
2516
|
+
const resumeId = last?.role === "user" && last.type === "approval_response" && typeof last.metadata?.approvalId === "string" ? last.metadata.approvalId : void 0;
|
|
2517
|
+
const snapshot = resumeId ? await store.get(resumeId) : void 0;
|
|
2518
|
+
if (resumeId && snapshot) {
|
|
2519
|
+
await store.delete(resumeId);
|
|
2520
|
+
const meta = last.metadata;
|
|
2521
|
+
const answer = typeof meta.text === "string" && meta.text || typeof meta.label === "string" && meta.label || last.content;
|
|
2522
|
+
messages = [
|
|
2523
|
+
...snapshot.messages,
|
|
2524
|
+
{
|
|
2525
|
+
role: "tool",
|
|
2526
|
+
toolCallId: snapshot.pendingToolCallId,
|
|
2527
|
+
content: `The user chose: ${answer}`
|
|
2528
|
+
}
|
|
2529
|
+
];
|
|
2530
|
+
} else {
|
|
2531
|
+
messages = historyToAgentMessages(history);
|
|
2532
|
+
firstCallAttachments = attachments.length > 0 ? attachments : void 0;
|
|
2533
|
+
}
|
|
2534
|
+
for (let step = 0; step < maxSteps; step++) {
|
|
2535
|
+
const events = callModel({
|
|
2536
|
+
system: effectiveSystem,
|
|
2537
|
+
messages,
|
|
2538
|
+
tools: toolSpecs,
|
|
2539
|
+
attachments: step === 0 ? firstCallAttachments : void 0
|
|
2540
|
+
});
|
|
2541
|
+
let assistantText = "";
|
|
2542
|
+
let assistantRaw;
|
|
2543
|
+
const toolCalls = [];
|
|
2544
|
+
for await (const ev of events) {
|
|
2545
|
+
if (ev.type === "text") {
|
|
2546
|
+
assistantText += ev.delta;
|
|
2547
|
+
yield ev.delta;
|
|
2548
|
+
} else if (ev.type === "reasoning") {
|
|
2549
|
+
yield { type: "reasoning", content: ev.delta };
|
|
2550
|
+
} else if (ev.type === "tool_call") {
|
|
2551
|
+
toolCalls.push(ev.toolCall);
|
|
2552
|
+
} else if (ev.type === "raw") {
|
|
2553
|
+
assistantRaw = ev.raw;
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
messages.push({
|
|
2557
|
+
role: "assistant",
|
|
2558
|
+
content: assistantText,
|
|
2559
|
+
...toolCalls.length > 0 ? { toolCalls } : {},
|
|
2560
|
+
...assistantRaw !== void 0 ? { raw: assistantRaw } : {}
|
|
2561
|
+
});
|
|
2562
|
+
if (toolCalls.length === 0) return;
|
|
2563
|
+
for (const tc of toolCalls) {
|
|
2564
|
+
if (tc.name === UPDATE_TODOS_TOOL.name) {
|
|
2565
|
+
yield { type: "todos", todos: extractTodos(tc.input) };
|
|
2566
|
+
messages.push({
|
|
2567
|
+
role: "tool",
|
|
2568
|
+
toolCallId: tc.id,
|
|
2569
|
+
content: "Task list updated and shown to the user."
|
|
2570
|
+
});
|
|
2571
|
+
} else if (tc.name === REQUEST_APPROVAL_TOOL.name) {
|
|
2572
|
+
yield { type: "approval", approval: buildApproval(tc) };
|
|
2573
|
+
await store.set(tc.id, { messages, pendingToolCallId: tc.id });
|
|
2574
|
+
return;
|
|
2575
|
+
} else {
|
|
2576
|
+
const tool = customByName.get(tc.name);
|
|
2577
|
+
let content;
|
|
2578
|
+
if (!tool) {
|
|
2579
|
+
content = `Error: unknown tool "${tc.name}".`;
|
|
2580
|
+
} else {
|
|
2581
|
+
try {
|
|
2582
|
+
content = (await tool.execute(tc.input)).content;
|
|
2583
|
+
} catch (err) {
|
|
2584
|
+
content = `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
messages.push({ role: "tool", toolCallId: tc.id, content });
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
yield "\n\n_(Reached the tool-step limit for this turn.)_";
|
|
2592
|
+
};
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
// src/Chat.Threads.tsx
|
|
2596
|
+
import * as React9 from "react";
|
|
2597
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
2598
|
+
var ChatThreadsContext = React9.createContext(
|
|
2599
|
+
null
|
|
2600
|
+
);
|
|
2601
|
+
function useChatThreads() {
|
|
2602
|
+
const value = React9.useContext(ChatThreadsContext);
|
|
2603
|
+
if (!value) {
|
|
2604
|
+
throw new Error(
|
|
2605
|
+
"useChatThreads must be used inside <ChatThreadsProvider>."
|
|
2606
|
+
);
|
|
2607
|
+
}
|
|
2608
|
+
return value;
|
|
2609
|
+
}
|
|
2610
|
+
function createMemoryThreadsAdapter(initialThreads = []) {
|
|
2611
|
+
let threads = [...initialThreads];
|
|
2612
|
+
return {
|
|
2613
|
+
list: () => threads,
|
|
2614
|
+
get: (id) => threads.find((t) => t.id === id),
|
|
2615
|
+
create: (initial) => {
|
|
2616
|
+
const thread = {
|
|
2617
|
+
id: initial?.id ?? crypto.randomUUID(),
|
|
2618
|
+
title: initial?.title ?? "New chat",
|
|
2619
|
+
messages: initial?.messages ?? [],
|
|
2620
|
+
createdAt: Date.now(),
|
|
2621
|
+
updatedAt: Date.now()
|
|
2622
|
+
};
|
|
2623
|
+
threads = [thread, ...threads];
|
|
2624
|
+
return thread;
|
|
2625
|
+
},
|
|
2626
|
+
save: (thread) => {
|
|
2627
|
+
const idx = threads.findIndex((t) => t.id === thread.id);
|
|
2628
|
+
const next = { ...thread, updatedAt: Date.now() };
|
|
2629
|
+
threads = idx >= 0 ? threads.map((t, i) => i === idx ? next : t) : [next, ...threads];
|
|
2630
|
+
},
|
|
2631
|
+
rename: (id, newTitle) => {
|
|
2632
|
+
threads = threads.map(
|
|
2633
|
+
(t) => t.id === id ? { ...t, title: newTitle, updatedAt: Date.now() } : t
|
|
2634
|
+
);
|
|
2635
|
+
},
|
|
2636
|
+
remove: (id) => {
|
|
2637
|
+
threads = threads.filter((t) => t.id !== id);
|
|
2638
|
+
}
|
|
2639
|
+
};
|
|
2640
|
+
}
|
|
2641
|
+
function ChatThreadsProvider({
|
|
2642
|
+
adapter: providedAdapter,
|
|
2643
|
+
activeThreadId: controlledId,
|
|
2644
|
+
defaultActiveThreadId,
|
|
2645
|
+
onActiveThreadChange,
|
|
2646
|
+
children
|
|
2647
|
+
}) {
|
|
2648
|
+
const adapter = React9.useMemo(
|
|
2649
|
+
() => providedAdapter ?? createMemoryThreadsAdapter(),
|
|
2650
|
+
[providedAdapter]
|
|
2651
|
+
);
|
|
2652
|
+
const [threads, setThreads] = React9.useState([]);
|
|
2653
|
+
const [uncontrolledId, setUncontrolledId] = React9.useState(
|
|
2654
|
+
defaultActiveThreadId
|
|
2655
|
+
);
|
|
2656
|
+
const [isLoading, setIsLoading] = React9.useState(true);
|
|
2657
|
+
const isControlled = controlledId !== void 0;
|
|
2658
|
+
const activeThreadId = isControlled ? controlledId : uncontrolledId;
|
|
2659
|
+
const setActiveThreadId = React9.useCallback(
|
|
2660
|
+
(id) => {
|
|
2661
|
+
if (!isControlled) setUncontrolledId(id);
|
|
2662
|
+
onActiveThreadChange?.(id);
|
|
2663
|
+
},
|
|
2664
|
+
[isControlled, onActiveThreadChange]
|
|
2665
|
+
);
|
|
2666
|
+
React9.useEffect(() => {
|
|
2667
|
+
let cancelled = false;
|
|
2668
|
+
(async () => {
|
|
2669
|
+
setIsLoading(true);
|
|
2670
|
+
const result = await adapter.list();
|
|
2671
|
+
if (cancelled) return;
|
|
2672
|
+
setThreads(result);
|
|
2673
|
+
setIsLoading(false);
|
|
2674
|
+
})();
|
|
2675
|
+
return () => {
|
|
2676
|
+
cancelled = true;
|
|
2677
|
+
};
|
|
2678
|
+
}, [adapter]);
|
|
2679
|
+
const createThread = React9.useCallback(
|
|
2680
|
+
async (initial) => {
|
|
2681
|
+
const thread = await adapter.create(initial);
|
|
2682
|
+
setThreads((prev) => [thread, ...prev.filter((t) => t.id !== thread.id)]);
|
|
2683
|
+
return thread;
|
|
2684
|
+
},
|
|
2685
|
+
[adapter]
|
|
2686
|
+
);
|
|
2687
|
+
const renameThread = React9.useCallback(
|
|
2688
|
+
async (id, newTitle) => {
|
|
2689
|
+
await adapter.rename(id, newTitle);
|
|
2690
|
+
setThreads(
|
|
2691
|
+
(prev) => prev.map(
|
|
2692
|
+
(t) => t.id === id ? { ...t, title: newTitle, updatedAt: Date.now() } : t
|
|
2693
|
+
)
|
|
2694
|
+
);
|
|
2695
|
+
},
|
|
2696
|
+
[adapter]
|
|
2697
|
+
);
|
|
2698
|
+
const removeThread = React9.useCallback(
|
|
2699
|
+
async (id) => {
|
|
2700
|
+
await adapter.remove(id);
|
|
2701
|
+
setThreads((prev) => prev.filter((t) => t.id !== id));
|
|
2702
|
+
if (activeThreadId === id) setActiveThreadId(void 0);
|
|
2703
|
+
},
|
|
2704
|
+
[adapter, activeThreadId, setActiveThreadId]
|
|
2705
|
+
);
|
|
2706
|
+
const threadsRef = React9.useRef(threads);
|
|
2707
|
+
threadsRef.current = threads;
|
|
2708
|
+
const updateThreadMessages = React9.useCallback(
|
|
2709
|
+
(id, messages) => {
|
|
2710
|
+
const existing = threadsRef.current.find((t) => t.id === id);
|
|
2711
|
+
if (!existing) return;
|
|
2712
|
+
const next = {
|
|
2713
|
+
...existing,
|
|
2714
|
+
messages,
|
|
2715
|
+
updatedAt: Date.now()
|
|
2716
|
+
};
|
|
2717
|
+
setThreads((prev) => prev.map((t) => t.id === id ? next : t));
|
|
2718
|
+
void adapter.save(next);
|
|
2719
|
+
},
|
|
2720
|
+
[adapter]
|
|
2721
|
+
);
|
|
2722
|
+
const activeThread = React9.useMemo(
|
|
2723
|
+
() => threads.find((t) => t.id === activeThreadId),
|
|
2724
|
+
[threads, activeThreadId]
|
|
2725
|
+
);
|
|
2726
|
+
const value = React9.useMemo(
|
|
2727
|
+
() => ({
|
|
2728
|
+
threads,
|
|
2729
|
+
activeThreadId,
|
|
2730
|
+
activeThread,
|
|
2731
|
+
setActiveThreadId,
|
|
2732
|
+
createThread,
|
|
2733
|
+
renameThread,
|
|
2734
|
+
removeThread,
|
|
2735
|
+
updateThreadMessages,
|
|
2736
|
+
isLoading
|
|
2737
|
+
}),
|
|
2738
|
+
[
|
|
2739
|
+
threads,
|
|
2740
|
+
activeThreadId,
|
|
2741
|
+
activeThread,
|
|
2742
|
+
setActiveThreadId,
|
|
2743
|
+
createThread,
|
|
2744
|
+
renameThread,
|
|
2745
|
+
removeThread,
|
|
2746
|
+
updateThreadMessages,
|
|
2747
|
+
isLoading
|
|
2748
|
+
]
|
|
2749
|
+
);
|
|
2750
|
+
return /* @__PURE__ */ jsx12(ChatThreadsContext.Provider, { value, children });
|
|
2751
|
+
}
|
|
2752
|
+
|
|
1521
2753
|
// src/index.ts
|
|
1522
2754
|
var Chat2 = Object.assign(Chat_default, {
|
|
1523
2755
|
Canvas: Chat_Canvas_default,
|
|
1524
2756
|
Messages: Chat_Messages_default,
|
|
1525
2757
|
Input: Chat_Input_default,
|
|
1526
2758
|
Error: Chat_Error_default,
|
|
1527
|
-
Callout: Chat_Callout_default
|
|
2759
|
+
Callout: Chat_Callout_default,
|
|
2760
|
+
Approval: Chat_Approval_default,
|
|
2761
|
+
TodoList: Chat_TodoList_default,
|
|
2762
|
+
AttachButton: Chat_AttachButton_default,
|
|
2763
|
+
ThinkingIndicator: Chat_ThinkingIndicator_default
|
|
1528
2764
|
});
|
|
1529
2765
|
var normalizeChatError2 = normalizeChatError;
|
|
1530
2766
|
var createHandler2 = createHandler;
|
|
1531
2767
|
export {
|
|
1532
2768
|
Chat2 as Chat,
|
|
2769
|
+
ChatThreadsProvider,
|
|
2770
|
+
DEFAULT_TASK_MESSAGES,
|
|
2771
|
+
REQUEST_APPROVAL_TOOL,
|
|
2772
|
+
UPDATE_TODOS_TOOL,
|
|
2773
|
+
createAgentHandler,
|
|
1533
2774
|
createHandler2 as createHandler,
|
|
2775
|
+
createMemoryThreadsAdapter,
|
|
2776
|
+
fileToBase64,
|
|
1534
2777
|
normalizeChatError2 as normalizeChatError,
|
|
1535
|
-
|
|
2778
|
+
resolveTaskMessages,
|
|
2779
|
+
useChatContext,
|
|
2780
|
+
useChatThreads
|
|
1536
2781
|
};
|
|
1537
2782
|
//# sourceMappingURL=index.js.map
|