@j-o-r/hello-dave 0.0.10 → 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/README.md +2 -0
- package/README.md.bak.1779452127 +240 -0
- package/TODO.md +30 -8
- package/agents/code_agent.js +6 -6
- package/agents/daisy_agent.js +10 -7
- package/agents/minimax.js +173 -0
- package/agents/stability.js +173 -0
- package/bin/codeDave +1 -1
- package/bin/dave.js +1 -1
- package/docs/music-toolsets.md +137 -0
- package/docs/plans/minimax-music-generation.md +80 -0
- package/docs/plans/unified-agent-architecture.md +146 -0
- package/docs/plans/websocket-streaming-plan.md.bak +317 -0
- package/docs/prompt/task_clarification_and_documentation.md +35 -0
- package/lib/API/minimax/ImageToolset.js +169 -0
- package/lib/API/minimax/MusicToolset.js +290 -0
- package/lib/API/minimax/VideoToolset.js +296 -0
- package/lib/API/minimax/image.generation.md +239 -0
- package/lib/API/minimax/image.js +219 -0
- package/lib/API/minimax/image.to.image.md +257 -0
- package/lib/API/minimax/index.js +16 -0
- package/lib/API/minimax/music.cover.preprocess.md +206 -0
- package/lib/API/minimax/music.generation.md +346 -0
- package/lib/API/minimax/music.js +257 -0
- package/lib/API/minimax/music.lyrics.generation.md +205 -0
- package/lib/API/minimax/video.download.md +133 -0
- package/lib/API/minimax/video.first.last.image.md +186 -0
- package/lib/API/minimax/video.from.image.md +206 -0
- package/lib/API/minimax/video.from.subject.md +164 -0
- package/lib/API/minimax/video.generation.md +192 -0
- package/lib/API/minimax/video.js +339 -0
- package/lib/API/minimax/video.query.md +128 -0
- package/lib/API/stability.ai/ImageToolset.js +357 -0
- package/lib/API/stability.ai/MusicToolset.js +302 -0
- package/lib/API/stability.ai/audio-3.md +205 -0
- package/lib/API/stability.ai/audio.js +679 -0
- package/lib/API/stability.ai/image.js +911 -0
- package/lib/API/stability.ai/image.md +271 -0
- package/lib/API/stability.ai/index.js +11 -0
- package/lib/API/stability.ai/openapi.json +17118 -0
- package/lib/API/x.ai/ImageToolset.js +165 -0
- package/lib/API/x.ai/image.editing.md +86 -0
- package/lib/API/x.ai/image.js +393 -0
- package/lib/API/x.ai/image.md +213 -0
- package/lib/API/x.ai/image.to.generation.md +494 -0
- package/lib/API/x.ai/image.to.video.md +23 -0
- package/lib/API/x.ai/index.js +9 -0
- package/lib/AgentManager.js +1 -1
- package/lib/CdnToolset.js +191 -0
- package/lib/ToolSet.js +19 -1
- package/lib/cdn.js +373 -0
- package/lib/fafs.js +3 -1
- package/lib/genericToolset.js +43 -166
- package/lib/index.js +9 -1
- package/package.json +2 -2
- package/types/API/minimax/ImageToolset.d.ts +3 -0
- package/types/API/minimax/MusicToolset.d.ts +3 -0
- package/types/API/minimax/VideoToolset.d.ts +3 -0
- package/types/API/minimax/image.d.ts +109 -0
- package/types/API/minimax/index.d.ts +15 -0
- package/types/API/minimax/music.d.ts +46 -0
- package/types/API/minimax/video.d.ts +165 -0
- package/types/API/stability.ai/ImageToolset.d.ts +3 -0
- package/types/API/stability.ai/MusicToolset.d.ts +3 -0
- package/types/API/stability.ai/audio.d.ts +193 -0
- package/types/API/stability.ai/image.d.ts +274 -0
- package/types/API/stability.ai/index.d.ts +11 -0
- package/types/API/x.ai/ImageToolset.d.ts +3 -0
- package/types/API/x.ai/image.d.ts +82 -0
- package/types/API/x.ai/index.d.ts +9 -0
- package/types/AgentManager.d.ts +1 -1
- package/types/CdnToolset.d.ts +20 -0
- package/types/ToolSet.d.ts +8 -0
- package/types/cdn.d.ts +141 -0
- package/types/index.d.ts +8 -2
- package/docs/multi-agent-clusters.md.bak +0 -229
|
@@ -0,0 +1,911 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file lib/API/stability.ai/image.js
|
|
3
|
+
* @module stability.ai/image
|
|
4
|
+
* @description Pure HTTP wrapper for the Stability AI Stable Image API (v2beta).
|
|
5
|
+
* Fully aligned with the official specifications extracted from:
|
|
6
|
+
* - lib/API/stability.ai/image.md
|
|
7
|
+
* - lib/API/stability.ai/openapi.json
|
|
8
|
+
*
|
|
9
|
+
* This is a clean, production-ready library providing core image workflows:
|
|
10
|
+
* 1. generateUltra() → Highest quality photorealistic generation
|
|
11
|
+
* 2. generateCore() → Fast & affordable generation
|
|
12
|
+
* 3. generateSD3() → Stable Diffusion 3.5 (text-to-image + image-to-image)
|
|
13
|
+
* 4. upscaleConservative() → Detail-preserving upscale to 4MP
|
|
14
|
+
* 5. editErase() → Remove objects using mask or alpha
|
|
15
|
+
* 6. editInpaint() → Intelligent inpainting / search-and-replace
|
|
16
|
+
* 7. editOutpaint() → Expand image in any direction
|
|
17
|
+
* 8. editSearchAndReplace() → Auto-segment and replace object
|
|
18
|
+
* 9. editSearchAndRecolor() → Change color of specific object
|
|
19
|
+
* 10. editRemoveBackground() → Segment and remove background
|
|
20
|
+
* 11. editReplaceBackgroundAndRelight() → Swap background + relight (async)
|
|
21
|
+
* 12. fetchImageResult() → Manual polling for async generation results
|
|
22
|
+
*
|
|
23
|
+
* Many endpoints support async (202 + polling via /v2beta/results/{id}).
|
|
24
|
+
* High-level methods handle submission + internal polling and return
|
|
25
|
+
* a local image file path plus rich metadata.
|
|
26
|
+
*
|
|
27
|
+
* Key Technical Details:
|
|
28
|
+
* - Uses `multipart/form-data` for all image uploads.
|
|
29
|
+
* - Supports local paths, remote URLs, Buffers, and Blobs.
|
|
30
|
+
* - Binary (`image/*`) or JSON (`application/json` for base64) responses.
|
|
31
|
+
* - Output formats: png (default), jpeg, webp.
|
|
32
|
+
* - Comprehensive error handling matching the spec.
|
|
33
|
+
*
|
|
34
|
+
* Usage Pattern (recommended):
|
|
35
|
+
* ```js
|
|
36
|
+
* import { generateUltra, editInpaint } from './image.js';
|
|
37
|
+
* const result = await generateUltra('majestic mountain landscape at sunset');
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @see {@link https://platform.stability.ai/docs} Official Stability AI docs
|
|
41
|
+
* @see {@link ./image.md} Detailed API specification
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @constant {string} BASE_URL
|
|
46
|
+
* @description Base URL for the Stability AI API v2beta image endpoints.
|
|
47
|
+
*/
|
|
48
|
+
const BASE_URL = 'https://api.stability.ai';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @constant {string} TMP_DIR
|
|
52
|
+
* @description Local temporary directory for storing generated and reference image files.
|
|
53
|
+
* Located at `<cwd>/.cache/stability`.
|
|
54
|
+
*/
|
|
55
|
+
const TMP_DIR = path.join(process.cwd(), '.cache', 'stability');
|
|
56
|
+
|
|
57
|
+
import { request as doRequest } from '@j-o-r/apiserver';
|
|
58
|
+
import fs from 'fs/promises';
|
|
59
|
+
import path from 'path';
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Builds authenticated headers for Stability AI requests.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} [acceptHeader='image/*'] - Accept header value.
|
|
65
|
+
* Use `'image/*'` for binary image response or `'application/json'` for base64 JSON.
|
|
66
|
+
* @returns {Object} Headers object containing Authorization (Bearer) and Accept.
|
|
67
|
+
* @throws {Error} If `STABILITY_API_KEY` environment variable is not set.
|
|
68
|
+
*/
|
|
69
|
+
const getHeaders = (acceptHeader = 'image/*') => {
|
|
70
|
+
if (!process.env.STABILITY_API_KEY) {
|
|
71
|
+
throw new Error('Missing STABILITY_API_KEY! Please export STABILITY_API_KEY=your_key');
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
'Authorization': `Bearer ${process.env.STABILITY_API_KEY}`,
|
|
75
|
+
'Accept': acceptHeader
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Ensures the temporary directory for image files exists.
|
|
81
|
+
*
|
|
82
|
+
* @async
|
|
83
|
+
* @returns {Promise<void>}
|
|
84
|
+
*/
|
|
85
|
+
async function ensureTmpDir() {
|
|
86
|
+
await fs.mkdir(TMP_DIR, { recursive: true });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Saves image data (Buffer, base64 string, URL, Blob, or ArrayBuffer) to a local file.
|
|
91
|
+
*
|
|
92
|
+
* @async
|
|
93
|
+
* @param {Buffer|string|Blob|ArrayBuffer} imageData - Image content to save.
|
|
94
|
+
* @param {string} [filenamePrefix='stability-image'] - Prefix for the generated filename.
|
|
95
|
+
* @param {string} [ext='png'] - File extension (`png`, `jpeg`, `webp`).
|
|
96
|
+
* @returns {Promise<string>} Absolute local file path of the saved image.
|
|
97
|
+
* @throws {Error} For unsupported formats or download failures.
|
|
98
|
+
*/
|
|
99
|
+
async function saveImageToLocal(imageData, filenamePrefix = 'stability-image', ext = 'png') {
|
|
100
|
+
await ensureTmpDir();
|
|
101
|
+
|
|
102
|
+
const filename = `${filenamePrefix}-${Date.now()}.${ext}`;
|
|
103
|
+
const localPath = path.join(TMP_DIR, filename);
|
|
104
|
+
|
|
105
|
+
let buffer;
|
|
106
|
+
if (typeof imageData === 'string') {
|
|
107
|
+
if (imageData.startsWith('http')) {
|
|
108
|
+
const response = await fetch(imageData);
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
throw new Error(`Failed to download image: ${response.status} ${response.statusText}`);
|
|
111
|
+
}
|
|
112
|
+
buffer = Buffer.from(await response.arrayBuffer());
|
|
113
|
+
} else if (imageData.match(/^[A-Za-z0-9+/=]+$/)) {
|
|
114
|
+
buffer = Buffer.from(imageData, 'base64');
|
|
115
|
+
} else {
|
|
116
|
+
throw new Error('Unsupported imageData string format');
|
|
117
|
+
}
|
|
118
|
+
} else if (imageData instanceof Buffer) {
|
|
119
|
+
buffer = imageData;
|
|
120
|
+
} else if (imageData instanceof Blob || imageData instanceof ArrayBuffer) {
|
|
121
|
+
buffer = Buffer.from(await (imageData instanceof Blob ? imageData.arrayBuffer() : imageData));
|
|
122
|
+
} else {
|
|
123
|
+
throw new Error('Unsupported imageData type for saving');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
await fs.writeFile(localPath, buffer);
|
|
127
|
+
return localPath;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* ============================================================
|
|
131
|
+
INTERNAL: Submit image generation request
|
|
132
|
+
============================================================ */
|
|
133
|
+
|
|
134
|
+
async function submitImageGeneration(endpoint, formData, acceptHeader = 'image/*') {
|
|
135
|
+
const url = `${BASE_URL}/v2beta/${endpoint}`;
|
|
136
|
+
const headers = getHeaders(acceptHeader);
|
|
137
|
+
|
|
138
|
+
const res = await doRequest(url, 'POST', headers, formData);
|
|
139
|
+
|
|
140
|
+
if (res.status === 200 || res.status === 202) {
|
|
141
|
+
if (res.response?.id) {
|
|
142
|
+
return res.response.id;
|
|
143
|
+
}
|
|
144
|
+
// Some endpoints return the image directly on 200
|
|
145
|
+
return res;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (res.status >= 400) {
|
|
149
|
+
throw new Error(`Stability API error ${res.status}: ${JSON.stringify(res.response)}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
throw new Error(`Unexpected status ${res.status} from ${endpoint}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* ============================================================
|
|
156
|
+
INTERNAL: Poll for image result
|
|
157
|
+
============================================================ */
|
|
158
|
+
|
|
159
|
+
async function pollForImageResult(id, acceptHeader = 'image/*', maxAttempts = 60, intervalMs = 5000) {
|
|
160
|
+
const url = `${BASE_URL}/v2beta/results/${id}`;
|
|
161
|
+
const headers = getHeaders(acceptHeader);
|
|
162
|
+
|
|
163
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
164
|
+
const res = await doRequest(url, 'GET', headers);
|
|
165
|
+
|
|
166
|
+
if (res.status === 200) {
|
|
167
|
+
return res;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (res.status === 202) {
|
|
171
|
+
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (res.status === 404) {
|
|
176
|
+
throw new Error(`Generation ${id} not found or expired`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
throw new Error(`Poll error ${res.status}: ${JSON.stringify(res.response)}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
throw new Error(`Timeout after ${maxAttempts} attempts polling generation ${id}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* ============================================================
|
|
186
|
+
INTERNAL: Process image result
|
|
187
|
+
============================================================ */
|
|
188
|
+
|
|
189
|
+
async function processImageResult(res, options = {}) {
|
|
190
|
+
const prefix = options.filenamePrefix || 'stability-image';
|
|
191
|
+
const ext = options.output_format || 'png';
|
|
192
|
+
|
|
193
|
+
if (res.responseType === 'blob') {
|
|
194
|
+
const buffer = Buffer.from(await res.response.arrayBuffer());
|
|
195
|
+
const localPath = await saveImageToLocal(buffer, prefix, ext);
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
local_path: localPath,
|
|
199
|
+
finish_reason: res.headers.get('finish-reason') || 'SUCCESS',
|
|
200
|
+
seed: res.headers.get('seed'),
|
|
201
|
+
x_request_id: res.headers.get('x-request-id'),
|
|
202
|
+
raw: { headers: Object.fromEntries(res.headers.entries()) }
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (res.responseType === 'js' || typeof res.response === 'object') {
|
|
207
|
+
const data = res.response;
|
|
208
|
+
let localPath = null;
|
|
209
|
+
|
|
210
|
+
if (data.image && typeof data.image === 'string') {
|
|
211
|
+
localPath = await saveImageToLocal(data.image, prefix, ext);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
local_path: localPath,
|
|
216
|
+
image_base64: data.image || null,
|
|
217
|
+
seed: data.seed,
|
|
218
|
+
finish_reason: data.finish_reason || 'SUCCESS',
|
|
219
|
+
raw: data
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
raw: res.response,
|
|
225
|
+
status: res.status
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* ============================================================
|
|
230
|
+
HELPER: Append image file/URL to FormData
|
|
231
|
+
============================================================ */
|
|
232
|
+
|
|
233
|
+
async function appendImageToFormData(formData, imageInput, fieldName = 'image') {
|
|
234
|
+
if (!imageInput) {
|
|
235
|
+
throw new Error(`${fieldName} input is required (file path, URL, Buffer, or Blob)`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
let filePath;
|
|
239
|
+
let filename;
|
|
240
|
+
let mimeType = 'image/png';
|
|
241
|
+
|
|
242
|
+
if (typeof imageInput === 'string') {
|
|
243
|
+
if (imageInput.startsWith('http://') || imageInput.startsWith('https://')) {
|
|
244
|
+
const response = await fetch(imageInput);
|
|
245
|
+
if (!response.ok) {
|
|
246
|
+
throw new Error(`Failed to download reference image: ${response.status} ${response.statusText}`);
|
|
247
|
+
}
|
|
248
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
249
|
+
await ensureTmpDir();
|
|
250
|
+
const tempFilename = `temp-ref-${Date.now()}.png`;
|
|
251
|
+
filePath = path.join(TMP_DIR, tempFilename);
|
|
252
|
+
await fs.writeFile(filePath, buffer);
|
|
253
|
+
filename = tempFilename;
|
|
254
|
+
} else {
|
|
255
|
+
filePath = imageInput;
|
|
256
|
+
filename = path.basename(imageInput);
|
|
257
|
+
const ext = path.extname(imageInput).toLowerCase();
|
|
258
|
+
mimeType = ext === '.jpg' || ext === '.jpeg' ? 'image/jpeg' : ext === '.webp' ? 'image/webp' : 'image/png';
|
|
259
|
+
}
|
|
260
|
+
} else if (imageInput instanceof Buffer) {
|
|
261
|
+
await ensureTmpDir();
|
|
262
|
+
const tempFilename = `temp-image-${Date.now()}.png`;
|
|
263
|
+
filePath = path.join(TMP_DIR, tempFilename);
|
|
264
|
+
await fs.writeFile(filePath, imageInput);
|
|
265
|
+
filename = tempFilename;
|
|
266
|
+
} else if (imageInput instanceof Blob) {
|
|
267
|
+
await ensureTmpDir();
|
|
268
|
+
const buffer = Buffer.from(await imageInput.arrayBuffer());
|
|
269
|
+
const tempFilename = imageInput.name || `temp-image-${Date.now()}.png`;
|
|
270
|
+
filePath = path.join(TMP_DIR, tempFilename);
|
|
271
|
+
await fs.writeFile(filePath, buffer);
|
|
272
|
+
filename = tempFilename;
|
|
273
|
+
mimeType = imageInput.type || 'image/png';
|
|
274
|
+
} else {
|
|
275
|
+
throw new Error(`${fieldName} must be a file path (string), URL (string), Buffer, or Blob`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const buffer = await fs.readFile(filePath);
|
|
279
|
+
const blob = new Blob([buffer], { type: mimeType });
|
|
280
|
+
formData.append(fieldName, blob, filename);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/* ============================================================
|
|
284
|
+
WORKFLOW: GENERATE ULTRA
|
|
285
|
+
============================================================ */
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Generates the highest quality photorealistic image using Stable Image Ultra.
|
|
289
|
+
*
|
|
290
|
+
* Our most advanced text-to-image model based on Stable Diffusion 3.5.
|
|
291
|
+
* Produces exceptional detail, realism, and prompt adherence — ideal for professional print and large-format applications.
|
|
292
|
+
*
|
|
293
|
+
* **Constraints** (from Stable Image Ultra spec):
|
|
294
|
+
* - Prompt: Max 10,000 characters. Strong descriptive prompt recommended.
|
|
295
|
+
* - Aspect ratio: One of `1:1` (default), `16:9`, `9:16`, `21:9`, `9:21`, `2:3`, `3:2`, `4:5`, `5:4`.
|
|
296
|
+
* - Image-to-image: Provide `image` + `strength` (0–1). Image must be 64px–16,384px per side.
|
|
297
|
+
* - Output resolution: 1 megapixel (default 1024×1024).
|
|
298
|
+
* - Cost: 8 credits per successful generation.
|
|
299
|
+
* - Output formats: `png` (default), `jpeg`, `webp`.
|
|
300
|
+
*
|
|
301
|
+
* @async
|
|
302
|
+
* @function generateUltra
|
|
303
|
+
* @param {string} prompt - Required descriptive prompt. Must be non-empty English string (max 10,000 chars).
|
|
304
|
+
* @param {Object} [options={}] - Optional generation parameters.
|
|
305
|
+
* @param {string} [options.negative_prompt] - Keywords describing what you do **not** want to see.
|
|
306
|
+
* @param {string} [options.aspect_ratio='1:1'] - Controls aspect ratio of the output image.
|
|
307
|
+
* @param {number} [options.seed=0] - Specific seed for reproducibility (0 = random, 0–4,294,967,294).
|
|
308
|
+
* @param {string} [options.output_format='png'] - `'png'`, `'jpeg'`, or `'webp'`.
|
|
309
|
+
* @param {string|Buffer|Blob} [options.image] - Optional reference image for image-to-image generation.
|
|
310
|
+
* @param {number} [options.strength] - Denoising strength (0–1). Required when `image` is provided.
|
|
311
|
+
* @param {string} [options.style_preset] - Guides the model toward a particular style (e.g. 'photographic', 'cinematic').
|
|
312
|
+
* @param {string} [options.accept='image/*'] - `'image/*'` (binary) or `'application/json'` (base64).
|
|
313
|
+
* @param {string} [options.filenamePrefix='stability-ultra'] - Prefix for saved local file.
|
|
314
|
+
* @returns {Promise<Object>} Standardized result:
|
|
315
|
+
* ```js
|
|
316
|
+
* {
|
|
317
|
+
* local_path: '/path/to/.cache/stability/stability-ultra-1234567890.png',
|
|
318
|
+
* finish_reason: 'SUCCESS',
|
|
319
|
+
* seed: 123456789,
|
|
320
|
+
* x_request_id: 'req_...',
|
|
321
|
+
* raw: { headers: {...} } // or full JSON if accept=application/json
|
|
322
|
+
* }
|
|
323
|
+
* ```
|
|
324
|
+
* @throws {Error} - 'Missing STABILITY_API_KEY', invalid prompt, API errors (400/403/422/429/500), polling timeout, or download failures.
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* // Basic text-to-image
|
|
328
|
+
* const result = await generateUltra('majestic mountain landscape at sunset, cinematic lighting');
|
|
329
|
+
* console.log('Saved to:', result.local_path);
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* // Image-to-image with strength
|
|
333
|
+
* const result = await generateUltra(
|
|
334
|
+
* 'same scene but with dramatic storm clouds',
|
|
335
|
+
* {
|
|
336
|
+
* image: './reference.png',
|
|
337
|
+
* strength: 0.65,
|
|
338
|
+
* aspect_ratio: '16:9',
|
|
339
|
+
* output_format: 'webp'
|
|
340
|
+
* }
|
|
341
|
+
* );
|
|
342
|
+
*/
|
|
343
|
+
async function generateUltra(prompt, options = {}) {
|
|
344
|
+
if (!prompt || typeof prompt !== 'string' || prompt.trim() === '') {
|
|
345
|
+
throw new Error('generateUltra() requires a non-empty prompt string');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const formData = new FormData();
|
|
349
|
+
formData.append('prompt', prompt);
|
|
350
|
+
|
|
351
|
+
if (options.negative_prompt) formData.append('negative_prompt', options.negative_prompt);
|
|
352
|
+
if (options.aspect_ratio) formData.append('aspect_ratio', options.aspect_ratio);
|
|
353
|
+
if (options.seed != null) formData.append('seed', String(options.seed));
|
|
354
|
+
if (options.output_format) formData.append('output_format', options.output_format);
|
|
355
|
+
if (options.style_preset) formData.append('style_preset', options.style_preset);
|
|
356
|
+
|
|
357
|
+
if (options.image) {
|
|
358
|
+
await appendImageToFormData(formData, options.image, 'image');
|
|
359
|
+
if (options.strength != null) formData.append('strength', String(options.strength));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const accept = options.accept || 'image/*';
|
|
363
|
+
const result = await submitImageGeneration('stable-image/generate/ultra', formData, accept);
|
|
364
|
+
|
|
365
|
+
if (result && result.status === 200) {
|
|
366
|
+
return processImageResult(result, { ...options, filenamePrefix: 'stability-ultra' });
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const id = result;
|
|
370
|
+
const polled = await pollForImageResult(id, accept);
|
|
371
|
+
return processImageResult(polled, { ...options, filenamePrefix: 'stability-ultra' });
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/* ============================================================
|
|
375
|
+
WORKFLOW: GENERATE CORE
|
|
376
|
+
============================================================ */
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Generates fast and affordable images using Stable Image Core.
|
|
380
|
+
*
|
|
381
|
+
* Optimized for rapid iteration during ideation. Next-generation model following SDXL.
|
|
382
|
+
*
|
|
383
|
+
* **Constraints**:
|
|
384
|
+
* - Prompt: Max 10,000 characters.
|
|
385
|
+
* - Aspect ratio: Same options as Ultra.
|
|
386
|
+
* - Output resolution: 1.5 megapixels.
|
|
387
|
+
* - Cost: 3 credits per successful generation.
|
|
388
|
+
*
|
|
389
|
+
* @async
|
|
390
|
+
* @function generateCore
|
|
391
|
+
* @param {string} prompt - Required descriptive prompt.
|
|
392
|
+
* @param {Object} [options={}] - Optional parameters (negative_prompt, aspect_ratio, seed, output_format, style_preset, accept, filenamePrefix).
|
|
393
|
+
* @returns {Promise<Object>} Same result structure as generateUltra.
|
|
394
|
+
*/
|
|
395
|
+
async function generateCore(prompt, options = {}) {
|
|
396
|
+
if (!prompt || typeof prompt !== 'string' || prompt.trim() === '') {
|
|
397
|
+
throw new Error('generateCore() requires a non-empty prompt string');
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const formData = new FormData();
|
|
401
|
+
formData.append('prompt', prompt);
|
|
402
|
+
|
|
403
|
+
if (options.negative_prompt) formData.append('negative_prompt', options.negative_prompt);
|
|
404
|
+
if (options.aspect_ratio) formData.append('aspect_ratio', options.aspect_ratio);
|
|
405
|
+
if (options.seed != null) formData.append('seed', String(options.seed));
|
|
406
|
+
if (options.output_format) formData.append('output_format', options.output_format);
|
|
407
|
+
if (options.style_preset) formData.append('style_preset', options.style_preset);
|
|
408
|
+
|
|
409
|
+
const accept = options.accept || 'image/*';
|
|
410
|
+
const result = await submitImageGeneration('stable-image/generate/core', formData, accept);
|
|
411
|
+
|
|
412
|
+
if (result && result.status === 200) {
|
|
413
|
+
return processImageResult(result, { ...options, filenamePrefix: 'stability-core' });
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const id = result;
|
|
417
|
+
const polled = await pollForImageResult(id, accept);
|
|
418
|
+
return processImageResult(polled, { ...options, filenamePrefix: 'stability-core' });
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/* ============================================================
|
|
422
|
+
WORKFLOW: GENERATE SD3
|
|
423
|
+
============================================================ */
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Generates images using Stable Diffusion 3.5 models.
|
|
427
|
+
*
|
|
428
|
+
* Supports `sd3.5-large`, `sd3.5-large-turbo`, `sd3.5-medium`, `sd3.5-flash`.
|
|
429
|
+
*
|
|
430
|
+
* **Key Parameters**:
|
|
431
|
+
* - `mode`: `'text-to-image'` (default) or `'image-to-image'`.
|
|
432
|
+
* - For image-to-image: `image` + `strength` required.
|
|
433
|
+
* - `cfg_scale`: Prompt adherence (default 4 for Large/Medium, 1 for Turbo/Flash).
|
|
434
|
+
*
|
|
435
|
+
* @async
|
|
436
|
+
* @function generateSD3
|
|
437
|
+
* @param {string} prompt - Required prompt.
|
|
438
|
+
* @param {Object} [options={}] - Optional parameters including `mode`, `model`, `image`, `strength`, `cfg_scale`, etc.
|
|
439
|
+
* @returns {Promise<Object>} Same result structure.
|
|
440
|
+
*/
|
|
441
|
+
async function generateSD3(prompt, options = {}) {
|
|
442
|
+
if (!prompt || typeof prompt !== 'string' || prompt.trim() === '') {
|
|
443
|
+
throw new Error('generateSD3() requires a non-empty prompt string');
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const formData = new FormData();
|
|
447
|
+
formData.append('prompt', prompt);
|
|
448
|
+
formData.append('mode', options.mode || 'text-to-image');
|
|
449
|
+
|
|
450
|
+
if (options.negative_prompt) formData.append('negative_prompt', options.negative_prompt);
|
|
451
|
+
if (options.aspect_ratio) formData.append('aspect_ratio', options.aspect_ratio);
|
|
452
|
+
if (options.seed != null) formData.append('seed', String(options.seed));
|
|
453
|
+
if (options.output_format) formData.append('output_format', options.output_format);
|
|
454
|
+
if (options.style_preset) formData.append('style_preset', options.style_preset);
|
|
455
|
+
if (options.cfg_scale != null) formData.append('cfg_scale', String(options.cfg_scale));
|
|
456
|
+
if (options.model) formData.append('model', options.model);
|
|
457
|
+
|
|
458
|
+
if (options.image) {
|
|
459
|
+
await appendImageToFormData(formData, options.image, 'image');
|
|
460
|
+
if (options.strength != null) formData.append('strength', String(options.strength));
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const accept = options.accept || 'image/*';
|
|
464
|
+
const result = await submitImageGeneration('stable-image/generate/sd3', formData, accept);
|
|
465
|
+
|
|
466
|
+
if (result && result.status === 200) {
|
|
467
|
+
return processImageResult(result, { ...options, filenamePrefix: 'stability-sd3' });
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const id = result;
|
|
471
|
+
const polled = await pollForImageResult(id, accept);
|
|
472
|
+
return processImageResult(polled, { ...options, filenamePrefix: 'stability-sd3' });
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/* ============================================================
|
|
476
|
+
WORKFLOW: UPSCALE CONSERVATIVE
|
|
477
|
+
============================================================ */
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Upscales an image conservatively while preserving original details.
|
|
481
|
+
*
|
|
482
|
+
* Takes images 64×64 to 1MP and upscales to 4MP with minimal alteration.
|
|
483
|
+
*
|
|
484
|
+
* **Constraints**:
|
|
485
|
+
* - Image: 64px–9,437,184 total pixels, aspect ratio 1:2.5 to 2.5:1.
|
|
486
|
+
* - Cost: 40 credits.
|
|
487
|
+
*
|
|
488
|
+
* @async
|
|
489
|
+
* @function upscaleConservative
|
|
490
|
+
* @param {string|Buffer|Blob} imageInput - Image to upscale.
|
|
491
|
+
* @param {string} prompt - Prompt describing desired output.
|
|
492
|
+
* @param {Object} [options={}] - Optional: negative_prompt, seed, output_format, creativity (0.2–0.5).
|
|
493
|
+
* @returns {Promise<Object>} Result with upscaled image.
|
|
494
|
+
*/
|
|
495
|
+
async function upscaleConservative(imageInput, prompt, options = {}) {
|
|
496
|
+
if (!imageInput) {
|
|
497
|
+
throw new Error('upscaleConservative() requires an image input');
|
|
498
|
+
}
|
|
499
|
+
if (!prompt || typeof prompt !== 'string' || prompt.trim() === '') {
|
|
500
|
+
throw new Error('upscaleConservative() requires a prompt');
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const formData = new FormData();
|
|
504
|
+
await appendImageToFormData(formData, imageInput, 'image');
|
|
505
|
+
formData.append('prompt', prompt);
|
|
506
|
+
|
|
507
|
+
if (options.negative_prompt) formData.append('negative_prompt', options.negative_prompt);
|
|
508
|
+
if (options.seed != null) formData.append('seed', String(options.seed));
|
|
509
|
+
if (options.output_format) formData.append('output_format', options.output_format);
|
|
510
|
+
if (options.creativity != null) formData.append('creativity', String(options.creativity));
|
|
511
|
+
|
|
512
|
+
const accept = options.accept || 'image/*';
|
|
513
|
+
const result = await submitImageGeneration('stable-image/upscale/conservative', formData, accept);
|
|
514
|
+
|
|
515
|
+
if (result && result.status === 200) {
|
|
516
|
+
return processImageResult(result, { ...options, filenamePrefix: 'stability-upscale-conservative' });
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const id = result;
|
|
520
|
+
const polled = await pollForImageResult(id, accept);
|
|
521
|
+
return processImageResult(polled, { ...options, filenamePrefix: 'stability-upscale-conservative' });
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/* ============================================================
|
|
525
|
+
EDIT: ERASE
|
|
526
|
+
============================================================ */
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Removes unwanted objects from an image using a mask or the alpha channel.
|
|
530
|
+
*
|
|
531
|
+
* The mask can be provided explicitly or derived from the alpha channel of the input image.
|
|
532
|
+
*
|
|
533
|
+
* **Constraints**:
|
|
534
|
+
* - Image: 64px–9,437,184 total pixels.
|
|
535
|
+
* - Cost: 5 credits.
|
|
536
|
+
*
|
|
537
|
+
* @async
|
|
538
|
+
* @function editErase
|
|
539
|
+
* @param {string|Buffer|Blob} imageInput - Image to edit.
|
|
540
|
+
* @param {Object} [options={}] - Optional parameters.
|
|
541
|
+
* @param {string|Buffer|Blob} [options.mask] - Optional mask image.
|
|
542
|
+
* @param {number} [options.grow_mask=5] - Grow mask edges by this many pixels.
|
|
543
|
+
* @param {number} [options.seed=0]
|
|
544
|
+
* @param {string} [options.output_format='png']
|
|
545
|
+
* @returns {Promise<Object>} Result with edited image.
|
|
546
|
+
*/
|
|
547
|
+
async function editErase(imageInput, options = {}) {
|
|
548
|
+
if (!imageInput) {
|
|
549
|
+
throw new Error('editErase() requires an image input');
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const formData = new FormData();
|
|
553
|
+
await appendImageToFormData(formData, imageInput, 'image');
|
|
554
|
+
|
|
555
|
+
if (options.mask) await appendImageToFormData(formData, options.mask, 'mask');
|
|
556
|
+
if (options.grow_mask != null) formData.append('grow_mask', String(options.grow_mask));
|
|
557
|
+
if (options.seed != null) formData.append('seed', String(options.seed));
|
|
558
|
+
if (options.output_format) formData.append('output_format', options.output_format);
|
|
559
|
+
|
|
560
|
+
const accept = options.accept || 'image/*';
|
|
561
|
+
const result = await submitImageGeneration('stable-image/edit/erase', formData, accept);
|
|
562
|
+
|
|
563
|
+
if (result && result.status === 200) {
|
|
564
|
+
return processImageResult(result, { ...options, filenamePrefix: 'stability-edit-erase' });
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const id = result;
|
|
568
|
+
const polled = await pollForImageResult(id, accept);
|
|
569
|
+
return processImageResult(polled, { ...options, filenamePrefix: 'stability-edit-erase' });
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/* ============================================================
|
|
573
|
+
EDIT: INPAINT (enhanced)
|
|
574
|
+
============================================================ */
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Intelligently modifies images by filling or replacing areas based on a mask or search prompt.
|
|
578
|
+
*
|
|
579
|
+
* Supports two modes:
|
|
580
|
+
* - `mask` mode: Provide explicit `mask` or use alpha channel.
|
|
581
|
+
* - `search` mode: Provide `search_prompt` for automatic object segmentation.
|
|
582
|
+
*
|
|
583
|
+
* **Constraints**:
|
|
584
|
+
* - Image: 64px–9,437,184 total pixels.
|
|
585
|
+
* - Cost: 5 credits.
|
|
586
|
+
*
|
|
587
|
+
* @async
|
|
588
|
+
* @function editInpaint
|
|
589
|
+
* @param {string|Buffer|Blob} imageInput - Base image.
|
|
590
|
+
* @param {string} prompt - What the replaced area should become.
|
|
591
|
+
* @param {Object} [options={}] - Optional: negative_prompt, seed, output_format, style_preset, grow_mask, mask, mode, search_prompt.
|
|
592
|
+
* @returns {Promise<Object>} Result with edited image.
|
|
593
|
+
*/
|
|
594
|
+
async function editInpaint(imageInput, prompt, options = {}) {
|
|
595
|
+
if (!imageInput) {
|
|
596
|
+
throw new Error('editInpaint() requires an image input');
|
|
597
|
+
}
|
|
598
|
+
if (!prompt || typeof prompt !== 'string' || prompt.trim() === '') {
|
|
599
|
+
throw new Error('editInpaint() requires a prompt');
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const formData = new FormData();
|
|
603
|
+
await appendImageToFormData(formData, imageInput, 'image');
|
|
604
|
+
formData.append('prompt', prompt);
|
|
605
|
+
|
|
606
|
+
if (options.negative_prompt) formData.append('negative_prompt', options.negative_prompt);
|
|
607
|
+
if (options.seed != null) formData.append('seed', String(options.seed));
|
|
608
|
+
if (options.output_format) formData.append('output_format', options.output_format);
|
|
609
|
+
if (options.style_preset) formData.append('style_preset', options.style_preset);
|
|
610
|
+
if (options.grow_mask != null) formData.append('grow_mask', String(options.grow_mask));
|
|
611
|
+
|
|
612
|
+
if (options.mode === 'search' && options.search_prompt) {
|
|
613
|
+
formData.append('mode', 'search');
|
|
614
|
+
formData.append('search_prompt', options.search_prompt);
|
|
615
|
+
} else if (options.mask) {
|
|
616
|
+
await appendImageToFormData(formData, options.mask, 'mask');
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const accept = options.accept || 'image/*';
|
|
620
|
+
const result = await submitImageGeneration('stable-image/edit/inpaint', formData, accept);
|
|
621
|
+
|
|
622
|
+
if (result && result.status === 200) {
|
|
623
|
+
return processImageResult(result, { ...options, filenamePrefix: 'stability-edit-inpaint' });
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const id = result;
|
|
627
|
+
const polled = await pollForImageResult(id, accept);
|
|
628
|
+
return processImageResult(polled, { ...options, filenamePrefix: 'stability-edit-inpaint' });
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/* ============================================================
|
|
632
|
+
EDIT: OUTPAINT
|
|
633
|
+
============================================================ */
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Expands an image by adding content in any direction (left, right, up, down).
|
|
637
|
+
*
|
|
638
|
+
* At least one direction must be non-zero.
|
|
639
|
+
*
|
|
640
|
+
* **Constraints**:
|
|
641
|
+
* - Image: 64px–9,437,184 total pixels, aspect ratio 1:2.5–2.5:1.
|
|
642
|
+
* - Cost: 4 credits.
|
|
643
|
+
*
|
|
644
|
+
* @async
|
|
645
|
+
* @function editOutpaint
|
|
646
|
+
* @param {string|Buffer|Blob} imageInput - Image to expand.
|
|
647
|
+
* @param {Object} [options={}] - Direction values in pixels (0–2000).
|
|
648
|
+
* @param {number} [options.left=0]
|
|
649
|
+
* @param {number} [options.right=0]
|
|
650
|
+
* @param {number} [options.up=0]
|
|
651
|
+
* @param {number} [options.down=0]
|
|
652
|
+
* @param {string} [options.prompt]
|
|
653
|
+
* @param {number} [options.creativity=0.5]
|
|
654
|
+
* @param {string} [options.style_preset]
|
|
655
|
+
* @returns {Promise<Object>} Result with expanded image.
|
|
656
|
+
*/
|
|
657
|
+
async function editOutpaint(imageInput, options = {}) {
|
|
658
|
+
if (!imageInput) {
|
|
659
|
+
throw new Error('editOutpaint() requires an image input');
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const formData = new FormData();
|
|
663
|
+
await appendImageToFormData(formData, imageInput, 'image');
|
|
664
|
+
|
|
665
|
+
if (options.left != null) formData.append('left', String(options.left));
|
|
666
|
+
if (options.right != null) formData.append('right', String(options.right));
|
|
667
|
+
if (options.up != null) formData.append('up', String(options.up));
|
|
668
|
+
if (options.down != null) formData.append('down', String(options.down));
|
|
669
|
+
|
|
670
|
+
if (options.prompt) formData.append('prompt', options.prompt);
|
|
671
|
+
if (options.creativity != null) formData.append('creativity', String(options.creativity));
|
|
672
|
+
if (options.seed != null) formData.append('seed', String(options.seed));
|
|
673
|
+
if (options.output_format) formData.append('output_format', options.output_format);
|
|
674
|
+
if (options.style_preset) formData.append('style_preset', options.style_preset);
|
|
675
|
+
|
|
676
|
+
const accept = options.accept || 'image/*';
|
|
677
|
+
const result = await submitImageGeneration('stable-image/edit/outpaint', formData, accept);
|
|
678
|
+
|
|
679
|
+
if (result && result.status === 200) {
|
|
680
|
+
return processImageResult(result, { ...options, filenamePrefix: 'stability-edit-outpaint' });
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const id = result;
|
|
684
|
+
const polled = await pollForImageResult(id, accept);
|
|
685
|
+
return processImageResult(polled, { ...options, filenamePrefix: 'stability-edit-outpaint' });
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/* ============================================================
|
|
689
|
+
EDIT: SEARCH AND REPLACE
|
|
690
|
+
============================================================ */
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Automatically segments an object described by `search_prompt` and replaces it with new content from `prompt`.
|
|
694
|
+
*
|
|
695
|
+
* **Constraints**:
|
|
696
|
+
* - Image: 64px–9,437,184 total pixels.
|
|
697
|
+
* - Cost: 5 credits.
|
|
698
|
+
*
|
|
699
|
+
* @async
|
|
700
|
+
* @function editSearchAndReplace
|
|
701
|
+
* @param {string|Buffer|Blob} imageInput - Base image.
|
|
702
|
+
* @param {string} prompt - What to replace the object with.
|
|
703
|
+
* @param {string} search_prompt - Short description of the object to find.
|
|
704
|
+
* @param {Object} [options={}] - Optional: negative_prompt, seed, output_format, style_preset, grow_mask.
|
|
705
|
+
* @returns {Promise<Object>} Result with edited image.
|
|
706
|
+
*/
|
|
707
|
+
async function editSearchAndReplace(imageInput, prompt, search_prompt, options = {}) {
|
|
708
|
+
if (!imageInput) {
|
|
709
|
+
throw new Error('editSearchAndReplace() requires an image input');
|
|
710
|
+
}
|
|
711
|
+
if (!prompt || !search_prompt) {
|
|
712
|
+
throw new Error('editSearchAndReplace() requires prompt and search_prompt');
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const formData = new FormData();
|
|
716
|
+
await appendImageToFormData(formData, imageInput, 'image');
|
|
717
|
+
formData.append('prompt', prompt);
|
|
718
|
+
formData.append('search_prompt', search_prompt);
|
|
719
|
+
|
|
720
|
+
if (options.negative_prompt) formData.append('negative_prompt', options.negative_prompt);
|
|
721
|
+
if (options.seed != null) formData.append('seed', String(options.seed));
|
|
722
|
+
if (options.output_format) formData.append('output_format', options.output_format);
|
|
723
|
+
if (options.style_preset) formData.append('style_preset', options.style_preset);
|
|
724
|
+
if (options.grow_mask != null) formData.append('grow_mask', String(options.grow_mask));
|
|
725
|
+
|
|
726
|
+
const accept = options.accept || 'image/*';
|
|
727
|
+
const result = await submitImageGeneration('stable-image/edit/search-and-replace', formData, accept);
|
|
728
|
+
|
|
729
|
+
if (result && result.status === 200) {
|
|
730
|
+
return processImageResult(result, { ...options, filenamePrefix: 'stability-edit-search-replace' });
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const id = result;
|
|
734
|
+
const polled = await pollForImageResult(id, accept);
|
|
735
|
+
return processImageResult(polled, { ...options, filenamePrefix: 'stability-edit-search-replace' });
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/* ============================================================
|
|
739
|
+
EDIT: SEARCH AND RECOLOR
|
|
740
|
+
============================================================ */
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Automatically segments an object and recolors it according to the prompt.
|
|
744
|
+
*
|
|
745
|
+
* @async
|
|
746
|
+
* @function editSearchAndRecolor
|
|
747
|
+
* @param {string|Buffer|Blob} imageInput - Base image.
|
|
748
|
+
* @param {string} prompt - Desired new color description.
|
|
749
|
+
* @param {string} select_prompt - Short description of the object to recolor.
|
|
750
|
+
* @param {Object} [options={}] - Optional parameters.
|
|
751
|
+
* @returns {Promise<Object>} Result with recolored image.
|
|
752
|
+
*/
|
|
753
|
+
async function editSearchAndRecolor(imageInput, prompt, select_prompt, options = {}) {
|
|
754
|
+
if (!imageInput || !prompt || !select_prompt) {
|
|
755
|
+
throw new Error('editSearchAndRecolor() requires image, prompt, and select_prompt');
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const formData = new FormData();
|
|
759
|
+
await appendImageToFormData(formData, imageInput, 'image');
|
|
760
|
+
formData.append('prompt', prompt);
|
|
761
|
+
formData.append('select_prompt', select_prompt);
|
|
762
|
+
|
|
763
|
+
if (options.negative_prompt) formData.append('negative_prompt', options.negative_prompt);
|
|
764
|
+
if (options.seed != null) formData.append('seed', String(options.seed));
|
|
765
|
+
if (options.output_format) formData.append('output_format', options.output_format);
|
|
766
|
+
if (options.style_preset) formData.append('style_preset', options.style_preset);
|
|
767
|
+
if (options.grow_mask != null) formData.append('grow_mask', String(options.grow_mask));
|
|
768
|
+
|
|
769
|
+
const accept = options.accept || 'image/*';
|
|
770
|
+
const result = await submitImageGeneration('stable-image/edit/search-and-recolor', formData, accept);
|
|
771
|
+
|
|
772
|
+
if (result && result.status === 200) {
|
|
773
|
+
return processImageResult(result, { ...options, filenamePrefix: 'stability-edit-search-recolor' });
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
const id = result;
|
|
777
|
+
const polled = await pollForImageResult(id, accept);
|
|
778
|
+
return processImageResult(polled, { ...options, filenamePrefix: 'stability-edit-search-recolor' });
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/* ============================================================
|
|
782
|
+
EDIT: REMOVE BACKGROUND
|
|
783
|
+
============================================================ */
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Accurately segments the foreground and removes the background.
|
|
787
|
+
*
|
|
788
|
+
* @async
|
|
789
|
+
* @function editRemoveBackground
|
|
790
|
+
* @param {string|Buffer|Blob} imageInput - Image to process.
|
|
791
|
+
* @param {Object} [options={}] - Optional: output_format ('png' or 'webp').
|
|
792
|
+
* @returns {Promise<Object>} Result with background removed.
|
|
793
|
+
*/
|
|
794
|
+
async function editRemoveBackground(imageInput, options = {}) {
|
|
795
|
+
if (!imageInput) {
|
|
796
|
+
throw new Error('editRemoveBackground() requires an image input');
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
const formData = new FormData();
|
|
800
|
+
await appendImageToFormData(formData, imageInput, 'image');
|
|
801
|
+
if (options.output_format) formData.append('output_format', options.output_format);
|
|
802
|
+
|
|
803
|
+
const accept = options.accept || 'image/*';
|
|
804
|
+
const result = await submitImageGeneration('stable-image/edit/remove-background', formData, accept);
|
|
805
|
+
|
|
806
|
+
if (result && result.status === 200) {
|
|
807
|
+
return processImageResult(result, { ...options, filenamePrefix: 'stability-edit-remove-bg' });
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const id = result;
|
|
811
|
+
const polled = await pollForImageResult(id, accept);
|
|
812
|
+
return processImageResult(polled, { ...options, filenamePrefix: 'stability-edit-remove-bg' });
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/* ============================================================
|
|
816
|
+
EDIT: REPLACE BACKGROUND AND RELIGHT (async)
|
|
817
|
+
============================================================ */
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Replaces the background and adjusts lighting to match the subject.
|
|
821
|
+
*
|
|
822
|
+
* Async endpoint — returns a generation ID for polling.
|
|
823
|
+
*
|
|
824
|
+
* @async
|
|
825
|
+
* @function editReplaceBackgroundAndRelight
|
|
826
|
+
* @param {string|Buffer|Blob} subjectImage - Main subject image.
|
|
827
|
+
* @param {Object} [options={}] - Optional parameters.
|
|
828
|
+
* @returns {Promise<string|Object>} Generation ID or processed result.
|
|
829
|
+
*/
|
|
830
|
+
async function editReplaceBackgroundAndRelight(subjectImage, options = {}) {
|
|
831
|
+
if (!subjectImage) {
|
|
832
|
+
throw new Error('editReplaceBackgroundAndRelight() requires a subject_image');
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const formData = new FormData();
|
|
836
|
+
await appendImageToFormData(formData, subjectImage, 'subject_image');
|
|
837
|
+
|
|
838
|
+
if (options.background_prompt) formData.append('background_prompt', options.background_prompt);
|
|
839
|
+
if (options.background_reference) await appendImageToFormData(formData, options.background_reference, 'background_reference');
|
|
840
|
+
if (options.foreground_prompt) formData.append('foreground_prompt', options.foreground_prompt);
|
|
841
|
+
if (options.negative_prompt) formData.append('negative_prompt', options.negative_prompt);
|
|
842
|
+
if (options.seed != null) formData.append('seed', String(options.seed));
|
|
843
|
+
if (options.output_format) formData.append('output_format', options.output_format);
|
|
844
|
+
|
|
845
|
+
const accept = options.accept || 'image/*';
|
|
846
|
+
const result = await submitImageGeneration('stable-image/edit/replace-background-and-relight', formData, accept);
|
|
847
|
+
|
|
848
|
+
if (result && result.status === 200) {
|
|
849
|
+
return processImageResult(result, { ...options, filenamePrefix: 'stability-edit-replace-bg' });
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
return result; // returns id for async polling
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/* ============================================================
|
|
856
|
+
WORKFLOW: FETCH IMAGE RESULT (manual)
|
|
857
|
+
============================================================ */
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* Manually fetches or checks the status of an image generation using its ID.
|
|
861
|
+
*
|
|
862
|
+
* @async
|
|
863
|
+
* @function fetchImageResult
|
|
864
|
+
* @param {string} id - Generation ID.
|
|
865
|
+
* @param {string} [acceptHeader='image/*'] - `'image/*'` or `'application/json'`.
|
|
866
|
+
* @returns {Promise<Object>} Completed result or `{ status: 'in-progress', id }`.
|
|
867
|
+
*/
|
|
868
|
+
async function fetchImageResult(id, acceptHeader = 'image/*') {
|
|
869
|
+
if (!id) {
|
|
870
|
+
throw new Error('fetchImageResult() requires a generation id');
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const headers = getHeaders(acceptHeader);
|
|
874
|
+
const url = `${BASE_URL}/v2beta/results/${id}`;
|
|
875
|
+
const res = await doRequest(url, 'GET', headers);
|
|
876
|
+
|
|
877
|
+
if (res.status === 200) {
|
|
878
|
+
return processImageResult(res);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
if (res.status === 202) {
|
|
882
|
+
return {
|
|
883
|
+
status: 'in-progress',
|
|
884
|
+
id,
|
|
885
|
+
raw: res.response
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
if (res.status === 404) {
|
|
890
|
+
throw new Error(`Generation ${id} not found or expired`);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
throw new Error(`fetchImageResult error ${res.status}: ${JSON.stringify(res.response)}`);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
export {
|
|
897
|
+
getHeaders,
|
|
898
|
+
saveImageToLocal,
|
|
899
|
+
generateUltra,
|
|
900
|
+
generateCore,
|
|
901
|
+
generateSD3,
|
|
902
|
+
upscaleConservative,
|
|
903
|
+
editErase,
|
|
904
|
+
editInpaint,
|
|
905
|
+
editOutpaint,
|
|
906
|
+
editSearchAndReplace,
|
|
907
|
+
editSearchAndRecolor,
|
|
908
|
+
editRemoveBackground,
|
|
909
|
+
editReplaceBackgroundAndRelight,
|
|
910
|
+
fetchImageResult
|
|
911
|
+
};
|