@xuda.io/ai_module 1.1.5646 → 1.1.5647
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 +84 -0
- package/index_ms.mjs +4 -0
- package/index_msa.mjs +4 -0
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -11285,6 +11285,90 @@ async function normalizeBase64To1024(
|
|
|
11285
11285
|
return normalizedBuffer.toString('base64');
|
|
11286
11286
|
}
|
|
11287
11287
|
|
|
11288
|
+
// Upload-time content check for a profile picture or a business logo.
|
|
11289
|
+
//
|
|
11290
|
+
// Deliberately NOT identity verification. It answers "is this the right KIND of
|
|
11291
|
+
// image, and is it good enough to use", never "is this the right person". There
|
|
11292
|
+
// is no face embedding, no matching against a reference, and nothing about the
|
|
11293
|
+
// image is retained — so this does not cross the biometric line drawn in
|
|
11294
|
+
// docs/plans/xuda-verify-consent.md, which stays gated on the face-model
|
|
11295
|
+
// decision and that document's pre-capture prerequisites. Identity matching is
|
|
11296
|
+
// still the unbuilt half of phase 2, see
|
|
11297
|
+
// docs/plans/handoff_profile_image_verification.md.
|
|
11298
|
+
//
|
|
11299
|
+
// The same classification already existed as a private closure inside
|
|
11300
|
+
// get_profile_avatar (inspect_person_in_image), but it only ran at AVATAR
|
|
11301
|
+
// GENERATION time, which is after verification. Users need the answer while
|
|
11302
|
+
// they are still looking at the picker, so it lives here as its own method and
|
|
11303
|
+
// covers the business/logo case the original never did.
|
|
11304
|
+
export const inspect_profile_picture = async function (req, job_id, headers) {
|
|
11305
|
+
try {
|
|
11306
|
+
const data = req?.data || req || {};
|
|
11307
|
+
const uid = req?.uid;
|
|
11308
|
+
const url = data.url;
|
|
11309
|
+
const account_type = data.account_type === 'business' ? 'business' : 'personal';
|
|
11310
|
+
|
|
11311
|
+
if (!uid) return { code: -401, data: 'not authorized' };
|
|
11312
|
+
if (!url) return { code: -1, data: 'url is required' };
|
|
11313
|
+
|
|
11314
|
+
// Attribution only; a missing profile must not block the check.
|
|
11315
|
+
let account_profile_info;
|
|
11316
|
+
try {
|
|
11317
|
+
account_profile_info = await account_ms.get_active_account_profile_info(uid);
|
|
11318
|
+
} catch (e) {}
|
|
11319
|
+
|
|
11320
|
+
const image_blob_ret = await get_image_blob_from_downloaded_image(url);
|
|
11321
|
+
if (!image_blob_ret?.image_blob) return { code: -1, data: 'could not read that image' };
|
|
11322
|
+
const base64 = Buffer.from(await image_blob_ret.image_blob.arrayBuffer()).toString('base64');
|
|
11323
|
+
|
|
11324
|
+
const is_business = account_type === 'business';
|
|
11325
|
+
const instruction = is_business
|
|
11326
|
+
? `This image was uploaded as the COMPANY LOGO for a business account. Decide whether it is usable as that logo or brand mark. Reject a photograph of a person, a selfie, a stock photo, a screenshot, a meme, an unrelated object, or an image with no identifiable mark. Also reject it if it is too blurry, too small or too cropped to use.`
|
|
11327
|
+
: `This image was uploaded as a PERSONAL PROFILE PICTURE. Decide whether it is a genuine photograph of a real person, suitable as an authentic account portrait. Reject a company logo, an illustration, a cartoon, an obviously AI-generated render, an object, an animal, a screenshot, or a group shot with no single clear subject. Also reject it if the face is not visible, heavily cropped, very blurry, or too small.`;
|
|
11328
|
+
|
|
11329
|
+
const ret = await submit_chat_gpt_prompt({
|
|
11330
|
+
uid,
|
|
11331
|
+
model: _conf.default_ai_model,
|
|
11332
|
+
prompt: [
|
|
11333
|
+
{
|
|
11334
|
+
role: 'user',
|
|
11335
|
+
content: [
|
|
11336
|
+
{ type: 'input_text', text: `${instruction}\n\nBe practical rather than strict: an ordinary phone photo in ordinary lighting is fine. Only reject when a real person would agree it is the wrong kind of image or genuinely unusable.` },
|
|
11337
|
+
{ type: 'input_image', image_url: `data:image/png;base64,${base64}` },
|
|
11338
|
+
],
|
|
11339
|
+
},
|
|
11340
|
+
],
|
|
11341
|
+
response_format: z.object({
|
|
11342
|
+
acceptable: z.boolean().describe('true only if the image is the right kind AND usable quality'),
|
|
11343
|
+
reason_code: z
|
|
11344
|
+
.string()
|
|
11345
|
+
.describe(
|
|
11346
|
+
'snake_case code. Use ok when acceptable. Otherwise one of: not_a_person, is_a_logo, illustration, ai_generated, object_or_animal, screenshot, multiple_faces, face_not_visible, too_blurry, too_small, too_cropped, not_a_logo, no_clear_mark',
|
|
11347
|
+
),
|
|
11348
|
+
message: z.string().describe('one short, friendly sentence telling the user what to upload instead. No jargon, no mention of models or scores.'),
|
|
11349
|
+
}),
|
|
11350
|
+
metadata: { func: 'inspect_profile_picture', account_type },
|
|
11351
|
+
account_profile_info,
|
|
11352
|
+
});
|
|
11353
|
+
|
|
11354
|
+
if (ret.code < 0) return ret;
|
|
11355
|
+
|
|
11356
|
+
const res = JSON5.parse(ret.data);
|
|
11357
|
+
return {
|
|
11358
|
+
code: 1,
|
|
11359
|
+
data: {
|
|
11360
|
+
acceptable: Boolean(res?.acceptable),
|
|
11361
|
+
reason_code: res?.reason_code || (res?.acceptable ? 'ok' : 'unusable'),
|
|
11362
|
+
message: res?.message || '',
|
|
11363
|
+
account_type,
|
|
11364
|
+
},
|
|
11365
|
+
};
|
|
11366
|
+
} catch (err) {
|
|
11367
|
+
console.error('[inspect_profile_picture]', err.message);
|
|
11368
|
+
return { code: -1, data: err.message };
|
|
11369
|
+
}
|
|
11370
|
+
};
|
|
11371
|
+
|
|
11288
11372
|
const get_image_blob_from_downloaded_image = async function (url) {
|
|
11289
11373
|
async function hasTransparentBackground(filePath) {
|
|
11290
11374
|
const image = sharp(filePath);
|
package/index_ms.mjs
CHANGED
|
@@ -241,6 +241,10 @@ export const is_business_contact_has_person = async function (...args) {
|
|
|
241
241
|
return await broker.send_to_queue("is_business_contact_has_person", ...args);
|
|
242
242
|
};
|
|
243
243
|
|
|
244
|
+
export const inspect_profile_picture = async function (...args) {
|
|
245
|
+
return await broker.send_to_queue("inspect_profile_picture", ...args);
|
|
246
|
+
};
|
|
247
|
+
|
|
244
248
|
export const get_name_from_email_addr = async function (...args) {
|
|
245
249
|
return await broker.send_to_queue("get_name_from_email_addr", ...args);
|
|
246
250
|
};
|
package/index_msa.mjs
CHANGED
|
@@ -241,6 +241,10 @@ export const is_business_contact_has_person = function (...args) {
|
|
|
241
241
|
broker.send_to_queue_async("is_business_contact_has_person", ...args);
|
|
242
242
|
};
|
|
243
243
|
|
|
244
|
+
export const inspect_profile_picture = function (...args) {
|
|
245
|
+
broker.send_to_queue_async("inspect_profile_picture", ...args);
|
|
246
|
+
};
|
|
247
|
+
|
|
244
248
|
export const get_name_from_email_addr = function (...args) {
|
|
245
249
|
broker.send_to_queue_async("get_name_from_email_addr", ...args);
|
|
246
250
|
};
|