@animaapp/anima-sdk-react 0.1.0 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@animaapp/anima-sdk-react",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "Anima's JavaScript utilities library",
6
6
  "author": "Anima App, Inc.",
@@ -16,8 +16,7 @@
16
16
  "url": "git+https://github.com/AnimaApp/anima-sdk.git"
17
17
  },
18
18
  "publishConfig": {
19
- "access": "public",
20
- "registry": "https://registry.npmjs.org/"
19
+ "access": "public"
21
20
  },
22
21
  "scripts": {
23
22
  "build": "vite build",
@@ -1,3 +1,4 @@
1
+ import { arrayBufferToBase64 } from "./utils";
1
2
  import type {
2
3
  AnimaSDKResult,
3
4
  GetCodeParams,
@@ -7,6 +8,12 @@ import { convertCodegenFilesToAnimaFiles } from "@animaapp/anima-sdk";
7
8
  import { EventSource } from "eventsource";
8
9
  import { useImmer } from "use-immer";
9
10
 
11
+ export type UseAnimaParams = Omit<GetCodeParams, "assetsStorage"> & {
12
+ assetsStorage?:
13
+ | GetCodeParams["assetsStorage"]
14
+ | { strategy: "local"; path: string };
15
+ };
16
+
10
17
  type Status = "idle" | "pending" | "success" | "aborted" | "error";
11
18
 
12
19
  type TaskStatus = "pending" | "running" | "finished";
@@ -33,7 +40,10 @@ const defaultProgress: CodegenStatus = {
33
40
  },
34
41
  };
35
42
 
36
- type StreamMessageByType<T extends StreamCodgenMessage['type']> = Extract<StreamCodgenMessage, { type: T }>;
43
+ type StreamMessageByType<T extends StreamCodgenMessage["type"]> = Extract<
44
+ StreamCodgenMessage,
45
+ { type: T }
46
+ >;
37
47
 
38
48
  export const useAnimaCodegen = ({
39
49
  url,
@@ -44,7 +54,9 @@ export const useAnimaCodegen = ({
44
54
  }) => {
45
55
  const [status, updateStatus] = useImmer<CodegenStatus>(defaultProgress);
46
56
 
47
- const getCode = async <T = GetCodeParams>(params: T) => {
57
+ const getCode = async <T extends UseAnimaParams = UseAnimaParams>(
58
+ params: T
59
+ ) => {
48
60
  updateStatus((draft) => {
49
61
  draft.status = "pending";
50
62
  draft.error = null;
@@ -52,6 +64,15 @@ export const useAnimaCodegen = ({
52
64
  draft.tasks = defaultProgress.tasks;
53
65
  });
54
66
 
67
+ const initialParams = structuredClone(params);
68
+
69
+ if (params.assetsStorage?.strategy === "local") {
70
+ params.assetsStorage = {
71
+ strategy: "external",
72
+ url: params.assetsStorage.path,
73
+ };
74
+ }
75
+
55
76
  const es = new EventSource(url, {
56
77
  fetch: (url, init) =>
57
78
  fetch(url, {
@@ -65,12 +86,11 @@ export const useAnimaCodegen = ({
65
86
  result: AnimaSDKResult | null;
66
87
  error: Error | null;
67
88
  }>((resolve) => {
68
-
69
89
  const result: Partial<AnimaSDKResult> = {};
70
90
 
71
91
  // Add specific event listeners
72
- es.addEventListener('start', (event) => {
73
- const message = JSON.parse(event.data) as StreamMessageByType<'start'>;
92
+ es.addEventListener("start", (event) => {
93
+ const message = JSON.parse(event.data) as StreamMessageByType<"start">;
74
94
  result.sessionId = message.sessionId;
75
95
 
76
96
  updateStatus((draft) => {
@@ -78,8 +98,10 @@ export const useAnimaCodegen = ({
78
98
  });
79
99
  });
80
100
 
81
- es.addEventListener('pre_codegen', (event) => {
82
- const message = JSON.parse(event.data) as StreamMessageByType<'pre_codegen'>;
101
+ es.addEventListener("pre_codegen", (event) => {
102
+ const message = JSON.parse(
103
+ event.data
104
+ ) as StreamMessageByType<"pre_codegen">;
83
105
  if (message.message === "Anima model built") {
84
106
  updateStatus((draft) => {
85
107
  draft.tasks.fetchDesign.status = "finished";
@@ -89,13 +111,15 @@ export const useAnimaCodegen = ({
89
111
  }
90
112
  });
91
113
 
92
- es.addEventListener('figma_metadata', (e) => {
93
- const message = JSON.parse(e.data) as StreamMessageByType<'figma_metadata'>;
114
+ es.addEventListener("figma_metadata", (e) => {
115
+ const message = JSON.parse(
116
+ e.data
117
+ ) as StreamMessageByType<"figma_metadata">;
94
118
  result.figmaFileName = message.figmaFileName;
95
119
  result.figmaSelectedFrameName = message.figmaSelectedFrameName;
96
120
  });
97
121
 
98
- es.addEventListener('aborted', (e) => {
122
+ es.addEventListener("aborted", () => {
99
123
  updateStatus((draft) => {
100
124
  draft.status = "aborted";
101
125
  });
@@ -105,8 +129,10 @@ export const useAnimaCodegen = ({
105
129
  });
106
130
  });
107
131
 
108
- es.addEventListener('generating_code', (e) => {
109
- const message = JSON.parse(e.data) as StreamMessageByType<'generating_code'>;
132
+ es.addEventListener("generating_code", (event) => {
133
+ const message = JSON.parse(
134
+ event.data
135
+ ) as StreamMessageByType<"generating_code">;
110
136
  if (message.payload.status === "success") {
111
137
  const codegenFiles = message.payload.files as Record<
112
138
  string,
@@ -121,22 +147,33 @@ export const useAnimaCodegen = ({
121
147
  });
122
148
  });
123
149
 
124
- es.addEventListener('codegen_completed', (e) => {
150
+ es.addEventListener("codegen_completed", () => {
125
151
  updateStatus((draft) => {
126
152
  draft.tasks.codeGeneration.status = "finished";
127
153
  });
128
154
  });
129
155
 
130
- es.addEventListener('assets_uploaded', (e) => {
156
+ es.addEventListener("assets_uploaded", () => {
131
157
  updateStatus((draft) => {
132
158
  draft.tasks.uploadAssets.status = "finished";
133
159
  });
134
160
  });
135
161
 
136
- es.addEventListener('error', (e: ErrorEvent | MessageEvent) => {
162
+ es.addEventListener("assets_list", (event) => {
163
+ const message = JSON.parse(
164
+ event.data
165
+ ) as StreamMessageByType<"assets_list">;
166
+
167
+ result.assets = message.payload.assets;
168
+ });
169
+
170
+ // TODO: For some reason, we receive errors even after the `done` event is triggered.
171
+ es.addEventListener("error", (error: ErrorEvent | MessageEvent) => {
137
172
  // Differentiate between an error message from the server and an error event from the EventSource
138
- if (e instanceof MessageEvent) {
139
- const message = JSON.parse(e.data) as StreamMessageByType<'error'>;
173
+ if (error instanceof MessageEvent) {
174
+ const message = JSON.parse(
175
+ error.data
176
+ ) as StreamMessageByType<"error">;
140
177
  updateStatus((draft) => {
141
178
  draft.status = "error";
142
179
  draft.error = new Error(message.payload.message);
@@ -148,21 +185,21 @@ export const useAnimaCodegen = ({
148
185
  });
149
186
  } else {
150
187
  // It's an EventSource error (e.g. HTTP error)
151
- console.error('EventSource error:', e);
188
+ console.error("EventSource error:", error);
152
189
 
153
190
  updateStatus((draft) => {
154
191
  draft.status = "error";
155
- draft.error = new Error("HTTP error: " + e.message);
192
+ draft.error = new Error("HTTP error: " + error.message);
156
193
  });
157
194
 
158
195
  resolve({
159
196
  result: null,
160
- error: new Error("HTTP error: " + e.message),
197
+ error: new Error("HTTP error: " + error.message),
161
198
  });
162
199
  }
163
200
  });
164
201
 
165
- es.addEventListener('done', (event) => {
202
+ es.addEventListener("done", () => {
166
203
  updateStatus((draft) => {
167
204
  draft.status = "success";
168
205
  draft.result = result as AnimaSDKResult;
@@ -173,7 +210,40 @@ export const useAnimaCodegen = ({
173
210
  });
174
211
 
175
212
  try {
176
- const { result, error } = await promise;
213
+ const { result: r, error } = await promise;
214
+
215
+ const result = structuredClone(r);
216
+
217
+ // Ideally, we should download the assets within the `assets_uploaded` event handler, since it'll improve the performance.
218
+ // But for some reason, it doesn't work. So, we download the assets here.
219
+ if (
220
+ initialParams.assetsStorage?.strategy === "local" &&
221
+ result?.assets?.length
222
+ ) {
223
+ const downloadAssetsPromises = result.assets.map(async (asset) => {
224
+ const response = await fetch(asset.url);
225
+ const buffer = await response.arrayBuffer();
226
+ return {
227
+ assetName: asset.name,
228
+ base64: arrayBufferToBase64(buffer),
229
+ };
230
+ });
231
+
232
+ const assets = await Promise.allSettled(downloadAssetsPromises);
233
+ for (const assetPromise of assets) {
234
+ const assetsList: Record<string, string> = {};
235
+ if (assetPromise.status === "fulfilled") {
236
+ const { assetName, base64 } = assetPromise.value;
237
+
238
+ assetsList[assetName] = base64;
239
+
240
+ result.files[`${initialParams.assetsStorage.path}/${assetName}`] = {
241
+ content: base64,
242
+ isBinary: true,
243
+ };
244
+ }
245
+ }
246
+ }
177
247
 
178
248
  if (error) {
179
249
  return { result: null, error };
package/src/utils.ts ADDED
@@ -0,0 +1,10 @@
1
+ export const arrayBufferToBase64 = (buffer: ArrayBuffer) => {
2
+ var binary = "";
3
+ var bytes = new Uint8Array(buffer);
4
+ var len = bytes.byteLength;
5
+ for (var i = 0; i < len; i++) {
6
+ binary += String.fromCharCode(bytes[i]);
7
+ }
8
+
9
+ return btoa(binary);
10
+ };