From 4d0d3959a9830ca09595fa75872ab32f4b966146 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Sat, 27 Sep 2025 23:53:40 +0800 Subject: [PATCH] support canceling AI image recognition --- .../mobile/AIImageRecognitionSheet.vue | 37 ++++++++++++++++--- src/lib/services.ts | 25 +++++++++++-- src/lib/ui/mobile.ts | 28 ++++++++++++++ src/locales/de.json | 3 ++ src/locales/en.json | 3 ++ src/locales/es.json | 3 ++ src/locales/fr.json | 3 ++ src/locales/it.json | 3 ++ src/locales/ja.json | 3 ++ src/locales/nl.json | 3 ++ src/locales/pt_BR.json | 3 ++ src/locales/ru.json | 3 ++ src/locales/th.json | 3 ++ src/locales/uk.json | 3 ++ src/locales/vi.json | 3 ++ src/locales/zh_Hans.json | 3 ++ src/locales/zh_Hant.json | 3 ++ src/stores/transaction.ts | 13 ++++++- .../list/dialogs/AIImageRecognitionDialog.vue | 30 ++++++++++++++- 19 files changed, 162 insertions(+), 13 deletions(-) diff --git a/src/components/mobile/AIImageRecognitionSheet.vue b/src/components/mobile/AIImageRecognitionSheet.vue index 23db6879..fb2ac59f 100644 --- a/src/components/mobile/AIImageRecognitionSheet.vue +++ b/src/components/mobile/AIImageRecognitionSheet.vue @@ -29,7 +29,7 @@ import { ref, useTemplateRef } from 'vue'; import { useI18n } from '@/locales/helpers.ts'; -import { useI18nUIComponents, showLoading, hideLoading } from '@/lib/ui/mobile.ts'; +import { useI18nUIComponents, closeAllDialog } from '@/lib/ui/mobile.ts'; import { useTransactionsStore } from '@/stores/transaction.ts'; @@ -38,6 +38,7 @@ import { SUPPORTED_IMAGE_EXTENSIONS } from '@/consts/file.ts'; import type { RecognizedReceiptImageResponse } from '@/models/large_language_model.ts'; +import { generateRandomUUID } from '@/lib/misc.ts'; import { compressJpgImage } from '@/lib/ui/common.ts'; import logger from '@/lib/logger.ts'; @@ -51,7 +52,7 @@ const emit = defineEmits<{ }>(); const { tt } = useI18n(); -const { showToast } = useI18nUIComponents(); +const { showCancelableLoading, showToast } = useI18nUIComponents(); const transactionsStore = useTransactionsStore(); @@ -59,6 +60,7 @@ const imageInput = useTemplateRef('imageInput'); const loading = ref(false); const recognizing = ref(false); +const cancelRecognizingUuid = ref(undefined); const imageFile = ref(null); const imageSrc = ref(undefined); @@ -105,19 +107,27 @@ function confirm(): void { return; } + cancelRecognizingUuid.value = generateRandomUUID(); recognizing.value = true; - showLoading(() => recognizing.value); + showCancelableLoading('Recognizing...', 'Cancel Recognition', cancelRecognize); transactionsStore.recognizeReceiptImage({ - imageFile: imageFile.value + imageFile: imageFile.value, + cancelableUuid: cancelRecognizingUuid.value }).then(response => { recognizing.value = false; - hideLoading(); + cancelRecognizingUuid.value = undefined; + closeAllDialog(); emit('update:show', false); emit('recognition:change', response); }).catch(error => { + if (error.canceled) { + return; + } + recognizing.value = false; - hideLoading(); + cancelRecognizingUuid.value = undefined; + closeAllDialog(); if (!error.processed) { showToast(error.message || error); @@ -125,6 +135,19 @@ function confirm(): void { }); } +function cancelRecognize(): void { + if (!cancelRecognizingUuid.value) { + return; + } + + transactionsStore.cancelRecognizeReceiptImage(cancelRecognizingUuid.value); + recognizing.value = false; + cancelRecognizingUuid.value = undefined; + closeAllDialog(); + + showToast('User Canceled'); +} + function cancel(): void { close(); } @@ -133,6 +156,7 @@ function close(): void { emit('update:show', false); loading.value = false; recognizing.value = false; + cancelRecognizingUuid.value = undefined; imageFile.value = null; imageSrc.value = undefined; } @@ -140,6 +164,7 @@ function close(): void { function onSheetOpen(): void { loading.value = false; recognizing.value = false; + cancelRecognizingUuid.value = undefined; imageFile.value = null; imageSrc.value = undefined; } diff --git a/src/lib/services.ts b/src/lib/services.ts index 33a124a0..d0f18f06 100644 --- a/src/lib/services.ts +++ b/src/lib/services.ts @@ -159,6 +159,7 @@ import { import { getTimezoneOffsetMinutes } from './datetime.ts'; import { generateRandomUUID } from './misc.ts'; import { getBasePath } from './web.ts'; +import logger from './logger.ts'; interface ApiRequestConfig extends AxiosRequestConfig { readonly headers: AxiosRequestHeaders; @@ -166,12 +167,14 @@ interface ApiRequestConfig extends AxiosRequestConfig { readonly ignoreBlocked?: boolean; readonly ignoreError?: boolean; readonly timeout?: number; + readonly cancelableUuid?: string; } export type ApiResponsePromise = Promise>>; let needBlockRequest = false; const blockedRequests: ((token: string | undefined) => void)[] = []; +const cancelableRequests: Record = {}; axios.defaults.baseURL = getBasePath() + BASE_API_URL_PATH; axios.defaults.timeout = DEFAULT_API_TIMEOUT; @@ -202,8 +205,20 @@ axios.interceptors.request.use((config: ApiRequestConfig) => { }); axios.interceptors.response.use(response => { + if ('cancelableUuid' in response.config && response.config.cancelableUuid && cancelableRequests[response.config.cancelableUuid as string]) { + logger.debug('Response canceled by user request, url: ' + response.config.url + ', cancelableUuid: ' + response.config.cancelableUuid); + delete cancelableRequests[response.config.cancelableUuid as string]; + return Promise.reject({ canceled: true }); + } + return response; }, error => { + if ('cancelableUuid' in error.response.config && error.response.config.cancelableUuid && cancelableRequests[error.response.config.cancelableUuid]) { + logger.debug('Response canceled by user request, url: ' + error.response.config.url + ', cancelableUuid: ' + error.response.config.cancelableUuid); + delete cancelableRequests[error.response.config.cancelableUuid]; + return Promise.reject({ canceled: true }); + } + if (error.response && !error.response.config.ignoreError && error.response.data && error.response.data.errorCode) { const errorCode = error.response.data.errorCode; @@ -650,12 +665,13 @@ export default { deleteTransactionTemplate: (req: TransactionTemplateDeleteRequest): ApiResponsePromise => { return axios.post>('v1/transaction/templates/delete.json', req); }, - recognizeReceiptImage: ({ imageFile }: { imageFile: File }): ApiResponsePromise => { + recognizeReceiptImage: ({ imageFile, cancelableUuid }: { imageFile: File, cancelableUuid?: string }): ApiResponsePromise => { return axios.postForm>('v1/llm/transactions/recognize_receipt_image.json', { image: imageFile }, { - timeout: DEFAULT_LLM_API_TIMEOUT - }); + timeout: DEFAULT_LLM_API_TIMEOUT, + cancelableUuid: cancelableUuid + } as ApiRequestConfig); }, getLatestExchangeRates: (param: { ignoreError?: boolean }): ApiResponsePromise => { return axios.get>('v1/exchange_rates/latest.json', { @@ -672,6 +688,9 @@ export default { getServerVersion: (): ApiResponsePromise => { return axios.get>('v1/systems/version.json'); }, + cancelRequest: (cancelableUuid: string) => { + cancelableRequests[cancelableUuid] = true; + }, generateQrCodeUrl: (qrCodeName: string): string => { return `${getBasePath()}${BASE_QRCODE_PATH}/${qrCodeName}.png`; }, diff --git a/src/lib/ui/mobile.ts b/src/lib/ui/mobile.ts index d9e22613..92a46c12 100644 --- a/src/lib/ui/mobile.ts +++ b/src/lib/ui/mobile.ts @@ -42,6 +42,12 @@ export function hideLoading(): void { }); } +export function closeAllDialog(): void { + f7ready((f7) => { + return f7.dialog.close(); + }); +} + export function createInlinePicker(containerEl: string, inputEl: string, cols: Picker.ColumnParameters[], value: string[], events?: { change: (picker: Picker.Picker, value: unknown, displayValue: unknown) => void }): Picker.Picker { return f7.picker.create({ containerEl: containerEl, @@ -282,6 +288,27 @@ export function useI18nUIComponents() { }); } + function showCancelableLoading(message: string, cancelButtonText: string, cancelCallback?: (dialog: Dialog.Dialog, e: Event) => void): void { + const cancelButton: Dialog.DialogButton = { + text: tt(cancelButtonText), + onClick: (dialog, event) => { + if (cancelCallback) { + cancelCallback(dialog, event); + } + } + }; + + f7ready((f7) => { + f7.dialog.create({ + title: tt(message), + content: `
${[0, 1, 2, 3, 4, 5, 6, 7].map(() => '').join('')}
`, + cssClass: 'dialog-preloader', + animate: isEnableAnimate(), + buttons: [cancelButton] + }).open(); + }); + } + function showToast(message: string, timeout?: number): void { f7ready((f7) => { f7.toast.create({ @@ -296,6 +323,7 @@ export function useI18nUIComponents() { showAlert: showAlert, showConfirm: showConfirm, showPrompt: showPrompt, + showCancelableLoading: showCancelableLoading, showToast: showToast, routeBackOnError } diff --git a/src/locales/de.json b/src/locales/de.json index d665c2e5..753020ba 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -1398,6 +1398,8 @@ "Clear": "Löschen", "Generate": "Generate", "Recognize": "Recognize", + "Recognizing...": "Recognizing...", + "Cancel Recognition": "Cancel Recognition", "None": "Keine", "Unspecified": "Nicht angegeben", "Not set": "Nicht festgelegt", @@ -1522,6 +1524,7 @@ "Save Display Order": "Anzeigereihenfolge speichern", "Change Language": "Sprache ändern", "Date is too early": "Datum ist zu früh", + "User Canceled": "User Canceled", "Welcome to ezBookkeeping": "Willkommen bei ezBookkeeping", "Please log in with your ezBookkeeping account": "Bitte melden Sie sich mit Ihrem ezBookkeeping-Konto an", "Unlock Application": "Anwendung entsperren", diff --git a/src/locales/en.json b/src/locales/en.json index c36067d2..fac1200a 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1398,6 +1398,8 @@ "Clear": "Clear", "Generate": "Generate", "Recognize": "Recognize", + "Recognizing...": "Recognizing...", + "Cancel Recognition": "Cancel Recognition", "None": "None", "Unspecified": "Unspecified", "Not set": "Not set", @@ -1522,6 +1524,7 @@ "Save Display Order": "Save Display Order", "Change Language": "Change Language", "Date is too early": "Date is too early", + "User Canceled": "User Canceled", "Welcome to ezBookkeeping": "Welcome to ezBookkeeping", "Please log in with your ezBookkeeping account": "Please log in with your ezBookkeeping account", "Unlock Application": "Unlock Application", diff --git a/src/locales/es.json b/src/locales/es.json index d85c5a5a..482bf3f5 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -1398,6 +1398,8 @@ "Clear": "Claro", "Generate": "Generate", "Recognize": "Recognize", + "Recognizing...": "Recognizing...", + "Cancel Recognition": "Cancel Recognition", "None": "Ninguno", "Unspecified": "No especificado", "Not set": "No establecido", @@ -1522,6 +1524,7 @@ "Save Display Order": "Guardar orden de visualización", "Change Language": "Cambiar idioma", "Date is too early": "La fecha es demasiado temprana", + "User Canceled": "User Canceled", "Welcome to ezBookkeeping": "Bienvenido a ezBookkeeping", "Please log in with your ezBookkeeping account": "Inicie sesión con su cuenta de ezBookkeeping", "Unlock Application": "Desbloquear aplicación", diff --git a/src/locales/fr.json b/src/locales/fr.json index 605032b7..936761d5 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -1398,6 +1398,8 @@ "Clear": "Effacer", "Generate": "Générer", "Recognize": "Reconnaître", + "Recognizing...": "Recognizing...", + "Cancel Recognition": "Cancel Recognition", "None": "Aucun", "Unspecified": "Non spécifié", "Not set": "Non défini", @@ -1522,6 +1524,7 @@ "Save Display Order": "Enregistrer l'ordre d'affichage", "Change Language": "Changer de langue", "Date is too early": "La date est trop ancienne", + "User Canceled": "User Canceled", "Welcome to ezBookkeeping": "Bienvenue dans ezBookkeeping", "Please log in with your ezBookkeeping account": "Veuillez vous connecter avec votre compte ezBookkeeping", "Unlock Application": "Déverrouiller l'application", diff --git a/src/locales/it.json b/src/locales/it.json index ee2c6421..121d2e69 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -1398,6 +1398,8 @@ "Clear": "Pulisci", "Generate": "Generate", "Recognize": "Recognize", + "Recognizing...": "Recognizing...", + "Cancel Recognition": "Cancel Recognition", "None": "Nessuno", "Unspecified": "Non specificato", "Not set": "Non impostato", @@ -1522,6 +1524,7 @@ "Save Display Order": "Salva ordine di visualizzazione", "Change Language": "Cambia lingua", "Date is too early": "Data troppo anticipata", + "User Canceled": "User Canceled", "Welcome to ezBookkeeping": "Benvenuto in ezBookkeeping", "Please log in with your ezBookkeeping account": "Accedi con il tuo account ezBookkeeping", "Unlock Application": "Sblocca applicazione", diff --git a/src/locales/ja.json b/src/locales/ja.json index 887c88f2..a5739854 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -1398,6 +1398,8 @@ "Clear": "消去", "Generate": "Generate", "Recognize": "Recognize", + "Recognizing...": "Recognizing...", + "Cancel Recognition": "Cancel Recognition", "None": "なし", "Unspecified": "不特定", "Not set": "セットしていない", @@ -1522,6 +1524,7 @@ "Save Display Order": "表示順の保存", "Change Language": "言語の変更", "Date is too early": "日付が早すぎます", + "User Canceled": "User Canceled", "Welcome to ezBookkeeping": "ezBookkeepingへようこそ", "Please log in with your ezBookkeeping account": "ezBookkeepingアカウントにログインしてください", "Unlock Application": "アプリのロックを解除", diff --git a/src/locales/nl.json b/src/locales/nl.json index c2fbe7ef..e0ce490b 100644 --- a/src/locales/nl.json +++ b/src/locales/nl.json @@ -1398,6 +1398,8 @@ "Clear": "Wissen", "Generate": "Genereren", "Recognize": "Recognize", + "Recognizing...": "Recognizing...", + "Cancel Recognition": "Cancel Recognition", "None": "Geen", "Unspecified": "Niet gespecificeerd", "Not set": "Niet ingesteld", @@ -1522,6 +1524,7 @@ "Save Display Order": "Weergavevolgorde opslaan", "Change Language": "Taal wijzigen", "Date is too early": "Datum is te vroeg", + "User Canceled": "User Canceled", "Welcome to ezBookkeeping": "Welkom bij ezBookkeeping", "Please log in with your ezBookkeeping account": "Log in met je ezBookkeeping-account", "Unlock Application": "Applicatie ontgrendelen", diff --git a/src/locales/pt_BR.json b/src/locales/pt_BR.json index 760f5ad4..ebe0c02d 100644 --- a/src/locales/pt_BR.json +++ b/src/locales/pt_BR.json @@ -1398,6 +1398,8 @@ "Clear": "Limpar", "Generate": "Generate", "Recognize": "Recognize", + "Recognizing...": "Recognizing...", + "Cancel Recognition": "Cancel Recognition", "None": "Nenhum", "Unspecified": "Não especificado", "Not set": "Não definido", @@ -1522,6 +1524,7 @@ "Save Display Order": "Salvar Ordem de Exibição", "Change Language": "Alterar Idioma", "Date is too early": "Data é muito cedo", + "User Canceled": "User Canceled", "Welcome to ezBookkeeping": "Bem-vindo ao ezBookkeeping", "Please log in with your ezBookkeeping account": "Por favor, faça login com sua conta ezBookkeeping", "Unlock Application": "Desbloquear Aplicativo", diff --git a/src/locales/ru.json b/src/locales/ru.json index e7f2f146..21fb336f 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -1398,6 +1398,8 @@ "Clear": "Очистить", "Generate": "Generate", "Recognize": "Recognize", + "Recognizing...": "Recognizing...", + "Cancel Recognition": "Cancel Recognition", "None": "Нет", "Unspecified": "Не указано", "Not set": "Не установлено", @@ -1522,6 +1524,7 @@ "Save Display Order": "Сохранить порядок отображения", "Change Language": "Изменить язык", "Date is too early": "Дата слишком ранняя", + "User Canceled": "User Canceled", "Welcome to ezBookkeeping": "Добро пожаловать в ezBookkeeping", "Please log in with your ezBookkeeping account": "Пожалуйста, войдите в свою учетную запись ezBookkeeping", "Unlock Application": "Разблокировать приложение", diff --git a/src/locales/th.json b/src/locales/th.json index 0dfb4c55..eade9b49 100644 --- a/src/locales/th.json +++ b/src/locales/th.json @@ -1398,6 +1398,8 @@ "Clear": "ล้าง", "Generate": "สร้าง", "Recognize": "จดจำ", + "Recognizing...": "Recognizing...", + "Cancel Recognition": "Cancel Recognition", "None": "ไม่มี", "Unspecified": "ไม่ระบุ", "Not set": "ยังไม่ได้ตั้งค่า", @@ -1522,6 +1524,7 @@ "Save Display Order": "บันทึกลำดับการแสดง", "Change Language": "เปลี่ยนภาษา", "Date is too early": "วันที่เร็วเกินไป", + "User Canceled": "User Canceled", "Welcome to ezBookkeeping": "ยินดีต้อนรับสู่ ezBookkeeping", "Please log in with your ezBookkeeping account": "กรุณาเข้าสู่ระบบด้วยบัญชี ezBookkeeping ของคุณ", "Unlock Application": "ปลดล็อกแอปพลิเคชัน", diff --git a/src/locales/uk.json b/src/locales/uk.json index 98e3397e..8849e338 100644 --- a/src/locales/uk.json +++ b/src/locales/uk.json @@ -1398,6 +1398,8 @@ "Clear": "Очистити", "Generate": "Generate", "Recognize": "Recognize", + "Recognizing...": "Recognizing...", + "Cancel Recognition": "Cancel Recognition", "None": "Немає", "Unspecified": "Не вказано", "Not set": "Не встановлено", @@ -1522,6 +1524,7 @@ "Save Display Order": "Зберегти порядок відображення", "Change Language": "Змінити мову", "Date is too early": "Дата занадто рання", + "User Canceled": "User Canceled", "Welcome to ezBookkeeping": "Ласкаво просимо до ezBookkeeping", "Please log in with your ezBookkeeping account": "Будь ласка, увійдіть до свого облікового запису ezBookkeeping", "Unlock Application": "Розблокувати застосунок", diff --git a/src/locales/vi.json b/src/locales/vi.json index 4798fe06..ebe28fa5 100644 --- a/src/locales/vi.json +++ b/src/locales/vi.json @@ -1398,6 +1398,8 @@ "Clear": "Xóa", "Generate": "Generate", "Recognize": "Recognize", + "Recognizing...": "Recognizing...", + "Cancel Recognition": "Cancel Recognition", "None": "Không có", "Unspecified": "Không xác định", "Not set": "Not set", @@ -1522,6 +1524,7 @@ "Save Display Order": "Lưu thứ tự hiển thị", "Change Language": "Thay đổi ngôn ngữ", "Date is too early": "Ngày quá sớm", + "User Canceled": "User Canceled", "Welcome to ezBookkeeping": "Chào mừng đến với ezBookkeeping", "Please log in with your ezBookkeeping account": "Vui lòng đăng nhập bằng tài khoản ezBookkeeping của bạn", "Unlock Application": "Mở khóa ứng dụng", diff --git a/src/locales/zh_Hans.json b/src/locales/zh_Hans.json index f0862d44..015ef5f9 100644 --- a/src/locales/zh_Hans.json +++ b/src/locales/zh_Hans.json @@ -1398,6 +1398,8 @@ "Clear": "清除", "Generate": "生成", "Recognize": "识别", + "Recognizing...": "正在识别...", + "Cancel Recognition": "取消识别", "None": "无", "Unspecified": "未指定", "Not set": "未设置", @@ -1522,6 +1524,7 @@ "Save Display Order": "保存显示顺序", "Change Language": "修改语言", "Date is too early": "日期过早", + "User Canceled": "用户已取消", "Welcome to ezBookkeeping": "欢迎使用 ezBookkeeping", "Please log in with your ezBookkeeping account": "请使用您的 ezBookkeeping 账号登录", "Unlock Application": "解锁应用", diff --git a/src/locales/zh_Hant.json b/src/locales/zh_Hant.json index c1d1a1a2..4b102648 100644 --- a/src/locales/zh_Hant.json +++ b/src/locales/zh_Hant.json @@ -1398,6 +1398,8 @@ "Clear": "清除", "Generate": "產生", "Recognize": "識別", + "Recognizing...": "正在識別...", + "Cancel Recognition": "取消識別", "None": "無", "Unspecified": "未指定", "Not set": "未設置", @@ -1522,6 +1524,7 @@ "Save Display Order": "儲存顯示順序", "Change Language": "變更語言", "Date is too early": "日期過早", + "User Canceled": "使用者已取消", "Welcome to ezBookkeeping": "歡迎使用 ezBookkeeping", "Please log in with your ezBookkeeping account": "請使用您的 ezBookkeeping 帳號登入", "Unlock Application": "解鎖應用程式", diff --git a/src/stores/transaction.ts b/src/stores/transaction.ts index 90460a10..16e9dcf5 100644 --- a/src/stores/transaction.ts +++ b/src/stores/transaction.ts @@ -1160,9 +1160,9 @@ export const useTransactionsStore = defineStore('transactions', () => { }); } - function recognizeReceiptImage({ imageFile }: { imageFile: File }): Promise { + function recognizeReceiptImage({ imageFile, cancelableUuid }: { imageFile: File, cancelableUuid?: string }): Promise { return new Promise((resolve, reject) => { - services.recognizeReceiptImage({ imageFile }).then(response => { + services.recognizeReceiptImage({ imageFile, cancelableUuid }).then(response => { const data = response.data; if (!data || !data.success || !data.result) { @@ -1172,6 +1172,10 @@ export const useTransactionsStore = defineStore('transactions', () => { resolve(data.result); }).catch(error => { + if (error.canceled) { + reject(error); + } + logger.error('failed to recognize image', error); if (error.response && error.response.data && error.response.data.errorMessage) { @@ -1185,6 +1189,10 @@ export const useTransactionsStore = defineStore('transactions', () => { }); } + function cancelRecognizeReceiptImage(cancelableUuid: string): void { + services.cancelRequest(cancelableUuid); + } + function parseImportDsvFile({ fileType, fileEncoding, importFile }: { fileType: string, fileEncoding?: string, importFile: File }): Promise { return new Promise((resolve, reject) => { services.parseImportDsvFile({ fileType, fileEncoding, importFile }).then(response => { @@ -1399,6 +1407,7 @@ export const useTransactionsStore = defineStore('transactions', () => { saveTransaction, deleteTransaction, recognizeReceiptImage, + cancelRecognizeReceiptImage, parseImportDsvFile, parseImportTransaction, importTransactions, diff --git a/src/views/desktop/transactions/list/dialogs/AIImageRecognitionDialog.vue b/src/views/desktop/transactions/list/dialogs/AIImageRecognitionDialog.vue index 21257a98..816407d5 100644 --- a/src/views/desktop/transactions/list/dialogs/AIImageRecognitionDialog.vue +++ b/src/views/desktop/transactions/list/dialogs/AIImageRecognitionDialog.vue @@ -33,8 +33,10 @@ {{ tt('Recognize') }} + {{ tt('Cancel Recognition') }} {{ tt('Cancel') }} + @click="cancel" v-if="!recognizing || !cancelRecognizingUuid">{{ tt('Cancel') }} @@ -58,6 +60,7 @@ import { SUPPORTED_IMAGE_EXTENSIONS } from '@/consts/file.ts'; import type { RecognizedReceiptImageResponse } from '@/models/large_language_model.ts'; +import { generateRandomUUID } from '@/lib/misc.ts'; import { compressJpgImage } from '@/lib/ui/common.ts'; import logger from '@/lib/logger.ts'; @@ -76,6 +79,7 @@ let rejectFunc: ((reason?: unknown) => void) | null = null; const showState = ref(false); const loading = ref(false); const recognizing = ref(false); +const cancelRecognizingUuid = ref(undefined); const imageFile = ref(null); const imageSrc = ref(undefined); const isDragOver = ref(false); @@ -96,6 +100,7 @@ function open(): Promise { showState.value = true; loading.value = false; recognizing.value = false; + cancelRecognizingUuid.value = undefined; imageFile.value = null; imageSrc.value = undefined; @@ -136,16 +141,24 @@ function recognize(): void { return; } + cancelRecognizingUuid.value = generateRandomUUID(); recognizing.value = true; transactionsStore.recognizeReceiptImage({ - imageFile: imageFile.value + imageFile: imageFile.value, + cancelableUuid: cancelRecognizingUuid.value }).then(response => { resolveFunc?.(response); showState.value = false; recognizing.value = false; + cancelRecognizingUuid.value = undefined; }).catch(error => { + if (error.canceled) { + return; + } + recognizing.value = false; + cancelRecognizingUuid.value = undefined; if (!error.processed) { snackbar.value?.showError(error); @@ -153,11 +166,24 @@ function recognize(): void { }); } +function cancelRecognize(): void { + if (!cancelRecognizingUuid.value) { + return; + } + + transactionsStore.cancelRecognizeReceiptImage(cancelRecognizingUuid.value); + recognizing.value = false; + cancelRecognizingUuid.value = undefined; + + snackbar.value?.showMessage('User Canceled'); +} + function cancel(): void { rejectFunc?.(); showState.value = false; loading.value = false; recognizing.value = false; + cancelRecognizingUuid.value = undefined; imageFile.value = null; imageSrc.value = undefined; }