@dataclouder/ngx-agent-cards 0.1.5 → 0.1.7

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.
@@ -1,7 +1,8 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, Injectable, inject, Pipe, input, output, Input, Component, signal, effect, computed, ChangeDetectorRef, DestroyRef, ChangeDetectionStrategy, ElementRef, ViewChild, ViewChildren } from '@angular/core';
2
+ import { InjectionToken, Injectable, inject, RendererFactory2, ApplicationRef, Injector, EnvironmentInjector, signal, createComponent, Pipe, input, output, Input, Component, effect, ViewChild, computed, DestroyRef, ChangeDetectionStrategy, ElementRef, ChangeDetectorRef, ViewChildren } from '@angular/core';
3
3
  import * as i1$1 from '@angular/common';
4
- import { CommonModule, DatePipe, DecimalPipe, NgComponentOutlet } from '@angular/common';
4
+ import { DOCUMENT, CommonModule, DatePipe, DecimalPipe, NgComponentOutlet } from '@angular/common';
5
+ import { HttpCoreService, AudioSpeed as AudioSpeed$1, TOAST_ALERTS_TOKEN, PaginationBase, DCFilterBarComponent, QuickTableComponent } from '@dataclouder/ngx-core';
5
6
  import { DynamicDialogRef, DialogService, DynamicDialogConfig, DynamicDialogModule } from 'primeng/dynamicdialog';
6
7
  import * as i1$3 from 'primeng/dialog';
7
8
  import { DialogModule } from 'primeng/dialog';
@@ -15,7 +16,6 @@ import { TextareaModule } from 'primeng/textarea';
15
16
  import * as i2$1 from 'primeng/button';
16
17
  import { ButtonModule } from 'primeng/button';
17
18
  import { nanoid } from 'nanoid';
18
- import { AudioSpeed as AudioSpeed$1, TOAST_ALERTS_TOKEN, PaginationBase, DCFilterBarComponent, QuickTableComponent } from '@dataclouder/ngx-core';
19
19
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
20
20
  import { Subject, fromEvent, filter } from 'rxjs';
21
21
  import { takeUntil, map } from 'rxjs/operators';
@@ -59,6 +59,8 @@ import * as i1$4 from 'primeng/paginator';
59
59
  import { PaginatorModule } from 'primeng/paginator';
60
60
  import * as i2$5 from 'primeng/speeddial';
61
61
  import { SpeedDialModule } from 'primeng/speeddial';
62
+ import * as i4$2 from 'primeng/tag';
63
+ import { TagModule } from 'primeng/tag';
62
64
 
63
65
  const characterCardStringDataDefinition = `
64
66
  interface CharacterCardData {
@@ -104,6 +106,23 @@ class ConversationMessagesDTO {
104
106
  }
105
107
  class ConversationDTO {
106
108
  }
109
+ // Define the structure for chat events emitted by DCChatComponent
110
+ var ChatEventType;
111
+ (function (ChatEventType) {
112
+ ChatEventType["WordClicked"] = "wordClicked";
113
+ // Add other potential event types here as needed
114
+ // e.g., MessageSent = 'messageSent', SettingsChanged = 'settingsChanged', GoalCompleted = 'goalCompleted'
115
+ })(ChatEventType || (ChatEventType = {}));
116
+ // Enum to define different types of conversation context for evaluation
117
+ var ContextType;
118
+ (function (ContextType) {
119
+ ContextType["CurrentMessageContent"] = "MessageContent";
120
+ ContextType["LastAssistantMessage"] = "LastAssistantMessage";
121
+ ContextType["LastUserMessage"] = "LastUserMessage";
122
+ ContextType["LastDialog"] = "LastDialog";
123
+ ContextType["Last2Dialogs"] = "Last2Dialogs";
124
+ ContextType["AllConversation"] = "AllConversation";
125
+ })(ContextType || (ContextType = {}));
107
126
 
108
127
  const CONVERSATION_AI_TOKEN = new InjectionToken('Conversation Ai Service');
109
128
  // abstract-my-service.ts
@@ -557,7 +576,7 @@ class DCConversationPromptBuilderService {
557
576
  // conversation will parse values with curren value in parseDcit:
558
577
  // Hello {{user}} im {{char}} your are talking {{base}} -> hello jordan im AI bot your are talking spanish
559
578
  // important you have to implement userDataExchange to return what you want in parseDict that have sense for your app.
560
- if (parseDict) {
579
+ if (parseDict && Object.keys(parseDict).length > 0) {
561
580
  return parseDict;
562
581
  }
563
582
  else {
@@ -688,6 +707,160 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
688
707
  }]
689
708
  }] });
690
709
 
710
+ class PopupService {
711
+ constructor() {
712
+ this.document = inject(DOCUMENT);
713
+ this.rendererFactory = inject(RendererFactory2);
714
+ this.appRef = inject(ApplicationRef);
715
+ this.injector = inject(Injector);
716
+ this.environmentInjector = inject(EnvironmentInjector);
717
+ this.currentPopupContainer = null;
718
+ this.currentComponentRef = null;
719
+ this.selectedWord = signal(null); // Keep signal for outside click logic
720
+ this.clickListenerUnlisten = null; // For outside click listener
721
+ this.renderer = this.rendererFactory.createRenderer(null, null);
722
+ }
723
+ /**
724
+ * Shows a popup containing a dynamically created component.
725
+ * @param top Position from the top of the viewport.
726
+ * @param left Position from the left of the viewport.
727
+ * @param componentType The type of component to render inside the popup.
728
+ * @param injector The injector to use for the dynamic component.
729
+ * @param wordData The WordData to pass to the component's input signal.
730
+ */
731
+ showPopup(// Constrain T
732
+ top, left, componentType, injector, wordData) {
733
+ if (this.currentComponentRef) {
734
+ this.hidePopup(); // Hide existing popup first
735
+ }
736
+ this.selectedWord.set(wordData); // Track associated word data
737
+ // 1. Create the popup container element
738
+ this.currentPopupContainer = this.renderer.createElement('div');
739
+ this.renderer.addClass(this.currentPopupContainer, 'word-options-popup-dynamic'); // Use this class for targeting (e.g., in outside click listener)
740
+ this.renderer.setStyle(this.currentPopupContainer, 'position', 'fixed');
741
+ this.renderer.setStyle(this.currentPopupContainer, 'top', `${top + 5}px`); // Small offset
742
+ this.renderer.setStyle(this.currentPopupContainer, 'left', `${left + 5}px`); // Small offset
743
+ this.renderer.setStyle(this.currentPopupContainer, 'border', '1px solid #ccc');
744
+ this.renderer.setStyle(this.currentPopupContainer, 'background', 'white');
745
+ // Remove padding from container, let the component handle its own padding
746
+ // this.renderer.setStyle(this.currentPopupContainer, 'padding', '8px');
747
+ this.renderer.setStyle(this.currentPopupContainer, 'border-radius', '4px');
748
+ this.renderer.setStyle(this.currentPopupContainer, 'z-index', '1103'); // Or higher if needed
749
+ this.renderer.setStyle(this.currentPopupContainer, 'box-shadow', '0 2px 8px rgba(0,0,0,0.15)');
750
+ // 2. Create the component dynamically
751
+ this.currentComponentRef = createComponent(componentType, {
752
+ environmentInjector: this.environmentInjector, // Use environment injector
753
+ elementInjector: injector, // Pass the custom injector for data/providers
754
+ });
755
+ // 3. Attach the component to the Angular application lifecycle
756
+ this.appRef.attachView(this.currentComponentRef.hostView);
757
+ // 4. Get the component's root DOM element
758
+ const componentElement = this.currentComponentRef.location.nativeElement;
759
+ // 5. Append the component's element to the container
760
+ this.renderer.appendChild(this.currentPopupContainer, componentElement);
761
+ // 6. Append the container to the document body
762
+ this.renderer.appendChild(this.document.body, this.currentPopupContainer);
763
+ // 7. Set input signal *after* component is created and attached
764
+ // Ensure the component instance has the 'wordData' signal input
765
+ if (this.currentComponentRef.instance.wordData && typeof this.currentComponentRef.instance.wordData.set === 'function') {
766
+ this.currentComponentRef.instance.wordData.set(wordData);
767
+ this.currentComponentRef.changeDetectorRef.detectChanges(); // Trigger change detection
768
+ }
769
+ else {
770
+ console.warn('Dynamic component does not have a writable signal input named "wordData".');
771
+ }
772
+ // 8. Add listener for outside clicks *after* popup is shown
773
+ this.addOutsideClickListener();
774
+ }
775
+ /**
776
+ * Hides the currently displayed popup and destroys the dynamic component.
777
+ */
778
+ hidePopup() {
779
+ if (this.currentComponentRef) {
780
+ this.appRef.detachView(this.currentComponentRef.hostView);
781
+ this.currentComponentRef.destroy();
782
+ this.currentComponentRef = null;
783
+ }
784
+ if (this.currentPopupContainer && this.document.body.contains(this.currentPopupContainer)) {
785
+ try {
786
+ this.renderer.removeChild(this.document.body, this.currentPopupContainer);
787
+ }
788
+ catch (error) {
789
+ console.warn('Error removing popup container:', error);
790
+ }
791
+ }
792
+ this.currentPopupContainer = null;
793
+ this.selectedWord.set(null); // Clear selected word signal
794
+ this.removeOutsideClickListener(); // Remove listener when popup is hidden
795
+ }
796
+ addOutsideClickListener() {
797
+ // Remove any existing listener first
798
+ this.removeOutsideClickListener();
799
+ // Use setTimeout to ensure the listener is added after the current click event cycle
800
+ setTimeout(() => {
801
+ this.clickListenerUnlisten = this.renderer.listen(this.document, 'click', (event) => {
802
+ const clickedInsidePopup = this.currentPopupContainer?.contains(event.target);
803
+ // Also check if the click target is the word itself (to prevent immediate closing)
804
+ // This requires knowledge of the word element ID structure
805
+ const clickedOnWord = event.target?.closest('[id^="word-"]');
806
+ if (!clickedInsidePopup && !clickedOnWord && this.currentComponentRef) {
807
+ console.log('Outside click detected, hiding popup.');
808
+ this.hidePopup();
809
+ }
810
+ });
811
+ }, 0);
812
+ }
813
+ removeOutsideClickListener() {
814
+ if (this.clickListenerUnlisten) {
815
+ this.clickListenerUnlisten();
816
+ this.clickListenerUnlisten = null;
817
+ }
818
+ }
819
+ // Optional: Cleanup logic if the service itself is destroyed
820
+ ngOnDestroy() {
821
+ this.hidePopup(); // Ensure cleanup on service destruction (also removes listener)
822
+ }
823
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: PopupService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
824
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: PopupService, providedIn: 'root' }); }
825
+ }
826
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: PopupService, decorators: [{
827
+ type: Injectable,
828
+ args: [{
829
+ providedIn: 'root',
830
+ }]
831
+ }], ctorParameters: () => [] });
832
+
833
+ // Change this on backend if i have more types
834
+ var AgentCardProgressStatus;
835
+ (function (AgentCardProgressStatus) {
836
+ AgentCardProgressStatus["NotStarted"] = "not_started";
837
+ AgentCardProgressStatus["Started"] = "started";
838
+ AgentCardProgressStatus["Completed"] = "completed";
839
+ })(AgentCardProgressStatus || (AgentCardProgressStatus = {}));
840
+ const DefaultAPI = {
841
+ Progress: 'api/agent-cards-progress',
842
+ };
843
+ class AgentUserProgressService {
844
+ constructor() {
845
+ // Services
846
+ this.httpService = inject(HttpCoreService);
847
+ }
848
+ getProgress() {
849
+ return this.httpService.get(DefaultAPI.Progress + '/user');
850
+ }
851
+ saveProgress(progress) {
852
+ return this.httpService.put(DefaultAPI.Progress + '/user', progress);
853
+ }
854
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AgentUserProgressService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
855
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AgentUserProgressService, providedIn: 'root' }); }
856
+ }
857
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AgentUserProgressService, decorators: [{
858
+ type: Injectable,
859
+ args: [{
860
+ providedIn: 'root',
861
+ }]
862
+ }] });
863
+
691
864
  /**
692
865
  * SafeJsonPipe - A pipe that safely stringifies objects with circular references
693
866
  * This pipe handles circular references and DOM objects that can't be serialized
@@ -998,7 +1171,8 @@ class MessageProcessingService {
998
1171
  processedMessage.imgUrl = extraData.userImg;
999
1172
  }
1000
1173
  else if (message.role === ChatRole.Assistant && extraData.assistantImg) {
1001
- const defaultVoice = this.getVoice(conversationSettings.voice);
1174
+ // TODO i think lessons send voice in voice, but should be in tts
1175
+ const defaultVoice = this.getVoice(conversationSettings?.tts?.voice || conversationSettings.voice);
1002
1176
  processedMessage.voice = defaultVoice;
1003
1177
  processedMessage.imgUrl = extraData.assistantImg;
1004
1178
  }
@@ -1007,10 +1181,11 @@ class MessageProcessingService {
1007
1181
  this.processMultiMessages(processedMessage, conversationSettings);
1008
1182
  }
1009
1183
  else if (conversationSettings.textEngine === TextEngines.MarkdownSSML) {
1010
- if (!conversationSettings.secondaryVoice) {
1184
+ // TODO: check how to add default secondary voice
1185
+ if (!conversationSettings?.tts?.secondaryVoice) {
1011
1186
  throw new Error('Secondary voice is required for SSML');
1012
1187
  }
1013
- const content = this.subsItalicsByTag(processedMessage.content, conversationSettings.secondaryVoice);
1188
+ const content = this.subsItalicsByTag(processedMessage.content, conversationSettings.tts.secondaryVoice);
1014
1189
  processedMessage.ssml = '<speak>' + content + '</speak>';
1015
1190
  }
1016
1191
  return processedMessage;
@@ -1056,14 +1231,15 @@ class MessageProcessingService {
1056
1231
  return voiceCodes[Math.floor(Math.random() * voiceCodes.length)];
1057
1232
  }
1058
1233
  else {
1059
- const voice = VoiceTTSOptions.find((voice) => voice.id === voice_value);
1060
- if (voice) {
1061
- return voice.id;
1062
- }
1063
- else {
1064
- console.error('Voice not found getting something random', voice_value);
1065
- return VoiceTTSOptions.find((voice) => voice.lang.includes(targetLang))?.id || '';
1066
- }
1234
+ return voice_value;
1235
+ // voice should exist, there wont be validation.
1236
+ // const voice = VoiceTTSOptions.find((voice) => voice.id === voice_value);
1237
+ // if (voice) {
1238
+ // return voice.id;
1239
+ // } else {
1240
+ // console.error('Voice not found getting something random', voice_value);
1241
+ // return VoiceTTSOptions.find((voice) => voice.lang.includes(targetLang))?.id || '';
1242
+ // }
1067
1243
  }
1068
1244
  }
1069
1245
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MessageProcessingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
@@ -1133,6 +1309,7 @@ function buildObjectTTSRequest(message, settings = {}) {
1133
1309
  generateTranscription,
1134
1310
  speedRate,
1135
1311
  speed,
1312
+ ssml: message?.ssml,
1136
1313
  };
1137
1314
  }
1138
1315
 
@@ -1150,6 +1327,7 @@ class ConversationService {
1150
1327
  this.isDestroyedSignal = signal(false);
1151
1328
  this.micStatusSignal = signal('ready');
1152
1329
  this.currentAudioStatus = signal(null);
1330
+ this.wordClickedSignal = signal(null); // Signal for clicked word
1153
1331
  // Var State
1154
1332
  this.avatarImages = {
1155
1333
  userImg: 'assets/defaults/avatar_user.png',
@@ -1162,6 +1340,20 @@ class ConversationService {
1162
1340
  getUserSettings() {
1163
1341
  return this.userDataExchange.getUserChatSettings();
1164
1342
  }
1343
+ /**
1344
+ * Notifies subscribers that a word has been clicked in a message.
1345
+ * @param wordData The data associated with the clicked word.
1346
+ */
1347
+ notifyWordClicked(wordData) {
1348
+ this.wordClickedSignal.set(wordData);
1349
+ }
1350
+ /**
1351
+ * Gets the signal that emits when a word is clicked.
1352
+ * @returns A signal emitting WordData or null.
1353
+ */
1354
+ getWordClickedSignal() {
1355
+ return this.wordClickedSignal;
1356
+ }
1165
1357
  // Add message to conversation
1166
1358
  addMessage(message) {
1167
1359
  this.messagesSignal.update((messages) => [...messages, message]);
@@ -1193,7 +1385,7 @@ class ConversationService {
1193
1385
  }
1194
1386
  setupConversationWithAgentCard(agentCard, parseDict = null) {
1195
1387
  // Set user AI avatar image
1196
- this.avatarImages.assistantImg = agentCard?.assets.image?.url || this.avatarImages.assistantImg;
1388
+ this.avatarImages.assistantImg = agentCard?.assets?.image?.url || this.avatarImages.assistantImg;
1197
1389
  const conversationSettings = this.conversationBuilder.buildConversationSettings(agentCard, parseDict);
1198
1390
  this.conversationSettingsSignal.set(conversationSettings);
1199
1391
  }
@@ -1204,10 +1396,9 @@ class ConversationService {
1204
1396
  async initConversation() {
1205
1397
  // Set user avatar images
1206
1398
  this.avatarImages.userImg = this.userDataExchange.getUserDataExchange().imgUrl;
1207
- console.log(this.avatarImages);
1208
1399
  const conversationSettings = this.conversationSettingsSignal();
1209
1400
  for (const i in conversationSettings.messages) {
1210
- conversationSettings.messages[i].messageId = 'msg_' + i;
1401
+ conversationSettings.messages[i].messageId = nanoid();
1211
1402
  }
1212
1403
  // Find first assistant message
1213
1404
  const firstAssistantMsg = conversationSettings?.messages.find((message) => message.role === ChatRole.Assistant);
@@ -1232,40 +1423,66 @@ class ConversationService {
1232
1423
  this.setupConversationWithAgentCard(agentCard, parseDict);
1233
1424
  await this.initConversation();
1234
1425
  }
1235
- // Send user message
1426
+ /**
1427
+ * Sends a user message, processes it, adds it to the conversation,
1428
+ * and triggers the AI response flow.
1429
+ * @param message The initial ChatMessage object from the user.
1430
+ * @param updateId Optional ID if this message should update an existing one (e.g., transcription added).
1431
+ * @param updateId Optional ID if this message should update an existing one (e.g., transcription added).
1432
+ * @returns A promise resolving to an object containing the user message ID and the assistant message ID (or null if an error occurred).
1433
+ */
1236
1434
  async sendUserMessage(message, updateId = null) {
1237
1435
  if (this.isThinkingSignal()) {
1238
- return;
1436
+ console.warn('AI is already thinking, message dropped.');
1437
+ return null; // Indicate message was not sent due to AI being busy
1239
1438
  }
1439
+ let userMessageId;
1440
+ // Add or update the user message
1240
1441
  if (updateId) {
1241
- this.updateMessage(updateId, message);
1442
+ // Ensure the update includes the ID if not already present in message object
1443
+ const messageToUpdate = { ...message, messageId: updateId };
1444
+ const updated = this.updateMessage(updateId, messageToUpdate);
1445
+ if (!updated) {
1446
+ console.error(`Failed to update message with ID: ${updateId}`);
1447
+ // Decide how to handle this - maybe throw an error or return null?
1448
+ return null;
1449
+ }
1450
+ userMessageId = updateId;
1242
1451
  }
1243
1452
  else {
1244
- // Add message to conversation
1453
+ // Process and add the new message
1245
1454
  const processedMessage = this.messageProcessingService.processMessage(message, this.conversationSettingsSignal(), this.avatarImages);
1455
+ // Ensure ID exists (processMessage should handle this, but fallback just in case)
1456
+ processedMessage.messageId = processedMessage.messageId || nanoid();
1246
1457
  this.addMessage(processedMessage);
1458
+ userMessageId = processedMessage.messageId;
1247
1459
  }
1248
- // Set thinking state
1460
+ // Set thinking state and get AI response
1249
1461
  this.isThinkingSignal.set(true);
1462
+ let assistantMessageId = null;
1250
1463
  try {
1251
- // Send to AI service
1252
- await this.sendCurrentConversation();
1464
+ assistantMessageId = await this.sendCurrentConversation();
1465
+ }
1466
+ catch (error) {
1467
+ console.error('Error sending conversation to AI:', error);
1468
+ // Handle error appropriately, maybe set a specific state or message
1253
1469
  }
1254
1470
  finally {
1255
1471
  this.isThinkingSignal.set(false);
1256
1472
  }
1473
+ return { userMessageId, assistantMessageId };
1257
1474
  }
1258
1475
  // Process assistant message
1259
1476
  // Send current conversation to AI
1260
1477
  async sendCurrentConversation() {
1261
1478
  if (this.isDestroyedSignal()) {
1262
- return;
1479
+ return null;
1263
1480
  }
1264
1481
  const messages = this.messagesSignal();
1265
1482
  const conversationSettings = this.conversationSettingsSignal();
1266
1483
  if (messages.length > 31) {
1267
1484
  // Safety limit to prevent infinite conversations
1268
- return;
1485
+ return null;
1269
1486
  }
1270
1487
  let conversationMessages = messages;
1271
1488
  // Add last prompt if available
@@ -1279,6 +1496,7 @@ class ConversationService {
1279
1496
  textEngine: conversationSettings.textEngine,
1280
1497
  model: conversationSettings.model || { modelName: '', provider: '' },
1281
1498
  };
1499
+ this.isThinkingSignal.set(true);
1282
1500
  // Call AI service
1283
1501
  const response = await this.agentCardService.callChatCompletion(conversation);
1284
1502
  if (!response) {
@@ -1290,6 +1508,7 @@ class ConversationService {
1290
1508
  // Add to messages
1291
1509
  this.addMessage(newMessage);
1292
1510
  this.isThinkingSignal.set(false);
1511
+ return newMessage.messageId;
1293
1512
  }
1294
1513
  // Reset conversation
1295
1514
  async resetConversation(agentCard) {
@@ -1312,7 +1531,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
1312
1531
  }] });
1313
1532
 
1314
1533
  function extractJsonFromResponse(content) {
1315
- const jsonMatch = content.match(/\{[\s\S]*?\}/); // Match everything between first { and }
1534
+ // Match everything between the first { and last } OR first [ and last ]
1535
+ const jsonMatch = content.match(/(\{[\s\S]*?\}|\[[\s\S]*?\])/);
1316
1536
  if (!jsonMatch)
1317
1537
  return null;
1318
1538
  try {
@@ -1367,6 +1587,7 @@ This is the conversation history:
1367
1587
  and give the response in next JSON format.
1368
1588
  ${evaluator.expectedResponseType}
1369
1589
  `;
