@dataclouder/ngx-agent-cards 0.0.84 → 0.0.86

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.
Files changed (29) hide show
  1. package/fesm2022/dataclouder-ngx-agent-cards.mjs +1515 -578
  2. package/fesm2022/dataclouder-ngx-agent-cards.mjs.map +1 -1
  3. package/lib/components/audio-text-sync/audio-text-sync.component.d.ts +42 -0
  4. package/lib/components/chat/chat.models.d.ts +2 -0
  5. package/lib/components/chat/dc-chat.component.d.ts +5 -7
  6. package/lib/components/chat-container/chat-container.component.d.ts +66 -0
  7. package/lib/components/chat-container/chat-footer/chat-footer.component.d.ts +30 -0
  8. package/lib/components/chat-container/chat-header/chat-header.component.d.ts +20 -0
  9. package/lib/components/chat-container/chat-messages-list/chat-message/chat-message.component.d.ts +30 -0
  10. package/lib/components/chat-container/chat-messages-list/chat-message/chat-message.utils.d.ts +3 -0
  11. package/lib/components/chat-container/chat-messages-list/chat-message/message-content/message-content.component.d.ts +24 -0
  12. package/lib/components/chat-container/chat-messages-list/chat-message/multi-message-content/multi-message-content.d.ts +22 -0
  13. package/lib/components/chat-container/chat-messages-list/chat-messages-list.component.d.ts +13 -0
  14. package/lib/components/dc-agent-card-details/dc-agent-card-details.component.d.ts +8 -4
  15. package/lib/components/dc-agent-card-lists/agent-card-default-ui/agent-card-default-ui.component.d.ts +2 -1
  16. package/lib/components/dc-agent-card-lists/dc-agent-card-lists.component.d.ts +3 -5
  17. package/lib/components/dc-agent-form/dc-agent-card-form.component.d.ts +12 -8
  18. package/lib/components/icons/icon-map.d.ts +0 -3
  19. package/lib/models/agent.models.d.ts +11 -15
  20. package/lib/models/conversation-enums.d.ts +2 -0
  21. package/lib/pipes/parse-card.pipe.d.ts +10 -0
  22. package/lib/services/audio-text-sync.service.d.ts +57 -0
  23. package/lib/services/conversation.service.d.ts +31 -0
  24. package/lib/services/dc-conversation-builder.service.d.ts +6 -1
  25. package/lib/services/evaluation.service.d.ts +17 -0
  26. package/lib/services/message-processing.service.d.ts +11 -0
  27. package/package.json +1 -1
  28. package/public-api.d.ts +2 -1
  29. package/lib/components/chat-message/chat-message.component.d.ts +0 -28
@@ -1,41 +1,43 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, Injectable, Inject, Component, Input, Pipe, ChangeDetectionStrategy, EventEmitter, Output, ViewChild, Optional, ViewChildren } from '@angular/core';
3
- import * as i2 from '@angular/common';
2
+ import { InjectionToken, Injectable, Inject, EventEmitter, Component, Input, Output, inject, DestroyRef, signal, Injector, effect, runInInjectionContext, ChangeDetectionStrategy, Pipe, ViewChild, Optional, ViewChildren } from '@angular/core';
3
+ import * as i1$2 from '@angular/common';
4
4
  import { CommonModule, NgComponentOutlet } from '@angular/common';
5
- import * as i1 from '@angular/platform-browser';
6
- import * as i1$1 from '@angular/forms';
7
- import { ReactiveFormsModule, FormControl, FormsModule } from '@angular/forms';
8
- import * as i1$2 from 'primeng/dynamicdialog';
5
+ import * as i1$3 from 'primeng/dynamicdialog';
9
6
  import { DialogService, DynamicDialogModule } from 'primeng/dynamicdialog';
10
- import * as i6 from 'primeng/skeleton';
11
- import { SkeletonModule } from 'primeng/skeleton';
12
- import * as i6$1 from 'primeng/dialog';
7
+ import * as i6 from 'primeng/dialog';
13
8
  import { DialogModule } from 'primeng/dialog';
14
- import * as i6$2 from '@dataclouder/ngx-core';
15
- import { TOAST_ALERTS_TOKEN, PaginationBase, DCFilterBarComponent, QuickTableComponent } from '@dataclouder/ngx-core';
16
- import { DCMicComponent } from '@dataclouder/ngx-mic';
17
- import * as i7$2 from 'primeng/progressbar';
9
+ import * as i2 from 'primeng/progressbar';
18
10
  import { ProgressBarModule } from 'primeng/progressbar';
19
- import * as i4$1 from 'primeng/checkbox';
11
+ import * as i1 from '@angular/forms';
12
+ import { FormControl, ReactiveFormsModule, FormsModule } from '@angular/forms';
13
+ import { DCMicComponent } from '@dataclouder/ngx-mic';
14
+ import * as i3 from 'primeng/textarea';
15
+ import { TextareaModule } from 'primeng/textarea';
16
+ import * as i7 from 'primeng/button';
17
+ import { ButtonModule } from 'primeng/button';
18
+ import * as i1$1 from '@angular/platform-browser';
19
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
20
+ import { BehaviorSubject, Subject, fromEvent, filter } from 'rxjs';
21
+ import { takeUntil, map } from 'rxjs/operators';
22
+ import * as i4 from 'primeng/skeleton';
23
+ import { SkeletonModule } from 'primeng/skeleton';
24
+ import * as i4$2 from 'primeng/checkbox';
20
25
  import { CheckboxModule } from 'primeng/checkbox';
21
26
  import { SliderModule } from 'primeng/slider';
22
- import * as i3 from 'primeng/radiobutton';
27
+ import * as i3$1 from 'primeng/radiobutton';
23
28
  import { RadioButtonModule } from 'primeng/radiobutton';
24
- import * as i7$1 from 'primeng/button';
25
- import { ButtonModule } from 'primeng/button';
26
29
  import * as i8 from 'primeng/rating';
27
30
  import { RatingModule } from 'primeng/rating';
28
31
  import * as i5 from 'primeng/table';
29
32
  import { TableModule } from 'primeng/table';
30
33
  import { BadgeModule } from 'primeng/badge';
31
- import * as i7 from 'primeng/tooltip';
34
+ import * as i7$1 from 'primeng/tooltip';
32
35
  import { TooltipModule } from 'primeng/tooltip';
33
- import { filter } from 'rxjs';
34
- import * as i4 from 'primeng/api';
36
+ import * as i4$1 from 'primeng/api';
37
+ import * as i6$1 from '@dataclouder/ngx-core';
38
+ import { TOAST_ALERTS_TOKEN, AudioSpeed as AudioSpeed$1, PaginationBase, DCFilterBarComponent, QuickTableComponent } from '@dataclouder/ngx-core';
35
39
  import { OverlayModule } from '@angular/cdk/overlay';
36
40
  import { PortalModule } from '@angular/cdk/portal';
37
- import * as i8$1 from 'primeng/textarea';
38
- import { TextareaModule } from 'primeng/textarea';
39
41
  import * as i5$1 from 'primeng/inputtext';
40
42
  import { InputTextModule } from 'primeng/inputtext';
41
43
  import * as i11 from 'primeng/togglebutton';
@@ -50,13 +52,13 @@ import * as i14 from 'primeng/select';
50
52
  import { SelectModule } from 'primeng/select';
51
53
  import * as i15 from 'primeng/popover';
52
54
  import { PopoverModule } from 'primeng/popover';
53
- import * as i3$1 from 'primeng/card';
55
+ import * as i3$2 from 'primeng/card';
54
56
  import { CardModule } from 'primeng/card';
55
- import * as i4$2 from 'primeng/dropdown';
57
+ import * as i4$3 from 'primeng/dropdown';
56
58
  import { DropdownModule } from 'primeng/dropdown';
57
59
  import { ChipModule } from 'primeng/chip';
58
- import * as i1$3 from '@angular/router';
59
- import * as i3$2 from 'primeng/paginator';
60
+ import * as i1$4 from '@angular/router';
61
+ import * as i3$3 from 'primeng/paginator';
60
62
  import { PaginatorModule } from 'primeng/paginator';
61
63
  import * as i2$3 from 'primeng/speeddial';
62
64
  import { SpeedDialModule } from 'primeng/speeddial';
@@ -79,8 +81,13 @@ var EAccountsPlatform;
79
81
  EAccountsPlatform["Youtube"] = "youtube";
80
82
  })(EAccountsPlatform || (EAccountsPlatform = {}));
81
83
  class WordTimestamps {
84
+ constructor() {
85
+ this.highlighted = false; // Initialize with false and use proper boolean type
86
+ }
82
87
  }
83
- class ChatMultiMessage {
88
+ class MessageAudio {
89
+ }
90
+ class ChatMultiMessage extends MessageAudio {
84
91
  }
85
92
  var ChatRole;
86
93
  (function (ChatRole) {
@@ -89,7 +96,7 @@ var ChatRole;
89
96
  ChatRole["Assistant"] = "assistant";
90
97
  ChatRole["AssistantHelper"] = "assistantHelper";
91
98
  })(ChatRole || (ChatRole = {}));
92
- class ChatMessage {
99
+ class ChatMessage extends MessageAudio {
93
100
  }
94
101
  class ChatUserSettings {
95
102
  }
@@ -119,6 +126,20 @@ const LangCodeDescriptionEs = {
119
126
  pt: 'Portugués',
120
127
  fr: 'Frances',
121
128
  };
129
+ // TODO: use the default settings when the user is not set
130
+ const defaultconvUserSettings = {
131
+ realTime: false,
132
+ repeatRecording: false,
133
+ fixGrammar: false,
134
+ superHearing: false,
135
+ voice: '',
136
+ autoTranslate: false,
137
+ highlightWords: true,
138
+ synthVoice: true,
139
+ model: { modelName: null, provider: 'google' },
140
+ speed: null,
141
+ speedRate: null, // temporal
142
+ };
122
143
  class VoiceTTSOption {
123
144
  }
124
145
  const VoiceTTSOptions = [
@@ -531,17 +552,25 @@ class DCConversationPromptBuilderService {
531
552
  this.parseConversation(initialConversation, parseDict);
532
553
  return initialConversation;
533
554
  }
534
- getDefaultParseDict(parseDict, conversation) {
555
+ getDefaultParseDict(parseDict, card) {
556
+ // Example of return { char: 'AI Bot', user: 'jordan', ... custom params, base: 'spanish' };
557
+ // conversation will parse values with curren value in parseDcit:
558
+ // Hello {{user}} im {{char}} your are talking {{base}} -> hello jordan im AI bot your are talking spanish
559
+ // important you have to implement userDataExchange to return what you want in parseDict that have sense for your app.
535
560
  if (parseDict) {
536
561
  return parseDict;
537
562
  }
538
- if (!parseDict) {
563
+ else {
539
564
  parseDict = this.userDataExchange.getParseDict();
540
- if (!parseDict) {
541
- parseDict = { char: conversation.characterCard.data.name || 'Bot' };
542
- }
565
+ const cardParseDict = this.getDefaultCardParseDict(card);
566
+ parseDict = { ...parseDict, ...cardParseDict };
567
+ return parseDict;
543
568
  }
544
- return parseDict;
569
+ }
570
+ getDefaultCardParseDict(card) {
571
+ // bot so far i don't use more than the character name.
572
+ // this is what silly tavern call macros https://docs.sillytavern.app/usage/core-concepts/macros/#general-macros
573
+ return { char: card.characterCard.data.name || 'Bot' };
545
574
  }
546
575
  convertConversationToHtml(messages, jailBrake = '') {
547
576
  let finalPrompt = '';
@@ -643,8 +672,8 @@ class DCConversationPromptBuilderService {
643
672
  last_prompt: jailBrakePrompt,
644
673
  textEngine: agent.conversationSettings.textEngine,
645
674
  conversationType: ConversationType.Scenario,
646
- voice: agent.tts.voice,
647
- secondaryVoice: agent.tts.secondaryVoice,
675
+ voice: agent.conversationSettings.tts.voice,
676
+ secondaryVoice: agent.conversationSettings.tts.secondaryVoice,
648
677
  // overrideConversationSettings: { autoTranslate: false, highlightWords: false, realTime: false, provider: 'google' },
649
678
  };
650
679
  return settings;
@@ -662,127 +691,119 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImpor
662
691
  args: [USER_DATA_EXCHANGE]
663
692
  }] }] });
664
693
 
665
- const ICONS = {
666
- gear: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="currentColor">
667
- <path d="M12 1.75a1.25 1.25 0 0 1 1.25 1.25v.917a8.456 8.456 0 0 1 2.334.98l.645-.647a1.25 1.25 0 1 1 1.768 1.768l-.646.645c.389.69.729 1.437.98 2.334h.917a1.25 1.25 0 0 1 0 2.5h-.917a8.456 8.456 0 0 1-.98 2.334l.646.645a1.25 1.25 0 1 1-1.768 1.768l-.645-.646a8.456 8.456 0 0 1-2.334.98v.917a1.25 1.25 0 0 1-2.5 0v-.917a8.456 8.456 0 0 1-2.334-.98l-.645.646a1.25 1.25 0 1 1-1.768-1.768l.646-.645a8.456 8.456 0 0 1-.98-2.334H1.75a1.25 1.25 0 0 1 0-2.5h.917a8.456 8.456 0 0 1 .98-2.334l-.646-.645a1.25 1.25 0 1 1 1.768-1.768l.645.647c.69-.389 1.437-.729 2.334-.98V3a1.25 1.25 0 0 1 1.25-1.25h.083ZM12 7.5a4.5 4.5 0 1 0 0 9 4.5 4.5 0 0 0 0-9Z"/>
668
- </svg>
669
- `,
670
- chat: `<svg viewBox="0 0 24 24" fill="currentColor">
671
- <path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c..." />
672
- </svg>`,
673
- play: `<svg
674
- xmlns="http://www.w3.org/2000/svg"
675
- width="16"
676
- height="16"
677
- viewBox="0 0 24 24"
678
- fill="none"
679
- stroke="#263042"
680
- stroke-width="2"
681
- stroke-linecap="round"
682
- stroke-linejoin="round">
683
- <circle cx="12" cy="12" r="10"></circle>
684
- <polygon points="10 8 16 12 10 16 10 8"></polygon>
685
- </svg>`,
686
- loading: `<svg
687
- xmlns="http://www.w3.org/2000/svg"
688
- width="16"
689
- height="16"
690
- viewBox="0 0 24 24"
691
- fill="none"
692
- stroke="#263042"
693
- stroke-width="2"
694
- stroke-linecap="round"
695
- stroke-linejoin="round">
696
- <line x1="12" y1="2" x2="12" y2="6"></line>
697
- <line x1="12" y1="18" x2="12" y2="22"></line>
698
- <line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line>
699
- <line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line>
700
- <line x1="2" y1="12" x2="6" y2="12"></line>
701
- <line x1="18" y1="12" x2="22" y2="12"></line>
702
- <line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line>
703
- <line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line>
704
- </svg>`,
705
- user: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
706
- <!-- Head circle -->
707
- <circle cx="12" cy="8" r="4" fill="currentColor"/>
708
-
709
- <!-- Body shape -->
710
- <path d="M20 19v1a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-1c0-3.87 3.13-7 7-7h2c3.87 0 7 3.13 7 7z" fill="currentColor"/>
711
- </svg>`,
712
- ai: `<svg
713
- xmlns="http://www.w3.org/2000/svg"
714
- viewBox="0 0 100 100"
715
- width="100"
716
- height="100"
717
- fill="none"
718
- stroke="black"
719
- stroke-width="2"
720
- >
721
- <!-- Brain outline -->
722
- <path
723
- d="M50 10
724
- C60 10, 70 20, 70 30
725
- S60 50, 50 50
726
- S30 40, 30 30
727
- S40 10, 50 10Z"
728
- fill="#B3E5FC"
729
- />
730
-
731
- <!-- Circuit lines -->
732
- <line x1="50" y1="50" x2="50" y2="70" stroke="#0288D1" stroke-width="2" />
733
- <line x1="50" y1="70" x2="40" y2="80" stroke="#0288D1" stroke-width="2" />
734
- <line x1="50" y1="70" x2="60" y2="80" stroke="#0288D1" stroke-width="2" />
735
-
736
- <!-- Nodes -->
737
- <circle cx="50" cy="70" r="2" fill="#0288D1" />
738
- <circle cx="40" cy="80" r="3" fill="#0288D1" />
739
- <circle cx="60" cy="80" r="3" fill="#0288D1" />
740
- </svg>
741
- `,
742
- };
694
+ class ChatHeaderComponent {
695
+ constructor(agentCardService) {
696
+ this.agentCardService = agentCardService;
697
+ this.isAdmin = false;
698
+ this.alternativeConversation = [];
699
+ this.restartConversationEvent = new EventEmitter();
700
+ this.showInfoEvent = new EventEmitter();
701
+ this.settingsClickEvent = new EventEmitter();
702
+ }
703
+ restartConversation(conversation = null) {
704
+ this.restartConversationEvent.emit(conversation);
705
+ }
706
+ showInfo() {
707
+ this.showInfoEvent.emit();
708
+ }
709
+ settingsClick() {
710
+ this.settingsClickEvent.emit();
711
+ }
712
+ async changeConversationCard() {
713
+ const response = prompt('¿Qué conversación quieres usar? Escribe el titulo ejemplo: word_reflection_level_1_base_es');
714
+ if (response) {
715
+ const filters = {
716
+ filters: {
717
+ title: { $regex: response },
718
+ },
719
+ };
720
+ console.log('filters', filters);
721
+ const conversationCards = await this.agentCardService.filterConversationCards(filters);
722
+ console.log('conversationCards', conversationCards);
723
+ this.alternativeConversation = conversationCards.rows;
724
+ }
725
+ }
726
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ChatHeaderComponent, deps: [{ token: CONVERSATION_AI_TOKEN }], target: i0.ɵɵFactoryTarget.Component }); }
727
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", type: ChatHeaderComponent, isStandalone: true, selector: "dc-chat-header", inputs: { isAdmin: "isAdmin", alternativeConversation: "alternativeConversation", agentCard: "agentCard" }, 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"], dependencies: [{ kind: "ngmodule", type: CommonModule }] }); }
728
+ }
729
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ChatHeaderComponent, decorators: [{
730
+ type: Component,
731
+ args: [{ selector: 'dc-chat-header', standalone: true, imports: [CommonModule], 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"] }]
732
+ }], ctorParameters: () => [{ type: AgentCardsAbstractService, decorators: [{
733
+ type: Inject,
734
+ args: [CONVERSATION_AI_TOKEN]
735
+ }] }], propDecorators: { isAdmin: [{
736
+ type: Input
737
+ }], alternativeConversation: [{
738
+ type: Input
739
+ }], agentCard: [{
740
+ type: Input
741
+ }], restartConversationEvent: [{
742
+ type: Output
743
+ }], showInfoEvent: [{
744
+ type: Output
745
+ }], settingsClickEvent: [{
746
+ type: Output
747
+ }] } });
743
748
 
