@autobe/ui 0.30.3 → 0.30.4-dev.20260324

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