@autobe/ui 0.30.4-dev.20260324 → 0.30.4

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