@xuda.io/ai_module 1.1.5607 → 1.1.5608
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/index.mjs +269 -43
- package/package.json +15 -15
package/index.mjs
CHANGED
|
@@ -7,6 +7,10 @@ import { pathToFileURL } from 'node:url';
|
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
8
|
|
|
9
9
|
import sharp from 'sharp';
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
11
|
+
import { removeBackground as imglyRemoveBackground } from '@imgly/background-removal-node';
|
|
12
|
+
|
|
13
|
+
const imglyPublicPath = pathToFileURL(path.dirname(createRequire(import.meta.url).resolve('@imgly/background-removal-node')) + path.sep).href;
|
|
10
14
|
|
|
11
15
|
import puppeteer from 'puppeteer-extra';
|
|
12
16
|
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
|
|
@@ -7265,7 +7269,7 @@ export const get_profile_avatar = async function (profile_picture, uid, prompt,
|
|
|
7265
7269
|
const tempOutputPath = path.join(tempDir, `output_${uniqueId}.png`);
|
|
7266
7270
|
const { is_user } = metadata;
|
|
7267
7271
|
let filename = email || _id || name;
|
|
7268
|
-
const
|
|
7272
|
+
const inspect_person_in_image = async function (base64) {
|
|
7269
7273
|
const ret = await submit_chat_gpt_prompt({
|
|
7270
7274
|
uid,
|
|
7271
7275
|
model: _conf.default_ai_model,
|
|
@@ -7275,7 +7279,7 @@ export const get_profile_avatar = async function (profile_picture, uid, prompt,
|
|
|
7275
7279
|
content: [
|
|
7276
7280
|
{
|
|
7277
7281
|
type: 'input_text',
|
|
7278
|
-
text: `
|
|
7282
|
+
text: `Inspect this profile image. Return whether it contains a real human portrait suitable for an authentic account avatar. Mark it unsuitable if the face is not visible, side-facing, heavily cropped, very blurry, tiny, or not a real person.`,
|
|
7279
7283
|
},
|
|
7280
7284
|
{
|
|
7281
7285
|
type: 'input_image',
|
|
@@ -7285,14 +7289,66 @@ export const get_profile_avatar = async function (profile_picture, uid, prompt,
|
|
|
7285
7289
|
},
|
|
7286
7290
|
],
|
|
7287
7291
|
response_format: z.object({
|
|
7288
|
-
is_real_person_in_picture: z.boolean().describe('if a
|
|
7292
|
+
is_real_person_in_picture: z.boolean().describe('true if a real human portrait is visible'),
|
|
7293
|
+
is_front_facing: z.boolean().describe('true if the face is mostly front-facing or only slightly angled'),
|
|
7294
|
+
is_face_too_cropped: z.boolean().describe('true if important face/head parts are cut off'),
|
|
7295
|
+
is_too_blurry: z.boolean().describe('true if the face is too blurry for an authentic avatar'),
|
|
7296
|
+
is_too_small: z.boolean().describe('true if the portrait is too small or low-detail'),
|
|
7297
|
+
needs_restoration: z.boolean().describe('true if the photo is old, scanned, scratched, faded, grainy, low-detail, or otherwise degraded such that face-restoration would noticeably improve it'),
|
|
7289
7298
|
}),
|
|
7290
7299
|
metadata: { _id, func: 'detect_real_person_in_image' },
|
|
7291
7300
|
account_profile_info,
|
|
7292
7301
|
});
|
|
7293
7302
|
|
|
7294
7303
|
const res = JSON5.parse(ret.data);
|
|
7295
|
-
return
|
|
7304
|
+
return {
|
|
7305
|
+
is_real_person_in_picture: Boolean(res?.is_real_person_in_picture),
|
|
7306
|
+
is_front_facing: Boolean(res?.is_front_facing),
|
|
7307
|
+
is_face_too_cropped: Boolean(res?.is_face_too_cropped),
|
|
7308
|
+
is_too_blurry: Boolean(res?.is_too_blurry),
|
|
7309
|
+
is_too_small: Boolean(res?.is_too_small),
|
|
7310
|
+
needs_restoration: Boolean(res?.needs_restoration),
|
|
7311
|
+
};
|
|
7312
|
+
};
|
|
7313
|
+
|
|
7314
|
+
const detect_face_box = async function (base64) {
|
|
7315
|
+
try {
|
|
7316
|
+
const ret = await submit_chat_gpt_prompt({
|
|
7317
|
+
uid,
|
|
7318
|
+
model: _conf.default_ai_model,
|
|
7319
|
+
prompt: [
|
|
7320
|
+
{
|
|
7321
|
+
role: 'user',
|
|
7322
|
+
content: [
|
|
7323
|
+
{
|
|
7324
|
+
type: 'input_text',
|
|
7325
|
+
text: `Return the bounding box of the visible face in this portrait, from the top of the head (including hair) to just below the chin, ear to ear. Use normalized coordinates in [0,1] where (0,0) is the top-left corner and (1,1) is the bottom-right corner of the image.`,
|
|
7326
|
+
},
|
|
7327
|
+
{ type: 'input_image', image_url: `data:image/png;base64,${base64}` },
|
|
7328
|
+
],
|
|
7329
|
+
},
|
|
7330
|
+
],
|
|
7331
|
+
response_format: z.object({
|
|
7332
|
+
face_top: z.number().min(0).max(1).describe('Top edge of face bbox, normalized'),
|
|
7333
|
+
face_left: z.number().min(0).max(1).describe('Left edge of face bbox, normalized'),
|
|
7334
|
+
face_width: z.number().min(0).max(1).describe('Width of face bbox, normalized'),
|
|
7335
|
+
face_height: z.number().min(0).max(1).describe('Height of face bbox, normalized'),
|
|
7336
|
+
}),
|
|
7337
|
+
metadata: { _id, func: 'detect_face_box' },
|
|
7338
|
+
account_profile_info,
|
|
7339
|
+
});
|
|
7340
|
+
const res = JSON5.parse(ret.data);
|
|
7341
|
+
if (!res || typeof res.face_height !== 'number' || res.face_height <= 0) return null;
|
|
7342
|
+
return {
|
|
7343
|
+
face_top: Number(res.face_top) || 0,
|
|
7344
|
+
face_left: Number(res.face_left) || 0,
|
|
7345
|
+
face_width: Number(res.face_width) || 0,
|
|
7346
|
+
face_height: Number(res.face_height) || 0,
|
|
7347
|
+
};
|
|
7348
|
+
} catch (err) {
|
|
7349
|
+
console.error('detect_face_box failed:', err.message);
|
|
7350
|
+
return null;
|
|
7351
|
+
}
|
|
7296
7352
|
};
|
|
7297
7353
|
|
|
7298
7354
|
try {
|
|
@@ -7482,57 +7538,48 @@ export const get_profile_avatar = async function (profile_picture, uid, prompt,
|
|
|
7482
7538
|
}
|
|
7483
7539
|
} else {
|
|
7484
7540
|
/// PERSONAL
|
|
7485
|
-
const
|
|
7486
|
-
const { bio, country, mainCategory, subCategory } = metadata;
|
|
7487
|
-
|
|
7488
|
-
const prompt = `Completely remove the background, enhance and color-correct the subject, and return only a centered head-and-shoulders portrait facing the camera. Add extra top margin, include no labels or text, and export the result as a clean transparent PNG.
|
|
7489
|
-
Ensure the subject is large enough to fill most of the frame. Use the supplied name, country, and context to guide the character’s appearance.
|
|
7490
|
-
Context: ${JSON.stringify({ name, bio, country, mainCategory, subCategory })}`;
|
|
7491
|
-
|
|
7492
|
-
// const { first_name, last_name, bio, country, mainCategory, subCategory } = account_obj.account_info;
|
|
7493
|
-
// const model = 'gpt-image-1-mini';
|
|
7494
|
-
const model = 'chatgpt-image-latest';
|
|
7495
|
-
let ai_avatar_response;
|
|
7496
|
-
try {
|
|
7497
|
-
ai_avatar_response = await client.images.edit({
|
|
7498
|
-
model,
|
|
7499
|
-
image: image_blob_ret.image_blob,
|
|
7541
|
+
const source_quality = await inspectAvatarSourceQuality(imageBase64);
|
|
7500
7542
|
|
|
7501
|
-
prompt,
|
|
7502
|
-
background: 'transparent',
|
|
7503
|
-
});
|
|
7504
|
-
report_ai_status(model);
|
|
7505
|
-
} catch (err) {
|
|
7506
|
-
report_ai_status(model, err);
|
|
7507
|
-
throw err;
|
|
7508
|
-
}
|
|
7509
|
-
account_msa.record_ai_usage(uid, ai_avatar_response.usage.input_tokens, ai_avatar_response.usage.output_tokens, 'profile avatar', prompt, model, { profile_picture }, account_profile_info);
|
|
7510
|
-
imageBase64 = ai_avatar_response.data[0].b64_json;
|
|
7511
|
-
avatar_source = 'ai profile';
|
|
7512
|
-
};
|
|
7513
7543
|
const create_fictional_avatar = async function () {
|
|
7514
7544
|
// const { name, bio, country, mainCategory, subCategory } = account_obj.account_info;
|
|
7515
7545
|
|
|
7516
|
-
const prompt = `analyze the the person info : ${JSON.stringify(metadata)}
|
|
7546
|
+
const prompt = `analyze the the person info : ${JSON.stringify(metadata)}
|
|
7517
7547
|
and create realistic fictional profile picture transparent background ,person in the center of the picture return only a centered head-and-shoulders portrait facing the camera. include no labels or text add top margin the person should cover the whole picture`;
|
|
7518
7548
|
|
|
7519
7549
|
imageBase64 = await create_image(uid, prompt, undefined, undefined, 1, undefined, undefined, { url: profile_picture }, account_profile_info);
|
|
7520
7550
|
avatar_source = 'fictional';
|
|
7521
7551
|
};
|
|
7522
|
-
|
|
7523
7552
|
// personal account
|
|
7524
|
-
|
|
7525
|
-
|
|
7526
|
-
|
|
7527
|
-
|
|
7528
|
-
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
7532
|
-
|
|
7553
|
+
let person_inspection = await inspect_person_in_image(imageBase64);
|
|
7554
|
+
const wants_restoration = person_inspection.needs_restoration || person_inspection.is_too_blurry;
|
|
7555
|
+
|
|
7556
|
+
if (wants_restoration && person_inspection.is_real_person_in_picture && !person_inspection.is_too_small) {
|
|
7557
|
+
console.log('Source photo flagged for restoration, calling gpt-image-1...');
|
|
7558
|
+
try {
|
|
7559
|
+
const restored_base64 = await restoreFaceWithOpenAI(imageBase64, { uid, account_profile_info, profile_picture });
|
|
7560
|
+
const verify = await inspect_person_in_image(restored_base64);
|
|
7561
|
+
if (verify.is_real_person_in_picture && !verify.is_too_blurry && !verify.is_face_too_cropped) {
|
|
7562
|
+
imageBase64 = restored_base64;
|
|
7563
|
+
person_inspection = verify;
|
|
7564
|
+
console.log('Face restoration applied');
|
|
7565
|
+
} else {
|
|
7566
|
+
console.log('Restoration verify failed, using original photo');
|
|
7567
|
+
}
|
|
7568
|
+
} catch (err) {
|
|
7569
|
+
console.error('Face restoration failed, using original photo:', err.message);
|
|
7533
7570
|
}
|
|
7571
|
+
}
|
|
7572
|
+
|
|
7573
|
+
const can_create_authentic_avatar = source_quality.is_usable && person_inspection.is_real_person_in_picture && !person_inspection.is_too_blurry && !person_inspection.is_too_small;
|
|
7574
|
+
|
|
7575
|
+
if (can_create_authentic_avatar) {
|
|
7576
|
+
const face_box = await detect_face_box(imageBase64);
|
|
7577
|
+
imageBase64 = await normalizeAuthenticProfileAvatar(imageBase64, {
|
|
7578
|
+
remove_background: !image_blob_ret.is_transparent,
|
|
7579
|
+
face_box,
|
|
7580
|
+
});
|
|
7581
|
+
avatar_source = 'authentic profile';
|
|
7534
7582
|
} else {
|
|
7535
|
-
// create avatar from profile picture
|
|
7536
7583
|
await create_fictional_avatar();
|
|
7537
7584
|
}
|
|
7538
7585
|
}
|
|
@@ -8618,6 +8665,185 @@ function normalizeFilename(name) {
|
|
|
8618
8665
|
.replace(/^_+|_+$/g, ''); // trim leading/trailing underscores
|
|
8619
8666
|
}
|
|
8620
8667
|
|
|
8668
|
+
async function inspectAvatarSourceQuality(base64Image) {
|
|
8669
|
+
const inputBuffer = Buffer.from(base64Image, 'base64');
|
|
8670
|
+
const metadata = await sharp(inputBuffer).metadata();
|
|
8671
|
+
const width = metadata.width || 0;
|
|
8672
|
+
const height = metadata.height || 0;
|
|
8673
|
+
const shortest_side = Math.min(width, height);
|
|
8674
|
+
const longest_side = Math.max(width, height);
|
|
8675
|
+
const aspect_ratio = shortest_side ? longest_side / shortest_side : 0;
|
|
8676
|
+
|
|
8677
|
+
return {
|
|
8678
|
+
width,
|
|
8679
|
+
height,
|
|
8680
|
+
shortest_side,
|
|
8681
|
+
aspect_ratio,
|
|
8682
|
+
has_alpha: Boolean(metadata.hasAlpha),
|
|
8683
|
+
is_usable: shortest_side >= 180 && aspect_ratio <= 3,
|
|
8684
|
+
};
|
|
8685
|
+
}
|
|
8686
|
+
|
|
8687
|
+
async function normalizeAuthenticProfileAvatar(base64Image, options = {}) {
|
|
8688
|
+
const { remove_background = true, face_box = null } = options;
|
|
8689
|
+
const inputBuffer = Buffer.from(base64Image, 'base64');
|
|
8690
|
+
const orientedBuffer = await sharp(inputBuffer).rotate().png({ force: true }).toBuffer();
|
|
8691
|
+
const orientedMeta = await sharp(orientedBuffer).metadata();
|
|
8692
|
+
const segmentedBuffer = remove_background ? await removePortraitBackground(orientedBuffer) : orientedBuffer;
|
|
8693
|
+
|
|
8694
|
+
const passportBuffer = face_box ? await cropToPassportFrame(segmentedBuffer, face_box, orientedMeta) : await cropToOpaqueBounds(segmentedBuffer);
|
|
8695
|
+
|
|
8696
|
+
const subjectBuffer = await sharp(passportBuffer).modulate({ brightness: 1.02, saturation: 1.04 }).sharpen({ sigma: 0.35, m1: 0.4, m2: 0.2 }).png({ quality: 98, compressionLevel: 8, force: true }).toBuffer();
|
|
8697
|
+
|
|
8698
|
+
return subjectBuffer.toString('base64');
|
|
8699
|
+
}
|
|
8700
|
+
|
|
8701
|
+
async function cropToPassportFrame(segmentedBuffer, face_box, sourceMeta) {
|
|
8702
|
+
const segmentedMeta = await sharp(segmentedBuffer).metadata();
|
|
8703
|
+
const sourceW = segmentedMeta.width || sourceMeta.width || 0;
|
|
8704
|
+
const sourceH = segmentedMeta.height || sourceMeta.height || 0;
|
|
8705
|
+
if (!sourceW || !sourceH) return cropToOpaqueBounds(segmentedBuffer);
|
|
8706
|
+
|
|
8707
|
+
const faceCenterX = (face_box.face_left + face_box.face_width / 2) * sourceW;
|
|
8708
|
+
const faceCenterY = (face_box.face_top + face_box.face_height / 2) * sourceH;
|
|
8709
|
+
const faceHeightPx = face_box.face_height * sourceH;
|
|
8710
|
+
if (!(faceHeightPx > 4)) return cropToOpaqueBounds(segmentedBuffer);
|
|
8711
|
+
|
|
8712
|
+
const faceFraction = 0.62;
|
|
8713
|
+
const frameSize = Math.max(8, Math.round(faceHeightPx / faceFraction));
|
|
8714
|
+
const verticalAnchor = 0.42;
|
|
8715
|
+
const cropLeft = Math.round(faceCenterX - frameSize / 2);
|
|
8716
|
+
const cropTop = Math.round(faceCenterY - frameSize * verticalAnchor);
|
|
8717
|
+
|
|
8718
|
+
const padLeft = Math.max(0, -cropLeft);
|
|
8719
|
+
const padTop = Math.max(0, -cropTop);
|
|
8720
|
+
const padRight = Math.max(0, cropLeft + frameSize - sourceW);
|
|
8721
|
+
const padBottom = Math.max(0, cropTop + frameSize - sourceH);
|
|
8722
|
+
|
|
8723
|
+
let workingBuffer = segmentedBuffer;
|
|
8724
|
+
if (padLeft || padTop || padRight || padBottom) {
|
|
8725
|
+
workingBuffer = await sharp(segmentedBuffer)
|
|
8726
|
+
.ensureAlpha()
|
|
8727
|
+
.extend({
|
|
8728
|
+
left: padLeft,
|
|
8729
|
+
top: padTop,
|
|
8730
|
+
right: padRight,
|
|
8731
|
+
bottom: padBottom,
|
|
8732
|
+
background: { r: 0, g: 0, b: 0, alpha: 0 },
|
|
8733
|
+
})
|
|
8734
|
+
.png({ force: true })
|
|
8735
|
+
.toBuffer();
|
|
8736
|
+
}
|
|
8737
|
+
|
|
8738
|
+
const workingMeta = await sharp(workingBuffer).metadata();
|
|
8739
|
+
const wW = workingMeta.width || 0;
|
|
8740
|
+
const wH = workingMeta.height || 0;
|
|
8741
|
+
|
|
8742
|
+
const wantLeft = cropLeft + padLeft;
|
|
8743
|
+
const wantTop = cropTop + padTop;
|
|
8744
|
+
const extractLeft = Math.max(0, Math.min(Math.max(0, wW - 1), wantLeft));
|
|
8745
|
+
const extractTop = Math.max(0, Math.min(Math.max(0, wH - 1), wantTop));
|
|
8746
|
+
const extractWidth = Math.max(1, Math.min(wW - extractLeft, frameSize));
|
|
8747
|
+
const extractHeight = Math.max(1, Math.min(wH - extractTop, frameSize));
|
|
8748
|
+
|
|
8749
|
+
if (extractWidth <= 0 || extractHeight <= 0) {
|
|
8750
|
+
console.warn('cropToPassportFrame: degenerate extract region, falling back', { sourceW, sourceH, frameSize, cropLeft, cropTop, wW, wH });
|
|
8751
|
+
return cropToOpaqueBounds(segmentedBuffer);
|
|
8752
|
+
}
|
|
8753
|
+
|
|
8754
|
+
const framed = await sharp(workingBuffer).extract({ left: extractLeft, top: extractTop, width: extractWidth, height: extractHeight }).png({ force: true }).toBuffer();
|
|
8755
|
+
|
|
8756
|
+
return sharp(framed)
|
|
8757
|
+
.resize(1024, 1024, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } })
|
|
8758
|
+
.png({ force: true })
|
|
8759
|
+
.toBuffer();
|
|
8760
|
+
}
|
|
8761
|
+
|
|
8762
|
+
async function removePortraitBackground(inputBuffer) {
|
|
8763
|
+
const orientedBuffer = await sharp(inputBuffer).rotate().png({ force: true }).toBuffer();
|
|
8764
|
+
const inputBlob = new Blob([orientedBuffer], { type: 'image/png' });
|
|
8765
|
+
|
|
8766
|
+
const outputBlob = await imglyRemoveBackground(inputBlob, {
|
|
8767
|
+
model: 'medium',
|
|
8768
|
+
publicPath: imglyPublicPath,
|
|
8769
|
+
output: { format: 'image/png' },
|
|
8770
|
+
});
|
|
8771
|
+
|
|
8772
|
+
return Buffer.from(await outputBlob.arrayBuffer());
|
|
8773
|
+
}
|
|
8774
|
+
|
|
8775
|
+
async function cropToOpaqueBounds(inputBuffer) {
|
|
8776
|
+
const image = sharp(inputBuffer).ensureAlpha();
|
|
8777
|
+
const { data, info } = await image.raw().toBuffer({ resolveWithObject: true });
|
|
8778
|
+
const { width, height, channels } = info;
|
|
8779
|
+
const alphaThreshold = 8;
|
|
8780
|
+
|
|
8781
|
+
let minX = width;
|
|
8782
|
+
let minY = height;
|
|
8783
|
+
let maxX = -1;
|
|
8784
|
+
let maxY = -1;
|
|
8785
|
+
|
|
8786
|
+
for (let y = 0; y < height; y++) {
|
|
8787
|
+
for (let x = 0; x < width; x++) {
|
|
8788
|
+
const alpha = data[(y * width + x) * channels + (channels - 1)];
|
|
8789
|
+
if (alpha > alphaThreshold) {
|
|
8790
|
+
if (x < minX) minX = x;
|
|
8791
|
+
if (x > maxX) maxX = x;
|
|
8792
|
+
if (y < minY) minY = y;
|
|
8793
|
+
if (y > maxY) maxY = y;
|
|
8794
|
+
}
|
|
8795
|
+
}
|
|
8796
|
+
}
|
|
8797
|
+
|
|
8798
|
+
if (maxX < 0 || maxY < 0) {
|
|
8799
|
+
return sharp(inputBuffer).png({ force: true }).toBuffer();
|
|
8800
|
+
}
|
|
8801
|
+
|
|
8802
|
+
const padX = Math.round((maxX - minX + 1) * 0.04);
|
|
8803
|
+
const padY = Math.round((maxY - minY + 1) * 0.04);
|
|
8804
|
+
const cropLeft = Math.max(0, minX - padX);
|
|
8805
|
+
const cropTop = Math.max(0, minY - padY);
|
|
8806
|
+
const cropWidth = Math.min(width - cropLeft, maxX - minX + 1 + padX * 2);
|
|
8807
|
+
const cropHeight = Math.min(height - cropTop, maxY - minY + 1 + padY * 2);
|
|
8808
|
+
|
|
8809
|
+
return sharp(inputBuffer).ensureAlpha().extract({ left: cropLeft, top: cropTop, width: cropWidth, height: cropHeight }).png({ force: true }).toBuffer();
|
|
8810
|
+
}
|
|
8811
|
+
|
|
8812
|
+
async function restoreFaceWithOpenAI(base64Image, ctx = {}) {
|
|
8813
|
+
const { uid, account_profile_info, profile_picture } = ctx;
|
|
8814
|
+
const inputBuffer = Buffer.from(base64Image, 'base64');
|
|
8815
|
+
const oriented = await sharp(inputBuffer).rotate().png({ force: true }).toBuffer();
|
|
8816
|
+
const meta = await sharp(oriented).metadata();
|
|
8817
|
+
const maxDim = Math.max(meta.width || 0, meta.height || 0);
|
|
8818
|
+
const sourceBuffer = maxDim > 1536 ? await sharp(oriented).resize({ width: 1536, height: 1536, fit: 'inside' }).png({ force: true }).toBuffer() : oriented;
|
|
8819
|
+
|
|
8820
|
+
const imageFile = await toFile(sourceBuffer, 'portrait.png', { type: 'image/png' });
|
|
8821
|
+
const model = 'gpt-image-1';
|
|
8822
|
+
let response;
|
|
8823
|
+
try {
|
|
8824
|
+
response = await client.images.edit({
|
|
8825
|
+
model,
|
|
8826
|
+
image: imageFile,
|
|
8827
|
+
input_fidelity: 'high',
|
|
8828
|
+
quality: 'high',
|
|
8829
|
+
size: 'auto',
|
|
8830
|
+
prompt:
|
|
8831
|
+
'Restore this portrait photograph. Remove scratches, dust, grain, noise, faded color, and any age-related damage. Recover natural skin texture, sharpen face details, and balance lighting and color. Preserve the exact same person completely unchanged: same face shape, same eye shape and color, same nose, same mouth, same hairline and hair, same skin tone, same age, same gender, same facial expression, and same clothing. Do not alter, beautify, age, or de-age the person. Output a clean, naturally lit photograph of the same person.',
|
|
8832
|
+
});
|
|
8833
|
+
report_ai_status(model);
|
|
8834
|
+
} catch (err) {
|
|
8835
|
+
report_ai_status(model, err);
|
|
8836
|
+
throw err;
|
|
8837
|
+
}
|
|
8838
|
+
|
|
8839
|
+
const outBase64 = response.data?.[0]?.b64_json;
|
|
8840
|
+
if (!outBase64) throw new Error('OpenAI returned no restored image');
|
|
8841
|
+
if (response.usage && account_msa?.record_ai_usage) {
|
|
8842
|
+
account_msa.record_ai_usage(uid, response.usage.input_tokens, response.usage.output_tokens, 'avatar face restoration', 'restore portrait', model, { profile_picture }, account_profile_info);
|
|
8843
|
+
}
|
|
8844
|
+
return outBase64;
|
|
8845
|
+
}
|
|
8846
|
+
|
|
8621
8847
|
async function normalizeBase64To1024(
|
|
8622
8848
|
base64Image,
|
|
8623
8849
|
resize = {
|
package/package.json
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xuda.io/ai_module",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5608",
|
|
4
4
|
"description": "Xuda AI Module",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"
|
|
8
|
+
"@imgly/background-removal-node": "^1.4.5",
|
|
9
|
+
"@openai/agents": "^0.3.4",
|
|
10
|
+
"amqplib": "^0.10.9",
|
|
9
11
|
"dotenv": "^16.0.3",
|
|
10
|
-
"
|
|
12
|
+
"firebase-admin": "^9.6.0",
|
|
11
13
|
"fs-extra": "^11.2.0",
|
|
12
|
-
"utf8": "^3.0.0",
|
|
13
|
-
"@openai/agents": "^0.3.4",
|
|
14
|
-
"uglify-js": "^3.19.3",
|
|
15
|
-
"json5": "^2.2.3",
|
|
16
|
-
"zod": "^3.25.67",
|
|
17
|
-
"zod-to-json-schema": "^3.24.6",
|
|
18
|
-
"sharp": "^0.34.4",
|
|
19
14
|
"https-proxy-agent": "^7.0.6",
|
|
15
|
+
"json5": "^2.2.3",
|
|
16
|
+
"lodash": "^4.17.21",
|
|
17
|
+
"md-to-pdf": "^5.2.5",
|
|
18
|
+
"openai": "^6.7.0",
|
|
20
19
|
"puppeteer-extra": "^3.3.6",
|
|
21
20
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
|
22
|
-
"
|
|
23
|
-
"
|
|
21
|
+
"sharp": "^0.34.4",
|
|
22
|
+
"uglify-js": "^3.19.3",
|
|
23
|
+
"utf8": "^3.0.0",
|
|
24
24
|
"vm2": "^3.10.0",
|
|
25
|
-
"
|
|
26
|
-
"
|
|
25
|
+
"youtube-dl-exec": "^2.1.3",
|
|
26
|
+
"zod": "^3.25.67",
|
|
27
|
+
"zod-to-json-schema": "^3.24.6"
|
|
27
28
|
},
|
|
28
|
-
"devDependencies": {},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"pub": "npm version patch --force && npm publish",
|
|
31
31
|
"test:codex": "node tests/execute-codex-request.mjs"
|