@dataclouder/ngx-agent-cards 0.1.4 → 0.1.6

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, computed, DestroyRef, ChangeDetectionStrategy, ElementRef, ChangeDetectorRef, ViewChild, 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
  }
@@ -1056,14 +1230,15 @@ class MessageProcessingService {
1056
1230
  return voiceCodes[Math.floor(Math.random() * voiceCodes.length)];
1057
1231
  }
1058
1232
  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
- }
1233
+ return voice_value;
1234
+ // voice should exist, there wont be validation.
1235
+ // const voice = VoiceTTSOptions.find((voice) => voice.id === voice_value);
1236
+ // if (voice) {
1237
+ // return voice.id;
1238
+ // } else {
1239
+ // console.error('Voice not found getting something random', voice_value);
1240
+ // return VoiceTTSOptions.find((voice) => voice.lang.includes(targetLang))?.id || '';
1241
+ // }
1067
1242
  }
1068
1243
  }
1069
1244
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MessageProcessingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
@@ -1150,6 +1325,7 @@ class ConversationService {
1150
1325
  this.isDestroyedSignal = signal(false);
1151
1326
  this.micStatusSignal = signal('ready');
1152
1327
  this.currentAudioStatus = signal(null);
1328
+ this.wordClickedSignal = signal(null); // Signal for clicked word
1153
1329
  // Var State
1154
1330
  this.avatarImages = {
1155
1331
  userImg: 'assets/defaults/avatar_user.png',
@@ -1162,6 +1338,20 @@ class ConversationService {
1162
1338
  getUserSettings() {
1163
1339
  return this.userDataExchange.getUserChatSettings();
1164
1340
  }
1341
+ /**
1342
+ * Notifies subscribers that a word has been clicked in a message.
1343
+ * @param wordData The data associated with the clicked word.
1344
+ */
1345
+ notifyWordClicked(wordData) {
1346
+ this.wordClickedSignal.set(wordData);
1347
+ }
1348
+ /**
1349
+ * Gets the signal that emits when a word is clicked.
1350
+ * @returns A signal emitting WordData or null.
1351
+ */
1352
+ getWordClickedSignal() {
1353
+ return this.wordClickedSignal;
1354
+ }
1165
1355
  // Add message to conversation
1166
1356
  addMessage(message) {
1167
1357
  this.messagesSignal.update((messages) => [...messages, message]);
@@ -1193,7 +1383,7 @@ class ConversationService {
1193
1383
  }
1194
1384
  setupConversationWithAgentCard(agentCard, parseDict = null) {
1195
1385
  // Set user AI avatar image
1196
- this.avatarImages.assistantImg = agentCard?.assets.image?.url || this.avatarImages.assistantImg;
1386
+ this.avatarImages.assistantImg = agentCard?.assets?.image?.url || this.avatarImages.assistantImg;
1197
1387
  const conversationSettings = this.conversationBuilder.buildConversationSettings(agentCard, parseDict);
1198
1388
  this.conversationSettingsSignal.set(conversationSettings);
1199
1389
  }
@@ -1207,7 +1397,7 @@ class ConversationService {
1207
1397
  console.log(this.avatarImages);
1208
1398
  const conversationSettings = this.conversationSettingsSignal();
1209
1399
  for (const i in conversationSettings.messages) {
1210
- conversationSettings.messages[i].messageId = 'msg_' + i;
1400
+ conversationSettings.messages[i].messageId = nanoid();
1211
1401
  }
1212
1402
  // Find first assistant message
1213
1403
  const firstAssistantMsg = conversationSettings?.messages.find((message) => message.role === ChatRole.Assistant);
@@ -1232,40 +1422,66 @@ class ConversationService {
1232
1422
  this.setupConversationWithAgentCard(agentCard, parseDict);
1233
1423
  await this.initConversation();
1234
1424
  }
1235
- // Send user message
1425
+ /**
1426
+ * Sends a user message, processes it, adds it to the conversation,
1427
+ * and triggers the AI response flow.
1428
+ * @param message The initial ChatMessage object from the user.
1429
+ * @param updateId Optional ID if this message should update an existing one (e.g., transcription added).
1430
+ * @param updateId Optional ID if this message should update an existing one (e.g., transcription added).
1431
+ * @returns A promise resolving to an object containing the user message ID and the assistant message ID (or null if an error occurred).
1432
+ */
1236
1433
  async sendUserMessage(message, updateId = null) {
1237
1434
  if (this.isThinkingSignal()) {
1238
- return;
1435
+ console.warn('AI is already thinking, message dropped.');
1436
+ return null; // Indicate message was not sent due to AI being busy
1239
1437
  }
1438
+ let userMessageId;
1439
+ // Add or update the user message
1240
1440
  if (updateId) {
1241
- this.updateMessage(updateId, message);
1441
+ // Ensure the update includes the ID if not already present in message object
1442
+ const messageToUpdate = { ...message, messageId: updateId };
1443
+ const updated = this.updateMessage(updateId, messageToUpdate);
1444
+ if (!updated) {
1445
+ console.error(`Failed to update message with ID: ${updateId}`);
1446
+ // Decide how to handle this - maybe throw an error or return null?
1447
+ return null;
1448
+ }
1449
+ userMessageId = updateId;
1242
1450
  }
1243
1451
  else {
1244
- // Add message to conversation
1452
+ // Process and add the new message
1245
1453
  const processedMessage = this.messageProcessingService.processMessage(message, this.conversationSettingsSignal(), this.avatarImages);
1454
+ // Ensure ID exists (processMessage should handle this, but fallback just in case)
1455
+ processedMessage.messageId = processedMessage.messageId || nanoid();
1246
1456
  this.addMessage(processedMessage);
1457
+ userMessageId = processedMessage.messageId;
1247
1458
  }
1248
- // Set thinking state
1459
+ // Set thinking state and get AI response
1249
1460
  this.isThinkingSignal.set(true);
1461
+ let assistantMessageId = null;
1250
1462
  try {
1251
- // Send to AI service
1252
- await this.sendCurrentConversation();
1463
+ assistantMessageId = await this.sendCurrentConversation();
1464
+ }
1465
+ catch (error) {
1466
+ console.error('Error sending conversation to AI:', error);
1467
+ // Handle error appropriately, maybe set a specific state or message
1253
1468
  }
1254
1469
  finally {
1255
1470
  this.isThinkingSignal.set(false);
1256
1471
  }
1472
+ return { userMessageId, assistantMessageId };
1257
1473
  }
1258
1474
  // Process assistant message
1259
1475
  // Send current conversation to AI
1260
1476
  async sendCurrentConversation() {
1261
1477
  if (this.isDestroyedSignal()) {
1262
- return;
1478
+ return null;
1263
1479
  }
1264
1480
  const messages = this.messagesSignal();
1265
1481
  const conversationSettings = this.conversationSettingsSignal();
1266
1482
  if (messages.length > 31) {
1267
1483
  // Safety limit to prevent infinite conversations
1268
- return;
1484
+ return null;
1269
1485
  }
1270
1486
  let conversationMessages = messages;
1271
1487
  // Add last prompt if available
@@ -1279,6 +1495,7 @@ class ConversationService {
1279
1495
  textEngine: conversationSettings.textEngine,
1280
1496
  model: conversationSettings.model || { modelName: '', provider: '' },
1281
1497
  };
1498
+ this.isThinkingSignal.set(true);
1282
1499
  // Call AI service
1283
1500
  const response = await this.agentCardService.callChatCompletion(conversation);
1284
1501
  if (!response) {
@@ -1290,6 +1507,7 @@ class ConversationService {
1290
1507
  // Add to messages
1291
1508
  this.addMessage(newMessage);
1292
1509
  this.isThinkingSignal.set(false);
1510
+ return newMessage.messageId;
1293
1511
  }
1294
1512
  // Reset conversation
1295
1513
  async resetConversation(agentCard) {
@@ -1367,6 +1585,7 @@ This is the conversation history:
1367
1585
  and give the response in next JSON format.
1368
1586
  ${evaluator.expectedResponseType}
1369
1587
  `;
1588
+ // TODO: evaluations gemini flashcard
1370
1589
  // Send evaluation request
1371
1590
  const evaluationMessages = [{ content: instructions, role: ChatRole.User }];
1372
1591
  // Not sure what strategy use, if works types should be definied in definitions, generarally response will be score, and feedback.
@@ -1392,67 +1611,108 @@ and give the response in next JSON format.
1392
1611
  }
1393
1612
  return jsonData;
1394
1613
  }
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
1614
+ // Helper function to get context messages based on type
1615
+ getContextMessages(messages, contextType) {
1616
+ const conversationMessages = messages.filter((message) => [ChatRole.User, ChatRole.Assistant].includes(message.role));
1617
+ if (conversationMessages.length === 0) {
1618
+ return [];
1619
+ }
1620
+ switch (contextType) {
1621
+ case ContextType.LastAssistantMessage: {
1622
+ const lastAssistant = conversationMessages
1623
+ .slice()
1624
+ .reverse()
1625
+ .find((m) => m.role === ChatRole.Assistant);
1626
+ return lastAssistant ? [lastAssistant] : [];
1627
+ }
1628
+ case ContextType.LastUserMessage: {
1629
+ const lastUser = conversationMessages
1630
+ .slice()
1631
+ .reverse()
1632
+ .find((m) => m.role === ChatRole.User);
1633
+ return lastUser ? [lastUser] : [];
1634
+ }
1635
+ case ContextType.LastDialog: {
1636
+ const lastUser = conversationMessages
1637
+ .slice()
1638
+ .reverse()
1639
+ .find((m) => m.role === ChatRole.User);
1640
+ const lastAssistant = conversationMessages
1641
+ .slice()
1642
+ .reverse()
1643
+ .find((m) => m.role === ChatRole.Assistant);
1644
+ const dialog = [];
1645
+ if (lastAssistant)
1646
+ dialog.push(lastAssistant);
1647
+ if (lastUser)
1648
+ dialog.push(lastUser);
1649
+ // Sort by timestamp if available, otherwise keep order [Assistant, User] if both exist
1650
+ // Assuming messageId or a timestamp property exists and is comparable
1651
+ // dialog.sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0)); // Example sorting
1652
+ return dialog;
1653
+ }
1654
+ case ContextType.Last2Dialogs: {
1655
+ const users = conversationMessages.filter((m) => m.role === ChatRole.User);
1656
+ const assistants = conversationMessages.filter((m) => m.role === ChatRole.Assistant);
1657
+ const lastTwoUsers = users.slice(-2);
1658
+ const lastTwoAssistants = assistants.slice(-2);
1659
+ const dialogs = [...lastTwoAssistants, ...lastTwoUsers];
1660
+ // Sort by timestamp if available
1661
+ // dialogs.sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0)); // Example sorting
1662
+ return dialogs;
1663
+ }
1664
+ case ContextType.AllConversation:
1665
+ default:
1666
+ return conversationMessages;
1667
+ }
1668
+ }
1669
+ /**
1670
+ * Evaluates a conversation context based on a specific task and attaches the result to a designated message.
1671
+ * @param agentTask The task definition for the evaluation.
1672
+ * @param messageToUpdate The specific ChatMessage object (user or assistant) to attach the evaluation results to.
1673
+ * @param contextType Determines which part of the conversation history to send as context (default: AllConversation).
1674
+ * @returns The JSON result of the evaluation.
1675
+ */
1676
+ async evaluateWithTask(agentTask, messageToUpdate, // Message to attach the evaluation to
1677
+ contextType = ContextType.AllConversation) {
1678
+ // Fetch the current conversation messages from the service
1679
+ const messages = this.conversationService.getMessagesSignal()();
1399
1680
  const requestMessages = [];
1400
- // Add system prompt if available
1401
- // Prioritize characterCard system_prompt, then description as fallback
1402
1681
  const systemPrompt = agentTask.systemPrompt || 'You are a helpful assistant.'; // Default fallback
1403
1682
  requestMessages.push({ role: ChatRole.System, content: systemPrompt });
1404
- // Add the main task content
1405
1683
  let taskContent = agentTask.task;
1406
1684
  if (agentTask.expectedResponseType) {
1407
- // Ensure proper formatting instructions
1408
1685
  taskContent += `\n\nPlease provide the response strictly in the following JSON format:\n${agentTask.expectedResponseType}`;
1409
1686
  }
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;
1687
+ if (contextType === ContextType.CurrentMessageContent) {
1688
+ taskContent += `\n\nHere is text: ${messageToUpdate.content}`;
1689
+ }
1690
+ else {
1691
+ // Get context messages based on the specified type
1692
+ const contextMessages = this.getContextMessages(messages, contextType);
1693
+ if (contextMessages.length > 0) {
1694
+ const conversationHistoryString = contextMessages
1695
+ .map((message) => `${message.role}: ${message.content}`) // Format messages
1696
+ .join('\n');
1697
+ taskContent += `\n\nHere is the relevant conversation history:\n${conversationHistoryString}`;
1429
1698
  }
1430
1699
  }
1700
+ // No longer need to find the last user message here, it's passed in as messageToUpdate
1431
1701
  requestMessages.push({ role: ChatRole.User, content: taskContent });
1432
1702
  try {
1433
- // Send evaluation request to the AI service
1434
- // Pass the model if the service method supports it
1435
- console.log(requestMessages);
1436
1703
  const response = await this.agentCardService.callChatCompletion({ messages: requestMessages, model: agentTask.model });
1437
- // Extract JSON from response, handling potential errors
1438
1704
  const jsonData = extractJsonFromResponse(response.content);
1439
- // Optional: Update specific signals or show tailored toasts for this function
1440
- // Example: this.taskEvaluationResultSignal.set(jsonData);
1441
1705
  this.evaluationResultSignal.set(jsonData);
1442
1706
  // Update score if available
1443
1707
  if (jsonData.score) {
1444
- // Assuming a similar scoring logic as evaluateConversation
1445
1708
  // Adjust the multiplier or condition if needed for task-based evaluation
1446
1709
  if (jsonData.score <= 3) {
1447
1710
  this.updateScore(jsonData.score * 10);
1448
1711
  }
1449
1712
  }
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
- });
1713
+ // Update the specific message provided by the caller with the result
1714
+ if (messageToUpdate?.messageId && jsonData) {
1715
+ this.conversationService.updateMessage(messageToUpdate.messageId, { evaluation: jsonData });
1456
1716
  }
1457
1717
  return jsonData;
1458
1718
  }
@@ -1466,14 +1726,12 @@ and give the response in next JSON format.
1466
1726
  throw error;
1467
1727
  }
1468
1728
  }
1469
- // Update score with limits
1470
1729
  updateScore(additionalScore) {
1471
1730
  this.scoreSignal.update((currentScore) => {
1472
1731
  const newScore = currentScore + additionalScore;
1473
1732
  return newScore > 100 ? 100 : newScore;
1474
1733
  });
1475
1734
  }
1476
- // Reset score
1477
1735
  resetScore() {
1478
1736
  this.scoreSignal.set(10);
1479
1737
  }
@@ -1496,8 +1754,9 @@ class ChatFooterComponent {
1496
1754
  // Inputs
1497
1755
  this.isAIThinking = input(false);
1498
1756
  // @deprecated in favor of appAgentTask
1499
- this.evaluatorAgentCard = input();
1500
- this.evaluatorAgentTask = input();
1757
+ // readonly evaluatorAgentCard = input<IMiniAgentCard>();
1758
+ this.taskOnUserMessage = input();
1759
+ this.taskOnAssistantMessage = input();
1501
1760
  this.micSettings = input({ useWhisper: true, lang: 'en' });
1502
1761
  // Outputs
1503
1762
  this.sendMessage = output();
@@ -1555,31 +1814,46 @@ class ChatFooterComponent {
1555
1814
  return;
1556
1815
  }
1557
1816
  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
1817
+ const message = { content: text, role: ChatRole.User };
1565
1818
  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();
1819
+ const result = await this.conversationService.sendUserMessage(message);
1820
+ const messages = this.conversationService.getMessagesSignal()();
1821
+ if (result.assistantMessageId) {
1822
+ const assistantMessage = messages.find((m) => m.messageId === result.assistantMessageId);
1823
+ this.evaluateConversation(assistantMessage, 'assistant');
1824
+ }
1825
+ if (result?.userMessageId) {
1826
+ // Evaluate the message only if it was successfully sent and we have the user message ID
1827
+ // Find the message using the userMessageId from the result object
1828
+ const messageToEvaluate = messages.find((m) => m.messageId === result.userMessageId);
1829
+ if (messageToEvaluate) {
1830
+ this.evaluateConversation(messageToEvaluate, 'user');
1831
+ }
1832
+ else {
1833
+ // Log the correct ID in the error message
1834
+ console.error(`Could not find message with ID ${result.userMessageId} to evaluate.`);
1835
+ }
1836
+ }
1837
+ else {
1838
+ console.warn('Message sending failed or returned no ID, skipping evaluation.');
1839
+ }
1570
1840
  }
1571
1841
  /**
1572
1842
  * Evaluate conversation using evaluator agent
1573
1843
  */
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);
1844
+ /**
1845
+ * Evaluate a specific message using the evaluator agent task.
1846
+ * @param messageToEvaluate The ChatMessage (typically the user's last message) to evaluate.
1847
+ */
1848
+ async evaluateConversation(messageToEvaluate, type) {
1849
+ let agentTask;
1850
+ if (type === 'user' && this.taskOnUserMessage()) {
1851
+ agentTask = this.taskOnUserMessage();
1852
+ const data = await this.evaluationService.evaluateWithTask(agentTask, messageToEvaluate);
1579
1853
  }
1580
- if (this.evaluatorAgentTask()) {
1581
- const data = await this.evaluationService.evaluateWithTask(messages, this.evaluatorAgentTask());
1582
- console.log(data);
1854
+ else if (type === 'assistant' && this.taskOnAssistantMessage()) {
1855
+ agentTask = this.taskOnAssistantMessage();
1856
+ const data = await this.evaluationService.evaluateWithTask(agentTask, messageToEvaluate, ContextType.CurrentMessageContent);
1583
1857
  }
1584
1858
  }
1585
1859
  handleAudioRecorded(event) {
@@ -1602,6 +1876,7 @@ class ChatFooterComponent {
1602
1876
  }
1603
1877
  this.isUserTalking = false;
1604
1878
  this.isGettingTranscription = true;
1879
+ debugger;
1605
1880
  try {
1606
1881
  const message = {
1607
1882
  content: '',
@@ -1609,7 +1884,8 @@ class ChatFooterComponent {
1609
1884
  audioUrl: URL.createObjectURL(eventBlob),
1610
1885
  isLoading: true,
1611
1886
  };
1612
- const messageId = this.conversationService.addMessage(message);
1887
+ // Instead of creating message, eneble animation user is spacking
1888
+ // const messageId = this.conversationService.addMessage(message);
1613
1889
  // Get transcription from audio
1614
1890
  const transcription = await this.agentCardService.getAudioTranscriptions(eventBlob, null);
1615
1891
  // Create updated message with transcription and isLoading set to false
@@ -1619,23 +1895,24 @@ class ChatFooterComponent {
1619
1895
  transcription: transcription || undefined,
1620
1896
  isLoading: false,
1621
1897
  };
1622
- // Send message to conversation
1623
1898
  // The evaluation will happen automatically in the conversation service
1624
- await this.conversationService.sendUserMessage(updatedMessage, messageId);
1899
+ await this.conversationService.sendUserMessage(updatedMessage);
1625
1900
  }
1626
1901
  finally {
1627
1902
  this.isGettingTranscription = false;
1903
+ // console.log(this.isAIThinking());
1904
+ // debugger;
1628
1905
  }
1629
1906
  }
1630
1907
  setScore(score) {
1631
1908
  this.evaluationService.setScore(100);
1632
1909
  }
1633
1910
  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"] }] }); }
1911
+ 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" }, 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
1912
  }
1636
1913
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatFooterComponent, decorators: [{
1637
1914
  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"] }]
1915
+ 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"] }]
1639
1916
  }], ctorParameters: () => [] });
1640
1917
 
1641
1918
  const ICONS = {
@@ -1715,12 +1992,70 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
1715
1992
  `, 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
1993
  }], ctorParameters: () => [] });
1717
1994
 
1995
+ // Given a text that can be in markdown but only in asterisk like *italic* or **bold** or ***bold italic***
1996
+ // Example Hola que tal es *Hey What's up* en inglés
1997
+ // i want to extract in array every word like this.
1998
+ // [{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: ''}]
1999
+ function extractTags(text) {
2000
+ const result = [];
2001
+ const tagStack = [];
2002
+ // Regex to match markdown markers (***, **, *) or spaces or sequences of non-space/non-asterisk characters (words with punctuation)
2003
+ const regex = /(\*\*\*|\*\*|\*|\s+|[^\s\*]+)/g;
2004
+ let match;
2005
+ while ((match = regex.exec(text)) !== null) {
2006
+ const token = match[0];
2007
+ if (token.trim() === '') {
2008
+ // Ignore spaces
2009
+ continue;
2010
+ }
2011
+ if (token === '***') {
2012
+ if (tagStack.length > 0 && tagStack[tagStack.length - 1] === 'bold italic') {
2013
+ tagStack.pop(); // Closing bold italic
2014
+ }
2015
+ else {
2016
+ tagStack.push('bold italic'); // Opening bold italic
2017
+ }
2018
+ }
2019
+ else if (token === '**') {
2020
+ if (tagStack.length > 0 && tagStack[tagStack.length - 1] === 'bold') {
2021
+ tagStack.pop(); // Closing bold
2022
+ }
2023
+ else {
2024
+ tagStack.push('bold'); // Opening bold
2025
+ }
2026
+ }
2027
+ else if (token === '*') {
2028
+ if (tagStack.length > 0 && tagStack[tagStack.length - 1] === 'italic') {
2029
+ tagStack.pop(); // Closing italic
2030
+ }
2031
+ else {
2032
+ tagStack.push('italic'); // Opening italic
2033
+ }
2034
+ }
2035
+ else {
2036
+ // It's a word (including punctuation)
2037
+ const currentTag = tagStack.length > 0 ? tagStack[tagStack.length - 1] : '';
2038
+ result.push({ word: token, tag: currentTag });
2039
+ }
2040
+ }
2041
+ // Note: This implementation assumes correctly matched opening and closing tags.
2042
+ // Unclosed tags at the end of the string will be ignored.
2043
+ return result;
2044
+ }
2045
+
1718
2046
  class TextHighlighterComponent {
1719
2047
  constructor() {
2048
+ // Inputs
1720
2049
  this.message = input.required(); // Or input.required<MessageAudio>() if always expected
1721
- this.highlightedWords = signal([]); // Signal for highlighted words
2050
+ // Outputs
2051
+ this.playAudio = output();
2052
+ this.audioCompleted = output();
2053
+ this.wordClicked = output(); // Output for clicked word with messageId
2054
+ // Signal States
2055
+ this.wordWithMeta = signal([]); // Signal for plain text words when no transcription
1722
2056
  this.isPlaying = signal(false); // Signal for play state
1723
2057
  // Signals State computed from the input message
2058
+ this.wordsWithMetaState = [];
1724
2059
  this.iconState = computed(() => {
1725
2060
  if (this.isLoading()) {
1726
2061
  return 'isLoading';
@@ -1742,25 +2077,20 @@ class TextHighlighterComponent {
1742
2077
  const msg = this.message(); // Read the input signal
1743
2078
  return msg?.text || msg?.content || 'N/A';
1744
2079
  });
1745
- this.classTag = computed(() => this.message()?.tag);
2080
+ this.classTag = computed(() => this.message()?.tag); // tag can be at message level or word level this is for message level
1746
2081
  // Computed signal for transcription availability
1747
2082
  this.hasTranscription = computed(() => {
1748
2083
  const hasTranscription = !!this.message()?.transcriptionTimestamps && this.message()?.transcriptionTimestamps.length > 0;
1749
2084
  return hasTranscription;
1750
2085
  });
1751
- this.playAudio = output();
1752
- this.audioCompleted = output();
1753
2086
  this.audioElement = null; // Audio element for playback
1754
2087
  this.destroy$ = new Subject(); // Cleanup localsubject
1755
2088
  // Inject services
1756
- this.cdr = inject(ChangeDetectorRef); // Keep for now, might remove if template fully signal-driven
1757
2089
  this.destroyRef = inject(DestroyRef);
1758
2090
  // Effect to react to message changes and re-initialize
1759
2091
  effect(() => {
1760
2092
  const currentMsg = this.message(); // Read the input signal
1761
- console.log('Input message signal changed:', currentMsg);
1762
2093
  if (currentMsg) {
1763
- // Re-run initialization logic whenever the message signal changes
1764
2094
  this.initializeBasedOnMessage(currentMsg);
1765
2095
  // Check if shouldPlayAudio flag is set
1766
2096
  if (currentMsg.shouldPlayAudio) {
@@ -1770,19 +2100,15 @@ class TextHighlighterComponent {
1770
2100
  else {
1771
2101
  // Handle case where message becomes undefined (cleanup?)
1772
2102
  this.cleanupAudio();
1773
- this.highlightedWords.set([]);
1774
2103
  }
1775
2104
  // No cdr.markForCheck() needed here usually if template bindings use signals/computed
1776
2105
  });
1777
2106
  // Keep the effect for highlightedWords for now, might be redundant if template binds directly
1778
2107
  effect(() => {
1779
- this.highlightedWords();
1780
- this.cdr.markForCheck(); // Keep if template uses non-signal bindings for highlighted words
2108
+ // some how i need to isolate the reading, becouse reading in main effect is very dangerous since later is set and produces infinite loop
2109
+ this.wordsWithMetaState = this.wordWithMeta();
1781
2110
  });
1782
2111
  }
1783
- /**
1784
- * Track function for ngFor to improve performance
1785
- */
1786
2112
  trackByIndex(index, item) {
1787
2113
  return index;
1788
2114
  }
@@ -1805,75 +2131,57 @@ class TextHighlighterComponent {
1805
2131
  }
1806
2132
  // Initialize words and sync based on transcription presence
1807
2133
  if (this.hasTranscription()) {
1808
- // Use computed signal
1809
- const timestamps = msg.transcriptionTimestamps || [];
1810
- this.initializeHighlightedWords(timestamps); // Pass timestamps
1811
- this.subcribeToAudioSync(timestamps); // Pass timestamps
2134
+ const wordsAndTimestamps = [];
2135
+ // NOTE: This merge is only going to work if transcriptionTimestamps and wordsWithMetaState have the same length
2136
+ // wordsWithMetaState is initialized here in initializePlainTextWords but transcriptionTimestamps in the father, check later if i have bugs.
2137
+ msg?.transcriptionTimestamps?.forEach((word, index) => {
2138
+ wordsAndTimestamps.push({ ...word, ...this.wordsWithMetaState[index], index });
2139
+ });
2140
+ console.log(wordsAndTimestamps);
2141
+ this.subcribeToAudioSync(wordsAndTimestamps); // Pass timestamps
1812
2142
  }
1813
2143
  else {
1814
2144
  this.subscribeToEndAudio();
2145
+ this.initializePlainTextWords(msg.text || msg.content || ''); // Initialize plain text words
1815
2146
  }
1816
2147
  }
1817
- /**
1818
- * Initialize the audio element and set up event listeners
1819
- */
1820
2148
  initializeAudio(audioUrl) {
1821
- // Clean up any existing audio element and listeners first
1822
2149
  this.cleanupAudio();
1823
2150
  this.audioElement = new Audio(audioUrl); // Use passed URL
1824
2151
  }
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
2152
  subcribeToAudioSync(timestamps) {
1840
- // Accept timestamps
1841
2153
  if (!this.audioElement) {
1842
2154
  // Guard against missing audio element
1843
2155
  return;
1844
2156
  }
1845
- // Important: Clean up previous listeners before setting up new ones
1846
2157
  this.destroy$.next(); // Signal previous subscriptions to complete
1847
- // Listen to timeupdate events
2158
+ console.log('Subscribing to audio sync with timestamps:', timestamps);
1848
2159
  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
2160
+ .pipe(takeUntilDestroyed(this.destroyRef), takeUntil(this.destroy$), // Use manual subject for re-initialization cleanup
1851
2161
  map(() => this.audioElement?.currentTime || 0))
1852
2162
  .subscribe((currentTime) => {
1853
2163
  // Use the passed timestamps array
1854
2164
  const updatedWords = timestamps.map((word, index) => {
1855
2165
  const isHighlighted = currentTime >= word.start - 0.15 && currentTime < word.end + 0.15;
1856
- return { word: word.word, index, isHighlighted };
2166
+ return { word: word.word, index, isHighlighted, tag: word.tag };
1857
2167
  });
1858
- this.highlightedWords.set(updatedWords);
2168
+ this.wordWithMeta.set(updatedWords);
1859
2169
  });
1860
2170
  // Listen to ended event for cleanup
1861
2171
  fromEvent(this.audioElement, 'ended')
1862
2172
  .pipe(takeUntilDestroyed(this.destroyRef), takeUntil(this.destroy$))
1863
2173
  .subscribe(() => {
1864
2174
  // Reset highlighting when audio ends
1865
- const resetWords = this.highlightedWords().map((word) => ({
2175
+ const resetWords = this.wordWithMeta().map((word) => ({
1866
2176
  // Read current words signal
1867
2177
  ...word,
1868
2178
  isHighlighted: false,
1869
2179
  }));
1870
- this.highlightedWords.set(resetWords);
2180
+ this.wordWithMeta.set(resetWords);
1871
2181
  // Set isPlaying to false when audio finishes
1872
2182
  this.isPlaying.set(false);
1873
- // Emit audio completed event with the current message
1874
2183
  const currentMsg = this.message();
1875
2184
  if (currentMsg) {
1876
- // Reset the shouldPlayAudio flag
1877
2185
  currentMsg.shouldPlayAudio = false;
1878
2186
  this.audioCompleted.emit(currentMsg);
1879
2187
  }
@@ -1906,11 +2214,11 @@ class TextHighlighterComponent {
1906
2214
  this.audioElement = null;
1907
2215
  this.destroy$.next(); // Ensure listeners tied to this audio instance are cleaned up
1908
2216
  // Reset highlighting immediately on cleanup
1909
- const resetWords = this.highlightedWords().map((word) => ({
2217
+ const resetWords = this.wordWithMeta().map((word) => ({
1910
2218
  ...word,
1911
2219
  isHighlighted: false,
1912
2220
  }));
1913
- this.highlightedWords.set(resetWords);
2221
+ this.wordWithMeta.set(resetWords);
1914
2222
  }
1915
2223
  }
1916
2224
  /**
@@ -1933,7 +2241,6 @@ class TextHighlighterComponent {
1933
2241
  // For simplicity, let's re-call initializeAudio here if needed,
1934
2242
  // though ideally the effect handles it.
1935
2243
  this.initializeAudio(currentMsg.audioUrl); // Ensure it's created if somehow missed
1936
- // this.initializeBasedOnMessage(currentMsg);
1937
2244
  this.startAudioPlayback();
1938
2245
  }
1939
2246
  else if (currentMsg) {
@@ -1942,23 +2249,44 @@ class TextHighlighterComponent {
1942
2249
  }
1943
2250
  }
1944
2251
  /**
1945
- * Start audio playback and handle any setup needed
2252
+ * Initialize plain text words by splitting the message text
1946
2253
  */
2254
+ initializePlainTextWords(text) {
2255
+ const words = extractTags(text);
2256
+ const plainWords = words.map((word, index) => ({
2257
+ word: word.word,
2258
+ index: index,
2259
+ isHighlighted: false,
2260
+ tag: word.tag,
2261
+ }));
2262
+ this.wordWithMeta.set(plainWords);
2263
+ }
2264
+ onWordClick(wordData) {
2265
+ const currentMsg = this.message();
2266
+ if (!currentMsg) {
2267
+ console.warn('Cannot emit wordClicked: message input is not available.');
2268
+ return;
2269
+ }
2270
+ // Determine the correct data structure based on input type
2271
+ const clickedWord = 'isHighlighted' in wordData
2272
+ ? { word: wordData.word, index: wordData.index, messageId: currentMsg.messageId }
2273
+ : { ...wordData, messageId: currentMsg.messageId };
2274
+ this.wordClicked.emit(clickedWord);
2275
+ }
1947
2276
  startAudioPlayback() {
1948
2277
  if (!this.audioElement)
1949
2278
  return;
1950
- // Play the audio
1951
2279
  this.audioElement.play().catch((error) => {
1952
2280
  console.error('Error playing audio:', error);
1953
2281
  });
1954
2282
  this.isPlaying.set(true); // Set play state to true
1955
2283
  }
1956
2284
  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 }); }
2285
+ 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
2286
  }
1959
2287
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: TextHighlighterComponent, decorators: [{
1960
2288
  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"] }]
2289
+ 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
2290
  }], ctorParameters: () => [] });
1963
2291
 
1964
2292
  class MessageOrchestratorComponent {
@@ -1967,7 +2295,6 @@ class MessageOrchestratorComponent {
1967
2295
  this.messages = input.required();
1968
2296
  this.messageRole = input.required();
1969
2297
  this.messagesSignal = signal([]);
1970
- // Effect to update messagesSignal when input messages change
1971
2298
  this.messagesEffect = effect(() => {
1972
2299
  // Get the latest messages from the input
1973
2300
  const currentMessages = this.messages();
@@ -2104,12 +2431,16 @@ class MessageOrchestratorComponent {
2104
2431
  this.isGenerating = false;
2105
2432
  }
2106
2433
  }
2434
+ onWordClicked(wordData) {
2435
+ console.log('Word clicked in MessageOrchestrator:', wordData);
2436
+ this.conversationService.notifyWordClicked(wordData);
2437
+ }
2107
2438
  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 }); }
2439
+ 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
2440
  }
2110
2441
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: MessageOrchestratorComponent, decorators: [{
2111
2442
  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"] }]
2443
+ 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
2444
  }] });
2114
2445
 
2115
2446
  const EVALUATION_EMOJIS = {
@@ -2131,7 +2462,7 @@ class ChatMessageComponent {
2131
2462
  this.messageTranslation = computed(() => this.chatMessage()?.translation);
2132
2463
  this.isUserMessage = computed(() => this.chatMessage()?.role === ChatRole.User);
2133
2464
  this.evaluationEmoji = computed(() => {
2134
- const score = this.chatMessage()?.evaluation?.score;
2465
+ const score = this.chatMessage()?.evaluation?.['score'];
2135
2466
  if (score === null || score === undefined) {
2136
2467
  return '';
2137
2468
  }
@@ -2159,14 +2490,17 @@ class ChatMessageComponent {
2159
2490
  }
2160
2491
  showEvaluation() {
2161
2492
  console.log('showEvaluation', this.chatMessage().evaluation);
2162
- alert(this.chatMessage().evaluation?.feedback || 'No feedback available');
2493
+ alert(this.chatMessage().evaluation?.['feedback'] || 'No feedback available');
2494
+ }
2495
+ printData() {
2496
+ console.log(this.chatMessage());
2163
2497
  }
2164
2498
  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 }); }
2499
+ 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
2500
  }
2167
2501
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatMessageComponent, decorators: [{
2168
2502
  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"] }]
2503
+ 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
2504
  }] });
2171
2505
 
2172
2506
  class ChatMessagesListComponent {
@@ -2178,7 +2512,7 @@ class ChatMessagesListComponent {
2178
2512
  this.conversationService = inject(ConversationService);
2179
2513
  this.elementRef = inject(ElementRef);
2180
2514
  // State
2181
- this.aiIcon = 'assets/default/ai.png';
2515
+ this.aiIcon = 'assets/defaults/avatar_ai.webp';
2182
2516
  this.isThinking = this.conversationService.isThinking();
2183
2517
  this.messages = computed(() => {
2184
2518
  // Get the actual array of messages from the signal by calling it as a function
@@ -2211,11 +2545,11 @@ class ChatMessagesListComponent {
2211
2545
  return `${message.role}-${index}-${message.content.substring(0, 20)}`;
2212
2546
  }
2213
2547
  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 }); }
2548
+ 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
2549
  }
2216
2550
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ChatMessagesListComponent, decorators: [{
2217
2551
  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"] }]
2552
+ 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
2553
  }], ctorParameters: () => [] });
2220
2554
 
2221
2555
  const SpeedDescription = {
@@ -2411,12 +2745,14 @@ class DCChatComponent {
2411
2745
  // Inputs
2412
2746
  this.chatUserSettings = this.userDataExchange.getUserChatSettings(); // Default to user data exchange
2413
2747
  // Signal Inputs
2414
- this.evaluatorAgentCard = input(); // @Deprecated in favor of appAgentTask
2415
- this.appAgentTask = input();
2748
+ // readonly evaluatorAgentCard = input<IMiniAgentCard>(); // @Deprecated in favor of appAgentTask
2749
+ this.taskOnUserMessage = input();
2750
+ this.taskOnAssistantMessage = input();
2416
2751
  this.parseDict = input({});
2417
2752
  // Outputs
2418
- this.sendMessage = output(); // Not implemented, should notifies whatever happened inside the chat
2753
+ this.sendMessage = output(); // Notifies about various events happening inside the chat
2419
2754
  this.goalCompleted = output(); // notifies when user completes goal (score reaches 100)
2755
+ // readonly wordClicked = output<WordData>(); // Replaced by sendMessage with ChatEventType.WordClicked
2420
2756
  // Signals States
2421
2757
  this.messages = signal([]);
2422
2758
  // States
@@ -2430,9 +2766,18 @@ class DCChatComponent {
2430
2766
  this.goalCompleted.emit();
2431
2767
  }
2432
2768
  });
2769
+ // Effect to watch for word clicks from the service
2770
+ effect(() => {
2771
+ const clickedWordData = this.conversationService.getWordClickedSignal()();
2772
+ if (clickedWordData) {
2773
+ // Emit through the consolidated sendMessage output
2774
+ this.sendMessage.emit({ type: ChatEventType.WordClicked, payload: clickedWordData });
2775
+ // Optional: Reset the signal in the service if you only want to emit once per click
2776
+ // this.conversationService.notifyWordClicked(null);
2777
+ }
2778
+ });
2433
2779
  }
2434
2780
  async ngOnInit() {
2435
- console.log(this.parseDict());
2436
2781
  if (this.conversationSettings) {
2437
2782
  this.conversationService.setDestroyed(false);
2438
2783
  await this.conversationService.initConversationWithSettings(this.conversationSettings);
@@ -2477,11 +2822,11 @@ class DCChatComponent {
2477
2822
  await this.ngOnInit();
2478
2823
  }
2479
2824
  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 }); }
2825
+ 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], 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
2826
  }