1590
+ // TODO: evaluations gemini flashcard
1370
1591
  // Send evaluation request
1371
1592
  const evaluationMessages = [{ content: instructions, role: ChatRole.User }];
1372
1593
  // Not sure what strategy use, if works types should be definied in definitions, generarally response will be score, and feedback.
@@ -1392,67 +1613,111 @@ and give the response in next JSON format.
1392
1613
  }
1393
1614
  return jsonData;
1394
1615
  }
1395
- // Evaluate with a specific task and agent
1396
- async evaluateWithTask(messages, agentTask) {
1397
- // const { agent, task } = appAgentTask;
1398
- // Construct messages for the AI call
1616
+ // Helper function to get context messages based on type
1617
+ getContextMessages(messages, contextType) {
1618
+ const conversationMessages = messages.filter((message) => [ChatRole.User, ChatRole.Assistant].includes(message.role));
1619
+ if (conversationMessages.length === 0) {
1620
+ return [];
1621
+ }
1622
+ switch (contextType) {
1623
+ case ContextType.LastAssistantMessage: {
1624
+ const lastAssistant = conversationMessages
1625
+ .slice()
1626
+ .reverse()
1627
+ .find((m) => m.role === ChatRole.Assistant);
1628
+ return lastAssistant ? [lastAssistant] : [];
1629
+ }
1630
+ case ContextType.LastUserMessage: {
1631
+ const lastUser = conversationMessages
1632
+ .slice()
1633
+ .reverse()
1634
+ .find((m) => m.role === ChatRole.User);
1635
+ return lastUser ? [lastUser] : [];
1636
+ }
1637
+ case ContextType.LastDialog: {
1638
+ const lastUser = conversationMessages
1639
+ .slice()
1640
+ .reverse()
1641
+ .find((m) => m.role === ChatRole.User);
1642
+ const lastAssistant = conversationMessages
1643
+ .slice()
1644
+ .reverse()
1645
+ .find((m) => m.role === ChatRole.Assistant);
1646
+ const dialog = [];
1647
+ if (lastAssistant)
1648
+ dialog.push(lastAssistant);
1649
+ if (lastUser)
1650
+ dialog.push(lastUser);
1651
+ // Sort by timestamp if available, otherwise keep order [Assistant, User] if both exist
1652
+ // Assuming messageId or a timestamp property exists and is comparable
1653
+ // dialog.sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0)); // Example sorting
1654
+ return dialog;
1655
+ }
1656
+ case ContextType.Last2Dialogs: {
1657
+ const users = conversationMessages.filter((m) => m.role === ChatRole.User);
1658
+ const assistants = conversationMessages.filter((m) => m.role === ChatRole.Assistant);
1659
+ const lastTwoUsers = users.slice(-2);
1660
+ const lastTwoAssistants = assistants.slice(-2);
1661
+ const dialogs = [...lastTwoAssistants, ...lastTwoUsers];
1662
+ // Sort by timestamp if available
1663
+ // dialogs.sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0)); // Example sorting
1664
+ return dialogs;
1665
+ }
1666
+ case ContextType.AllConversation:
1667
+ default:
1668
+ return conversationMessages;
1669
+ }
1670
+ }
1671
+ /**
1672
+ * Evaluates a conversation context based on a specific task and attaches the result to a designated message.
1673
+ * @param agentTask The task definition for the evaluation.
1674
+ * @param messageToUpdate The specific ChatMessage object (user or assistant) to attach the evaluation results to.
1675
+ * @param contextType Determines which part of the conversation history to send as context (default: AllConversation).
1676
+ * @returns The JSON result of the evaluation.
1677
+ */
1678
+ async evaluateWithTask(agentTask, messageToUpdate, // Message to attach the evaluation to
1679
+ contextType = ContextType.AllConversation) {
1680
+ // Fetch the current conversation messages from the service
1681
+ const messages = this.conversationService.getMessagesSignal()();
1399
1682
  const requestMessages = [];
1400
- // Add system prompt if available
1401
- // Prioritize characterCard system_prompt, then description as fallback
1402
1683
  const systemPrompt = agentTask.systemPrompt || 'You are a helpful assistant.'; // Default fallback
1403
1684
  requestMessages.push({ role: ChatRole.System, content: systemPrompt });
1404
- // Add the main task content
1405
1685
  let taskContent = agentTask.task;
1406
1686
  if (agentTask.expectedResponseType) {
1407
- // Ensure proper formatting instructions
1408
1687
  taskContent += `\n\nPlease provide the response strictly in the following JSON format:\n${agentTask.expectedResponseType}`;
1409
1688
  }
1410
- // Note: Handling task.sources might require fetching data if they are identifiers/URLs.
1411
- // For now, assuming sources are simple strings if provided.
1412
- // if (agentTask.sources && agentTask.sources.length > 0) {
1413
- // taskContent += `\n\nUse the following sources as context:\n${agentTask.sources.join('\n---\n')}`;
1414
- // }
1415
- // Add conversation history
1416
- if (messages.length > 0) {
1417
- const conversationHistory = messages
1418
- .filter((message) => [ChatRole.User, ChatRole.Assistant].includes(message.role))
1419
- .map((message) => `${message.role}: ${message.content}`)
1420
- .join('\n');
1421
- taskContent += `\n\nHere is the conversation history:\n${conversationHistory}`;
1422
- }
1423
- let lastUserMessage = null;
1424
- for (let i = messages.length - 1; i >= 0; i--) {
1425
- // reverse the array to find the last user message
1426
- if (messages[i].role === ChatRole.User) {
1427
- lastUserMessage = messages[i];
1428
- break;
1689
+ if (contextType === ContextType.CurrentMessageContent) {
1690
+ taskContent += `\n\nHere is text: ${messageToUpdate.content}`;
1691
+ }
1692
+ else {
1693
+ // Get context messages based on the specified type
1694
+ const contextMessages = this.getContextMessages(messages, contextType);
1695
+ if (contextMessages.length > 0) {
1696
+ const conversationHistoryString = contextMessages
1697
+ .map((message) => `${message.role}: ${message.content}`) // Format messages
1698
+ .join('\n');
1699
+ taskContent += `\n\nHere is the relevant conversation history:\n${conversationHistoryString}`;
1429
1700
  }
1430
1701
  }
1702
+ // No longer need to find the last user message here, it's passed in as messageToUpdate
1431
1703
  requestMessages.push({ role: ChatRole.User, content: taskContent });
1432
1704
  try {
1433
- // Send evaluation request to the AI service
1434
- // Pass the model if the service method supports it
1435
- console.log(requestMessages);
1436
1705
  const response = await this.agentCardService.callChatCompletion({ messages: requestMessages, model: agentTask.model });
1437
- // Extract JSON from response, handling potential errors
1438
- const jsonData = extractJsonFromResponse(response.content);
1439
- // Optional: Update specific signals or show tailored toasts for this function
1440
- // Example: this.taskEvaluationResultSignal.set(jsonData);
1706
+ let jsonData = extractJsonFromResponse(response.content);
1707
+ if (jsonData === null) {
1708
+ jsonData = { score: 1, feedback: 'Good job!' };
1709
+ }
1441
1710
  this.evaluationResultSignal.set(jsonData);
1442
1711
  // Update score if available
1443
1712
  if (jsonData.score) {
1444
- // Assuming a similar scoring logic as evaluateConversation
1445
1713
  // Adjust the multiplier or condition if needed for task-based evaluation
1446
1714
  if (jsonData.score <= 3) {
1447
1715
  this.updateScore(jsonData.score * 10);
1448
1716
  }
1449
1717
  }
1450
- // Example: this.toastService.success({ title: 'Task Evaluation Complete' });
1451
- // Update the evaluated message with the result
1452
- if (lastUserMessage?.messageId && jsonData) {
1453
- this.conversationService.updateMessage(lastUserMessage.messageId, {
1454
- evaluation: jsonData, // Add evaluation data to the message
1455
- });
1718
+ // Update the specific message provided by the caller with the result
1719
+ if (messageToUpdate?.messageId && jsonData) {
1720
+ this.conversationService.updateMessage(messageToUpdate.messageId, { evaluation: jsonData });
1456
1721
  }
1457
1722
  return jsonData;
1458
1723
  }
@@ -1466,16 +1731,14 @@ and give the response in next JSON format.
1466
1731
  throw error;
1467
1732
  }
1468
1733
  }
1469
- // Update score with limits
1470
1734
  updateScore(additionalScore) {
1471
1735
  this.scoreSignal.update((currentScore) => {
1472
1736
  const newScore = currentScore + additionalScore;
1473
1737
  return newScore > 100 ? 100 : newScore;
1474
1738
  });
1475
1739
  }
1476
- // Reset score
1477
1740
  resetScore() {
1478
- this.scoreSignal.set(10);
1741
+ this.scoreSignal.set(0);
1479
1742
  }
1480
1743
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: EvaluationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1481
1744
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: EvaluationService, providedIn: 'root' }); }
@@ -1496,8 +1759,9 @@ class ChatFooterComponent {
1496
1759
  // Inputs
1497
1760
  this.isAIThinking = input(false);
1498
1761
  // @deprecated in favor of appAgentTask
1499
- this.evaluatorAgentCard = input();
1500
- this.evaluatorAgentTask = input();
1762
+ // readonly evaluatorAgentCard = input<IMiniAgentCard>();
1763
+ this.taskOnUserMessage = input();
1764
+ this.taskOnAssistantMessage = input();
1501
1765
  this.micSettings = input({ useWhisper: true, lang: 'en' });
1502
1766
  // Outputs
1503
1767
  this.sendMessage = output();
@@ -1555,31 +1819,46 @@ class ChatFooterComponent {
1555
1819
  return;
1556
1820
  }
1557
1821
  const text = this.chatInputControl.value;
1558
- const message = {
1559
- content: text,
1560
- role: ChatRole.User,
1561
- };
1562
- // Emit the message for parent components that need it
1563
- // this.sendMessage.emit(message);
1564
- // Clear the input field
1822
+ const message = { content: text, role: ChatRole.User };
1565
1823
  this.chatInputControl.setValue('');
1566
- // Send the user message to the conversation service
1567
- await this.conversationService.sendUserMessage(message);
1568
- // Evaluate conversation after sending message
1569
- this.evaluateConversation();
1824
+ const result = await this.conversationService.sendUserMessage(message);
1825
+ const messages = this.conversationService.getMessagesSignal()();
1826
+ if (result.assistantMessageId) {
1827
+ const assistantMessage = messages.find((m) => m.messageId === result.assistantMessageId);
1828
+ this.evaluateConversation(assistantMessage, 'assistant');
1829
+ }
1830
+ if (result?.userMessageId) {
1831
+ // Evaluate the message only if it was successfully sent and we have the user message ID
1832
+ // Find the message using the userMessageId from the result object
1833
+ const messageToEvaluate = messages.find((m) => m.messageId === result.userMessageId);
1834
+ if (messageToEvaluate) {
1835
+ this.evaluateConversation(messageToEvaluate, 'user');
1836
+ }
1837
+ else {
1838
+ // Log the correct ID in the error message
1839
+ console.error(`Could not find message with ID ${result.userMessageId} to evaluate.`);
1840
+ }
1841
+ }
1842
+ else {
1843
+ console.warn('Message sending failed or returned no ID, skipping evaluation.');
1844
+ }
1570
1845
  }
