@blocksdiy/react-common 1.47.0 → 1.49.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 +14 -24
- package/dist/components/new-agent-chat.d.ts.map +1 -1
- package/dist/components/new-agent-chat.js +120 -74
- package/dist/hooks/agent-conversation-context.d.ts +32 -0
- package/dist/hooks/agent-conversation-context.d.ts.map +1 -0
- package/dist/hooks/agent-conversation-context.js +39 -0
- package/dist/hooks/agent-thread-live-update.d.ts +35 -64
- package/dist/hooks/agent-thread-live-update.d.ts.map +1 -1
- package/dist/hooks/agent-thread-live-update.js +81 -64
- package/dist/hooks/use-agent-conversation.d.ts +105 -0
- package/dist/hooks/use-agent-conversation.d.ts.map +1 -0
- package/dist/hooks/use-agent-conversation.js +204 -0
- package/dist/hooks/use-agent-threads.d.ts +3 -50
- package/dist/hooks/use-agent-threads.d.ts.map +1 -1
- package/dist/hooks/use-agent-threads.js +53 -150
- package/dist/utils/composer-message.d.ts +57 -0
- package/dist/utils/composer-message.d.ts.map +1 -0
- package/dist/utils/composer-message.js +96 -0
- package/dist/utils/message-run-ids.d.ts +23 -3
- package/dist/utils/message-run-ids.d.ts.map +1 -1
- package/dist/utils/message-run-ids.js +45 -13
- package/package.json +2 -2
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// Headless state for the conversation an agent surface has open, over the threads index
|
|
2
|
+
// (useAgentThreads) and the chat runtime (useAgentChat): which conversation is selected and
|
|
3
|
+
// how a draft becomes a thread; the conversation's row; and the bridge that keeps the index
|
|
4
|
+
// and the transcript agreeing. UI-free — the agent-layout registry item renders it.
|
|
5
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
6
|
+
import { FEEDBACK_MESSAGE_NAME } from "../utils/composer-message.js";
|
|
7
|
+
import { agentThreadKey } from "./agent-thread-live-update.js";
|
|
8
|
+
export const newDraftSelection = (chatId, draftThreadId = crypto.randomUUID()) => ({
|
|
9
|
+
conversation: { chatId, chatThreadId: draftThreadId },
|
|
10
|
+
draftThreadId,
|
|
11
|
+
draftSent: false,
|
|
12
|
+
});
|
|
13
|
+
/** A draft stands by even when the URL opens a thread: "New chat" from there must not mint twice. */
|
|
14
|
+
export const initialSelection = (ownChatId, urlConversation) => {
|
|
15
|
+
const draft = newDraftSelection(ownChatId);
|
|
16
|
+
return urlConversation ? { ...draft, conversation: urlConversation } : draft;
|
|
17
|
+
};
|
|
18
|
+
export const isDraftSelection = ({ conversation, draftThreadId, draftSent }) => conversation.chatThreadId === draftThreadId && !draftSent;
|
|
19
|
+
/** After choosing a thread; the same object when it is already open (no re-render). */
|
|
20
|
+
export const selectionWithThread = (prev, conversation) => agentThreadKey(prev.conversation) === agentThreadKey(conversation) ? prev : { ...prev, conversation };
|
|
21
|
+
/** After "New chat": an untouched draft stays; anything else mints a fresh one. */
|
|
22
|
+
export const selectionWithNewChat = (prev, ownChatId) => (isDraftSelection(prev) ? prev : newDraftSelection(ownChatId));
|
|
23
|
+
/** After the first send on the draft; unchanged when the draft is not the open conversation. */
|
|
24
|
+
export const selectionAfterDraftSent = (prev) => isDraftSelection(prev) ? { ...prev, draftSent: true } : prev;
|
|
25
|
+
/** Identity of a URL target; the selection follows the URL whenever this changes. */
|
|
26
|
+
export const urlSelectionKey = (target) => target === null ? null : target === "new" ? "new" : agentThreadKey(target);
|
|
27
|
+
/**
|
|
28
|
+
* The open conversation follows the URL — the one source of truth, so choosing a thread is a
|
|
29
|
+
* navigation, never a call here; the draft is local state.
|
|
30
|
+
*/
|
|
31
|
+
export const useAgentConversationSelection = ({ ownChatId, urlTarget, }) => {
|
|
32
|
+
const urlConversation = urlTarget !== null && urlTarget !== "new" ? urlTarget : undefined;
|
|
33
|
+
const [selection, setSelection] = useState(() => initialSelection(ownChatId, urlConversation));
|
|
34
|
+
const markDraftSent = useCallback(() => setSelection(selectionAfterDraftSent), []);
|
|
35
|
+
// Synced during render (React's "adjusting state when a prop changes"), so the chat root
|
|
36
|
+
// below never renders a frame on the previous conversation.
|
|
37
|
+
const urlKey = urlSelectionKey(urlTarget);
|
|
38
|
+
const [syncedUrlKey, setSyncedUrlKey] = useState(urlKey);
|
|
39
|
+
if (syncedUrlKey !== urlKey) {
|
|
40
|
+
setSyncedUrlKey(urlKey);
|
|
41
|
+
if (urlConversation) {
|
|
42
|
+
setSelection((prev) => selectionWithThread(prev, urlConversation));
|
|
43
|
+
}
|
|
44
|
+
else if (urlTarget === "new") {
|
|
45
|
+
setSelection((prev) => selectionWithNewChat(prev, ownChatId));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { conversation: selection.conversation, isDraft: isDraftSelection(selection), markDraftSent };
|
|
49
|
+
};
|
|
50
|
+
/* ---- The open conversation's row, and the index ↔ chat bridge ----------------------------- */
|
|
51
|
+
// Mirrors wf-actions-py RUN_STALE_AFTER: the server ends a run still marked this long after it
|
|
52
|
+
// started, ahead of a list read — and publishes nothing for it, so a viewer has to ask.
|
|
53
|
+
export const RUN_STALE_MS = 30 * 60 * 1000;
|
|
54
|
+
// A refresh re-walks every loaded page; refocus is throttled to this.
|
|
55
|
+
const FOCUS_REFRESH_MIN_INTERVAL_MS = 15_000;
|
|
56
|
+
// While a mark stays past the ceiling (this clock may run ahead of the server's), ask again each minute.
|
|
57
|
+
const STALE_RUN_REPOLL_MS = 60_000;
|
|
58
|
+
/** A row for a conversation the index lists nothing for (yet): the draft, or a thread whose row has not landed. */
|
|
59
|
+
export const agentThreadStandIn = (agentId, conversation, isDraft) => {
|
|
60
|
+
const now = new Date().toISOString();
|
|
61
|
+
return {
|
|
62
|
+
agentId,
|
|
63
|
+
chatId: conversation.chatId,
|
|
64
|
+
chatThreadId: conversation.chatThreadId,
|
|
65
|
+
source: "direct_chat",
|
|
66
|
+
status: "idle",
|
|
67
|
+
title: null,
|
|
68
|
+
turnCount: isDraft ? 0 : 1,
|
|
69
|
+
firstSeenAt: now,
|
|
70
|
+
lastActivityAt: now,
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
/** When the oldest run mark among `rows` reaches the stale ceiling (epoch ms); null with no mark. */
|
|
74
|
+
export const nextRunStaleAt = (rows) => {
|
|
75
|
+
let next = null;
|
|
76
|
+
for (const row of rows) {
|
|
77
|
+
const startedAt = Date.parse(row.runningSince ?? "");
|
|
78
|
+
if (Number.isFinite(startedAt) && (next === null || startedAt + RUN_STALE_MS < next)) {
|
|
79
|
+
next = startedAt + RUN_STALE_MS;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return next;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* The accounting after a row for `key` reports `turnCount` turns. The first row is a baseline —
|
|
86
|
+
* the chat replayed it on mount — not a turn to fetch. `turnCount` is server-monotonic, so more
|
|
87
|
+
* turns than accounted is a turn this client did not make: replayed once nothing is streaming
|
|
88
|
+
* (a run's own stream is the authoritative view; replaying underneath it would fight its events).
|
|
89
|
+
*/
|
|
90
|
+
export const accountedTurnsAfterRow = (accounted, key, turnCount, isThinking) => {
|
|
91
|
+
if (!accounted || accounted.key !== key) {
|
|
92
|
+
return { accounted: { key, turns: turnCount }, replay: false };
|
|
93
|
+
}
|
|
94
|
+
if (turnCount <= accounted.turns || isThinking) {
|
|
95
|
+
return { accounted, replay: false };
|
|
96
|
+
}
|
|
97
|
+
return { accounted: { key, turns: turnCount }, replay: true };
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* The accounting after this client's own send on `key`: one more turn. A draft's first send
|
|
101
|
+
* starts the count, so the row that lands for it is read as this send's, not a foreign turn.
|
|
102
|
+
*/
|
|
103
|
+
export const accountedTurnsAfterOwnSend = (accounted, key, isDraft) => {
|
|
104
|
+
if (accounted && accounted.key === key) {
|
|
105
|
+
return { key, turns: accounted.turns + 1 };
|
|
106
|
+
}
|
|
107
|
+
return isDraft ? { key, turns: 1 } : accounted;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Keeps the index and the transcript agreeing for the open conversation:
|
|
111
|
+
* - a run on it this client is not streaming (the listed row's `runningRunId`) reads as the chat's own;
|
|
112
|
+
* - a turn this client did not send (`turnCount` ahead of what it accounted for) replays the transcript;
|
|
113
|
+
* - a run mark past the stale ceiling asks the server again (the settle publishes no event);
|
|
114
|
+
* - refocus re-walks the list (realtime has no replay), throttled.
|
|
115
|
+
*/
|
|
116
|
+
export const useAgentConversation = ({ threads, agentId, conversation, isDraft, chat, }) => {
|
|
117
|
+
const { threads: rows, refresh, notifyThreadActivity, renameThread: renameListedThread } = threads;
|
|
118
|
+
const { isThinking, reloadMessages, setRunningElsewhere } = chat;
|
|
119
|
+
const conversationKey = agentThreadKey(conversation);
|
|
120
|
+
const listedRow = rows.find((row) => agentThreadKey(row) === conversationKey);
|
|
121
|
+
// A row a filter hides is remembered, so the open conversation keeps its title. Stored
|
|
122
|
+
// during render (React's "storing information from previous renders"), not in an effect.
|
|
123
|
+
const [lastListedRow, setLastListedRow] = useState(null);
|
|
124
|
+
if (listedRow && listedRow !== lastListedRow) {
|
|
125
|
+
setLastListedRow(listedRow);
|
|
126
|
+
}
|
|
127
|
+
const knownRow = listedRow ?? (lastListedRow && agentThreadKey(lastListedRow) === conversationKey ? lastListedRow : undefined);
|
|
128
|
+
// Not memoised: a stand-in's stamps are "now", and a send stands its row in with them.
|
|
129
|
+
const row = knownRow ?? agentThreadStandIn(agentId, conversation, isDraft);
|
|
130
|
+
const rowRef = useRef(row);
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
rowRef.current = row;
|
|
133
|
+
});
|
|
134
|
+
const renameThread = useCallback(async (thread, title) => {
|
|
135
|
+
await renameListedThread(thread, title);
|
|
136
|
+
const trimmed = title.trim();
|
|
137
|
+
if (!trimmed) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
setLastListedRow((prev) => prev && agentThreadKey(prev) === agentThreadKey(thread) && prev.title !== trimmed
|
|
141
|
+
? { ...prev, title: trimmed }
|
|
142
|
+
: prev);
|
|
143
|
+
}, [renameListedThread]);
|
|
144
|
+
// A run elsewhere on this conversation — from the LISTED row only: a row a filter hides gets
|
|
145
|
+
// no live updates, so a remembered one would outlive its run.
|
|
146
|
+
const runningElsewhereRunId = listedRow?.runningRunId ?? null;
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
setRunningElsewhere?.(runningElsewhereRunId);
|
|
149
|
+
// Nothing else clears it: told once, the chat would stay shut past this surface's lifetime.
|
|
150
|
+
return () => setRunningElsewhere?.(null);
|
|
151
|
+
}, [setRunningElsewhere, runningElsewhereRunId]);
|
|
152
|
+
// The stale ceiling: ask at the oldest mark's ceiling, then each minute while a mark stays.
|
|
153
|
+
const nextStaleAt = nextRunStaleAt(rows);
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (nextStaleAt === null) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
let timer;
|
|
159
|
+
const ask = () => {
|
|
160
|
+
void refresh();
|
|
161
|
+
timer = setTimeout(ask, STALE_RUN_REPOLL_MS);
|
|
162
|
+
};
|
|
163
|
+
timer = setTimeout(ask, Math.max(nextStaleAt - Date.now(), 0));
|
|
164
|
+
return () => clearTimeout(timer);
|
|
165
|
+
}, [nextStaleAt, refresh]);
|
|
166
|
+
// Realtime has no replay — refocus re-walks the list, throttled.
|
|
167
|
+
const lastFocusRefreshAtRef = useRef(0);
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
const onFocus = () => {
|
|
170
|
+
const now = Date.now();
|
|
171
|
+
if (now - lastFocusRefreshAtRef.current < FOCUS_REFRESH_MIN_INTERVAL_MS) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
lastFocusRefreshAtRef.current = now;
|
|
175
|
+
void refresh();
|
|
176
|
+
};
|
|
177
|
+
window.addEventListener("focus", onFocus);
|
|
178
|
+
return () => window.removeEventListener("focus", onFocus);
|
|
179
|
+
}, [refresh]);
|
|
180
|
+
// Turns this client did not send (see accountedTurnsAfterRow). `isThinking` is a dependency,
|
|
181
|
+
// so this runs again the moment a run on the conversation settles.
|
|
182
|
+
const accountedTurnsRef = useRef(null);
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
if (!knownRow) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const next = accountedTurnsAfterRow(accountedTurnsRef.current, conversationKey, knownRow.turnCount, isThinking);
|
|
188
|
+
accountedTurnsRef.current = next.accounted;
|
|
189
|
+
if (next.replay) {
|
|
190
|
+
void reloadMessages();
|
|
191
|
+
}
|
|
192
|
+
}, [knownRow, conversationKey, isThinking, reloadMessages]);
|
|
193
|
+
const notifyMessageSent = useCallback((text, message) => {
|
|
194
|
+
// A feedback send is recorded on the conversation with no reply there: activity moves;
|
|
195
|
+
// no turn is accounted, and the prompt does not name the thread.
|
|
196
|
+
const isFeedbackSend = message?.name === FEEDBACK_MESSAGE_NAME;
|
|
197
|
+
notifyThreadActivity(rowRef.current, isFeedbackSend ? undefined : text);
|
|
198
|
+
if (isFeedbackSend) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
accountedTurnsRef.current = accountedTurnsAfterOwnSend(accountedTurnsRef.current, conversationKey, isDraft);
|
|
202
|
+
}, [notifyThreadActivity, conversationKey, isDraft]);
|
|
203
|
+
return { row, isStandIn: knownRow === undefined, renameThread, notifyMessageSent };
|
|
204
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type AgentThreadItem, type AgentThreadSource, type AgentThreadStatus } from "./agent-thread-live-update.js";
|
|
2
2
|
export * from "./agent-thread-live-update.js";
|
|
3
|
+
export { type AgentConversationChat, type AgentConversationRef, type AgentConversationUrlTarget, agentThreadStandIn, nextRunStaleAt, RUN_STALE_MS, useAgentConversation, type UseAgentConversationOptions, type UseAgentConversationResult, useAgentConversationSelection, type UseAgentConversationSelectionOptions, type UseAgentConversationSelectionResult, } from "./use-agent-conversation.js";
|
|
4
|
+
export { type AgentConversationContextValue, AgentConversationProvider, type AgentConversationProviderProps, type AgentThreadFilters, useAgentConversationContext, } from "./agent-conversation-context.js";
|
|
3
5
|
export interface AgentThreadsPage {
|
|
4
6
|
items: AgentThreadItem[];
|
|
5
7
|
nextCursor: string | null;
|
|
@@ -12,75 +14,26 @@ export interface UseAgentThreadsOptions {
|
|
|
12
14
|
limit?: number;
|
|
13
15
|
sort?: "lastActivity" | "firstSeen";
|
|
14
16
|
order?: "asc" | "desc";
|
|
15
|
-
/** Platform host override; defaults to getApiHost() (same convention as the agent chat). */
|
|
16
17
|
baseUrl?: string;
|
|
17
|
-
/** Bearer token for callers that don't share the platform session cookie. */
|
|
18
18
|
token?: string;
|
|
19
|
-
/** Extra request headers, merged over the defaults. */
|
|
20
19
|
headers?: Record<string, string>;
|
|
21
20
|
}
|
|
22
21
|
export interface UseAgentThreadsResult {
|
|
23
22
|
threads: AgentThreadItem[];
|
|
24
|
-
/**
|
|
25
|
-
* Report a user send on a conversation — ChatGPT-style optimistic listing. Pass the
|
|
26
|
-
* conversation's row (the listed one when the index has it, else one the consumer
|
|
27
|
-
* builds from the (chatId, chatThreadId) pair) and the prompt text: a conversation with
|
|
28
|
-
* no server row yet appears in `threads` immediately, titled by the truncated prompt,
|
|
29
|
-
* and every send bumps the row's recency; server rows take over under the same identity
|
|
30
|
-
* as events land. Identity-stable — safe to hand to memoized chat surfaces.
|
|
31
|
-
*/
|
|
32
23
|
notifyThreadActivity: (thread: AgentThreadItem, prompt?: string) => void;
|
|
33
|
-
/**
|
|
34
|
-
* Rename a conversation. Optimistic — the list shows the new title immediately and
|
|
35
|
-
* rolls back on failure (the promise rejects so callers can surface an error).
|
|
36
|
-
*/
|
|
37
24
|
renameThread: (thread: Pick<AgentThreadItem, "chatId" | "chatThreadId">, title: string) => Promise<void>;
|
|
38
|
-
/** First page (or refresh) in flight. */
|
|
39
25
|
isLoading: boolean;
|
|
40
|
-
/** A cursor page is being appended. */
|
|
41
26
|
isLoadingMore: boolean;
|
|
42
27
|
error: Error | null;
|
|
43
28
|
hasMore: boolean;
|
|
44
29
|
loadMore: () => Promise<void>;
|
|
45
|
-
/**
|
|
46
|
-
* Re-walk every loaded page. Freshness is otherwise live (the hook subscribes to
|
|
47
|
-
* AGENT_THREAD_UPDATED itself) — keep refresh for recovery, e.g. a throttled
|
|
48
|
-
* window-focus refresh after a dropped connection.
|
|
49
|
-
*/
|
|
50
30
|
refresh: () => Promise<void>;
|
|
51
31
|
}
|
|
52
|
-
/**
|
|
53
|
-
* The platform inbox's fixed chatId for an agent: one shared value per agent block, so
|
|
54
|
-
* index rows are recognizable as "direct chats with this agent on the platform" at a
|
|
55
|
-
* glance (and filterable via the endpoint's chatId param). Conversations under it are
|
|
56
|
-
* distinguished by their unique chatThreadId.
|
|
57
|
-
*/
|
|
58
32
|
export declare const AGENT_INBOX_CHAT_PREFIX = "agent-inbox-";
|
|
59
33
|
export declare const agentInboxChatId: (agentId: string) => string;
|
|
60
|
-
/**
|
|
61
|
-
* Short human label for a row with no generated title yet: the half of the identity
|
|
62
|
-
* pair that varies for the row. Inbox rows share one fixed chatId (above) and vary by
|
|
63
|
-
* thread id; rows from other surfaces (published-app chats) are the reverse. One-sided
|
|
64
|
-
* writers (agent calls) index a "_" placeholder for the missing half — fall back to
|
|
65
|
-
* the real half rather than labeling every such row "_".
|
|
66
|
-
*/
|
|
67
34
|
export declare const agentThreadShortLabel: (thread: Pick<AgentThreadItem, "chatId" | "chatThreadId">) => string;
|
|
68
|
-
/** How a row is labelled: its title, or the id-based short label while it has none. */
|
|
69
35
|
export declare const agentThreadDisplayTitle: (thread: Pick<AgentThreadItem, "chatId" | "chatThreadId" | "title">) => string;
|
|
70
|
-
/**
|
|
71
|
-
* Headless agent-threads index: fetches an agent's conversation threads from the platform
|
|
72
|
-
* endpoint with keyset-cursor pagination, then keeps them live — every threads-table write
|
|
73
|
-
* publishes AGENT_THREAD_UPDATED on the app's Pusher channel and the hook upserts the row,
|
|
74
|
-
* falling back to the getOne endpoint when a payload lacks the full row. New conversations
|
|
75
|
-
* list optimistically the moment the first prompt is sent (wire notifyThreadActivity to the
|
|
76
|
-
* chat's send seam). The LIST only — which conversation is open is the consumer's (a
|
|
77
|
-
* conversation is addressed by its (chatId, chatThreadId) pair; a new one is a fresh UUID
|
|
78
|
-
* thread id under the surface's chatId — CopilotKit replaces non-UUID ids per load).
|
|
79
|
-
* Pair it with whatever list UI the host app uses. No per-host wiring: both hosts already
|
|
80
|
-
* run the shared websocketsService singleton and subscribe the app channel (the platform
|
|
81
|
-
* via its layouts' useWebsockets hooks, a boilerplate app via the client SDK's
|
|
82
|
-
* ClientProvider), and listening before the socket connects just parks the handler.
|
|
83
|
-
*/
|
|
36
|
+
/** Agent-threads index with pagination, realtime upserts, and optimistic send listing. */
|
|
84
37
|
export declare const useAgentThreads: (options: UseAgentThreadsOptions) => UseAgentThreadsResult;
|
|
85
38
|
export default useAgentThreads;
|
|
86
39
|
//# sourceMappingURL=use-agent-threads.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-agent-threads.d.ts","sourceRoot":"","sources":["../../src/hooks/use-agent-threads.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,eAAe,EAEpB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,
|
|
1
|
+
{"version":3,"file":"use-agent-threads.d.ts","sourceRoot":"","sources":["../../src/hooks/use-agent-threads.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,eAAe,EAEpB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EAUvB,MAAM,4BAA4B,CAAC;AAEpC,cAAc,4BAA4B,CAAC;AAC3C,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,0BAA0B,EAC/B,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,oBAAoB,EACpB,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,EAC/B,6BAA6B,EAC7B,KAAK,oCAAoC,EACzC,KAAK,mCAAmC,GACzC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,KAAK,6BAA6B,EAClC,yBAAyB,EACzB,KAAK,8BAA8B,EACnC,KAAK,kBAAkB,EACvB,2BAA2B,GAC5B,MAAM,8BAA8B,CAAC;AAItC,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,cAAc,GAAG,WAAW,CAAC;IACpC,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,oBAAoB,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACzE,YAAY,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,GAAG,cAAc,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzG,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,eAAO,MAAM,uBAAuB,iBAAiB,CAAC;AACtD,eAAO,MAAM,gBAAgB,GAAI,SAAS,MAAM,KAAG,MAAgD,CAAC;AAEpG,eAAO,MAAM,qBAAqB,GAAI,QAAQ,IAAI,CAAC,eAAe,EAAE,QAAQ,GAAG,cAAc,CAAC,KAAG,MAIhG,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,QAAQ,IAAI,CAAC,eAAe,EAAE,QAAQ,GAAG,cAAc,GAAG,OAAO,CAAC,KAAG,MACpD,CAAC;AAuC1D,0FAA0F;AAC1F,eAAO,MAAM,eAAe,GAAI,SAAS,sBAAsB,KAAG,qBA6UjE,CAAC;AAEF,eAAe,eAAe,CAAC"}
|