@autobe/ui 0.30.4-dev.20260324 → 0.30.5

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 (53) hide show
  1. package/LICENSE +661 -661
  2. package/lib/components/AutoBeChatMain.js +5 -5
  3. package/lib/components/AutoBeConfigModal.js +9 -9
  4. package/package.json +2 -2
  5. package/src/components/AutoBeAssistantMessageMovie.tsx +22 -22
  6. package/src/components/AutoBeChatMain.tsx +394 -394
  7. package/src/components/AutoBeChatSidebar.tsx +414 -414
  8. package/src/components/AutoBeConfigButton.tsx +83 -83
  9. package/src/components/AutoBeConfigModal.tsx +443 -443
  10. package/src/components/AutoBeStatusButton.tsx +75 -75
  11. package/src/components/AutoBeStatusModal.tsx +486 -486
  12. package/src/components/AutoBeUserMessageMovie.tsx +27 -27
  13. package/src/components/common/ActionButton.tsx +205 -205
  14. package/src/components/common/ActionButtonGroup.tsx +80 -80
  15. package/src/components/common/AutoBeConfigInput.tsx +185 -185
  16. package/src/components/common/ChatBubble.tsx +119 -119
  17. package/src/components/common/Collapsible.tsx +95 -95
  18. package/src/components/common/CompactSessionIndicator.tsx +73 -73
  19. package/src/components/common/CompactSessionList.tsx +82 -82
  20. package/src/components/common/openai/OpenAIContent.tsx +53 -53
  21. package/src/components/common/openai/OpenAIUserAudioContent.tsx +70 -70
  22. package/src/components/common/openai/OpenAIUserFileContent.tsx +76 -76
  23. package/src/components/common/openai/OpenAIUserImageContent.tsx +34 -34
  24. package/src/components/common/openai/OpenAIUserTextContent.tsx +15 -15
  25. package/src/components/events/AutoBeCompleteEventMovie.tsx +402 -402
  26. package/src/components/events/AutoBeCorrectEventMovie.tsx +354 -354
  27. package/src/components/events/AutoBeEventGroupMovie.tsx +18 -18
  28. package/src/components/events/AutoBeEventMovie.tsx +154 -154
  29. package/src/components/events/AutoBeProgressEventMovie.tsx +207 -207
  30. package/src/components/events/AutoBeScenarioEventMovie.tsx +135 -135
  31. package/src/components/events/AutoBeStartEventMovie.tsx +82 -82
  32. package/src/components/events/AutoBeValidateEventMovie.tsx +249 -249
  33. package/src/components/events/README.md +300 -300
  34. package/src/components/events/common/CollapsibleEventGroup.tsx +211 -211
  35. package/src/components/events/common/EventCard.tsx +61 -61
  36. package/src/components/events/common/EventContent.tsx +31 -31
  37. package/src/components/events/common/EventHeader.tsx +85 -85
  38. package/src/components/events/common/EventIcon.tsx +82 -82
  39. package/src/components/events/common/ProgressBar.tsx +64 -64
  40. package/src/components/events/groups/CorrectEventGroup.tsx +183 -183
  41. package/src/components/events/groups/ValidateEventGroup.tsx +143 -143
  42. package/src/components/events/utils/eventGrouper.tsx +116 -116
  43. package/src/components/upload/AutoBeChatUploadBox.tsx +433 -433
  44. package/src/components/upload/AutoBeChatUploadSendButton.tsx +66 -66
  45. package/src/components/upload/AutoBeFileUploadBox.tsx +123 -123
  46. package/src/components/upload/AutoBeVoiceRecoderButton.tsx +100 -100
  47. package/src/context/AutoBeAgentContext.tsx +301 -301
  48. package/src/context/AutoBeAgentSessionList.tsx +58 -58
  49. package/src/context/SearchParamsContext.tsx +49 -49
  50. package/src/icons/Receipt.tsx +74 -74
  51. package/src/structure/AutoBeListener.ts +368 -368
  52. package/tsconfig.json +9 -9
  53. package/README.md +0 -261
@@ -1,394 +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 { 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;
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;