@dataclouder/ngx-agent-cards 0.0.88 → 0.0.90

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 (22) hide show
  1. package/fesm2022/dataclouder-ngx-agent-cards.mjs +463 -671
  2. package/fesm2022/dataclouder-ngx-agent-cards.mjs.map +1 -1
  3. package/lib/components/chat-container/chat-container.component.d.ts +5 -19
  4. package/lib/components/chat-container/chat-footer/chat-footer.component.d.ts +7 -2
  5. package/lib/components/chat-container/chat-messages-list/chat-message/chat-message.component.d.ts +5 -28
  6. package/lib/components/chat-container/chat-messages-list/chat-message/chat-message.utils.d.ts +1 -1
  7. package/lib/components/chat-container/chat-messages-list/chat-messages-list.component.d.ts +8 -5
  8. package/lib/components/chat-container/chat-messages-list/message-orchestrator/message-orchestrator.component.d.ts +26 -0
  9. package/lib/components/chat-settings/dc-conversation-userchat-settings.component.d.ts +2 -3
  10. package/lib/components/icons/icons.component.d.ts +1 -1
  11. package/lib/components/{standalone-audio-text-sync/standalone-audio-text-sync.component.d.ts → text-highlighter/text-highlighter.d.ts} +10 -10
  12. package/lib/models/agent.models.d.ts +4 -7
  13. package/lib/models/conversation-ai.class.d.ts +3 -3
  14. package/lib/models/user-data-exchange.d.ts +2 -0
  15. package/lib/pipes/safe-json.pipe.d.ts +15 -0
  16. package/lib/services/conversation.service.d.ts +10 -13
  17. package/lib/services/dc-conversation-builder.service.d.ts +0 -2
  18. package/lib/services/message-processing.service.d.ts +2 -2
  19. package/package.json +1 -1
  20. package/public-api.d.ts +3 -1
  21. package/lib/components/chat-container/chat-messages-list/chat-message/multi-message-content/multi-message-content.d.ts +0 -33
  22. package/lib/services/audio-text-sync.service.d.ts +0 -57
@@ -1,9 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, Injectable, inject, input, output, Input, Component, signal, DestroyRef, computed, ChangeDetectorRef, effect, ChangeDetectionStrategy, Pipe, ViewChild, ViewChildren } from '@angular/core';
2
+ import { InjectionToken, Injectable, inject, Pipe, input, output, Input, Component, signal, computed, ChangeDetectorRef, DestroyRef, effect, ChangeDetectionStrategy, ElementRef, ViewChild, ViewChildren } from '@angular/core';
3
3
  import * as i1$1 from '@angular/common';
4
4
  import { CommonModule, DatePipe, DecimalPipe, NgComponentOutlet } from '@angular/common';
5
5
  import { DynamicDialogRef, DialogService, DynamicDialogConfig, DynamicDialogModule } from 'primeng/dynamicdialog';
6
- import * as i2$4 from 'primeng/dialog';
6
+ import * as i1$3 from 'primeng/dialog';
7
7
  import { DialogModule } from 'primeng/dialog';
8
8
  import * as i2 from 'primeng/progressbar';
9
9
  import { ProgressBarModule } from 'primeng/progressbar';
@@ -14,11 +14,11 @@ import * as i3 from 'primeng/textarea';
14
14
  import { TextareaModule } from 'primeng/textarea';
15
15
  import * as i2$1 from 'primeng/button';
16
16
  import { ButtonModule } from 'primeng/button';
17
+ import { AudioSpeed as AudioSpeed$1, MicVadComponent, TOAST_ALERTS_TOKEN, PaginationBase, DCFilterBarComponent, QuickTableComponent } from '@dataclouder/ngx-core';
17
18
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
18
- import { BehaviorSubject, Subject, fromEvent, filter } from 'rxjs';
19
+ import { Subject, fromEvent, filter } from 'rxjs';
19
20
  import { takeUntil, map } from 'rxjs/operators';
20
21
  import { DomSanitizer } from '@angular/platform-browser';
21
- import { AudioSpeed as AudioSpeed$1, TOAST_ALERTS_TOKEN, PaginationBase, DCFilterBarComponent, QuickTableComponent } from '@dataclouder/ngx-core';
22
22
  import * as i1$2 from 'primeng/skeleton';
23
23
  import { SkeletonModule } from 'primeng/skeleton';
24
24
  import * as i2$3 from 'primeng/checkbox';
@@ -49,14 +49,14 @@ import * as i9 from 'primeng/select';
49
49
  import { SelectModule } from 'primeng/select';
50
50
  import * as i10 from 'primeng/popover';
51
51
  import { PopoverModule } from 'primeng/popover';
52
- import * as i2$5 from 'primeng/card';
52
+ import * as i2$4 from 'primeng/card';
53
53
  import { CardModule } from 'primeng/card';
54
54
  import * as i3$2 from 'primeng/dropdown';
55
55
  import { DropdownModule } from 'primeng/dropdown';
56
56
  import { ChipModule } from 'primeng/chip';
57
- import * as i1$3 from 'primeng/paginator';
57
+ import * as i1$4 from 'primeng/paginator';
58
58
  import { PaginatorModule } from 'primeng/paginator';
59
- import * as i2$6 from 'primeng/speeddial';
59
+ import * as i2$5 from 'primeng/speeddial';
60
60
  import { SpeedDialModule } from 'primeng/speeddial';
61
61
 
