@extrachill/chat 0.5.0 → 0.6.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/CHANGELOG.md +10 -0
- package/README.md +1 -1
- package/css/chat.css +184 -1
- package/dist/Chat.d.ts +27 -1
- package/dist/Chat.d.ts.map +1 -1
- package/dist/Chat.js +4 -2
- package/dist/client-context.d.ts +31 -0
- package/dist/client-context.d.ts.map +1 -0
- package/dist/client-context.js +100 -0
- package/dist/components/ChatMessages.d.ts +18 -1
- package/dist/components/ChatMessages.d.ts.map +1 -1
- package/dist/components/ChatMessages.js +5 -1
- package/dist/components/CopyTranscriptButton.d.ts +12 -0
- package/dist/components/CopyTranscriptButton.d.ts.map +1 -0
- package/dist/components/CopyTranscriptButton.js +26 -0
- package/dist/components/DiffCard.d.ts +40 -0
- package/dist/components/DiffCard.d.ts.map +1 -0
- package/dist/components/DiffCard.js +162 -0
- package/dist/components/ErrorBoundary.d.ts +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/diff.d.ts +27 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +109 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/transcript.d.ts +4 -0
- package/dist/transcript.d.ts.map +1 -0
- package/dist/transcript.js +32 -0
- package/package.json +1 -1
- package/src/Chat.tsx +58 -9
- package/src/client-context.ts +160 -0
- package/src/components/ChatMessages.tsx +26 -0
- package/src/components/CopyTranscriptButton.tsx +58 -0
- package/src/components/DiffCard.tsx +252 -0
- package/src/components/index.ts +1 -0
- package/src/diff.ts +159 -0
- package/src/index.ts +39 -1
- package/src/transcript.ts +41 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.0] - 2026-03-25
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- add DiffCard component and toolRenderers prop for custom tool rendering
|
|
7
|
+
|
|
8
|
+
## [0.5.1] - 2026-03-24
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- add metadata prop to Chat component for client context injection
|
|
12
|
+
|
|
3
13
|
## [0.5.0] - 2026-03-24
|
|
4
14
|
|
|
5
15
|
### Added
|
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ Override CSS custom properties on `.ec-chat` to match your design system:
|
|
|
68
68
|
## Consumers
|
|
69
69
|
|
|
70
70
|
- **extrachill-studio** — Studio Chat tab (agent_id=5)
|
|
71
|
-
- **extrachill-
|
|
71
|
+
- **extrachill-roadie** — Portable floating agent chat
|
|
72
72
|
- **data-machine** — Admin chat sidebar
|
|
73
73
|
|
|
74
74
|
## License
|
package/css/chat.css
CHANGED
|
@@ -79,6 +79,35 @@
|
|
|
79
79
|
height: 100%;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
.ec-chat__actions {
|
|
83
|
+
display: flex;
|
|
84
|
+
justify-content: flex-end;
|
|
85
|
+
padding: 8px 16px 0;
|
|
86
|
+
gap: 8px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.ec-chat-copy {
|
|
90
|
+
appearance: none;
|
|
91
|
+
border: 1px solid var(--ec-chat-border);
|
|
92
|
+
background: var(--ec-chat-input-bg);
|
|
93
|
+
color: var(--ec-chat-text);
|
|
94
|
+
border-radius: 999px;
|
|
95
|
+
padding: 6px 12px;
|
|
96
|
+
font: inherit;
|
|
97
|
+
font-size: 12px;
|
|
98
|
+
cursor: pointer;
|
|
99
|
+
transition: background-color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.ec-chat-copy:hover:not(:disabled) {
|
|
103
|
+
background: var(--ec-chat-session-hover-bg);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.ec-chat-copy:disabled {
|
|
107
|
+
opacity: 0.5;
|
|
108
|
+
cursor: not-allowed;
|
|
109
|
+
}
|
|
110
|
+
|
|
82
111
|
/* ============================================
|
|
83
112
|
Chat Messages Container
|
|
84
113
|
============================================ */
|
|
@@ -170,10 +199,16 @@
|
|
|
170
199
|
.ec-chat-message__bubble h2 { font-size: 1.15em; }
|
|
171
200
|
.ec-chat-message__bubble h3 { font-size: 1.05em; }
|
|
172
201
|
|
|
173
|
-
.ec-chat-message__bubble ul
|
|
202
|
+
.ec-chat-message__bubble ul {
|
|
203
|
+
margin: 6px 0;
|
|
204
|
+
padding-left: 1.5em;
|
|
205
|
+
list-style: disc;
|
|
206
|
+
}
|
|
207
|
+
|
|
174
208
|
.ec-chat-message__bubble ol {
|
|
175
209
|
margin: 6px 0;
|
|
176
210
|
padding-left: 1.5em;
|
|
211
|
+
list-style: decimal;
|
|
177
212
|
}
|
|
178
213
|
|
|
179
214
|
.ec-chat-message__bubble li {
|
|
@@ -820,3 +855,151 @@
|
|
|
820
855
|
top: -4px;
|
|
821
856
|
right: -4px;
|
|
822
857
|
}
|
|
858
|
+
|
|
859
|
+
/* ============================================
|
|
860
|
+
Diff Card
|
|
861
|
+
============================================ */
|
|
862
|
+
|
|
863
|
+
.ec-chat-diff {
|
|
864
|
+
--ec-chat-diff-added-bg: #dcfce7;
|
|
865
|
+
--ec-chat-diff-added-text: #166534;
|
|
866
|
+
--ec-chat-diff-removed-bg: #fee2e2;
|
|
867
|
+
--ec-chat-diff-removed-text: #991b1b;
|
|
868
|
+
--ec-chat-diff-accept-bg: #16a34a;
|
|
869
|
+
--ec-chat-diff-accept-text: #ffffff;
|
|
870
|
+
--ec-chat-diff-reject-bg: transparent;
|
|
871
|
+
--ec-chat-diff-reject-text: var(--ec-chat-text-muted);
|
|
872
|
+
--ec-chat-diff-reject-border: var(--ec-chat-border);
|
|
873
|
+
|
|
874
|
+
margin: 4px 0;
|
|
875
|
+
border: 1px solid var(--ec-chat-tool-border);
|
|
876
|
+
border-radius: 8px;
|
|
877
|
+
background: var(--ec-chat-tool-bg);
|
|
878
|
+
overflow: hidden;
|
|
879
|
+
font-size: 13px;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
.ec-chat-diff--accepted {
|
|
883
|
+
border-color: var(--ec-chat-diff-accept-bg);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
.ec-chat-diff--rejected {
|
|
887
|
+
opacity: 0.6;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
.ec-chat-diff__header {
|
|
891
|
+
display: flex;
|
|
892
|
+
align-items: center;
|
|
893
|
+
gap: 8px;
|
|
894
|
+
padding: 8px 12px;
|
|
895
|
+
border-bottom: 1px solid var(--ec-chat-tool-border);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
.ec-chat-diff__icon {
|
|
899
|
+
flex-shrink: 0;
|
|
900
|
+
font-size: 14px;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
.ec-chat-diff--accepted .ec-chat-diff__icon {
|
|
904
|
+
color: var(--ec-chat-diff-accept-bg);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
.ec-chat-diff--rejected .ec-chat-diff__icon {
|
|
908
|
+
color: var(--ec-chat-tool-error);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
.ec-chat-diff__label {
|
|
912
|
+
flex: 1;
|
|
913
|
+
font-weight: 500;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
.ec-chat-diff__status {
|
|
917
|
+
font-size: 11px;
|
|
918
|
+
font-weight: 600;
|
|
919
|
+
text-transform: uppercase;
|
|
920
|
+
letter-spacing: 0.05em;
|
|
921
|
+
color: var(--ec-chat-text-muted);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
.ec-chat-diff__meta {
|
|
925
|
+
padding: 0 12px 8px;
|
|
926
|
+
font-size: 12px;
|
|
927
|
+
color: var(--ec-chat-text-muted);
|
|
928
|
+
border-bottom: 1px solid var(--ec-chat-tool-border);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
.ec-chat-diff__content {
|
|
932
|
+
padding: 10px 12px;
|
|
933
|
+
line-height: 1.6;
|
|
934
|
+
white-space: pre-wrap;
|
|
935
|
+
word-wrap: break-word;
|
|
936
|
+
overflow-wrap: break-word;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
.ec-chat-diff__added {
|
|
940
|
+
background: var(--ec-chat-diff-added-bg);
|
|
941
|
+
color: var(--ec-chat-diff-added-text);
|
|
942
|
+
text-decoration: none;
|
|
943
|
+
border-radius: 2px;
|
|
944
|
+
padding: 1px 2px;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
.ec-chat-diff__removed {
|
|
948
|
+
background: var(--ec-chat-diff-removed-bg);
|
|
949
|
+
color: var(--ec-chat-diff-removed-text);
|
|
950
|
+
text-decoration: line-through;
|
|
951
|
+
border-radius: 2px;
|
|
952
|
+
padding: 1px 2px;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
.ec-chat-diff__actions {
|
|
956
|
+
display: flex;
|
|
957
|
+
gap: 8px;
|
|
958
|
+
padding: 8px 12px;
|
|
959
|
+
border-top: 1px solid var(--ec-chat-tool-border);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
.ec-chat-diff__accept {
|
|
963
|
+
padding: 6px 16px;
|
|
964
|
+
border: none;
|
|
965
|
+
border-radius: 6px;
|
|
966
|
+
background: var(--ec-chat-diff-accept-bg);
|
|
967
|
+
color: var(--ec-chat-diff-accept-text);
|
|
968
|
+
font-family: inherit;
|
|
969
|
+
font-size: 13px;
|
|
970
|
+
font-weight: 500;
|
|
971
|
+
cursor: pointer;
|
|
972
|
+
transition: opacity 0.15s;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
.ec-chat-diff__accept:hover:not(:disabled) {
|
|
976
|
+
opacity: 0.9;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
.ec-chat-diff__accept:disabled {
|
|
980
|
+
opacity: 0.5;
|
|
981
|
+
cursor: not-allowed;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
.ec-chat-diff__reject {
|
|
985
|
+
padding: 6px 16px;
|
|
986
|
+
border: 1px solid var(--ec-chat-diff-reject-border);
|
|
987
|
+
border-radius: 6px;
|
|
988
|
+
background: var(--ec-chat-diff-reject-bg);
|
|
989
|
+
color: var(--ec-chat-diff-reject-text);
|
|
990
|
+
font-family: inherit;
|
|
991
|
+
font-size: 13px;
|
|
992
|
+
font-weight: 500;
|
|
993
|
+
cursor: pointer;
|
|
994
|
+
transition: background 0.15s, color 0.15s;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
.ec-chat-diff__reject:hover:not(:disabled) {
|
|
998
|
+
background: var(--ec-chat-diff-removed-bg);
|
|
999
|
+
color: var(--ec-chat-diff-removed-text);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
.ec-chat-diff__reject:disabled {
|
|
1003
|
+
opacity: 0.5;
|
|
1004
|
+
cursor: not-allowed;
|
|
1005
|
+
}
|
package/dist/Chat.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import type { ChatMessage as ChatMessageType, ContentFormat } from './types/index.ts';
|
|
3
3
|
import type { FetchFn } from './api.ts';
|
|
4
|
+
import type { ToolGroup } from './components/ToolMessage.tsx';
|
|
4
5
|
import { type UseChatOptions } from './hooks/useChat.ts';
|
|
6
|
+
import type { UseChatReturn } from './hooks/useChat.ts';
|
|
7
|
+
export type ChatSessionUi = 'list' | 'none';
|
|
5
8
|
export interface ChatProps {
|
|
6
9
|
/**
|
|
7
10
|
* Base path for the chat REST endpoints.
|
|
@@ -25,6 +28,14 @@ export interface ChatProps {
|
|
|
25
28
|
showTools?: boolean;
|
|
26
29
|
/** Map of tool function names to friendly display labels. */
|
|
27
30
|
toolNames?: Record<string, string>;
|
|
31
|
+
/**
|
|
32
|
+
* Custom renderers for specific tool names.
|
|
33
|
+
*
|
|
34
|
+
* Map tool function names to React render functions. When a tool group
|
|
35
|
+
* matches a registered name, the custom renderer is used instead of
|
|
36
|
+
* the default ToolMessage JSON display.
|
|
37
|
+
*/
|
|
38
|
+
toolRenderers?: Record<string, (group: ToolGroup) => ReactNode>;
|
|
28
39
|
/** Placeholder text for the input. */
|
|
29
40
|
placeholder?: string;
|
|
30
41
|
/** Content shown when conversation is empty. */
|
|
@@ -43,12 +54,27 @@ export interface ChatProps {
|
|
|
43
54
|
className?: string;
|
|
44
55
|
/** Whether to show the session switcher. Defaults to true. */
|
|
45
56
|
showSessions?: boolean;
|
|
57
|
+
/** Session UI mode. 'list' renders the built-in switcher, 'none' lets the consumer render its own. */
|
|
58
|
+
sessionUi?: ChatSessionUi;
|
|
46
59
|
/** Label shown during multi-turn processing. */
|
|
47
60
|
processingLabel?: (turnCount: number) => string;
|
|
48
61
|
/** Whether to show the attachment button in the input. Defaults to true. */
|
|
49
62
|
allowAttachments?: boolean;
|
|
50
63
|
/** Accepted file types for attachments. Defaults to 'image/*,video/*'. */
|
|
51
64
|
acceptFileTypes?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Arbitrary metadata forwarded to the backend with each message.
|
|
67
|
+
* Use for client-side context injection (e.g. `{ client_context: { tab: 'compose', postId: 123 } }`).
|
|
68
|
+
*/
|
|
69
|
+
metadata?: Record<string, unknown>;
|
|
70
|
+
/** Whether to show a built-in copy transcript button. Defaults to false. */
|
|
71
|
+
showCopyTranscript?: boolean;
|
|
72
|
+
/** Label for the built-in copy transcript button. */
|
|
73
|
+
copyTranscriptLabel?: string;
|
|
74
|
+
/** Label shown after the transcript is copied. */
|
|
75
|
+
copyTranscriptCopiedLabel?: string;
|
|
76
|
+
/** Optional custom header/actions area rendered above messages with live chat state. */
|
|
77
|
+
renderHeader?: (chat: UseChatReturn) => ReactNode;
|
|
52
78
|
}
|
|
53
79
|
/**
|
|
54
80
|
* Ready-to-use chat component.
|
|
@@ -73,5 +99,5 @@ export interface ChatProps {
|
|
|
73
99
|
* }
|
|
74
100
|
* ```
|
|
75
101
|
*/
|
|
76
|
-
export declare function Chat({ basePath, fetchFn, agentId, contentFormat, renderContent, showTools, toolNames, placeholder, emptyState, initialMessages, initialSessionId, maxContinueTurns, onError, onMessage, className, showSessions, processingLabel, allowAttachments, acceptFileTypes, }: ChatProps): import("react/jsx-runtime").JSX.Element;
|
|
102
|
+
export declare function Chat({ basePath, fetchFn, agentId, contentFormat, renderContent, showTools, toolNames, toolRenderers, placeholder, emptyState, initialMessages, initialSessionId, maxContinueTurns, onError, onMessage, className, showSessions, sessionUi, processingLabel, allowAttachments, acceptFileTypes, metadata, showCopyTranscript, copyTranscriptLabel, copyTranscriptCopiedLabel, renderHeader, }: ChatProps): import("react/jsx-runtime").JSX.Element;
|
|
77
103
|
//# sourceMappingURL=Chat.d.ts.map
|
package/dist/Chat.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../src/Chat.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,IAAI,eAAe,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAW,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAQlE,MAAM,WAAW,SAAS;IACzB;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,4CAA4C;IAC5C,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC;IAC9E,sEAAsE;IACtE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,+CAA+C;IAC/C,eAAe,CAAC,EAAE,eAAe,EAAE,CAAC;IACpC,0BAA0B;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kCAAkC;IAClC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mCAAmC;IACnC,OAAO,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;IACpC,0CAA0C;IAC1C,SAAS,CAAC,EAAE,cAAc,CAAC,WAAW,CAAC,CAAC;IACxC,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gDAAgD;IAChD,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;IAChD,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,0EAA0E;IAC1E,eAAe,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../src/Chat.tsx"],"names":[],"mappings":"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"}
|
package/dist/Chat.js
CHANGED
|
@@ -6,6 +6,7 @@ import { ChatMessages } from "./components/ChatMessages.js";
|
|
|
6
6
|
import { ChatInput } from "./components/ChatInput.js";
|
|
7
7
|
import { TypingIndicator } from "./components/TypingIndicator.js";
|
|
8
8
|
import { SessionSwitcher } from "./components/SessionSwitcher.js";
|
|
9
|
+
import { CopyTranscriptButton } from "./components/CopyTranscriptButton.js";
|
|
9
10
|
/**
|
|
10
11
|
* Ready-to-use chat component.
|
|
11
12
|
*
|
|
@@ -29,7 +30,7 @@ import { SessionSwitcher } from "./components/SessionSwitcher.js";
|
|
|
29
30
|
* }
|
|
30
31
|
* ```
|
|
31
32
|
*/
|
|
32
|
-
export function Chat({ basePath, fetchFn, agentId, contentFormat = 'markdown', renderContent, showTools = true, toolNames, placeholder, emptyState, initialMessages, initialSessionId, maxContinueTurns, onError, onMessage, className, showSessions = true, processingLabel, allowAttachments = true, acceptFileTypes, }) {
|
|
33
|
+
export function Chat({ basePath, fetchFn, agentId, contentFormat = 'markdown', renderContent, showTools = true, toolNames, toolRenderers, placeholder, emptyState, initialMessages, initialSessionId, maxContinueTurns, onError, onMessage, className, showSessions = true, sessionUi = 'list', processingLabel, allowAttachments = true, acceptFileTypes, metadata, showCopyTranscript = false, copyTranscriptLabel, copyTranscriptCopiedLabel, renderHeader, }) {
|
|
33
34
|
const chat = useChat({
|
|
34
35
|
basePath,
|
|
35
36
|
fetchFn,
|
|
@@ -39,10 +40,11 @@ export function Chat({ basePath, fetchFn, agentId, contentFormat = 'markdown', r
|
|
|
39
40
|
maxContinueTurns,
|
|
40
41
|
onError,
|
|
41
42
|
onMessage,
|
|
43
|
+
metadata,
|
|
42
44
|
});
|
|
43
45
|
const baseClass = 'ec-chat';
|
|
44
46
|
const classes = [baseClass, className].filter(Boolean).join(' ');
|
|
45
|
-
return (_jsx(ErrorBoundary, { onError: onError ? (err) => onError(err) : undefined, children: _jsx("div", { className: classes, children: _jsxs(AvailabilityGate, { availability: chat.availability, children: [showSessions && (_jsx(SessionSwitcher, { sessions: chat.sessions, activeSessionId: chat.sessionId ?? undefined, onSelect: chat.switchSession, onNew: chat.newSession, onDelete: chat.deleteSession, loading: chat.sessionsLoading })), _jsx(ChatMessages, { messages: chat.messages, contentFormat: contentFormat, renderContent: renderContent, showTools: showTools, toolNames: toolNames, emptyState: emptyState }), _jsx(TypingIndicator, { visible: chat.isLoading, label: chat.turnCount > 0
|
|
47
|
+
return (_jsx(ErrorBoundary, { onError: onError ? (err) => onError(err) : undefined, children: _jsx("div", { className: classes, children: _jsxs(AvailabilityGate, { availability: chat.availability, children: [renderHeader?.(chat), showCopyTranscript && (_jsx("div", { className: "ec-chat__actions", children: _jsx(CopyTranscriptButton, { messages: chat.messages, label: copyTranscriptLabel, copiedLabel: copyTranscriptCopiedLabel }) })), showSessions && sessionUi === 'list' && (_jsx(SessionSwitcher, { sessions: chat.sessions, activeSessionId: chat.sessionId ?? undefined, onSelect: chat.switchSession, onNew: chat.newSession, onDelete: chat.deleteSession, loading: chat.sessionsLoading })), _jsx(ChatMessages, { messages: chat.messages, contentFormat: contentFormat, renderContent: renderContent, showTools: showTools, toolNames: toolNames, toolRenderers: toolRenderers, emptyState: emptyState }), _jsx(TypingIndicator, { visible: chat.isLoading, label: chat.turnCount > 0
|
|
46
48
|
? (processingLabel
|
|
47
49
|
? processingLabel(chat.turnCount)
|
|
48
50
|
: `Processing turn ${chat.turnCount}...`)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface ClientContextProvider {
|
|
2
|
+
id: string;
|
|
3
|
+
priority?: number;
|
|
4
|
+
getContext: () => Record<string, unknown> | null;
|
|
5
|
+
}
|
|
6
|
+
export interface ClientContextProviderSnapshot {
|
|
7
|
+
id: string;
|
|
8
|
+
priority: number;
|
|
9
|
+
context: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
export interface ClientContextSnapshot {
|
|
12
|
+
activeContext: Record<string, unknown> | null;
|
|
13
|
+
providers: ClientContextProviderSnapshot[];
|
|
14
|
+
}
|
|
15
|
+
export interface ClientContextRegistry {
|
|
16
|
+
registerProvider: (provider: ClientContextProvider) => () => void;
|
|
17
|
+
unregisterProvider: (id: string) => void;
|
|
18
|
+
subscribe: (listener: () => void) => () => void;
|
|
19
|
+
notify: () => void;
|
|
20
|
+
getSnapshot: () => ClientContextSnapshot;
|
|
21
|
+
}
|
|
22
|
+
declare global {
|
|
23
|
+
interface Window {
|
|
24
|
+
ecClientContextRegistry?: ClientContextRegistry;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export declare function getOrCreateClientContextRegistry(): ClientContextRegistry;
|
|
28
|
+
export declare function registerClientContextProvider(provider: ClientContextProvider): () => void;
|
|
29
|
+
export declare function getClientContextMetadata(): Record<string, unknown>;
|
|
30
|
+
export declare function useClientContextMetadata(): Record<string, unknown>;
|
|
31
|
+
//# sourceMappingURL=client-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-context.d.ts","sourceRoot":"","sources":["../src/client-context.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,qBAAqB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,MAAM,CAAE,MAAM,EAAE,OAAO,CAAE,GAAG,IAAI,CAAC;CACnD;AAED,MAAM,WAAW,6BAA6B;IAC7C,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAE,MAAM,EAAE,OAAO,CAAE,CAAC;CACnC;AAED,MAAM,WAAW,qBAAqB;IACrC,aAAa,EAAE,MAAM,CAAE,MAAM,EAAE,OAAO,CAAE,GAAG,IAAI,CAAC;IAChD,SAAS,EAAE,6BAA6B,EAAE,CAAC;CAC3C;AAED,MAAM,WAAW,qBAAqB;IACrC,gBAAgB,EAAE,CAAE,QAAQ,EAAE,qBAAqB,KAAM,MAAM,IAAI,CAAC;IACpE,kBAAkB,EAAE,CAAE,EAAE,EAAE,MAAM,KAAM,IAAI,CAAC;IAC3C,SAAS,EAAE,CAAE,QAAQ,EAAE,MAAM,IAAI,KAAM,MAAM,IAAI,CAAC;IAClD,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,WAAW,EAAE,MAAM,qBAAqB,CAAC;CACzC;AAED,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,MAAM;QACf,uBAAuB,CAAC,EAAE,qBAAqB,CAAC;KAChD;CACD;AAED,wBAAgB,gCAAgC,IAAI,qBAAqB,CAgFxE;AAED,wBAAgB,6BAA6B,CAAE,QAAQ,EAAE,qBAAqB,GAAI,MAAM,IAAI,CAE3F;AAED,wBAAgB,wBAAwB,IAAI,MAAM,CAAE,MAAM,EAAE,OAAO,CAAE,CAqBpE;AAED,wBAAgB,wBAAwB,IAAI,MAAM,CAAE,MAAM,EAAE,OAAO,CAAE,CAiBpE"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
export function getOrCreateClientContextRegistry() {
|
|
3
|
+
if (typeof window === 'undefined') {
|
|
4
|
+
return {
|
|
5
|
+
registerProvider: () => () => undefined,
|
|
6
|
+
unregisterProvider: () => undefined,
|
|
7
|
+
subscribe: () => () => undefined,
|
|
8
|
+
notify: () => undefined,
|
|
9
|
+
getSnapshot: () => ({
|
|
10
|
+
activeContext: null,
|
|
11
|
+
providers: [],
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
if (window.ecClientContextRegistry) {
|
|
16
|
+
return window.ecClientContextRegistry;
|
|
17
|
+
}
|
|
18
|
+
const providers = new Map();
|
|
19
|
+
const listeners = new Set();
|
|
20
|
+
const registry = {
|
|
21
|
+
registerProvider(provider) {
|
|
22
|
+
providers.set(provider.id, provider);
|
|
23
|
+
registry.notify();
|
|
24
|
+
return () => {
|
|
25
|
+
registry.unregisterProvider(provider.id);
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
unregisterProvider(id) {
|
|
29
|
+
if (providers.delete(id)) {
|
|
30
|
+
registry.notify();
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
subscribe(listener) {
|
|
34
|
+
listeners.add(listener);
|
|
35
|
+
return () => {
|
|
36
|
+
listeners.delete(listener);
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
notify() {
|
|
40
|
+
listeners.forEach((listener) => listener());
|
|
41
|
+
},
|
|
42
|
+
getSnapshot() {
|
|
43
|
+
const providerSnapshots = Array.from(providers.values())
|
|
44
|
+
.map((provider) => {
|
|
45
|
+
const context = provider.getContext();
|
|
46
|
+
if (!context) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
id: provider.id,
|
|
51
|
+
priority: provider.priority ?? 0,
|
|
52
|
+
context,
|
|
53
|
+
};
|
|
54
|
+
})
|
|
55
|
+
.filter((provider) => Boolean(provider))
|
|
56
|
+
.sort((a, b) => b.priority - a.priority);
|
|
57
|
+
return {
|
|
58
|
+
activeContext: providerSnapshots[0]?.context ?? null,
|
|
59
|
+
providers: providerSnapshots,
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
window.ecClientContextRegistry = registry;
|
|
64
|
+
return registry;
|
|
65
|
+
}
|
|
66
|
+
export function registerClientContextProvider(provider) {
|
|
67
|
+
return getOrCreateClientContextRegistry().registerProvider(provider);
|
|
68
|
+
}
|
|
69
|
+
export function getClientContextMetadata() {
|
|
70
|
+
if (typeof window === 'undefined') {
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
const snapshot = getOrCreateClientContextRegistry().getSnapshot();
|
|
74
|
+
return {
|
|
75
|
+
client_context: {
|
|
76
|
+
site: window.location.hostname,
|
|
77
|
+
url: window.location.href,
|
|
78
|
+
path: window.location.pathname,
|
|
79
|
+
page_title: document.title,
|
|
80
|
+
active_context: snapshot.activeContext,
|
|
81
|
+
available_contexts: snapshot.providers.map((provider) => ({
|
|
82
|
+
id: provider.id,
|
|
83
|
+
priority: provider.priority,
|
|
84
|
+
context: provider.context,
|
|
85
|
+
})),
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export function useClientContextMetadata() {
|
|
90
|
+
const registry = useMemo(() => getOrCreateClientContextRegistry(), []);
|
|
91
|
+
const [metadata, setMetadata] = useState(() => getClientContextMetadata());
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
const sync = () => {
|
|
94
|
+
setMetadata(getClientContextMetadata());
|
|
95
|
+
};
|
|
96
|
+
sync();
|
|
97
|
+
return registry.subscribe(sync);
|
|
98
|
+
}, [registry]);
|
|
99
|
+
return metadata;
|
|
100
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import type { ChatMessage as ChatMessageType, ContentFormat } from '../types/index.ts';
|
|
3
|
+
import { type ToolGroup } from './ToolMessage.tsx';
|
|
3
4
|
export interface ChatMessagesProps {
|
|
4
5
|
/** All messages in the conversation. */
|
|
5
6
|
messages: ChatMessageType[];
|
|
@@ -11,6 +12,22 @@ export interface ChatMessagesProps {
|
|
|
11
12
|
showTools?: boolean;
|
|
12
13
|
/** Custom tool name display map. Maps tool function names to friendly labels. */
|
|
13
14
|
toolNames?: Record<string, string>;
|
|
15
|
+
/**
|
|
16
|
+
* Custom renderers for specific tool names.
|
|
17
|
+
*
|
|
18
|
+
* Map tool function names to React render functions. When a tool group
|
|
19
|
+
* matches a registered name, the custom renderer is used instead of
|
|
20
|
+
* the default `ToolMessage` JSON display.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* toolRenderers={{
|
|
25
|
+
* edit_post_blocks: (group) => <DiffCard diff={parseDiff(group)} />,
|
|
26
|
+
* replace_post_blocks: (group) => <DiffCard diff={parseDiff(group)} />,
|
|
27
|
+
* }}
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
toolRenderers?: Record<string, (group: ToolGroup) => ReactNode>;
|
|
14
31
|
/** Whether to auto-scroll to bottom on new messages. Defaults to true. */
|
|
15
32
|
autoScroll?: boolean;
|
|
16
33
|
/** Placeholder content shown when there are no messages. */
|
|
@@ -24,5 +41,5 @@ export interface ChatMessagesProps {
|
|
|
24
41
|
* Filters system messages, groups tool_call/tool_result pairs,
|
|
25
42
|
* and renders user/assistant messages as ChatMessage components.
|
|
26
43
|
*/
|
|
27
|
-
export declare function ChatMessages({ messages, contentFormat, renderContent, showTools, toolNames, autoScroll, emptyState, className, }: ChatMessagesProps): import("react/jsx-runtime").JSX.Element;
|
|
44
|
+
export declare function ChatMessages({ messages, contentFormat, renderContent, showTools, toolNames, toolRenderers, autoScroll, emptyState, className, }: ChatMessagesProps): import("react/jsx-runtime").JSX.Element;
|
|
28
45
|
//# sourceMappingURL=ChatMessages.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatMessages.d.ts","sourceRoot":"","sources":["../../src/components/ChatMessages.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,IAAI,eAAe,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"ChatMessages.d.ts","sourceRoot":"","sources":["../../src/components/ChatMessages.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAC1D,OAAO,KAAK,EAAE,WAAW,IAAI,eAAe,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvF,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEhE,MAAM,WAAW,iBAAiB;IACjC,wCAAwC;IACxC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,6DAA6D;IAC7D,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,6DAA6D;IAC7D,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC;IAC9E,oEAAoE;IACpE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,SAAS,CAAC,CAAC;IAChE,0EAA0E;IAC1E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAC5B,QAAQ,EACR,aAAa,EACb,aAAa,EACb,SAAiB,EACjB,SAAS,EACT,aAAa,EACb,UAAiB,EACjB,UAAU,EACV,SAAS,GACT,EAAE,iBAAiB,2CAmEnB"}
|
|
@@ -8,7 +8,7 @@ import { ToolMessage } from "./ToolMessage.js";
|
|
|
8
8
|
* Filters system messages, groups tool_call/tool_result pairs,
|
|
9
9
|
* and renders user/assistant messages as ChatMessage components.
|
|
10
10
|
*/
|
|
11
|
-
export function ChatMessages({ messages, contentFormat, renderContent, showTools = false, toolNames, autoScroll = true, emptyState, className, }) {
|
|
11
|
+
export function ChatMessages({ messages, contentFormat, renderContent, showTools = false, toolNames, toolRenderers, autoScroll = true, emptyState, className, }) {
|
|
12
12
|
const bottomRef = useRef(null);
|
|
13
13
|
const containerRef = useRef(null);
|
|
14
14
|
useEffect(() => {
|
|
@@ -32,6 +32,10 @@ export function ChatMessages({ messages, contentFormat, renderContent, showTools
|
|
|
32
32
|
return (_jsx(ChatMessage, { message: item.message, contentFormat: contentFormat, renderContent: renderContent }, item.message.id));
|
|
33
33
|
}
|
|
34
34
|
if (item.type === 'tool-group' && showTools) {
|
|
35
|
+
const customRenderer = toolRenderers?.[item.group.toolName];
|
|
36
|
+
if (customRenderer) {
|
|
37
|
+
return (_jsx("div", { children: customRenderer(item.group) }, item.group.callMessage.id));
|
|
38
|
+
}
|
|
35
39
|
return (_jsx(ToolMessage, { group: item.group, toolNames: toolNames }, item.group.callMessage.id));
|
|
36
40
|
}
|
|
37
41
|
return null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ChatMessage } from '../types/index.ts';
|
|
2
|
+
export interface CopyTranscriptButtonProps {
|
|
3
|
+
messages: ChatMessage[];
|
|
4
|
+
label?: string;
|
|
5
|
+
copiedLabel?: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
copyTimeoutMs?: number;
|
|
8
|
+
onCopy?: (markdown: string) => void;
|
|
9
|
+
onError?: (error: Error) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function CopyTranscriptButton({ messages, label, copiedLabel, className, copyTimeoutMs, onCopy, onError, }: CopyTranscriptButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
//# sourceMappingURL=CopyTranscriptButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CopyTranscriptButton.d.ts","sourceRoot":"","sources":["../../src/components/CopyTranscriptButton.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD,MAAM,WAAW,yBAAyB;IACzC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,CAAE,QAAQ,EAAE,MAAM,KAAM,IAAI,CAAC;IACtC,OAAO,CAAC,EAAE,CAAE,KAAK,EAAE,KAAK,KAAM,IAAI,CAAC;CACnC;AAED,wBAAgB,oBAAoB,CAAE,EACrC,QAAQ,EACR,KAAc,EACd,WAAuB,EACvB,SAAS,EACT,aAAoB,EACpB,MAAM,EACN,OAAO,GACP,EAAE,yBAAyB,2CAmC3B"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
import { copyChatAsMarkdown } from "../transcript.js";
|
|
4
|
+
export function CopyTranscriptButton({ messages, label = 'Copy', copiedLabel = 'Copied!', className, copyTimeoutMs = 2000, onCopy, onError, }) {
|
|
5
|
+
const [isCopied, setIsCopied] = useState(false);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
if (!isCopied) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
const timer = window.setTimeout(() => setIsCopied(false), copyTimeoutMs);
|
|
11
|
+
return () => window.clearTimeout(timer);
|
|
12
|
+
}, [isCopied, copyTimeoutMs]);
|
|
13
|
+
const handleClick = useCallback(async () => {
|
|
14
|
+
try {
|
|
15
|
+
const markdown = await copyChatAsMarkdown(messages);
|
|
16
|
+
setIsCopied(true);
|
|
17
|
+
onCopy?.(markdown);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
onError?.(error);
|
|
21
|
+
}
|
|
22
|
+
}, [messages, onCopy, onError]);
|
|
23
|
+
const baseClass = 'ec-chat-copy';
|
|
24
|
+
const classes = [baseClass, className].filter(Boolean).join(' ');
|
|
25
|
+
return (_jsx("button", { type: "button", className: classes, onClick: handleClick, disabled: messages.length === 0, children: isCopied ? copiedLabel : label }));
|
|
26
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { CanonicalDiffData } from '../diff.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Diff data returned by a content-editing tool in preview mode.
|
|
4
|
+
*/
|
|
5
|
+
export type DiffData = CanonicalDiffData;
|
|
6
|
+
export interface DiffCardProps {
|
|
7
|
+
/** The diff data to visualize. */
|
|
8
|
+
diff: DiffData;
|
|
9
|
+
/** Called when the user accepts the change. */
|
|
10
|
+
onAccept?: (diffId: string) => void;
|
|
11
|
+
/** Called when the user rejects the change. */
|
|
12
|
+
onReject?: (diffId: string) => void;
|
|
13
|
+
/** Whether the accept/reject action is in progress. */
|
|
14
|
+
loading?: boolean;
|
|
15
|
+
/** Additional CSS class name. */
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Portable diff visualization card.
|
|
20
|
+
*
|
|
21
|
+
* Renders a before/after comparison with word-level `<ins>` / `<del>` tags
|
|
22
|
+
* and Accept / Reject buttons. Pure React — no Gutenberg or WordPress
|
|
23
|
+
* dependencies. Works anywhere `@extrachill/chat` is consumed.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* <DiffCard
|
|
28
|
+
* diff={{
|
|
29
|
+
* diffId: 'abc123',
|
|
30
|
+
* diffType: 'edit',
|
|
31
|
+
* originalContent: 'Hello world',
|
|
32
|
+
* replacementContent: 'Hello universe',
|
|
33
|
+
* }}
|
|
34
|
+
* onAccept={(id) => apiFetch({ path: `/resolve/${id}`, method: 'POST', data: { action: 'accept' } })}
|
|
35
|
+
* onReject={(id) => apiFetch({ path: `/resolve/${id}`, method: 'POST', data: { action: 'reject' } })}
|
|
36
|
+
* />
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function DiffCard({ diff, onAccept, onReject, loading, className, }: DiffCardProps): import("react/jsx-runtime").JSX.Element;
|
|
40
|
+
//# sourceMappingURL=DiffCard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DiffCard.d.ts","sourceRoot":"","sources":["../../src/components/DiffCard.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,iBAAiB,CAAC;AAEzC,MAAM,WAAW,aAAa;IAC7B,kCAAkC;IAClC,IAAI,EAAE,QAAQ,CAAC;IACf,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,uDAAuD;IACvD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAID;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,QAAQ,CAAC,EACxB,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,OAAe,EACf,SAAS,GACT,EAAE,aAAa,2CAuEf"}
|