@if (snapshot.runningTools.length > 0) {
@for (tool of snapshot.runningTools; track tool) {
@@ -54,8 +54,11 @@
}
@if (!snapshot.isRunning && !isFirst && type === "Bot") {
-
diff --git a/frontend/src/app/shared/components/chat-item.component.ts b/frontend/src/app/shared/components/chat-item.component.ts
index cf5163bdf..a380a7924 100644
--- a/frontend/src/app/shared/components/chat-item.component.ts
+++ b/frontend/src/app/shared/components/chat-item.component.ts
@@ -7,9 +7,10 @@
import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core';
-import { Observable } from 'rxjs';
-import { HTTP, MarkdownDirective, ResizedDirective, StatefulComponent, TranslatePipe, Types } from '@app/framework';
-import { ChatEventDto, Profile } from '../internal';
+import { lastValueFrom, Observable } from 'rxjs';
+import { filter } from 'rxjs/operators';
+import { ApiUrlConfig, HTTP, LoaderComponent, MarkdownDirective, markdownExtractImage, markdownHasImage, markdownTransformImages, ResizedDirective, StatefulComponent, TranslatePipe, Types } from '@app/framework';
+import { AssetDto, AssetUploaderState, ChatEventDto, Profile } from '../internal';
import { UserIdPicturePipe } from './pipes';
interface State {
@@ -19,6 +20,9 @@ interface State {
// True, when failed
isFailed: boolean;
+ // True, when a copy is in process.
+ isCopying: boolean;
+
// The content.
content: string;
@@ -32,6 +36,7 @@ interface State {
styleUrls: ['./chat-item.component.scss'],
templateUrl: './chat-item.component.html',
imports: [
+ LoaderComponent,
MarkdownDirective,
ResizedDirective,
TranslatePipe,
@@ -43,12 +48,12 @@ export class ChatItemComponent extends StatefulComponent
{
@ViewChild('focusElement', { static: false })
public focusElement!: ElementRef;
- @ViewChild('contentElement', { static: false })
- public contentElement!: ElementRef;
-
@Input({ required: true })
public type: 'Bot' | 'User' | 'System' = 'Bot';
+ @Input({ required: true })
+ public folderId?: string;
+
@Input({ required: true })
public user!: Profile;
@@ -105,9 +110,13 @@ export class ChatItemComponent extends StatefulComponent {
@Output()
public contentSelect = new EventEmitter();
- constructor() {
+ constructor(
+ private readonly apiUrl: ApiUrlConfig,
+ private readonly assetUploader: AssetUploaderState,
+ ) {
super({
content: '',
+ isCopying: false,
isFailed: false,
isRunning: false,
runningTools: [],
@@ -122,19 +131,36 @@ export class ChatItemComponent extends StatefulComponent {
this.focusElement.nativeElement?.scrollIntoView();
}
- public selectContent() {
- this.contentSelect.emit(this.snapshot.content);
+ public async selectContent() {
+ let markdown = this.snapshot.content;
+
+ if (!markdownHasImage(markdown)) {
+ this.contentSelect.emit(markdown);
+ }
+
+ this.next({ isCopying: true });
+ try {
+ markdown = await markdownTransformImages(markdown, async img => {
+ const asset = await lastValueFrom(
+ this.assetUploader.uploadFile(img, this.folderId)
+ .pipe(filter(x => Types.is(x, AssetDto)))) as AssetDto;
+
+ return asset.fullUrl(this.apiUrl);
+ });
+
+ this.contentSelect.emit(markdown);
+ } finally {
+ this.next({ isCopying: false });
+ }
}
public selectImage() {
- const image = this.contentElement.nativeElement?.querySelector('img');
+ const image = markdownExtractImage(this.snapshot.content);
if (!image) {
return;
}
- const name = image.alt || 'image.webp';
-
- this.contentSelect.emit({ url: image.src, name });
+ this.contentSelect.emit(image);
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/app/shared/components/contents/content-list-field.component.ts b/frontend/src/app/shared/components/contents/content-list-field.component.ts
index 3119a86f7..dc421e2c3 100644
--- a/frontend/src/app/shared/components/contents/content-list-field.component.ts
+++ b/frontend/src/app/shared/components/contents/content-list-field.component.ts
@@ -37,7 +37,7 @@ interface State {
TranslatePipe,
TranslationStatusComponent,
UserNameRefPipe,
- UserPictureRefPipe
+ UserPictureRefPipe,
],
})
export class ContentListFieldComponent extends StatefulComponent {
diff --git a/frontend/src/app/shared/components/forms/rich-editor.component.html b/frontend/src/app/shared/components/forms/rich-editor.component.html
index 2639770e3..3ec7fdbc3 100644
--- a/frontend/src/app/shared/components/forms/rich-editor.component.html
+++ b/frontend/src/app/shared/components/forms/rich-editor.component.html
@@ -14,4 +14,8 @@
[schemaIdentifiers]="schemaIds"
*sqxModal="contentsDialog">
-
+
diff --git a/frontend/src/app/shared/components/search/search-form.component.ts b/frontend/src/app/shared/components/search/search-form.component.ts
index e9c6c5963..f884c5938 100644
--- a/frontend/src/app/shared/components/search/search-form.component.ts
+++ b/frontend/src/app/shared/components/search/search-form.component.ts
@@ -38,7 +38,7 @@ import { SavedQueriesComponent } from './shared-queries.component';
TooltipDirective,
TourHintDirective,
TourStepDirective,
- TranslatePipe
+ TranslatePipe,
],
})
export class SearchFormComponent {
diff --git a/frontend/src/app/shared/services/query.ts b/frontend/src/app/shared/services/query.ts
index 3e863754f..87621680c 100644
--- a/frontend/src/app/shared/services/query.ts
+++ b/frontend/src/app/shared/services/query.ts
@@ -146,7 +146,7 @@ export function isLogical(input: FilterNode): input is FilterLogical {
}
export function isComparison(input: FilterNode): input is FilterComparison {
- return !isNegation(input) &&! isComparison(input);
+ return !isNegation(input) && !isComparison(input);
}
export interface QuerySorting {
diff --git a/frontend/src/app/shared/services/roles.service.spec.ts b/frontend/src/app/shared/services/roles.service.spec.ts
index 3d8068100..46e140fd7 100644
--- a/frontend/src/app/shared/services/roles.service.spec.ts
+++ b/frontend/src/app/shared/services/roles.service.spec.ts
@@ -5,10 +5,10 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
+import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { inject, TestBed } from '@angular/core/testing';
import { ApiUrlConfig, Resource, ResourceLinks, RoleDto, RolesDto, RolesPayload, RolesService, Version } from '@app/shared/internal';
-import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
describe('RolesService', () => {
const version = new Version('1');
@@ -21,7 +21,7 @@ describe('RolesService', () => {
{ provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') },
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
- ]
+ ],
});
});
diff --git a/frontend/src/app/shared/services/translations.service.ts b/frontend/src/app/shared/services/translations.service.ts
index 6b2aa68cb..c23141bc4 100644
--- a/frontend/src/app/shared/services/translations.service.ts
+++ b/frontend/src/app/shared/services/translations.service.ts
@@ -9,7 +9,7 @@ import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
-import { ApiUrlConfig, StringHelper, pretifyError } from '@app/framework';
+import { ApiUrlConfig, pretifyError, StringHelper } from '@app/framework';
import { AuthService } from './auth.service';
export class TranslationDto {
diff --git a/frontend/src/app/shell/pages/internal/chat-menu.component.ts b/frontend/src/app/shell/pages/internal/chat-menu.component.ts
index 562144d84..c944b3ef9 100644
--- a/frontend/src/app/shell/pages/internal/chat-menu.component.ts
+++ b/frontend/src/app/shell/pages/internal/chat-menu.component.ts
@@ -18,7 +18,7 @@ import { AppsState, ChatDialogComponent, DialogModel, ModalDirective, UIOptions
imports: [
AsyncPipe,
ChatDialogComponent,
- ModalDirective
+ ModalDirective,
],
})
export class ChatMenuComponent {
@@ -27,7 +27,7 @@ export class ChatMenuComponent {
public readonly hasChatBot = inject(UIOptions).value.canUseChatBot;
constructor(
- public readonly appsState: AppsState
+ public readonly appsState: AppsState,
) {
}
}
diff --git a/frontend/src/app/shell/pages/internal/internal-area.component.ts b/frontend/src/app/shell/pages/internal/internal-area.component.ts
index 9e803e8c8..facd55ea8 100644
--- a/frontend/src/app/shell/pages/internal/internal-area.component.ts
+++ b/frontend/src/app/shell/pages/internal/internal-area.component.ts
@@ -11,12 +11,12 @@ import { ActivatedRoute, RouterLink, RouterOutlet } from '@angular/router';
import { DialogService, LoadingService, Notification, Subscriptions, UIOptions } from '@app/shared';
import { AssetUploaderComponent } from '@app/shared/components/assets/asset-uploader.component';
import { AppsMenuComponent } from './apps-menu.component';
+import { ChatMenuComponent } from './chat-menu.component';
import { FeedbackMenuComponent } from './feedback-menu.component';
import { LogoComponent } from './logo.component';
import { NotificationsMenuComponent } from './notifications-menu.component';
import { ProfileMenuComponent } from './profile-menu.component';
import { SearchMenuComponent } from './search-menu.component';
-import { ChatMenuComponent } from './chat-menu.component';
@Component({
standalone: true,