744
- class IconsComponent {
745
- constructor(sanitizer) {
746
- this.sanitizer = sanitizer;
747
- this.size = 14;
748
- this.color = 'currentColor';
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();
749
758
  }
750
- ngOnChanges(changes) {
751
- if (changes['name']) {
752
- const svg = ICONS[this.name] || '';
753
- this.sanitizedIcon = this.sanitizer.bypassSecurityTrustHtml(svg);
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;
754
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('');
755
788
  }
756
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: IconsComponent, deps: [{ token: i1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component }); }
757
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", type: IconsComponent, isStandalone: true, selector: "dc-icon", inputs: { name: "name", size: "size", color: "color" }, usesOnChanges: true, ngImport: i0, template: `
758
- <span
759
- [innerHTML]="sanitizedIcon"
760
- [style.display]="'inline-flex'"
761
- [style.alignItems]="'center'"
762
- [style.justifyContent]="'center'"
763
- [style.width.px]="size"
764
- [style.height.px]="size"
765
- [style.color]="color"></span>
766
- `, 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"] }); }
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"] }] }); }
767
791
  }
768
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: IconsComponent, decorators: [{
792
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ChatFooterComponent, decorators: [{
769
793
  type: Component,
770
- args: [{ selector: 'dc-icon', template: `
771
- <span
772
- [innerHTML]="sanitizedIcon"
773
- [style.display]="'inline-flex'"
774
- [style.alignItems]="'center'"
775
- [style.justifyContent]="'center'"
776
- [style.width.px]="size"
777
- [style.height.px]="size"
778
- [style.color]="color"></span>
779
- `, 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"] }]
780
- }], ctorParameters: () => [{ type: i1.DomSanitizer }], propDecorators: { name: [{
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: [{
781
796
  type: Input
782
- }], size: [{
797
+ }], score: [{
783
798
  type: Input
784
- }], color: [{
799
+ }], micSettings: [{
785
800
  type: Input
801
+ }], sendMessage: [{
802
+ type: Output
803
+ }], textInputChanged: [{
804
+ type: Output
805
+ }], micFinishedEvent: [{
806
+ type: Output
786
807
  }] } });
787
808
 
788
809
  function markdownToHtml(markdown) {
@@ -986,104 +1007,507 @@ function removeAllEmojis(text) {
986
1007
  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, '');
987
1008
  }
988
1009
 
989
- function markdownToHTML(markdownText) {
990
- // Convert italics-bold (***text***)
991
- let htmlText = markdownText.replace(/\*\*\*(.+?)\*\*\*/g, '<em><strong>$1</strong></em>');
992
- // Convert bold (**text**)
993
- htmlText = htmlText.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
994
- // Convert italics (*text*)
995
- htmlText = htmlText.replace(/\*(.+?)\*/g, '<em>$1</em>');
996
- // Convert text enclosed by double quotes ("text")
997
- // htmlText = htmlText.replace(/"(.+?)"/g, '<cite>$1</cite>');
998
- return htmlText;
999
- }
1000
- class SimpleMdToHtmlPipe {
1001
- transform(text) {
1002
- return markdownToHTML(text);
1003
- }
1004
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: SimpleMdToHtmlPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
1005
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.1.1", ngImport: i0, type: SimpleMdToHtmlPipe, isStandalone: true, name: "simpleMdToHtml" }); }
1006
- }
1007
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: SimpleMdToHtmlPipe, decorators: [{
1008
- type: Pipe,
1009
- args: [{
1010
- name: 'simpleMdToHtml',
1011
- standalone: true,
1012
- }]
1013
- }] });
1014
- class StartDivToHtmlPipe {
1015
- // solo create espacio entre lineas
1016
- transform(text) {
1017
- text = text.replace(/<start>/gi, '<hr>');
1018
- text = text.replace(/\n/g, '<br>');
1019
- return text;
1020
- }
1021
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: StartDivToHtmlPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
1022
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.1.1", ngImport: i0, type: StartDivToHtmlPipe, isStandalone: true, name: "startDividerToHtml" }); }
1023
- }
1024
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: StartDivToHtmlPipe, decorators: [{
1025
- type: Pipe,
1026
- args: [{
1027
- name: 'startDividerToHtml',
1028
- standalone: true,
1029
- }]
1030
- }] });
1031
- class MdToHtmlArrayPipe {
1032
- // devuelve un array de objetos con el contenido y el tag
1033
- transform(text) {
1034
- const htmlArray = convertToHTML(text);
1035
- return htmlArray.map((val) => val.content).join('');
1036
- }
1037
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: MdToHtmlArrayPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
1038
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.1.1", ngImport: i0, type: MdToHtmlArrayPipe, isStandalone: true, name: "mdToHtmlArray" }); }
1039
- }
1040
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: MdToHtmlArrayPipe, decorators: [{
1041
- type: Pipe,
1042
- args: [{
1043
- name: 'mdToHtmlArray',
1044
- standalone: true,
1045
- }]
1046
- }] });
1010
+ const ICONS = {
1011
+ chat: `<svg viewBox="0 0 24 24" fill="currentColor">
1012
+ <path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c..." />
1013
+ </svg>`,
1014
+ play: `<svg
1015
+ xmlns="http://www.w3.org/2000/svg"
1016
+ width="16"
1017
+ height="16"
1018
+ viewBox="0 0 24 24"
1019
+ fill="none"
1020
+ stroke="#263042"
1021
+ stroke-width="2"
1022
+ stroke-linecap="round"
1023
+ stroke-linejoin="round">
1024
+ <circle cx="12" cy="12" r="10"></circle>
1025
+ <polygon points="10 8 16 12 10 16 10 8"></polygon>
1026
+ </svg>`,
1027
+ loading: `<svg
1028
+ xmlns="http://www.w3.org/2000/svg"
1029
+ width="16"
1030
+ height="16"
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
1047
 
1048
- class ChatMessageComponent {
1049
- constructor(audioService, agentCardService, cdr) {
1050
- this.audioService = audioService;
1051
- this.agentCardService = agentCardService;
1052
- this.cdr = cdr;
1053
- this.isDestroyed = false;
1054
- // TODO: use the default settings when the user is not set
1055
- this.defaultconvUserSettings = {
1056
- realTime: false,
1057
- repeatRecording: false,
1058
- fixGrammar: false,
1059
- superHearing: false,
1060
- voice: '',
1061
- autoTranslate: false,
1062
- highlightWords: true,
1063
- synthVoice: true,
1064
- model: { modelName: null, provider: 'google' },
1065
- speed: null,
1066
- speedRate: null, // temporal
1067
- };
1048
+ class IconsComponent {
1049
+ constructor(sanitizer) {
1050
+ this.sanitizer = sanitizer;
1051
+ this.size = 14;
1052
+ this.color = 'currentColor';
1068
1053
  }
1069
- async ngOnInit() {
1070
- // this.conversationSettings = { ...this.conversationSettings, ...settings };
1071
- this.conversationChatSettings = await this.agentCardService.getConversationUserChatSettings();
1072
- if (this.chatMessage.role == ChatRole.AssistantHelper) {
1073
- return;
1054
+ ngOnChanges(changes) {
1055
+ if (changes['name']) {
1056
+ const svg = ICONS[this.name] || '';
1057
+ this.sanitizedIcon = this.sanitizer.bypassSecurityTrustHtml(svg);
1074
1058
  }
1075
- if (this.chatMessage.role == ChatRole.User) {
1076
- if (this.conversationChatSettings?.repeatRecording && this.conversationChatSettings?.synthVoice) {
1077
- this.playMessage(this.chatMessage);
1059
+ }
1060
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: IconsComponent, deps: [{ token: i1$1.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component }); }
1061
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", type: IconsComponent, isStandalone: true, selector: "dc-icon", inputs: { name: "name", size: "size", color: "color" }, usesOnChanges: true, ngImport: i0, template: `
1062
+ <span
1063
+ [innerHTML]="sanitizedIcon"
1064
+ [style.display]="'inline-flex'"
1065
+ [style.alignItems]="'center'"
1066
+ [style.justifyContent]="'center'"
1067
+ [style.width.px]="size"
1068
+ [style.height.px]="size"
1069
+ [style.color]="color"></span>
1070
+ `, 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"] }); }
1071
+ }
1072
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: IconsComponent, decorators: [{
1073
+ type: Component,
1074
+ args: [{ selector: 'dc-icon', template: `
1075
+ <span
1076
+ [innerHTML]="sanitizedIcon"
1077
+ [style.display]="'inline-flex'"
1078
+ [style.alignItems]="'center'"
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();
1104
+ });
1105
+ }
1106
+ /**
1107
+ * Synchronizes audio playback with text transcription
1108
+ * @param audioElement The audio element to sync with
1109
+ * @param transcriptionTimestamps Array of word timestamps
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
+ });
1167
+ }
1168
+ /**
1169
+ * Stops the sync for a specific message and cleans up resources
1170
+ * @param messageId The ID of the message to stop syncing
1171
+ */
1172
+ stopSync(messageId) {
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([]);
1078
1190
  }
1079
1191
  }
1192
+ }
1193
+ /**
1194
+ * Stops all syncs and cleans up all resources
1195
+ */
1196
+ stopAllSyncs() {
1197
+ // Get all message IDs and stop each sync
1198
+ for (const messageId of this.activeAudioMap.keys()) {
1199
+ this.stopSync(messageId);
1200
+ }
1201
+ // Clear all maps
1202
+ this.highlightedWordsSignalMap.clear();
1203
+ this.highlightedWords$Map.clear();
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([]));
1215
+ }
1216
+ return this.highlightedWordsSignalMap.get(messageId);
1217
+ }
1218
+ /**
1219
+ * Returns the highlighted words observable for a specific message
1220
+ * @param messageId The ID of the message
1221
+ */
1222
+ getHighlightedWords$(messageId) {
1223
+ // Create a new subject for this message if it doesn't exist
1224
+ if (!this.highlightedWords$Map.has(messageId)) {
1225
+ this.highlightedWords$Map.set(messageId, new BehaviorSubject([]));
1226
+ }
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
+ }
1246
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: AudioTextSyncService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1247
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: AudioTextSyncService, providedIn: 'root' }); }
1248
+ }
1249
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: AudioTextSyncService, decorators: [{
1250
+ type: Injectable,
1251
+ args: [{
1252
+ providedIn: 'root',
1253
+ }]
1254
+ }], ctorParameters: () => [] });
1255
+
1256
+ class AudioTextSyncComponent {
1257
+ constructor(cdr, audioTextSyncService) {
1258
+ this.cdr = cdr;
1259
+ this.audioTextSyncService = audioTextSyncService;
1260
+ this.isLoading = false;
1261
+ this.playAudio = new EventEmitter();
1262
+ // Get the highlighted words signal from the service for this specific message
1263
+ this.highlightedWords = signal([]);
1264
+ // Unique ID for this message instance
1265
+ this.messageId = '';
1266
+ this.injector = inject(Injector);
1267
+ // Setup effect to mark for check when highlighted words change
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 }}&nbsp;</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 }}&nbsp;</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
+ }
1080
1452
  else {
1081
- if (!this.conversationChatSettings?.synthVoice) {
1082
- return;
1453
+ transcriptionMap.set(lowercaseWord, [obj]);
1454
+ }
1455
+ }
1456
+ // Split the original text into an array of words
1457
+ const words = originalText.split(' ');
1458
+ let currentTime = 0;
1459
+ for (const word of words) {
1460
+ const lowercaseWord = word.toLowerCase();
1461
+ const matchedObjs = transcriptionMap.get(lowercaseWord);
1462
+ if (matchedObjs && matchedObjs.length > 0) {
1463
+ const matchedObj = matchedObjs.shift();
1464
+ result.push({
1465
+ word: word,
1466
+ start: Number(matchedObj.start.toFixed(3)),
1467
+ end: Number(matchedObj.end.toFixed(3)),
1468
+ });
1469
+ currentTime = matchedObj.end;
1470
+ }
1471
+ else {
1472
+ result.push({
1473
+ word: word,
1474
+ start: Number(currentTime.toFixed(3)),
1475
+ end: Number(currentTime.toFixed(3)),
1476
+ });
1477
+ }
1478
+ }
1479
+ return result;
1480
+ }
1481
+ function extractAudioAndTranscription(message, audio) {
1482
+ message.audioUrl = audio.blobUrl;
1483
+ message.transcription = audio.transcription;
1484
+ if (message.transcription) {
1485
+ message.transcriptionTimestamps = matchTranscription(message.text || message.content, message.transcription.words);
1486
+ }
1487
+ }
1488
+
1489
+ class ChatMessageComponent {
1490
+ constructor(audioService, agentCardService, audioTextSyncService) {
1491
+ this.audioService = audioService;
1492
+ this.agentCardService = agentCardService;
1493
+ this.audioTextSyncService = audioTextSyncService;
1494
+ this.destroyRef = inject(DestroyRef);
1495
+ this.conversationChatSettings = signal(null);
1496
+ this.isLoading = signal(false);
1497
+ }
1498
+ async ngOnInit() {
1499
+ const settings = await this.agentCardService.getConversationUserChatSettings();
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);
1083
1508
  }
1084
- console.log(this.chatMessage.multiMessages);
1085
- this.cdr.markForCheck();
1086
- // this is an assistant message
1509
+ }
1510
+ else if (settings$?.synthVoice) {
1087
1511
  if (this.chatMessage.multiMessages) {
1088
1512
  this.generateAndPlayAllAudios(this.chatMessage.multiMessages);
1089
1513
  }
@@ -1092,170 +1516,443 @@ class ChatMessageComponent {
1092
1516
  }
1093
1517
  }
1094
1518
  }
1095
- ngOnDestroy() {
1096
- this.isDestroyed = true;
1097
- }
1098
- async generateAndPlayAudio(message, overwritetext = null) {
1099
- const text = overwritetext || message.content;
1100
- const ttsObject = this.getTTSRequest({ ...this.chatMessage, text });
1101
- if (message.ssml) {
1102
- ttsObject.ssml = message.ssml;
1103
- }
1104
- const speechAudio = await this.agentCardService.getTextAudioFile(ttsObject);
1105
- this.extractAudioAndTranscription(message, speechAudio);
1106
- this.playMessage(message);
1107
- // TODO: cuando no sirve el server utilizar esta voz del navegador
1108
- // else if (this.selectedAudioType === 'default') {
1109
- // await this.speechService.speach(message.content, { speed: 1, tone: 0.9 });
1110
- // }
1111
- }
1112
- extractAudioAndTranscription(message, audio) {
1113
- message.audioUrl = audio.blobUrl;
1114
- message.transcription = audio.transcription;
1115
- if (message.transcription) {
1116
- message.transcriptionTimestamps = this.matchTranscription(message.text || message.content, message.transcription.words);
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);
1117
1533
  }
1118
1534
  }
1119
1535
  playMessage(message) {
1120
- console.log('playMessage', message);
1121
- if (message.audioUrl) {
1122
- const audioHtml = this.audioService.playAudio(message.audioUrl);
1123
- message['audioHtml'] = audioHtml;
1124
- this.subscribeToTimeUpdate(audioHtml, message.transcriptionTimestamps);
1125
- return audioHtml;
1126
- }
1127
- else {
1128
- // this.speechService.speach(message.content);
1536
+ if (!message.audioUrl) {
1537
+ return null;
1129
1538
  }
1130
- return null;
1131
- }
1132
- subscribeToTimeUpdate(audioHtml, transcriptionTimestamps = null) {
1133
- console.log('subscribing to time updates', audioHtml, transcriptionTimestamps);
1134
- if (transcriptionTimestamps) {
1135
- audioHtml.ontimeupdate = (time) => {
1136
- transcriptionTimestamps.forEach((word) => {
1137
- word.highlighted = audioHtml.currentTime >= word.start - 0.15 && audioHtml.currentTime < word.end + 0.15;
1138
- console.log('highliting word', word, 'enable detecting');
1139
- this.cdr.detectChanges();
1140
- });
1141
- };
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);
1142
1558
  }
1143
- audioHtml.onended = () => {
1144
- audioHtml.ontimeupdate = null;
1145
- audioHtml = null;
1146
- // this.chatBroker.currentMessagePlayed$.next(true);
1147
- };
1559
+ return audioElement;
1148
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
+ */
1149
1579
  generateAndPlayAllAudios(multiMessages) {
1580
+ if (!multiMessages.length)
1581
+ return;
1150
1582
  let currentIndex = 0;
1151
1583
  const playAudioSequentially = async () => {
1152
- if (this.isDestroyed)
1153
- return;
1154
1584
  if (currentIndex >= multiMessages.length) {
1155
- console.log('All audio files have been played.');
1156
1585
  return;
1157
1586
  }
1158
- const msn = multiMessages[currentIndex];
1159
- // Audio is processed in ahead at last, first time will wait for audio, the rest is only wait,
1160
- if (!msn.audioUrl) {
1161
- if (msn.audioPromise) {
1162
- await msn.audioPromise;
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
+ });
1163
1609
  }
1164
1610
  else {
1165
- const request = this.getTTSRequest(msn);
1166
- msn.isLoading = true;
1167
- this.cdr.detectChanges();
1168
- msn.audioPromise = this.agentCardService.getTextAudioFile(request);
1169
- const audio = await msn.audioPromise;
1170
- this.extractAudioAndTranscription(msn, audio);
1171
- msn.isLoading = false;
1172
- this.cdr.detectChanges();
1611
+ // If playback failed, move to next message
1612
+ currentIndex++;
1613
+ playAudioSequentially();
1173
1614
  }
1615
+ // Preload next message audio
1616
+ this.preloadNextMessageAudio(multiMessages, currentIndex);
1174
1617
  }
1175
- const audioHtml = this.playMessage(msn);
1176
- audioHtml.onended = () => {
1177
- currentIndex++;
1178
- playAudioSequentially();
1179
- };
1180
- // Load the next audio file in advance
1181
- if (currentIndex + 1 < multiMessages.length) {
1182
- const nextMessage = multiMessages[currentIndex + 1];
1183
- const request = this.getTTSRequest(nextMessage);
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);
1184
1630
  nextMessage.isLoading = true;
1185
1631
  nextMessage.audioPromise = this.agentCardService.getTextAudioFile(request);
1186
1632
  nextMessage.audioPromise.then((audio) => {
1187
- this.extractAudioAndTranscription(nextMessage, audio);
1633
+ extractAudioAndTranscription(nextMessage, audio);
1188
1634
  nextMessage.isLoading = false;
1189
1635
  });
1190
1636
  }
