feat: 支持对话与设置页单图编辑
This commit is contained in:
1 parent
7e2ec28b80
commit
99e087112b
8 files changed
+361
-30
No files matched your search
@@ -1,4 +1,5 @@
|
||||
const MAX_BYTES = 64 * 1024 * 1024;
|
||||
const MAX_EDIT_BYTES = 20 * 1024 * 1024;
|
||||
const TIMEOUT_MS = 180_000;
|
||||
const QUALITY = new Set(['auto', 'low', 'medium', 'high', 'standard', 'hd']);
|
||||
const BACKGROUND = new Set(['auto', 'transparent', 'opaque']);
|
||||
@@ -52,6 +53,7 @@ async function operation(signal, work) {
|
||||
const timer = setTimeout(() => { timedOut = true; controller.abort(); }, TIMEOUT_MS);
|
||||
timer.unref?.();
|
||||
try {
|
||||
if (controller.signal.aborted) throw new Error('Request aborted.');
|
||||
return await work(controller.signal);
|
||||
} catch (error) {
|
||||
if (controller.signal.aborted) throw new Error(timedOut ? 'Request timed out after 180 seconds.' : 'Request aborted.');
|
||||
@@ -119,6 +121,19 @@ function mediaType(bytes) {
|
||||
throw new Error('Image bytes are not a supported PNG, JPEG, WebP, or GIF image.');
|
||||
}
|
||||
|
||||
/** Validate one PNG/JPEG/WebP upload by signature, not full image decoding.
|
||||
* Accepts Buffer/Uint8Array up to 20 MiB; returns an owned Buffer copy and MIME type.
|
||||
*/
|
||||
export function validateEditImage(data) {
|
||||
if (!(data instanceof Uint8Array) || data.byteLength === 0) throw new Error('Edit image must be nonempty PNG, JPEG, or WebP bytes.');
|
||||
if (data.byteLength > MAX_EDIT_BYTES) throw new Error('Edit image exceeds the 20 MiB upload limit.');
|
||||
const bytes = Buffer.from(data);
|
||||
let type;
|
||||
try { type = mediaType(bytes); } catch { /* Use the upload-specific error below. */ }
|
||||
if (!['image/png', 'image/jpeg', 'image/webp'].includes(type)) throw new Error('Edit image must be a PNG, JPEG, or WebP image.');
|
||||
return { data: bytes, mediaType: type };
|
||||
}
|
||||
|
||||
function imageUrl(value) {
|
||||
let url;
|
||||
try { url = new URL(value); } catch { throw new Error('API returned an invalid image URL.'); }
|
||||
@@ -144,6 +159,23 @@ export async function generateImages({ baseUrl, apiKey, input, signal }) {
|
||||
const headers = { ...apiHeaders(apiKey), 'Content-Type': 'application/json' };
|
||||
const body = JSON.stringify(generation);
|
||||
if (Buffer.byteLength(body) > MAX_BYTES) throw new Error('Request exceeds the 64 MB request limit.');
|
||||
return requestImages({ endpoint, headers, body, generation, signal });
|
||||
}
|
||||
|
||||
/** Edit one image; input and return value share the generateImages contract. */
|
||||
export async function editImages({ baseUrl, apiKey, input, image, signal }) {
|
||||
const endpoint = `${validateBaseUrl(baseUrl)}/images/edits`;
|
||||
const generation = validateGeneration(input);
|
||||
const headers = apiHeaders(apiKey);
|
||||
const upload = validateEditImage(image);
|
||||
const extension = { 'image/png': 'png', 'image/jpeg': 'jpg', 'image/webp': 'webp' }[upload.mediaType];
|
||||
const body = new FormData();
|
||||
for (const [key, value] of Object.entries(generation)) body.set(key, String(value));
|
||||
body.set('image', new Blob([upload.data], { type: upload.mediaType }), `image.${extension}`);
|
||||
return requestImages({ endpoint, headers, body, generation, signal });
|
||||
}
|
||||
|
||||
function requestImages({ endpoint, headers, body, generation, signal }) {
|
||||
return operation(signal, async combinedSignal => {
|
||||
const budget = { remaining: MAX_BYTES };
|
||||
const json = await fetchJson(endpoint, { method: 'POST', headers, body, signal: combinedSignal }, budget);
|
||||
|
||||
Reference in new issue
Block a user