1571
1846
  /**
1572
1847
  * Evaluate conversation using evaluator agent
1573
1848
  */
1574
- async evaluateConversation() {
1575
- const messages = this.conversationService.getMessagesSignal()();
1576
- if (this.evaluatorAgentCard()) {
1577
- const data = await this.evaluationService.evaluateConversation(messages, this.evaluatorAgentCard());
1578
- console.log(data);
1849
+ /**
1850
+ * Evaluate a specific message using the evaluator agent task.
1851
+ * @param messageToEvaluate The ChatMessage (typically the user's last message) to evaluate.
1852
+ */
1853
+ async evaluateConversation(messageToEvaluate, type) {
1854
+ let agentTask;
1855
+ if (type === 'user' && this.taskOnUserMessage()) {
1856
+ agentTask = this.taskOnUserMessage();
1857
+ const data = await this.evaluationService.evaluateWithTask(agentTask, messageToEvaluate);
1579
1858
  }
1580
- if (this.evaluatorAgentTask()) {
1581
- const data = await this.evaluationService.evaluateWithTask(messages, this.evaluatorAgentTask());
1582
- console.log(data);
1859
+ else if (type === 'assistant' && this.taskOnAssistantMessage()) {
1860
+ agentTask = this.taskOnAssistantMessage();
1861
+ const data = await this.evaluationService.evaluateWithTask(agentTask, messageToEvaluate, ContextType.CurrentMessageContent);
1583
1862
  }
1584
1863
  }
1585
1864
  handleAudioRecorded(event) {
@@ -1609,7 +1888,8 @@ class ChatFooterComponent {
1609
1888
  audioUrl: URL.createObjectURL(eventBlob),
1610
1889
  isLoading: true,
1611
1890
  };
1612
- const messageId = this.conversationService.addMessage(message);
1891
+ // Instead of creating message, eneble animation user is spacking
1892
+ // const messageId = this.conversationService.addMessage(message);
1613
1893
  // Get transcription from audio
1614
1894
  const transcription = await this.agentCardService.getAudioTranscriptions(eventBlob, null);
1615
1895
  // Create updated message with transcription and isLoading set to false
@@ -1619,24 +1899,36 @@ class ChatFooterComponent {
1619
1899
  transcription: transcription || undefined,
1620
1900
  isLoading: false,
1621
1901
  };
1622
- // Send message to conversation
1623
1902
  // The evaluation will happen automatically in the conversation service
1624
- await this.conversationService.sendUserMessage(updatedMessage, messageId);
1903
+ await this.conversationService.sendUserMessage(updatedMessage);
1625
1904
  }
1626
1905
  finally {
1627
1906
  this.isGettingTranscription = false;
1907
+ // console.log(this.isAIThinking());
1908
+ //
1628
1909
  }
1629
1910
  }
1630
1911
  setScore(score) {
1631
1912
  this.evaluationService.setScore(100);
1632
1913
  }
1914
+ /**
1915
+ * Stops the microphone recording if the component is available.
1916
+ */
1917
+ stopMic() {
1918
+ if (this.micComponent) {
1919
+ this.micComponent.stopRecording();
1920
+ }
1921
+ }
1633
1922
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1634
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.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 }, evaluatorAgentTask: { classPropertyName: "evaluatorAgentTask", publicName: "evaluatorAgentTask", 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\n (audioRecorded)=\"handleAudioRecorded($event)\"\n (statusChanged)=\"handleMicStatusChanged($event)\"\n [continueListening]=\"shouldContinueListening()\" />\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 @if(evaluatorAgentTask()) {\n <div (click)=\"setScore(100)\">\n <p-progressbar showValue=\"false\" [value]=\"score()\" [style]=\"{ height: '6px' }\" />\n </div>\n }\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: ["continueListening"], outputs: ["statusChanged", "audioRecorded", "error"] }] }); }
1923
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.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 }, taskOnUserMessage: { classPropertyName: "taskOnUserMessage", publicName: "taskOnUserMessage", isSignal: true, isRequired: false, transformFunction: null }, taskOnAssistantMessage: { classPropertyName: "taskOnAssistantMessage", publicName: "taskOnAssistantMessage", isSignal: true, isRequired: false, transformFunction: null }, micSettings: { classPropertyName: "micSettings", publicName: "micSettings", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sendMessage: "sendMessage", textInputChanged: "textInputChanged" }, viewQueries: [{ propertyName: "micComponent", first: true, predicate: MicVadComponent, descendants: true }], ngImport: i0, template: "<div class=\"progress-input\">\n <div class=\"input-container\">\n <app-mic-vad\n (audioRecorded)=\"handleAudioRecorded($event)\"\n (statusChanged)=\"handleMicStatusChanged($event)\"\n [continueListening]=\"shouldContinueListening()\" />\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 @if(taskOnUserMessage()) {\n <div (click)=\"setScore(100)\">\n <p-progressbar showValue=\"false\" [value]=\"score()\" [style]=\"{ height: '6px' }\" />\n </div>\n }\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: ["continueListening"], outputs: ["statusChanged", "audioRecorded", "error"] }] }); }
1635
1924
  }
