@xuda.io/ai_module 1.1.4523 → 1.1.4525
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 +95 -1
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -4447,7 +4447,7 @@ const chat_email = async function (req, job_id, headers) {
|
|
|
4447
4447
|
}
|
|
4448
4448
|
|
|
4449
4449
|
const email_module = await import(`${module_path}/email_module/index.mjs`);
|
|
4450
|
-
|
|
4450
|
+
sent_email_result = await email_module.sendEmailFromAccount(email_account_doc, contact_info.email, subject, body, null, attachments);
|
|
4451
4451
|
if (!sent_email_result.success) {
|
|
4452
4452
|
throw new Error('error sending email');
|
|
4453
4453
|
}
|
|
@@ -6998,3 +6998,97 @@ const get_chat_picture_cache = async function (uid, docType, name) {
|
|
|
6998
6998
|
|
|
6999
6999
|
return null;
|
|
7000
7000
|
};
|
|
7001
|
+
|
|
7002
|
+
const save_attachment_to_file = async function (attachment) {
|
|
7003
|
+
async function hasTransparentBackground(filePath) {
|
|
7004
|
+
const image = sharp(filePath);
|
|
7005
|
+
const metadata = await image.metadata();
|
|
7006
|
+
|
|
7007
|
+
// If the image doesn't have an alpha channel at all → no transparency possible
|
|
7008
|
+
if (!metadata.hasAlpha) {
|
|
7009
|
+
return false;
|
|
7010
|
+
}
|
|
7011
|
+
|
|
7012
|
+
// For images with alpha channel, check if any pixel actually has alpha < 255
|
|
7013
|
+
const { data, info } = await image.raw().toBuffer({ resolveWithObject: true });
|
|
7014
|
+
|
|
7015
|
+
// Determine position of alpha channel based on number of channels
|
|
7016
|
+
const channels = info.channels; // 2 (grayscale+alpha), 4 (RGBA), etc.
|
|
7017
|
+
const alphaOffset = channels === 2 ? 1 : channels === 4 ? 3 : -1;
|
|
7018
|
+
|
|
7019
|
+
if (alphaOffset === -1) {
|
|
7020
|
+
// Unusual channel count – be conservative and assume it might have transparency
|
|
7021
|
+
return true;
|
|
7022
|
+
}
|
|
7023
|
+
|
|
7024
|
+
for (let i = alphaOffset; i < data.length; i += channels) {
|
|
7025
|
+
if (data[i] < 255) {
|
|
7026
|
+
return true; // Found at least one transparent/semi-transparent pixel
|
|
7027
|
+
}
|
|
7028
|
+
}
|
|
7029
|
+
|
|
7030
|
+
return false; // Has alpha channel but all pixels are fully opaque
|
|
7031
|
+
}
|
|
7032
|
+
|
|
7033
|
+
async function imageToPngBlob(filePath) {
|
|
7034
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
7035
|
+
|
|
7036
|
+
let buffer;
|
|
7037
|
+
|
|
7038
|
+
let is_transparent = false;
|
|
7039
|
+
if (ext === '.png') {
|
|
7040
|
+
buffer = await fs.promises.readFile(filePath);
|
|
7041
|
+
is_transparent = await hasTransparentBackground(filePath);
|
|
7042
|
+
} else {
|
|
7043
|
+
console.log(`Converting ${path.basename(filePath)} to PNG...`);
|
|
7044
|
+
buffer = await sharp(filePath).png({ quality: 100, compressionLevel: 9, force: true }).toBuffer();
|
|
7045
|
+
}
|
|
7046
|
+
|
|
7047
|
+
// Create a Blob with correct MIME type
|
|
7048
|
+
|
|
7049
|
+
return { is_transparent, image_blob: new Blob([buffer], { type: 'image/png' }) };
|
|
7050
|
+
}
|
|
7051
|
+
// try {
|
|
7052
|
+
if (!url) {
|
|
7053
|
+
throw new Error('No URL provided in request');
|
|
7054
|
+
}
|
|
7055
|
+
|
|
7056
|
+
// 3. Download the image to a Buffer
|
|
7057
|
+
const res = await fetch(attachment.file_url);
|
|
7058
|
+
if (!res.ok) {
|
|
7059
|
+
throw new Error(`Failed to fetch image URL: ${res.status} ${res.statusText}`);
|
|
7060
|
+
}
|
|
7061
|
+
|
|
7062
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
7063
|
+
const imageBuffer = Buffer.from(arrayBuffer);
|
|
7064
|
+
|
|
7065
|
+
const tempDir = os.tmpdir();
|
|
7066
|
+
const uniqueId = Date.now() + '_' + Math.random().toString(36).substring(7);
|
|
7067
|
+
|
|
7068
|
+
const inputExt = path.extname(url) || '.jpg';
|
|
7069
|
+
const tempInputPath = path.join(tempDir, `input_${uniqueId}${inputExt}`);
|
|
7070
|
+
// const tempOutputPath = path.join(tempDir, `output_${uniqueId}.png`); // Output is always PNG (transparent)
|
|
7071
|
+
|
|
7072
|
+
// 5. Write the downloaded image to disk
|
|
7073
|
+
fs.writeFileSync(tempInputPath, imageBuffer);
|
|
7074
|
+
/////////////////////////////////
|
|
7075
|
+
|
|
7076
|
+
const imageFiles = [tempInputPath];
|
|
7077
|
+
|
|
7078
|
+
const images = await Promise.all(
|
|
7079
|
+
imageFiles.map(async (filePath) => {
|
|
7080
|
+
const img_ret = await imageToPngBlob(filePath);
|
|
7081
|
+
const blob = img_ret.image_blob;
|
|
7082
|
+
|
|
7083
|
+
// OpenAI expects a filename for logging/debugging
|
|
7084
|
+
const filename = path.basename(filePath, path.extname(filePath)) + '.png';
|
|
7085
|
+
blob.name = filename; // Optional but helpful
|
|
7086
|
+
// @ts-ignore – name is allowed in Node 20+
|
|
7087
|
+
blob.lastModified = Date.now();
|
|
7088
|
+
|
|
7089
|
+
return { is_transparent: img_ret.is_transparent, image_blob: blob };
|
|
7090
|
+
}),
|
|
7091
|
+
);
|
|
7092
|
+
|
|
7093
|
+
return images[0];
|
|
7094
|
+
};
|