@autobe/ui 0.29.2 → 0.30.0-dev.20260315

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.
Files changed (151) hide show
  1. package/LICENSE +661 -661
  2. package/README.md +261 -0
  3. package/lib/components/AutoBeChatMain.js +5 -5
  4. package/lib/components/AutoBeChatMain.js.map +1 -1
  5. package/lib/components/AutoBeConfigModal.js +9 -9
  6. package/lib/components/AutoBeStatusModal.js +4 -4
  7. package/lib/components/AutoBeStatusModal.js.map +1 -1
  8. package/lib/components/AutoBeUserMessageMovie.d.ts +2 -2
  9. package/lib/components/common/ChatBubble.d.ts +2 -2
  10. package/lib/components/common/openai/OpenAIContent.d.ts +2 -2
  11. package/lib/components/common/openai/OpenAIContent.js.map +1 -1
  12. package/lib/components/common/openai/OpenAIUserAudioContent.js +1 -1
  13. package/lib/components/common/openai/OpenAIUserAudioContent.js.map +1 -1
  14. package/lib/components/common/openai/OpenAIUserFileContent.js +1 -1
  15. package/lib/components/common/openai/OpenAIUserFileContent.js.map +1 -1
  16. package/lib/components/common/openai/OpenAIUserImageContent.d.ts +2 -2
  17. package/lib/components/events/AutoBeCompleteEventMovie.d.ts +2 -2
  18. package/lib/components/events/AutoBeCompleteEventMovie.js +5 -5
  19. package/lib/components/events/AutoBeCompleteEventMovie.js.map +1 -1
  20. package/lib/components/events/AutoBeCorrectEventMovie.d.ts +2 -2
  21. package/lib/components/events/AutoBeCorrectEventMovie.js +4 -4
  22. package/lib/components/events/AutoBeCorrectEventMovie.js.map +1 -1
  23. package/lib/components/events/AutoBeEventMovie.js +38 -17
  24. package/lib/components/events/AutoBeEventMovie.js.map +1 -1
  25. package/lib/components/events/AutoBeProgressEventMovie.js +73 -13
  26. package/lib/components/events/AutoBeProgressEventMovie.js.map +1 -1
  27. package/lib/components/events/AutoBeScenarioEventMovie.d.ts +2 -2
  28. package/lib/components/events/AutoBeScenarioEventMovie.js +18 -5
  29. package/lib/components/events/AutoBeScenarioEventMovie.js.map +1 -1
  30. package/lib/components/events/AutoBeStartEventMovie.d.ts +2 -2
  31. package/lib/components/events/AutoBeStartEventMovie.js +2 -2
  32. package/lib/components/events/AutoBeStartEventMovie.js.map +1 -1
  33. package/lib/components/events/AutoBeValidateEventMovie.d.ts +2 -2
  34. package/lib/components/events/AutoBeValidateEventMovie.js +3 -11
  35. package/lib/components/events/AutoBeValidateEventMovie.js.map +1 -1
  36. package/lib/components/events/groups/CorrectEventGroup.d.ts +2 -2
  37. package/lib/components/events/groups/CorrectEventGroup.js +1 -1
  38. package/lib/components/events/groups/CorrectEventGroup.js.map +1 -1
  39. package/lib/components/events/groups/ValidateEventGroup.d.ts +2 -2
  40. package/lib/components/events/groups/ValidateEventGroup.js +1 -2
  41. package/lib/components/events/groups/ValidateEventGroup.js.map +1 -1
  42. package/lib/components/events/utils/eventGrouper.js +1 -2
  43. package/lib/components/events/utils/eventGrouper.js.map +1 -1
  44. package/lib/components/upload/AutoBeChatUploadBox.d.ts +3 -4
  45. package/lib/components/upload/AutoBeChatUploadBox.js +2 -1
  46. package/lib/components/upload/AutoBeChatUploadBox.js.map +1 -1
  47. package/lib/components/upload/AutoBeChatUploadSendButton.js +1 -1
  48. package/lib/components/upload/AutoBeChatUploadSendButton.js.map +1 -1
  49. package/lib/context/AutoBeAgentContext.d.ts +1 -3
  50. package/lib/context/AutoBeAgentContext.js +0 -4
  51. package/lib/context/AutoBeAgentContext.js.map +1 -1
  52. package/lib/hooks/useSessionStorage.d.ts +4 -0
  53. package/lib/hooks/useSessionStorage.js +16 -0
  54. package/lib/hooks/useSessionStorage.js.map +1 -0
  55. package/lib/index.d.ts +1 -0
  56. package/lib/index.js +1 -0
  57. package/lib/index.js.map +1 -1
  58. package/lib/strategy/AutoBeAgentSessionStorageStrategy.d.ts +10 -0
  59. package/lib/strategy/AutoBeAgentSessionStorageStrategy.js +117 -0
  60. package/lib/strategy/AutoBeAgentSessionStorageStrategy.js.map +1 -0
  61. package/lib/structure/AutoBeListener.js +91 -23
  62. package/lib/structure/AutoBeListener.js.map +1 -1
  63. package/lib/structure/AutoBeListenerState.d.ts +3 -3
  64. package/lib/structure/AutoBeListenerState.js +4 -4
  65. package/lib/structure/AutoBeListenerState.js.map +1 -1
  66. package/lib/structure/IAutoBeAgentSessionStorageStrategy.js +1 -1
  67. package/lib/structure/IAutoBeAgentSessionStorageStrategy.js.map +1 -1
  68. package/lib/utils/AutoBeFileUploader.d.ts +2 -2
  69. package/lib/utils/AutoBeFileUploader.js.map +1 -1
  70. package/package.json +3 -4
  71. package/src/components/AutoBeAssistantMessageMovie.tsx +22 -22
  72. package/src/components/AutoBeChatMain.tsx +376 -376
  73. package/src/components/AutoBeChatSidebar.tsx +414 -414
  74. package/src/components/AutoBeConfigButton.tsx +83 -83
  75. package/src/components/AutoBeConfigModal.tsx +443 -443
  76. package/src/components/AutoBeStatusButton.tsx +75 -75
  77. package/src/components/AutoBeStatusModal.tsx +486 -484
  78. package/src/components/AutoBeUserMessageMovie.tsx +27 -27
  79. package/src/components/common/ActionButton.tsx +205 -205
  80. package/src/components/common/ActionButtonGroup.tsx +80 -80
  81. package/src/components/common/AutoBeConfigInput.tsx +185 -185
  82. package/src/components/common/ChatBubble.tsx +119 -119
  83. package/src/components/common/Collapsible.tsx +95 -95
  84. package/src/components/common/CompactSessionIndicator.tsx +73 -73
  85. package/src/components/common/CompactSessionList.tsx +82 -82
  86. package/src/components/common/index.ts +8 -8
  87. package/src/components/common/openai/OpenAIContent.tsx +53 -53
  88. package/src/components/common/openai/OpenAIUserAudioContent.tsx +70 -70
  89. package/src/components/common/openai/OpenAIUserFileContent.tsx +76 -76
  90. package/src/components/common/openai/OpenAIUserImageContent.tsx +34 -34
  91. package/src/components/common/openai/OpenAIUserTextContent.tsx +15 -15
  92. package/src/components/common/openai/index.ts +5 -5
  93. package/src/components/events/AutoBeCompleteEventMovie.tsx +402 -402
  94. package/src/components/events/AutoBeCorrectEventMovie.tsx +354 -368
  95. package/src/components/events/AutoBeEventGroupMovie.tsx +18 -18
  96. package/src/components/events/AutoBeEventMovie.tsx +158 -139
  97. package/src/components/events/AutoBeProgressEventMovie.tsx +217 -157
  98. package/src/components/events/AutoBeScenarioEventMovie.tsx +135 -95
  99. package/src/components/events/AutoBeStartEventMovie.tsx +82 -82
  100. package/src/components/events/AutoBeValidateEventMovie.tsx +249 -286
  101. package/src/components/events/README.md +300 -300
  102. package/src/components/events/common/CollapsibleEventGroup.tsx +211 -211
  103. package/src/components/events/common/EventCard.tsx +61 -61
  104. package/src/components/events/common/EventContent.tsx +31 -31
  105. package/src/components/events/common/EventHeader.tsx +85 -85
  106. package/src/components/events/common/EventIcon.tsx +82 -82
  107. package/src/components/events/common/ProgressBar.tsx +64 -64
  108. package/src/components/events/common/index.ts +13 -13
  109. package/src/components/events/groups/CorrectEventGroup.tsx +183 -183
  110. package/src/components/events/groups/ValidateEventGroup.tsx +143 -146
  111. package/src/components/events/groups/index.ts +8 -8
  112. package/src/components/events/index.ts +16 -16
  113. package/src/components/events/utils/eventGrouper.tsx +116 -117
  114. package/src/components/events/utils/index.ts +1 -1
  115. package/src/components/index.ts +13 -13
  116. package/src/components/upload/AutoBeChatUploadBox.tsx +425 -424
  117. package/src/components/upload/AutoBeChatUploadSendButton.tsx +66 -66
  118. package/src/components/upload/AutoBeFileUploadBox.tsx +123 -123
  119. package/src/components/upload/AutoBeUploadConfig.ts +5 -5
  120. package/src/components/upload/AutoBeVoiceRecoderButton.tsx +100 -100
  121. package/src/components/upload/index.ts +5 -5
  122. package/src/constant/color.ts +28 -28
  123. package/src/context/AutoBeAgentContext.tsx +245 -258
  124. package/src/context/AutoBeAgentSessionList.tsx +58 -58
  125. package/src/context/SearchParamsContext.tsx +49 -49
  126. package/src/hooks/index.ts +3 -3
  127. package/src/hooks/useEscapeKey.ts +24 -24
  128. package/src/hooks/useIsomorphicLayoutEffect.ts +8 -8
  129. package/src/hooks/useMediaQuery.ts +73 -73
  130. package/src/hooks/useSessionStorage.ts +10 -0
  131. package/src/icons/Receipt.tsx +74 -74
  132. package/src/index.ts +9 -8
  133. package/src/strategy/AutoBeAgentSessionStorageStrategy.ts +127 -0
  134. package/src/structure/AutoBeListener.ts +373 -304
  135. package/src/structure/AutoBeListenerState.ts +53 -53
  136. package/src/structure/IAutoBeAgentSessionStorageStrategy.ts +87 -87
  137. package/src/structure/IAutoBeEventGroup.ts +6 -6
  138. package/src/structure/index.ts +4 -4
  139. package/src/types/config.ts +44 -44
  140. package/src/types/index.ts +1 -1
  141. package/src/utils/AutoBeFileUploader.ts +279 -279
  142. package/src/utils/AutoBeVoiceRecorder.ts +95 -95
  143. package/src/utils/__tests__/crypto.test.ts +286 -286
  144. package/src/utils/__tests__/storage.test.ts +229 -229
  145. package/src/utils/crypto.ts +95 -95
  146. package/src/utils/index.ts +6 -6
  147. package/src/utils/number.ts +17 -17
  148. package/src/utils/storage.ts +96 -96
  149. package/src/utils/time.ts +14 -14
  150. package/tsconfig.json +9 -9
  151. package/vitest.config.ts +15 -15
