@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.
- package/LICENSE +661 -661
- package/README.md +261 -0
- package/lib/components/AutoBeChatMain.js +5 -5
- package/lib/components/AutoBeChatMain.js.map +1 -1
- package/lib/components/AutoBeConfigModal.js +9 -9
- package/lib/components/AutoBeStatusModal.js +4 -4
- package/lib/components/AutoBeStatusModal.js.map +1 -1
- package/lib/components/AutoBeUserMessageMovie.d.ts +2 -2
- package/lib/components/common/ChatBubble.d.ts +2 -2
- package/lib/components/common/openai/OpenAIContent.d.ts +2 -2
- package/lib/components/common/openai/OpenAIContent.js.map +1 -1
- package/lib/components/common/openai/OpenAIUserAudioContent.js +1 -1
- package/lib/components/common/openai/OpenAIUserAudioContent.js.map +1 -1
- package/lib/components/common/openai/OpenAIUserFileContent.js +1 -1
- package/lib/components/common/openai/OpenAIUserFileContent.js.map +1 -1
- package/lib/components/common/openai/OpenAIUserImageContent.d.ts +2 -2
- package/lib/components/events/AutoBeCompleteEventMovie.d.ts +2 -2
- package/lib/components/events/AutoBeCompleteEventMovie.js +5 -5
- package/lib/components/events/AutoBeCompleteEventMovie.js.map +1 -1
- package/lib/components/events/AutoBeCorrectEventMovie.d.ts +2 -2
- package/lib/components/events/AutoBeCorrectEventMovie.js +4 -4
- package/lib/components/events/AutoBeCorrectEventMovie.js.map +1 -1
- package/lib/components/events/AutoBeEventMovie.js +38 -17
- package/lib/components/events/AutoBeEventMovie.js.map +1 -1
- package/lib/components/events/AutoBeProgressEventMovie.js +73 -13
- package/lib/components/events/AutoBeProgressEventMovie.js.map +1 -1
- package/lib/components/events/AutoBeScenarioEventMovie.d.ts +2 -2
- package/lib/components/events/AutoBeScenarioEventMovie.js +18 -5
- package/lib/components/events/AutoBeScenarioEventMovie.js.map +1 -1
- package/lib/components/events/AutoBeStartEventMovie.d.ts +2 -2
- package/lib/components/events/AutoBeStartEventMovie.js +2 -2
- package/lib/components/events/AutoBeStartEventMovie.js.map +1 -1
- package/lib/components/events/AutoBeValidateEventMovie.d.ts +2 -2
- package/lib/components/events/AutoBeValidateEventMovie.js +3 -11
- package/lib/components/events/AutoBeValidateEventMovie.js.map +1 -1
- package/lib/components/events/groups/CorrectEventGroup.d.ts +2 -2
- package/lib/components/events/groups/CorrectEventGroup.js +1 -1
- package/lib/components/events/groups/CorrectEventGroup.js.map +1 -1
- package/lib/components/events/groups/ValidateEventGroup.d.ts +2 -2
- package/lib/components/events/groups/ValidateEventGroup.js +1 -2
- package/lib/components/events/groups/ValidateEventGroup.js.map +1 -1
- package/lib/components/events/utils/eventGrouper.js +1 -2
- package/lib/components/events/utils/eventGrouper.js.map +1 -1
- package/lib/components/upload/AutoBeChatUploadBox.d.ts +3 -4
- package/lib/components/upload/AutoBeChatUploadBox.js +2 -1
- package/lib/components/upload/AutoBeChatUploadBox.js.map +1 -1
- package/lib/components/upload/AutoBeChatUploadSendButton.js +1 -1
- package/lib/components/upload/AutoBeChatUploadSendButton.js.map +1 -1
- package/lib/context/AutoBeAgentContext.d.ts +1 -3
- package/lib/context/AutoBeAgentContext.js +0 -4
- package/lib/context/AutoBeAgentContext.js.map +1 -1
- package/lib/hooks/useSessionStorage.d.ts +4 -0
- package/lib/hooks/useSessionStorage.js +16 -0
- package/lib/hooks/useSessionStorage.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/strategy/AutoBeAgentSessionStorageStrategy.d.ts +10 -0
- package/lib/strategy/AutoBeAgentSessionStorageStrategy.js +117 -0
- package/lib/strategy/AutoBeAgentSessionStorageStrategy.js.map +1 -0
- package/lib/structure/AutoBeListener.js +91 -23
- package/lib/structure/AutoBeListener.js.map +1 -1
- package/lib/structure/AutoBeListenerState.d.ts +3 -3
- package/lib/structure/AutoBeListenerState.js +4 -4
- package/lib/structure/AutoBeListenerState.js.map +1 -1
- package/lib/structure/IAutoBeAgentSessionStorageStrategy.js +1 -1
- package/lib/structure/IAutoBeAgentSessionStorageStrategy.js.map +1 -1
- package/lib/utils/AutoBeFileUploader.d.ts +2 -2
- package/lib/utils/AutoBeFileUploader.js.map +1 -1
- package/package.json +3 -4
- 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 -484
- 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 -368
- package/src/components/events/AutoBeEventGroupMovie.tsx +18 -18
- package/src/components/events/AutoBeEventMovie.tsx +158 -139
- package/src/components/events/AutoBeProgressEventMovie.tsx +217 -157
- package/src/components/events/AutoBeScenarioEventMovie.tsx +135 -95
- package/src/components/events/AutoBeStartEventMovie.tsx +82 -82
- package/src/components/events/AutoBeValidateEventMovie.tsx +249 -286
- 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 -146
- 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 -117
- package/src/components/events/utils/index.ts +1 -1
- package/src/components/index.ts +13 -13
- package/src/components/upload/AutoBeChatUploadBox.tsx +425 -424
- 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 -258
- 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 -0
- package/src/icons/Receipt.tsx +74 -74
- package/src/index.ts +9 -8
- package/src/strategy/AutoBeAgentSessionStorageStrategy.ts +127 -0
- package/src/structure/AutoBeListener.ts +373 -304
- 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
|
@@ -1,402 +1,402 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AutoBeAnalyzeCompleteEvent,
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
AutoBeRealizeCompleteEvent,
|
|
6
|
-
AutoBeTestCompleteEvent,
|
|
7
|
-
IAutoBeGetFilesOptions,
|
|
8
|
-
} from "@autobe/interface";
|
|
9
|
-
import StackBlitzSDK from "@stackblitz/sdk";
|
|
10
|
-
import JsZip from "jszip";
|
|
11
|
-
import { useState } from "react";
|
|
12
|
-
import { VariadicSingleton } from "tstl";
|
|
13
|
-
|
|
14
|
-
import { EventCard, EventContent, EventHeader } from "./common";
|
|
15
|
-
|
|
16
|
-
export interface IAutoBeCompleteEventMovieProps {
|
|
17
|
-
getFiles: (
|
|
18
|
-
options?: Partial<IAutoBeGetFilesOptions>,
|
|
19
|
-
) => Promise<Record<string, string>>;
|
|
20
|
-
event:
|
|
21
|
-
| AutoBeAnalyzeCompleteEvent
|
|
22
|
-
|
|
|
23
|
-
| AutoBeInterfaceCompleteEvent
|
|
24
|
-
| AutoBeTestCompleteEvent
|
|
25
|
-
| AutoBeRealizeCompleteEvent;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export const AutoBeCompleteEventMovie = (
|
|
29
|
-
props: IAutoBeCompleteEventMovieProps,
|
|
30
|
-
) => {
|
|
31
|
-
const phase = getPhase(props.event);
|
|
32
|
-
const [size, setSize] = useState<number | null>(null);
|
|
33
|
-
const [downloading, setDownloading] = useState(false);
|
|
34
|
-
|
|
35
|
-
const getFiles = async (dbms: "postgres" | "sqlite") => {
|
|
36
|
-
setDownloading(true);
|
|
37
|
-
try {
|
|
38
|
-
const result = await props.getFiles({
|
|
39
|
-
dbms,
|
|
40
|
-
phase,
|
|
41
|
-
});
|
|
42
|
-
return result;
|
|
43
|
-
} catch (error) {
|
|
44
|
-
throw error;
|
|
45
|
-
} finally {
|
|
46
|
-
setDownloading(false);
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const openStackBlitz = async () => {
|
|
51
|
-
const files: Record<string, string> = Object.fromEntries(
|
|
52
|
-
Object.entries(await getFiles("sqlite")).filter(
|
|
53
|
-
([key, value]) =>
|
|
54
|
-
key.startsWith("autobe/") === false &&
|
|
55
|
-
new TextEncoder().encode(value).length < 512 * 1024, // 512 KB
|
|
56
|
-
),
|
|
57
|
-
);
|
|
58
|
-
const size: number = Object.values(files)
|
|
59
|
-
.map((str) => new TextEncoder().encode(str).length)
|
|
60
|
-
.reduce((a, b) => a + b, 0);
|
|
61
|
-
setSize(size);
|
|
62
|
-
StackBlitzSDK.openProject(
|
|
63
|
-
{
|
|
64
|
-
title: `AutoBE Generated Backend Server (${props.event.type})`,
|
|
65
|
-
template: "node",
|
|
66
|
-
files,
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
newWindow: true,
|
|
70
|
-
},
|
|
71
|
-
);
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
const download = async (dbms: "postgres" | "sqlite") => {
|
|
75
|
-
const zip: JsZip = new JsZip();
|
|
76
|
-
const directory = new VariadicSingleton((location: string): JsZip => {
|
|
77
|
-
const separated: string[] = location.split("/");
|
|
78
|
-
if (separated.length === 1) return zip.folder(separated[0])!;
|
|
79
|
-
const parent: JsZip = directory.get(separated.slice(0, -1).join("/"));
|
|
80
|
-
return parent.folder(separated.at(-1)!)!;
|
|
81
|
-
});
|
|
82
|
-
for (const [file, content] of Object.entries(await getFiles(dbms))) {
|
|
83
|
-
const separated: string[] = file.split("/");
|
|
84
|
-
if (separated.length === 1) zip.file(file, content);
|
|
85
|
-
else {
|
|
86
|
-
const folder: JsZip = directory.get(separated.slice(0, -1).join("/"));
|
|
87
|
-
folder.file(separated.at(-1)!, content);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
const data: Blob = await zip.generateAsync({ type: "blob" });
|
|
91
|
-
|
|
92
|
-
const url: string = URL.createObjectURL(data);
|
|
93
|
-
const a: HTMLAnchorElement = document.createElement("a");
|
|
94
|
-
a.href = url;
|
|
95
|
-
a.download = `AutoBE.${props.event.type}.zip`;
|
|
96
|
-
document.body.appendChild(a);
|
|
97
|
-
a.click();
|
|
98
|
-
document.body.removeChild(a);
|
|
99
|
-
URL.revokeObjectURL(url);
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const title: string | null = getTitle(props.event);
|
|
103
|
-
if (title === null) return null;
|
|
104
|
-
|
|
105
|
-
return (
|
|
106
|
-
<EventCard variant="success">
|
|
107
|
-
<EventHeader
|
|
108
|
-
title={`${title} Completed`}
|
|
109
|
-
timestamp={props.event.created_at}
|
|
110
|
-
iconType="success"
|
|
111
|
-
/>
|
|
112
|
-
<EventContent>
|
|
113
|
-
<div style={{ marginBottom: "1rem" }}>
|
|
114
|
-
<div
|
|
115
|
-
style={{
|
|
116
|
-
display: "inline-flex",
|
|
117
|
-
alignItems: "center",
|
|
118
|
-
padding: "6px 12px",
|
|
119
|
-
border: "1px solid #4caf50",
|
|
120
|
-
borderRadius: "16px",
|
|
121
|
-
backgroundColor: "transparent",
|
|
122
|
-
color: "#4caf50",
|
|
123
|
-
fontSize: "14px",
|
|
124
|
-
fontWeight: 500,
|
|
125
|
-
gap: "6px",
|
|
126
|
-
marginBottom: "1rem",
|
|
127
|
-
}}
|
|
128
|
-
>
|
|
129
|
-
✅ {title} Completed
|
|
130
|
-
</div>
|
|
131
|
-
<br />
|
|
132
|
-
<br />
|
|
133
|
-
{title} has been completed.
|
|
134
|
-
{getMessage(openStackBlitz, props.event)}
|
|
135
|
-
<br />
|
|
136
|
-
<br />
|
|
137
|
-
Please check the result in the file explorer.
|
|
138
|
-
{size !== null && size >= LIMIT && (
|
|
139
|
-
<div
|
|
140
|
-
style={{
|
|
141
|
-
backgroundColor: "#fffbeb",
|
|
142
|
-
border: "1px solid #fed7aa",
|
|
143
|
-
borderRadius: "0.5rem",
|
|
144
|
-
padding: "1rem",
|
|
145
|
-
marginTop: "1rem",
|
|
146
|
-
color: "#92400e",
|
|
147
|
-
}}
|
|
148
|
-
>
|
|
149
|
-
<strong>⚠️ Warning:</strong> This project is too large (
|
|
150
|
-
{(size / 1024 / 1024).toFixed(1)} MB) for StackBlitz.
|
|
151
|
-
<br />
|
|
152
|
-
<br />
|
|
153
|
-
Try downloading it directly instead!
|
|
154
|
-
</div>
|
|
155
|
-
)}
|
|
156
|
-
</div>
|
|
157
|
-
|
|
158
|
-
{/* Action Buttons */}
|
|
159
|
-
<div
|
|
160
|
-
style={{
|
|
161
|
-
display: "flex",
|
|
162
|
-
gap: "0.75rem",
|
|
163
|
-
justifyContent: "flex-end",
|
|
164
|
-
flexWrap: "wrap",
|
|
165
|
-
}}
|
|
166
|
-
>
|
|
167
|
-
{downloading ? (
|
|
168
|
-
<button
|
|
169
|
-
disabled
|
|
170
|
-
style={{
|
|
171
|
-
display: "flex",
|
|
172
|
-
alignItems: "center",
|
|
173
|
-
gap: "0.5rem",
|
|
174
|
-
padding: "0.75rem 1rem",
|
|
175
|
-
backgroundColor: "#f3f4f6",
|
|
176
|
-
border: "1px solid #d1d5db",
|
|
177
|
-
borderRadius: "0.5rem",
|
|
178
|
-
cursor: "not-allowed",
|
|
179
|
-
fontSize: "0.875rem",
|
|
180
|
-
color: "#6b7280",
|
|
181
|
-
}}
|
|
182
|
-
>
|
|
183
|
-
⏳ Downloading Source Codes...
|
|
184
|
-
</button>
|
|
185
|
-
) : props.event.type === "analyzeComplete" ? (
|
|
186
|
-
<>
|
|
187
|
-
<ActionButton
|
|
188
|
-
icon="📥"
|
|
189
|
-
text="Zip (SQLite)"
|
|
190
|
-
onClick={() => download("sqlite")}
|
|
191
|
-
title={
|
|
192
|
-
phase !== "analyze"
|
|
193
|
-
? "Download SQLite-based backend application (ideal for local development and testing)"
|
|
194
|
-
: "Download requirement analysis report"
|
|
195
|
-
}
|
|
196
|
-
/>
|
|
197
|
-
<ActionButton
|
|
198
|
-
icon="🚀"
|
|
199
|
-
text="StackBlitz"
|
|
200
|
-
onClick={() => openStackBlitz()}
|
|
201
|
-
variant={size !== null && size >= LIMIT ? "warning" : "primary"}
|
|
202
|
-
title="Open project in StackBlitz for instant online development and testing"
|
|
203
|
-
/>
|
|
204
|
-
</>
|
|
205
|
-
) : (
|
|
206
|
-
<>
|
|
207
|
-
<ActionButton
|
|
208
|
-
icon="☁️"
|
|
209
|
-
text="Zip (Postgres)"
|
|
210
|
-
onClick={() => download("postgres")}
|
|
211
|
-
title="Download PostgreSQL-based backend application (optimized for production deployment)"
|
|
212
|
-
/>
|
|
213
|
-
<ActionButton
|
|
214
|
-
icon="📥"
|
|
215
|
-
text="Zip (SQLite)"
|
|
216
|
-
onClick={() => download("sqlite")}
|
|
217
|
-
title="Download SQLite-based backend application (ideal for local development and testing)"
|
|
218
|
-
/>
|
|
219
|
-
<ActionButton
|
|
220
|
-
icon="🚀"
|
|
221
|
-
text="StackBlitz"
|
|
222
|
-
onClick={() => openStackBlitz()}
|
|
223
|
-
variant={size !== null && size >= LIMIT ? "warning" : "primary"}
|
|
224
|
-
title="Open project in StackBlitz for instant online development and testing"
|
|
225
|
-
/>
|
|
226
|
-
</>
|
|
227
|
-
)}
|
|
228
|
-
</div>
|
|
229
|
-
</EventContent>
|
|
230
|
-
</EventCard>
|
|
231
|
-
);
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
interface IActionButtonProps {
|
|
235
|
-
icon: string;
|
|
236
|
-
text: string;
|
|
237
|
-
onClick: () => void;
|
|
238
|
-
title?: string;
|
|
239
|
-
variant?: "primary" | "warning";
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const ActionButton = (props: IActionButtonProps) => {
|
|
243
|
-
const { icon, text, onClick, title, variant = "primary" } = props;
|
|
244
|
-
|
|
245
|
-
const getButtonStyles = () => {
|
|
246
|
-
const baseStyles = {
|
|
247
|
-
display: "flex",
|
|
248
|
-
alignItems: "center",
|
|
249
|
-
gap: "0.5rem",
|
|
250
|
-
padding: "0.75rem 1rem",
|
|
251
|
-
borderRadius: "0.5rem",
|
|
252
|
-
cursor: "pointer",
|
|
253
|
-
fontSize: "0.875rem",
|
|
254
|
-
fontWeight: 500,
|
|
255
|
-
border: "1px solid",
|
|
256
|
-
transition: "all 0.2s ease",
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
if (variant === "warning") {
|
|
260
|
-
return {
|
|
261
|
-
...baseStyles,
|
|
262
|
-
backgroundColor: "#fffbeb",
|
|
263
|
-
borderColor: "#f59e0b",
|
|
264
|
-
color: "#92400e",
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return {
|
|
269
|
-
...baseStyles,
|
|
270
|
-
backgroundColor: "#3b82f6",
|
|
271
|
-
borderColor: "#3b82f6",
|
|
272
|
-
color: "#ffffff",
|
|
273
|
-
};
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
return (
|
|
277
|
-
<button
|
|
278
|
-
onClick={onClick}
|
|
279
|
-
title={title}
|
|
280
|
-
style={getButtonStyles()}
|
|
281
|
-
onMouseEnter={(e) => {
|
|
282
|
-
if (variant === "warning") {
|
|
283
|
-
e.currentTarget.style.backgroundColor = "#fef3c7";
|
|
284
|
-
} else {
|
|
285
|
-
e.currentTarget.style.backgroundColor = "#2563eb";
|
|
286
|
-
}
|
|
287
|
-
}}
|
|
288
|
-
onMouseLeave={(e) => {
|
|
289
|
-
if (variant === "warning") {
|
|
290
|
-
e.currentTarget.style.backgroundColor = "#fffbeb";
|
|
291
|
-
} else {
|
|
292
|
-
e.currentTarget.style.backgroundColor = "#3b82f6";
|
|
293
|
-
}
|
|
294
|
-
}}
|
|
295
|
-
>
|
|
296
|
-
<span>{icon}</span>
|
|
297
|
-
<span>{text}</span>
|
|
298
|
-
</button>
|
|
299
|
-
);
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
// Helper functions
|
|
303
|
-
|
|
304
|
-
function getTitle(
|
|
305
|
-
event: IAutoBeCompleteEventMovieProps["event"],
|
|
306
|
-
): string | null {
|
|
307
|
-
switch (event.type) {
|
|
308
|
-
case "analyzeComplete":
|
|
309
|
-
return "Analyze";
|
|
310
|
-
case "
|
|
311
|
-
if (event.compiled.type !== "success") return "Prisma (Error)";
|
|
312
|
-
return "Prisma";
|
|
313
|
-
case "interfaceComplete":
|
|
314
|
-
return "Interface";
|
|
315
|
-
case "testComplete":
|
|
316
|
-
return "Test";
|
|
317
|
-
case "realizeComplete":
|
|
318
|
-
return "Realize";
|
|
319
|
-
default:
|
|
320
|
-
event satisfies never;
|
|
321
|
-
throw new Error("Unknown event type");
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const getMessage = (
|
|
326
|
-
openStackBlitz: () => void,
|
|
327
|
-
event: IAutoBeCompleteEventMovieProps["event"],
|
|
328
|
-
) => {
|
|
329
|
-
if (event.type === "
|
|
330
|
-
return (
|
|
331
|
-
<>
|
|
332
|
-
<br />
|
|
333
|
-
<br />
|
|
334
|
-
Succeeded to compose <code>
|
|
335
|
-
(Abstract Syntax Tree) data, but failed to generate Prisma schema files
|
|
336
|
-
from it. This is a bug of <code>@autobe</code>. Please{" "}
|
|
337
|
-
<a
|
|
338
|
-
href="https://github.com/wrtnlabs/autobe/issues"
|
|
339
|
-
target="_blank"
|
|
340
|
-
style={{ color: "#3b82f6", textDecoration: "underline" }}
|
|
341
|
-
>
|
|
342
|
-
write a bug report to our repository
|
|
343
|
-
</a>{" "}
|
|
344
|
-
with the{" "}
|
|
345
|
-
<a
|
|
346
|
-
href="#"
|
|
347
|
-
onClick={() => openStackBlitz()}
|
|
348
|
-
style={{ color: "#3b82f6", textDecoration: "underline" }}
|
|
349
|
-
>
|
|
350
|
-
<code>autobe/histories.json</code>
|
|
351
|
-
</a>{" "}
|
|
352
|
-
file.
|
|
353
|
-
</>
|
|
354
|
-
);
|
|
355
|
-
else if (
|
|
356
|
-
(event.type === "testComplete" || event.type === "realizeComplete") &&
|
|
357
|
-
event.compiled.type !== "success"
|
|
358
|
-
)
|
|
359
|
-
return (
|
|
360
|
-
<>
|
|
361
|
-
<br />
|
|
362
|
-
<br />
|
|
363
|
-
Succeeded to compose{" "}
|
|
364
|
-
{event.type === "testComplete" ? "test functions" : "realize functions"}
|
|
365
|
-
, but failed to pass the TypeScript compilation. This is a bug of{" "}
|
|
366
|
-
<code>@autobe</code>. Please{" "}
|
|
367
|
-
<a
|
|
368
|
-
href="https://github.com/wrtnlabs/autobe/issues"
|
|
369
|
-
target="_blank"
|
|
370
|
-
style={{ color: "#3b82f6", textDecoration: "underline" }}
|
|
371
|
-
>
|
|
372
|
-
write a bug report to our repository
|
|
373
|
-
</a>{" "}
|
|
374
|
-
with the{" "}
|
|
375
|
-
<a
|
|
376
|
-
href="#"
|
|
377
|
-
onClick={() => openStackBlitz()}
|
|
378
|
-
style={{ color: "#3b82f6", textDecoration: "underline" }}
|
|
379
|
-
>
|
|
380
|
-
<code>autobe/histories.json</code>
|
|
381
|
-
</a>{" "}
|
|
382
|
-
file.
|
|
383
|
-
</>
|
|
384
|
-
);
|
|
385
|
-
return null;
|
|
386
|
-
};
|
|
387
|
-
|
|
388
|
-
const getPhase = (event: IAutoBeCompleteEventMovieProps["event"]) => {
|
|
389
|
-
if (event.type === "analyzeComplete") return "analyze";
|
|
390
|
-
else if (event.type === "
|
|
391
|
-
else if (event.type === "interfaceComplete") return "interface";
|
|
392
|
-
else if (event.type === "testComplete") return "test";
|
|
393
|
-
else if (event.type === "realizeComplete") return "realize";
|
|
394
|
-
else {
|
|
395
|
-
event satisfies never;
|
|
396
|
-
return undefined;
|
|
397
|
-
}
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
const LIMIT = 3 * 1024 * 1024;
|
|
401
|
-
|
|
402
|
-
export default AutoBeCompleteEventMovie;
|
|
1
|
+
import {
|
|
2
|
+
AutoBeAnalyzeCompleteEvent,
|
|
3
|
+
AutoBeDatabaseCompleteEvent,
|
|
4
|
+
AutoBeInterfaceCompleteEvent,
|
|
5
|
+
AutoBeRealizeCompleteEvent,
|
|
6
|
+
AutoBeTestCompleteEvent,
|
|
7
|
+
IAutoBeGetFilesOptions,
|
|
8
|
+
} from "@autobe/interface";
|
|
9
|
+
import StackBlitzSDK from "@stackblitz/sdk";
|
|
10
|
+
import JsZip from "jszip";
|
|
11
|
+
import { useState } from "react";
|
|
12
|
+
import { VariadicSingleton } from "tstl";
|
|
13
|
+
|
|
14
|
+
import { EventCard, EventContent, EventHeader } from "./common";
|
|
15
|
+
|
|
16
|
+
export interface IAutoBeCompleteEventMovieProps {
|
|
17
|
+
getFiles: (
|
|
18
|
+
options?: Partial<IAutoBeGetFilesOptions>,
|
|
19
|
+
) => Promise<Record<string, string>>;
|
|
20
|
+
event:
|
|
21
|
+
| AutoBeAnalyzeCompleteEvent
|
|
22
|
+
| AutoBeDatabaseCompleteEvent
|
|
23
|
+
| AutoBeInterfaceCompleteEvent
|
|
24
|
+
| AutoBeTestCompleteEvent
|
|
25
|
+
| AutoBeRealizeCompleteEvent;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const AutoBeCompleteEventMovie = (
|
|
29
|
+
props: IAutoBeCompleteEventMovieProps,
|
|
30
|
+
) => {
|
|
31
|
+
const phase = getPhase(props.event);
|
|
32
|
+
const [size, setSize] = useState<number | null>(null);
|
|
33
|
+
const [downloading, setDownloading] = useState(false);
|
|
34
|
+
|
|
35
|
+
const getFiles = async (dbms: "postgres" | "sqlite") => {
|
|
36
|
+
setDownloading(true);
|
|
37
|
+
try {
|
|
38
|
+
const result = await props.getFiles({
|
|
39
|
+
dbms,
|
|
40
|
+
phase,
|
|
41
|
+
});
|
|
42
|
+
return result;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
throw error;
|
|
45
|
+
} finally {
|
|
46
|
+
setDownloading(false);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const openStackBlitz = async () => {
|
|
51
|
+
const files: Record<string, string> = Object.fromEntries(
|
|
52
|
+
Object.entries(await getFiles("sqlite")).filter(
|
|
53
|
+
([key, value]) =>
|
|
54
|
+
key.startsWith("autobe/") === false &&
|
|
55
|
+
new TextEncoder().encode(value).length < 512 * 1024, // 512 KB
|
|
56
|
+
),
|
|
57
|
+
);
|
|
58
|
+
const size: number = Object.values(files)
|
|
59
|
+
.map((str) => new TextEncoder().encode(str).length)
|
|
60
|
+
.reduce((a, b) => a + b, 0);
|
|
61
|
+
setSize(size);
|
|
62
|
+
StackBlitzSDK.openProject(
|
|
63
|
+
{
|
|
64
|
+
title: `AutoBE Generated Backend Server (${props.event.type})`,
|
|
65
|
+
template: "node",
|
|
66
|
+
files,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
newWindow: true,
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const download = async (dbms: "postgres" | "sqlite") => {
|
|
75
|
+
const zip: JsZip = new JsZip();
|
|
76
|
+
const directory = new VariadicSingleton((location: string): JsZip => {
|
|
77
|
+
const separated: string[] = location.split("/");
|
|
78
|
+
if (separated.length === 1) return zip.folder(separated[0])!;
|
|
79
|
+
const parent: JsZip = directory.get(separated.slice(0, -1).join("/"));
|
|
80
|
+
return parent.folder(separated.at(-1)!)!;
|
|
81
|
+
});
|
|
82
|
+
for (const [file, content] of Object.entries(await getFiles(dbms))) {
|
|
83
|
+
const separated: string[] = file.split("/");
|
|
84
|
+
if (separated.length === 1) zip.file(file, content);
|
|
85
|
+
else {
|
|
86
|
+
const folder: JsZip = directory.get(separated.slice(0, -1).join("/"));
|
|
87
|
+
folder.file(separated.at(-1)!, content);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const data: Blob = await zip.generateAsync({ type: "blob" });
|
|
91
|
+
|
|
92
|
+
const url: string = URL.createObjectURL(data);
|
|
93
|
+
const a: HTMLAnchorElement = document.createElement("a");
|
|
94
|
+
a.href = url;
|
|
95
|
+
a.download = `AutoBE.${props.event.type}.zip`;
|
|
96
|
+
document.body.appendChild(a);
|
|
97
|
+
a.click();
|
|
98
|
+
document.body.removeChild(a);
|
|
99
|
+
URL.revokeObjectURL(url);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const title: string | null = getTitle(props.event);
|
|
103
|
+
if (title === null) return null;
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<EventCard variant="success">
|
|
107
|
+
<EventHeader
|
|
108
|
+
title={`${title} Completed`}
|
|
109
|
+
timestamp={props.event.created_at}
|
|
110
|
+
iconType="success"
|
|
111
|
+
/>
|
|
112
|
+
<EventContent>
|
|
113
|
+
<div style={{ marginBottom: "1rem" }}>
|
|
114
|
+
<div
|
|
115
|
+
style={{
|
|
116
|
+
display: "inline-flex",
|
|
117
|
+
alignItems: "center",
|
|
118
|
+
padding: "6px 12px",
|
|
119
|
+
border: "1px solid #4caf50",
|
|
120
|
+
borderRadius: "16px",
|
|
121
|
+
backgroundColor: "transparent",
|
|
122
|
+
color: "#4caf50",
|
|
123
|
+
fontSize: "14px",
|
|
124
|
+
fontWeight: 500,
|
|
125
|
+
gap: "6px",
|
|
126
|
+
marginBottom: "1rem",
|
|
127
|
+
}}
|
|
128
|
+
>
|
|
129
|
+
✅ {title} Completed
|
|
130
|
+
</div>
|
|
131
|
+
<br />
|
|
132
|
+
<br />
|
|
133
|
+
{title} has been completed.
|
|
134
|
+
{getMessage(openStackBlitz, props.event)}
|
|
135
|
+
<br />
|
|
136
|
+
<br />
|
|
137
|
+
Please check the result in the file explorer.
|
|
138
|
+
{size !== null && size >= LIMIT && (
|
|
139
|
+
<div
|
|
140
|
+
style={{
|
|
141
|
+
backgroundColor: "#fffbeb",
|
|
142
|
+
border: "1px solid #fed7aa",
|
|
143
|
+
borderRadius: "0.5rem",
|
|
144
|
+
padding: "1rem",
|
|
145
|
+
marginTop: "1rem",
|
|
146
|
+
color: "#92400e",
|
|
147
|
+
}}
|
|
148
|
+
>
|
|
149
|
+
<strong>⚠️ Warning:</strong> This project is too large (
|
|
150
|
+
{(size / 1024 / 1024).toFixed(1)} MB) for StackBlitz.
|
|
151
|
+
<br />
|
|
152
|
+
<br />
|
|
153
|
+
Try downloading it directly instead!
|
|
154
|
+
</div>
|
|
155
|
+
)}
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
{/* Action Buttons */}
|
|
159
|
+
<div
|
|
160
|
+
style={{
|
|
161
|
+
display: "flex",
|
|
162
|
+
gap: "0.75rem",
|
|
163
|
+
justifyContent: "flex-end",
|
|
164
|
+
flexWrap: "wrap",
|
|
165
|
+
}}
|
|
166
|
+
>
|
|
167
|
+
{downloading ? (
|
|
168
|
+
<button
|
|
169
|
+
disabled
|
|
170
|
+
style={{
|
|
171
|
+
display: "flex",
|
|
172
|
+
alignItems: "center",
|
|
173
|
+
gap: "0.5rem",
|
|
174
|
+
padding: "0.75rem 1rem",
|
|
175
|
+
backgroundColor: "#f3f4f6",
|
|
176
|
+
border: "1px solid #d1d5db",
|
|
177
|
+
borderRadius: "0.5rem",
|
|
178
|
+
cursor: "not-allowed",
|
|
179
|
+
fontSize: "0.875rem",
|
|
180
|
+
color: "#6b7280",
|
|
181
|
+
}}
|
|
182
|
+
>
|
|
183
|
+
⏳ Downloading Source Codes...
|
|
184
|
+
</button>
|
|
185
|
+
) : props.event.type === "analyzeComplete" ? (
|
|
186
|
+
<>
|
|
187
|
+
<ActionButton
|
|
188
|
+
icon="📥"
|
|
189
|
+
text="Zip (SQLite)"
|
|
190
|
+
onClick={() => download("sqlite")}
|
|
191
|
+
title={
|
|
192
|
+
phase !== "analyze"
|
|
193
|
+
? "Download SQLite-based backend application (ideal for local development and testing)"
|
|
194
|
+
: "Download requirement analysis report"
|
|
195
|
+
}
|
|
196
|
+
/>
|
|
197
|
+
<ActionButton
|
|
198
|
+
icon="🚀"
|
|
199
|
+
text="StackBlitz"
|
|
200
|
+
onClick={() => openStackBlitz()}
|
|
201
|
+
variant={size !== null && size >= LIMIT ? "warning" : "primary"}
|
|
202
|
+
title="Open project in StackBlitz for instant online development and testing"
|
|
203
|
+
/>
|
|
204
|
+
</>
|
|
205
|
+
) : (
|
|
206
|
+
<>
|
|
207
|
+
<ActionButton
|
|
208
|
+
icon="☁️"
|
|
209
|
+
text="Zip (Postgres)"
|
|
210
|
+
onClick={() => download("postgres")}
|
|
211
|
+
title="Download PostgreSQL-based backend application (optimized for production deployment)"
|
|
212
|
+
/>
|
|
213
|
+
<ActionButton
|
|
214
|
+
icon="📥"
|
|
215
|
+
text="Zip (SQLite)"
|
|
216
|
+
onClick={() => download("sqlite")}
|
|
217
|
+
title="Download SQLite-based backend application (ideal for local development and testing)"
|
|
218
|
+
/>
|
|
219
|
+
<ActionButton
|
|
220
|
+
icon="🚀"
|
|
221
|
+
text="StackBlitz"
|
|
222
|
+
onClick={() => openStackBlitz()}
|
|
223
|
+
variant={size !== null && size >= LIMIT ? "warning" : "primary"}
|
|
224
|
+
title="Open project in StackBlitz for instant online development and testing"
|
|
225
|
+
/>
|
|
226
|
+
</>
|
|
227
|
+
)}
|
|
228
|
+
</div>
|
|
229
|
+
</EventContent>
|
|
230
|
+
</EventCard>
|
|
231
|
+
);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
interface IActionButtonProps {
|
|
235
|
+
icon: string;
|
|
236
|
+
text: string;
|
|
237
|
+
onClick: () => void;
|
|
238
|
+
title?: string;
|
|
239
|
+
variant?: "primary" | "warning";
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const ActionButton = (props: IActionButtonProps) => {
|
|
243
|
+
const { icon, text, onClick, title, variant = "primary" } = props;
|
|
244
|
+
|
|
245
|
+
const getButtonStyles = () => {
|
|
246
|
+
const baseStyles = {
|
|
247
|
+
display: "flex",
|
|
248
|
+
alignItems: "center",
|
|
249
|
+
gap: "0.5rem",
|
|
250
|
+
padding: "0.75rem 1rem",
|
|
251
|
+
borderRadius: "0.5rem",
|
|
252
|
+
cursor: "pointer",
|
|
253
|
+
fontSize: "0.875rem",
|
|
254
|
+
fontWeight: 500,
|
|
255
|
+
border: "1px solid",
|
|
256
|
+
transition: "all 0.2s ease",
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
if (variant === "warning") {
|
|
260
|
+
return {
|
|
261
|
+
...baseStyles,
|
|
262
|
+
backgroundColor: "#fffbeb",
|
|
263
|
+
borderColor: "#f59e0b",
|
|
264
|
+
color: "#92400e",
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
...baseStyles,
|
|
270
|
+
backgroundColor: "#3b82f6",
|
|
271
|
+
borderColor: "#3b82f6",
|
|
272
|
+
color: "#ffffff",
|
|
273
|
+
};
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<button
|
|
278
|
+
onClick={onClick}
|
|
279
|
+
title={title}
|
|
280
|
+
style={getButtonStyles()}
|
|
281
|
+
onMouseEnter={(e) => {
|
|
282
|
+
if (variant === "warning") {
|
|
283
|
+
e.currentTarget.style.backgroundColor = "#fef3c7";
|
|
284
|
+
} else {
|
|
285
|
+
e.currentTarget.style.backgroundColor = "#2563eb";
|
|
286
|
+
}
|
|
287
|
+
}}
|
|
288
|
+
onMouseLeave={(e) => {
|
|
289
|
+
if (variant === "warning") {
|
|
290
|
+
e.currentTarget.style.backgroundColor = "#fffbeb";
|
|
291
|
+
} else {
|
|
292
|
+
e.currentTarget.style.backgroundColor = "#3b82f6";
|
|
293
|
+
}
|
|
294
|
+
}}
|
|
295
|
+
>
|
|
296
|
+
<span>{icon}</span>
|
|
297
|
+
<span>{text}</span>
|
|
298
|
+
</button>
|
|
299
|
+
);
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// Helper functions
|
|
303
|
+
|
|
304
|
+
function getTitle(
|
|
305
|
+
event: IAutoBeCompleteEventMovieProps["event"],
|
|
306
|
+
): string | null {
|
|
307
|
+
switch (event.type) {
|
|
308
|
+
case "analyzeComplete":
|
|
309
|
+
return "Analyze";
|
|
310
|
+
case "databaseComplete":
|
|
311
|
+
if (event.compiled.type !== "success") return "Prisma (Error)";
|
|
312
|
+
return "Prisma";
|
|
313
|
+
case "interfaceComplete":
|
|
314
|
+
return "Interface";
|
|
315
|
+
case "testComplete":
|
|
316
|
+
return "Test";
|
|
317
|
+
case "realizeComplete":
|
|
318
|
+
return "Realize";
|
|
319
|
+
default:
|
|
320
|
+
event satisfies never;
|
|
321
|
+
throw new Error("Unknown event type");
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const getMessage = (
|
|
326
|
+
openStackBlitz: () => void,
|
|
327
|
+
event: IAutoBeCompleteEventMovieProps["event"],
|
|
328
|
+
) => {
|
|
329
|
+
if (event.type === "databaseComplete" && event.compiled.type === "failure")
|
|
330
|
+
return (
|
|
331
|
+
<>
|
|
332
|
+
<br />
|
|
333
|
+
<br />
|
|
334
|
+
Succeeded to compose <code>AutoBeDatabase.IApplication</code> typed AST
|
|
335
|
+
(Abstract Syntax Tree) data, but failed to generate Prisma schema files
|
|
336
|
+
from it. This is a bug of <code>@autobe</code>. Please{" "}
|
|
337
|
+
<a
|
|
338
|
+
href="https://github.com/wrtnlabs/autobe/issues"
|
|
339
|
+
target="_blank"
|
|
340
|
+
style={{ color: "#3b82f6", textDecoration: "underline" }}
|
|
341
|
+
>
|
|
342
|
+
write a bug report to our repository
|
|
343
|
+
</a>{" "}
|
|
344
|
+
with the{" "}
|
|
345
|
+
<a
|
|
346
|
+
href="#"
|
|
347
|
+
onClick={() => openStackBlitz()}
|
|
348
|
+
style={{ color: "#3b82f6", textDecoration: "underline" }}
|
|
349
|
+
>
|
|
350
|
+
<code>autobe/histories.json</code>
|
|
351
|
+
</a>{" "}
|
|
352
|
+
file.
|
|
353
|
+
</>
|
|
354
|
+
);
|
|
355
|
+
else if (
|
|
356
|
+
(event.type === "testComplete" || event.type === "realizeComplete") &&
|
|
357
|
+
event.compiled.type !== "success"
|
|
358
|
+
)
|
|
359
|
+
return (
|
|
360
|
+
<>
|
|
361
|
+
<br />
|
|
362
|
+
<br />
|
|
363
|
+
Succeeded to compose{" "}
|
|
364
|
+
{event.type === "testComplete" ? "test functions" : "realize functions"}
|
|
365
|
+
, but failed to pass the TypeScript compilation. This is a bug of{" "}
|
|
366
|
+
<code>@autobe</code>. Please{" "}
|
|
367
|
+
<a
|
|
368
|
+
href="https://github.com/wrtnlabs/autobe/issues"
|
|
369
|
+
target="_blank"
|
|
370
|
+
style={{ color: "#3b82f6", textDecoration: "underline" }}
|
|
371
|
+
>
|
|
372
|
+
write a bug report to our repository
|
|
373
|
+
</a>{" "}
|
|
374
|
+
with the{" "}
|
|
375
|
+
<a
|
|
376
|
+
href="#"
|
|
377
|
+
onClick={() => openStackBlitz()}
|
|
378
|
+
style={{ color: "#3b82f6", textDecoration: "underline" }}
|
|
379
|
+
>
|
|
380
|
+
<code>autobe/histories.json</code>
|
|
381
|
+
</a>{" "}
|
|
382
|
+
file.
|
|
383
|
+
</>
|
|
384
|
+
);
|
|
385
|
+
return null;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const getPhase = (event: IAutoBeCompleteEventMovieProps["event"]) => {
|
|
389
|
+
if (event.type === "analyzeComplete") return "analyze";
|
|
390
|
+
else if (event.type === "databaseComplete") return "database";
|
|
391
|
+
else if (event.type === "interfaceComplete") return "interface";
|
|
392
|
+
else if (event.type === "testComplete") return "test";
|
|
393
|
+
else if (event.type === "realizeComplete") return "realize";
|
|
394
|
+
else {
|
|
395
|
+
event satisfies never;
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const LIMIT = 3 * 1024 * 1024;
|
|
401
|
+
|
|
402
|
+
export default AutoBeCompleteEventMovie;
|