1636
1925
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatFooterComponent, decorators: [{
1637
1926
  type: Component,
1638
- args: [{ selector: 'dc-chat-footer', standalone: true, imports: [ReactiveFormsModule, ProgressBarModule, TextareaModule, ButtonModule, MicVadComponent], template: "<div class=\"progress-input\">\n <div class=\"input-container\">\n <app-mic-vad\n (audioRecorded)=\"handleAudioRecorded($event)\"\n (statusChanged)=\"handleMicStatusChanged($event)\"\n [continueListening]=\"shouldContinueListening()\" />\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 @if(evaluatorAgentTask()) {\n <div (click)=\"setScore(100)\">\n <p-progressbar showValue=\"false\" [value]=\"score()\" [style]=\"{ height: '6px' }\" />\n </div>\n }\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"] }]
1639
- }], ctorParameters: () => [] });
1927
+ args: [{ selector: 'dc-chat-footer', standalone: true, imports: [ReactiveFormsModule, ProgressBarModule, TextareaModule, ButtonModule, MicVadComponent], template: "<div class=\"progress-input\">\n <div class=\"input-container\">\n <app-mic-vad\n (audioRecorded)=\"handleAudioRecorded($event)\"\n (statusChanged)=\"handleMicStatusChanged($event)\"\n [continueListening]=\"shouldContinueListening()\" />\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 @if(taskOnUserMessage()) {\n <div (click)=\"setScore(100)\">\n <p-progressbar showValue=\"false\" [value]=\"score()\" [style]=\"{ height: '6px' }\" />\n </div>\n }\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"] }]
1928
+ }], ctorParameters: () => [], propDecorators: { micComponent: [{
1929
+ type: ViewChild,
1930
+ args: [MicVadComponent]
1931
+ }] } });
1640
1932
 
1641
1933
  const ICONS = {
1642
1934
  chat: `<svg viewBox="0 0 24 24" fill="currentColor">
@@ -1715,12 +2007,70 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
1715
2007
  `, 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"] }]
1716
2008
  }], ctorParameters: () => [] });
1717
2009
 
