🖼️ feat: Avatar GIF Support & Dynamic Extensions (#7657)

This commit is contained in:
Marco Beretta 2025-06-02 13:51:38 +02:00 committed by GitHub
parent aca89091d9
commit 442b149d55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 58 additions and 10 deletions

View file

@ -18,6 +18,7 @@ const {
} = require('~/models/Agent');
const { uploadImageBuffer, filterFile } = require('~/server/services/Files/process');
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
const { resizeAvatar } = require('~/server/services/Files/images/avatar');
const { refreshS3Url } = require('~/server/services/Files/S3/crud');
const { updateAction, getActions } = require('~/models/Action');
const { updateAgentProjects } = require('~/models/Agent');
@ -373,12 +374,26 @@ const uploadAgentAvatarHandler = async (req, res) => {
}
const buffer = await fs.readFile(req.file.path);
const image = await uploadImageBuffer({
req,
context: FileContext.avatar,
metadata: { buffer },
const fileStrategy = req.app.locals.fileStrategy;
const resizedBuffer = await resizeAvatar({
userId: req.user.id,
input: buffer,
});
const { processAvatar } = getStrategyFunctions(fileStrategy);
const avatarUrl = await processAvatar({
buffer: resizedBuffer,
userId: req.user.id,
manual: 'false',
});
const image = {
filepath: avatarUrl,
source: fileStrategy,
};
let _avatar;
try {
const agent = await getAgent({ id: agent_id });
@ -403,7 +418,7 @@ const uploadAgentAvatarHandler = async (req, res) => {
const data = {
avatar: {
filepath: image.filepath,
source: req.app.locals.fileStrategy,
source: image.source,
},
};

View file

@ -97,10 +97,14 @@ async function prepareAzureImageURL(req, file) {
*/
async function processAzureAvatar({ buffer, userId, manual, basePath = 'images', containerName }) {
try {
const metadata = await sharp(buffer).metadata();
const extension = metadata.format === 'gif' ? 'gif' : 'png';
const fileName = `avatar.${extension}`;
const downloadURL = await saveBufferToAzure({
userId,
buffer,
fileName: 'avatar.png',
fileName,
basePath,
containerName,
});

View file

@ -87,10 +87,14 @@ async function prepareImageURL(req, file) {
*/
async function processFirebaseAvatar({ buffer, userId, manual }) {
try {
const metadata = await sharp(buffer).metadata();
const extension = metadata.format === 'gif' ? 'gif' : 'png';
const fileName = `avatar.${extension}`;
const downloadURL = await saveBufferToFirebase({
userId,
buffer,
fileName: 'avatar.png',
fileName,
});
const isManual = manual === 'true';

View file

@ -129,7 +129,10 @@ async function processLocalAvatar({ buffer, userId, manual }) {
userId,
);
const fileName = `avatar-${new Date().getTime()}.png`;
const metadata = await sharp(buffer).metadata();
const extension = metadata.format === 'gif' ? 'gif' : 'png';
const fileName = `avatar-${new Date().getTime()}.${extension}`;
const urlRoute = `/images/${userId}/${fileName}`;
const avatarPath = path.join(userDir, fileName);

View file

@ -99,7 +99,11 @@ async function prepareImageURLS3(req, file) {
*/
async function processS3Avatar({ buffer, userId, manual, basePath = defaultBasePath }) {
try {
const downloadURL = await saveBufferToS3({ userId, buffer, fileName: 'avatar.png', basePath });
const metadata = await sharp(buffer).metadata();
const extension = metadata.format === 'gif' ? 'gif' : 'png';
const fileName = `avatar.${extension}`;
const downloadURL = await saveBufferToS3({ userId, buffer, fileName, basePath });
if (manual === 'true') {
await updateUser(userId, { avatar: downloadURL });
}

View file

@ -44,8 +44,25 @@ async function resizeAvatar({ userId, input, desiredFormat = EImageOutputType.PN
throw new Error('Invalid input type. Expected URL, Buffer, or File.');
}
const { width, height } = await sharp(imageBuffer).metadata();
const metadata = await sharp(imageBuffer).metadata();
const { width, height } = metadata;
const minSize = Math.min(width, height);
if (metadata.format === 'gif') {
const resizedBuffer = await sharp(imageBuffer, { animated: true })
.extract({
left: Math.floor((width - minSize) / 2),
top: Math.floor((height - minSize) / 2),
width: minSize,
height: minSize,
})
.resize(250, 250)
.gif()
.toBuffer();
return resizedBuffer;
}
const squaredBuffer = await sharp(imageBuffer)
.extract({
left: Math.floor((width - minSize) / 2),

View file

@ -50,6 +50,7 @@ export const AgentAvatarRender = ({
width="80"
height="80"
style={{ opacity: progress < 1 ? 0.4 : 1 }}
key={url || 'default-key'}
/>
{progress < 1 && (
<div className="absolute inset-0 flex items-center justify-center bg-black/5 text-white">