1191
- };
1192
- playAudioSequentially();
1637
+ }
1193
1638
  }
1194
- getTTSRequest(nextMessage) {
1195
- // const settingsConv = this.userService.getUserSnapshot().settings.conversation;
1196
- const generateTranscription = this.conversationChatSettings?.highlightWords ?? false;
1197
- const speedRate = this.conversationChatSettings?.speedRate || 0;
1198
- const speed = typeof this.conversationChatSettings?.speed === 'string' ? this.conversationChatSettings.speed : AudioSpeed.Regular;
1199
- const text = removeEmojis(nextMessage.text);
1200
- const data = {
1201
- text: text,
1202
- voice: nextMessage.voice || this.chatMessage.voice,
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,
1203
1648
  generateTranscription,
1204
1649
  speedRate,
1205
1650
  speed,
1206
1651
  };
1207
- return data;
1208
- }
1209
- matchTranscription(originalText, transcription) {
1210
- const result = [];
1211
- const transcriptionMap = new Map();
1212
- // Create a map of lowercase words to an array of their corresponding objects in transcription
1213
- for (const obj of transcription) {
1214
- const lowercaseWord = obj.word.trim().toLowerCase();
1215
- if (transcriptionMap.has(lowercaseWord)) {
1216
- transcriptionMap.get(lowercaseWord).push(obj);
1217
- }
1218
- else {
1219
- transcriptionMap.set(lowercaseWord, [obj]);
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');
1220
1694
  }
1695
+ const content = this.subsItalicsByTag(processedMessage.content, conversationSettings.secondaryVoice);
1696
+ processedMessage.ssml = '<speak>' + content + '</speak>';
1221
1697
  }
1222
- // Split the original text into an array of words
1223
- const words = originalText.split(' ');
1224
- let currentTime = 0;
1225
- for (const word of words) {
1226
- const lowercaseWord = word.toLowerCase();
1227
- const matchedObjs = transcriptionMap.get(lowercaseWord);
1228
- if (matchedObjs && matchedObjs.length > 0) {
1229
- const matchedObj = matchedObjs.shift();
1230
- result.push({
1231
- word: word,
1232
- start: matchedObj.start,
1233
- end: matchedObj.end,
1234
- });
1235
- currentTime = matchedObj.end;
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;
1236
1744
  }
1237
1745
  else {
1238
- result.push({
1239
- word: word,
1240
- start: currentTime,
1241
- end: currentTime,
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' }); }
1753
+ }
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
+
1761
+ class ConversationService {
1762
+ constructor(agentCardService, messageProcessingService) {
1763
+ this.agentCardService = agentCardService;
1764
+ this.messageProcessingService = messageProcessingService;
1765
+ this.messagesSignal = signal([]);
1766
+ this.isThinkingSignal = signal(false);
1767
+ this.conversationSettingsSignal = signal(null);
1768
+ this.agentCardSignal = signal(null);
1769
+ this.isDestroyedSignal = signal(false);
1770
+ }
1771
+ // Get messages as a signal
1772
+ getMessages() {
1773
+ return this.messagesSignal;
1774
+ }
1775
+ // Get thinking state as a signal
1776
+ isThinking() {
1777
+ return this.isThinkingSignal;
1778
+ }
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
+ // Set destroyed state
1788
+ setDestroyed(value) {
1789
+ this.isDestroyedSignal.set(value);
1790
+ }
1791
+ // Initialize conversation
1792
+ async initConversation(agentCard, conversationBuilder, parseDict) {
1793
+ if (!agentCard?.conversationSettings) {
1794
+ throw new Error('Conversation settings are required');
1795
+ }
1796
+ this.agentCardSignal.set(agentCard);
1797
+ // Build conversation settings
1798
+ const conversationSettings = conversationBuilder.buildConversationSettings(agentCard, parseDict);
1799
+ this.conversationSettingsSignal.set(conversationSettings);
1800
+ // Update agent card with conversation settings
1801
+ const updatedAgentCard = { ...agentCard, conversationSettings };
1802
+ this.agentCardSignal.set(updatedAgentCard);
1803
+ if (!conversationSettings.messages) {
1804
+ throw new Error('conversationSettings.messages is required in proper format to start conversation');
1805
+ }
1806
+ // Set initial messages
1807
+ this.messagesSignal.set(conversationSettings.messages);
1808
+ // Find first assistant message
1809
+ const firstAssistantMsg = conversationSettings.messages.find((message) => message.role === ChatRole.Assistant);
1810
+ if (firstAssistantMsg) {
1811
+ // Process the first assistant message
1812
+ this.processAssistantMessage(firstAssistantMsg, true);
1813
+ }
1814
+ else if (agentCard.conversationSettings.autoStart) {
1815
+ // Auto-start conversation if configured
1816
+ await this.sendCurrentConversation();
1817
+ }
1818
+ }
1819
+ // Send user message
1820
+ async sendUserMessage(message) {
1821
+ if (this.isThinkingSignal()) {
1822
+ return;
1823
+ }
1824
+ // Add message to conversation
1825
+ this.addMessage(message);
1826
+ // Set thinking state
1827
+ this.isThinkingSignal.set(true);
1828
+ 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
+ }
1242
1840
  });
1243
1841
  }
1842
+ // Send to AI service
1843
+ await this.sendCurrentConversation();
1244
1844
  }
1245
- return result;
1845
+ finally {
1846
+ this.isThinkingSignal.set(false);
1847
+ }
1848
+ }
1849
+ // Add message to conversation
1850
+ addMessage(message) {
1851
+ this.messagesSignal.update((messages) => [...messages, message]);
1246
1852
  }
1247
- 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: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
1248
- 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: "@if (chatMessage?.multiMessages) {\n<h5>Multi message</h5>\n<span style=\"line-height: 1.5\" *ngFor=\"let message of chatMessage.multiMessages\">\n <div [ngClass]=\"message.tag\" *ngIf=\"message.transcriptionTimestamps\">\n <ng-container *ngTemplateOutlet=\"iconTemplate; context: { isLoading: message.isLoading, message: message }\"></ng-container>\n <span *ngFor=\"let word of message.transcriptionTimestamps\" [class.highlight]=\"word.highlighted\">\n {{ word.word }}\n </span>\n </div>\n\n <div *ngIf=\"!message.transcriptionTimestamps\">\n <div (click)=\"playMessage(message)\">\n <ng-container *ngTemplateOutlet=\"iconTemplate; context: { isLoading: message.isLoading, message: message }\"></ng-container>\n <span style=\"margin-left: 2px\" [ngClass]=\"message.tag\" [innerHtml]=\"message.text\"> </span>\n </div>\n </div>\n</span>\n}@else {\n\n<span>\n @if (chatMessage.transcriptionTimestamps) {\n <div>\n <span *ngFor=\"let word of chatMessage.transcriptionTimestamps\" [class.highlight]=\"word.highlighted\">\n {{ word.word }}\n </span>\n </div>\n } @else {\n <div>\n <span (click)=\"playMessage(chatMessage)\" [innerHTML]=\"chatMessage.content | simpleMdToHtml\"></span>\n </div>\n }\n</span>\n}\n\n<div class=\"small\" *ngIf=\"chatMessage.translation\">\n <hr class=\"divider\" />\n {{ chatMessage.translation }}\n</div>\n\n<ng-template #iconTemplate let-isLoading=\"isLoading\" let-message=\"message\">\n <i *ngIf=\"isLoading\" (click)=\"playMessage(message)\">\n <dc-icon class=\"spin-animation\" name=\"loading\"></dc-icon>\n </i>\n\n <i *ngIf=\"!isLoading\" (click)=\"playMessage(message)\">\n <dc-icon name=\"play\"></dc-icon>\n </i>\n</ng-template>\n", styles: [":host{display:block}.highlight{background-color:#fff8a7;color:#011630;text-decoration:underline #59a4ff solid 3px}::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}.small{margin-top:8x;font-size:small;line-height:1.6;color:#393744;font-style:italic}.divider{margin:.5rem 40px;border-top:1px solid #ffa77e}.spin-animation{display:inline-block;animation:spin 2s linear infinite;transform-origin:center center;transform-box:fill-box;vertical-align:middle}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: IconsComponent, selector: "dc-icon", inputs: ["name", "size", "color"] }, { kind: "pipe", type: SimpleMdToHtmlPipe, name: "simpleMdToHtml" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1853
+ // Process assistant message
1854
+ processAssistantMessage(message, mutate = false) {
1855
+ const conversationSettings = this.conversationSettingsSignal();
1856
+ if (!conversationSettings) {
1857
+ throw new Error('Conversation settings not initialized');
1858
+ }
1859
+ return this.messageProcessingService.processMessage(message, conversationSettings, mutate);
1860
+ }
1861
+ // Send current conversation to AI
1862
+ async sendCurrentConversation() {
1863
+ if (this.isDestroyedSignal()) {
1864
+ return;
1865
+ }
1866
+ const messages = this.messagesSignal();
1867
+ const conversationSettings = this.conversationSettingsSignal();
1868
+ const agentCard = this.agentCardSignal();
1869
+ if (!conversationSettings || !agentCard) {
1870
+ throw new Error('Conversation not properly initialized');
1871
+ }
1872
+ if (messages.length > 31) {
1873
+ // Safety limit to prevent infinite conversations
1874
+ return;
1875
+ }
1876
+ let conversationMessages = messages;
1877
+ // Add last prompt if available
1878
+ if (conversationSettings.last_prompt) {
1879
+ conversationMessages = [
1880
+ ...messages,
1881
+ { content: conversationSettings.last_prompt, role: ChatRole.System },
1882
+ ];
1883
+ }
1884
+ // Prepare conversation DTO
1885
+ const conversation = {
1886
+ messages: conversationMessages,
1887
+ conversationType: conversationSettings.conversationType,
1888
+ textEngine: conversationSettings.textEngine,
1889
+ model: agentCard.model,
1890
+ };
1891
+ // Call AI service
1892
+ const response = await this.agentCardService.callChatCompletion(conversation);
1893
+ if (!response) {
1894
+ console.error('No message returned from AI, is your service working?');
1895
+ throw new Error('No message returned from AI');
1896
+ }
1897
+ // Process response
1898
+ const newMessage = this.processAssistantMessage(response);
1899
+ // Add to messages
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;
1907
+ }
1908
+ // Reset conversation
1909
+ async resetConversation(agentCard) {
1910
+ if (agentCard) {
1911
+ this.agentCardSignal.set(agentCard);
1912
+ }
1913
+ // Clear messages
1914
+ this.messagesSignal.set([]);
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);
1920
+ }
1921
+ }
1922
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ConversationService, deps: [{ token: CONVERSATION_AI_TOKEN }, { token: MessageProcessingService }], target: i0.ɵɵFactoryTarget.Injectable }); }
1923
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ConversationService, providedIn: 'root' }); }
1249
1924
  }
1250
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ChatMessageComponent, decorators: [{
1251
- type: Component,
1252
- args: [{ selector: 'dc-chat-message', standalone: true, imports: [CommonModule, IconsComponent, SimpleMdToHtmlPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (chatMessage?.multiMessages) {\n<h5>Multi message</h5>\n<span style=\"line-height: 1.5\" *ngFor=\"let message of chatMessage.multiMessages\">\n <div [ngClass]=\"message.tag\" *ngIf=\"message.transcriptionTimestamps\">\n <ng-container *ngTemplateOutlet=\"iconTemplate; context: { isLoading: message.isLoading, message: message }\"></ng-container>\n <span *ngFor=\"let word of message.transcriptionTimestamps\" [class.highlight]=\"word.highlighted\">\n {{ word.word }}\n </span>\n </div>\n\n <div *ngIf=\"!message.transcriptionTimestamps\">\n <div (click)=\"playMessage(message)\">\n <ng-container *ngTemplateOutlet=\"iconTemplate; context: { isLoading: message.isLoading, message: message }\"></ng-container>\n <span style=\"margin-left: 2px\" [ngClass]=\"message.tag\" [innerHtml]=\"message.text\"> </span>\n </div>\n </div>\n</span>\n}@else {\n\n<span>\n @if (chatMessage.transcriptionTimestamps) {\n <div>\n <span *ngFor=\"let word of chatMessage.transcriptionTimestamps\" [class.highlight]=\"word.highlighted\">\n {{ word.word }}\n </span>\n </div>\n } @else {\n <div>\n <span (click)=\"playMessage(chatMessage)\" [innerHTML]=\"chatMessage.content | simpleMdToHtml\"></span>\n </div>\n }\n</span>\n}\n\n<div class=\"small\" *ngIf=\"chatMessage.translation\">\n <hr class=\"divider\" />\n {{ chatMessage.translation }}\n</div>\n\n<ng-template #iconTemplate let-isLoading=\"isLoading\" let-message=\"message\">\n <i *ngIf=\"isLoading\" (click)=\"playMessage(message)\">\n <dc-icon class=\"spin-animation\" name=\"loading\"></dc-icon>\n </i>\n\n <i *ngIf=\"!isLoading\" (click)=\"playMessage(message)\">\n <dc-icon name=\"play\"></dc-icon>\n </i>\n</ng-template>\n", styles: [":host{display:block}.highlight{background-color:#fff8a7;color:#011630;text-decoration:underline #59a4ff solid 3px}::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}.small{margin-top:8x;font-size:small;line-height:1.6;color:#393744;font-style:italic}.divider{margin:.5rem 40px;border-top:1px solid #ffa77e}.spin-animation{display:inline-block;animation:spin 2s linear infinite;transform-origin:center center;transform-box:fill-box;vertical-align:middle}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
1253
- }], ctorParameters: () => [{ type: AudioService }, { type: AgentCardsAbstractService, decorators: [{
1925
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ConversationService, decorators: [{
1926
+ type: Injectable,
1927
+ args: [{
1928
+ providedIn: 'root',
1929
+ }]
1930
+ }], ctorParameters: () => [{ type: AgentCardsAbstractService, decorators: [{
1254
1931
  type: Inject,
1255
1932
  args: [CONVERSATION_AI_TOKEN]
1256
- }] }, { type: i0.ChangeDetectorRef }], propDecorators: { chatMessage: [{
1933
+ }] }, { type: MessageProcessingService }] });
1934
+
1935
+ class ChatMessagesListComponent {
1936
+ constructor() {
1937
+ this.aiIcon = 'assets/default/ai.png';
1938
+ this.conversationService = inject(ConversationService);
1939
+ // Get messages and thinking state from the conversation service
1940
+ this.messages = this.conversationService.getMessages();
1941
+ this.isThinking = this.conversationService.isThinking();
1942
+ }
1943
+ // Track messages by their content and role for efficient rendering
1944
+ trackByMessage(index, message) {
1945
+ return `${message.role}-${index}-${message.content.substring(0, 20)}`;
1946
+ }
1947
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ChatMessagesListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1948
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", type: ChatMessagesListComponent, isStandalone: true, selector: "dc-chat-messages-list", inputs: { chatUserSettings: "chatUserSettings", thinkingImg: "thinkingImg" }, ngImport: i0, template: "<div class=\"messages-container\">\n @for (message of messages(); track trackByMessage($index, message)) {\n <dc-chat-message \n [chatMessage]=\"message\" \n [chatUserSettings]=\"chatUserSettings\">\n </dc-chat-message>\n }\n \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: "ngmodule", type: CommonModule }, { kind: "component", type: ChatMessageComponent, selector: "dc-chat-message", inputs: ["chatMessage", "chatUserSettings"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i4.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "style", "shape", "animation", "borderRadius", "size", "width", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1949
+ }
1950
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ChatMessagesListComponent, decorators: [{
1951
+ type: Component,
1952
+ args: [{ selector: 'dc-chat-messages-list', standalone: true, imports: [CommonModule, ChatMessageComponent, SkeletonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"messages-container\">\n @for (message of messages(); track trackByMessage($index, message)) {\n <dc-chat-message \n [chatMessage]=\"message\" \n [chatUserSettings]=\"chatUserSettings\">\n </dc-chat-message>\n }\n \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"] }]
1953
+ }], propDecorators: { chatUserSettings: [{
1257
1954
  type: Input
1258
- }], chatUserSettings: [{
1955
+ }], thinkingImg: [{
1259
1956
  type: Input
1260
1957
  }] } });
1261
1958
 
@@ -1349,7 +2046,7 @@ class ProviderSelectorComponent {
1349
2046
  }
1350
2047
  }
1351
2048
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ProviderSelectorComponent, deps: [{ token: CONVERSATION_AI_TOKEN }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
1352
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", 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: CommonModule }, { kind: "pipe", type: i2.DecimalPipe, name: "number" }, { kind: "pipe", type: i2.DatePipe, name: "date" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: RadioButtonModule }, { kind: "component", type: i3.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: i4.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: TableModule }, { kind: "component", type: i5.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: i6.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "style", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i7.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" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2049
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", 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: CommonModule }, { kind: "pipe", type: i1$2.DecimalPipe, name: "number" }, { kind: "pipe", type: i1$2.DatePipe, name: "date" }, { 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: i3$1.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: i4$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: TableModule }, { kind: "component", type: i5.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: i4.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "style", "shape", "animation", "borderRadius", "size", "width", "height"] }, { 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: "pipe", type: TruncatePipe, name: "truncate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1353
2050
  }
1354
2051
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ProviderSelectorComponent, decorators: [{
1355
2052
  type: Component,
@@ -1399,85 +2096,308 @@ class DCConversationUserChatSettingsComponent {
1399
2096
  });
1400
2097
  this.isAdmin = true;
1401
2098
  }
1402
- ngOnInit() {
1403
- console.log('Settings form initialized', this.form.value, this.form.controls.highlightWords);
1404
- this.loadInitialSettings();
1405
- this.form.valueChanges.subscribe((value) => {
1406
- this.onSettingsChange.emit(value);
1407
- });
2099
+ ngOnInit() {
2100
+ console.log('Settings form initialized', this.form.value, this.form.controls.highlightWords);
2101
+ this.loadInitialSettings();
2102
+ this.form.valueChanges.subscribe((value) => {
2103
+ this.onSettingsChange.emit(value);
2104
+ });
2105
+ }
2106
+ async loadInitialSettings() {
2107
+ const settings = await this.conversationCardAIService.getConversationUserChatSettings();
2108
+ if (settings) {
2109
+ this.form.patchValue(settings);
2110
+ }
2111
+ }
2112
+ close() {
2113
+ this.dialogRef.close();
2114
+ }
2115
+ async saveSettings() {
2116
+ if (this.form.valid) {
2117
+ await this.conversationCardAIService.saveConversationUserChatSettings(this.form.value);
2118
+ this.dialogRef.close(this.form.value);
2119
+ }
2120
+ }
2121
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCConversationUserChatSettingsComponent, deps: [{ token: i1$3.DynamicDialogRef }, { token: i1.FormBuilder }, { token: CONVERSATION_AI_TOKEN }], target: i0.ɵɵFactoryTarget.Component }); }
2122
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", type: DCConversationUserChatSettingsComponent, isStandalone: true, selector: "dc-chat-settings-dialog", inputs: { showFeature: "showFeature" }, outputs: { onSettingsChange: "onSettingsChange" }, viewQueries: [{ propertyName: "tooltipRef", first: true, predicate: ["tooltipRef"], descendants: true }], ngImport: i0, template: "<div class=\"dialog-container\">\n <form [formGroup]=\"form\">\n <div class=\"settings-section\" *ngIf=\"showFeature.synthVoice\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.highlightWords\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.speed\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.realTime\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.realTime\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.superHearing\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.fixGrammar\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.autoTranslate\">\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 <div class=\"voice-selection\" *ngIf=\"showFeature.autoTranslate\">\n <span>Voz Preferencial:</span>\n <br />\n <p-radioButton value=\"random\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Aleatorio</label>\n\n <p-radioButton value=\"randomMan\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Hombre</label>\n\n <p-radioButton value=\"randomWoman\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Mujer</label>\n </div>\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: CommonModule }, { 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.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: i4$2.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: i4$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: SliderModule }, { kind: "ngmodule", type: RadioButtonModule }, { kind: "component", type: i3$1.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: 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: "pipe", type: SpeedDescPipe, name: "speedDisplay" }, { kind: "ngmodule", type: RatingModule }, { kind: "component", type: i8.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
+ }
2124
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCConversationUserChatSettingsComponent, decorators: [{
2125
+ type: Component,
2126
+ args: [{ selector: 'dc-chat-settings-dialog', standalone: true, imports: [
2127
+ CommonModule,
2128
+ ReactiveFormsModule,
2129
+ CheckboxModule,
2130
+ SliderModule,
2131
+ RadioButtonModule,
2132
+ ButtonModule,
2133
+ SpeedDescPipe,
2134
+ RatingModule,
2135
+ TableModule,
2136
+ BadgeModule,
2137
+ SkeletonModule,
2138
+ TooltipModule,
2139
+ ProviderSelectorComponent,
2140
+ ], template: "<div class=\"dialog-container\">\n <form [formGroup]=\"form\">\n <div class=\"settings-section\" *ngIf=\"showFeature.synthVoice\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.highlightWords\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.speed\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.realTime\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.realTime\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.superHearing\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.fixGrammar\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.autoTranslate\">\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 <div class=\"voice-selection\" *ngIf=\"showFeature.autoTranslate\">\n <span>Voz Preferencial:</span>\n <br />\n <p-radioButton value=\"random\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Aleatorio</label>\n\n <p-radioButton value=\"randomMan\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Hombre</label>\n\n <p-radioButton value=\"randomWoman\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Mujer</label>\n </div>\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"] }]
2141
+ }], ctorParameters: () => [{ type: i1$3.DynamicDialogRef }, { type: i1.FormBuilder }, { type: AgentCardsAbstractService, decorators: [{
2142
+ type: Inject,
2143
+ args: [CONVERSATION_AI_TOKEN]
2144
+ }] }], propDecorators: { showFeature: [{
2145
+ type: Input
2146
+ }], onSettingsChange: [{
2147
+ type: Output
2148
+ }], tooltipRef: [{
2149
+ type: ViewChild,
2150
+ args: ['tooltipRef']
2151
+ }] } });
2152
+
2153
+ const EvalResultStringDefinition = `
2154
+ interface EvalResult {
2155
+ score: number; // Score of the user's response 0 to 3
2156
+ feedback: string; // Feedback of the user's understanding of the conversation
2157
+ }`;
2158
+ const DefaultEvaluatorAgentCard = {
2159
+ task: 'Evaluate the user understanding of the lesson',
2160
+ messages: [],
2161
+ expectedResponseType: EvalResultStringDefinition,
2162
+ model: { id: 'gpt-4o-mini', provider: 'openai' },
2163
+ sources: [],
2164
+ };
2165
+
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
+ class ChatContainerComponent {
2254
+ constructor(agentCardService, toastService, conversationBuilder, dialogService, conversationService, evaluationService) {
2255
+ this.agentCardService = agentCardService;
2256
+ this.toastService = toastService;
2257
+ this.conversationBuilder = conversationBuilder;
2258
+ this.dialogService = dialogService;
2259
+ this.conversationService = conversationService;
2260
+ this.evaluationService = evaluationService;
2261
+ this.evaluatorAgentCard = DefaultEvaluatorAgentCard;
2262
+ this.parseDict = {};
2263
+ this.sendMessage = new EventEmitter();
2264
+ 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
+ this.isInfoVisible = false;
2269
+ this.isChatSettingsVisible = false;
2270
+ this.isUserTalking = false;
2271
+ this.isGettingTranscription = false;
2272
+ this.isAdmin = true;
2273
+ // Get reactive state from services
2274
+ this.messages = this.conversationService.getMessages();
2275
+ this.isThinking = this.conversationService.isThinking();
2276
+ this.score = this.evaluationService.getScore();
2277
+ }
2278
+ async ngOnInit() {
2279
+ debugger;
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);
1408
2286
  }