2010
+ // Given a text that can be in markdown but only in asterisk like *italic* or **bold** or ***bold italic***
2011
+ // Example Hola que tal es *Hey What's up* en inglés
2012
+ // i want to extract in array every word like this.
2013
+ // [{word: 'Hola', tag: ''}, {word: 'que', tag: ''}, {word: 'tal', tag: ''}, {word: 'es', tag: ''}, {word: 'Hey', tag: 'italic'}, {word: 'What's', tag: 'italic'}, {word: 'up', tag: 'italic'}, {word: 'en', tag: ''}, {word: 'inglés', tag: ''}]
2014
+ function extractTags(text) {
2015
+ const result = [];
2016
+ const tagStack = [];
2017
+ // Regex to match markdown markers (***, **, *) or spaces or sequences of non-space/non-asterisk characters (words with punctuation)
2018
+ const regex = /(\*\*\*|\*\*|\*|\s+|[^\s\*]+)/g;
2019
+ let match;
2020
+ while ((match = regex.exec(text)) !== null) {
2021
+ const token = match[0];
2022
+ if (token.trim() === '') {
2023
+ // Ignore spaces
2024
+ continue;
2025
+ }
2026
+ if (token === '***') {
2027
+ if (tagStack.length > 0 && tagStack[tagStack.length - 1] === 'bold italic') {
2028
+ tagStack.pop(); // Closing bold italic
2029
+ }
2030
+ else {
2031
+ tagStack.push('bold italic'); // Opening bold italic
2032
+ }
2033
+ }
2034
+ else if (token === '**') {
2035
+ if (tagStack.length > 0 && tagStack[tagStack.length - 1] === 'bold') {
2036
+ tagStack.pop(); // Closing bold
2037
+ }
2038
+ else {
2039
+ tagStack.push('bold'); // Opening bold
2040
+ }
2041
+ }
2042
+ else if (token === '*') {
2043
+ if (tagStack.length > 0 && tagStack[tagStack.length - 1] === 'italic') {
2044
+ tagStack.pop(); // Closing italic
2045
+ }
2046
+ else {
2047
+ tagStack.push('italic'); // Opening italic
2048
+ }
2049
+ }
2050
+ else {
2051
+ // It's a word (including punctuation)
2052
+ const currentTag = tagStack.length > 0 ? tagStack[tagStack.length - 1] : '';
2053
+ result.push({ word: token, tag: currentTag });
2054
+ }
2055
+ }
2056
+ // Note: This implementation assumes correctly matched opening and closing tags.
2057
+ // Unclosed tags at the end of the string will be ignored.
2058
+ return result;
2059
+ }
2060
+
1718
2061
  class TextHighlighterComponent {
1719
2062
  constructor() {
2063
+ // Inputs
1720
2064
  this.message = input.required(); // Or input.required<MessageAudio>() if always expected
1721
- this.highlightedWords = signal([]); // Signal for highlighted words
2065
+ // Outputs
2066
+ this.playAudio = output();
2067
+ this.audioCompleted = output();
2068
+ this.wordClicked = output(); // Output for clicked word with messageId
2069
+ // Signal States
2070
+ this.wordWithMeta = signal([]); // Signal for plain text words when no transcription
1722
2071
  this.isPlaying = signal(false); // Signal for play state
1723
2072
  // Signals State computed from the input message
2073
+ this.wordsWithMetaState = [];
1724
2074
  this.iconState = computed(() => {
1725
2075
  if (this.isLoading()) {
1726
2076
  return 'isLoading';
@@ -1742,25 +2092,21 @@ class TextHighlighterComponent {
1742
2092
  const msg = this.message(); // Read the input signal
1743
2093
  return msg?.text || msg?.content || 'N/A';
1744
2094
  });
1745
- this.classTag = computed(() => this.message()?.tag);
2095
+ this.classTag = computed(() => this.message()?.tag); // tag can be at message level or word level this is for message level
1746
2096
  // Computed signal for transcription availability
1747
2097
  this.hasTranscription = computed(() => {
1748
2098
  const hasTranscription = !!this.message()?.transcriptionTimestamps && this.message()?.transcriptionTimestamps.length > 0;
1749
2099
  return hasTranscription;
1750
2100
  });
1751
- this.playAudio = output();
1752
- this.audioCompleted = output();
1753
2101
  this.audioElement = null; // Audio element for playback
1754
2102
  this.destroy$ = new Subject(); // Cleanup localsubject
1755
2103
  // Inject services
1756
- this.cdr = inject(ChangeDetectorRef); // Keep for now, might remove if template fully signal-driven
1757
2104
  this.destroyRef = inject(DestroyRef);
1758
2105
  // Effect to react to message changes and re-initialize
1759
2106
  effect(() => {
1760
2107
  const currentMsg = this.message(); // Read the input signal
1761
- console.log('Input message signal changed:', currentMsg);
1762
- if (currentMsg) {
1763
- // Re-run initialization logic whenever the message signal changes
2108
+ // Somethimes message changes but audio is already playing.
2109
+ if (currentMsg && !this.isPlaying()) {
1764
2110
  this.initializeBasedOnMessage(currentMsg);
1765
2111
  // Check if shouldPlayAudio flag is set
1766
2112
  if (currentMsg.shouldPlayAudio) {
@@ -1768,21 +2114,15 @@ class TextHighlighterComponent {
1768
2114
  }
1769
2115
  }
1770
2116
  else {
1771
- // Handle case where message becomes undefined (cleanup?)
1772
- this.cleanupAudio();
1773
- this.highlightedWords.set([]);
2117
+ // this.cleanupAudio();
1774
2118
  }
1775
- // No cdr.markForCheck() needed here usually if template bindings use signals/computed
1776
2119
  });
1777
2120
  // Keep the effect for highlightedWords for now, might be redundant if template binds directly
1778
2121
  effect(() => {
1779
- this.highlightedWords();
1780
- this.cdr.markForCheck(); // Keep if template uses non-signal bindings for highlighted words
2122
+ // some how i need to isolate the reading, becouse reading in main effect is very dangerous since later is set and produces infinite loop
2123
+ this.wordsWithMetaState = this.wordWithMeta();
1781
2124
  });
1782
2125
  }
1783
- /**
1784
- * Track function for ngFor to improve performance
1785
- */
1786
2126
  trackByIndex(index, item) {
1787
2127
  return index;
1788
2128
  }
@@ -1805,75 +2145,58 @@ class TextHighlighterComponent {
1805
2145
  }
1806
2146
  // Initialize words and sync based on transcription presence
1807
2147
  if (this.hasTranscription()) {
1808
- // Use computed signal
1809
- const timestamps = msg.transcriptionTimestamps || [];
1810
- this.initializeHighlightedWords(timestamps); // Pass timestamps
1811
- this.subcribeToAudioSync(timestamps); // Pass timestamps
2148
+ const wordsAndTimestamps = [];
2149
+ // NOTE: This merge is only going to work if transcriptionTimestamps and wordsWithMetaState have the same length
2150
+ // wordsWithMetaState is initialized here in initializePlainTextWords but transcriptionTimestamps in the father, check later if i have bugs.
2151
+ msg?.transcriptionTimestamps?.forEach((word, index) => {
2152
+ wordsAndTimestamps.push({ ...word, ...this.wordsWithMetaState[index], index });
2153
+ });
2154
+ console.log(wordsAndTimestamps);
2155
+ this.subcribeToAudioSync(wordsAndTimestamps); // Pass timestamps
1812
2156
  }
1813
2157
  else {
1814
2158
  this.subscribeToEndAudio();
2159
+ this.initializePlainTextWords(msg.text || msg.content || ''); // Initialize plain text words
1815
2160
  }
1816
2161
  }
1817
- /**
1818
- * Initialize the audio element and set up event listeners
1819
- */
1820
2162
  initializeAudio(audioUrl) {
1821
- // Clean up any existing audio element and listeners first
1822
2163
  this.cleanupAudio();
1823
2164
  this.audioElement = new Audio(audioUrl); // Use passed URL
1824
2165
  }
1825
- /**
1826
- * Initialize highlighted words from transcription timestamps
1827
- */
1828
- initializeHighlightedWords(timestamps) {
1829
- const initialWords = timestamps.map((word, index) => ({
1830
- word: word.word,
1831
- index,
1832
- isHighlighted: false,
1833
- }));
1834
- this.highlightedWords.set(initialWords);
1835
- }
1836
- /**
1837
- * Set up audio synchronization with text
1838
- */
1839
2166
  subcribeToAudioSync(timestamps) {
1840
- // Accept timestamps
1841
2167
  if (!this.audioElement) {
1842
2168
  // Guard against missing audio element
1843
2169
  return;
1844
2170
  }
1845
- // Important: Clean up previous listeners before setting up new ones
1846
2171
  this.destroy$.next(); // Signal previous subscriptions to complete
1847
- // Listen to timeupdate events
2172
+ console.log('Subscribing to audio sync with timestamps:', timestamps);
1848
2173
  fromEvent(this.audioElement, 'timeupdate')
1849
- .pipe(takeUntilDestroyed(this.destroyRef), // Use DestroyRef for component lifecycle
1850
- takeUntil(this.destroy$), // Use manual subject for re-initialization cleanup
2174
+ .pipe(takeUntilDestroyed(this.destroyRef), takeUntil(this.destroy$), // Use manual subject for re-initialization cleanup
1851
2175
  map(() => this.audioElement?.currentTime || 0))
1852
2176
  .subscribe((currentTime) => {
1853
2177
  // Use the passed timestamps array
1854
2178
  const updatedWords = timestamps.map((word, index) => {
1855
2179
  const isHighlighted = currentTime >= word.start - 0.15 && currentTime < word.end + 0.15;
1856
- return { word: word.word, index, isHighlighted };
2180
+ console.log(isHighlighted, currentTime, word.start, word.end, word.start - 0.15 && currentTime < word.end + 0.15);
2181
+ return { word: word.word, index, isHighlighted, tag: word.tag };
1857
2182
  });
1858
- this.highlightedWords.set(updatedWords);
2183
+ this.wordWithMeta.set(updatedWords);
1859
2184
  });
1860
2185
  // Listen to ended event for cleanup
1861
2186
  fromEvent(this.audioElement, 'ended')
1862
2187
  .pipe(takeUntilDestroyed(this.destroyRef), takeUntil(this.destroy$))
1863
2188
  .subscribe(() => {
1864
2189
  // Reset highlighting when audio ends
1865
- const resetWords = this.highlightedWords().map((word) => ({
2190
+ const resetWords = this.wordWithMeta().map((word) => ({
1866
2191
  // Read current words signal
1867
2192
  ...word,
1868
2193
  isHighlighted: false,
1869
2194
  }));
1870
- this.highlightedWords.set(resetWords);
2195
+ this.wordWithMeta.set(resetWords);
1871
2196
  // Set isPlaying to false when audio finishes
1872
2197
  this.isPlaying.set(false);
1873
- // Emit audio completed event with the current message
1874
2198
  const currentMsg = this.message();
1875
2199
  if (currentMsg) {
1876
- // Reset the shouldPlayAudio flag
1877
2200
  currentMsg.shouldPlayAudio = false;
1878
2201
  this.audioCompleted.emit(currentMsg);
1879
2202
  }
@@ -1906,11 +2229,11 @@ class TextHighlighterComponent {
1906
2229
  this.audioElement = null;
1907
2230
  this.destroy$.next(); // Ensure listeners tied to this audio instance are cleaned up
1908
2231
  // Reset highlighting immediately on cleanup
1909
- const resetWords = this.highlightedWords().map((word) => ({
2232
+ const resetWords = this.wordWithMeta().map((word) => ({
1910
2233
  ...word,
1911
2234
  isHighlighted: false,
1912
2235
  }));
1913
- this.highlightedWords.set(resetWords);
2236
+ this.wordWithMeta.set(resetWords);
1914
2237
  }
1915
2238
  }
1916
2239
  /**
@@ -1933,7 +2256,6 @@ class TextHighlighterComponent {
1933
2256
  // For simplicity, let's re-call initializeAudio here if needed,
1934
2257
  // though ideally the effect handles it.
1935
2258
  this.initializeAudio(currentMsg.audioUrl); // Ensure it's created if somehow missed
1936
- // this.initializeBasedOnMessage(currentMsg);
1937
2259
  this.startAudioPlayback();
1938
2260
  }
1939
2261
  else if (currentMsg) {
@@ -1942,23 +2264,44 @@ class TextHighlighterComponent {
1942
2264
  }
1943
2265
  }
1944
2266
  /**
1945
- * Start audio playback and handle any setup needed
2267
+ * Initialize plain text words by splitting the message text
1946
2268
  */
2269
+ initializePlainTextWords(text) {
2270
+ const words = extractTags(text);
2271
+ const plainWords = words.map((word, index) => ({
2272
+ word: word.word,
2273
+ index: index,
2274
+ isHighlighted: false,
2275
+ tag: word.tag,
2276
+ }));
2277
+ this.wordWithMeta.set(plainWords);
2278
+ }
2279
+ onWordClick(wordData) {
2280
+ const currentMsg = this.message();
2281
+ if (!currentMsg) {
2282
+ console.warn('Cannot emit wordClicked: message input is not available.');
2283
+ return;
2284
+ }
2285
+ // Determine the correct data structure based on input type
2286
+ const clickedWord = 'isHighlighted' in wordData
2287
+ ? { word: wordData.word, index: wordData.index, messageId: currentMsg.messageId }
2288
+ : { ...wordData, messageId: currentMsg.messageId };
2289
+ this.wordClicked.emit(clickedWord);
2290
+ }
1947
2291
  startAudioPlayback() {
1948
2292
  if (!this.audioElement)
1949
2293
  return;
1950
- // Play the audio
1951
2294
  this.audioElement.play().catch((error) => {
1952
2295
  console.error('Error playing audio:', error);
1953
2296
  });
1954
2297
  this.isPlaying.set(true); // Set play state to true
1955
2298
  }
1956
2299
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: TextHighlighterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1957
- 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 }); }
2300
+ 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", wordClicked: "wordClicked" }, ngImport: i0, template: "<div class=\"audio-text-sync-container\">\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 @for (word of wordWithMeta(); track trackByIndex($index, word)) {\n <span [class]=\"word.tag\" [class.highlight]=\"word.isHighlighted\" (click)=\"onWordClick(word)\" [attr.id]=\"'word-' + message().messageId + '-' + word.index\"\n >{{ word.word }}&nbsp;</span\n >\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}.italic{font-style:italic;color:#6495ed}.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 }); }
1958
2301
  }
1959
2302
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: TextHighlighterComponent, decorators: [{
1960
2303
  type: Component,
1961
- 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"] }]
2304
+ args: [{ selector: 'dc-text-highlighter', standalone: true, imports: [IconsComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"audio-text-sync-container\">\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 @for (word of wordWithMeta(); track trackByIndex($index, word)) {\n <span [class]=\"word.tag\" [class.highlight]=\"word.isHighlighted\" (click)=\"onWordClick(word)\" [attr.id]=\"'word-' + message().messageId + '-' + word.index\"\n >{{ word.word }}&nbsp;</span\n >\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}.italic{font-style:italic;color:#6495ed}.em_strong{font-weight:700;font-style:italic;color:inherit}\n"] }]
1962
2305
  }], ctorParameters: () => [] });
1963
2306
 
1964
2307
  class MessageOrchestratorComponent {
@@ -1967,7 +2310,6 @@ class MessageOrchestratorComponent {
1967
2310
  this.messages = input.required();
1968
2311
  this.messageRole = input.required();
1969
2312
  this.messagesSignal = signal([]);
1970
- // Effect to update messagesSignal when input messages change
1971
2313
  this.messagesEffect = effect(() => {
1972
2314
  // Get the latest messages from the input
1973
2315
  const currentMessages = this.messages();
@@ -2104,12 +2446,16 @@ class MessageOrchestratorComponent {
2104
2446
  this.isGenerating = false;
2105
2447
  }
2106
2448
  }
2449
+ onWordClicked(wordData) {
2450
+ console.log('Word clicked in MessageOrchestrator:', wordData);
2451
+ this.conversationService.notifyWordClicked(wordData);
2452
+ }
2107
2453
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MessageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2108
- 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 }); }
2454
+ 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: "<div class=\"message-orchestrator-container\">\n @for (message of messagesSignal(); track message.messageId) {\n <dc-text-highlighter\n [message]=\"message\"\n (playAudio)=\"playAudio.emit($event)\"\n (audioCompleted)=\"onAudioCompleted($event)\"\n (wordClicked)=\"onWordClicked($event)\" />\n }\n</div>\n", styles: [":host{display:block}.word-options-popup{position:absolute;border:1px solid #ccc;background:#fff;padding:5px;z-index:1000}\n"], dependencies: [{ kind: "component", type: TextHighlighterComponent, selector: "dc-text-highlighter", inputs: ["message"], outputs: ["playAudio", "audioCompleted", "wordClicked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2109
2455
  }
2110
2456
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MessageOrchestratorComponent, decorators: [{
2111
2457
  type: Component,
2112
- 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"] }]
2458
+ args: [{ selector: 'dc-message-orchestrator', imports: [TextHighlighterComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"message-orchestrator-container\">\n @for (message of messagesSignal(); track message.messageId) {\n <dc-text-highlighter\n [message]=\"message\"\n (playAudio)=\"playAudio.emit($event)\"\n (audioCompleted)=\"onAudioCompleted($event)\"\n (wordClicked)=\"onWordClicked($event)\" />\n }\n</div>\n", styles: [":host{display:block}.word-options-popup{position:absolute;border:1px solid #ccc;background:#fff;padding:5px;z-index:1000}\n"] }]
2113
2459
  }] });
2114
2460
 
2115
2461
  const EVALUATION_EMOJIS = {
@@ -2131,7 +2477,7 @@ class ChatMessageComponent {
2131
2477
  this.messageTranslation = computed(() => this.chatMessage()?.translation);
2132
2478
  this.isUserMessage = computed(() => this.chatMessage()?.role === ChatRole.User);
2133
2479
  this.evaluationEmoji = computed(() => {
2134
- const score = this.chatMessage()?.evaluation?.score;
2480
+ const score = this.chatMessage()?.evaluation?.['score'];
2135
2481
  if (score === null || score === undefined) {
2136
2482
  return '';
2137
2483
  }
@@ -2159,14 +2505,17 @@ class ChatMessageComponent {
2159
2505
  }
2160
2506
  showEvaluation() {
2161
2507
  console.log('showEvaluation', this.chatMessage().evaluation);
2162
- alert(this.chatMessage().evaluation?.feedback || 'No feedback available');
2508
+ alert(this.chatMessage().evaluation?.['feedback'] || 'No feedback available');
2509
+ }
2510
+ printData() {
2511
+ console.log(this.chatMessage());
2163
2512
  }
2164
2513
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatMessageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2165
- 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]=\"chatMessage().imgUrl\" alt=\"AI\" 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 <!-- Evaluation if available for user message -->\n @if (isUserMessage() && chatMessage().evaluation) {\n <div style=\"position: absolute; bottom: -10px; left: 20px\">\n <span>{{ evaluationEmoji() }}</span>\n <span class=\"pointer\" (click)=\"showEvaluation()\"> \uD83E\uDDD0 </span>\n </div>\n }\n </div>\n <!-- Avatar for user messages -->\n @if (isUserMessage()) {\n <div class=\"avatar-container\">\n <div class=\"avatar user-avatar\">\n <img [src]=\"chatMessage().imgUrl\" 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;position:relative}.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 }); }
2514
+ 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\" (click)=\"printData()\">\n <img [src]=\"chatMessage().imgUrl\" alt=\"AI\" 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 (chatMessage().evaluation?.['translation']) {\n <div class=\"translation\">\n <hr class=\"divider\" />\n {{ chatMessage().evaluation?.['translation'] }}\n </div>\n } @if (isUserMessage() && chatMessage().evaluation) {\n <div class=\"translation\" style=\"color: white\">\n <hr class=\"divider\" />\n {{ chatMessage().evaluation?.['feedback'] }}\n </div>\n\n <div style=\"position: absolute; bottom: -10px; left: 20px\">\n <span>{{ evaluationEmoji() }}</span>\n <span class=\"pointer\" (click)=\"showEvaluation()\"> \uD83E\uDDD0 </span>\n </div>\n\n }\n </div>\n\n <!-- Avatar for user messages -->\n @if (isUserMessage()) {\n <div class=\"avatar-container\" (click)=\"printData()\">\n <div class=\"avatar user-avatar\">\n <img [src]=\"chatMessage()?.imgUrl || '/assets/defaults/avatar_user.jpg'\" alt=\"User\" class=\"avatar-image\" />\n </div>\n </div>\n }\n\n <br />\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;position:relative}.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 }); }
2166
2515
  }
2167
2516
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatMessageComponent, decorators: [{
2168
2517
  type: Component,
2169
- 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]=\"chatMessage().imgUrl\" alt=\"AI\" 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 <!-- Evaluation if available for user message -->\n @if (isUserMessage() && chatMessage().evaluation) {\n <div style=\"position: absolute; bottom: -10px; left: 20px\">\n <span>{{ evaluationEmoji() }}</span>\n <span class=\"pointer\" (click)=\"showEvaluation()\"> \uD83E\uDDD0 </span>\n </div>\n }\n </div>\n <!-- Avatar for user messages -->\n @if (isUserMessage()) {\n <div class=\"avatar-container\">\n <div class=\"avatar user-avatar\">\n <img [src]=\"chatMessage().imgUrl\" 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;position:relative}.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"] }]
2518
+ 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\" (click)=\"printData()\">\n <img [src]=\"chatMessage().imgUrl\" alt=\"AI\" 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 (chatMessage().evaluation?.['translation']) {\n <div class=\"translation\">\n <hr class=\"divider\" />\n {{ chatMessage().evaluation?.['translation'] }}\n </div>\n } @if (isUserMessage() && chatMessage().evaluation) {\n <div class=\"translation\" style=\"color: white\">\n <hr class=\"divider\" />\n {{ chatMessage().evaluation?.['feedback'] }}\n </div>\n\n <div style=\"position: absolute; bottom: -10px; left: 20px\">\n <span>{{ evaluationEmoji() }}</span>\n <span class=\"pointer\" (click)=\"showEvaluation()\"> \uD83E\uDDD0 </span>\n </div>\n\n }\n </div>\n\n <!-- Avatar for user messages -->\n @if (isUserMessage()) {\n <div class=\"avatar-container\" (click)=\"printData()\">\n <div class=\"avatar user-avatar\">\n <img [src]=\"chatMessage()?.imgUrl || '/assets/defaults/avatar_user.jpg'\" alt=\"User\" class=\"avatar-image\" />\n </div>\n </div>\n }\n\n <br />\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;position:relative}.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"] }]
2170
2519
  }] });
2171
2520
 
2172
2521
  class ChatMessagesListComponent {
@@ -2178,7 +2527,7 @@ class ChatMessagesListComponent {
2178
2527
  this.conversationService = inject(ConversationService);
2179
2528
  this.elementRef = inject(ElementRef);
2180
2529
  // State
2181
- this.aiIcon = 'assets/default/ai.png';
2530
+ this.aiIcon = 'assets/defaults/avatar_ai.webp';
2182
2531
  this.isThinking = this.conversationService.isThinking();
2183
2532
  this.messages = computed(() => {
2184
2533
  // Get the actual array of messages from the signal by calling it as a function
@@ -2211,11 +2560,11 @@ class ChatMessagesListComponent {
2211
2560
  return `${message.role}-${index}-${message.content.substring(0, 20)}`;
2212
2561
  }
2213
2562
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatMessagesListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2214
- 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 message.messageId) {\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 }); }
2563
+ 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 message.messageId) {\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;border-radius:20px;background:#fff}.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 }); }
2215
2564
  }
2216
2565
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatMessagesListComponent, decorators: [{
2217
2566
  type: Component,
2218
- 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 message.messageId) {\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"] }]
2567
+ 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 message.messageId) {\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;border-radius:20px;background:#fff}.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"] }]
2219
2568
  }], ctorParameters: () => [] });
2220
2569
 
