@rohitaryal/whisk-api 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -118,11 +118,12 @@ if (result.Err) {
118
118
  # Install dependencies
119
119
  bun install
120
120
 
121
+ # Set up environment
122
+ export COOKIE="your_cookie_here"
123
+
121
124
  # Run tests
122
125
  bun test
123
126
 
124
- # Set up environment
125
- export COOKIE="your_cookie_here"
126
127
  ```
127
128
 
128
129
  ## Testing
@@ -135,8 +136,7 @@ bun test
135
136
 
136
137
  ## Limitations
137
138
 
138
- - Requires valid Google Labs authentication
139
- - Rate limiting applies based on Google's policies
139
+ - Requires valid Google's logged in cookies.
140
140
  - Regional availability may vary
141
141
  - Unofficial API subject to changes
142
142
 
package/package.json CHANGED
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "main": "src/index.ts",
16
16
  "description": "[![Test](https://github.com/rohitaryal/whisk-api/actions/workflows/test.yaml/badge.svg)](https://github.com/rohitaryal/whisk-api/actions/workflows/test.yaml) [![License](https://img.shields.io/npm/l/whisk-api.svg)](https://github.com/rohitaryal/whisk-api/blob/main/LICENSE) [![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?logo=typescript&logoColor=white)](https://www.typescriptlang.org/) [![Node.js](https://img.shields.io/badge/Node.js-339933?logo=node.js&logoColor=white)](https://nodejs.org/) [![Bun.js](https://img.shields.io/badge/Bun.js-000000?logo=bun&logoColor=pink)](https://nodejs.org/)",
17
- "version": "1.0.0",
17
+ "version": "1.0.1",
18
18
  "directories": {
19
19
  "example": "examples",
20
20
  "test": "tests"
package/bun.lock DELETED
@@ -1,25 +0,0 @@
1
- {
2
- "lockfileVersion": 1,
3
- "workspaces": {
4
- "": {
5
- "name": "whisk-api",
6
- "devDependencies": {
7
- "@types/bun": "latest",
8
- },
9
- "peerDependencies": {
10
- "typescript": "^5",
11
- },
12
- },
13
- },
14
- "packages": {
15
- "@types/bun": ["@types/bun@1.2.14", "", { "dependencies": { "bun-types": "1.2.14" } }, "sha512-VsFZKs8oKHzI7zwvECiAJ5oSorWndIWEVhfbYqZd4HI/45kzW7PN2Rr5biAzvGvRuNmYLSANY+H59ubHq8xw7Q=="],
16
-
17
- "@types/node": ["@types/node@22.15.21", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ=="],
18
-
19
- "bun-types": ["bun-types@1.2.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-Kuh4Ub28ucMRWeiUUWMHsT9Wcbr4H3kLIO72RZZElSDxSu7vpetRvxIUDUaW6QtaIeixIpm7OXtNnZPf82EzwA=="],
20
-
21
- "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
22
-
23
- "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
24
- }
25
- }
@@ -1,172 +0,0 @@
1
- export type ImageModel =
2
- | "IMAGEN_2"
3
- | "IMAGEN_3"
4
- | "IMAGEN_3_1"
5
- | "IMAGEN_3_5"
6
- | "IMAGEN_3_PORTRAIT"
7
- | "IMAGEN_3_LANDSCAPE"
8
- | "IMAGEN_3_PORTRAIT_THREE_FOUR"
9
- | "IMAGEN_3_LANDSCAPE_FOUR_THREE";
10
- export type AspectRatio =
11
- | "IMAGE_ASPECT_RATIO_SQUARE"
12
- | "IMAGE_ASPECT_RATIO_PORTRAIT"
13
- | "IMAGE_ASPECT_RATIO_LANDSCAPE"
14
- | "IMAGE_ASPECT_RATIO_UNSPECIFIED"
15
- | "IMAGE_ASPECT_RATIO_LANDSCAPE_FOUR_THREE"
16
- | "IMAGE_ASPECT_RATIO_PORTRAIT_THREE_FOUR";
17
-
18
- export interface Credentials {
19
- cookie: string;
20
- authorizationKey?: string;
21
- }
22
-
23
- export interface Result<T> {
24
- Ok?: T;
25
- Err?: Error;
26
- }
27
-
28
- export interface Request {
29
- url: string;
30
- headers: Headers;
31
- body?: string;
32
- method:
33
- | "GET"
34
- | "POST"
35
- | "HEAD"
36
- | "OPTIONS"
37
- | "PUT"
38
- | "PATCH"
39
- | "DELETE";
40
- }
41
-
42
- export interface Prompt {
43
- seed?: number;
44
- prompt: string;
45
- projectId?: string;
46
- imageModel?: ImageModel;
47
- aspectRatio?: AspectRatio;
48
- }
49
-
50
- export interface Projects {
51
- name: string;
52
- media: Media;
53
- displayName: string;
54
- createTime: string;
55
- }
56
-
57
- export interface Media {
58
- name: string;
59
- image: Image;
60
- mediaGenerationId: MediaGenerationId;
61
- }
62
-
63
- export interface Image {
64
- seed: number;
65
- prompt: string;
66
- modelNameType: string;
67
- previousMediaGenerationId: string;
68
- workflowId: string;
69
- fingerprintLogRecordId: string;
70
- }
71
-
72
- export interface MediaGenerationId {
73
- mediaType: string;
74
- workflowId: string;
75
- workflowStepId: string;
76
- mediaKey: string;
77
- }
78
-
79
- export interface Images {
80
- name: string;
81
- media: Media;
82
- createTime: string;
83
- }
84
-
85
- export interface FetchedImage {
86
- name: string;
87
- image: FetchedImageDetails;
88
- createTime: string;
89
- backboneMetadata: BackboneMetadata;
90
- mediaGenerationId: MediaGenerationId;
91
- }
92
-
93
- export interface FetchedImageDetails {
94
- encodedImage: string;
95
- seed: number;
96
- mediaGenerationId: string;
97
- mediaVisibility: "PRIVATE" | "PUBLIC";
98
- prompt: string;
99
- modelNameType: string;
100
- previousMediaGenerationId: string;
101
- workflowId: string;
102
- fingerprintLogRecordId: string;
103
- }
104
-
105
- export interface BackboneMetadata {
106
- mediaCategory: "MEDIA_CATEGORY_BOARD";
107
- recipeInput: RecipeInput;
108
- }
109
-
110
- export interface RecipeInput {
111
- userInput: UserInput;
112
- mediaInputs: MediaInput[];
113
- }
114
-
115
- export interface UserInput {
116
- userInstructions: string;
117
- }
118
-
119
- export interface MediaInput {
120
- mediaCategory: "MEDIA_CATEGORY_BOARD";
121
- }
122
-
123
- export interface ImageMetadata {
124
- name: string;
125
- image: ImageDetails;
126
- createTime: string;
127
- backboneMetadata: BackboneMetadata;
128
- }
129
-
130
- export interface ImageDetails {
131
- mediaGenerationId: string;
132
- mediaVisibility: "PRIVATE" | "PUBLIC";
133
- prompt: string;
134
- }
135
-
136
- export interface GenerationResult {
137
- imagePanels: ImagePanel[];
138
- workflowId: string;
139
- }
140
-
141
- export interface ImagePanel {
142
- prompt: string;
143
- generatedImages: GeneratedImage[];
144
- }
145
-
146
- export interface GeneratedImage {
147
- encodedImage: string;
148
- seed: number;
149
- mediaGenerationId: string;
150
- prompt: string;
151
- isMaskEditedImage?: boolean
152
- modelNameType?: string;
153
- workflowId?: string;
154
- fingerprintLogRecordId?: string;
155
- imageModel?: ImageModel;
156
- }
157
-
158
- export interface RefinementRequest {
159
- existingPrompt: string;
160
- newRefinement: string;
161
- base64image: string;
162
- /**
163
- * The media key of the image you want to refine.
164
- * You can get this by calling `getImageHistory()[0...N].name`
165
- */
166
- imageId: string;
167
- seed?: number;
168
- count?: number,
169
- imageModel?: ImageModel;
170
- aspectRatio?: AspectRatio;
171
- projectId?: string;
172
- }
package/src/index.ts DELETED
@@ -1,715 +0,0 @@
1
- import type { Credentials, FetchedImage, GenerationResult, ImageMetadata, Images, Projects, Prompt, RefinementRequest, Result } from "./global.types";
2
- import type { Request } from "./global.types";
3
- import { request } from "./utils/request";
4
- import { writeFileSync } from "fs";
5
-
6
- export default class Whisk {
7
- credentials: Credentials;
8
-
9
- constructor(credentials: Credentials) {
10
- if (!credentials.cookie || credentials.cookie == "INVALID_COOKIE") {
11
- throw new Error("Cookie is missing or invalid.")
12
- }
13
-
14
- this.credentials = structuredClone(credentials)
15
- }
16
-
17
- async #checkCredentials() {
18
- if (!this.credentials.cookie) {
19
- throw new Error("Credentials are not set. Please provide a valid cookie.");
20
- }
21
-
22
- if (!this.credentials.authorizationKey) {
23
- const resp = await this.getAuthorizationToken();
24
-
25
- if (resp.Err || !resp.Ok) {
26
- throw new Error("Failed to get authorization token: " + resp.Err);
27
- }
28
-
29
- this.credentials.authorizationKey = resp.Ok;
30
- }
31
- }
32
-
33
- /**
34
- * Check if `Whisk` is available in your region.
35
- *
36
- * This un-availability can be easily bypassed by
37
- * generating authorization token from a region where
38
- * its available. Use VPN with US regions.
39
- */
40
- async isAvailable(): Promise<Result<boolean>> {
41
- const req: Request = {
42
- body: "{}",
43
- method: "POST",
44
- url: "https://aisandbox-pa.googleapis.com/v1:checkAppAvailability",
45
- headers: new Headers({ // The API key might not work next-time (unsure)
46
- "Content-Type": "text/plain;charset=UTF-8",
47
- "X-Goog-Api-Key": "AIzaSyBtrm0o5ab1c-Ec8ZuLcGt3oJAA5VWt3pY",
48
- }),
49
- };
50
-
51
- const response = await request(req);
52
- if (response.Err || !response.Ok) {
53
- return { Err: response.Err };
54
- }
55
-
56
- try {
57
- const responseBody = JSON.parse(response.Ok);
58
- return { Ok: responseBody.availabilityState === "AVAILABLE" };
59
- } catch (err) {
60
- return { Err: new Error("Failed to parse response: " + response.Ok) };
61
- }
62
- }
63
-
64
- /**
65
- * Generates the authorization token for the user.
66
- * This generated token is required to make *most* of API calls.
67
- */
68
- async getAuthorizationToken(): Promise<Result<string>> {
69
- // Not on this one
70
- // await this.#checkCredentials();
71
- if (!this.credentials.cookie) {
72
- return { Err: new Error("Empty or invalid cookies.") }
73
- }
74
-
75
- const req: Request = {
76
- method: "GET",
77
- url: "https://labs.google/fx/api/auth/session",
78
- headers: new Headers({ "Cookie": String(this.credentials.cookie) }),
79
- };
80
-
81
- const resp = await request(req);
82
- if (resp.Err || !resp.Ok) {
83
- return { Err: resp.Err }
84
- }
85
-
86
- try {
87
- const parsedResp = JSON.parse(resp.Ok);
88
- const token = parsedResp?.access_token;
89
-
90
- if (!token) {
91
- return { Err: new Error("Failed to get session token: " + resp.Ok) }
92
- }
93
-
94
- // Let's not mutate the credentials directly
95
- // this.credentials.authorizationKey = token;
96
- return { Ok: String(token) };
97
- } catch (err) {
98
- return { Err: new Error("Failed to parse response: " + resp.Ok) };
99
- }
100
- }
101
-
102
- /**
103
- * Get the current credit status of the user. This is for `veo` only and not `whisk`.
104
- */
105
- async getCreditStatus(): Promise<Result<number>> {
106
- await this.#checkCredentials();
107
-
108
- const req: Request = {
109
- method: "POST",
110
- body: JSON.stringify({ "tool": "BACKBONE", "videoModel": "VEO_2_1_I2V" }), // Unknown of other models
111
- url: "https://aisandbox-pa.googleapis.com/v1:GetUserVideoCreditStatusAction",
112
- headers: new Headers({ "Authorization": String(this.credentials.authorizationKey) }),
113
- };
114
-
115
- const response = await request(req);
116
- if (response.Err || !response.Ok) {
117
- return { Err: response.Err };
118
- }
119
-
120
- try {
121
- const responseBody = JSON.parse(response.Ok);
122
-
123
- // Other properties don't seem to be useful
124
- return { Ok: Number(responseBody.credits) }
125
- } catch (err) {
126
- return { Err: new Error("Failed to parse response: " + response.Ok) };
127
- }
128
- }
129
-
130
- /**
131
- * Generates a new project ID (a unique identifier for each project) for the
132
- * given title so that you can start generating images in that specific project.
133
- *
134
- * @param projectTitle The name you want to give to the project.
135
- */
136
- async getNewProjectId(projectTitle: string): Promise<Result<string>> {
137
- await this.#checkCredentials();
138
-
139
- const req: Request = {
140
- method: "POST",
141
- // Long ass JSON
142
- body: JSON.stringify({
143
- "json": {
144
- "clientContext": {
145
- "tool": "BACKBONE",
146
- "sessionId": ";1748266079775" // Doesn't matter whatever the value is
147
- // But probably the last login time
148
- },
149
- "workflowMetadata": { "workflowName": projectTitle }
150
- }
151
- }),
152
- url: "https://labs.google/fx/api/trpc/media.createOrUpdateWorkflow",
153
- headers: new Headers({ "Cookie": String(this.credentials.cookie) }),
154
- };
155
-
156
- const resp = await request(req);
157
- if (resp.Err || !resp.Ok) {
158
- return { Err: resp.Err }
159
- }
160
-
161
- try {
162
- const parsedResp = JSON.parse(resp.Ok);
163
- const workflowID = parsedResp?.result?.data?.json?.result?.workflowId;
164
-
165
- return workflowID ? { Ok: String(workflowID) } : { Err: new Error("Failed to create new library" + resp.Ok) };
166
- } catch (err) {
167
- return { Err: new Error("Failed to parse response: " + resp.Ok) }
168
- }
169
- }
170
-
171
- /**
172
- * Get all of your project history or library.
173
- *
174
- * @param limitCount The number of projects you want to fetch.
175
- */
176
- async getProjectHistory(limitCount: number): Promise<Result<Projects[]>> {
177
- await this.#checkCredentials();
178
-
179
- const reqJson = {
180
- "json": {
181
- "rawQuery": "",
182
- "type": "BACKBONE",
183
- "subtype": "PROJECT",
184
- "limit": limitCount,
185
- "cursor": null
186
- },
187
- "meta": { "values": { "cursor": ["undefined"] } }
188
- };
189
-
190
- const req: Request = {
191
- method: "GET",
192
- headers: new Headers({
193
- "Content-Type": "application/json",
194
- "Cookie": String(this.credentials.cookie),
195
- }),
196
- url: `https://labs.google/fx/api/trpc/media.fetchUserHistory?input=` + JSON.stringify(reqJson),
197
- };
198
-
199
- const resp = await request(req);
200
- if (resp.Err || !resp.Ok) {
201
- return { Err: resp.Err }
202
- }
203
-
204
- try {
205
- const parsedResp = JSON.parse(resp.Ok);
206
- const workflowList = parsedResp?.result?.data?.json?.result?.userWorkflows;
207
-
208
- // More cases required here
209
- if (workflowList && Array.isArray(workflowList)) {
210
- return { Ok: workflowList as Projects[] }
211
- }
212
-
213
- return { Err: new Error("Failed to get project history: " + resp.Ok) }
214
- } catch (err) {
215
- return { Err: new Error("Failed to parse response: " + resp.Ok) };
216
- }
217
- }
218
-
219
- /**
220
- * Get the image history of the user.
221
- *
222
- * @param limitCount The number of images you want to fetch.
223
- */
224
- async getImageHistory(limitCount: number): Promise<Result<Images[]>> {
225
- await this.#checkCredentials();
226
-
227
- // No upper known limit
228
- if (limitCount <= 0) {
229
- return { Err: new Error("Limit count must be between 1 and 100.") };
230
- }
231
-
232
- const reqJson = {
233
- "json": {
234
- "rawQuery": "",
235
- "type": "BACKBONE",
236
- "subtype": "IMAGE",
237
- "limit": limitCount,
238
- "cursor": null
239
- },
240
- "meta": { "values": { "cursor": ["undefined"] } }
241
- };
242
-
243
- const req: Request = {
244
- method: "GET",
245
- headers: new Headers({
246
- "Content-Type": "application/json",
247
- "Cookie": String(this.credentials.cookie),
248
- }),
249
- url: `https://labs.google/fx/api/trpc/media.fetchUserHistory?input=` + JSON.stringify(reqJson),
250
- };
251
-
252
- const resp = await request(req);
253
- if (resp.Err || !resp.Ok) return { Err: resp.Err }
254
-
255
- try {
256
- const parsedResp = JSON.parse(resp.Ok);
257
- const mediaList = parsedResp?.result?.data?.json?.result?.userWorkflows;
258
-
259
- // More cases required here
260
- if (mediaList && Array.isArray(mediaList)) {
261
- return { Ok: mediaList as Images[] }
262
- }
263
-
264
- return { Err: new Error("Failed to get image history: " + resp.Ok) }
265
- } catch (err) {
266
- return { Err: new Error("Failed to parse response: " + resp.Ok) };
267
- }
268
- }
269
-
270
- /**
271
- * Fetches the content of a project by its ID.
272
- *
273
- * @param projectId The ID of the project you want to fetch content from.
274
- */
275
- async getProjectContent(projectId: string): Promise<Result<ImageMetadata[]>> {
276
- await this.#checkCredentials();
277
-
278
- if (!projectId) {
279
- return { Err: new Error("Project ID is required to fetch project content.") };
280
- }
281
-
282
- const reqJson = { "json": { "workflowId": projectId } };
283
- const req: Request = {
284
- method: "GET",
285
- headers: new Headers({
286
- "Content-Type": "application/json",
287
- "Cookie": String(this.credentials.cookie),
288
- }),
289
- url: `https://labs.google/fx/api/trpc/media.getProjectWorkflow?input=` + JSON.stringify(reqJson),
290
- };
291
-
292
- const resp = await request(req);
293
- if (resp.Err || !resp.Ok) {
294
- return { Err: resp.Err }
295
- }
296
-
297
- try {
298
- const parsedResp = JSON.parse(resp.Ok);
299
- const mediaList = parsedResp?.result?.data?.json?.result?.media;
300
-
301
- // More cases required here
302
- if (!mediaList || !Array.isArray(mediaList)) {
303
- return { Err: new Error("Failed to get project content: " + resp.Ok) };
304
- }
305
-
306
- return { Ok: mediaList as ImageMetadata[] };
307
- } catch (err) {
308
- return { Err: new Error("Failed to parse response: " + resp.Ok) };
309
- }
310
- }
311
-
312
-
313
- /**
314
- * Rename a project title.
315
- *
316
- * @param newName New name for your project
317
- * @param projectId Identifier for project that you need to rename
318
- */
319
- async renameProject(newName: string, projectId: string): Promise<Result<string>> {
320
- if (!this.credentials.cookie) {
321
- return { Err: new Error("Cookie field is empty") };
322
- }
323
-
324
- const reqJson = {
325
- "json": {
326
- "workflowId": projectId,
327
- "clientContext": {
328
- "sessionId": ";1748333296243",
329
- "tool": "BACKBONE",
330
- "workflowId": projectId
331
- },
332
- "workflowMetadata": { "workflowName": newName }
333
- }
334
- };
335
-
336
- const req: Request = {
337
- method: "POST",
338
- body: JSON.stringify(reqJson),
339
- headers: new Headers({
340
- "Content-Type": "application/json",
341
- "Cookie": String(this.credentials.cookie),
342
- }),
343
- url: "https://labs.google/fx/api/trpc/media.createOrUpdateWorkflow",
344
- };
345
-
346
- const resp = await request(req);
347
- if (resp.Err || !resp.Ok) {
348
- return { Err: resp.Err };
349
- }
350
-
351
- try {
352
- const parsedBody = JSON.parse(resp.Ok);
353
- const workflowId = parsedBody?.result?.data?.json?.result?.workflowId;
354
-
355
- if (parsedBody.error || !workflowId) {
356
- return { Err: new Error("Failed to rename project: " + resp.Ok) }
357
- }
358
-
359
- return { Ok: String(workflowId) }
360
- } catch (err) {
361
- return { Err: new Error("Failed to parse JSON: " + resp.Ok) }
362
- }
363
- }
364
-
365
- /**
366
- * Delete project(s) from libary
367
- *
368
- * @param projectIds Array of project id that you need to delete.
369
- */
370
- async deleteProjects(projectIds: string[]): Promise<Result<boolean>> {
371
- if (!this.credentials.cookie) {
372
- return { Err: new Error("Cookie field is empty") };
373
- }
374
-
375
- const reqJson = {
376
- "json":
377
- {
378
- "parent": "userProject/",
379
- "names": projectIds,
380
- }
381
- };
382
-
383
- const req: Request = {
384
- method: "POST",
385
- body: JSON.stringify(reqJson),
386
- url: "https://labs.google/fx/api/trpc/media.deleteMedia",
387
- headers: new Headers({
388
- "Content-Type": "application/json",
389
- "Cookie": String(this.credentials.cookie),
390
- }
391
- )
392
- };
393
-
394
- const resp = await request(req);
395
- if (resp.Err || !resp.Ok) {
396
- return { Err: resp.Err }
397
- }
398
-
399
- try {
400
- const parsedResp = JSON.parse(resp.Ok);
401
-
402
- if (parsedResp.error) {
403
- return { Err: new Error("Failed to delete media: " + resp.Ok) }
404
- }
405
-
406
- return { Ok: true };
407
- } catch (err) {
408
- return { Err: new Error("Failed to parse JSON: " + resp.Ok) }
409
- }
410
- }
411
-
412
- /**
413
- * Fetches the base64 encoded image from its media key (name).
414
- * Media key can be obtained by calling: `getImageHistory()[0...N].name`
415
- *
416
- * @param mediaKey The media key of the image you want to fetch.
417
- */
418
- async getMedia(mediaKey: string): Promise<Result<FetchedImage>> {
419
- await this.#checkCredentials();
420
-
421
- if (!mediaKey) {
422
- return { Err: new Error("Media key is required to fetch the image.") };
423
- }
424
-
425
- const reqJson = { "json": { "mediaKey": mediaKey } };
426
- const req: Request = {
427
- method: "GET",
428
- headers: new Headers({
429
- "Content-Type": "application/json",
430
- "Cookie": String(this.credentials.cookie),
431
- }),
432
- url: `https://labs.google/fx/api/trpc/media.fetchMedia?input=` + JSON.stringify(reqJson),
433
- };
434
-
435
- const resp = await request(req);
436
- if (resp.Err || !resp.Ok) {
437
- return { Err: resp.Err }
438
- }
439
-
440
- try {
441
- const parsedResp = JSON.parse(resp.Ok);
442
- const image = parsedResp?.result?.data?.json?.result;
443
-
444
- if (!image) {
445
- return { Err: new Error("Failed to get media: " + resp.Ok) };
446
- }
447
-
448
- return { Ok: image as FetchedImage };
449
- } catch (err) {
450
- return { Err: new Error("Failed to parse response: " + resp.Ok) };
451
- }
452
- }
453
-
454
- /**
455
- * Generates an image based on the provided prompt.
456
- *
457
- * @param prompt The prompt containing the details for image generation.
458
- */
459
- async generateImage(prompt: Prompt): Promise<Result<GenerationResult>> {
460
- await this.#checkCredentials();
461
-
462
- if (!prompt || !prompt.prompt) {
463
- return { Err: new Error("Invalid prompt. Please provide a valid prompt and projectId") };
464
- }
465
-
466
- // You missed the projectId, so let's create a new one
467
- if (!prompt.projectId) {
468
- const id = await this.getNewProjectId("New Project");
469
- if (id.Err || !id.Ok)
470
- return { Err: id.Err }
471
-
472
- prompt.projectId = id.Ok;
473
- }
474
-
475
- // Because seed can be zero
476
- if (prompt.seed == undefined) {
477
- prompt.seed = 0;
478
- }
479
-
480
- if (!prompt.imageModel) {
481
- prompt.imageModel = "IMAGEN_3_5";
482
- }
483
-
484
- if (!prompt.aspectRatio) {
485
- prompt.aspectRatio = "IMAGE_ASPECT_RATIO_LANDSCAPE"; // Default in frontend
486
- }
487
-
488
- const reqJson = {
489
- "clientContext": {
490
- "workflowId": prompt.projectId,
491
- "tool": "BACKBONE",
492
- "sessionId": ";1748281496093"
493
- },
494
- "imageModelSettings": {
495
- "imageModel": prompt.imageModel,
496
- "aspectRatio": prompt.aspectRatio,
497
- },
498
- "seed": prompt.seed,
499
- "prompt": prompt.prompt,
500
- "mediaCategory": "MEDIA_CATEGORY_BOARD"
501
- };
502
-
503
- const req: Request = {
504
- method: "POST",
505
- body: JSON.stringify(reqJson),
506
- url: "https://aisandbox-pa.googleapis.com/v1/whisk:generateImage",
507
- headers: new Headers({
508
- "Content-Type": "application/json",
509
- "Authorization": `Bearer ${String(this.credentials.authorizationKey)}`, // Requires bearer
510
- }),
511
- };
512
-
513
- const resp = await request(req);
514
- if (resp.Err || !resp.Ok) {
515
- return { Err: resp.Err }
516
- }
517
-
518
- try {
519
- const parsedResp = JSON.parse(resp.Ok);
520
- if (parsedResp.error) {
521
- return { Err: new Error("Failed to generate image: " + resp.Ok) }
522
- }
523
-
524
- return { Ok: parsedResp as GenerationResult }
525
- } catch (err) {
526
- return { Err: new Error("Failed to parse response:" + resp.Ok) }
527
- }
528
- }
529
-
530
- /**
531
- * Refine a generated image.
532
- *
533
- * Refination actually happens in the followin way:
534
- * 1. Client provides an image (base64 encoded) to refine with new prompt eg: "xyz".
535
- * 2. Server responds with *a new prompt describing your image* eg: AI-Mix("pqr", "xyz")
536
- * Where `pqr` - Description of original image
537
- * 3. Client requests image re-generation as: AI-Mix("pqr", "xyz")
538
- * 4. Server responds with new base64 encoded image
539
- */
540
- async refineImage(ref: RefinementRequest): Promise<Result<GenerationResult>> {
541
- await this.#checkCredentials();
542
-
543
- if (ref.seed == undefined) {
544
- ref.seed = 0;
545
- }
546
-
547
- if (!ref.aspectRatio) {
548
- ref.aspectRatio = "IMAGE_ASPECT_RATIO_LANDSCAPE"; // Default in frontend
549
- }
550
-
551
- if (!ref.imageModel) {
552
- ref.imageModel = "IMAGEN_3_5"; // Default in frontend (This is actually Imagen 4)
553
- }
554
-
555
- if (!ref.count) {
556
- ref.count = 1; // Default in frontend
557
- }
558
-
559
- const reqJson = {
560
- "json": {
561
- "existingPrompt": ref.existingPrompt,
562
- "textInput": ref.newRefinement,
563
- "editingImage": {
564
- "imageId": ref.imageId,
565
- "base64Image": ref.base64image,
566
- "category": "STORYBOARD",
567
- "prompt": ref.existingPrompt,
568
- "mediaKey": ref.imageId,
569
- "isLoading": false,
570
- "isFavorite": null,
571
- "isActive": true,
572
- "isPreset": false,
573
- "isSelected": false,
574
- "index": 0,
575
- "imageObjectUrl": "blob:https://labs.google/1c612ac4-ecdf-4f77-9898-82ac488ad77f",
576
- "recipeInput": {
577
- "mediaInputs": [],
578
- "userInput": {
579
- "userInstructions": ref.existingPrompt
580
- }
581
- },
582
- "currentImageAction": "REFINING",
583
- "seed": ref.seed
584
- },
585
- "sessionId": ";1748338835952" // doesn't matter
586
- },
587
- "meta": {
588
- "values": {
589
- "editingImage.isFavorite": [
590
- "undefined"
591
- ]
592
- }
593
- }
594
- };
595
-
596
- const req: Request = {
597
- method: "POST",
598
- body: JSON.stringify(reqJson),
599
- url: "https://labs.google/fx/api/trpc/backbone.generateRewrittenPrompt",
600
- headers: new Headers({
601
- "Content-Type": "application/json",
602
- "Cookie": String(this.credentials.cookie),
603
- }),
604
- };
605
-
606
- const resp = await request(req);
607
- if (resp.Err || !resp.Ok) {
608
- return { Err: resp.Err }
609
- }
610
-
611
- let parsedResp;
612
- try {
613
- parsedResp = JSON.parse(resp.Ok);
614
- if (parsedResp.error) {
615
- return { Err: new Error("Failed to refine image: " + resp.Ok) };
616
- }
617
- }
618
- catch (err) {
619
- return { Err: new Error("Failed to parse response: " + resp.Ok) };
620
- }
621
-
622
- const newPrompt = parsedResp?.result?.data?.json;
623
- if (!newPrompt) {
624
- return { Err: new Error("Failed to get new prompt from response: " + resp.Ok) };
625
- }
626
-
627
- const reqJson2 = {
628
- "userInput": {
629
- "candidatesCount": ref.count,
630
- "seed": ref.seed,
631
- "prompts": [newPrompt],
632
- "mediaCategory": "MEDIA_CATEGORY_BOARD",
633
- "recipeInput": {
634
- "userInput": {
635
- "userInstructions": newPrompt,
636
- },
637
- "mediaInputs": []
638
- }
639
- },
640
- "clientContext": {
641
- "sessionId": ";1748338835952", // can be anything
642
- "tool": "BACKBONE",
643
- "workflowId": ref.projectId,
644
- },
645
- "modelInput": {
646
- "modelNameType": ref.imageModel
647
- },
648
- "aspectRatio": ref.aspectRatio
649
- }
650
-
651
- const req2: Request = {
652
- method: "POST",
653
- body: JSON.stringify(reqJson2),
654
- url: "https://aisandbox-pa.googleapis.com/v1:runBackboneImageGeneration",
655
- headers: new Headers({
656
- "Content-Type": "text/plain;charset=UTF-8", // Yes
657
- "Authorization": `Bearer ${String(this.credentials.authorizationKey)}`, // Requires bearer
658
- }),
659
- };
660
-
661
- const resp2 = await request(req2);
662
- if (resp2.Err || !resp2.Ok) {
663
- return { Err: resp2.Err }
664
- }
665
-
666
- try {
667
- const parsedResp2 = JSON.parse(resp2.Ok);
668
- if (parsedResp2.error) {
669
- return { Err: new Error("Failed to refine image: " + resp2.Ok) };
670
- }
671
-
672
- return { Ok: parsedResp2 as GenerationResult };
673
- } catch (err) {
674
- return { Err: new Error("Failed to parse response: " + resp2.Ok) };
675
- }
676
- }
677
-
678
- /**
679
- * Save image to a file with the given name.
680
- *
681
- * @param image The base64 encoded image string.
682
- * @param fileName The name of the file where the image will be saved.
683
- */
684
- saveImage(image: string, fileName: string): Error | null {
685
- try {
686
- writeFileSync(fileName, image, { encoding: 'base64' });
687
- return null;
688
- } catch (err) {
689
- return new Error("Failed to save image: " + err);
690
- }
691
- }
692
-
693
- /**
694
- * Save image from its id directly
695
- *
696
- * @param imageId The ID of the image you want to save.
697
- * @param fileName The name of the file where the image will be saved.
698
- */
699
- async saveImageDirect(imageId: string, fileName: string): Promise<Result<boolean>> {
700
- const image = await this.getMedia(imageId);
701
-
702
- if (image.Err || !image.Ok) {
703
- return { Err: image.Err };
704
- }
705
-
706
- try {
707
- writeFileSync(fileName, image.Ok.image.encodedImage, { encoding: 'base64' });
708
- return { Ok: true };
709
- } catch (err) {
710
- return {
711
- Err: new Error("Failed to save image: " + err)
712
- }
713
- }
714
- }
715
- }
@@ -1,39 +0,0 @@
1
- import type { Request, Result } from "../global.types";
2
-
3
- const request = async function (req: Request): Promise<Result<string>> {
4
- req.headers.set("Origin", "https://labs.google");
5
- req.headers.set("Referer", "https://labs.google/fx/tools/whisk");
6
-
7
- // console.log(req.headers);
8
-
9
- // if (!req.headers.has("Authorization")) {
10
- // console.warn("Warning: Request is missing authorization headers: " + req.url)
11
- // }
12
-
13
- try {
14
- const reqs = await fetch(req.url, {
15
- body: req.body,
16
- method: req.method,
17
- headers: req.headers,
18
- });
19
-
20
- const res = await reqs.text();
21
-
22
- if (!reqs.ok) {
23
- return {
24
- Err: new Error(res)
25
- }
26
- }
27
-
28
- return {
29
- Ok: res,
30
- };
31
-
32
- } catch (err) {
33
- return {
34
- Err: (err instanceof Error) ? err : new Error("Failed to fetch.")
35
- }
36
- }
37
- }
38
-
39
- export { request };