@golpoai/sdk 0.1.10 → 1.0.0

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/dist/index.cjs CHANGED
@@ -57,33 +57,55 @@ var Golpo = class {
57
57
  async createPodcastJob(prompt, opts = {}) {
58
58
  const {
59
59
  uploads,
60
- concurrency = 8,
61
- addMusic = false,
62
60
  voiceInstructions,
63
- personality1,
64
- personality2,
65
- doResearch = false,
66
- ttsModel = "accurate",
67
61
  language,
68
- style = "conversational",
62
+ style = "solo-female-3",
69
63
  bgMusic,
70
- outputVolume = 1,
71
- no_voice_chunking = false
64
+ // Extended params
65
+ newScript,
66
+ timing = 1
67
+ // timing in minutes – default 1 if not provided
72
68
  } = opts;
69
+ const concurrency = 8;
73
70
  const fields = [
74
71
  ["prompt", prompt],
75
- ["add_music", String(addMusic)],
76
- ["do_research", String(doResearch)],
77
- ["tts_model", ttsModel],
78
- ["style", style],
79
- ["output_volume", String(outputVolume)]
72
+ ["style", style]
80
73
  ];
81
74
  if (voiceInstructions) fields.push(["voice_instructions", voiceInstructions]);
82
- if (personality1) fields.push(["personality_1", personality1]);
83
- if (personality2) fields.push(["personality_2", personality2]);
84
75
  if (language) fields.push(["language", language]);
85
76
  if (bgMusic) fields.push(["bg_music", bgMusic]);
86
- if (no_voice_chunking) fields.push(["no_voice_chunking", String(no_voice_chunking)]);
77
+ if (newScript != null) fields.push(["new_script", newScript]);
78
+ fields.push(["timing", String(timing)]);
79
+ fields.push(["video_type", "null"]);
80
+ const hasUploads = (() => {
81
+ if (!uploads) return false;
82
+ if (typeof uploads === "string") return uploads.trim().length > 0;
83
+ const iterator = uploads[Symbol.iterator]();
84
+ const first = iterator.next();
85
+ return !first.done;
86
+ })();
87
+ const request_payload = {
88
+ prompt,
89
+ video_voice: style,
90
+ video_duration: String(timing),
91
+ video_type: "null",
92
+ language,
93
+ voice_instructions: voiceInstructions,
94
+ new_script: newScript,
95
+ bg_music: bgMusic,
96
+ attached_documents: uploads,
97
+ source: "node-sdk"
98
+ };
99
+ try {
100
+ const result = await this.createVideoRecord({
101
+ topic: prompt,
102
+ requestPayload: request_payload
103
+ });
104
+ if (result && result.videoId) {
105
+ fields.push(["video_id", result.videoId]);
106
+ }
107
+ } catch (error) {
108
+ }
87
109
  const formData = await this.createFormData(fields, uploads, concurrency);
88
110
  const { data } = await this.http.post("/generate", formData, {
89
111
  timeout: 24e4,
@@ -120,50 +142,201 @@ var Golpo = class {
120
142
  const {
121
143
  uploads,
122
144
  pollIntervalMs = 2e3,
123
- concurrency = 8,
124
145
  voiceInstructions,
125
- personality1,
126
- doResearch = false,
127
- ttsModel = "accurate",
128
146
  language,
129
- style = "solo-female",
147
+ style = "solo-female-3",
130
148
  bgMusic = "engaging",
131
- bgVolume = 1.4,
132
- outputVolume = 1,
133
149
  videoType = "long",
134
- includeWatermark = true,
150
+ includeWatermark = false,
135
151
  logo,
136
- timing = "1"
152
+ timing = 1,
153
+ useColor = true,
154
+ // newly added backend parameters
155
+ newScript,
156
+ logoPlacement,
157
+ videoInstructions,
158
+ useLineart2Style,
159
+ audioClip,
160
+ isPublic,
161
+ useAsIs,
162
+ skipAnimation,
163
+ userImages,
164
+ userImagesDescriptions,
165
+ userVideos,
166
+ userVideosDescription,
167
+ userAudioInVideo,
168
+ use2Style,
169
+ // specificAnimation, // not yet supported by the Golpo 2.0 pipeline
170
+ imageStyle,
171
+ inputImages,
172
+ penStyle,
173
+ showPencilCursor,
174
+ pacing,
175
+ justReturnScript
137
176
  } = opts;
177
+ const concurrency = 8;
178
+ const request_payload = {
179
+ prompt,
180
+ video_voice: style,
181
+ video_duration: String(timing),
182
+ video_type: videoType,
183
+ language,
184
+ voice_instructions: voiceInstructions,
185
+ bg_music: bgMusic,
186
+ use_color: useColor,
187
+ direct_script: newScript,
188
+ script_mode: newScript ? true : false,
189
+ logo_placement: logoPlacement,
190
+ video_instructions: videoInstructions,
191
+ use_lineart_2_style: useLineart2Style,
192
+ audio_clip: audioClip,
193
+ is_public: isPublic,
194
+ use_as_is: useAsIs,
195
+ skip_animation: skipAnimation,
196
+ user_images: userImages,
197
+ user_images_descriptions: userImagesDescriptions,
198
+ user_videos: userVideos,
199
+ user_videos_description: userVideosDescription,
200
+ user_audio_in_video: userAudioInVideo,
201
+ use_2_0_style: use2Style,
202
+ // specific_animation: specificAnimation, // not yet supported by the pipeline
203
+ image_style: imageStyle,
204
+ input_images: inputImages,
205
+ pen_style: penStyle,
206
+ show_pencil_cursor: penStyle ? true : showPencilCursor ?? false,
207
+ pacing,
208
+ own_narration_video_mode: null,
209
+ own_narration_pip_position: null,
210
+ just_return_script: justReturnScript,
211
+ attached_documents: uploads,
212
+ source: "node-sdk"
213
+ };
138
214
  const fields = [
139
215
  ["prompt", prompt],
140
- ["do_research", String(doResearch)],
141
- ["tts_model", ttsModel],
142
- ["bg_volume", String(bgVolume)],
143
- ["output_volume", String(outputVolume)],
144
216
  ["style", style],
145
217
  ["video_type", videoType],
146
218
  ["include_watermark", String(includeWatermark)],
147
- ["timing", timing]
219
+ ["timing", String(timing)],
220
+ ["use_color", String(useColor)]
148
221
  ];
149
222
  if (language) fields.push(["language", language]);
150
223
  if (voiceInstructions) fields.push(["voice_instructions", voiceInstructions]);
151
- if (personality1) fields.push(["personality_1", personality1]);
152
224
  if (bgMusic) fields.push(["bg_music", bgMusic]);
225
+ try {
226
+ const result = await this.createVideoRecord({
227
+ topic: prompt,
228
+ requestPayload: request_payload
229
+ });
230
+ if (result && result.videoId) {
231
+ fields.push(["video_id", result.videoId]);
232
+ }
233
+ } catch (error) {
234
+ }
235
+ if (newScript) fields.push(["new_script", newScript]);
236
+ if (logoPlacement) fields.push(["logo_placement", logoPlacement]);
237
+ if (videoInstructions) fields.push(["video_instructions", videoInstructions]);
238
+ if (useLineart2Style) fields.push(["use_lineart_2_style", useLineart2Style]);
239
+ if (isPublic != null) fields.push(["is_public", String(isPublic)]);
240
+ if (useAsIs) {
241
+ const value = Array.isArray(useAsIs) ? JSON.stringify(useAsIs) : useAsIs;
242
+ fields.push(["use_as_is", value]);
243
+ }
244
+ if (skipAnimation) {
245
+ const value = Array.isArray(skipAnimation) ? JSON.stringify(skipAnimation) : skipAnimation;
246
+ fields.push(["skip_animation", value]);
247
+ }
248
+ if (userImagesDescriptions) {
249
+ const value = Array.isArray(userImagesDescriptions) ? JSON.stringify(userImagesDescriptions) : userImagesDescriptions;
250
+ fields.push(["user_images_descriptions", value]);
251
+ }
252
+ if (userVideosDescription) {
253
+ const value = Array.isArray(userVideosDescription) ? JSON.stringify(userVideosDescription) : userVideosDescription;
254
+ fields.push(["user_videos_description", value]);
255
+ }
256
+ if (userAudioInVideo) {
257
+ fields.push(["user_audio_in_video", userAudioInVideo]);
258
+ } else if (userVideos) {
259
+ fields.push(["user_audio_in_video", "[]"]);
260
+ }
261
+ if (use2Style != null) fields.push(["use_2_0_style", String(use2Style)]);
262
+ if (imageStyle) fields.push(["image_style", imageStyle]);
263
+ if (penStyle) fields.push(["pen_style", penStyle]);
264
+ const effectiveShowPencilCursor = penStyle ? true : showPencilCursor ?? false;
265
+ fields.push(["show_pencil_cursor", String(effectiveShowPencilCursor)]);
266
+ if (pacing) fields.push(["pacing", pacing]);
267
+ if (justReturnScript != null) fields.push(["just_return_script", String(justReturnScript)]);
153
268
  if (logo) {
154
269
  fields.push(["logo_path", logo]);
155
270
  }
156
- const formData = await this.createFormData(fields, uploads, concurrency, logo);
271
+ const formData = await this.createFormData(
272
+ fields,
273
+ uploads,
274
+ concurrency,
275
+ logo,
276
+ audioClip,
277
+ userImages,
278
+ userVideos,
279
+ inputImages
280
+ );
157
281
  const { data } = await this.http.post("/generate", formData, {
158
282
  timeout: 24e4,
159
283
  headers: formData.getHeaders()
160
284
  });
285
+ if (justReturnScript) {
286
+ const r2 = await this.pollUntilComplete(
287
+ data.job_id,
288
+ pollIntervalMs,
289
+ "/script-status"
290
+ );
291
+ return { url: "", script: r2.podcast_script, videoId: data.job_id };
292
+ }
161
293
  const r = await this.pollUntilComplete(
162
294
  data.job_id,
163
295
  pollIntervalMs
164
296
  );
165
297
  return { url: r.podcast_url, script: r.podcast_script, videoId: data.job_id };
166
298
  }
299
+ /**
300
+ * Private helper: Get user email from API key
301
+ * Backend resolves: api_key -> user_id -> user_credits.email
302
+ */
303
+ async getUserEmailFromApiKey() {
304
+ try {
305
+ const { data } = await this.http.get("/api/resolve-email-from-api-key");
306
+ return data.email || null;
307
+ } catch (error) {
308
+ return null;
309
+ }
310
+ }
311
+ async createVideoRecord(opts) {
312
+ const {
313
+ topic,
314
+ context = "",
315
+ scenes = 6,
316
+ pipeline = "color",
317
+ shareDocuments = false,
318
+ logoPlacement = null,
319
+ audioClipUrl = null,
320
+ requestPayload
321
+ } = opts;
322
+ const emailFromApiKey = await this.getUserEmailFromApiKey();
323
+ if (!emailFromApiKey) {
324
+ throw new Error("Email not found");
325
+ }
326
+ const body = {
327
+ topic,
328
+ context,
329
+ scenes,
330
+ pipeline,
331
+ user_email: emailFromApiKey,
332
+ share_documents: shareDocuments,
333
+ logo_placement: logoPlacement,
334
+ audio_clip_url: audioClipUrl,
335
+ request_payload: requestPayload ? JSON.stringify(requestPayload) : null
336
+ };
337
+ const { data } = await this.http.post("/api/create-video-record", body);
338
+ return { videoId: data.video_id };
339
+ }
167
340
  /**
168
341
  * Edit specific frames of a video and regenerate the video.
169
342
  * This method polls until completion and returns the final edited video URL.
@@ -387,14 +560,19 @@ var Golpo = class {
387
560
  /* ------------------------------------------------------------ *
388
561
  * INTERNAL HELPERS
389
562
  * ------------------------------------------------------------ */
390
- async pollUntilComplete(jobId, interval) {
563
+ async pollUntilComplete(jobId, interval, statusPath = "/status") {
391
564
  while (true) {
565
+ let data;
392
566
  try {
393
- const { data } = await this.http.get(`/status/${jobId}`);
394
- if (data.status === "completed") return data;
567
+ const res = await this.http.get(`${statusPath}/${jobId}`);
568
+ data = res.data;
395
569
  } catch (error) {
396
570
  console.warn(`Status check failed for job ${jobId}, retrying in ${interval}ms:`, error instanceof Error ? error.message : error);
397
571
  }
572
+ if (data?.status === "completed") return data;
573
+ if (data?.status === "failed") {
574
+ throw new Error(`Job ${jobId} failed: ${data.error || data.message || "unknown error"}`);
575
+ }
398
576
  await new Promise((r) => setTimeout(r, interval));
399
577
  }
400
578
  }
@@ -444,7 +622,50 @@ var Golpo = class {
444
622
  attempts++;
445
623
  }
446
624
  }
447
- async createFormData(fields, uploads, concurrency, logo) {
625
+ /**
626
+ * Upload a file using /upload-url endpoint and return the final URL.
627
+ * Replicates the _presign_and_upload_one logic from the backend.
628
+ */
629
+ async uploadFileAndGetUrl(filePath) {
630
+ const absPath = (0, import_path.resolve)(filePath.trim().replace(/^<|>$/g, ""));
631
+ try {
632
+ await import_fs.promises.access(absPath);
633
+ } catch (error) {
634
+ throw new Error(`File not found: ${absPath}. Original path: ${filePath}`);
635
+ }
636
+ const fileName = (0, import_path.basename)(absPath);
637
+ const fileData = await import_fs.promises.readFile(absPath);
638
+ const mimeType = (0, import_mime_types.lookup)(absPath) || "application/octet-stream";
639
+ const { data: presignInfo } = await this.http.post(
640
+ "/upload-url",
641
+ { filename: fileName },
642
+ { headers: { "Content-Type": "application/x-www-form-urlencoded" } }
643
+ );
644
+ let finalUrl;
645
+ if (presignInfo.url && presignInfo.fields) {
646
+ const uploadFormData = new import_form_data.default();
647
+ Object.entries(presignInfo.fields).forEach(([key, value]) => {
648
+ uploadFormData.append(key, value);
649
+ });
650
+ uploadFormData.append("file", fileData, { filename: fileName });
651
+ await import_axios.default.post(presignInfo.url, uploadFormData, {
652
+ headers: uploadFormData.getHeaders(),
653
+ timeout: 3e4
654
+ });
655
+ finalUrl = presignInfo.final_url || presignInfo.url.split("?")[0];
656
+ } else if (presignInfo.upload_url || presignInfo.url) {
657
+ const uploadUrl = presignInfo.upload_url || presignInfo.url;
658
+ await import_axios.default.put(uploadUrl, fileData, {
659
+ headers: { "Content-Type": mimeType },
660
+ timeout: 3e4
661
+ });
662
+ finalUrl = presignInfo.final_url || uploadUrl.split("?")[0];
663
+ } else {
664
+ throw new Error(`Unrecognized presign response: ${JSON.stringify(presignInfo)}`);
665
+ }
666
+ return finalUrl;
667
+ }
668
+ async createFormData(fields, uploads, concurrency, logo, audioClip, userImages, userVideos, inputImages) {
448
669
  const formData = new import_form_data.default();
449
670
  fields.forEach(([key, value]) => {
450
671
  if (key !== "logo_path") {
@@ -453,16 +674,147 @@ var Golpo = class {
453
674
  });
454
675
  if (logo) {
455
676
  const logoPath = logo.trim().replace(/^<|>$/g, "");
677
+ let logoUrl;
456
678
  if (URL_RE.test(logoPath)) {
457
- throw new Error("URL uploads not supported for logo - only local file paths are accepted");
679
+ logoUrl = logoPath;
680
+ } else {
681
+ logoUrl = await this.uploadFileAndGetUrl(logoPath);
458
682
  }
459
- const absLogoPath = (0, import_path.resolve)(logoPath);
460
- const logoFileName = (0, import_path.basename)(absLogoPath);
461
- const logoMimeType = (0, import_mime_types.lookup)(absLogoPath) || "application/octet-stream";
462
- formData.append("logo", (0, import_fs.createReadStream)(absLogoPath), {
463
- filename: logoFileName,
464
- contentType: logoMimeType
683
+ formData.append("logo", logoUrl);
684
+ }
685
+ if (audioClip) {
686
+ const audioList = Array.isArray(audioClip) ? audioClip : [audioClip];
687
+ const audioLocalFiles = [];
688
+ const audioUrls = [];
689
+ audioList.forEach((item) => {
690
+ const path = String(item).trim().replace(/^<|>$/g, "");
691
+ if (URL_RE.test(path)) {
692
+ audioUrls.push(path);
693
+ } else {
694
+ audioLocalFiles.push(path);
695
+ }
465
696
  });
697
+ if (audioLocalFiles.length > 0) {
698
+ const limit = (0, import_p_limit.default)(concurrency);
699
+ const uploadedUrls = await Promise.all(
700
+ audioLocalFiles.map(
701
+ (filePath) => limit(() => this.uploadFileAndGetUrl(filePath))
702
+ )
703
+ );
704
+ audioUrls.push(...uploadedUrls);
705
+ }
706
+ if (audioUrls.length > 0) {
707
+ formData.append("audio_clip", audioUrls[0]);
708
+ }
709
+ }
710
+ if (userImages) {
711
+ const imagesList = Array.isArray(userImages) ? userImages : [userImages];
712
+ const imageLocalFiles = [];
713
+ const imageUrls = [];
714
+ console.log("[SDK] Processing userImages:", imagesList);
715
+ imagesList.forEach((item) => {
716
+ const path = String(item).trim().replace(/^<|>$/g, "");
717
+ if (URL_RE.test(path)) {
718
+ console.log("[SDK] userImages item is URL:", path);
719
+ imageUrls.push(path);
720
+ } else {
721
+ console.log("[SDK] userImages item is local file:", path);
722
+ imageLocalFiles.push(path);
723
+ }
724
+ });
725
+ if (imageLocalFiles.length > 0) {
726
+ console.log("[SDK] Uploading", imageLocalFiles.length, "image file(s)");
727
+ const limit = (0, import_p_limit.default)(concurrency);
728
+ try {
729
+ const uploadedUrls = await Promise.all(
730
+ imageLocalFiles.map(
731
+ (filePath) => limit(async () => {
732
+ try {
733
+ console.log("[SDK] Uploading image file:", filePath);
734
+ const url = await this.uploadFileAndGetUrl(filePath);
735
+ console.log("[SDK] Image uploaded, got URL:", url);
736
+ return url;
737
+ } catch (error) {
738
+ console.error(`[SDK] Failed to upload image file ${filePath}:`, error);
739
+ throw error;
740
+ }
741
+ })
742
+ )
743
+ );
744
+ imageUrls.push(...uploadedUrls);
745
+ console.log("[SDK] All images uploaded, total URLs:", imageUrls.length);
746
+ } catch (error) {
747
+ console.error("[SDK] Error uploading image files:", error);
748
+ throw error;
749
+ }
750
+ }
751
+ if (imageUrls.length > 0) {
752
+ const jsonArray = JSON.stringify(imageUrls);
753
+ console.log("[SDK] Sending user_images as JSON:", jsonArray);
754
+ formData.append("user_images", jsonArray);
755
+ } else {
756
+ console.log("[SDK] No image URLs to send");
757
+ }
758
+ }
759
+ if (userVideos) {
760
+ const videosList = Array.isArray(userVideos) ? userVideos : [userVideos];
761
+ const videoLocalFiles = [];
762
+ const videoUrls = [];
763
+ videosList.forEach((item) => {
764
+ const path = String(item).trim().replace(/^<|>$/g, "");
765
+ if (URL_RE.test(path)) {
766
+ videoUrls.push(path);
767
+ } else {
768
+ videoLocalFiles.push(path);
769
+ }
770
+ });
771
+ if (videoLocalFiles.length > 0) {
772
+ const limit = (0, import_p_limit.default)(concurrency);
773
+ try {
774
+ const uploadedUrls = await Promise.all(
775
+ videoLocalFiles.map(
776
+ (filePath) => limit(async () => {
777
+ try {
778
+ return await this.uploadFileAndGetUrl(filePath);
779
+ } catch (error) {
780
+ console.error(`Failed to upload video file ${filePath}:`, error);
781
+ throw error;
782
+ }
783
+ })
784
+ )
785
+ );
786
+ videoUrls.push(...uploadedUrls);
787
+ } catch (error) {
788
+ console.error("Error uploading video files:", error);
789
+ throw error;
790
+ }
791
+ }
792
+ if (videoUrls.length > 0) {
793
+ formData.append("user_videos", JSON.stringify(videoUrls));
794
+ }
795
+ }
796
+ if (inputImages) {
797
+ const imagesList = Array.isArray(inputImages) ? inputImages : [inputImages];
798
+ const localFiles = [];
799
+ const imageUrls = [];
800
+ imagesList.forEach((item) => {
801
+ const path = String(item).trim().replace(/^<|>$/g, "");
802
+ if (URL_RE.test(path)) {
803
+ imageUrls.push(path);
804
+ } else {
805
+ localFiles.push(path);
806
+ }
807
+ });
808
+ if (localFiles.length > 0) {
809
+ const limit = (0, import_p_limit.default)(concurrency);
810
+ const uploadedUrls = await Promise.all(
811
+ localFiles.map((filePath) => limit(() => this.uploadFileAndGetUrl(filePath)))
812
+ );
813
+ imageUrls.push(...uploadedUrls);
814
+ }
815
+ if (imageUrls.length > 0) {
816
+ formData.append("input_images", JSON.stringify(imageUrls));
817
+ }
466
818
  }
467
819
  if (uploads) {
468
820
  const list = typeof uploads === "string" ? [uploads] : [...uploads];
package/dist/index.d.cts CHANGED
@@ -1,36 +1,57 @@
1
+ interface CreateVideoRecordOptions {
2
+ topic: string;
3
+ userEmail?: string;
4
+ context?: string;
5
+ scenes?: number;
6
+ pipeline?: string;
7
+ shareDocuments?: boolean;
8
+ logoPlacement?: string;
9
+ audioClipUrl?: string;
10
+ requestPayload?: any;
11
+ videoId?: string;
12
+ }
1
13
  interface CreatePodcastOptions {
2
14
  uploads?: string | Iterable<string>;
3
- addMusic?: boolean;
4
15
  voiceInstructions?: string;
5
- personality1?: string;
6
- personality2?: string;
7
- doResearch?: boolean;
8
- ttsModel?: 'accurate' | 'fast';
9
16
  language?: string;
10
- style?: 'conversational' | 'solo-male' | 'solo-female';
11
- bgMusic?: 'jazz' | 'lofi' | 'dramatic' | null;
12
- outputVolume?: number;
17
+ style?: 'solo-male-4' | 'solo-female-4' | 'solo-male-3' | 'solo-female-3';
18
+ bgMusic?: 'jazz' | 'lofi' | 'dramatic' | 'whimsical' | 'engaging' | 'hyper' | 'inspirational' | 'documentary' | null;
13
19
  pollIntervalMs?: number;
14
- concurrency?: number;
15
- no_voice_chunking?: boolean;
20
+ newScript?: string;
21
+ timing?: 0.25 | 0.5 | 1 | 2 | 4 | 8 | 10;
16
22
  }
17
23
  interface CreateVideoOptions {
18
24
  uploads?: string | Iterable<string>;
19
25
  voiceInstructions?: string;
20
- personality1?: string;
21
- doResearch?: boolean;
22
- ttsModel?: 'accurate' | 'fast';
23
26
  language?: string;
24
- style?: 'solo-male' | 'solo-female';
25
- bgMusic?: 'engaging' | 'lofi' | null;
26
- bgVolume?: number;
27
- outputVolume?: number;
27
+ style?: 'solo-male-3' | 'solo-female-3' | 'solo-male-4' | 'solo-female-4';
28
+ bgMusic?: 'jazz' | 'lofi' | 'dramatic' | 'whimsical' | 'engaging' | 'hyper' | 'inspirational' | 'documentary' | null;
28
29
  videoType?: 'short' | 'long';
29
30
  includeWatermark?: boolean;
30
31
  logo?: string;
31
- timing?: '1' | '2';
32
+ timing?: 0.25 | 0.5 | 1 | 2 | 4 | 8 | 10;
32
33
  pollIntervalMs?: number;
33
- concurrency?: number;
34
+ useColor?: boolean;
35
+ newScript?: string;
36
+ logoPlacement?: string;
37
+ videoInstructions?: string;
38
+ useLineart2Style?: string;
39
+ audioClip?: string | string[];
40
+ isPublic?: boolean;
41
+ useAsIs?: string | boolean[];
42
+ skipAnimation?: string | boolean[];
43
+ userImages?: string | string[];
44
+ userImagesDescriptions?: string | string[];
45
+ userVideos?: string | string[];
46
+ userVideosDescription?: string | string[];
47
+ userAudioInVideo?: string;
48
+ use2Style?: boolean;
49
+ imageStyle?: 'neon' | 'whiteboard' | 'modern_minimal' | 'playful' | 'technical' | 'editorial';
50
+ inputImages?: string | string[];
51
+ penStyle?: 'stylus' | 'marker' | 'pen';
52
+ showPencilCursor?: boolean;
53
+ pacing?: 'normal' | 'fast';
54
+ justReturnScript?: boolean;
34
55
  }
35
56
  interface EditVideoOptions {
36
57
  frameIds: string[];
@@ -83,6 +104,12 @@ declare class Golpo {
83
104
  }>;
84
105
  createPodcast(prompt: string, opts?: CreatePodcastOptions): Promise<GenerationResult>;
85
106
  createVideo(prompt: string, opts?: CreateVideoOptions): Promise<GenerationResult>;
107
+ /**
108
+ * Private helper: Get user email from API key
109
+ * Backend resolves: api_key -> user_id -> user_credits.email
110
+ */
111
+ private getUserEmailFromApiKey;
112
+ private createVideoRecord;
86
113
  /**
87
114
  * Edit specific frames of a video and regenerate the video.
88
115
  * This method polls until completion and returns the final edited video URL.
@@ -132,7 +159,12 @@ declare class Golpo {
132
159
  combineVideos(opts: CombineVideosOptions): Promise<CombinedVideoResult>;
133
160
  private pollUntilComplete;
134
161
  private pollEditStatus;
162
+ /**
163
+ * Upload a file using /upload-url endpoint and return the final URL.
164
+ * Replicates the _presign_and_upload_one logic from the backend.
165
+ */
166
+ private uploadFileAndGetUrl;
135
167
  private createFormData;
136
168
  }
137
169
 
138
- export { type CombineVideosOptions, type CombinedVideoResult, type CreatePodcastOptions, type CreateVideoOptions, type EditVideoOptions, type GenerationResult, Golpo, type VideoEditResult, Golpo as default };
170
+ export { type CombineVideosOptions, type CombinedVideoResult, type CreatePodcastOptions, type CreateVideoOptions, type CreateVideoRecordOptions, type EditVideoOptions, type GenerationResult, Golpo, type VideoEditResult, Golpo as default };
package/dist/index.d.ts CHANGED
@@ -1,36 +1,57 @@
1
+ interface CreateVideoRecordOptions {
2
+ topic: string;
3
+ userEmail?: string;
4
+ context?: string;
5
+ scenes?: number;
6
+ pipeline?: string;
7
+ shareDocuments?: boolean;
8
+ logoPlacement?: string;
9
+ audioClipUrl?: string;
10
+ requestPayload?: any;
11
+ videoId?: string;
12
+ }
1
13
  interface CreatePodcastOptions {
2
14
  uploads?: string | Iterable<string>;
3
- addMusic?: boolean;
4
15
  voiceInstructions?: string;
5
- personality1?: string;
6
- personality2?: string;
7
- doResearch?: boolean;
8
- ttsModel?: 'accurate' | 'fast';
9
16
  language?: string;
10
- style?: 'conversational' | 'solo-male' | 'solo-female';
11
- bgMusic?: 'jazz' | 'lofi' | 'dramatic' | null;
12
- outputVolume?: number;
17
+ style?: 'solo-male-4' | 'solo-female-4' | 'solo-male-3' | 'solo-female-3';
18
+ bgMusic?: 'jazz' | 'lofi' | 'dramatic' | 'whimsical' | 'engaging' | 'hyper' | 'inspirational' | 'documentary' | null;
13
19
  pollIntervalMs?: number;
14
- concurrency?: number;
15
- no_voice_chunking?: boolean;
20
+ newScript?: string;
21
+ timing?: 0.25 | 0.5 | 1 | 2 | 4 | 8 | 10;
16
22
  }
17
23
  interface CreateVideoOptions {
18
24
  uploads?: string | Iterable<string>;
19
25
  voiceInstructions?: string;
20
- personality1?: string;
21
- doResearch?: boolean;
22
- ttsModel?: 'accurate' | 'fast';
23
26
  language?: string;
24
- style?: 'solo-male' | 'solo-female';
25
- bgMusic?: 'engaging' | 'lofi' | null;
26
- bgVolume?: number;
27
- outputVolume?: number;
27
+ style?: 'solo-male-3' | 'solo-female-3' | 'solo-male-4' | 'solo-female-4';
28
+ bgMusic?: 'jazz' | 'lofi' | 'dramatic' | 'whimsical' | 'engaging' | 'hyper' | 'inspirational' | 'documentary' | null;
28
29
  videoType?: 'short' | 'long';
29
30
  includeWatermark?: boolean;
30
31
  logo?: string;
31
- timing?: '1' | '2';
32
+ timing?: 0.25 | 0.5 | 1 | 2 | 4 | 8 | 10;
32
33
  pollIntervalMs?: number;
33
- concurrency?: number;
34
+ useColor?: boolean;
35
+ newScript?: string;
36
+ logoPlacement?: string;
37
+ videoInstructions?: string;
38
+ useLineart2Style?: string;
39
+ audioClip?: string | string[];
40
+ isPublic?: boolean;
41
+ useAsIs?: string | boolean[];
42
+ skipAnimation?: string | boolean[];
43
+ userImages?: string | string[];
44
+ userImagesDescriptions?: string | string[];
45
+ userVideos?: string | string[];
46
+ userVideosDescription?: string | string[];
47
+ userAudioInVideo?: string;
48
+ use2Style?: boolean;
49
+ imageStyle?: 'neon' | 'whiteboard' | 'modern_minimal' | 'playful' | 'technical' | 'editorial';
50
+ inputImages?: string | string[];
51
+ penStyle?: 'stylus' | 'marker' | 'pen';
52
+ showPencilCursor?: boolean;
53
+ pacing?: 'normal' | 'fast';
54
+ justReturnScript?: boolean;
34
55
  }
35
56
  interface EditVideoOptions {
36
57
  frameIds: string[];
@@ -83,6 +104,12 @@ declare class Golpo {
83
104
  }>;
84
105
  createPodcast(prompt: string, opts?: CreatePodcastOptions): Promise<GenerationResult>;
85
106
  createVideo(prompt: string, opts?: CreateVideoOptions): Promise<GenerationResult>;
107
+ /**
108
+ * Private helper: Get user email from API key
109
+ * Backend resolves: api_key -> user_id -> user_credits.email
110
+ */
111
+ private getUserEmailFromApiKey;
112
+ private createVideoRecord;
86
113
  /**
87
114
  * Edit specific frames of a video and regenerate the video.
88
115
  * This method polls until completion and returns the final edited video URL.
@@ -132,7 +159,12 @@ declare class Golpo {
132
159
  combineVideos(opts: CombineVideosOptions): Promise<CombinedVideoResult>;
133
160
  private pollUntilComplete;
134
161
  private pollEditStatus;
162
+ /**
163
+ * Upload a file using /upload-url endpoint and return the final URL.
164
+ * Replicates the _presign_and_upload_one logic from the backend.
165
+ */
166
+ private uploadFileAndGetUrl;
135
167
  private createFormData;
136
168
  }
137
169
 
138
- export { type CombineVideosOptions, type CombinedVideoResult, type CreatePodcastOptions, type CreateVideoOptions, type EditVideoOptions, type GenerationResult, Golpo, type VideoEditResult, Golpo as default };
170
+ export { type CombineVideosOptions, type CombinedVideoResult, type CreatePodcastOptions, type CreateVideoOptions, type CreateVideoRecordOptions, type EditVideoOptions, type GenerationResult, Golpo, type VideoEditResult, Golpo as default };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/Golpo.ts
2
2
  import axios from "axios";
3
- import { createReadStream } from "fs";
3
+ import { createReadStream, promises as fs } from "fs";
4
4
  import { basename, resolve } from "path";
5
5
  import { lookup as mimeLookup } from "mime-types";
6
6
  import pLimit from "p-limit";
@@ -20,33 +20,55 @@ var Golpo = class {
20
20
  async createPodcastJob(prompt, opts = {}) {
21
21
  const {
22
22
  uploads,
23
- concurrency = 8,
24
- addMusic = false,
25
23
  voiceInstructions,
26
- personality1,
27
- personality2,
28
- doResearch = false,
29
- ttsModel = "accurate",
30
24
  language,
31
- style = "conversational",
25
+ style = "solo-female-3",
32
26
  bgMusic,
33
- outputVolume = 1,
34
- no_voice_chunking = false
27
+ // Extended params
28
+ newScript,
29
+ timing = 1
30
+ // timing in minutes – default 1 if not provided
35
31
  } = opts;
32
+ const concurrency = 8;
36
33
  const fields = [
37
34
  ["prompt", prompt],
38
- ["add_music", String(addMusic)],
39
- ["do_research", String(doResearch)],
40
- ["tts_model", ttsModel],
41
- ["style", style],
42
- ["output_volume", String(outputVolume)]
35
+ ["style", style]
43
36
  ];
44
37
  if (voiceInstructions) fields.push(["voice_instructions", voiceInstructions]);
45
- if (personality1) fields.push(["personality_1", personality1]);
46
- if (personality2) fields.push(["personality_2", personality2]);
47
38
  if (language) fields.push(["language", language]);
48
39
  if (bgMusic) fields.push(["bg_music", bgMusic]);
49
- if (no_voice_chunking) fields.push(["no_voice_chunking", String(no_voice_chunking)]);
40
+ if (newScript != null) fields.push(["new_script", newScript]);
41
+ fields.push(["timing", String(timing)]);
42
+ fields.push(["video_type", "null"]);
43
+ const hasUploads = (() => {
44
+ if (!uploads) return false;
45
+ if (typeof uploads === "string") return uploads.trim().length > 0;
46
+ const iterator = uploads[Symbol.iterator]();
47
+ const first = iterator.next();
48
+ return !first.done;
49
+ })();
50
+ const request_payload = {
51
+ prompt,
52
+ video_voice: style,
53
+ video_duration: String(timing),
54
+ video_type: "null",
55
+ language,
56
+ voice_instructions: voiceInstructions,
57
+ new_script: newScript,
58
+ bg_music: bgMusic,
59
+ attached_documents: uploads,
60
+ source: "node-sdk"
61
+ };
62
+ try {
63
+ const result = await this.createVideoRecord({
64
+ topic: prompt,
65
+ requestPayload: request_payload
66
+ });
67
+ if (result && result.videoId) {
68
+ fields.push(["video_id", result.videoId]);
69
+ }
70
+ } catch (error) {
71
+ }
50
72
  const formData = await this.createFormData(fields, uploads, concurrency);
51
73
  const { data } = await this.http.post("/generate", formData, {
52
74
  timeout: 24e4,
@@ -83,50 +105,201 @@ var Golpo = class {
83
105
  const {
84
106
  uploads,
85
107
  pollIntervalMs = 2e3,
86
- concurrency = 8,
87
108
  voiceInstructions,
88
- personality1,
89
- doResearch = false,
90
- ttsModel = "accurate",
91
109
  language,
92
- style = "solo-female",
110
+ style = "solo-female-3",
93
111
  bgMusic = "engaging",
94
- bgVolume = 1.4,
95
- outputVolume = 1,
96
112
  videoType = "long",
97
- includeWatermark = true,
113
+ includeWatermark = false,
98
114
  logo,
99
- timing = "1"
115
+ timing = 1,
116
+ useColor = true,
117
+ // newly added backend parameters
118
+ newScript,
119
+ logoPlacement,
120
+ videoInstructions,
121
+ useLineart2Style,
122
+ audioClip,
123
+ isPublic,
124
+ useAsIs,
125
+ skipAnimation,
126
+ userImages,
127
+ userImagesDescriptions,
128
+ userVideos,
129
+ userVideosDescription,
130
+ userAudioInVideo,
131
+ use2Style,
132
+ // specificAnimation, // not yet supported by the Golpo 2.0 pipeline
133
+ imageStyle,
134
+ inputImages,
135
+ penStyle,
136
+ showPencilCursor,
137
+ pacing,
138
+ justReturnScript
100
139
  } = opts;
140
+ const concurrency = 8;
141
+ const request_payload = {
142
+ prompt,
143
+ video_voice: style,
144
+ video_duration: String(timing),
145
+ video_type: videoType,
146
+ language,
147
+ voice_instructions: voiceInstructions,
148
+ bg_music: bgMusic,
149
+ use_color: useColor,
150
+ direct_script: newScript,
151
+ script_mode: newScript ? true : false,
152
+ logo_placement: logoPlacement,
153
+ video_instructions: videoInstructions,
154
+ use_lineart_2_style: useLineart2Style,
155
+ audio_clip: audioClip,
156
+ is_public: isPublic,
157
+ use_as_is: useAsIs,
158
+ skip_animation: skipAnimation,
159
+ user_images: userImages,
160
+ user_images_descriptions: userImagesDescriptions,
161
+ user_videos: userVideos,
162
+ user_videos_description: userVideosDescription,
163
+ user_audio_in_video: userAudioInVideo,
164
+ use_2_0_style: use2Style,
165
+ // specific_animation: specificAnimation, // not yet supported by the pipeline
166
+ image_style: imageStyle,
167
+ input_images: inputImages,
168
+ pen_style: penStyle,
169
+ show_pencil_cursor: penStyle ? true : showPencilCursor ?? false,
170
+ pacing,
171
+ own_narration_video_mode: null,
172
+ own_narration_pip_position: null,
173
+ just_return_script: justReturnScript,
174
+ attached_documents: uploads,
175
+ source: "node-sdk"
176
+ };
101
177
  const fields = [
102
178
  ["prompt", prompt],
103
- ["do_research", String(doResearch)],
104
- ["tts_model", ttsModel],
105
- ["bg_volume", String(bgVolume)],
106
- ["output_volume", String(outputVolume)],
107
179
  ["style", style],
108
180
  ["video_type", videoType],
109
181
  ["include_watermark", String(includeWatermark)],
110
- ["timing", timing]
182
+ ["timing", String(timing)],
183
+ ["use_color", String(useColor)]
111
184
  ];
112
185
  if (language) fields.push(["language", language]);
113
186
  if (voiceInstructions) fields.push(["voice_instructions", voiceInstructions]);
114
- if (personality1) fields.push(["personality_1", personality1]);
115
187
  if (bgMusic) fields.push(["bg_music", bgMusic]);
188
+ try {
189
+ const result = await this.createVideoRecord({
190
+ topic: prompt,
191
+ requestPayload: request_payload
192
+ });
193
+ if (result && result.videoId) {
194
+ fields.push(["video_id", result.videoId]);
195
+ }
196
+ } catch (error) {
197
+ }
198
+ if (newScript) fields.push(["new_script", newScript]);
199
+ if (logoPlacement) fields.push(["logo_placement", logoPlacement]);
200
+ if (videoInstructions) fields.push(["video_instructions", videoInstructions]);
201
+ if (useLineart2Style) fields.push(["use_lineart_2_style", useLineart2Style]);
202
+ if (isPublic != null) fields.push(["is_public", String(isPublic)]);
203
+ if (useAsIs) {
204
+ const value = Array.isArray(useAsIs) ? JSON.stringify(useAsIs) : useAsIs;
205
+ fields.push(["use_as_is", value]);
206
+ }
207
+ if (skipAnimation) {
208
+ const value = Array.isArray(skipAnimation) ? JSON.stringify(skipAnimation) : skipAnimation;
209
+ fields.push(["skip_animation", value]);
210
+ }
211
+ if (userImagesDescriptions) {
212
+ const value = Array.isArray(userImagesDescriptions) ? JSON.stringify(userImagesDescriptions) : userImagesDescriptions;
213
+ fields.push(["user_images_descriptions", value]);
214
+ }
215
+ if (userVideosDescription) {
216
+ const value = Array.isArray(userVideosDescription) ? JSON.stringify(userVideosDescription) : userVideosDescription;
217
+ fields.push(["user_videos_description", value]);
218
+ }
219
+ if (userAudioInVideo) {
220
+ fields.push(["user_audio_in_video", userAudioInVideo]);
221
+ } else if (userVideos) {
222
+ fields.push(["user_audio_in_video", "[]"]);
223
+ }
224
+ if (use2Style != null) fields.push(["use_2_0_style", String(use2Style)]);
225
+ if (imageStyle) fields.push(["image_style", imageStyle]);
226
+ if (penStyle) fields.push(["pen_style", penStyle]);
227
+ const effectiveShowPencilCursor = penStyle ? true : showPencilCursor ?? false;
228
+ fields.push(["show_pencil_cursor", String(effectiveShowPencilCursor)]);
229
+ if (pacing) fields.push(["pacing", pacing]);
230
+ if (justReturnScript != null) fields.push(["just_return_script", String(justReturnScript)]);
116
231
  if (logo) {
117
232
  fields.push(["logo_path", logo]);
118
233
  }
119
- const formData = await this.createFormData(fields, uploads, concurrency, logo);
234
+ const formData = await this.createFormData(
235
+ fields,
236
+ uploads,
237
+ concurrency,
238
+ logo,
239
+ audioClip,
240
+ userImages,
241
+ userVideos,
242
+ inputImages
243
+ );
120
244
  const { data } = await this.http.post("/generate", formData, {
121
245
  timeout: 24e4,
122
246
  headers: formData.getHeaders()
123
247
  });
248
+ if (justReturnScript) {
249
+ const r2 = await this.pollUntilComplete(
250
+ data.job_id,
251
+ pollIntervalMs,
252
+ "/script-status"
253
+ );
254
+ return { url: "", script: r2.podcast_script, videoId: data.job_id };
255
+ }
124
256
  const r = await this.pollUntilComplete(
125
257
  data.job_id,
126
258
  pollIntervalMs
127
259
  );
128
260
  return { url: r.podcast_url, script: r.podcast_script, videoId: data.job_id };
129
261
  }
262
+ /**
263
+ * Private helper: Get user email from API key
264
+ * Backend resolves: api_key -> user_id -> user_credits.email
265
+ */
266
+ async getUserEmailFromApiKey() {
267
+ try {
268
+ const { data } = await this.http.get("/api/resolve-email-from-api-key");
269
+ return data.email || null;
270
+ } catch (error) {
271
+ return null;
272
+ }
273
+ }
274
+ async createVideoRecord(opts) {
275
+ const {
276
+ topic,
277
+ context = "",
278
+ scenes = 6,
279
+ pipeline = "color",
280
+ shareDocuments = false,
281
+ logoPlacement = null,
282
+ audioClipUrl = null,
283
+ requestPayload
284
+ } = opts;
285
+ const emailFromApiKey = await this.getUserEmailFromApiKey();
286
+ if (!emailFromApiKey) {
287
+ throw new Error("Email not found");
288
+ }
289
+ const body = {
290
+ topic,
291
+ context,
292
+ scenes,
293
+ pipeline,
294
+ user_email: emailFromApiKey,
295
+ share_documents: shareDocuments,
296
+ logo_placement: logoPlacement,
297
+ audio_clip_url: audioClipUrl,
298
+ request_payload: requestPayload ? JSON.stringify(requestPayload) : null
299
+ };
300
+ const { data } = await this.http.post("/api/create-video-record", body);
301
+ return { videoId: data.video_id };
302
+ }
130
303
  /**
131
304
  * Edit specific frames of a video and regenerate the video.
132
305
  * This method polls until completion and returns the final edited video URL.
@@ -350,14 +523,19 @@ var Golpo = class {
350
523
  /* ------------------------------------------------------------ *
351
524
  * INTERNAL HELPERS
352
525
  * ------------------------------------------------------------ */
353
- async pollUntilComplete(jobId, interval) {
526
+ async pollUntilComplete(jobId, interval, statusPath = "/status") {
354
527
  while (true) {
528
+ let data;
355
529
  try {
356
- const { data } = await this.http.get(`/status/${jobId}`);
357
- if (data.status === "completed") return data;
530
+ const res = await this.http.get(`${statusPath}/${jobId}`);
531
+ data = res.data;
358
532
  } catch (error) {
359
533
  console.warn(`Status check failed for job ${jobId}, retrying in ${interval}ms:`, error instanceof Error ? error.message : error);
360
534
  }
535
+ if (data?.status === "completed") return data;
536
+ if (data?.status === "failed") {
537
+ throw new Error(`Job ${jobId} failed: ${data.error || data.message || "unknown error"}`);
538
+ }
361
539
  await new Promise((r) => setTimeout(r, interval));
362
540
  }
363
541
  }
@@ -407,7 +585,50 @@ var Golpo = class {
407
585
  attempts++;
408
586
  }
409
587
  }
410
- async createFormData(fields, uploads, concurrency, logo) {
588
+ /**
589
+ * Upload a file using /upload-url endpoint and return the final URL.
590
+ * Replicates the _presign_and_upload_one logic from the backend.
591
+ */
592
+ async uploadFileAndGetUrl(filePath) {
593
+ const absPath = resolve(filePath.trim().replace(/^<|>$/g, ""));
594
+ try {
595
+ await fs.access(absPath);
596
+ } catch (error) {
597
+ throw new Error(`File not found: ${absPath}. Original path: ${filePath}`);
598
+ }
599
+ const fileName = basename(absPath);
600
+ const fileData = await fs.readFile(absPath);
601
+ const mimeType = mimeLookup(absPath) || "application/octet-stream";
602
+ const { data: presignInfo } = await this.http.post(
603
+ "/upload-url",
604
+ { filename: fileName },
605
+ { headers: { "Content-Type": "application/x-www-form-urlencoded" } }
606
+ );
607
+ let finalUrl;
608
+ if (presignInfo.url && presignInfo.fields) {
609
+ const uploadFormData = new FormData();
610
+ Object.entries(presignInfo.fields).forEach(([key, value]) => {
611
+ uploadFormData.append(key, value);
612
+ });
613
+ uploadFormData.append("file", fileData, { filename: fileName });
614
+ await axios.post(presignInfo.url, uploadFormData, {
615
+ headers: uploadFormData.getHeaders(),
616
+ timeout: 3e4
617
+ });
618
+ finalUrl = presignInfo.final_url || presignInfo.url.split("?")[0];
619
+ } else if (presignInfo.upload_url || presignInfo.url) {
620
+ const uploadUrl = presignInfo.upload_url || presignInfo.url;
621
+ await axios.put(uploadUrl, fileData, {
622
+ headers: { "Content-Type": mimeType },
623
+ timeout: 3e4
624
+ });
625
+ finalUrl = presignInfo.final_url || uploadUrl.split("?")[0];
626
+ } else {
627
+ throw new Error(`Unrecognized presign response: ${JSON.stringify(presignInfo)}`);
628
+ }
629
+ return finalUrl;
630
+ }
631
+ async createFormData(fields, uploads, concurrency, logo, audioClip, userImages, userVideos, inputImages) {
411
632
  const formData = new FormData();
412
633
  fields.forEach(([key, value]) => {
413
634
  if (key !== "logo_path") {
@@ -416,16 +637,147 @@ var Golpo = class {
416
637
  });
417
638
  if (logo) {
418
639
  const logoPath = logo.trim().replace(/^<|>$/g, "");
640
+ let logoUrl;
419
641
  if (URL_RE.test(logoPath)) {
420
- throw new Error("URL uploads not supported for logo - only local file paths are accepted");
642
+ logoUrl = logoPath;
643
+ } else {
644
+ logoUrl = await this.uploadFileAndGetUrl(logoPath);
421
645
  }
422
- const absLogoPath = resolve(logoPath);
423
- const logoFileName = basename(absLogoPath);
424
- const logoMimeType = mimeLookup(absLogoPath) || "application/octet-stream";
425
- formData.append("logo", createReadStream(absLogoPath), {
426
- filename: logoFileName,
427
- contentType: logoMimeType
646
+ formData.append("logo", logoUrl);
647
+ }
648
+ if (audioClip) {
649
+ const audioList = Array.isArray(audioClip) ? audioClip : [audioClip];
650
+ const audioLocalFiles = [];
651
+ const audioUrls = [];
652
+ audioList.forEach((item) => {
653
+ const path = String(item).trim().replace(/^<|>$/g, "");
654
+ if (URL_RE.test(path)) {
655
+ audioUrls.push(path);
656
+ } else {
657
+ audioLocalFiles.push(path);
658
+ }
428
659
  });
660
+ if (audioLocalFiles.length > 0) {
661
+ const limit = pLimit(concurrency);
662
+ const uploadedUrls = await Promise.all(
663
+ audioLocalFiles.map(
664
+ (filePath) => limit(() => this.uploadFileAndGetUrl(filePath))
665
+ )
666
+ );
667
+ audioUrls.push(...uploadedUrls);
668
+ }
669
+ if (audioUrls.length > 0) {
670
+ formData.append("audio_clip", audioUrls[0]);
671
+ }
672
+ }
673
+ if (userImages) {
674
+ const imagesList = Array.isArray(userImages) ? userImages : [userImages];
675
+ const imageLocalFiles = [];
676
+ const imageUrls = [];
677
+ console.log("[SDK] Processing userImages:", imagesList);
678
+ imagesList.forEach((item) => {
679
+ const path = String(item).trim().replace(/^<|>$/g, "");
680
+ if (URL_RE.test(path)) {
681
+ console.log("[SDK] userImages item is URL:", path);
682
+ imageUrls.push(path);
683
+ } else {
684
+ console.log("[SDK] userImages item is local file:", path);
685
+ imageLocalFiles.push(path);
686
+ }
687
+ });
688
+ if (imageLocalFiles.length > 0) {
689
+ console.log("[SDK] Uploading", imageLocalFiles.length, "image file(s)");
690
+ const limit = pLimit(concurrency);
691
+ try {
692
+ const uploadedUrls = await Promise.all(
693
+ imageLocalFiles.map(
694
+ (filePath) => limit(async () => {
695
+ try {
696
+ console.log("[SDK] Uploading image file:", filePath);
697
+ const url = await this.uploadFileAndGetUrl(filePath);
698
+ console.log("[SDK] Image uploaded, got URL:", url);
699
+ return url;
700
+ } catch (error) {
701
+ console.error(`[SDK] Failed to upload image file ${filePath}:`, error);
702
+ throw error;
703
+ }
704
+ })
705
+ )
706
+ );
707
+ imageUrls.push(...uploadedUrls);
708
+ console.log("[SDK] All images uploaded, total URLs:", imageUrls.length);
709
+ } catch (error) {
710
+ console.error("[SDK] Error uploading image files:", error);
711
+ throw error;
712
+ }
713
+ }
714
+ if (imageUrls.length > 0) {
715
+ const jsonArray = JSON.stringify(imageUrls);
716
+ console.log("[SDK] Sending user_images as JSON:", jsonArray);
717
+ formData.append("user_images", jsonArray);
718
+ } else {
719
+ console.log("[SDK] No image URLs to send");
720
+ }
721
+ }
722
+ if (userVideos) {
723
+ const videosList = Array.isArray(userVideos) ? userVideos : [userVideos];
724
+ const videoLocalFiles = [];
725
+ const videoUrls = [];
726
+ videosList.forEach((item) => {
727
+ const path = String(item).trim().replace(/^<|>$/g, "");
728
+ if (URL_RE.test(path)) {
729
+ videoUrls.push(path);
730
+ } else {
731
+ videoLocalFiles.push(path);
732
+ }
733
+ });
734
+ if (videoLocalFiles.length > 0) {
735
+ const limit = pLimit(concurrency);
736
+ try {
737
+ const uploadedUrls = await Promise.all(
738
+ videoLocalFiles.map(
739
+ (filePath) => limit(async () => {
740
+ try {
741
+ return await this.uploadFileAndGetUrl(filePath);
742
+ } catch (error) {
743
+ console.error(`Failed to upload video file ${filePath}:`, error);
744
+ throw error;
745
+ }
746
+ })
747
+ )
748
+ );
749
+ videoUrls.push(...uploadedUrls);
750
+ } catch (error) {
751
+ console.error("Error uploading video files:", error);
752
+ throw error;
753
+ }
754
+ }
755
+ if (videoUrls.length > 0) {
756
+ formData.append("user_videos", JSON.stringify(videoUrls));
757
+ }
758
+ }
759
+ if (inputImages) {
760
+ const imagesList = Array.isArray(inputImages) ? inputImages : [inputImages];
761
+ const localFiles = [];
762
+ const imageUrls = [];
763
+ imagesList.forEach((item) => {
764
+ const path = String(item).trim().replace(/^<|>$/g, "");
765
+ if (URL_RE.test(path)) {
766
+ imageUrls.push(path);
767
+ } else {
768
+ localFiles.push(path);
769
+ }
770
+ });
771
+ if (localFiles.length > 0) {
772
+ const limit = pLimit(concurrency);
773
+ const uploadedUrls = await Promise.all(
774
+ localFiles.map((filePath) => limit(() => this.uploadFileAndGetUrl(filePath)))
775
+ );
776
+ imageUrls.push(...uploadedUrls);
777
+ }
778
+ if (imageUrls.length > 0) {
779
+ formData.append("input_images", JSON.stringify(imageUrls));
780
+ }
429
781
  }
430
782
  if (uploads) {
431
783
  const list = typeof uploads === "string" ? [uploads] : [...uploads];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@golpoai/sdk",
3
- "version": "0.1.10",
3
+ "version": "1.0.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {