support canceling AI image recognition
This commit is contained in:
@@ -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<HTMLInputElement>('imageInput');
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
const recognizing = ref<boolean>(false);
|
||||
const cancelRecognizingUuid = ref<string | undefined>(undefined);
|
||||
const imageFile = ref<File | null>(null);
|
||||
const imageSrc = ref<string | undefined>(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;
|
||||
}
|
||||
|
||||
@@ -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<T> = Promise<AxiosResponse<ApiResponse<T>>>;
|
||||
|
||||
let needBlockRequest = false;
|
||||
const blockedRequests: ((token: string | undefined) => void)[] = [];
|
||||
const cancelableRequests: Record<string, boolean> = {};
|
||||
|
||||
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<boolean> => {
|
||||
return axios.post<ApiResponse<boolean>>('v1/transaction/templates/delete.json', req);
|
||||
},
|
||||
recognizeReceiptImage: ({ imageFile }: { imageFile: File }): ApiResponsePromise<RecognizedReceiptImageResponse> => {
|
||||
recognizeReceiptImage: ({ imageFile, cancelableUuid }: { imageFile: File, cancelableUuid?: string }): ApiResponsePromise<RecognizedReceiptImageResponse> => {
|
||||
return axios.postForm<ApiResponse<RecognizedReceiptImageResponse>>('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<LatestExchangeRateResponse> => {
|
||||
return axios.get<ApiResponse<LatestExchangeRateResponse>>('v1/exchange_rates/latest.json', {
|
||||
@@ -672,6 +688,9 @@ export default {
|
||||
getServerVersion: (): ApiResponsePromise<VersionInfo> => {
|
||||
return axios.get<ApiResponse<VersionInfo>>('v1/systems/version.json');
|
||||
},
|
||||
cancelRequest: (cancelableUuid: string) => {
|
||||
cancelableRequests[cancelableUuid] = true;
|
||||
},
|
||||
generateQrCodeUrl: (qrCodeName: string): string => {
|
||||
return `${getBasePath()}${BASE_QRCODE_PATH}/${qrCodeName}.png`;
|
||||
},
|
||||
|
||||
@@ -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: `<div class="preloader"><span class="preloader-inner">${[0, 1, 2, 3, 4, 5, 6, 7].map(() => '<span class="preloader-inner-line"></span>').join('')}</span></div>`,
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "アプリのロックを解除",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Разблокировать приложение",
|
||||
|
||||
@@ -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": "ปลดล็อกแอปพลิเคชัน",
|
||||
|
||||
@@ -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": "Розблокувати застосунок",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "解锁应用",
|
||||
|
||||
@@ -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": "解鎖應用程式",
|
||||
|
||||
@@ -1160,9 +1160,9 @@ export const useTransactionsStore = defineStore('transactions', () => {
|
||||
});
|
||||
}
|
||||
|
||||
function recognizeReceiptImage({ imageFile }: { imageFile: File }): Promise<RecognizedReceiptImageResponse> {
|
||||
function recognizeReceiptImage({ imageFile, cancelableUuid }: { imageFile: File, cancelableUuid?: string }): Promise<RecognizedReceiptImageResponse> {
|
||||
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<string[][]> {
|
||||
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,
|
||||
|
||||
@@ -33,8 +33,10 @@
|
||||
{{ tt('Recognize') }}
|
||||
<v-progress-circular indeterminate size="22" class="ms-2" v-if="recognizing"></v-progress-circular>
|
||||
</v-btn>
|
||||
<v-btn color="secondary" variant="tonal" :disabled="loading"
|
||||
@click="cancelRecognize" v-if="recognizing && cancelRecognizingUuid">{{ tt('Cancel Recognition') }}</v-btn>
|
||||
<v-btn color="secondary" variant="tonal" :disabled="loading || recognizing"
|
||||
@click="cancel">{{ tt('Cancel') }}</v-btn>
|
||||
@click="cancel" v-if="!recognizing || !cancelRecognizingUuid">{{ tt('Cancel') }}</v-btn>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
@@ -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<boolean>(false);
|
||||
const loading = ref<boolean>(false);
|
||||
const recognizing = ref<boolean>(false);
|
||||
const cancelRecognizingUuid = ref<string | undefined>(undefined);
|
||||
const imageFile = ref<File | null>(null);
|
||||
const imageSrc = ref<string | undefined>(undefined);
|
||||
const isDragOver = ref<boolean>(false);
|
||||
@@ -96,6 +100,7 @@ function open(): Promise<RecognizedReceiptImageResponse> {
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user