studio-lumiere-cli 0.1.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/.agents/skills/annotate-image/SKILL.md +99 -0
- package/.agents/skills/config-troubleshooting/SKILL.md +97 -0
- package/.agents/skills/generate-images/SKILL.md +667 -0
- package/.agents/skills/generate-video/SKILL.md +328 -0
- package/.agents/skills/image-grid/SKILL.md +96 -0
- package/.agents/skills/image-overlay/SKILL.md +66 -0
- package/.agents/skills/image-overlay/agents/openai.yaml +4 -0
- package/.agents/skills/image-overlay/scripts/overlay-image.js +218 -0
- package/.agents/skills/muse-management/SKILL.md +232 -0
- package/.agents/skills/refine-images/SKILL.md +192 -0
- package/.agents/skills/tired-girl/SKILL.md +131 -0
- package/.env.example +2 -0
- package/AGENTS.md +66 -0
- package/README.md +96 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +214 -0
- package/dist/cli.js.map +1 -0
- package/dist/clients/geminiClient.d.ts +37 -0
- package/dist/clients/geminiClient.js +129 -0
- package/dist/clients/geminiClient.js.map +1 -0
- package/dist/config/constants.d.ts +63 -0
- package/dist/config/constants.js +1005 -0
- package/dist/config/constants.js.map +1 -0
- package/dist/config/options.d.ts +1 -0
- package/dist/config/options.js +2 -0
- package/dist/config/options.js.map +1 -0
- package/dist/config/templates.d.ts +3 -0
- package/dist/config/templates.js +4 -0
- package/dist/config/templates.js.map +1 -0
- package/dist/config/tiredGirl.d.ts +3 -0
- package/dist/config/tiredGirl.js +9 -0
- package/dist/config/tiredGirl.js.map +1 -0
- package/dist/image/annotate.d.ts +2 -0
- package/dist/image/annotate.js +119 -0
- package/dist/image/annotate.js.map +1 -0
- package/dist/image/grid.d.ts +2 -0
- package/dist/image/grid.js +44 -0
- package/dist/image/grid.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/pipelines/createMuse.d.ts +3 -0
- package/dist/pipelines/createMuse.js +49 -0
- package/dist/pipelines/createMuse.js.map +1 -0
- package/dist/pipelines/generateImages.d.ts +2 -0
- package/dist/pipelines/generateImages.js +140 -0
- package/dist/pipelines/generateImages.js.map +1 -0
- package/dist/pipelines/generateTiredGirl.d.ts +2 -0
- package/dist/pipelines/generateTiredGirl.js +73 -0
- package/dist/pipelines/generateTiredGirl.js.map +1 -0
- package/dist/pipelines/generateVideo.d.ts +2 -0
- package/dist/pipelines/generateVideo.js +27 -0
- package/dist/pipelines/generateVideo.js.map +1 -0
- package/dist/pipelines/refineImage.d.ts +2 -0
- package/dist/pipelines/refineImage.js +28 -0
- package/dist/pipelines/refineImage.js.map +1 -0
- package/dist/pipelines/resolve.d.ts +11 -0
- package/dist/pipelines/resolve.js +74 -0
- package/dist/pipelines/resolve.js.map +1 -0
- package/dist/pipelines/upscaleImage.d.ts +2 -0
- package/dist/pipelines/upscaleImage.js +23 -0
- package/dist/pipelines/upscaleImage.js.map +1 -0
- package/dist/prompt/buildPrompt.d.ts +4 -0
- package/dist/prompt/buildPrompt.js +322 -0
- package/dist/prompt/buildPrompt.js.map +1 -0
- package/dist/prompt/tiredGirlPrompt.d.ts +3 -0
- package/dist/prompt/tiredGirlPrompt.js +33 -0
- package/dist/prompt/tiredGirlPrompt.js.map +1 -0
- package/dist/storage/files.d.ts +15 -0
- package/dist/storage/files.js +34 -0
- package/dist/storage/files.js.map +1 -0
- package/dist/storage/museStore.d.ts +5 -0
- package/dist/storage/museStore.js +26 -0
- package/dist/storage/museStore.js.map +1 -0
- package/dist/types.d.ts +169 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/examples/generate.d.ts +1 -0
- package/examples/generate.js +28 -0
- package/examples/generate.js.map +1 -0
- package/examples/generate.ts +30 -0
- package/examples/muse.d.ts +1 -0
- package/examples/muse.js +18 -0
- package/examples/muse.js.map +1 -0
- package/examples/muse.ts +20 -0
- package/examples/video.d.ts +1 -0
- package/examples/video.js +18 -0
- package/examples/video.js.map +1 -0
- package/examples/video.ts +20 -0
- package/logo-round.png +0 -0
- package/logo.jpeg +0 -0
- package/package.json +27 -0
- package/src/cli.ts +259 -0
- package/src/clients/geminiClient.ts +168 -0
- package/src/config/constants.ts +1105 -0
- package/src/config/options.ts +15 -0
- package/src/config/templates.ts +4 -0
- package/src/config/tiredGirl.ts +11 -0
- package/src/image/annotate.ts +139 -0
- package/src/image/grid.ts +58 -0
- package/src/index.ts +27 -0
- package/src/pipelines/createMuse.ts +76 -0
- package/src/pipelines/generateImages.ts +203 -0
- package/src/pipelines/generateTiredGirl.ts +86 -0
- package/src/pipelines/generateVideo.ts +36 -0
- package/src/pipelines/refineImage.ts +36 -0
- package/src/pipelines/resolve.ts +88 -0
- package/src/pipelines/upscaleImage.ts +30 -0
- package/src/prompt/buildPrompt.ts +380 -0
- package/src/prompt/tiredGirlPrompt.ts +35 -0
- package/src/storage/files.ts +41 -0
- package/src/storage/museStore.ts +31 -0
- package/src/types.ts +198 -0
- package/tsconfig.json +15 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import {
|
|
5
|
+
generateImages,
|
|
6
|
+
refineImage,
|
|
7
|
+
upscaleImage,
|
|
8
|
+
generateTiredGirl,
|
|
9
|
+
createMuse,
|
|
10
|
+
generateVideo,
|
|
11
|
+
annotateImage,
|
|
12
|
+
createImageGrid,
|
|
13
|
+
TEMPLATES,
|
|
14
|
+
ETHNICITIES,
|
|
15
|
+
SKIN_TONES,
|
|
16
|
+
HAIR_COLORS,
|
|
17
|
+
BACKGROUNDS,
|
|
18
|
+
BACKGROUND_TYPES,
|
|
19
|
+
VIBES,
|
|
20
|
+
RESOLUTIONS,
|
|
21
|
+
OCCASIONS
|
|
22
|
+
} from "./index.js";
|
|
23
|
+
import { LumiereConfig } from "./types.js";
|
|
24
|
+
|
|
25
|
+
const program = new Command();
|
|
26
|
+
|
|
27
|
+
const parseList = (value: string): string[] => value.split(",").map((v) => v.trim()).filter(Boolean);
|
|
28
|
+
|
|
29
|
+
const loadConfig = (): LumiereConfig => {
|
|
30
|
+
const apiKey = process.env.GEMINI_API_KEY;
|
|
31
|
+
if (!apiKey) {
|
|
32
|
+
throw new Error("Missing GEMINI_API_KEY in environment.");
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
apiKey,
|
|
36
|
+
outputDir: process.env.LUMIERE_OUTPUT_DIR ?? "outputs"
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
program
|
|
41
|
+
.name("lumiere")
|
|
42
|
+
.description("Local Studio Lumiere SDK + CLI")
|
|
43
|
+
.version("0.1.0");
|
|
44
|
+
|
|
45
|
+
program
|
|
46
|
+
.command("list")
|
|
47
|
+
.argument("type", "templates|ethnicities|skin-tones|hair-colors|backgrounds|background-types|vibes|resolutions|occasions")
|
|
48
|
+
.action((type) => {
|
|
49
|
+
const map: Record<string, unknown> = {
|
|
50
|
+
templates: TEMPLATES,
|
|
51
|
+
ethnicities: ETHNICITIES,
|
|
52
|
+
"skin-tones": SKIN_TONES,
|
|
53
|
+
"hair-colors": HAIR_COLORS,
|
|
54
|
+
backgrounds: BACKGROUNDS,
|
|
55
|
+
"background-types": BACKGROUND_TYPES,
|
|
56
|
+
vibes: VIBES,
|
|
57
|
+
resolutions: RESOLUTIONS,
|
|
58
|
+
occasions: OCCASIONS
|
|
59
|
+
};
|
|
60
|
+
const payload = map[type];
|
|
61
|
+
if (!payload) {
|
|
62
|
+
throw new Error(`Unknown list type: ${type}`);
|
|
63
|
+
}
|
|
64
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
program
|
|
68
|
+
.command("generate")
|
|
69
|
+
.requiredOption("--images <paths>", "Comma-separated image paths")
|
|
70
|
+
.requiredOption("--template <id>", "Template id")
|
|
71
|
+
.option("--detail <id>")
|
|
72
|
+
.option("--secondary-detail <id>")
|
|
73
|
+
.option("--tertiary-detail <id>")
|
|
74
|
+
.option("--ethnicity <id>")
|
|
75
|
+
.option("--skin-tone <id>")
|
|
76
|
+
.option("--hair-color <id>")
|
|
77
|
+
.option("--background <id>")
|
|
78
|
+
.option("--background-type <id>")
|
|
79
|
+
.option("--vibe <id>")
|
|
80
|
+
.option("--resolution <id>")
|
|
81
|
+
.option("--occasion <id>")
|
|
82
|
+
.option("--quantity <n>", "Number of images", "1")
|
|
83
|
+
.option("--muse-id <id>")
|
|
84
|
+
.option("--muse-images <paths>", "Comma-separated muse image paths")
|
|
85
|
+
.option("--no-enhance", "Skip prompt enhancement")
|
|
86
|
+
.action(async (options) => {
|
|
87
|
+
const config = loadConfig();
|
|
88
|
+
const result = await generateImages(config, {
|
|
89
|
+
inputImages: parseList(options.images),
|
|
90
|
+
quantity: Number(options.quantity),
|
|
91
|
+
selections: {
|
|
92
|
+
templateId: options.template,
|
|
93
|
+
detailId: options.detail,
|
|
94
|
+
secondaryDetailId: options.secondaryDetail,
|
|
95
|
+
tertiaryDetailId: options.tertiaryDetail,
|
|
96
|
+
ethnicityId: options.ethnicity,
|
|
97
|
+
skinToneId: options.skinTone,
|
|
98
|
+
hairColorId: options.hairColor,
|
|
99
|
+
backgroundId: options.background,
|
|
100
|
+
backgroundTypeId: options.backgroundType,
|
|
101
|
+
vibeId: options.vibe,
|
|
102
|
+
resolutionId: options.resolution,
|
|
103
|
+
occasionId: options.occasion
|
|
104
|
+
},
|
|
105
|
+
museId: options.museId,
|
|
106
|
+
museImagePaths: options.museImages ? parseList(options.museImages) : undefined,
|
|
107
|
+
enhancePrompt: options.enhance
|
|
108
|
+
});
|
|
109
|
+
console.log(JSON.stringify(result, null, 2));
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
program
|
|
113
|
+
.command("refine")
|
|
114
|
+
.requiredOption("--image <path>")
|
|
115
|
+
.requiredOption("--instruction <text>")
|
|
116
|
+
.option("--size <percent>")
|
|
117
|
+
.action(async (options) => {
|
|
118
|
+
const config = loadConfig();
|
|
119
|
+
const output = await refineImage(config, {
|
|
120
|
+
inputImage: options.image,
|
|
121
|
+
instruction: options.instruction,
|
|
122
|
+
sizeAdjustment: options.size ? Number(options.size) : undefined
|
|
123
|
+
});
|
|
124
|
+
console.log(output);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
program
|
|
128
|
+
.command("upscale")
|
|
129
|
+
.requiredOption("--image <path>")
|
|
130
|
+
.option("--scale <number>", "Scale factor", "2")
|
|
131
|
+
.action(async (options) => {
|
|
132
|
+
const config = loadConfig();
|
|
133
|
+
const output = await upscaleImage(config, {
|
|
134
|
+
inputImage: options.image,
|
|
135
|
+
scale: Number(options.scale)
|
|
136
|
+
});
|
|
137
|
+
console.log(output);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
program
|
|
141
|
+
.command("muse")
|
|
142
|
+
.requiredOption("--name <name>")
|
|
143
|
+
.requiredOption("--source <path>")
|
|
144
|
+
.option("--variations <n>", "Number of variations", "3")
|
|
145
|
+
.action(async (options) => {
|
|
146
|
+
const config = loadConfig();
|
|
147
|
+
const result = await createMuse(config, {
|
|
148
|
+
name: options.name,
|
|
149
|
+
sourceImage: options.source,
|
|
150
|
+
variations: Number(options.variations)
|
|
151
|
+
});
|
|
152
|
+
console.log(JSON.stringify(result, null, 2));
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
program
|
|
156
|
+
.command("video")
|
|
157
|
+
.requiredOption("--prompt <text>")
|
|
158
|
+
.option("--aspect <ratio>")
|
|
159
|
+
.option("--duration <seconds>")
|
|
160
|
+
.action(async (options) => {
|
|
161
|
+
const config = loadConfig();
|
|
162
|
+
const result = await generateVideo(config, {
|
|
163
|
+
prompt: options.prompt,
|
|
164
|
+
aspectRatio: options.aspect,
|
|
165
|
+
durationSeconds: options.duration ? Number(options.duration) : undefined
|
|
166
|
+
});
|
|
167
|
+
console.log(JSON.stringify(result, null, 2));
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
program
|
|
171
|
+
.command("tired-girl")
|
|
172
|
+
.option("--muse-id <id>", "Muse ID (preferred)")
|
|
173
|
+
.option("--image <path>", "Single reference image (used if no muse-id)")
|
|
174
|
+
.option("--styles <ids>", "Comma-separated style IDs")
|
|
175
|
+
.option("--quantity <n>", "Number of variants", "1")
|
|
176
|
+
.action(async (options) => {
|
|
177
|
+
const config = loadConfig();
|
|
178
|
+
const result = await generateTiredGirl(config, {
|
|
179
|
+
museId: options.museId,
|
|
180
|
+
inputImage: options.image,
|
|
181
|
+
styleIds: options.styles ? parseList(options.styles) : undefined,
|
|
182
|
+
quantity: Number(options.quantity)
|
|
183
|
+
});
|
|
184
|
+
console.log(JSON.stringify(result, null, 2));
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
program
|
|
188
|
+
.command("annotate")
|
|
189
|
+
.requiredOption("--input <path>", "Input image path")
|
|
190
|
+
.requiredOption("--output <path>", "Output image path")
|
|
191
|
+
.requiredOption("--text <text>", "Overlay text")
|
|
192
|
+
.option("--font <name>", "Font family (default: Poppins)")
|
|
193
|
+
.option("--size <px>", "Font size in px")
|
|
194
|
+
.option("--weight <n>", "Font weight (default: 700)")
|
|
195
|
+
.option("--color <hex>", "Text color (default: #FFFFFF)")
|
|
196
|
+
.option("--stroke-color <value>", "Stroke/outline color (default: rgba(0,0,0,0.6))")
|
|
197
|
+
.option("--stroke-width <px>", "Stroke/outline width (default: fontSize * 0.08)")
|
|
198
|
+
.option("--banner", "Enable semi-transparent banner behind text")
|
|
199
|
+
.option("--banner-color <value>", "Banner color (default: rgba(0,0,0,0.45))")
|
|
200
|
+
.option("--banner-padding <px>", "Banner padding (default: fontSize * 0.6)")
|
|
201
|
+
.option("--banner-radius <px>", "Banner corner radius (default: fontSize * 0.4)")
|
|
202
|
+
.option(
|
|
203
|
+
"--position <pos>",
|
|
204
|
+
"top-center|top-left|top-right|bottom-center|bottom-center-high|bottom-left|bottom-right|center"
|
|
205
|
+
)
|
|
206
|
+
.option("--padding <px>", "Padding from edges")
|
|
207
|
+
.option("--max-width <ratio>", "Max width ratio (0-1)")
|
|
208
|
+
.action(async (options) => {
|
|
209
|
+
await annotateImage(
|
|
210
|
+
options.input,
|
|
211
|
+
options.output,
|
|
212
|
+
options.text,
|
|
213
|
+
{
|
|
214
|
+
fontFamily: options.font,
|
|
215
|
+
fontSize: options.size ? Number(options.size) : undefined,
|
|
216
|
+
fontWeight: options.weight ? Number(options.weight) : undefined,
|
|
217
|
+
color: options.color,
|
|
218
|
+
strokeColor: options.strokeColor,
|
|
219
|
+
strokeWidth: options.strokeWidth ? Number(options.strokeWidth) : undefined,
|
|
220
|
+
banner: options.banner ?? false,
|
|
221
|
+
bannerColor: options.bannerColor,
|
|
222
|
+
bannerPadding: options.bannerPadding ? Number(options.bannerPadding) : undefined,
|
|
223
|
+
bannerRadius: options.bannerRadius ? Number(options.bannerRadius) : undefined,
|
|
224
|
+
position: options.position,
|
|
225
|
+
padding: options.padding ? Number(options.padding) : undefined,
|
|
226
|
+
maxWidthRatio: options.maxWidth ? Number(options.maxWidth) : undefined
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
console.log(options.output);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
program
|
|
233
|
+
.command("grid")
|
|
234
|
+
.requiredOption("--inputs <paths>", "Comma-separated image paths")
|
|
235
|
+
.requiredOption("--output <path>", "Output image path")
|
|
236
|
+
.option("--columns <n>", "Number of columns", "2")
|
|
237
|
+
.option("--rows <n>", "Number of rows", "2")
|
|
238
|
+
.option("--padding <px>", "Padding between tiles (default: 20)")
|
|
239
|
+
.option("--background <hex>", "Background color (default: #000000)")
|
|
240
|
+
.option("--tile-width <px>", "Fixed tile width")
|
|
241
|
+
.option("--tile-height <px>", "Fixed tile height")
|
|
242
|
+
.action(async (options) => {
|
|
243
|
+
await createImageGrid(
|
|
244
|
+
parseList(options.inputs),
|
|
245
|
+
options.output,
|
|
246
|
+
{
|
|
247
|
+
columns: Number(options.columns),
|
|
248
|
+
rows: Number(options.rows),
|
|
249
|
+
padding: options.padding ? Number(options.padding) : undefined,
|
|
250
|
+
background: options.background,
|
|
251
|
+
tileWidth: options.tileWidth ? Number(options.tileWidth) : undefined,
|
|
252
|
+
tileHeight: options.tileHeight ? Number(options.tileHeight) : undefined
|
|
253
|
+
}
|
|
254
|
+
);
|
|
255
|
+
console.log(options.output);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
program.parseAsync();
|
|
259
|
+
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { GoogleGenAI } from "@google/genai";
|
|
2
|
+
import { AspectRatio, LumiereConfig } from "../types.js";
|
|
3
|
+
|
|
4
|
+
type RetryConfig = {
|
|
5
|
+
maxRetries: number;
|
|
6
|
+
baseDelayMs: number;
|
|
7
|
+
maxDelayMs: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type ModelConfig = {
|
|
11
|
+
prompt: string;
|
|
12
|
+
image: string;
|
|
13
|
+
video: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
17
|
+
|
|
18
|
+
const isRetryableError = (error: unknown): boolean => {
|
|
19
|
+
const message = String((error as any)?.message || "").toLowerCase();
|
|
20
|
+
return (
|
|
21
|
+
message.includes("429") ||
|
|
22
|
+
message.includes("503") ||
|
|
23
|
+
message.includes("overloaded") ||
|
|
24
|
+
message.includes("unavailable") ||
|
|
25
|
+
message.includes("timeout")
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const withRetry = async <T>(fn: () => Promise<T>, config: RetryConfig): Promise<T> => {
|
|
30
|
+
let lastError: unknown;
|
|
31
|
+
for (let attempt = 0; attempt <= config.maxRetries; attempt += 1) {
|
|
32
|
+
try {
|
|
33
|
+
return await fn();
|
|
34
|
+
} catch (error) {
|
|
35
|
+
lastError = error;
|
|
36
|
+
if (attempt === config.maxRetries || !isRetryableError(error)) {
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
const delay = Math.min(config.baseDelayMs * 2 ** attempt, config.maxDelayMs);
|
|
40
|
+
await sleep(delay + Math.random() * 0.3 * delay);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
throw lastError;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export class GeminiClient {
|
|
47
|
+
private ai: GoogleGenAI;
|
|
48
|
+
private models: ModelConfig;
|
|
49
|
+
private retry: RetryConfig;
|
|
50
|
+
|
|
51
|
+
constructor(config: LumiereConfig) {
|
|
52
|
+
this.ai = new GoogleGenAI({ apiKey: config.apiKey });
|
|
53
|
+
this.models = {
|
|
54
|
+
prompt: config.models?.prompt ?? "gemini-3-flash-preview",
|
|
55
|
+
image: config.models?.image ?? "gemini-3-pro-image-preview",
|
|
56
|
+
video: config.models?.video ?? "veo-3.1-generate-preview"
|
|
57
|
+
};
|
|
58
|
+
this.retry = {
|
|
59
|
+
maxRetries: config.retry?.maxRetries ?? 3,
|
|
60
|
+
baseDelayMs: config.retry?.baseDelayMs ?? 1500,
|
|
61
|
+
maxDelayMs: config.retry?.maxDelayMs ?? 12000
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async generateText(prompt: string): Promise<string> {
|
|
66
|
+
return withRetry(async () => {
|
|
67
|
+
const response = await this.ai.models.generateContent({
|
|
68
|
+
model: this.models.prompt,
|
|
69
|
+
contents: prompt
|
|
70
|
+
});
|
|
71
|
+
return response.text ?? "";
|
|
72
|
+
}, this.retry);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async generateEnhancedPrompt(params: {
|
|
76
|
+
parts: Array<{ text?: string; inlineData?: { mimeType: string; data: string } }>;
|
|
77
|
+
systemInstruction?: string;
|
|
78
|
+
}): Promise<string> {
|
|
79
|
+
return withRetry(async () => {
|
|
80
|
+
const response = await this.ai.models.generateContent({
|
|
81
|
+
model: this.models.prompt,
|
|
82
|
+
contents: [
|
|
83
|
+
{
|
|
84
|
+
role: "user",
|
|
85
|
+
parts: params.parts
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
config: {
|
|
89
|
+
systemInstruction: params.systemInstruction
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
return response.text ?? "";
|
|
93
|
+
}, this.retry);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async generateImage(params: {
|
|
97
|
+
parts: Array<{ text?: string; inlineData?: { mimeType: string; data: string } }>;
|
|
98
|
+
aspectRatio?: AspectRatio;
|
|
99
|
+
imageSize?: "1K" | "2K" | "4K";
|
|
100
|
+
}): Promise<string[]> {
|
|
101
|
+
return withRetry(async () => {
|
|
102
|
+
const response = await this.ai.models.generateContent({
|
|
103
|
+
model: this.models.image,
|
|
104
|
+
contents: [
|
|
105
|
+
{
|
|
106
|
+
role: "user",
|
|
107
|
+
parts: params.parts
|
|
108
|
+
}
|
|
109
|
+
],
|
|
110
|
+
config: {
|
|
111
|
+
imageConfig: {
|
|
112
|
+
aspectRatio: params.aspectRatio,
|
|
113
|
+
imageSize: params.imageSize ?? "2K"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const images: string[] = [];
|
|
119
|
+
const parts = response.candidates?.[0]?.content?.parts ?? [];
|
|
120
|
+
for (const part of parts) {
|
|
121
|
+
const inlineData = (part as any)?.inlineData;
|
|
122
|
+
if (inlineData?.data) {
|
|
123
|
+
images.push(`data:${inlineData.mimeType ?? "image/png"};base64,${inlineData.data}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (images.length === 0) {
|
|
127
|
+
throw new Error("No image data returned from Gemini.");
|
|
128
|
+
}
|
|
129
|
+
return images;
|
|
130
|
+
}, this.retry);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async generateVideo(params: {
|
|
134
|
+
prompt: string;
|
|
135
|
+
aspectRatio?: AspectRatio;
|
|
136
|
+
durationSeconds?: number;
|
|
137
|
+
}): Promise<{ operationName?: string; videoBytes?: Uint8Array }>{
|
|
138
|
+
return withRetry(async () => {
|
|
139
|
+
let operation = await this.ai.models.generateVideos({
|
|
140
|
+
model: this.models.video,
|
|
141
|
+
prompt: params.prompt,
|
|
142
|
+
config: {
|
|
143
|
+
aspectRatio: params.aspectRatio,
|
|
144
|
+
durationSeconds: params.durationSeconds
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
while (!operation.done) {
|
|
149
|
+
await sleep(10000);
|
|
150
|
+
operation = await this.ai.operations.getVideosOperation({ operation });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const generated = operation.response?.generatedVideos?.[0]?.video;
|
|
154
|
+
if (!generated) {
|
|
155
|
+
return { operationName: operation.name };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const rawBytes = (generated as any).videoBytes;
|
|
159
|
+
let videoBytes: Uint8Array | undefined;
|
|
160
|
+
if (rawBytes) {
|
|
161
|
+
videoBytes = typeof rawBytes === "string" ? Buffer.from(rawBytes, "base64") : rawBytes;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return { operationName: operation.name, videoBytes };
|
|
165
|
+
}, this.retry);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|