2221
2570
  const SpeedDescription = {
@@ -2411,12 +2760,14 @@ class DCChatComponent {
2411
2760
  // Inputs
2412
2761
  this.chatUserSettings = this.userDataExchange.getUserChatSettings(); // Default to user data exchange
2413
2762
  // Signal Inputs
2414
- this.evaluatorAgentCard = input(); // @Deprecated in favor of appAgentTask
2415
- this.appAgentTask = input();
2763
+ // readonly evaluatorAgentCard = input<IMiniAgentCard>(); // @Deprecated in favor of appAgentTask
2764
+ this.taskOnUserMessage = input();
2765
+ this.taskOnAssistantMessage = input();
2416
2766
  this.parseDict = input({});
2417
2767
  // Outputs
2418
- this.sendMessage = output(); // Not implemented, should notifies whatever happened inside the chat
2768
+ this.sendMessage = output(); // Notifies about various events happening inside the chat
2419
2769
  this.goalCompleted = output(); // notifies when user completes goal (score reaches 100)
2770
+ // readonly wordClicked = output<WordData>(); // Replaced by sendMessage with ChatEventType.WordClicked
2420
2771
  // Signals States
2421
2772
  this.messages = signal([]);
2422
2773
  // States
@@ -2430,9 +2781,18 @@ class DCChatComponent {
2430
2781
  this.goalCompleted.emit();
2431
2782
  }
2432
2783
  });
2784
+ // Effect to watch for word clicks from the service
2785
+ effect(() => {
2786
+ const clickedWordData = this.conversationService.getWordClickedSignal()();
2787
+ if (clickedWordData) {
2788
+ // Emit through the consolidated sendMessage output
2789
+ this.sendMessage.emit({ type: ChatEventType.WordClicked, payload: clickedWordData });
2790
+ // Optional: Reset the signal in the service if you only want to emit once per click
2791
+ // this.conversationService.notifyWordClicked(null);
2792
+ }
2793
+ });
2433
2794
  }
