@autobe/ui 0.29.2 → 0.30.0-dev.20260315

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/LICENSE +661 -661
  2. package/README.md +261 -0
  3. package/lib/components/AutoBeChatMain.js +5 -5
  4. package/lib/components/AutoBeChatMain.js.map +1 -1
  5. package/lib/components/AutoBeConfigModal.js +9 -9
  6. package/lib/components/AutoBeStatusModal.js +4 -4
  7. package/lib/components/AutoBeStatusModal.js.map +1 -1
  8. package/lib/components/AutoBeUserMessageMovie.d.ts +2 -2
  9. package/lib/components/common/ChatBubble.d.ts +2 -2
  10. package/lib/components/common/openai/OpenAIContent.d.ts +2 -2
  11. package/lib/components/common/openai/OpenAIContent.js.map +1 -1
  12. package/lib/components/common/openai/OpenAIUserAudioContent.js +1 -1
  13. package/lib/components/common/openai/OpenAIUserAudioContent.js.map +1 -1
  14. package/lib/components/common/openai/OpenAIUserFileContent.js +1 -1
  15. package/lib/components/common/openai/OpenAIUserFileContent.js.map +1 -1
  16. package/lib/components/common/openai/OpenAIUserImageContent.d.ts +2 -2
  17. package/lib/components/events/AutoBeCompleteEventMovie.d.ts +2 -2
  18. package/lib/components/events/AutoBeCompleteEventMovie.js +5 -5
  19. package/lib/components/events/AutoBeCompleteEventMovie.js.map +1 -1
  20. package/lib/components/events/AutoBeCorrectEventMovie.d.ts +2 -2
  21. package/lib/components/events/AutoBeCorrectEventMovie.js +4 -4
  22. package/lib/components/events/AutoBeCorrectEventMovie.js.map +1 -1
  23. package/lib/components/events/AutoBeEventMovie.js +38 -17
  24. package/lib/components/events/AutoBeEventMovie.js.map +1 -1
  25. package/lib/components/events/AutoBeProgressEventMovie.js +73 -13
  26. package/lib/components/events/AutoBeProgressEventMovie.js.map +1 -1
  27. package/lib/components/events/AutoBeScenarioEventMovie.d.ts +2 -2
  28. package/lib/components/events/AutoBeScenarioEventMovie.js +18 -5
  29. package/lib/components/events/AutoBeScenarioEventMovie.js.map +1 -1
  30. package/lib/components/events/AutoBeStartEventMovie.d.ts +2 -2
  31. package/lib/components/events/AutoBeStartEventMovie.js +2 -2
  32. package/lib/components/events/AutoBeStartEventMovie.js.map +1 -1
  33. package/lib/components/events/AutoBeValidateEventMovie.d.ts +2 -2
  34. package/lib/components/events/AutoBeValidateEventMovie.js +3 -11
  35. package/lib/components/events/AutoBeValidateEventMovie.js.map +1 -1
  36. package/lib/components/events/groups/CorrectEventGroup.d.ts +2 -2
  37. package/lib/components/events/groups/CorrectEventGroup.js +1 -1
  38. package/lib/components/events/groups/CorrectEventGroup.js.map +1 -1
  39. package/lib/components/events/groups/ValidateEventGroup.d.ts +2 -2
  40. package/lib/components/events/groups/ValidateEventGroup.js +1 -2
  41. package/lib/components/events/groups/ValidateEventGroup.js.map +1 -1
  42. package/lib/components/events/utils/eventGrouper.js +1 -2
  43. package/lib/components/events/utils/eventGrouper.js.map +1 -1
  44. package/lib/components/upload/AutoBeChatUploadBox.d.ts +3 -4
  45. package/lib/components/upload/AutoBeChatUploadBox.js +2 -1
  46. package/lib/components/upload/AutoBeChatUploadBox.js.map +1 -1
  47. package/lib/components/upload/AutoBeChatUploadSendButton.js +1 -1
  48. package/lib/components/upload/AutoBeChatUploadSendButton.js.map +1 -1
  49. package/lib/context/AutoBeAgentContext.d.ts +1 -3
  50. package/lib/context/AutoBeAgentContext.js +0 -4
  51. package/lib/context/AutoBeAgentContext.js.map +1 -1
  52. package/lib/hooks/useSessionStorage.d.ts +4 -0
  53. package/lib/hooks/useSessionStorage.js +16 -0
  54. package/lib/hooks/useSessionStorage.js.map +1 -0
  55. package/lib/index.d.ts +1 -0
  56. package/lib/index.js +1 -0
  57. package/lib/index.js.map +1 -1
  58. package/lib/strategy/AutoBeAgentSessionStorageStrategy.d.ts +10 -0
  59. package/lib/strategy/AutoBeAgentSessionStorageStrategy.js +117 -0
  60. package/lib/strategy/AutoBeAgentSessionStorageStrategy.js.map +1 -0
  61. package/lib/structure/AutoBeListener.js +91 -23
  62. package/lib/structure/AutoBeListener.js.map +1 -1
  63. package/lib/structure/AutoBeListenerState.d.ts +3 -3
  64. package/lib/structure/AutoBeListenerState.js +4 -4
  65. package/lib/structure/AutoBeListenerState.js.map +1 -1
  66. package/lib/structure/IAutoBeAgentSessionStorageStrategy.js +1 -1
  67. package/lib/structure/IAutoBeAgentSessionStorageStrategy.js.map +1 -1
  68. package/lib/utils/AutoBeFileUploader.d.ts +2 -2
  69. package/lib/utils/AutoBeFileUploader.js.map +1 -1
  70. package/package.json +3 -4
  71. package/src/components/AutoBeAssistantMessageMovie.tsx +22 -22
  72. package/src/components/AutoBeChatMain.tsx +376 -376
  73. package/src/components/AutoBeChatSidebar.tsx +414 -414
  74. package/src/components/AutoBeConfigButton.tsx +83 -83
  75. package/src/components/AutoBeConfigModal.tsx +443 -443
  76. package/src/components/AutoBeStatusButton.tsx +75 -75
  77. package/src/components/AutoBeStatusModal.tsx +486 -484
  78. package/src/components/AutoBeUserMessageMovie.tsx +27 -27
  79. package/src/components/common/ActionButton.tsx +205 -205
  80. package/src/components/common/ActionButtonGroup.tsx +80 -80
  81. package/src/components/common/AutoBeConfigInput.tsx +185 -185
  82. package/src/components/common/ChatBubble.tsx +119 -119
  83. package/src/components/common/Collapsible.tsx +95 -95
  84. package/src/components/common/CompactSessionIndicator.tsx +73 -73
  85. package/src/components/common/CompactSessionList.tsx +82 -82
  86. package/src/components/common/index.ts +8 -8
  87. package/src/components/common/openai/OpenAIContent.tsx +53 -53
  88. package/src/components/common/openai/OpenAIUserAudioContent.tsx +70 -70
  89. package/src/components/common/openai/OpenAIUserFileContent.tsx +76 -76
  90. package/src/components/common/openai/OpenAIUserImageContent.tsx +34 -34
  91. package/src/components/common/openai/OpenAIUserTextContent.tsx +15 -15
  92. package/src/components/common/openai/index.ts +5 -5
  93. package/src/components/events/AutoBeCompleteEventMovie.tsx +402 -402
  94. package/src/components/events/AutoBeCorrectEventMovie.tsx +354 -368
  95. package/src/components/events/AutoBeEventGroupMovie.tsx +18 -18
  96. package/src/components/events/AutoBeEventMovie.tsx +158 -139
  97. package/src/components/events/AutoBeProgressEventMovie.tsx +217 -157
  98. package/src/components/events/AutoBeScenarioEventMovie.tsx +135 -95
  99. package/src/components/events/AutoBeStartEventMovie.tsx +82 -82
  100. package/src/components/events/AutoBeValidateEventMovie.tsx +249 -286
  101. package/src/components/events/README.md +300 -300
  102. package/src/components/events/common/CollapsibleEventGroup.tsx +211 -211
  103. package/src/components/events/common/EventCard.tsx +61 -61
  104. package/src/components/events/common/EventContent.tsx +31 -31
  105. package/src/components/events/common/EventHeader.tsx +85 -85
  106. package/src/components/events/common/EventIcon.tsx +82 -82
  107. package/src/components/events/common/ProgressBar.tsx +64 -64
  108. package/src/components/events/common/index.ts +13 -13
  109. package/src/components/events/groups/CorrectEventGroup.tsx +183 -183
  110. package/src/components/events/groups/ValidateEventGroup.tsx +143 -146
  111. package/src/components/events/groups/index.ts +8 -8
  112. package/src/components/events/index.ts +16 -16
  113. package/src/components/events/utils/eventGrouper.tsx +116 -117
  114. package/src/components/events/utils/index.ts +1 -1
  115. package/src/components/index.ts +13 -13
  116. package/src/components/upload/AutoBeChatUploadBox.tsx +425 -424
  117. package/src/components/upload/AutoBeChatUploadSendButton.tsx +66 -66
  118. package/src/components/upload/AutoBeFileUploadBox.tsx +123 -123
  119. package/src/components/upload/AutoBeUploadConfig.ts +5 -5
  120. package/src/components/upload/AutoBeVoiceRecoderButton.tsx +100 -100
  121. package/src/components/upload/index.ts +5 -5
  122. package/src/constant/color.ts +28 -28
  123. package/src/context/AutoBeAgentContext.tsx +245 -258
  124. package/src/context/AutoBeAgentSessionList.tsx +58 -58
  125. package/src/context/SearchParamsContext.tsx +49 -49
  126. package/src/hooks/index.ts +3 -3
  127. package/src/hooks/useEscapeKey.ts +24 -24
  128. package/src/hooks/useIsomorphicLayoutEffect.ts +8 -8
  129. package/src/hooks/useMediaQuery.ts +73 -73
  130. package/src/hooks/useSessionStorage.ts +10 -0
  131. package/src/icons/Receipt.tsx +74 -74
  132. package/src/index.ts +9 -8
  133. package/src/strategy/AutoBeAgentSessionStorageStrategy.ts +127 -0
  134. package/src/structure/AutoBeListener.ts +373 -304
  135. package/src/structure/AutoBeListenerState.ts +53 -53
  136. package/src/structure/IAutoBeAgentSessionStorageStrategy.ts +87 -87
  137. package/src/structure/IAutoBeEventGroup.ts +6 -6
  138. package/src/structure/index.ts +4 -4
  139. package/src/types/config.ts +44 -44
  140. package/src/types/index.ts +1 -1
  141. package/src/utils/AutoBeFileUploader.ts +279 -279
  142. package/src/utils/AutoBeVoiceRecorder.ts +95 -95
  143. package/src/utils/__tests__/crypto.test.ts +286 -286
  144. package/src/utils/__tests__/storage.test.ts +229 -229
  145. package/src/utils/crypto.ts +95 -95
  146. package/src/utils/index.ts +6 -6
  147. package/src/utils/number.ts +17 -17
  148. package/src/utils/storage.ts +96 -96
  149. package/src/utils/time.ts +14 -14
  150. package/tsconfig.json +9 -9
  151. package/vitest.config.ts +15 -15
@@ -1,402 +1,402 @@
1
- import {
2
- AutoBeAnalyzeCompleteEvent,
3
- AutoBeInterfaceCompleteEvent,
4
- AutoBePrismaCompleteEvent,
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
- | AutoBePrismaCompleteEvent
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 "prismaComplete":
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 === "prismaComplete" && event.compiled.type === "failure")
330
- return (
331
- <>
332
- <br />
333
- <br />
334
- Succeeded to compose <code>AutoBePrisma.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 === "prismaComplete") return "prisma";
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;