@blocksdiy/react-common 1.28.0 → 1.28.1
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.
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { type Message, type UserMessage } from "@ag-ui/client";
|
|
1
|
+
import { type Message, ToolMessage, type UserMessage } from "@ag-ui/client";
|
|
2
|
+
import { useRenderToolCall } from "@copilotkit/react-core/v2";
|
|
2
3
|
import { ReactNode, SetStateAction } from "react";
|
|
3
|
-
export { type Message, type AssistantMessage } from "@ag-ui/client";
|
|
4
|
-
export { useRenderTool,
|
|
4
|
+
export { type Message, type AssistantMessage, type UserMessage } from "@ag-ui/client";
|
|
5
|
+
export { useRenderTool, useDefaultRenderTool, useHumanInTheLoop, useFrontendTool, ToolCallStatus, } from "@copilotkit/react-core/v2";
|
|
5
6
|
export interface Attachment {
|
|
6
7
|
url: string;
|
|
7
8
|
fileType: string;
|
|
@@ -10,8 +11,7 @@ export interface Attachment {
|
|
|
10
11
|
export interface AgentChatComponent {
|
|
11
12
|
id: string;
|
|
12
13
|
name: string;
|
|
13
|
-
|
|
14
|
-
code?: string;
|
|
14
|
+
code: string;
|
|
15
15
|
input?: Record<string, unknown>;
|
|
16
16
|
description?: string;
|
|
17
17
|
/**
|
|
@@ -51,6 +51,14 @@ export interface AgentChatContextValue {
|
|
|
51
51
|
agent: Agent | null;
|
|
52
52
|
agentChat: AgentChat | null;
|
|
53
53
|
components: AgentChatComponent[];
|
|
54
|
+
messageGroups: {
|
|
55
|
+
messageIds: string[];
|
|
56
|
+
role: "assistant" | "user";
|
|
57
|
+
}[];
|
|
58
|
+
messageById: Map<string, Message>;
|
|
59
|
+
toolMessages: ToolMessage[];
|
|
60
|
+
renderToolCall: ReturnType<typeof useRenderToolCall>;
|
|
61
|
+
hasSentMessageInSession: boolean;
|
|
54
62
|
sendMessage: (message: Omit<UserMessage, "id" | "role">) => Promise<void>;
|
|
55
63
|
sendFromInputs: () => Promise<void>;
|
|
56
64
|
stopGeneration: () => void;
|
|
@@ -64,6 +72,11 @@ export interface AgentChatContextValue {
|
|
|
64
72
|
}
|
|
65
73
|
export declare const AgentChatContext: import("react").Context<AgentChatContextValue | null>;
|
|
66
74
|
export declare const useAgentChat: () => AgentChatContextValue;
|
|
75
|
+
interface AgentChatScrollContextValue {
|
|
76
|
+
isAtBottom: boolean;
|
|
77
|
+
scrollToBottom: () => void;
|
|
78
|
+
}
|
|
79
|
+
export declare const useAgentChatScroll: () => AgentChatScrollContextValue;
|
|
67
80
|
export interface AgentChatRootProps {
|
|
68
81
|
appId: string;
|
|
69
82
|
token?: string;
|
|
@@ -88,10 +101,22 @@ export interface AgentChatMessageProps {
|
|
|
88
101
|
index: number;
|
|
89
102
|
}
|
|
90
103
|
export declare function AgentChatMessage({ message, index, ...props }: React.ComponentProps<"div"> & AgentChatMessageProps): import("react/jsx-runtime").JSX.Element | null;
|
|
104
|
+
export interface AgentChatMessageGroupProps {
|
|
105
|
+
messageGroup: {
|
|
106
|
+
messageIds: string[];
|
|
107
|
+
role: "assistant" | "user";
|
|
108
|
+
};
|
|
109
|
+
isLast?: boolean;
|
|
110
|
+
}
|
|
111
|
+
export declare function AgentChatMessageGroup({ messageGroup, isLast, style, ...props }: React.ComponentProps<"div"> & AgentChatMessageGroupProps): import("react/jsx-runtime").JSX.Element;
|
|
91
112
|
export interface AgentChatMessagesProps {
|
|
92
113
|
scrollAreaClassName?: string;
|
|
93
114
|
}
|
|
94
|
-
export declare function AgentChatMessages({ scrollAreaClassName, ...props }: React.ComponentProps<"div"> & AgentChatMessagesProps): import("react/jsx-runtime").JSX.Element;
|
|
115
|
+
export declare function AgentChatMessages({ scrollAreaClassName, children, ...props }: React.ComponentProps<"div"> & AgentChatMessagesProps): import("react/jsx-runtime").JSX.Element;
|
|
116
|
+
export interface AgentChatScrollToBottomProps {
|
|
117
|
+
asChild?: boolean;
|
|
118
|
+
}
|
|
119
|
+
export declare function AgentChatScrollToBottom({ asChild, onClick, ...props }: React.ComponentProps<"button"> & AgentChatScrollToBottomProps): import("react/jsx-runtime").JSX.Element | null;
|
|
95
120
|
export interface AgentChatThinkingProps {
|
|
96
121
|
asChild?: boolean;
|
|
97
122
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"new-agent-chat.d.ts","sourceRoot":"","sources":["../../src/components/new-agent-chat.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"new-agent-chat.d.ts","sourceRoot":"","sources":["../../src/components/new-agent-chat.tsx"],"names":[],"mappings":"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"}
|
|
@@ -2,11 +2,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { AppVersionService, BlocksApiService, websocketsService } from "@blocksdiy/blocks-client-api";
|
|
3
3
|
import { getApiHost } from "@blocksdiy/blocks-client-api/envService";
|
|
4
4
|
import { CopilotKit, useCopilotChatInternal } from "@copilotkit/react-core";
|
|
5
|
-
import { useCopilotKit } from "@copilotkit/react-core/v2";
|
|
5
|
+
import { useCopilotKit, useRenderToolCall } from "@copilotkit/react-core/v2";
|
|
6
6
|
import { Slot } from "@radix-ui/react-slot";
|
|
7
7
|
import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
|
|
8
|
-
export { useRenderTool,
|
|
9
|
-
import { useStickToBottom } from "use-stick-to-bottom";
|
|
8
|
+
export { useRenderTool, useDefaultRenderTool, useHumanInTheLoop, useFrontendTool, ToolCallStatus, } from "@copilotkit/react-core/v2";
|
|
10
9
|
// Client-only `useLayoutEffect`, falls back to `useEffect` during SSR.
|
|
11
10
|
// Needed so the `agent.threadId` mirror commits before CopilotKit's
|
|
12
11
|
// connect-on-mount effect reads it.
|
|
@@ -61,15 +60,82 @@ const validateAndConvertFiles = (files) => {
|
|
|
61
60
|
}
|
|
62
61
|
const hasOversizedFiles = files.some((file) => file.size > MAX_FILE_SIZE_BYTES);
|
|
63
62
|
if (hasOversizedFiles) {
|
|
64
|
-
console.error(`Upload failed because some files are bigger than ${MAX_FILE_SIZE_MB} MB.`);
|
|
65
63
|
return null;
|
|
66
64
|
}
|
|
67
65
|
return files.map((file) => ({
|
|
68
|
-
fileType: file.type,
|
|
66
|
+
fileType: file.type || "application/octet-stream",
|
|
69
67
|
fileName: file.name,
|
|
70
68
|
url: URL.createObjectURL(file),
|
|
71
69
|
}));
|
|
72
70
|
};
|
|
71
|
+
const fileFromAttachment = async (attachment) => {
|
|
72
|
+
const response = await fetch(attachment.url);
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw new Error("The blob URL is no longer valid");
|
|
75
|
+
}
|
|
76
|
+
const blob = await response.blob();
|
|
77
|
+
return new File([blob], attachment.fileName, {
|
|
78
|
+
type: attachment.fileType,
|
|
79
|
+
lastModified: Date.now(),
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
const getBrowserUploadUrl = (uploadUrl) => {
|
|
83
|
+
try {
|
|
84
|
+
const url = new URL(uploadUrl);
|
|
85
|
+
if (url.hostname === "localhost" && url.port === "4566") {
|
|
86
|
+
url.hostname = "blocks.localhost";
|
|
87
|
+
return url.toString();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return uploadUrl;
|
|
92
|
+
}
|
|
93
|
+
return uploadUrl;
|
|
94
|
+
};
|
|
95
|
+
const uploadFile = async ({ appId, blocksApiService, file, }) => {
|
|
96
|
+
const formData = new FormData();
|
|
97
|
+
const { uploadUrl, fields, protectedUrl } = await blocksApiService.getDataAssetUploadUrl({
|
|
98
|
+
productId: appId,
|
|
99
|
+
fileName: file.name,
|
|
100
|
+
fileType: file.type,
|
|
101
|
+
});
|
|
102
|
+
const request = new XMLHttpRequest();
|
|
103
|
+
if (uploadUrl && fields) {
|
|
104
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
105
|
+
formData.append(key, value);
|
|
106
|
+
}
|
|
107
|
+
formData.append("file", file);
|
|
108
|
+
request.open("POST", getBrowserUploadUrl(uploadUrl));
|
|
109
|
+
}
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
request.onload = () => {
|
|
112
|
+
if (request.status >= 200 && request.status < 300) {
|
|
113
|
+
resolve(protectedUrl);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
reject(new Error("Error uploading file"));
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
request.onerror = () => {
|
|
120
|
+
reject(new Error("Error uploading file"));
|
|
121
|
+
};
|
|
122
|
+
request.send(formData);
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
const uploadAttachments = async ({ attachments, blocksApiService, appId, }) => {
|
|
126
|
+
if (!attachments?.length) {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
return Promise.all(attachments.map(async (attachment) => {
|
|
130
|
+
const file = await fileFromAttachment(attachment);
|
|
131
|
+
const protectedUrl = await uploadFile({ appId, blocksApiService, file });
|
|
132
|
+
return {
|
|
133
|
+
url: protectedUrl,
|
|
134
|
+
fileName: attachment.fileName,
|
|
135
|
+
fileType: attachment.fileType,
|
|
136
|
+
};
|
|
137
|
+
}));
|
|
138
|
+
};
|
|
73
139
|
const hasSendableInput = (prompt, attachments) => {
|
|
74
140
|
return prompt.trim().length > 0 || attachments.length > 0;
|
|
75
141
|
};
|
|
@@ -81,6 +147,55 @@ export const useAgentChat = () => {
|
|
|
81
147
|
}
|
|
82
148
|
return context;
|
|
83
149
|
};
|
|
150
|
+
const NEW_MESSAGE_SCROLL_HIDE_MS = 500;
|
|
151
|
+
const AgentChatScrollContext = createContext(null);
|
|
152
|
+
export const useAgentChatScroll = () => {
|
|
153
|
+
const context = useContext(AgentChatScrollContext);
|
|
154
|
+
if (!context) {
|
|
155
|
+
throw new Error("useAgentChatScroll must be used within an AgentChatMessages provider");
|
|
156
|
+
}
|
|
157
|
+
return context;
|
|
158
|
+
};
|
|
159
|
+
const getMessageGroups = (scrollElement) => {
|
|
160
|
+
return Array.from(scrollElement.querySelectorAll('[data-slot="agent-chat-message-group"]'));
|
|
161
|
+
};
|
|
162
|
+
const SCROLL_BOTTOM_OFFSET_PX = 70;
|
|
163
|
+
const getTargetScrollTop = (scrollElement) => {
|
|
164
|
+
return Math.max(scrollElement.scrollHeight - 1 - scrollElement.clientHeight, 0);
|
|
165
|
+
};
|
|
166
|
+
const isScrollAtBottom = (scrollElement) => {
|
|
167
|
+
return getTargetScrollTop(scrollElement) - scrollElement.scrollTop <= SCROLL_BOTTOM_OFFSET_PX;
|
|
168
|
+
};
|
|
169
|
+
const setScrollTopInstantly = (scrollElement, scrollTop) => {
|
|
170
|
+
const { scrollBehavior } = getComputedStyle(scrollElement);
|
|
171
|
+
if (scrollBehavior !== "auto") {
|
|
172
|
+
scrollElement.style.scrollBehavior = "auto";
|
|
173
|
+
}
|
|
174
|
+
scrollElement.scrollTop = scrollTop;
|
|
175
|
+
if (scrollBehavior !== "auto") {
|
|
176
|
+
scrollElement.style.scrollBehavior = scrollBehavior;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
const AGENT_CHAT_CONTAINER_HEIGHT_VAR = "--agent-chat-container-height";
|
|
180
|
+
const AGENT_CHAT_LATEST_USER_HEIGHT_VAR = "--agent-chat-latest-user-message-height";
|
|
181
|
+
const LAST_ASSISTANT_MESSAGE_GROUP_STYLE = {
|
|
182
|
+
minHeight: `max(calc(var(${AGENT_CHAT_CONTAINER_HEIGHT_VAR}, 0px) - var(${AGENT_CHAT_LATEST_USER_HEIGHT_VAR}, var(${AGENT_CHAT_CONTAINER_HEIGHT_VAR}, 0px))), 0px)`,
|
|
183
|
+
};
|
|
184
|
+
const updateScrollHeightVariables = (scrollElement) => {
|
|
185
|
+
const messageGroups = getMessageGroups(scrollElement);
|
|
186
|
+
const latestUserGroup = messageGroups.reverse().find((messageGroup) => messageGroup.dataset.messageGroup === "user");
|
|
187
|
+
scrollElement.style.setProperty(AGENT_CHAT_CONTAINER_HEIGHT_VAR, `${scrollElement.clientHeight}px`);
|
|
188
|
+
if (latestUserGroup) {
|
|
189
|
+
scrollElement.style.setProperty(AGENT_CHAT_LATEST_USER_HEIGHT_VAR, `${latestUserGroup.offsetHeight}px`);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
scrollElement.style.removeProperty(AGENT_CHAT_LATEST_USER_HEIGHT_VAR);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
const clearScrollHeightVariables = (scrollElement) => {
|
|
196
|
+
scrollElement.style.removeProperty(AGENT_CHAT_CONTAINER_HEIGHT_VAR);
|
|
197
|
+
scrollElement.style.removeProperty(AGENT_CHAT_LATEST_USER_HEIGHT_VAR);
|
|
198
|
+
};
|
|
84
199
|
const SessionReadables = () => {
|
|
85
200
|
// const now = new Date();
|
|
86
201
|
// useCopilotReadable({
|
|
@@ -105,6 +220,33 @@ const ChatInitializer = ({ agentId, agentChatId, chatId, token, appId, component
|
|
|
105
220
|
const [isConfigLoaded, setIsConfigLoaded] = useState(false);
|
|
106
221
|
const { messages: copilotMessages, sendMessage: copilotSendMessage, stopGeneration: copilotStopGeneration, isAvailable: isAgentReady, isLoading: isAgentRunning, agent: copilotAgent, } = useCopilotChatInternal();
|
|
107
222
|
const { copilotkit } = useCopilotKit();
|
|
223
|
+
const renderToolCall = useRenderToolCall();
|
|
224
|
+
const messageById = useMemo(() => {
|
|
225
|
+
const messagesSet = new Map();
|
|
226
|
+
copilotMessages.forEach((message) => {
|
|
227
|
+
messagesSet.set(message.id, message);
|
|
228
|
+
});
|
|
229
|
+
return messagesSet;
|
|
230
|
+
}, [copilotMessages]);
|
|
231
|
+
const toolMessages = useMemo(() => {
|
|
232
|
+
return copilotMessages.filter((message) => message.role === "tool");
|
|
233
|
+
}, [copilotMessages]);
|
|
234
|
+
const messageGroups = useMemo(() => {
|
|
235
|
+
const groups = [];
|
|
236
|
+
copilotMessages.forEach((message) => {
|
|
237
|
+
if (message.role !== "assistant" && message.role !== "user") {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const lastGroup = groups[groups.length - 1];
|
|
241
|
+
if (!lastGroup || lastGroup.role !== message.role) {
|
|
242
|
+
groups.push({ role: message.role, messageIds: [message.id] });
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
lastGroup.messageIds.push(message.id);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
return groups;
|
|
249
|
+
}, [copilotMessages]);
|
|
108
250
|
// Mirror `currentThreadId` onto the agent instance — the `<CopilotKit>`
|
|
109
251
|
// prop only feeds React context, but `agent.threadId` is what hits the
|
|
110
252
|
// wire and the LangGraph checkpoint. `<CopilotChat>` does this for you;
|
|
@@ -120,6 +262,7 @@ const ChatInitializer = ({ agentId, agentChatId, chatId, token, appId, component
|
|
|
120
262
|
const [prompt, setPrompt] = useState("");
|
|
121
263
|
const [attachments, setAttachments] = useState([]);
|
|
122
264
|
const [isDraggingFiles, setIsDraggingFiles] = useState(false);
|
|
265
|
+
const [hasSentMessageInSession, setHasSentMessageInSession] = useState(false);
|
|
123
266
|
const blocksApiService = useMemo(() => new BlocksApiService({ token }), [token]);
|
|
124
267
|
const attachmentsEnabled = agentChat ? agentChat.disableAttachments !== true : false;
|
|
125
268
|
const canSendMessage = Boolean(agentChatId && agentChat && agent && isConfigLoaded && isAgentReady && !isAgentRunning);
|
|
@@ -131,6 +274,7 @@ const ChatInitializer = ({ agentId, agentChatId, chatId, token, appId, component
|
|
|
131
274
|
if (!canSendMessage) {
|
|
132
275
|
return Promise.resolve();
|
|
133
276
|
}
|
|
277
|
+
setHasSentMessageInSession(true);
|
|
134
278
|
return copilotSendMessage({ id: generateId(), role: "user", ...message });
|
|
135
279
|
}, [canSendMessage, copilotSendMessage]);
|
|
136
280
|
const stopGeneration = useCallback(() => {
|
|
@@ -151,7 +295,7 @@ const ChatInitializer = ({ agentId, agentChatId, chatId, token, appId, component
|
|
|
151
295
|
if (!componentIds?.length) {
|
|
152
296
|
return [];
|
|
153
297
|
}
|
|
154
|
-
const
|
|
298
|
+
const chatComponentBlocks = await Promise.all(componentIds.map(async (componentId) => {
|
|
155
299
|
try {
|
|
156
300
|
const block = await blocksApiService.getBlock(componentId);
|
|
157
301
|
const id = block?.id ?? componentId;
|
|
@@ -167,7 +311,6 @@ const ChatInitializer = ({ agentId, agentChatId, chatId, token, appId, component
|
|
|
167
311
|
code,
|
|
168
312
|
input: block?.data?.input,
|
|
169
313
|
description,
|
|
170
|
-
toolName: getAgentChatComponentToolName(name),
|
|
171
314
|
userInterrupt: Boolean(block?.data?.userInterrupt),
|
|
172
315
|
};
|
|
173
316
|
}
|
|
@@ -175,7 +318,7 @@ const ChatInitializer = ({ agentId, agentChatId, chatId, token, appId, component
|
|
|
175
318
|
return null;
|
|
176
319
|
}
|
|
177
320
|
}));
|
|
178
|
-
return
|
|
321
|
+
return chatComponentBlocks.filter((chatComponentBlock) => Boolean(chatComponentBlock));
|
|
179
322
|
}, [blocksApiService, componentIds]);
|
|
180
323
|
const componentIdsKey = componentIds?.join(",") ?? "";
|
|
181
324
|
const initKeyRef = useRef(null);
|
|
@@ -198,6 +341,7 @@ const ChatInitializer = ({ agentId, agentChatId, chatId, token, appId, component
|
|
|
198
341
|
}
|
|
199
342
|
initKeyRef.current = initKey;
|
|
200
343
|
initialPromptSentRef.current = false;
|
|
344
|
+
setHasSentMessageInSession(false);
|
|
201
345
|
const ac = new AbortController();
|
|
202
346
|
const initialize = async () => {
|
|
203
347
|
setIsConfigLoaded(false);
|
|
@@ -302,12 +446,45 @@ const ChatInitializer = ({ agentId, agentChatId, chatId, token, appId, component
|
|
|
302
446
|
});
|
|
303
447
|
void copilotkit.runAgent({ agent: copilotAgent });
|
|
304
448
|
}, [isAgentReady, isConfigLoaded, agentChat, copilotAgent, canSendMessage, copilotkit]);
|
|
449
|
+
const buildMessageContentFromInput = (currentPrompt = prompt, currentAttachments = attachments) => {
|
|
450
|
+
if (currentAttachments.length === 0) {
|
|
451
|
+
return currentPrompt;
|
|
452
|
+
}
|
|
453
|
+
const contentArray = [];
|
|
454
|
+
currentAttachments.forEach((attachment) => {
|
|
455
|
+
const source = { type: "url", value: attachment.url, mimeType: attachment.fileType };
|
|
456
|
+
const metadata = { filename: attachment.fileName };
|
|
457
|
+
switch (true) {
|
|
458
|
+
case attachment.fileType.startsWith("image/"):
|
|
459
|
+
contentArray.push({ type: "image", source, metadata });
|
|
460
|
+
break;
|
|
461
|
+
case attachment.fileType.startsWith("audio/"):
|
|
462
|
+
contentArray.push({ type: "audio", source, metadata });
|
|
463
|
+
break;
|
|
464
|
+
case attachment.fileType.startsWith("video/"):
|
|
465
|
+
contentArray.push({ type: "video", source, metadata });
|
|
466
|
+
break;
|
|
467
|
+
default:
|
|
468
|
+
contentArray.push({ type: "document", source, metadata });
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
if (currentPrompt) {
|
|
472
|
+
contentArray.push({ type: "text", text: currentPrompt });
|
|
473
|
+
}
|
|
474
|
+
return contentArray;
|
|
475
|
+
};
|
|
305
476
|
const sendFromInputs = async () => {
|
|
306
477
|
if (!canSendMessage || !hasSendableInput(prompt, attachments)) {
|
|
307
478
|
return;
|
|
308
479
|
}
|
|
309
480
|
const currentPrompt = prompt;
|
|
310
|
-
|
|
481
|
+
const currentAttachments = attachments;
|
|
482
|
+
const uploadedAttachments = await uploadAttachments({
|
|
483
|
+
attachments: currentAttachments,
|
|
484
|
+
appId,
|
|
485
|
+
blocksApiService,
|
|
486
|
+
});
|
|
487
|
+
const messageContent = buildMessageContentFromInput(currentPrompt, uploadedAttachments ?? []);
|
|
311
488
|
setPrompt("");
|
|
312
489
|
setAttachments([]);
|
|
313
490
|
// Auto-skip any frontend user-choice tool call that's still waiting.
|
|
@@ -348,7 +525,9 @@ const ChatInitializer = ({ agentId, agentChatId, chatId, token, appId, component
|
|
|
348
525
|
});
|
|
349
526
|
}
|
|
350
527
|
}
|
|
351
|
-
await sendMessage({
|
|
528
|
+
await sendMessage({
|
|
529
|
+
content: messageContent,
|
|
530
|
+
});
|
|
352
531
|
};
|
|
353
532
|
const addAttachments = (attachments) => {
|
|
354
533
|
if (!attachmentsEnabled) {
|
|
@@ -377,6 +556,11 @@ const ChatInitializer = ({ agentId, agentChatId, chatId, token, appId, component
|
|
|
377
556
|
};
|
|
378
557
|
return (_jsx(AgentChatContext.Provider, { value: {
|
|
379
558
|
messages: copilotMessages,
|
|
559
|
+
messageGroups,
|
|
560
|
+
renderToolCall,
|
|
561
|
+
messageById,
|
|
562
|
+
toolMessages,
|
|
563
|
+
hasSentMessageInSession,
|
|
380
564
|
prompt,
|
|
381
565
|
attachments,
|
|
382
566
|
sendMessage,
|
|
@@ -438,24 +622,139 @@ export function AgentChatMessage({ message, index, ...props }) {
|
|
|
438
622
|
}
|
|
439
623
|
return _jsx("div", { "data-message-index": index, "data-message-role": message.role, ...props });
|
|
440
624
|
}
|
|
441
|
-
export function
|
|
442
|
-
const {
|
|
443
|
-
const
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
625
|
+
export function AgentChatMessageGroup({ messageGroup, isLast, style, ...props }) {
|
|
626
|
+
const { hasSentMessageInSession, messageGroups } = useAgentChat();
|
|
627
|
+
const lastMessageGroup = messageGroups[messageGroups.length - 1];
|
|
628
|
+
const isLastMessageGroup = isLast ??
|
|
629
|
+
(lastMessageGroup?.role === messageGroup.role &&
|
|
630
|
+
lastMessageGroup.messageIds.length === messageGroup.messageIds.length &&
|
|
631
|
+
lastMessageGroup.messageIds.every((messageId, index) => messageId === messageGroup.messageIds[index]));
|
|
632
|
+
const mergedStyle = hasSentMessageInSession && isLastMessageGroup && messageGroup.role === "assistant"
|
|
633
|
+
? {
|
|
634
|
+
...style,
|
|
635
|
+
...LAST_ASSISTANT_MESSAGE_GROUP_STYLE,
|
|
636
|
+
}
|
|
637
|
+
: style;
|
|
638
|
+
return (_jsx("div", { "data-slot": "agent-chat-message-group", "data-message-group": messageGroup.role, "data-message-group-role": messageGroup.role, style: mergedStyle, ...props }));
|
|
639
|
+
}
|
|
640
|
+
export function AgentChatMessages({ scrollAreaClassName, children, ...props }) {
|
|
641
|
+
const { isFetchingMessages, isThinking, messageGroups } = useAgentChat();
|
|
642
|
+
const messageGroupCount = messageGroups.length;
|
|
643
|
+
const scrollRef = useRef(null);
|
|
644
|
+
const previousMessageGroupCountRef = useRef(messageGroupCount);
|
|
645
|
+
const hasCompletedInitialScrollRef = useRef(false);
|
|
646
|
+
const previousIsThinkingRef = useRef(isThinking);
|
|
647
|
+
const newMessageScrollTimeoutRef = useRef(null);
|
|
648
|
+
const [isNewMessageScrollInProgress, setIsNewMessageScrollInProgress] = useState(false);
|
|
649
|
+
const [isAtBottom, setIsAtBottom] = useState(true);
|
|
650
|
+
const scrollToBottom = useCallback((behavior = "smooth") => {
|
|
651
|
+
const scrollElement = scrollRef.current;
|
|
652
|
+
if (!scrollElement) {
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
const targetScrollTop = getTargetScrollTop(scrollElement);
|
|
656
|
+
if (behavior === "instant") {
|
|
657
|
+
setScrollTopInstantly(scrollElement, targetScrollTop);
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
scrollElement.scrollTo({ top: targetScrollTop, behavior });
|
|
661
|
+
}
|
|
662
|
+
setIsAtBottom(true);
|
|
663
|
+
}, []);
|
|
664
|
+
const scrollContextValue = useMemo(() => ({
|
|
665
|
+
isAtBottom: isAtBottom || isNewMessageScrollInProgress,
|
|
666
|
+
scrollToBottom: () => {
|
|
667
|
+
void scrollToBottom();
|
|
668
|
+
},
|
|
669
|
+
}), [isAtBottom, isNewMessageScrollInProgress, scrollToBottom]);
|
|
670
|
+
useIsomorphicLayoutEffect(() => {
|
|
671
|
+
const scrollElement = scrollRef.current;
|
|
672
|
+
if (!scrollElement) {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
if (getComputedStyle(scrollElement).overflow === "visible") {
|
|
676
|
+
scrollElement.style.overflow = "auto";
|
|
677
|
+
}
|
|
678
|
+
const updateIsAtBottom = () => {
|
|
679
|
+
setIsAtBottom(isScrollAtBottom(scrollElement));
|
|
680
|
+
};
|
|
681
|
+
updateIsAtBottom();
|
|
682
|
+
scrollElement.addEventListener("scroll", updateIsAtBottom, { passive: true });
|
|
683
|
+
return () => {
|
|
684
|
+
scrollElement.removeEventListener("scroll", updateIsAtBottom);
|
|
685
|
+
};
|
|
686
|
+
}, []);
|
|
449
687
|
useEffect(() => {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
688
|
+
return () => {
|
|
689
|
+
if (newMessageScrollTimeoutRef.current !== null) {
|
|
690
|
+
window.clearTimeout(newMessageScrollTimeoutRef.current);
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
}, []);
|
|
694
|
+
useEffect(() => {
|
|
695
|
+
const scrollElement = scrollRef.current;
|
|
696
|
+
const wasThinking = previousIsThinkingRef.current;
|
|
697
|
+
previousIsThinkingRef.current = isThinking;
|
|
698
|
+
if (!scrollElement || isThinking || !wasThinking) {
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
setIsAtBottom(isScrollAtBottom(scrollElement));
|
|
702
|
+
}, [isThinking]);
|
|
703
|
+
useIsomorphicLayoutEffect(() => {
|
|
704
|
+
const scrollElement = scrollRef.current;
|
|
705
|
+
if (isFetchingMessages) {
|
|
706
|
+
previousMessageGroupCountRef.current = messageGroupCount;
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
if (!scrollElement) {
|
|
710
|
+
previousMessageGroupCountRef.current = messageGroupCount;
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
if (!hasCompletedInitialScrollRef.current) {
|
|
714
|
+
clearScrollHeightVariables(scrollElement);
|
|
715
|
+
scrollToBottom("instant");
|
|
716
|
+
previousMessageGroupCountRef.current = messageGroupCount;
|
|
717
|
+
hasCompletedInitialScrollRef.current = true;
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
if (messageGroupCount < previousMessageGroupCountRef.current) {
|
|
721
|
+
previousMessageGroupCountRef.current = messageGroupCount;
|
|
722
|
+
clearScrollHeightVariables(scrollElement);
|
|
723
|
+
scrollToBottom("instant");
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
if (messageGroupCount > previousMessageGroupCountRef.current) {
|
|
727
|
+
updateScrollHeightVariables(scrollElement);
|
|
728
|
+
setIsNewMessageScrollInProgress(true);
|
|
729
|
+
if (newMessageScrollTimeoutRef.current !== null) {
|
|
730
|
+
window.clearTimeout(newMessageScrollTimeoutRef.current);
|
|
731
|
+
}
|
|
732
|
+
newMessageScrollTimeoutRef.current = window.setTimeout(() => {
|
|
733
|
+
setIsNewMessageScrollInProgress(false);
|
|
734
|
+
newMessageScrollTimeoutRef.current = null;
|
|
735
|
+
}, NEW_MESSAGE_SCROLL_HIDE_MS);
|
|
736
|
+
scrollToBottom("smooth");
|
|
737
|
+
}
|
|
738
|
+
previousMessageGroupCountRef.current = messageGroupCount;
|
|
739
|
+
}, [isFetchingMessages, messageGroupCount, scrollToBottom]);
|
|
740
|
+
return (_jsx(AgentChatScrollContext.Provider, { value: scrollContextValue, children: _jsx("div", { ...props, children: _jsx("div", { ref: scrollRef, style: {
|
|
741
|
+
height: "100%",
|
|
742
|
+
width: "100%",
|
|
743
|
+
scrollbarGutter: "stable both-edges",
|
|
744
|
+
}, children: _jsx("div", { className: scrollAreaClassName, children: children }) }) }) }));
|
|
745
|
+
}
|
|
746
|
+
export function AgentChatScrollToBottom({ asChild = false, onClick, ...props }) {
|
|
747
|
+
const { isAtBottom, scrollToBottom } = useAgentChatScroll();
|
|
748
|
+
const Comp = asChild ? Slot : "button";
|
|
749
|
+
if (isAtBottom) {
|
|
750
|
+
return null;
|
|
751
|
+
}
|
|
752
|
+
return (_jsx(Comp, { "data-slot": "agent-chat-scroll-to-bottom", onClick: (event) => {
|
|
753
|
+
onClick?.(event);
|
|
754
|
+
if (!event.defaultPrevented) {
|
|
755
|
+
scrollToBottom();
|
|
756
|
+
}
|
|
757
|
+
}, ...props }));
|
|
459
758
|
}
|
|
460
759
|
export function AgentChatThinking({ asChild = false, ...props }) {
|
|
461
760
|
const { isThinking } = useAgentChat();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocksdiy/react-common",
|
|
3
|
-
"version": "1.28.
|
|
3
|
+
"version": "1.28.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "React common",
|
|
6
6
|
"keywords": [],
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"penpal": "^7.0.6",
|
|
39
39
|
"react": "^19.2.4",
|
|
40
40
|
"use-stick-to-bottom": "1.1.4",
|
|
41
|
-
"@blocksdiy/blocks-client-api": "1.
|
|
41
|
+
"@blocksdiy/blocks-client-api": "1.10.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/react": "^19.2.14",
|