2482
2827
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCChatComponent, decorators: [{
2483
2828
  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"] }]
2829
+ 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"] }]
2485
2830
  }], ctorParameters: () => [], propDecorators: { chatUserSettings: [{
2486
2831
  type: Input
2487
2832
  }], conversationSettings: [{
@@ -3278,7 +3623,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
3278
3623
  DynamicDialogModule,
3279
3624
  PopoverModule,
3280
3625
  ProviderSelectorComponent,
3281
- AccountPlatformForm
3626
+ AccountPlatformForm,
3282
3627
  ], 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
3628
  }], ctorParameters: () => [] });
3284
3629
 
@@ -3308,11 +3653,11 @@ class DCConversationCardUIComponent {
3308
3653
  this.onCardAction.emit({ event: 'delete', card: this.card() });
3309
3654
  }
3310
3655
  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 }); }
3656
+ 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 @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: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"] }, { 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
3657
  }
3313
3658
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCConversationCardUIComponent, decorators: [{
3314
3659
  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"] }]
3660
+ 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 <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 @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: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"] }]
3316
3661
  }] });
3317
3662
 
3318
3663
  // 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
@@ -3421,6 +3766,9 @@ class AgentCardListComponent extends PaginationBase {
3421
3766
  this.viewMode = this.viewMode === 'cards' ? 'table' : 'cards';
3422
3767
  }