62
62
  const characterCardStringDataDefinition = `
@@ -83,8 +83,6 @@ class WordTimestamps {
83
83
  }
84
84
  class MessageAudio {
85
85
  }
86
- class ChatMultiMessage extends MessageAudio {
87
- }
88
86
  var ChatRole;
89
87
  (function (ChatRole) {
90
88
  ChatRole["System"] = "system";
@@ -536,13 +534,13 @@ class DCConversationPromptBuilderService {
536
534
  }
537
535
  // For chat conversation i need inital settings.
538
536
  buildConversationSettings(agentCard, parseDict = null) {
539
- const converstionSettings = agentCard?.conversationSettings;
537
+ const converstionSettings = agentCard?.conversationSettings || {};
540
538
  converstionSettings.messages = this.buildConversationMessages(agentCard, parseDict);
541
539
  converstionSettings.last_prompt = this.getJailBrakePrompt(agentCard);
542
540
  return converstionSettings;
543
541
  }
544
542
  buildConversationMessages(agentCard, parseDict = null) {
545
- const initialConversation = this.buildInitialConversation(agentCard.characterCard, agentCard.conversationSettings.conversationType);
543
+ const initialConversation = this.buildInitialConversation(agentCard.characterCard, agentCard?.conversationSettings?.conversationType);
546
544
  parseDict = this.getDefaultParseDict(parseDict, agentCard);
547
545
  // Si quiero agregar todo tipo de info, el parse dict es algo que debe hacer el cliente.
548
546
  this.parseConversation(initialConversation, parseDict);
@@ -609,7 +607,7 @@ class DCConversationPromptBuilderService {
609
607
  }
610
608
  return prompt;
611
609
  }
612
- buildInitialConversation(characterCard, conversationType) {
610
+ buildInitialConversation(characterCard, conversationType = ConversationType.General) {
613
611
  let systemPromptInstructions = this.getDefaultPromptByType(conversationType);
614
612
  if (characterCard?.data?.system_prompt) {
615
613
  systemPromptInstructions = characterCard.data.system_prompt;
@@ -682,7 +680,59 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
682
680
  args: [{
683
681
  providedIn: 'root',
684
682
  }]
685
- }], ctorParameters: () => [] });
683
+ }] });
684
+
685
+ /**
686
+ * SafeJsonPipe - A pipe that safely stringifies objects with circular references
687
+ * This pipe handles circular references and DOM objects that can't be serialized
688
+ */
689
+ class SafeJsonPipe {
690
+ transform(value) {
691
+ // Create a new object with only serializable properties
692
+ return this.stringifySafely(value);
693
+ }
694
+ /**
695
+ * Safely stringify an object, handling circular references
696
+ */
697
+ stringifySafely(obj) {
698
+ const seen = new WeakSet();
699
+ return JSON.stringify(obj, (key, value) => {
700
+ // Skip properties that start with underscore (often internal properties)
701
+ if (key.startsWith('_')) {
702
+ return undefined;
703
+ }
704
+ // Handle DOM elements and other non-serializable objects
705
+ if (value instanceof HTMLElement || value instanceof Node) {
706
+ return '[DOM Element]';
707
+ }
708
+ // Handle audio elements specifically
709
+ if (key === 'audioHtml' || key === 'audioElement') {
710
+ return '[Audio Element]';
711
+ }
712
+ // Handle functions
713
+ if (typeof value === 'function') {
714
+ return '[Function]';
715
+ }
716
+ // Handle circular references
717
+ if (typeof value === 'object' && value !== null) {
718
+ if (seen.has(value)) {
719
+ return '[Circular Reference]';
720
+ }
721
+ seen.add(value);
722
+ }
723
+ return value;
724
+ }, 2);
725
+ }
726
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: SafeJsonPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
727
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.4", ngImport: i0, type: SafeJsonPipe, isStandalone: true, name: "safeJson" }); }
728
+ }
729
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: SafeJsonPipe, decorators: [{
730
+ type: Pipe,
731
+ args: [{
732
+ name: 'safeJson',
733
+ standalone: true
734
+ }]
735
+ }] });
686
736
 
687
737
  class ChatHeaderComponent {
688
738
  constructor() {
@@ -932,22 +982,14 @@ function removeAllEmojis(text) {
932
982
  class MessageProcessingService {
933
983
  constructor() { }
934
984
  // Process message for display
935
- processMessage(message, conversationSettings, mutate = false) {
985
+ processMessage(message, conversationSettings) {
936
986
  const defaultVoice = this.getVoice(conversationSettings.voice);
937
987
  let processedMessage;
938
- if (mutate) {
939
- // Modify the existing message object
940
- message.voice = defaultVoice;
941
- processedMessage = message;
942
- }
943
- else {
944
- // Create a new message object
945
- processedMessage = {
946
- content: message.content,
947
- role: message.role,
948
- voice: defaultVoice
949
- };
950
- }
988
+ processedMessage = {
989
+ content: message.content,
990
+ role: message.role,
991
+ voice: defaultVoice,
992
+ };
951
993
  // Process based on text engine
952
994
  if (conversationSettings.textEngine === TextEngines.MarkdownMultiMessages) {
953
995
  this.processMultiMessages(processedMessage, conversationSettings);
@@ -959,7 +1001,8 @@ class MessageProcessingService {
959
1001
  const content = this.subsItalicsByTag(processedMessage.content, conversationSettings.secondaryVoice);
960
1002
  processedMessage.ssml = '<speak>' + content + '</speak>';
961
1003
  }
962
- return processedMessage;
1004
+ console.log(processedMessage);
1005
+ return { ...message, ...processedMessage };
963
1006
  }
964
1007
  // Process multi-messages from markdown
965
1008
  processMultiMessages(message, settings) {
@@ -978,7 +1021,7 @@ class MessageProcessingService {
978
1021
  audioUrl: null,
979
1022
  audioPromise: null,
980
1023
  text: val.text,
981
- tag: val.tag
1024
+ tag: val.tag,
982
1025
  };
983
1026
  });
984
1027
  }
@@ -1022,64 +1065,131 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
1022
1065
  }]
1023
1066
  }], ctorParameters: () => [] });
1024
1067
 
1068
+ function matchTranscription(originalText, transcription) {
1069
+ const result = [];
1070
+ const transcriptionMap = new Map();
1071
+ // Create a map of lowercase words to an array of their corresponding objects in transcription
1072
+ for (const obj of transcription) {
1073
+ const lowercaseWord = obj.word.trim().toLowerCase();
1074
+ if (transcriptionMap.has(lowercaseWord)) {
1075
+ transcriptionMap.get(lowercaseWord).push(obj);
1076
+ }
1077
+ else {
1078
+ transcriptionMap.set(lowercaseWord, [obj]);
1079
+ }
1080
+ }
1081
+ // Split the original text into an array of words
1082
+ const words = originalText.split(' ');
1083
+ let currentTime = 0;
1084
+ for (const word of words) {
1085
+ const lowercaseWord = word.toLowerCase();
1086
+ const matchedObjs = transcriptionMap.get(lowercaseWord);
1087
+ if (matchedObjs && matchedObjs.length > 0) {
1088
+ const matchedObj = matchedObjs.shift();
1089
+ result.push({
1090
+ word: word,
1091
+ start: Number(matchedObj.start.toFixed(3)),
1092
+ end: Number(matchedObj.end.toFixed(3)),
1093
+ });
1094
+ currentTime = matchedObj.end;
1095
+ }
1096
+ else {
1097
+ result.push({
1098
+ word: word,
1099
+ start: Number(currentTime.toFixed(3)),
1100
+ end: Number(currentTime.toFixed(3)),
1101
+ });
1102
+ }
1103
+ }
1104
+ return result;
1105
+ }
1106
+ function extractAudioAndTranscription(message, audio) {
1107
+ message.audioUrl = audio.blobUrl;
1108
+ message.transcription = audio.transcription;
1109
+ if (message.transcription) {
1110
+ message.transcriptionTimestamps = matchTranscription(message.text || message.content, message.transcription.words);
1111
+ }
1112
+ return message;
1113
+ }
1114
+ function buildObjectTTSRequest(message, settings = {}) {
1115
+ const generateTranscription = settings?.highlightWords ?? false;
1116
+ const speedRate = settings?.speedRate || 0;
1117
+ const speed = typeof settings?.speed === 'string' ? settings.speed : AudioSpeed$1.Regular;
1118
+ const text = removeEmojis(message.text || message.content);
1119
+ return {
1120
+ text,
1121
+ voice: message.voice || settings.voice,
1122
+ generateTranscription,
1123
+ speedRate,
1124
+ speed,
1125
+ };
1126
+ }
1127
+
1025
1128
  class ConversationService {
1026
1129
  constructor() {
1130
+ // Services
1027
1131
  this.agentCardService = inject(CONVERSATION_AI_TOKEN);
1028
1132
  this.messageProcessingService = inject(MessageProcessingService);
1133
+ this.conversationBuilder = inject(DCConversationPromptBuilderService);
1134
+ this.userDataExchange = inject(USER_DATA_EXCHANGE);
1135
+ // Signals
1029
1136
  this.messagesSignal = signal([]);
1030
1137
  this.isThinkingSignal = signal(false);
1031
1138
  this.conversationSettingsSignal = signal(null);
1032
- this.agentCardSignal = signal(null);
1033
1139
  this.isDestroyedSignal = signal(false);
1034
1140
  }
1035
1141
  // Get messages as a signal
1036
- getMessages() {
1142
+ getMessagesSignal() {
1037
1143
  return this.messagesSignal;
1038
1144
  }
1145
+ // Add message to conversation
1146
+ addMessage(message) {
1147
+ this.messagesSignal.update((messages) => [...messages, message]);
1148
+ }
1039
1149
  // Get thinking state as a signal
1040
1150
  isThinking() {
1041
1151
  return this.isThinkingSignal;
1042
1152
  }
1043
- // Get conversation settings as a signal
1044
- getConversationSettings() {
1045
- return this.conversationSettingsSignal;
1046
- }
1047
- // Get agent card as a signal
1048
- getAgentCard() {
1049
- return this.agentCardSignal;
1050
- }
1051
1153
  // Set destroyed state
1052
1154
  setDestroyed(value) {
1053
1155
  this.isDestroyedSignal.set(value);
1054
1156
  }
1055
- // Initialize conversation
1056
- async initConversation(agentCard, conversationBuilder, parseDict) {
1057
- if (!agentCard?.conversationSettings) {
1058
- throw new Error('Conversation settings are required');
1059
- }
1060
- this.agentCardSignal.set(agentCard);
1061
- // Build conversation settings
1062
- const conversationSettings = conversationBuilder.buildConversationSettings(agentCard, parseDict);
1157
+ setupConversationWithAgentCard(agentCard) {
1158
+ const conversationSettings = this.conversationBuilder.buildConversationSettings(agentCard);
1159
+ this.conversationSettingsSignal.set(conversationSettings);
1160
+ }
1161
+ async initConversationWithSettings(conversationSettings) {
1063
1162
  this.conversationSettingsSignal.set(conversationSettings);
1064
- // Update agent card with conversation settings
1065
- const updatedAgentCard = { ...agentCard, conversationSettings };
1066
- this.agentCardSignal.set(updatedAgentCard);
1067
- if (!conversationSettings.messages) {
1068
- throw new Error('conversationSettings.messages is required in proper format to start conversation');
1069
- }
1070
- // Set initial messages
1071
- this.messagesSignal.set(conversationSettings.messages);
1163
+ await this.initConversation();
1164
+ }
1165
+ async initConversation() {
1166
+ const conversationSettings = this.conversationSettingsSignal();
1167
+ for (const i in conversationSettings.messages) {
1168
+ conversationSettings.messages[i].messageId = 'msg_' + i;
1169
+ }
1072
1170
  // Find first assistant message
1073
- const firstAssistantMsg = conversationSettings.messages.find((message) => message.role === ChatRole.Assistant);
1171
+ const firstAssistantMsg = conversationSettings?.messages.find((message) => message.role === ChatRole.Assistant);
1172
+ this.messagesSignal.set(conversationSettings?.messages || []);
1074
1173
  if (firstAssistantMsg) {
1075
1174
  // Process the first assistant message
1076
- this.processAssistantMessage(firstAssistantMsg, true);
1175
+ const processedMessage = this.messageProcessingService.processMessage(firstAssistantMsg, this.conversationSettingsSignal());
1176
+ // Find the index of the message with the matching ID
1177
+ const messageIndex = conversationSettings.messages.findIndex((message) => message.messageId === firstAssistantMsg.messageId);
1178
+ // If found, replace the message at that index
1179
+ if (messageIndex !== -1) {
1180
+ conversationSettings.messages[messageIndex] = processedMessage;
1181
+ }
1077
1182
  }
1078
- else if (agentCard.conversationSettings.autoStart) {
1183
+ else if (conversationSettings?.autoStart) {
1079
1184
  // Auto-start conversation if configured
1080
1185
  await this.sendCurrentConversation();
1081
1186
  }
1082
1187
  }
1188
+ // Initialize conversation
1189
+ async initConversationWithAgentCard(agentCard) {
1190
+ this.setupConversationWithAgentCard(agentCard);
1191
+ await this.initConversation();
1192
+ }
1083
1193
  // Send user message
1084
1194
  async sendUserMessage(message) {
1085
1195
  if (this.isThinkingSignal()) {
@@ -1090,19 +1200,6 @@ class ConversationService {
1090
1200
  // Set thinking state
1091
1201
  this.isThinkingSignal.set(true);
1092
1202
  try {
1093
- if (message.audioUrl && this.getAudioPlaybackSetting()) {
1094
- // Wait for audio to finish playing before sending response
1095
- await new Promise((resolve) => {
1096
- if (message.audioHtml) {
1097
- message.audioHtml.addEventListener('ended', () => {
1098
- resolve();
1099
- });
1100
- }
1101
- else {
1102
- resolve();
1103
- }
1104
- });
1105
- }
1106
1203
  // Send to AI service
1107
1204
  await this.sendCurrentConversation();
1108
1205
  }
@@ -1110,18 +1207,7 @@ class ConversationService {
1110
1207
  this.isThinkingSignal.set(false);
1111
1208
  }
1112
1209
  }
1113
- // Add message to conversation
1114
- addMessage(message) {
1115
- this.messagesSignal.update((messages) => [...messages, message]);
1116
- }
1117
1210
  // Process assistant message
1118
- processAssistantMessage(message, mutate = false) {
1119
- const conversationSettings = this.conversationSettingsSignal();
1120
- if (!conversationSettings) {
1121
- throw new Error('Conversation settings not initialized');
1122
- }
1123
- return this.messageProcessingService.processMessage(message, conversationSettings, mutate);
1124
- }
1125
1211
  // Send current conversation to AI
1126
1212
  async sendCurrentConversation() {
1127
1213
  if (this.isDestroyedSignal()) {
@@ -1129,10 +1215,6 @@ class ConversationService {
1129
1215
  }
1130
1216
  const messages = this.messagesSignal();
1131
1217
  const conversationSettings = this.conversationSettingsSignal();
1132
- const agentCard = this.agentCardSignal();
1133
- if (!conversationSettings || !agentCard) {
1134
- throw new Error('Conversation not properly initialized');
1135
- }
1136
1218
  if (messages.length > 31) {
1137
1219
  // Safety limit to prevent infinite conversations
1138
1220
  return;
@@ -1140,17 +1222,14 @@ class ConversationService {
1140
1222
  let conversationMessages = messages;
1141
1223
  // Add last prompt if available
1142
1224
  if (conversationSettings.last_prompt) {
1143
- conversationMessages = [
1144
- ...messages,
1145
- { content: conversationSettings.last_prompt, role: ChatRole.System },
1146
- ];
1225
+ conversationMessages = [...messages, { content: conversationSettings.last_prompt, role: ChatRole.System }];
1147
1226
  }
1148
1227
  // Prepare conversation DTO
1149
1228
  const conversation = {
1150
1229
  messages: conversationMessages,
1151
1230
  conversationType: conversationSettings.conversationType,
1152
1231
  textEngine: conversationSettings.textEngine,
1153
- model: agentCard.model,
1232
+ model: conversationSettings.model || { modelName: '', provider: '' },
1154
1233
  };
1155
1234
  // Call AI service
1156
1235
  const response = await this.agentCardService.callChatCompletion(conversation);
@@ -1159,29 +1238,20 @@ class ConversationService {
1159
1238
  throw new Error('No message returned from AI');
1160
1239
  }
1161
1240
  // Process response
1162
- const newMessage = this.processAssistantMessage(response);
1241
+ const newMessage = this.messageProcessingService.processMessage(response, conversationSettings);
1163
1242
  // Add to messages
1164
1243
  this.addMessage(newMessage);
1165
1244
  this.isThinkingSignal.set(false);
1166
1245
  }
1167
- // Helper to get audio playback setting
1168
- getAudioPlaybackSetting() {
1169
- const agentCard = this.agentCardSignal();
1170
- return agentCard?.conversationSettings?.repeatRecording || false;
1171
- }
1172
1246
  // Reset conversation
1173
1247
  async resetConversation(agentCard) {
1174
- if (agentCard) {
1175
- this.agentCardSignal.set(agentCard);
1176
- }
1177
1248
  // Clear messages
1178
1249
  this.messagesSignal.set([]);
1179
- // Re-initialize with current agent card
1180
- const currentAgentCard = agentCard || this.agentCardSignal();
1181
- if (currentAgentCard) {
1182
- // Note: This would need the conversationBuilder to be injected or passed
1183
- // await this.initConversation(currentAgentCard, conversationBuilder);
1184
- }
1250
+ }
1251
+ async getTTSFile(message) {
1252
+ const userChatSettings = await this.userDataExchange.getUserChatSettings();
1253
+ const ttsRequest = buildObjectTTSRequest(message, userChatSettings);
1254
+ return this.agentCardService.getTextAudioFile(ttsRequest);
1185
1255
  }
1186
1256
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ConversationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1187
1257
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ConversationService, providedIn: 'root' }); }
@@ -1191,7 +1261,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
1191
1261
  args: [{
1192
1262
  providedIn: 'root',
1193
1263
  }]
1194
- }], ctorParameters: () => [] });
1264
+ }] });
1195
1265
 
1196
1266
  function extractJsonFromResponse(content) {
1197
1267
  const jsonMatch = content.match(/\{[\s\S]*?\}/); // Match everything between first { and }
@@ -1279,14 +1349,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
1279
1349
 
1280
1350
  class ChatFooterComponent {
1281
1351
  constructor() {
1352
+ // Services
1282
1353
  this.conversationService = inject(ConversationService);
1283
1354
  this.evaluationService = inject(EvaluationService);
1355
+ this.agentCardService = inject(CONVERSATION_AI_TOKEN);
1356
+ // Inputs
1284
1357
  this.isAIThinking = input(false);
1285
1358
  this.evaluatorAgentCard = input();
1286
1359
  this.micSettings = input({ useWhisper: true, lang: 'en' });
1360
+ // Outputs
1287
1361
  this.sendMessage = output();
1288
1362
  this.textInputChanged = output();
1289
- this.micFinishedEvent = output();
1363
+ // readonly micFinishedEvent = output<any>();
1364
+ this.isGettingTranscription = false;
1365
+ this.isUserTalking = false;
1290
1366
  this.chatInputControl = new FormControl();
1291
1367
  // Get score from evaluation service
1292
1368
  this.score = this.evaluationService.getScore();
@@ -1304,7 +1380,7 @@ class ChatFooterComponent {
1304
1380
  * @param eventBlob The blob event from the mic component
1305
1381
  */
1306
1382
  micFinished(eventBlob) {
1307
- this.micFinishedEvent.emit(eventBlob);
1383
+ // this.micFinishedEvent.emit(eventBlob);
1308
1384
  }
1309
1385
  /**
1310
1386
  * Sends the user message
@@ -1319,7 +1395,7 @@ class ChatFooterComponent {
1319
1395
  role: ChatRole.User,
1320
1396
  };
1321
1397
  // Emit the message for parent components that need it
1322
- this.sendMessage.emit(message);
1398
+ // this.sendMessage.emit(message);
1323
1399
  // Clear the input field
1324
1400
  this.chatInputControl.setValue('');
1325
1401
  // Send the user message to the conversation service
@@ -1331,182 +1407,48 @@ class ChatFooterComponent {
1331
1407
  * Evaluate conversation using evaluator agent
1332
1408
  */
1333
1409
  async evaluateConversation() {
1334
- const messages = this.conversationService.getMessages()();
1410
+ const messages = this.conversationService.getMessagesSignal()();
1335
1411
  if (this.evaluatorAgentCard()) {
1336
1412
  await this.evaluationService.evaluateConversation(messages, this.evaluatorAgentCard());
1337
1413
  }
1338
1414
  }
1339
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1340
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.4", type: ChatFooterComponent, isStandalone: true, selector: "dc-chat-footer", inputs: { isAIThinking: { classPropertyName: "isAIThinking", publicName: "isAIThinking", isSignal: true, isRequired: false, transformFunction: null }, evaluatorAgentCard: { classPropertyName: "evaluatorAgentCard", publicName: "evaluatorAgentCard", isSignal: true, isRequired: false, transformFunction: null }, micSettings: { classPropertyName: "micSettings", publicName: "micSettings", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sendMessage: "sendMessage", textInputChanged: "textInputChanged", 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: ReactiveFormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: ProgressBarModule }, { kind: "component", type: i2.ProgressBar, selector: "p-progressBar, p-progressbar, p-progress-bar", inputs: ["value", "showValue", "styleClass", "valueStyleClass", "style", "unit", "mode", "color"] }, { kind: "component", type: DCMicComponent, selector: "dc-mic", inputs: ["isDone", "useWhisper", "targetOrBase", "micSettings"], outputs: ["onInterpretedText", "onFinishedRecognition", "onFinished"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i3.Textarea, selector: "[pTextarea]", inputs: ["autoResize", "variant", "fluid", "pSize"], outputs: ["onResize"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }] }); }
1341
- }
1342
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatFooterComponent, decorators: [{
1343
- type: Component,
1344
- args: [{ selector: 'dc-chat-footer', standalone: true, imports: [ReactiveFormsModule, ProgressBarModule, DCMicComponent, TextareaModule, ButtonModule], template: "<div class=\"progress-input\">\n <div class=\"input-container\">\n <dc-mic\n style=\"display: flex; align-items: center\"\n (onInterpretedText)=\"setInputText($event)\"\n (onFinished)=\"micFinished($event)\"\n [micSettings]=\"micSettings()\"></dc-mic>\n\n <textarea pTextarea [formControl]=\"chatInputControl\" (keyup.enter)=\"sendUserMessage()\" rows=\"1\"></textarea>\n\n <p-button (click)=\"sendUserMessage()\" [disabled]=\"isAIThinking() || !chatInputControl.value\" label=\"Enviar\" [rounded]=\"true\" />\n </div>\n\n <div>\n <p-progressbar showValue=\"false\" [value]=\"score()\" [style]=\"{ height: '6px' }\" />\n </div>\n</div>\n", styles: [".progress-input{padding:10px;background-color:#f5f5f545;border-top:1px solid #b1a8a8}.progress-input .input-container{display:flex;align-items:center;margin-bottom:5px}.progress-input .input-container textarea{flex:1;resize:none;margin:0 10px}.progress-input .input-container .send-button{background-color:#007bff;color:#fff;border:none;border-radius:4px;padding:8px 15px;cursor:pointer}.progress-input .input-container .send-button:disabled{background-color:#ccc;cursor:not-allowed}.progress-input .input-container .send-button:hover:not(:disabled){background-color:#0069d9}\n"] }]
1345
- }] });
1346
-
1347
- class AudioTextSyncService {
1348
- constructor() {
1349
- // Maps to store message-specific signals and observables
1350
- this.highlightedWordsSignalMap = new Map();
1351
- this.highlightedWords$Map = new Map();
1352
- // Maps for cleanup and active audio elements
1353
- this.cleanup$Map = new Map();
1354
- this.activeAudioMap = new Map();
1355
- this.destroyRef = inject(DestroyRef);
1356
- // Ensure cleanup when service is destroyed
1357
- this.destroyRef.onDestroy(() => {
1358
- this.stopAllSyncs();
1359
- });
1360
- }
1361
- /**
1362
- * Synchronizes audio playback with text transcription
1363
- * @param audioElement The audio element to sync with
1364
- * @param transcriptionTimestamps Array of word timestamps
1365
- * @param messageId Unique identifier for the message
1366
- */
1367
- syncAudioWithText(audioElement, transcriptionTimestamps, messageId) {
1368
- // Stop any existing sync for this message
1369
- this.stopSync(messageId);
1370
- // Create new signal and subject for this message if they don't exist
1371
- if (!this.highlightedWordsSignalMap.has(messageId)) {
1372
- this.highlightedWordsSignalMap.set(messageId, signal([]));
1373
- }
1374
- if (!this.highlightedWords$Map.has(messageId)) {
1375
- this.highlightedWords$Map.set(messageId, new BehaviorSubject([]));
1376
- }
1377
- // Create cleanup subject
1378
- const cleanup$ = new Subject();
1379
- this.cleanup$Map.set(messageId, cleanup$);
1380
- // Store the active audio element
1381
- this.activeAudioMap.set(messageId, audioElement);
1382
- // Get the signal and subject for this message
1383
- const messageSignal = this.highlightedWordsSignalMap.get(messageId);
1384
- const messageSubject = this.highlightedWords$Map.get(messageId);
1385
- // Initialize the highlighted words state
1386
- const initialWords = transcriptionTimestamps.map((word, index) => ({
1387
- word: word.word,
1388
- index,
1389
- isHighlighted: false,
1390
- }));
1391
- // Update both signal and observable
1392
- messageSignal.set(initialWords);
1393
- messageSubject.next(initialWords);
1394
- // Listen to timeupdate events
1395
- fromEvent(audioElement, 'timeupdate')
1396
- .pipe(takeUntilDestroyed(this.destroyRef), takeUntil(cleanup$), map(() => audioElement.currentTime))
1397
- .subscribe((currentTime) => {
1398
- const updatedWords = transcriptionTimestamps.map((word, index) => {
1399
- const isHighlighted = currentTime >= word.start - 0.15 && currentTime < word.end + 0.15;
1400
- return {
1401
- word: word.word,
1402
- index,
1403
- isHighlighted,
1404
- };
1405
- });
1406
- // Update both signal and observable for this message
1407
- messageSignal.set(updatedWords);
1408
- messageSubject.next(updatedWords);
1409
- });
1410
- // Listen to ended event for cleanup
1411
- fromEvent(audioElement, 'ended')
1412
- .pipe(takeUntilDestroyed(this.destroyRef), takeUntil(cleanup$))
1413
- .subscribe(() => {
1414
- // Reset highlighting when audio ends
1415
- const resetWords = initialWords.map((word) => ({
1416
- ...word,
1417
- isHighlighted: false,
1418
- }));
1419
- messageSignal.set(resetWords);
1420
- messageSubject.next(resetWords);
1421
- });
1422
- }
1423
- /**
1424
- * Stops the sync for a specific message and cleans up resources
1425
- * @param messageId The ID of the message to stop syncing
1426
- */
1427
- stopSync(messageId) {
1428
- if (this.activeAudioMap.has(messageId)) {
1429
- // Get the cleanup subject for this message
1430
- const cleanup$ = this.cleanup$Map.get(messageId);
1431
- if (cleanup$) {
1432
- cleanup$.next();
1433
- cleanup$.complete();
1434
- this.cleanup$Map.delete(messageId);
1435
- }
1436
- this.activeAudioMap.delete(messageId);
1437
- // Reset state for this message
1438
- const messageSignal = this.highlightedWordsSignalMap.get(messageId);
1439
- const messageSubject = this.highlightedWords$Map.get(messageId);
1440
- if (messageSignal) {
1441
- messageSignal.set([]);
1442
- }
1443
- if (messageSubject) {
1444
- messageSubject.next([]);
1445
- }
1446
- }
1415
+ handleAudioRecorded(event) {
1416
+ console.log(event);
1417
+ this.onMicFinished(event.blob);
1447
1418
  }
1448
- /**
1449
- * Stops all syncs and cleans up all resources
1450
- */
1451
- stopAllSyncs() {
1452
- // Get all message IDs and stop each sync
1453
- for (const messageId of this.activeAudioMap.keys()) {
1454
- this.stopSync(messageId);
1419
+ async onMicFinished(eventBlob) {
1420
+ if (!(eventBlob instanceof Blob)) {
1421
+ return;
1455
1422
  }
1456
- // Clear all maps
1457
- this.highlightedWordsSignalMap.clear();
1458
- this.highlightedWords$Map.clear();
1459
- this.cleanup$Map.clear();
1460
- this.activeAudioMap.clear();
1461
- }
1462
- /**
1463
- * Returns the highlighted words signal for a specific message
1464
- * @param messageId The ID of the message
1465
- */
1466
- getHighlightedWordsSignal(messageId) {
1467
- // Create a new signal for this message if it doesn't exist
1468
- if (!this.highlightedWordsSignalMap.has(messageId)) {
1469
- this.highlightedWordsSignalMap.set(messageId, signal([]));
1423
+ this.isUserTalking = false;
1424
+ this.isGettingTranscription = true;
1425
+ try {
1426
+ // Get transcription from audio
1427
+ const transcription = await this.agentCardService.getAudioTranscriptions(eventBlob, null);
1428
+ const message = {
1429
+ content: '',
1430
+ role: ChatRole.User,
1431
+ audioUrl: URL.createObjectURL(eventBlob),
1432
+ };
1433
+ if (transcription) {
1434
+ message.content = transcription.text;
1435
+ message.transcription = transcription;
1436
+ }
1437
+ // Send message to conversation
1438
+ // The evaluation will happen automatically in the conversation service
1439
+ await this.conversationService.sendUserMessage(message);
1470
1440
  }
1471
- return this.highlightedWordsSignalMap.get(messageId);
1472
- }
1473
- /**
1474
- * Returns the highlighted words observable for a specific message
1475
- * @param messageId The ID of the message
1476
- */
1477
- getHighlightedWords$(messageId) {
1478
- // Create a new subject for this message if it doesn't exist
1479
- if (!this.highlightedWords$Map.has(messageId)) {
1480
- this.highlightedWords$Map.set(messageId, new BehaviorSubject([]));
1441
+ finally {
1442
+ this.isGettingTranscription = false;
1481
1443
  }
1482
- return this.highlightedWords$Map.get(messageId).asObservable();
1483
- }
1484
- /**
1485
- * Checks if a word at a specific index is currently highlighted for a specific message
1486
- * @param messageId The ID of the message
1487
- * @param index The index of the word to check
1488
- */
1489
- isWordHighlighted(messageId, index) {
1490
- const messageSignal = this.getHighlightedWordsSignal(messageId);
1491
- return messageSignal().some((word) => word.index === index && word.isHighlighted);
1492
- }
1493
- /**
1494
- * Returns an observable that emits true when a word at a specific index is highlighted for a specific message
1495
- * @param messageId The ID of the message
1496
- * @param index The index of the word to observe
1497
- */
1498
- isWordHighlighted$(messageId, index) {
1499
- return this.getHighlightedWords$(messageId).pipe(map((words) => words.some((word) => word.index === index && word.isHighlighted)));
1500
1444
  }
1501
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AudioTextSyncService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1502
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AudioTextSyncService, providedIn: 'root' }); }
1445
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1446
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.4", type: ChatFooterComponent, isStandalone: true, selector: "dc-chat-footer", inputs: { isAIThinking: { classPropertyName: "isAIThinking", publicName: "isAIThinking", isSignal: true, isRequired: false, transformFunction: null }, evaluatorAgentCard: { classPropertyName: "evaluatorAgentCard", publicName: "evaluatorAgentCard", isSignal: true, isRequired: false, transformFunction: null }, micSettings: { classPropertyName: "micSettings", publicName: "micSettings", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sendMessage: "sendMessage", textInputChanged: "textInputChanged" }, ngImport: i0, template: "<div class=\"progress-input\">\n <div class=\"input-container\">\n <app-mic-vad (audioRecorded)=\"handleAudioRecorded($event)\" />\n <!-- <dc-mic\n style=\"display: flex; align-items: center\"\n (onInterpretedText)=\"setInputText($event)\"\n (onFinished)=\"micFinished($event)\"\n [micSettings]=\"micSettings()\"></dc-mic> -->\n\n <textarea pTextarea [formControl]=\"chatInputControl\" (keyup.enter)=\"sendUserMessage()\" rows=\"1\"></textarea>\n\n <p-button (click)=\"sendUserMessage()\" [disabled]=\"isAIThinking() || !chatInputControl.value\" label=\"Enviar\" [rounded]=\"true\" />\n </div>\n\n <div>\n <p-progressbar showValue=\"false\" [value]=\"score()\" [style]=\"{ height: '6px' }\" />\n </div>\n</div>\n", styles: [".progress-input{padding:10px;background-color:#f5f5f545;border-top:1px solid #b1a8a8}.progress-input .input-container{display:flex;align-items:center;margin-bottom:5px}.progress-input .input-container textarea{flex:1;resize:none;margin:0 10px}.progress-input .input-container .send-button{background-color:#007bff;color:#fff;border:none;border-radius:4px;padding:8px 15px;cursor:pointer}.progress-input .input-container .send-button:disabled{background-color:#ccc;cursor:not-allowed}.progress-input .input-container .send-button:hover:not(:disabled){background-color:#0069d9}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: ProgressBarModule }, { kind: "component", type: i2.ProgressBar, selector: "p-progressBar, p-progressbar, p-progress-bar", inputs: ["value", "showValue", "styleClass", "valueStyleClass", "style", "unit", "mode", "color"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i3.Textarea, selector: "[pTextarea]", inputs: ["autoResize", "variant", "fluid", "pSize"], outputs: ["onResize"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: MicVadComponent, selector: "app-mic-vad", inputs: ["buttonLabel", "recordingLabel", "listeningLabel", "buttonIcon", "recordingIcon", "buttonClass"], outputs: ["recordingStarted", "recordingStopped", "speechDetected", "speechEnded", "audioRecorded", "error"] }] }); }
1503
1447
  }
1504
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AudioTextSyncService, decorators: [{
1505
- type: Injectable,
1506
- args: [{
1507
- providedIn: 'root',
1508
- }]
1509
- }], ctorParameters: () => [] });
1448
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatFooterComponent, decorators: [{
1449
+ type: Component,
1450
+ args: [{ selector: 'dc-chat-footer', standalone: true, imports: [ReactiveFormsModule, ProgressBarModule, DCMicComponent, TextareaModule, ButtonModule, MicVadComponent], template: "<div class=\"progress-input\">\n <div class=\"input-container\">\n <app-mic-vad (audioRecorded)=\"handleAudioRecorded($event)\" />\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"] }]
1451
+ }] });
1510
1452
 
1511
1453
  const ICONS = {
1512
1454
  chat: `<svg viewBox="0 0 24 24" fill="currentColor">