1409
- async loadInitialSettings() {
1410
- const settings = await this.conversationCardAIService.getConversationUserChatSettings();
1411
- if (settings) {
1412
- this.form.patchValue(settings);
2287
+ ngOnDestroy() {
2288
+ // Mark conversation as destroyed to prevent async operations
2289
+ this.conversationService.setDestroyed(true);
2290
+ }
2291
+ /**
2292
+ * Handle user message from chat footer
2293
+ */
2294
+ async onUserMessage(message) {
2295
+ if (this.isThinking()) {
2296
+ return;
1413
2297
  }
2298
+ await this.conversationService.sendUserMessage(message);
2299
+ // Evaluate conversation after sending message
2300
+ this.evaluateConversation();
1414
2301
  }
1415
- close() {
1416
- this.dialogRef.close();
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
+ }
1417
2338
  }
1418
- async saveSettings() {
1419
- if (this.form.valid) {
1420
- await this.conversationCardAIService.saveConversationUserChatSettings(this.form.value);
1421
- this.dialogRef.close(this.form.value);
2339
+ /**
2340
+ * Open chat settings dialog
2341
+ */
2342
+ changeUserChatSettings() {
2343
+ this.dialogService
2344
+ .open(DCConversationUserChatSettingsComponent, {
2345
+ width: '90vw',
2346
+ header: 'Chat Settings',
2347
+ styleClass: 'settings-dialog',
2348
+ closable: true,
2349
+ closeOnEscape: true,
2350
+ })
2351
+ .onClose.subscribe(async () => {
2352
+ this.chatUserSettings = await this.agentCardService.getConversationUserChatSettings();
2353
+ });
2354
+ }
2355
+ /**
2356
+ * Show debug info
2357
+ */
2358
+ showInfo() {
2359
+ this.isInfoVisible = true;
2360
+ }
2361
+ /**
2362
+ * Restart conversation
2363
+ */
2364
+ async restartConversation(conversation = null) {
2365
+ if (conversation) {
2366
+ this.agentCard = conversation;
1422
2367
  }
2368
+ await this.ngOnInit();
2369
+ }
2370
+ /**
2371
+ * Evaluate conversation using evaluator agent
2372
+ */
2373
+ async evaluateConversation() {
2374
+ const messages = this.messages();
2375
+ await this.evaluationService.evaluateConversation(messages, this.evaluatorAgentCard);
1423
2376
  }
1424
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCConversationUserChatSettingsComponent, deps: [{ token: i1$2.DynamicDialogRef }, { token: i1$1.FormBuilder }, { token: CONVERSATION_AI_TOKEN }], target: i0.ɵɵFactoryTarget.Component }); }
1425
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", type: DCConversationUserChatSettingsComponent, isStandalone: true, selector: "dc-chat-settings-dialog", inputs: { showFeature: "showFeature" }, outputs: { onSettingsChange: "onSettingsChange" }, viewQueries: [{ propertyName: "tooltipRef", first: true, predicate: ["tooltipRef"], descendants: true }], ngImport: i0, template: "<div class=\"dialog-container\">\n <form [formGroup]=\"form\">\n <div class=\"settings-section\" *ngIf=\"showFeature.synthVoice\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.highlightWords\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.speed\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.realTime\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.realTime\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.superHearing\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.fixGrammar\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.autoTranslate\">\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 <div class=\"voice-selection\" *ngIf=\"showFeature.autoTranslate\">\n <span>Voz Preferencial:</span>\n <br />\n <p-radioButton value=\"random\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Aleatorio</label>\n\n <p-radioButton value=\"randomMan\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Hombre</label>\n\n <p-radioButton value=\"randomWoman\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Mujer</label>\n </div>\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: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { 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: "directive", type: i4.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: SliderModule }, { kind: "ngmodule", type: RadioButtonModule }, { kind: "component", type: i3.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: i7$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: i8.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"] }] }); }
2377
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ChatContainerComponent, deps: [{ token: CONVERSATION_AI_TOKEN }, { token: TOAST_ALERTS_TOKEN }, { token: DCConversationPromptBuilderService }, { token: i1$3.DialogService }, { token: ConversationService }, { token: EvaluationService }], target: i0.ɵɵFactoryTarget.Component }); }
2378
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", type: ChatContainerComponent, 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 }); }
1426
2379
  }
1427
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCConversationUserChatSettingsComponent, decorators: [{
2380
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ChatContainerComponent, decorators: [{
1428
2381
  type: Component,
1429
- args: [{ selector: 'dc-chat-settings-dialog', standalone: true, imports: [
1430
- CommonModule,
1431
- ReactiveFormsModule,
1432
- CheckboxModule,
1433
- SliderModule,
1434
- RadioButtonModule,
1435
- ButtonModule,
1436
- SpeedDescPipe,
1437
- RatingModule,
1438
- TableModule,
1439
- BadgeModule,
1440
- SkeletonModule,
1441
- TooltipModule,
1442
- ProviderSelectorComponent,
1443
- ], template: "<div class=\"dialog-container\">\n <form [formGroup]=\"form\">\n <div class=\"settings-section\" *ngIf=\"showFeature.synthVoice\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.highlightWords\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.speed\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.realTime\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.realTime\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.superHearing\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.fixGrammar\">\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 <div class=\"settings-section\" *ngIf=\"showFeature.autoTranslate\">\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 <div class=\"voice-selection\" *ngIf=\"showFeature.autoTranslate\">\n <span>Voz Preferencial:</span>\n <br />\n <p-radioButton value=\"random\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Aleatorio</label>\n\n <p-radioButton value=\"randomMan\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Hombre</label>\n\n <p-radioButton value=\"randomWoman\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Mujer</label>\n </div>\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"] }]
1444
- }], ctorParameters: () => [{ type: i1$2.DynamicDialogRef }, { type: i1$1.FormBuilder }, { type: AgentCardsAbstractService, decorators: [{
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\" [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"] }]
2383
+ }], ctorParameters: () => [{ type: AgentCardsAbstractService, decorators: [{
1445
2384
  type: Inject,
1446
2385
  args: [CONVERSATION_AI_TOKEN]
1447
- }] }], propDecorators: { showFeature: [{
2386
+ }] }, { type: i6$1.ToastAlertsAbstractService, decorators: [{
2387
+ type: Inject,
2388
+ args: [TOAST_ALERTS_TOKEN]
2389
+ }] }, { type: DCConversationPromptBuilderService }, { type: i1$3.DialogService }, { type: ConversationService }, { type: EvaluationService }], propDecorators: { chatUserSettings: [{
1448
2390
  type: Input
1449
- }], onSettingsChange: [{
2391
+ }], agentCard: [{
2392
+ type: Input
2393
+ }], evaluatorAgentCard: [{
2394
+ type: Input
2395
+ }], parseDict: [{
2396
+ type: Input
2397
+ }], sendMessage: [{
1450
2398
  type: Output
1451
- }], tooltipRef: [{
1452
- type: ViewChild,
1453
- args: ['tooltipRef']
1454
2399
  }] } });
1455
2400
 
