@autobe/ui 0.30.0-dev.20260315 → 0.30.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/LICENSE +661 -661
- package/lib/components/AutoBeChatMain.js +5 -5
- package/lib/components/AutoBeConfigModal.js +9 -9
- package/package.json +2 -2
- package/src/components/AutoBeAssistantMessageMovie.tsx +22 -22
- package/src/components/AutoBeChatMain.tsx +376 -376
- package/src/components/AutoBeChatSidebar.tsx +414 -414
- package/src/components/AutoBeConfigButton.tsx +83 -83
- package/src/components/AutoBeConfigModal.tsx +443 -443
- package/src/components/AutoBeStatusButton.tsx +75 -75
- package/src/components/AutoBeStatusModal.tsx +486 -486
- package/src/components/AutoBeUserMessageMovie.tsx +27 -27
- package/src/components/common/ActionButton.tsx +205 -205
- package/src/components/common/ActionButtonGroup.tsx +80 -80
- package/src/components/common/AutoBeConfigInput.tsx +185 -185
- package/src/components/common/ChatBubble.tsx +119 -119
- package/src/components/common/Collapsible.tsx +95 -95
- package/src/components/common/CompactSessionIndicator.tsx +73 -73
- package/src/components/common/CompactSessionList.tsx +82 -82
- package/src/components/common/index.ts +8 -8
- package/src/components/common/openai/OpenAIContent.tsx +53 -53
- package/src/components/common/openai/OpenAIUserAudioContent.tsx +70 -70
- package/src/components/common/openai/OpenAIUserFileContent.tsx +76 -76
- package/src/components/common/openai/OpenAIUserImageContent.tsx +34 -34
- package/src/components/common/openai/OpenAIUserTextContent.tsx +15 -15
- package/src/components/common/openai/index.ts +5 -5
- package/src/components/events/AutoBeCompleteEventMovie.tsx +402 -402
- package/src/components/events/AutoBeCorrectEventMovie.tsx +354 -354
- package/src/components/events/AutoBeEventGroupMovie.tsx +18 -18
- package/src/components/events/AutoBeEventMovie.tsx +158 -158
- package/src/components/events/AutoBeProgressEventMovie.tsx +217 -217
- package/src/components/events/AutoBeScenarioEventMovie.tsx +135 -135
- package/src/components/events/AutoBeStartEventMovie.tsx +82 -82
- package/src/components/events/AutoBeValidateEventMovie.tsx +249 -249
- package/src/components/events/README.md +300 -300
- package/src/components/events/common/CollapsibleEventGroup.tsx +211 -211
- package/src/components/events/common/EventCard.tsx +61 -61
- package/src/components/events/common/EventContent.tsx +31 -31
- package/src/components/events/common/EventHeader.tsx +85 -85
- package/src/components/events/common/EventIcon.tsx +82 -82
- package/src/components/events/common/ProgressBar.tsx +64 -64
- package/src/components/events/common/index.ts +13 -13
- package/src/components/events/groups/CorrectEventGroup.tsx +183 -183
- package/src/components/events/groups/ValidateEventGroup.tsx +143 -143
- package/src/components/events/groups/index.ts +8 -8
- package/src/components/events/index.ts +16 -16
- package/src/components/events/utils/eventGrouper.tsx +116 -116
- package/src/components/events/utils/index.ts +1 -1
- package/src/components/index.ts +13 -13
- package/src/components/upload/AutoBeChatUploadBox.tsx +425 -425
- package/src/components/upload/AutoBeChatUploadSendButton.tsx +66 -66
- package/src/components/upload/AutoBeFileUploadBox.tsx +123 -123
- package/src/components/upload/AutoBeUploadConfig.ts +5 -5
- package/src/components/upload/AutoBeVoiceRecoderButton.tsx +100 -100
- package/src/components/upload/index.ts +5 -5
- package/src/constant/color.ts +28 -28
- package/src/context/AutoBeAgentContext.tsx +245 -245
- package/src/context/AutoBeAgentSessionList.tsx +58 -58
- package/src/context/SearchParamsContext.tsx +49 -49
- package/src/hooks/index.ts +3 -3
- package/src/hooks/useEscapeKey.ts +24 -24
- package/src/hooks/useIsomorphicLayoutEffect.ts +8 -8
- package/src/hooks/useMediaQuery.ts +73 -73
- package/src/hooks/useSessionStorage.ts +10 -10
- package/src/icons/Receipt.tsx +74 -74
- package/src/index.ts +9 -9
- package/src/strategy/AutoBeAgentSessionStorageStrategy.ts +127 -127
- package/src/structure/AutoBeListener.ts +373 -373
- package/src/structure/AutoBeListenerState.ts +53 -53
- package/src/structure/IAutoBeAgentSessionStorageStrategy.ts +87 -87
- package/src/structure/IAutoBeEventGroup.ts +6 -6
- package/src/structure/index.ts +4 -4
- package/src/types/config.ts +44 -44
- package/src/types/index.ts +1 -1
- package/src/utils/AutoBeFileUploader.ts +279 -279
- package/src/utils/AutoBeVoiceRecorder.ts +95 -95
- package/src/utils/__tests__/crypto.test.ts +286 -286
- package/src/utils/__tests__/storage.test.ts +229 -229
- package/src/utils/crypto.ts +95 -95
- package/src/utils/index.ts +6 -6
- package/src/utils/number.ts +17 -17
- package/src/utils/storage.ts +96 -96
- package/src/utils/time.ts +14 -14
- package/tsconfig.json +9 -9
- package/vitest.config.ts +15 -15
- package/README.md +0 -261
|
@@ -1,376 +1,376 @@
|
|
|
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 { 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;
|