@animaapp/anima-sdk-react 0.3.1 → 0.3.5

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.
@@ -1,306 +0,0 @@
1
- import { arrayBufferToBase64 } from "./utils";
2
- import type {
3
- AnimaSDKResult,
4
- GetCodeParams,
5
- StreamCodgenMessage,
6
- } from "@animaapp/anima-sdk";
7
- import { CodegenError } from "@animaapp/anima-sdk";
8
- import { EventSource } from "eventsource";
9
- import { useImmer } from "use-immer";
10
-
11
- type LocalAssetsStorage =
12
- | { strategy: "local"; path: string }
13
- | { strategy: "local"; filePath: string; referencePath: string };
14
-
15
- export type UseAnimaParams = Omit<GetCodeParams, "assetsStorage"> & {
16
- assetsStorage?: GetCodeParams["assetsStorage"] | LocalAssetsStorage;
17
- };
18
-
19
- type Status = "idle" | "pending" | "success" | "aborted" | "error";
20
-
21
- type TaskStatus = "pending" | "running" | "finished";
22
-
23
- type CodegenStatus = {
24
- status: Status;
25
- error: CodegenError | null;
26
- result: AnimaSDKResult | null;
27
- tasks: {
28
- fetchDesign: { status: TaskStatus };
29
- codeGeneration: { status: TaskStatus; progress: number };
30
- uploadAssets: { status: TaskStatus };
31
- };
32
- };
33
-
34
- const defaultProgress: CodegenStatus = {
35
- status: "idle",
36
- error: null,
37
- result: null,
38
- tasks: {
39
- fetchDesign: { status: "pending" },
40
- codeGeneration: { status: "pending", progress: 0 },
41
- uploadAssets: { status: "pending" },
42
- },
43
- };
44
-
45
- type StreamMessageByType<T extends StreamCodgenMessage["type"]> = Extract<
46
- StreamCodgenMessage,
47
- { type: T }
48
- >;
49
-
50
- const getAssetsLocalStrategyParams = (
51
- localAssetsStorage: LocalAssetsStorage
52
- ) => {
53
- if ("path" in localAssetsStorage) {
54
- return {
55
- filePath: localAssetsStorage.path.replace(/^\//, ""),
56
- referencePath:
57
- localAssetsStorage.path === "/" ? "" : localAssetsStorage.path, // Workaround to avoid duplicated slashes in the URL. Ideally, the fix should be done in Codegen.
58
- };
59
- }
60
-
61
- return {
62
- filePath: localAssetsStorage.filePath.replace(/^\//, ""),
63
- referencePath:
64
- localAssetsStorage.referencePath === "/"
65
- ? ""
66
- : localAssetsStorage.referencePath,
67
- };
68
- };
69
-
70
- export const useAnimaCodegen = ({
71
- url,
72
- method = "POST",
73
- }: {
74
- url: string;
75
- method?: string;
76
- }) => {
77
- const [status, updateStatus] = useImmer<CodegenStatus>(defaultProgress);
78
-
79
- const getCode = async <T extends UseAnimaParams = UseAnimaParams>(
80
- params: T
81
- ) => {
82
- updateStatus((draft) => {
83
- draft.status = "pending";
84
- draft.error = null;
85
- draft.result = null;
86
- draft.tasks = defaultProgress.tasks;
87
- });
88
-
89
- const initialParams = structuredClone(params);
90
-
91
- if (params.assetsStorage?.strategy === "local") {
92
- const { referencePath } = getAssetsLocalStrategyParams(
93
- params.assetsStorage
94
- );
95
-
96
- params.assetsStorage = {
97
- strategy: "external",
98
- url: referencePath,
99
- };
100
- }
101
-
102
- // TODO: We have two workarounds here because of limitations on the `eventsource` package:
103
- // 1. We need to use the `fetch` function from the `EventSource` constructor to send the request with the correct method and body (https://github.com/EventSource/eventsource/issues/316#issuecomment-2525315835).
104
- // 2. We need to store the last fetch response to handle errors to read its body response, since it isn't expoted by the package (https://github.com/EventSource/eventsource/blob/8aa7057bccd7fb819372a3b2c1292e7b53424d52/src/EventSource.ts#L348-L376)
105
- // We might need to use other library, or do it from our self, to improve the code quality.
106
- let lastFetchResponse: ReturnType<typeof fetch>;
107
- const es = new EventSource(url, {
108
- fetch: (url, init) => {
109
- lastFetchResponse = fetch(url, {
110
- ...init,
111
- method,
112
- body: JSON.stringify(params),
113
- });
114
-
115
- return lastFetchResponse;
116
- },
117
- });
118
-
119
- const promise = new Promise<{
120
- result: AnimaSDKResult | null;
121
- error: CodegenError | null;
122
- }>((resolve) => {
123
- const result: Partial<AnimaSDKResult> = {};
124
-
125
- // Add specific event listeners
126
- es.addEventListener("start", (event) => {
127
- const message = JSON.parse(event.data) as StreamMessageByType<"start">;
128
- result.sessionId = message.sessionId;
129
-
130
- updateStatus((draft) => {
131
- draft.tasks.fetchDesign.status = "running";
132
- });
133
- });
134
-
135
- es.addEventListener("pre_codegen", (event) => {
136
- const message = JSON.parse(
137
- event.data
138
- ) as StreamMessageByType<"pre_codegen">;
139
- if (message.message === "Anima model built") {
140
- updateStatus((draft) => {
141
- draft.tasks.fetchDesign.status = "finished";
142
- draft.tasks.codeGeneration.status = "running";
143
- draft.tasks.uploadAssets.status = "running";
144
- });
145
- }
146
- });
147
-
148
- es.addEventListener("figma_metadata", (e) => {
149
- const message = JSON.parse(
150
- e.data
151
- ) as StreamMessageByType<"figma_metadata">;
152
- result.figmaFileName = message.figmaFileName;
153
- result.figmaSelectedFrameName = message.figmaSelectedFrameName;
154
- });
155
-
156
- es.addEventListener("aborted", () => {
157
- const error = new CodegenError({ name: "Aborted", reason: "Unknown" });
158
-
159
- updateStatus((draft) => {
160
- draft.status = "aborted";
161
- (draft.result = null), (draft.error = error);
162
- });
163
-
164
- resolve({
165
- result: null,
166
- error,
167
- });
168
- });
169
-
170
- es.addEventListener("generating_code", (event) => {
171
- const message = JSON.parse(
172
- event.data
173
- ) as StreamMessageByType<"generating_code">;
174
- if (message.payload.status === "success") {
175
- result.files = message.payload.files;
176
- }
177
-
178
- updateStatus((draft) => {
179
- draft.tasks.codeGeneration.progress = message.payload.progress;
180
- draft.tasks.codeGeneration.status = "running";
181
- });
182
- });
183
-
184
- es.addEventListener("codegen_completed", () => {
185
- updateStatus((draft) => {
186
- draft.tasks.codeGeneration.status = "finished";
187
- });
188
- });
189
-
190
- es.addEventListener("assets_uploaded", () => {
191
- updateStatus((draft) => {
192
- draft.tasks.uploadAssets.status = "finished";
193
- });
194
- });
195
-
196
- es.addEventListener("assets_list", (event) => {
197
- const message = JSON.parse(
198
- event.data
199
- ) as StreamMessageByType<"assets_list">;
200
-
201
- result.assets = message.payload.assets;
202
- });
203
-
204
- // TODO: For some reason, we receive errors even after the `done` event is triggered.
205
- es.addEventListener("error", async (error: ErrorEvent | MessageEvent) => {
206
- let errorPayload: StreamMessageByType<"error"> | undefined;
207
-
208
- try {
209
- if (error instanceof MessageEvent) {
210
- errorPayload = JSON.parse(error.data);
211
- } else {
212
- const response = await lastFetchResponse;
213
- errorPayload = await response.json();
214
- }
215
- } catch {}
216
-
217
- const codegenError = new CodegenError({
218
- name: errorPayload?.payload.name ?? "Unknown error",
219
- reason: errorPayload?.payload.message ?? "Unknown",
220
- status: errorPayload?.payload.status,
221
- detail: errorPayload?.payload.detail,
222
- });
223
-
224
- updateStatus((draft) => {
225
- draft.status = "error";
226
- draft.error = codegenError;
227
- });
228
-
229
- resolve({
230
- result: null,
231
- error: codegenError,
232
- });
233
- });
234
-
235
- es.addEventListener("done", (event) => {
236
- const message = JSON.parse(event.data) as StreamMessageByType<"done">;
237
- result.tokenUsage = message.payload.tokenUsage;
238
-
239
- updateStatus((draft) => {
240
- draft.status = "success";
241
- draft.result = result as AnimaSDKResult;
242
- });
243
-
244
- resolve({ result: result as AnimaSDKResult, error: null });
245
- });
246
- });
247
-
248
- try {
249
- const { result: r, error } = await promise;
250
-
251
- const result = structuredClone(r);
252
-
253
- // Ideally, we should download the assets within the `assets_uploaded` event handler, since it'll improve the performance.
254
- // But for some reason, it doesn't work. So, we download the assets here.
255
- if (
256
- initialParams.assetsStorage?.strategy === "local" &&
257
- result?.assets?.length
258
- ) {
259
- const { filePath } = getAssetsLocalStrategyParams(
260
- initialParams.assetsStorage
261
- );
262
-
263
- const downloadAssetsPromises = result.assets.map(async (asset) => {
264
- const response = await fetch(asset.url);
265
- const buffer = await response.arrayBuffer();
266
- return {
267
- assetName: asset.name,
268
- base64: arrayBufferToBase64(buffer),
269
- };
270
- });
271
-
272
- const assets = await Promise.allSettled(downloadAssetsPromises);
273
- for (const assetPromise of assets) {
274
- const assetsList: Record<string, string> = {};
275
- if (assetPromise.status === "fulfilled") {
276
- const { assetName, base64 } = assetPromise.value;
277
-
278
- assetsList[assetName] = base64;
279
-
280
- const assetPath = filePath ? `${filePath}/${assetName}` : assetName;
281
- result.files[assetPath] = {
282
- content: base64,
283
- isBinary: true,
284
- };
285
- }
286
- }
287
- }
288
-
289
- if (error) {
290
- return { result: null, error };
291
- }
292
-
293
- return { result, error };
294
- } finally {
295
- es.close();
296
- }
297
- };
298
-
299
- return {
300
- getCode,
301
- status: status.status,
302
- tasks: status.tasks,
303
- error: status.error,
304
- result: status.result,
305
- };
306
- };
@@ -1,44 +0,0 @@
1
- import useSWR from "swr";
2
- import { getFigmaFile } from "@animaapp/anima-sdk";
3
-
4
- export const useFigmaFile = ({
5
- fileKey,
6
- authToken,
7
- enabled = true,
8
- params = {},
9
- }: {
10
- fileKey: string;
11
- authToken: string;
12
- enabled?: boolean;
13
- params?: {
14
- depth?: number;
15
- };
16
- }) => {
17
- const isEnabled = Boolean(enabled && fileKey && authToken);
18
-
19
- const { data, isLoading, error } = useSWR(
20
- ["figma", fileKey, authToken, params],
21
- () => {
22
- if (!isEnabled) {
23
- return null;
24
- }
25
-
26
- return getFigmaFile({
27
- fileKey,
28
- authToken,
29
- params,
30
- });
31
- },
32
- {
33
- revalidateIfStale: false,
34
- revalidateOnFocus: false,
35
- revalidateOnReconnect: false,
36
- }
37
- );
38
-
39
- return {
40
- data: data ?? null,
41
- isLoading,
42
- error,
43
- };
44
- };
package/src/utils.ts DELETED
@@ -1,10 +0,0 @@
1
- export const arrayBufferToBase64 = (buffer: ArrayBuffer) => {
2
- let binary = "";
3
- const bytes = new Uint8Array(buffer);
4
- const len = bytes.byteLength;
5
- for (let i = 0; i < len; i++) {
6
- binary += String.fromCharCode(bytes[i]);
7
- }
8
-
9
- return btoa(binary);
10
- };
package/vite.config.ts DELETED
@@ -1,27 +0,0 @@
1
- import { defineConfig } from "vite";
2
- import dts from "vite-plugin-dts";
3
- import tsconfigPaths from "vite-tsconfig-paths";
4
-
5
- export default defineConfig({
6
- build: {
7
- lib: {
8
- entry: "./src/index.ts",
9
- formats: ["cjs", "es"],
10
- fileName: "index",
11
- },
12
- outDir: "./dist",
13
- emptyOutDir: true,
14
- target: "es6",
15
- sourcemap: true,
16
- rollupOptions: {
17
- external: ["react"],
18
- },
19
- },
20
- plugins: [
21
- tsconfigPaths(),
22
- dts({
23
- rollupTypes: true,
24
- insertTypesEntry: true,
25
- }),
26
- ],
27
- });