@dataclouder/ngx-agent-cards 0.0.87 → 0.0.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -7
- package/fesm2022/dataclouder-ngx-agent-cards.mjs +1134 -1352
- package/fesm2022/dataclouder-ngx-agent-cards.mjs.map +1 -1
- package/lib/components/chat-container/chat-container.component.d.ts +9 -38
- package/lib/components/chat-container/chat-footer/chat-footer.component.d.ts +18 -10
- package/lib/components/chat-container/chat-header/chat-header.component.d.ts +7 -8
- package/lib/components/chat-container/chat-messages-list/chat-message/chat-message.component.d.ts +11 -27
- package/lib/components/chat-container/chat-messages-list/chat-message/chat-message.utils.d.ts +3 -2
- package/lib/components/chat-container/chat-messages-list/chat-messages-list.component.d.ts +3 -3
- package/lib/components/chat-container/chat-messages-list/message-orchestrator/message-orchestrator.component.d.ts +26 -0
- package/lib/components/chat-settings/dc-conversation-userchat-settings.component.d.ts +7 -8
- package/lib/components/dc-agent-card-details/dc-agent-card-details.component.d.ts +4 -6
- package/lib/components/dc-agent-card-lists/agent-card-default-ui/agent-card-default-ui.component.d.ts +5 -5
- package/lib/components/dc-agent-card-lists/dc-agent-card-lists.component.d.ts +9 -10
- package/lib/components/dc-agent-form/account-platform/account-platform-form.component.d.ts +3 -4
- package/lib/components/dc-agent-form/dc-agent-card-form.component.d.ts +16 -18
- package/lib/components/icons/icons.component.d.ts +7 -7
- package/lib/components/prompt-preview-dialog/prompt-preview-dialog.component.d.ts +3 -2
- package/lib/components/provider-selector/provider-selector.component.d.ts +3 -3
- package/lib/components/text-highlighter/text-highlighter.d.ts +62 -0
- package/lib/components/translate-dialog/translate-dialog.component.d.ts +3 -5
- package/lib/models/agent.models.d.ts +5 -7
- package/lib/models/conversation-ai.class.d.ts +2 -2
- package/lib/models/user-data-exchange.d.ts +2 -0
- package/lib/pipes/safe-json.pipe.d.ts +15 -0
- package/lib/services/conversation.service.d.ts +8 -14
- package/lib/services/dc-conversation-builder.service.d.ts +0 -2
- package/lib/services/evaluation.service.d.ts +2 -2
- package/lib/services/message-processing.service.d.ts +2 -2
- package/package.json +1 -1
- package/public-api.d.ts +4 -0
- package/lib/components/audio-text-sync/audio-text-sync.component.d.ts +0 -42
- package/lib/components/chat-container/chat-messages-list/chat-message/message-content/message-content.component.d.ts +0 -24
- package/lib/components/chat-container/chat-messages-list/chat-message/multi-message-content/multi-message-content.d.ts +0 -22
- package/lib/services/audio-text-sync.service.d.ts +0 -57
|
@@ -1,66 +1,62 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, Injectable,
|
|
3
|
-
import * as i1$
|
|
4
|
-
import { CommonModule, NgComponentOutlet } from '@angular/common';
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import * as i6 from 'primeng/dialog';
|
|
2
|
+
import { InjectionToken, Injectable, inject, Pipe, input, output, Input, Component, signal, computed, ChangeDetectorRef, DestroyRef, effect, ChangeDetectionStrategy, ViewChild, ViewChildren } from '@angular/core';
|
|
3
|
+
import * as i1$1 from '@angular/common';
|
|
4
|
+
import { CommonModule, DatePipe, DecimalPipe, NgComponentOutlet } from '@angular/common';
|
|
5
|
+
import { DynamicDialogRef, DialogService, DynamicDialogConfig, DynamicDialogModule } from 'primeng/dynamicdialog';
|
|
6
|
+
import * as i1$3 from 'primeng/dialog';
|
|
8
7
|
import { DialogModule } from 'primeng/dialog';
|
|
9
8
|
import * as i2 from 'primeng/progressbar';
|
|
10
9
|
import { ProgressBarModule } from 'primeng/progressbar';
|
|
11
10
|
import * as i1 from '@angular/forms';
|
|
12
|
-
import { FormControl, ReactiveFormsModule, FormsModule } from '@angular/forms';
|
|
11
|
+
import { FormControl, ReactiveFormsModule, FormBuilder, FormsModule } from '@angular/forms';
|
|
13
12
|
import { DCMicComponent } from '@dataclouder/ngx-mic';
|
|
14
13
|
import * as i3 from 'primeng/textarea';
|
|
15
14
|
import { TextareaModule } from 'primeng/textarea';
|
|
16
|
-
import * as
|
|
15
|
+
import * as i2$1 from 'primeng/button';
|
|
17
16
|
import { ButtonModule } from 'primeng/button';
|
|
18
|
-
import
|
|
17
|
+
import { AudioSpeed as AudioSpeed$1, TOAST_ALERTS_TOKEN, PaginationBase, DCFilterBarComponent, QuickTableComponent } from '@dataclouder/ngx-core';
|
|
19
18
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
20
|
-
import {
|
|
19
|
+
import { Subject, fromEvent, filter } from 'rxjs';
|
|
21
20
|
import { takeUntil, map } from 'rxjs/operators';
|
|
22
|
-
import
|
|
21
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
22
|
+
import * as i1$2 from 'primeng/skeleton';
|
|
23
23
|
import { SkeletonModule } from 'primeng/skeleton';
|
|
24
|
-
import * as
|
|
24
|
+
import * as i2$3 from 'primeng/checkbox';
|
|
25
25
|
import { CheckboxModule } from 'primeng/checkbox';
|
|
26
26
|
import { SliderModule } from 'primeng/slider';
|
|
27
|
-
import * as
|
|
27
|
+
import * as i2$2 from 'primeng/radiobutton';
|
|
28
28
|
import { RadioButtonModule } from 'primeng/radiobutton';
|
|
29
|
-
import * as
|
|
29
|
+
import * as i6$1 from 'primeng/rating';
|
|
30
30
|
import { RatingModule } from 'primeng/rating';
|
|
31
|
-
import * as
|
|
31
|
+
import * as i4 from 'primeng/table';
|
|
32
32
|
import { TableModule } from 'primeng/table';
|
|
33
33
|
import { BadgeModule } from 'primeng/badge';
|
|
34
|
-
import * as
|
|
34
|
+
import * as i6 from 'primeng/tooltip';
|
|
35
35
|
import { TooltipModule } from 'primeng/tooltip';
|
|
36
|
-
import * as
|
|
37
|
-
import
|
|
38
|
-
import { TOAST_ALERTS_TOKEN, AudioSpeed as AudioSpeed$1, PaginationBase, DCFilterBarComponent, QuickTableComponent } from '@dataclouder/ngx-core';
|
|
36
|
+
import * as i3$1 from 'primeng/api';
|
|
37
|
+
import { ActivatedRoute, Router } from '@angular/router';
|
|
39
38
|
import { OverlayModule } from '@angular/cdk/overlay';
|
|
40
39
|
import { PortalModule } from '@angular/cdk/portal';
|
|
41
|
-
import * as
|
|
40
|
+
import * as i4$1 from 'primeng/inputtext';
|
|
42
41
|
import { InputTextModule } from 'primeng/inputtext';
|
|
43
|
-
import * as
|
|
42
|
+
import * as i6$2 from 'primeng/togglebutton';
|
|
44
43
|
import { ToggleButtonModule } from 'primeng/togglebutton';
|
|
45
|
-
import
|
|
46
|
-
import {
|
|
47
|
-
import * as
|
|
48
|
-
import { DIALOG_DATA } from '@angular/cdk/dialog';
|
|
49
|
-
import * as i13 from 'primeng/toggleswitch';
|
|
44
|
+
import { ResolutionType, AspectType, MultiImagesStorageService, CropperComponentModal } from '@dataclouder/ngx-cloud-storage';
|
|
45
|
+
import { DialogRef, DIALOG_DATA } from '@angular/cdk/dialog';
|
|
46
|
+
import * as i8 from 'primeng/toggleswitch';
|
|
50
47
|
import { ToggleSwitchModule } from 'primeng/toggleswitch';
|
|
51
|
-
import * as
|
|
48
|
+
import * as i9 from 'primeng/select';
|
|
52
49
|
import { SelectModule } from 'primeng/select';
|
|
53
|
-
import * as
|
|
50
|
+
import * as i10 from 'primeng/popover';
|
|
54
51
|
import { PopoverModule } from 'primeng/popover';
|
|
55
|
-
import * as
|
|
52
|
+
import * as i2$4 from 'primeng/card';
|
|
56
53
|
import { CardModule } from 'primeng/card';
|
|
57
|
-
import * as
|
|
54
|
+
import * as i3$2 from 'primeng/dropdown';
|
|
58
55
|
import { DropdownModule } from 'primeng/dropdown';
|
|
59
56
|
import { ChipModule } from 'primeng/chip';
|
|
60
|
-
import * as i1$
|
|
61
|
-
import * as i3$3 from 'primeng/paginator';
|
|
57
|
+
import * as i1$4 from 'primeng/paginator';
|
|
62
58
|
import { PaginatorModule } from 'primeng/paginator';
|
|
63
|
-
import * as i2$
|
|
59
|
+
import * as i2$5 from 'primeng/speeddial';
|
|
64
60
|
import { SpeedDialModule } from 'primeng/speeddial';
|
|
65
61
|
|
|
66
62
|
const characterCardStringDataDefinition = `
|
|
@@ -87,8 +83,6 @@ class WordTimestamps {
|
|
|
87
83
|
}
|
|
88
84
|
class MessageAudio {
|
|
89
85
|
}
|
|
90
|
-
class ChatMultiMessage extends MessageAudio {
|
|
91
|
-
}
|
|
92
86
|
var ChatRole;
|
|
93
87
|
(function (ChatRole) {
|
|
94
88
|
ChatRole["System"] = "system";
|
|
@@ -512,10 +506,10 @@ class AudioService {
|
|
|
512
506
|
// const srcURL = await this.filesCacheService.getURLSrcFile(path);
|
|
513
507
|
this.playWithSrc(path);
|
|
514
508
|
}
|
|
515
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
516
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.
|
|
509
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AudioService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
510
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AudioService, providedIn: 'root' }); }
|
|
517
511
|
}
|
|
518
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
512
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AudioService, decorators: [{
|
|
519
513
|
type: Injectable,
|
|
520
514
|
args: [{
|
|
521
515
|
providedIn: 'root',
|
|
@@ -535,8 +529,8 @@ class UserDataExchangeAbstractService {
|
|
|
535
529
|
}
|
|
536
530
|
|
|
537
531
|
class DCConversationPromptBuilderService {
|
|
538
|
-
constructor(
|
|
539
|
-
this.userDataExchange =
|
|
532
|
+
constructor() {
|
|
533
|
+
this.userDataExchange = inject(USER_DATA_EXCHANGE);
|
|
540
534
|
}
|
|
541
535
|
// For chat conversation i need inital settings.
|
|
542
536
|
buildConversationSettings(agentCard, parseDict = null) {
|
|
@@ -678,27 +672,76 @@ class DCConversationPromptBuilderService {
|
|
|
678
672
|
};
|
|
679
673
|
return settings;
|
|
680
674
|
}
|
|
681
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
682
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.
|
|
675
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCConversationPromptBuilderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
676
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCConversationPromptBuilderService, providedIn: 'root' }); }
|
|
683
677
|
}
|
|
684
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
678
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCConversationPromptBuilderService, decorators: [{
|
|
685
679
|
type: Injectable,
|
|
686
680
|
args: [{
|
|
687
681
|
providedIn: 'root',
|
|
688
682
|
}]
|
|
689
|
-
}]
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
683
|
+
}] });
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* SafeJsonPipe - A pipe that safely stringifies objects with circular references
|
|
687
|
+
* This pipe handles circular references and DOM objects that can't be serialized
|
|
688
|
+
*/
|
|
689
|
+
class SafeJsonPipe {
|
|
690
|
+
transform(value) {
|
|
691
|
+
// Create a new object with only serializable properties
|
|
692
|
+
return this.stringifySafely(value);
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Safely stringify an object, handling circular references
|
|
696
|
+
*/
|
|
697
|
+
stringifySafely(obj) {
|
|
698
|
+
const seen = new WeakSet();
|
|
699
|
+
return JSON.stringify(obj, (key, value) => {
|
|
700
|
+
// Skip properties that start with underscore (often internal properties)
|
|
701
|
+
if (key.startsWith('_')) {
|
|
702
|
+
return undefined;
|
|
703
|
+
}
|
|
704
|
+
// Handle DOM elements and other non-serializable objects
|
|
705
|
+
if (value instanceof HTMLElement || value instanceof Node) {
|
|
706
|
+
return '[DOM Element]';
|
|
707
|
+
}
|
|
708
|
+
// Handle audio elements specifically
|
|
709
|
+
if (key === 'audioHtml' || key === 'audioElement') {
|
|
710
|
+
return '[Audio Element]';
|
|
711
|
+
}
|
|
712
|
+
// Handle functions
|
|
713
|
+
if (typeof value === 'function') {
|
|
714
|
+
return '[Function]';
|
|
715
|
+
}
|
|
716
|
+
// Handle circular references
|
|
717
|
+
if (typeof value === 'object' && value !== null) {
|
|
718
|
+
if (seen.has(value)) {
|
|
719
|
+
return '[Circular Reference]';
|
|
720
|
+
}
|
|
721
|
+
seen.add(value);
|
|
722
|
+
}
|
|
723
|
+
return value;
|
|
724
|
+
}, 2);
|
|
725
|
+
}
|
|
726
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: SafeJsonPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
727
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.4", ngImport: i0, type: SafeJsonPipe, isStandalone: true, name: "safeJson" }); }
|
|
728
|
+
}
|
|
729
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: SafeJsonPipe, decorators: [{
|
|
730
|
+
type: Pipe,
|
|
731
|
+
args: [{
|
|
732
|
+
name: 'safeJson',
|
|
733
|
+
standalone: true
|
|
734
|
+
}]
|
|
735
|
+
}] });
|
|
693
736
|
|
|
694
737
|
class ChatHeaderComponent {
|
|
695
|
-
constructor(
|
|
696
|
-
this.agentCardService =
|
|
697
|
-
this.isAdmin = false;
|
|
738
|
+
constructor() {
|
|
739
|
+
this.agentCardService = inject(CONVERSATION_AI_TOKEN);
|
|
740
|
+
this.isAdmin = input(false);
|
|
698
741
|
this.alternativeConversation = [];
|
|
699
|
-
this.restartConversationEvent =
|
|
700
|
-
this.showInfoEvent =
|
|
701
|
-
this.settingsClickEvent =
|
|
742
|
+
this.restartConversationEvent = output();
|
|
743
|
+
this.showInfoEvent = output();
|
|
744
|
+
this.settingsClickEvent = output();
|
|
702
745
|
}
|
|
703
746
|
restartConversation(conversation = null) {
|
|
704
747
|
this.restartConversationEvent.emit(conversation);
|
|
@@ -723,87 +766,16 @@ class ChatHeaderComponent {
|
|
|
723
766
|
this.alternativeConversation = conversationCards.rows;
|
|
724
767
|
}
|
|
725
768
|
}
|
|
726
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
727
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.
|
|
769
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
770
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: ChatHeaderComponent, isStandalone: true, selector: "dc-chat-header", inputs: { isAdmin: { classPropertyName: "isAdmin", publicName: "isAdmin", isSignal: true, isRequired: false, transformFunction: null }, alternativeConversation: { classPropertyName: "alternativeConversation", publicName: "alternativeConversation", isSignal: false, isRequired: false, transformFunction: null }, agentCard: { classPropertyName: "agentCard", publicName: "agentCard", isSignal: false, isRequired: false, transformFunction: null } }, outputs: { restartConversationEvent: "restartConversationEvent", showInfoEvent: "showInfoEvent", settingsClickEvent: "settingsClickEvent" }, ngImport: i0, template: "<div class=\"chat-header\">\n <span class=\"pointer\" (click)=\"restartConversation()\">\n @if (agentCard?.title) {\n {{ agentCard.title }}\n } @else {\n Reiniciar conversaci\u00F3n\n }\n </span>\n\n @for (conversation of alternativeConversation; track conversation._id) {\n <span class=\"pointer\" (click)=\"restartConversation(conversation)\"> {{ conversation.title }} </span>\n }\n\n <div class=\"header-controls\">\n @if (isAdmin()){\n <div class=\"admin-controls\">\n <span class=\"pointer\" (click)=\"changeConversationCard()\"> \uD83D\uDD04 </span>\n <span class=\"pointer\" (click)=\"showInfo()\"> \u26A1\uFE0F </span>\n </div>\n }\n\n <div>\n <span class=\"pointer\" (click)=\"settingsClick()\"> \u2699\uFE0F </span>\n </div>\n </div>\n</div>\n", styles: [".chat-header{display:flex;justify-content:space-between;align-items:center;width:100%}.pointer{cursor:pointer}.header-controls{font-size:large;display:flex;justify-content:space-between;gap:10px}.admin-controls{background-color:bisque;padding:2px 5px;border-radius:4px}\n"] }); }
|
|
728
771
|
}
|
|
729
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
772
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatHeaderComponent, decorators: [{
|
|
730
773
|
type: Component,
|
|
731
|
-
args: [{ selector: 'dc-chat-header', standalone: true, imports: [
|
|
732
|
-
}], ctorParameters: () => [
|
|
733
|
-
type: Inject,
|
|
734
|
-
args: [CONVERSATION_AI_TOKEN]
|
|
735
|
-
}] }], propDecorators: { isAdmin: [{
|
|
736
|
-
type: Input
|
|
737
|
-
}], alternativeConversation: [{
|
|
774
|
+
args: [{ selector: 'dc-chat-header', standalone: true, imports: [], template: "<div class=\"chat-header\">\n <span class=\"pointer\" (click)=\"restartConversation()\">\n @if (agentCard?.title) {\n {{ agentCard.title }}\n } @else {\n Reiniciar conversaci\u00F3n\n }\n </span>\n\n @for (conversation of alternativeConversation; track conversation._id) {\n <span class=\"pointer\" (click)=\"restartConversation(conversation)\"> {{ conversation.title }} </span>\n }\n\n <div class=\"header-controls\">\n @if (isAdmin()){\n <div class=\"admin-controls\">\n <span class=\"pointer\" (click)=\"changeConversationCard()\"> \uD83D\uDD04 </span>\n <span class=\"pointer\" (click)=\"showInfo()\"> \u26A1\uFE0F </span>\n </div>\n }\n\n <div>\n <span class=\"pointer\" (click)=\"settingsClick()\"> \u2699\uFE0F </span>\n </div>\n </div>\n</div>\n", styles: [".chat-header{display:flex;justify-content:space-between;align-items:center;width:100%}.pointer{cursor:pointer}.header-controls{font-size:large;display:flex;justify-content:space-between;gap:10px}.admin-controls{background-color:bisque;padding:2px 5px;border-radius:4px}\n"] }]
|
|
775
|
+
}], ctorParameters: () => [], propDecorators: { alternativeConversation: [{
|
|
738
776
|
type: Input
|
|
739
777
|
}], agentCard: [{
|
|
740
778
|
type: Input
|
|
741
|
-
}], restartConversationEvent: [{
|
|
742
|
-
type: Output
|
|
743
|
-
}], showInfoEvent: [{
|
|
744
|
-
type: Output
|
|
745
|
-
}], settingsClickEvent: [{
|
|
746
|
-
type: Output
|
|
747
|
-
}] } });
|
|
748
|
-
|
|
749
|
-
class ChatFooterComponent {
|
|
750
|
-
constructor() {
|
|
751
|
-
this.isAIThinking = false;
|
|
752
|
-
this.score = 0;
|
|
753
|
-
this.micSettings = { useWhisper: true, lang: 'en' };
|
|
754
|
-
this.sendMessage = new EventEmitter();
|
|
755
|
-
this.textInputChanged = new EventEmitter();
|
|
756
|
-
this.micFinishedEvent = new EventEmitter();
|
|
757
|
-
this.chatInputControl = new FormControl();
|
|
758
|
-
}
|
|
759
|
-
/**
|
|
760
|
-
* Sets the input text in the textarea
|
|
761
|
-
* @param text The text to set
|
|
762
|
-
*/
|
|
763
|
-
setInputText(text) {
|
|
764
|
-
this.chatInputControl.setValue(text);
|
|
765
|
-
this.textInputChanged.emit(text);
|
|
766
|
-
}
|
|
767
|
-
/**
|
|
768
|
-
* Handles the mic finished event
|
|
769
|
-
* @param eventBlob The blob event from the mic component
|
|
770
|
-
*/
|
|
771
|
-
micFinished(eventBlob) {
|
|
772
|
-
this.micFinishedEvent.emit(eventBlob);
|
|
773
|
-
}
|
|
774
|
-
/**
|
|
775
|
-
* Sends the user message
|
|
776
|
-
*/
|
|
777
|
-
sendUserMessage() {
|
|
778
|
-
if (this.isAIThinking || !this.chatInputControl.value) {
|
|
779
|
-
return;
|
|
780
|
-
}
|
|
781
|
-
const text = this.chatInputControl.value;
|
|
782
|
-
const message = {
|
|
783
|
-
content: text,
|
|
784
|
-
role: ChatRole.User,
|
|
785
|
-
};
|
|
786
|
-
this.sendMessage.emit(message);
|
|
787
|
-
this.chatInputControl.setValue('');
|
|
788
|
-
}
|
|
789
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ChatFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
790
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", type: ChatFooterComponent, isStandalone: true, selector: "dc-chat-footer", inputs: { isAIThinking: "isAIThinking", score: "score", micSettings: "micSettings" }, outputs: { sendMessage: "sendMessage", textInputChanged: "textInputChanged", micFinishedEvent: "micFinishedEvent" }, ngImport: i0, template: "<div class=\"progress-input\">\n <div class=\"input-container\">\n <dc-mic\n style=\"display: flex; align-items: center\"\n (onInterpretedText)=\"setInputText($event)\"\n (onFinished)=\"micFinished($event)\"\n [micSettings]=\"micSettings\"></dc-mic>\n\n <textarea pTextarea [formControl]=\"chatInputControl\" (keyup.enter)=\"sendUserMessage()\" rows=\"1\"></textarea>\n\n <p-button (click)=\"sendUserMessage()\" [disabled]=\"isAIThinking || !chatInputControl.value\" label=\"Enviar\" [rounded]=\"true\" />\n </div>\n\n <div>\n <p-progressbar showValue=\"false\" [value]=\"score\" [style]=\"{ height: '6px' }\" />\n </div>\n</div>\n", styles: [".progress-input{padding:10px;background-color:#f5f5f545;border-top:1px solid #b1a8a8}.progress-input .input-container{display:flex;align-items:center;margin-bottom:5px}.progress-input .input-container textarea{flex:1;resize:none;margin:0 10px}.progress-input .input-container .send-button{background-color:#007bff;color:#fff;border:none;border-radius:4px;padding:8px 15px;cursor:pointer}.progress-input .input-container .send-button:disabled{background-color:#ccc;cursor:not-allowed}.progress-input .input-container .send-button:hover:not(:disabled){background-color:#0069d9}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: ProgressBarModule }, { kind: "component", type: i2.ProgressBar, selector: "p-progressBar, p-progressbar, p-progress-bar", inputs: ["value", "showValue", "styleClass", "valueStyleClass", "style", "unit", "mode", "color"] }, { kind: "component", type: DCMicComponent, selector: "dc-mic", inputs: ["isDone", "useWhisper", "targetOrBase", "micSettings"], outputs: ["onInterpretedText", "onFinishedRecognition", "onFinished"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i3.Textarea, selector: "[pTextarea]", inputs: ["autoResize", "variant", "fluid", "pSize"], outputs: ["onResize"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i7.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }] }); }
|
|
791
|
-
}
|
|
792
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ChatFooterComponent, decorators: [{
|
|
793
|
-
type: Component,
|
|
794
|
-
args: [{ selector: 'dc-chat-footer', standalone: true, imports: [CommonModule, ReactiveFormsModule, ProgressBarModule, DCMicComponent, TextareaModule, ButtonModule], template: "<div class=\"progress-input\">\n <div class=\"input-container\">\n <dc-mic\n style=\"display: flex; align-items: center\"\n (onInterpretedText)=\"setInputText($event)\"\n (onFinished)=\"micFinished($event)\"\n [micSettings]=\"micSettings\"></dc-mic>\n\n <textarea pTextarea [formControl]=\"chatInputControl\" (keyup.enter)=\"sendUserMessage()\" rows=\"1\"></textarea>\n\n <p-button (click)=\"sendUserMessage()\" [disabled]=\"isAIThinking || !chatInputControl.value\" label=\"Enviar\" [rounded]=\"true\" />\n </div>\n\n <div>\n <p-progressbar showValue=\"false\" [value]=\"score\" [style]=\"{ height: '6px' }\" />\n </div>\n</div>\n", styles: [".progress-input{padding:10px;background-color:#f5f5f545;border-top:1px solid #b1a8a8}.progress-input .input-container{display:flex;align-items:center;margin-bottom:5px}.progress-input .input-container textarea{flex:1;resize:none;margin:0 10px}.progress-input .input-container .send-button{background-color:#007bff;color:#fff;border:none;border-radius:4px;padding:8px 15px;cursor:pointer}.progress-input .input-container .send-button:disabled{background-color:#ccc;cursor:not-allowed}.progress-input .input-container .send-button:hover:not(:disabled){background-color:#0069d9}\n"] }]
|
|
795
|
-
}], propDecorators: { isAIThinking: [{
|
|
796
|
-
type: Input
|
|
797
|
-
}], score: [{
|
|
798
|
-
type: Input
|
|
799
|
-
}], micSettings: [{
|
|
800
|
-
type: Input
|
|
801
|
-
}], sendMessage: [{
|
|
802
|
-
type: Output
|
|
803
|
-
}], textInputChanged: [{
|
|
804
|
-
type: Output
|
|
805
|
-
}], micFinishedEvent: [{
|
|
806
|
-
type: Output
|
|
807
779
|
}] } });
|
|
808
780
|
|
|
809
781
|
function markdownToHtml(markdown) {
|
|
@@ -1007,451 +979,104 @@ function removeAllEmojis(text) {
|
|
|
1007
979
|
return text.replace(/[\p{Emoji_Presentation}\p{Extended_Pictographic}\u{1F3FB}-\u{1F3FF}\u{E0020}-\u{E007F}\u{FE00}-\u{FE0F}\u{1F900}-\u{1F9FF}\u{1F1E6}-\u{1F1FF}]/gu, '');
|
|
1008
980
|
}
|
|
1009
981
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
viewBox="0 0 24 24"
|
|
1032
|
-
fill="none"
|
|
1033
|
-
stroke="#263042"
|
|
1034
|
-
stroke-width="2"
|
|
1035
|
-
stroke-linecap="round"
|
|
1036
|
-
stroke-linejoin="round">
|
|
1037
|
-
<line x1="12" y1="2" x2="12" y2="6"></line>
|
|
1038
|
-
<line x1="12" y1="18" x2="12" y2="22"></line>
|
|
1039
|
-
<line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line>
|
|
1040
|
-
<line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line>
|
|
1041
|
-
<line x1="2" y1="12" x2="6" y2="12"></line>
|
|
1042
|
-
<line x1="18" y1="12" x2="22" y2="12"></line>
|
|
1043
|
-
<line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line>
|
|
1044
|
-
<line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line>
|
|
1045
|
-
</svg>`,
|
|
1046
|
-
};
|
|
1047
|
-
|
|
1048
|
-
class IconsComponent {
|
|
1049
|
-
constructor(sanitizer) {
|
|
1050
|
-
this.sanitizer = sanitizer;
|
|
1051
|
-
this.size = 14;
|
|
1052
|
-
this.color = 'currentColor';
|
|
1053
|
-
}
|
|
1054
|
-
ngOnChanges(changes) {
|
|
1055
|
-
if (changes['name']) {
|
|
1056
|
-
const svg = ICONS[this.name] || '';
|
|
1057
|
-
this.sanitizedIcon = this.sanitizer.bypassSecurityTrustHtml(svg);
|
|
982
|
+
class MessageProcessingService {
|
|
983
|
+
constructor() { }
|
|
984
|
+
// Process message for display
|
|
985
|
+
processMessage(message, conversationSettings) {
|
|
986
|
+
const defaultVoice = this.getVoice(conversationSettings.voice);
|
|
987
|
+
let processedMessage;
|
|
988
|
+
processedMessage = {
|
|
989
|
+
content: message.content,
|
|
990
|
+
role: message.role,
|
|
991
|
+
voice: defaultVoice,
|
|
992
|
+
};
|
|
993
|
+
// Process based on text engine
|
|
994
|
+
if (conversationSettings.textEngine === TextEngines.MarkdownMultiMessages) {
|
|
995
|
+
this.processMultiMessages(processedMessage, conversationSettings);
|
|
996
|
+
}
|
|
997
|
+
else if (conversationSettings.textEngine === TextEngines.MarkdownSSML) {
|
|
998
|
+
if (!conversationSettings.secondaryVoice) {
|
|
999
|
+
throw new Error('Secondary voice is required for SSML');
|
|
1000
|
+
}
|
|
1001
|
+
const content = this.subsItalicsByTag(processedMessage.content, conversationSettings.secondaryVoice);
|
|
1002
|
+
processedMessage.ssml = '<speak>' + content + '</speak>';
|
|
1058
1003
|
}
|
|
1004
|
+
console.log(processedMessage);
|
|
1005
|
+
return { ...message, ...processedMessage };
|
|
1059
1006
|
}
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
[style.justifyContent]="'center'"
|
|
1080
|
-
[style.width.px]="size"
|
|
1081
|
-
[style.height.px]="size"
|
|
1082
|
-
[style.color]="color"></span>
|
|
1083
|
-
`, standalone: true, styles: [":host{display:inline-flex;align-items:center;line-height:0}span{line-height:0}:host ::ng-deep svg{width:100%;height:100%}\n"] }]
|
|
1084
|
-
}], ctorParameters: () => [{ type: i1$1.DomSanitizer }], propDecorators: { name: [{
|
|
1085
|
-
type: Input
|
|
1086
|
-
}], size: [{
|
|
1087
|
-
type: Input
|
|
1088
|
-
}], color: [{
|
|
1089
|
-
type: Input
|
|
1090
|
-
}] } });
|
|
1091
|
-
|
|
1092
|
-
class AudioTextSyncService {
|
|
1093
|
-
constructor() {
|
|
1094
|
-
// Maps to store message-specific signals and observables
|
|
1095
|
-
this.highlightedWordsSignalMap = new Map();
|
|
1096
|
-
this.highlightedWords$Map = new Map();
|
|
1097
|
-
// Maps for cleanup and active audio elements
|
|
1098
|
-
this.cleanup$Map = new Map();
|
|
1099
|
-
this.activeAudioMap = new Map();
|
|
1100
|
-
this.destroyRef = inject(DestroyRef);
|
|
1101
|
-
// Ensure cleanup when service is destroyed
|
|
1102
|
-
this.destroyRef.onDestroy(() => {
|
|
1103
|
-
this.stopAllSyncs();
|
|
1007
|
+
// Process multi-messages from markdown
|
|
1008
|
+
processMultiMessages(message, settings) {
|
|
1009
|
+
// Convert markdown to HTML segments
|
|
1010
|
+
const mdToHtml = convertToHTML(message.content);
|
|
1011
|
+
// Map segments to multi-messages
|
|
1012
|
+
message.multiMessages = mdToHtml.map((val) => {
|
|
1013
|
+
// Get the narrator voice from conversation settings
|
|
1014
|
+
const narratorVoice = settings.secondaryVoice || 'en-US-News-L';
|
|
1015
|
+
// Determine if this is italicized text (narrator)
|
|
1016
|
+
const isItalics = val.tag === 'em';
|
|
1017
|
+
const voice = isItalics ? narratorVoice : message.voice;
|
|
1018
|
+
return {
|
|
1019
|
+
voice,
|
|
1020
|
+
content: val.content,
|
|
1021
|
+
audioUrl: null,
|
|
1022
|
+
audioPromise: null,
|
|
1023
|
+
text: val.text,
|
|
1024
|
+
tag: val.tag,
|
|
1025
|
+
};
|
|
1104
1026
|
});
|
|
1105
1027
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
* @param messageId Unique identifier for the message
|
|
1111
|
-
*/
|
|
1112
|
-
syncAudioWithText(audioElement, transcriptionTimestamps, messageId) {
|
|
1113
|
-
// Stop any existing sync for this message
|
|
1114
|
-
this.stopSync(messageId);
|
|
1115
|
-
// Create new signal and subject for this message if they don't exist
|
|
1116
|
-
if (!this.highlightedWordsSignalMap.has(messageId)) {
|
|
1117
|
-
this.highlightedWordsSignalMap.set(messageId, signal([]));
|
|
1118
|
-
}
|
|
1119
|
-
if (!this.highlightedWords$Map.has(messageId)) {
|
|
1120
|
-
this.highlightedWords$Map.set(messageId, new BehaviorSubject([]));
|
|
1121
|
-
}
|
|
1122
|
-
// Create cleanup subject
|
|
1123
|
-
const cleanup$ = new Subject();
|
|
1124
|
-
this.cleanup$Map.set(messageId, cleanup$);
|
|
1125
|
-
// Store the active audio element
|
|
1126
|
-
this.activeAudioMap.set(messageId, audioElement);
|
|
1127
|
-
// Get the signal and subject for this message
|
|
1128
|
-
const messageSignal = this.highlightedWordsSignalMap.get(messageId);
|
|
1129
|
-
const messageSubject = this.highlightedWords$Map.get(messageId);
|
|
1130
|
-
// Initialize the highlighted words state
|
|
1131
|
-
const initialWords = transcriptionTimestamps.map((word, index) => ({
|
|
1132
|
-
word: word.word,
|
|
1133
|
-
index,
|
|
1134
|
-
isHighlighted: false,
|
|
1135
|
-
}));
|
|
1136
|
-
// Update both signal and observable
|
|
1137
|
-
messageSignal.set(initialWords);
|
|
1138
|
-
messageSubject.next(initialWords);
|
|
1139
|
-
// Listen to timeupdate events
|
|
1140
|
-
fromEvent(audioElement, 'timeupdate')
|
|
1141
|
-
.pipe(takeUntilDestroyed(this.destroyRef), takeUntil(cleanup$), map(() => audioElement.currentTime))
|
|
1142
|
-
.subscribe((currentTime) => {
|
|
1143
|
-
const updatedWords = transcriptionTimestamps.map((word, index) => {
|
|
1144
|
-
const isHighlighted = currentTime >= word.start - 0.15 && currentTime < word.end + 0.15;
|
|
1145
|
-
return {
|
|
1146
|
-
word: word.word,
|
|
1147
|
-
index,
|
|
1148
|
-
isHighlighted,
|
|
1149
|
-
};
|
|
1150
|
-
});
|
|
1151
|
-
// Update both signal and observable for this message
|
|
1152
|
-
messageSignal.set(updatedWords);
|
|
1153
|
-
messageSubject.next(updatedWords);
|
|
1154
|
-
});
|
|
1155
|
-
// Listen to ended event for cleanup
|
|
1156
|
-
fromEvent(audioElement, 'ended')
|
|
1157
|
-
.pipe(takeUntilDestroyed(this.destroyRef), takeUntil(cleanup$))
|
|
1158
|
-
.subscribe(() => {
|
|
1159
|
-
// Reset highlighting when audio ends
|
|
1160
|
-
const resetWords = initialWords.map((word) => ({
|
|
1161
|
-
...word,
|
|
1162
|
-
isHighlighted: false,
|
|
1163
|
-
}));
|
|
1164
|
-
messageSignal.set(resetWords);
|
|
1165
|
-
messageSubject.next(resetWords);
|
|
1166
|
-
});
|
|
1028
|
+
// Replace italics with voice tags for SSML
|
|
1029
|
+
subsItalicsByTag(text, voiceId = 'it-IT-Neural2-A', tagName = 'voice') {
|
|
1030
|
+
const regex = /\*(.*?)\*/g;
|
|
1031
|
+
return text.replace(regex, (match, p1) => `<${tagName} name="${voiceId}">${p1}</${tagName}>`);
|
|
1167
1032
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
if (this.activeAudioMap.has(messageId)) {
|
|
1174
|
-
// Get the cleanup subject for this message
|
|
1175
|
-
const cleanup$ = this.cleanup$Map.get(messageId);
|
|
1176
|
-
if (cleanup$) {
|
|
1177
|
-
cleanup$.next();
|
|
1178
|
-
cleanup$.complete();
|
|
1179
|
-
this.cleanup$Map.delete(messageId);
|
|
1180
|
-
}
|
|
1181
|
-
this.activeAudioMap.delete(messageId);
|
|
1182
|
-
// Reset state for this message
|
|
1183
|
-
const messageSignal = this.highlightedWordsSignalMap.get(messageId);
|
|
1184
|
-
const messageSubject = this.highlightedWords$Map.get(messageId);
|
|
1185
|
-
if (messageSignal) {
|
|
1186
|
-
messageSignal.set([]);
|
|
1187
|
-
}
|
|
1188
|
-
if (messageSubject) {
|
|
1189
|
-
messageSubject.next([]);
|
|
1190
|
-
}
|
|
1033
|
+
// Get appropriate voice based on settings
|
|
1034
|
+
getVoice(voice_value, targetLang = 'en') {
|
|
1035
|
+
if ([null, '', 'random'].includes(voice_value)) {
|
|
1036
|
+
const voiceCodes = VoiceTTSOptions.map((val) => val.id);
|
|
1037
|
+
return voiceCodes[Math.floor(Math.random() * voiceCodes.length)];
|
|
1191
1038
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
*/
|
|
1196
|
-
stopAllSyncs() {
|
|
1197
|
-
// Get all message IDs and stop each sync
|
|
1198
|
-
for (const messageId of this.activeAudioMap.keys()) {
|
|
1199
|
-
this.stopSync(messageId);
|
|
1039
|
+
if (voice_value === 'randomMan') {
|
|
1040
|
+
const voiceCodes = VoiceTTSOptions.filter((voice) => voice.gender === 'male' && voice.lang.includes(targetLang)).map((val) => val.id);
|
|
1041
|
+
return voiceCodes[Math.floor(Math.random() * voiceCodes.length)];
|
|
1200
1042
|
}
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
this.cleanup$Map.clear();
|
|
1205
|
-
this.activeAudioMap.clear();
|
|
1206
|
-
}
|
|
1207
|
-
/**
|
|
1208
|
-
* Returns the highlighted words signal for a specific message
|
|
1209
|
-
* @param messageId The ID of the message
|
|
1210
|
-
*/
|
|
1211
|
-
getHighlightedWordsSignal(messageId) {
|
|
1212
|
-
// Create a new signal for this message if it doesn't exist
|
|
1213
|
-
if (!this.highlightedWordsSignalMap.has(messageId)) {
|
|
1214
|
-
this.highlightedWordsSignalMap.set(messageId, signal([]));
|
|
1043
|
+
else if (voice_value === 'randomWoman') {
|
|
1044
|
+
const voiceCodes = VoiceTTSOptions.filter((voice) => voice.gender === 'female' && voice.lang.includes(targetLang)).map((val) => val.id);
|
|
1045
|
+
return voiceCodes[Math.floor(Math.random() * voiceCodes.length)];
|
|
1215
1046
|
}
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
this.highlightedWords$Map.set(messageId, new BehaviorSubject([]));
|
|
1047
|
+
else {
|
|
1048
|
+
const voice = VoiceTTSOptions.find((voice) => voice.id === voice_value);
|
|
1049
|
+
if (voice) {
|
|
1050
|
+
return voice.id;
|
|
1051
|
+
}
|
|
1052
|
+
else {
|
|
1053
|
+
console.error('Voice not found getting something random', voice_value);
|
|
1054
|
+
return VoiceTTSOptions.find((voice) => voice.lang.includes(targetLang))?.id || '';
|
|
1055
|
+
}
|
|
1226
1056
|
}
|
|
1227
|
-
return this.highlightedWords$Map.get(messageId).asObservable();
|
|
1228
|
-
}
|
|
1229
|
-
/**
|
|
1230
|
-
* Checks if a word at a specific index is currently highlighted for a specific message
|
|
1231
|
-
* @param messageId The ID of the message
|
|
1232
|
-
* @param index The index of the word to check
|
|
1233
|
-
*/
|
|
1234
|
-
isWordHighlighted(messageId, index) {
|
|
1235
|
-
const messageSignal = this.getHighlightedWordsSignal(messageId);
|
|
1236
|
-
return messageSignal().some((word) => word.index === index && word.isHighlighted);
|
|
1237
|
-
}
|
|
1238
|
-
/**
|
|
1239
|
-
* Returns an observable that emits true when a word at a specific index is highlighted for a specific message
|
|
1240
|
-
* @param messageId The ID of the message
|
|
1241
|
-
* @param index The index of the word to observe
|
|
1242
|
-
*/
|
|
1243
|
-
isWordHighlighted$(messageId, index) {
|
|
1244
|
-
return this.getHighlightedWords$(messageId).pipe(map((words) => words.some((word) => word.index === index && word.isHighlighted)));
|
|
1245
1057
|
}
|
|
1246
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
1247
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.
|
|
1058
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MessageProcessingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1059
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MessageProcessingService, providedIn: 'root' }); }
|
|
1248
1060
|
}
|
|
1249
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
1061
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MessageProcessingService, decorators: [{
|
|
1250
1062
|
type: Injectable,
|
|
1251
1063
|
args: [{
|
|
1252
1064
|
providedIn: 'root',
|
|
1253
1065
|
}]
|
|
1254
1066
|
}], ctorParameters: () => [] });
|
|
1255
1067
|
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
effect(() => {
|
|
1269
|
-
// Just accessing the signal in an effect will re-run the effect when the signal changes
|
|
1270
|
-
this.highlightedWords();
|
|
1271
|
-
// Mark for check to ensure the component updates
|
|
1272
|
-
this.cdr.markForCheck();
|
|
1273
|
-
});
|
|
1274
|
-
}
|
|
1275
|
-
/**
|
|
1276
|
-
* Check if the message has transcription timestamps
|
|
1277
|
-
*/
|
|
1278
|
-
get hasTranscription() {
|
|
1279
|
-
const hasTranscription = !!this.message.transcriptionTimestamps;
|
|
1280
|
-
console.log('hasTranscription check:', {
|
|
1281
|
-
hasTranscription,
|
|
1282
|
-
transcriptionTimestamps: this.message.transcriptionTimestamps,
|
|
1283
|
-
messageId: this.messageId,
|
|
1284
|
-
});
|
|
1285
|
-
return hasTranscription;
|
|
1286
|
-
}
|
|
1287
|
-
/**
|
|
1288
|
-
* Get the message text content
|
|
1289
|
-
*/
|
|
1290
|
-
get messageText() {
|
|
1291
|
-
return this.message.content || this.message.text;
|
|
1292
|
-
}
|
|
1293
|
-
/**
|
|
1294
|
-
* Track function for ngFor to improve performance
|
|
1295
|
-
* @param index The current item's index
|
|
1296
|
-
* @param item The current item
|
|
1297
|
-
*/
|
|
1298
|
-
trackByIndex(index, item) {
|
|
1299
|
-
return index;
|
|
1300
|
-
}
|
|
1301
|
-
ngOnInit() {
|
|
1302
|
-
// Generate a unique ID for this message instance
|
|
1303
|
-
this.messageId = this.generateMessageId();
|
|
1304
|
-
// Connect to the service's signal for this message
|
|
1305
|
-
// We need to use a different approach since we can't directly assign a read-only signal
|
|
1306
|
-
const messageSignal = this.audioTextSyncService.getHighlightedWordsSignal(this.messageId);
|
|
1307
|
-
// Create an effect to update our local signal when the service signal changes
|
|
1308
|
-
// Use runInInjectionContext to properly create the effect in ngOnInit
|
|
1309
|
-
runInInjectionContext(this.injector, () => {
|
|
1310
|
-
effect(() => {
|
|
1311
|
-
const words = messageSignal();
|
|
1312
|
-
console.log('Effect updating highlightedWords:', {
|
|
1313
|
-
words,
|
|
1314
|
-
messageId: this.messageId,
|
|
1315
|
-
length: words.length,
|
|
1316
|
-
});
|
|
1317
|
-
this.highlightedWords.set(words);
|
|
1318
|
-
});
|
|
1319
|
-
});
|
|
1320
|
-
// If the message has transcription but no highlighted words are set,
|
|
1321
|
-
// initialize them with the message's transcription
|
|
1322
|
-
if (this.hasTranscription && this.highlightedWords().length === 0) {
|
|
1323
|
-
const transcriptionTimestamps = this.message.transcriptionTimestamps || [];
|
|
1324
|
-
const initialWords = transcriptionTimestamps.map((word, index) => ({
|
|
1325
|
-
word: word.word,
|
|
1326
|
-
index,
|
|
1327
|
-
isHighlighted: word.highlighted || false,
|
|
1328
|
-
}));
|
|
1329
|
-
this.highlightedWords.set(initialWords);
|
|
1330
|
-
// Also initialize the service signal for this message
|
|
1331
|
-
// This ensures the words are available even before playing
|
|
1332
|
-
this.audioTextSyncService.syncAudioWithText(this.message['audioHtml'] || new Audio(), this.message.transcriptionTimestamps || [], this.messageId);
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
/**
|
|
1336
|
-
* Generate a unique ID for this message instance
|
|
1337
|
-
* Uses message content/text and a timestamp to ensure uniqueness
|
|
1338
|
-
*/
|
|
1339
|
-
generateMessageId() {
|
|
1340
|
-
const messageContent = this.message.content || this.message.text || '';
|
|
1341
|
-
const timestamp = new Date().getTime();
|
|
1342
|
-
return `msg_${messageContent.substring(0, 20).replace(/\s+/g, '_')}_${timestamp}`;
|
|
1343
|
-
}
|
|
1344
|
-
/**
|
|
1345
|
-
* Emit event to play the audio message
|
|
1346
|
-
*/
|
|
1347
|
-
onPlayMessage() {
|
|
1348
|
-
this.playAudio.emit(this.message);
|
|
1349
|
-
}
|
|
1350
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: AudioTextSyncComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: AudioTextSyncService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1351
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", type: AudioTextSyncComponent, isStandalone: true, selector: "dc-audio-text-sync", inputs: { message: "message", isLoading: "isLoading" }, outputs: { playAudio: "playAudio" }, ngImport: i0, template: "<div style=\"display: flex\">\n <!-- Icon for play/loading status -->\n <i (click)=\"onPlayMessage()\">\n <dc-icon *ngIf=\"isLoading\" class=\"spin-animation\" name=\"loading\"></dc-icon>\n <dc-icon *ngIf=\"!isLoading\" name=\"play\"></dc-icon>\n </i>\n\n <!-- Display transcription with highlighting -->\n <div>\n @if (hasTranscription) { @for (wordState of highlightedWords(); track trackByIndex($index, wordState)) {\n <span [class.highlight]=\"wordState.isHighlighted\">{{ wordState.word }} </span>\n\n } }@else {\n <!-- Display regular text content when no transcription is available -->\n <span [innerHtml]=\"messageText\"></span>\n }\n </div>\n</div>\n", styles: [".highlight{background-color:#ff0;border-radius:3px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IconsComponent, selector: "dc-icon", inputs: ["name", "size", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1352
|
-
}
|
|
1353
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: AudioTextSyncComponent, decorators: [{
|
|
1354
|
-
type: Component,
|
|
1355
|
-
args: [{ selector: 'dc-audio-text-sync', standalone: true, imports: [CommonModule, IconsComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div style=\"display: flex\">\n <!-- Icon for play/loading status -->\n <i (click)=\"onPlayMessage()\">\n <dc-icon *ngIf=\"isLoading\" class=\"spin-animation\" name=\"loading\"></dc-icon>\n <dc-icon *ngIf=\"!isLoading\" name=\"play\"></dc-icon>\n </i>\n\n <!-- Display transcription with highlighting -->\n <div>\n @if (hasTranscription) { @for (wordState of highlightedWords(); track trackByIndex($index, wordState)) {\n <span [class.highlight]=\"wordState.isHighlighted\">{{ wordState.word }} </span>\n\n } }@else {\n <!-- Display regular text content when no transcription is available -->\n <span [innerHtml]=\"messageText\"></span>\n }\n </div>\n</div>\n", styles: [".highlight{background-color:#ff0;border-radius:3px}\n"] }]
|
|
1356
|
-
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: AudioTextSyncService }], propDecorators: { message: [{
|
|
1357
|
-
type: Input
|
|
1358
|
-
}], isLoading: [{
|
|
1359
|
-
type: Input
|
|
1360
|
-
}], playAudio: [{
|
|
1361
|
-
type: Output
|
|
1362
|
-
}] } });
|
|
1363
|
-
|
|
1364
|
-
class MessageContentComponent {
|
|
1365
|
-
constructor() {
|
|
1366
|
-
this.isLoading = false;
|
|
1367
|
-
this.playAudio = new EventEmitter();
|
|
1368
|
-
}
|
|
1369
|
-
get hasTranscription() {
|
|
1370
|
-
return !!this.message.transcriptionTimestamps;
|
|
1371
|
-
}
|
|
1372
|
-
get messageText() {
|
|
1373
|
-
return this.message.content || this.message.text;
|
|
1374
|
-
}
|
|
1375
|
-
get messageTag() {
|
|
1376
|
-
return this.message.tag || null;
|
|
1377
|
-
}
|
|
1378
|
-
onPlayMessage() {
|
|
1379
|
-
this.playAudio.emit(this.message);
|
|
1380
|
-
}
|
|
1381
|
-
/**
|
|
1382
|
-
* Track function for ngFor to improve performance
|
|
1383
|
-
* @param index The current item's index
|
|
1384
|
-
* @param item The current item
|
|
1385
|
-
*/
|
|
1386
|
-
trackByIndex(index, item) {
|
|
1387
|
-
return index;
|
|
1388
|
-
}
|
|
1389
|
-
ngOnInit() {
|
|
1390
|
-
console.log(this.message);
|
|
1391
|
-
}
|
|
1392
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: MessageContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1393
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", type: MessageContentComponent, isStandalone: true, selector: "dc-message-content", inputs: { message: "message", isLoading: "isLoading" }, outputs: { playAudio: "playAudio" }, ngImport: i0, template: "<!-- Message with transcription -->\n@if (hasTranscription) {\n<dc-audio-text-sync [message]=\"message\" [isLoading]=\"isLoading\" (playAudio)=\"playAudio.emit($event)\"></dc-audio-text-sync>\n}@else {\n<!-- Message without transcription -->\n<div style=\"display: flex\">\n <!-- Icon for play/loading status -->\n <i (click)=\"onPlayMessage()\">\n <dc-icon *ngIf=\"isLoading\" class=\"spin-animation\" name=\"loading\"></dc-icon>\n <dc-icon *ngIf=\"!isLoading\" name=\"play\"></dc-icon>\n </i>\n <span style=\"margin-left: 2px\" [ngClass]=\"messageTag\" [innerHtml]=\"message.text || message.content\"></span>\n</div>\n}\n", styles: [".highlight{background-color:#ffff004d}i{cursor:pointer;display:inline-flex;align-items:center;margin-right:4px}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IconsComponent, selector: "dc-icon", inputs: ["name", "size", "color"] }, { kind: "component", type: AudioTextSyncComponent, selector: "dc-audio-text-sync", inputs: ["message", "isLoading"], outputs: ["playAudio"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1394
|
-
}
|
|
1395
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: MessageContentComponent, decorators: [{
|
|
1396
|
-
type: Component,
|
|
1397
|
-
args: [{ selector: 'dc-message-content', standalone: true, imports: [CommonModule, IconsComponent, AudioTextSyncComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Message with transcription -->\n@if (hasTranscription) {\n<dc-audio-text-sync [message]=\"message\" [isLoading]=\"isLoading\" (playAudio)=\"playAudio.emit($event)\"></dc-audio-text-sync>\n}@else {\n<!-- Message without transcription -->\n<div style=\"display: flex\">\n <!-- Icon for play/loading status -->\n <i (click)=\"onPlayMessage()\">\n <dc-icon *ngIf=\"isLoading\" class=\"spin-animation\" name=\"loading\"></dc-icon>\n <dc-icon *ngIf=\"!isLoading\" name=\"play\"></dc-icon>\n </i>\n <span style=\"margin-left: 2px\" [ngClass]=\"messageTag\" [innerHtml]=\"message.text || message.content\"></span>\n</div>\n}\n", styles: [".highlight{background-color:#ffff004d}i{cursor:pointer;display:inline-flex;align-items:center;margin-right:4px}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
|
|
1398
|
-
}], ctorParameters: () => [], propDecorators: { message: [{
|
|
1399
|
-
type: Input
|
|
1400
|
-
}], isLoading: [{
|
|
1401
|
-
type: Input
|
|
1402
|
-
}], playAudio: [{
|
|
1403
|
-
type: Output
|
|
1404
|
-
}] } });
|
|
1405
|
-
|
|
1406
|
-
class MultiMessageContentComponent {
|
|
1407
|
-
constructor() {
|
|
1408
|
-
this.isLoading = false;
|
|
1409
|
-
this.playAudio = new EventEmitter();
|
|
1410
|
-
}
|
|
1411
|
-
/**
|
|
1412
|
-
* Checks if a message has transcription timestamps
|
|
1413
|
-
* @param message The message to check
|
|
1414
|
-
*/
|
|
1415
|
-
hasTranscription(message) {
|
|
1416
|
-
// debugger;
|
|
1417
|
-
return !!message.transcriptionTimestamps;
|
|
1418
|
-
}
|
|
1419
|
-
/**
|
|
1420
|
-
* Emits the playAudio event with the selected message
|
|
1421
|
-
* @param message The message to play
|
|
1422
|
-
*/
|
|
1423
|
-
onPlayMessage(message) {
|
|
1424
|
-
this.playAudio.emit(message);
|
|
1425
|
-
}
|
|
1426
|
-
ngOnInit() {
|
|
1427
|
-
console.log(this.messages);
|
|
1428
|
-
}
|
|
1429
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: MultiMessageContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1430
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", type: MultiMessageContentComponent, isStandalone: true, selector: "dc-multi-message-content", inputs: { messages: "messages", isLoading: "isLoading" }, outputs: { playAudio: "playAudio" }, ngImport: i0, template: "@for (message of messages; track message) {\n<!-- Message with transcription -->\n@if (hasTranscription(message)) {\n<div style=\"display: flex\">\n <ng-container *ngTemplateOutlet=\"iconTemplate; context: { isLoading: isLoading, message: message }\"></ng-container>\n <dc-audio-text-sync [message]=\"message\"></dc-audio-text-sync>\n</div>\n}@else {\n<!-- Message without transcription -->\n\n<div style=\"display: flex\">\n <ng-container *ngTemplateOutlet=\"iconTemplate; context: { isLoading: isLoading, message: message }\"></ng-container>\n <span style=\"margin-left: 2px\" [ngClass]=\"message.tag\" [innerHtml]=\"message.text || message.content\"></span>\n</div>\n} }\n\n<!-- Icon template for play/loading status -->\n<ng-template #iconTemplate let-isLoading=\"isLoading\" let-message=\"message\">\n <i (click)=\"onPlayMessage(message)\">\n <dc-icon *ngIf=\"isLoading\" class=\"spin-animation\" name=\"loading\"></dc-icon>\n <dc-icon *ngIf=\"!isLoading\" name=\"play\"></dc-icon>\n </i>\n</ng-template>\n", styles: [".highlight{background-color:#ffff004d}i{cursor:pointer;display:inline-flex;align-items:center;margin-right:4px}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconsComponent, selector: "dc-icon", inputs: ["name", "size", "color"] }, { kind: "component", type: AudioTextSyncComponent, selector: "dc-audio-text-sync", inputs: ["message", "isLoading"], outputs: ["playAudio"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1431
|
-
}
|
|
1432
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: MultiMessageContentComponent, decorators: [{
|
|
1433
|
-
type: Component,
|
|
1434
|
-
args: [{ selector: 'dc-multi-message-content', standalone: true, imports: [CommonModule, IconsComponent, AudioTextSyncComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@for (message of messages; track message) {\n<!-- Message with transcription -->\n@if (hasTranscription(message)) {\n<div style=\"display: flex\">\n <ng-container *ngTemplateOutlet=\"iconTemplate; context: { isLoading: isLoading, message: message }\"></ng-container>\n <dc-audio-text-sync [message]=\"message\"></dc-audio-text-sync>\n</div>\n}@else {\n<!-- Message without transcription -->\n\n<div style=\"display: flex\">\n <ng-container *ngTemplateOutlet=\"iconTemplate; context: { isLoading: isLoading, message: message }\"></ng-container>\n <span style=\"margin-left: 2px\" [ngClass]=\"message.tag\" [innerHtml]=\"message.text || message.content\"></span>\n</div>\n} }\n\n<!-- Icon template for play/loading status -->\n<ng-template #iconTemplate let-isLoading=\"isLoading\" let-message=\"message\">\n <i (click)=\"onPlayMessage(message)\">\n <dc-icon *ngIf=\"isLoading\" class=\"spin-animation\" name=\"loading\"></dc-icon>\n <dc-icon *ngIf=\"!isLoading\" name=\"play\"></dc-icon>\n </i>\n</ng-template>\n", styles: [".highlight{background-color:#ffff004d}i{cursor:pointer;display:inline-flex;align-items:center;margin-right:4px}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
|
|
1435
|
-
}], ctorParameters: () => [], propDecorators: { messages: [{
|
|
1436
|
-
type: Input
|
|
1437
|
-
}], isLoading: [{
|
|
1438
|
-
type: Input
|
|
1439
|
-
}], playAudio: [{
|
|
1440
|
-
type: Output
|
|
1441
|
-
}] } });
|
|
1442
|
-
|
|
1443
|
-
function matchTranscription(originalText, transcription) {
|
|
1444
|
-
const result = [];
|
|
1445
|
-
const transcriptionMap = new Map();
|
|
1446
|
-
// Create a map of lowercase words to an array of their corresponding objects in transcription
|
|
1447
|
-
for (const obj of transcription) {
|
|
1448
|
-
const lowercaseWord = obj.word.trim().toLowerCase();
|
|
1449
|
-
if (transcriptionMap.has(lowercaseWord)) {
|
|
1450
|
-
transcriptionMap.get(lowercaseWord).push(obj);
|
|
1451
|
-
}
|
|
1452
|
-
else {
|
|
1453
|
-
transcriptionMap.set(lowercaseWord, [obj]);
|
|
1454
|
-
}
|
|
1068
|
+
function matchTranscription(originalText, transcription) {
|
|
1069
|
+
const result = [];
|
|
1070
|
+
const transcriptionMap = new Map();
|
|
1071
|
+
// Create a map of lowercase words to an array of their corresponding objects in transcription
|
|
1072
|
+
for (const obj of transcription) {
|
|
1073
|
+
const lowercaseWord = obj.word.trim().toLowerCase();
|
|
1074
|
+
if (transcriptionMap.has(lowercaseWord)) {
|
|
1075
|
+
transcriptionMap.get(lowercaseWord).push(obj);
|
|
1076
|
+
}
|
|
1077
|
+
else {
|
|
1078
|
+
transcriptionMap.set(lowercaseWord, [obj]);
|
|
1079
|
+
}
|
|
1455
1080
|
}
|
|
1456
1081
|
// Split the original text into an array of words
|
|
1457
1082
|
const words = originalText.split(' ');
|
|
@@ -1484,334 +1109,78 @@ function extractAudioAndTranscription(message, audio) {
|
|
|
1484
1109
|
if (message.transcription) {
|
|
1485
1110
|
message.transcriptionTimestamps = matchTranscription(message.text || message.content, message.transcription.words);
|
|
1486
1111
|
}
|
|
1112
|
+
return message;
|
|
1487
1113
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
this.conversationChatSettings.set(settings);
|
|
1501
|
-
if (this.chatMessage.role === ChatRole.AssistantHelper) {
|
|
1502
|
-
return;
|
|
1503
|
-
}
|
|
1504
|
-
const settings$ = this.conversationChatSettings();
|
|
1505
|
-
if (this.chatMessage.role === ChatRole.User) {
|
|
1506
|
-
if (settings$?.repeatRecording && settings$?.synthVoice) {
|
|
1507
|
-
this.playMessage(this.chatMessage);
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
else if (settings$?.synthVoice) {
|
|
1511
|
-
if (this.chatMessage.multiMessages) {
|
|
1512
|
-
this.generateAndPlayAllAudios(this.chatMessage.multiMessages);
|
|
1513
|
-
}
|
|
1514
|
-
else {
|
|
1515
|
-
this.generateAndPlayAudio(this.chatMessage);
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
async generateAndPlayAudio(message, overwriteText = null) {
|
|
1520
|
-
this.isLoading.set(true);
|
|
1521
|
-
try {
|
|
1522
|
-
const text = overwriteText || message.content;
|
|
1523
|
-
const ttsObject = this.buildObjectTTSRequest({ ...this.chatMessage, text });
|
|
1524
|
-
if (message.ssml) {
|
|
1525
|
-
ttsObject.ssml = message.ssml;
|
|
1526
|
-
}
|
|
1527
|
-
const speechAudio = await this.agentCardService.getTextAudioFile(ttsObject);
|
|
1528
|
-
extractAudioAndTranscription(message, speechAudio);
|
|
1529
|
-
this.playMessage(message);
|
|
1530
|
-
}
|
|
1531
|
-
finally {
|
|
1532
|
-
this.isLoading.set(false);
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
playMessage(message) {
|
|
1536
|
-
if (!message.audioUrl) {
|
|
1537
|
-
return null;
|
|
1538
|
-
}
|
|
1539
|
-
const audioElement = this.audioService.playAudio(message.audioUrl);
|
|
1540
|
-
message['audioHtml'] = audioElement;
|
|
1541
|
-
console.log('Playing message with transcription:', {
|
|
1542
|
-
hasTranscription: !!message.transcriptionTimestamps,
|
|
1543
|
-
transcriptionLength: message.transcriptionTimestamps?.length,
|
|
1544
|
-
audioUrl: message.audioUrl,
|
|
1545
|
-
audioElement,
|
|
1546
|
-
});
|
|
1547
|
-
if (message.transcriptionTimestamps) {
|
|
1548
|
-
// Generate a unique ID for this message
|
|
1549
|
-
const messageId = this.generateMessageId(message);
|
|
1550
|
-
console.log('Syncing audio with text:', {
|
|
1551
|
-
messageId,
|
|
1552
|
-
transcriptionTimestamps: message.transcriptionTimestamps,
|
|
1553
|
-
wordCount: message.transcriptionTimestamps.length,
|
|
1554
|
-
});
|
|
1555
|
-
// Use the audio-text sync service instead of direct manipulation
|
|
1556
|
-
// Pass the messageId to ensure each message has its own state
|
|
1557
|
-
this.audioTextSyncService.syncAudioWithText(audioElement, message.transcriptionTimestamps, messageId);
|
|
1558
|
-
}
|
|
1559
|
-
return audioElement;
|
|
1560
|
-
}
|
|
1561
|
-
/**
|
|
1562
|
-
* Generate a unique ID for a message
|
|
1563
|
-
* Uses message content/text and a timestamp to ensure uniqueness
|
|
1564
|
-
*/
|
|
1565
|
-
generateMessageId(message) {
|
|
1566
|
-
const messageContent = message.content || message.text || '';
|
|
1567
|
-
// Use a safe approach to get an ID-like value since 'id' isn't guaranteed on these types
|
|
1568
|
-
const messageId = message._id || message.id || '';
|
|
1569
|
-
const timestamp = new Date().getTime();
|
|
1570
|
-
return `msg_${messageId}_${messageContent.substring(0, 10).replace(/\s+/g, '_')}_${timestamp}`;
|
|
1571
|
-
}
|
|
1572
|
-
// This method is no longer needed as we're using the AudioTextSyncService
|
|
1573
|
-
// It's kept here as a reference but can be removed
|
|
1574
|
-
/*
|
|
1575
|
-
private setupTranscriptionHighlighting(audioElement: HTMLAudioElement, transcriptionTimestamps: WordTimestamps[]): void {
|
|
1576
|
-
// This functionality has been moved to AudioTextSyncService
|
|
1577
|
-
}
|
|
1578
|
-
*/
|
|
1579
|
-
generateAndPlayAllAudios(multiMessages) {
|
|
1580
|
-
if (!multiMessages.length)
|
|
1581
|
-
return;
|
|
1582
|
-
let currentIndex = 0;
|
|
1583
|
-
const playAudioSequentially = async () => {
|
|
1584
|
-
if (currentIndex >= multiMessages.length) {
|
|
1585
|
-
return;
|
|
1586
|
-
}
|
|
1587
|
-
const currentMessage = multiMessages[currentIndex];
|
|
1588
|
-
currentMessage.isLoading = true;
|
|
1589
|
-
try {
|
|
1590
|
-
// Process current message audio if needed
|
|
1591
|
-
if (!currentMessage.audioUrl) {
|
|
1592
|
-
if (currentMessage.audioPromise) {
|
|
1593
|
-
await currentMessage.audioPromise;
|
|
1594
|
-
}
|
|
1595
|
-
else {
|
|
1596
|
-
const request = this.buildObjectTTSRequest(currentMessage);
|
|
1597
|
-
currentMessage.audioPromise = this.agentCardService.getTextAudioFile(request);
|
|
1598
|
-
const audio = await currentMessage.audioPromise;
|
|
1599
|
-
extractAudioAndTranscription(currentMessage, audio);
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
// Play the current message
|
|
1603
|
-
const audioElement = this.playMessage(currentMessage);
|
|
1604
|
-
if (audioElement) {
|
|
1605
|
-
audioElement.addEventListener('ended', () => {
|
|
1606
|
-
currentIndex++;
|
|
1607
|
-
playAudioSequentially();
|
|
1608
|
-
});
|
|
1609
|
-
}
|
|
1610
|
-
else {
|
|
1611
|
-
// If playback failed, move to next message
|
|
1612
|
-
currentIndex++;
|
|
1613
|
-
playAudioSequentially();
|
|
1614
|
-
}
|
|
1615
|
-
// Preload next message audio
|
|
1616
|
-
this.preloadNextMessageAudio(multiMessages, currentIndex);
|
|
1617
|
-
}
|
|
1618
|
-
finally {
|
|
1619
|
-
currentMessage.isLoading = false;
|
|
1620
|
-
}
|
|
1621
|
-
};
|
|
1622
|
-
// Start the sequential playback
|
|
1623
|
-
playAudioSequentially();
|
|
1624
|
-
}
|
|
1625
|
-
preloadNextMessageAudio(messages, currentIndex) {
|
|
1626
|
-
if (currentIndex + 1 < messages.length) {
|
|
1627
|
-
const nextMessage = messages[currentIndex + 1];
|
|
1628
|
-
if (!nextMessage.audioUrl && !nextMessage.audioPromise) {
|
|
1629
|
-
const request = this.buildObjectTTSRequest(nextMessage);
|
|
1630
|
-
nextMessage.isLoading = true;
|
|
1631
|
-
nextMessage.audioPromise = this.agentCardService.getTextAudioFile(request);
|
|
1632
|
-
nextMessage.audioPromise.then((audio) => {
|
|
1633
|
-
extractAudioAndTranscription(nextMessage, audio);
|
|
1634
|
-
nextMessage.isLoading = false;
|
|
1635
|
-
});
|
|
1636
|
-
}
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1639
|
-
buildObjectTTSRequest(message) {
|
|
1640
|
-
const settings = this.conversationChatSettings();
|
|
1641
|
-
const generateTranscription = settings?.highlightWords ?? false;
|
|
1642
|
-
const speedRate = settings?.speedRate || 0;
|
|
1643
|
-
const speed = typeof settings?.speed === 'string' ? settings.speed : AudioSpeed.Regular;
|
|
1644
|
-
const text = removeEmojis(message.text || message.content);
|
|
1645
|
-
return {
|
|
1646
|
-
text,
|
|
1647
|
-
voice: message.voice || this.chatMessage.voice,
|
|
1648
|
-
generateTranscription,
|
|
1649
|
-
speedRate,
|
|
1650
|
-
speed,
|
|
1651
|
-
};
|
|
1652
|
-
}
|
|
1653
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ChatMessageComponent, deps: [{ token: AudioService }, { token: CONVERSATION_AI_TOKEN }, { token: AudioTextSyncService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1654
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", type: ChatMessageComponent, isStandalone: true, selector: "dc-chat-message", inputs: { chatMessage: "chatMessage", chatUserSettings: "chatUserSettings" }, ngImport: i0, template: "<!-- Multi-message display -->\n<span class=\"message-container\">\n @if (chatMessage?.multiMessages) {\n <!-- Single message display -->\n <dc-multi-message-content [messages]=\"chatMessage.multiMessages\" (playAudio)=\"playMessage($event)\"> </dc-multi-message-content>\n } @else {\n <dc-message-content [message]=\"chatMessage\" [isLoading]=\"isLoading()\" (playAudio)=\"playMessage($event)\"> </dc-message-content>\n }\n</span>\n\n@if (chatMessage.translation) {\n<!-- Translation display if available -->\n\n<div class=\"translation\">\n <hr class=\"divider\" />\n {{ chatMessage.translation }}\n</div>\n}\n", styles: [":host{display:block}.message-container{display:block;line-height:1.5}::ng-deep .em{color:#0d5878;font-style:italic}::ng-deep .strong{font-weight:700;color:#515151}::ng-deep .em_strong{font-weight:700;font-style:italic;color:#0d5878}.translation{margin-top:8px;font-size:small;line-height:1.6;color:#393744;font-style:italic}.divider{margin:.5rem 40px;border-top:1px solid #ffa77e}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: MessageContentComponent, selector: "dc-message-content", inputs: ["message", "isLoading"], outputs: ["playAudio"] }, { kind: "component", type: MultiMessageContentComponent, selector: "dc-multi-message-content", inputs: ["messages", "isLoading"], outputs: ["playAudio"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1655
|
-
}
|
|
1656
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ChatMessageComponent, decorators: [{
|
|
1657
|
-
type: Component,
|
|
1658
|
-
args: [{ selector: 'dc-chat-message', standalone: true, imports: [CommonModule, MessageContentComponent, MultiMessageContentComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Multi-message display -->\n<span class=\"message-container\">\n @if (chatMessage?.multiMessages) {\n <!-- Single message display -->\n <dc-multi-message-content [messages]=\"chatMessage.multiMessages\" (playAudio)=\"playMessage($event)\"> </dc-multi-message-content>\n } @else {\n <dc-message-content [message]=\"chatMessage\" [isLoading]=\"isLoading()\" (playAudio)=\"playMessage($event)\"> </dc-message-content>\n }\n</span>\n\n@if (chatMessage.translation) {\n<!-- Translation display if available -->\n\n<div class=\"translation\">\n <hr class=\"divider\" />\n {{ chatMessage.translation }}\n</div>\n}\n", styles: [":host{display:block}.message-container{display:block;line-height:1.5}::ng-deep .em{color:#0d5878;font-style:italic}::ng-deep .strong{font-weight:700;color:#515151}::ng-deep .em_strong{font-weight:700;font-style:italic;color:#0d5878}.translation{margin-top:8px;font-size:small;line-height:1.6;color:#393744;font-style:italic}.divider{margin:.5rem 40px;border-top:1px solid #ffa77e}\n"] }]
|
|
1659
|
-
}], ctorParameters: () => [{ type: AudioService }, { type: AgentCardsAbstractService, decorators: [{
|
|
1660
|
-
type: Inject,
|
|
1661
|
-
args: [CONVERSATION_AI_TOKEN]
|
|
1662
|
-
}] }, { type: AudioTextSyncService }], propDecorators: { chatMessage: [{
|
|
1663
|
-
type: Input
|
|
1664
|
-
}], chatUserSettings: [{
|
|
1665
|
-
type: Input
|
|
1666
|
-
}] } });
|
|
1667
|
-
|
|
1668
|
-
class MessageProcessingService {
|
|
1669
|
-
constructor() { }
|
|
1670
|
-
// Process message for display
|
|
1671
|
-
processMessage(message, conversationSettings, mutate = false) {
|
|
1672
|
-
const defaultVoice = this.getVoice(conversationSettings.voice);
|
|
1673
|
-
let processedMessage;
|
|
1674
|
-
if (mutate) {
|
|
1675
|
-
// Modify the existing message object
|
|
1676
|
-
message.voice = defaultVoice;
|
|
1677
|
-
processedMessage = message;
|
|
1678
|
-
}
|
|
1679
|
-
else {
|
|
1680
|
-
// Create a new message object
|
|
1681
|
-
processedMessage = {
|
|
1682
|
-
content: message.content,
|
|
1683
|
-
role: message.role,
|
|
1684
|
-
voice: defaultVoice
|
|
1685
|
-
};
|
|
1686
|
-
}
|
|
1687
|
-
// Process based on text engine
|
|
1688
|
-
if (conversationSettings.textEngine === TextEngines.MarkdownMultiMessages) {
|
|
1689
|
-
this.processMultiMessages(processedMessage, conversationSettings);
|
|
1690
|
-
}
|
|
1691
|
-
else if (conversationSettings.textEngine === TextEngines.MarkdownSSML) {
|
|
1692
|
-
if (!conversationSettings.secondaryVoice) {
|
|
1693
|
-
throw new Error('Secondary voice is required for SSML');
|
|
1694
|
-
}
|
|
1695
|
-
const content = this.subsItalicsByTag(processedMessage.content, conversationSettings.secondaryVoice);
|
|
1696
|
-
processedMessage.ssml = '<speak>' + content + '</speak>';
|
|
1697
|
-
}
|
|
1698
|
-
return processedMessage;
|
|
1699
|
-
}
|
|
1700
|
-
// Process multi-messages from markdown
|
|
1701
|
-
processMultiMessages(message, settings) {
|
|
1702
|
-
// Convert markdown to HTML segments
|
|
1703
|
-
const mdToHtml = convertToHTML(message.content);
|
|
1704
|
-
// Map segments to multi-messages
|
|
1705
|
-
message.multiMessages = mdToHtml.map((val) => {
|
|
1706
|
-
// Get the narrator voice from conversation settings
|
|
1707
|
-
const narratorVoice = settings.secondaryVoice || 'en-US-News-L';
|
|
1708
|
-
// Determine if this is italicized text (narrator)
|
|
1709
|
-
const isItalics = val.tag === 'em';
|
|
1710
|
-
const voice = isItalics ? narratorVoice : message.voice;
|
|
1711
|
-
return {
|
|
1712
|
-
voice,
|
|
1713
|
-
content: val.content,
|
|
1714
|
-
audioUrl: null,
|
|
1715
|
-
audioPromise: null,
|
|
1716
|
-
text: val.text,
|
|
1717
|
-
tag: val.tag
|
|
1718
|
-
};
|
|
1719
|
-
});
|
|
1720
|
-
}
|
|
1721
|
-
// Replace italics with voice tags for SSML
|
|
1722
|
-
subsItalicsByTag(text, voiceId = 'it-IT-Neural2-A', tagName = 'voice') {
|
|
1723
|
-
const regex = /\*(.*?)\*/g;
|
|
1724
|
-
return text.replace(regex, (match, p1) => `<${tagName} name="${voiceId}">${p1}</${tagName}>`);
|
|
1725
|
-
}
|
|
1726
|
-
// Get appropriate voice based on settings
|
|
1727
|
-
getVoice(voice_value, targetLang = 'en') {
|
|
1728
|
-
if ([null, '', 'random'].includes(voice_value)) {
|
|
1729
|
-
const voiceCodes = VoiceTTSOptions.map((val) => val.id);
|
|
1730
|
-
return voiceCodes[Math.floor(Math.random() * voiceCodes.length)];
|
|
1731
|
-
}
|
|
1732
|
-
if (voice_value === 'randomMan') {
|
|
1733
|
-
const voiceCodes = VoiceTTSOptions.filter((voice) => voice.gender === 'male' && voice.lang.includes(targetLang)).map((val) => val.id);
|
|
1734
|
-
return voiceCodes[Math.floor(Math.random() * voiceCodes.length)];
|
|
1735
|
-
}
|
|
1736
|
-
else if (voice_value === 'randomWoman') {
|
|
1737
|
-
const voiceCodes = VoiceTTSOptions.filter((voice) => voice.gender === 'female' && voice.lang.includes(targetLang)).map((val) => val.id);
|
|
1738
|
-
return voiceCodes[Math.floor(Math.random() * voiceCodes.length)];
|
|
1739
|
-
}
|
|
1740
|
-
else {
|
|
1741
|
-
const voice = VoiceTTSOptions.find((voice) => voice.id === voice_value);
|
|
1742
|
-
if (voice) {
|
|
1743
|
-
return voice.id;
|
|
1744
|
-
}
|
|
1745
|
-
else {
|
|
1746
|
-
console.error('Voice not found getting something random', voice_value);
|
|
1747
|
-
return VoiceTTSOptions.find((voice) => voice.lang.includes(targetLang))?.id || '';
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
}
|
|
1751
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: MessageProcessingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1752
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: MessageProcessingService, providedIn: 'root' }); }
|
|
1114
|
+
function buildObjectTTSRequest(message, settings = {}) {
|
|
1115
|
+
const generateTranscription = settings?.highlightWords ?? false;
|
|
1116
|
+
const speedRate = settings?.speedRate || 0;
|
|
1117
|
+
const speed = typeof settings?.speed === 'string' ? settings.speed : AudioSpeed$1.Regular;
|
|
1118
|
+
const text = removeEmojis(message.text || message.content);
|
|
1119
|
+
return {
|
|
1120
|
+
text,
|
|
1121
|
+
voice: message.voice || settings.voice,
|
|
1122
|
+
generateTranscription,
|
|
1123
|
+
speedRate,
|
|
1124
|
+
speed,
|
|
1125
|
+
};
|
|
1753
1126
|
}
|
|
1754
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: MessageProcessingService, decorators: [{
|
|
1755
|
-
type: Injectable,
|
|
1756
|
-
args: [{
|
|
1757
|
-
providedIn: 'root',
|
|
1758
|
-
}]
|
|
1759
|
-
}], ctorParameters: () => [] });
|
|
1760
1127
|
|
|
1761
1128
|
class ConversationService {
|
|
1762
|
-
constructor(
|
|
1763
|
-
|
|
1764
|
-
this.
|
|
1129
|
+
constructor() {
|
|
1130
|
+
// Services
|
|
1131
|
+
this.agentCardService = inject(CONVERSATION_AI_TOKEN);
|
|
1132
|
+
this.messageProcessingService = inject(MessageProcessingService);
|
|
1133
|
+
this.conversationBuilder = inject(DCConversationPromptBuilderService);
|
|
1134
|
+
this.userDataExchange = inject(USER_DATA_EXCHANGE);
|
|
1135
|
+
// Signals
|
|
1765
1136
|
this.messagesSignal = signal([]);
|
|
1766
1137
|
this.isThinkingSignal = signal(false);
|
|
1767
1138
|
this.conversationSettingsSignal = signal(null);
|
|
1768
|
-
this.agentCardSignal = signal(null);
|
|
1769
1139
|
this.isDestroyedSignal = signal(false);
|
|
1770
1140
|
}
|
|
1771
1141
|
// Get messages as a signal
|
|
1772
|
-
|
|
1142
|
+
getMessagesSignal() {
|
|
1773
1143
|
return this.messagesSignal;
|
|
1774
1144
|
}
|
|
1145
|
+
// Add message to conversation
|
|
1146
|
+
addMessage(message) {
|
|
1147
|
+
this.messagesSignal.update((messages) => [...messages, message]);
|
|
1148
|
+
}
|
|
1775
1149
|
// Get thinking state as a signal
|
|
1776
1150
|
isThinking() {
|
|
1777
1151
|
return this.isThinkingSignal;
|
|
1778
1152
|
}
|
|
1779
|
-
// Get conversation settings as a signal
|
|
1780
|
-
getConversationSettings() {
|
|
1781
|
-
return this.conversationSettingsSignal;
|
|
1782
|
-
}
|
|
1783
|
-
// Get agent card as a signal
|
|
1784
|
-
getAgentCard() {
|
|
1785
|
-
return this.agentCardSignal;
|
|
1786
|
-
}
|
|
1787
1153
|
// Set destroyed state
|
|
1788
1154
|
setDestroyed(value) {
|
|
1789
1155
|
this.isDestroyedSignal.set(value);
|
|
1790
1156
|
}
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1157
|
+
setupConversationWithAgentCard(agentCard) {
|
|
1158
|
+
const conversationSettings = this.conversationBuilder.buildConversationSettings(agentCard);
|
|
1159
|
+
// set initial ids
|
|
1160
|
+
for (const i in conversationSettings.messages) {
|
|
1161
|
+
conversationSettings.messages[i].messageId = 'msg_' + i;
|
|
1795
1162
|
}
|
|
1796
|
-
this.agentCardSignal.set(agentCard);
|
|
1797
|
-
// Build conversation settings
|
|
1798
|
-
const conversationSettings = conversationBuilder.buildConversationSettings(agentCard, parseDict);
|
|
1799
1163
|
this.conversationSettingsSignal.set(conversationSettings);
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
}
|
|
1806
|
-
// Set initial messages
|
|
1807
|
-
this.messagesSignal.set(conversationSettings.messages);
|
|
1164
|
+
}
|
|
1165
|
+
// Initialize conversation
|
|
1166
|
+
async initConversation(agentCard) {
|
|
1167
|
+
this.setupConversationWithAgentCard(agentCard);
|
|
1168
|
+
const conversationSettings = this.conversationSettingsSignal();
|
|
1808
1169
|
// Find first assistant message
|
|
1809
1170
|
const firstAssistantMsg = conversationSettings.messages.find((message) => message.role === ChatRole.Assistant);
|
|
1810
1171
|
if (firstAssistantMsg) {
|
|
1811
1172
|
// Process the first assistant message
|
|
1812
|
-
this.
|
|
1173
|
+
const processedMessage = this.messageProcessingService.processMessage(firstAssistantMsg, this.conversationSettingsSignal());
|
|
1174
|
+
// Find the index of the message with the matching ID
|
|
1175
|
+
const messageIndex = conversationSettings.messages.findIndex((message) => message.messageId === firstAssistantMsg.messageId);
|
|
1176
|
+
// If found, replace the message at that index
|
|
1177
|
+
if (messageIndex !== -1) {
|
|
1178
|
+
conversationSettings.messages[messageIndex] = processedMessage;
|
|
1179
|
+
}
|
|
1180
|
+
this.messagesSignal.set(conversationSettings.messages);
|
|
1181
|
+
// this.addMessage(processedMessage);
|
|
1813
1182
|
}
|
|
1814
|
-
else if (
|
|
1183
|
+
else if (conversationSettings.autoStart) {
|
|
1815
1184
|
// Auto-start conversation if configured
|
|
1816
1185
|
await this.sendCurrentConversation();
|
|
1817
1186
|
}
|
|
@@ -1826,135 +1195,771 @@ class ConversationService {
|
|
|
1826
1195
|
// Set thinking state
|
|
1827
1196
|
this.isThinkingSignal.set(true);
|
|
1828
1197
|
try {
|
|
1829
|
-
if (message.audioUrl && this.getAudioPlaybackSetting()) {
|
|
1830
|
-
// Wait for audio to finish playing before sending response
|
|
1831
|
-
await new Promise((resolve) => {
|
|
1832
|
-
if (message.audioHtml) {
|
|
1833
|
-
message.audioHtml.addEventListener('ended', () => {
|
|
1834
|
-
resolve();
|
|
1835
|
-
});
|
|
1836
|
-
}
|
|
1837
|
-
else {
|
|
1838
|
-
resolve();
|
|
1839
|
-
}
|
|
1840
|
-
});
|
|
1841
|
-
}
|
|
1842
1198
|
// Send to AI service
|
|
1843
1199
|
await this.sendCurrentConversation();
|
|
1844
1200
|
}
|
|
1845
|
-
finally {
|
|
1846
|
-
this.isThinkingSignal.set(false);
|
|
1201
|
+
finally {
|
|
1202
|
+
this.isThinkingSignal.set(false);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
// Process assistant message
|
|
1206
|
+
// Send current conversation to AI
|
|
1207
|
+
async sendCurrentConversation() {
|
|
1208
|
+
if (this.isDestroyedSignal()) {
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
const messages = this.messagesSignal();
|
|
1212
|
+
const conversationSettings = this.conversationSettingsSignal();
|
|
1213
|
+
if (messages.length > 31) {
|
|
1214
|
+
// Safety limit to prevent infinite conversations
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
let conversationMessages = messages;
|
|
1218
|
+
// Add last prompt if available
|
|
1219
|
+
if (conversationSettings.last_prompt) {
|
|
1220
|
+
conversationMessages = [...messages, { content: conversationSettings.last_prompt, role: ChatRole.System }];
|
|
1221
|
+
}
|
|
1222
|
+
// Prepare conversation DTO
|
|
1223
|
+
const conversation = {
|
|
1224
|
+
messages: conversationMessages,
|
|
1225
|
+
conversationType: conversationSettings.conversationType,
|
|
1226
|
+
textEngine: conversationSettings.textEngine,
|
|
1227
|
+
model: { modelName: '', provider: '' },
|
|
1228
|
+
};
|
|
1229
|
+
console.warn('fix model name modelName take from user settings');
|
|
1230
|
+
// Call AI service
|
|
1231
|
+
const response = await this.agentCardService.callChatCompletion(conversation);
|
|
1232
|
+
if (!response) {
|
|
1233
|
+
console.error('No message returned from AI, is your service working?');
|
|
1234
|
+
throw new Error('No message returned from AI');
|
|
1235
|
+
}
|
|
1236
|
+
// Process response
|
|
1237
|
+
const newMessage = this.messageProcessingService.processMessage(response, conversationSettings);
|
|
1238
|
+
// Add to messages
|
|
1239
|
+
this.addMessage(newMessage);
|
|
1240
|
+
this.isThinkingSignal.set(false);
|
|
1241
|
+
}
|
|
1242
|
+
// Reset conversation
|
|
1243
|
+
async resetConversation(agentCard) {
|
|
1244
|
+
// Clear messages
|
|
1245
|
+
this.messagesSignal.set([]);
|
|
1246
|
+
}
|
|
1247
|
+
async getTTSFile(message) {
|
|
1248
|
+
const userChatSettings = await this.userDataExchange.getUserChatSettings();
|
|
1249
|
+
const ttsRequest = buildObjectTTSRequest(message, userChatSettings);
|
|
1250
|
+
return this.agentCardService.getTextAudioFile(ttsRequest);
|
|
1251
|
+
}
|
|
1252
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ConversationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1253
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ConversationService, providedIn: 'root' }); }
|
|
1254
|
+
}
|
|
1255
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ConversationService, decorators: [{
|
|
1256
|
+
type: Injectable,
|
|
1257
|
+
args: [{
|
|
1258
|
+
providedIn: 'root',
|
|
1259
|
+
}]
|
|
1260
|
+
}] });
|
|
1261
|
+
|
|
1262
|
+
function extractJsonFromResponse(content) {
|
|
1263
|
+
const jsonMatch = content.match(/\{[\s\S]*?\}/); // Match everything between first { and }
|
|
1264
|
+
if (!jsonMatch)
|
|
1265
|
+
return null;
|
|
1266
|
+
try {
|
|
1267
|
+
return JSON.parse(jsonMatch[0]);
|
|
1268
|
+
}
|
|
1269
|
+
catch (error) {
|
|
1270
|
+
console.error('Error parsing JSON:', error);
|
|
1271
|
+
return null;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
class EvaluationService {
|
|
1276
|
+
constructor() {
|
|
1277
|
+
this.agentCardService = inject(CONVERSATION_AI_TOKEN);
|
|
1278
|
+
this.scoreSignal = signal(10);
|
|
1279
|
+
this.evaluationResultSignal = signal(null);
|
|
1280
|
+
}
|
|
1281
|
+
// Get score as a signal
|
|
1282
|
+
getScore() {
|
|
1283
|
+
return this.scoreSignal;
|
|
1284
|
+
}
|
|
1285
|
+
// Get evaluation result as a signal
|
|
1286
|
+
getEvaluationResult() {
|
|
1287
|
+
return this.evaluationResultSignal;
|
|
1288
|
+
}
|
|
1289
|
+
// Evaluate conversation
|
|
1290
|
+
async evaluateConversation(messages, evaluator) {
|
|
1291
|
+
// Filter conversation to only include user and assistant messages
|
|
1292
|
+
const conversationMessages = messages.filter((message) => [ChatRole.User, ChatRole.Assistant].includes(message.role));
|
|
1293
|
+
// Check if there are any user messages to evaluate
|
|
1294
|
+
const userMessages = conversationMessages.filter((message) => message.role === ChatRole.User);
|
|
1295
|
+
if (userMessages.length <= 0) {
|
|
1296
|
+
console.log('No messages to evaluate', conversationMessages);
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
// Format conversation for evaluation
|
|
1300
|
+
const conversationMessagesString = conversationMessages
|
|
1301
|
+
.map((message) => `${message.role}: ${message.content}`)
|
|
1302
|
+
.join('\n');
|
|
1303
|
+
// Create evaluation prompt
|
|
1304
|
+
const instructions = `
|
|
1305
|
+
Please replay to this task:
|
|
1306
|
+
${evaluator.task}
|
|
1307
|
+
This is the conversation history:
|
|
1308
|
+
${conversationMessagesString}
|
|
1309
|
+
and give the response in next JSON format.
|
|
1310
|
+
${evaluator.expectedResponseType}
|
|
1311
|
+
`;
|
|
1312
|
+
// Send evaluation request
|
|
1313
|
+
const evaluationMessages = [{ content: instructions, role: ChatRole.User }];
|
|
1314
|
+
const response = await this.agentCardService.callChatCompletion({ messages: evaluationMessages });
|
|
1315
|
+
// Extract JSON from response
|
|
1316
|
+
const jsonData = extractJsonFromResponse(response.content);
|
|
1317
|
+
this.evaluationResultSignal.set(jsonData);
|
|
1318
|
+
// Update score if available
|
|
1319
|
+
if (jsonData.score) {
|
|
1320
|
+
if (jsonData.score <= 3) {
|
|
1321
|
+
this.updateScore(jsonData.score * 10);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
// Update score with limits
|
|
1326
|
+
updateScore(additionalScore) {
|
|
1327
|
+
this.scoreSignal.update(currentScore => {
|
|
1328
|
+
const newScore = currentScore + additionalScore;
|
|
1329
|
+
return newScore > 100 ? 100 : newScore;
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
// Reset score
|
|
1333
|
+
resetScore() {
|
|
1334
|
+
this.scoreSignal.set(10);
|
|
1335
|
+
}
|
|
1336
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: EvaluationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1337
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: EvaluationService, providedIn: 'root' }); }
|
|
1338
|
+
}
|
|
1339
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: EvaluationService, decorators: [{
|
|
1340
|
+
type: Injectable,
|
|
1341
|
+
args: [{
|
|
1342
|
+
providedIn: 'root',
|
|
1343
|
+
}]
|
|
1344
|
+
}], ctorParameters: () => [] });
|
|
1345
|
+
|
|
1346
|
+
class ChatFooterComponent {
|
|
1347
|
+
constructor() {
|
|
1348
|
+
this.conversationService = inject(ConversationService);
|
|
1349
|
+
this.evaluationService = inject(EvaluationService);
|
|
1350
|
+
this.isAIThinking = input(false);
|
|
1351
|
+
this.evaluatorAgentCard = input();
|
|
1352
|
+
this.micSettings = input({ useWhisper: true, lang: 'en' });
|
|
1353
|
+
this.sendMessage = output();
|
|
1354
|
+
this.textInputChanged = output();
|
|
1355
|
+
// readonly micFinishedEvent = output<any>();
|
|
1356
|
+
this.isGettingTranscription = false;
|
|
1357
|
+
this.isUserTalking = false;
|
|
1358
|
+
this.chatInputControl = new FormControl();
|
|
1359
|
+
// Get score from evaluation service
|
|
1360
|
+
this.score = this.evaluationService.getScore();
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Sets the input text in the textarea
|
|
1364
|
+
* @param text The text to set
|
|
1365
|
+
*/
|
|
1366
|
+
setInputText(text) {
|
|
1367
|
+
this.chatInputControl.setValue(text);
|
|
1368
|
+
this.textInputChanged.emit(text);
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Handles the mic finished event
|
|
1372
|
+
* @param eventBlob The blob event from the mic component
|
|
1373
|
+
*/
|
|
1374
|
+
micFinished(eventBlob) {
|
|
1375
|
+
// this.micFinishedEvent.emit(eventBlob);
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Sends the user message
|
|
1379
|
+
*/
|
|
1380
|
+
async sendUserMessage() {
|
|
1381
|
+
if (this.isAIThinking() || !this.chatInputControl.value) {
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
const text = this.chatInputControl.value;
|
|
1385
|
+
const message = {
|
|
1386
|
+
content: text,
|
|
1387
|
+
role: ChatRole.User,
|
|
1388
|
+
};
|
|
1389
|
+
// Emit the message for parent components that need it
|
|
1390
|
+
this.sendMessage.emit(message);
|
|
1391
|
+
// Clear the input field
|
|
1392
|
+
this.chatInputControl.setValue('');
|
|
1393
|
+
// Send the user message to the conversation service
|
|
1394
|
+
await this.conversationService.sendUserMessage(message);
|
|
1395
|
+
// Evaluate conversation after sending message
|
|
1396
|
+
this.evaluateConversation();
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Evaluate conversation using evaluator agent
|
|
1400
|
+
*/
|
|
1401
|
+
async evaluateConversation() {
|
|
1402
|
+
const messages = this.conversationService.getMessagesSignal()();
|
|
1403
|
+
if (this.evaluatorAgentCard()) {
|
|
1404
|
+
await this.evaluationService.evaluateConversation(messages, this.evaluatorAgentCard());
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
async onMicFinished(eventBlob) {
|
|
1408
|
+
if (!(eventBlob instanceof Blob)) {
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
// Check if audio is too small
|
|
1412
|
+
if (eventBlob.size < 10000) {
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
this.isUserTalking = false;
|
|
1416
|
+
this.isGettingTranscription = true;
|
|
1417
|
+
try {
|
|
1418
|
+
// Get transcription from audio
|
|
1419
|
+
// const transcription = await this.agentCardService.getAudioTranscriptions(eventBlob, {
|
|
1420
|
+
// conversationId: this.agentCard._id,
|
|
1421
|
+
// });
|
|
1422
|
+
// Create message with transcription
|
|
1423
|
+
// const message: ChatMessage = {
|
|
1424
|
+
// content: transcription.text,
|
|
1425
|
+
// role: ChatRole.User,
|
|
1426
|
+
// transcriptionTimestamps: transcription.words,
|
|
1427
|
+
// };
|
|
1428
|
+
const message = {
|
|
1429
|
+
content: '',
|
|
1430
|
+
role: ChatRole.User,
|
|
1431
|
+
audioUrl: URL.createObjectURL(eventBlob),
|
|
1432
|
+
};
|
|
1433
|
+
// Send message to conversation
|
|
1434
|
+
// The evaluation will happen automatically in the conversation service
|
|
1435
|
+
await this.conversationService.sendUserMessage(message);
|
|
1436
|
+
}
|
|
1437
|
+
finally {
|
|
1438
|
+
this.isGettingTranscription = false;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1442
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.4", type: ChatFooterComponent, isStandalone: true, selector: "dc-chat-footer", inputs: { isAIThinking: { classPropertyName: "isAIThinking", publicName: "isAIThinking", isSignal: true, isRequired: false, transformFunction: null }, evaluatorAgentCard: { classPropertyName: "evaluatorAgentCard", publicName: "evaluatorAgentCard", isSignal: true, isRequired: false, transformFunction: null }, micSettings: { classPropertyName: "micSettings", publicName: "micSettings", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sendMessage: "sendMessage", textInputChanged: "textInputChanged" }, ngImport: i0, template: "<div class=\"progress-input\">\n <div class=\"input-container\">\n <dc-mic\n style=\"display: flex; align-items: center\"\n (onInterpretedText)=\"setInputText($event)\"\n (onFinished)=\"micFinished($event)\"\n [micSettings]=\"micSettings()\"></dc-mic>\n\n <textarea pTextarea [formControl]=\"chatInputControl\" (keyup.enter)=\"sendUserMessage()\" rows=\"1\"></textarea>\n\n <p-button (click)=\"sendUserMessage()\" [disabled]=\"isAIThinking() || !chatInputControl.value\" label=\"Enviar\" [rounded]=\"true\" />\n </div>\n\n <div>\n <p-progressbar showValue=\"false\" [value]=\"score()\" [style]=\"{ height: '6px' }\" />\n </div>\n</div>\n", styles: [".progress-input{padding:10px;background-color:#f5f5f545;border-top:1px solid #b1a8a8}.progress-input .input-container{display:flex;align-items:center;margin-bottom:5px}.progress-input .input-container textarea{flex:1;resize:none;margin:0 10px}.progress-input .input-container .send-button{background-color:#007bff;color:#fff;border:none;border-radius:4px;padding:8px 15px;cursor:pointer}.progress-input .input-container .send-button:disabled{background-color:#ccc;cursor:not-allowed}.progress-input .input-container .send-button:hover:not(:disabled){background-color:#0069d9}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: ProgressBarModule }, { kind: "component", type: i2.ProgressBar, selector: "p-progressBar, p-progressbar, p-progress-bar", inputs: ["value", "showValue", "styleClass", "valueStyleClass", "style", "unit", "mode", "color"] }, { kind: "component", type: DCMicComponent, selector: "dc-mic", inputs: ["isDone", "useWhisper", "targetOrBase", "micSettings"], outputs: ["onInterpretedText", "onFinishedRecognition", "onFinished"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i3.Textarea, selector: "[pTextarea]", inputs: ["autoResize", "variant", "fluid", "pSize"], outputs: ["onResize"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }] }); }
|
|
1443
|
+
}
|
|
1444
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatFooterComponent, decorators: [{
|
|
1445
|
+
type: Component,
|
|
1446
|
+
args: [{ selector: 'dc-chat-footer', standalone: true, imports: [ReactiveFormsModule, ProgressBarModule, DCMicComponent, TextareaModule, ButtonModule], template: "<div class=\"progress-input\">\n <div class=\"input-container\">\n <dc-mic\n style=\"display: flex; align-items: center\"\n (onInterpretedText)=\"setInputText($event)\"\n (onFinished)=\"micFinished($event)\"\n [micSettings]=\"micSettings()\"></dc-mic>\n\n <textarea pTextarea [formControl]=\"chatInputControl\" (keyup.enter)=\"sendUserMessage()\" rows=\"1\"></textarea>\n\n <p-button (click)=\"sendUserMessage()\" [disabled]=\"isAIThinking() || !chatInputControl.value\" label=\"Enviar\" [rounded]=\"true\" />\n </div>\n\n <div>\n <p-progressbar showValue=\"false\" [value]=\"score()\" [style]=\"{ height: '6px' }\" />\n </div>\n</div>\n", styles: [".progress-input{padding:10px;background-color:#f5f5f545;border-top:1px solid #b1a8a8}.progress-input .input-container{display:flex;align-items:center;margin-bottom:5px}.progress-input .input-container textarea{flex:1;resize:none;margin:0 10px}.progress-input .input-container .send-button{background-color:#007bff;color:#fff;border:none;border-radius:4px;padding:8px 15px;cursor:pointer}.progress-input .input-container .send-button:disabled{background-color:#ccc;cursor:not-allowed}.progress-input .input-container .send-button:hover:not(:disabled){background-color:#0069d9}\n"] }]
|
|
1447
|
+
}] });
|
|
1448
|
+
|
|
1449
|
+
const ICONS = {
|
|
1450
|
+
chat: `<svg viewBox="0 0 24 24" fill="currentColor">
|
|
1451
|
+
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c..." />
|
|
1452
|
+
</svg>`,
|
|
1453
|
+
play: `<svg
|
|
1454
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
1455
|
+
width="16"
|
|
1456
|
+
height="16"
|
|
1457
|
+
viewBox="0 0 24 24"
|
|
1458
|
+
fill="none"
|
|
1459
|
+
stroke="#263042"
|
|
1460
|
+
stroke-width="2"
|
|
1461
|
+
stroke-linecap="round"
|
|
1462
|
+
stroke-linejoin="round">
|
|
1463
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
1464
|
+
<polygon points="10 8 16 12 10 16 10 8"></polygon>
|
|
1465
|
+
</svg>`,
|
|
1466
|
+
loading: `<svg
|
|
1467
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
1468
|
+
width="16"
|
|
1469
|
+
height="16"
|
|
1470
|
+
viewBox="0 0 24 24"
|
|
1471
|
+
fill="none"
|
|
1472
|
+
stroke="#263042"
|
|
1473
|
+
stroke-width="2"
|
|
1474
|
+
stroke-linecap="round"
|
|
1475
|
+
stroke-linejoin="round">
|
|
1476
|
+
<line x1="12" y1="2" x2="12" y2="6"></line>
|
|
1477
|
+
<line x1="12" y1="18" x2="12" y2="22"></line>
|
|
1478
|
+
<line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line>
|
|
1479
|
+
<line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line>
|
|
1480
|
+
<line x1="2" y1="12" x2="6" y2="12"></line>
|
|
1481
|
+
<line x1="18" y1="12" x2="22" y2="12"></line>
|
|
1482
|
+
<line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line>
|
|
1483
|
+
<line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line>
|
|
1484
|
+
</svg>`,
|
|
1485
|
+
};
|
|
1486
|
+
|
|
1487
|
+
class IconsComponent {
|
|
1488
|
+
constructor() {
|
|
1489
|
+
this.sanitizer = inject(DomSanitizer);
|
|
1490
|
+
this.name = input.required();
|
|
1491
|
+
this.size = input(14);
|
|
1492
|
+
this.color = input('currentColor');
|
|
1493
|
+
}
|
|
1494
|
+
ngOnChanges(changes) {
|
|
1495
|
+
if (changes['name']) {
|
|
1496
|
+
const svg = ICONS[this.name()] || '';
|
|
1497
|
+
this.sanitizedIcon = this.sanitizer.bypassSecurityTrustHtml(svg);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: IconsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1501
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.4", type: IconsComponent, isStandalone: true, selector: "dc-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null } }, usesOnChanges: true, ngImport: i0, template: `
|
|
1502
|
+
<span
|
|
1503
|
+
[innerHTML]="sanitizedIcon"
|
|
1504
|
+
[style.display]="'inline-flex'"
|
|
1505
|
+
[style.alignItems]="'center'"
|
|
1506
|
+
[style.justifyContent]="'center'"
|
|
1507
|
+
[style.width.px]="size()"
|
|
1508
|
+
[style.height.px]="size()"
|
|
1509
|
+
[style.color]="color()"></span>
|
|
1510
|
+
`, isInline: true, styles: [":host{display:inline-flex;align-items:center;line-height:0}span{line-height:0}:host ::ng-deep svg{width:100%;height:100%}\n"] }); }
|
|
1511
|
+
}
|
|
1512
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: IconsComponent, decorators: [{
|
|
1513
|
+
type: Component,
|
|
1514
|
+
args: [{ selector: 'dc-icon', template: `
|
|
1515
|
+
<span
|
|
1516
|
+
[innerHTML]="sanitizedIcon"
|
|
1517
|
+
[style.display]="'inline-flex'"
|
|
1518
|
+
[style.alignItems]="'center'"
|
|
1519
|
+
[style.justifyContent]="'center'"
|
|
1520
|
+
[style.width.px]="size()"
|
|
1521
|
+
[style.height.px]="size()"
|
|
1522
|
+
[style.color]="color()"></span>
|
|
1523
|
+
`, standalone: true, styles: [":host{display:inline-flex;align-items:center;line-height:0}span{line-height:0}:host ::ng-deep svg{width:100%;height:100%}\n"] }]
|
|
1524
|
+
}], ctorParameters: () => [] });
|
|
1525
|
+
|
|
1526
|
+
class TextHighlighterComponent {
|
|
1527
|
+
constructor() {
|
|
1528
|
+
this.message = input.required(); // Or input.required<MessageAudio>() if always expected
|
|
1529
|
+
this.highlightedWords = signal([]); // Signal for highlighted words
|
|
1530
|
+
this.isPlaying = signal(false); // Signal for play state
|
|
1531
|
+
// Signals State computed from the input message
|
|
1532
|
+
this.iconState = computed(() => {
|
|
1533
|
+
if (this.isLoading()) {
|
|
1534
|
+
return 'isLoading';
|
|
1535
|
+
}
|
|
1536
|
+
else if (this.isPlaying()) {
|
|
1537
|
+
return 'isPlaying';
|
|
1538
|
+
}
|
|
1539
|
+
else if (this.message()?.audioUrl) {
|
|
1540
|
+
return 'playable';
|
|
1541
|
+
}
|
|
1542
|
+
else {
|
|
1543
|
+
return 'idle';
|
|
1544
|
+
}
|
|
1545
|
+
});
|
|
1546
|
+
this.isLoading = computed(() => this.message()?.isLoading);
|
|
1547
|
+
this.shouldPlayAudio = computed(() => !!this.message() && this.message()?.shouldPlayAudio);
|
|
1548
|
+
// Computed signal for message text
|
|
1549
|
+
this.messageText = computed(() => {
|
|
1550
|
+
console.log('Message text computed', this.message());
|
|
1551
|
+
const msg = this.message(); // Read the input signal
|
|
1552
|
+
return msg?.text || msg?.content || 'N/A';
|
|
1553
|
+
});
|
|
1554
|
+
this.classTag = computed(() => this.message()?.tag);
|
|
1555
|
+
// Computed signal for transcription availability
|
|
1556
|
+
this.hasTranscription = computed(() => {
|
|
1557
|
+
const hasTranscription = !!this.message()?.transcriptionTimestamps && this.message()?.transcriptionTimestamps.length > 0;
|
|
1558
|
+
return hasTranscription;
|
|
1559
|
+
});
|
|
1560
|
+
this.playAudio = output();
|
|
1561
|
+
this.audioCompleted = output();
|
|
1562
|
+
this.audioElement = null; // Audio element for playback
|
|
1563
|
+
this.destroy$ = new Subject(); // Cleanup localsubject
|
|
1564
|
+
// Inject services
|
|
1565
|
+
this.cdr = inject(ChangeDetectorRef); // Keep for now, might remove if template fully signal-driven
|
|
1566
|
+
this.destroyRef = inject(DestroyRef);
|
|
1567
|
+
// Effect to react to message changes and re-initialize
|
|
1568
|
+
effect(() => {
|
|
1569
|
+
const currentMsg = this.message(); // Read the input signal
|
|
1570
|
+
console.log('Input message signal changed:', currentMsg);
|
|
1571
|
+
if (currentMsg) {
|
|
1572
|
+
// Re-run initialization logic whenever the message signal changes
|
|
1573
|
+
this.initializeBasedOnMessage(currentMsg);
|
|
1574
|
+
// Check if shouldPlayAudio flag is set
|
|
1575
|
+
if (currentMsg.shouldPlayAudio) {
|
|
1576
|
+
this.startAudioPlayback();
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
else {
|
|
1580
|
+
// Handle case where message becomes undefined (cleanup?)
|
|
1581
|
+
this.cleanupAudio();
|
|
1582
|
+
this.highlightedWords.set([]);
|
|
1583
|
+
}
|
|
1584
|
+
// No cdr.markForCheck() needed here usually if template bindings use signals/computed
|
|
1585
|
+
});
|
|
1586
|
+
// Keep the effect for highlightedWords for now, might be redundant if template binds directly
|
|
1587
|
+
effect(() => {
|
|
1588
|
+
this.highlightedWords();
|
|
1589
|
+
this.cdr.markForCheck(); // Keep if template uses non-signal bindings for highlighted words
|
|
1590
|
+
});
|
|
1591
|
+
}
|
|
1592
|
+
/**
|
|
1593
|
+
* Track function for ngFor to improve performance
|
|
1594
|
+
*/
|
|
1595
|
+
trackByIndex(index, item) {
|
|
1596
|
+
return index;
|
|
1597
|
+
}
|
|
1598
|
+
ngOnDestroy() {
|
|
1599
|
+
this.destroy$.next();
|
|
1600
|
+
this.destroy$.complete();
|
|
1601
|
+
this.cleanupAudio();
|
|
1602
|
+
}
|
|
1603
|
+
initializeBasedOnMessage(msg) {
|
|
1604
|
+
// Initialize or cleanup audio based on URL presence and change
|
|
1605
|
+
if (msg.audioUrl && (!this.audioElement || this.audioElement.src !== msg.audioUrl)) {
|
|
1606
|
+
this.initializeAudio(msg.audioUrl); // Pass URL
|
|
1607
|
+
}
|
|
1608
|
+
else if (!msg.audioUrl && this.audioElement) {
|
|
1609
|
+
this.cleanupAudio();
|
|
1610
|
+
}
|
|
1611
|
+
else if (!msg.audioUrl) {
|
|
1612
|
+
// No audio URL and no audio element - just log a warning instead of throwing an error
|
|
1613
|
+
console.warn('No audioUrl provided in the message. Audio playback will not be available.');
|
|
1614
|
+
}
|
|
1615
|
+
// Initialize words and sync based on transcription presence
|
|
1616
|
+
if (this.hasTranscription()) {
|
|
1617
|
+
// Use computed signal
|
|
1618
|
+
const timestamps = msg.transcriptionTimestamps || [];
|
|
1619
|
+
this.initializeHighlightedWords(timestamps); // Pass timestamps
|
|
1620
|
+
this.subcribeToAudioSync(timestamps); // Pass timestamps
|
|
1621
|
+
}
|
|
1622
|
+
else {
|
|
1623
|
+
this.subscribeToEndAudio();
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Initialize the audio element and set up event listeners
|
|
1628
|
+
*/
|
|
1629
|
+
initializeAudio(audioUrl) {
|
|
1630
|
+
// Clean up any existing audio element and listeners first
|
|
1631
|
+
this.cleanupAudio();
|
|
1632
|
+
this.audioElement = new Audio(audioUrl); // Use passed URL
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1635
|
+
* Initialize highlighted words from transcription timestamps
|
|
1636
|
+
*/
|
|
1637
|
+
initializeHighlightedWords(timestamps) {
|
|
1638
|
+
const initialWords = timestamps.map((word, index) => ({
|
|
1639
|
+
word: word.word,
|
|
1640
|
+
index,
|
|
1641
|
+
isHighlighted: false,
|
|
1642
|
+
}));
|
|
1643
|
+
this.highlightedWords.set(initialWords);
|
|
1644
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Set up audio synchronization with text
|
|
1647
|
+
*/
|
|
1648
|
+
subcribeToAudioSync(timestamps) {
|
|
1649
|
+
// Accept timestamps
|
|
1650
|
+
if (!this.audioElement) {
|
|
1651
|
+
// Guard against missing audio element
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
// Important: Clean up previous listeners before setting up new ones
|
|
1655
|
+
this.destroy$.next(); // Signal previous subscriptions to complete
|
|
1656
|
+
// Listen to timeupdate events
|
|
1657
|
+
fromEvent(this.audioElement, 'timeupdate')
|
|
1658
|
+
.pipe(takeUntilDestroyed(this.destroyRef), // Use DestroyRef for component lifecycle
|
|
1659
|
+
takeUntil(this.destroy$), // Use manual subject for re-initialization cleanup
|
|
1660
|
+
map(() => this.audioElement?.currentTime || 0))
|
|
1661
|
+
.subscribe((currentTime) => {
|
|
1662
|
+
// Use the passed timestamps array
|
|
1663
|
+
const updatedWords = timestamps.map((word, index) => {
|
|
1664
|
+
const isHighlighted = currentTime >= word.start - 0.15 && currentTime < word.end + 0.15;
|
|
1665
|
+
return { word: word.word, index, isHighlighted };
|
|
1666
|
+
});
|
|
1667
|
+
this.highlightedWords.set(updatedWords);
|
|
1668
|
+
});
|
|
1669
|
+
// Listen to ended event for cleanup
|
|
1670
|
+
fromEvent(this.audioElement, 'ended')
|
|
1671
|
+
.pipe(takeUntilDestroyed(this.destroyRef), takeUntil(this.destroy$))
|
|
1672
|
+
.subscribe(() => {
|
|
1673
|
+
// Reset highlighting when audio ends
|
|
1674
|
+
const resetWords = this.highlightedWords().map((word) => ({
|
|
1675
|
+
// Read current words signal
|
|
1676
|
+
...word,
|
|
1677
|
+
isHighlighted: false,
|
|
1678
|
+
}));
|
|
1679
|
+
this.highlightedWords.set(resetWords);
|
|
1680
|
+
// Set isPlaying to false when audio finishes
|
|
1681
|
+
this.isPlaying.set(false);
|
|
1682
|
+
// Emit audio completed event with the current message
|
|
1683
|
+
const currentMsg = this.message();
|
|
1684
|
+
if (currentMsg) {
|
|
1685
|
+
// Reset the shouldPlayAudio flag
|
|
1686
|
+
currentMsg.shouldPlayAudio = false;
|
|
1687
|
+
this.audioCompleted.emit(currentMsg);
|
|
1688
|
+
}
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
subscribeToEndAudio() {
|
|
1692
|
+
if (!this.audioElement) {
|
|
1693
|
+
return;
|
|
1694
|
+
}
|
|
1695
|
+
fromEvent(this.audioElement, 'ended')
|
|
1696
|
+
.pipe(takeUntilDestroyed(this.destroyRef), takeUntil(this.destroy$))
|
|
1697
|
+
.subscribe(() => {
|
|
1698
|
+
// Set isPlaying to false when audio finishes
|
|
1699
|
+
this.isPlaying.set(false);
|
|
1700
|
+
// Emit audio completed event with the current message
|
|
1701
|
+
const currentMsg = this.message();
|
|
1702
|
+
if (currentMsg) {
|
|
1703
|
+
currentMsg.shouldPlayAudio = false;
|
|
1704
|
+
this.audioCompleted.emit(currentMsg);
|
|
1705
|
+
}
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* Clean up audio element and event listeners
|
|
1710
|
+
*/
|
|
1711
|
+
cleanupAudio() {
|
|
1712
|
+
if (this.audioElement) {
|
|
1713
|
+
this.audioElement.pause();
|
|
1714
|
+
this.audioElement.removeAttribute('src'); // Use removeAttribute for better cleanup
|
|
1715
|
+
this.audioElement = null;
|
|
1716
|
+
this.destroy$.next(); // Ensure listeners tied to this audio instance are cleaned up
|
|
1717
|
+
// Reset highlighting immediately on cleanup
|
|
1718
|
+
const resetWords = this.highlightedWords().map((word) => ({
|
|
1719
|
+
...word,
|
|
1720
|
+
isHighlighted: false,
|
|
1721
|
+
}));
|
|
1722
|
+
this.highlightedWords.set(resetWords);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
/**
|
|
1726
|
+
* Play or pause the audio
|
|
1727
|
+
*/
|
|
1728
|
+
onPlayMessage() {
|
|
1729
|
+
console.log('Playing message audio', this.message().messageId);
|
|
1730
|
+
const currentMsg = this.message(); // Read signal
|
|
1731
|
+
if (this.audioElement) {
|
|
1732
|
+
if (this.audioElement.paused) {
|
|
1733
|
+
this.startAudioPlayback();
|
|
1734
|
+
}
|
|
1735
|
+
else {
|
|
1736
|
+
this.audioElement.pause();
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
else if (currentMsg?.audioUrl) {
|
|
1740
|
+
// Initialization now happens via effect. If element doesn't exist yet,
|
|
1741
|
+
// the effect should create it. Clicking play might need to ensure init runs.
|
|
1742
|
+
// For simplicity, let's re-call initializeAudio here if needed,
|
|
1743
|
+
// though ideally the effect handles it.
|
|
1744
|
+
this.initializeAudio(currentMsg.audioUrl); // Ensure it's created if somehow missed
|
|
1745
|
+
// this.initializeBasedOnMessage(currentMsg);
|
|
1746
|
+
this.startAudioPlayback();
|
|
1747
|
+
}
|
|
1748
|
+
else if (currentMsg) {
|
|
1749
|
+
// If no audio URL is available, emit event to parent component
|
|
1750
|
+
this.playAudio.emit(currentMsg); // Emit the message value
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
/**
|
|
1754
|
+
* Start audio playback and handle any setup needed
|
|
1755
|
+
*/
|
|
1756
|
+
startAudioPlayback() {
|
|
1757
|
+
if (!this.audioElement)
|
|
1758
|
+
return;
|
|
1759
|
+
// Play the audio
|
|
1760
|
+
this.audioElement.play().catch((error) => {
|
|
1761
|
+
console.error('Error playing audio:', error);
|
|
1762
|
+
});
|
|
1763
|
+
this.isPlaying.set(true); // Set play state to true
|
|
1764
|
+
}
|
|
1765
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: TextHighlighterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1766
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: TextHighlighterComponent, isStandalone: true, selector: "dc-text-highlighter", inputs: { message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { playAudio: "playAudio", audioCompleted: "audioCompleted" }, ngImport: i0, template: "<div class=\"audio-text-sync-container\">\n <div> </div>\n <i (click)=\"onPlayMessage()\" class=\"play-button\">\n @if (iconState() === 'isLoading') {\n <!-- <dc-icon class=\"spin-animation\" name=\"loading\"></dc-icon> -->\n <i class=\"spin-animation pi pi-spinner-dotted\"></i>\n } @else if (iconState() === 'isPlaying') {\n <i class=\"pi pi-volume-up\"></i>\n } @else if (iconState() === 'playable') {\n <!-- Display play icon when not playing -->\n <dc-icon name=\"play\"></dc-icon>\n } @else {\n <!-- Nothing-->\n <!-- <dc-icon name=\"play\"></dc-icon> -->\n }\n </i>\n\n <!-- Display transcription with highlighting -->\n <div [class]=\"'text-content ' + classTag()\">\n @if (hasTranscription()) { @for (wordState of highlightedWords(); track trackByIndex($index, wordState)) {\n <span [class.highlight]=\"wordState.isHighlighted\">{{ wordState.word }} </span>\n } } @else {\n <!-- Display regular text content when no transcription is available -->\n <span> {{ messageText() }}</span>\n }\n </div>\n</div>\n", styles: [":host{display:block}.audio-text-sync-container{display:flex;align-items:flex-start;gap:2px;align-items:center}.play-button{cursor:pointer;display:flex;align-items:center;justify-content:center;min-width:24px;margin-top:4px}.play-button:hover{opacity:.8}.text-content{flex:1}.highlight{background-color:#3cd8ff;border-radius:4px}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.em{font-style:italic;color:#6495ed}.strong{font-weight:700;color:inherit}.em_strong{font-weight:700;font-style:italic;color:inherit}\n"], dependencies: [{ kind: "component", type: IconsComponent, selector: "dc-icon", inputs: ["name", "size", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1767
|
+
}
|
|
1768
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: TextHighlighterComponent, decorators: [{
|
|
1769
|
+
type: Component,
|
|
1770
|
+
args: [{ selector: 'dc-text-highlighter', standalone: true, imports: [IconsComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"audio-text-sync-container\">\n <div> </div>\n <i (click)=\"onPlayMessage()\" class=\"play-button\">\n @if (iconState() === 'isLoading') {\n <!-- <dc-icon class=\"spin-animation\" name=\"loading\"></dc-icon> -->\n <i class=\"spin-animation pi pi-spinner-dotted\"></i>\n } @else if (iconState() === 'isPlaying') {\n <i class=\"pi pi-volume-up\"></i>\n } @else if (iconState() === 'playable') {\n <!-- Display play icon when not playing -->\n <dc-icon name=\"play\"></dc-icon>\n } @else {\n <!-- Nothing-->\n <!-- <dc-icon name=\"play\"></dc-icon> -->\n }\n </i>\n\n <!-- Display transcription with highlighting -->\n <div [class]=\"'text-content ' + classTag()\">\n @if (hasTranscription()) { @for (wordState of highlightedWords(); track trackByIndex($index, wordState)) {\n <span [class.highlight]=\"wordState.isHighlighted\">{{ wordState.word }} </span>\n } } @else {\n <!-- Display regular text content when no transcription is available -->\n <span> {{ messageText() }}</span>\n }\n </div>\n</div>\n", styles: [":host{display:block}.audio-text-sync-container{display:flex;align-items:flex-start;gap:2px;align-items:center}.play-button{cursor:pointer;display:flex;align-items:center;justify-content:center;min-width:24px;margin-top:4px}.play-button:hover{opacity:.8}.text-content{flex:1}.highlight{background-color:#3cd8ff;border-radius:4px}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.em{font-style:italic;color:#6495ed}.strong{font-weight:700;color:inherit}.em_strong{font-weight:700;font-style:italic;color:inherit}\n"] }]
|
|
1771
|
+
}], ctorParameters: () => [] });
|
|
1772
|
+
|
|
1773
|
+
class MessageOrchestratorComponent {
|
|
1774
|
+
constructor() {
|
|
1775
|
+
this.agentCardService = inject(CONVERSATION_AI_TOKEN);
|
|
1776
|
+
this.conversationService = inject(ConversationService);
|
|
1777
|
+
this.messages = input.required();
|
|
1778
|
+
this.messageRole = input.required();
|
|
1779
|
+
this.messagesSignal = signal([]);
|
|
1780
|
+
this.playAudio = output();
|
|
1781
|
+
this.audioCompleted = output();
|
|
1782
|
+
// Audio queue management
|
|
1783
|
+
this.audioQueue = [];
|
|
1784
|
+
this.isGenerating = false;
|
|
1785
|
+
this.currentPlayingIndex = null;
|
|
1786
|
+
this.preGenerationInProgress = false;
|
|
1787
|
+
}
|
|
1788
|
+
ngOnInit() {
|
|
1789
|
+
if (this.messageRole() === ChatRole.Assistant) {
|
|
1790
|
+
console.log('MessageOrchestratorComponent initialized', this.messages());
|
|
1791
|
+
// Initialize the queue with all message indices
|
|
1792
|
+
this.initializeAudioQueue();
|
|
1793
|
+
// Start processing the queue - generate the first audio
|
|
1794
|
+
this.processNextInQueue();
|
|
1795
|
+
}
|
|
1796
|
+
else {
|
|
1797
|
+
// since user only have one message, just activate signal
|
|
1798
|
+
this.changeStates(0, this.messages()[0]);
|
|
1847
1799
|
}
|
|
1848
1800
|
}
|
|
1849
|
-
//
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
processAssistantMessage(message, mutate = false) {
|
|
1855
|
-
const conversationSettings = this.conversationSettingsSignal();
|
|
1856
|
-
if (!conversationSettings) {
|
|
1857
|
-
throw new Error('Conversation settings not initialized');
|
|
1801
|
+
// Initialize the queue with all message indices
|
|
1802
|
+
initializeAudioQueue() {
|
|
1803
|
+
const messages = this.messages();
|
|
1804
|
+
if (messages && messages.length > 0) {
|
|
1805
|
+
this.audioQueue = messages.map((_, index) => index);
|
|
1858
1806
|
}
|
|
1859
|
-
return this.messageProcessingService.processMessage(message, conversationSettings, mutate);
|
|
1860
1807
|
}
|
|
1861
|
-
//
|
|
1862
|
-
async
|
|
1863
|
-
|
|
1808
|
+
// Process the next message in the queue
|
|
1809
|
+
async processNextInQueue() {
|
|
1810
|
+
console.log('Processing next message in queue', this.audioQueue);
|
|
1811
|
+
if (this.audioQueue.length === 0 || this.isGenerating) {
|
|
1864
1812
|
return;
|
|
1865
1813
|
}
|
|
1866
|
-
|
|
1867
|
-
const
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1814
|
+
// Get the next index from the queue
|
|
1815
|
+
const nextIndex = this.audioQueue.shift();
|
|
1816
|
+
// Start generating
|
|
1817
|
+
this.isGenerating = true;
|
|
1818
|
+
// Generate audio for the current message
|
|
1819
|
+
await this.generateAudioForIndex(nextIndex);
|
|
1820
|
+
// After generation, mark as not generating
|
|
1821
|
+
this.isGenerating = false;
|
|
1822
|
+
// If there's another message in the queue, pre-generate it
|
|
1823
|
+
this.preGenerateNextIfNeeded();
|
|
1824
|
+
}
|
|
1825
|
+
// Pre-generate the next audio if available
|
|
1826
|
+
async preGenerateNextIfNeeded() {
|
|
1827
|
+
if (this.audioQueue.length > 0 && !this.preGenerationInProgress) {
|
|
1828
|
+
const nextIndex = this.audioQueue[0]; // Peek at next index but don't remove
|
|
1829
|
+
// Mark pre-generation as in progress
|
|
1830
|
+
this.preGenerationInProgress = true;
|
|
1831
|
+
// Mark as loading but don't set shouldPlayAudio yet
|
|
1832
|
+
const messages = this.messages();
|
|
1833
|
+
const message = { ...messages[nextIndex], isLoading: true };
|
|
1834
|
+
this.changeStates(nextIndex, message);
|
|
1835
|
+
// Generate in background
|
|
1836
|
+
const messageAudio = await this.generateAudio(messages[nextIndex], null);
|
|
1837
|
+
messageAudio.isLoading = false;
|
|
1838
|
+
messageAudio.shouldPlayAudio = false; // Don't auto-play yet
|
|
1839
|
+
this.changeStates(nextIndex, messageAudio);
|
|
1840
|
+
// Reset pre-generation flag
|
|
1841
|
+
this.preGenerationInProgress = false;
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
// Generate audio for a specific index
|
|
1845
|
+
async generateAudioForIndex(index) {
|
|
1846
|
+
const messages = this.messages();
|
|
1847
|
+
// Check if this message was already pre-generated (has audioUrl)
|
|
1848
|
+
if (messages[index].audioUrl) {
|
|
1849
|
+
// If it was pre-generated, just set it to play
|
|
1850
|
+
const messageAudio = { ...messages[index], shouldPlayAudio: true };
|
|
1851
|
+
this.changeStates(index, messageAudio);
|
|
1852
|
+
this.currentPlayingIndex = index;
|
|
1874
1853
|
return;
|
|
1875
1854
|
}
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
//
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
this.addMessage(newMessage);
|
|
1901
|
-
this.isThinkingSignal.set(false);
|
|
1902
|
-
}
|
|
1903
|
-
// Helper to get audio playback setting
|
|
1904
|
-
getAudioPlaybackSetting() {
|
|
1905
|
-
const agentCard = this.agentCardSignal();
|
|
1906
|
-
return agentCard?.conversationSettings?.repeatRecording || false;
|
|
1855
|
+
// Otherwise generate it now
|
|
1856
|
+
const message = { ...messages[index], isLoading: true };
|
|
1857
|
+
this.changeStates(index, message);
|
|
1858
|
+
const messageAudio = await this.generateAudio(messages[index], null);
|
|
1859
|
+
messageAudio.isLoading = false;
|
|
1860
|
+
messageAudio.shouldPlayAudio = true;
|
|
1861
|
+
this.changeStates(index, messageAudio);
|
|
1862
|
+
this.currentPlayingIndex = index;
|
|
1863
|
+
}
|
|
1864
|
+
// Handle audio completion event from text-highlighter
|
|
1865
|
+
onAudioCompleted(message) {
|
|
1866
|
+
// Forward the event
|
|
1867
|
+
this.audioCompleted.emit(message);
|
|
1868
|
+
// Reset current playing index
|
|
1869
|
+
this.currentPlayingIndex = null;
|
|
1870
|
+
// If there are more messages in the queue, process the next one
|
|
1871
|
+
if (this.audioQueue.length > 0) {
|
|
1872
|
+
this.processNextInQueue();
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
changeStates(index, messageAudio) {
|
|
1876
|
+
const messages = this.messages();
|
|
1877
|
+
messages[index] = { ...messages[index], ...messageAudio };
|
|
1878
|
+
this.messagesSignal.set([...messages]);
|
|
1907
1879
|
}
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1880
|
+
async generateAudio(message, overwriteText = null) {
|
|
1881
|
+
try {
|
|
1882
|
+
const text = overwriteText || message.text || message.content;
|
|
1883
|
+
const ttsObject = buildObjectTTSRequest({ ...message, text }, { highlightWords: true });
|
|
1884
|
+
const speechAudio = await this.conversationService.getTTSFile(ttsObject);
|
|
1885
|
+
message = extractAudioAndTranscription(message, speechAudio);
|
|
1886
|
+
return message;
|
|
1912
1887
|
}
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
// Re-initialize with current agent card
|
|
1916
|
-
const currentAgentCard = agentCard || this.agentCardSignal();
|
|
1917
|
-
if (currentAgentCard) {
|
|
1918
|
-
// Note: This would need the conversationBuilder to be injected or passed
|
|
1919
|
-
// await this.initConversation(currentAgentCard, conversationBuilder);
|
|
1888
|
+
finally {
|
|
1889
|
+
// No longer emitting audioCompleted here as it's handled by the text-highlighter
|
|
1920
1890
|
}
|
|
1921
1891
|
}
|
|
1922
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
1923
|
-
static { this.ɵ
|
|
1892
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MessageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1893
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: MessageOrchestratorComponent, isStandalone: true, selector: "dc-message-orchestrator", inputs: { messages: { classPropertyName: "messages", publicName: "messages", isSignal: true, isRequired: true, transformFunction: null }, messageRole: { classPropertyName: "messageRole", publicName: "messageRole", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { playAudio: "playAudio", audioCompleted: "audioCompleted" }, ngImport: i0, template: "@for (message of messagesSignal(); track message.messageId) {\n<dc-text-highlighter [message]=\"message\" (playAudio)=\"playAudio.emit($event)\" (audioCompleted)=\"onAudioCompleted($event)\" />\n}\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "component", type: TextHighlighterComponent, selector: "dc-text-highlighter", inputs: ["message"], outputs: ["playAudio", "audioCompleted"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1924
1894
|
}
|
|
1925
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
1926
|
-
type:
|
|
1927
|
-
args: [{
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1895
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MessageOrchestratorComponent, decorators: [{
|
|
1896
|
+
type: Component,
|
|
1897
|
+
args: [{ selector: 'dc-message-orchestrator', imports: [TextHighlighterComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@for (message of messagesSignal(); track message.messageId) {\n<dc-text-highlighter [message]=\"message\" (playAudio)=\"playAudio.emit($event)\" (audioCompleted)=\"onAudioCompleted($event)\" />\n}\n", styles: [":host{display:block}\n"] }]
|
|
1898
|
+
}] });
|
|
1899
|
+
|
|
1900
|
+
class ChatMessageComponent {
|
|
1901
|
+
constructor() {
|
|
1902
|
+
this.chatMessage = input.required();
|
|
1903
|
+
this.chatUserSettings = input(null);
|
|
1904
|
+
this.audioMessage = signal(null);
|
|
1905
|
+
// Computed properties for easier access to signal values
|
|
1906
|
+
this.hasMultiMessages = computed(() => !!this.chatMessage()?.multiMessages);
|
|
1907
|
+
this.multiMessages = computed(() => this.chatMessage()?.multiMessages || []);
|
|
1908
|
+
this.messageTranslation = computed(() => this.chatMessage()?.translation);
|
|
1909
|
+
this.isUserMessage = computed(() => this.chatMessage()?.role === ChatRole.User);
|
|
1910
|
+
// Field initializer for the effect - runs during component creation
|
|
1911
|
+
this.messageEffect = effect(() => {
|
|
1912
|
+
const message = this.chatMessage();
|
|
1913
|
+
if (!message)
|
|
1914
|
+
return;
|
|
1915
|
+
if (message.role === ChatRole.AssistantHelper) {
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
if (message.role === ChatRole.User) {
|
|
1919
|
+
this.audioMessage.set({ ...message });
|
|
1920
|
+
}
|
|
1921
|
+
else if (message.role === ChatRole.Assistant) {
|
|
1922
|
+
if (this.multiMessages().length > 0) {
|
|
1923
|
+
}
|
|
1924
|
+
else {
|
|
1925
|
+
this.audioMessage.set({ ...message });
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatMessageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1931
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: ChatMessageComponent, isStandalone: true, selector: "dc-chat-message", inputs: { chatMessage: { classPropertyName: "chatMessage", publicName: "chatMessage", isSignal: true, isRequired: true, transformFunction: null }, chatUserSettings: { classPropertyName: "chatUserSettings", publicName: "chatUserSettings", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"message-wrapper\" [ngClass]=\"{ 'user-message': isUserMessage(), 'assistant-message': !isUserMessage() }\">\n <div class=\"message-container\">\n <!-- Avatar for assistant messages -->\n\n @if (!isUserMessage()) {\n <div class=\"avatar-container\">\n <div class=\"avatar\">\n <img src=\"assets/defaults/avatar.jpg\" alt=\"User\" class=\"avatar-image\" />\n </div>\n </div>\n }\n\n <!-- Message content -->\n <div class=\"message-bubble\">\n @if (hasMultiMessages()) {\n <dc-message-orchestrator [messages]=\"multiMessages()\" [messageRole]=\"chatMessage().role\"></dc-message-orchestrator>\n } @else {\n <dc-message-orchestrator [messages]=\"[audioMessage()]\" [messageRole]=\"chatMessage().role\"></dc-message-orchestrator>\n }\n\n <!-- Translation if available -->\n @if (messageTranslation()) {\n <div class=\"translation\">\n <hr class=\"divider\" />\n {{ messageTranslation() }}\n </div>\n }\n </div>\n\n <!-- Avatar for user messages -->\n @if (isUserMessage()) {\n <div class=\"avatar-container\">\n <div class=\"avatar user-avatar\">\n <img src=\"assets/defaults/avatar.jpg\" alt=\"User\" class=\"avatar-image\" />\n </div>\n </div>\n }\n </div>\n</div>\n", styles: [":host{display:block;margin-bottom:16px}.message-wrapper{display:flex;width:100%;margin-bottom:12px}.message-container{display:flex;max-width:85%;line-height:1.5}.user-message{justify-content:flex-end}.user-message .message-container{flex-direction:row}.user-message .message-bubble{background-color:#0d5878;color:#fff;border-radius:18px 18px 0;margin-left:8px}.assistant-message{justify-content:flex-start}.assistant-message .message-container{flex-direction:row}.assistant-message .message-bubble{background-color:#f0f0f0;color:#333;border-radius:18px 18px 18px 0;margin-left:8px}.message-bubble{padding:12px 16px;box-shadow:0 1px 2px #0000001a;max-width:calc(100% - 50px);word-wrap:break-word;overflow-wrap:break-word;word-break:break-word;hyphens:auto;min-width:0}.avatar-container{display:flex;align-items:flex-end}.avatar{width:36px;height:36px;border-radius:50%;background-color:#0d5878;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;font-size:14px;overflow:hidden}.avatar-image{width:100%;height:100%;object-fit:cover}.user-avatar{background-color:#ffa77e}::ng-deep .em{color:inherit;font-style:italic}::ng-deep .strong{font-weight:700;color:inherit}::ng-deep .em_strong{font-weight:700;font-style:italic;color:inherit}.translation{margin-top:8px;font-size:small;line-height:1.6;color:#393744;font-style:italic}.divider{margin:.5rem 40px;border-top:1px solid #ffa77e}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: MessageOrchestratorComponent, selector: "dc-message-orchestrator", inputs: ["messages", "messageRole"], outputs: ["playAudio", "audioCompleted"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1932
|
+
}
|
|
1933
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatMessageComponent, decorators: [{
|
|
1934
|
+
type: Component,
|
|
1935
|
+
args: [{ selector: 'dc-chat-message', standalone: true, imports: [CommonModule, MessageOrchestratorComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"message-wrapper\" [ngClass]=\"{ 'user-message': isUserMessage(), 'assistant-message': !isUserMessage() }\">\n <div class=\"message-container\">\n <!-- Avatar for assistant messages -->\n\n @if (!isUserMessage()) {\n <div class=\"avatar-container\">\n <div class=\"avatar\">\n <img src=\"assets/defaults/avatar.jpg\" alt=\"User\" class=\"avatar-image\" />\n </div>\n </div>\n }\n\n <!-- Message content -->\n <div class=\"message-bubble\">\n @if (hasMultiMessages()) {\n <dc-message-orchestrator [messages]=\"multiMessages()\" [messageRole]=\"chatMessage().role\"></dc-message-orchestrator>\n } @else {\n <dc-message-orchestrator [messages]=\"[audioMessage()]\" [messageRole]=\"chatMessage().role\"></dc-message-orchestrator>\n }\n\n <!-- Translation if available -->\n @if (messageTranslation()) {\n <div class=\"translation\">\n <hr class=\"divider\" />\n {{ messageTranslation() }}\n </div>\n }\n </div>\n\n <!-- Avatar for user messages -->\n @if (isUserMessage()) {\n <div class=\"avatar-container\">\n <div class=\"avatar user-avatar\">\n <img src=\"assets/defaults/avatar.jpg\" alt=\"User\" class=\"avatar-image\" />\n </div>\n </div>\n }\n </div>\n</div>\n", styles: [":host{display:block;margin-bottom:16px}.message-wrapper{display:flex;width:100%;margin-bottom:12px}.message-container{display:flex;max-width:85%;line-height:1.5}.user-message{justify-content:flex-end}.user-message .message-container{flex-direction:row}.user-message .message-bubble{background-color:#0d5878;color:#fff;border-radius:18px 18px 0;margin-left:8px}.assistant-message{justify-content:flex-start}.assistant-message .message-container{flex-direction:row}.assistant-message .message-bubble{background-color:#f0f0f0;color:#333;border-radius:18px 18px 18px 0;margin-left:8px}.message-bubble{padding:12px 16px;box-shadow:0 1px 2px #0000001a;max-width:calc(100% - 50px);word-wrap:break-word;overflow-wrap:break-word;word-break:break-word;hyphens:auto;min-width:0}.avatar-container{display:flex;align-items:flex-end}.avatar{width:36px;height:36px;border-radius:50%;background-color:#0d5878;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;font-size:14px;overflow:hidden}.avatar-image{width:100%;height:100%;object-fit:cover}.user-avatar{background-color:#ffa77e}::ng-deep .em{color:inherit;font-style:italic}::ng-deep .strong{font-weight:700;color:inherit}::ng-deep .em_strong{font-weight:700;font-style:italic;color:inherit}.translation{margin-top:8px;font-size:small;line-height:1.6;color:#393744;font-style:italic}.divider{margin:.5rem 40px;border-top:1px solid #ffa77e}\n"] }]
|
|
1936
|
+
}] });
|
|
1934
1937
|
|
|
1935
1938
|
class ChatMessagesListComponent {
|
|
1936
1939
|
constructor() {
|
|
1940
|
+
this.chatUserSettings = input.required();
|
|
1937
1941
|
this.aiIcon = 'assets/default/ai.png';
|
|
1938
1942
|
this.conversationService = inject(ConversationService);
|
|
1943
|
+
this.inputMessages = input.required();
|
|
1939
1944
|
// Get messages and thinking state from the conversation service
|
|
1940
|
-
this.messages =
|
|
1945
|
+
this.messages = computed(() => {
|
|
1946
|
+
// Get the actual array of messages from the signal by calling it as a function
|
|
1947
|
+
const allMessages = this.conversationService.getMessagesSignal()();
|
|
1948
|
+
return allMessages.filter((message) => message.role !== ChatRole.System);
|
|
1949
|
+
});
|
|
1941
1950
|
this.isThinking = this.conversationService.isThinking();
|
|
1942
1951
|
}
|
|
1943
1952
|
// Track messages by their content and role for efficient rendering
|
|
1944
1953
|
trackByMessage(index, message) {
|
|
1945
1954
|
return `${message.role}-${index}-${message.content.substring(0, 20)}`;
|
|
1946
1955
|
}
|
|
1947
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
1948
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.
|
|
1956
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatMessagesListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1957
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: ChatMessagesListComponent, isStandalone: true, selector: "dc-chat-messages-list", inputs: { chatUserSettings: { classPropertyName: "chatUserSettings", publicName: "chatUserSettings", isSignal: true, isRequired: true, transformFunction: null }, inputMessages: { classPropertyName: "inputMessages", publicName: "inputMessages", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"messages-container\">\n @for (message of messages(); track trackByMessage($index, message)) {\n <dc-chat-message [chatMessage]=\"message\" [chatUserSettings]=\"chatUserSettings()\"> </dc-chat-message>\n } @if (isThinking()) {\n <div class=\"thinking-container\">\n <div class=\"thinking-message\">\n <div class=\"thinking-avatar\">\n <img [src]=\"aiIcon\" alt=\"AI thinking\" class=\"avatar-img\" />\n </div>\n <div class=\"thinking-content\">\n <p-skeleton width=\"80%\" height=\"2rem\"></p-skeleton>\n <p-skeleton width=\"60%\" height=\"2rem\"></p-skeleton>\n </div>\n </div>\n </div>\n }\n</div>\n", styles: [".messages-container{display:flex;flex-direction:column;gap:1rem;padding:1rem;overflow-y:auto;max-height:calc(100vh - 200px)}.thinking-container{padding:.5rem 0}.thinking-message{display:flex;gap:1rem;align-items:flex-start}.thinking-avatar{width:40px;height:40px;border-radius:50%;overflow:hidden;flex-shrink:0}.avatar-img{width:100%;height:100%;object-fit:cover}.thinking-content{flex:1;display:flex;flex-direction:column;gap:.5rem}\n"], dependencies: [{ kind: "component", type: ChatMessageComponent, selector: "dc-chat-message", inputs: ["chatMessage", "chatUserSettings"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1$2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "style", "shape", "animation", "borderRadius", "size", "width", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1949
1958
|
}
|
|
1950
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
1959
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatMessagesListComponent, decorators: [{
|
|
1951
1960
|
type: Component,
|
|
1952
|
-
args: [{ selector: 'dc-chat-messages-list', standalone: true, imports: [
|
|
1953
|
-
}]
|
|
1954
|
-
type: Input
|
|
1955
|
-
}], thinkingImg: [{
|
|
1956
|
-
type: Input
|
|
1957
|
-
}] } });
|
|
1961
|
+
args: [{ selector: 'dc-chat-messages-list', standalone: true, imports: [ChatMessageComponent, SkeletonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"messages-container\">\n @for (message of messages(); track trackByMessage($index, message)) {\n <dc-chat-message [chatMessage]=\"message\" [chatUserSettings]=\"chatUserSettings()\"> </dc-chat-message>\n } @if (isThinking()) {\n <div class=\"thinking-container\">\n <div class=\"thinking-message\">\n <div class=\"thinking-avatar\">\n <img [src]=\"aiIcon\" alt=\"AI thinking\" class=\"avatar-img\" />\n </div>\n <div class=\"thinking-content\">\n <p-skeleton width=\"80%\" height=\"2rem\"></p-skeleton>\n <p-skeleton width=\"60%\" height=\"2rem\"></p-skeleton>\n </div>\n </div>\n </div>\n }\n</div>\n", styles: [".messages-container{display:flex;flex-direction:column;gap:1rem;padding:1rem;overflow-y:auto;max-height:calc(100vh - 200px)}.thinking-container{padding:.5rem 0}.thinking-message{display:flex;gap:1rem;align-items:flex-start}.thinking-avatar{width:40px;height:40px;border-radius:50%;overflow:hidden;flex-shrink:0}.avatar-img{width:100%;height:100%;object-fit:cover}.thinking-content{flex:1;display:flex;flex-direction:column;gap:.5rem}\n"] }]
|
|
1962
|
+
}] });
|
|
1958
1963
|
|
|
1959
1964
|
const SpeedDescription = {
|
|
1960
1965
|
1: 'Muy Lento',
|
|
@@ -1967,10 +1972,10 @@ class SpeedDescPipe {
|
|
|
1967
1972
|
transform(speed) {
|
|
1968
1973
|
return SpeedDescription[speed];
|
|
1969
1974
|
}
|
|
1970
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
1971
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.
|
|
1975
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: SpeedDescPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
1976
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.4", ngImport: i0, type: SpeedDescPipe, isStandalone: true, name: "speedDisplay" }); }
|
|
1972
1977
|
}
|
|
1973
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
1978
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: SpeedDescPipe, decorators: [{
|
|
1974
1979
|
type: Pipe,
|
|
1975
1980
|
args: [{
|
|
1976
1981
|
name: 'speedDisplay',
|
|
@@ -1987,10 +1992,10 @@ class TruncatePipe {
|
|
|
1987
1992
|
}
|
|
1988
1993
|
return value.substring(0, length) + suffix;
|
|
1989
1994
|
}
|
|
1990
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
1991
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.
|
|
1995
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: TruncatePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
1996
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.4", ngImport: i0, type: TruncatePipe, isStandalone: true, name: "truncate" }); }
|
|
1992
1997
|
}
|
|
1993
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
1998
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: TruncatePipe, decorators: [{
|
|
1994
1999
|
type: Pipe,
|
|
1995
2000
|
args: [{
|
|
1996
2001
|
name: 'truncate',
|
|
@@ -2012,9 +2017,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImpor
|
|
|
2012
2017
|
* ```
|
|
2013
2018
|
*/
|
|
2014
2019
|
class ProviderSelectorComponent {
|
|
2015
|
-
constructor(
|
|
2016
|
-
this.conversationCardAIService =
|
|
2017
|
-
this.cdr =
|
|
2020
|
+
constructor() {
|
|
2021
|
+
this.conversationCardAIService = inject(CONVERSATION_AI_TOKEN);
|
|
2022
|
+
this.cdr = inject(ChangeDetectorRef);
|
|
2018
2023
|
/** Flag indicating whether the models are currently being loaded */
|
|
2019
2024
|
this.isLoadingModels = true;
|
|
2020
2025
|
/** Array of available models for the selected provider */
|
|
@@ -2045,26 +2050,23 @@ class ProviderSelectorComponent {
|
|
|
2045
2050
|
});
|
|
2046
2051
|
}
|
|
2047
2052
|
}
|
|
2048
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
2049
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.
|
|
2053
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ProviderSelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2054
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: ProviderSelectorComponent, isStandalone: true, selector: "dc-provider-selector", inputs: { parentForm: "parentForm" }, ngImport: i0, template: "<div>\n <hr />\n <b>Admin Section</b>\n <br />\n\n <b>Providers:</b>\n <div style=\"display: flex; gap: 10px\" [formGroup]=\"parentForm\">\n <div class=\"space\">\n <p-radioButton value=\"groq\" formControlName=\"provider\"></p-radioButton>\n <label class=\"space\">Groq</label>\n </div>\n\n <div class=\"space\">\n <p-radioButton value=\"openai\" formControlName=\"provider\"></p-radioButton>\n <label>Open AI</label>\n </div>\n\n <div class=\"space\">\n <p-radioButton value=\"google\" formControlName=\"provider\"></p-radioButton>\n <label class=\"space\">Google</label>\n </div>\n\n <div class=\"space\">\n <p-radioButton value=\"openrouter\" formControlName=\"provider\"></p-radioButton>\n <label class=\"space\">Open Router</label>\n </div>\n </div>\n\n <b>Modelo: </b>\n <span pTooltip=\"Modelo Seleccionado\">{{ parentForm.controls.modelName.value }}</span>\n @if (parentForm.controls.provider.value) { @if(isLoadingModels) {\n <p-skeleton height=\"200px\" width=\"100%\"></p-skeleton>\n } @else {\n <p-table [value]=\"modelnames\" stripedRows [size]=\"'small'\" [paginator]=\"true\" [rows]=\"12\" [formGroup]=\"parentForm\">\n <ng-template pTemplate=\"header\">\n <tr>\n <th></th>\n <th>Name</th>\n <th>$$Prompt M</th>\n <th>$$Completion M</th>\n <th>$$Cost 90/10 M</th>\n <th>Created</th>\n </tr>\n </ng-template>\n <ng-template pTemplate=\"body\" let-model>\n <tr [pTooltip]=\"model.description | truncate : 200\" tooltipPosition=\"top\">\n <td><p-radioButton [value]=\"model.id\" formControlName=\"modelName\"></p-radioButton></td>\n <td>{{ model.name }}</td>\n <td>${{ +model.pricing?.prompt * 1000000 | number : '1.2-2' }}</td>\n <td>${{ +model.pricing?.completion * 1000000 | number : '1.2-2' }}</td>\n <td>${{ +model.pricing?.prompt * 1000000 * 0.9 + +model.pricing?.completion * 1000000 * 0.1 | number : '1.2-2' }}</td>\n <td>{{ model.created * 1000 | date : 'dd/MM/yyyy' }}</td>\n </tr>\n </ng-template>\n </p-table>\n } }\n</div>\n", styles: [":host{display:block}.space{display:flex;gap:2px}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: RadioButtonModule }, { kind: "component", type: i2$2.RadioButton, selector: "p-radioButton, p-radiobutton, p-radio-button", inputs: ["value", "formControlName", "name", "disabled", "variant", "size", "tabindex", "inputId", "ariaLabelledBy", "ariaLabel", "style", "styleClass", "autofocus", "binary"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i3$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: TableModule }, { kind: "component", type: i4.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "style", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "scrollDirection", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "responsive", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "autoLayout", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "virtualRowHeight", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1$2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "style", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "pipe", type: TruncatePipe, name: "truncate" }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "pipe", type: DecimalPipe, name: "number" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2050
2055
|
}
|
|
2051
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
2056
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ProviderSelectorComponent, decorators: [{
|
|
2052
2057
|
type: Component,
|
|
2053
|
-
args: [{ selector: 'dc-provider-selector', standalone: true, imports: [
|
|
2054
|
-
}], ctorParameters: () => [
|
|
2055
|
-
type: Inject,
|
|
2056
|
-
args: [CONVERSATION_AI_TOKEN]
|
|
2057
|
-
}] }, { type: i0.ChangeDetectorRef }], propDecorators: { parentForm: [{
|
|
2058
|
+
args: [{ selector: 'dc-provider-selector', standalone: true, imports: [ReactiveFormsModule, RadioButtonModule, TableModule, SkeletonModule, TooltipModule, TruncatePipe, DatePipe, DecimalPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div>\n <hr />\n <b>Admin Section</b>\n <br />\n\n <b>Providers:</b>\n <div style=\"display: flex; gap: 10px\" [formGroup]=\"parentForm\">\n <div class=\"space\">\n <p-radioButton value=\"groq\" formControlName=\"provider\"></p-radioButton>\n <label class=\"space\">Groq</label>\n </div>\n\n <div class=\"space\">\n <p-radioButton value=\"openai\" formControlName=\"provider\"></p-radioButton>\n <label>Open AI</label>\n </div>\n\n <div class=\"space\">\n <p-radioButton value=\"google\" formControlName=\"provider\"></p-radioButton>\n <label class=\"space\">Google</label>\n </div>\n\n <div class=\"space\">\n <p-radioButton value=\"openrouter\" formControlName=\"provider\"></p-radioButton>\n <label class=\"space\">Open Router</label>\n </div>\n </div>\n\n <b>Modelo: </b>\n <span pTooltip=\"Modelo Seleccionado\">{{ parentForm.controls.modelName.value }}</span>\n @if (parentForm.controls.provider.value) { @if(isLoadingModels) {\n <p-skeleton height=\"200px\" width=\"100%\"></p-skeleton>\n } @else {\n <p-table [value]=\"modelnames\" stripedRows [size]=\"'small'\" [paginator]=\"true\" [rows]=\"12\" [formGroup]=\"parentForm\">\n <ng-template pTemplate=\"header\">\n <tr>\n <th></th>\n <th>Name</th>\n <th>$$Prompt M</th>\n <th>$$Completion M</th>\n <th>$$Cost 90/10 M</th>\n <th>Created</th>\n </tr>\n </ng-template>\n <ng-template pTemplate=\"body\" let-model>\n <tr [pTooltip]=\"model.description | truncate : 200\" tooltipPosition=\"top\">\n <td><p-radioButton [value]=\"model.id\" formControlName=\"modelName\"></p-radioButton></td>\n <td>{{ model.name }}</td>\n <td>${{ +model.pricing?.prompt * 1000000 | number : '1.2-2' }}</td>\n <td>${{ +model.pricing?.completion * 1000000 | number : '1.2-2' }}</td>\n <td>${{ +model.pricing?.prompt * 1000000 * 0.9 + +model.pricing?.completion * 1000000 * 0.1 | number : '1.2-2' }}</td>\n <td>{{ model.created * 1000 | date : 'dd/MM/yyyy' }}</td>\n </tr>\n </ng-template>\n </p-table>\n } }\n</div>\n", styles: [":host{display:block}.space{display:flex;gap:2px}\n"] }]
|
|
2059
|
+
}], ctorParameters: () => [], propDecorators: { parentForm: [{
|
|
2058
2060
|
type: Input
|
|
2059
2061
|
}] } });
|
|
2060
2062
|
|
|
2061
2063
|
class DCConversationUserChatSettingsComponent {
|
|
2062
|
-
constructor(
|
|
2063
|
-
this.dialogRef =
|
|
2064
|
-
this.fb =
|
|
2065
|
-
this.conversationCardAIService =
|
|
2064
|
+
constructor() {
|
|
2065
|
+
this.dialogRef = inject(DynamicDialogRef);
|
|
2066
|
+
this.fb = inject(FormBuilder);
|
|
2067
|
+
this.conversationCardAIService = inject(CONVERSATION_AI_TOKEN);
|
|
2066
2068
|
this.isLoadingModels = true;
|
|
2067
|
-
this.showFeature = {
|
|
2069
|
+
this.showFeature = input({
|
|
2068
2070
|
synthVoice: true,
|
|
2069
2071
|
highlightWords: true,
|
|
2070
2072
|
speed: true,
|
|
@@ -2072,8 +2074,8 @@ class DCConversationUserChatSettingsComponent {
|
|
|
2072
2074
|
superHearing: true,
|
|
2073
2075
|
fixGrammar: true,
|
|
2074
2076
|
autoTranslate: true,
|
|
2075
|
-
};
|
|
2076
|
-
this.onSettingsChange =
|
|
2077
|
+
});
|
|
2078
|
+
this.onSettingsChange = output();
|
|
2077
2079
|
this.textEngines = Object.values(TextEngines);
|
|
2078
2080
|
this.voiceTTSOptions = Object.values(VoiceTTSOptions);
|
|
2079
2081
|
this.textEngineOptions = TextEngineOptions;
|
|
@@ -2118,13 +2120,12 @@ class DCConversationUserChatSettingsComponent {
|
|
|
2118
2120
|
this.dialogRef.close(this.form.value);
|
|
2119
2121
|
}
|
|
2120
2122
|
}
|
|
2121
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
2122
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.
|
|
2123
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCConversationUserChatSettingsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2124
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: DCConversationUserChatSettingsComponent, isStandalone: true, selector: "dc-chat-settings-dialog", inputs: { showFeature: { classPropertyName: "showFeature", publicName: "showFeature", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSettingsChange: "onSettingsChange" }, viewQueries: [{ propertyName: "tooltipRef", first: true, predicate: ["tooltipRef"], descendants: true }], ngImport: i0, template: "<div class=\"dialog-container\">\n <form [formGroup]=\"form\">\n @if (showFeature().synthVoice) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"synthVoice\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.synthVoice.disabled\">Escuchar Voz</span>\n <br />\n <small>Desmarca si solo quieres leer texto</small>\n </p>\n </div>\n }\n\n @if (showFeature().highlightWords) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"highlightWords\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Narraci\u00F3n de texto</span>\n <br />\n <small>Remarca las palabras como se van pronuncionando</small>\n </p>\n </div>\n }\n\n @if (showFeature().speed) {\n <div class=\"settings-section\">\n <p>\n Velocidad ({{ form.controls.speed.value | speedDisplay }})\n <br />\n <p-rating formControlName=\"speed\">\n <ng-template pTemplate=\"onicon\">\n <i class=\"pi pi-caret-right\"></i>\n </ng-template>\n <ng-template pTemplate=\"officon\">\n <i class=\"pi pi-circle\"></i>\n </ng-template>\n </p-rating>\n </p>\n </div>\n }\n\n @if (showFeature().realTime) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"realTime\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.realTime.disabled\">Tiempo real</span>\n <br />\n <small>No tienes que presionar el microphono, comenzar\u00E1 a grabar en cuanto la AI termine de hablar, cierra el chat para finalizar conversaci\u00F3n.</small>\n </p>\n </div>\n }\n\n @if (showFeature().realTime) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"repeatRecording\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Reproducir mi grabaci\u00F3n</span>\n <br />\n <small>Escucha tu dialogo, despu\u00E9s de grabar, te ayudar\u00E1 a notar tus errores.</small>\n </p>\n </div>\n }\n\n @if (showFeature().superHearing) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"superHearing\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Super O\u00EDdo \uD83E\uDDBE</span>\n <br />\n <small>Tu audio se procesa en el servidor para mejor efectividad, si no usa el navegador.</small>\n </p>\n </div>\n }\n\n @if (showFeature().fixGrammar) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"fixGrammar\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.fixGrammar.disabled\">Corregir gram\u00E1tica</span>\n <br />\n <small>La ai corrige tu forma de hablar/escribir y te retrolimenta de tus errores</small>\n </p>\n </div>\n }\n\n @if (showFeature().autoTranslate) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"autoTranslate\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.autoTranslate.disabled\">Mostrar Traducciones</span>\n <br />\n <small>Texto adicional con la traducci\u00F3n</small>\n </p>\n </div>\n }\n\n @if (showFeature().autoTranslate) {\n <div class=\"voice-selection\">\n <span>Voz Preferencial:</span>\n <br />\n <p-radioButton value=\"random\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Aleatorio</label>\n <p-radioButton value=\"randomMan\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Hombre</label>\n <p-radioButton value=\"randomWoman\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Mujer</label>\n </div>\n }\n\n @if(isAdmin) {\n <div>\n <hr />\n <b>Admin Section</b>\n <br />\n\n <b>Modelo:</b>\n\n <dc-provider-selector [parentForm]=\"form.controls.model\"></dc-provider-selector>\n </div>\n }\n\n <div class=\"button-group\">\n <p-button (click)=\"saveSettings()\" label=\"Guardar cambios\"></p-button>\n <p-button (click)=\"close()\" label=\"Cancelar\" styleClass=\"p-button-secondary\"></p-button>\n </div>\n </form>\n</div>\n", styles: [".dialog-container{padding:20px;background:#fff;border-radius:8px;min-width:300px;max-width:500px}.dialog-content{margin:20px 0}.dialog-actions{display:flex;justify-content:flex-end}.settings-section{margin-bottom:20px}.settings-section label{display:block;margin-bottom:5px;font-weight:700}.settings-section small{display:block;color:#666;margin-top:2px}.voice-selection{margin:15px 0}.voice-selection label{margin-right:15px}.button-group{margin-top:20px;display:flex;gap:10px;justify-content:flex-end}button{padding:8px 16px;border-radius:4px;border:none;cursor:pointer}button:first-child{background-color:#007bff;color:#fff}button:last-child{background-color:#6c757d;color:#fff}.space{margin-left:3px}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i2$3.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["value", "name", "disabled", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "style", "inputStyle", "styleClass", "inputClass", "indeterminate", "size", "formControl", "checkboxIcon", "readonly", "required", "autofocus", "trueValue", "falseValue", "variant"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "directive", type: i3$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: SliderModule }, { kind: "ngmodule", type: RadioButtonModule }, { kind: "component", type: i2$2.RadioButton, selector: "p-radioButton, p-radiobutton, p-radio-button", inputs: ["value", "formControlName", "name", "disabled", "variant", "size", "tabindex", "inputId", "ariaLabelledBy", "ariaLabel", "style", "styleClass", "autofocus", "binary"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "pipe", type: SpeedDescPipe, name: "speedDisplay" }, { kind: "ngmodule", type: RatingModule }, { kind: "component", type: i6$1.Rating, selector: "p-rating", inputs: ["disabled", "readonly", "stars", "iconOnClass", "iconOnStyle", "iconOffClass", "iconOffStyle", "autofocus"], outputs: ["onRate", "onCancel", "onFocus", "onBlur"] }, { kind: "ngmodule", type: TableModule }, { kind: "ngmodule", type: BadgeModule }, { kind: "ngmodule", type: SkeletonModule }, { kind: "ngmodule", type: TooltipModule }, { kind: "component", type: ProviderSelectorComponent, selector: "dc-provider-selector", inputs: ["parentForm"] }] }); }
|
|
2123
2125
|
}
|
|
2124
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
2126
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCConversationUserChatSettingsComponent, decorators: [{
|
|
2125
2127
|
type: Component,
|
|
2126
2128
|
args: [{ selector: 'dc-chat-settings-dialog', standalone: true, imports: [
|
|
2127
|
-
CommonModule,
|
|
2128
2129
|
ReactiveFormsModule,
|
|
2129
2130
|
CheckboxModule,
|
|
2130
2131
|
SliderModule,
|
|
@@ -2136,16 +2137,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImpor
|
|
|
2136
2137
|
BadgeModule,
|
|
2137
2138
|
SkeletonModule,
|
|
2138
2139
|
TooltipModule,
|
|
2139
|
-
ProviderSelectorComponent
|
|
2140
|
-
], template: "<div class=\"dialog-container\">\n <form [formGroup]=\"form\">\n <div class=\"settings-section\"
|
|
2141
|
-
}], ctorParameters: () => [
|
|
2142
|
-
type: Inject,
|
|
2143
|
-
args: [CONVERSATION_AI_TOKEN]
|
|
2144
|
-
}] }], propDecorators: { showFeature: [{
|
|
2145
|
-
type: Input
|
|
2146
|
-
}], onSettingsChange: [{
|
|
2147
|
-
type: Output
|
|
2148
|
-
}], tooltipRef: [{
|
|
2140
|
+
ProviderSelectorComponent
|
|
2141
|
+
], template: "<div class=\"dialog-container\">\n <form [formGroup]=\"form\">\n @if (showFeature().synthVoice) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"synthVoice\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.synthVoice.disabled\">Escuchar Voz</span>\n <br />\n <small>Desmarca si solo quieres leer texto</small>\n </p>\n </div>\n }\n\n @if (showFeature().highlightWords) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"highlightWords\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Narraci\u00F3n de texto</span>\n <br />\n <small>Remarca las palabras como se van pronuncionando</small>\n </p>\n </div>\n }\n\n @if (showFeature().speed) {\n <div class=\"settings-section\">\n <p>\n Velocidad ({{ form.controls.speed.value | speedDisplay }})\n <br />\n <p-rating formControlName=\"speed\">\n <ng-template pTemplate=\"onicon\">\n <i class=\"pi pi-caret-right\"></i>\n </ng-template>\n <ng-template pTemplate=\"officon\">\n <i class=\"pi pi-circle\"></i>\n </ng-template>\n </p-rating>\n </p>\n </div>\n }\n\n @if (showFeature().realTime) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"realTime\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.realTime.disabled\">Tiempo real</span>\n <br />\n <small>No tienes que presionar el microphono, comenzar\u00E1 a grabar en cuanto la AI termine de hablar, cierra el chat para finalizar conversaci\u00F3n.</small>\n </p>\n </div>\n }\n\n @if (showFeature().realTime) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"repeatRecording\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Reproducir mi grabaci\u00F3n</span>\n <br />\n <small>Escucha tu dialogo, despu\u00E9s de grabar, te ayudar\u00E1 a notar tus errores.</small>\n </p>\n </div>\n }\n\n @if (showFeature().superHearing) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"superHearing\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Super O\u00EDdo \uD83E\uDDBE</span>\n <br />\n <small>Tu audio se procesa en el servidor para mejor efectividad, si no usa el navegador.</small>\n </p>\n </div>\n }\n\n @if (showFeature().fixGrammar) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"fixGrammar\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.fixGrammar.disabled\">Corregir gram\u00E1tica</span>\n <br />\n <small>La ai corrige tu forma de hablar/escribir y te retrolimenta de tus errores</small>\n </p>\n </div>\n }\n\n @if (showFeature().autoTranslate) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"autoTranslate\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.autoTranslate.disabled\">Mostrar Traducciones</span>\n <br />\n <small>Texto adicional con la traducci\u00F3n</small>\n </p>\n </div>\n }\n\n @if (showFeature().autoTranslate) {\n <div class=\"voice-selection\">\n <span>Voz Preferencial:</span>\n <br />\n <p-radioButton value=\"random\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Aleatorio</label>\n <p-radioButton value=\"randomMan\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Hombre</label>\n <p-radioButton value=\"randomWoman\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Mujer</label>\n </div>\n }\n\n @if(isAdmin) {\n <div>\n <hr />\n <b>Admin Section</b>\n <br />\n\n <b>Modelo:</b>\n\n <dc-provider-selector [parentForm]=\"form.controls.model\"></dc-provider-selector>\n </div>\n }\n\n <div class=\"button-group\">\n <p-button (click)=\"saveSettings()\" label=\"Guardar cambios\"></p-button>\n <p-button (click)=\"close()\" label=\"Cancelar\" styleClass=\"p-button-secondary\"></p-button>\n </div>\n </form>\n</div>\n", styles: [".dialog-container{padding:20px;background:#fff;border-radius:8px;min-width:300px;max-width:500px}.dialog-content{margin:20px 0}.dialog-actions{display:flex;justify-content:flex-end}.settings-section{margin-bottom:20px}.settings-section label{display:block;margin-bottom:5px;font-weight:700}.settings-section small{display:block;color:#666;margin-top:2px}.voice-selection{margin:15px 0}.voice-selection label{margin-right:15px}.button-group{margin-top:20px;display:flex;gap:10px;justify-content:flex-end}button{padding:8px 16px;border-radius:4px;border:none;cursor:pointer}button:first-child{background-color:#007bff;color:#fff}button:last-child{background-color:#6c757d;color:#fff}.space{margin-left:3px}\n"] }]
|
|
2142
|
+
}], ctorParameters: () => [], propDecorators: { tooltipRef: [{
|
|
2149
2143
|
type: ViewChild,
|
|
2150
2144
|
args: ['tooltipRef']
|
|
2151
2145
|
}] } });
|
|
@@ -2163,179 +2157,31 @@ const DefaultEvaluatorAgentCard = {
|
|
|
2163
2157
|
sources: [],
|
|
2164
2158
|
};
|
|
2165
2159
|
|
|
2166
|
-
function extractJsonFromResponse(content) {
|
|
2167
|
-
const jsonMatch = content.match(/\{[\s\S]*?\}/); // Match everything between first { and }
|
|
2168
|
-
if (!jsonMatch)
|
|
2169
|
-
return null;
|
|
2170
|
-
try {
|
|
2171
|
-
return JSON.parse(jsonMatch[0]);
|
|
2172
|
-
}
|
|
2173
|
-
catch (error) {
|
|
2174
|
-
console.error('Error parsing JSON:', error);
|
|
2175
|
-
return null;
|
|
2176
|
-
}
|
|
2177
|
-
}
|
|
2178
|
-
|
|
2179
|
-
class EvaluationService {
|
|
2180
|
-
constructor(agentCardService) {
|
|
2181
|
-
this.agentCardService = agentCardService;
|
|
2182
|
-
this.scoreSignal = signal(10);
|
|
2183
|
-
this.evaluationResultSignal = signal(null);
|
|
2184
|
-
}
|
|
2185
|
-
// Get score as a signal
|
|
2186
|
-
getScore() {
|
|
2187
|
-
return this.scoreSignal;
|
|
2188
|
-
}
|
|
2189
|
-
// Get evaluation result as a signal
|
|
2190
|
-
getEvaluationResult() {
|
|
2191
|
-
return this.evaluationResultSignal;
|
|
2192
|
-
}
|
|
2193
|
-
// Evaluate conversation
|
|
2194
|
-
async evaluateConversation(messages, evaluator) {
|
|
2195
|
-
// Filter conversation to only include user and assistant messages
|
|
2196
|
-
const conversationMessages = messages.filter((message) => [ChatRole.User, ChatRole.Assistant].includes(message.role));
|
|
2197
|
-
// Check if there are any user messages to evaluate
|
|
2198
|
-
const userMessages = conversationMessages.filter((message) => message.role === ChatRole.User);
|
|
2199
|
-
if (userMessages.length <= 0) {
|
|
2200
|
-
console.log('No messages to evaluate', conversationMessages);
|
|
2201
|
-
return;
|
|
2202
|
-
}
|
|
2203
|
-
// Format conversation for evaluation
|
|
2204
|
-
const conversationMessagesString = conversationMessages
|
|
2205
|
-
.map((message) => `${message.role}: ${message.content}`)
|
|
2206
|
-
.join('\n');
|
|
2207
|
-
// Create evaluation prompt
|
|
2208
|
-
const instructions = `
|
|
2209
|
-
Please replay to this task:
|
|
2210
|
-
${evaluator.task}
|
|
2211
|
-
This is the conversation history:
|
|
2212
|
-
${conversationMessagesString}
|
|
2213
|
-
and give the response in next JSON format.
|
|
2214
|
-
${evaluator.expectedResponseType}
|
|
2215
|
-
`;
|
|
2216
|
-
// Send evaluation request
|
|
2217
|
-
const evaluationMessages = [{ content: instructions, role: ChatRole.User }];
|
|
2218
|
-
const response = await this.agentCardService.callChatCompletion({ messages: evaluationMessages });
|
|
2219
|
-
// Extract JSON from response
|
|
2220
|
-
const jsonData = extractJsonFromResponse(response.content);
|
|
2221
|
-
this.evaluationResultSignal.set(jsonData);
|
|
2222
|
-
// Update score if available
|
|
2223
|
-
if (jsonData.score) {
|
|
2224
|
-
if (jsonData.score <= 3) {
|
|
2225
|
-
this.updateScore(jsonData.score * 10);
|
|
2226
|
-
}
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
// Update score with limits
|
|
2230
|
-
updateScore(additionalScore) {
|
|
2231
|
-
this.scoreSignal.update(currentScore => {
|
|
2232
|
-
const newScore = currentScore + additionalScore;
|
|
2233
|
-
return newScore > 100 ? 100 : newScore;
|
|
2234
|
-
});
|
|
2235
|
-
}
|
|
2236
|
-
// Reset score
|
|
2237
|
-
resetScore() {
|
|
2238
|
-
this.scoreSignal.set(10);
|
|
2239
|
-
}
|
|
2240
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: EvaluationService, deps: [{ token: CONVERSATION_AI_TOKEN }], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2241
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: EvaluationService, providedIn: 'root' }); }
|
|
2242
|
-
}
|
|
2243
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: EvaluationService, decorators: [{
|
|
2244
|
-
type: Injectable,
|
|
2245
|
-
args: [{
|
|
2246
|
-
providedIn: 'root',
|
|
2247
|
-
}]
|
|
2248
|
-
}], ctorParameters: () => [{ type: AgentCardsAbstractService, decorators: [{
|
|
2249
|
-
type: Inject,
|
|
2250
|
-
args: [CONVERSATION_AI_TOKEN]
|
|
2251
|
-
}] }] });
|
|
2252
|
-
|
|
2253
2160
|
class DCChatComponent {
|
|
2254
|
-
constructor(
|
|
2255
|
-
this.
|
|
2256
|
-
this.
|
|
2257
|
-
this.
|
|
2258
|
-
this.
|
|
2259
|
-
this.
|
|
2260
|
-
|
|
2261
|
-
this.evaluatorAgentCard = DefaultEvaluatorAgentCard;
|
|
2262
|
-
this.parseDict = {};
|
|
2263
|
-
this.sendMessage =
|
|
2161
|
+
constructor() {
|
|
2162
|
+
this.conversationBuilder = inject(DCConversationPromptBuilderService);
|
|
2163
|
+
this.dialogService = inject(DialogService);
|
|
2164
|
+
this.conversationService = inject(ConversationService);
|
|
2165
|
+
this.userDataExchange = inject(USER_DATA_EXCHANGE);
|
|
2166
|
+
this.chatUserSettings = this.userDataExchange.getUserChatSettings(); // Default to user data exchange
|
|
2167
|
+
// TODO: those are optional think if i need them,
|
|
2168
|
+
this.evaluatorAgentCard = input(DefaultEvaluatorAgentCard);
|
|
2169
|
+
this.parseDict = input({});
|
|
2170
|
+
this.sendMessage = output(); // notifies whatever happened inside the chat
|
|
2264
2171
|
this.micSettings = { useWhisper: true, lang: 'en' };
|
|
2265
|
-
this.imageUser = `assets/default/user.svg`;
|
|
2266
|
-
this.thinkingImg = `assets/default/thinking.svg`;
|
|
2267
|
-
this.aiIcon = `assets/default/ai.svg`;
|
|
2268
2172
|
this.isInfoVisible = false;
|
|
2269
|
-
this.isChatSettingsVisible = false;
|
|
2270
|
-
this.isUserTalking = false;
|
|
2271
|
-
this.isGettingTranscription = false;
|
|
2272
2173
|
this.isAdmin = true;
|
|
2273
2174
|
// Get reactive state from services
|
|
2274
|
-
|
|
2275
|
-
this.
|
|
2276
|
-
this.score = this.evaluationService.getScore();
|
|
2175
|
+
// public messages = this.conversationService.getMessages();
|
|
2176
|
+
this.messages = signal([]);
|
|
2277
2177
|
}
|
|
2278
2178
|
async ngOnInit() {
|
|
2279
|
-
|
|
2280
|
-
// Get user settings if not provided
|
|
2281
|
-
if (!this.chatUserSettings) {
|
|
2282
|
-
this.chatUserSettings = await this.agentCardService.getConversationUserChatSettings();
|
|
2283
|
-
}
|
|
2284
|
-
// Initialize conversation
|
|
2285
|
-
await this.conversationService.initConversation(this.agentCard, this.conversationBuilder, this.parseDict);
|
|
2179
|
+
this.conversationService.initConversation(this.agentCard);
|
|
2286
2180
|
}
|
|
2287
2181
|
ngOnDestroy() {
|
|
2288
2182
|
// Mark conversation as destroyed to prevent async operations
|
|
2289
2183
|
this.conversationService.setDestroyed(true);
|
|
2290
2184
|
}
|
|
2291
|
-
/**
|
|
2292
|
-
* Handle user message from chat footer
|
|
2293
|
-
*/
|
|
2294
|
-
async onUserMessage(message) {
|
|
2295
|
-
if (this.isThinking()) {
|
|
2296
|
-
return;
|
|
2297
|
-
}
|
|
2298
|
-
await this.conversationService.sendUserMessage(message);
|
|
2299
|
-
// Evaluate conversation after sending message
|
|
2300
|
-
this.evaluateConversation();
|
|
2301
|
-
}
|
|
2302
|
-
/**
|
|
2303
|
-
* Handle microphone input finished
|
|
2304
|
-
*/
|
|
2305
|
-
async onMicFinished(eventBlob) {
|
|
2306
|
-
if (!(eventBlob instanceof Blob)) {
|
|
2307
|
-
if (!this.chatUserSettings.superHearing) {
|
|
2308
|
-
this.isUserTalking = false;
|
|
2309
|
-
}
|
|
2310
|
-
return;
|
|
2311
|
-
}
|
|
2312
|
-
// Check if audio is too small
|
|
2313
|
-
if (eventBlob.size < 10000) {
|
|
2314
|
-
return;
|
|
2315
|
-
}
|
|
2316
|
-
this.isUserTalking = false;
|
|
2317
|
-
this.isGettingTranscription = true;
|
|
2318
|
-
try {
|
|
2319
|
-
// Get transcription from audio
|
|
2320
|
-
const transcription = await this.agentCardService.getAudioTranscriptions(eventBlob, {
|
|
2321
|
-
conversationId: this.agentCard._id,
|
|
2322
|
-
});
|
|
2323
|
-
// Create message with transcription
|
|
2324
|
-
const message = {
|
|
2325
|
-
content: transcription.text,
|
|
2326
|
-
role: ChatRole.User,
|
|
2327
|
-
transcriptionTimestamps: transcription.words,
|
|
2328
|
-
};
|
|
2329
|
-
message['audioUrl'] = URL.createObjectURL(eventBlob);
|
|
2330
|
-
// Send message to conversation
|
|
2331
|
-
await this.conversationService.sendUserMessage(message);
|
|
2332
|
-
// Evaluate conversation
|
|
2333
|
-
this.evaluateConversation();
|
|
2334
|
-
}
|
|
2335
|
-
finally {
|
|
2336
|
-
this.isGettingTranscription = false;
|
|
2337
|
-
}
|
|
2338
|
-
}
|
|
2339
2185
|
/**
|
|
2340
2186
|
* Open chat settings dialog
|
|
2341
2187
|
*/
|
|
@@ -2349,7 +2195,7 @@ class DCChatComponent {
|
|
|
2349
2195
|
closeOnEscape: true,
|
|
2350
2196
|
})
|
|
2351
2197
|
.onClose.subscribe(async () => {
|
|
2352
|
-
|
|
2198
|
+
// TODO: Que hacer cuando cambie las configuraciones del usario?, creo que si existe en exchange no necesito guardarlo y leer desde el servicio.
|
|
2353
2199
|
});
|
|
2354
2200
|
}
|
|
2355
2201
|
/**
|
|
@@ -2367,35 +2213,16 @@ class DCChatComponent {
|
|
|
2367
2213
|
}
|
|
2368
2214
|
await this.ngOnInit();
|
|
2369
2215
|
}
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
*/
|
|
2373
|
-
async evaluateConversation() {
|
|
2374
|
-
const messages = this.messages();
|
|
2375
|
-
await this.evaluationService.evaluateConversation(messages, this.evaluatorAgentCard);
|
|
2376
|
-
}
|
|
2377
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCChatComponent, deps: [{ token: CONVERSATION_AI_TOKEN }, { token: TOAST_ALERTS_TOKEN }, { token: DCConversationPromptBuilderService }, { token: i1$4.DialogService }, { token: ConversationService }, { token: EvaluationService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2378
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", type: DCChatComponent, isStandalone: true, selector: "dc-chat", inputs: { chatUserSettings: "chatUserSettings", agentCard: "agentCard", evaluatorAgentCard: "evaluatorAgentCard", parseDict: "parseDict" }, outputs: { sendMessage: "sendMessage" }, providers: [DialogService], ngImport: i0, template: "<div class=\"chat-container\">\n <!-- Chat Header -->\n <dc-chat-header\n [agentCard]=\"agentCard\"\n [isAdmin]=\"isAdmin\"\n (showInfoEvent)=\"showInfo()\"\n (settingsClickEvent)=\"changeUserChatSettings()\"\n (restartConversationEvent)=\"restartConversation($event)\">\n </dc-chat-header>\n\n <!-- Messages List -->\n <dc-chat-messages-list [chatUserSettings]=\"chatUserSettings\" [thinkingImg]=\"thinkingImg\"> </dc-chat-messages-list>\n\n <!-- Chat Footer -->\n <dc-chat-footer [isAIThinking]=\"isThinking()\" [micSettings]=\"micSettings\" (sendMessage)=\"onUserMessage($event)\" (micFinishedEvent)=\"onMicFinished($event)\">\n </dc-chat-footer>\n\n <!-- Progress Bar for Score -->\n <div class=\"score-container\" *ngIf=\"score() > 0\">\n <p-progressBar [value]=\"score()\" [showValue]=\"true\"></p-progressBar>\n </div>\n\n <!-- Info Dialog -->\n <p-dialog [(visible)]=\"isInfoVisible\" [modal]=\"true\" [draggable]=\"false\" [resizable]=\"false\" header=\"Conversation Info\">\n <div class=\"info-content\">\n <h3>Agent Card</h3>\n <pre>{{ agentCard | json }}</pre>\n\n <h3>User Settings</h3>\n <pre>{{ chatUserSettings | json }}</pre>\n </div>\n </p-dialog>\n</div>\n", styles: [".chat-container{display:flex;flex-direction:column;height:100%;max-height:100vh;overflow:hidden;background-color:var(--surface-ground, #f8f9fa)}dc-chat-messages-list{flex:1;overflow-y:auto;padding:.5rem}.score-container{padding:.5rem 1rem;background-color:var(--surface-card, #ffffff);border-top:1px solid var(--surface-border, #dee2e6)}.info-content{max-height:70vh;overflow-y:auto}.info-content h3{margin-top:1rem;margin-bottom:.5rem;font-size:1.2rem}.info-content pre{background-color:var(--surface-hover, #f1f1f1);padding:1rem;border-radius:4px;overflow-x:auto;font-size:.9rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$2.JsonPipe, name: "json" }, { kind: "component", type: ChatHeaderComponent, selector: "dc-chat-header", inputs: ["isAdmin", "alternativeConversation", "agentCard"], outputs: ["restartConversationEvent", "showInfoEvent", "settingsClickEvent"] }, { kind: "component", type: ChatFooterComponent, selector: "dc-chat-footer", inputs: ["isAIThinking", "score", "micSettings"], outputs: ["sendMessage", "textInputChanged", "micFinishedEvent"] }, { kind: "component", type: ChatMessagesListComponent, selector: "dc-chat-messages-list", inputs: ["chatUserSettings", "thinkingImg"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i6.Dialog, selector: "p-dialog", inputs: ["header", "draggable", "resizable", "positionLeft", "positionTop", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "responsive", "appendTo", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "breakpoint", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: ProgressBarModule }, { kind: "component", type: i2.ProgressBar, selector: "p-progressBar, p-progressbar, p-progress-bar", inputs: ["value", "showValue", "styleClass", "valueStyleClass", "style", "unit", "mode", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2216
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCChatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2217
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.4", type: DCChatComponent, isStandalone: true, selector: "dc-chat", inputs: { chatUserSettings: { classPropertyName: "chatUserSettings", publicName: "chatUserSettings", isSignal: false, isRequired: false, transformFunction: null }, agentCard: { classPropertyName: "agentCard", publicName: "agentCard", isSignal: false, isRequired: false, transformFunction: null }, evaluatorAgentCard: { classPropertyName: "evaluatorAgentCard", publicName: "evaluatorAgentCard", isSignal: true, isRequired: false, transformFunction: null }, parseDict: { classPropertyName: "parseDict", publicName: "parseDict", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sendMessage: "sendMessage" }, providers: [DialogService], ngImport: i0, template: "<div class=\"chat-container\">\n <!-- Chat Header -->\n <dc-chat-header\n [agentCard]=\"agentCard\"\n [isAdmin]=\"isAdmin\"\n (showInfoEvent)=\"showInfo()\"\n (settingsClickEvent)=\"changeUserChatSettings()\"\n (restartConversationEvent)=\"restartConversation($event)\">\n </dc-chat-header>\n\n <!-- Messages List -->\n <dc-chat-messages-list [chatUserSettings]=\"chatUserSettings\" [inputMessages]=\"messages()\"> </dc-chat-messages-list>\n\n <!-- Chat Footer -->\n <dc-chat-footer [micSettings]=\"micSettings\" [evaluatorAgentCard]=\"evaluatorAgentCard()\"> </dc-chat-footer>\n\n <!-- Info Dialog -->\n <p-dialog [(visible)]=\"isInfoVisible\" [modal]=\"true\" [draggable]=\"false\" [resizable]=\"false\" header=\"Conversation Info\">\n <div class=\"info-content\">\n <h3>Agent Card</h3>\n <pre>{{ agentCard | safeJson }}</pre>\n\n <h3>User Settings</h3>\n <pre>{{ chatUserSettings | safeJson }}</pre>\n </div>\n </p-dialog>\n</div>\n", styles: ["::ng-deep .p-drawer-content{padding:0!important}.chat-container{display:flex;flex-direction:column;height:100%;max-height:100vh;overflow:hidden;background-color:transparent}dc-chat-messages-list{flex:1;overflow-y:auto;padding:.5rem}.score-container{padding:.5rem 1rem;background-color:var(--surface-card, #ffffff);border-top:1px solid var(--surface-border, #dee2e6)}.info-content{max-height:70vh;overflow-y:auto}.info-content h3{margin-top:1rem;margin-bottom:.5rem;font-size:1.2rem}.info-content pre{background-color:var(--surface-hover, #f1f1f1);padding:1rem;border-radius:4px;overflow-x:auto;font-size:.9rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ChatHeaderComponent, selector: "dc-chat-header", inputs: ["isAdmin", "alternativeConversation", "agentCard"], outputs: ["restartConversationEvent", "showInfoEvent", "settingsClickEvent"] }, { kind: "component", type: ChatFooterComponent, selector: "dc-chat-footer", inputs: ["isAIThinking", "evaluatorAgentCard", "micSettings"], outputs: ["sendMessage", "textInputChanged"] }, { kind: "component", type: ChatMessagesListComponent, selector: "dc-chat-messages-list", inputs: ["chatUserSettings", "inputMessages"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i1$3.Dialog, selector: "p-dialog", inputs: ["header", "draggable", "resizable", "positionLeft", "positionTop", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "responsive", "appendTo", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "breakpoint", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: ProgressBarModule }, { kind: "pipe", type: SafeJsonPipe, name: "safeJson" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2379
2218
|
}
|
|
2380
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
2219
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCChatComponent, decorators: [{
|
|
2381
2220
|
type: Component,
|
|
2382
|
-
args: [{ selector: 'dc-chat', standalone: true, imports: [CommonModule, ChatHeaderComponent, ChatFooterComponent, ChatMessagesListComponent, DialogModule, ProgressBarModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [DialogService], template: "<div class=\"chat-container\">\n <!-- Chat Header -->\n <dc-chat-header\n [agentCard]=\"agentCard\"\n [isAdmin]=\"isAdmin\"\n (showInfoEvent)=\"showInfo()\"\n (settingsClickEvent)=\"changeUserChatSettings()\"\n (restartConversationEvent)=\"restartConversation($event)\">\n </dc-chat-header>\n\n <!-- Messages List -->\n <dc-chat-messages-list [chatUserSettings]=\"chatUserSettings\" [
|
|
2383
|
-
}],
|
|
2384
|
-
type: Inject,
|
|
2385
|
-
args: [CONVERSATION_AI_TOKEN]
|
|
2386
|
-
}] }, { type: i6$1.ToastAlertsAbstractService, decorators: [{
|
|
2387
|
-
type: Inject,
|
|
2388
|
-
args: [TOAST_ALERTS_TOKEN]
|
|
2389
|
-
}] }, { type: DCConversationPromptBuilderService }, { type: i1$4.DialogService }, { type: ConversationService }, { type: EvaluationService }], propDecorators: { chatUserSettings: [{
|
|
2221
|
+
args: [{ selector: 'dc-chat', standalone: true, imports: [CommonModule, ChatHeaderComponent, ChatFooterComponent, ChatMessagesListComponent, DialogModule, ProgressBarModule, SafeJsonPipe], changeDetection: ChangeDetectionStrategy.OnPush, providers: [DialogService], template: "<div class=\"chat-container\">\n <!-- Chat Header -->\n <dc-chat-header\n [agentCard]=\"agentCard\"\n [isAdmin]=\"isAdmin\"\n (showInfoEvent)=\"showInfo()\"\n (settingsClickEvent)=\"changeUserChatSettings()\"\n (restartConversationEvent)=\"restartConversation($event)\">\n </dc-chat-header>\n\n <!-- Messages List -->\n <dc-chat-messages-list [chatUserSettings]=\"chatUserSettings\" [inputMessages]=\"messages()\"> </dc-chat-messages-list>\n\n <!-- Chat Footer -->\n <dc-chat-footer [micSettings]=\"micSettings\" [evaluatorAgentCard]=\"evaluatorAgentCard()\"> </dc-chat-footer>\n\n <!-- Info Dialog -->\n <p-dialog [(visible)]=\"isInfoVisible\" [modal]=\"true\" [draggable]=\"false\" [resizable]=\"false\" header=\"Conversation Info\">\n <div class=\"info-content\">\n <h3>Agent Card</h3>\n <pre>{{ agentCard | safeJson }}</pre>\n\n <h3>User Settings</h3>\n <pre>{{ chatUserSettings | safeJson }}</pre>\n </div>\n </p-dialog>\n</div>\n", styles: ["::ng-deep .p-drawer-content{padding:0!important}.chat-container{display:flex;flex-direction:column;height:100%;max-height:100vh;overflow:hidden;background-color:transparent}dc-chat-messages-list{flex:1;overflow-y:auto;padding:.5rem}.score-container{padding:.5rem 1rem;background-color:var(--surface-card, #ffffff);border-top:1px solid var(--surface-border, #dee2e6)}.info-content{max-height:70vh;overflow-y:auto}.info-content h3{margin-top:1rem;margin-bottom:.5rem;font-size:1.2rem}.info-content pre{background-color:var(--surface-hover, #f1f1f1);padding:1rem;border-radius:4px;overflow-x:auto;font-size:.9rem}\n"] }]
|
|
2222
|
+
}], propDecorators: { chatUserSettings: [{
|
|
2390
2223
|
type: Input
|
|
2391
2224
|
}], agentCard: [{
|
|
2392
2225
|
type: Input
|
|
2393
|
-
}], evaluatorAgentCard: [{
|
|
2394
|
-
type: Input
|
|
2395
|
-
}], parseDict: [{
|
|
2396
|
-
type: Input
|
|
2397
|
-
}], sendMessage: [{
|
|
2398
|
-
type: Output
|
|
2399
2226
|
}] } });
|
|
2400
2227
|
|
|
2401
2228
|
async function getCharacterData(file) {
|
|
@@ -2529,32 +2356,32 @@ function getFileBuffer(file) {
|
|
|
2529
2356
|
}
|
|
2530
2357
|
|
|
2531
2358
|
class PromptPreviewDialogComponent {
|
|
2532
|
-
constructor(
|
|
2533
|
-
this.dynamicDialogConfig =
|
|
2534
|
-
this.dialogRef =
|
|
2359
|
+
constructor() {
|
|
2360
|
+
this.dynamicDialogConfig = inject(DynamicDialogConfig);
|
|
2361
|
+
this.dialogRef = inject(DynamicDialogRef);
|
|
2535
2362
|
this.data = this.dynamicDialogConfig.data;
|
|
2536
2363
|
}
|
|
2537
2364
|
close() {
|
|
2538
2365
|
this.dialogRef.close();
|
|
2539
2366
|
}
|
|
2540
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
2541
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.
|
|
2367
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: PromptPreviewDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2368
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", type: PromptPreviewDialogComponent, isStandalone: true, selector: "dc-prompt-preview-dialog", ngImport: i0, template: ` <div class="prompt-preview-content" [innerHTML]="data.html"></div> `, isInline: true, dependencies: [{ kind: "ngmodule", type: DialogModule }] }); }
|
|
2542
2369
|
}
|
|
2543
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
2370
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: PromptPreviewDialogComponent, decorators: [{
|
|
2544
2371
|
type: Component,
|
|
2545
2372
|
args: [{
|
|
2546
2373
|
selector: 'dc-prompt-preview-dialog',
|
|
2547
2374
|
standalone: true,
|
|
2548
|
-
imports: [
|
|
2375
|
+
imports: [DialogModule],
|
|
2549
2376
|
template: ` <div class="prompt-preview-content" [innerHTML]="data.html"></div> `,
|
|
2550
2377
|
}]
|
|
2551
|
-
}], ctorParameters: () => [
|
|
2378
|
+
}], ctorParameters: () => [] });
|
|
2552
2379
|
|
|
2553
2380
|
class TranslateDialogComponent {
|
|
2554
|
-
constructor(
|
|
2555
|
-
this.fb =
|
|
2556
|
-
this.dialogRef =
|
|
2557
|
-
this.data =
|
|
2381
|
+
constructor() {
|
|
2382
|
+
this.fb = inject(FormBuilder);
|
|
2383
|
+
this.dialogRef = inject(DialogRef);
|
|
2384
|
+
this.data = inject(DIALOG_DATA);
|
|
2558
2385
|
this.languages = Object.entries(LangCodeDescriptionEs).map(([code, description]) => ({
|
|
2559
2386
|
code,
|
|
2560
2387
|
description,
|
|
@@ -2570,17 +2397,19 @@ class TranslateDialogComponent {
|
|
|
2570
2397
|
onConfirm() {
|
|
2571
2398
|
this.dialogRef.close(this.form.value.targetLang);
|
|
2572
2399
|
}
|
|
2573
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
2574
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
2400
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: TranslateDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2401
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: TranslateDialogComponent, isStandalone: true, selector: "dc-translate-dialog", ngImport: i0, template: `
|
|
2575
2402
|
<div class="translate-dialog">
|
|
2576
2403
|
<h4>Tu idioma actual es: {{ data.currentLang }}</h4>
|
|
2577
2404
|
<h2>Selecciona el idioma al que quieres traducir</h2>
|
|
2578
2405
|
<form [formGroup]="form">
|
|
2579
2406
|
<select formControlName="targetLang">
|
|
2580
2407
|
<option value="">Select language...</option>
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2408
|
+
@for (lang of languages; track lang) {
|
|
2409
|
+
<option [value]="lang.code">
|
|
2410
|
+
{{ lang.description }}
|
|
2411
|
+
</option>
|
|
2412
|
+
}
|
|
2584
2413
|
</select>
|
|
2585
2414
|
</form>
|
|
2586
2415
|
<div class="actions">
|
|
@@ -2588,20 +2417,22 @@ class TranslateDialogComponent {
|
|
|
2588
2417
|
<button (click)="onConfirm()" [disabled]="!form.value.targetLang"> Translate </button>
|
|
2589
2418
|
</div>
|
|
2590
2419
|
</div>
|
|
2591
|
-
|
|
2420
|
+
`, isInline: true, styles: [".translate-dialog{padding:20px;background:#fff;border-radius:8px;box-shadow:0 2px 8px #00000026}.actions{margin-top:20px;display:flex;justify-content:flex-end;gap:10px}select{width:100%;padding:8px;margin-top:10px;border:1px solid #ccc;border-radius:4px}button{padding:8px 16px;border:none;border-radius:4px;cursor:pointer}button:first-child{background:#f0f0f0}button:last-child{background:#007bff;color:#fff}button:disabled{background:#ccc;cursor:not-allowed}h2{margin:0 0 16px;color:#333}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }] }); }
|
|
2592
2421
|
}
|
|
2593
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
2422
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: TranslateDialogComponent, decorators: [{
|
|
2594
2423
|
type: Component,
|
|
2595
|
-
args: [{ selector: 'dc-translate-dialog', standalone: true, imports: [
|
|
2424
|
+
args: [{ selector: 'dc-translate-dialog', standalone: true, imports: [ReactiveFormsModule], template: `
|
|
2596
2425
|
<div class="translate-dialog">
|
|
2597
2426
|
<h4>Tu idioma actual es: {{ data.currentLang }}</h4>
|
|
2598
2427
|
<h2>Selecciona el idioma al que quieres traducir</h2>
|
|
2599
2428
|
<form [formGroup]="form">
|
|
2600
2429
|
<select formControlName="targetLang">
|
|
2601
2430
|
<option value="">Select language...</option>
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2431
|
+
@for (lang of languages; track lang) {
|
|
2432
|
+
<option [value]="lang.code">
|
|
2433
|
+
{{ lang.description }}
|
|
2434
|
+
</option>
|
|
2435
|
+
}
|
|
2605
2436
|
</select>
|
|
2606
2437
|
</form>
|
|
2607
2438
|
<div class="actions">
|
|
@@ -2609,11 +2440,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImpor
|
|
|
2609
2440
|
<button (click)="onConfirm()" [disabled]="!form.value.targetLang"> Translate </button>
|
|
2610
2441
|
</div>
|
|
2611
2442
|
</div>
|
|
2612
|
-
|
|
2613
|
-
}], ctorParameters: () => [
|
|
2614
|
-
type: Inject,
|
|
2615
|
-
args: [DIALOG_DATA]
|
|
2616
|
-
}] }] });
|
|
2443
|
+
`, styles: [".translate-dialog{padding:20px;background:#fff;border-radius:8px;box-shadow:0 2px 8px #00000026}.actions{margin-top:20px;display:flex;justify-content:flex-end;gap:10px}select{width:100%;padding:8px;margin-top:10px;border:1px solid #ccc;border-radius:4px}button{padding:8px 16px;border:none;border-radius:4px;cursor:pointer}button:first-child{background:#f0f0f0}button:last-child{background:#007bff;color:#fff}button:disabled{background:#ccc;cursor:not-allowed}h2{margin:0 0 16px;color:#333}\n"] }]
|
|
2444
|
+
}], ctorParameters: () => [] });
|
|
2617
2445
|
|
|
2618
2446
|
function markdownToHTML(markdownText) {
|
|
2619
2447
|
// Convert italics-bold (***text***)
|
|
@@ -2630,10 +2458,10 @@ class SimpleMdToHtmlPipe {
|
|
|
2630
2458
|
transform(text) {
|
|
2631
2459
|
return markdownToHTML(text);
|
|
2632
2460
|
}
|
|
2633
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
2634
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.
|
|
2461
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: SimpleMdToHtmlPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
2462
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.4", ngImport: i0, type: SimpleMdToHtmlPipe, isStandalone: true, name: "simpleMdToHtml" }); }
|
|
2635
2463
|
}
|
|
2636
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
2464
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: SimpleMdToHtmlPipe, decorators: [{
|
|
2637
2465
|
type: Pipe,
|
|
2638
2466
|
args: [{
|
|
2639
2467
|
name: 'simpleMdToHtml',
|
|
@@ -2647,10 +2475,10 @@ class StartDivToHtmlPipe {
|
|
|
2647
2475
|
text = text.replace(/\n/g, '<br>');
|
|
2648
2476
|
return text;
|
|
2649
2477
|
}
|
|
2650
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
2651
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.
|
|
2478
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: StartDivToHtmlPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
2479
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.4", ngImport: i0, type: StartDivToHtmlPipe, isStandalone: true, name: "startDividerToHtml" }); }
|
|
2652
2480
|
}
|
|
2653
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
2481
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: StartDivToHtmlPipe, decorators: [{
|
|
2654
2482
|
type: Pipe,
|
|
2655
2483
|
args: [{
|
|
2656
2484
|
name: 'startDividerToHtml',
|
|
@@ -2663,10 +2491,10 @@ class MdToHtmlArrayPipe {
|
|
|
2663
2491
|
const htmlArray = convertToHTML(text);
|
|
2664
2492
|
return htmlArray.map((val) => val.content).join('');
|
|
2665
2493
|
}
|
|
2666
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
2667
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.
|
|
2494
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MdToHtmlArrayPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
2495
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.4", ngImport: i0, type: MdToHtmlArrayPipe, isStandalone: true, name: "mdToHtmlArray" }); }
|
|
2668
2496
|
}
|
|
2669
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
2497
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MdToHtmlArrayPipe, decorators: [{
|
|
2670
2498
|
type: Pipe,
|
|
2671
2499
|
args: [{
|
|
2672
2500
|
name: 'mdToHtmlArray',
|
|
@@ -2675,11 +2503,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImpor
|
|
|
2675
2503
|
}] });
|
|
2676
2504
|
|
|
2677
2505
|
class AccountPlatformForm {
|
|
2678
|
-
constructor(
|
|
2679
|
-
this.route =
|
|
2680
|
-
this.fb =
|
|
2681
|
-
this.router =
|
|
2682
|
-
this.toastService =
|
|
2506
|
+
constructor() {
|
|
2507
|
+
this.route = inject(ActivatedRoute);
|
|
2508
|
+
this.fb = inject(FormBuilder);
|
|
2509
|
+
this.router = inject(Router);
|
|
2510
|
+
this.toastService = inject(TOAST_ALERTS_TOKEN);
|
|
2683
2511
|
// Format the platform options for dropdown display
|
|
2684
2512
|
this.platformOptions = Object.values(EAccountsPlatform).map((value) => ({
|
|
2685
2513
|
label: value.charAt(0).toUpperCase() + value.slice(1), // Capitalize first letter
|
|
@@ -2734,16 +2562,13 @@ class AccountPlatformForm {
|
|
|
2734
2562
|
this.toastService.warn(infoToast);
|
|
2735
2563
|
}
|
|
2736
2564
|
}
|
|
2737
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
2738
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.
|
|
2565
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AccountPlatformForm, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2566
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: AccountPlatformForm, isStandalone: true, selector: "account-platform-form", inputs: { formArray: "formArray" }, ngImport: i0, template: "<div class=\"source-form-card\">\n <p-card header=\"Cuenta\">\n @for (formAccount of formArray.controls; track formAccount) {\n <form [formGroup]=\"$any(formAccount)\">\n <div class=\"form-field\">\n <label class=\"block\">Platform</label>\n <p-dropdown [options]=\"platformOptions\" formControlName=\"platform\" optionLabel=\"label\" optionValue=\"value\" placeholder=\"Select a platform\"></p-dropdown>\n </div>\n\n <div class=\"form-field\">\n <label for=\"name\" class=\"block\">Username</label>\n <input pInputText id=\"name\" type=\"text\" formControlName=\"name\" placeholder=\"Enter name\" class=\"w-full\" />\n </div>\n\n <div class=\"form-field\">\n <label class=\"block\">Email</label>\n <input pInputText type=\"text\" formControlName=\"email\" placeholder=\"Enter name\" class=\"w-full\" />\n </div>\n </form>\n }\n </p-card>\n</div>\n", styles: [":host{display:block;padding:1rem}.source-form-card{max-width:800px;margin:0 auto}.form-field{margin-bottom:1.5rem;display:flex;flex-direction:column}.form-field label{margin-bottom:.5rem;font-weight:500;color:#495057}.form-field input,.form-field textarea,.form-field ::ng-deep .p-element{margin-top:.25rem}:host ::ng-deep .p-card .p-card-content>div:last-child{margin-top:1.5rem;display:flex;justify-content:flex-end}:host ::ng-deep .p-card .p-card-header{background-color:#f8f9fa;padding:1rem;border-bottom:1px solid #dee2e6}h3{color:#495057;margin-bottom:1.5rem;text-align:center}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i2$4.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "ngmodule", type: DropdownModule }, { kind: "component", type: i3$2.Dropdown, selector: "p-dropdown", inputs: ["id", "scrollHeight", "filter", "name", "style", "panelStyle", "styleClass", "panelStyleClass", "readonly", "required", "editable", "appendTo", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "variant", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "autoDisplayFirst", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "maxlength", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "fluid", "disabled", "itemSize", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "filterValue", "options"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: SelectModule }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4$1.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }, { kind: "ngmodule", type: ChipModule }, { kind: "ngmodule", type: TooltipModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2739
2567
|
}
|
|
2740
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
2568
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AccountPlatformForm, decorators: [{
|
|
2741
2569
|
type: Component,
|
|
2742
2570
|
args: [{ selector: 'account-platform-form', imports: [ReactiveFormsModule, CardModule, TextareaModule, DropdownModule, ButtonModule, SelectModule, InputTextModule, ChipModule, TooltipModule], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "<div class=\"source-form-card\">\n <p-card header=\"Cuenta\">\n @for (formAccount of formArray.controls; track formAccount) {\n <form [formGroup]=\"$any(formAccount)\">\n <div class=\"form-field\">\n <label class=\"block\">Platform</label>\n <p-dropdown [options]=\"platformOptions\" formControlName=\"platform\" optionLabel=\"label\" optionValue=\"value\" placeholder=\"Select a platform\"></p-dropdown>\n </div>\n\n <div class=\"form-field\">\n <label for=\"name\" class=\"block\">Username</label>\n <input pInputText id=\"name\" type=\"text\" formControlName=\"name\" placeholder=\"Enter name\" class=\"w-full\" />\n </div>\n\n <div class=\"form-field\">\n <label class=\"block\">Email</label>\n <input pInputText type=\"text\" formControlName=\"email\" placeholder=\"Enter name\" class=\"w-full\" />\n </div>\n </form>\n }\n </p-card>\n</div>\n", styles: [":host{display:block;padding:1rem}.source-form-card{max-width:800px;margin:0 auto}.form-field{margin-bottom:1.5rem;display:flex;flex-direction:column}.form-field label{margin-bottom:.5rem;font-weight:500;color:#495057}.form-field input,.form-field textarea,.form-field ::ng-deep .p-element{margin-top:.25rem}:host ::ng-deep .p-card .p-card-content>div:last-child{margin-top:1.5rem;display:flex;justify-content:flex-end}:host ::ng-deep .p-card .p-card-header{background-color:#f8f9fa;padding:1rem;border-bottom:1px solid #dee2e6}h3{color:#495057;margin-bottom:1.5rem;text-align:center}\n"] }]
|
|
2743
|
-
}], ctorParameters: () => [
|
|
2744
|
-
type: Inject,
|
|
2745
|
-
args: [TOAST_ALERTS_TOKEN]
|
|
2746
|
-
}] }], propDecorators: { formArray: [{
|
|
2571
|
+
}], ctorParameters: () => [], propDecorators: { formArray: [{
|
|
2747
2572
|
type: Input
|
|
2748
2573
|
}] } });
|
|
2749
2574
|
|
|
@@ -2756,16 +2581,16 @@ class DCAgentCardFormComponent {
|
|
|
2756
2581
|
};
|
|
2757
2582
|
return imageSettings;
|
|
2758
2583
|
}
|
|
2759
|
-
constructor(
|
|
2760
|
-
this.fb =
|
|
2761
|
-
this.storageService =
|
|
2762
|
-
this.agentCardService =
|
|
2763
|
-
this.cdr =
|
|
2764
|
-
this.router =
|
|
2765
|
-
this.activatedRoute =
|
|
2766
|
-
this.dialogService =
|
|
2767
|
-
this.promptBuilder =
|
|
2768
|
-
this.toastService =
|
|
2584
|
+
constructor() {
|
|
2585
|
+
this.fb = inject(FormBuilder);
|
|
2586
|
+
this.storageService = inject(MultiImagesStorageService);
|
|
2587
|
+
this.agentCardService = inject(CONVERSATION_AI_TOKEN);
|
|
2588
|
+
this.cdr = inject(ChangeDetectorRef);
|
|
2589
|
+
this.router = inject(Router);
|
|
2590
|
+
this.activatedRoute = inject(ActivatedRoute);
|
|
2591
|
+
this.dialogService = inject(DialogService);
|
|
2592
|
+
this.promptBuilder = inject(DCConversationPromptBuilderService);
|
|
2593
|
+
this.toastService = inject(TOAST_ALERTS_TOKEN, { optional: true });
|
|
2769
2594
|
// select options
|
|
2770
2595
|
this.textEngines = Object.values(TextEngines);
|
|
2771
2596
|
this.conversationOptions = ConversationTypeOptions;
|
|
@@ -2775,26 +2600,26 @@ class DCAgentCardFormComponent {
|
|
|
2775
2600
|
this.languageOptions = Object.entries(LangCodeDescriptionEs).map(([value, label]) => ({ value, label }));
|
|
2776
2601
|
this.agentCardId = this.activatedRoute.snapshot.paramMap.get('id');
|
|
2777
2602
|
this.audioSpeedOptions = Object.values(AudioSpeed$1).map((speed) => ({ label: speed, value: speed }));
|
|
2778
|
-
this.storageSettings = this.getSettings();
|
|
2779
|
-
this.bannerImgSettings = {
|
|
2603
|
+
this.storageSettings = input(this.getSettings());
|
|
2604
|
+
this.bannerImgSettings = input({
|
|
2780
2605
|
path: 'conversation-cards/' + this.agentCardId,
|
|
2781
2606
|
fileName: null,
|
|
2782
2607
|
cropSettings: { aspectRatio: AspectType.Rectangle, resolutions: [ResolutionType.MediumLarge], resizeToWidth: 700 },
|
|
2783
|
-
};
|
|
2784
|
-
this.imageStorageSettings = {
|
|
2608
|
+
});
|
|
2609
|
+
this.imageStorageSettings = input({
|
|
2785
2610
|
path: 'conversation-cards/' + this.agentCardId,
|
|
2786
2611
|
fileName: null,
|
|
2787
2612
|
cropSettings: { aspectRatio: AspectType.Vertical_9_16, resolutions: [ResolutionType.MediumLarge], resizeToWidth: 500 },
|
|
2788
|
-
};
|
|
2613
|
+
});
|
|
2789
2614
|
this.stickerStorageSettings = {
|
|
2790
2615
|
path: `conversation-cards/${this.agentCardId}/stickers`,
|
|
2791
2616
|
fileName: null,
|
|
2792
2617
|
cropSettings: { aspectRatio: AspectType.Square, resolutions: [ResolutionType.MediumLarge], resizeToWidth: 400 },
|
|
2793
2618
|
};
|
|
2794
|
-
this.onImageLoaded =
|
|
2795
|
-
this.onSave =
|
|
2796
|
-
this.onGoDetails =
|
|
2797
|
-
this.onTranslate =
|
|
2619
|
+
this.onImageLoaded = output();
|
|
2620
|
+
this.onSave = output();
|
|
2621
|
+
this.onGoDetails = output();
|
|
2622
|
+
this.onTranslate = output();
|
|
2798
2623
|
this.markdownForm = this.fb.group({ seeMarkdown: [false] });
|
|
2799
2624
|
// Type is IAgentCard
|
|
2800
2625
|
this.form = this.fb.group({
|
|
@@ -2831,7 +2656,7 @@ class DCAgentCardFormComponent {
|
|
|
2831
2656
|
ngOnInit() {
|
|
2832
2657
|
this.form.controls.conversationSettings.controls.autoStart;
|
|
2833
2658
|
console.log('form', this.form.value, this.conversationOptions);
|
|
2834
|
-
this.imageSettings = this.storageSettings.cropSettings;
|
|
2659
|
+
this.imageSettings = this.storageSettings().cropSettings;
|
|
2835
2660
|
this.loadConversationCard();
|
|
2836
2661
|
this.markdownForm.get('checked')?.valueChanges.subscribe((value) => {
|
|
2837
2662
|
this.markdownForm.patchValue({ seeMarkdown: value }, { emitEvent: false });
|
|
@@ -2846,7 +2671,7 @@ class DCAgentCardFormComponent {
|
|
|
2846
2671
|
this.patchFormWithConversationData();
|
|
2847
2672
|
this.handleAlternateGreetings();
|
|
2848
2673
|
}
|
|
2849
|
-
this.storageSettings.path = 'conversation-cards/' + this.agentCardId;
|
|
2674
|
+
this.storageSettings().path = 'conversation-cards/' + this.agentCardId;
|
|
2850
2675
|
this.cdr.detectChanges();
|
|
2851
2676
|
}
|
|
2852
2677
|
catch (err) {
|
|
@@ -3164,13 +2989,12 @@ class DCAgentCardFormComponent {
|
|
|
3164
2989
|
this.toastService.success({ title: 'Sticker removed', subtitle: 'Sticker was removed' });
|
|
3165
2990
|
this.cdr.detectChanges();
|
|
3166
2991
|
}
|
|
3167
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
3168
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", type: DCAgentCardFormComponent, isStandalone: true, selector: "dc-agent-form", inputs: { storageSettings: "storageSettings", bannerImgSettings: "bannerImgSettings", imageStorageSettings: "imageStorageSettings" }, outputs: { onImageLoaded: "onImageLoaded", onSave: "onSave", onGoDetails: "onGoDetails", onTranslate: "onTranslate" }, providers: [DialogService], ngImport: i0, template: "<div class=\"top-buttons\">\n <button pButton severity=\"info\" (click)=\"checkPrompt()\" label=\"\uD83D\uDC41\uFE0F Ver instrucciones finales \uD83D\uDCD3\"></button>\n\n <button pButton severity=\"info\" (click)=\"goToDetails()\" label=\"\uD83D\uDCAC Conversar\"></button>\n <button pButton severity=\"primary\" (click)=\"saveConversation()\" label=\"\uD83D\uDCBE Guardar cambios\"></button>\n</div>\n\n<div class=\"top-buttons\">\n <p-button severity=\"help\" (click)=\"translate()\" label=\"\uD83D\uDD04 Traducir\"></p-button>\n <p-button [loading]=\"isGenerating\" severity=\"help\" (click)=\"generateCharacter()\" label=\"Generar \uD83E\uDDBE\"></p-button>\n\n <p-button severity=\"info\" (click)=\"downloadConversation()\" label=\"\uD83D\uDCC1 Exportar \u2B07\uFE0F\"></p-button>\n <p-button severity=\"info\" (click)=\"importConversation()\" label=\"\uD83C\uDCCF Importar \u2B06\uFE0F\"></p-button>\n</div>\n\n<br />\n<br />\n<form [formGroup]=\"form\" class=\"conversation-form\">\n <div class=\"form-grid\">\n <div class=\"left-column\">\n <div style=\"display: flex; gap: 15px\">\n <div class=\"form-field\">\n <label for=\"version\">Version: {{ form.controls.version.value }} <span pTooltip=\"Version number of the conversation\">\u2139\uFE0F</span></label>\n </div>\n\n <div class=\"form-field\">\n <label for=\"id\"\n >ID: <span pTooltip=\"Unique identifier for this conversation\"> {{ form.controls.id.value }} \u2139\uFE0F</span></label\n >\n </div>\n </div>\n\n <div class=\"form-field\">\n <label for=\"title\">Title <span pTooltip=\"T\u00EDtulo de la conversaci\u00F3n\">\u2139\uFE0F</span></label>\n <input pInputText id=\"title\" type=\"text\" formControlName=\"title\" />\n @if(form.controls.title.errors?.['required'] && form.controls.title.touched){\n <div class=\"error\"> Title is required </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"lang\">Language <span pTooltip=\"Select the primary language for the conversation\">\u2139\uFE0F</span></label>\n <p-select\n id=\"lang\"\n [options]=\"languageOptions\"\n formControlName=\"lang\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Language'\"></p-select>\n </div>\n\n <div formGroupName=\"conversationSettings\" class=\"group\">\n <h3>Conversation Settings <span pTooltip=\"Additional information about the conversation\">\u2139\uFE0F</span></h3>\n\n <div class=\"form-field\">\n <label for=\"textEngine\">\n Text Engine\n <span\n class=\"cursor-pointer\"\n (click)=\"textEngineDialog.toggle($event)\"\n pTooltip=\"Sistema de generaci\u00F3n de texto y audios. Client: el cliente llama al servidor en cada dialogo de voz/personaje, es optimo para historias, Server SSML: se sintetiza todo el audio en uno solo con los distintos cambios de voz/personaje, util para la reflexi\u00F3n porque es bilingue, utiliza dialogos en ingles y espa\u00F1ol en el mismo dialogo/audio\"\n >\u2139\uFE0F</span\n >\n </label>\n\n <p-select\n id=\"textEngine\"\n [options]=\"textEngineOptions\"\n formControlName=\"textEngine\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Text Engine'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"conversationType\">Conversation Type <span pTooltip=\"Choose the type of conversation interaction\">\u2139\uFE0F</span></label>\n <p-select\n id=\"conversationType\"\n [options]=\"conversationOptions\"\n formControlName=\"conversationType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Conversation Type'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label> Auto Start <span pTooltip=\"Start conversation automatically\">\u2139\uFE0F</span> </label>\n <p-toggleSwitch formControlName=\"autoStart\"> </p-toggleSwitch>\n </div>\n\n <div formGroupName=\"tts\" class=\"group\">\n <h3>TTS Settings <span pTooltip=\"Text-to-Speech configuration options\">\u2139\uFE0F</span></h3>\n\n <div class=\"form-field\">\n <label for=\"voice\">Voice <span pTooltip=\"Select the primary voice for text-to-speech\">\u2139\uFE0F</span></label>\n <p-select\n id=\"voice\"\n [options]=\"voiceTTSOptions\"\n formControlName=\"voice\"\n optionLabel=\"name\"\n optionValue=\"id\"\n [placeholder]=\"'Select Voice'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"secondaryVoice\">Secondary Voice <span pTooltip=\"Select an alternative voice for text-to-speech\">\u2139\uFE0F</span></label>\n <p-select\n id=\"secondaryVoice\"\n [options]=\"voiceTTSOptions\"\n formControlName=\"secondaryVoice\"\n optionLabel=\"name\"\n optionValue=\"id\"\n [placeholder]=\"'Select Secondary Voice'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"speed\">Speed <span pTooltip=\"Set the speech rate for text-to-speech conversion\">\u2139\uFE0F</span></label>\n <p-select\n id=\"speed\"\n [options]=\"audioSpeedOptions\"\n formControlName=\"speed\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Speed'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"speedRate\">Speed Rate <span pTooltip=\"Adjust the rate of speech delivery\">\u2139\uFE0F</span></label>\n <input pInputText id=\"speedRate\" type=\"number\" formControlName=\"speedRate\" step=\"0.1\" />\n </div>\n </div>\n </div>\n\n <div formGroupName=\"metaApp\" class=\"group\">\n <h3>Meta Information <span pTooltip=\"Additional information about the conversation\">\u2139\uFE0F</span></h3>\n <div class=\"form-field\">\n <label for=\"authorId\">Author ID <span pTooltip=\"Unique identifier for the conversation author\">\u2139\uFE0F</span></label>\n <input pInputText id=\"authorId\" type=\"text\" formControlName=\"authorId\" />\n </div>\n\n <div class=\"form-field\">\n <label for=\"authorEmail\">Author Email \u2139\uFE0F</label>\n <input pInputText id=\"authorEmail\" type=\"email\" formControlName=\"authorEmail\" />\n <div class=\"error\" *ngIf=\"form.get('metaApp.authorEmail')?.errors?.['email'] && form.get('metaApp.authorEmail')?.touched\">\n Please enter a valid email address\n </div>\n </div>\n\n <div class=\"form-field\">\n <label for=\"takenCount\"\n >Taken Count <span pTooltip=\"Es el contador de cuantas veces se ha tomado esta conversaci\u00F3n, no sirve por ahora\"> \u2139\uFE0F</span></label\n >\n <input pInputText id=\"takenCount\" type=\"number\" formControlName=\"takenCount\" />\n </div>\n\n <div class=\"form-field checkbox\">\n <label>\n <p-checkbox [binary]=\"true\" formControlName=\"isPublic\" />\n Public\n </label>\n </div>\n\n <div class=\"form-field checkbox\">\n <label>\n <p-checkbox [binary]=\"true\" formControlName=\"isPublished\" />\n Published\n </label>\n </div>\n </div>\n\n <div class=\"group\">\n <h4>Model Settings <span pTooltip=\"AI model configuration\">\u2139\uFE0F</span></h4>\n\n <dc-provider-selector [parentForm]=\"form.controls.model\"></dc-provider-selector>\n </div>\n\n <div class=\"group\">\n <h4>Gestion de cuentas</h4>\n @if(form.controls.accounts){\n <account-platform-form [formArray]=\"form.controls.accounts\"></account-platform-form>\n\n }\n </div>\n </div>\n\n <div class=\"right-column\">\n <div style=\"position: relative; min-height: 60px\">\n <img [src]=\"conversation?.assets?.bannerImg?.url || 'assets/images/default_banner.webp'\" class=\"main-banner-image-card\" />\n @if(!conversation?.assets?.bannerImg?.url && agentCardId) {\n\n <dc-cropper-modal\n style=\"position: absolute; bottom: 10px; right: 10px\"\n #cropperBanner\n id=\"cropperBanner\"\n [buttonLabel]=\"conversation?.assets?.bannerImg?.url ? 'Cambiar el banner' : 'Cargar un banner'\"\n [imgStorageSettings]=\"bannerImgSettings\"\n [currentStorage]=\"conversation?.assets?.bannerImg\"\n (onFileSelected)=\"onImageSelected($event)\"\n (imageUploaded)=\"onImageUploaded($event, 'bannerImg')\"></dc-cropper-modal>\n\n }\n </div>\n <div style=\"position: relative\">\n <img [src]=\"conversation?.assets?.image?.url || 'assets/images/default_2_3.webp'\" class=\"main-image-card\" />\n @if (!agentCardId) {\n <button pButton (click)=\"saveConversation()\"> Guarda el scenario para subir la imagen</button>\n } @else {\n\n <dc-cropper-modal\n style=\"position: absolute; bottom: 10px; left: 50%\"\n id=\"cropperCardImage\"\n #cropperCardImage\n [buttonLabel]=\"conversation?.assets?.image?.url ? 'Cambiar imagen' : 'Cargar una imagen'\"\n [imgStorageSettings]=\"imageStorageSettings\"\n (onFileSelected)=\"onImageSelected($event)\"\n (imageUploaded)=\"onImageUploaded($event, 'image')\"></dc-cropper-modal>\n }\n </div>\n\n <div>\n <h4>Agregar stickers</h4>\n\n <dc-cropper-modal\n id=\"cropperCardImage\"\n #cropperStickers\n [buttonLabel]=\"'agregar sticker'\"\n [imgStorageSettings]=\"stickerStorageSettings\"\n (onFileSelected)=\"onImageSelected($event)\"\n (imageUploaded)=\"onImageUploaded($event, 'sticker')\"></dc-cropper-modal>\n </div>\n\n <div style=\"display: flex; flex-wrap: wrap; gap: 10px\">\n @for (sticker of conversation?.assets?.stickers; track sticker.url) {\n <div style=\"position: relative\">\n <img width=\"100\" [src]=\"sticker.url\" alt=\"\" />\n <p-button (click)=\"removeSticker(sticker)\" class=\"remove-sticker\" icon=\"pi pi-times\" [rounded]=\"true\" [text]=\"true\" severity=\"danger\" />\n </div>\n }\n </div>\n\n <!-- <input pInputText type=\"file\" accept=\"image/*\" (change)=\"onImageSelected($event)\" /> -->\n\n <div formGroupName=\"characterCard\">\n <div formGroupName=\"data\" class=\"card-group\">\n <h3>Character Card <span pTooltip=\"Informaci\u00F3n de la ficha del personaje\">\u2139\uFE0F</span></h3>\n <div class=\"form-field\">\n <label for=\"cardName\">Name <span pTooltip=\"El nombre del personaje\">\u2139\uFE0F</span></label>\n <input pInputText id=\"cardName\" type=\"text\" formControlName=\"name\" />\n <div class=\"error\" *ngIf=\"form.get('characterCard.data.name')?.errors?.['required'] && form.get('characterCard.data.name')?.touched\">\n Name is required\n </div>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardDescription\">Description <span pTooltip=\"Descripci\u00F3n detallada del personaje\">\u2139\uFE0F</span></label>\n <textarea class=\"textmin\" rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardDescription\" formControlName=\"description\"></textarea>\n <div class=\"error\" *ngIf=\"form.get('characterCard.data.description')?.errors?.['required'] && form.get('characterCard.data.description')?.touched\">\n Description is required\n </div>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardScenario\">Scenario <span pTooltip=\"Describe the context or setting for the conversation\">\u2139\uFE0F</span></label>\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardScenario\" formControlName=\"scenario\"></textarea>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardFirstMessage\">\n First Message\n <span pTooltip=\"Es muy importante que la historia inicie bien, ya que es el patr\u00F3n inicial para la AI, respetar las convenciones de texto\"\n >\u2139\uFE0F</span\n >\n\n <p-togglebutton\n [formControl]=\"markdownForm.controls.seeMarkdown\"\n [onLabel]=\"'Editar'\"\n [offLabel]=\"'Ver Markdown Texto'\"\n size=\"small\"\n styleClass=\"min-w-16\"\n (onChange)=\"checkCdr()\" />\n </label>\n\n @if(markdownForm.controls.seeMarkdown.value){\n <div [innerHTML]=\"form.controls.characterCard.controls.data.controls.first_mes.value | mdToHtmlArray\"></div>\n }@else{\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardFirstMessage\" formControlName=\"first_mes\"> </textarea>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"mes_example\">Mensajes de Ejemplo <span pTooltip=\"Importante para el estilo de la conversaci\u00F3n\">\u2139\uFE0F</span></label>\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"mes_example\" formControlName=\"mes_example\"></textarea>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardCreatorNotes\">Creator Notes <span pTooltip=\"son solo notas del creador, no afecta nada a la conversaci\u00F3n\">\u2139\uFE0F</span></label>\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardCreatorNotes\" formControlName=\"creator_notes\"></textarea>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardSystemPrompt\">System Prompt (Opcional) <span pTooltip=\"Instrucciones del sistema para la conversaci\u00F3n\">\u2139\uFE0F</span></label>\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardSystemPrompt\" formControlName=\"system_prompt\"></textarea>\n <div\n class=\"error\"\n *ngIf=\"form.get('characterCard.data.system_prompt')?.errors?.['required'] && form.get('characterCard.data.system_prompt')?.touched\">\n System prompt is required\n </div>\n </div>\n\n <div style=\"display: flex; flex-direction: column\">\n <label for=\"cardPostHistoryInstructions\"\n >Post-History Instructions (Opcional)\n <span\n pTooltip=\"Dejar en blanco, al menos que se sepa como funciona, esto se llama jailbreak, es para darle instrucciones finales y m\u00E1s importantes al modelo\"\n >\u2139\uFE0F</span\n ></label\n >\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" formControlName=\"post_history_instructions\"></textarea>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardAlternateGreetings\">Alternate Greetings <span pTooltip=\"Saludos alternativos para comenzar una historia diferente\">\u2139\uFE0F</span></label>\n <div class=\"array-field\">\n <div\n *ngFor=\"let greeting of form.controls.characterCard.controls.data.controls.alternate_greetings.controls; let i = index\"\n class=\"array-item\"\n style=\"position: relative\">\n <textarea\n pTextarea\n rows=\"1\"\n [autoResize]=\"true\"\n [id]=\"'cardAlternateGreeting' + i\"\n [formControl]=\"greeting\"\n (input)=\"updateArrayField('alternate_greetings', i, $event)\">\n </textarea>\n <button pButton severity=\"danger\" class=\"remove-button\" (click)=\"removeArrayItem('alternate_greetings', i)\">✖</button>\n </div>\n <button pButton severity=\"info\" (click)=\"addArrayItem('alternate_greetings')\">Add Greeting</button>\n </div>\n </div>\n\n <div class=\"form-field\">\n <label pTooltip=\"Agrega las categorias\" for=\"cardTags\">Tags \u2139\uFE0F</label>\n <div class=\"array-field\">\n <div *ngFor=\"let tag of form.controls.characterCard.controls.data.controls.tags.controls; let i = index\" class=\"array-item\">\n <input [id]=\"'cardTag' + i\" type=\"text\" [formControl]=\"tag\" (input)=\"updateArrayField('tags', i, $event)\" />\n <button pButton severity=\"danger\" (click)=\"removeArrayItem('tags', i)\">Remove</button>\n </div>\n <button pButton severity=\"info\" (click)=\"addArrayItem('tags')\">Add Tag</button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</form>\n\n<p-popover #textEngineDialog header=\"Text Engine Information\">\n <div class=\"p-4\">\n <h3>Text Engine Types</h3>\n <ul>\n <li> <strong>Texto Simple</strong> La conversaci\u00F3n es como chatgpt, preguntas y responde, es la m\u00E1s b\u00E1sica</li>\n\n <li\n ><strong>Multi Mensajes</strong> Utiliza markdown (recomendable entenderlo), sirve para darle formato al texto y sea m\u00E1s agradable de leer, el sistema\n puede partir dialogos que tienen distinto formato, como normal, cursiva y negritas, asi puede generar distintas voces y estilo para el narrador y\n personaje principal</li\n >\n <li\n ><strong>MD SSML :</strong> Markdown con Lenguaje de marcaci\u00F3n de s\u00EDntesis de voz (SSML), es tambien markdown pero a diferencia de multimessage, solo se\n presenta un mensaje. y la voz se genera para toda la linea,normalmente lo uso para conversaciones bilingues.</li\n >\n </ul>\n </div>\n</p-popover>\n\n<div class=\"float-button\">\n <p-button icon=\"pi pi-save\" (click)=\"saveConversation()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n</div>\n", styles: [".textmin{min-width:36vw}.main-image-card{max-width:280px;display:block;margin:0 auto;border-radius:8px}.main-banner-image-card{border-radius:8px}.remove-sticker{position:absolute;top:5px;right:5px}.conversation-form{max-width:100%;padding:20px;background-color:#fff;border-radius:8px;box-shadow:0 2px 4px #0000001a}.conversation-form .card-group{background-color:#f8f9fa;padding:20px;border-radius:6px;margin-bottom:24px}.conversation-form .card-group h3{margin:0 0 20px;color:#2c3e50;font-size:1.25rem}.conversation-form .form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:2rem;width:100%;max-width:100%}@media (max-width: 768px){.conversation-form .form-grid{grid-template-columns:1fr}}.conversation-form .form-field{margin-bottom:1.5rem;display:flex;flex-direction:column;gap:.5rem}.conversation-form .form-field label{font-weight:500}.conversation-form .form-field textarea{resize:vertical}.conversation-form .form-field.checkbox{flex-direction:row;align-items:center;gap:.5rem}.conversation-form .form-field.checkbox input[type=checkbox]{width:auto}.conversation-form .form-field .error{color:#dc3545;font-size:.875rem;margin-top:.25rem}.conversation-form .form-field .remove-button{position:absolute;border:none;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;cursor:pointer;top:-10px;right:-10px}.conversation-form .left-column,.conversation-form .right-column{display:flex;flex-direction:column;gap:1rem}.conversation-form .array-field{display:flex;flex-direction:column;gap:.5rem}.conversation-form .array-field .array-item{display:flex;gap:.5rem}.conversation-form .array-field .array-item input,.conversation-form .array-field .array-item textarea{flex:1}.conversation-form .array-field .array-item button{padding:.5rem}.conversation-form .array-field button[type=button]{background-color:#28a745;color:#fff;border:none;padding:8px 12px;border-radius:4px;cursor:pointer;transition:background-color .2s}.conversation-form .array-field button[type=button]:hover{background-color:#218838}.conversation-form .group,.conversation-form .meta-group,.conversation-form .card-group{background-color:#f8f9fa;padding:1rem;border-radius:4px;margin-bottom:1.5rem}.conversation-form .group h3,.conversation-form .meta-group h3,.conversation-form .card-group h3{margin-top:0;margin-bottom:1rem}.top-buttons{display:flex;justify-content:space-between;margin-bottom:2rem;gap:1rem}.top-buttons button{flex:1}::ng-deep em{font-weight:900;color:#014a93}.float-button{position:fixed;bottom:4rem;right:2rem;z-index:1000;display:flex;gap:1px}.float-button :host ::ng-deep .p-button{width:4rem;height:4rem;border-radius:50%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "component", type: CropperComponentModal, selector: "dc-cropper-modal", inputs: ["imgStorageSettings", "buttonLabel", "currentStorage"], outputs: ["imageUploaded", "onImageCropped", "onFileSelected"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "ngmodule", type: PortalModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i7.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "component", type: i7.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i3.Textarea, selector: "[pTextarea]", inputs: ["autoResize", "variant", "fluid", "pSize"], outputs: ["onResize"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i5$1.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }, { kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i4$1.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["value", "name", "disabled", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "style", "inputStyle", "styleClass", "inputClass", "indeterminate", "size", "formControl", "checkboxIcon", "readonly", "required", "autofocus", "trueValue", "falseValue", "variant"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ToggleButtonModule }, { kind: "component", type: i11.ToggleButton, selector: "p-toggleButton, p-togglebutton, p-toggle-button", inputs: ["onLabel", "offLabel", "onIcon", "offIcon", "ariaLabel", "ariaLabelledBy", "disabled", "style", "styleClass", "inputId", "tabindex", "size", "iconPos", "autofocus", "allowEmpty"], outputs: ["onChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i7$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "ngmodule", type: ToggleSwitchModule }, { kind: "component", type: i13.ToggleSwitch, selector: "p-toggleswitch, p-toggleSwitch, p-toggle-switch", inputs: ["style", "styleClass", "tabindex", "inputId", "name", "disabled", "readonly", "trueValue", "falseValue", "ariaLabel", "ariaLabelledBy", "autofocus"], outputs: ["onChange"] }, { kind: "pipe", type: MdToHtmlArrayPipe, name: "mdToHtmlArray" }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i14.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "name", "style", "panelStyle", "styleClass", "panelStyleClass", "readonly", "required", "editable", "appendTo", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "variant", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "autoDisplayFirst", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "size", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "maxlength", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "fluid", "disabled", "itemSize", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "filterValue", "options"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: DynamicDialogModule }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i15.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions"], outputs: ["onShow", "onHide"] }, { kind: "component", type: ProviderSelectorComponent, selector: "dc-provider-selector", inputs: ["parentForm"] }, { kind: "component", type: AccountPlatformForm, selector: "account-platform-form", inputs: ["formArray"] }] }); }
|
|
2992
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCAgentCardFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2993
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: DCAgentCardFormComponent, isStandalone: true, selector: "dc-agent-form", inputs: { storageSettings: { classPropertyName: "storageSettings", publicName: "storageSettings", isSignal: true, isRequired: false, transformFunction: null }, bannerImgSettings: { classPropertyName: "bannerImgSettings", publicName: "bannerImgSettings", isSignal: true, isRequired: false, transformFunction: null }, imageStorageSettings: { classPropertyName: "imageStorageSettings", publicName: "imageStorageSettings", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onImageLoaded: "onImageLoaded", onSave: "onSave", onGoDetails: "onGoDetails", onTranslate: "onTranslate" }, providers: [DialogService], ngImport: i0, template: "<div class=\"top-buttons\">\n <button pButton severity=\"info\" (click)=\"checkPrompt()\" label=\"\uD83D\uDC41\uFE0F Ver instrucciones finales \uD83D\uDCD3\"></button>\n\n <button pButton severity=\"info\" (click)=\"goToDetails()\" label=\"\uD83D\uDCAC Conversar\"></button>\n <button pButton severity=\"primary\" (click)=\"saveConversation()\" label=\"\uD83D\uDCBE Guardar cambios\"></button>\n</div>\n\n<div class=\"top-buttons\">\n <p-button severity=\"help\" (click)=\"translate()\" label=\"\uD83D\uDD04 Traducir\"></p-button>\n <p-button [loading]=\"isGenerating\" severity=\"help\" (click)=\"generateCharacter()\" label=\"Generar \uD83E\uDDBE\"></p-button>\n\n <p-button severity=\"info\" (click)=\"downloadConversation()\" label=\"\uD83D\uDCC1 Exportar \u2B07\uFE0F\"></p-button>\n <p-button severity=\"info\" (click)=\"importConversation()\" label=\"\uD83C\uDCCF Importar \u2B06\uFE0F\"></p-button>\n</div>\n\n<br />\n<br />\n<form [formGroup]=\"form\" class=\"conversation-form\">\n <div class=\"form-grid\">\n <div class=\"left-column\">\n <div style=\"display: flex; gap: 15px\">\n <div class=\"form-field\">\n <label for=\"version\">Version: {{ form.controls.version.value }} <span pTooltip=\"Version number of the conversation\">\u2139\uFE0F</span></label>\n </div>\n\n <div class=\"form-field\">\n <label for=\"id\"\n >ID: <span pTooltip=\"Unique identifier for this conversation\"> {{ form.controls.id.value }} \u2139\uFE0F</span></label\n >\n </div>\n </div>\n\n <div class=\"form-field\">\n <label for=\"title\">Title <span pTooltip=\"T\u00EDtulo de la conversaci\u00F3n\">\u2139\uFE0F</span></label>\n <input pInputText id=\"title\" type=\"text\" formControlName=\"title\" />\n @if(form.controls.title.errors?.['required'] && form.controls.title.touched){\n <div class=\"error\"> Title is required </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"lang\">Language <span pTooltip=\"Select the primary language for the conversation\">\u2139\uFE0F</span></label>\n <p-select\n id=\"lang\"\n [options]=\"languageOptions\"\n formControlName=\"lang\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Language'\"></p-select>\n </div>\n\n <div formGroupName=\"conversationSettings\" class=\"group\">\n <h3>Conversation Settings <span pTooltip=\"Additional information about the conversation\">\u2139\uFE0F</span></h3>\n\n <div class=\"form-field\">\n <label for=\"textEngine\">\n Text Engine\n <span\n class=\"cursor-pointer\"\n (click)=\"textEngineDialog.toggle($event)\"\n pTooltip=\"Sistema de generaci\u00F3n de texto y audios. Client: el cliente llama al servidor en cada dialogo de voz/personaje, es optimo para historias, Server SSML: se sintetiza todo el audio en uno solo con los distintos cambios de voz/personaje, util para la reflexi\u00F3n porque es bilingue, utiliza dialogos en ingles y espa\u00F1ol en el mismo dialogo/audio\"\n >\u2139\uFE0F</span\n >\n </label>\n\n <p-select\n id=\"textEngine\"\n [options]=\"textEngineOptions\"\n formControlName=\"textEngine\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Text Engine'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"conversationType\">Conversation Type <span pTooltip=\"Choose the type of conversation interaction\">\u2139\uFE0F</span></label>\n <p-select\n id=\"conversationType\"\n [options]=\"conversationOptions\"\n formControlName=\"conversationType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Conversation Type'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label> Auto Start <span pTooltip=\"Start conversation automatically\">\u2139\uFE0F</span> </label>\n <p-toggleSwitch formControlName=\"autoStart\"> </p-toggleSwitch>\n </div>\n\n <div formGroupName=\"tts\" class=\"group\">\n <h3>TTS Settings <span pTooltip=\"Text-to-Speech configuration options\">\u2139\uFE0F</span></h3>\n\n <div class=\"form-field\">\n <label for=\"voice\">Voice <span pTooltip=\"Select the primary voice for text-to-speech\">\u2139\uFE0F</span></label>\n <p-select\n id=\"voice\"\n [options]=\"voiceTTSOptions\"\n formControlName=\"voice\"\n optionLabel=\"name\"\n optionValue=\"id\"\n [placeholder]=\"'Select Voice'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"secondaryVoice\">Secondary Voice <span pTooltip=\"Select an alternative voice for text-to-speech\">\u2139\uFE0F</span></label>\n <p-select\n id=\"secondaryVoice\"\n [options]=\"voiceTTSOptions\"\n formControlName=\"secondaryVoice\"\n optionLabel=\"name\"\n optionValue=\"id\"\n [placeholder]=\"'Select Secondary Voice'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"speed\">Speed <span pTooltip=\"Set the speech rate for text-to-speech conversion\">\u2139\uFE0F</span></label>\n <p-select\n id=\"speed\"\n [options]=\"audioSpeedOptions\"\n formControlName=\"speed\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Speed'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"speedRate\">Speed Rate <span pTooltip=\"Adjust the rate of speech delivery\">\u2139\uFE0F</span></label>\n <input pInputText id=\"speedRate\" type=\"number\" formControlName=\"speedRate\" step=\"0.1\" />\n </div>\n </div>\n </div>\n\n <div formGroupName=\"metaApp\" class=\"group\">\n <h3>Meta Information <span pTooltip=\"Additional information about the conversation\">\u2139\uFE0F</span></h3>\n <div class=\"form-field\">\n <label for=\"authorId\">Author ID <span pTooltip=\"Unique identifier for the conversation author\">\u2139\uFE0F</span></label>\n <input pInputText id=\"authorId\" type=\"text\" formControlName=\"authorId\" />\n </div>\n\n <div class=\"form-field\">\n <label for=\"authorEmail\">Author Email \u2139\uFE0F</label>\n <input pInputText id=\"authorEmail\" type=\"email\" formControlName=\"authorEmail\" />\n @if (form.get('metaApp.authorEmail')?.errors?.['email'] && form.get('metaApp.authorEmail')?.touched) {\n <div class=\"error\">\n Please enter a valid email address\n </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"takenCount\"\n >Taken Count <span pTooltip=\"Es el contador de cuantas veces se ha tomado esta conversaci\u00F3n, no sirve por ahora\"> \u2139\uFE0F</span></label\n >\n <input pInputText id=\"takenCount\" type=\"number\" formControlName=\"takenCount\" />\n </div>\n\n <div class=\"form-field checkbox\">\n <label>\n <p-checkbox [binary]=\"true\" formControlName=\"isPublic\" />\n Public\n </label>\n </div>\n\n <div class=\"form-field checkbox\">\n <label>\n <p-checkbox [binary]=\"true\" formControlName=\"isPublished\" />\n Published\n </label>\n </div>\n </div>\n\n <div class=\"group\">\n <h4>Model Settings <span pTooltip=\"AI model configuration\">\u2139\uFE0F</span></h4>\n\n <dc-provider-selector [parentForm]=\"form.controls.model\"></dc-provider-selector>\n </div>\n\n <div class=\"group\">\n <h4>Gestion de cuentas</h4>\n @if(form.controls.accounts){\n <account-platform-form [formArray]=\"form.controls.accounts\"></account-platform-form>\n\n }\n </div>\n </div>\n\n <div class=\"right-column\">\n <div style=\"position: relative; min-height: 60px\">\n <img [src]=\"conversation?.assets?.bannerImg?.url || 'assets/images/default_banner.webp'\" class=\"main-banner-image-card\" />\n @if(!conversation?.assets?.bannerImg?.url && agentCardId) {\n\n <dc-cropper-modal\n style=\"position: absolute; bottom: 10px; right: 10px\"\n #cropperBanner\n id=\"cropperBanner\"\n [buttonLabel]=\"conversation?.assets?.bannerImg?.url ? 'Cambiar el banner' : 'Cargar un banner'\"\n [imgStorageSettings]=\"bannerImgSettings()\"\n [currentStorage]=\"conversation?.assets?.bannerImg\"\n (onFileSelected)=\"onImageSelected($event)\"\n (imageUploaded)=\"onImageUploaded($event, 'bannerImg')\"></dc-cropper-modal>\n\n }\n </div>\n <div style=\"position: relative\">\n <img [src]=\"conversation?.assets?.image?.url || 'assets/images/default_2_3.webp'\" class=\"main-image-card\" />\n @if (!agentCardId) {\n <button pButton (click)=\"saveConversation()\"> Guarda el scenario para subir la imagen</button>\n } @else {\n\n <dc-cropper-modal\n style=\"position: absolute; bottom: 10px; left: 50%\"\n id=\"cropperCardImage\"\n #cropperCardImage\n [buttonLabel]=\"conversation?.assets?.image?.url ? 'Cambiar imagen' : 'Cargar una imagen'\"\n [imgStorageSettings]=\"imageStorageSettings()\"\n (onFileSelected)=\"onImageSelected($event)\"\n (imageUploaded)=\"onImageUploaded($event, 'image')\"></dc-cropper-modal>\n }\n </div>\n\n <div>\n <h4>Agregar stickers</h4>\n\n <dc-cropper-modal\n id=\"cropperCardImage\"\n #cropperStickers\n [buttonLabel]=\"'agregar sticker'\"\n [imgStorageSettings]=\"stickerStorageSettings\"\n (onFileSelected)=\"onImageSelected($event)\"\n (imageUploaded)=\"onImageUploaded($event, 'sticker')\"></dc-cropper-modal>\n </div>\n\n <div style=\"display: flex; flex-wrap: wrap; gap: 10px\">\n @for (sticker of conversation?.assets?.stickers; track sticker.url) {\n <div style=\"position: relative\">\n <img width=\"100\" [src]=\"sticker.url\" alt=\"\" />\n <p-button (click)=\"removeSticker(sticker)\" class=\"remove-sticker\" icon=\"pi pi-times\" [rounded]=\"true\" [text]=\"true\" severity=\"danger\" />\n </div>\n }\n </div>\n\n <!-- <input pInputText type=\"file\" accept=\"image/*\" (change)=\"onImageSelected($event)\" /> -->\n\n <div formGroupName=\"characterCard\">\n <div formGroupName=\"data\" class=\"card-group\">\n <h3>Character Card <span pTooltip=\"Informaci\u00F3n de la ficha del personaje\">\u2139\uFE0F</span></h3>\n <div class=\"form-field\">\n <label for=\"cardName\">Name <span pTooltip=\"El nombre del personaje\">\u2139\uFE0F</span></label>\n <input pInputText id=\"cardName\" type=\"text\" formControlName=\"name\" />\n @if (form.get('characterCard.data.name')?.errors?.['required'] && form.get('characterCard.data.name')?.touched) {\n <div class=\"error\">\n Name is required\n </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardDescription\">Description <span pTooltip=\"Descripci\u00F3n detallada del personaje\">\u2139\uFE0F</span></label>\n <textarea class=\"textmin\" rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardDescription\" formControlName=\"description\"></textarea>\n @if (form.get('characterCard.data.description')?.errors?.['required'] && form.get('characterCard.data.description')?.touched) {\n <div class=\"error\">\n Description is required\n </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardScenario\">Scenario <span pTooltip=\"Describe the context or setting for the conversation\">\u2139\uFE0F</span></label>\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardScenario\" formControlName=\"scenario\"></textarea>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardFirstMessage\">\n First Message\n <span pTooltip=\"Es muy importante que la historia inicie bien, ya que es el patr\u00F3n inicial para la AI, respetar las convenciones de texto\"\n >\u2139\uFE0F</span\n >\n\n <p-togglebutton\n [formControl]=\"markdownForm.controls.seeMarkdown\"\n [onLabel]=\"'Editar'\"\n [offLabel]=\"'Ver Markdown Texto'\"\n size=\"small\"\n styleClass=\"min-w-16\"\n (onChange)=\"checkCdr()\" />\n </label>\n\n @if(markdownForm.controls.seeMarkdown.value){\n <div [innerHTML]=\"form.controls.characterCard.controls.data.controls.first_mes.value | mdToHtmlArray\"></div>\n }@else{\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardFirstMessage\" formControlName=\"first_mes\"> </textarea>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"mes_example\">Mensajes de Ejemplo <span pTooltip=\"Importante para el estilo de la conversaci\u00F3n\">\u2139\uFE0F</span></label>\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"mes_example\" formControlName=\"mes_example\"></textarea>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardCreatorNotes\">Creator Notes <span pTooltip=\"son solo notas del creador, no afecta nada a la conversaci\u00F3n\">\u2139\uFE0F</span></label>\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardCreatorNotes\" formControlName=\"creator_notes\"></textarea>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardSystemPrompt\">System Prompt (Opcional) <span pTooltip=\"Instrucciones del sistema para la conversaci\u00F3n\">\u2139\uFE0F</span></label>\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardSystemPrompt\" formControlName=\"system_prompt\"></textarea>\n @if (form.get('characterCard.data.system_prompt')?.errors?.['required'] && form.get('characterCard.data.system_prompt')?.touched) {\n <div\n class=\"error\"\n >\n System prompt is required\n </div>\n }\n </div>\n\n <div style=\"display: flex; flex-direction: column\">\n <label for=\"cardPostHistoryInstructions\"\n >Post-History Instructions (Opcional)\n <span\n pTooltip=\"Dejar en blanco, al menos que se sepa como funciona, esto se llama jailbreak, es para darle instrucciones finales y m\u00E1s importantes al modelo\"\n >\u2139\uFE0F</span\n ></label\n >\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" formControlName=\"post_history_instructions\"></textarea>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardAlternateGreetings\">Alternate Greetings <span pTooltip=\"Saludos alternativos para comenzar una historia diferente\">\u2139\uFE0F</span></label>\n <div class=\"array-field\">\n @for (greeting of form.controls.characterCard.controls.data.controls.alternate_greetings.controls; track greeting; let i = $index) {\n <div\n class=\"array-item\"\n style=\"position: relative\">\n <textarea\n pTextarea\n rows=\"1\"\n [autoResize]=\"true\"\n [id]=\"'cardAlternateGreeting' + i\"\n [formControl]=\"greeting\"\n (input)=\"updateArrayField('alternate_greetings', i, $event)\">\n </textarea>\n <button pButton severity=\"danger\" class=\"remove-button\" (click)=\"removeArrayItem('alternate_greetings', i)\">✖</button>\n </div>\n }\n <button pButton severity=\"info\" (click)=\"addArrayItem('alternate_greetings')\">Add Greeting</button>\n </div>\n </div>\n\n <div class=\"form-field\">\n <label pTooltip=\"Agrega las categorias\" for=\"cardTags\">Tags \u2139\uFE0F</label>\n <div class=\"array-field\">\n @for (tag of form.controls.characterCard.controls.data.controls.tags.controls; track tag; let i = $index) {\n <div class=\"array-item\">\n <input [id]=\"'cardTag' + i\" type=\"text\" [formControl]=\"tag\" (input)=\"updateArrayField('tags', i, $event)\" />\n <button pButton severity=\"danger\" (click)=\"removeArrayItem('tags', i)\">Remove</button>\n </div>\n }\n <button pButton severity=\"info\" (click)=\"addArrayItem('tags')\">Add Tag</button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </form>\n\n <p-popover #textEngineDialog header=\"Text Engine Information\">\n <div class=\"p-4\">\n <h3>Text Engine Types</h3>\n <ul>\n <li> <strong>Texto Simple</strong> La conversaci\u00F3n es como chatgpt, preguntas y responde, es la m\u00E1s b\u00E1sica</li>\n\n <li\n ><strong>Multi Mensajes</strong> Utiliza markdown (recomendable entenderlo), sirve para darle formato al texto y sea m\u00E1s agradable de leer, el sistema\n puede partir dialogos que tienen distinto formato, como normal, cursiva y negritas, asi puede generar distintas voces y estilo para el narrador y\n personaje principal</li\n >\n <li\n ><strong>MD SSML :</strong> Markdown con Lenguaje de marcaci\u00F3n de s\u00EDntesis de voz (SSML), es tambien markdown pero a diferencia de multimessage, solo se\n presenta un mensaje. y la voz se genera para toda la linea,normalmente lo uso para conversaciones bilingues.</li\n >\n </ul>\n </div>\n </p-popover>\n\n <div class=\"float-button\">\n <p-button icon=\"pi pi-save\" (click)=\"saveConversation()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n </div>\n", styles: [".textmin{min-width:36vw}.main-image-card{max-width:280px;display:block;margin:0 auto;border-radius:8px}.main-banner-image-card{border-radius:8px}.remove-sticker{position:absolute;top:5px;right:5px}.conversation-form{max-width:100%;padding:20px;background-color:#fff;border-radius:8px;box-shadow:0 2px 4px #0000001a}.conversation-form .card-group{background-color:#f8f9fa;padding:20px;border-radius:6px;margin-bottom:24px}.conversation-form .card-group h3{margin:0 0 20px;color:#2c3e50;font-size:1.25rem}.conversation-form .form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:2rem;width:100%;max-width:100%}@media (max-width: 768px){.conversation-form .form-grid{grid-template-columns:1fr}}.conversation-form .form-field{margin-bottom:1.5rem;display:flex;flex-direction:column;gap:.5rem}.conversation-form .form-field label{font-weight:500}.conversation-form .form-field textarea{resize:vertical}.conversation-form .form-field.checkbox{flex-direction:row;align-items:center;gap:.5rem}.conversation-form .form-field.checkbox input[type=checkbox]{width:auto}.conversation-form .form-field .error{color:#dc3545;font-size:.875rem;margin-top:.25rem}.conversation-form .form-field .remove-button{position:absolute;border:none;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;cursor:pointer;top:-10px;right:-10px}.conversation-form .left-column,.conversation-form .right-column{display:flex;flex-direction:column;gap:1rem}.conversation-form .array-field{display:flex;flex-direction:column;gap:.5rem}.conversation-form .array-field .array-item{display:flex;gap:.5rem}.conversation-form .array-field .array-item input,.conversation-form .array-field .array-item textarea{flex:1}.conversation-form .array-field .array-item button{padding:.5rem}.conversation-form .array-field button[type=button]{background-color:#28a745;color:#fff;border:none;padding:8px 12px;border-radius:4px;cursor:pointer;transition:background-color .2s}.conversation-form .array-field button[type=button]:hover{background-color:#218838}.conversation-form .group,.conversation-form .meta-group,.conversation-form .card-group{background-color:#f8f9fa;padding:1rem;border-radius:4px;margin-bottom:1.5rem}.conversation-form .group h3,.conversation-form .meta-group h3,.conversation-form .card-group h3{margin-top:0;margin-bottom:1rem}.top-buttons{display:flex;justify-content:space-between;margin-bottom:2rem;gap:1rem}.top-buttons button{flex:1}::ng-deep em{font-weight:900;color:#014a93}.float-button{position:fixed;bottom:4rem;right:2rem;z-index:1000;display:flex;gap:1px}.float-button :host ::ng-deep .p-button{width:4rem;height:4rem;border-radius:50%}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "component", type: CropperComponentModal, selector: "dc-cropper-modal", inputs: ["imgStorageSettings", "buttonLabel", "currentStorage"], outputs: ["imageUploaded", "onImageCropped", "onFileSelected"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "ngmodule", type: PortalModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "component", type: i2$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i3.Textarea, selector: "[pTextarea]", inputs: ["autoResize", "variant", "fluid", "pSize"], outputs: ["onResize"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4$1.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }, { kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i2$3.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["value", "name", "disabled", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "style", "inputStyle", "styleClass", "inputClass", "indeterminate", "size", "formControl", "checkboxIcon", "readonly", "required", "autofocus", "trueValue", "falseValue", "variant"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ToggleButtonModule }, { kind: "component", type: i6$2.ToggleButton, selector: "p-toggleButton, p-togglebutton, p-toggle-button", inputs: ["onLabel", "offLabel", "onIcon", "offIcon", "ariaLabel", "ariaLabelledBy", "disabled", "style", "styleClass", "inputId", "tabindex", "size", "iconPos", "autofocus", "allowEmpty"], outputs: ["onChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "ngmodule", type: ToggleSwitchModule }, { kind: "component", type: i8.ToggleSwitch, selector: "p-toggleswitch, p-toggleSwitch, p-toggle-switch", inputs: ["style", "styleClass", "tabindex", "inputId", "name", "disabled", "readonly", "trueValue", "falseValue", "ariaLabel", "ariaLabelledBy", "autofocus"], outputs: ["onChange"] }, { kind: "pipe", type: MdToHtmlArrayPipe, name: "mdToHtmlArray" }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i9.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "name", "style", "panelStyle", "styleClass", "panelStyleClass", "readonly", "required", "editable", "appendTo", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "variant", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "autoDisplayFirst", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "size", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "maxlength", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "fluid", "disabled", "itemSize", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "filterValue", "options"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: DynamicDialogModule }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i10.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions"], outputs: ["onShow", "onHide"] }, { kind: "component", type: ProviderSelectorComponent, selector: "dc-provider-selector", inputs: ["parentForm"] }, { kind: "component", type: AccountPlatformForm, selector: "account-platform-form", inputs: ["formArray"] }] }); }
|
|
3169
2994
|
}
|
|
3170
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
2995
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCAgentCardFormComponent, decorators: [{
|
|
3171
2996
|
type: Component,
|
|
3172
2997
|
args: [{ selector: 'dc-agent-form', standalone: true, providers: [DialogService], imports: [
|
|
3173
|
-
CommonModule,
|
|
3174
2998
|
ReactiveFormsModule,
|
|
3175
2999
|
CropperComponentModal,
|
|
3176
3000
|
OverlayModule,
|
|
@@ -3189,31 +3013,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImpor
|
|
|
3189
3013
|
DynamicDialogModule,
|
|
3190
3014
|
PopoverModule,
|
|
3191
3015
|
ProviderSelectorComponent,
|
|
3192
|
-
AccountPlatformForm
|
|
3193
|
-
], template: "<div class=\"top-buttons\">\n <button pButton severity=\"info\" (click)=\"checkPrompt()\" label=\"\uD83D\uDC41\uFE0F Ver instrucciones finales \uD83D\uDCD3\"></button>\n\n <button pButton severity=\"info\" (click)=\"goToDetails()\" label=\"\uD83D\uDCAC Conversar\"></button>\n <button pButton severity=\"primary\" (click)=\"saveConversation()\" label=\"\uD83D\uDCBE Guardar cambios\"></button>\n</div>\n\n<div class=\"top-buttons\">\n <p-button severity=\"help\" (click)=\"translate()\" label=\"\uD83D\uDD04 Traducir\"></p-button>\n <p-button [loading]=\"isGenerating\" severity=\"help\" (click)=\"generateCharacter()\" label=\"Generar \uD83E\uDDBE\"></p-button>\n\n <p-button severity=\"info\" (click)=\"downloadConversation()\" label=\"\uD83D\uDCC1 Exportar \u2B07\uFE0F\"></p-button>\n <p-button severity=\"info\" (click)=\"importConversation()\" label=\"\uD83C\uDCCF Importar \u2B06\uFE0F\"></p-button>\n</div>\n\n<br />\n<br />\n<form [formGroup]=\"form\" class=\"conversation-form\">\n <div class=\"form-grid\">\n <div class=\"left-column\">\n <div style=\"display: flex; gap: 15px\">\n <div class=\"form-field\">\n <label for=\"version\">Version: {{ form.controls.version.value }} <span pTooltip=\"Version number of the conversation\">\u2139\uFE0F</span></label>\n </div>\n\n <div class=\"form-field\">\n <label for=\"id\"\n >ID: <span pTooltip=\"Unique identifier for this conversation\"> {{ form.controls.id.value }} \u2139\uFE0F</span></label\n >\n </div>\n </div>\n\n <div class=\"form-field\">\n <label for=\"title\">Title <span pTooltip=\"T\u00EDtulo de la conversaci\u00F3n\">\u2139\uFE0F</span></label>\n <input pInputText id=\"title\" type=\"text\" formControlName=\"title\" />\n @if(form.controls.title.errors?.['required'] && form.controls.title.touched){\n <div class=\"error\"> Title is required </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"lang\">Language <span pTooltip=\"Select the primary language for the conversation\">\u2139\uFE0F</span></label>\n <p-select\n id=\"lang\"\n [options]=\"languageOptions\"\n formControlName=\"lang\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Language'\"></p-select>\n </div>\n\n <div formGroupName=\"conversationSettings\" class=\"group\">\n <h3>Conversation Settings <span pTooltip=\"Additional information about the conversation\">\u2139\uFE0F</span></h3>\n\n <div class=\"form-field\">\n <label for=\"textEngine\">\n Text Engine\n <span\n class=\"cursor-pointer\"\n (click)=\"textEngineDialog.toggle($event)\"\n pTooltip=\"Sistema de generaci\u00F3n de texto y audios. Client: el cliente llama al servidor en cada dialogo de voz/personaje, es optimo para historias, Server SSML: se sintetiza todo el audio en uno solo con los distintos cambios de voz/personaje, util para la reflexi\u00F3n porque es bilingue, utiliza dialogos en ingles y espa\u00F1ol en el mismo dialogo/audio\"\n >\u2139\uFE0F</span\n >\n </label>\n\n <p-select\n id=\"textEngine\"\n [options]=\"textEngineOptions\"\n formControlName=\"textEngine\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Text Engine'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"conversationType\">Conversation Type <span pTooltip=\"Choose the type of conversation interaction\">\u2139\uFE0F</span></label>\n <p-select\n id=\"conversationType\"\n [options]=\"conversationOptions\"\n formControlName=\"conversationType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Conversation Type'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label> Auto Start <span pTooltip=\"Start conversation automatically\">\u2139\uFE0F</span> </label>\n <p-toggleSwitch formControlName=\"autoStart\"> </p-toggleSwitch>\n </div>\n\n <div formGroupName=\"tts\" class=\"group\">\n <h3>TTS Settings <span pTooltip=\"Text-to-Speech configuration options\">\u2139\uFE0F</span></h3>\n\n <div class=\"form-field\">\n <label for=\"voice\">Voice <span pTooltip=\"Select the primary voice for text-to-speech\">\u2139\uFE0F</span></label>\n <p-select\n id=\"voice\"\n [options]=\"voiceTTSOptions\"\n formControlName=\"voice\"\n optionLabel=\"name\"\n optionValue=\"id\"\n [placeholder]=\"'Select Voice'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"secondaryVoice\">Secondary Voice <span pTooltip=\"Select an alternative voice for text-to-speech\">\u2139\uFE0F</span></label>\n <p-select\n id=\"secondaryVoice\"\n [options]=\"voiceTTSOptions\"\n formControlName=\"secondaryVoice\"\n optionLabel=\"name\"\n optionValue=\"id\"\n [placeholder]=\"'Select Secondary Voice'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"speed\">Speed <span pTooltip=\"Set the speech rate for text-to-speech conversion\">\u2139\uFE0F</span></label>\n <p-select\n id=\"speed\"\n [options]=\"audioSpeedOptions\"\n formControlName=\"speed\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Speed'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"speedRate\">Speed Rate <span pTooltip=\"Adjust the rate of speech delivery\">\u2139\uFE0F</span></label>\n <input pInputText id=\"speedRate\" type=\"number\" formControlName=\"speedRate\" step=\"0.1\" />\n </div>\n </div>\n </div>\n\n <div formGroupName=\"metaApp\" class=\"group\">\n <h3>Meta Information <span pTooltip=\"Additional information about the conversation\">\u2139\uFE0F</span></h3>\n <div class=\"form-field\">\n <label for=\"authorId\">Author ID <span pTooltip=\"Unique identifier for the conversation author\">\u2139\uFE0F</span></label>\n <input pInputText id=\"authorId\" type=\"text\" formControlName=\"authorId\" />\n </div>\n\n <div class=\"form-field\">\n <label for=\"authorEmail\">Author Email \u2139\uFE0F</label>\n <input pInputText id=\"authorEmail\" type=\"email\" formControlName=\"authorEmail\" />\n <div class=\"error\" *ngIf=\"form.get('metaApp.authorEmail')?.errors?.['email'] && form.get('metaApp.authorEmail')?.touched\">\n Please enter a valid email address\n </div>\n </div>\n\n <div class=\"form-field\">\n <label for=\"takenCount\"\n >Taken Count <span pTooltip=\"Es el contador de cuantas veces se ha tomado esta conversaci\u00F3n, no sirve por ahora\"> \u2139\uFE0F</span></label\n >\n <input pInputText id=\"takenCount\" type=\"number\" formControlName=\"takenCount\" />\n </div>\n\n <div class=\"form-field checkbox\">\n <label>\n <p-checkbox [binary]=\"true\" formControlName=\"isPublic\" />\n Public\n </label>\n </div>\n\n <div class=\"form-field checkbox\">\n <label>\n <p-checkbox [binary]=\"true\" formControlName=\"isPublished\" />\n Published\n </label>\n </div>\n </div>\n\n <div class=\"group\">\n <h4>Model Settings <span pTooltip=\"AI model configuration\">\u2139\uFE0F</span></h4>\n\n <dc-provider-selector [parentForm]=\"form.controls.model\"></dc-provider-selector>\n </div>\n\n <div class=\"group\">\n <h4>Gestion de cuentas</h4>\n @if(form.controls.accounts){\n <account-platform-form [formArray]=\"form.controls.accounts\"></account-platform-form>\n\n }\n </div>\n </div>\n\n <div class=\"right-column\">\n <div style=\"position: relative; min-height: 60px\">\n <img [src]=\"conversation?.assets?.bannerImg?.url || 'assets/images/default_banner.webp'\" class=\"main-banner-image-card\" />\n @if(!conversation?.assets?.bannerImg?.url && agentCardId) {\n\n <dc-cropper-modal\n style=\"position: absolute; bottom: 10px; right: 10px\"\n #cropperBanner\n id=\"cropperBanner\"\n [buttonLabel]=\"conversation?.assets?.bannerImg?.url ? 'Cambiar el banner' : 'Cargar un banner'\"\n [imgStorageSettings]=\"bannerImgSettings\"\n [currentStorage]=\"conversation?.assets?.bannerImg\"\n (onFileSelected)=\"onImageSelected($event)\"\n (imageUploaded)=\"onImageUploaded($event, 'bannerImg')\"></dc-cropper-modal>\n\n }\n </div>\n <div style=\"position: relative\">\n <img [src]=\"conversation?.assets?.image?.url || 'assets/images/default_2_3.webp'\" class=\"main-image-card\" />\n @if (!agentCardId) {\n <button pButton (click)=\"saveConversation()\"> Guarda el scenario para subir la imagen</button>\n } @else {\n\n <dc-cropper-modal\n style=\"position: absolute; bottom: 10px; left: 50%\"\n id=\"cropperCardImage\"\n #cropperCardImage\n [buttonLabel]=\"conversation?.assets?.image?.url ? 'Cambiar imagen' : 'Cargar una imagen'\"\n [imgStorageSettings]=\"imageStorageSettings\"\n (onFileSelected)=\"onImageSelected($event)\"\n (imageUploaded)=\"onImageUploaded($event, 'image')\"></dc-cropper-modal>\n }\n </div>\n\n <div>\n <h4>Agregar stickers</h4>\n\n <dc-cropper-modal\n id=\"cropperCardImage\"\n #cropperStickers\n [buttonLabel]=\"'agregar sticker'\"\n [imgStorageSettings]=\"stickerStorageSettings\"\n (onFileSelected)=\"onImageSelected($event)\"\n (imageUploaded)=\"onImageUploaded($event, 'sticker')\"></dc-cropper-modal>\n </div>\n\n <div style=\"display: flex; flex-wrap: wrap; gap: 10px\">\n @for (sticker of conversation?.assets?.stickers; track sticker.url) {\n <div style=\"position: relative\">\n <img width=\"100\" [src]=\"sticker.url\" alt=\"\" />\n <p-button (click)=\"removeSticker(sticker)\" class=\"remove-sticker\" icon=\"pi pi-times\" [rounded]=\"true\" [text]=\"true\" severity=\"danger\" />\n </div>\n }\n </div>\n\n <!-- <input pInputText type=\"file\" accept=\"image/*\" (change)=\"onImageSelected($event)\" /> -->\n\n <div formGroupName=\"characterCard\">\n <div formGroupName=\"data\" class=\"card-group\">\n <h3>Character Card <span pTooltip=\"Informaci\u00F3n de la ficha del personaje\">\u2139\uFE0F</span></h3>\n <div class=\"form-field\">\n <label for=\"cardName\">Name <span pTooltip=\"El nombre del personaje\">\u2139\uFE0F</span></label>\n <input pInputText id=\"cardName\" type=\"text\" formControlName=\"name\" />\n <div class=\"error\" *ngIf=\"form.get('characterCard.data.name')?.errors?.['required'] && form.get('characterCard.data.name')?.touched\">\n Name is required\n </div>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardDescription\">Description <span pTooltip=\"Descripci\u00F3n detallada del personaje\">\u2139\uFE0F</span></label>\n <textarea class=\"textmin\" rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardDescription\" formControlName=\"description\"></textarea>\n <div class=\"error\" *ngIf=\"form.get('characterCard.data.description')?.errors?.['required'] && form.get('characterCard.data.description')?.touched\">\n Description is required\n </div>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardScenario\">Scenario <span pTooltip=\"Describe the context or setting for the conversation\">\u2139\uFE0F</span></label>\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardScenario\" formControlName=\"scenario\"></textarea>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardFirstMessage\">\n First Message\n <span pTooltip=\"Es muy importante que la historia inicie bien, ya que es el patr\u00F3n inicial para la AI, respetar las convenciones de texto\"\n >\u2139\uFE0F</span\n >\n\n <p-togglebutton\n [formControl]=\"markdownForm.controls.seeMarkdown\"\n [onLabel]=\"'Editar'\"\n [offLabel]=\"'Ver Markdown Texto'\"\n size=\"small\"\n styleClass=\"min-w-16\"\n (onChange)=\"checkCdr()\" />\n </label>\n\n @if(markdownForm.controls.seeMarkdown.value){\n <div [innerHTML]=\"form.controls.characterCard.controls.data.controls.first_mes.value | mdToHtmlArray\"></div>\n }@else{\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardFirstMessage\" formControlName=\"first_mes\"> </textarea>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"mes_example\">Mensajes de Ejemplo <span pTooltip=\"Importante para el estilo de la conversaci\u00F3n\">\u2139\uFE0F</span></label>\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"mes_example\" formControlName=\"mes_example\"></textarea>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardCreatorNotes\">Creator Notes <span pTooltip=\"son solo notas del creador, no afecta nada a la conversaci\u00F3n\">\u2139\uFE0F</span></label>\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardCreatorNotes\" formControlName=\"creator_notes\"></textarea>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardSystemPrompt\">System Prompt (Opcional) <span pTooltip=\"Instrucciones del sistema para la conversaci\u00F3n\">\u2139\uFE0F</span></label>\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardSystemPrompt\" formControlName=\"system_prompt\"></textarea>\n <div\n class=\"error\"\n *ngIf=\"form.get('characterCard.data.system_prompt')?.errors?.['required'] && form.get('characterCard.data.system_prompt')?.touched\">\n System prompt is required\n </div>\n </div>\n\n <div style=\"display: flex; flex-direction: column\">\n <label for=\"cardPostHistoryInstructions\"\n >Post-History Instructions (Opcional)\n <span\n pTooltip=\"Dejar en blanco, al menos que se sepa como funciona, esto se llama jailbreak, es para darle instrucciones finales y m\u00E1s importantes al modelo\"\n >\u2139\uFE0F</span\n ></label\n >\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" formControlName=\"post_history_instructions\"></textarea>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardAlternateGreetings\">Alternate Greetings <span pTooltip=\"Saludos alternativos para comenzar una historia diferente\">\u2139\uFE0F</span></label>\n <div class=\"array-field\">\n <div\n *ngFor=\"let greeting of form.controls.characterCard.controls.data.controls.alternate_greetings.controls; let i = index\"\n class=\"array-item\"\n style=\"position: relative\">\n <textarea\n pTextarea\n rows=\"1\"\n [autoResize]=\"true\"\n [id]=\"'cardAlternateGreeting' + i\"\n [formControl]=\"greeting\"\n (input)=\"updateArrayField('alternate_greetings', i, $event)\">\n </textarea>\n <button pButton severity=\"danger\" class=\"remove-button\" (click)=\"removeArrayItem('alternate_greetings', i)\">✖</button>\n </div>\n <button pButton severity=\"info\" (click)=\"addArrayItem('alternate_greetings')\">Add Greeting</button>\n </div>\n </div>\n\n <div class=\"form-field\">\n <label pTooltip=\"Agrega las categorias\" for=\"cardTags\">Tags \u2139\uFE0F</label>\n <div class=\"array-field\">\n <div *ngFor=\"let tag of form.controls.characterCard.controls.data.controls.tags.controls; let i = index\" class=\"array-item\">\n <input [id]=\"'cardTag' + i\" type=\"text\" [formControl]=\"tag\" (input)=\"updateArrayField('tags', i, $event)\" />\n <button pButton severity=\"danger\" (click)=\"removeArrayItem('tags', i)\">Remove</button>\n </div>\n <button pButton severity=\"info\" (click)=\"addArrayItem('tags')\">Add Tag</button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</form>\n\n<p-popover #textEngineDialog header=\"Text Engine Information\">\n <div class=\"p-4\">\n <h3>Text Engine Types</h3>\n <ul>\n <li> <strong>Texto Simple</strong> La conversaci\u00F3n es como chatgpt, preguntas y responde, es la m\u00E1s b\u00E1sica</li>\n\n <li\n ><strong>Multi Mensajes</strong> Utiliza markdown (recomendable entenderlo), sirve para darle formato al texto y sea m\u00E1s agradable de leer, el sistema\n puede partir dialogos que tienen distinto formato, como normal, cursiva y negritas, asi puede generar distintas voces y estilo para el narrador y\n personaje principal</li\n >\n <li\n ><strong>MD SSML :</strong> Markdown con Lenguaje de marcaci\u00F3n de s\u00EDntesis de voz (SSML), es tambien markdown pero a diferencia de multimessage, solo se\n presenta un mensaje. y la voz se genera para toda la linea,normalmente lo uso para conversaciones bilingues.</li\n >\n </ul>\n </div>\n</p-popover>\n\n<div class=\"float-button\">\n <p-button icon=\"pi pi-save\" (click)=\"saveConversation()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n</div>\n", styles: [".textmin{min-width:36vw}.main-image-card{max-width:280px;display:block;margin:0 auto;border-radius:8px}.main-banner-image-card{border-radius:8px}.remove-sticker{position:absolute;top:5px;right:5px}.conversation-form{max-width:100%;padding:20px;background-color:#fff;border-radius:8px;box-shadow:0 2px 4px #0000001a}.conversation-form .card-group{background-color:#f8f9fa;padding:20px;border-radius:6px;margin-bottom:24px}.conversation-form .card-group h3{margin:0 0 20px;color:#2c3e50;font-size:1.25rem}.conversation-form .form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:2rem;width:100%;max-width:100%}@media (max-width: 768px){.conversation-form .form-grid{grid-template-columns:1fr}}.conversation-form .form-field{margin-bottom:1.5rem;display:flex;flex-direction:column;gap:.5rem}.conversation-form .form-field label{font-weight:500}.conversation-form .form-field textarea{resize:vertical}.conversation-form .form-field.checkbox{flex-direction:row;align-items:center;gap:.5rem}.conversation-form .form-field.checkbox input[type=checkbox]{width:auto}.conversation-form .form-field .error{color:#dc3545;font-size:.875rem;margin-top:.25rem}.conversation-form .form-field .remove-button{position:absolute;border:none;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;cursor:pointer;top:-10px;right:-10px}.conversation-form .left-column,.conversation-form .right-column{display:flex;flex-direction:column;gap:1rem}.conversation-form .array-field{display:flex;flex-direction:column;gap:.5rem}.conversation-form .array-field .array-item{display:flex;gap:.5rem}.conversation-form .array-field .array-item input,.conversation-form .array-field .array-item textarea{flex:1}.conversation-form .array-field .array-item button{padding:.5rem}.conversation-form .array-field button[type=button]{background-color:#28a745;color:#fff;border:none;padding:8px 12px;border-radius:4px;cursor:pointer;transition:background-color .2s}.conversation-form .array-field button[type=button]:hover{background-color:#218838}.conversation-form .group,.conversation-form .meta-group,.conversation-form .card-group{background-color:#f8f9fa;padding:1rem;border-radius:4px;margin-bottom:1.5rem}.conversation-form .group h3,.conversation-form .meta-group h3,.conversation-form .card-group h3{margin-top:0;margin-bottom:1rem}.top-buttons{display:flex;justify-content:space-between;margin-bottom:2rem;gap:1rem}.top-buttons button{flex:1}::ng-deep em{font-weight:900;color:#014a93}.float-button{position:fixed;bottom:4rem;right:2rem;z-index:1000;display:flex;gap:1px}.float-button :host ::ng-deep .p-button{width:4rem;height:4rem;border-radius:50%}\n"] }]
|
|
3194
|
-
}], ctorParameters: () => [
|
|
3195
|
-
type: Inject,
|
|
3196
|
-
args: [CONVERSATION_AI_TOKEN]
|
|
3197
|
-
}] }, { type: i0.ChangeDetectorRef }, { type: i1$5.Router }, { type: i1$5.ActivatedRoute }, { type: i1$4.DialogService }, { type: DCConversationPromptBuilderService }, { type: i6$1.ToastAlertsAbstractService, decorators: [{
|
|
3198
|
-
type: Optional
|
|
3199
|
-
}, {
|
|
3200
|
-
type: Inject,
|
|
3201
|
-
args: [TOAST_ALERTS_TOKEN]
|
|
3202
|
-
}] }], propDecorators: { storageSettings: [{
|
|
3203
|
-
type: Input
|
|
3204
|
-
}], bannerImgSettings: [{
|
|
3205
|
-
type: Input
|
|
3206
|
-
}], imageStorageSettings: [{
|
|
3207
|
-
type: Input
|
|
3208
|
-
}], onImageLoaded: [{
|
|
3209
|
-
type: Output
|
|
3210
|
-
}], onSave: [{
|
|
3211
|
-
type: Output
|
|
3212
|
-
}], onGoDetails: [{
|
|
3213
|
-
type: Output
|
|
3214
|
-
}], onTranslate: [{
|
|
3215
|
-
type: Output
|
|
3216
|
-
}] } });
|
|
3016
|
+
AccountPlatformForm
|
|
3017
|
+
], template: "<div class=\"top-buttons\">\n <button pButton severity=\"info\" (click)=\"checkPrompt()\" label=\"\uD83D\uDC41\uFE0F Ver instrucciones finales \uD83D\uDCD3\"></button>\n\n <button pButton severity=\"info\" (click)=\"goToDetails()\" label=\"\uD83D\uDCAC Conversar\"></button>\n <button pButton severity=\"primary\" (click)=\"saveConversation()\" label=\"\uD83D\uDCBE Guardar cambios\"></button>\n</div>\n\n<div class=\"top-buttons\">\n <p-button severity=\"help\" (click)=\"translate()\" label=\"\uD83D\uDD04 Traducir\"></p-button>\n <p-button [loading]=\"isGenerating\" severity=\"help\" (click)=\"generateCharacter()\" label=\"Generar \uD83E\uDDBE\"></p-button>\n\n <p-button severity=\"info\" (click)=\"downloadConversation()\" label=\"\uD83D\uDCC1 Exportar \u2B07\uFE0F\"></p-button>\n <p-button severity=\"info\" (click)=\"importConversation()\" label=\"\uD83C\uDCCF Importar \u2B06\uFE0F\"></p-button>\n</div>\n\n<br />\n<br />\n<form [formGroup]=\"form\" class=\"conversation-form\">\n <div class=\"form-grid\">\n <div class=\"left-column\">\n <div style=\"display: flex; gap: 15px\">\n <div class=\"form-field\">\n <label for=\"version\">Version: {{ form.controls.version.value }} <span pTooltip=\"Version number of the conversation\">\u2139\uFE0F</span></label>\n </div>\n\n <div class=\"form-field\">\n <label for=\"id\"\n >ID: <span pTooltip=\"Unique identifier for this conversation\"> {{ form.controls.id.value }} \u2139\uFE0F</span></label\n >\n </div>\n </div>\n\n <div class=\"form-field\">\n <label for=\"title\">Title <span pTooltip=\"T\u00EDtulo de la conversaci\u00F3n\">\u2139\uFE0F</span></label>\n <input pInputText id=\"title\" type=\"text\" formControlName=\"title\" />\n @if(form.controls.title.errors?.['required'] && form.controls.title.touched){\n <div class=\"error\"> Title is required </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"lang\">Language <span pTooltip=\"Select the primary language for the conversation\">\u2139\uFE0F</span></label>\n <p-select\n id=\"lang\"\n [options]=\"languageOptions\"\n formControlName=\"lang\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Language'\"></p-select>\n </div>\n\n <div formGroupName=\"conversationSettings\" class=\"group\">\n <h3>Conversation Settings <span pTooltip=\"Additional information about the conversation\">\u2139\uFE0F</span></h3>\n\n <div class=\"form-field\">\n <label for=\"textEngine\">\n Text Engine\n <span\n class=\"cursor-pointer\"\n (click)=\"textEngineDialog.toggle($event)\"\n pTooltip=\"Sistema de generaci\u00F3n de texto y audios. Client: el cliente llama al servidor en cada dialogo de voz/personaje, es optimo para historias, Server SSML: se sintetiza todo el audio en uno solo con los distintos cambios de voz/personaje, util para la reflexi\u00F3n porque es bilingue, utiliza dialogos en ingles y espa\u00F1ol en el mismo dialogo/audio\"\n >\u2139\uFE0F</span\n >\n </label>\n\n <p-select\n id=\"textEngine\"\n [options]=\"textEngineOptions\"\n formControlName=\"textEngine\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Text Engine'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"conversationType\">Conversation Type <span pTooltip=\"Choose the type of conversation interaction\">\u2139\uFE0F</span></label>\n <p-select\n id=\"conversationType\"\n [options]=\"conversationOptions\"\n formControlName=\"conversationType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Conversation Type'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label> Auto Start <span pTooltip=\"Start conversation automatically\">\u2139\uFE0F</span> </label>\n <p-toggleSwitch formControlName=\"autoStart\"> </p-toggleSwitch>\n </div>\n\n <div formGroupName=\"tts\" class=\"group\">\n <h3>TTS Settings <span pTooltip=\"Text-to-Speech configuration options\">\u2139\uFE0F</span></h3>\n\n <div class=\"form-field\">\n <label for=\"voice\">Voice <span pTooltip=\"Select the primary voice for text-to-speech\">\u2139\uFE0F</span></label>\n <p-select\n id=\"voice\"\n [options]=\"voiceTTSOptions\"\n formControlName=\"voice\"\n optionLabel=\"name\"\n optionValue=\"id\"\n [placeholder]=\"'Select Voice'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"secondaryVoice\">Secondary Voice <span pTooltip=\"Select an alternative voice for text-to-speech\">\u2139\uFE0F</span></label>\n <p-select\n id=\"secondaryVoice\"\n [options]=\"voiceTTSOptions\"\n formControlName=\"secondaryVoice\"\n optionLabel=\"name\"\n optionValue=\"id\"\n [placeholder]=\"'Select Secondary Voice'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"speed\">Speed <span pTooltip=\"Set the speech rate for text-to-speech conversion\">\u2139\uFE0F</span></label>\n <p-select\n id=\"speed\"\n [options]=\"audioSpeedOptions\"\n formControlName=\"speed\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Speed'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"speedRate\">Speed Rate <span pTooltip=\"Adjust the rate of speech delivery\">\u2139\uFE0F</span></label>\n <input pInputText id=\"speedRate\" type=\"number\" formControlName=\"speedRate\" step=\"0.1\" />\n </div>\n </div>\n </div>\n\n <div formGroupName=\"metaApp\" class=\"group\">\n <h3>Meta Information <span pTooltip=\"Additional information about the conversation\">\u2139\uFE0F</span></h3>\n <div class=\"form-field\">\n <label for=\"authorId\">Author ID <span pTooltip=\"Unique identifier for the conversation author\">\u2139\uFE0F</span></label>\n <input pInputText id=\"authorId\" type=\"text\" formControlName=\"authorId\" />\n </div>\n\n <div class=\"form-field\">\n <label for=\"authorEmail\">Author Email \u2139\uFE0F</label>\n <input pInputText id=\"authorEmail\" type=\"email\" formControlName=\"authorEmail\" />\n @if (form.get('metaApp.authorEmail')?.errors?.['email'] && form.get('metaApp.authorEmail')?.touched) {\n <div class=\"error\">\n Please enter a valid email address\n </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"takenCount\"\n >Taken Count <span pTooltip=\"Es el contador de cuantas veces se ha tomado esta conversaci\u00F3n, no sirve por ahora\"> \u2139\uFE0F</span></label\n >\n <input pInputText id=\"takenCount\" type=\"number\" formControlName=\"takenCount\" />\n </div>\n\n <div class=\"form-field checkbox\">\n <label>\n <p-checkbox [binary]=\"true\" formControlName=\"isPublic\" />\n Public\n </label>\n </div>\n\n <div class=\"form-field checkbox\">\n <label>\n <p-checkbox [binary]=\"true\" formControlName=\"isPublished\" />\n Published\n </label>\n </div>\n </div>\n\n <div class=\"group\">\n <h4>Model Settings <span pTooltip=\"AI model configuration\">\u2139\uFE0F</span></h4>\n\n <dc-provider-selector [parentForm]=\"form.controls.model\"></dc-provider-selector>\n </div>\n\n <div class=\"group\">\n <h4>Gestion de cuentas</h4>\n @if(form.controls.accounts){\n <account-platform-form [formArray]=\"form.controls.accounts\"></account-platform-form>\n\n }\n </div>\n </div>\n\n <div class=\"right-column\">\n <div style=\"position: relative; min-height: 60px\">\n <img [src]=\"conversation?.assets?.bannerImg?.url || 'assets/images/default_banner.webp'\" class=\"main-banner-image-card\" />\n @if(!conversation?.assets?.bannerImg?.url && agentCardId) {\n\n <dc-cropper-modal\n style=\"position: absolute; bottom: 10px; right: 10px\"\n #cropperBanner\n id=\"cropperBanner\"\n [buttonLabel]=\"conversation?.assets?.bannerImg?.url ? 'Cambiar el banner' : 'Cargar un banner'\"\n [imgStorageSettings]=\"bannerImgSettings()\"\n [currentStorage]=\"conversation?.assets?.bannerImg\"\n (onFileSelected)=\"onImageSelected($event)\"\n (imageUploaded)=\"onImageUploaded($event, 'bannerImg')\"></dc-cropper-modal>\n\n }\n </div>\n <div style=\"position: relative\">\n <img [src]=\"conversation?.assets?.image?.url || 'assets/images/default_2_3.webp'\" class=\"main-image-card\" />\n @if (!agentCardId) {\n <button pButton (click)=\"saveConversation()\"> Guarda el scenario para subir la imagen</button>\n } @else {\n\n <dc-cropper-modal\n style=\"position: absolute; bottom: 10px; left: 50%\"\n id=\"cropperCardImage\"\n #cropperCardImage\n [buttonLabel]=\"conversation?.assets?.image?.url ? 'Cambiar imagen' : 'Cargar una imagen'\"\n [imgStorageSettings]=\"imageStorageSettings()\"\n (onFileSelected)=\"onImageSelected($event)\"\n (imageUploaded)=\"onImageUploaded($event, 'image')\"></dc-cropper-modal>\n }\n </div>\n\n <div>\n <h4>Agregar stickers</h4>\n\n <dc-cropper-modal\n id=\"cropperCardImage\"\n #cropperStickers\n [buttonLabel]=\"'agregar sticker'\"\n [imgStorageSettings]=\"stickerStorageSettings\"\n (onFileSelected)=\"onImageSelected($event)\"\n (imageUploaded)=\"onImageUploaded($event, 'sticker')\"></dc-cropper-modal>\n </div>\n\n <div style=\"display: flex; flex-wrap: wrap; gap: 10px\">\n @for (sticker of conversation?.assets?.stickers; track sticker.url) {\n <div style=\"position: relative\">\n <img width=\"100\" [src]=\"sticker.url\" alt=\"\" />\n <p-button (click)=\"removeSticker(sticker)\" class=\"remove-sticker\" icon=\"pi pi-times\" [rounded]=\"true\" [text]=\"true\" severity=\"danger\" />\n </div>\n }\n </div>\n\n <!-- <input pInputText type=\"file\" accept=\"image/*\" (change)=\"onImageSelected($event)\" /> -->\n\n <div formGroupName=\"characterCard\">\n <div formGroupName=\"data\" class=\"card-group\">\n <h3>Character Card <span pTooltip=\"Informaci\u00F3n de la ficha del personaje\">\u2139\uFE0F</span></h3>\n <div class=\"form-field\">\n <label for=\"cardName\">Name <span pTooltip=\"El nombre del personaje\">\u2139\uFE0F</span></label>\n <input pInputText id=\"cardName\" type=\"text\" formControlName=\"name\" />\n @if (form.get('characterCard.data.name')?.errors?.['required'] && form.get('characterCard.data.name')?.touched) {\n <div class=\"error\">\n Name is required\n </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardDescription\">Description <span pTooltip=\"Descripci\u00F3n detallada del personaje\">\u2139\uFE0F</span></label>\n <textarea class=\"textmin\" rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardDescription\" formControlName=\"description\"></textarea>\n @if (form.get('characterCard.data.description')?.errors?.['required'] && form.get('characterCard.data.description')?.touched) {\n <div class=\"error\">\n Description is required\n </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardScenario\">Scenario <span pTooltip=\"Describe the context or setting for the conversation\">\u2139\uFE0F</span></label>\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardScenario\" formControlName=\"scenario\"></textarea>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardFirstMessage\">\n First Message\n <span pTooltip=\"Es muy importante que la historia inicie bien, ya que es el patr\u00F3n inicial para la AI, respetar las convenciones de texto\"\n >\u2139\uFE0F</span\n >\n\n <p-togglebutton\n [formControl]=\"markdownForm.controls.seeMarkdown\"\n [onLabel]=\"'Editar'\"\n [offLabel]=\"'Ver Markdown Texto'\"\n size=\"small\"\n styleClass=\"min-w-16\"\n (onChange)=\"checkCdr()\" />\n </label>\n\n @if(markdownForm.controls.seeMarkdown.value){\n <div [innerHTML]=\"form.controls.characterCard.controls.data.controls.first_mes.value | mdToHtmlArray\"></div>\n }@else{\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardFirstMessage\" formControlName=\"first_mes\"> </textarea>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"mes_example\">Mensajes de Ejemplo <span pTooltip=\"Importante para el estilo de la conversaci\u00F3n\">\u2139\uFE0F</span></label>\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"mes_example\" formControlName=\"mes_example\"></textarea>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardCreatorNotes\">Creator Notes <span pTooltip=\"son solo notas del creador, no afecta nada a la conversaci\u00F3n\">\u2139\uFE0F</span></label>\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardCreatorNotes\" formControlName=\"creator_notes\"></textarea>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardSystemPrompt\">System Prompt (Opcional) <span pTooltip=\"Instrucciones del sistema para la conversaci\u00F3n\">\u2139\uFE0F</span></label>\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardSystemPrompt\" formControlName=\"system_prompt\"></textarea>\n @if (form.get('characterCard.data.system_prompt')?.errors?.['required'] && form.get('characterCard.data.system_prompt')?.touched) {\n <div\n class=\"error\"\n >\n System prompt is required\n </div>\n }\n </div>\n\n <div style=\"display: flex; flex-direction: column\">\n <label for=\"cardPostHistoryInstructions\"\n >Post-History Instructions (Opcional)\n <span\n pTooltip=\"Dejar en blanco, al menos que se sepa como funciona, esto se llama jailbreak, es para darle instrucciones finales y m\u00E1s importantes al modelo\"\n >\u2139\uFE0F</span\n ></label\n >\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" formControlName=\"post_history_instructions\"></textarea>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardAlternateGreetings\">Alternate Greetings <span pTooltip=\"Saludos alternativos para comenzar una historia diferente\">\u2139\uFE0F</span></label>\n <div class=\"array-field\">\n @for (greeting of form.controls.characterCard.controls.data.controls.alternate_greetings.controls; track greeting; let i = $index) {\n <div\n class=\"array-item\"\n style=\"position: relative\">\n <textarea\n pTextarea\n rows=\"1\"\n [autoResize]=\"true\"\n [id]=\"'cardAlternateGreeting' + i\"\n [formControl]=\"greeting\"\n (input)=\"updateArrayField('alternate_greetings', i, $event)\">\n </textarea>\n <button pButton severity=\"danger\" class=\"remove-button\" (click)=\"removeArrayItem('alternate_greetings', i)\">✖</button>\n </div>\n }\n <button pButton severity=\"info\" (click)=\"addArrayItem('alternate_greetings')\">Add Greeting</button>\n </div>\n </div>\n\n <div class=\"form-field\">\n <label pTooltip=\"Agrega las categorias\" for=\"cardTags\">Tags \u2139\uFE0F</label>\n <div class=\"array-field\">\n @for (tag of form.controls.characterCard.controls.data.controls.tags.controls; track tag; let i = $index) {\n <div class=\"array-item\">\n <input [id]=\"'cardTag' + i\" type=\"text\" [formControl]=\"tag\" (input)=\"updateArrayField('tags', i, $event)\" />\n <button pButton severity=\"danger\" (click)=\"removeArrayItem('tags', i)\">Remove</button>\n </div>\n }\n <button pButton severity=\"info\" (click)=\"addArrayItem('tags')\">Add Tag</button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </form>\n\n <p-popover #textEngineDialog header=\"Text Engine Information\">\n <div class=\"p-4\">\n <h3>Text Engine Types</h3>\n <ul>\n <li> <strong>Texto Simple</strong> La conversaci\u00F3n es como chatgpt, preguntas y responde, es la m\u00E1s b\u00E1sica</li>\n\n <li\n ><strong>Multi Mensajes</strong> Utiliza markdown (recomendable entenderlo), sirve para darle formato al texto y sea m\u00E1s agradable de leer, el sistema\n puede partir dialogos que tienen distinto formato, como normal, cursiva y negritas, asi puede generar distintas voces y estilo para el narrador y\n personaje principal</li\n >\n <li\n ><strong>MD SSML :</strong> Markdown con Lenguaje de marcaci\u00F3n de s\u00EDntesis de voz (SSML), es tambien markdown pero a diferencia de multimessage, solo se\n presenta un mensaje. y la voz se genera para toda la linea,normalmente lo uso para conversaciones bilingues.</li\n >\n </ul>\n </div>\n </p-popover>\n\n <div class=\"float-button\">\n <p-button icon=\"pi pi-save\" (click)=\"saveConversation()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n </div>\n", styles: [".textmin{min-width:36vw}.main-image-card{max-width:280px;display:block;margin:0 auto;border-radius:8px}.main-banner-image-card{border-radius:8px}.remove-sticker{position:absolute;top:5px;right:5px}.conversation-form{max-width:100%;padding:20px;background-color:#fff;border-radius:8px;box-shadow:0 2px 4px #0000001a}.conversation-form .card-group{background-color:#f8f9fa;padding:20px;border-radius:6px;margin-bottom:24px}.conversation-form .card-group h3{margin:0 0 20px;color:#2c3e50;font-size:1.25rem}.conversation-form .form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:2rem;width:100%;max-width:100%}@media (max-width: 768px){.conversation-form .form-grid{grid-template-columns:1fr}}.conversation-form .form-field{margin-bottom:1.5rem;display:flex;flex-direction:column;gap:.5rem}.conversation-form .form-field label{font-weight:500}.conversation-form .form-field textarea{resize:vertical}.conversation-form .form-field.checkbox{flex-direction:row;align-items:center;gap:.5rem}.conversation-form .form-field.checkbox input[type=checkbox]{width:auto}.conversation-form .form-field .error{color:#dc3545;font-size:.875rem;margin-top:.25rem}.conversation-form .form-field .remove-button{position:absolute;border:none;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;cursor:pointer;top:-10px;right:-10px}.conversation-form .left-column,.conversation-form .right-column{display:flex;flex-direction:column;gap:1rem}.conversation-form .array-field{display:flex;flex-direction:column;gap:.5rem}.conversation-form .array-field .array-item{display:flex;gap:.5rem}.conversation-form .array-field .array-item input,.conversation-form .array-field .array-item textarea{flex:1}.conversation-form .array-field .array-item button{padding:.5rem}.conversation-form .array-field button[type=button]{background-color:#28a745;color:#fff;border:none;padding:8px 12px;border-radius:4px;cursor:pointer;transition:background-color .2s}.conversation-form .array-field button[type=button]:hover{background-color:#218838}.conversation-form .group,.conversation-form .meta-group,.conversation-form .card-group{background-color:#f8f9fa;padding:1rem;border-radius:4px;margin-bottom:1.5rem}.conversation-form .group h3,.conversation-form .meta-group h3,.conversation-form .card-group h3{margin-top:0;margin-bottom:1rem}.top-buttons{display:flex;justify-content:space-between;margin-bottom:2rem;gap:1rem}.top-buttons button{flex:1}::ng-deep em{font-weight:900;color:#014a93}.float-button{position:fixed;bottom:4rem;right:2rem;z-index:1000;display:flex;gap:1px}.float-button :host ::ng-deep .p-button{width:4rem;height:4rem;border-radius:50%}\n"] }]
|
|
3018
|
+
}], ctorParameters: () => [] });
|
|
3217
3019
|
|
|
3218
3020
|
class DCConversationCardUIComponent {
|
|
3219
3021
|
constructor() {
|
|
@@ -3222,36 +3024,31 @@ class DCConversationCardUIComponent {
|
|
|
3222
3024
|
{ label: 'Delete', icon: 'pi pi-trash', title: 'delete', severity: 'danger', command: () => this.onDelete() },
|
|
3223
3025
|
{ label: 'Select', icon: 'pi pi-check', title: 'select', severity: 'success', command: () => this.onDetails() },
|
|
3224
3026
|
];
|
|
3225
|
-
this.
|
|
3226
|
-
this.
|
|
3027
|
+
this.card = input(undefined);
|
|
3028
|
+
this.showOptions = input(true);
|
|
3029
|
+
this.onCardAction = output();
|
|
3227
3030
|
}
|
|
3228
3031
|
ngOnInit() {
|
|
3229
|
-
const name = this.card.characterCard.data.name;
|
|
3230
|
-
const description = this.card.characterCard.data.description;
|
|
3231
|
-
this.card.characterCard.data.description = description.replace(/{{char}}/g, name);
|
|
3032
|
+
const name = this.card().characterCard.data.name;
|
|
3033
|
+
const description = this.card().characterCard.data.description;
|
|
3034
|
+
this.card().characterCard.data.description = description.replace(/{{char}}/g, name);
|
|
3232
3035
|
}
|
|
3233
3036
|
onDetails() {
|
|
3234
|
-
this.onCardAction.emit({ event: 'details', card: this.card });
|
|
3037
|
+
this.onCardAction.emit({ event: 'details', card: this.card() });
|
|
3235
3038
|
}
|
|
3236
3039
|
onEdit() {
|
|
3237
|
-
this.onCardAction.emit({ event: 'edit', card: this.card });
|
|
3040
|
+
this.onCardAction.emit({ event: 'edit', card: this.card() });
|
|
3238
3041
|
}
|
|
3239
3042
|
onDelete() {
|
|
3240
|
-
this.onCardAction.emit({ event: 'delete', card: this.card });
|
|
3043
|
+
this.onCardAction.emit({ event: 'delete', card: this.card() });
|
|
3241
3044
|
}
|
|
3242
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
3243
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.
|
|
3045
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCConversationCardUIComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3046
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: DCConversationCardUIComponent, isStandalone: true, selector: "dc-agent-card-default-ui", inputs: { card: { classPropertyName: "card", publicName: "card", isSignal: true, isRequired: false, transformFunction: null }, showOptions: { classPropertyName: "showOptions", publicName: "showOptions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onCardAction: "onCardAction" }, ngImport: i0, template: "<p-card class=\"card-image\">\n @if(showOptions()) {\n <div style=\"position: absolute; top: 5px; right: 5px; z-index: 1000\">\n <p-speeddial\n [model]=\"speedDialModel\"\n [radius]=\"70\"\n type=\"quarter-circle\"\n direction=\"down-left\"\n [buttonProps]=\"{ severity: 'primary', rounded: true, outlined: true, raised: true }\" />\n </div>\n }\n\n <img [src]=\"card()?.assets?.image?.url || 'assets/images/default_conversation_card.webp'\" alt=\"\" />\n\n <div (click)=\"onDetails()\" class=\"content\">\n <h2 class=\"title-text\">{{ card().title }}</h2>\n\n <h5 class=\"title\">\n <span [innerHTML]=\"card().characterCard?.data.description | truncate : 100\"></span>\n </h5>\n\n <p-button\n (click)=\"onDetails()\"\n [style]=\"{ position: 'absolute', bottom: '10px', right: '10px' }\"\n icon=\"pi pi-comment\"\n [rounded]=\"true\"\n severity=\"info\"\n [outlined]=\"true\"\n [raised]=\"true\" />\n </div>\n</p-card>\n", styles: [":host{display:block}:host ::ng-deep .p-card{height:100%}:host ::ng-deep .p-card-body{height:100%;padding:0!important}.card-image{width:280px;height:380px;position:relative;align-items:center;display:block;padding:-10px}.card-image img{position:absolute;z-index:3;width:100%;height:100%;opacity:.75;object-fit:cover;transition:opacity .5s}.content{position:absolute;inset:0;z-index:4;padding:1.5rem;color:#fff;background:linear-gradient(to bottom,#0003,#0000001a);height:100%;display:flex;flex-direction:column}.content:hover{background:linear-gradient(to bottom,color-mix(in srgb,var(--p-primary-color) 20%,transparent),color-mix(in srgb,black 10%,transparent));cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: PopoverModule }, { kind: "pipe", type: TruncatePipe, name: "truncate" }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SpeedDialModule }, { kind: "component", type: i2$5.SpeedDial, selector: "p-speeddial, p-speedDial, p-speed-dial", inputs: ["id", "model", "visible", "style", "className", "direction", "transitionDelay", "type", "radius", "mask", "disabled", "hideOnClickOutside", "buttonStyle", "buttonClassName", "maskStyle", "maskClassName", "showIcon", "hideIcon", "rotateAnimation", "ariaLabel", "ariaLabelledBy", "tooltipOptions", "buttonProps"], outputs: ["onVisibleChange", "visibleChange", "onClick", "onShow", "onHide"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i2$4.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3244
3047
|
}
|
|
3245
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
3048
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCConversationCardUIComponent, decorators: [{
|
|
3246
3049
|
type: Component,
|
|
3247
|
-
args: [{ selector: 'dc-agent-card-default-ui', imports: [PopoverModule, TruncatePipe, ButtonModule, SpeedDialModule, CardModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<p-card class=\"card-image\">\n @if(showOptions) {\n <div style=\"position: absolute; top: 5px; right: 5px; z-index: 1000\">\n <p-speeddial\n [model]=\"speedDialModel\"\n [radius]=\"70\"\n type=\"quarter-circle\"\n direction=\"down-left\"\n [buttonProps]=\"{ severity: 'primary', rounded: true, outlined: true, raised: true }\" />\n </div>\n }\n\n <img [src]=\"card?.assets?.image?.url || 'assets/images/default_conversation_card.webp'\" alt=\"\" />\n\n <div (click)=\"onDetails()\" class=\"content\">\n <h2 class=\"title-text\">{{ card.title }}</h2>\n\n <h5 class=\"title\">\n <span [innerHTML]=\"card.characterCard?.data.description | truncate : 100\"></span>\n </h5>\n\n <p-button\n (click)=\"onDetails()\"\n [style]=\"{ position: 'absolute', bottom: '10px', right: '10px' }\"\n icon=\"pi pi-comment\"\n [rounded]=\"true\"\n severity=\"info\"\n [outlined]=\"true\"\n [raised]=\"true\" />\n </div>\n</p-card>\n", styles: [":host{display:block}:host ::ng-deep .p-card{height:100%}:host ::ng-deep .p-card-body{height:100%;padding:0!important}.card-image{width:280px;height:380px;position:relative;align-items:center;display:block;padding:-10px}.card-image img{position:absolute;z-index:3;width:100%;height:100%;opacity:.75;object-fit:cover;transition:opacity .5s}.content{position:absolute;inset:0;z-index:4;padding:1.5rem;color:#fff;background:linear-gradient(to bottom,#0003,#0000001a);height:100%;display:flex;flex-direction:column}.content:hover{background:linear-gradient(to bottom,color-mix(in srgb,var(--p-primary-color) 20%,transparent),color-mix(in srgb,black 10%,transparent));cursor:pointer}\n"] }]
|
|
3248
|
-
}]
|
|
3249
|
-
type: Input
|
|
3250
|
-
}], showOptions: [{
|
|
3251
|
-
type: Input
|
|
3252
|
-
}], onCardAction: [{
|
|
3253
|
-
type: Output
|
|
3254
|
-
}] } });
|
|
3050
|
+
args: [{ selector: 'dc-agent-card-default-ui', imports: [PopoverModule, TruncatePipe, ButtonModule, SpeedDialModule, CardModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<p-card class=\"card-image\">\n @if(showOptions()) {\n <div style=\"position: absolute; top: 5px; right: 5px; z-index: 1000\">\n <p-speeddial\n [model]=\"speedDialModel\"\n [radius]=\"70\"\n type=\"quarter-circle\"\n direction=\"down-left\"\n [buttonProps]=\"{ severity: 'primary', rounded: true, outlined: true, raised: true }\" />\n </div>\n }\n\n <img [src]=\"card()?.assets?.image?.url || 'assets/images/default_conversation_card.webp'\" alt=\"\" />\n\n <div (click)=\"onDetails()\" class=\"content\">\n <h2 class=\"title-text\">{{ card().title }}</h2>\n\n <h5 class=\"title\">\n <span [innerHTML]=\"card().characterCard?.data.description | truncate : 100\"></span>\n </h5>\n\n <p-button\n (click)=\"onDetails()\"\n [style]=\"{ position: 'absolute', bottom: '10px', right: '10px' }\"\n icon=\"pi pi-comment\"\n [rounded]=\"true\"\n severity=\"info\"\n [outlined]=\"true\"\n [raised]=\"true\" />\n </div>\n</p-card>\n", styles: [":host{display:block}:host ::ng-deep .p-card{height:100%}:host ::ng-deep .p-card-body{height:100%;padding:0!important}.card-image{width:280px;height:380px;position:relative;align-items:center;display:block;padding:-10px}.card-image img{position:absolute;z-index:3;width:100%;height:100%;opacity:.75;object-fit:cover;transition:opacity .5s}.content{position:absolute;inset:0;z-index:4;padding:1.5rem;color:#fff;background:linear-gradient(to bottom,#0003,#0000001a);height:100%;display:flex;flex-direction:column}.content:hover{background:linear-gradient(to bottom,color-mix(in srgb,var(--p-primary-color) 20%,transparent),color-mix(in srgb,black 10%,transparent));cursor:pointer}\n"] }]
|
|
3051
|
+
}] });
|
|
3255
3052
|
|
|
3256
3053
|
// This component contains a really avanced strategy to dinamically render Conversation Cards Details so every app can implement it with their own Style and Behavior
|
|
3257
3054
|
// The trick is use NGFOR with ngComponentOutlet and pass input, problem is, is not possible to get Outpus events, Option one is use injector with ngComponentOutlet and pass context function to handle , but somehow it inject html and due to sanity im not able to see popups
|
|
@@ -3262,14 +3059,18 @@ const DefaultColumns = [
|
|
|
3262
3059
|
{ field: 'title', header: 'Title', type: 'text' },
|
|
3263
3060
|
];
|
|
3264
3061
|
class AgentCardListComponent extends PaginationBase {
|
|
3265
|
-
constructor(
|
|
3062
|
+
constructor() {
|
|
3063
|
+
const route = inject(ActivatedRoute);
|
|
3064
|
+
const router = inject(Router);
|
|
3266
3065
|
super(route, router);
|
|
3267
|
-
this.agentCardService =
|
|
3268
|
-
this.toastService =
|
|
3269
|
-
this.cdr =
|
|
3066
|
+
this.agentCardService = inject(CONVERSATION_AI_TOKEN);
|
|
3067
|
+
this.toastService = inject(TOAST_ALERTS_TOKEN);
|
|
3068
|
+
this.cdr = inject(ChangeDetectorRef);
|
|
3270
3069
|
this.viewMode = 'cards';
|
|
3271
|
-
this.
|
|
3272
|
-
this.
|
|
3070
|
+
this.customCardComponent = input(undefined);
|
|
3071
|
+
this.showOptions = input(true);
|
|
3072
|
+
this.gridLayout = input(true);
|
|
3073
|
+
this.getCustomButtons = input();
|
|
3273
3074
|
this.agentCards = [];
|
|
3274
3075
|
this.cardEventSubs = [];
|
|
3275
3076
|
this.cardComponent = null;
|
|
@@ -3279,7 +3080,7 @@ class AgentCardListComponent extends PaginationBase {
|
|
|
3279
3080
|
// this.buttonActions = DefaultActions;
|
|
3280
3081
|
this.filterConfig.returnProps = { _id: 1, title: 1, assets: 1, description: 1, 'characterCard.data.description': 1, 'characterCard.data.name': 1 };
|
|
3281
3082
|
this.loadConversationCards();
|
|
3282
|
-
this.cardComponent = this.customCardComponent || DCConversationCardUIComponent;
|
|
3083
|
+
this.cardComponent = this.customCardComponent() || DCConversationCardUIComponent;
|
|
3283
3084
|
}
|
|
3284
3085
|
subscribeDinamicInstantToEvents() {
|
|
3285
3086
|
// Clear previous cardEventSubs
|
|
@@ -3289,9 +3090,12 @@ class AgentCardListComponent extends PaginationBase {
|
|
|
3289
3090
|
subscribeToCardEvents() {
|
|
3290
3091
|
this.outlets.forEach((outlet) => {
|
|
3291
3092
|
const instance = outlet.componentInstance;
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3093
|
+
if (instance && instance.onCardAction) {
|
|
3094
|
+
const subscription = instance.onCardAction.subscribe((action) => {
|
|
3095
|
+
this.onCardAction({ action: action.event, item: action.card });
|
|
3096
|
+
});
|
|
3097
|
+
this.cardEventSubs.push(subscription);
|
|
3098
|
+
}
|
|
3295
3099
|
});
|
|
3296
3100
|
}
|
|
3297
3101
|
clearcardEventSubs() {
|
|
@@ -3385,13 +3189,12 @@ class AgentCardListComponent extends PaginationBase {
|
|
|
3385
3189
|
this.doAction(actionEvent); // handle by father.
|
|
3386
3190
|
}
|
|
3387
3191
|
}
|
|
3388
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
3389
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.
|
|
3192
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AgentCardListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3193
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: AgentCardListComponent, isStandalone: true, selector: "dc-agent-card-lists", inputs: { viewMode: { classPropertyName: "viewMode", publicName: "viewMode", isSignal: false, isRequired: false, transformFunction: null }, customCardComponent: { classPropertyName: "customCardComponent", publicName: "customCardComponent", isSignal: true, isRequired: false, transformFunction: null }, showOptions: { classPropertyName: "showOptions", publicName: "showOptions", isSignal: true, isRequired: false, transformFunction: null }, gridLayout: { classPropertyName: "gridLayout", publicName: "gridLayout", isSignal: true, isRequired: false, transformFunction: null }, getCustomButtons: { classPropertyName: "getCustomButtons", publicName: "getCustomButtons", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "outlets", predicate: ["outlet"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<dc-filter-bar [isAdmin]=\"true\" (onFilterAction)=\"doFilterBarAction($event)\"></dc-filter-bar>\n\n@if(viewMode === 'table'){\n<app-quick-table [columns]=\"columns\" [tableData]=\"agentCards\" [actions]=\"actions()\" (onAction)=\"onCardAction($event)\"></app-quick-table>\n\n}@else{\n\n<div class=\"conversation-card-lists\">\n @if(!isLoading) {\n <div [ngClass]=\"{ 'cards-container': gridLayout() }\">\n @for (card of agentCards; track card) {\n <div style=\"position: relative\">\n <ng-container #outlet=\"ngComponentOutlet\" [ngComponentOutlet]=\"cardComponent\" [ngComponentOutletInputs]=\"{ card: card, showOptions: showOptions() }\">\n </ng-container>\n </div>\n }\n </div>\n }\n</div>\n\n@if(isLoading) {\n<div>\n <p-skeleton styleClass=\"mb-2\" />\n <p-skeleton width=\"10rem\" styleClass=\"mb-2\" />\n <p-skeleton width=\"5rem\" styleClass=\"mb-2\" />\n <p-skeleton height=\"2rem\" styleClass=\"mb-2\" />\n <p-skeleton width=\"10rem\" height=\"4rem\" />\n</div>\n} @if(agentCards.length === 0) {\n<div>\n <p>No conversations found or no connection with server</p>\n</div>\n} }\n\n<p-paginator\n currentPageReportTemplate=\"{{ totalRecords }} conversations\"\n [showCurrentPageReport]=\"true\"\n (onPageChange)=\"onPageChange($event)\"\n [first]=\"paginatorFirst\"\n [rows]=\"paginatorRows\"\n [totalRecords]=\"totalRecords\"\n [rowsPerPageOptions]=\"[10, 20, 30]\">\n</p-paginator>\n", styles: [":host{display:block;height:100%}.options-icon{cursor:pointer;position:absolute;top:2px;right:3px;font-size:1.2rem;color:#dde9e9;background-color:#4f486281;border-radius:50%;padding:5px;z-index:1000}.conversation-card-lists{padding:1.5rem;width:100%;height:100%;display:flex;flex-direction:column}.conversation-card-lists .cards-container{display:flex;flex-wrap:wrap;gap:2rem;width:100%;justify-content:center;flex:1;overflow-y:auto;min-height:0}.conversation-card-lists .cards-container>div{flex:0 0 240px}.conversation-card-lists .dc-card{position:relative;background:#fff;border-radius:8px;box-shadow:0 2px 4px #0000001a;padding:.5rem;transition:transform .2s ease,box-shadow .2s ease;display:flex;flex-direction:column;gap:2px}.conversation-card-lists .dc-card:hover{transform:translateY(-2px);box-shadow:0 4px 8px #00000026}.conversation-card-lists .dc-card .dc-card-header{position:absolute;top:10px;left:5px;border-radius:5px;padding:5px}.conversation-card-lists .dc-card .dc-card-header:before{content:\"\";position:absolute;inset:0;background-color:#4d30db81;filter:blur(2px);border-radius:5px;z-index:0}.conversation-card-lists .dc-card .dc-card-header h3{margin:0;font-size:1.25rem;font-weight:600;color:#ece7e7;position:relative;z-index:1}.conversation-card-lists .dc-card .dc-card-content{flex:1}.conversation-card-lists .dc-card .dc-card-content p{margin:0;color:#666;line-height:1.5}.conversation-card-lists .dc-card button{padding:.5rem 1rem;border:none;border-radius:4px;background-color:#007bff;color:#fff;cursor:pointer;font-weight:500;transition:background-color .2s ease}.conversation-card-lists .dc-card button:hover{background-color:#0056b3}.conversation-card-lists .dc-card button:active{transform:translateY(1px)}:host{display:flex;flex-direction:column;height:100%}p-paginator{margin-top:1rem;flex-shrink:0}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: PaginatorModule }, { kind: "component", type: i1$4.Paginator, selector: "p-paginator", inputs: ["pageLinkSize", "style", "styleClass", "alwaysShow", "dropdownAppendTo", "templateLeft", "templateRight", "appendTo", "dropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showFirstLastIcon", "totalRecords", "rows", "rowsPerPageOptions", "showJumpToPageDropdown", "showJumpToPageInput", "jumpToPageItemTemplate", "showPageLinks", "locale", "dropdownItemTemplate", "first"], outputs: ["onPageChange"] }, { kind: "component", type: DCFilterBarComponent, selector: "dc-filter-bar", inputs: ["isAdmin", "customFilters", "items"], outputs: ["onFilterAction", "onChangeSort"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1$2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "style", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "ngmodule", type: SpeedDialModule }, { kind: "component", type: QuickTableComponent, selector: "app-quick-table", inputs: ["onlyView", "columns", "tableData", "actions"], outputs: ["onAction"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
|
|
3390
3194
|
}
|
|
3391
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
3195
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AgentCardListComponent, decorators: [{
|
|
3392
3196
|
type: Component,
|
|
3393
3197
|
args: [{ selector: 'dc-agent-card-lists', imports: [
|
|
3394
|
-
CommonModule,
|
|
3395
3198
|
NgComponentOutlet,
|
|
3396
3199
|
PopoverModule,
|
|
3397
3200
|
ButtonModule,
|
|
@@ -3400,22 +3203,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImpor
|
|
|
3400
3203
|
SkeletonModule,
|
|
3401
3204
|
SpeedDialModule,
|
|
3402
3205
|
QuickTableComponent,
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
args: [CONVERSATION_AI_TOKEN]
|
|
3407
|
-
}] }, { type: i6$1.ToastAlertsAbstractService, decorators: [{
|
|
3408
|
-
type: Inject,
|
|
3409
|
-
args: [TOAST_ALERTS_TOKEN]
|
|
3410
|
-
}] }, { type: i1$5.ActivatedRoute }, { type: i1$5.Router }, { type: i0.ChangeDetectorRef }], propDecorators: { viewMode: [{
|
|
3411
|
-
type: Input
|
|
3412
|
-
}], customCardComponent: [{
|
|
3413
|
-
type: Input
|
|
3414
|
-
}], showOptions: [{
|
|
3415
|
-
type: Input
|
|
3416
|
-
}], gridLayout: [{
|
|
3417
|
-
type: Input
|
|
3418
|
-
}], getCustomButtons: [{
|
|
3206
|
+
CommonModule,
|
|
3207
|
+
], standalone: true, template: "<dc-filter-bar [isAdmin]=\"true\" (onFilterAction)=\"doFilterBarAction($event)\"></dc-filter-bar>\n\n@if(viewMode === 'table'){\n<app-quick-table [columns]=\"columns\" [tableData]=\"agentCards\" [actions]=\"actions()\" (onAction)=\"onCardAction($event)\"></app-quick-table>\n\n}@else{\n\n<div class=\"conversation-card-lists\">\n @if(!isLoading) {\n <div [ngClass]=\"{ 'cards-container': gridLayout() }\">\n @for (card of agentCards; track card) {\n <div style=\"position: relative\">\n <ng-container #outlet=\"ngComponentOutlet\" [ngComponentOutlet]=\"cardComponent\" [ngComponentOutletInputs]=\"{ card: card, showOptions: showOptions() }\">\n </ng-container>\n </div>\n }\n </div>\n }\n</div>\n\n@if(isLoading) {\n<div>\n <p-skeleton styleClass=\"mb-2\" />\n <p-skeleton width=\"10rem\" styleClass=\"mb-2\" />\n <p-skeleton width=\"5rem\" styleClass=\"mb-2\" />\n <p-skeleton height=\"2rem\" styleClass=\"mb-2\" />\n <p-skeleton width=\"10rem\" height=\"4rem\" />\n</div>\n} @if(agentCards.length === 0) {\n<div>\n <p>No conversations found or no connection with server</p>\n</div>\n} }\n\n<p-paginator\n currentPageReportTemplate=\"{{ totalRecords }} conversations\"\n [showCurrentPageReport]=\"true\"\n (onPageChange)=\"onPageChange($event)\"\n [first]=\"paginatorFirst\"\n [rows]=\"paginatorRows\"\n [totalRecords]=\"totalRecords\"\n [rowsPerPageOptions]=\"[10, 20, 30]\">\n</p-paginator>\n", styles: [":host{display:block;height:100%}.options-icon{cursor:pointer;position:absolute;top:2px;right:3px;font-size:1.2rem;color:#dde9e9;background-color:#4f486281;border-radius:50%;padding:5px;z-index:1000}.conversation-card-lists{padding:1.5rem;width:100%;height:100%;display:flex;flex-direction:column}.conversation-card-lists .cards-container{display:flex;flex-wrap:wrap;gap:2rem;width:100%;justify-content:center;flex:1;overflow-y:auto;min-height:0}.conversation-card-lists .cards-container>div{flex:0 0 240px}.conversation-card-lists .dc-card{position:relative;background:#fff;border-radius:8px;box-shadow:0 2px 4px #0000001a;padding:.5rem;transition:transform .2s ease,box-shadow .2s ease;display:flex;flex-direction:column;gap:2px}.conversation-card-lists .dc-card:hover{transform:translateY(-2px);box-shadow:0 4px 8px #00000026}.conversation-card-lists .dc-card .dc-card-header{position:absolute;top:10px;left:5px;border-radius:5px;padding:5px}.conversation-card-lists .dc-card .dc-card-header:before{content:\"\";position:absolute;inset:0;background-color:#4d30db81;filter:blur(2px);border-radius:5px;z-index:0}.conversation-card-lists .dc-card .dc-card-header h3{margin:0;font-size:1.25rem;font-weight:600;color:#ece7e7;position:relative;z-index:1}.conversation-card-lists .dc-card .dc-card-content{flex:1}.conversation-card-lists .dc-card .dc-card-content p{margin:0;color:#666;line-height:1.5}.conversation-card-lists .dc-card button{padding:.5rem 1rem;border:none;border-radius:4px;background-color:#007bff;color:#fff;cursor:pointer;font-weight:500;transition:background-color .2s ease}.conversation-card-lists .dc-card button:hover{background-color:#0056b3}.conversation-card-lists .dc-card button:active{transform:translateY(1px)}:host{display:flex;flex-direction:column;height:100%}p-paginator{margin-top:1rem;flex-shrink:0}\n"] }]
|
|
3208
|
+
}], ctorParameters: () => [], propDecorators: { viewMode: [{
|
|
3419
3209
|
type: Input
|
|
3420
3210
|
}], outlets: [{
|
|
3421
3211
|
type: ViewChildren,
|
|
@@ -3436,10 +3226,10 @@ class ParseCardPipe {
|
|
|
3436
3226
|
const result = this.builderConversation.applyReplacements(text, parseDict);
|
|
3437
3227
|
return result;
|
|
3438
3228
|
}
|
|
3439
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
3440
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.
|
|
3229
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ParseCardPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
3230
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.4", ngImport: i0, type: ParseCardPipe, isStandalone: true, name: "parseCard" }); }
|
|
3441
3231
|
}
|
|
3442
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
3232
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ParseCardPipe, decorators: [{
|
|
3443
3233
|
type: Pipe,
|
|
3444
3234
|
args: [{
|
|
3445
3235
|
name: 'parseCard',
|
|
@@ -3448,13 +3238,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImpor
|
|
|
3448
3238
|
}] });
|
|
3449
3239
|
|
|
3450
3240
|
class DcAgentCardDetailsComponent {
|
|
3451
|
-
constructor(
|
|
3452
|
-
this.agentCardService =
|
|
3453
|
-
this.route =
|
|
3454
|
-
this.cdr =
|
|
3455
|
-
this.userDataExchange =
|
|
3241
|
+
constructor() {
|
|
3242
|
+
this.agentCardService = inject(CONVERSATION_AI_TOKEN);
|
|
3243
|
+
this.route = inject(ActivatedRoute);
|
|
3244
|
+
this.cdr = inject(ChangeDetectorRef);
|
|
3245
|
+
this.userDataExchange = inject(USER_DATA_EXCHANGE);
|
|
3456
3246
|
this.agentCardId = '';
|
|
3457
|
-
this.onStartConversation =
|
|
3247
|
+
this.onStartConversation = output();
|
|
3458
3248
|
this.showInfoLayer = false;
|
|
3459
3249
|
}
|
|
3460
3250
|
async ngOnInit() {
|
|
@@ -3480,22 +3270,14 @@ class DcAgentCardDetailsComponent {
|
|
|
3480
3270
|
this.showInfoLayer = !this.showInfoLayer;
|
|
3481
3271
|
this.cdr.markForCheck();
|
|
3482
3272
|
}
|
|
3483
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
3484
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
3273
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DcAgentCardDetailsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
3274
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: DcAgentCardDetailsComponent, isStandalone: true, selector: "dc-agent-card-details", inputs: { agentCardId: "agentCardId" }, outputs: { onStartConversation: "onStartConversation" }, ngImport: i0, template: "<div style=\"display: flex; justify-content: center; align-items: center\">\n <p-card>\n <div class=\"card-container\">\n <img class=\"card-image\" [src]=\"agentCard?.assets?.image?.url || 'assets/images/default_conversation_card.webp'\" alt=\"\" />\n\n <div class=\"info-button\" (click)=\"toggleInfoLayer()\">\n <p-button icon=\"pi pi-arrow-down-left\" [rounded]=\"true\" [raised]=\"true\" severity=\"primary\" [outlined]=\"true\" />\n </div>\n\n <div style=\"position: absolute; bottom: 20px; right: 50%; transform: translateX(50%); z-index: 3\">\n <p-button size=\"large\" label=\"Iniciar Conversaci\u00F3n\" [rounded]=\"true\" (click)=\"startConversation()\" />\n </div>\n\n <div class=\"info-layer\" [class.active]=\"showInfoLayer\">\n <div class=\"info-content\">\n <h1\n ><strong>{{ agentCard?.title }}</strong></h1\n >\n <p>{{ agentCard?.characterCard.data?.name }}</p>\n\n @if (agentCard?.characterCard.data?.scenario) {\n <div class=\"scenario\">\n <h4>Scenario</h4>\n <p>{{ agentCard?.characterCard.data.scenario | parseCard : agentCard }}</p>\n </div>\n }\n </div>\n </div>\n </div>\n </p-card>\n </div>\n\n <!-- <div class=\"dc-conversation-card-details\">\n <div class=\"header\">\n <h2>{{ agentCard?.title }}</h2>\n <span class=\"version\">v{{ agentCard?.version }}</span>\n </div>\n\n <div class=\"character-card\" *ngIf=\"agentCard?.characterCard\">\n <div class=\"character-header\">\n <h3>{{ agentCard?.characterCard.data.name }}</h3>\n <div class=\"tags\">\n <span class=\"tag\" *ngFor=\"let tag of agentCard?.characterCard.data?.tags\">{{ tag }}</span>\n </div>\n </div>\n\n <div class=\"image-wrapper\">\n <div class=\"image\">\n <img [src]=\"agentCard?.assets?.image?.url || 'assets/images/default_conversation_card.webp'\" alt=\"\" />\n <div class=\"actions\">\n <p-button label=\"Start Conversation\" (click)=\"startConversation()\"></p-button>\n </div>\n </div>\n </div>\n\n <div class=\"description\">\n <p>{{ agentCard?.characterCard.data?.description | parseCard : agentCard }}</p>\n </div>\n\n <div class=\"scenario\" *ngIf=\"agentCard?.characterCard.data?.scenario\">\n <h4>Scenario</h4>\n <p>{{ agentCard?.characterCard.data.scenario }}</p>\n </div>\n\n <div class=\"first-message\" *ngIf=\"agentCard?.characterCard.data?.first_mes\">\n <h4>First Message</h4>\n <p>{{ agentCard?.characterCard.data.first_mes }}</p>\n </div>\n\n <div class=\"alternate-greetings\" *ngIf=\"agentCard?.characterCard.data?.alternate_greetings?.length\">\n <h4>Alternate Greetings</h4>\n <ul>\n <li *ngFor=\"let greeting of agentCard?.characterCard.data.alternate_greetings\">{{ greeting }}</li>\n </ul>\n </div>\n </div>\n\n <div class=\"settings\">\n <div class=\"conversation-settings\">\n <h3>Conversation Settings</h3>\n <div class=\"setting-item\">\n <span class=\"label\">Type:</span>\n <span class=\"value\">{{ agentCard?.conversationSettings?.conversationType }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Language:</span>\n <span class=\"value\">{{ agentCard?.lang }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Text Engine:</span>\n <span class=\"value\">{{ agentCard?.conversationSettings?.textEngine }}</span>\n </div>\n </div>\n\n <div class=\"tts-settings\" *ngIf=\"agentCard?.tts\">\n <h3>TTS Settings</h3>\n <div class=\"setting-item\">\n <span class=\"label\">Primary Voice:</span>\n <span class=\"value\">{{ agentCard?.tts.voice }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Secondary Voice:</span>\n <span class=\"value\">{{ agentCard?.tts.secondaryVoice }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Speed:</span>\n <span class=\"value\">{{ agentCard?.tts.speed }} ({{ agentCard?.tts.speedRate }}x)</span>\n </div>\n </div>\n </div>\n</div> -->\n", styles: ["::ng-deep .p-card{width:420px;height:700px}::ng-deep .p-card .p-card-body{width:100%;height:100%}.card-image{height:100%;width:100%;object-fit:cover;object-position:center;position:absolute;top:0;left:0;transition:filter .3s ease}.info-button{position:absolute;top:15px;right:15px;z-index:3}.info-button:hover{transform:scale(1.1)}.info-layer{height:100%;width:100%;position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;z-index:2;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);background-color:#ed122833;color:#fff;opacity:1;clip-path:circle(0% at top right);transition:clip-path .5s cubic-bezier(.25,1,.5,1);pointer-events:none}.info-layer.active{clip-path:circle(150% at top right);pointer-events:auto}.info-content{padding:15px;text-align:center;max-width:90%}.info-content h1{margin-top:0;font-size:18px;margin-bottom:10px}.info-content p{font-size:12px;margin:0}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i2$4.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "pipe", type: ParseCardPipe, name: "parseCard" }] }); }
|
|
3485
3275
|
}
|
|
3486
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
3276
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DcAgentCardDetailsComponent, decorators: [{
|
|
3487
3277
|
type: Component,
|
|
3488
|
-
args: [{ selector: 'dc-agent-card-details', standalone: true, imports: [
|
|
3489
|
-
}], ctorParameters: () => [
|
|
3490
|
-
type: Inject,
|
|
3491
|
-
args: [CONVERSATION_AI_TOKEN]
|
|
3492
|
-
}] }, { type: i1$5.ActivatedRoute }, { type: i0.ChangeDetectorRef }, { type: UserDataExchangeAbstractService, decorators: [{
|
|
3493
|
-
type: Inject,
|
|
3494
|
-
args: [USER_DATA_EXCHANGE]
|
|
3495
|
-
}] }], propDecorators: { agentCardId: [{
|
|
3278
|
+
args: [{ selector: 'dc-agent-card-details', standalone: true, imports: [ButtonModule, CardModule, ParseCardPipe], template: "<div style=\"display: flex; justify-content: center; align-items: center\">\n <p-card>\n <div class=\"card-container\">\n <img class=\"card-image\" [src]=\"agentCard?.assets?.image?.url || 'assets/images/default_conversation_card.webp'\" alt=\"\" />\n\n <div class=\"info-button\" (click)=\"toggleInfoLayer()\">\n <p-button icon=\"pi pi-arrow-down-left\" [rounded]=\"true\" [raised]=\"true\" severity=\"primary\" [outlined]=\"true\" />\n </div>\n\n <div style=\"position: absolute; bottom: 20px; right: 50%; transform: translateX(50%); z-index: 3\">\n <p-button size=\"large\" label=\"Iniciar Conversaci\u00F3n\" [rounded]=\"true\" (click)=\"startConversation()\" />\n </div>\n\n <div class=\"info-layer\" [class.active]=\"showInfoLayer\">\n <div class=\"info-content\">\n <h1\n ><strong>{{ agentCard?.title }}</strong></h1\n >\n <p>{{ agentCard?.characterCard.data?.name }}</p>\n\n @if (agentCard?.characterCard.data?.scenario) {\n <div class=\"scenario\">\n <h4>Scenario</h4>\n <p>{{ agentCard?.characterCard.data.scenario | parseCard : agentCard }}</p>\n </div>\n }\n </div>\n </div>\n </div>\n </p-card>\n </div>\n\n <!-- <div class=\"dc-conversation-card-details\">\n <div class=\"header\">\n <h2>{{ agentCard?.title }}</h2>\n <span class=\"version\">v{{ agentCard?.version }}</span>\n </div>\n\n <div class=\"character-card\" *ngIf=\"agentCard?.characterCard\">\n <div class=\"character-header\">\n <h3>{{ agentCard?.characterCard.data.name }}</h3>\n <div class=\"tags\">\n <span class=\"tag\" *ngFor=\"let tag of agentCard?.characterCard.data?.tags\">{{ tag }}</span>\n </div>\n </div>\n\n <div class=\"image-wrapper\">\n <div class=\"image\">\n <img [src]=\"agentCard?.assets?.image?.url || 'assets/images/default_conversation_card.webp'\" alt=\"\" />\n <div class=\"actions\">\n <p-button label=\"Start Conversation\" (click)=\"startConversation()\"></p-button>\n </div>\n </div>\n </div>\n\n <div class=\"description\">\n <p>{{ agentCard?.characterCard.data?.description | parseCard : agentCard }}</p>\n </div>\n\n <div class=\"scenario\" *ngIf=\"agentCard?.characterCard.data?.scenario\">\n <h4>Scenario</h4>\n <p>{{ agentCard?.characterCard.data.scenario }}</p>\n </div>\n\n <div class=\"first-message\" *ngIf=\"agentCard?.characterCard.data?.first_mes\">\n <h4>First Message</h4>\n <p>{{ agentCard?.characterCard.data.first_mes }}</p>\n </div>\n\n <div class=\"alternate-greetings\" *ngIf=\"agentCard?.characterCard.data?.alternate_greetings?.length\">\n <h4>Alternate Greetings</h4>\n <ul>\n <li *ngFor=\"let greeting of agentCard?.characterCard.data.alternate_greetings\">{{ greeting }}</li>\n </ul>\n </div>\n </div>\n\n <div class=\"settings\">\n <div class=\"conversation-settings\">\n <h3>Conversation Settings</h3>\n <div class=\"setting-item\">\n <span class=\"label\">Type:</span>\n <span class=\"value\">{{ agentCard?.conversationSettings?.conversationType }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Language:</span>\n <span class=\"value\">{{ agentCard?.lang }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Text Engine:</span>\n <span class=\"value\">{{ agentCard?.conversationSettings?.textEngine }}</span>\n </div>\n </div>\n\n <div class=\"tts-settings\" *ngIf=\"agentCard?.tts\">\n <h3>TTS Settings</h3>\n <div class=\"setting-item\">\n <span class=\"label\">Primary Voice:</span>\n <span class=\"value\">{{ agentCard?.tts.voice }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Secondary Voice:</span>\n <span class=\"value\">{{ agentCard?.tts.secondaryVoice }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Speed:</span>\n <span class=\"value\">{{ agentCard?.tts.speed }} ({{ agentCard?.tts.speedRate }}x)</span>\n </div>\n </div>\n </div>\n</div> -->\n", styles: ["::ng-deep .p-card{width:420px;height:700px}::ng-deep .p-card .p-card-body{width:100%;height:100%}.card-image{height:100%;width:100%;object-fit:cover;object-position:center;position:absolute;top:0;left:0;transition:filter .3s ease}.info-button{position:absolute;top:15px;right:15px;z-index:3}.info-button:hover{transform:scale(1.1)}.info-layer{height:100%;width:100%;position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;z-index:2;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);background-color:#ed122833;color:#fff;opacity:1;clip-path:circle(0% at top right);transition:clip-path .5s cubic-bezier(.25,1,.5,1);pointer-events:none}.info-layer.active{clip-path:circle(150% at top right);pointer-events:auto}.info-content{padding:15px;text-align:center;max-width:90%}.info-content h1{margin-top:0;font-size:18px;margin-bottom:10px}.info-content p{font-size:12px;margin:0}\n"] }]
|
|
3279
|
+
}], ctorParameters: () => [], propDecorators: { agentCardId: [{
|
|
3496
3280
|
type: Input
|
|
3497
|
-
}], onStartConversation: [{
|
|
3498
|
-
type: Output
|
|
3499
3281
|
}] } });
|
|
3500
3282
|
|
|
3501
3283
|
/*
|
|
@@ -3507,5 +3289,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImpor
|
|
|
3507
3289
|
* Generated bundle index. Do not edit.
|
|
3508
3290
|
*/
|
|
3509
3291
|
|
|
3510
|
-
export { AgentCardListComponent, AgentCardsAbstractService, AudioService, AudioSpeed, CONVERSATION_AI_TOKEN, ChatMessage,
|
|
3292
|
+
export { AgentCardListComponent, AgentCardsAbstractService, AudioService, AudioSpeed, CONVERSATION_AI_TOKEN, ChatMessage, ChatRole, ChatUserSettings, ConversationDTO, ConversationMessagesDTO, ConversationType, ConversationTypeOptions, DCAgentCardFormComponent, DCChatComponent, DCConversationCardUIComponent, DCConversationPromptBuilderService, DcAgentCardDetailsComponent, DefaultEvaluatorAgentCard, EAccountsPlatform, LangCodeDescriptionEs, MessageAudio, MessageOrchestratorComponent, ProviderSelectorComponent, TextEngineOptions, TextEngines, TextHighlighterComponent, USER_DATA_EXCHANGE, UserDataExchangeAbstractService, VoiceTTSOption, VoiceTTSOptions, WordTimestamps, buildObjectTTSRequest, characterCardStringDataDefinition, convertToHTML, defaultconvUserSettings, extractAudioAndTranscription, extractJsonFromResponse, markdownBasicToHTML, markdownToHTML$1 as markdownToHTML, markdownToHTML2, markdownToHtml, matchTranscription, provideChatAIService, provideUserDataExchange, removeAllEmojis, removeEmojis };
|
|
3511
3293
|
//# sourceMappingURL=dataclouder-ngx-agent-cards.mjs.map
|