1456
- function extractJsonFromResponse(content) {
1457
- const jsonMatch = content.match(/\{[\s\S]*?\}/); // Match everything between first { and }
1458
- if (!jsonMatch)
1459
- return null;
1460
- try {
1461
- return JSON.parse(jsonMatch[0]);
1462
- }
1463
- catch (error) {
1464
- console.error('Error parsing JSON:', error);
1465
- return null;
1466
- }
1467
- }
1468
-
1469
- const EvalResultStringDefinition = `
1470
- interface EvalResult {
1471
- score: number; // Score of the user's response 0 to 3
1472
- feedback: string; // Feedback of the user's understanding of the conversation
1473
- }`;
1474
- const DefaultEvaluatorAgentCard = {
1475
- task: 'Evaluate the user understanding of the lesson',
1476
- messages: [],
1477
- expectedResponseType: EvalResultStringDefinition,
1478
- model: { id: 'gpt-4o-mini', provider: 'openai' },
1479
- sources: [],
1480
- };
1481
2401
  class DCChatComponent {
1482
2402
  constructor(agentCardService, toastService, conversationBuilder, dialogService, cdr) {
1483
2403
  this.agentCardService = agentCardService;
@@ -1488,40 +2408,19 @@ class DCChatComponent {
1488
2408
  this.sendMessage = new EventEmitter();
1489
2409
  this.micSettings = { useWhisper: true, lang: 'en' };
1490
2410
  this.messages = [];
1491
- this.imageUser = `data:image/svg+xml;base64,${btoa(decodeURIComponent(ICONS.user))}`;
1492
- this.thinkingImg = `data:image/svg+xml;base64,${btoa(decodeURIComponent(ICONS.gear))}`;
2411
+ this.imageUser = `assets/default/user.svg`;
2412
+ this.thinkingImg = `assets/default/thinking.svg`;
2413
+ this.aiIcon = `assets/default/ai.svg`;
1493
2414
  this.isDestroyed = false;
1494
2415
  this.score = 10;
1495
- // @ViewChild('textSelectedDiv') textSelectedDiv!: ElementRef;
1496
- // get viewChild to the app-mic
1497
- // @ViewChild(MicComponent) micComponent: MicComponent;
2416
+ this.isAdmin = true;
2417
+ this.isInfoVisible = false;
1498
2418
  this.isChatSettingsVisible = false;
1499
- this.chatInputControl = new FormControl();
2419
+ // chatInputControl moved to ChatFooterComponent
1500
2420
  this.isAIThinking = false;
1501
2421
  this.isUserTalking = false;
1502
- this.aiIcon = `data:image/svg+xml;base64,${btoa(decodeURIComponent(ICONS.ai))}`;
1503
2422
  this.user = null; // TODO: remove user.
1504
2423
  this.isGettingTranscription = false;
1505
- // public fakeConversation() {
1506
- // const currentConversation = this.messages.filter((message) => [ChatRole.User, ChatRole.Assistant].includes(message.role));
1507
- // const index = currentConversation.length / 2 - 1;
1508
- // const message = { content: this.chatboxService.fakeConversations[index], role: ChatRole.Assistant };
1509
- // this.isAIThinking = true;
1510
- // setTimeout(() => {
1511
- // this.messages.push(message);
1512
- // // this.speechTextAndAttachAudio(message);
1513
- // }, 700);
1514
- // return;
1515
- // }
1516
- // public updateSettings() {
1517
- // // it is updated by subscription when user is updated
1518
- // // this.conversationSettings = this.userService.getUserSnapshot().settings.conversation;
1519
- // }
1520
- // private async getConversationEvaluation() {
1521
- // // Obtiene solo los mensajes de la historia.
1522
- // const messagesFiltered = this.messages.filter((message) => [ChatRole.User, ChatRole.Assistant].includes(message.role));
1523
- // const scenario_challenge = this.chatboxService.item['card']['scenario'];
1524
- this.isInfoVisible = false;
1525
2424
  }
1526
2425
  async ngOnInit() {
1527
2426
  if (!this.agentCard?.conversationSettings) {
@@ -1543,9 +2442,6 @@ class DCChatComponent {
1543
2442
  const firstAssistantMsn = this.messages.find((message) => message.role == ChatRole.Assistant);
1544
2443
  if (firstAssistantMsn) {
1545
2444
  if (this.chatUserSettings?.autoTranslate) {
1546
- // this.aiService.translate(firstAssistantMsn.content).then((res: any) => {
1547
- // firstAssistantMsn.translation = res?.translation;
1548
- // });
1549
2445
  }
1550
2446
  this.buildChatMessage(firstAssistantMsn, true);
1551
2447
  }
@@ -1553,12 +2449,6 @@ class DCChatComponent {
1553
2449
  this.sendCurrentConversation();
1554
2450
  }
1555
2451
  }
1556
- // onSendMessage(): void {
1557
- // if (this.userMessage?.trim()) {
1558
- // this.sendMessage.emit(this.userMessage);
1559
- // this.userMessage = '';
1560
- // }
1561
- // }
1562
2452
  buildChatMessage(message, mutate = false) {
1563
2453
  // De la respuesta del backend configura el mensaje para ser rendiriado y procesado en la interfaz
1564
2454
  if (mutate) {
@@ -1579,7 +2469,7 @@ class DCChatComponent {
1579
2469
  }
1580
2470
  return message;
1581
2471
  }
1582
- settingsClick() {
2472
+ changeUserChatSettings() {
1583
2473
  this.dialogService
1584
2474
  .open(DCConversationUserChatSettingsComponent, {
1585
2475
  width: '90vw',
@@ -1612,28 +2502,12 @@ class DCChatComponent {
1612
2502
  if (this.isAIThinking) {
1613
2503
  return;
1614
2504
  }
1615
- if (!message || !message.content) {
1616
- const text = this.chatInputControl.value;
1617
- if (!text) {
1618
- return;
1619
- }
1620
- message = { content: text, role: ChatRole.User };
2505
+ if (!message) {
2506
+ return;
1621
2507
  }
1622
2508
  this.messages.push(message); // This activates the render of the message
1623
- // ESTO debe ser una especie de plugin.
1624
- // if (this.conversationSettings?.fixGrammar && this.chatboxService.settings.conversationType == ConversationType.Scenario) {
1625
- // // call api to improve the text
1626
- // this.aiService.callInstruction(message.content).then((res) => {
1627
- // this.messages.push({ content: res.text, role: ChatRole.AssistantHelper });
1628
- // });
1629
- // }
1630
- this.chatInputControl.setValue('');
1631
2509
  this.setAIthinking();
1632
2510
  try {
1633
- // if (this.chatboxService.fakeConversations.length > 0) {
1634
- // this.fakeConversation();
1635
- // return;
1636
- // }
1637
2511
  if (message.audioUrl && this.chatUserSettings.repeatRecording) {
1638
2512
  // Esto significa que el usuario grabo un audio y tengo que esperar a que termine para seguir con la conversación
1639
2513
  await new Promise((resolve) => setTimeout(resolve, 500));
@@ -1681,20 +2555,12 @@ class DCChatComponent {
1681
2555
  }
1682
2556
  console.log('newMessage', response);
1683
2557
  const newMessage = this.buildChatMessage(response);
1684
- // if (this.conversationSettings?.autoTranslate) {
1685
- // this.aiService.translate(newMessage.content).then((res: any) => {
1686
- // newMessage.translation = res?.translation;
1687
- // });
1688
- // }
2558
+ // TODO: think on how to include this as pluggin. this.conversationSettings?.autoTranslate
1689
2559
  this.messages.push(newMessage); // This point ChatMessage will be rendered then ngOnInit will handle everything.
1690
2560
  // Aqui se debe evaluar si el mensaje es un evaluador, si es así, se debe llamar a la función de evaluación.
1691
2561
  this.evaluateConversation();
1692
2562
  this.isAIThinking = false;
1693
2563
  this.cdr.detectChanges();
1694
- // if (this.scenarioType == ScenarioType.Challenge) {
1695
- // console.log('challenge');
1696
- // this.getConversationEvaluation();
1697
- // }
1698
2564
  if (this.chatUserSettings.realTime) {
1699
2565
  if (this.isDestroyed)
1700
2566
  return;
@@ -1729,7 +2595,8 @@ class DCChatComponent {
1729
2595
  }
1730
2596
  }
1731
2597
  setInputText(text) {
1732
- this.chatInputControl.setValue(text);
2598
+ // Method now receives text from the chat-footer component
2599
+ // No need to set the value as it's handled in the child component
1733
2600
  }
1734
2601
  async micFinished(eventBlob) {
1735
2602
  if (eventBlob instanceof Blob) {
@@ -1759,25 +2626,7 @@ class DCChatComponent {
1759
2626
  // // when you click on profile this is activated
1760
2627
  playMessage(message) {
1761
2628
  console.log('playMessage', message);
1762
- // if (message.audioUrl) {
1763
- // this.audioService.playAudio(message.audioUrl);
1764
- // } else {
1765
- // this.speechService.speach(message.content);
1766
- // }
1767
2629
  }
1768
- // public restartConversation() {
1769
- // this.messages = [];
1770
- // this.messages.length;
1771
- // }
1772
- // public someTextSelected(event) {
1773
- // console.log('someTextSelected', event);
1774
- // this.wordMenuService.showMenu(event.position, event.text);
1775
- // }
1776
- // public userTalking() {
1777
- // if (this.conversationSettings.superHearing) {
1778
- // this.isUserTalking = true;
1779
- // }
1780
- // }
1781
2630
  subsItalicsByTag(text, voiceId = 'it-IT-Neural2-A', tagName = 'voice') {
1782
2631
  const regex = /\*(.*?)\*/g;
1783
2632
  return text.replace(regex, (match, p1) => `<${tagName} name="${voiceId}">${p1}</${tagName}>`);
@@ -1803,24 +2652,9 @@ class DCChatComponent {
1803
2652
  console.log('chatUserSettings', this.chatUserSettings);
1804
2653
  this.isInfoVisible = true;
1805
2654
  }
1806
- async changeConversationCard() {
1807
- const response = prompt('¿Qué conversación quieres usar? Escribe el titulo ejemplo: word_reflection_level_1_base_es');
1808
- if (response) {
1809
- const filters = {
1810
- filters: {
1811
- title: { $regex: response },
1812
- },
1813
- };
1814
- console.log('filters', filters);
1815
- const conversationCards = await this.agentCardService.filterConversationCards(filters);
1816
- console.log('conversationCards', conversationCards);
1817
- this.alternativeConversation = conversationCards.rows;
1818
- }
1819
- }
1820
2655
  async restartConversation(conversation = null) {
1821
2656
  if (conversation) {
1822
2657
  this.agentCard = conversation;
1823
- // this.conversationSettings = this.promptBuilder.buildConversationSettings(this.conversationCard, this.parseDict);
1824
2658
  }
1825
2659
  await this.ngOnInit();
1826
2660
  }
@@ -1867,19 +2701,29 @@ and give the response in next JSON format.
1867
2701
  }
1868
2702
  console.log('response', response);
1869
2703
  }
1870
- 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$2.DialogService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
1871
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.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 style=\"display: flex; justify-content: space-between\">\n <span class=\"pointer\" (click)=\"restartConversation()\">Reiniciar conversaci\u00F3n</span>\n\n @for (conversation of alternativeConversation; track conversation._id) {\n <span class=\"pointer\" (click)=\"restartConversation(conversation)\"> {{ conversation.title }} </span>\n }\n\n <div style=\"font-size: large\">\n <span class=\"pointer\" (click)=\"changeConversationCard()\"> \uD83D\uDD04 </span>\n\n <span class=\"pointer\" (click)=\"showInfo()\"> \u26A1\uFE0F </span>\n\n <span class=\"pointer\" (click)=\"settingsClick()\"> \u2699\uFE0F </span>\n </div>\n</div>\n<div class=\"chat-container\" (touchmove)=\"$event.stopPropagation()\">\n <ul class=\"chat\">\n <ng-container *ngFor=\"let msn of messages\">\n <li *ngIf=\"msn.role == 'assistant'\" class=\"message left\">\n <img (click)=\"playMessage(msn)\" class=\"logo fit-top-image\" [src]=\"agentCard?.assets?.image?.url ?? imageUser\" />\n\n <dc-chat-message [chatUserSettings]=\"chatUserSettings\" appTextSelectable [chatMessage]=\"msn\"></dc-chat-message>\n </li>\n\n <li *ngIf=\"msn.role == 'assistantHelper'\" class=\"message-helper right\">\n <dc-chat-message [chatUserSettings]=\"chatUserSettings\" appTextSelectable [chatMessage]=\"msn\"></dc-chat-message>\n </li>\n\n <li *ngIf=\"msn.role == 'user'\" class=\"message right\">\n <img (click)=\"playMessage(msn)\" class=\"logo\" [src]=\"user?.urlPicture || imageUser\" alt=\"\" style=\"object-fit: cover; object-position: top\" />\n <dc-chat-message appTextSelectable [chatMessage]=\"msn\"></dc-chat-message>\n </li>\n </ng-container>\n\n @if (isAIThinking) {\n <li class=\"message left\">\n <img class=\"logo fit-top-image\" [src]=\"agentCard?.assets?.image?.url ?? aiIcon\" />\n\n <span style=\"font-size: 1.5em; position: absolute; left: 20px; bottom: -5px\">\uD83E\uDD14</span>\n\n <div class=\"flex\">\n <!-- <p-skeleton shape=\"circle\" size=\"4rem\" styleClass=\"mr-2\" /> -->\n <div class=\"self-center\" style=\"flex: 1\">\n <p-skeleton width=\"100%\" styleClass=\"mb-2\" />\n <p-skeleton width=\"75%\" />\n </div>\n </div>\n </li>\n }\n\n <li *ngIf=\"isUserTalking || isGettingTranscription\" class=\"message right\">\n <img class=\"logo fit-top-image\" [src]=\"user?.urlPicture || imageUser\" alt=\"\" />\n @if (isUserTalking) {\n <p> ... </p>\n } @if (isGettingTranscription) {\n <i class=\"pi pi-spin pi-spinner-dotted\"></i>\n }\n </li>\n </ul>\n\n <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 <textarea class=\"text-fix white-background\" [formControl]=\"chatInputControl\" (keyup.enter)=\"sendUserMessage()\" rows=\"1\"></textarea>\n <button [disabled]=\"isAIThinking || !chatInputControl.value\" (click)=\"sendUserMessage()\" class=\"send-button\"> Enviar </button>\n </div>\n\n <div>\n <p-progressbar showValue=\"false\" [value]=\"score\" [style]=\"{ height: '6px' }\" />\n </div>\n </div>\n</div>\n\n<p-dialog header=\"Informaci\u00F3n de la conversaci\u00F3n\" [(visible)]=\"isInfoVisible\" [modal]=\"true\">\n <p> <strong> Modelo: </strong> {{ this.chatUserSettings?.model?.provider }} - {{ this.chatUserSettings?.model?.modelName }} </p>\n\n <p>\n <strong> Tipo : </strong> {{ agentCard?.conversationSettings?.conversationType }} <strong> Text Engine: </strong>\n {{ agentCard?.conversationSettings?.textEngine }}\n </p>\n\n <p>\n <strong> Language: </strong> {{ agentCard?.lang }} <strong> TTS: </strong> {{ agentCard?.tts?.voice }} <strong> Secondary TTS: </strong>\n {{ agentCard?.tts?.secondaryVoice }}\n </p>\n\n <p> <strong> Parametros: </strong> {{ parseDict | json }} </p>\n <hr />\n <h5>Conversaci\u00F3n hasta ahora</h5>\n\n <div *ngFor=\"let message of conversationSettings.messages\">\n <p\n ><strong>{{ message.role }}: </strong> {{ message.content }}</p\n >\n </div>\n</p-dialog>\n", styles: [".top-5{margin-top:10px}.logo{width:30px;height:30px}.white-background{background-color:#fff}.selectable{z-index:1;position:fixed;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px);background:transparent;border-radius:20px;display:block;padding:3px}*{box-sizing:border-box}.background1{background:url(/assets/background/default-background.webp);background-size:cover;background-position:center center;display:flex;align-items:center;justify-content:center;flex-direction:column;min-height:0px;overflow:auto;max-height:500px}h2{text-shadow:1px 1px 2px rgb(0,0,0);color:#fff;letter-spacing:1px;text-transform:uppercase;text-align:center}.chat-container{border-radius:10px;overflow:hidden;padding:15px;position:relative;max-width:100%;height:100%;display:flex;flex-direction:column}.chat{display:flex;flex-direction:column;list-style-type:none;padding:0;margin:0;overflow-y:auto;flex:1}.message-helper{background-color:#ffffffe6;box-shadow:0 7px 5px #00000080;position:relative;margin-bottom:30px}.message-helper.right{align-self:flex-end;padding:4px 45px 4px 15px}.message{background-color:#f6f6f6e6;border-radius:50px;box-shadow:7px 11px 14px #00000080;position:relative;margin-bottom:30px}.message .logo{position:absolute;top:50%;transform:translateY(-50%);border-radius:50%}.message .fit-top-image{object-fit:cover;object-position:top}.message.left{padding:10px 15px 10px 45px}.message.left .logo{left:5px}.message.right{align-self:flex-end;padding:10px 45px 10px 15px}.message.right .logo{right:5px}.input-container{margin-top:auto;display:flex;gap:10px;padding-top:15px;background:inherit}.input-container textarea{flex:1;padding:8px;border-radius:20px;border:1px solid #ccc;resize:none}.input-container .send-button{padding:8px 15px;border-radius:20px;border:none;background:#007bff;color:#fff;cursor:pointer}.input-container .send-button:disabled{background:#ccc;cursor:not-allowed}::ng-deep .cdk-overlay-container{z-index:1400}::ng-deep .cdk-global-overlay-wrapper{z-index:1400}::ng-deep .dialog-backdrop{background:#00000080;z-index:1299}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i2.JsonPipe, name: "json" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.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$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "component", type: ChatMessageComponent, selector: "dc-chat-message", inputs: ["chatMessage", "chatUserSettings"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i6.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "style", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: DCMicComponent, selector: "dc-mic", inputs: ["isDone", "useWhisper", "targetOrBase", "micSettings"], outputs: ["onInterpretedText", "onFinishedRecognition", "onFinished"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i6$1.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: i7$2.ProgressBar, selector: "p-progressBar, p-progressbar, p-progress-bar", inputs: ["value", "showValue", "styleClass", "valueStyleClass", "style", "unit", "mode", "color"] }] }); }
2704
+ 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$3.DialogService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
2705
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.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: "<dc-chat-header\n [isAdmin]=\"isAdmin\"\n [alternativeConversation]=\"alternativeConversation\"\n (restartConversationEvent)=\"restartConversation($event || null)\"\n (showInfoEvent)=\"showInfo()\"\n (settingsClickEvent)=\"changeUserChatSettings()\">\n</dc-chat-header>\n\n<main class=\"chat-container\" (touchmove)=\"$event.stopPropagation()\">\n <div class=\"chat\" aria-live=\"polite\">\n @for (message of messages; track message) { @switch (message.role) { @case ('assistant') {\n <div class=\"message left\">\n <img class=\"logo fit-top-image\" [src]=\"agentCard?.assets?.image?.url ?? imageUser\" (click)=\"playMessage(message)\" />\n <dc-chat-message [chatUserSettings]=\"chatUserSettings\" [chatMessage]=\"message\" appTextSelectable> </dc-chat-message>\n </div>\n } @case ('assistantHelper') {\n <div class=\"message-helper right\">\n <dc-chat-message [chatUserSettings]=\"chatUserSettings\" [chatMessage]=\"message\" appTextSelectable> </dc-chat-message>\n </div>\n } @case ('user') {\n <div class=\"message right\">\n <img class=\"logo fit-top-image\" [src]=\"user?.urlPicture || 'assets/defaults/avatar.jpg'\" [alt]=\"'User avatar'\" (click)=\"playMessage(message)\" />\n <dc-chat-message [chatMessage]=\"message\" appTextSelectable> </dc-chat-message>\n </div>\n } } }\n\n <!-- AI thinking indicator -->\n @if (isAIThinking) {\n <div class=\"message message--assistant message--thinking\">\n <span class=\"thinking-emoji\" aria-hidden=\"true\">\uD83E\uDD14</span>\n <div class=\"thinking-indicator\">\n <div class=\"skeleton-container\">\n <p-skeleton width=\"100%\" />\n <br />\n <p-skeleton width=\"75%\" />\n </div>\n </div>\n </div>\n }\n\n <!-- User typing or transcribing indicator -->\n @if (isUserTalking || isGettingTranscription) {\n <div class=\"message message--user message--activity\">\n <img class=\"avatar avatar--user\" [src]=\"user?.urlPicture || imageUser\" [alt]=\"'User avatar'\" />\n @if (isUserTalking) {\n <p class=\"typing-indicator\" aria-label=\"User is speaking\">...</p>\n } @if (isGettingTranscription) {\n <i class=\"pi pi-spin pi-spinner-dotted\" aria-label=\"Transcribing speech\"></i>\n }\n </div>\n }\n </div>\n</main>\n\n<dc-chat-footer\n [isAIThinking]=\"isAIThinking\"\n [score]=\"score\"\n [micSettings]=\"micSettings\"\n (sendMessage)=\"sendUserMessage($event)\"\n (textInputChanged)=\"setInputText($event)\"\n (micFinishedEvent)=\"micFinished($event)\">\n</dc-chat-footer>\n\n<!-- Information dialog -->\n<p-dialog\n header=\"Informaci\u00F3n de la conversaci\u00F3n\"\n [(visible)]=\"isInfoVisible\"\n [modal]=\"true\"\n [responsive]=\"true\"\n styleClass=\"conversation-info-dialog\"\n aria-labelledby=\"conversation-info-title\">\n <div class=\"dialog-content\">\n <section class=\"model-info\">\n <h3 id=\"conversation-info-title\" class=\"sr-only\">Informaci\u00F3n de la conversaci\u00F3n</h3>\n <dl class=\"info-list\">\n <dt>Modelo:</dt>\n <dd>{{ chatUserSettings?.model?.provider }} - {{ chatUserSettings?.model?.modelName }}</dd>\n\n <dt>Tipo:</dt>\n <dd>{{ agentCard?.conversationSettings?.conversationType }}</dd>\n\n <dt>Text Engine:</dt>\n <dd>{{ agentCard?.conversationSettings?.textEngine }}</dd>\n\n <dt>Language:</dt>\n <dd>{{ agentCard?.lang }}</dd>\n\n <dt>TTS:</dt>\n <dd>{{ agentCard?.conversationSettings?.tts?.voice }}</dd>\n\n <dt>Secondary TTS:</dt>\n <dd>{{ agentCard?.conversationSettings?.tts?.secondaryVoice }}</dd>\n </dl>\n\n <div class=\"parameters\">\n <h4>Par\u00E1metros:</h4>\n <pre>{{ parseDict | json }}</pre>\n </div>\n </section>\n\n <section class=\"conversation-history\">\n <h4>Conversaci\u00F3n hasta ahora</h4>\n <div class=\"message-history-list\">\n @for (message of conversationSettings.messages; track message) {\n <div class=\"message-history-item\">\n <span class=\"message-role\">{{ message.role }}:</span>\n <span class=\"message-content\">{{ message.content }}</span>\n </div>\n }\n </div>\n </section>\n </div>\n</p-dialog>\n", styles: [":host{display:flex;flex-direction:column;height:100%}*{box-sizing:border-box}.chat-container{border-radius:10px;overflow:hidden;padding:15px;position:relative;max-width:100%;height:100%;display:flex;flex-direction:column}.chat{display:flex;flex-direction:column;list-style-type:none;padding:0;margin:0;overflow-y:auto;flex:1}.logo{width:30px;height:30px}.logo.fit-top-image{object-fit:cover;object-position:top;border-radius:50%}.message{background-color:#f6f6f6f2;border-radius:25px;box-shadow:0 5px 15px #0000001a;position:relative;margin-bottom:20px;transition:all .2s ease}.message .logo{position:absolute;top:50%;transform:translateY(-50%);border-radius:50%;cursor:pointer}.message .logo:hover{transform:translateY(-50%) scale(1.05)}.message.left{padding:12px 15px 12px 45px;margin-right:20%}.message.left .logo{left:5px}.message.right{align-self:flex-end;padding:12px 45px 12px 15px;margin-left:20%;background-color:#e6f0fff2}.message.right .logo{right:5px}.message--thinking,.message--activity{opacity:.7}.message-helper{background-color:#ffffffe6;box-shadow:0 3px 10px #0000001a;position:relative;margin-bottom:20px;border-radius:20px}.message-helper.right{align-self:flex-end;padding:8px 15px;margin-left:20%}.thinking-indicator{padding:5px}.thinking-indicator .skeleton-container{width:200px}::ng-deep .cdk-overlay-container,::ng-deep .cdk-global-overlay-wrapper{z-index:1400}::ng-deep .dialog-backdrop{background:#00000080;z-index:1299}::ng-deep .conversation-info-dialog .dialog-content{max-height:70vh;overflow-y:auto}::ng-deep .conversation-info-dialog .info-list{display:grid;grid-template-columns:auto 1fr;gap:8px;margin-bottom:20px}::ng-deep .conversation-info-dialog .info-list dt{font-weight:700}::ng-deep .conversation-info-dialog .message-history-list{max-height:300px;overflow-y:auto;border:1px solid #eee;border-radius:5px;padding:10px}::ng-deep .conversation-info-dialog .message-history-list .message-history-item{padding:5px 0;border-bottom:1px solid #f0f0f0}::ng-deep .conversation-info-dialog .message-history-list .message-history-item:last-child{border-bottom:none}::ng-deep .conversation-info-dialog .message-history-list .message-history-item .message-role{font-weight:700;margin-right:5px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$2.JsonPipe, name: "json" }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: ChatMessageComponent, selector: "dc-chat-message", inputs: ["chatMessage", "chatUserSettings"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i4.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "style", "shape", "animation", "borderRadius", "size", "width", "height"] }, { 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: 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"] }] }); }
1872
2706
  }
1873
2707
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCChatComponent, decorators: [{
1874
2708
  type: Component,
1875
- args: [{ selector: 'dc-chat', standalone: true, imports: [CommonModule, FormsModule, ChatMessageComponent, ReactiveFormsModule, SkeletonModule, DCMicComponent, DialogModule, ProgressBarModule], providers: [DialogService], template: "<div style=\"display: flex; justify-content: space-between\">\n <span class=\"pointer\" (click)=\"restartConversation()\">Reiniciar conversaci\u00F3n</span>\n\n @for (conversation of alternativeConversation; track conversation._id) {\n <span class=\"pointer\" (click)=\"restartConversation(conversation)\"> {{ conversation.title }} </span>\n }\n\n <div style=\"font-size: large\">\n <span class=\"pointer\" (click)=\"changeConversationCard()\"> \uD83D\uDD04 </span>\n\n <span class=\"pointer\" (click)=\"showInfo()\"> \u26A1\uFE0F </span>\n\n <span class=\"pointer\" (click)=\"settingsClick()\"> \u2699\uFE0F </span>\n </div>\n</div>\n<div class=\"chat-container\" (touchmove)=\"$event.stopPropagation()\">\n <ul class=\"chat\">\n <ng-container *ngFor=\"let msn of messages\">\n <li *ngIf=\"msn.role == 'assistant'\" class=\"message left\">\n <img (click)=\"playMessage(msn)\" class=\"logo fit-top-image\" [src]=\"agentCard?.assets?.image?.url ?? imageUser\" />\n\n <dc-chat-message [chatUserSettings]=\"chatUserSettings\" appTextSelectable [chatMessage]=\"msn\"></dc-chat-message>\n </li>\n\n <li *ngIf=\"msn.role == 'assistantHelper'\" class=\"message-helper right\">\n <dc-chat-message [chatUserSettings]=\"chatUserSettings\" appTextSelectable [chatMessage]=\"msn\"></dc-chat-message>\n </li>\n\n <li *ngIf=\"msn.role == 'user'\" class=\"message right\">\n <img (click)=\"playMessage(msn)\" class=\"logo\" [src]=\"user?.urlPicture || imageUser\" alt=\"\" style=\"object-fit: cover; object-position: top\" />\n <dc-chat-message appTextSelectable [chatMessage]=\"msn\"></dc-chat-message>\n </li>\n </ng-container>\n\n @if (isAIThinking) {\n <li class=\"message left\">\n <img class=\"logo fit-top-image\" [src]=\"agentCard?.assets?.image?.url ?? aiIcon\" />\n\n <span style=\"font-size: 1.5em; position: absolute; left: 20px; bottom: -5px\">\uD83E\uDD14</span>\n\n <div class=\"flex\">\n <!-- <p-skeleton shape=\"circle\" size=\"4rem\" styleClass=\"mr-2\" /> -->\n <div class=\"self-center\" style=\"flex: 1\">\n <p-skeleton width=\"100%\" styleClass=\"mb-2\" />\n <p-skeleton width=\"75%\" />\n </div>\n </div>\n </li>\n }\n\n <li *ngIf=\"isUserTalking || isGettingTranscription\" class=\"message right\">\n <img class=\"logo fit-top-image\" [src]=\"user?.urlPicture || imageUser\" alt=\"\" />\n @if (isUserTalking) {\n <p> ... </p>\n } @if (isGettingTranscription) {\n <i class=\"pi pi-spin pi-spinner-dotted\"></i>\n }\n </li>\n </ul>\n\n <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 <textarea class=\"text-fix white-background\" [formControl]=\"chatInputControl\" (keyup.enter)=\"sendUserMessage()\" rows=\"1\"></textarea>\n <button [disabled]=\"isAIThinking || !chatInputControl.value\" (click)=\"sendUserMessage()\" class=\"send-button\"> Enviar </button>\n </div>\n\n <div>\n <p-progressbar showValue=\"false\" [value]=\"score\" [style]=\"{ height: '6px' }\" />\n </div>\n </div>\n</div>\n\n<p-dialog header=\"Informaci\u00F3n de la conversaci\u00F3n\" [(visible)]=\"isInfoVisible\" [modal]=\"true\">\n <p> <strong> Modelo: </strong> {{ this.chatUserSettings?.model?.provider }} - {{ this.chatUserSettings?.model?.modelName }} </p>\n\n <p>\n <strong> Tipo : </strong> {{ agentCard?.conversationSettings?.conversationType }} <strong> Text Engine: </strong>\n {{ agentCard?.conversationSettings?.textEngine }}\n </p>\n\n <p>\n <strong> Language: </strong> {{ agentCard?.lang }} <strong> TTS: </strong> {{ agentCard?.tts?.voice }} <strong> Secondary TTS: </strong>\n {{ agentCard?.tts?.secondaryVoice }}\n </p>\n\n <p> <strong> Parametros: </strong> {{ parseDict | json }} </p>\n <hr />\n <h5>Conversaci\u00F3n hasta ahora</h5>\n\n <div *ngFor=\"let message of conversationSettings.messages\">\n <p\n ><strong>{{ message.role }}: </strong> {{ message.content }}</p\n >\n </div>\n</p-dialog>\n", styles: [".top-5{margin-top:10px}.logo{width:30px;height:30px}.white-background{background-color:#fff}.selectable{z-index:1;position:fixed;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px);background:transparent;border-radius:20px;display:block;padding:3px}*{box-sizing:border-box}.background1{background:url(/assets/background/default-background.webp);background-size:cover;background-position:center center;display:flex;align-items:center;justify-content:center;flex-direction:column;min-height:0px;overflow:auto;max-height:500px}h2{text-shadow:1px 1px 2px rgb(0,0,0);color:#fff;letter-spacing:1px;text-transform:uppercase;text-align:center}.chat-container{border-radius:10px;overflow:hidden;padding:15px;position:relative;max-width:100%;height:100%;display:flex;flex-direction:column}.chat{display:flex;flex-direction:column;list-style-type:none;padding:0;margin:0;overflow-y:auto;flex:1}.message-helper{background-color:#ffffffe6;box-shadow:0 7px 5px #00000080;position:relative;margin-bottom:30px}.message-helper.right{align-self:flex-end;padding:4px 45px 4px 15px}.message{background-color:#f6f6f6e6;border-radius:50px;box-shadow:7px 11px 14px #00000080;position:relative;margin-bottom:30px}.message .logo{position:absolute;top:50%;transform:translateY(-50%);border-radius:50%}.message .fit-top-image{object-fit:cover;object-position:top}.message.left{padding:10px 15px 10px 45px}.message.left .logo{left:5px}.message.right{align-self:flex-end;padding:10px 45px 10px 15px}.message.right .logo{right:5px}.input-container{margin-top:auto;display:flex;gap:10px;padding-top:15px;background:inherit}.input-container textarea{flex:1;padding:8px;border-radius:20px;border:1px solid #ccc;resize:none}.input-container .send-button{padding:8px 15px;border-radius:20px;border:none;background:#007bff;color:#fff;cursor:pointer}.input-container .send-button:disabled{background:#ccc;cursor:not-allowed}::ng-deep .cdk-overlay-container{z-index:1400}::ng-deep .cdk-global-overlay-wrapper{z-index:1400}::ng-deep .dialog-backdrop{background:#00000080;z-index:1299}\n"] }]
2709
+ args: [{ selector: 'dc-chat', standalone: true, imports: [
2710
+ CommonModule,
2711
+ FormsModule,
2712
+ ChatMessageComponent,
2713
+ ReactiveFormsModule,
2714
+ SkeletonModule,
2715
+ DialogModule,
2716
+ ProgressBarModule,
2717
+ ChatHeaderComponent,
2718
+ ChatFooterComponent,
2719
+ ], providers: [DialogService], template: "<dc-chat-header\n [isAdmin]=\"isAdmin\"\n [alternativeConversation]=\"alternativeConversation\"\n (restartConversationEvent)=\"restartConversation($event || null)\"\n (showInfoEvent)=\"showInfo()\"\n (settingsClickEvent)=\"changeUserChatSettings()\">\n</dc-chat-header>\n\n<main class=\"chat-container\" (touchmove)=\"$event.stopPropagation()\">\n <div class=\"chat\" aria-live=\"polite\">\n @for (message of messages; track message) { @switch (message.role) { @case ('assistant') {\n <div class=\"message left\">\n <img class=\"logo fit-top-image\" [src]=\"agentCard?.assets?.image?.url ?? imageUser\" (click)=\"playMessage(message)\" />\n <dc-chat-message [chatUserSettings]=\"chatUserSettings\" [chatMessage]=\"message\" appTextSelectable> </dc-chat-message>\n </div>\n } @case ('assistantHelper') {\n <div class=\"message-helper right\">\n <dc-chat-message [chatUserSettings]=\"chatUserSettings\" [chatMessage]=\"message\" appTextSelectable> </dc-chat-message>\n </div>\n } @case ('user') {\n <div class=\"message right\">\n <img class=\"logo fit-top-image\" [src]=\"user?.urlPicture || 'assets/defaults/avatar.jpg'\" [alt]=\"'User avatar'\" (click)=\"playMessage(message)\" />\n <dc-chat-message [chatMessage]=\"message\" appTextSelectable> </dc-chat-message>\n </div>\n } } }\n\n <!-- AI thinking indicator -->\n @if (isAIThinking) {\n <div class=\"message message--assistant message--thinking\">\n <span class=\"thinking-emoji\" aria-hidden=\"true\">\uD83E\uDD14</span>\n <div class=\"thinking-indicator\">\n <div class=\"skeleton-container\">\n <p-skeleton width=\"100%\" />\n <br />\n <p-skeleton width=\"75%\" />\n </div>\n </div>\n </div>\n }\n\n <!-- User typing or transcribing indicator -->\n @if (isUserTalking || isGettingTranscription) {\n <div class=\"message message--user message--activity\">\n <img class=\"avatar avatar--user\" [src]=\"user?.urlPicture || imageUser\" [alt]=\"'User avatar'\" />\n @if (isUserTalking) {\n <p class=\"typing-indicator\" aria-label=\"User is speaking\">...</p>\n } @if (isGettingTranscription) {\n <i class=\"pi pi-spin pi-spinner-dotted\" aria-label=\"Transcribing speech\"></i>\n }\n </div>\n }\n </div>\n</main>\n\n<dc-chat-footer\n [isAIThinking]=\"isAIThinking\"\n [score]=\"score\"\n [micSettings]=\"micSettings\"\n (sendMessage)=\"sendUserMessage($event)\"\n (textInputChanged)=\"setInputText($event)\"\n (micFinishedEvent)=\"micFinished($event)\">\n</dc-chat-footer>\n\n<!-- Information dialog -->\n<p-dialog\n header=\"Informaci\u00F3n de la conversaci\u00F3n\"\n [(visible)]=\"isInfoVisible\"\n [modal]=\"true\"\n [responsive]=\"true\"\n styleClass=\"conversation-info-dialog\"\n aria-labelledby=\"conversation-info-title\">\n <div class=\"dialog-content\">\n <section class=\"model-info\">\n <h3 id=\"conversation-info-title\" class=\"sr-only\">Informaci\u00F3n de la conversaci\u00F3n</h3>\n <dl class=\"info-list\">\n <dt>Modelo:</dt>\n <dd>{{ chatUserSettings?.model?.provider }} - {{ chatUserSettings?.model?.modelName }}</dd>\n\n <dt>Tipo:</dt>\n <dd>{{ agentCard?.conversationSettings?.conversationType }}</dd>\n\n <dt>Text Engine:</dt>\n <dd>{{ agentCard?.conversationSettings?.textEngine }}</dd>\n\n <dt>Language:</dt>\n <dd>{{ agentCard?.lang }}</dd>\n\n <dt>TTS:</dt>\n <dd>{{ agentCard?.conversationSettings?.tts?.voice }}</dd>\n\n <dt>Secondary TTS:</dt>\n <dd>{{ agentCard?.conversationSettings?.tts?.secondaryVoice }}</dd>\n </dl>\n\n <div class=\"parameters\">\n <h4>Par\u00E1metros:</h4>\n <pre>{{ parseDict | json }}</pre>\n </div>\n </section>\n\n <section class=\"conversation-history\">\n <h4>Conversaci\u00F3n hasta ahora</h4>\n <div class=\"message-history-list\">\n @for (message of conversationSettings.messages; track message) {\n <div class=\"message-history-item\">\n <span class=\"message-role\">{{ message.role }}:</span>\n <span class=\"message-content\">{{ message.content }}</span>\n </div>\n }\n </div>\n </section>\n </div>\n</p-dialog>\n", styles: [":host{display:flex;flex-direction:column;height:100%}*{box-sizing:border-box}.chat-container{border-radius:10px;overflow:hidden;padding:15px;position:relative;max-width:100%;height:100%;display:flex;flex-direction:column}.chat{display:flex;flex-direction:column;list-style-type:none;padding:0;margin:0;overflow-y:auto;flex:1}.logo{width:30px;height:30px}.logo.fit-top-image{object-fit:cover;object-position:top;border-radius:50%}.message{background-color:#f6f6f6f2;border-radius:25px;box-shadow:0 5px 15px #0000001a;position:relative;margin-bottom:20px;transition:all .2s ease}.message .logo{position:absolute;top:50%;transform:translateY(-50%);border-radius:50%;cursor:pointer}.message .logo:hover{transform:translateY(-50%) scale(1.05)}.message.left{padding:12px 15px 12px 45px;margin-right:20%}.message.left .logo{left:5px}.message.right{align-self:flex-end;padding:12px 45px 12px 15px;margin-left:20%;background-color:#e6f0fff2}.message.right .logo{right:5px}.message--thinking,.message--activity{opacity:.7}.message-helper{background-color:#ffffffe6;box-shadow:0 3px 10px #0000001a;position:relative;margin-bottom:20px;border-radius:20px}.message-helper.right{align-self:flex-end;padding:8px 15px;margin-left:20%}.thinking-indicator{padding:5px}.thinking-indicator .skeleton-container{width:200px}::ng-deep .cdk-overlay-container,::ng-deep .cdk-global-overlay-wrapper{z-index:1400}::ng-deep .dialog-backdrop{background:#00000080;z-index:1299}::ng-deep .conversation-info-dialog .dialog-content{max-height:70vh;overflow-y:auto}::ng-deep .conversation-info-dialog .info-list{display:grid;grid-template-columns:auto 1fr;gap:8px;margin-bottom:20px}::ng-deep .conversation-info-dialog .info-list dt{font-weight:700}::ng-deep .conversation-info-dialog .message-history-list{max-height:300px;overflow-y:auto;border:1px solid #eee;border-radius:5px;padding:10px}::ng-deep .conversation-info-dialog .message-history-list .message-history-item{padding:5px 0;border-bottom:1px solid #f0f0f0}::ng-deep .conversation-info-dialog .message-history-list .message-history-item:last-child{border-bottom:none}::ng-deep .conversation-info-dialog .message-history-list .message-history-item .message-role{font-weight:700;margin-right:5px}\n"] }]
1876
2720
  }], ctorParameters: () => [{ type: AgentCardsAbstractService, decorators: [{
1877
2721
  type: Inject,
1878
2722
  args: [CONVERSATION_AI_TOKEN]
1879
- }] }, { type: i6$2.ToastAlertsAbstractService, decorators: [{
2723
+ }] }, { type: i6$1.ToastAlertsAbstractService, decorators: [{
1880
2724
  type: Inject,
1881
2725
  args: [TOAST_ALERTS_TOKEN]
1882
- }] }, { type: DCConversationPromptBuilderService }, { type: i1$2.DialogService }, { type: i0.ChangeDetectorRef }], propDecorators: { chatUserSettings: [{
2726
+ }] }, { type: DCConversationPromptBuilderService }, { type: i1$3.DialogService }, { type: i0.ChangeDetectorRef }], propDecorators: { chatUserSettings: [{
1883
2727
  type: Input
1884
2728
  }], agentCard: [{
1885
2729
  type: Input
@@ -2030,7 +2874,7 @@ class PromptPreviewDialogComponent {
2030
2874
  close() {
2031
2875
  this.dialogRef.close();
2032
2876
  }
2033
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: PromptPreviewDialogComponent, deps: [{ token: i1$2.DynamicDialogConfig }, { token: i1$2.DynamicDialogRef }], target: i0.ɵɵFactoryTarget.Component }); }
2877
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: PromptPreviewDialogComponent, deps: [{ token: i1$3.DynamicDialogConfig }, { token: i1$3.DynamicDialogRef }], target: i0.ɵɵFactoryTarget.Component }); }
2034
2878
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", 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: CommonModule }, { kind: "ngmodule", type: DialogModule }] }); }
2035
2879
  }
2036
2880
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: PromptPreviewDialogComponent, decorators: [{
@@ -2041,7 +2885,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImpor
2041
2885
  imports: [CommonModule, DialogModule],
2042
2886
  template: ` <div class="prompt-preview-content" [innerHTML]="data.html"></div> `,
2043
2887
  }]
2044
- }], ctorParameters: () => [{ type: i1$2.DynamicDialogConfig }, { type: i1$2.DynamicDialogRef }] });
2888
+ }], ctorParameters: () => [{ type: i1$3.DynamicDialogConfig }, { type: i1$3.DynamicDialogRef }] });
2045
2889
 