2434
2795
  async ngOnInit() {
2435
- console.log(this.parseDict());
2436
2796
  if (this.conversationSettings) {
2437
2797
  this.conversationService.setDestroyed(false);
2438
2798
  await this.conversationService.initConversationWithSettings(this.conversationSettings);
@@ -2440,10 +2800,13 @@ class DCChatComponent {
2440
2800
  else {
2441
2801
  await this.conversationService.initConversationWithAgentCard(this.agentCard, this.parseDict());
2442
2802
  }
2803
+ this.evaluationService.resetScore();
2443
2804
  }
2444
2805
  ngOnDestroy() {
2445
2806
  // Mark conversation as destroyed to prevent async operations
2446
2807
  this.conversationService.setDestroyed(true);
2808
+ // Ensure the microphone is stopped when the chat component is destroyed
2809
+ this.chatFooterComponent?.stopMic();
2447
2810
  }
2448
2811
  /**
2449
2812
  * Open chat settings dialog
@@ -2477,12 +2840,15 @@ class DCChatComponent {
2477
2840
  await this.ngOnInit();
2478
2841
  }
2479
2842
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCChatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2480
- 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 }, appAgentTask: { classPropertyName: "appAgentTask", publicName: "appAgentTask", isSignal: true, isRequired: false, transformFunction: null }, parseDict: { classPropertyName: "parseDict", publicName: "parseDict", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sendMessage: "sendMessage", goalCompleted: "goalCompleted" }, 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\" [evaluatorAgentTask]=\"appAgentTask()\"> </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", "evaluatorAgentTask", "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 }); }
2843
+ 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 }, taskOnUserMessage: { classPropertyName: "taskOnUserMessage", publicName: "taskOnUserMessage", isSignal: true, isRequired: false, transformFunction: null }, taskOnAssistantMessage: { classPropertyName: "taskOnAssistantMessage", publicName: "taskOnAssistantMessage", isSignal: true, isRequired: false, transformFunction: null }, parseDict: { classPropertyName: "parseDict", publicName: "parseDict", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sendMessage: "sendMessage", goalCompleted: "goalCompleted" }, providers: [DialogService], viewQueries: [{ propertyName: "chatFooterComponent", first: true, predicate: ChatFooterComponent, descendants: true }], 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\" [taskOnUserMessage]=\"taskOnUserMessage()\" [taskOnAssistantMessage]=\"taskOnAssistantMessage()\"> </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", "taskOnUserMessage", "taskOnAssistantMessage", "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 }); }
2481
2844
  }
2482
2845
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCChatComponent, decorators: [{
2483
2846
  type: Component,
2484
- 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\" [evaluatorAgentTask]=\"appAgentTask()\"> </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"] }]
2485
- }], ctorParameters: () => [], propDecorators: { chatUserSettings: [{
2847
+ 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\" [taskOnUserMessage]=\"taskOnUserMessage()\" [taskOnAssistantMessage]=\"taskOnAssistantMessage()\"> </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"] }]
2848
+ }], ctorParameters: () => [], propDecorators: { chatFooterComponent: [{
2849
+ type: ViewChild,
2850
+ args: [ChatFooterComponent]
2851
+ }], chatUserSettings: [{
2486
2852
  type: Input
2487
2853
  }], conversationSettings: [{
2488
2854
  type: Input
@@ -2837,6 +3203,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
2837
3203
  type: Input
2838
3204
  }] } });
2839
3205
 
3206
+ // import { extractJsonFromResponse } from '@dataclouder/ngx-agent-cards';
2840
3207
  class DCAgentCardFormComponent {
2841
3208
  getSettings() {
2842
3209
  const imageSettings = {
@@ -3219,7 +3586,7 @@ class DCAgentCardFormComponent {
3219
3586
  textEngine: TextEngines.SimpleText,
3220
3587
  conversationType: ConversationType.General,
3221
3588
  });
3222
- const jsonData = this.extractJsonFromResponse(response.content);
3589
+ const jsonData = extractJsonFromResponse(response.content);
3223
3590
  if (jsonData) {
3224
3591
  this.toastService.success({ title: 'Character generated', subtitle: 'No te olvides de guardar cambios si estas seguro' });
3225
3592
  this.form.controls.characterCard.patchValue({ data: jsonData });
@@ -3232,18 +3599,6 @@ class DCAgentCardFormComponent {
3232
3599
  }
3233
3600
  this.isGenerating = false;
3234
3601
  }
3235
- extractJsonFromResponse(content) {
3236
- const jsonMatch = content.match(/\{[\s\S]*?\}/); // Match everything between first { and }
3237
- if (!jsonMatch)
3238
- return null;
3239
- try {
3240
- return JSON.parse(jsonMatch[0]);
3241
- }
3242
- catch (error) {
3243
- console.error('Error parsing JSON:', error);
3244
- return null;
3245
- }
3246
- }
3247
3602
  async removeSticker(sticker) {
3248
3603
  console.log('remove sticker', sticker);
3249
3604
  this.storageService.deleteImage(sticker.path);
@@ -3278,7 +3633,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
3278
3633
  DynamicDialogModule,
3279
3634
  PopoverModule,
3280
3635
  ProviderSelectorComponent,
3281
- AccountPlatformForm
3636
+ AccountPlatformForm,
3282
3637
  ], template: "<div class=\"top-buttons\">\n <button pButton severity=\"info\" (click)=\"checkPrompt()\" label=\"\uD83D\uDC41\uFE0F Ver instrucciones finales \uD83D\uDCD3\"></button>\n\n <button pButton severity=\"info\" (click)=\"goToDetails()\" label=\"\uD83D\uDCAC Conversar\"></button>\n <button pButton severity=\"primary\" (click)=\"saveConversation()\" label=\"\uD83D\uDCBE Guardar cambios\"></button>\n</div>\n\n<div class=\"top-buttons\">\n <p-button severity=\"help\" (click)=\"translate()\" label=\"\uD83D\uDD04 Traducir\"></p-button>\n <p-button [loading]=\"isGenerating\" severity=\"help\" (click)=\"generateCharacter()\" label=\"Generar \uD83E\uDDBE\"></p-button>\n\n <p-button severity=\"info\" (click)=\"downloadConversation()\" label=\"\uD83D\uDCC1 Exportar \u2B07\uFE0F\"></p-button>\n <p-button severity=\"info\" (click)=\"importConversation()\" label=\"\uD83C\uDCCF Importar \u2B06\uFE0F\"></p-button>\n</div>\n\n<br />\n<br />\n<form [formGroup]=\"form\" class=\"conversation-form\">\n <div class=\"form-grid\">\n <div class=\"left-column\">\n <div style=\"display: flex; gap: 15px\">\n <div class=\"form-field\">\n <label for=\"version\">Version: {{ form.controls.version.value }} <span pTooltip=\"Version number of the conversation\">\u2139\uFE0F</span></label>\n </div>\n\n <div class=\"form-field\">\n <label for=\"id\"\n >ID: <span pTooltip=\"Unique identifier for this conversation\"> {{ form.controls.id.value }} \u2139\uFE0F</span></label\n >\n </div>\n </div>\n\n <div class=\"form-field\">\n <label for=\"title\">Title <span pTooltip=\"T\u00EDtulo de la conversaci\u00F3n\">\u2139\uFE0F</span></label>\n <input pInputText id=\"title\" type=\"text\" formControlName=\"title\" />\n @if(form.controls.title.errors?.['required'] && form.controls.title.touched){\n <div class=\"error\"> Title is required </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"lang\">Language <span pTooltip=\"Select the primary language for the conversation\">\u2139\uFE0F</span></label>\n <p-select\n id=\"lang\"\n [options]=\"languageOptions\"\n formControlName=\"lang\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Language'\"></p-select>\n </div>\n\n <div formGroupName=\"conversationSettings\" class=\"group\">\n <h3>Conversation Settings <span pTooltip=\"Additional information about the conversation\">\u2139\uFE0F</span></h3>\n\n <div class=\"form-field\">\n <label for=\"textEngine\">\n Text Engine\n <span\n class=\"cursor-pointer\"\n (click)=\"textEngineDialog.toggle($event)\"\n pTooltip=\"Sistema de generaci\u00F3n de texto y audios. Client: el cliente llama al servidor en cada dialogo de voz/personaje, es optimo para historias, Server SSML: se sintetiza todo el audio en uno solo con los distintos cambios de voz/personaje, util para la reflexi\u00F3n porque es bilingue, utiliza dialogos en ingles y espa\u00F1ol en el mismo dialogo/audio\"\n >\u2139\uFE0F</span\n >\n </label>\n\n <p-select\n id=\"textEngine\"\n [options]=\"textEngineOptions\"\n formControlName=\"textEngine\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Text Engine'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"conversationType\">Conversation Type <span pTooltip=\"Choose the type of conversation interaction\">\u2139\uFE0F</span></label>\n <p-select\n id=\"conversationType\"\n [options]=\"conversationOptions\"\n formControlName=\"conversationType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Conversation Type'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label> Auto Start <span pTooltip=\"Start conversation automatically\">\u2139\uFE0F</span> </label>\n <p-toggleSwitch formControlName=\"autoStart\"> </p-toggleSwitch>\n </div>\n\n <div formGroupName=\"tts\" class=\"group\">\n <h3>TTS Settings <span pTooltip=\"Text-to-Speech configuration options\">\u2139\uFE0F</span></h3>\n\n <div class=\"form-field\">\n <label for=\"voice\">Voice <span pTooltip=\"Select the primary voice for text-to-speech\">\u2139\uFE0F</span></label>\n <p-select\n id=\"voice\"\n [options]=\"voiceTTSOptions\"\n formControlName=\"voice\"\n optionLabel=\"name\"\n optionValue=\"id\"\n [placeholder]=\"'Select Voice'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"secondaryVoice\">Secondary Voice <span pTooltip=\"Select an alternative voice for text-to-speech\">\u2139\uFE0F</span></label>\n <p-select\n id=\"secondaryVoice\"\n [options]=\"voiceTTSOptions\"\n formControlName=\"secondaryVoice\"\n optionLabel=\"name\"\n optionValue=\"id\"\n [placeholder]=\"'Select Secondary Voice'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"speed\">Speed <span pTooltip=\"Set the speech rate for text-to-speech conversion\">\u2139\uFE0F</span></label>\n <p-select\n id=\"speed\"\n [options]=\"audioSpeedOptions\"\n formControlName=\"speed\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Speed'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"speedRate\">Speed Rate <span pTooltip=\"Adjust the rate of speech delivery\">\u2139\uFE0F</span></label>\n <input pInputText id=\"speedRate\" type=\"number\" formControlName=\"speedRate\" step=\"0.1\" />\n </div>\n </div>\n </div>\n\n <div formGroupName=\"metaApp\" class=\"group\">\n <h3>Meta Information <span pTooltip=\"Additional information about the conversation\">\u2139\uFE0F</span></h3>\n <div class=\"form-field\">\n <label for=\"authorId\">Author ID <span pTooltip=\"Unique identifier for the conversation author\">\u2139\uFE0F</span></label>\n <input pInputText id=\"authorId\" type=\"text\" formControlName=\"authorId\" />\n </div>\n\n <div class=\"form-field\">\n <label for=\"authorEmail\">Author Email \u2139\uFE0F</label>\n <input pInputText id=\"authorEmail\" type=\"email\" formControlName=\"authorEmail\" />\n @if (form.get('metaApp.authorEmail')?.errors?.['email'] && form.get('metaApp.authorEmail')?.touched) {\n <div class=\"error\">\n Please enter a valid email address\n </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"takenCount\"\n >Taken Count <span pTooltip=\"Es el contador de cuantas veces se ha tomado esta conversaci\u00F3n, no sirve por ahora\"> \u2139\uFE0F</span></label\n >\n <input pInputText id=\"takenCount\" type=\"number\" formControlName=\"takenCount\" />\n </div>\n\n <div class=\"form-field checkbox\">\n <label>\n <p-checkbox [binary]=\"true\" formControlName=\"isPublic\" />\n Public\n </label>\n </div>\n\n <div class=\"form-field checkbox\">\n <label>\n <p-checkbox [binary]=\"true\" formControlName=\"isPublished\" />\n Published\n </label>\n </div>\n </div>\n\n <div class=\"group\">\n <h4>Model Settings <span pTooltip=\"AI model configuration\">\u2139\uFE0F</span></h4>\n\n <dc-provider-selector [parentForm]=\"form.controls.model\"></dc-provider-selector>\n </div>\n\n <div class=\"group\">\n <h4>Gestion de cuentas</h4>\n @if(form.controls.accounts){\n <account-platform-form [formArray]=\"form.controls.accounts\"></account-platform-form>\n\n }\n </div>\n </div>\n\n <div class=\"right-column\">\n <div style=\"position: relative; min-height: 60px\">\n <img [src]=\"conversation?.assets?.bannerImg?.url || 'assets/images/default_banner.webp'\" class=\"main-banner-image-card\" />\n @if(!conversation?.assets?.bannerImg?.url && agentCardId) {\n\n <dc-cropper-modal\n style=\"position: absolute; bottom: 10px; right: 10px\"\n #cropperBanner\n id=\"cropperBanner\"\n [buttonLabel]=\"conversation?.assets?.bannerImg?.url ? 'Cambiar el banner' : 'Cargar un banner'\"\n [imgStorageSettings]=\"bannerImgSettings()\"\n [currentStorage]=\"conversation?.assets?.bannerImg\"\n (onFileSelected)=\"onImageSelected($event)\"\n (imageUploaded)=\"onImageUploaded($event, 'bannerImg')\"></dc-cropper-modal>\n\n }\n </div>\n <div style=\"position: relative\">\n <img [src]=\"conversation?.assets?.image?.url || 'assets/images/default_2_3.webp'\" class=\"main-image-card\" />\n @if (!agentCardId) {\n <button pButton (click)=\"saveConversation()\"> Guarda el scenario para subir la imagen</button>\n } @else {\n\n <dc-cropper-modal\n style=\"position: absolute; bottom: 10px; left: 50%\"\n id=\"cropperCardImage\"\n #cropperCardImage\n [buttonLabel]=\"conversation?.assets?.image?.url ? 'Cambiar imagen' : 'Cargar una imagen'\"\n [imgStorageSettings]=\"imageStorageSettings()\"\n (onFileSelected)=\"onImageSelected($event)\"\n (imageUploaded)=\"onImageUploaded($event, 'image')\"></dc-cropper-modal>\n }\n </div>\n\n <div>\n <h4>Agregar stickers</h4>\n\n <dc-cropper-modal\n id=\"cropperCardImage\"\n #cropperStickers\n [buttonLabel]=\"'agregar sticker'\"\n [imgStorageSettings]=\"stickerStorageSettings\"\n (onFileSelected)=\"onImageSelected($event)\"\n (imageUploaded)=\"onImageUploaded($event, 'sticker')\"></dc-cropper-modal>\n </div>\n\n <div style=\"display: flex; flex-wrap: wrap; gap: 10px\">\n @for (sticker of conversation?.assets?.stickers; track sticker.url) {\n <div style=\"position: relative\">\n <img width=\"100\" [src]=\"sticker.url\" alt=\"\" />\n <p-button (click)=\"removeSticker(sticker)\" class=\"remove-sticker\" icon=\"pi pi-times\" [rounded]=\"true\" [text]=\"true\" severity=\"danger\" />\n </div>\n }\n </div>\n\n <!-- <input pInputText type=\"file\" accept=\"image/*\" (change)=\"onImageSelected($event)\" /> -->\n\n <div formGroupName=\"characterCard\">\n <div formGroupName=\"data\" class=\"card-group\">\n <h3>Character Card <span pTooltip=\"Informaci\u00F3n de la ficha del personaje\">\u2139\uFE0F</span></h3>\n <div class=\"form-field\">\n <label for=\"cardName\">Name <span pTooltip=\"El nombre del personaje\">\u2139\uFE0F</span></label>\n <input pInputText id=\"cardName\" type=\"text\" formControlName=\"name\" />\n @if (form.get('characterCard.data.name')?.errors?.['required'] && form.get('characterCard.data.name')?.touched) {\n <div class=\"error\">\n Name is required\n </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardDescription\">Description <span pTooltip=\"Descripci\u00F3n detallada del personaje\">\u2139\uFE0F</span></label>\n <textarea class=\"textmin\" rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardDescription\" formControlName=\"description\"></textarea>\n @if (form.get('characterCard.data.description')?.errors?.['required'] && form.get('characterCard.data.description')?.touched) {\n <div class=\"error\">\n Description is required\n </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardScenario\">Scenario <span pTooltip=\"Describe the context or setting for the conversation\">\u2139\uFE0F</span></label>\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardScenario\" formControlName=\"scenario\"></textarea>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardFirstMessage\">\n First Message\n <span pTooltip=\"Es muy importante que la historia inicie bien, ya que es el patr\u00F3n inicial para la AI, respetar las convenciones de texto\"\n >\u2139\uFE0F</span\n >\n\n <p-togglebutton\n [formControl]=\"markdownForm.controls.seeMarkdown\"\n [onLabel]=\"'Editar'\"\n [offLabel]=\"'Ver Markdown Texto'\"\n size=\"small\"\n styleClass=\"min-w-16\"\n (onChange)=\"checkCdr()\" />\n </label>\n\n @if(markdownForm.controls.seeMarkdown.value){\n <div [innerHTML]=\"form.controls.characterCard.controls.data.controls.first_mes.value | mdToHtmlArray\"></div>\n }@else{\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardFirstMessage\" formControlName=\"first_mes\"> </textarea>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"mes_example\">Mensajes de Ejemplo <span pTooltip=\"Importante para el estilo de la conversaci\u00F3n\">\u2139\uFE0F</span></label>\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"mes_example\" formControlName=\"mes_example\"></textarea>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardCreatorNotes\">Creator Notes <span pTooltip=\"son solo notas del creador, no afecta nada a la conversaci\u00F3n\">\u2139\uFE0F</span></label>\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardCreatorNotes\" formControlName=\"creator_notes\"></textarea>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardSystemPrompt\">System Prompt (Opcional) <span pTooltip=\"Instrucciones del sistema para la conversaci\u00F3n\">\u2139\uFE0F</span></label>\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" id=\"cardSystemPrompt\" formControlName=\"system_prompt\"></textarea>\n @if (form.get('characterCard.data.system_prompt')?.errors?.['required'] && form.get('characterCard.data.system_prompt')?.touched) {\n <div\n class=\"error\"\n >\n System prompt is required\n </div>\n }\n </div>\n\n <div style=\"display: flex; flex-direction: column\">\n <label for=\"cardPostHistoryInstructions\"\n >Post-History Instructions (Opcional)\n <span\n pTooltip=\"Dejar en blanco, al menos que se sepa como funciona, esto se llama jailbreak, es para darle instrucciones finales y m\u00E1s importantes al modelo\"\n >\u2139\uFE0F</span\n ></label\n >\n <textarea rows=\"1\" pTextarea [autoResize]=\"true\" formControlName=\"post_history_instructions\"></textarea>\n </div>\n\n <div class=\"form-field\">\n <label for=\"cardAlternateGreetings\">Alternate Greetings <span pTooltip=\"Saludos alternativos para comenzar una historia diferente\">\u2139\uFE0F</span></label>\n <div class=\"array-field\">\n @for (greeting of form.controls.characterCard.controls.data.controls.alternate_greetings.controls; track greeting; let i = $index) {\n <div\n class=\"array-item\"\n style=\"position: relative\">\n <textarea\n pTextarea\n rows=\"1\"\n [autoResize]=\"true\"\n [id]=\"'cardAlternateGreeting' + i\"\n [formControl]=\"greeting\"\n (input)=\"updateArrayField('alternate_greetings', i, $event)\">\n </textarea>\n <button pButton severity=\"danger\" class=\"remove-button\" (click)=\"removeArrayItem('alternate_greetings', i)\">&#x2716;</button>\n </div>\n }\n <button pButton severity=\"info\" (click)=\"addArrayItem('alternate_greetings')\">Add Greeting</button>\n </div>\n </div>\n\n <div class=\"form-field\">\n <label pTooltip=\"Agrega las categorias\" for=\"cardTags\">Tags \u2139\uFE0F</label>\n <div class=\"array-field\">\n @for (tag of form.controls.characterCard.controls.data.controls.tags.controls; track tag; let i = $index) {\n <div class=\"array-item\">\n <input [id]=\"'cardTag' + i\" type=\"text\" [formControl]=\"tag\" (input)=\"updateArrayField('tags', i, $event)\" />\n <button pButton severity=\"danger\" (click)=\"removeArrayItem('tags', i)\">Remove</button>\n </div>\n }\n <button pButton severity=\"info\" (click)=\"addArrayItem('tags')\">Add Tag</button>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </form>\n\n <p-popover #textEngineDialog header=\"Text Engine Information\">\n <div class=\"p-4\">\n <h3>Text Engine Types</h3>\n <ul>\n <li> <strong>Texto Simple</strong> La conversaci\u00F3n es como chatgpt, preguntas y responde, es la m\u00E1s b\u00E1sica</li>\n\n <li\n ><strong>Multi Mensajes</strong> Utiliza markdown (recomendable entenderlo), sirve para darle formato al texto y sea m\u00E1s agradable de leer, el sistema\n puede partir dialogos que tienen distinto formato, como normal, cursiva y negritas, asi puede generar distintas voces y estilo para el narrador y\n personaje principal</li\n >\n <li\n ><strong>MD SSML :</strong> Markdown con Lenguaje de marcaci\u00F3n de s\u00EDntesis de voz (SSML), es tambien markdown pero a diferencia de multimessage, solo se\n presenta un mensaje. y la voz se genera para toda la linea,normalmente lo uso para conversaciones bilingues.</li\n >\n </ul>\n </div>\n </p-popover>\n\n <div class=\"float-button\">\n <p-button icon=\"pi pi-save\" (click)=\"saveConversation()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n </div>\n", styles: [".textmin{min-width:36vw}.main-image-card{max-width:280px;display:block;margin:0 auto;border-radius:8px}.main-banner-image-card{border-radius:8px}.remove-sticker{position:absolute;top:5px;right:5px}.conversation-form{max-width:100%;padding:20px;background-color:#fff;border-radius:8px;box-shadow:0 2px 4px #0000001a}.conversation-form .card-group{background-color:#f8f9fa;padding:20px;border-radius:6px;margin-bottom:24px}.conversation-form .card-group h3{margin:0 0 20px;color:#2c3e50;font-size:1.25rem}.conversation-form .form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:2rem;width:100%;max-width:100%}@media (max-width: 768px){.conversation-form .form-grid{grid-template-columns:1fr}}.conversation-form .form-field{margin-bottom:1.5rem;display:flex;flex-direction:column;gap:.5rem}.conversation-form .form-field label{font-weight:500}.conversation-form .form-field textarea{resize:vertical}.conversation-form .form-field.checkbox{flex-direction:row;align-items:center;gap:.5rem}.conversation-form .form-field.checkbox input[type=checkbox]{width:auto}.conversation-form .form-field .error{color:#dc3545;font-size:.875rem;margin-top:.25rem}.conversation-form .form-field .remove-button{position:absolute;border:none;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;cursor:pointer;top:-10px;right:-10px}.conversation-form .left-column,.conversation-form .right-column{display:flex;flex-direction:column;gap:1rem}.conversation-form .array-field{display:flex;flex-direction:column;gap:.5rem}.conversation-form .array-field .array-item{display:flex;gap:.5rem}.conversation-form .array-field .array-item input,.conversation-form .array-field .array-item textarea{flex:1}.conversation-form .array-field .array-item button{padding:.5rem}.conversation-form .array-field button[type=button]{background-color:#28a745;color:#fff;border:none;padding:8px 12px;border-radius:4px;cursor:pointer;transition:background-color .2s}.conversation-form .array-field button[type=button]:hover{background-color:#218838}.conversation-form .group,.conversation-form .meta-group,.conversation-form .card-group{background-color:#f8f9fa;padding:1rem;border-radius:4px;margin-bottom:1.5rem}.conversation-form .group h3,.conversation-form .meta-group h3,.conversation-form .card-group h3{margin-top:0;margin-bottom:1rem}.top-buttons{display:flex;justify-content:space-between;margin-bottom:2rem;gap:1rem}.top-buttons button{flex:1}::ng-deep em{font-weight:900;color:#014a93}.float-button{position:fixed;bottom:4rem;right:2rem;z-index:1000;display:flex;gap:1px}.float-button :host ::ng-deep .p-button{width:4rem;height:4rem;border-radius:50%}\n"] }]
3283
3638
  }], ctorParameters: () => [] });
3284
3639
 
@@ -3308,11 +3663,11 @@ class DCConversationCardUIComponent {
3308
3663
  this.onCardAction.emit({ event: 'delete', card: this.card() });
3309
3664
  }
3310
3665
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCConversationCardUIComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3311
- 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 }); }
3666
+ 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 <p class=\"text-xl font-bold text-shadow-lg/30\">{{ card().title }}</p>\n\n <!-- <h5 class=\"title\">\n <span [innerHTML]=\"card().characterCard?.data.description | truncate : 100\"></span>\n </h5> -->\n\n <p style=\"margin-top: 40px\">\n <span class=\"title text-shadow-lg/30\" [innerHTML]=\"card().characterCard?.data.creator_notes | truncate : 200\"></span>\n </p>\n\n @if(card().taken){\n\n <div style=\"position: absolute; bottom: 10px; left: 10px\">\n <p-tag icon=\"pi pi-check-circle\" severity=\"secondary\" value=\"Tomada\" [rounded]=\"true\" />\n </div>\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:1rem;color:#fff;background:linear-gradient(to bottom,#0003,#0000001a);height:100%;display:flex;flex-direction:column;justify-content:space-between}.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"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i4$2.Tag, selector: "p-tag", inputs: ["style", "styleClass", "severity", "value", "icon", "rounded"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3312
3667
  }
3313
3668
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCConversationCardUIComponent, decorators: [{
3314
3669
  type: Component,
3315
- args: [{ selector: 'dc-agent-card-default-ui', imports: [PopoverModule, TruncatePipe, ButtonModule, SpeedDialModule, CardModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<p-card class=\"card-image\">\n @if(showOptions()) {\n <div style=\"position: absolute; top: 5px; right: 5px; z-index: 1000\">\n <p-speeddial\n [model]=\"speedDialModel\"\n [radius]=\"70\"\n type=\"quarter-circle\"\n direction=\"down-left\"\n [buttonProps]=\"{ severity: 'primary', rounded: true, outlined: true, raised: true }\" />\n </div>\n }\n\n <img [src]=\"card()?.assets?.image?.url || 'assets/images/default_conversation_card.webp'\" alt=\"\" />\n\n <div (click)=\"onDetails()\" class=\"content\">\n <h2 class=\"title-text\">{{ card().title }}</h2>\n\n <h5 class=\"title\">\n <span [innerHTML]=\"card().characterCard?.data.description | truncate : 100\"></span>\n </h5>\n\n <p-button\n (click)=\"onDetails()\"\n [style]=\"{ position: 'absolute', bottom: '10px', right: '10px' }\"\n icon=\"pi pi-comment\"\n [rounded]=\"true\"\n severity=\"info\"\n [outlined]=\"true\"\n [raised]=\"true\" />\n </div>\n</p-card>\n", styles: [":host{display:block}:host ::ng-deep .p-card{height:100%}:host ::ng-deep .p-card-body{height:100%;padding:0!important}.card-image{width:280px;height:380px;position:relative;align-items:center;display:block;padding:-10px}.card-image img{position:absolute;z-index:3;width:100%;height:100%;opacity:.75;object-fit:cover;transition:opacity .5s}.content{position:absolute;inset:0;z-index:4;padding:1.5rem;color:#fff;background:linear-gradient(to bottom,#0003,#0000001a);height:100%;display:flex;flex-direction:column}.content:hover{background:linear-gradient(to bottom,color-mix(in srgb,var(--p-primary-color) 20%,transparent),color-mix(in srgb,black 10%,transparent));cursor:pointer}\n"] }]
3670
+ args: [{ selector: 'dc-agent-card-default-ui', imports: [PopoverModule, TruncatePipe, ButtonModule, SpeedDialModule, CardModule, TagModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<p-card class=\"card-image\">\n @if(showOptions()) {\n <div style=\"position: absolute; top: 5px; right: 5px; z-index: 1000\">\n <p-speeddial\n [model]=\"speedDialModel\"\n [radius]=\"70\"\n type=\"quarter-circle\"\n direction=\"down-left\"\n [buttonProps]=\"{ severity: 'primary', rounded: true, outlined: true, raised: true }\" />\n </div>\n }\n\n <img [src]=\"card()?.assets?.image?.url || 'assets/images/default_conversation_card.webp'\" alt=\"\" />\n\n <div (click)=\"onDetails()\" class=\"content\">\n <p class=\"text-xl font-bold text-shadow-lg/30\">{{ card().title }}</p>\n\n <!-- <h5 class=\"title\">\n <span [innerHTML]=\"card().characterCard?.data.description | truncate : 100\"></span>\n </h5> -->\n\n <p style=\"margin-top: 40px\">\n <span class=\"title text-shadow-lg/30\" [innerHTML]=\"card().characterCard?.data.creator_notes | truncate : 200\"></span>\n </p>\n\n @if(card().taken){\n\n <div style=\"position: absolute; bottom: 10px; left: 10px\">\n <p-tag icon=\"pi pi-check-circle\" severity=\"secondary\" value=\"Tomada\" [rounded]=\"true\" />\n </div>\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:1rem;color:#fff;background:linear-gradient(to bottom,#0003,#0000001a);height:100%;display:flex;flex-direction:column;justify-content:space-between}.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"] }]
3316
3671
  }] });
3317
3672
 
3318
3673
  // This component contains a really avanced strategy to dinamically render Conversation Cards Details so every app can implement it with their own Style and Behavior
@@ -3328,9 +3683,11 @@ class AgentCardListComponent extends PaginationBase {
3328
3683
  const route = inject(ActivatedRoute);
3329
3684
  const router = inject(Router);
3330
3685
  super(route, router);
3686
+ // Services
3331
3687
  this.agentCardService = inject(CONVERSATION_AI_TOKEN);
3332
3688
  this.toastService = inject(TOAST_ALERTS_TOKEN);
3333
3689
  this.cdr = inject(ChangeDetectorRef);
3690
+ // Inputs
3334
3691
  this.viewMode = 'cards';
3335
3692
  this.customCardComponent = input(undefined);
3336
3693
  this.showOptions = input(true);
@@ -3343,7 +3700,15 @@ class AgentCardListComponent extends PaginationBase {
3343
3700
  ngOnInit() {
3344
3701
  this.columns = DefaultColumns;
3345
3702
  // this.buttonActions = DefaultActions;
3346
- this.filterConfig.returnProps = { _id: 1, title: 1, assets: 1, description: 1, 'characterCard.data.description': 1, 'characterCard.data.name': 1 };
3703
+ this.filterConfig.returnProps = {
3704
+ _id: 1,
3705
+ title: 1,
3706
+ assets: 1,
3707
+ description: 1,
3708
+ 'characterCard.data.description': 1,
3709
+ 'characterCard.data.name': 1,
3710
+ 'characterCard.data.creator_notes': 1,
3711
+ };
3347
3712
  this.loadConversationCards();
3348
3713
  this.cardComponent = this.customCardComponent() || DCConversationCardUIComponent;
3349
3714
  }
@@ -3458,7 +3823,7 @@ class AgentCardListComponent extends PaginationBase {
3458
3823
  }
3459
3824
  }
3460
3825
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AgentCardListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3461
- 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)\" (onNew)=\"newAgentCard()\"></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", "items", "customFilters"], outputs: ["onFilterAction", "onChangeSort", "onNew"] }, { 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: ["columns", "tableData", "actions"], outputs: ["onAction"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
3826
+ 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]=\"showOptions()\" (onFilterAction)=\"doFilterBarAction($event)\" (onNew)=\"newAgentCard()\"></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", "items", "customFilters"], outputs: ["onFilterAction", "onChangeSort", "onNew"] }, { 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: ["columns", "tableData", "actions"], outputs: ["onAction"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
3462
3827
  }
3463
3828
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AgentCardListComponent, decorators: [{
3464
3829
  type: Component,
@@ -3472,7 +3837,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
3472
3837
  SpeedDialModule,
3473
3838
  QuickTableComponent,
3474
3839
  CommonModule,
3475
- ], standalone: true, template: "<dc-filter-bar [isAdmin]=\"true\" (onFilterAction)=\"doFilterBarAction($event)\" (onNew)=\"newAgentCard()\"></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"] }]
3840
+ ], standalone: true, template: "<dc-filter-bar [isAdmin]=\"showOptions()\" (onFilterAction)=\"doFilterBarAction($event)\" (onNew)=\"newAgentCard()\"></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"] }]
3476
3841
  }], ctorParameters: () => [], propDecorators: { viewMode: [{
3477
3842
  type: Input
3478
3843
  }], outlets: [{
@@ -3538,11 +3903,11 @@ class DcAgentCardDetailsComponent {
3538
3903
  this.cdr.markForCheck();
3539
3904
  }
3540
3905
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DcAgentCardDetailsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3541
- 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", 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" }] }); }
3906
+ 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", 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}::ng-deep .info-button .p-button{background-color:#ffffff47}\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" }] }); }
3542
3907
  }
3543
3908
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DcAgentCardDetailsComponent, decorators: [{
3544
3909
  type: Component,
3545
- args: [{ selector: 'dc-agent-card-details', standalone: true, imports: [ButtonModule, CardModule, ParseCardPipe], template: "<div style=\"display: flex; justify-content: center; align-items: center\">\n <p-card>\n <div class=\"card-container\">\n <img class=\"card-image\" [src]=\"agentCard?.assets?.image?.url || 'assets/images/default_conversation_card.webp'\" alt=\"\" />\n\n <div class=\"info-button\" (click)=\"toggleInfoLayer()\">\n <p-button icon=\"pi pi-arrow-down-left\" [rounded]=\"true\" [raised]=\"true\" severity=\"primary\" [outlined]=\"true\" />\n </div>\n\n <div style=\"position: absolute; bottom: 20px; right: 50%; transform: translateX(50%); z-index: 3\">\n <p-button size=\"large\" label=\"Iniciar Conversaci\u00F3n\" [rounded]=\"true\" (click)=\"startConversation()\" />\n </div>\n\n <div class=\"info-layer\" [class.active]=\"showInfoLayer\">\n <div class=\"info-content\">\n <h1\n ><strong>{{ agentCard?.title }}</strong></h1\n >\n <p>{{ agentCard?.characterCard.data?.name }}</p>\n\n @if (agentCard?.characterCard.data?.scenario) {\n <div class=\"scenario\">\n <h4>Scenario</h4>\n <p>{{ agentCard?.characterCard.data.scenario | parseCard : agentCard }}</p>\n </div>\n }\n </div>\n </div>\n </div>\n </p-card>\n</div>\n", 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"] }]
3910
+ args: [{ selector: 'dc-agent-card-details', standalone: true, imports: [ButtonModule, CardModule, ParseCardPipe], template: "<div style=\"display: flex; justify-content: center; align-items: center\">\n <p-card>\n <div class=\"card-container\">\n <img class=\"card-image\" [src]=\"agentCard?.assets?.image?.url || 'assets/images/default_conversation_card.webp'\" alt=\"\" />\n\n <div class=\"info-button\" (click)=\"toggleInfoLayer()\">\n <p-button icon=\"pi pi-arrow-down-left\" [rounded]=\"true\" [raised]=\"true\" severity=\"primary\" [outlined]=\"true\" />\n </div>\n\n <div style=\"position: absolute; bottom: 20px; right: 50%; transform: translateX(50%); z-index: 3\">\n <p-button size=\"large\" label=\"Iniciar Conversaci\u00F3n\" [rounded]=\"true\" (click)=\"startConversation()\" />\n </div>\n\n <div class=\"info-layer\" [class.active]=\"showInfoLayer\">\n <div class=\"info-content\">\n <h1\n ><strong>{{ agentCard?.title }}</strong></h1\n >\n <p>{{ agentCard?.characterCard.data?.name }}</p>\n\n @if (agentCard?.characterCard.data?.scenario) {\n <div class=\"scenario\">\n <h4>Scenario</h4>\n <p>{{ agentCard?.characterCard.data.scenario | parseCard : agentCard }}</p>\n </div>\n }\n </div>\n </div>\n </div>\n </p-card>\n</div>\n", 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}::ng-deep .info-button .p-button{background-color:#ffffff47}\n"] }]
3546
3911
  }], propDecorators: { agentCardId: [{
3547
3912
  type: Input
3548
3913
  }] } });
@@ -3556,5 +3921,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
3556
3921
  * Generated bundle index. Do not edit.
3557
3922
  */
3558
3923
 
3559
- export { AgentCardListComponent, AgentCardsAbstractService, AudioService, AudioSpeed, CONVERSATION_AI_TOKEN, ChatMessage, ChatRole, ChatUserSettings, ConversationDTO, ConversationMessagesDTO, ConversationType, ConversationTypeOptions, DCAgentCardFormComponent, DCChatComponent, DCConversationCardUIComponent, DCConversationPromptBuilderService, DcAgentCardDetailsComponent, EAccountsPlatform, EvalResultStringDefinition, 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 };
3924
+ export { AgentCardListComponent, AgentCardProgressStatus, AgentCardsAbstractService, AgentUserProgressService, AudioService, AudioSpeed, CONVERSATION_AI_TOKEN, ChatEventType, ChatMessage, ChatRole, ChatUserSettings, ContextType, ConversationDTO, ConversationMessagesDTO, ConversationType, ConversationTypeOptions, DCAgentCardFormComponent, DCChatComponent, DCConversationCardUIComponent, DCConversationPromptBuilderService, DcAgentCardDetailsComponent, EAccountsPlatform, EvalResultStringDefinition, LangCodeDescriptionEs, MessageAudio, MessageOrchestratorComponent, PopupService, 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 };
3560
3925
  //# sourceMappingURL=dataclouder-ngx-agent-cards.mjs.map