@@ -1585,40 +1527,48 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
1585
1527
  `, 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"] }]
1586
1528
  }], ctorParameters: () => [] });
1587
1529
 
1588
- /**
1589
- * Standalone component for audio-text synchronization
1590
- * This component can work independently from the chat component
1591
- */
1592
- class StandaloneAudioTextSyncComponent {
1530
+ class TextHighlighterComponent {
1593
1531
  constructor() {
1594
- this.message = input(undefined); // Or input.required<MessageAudio>() if always expected
1532
+ this.message = input.required(); // Or input.required<MessageAudio>() if always expected
1595
1533
  this.highlightedWords = signal([]); // Signal for highlighted words
1596
- this.isLoading = computed(() => this.message()?.isLoading);
1597
- this.shouldPlayAudio = computed(() => {
1598
- debugger;
1599
- const shouldPlay = !!this.message() && this.message()?.shouldPlayAudio;
1600
- return shouldPlay;
1534
+ this.isPlaying = signal(false); // Signal for play state
1535
+ // Signals State computed from the input message
1536
+ this.iconState = computed(() => {
1537
+ if (this.isLoading()) {
1538
+ return 'isLoading';
1539
+ }
1540
+ else if (this.isPlaying()) {
1541
+ return 'isPlaying';
1542
+ }
1543
+ else if (this.message()?.audioUrl) {
1544
+ return 'playable';
1545
+ }
1546
+ else {
1547
+ return 'idle';
1548
+ }
1601
1549
  });
1602
- this.playAudio = output();
1603
- this.audioCompleted = output();
1604
- this.audioElement = null; // Audio element for playback
1605
- this.destroy$ = new Subject(); // Cleanup subject
1606
- // Inject services
1607
- this.cdr = inject(ChangeDetectorRef); // Keep for now, might remove if template fully signal-driven
1608
- this.destroyRef = inject(DestroyRef);
1550
+ this.isLoading = computed(() => this.message()?.isLoading);
1551
+ this.shouldPlayAudio = computed(() => !!this.message() && this.message()?.shouldPlayAudio);
1609
1552
  // Computed signal for message text
1610
1553
  this.messageText = computed(() => {
1611
1554
  const msg = this.message(); // Read the input signal
1612
- return msg?.content || msg?.text || '';
1555
+ return msg?.text || msg?.content || 'N/A';
1613
1556
  });
1557
+ this.classTag = computed(() => this.message()?.tag);
1614
1558
  // Computed signal for transcription availability
1615
1559
  this.hasTranscription = computed(() => {
1616
1560
  const hasTranscription = !!this.message()?.transcriptionTimestamps && this.message()?.transcriptionTimestamps.length > 0;
1617
1561
  return hasTranscription;
1618
1562
  });
1563
+ this.playAudio = output();
1564
+ this.audioCompleted = output();
1565
+ this.audioElement = null; // Audio element for playback
1566
+ this.destroy$ = new Subject(); // Cleanup localsubject
1567
+ // Inject services
1568
+ this.cdr = inject(ChangeDetectorRef); // Keep for now, might remove if template fully signal-driven
1569
+ this.destroyRef = inject(DestroyRef);
1619
1570
  // Effect to react to message changes and re-initialize
1620
1571
  effect(() => {
1621
- debugger;
1622
1572
  const currentMsg = this.message(); // Read the input signal
1623
1573
  console.log('Input message signal changed:', currentMsg);
1624
1574
  if (currentMsg) {
@@ -1653,30 +1603,27 @@ class StandaloneAudioTextSyncComponent {
1653
1603
  this.destroy$.complete();
1654
1604
  this.cleanupAudio();
1655
1605
  }
1656
- // This method is called by the effect when the message signal changes
1657
1606
  initializeBasedOnMessage(msg) {
1658
1607
  // Initialize or cleanup audio based on URL presence and change
1659
1608
  if (msg.audioUrl && (!this.audioElement || this.audioElement.src !== msg.audioUrl)) {
1660
1609
  this.initializeAudio(msg.audioUrl); // Pass URL
1661
1610
  }
1662
1611
  else if (!msg.audioUrl && this.audioElement) {
1663
- // Cleanup if URL removed and element exists
1664
1612
  this.cleanupAudio();
1665
1613
  }
1614
+ else if (!msg.audioUrl) {
1615
+ // No audio URL and no audio element - just log a warning instead of throwing an error
1616
+ console.warn('No audioUrl provided in the message. Audio playback will not be available.');
1617
+ }
1666
1618
  // Initialize words and sync based on transcription presence
1667
1619
  if (this.hasTranscription()) {
1668
1620
  // Use computed signal
1669
1621
  const timestamps = msg.transcriptionTimestamps || [];
1670
1622
  this.initializeHighlightedWords(timestamps); // Pass timestamps
1671
- // Ensure audio sync setup happens if audio element exists
1672
- if (this.audioElement) {
1673
- this.setupAudioSync(timestamps); // Pass timestamps
1674
- }
1623
+ this.subcribeToAudioSync(timestamps); // Pass timestamps
1675
1624
  }
1676
1625
  else {
1677
- // No transcription, clear words and potentially stop sync listeners
1678
- this.highlightedWords.set([]);
1679
- this.destroy$.next(); // Signal existing listeners to stop
1626
+ this.subscribeToEndAudio();
1680
1627
  }
1681
1628
  }
1682
1629
  /**
@@ -1701,7 +1648,7 @@ class StandaloneAudioTextSyncComponent {
1701
1648
  /**
1702
1649
  * Set up audio synchronization with text
1703
1650
  */
1704
- setupAudioSync(timestamps) {
1651
+ subcribeToAudioSync(timestamps) {
1705
1652
  // Accept timestamps
1706
1653
  if (!this.audioElement) {
1707
1654
  // Guard against missing audio element
@@ -1733,6 +1680,8 @@ class StandaloneAudioTextSyncComponent {
1733
1680
  isHighlighted: false,
1734
1681
  }));
1735
1682
  this.highlightedWords.set(resetWords);
1683
+ // Set isPlaying to false when audio finishes
1684
+ this.isPlaying.set(false);
1736
1685
  // Emit audio completed event with the current message
1737
1686
  const currentMsg = this.message();
1738
1687
  if (currentMsg) {
@@ -1742,6 +1691,23 @@ class StandaloneAudioTextSyncComponent {
1742
1691
  }
1743
1692
  });
1744
1693
  }
1694
+ subscribeToEndAudio() {
1695
+ if (!this.audioElement) {
1696
+ return;
1697
+ }
1698
+ fromEvent(this.audioElement, 'ended')
1699
+ .pipe(takeUntilDestroyed(this.destroyRef), takeUntil(this.destroy$))
1700
+ .subscribe(() => {
1701
+ // Set isPlaying to false when audio finishes
1702
+ this.isPlaying.set(false);
1703
+ // Emit audio completed event with the current message
1704
+ const currentMsg = this.message();
1705
+ if (currentMsg) {
1706
+ currentMsg.shouldPlayAudio = false;
1707
+ this.audioCompleted.emit(currentMsg);
1708
+ }
1709
+ });
1710
+ }
1745
1711
  /**
1746
1712
  * Clean up audio element and event listeners
1747
1713
  */
@@ -1763,6 +1729,7 @@ class StandaloneAudioTextSyncComponent {
1763
1729
  * Play or pause the audio
1764
1730
  */
1765
1731
  onPlayMessage() {
1732
+ console.log('Playing message audio', this.message().messageId);
1766
1733
  const currentMsg = this.message(); // Read signal
1767
1734
  if (this.audioElement) {
1768
1735
  if (this.audioElement.paused) {
@@ -1778,6 +1745,7 @@ class StandaloneAudioTextSyncComponent {
1778
1745
  // For simplicity, let's re-call initializeAudio here if needed,
1779
1746
  // though ideally the effect handles it.
1780
1747
  this.initializeAudio(currentMsg.audioUrl); // Ensure it's created if somehow missed
1748
+ // this.initializeBasedOnMessage(currentMsg);
1781
1749
  this.startAudioPlayback();
1782
1750
  }
1783
1751
  else if (currentMsg) {
@@ -1795,363 +1763,228 @@ class StandaloneAudioTextSyncComponent {
1795
1763
  this.audioElement.play().catch((error) => {
1796
1764
  console.error('Error playing audio:', error);
1797
1765
  });
1766
+ this.isPlaying.set(true); // Set play state to true
1798
1767
  }
1799
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: StandaloneAudioTextSyncComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1800
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: StandaloneAudioTextSyncComponent, isStandalone: true, selector: "dc-standalone-audio-text-sync", inputs: { message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { playAudio: "playAudio", audioCompleted: "audioCompleted" }, ngImport: i0, template: "<div class=\"audio-text-sync-container\">\n <!-- Icon for play/loading status -->\n <div>\n <!-- <span> lo: {{ isLoading() }} tr: {{ hasTranscription() }} play: {{ shouldPlayAudio() }} </span> -->\n </div>\n <i (click)=\"onPlayMessage()\" class=\"play-button\">\n @if (isLoading()) {\n <!-- <dc-icon class=\"spin-animation\" name=\"loading\"></dc-icon> -->\n <i class=\"spin-animation pi pi-spinner-dotted\"></i>\n\n } @else {\n <dc-icon name=\"play\"></dc-icon>\n }\n </i>\n\n <!-- Display transcription with highlighting -->\n <div class=\"text-content\">\n @if (hasTranscription()) { @for (wordState of highlightedWords(); track trackByIndex($index, wordState)) {\n <span [class.highlight]=\"wordState.isHighlighted\">{{ wordState.word }}&nbsp;</span>\n } } @else {\n <!-- Display regular text content when no transcription is available -->\n <span [innerHtml]=\"messageText()\"></span>\n }\n </div>\n</div>\n", styles: [":host{display:block}.audio-text-sync-container{display:flex;align-items:flex-start;gap:8px}.play-button{cursor:pointer;display:flex;align-items:center;justify-content:center;min-width:24px}.play-button:hover{opacity:.8}.text-content{flex:1}.highlight{background-color:#3cd8ff;border-radius:4px}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "component", type: IconsComponent, selector: "dc-icon", inputs: ["name", "size", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1768
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: TextHighlighterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1769
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: TextHighlighterComponent, isStandalone: true, selector: "dc-text-highlighter", inputs: { message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { playAudio: "playAudio", audioCompleted: "audioCompleted" }, ngImport: i0, template: "<div class=\"audio-text-sync-container\">\n <div> </div>\n <i (click)=\"onPlayMessage()\" class=\"play-button\">\n @if (iconState() === 'isLoading') {\n <!-- <dc-icon class=\"spin-animation\" name=\"loading\"></dc-icon> -->\n <i class=\"spin-animation pi pi-spinner-dotted\"></i>\n } @else if (iconState() === 'isPlaying') {\n <i class=\"pi pi-volume-up\"></i>\n } @else if (iconState() === 'playable') {\n <!-- Display play icon when not playing -->\n <dc-icon name=\"play\"></dc-icon>\n } @else {\n <!-- Nothing-->\n <!-- <dc-icon name=\"play\"></dc-icon> -->\n }\n </i>\n\n <!-- Display transcription with highlighting -->\n <div [class]=\"'text-content ' + classTag()\">\n @if (hasTranscription()) { @for (wordState of highlightedWords(); track trackByIndex($index, wordState)) {\n <span [class.highlight]=\"wordState.isHighlighted\">{{ wordState.word }}&nbsp;</span>\n } } @else {\n <!-- Display regular text content when no transcription is available -->\n <span> {{ messageText() }}</span>\n }\n </div>\n</div>\n", styles: [":host{display:block}.audio-text-sync-container{display:flex;align-items:flex-start;gap:2px;align-items:center}.play-button{cursor:pointer;display:flex;align-items:center;justify-content:center;min-width:24px;margin-top:4px}.play-button:hover{opacity:.8}.text-content{flex:1}.highlight{background-color:#3cd8ff;border-radius:4px}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.em{font-style:italic;color:#6495ed}.strong{font-weight:700;color:inherit}.em_strong{font-weight:700;font-style:italic;color:inherit}\n"], dependencies: [{ kind: "component", type: IconsComponent, selector: "dc-icon", inputs: ["name", "size", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1801
1770
  }
1802
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: StandaloneAudioTextSyncComponent, decorators: [{
1771
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: TextHighlighterComponent, decorators: [{
1803
1772
  type: Component,
1804
- args: [{ selector: 'dc-standalone-audio-text-sync', standalone: true, imports: [IconsComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"audio-text-sync-container\">\n <!-- Icon for play/loading status -->\n <div>\n <!-- <span> lo: {{ isLoading() }} tr: {{ hasTranscription() }} play: {{ shouldPlayAudio() }} </span> -->\n </div>\n <i (click)=\"onPlayMessage()\" class=\"play-button\">\n @if (isLoading()) {\n <!-- <dc-icon class=\"spin-animation\" name=\"loading\"></dc-icon> -->\n <i class=\"spin-animation pi pi-spinner-dotted\"></i>\n\n } @else {\n <dc-icon name=\"play\"></dc-icon>\n }\n </i>\n\n <!-- Display transcription with highlighting -->\n <div class=\"text-content\">\n @if (hasTranscription()) { @for (wordState of highlightedWords(); track trackByIndex($index, wordState)) {\n <span [class.highlight]=\"wordState.isHighlighted\">{{ wordState.word }}&nbsp;</span>\n } } @else {\n <!-- Display regular text content when no transcription is available -->\n <span [innerHtml]=\"messageText()\"></span>\n }\n </div>\n</div>\n", styles: [":host{display:block}.audio-text-sync-container{display:flex;align-items:flex-start;gap:8px}.play-button{cursor:pointer;display:flex;align-items:center;justify-content:center;min-width:24px}.play-button:hover{opacity:.8}.text-content{flex:1}.highlight{background-color:#3cd8ff;border-radius:4px}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n"] }]
1773
+ args: [{ selector: 'dc-text-highlighter', standalone: true, imports: [IconsComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"audio-text-sync-container\">\n <div> </div>\n <i (click)=\"onPlayMessage()\" class=\"play-button\">\n @if (iconState() === 'isLoading') {\n <!-- <dc-icon class=\"spin-animation\" name=\"loading\"></dc-icon> -->\n <i class=\"spin-animation pi pi-spinner-dotted\"></i>\n } @else if (iconState() === 'isPlaying') {\n <i class=\"pi pi-volume-up\"></i>\n } @else if (iconState() === 'playable') {\n <!-- Display play icon when not playing -->\n <dc-icon name=\"play\"></dc-icon>\n } @else {\n <!-- Nothing-->\n <!-- <dc-icon name=\"play\"></dc-icon> -->\n }\n </i>\n\n <!-- Display transcription with highlighting -->\n <div [class]=\"'text-content ' + classTag()\">\n @if (hasTranscription()) { @for (wordState of highlightedWords(); track trackByIndex($index, wordState)) {\n <span [class.highlight]=\"wordState.isHighlighted\">{{ wordState.word }}&nbsp;</span>\n } } @else {\n <!-- Display regular text content when no transcription is available -->\n <span> {{ messageText() }}</span>\n }\n </div>\n</div>\n", styles: [":host{display:block}.audio-text-sync-container{display:flex;align-items:flex-start;gap:2px;align-items:center}.play-button{cursor:pointer;display:flex;align-items:center;justify-content:center;min-width:24px;margin-top:4px}.play-button:hover{opacity:.8}.text-content{flex:1}.highlight{background-color:#3cd8ff;border-radius:4px}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.em{font-style:italic;color:#6495ed}.strong{font-weight:700;color:inherit}.em_strong{font-weight:700;font-style:italic;color:inherit}\n"] }]
1805
1774
  }], ctorParameters: () => [] });
1806
1775
 
1807
- class MultiMessageContentComponent {
1776
+ class MessageOrchestratorComponent {
1808
1777
  constructor() {
1778
+ this.agentCardService = inject(CONVERSATION_AI_TOKEN);
1779
+ this.conversationService = inject(ConversationService);
1809
1780
  this.messages = input.required();
1810
- this.isLoading = input(false);
1781
+ this.messageRole = input.required();
1782
+ this.messagesSignal = signal([]);
1811
1783
  this.playAudio = output();
1812
- // Track current playing message index
1813
- this.currentPlayingIndex = -1;
1814
- }
1815
- /**
1816
- * Checks if a message has transcription timestamps
1817
- * @param message The message to check
1818
- */
1819
- hasTranscription(message) {
1820
- //
1821
- return !!message.transcriptionTimestamps;
1784
+ this.audioCompleted = output();
1785
+ // Audio queue management
1786
+ this.audioQueue = [];
1787
+ this.isGenerating = false;
1788
+ this.currentPlayingIndex = null;
1789
+ this.preGenerationInProgress = false;
1822
1790
  }
1823
- /**
1824
- * Emits the playAudio event with the selected message and starts sequential playback
1825
- * @param message The message to play
1826
- */
1827
- onPlayMessage(message) {
1828
- // Find the index of the message to play
1829
- const index = this.messages().findIndex((msg) => msg === message || msg.messageId === message.messageId);
1830
- if (index >= 0) {
1831
- this.startSequentialPlayback(index);
1791
+ ngOnInit() {
1792
+ if (this.messageRole() === ChatRole.Assistant) {
1793
+ console.log('MessageOrchestratorComponent initialized', this.messages());
1794
+ // Initialize the queue with all message indices
1795
+ this.initializeAudioQueue();
1796
+ // Start processing the queue - generate the first audio
1797
+ this.processNextInQueue();
1798
+ }
1799
+ else {
1800
+ // since user only have one message, just activate signal
1801
+ this.changeStates(0, this.messages()[0]);
1832
1802
  }
1833
- // Also emit to parent if needed
1834
- this.playAudio.emit(message);
1835
1803
  }
1836
- /**
1837
- * Method to start playing the sequence
1838
- * @param startIndex Index of the message to start playing from
1839
- */
1840
- startSequentialPlayback(startIndex = 0) {
1841
- this.currentPlayingIndex = startIndex;
1804
+ // Initialize the queue with all message indices
1805
+ initializeAudioQueue() {
1842
1806
  const messages = this.messages();
1843
- if (messages && messages.length > startIndex) {
1844
- // Reset all messages
1845
- messages.forEach((msg) => (msg.shouldPlayAudio = false));
1846
- // Set the current message to play
1847
- messages[startIndex].shouldPlayAudio = true;
1807
+ if (messages && messages.length > 0) {
1808
+ this.audioQueue = messages.map((_, index) => index);
1848
1809
  }
1849
1810
  }
1850
- /**
1851
- * Handle audio completion from child component
1852
- * @param message The message that completed playback
1853
- */
1854
- onAudioCompleted(message) {
1855
- // Find the index of the completed message
1856
- const completedIndex = this.messages().findIndex((msg) => msg === message || msg.messageId === message.messageId);
1857
- if (completedIndex >= 0 && completedIndex < this.messages().length - 1) {
1858
- // Reset current message
1859
- const messages = this.messages();
1860
- messages[completedIndex].shouldPlayAudio = false;
1861
- // Play the next message
1862
- this.currentPlayingIndex = completedIndex + 1;
1863
- messages[this.currentPlayingIndex].shouldPlayAudio = true;
1811
+ // Process the next message in the queue
1812
+ async processNextInQueue() {
1813
+ console.log('Processing next message in queue', this.audioQueue);
1814
+ if (this.audioQueue.length === 0 || this.isGenerating) {
1815
+ return;
1864
1816
  }
1865
- else {
1866
- // All messages played or message not found
1867
- this.currentPlayingIndex = -1;
1817
+ // Get the next index from the queue
1818
+ const nextIndex = this.audioQueue.shift();
1819
+ // Start generating
1820
+ this.isGenerating = true;
1821
+ // Generate audio for the current message
1822
+ await this.generateAudioForIndex(nextIndex);
1823
+ // After generation, mark as not generating
1824
+ this.isGenerating = false;
1825
+ // If there's another message in the queue, pre-generate it
1826
+ this.preGenerateNextIfNeeded();
1827
+ }
1828
+ // Pre-generate the next audio if available
1829
+ async preGenerateNextIfNeeded() {
1830
+ if (this.audioQueue.length > 0 && !this.preGenerationInProgress) {
1831
+ const nextIndex = this.audioQueue[0]; // Peek at next index but don't remove
1832
+ // Mark pre-generation as in progress
1833
+ this.preGenerationInProgress = true;
1834
+ // Mark as loading but don't set shouldPlayAudio yet
1835
+ const messages = this.messages();
1836
+ const message = { ...messages[nextIndex], isLoading: true };
1837
+ this.changeStates(nextIndex, message);
1838
+ // Generate in background
1839
+ const messageAudio = await this.generateAudio(messages[nextIndex], null);
1840
+ messageAudio.isLoading = false;
1841
+ messageAudio.shouldPlayAudio = false; // Don't auto-play yet
1842
+ this.changeStates(nextIndex, messageAudio);
1843
+ // Reset pre-generation flag
1844
+ this.preGenerationInProgress = false;
1845
+ }
1846
+ }
1847
+ // Generate audio for a specific index
1848
+ async generateAudioForIndex(index) {
1849
+ const messages = this.messages();
1850
+ // Check if this message was already pre-generated (has audioUrl)
1851
+ if (messages[index].audioUrl) {
1852
+ // If it was pre-generated, just set it to play
1853
+ const messageAudio = { ...messages[index], shouldPlayAudio: true };
1854
+ this.changeStates(index, messageAudio);
1855
+ this.currentPlayingIndex = index;
1856
+ return;
1868
1857
  }
1858
+ // Otherwise generate it now
1859
+ const message = { ...messages[index], isLoading: true };
1860
+ this.changeStates(index, message);
1861
+ const messageAudio = await this.generateAudio(messages[index], null);
1862
+ messageAudio.isLoading = false;
1863
+ messageAudio.shouldPlayAudio = true;
1864
+ this.changeStates(index, messageAudio);
1865
+ this.currentPlayingIndex = index;
1869
1866
  }
1870
- ngOnInit() {
1871
- console.log(this.messages());
1872
- }
1873
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MultiMessageContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1874
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: MultiMessageContentComponent, isStandalone: true, selector: "dc-multi-message-content", inputs: { messages: { classPropertyName: "messages", publicName: "messages", isSignal: true, isRequired: true, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { playAudio: "playAudio" }, ngImport: i0, template: "@for (message of messages(); track message) {\n <!-- Message with transcription -->\n @if (hasTranscription(message)) {\n <dc-standalone-audio-text-sync [message]=\"message\" (playAudio)=\"onPlayMessage(message)\" (audioCompleted)=\"onAudioCompleted(message)\"></dc-standalone-audio-text-sync>\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 @if (isLoading) {\n <dc-icon class=\"spin-animation\" name=\"loading\"></dc-icon>\n }\n @if (!isLoading) {\n <dc-icon name=\"play\"></dc-icon>\n }\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: "component", type: IconsComponent, selector: "dc-icon", inputs: ["name", "size", "color"] }, { kind: "component", type: StandaloneAudioTextSyncComponent, selector: "dc-standalone-audio-text-sync", inputs: ["message"], outputs: ["playAudio", "audioCompleted"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1875
- }
1876
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MultiMessageContentComponent, decorators: [{
1877
- type: Component,
1878
- args: [{ selector: 'dc-multi-message-content', standalone: true, imports: [IconsComponent, StandaloneAudioTextSyncComponent, CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "@for (message of messages(); track message) {\n <!-- Message with transcription -->\n @if (hasTranscription(message)) {\n <dc-standalone-audio-text-sync [message]=\"message\" (playAudio)=\"onPlayMessage(message)\" (audioCompleted)=\"onAudioCompleted(message)\"></dc-standalone-audio-text-sync>\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 @if (isLoading) {\n <dc-icon class=\"spin-animation\" name=\"loading\"></dc-icon>\n }\n @if (!isLoading) {\n <dc-icon name=\"play\"></dc-icon>\n }\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"] }]
1879
- }], ctorParameters: () => [] });
1880
-
1881
- function matchTranscription(originalText, transcription) {
1882
- const result = [];
1883
- const transcriptionMap = new Map();
1884
- // Create a map of lowercase words to an array of their corresponding objects in transcription
1885
- for (const obj of transcription) {
1886
- const lowercaseWord = obj.word.trim().toLowerCase();
1887
- if (transcriptionMap.has(lowercaseWord)) {
1888
- transcriptionMap.get(lowercaseWord).push(obj);
1889
- }
1890
- else {
1891
- transcriptionMap.set(lowercaseWord, [obj]);
1867
+ // Handle audio completion event from text-highlighter
1868
+ onAudioCompleted(message) {
1869
+ // Forward the event
1870
+ this.audioCompleted.emit(message);
1871
+ // Reset current playing index
1872
+ this.currentPlayingIndex = null;
1873
+ // If there are more messages in the queue, process the next one
1874
+ if (this.audioQueue.length > 0) {
1875
+ this.processNextInQueue();
1892
1876
  }
1893
1877
  }
1894
- // Split the original text into an array of words
1895
- const words = originalText.split(' ');
1896
- let currentTime = 0;
1897
- for (const word of words) {
1898
- const lowercaseWord = word.toLowerCase();
1899
- const matchedObjs = transcriptionMap.get(lowercaseWord);
1900
- if (matchedObjs && matchedObjs.length > 0) {
1901
- const matchedObj = matchedObjs.shift();
1902
- result.push({
1903
- word: word,
1904
- start: Number(matchedObj.start.toFixed(3)),
1905
- end: Number(matchedObj.end.toFixed(3)),
1906
- });
1907
- currentTime = matchedObj.end;
1878
+ changeStates(index, messageAudio) {
1879
+ const messages = this.messages();
1880
+ messages[index] = { ...messages[index], ...messageAudio };
1881
+ this.messagesSignal.set([...messages]);
1882
+ }
1883
+ async generateAudio(message, overwriteText = null) {
1884
+ try {
1885
+ const text = overwriteText || message.text || message.content;
1886
+ const ttsObject = buildObjectTTSRequest({ ...message, text }, { highlightWords: true });
1887
+ const speechAudio = await this.conversationService.getTTSFile(ttsObject);
1888
+ message = extractAudioAndTranscription(message, speechAudio);
1889
+ return message;
1908
1890
  }
1909
- else {
1910
- result.push({
1911
- word: word,
1912
- start: Number(currentTime.toFixed(3)),
1913
- end: Number(currentTime.toFixed(3)),
1914
- });
1891
+ finally {
1892
+ // No longer emitting audioCompleted here as it's handled by the text-highlighter
1915
1893
  }
1916
1894
  }
1917
- return result;
1918
- }
1919
- function extractAudioAndTranscription(message, audio) {
1920
- message.audioUrl = audio.blobUrl;
1921
- message.transcription = audio.transcription;
1922
- if (message.transcription) {
1923
- message.transcriptionTimestamps = matchTranscription(message.text || message.content, message.transcription.words);
1924
- }
1925
- }
1926
- function buildObjectTTSRequest(message, settings = {}) {
1927
- const generateTranscription = settings?.highlightWords ?? false;
1928
- const speedRate = settings?.speedRate || 0;
1929
- const speed = typeof settings?.speed === 'string' ? settings.speed : AudioSpeed$1.Regular;
1930
- const text = removeEmojis(message.text || message.content);
1931
- return {
1932
- text,
1933
- voice: message.voice || settings.voice,
1934
- generateTranscription,
1935
- speedRate,
1936
- speed,
1937
- };
1895
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MessageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1896
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: MessageOrchestratorComponent, isStandalone: true, selector: "dc-message-orchestrator", inputs: { messages: { classPropertyName: "messages", publicName: "messages", isSignal: true, isRequired: true, transformFunction: null }, messageRole: { classPropertyName: "messageRole", publicName: "messageRole", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { playAudio: "playAudio", audioCompleted: "audioCompleted" }, ngImport: i0, template: "@for (message of messagesSignal(); track message.messageId) {\n<dc-text-highlighter [message]=\"message\" (playAudio)=\"playAudio.emit($event)\" (audioCompleted)=\"onAudioCompleted($event)\" />\n}\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "component", type: TextHighlighterComponent, selector: "dc-text-highlighter", inputs: ["message"], outputs: ["playAudio", "audioCompleted"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1938
1897
  }
1898
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MessageOrchestratorComponent, decorators: [{
1899
+ type: Component,
1900
+ args: [{ selector: 'dc-message-orchestrator', imports: [TextHighlighterComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@for (message of messagesSignal(); track message.messageId) {\n<dc-text-highlighter [message]=\"message\" (playAudio)=\"playAudio.emit($event)\" (audioCompleted)=\"onAudioCompleted($event)\" />\n}\n", styles: [":host{display:block}\n"] }]
1901
+ }] });
1939
1902
 
1940
1903
  class ChatMessageComponent {
1941
1904
  constructor() {
1942
- this.audioService = inject(AudioService);
1943
- this.agentCardService = inject(CONVERSATION_AI_TOKEN);
1944
- this.audioTextSyncService = inject(AudioTextSyncService);
1945
1905
  this.chatMessage = input.required();
1946
1906
  this.chatUserSettings = input(null);
1947
1907
  this.audioMessage = signal(null);
1948
- this.destroyRef = inject(DestroyRef);
1949
- this.conversationChatSettings = signal(null);
1950
- this.isLoading = signal(false);
1908
+ // Computed properties for easier access to signal values
1909
+ this.hasMultiMessages = computed(() => !!this.chatMessage()?.multiMessages);
1910
+ this.multiMessages = computed(() => this.chatMessage()?.multiMessages || []);
1911
+ this.messageTranslation = computed(() => this.chatMessage()?.translation);
1912
+ this.isUserMessage = computed(() => this.chatMessage()?.role === ChatRole.User);
1951
1913
  // Field initializer for the effect - runs during component creation
1952
1914
  this.messageEffect = effect(() => {
1953
1915
  const message = this.chatMessage();
1954
1916
  if (!message)
1955
1917
  return;
1956
- const settings$ = this.conversationChatSettings();
1957
- // Skip processing if settings aren't loaded yet
1958
- if (!settings$)
1959
- return;
1960
1918
  if (message.role === ChatRole.AssistantHelper) {
1961
1919
  return;
1962
1920
  }
1963
1921
  if (message.role === ChatRole.User) {
1964
- if (settings$.repeatRecording && settings$.synthVoice && message.audioUrl) {
1965
- this.playMessage(message);
1966
- }
1922
+ this.audioMessage.set({ ...message });
1967
1923
  }
1968
- else if (settings$.synthVoice) {
1969
- if (message.multiMessages) {
1970
- this.generateAndPlayAllAudios(message.multiMessages);
1924
+ else if (message.role === ChatRole.Assistant) {
1925
+ if (this.multiMessages().length > 0) {
1971
1926
  }
1972
1927
  else {
1973
- this.generateAndPlayAudio(message);
1928
+ this.audioMessage.set({ ...message });
1974
1929
  }
1975
1930
  }
1976
1931
  });
1977
- // Computed properties for easier access to signal values
1978
- this.messageRole = computed(() => this.chatMessage()?.role);
1979
- this.hasMultiMessages = computed(() => !!this.chatMessage()?.multiMessages);
1980
- this.multiMessages = computed(() => this.chatMessage()?.multiMessages || []);
1981
- this.messageContent = computed(() => this.chatMessage()?.content || '');
1982
- this.messageTranslation = computed(() => this.chatMessage()?.translation);
1983
- this.isUserMessage = computed(() => this.messageRole() === ChatRole.User);
1984
- this.isAssistantMessage = computed(() => this.messageRole() === ChatRole.Assistant);
1985
- this.isAssistantHelperMessage = computed(() => this.messageRole() === ChatRole.AssistantHelper);
1986
- }
1987
- async ngOnInit() {
1988
- // Load settings - the effect will react to this change
1989
- const settings = await this.agentCardService.getConversationUserChatSettings();
1990
- this.conversationChatSettings.set(settings);
1991
- // No need for effect here as it's now a field initializer
1992
- }
1993
- async generateAndPlayAudio(message, overwriteText = null) {
1994
- // this.isLoading.set(true);
1995
- this.audioMessage.set({ ...message, isLoading: true });
1996
- try {
1997
- const text = overwriteText || message.content;
1998
- const ttsObject = buildObjectTTSRequest({ ...message, text }, this.conversationChatSettings());
1999
- if (message.ssml) {
2000
- ttsObject.ssml = message.ssml;
2001
- }
2002
- const speechAudio = await this.agentCardService.getTextAudioFile(ttsObject);
2003
- extractAudioAndTranscription(message, speechAudio);
2004
- this.audioMessage.set({ ...message, isLoading: false, shouldPlayAudio: true });
2005
- // this.playMessage(message);
2006
- }
2007
- finally {
2008
- this.isLoading.set(false);
2009
- }
2010
- }
2011
- playMessage(message) {
2012
- debugger;
2013
- if (!message.audioUrl) {
2014
- return null;
2015
- }
2016
- const audioElement = this.audioService.playAudio(message.audioUrl);
2017
- message['audioHtml'] = audioElement;
2018
- // console.log('Playing message with transcription:', {
2019
- // hasTranscription: !!message.transcriptionTimestamps,
2020
- // transcriptionLength: message.transcriptionTimestamps?.length,
2021
- // audioUrl: message.audioUrl,
2022
- // audioElement,
2023
- // });
2024
- if (message.transcriptionTimestamps) {
2025
- // Generate a unique ID for this message
2026
- const messageId = this.generateMessageId(message);
2027
- console.log('Syncing audio with text:', {
2028
- messageId,
2029
- transcriptionTimestamps: message.transcriptionTimestamps,
2030
- wordCount: message.transcriptionTimestamps.length,
2031
- });
2032
- // Use the audio-text sync service instead of direct manipulation
2033
- // Pass the messageId to ensure each message has its own state
2034
- // this.audioTextSyncService.syncAudioWithText(audioElement, message.transcriptionTimestamps, messageId);
2035
- }
2036
- return audioElement;
2037
- }
2038
- /**
2039
- * Generate a unique ID for a message
2040
- * Uses message content/text and a timestamp to ensure uniqueness
2041
- */
2042
- generateMessageId(message) {
2043
- const messageContent = message.content || message.text || '';
2044
- // Use a safe approach to get an ID-like value since 'id' isn't guaranteed on these types
2045
- const messageId = message._id || message.id || '';
2046
- const timestamp = new Date().getTime();
2047
- return `msg_${messageId}_${messageContent.substring(0, 10).replace(/\s+/g, '_')}_${timestamp}`;
2048
- }
2049
- // This method is no longer needed as we're using the AudioTextSyncService
2050
- // It's kept here as a reference but can be removed
2051
- /*
2052
- private setupTranscriptionHighlighting(audioElement: HTMLAudioElement, transcriptionTimestamps: WordTimestamps[]): void {
2053
- // This functionality has been moved to AudioTextSyncService
2054
- }
2055
- */
2056
- generateAndPlayAllAudios(multiMessages) {
2057
- if (!multiMessages.length)
2058
- return;
2059
- let currentIndex = 0;
2060
- const playAudioSequentially = async () => {
2061
- if (currentIndex >= multiMessages.length) {
2062
- return;
2063
- }
2064
- const currentMessage = multiMessages[currentIndex];
2065
- currentMessage.isLoading = true;
2066
- try {
2067
- // Process current message audio if needed
2068
- if (!currentMessage.audioUrl) {
2069
- if (currentMessage.audioPromise) {
2070
- await currentMessage.audioPromise;
2071
- }
2072
- else {
2073
- const request = buildObjectTTSRequest(currentMessage, this.conversationChatSettings());
2074
- currentMessage.audioPromise = this.agentCardService.getTextAudioFile(request);
2075
- const audio = await currentMessage.audioPromise;
2076
- extractAudioAndTranscription(currentMessage, audio);
2077
- }
2078
- }
2079
- // Play the current message
2080
- const audioElement = this.playMessage(currentMessage);
2081
- if (audioElement) {
2082
- audioElement.addEventListener('ended', () => {
2083
- currentIndex++;
2084
- playAudioSequentially();
2085
- });
2086
- }
2087
- else {
2088
- // If playback failed, move to next message
2089
- currentIndex++;
2090
- playAudioSequentially();
2091
- }
2092
- // Preload next message audio
2093
- this.preloadNextMessageAudio(multiMessages, currentIndex);
2094
- }
2095
- finally {
2096
- currentMessage.isLoading = false;
2097
- }
2098
- };
2099
- // Start the sequential playback
2100
- playAudioSequentially();
2101
- }
2102
- preloadNextMessageAudio(messages, currentIndex) {
2103
- if (currentIndex + 1 < messages.length) {
2104
- const nextMessage = messages[currentIndex + 1];
2105
- if (!nextMessage.audioUrl && !nextMessage.audioPromise) {
2106
- const request = buildObjectTTSRequest(nextMessage, this.conversationChatSettings());
2107
- nextMessage.isLoading = true;
2108
- nextMessage.audioPromise = this.agentCardService.getTextAudioFile(request);
2109
- nextMessage.audioPromise.then((audio) => {
2110
- extractAudioAndTranscription(nextMessage, audio);
2111
- nextMessage.isLoading = false;
2112
- });
2113
- }
2114
- }
2115
1932
  }
2116
1933
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatMessageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2117
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: ChatMessageComponent, isStandalone: true, selector: "dc-chat-message", inputs: { chatMessage: { classPropertyName: "chatMessage", publicName: "chatMessage", isSignal: true, isRequired: true, transformFunction: null }, chatUserSettings: { classPropertyName: "chatUserSettings", publicName: "chatUserSettings", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"message-wrapper\" [ngClass]=\"{ 'user-message': isUserMessage(), 'assistant-message': !isUserMessage() }\">\n <div class=\"message-container\">\n <!-- Avatar for assistant messages -->\n\n @if (!isUserMessage()) {\n <div class=\"avatar-container\">\n <div class=\"avatar\">\n <span class=\"avatar-initial\">A</span>\n </div>\n </div>\n }\n\n <!-- Message content -->\n <div class=\"message-bubble\">\n @if (hasMultiMessages()) {\n <dc-multi-message-content [messages]=\"multiMessages()\" (playAudio)=\"playMessage($event)\"></dc-multi-message-content>\n } @else {\n <dc-standalone-audio-text-sync [message]=\"audioMessage()\"></dc-standalone-audio-text-sync>\n }\n\n <!-- Translation if available -->\n @if (messageTranslation()) {\n <div class=\"translation\">\n <hr class=\"divider\" />\n {{ messageTranslation() }}\n </div>\n }\n </div>\n\n <!-- Avatar for user messages -->\n @if (isUserMessage()) {\n <div class=\"avatar-container\">\n <div class=\"avatar user-avatar\">\n <span class=\"avatar-initial\">U</span>\n </div>\n </div>\n }\n </div>\n</div>\n", styles: [":host{display:block;margin-bottom:16px}.message-wrapper{display:flex;width:100%;margin-bottom:12px}.message-container{display:flex;max-width:85%;line-height:1.5}.user-message{justify-content:flex-end}.user-message .message-container{flex-direction:row-reverse}.user-message .message-bubble{background-color:#0d5878;color:#fff;border-radius:18px 18px 0;margin-right:8px}.assistant-message{justify-content:flex-start}.assistant-message .message-container{flex-direction:row}.assistant-message .message-bubble{background-color:#f0f0f0;color:#333;border-radius:18px 18px 18px 0;margin-left:8px}.message-bubble{padding:12px 16px;box-shadow:0 1px 2px #0000001a;max-width:calc(100% - 50px);word-wrap:break-word;overflow-wrap:break-word;word-break:break-word;hyphens:auto;min-width:0}.avatar-container{display:flex;align-items:flex-end}.avatar{width:36px;height:36px;border-radius:50%;background-color:#0d5878;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;font-size:14px}.user-avatar{background-color:#ffa77e}::ng-deep .em{color:inherit;font-style:italic}::ng-deep .strong{font-weight:700;color:inherit}::ng-deep .em_strong{font-weight:700;font-style:italic;color:inherit}.translation{margin-top:8px;font-size:small;line-height:1.6;color:#393744;font-style:italic}.divider{margin:.5rem 40px;border-top:1px solid #ffa77e}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: MultiMessageContentComponent, selector: "dc-multi-message-content", inputs: ["messages", "isLoading"], outputs: ["playAudio"] }, { kind: "component", type: StandaloneAudioTextSyncComponent, selector: "dc-standalone-audio-text-sync", inputs: ["message"], outputs: ["playAudio", "audioCompleted"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1934
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: ChatMessageComponent, isStandalone: true, selector: "dc-chat-message", inputs: { chatMessage: { classPropertyName: "chatMessage", publicName: "chatMessage", isSignal: true, isRequired: true, transformFunction: null }, chatUserSettings: { classPropertyName: "chatUserSettings", publicName: "chatUserSettings", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"message-wrapper\" [ngClass]=\"{ 'user-message': isUserMessage(), 'assistant-message': !isUserMessage() }\">\n <div class=\"message-container\">\n <!-- Avatar for assistant messages -->\n\n @if (!isUserMessage()) {\n <div class=\"avatar-container\">\n <div class=\"avatar\">\n <img src=\"assets/defaults/avatar.jpg\" alt=\"User\" class=\"avatar-image\" />\n </div>\n </div>\n }\n\n <!-- Message content -->\n <div class=\"message-bubble\">\n @if (hasMultiMessages()) {\n <dc-message-orchestrator [messages]=\"multiMessages()\" [messageRole]=\"chatMessage().role\"></dc-message-orchestrator>\n } @else {\n <dc-message-orchestrator [messages]=\"[audioMessage()]\" [messageRole]=\"chatMessage().role\"></dc-message-orchestrator>\n }\n\n <!-- Translation if available -->\n @if (messageTranslation()) {\n <div class=\"translation\">\n <hr class=\"divider\" />\n {{ messageTranslation() }}\n </div>\n }\n </div>\n\n <!-- Avatar for user messages -->\n @if (isUserMessage()) {\n <div class=\"avatar-container\">\n <div class=\"avatar user-avatar\">\n <img src=\"assets/defaults/avatar.jpg\" alt=\"User\" class=\"avatar-image\" />\n </div>\n </div>\n }\n </div>\n</div>\n", styles: [":host{display:block;margin-bottom:16px}.message-wrapper{display:flex;width:100%;margin-bottom:12px}.message-container{display:flex;max-width:85%;line-height:1.5}.user-message{justify-content:flex-end}.user-message .message-container{flex-direction:row}.user-message .message-bubble{background-color:#0d5878;color:#fff;border-radius:18px 18px 0;margin-left:8px}.assistant-message{justify-content:flex-start}.assistant-message .message-container{flex-direction:row}.assistant-message .message-bubble{background-color:#f0f0f0;color:#333;border-radius:18px 18px 18px 0;margin-left:8px}.message-bubble{padding:12px 16px;box-shadow:0 1px 2px #0000001a;max-width:calc(100% - 50px);word-wrap:break-word;overflow-wrap:break-word;word-break:break-word;hyphens:auto;min-width:0}.avatar-container{display:flex;align-items:flex-end}.avatar{width:36px;height:36px;border-radius:50%;background-color:#0d5878;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;font-size:14px;overflow:hidden}.avatar-image{width:100%;height:100%;object-fit:cover}.user-avatar{background-color:#ffa77e}::ng-deep .em{color:inherit;font-style:italic}::ng-deep .strong{font-weight:700;color:inherit}::ng-deep .em_strong{font-weight:700;font-style:italic;color:inherit}.translation{margin-top:8px;font-size:small;line-height:1.6;color:#393744;font-style:italic}.divider{margin:.5rem 40px;border-top:1px solid #ffa77e}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: MessageOrchestratorComponent, selector: "dc-message-orchestrator", inputs: ["messages", "messageRole"], outputs: ["playAudio", "audioCompleted"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2118
1935
  }
2119
1936
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatMessageComponent, decorators: [{
2120
1937
  type: Component,
2121
- args: [{ selector: 'dc-chat-message', standalone: true, imports: [CommonModule, MultiMessageContentComponent, StandaloneAudioTextSyncComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"message-wrapper\" [ngClass]=\"{ 'user-message': isUserMessage(), 'assistant-message': !isUserMessage() }\">\n <div class=\"message-container\">\n <!-- Avatar for assistant messages -->\n\n @if (!isUserMessage()) {\n <div class=\"avatar-container\">\n <div class=\"avatar\">\n <span class=\"avatar-initial\">A</span>\n </div>\n </div>\n }\n\n <!-- Message content -->\n <div class=\"message-bubble\">\n @if (hasMultiMessages()) {\n <dc-multi-message-content [messages]=\"multiMessages()\" (playAudio)=\"playMessage($event)\"></dc-multi-message-content>\n } @else {\n <dc-standalone-audio-text-sync [message]=\"audioMessage()\"></dc-standalone-audio-text-sync>\n }\n\n <!-- Translation if available -->\n @if (messageTranslation()) {\n <div class=\"translation\">\n <hr class=\"divider\" />\n {{ messageTranslation() }}\n </div>\n }\n </div>\n\n <!-- Avatar for user messages -->\n @if (isUserMessage()) {\n <div class=\"avatar-container\">\n <div class=\"avatar user-avatar\">\n <span class=\"avatar-initial\">U</span>\n </div>\n </div>\n }\n </div>\n</div>\n", styles: [":host{display:block;margin-bottom:16px}.message-wrapper{display:flex;width:100%;margin-bottom:12px}.message-container{display:flex;max-width:85%;line-height:1.5}.user-message{justify-content:flex-end}.user-message .message-container{flex-direction:row-reverse}.user-message .message-bubble{background-color:#0d5878;color:#fff;border-radius:18px 18px 0;margin-right:8px}.assistant-message{justify-content:flex-start}.assistant-message .message-container{flex-direction:row}.assistant-message .message-bubble{background-color:#f0f0f0;color:#333;border-radius:18px 18px 18px 0;margin-left:8px}.message-bubble{padding:12px 16px;box-shadow:0 1px 2px #0000001a;max-width:calc(100% - 50px);word-wrap:break-word;overflow-wrap:break-word;word-break:break-word;hyphens:auto;min-width:0}.avatar-container{display:flex;align-items:flex-end}.avatar{width:36px;height:36px;border-radius:50%;background-color:#0d5878;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;font-size:14px}.user-avatar{background-color:#ffa77e}::ng-deep .em{color:inherit;font-style:italic}::ng-deep .strong{font-weight:700;color:inherit}::ng-deep .em_strong{font-weight:700;font-style:italic;color:inherit}.translation{margin-top:8px;font-size:small;line-height:1.6;color:#393744;font-style:italic}.divider{margin:.5rem 40px;border-top:1px solid #ffa77e}\n"] }]
2122
- }], ctorParameters: () => [] });
1938
+ args: [{ selector: 'dc-chat-message', standalone: true, imports: [CommonModule, MessageOrchestratorComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"message-wrapper\" [ngClass]=\"{ 'user-message': isUserMessage(), 'assistant-message': !isUserMessage() }\">\n <div class=\"message-container\">\n <!-- Avatar for assistant messages -->\n\n @if (!isUserMessage()) {\n <div class=\"avatar-container\">\n <div class=\"avatar\">\n <img src=\"assets/defaults/avatar.jpg\" alt=\"User\" class=\"avatar-image\" />\n </div>\n </div>\n }\n\n <!-- Message content -->\n <div class=\"message-bubble\">\n @if (hasMultiMessages()) {\n <dc-message-orchestrator [messages]=\"multiMessages()\" [messageRole]=\"chatMessage().role\"></dc-message-orchestrator>\n } @else {\n <dc-message-orchestrator [messages]=\"[audioMessage()]\" [messageRole]=\"chatMessage().role\"></dc-message-orchestrator>\n }\n\n <!-- Translation if available -->\n @if (messageTranslation()) {\n <div class=\"translation\">\n <hr class=\"divider\" />\n {{ messageTranslation() }}\n </div>\n }\n </div>\n\n <!-- Avatar for user messages -->\n @if (isUserMessage()) {\n <div class=\"avatar-container\">\n <div class=\"avatar user-avatar\">\n <img src=\"assets/defaults/avatar.jpg\" alt=\"User\" class=\"avatar-image\" />\n </div>\n </div>\n }\n </div>\n</div>\n", styles: [":host{display:block;margin-bottom:16px}.message-wrapper{display:flex;width:100%;margin-bottom:12px}.message-container{display:flex;max-width:85%;line-height:1.5}.user-message{justify-content:flex-end}.user-message .message-container{flex-direction:row}.user-message .message-bubble{background-color:#0d5878;color:#fff;border-radius:18px 18px 0;margin-left:8px}.assistant-message{justify-content:flex-start}.assistant-message .message-container{flex-direction:row}.assistant-message .message-bubble{background-color:#f0f0f0;color:#333;border-radius:18px 18px 18px 0;margin-left:8px}.message-bubble{padding:12px 16px;box-shadow:0 1px 2px #0000001a;max-width:calc(100% - 50px);word-wrap:break-word;overflow-wrap:break-word;word-break:break-word;hyphens:auto;min-width:0}.avatar-container{display:flex;align-items:flex-end}.avatar{width:36px;height:36px;border-radius:50%;background-color:#0d5878;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;font-size:14px;overflow:hidden}.avatar-image{width:100%;height:100%;object-fit:cover}.user-avatar{background-color:#ffa77e}::ng-deep .em{color:inherit;font-style:italic}::ng-deep .strong{font-weight:700;color:inherit}::ng-deep .em_strong{font-weight:700;font-style:italic;color:inherit}.translation{margin-top:8px;font-size:small;line-height:1.6;color:#393744;font-style:italic}.divider{margin:.5rem 40px;border-top:1px solid #ffa77e}\n"] }]
1939
+ }] });
2123
1940
 
2124
1941
  class ChatMessagesListComponent {
2125
1942
  constructor() {
2126
1943
  this.chatUserSettings = input.required();
2127
- this.thinkingImg = input.required();
2128
1944
  this.aiIcon = 'assets/default/ai.png';
2129
1945
  this.conversationService = inject(ConversationService);
2130
- // Create a signal for the filter text
2131
- this.filterText = signal('');
1946
+ this.inputMessages = input.required();
1947
+ // Inject ElementRef to access the component's host element
1948
+ this.elementRef = inject((ElementRef));
2132
1949
  // Get messages and thinking state from the conversation service
2133
1950
  this.messages = computed(() => {
2134
1951
  // Get the actual array of messages from the signal by calling it as a function
2135
- const allMessages = this.conversationService.getMessages()();
2136
- return allMessages.filter((message) => message.role !== 'system');
1952
+ const allMessages = this.conversationService.getMessagesSignal()();
1953
+ console.log('Getting messages', allMessages);
1954
+ return allMessages.filter((message) => message.role !== ChatRole.System);
2137
1955
  });
2138
1956
  this.isThinking = this.conversationService.isThinking();
1957
+ // Use effect to automatically scroll to bottom when messages change
1958
+ effect(() => {
1959
+ // Access the messages to create a dependency
1960
+ const messages = this.messages();
1961
+ // Schedule the scroll after the view has been updated
1962
+ setTimeout(() => this.scrollToBottom(), 0);
1963
+ });
2139
1964
  }
2140
- // Method to update the filter text
2141
- updateFilter(value) {
2142
- this.filterText.set(value);
1965
+ ngAfterViewInit() {
1966
+ // Initial scroll to bottom when view is initialized
1967
+ this.scrollToBottom();
1968
+ }
1969
+ // Scroll to the bottom of the component itself with smooth animation
1970
+ scrollToBottom() {
1971
+ const element = this.elementRef.nativeElement;
1972
+ element.scrollTo({
1973
+ top: element.scrollHeight,
1974
+ behavior: 'smooth'
1975
+ });
2143
1976
  }
2144
1977
  // Track messages by their content and role for efficient rendering
2145
1978
  trackByMessage(index, message) {
2146
1979
  return `${message.role}-${index}-${message.content.substring(0, 20)}`;
2147
1980
  }
2148
1981
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatMessagesListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2149
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: ChatMessagesListComponent, isStandalone: true, selector: "dc-chat-messages-list", inputs: { chatUserSettings: { classPropertyName: "chatUserSettings", publicName: "chatUserSettings", isSignal: true, isRequired: true, transformFunction: null }, thinkingImg: { classPropertyName: "thinkingImg", publicName: "thinkingImg", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"messages-container\">\n @for (message of messages(); track trackByMessage($index, message)) {\n <dc-chat-message [chatMessage]=\"message\" [chatUserSettings]=\"chatUserSettings()\"> </dc-chat-message>\n } @if (isThinking()) {\n <div class=\"thinking-container\">\n <div class=\"thinking-message\">\n <div class=\"thinking-avatar\">\n <img [src]=\"aiIcon\" alt=\"AI thinking\" class=\"avatar-img\" />\n </div>\n <div class=\"thinking-content\">\n <p-skeleton width=\"80%\" height=\"2rem\"></p-skeleton>\n <p-skeleton width=\"60%\" height=\"2rem\"></p-skeleton>\n </div>\n </div>\n </div>\n }\n</div>\n", styles: [".messages-container{display:flex;flex-direction:column;gap:1rem;padding:1rem;overflow-y:auto;max-height:calc(100vh - 200px)}.thinking-container{padding:.5rem 0}.thinking-message{display:flex;gap:1rem;align-items:flex-start}.thinking-avatar{width:40px;height:40px;border-radius:50%;overflow:hidden;flex-shrink:0}.avatar-img{width:100%;height:100%;object-fit:cover}.thinking-content{flex:1;display:flex;flex-direction:column;gap:.5rem}\n"], dependencies: [{ kind: "component", type: ChatMessageComponent, selector: "dc-chat-message", inputs: ["chatMessage", "chatUserSettings"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1$2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "style", "shape", "animation", "borderRadius", "size", "width", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1982
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: ChatMessagesListComponent, isStandalone: true, selector: "dc-chat-messages-list", inputs: { chatUserSettings: { classPropertyName: "chatUserSettings", publicName: "chatUserSettings", isSignal: true, isRequired: true, transformFunction: null }, inputMessages: { classPropertyName: "inputMessages", publicName: "inputMessages", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div class=\"messages-container\">\n @for (message of messages(); track trackByMessage($index, message)) {\n <dc-chat-message [chatMessage]=\"message\" [chatUserSettings]=\"chatUserSettings()\"> </dc-chat-message>\n } @if (isThinking()) {\n <div class=\"thinking-container\">\n <div class=\"thinking-message\">\n <div class=\"thinking-avatar\">\n <img [src]=\"aiIcon\" alt=\"AI thinking\" class=\"avatar-img\" />\n </div>\n <div class=\"thinking-content\">\n <p-skeleton width=\"80%\" height=\"2rem\"></p-skeleton>\n <p-skeleton width=\"60%\" height=\"2rem\"></p-skeleton>\n </div>\n </div>\n </div>\n }\n</div>\n", styles: [".messages-container{display:flex;flex-direction:column;gap:1rem;padding:1rem;height:100%}.thinking-container{padding:.5rem 0}.thinking-message{display:flex;gap:1rem;align-items:flex-start}.thinking-avatar{width:40px;height:40px;border-radius:50%;overflow:hidden;flex-shrink:0}.avatar-img{width:100%;height:100%;object-fit:cover}.thinking-content{flex:1;display:flex;flex-direction:column;gap:.5rem}\n"], dependencies: [{ kind: "component", type: ChatMessageComponent, selector: "dc-chat-message", inputs: ["chatMessage", "chatUserSettings"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1$2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "style", "shape", "animation", "borderRadius", "size", "width", "height"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2150
1983
  }
2151
1984
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatMessagesListComponent, decorators: [{
2152
1985
  type: Component,
2153
- args: [{ selector: 'dc-chat-messages-list', standalone: true, imports: [ChatMessageComponent, SkeletonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"messages-container\">\n @for (message of messages(); track trackByMessage($index, message)) {\n <dc-chat-message [chatMessage]=\"message\" [chatUserSettings]=\"chatUserSettings()\"> </dc-chat-message>\n } @if (isThinking()) {\n <div class=\"thinking-container\">\n <div class=\"thinking-message\">\n <div class=\"thinking-avatar\">\n <img [src]=\"aiIcon\" alt=\"AI thinking\" class=\"avatar-img\" />\n </div>\n <div class=\"thinking-content\">\n <p-skeleton width=\"80%\" height=\"2rem\"></p-skeleton>\n <p-skeleton width=\"60%\" height=\"2rem\"></p-skeleton>\n </div>\n </div>\n </div>\n }\n</div>\n", styles: [".messages-container{display:flex;flex-direction:column;gap:1rem;padding:1rem;overflow-y:auto;max-height:calc(100vh - 200px)}.thinking-container{padding:.5rem 0}.thinking-message{display:flex;gap:1rem;align-items:flex-start}.thinking-avatar{width:40px;height:40px;border-radius:50%;overflow:hidden;flex-shrink:0}.avatar-img{width:100%;height:100%;object-fit:cover}.thinking-content{flex:1;display:flex;flex-direction:column;gap:.5rem}\n"] }]
2154
- }] });
1986
+ args: [{ selector: 'dc-chat-messages-list', standalone: true, imports: [ChatMessageComponent, SkeletonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"messages-container\">\n @for (message of messages(); track trackByMessage($index, message)) {\n <dc-chat-message [chatMessage]=\"message\" [chatUserSettings]=\"chatUserSettings()\"> </dc-chat-message>\n } @if (isThinking()) {\n <div class=\"thinking-container\">\n <div class=\"thinking-message\">\n <div class=\"thinking-avatar\">\n <img [src]=\"aiIcon\" alt=\"AI thinking\" class=\"avatar-img\" />\n </div>\n <div class=\"thinking-content\">\n <p-skeleton width=\"80%\" height=\"2rem\"></p-skeleton>\n <p-skeleton width=\"60%\" height=\"2rem\"></p-skeleton>\n </div>\n </div>\n </div>\n }\n</div>\n", styles: [".messages-container{display:flex;flex-direction:column;gap:1rem;padding:1rem;height:100%}.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"] }]
1987
+ }], ctorParameters: () => [] });
2155
1988
 
2156
1989
  const SpeedDescription = {
2157
1990
  1: 'Muy Lento',
@@ -2329,99 +2162,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
2329
2162
  BadgeModule,
2330
2163
  SkeletonModule,
2331
2164
  TooltipModule,
2332
- ProviderSelectorComponent
2165
+ ProviderSelectorComponent,
2333
2166
  ], template: "<div class=\"dialog-container\">\n <form [formGroup]=\"form\">\n @if (showFeature().synthVoice) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"synthVoice\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.synthVoice.disabled\">Escuchar Voz</span>\n <br />\n <small>Desmarca si solo quieres leer texto</small>\n </p>\n </div>\n }\n\n @if (showFeature().highlightWords) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"highlightWords\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Narraci\u00F3n de texto</span>\n <br />\n <small>Remarca las palabras como se van pronuncionando</small>\n </p>\n </div>\n }\n\n @if (showFeature().speed) {\n <div class=\"settings-section\">\n <p>\n Velocidad ({{ form.controls.speed.value | speedDisplay }})\n <br />\n <p-rating formControlName=\"speed\">\n <ng-template pTemplate=\"onicon\">\n <i class=\"pi pi-caret-right\"></i>\n </ng-template>\n <ng-template pTemplate=\"officon\">\n <i class=\"pi pi-circle\"></i>\n </ng-template>\n </p-rating>\n </p>\n </div>\n }\n\n @if (showFeature().realTime) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"realTime\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.realTime.disabled\">Tiempo real</span>\n <br />\n <small>No tienes que presionar el microphono, comenzar\u00E1 a grabar en cuanto la AI termine de hablar, cierra el chat para finalizar conversaci\u00F3n.</small>\n </p>\n </div>\n }\n\n @if (showFeature().realTime) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"repeatRecording\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Reproducir mi grabaci\u00F3n</span>\n <br />\n <small>Escucha tu dialogo, despu\u00E9s de grabar, te ayudar\u00E1 a notar tus errores.</small>\n </p>\n </div>\n }\n\n @if (showFeature().superHearing) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"superHearing\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Super O\u00EDdo \uD83E\uDDBE</span>\n <br />\n <small>Tu audio se procesa en el servidor para mejor efectividad, si no usa el navegador.</small>\n </p>\n </div>\n }\n\n @if (showFeature().fixGrammar) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"fixGrammar\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.fixGrammar.disabled\">Corregir gram\u00E1tica</span>\n <br />\n <small>La ai corrige tu forma de hablar/escribir y te retrolimenta de tus errores</small>\n </p>\n </div>\n }\n\n @if (showFeature().autoTranslate) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"autoTranslate\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.autoTranslate.disabled\">Mostrar Traducciones</span>\n <br />\n <small>Texto adicional con la traducci\u00F3n</small>\n </p>\n </div>\n }\n\n @if (showFeature().autoTranslate) {\n <div class=\"voice-selection\">\n <span>Voz Preferencial:</span>\n <br />\n <p-radioButton value=\"random\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Aleatorio</label>\n <p-radioButton value=\"randomMan\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Hombre</label>\n <p-radioButton value=\"randomWoman\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Mujer</label>\n </div>\n }\n\n @if(isAdmin) {\n <div>\n <hr />\n <b>Admin Section</b>\n <br />\n\n <b>Modelo:</b>\n\n <dc-provider-selector [parentForm]=\"form.controls.model\"></dc-provider-selector>\n </div>\n }\n\n <div class=\"button-group\">\n <p-button (click)=\"saveSettings()\" label=\"Guardar cambios\"></p-button>\n <p-button (click)=\"close()\" label=\"Cancelar\" styleClass=\"p-button-secondary\"></p-button>\n </div>\n </form>\n</div>\n", styles: [".dialog-container{padding:20px;background:#fff;border-radius:8px;min-width:300px;max-width:500px}.dialog-content{margin:20px 0}.dialog-actions{display:flex;justify-content:flex-end}.settings-section{margin-bottom:20px}.settings-section label{display:block;margin-bottom:5px;font-weight:700}.settings-section small{display:block;color:#666;margin-top:2px}.voice-selection{margin:15px 0}.voice-selection label{margin-right:15px}.button-group{margin-top:20px;display:flex;gap:10px;justify-content:flex-end}button{padding:8px 16px;border-radius:4px;border:none;cursor:pointer}button:first-child{background-color:#007bff;color:#fff}button:last-child{background-color:#6c757d;color:#fff}.space{margin-left:3px}\n"] }]
2334
2167
  }], ctorParameters: () => [], propDecorators: { tooltipRef: [{
2335
2168
  type: ViewChild,
2336
2169
  args: ['tooltipRef']
2337
2170
  }] } });
2338
2171
 
2339
- const EvalResultStringDefinition = `
2340
- interface EvalResult {
2341
- score: number; // Score of the user's response 0 to 3
2342
- feedback: string; // Feedback of the user's understanding of the conversation
2343
- }`;
2344
- const DefaultEvaluatorAgentCard = {
2345
- task: 'Evaluate the user understanding of the lesson',
2346
- messages: [],
2347
- expectedResponseType: EvalResultStringDefinition,
2348
- model: { id: 'gpt-4o-mini', provider: 'openai' },
2349
- sources: [],
2350
- };
2351
-
2352
2172
  class DCChatComponent {
2353
2173
  constructor() {
2354
- this.agentCardService = inject(CONVERSATION_AI_TOKEN);
2355
2174
  this.conversationBuilder = inject(DCConversationPromptBuilderService);
2356
2175
  this.dialogService = inject(DialogService);
2357
2176
  this.conversationService = inject(ConversationService);
2358
- this.evaluationService = inject(EvaluationService);
2359
- this.evaluatorAgentCard = input(DefaultEvaluatorAgentCard);
2177
+ this.userDataExchange = inject(USER_DATA_EXCHANGE);
2178
+ this.chatUserSettings = this.userDataExchange.getUserChatSettings(); // Default to user data exchange
2179
+ // TODO: those are optional think if i need them,
2180
+ this.evaluatorAgentCard = input();
2360
2181
  this.parseDict = input({});
2361
- this.sendMessage = output();
2182
+ this.sendMessage = output(); // notifies whatever happened inside the chat
2362
2183
  this.micSettings = { useWhisper: true, lang: 'en' };
2363
- this.imageUser = `assets/default/user.svg`;
2364
- this.thinkingImg = `assets/default/thinking.svg`;
2365
- this.aiIcon = `assets/default/ai.svg`;
2366
2184
  this.isInfoVisible = false;
2367
- this.isChatSettingsVisible = false;
2368
- this.isUserTalking = false;
2369
- this.isGettingTranscription = false;
2370
2185
  this.isAdmin = true;
2371
2186
  // Get reactive state from services
2372
- this.messages = this.conversationService.getMessages();
2373
- this.isThinking = this.conversationService.isThinking();
2374
- this.score = this.evaluationService.getScore();
2187
+ // public messages = this.conversationService.getMessages();
2188
+ this.messages = signal([]);
2375
2189
  }
2376
2190
  async ngOnInit() {
2377
- // Get user settings if not provided
2378
- if (!this.chatUserSettings) {
2379
- this.chatUserSettings = await this.agentCardService.getConversationUserChatSettings();
2191
+ if (this.conversationSettings) {
2192
+ await this.conversationService.initConversationWithSettings(this.conversationSettings);
2193
+ }
2194
+ else {
2195
+ await this.conversationService.initConversationWithAgentCard(this.agentCard);
2380
2196
  }
2381
- // Initialize conversation
2382
- await this.conversationService.initConversation(this.agentCard, this.conversationBuilder, this.parseDict());
2383
2197
  }
2384
2198
  ngOnDestroy() {
2385
2199
  // Mark conversation as destroyed to prevent async operations
2386
2200
  this.conversationService.setDestroyed(true);
2387
2201
  }
2388
- // onUserMessage method has been moved to the chat-footer component
2389
- /**
2390
- * Handle microphone input finished
2391
- */
2392
- async onMicFinished(eventBlob) {
2393
- if (!(eventBlob instanceof Blob)) {
2394
- if (!this.chatUserSettings.superHearing) {
2395
- this.isUserTalking = false;
2396
- }
2397
- return;
2398
- }
2399
- // Check if audio is too small
2400
- if (eventBlob.size < 10000) {
2401
- return;
2402
- }
2403
- this.isUserTalking = false;
2404
- this.isGettingTranscription = true;
2405
- try {
2406
- // Get transcription from audio
2407
- const transcription = await this.agentCardService.getAudioTranscriptions(eventBlob, {
2408
- conversationId: this.agentCard._id,
2409
- });
2410
- // Create message with transcription
2411
- const message = {
2412
- content: transcription.text,
2413
- role: ChatRole.User,
2414
- transcriptionTimestamps: transcription.words,
2415
- };
2416
- message['audioUrl'] = URL.createObjectURL(eventBlob);
2417
- // Send message to conversation
2418
- // The evaluation will happen automatically in the conversation service
2419
- await this.conversationService.sendUserMessage(message);
2420
- }
2421
- finally {
2422
- this.isGettingTranscription = false;
2423
- }
2424
- }
2425
2202
  /**
2426
2203
  * Open chat settings dialog
2427
2204
  */
@@ -2435,7 +2212,7 @@ class DCChatComponent {
2435
2212
  closeOnEscape: true,
2436
2213
  })
2437
2214
  .onClose.subscribe(async () => {
2438
- this.chatUserSettings = await this.agentCardService.getConversationUserChatSettings();
2215
+ // TODO: Que hacer cuando cambie las configuraciones del usario?, creo que si existe en exchange no necesito guardarlo y leer desde el servicio.
2439
2216
  });
2440
2217
  }
2441
2218
  /**
@@ -2454,12 +2231,14 @@ class DCChatComponent {
2454
2231
  await this.ngOnInit();
2455
2232
  }
2456
2233
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCChatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2457
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.4", type: DCChatComponent, isStandalone: true, selector: "dc-chat", inputs: { chatUserSettings: { classPropertyName: "chatUserSettings", publicName: "chatUserSettings", isSignal: false, isRequired: false, transformFunction: null }, agentCard: { classPropertyName: "agentCard", publicName: "agentCard", isSignal: false, isRequired: false, transformFunction: null }, evaluatorAgentCard: { classPropertyName: "evaluatorAgentCard", publicName: "evaluatorAgentCard", isSignal: true, isRequired: false, transformFunction: null }, parseDict: { classPropertyName: "parseDict", publicName: "parseDict", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sendMessage: "sendMessage" }, providers: [DialogService], ngImport: i0, template: "<div class=\"chat-container\">\n <!-- Chat Header -->\n <dc-chat-header\n [agentCard]=\"agentCard\"\n [isAdmin]=\"isAdmin\"\n (showInfoEvent)=\"showInfo()\"\n (settingsClickEvent)=\"changeUserChatSettings()\"\n (restartConversationEvent)=\"restartConversation($event)\">\n </dc-chat-header>\n\n <!-- Messages List -->\n <dc-chat-messages-list [chatUserSettings]=\"chatUserSettings\" [thinkingImg]=\"thinkingImg\"> </dc-chat-messages-list>\n\n <!-- Chat Footer -->\n <dc-chat-footer \n [isAIThinking]=\"isThinking()\" \n [micSettings]=\"micSettings\" \n [evaluatorAgentCard]=\"evaluatorAgentCard()\" \n (micFinishedEvent)=\"onMicFinished($event)\">\n </dc-chat-footer>\n\n <!-- Progress Bar for Score -->\n <!-- @if (score() > 0) {\n <div class=\"score-container\">\n <p-progressBar [value]=\"score()\" [showValue]=\"true\"></p-progressBar>\n </div>\n } -->\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:transparent}dc-chat-messages-list{flex:1;overflow-y:auto;padding:.5rem}.score-container{padding:.5rem 1rem;background-color:var(--surface-card, #ffffff);border-top:1px solid var(--surface-border, #dee2e6)}.info-content{max-height:70vh;overflow-y:auto}.info-content h3{margin-top:1rem;margin-bottom:.5rem;font-size:1.2rem}.info-content pre{background-color:var(--surface-hover, #f1f1f1);padding:1rem;border-radius:4px;overflow-x:auto;font-size:.9rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$1.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", "evaluatorAgentCard", "micSettings"], outputs: ["sendMessage", "textInputChanged", "micFinishedEvent"] }, { kind: "component", type: ChatMessagesListComponent, selector: "dc-chat-messages-list", inputs: ["chatUserSettings", "thinkingImg"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i2$4.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 }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2234
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.4", type: DCChatComponent, isStandalone: true, selector: "dc-chat", inputs: { chatUserSettings: { classPropertyName: "chatUserSettings", publicName: "chatUserSettings", isSignal: false, isRequired: false, transformFunction: null }, conversationSettings: { classPropertyName: "conversationSettings", publicName: "conversationSettings", isSignal: false, isRequired: false, transformFunction: null }, agentCard: { classPropertyName: "agentCard", publicName: "agentCard", isSignal: false, isRequired: false, transformFunction: null }, evaluatorAgentCard: { classPropertyName: "evaluatorAgentCard", publicName: "evaluatorAgentCard", isSignal: true, isRequired: false, transformFunction: null }, parseDict: { classPropertyName: "parseDict", publicName: "parseDict", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sendMessage: "sendMessage" }, providers: [DialogService], ngImport: i0, template: "<div class=\"chat-container\">\n <!-- Chat Header -->\n <dc-chat-header\n [agentCard]=\"agentCard\"\n [isAdmin]=\"isAdmin\"\n (showInfoEvent)=\"showInfo()\"\n (settingsClickEvent)=\"changeUserChatSettings()\"\n (restartConversationEvent)=\"restartConversation($event)\">\n </dc-chat-header>\n\n <!-- Messages List -->\n <dc-chat-messages-list [chatUserSettings]=\"chatUserSettings\" [inputMessages]=\"messages()\"> </dc-chat-messages-list>\n\n <!-- Chat Footer -->\n <dc-chat-footer [micSettings]=\"micSettings\" [evaluatorAgentCard]=\"evaluatorAgentCard()\"> </dc-chat-footer>\n\n <!-- Info Dialog -->\n <p-dialog [(visible)]=\"isInfoVisible\" [modal]=\"true\" [draggable]=\"false\" [resizable]=\"false\" header=\"Conversation Info\">\n <div class=\"info-content\">\n <h3>Agent Card</h3>\n <pre>{{ agentCard | safeJson }}</pre>\n\n <h3>User Settings</h3>\n <pre>{{ chatUserSettings | safeJson }}</pre>\n </div>\n </p-dialog>\n</div>\n", styles: ["::ng-deep .p-drawer-content{padding:0!important}.chat-container{display:flex;flex-direction:column;height:100%;max-height:100vh;overflow:hidden;background-color:transparent}dc-chat-messages-list{flex:1;overflow-y:auto;padding:.5rem}.score-container{padding:.5rem 1rem;background-color:var(--surface-card, #ffffff);border-top:1px solid var(--surface-border, #dee2e6)}.info-content{max-height:70vh;overflow-y:auto}.info-content h3{margin-top:1rem;margin-bottom:.5rem;font-size:1.2rem}.info-content pre{background-color:var(--surface-hover, #f1f1f1);padding:1rem;border-radius:4px;overflow-x:auto;font-size:.9rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ChatHeaderComponent, selector: "dc-chat-header", inputs: ["isAdmin", "alternativeConversation", "agentCard"], outputs: ["restartConversationEvent", "showInfoEvent", "settingsClickEvent"] }, { kind: "component", type: ChatFooterComponent, selector: "dc-chat-footer", inputs: ["isAIThinking", "evaluatorAgentCard", "micSettings"], outputs: ["sendMessage", "textInputChanged"] }, { kind: "component", type: ChatMessagesListComponent, selector: "dc-chat-messages-list", inputs: ["chatUserSettings", "inputMessages"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i1$3.Dialog, selector: "p-dialog", inputs: ["header", "draggable", "resizable", "positionLeft", "positionTop", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "responsive", "appendTo", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "breakpoint", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: ProgressBarModule }, { kind: "pipe", type: SafeJsonPipe, name: "safeJson" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2458
2235
  }
2459
2236
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCChatComponent, decorators: [{
2460
2237
  type: Component,
2461
- 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 \n [isAIThinking]=\"isThinking()\" \n [micSettings]=\"micSettings\" \n [evaluatorAgentCard]=\"evaluatorAgentCard()\" \n (micFinishedEvent)=\"onMicFinished($event)\">\n </dc-chat-footer>\n\n <!-- Progress Bar for Score -->\n <!-- @if (score() > 0) {\n <div class=\"score-container\">\n <p-progressBar [value]=\"score()\" [showValue]=\"true\"></p-progressBar>\n </div>\n } -->\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:transparent}dc-chat-messages-list{flex:1;overflow-y:auto;padding:.5rem}.score-container{padding:.5rem 1rem;background-color:var(--surface-card, #ffffff);border-top:1px solid var(--surface-border, #dee2e6)}.info-content{max-height:70vh;overflow-y:auto}.info-content h3{margin-top:1rem;margin-bottom:.5rem;font-size:1.2rem}.info-content pre{background-color:var(--surface-hover, #f1f1f1);padding:1rem;border-radius:4px;overflow-x:auto;font-size:.9rem}\n"] }]
2462
- }], ctorParameters: () => [], propDecorators: { chatUserSettings: [{
2238
+ args: [{ selector: 'dc-chat', standalone: true, imports: [CommonModule, ChatHeaderComponent, ChatFooterComponent, ChatMessagesListComponent, DialogModule, ProgressBarModule, SafeJsonPipe], changeDetection: ChangeDetectionStrategy.OnPush, providers: [DialogService], template: "<div class=\"chat-container\">\n <!-- Chat Header -->\n <dc-chat-header\n [agentCard]=\"agentCard\"\n [isAdmin]=\"isAdmin\"\n (showInfoEvent)=\"showInfo()\"\n (settingsClickEvent)=\"changeUserChatSettings()\"\n (restartConversationEvent)=\"restartConversation($event)\">\n </dc-chat-header>\n\n <!-- Messages List -->\n <dc-chat-messages-list [chatUserSettings]=\"chatUserSettings\" [inputMessages]=\"messages()\"> </dc-chat-messages-list>\n\n <!-- Chat Footer -->\n <dc-chat-footer [micSettings]=\"micSettings\" [evaluatorAgentCard]=\"evaluatorAgentCard()\"> </dc-chat-footer>\n\n <!-- Info Dialog -->\n <p-dialog [(visible)]=\"isInfoVisible\" [modal]=\"true\" [draggable]=\"false\" [resizable]=\"false\" header=\"Conversation Info\">\n <div class=\"info-content\">\n <h3>Agent Card</h3>\n <pre>{{ agentCard | safeJson }}</pre>\n\n <h3>User Settings</h3>\n <pre>{{ chatUserSettings | safeJson }}</pre>\n </div>\n </p-dialog>\n</div>\n", styles: ["::ng-deep .p-drawer-content{padding:0!important}.chat-container{display:flex;flex-direction:column;height:100%;max-height:100vh;overflow:hidden;background-color:transparent}dc-chat-messages-list{flex:1;overflow-y:auto;padding:.5rem}.score-container{padding:.5rem 1rem;background-color:var(--surface-card, #ffffff);border-top:1px solid var(--surface-border, #dee2e6)}.info-content{max-height:70vh;overflow-y:auto}.info-content h3{margin-top:1rem;margin-bottom:.5rem;font-size:1.2rem}.info-content pre{background-color:var(--surface-hover, #f1f1f1);padding:1rem;border-radius:4px;overflow-x:auto;font-size:.9rem}\n"] }]
2239
+ }], propDecorators: { chatUserSettings: [{
2240
+ type: Input
2241
+ }], conversationSettings: [{
2463
2242
  type: Input
2464
2243
  }], agentCard: [{
2465
2244
  type: Input
@@ -2803,7 +2582,7 @@ class AccountPlatformForm {
2803
2582
  }
2804
2583
  }
2805
2584
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AccountPlatformForm, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2806
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: AccountPlatformForm, isStandalone: true, selector: "account-platform-form", inputs: { formArray: "formArray" }, ngImport: i0, template: "<div class=\"source-form-card\">\n <p-card header=\"Cuenta\">\n @for (formAccount of formArray.controls; track formAccount) {\n <form [formGroup]=\"$any(formAccount)\">\n <div class=\"form-field\">\n <label class=\"block\">Platform</label>\n <p-dropdown [options]=\"platformOptions\" formControlName=\"platform\" optionLabel=\"label\" optionValue=\"value\" placeholder=\"Select a platform\"></p-dropdown>\n </div>\n\n <div class=\"form-field\">\n <label for=\"name\" class=\"block\">Username</label>\n <input pInputText id=\"name\" type=\"text\" formControlName=\"name\" placeholder=\"Enter name\" class=\"w-full\" />\n </div>\n\n <div class=\"form-field\">\n <label class=\"block\">Email</label>\n <input pInputText type=\"text\" formControlName=\"email\" placeholder=\"Enter name\" class=\"w-full\" />\n </div>\n </form>\n }\n </p-card>\n</div>\n", styles: [":host{display:block;padding:1rem}.source-form-card{max-width:800px;margin:0 auto}.form-field{margin-bottom:1.5rem;display:flex;flex-direction:column}.form-field label{margin-bottom:.5rem;font-weight:500;color:#495057}.form-field input,.form-field textarea,.form-field ::ng-deep .p-element{margin-top:.25rem}:host ::ng-deep .p-card .p-card-content>div:last-child{margin-top:1.5rem;display:flex;justify-content:flex-end}:host ::ng-deep .p-card .p-card-header{background-color:#f8f9fa;padding:1rem;border-bottom:1px solid #dee2e6}h3{color:#495057;margin-bottom:1.5rem;text-align:center}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i2$5.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "ngmodule", type: DropdownModule }, { kind: "component", type: i3$2.Dropdown, selector: "p-dropdown", inputs: ["id", "scrollHeight", "filter", "name", "style", "panelStyle", "styleClass", "panelStyleClass", "readonly", "required", "editable", "appendTo", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "variant", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "autoDisplayFirst", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "maxlength", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "fluid", "disabled", "itemSize", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "filterValue", "options"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: SelectModule }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4$1.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }, { kind: "ngmodule", type: ChipModule }, { kind: "ngmodule", type: TooltipModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2585
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: AccountPlatformForm, isStandalone: true, selector: "account-platform-form", inputs: { formArray: "formArray" }, ngImport: i0, template: "<div class=\"source-form-card\">\n <p-card header=\"Cuenta\">\n @for (formAccount of formArray.controls; track formAccount) {\n <form [formGroup]=\"$any(formAccount)\">\n <div class=\"form-field\">\n <label class=\"block\">Platform</label>\n <p-dropdown [options]=\"platformOptions\" formControlName=\"platform\" optionLabel=\"label\" optionValue=\"value\" placeholder=\"Select a platform\"></p-dropdown>\n </div>\n\n <div class=\"form-field\">\n <label for=\"name\" class=\"block\">Username</label>\n <input pInputText id=\"name\" type=\"text\" formControlName=\"name\" placeholder=\"Enter name\" class=\"w-full\" />\n </div>\n\n <div class=\"form-field\">\n <label class=\"block\">Email</label>\n <input pInputText type=\"text\" formControlName=\"email\" placeholder=\"Enter name\" class=\"w-full\" />\n </div>\n </form>\n }\n </p-card>\n</div>\n", styles: [":host{display:block;padding:1rem}.source-form-card{max-width:800px;margin:0 auto}.form-field{margin-bottom:1.5rem;display:flex;flex-direction:column}.form-field label{margin-bottom:.5rem;font-weight:500;color:#495057}.form-field input,.form-field textarea,.form-field ::ng-deep .p-element{margin-top:.25rem}:host ::ng-deep .p-card .p-card-content>div:last-child{margin-top:1.5rem;display:flex;justify-content:flex-end}:host ::ng-deep .p-card .p-card-header{background-color:#f8f9fa;padding:1rem;border-bottom:1px solid #dee2e6}h3{color:#495057;margin-bottom:1.5rem;text-align:center}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i2$4.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "ngmodule", type: DropdownModule }, { kind: "component", type: i3$2.Dropdown, selector: "p-dropdown", inputs: ["id", "scrollHeight", "filter", "name", "style", "panelStyle", "styleClass", "panelStyleClass", "readonly", "required", "editable", "appendTo", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "variant", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "autoDisplayFirst", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "maxlength", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "fluid", "disabled", "itemSize", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "filterValue", "options"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: SelectModule }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4$1.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }, { kind: "ngmodule", type: ChipModule }, { kind: "ngmodule", type: TooltipModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2807
2586
  }
2808
2587
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AccountPlatformForm, decorators: [{
2809
2588
  type: Component,
@@ -3283,7 +3062,7 @@ class DCConversationCardUIComponent {
3283
3062
  this.onCardAction.emit({ event: 'delete', card: this.card() });
3284
3063
  }
3285
3064
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCConversationCardUIComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3286
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: DCConversationCardUIComponent, isStandalone: true, selector: "dc-agent-card-default-ui", inputs: { card: { classPropertyName: "card", publicName: "card", isSignal: true, isRequired: false, transformFunction: null }, showOptions: { classPropertyName: "showOptions", publicName: "showOptions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onCardAction: "onCardAction" }, ngImport: i0, template: "<p-card class=\"card-image\">\n @if(showOptions()) {\n <div style=\"position: absolute; top: 5px; right: 5px; z-index: 1000\">\n <p-speeddial\n [model]=\"speedDialModel\"\n [radius]=\"70\"\n type=\"quarter-circle\"\n direction=\"down-left\"\n [buttonProps]=\"{ severity: 'primary', rounded: true, outlined: true, raised: true }\" />\n </div>\n }\n\n <img [src]=\"card()?.assets?.image?.url || 'assets/images/default_conversation_card.webp'\" alt=\"\" />\n\n <div (click)=\"onDetails()\" class=\"content\">\n <h2 class=\"title-text\">{{ card().title }}</h2>\n\n <h5 class=\"title\">\n <span [innerHTML]=\"card().characterCard?.data.description | truncate : 100\"></span>\n </h5>\n\n <p-button\n (click)=\"onDetails()\"\n [style]=\"{ position: 'absolute', bottom: '10px', right: '10px' }\"\n icon=\"pi pi-comment\"\n [rounded]=\"true\"\n severity=\"info\"\n [outlined]=\"true\"\n [raised]=\"true\" />\n </div>\n</p-card>\n", styles: [":host{display:block}:host ::ng-deep .p-card{height:100%}:host ::ng-deep .p-card-body{height:100%;padding:0!important}.card-image{width:280px;height:380px;position:relative;align-items:center;display:block;padding:-10px}.card-image img{position:absolute;z-index:3;width:100%;height:100%;opacity:.75;object-fit:cover;transition:opacity .5s}.content{position:absolute;inset:0;z-index:4;padding:1.5rem;color:#fff;background:linear-gradient(to bottom,#0003,#0000001a);height:100%;display:flex;flex-direction:column}.content:hover{background:linear-gradient(to bottom,color-mix(in srgb,var(--p-primary-color) 20%,transparent),color-mix(in srgb,black 10%,transparent));cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: PopoverModule }, { kind: "pipe", type: TruncatePipe, name: "truncate" }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SpeedDialModule }, { kind: "component", type: i2$6.SpeedDial, selector: "p-speeddial, p-speedDial, p-speed-dial", inputs: ["id", "model", "visible", "style", "className", "direction", "transitionDelay", "type", "radius", "mask", "disabled", "hideOnClickOutside", "buttonStyle", "buttonClassName", "maskStyle", "maskClassName", "showIcon", "hideIcon", "rotateAnimation", "ariaLabel", "ariaLabelledBy", "tooltipOptions", "buttonProps"], outputs: ["onVisibleChange", "visibleChange", "onClick", "onShow", "onHide"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i2$5.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3065
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: DCConversationCardUIComponent, isStandalone: true, selector: "dc-agent-card-default-ui", inputs: { card: { classPropertyName: "card", publicName: "card", isSignal: true, isRequired: false, transformFunction: null }, showOptions: { classPropertyName: "showOptions", publicName: "showOptions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onCardAction: "onCardAction" }, ngImport: i0, template: "<p-card class=\"card-image\">\n @if(showOptions()) {\n <div style=\"position: absolute; top: 5px; right: 5px; z-index: 1000\">\n <p-speeddial\n [model]=\"speedDialModel\"\n [radius]=\"70\"\n type=\"quarter-circle\"\n direction=\"down-left\"\n [buttonProps]=\"{ severity: 'primary', rounded: true, outlined: true, raised: true }\" />\n </div>\n }\n\n <img [src]=\"card()?.assets?.image?.url || 'assets/images/default_conversation_card.webp'\" alt=\"\" />\n\n <div (click)=\"onDetails()\" class=\"content\">\n <h2 class=\"title-text\">{{ card().title }}</h2>\n\n <h5 class=\"title\">\n <span [innerHTML]=\"card().characterCard?.data.description | truncate : 100\"></span>\n </h5>\n\n <p-button\n (click)=\"onDetails()\"\n [style]=\"{ position: 'absolute', bottom: '10px', right: '10px' }\"\n icon=\"pi pi-comment\"\n [rounded]=\"true\"\n severity=\"info\"\n [outlined]=\"true\"\n [raised]=\"true\" />\n </div>\n</p-card>\n", styles: [":host{display:block}:host ::ng-deep .p-card{height:100%}:host ::ng-deep .p-card-body{height:100%;padding:0!important}.card-image{width:280px;height:380px;position:relative;align-items:center;display:block;padding:-10px}.card-image img{position:absolute;z-index:3;width:100%;height:100%;opacity:.75;object-fit:cover;transition:opacity .5s}.content{position:absolute;inset:0;z-index:4;padding:1.5rem;color:#fff;background:linear-gradient(to bottom,#0003,#0000001a);height:100%;display:flex;flex-direction:column}.content:hover{background:linear-gradient(to bottom,color-mix(in srgb,var(--p-primary-color) 20%,transparent),color-mix(in srgb,black 10%,transparent));cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: PopoverModule }, { kind: "pipe", type: TruncatePipe, name: "truncate" }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SpeedDialModule }, { kind: "component", type: i2$5.SpeedDial, selector: "p-speeddial, p-speedDial, p-speed-dial", inputs: ["id", "model", "visible", "style", "className", "direction", "transitionDelay", "type", "radius", "mask", "disabled", "hideOnClickOutside", "buttonStyle", "buttonClassName", "maskStyle", "maskClassName", "showIcon", "hideIcon", "rotateAnimation", "ariaLabel", "ariaLabelledBy", "tooltipOptions", "buttonProps"], outputs: ["onVisibleChange", "visibleChange", "onClick", "onShow", "onHide"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i2$4.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3287
3066
  }
3288
3067
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCConversationCardUIComponent, decorators: [{
3289
3068
  type: Component,
@@ -3430,7 +3209,7 @@ class AgentCardListComponent extends PaginationBase {
3430
3209
  }
3431
3210
  }
3432
3211
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AgentCardListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3433
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: AgentCardListComponent, isStandalone: true, selector: "dc-agent-card-lists", inputs: { viewMode: { classPropertyName: "viewMode", publicName: "viewMode", isSignal: false, isRequired: false, transformFunction: null }, customCardComponent: { classPropertyName: "customCardComponent", publicName: "customCardComponent", isSignal: true, isRequired: false, transformFunction: null }, showOptions: { classPropertyName: "showOptions", publicName: "showOptions", isSignal: true, isRequired: false, transformFunction: null }, gridLayout: { classPropertyName: "gridLayout", publicName: "gridLayout", isSignal: true, isRequired: false, transformFunction: null }, getCustomButtons: { classPropertyName: "getCustomButtons", publicName: "getCustomButtons", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "outlets", predicate: ["outlet"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<dc-filter-bar [isAdmin]=\"true\" (onFilterAction)=\"doFilterBarAction($event)\"></dc-filter-bar>\n\n@if(viewMode === 'table'){\n<app-quick-table [columns]=\"columns\" [tableData]=\"agentCards\" [actions]=\"actions()\" (onAction)=\"onCardAction($event)\"></app-quick-table>\n\n}@else{\n\n<div class=\"conversation-card-lists\">\n @if(!isLoading) {\n <div [ngClass]=\"{ 'cards-container': gridLayout() }\">\n @for (card of agentCards; track card) {\n <div style=\"position: relative\">\n <ng-container #outlet=\"ngComponentOutlet\" [ngComponentOutlet]=\"cardComponent\" [ngComponentOutletInputs]=\"{ card: card, showOptions: showOptions() }\">\n </ng-container>\n </div>\n }\n </div>\n }\n</div>\n\n@if(isLoading) {\n<div>\n <p-skeleton styleClass=\"mb-2\" />\n <p-skeleton width=\"10rem\" styleClass=\"mb-2\" />\n <p-skeleton width=\"5rem\" styleClass=\"mb-2\" />\n <p-skeleton height=\"2rem\" styleClass=\"mb-2\" />\n <p-skeleton width=\"10rem\" height=\"4rem\" />\n</div>\n} @if(agentCards.length === 0) {\n<div>\n <p>No conversations found or no connection with server</p>\n</div>\n} }\n\n<p-paginator\n currentPageReportTemplate=\"{{ totalRecords }} conversations\"\n [showCurrentPageReport]=\"true\"\n (onPageChange)=\"onPageChange($event)\"\n [first]=\"paginatorFirst\"\n [rows]=\"paginatorRows\"\n [totalRecords]=\"totalRecords\"\n [rowsPerPageOptions]=\"[10, 20, 30]\">\n</p-paginator>\n", styles: [":host{display:block;height:100%}.options-icon{cursor:pointer;position:absolute;top:2px;right:3px;font-size:1.2rem;color:#dde9e9;background-color:#4f486281;border-radius:50%;padding:5px;z-index:1000}.conversation-card-lists{padding:1.5rem;width:100%;height:100%;display:flex;flex-direction:column}.conversation-card-lists .cards-container{display:flex;flex-wrap:wrap;gap:2rem;width:100%;justify-content:center;flex:1;overflow-y:auto;min-height:0}.conversation-card-lists .cards-container>div{flex:0 0 240px}.conversation-card-lists .dc-card{position:relative;background:#fff;border-radius:8px;box-shadow:0 2px 4px #0000001a;padding:.5rem;transition:transform .2s ease,box-shadow .2s ease;display:flex;flex-direction:column;gap:2px}.conversation-card-lists .dc-card:hover{transform:translateY(-2px);box-shadow:0 4px 8px #00000026}.conversation-card-lists .dc-card .dc-card-header{position:absolute;top:10px;left:5px;border-radius:5px;padding:5px}.conversation-card-lists .dc-card .dc-card-header:before{content:\"\";position:absolute;inset:0;background-color:#4d30db81;filter:blur(2px);border-radius:5px;z-index:0}.conversation-card-lists .dc-card .dc-card-header h3{margin:0;font-size:1.25rem;font-weight:600;color:#ece7e7;position:relative;z-index:1}.conversation-card-lists .dc-card .dc-card-content{flex:1}.conversation-card-lists .dc-card .dc-card-content p{margin:0;color:#666;line-height:1.5}.conversation-card-lists .dc-card button{padding:.5rem 1rem;border:none;border-radius:4px;background-color:#007bff;color:#fff;cursor:pointer;font-weight:500;transition:background-color .2s ease}.conversation-card-lists .dc-card button:hover{background-color:#0056b3}.conversation-card-lists .dc-card button:active{transform:translateY(1px)}:host{display:flex;flex-direction:column;height:100%}p-paginator{margin-top:1rem;flex-shrink:0}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: PaginatorModule }, { kind: "component", type: i1$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: i1$2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "style", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "ngmodule", type: SpeedDialModule }, { kind: "component", type: QuickTableComponent, selector: "app-quick-table", inputs: ["onlyView", "columns", "tableData", "actions"], outputs: ["onAction"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
3212
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: AgentCardListComponent, isStandalone: true, selector: "dc-agent-card-lists", inputs: { viewMode: { classPropertyName: "viewMode", publicName: "viewMode", isSignal: false, isRequired: false, transformFunction: null }, customCardComponent: { classPropertyName: "customCardComponent", publicName: "customCardComponent", isSignal: true, isRequired: false, transformFunction: null }, showOptions: { classPropertyName: "showOptions", publicName: "showOptions", isSignal: true, isRequired: false, transformFunction: null }, gridLayout: { classPropertyName: "gridLayout", publicName: "gridLayout", isSignal: true, isRequired: false, transformFunction: null }, getCustomButtons: { classPropertyName: "getCustomButtons", publicName: "getCustomButtons", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "outlets", predicate: ["outlet"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<dc-filter-bar [isAdmin]=\"true\" (onFilterAction)=\"doFilterBarAction($event)\"></dc-filter-bar>\n\n@if(viewMode === 'table'){\n<app-quick-table [columns]=\"columns\" [tableData]=\"agentCards\" [actions]=\"actions()\" (onAction)=\"onCardAction($event)\"></app-quick-table>\n\n}@else{\n\n<div class=\"conversation-card-lists\">\n @if(!isLoading) {\n <div [ngClass]=\"{ 'cards-container': gridLayout() }\">\n @for (card of agentCards; track card) {\n <div style=\"position: relative\">\n <ng-container #outlet=\"ngComponentOutlet\" [ngComponentOutlet]=\"cardComponent\" [ngComponentOutletInputs]=\"{ card: card, showOptions: showOptions() }\">\n </ng-container>\n </div>\n }\n </div>\n }\n</div>\n\n@if(isLoading) {\n<div>\n <p-skeleton styleClass=\"mb-2\" />\n <p-skeleton width=\"10rem\" styleClass=\"mb-2\" />\n <p-skeleton width=\"5rem\" styleClass=\"mb-2\" />\n <p-skeleton height=\"2rem\" styleClass=\"mb-2\" />\n <p-skeleton width=\"10rem\" height=\"4rem\" />\n</div>\n} @if(agentCards.length === 0) {\n<div>\n <p>No conversations found or no connection with server</p>\n</div>\n} }\n\n<p-paginator\n currentPageReportTemplate=\"{{ totalRecords }} conversations\"\n [showCurrentPageReport]=\"true\"\n (onPageChange)=\"onPageChange($event)\"\n [first]=\"paginatorFirst\"\n [rows]=\"paginatorRows\"\n [totalRecords]=\"totalRecords\"\n [rowsPerPageOptions]=\"[10, 20, 30]\">\n</p-paginator>\n", styles: [":host{display:block;height:100%}.options-icon{cursor:pointer;position:absolute;top:2px;right:3px;font-size:1.2rem;color:#dde9e9;background-color:#4f486281;border-radius:50%;padding:5px;z-index:1000}.conversation-card-lists{padding:1.5rem;width:100%;height:100%;display:flex;flex-direction:column}.conversation-card-lists .cards-container{display:flex;flex-wrap:wrap;gap:2rem;width:100%;justify-content:center;flex:1;overflow-y:auto;min-height:0}.conversation-card-lists .cards-container>div{flex:0 0 240px}.conversation-card-lists .dc-card{position:relative;background:#fff;border-radius:8px;box-shadow:0 2px 4px #0000001a;padding:.5rem;transition:transform .2s ease,box-shadow .2s ease;display:flex;flex-direction:column;gap:2px}.conversation-card-lists .dc-card:hover{transform:translateY(-2px);box-shadow:0 4px 8px #00000026}.conversation-card-lists .dc-card .dc-card-header{position:absolute;top:10px;left:5px;border-radius:5px;padding:5px}.conversation-card-lists .dc-card .dc-card-header:before{content:\"\";position:absolute;inset:0;background-color:#4d30db81;filter:blur(2px);border-radius:5px;z-index:0}.conversation-card-lists .dc-card .dc-card-header h3{margin:0;font-size:1.25rem;font-weight:600;color:#ece7e7;position:relative;z-index:1}.conversation-card-lists .dc-card .dc-card-content{flex:1}.conversation-card-lists .dc-card .dc-card-content p{margin:0;color:#666;line-height:1.5}.conversation-card-lists .dc-card button{padding:.5rem 1rem;border:none;border-radius:4px;background-color:#007bff;color:#fff;cursor:pointer;font-weight:500;transition:background-color .2s ease}.conversation-card-lists .dc-card button:hover{background-color:#0056b3}.conversation-card-lists .dc-card button:active{transform:translateY(1px)}:host{display:flex;flex-direction:column;height:100%}p-paginator{margin-top:1rem;flex-shrink:0}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: PaginatorModule }, { kind: "component", type: i1$4.Paginator, selector: "p-paginator", inputs: ["pageLinkSize", "style", "styleClass", "alwaysShow", "dropdownAppendTo", "templateLeft", "templateRight", "appendTo", "dropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showFirstLastIcon", "totalRecords", "rows", "rowsPerPageOptions", "showJumpToPageDropdown", "showJumpToPageInput", "jumpToPageItemTemplate", "showPageLinks", "locale", "dropdownItemTemplate", "first"], outputs: ["onPageChange"] }, { kind: "component", type: DCFilterBarComponent, selector: "dc-filter-bar", inputs: ["isAdmin", "customFilters", "items"], outputs: ["onFilterAction", "onChangeSort"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1$2.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "style", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "ngmodule", type: SpeedDialModule }, { kind: "component", type: QuickTableComponent, selector: "app-quick-table", inputs: ["onlyView", "columns", "tableData", "actions"], outputs: ["onAction"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
3434
3213
  }
3435
3214
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AgentCardListComponent, decorators: [{
3436
3215
  type: Component,
@@ -3511,7 +3290,7 @@ class DcAgentCardDetailsComponent {
3511
3290
  this.cdr.markForCheck();
3512
3291
  }
3513
3292
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DcAgentCardDetailsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3514
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: DcAgentCardDetailsComponent, isStandalone: true, selector: "dc-agent-card-details", inputs: { agentCardId: "agentCardId" }, outputs: { onStartConversation: "onStartConversation" }, ngImport: i0, template: "<div style=\"display: flex; justify-content: center; align-items: center\">\n <p-card>\n <div class=\"card-container\">\n <img class=\"card-image\" [src]=\"agentCard?.assets?.image?.url || 'assets/images/default_conversation_card.webp'\" alt=\"\" />\n\n <div class=\"info-button\" (click)=\"toggleInfoLayer()\">\n <p-button icon=\"pi pi-arrow-down-left\" [rounded]=\"true\" [raised]=\"true\" severity=\"primary\" [outlined]=\"true\" />\n </div>\n\n <div style=\"position: absolute; bottom: 20px; right: 50%; transform: translateX(50%); z-index: 3\">\n <p-button size=\"large\" label=\"Iniciar Conversaci\u00F3n\" [rounded]=\"true\" (click)=\"startConversation()\" />\n </div>\n\n <div class=\"info-layer\" [class.active]=\"showInfoLayer\">\n <div class=\"info-content\">\n <h1\n ><strong>{{ agentCard?.title }}</strong></h1\n >\n <p>{{ agentCard?.characterCard.data?.name }}</p>\n\n @if (agentCard?.characterCard.data?.scenario) {\n <div class=\"scenario\">\n <h4>Scenario</h4>\n <p>{{ agentCard?.characterCard.data.scenario | parseCard : agentCard }}</p>\n </div>\n }\n </div>\n </div>\n </div>\n </p-card>\n </div>\n\n <!-- <div class=\"dc-conversation-card-details\">\n <div class=\"header\">\n <h2>{{ agentCard?.title }}</h2>\n <span class=\"version\">v{{ agentCard?.version }}</span>\n </div>\n\n <div class=\"character-card\" *ngIf=\"agentCard?.characterCard\">\n <div class=\"character-header\">\n <h3>{{ agentCard?.characterCard.data.name }}</h3>\n <div class=\"tags\">\n <span class=\"tag\" *ngFor=\"let tag of agentCard?.characterCard.data?.tags\">{{ tag }}</span>\n </div>\n </div>\n\n <div class=\"image-wrapper\">\n <div class=\"image\">\n <img [src]=\"agentCard?.assets?.image?.url || 'assets/images/default_conversation_card.webp'\" alt=\"\" />\n <div class=\"actions\">\n <p-button label=\"Start Conversation\" (click)=\"startConversation()\"></p-button>\n </div>\n </div>\n </div>\n\n <div class=\"description\">\n <p>{{ agentCard?.characterCard.data?.description | parseCard : agentCard }}</p>\n </div>\n\n <div class=\"scenario\" *ngIf=\"agentCard?.characterCard.data?.scenario\">\n <h4>Scenario</h4>\n <p>{{ agentCard?.characterCard.data.scenario }}</p>\n </div>\n\n <div class=\"first-message\" *ngIf=\"agentCard?.characterCard.data?.first_mes\">\n <h4>First Message</h4>\n <p>{{ agentCard?.characterCard.data.first_mes }}</p>\n </div>\n\n <div class=\"alternate-greetings\" *ngIf=\"agentCard?.characterCard.data?.alternate_greetings?.length\">\n <h4>Alternate Greetings</h4>\n <ul>\n <li *ngFor=\"let greeting of agentCard?.characterCard.data.alternate_greetings\">{{ greeting }}</li>\n </ul>\n </div>\n </div>\n\n <div class=\"settings\">\n <div class=\"conversation-settings\">\n <h3>Conversation Settings</h3>\n <div class=\"setting-item\">\n <span class=\"label\">Type:</span>\n <span class=\"value\">{{ agentCard?.conversationSettings?.conversationType }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Language:</span>\n <span class=\"value\">{{ agentCard?.lang }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Text Engine:</span>\n <span class=\"value\">{{ agentCard?.conversationSettings?.textEngine }}</span>\n </div>\n </div>\n\n <div class=\"tts-settings\" *ngIf=\"agentCard?.tts\">\n <h3>TTS Settings</h3>\n <div class=\"setting-item\">\n <span class=\"label\">Primary Voice:</span>\n <span class=\"value\">{{ agentCard?.tts.voice }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Secondary Voice:</span>\n <span class=\"value\">{{ agentCard?.tts.secondaryVoice }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Speed:</span>\n <span class=\"value\">{{ agentCard?.tts.speed }} ({{ agentCard?.tts.speedRate }}x)</span>\n </div>\n </div>\n </div>\n</div> -->\n", styles: ["::ng-deep .p-card{width:420px;height:700px}::ng-deep .p-card .p-card-body{width:100%;height:100%}.card-image{height:100%;width:100%;object-fit:cover;object-position:center;position:absolute;top:0;left:0;transition:filter .3s ease}.info-button{position:absolute;top:15px;right:15px;z-index:3}.info-button:hover{transform:scale(1.1)}.info-layer{height:100%;width:100%;position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;z-index:2;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);background-color:#ed122833;color:#fff;opacity:1;clip-path:circle(0% at top right);transition:clip-path .5s cubic-bezier(.25,1,.5,1);pointer-events:none}.info-layer.active{clip-path:circle(150% at top right);pointer-events:auto}.info-content{padding:15px;text-align:center;max-width:90%}.info-content h1{margin-top:0;font-size:18px;margin-bottom:10px}.info-content p{font-size:12px;margin:0}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i2$5.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "pipe", type: ParseCardPipe, name: "parseCard" }] }); }
3293
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: DcAgentCardDetailsComponent, isStandalone: true, selector: "dc-agent-card-details", inputs: { agentCardId: "agentCardId" }, outputs: { onStartConversation: "onStartConversation" }, ngImport: i0, template: "<div style=\"display: flex; justify-content: center; align-items: center\">\n <p-card>\n <div class=\"card-container\">\n <img class=\"card-image\" [src]=\"agentCard?.assets?.image?.url || 'assets/images/default_conversation_card.webp'\" alt=\"\" />\n\n <div class=\"info-button\" (click)=\"toggleInfoLayer()\">\n <p-button icon=\"pi pi-arrow-down-left\" [rounded]=\"true\" [raised]=\"true\" severity=\"primary\" [outlined]=\"true\" />\n </div>\n\n <div style=\"position: absolute; bottom: 20px; right: 50%; transform: translateX(50%); z-index: 3\">\n <p-button size=\"large\" label=\"Iniciar Conversaci\u00F3n\" [rounded]=\"true\" (click)=\"startConversation()\" />\n </div>\n\n <div class=\"info-layer\" [class.active]=\"showInfoLayer\">\n <div class=\"info-content\">\n <h1\n ><strong>{{ agentCard?.title }}</strong></h1\n >\n <p>{{ agentCard?.characterCard.data?.name }}</p>\n\n @if (agentCard?.characterCard.data?.scenario) {\n <div class=\"scenario\">\n <h4>Scenario</h4>\n <p>{{ agentCard?.characterCard.data.scenario | parseCard : agentCard }}</p>\n </div>\n }\n </div>\n </div>\n </div>\n </p-card>\n </div>\n\n <!-- <div class=\"dc-conversation-card-details\">\n <div class=\"header\">\n <h2>{{ agentCard?.title }}</h2>\n <span class=\"version\">v{{ agentCard?.version }}</span>\n </div>\n\n <div class=\"character-card\" *ngIf=\"agentCard?.characterCard\">\n <div class=\"character-header\">\n <h3>{{ agentCard?.characterCard.data.name }}</h3>\n <div class=\"tags\">\n <span class=\"tag\" *ngFor=\"let tag of agentCard?.characterCard.data?.tags\">{{ tag }}</span>\n </div>\n </div>\n\n <div class=\"image-wrapper\">\n <div class=\"image\">\n <img [src]=\"agentCard?.assets?.image?.url || 'assets/images/default_conversation_card.webp'\" alt=\"\" />\n <div class=\"actions\">\n <p-button label=\"Start Conversation\" (click)=\"startConversation()\"></p-button>\n </div>\n </div>\n </div>\n\n <div class=\"description\">\n <p>{{ agentCard?.characterCard.data?.description | parseCard : agentCard }}</p>\n </div>\n\n <div class=\"scenario\" *ngIf=\"agentCard?.characterCard.data?.scenario\">\n <h4>Scenario</h4>\n <p>{{ agentCard?.characterCard.data.scenario }}</p>\n </div>\n\n <div class=\"first-message\" *ngIf=\"agentCard?.characterCard.data?.first_mes\">\n <h4>First Message</h4>\n <p>{{ agentCard?.characterCard.data.first_mes }}</p>\n </div>\n\n <div class=\"alternate-greetings\" *ngIf=\"agentCard?.characterCard.data?.alternate_greetings?.length\">\n <h4>Alternate Greetings</h4>\n <ul>\n <li *ngFor=\"let greeting of agentCard?.characterCard.data.alternate_greetings\">{{ greeting }}</li>\n </ul>\n </div>\n </div>\n\n <div class=\"settings\">\n <div class=\"conversation-settings\">\n <h3>Conversation Settings</h3>\n <div class=\"setting-item\">\n <span class=\"label\">Type:</span>\n <span class=\"value\">{{ agentCard?.conversationSettings?.conversationType }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Language:</span>\n <span class=\"value\">{{ agentCard?.lang }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Text Engine:</span>\n <span class=\"value\">{{ agentCard?.conversationSettings?.textEngine }}</span>\n </div>\n </div>\n\n <div class=\"tts-settings\" *ngIf=\"agentCard?.tts\">\n <h3>TTS Settings</h3>\n <div class=\"setting-item\">\n <span class=\"label\">Primary Voice:</span>\n <span class=\"value\">{{ agentCard?.tts.voice }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Secondary Voice:</span>\n <span class=\"value\">{{ agentCard?.tts.secondaryVoice }}</span>\n </div>\n <div class=\"setting-item\">\n <span class=\"label\">Speed:</span>\n <span class=\"value\">{{ agentCard?.tts.speed }} ({{ agentCard?.tts.speedRate }}x)</span>\n </div>\n </div>\n </div>\n</div> -->\n", styles: ["::ng-deep .p-card{width:420px;height:700px}::ng-deep .p-card .p-card-body{width:100%;height:100%}.card-image{height:100%;width:100%;object-fit:cover;object-position:center;position:absolute;top:0;left:0;transition:filter .3s ease}.info-button{position:absolute;top:15px;right:15px;z-index:3}.info-button:hover{transform:scale(1.1)}.info-layer{height:100%;width:100%;position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;z-index:2;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);background-color:#ed122833;color:#fff;opacity:1;clip-path:circle(0% at top right);transition:clip-path .5s cubic-bezier(.25,1,.5,1);pointer-events:none}.info-layer.active{clip-path:circle(150% at top right);pointer-events:auto}.info-content{padding:15px;text-align:center;max-width:90%}.info-content h1{margin-top:0;font-size:18px;margin-bottom:10px}.info-content p{font-size:12px;margin:0}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i2$4.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "pipe", type: ParseCardPipe, name: "parseCard" }] }); }
3515
3294
  }
3516
3295
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DcAgentCardDetailsComponent, decorators: [{
3517
3296
  type: Component,
@@ -3520,6 +3299,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
3520
3299
  type: Input
3521
3300
  }] } });
3522
3301
 
3302
+ const EvalResultStringDefinition = `
3303
+ interface EvalResult {
3304
+ score: number; // Score of the user's response 0 to 3
3305
+ feedback: string; // Feedback of the user's understanding of the conversation
3306
+ }`;
3307
+ const DefaultEvaluatorAgentCard = {
3308
+ task: 'Evaluate the user understanding of the lesson',
3309
+ messages: [],
3310
+ expectedResponseType: EvalResultStringDefinition,
3311
+ model: { id: 'gpt-4o-mini', provider: 'openai' },
3312
+ sources: [],
3313
+ };
3314
+
3523
3315
  /*
3524
3316
  * Public API Surface of ngx-agent-cards
3525
3317
  */
@@ -3529,5 +3321,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
3529
3321
  * Generated bundle index. Do not edit.
3530
3322
  */
3531
3323
 
3532
- export { AgentCardListComponent, AgentCardsAbstractService, AudioService, AudioSpeed, CONVERSATION_AI_TOKEN, ChatMessage, ChatMultiMessage, ChatRole, ChatUserSettings, ConversationDTO, ConversationMessagesDTO, ConversationType, ConversationTypeOptions, DCAgentCardFormComponent, DCChatComponent, DCConversationCardUIComponent, DCConversationPromptBuilderService, DcAgentCardDetailsComponent, DefaultEvaluatorAgentCard, EAccountsPlatform, LangCodeDescriptionEs, MessageAudio, ProviderSelectorComponent, StandaloneAudioTextSyncComponent, TextEngineOptions, TextEngines, USER_DATA_EXCHANGE, UserDataExchangeAbstractService, VoiceTTSOption, VoiceTTSOptions, WordTimestamps, buildObjectTTSRequest, characterCardStringDataDefinition, defaultconvUserSettings, extractAudioAndTranscription, extractJsonFromResponse, matchTranscription, provideChatAIService, provideUserDataExchange };
3324
+ export { AgentCardListComponent, AgentCardsAbstractService, AudioService, AudioSpeed, CONVERSATION_AI_TOKEN, ChatMessage, ChatRole, ChatUserSettings, ConversationDTO, ConversationMessagesDTO, ConversationType, ConversationTypeOptions, DCAgentCardFormComponent, DCChatComponent, DCConversationCardUIComponent, DCConversationPromptBuilderService, DcAgentCardDetailsComponent, DefaultEvaluatorAgentCard, EAccountsPlatform, LangCodeDescriptionEs, MessageAudio, MessageOrchestratorComponent, ProviderSelectorComponent, TextEngineOptions, TextEngines, TextHighlighterComponent, USER_DATA_EXCHANGE, UserDataExchangeAbstractService, VoiceTTSOption, VoiceTTSOptions, WordTimestamps, buildObjectTTSRequest, characterCardStringDataDefinition, convertToHTML, defaultconvUserSettings, extractAudioAndTranscription, extractJsonFromResponse, markdownBasicToHTML, markdownToHTML$1 as markdownToHTML, markdownToHTML2, markdownToHtml, matchTranscription, provideChatAIService, provideUserDataExchange, removeAllEmojis, removeEmojis };
3533
3325
  //# sourceMappingURL=dataclouder-ngx-agent-cards.mjs.map