@blocksdiy/react-common 1.44.0 → 1.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/new-agent-chat.d.ts +32 -2
- package/dist/components/new-agent-chat.d.ts.map +1 -1
- package/dist/components/new-agent-chat.js +77 -10
- package/dist/utils/message-run-ids.d.ts +26 -0
- package/dist/utils/message-run-ids.d.ts.map +1 -0
- package/dist/utils/message-run-ids.js +65 -0
- package/package.json +2 -2
|
@@ -2,7 +2,9 @@ import { type Message, ToolMessage, type UserMessage } from "@ag-ui/client";
|
|
|
2
2
|
import { useRenderToolCall } from "@copilotkit/react-core/v2";
|
|
3
3
|
import { ReactNode, SetStateAction } from "react";
|
|
4
4
|
import { type RenderProp } from "../utils/compose-render.js";
|
|
5
|
+
import { type MessageRunIds } from "../utils/message-run-ids.js";
|
|
5
6
|
export { useTextGrowthAnimating } from "../hooks/text-growth-animation.js";
|
|
7
|
+
export { MESSAGE_RUN_IDS_EVENT_NAME, messageIdsOfRun, type MessageRunIds } from "../utils/message-run-ids.js";
|
|
6
8
|
export { type Message, type AssistantMessage, type UserMessage } from "@ag-ui/client";
|
|
7
9
|
export { useRenderTool, useDefaultRenderTool, useHumanInTheLoop, useFrontendTool, ToolCallStatus, } from "@copilotkit/react-core/v2";
|
|
8
10
|
export interface Attachment {
|
|
@@ -58,6 +60,13 @@ export interface AgentChatContextValue {
|
|
|
58
60
|
role: "assistant" | "user";
|
|
59
61
|
}[];
|
|
60
62
|
messageById: Map<string, Message>;
|
|
63
|
+
/**
|
|
64
|
+
* The agent run (turn) that produced each replayed message — the `agentRunId` the rows
|
|
65
|
+
* it wrote carry. Filled from the connect replay; messages from before run attribution
|
|
66
|
+
* (and live-streamed ones) have no entry. Optional so contexts a host builds by hand
|
|
67
|
+
* (previews, stubs) need not state it; absent reads as "no run known".
|
|
68
|
+
*/
|
|
69
|
+
runIdByMessageId?: MessageRunIds;
|
|
61
70
|
toolMessages: ToolMessage[];
|
|
62
71
|
renderToolCall: ReturnType<typeof useRenderToolCall>;
|
|
63
72
|
sendMessage: (message: Omit<UserMessage, "id" | "role">) => Promise<void>;
|
|
@@ -163,8 +172,29 @@ export interface AgentChatMessageGroupProps {
|
|
|
163
172
|
export declare function AgentChatMessageGroup({ messageGroup, ...props }: React.ComponentProps<"div"> & AgentChatMessageGroupProps): import("react").JSX.Element;
|
|
164
173
|
export interface AgentChatMessagesProps {
|
|
165
174
|
scrollAreaClassName?: string;
|
|
166
|
-
|
|
167
|
-
|
|
175
|
+
/**
|
|
176
|
+
* An agent run (turn) to open the conversation at — the `agentRunId` a row the agent
|
|
177
|
+
* wrote carries. Once the transcript has replayed, the first message group of that run
|
|
178
|
+
* is scrolled into view and the run's messages are marked highlighted for
|
|
179
|
+
* `focusHighlightMs` (read it with `useAgentChatRunFocus`). Happens once per
|
|
180
|
+
* conversation; a no-op when the run is not in this conversation (an older thread,
|
|
181
|
+
* another conversation's run).
|
|
182
|
+
*/
|
|
183
|
+
focusRunId?: string;
|
|
184
|
+
/** How long a focused run stays highlighted; defaults to 2400ms. */
|
|
185
|
+
focusHighlightMs?: number;
|
|
186
|
+
}
|
|
187
|
+
export interface AgentChatRunFocusValue {
|
|
188
|
+
/** The run being focused (the `focusRunId` passed to AgentChatMessages), if any. */
|
|
189
|
+
runId?: string;
|
|
190
|
+
/** Ids of the messages that run produced, in transcript order. */
|
|
191
|
+
messageIds: ReadonlySet<string>;
|
|
192
|
+
/** True for a moment after the run is scrolled into view — drive the highlight off it. */
|
|
193
|
+
isHighlighted: boolean;
|
|
194
|
+
}
|
|
195
|
+
/** The run focus of the enclosing AgentChatMessages (see its `focusRunId`). */
|
|
196
|
+
export declare const useAgentChatRunFocus: () => AgentChatRunFocusValue;
|
|
197
|
+
export declare function AgentChatMessages({ scrollAreaClassName, focusRunId, focusHighlightMs, ...props }: React.ComponentProps<"div"> & AgentChatMessagesProps): import("react").JSX.Element;
|
|
168
198
|
export interface AgentChatThinkingProps {
|
|
169
199
|
asChild?: boolean;
|
|
170
200
|
render?: RenderProp;
|
|
@@ -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,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAI5E,OAAO,EAAiB,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAEL,SAAS,EACT,cAAc,EAQf,MAAM,OAAO,CAAC;AAGf,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"new-agent-chat.d.ts","sourceRoot":"","sources":["../../src/components/new-agent-chat.tsx"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,9 @@ import { useCopilotKit, useRenderToolCall } from "@copilotkit/react-core/v2";
|
|
|
6
6
|
import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
|
|
7
7
|
import { useStickToBottom } from "use-stick-to-bottom";
|
|
8
8
|
import { ComposeRender } from "../utils/compose-render.js";
|
|
9
|
+
import { EMPTY_MESSAGE_RUN_IDS, findMessageGroupElement, mergeMessageRunIds, MESSAGE_IDS_ATTRIBUTE, messageIdsOfRun, readMessageRunIdsEvent, } from "../utils/message-run-ids.js";
|
|
9
10
|
export { useTextGrowthAnimating } from "../hooks/text-growth-animation.js";
|
|
11
|
+
export { MESSAGE_RUN_IDS_EVENT_NAME, messageIdsOfRun } from "../utils/message-run-ids.js";
|
|
10
12
|
export { useRenderTool, useDefaultRenderTool, useHumanInTheLoop, useFrontendTool, ToolCallStatus, } from "@copilotkit/react-core/v2";
|
|
11
13
|
// Client-only `useLayoutEffect`, falls back to `useEffect` during SSR.
|
|
12
14
|
// Needed so the `agent.threadId` mirror commits before CopilotKit's
|
|
@@ -309,6 +311,23 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
|
|
|
309
311
|
});
|
|
310
312
|
return groups;
|
|
311
313
|
}, [copilotMessages]);
|
|
314
|
+
// Which run produced each replayed message: the connect replay sends it as a CUSTOM
|
|
315
|
+
// event (see utils/message-run-ids). Cleared with the transcript on a conversation swap.
|
|
316
|
+
const [runIdByMessageId, setRunIdByMessageId] = useState(EMPTY_MESSAGE_RUN_IDS);
|
|
317
|
+
useEffect(() => {
|
|
318
|
+
if (!copilotAgent) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const { unsubscribe } = copilotAgent.subscribe({
|
|
322
|
+
onCustomEvent: ({ event }) => {
|
|
323
|
+
const runIds = readMessageRunIdsEvent(event);
|
|
324
|
+
if (runIds) {
|
|
325
|
+
setRunIdByMessageId((previous) => mergeMessageRunIds(previous, runIds));
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
return unsubscribe;
|
|
330
|
+
}, [copilotAgent]);
|
|
312
331
|
const interruptAnswersRef = useRef(new Map());
|
|
313
332
|
// Mirror `currentThreadId` onto the agent instance — the `<CopilotKit>`
|
|
314
333
|
// prop only feeds React context, but `agent.threadId` is what hits the
|
|
@@ -340,6 +359,7 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
|
|
|
340
359
|
}
|
|
341
360
|
prevConversationKeyRef.current = conversationKey;
|
|
342
361
|
copilotAgent?.setMessages?.([]);
|
|
362
|
+
setRunIdByMessageId(EMPTY_MESSAGE_RUN_IDS);
|
|
343
363
|
setPrompt("");
|
|
344
364
|
setAttachments([]);
|
|
345
365
|
// The clear above runs in the layout phase, but the previous conversation's run is
|
|
@@ -755,6 +775,7 @@ const ChatInitializer = ({ agentBlock, agentChatBlock, chatId, token, appId, com
|
|
|
755
775
|
messageGroups,
|
|
756
776
|
renderToolCall,
|
|
757
777
|
messageById,
|
|
778
|
+
runIdByMessageId,
|
|
758
779
|
toolMessages,
|
|
759
780
|
prompt,
|
|
760
781
|
attachments,
|
|
@@ -940,16 +961,26 @@ export function AgentChatMessage({ message, index, ...props }) {
|
|
|
940
961
|
return _jsx("div", { "data-message-index": index, "data-message-role": message.role, ...props });
|
|
941
962
|
}
|
|
942
963
|
export function AgentChatMessageGroup({ messageGroup, ...props }) {
|
|
943
|
-
return (_jsx("div", { "data-slot": "agent-chat-message-group", "data-message-group": messageGroup.role, "data-message-group-role": messageGroup.role, ...props }));
|
|
964
|
+
return (_jsx("div", { "data-slot": "agent-chat-message-group", "data-message-group": messageGroup.role, "data-message-group-role": messageGroup.role, [MESSAGE_IDS_ATTRIBUTE]: messageGroup.messageIds.join(" "), ...props }));
|
|
944
965
|
}
|
|
945
|
-
|
|
946
|
-
|
|
966
|
+
const NO_RUN_FOCUS = { messageIds: new Set(), isHighlighted: false };
|
|
967
|
+
const AgentChatRunFocusContext = createContext(NO_RUN_FOCUS);
|
|
968
|
+
/** The run focus of the enclosing AgentChatMessages (see its `focusRunId`). */
|
|
969
|
+
export const useAgentChatRunFocus = () => useContext(AgentChatRunFocusContext);
|
|
970
|
+
const DEFAULT_FOCUS_HIGHLIGHT_MS = 2400;
|
|
971
|
+
export function AgentChatMessages({ scrollAreaClassName, focusRunId, focusHighlightMs = DEFAULT_FOCUS_HIGHLIGHT_MS, ...props }) {
|
|
972
|
+
const { messages, runIdByMessageId = EMPTY_MESSAGE_RUN_IDS, isFetchingMessages, currentThreadId } = useAgentChat();
|
|
947
973
|
const userMessageCount = messages.filter((message) => message.role === "user" && message.name !== HIDDEN_INITIAL_PROMPT_MESSAGE_NAME).length;
|
|
948
974
|
const previousUserMessageCountRef = useRef(userMessageCount);
|
|
949
|
-
const { scrollRef, contentRef, scrollToBottom } = useStickToBottom({
|
|
975
|
+
const { scrollRef, contentRef, scrollToBottom, stopScroll } = useStickToBottom({
|
|
950
976
|
resize: "smooth",
|
|
951
977
|
initial: { damping: 1, stiffness: 1 },
|
|
952
978
|
});
|
|
979
|
+
const contentElementRef = useRef(null);
|
|
980
|
+
const setContentRef = useCallback((element) => {
|
|
981
|
+
contentElementRef.current = element;
|
|
982
|
+
contentRef(element);
|
|
983
|
+
}, [contentRef]);
|
|
953
984
|
useEffect(() => {
|
|
954
985
|
if (userMessageCount > previousUserMessageCountRef.current) {
|
|
955
986
|
void scrollToBottom({
|
|
@@ -959,12 +990,48 @@ export function AgentChatMessages({ scrollAreaClassName, ...props }) {
|
|
|
959
990
|
}
|
|
960
991
|
previousUserMessageCountRef.current = userMessageCount;
|
|
961
992
|
}, [scrollToBottom, userMessageCount]);
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
993
|
+
const runMessageIds = useMemo(() => messageIdsOfRun(messages, runIdByMessageId, focusRunId), [messages, runIdByMessageId, focusRunId]);
|
|
994
|
+
const [isHighlighted, setIsHighlighted] = useState(false);
|
|
995
|
+
const focusedKeyRef = useRef(null);
|
|
996
|
+
// Declared after the stick-to-bottom effect above on purpose: the replay that lands the
|
|
997
|
+
// run also bumps the user-message count, and this effect has to run second so its
|
|
998
|
+
// `stopScroll` cancels that scroll-to-bottom instead of being overridden by it.
|
|
999
|
+
useEffect(() => {
|
|
1000
|
+
const firstMessageId = runMessageIds[0];
|
|
1001
|
+
if (!focusRunId || isFetchingMessages || !firstMessageId) {
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
const focusKey = `${currentThreadId ?? ""}|${focusRunId}`;
|
|
1005
|
+
if (focusedKeyRef.current === focusKey) {
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
focusedKeyRef.current = focusKey;
|
|
1009
|
+
// Leave the bottom lock, or the next resize (a replayed group fading in, a markdown
|
|
1010
|
+
// block laying out) pulls the transcript back down past the run.
|
|
1011
|
+
stopScroll();
|
|
1012
|
+
requestAnimationFrame(() => {
|
|
1013
|
+
const reduceMotion = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
1014
|
+
findMessageGroupElement(contentElementRef.current, firstMessageId)?.scrollIntoView({
|
|
1015
|
+
block: "start",
|
|
1016
|
+
behavior: reduceMotion ? "auto" : "smooth",
|
|
1017
|
+
});
|
|
1018
|
+
setIsHighlighted(true);
|
|
1019
|
+
});
|
|
1020
|
+
}, [focusRunId, isFetchingMessages, runMessageIds, currentThreadId, stopScroll]);
|
|
1021
|
+
useEffect(() => {
|
|
1022
|
+
if (!isHighlighted) {
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
const timeoutId = window.setTimeout(() => setIsHighlighted(false), focusHighlightMs);
|
|
1026
|
+
return () => window.clearTimeout(timeoutId);
|
|
1027
|
+
}, [isHighlighted, focusHighlightMs]);
|
|
1028
|
+
const runFocus = useMemo(() => (focusRunId ? { runId: focusRunId, messageIds: new Set(runMessageIds), isHighlighted } : NO_RUN_FOCUS), [focusRunId, runMessageIds, isHighlighted]);
|
|
1029
|
+
return (_jsx(AgentChatRunFocusContext.Provider, { value: runFocus, children: _jsx("div", { ref: scrollRef, className: scrollAreaClassName, style: {
|
|
1030
|
+
height: "100%",
|
|
1031
|
+
width: "100%",
|
|
1032
|
+
overflow: "auto",
|
|
1033
|
+
scrollbarGutter: "stable both-edges",
|
|
1034
|
+
}, children: _jsx("div", { ref: setContentRef, "data-slot": "agent-chat-messages", ...props }) }) }));
|
|
968
1035
|
}
|
|
969
1036
|
export function AgentChatThinking({ asChild = false, render, ...props }) {
|
|
970
1037
|
const { isThinking } = useAgentChat();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which agent run (turn) produced each message of a conversation.
|
|
3
|
+
*
|
|
4
|
+
* A row an app agent writes records the run that wrote it (`agentRunId`). On
|
|
5
|
+
* `agent/connect` the server replays the transcript and then sends one AG-UI CUSTOM
|
|
6
|
+
* event, `blocks.message_run_ids`, carrying `{ runIds: { [messageId]: runId } }` for
|
|
7
|
+
* the replayed messages it can attribute (older messages have none). The run id
|
|
8
|
+
* rides in an event rather than on the messages because the AG-UI client validates
|
|
9
|
+
* snapshot messages against a strict schema that drops unknown fields.
|
|
10
|
+
*/
|
|
11
|
+
export declare const MESSAGE_RUN_IDS_EVENT_NAME = "blocks.message_run_ids";
|
|
12
|
+
export type MessageRunIds = ReadonlyMap<string, string>;
|
|
13
|
+
export declare const EMPTY_MESSAGE_RUN_IDS: MessageRunIds;
|
|
14
|
+
/** The `{ messageId: runId }` payload of a run-ids CUSTOM event, or null for any other event. */
|
|
15
|
+
export declare const readMessageRunIdsEvent: (event: unknown) => Record<string, string> | null;
|
|
16
|
+
/** `previous` plus `incoming`; returns `previous` itself when nothing changes (no re-render). */
|
|
17
|
+
export declare const mergeMessageRunIds: (previous: MessageRunIds, incoming: Record<string, string>) => MessageRunIds;
|
|
18
|
+
/** Ids of the messages `runId` produced, in transcript order (empty when the run isn't in it). */
|
|
19
|
+
export declare const messageIdsOfRun: (messages: readonly {
|
|
20
|
+
id: string;
|
|
21
|
+
}[], runIds: MessageRunIds, runId: string | undefined) => string[];
|
|
22
|
+
/** The attribute a message group carries so a run's first message can be found in the DOM. */
|
|
23
|
+
export declare const MESSAGE_IDS_ATTRIBUTE = "data-message-ids";
|
|
24
|
+
/** The rendered message group containing `messageId` inside `root`, if any. */
|
|
25
|
+
export declare const findMessageGroupElement: (root: ParentNode | null, messageId: string) => HTMLElement | null;
|
|
26
|
+
//# sourceMappingURL=message-run-ids.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-run-ids.d.ts","sourceRoot":"","sources":["../../src/utils/message-run-ids.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,eAAO,MAAM,0BAA0B,2BAA2B,CAAC;AAEnE,MAAM,MAAM,aAAa,GAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAExD,eAAO,MAAM,qBAAqB,EAAE,aAAyB,CAAC;AAK9D,iGAAiG;AACjG,eAAO,MAAM,sBAAsB,GAAI,OAAO,OAAO,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAehF,CAAC;AAEF,iGAAiG;AACjG,eAAO,MAAM,kBAAkB,GAAI,UAAU,aAAa,EAAE,UAAU,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAG,aAU9F,CAAC;AAEF,kGAAkG;AAClG,eAAO,MAAM,eAAe,GAC1B,UAAU,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,EACnC,QAAQ,aAAa,EACrB,OAAO,MAAM,GAAG,SAAS,KACxB,MAAM,EAKR,CAAC;AAEF,8FAA8F;AAC9F,eAAO,MAAM,qBAAqB,qBAAqB,CAAC;AAExD,+EAA+E;AAC/E,eAAO,MAAM,uBAAuB,GAAI,MAAM,UAAU,GAAG,IAAI,EAAE,WAAW,MAAM,KAAG,WAAW,GAAG,IAYlG,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which agent run (turn) produced each message of a conversation.
|
|
3
|
+
*
|
|
4
|
+
* A row an app agent writes records the run that wrote it (`agentRunId`). On
|
|
5
|
+
* `agent/connect` the server replays the transcript and then sends one AG-UI CUSTOM
|
|
6
|
+
* event, `blocks.message_run_ids`, carrying `{ runIds: { [messageId]: runId } }` for
|
|
7
|
+
* the replayed messages it can attribute (older messages have none). The run id
|
|
8
|
+
* rides in an event rather than on the messages because the AG-UI client validates
|
|
9
|
+
* snapshot messages against a strict schema that drops unknown fields.
|
|
10
|
+
*/
|
|
11
|
+
export const MESSAGE_RUN_IDS_EVENT_NAME = "blocks.message_run_ids";
|
|
12
|
+
export const EMPTY_MESSAGE_RUN_IDS = new Map();
|
|
13
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
+
/** The `{ messageId: runId }` payload of a run-ids CUSTOM event, or null for any other event. */
|
|
15
|
+
export const readMessageRunIdsEvent = (event) => {
|
|
16
|
+
if (!isRecord(event) || event.name !== MESSAGE_RUN_IDS_EVENT_NAME || !isRecord(event.value)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const runIds = event.value.runIds;
|
|
20
|
+
if (!isRecord(runIds)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const out = {};
|
|
24
|
+
for (const [messageId, runId] of Object.entries(runIds)) {
|
|
25
|
+
if (messageId && typeof runId === "string" && runId) {
|
|
26
|
+
out[messageId] = runId;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
};
|
|
31
|
+
/** `previous` plus `incoming`; returns `previous` itself when nothing changes (no re-render). */
|
|
32
|
+
export const mergeMessageRunIds = (previous, incoming) => {
|
|
33
|
+
let next = null;
|
|
34
|
+
for (const [messageId, runId] of Object.entries(incoming)) {
|
|
35
|
+
if (previous.get(messageId) === runId) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
next ??= new Map(previous);
|
|
39
|
+
next.set(messageId, runId);
|
|
40
|
+
}
|
|
41
|
+
return next ?? previous;
|
|
42
|
+
};
|
|
43
|
+
/** Ids of the messages `runId` produced, in transcript order (empty when the run isn't in it). */
|
|
44
|
+
export const messageIdsOfRun = (messages, runIds, runId) => {
|
|
45
|
+
if (!runId) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
return messages.filter((message) => runIds.get(message.id) === runId).map((message) => message.id);
|
|
49
|
+
};
|
|
50
|
+
/** The attribute a message group carries so a run's first message can be found in the DOM. */
|
|
51
|
+
export const MESSAGE_IDS_ATTRIBUTE = "data-message-ids";
|
|
52
|
+
/** The rendered message group containing `messageId` inside `root`, if any. */
|
|
53
|
+
export const findMessageGroupElement = (root, messageId) => {
|
|
54
|
+
if (!root) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const groups = root.querySelectorAll(`[${MESSAGE_IDS_ATTRIBUTE}]`);
|
|
58
|
+
for (const group of Array.from(groups)) {
|
|
59
|
+
const ids = (group.getAttribute(MESSAGE_IDS_ATTRIBUTE) ?? "").split(" ");
|
|
60
|
+
if (ids.includes(messageId)) {
|
|
61
|
+
return group;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocksdiy/react-common",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.46.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "React common",
|
|
6
6
|
"keywords": [],
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"penpal": "^8.0.0",
|
|
40
40
|
"react": "^19.2.8",
|
|
41
41
|
"use-stick-to-bottom": "1.1.4",
|
|
42
|
-
"@blocksdiy/blocks-client-api": "1.
|
|
42
|
+
"@blocksdiy/blocks-client-api": "1.24.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/react": "^19.2.18",
|