diff --git a/apps/desktop/src/app/history/history.component.html b/apps/desktop/src/app/history/history.component.html index 2a0a198f9..bddf4d502 100644 --- a/apps/desktop/src/app/history/history.component.html +++ b/apps/desktop/src/app/history/history.component.html @@ -14,15 +14,13 @@

{{ 'history.title' | translate }}

{{ 'history.back' | translate }} - + {{ 'history.date' | translate }} {{ 'history.source' | translate }} {{ 'upload.steps.destination' | translate }} {{ 'history.knowledge_box' | translate }} - + {{ 'history.data' | translate }} @@ -30,14 +28,19 @@

{{ 'history.title' | translate }}

{{ upload.from }} {{ upload.to }} {{ upload.kbSlug }} - + + {{ upload.total }}
diff --git a/apps/desktop/src/app/history/history.component.ts b/apps/desktop/src/app/history/history.component.ts index 1968b223c..cd7a9ad10 100644 --- a/apps/desktop/src/app/history/history.component.ts +++ b/apps/desktop/src/app/history/history.component.ts @@ -35,6 +35,10 @@ export class HistoryComponent { progress: (100 * sync.files.filter((f) => f.status === FileStatus.UPLOADED).length) / sync.files.length, started: sync.started, completed: sync.completed, + errors: sync.files + .filter((f) => f.status === FileStatus.ERROR) + .map((f) => f.error || 'Unknown error') + .join(' – '), })), ), ); diff --git a/apps/desktop/src/app/history/history.module.ts b/apps/desktop/src/app/history/history.module.ts index 503428eb0..4e81e9b18 100644 --- a/apps/desktop/src/app/history/history.module.ts +++ b/apps/desktop/src/app/history/history.module.ts @@ -5,7 +5,7 @@ import { ProgressBarModule } from '@flaps/common'; import { TranslateModule } from '@ngx-translate/core'; import { HistoryComponent } from './history.component'; -import { PaButtonModule, PaIconModule, PaTableModule } from '@guillotinaweb/pastanaga-angular'; +import { PaButtonModule, PaIconModule, PaTableModule, PaTooltipModule } from '@guillotinaweb/pastanaga-angular'; import { BackButtonComponent } from '@nuclia/sistema'; @NgModule({ @@ -18,6 +18,7 @@ import { BackButtonComponent } from '@nuclia/sistema'; PaButtonModule, PaIconModule, PaTableModule, + PaTooltipModule, ], exports: [], declarations: [HistoryComponent], diff --git a/apps/desktop/src/app/sync/destinations/nuclia-cloud.ts b/apps/desktop/src/app/sync/destinations/nuclia-cloud.ts index c791b8350..311b589d4 100644 --- a/apps/desktop/src/app/sync/destinations/nuclia-cloud.ts +++ b/apps/desktop/src/app/sync/destinations/nuclia-cloud.ts @@ -1,5 +1,5 @@ -import { INuclia, Nuclia, NucliaOptions, WritableKnowledgeBox } from '@nuclia/core'; -import { catchError, from, map, Observable, of, switchMap } from 'rxjs'; +import { Account, INuclia, Nuclia, NucliaOptions, WritableKnowledgeBox } from '@nuclia/core'; +import { catchError, from, map, Observable, of, switchMap, tap } from 'rxjs'; import { ConnectorParameters, ConnectorSettings, @@ -25,7 +25,8 @@ export const NucliaCloudKB: DestinationConnectorDefinition = { class NucliaCloudKBImpl implements IDestinationConnector { nuclia: INuclia; - kb?: WritableKnowledgeBox; + kbs: { [slug: string]: WritableKnowledgeBox } = {}; + account?: Account; constructor(nuclia: INuclia) { this.nuclia = nuclia; @@ -56,10 +57,18 @@ class NucliaCloudKBImpl implements IDestinationConnector { ): Observable { if (params && params['kb'] && data.blob) { const blob = data.blob; - const kb$ = this.kb - ? of(this.kb) - : this.nuclia.db.getKnowledgeBox(localStorage.getItem(ACCOUNT_KEY) || '', params['kb']); - return kb$.pipe( + const mimetype = lookup(filename) || 'application/octet-stream'; + return this.getAccount().pipe( + tap((account) => { + const applicableLimit = this.isMedia(mimetype) + ? account.limits.upload.upload_limit_max_media_file_size + : account.limits.upload.upload_limit_max_non_media_file_size; + if (blob.size > applicableLimit) { + console.error(`File too large. Size=${blob.size}, limit=${applicableLimit}`); + throw new Error(`File "${filename}" is too large.`); + } + }), + switchMap(() => this.getKb(params['kb'])), switchMap((kb) => from(sha256(originalId)).pipe( switchMap((slug) => @@ -77,7 +86,7 @@ class NucliaCloudKBImpl implements IDestinationConnector { ), switchMap((resource) => { return resource.upload('file', new File([blob], filename), false, { - contentType: lookup(filename) || 'application/octet-stream', + contentType: mimetype, }); }), ), @@ -95,10 +104,7 @@ class NucliaCloudKBImpl implements IDestinationConnector { data: { uri: string; extra_headers: { [key: string]: string } }, ): Observable { if (params && params['kb']) { - const kb$ = this.kb - ? of(this.kb) - : this.nuclia.db.getKnowledgeBox(localStorage.getItem(ACCOUNT_KEY) || '', params['kb']); - return kb$.pipe( + return this.getKb(params['kb']).pipe( switchMap((kb) => kb.createResource({ title: filename, files: { [filename]: { file: data } } })), map(() => undefined), ); @@ -128,4 +134,28 @@ class NucliaCloudKBImpl implements IDestinationConnector { })), ); } + + private getKb(slug: string): Observable { + if (!this.kbs[slug]) { + return this.nuclia.db + .getKnowledgeBox(localStorage.getItem(ACCOUNT_KEY) || '', slug) + .pipe(tap((kb) => (this.kbs[slug] = kb))); + } else { + return of(this.kbs[slug]); + } + } + + private getAccount(): Observable { + if (!this.account) { + return this.nuclia.db + .getAccount(localStorage.getItem(ACCOUNT_KEY) || '') + .pipe(tap((account) => (this.account = account))); + } else { + return of(this.account); + } + } + + private isMedia(mimetype: string): boolean { + return mimetype.startsWith('image/') || mimetype.startsWith('video/') || mimetype.startsWith('audio/'); + } } diff --git a/apps/desktop/src/app/sync/models.ts b/apps/desktop/src/app/sync/models.ts index 90b130e27..a6d95c6c2 100644 --- a/apps/desktop/src/app/sync/models.ts +++ b/apps/desktop/src/app/sync/models.ts @@ -35,6 +35,7 @@ export enum FileStatus { PENDING = 'PENDING', PROCESSING = 'PROCESSING', UPLOADED = 'UPLOADED', + ERROR = 'ERROR', } export interface SyncItem { @@ -43,6 +44,7 @@ export interface SyncItem { originalId: string; metadata: { [key: string]: string }; status: FileStatus; + error?: string; } export interface SearchResults { diff --git a/apps/desktop/src/app/sync/sync.service.ts b/apps/desktop/src/app/sync/sync.service.ts index 814e83c92..6783dacdf 100644 --- a/apps/desktop/src/app/sync/sync.service.ts +++ b/apps/desktop/src/app/sync/sync.service.ts @@ -172,7 +172,6 @@ export class SyncService { destinationInstance.uploadLink!(f.title, sync.destination.params, link).pipe( tap(() => { f.status = FileStatus.UPLOADED; - sync.completed = true; this.onQueueUpdate(); }), ), @@ -191,9 +190,14 @@ export class SyncService { return destinationInstance .upload(f.originalId, f.title, sync.destination.params, { blob }) .pipe( + catchError((error) => { + f.status = FileStatus.ERROR; + f.error = error.toString(); + this.onQueueUpdate(); + return of(undefined); + }), tap(() => { - f.status = FileStatus.UPLOADED; - sync.completed = true; + f.status = FileStatus.ERROR ? FileStatus.ERROR : FileStatus.UPLOADED; this.onQueueUpdate(); }), ); @@ -216,6 +220,7 @@ export class SyncService { ); }), tap(() => { + sync.completed = true; this.onQueueUpdate(); }), ); diff --git a/package.json b/package.json index 42e75b1b6..0d2c0f059 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nuclia", - "version": "1.2.3", + "version": "1.2.4", "license": "MIT", "author": "Nuclia.cloud", "description": "Nuclia frontend apps and libs",