@dataclouder/ngx-agent-cards 0.1.5 → 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.
- package/fesm2022/dataclouder-ngx-agent-cards.mjs +510 -165
- package/fesm2022/dataclouder-ngx-agent-cards.mjs.map +1 -1
- package/lib/components/chat-container/chat-container.component.d.ts +6 -5
- package/lib/components/chat-container/chat-footer/chat-footer.component.d.ts +8 -4
- package/lib/components/chat-container/chat-messages-list/chat-message/chat-message.component.d.ts +1 -0
- package/lib/components/chat-container/chat-messages-list/message-orchestrator/message-orchestrator.component.d.ts +2 -0
- package/lib/components/dc-agent-card-lists/agent-card-default-ui/agent-card-default-ui.component.d.ts +2 -2
- package/lib/components/text-highlighter/extract-tags.d.ts +4 -0
- package/lib/components/text-highlighter/text-highlighter.d.ts +19 -24
- package/lib/models/agent.models.d.ts +28 -2
- package/lib/services/agent-user-progress.service.d.ts +17 -0
- package/lib/services/conversation.service.d.ts +25 -2
- package/lib/services/evaluation.service.d.ts +11 -1
- package/lib/services/popup.service.d.ts +41 -0
- package/package.json +1 -1
- package/public-api.d.ts +2 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, Injectable, inject, Pipe, input, output, Input, Component,
|
|
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
|
-
|
|
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
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
1252
|
-
|
|
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
|
-
//
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
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
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
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
|
-
//
|
|
1451
|
-
|
|
1452
|
-
|
|
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
|
-
|
|
1500
|
-
this.
|
|
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
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
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
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
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.
|
|
1581
|
-
|
|
1582
|
-
|
|
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
|
-
|
|
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
|
|
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 },
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1780
|
-
this.
|
|
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
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
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
|
-
|
|
2158
|
+
console.log('Subscribing to audio sync with timestamps:', timestamps);
|
|
1848
2159
|
fromEvent(this.audioElement, 'timeupdate')
|
|
1849
|
-
.pipe(takeUntilDestroyed(this.destroyRef), // Use
|
|
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.
|
|
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.
|
|
2175
|
+
const resetWords = this.wordWithMeta().map((word) => ({
|
|
1866
2176
|
// Read current words signal
|
|
1867
2177
|
...word,
|
|
1868
2178
|
isHighlighted: false,
|
|
1869
2179
|
}));
|
|
1870
|
-
this.
|
|
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.
|
|
2217
|
+
const resetWords = this.wordWithMeta().map((word) => ({
|
|
1910
2218
|
...word,
|
|
1911
2219
|
isHighlighted: false,
|
|
1912
2220
|
}));
|
|
1913
|
-
this.
|
|
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
|
-
*
|
|
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 <
|
|
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 }} </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 <
|
|
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 }} </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
|
|
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
|
|
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 (
|
|
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 (
|
|
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/
|
|
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
|
-
|
|
2415
|
-
this.
|
|
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(); //
|
|
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 },
|
|
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\" [
|
|
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)\">✖</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
|
|
@@ -3556,5 +3901,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
|
|
|
3556
3901
|
* Generated bundle index. Do not edit.
|
|
3557
3902
|
*/
|
|
3558
3903
|
|
|
3559
|
-
export { AgentCardListComponent, AgentCardsAbstractService, AudioService, AudioSpeed, CONVERSATION_AI_TOKEN, ChatMessage, ChatRole, ChatUserSettings, ConversationDTO, ConversationMessagesDTO, ConversationType, ConversationTypeOptions, DCAgentCardFormComponent, DCChatComponent, DCConversationCardUIComponent, DCConversationPromptBuilderService, DcAgentCardDetailsComponent, EAccountsPlatform, EvalResultStringDefinition, LangCodeDescriptionEs, MessageAudio, MessageOrchestratorComponent, ProviderSelectorComponent, TextEngineOptions, TextEngines, TextHighlighterComponent, USER_DATA_EXCHANGE, UserDataExchangeAbstractService, VoiceTTSOption, VoiceTTSOptions, WordTimestamps, buildObjectTTSRequest, characterCardStringDataDefinition, convertToHTML, defaultconvUserSettings, extractAudioAndTranscription, extractJsonFromResponse, markdownBasicToHTML, markdownToHTML$1 as markdownToHTML, markdownToHTML2, markdownToHtml, matchTranscription, provideChatAIService, provideUserDataExchange, removeAllEmojis, removeEmojis };
|
|
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 };
|
|
3560
3905
|
//# sourceMappingURL=dataclouder-ngx-agent-cards.mjs.map
|