feat: add retry loadWithRetry composable (#12873)

This commit is contained in:
Shivam Mishra
2025-11-20 19:40:20 +05:30
committed by GitHub
parent 9a2136caf1
commit da4110a495
2 changed files with 72 additions and 11 deletions

View File

@@ -1,7 +1,8 @@
<script setup>
import { ref, computed } from 'vue';
import { ref, computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { useAlert } from 'dashboard/composables';
import { useLoadWithRetry } from 'dashboard/composables/loadWithRetry';
import BaseBubble from './Base.vue';
import Button from 'next/button/Button.vue';
import Icon from 'next/icon/Icon.vue';
@@ -11,7 +12,6 @@ import { downloadFile } from '@chatwoot/utils';
import GalleryView from 'dashboard/components/widgets/conversation/components/GalleryView.vue';
const emit = defineEmits(['error']);
const { t } = useI18n();
const { filteredCurrentChatAttachments, attachments } = useMessageContext();
@@ -20,14 +20,16 @@ const attachment = computed(() => {
return attachments.value[0];
});
const hasError = ref(false);
const { isLoaded, hasError, loadWithRetry } = useLoadWithRetry();
const showGallery = ref(false);
const isDownloading = ref(false);
const handleError = () => {
hasError.value = true;
emit('error');
};
onMounted(() => {
if (attachment.value?.dataUrl) {
loadWithRetry(attachment.value.dataUrl);
}
});
const downloadAttachment = async () => {
const { fileType, dataUrl, extension } = attachment.value;
@@ -40,6 +42,10 @@ const downloadAttachment = async () => {
isDownloading.value = false;
}
};
const handleImageError = () => {
hasError.value = true;
};
</script>
<template>
@@ -54,14 +60,12 @@ const downloadAttachment = async () => {
{{ $t('COMPONENTS.MEDIA.IMAGE_UNAVAILABLE') }}
</p>
</div>
<div v-else class="relative group rounded-lg overflow-hidden">
<div v-else-if="isLoaded" class="relative group rounded-lg overflow-hidden">
<img
class="skip-context-menu"
:src="attachment.dataUrl"
:width="attachment.width"
:height="attachment.height"
@click="onClick"
@error="handleError"
/>
<div
class="inset-0 p-2 pointer-events-none absolute bg-gradient-to-tl from-n-slate-12/30 dark:from-n-slate-1/50 via-transparent to-transparent hidden group-hover:flex"
@@ -86,7 +90,7 @@ const downloadAttachment = async () => {
v-model:show="showGallery"
:attachment="useSnakeCase(attachment)"
:all-attachments="filteredCurrentChatAttachments"
@error="handleError"
@error="handleImageError"
@close="() => (showGallery = false)"
/>
</template>

View File

@@ -0,0 +1,57 @@
import { ref } from 'vue';
export const useLoadWithRetry = (config = {}) => {
const maxRetry = config.max_retry || 3;
const backoff = config.backoff || 1000;
const isLoaded = ref(false);
const hasError = ref(false);
const loadWithRetry = async url => {
const attemptLoad = () => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
isLoaded.value = true;
hasError.value = false;
resolve();
};
img.onerror = () => {
reject(new Error('Failed to load image'));
};
img.src = url;
});
};
const sleep = ms => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
const retry = async (attempt = 0) => {
try {
await attemptLoad();
} catch (error) {
if (attempt + 1 >= maxRetry) {
hasError.value = true;
isLoaded.value = false;
return;
}
await sleep(backoff * (attempt + 1));
await retry(attempt + 1);
}
};
await retry();
};
return {
isLoaded,
hasError,
loadWithRetry,
};
};