2046
2890
  class TranslateDialogComponent {
2047
2891
  constructor(fb, dialogRef, data) {
@@ -2063,7 +2907,7 @@ class TranslateDialogComponent {
2063
2907
  onConfirm() {
2064
2908
  this.dialogRef.close(this.form.value.targetLang);
2065
2909
  }
2066
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: TranslateDialogComponent, deps: [{ token: i1$1.FormBuilder }, { token: i2$1.DialogRef }, { token: DIALOG_DATA }], target: i0.ɵɵFactoryTarget.Component }); }
2910
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: TranslateDialogComponent, deps: [{ token: i1.FormBuilder }, { token: i2$1.DialogRef }, { token: DIALOG_DATA }], target: i0.ɵɵFactoryTarget.Component }); }
2067
2911
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", type: TranslateDialogComponent, isStandalone: true, selector: "dc-translate-dialog", ngImport: i0, template: `
2068
2912
  <div class="translate-dialog">
2069
2913
  <h4>Tu idioma actual es: {{ data.currentLang }}</h4>
@@ -2081,7 +2925,7 @@ class TranslateDialogComponent {
2081
2925
  <button (click)="onConfirm()" [disabled]="!form.value.targetLang"> Translate </button>
2082
2926
  </div>
2083
2927
  </div>
2084
- `, 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: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }] }); }
2928
+ `, 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: CommonModule }, { kind: "directive", type: i1$2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { 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"] }] }); }
2085
2929
  }
2086
2930
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: TranslateDialogComponent, decorators: [{
2087
2931
  type: Component,
@@ -2103,11 +2947,70 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImpor
2103
2947
  </div>
2104
2948
  </div>
2105
2949
  `, 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"] }]
2106
- }], ctorParameters: () => [{ type: i1$1.FormBuilder }, { type: i2$1.DialogRef }, { type: undefined, decorators: [{
2950
+ }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2$1.DialogRef }, { type: undefined, decorators: [{
2107
2951
  type: Inject,
2108
2952
  args: [DIALOG_DATA]
2109
2953
  }] }] });
2110
2954
 
2955
+ function markdownToHTML(markdownText) {
2956
+ // Convert italics-bold (***text***)
2957
+ let htmlText = markdownText.replace(/\*\*\*(.+?)\*\*\*/g, '<em><strong>$1</strong></em>');
2958
+ // Convert bold (**text**)
2959
+ htmlText = htmlText.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
2960
+ // Convert italics (*text*)
2961
+ htmlText = htmlText.replace(/\*(.+?)\*/g, '<em>$1</em>');
2962
+ // Convert text enclosed by double quotes ("text")
2963
+ // htmlText = htmlText.replace(/"(.+?)"/g, '<cite>$1</cite>');
2964
+ return htmlText;
2965
+ }
2966
+ class SimpleMdToHtmlPipe {
2967
+ transform(text) {
2968
+ return markdownToHTML(text);
2969
+ }
2970
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: SimpleMdToHtmlPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
2971
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.1.1", ngImport: i0, type: SimpleMdToHtmlPipe, isStandalone: true, name: "simpleMdToHtml" }); }
2972
+ }
2973
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: SimpleMdToHtmlPipe, decorators: [{
2974
+ type: Pipe,
2975
+ args: [{
2976
+ name: 'simpleMdToHtml',
2977
+ standalone: true,
2978
+ }]
2979
+ }] });
2980
+ class StartDivToHtmlPipe {
2981
+ // solo create espacio entre lineas
2982
+ transform(text) {
2983
+ text = text.replace(/<start>/gi, '<hr>');
2984
+ text = text.replace(/\n/g, '<br>');
2985
+ return text;
2986
+ }
2987
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: StartDivToHtmlPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
2988
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.1.1", ngImport: i0, type: StartDivToHtmlPipe, isStandalone: true, name: "startDividerToHtml" }); }
2989
+ }
2990
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: StartDivToHtmlPipe, decorators: [{
2991
+ type: Pipe,
2992
+ args: [{
2993
+ name: 'startDividerToHtml',
2994
+ standalone: true,
2995
+ }]
2996
+ }] });
2997
+ class MdToHtmlArrayPipe {
2998
+ // devuelve un array de objetos con el contenido y el tag
2999
+ transform(text) {
3000
+ const htmlArray = convertToHTML(text);
3001
+ return htmlArray.map((val) => val.content).join('');
3002
+ }
3003
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: MdToHtmlArrayPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
3004
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.1.1", ngImport: i0, type: MdToHtmlArrayPipe, isStandalone: true, name: "mdToHtmlArray" }); }
3005
+ }
3006
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: MdToHtmlArrayPipe, decorators: [{
3007
+ type: Pipe,
3008
+ args: [{
3009
+ name: 'mdToHtmlArray',
3010
+ standalone: true,
3011
+ }]
3012
+ }] });
3013
+
2111
3014
  class AccountPlatformForm {
2112
3015
  constructor(route, fb, router, toastService) {
2113
3016
  this.route = route;
@@ -2168,13 +3071,13 @@ class AccountPlatformForm {
2168
3071
  this.toastService.warn(infoToast);
2169
3072
  }
2170
3073
  }
2171
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: AccountPlatformForm, deps: [{ token: i1$3.ActivatedRoute }, { token: i1$1.FormBuilder }, { token: i1$3.Router }, { token: TOAST_ALERTS_TOKEN }], target: i0.ɵɵFactoryTarget.Component }); }
2172
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", 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$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.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$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i3$1.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "ngmodule", type: DropdownModule }, { kind: "component", type: i4$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: i5$1.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }, { kind: "ngmodule", type: ChipModule }, { kind: "ngmodule", type: TooltipModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3074
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: AccountPlatformForm, deps: [{ token: i1$4.ActivatedRoute }, { token: i1.FormBuilder }, { token: i1$4.Router }, { token: TOAST_ALERTS_TOKEN }], target: i0.ɵɵFactoryTarget.Component }); }
3075
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", 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: i3$2.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "ngmodule", type: DropdownModule }, { kind: "component", type: i4$3.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: i5$1.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }, { kind: "ngmodule", type: ChipModule }, { kind: "ngmodule", type: TooltipModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2173
3076
  }
2174
3077
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: AccountPlatformForm, decorators: [{
2175
3078
  type: Component,
2176
3079
  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"] }]
2177
- }], ctorParameters: () => [{ type: i1$3.ActivatedRoute }, { type: i1$1.FormBuilder }, { type: i1$3.Router }, { type: i6$2.ToastAlertsAbstractService, decorators: [{
3080
+ }], ctorParameters: () => [{ type: i1$4.ActivatedRoute }, { type: i1.FormBuilder }, { type: i1$4.Router }, { type: i6$1.ToastAlertsAbstractService, decorators: [{
2178
3081
  type: Inject,
2179
3082
  args: [TOAST_ALERTS_TOKEN]
2180
3083
  }] }], propDecorators: { formArray: [{
@@ -2208,6 +3111,7 @@ class DCAgentCardFormComponent {
2208
3111
  this.accountsOptions = Object.values(EAccountsPlatform);
2209
3112
  this.languageOptions = Object.entries(LangCodeDescriptionEs).map(([value, label]) => ({ value, label }));
2210
3113
  this.agentCardId = this.activatedRoute.snapshot.paramMap.get('id');
3114
+ this.audioSpeedOptions = Object.values(AudioSpeed$1).map((speed) => ({ label: speed, value: speed }));
2211
3115
  this.storageSettings = this.getSettings();
2212
3116
  this.bannerImgSettings = {
2213
3117
  path: 'conversation-cards/' + this.agentCardId,
@@ -2252,9 +3156,9 @@ class DCAgentCardFormComponent {
2252
3156
  textEngine: [TextEngines.SimpleText],
2253
3157
  conversationType: [ConversationType.General],
2254
3158
  autoStart: [true],
3159
+ tts: this.fb.group({ voice: [''], secondaryVoice: [''], speed: ['1.0'], speedRate: [1.0] }),
2255
3160
  }),
2256
3161
  lang: [''],
2257
- tts: this.fb.group({ voice: [''], secondaryVoice: [''], speed: ['1.0'], speedRate: [1.0] }),
2258
3162
  metaApp: this.fb.group({ isPublished: [false], isPublic: [false], authorId: [''], authorEmail: [''], takenCount: [0] }),
2259
3163
  model: this.fb.group({ id: [''], modelName: [''], provider: [''] }),
2260
3164
  accounts: this.fb.array([]),
@@ -2597,12 +3501,12 @@ class DCAgentCardFormComponent {
2597
3501
  this.toastService.success({ title: 'Sticker removed', subtitle: 'Sticker was removed' });
2598
3502
  this.cdr.detectChanges();
2599
3503
  }
2600
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCAgentCardFormComponent, deps: [{ token: i1$1.FormBuilder }, { token: i2$2.MultiImagesStorageService }, { token: CONVERSATION_AI_TOKEN }, { token: i0.ChangeDetectorRef }, { token: i1$3.Router }, { token: i1$3.ActivatedRoute }, { token: i1$2.DialogService }, { token: DCConversationPromptBuilderService }, { token: TOAST_ALERTS_TOKEN, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
2601
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", type: DCAgentCardFormComponent, isStandalone: true, selector: "dc-conversation-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 </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 <input pInputText id=\"speed\" type=\"text\" formControlName=\"speed\" />\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\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)\">&#x2716;</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: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.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$1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$1.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$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "component", type: i7$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: i8$1.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.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"] }] }); }
3504
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCAgentCardFormComponent, deps: [{ token: i1.FormBuilder }, { token: i2$2.MultiImagesStorageService }, { token: CONVERSATION_AI_TOKEN }, { token: i0.ChangeDetectorRef }, { token: i1$4.Router }, { token: i1$4.ActivatedRoute }, { token: i1$3.DialogService }, { token: DCConversationPromptBuilderService }, { token: TOAST_ALERTS_TOKEN, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
3505
+ 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)\">&#x2716;</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$2.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"] }] }); }
2602
3506
  }
2603
3507
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCAgentCardFormComponent, decorators: [{
2604
3508
  type: Component,
2605
- args: [{ selector: 'dc-conversation-form', standalone: true, providers: [DialogService], imports: [
3509
+ args: [{ selector: 'dc-agent-form', standalone: true, providers: [DialogService], imports: [
2606
3510
  CommonModule,
2607
3511
  ReactiveFormsModule,
2608
3512
  CropperComponentModal,
@@ -2623,11 +3527,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImpor
2623
3527
  PopoverModule,
2624
3528
  ProviderSelectorComponent,
2625
3529
  AccountPlatformForm,
2626
- ], 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 </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 <input pInputText id=\"speed\" type=\"text\" formControlName=\"speed\" />\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\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)\">&#x2716;</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"] }]
2627
- }], ctorParameters: () => [{ type: i1$1.FormBuilder }, { type: i2$2.MultiImagesStorageService }, { type: AgentCardsAbstractService, decorators: [{
3530
+ ], 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)\">&#x2716;</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"] }]
3531
+ }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2$2.MultiImagesStorageService }, { type: AgentCardsAbstractService, decorators: [{
2628
3532
  type: Inject,
2629
3533
  args: [CONVERSATION_AI_TOKEN]
2630
- }] }, { type: i0.ChangeDetectorRef }, { type: i1$3.Router }, { type: i1$3.ActivatedRoute }, { type: i1$2.DialogService }, { type: DCConversationPromptBuilderService }, { type: i6$2.ToastAlertsAbstractService, decorators: [{
3534
+ }] }, { type: i0.ChangeDetectorRef }, { type: i1$4.Router }, { type: i1$4.ActivatedRoute }, { type: i1$3.DialogService }, { type: DCConversationPromptBuilderService }, { type: i6$1.ToastAlertsAbstractService, decorators: [{
2631
3535
  type: Optional
2632
3536
  }, {
2633
3537
  type: Inject,
@@ -2655,6 +3559,7 @@ class DCConversationCardUIComponent {
2655
3559
  { label: 'Delete', icon: 'pi pi-trash', title: 'delete', severity: 'danger', command: () => this.onDelete() },
2656
3560
  { label: 'Select', icon: 'pi pi-check', title: 'select', severity: 'success', command: () => this.onDetails() },
2657
3561
  ];
3562
+ this.showOptions = true;
2658
3563
  this.onCardAction = new EventEmitter();
2659
3564
  }
2660
3565
  ngOnInit() {
@@ -2672,13 +3577,15 @@ class DCConversationCardUIComponent {
2672
3577
  this.onCardAction.emit({ event: 'delete', card: this.card });
2673
3578
  }
2674
3579
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCConversationCardUIComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2675
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", type: DCConversationCardUIComponent, isStandalone: true, selector: "dc-agent-card-default-ui", inputs: { card: "card" }, outputs: { onCardAction: "onCardAction" }, ngImport: i0, template: "<p-card class=\"card-image\">\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 <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: i7$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$3.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: i3$1.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3580
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", type: DCConversationCardUIComponent, isStandalone: true, selector: "dc-agent-card-default-ui", inputs: { card: "card", showOptions: "showOptions" }, 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: 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: SpeedDialModule }, { kind: "component", type: i2$3.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: i3$2.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2676
3581
  }
2677
3582
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCConversationCardUIComponent, decorators: [{
2678
3583
  type: Component,
2679
- args: [{ selector: 'dc-agent-card-default-ui', imports: [PopoverModule, TruncatePipe, ButtonModule, SpeedDialModule, CardModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<p-card class=\"card-image\">\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 <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"] }]
3584
+ 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"] }]
2680
3585
  }], propDecorators: { card: [{
2681
3586
  type: Input
3587
+ }], showOptions: [{
3588
+ type: Input
2682
3589
  }], onCardAction: [{
2683
3590
  type: Output
2684
3591
  }] } });
@@ -2698,6 +3605,7 @@ class AgentCardListComponent extends PaginationBase {
2698
3605
  this.toastService = toastService;
2699
3606
  this.cdr = cdr;
2700
3607
  this.viewMode = 'cards';
3608
+ this.showOptions = true;
2701
3609
  this.gridLayout = true;
2702
3610
  this.agentCards = [];
2703
3611
  this.cardEventSubs = [];
@@ -2814,8 +3722,8 @@ class AgentCardListComponent extends PaginationBase {
2814
3722
  this.doAction(actionEvent); // handle by father.
2815
3723
  }
2816
3724
  }
2817
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: AgentCardListComponent, deps: [{ token: CONVERSATION_AI_TOKEN }, { token: TOAST_ALERTS_TOKEN }, { token: i1$3.ActivatedRoute }, { token: i1$3.Router }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
2818
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", type: AgentCardListComponent, isStandalone: true, selector: "dc-agent-card-lists", inputs: { viewMode: "viewMode", customCardComponent: "customCardComponent", gridLayout: "gridLayout", getCustomButtons: "getCustomButtons" }, viewQueries: [{ propertyName: "outlets", predicate: ["outlet"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<dc-filter-bar [isAdmin]=\"true\" (onAction)=\"doFilterBarAction($event)\" (onSearch)=\"search($event)\" (onFilterChange)=\"filterChange($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 }\"> </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: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.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: i3$2.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: ["onAction", "onChangeSort", "onNew", "onSearch", "onFilterChange"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i6.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"] }] }); }
3725
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: AgentCardListComponent, deps: [{ token: CONVERSATION_AI_TOKEN }, { token: TOAST_ALERTS_TOKEN }, { token: i1$4.ActivatedRoute }, { token: i1$4.Router }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
3726
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", type: AgentCardListComponent, isStandalone: true, selector: "dc-agent-card-lists", inputs: { viewMode: "viewMode", customCardComponent: "customCardComponent", showOptions: "showOptions", gridLayout: "gridLayout", getCustomButtons: "getCustomButtons" }, 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: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.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: i3$3.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: i4.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"] }] }); }
2819
3727
  }
2820
3728
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: AgentCardListComponent, decorators: [{
2821
3729
  type: Component,
@@ -2829,17 +3737,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImpor
2829
3737
  SkeletonModule,
2830
3738
  SpeedDialModule,
2831
3739
  QuickTableComponent,
2832
- ], standalone: true, template: "<dc-filter-bar [isAdmin]=\"true\" (onAction)=\"doFilterBarAction($event)\" (onSearch)=\"search($event)\" (onFilterChange)=\"filterChange($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 }\"> </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"] }]
3740
+ ], 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"] }]
2833
3741
  }], ctorParameters: () => [{ type: AgentCardsAbstractService, decorators: [{
2834
3742
  type: Inject,
2835
3743
  args: [CONVERSATION_AI_TOKEN]
2836
- }] }, { type: i6$2.ToastAlertsAbstractService, decorators: [{
3744
+ }] }, { type: i6$1.ToastAlertsAbstractService, decorators: [{
2837
3745
  type: Inject,
2838
3746
  args: [TOAST_ALERTS_TOKEN]
2839
- }] }, { type: i1$3.ActivatedRoute }, { type: i1$3.Router }, { type: i0.ChangeDetectorRef }], propDecorators: { viewMode: [{
3747
+ }] }, { type: i1$4.ActivatedRoute }, { type: i1$4.Router }, { type: i0.ChangeDetectorRef }], propDecorators: { viewMode: [{
2840
3748
  type: Input
2841
3749
  }], customCardComponent: [{
2842
3750
  type: Input
3751
+ }], showOptions: [{
3752
+ type: Input
2843
3753
  }], gridLayout: [{
2844
3754
  type: Input
2845
3755
  }], getCustomButtons: [{
@@ -2849,50 +3759,77 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImpor
2849
3759
  args: ['outlet']
2850
3760
  }] } });
2851
3761
 
2852
- const DefaultConversationSettings = {
2853
- textEngine: TextEngines.SimpleText,
2854
- conversationType: ConversationType.Default,
2855
- tts: { voice: '', secondaryVoice: '', speed: '1', speedRate: 1 },
2856
- autoStart: false, // usally start with characterCard.first_mes but if not set also can start with LLM response.
2857
- messages: [], // Esto es calculado por el prompt builder tomando el agente.
2858
- };
3762
+ class ParseCardPipe {
3763
+ constructor() {
3764
+ this.userDataExchange = inject(USER_DATA_EXCHANGE);
3765
+ this.builderConversation = inject(DCConversationPromptBuilderService);
3766
+ }
3767
+ transform(text, card = null) {
3768
+ let parseDict = this.userDataExchange.getParseDict();
3769
+ if (card) {
3770
+ const cardMacros = { char: card?.characterCard?.data?.name };
3771
+ parseDict = { ...parseDict, ...cardMacros };
3772
+ }
3773
+ const result = this.builderConversation.applyReplacements(text, parseDict);
3774
+ return result;
3775
+ }
3776
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ParseCardPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
3777
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.1.1", ngImport: i0, type: ParseCardPipe, isStandalone: true, name: "parseCard" }); }
3778
+ }
3779
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ParseCardPipe, decorators: [{
3780
+ type: Pipe,
3781
+ args: [{
3782
+ name: 'parseCard',
3783
+ standalone: true,
3784
+ }]
3785
+ }] });
3786
+
2859
3787
  class DcAgentCardDetailsComponent {
2860
- constructor(agentCardService, route, cdr) {
3788
+ constructor(agentCardService, route, cdr, userDataExchange) {
2861
3789
  this.agentCardService = agentCardService;
2862
3790
  this.route = route;
2863
3791
  this.cdr = cdr;
2864
- this.conversationCardId = '';
3792
+ this.userDataExchange = userDataExchange;
3793
+ this.agentCardId = '';
2865
3794
  this.onStartConversation = new EventEmitter();
3795
+ this.showInfoLayer = false;
2866
3796
  }
2867
3797
  async ngOnInit() {
2868
- console.log('DcAgentCardDetailsComponent', this.conversationCardId);
3798
+ console.log('DcAgentCardDetailsComponent', this.agentCardId);
2869
3799
  const id = this.route.snapshot.paramMap.get('id');
2870
3800
  if (id) {
2871
- this.conversationCardId = id;
2872
- console.log(this.conversationCardId);
3801
+ this.agentCardId = id;
3802
+ console.log(this.agentCardId);
2873
3803
  }
2874
- this.conversation = await this.agentCardService.findConversationCardByID(this.conversationCardId);
2875
- if (!this.conversation.conversationSettings) {
3804
+ this.agentCard = await this.agentCardService.findConversationCardByID(this.agentCardId);
3805
+ if (!this.agentCard.conversationSettings) {
2876
3806
  console.warn('⚠️ Conversation settings not found ⚠️ probably is an old version of the card.');
2877
- this.conversation.conversationSettings = {};
3807
+ this.agentCard.conversationSettings = {};
2878
3808
  }
2879
- console.log(this.conversation);
3809
+ console.log(this.agentCard);
2880
3810
  this.cdr.detectChanges();
2881
3811
  }
2882
3812
  startConversation() {
2883
- console.log('⚠️ last version startConversation', this.conversation);
2884
- this.onStartConversation.emit(this.conversation);
3813
+ console.log('⚠️ last version startConversation', this.agentCard);
3814
+ this.onStartConversation.emit(this.agentCard);
3815
+ }
3816
+ toggleInfoLayer() {
3817
+ this.showInfoLayer = !this.showInfoLayer;
3818
+ this.cdr.markForCheck();
2885
3819
  }
2886
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DcAgentCardDetailsComponent, deps: [{ token: CONVERSATION_AI_TOKEN }, { token: i1$3.ActivatedRoute }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
2887
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", type: DcAgentCardDetailsComponent, isStandalone: true, selector: "dc-agent-card-details", inputs: { conversationCardId: "conversationCardId" }, outputs: { onStartConversation: "onStartConversation" }, ngImport: i0, template: "<div class=\"dc-conversation-card-details\">\n <div class=\"header\">\n <h2>{{ conversation?.title }}</h2>\n <span class=\"version\">v{{ conversation?.version }}</span>\n </div>\n\n <div class=\"character-card\" *ngIf=\"conversation?.characterCard\">\n <div class=\"character-header\">\n <h3>{{ conversation?.characterCard.data.name }}</h3>\n <div class=\"tags\">\n <span class=\"tag\" *ngFor=\"let tag of conversation?.characterCard.data?.tags\">{{ tag }}</span>\n </div>\n </div>\n\n <div class=\"image-wrapper\">\n <div class=\"image\">\n <img [src]=\"conversation?.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>{{ conversation?.characterCard.data?.description }}</p>\n </div>\n\n <div class=\"scenario\" *ngIf=\"conversation?.characterCard.data?.scenario\">\n <h4>Scenario</h4>\n <p>{{ conversation?.characterCard.data.scenario }}</p>\n </div>\n\n <div class=\"first-message\" *ngIf=\"conversation?.characterCard.data?.first_mes\">\n <h4>First Message</h4>\n <p>{{ conversation?.characterCard.data.first_mes }}</p>\n </div>\n\n <div class=\"alternate-greetings\" *ngIf=\"conversation?.characterCard.data?.alternate_greetings?.length\">\n <h4>Alternate Greetings</h4>\n <ul>\n <li *ngFor=\"let greeting of conversation?.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\">{{ conversation?.conversationSettings?.conversationType }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Language:</span>\n <span class=\"value\">{{ conversation?.lang }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Text Engine:</span>\n <span class=\"value\">{{ conversation?.conversationSettings?.textEngine }}</span>\n </div>\n </div>\n\n <div class=\"tts-settings\" *ngIf=\"conversation?.tts\">\n <h3>TTS Settings</h3>\n <div class=\"setting-item\">\n <span class=\"label\">Primary Voice:</span>\n <span class=\"value\">{{ conversation?.tts.voice }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Secondary Voice:</span>\n <span class=\"value\">{{ conversation?.tts.secondaryVoice }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Speed:</span>\n <span class=\"value\">{{ conversation?.tts.speed }} ({{ conversation?.tts.speedRate }}x)</span>\n </div>\n </div>\n </div>\n</div>\n", styles: [".dc-conversation-card-details{padding:1.5rem;max-width:800px;margin:0 auto}.dc-conversation-card-details .header{display:flex;align-items:center;margin-bottom:2rem;gap:1rem}.dc-conversation-card-details .header h2{margin:0;font-size:1.8rem;color:#333}.dc-conversation-card-details .header .version{color:#666;font-size:.9rem;padding:.2rem .5rem;background:#f5f5f5;border-radius:4px}.dc-conversation-card-details .character-card{background:#fff;border-radius:8px;padding:1.5rem;margin-bottom:2rem;box-shadow:0 2px 4px #0000001a}.dc-conversation-card-details .character-card .character-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:1rem}.dc-conversation-card-details .character-card .character-header h3{margin:0;color:#2c3e50;font-size:1.4rem}.dc-conversation-card-details .character-card .character-header .tags{display:flex;gap:.5rem;flex-wrap:wrap}.dc-conversation-card-details .character-card .character-header .tags .tag{background:#e9ecef;color:#495057;padding:.2rem .6rem;border-radius:4px;font-size:.8rem}.dc-conversation-card-details .character-card .description{color:#555;line-height:1.6;margin-bottom:1.5rem}.dc-conversation-card-details .character-card h4{color:#2c3e50;margin:1rem 0 .5rem;font-size:1.1rem}.dc-conversation-card-details .character-card .scenario,.dc-conversation-card-details .character-card .first-message{background:#f8f9fa;padding:1rem;border-radius:6px;margin-bottom:1rem}.dc-conversation-card-details .character-card .scenario p,.dc-conversation-card-details .character-card .first-message p{margin:0;color:#666}.dc-conversation-card-details .character-card .alternate-greetings ul{list-style:none;padding:0;margin:0}.dc-conversation-card-details .character-card .alternate-greetings ul li{padding:.5rem;border-bottom:1px solid #eee;color:#666}.dc-conversation-card-details .character-card .alternate-greetings ul li:last-child{border-bottom:none}.dc-conversation-card-details .settings{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem}@media (max-width: 768px){.dc-conversation-card-details .settings{grid-template-columns:1fr}}.dc-conversation-card-details .settings .conversation-settings,.dc-conversation-card-details .settings .tts-settings{background:#fff;border-radius:8px;padding:1.5rem;box-shadow:0 2px 4px #0000001a}.dc-conversation-card-details .settings .conversation-settings h3,.dc-conversation-card-details .settings .tts-settings h3{margin:0 0 1rem;color:#2c3e50;font-size:1.2rem}.dc-conversation-card-details .settings .conversation-settings .setting-item,.dc-conversation-card-details .settings .tts-settings .setting-item{display:flex;justify-content:space-between;padding:.5rem 0;border-bottom:1px solid #eee}.dc-conversation-card-details .settings .conversation-settings .setting-item:last-child,.dc-conversation-card-details .settings .tts-settings .setting-item:last-child{border-bottom:none}.dc-conversation-card-details .settings .conversation-settings .setting-item .label,.dc-conversation-card-details .settings .tts-settings .setting-item .label{color:#666;font-weight:500}.dc-conversation-card-details .settings .conversation-settings .setting-item .value,.dc-conversation-card-details .settings .tts-settings .setting-item .value{color:#333}.btn{padding:.5rem 1rem;background:#007bff;color:#fff;border:none;border-radius:4px;cursor:pointer;transition:background-color .2s ease-in-out}.btn:hover{background:#0056b3}.image-wrapper{display:flex;justify-content:center;width:100%}.image-wrapper .image{position:relative;max-width:400px;width:100%}.image-wrapper .image img{width:100%;height:auto;object-fit:cover;border-radius:8px}.image-wrapper .image .actions{position:absolute;bottom:20px;left:50%;transform:translate(-50%);z-index:2}.image-wrapper .image .actions .btn{min-width:200px;background:#007bffe6;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.image-wrapper .image .actions .btn:hover{background:#0056b3f2}.actions-wrapper{display:flex;justify-content:center;width:100%;margin:1rem 0}.actions-wrapper .actions .btn{min-width:200px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i7$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"] }] }); }
3820
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DcAgentCardDetailsComponent, deps: [{ token: CONVERSATION_AI_TOKEN }, { token: i1$4.ActivatedRoute }, { token: i0.ChangeDetectorRef }, { token: USER_DATA_EXCHANGE }], target: i0.ɵɵFactoryTarget.Component }); }
3821
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", 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 <div class=\"scenario\" *ngIf=\"agentCard?.characterCard.data?.scenario\">\n <h4>Scenario</h4>\n <p>{{ agentCard?.characterCard.data.scenario | parseCard : agentCard }}</p>\n </div>\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: CommonModule }, { kind: "directive", type: i1$2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { 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"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i3$2.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "pipe", type: ParseCardPipe, name: "parseCard" }] }); }
2888
3822
  }
2889
3823
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DcAgentCardDetailsComponent, decorators: [{
2890
3824
  type: Component,
2891
- args: [{ selector: 'dc-agent-card-details', standalone: true, imports: [CommonModule, ButtonModule], template: "<div class=\"dc-conversation-card-details\">\n <div class=\"header\">\n <h2>{{ conversation?.title }}</h2>\n <span class=\"version\">v{{ conversation?.version }}</span>\n </div>\n\n <div class=\"character-card\" *ngIf=\"conversation?.characterCard\">\n <div class=\"character-header\">\n <h3>{{ conversation?.characterCard.data.name }}</h3>\n <div class=\"tags\">\n <span class=\"tag\" *ngFor=\"let tag of conversation?.characterCard.data?.tags\">{{ tag }}</span>\n </div>\n </div>\n\n <div class=\"image-wrapper\">\n <div class=\"image\">\n <img [src]=\"conversation?.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>{{ conversation?.characterCard.data?.description }}</p>\n </div>\n\n <div class=\"scenario\" *ngIf=\"conversation?.characterCard.data?.scenario\">\n <h4>Scenario</h4>\n <p>{{ conversation?.characterCard.data.scenario }}</p>\n </div>\n\n <div class=\"first-message\" *ngIf=\"conversation?.characterCard.data?.first_mes\">\n <h4>First Message</h4>\n <p>{{ conversation?.characterCard.data.first_mes }}</p>\n </div>\n\n <div class=\"alternate-greetings\" *ngIf=\"conversation?.characterCard.data?.alternate_greetings?.length\">\n <h4>Alternate Greetings</h4>\n <ul>\n <li *ngFor=\"let greeting of conversation?.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\">{{ conversation?.conversationSettings?.conversationType }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Language:</span>\n <span class=\"value\">{{ conversation?.lang }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Text Engine:</span>\n <span class=\"value\">{{ conversation?.conversationSettings?.textEngine }}</span>\n </div>\n </div>\n\n <div class=\"tts-settings\" *ngIf=\"conversation?.tts\">\n <h3>TTS Settings</h3>\n <div class=\"setting-item\">\n <span class=\"label\">Primary Voice:</span>\n <span class=\"value\">{{ conversation?.tts.voice }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Secondary Voice:</span>\n <span class=\"value\">{{ conversation?.tts.secondaryVoice }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Speed:</span>\n <span class=\"value\">{{ conversation?.tts.speed }} ({{ conversation?.tts.speedRate }}x)</span>\n </div>\n </div>\n </div>\n</div>\n", styles: [".dc-conversation-card-details{padding:1.5rem;max-width:800px;margin:0 auto}.dc-conversation-card-details .header{display:flex;align-items:center;margin-bottom:2rem;gap:1rem}.dc-conversation-card-details .header h2{margin:0;font-size:1.8rem;color:#333}.dc-conversation-card-details .header .version{color:#666;font-size:.9rem;padding:.2rem .5rem;background:#f5f5f5;border-radius:4px}.dc-conversation-card-details .character-card{background:#fff;border-radius:8px;padding:1.5rem;margin-bottom:2rem;box-shadow:0 2px 4px #0000001a}.dc-conversation-card-details .character-card .character-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:1rem}.dc-conversation-card-details .character-card .character-header h3{margin:0;color:#2c3e50;font-size:1.4rem}.dc-conversation-card-details .character-card .character-header .tags{display:flex;gap:.5rem;flex-wrap:wrap}.dc-conversation-card-details .character-card .character-header .tags .tag{background:#e9ecef;color:#495057;padding:.2rem .6rem;border-radius:4px;font-size:.8rem}.dc-conversation-card-details .character-card .description{color:#555;line-height:1.6;margin-bottom:1.5rem}.dc-conversation-card-details .character-card h4{color:#2c3e50;margin:1rem 0 .5rem;font-size:1.1rem}.dc-conversation-card-details .character-card .scenario,.dc-conversation-card-details .character-card .first-message{background:#f8f9fa;padding:1rem;border-radius:6px;margin-bottom:1rem}.dc-conversation-card-details .character-card .scenario p,.dc-conversation-card-details .character-card .first-message p{margin:0;color:#666}.dc-conversation-card-details .character-card .alternate-greetings ul{list-style:none;padding:0;margin:0}.dc-conversation-card-details .character-card .alternate-greetings ul li{padding:.5rem;border-bottom:1px solid #eee;color:#666}.dc-conversation-card-details .character-card .alternate-greetings ul li:last-child{border-bottom:none}.dc-conversation-card-details .settings{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem}@media (max-width: 768px){.dc-conversation-card-details .settings{grid-template-columns:1fr}}.dc-conversation-card-details .settings .conversation-settings,.dc-conversation-card-details .settings .tts-settings{background:#fff;border-radius:8px;padding:1.5rem;box-shadow:0 2px 4px #0000001a}.dc-conversation-card-details .settings .conversation-settings h3,.dc-conversation-card-details .settings .tts-settings h3{margin:0 0 1rem;color:#2c3e50;font-size:1.2rem}.dc-conversation-card-details .settings .conversation-settings .setting-item,.dc-conversation-card-details .settings .tts-settings .setting-item{display:flex;justify-content:space-between;padding:.5rem 0;border-bottom:1px solid #eee}.dc-conversation-card-details .settings .conversation-settings .setting-item:last-child,.dc-conversation-card-details .settings .tts-settings .setting-item:last-child{border-bottom:none}.dc-conversation-card-details .settings .conversation-settings .setting-item .label,.dc-conversation-card-details .settings .tts-settings .setting-item .label{color:#666;font-weight:500}.dc-conversation-card-details .settings .conversation-settings .setting-item .value,.dc-conversation-card-details .settings .tts-settings .setting-item .value{color:#333}.btn{padding:.5rem 1rem;background:#007bff;color:#fff;border:none;border-radius:4px;cursor:pointer;transition:background-color .2s ease-in-out}.btn:hover{background:#0056b3}.image-wrapper{display:flex;justify-content:center;width:100%}.image-wrapper .image{position:relative;max-width:400px;width:100%}.image-wrapper .image img{width:100%;height:auto;object-fit:cover;border-radius:8px}.image-wrapper .image .actions{position:absolute;bottom:20px;left:50%;transform:translate(-50%);z-index:2}.image-wrapper .image .actions .btn{min-width:200px;background:#007bffe6;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.image-wrapper .image .actions .btn:hover{background:#0056b3f2}.actions-wrapper{display:flex;justify-content:center;width:100%;margin:1rem 0}.actions-wrapper .actions .btn{min-width:200px}\n"] }]
3825
+ args: [{ selector: 'dc-agent-card-details', standalone: true, imports: [CommonModule, 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 <div class=\"scenario\" *ngIf=\"agentCard?.characterCard.data?.scenario\">\n <h4>Scenario</h4>\n <p>{{ agentCard?.characterCard.data.scenario | parseCard : agentCard }}</p>\n </div>\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"] }]
2892
3826
  }], ctorParameters: () => [{ type: AgentCardsAbstractService, decorators: [{
2893
3827
  type: Inject,
2894
3828
  args: [CONVERSATION_AI_TOKEN]
2895
- }] }, { type: i1$3.ActivatedRoute }, { type: i0.ChangeDetectorRef }], propDecorators: { conversationCardId: [{
3829
+ }] }, { type: i1$4.ActivatedRoute }, { type: i0.ChangeDetectorRef }, { type: UserDataExchangeAbstractService, decorators: [{
3830
+ type: Inject,
3831
+ args: [USER_DATA_EXCHANGE]
3832
+ }] }], propDecorators: { agentCardId: [{
2896
3833
  type: Input
2897
3834
  }], onStartConversation: [{
2898
3835
  type: Output
@@ -2907,5 +3844,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImpor
2907
3844
  * Generated bundle index. Do not edit.
2908
3845
  */
2909
3846
 
2910
- export { AgentCardListComponent, AgentCardsAbstractService, AudioService, AudioSpeed, CONVERSATION_AI_TOKEN, ChatMessage, ChatMessageComponent, ChatMultiMessage, ChatRole, ChatUserSettings, ConversationDTO, ConversationMessagesDTO, ConversationType, ConversationTypeOptions, DCAgentCardFormComponent, DCChatComponent, DCConversationCardUIComponent, DCConversationPromptBuilderService, DcAgentCardDetailsComponent, EAccountsPlatform, LangCodeDescriptionEs, ProviderSelectorComponent, TextEngineOptions, TextEngines, USER_DATA_EXCHANGE, UserDataExchangeAbstractService, VoiceTTSOption, VoiceTTSOptions, WordTimestamps, characterCardStringDataDefinition, extractJsonFromResponse, provideChatAIService, provideUserDataExchange };
3847
+ export { AgentCardListComponent, AgentCardsAbstractService, AudioService, AudioSpeed, CONVERSATION_AI_TOKEN, ChatContainerComponent, ChatMessage, ChatMultiMessage, ChatRole, ChatUserSettings, ConversationDTO, ConversationMessagesDTO, ConversationType, ConversationTypeOptions, DCAgentCardFormComponent, DCChatComponent, DCConversationCardUIComponent, DCConversationPromptBuilderService, DcAgentCardDetailsComponent, EAccountsPlatform, LangCodeDescriptionEs, MessageAudio, ProviderSelectorComponent, TextEngineOptions, TextEngines, USER_DATA_EXCHANGE, UserDataExchangeAbstractService, VoiceTTSOption, VoiceTTSOptions, WordTimestamps, characterCardStringDataDefinition, defaultconvUserSettings, extractJsonFromResponse, provideChatAIService, provideUserDataExchange };
2911
3848
  //# sourceMappingURL=dataclouder-ngx-agent-cards.mjs.map