@@ -1,376 +1,376 @@
1
- import { AutoBeUserMessageContent } from "@autobe/interface";
2
- import { OverlayProvider, overlay } from "overlay-kit";
3
- import { RefObject, useEffect, useRef } from "react";
4
-
5
- import { AutoBeChatUploadBox, useAutoBeAgentSessionList } from "..";
6
- import { useAutoBeAgent } from "../context/AutoBeAgentContext";
7
- import { useMediaQuery } from "../hooks";
8
- import {
9
- DEFAULT_CONFIG,
10
- IAutoBeConfig,
11
- IAutoBePartialConfig,
12
- } from "../types/config";
13
- import { getEncryptedSessionStorage } from "../utils/storage";
14
- import AutoBeConfigButton from "./AutoBeConfigButton";
15
- import AutoBeConfigModal, { IConfigField } from "./AutoBeConfigModal";
16
- import AutoBeStatusButton from "./AutoBeStatusButton";
17
- import AutoBeEventGroupMovie from "./events/AutoBeEventGroupMovie";
18
-
19
- export interface IAutoBeChatMainProps {
20
- isUnusedConfig?: boolean;
21
- isReplay?: boolean;
22
- isMobile: boolean;
23
- setError: (error: Error) => void;
24
- className?: string;
25
- style?: React.CSSProperties;
26
- configFields?: IConfigField[];
27
-
28
- /** Additional required config fields beyond openApiKey */
29
- requiredFields?: string[];
30
- }
31
-
32
- export const AutoBeChatMain = (props: IAutoBeChatMainProps) => {
33
- const bodyContainerRef = useRef<HTMLDivElement>(null);
34
- const scrollAnchorRef = useRef<HTMLDivElement>(null);
35
- const { eventGroups, getAutoBeService, connectionStatus } = useAutoBeAgent();
36
- const { refreshSessionList } = useAutoBeAgentSessionList();
37
- const listener: RefObject<AutoBeChatUploadBox.IListener> = useRef({
38
- handleDragEnter: () => {},
39
- handleDragLeave: () => {},
40
- handleDrop: () => {},
41
- handleDragOver: () => {},
42
- });
43
-
44
- // Simplified config reader
45
- const getCurrentConfig = (): IAutoBeConfig => {
46
- const config: IAutoBePartialConfig = {};
47
-
48
- props.configFields?.forEach((field) => {
49
- const value = field.encrypted
50
- ? getEncryptedSessionStorage(field.storageKey)
51
- : localStorage.getItem(field.storageKey) || "";
52
-
53
- if (field.type === "checkbox") {
54
- config[field.key] = String(value) === "true";
55
- } else if (field.type === "number") {
56
- config[field.key] = parseInt(String(value)) || 0;
57
- } else {
58
- config[field.key] = String(value);
59
- }
60
- });
61
-
62
- return { ...DEFAULT_CONFIG, ...config };
63
- };
64
-
65
- // Check if required config is available
66
- const hasRequiredConfig = (): boolean => {
67
- const config = getCurrentConfig();
68
-
69
- // Check additional required fields from props
70
- if (props.requiredFields) {
71
- for (const field of props.requiredFields) {
72
- if (!config[field]) {
73
- return false;
74
- }
75
- }
76
- }
77
-
78
- return true;
79
- };
80
-
81
- // Unified service connection handler
82
- const conversate = async (
83
- messages: AutoBeUserMessageContent[],
84
- ): Promise<void> => {
85
- // Check if we have required config
86
- if (props.isUnusedConfig === false && !hasRequiredConfig()) {
87
- overlay.open(({ isOpen, close }) => (
88
- <AutoBeConfigModal
89
- isOpen={isOpen}
90
- onClose={close}
91
- title="Server Connection Required"
92
- fields={props.configFields || []}
93
- onSave={() => {
94
- conversate(messages);
95
- }}
96
- />
97
- ));
98
- }
99
-
100
- // Connect to service
101
- try {
102
- const config = getCurrentConfig();
103
- const serviceData = await getAutoBeService(config);
104
- if (messages.length !== 0) {
105
- await new Promise((resolve) => {
106
- if (serviceData.listener.getEnable() === true) {
107
- resolve(void 0);
108
- }
109
- serviceData.listener.onEnable(async (value) => {
110
- if (value === true) {
111
- resolve(void 0);
112
- }
113
- });
114
- });
115
- await serviceData.service.conversate(messages);
116
- }
117
- if (eventGroups.length === 0) {
118
- refreshSessionList();
119
- }
120
- } catch (error) {
121
- console.error("Failed to connect:", error);
122
- props.setError(error as Error);
123
- }
124
- };
125
-
126
- // Auto-scroll when new events arrive
127
- useEffect(() => {
128
- if (eventGroups.length > 0) {
129
- scrollAnchorRef.current?.scrollIntoView({ behavior: "smooth" });
130
- }
131
- }, [eventGroups.length]);
132
-
133
- // Auto-connect if there are existing conversations and config is ready
134
- useEffect(() => {
135
- if (props.isReplay === true) {
136
- conversate([]);
137
- return;
138
- }
139
-
140
- if (
141
- eventGroups.length > 0 &&
142
- hasRequiredConfig() &&
143
- connectionStatus === "disconnected"
144
- ) {
145
- conversate([]);
146
- return;
147
- }
148
- }, [connectionStatus, eventGroups.length]);
149
-
150
- return (
151
- <OverlayProvider>
152
- <div
153
- onDragEnter={(e) => listener.current.handleDragEnter(e)}
154
- onDragLeave={(e) => listener.current.handleDragLeave(e)}
155
- onDragOver={(e) => listener.current.handleDragOver(e)}
156
- onDrop={(e) => listener.current.handleDrop(e)}
157
- style={{
158
- position: "relative",
159
- overflowY: "auto",
160
- margin: 0,
161
- flexGrow: 1,
162
- display: "flex",
163
- flexDirection: "column",
164
- ...props.style,
165
- }}
166
- className={props.className}
167
- ref={bodyContainerRef}
168
- >
169
- {/* Control Buttons & Status - Sticky position in top right */}
170
- <div
171
- style={{
172
- position: "sticky",
173
- top: "1rem",
174
- zIndex: 1001,
175
- display: "flex",
176
- justifyContent: "flex-end",
177
- alignItems: "center",
178
- gap: "0.5rem",
179
- marginBottom: "-3rem",
180
- paddingRight: "1.5rem",
181
- }}
182
- >
183
- {/* Connection Status Indicator */}
184
- {connectionStatus === "disconnected" && (
185
- <div
186
- style={{
187
- background: "#f8d7da",
188
- color: "#721c24",
189
- border: "1px solid #f5c6cb",
190
- borderRadius: "50px",
191
- padding: "0.4rem 0.8rem",
192
- fontSize: "0.8rem",
193
- fontWeight: "500",
194
- display: "flex",
195
- alignItems: "center",
196
- gap: "0.4rem",
197
- boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)",
198
- }}
199
- >
200
- <div
201
- style={{
202
- width: "6px",
203
- height: "6px",
204
- backgroundColor: "#dc3545",
205
- borderRadius: "50%",
206
- animation: "pulse 2s infinite",
207
- }}
208
- ></div>
209
- Disconnected
210
- </div>
211
- )}
212
-
213
- {connectionStatus === "connecting" && (
214
- <div
215
- style={{
216
- background: "#fff3cd",
217
- color: "#856404",
218
- border: "1px solid #ffeaa7",
219
- borderRadius: "50px",
220
- padding: "0.4rem 0.8rem",
221
- fontSize: "0.8rem",
222
- fontWeight: "500",
223
- display: "flex",
224
- alignItems: "center",
225
- gap: "0.4rem",
226
- boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)",
227
- }}
228
- >
229
- <div
230
- style={{
231
- width: "6px",
232
- height: "6px",
233
- backgroundColor: "#f39c12",
234
- borderRadius: "50%",
235
- animation: "pulse 1.5s infinite",
236
- }}
237
- ></div>
238
- Connecting...
239
- </div>
240
- )}
241
-
242
- {connectionStatus === "connected" && (
243
- <div
244
- style={{
245
- background: "#d4edda",
246
- color: "#155724",
247
- border: "1px solid #c3e6cb",
248
- borderRadius: "50px",
249
- padding: "0.4rem 0.8rem",
250
- fontSize: "0.8rem",
251
- fontWeight: "500",
252
- display: "flex",
253
- alignItems: "center",
254
- gap: "0.4rem",
255
- boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)",
256
- }}
257
- >
258
- <div
259
- style={{
260
- width: "6px",
261
- height: "6px",
262
- backgroundColor: "#28a745",
263
- borderRadius: "50%",
264
- animation: "pulse 1.5s infinite",
265
- }}
266
- ></div>
267
- Connected
268
- </div>
269
- )}
270
-
271
- <style>
272
- {`
273
- @keyframes pulse {
274
- 0%, 100% { opacity: 1; }
275
- 50% { opacity: 0.5; }
276
- }
277
- `}
278
- </style>
279
-
280
- {props.isUnusedConfig === false &&
281
- props.configFields?.length != null &&
282
- props.configFields.length > 0 && (
283
- <AutoBeConfigButton fields={props.configFields || []} />
284
- )}
285
- <AutoBeStatusButton />
286
- </div>
287
- <div
288
- style={{
289
- display: "flex",
290
- flexDirection: "column",
291
- maxWidth: useMediaQuery.WIDTH_MD,
292
- width: "100%",
293
- margin: "0 auto",
294
- }}
295
- >
296
- <div
297
- style={{
298
- padding: "2rem",
299
- gap: 16,
300
- display: "flex",
301
- flexDirection: "column",
302
- }}
303
- >
304
- {connectionStatus === "disconnected" && (
305
- <div
306
- style={{
307
- display: "flex",
308
- flexDirection: "column",
309
- justifyContent: "center",
310
- alignItems: "center",
311
- padding: "3rem",
312
- color: "#666",
313
- textAlign: "center",
314
- gap: "1rem",
315
- }}
316
- >
317
- <div style={{ fontSize: "3rem" }}>⚙️</div>
318
- <div style={{ fontSize: "1.25rem", fontWeight: "600" }}>
319
- Configuration Required
320
- </div>
321
- <div
322
- style={{
323
- fontSize: "1rem",
324
- maxWidth: "400px",
325
- lineHeight: "1.5",
326
- }}
327
- >
328
- Please click the settings button ⚙️ to configure your server
329
- connection and API credentials, or start typing to begin
330
- setup.
331
- </div>
332
- </div>
333
- )}
334
-
335
- {connectionStatus === "connected" && (
336
- <AutoBeEventGroupMovie eventGroups={eventGroups} />
337
- )}
338
- </div>
339
- </div>
340
-
341
- {/*
342
- * Prompt input area
343
- * this flexGrow: 1 means that the prompt input area will take up the remaining space
344
- * so that the upload box will be at the bottom of the screen
345
- */}
346
- <div
347
- style={{ flexGrow: 1, minHeight: "1rem" }}
348
- ref={scrollAnchorRef}
349
- ></div>
350
- <div
351
- style={{
352
- position: "sticky",
353
- bottom: 16,
354
- left: 0,
355
- right: 0,
356
- zIndex: 1000,
357
- }}
358
- >
359
- <AutoBeChatUploadBox
360
- listener={listener}
361
- uploadConfig={
362
- getCurrentConfig().supportAudioEnable
363
- ? {
364
- supportAudio: true,
365
- }
366
- : undefined
367
- }
368
- conversate={conversate}
369
- setError={props.setError}
370
- />
371
- </div>
372
- </div>
373
- </OverlayProvider>
374
- );
375
- };
376
- export default AutoBeChatMain;
1
+ import { AutoBeUserConversateContent } from "@autobe/interface";
2
+ import { OverlayProvider, overlay } from "overlay-kit";
3
+ import { RefObject, useEffect, useRef } from "react";
4
+
5
+ import { AutoBeChatUploadBox, useAutoBeAgentSessionList } from "..";
6
+ import { useAutoBeAgent } from "../context/AutoBeAgentContext";
7
+ import { useMediaQuery } from "../hooks";
8
+ import {
9
+ DEFAULT_CONFIG,
10
+ IAutoBeConfig,
11
+ IAutoBePartialConfig,
12
+ } from "../types/config";
13
+ import { getEncryptedSessionStorage } from "../utils/storage";
14
+ import AutoBeConfigButton from "./AutoBeConfigButton";
15
+ import AutoBeConfigModal, { IConfigField } from "./AutoBeConfigModal";
16
+ import AutoBeStatusButton from "./AutoBeStatusButton";
17
+ import AutoBeEventGroupMovie from "./events/AutoBeEventGroupMovie";
18
+
19
+ export interface IAutoBeChatMainProps {
20
+ isUnusedConfig?: boolean;
21
+ isReplay?: boolean;
22
+ isMobile: boolean;
23
+ setError: (error: Error) => void;
24
+ className?: string;
25
+ style?: React.CSSProperties;
26
+ configFields?: IConfigField[];
27
+
28
+ /** Additional required config fields beyond openApiKey */
29
+ requiredFields?: string[];
30
+ }
31
+
32
+ export const AutoBeChatMain = (props: IAutoBeChatMainProps) => {
33
+ const bodyContainerRef = useRef<HTMLDivElement>(null);
34
+ const scrollAnchorRef = useRef<HTMLDivElement>(null);
35
+ const { eventGroups, getAutoBeService, connectionStatus } = useAutoBeAgent();
36
+ const { refreshSessionList } = useAutoBeAgentSessionList();
37
+ const listener: RefObject<AutoBeChatUploadBox.IListener> = useRef({
38
+ handleDragEnter: () => {},
39
+ handleDragLeave: () => {},
40
+ handleDrop: () => {},
41
+ handleDragOver: () => {},
42
+ });
43
+
44
+ // Simplified config reader
45
+ const getCurrentConfig = (): IAutoBeConfig => {
46
+ const config: IAutoBePartialConfig = {};
47
+
48
+ props.configFields?.forEach((field) => {
49
+ const value = field.encrypted
50
+ ? getEncryptedSessionStorage(field.storageKey)
51
+ : localStorage.getItem(field.storageKey) || "";
52
+
53
+ if (field.type === "checkbox") {
54
+ config[field.key] = String(value) === "true";
55
+ } else if (field.type === "number") {
56
+ config[field.key] = parseInt(String(value)) || 0;
57
+ } else {
58
+ config[field.key] = String(value);
59
+ }
60
+ });
61
+
62
+ return { ...DEFAULT_CONFIG, ...config };
63
+ };
64
+
65
+ // Check if required config is available
66
+ const hasRequiredConfig = (): boolean => {
67
+ const config = getCurrentConfig();
68
+
69
+ // Check additional required fields from props
70
+ if (props.requiredFields) {
71
+ for (const field of props.requiredFields) {
72
+ if (!config[field]) {
73
+ return false;
74
+ }
75
+ }
76
+ }
77
+
78
+ return true;
79
+ };
80
+
81
+ // Unified service connection handler
82
+ const conversate = async (
83
+ messages: AutoBeUserConversateContent[],
84
+ ): Promise<void> => {
85
+ // Check if we have required config
86
+ if (props.isUnusedConfig === false && !hasRequiredConfig()) {
87
+ overlay.open(({ isOpen, close }) => (
88
+ <AutoBeConfigModal
89
+ isOpen={isOpen}
90
+ onClose={close}
91
+ title="Server Connection Required"
92
+ fields={props.configFields || []}
93
+ onSave={() => {
94
+ conversate(messages);
95
+ }}
96
+ />
97
+ ));
98
+ }
99
+
100
+ // Connect to service
101
+ try {
102
+ const config = getCurrentConfig();
103
+ const serviceData = await getAutoBeService(config);
104
+ if (messages.length !== 0) {
105
+ await new Promise((resolve) => {
106
+ if (serviceData.listener.getEnable() === true) {
107
+ resolve(void 0);
108
+ }
109
+ serviceData.listener.onEnable(async (value) => {
110
+ if (value === true) {
111
+ resolve(void 0);
112
+ }
113
+ });
114
+ });
115
+ await serviceData.service.conversate(messages);
116
+ }
117
+ if (eventGroups.length === 0) {
118
+ refreshSessionList();
119
+ }
120
+ } catch (error) {
121
+ console.error("Failed to connect:", error);
122
+ props.setError(error as Error);
123
+ }
124
+ };
125
+
126
+ // Auto-scroll when new events arrive
127
+ useEffect(() => {
128
+ if (eventGroups.length > 0) {
129
+ scrollAnchorRef.current?.scrollIntoView({ behavior: "smooth" });
130
+ }
131
+ }, [eventGroups.length]);
132
+
133
+ // Auto-connect if there are existing conversations and config is ready
134
+ useEffect(() => {
135
+ if (props.isReplay === true) {
136
+ conversate([]);
137
+ return;
138
+ }
139
+
140
+ if (
141
+ eventGroups.length > 0 &&
142
+ hasRequiredConfig() &&
143
+ connectionStatus === "disconnected"
144
+ ) {
145
+ conversate([]);
146
+ return;
147
+ }
148
+ }, [connectionStatus, eventGroups.length]);
149
+
150
+ return (
151
+ <OverlayProvider>
152
+ <div
153
+ onDragEnter={(e) => listener.current.handleDragEnter(e)}
154
+ onDragLeave={(e) => listener.current.handleDragLeave(e)}
155
+ onDragOver={(e) => listener.current.handleDragOver(e)}
156
+ onDrop={(e) => listener.current.handleDrop(e)}
157
+ style={{
158
+ position: "relative",
159
+ overflowY: "auto",
160
+ margin: 0,
161
+ flexGrow: 1,
162
+ display: "flex",
163
+ flexDirection: "column",
164
+ ...props.style,
165
+ }}
166
+ className={props.className}
167
+ ref={bodyContainerRef}
168
+ >
169
+ {/* Control Buttons & Status - Sticky position in top right */}
170
+ <div
171
+ style={{
172
+ position: "sticky",
173
+ top: "1rem",
174
+ zIndex: 1001,
175
+ display: "flex",
176
+ justifyContent: "flex-end",
177
+ alignItems: "center",
178
+ gap: "0.5rem",
179
+ marginBottom: "-3rem",
180
+ paddingRight: "1.5rem",
181
+ }}
182
+ >
183
+ {/* Connection Status Indicator */}
184
+ {connectionStatus === "disconnected" && (
185
+ <div
186
+ style={{
187
+ background: "#f8d7da",
188
+ color: "#721c24",
189
+ border: "1px solid #f5c6cb",
190
+ borderRadius: "50px",
191
+ padding: "0.4rem 0.8rem",
192
+ fontSize: "0.8rem",
193
+ fontWeight: "500",
194
+ display: "flex",
195
+ alignItems: "center",
196
+ gap: "0.4rem",
197
+ boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)",
198
+ }}
199
+ >
200
+ <div
201
+ style={{
202
+ width: "6px",
203
+ height: "6px",
204
+ backgroundColor: "#dc3545",
205
+ borderRadius: "50%",
206
+ animation: "pulse 2s infinite",
207
+ }}
208
+ ></div>
209
+ Disconnected
210
+ </div>
211
+ )}
212
+
213
+ {connectionStatus === "connecting" && (
214
+ <div
215
+ style={{
216
+ background: "#fff3cd",
217
+ color: "#856404",
218
+ border: "1px solid #ffeaa7",
219
+ borderRadius: "50px",
220
+ padding: "0.4rem 0.8rem",
221
+ fontSize: "0.8rem",
222
+ fontWeight: "500",
223
+ display: "flex",
224
+ alignItems: "center",
225
+ gap: "0.4rem",
226
+ boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)",
227
+ }}
228
+ >
229
+ <div
230
+ style={{
231
+ width: "6px",
232
+ height: "6px",
233
+ backgroundColor: "#f39c12",
234
+ borderRadius: "50%",
235
+ animation: "pulse 1.5s infinite",
236
+ }}
237
+ ></div>
238
+ Connecting...
239
+ </div>
240
+ )}
241
+
242
+ {connectionStatus === "connected" && (
243
+ <div
244
+ style={{
245
+ background: "#d4edda",
246
+ color: "#155724",
247
+ border: "1px solid #c3e6cb",
248
+ borderRadius: "50px",
249
+ padding: "0.4rem 0.8rem",
250
+ fontSize: "0.8rem",
251
+ fontWeight: "500",
252
+ display: "flex",
253
+ alignItems: "center",
254
+ gap: "0.4rem",
255
+ boxShadow: "0 2px 4px rgba(0, 0, 0, 0.1)",
256
+ }}
257
+ >
258
+ <div
259
+ style={{
260
+ width: "6px",
261
+ height: "6px",
262
+ backgroundColor: "#28a745",
263
+ borderRadius: "50%",
264
+ animation: "pulse 1.5s infinite",
265
+ }}
266
+ ></div>
267
+ Connected
268
+ </div>
269
+ )}
270
+
271
+ <style>
272
+ {`
273
+ @keyframes pulse {
274
+ 0%, 100% { opacity: 1; }
275
+ 50% { opacity: 0.5; }
276
+ }
277
+ `}
278
+ </style>
279
+
280
+ {props.isUnusedConfig === false &&
281
+ props.configFields?.length != null &&
282
+ props.configFields.length > 0 && (
283
+ <AutoBeConfigButton fields={props.configFields || []} />
284
+ )}
285
+ <AutoBeStatusButton />
286
+ </div>
287
+ <div
288
+ style={{
289
+ display: "flex",
290
+ flexDirection: "column",
291
+ maxWidth: useMediaQuery.WIDTH_MD,
292
+ width: "100%",
293
+ margin: "0 auto",
294
+ }}
295
+ >
296
+ <div
297
+ style={{
298
+ padding: "2rem",
299
+ gap: 16,
300
+ display: "flex",
301
+ flexDirection: "column",
302
+ }}
303
+ >
304
+ {connectionStatus === "disconnected" && (
305
+ <div
306
+ style={{
307
+ display: "flex",
308
+ flexDirection: "column",
309
+ justifyContent: "center",
310
+ alignItems: "center",
311
+ padding: "3rem",
312
+ color: "#666",
313
+ textAlign: "center",
314
+ gap: "1rem",
315
+ }}
316
+ >
317
+ <div style={{ fontSize: "3rem" }}>⚙️</div>
318
+ <div style={{ fontSize: "1.25rem", fontWeight: "600" }}>
319
+ Configuration Required
320
+ </div>
321
+ <div
322
+ style={{
323
+ fontSize: "1rem",
324
+ maxWidth: "400px",
325
+ lineHeight: "1.5",
326
+ }}
327
+ >
328
+ Please click the settings button ⚙️ to configure your server
329
+ connection and API credentials, or start typing to begin
330
+ setup.
331
+ </div>
332
+ </div>
333
+ )}
334
+
335
+ {connectionStatus === "connected" && (
336
+ <AutoBeEventGroupMovie eventGroups={eventGroups} />
337
+ )}
338
+ </div>
339
+ </div>
340
+
341
+ {/*
342
+ * Prompt input area
343
+ * this flexGrow: 1 means that the prompt input area will take up the remaining space
344
+ * so that the upload box will be at the bottom of the screen
345
+ */}
346
+ <div
347
+ style={{ flexGrow: 1, minHeight: "1rem" }}
348
+ ref={scrollAnchorRef}
349
+ ></div>
350
+ <div
351
+ style={{
352
+ position: "sticky",
353
+ bottom: 16,
354
+ left: 0,
355
+ right: 0,
356
+ zIndex: 1000,
357
+ }}
358
+ >
359
+ <AutoBeChatUploadBox
360
+ listener={listener}
361
+ uploadConfig={
362
+ getCurrentConfig().supportAudioEnable
363
+ ? {
364
+ supportAudio: true,
365
+ }
366
+ : undefined
367
+ }
368
+ conversate={conversate}
369
+ setError={props.setError}
370
+ />
371
+ </div>
372
+ </div>
373
+ </OverlayProvider>
374
+ );
375
+ };
376
+ export default AutoBeChatMain;