3423
3768
  }
3769
+ newAgentCard() {
3770
+ this.onAction.emit({ action: 'new' });
3771
+ }
3424
3772
  async search(text) {
3425
3773
  console.log('search', text);
3426
3774
  const filters = {
@@ -3455,7 +3803,7 @@ class AgentCardListComponent extends PaginationBase {
3455
3803
  }
3456
3804
  }
3457
3805
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AgentCardListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3458
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: AgentCardListComponent, isStandalone: true, selector: "dc-agent-card-lists", inputs: { viewMode: { classPropertyName: "viewMode", publicName: "viewMode", isSignal: false, isRequired: false, transformFunction: null }, customCardComponent: { classPropertyName: "customCardComponent", publicName: "customCardComponent", isSignal: true, isRequired: false, transformFunction: null }, showOptions: { classPropertyName: "showOptions", publicName: "showOptions", isSignal: true, isRequired: false, transformFunction: null }, gridLayout: { classPropertyName: "gridLayout", publicName: "gridLayout", isSignal: true, isRequired: false, transformFunction: null }, getCustomButtons: { classPropertyName: "getCustomButtons", publicName: "getCustomButtons", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "outlets", predicate: ["outlet"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<dc-filter-bar [isAdmin]=\"true\" (onFilterAction)=\"doFilterBarAction($event)\"></dc-filter-bar>\n\n@if(viewMode === 'table'){\n<app-quick-table [columns]=\"columns\" [tableData]=\"agentCards\" [actions]=\"actions()\" (onAction)=\"onCardAction($event)\"></app-quick-table>\n\n}@else{\n\n<div class=\"conversation-card-lists\">\n @if(!isLoading) {\n <div [ngClass]=\"{ 'cards-container': gridLayout() }\">\n @for (card of agentCards; track card) {\n <div style=\"position: relative\">\n <ng-container #outlet=\"ngComponentOutlet\" [ngComponentOutlet]=\"cardComponent\" [ngComponentOutletInputs]=\"{ card: card, showOptions: showOptions() }\">\n </ng-container>\n </div>\n }\n </div>\n }\n</div>\n\n@if(isLoading) {\n<div>\n <p-skeleton styleClass=\"mb-2\" />\n <p-skeleton width=\"10rem\" styleClass=\"mb-2\" />\n <p-skeleton width=\"5rem\" styleClass=\"mb-2\" />\n <p-skeleton height=\"2rem\" styleClass=\"mb-2\" />\n <p-skeleton width=\"10rem\" height=\"4rem\" />\n</div>\n} @if(agentCards.length === 0) {\n<div>\n <p>No conversations found or no connection with server</p>\n</div>\n} }\n\n<p-paginator\n currentPageReportTemplate=\"{{ totalRecords }} conversations\"\n [showCurrentPageReport]=\"true\"\n (onPageChange)=\"onPageChange($event)\"\n [first]=\"paginatorFirst\"\n [rows]=\"paginatorRows\"\n [totalRecords]=\"totalRecords\"\n [rowsPerPageOptions]=\"[10, 20, 30]\">\n</p-paginator>\n", styles: [":host{display:block;height:100%}.options-icon{cursor:pointer;position:absolute;top:2px;right:3px;font-size:1.2rem;color:#dde9e9;background-color:#4f486281;border-radius:50%;padding:5px;z-index:1000}.conversation-card-lists{padding:1.5rem;width:100%;height:100%;display:flex;flex-direction:column}.conversation-card-lists .cards-container{display:flex;flex-wrap:wrap;gap:2rem;width:100%;justify-content:center;flex:1;overflow-y:auto;min-height:0}.conversation-card-lists .cards-container>div{flex:0 0 240px}.conversation-card-lists .dc-card{position:relative;background:#fff;border-radius:8px;box-shadow:0 2px 4px #0000001a;padding:.5rem;transition:transform .2s ease,box-shadow .2s ease;display:flex;flex-direction:column;gap:2px}.conversation-card-lists .dc-card:hover{transform:translateY(-2px);box-shadow:0 4px 8px #00000026}.conversation-card-lists .dc-card .dc-card-header{position:absolute;top:10px;left:5px;border-radius:5px;padding:5px}.conversation-card-lists .dc-card .dc-card-header:before{content:\"\";position:absolute;inset:0;background-color:#4d30db81;filter:blur(2px);border-radius:5px;z-index:0}.conversation-card-lists .dc-card .dc-card-header h3{margin:0;font-size:1.25rem;font-weight:600;color:#ece7e7;position:relative;z-index:1}.conversation-card-lists .dc-card .dc-card-content{flex:1}.conversation-card-lists .dc-card .dc-card-content p{margin:0;color:#666;line-height:1.5}.conversation-card-lists .dc-card button{padding:.5rem 1rem;border:none;border-radius:4px;background-color:#007bff;color:#fff;cursor:pointer;font-weight:500;transition:background-color .2s ease}.conversation-card-lists .dc-card button:hover{background-color:#0056b3}.conversation-card-lists .dc-card button:active{transform:translateY(1px)}:host{display:flex;flex-direction:column;height:100%}p-paginator{margin-top:1rem;flex-shrink:0}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: PaginatorModule }, { kind: "component", type: i1$4.Paginator, selector: "p-paginator", inputs: ["pageLinkSize", "style", "styleClass", "alwaysShow", "dropdownAppendTo", "templateLeft", "templateRight", "appendTo", "dropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showFirstLastIcon", "totalRecords", "rows", "rowsPerPageOptions", "showJumpToPageDropdown", "showJumpToPageInput", "jumpToPageItemTemplate", "showPageLinks", "locale", "dropdownItemTemplate", "first"], outputs: ["onPageChange"] }, { kind: "component", type: DCFilterBarComponent, selector: "dc-filter-bar", inputs: ["isAdmin", "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"] }] }); }
3806
+ 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"] }] }); }
3459
3807
  }
3460
3808
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: AgentCardListComponent, decorators: [{
3461
3809
  type: Component,
@@ -3469,7 +3817,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
3469
3817
  SpeedDialModule,
3470
3818
  QuickTableComponent,
3471
3819
  CommonModule,
3472
- ], standalone: true, template: "<dc-filter-bar [isAdmin]=\"true\" (onFilterAction)=\"doFilterBarAction($event)\"></dc-filter-bar>\n\n@if(viewMode === 'table'){\n<app-quick-table [columns]=\"columns\" [tableData]=\"agentCards\" [actions]=\"actions()\" (onAction)=\"onCardAction($event)\"></app-quick-table>\n\n}@else{\n\n<div class=\"conversation-card-lists\">\n @if(!isLoading) {\n <div [ngClass]=\"{ 'cards-container': gridLayout() }\">\n @for (card of agentCards; track card) {\n <div style=\"position: relative\">\n <ng-container #outlet=\"ngComponentOutlet\" [ngComponentOutlet]=\"cardComponent\" [ngComponentOutletInputs]=\"{ card: card, showOptions: showOptions() }\">\n </ng-container>\n </div>\n }\n </div>\n }\n</div>\n\n@if(isLoading) {\n<div>\n <p-skeleton styleClass=\"mb-2\" />\n <p-skeleton width=\"10rem\" styleClass=\"mb-2\" />\n <p-skeleton width=\"5rem\" styleClass=\"mb-2\" />\n <p-skeleton height=\"2rem\" styleClass=\"mb-2\" />\n <p-skeleton width=\"10rem\" height=\"4rem\" />\n</div>\n} @if(agentCards.length === 0) {\n<div>\n <p>No conversations found or no connection with server</p>\n</div>\n} }\n\n<p-paginator\n currentPageReportTemplate=\"{{ totalRecords }} conversations\"\n [showCurrentPageReport]=\"true\"\n (onPageChange)=\"onPageChange($event)\"\n [first]=\"paginatorFirst\"\n [rows]=\"paginatorRows\"\n [totalRecords]=\"totalRecords\"\n [rowsPerPageOptions]=\"[10, 20, 30]\">\n</p-paginator>\n", styles: [":host{display:block;height:100%}.options-icon{cursor:pointer;position:absolute;top:2px;right:3px;font-size:1.2rem;color:#dde9e9;background-color:#4f486281;border-radius:50%;padding:5px;z-index:1000}.conversation-card-lists{padding:1.5rem;width:100%;height:100%;display:flex;flex-direction:column}.conversation-card-lists .cards-container{display:flex;flex-wrap:wrap;gap:2rem;width:100%;justify-content:center;flex:1;overflow-y:auto;min-height:0}.conversation-card-lists .cards-container>div{flex:0 0 240px}.conversation-card-lists .dc-card{position:relative;background:#fff;border-radius:8px;box-shadow:0 2px 4px #0000001a;padding:.5rem;transition:transform .2s ease,box-shadow .2s ease;display:flex;flex-direction:column;gap:2px}.conversation-card-lists .dc-card:hover{transform:translateY(-2px);box-shadow:0 4px 8px #00000026}.conversation-card-lists .dc-card .dc-card-header{position:absolute;top:10px;left:5px;border-radius:5px;padding:5px}.conversation-card-lists .dc-card .dc-card-header:before{content:\"\";position:absolute;inset:0;background-color:#4d30db81;filter:blur(2px);border-radius:5px;z-index:0}.conversation-card-lists .dc-card .dc-card-header h3{margin:0;font-size:1.25rem;font-weight:600;color:#ece7e7;position:relative;z-index:1}.conversation-card-lists .dc-card .dc-card-content{flex:1}.conversation-card-lists .dc-card .dc-card-content p{margin:0;color:#666;line-height:1.5}.conversation-card-lists .dc-card button{padding:.5rem 1rem;border:none;border-radius:4px;background-color:#007bff;color:#fff;cursor:pointer;font-weight:500;transition:background-color .2s ease}.conversation-card-lists .dc-card button:hover{background-color:#0056b3}.conversation-card-lists .dc-card button:active{transform:translateY(1px)}:host{display:flex;flex-direction:column;height:100%}p-paginator{margin-top:1rem;flex-shrink:0}\n"] }]
3820
+ ], 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"] }]
3473
3821
  }], ctorParameters: () => [], propDecorators: { viewMode: [{
3474
3822
  type: Input
3475
3823
  }], outlets: [{
@@ -3553,5 +3901,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
3553
3901
  * Generated bundle index. Do not edit.
3554
3902
  */
3555
3903
 
3556
- 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 };
3904
+ 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 };
3557
3905
  //# sourceMappingURL=dataclouder-ngx-agent-cards.mjs.map