@blocksdiy/react-common 1.27.1 → 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,10 +11,15 @@ 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
|
+
/**
|
|
18
|
+
* When true, the chat_component tool pauses the agent run on the BE
|
|
19
|
+
* via `interrupt(...)` and the FE must call `respond(...)` (exposed
|
|
20
|
+
* to the rendered code) to resume it. Mirrors `block.data.userInterrupt`.
|
|
21
|
+
*/
|
|
22
|
+
userInterrupt?: boolean;
|
|
17
23
|
}
|
|
18
24
|
export declare const getAgentChatComponentToolName: (name: string) => string;
|
|
19
25
|
export interface AgentChat {
|
|
@@ -45,6 +51,14 @@ export interface AgentChatContextValue {
|
|
|
45
51
|
agent: Agent | null;
|
|
46
52
|
agentChat: AgentChat | null;
|
|
47
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;
|
|
48
62
|
sendMessage: (message: Omit<UserMessage, "id" | "role">) => Promise<void>;
|
|
49
63
|
sendFromInputs: () => Promise<void>;
|
|
50
64
|
stopGeneration: () => void;
|
|
@@ -58,6 +72,11 @@ export interface AgentChatContextValue {
|
|
|
58
72
|
}
|
|
59
73
|
export declare const AgentChatContext: import("react").Context<AgentChatContextValue | null>;
|
|
60
74
|
export declare const useAgentChat: () => AgentChatContextValue;
|
|
75
|
+
interface AgentChatScrollContextValue {
|
|
76
|
+
isAtBottom: boolean;
|
|
77
|
+
scrollToBottom: () => void;
|
|
78
|
+
}
|
|
79
|
+
export declare const useAgentChatScroll: () => AgentChatScrollContextValue;
|
|
61
80
|
export interface AgentChatRootProps {
|
|
62
81
|
appId: string;
|
|
63
82
|
token?: string;
|
|
@@ -82,10 +101,22 @@ export interface AgentChatMessageProps {
|
|
|
82
101
|
index: number;
|
|
83
102
|
}
|
|
84
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;
|
|
85
112
|
export interface AgentChatMessagesProps {
|
|
86
113
|
scrollAreaClassName?: string;
|
|
87
114
|
}
|
|
88
|
-
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;
|
|
89
120
|
export interface AgentChatThinkingProps {
|
|
90
121
|
asChild?: boolean;
|
|
91
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,14 +311,14 @@ const ChatInitializer = ({ agentId, agentChatId, chatId, token, appId, component
|
|
|
167
311
|
code,
|
|
168
312
|
input: block?.data?.input,
|
|
169
313
|
description,
|
|
170
|
-
|
|
314
|
+
userInterrupt: Boolean(block?.data?.userInterrupt),
|
|
171
315
|
};
|
|
172
316
|
}
|
|
173
317
|
catch {
|
|
174
318
|
return null;
|
|
175
319
|
}
|
|
176
320
|
}));
|
|
177
|
-
return
|
|
321
|
+
return chatComponentBlocks.filter((chatComponentBlock) => Boolean(chatComponentBlock));
|
|
178
322
|
}, [blocksApiService, componentIds]);
|
|
179
323
|
const componentIdsKey = componentIds?.join(",") ?? "";
|
|
180
324
|
const initKeyRef = useRef(null);
|
|
@@ -197,6 +341,7 @@ const ChatInitializer = ({ agentId, agentChatId, chatId, token, appId, component
|
|
|
197
341
|
}
|
|
198
342
|
initKeyRef.current = initKey;
|
|
199
343
|
initialPromptSentRef.current = false;
|
|
344
|
+
setHasSentMessageInSession(false);
|
|
200
345
|
const ac = new AbortController();
|
|
201
346
|
const initialize = async () => {
|
|
202
347
|
setIsConfigLoaded(false);
|
|
@@ -301,12 +446,45 @@ const ChatInitializer = ({ agentId, agentChatId, chatId, token, appId, component
|
|
|
301
446
|
});
|
|
302
447
|
void copilotkit.runAgent({ agent: copilotAgent });
|
|
303
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
|
+
};
|
|
304
476
|
const sendFromInputs = async () => {
|
|
305
477
|
if (!canSendMessage || !hasSendableInput(prompt, attachments)) {
|
|
306
478
|
return;
|
|
307
479
|
}
|
|
308
480
|
const currentPrompt = prompt;
|
|
309
|
-
|
|
481
|
+
const currentAttachments = attachments;
|
|
482
|
+
const uploadedAttachments = await uploadAttachments({
|
|
483
|
+
attachments: currentAttachments,
|
|
484
|
+
appId,
|
|
485
|
+
blocksApiService,
|
|
486
|
+
});
|
|
487
|
+
const messageContent = buildMessageContentFromInput(currentPrompt, uploadedAttachments ?? []);
|
|
310
488
|
setPrompt("");
|
|
311
489
|
setAttachments([]);
|
|
312
490
|
// Auto-skip any frontend user-choice tool call that's still waiting.
|
|
@@ -347,7 +525,9 @@ const ChatInitializer = ({ agentId, agentChatId, chatId, token, appId, component
|
|
|
347
525
|
});
|
|
348
526
|
}
|
|
349
527
|
}
|
|
350
|
-
await sendMessage({
|
|
528
|
+
await sendMessage({
|
|
529
|
+
content: messageContent,
|
|
530
|
+
});
|
|
351
531
|
};
|
|
352
532
|
const addAttachments = (attachments) => {
|
|
353
533
|
if (!attachmentsEnabled) {
|
|
@@ -376,6 +556,11 @@ const ChatInitializer = ({ agentId, agentChatId, chatId, token, appId, component
|
|
|
376
556
|
};
|
|
377
557
|
return (_jsx(AgentChatContext.Provider, { value: {
|
|
378
558
|
messages: copilotMessages,
|
|
559
|
+
messageGroups,
|
|
560
|
+
renderToolCall,
|
|
561
|
+
messageById,
|
|
562
|
+
toolMessages,
|
|
563
|
+
hasSentMessageInSession,
|
|
379
564
|
prompt,
|
|
380
565
|
attachments,
|
|
381
566
|
sendMessage,
|
|
@@ -437,24 +622,139 @@ export function AgentChatMessage({ message, index, ...props }) {
|
|
|
437
622
|
}
|
|
438
623
|
return _jsx("div", { "data-message-index": index, "data-message-role": message.role, ...props });
|
|
439
624
|
}
|
|
440
|
-
export function
|
|
441
|
-
const {
|
|
442
|
-
const
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
+
}, []);
|
|
448
687
|
useEffect(() => {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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 }));
|
|
458
758
|
}
|
|
459
759
|
export function AgentChatThinking({ asChild = false, ...props }) {
|
|
460
760
|
const { isThinking } = useAgentChat();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocksdiy/react-common",
|
|
3
|
-
"version": "1.
|
|
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",
|