@dataclouder/ngx-agent-cards 0.2.14 → 0.2.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, Injectable, inject, signal, computed, RendererFactory2, ApplicationRef, Injector, EnvironmentInjector, createComponent, ChangeDetectionStrategy, Component, output, Input, input, effect, ViewChild, DestroyRef,
|
|
2
|
+
import { InjectionToken, Injectable, inject, signal, computed, RendererFactory2, ApplicationRef, Injector, EnvironmentInjector, createComponent, ChangeDetectionStrategy, Component, output, Input, input, effect, ViewChild, DestroyRef, untracked, HostBinding, ElementRef, Pipe, ChangeDetectorRef, EventEmitter, Output, viewChild } from '@angular/core';
|
|
3
3
|
import * as i1$1 from '@dataclouder/ngx-core';
|
|
4
4
|
import { MoodStateOptions, LANGUAGES, EntityCommunicationService, TOAST_ALERTS_TOKEN, APP_CONFIG, MoodState, AudioSpeed as AudioSpeed$1, LoadingBarService, EModelQuality, getLangDesc, AudioSpeedReverse, formatCamelCaseString, EntityBaseDetailComponent, AudioNotificationService, SUPPORTED_LANGUAGES, FormUtilsService, LangDescTranslation, FlagPipe, FlagImgPipe, DcTagsFormComponent, EntityBaseFormComponent, getSupportedLanguageOptions, DcManageableFormComponent, DcLearnableFormComponent, EntityBaseListV2Component, DCFilterBarComponent, QuickTableComponent, EmptyStateComponent, EntityBaseSignalFormComponent } from '@dataclouder/ngx-core';
|
|
5
5
|
import { UserService } from '@dataclouder/ngx-users';
|
|
6
6
|
import { AiWhisperService, TtsService, NgxAiServicesService, GeneratedAssetsService, VoiceSelectorComponent } from '@dataclouder/ngx-ai-services';
|
|
7
7
|
import { FirebaseAuthService } from '@dataclouder/ngx-auth';
|
|
8
|
-
import { firstValueFrom, Subject, fromEvent,
|
|
8
|
+
import { firstValueFrom, Subject, fromEvent, BehaviorSubject, filter, Subscription } from 'rxjs';
|
|
9
9
|
import * as i1 from '@angular/common';
|
|
10
10
|
import { DOCUMENT, CommonModule, KeyValuePipe, NgTemplateOutlet, DatePipe, SlicePipe } from '@angular/common';
|
|
11
11
|
import { nanoid } from 'nanoid';
|
|
@@ -24,7 +24,7 @@ import { DCMicComponent } from '@dataclouder/ngx-mic';
|
|
|
24
24
|
import * as i4 from 'primeng/textarea';
|
|
25
25
|
import { TextareaModule } from 'primeng/textarea';
|
|
26
26
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
27
|
-
import {
|
|
27
|
+
import { take, takeUntil, map } from 'rxjs/operators';
|
|
28
28
|
import * as i2$2 from 'primeng/popover';
|
|
29
29
|
import { PopoverModule } from 'primeng/popover';
|
|
30
30
|
import * as i1$3 from 'primeng/skeleton';
|
|
@@ -283,6 +283,7 @@ var ChatEventType;
|
|
|
283
283
|
ChatEventType["WordClicked"] = "wordClicked";
|
|
284
284
|
ChatEventType["MoodDetected"] = "moodDetected";
|
|
285
285
|
ChatEventType["AudioStarted"] = "audioStarted";
|
|
286
|
+
ChatEventType["AudioStopped"] = "audioStopped";
|
|
286
287
|
// Add other potential event types here as needed
|
|
287
288
|
// e.g., MessageSent = 'messageSent', SettingsChanged = 'settingsChanged', GoalCompleted = 'goalCompleted'
|
|
288
289
|
})(ChatEventType || (ChatEventType = {}));
|
|
@@ -1865,6 +1866,57 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
1865
1866
|
}]
|
|
1866
1867
|
}] });
|
|
1867
1868
|
|
|
1869
|
+
class ChatMonitorService {
|
|
1870
|
+
// Actualmente la única comunicación es con el background para cambiar el fondo.
|
|
1871
|
+
// El problema de comunicar así es si tiene que ser singleton, y en control markets tengo varias instancias de dc-chat.
|
|
1872
|
+
// Voy a utilizar el evento por ahora, y ver si combiene luego crear un servicio.
|
|
1873
|
+
// Se requiere comunicar el chat-engine con la app afuera, va a ver 2 formas de hacerlo.
|
|
1874
|
+
// Mediante eventos del chat, o estar subscrito al chat-monitor.
|
|
1875
|
+
// La complejidad del chat monitor es menor porque puedo injectarlo en cualquier lugar.
|
|
1876
|
+
// Los eventos estan bien pero si el vento viene muy interno tengo que propagarlo bastante.
|
|
1877
|
+
constructor() {
|
|
1878
|
+
this.activeAudioMessage = signal(null, ...(ngDevMode ? [{ debugName: "activeAudioMessage" }] : /* istanbul ignore next */ []));
|
|
1879
|
+
this.currentMood = signal(null, ...(ngDevMode ? [{ debugName: "currentMood" }] : /* istanbul ignore next */ []));
|
|
1880
|
+
this.isConversationActive = signal(false, ...(ngDevMode ? [{ debugName: "isConversationActive" }] : /* istanbul ignore next */ []));
|
|
1881
|
+
// Background
|
|
1882
|
+
this.backgroundUrl = signal(undefined, ...(ngDevMode ? [{ debugName: "backgroundUrl" }] : /* istanbul ignore next */ []));
|
|
1883
|
+
// Audio pipeline signals — source of truth for Live2D lip-sync bridge
|
|
1884
|
+
this.messageAudioWillPlay$ = signal(null, ...(ngDevMode ? [{ debugName: "messageAudioWillPlay$" }] : /* istanbul ignore next */ []));
|
|
1885
|
+
this.messageAudioStatus$ = signal(null, ...(ngDevMode ? [{ debugName: "messageAudioStatus$" }] : /* istanbul ignore next */ []));
|
|
1886
|
+
console.log(`%c ChatMonitorService instantiated: ${Math.random()}`, 'color: #00ff00; font-weight: bold; padding: 2px 4px; border: 1px solid #00ff00;');
|
|
1887
|
+
}
|
|
1888
|
+
logCurrentMood(mood) {
|
|
1889
|
+
this.currentMood.set(mood);
|
|
1890
|
+
}
|
|
1891
|
+
cleanMonitorVars() {
|
|
1892
|
+
this.currentMood.set(null);
|
|
1893
|
+
this.activeAudioMessage.set(null);
|
|
1894
|
+
this.isConversationActive.set(false);
|
|
1895
|
+
}
|
|
1896
|
+
setBackground(imageUrl) {
|
|
1897
|
+
if (imageUrl) {
|
|
1898
|
+
this.backgroundUrl.set(imageUrl);
|
|
1899
|
+
}
|
|
1900
|
+
else {
|
|
1901
|
+
this.backgroundUrl.set(undefined);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
logMessageAudioWillPlay(message) {
|
|
1905
|
+
this.messageAudioWillPlay$.set(message);
|
|
1906
|
+
}
|
|
1907
|
+
logMessageAudioStatus(message, status) {
|
|
1908
|
+
this.messageAudioStatus$.set({ message, status });
|
|
1909
|
+
}
|
|
1910
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMonitorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1911
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMonitorService, providedIn: 'root' }); }
|
|
1912
|
+
}
|
|
1913
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMonitorService, decorators: [{
|
|
1914
|
+
type: Injectable,
|
|
1915
|
+
args: [{
|
|
1916
|
+
providedIn: 'root',
|
|
1917
|
+
}]
|
|
1918
|
+
}], ctorParameters: () => [] });
|
|
1919
|
+
|
|
1868
1920
|
/**
|
|
1869
1921
|
* @description
|
|
1870
1922
|
* Service responsible for managing the real-time, dynamic state of a conversation flow.
|
|
@@ -1879,6 +1931,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
1879
1931
|
*/
|
|
1880
1932
|
class ConversationFlowStateService {
|
|
1881
1933
|
constructor() {
|
|
1934
|
+
this.chatMonitorService = inject(ChatMonitorService, { optional: true });
|
|
1882
1935
|
this.initialState = {
|
|
1883
1936
|
goal: { value: 0 },
|
|
1884
1937
|
challenges: [],
|
|
@@ -1944,6 +1997,7 @@ class ConversationFlowStateService {
|
|
|
1944
1997
|
}
|
|
1945
1998
|
if (newState.moodState?.value) {
|
|
1946
1999
|
this.moodUpdated.next(newState.moodState.value);
|
|
2000
|
+
this.chatMonitorService?.logCurrentMood(newState.moodState.value);
|
|
1947
2001
|
}
|
|
1948
2002
|
if (newState.goal && newState.goal.deltaPoints !== undefined && newState.goal.deltaPoints !== 0) {
|
|
1949
2003
|
this.goalEvaluationUpdated.next({ deltaPoints: newState.goal.deltaPoints });
|
|
@@ -2004,6 +2058,7 @@ class ConversationFlowStateService {
|
|
|
2004
2058
|
}));
|
|
2005
2059
|
if (moodState.value) {
|
|
2006
2060
|
this.moodUpdated.next(moodState.value);
|
|
2061
|
+
this.chatMonitorService?.logCurrentMood(moodState.value);
|
|
2007
2062
|
}
|
|
2008
2063
|
}
|
|
2009
2064
|
/**
|
|
@@ -2505,58 +2560,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
2505
2560
|
}]
|
|
2506
2561
|
}] });
|
|
2507
2562
|
|
|
2508
|
-
class ChatMonitorService {
|
|
2509
|
-
// Actualmente la única comunicación es con el background para cambiar el fondo.
|
|
2510
|
-
// El problema de comunicar así es si tiene que ser singleton, y en control markets tengo varias instancias de dc-chat.
|
|
2511
|
-
// Voy a utilizar el evento por ahora, y ver si combiene luego crear un servicio.
|
|
2512
|
-
// Se requiere comunicar el chat-engine con la app afuera, va a ver 2 formas de hacerlo.
|
|
2513
|
-
// Mediante eventos del chat, o estar subscrito al chat-monitor.
|
|
2514
|
-
// La complejidad del chat monitor es menor porque puedo injectarlo en cualquier lugar.
|
|
2515
|
-
// Los eventos estan bien pero si el vento viene muy interno tengo que propagarlo bastante.
|
|
2516
|
-
constructor() {
|
|
2517
|
-
this.messageAudioWillPlay = signal(null, ...(ngDevMode ? [{ debugName: "messageAudioWillPlay" }] : /* istanbul ignore next */ []));
|
|
2518
|
-
this.messageAudioWillPlay$ = this.messageAudioWillPlay.asReadonly();
|
|
2519
|
-
this.currentMood = signal(null, ...(ngDevMode ? [{ debugName: "currentMood" }] : /* istanbul ignore next */ []));
|
|
2520
|
-
this.isConversationActive = signal(false, ...(ngDevMode ? [{ debugName: "isConversationActive" }] : /* istanbul ignore next */ []));
|
|
2521
|
-
// Background
|
|
2522
|
-
this.backgroundUrl = signal(undefined, ...(ngDevMode ? [{ debugName: "backgroundUrl" }] : /* istanbul ignore next */ []));
|
|
2523
|
-
console.log(`%c ChatMonitorService instantiated: ${Math.random()}`, 'color: #00ff00; font-weight: bold; padding: 2px 4px; border: 1px solid #00ff00;');
|
|
2524
|
-
}
|
|
2525
|
-
logMessageAudioWillPlay(message) {
|
|
2526
|
-
// console.log('Audio will play....', message);
|
|
2527
|
-
this.messageAudioWillPlay.set(message);
|
|
2528
|
-
}
|
|
2529
|
-
logCurrentMood(mood) {
|
|
2530
|
-
this.currentMood.set(mood);
|
|
2531
|
-
}
|
|
2532
|
-
cleanMonitorVars() {
|
|
2533
|
-
this.currentMood.set(null);
|
|
2534
|
-
this.messageAudioWillPlay.set(null);
|
|
2535
|
-
this.isConversationActive.set(false);
|
|
2536
|
-
}
|
|
2537
|
-
setBackground(imageUrl) {
|
|
2538
|
-
if (imageUrl) {
|
|
2539
|
-
this.backgroundUrl.set(imageUrl);
|
|
2540
|
-
}
|
|
2541
|
-
else {
|
|
2542
|
-
this.backgroundUrl.set(undefined);
|
|
2543
|
-
}
|
|
2544
|
-
}
|
|
2545
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMonitorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2546
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMonitorService, providedIn: 'root' }); }
|
|
2547
|
-
}
|
|
2548
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMonitorService, decorators: [{
|
|
2549
|
-
type: Injectable,
|
|
2550
|
-
args: [{
|
|
2551
|
-
providedIn: 'root',
|
|
2552
|
-
}]
|
|
2553
|
-
}], ctorParameters: () => [] });
|
|
2554
|
-
|
|
2555
2563
|
class MessageProcessingService {
|
|
2556
2564
|
constructor() {
|
|
2557
2565
|
this.MAX_CHUNK_LENGTH = 190;
|
|
2558
2566
|
}
|
|
2559
|
-
processMessage(message, conversationSettings) {
|
|
2567
|
+
processMessage(message, conversationSettings, voiceSettings) {
|
|
2560
2568
|
const processedMessage = {
|
|
2561
2569
|
...message,
|
|
2562
2570
|
messageId: message.messageId || nanoid(),
|
|
@@ -2568,25 +2576,25 @@ class MessageProcessingService {
|
|
|
2568
2576
|
return processedMessage;
|
|
2569
2577
|
}
|
|
2570
2578
|
if (processedMessage.role === ChatRole.Assistant) {
|
|
2571
|
-
return this.processAssistantMessage(processedMessage, conversationSettings);
|
|
2579
|
+
return this.processAssistantMessage(processedMessage, conversationSettings, voiceSettings);
|
|
2572
2580
|
}
|
|
2573
2581
|
return processedMessage;
|
|
2574
2582
|
}
|
|
2575
|
-
processAssistantMessage(message, settings) {
|
|
2583
|
+
processAssistantMessage(message, settings, voiceSettings) {
|
|
2576
2584
|
if (settings.avatarImages.assistant) {
|
|
2577
2585
|
message.imgUrl = settings.avatarImages.assistant;
|
|
2578
2586
|
}
|
|
2579
|
-
const mainVoice = settings?.mainVoice?.voice || settings?.tts?.voice;
|
|
2587
|
+
const mainVoice = voiceSettings?.main?.voice || settings?.mainVoice?.voice || settings?.tts?.voice;
|
|
2580
2588
|
message.voice = this.getVoice(mainVoice);
|
|
2581
2589
|
switch (settings.textEngine) {
|
|
2582
2590
|
case TextEngines.SimpleText:
|
|
2583
2591
|
this.processSimpleText(message, settings);
|
|
2584
2592
|
break;
|
|
2585
2593
|
case TextEngines.MarkdownMultiMessages:
|
|
2586
|
-
this.processMultiMessages(message, settings);
|
|
2594
|
+
this.processMultiMessages(message, settings, voiceSettings);
|
|
2587
2595
|
break;
|
|
2588
2596
|
case TextEngines.MarkdownSSML:
|
|
2589
|
-
this.processSSML(message, settings);
|
|
2597
|
+
this.processSSML(message, settings, voiceSettings);
|
|
2590
2598
|
break;
|
|
2591
2599
|
}
|
|
2592
2600
|
return message;
|
|
@@ -2605,9 +2613,9 @@ class MessageProcessingService {
|
|
|
2605
2613
|
};
|
|
2606
2614
|
});
|
|
2607
2615
|
}
|
|
2608
|
-
processMultiMessages(message, settings) {
|
|
2616
|
+
processMultiMessages(message, settings, voiceSettings) {
|
|
2609
2617
|
const htmlSegments = convertToHTML(message.content);
|
|
2610
|
-
const secondaryVoice = settings?.secondaryVoice?.voice || settings?.tts?.secondaryVoice || 'en-US-News-L';
|
|
2618
|
+
const secondaryVoice = voiceSettings?.secondary?.voice || settings?.secondaryVoice?.voice || settings?.tts?.secondaryVoice || 'en-US-News-L';
|
|
2611
2619
|
const processedSegments = [];
|
|
2612
2620
|
for (const segment of htmlSegments) {
|
|
2613
2621
|
const isItalics = segment.tag === 'em';
|
|
@@ -2656,11 +2664,12 @@ class MessageProcessingService {
|
|
|
2656
2664
|
return `<p>${text}</p>`;
|
|
2657
2665
|
}
|
|
2658
2666
|
}
|
|
2659
|
-
processSSML(message, settings) {
|
|
2660
|
-
|
|
2667
|
+
processSSML(message, settings, voiceSettings) {
|
|
2668
|
+
const secondaryVoice = voiceSettings?.secondary?.voice || settings?.secondaryVoice?.voice || settings?.tts?.secondaryVoice;
|
|
2669
|
+
if (!secondaryVoice) {
|
|
2661
2670
|
throw new Error('Secondary voice is required for SSML');
|
|
2662
2671
|
}
|
|
2663
|
-
const content = this.subsItalicsByTag(message.content,
|
|
2672
|
+
const content = this.subsItalicsByTag(message.content, secondaryVoice);
|
|
2664
2673
|
message.ssml = `<speak>${content}</speak>`;
|
|
2665
2674
|
}
|
|
2666
2675
|
splitContent(content, maxLength) {
|
|
@@ -3469,7 +3478,7 @@ class ConversationService {
|
|
|
3469
3478
|
}
|
|
3470
3479
|
createNewUserMessage() {
|
|
3471
3480
|
const message = { content: '...', role: ChatRole.User };
|
|
3472
|
-
const processedMessage = this.messageProcessingService.processMessage(message, this.conversationSettingsState());
|
|
3481
|
+
const processedMessage = this.messageProcessingService.processMessage(message, this.conversationSettingsState(), this.agentCardState.agentCard$()?.voice);
|
|
3473
3482
|
this.messagesStateService.addMessage(processedMessage);
|
|
3474
3483
|
return processedMessage.messageId;
|
|
3475
3484
|
}
|
|
@@ -3576,7 +3585,7 @@ class ConversationService {
|
|
|
3576
3585
|
const firstAssistantMsg = conversationSettings.messages.find((message) => message.role === ChatRole.Assistant);
|
|
3577
3586
|
if (firstAssistantMsg) {
|
|
3578
3587
|
// Process the first assistant message
|
|
3579
|
-
const processedMessage = this.messageProcessingService.processMessage(firstAssistantMsg, this.conversationSettingsState());
|
|
3588
|
+
const processedMessage = this.messageProcessingService.processMessage(firstAssistantMsg, this.conversationSettingsState(), this.agentCardState.agentCard$()?.voice);
|
|
3580
3589
|
this.messagesStateService.updateMessage(firstAssistantMsg.messageId, processedMessage);
|
|
3581
3590
|
// Find the index of the message with the matching ID
|
|
3582
3591
|
const messageIndex = conversationSettings.messages.findIndex((message) => message.messageId === firstAssistantMsg.messageId);
|
|
@@ -3625,7 +3634,7 @@ class ConversationService {
|
|
|
3625
3634
|
}
|
|
3626
3635
|
else {
|
|
3627
3636
|
// Means is new meessage, Process and add the new message
|
|
3628
|
-
const processedMessage = this.messageProcessingService.processMessage(message, this.conversationSettingsState());
|
|
3637
|
+
const processedMessage = this.messageProcessingService.processMessage(message, this.conversationSettingsState(), this.agentCardState.agentCard$()?.voice);
|
|
3629
3638
|
// Ensure ID exists (processMessage should handle this, but fallback just in case)
|
|
3630
3639
|
processedMessage.messageId = processedMessage.messageId || nanoid();
|
|
3631
3640
|
this.messagesStateService.addMessage(processedMessage);
|
|
@@ -3696,7 +3705,7 @@ class ConversationService {
|
|
|
3696
3705
|
throw new Error('No message returned from AI');
|
|
3697
3706
|
}
|
|
3698
3707
|
// Process response
|
|
3699
|
-
const newMessage = this.messageProcessingService.processMessage({ content: response.content, role: ChatRole.Assistant }, conversationSettings);
|
|
3708
|
+
const newMessage = this.messageProcessingService.processMessage({ content: response.content, role: ChatRole.Assistant }, conversationSettings, this.agentCardState.agentCard$()?.voice);
|
|
3700
3709
|
this.messagesStateService.addMessage(newMessage);
|
|
3701
3710
|
this.isThinkingSignal.set(false);
|
|
3702
3711
|
// Run Dynamic Flow Evaluations
|
|
@@ -3705,9 +3714,10 @@ class ConversationService {
|
|
|
3705
3714
|
}
|
|
3706
3715
|
async handleStreamingResponse(conversation, conversationSettings) {
|
|
3707
3716
|
const messageId = nanoid();
|
|
3708
|
-
const
|
|
3717
|
+
const agentCard = this.agentCardState.agentCard$();
|
|
3718
|
+
const mainVoice = agentCard?.voice?.main?.voice || conversationSettings?.mainVoice?.voice || conversationSettings?.tts?.voice;
|
|
3709
3719
|
const assistantVoice = this.messageProcessingService.getVoice(mainVoice);
|
|
3710
|
-
const secondaryVoice = conversationSettings?.secondaryVoice?.voice || conversationSettings?.tts?.secondaryVoice || 'en-US-News-L';
|
|
3720
|
+
const secondaryVoice = agentCard?.voice?.secondary?.voice || conversationSettings?.secondaryVoice?.voice || conversationSettings?.tts?.secondaryVoice || 'en-US-News-L';
|
|
3711
3721
|
let assistantMessage = {
|
|
3712
3722
|
messageId,
|
|
3713
3723
|
role: ChatRole.Assistant,
|
|
@@ -3879,7 +3889,7 @@ class ConversationService {
|
|
|
3879
3889
|
isLoading: true,
|
|
3880
3890
|
};
|
|
3881
3891
|
// Process to get initial structure and ensure it's ready for display
|
|
3882
|
-
const processedPlaceholder = this.messageProcessingService.processMessage(placeholder, this.conversationSettingsState());
|
|
3892
|
+
const processedPlaceholder = this.messageProcessingService.processMessage(placeholder, this.conversationSettingsState(), this.agentCardState.agentCard$()?.voice);
|
|
3883
3893
|
// Use passed updateId or newly generated one from processing
|
|
3884
3894
|
const messageId = updateId || processedPlaceholder.messageId || nanoid();
|
|
3885
3895
|
processedPlaceholder.messageId = messageId;
|
|
@@ -4003,7 +4013,6 @@ function provideLive2D(config) {
|
|
|
4003
4013
|
|
|
4004
4014
|
class VideoPlayerService {
|
|
4005
4015
|
constructor() {
|
|
4006
|
-
this.conversationFlowStateService = inject(ConversationFlowStateService);
|
|
4007
4016
|
this.useNativePlayer = false;
|
|
4008
4017
|
this.isRewinding = false;
|
|
4009
4018
|
this.videoQueue = [];
|
|
@@ -4013,17 +4022,6 @@ class VideoPlayerService {
|
|
|
4013
4022
|
this.agentCard = signal(undefined, ...(ngDevMode ? [{ debugName: "agentCard" }] : /* istanbul ignore next */ []));
|
|
4014
4023
|
console.log(' CONSTRUCTOR FOR VIDEO PLAYER...');
|
|
4015
4024
|
this.playAndRewindedSubscription = this.playAndRewinded$.subscribe(() => { });
|
|
4016
|
-
this.moodSubscription = this.conversationFlowStateService.moodUpdated$.subscribe((mood) => {
|
|
4017
|
-
console.log('CHANGING MOOD.. ', mood);
|
|
4018
|
-
const card = this.agentCard();
|
|
4019
|
-
if (mood && card?.assets?.motions) {
|
|
4020
|
-
const motionUrl = card.assets.motions.find((m) => m.metadata?.moodState === mood)?.url;
|
|
4021
|
-
if (motionUrl) {
|
|
4022
|
-
this.addVideosToQueue([motionUrl]);
|
|
4023
|
-
this.playAndRewind();
|
|
4024
|
-
}
|
|
4025
|
-
}
|
|
4026
|
-
});
|
|
4027
4025
|
}
|
|
4028
4026
|
setAgentCard(card) {
|
|
4029
4027
|
this.agentCard.set(card);
|
|
@@ -4155,9 +4153,6 @@ class VideoPlayerService {
|
|
|
4155
4153
|
if (this.playAndRewindedSubscription) {
|
|
4156
4154
|
this.playAndRewindedSubscription.unsubscribe();
|
|
4157
4155
|
}
|
|
4158
|
-
if (this.moodSubscription) {
|
|
4159
|
-
this.moodSubscription.unsubscribe();
|
|
4160
|
-
}
|
|
4161
4156
|
}
|
|
4162
4157
|
playNextInQueueNative() {
|
|
4163
4158
|
if (this.videoQueue.length > 0) {
|
|
@@ -4245,25 +4240,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
4245
4240
|
|
|
4246
4241
|
class VideoPlayerNativeService {
|
|
4247
4242
|
constructor() {
|
|
4248
|
-
this.conversationFlowStateService = inject(ConversationFlowStateService);
|
|
4249
4243
|
this.videoQueue = [];
|
|
4250
4244
|
this.defaultVideoUrl = null;
|
|
4251
4245
|
this.pendingPlayingHandler = null;
|
|
4252
4246
|
this.pendingPlayingTarget = null;
|
|
4253
4247
|
this.agentCard = signal(undefined, ...(ngDevMode ? [{ debugName: "agentCard" }] : /* istanbul ignore next */ []));
|
|
4254
4248
|
this.isVideoPlaying = signal(false, ...(ngDevMode ? [{ debugName: "isVideoPlaying" }] : /* istanbul ignore next */ []));
|
|
4255
|
-
this.moodSubscription = this.conversationFlowStateService.moodUpdated$.subscribe((mood) => {
|
|
4256
|
-
const card = this.agentCard();
|
|
4257
|
-
if (!mood || !card?.assets?.motions)
|
|
4258
|
-
return;
|
|
4259
|
-
const motionUrl = card.assets.motions.find((m) => m.metadata?.moodState === mood)?.url;
|
|
4260
|
-
if (!motionUrl)
|
|
4261
|
-
return;
|
|
4262
|
-
// Interrupt current playback. transitionToVideo loads the new clip
|
|
4263
|
-
// into the hidden <video> and crossfades only when its first frame
|
|
4264
|
-
// is ready, so the active clip keeps showing until then (no flash).
|
|
4265
|
-
this.transitionToVideo(motionUrl);
|
|
4266
|
-
});
|
|
4267
4249
|
this.onVideoEndedBound = this.onVideoEnded.bind(this);
|
|
4268
4250
|
}
|
|
4269
4251
|
setAgentCard(card) {
|
|
@@ -4346,7 +4328,6 @@ class VideoPlayerNativeService {
|
|
|
4346
4328
|
this.agentCard.set(undefined);
|
|
4347
4329
|
}
|
|
4348
4330
|
ngOnDestroy() {
|
|
4349
|
-
this.moodSubscription?.unsubscribe();
|
|
4350
4331
|
this.destroyPlayer();
|
|
4351
4332
|
}
|
|
4352
4333
|
onVideoEnded() {
|
|
@@ -4629,563 +4610,831 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
4629
4610
|
args: [DCMicComponent]
|
|
4630
4611
|
}], isAIThinking: [{ type: i0.Input, args: [{ isSignal: true, alias: "isAIThinking", required: false }] }], micSettings: [{ type: i0.Input, args: [{ isSignal: true, alias: "micSettings", required: false }] }], sendMessage: [{ type: i0.Output, args: ["sendMessage"] }], textInputChanged: [{ type: i0.Output, args: ["textInputChanged"] }] } });
|
|
4631
4612
|
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
//
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
}
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4613
|
+
class AudioTextSyncService {
|
|
4614
|
+
constructor() {
|
|
4615
|
+
// Maps to store message-specific signals and observables
|
|
4616
|
+
this.highlightedWordsSignalMap = new Map();
|
|
4617
|
+
this.highlightedWords$Map = new Map();
|
|
4618
|
+
// Maps for cleanup and active audio elements
|
|
4619
|
+
this.cleanup$Map = new Map();
|
|
4620
|
+
this.activeAudioMap = new Map();
|
|
4621
|
+
this.currentPlayingAudio = null;
|
|
4622
|
+
this.chatMonitorService = inject(ChatMonitorService, { optional: true });
|
|
4623
|
+
this.destroyRef = inject(DestroyRef);
|
|
4624
|
+
this.currentActiveMessage = signal(null, ...(ngDevMode ? [{ debugName: "currentActiveMessage" }] : /* istanbul ignore next */ []));
|
|
4625
|
+
// Ensure cleanup when service is destroyed
|
|
4626
|
+
this.destroyRef.onDestroy(() => {
|
|
4627
|
+
this.stopAllSyncs();
|
|
4628
|
+
});
|
|
4629
|
+
}
|
|
4630
|
+
/**
|
|
4631
|
+
* Plays the given audio element exclusively, pausing any other active audio.
|
|
4632
|
+
* @param audioElement The audio element to play.
|
|
4633
|
+
* @param message The message associated with this audio playback.
|
|
4634
|
+
*/
|
|
4635
|
+
playAudio(audioElement, message) {
|
|
4636
|
+
if (this.currentPlayingAudio && this.currentPlayingAudio !== audioElement) {
|
|
4637
|
+
this.currentPlayingAudio.pause();
|
|
4638
|
+
this.currentPlayingAudio.currentTime = 0;
|
|
4639
|
+
}
|
|
4640
|
+
// Ensure audioUrl is present on the message if it's currently empty but the element has a source
|
|
4641
|
+
if (!message.audioUrl && audioElement.src) {
|
|
4642
|
+
console.log('AudioTextSyncService: fixing empty message.audioUrl with audioElement.src:', audioElement.src);
|
|
4643
|
+
message.audioUrl = audioElement.src;
|
|
4644
|
+
}
|
|
4645
|
+
console.log('AudioTextSyncService: playAudio emitting to ChatMonitorService activeAudioMessage:', {
|
|
4646
|
+
messageId: message.messageId,
|
|
4647
|
+
audioUrl: message.audioUrl,
|
|
4648
|
+
src: audioElement.src
|
|
4649
|
+
});
|
|
4650
|
+
this.currentPlayingAudio = audioElement;
|
|
4651
|
+
this.currentActiveMessage.set(message);
|
|
4652
|
+
this.chatMonitorService?.activeAudioMessage.set(message);
|
|
4653
|
+
this.chatMonitorService?.logMessageAudioWillPlay(message);
|
|
4654
|
+
this.chatMonitorService?.logMessageAudioStatus(message, 'playing');
|
|
4655
|
+
// Setup ended listener to clear state automatically when playback finishes
|
|
4656
|
+
fromEvent(audioElement, 'ended')
|
|
4657
|
+
.pipe(take(1))
|
|
4658
|
+
.subscribe(() => {
|
|
4659
|
+
if (this.currentPlayingAudio === audioElement) {
|
|
4660
|
+
this.currentActiveMessage.set(null);
|
|
4661
|
+
this.chatMonitorService?.activeAudioMessage.set(null);
|
|
4662
|
+
this.chatMonitorService?.logMessageAudioStatus(message, 'finished');
|
|
4651
4663
|
}
|
|
4652
|
-
|
|
4653
|
-
|
|
4664
|
+
});
|
|
4665
|
+
audioElement.play().catch((error) => {
|
|
4666
|
+
console.error('Error playing audio in AudioTextSyncService:', error);
|
|
4667
|
+
this.currentActiveMessage.set(null);
|
|
4668
|
+
this.chatMonitorService?.activeAudioMessage.set(null);
|
|
4669
|
+
this.chatMonitorService?.logMessageAudioStatus(message, 'stopped');
|
|
4670
|
+
});
|
|
4671
|
+
}
|
|
4672
|
+
/**
|
|
4673
|
+
* Pauses the given audio element.
|
|
4674
|
+
* @param audioElement The audio element to pause.
|
|
4675
|
+
*/
|
|
4676
|
+
pauseAudio(audioElement) {
|
|
4677
|
+
const message = this.currentActiveMessage();
|
|
4678
|
+
audioElement.pause();
|
|
4679
|
+
if (this.currentPlayingAudio === audioElement) {
|
|
4680
|
+
this.currentActiveMessage.set(null);
|
|
4681
|
+
this.chatMonitorService?.activeAudioMessage.set(null);
|
|
4682
|
+
if (message) {
|
|
4683
|
+
this.chatMonitorService?.logMessageAudioStatus(message, 'stopped');
|
|
4654
4684
|
}
|
|
4655
4685
|
}
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4686
|
+
}
|
|
4687
|
+
/**
|
|
4688
|
+
* Synchronizes audio playback with text transcription
|
|
4689
|
+
* @param audioElement The audio element to sync with
|
|
4690
|
+
* @param transcriptionTimestamps Array of word timestamps
|
|
4691
|
+
* @param messageId Unique identifier for the message
|
|
4692
|
+
*/
|
|
4693
|
+
syncAudioWithText(audioElement, transcriptionTimestamps, messageId) {
|
|
4694
|
+
// Stop any existing sync for this message
|
|
4695
|
+
this.stopSync(messageId);
|
|
4696
|
+
// Create new signal and subject for this message if they don't exist
|
|
4697
|
+
if (!this.highlightedWordsSignalMap.has(messageId)) {
|
|
4698
|
+
this.highlightedWordsSignalMap.set(messageId, signal([]));
|
|
4699
|
+
}
|
|
4700
|
+
if (!this.highlightedWords$Map.has(messageId)) {
|
|
4701
|
+
this.highlightedWords$Map.set(messageId, new BehaviorSubject([]));
|
|
4702
|
+
}
|
|
4703
|
+
// Create cleanup subject
|
|
4704
|
+
const cleanup$ = new Subject();
|
|
4705
|
+
this.cleanup$Map.set(messageId, cleanup$);
|
|
4706
|
+
// Store the active audio element
|
|
4707
|
+
this.activeAudioMap.set(messageId, audioElement);
|
|
4708
|
+
// Get the signal and subject for this message
|
|
4709
|
+
const messageSignal = this.highlightedWordsSignalMap.get(messageId);
|
|
4710
|
+
const messageSubject = this.highlightedWords$Map.get(messageId);
|
|
4711
|
+
// Initialize the highlighted words state
|
|
4712
|
+
const initialWords = transcriptionTimestamps.map((word, index) => ({
|
|
4713
|
+
word: word.word,
|
|
4714
|
+
index,
|
|
4715
|
+
isHighlighted: false,
|
|
4716
|
+
}));
|
|
4717
|
+
// Update both signal and observable
|
|
4718
|
+
messageSignal.set(initialWords);
|
|
4719
|
+
messageSubject.next(initialWords);
|
|
4720
|
+
// Listen to timeupdate events
|
|
4721
|
+
fromEvent(audioElement, 'timeupdate')
|
|
4722
|
+
.pipe(takeUntilDestroyed(this.destroyRef), takeUntil(cleanup$), map(() => audioElement.currentTime))
|
|
4723
|
+
.subscribe((currentTime) => {
|
|
4724
|
+
const updatedWords = transcriptionTimestamps.map((word, index) => {
|
|
4725
|
+
const isHighlighted = currentTime >= word.start - 0.15 && currentTime < word.end + 0.15;
|
|
4726
|
+
return {
|
|
4727
|
+
word: word.word,
|
|
4728
|
+
index,
|
|
4729
|
+
isHighlighted,
|
|
4730
|
+
};
|
|
4731
|
+
});
|
|
4732
|
+
// Update both signal and observable for this message
|
|
4733
|
+
messageSignal.set(updatedWords);
|
|
4734
|
+
messageSubject.next(updatedWords);
|
|
4735
|
+
});
|
|
4736
|
+
// Listen to ended event for cleanup
|
|
4737
|
+
fromEvent(audioElement, 'ended')
|
|
4738
|
+
.pipe(takeUntilDestroyed(this.destroyRef), takeUntil(cleanup$))
|
|
4739
|
+
.subscribe(() => {
|
|
4740
|
+
// Reset highlighting when audio ends
|
|
4741
|
+
const resetWords = initialWords.map((word) => ({
|
|
4742
|
+
...word,
|
|
4743
|
+
isHighlighted: false,
|
|
4744
|
+
}));
|
|
4745
|
+
messageSignal.set(resetWords);
|
|
4746
|
+
messageSubject.next(resetWords);
|
|
4747
|
+
});
|
|
4748
|
+
}
|
|
4749
|
+
/**
|
|
4750
|
+
* Stops the sync for a specific message and cleans up resources
|
|
4751
|
+
* @param messageId The ID of the message to stop syncing
|
|
4752
|
+
*/
|
|
4753
|
+
stopSync(messageId) {
|
|
4754
|
+
if (this.activeAudioMap.has(messageId)) {
|
|
4755
|
+
const audio = this.activeAudioMap.get(messageId);
|
|
4756
|
+
const currentMsg = this.currentActiveMessage();
|
|
4757
|
+
if (audio && this.currentPlayingAudio === audio) {
|
|
4758
|
+
audio.pause();
|
|
4759
|
+
audio.currentTime = 0;
|
|
4760
|
+
this.currentPlayingAudio = null;
|
|
4761
|
+
this.currentActiveMessage.set(null);
|
|
4762
|
+
this.chatMonitorService?.activeAudioMessage.set(null);
|
|
4763
|
+
if (currentMsg) {
|
|
4764
|
+
this.chatMonitorService?.logMessageAudioStatus(currentMsg, 'stopped');
|
|
4765
|
+
}
|
|
4659
4766
|
}
|
|
4660
|
-
|
|
4661
|
-
|
|
4767
|
+
// Get the cleanup subject for this message
|
|
4768
|
+
const cleanup$ = this.cleanup$Map.get(messageId);
|
|
4769
|
+
if (cleanup$) {
|
|
4770
|
+
cleanup$.next();
|
|
4771
|
+
cleanup$.complete();
|
|
4772
|
+
this.cleanup$Map.delete(messageId);
|
|
4662
4773
|
}
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4774
|
+
this.activeAudioMap.delete(messageId);
|
|
4775
|
+
// Reset state for this message
|
|
4776
|
+
const messageSignal = this.highlightedWordsSignalMap.get(messageId);
|
|
4777
|
+
const messageSubject = this.highlightedWords$Map.get(messageId);
|
|
4778
|
+
if (messageSignal) {
|
|
4779
|
+
messageSignal.set([]);
|
|
4667
4780
|
}
|
|
4668
|
-
|
|
4669
|
-
|
|
4781
|
+
if (messageSubject) {
|
|
4782
|
+
messageSubject.next([]);
|
|
4670
4783
|
}
|
|
4671
4784
|
}
|
|
4672
|
-
else {
|
|
4673
|
-
// It's a word (including punctuation)
|
|
4674
|
-
const currentTag = tagStack.length > 0 ? tagStack[tagStack.length - 1] : '';
|
|
4675
|
-
result.push({ word: token, tag: currentTag });
|
|
4676
|
-
}
|
|
4677
|
-
}
|
|
4678
|
-
// Note: This implementation assumes correctly matched opening and closing tags.
|
|
4679
|
-
// Unclosed tags at the end of the string will be ignored.
|
|
4680
|
-
return result;
|
|
4681
|
-
}
|
|
4682
|
-
|
|
4683
|
-
// Removed AudioState as it's replaced by AudioStatus from agent.models
|
|
4684
|
-
class MessageContentDisplayer {
|
|
4685
|
-
get hostHighlightColor() {
|
|
4686
|
-
return this.highlightColor();
|
|
4687
4785
|
}
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
// Signals State computed from the input message
|
|
4702
|
-
this.wordsWithMetaState = [];
|
|
4703
|
-
this.lastHasTranscription = false;
|
|
4704
|
-
this.audioElement = signal(null, ...(ngDevMode ? [{ debugName: "audioElement" }] : /* istanbul ignore next */ []));
|
|
4705
|
-
this.destroyRef = inject(DestroyRef);
|
|
4706
|
-
this.iconState = computed(() => {
|
|
4707
|
-
const msg = this.message();
|
|
4708
|
-
if (msg?.isLoading) {
|
|
4709
|
-
return 'loading';
|
|
4710
|
-
}
|
|
4711
|
-
const status = this.localAudioStatus() || msg?.audioStatus;
|
|
4712
|
-
if (status === 'playing') {
|
|
4713
|
-
return 'playing';
|
|
4714
|
-
}
|
|
4715
|
-
if (status === 'stopped') {
|
|
4716
|
-
return 'paused';
|
|
4717
|
-
}
|
|
4718
|
-
if (status === 'finished') {
|
|
4719
|
-
return 'finished';
|
|
4720
|
-
}
|
|
4721
|
-
if (status === 'pending') {
|
|
4722
|
-
return 'pending';
|
|
4723
|
-
}
|
|
4724
|
-
if (msg?.audioUrl) {
|
|
4725
|
-
return 'playable';
|
|
4786
|
+
/**
|
|
4787
|
+
* Stops all syncs and cleans up all resources
|
|
4788
|
+
*/
|
|
4789
|
+
stopAllSyncs() {
|
|
4790
|
+
const currentMsg = this.currentActiveMessage();
|
|
4791
|
+
if (this.currentPlayingAudio) {
|
|
4792
|
+
this.currentPlayingAudio.pause();
|
|
4793
|
+
this.currentPlayingAudio.currentTime = 0;
|
|
4794
|
+
this.currentPlayingAudio = null;
|
|
4795
|
+
this.currentActiveMessage.set(null);
|
|
4796
|
+
this.chatMonitorService?.activeAudioMessage.set(null);
|
|
4797
|
+
if (currentMsg) {
|
|
4798
|
+
this.chatMonitorService?.logMessageAudioStatus(currentMsg, 'stopped');
|
|
4726
4799
|
}
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
this.
|
|
4735
|
-
|
|
4736
|
-
this.
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4800
|
+
}
|
|
4801
|
+
// Get all message IDs and stop each sync
|
|
4802
|
+
for (const messageId of this.activeAudioMap.keys()) {
|
|
4803
|
+
this.stopSync(messageId);
|
|
4804
|
+
}
|
|
4805
|
+
// Clear all maps
|
|
4806
|
+
this.highlightedWordsSignalMap.clear();
|
|
4807
|
+
this.highlightedWords$Map.clear();
|
|
4808
|
+
this.cleanup$Map.clear();
|
|
4809
|
+
this.activeAudioMap.clear();
|
|
4810
|
+
}
|
|
4811
|
+
/**
|
|
4812
|
+
* Returns the highlighted words signal for a specific message
|
|
4813
|
+
* @param messageId The ID of the message
|
|
4814
|
+
*/
|
|
4815
|
+
getHighlightedWordsSignal(messageId) {
|
|
4816
|
+
// Create a new signal for this message if it doesn't exist
|
|
4817
|
+
if (!this.highlightedWordsSignalMap.has(messageId)) {
|
|
4818
|
+
this.highlightedWordsSignalMap.set(messageId, signal([]));
|
|
4819
|
+
}
|
|
4820
|
+
return this.highlightedWordsSignalMap.get(messageId);
|
|
4821
|
+
}
|
|
4822
|
+
/**
|
|
4823
|
+
* Returns the highlighted words observable for a specific message
|
|
4824
|
+
* @param messageId The ID of the message
|
|
4825
|
+
*/
|
|
4826
|
+
getHighlightedWords$(messageId) {
|
|
4827
|
+
// Create a new subject for this message if it doesn't exist
|
|
4828
|
+
if (!this.highlightedWords$Map.has(messageId)) {
|
|
4829
|
+
this.highlightedWords$Map.set(messageId, new BehaviorSubject([]));
|
|
4830
|
+
}
|
|
4831
|
+
return this.highlightedWords$Map.get(messageId).asObservable();
|
|
4832
|
+
}
|
|
4833
|
+
/**
|
|
4834
|
+
* Checks if a word at a specific index is currently highlighted for a specific message
|
|
4835
|
+
* @param messageId The ID of the message
|
|
4836
|
+
* @param index The index of the word to check
|
|
4837
|
+
*/
|
|
4838
|
+
isWordHighlighted(messageId, index) {
|
|
4839
|
+
const messageSignal = this.getHighlightedWordsSignal(messageId);
|
|
4840
|
+
return messageSignal().some((word) => word.index === index && word.isHighlighted);
|
|
4841
|
+
}
|
|
4842
|
+
/**
|
|
4843
|
+
* Returns an observable that emits true when a word at a specific index is highlighted for a specific message
|
|
4844
|
+
* @param messageId The ID of the message
|
|
4845
|
+
* @param index The index of the word to observe
|
|
4846
|
+
*/
|
|
4847
|
+
isWordHighlighted$(messageId, index) {
|
|
4848
|
+
return this.getHighlightedWords$(messageId).pipe(map((words) => words.some((word) => word.index === index && word.isHighlighted)));
|
|
4849
|
+
}
|
|
4850
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AudioTextSyncService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4851
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AudioTextSyncService, providedIn: 'root' }); }
|
|
4852
|
+
}
|
|
4853
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AudioTextSyncService, decorators: [{
|
|
4854
|
+
type: Injectable,
|
|
4855
|
+
args: [{
|
|
4856
|
+
providedIn: 'root',
|
|
4857
|
+
}]
|
|
4858
|
+
}], ctorParameters: () => [] });
|
|
4859
|
+
|
|
4860
|
+
/**
|
|
4861
|
+
* @Injectable
|
|
4862
|
+
*
|
|
4863
|
+
* @description
|
|
4864
|
+
* This service orchestrates the playback of chat messages, ensuring that they are played sequentially.
|
|
4865
|
+
* It manages a queue of messages and handles the generation and playback of audio for each message.
|
|
4866
|
+
*
|
|
4867
|
+
* @see ChatMessageOrchestratorComponent
|
|
4868
|
+
*/
|
|
4869
|
+
class MessageOrchestrationService {
|
|
4870
|
+
constructor() {
|
|
4871
|
+
// Services
|
|
4872
|
+
this.conversationService = inject(ConversationService);
|
|
4873
|
+
this.chatMonitorService = inject(ChatMonitorService);
|
|
4874
|
+
this.userService = inject(UserService);
|
|
4875
|
+
this.messagesStateService = inject(MessagesStateService);
|
|
4876
|
+
// State Signals
|
|
4877
|
+
this.messages = signal([], ...(ngDevMode ? [{ debugName: "messages" }] : /* istanbul ignore next */ []));
|
|
4878
|
+
this.messageRole = signal(ChatRole.User, ...(ngDevMode ? [{ debugName: "messageRole" }] : /* istanbul ignore next */ []));
|
|
4879
|
+
this.messageToPlay = signal(null, ...(ngDevMode ? [{ debugName: "messageToPlay" }] : /* istanbul ignore next */ []));
|
|
4880
|
+
// Public Signals for components to consume
|
|
4881
|
+
this.messagesSignal = this.messages.asReadonly();
|
|
4882
|
+
this.messageToPlay$ = this.messageToPlay.asReadonly();
|
|
4883
|
+
// Audio queue management
|
|
4884
|
+
this.audioQueue = [];
|
|
4885
|
+
this.isGenerating = signal(false, ...(ngDevMode ? [{ debugName: "isGenerating" }] : /* istanbul ignore next */ []));
|
|
4886
|
+
this.currentPlayingIndex = signal(null, ...(ngDevMode ? [{ debugName: "currentPlayingIndex" }] : /* istanbul ignore next */ []));
|
|
4887
|
+
this.preGenerationInProgress = signal(false, ...(ngDevMode ? [{ debugName: "preGenerationInProgress" }] : /* istanbul ignore next */ []));
|
|
4888
|
+
console.log('MessageOrchestrationService initialized');
|
|
4889
|
+
}
|
|
4890
|
+
/**
|
|
4891
|
+
* @description
|
|
4892
|
+
* Starts the orchestration process for a list of messages.
|
|
4893
|
+
*
|
|
4894
|
+
* @param messages - An array of `MessageContent` objects to be orchestrated.
|
|
4895
|
+
* @param role - The `ChatRole` of the messages' author.
|
|
4896
|
+
*/
|
|
4897
|
+
startOrchestration(messages, role) {
|
|
4898
|
+
untracked(() => {
|
|
4899
|
+
const currentMessages = this.messages();
|
|
4900
|
+
const isAssistant = role === ChatRole.Assistant;
|
|
4901
|
+
// Check if it's a continuation of the same orchestration (e.g., streaming)
|
|
4902
|
+
const isContinuation = currentMessages.length > 0 &&
|
|
4903
|
+
messages.length >= currentMessages.length &&
|
|
4904
|
+
messages[0].messageId === currentMessages[0].messageId;
|
|
4905
|
+
// Update internal state signal with new messages
|
|
4906
|
+
// If it's a continuation, we want to merge properties carefully to avoid flickering or resetting local state like 'playing' status
|
|
4907
|
+
if (isContinuation) {
|
|
4908
|
+
this.messages.update(prev => {
|
|
4909
|
+
return messages.map((newMsg, idx) => {
|
|
4910
|
+
const prevMsg = prev[idx];
|
|
4911
|
+
if (prevMsg) {
|
|
4912
|
+
// Merge properties: keep local transient state like audioStatus if it's currently managed by this service
|
|
4913
|
+
return { ...newMsg, ...prevMsg, ...newMsg }; // prioritize newMsg but keep prevMsg as base if needed
|
|
4914
|
+
}
|
|
4915
|
+
return newMsg;
|
|
4916
|
+
});
|
|
4917
|
+
});
|
|
4740
4918
|
}
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
// if transcriptionTimestamps is calculated then hasTranscription is true.
|
|
4744
|
-
this.hasTranscription = computed(() => {
|
|
4745
|
-
return this.transcriptionTimestamps().length > 0;
|
|
4746
|
-
}, ...(ngDevMode ? [{ debugName: "hasTranscription" }] : /* istanbul ignore next */ []));
|
|
4747
|
-
effect(() => {
|
|
4748
|
-
const currentMsg = this.message();
|
|
4749
|
-
if (!currentMsg)
|
|
4750
|
-
return;
|
|
4751
|
-
const hasTranscription = this.hasTranscription();
|
|
4752
|
-
const idChanged = currentMsg.messageId !== this.currentMessageId;
|
|
4753
|
-
const audioUrlChanged = currentMsg.audioUrl !== this.lastAudioUrl;
|
|
4754
|
-
const transcriptionChanged = hasTranscription !== this.lastHasTranscription;
|
|
4755
|
-
if (idChanged || audioUrlChanged || transcriptionChanged) {
|
|
4756
|
-
this.currentMessageId = currentMsg.messageId;
|
|
4757
|
-
this.lastAudioUrl = currentMsg.audioUrl;
|
|
4758
|
-
this.lastHasTranscription = hasTranscription;
|
|
4759
|
-
this.localAudioStatus.set(null); // Reset local state on message/audio change
|
|
4760
|
-
this.initializeBasedOnMessage(currentMsg);
|
|
4919
|
+
else {
|
|
4920
|
+
this.messages.set([...messages]);
|
|
4761
4921
|
}
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4922
|
+
this.messageRole.set(role);
|
|
4923
|
+
if (isAssistant) {
|
|
4924
|
+
const chatSettings = this.userService.user()?.settings?.conversation;
|
|
4925
|
+
if (chatSettings?.synthVoice) {
|
|
4926
|
+
if (!isContinuation) {
|
|
4927
|
+
console.log('Starting new orchestration');
|
|
4928
|
+
this.initializeAudioQueue();
|
|
4929
|
+
this.processNextInQueue();
|
|
4930
|
+
}
|
|
4931
|
+
else {
|
|
4932
|
+
console.log('Updating existing orchestration queue');
|
|
4933
|
+
// Update the queue with new indices
|
|
4934
|
+
for (let i = currentMessages.length; i < messages.length; i++) {
|
|
4935
|
+
if (messages[i].audioStatus !== 'finished' && !this.audioQueue.includes(i)) {
|
|
4936
|
+
this.audioQueue.push(i);
|
|
4937
|
+
}
|
|
4938
|
+
}
|
|
4939
|
+
this.processNextInQueue();
|
|
4940
|
+
}
|
|
4941
|
+
}
|
|
4766
4942
|
}
|
|
4767
4943
|
});
|
|
4768
|
-
effect(() => {
|
|
4769
|
-
this.wordsWithMetaState = this.wordWithMeta();
|
|
4770
|
-
});
|
|
4771
4944
|
}
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4945
|
+
audioCompleted(message) {
|
|
4946
|
+
const index = this.currentPlayingIndex();
|
|
4947
|
+
if (index !== null) {
|
|
4948
|
+
console.log(`Audio completed for index ${index}, setting status to finished`);
|
|
4949
|
+
this.updateAudioStatus(message, 'finished', index);
|
|
4950
|
+
}
|
|
4951
|
+
else {
|
|
4952
|
+
console.warn('Audio completed but currentPlayingIndex is null');
|
|
4953
|
+
}
|
|
4954
|
+
this.conversationService.currentAudioStatus.set({ message, completed: true });
|
|
4955
|
+
this.currentPlayingIndex.set(null);
|
|
4956
|
+
this.messageToPlay.set(null);
|
|
4957
|
+
if (this.audioQueue.length > 0) {
|
|
4958
|
+
this.processNextInQueue();
|
|
4959
|
+
}
|
|
4777
4960
|
}
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4961
|
+
/**
|
|
4962
|
+
* @description
|
|
4963
|
+
* Updates the audio status of a message.
|
|
4964
|
+
*
|
|
4965
|
+
* @param message - The `MessageContent` object whose status has changed.
|
|
4966
|
+
* @param status - The new `AudioStatus`.
|
|
4967
|
+
* @param index - The index of the message in the orchestration queue.
|
|
4968
|
+
*/
|
|
4969
|
+
updateAudioStatus(message, status, index) {
|
|
4970
|
+
console.log(`Updating audio status for index ${index} to ${status}. MessageId: ${message.messageId}`);
|
|
4971
|
+
if (status === 'playing') {
|
|
4972
|
+
if (message.messageId) {
|
|
4973
|
+
this.messagesStateService.setActiveMessage(message.messageId, index);
|
|
4974
|
+
}
|
|
4975
|
+
// Update local state: set this one as active, others as inactive
|
|
4976
|
+
this.messages.update((messages) => messages.map((m, idx) => ({
|
|
4977
|
+
...m,
|
|
4978
|
+
isActive: idx === index,
|
|
4979
|
+
// Also update the current one's status
|
|
4980
|
+
...(idx === index ? { audioStatus: status } : {}),
|
|
4981
|
+
})));
|
|
4781
4982
|
}
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
this.
|
|
4785
|
-
|
|
4983
|
+
else {
|
|
4984
|
+
// For other statuses (stopped, finished), preserve isActive and update status
|
|
4985
|
+
this.changeStates(index, { ...message, audioStatus: status });
|
|
4986
|
+
}
|
|
4987
|
+
// 4. Update global state
|
|
4988
|
+
if (message.messageId) {
|
|
4989
|
+
this.messagesStateService.updateMultiMessage(message.messageId, index, {
|
|
4990
|
+
audioStatus: status,
|
|
4786
4991
|
});
|
|
4787
|
-
const firstState = this.transcriptionTimestamps()?.map((word, index) => ({
|
|
4788
|
-
word: word.word,
|
|
4789
|
-
index,
|
|
4790
|
-
isHighlighted: false,
|
|
4791
|
-
tag: '',
|
|
4792
|
-
marked: word.word.toLowerCase() == this.markWord()?.toLowerCase(),
|
|
4793
|
-
}));
|
|
4794
|
-
console.log('firstState', firstState);
|
|
4795
|
-
this.wordWithMeta.set(firstState);
|
|
4796
|
-
// Para mostrar las transcripciones iliminadas: el audio tiene que sonar y en la subscripción apareceran los cambios.
|
|
4797
4992
|
}
|
|
4798
|
-
|
|
4799
|
-
|
|
4993
|
+
this.chatMonitorService.logMessageAudioStatus(message, status);
|
|
4994
|
+
}
|
|
4995
|
+
/**
|
|
4996
|
+
* @description
|
|
4997
|
+
* Notifies the `ConversationService` when a word is clicked.
|
|
4998
|
+
*
|
|
4999
|
+
* @param wordData - The `WordData` object containing information about the clicked word.
|
|
5000
|
+
*/
|
|
5001
|
+
wordClicked(wordData) {
|
|
5002
|
+
this.conversationService.notifyWordClicked(wordData);
|
|
5003
|
+
}
|
|
5004
|
+
initializeAudioQueue() {
|
|
5005
|
+
const messages = this.messages();
|
|
5006
|
+
if (messages && messages.length > 0) {
|
|
5007
|
+
this.audioQueue = messages
|
|
5008
|
+
.map((m, index) => ({ status: m.audioStatus, index }))
|
|
5009
|
+
.filter((item) => item.status !== 'finished' && item.status !== 'skip')
|
|
5010
|
+
.map((item) => item.index);
|
|
5011
|
+
console.log('Audio queue initialized:', this.audioQueue);
|
|
4800
5012
|
}
|
|
4801
|
-
// Removal of shouldPlayAudio check - playback now handled by isPendingAudio effect
|
|
4802
5013
|
}
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
audio = new Audio(audioUrl);
|
|
4807
|
-
this.audioElement.set(audio);
|
|
4808
|
-
this.setupAudioEventListeners(audio);
|
|
5014
|
+
async processNextInQueue() {
|
|
5015
|
+
if (this.audioQueue.length === 0 || this.isGenerating() || this.currentPlayingIndex() !== null) {
|
|
5016
|
+
return;
|
|
4809
5017
|
}
|
|
4810
|
-
|
|
4811
|
-
|
|
5018
|
+
const nextIndex = this.audioQueue.shift();
|
|
5019
|
+
this.isGenerating.set(true);
|
|
5020
|
+
try {
|
|
5021
|
+
await this.generateAudioForIndex(nextIndex);
|
|
5022
|
+
}
|
|
5023
|
+
catch (error) {
|
|
5024
|
+
console.error('Error generating audio:', error);
|
|
5025
|
+
}
|
|
5026
|
+
finally {
|
|
5027
|
+
this.isGenerating.set(false);
|
|
4812
5028
|
}
|
|
5029
|
+
this.preGenerateNextIfNeeded();
|
|
4813
5030
|
}
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
.
|
|
4818
|
-
|
|
4819
|
-
|
|
5031
|
+
async preGenerateNextIfNeeded() {
|
|
5032
|
+
if (this.audioQueue.length > 0 && !this.preGenerationInProgress()) {
|
|
5033
|
+
const nextIndex = this.audioQueue[0];
|
|
5034
|
+
const messages = this.messages();
|
|
5035
|
+
const message = messages[nextIndex];
|
|
5036
|
+
// Skip if audio is already generated or generation is not needed
|
|
5037
|
+
if (message.audioUrl || message.isLoading) {
|
|
4820
5038
|
return;
|
|
4821
5039
|
}
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
tag: word.tag,
|
|
4832
|
-
marked: word.word.toLowerCase() == this.markWord()?.toLowerCase(),
|
|
4833
|
-
}));
|
|
4834
|
-
this.wordWithMeta.set(updatedWords);
|
|
5040
|
+
this.preGenerationInProgress.set(true);
|
|
5041
|
+
const loadingMessage = { ...message, isLoading: true };
|
|
5042
|
+
this.changeStates(nextIndex, loadingMessage);
|
|
5043
|
+
try {
|
|
5044
|
+
const messageAudio = await this.generateAudio(message, null);
|
|
5045
|
+
messageAudio.isLoading = false;
|
|
5046
|
+
// Pre-generated audio stays in the background, no status update needed here or set to 'stopped'/'playable'
|
|
5047
|
+
// Actually, if it's pre-rendered it should probably stay as whatever it was but with audioUrl
|
|
5048
|
+
this.changeStates(nextIndex, messageAudio);
|
|
4835
5049
|
}
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4839
|
-
.subscribe(() => {
|
|
4840
|
-
const resetWords = this.wordWithMeta().map((word) => ({ ...word, isHighlighted: false }));
|
|
4841
|
-
this.wordWithMeta.set(resetWords);
|
|
4842
|
-
const currentMsg = this.message();
|
|
4843
|
-
if (currentMsg) {
|
|
4844
|
-
// No direct mutation of input!
|
|
4845
|
-
this.updateAudioStatus('finished');
|
|
4846
|
-
this.audioCompleted.emit(currentMsg);
|
|
5050
|
+
finally {
|
|
5051
|
+
this.preGenerationInProgress.set(false);
|
|
4847
5052
|
}
|
|
5053
|
+
}
|
|
5054
|
+
}
|
|
5055
|
+
async generateAudioForIndex(index) {
|
|
5056
|
+
const messages = this.messages();
|
|
5057
|
+
if (messages[index].audioUrl) {
|
|
5058
|
+
const messageAudio = { ...messages[index], audioStatus: 'pending' };
|
|
5059
|
+
this.changeStates(index, messageAudio);
|
|
5060
|
+
this.chatMonitorService.logMessageAudioWillPlay(messageAudio);
|
|
5061
|
+
this.messageToPlay.set(messageAudio);
|
|
5062
|
+
this.currentPlayingIndex.set(index);
|
|
5063
|
+
return;
|
|
5064
|
+
}
|
|
5065
|
+
const message = { ...messages[index], isLoading: true };
|
|
5066
|
+
this.changeStates(index, message);
|
|
5067
|
+
try {
|
|
5068
|
+
const messageAudio = await this.generateAudio(messages[index], null);
|
|
5069
|
+
messageAudio.isLoading = false;
|
|
5070
|
+
messageAudio.audioStatus = 'pending';
|
|
5071
|
+
this.changeStates(index, messageAudio);
|
|
5072
|
+
this.chatMonitorService.logMessageAudioWillPlay(messageAudio);
|
|
5073
|
+
this.messageToPlay.set(messageAudio);
|
|
5074
|
+
}
|
|
5075
|
+
finally {
|
|
5076
|
+
// Solo limpiamos isLoading sobre el estado ACTUAL del signal (no el snapshot previo al try),
|
|
5077
|
+
// para no pisar el audioStatus:'pending' que acaba de setear el bloque try.
|
|
5078
|
+
this.messages.update(msgs => {
|
|
5079
|
+
const updated = [...msgs];
|
|
5080
|
+
updated[index] = { ...updated[index], isLoading: false };
|
|
5081
|
+
return updated;
|
|
5082
|
+
});
|
|
5083
|
+
}
|
|
5084
|
+
this.currentPlayingIndex.set(index);
|
|
5085
|
+
}
|
|
5086
|
+
changeStates(index, messageAudio) {
|
|
5087
|
+
this.messages.update((messages) => {
|
|
5088
|
+
const newMessages = [...messages];
|
|
5089
|
+
newMessages[index] = { ...messages[index], ...messageAudio };
|
|
5090
|
+
return newMessages;
|
|
4848
5091
|
});
|
|
4849
5092
|
}
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
5093
|
+
async generateAudio(message, overwriteText = null) {
|
|
5094
|
+
if (message.audioUrl) {
|
|
5095
|
+
return message;
|
|
5096
|
+
}
|
|
5097
|
+
try {
|
|
5098
|
+
const text = overwriteText || message.text || message.content;
|
|
5099
|
+
const ttsObject = buildObjectTTSRequest({ ...message, text }, { highlightWords: true });
|
|
5100
|
+
const speechAudio = await this.conversationService.getTTSFile(ttsObject);
|
|
5101
|
+
message = extractAudioAndTranscription(message, speechAudio);
|
|
5102
|
+
return message;
|
|
4859
5103
|
}
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
const audioElement = this.audioElement();
|
|
4863
|
-
if (audioElement) {
|
|
4864
|
-
audioElement.pause();
|
|
4865
|
-
audioElement.src = '';
|
|
4866
|
-
const resetWords = this.wordWithMeta().map((word) => ({ ...word, isHighlighted: false }));
|
|
4867
|
-
this.wordWithMeta.set(resetWords);
|
|
5104
|
+
finally {
|
|
5105
|
+
this.isGenerating.set(false);
|
|
4868
5106
|
}
|
|
4869
5107
|
}
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
5108
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageOrchestrationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
5109
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageOrchestrationService }); }
|
|
5110
|
+
}
|
|
5111
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageOrchestrationService, decorators: [{
|
|
5112
|
+
type: Injectable
|
|
5113
|
+
}], ctorParameters: () => [] });
|
|
5114
|
+
|
|
5115
|
+
// Given a text that can be in markdown but only in asterisk like *italic* or **bold** or ***bold italic***
|
|
5116
|
+
// Example Hola que tal es *Hey What's up* en inglés
|
|
5117
|
+
// i want to extract in array every word like this.
|
|
5118
|
+
// [{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: ''}]
|
|
5119
|
+
function extractTags(text) {
|
|
5120
|
+
const result = [];
|
|
5121
|
+
const tagStack = [];
|
|
5122
|
+
// Regex to match markdown markers (***, **, *) or spaces or sequences of non-space/non-asterisk characters (words with punctuation)
|
|
5123
|
+
const regex = /(\*\*\*|\*\*|\*|\s+|[^\s\*]+)/g;
|
|
5124
|
+
let match;
|
|
5125
|
+
while ((match = regex.exec(text)) !== null) {
|
|
5126
|
+
const token = match[0];
|
|
5127
|
+
if (token.trim() === '') {
|
|
5128
|
+
// Ignore spaces
|
|
5129
|
+
continue;
|
|
5130
|
+
}
|
|
5131
|
+
if (token === '***') {
|
|
5132
|
+
if (tagStack.length > 0 && tagStack[tagStack.length - 1] === 'bold italic') {
|
|
5133
|
+
tagStack.pop(); // Closing bold italic
|
|
4877
5134
|
}
|
|
4878
5135
|
else {
|
|
4879
|
-
|
|
4880
|
-
this.updateAudioStatus('stopped');
|
|
5136
|
+
tagStack.push('bold italic'); // Opening bold italic
|
|
4881
5137
|
}
|
|
4882
5138
|
}
|
|
4883
|
-
else if (
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
this.playAudio.emit(currentMsg);
|
|
5139
|
+
else if (token === '**') {
|
|
5140
|
+
if (tagStack.length > 0 && tagStack[tagStack.length - 1] === 'bold') {
|
|
5141
|
+
tagStack.pop(); // Closing bold
|
|
5142
|
+
}
|
|
5143
|
+
else {
|
|
5144
|
+
tagStack.push('bold'); // Opening bold
|
|
5145
|
+
}
|
|
4891
5146
|
}
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
tag: word.tag,
|
|
4900
|
-
}));
|
|
4901
|
-
this.wordWithMeta.set(plainWords);
|
|
4902
|
-
}
|
|
4903
|
-
onWordClick(wordData) {
|
|
4904
|
-
const currentMsg = this.message();
|
|
4905
|
-
if (!currentMsg)
|
|
4906
|
-
return;
|
|
4907
|
-
const clickedWord = 'isHighlighted' in wordData
|
|
4908
|
-
? { word: wordData.word, index: wordData.index, messageId: currentMsg.messageId }
|
|
4909
|
-
: { ...wordData, messageId: currentMsg.messageId };
|
|
4910
|
-
this.wordClicked.emit(clickedWord);
|
|
4911
|
-
}
|
|
4912
|
-
startAudioPlayback() {
|
|
4913
|
-
const audioElement = this.audioElement();
|
|
4914
|
-
if (!audioElement)
|
|
4915
|
-
return;
|
|
4916
|
-
try {
|
|
4917
|
-
audioElement.play();
|
|
4918
|
-
console.log('Audio playing');
|
|
5147
|
+
else if (token === '*') {
|
|
5148
|
+
if (tagStack.length > 0 && tagStack[tagStack.length - 1] === 'italic') {
|
|
5149
|
+
tagStack.pop(); // Closing italic
|
|
5150
|
+
}
|
|
5151
|
+
else {
|
|
5152
|
+
tagStack.push('italic'); // Opening italic
|
|
5153
|
+
}
|
|
4919
5154
|
}
|
|
4920
|
-
|
|
4921
|
-
|
|
5155
|
+
else {
|
|
5156
|
+
// It's a word (including punctuation)
|
|
5157
|
+
const currentTag = tagStack.length > 0 ? tagStack[tagStack.length - 1] : '';
|
|
5158
|
+
result.push({ word: token, tag: currentTag });
|
|
4922
5159
|
}
|
|
4923
|
-
// Important: we move it to playing status immediately to avoid re-triggering the effect
|
|
4924
|
-
this.updateAudioStatus('playing');
|
|
4925
5160
|
}
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageContentDisplayer, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4930
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MessageContentDisplayer, isStandalone: true, selector: "message-content-displayer", inputs: { message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: true, transformFunction: null }, markWord: { classPropertyName: "markWord", publicName: "markWord", isSignal: true, isRequired: false, transformFunction: null }, highlightColor: { classPropertyName: "highlightColor", publicName: "highlightColor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { playAudio: "playAudio", audioCompleted: "audioCompleted", audioStatusChanged: "audioStatusChanged", wordClicked: "wordClicked" }, host: { properties: { "style.--highlight-bg-color": "this.hostHighlightColor" } }, ngImport: i0, template: "<div class=\"audio-text-sync-container\" [class.is-active]=\"message().isActive\">\n <i (click)=\"onPlayMessage()\" class=\"play-button\">\n @switch (iconState()) { \n @case ('loading') { <i class=\"spin-animation pi pi-spinner-dotted\"></i> } \n @case ('playing') { <i class=\"pi pi-volume-up\"></i> }\n @case ('paused') { <i class=\"pi pi-pause-circle\"></i> } \n @case ('finished') { <i class=\"pi pi-play-circle\" style=\"color: white\"></i> } \n @case ('pending') { <i class=\"pi pi-angle-right\"></i> }\n @case ('playable') { <i class=\"pi pi-play-circle\" style=\"color: white\"></i> } \n }\n </i>\n\n <!-- Display transcription with highlighting -->\n <p style=\"width: 100%\" [class]=\"'text-content ' + classTag()\">\n @for (word of wordWithMeta(); track trackByIndex($index, word)) {\n <span\n [class]=\"word.tag\"\n [class.highlight]=\"word.isHighlighted\"\n [class.marked]=\"word.marked\"\n (click)=\"onWordClick(word)\"\n [attr.id]=\"'word-' + message().messageId + '-' + word.index\"\n >{{ word.word }}\n </span>\n }\n </p>\n</div>\n@if (message().imageUrl) {\n<div class=\"tour-image-container\" style=\"display: flex; justify-content: center\">\n <img [src]=\"message().imageUrl\" alt=\"Tour Image\" style=\"max-width: 220px; height: auto\" />\n</div>\n}\n", styles: [":host{display:block}.audio-text-sync-container{display:flex;align-items:flex-start;gap:2px;align-items:center;transition:background-color .3s ease}.audio-text-sync-container.is-active{background-color:#3cd8ff1a;border-radius:8px;padding-left:4px}.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:var(--highlight-bg-color, rgb(60, 216, 255));border-radius:4px}::ng-deep .marked{position:relative;color:#f8bfbc}::ng-deep .marked:before{content:\"\";z-index:-2;left:-.1em;top:.1em;border-width:2px;border-style:solid;border-color:#f27068;position:absolute;border-right-color:transparent;width:100%;height:1em;transform:rotate(2deg);opacity:.7;border-radius:50%;padding:.1em .25em}::ng-deep .marked:after{content:\"\";z-index:-1;left:-.1em;top:.4em;padding:.1em .25em;border-width:2px;border-style:solid;border-color:#f27068;border-left-color:transparent;border-top-color:transparent;position:absolute;width:100%;height:1em;transform:rotate(-1deg);opacity:.7;border-radius:50%}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.em{font-style:italic;color:var(--chat-message-italic-color, rgb(184, 208, 252))}.strong{font-weight:700;color:inherit}.italic{font-style:italic;color:var(--chat-message-italic-color, rgb(252, 198, 184))}.em_strong{font-weight:700;font-style:italic;color:inherit}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
5161
|
+
// Note: This implementation assumes correctly matched opening and closing tags.
|
|
5162
|
+
// Unclosed tags at the end of the string will be ignored.
|
|
5163
|
+
return result;
|
|
4931
5164
|
}
|
|
4932
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageContentDisplayer, decorators: [{
|
|
4933
|
-
type: Component,
|
|
4934
|
-
args: [{ selector: 'message-content-displayer', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"audio-text-sync-container\" [class.is-active]=\"message().isActive\">\n <i (click)=\"onPlayMessage()\" class=\"play-button\">\n @switch (iconState()) { \n @case ('loading') { <i class=\"spin-animation pi pi-spinner-dotted\"></i> } \n @case ('playing') { <i class=\"pi pi-volume-up\"></i> }\n @case ('paused') { <i class=\"pi pi-pause-circle\"></i> } \n @case ('finished') { <i class=\"pi pi-play-circle\" style=\"color: white\"></i> } \n @case ('pending') { <i class=\"pi pi-angle-right\"></i> }\n @case ('playable') { <i class=\"pi pi-play-circle\" style=\"color: white\"></i> } \n }\n </i>\n\n <!-- Display transcription with highlighting -->\n <p style=\"width: 100%\" [class]=\"'text-content ' + classTag()\">\n @for (word of wordWithMeta(); track trackByIndex($index, word)) {\n <span\n [class]=\"word.tag\"\n [class.highlight]=\"word.isHighlighted\"\n [class.marked]=\"word.marked\"\n (click)=\"onWordClick(word)\"\n [attr.id]=\"'word-' + message().messageId + '-' + word.index\"\n >{{ word.word }}\n </span>\n }\n </p>\n</div>\n@if (message().imageUrl) {\n<div class=\"tour-image-container\" style=\"display: flex; justify-content: center\">\n <img [src]=\"message().imageUrl\" alt=\"Tour Image\" style=\"max-width: 220px; height: auto\" />\n</div>\n}\n", styles: [":host{display:block}.audio-text-sync-container{display:flex;align-items:flex-start;gap:2px;align-items:center;transition:background-color .3s ease}.audio-text-sync-container.is-active{background-color:#3cd8ff1a;border-radius:8px;padding-left:4px}.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:var(--highlight-bg-color, rgb(60, 216, 255));border-radius:4px}::ng-deep .marked{position:relative;color:#f8bfbc}::ng-deep .marked:before{content:\"\";z-index:-2;left:-.1em;top:.1em;border-width:2px;border-style:solid;border-color:#f27068;position:absolute;border-right-color:transparent;width:100%;height:1em;transform:rotate(2deg);opacity:.7;border-radius:50%;padding:.1em .25em}::ng-deep .marked:after{content:\"\";z-index:-1;left:-.1em;top:.4em;padding:.1em .25em;border-width:2px;border-style:solid;border-color:#f27068;border-left-color:transparent;border-top-color:transparent;position:absolute;width:100%;height:1em;transform:rotate(-1deg);opacity:.7;border-radius:50%}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.em{font-style:italic;color:var(--chat-message-italic-color, rgb(184, 208, 252))}.strong{font-weight:700;color:inherit}.italic{font-style:italic;color:var(--chat-message-italic-color, rgb(252, 198, 184))}.em_strong{font-weight:700;font-style:italic;color:inherit}\n"] }]
|
|
4935
|
-
}], ctorParameters: () => [], propDecorators: { message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: true }] }], markWord: [{ type: i0.Input, args: [{ isSignal: true, alias: "markWord", required: false }] }], highlightColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlightColor", required: false }] }], hostHighlightColor: [{
|
|
4936
|
-
type: HostBinding,
|
|
4937
|
-
args: ['style.--highlight-bg-color']
|
|
4938
|
-
}], playAudio: [{ type: i0.Output, args: ["playAudio"] }], audioCompleted: [{ type: i0.Output, args: ["audioCompleted"] }], audioStatusChanged: [{ type: i0.Output, args: ["audioStatusChanged"] }], wordClicked: [{ type: i0.Output, args: ["wordClicked"] }] } });
|
|
4939
5165
|
|
|
4940
5166
|
/**
|
|
4941
|
-
* @
|
|
5167
|
+
* @Component
|
|
4942
5168
|
*
|
|
4943
5169
|
* @description
|
|
4944
|
-
*
|
|
4945
|
-
* It manages a queue of messages and handles the generation and playback of audio for each message.
|
|
5170
|
+
* Variante "atada al chat" de `MessageContentDisplayer`.
|
|
4946
5171
|
*
|
|
4947
|
-
*
|
|
5172
|
+
* A diferencia del `MessageContentDisplayer` (que es agnóstico y se comunica
|
|
5173
|
+
* exclusivamente mediante `@Output()` para poder reutilizarse fuera del chat),
|
|
5174
|
+
* este componente **inyecta directamente** el `MessageOrchestrationService`
|
|
5175
|
+
* (instancia local provista por `ChatMessageOrchestratorComponent`) y le reporta
|
|
5176
|
+
* los cambios de estado de audio sin emitir eventos.
|
|
5177
|
+
*
|
|
5178
|
+
* Punto clave del refactor:
|
|
5179
|
+
* - NO mantiene un `localAudioStatus`. El `iconState` deriva ÚNICAMENTE del
|
|
5180
|
+
* input `message()`, que a su vez baja del signal del orquestador. Esto elimina
|
|
5181
|
+
* el bug donde el icono/estado de una burbuja anterior se quedaba en "playing"
|
|
5182
|
+
* al activar otro mensaje, porque el estado local divergía del global.
|
|
5183
|
+
*
|
|
5184
|
+
* @see MessageOrchestrationService
|
|
5185
|
+
* @see MessageContentDisplayer (versión desacoplada / reutilizable)
|
|
4948
5186
|
*/
|
|
4949
|
-
class
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
this.conversationService = inject(ConversationService);
|
|
4953
|
-
this.chatMonitorService = inject(ChatMonitorService);
|
|
4954
|
-
this.userService = inject(UserService);
|
|
4955
|
-
this.messagesStateService = inject(MessagesStateService);
|
|
4956
|
-
// State Signals
|
|
4957
|
-
this.messages = signal([], ...(ngDevMode ? [{ debugName: "messages" }] : /* istanbul ignore next */ []));
|
|
4958
|
-
this.messageRole = signal(ChatRole.User, ...(ngDevMode ? [{ debugName: "messageRole" }] : /* istanbul ignore next */ []));
|
|
4959
|
-
this.messageToPlay = signal(null, ...(ngDevMode ? [{ debugName: "messageToPlay" }] : /* istanbul ignore next */ []));
|
|
4960
|
-
// Public Signals for components to consume
|
|
4961
|
-
this.messagesSignal = this.messages.asReadonly();
|
|
4962
|
-
this.messageToPlay$ = this.messageToPlay.asReadonly();
|
|
4963
|
-
// Audio queue management
|
|
4964
|
-
this.audioQueue = [];
|
|
4965
|
-
this.isGenerating = signal(false, ...(ngDevMode ? [{ debugName: "isGenerating" }] : /* istanbul ignore next */ []));
|
|
4966
|
-
this.currentPlayingIndex = signal(null, ...(ngDevMode ? [{ debugName: "currentPlayingIndex" }] : /* istanbul ignore next */ []));
|
|
4967
|
-
this.preGenerationInProgress = signal(false, ...(ngDevMode ? [{ debugName: "preGenerationInProgress" }] : /* istanbul ignore next */ []));
|
|
4968
|
-
console.log('MessageOrchestrationService initialized');
|
|
5187
|
+
class MessageContentReactive {
|
|
5188
|
+
get hostHighlightColor() {
|
|
5189
|
+
return this.highlightColor();
|
|
4969
5190
|
}
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
}
|
|
4995
|
-
return newMsg;
|
|
4996
|
-
});
|
|
4997
|
-
});
|
|
5191
|
+
constructor() {
|
|
5192
|
+
// Inputs
|
|
5193
|
+
this.message = input.required(...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
|
|
5194
|
+
/** Índice del fragmento dentro del array de multiMessages del orquestador. */
|
|
5195
|
+
this.index = input.required(...(ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []));
|
|
5196
|
+
this.markWord = input(...(ngDevMode ? [undefined, { debugName: "markWord" }] : /* istanbul ignore next */ []));
|
|
5197
|
+
this.highlightColor = input(...(ngDevMode ? [undefined, { debugName: "highlightColor" }] : /* istanbul ignore next */ []));
|
|
5198
|
+
// Services (atados al chat — fuente de verdad)
|
|
5199
|
+
this.orchestrationService = inject(MessageOrchestrationService);
|
|
5200
|
+
this.audioTextSyncService = inject(AudioTextSyncService);
|
|
5201
|
+
this.destroyRef = inject(DestroyRef);
|
|
5202
|
+
// Signal States (solo presentación de palabras / karaoke)
|
|
5203
|
+
this.wordWithMeta = signal([], ...(ngDevMode ? [{ debugName: "wordWithMeta" }] : /* istanbul ignore next */ []));
|
|
5204
|
+
this.wordsWithMetaState = [];
|
|
5205
|
+
this.lastHasTranscription = false;
|
|
5206
|
+
this.audioElement = signal(null, ...(ngDevMode ? [{ debugName: "audioElement" }] : /* istanbul ignore next */ []));
|
|
5207
|
+
/**
|
|
5208
|
+
* Estado del icono derivado ÚNICAMENTE del input `message()`.
|
|
5209
|
+
* No hay estado local: el orquestador es la única fuente de verdad.
|
|
5210
|
+
*/
|
|
5211
|
+
this.iconState = computed(() => {
|
|
5212
|
+
const msg = this.message();
|
|
5213
|
+
if (msg?.isLoading) {
|
|
5214
|
+
return 'loading';
|
|
4998
5215
|
}
|
|
4999
|
-
|
|
5000
|
-
|
|
5216
|
+
const status = msg?.audioStatus;
|
|
5217
|
+
if (status === 'playing') {
|
|
5218
|
+
return 'playing';
|
|
5001
5219
|
}
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5220
|
+
if (status === 'stopped') {
|
|
5221
|
+
return 'paused';
|
|
5222
|
+
}
|
|
5223
|
+
if (status === 'finished') {
|
|
5224
|
+
return 'finished';
|
|
5225
|
+
}
|
|
5226
|
+
if (status === 'pending') {
|
|
5227
|
+
return 'pending';
|
|
5228
|
+
}
|
|
5229
|
+
if (msg?.audioUrl) {
|
|
5230
|
+
return 'playable';
|
|
5231
|
+
}
|
|
5232
|
+
return 'idle';
|
|
5233
|
+
}, ...(ngDevMode ? [{ debugName: "iconState" }] : /* istanbul ignore next */ []));
|
|
5234
|
+
this.isPendingAudio = computed(() => this.message()?.audioStatus === 'pending', ...(ngDevMode ? [{ debugName: "isPendingAudio" }] : /* istanbul ignore next */ []));
|
|
5235
|
+
this.messageText = computed(() => {
|
|
5236
|
+
const msg = this.message();
|
|
5237
|
+
return msg?.text || msg?.content || 'N/A';
|
|
5238
|
+
}, ...(ngDevMode ? [{ debugName: "messageText" }] : /* istanbul ignore next */ []));
|
|
5239
|
+
this.classTag = computed(() => this.message()?.tag, ...(ngDevMode ? [{ debugName: "classTag" }] : /* istanbul ignore next */ []));
|
|
5240
|
+
this.transcriptionTimestamps = computed(() => {
|
|
5241
|
+
const msg = this.message();
|
|
5242
|
+
if (!msg || !msg.transcription) {
|
|
5243
|
+
return [];
|
|
5244
|
+
}
|
|
5245
|
+
return matchTranscription(msg.text || msg.content, msg.transcription.words);
|
|
5246
|
+
}, ...(ngDevMode ? [{ debugName: "transcriptionTimestamps" }] : /* istanbul ignore next */ []));
|
|
5247
|
+
this.hasTranscription = computed(() => this.transcriptionTimestamps().length > 0, ...(ngDevMode ? [{ debugName: "hasTranscription" }] : /* istanbul ignore next */ []));
|
|
5248
|
+
// Re-inicializa audio/palabras cuando cambia el mensaje, su audio o su transcripción.
|
|
5249
|
+
effect(() => {
|
|
5250
|
+
const currentMsg = this.message();
|
|
5251
|
+
if (!currentMsg)
|
|
5252
|
+
return;
|
|
5253
|
+
const hasTranscription = this.hasTranscription();
|
|
5254
|
+
const idChanged = currentMsg.messageId !== this.currentMessageId;
|
|
5255
|
+
const audioUrlChanged = currentMsg.audioUrl !== this.lastAudioUrl;
|
|
5256
|
+
const transcriptionChanged = hasTranscription !== this.lastHasTranscription;
|
|
5257
|
+
if (idChanged || audioUrlChanged || transcriptionChanged) {
|
|
5258
|
+
this.currentMessageId = currentMsg.messageId;
|
|
5259
|
+
this.lastAudioUrl = currentMsg.audioUrl;
|
|
5260
|
+
this.lastHasTranscription = hasTranscription;
|
|
5261
|
+
this.initializeBasedOnMessage(currentMsg);
|
|
5262
|
+
}
|
|
5263
|
+
});
|
|
5264
|
+
// Cuando el orquestador marca este fragmento como 'pending', se reproduce.
|
|
5265
|
+
effect(() => {
|
|
5266
|
+
if (this.isPendingAudio()) {
|
|
5267
|
+
this.startAudioPlayback();
|
|
5022
5268
|
}
|
|
5023
5269
|
});
|
|
5270
|
+
effect(() => {
|
|
5271
|
+
this.wordsWithMetaState = this.wordWithMeta();
|
|
5272
|
+
});
|
|
5024
5273
|
}
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
if (index !== null) {
|
|
5028
|
-
console.log(`Audio completed for index ${index}, setting status to finished`);
|
|
5029
|
-
this.updateAudioStatus(message, 'finished', index);
|
|
5030
|
-
}
|
|
5031
|
-
else {
|
|
5032
|
-
console.warn('Audio completed but currentPlayingIndex is null');
|
|
5033
|
-
}
|
|
5034
|
-
this.conversationService.currentAudioStatus.set({ message, completed: true });
|
|
5035
|
-
this.currentPlayingIndex.set(null);
|
|
5036
|
-
this.messageToPlay.set(null);
|
|
5037
|
-
if (this.audioQueue.length > 0) {
|
|
5038
|
-
this.processNextInQueue();
|
|
5039
|
-
}
|
|
5274
|
+
trackByIndex(index, item) {
|
|
5275
|
+
return index;
|
|
5040
5276
|
}
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
this.messagesStateService.setActiveMessage(message.messageId, index);
|
|
5277
|
+
ngOnDestroy() {
|
|
5278
|
+
this.cleanupAudio();
|
|
5279
|
+
}
|
|
5280
|
+
onPlayMessage() {
|
|
5281
|
+
const currentMsg = this.message();
|
|
5282
|
+
const audioElement = this.audioElement();
|
|
5283
|
+
if (audioElement) {
|
|
5284
|
+
if (audioElement.paused) {
|
|
5285
|
+
this.startAudioPlayback();
|
|
5286
|
+
}
|
|
5287
|
+
else {
|
|
5288
|
+
this.audioTextSyncService.pauseAudio(audioElement);
|
|
5289
|
+
this.reportStatus('stopped');
|
|
5055
5290
|
}
|
|
5056
|
-
// Update local state: set this one as active, others as inactive
|
|
5057
|
-
this.messages.update((messages) => messages.map((m, idx) => ({
|
|
5058
|
-
...m,
|
|
5059
|
-
isActive: idx === index,
|
|
5060
|
-
// Also update the current one's status
|
|
5061
|
-
...(idx === index ? { audioStatus: status } : {}),
|
|
5062
|
-
})));
|
|
5063
|
-
}
|
|
5064
|
-
else {
|
|
5065
|
-
// For other statuses (stopped, finished), preserve isActive and update status
|
|
5066
|
-
this.changeStates(index, { ...message, audioStatus: status });
|
|
5067
5291
|
}
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
this.
|
|
5071
|
-
audioStatus: status,
|
|
5072
|
-
});
|
|
5292
|
+
else if (currentMsg?.audioUrl) {
|
|
5293
|
+
this.initializeAudio(currentMsg.audioUrl);
|
|
5294
|
+
this.startAudioPlayback();
|
|
5073
5295
|
}
|
|
5296
|
+
// Si no hay audioUrl, el orquestador lo generará vía la cola (status 'pending').
|
|
5297
|
+
}
|
|
5298
|
+
onWordClick(wordData) {
|
|
5299
|
+
const currentMsg = this.message();
|
|
5300
|
+
if (!currentMsg)
|
|
5301
|
+
return;
|
|
5302
|
+
this.orchestrationService.wordClicked({
|
|
5303
|
+
word: wordData.word,
|
|
5304
|
+
index: wordData.index,
|
|
5305
|
+
messageId: currentMsg.messageId,
|
|
5306
|
+
});
|
|
5074
5307
|
}
|
|
5308
|
+
// --- Reporte de estado directo al orquestador (sin @Output) ---
|
|
5075
5309
|
/**
|
|
5076
|
-
*
|
|
5077
|
-
*
|
|
5078
|
-
*
|
|
5079
|
-
* @param wordData - The `WordData` object containing information about the clicked word.
|
|
5310
|
+
* Reporta el nuevo estado de audio al orquestador, que es la fuente de verdad.
|
|
5311
|
+
* Guarda contra reportes redundantes (el evento `timeupdate` se dispara en cada tick).
|
|
5080
5312
|
*/
|
|
5081
|
-
|
|
5082
|
-
this.
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
const messages = this.messages();
|
|
5086
|
-
if (messages && messages.length > 0) {
|
|
5087
|
-
this.audioQueue = messages
|
|
5088
|
-
.map((m, index) => ({ status: m.audioStatus, index }))
|
|
5089
|
-
.filter((item) => item.status !== 'finished' && item.status !== 'skip')
|
|
5090
|
-
.map((item) => item.index);
|
|
5091
|
-
console.log('Audio queue initialized:', this.audioQueue);
|
|
5313
|
+
reportStatus(status) {
|
|
5314
|
+
const currentMsg = this.message();
|
|
5315
|
+
if (currentMsg && currentMsg.audioStatus !== status) {
|
|
5316
|
+
this.orchestrationService.updateAudioStatus(currentMsg, status, this.index());
|
|
5092
5317
|
}
|
|
5093
5318
|
}
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5319
|
+
// --- Audio lifecycle ---
|
|
5320
|
+
initializeBasedOnMessage(msg) {
|
|
5321
|
+
if (msg.audioUrl) {
|
|
5322
|
+
this.initializeAudio(msg.audioUrl);
|
|
5097
5323
|
}
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5324
|
+
if (this.hasTranscription()) {
|
|
5325
|
+
const firstState = this.transcriptionTimestamps()?.map((word, index) => ({
|
|
5326
|
+
word: word.word,
|
|
5327
|
+
index,
|
|
5328
|
+
isHighlighted: false,
|
|
5329
|
+
tag: '',
|
|
5330
|
+
marked: word.word.toLowerCase() == this.markWord()?.toLowerCase(),
|
|
5331
|
+
}));
|
|
5332
|
+
this.wordWithMeta.set(firstState);
|
|
5102
5333
|
}
|
|
5103
|
-
|
|
5104
|
-
|
|
5334
|
+
else {
|
|
5335
|
+
this.initializePlainTextWords(msg.text || msg.content || '');
|
|
5105
5336
|
}
|
|
5106
|
-
|
|
5107
|
-
|
|
5337
|
+
}
|
|
5338
|
+
initializeAudio(audioUrl) {
|
|
5339
|
+
let audio = this.audioElement();
|
|
5340
|
+
if (!audio) {
|
|
5341
|
+
audio = new Audio(audioUrl);
|
|
5342
|
+
this.audioElement.set(audio);
|
|
5343
|
+
this.setupAudioEventListeners(audio);
|
|
5344
|
+
}
|
|
5345
|
+
else if (audio.src && audioUrl && !audio.src.endsWith(audioUrl)) {
|
|
5346
|
+
audio.src = audioUrl;
|
|
5108
5347
|
}
|
|
5109
|
-
this.preGenerateNextIfNeeded();
|
|
5110
5348
|
}
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5349
|
+
setupAudioEventListeners(audioElement) {
|
|
5350
|
+
fromEvent(audioElement, 'timeupdate')
|
|
5351
|
+
.pipe(takeUntilDestroyed(this.destroyRef), map(() => audioElement.currentTime || 0))
|
|
5352
|
+
.subscribe((currentTime) => {
|
|
5353
|
+
if (this.hasTranscription()) {
|
|
5354
|
+
const wordsAndTimestamps = [];
|
|
5355
|
+
this.transcriptionTimestamps()?.forEach((word, index) => {
|
|
5356
|
+
wordsAndTimestamps.push({ ...word, ...this.wordsWithMetaState[index], index });
|
|
5357
|
+
});
|
|
5358
|
+
const updatedWords = wordsAndTimestamps.map((word, index) => ({
|
|
5359
|
+
word: word.word,
|
|
5360
|
+
index,
|
|
5361
|
+
isHighlighted: currentTime >= word.start - 0.15 && currentTime < word.end + 0.15,
|
|
5362
|
+
tag: word.tag,
|
|
5363
|
+
marked: word.word.toLowerCase() == this.markWord()?.toLowerCase(),
|
|
5364
|
+
}));
|
|
5365
|
+
this.wordWithMeta.set(updatedWords);
|
|
5119
5366
|
}
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
this.
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5367
|
+
});
|
|
5368
|
+
fromEvent(audioElement, 'play')
|
|
5369
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
5370
|
+
.subscribe(() => {
|
|
5371
|
+
this.reportStatus('playing');
|
|
5372
|
+
});
|
|
5373
|
+
fromEvent(audioElement, 'pause')
|
|
5374
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
5375
|
+
.subscribe(() => {
|
|
5376
|
+
const resetWords = this.wordWithMeta().map((word) => ({ ...word, isHighlighted: false }));
|
|
5377
|
+
this.wordWithMeta.set(resetWords);
|
|
5378
|
+
if (audioElement.currentTime !== audioElement.duration) {
|
|
5379
|
+
this.reportStatus('stopped');
|
|
5129
5380
|
}
|
|
5130
|
-
|
|
5131
|
-
|
|
5381
|
+
});
|
|
5382
|
+
fromEvent(audioElement, 'ended')
|
|
5383
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
5384
|
+
.subscribe(() => {
|
|
5385
|
+
const resetWords = this.wordWithMeta().map((word) => ({ ...word, isHighlighted: false }));
|
|
5386
|
+
this.wordWithMeta.set(resetWords);
|
|
5387
|
+
const currentMsg = this.message();
|
|
5388
|
+
if (currentMsg) {
|
|
5389
|
+
// El servicio marca 'finished' y avanza la cola en un solo paso.
|
|
5390
|
+
this.orchestrationService.audioCompleted(currentMsg);
|
|
5132
5391
|
}
|
|
5133
|
-
}
|
|
5392
|
+
});
|
|
5134
5393
|
}
|
|
5135
|
-
|
|
5136
|
-
const
|
|
5137
|
-
if (
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
this.messageToPlay.set(messageAudio);
|
|
5142
|
-
this.currentPlayingIndex.set(index);
|
|
5394
|
+
startAudioPlayback() {
|
|
5395
|
+
const audioElement = this.audioElement();
|
|
5396
|
+
if (!audioElement)
|
|
5397
|
+
return;
|
|
5398
|
+
const currentMsg = this.message();
|
|
5399
|
+
if (!currentMsg)
|
|
5143
5400
|
return;
|
|
5144
|
-
}
|
|
5145
|
-
const message = { ...messages[index], isLoading: true };
|
|
5146
|
-
this.changeStates(index, message);
|
|
5147
5401
|
try {
|
|
5148
|
-
|
|
5149
|
-
messageAudio.isLoading = false;
|
|
5150
|
-
messageAudio.audioStatus = 'pending';
|
|
5151
|
-
this.changeStates(index, messageAudio);
|
|
5152
|
-
this.chatMonitorService.logMessageAudioWillPlay(messageAudio);
|
|
5153
|
-
this.messageToPlay.set(messageAudio);
|
|
5402
|
+
this.audioTextSyncService.playAudio(audioElement, currentMsg);
|
|
5154
5403
|
}
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
this.changeStates(index, message);
|
|
5404
|
+
catch (error) {
|
|
5405
|
+
console.error('Error playing audio:', error);
|
|
5158
5406
|
}
|
|
5159
|
-
this.
|
|
5160
|
-
}
|
|
5161
|
-
changeStates(index, messageAudio) {
|
|
5162
|
-
this.messages.update((messages) => {
|
|
5163
|
-
const newMessages = [...messages];
|
|
5164
|
-
newMessages[index] = { ...messages[index], ...messageAudio };
|
|
5165
|
-
return newMessages;
|
|
5166
|
-
});
|
|
5407
|
+
this.reportStatus('playing');
|
|
5167
5408
|
}
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
const
|
|
5174
|
-
|
|
5175
|
-
const speechAudio = await this.conversationService.getTTSFile(ttsObject);
|
|
5176
|
-
message = extractAudioAndTranscription(message, speechAudio);
|
|
5177
|
-
return message;
|
|
5178
|
-
}
|
|
5179
|
-
finally {
|
|
5180
|
-
this.isGenerating.set(false);
|
|
5409
|
+
cleanupAudio() {
|
|
5410
|
+
const audioElement = this.audioElement();
|
|
5411
|
+
if (audioElement) {
|
|
5412
|
+
this.audioTextSyncService.pauseAudio(audioElement);
|
|
5413
|
+
audioElement.src = '';
|
|
5414
|
+
const resetWords = this.wordWithMeta().map((word) => ({ ...word, isHighlighted: false }));
|
|
5415
|
+
this.wordWithMeta.set(resetWords);
|
|
5181
5416
|
}
|
|
5182
5417
|
}
|
|
5183
|
-
|
|
5184
|
-
|
|
5418
|
+
initializePlainTextWords(text) {
|
|
5419
|
+
const words = extractTags(text);
|
|
5420
|
+
const plainWords = words.map((word, index) => ({
|
|
5421
|
+
word: word.word,
|
|
5422
|
+
index: index,
|
|
5423
|
+
isHighlighted: false,
|
|
5424
|
+
tag: word.tag,
|
|
5425
|
+
}));
|
|
5426
|
+
this.wordWithMeta.set(plainWords);
|
|
5427
|
+
}
|
|
5428
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageContentReactive, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
5429
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MessageContentReactive, isStandalone: true, selector: "message-content-reactive", inputs: { message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: true, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: true, transformFunction: null }, markWord: { classPropertyName: "markWord", publicName: "markWord", isSignal: true, isRequired: false, transformFunction: null }, highlightColor: { classPropertyName: "highlightColor", publicName: "highlightColor", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.--highlight-bg-color": "this.hostHighlightColor" } }, ngImport: i0, template: "<div class=\"audio-text-sync-container\" [class.is-active]=\"message().isActive\">\n <i (click)=\"onPlayMessage()\" class=\"play-button\">\n @switch (iconState()) {\n @case ('loading') { <i class=\"spin-animation pi pi-spinner-dotted\"></i> }\n @case ('playing') { <i class=\"pi pi-volume-up\"></i> }\n @case ('paused') { <i class=\"pi pi-pause-circle\"></i> }\n @case ('finished') { <i class=\"pi pi-play-circle\" style=\"color: white\"></i> }\n @case ('pending') { <i class=\"pi pi-angle-right\"></i> }\n @case ('playable') { <i class=\"pi pi-play-circle\" style=\"color: white\"></i> }\n }\n </i>\n\n <!-- Display transcription with highlighting -->\n <p style=\"width: 100%\" [class]=\"'text-content ' + classTag()\">\n @for (word of wordWithMeta(); track trackByIndex($index, word)) {\n <span\n [class]=\"word.tag\"\n [class.highlight]=\"word.isHighlighted\"\n [class.marked]=\"word.marked\"\n (click)=\"onWordClick(word)\"\n [attr.id]=\"'word-' + message().messageId + '-' + word.index\"\n >{{ word.word }}\n </span>\n }\n </p>\n</div>\n@if (message().imageUrl) {\n<div class=\"tour-image-container\" style=\"display: flex; justify-content: center\">\n <img [src]=\"message().imageUrl\" alt=\"Tour Image\" style=\"max-width: 220px; height: auto\" />\n</div>\n}\n", styles: [":host{display:block}.audio-text-sync-container{display:flex;align-items:flex-start;gap:2px;align-items:center;transition:background-color .3s ease}.audio-text-sync-container.is-active{background-color:#3cd8ff1a;border-radius:8px;padding-left:4px}.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:var(--highlight-bg-color, rgb(60, 216, 255));border-radius:4px}::ng-deep .marked{position:relative;color:#f8bfbc}::ng-deep .marked:before{content:\"\";z-index:-2;left:-.1em;top:.1em;border-width:2px;border-style:solid;border-color:#f27068;position:absolute;border-right-color:transparent;width:100%;height:1em;transform:rotate(2deg);opacity:.7;border-radius:50%;padding:.1em .25em}::ng-deep .marked:after{content:\"\";z-index:-1;left:-.1em;top:.4em;padding:.1em .25em;border-width:2px;border-style:solid;border-color:#f27068;border-left-color:transparent;border-top-color:transparent;position:absolute;width:100%;height:1em;transform:rotate(-1deg);opacity:.7;border-radius:50%}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.em{font-style:italic;color:var(--chat-message-italic-color, rgb(184, 208, 252))}.strong{font-weight:700;color:inherit}.italic{font-style:italic;color:var(--chat-message-italic-color, rgb(252, 198, 184))}.em_strong{font-weight:700;font-style:italic;color:inherit}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
5185
5430
|
}
|
|
5186
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type:
|
|
5187
|
-
type:
|
|
5188
|
-
|
|
5431
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageContentReactive, decorators: [{
|
|
5432
|
+
type: Component,
|
|
5433
|
+
args: [{ selector: 'message-content-reactive', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"audio-text-sync-container\" [class.is-active]=\"message().isActive\">\n <i (click)=\"onPlayMessage()\" class=\"play-button\">\n @switch (iconState()) {\n @case ('loading') { <i class=\"spin-animation pi pi-spinner-dotted\"></i> }\n @case ('playing') { <i class=\"pi pi-volume-up\"></i> }\n @case ('paused') { <i class=\"pi pi-pause-circle\"></i> }\n @case ('finished') { <i class=\"pi pi-play-circle\" style=\"color: white\"></i> }\n @case ('pending') { <i class=\"pi pi-angle-right\"></i> }\n @case ('playable') { <i class=\"pi pi-play-circle\" style=\"color: white\"></i> }\n }\n </i>\n\n <!-- Display transcription with highlighting -->\n <p style=\"width: 100%\" [class]=\"'text-content ' + classTag()\">\n @for (word of wordWithMeta(); track trackByIndex($index, word)) {\n <span\n [class]=\"word.tag\"\n [class.highlight]=\"word.isHighlighted\"\n [class.marked]=\"word.marked\"\n (click)=\"onWordClick(word)\"\n [attr.id]=\"'word-' + message().messageId + '-' + word.index\"\n >{{ word.word }}\n </span>\n }\n </p>\n</div>\n@if (message().imageUrl) {\n<div class=\"tour-image-container\" style=\"display: flex; justify-content: center\">\n <img [src]=\"message().imageUrl\" alt=\"Tour Image\" style=\"max-width: 220px; height: auto\" />\n</div>\n}\n", styles: [":host{display:block}.audio-text-sync-container{display:flex;align-items:flex-start;gap:2px;align-items:center;transition:background-color .3s ease}.audio-text-sync-container.is-active{background-color:#3cd8ff1a;border-radius:8px;padding-left:4px}.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:var(--highlight-bg-color, rgb(60, 216, 255));border-radius:4px}::ng-deep .marked{position:relative;color:#f8bfbc}::ng-deep .marked:before{content:\"\";z-index:-2;left:-.1em;top:.1em;border-width:2px;border-style:solid;border-color:#f27068;position:absolute;border-right-color:transparent;width:100%;height:1em;transform:rotate(2deg);opacity:.7;border-radius:50%;padding:.1em .25em}::ng-deep .marked:after{content:\"\";z-index:-1;left:-.1em;top:.4em;padding:.1em .25em;border-width:2px;border-style:solid;border-color:#f27068;border-left-color:transparent;border-top-color:transparent;position:absolute;width:100%;height:1em;transform:rotate(-1deg);opacity:.7;border-radius:50%}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.em{font-style:italic;color:var(--chat-message-italic-color, rgb(184, 208, 252))}.strong{font-weight:700;color:inherit}.italic{font-style:italic;color:var(--chat-message-italic-color, rgb(252, 198, 184))}.em_strong{font-weight:700;font-style:italic;color:inherit}\n"] }]
|
|
5434
|
+
}], ctorParameters: () => [], propDecorators: { message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: true }] }], index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: true }] }], markWord: [{ type: i0.Input, args: [{ isSignal: true, alias: "markWord", required: false }] }], highlightColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlightColor", required: false }] }], hostHighlightColor: [{
|
|
5435
|
+
type: HostBinding,
|
|
5436
|
+
args: ['style.--highlight-bg-color']
|
|
5437
|
+
}] } });
|
|
5189
5438
|
|
|
5190
5439
|
/**
|
|
5191
5440
|
* @Component
|
|
@@ -5194,8 +5443,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
5194
5443
|
* This component orchestrates the display and audio playback of chat messages.
|
|
5195
5444
|
* It uses the `MessageOrchestrationService` to manage the message queue and audio playback.
|
|
5196
5445
|
*
|
|
5446
|
+
* Children (`MessageContentReactive`) inject the same `MessageOrchestrationService`
|
|
5447
|
+
* instance directly and report their state to it, so this component no longer needs
|
|
5448
|
+
* to relay per-segment audio events via `@Output()`.
|
|
5449
|
+
*
|
|
5197
5450
|
* @see MessageOrchestrationService
|
|
5198
|
-
* @see
|
|
5451
|
+
* @see MessageContentReactive
|
|
5199
5452
|
*/
|
|
5200
5453
|
class ChatMessageOrchestratorComponent {
|
|
5201
5454
|
constructor() {
|
|
@@ -5221,41 +5474,12 @@ class ChatMessageOrchestratorComponent {
|
|
|
5221
5474
|
this.orchestrationService.startOrchestration(this.messages(), this.messageRole());
|
|
5222
5475
|
});
|
|
5223
5476
|
}
|
|
5224
|
-
/**
|
|
5225
|
-
* @description
|
|
5226
|
-
* Handles the `audioCompleted` event from the `MessageContentDisplayer`.
|
|
5227
|
-
*
|
|
5228
|
-
* @param message - The `MessageContent` object whose audio has finished playing.
|
|
5229
|
-
*/
|
|
5230
|
-
onAudioCompleted(message) {
|
|
5231
|
-
this.orchestrationService.audioCompleted(message);
|
|
5232
|
-
this.audioCompleted.emit(message);
|
|
5233
|
-
}
|
|
5234
|
-
/**
|
|
5235
|
-
* @description
|
|
5236
|
-
* Handles the `audioStatusChanged` event from the `MessageContentDisplayer`.
|
|
5237
|
-
*
|
|
5238
|
-
* @param event - The event object containing the `MessageContent` and its new `AudioStatus`.
|
|
5239
|
-
* @param index - The index of the message in the array.
|
|
5240
|
-
*/
|
|
5241
|
-
onAudioStatusChanged(event, index) {
|
|
5242
|
-
this.orchestrationService.updateAudioStatus(event.message, event.status, index);
|
|
5243
|
-
}
|
|
5244
|
-
/**
|
|
5245
|
-
* @description
|
|
5246
|
-
* Handles the `wordClicked` event from the `MessageContentDisplayer`.
|
|
5247
|
-
*
|
|
5248
|
-
* @param wordData - The `WordData` object containing information about the clicked word.
|
|
5249
|
-
*/
|
|
5250
|
-
onWordClicked(wordData) {
|
|
5251
|
-
this.orchestrationService.wordClicked(wordData);
|
|
5252
|
-
}
|
|
5253
5477
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMessageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
5254
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ChatMessageOrchestratorComponent, 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: { audioCompleted: "audioCompleted" }, providers: [MessageOrchestrationService], ngImport: i0, template: "<div class=\"message-orchestrator-container\">\n @for (message of messagesSignal(); track $index) {\n <message-content-
|
|
5478
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ChatMessageOrchestratorComponent, 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: { audioCompleted: "audioCompleted" }, providers: [MessageOrchestrationService], ngImport: i0, template: "<div class=\"message-orchestrator-container\">\n @for (message of messagesSignal(); track $index) {\n <message-content-reactive [message]=\"message\" [index]=\"$index\" />\n }\n</div>\n", styles: [":host{display:block}.word-options-popup{position:absolute;border:1px solid #ccc;background:#fff;padding:5px;z-index:1000}message-content-displayer{text-shadow:1px 1px 4px rgba(255,255,255,.4)}\n"], dependencies: [{ kind: "component", type: MessageContentReactive, selector: "message-content-reactive", inputs: ["message", "index", "markWord", "highlightColor"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
5255
5479
|
}
|
|
5256
5480
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMessageOrchestratorComponent, decorators: [{
|
|
5257
5481
|
type: Component,
|
|
5258
|
-
args: [{ selector: 'dc-message-orchestrator', imports: [
|
|
5482
|
+
args: [{ selector: 'dc-message-orchestrator', imports: [MessageContentReactive], changeDetection: ChangeDetectionStrategy.OnPush, providers: [MessageOrchestrationService], template: "<div class=\"message-orchestrator-container\">\n @for (message of messagesSignal(); track $index) {\n <message-content-reactive [message]=\"message\" [index]=\"$index\" />\n }\n</div>\n", styles: [":host{display:block}.word-options-popup{position:absolute;border:1px solid #ccc;background:#fff;padding:5px;z-index:1000}message-content-displayer{text-shadow:1px 1px 4px rgba(255,255,255,.4)}\n"] }]
|
|
5259
5483
|
}], ctorParameters: () => [], propDecorators: { messages: [{ type: i0.Input, args: [{ isSignal: true, alias: "messages", required: true }] }], messageRole: [{ type: i0.Input, args: [{ isSignal: true, alias: "messageRole", required: true }] }], audioCompleted: [{ type: i0.Output, args: ["audioCompleted"] }] } });
|
|
5260
5484
|
|
|
5261
5485
|
const EMOJI_TO_LABEL = new Map(MoodStateOptions.map((o) => [o.emoji, o.label]));
|
|
@@ -5571,169 +5795,262 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
5571
5795
|
args: ['tooltipRef']
|
|
5572
5796
|
}] } });
|
|
5573
5797
|
|
|
5574
|
-
|
|
5798
|
+
// Removed AudioState as it's replaced by AudioStatus from agent.models
|
|
5799
|
+
class MessageContentDisplayer {
|
|
5800
|
+
get hostHighlightColor() {
|
|
5801
|
+
return this.highlightColor();
|
|
5802
|
+
}
|
|
5575
5803
|
constructor() {
|
|
5576
|
-
//
|
|
5577
|
-
this.
|
|
5578
|
-
this.
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
this.
|
|
5804
|
+
// Inputs
|
|
5805
|
+
this.message = input.required(...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
|
|
5806
|
+
this.markWord = input(...(ngDevMode ? [undefined, { debugName: "markWord" }] : /* istanbul ignore next */ []));
|
|
5807
|
+
this.highlightColor = input(...(ngDevMode ? [undefined, { debugName: "highlightColor" }] : /* istanbul ignore next */ []));
|
|
5808
|
+
// Outputs
|
|
5809
|
+
this.playAudio = output();
|
|
5810
|
+
this.audioCompleted = output();
|
|
5811
|
+
this.audioStatusChanged = output();
|
|
5812
|
+
this.wordClicked = output();
|
|
5813
|
+
// Signal States
|
|
5814
|
+
this.wordWithMeta = signal([], ...(ngDevMode ? [{ debugName: "wordWithMeta" }] : /* istanbul ignore next */ []));
|
|
5815
|
+
this.localAudioStatus = signal(null, ...(ngDevMode ? [{ debugName: "localAudioStatus" }] : /* istanbul ignore next */ []));
|
|
5816
|
+
// Signals State computed from the input message
|
|
5817
|
+
this.wordsWithMetaState = [];
|
|
5818
|
+
this.lastHasTranscription = false;
|
|
5819
|
+
this.audioElement = signal(null, ...(ngDevMode ? [{ debugName: "audioElement" }] : /* istanbul ignore next */ []));
|
|
5582
5820
|
this.destroyRef = inject(DestroyRef);
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5821
|
+
this.iconState = computed(() => {
|
|
5822
|
+
const msg = this.message();
|
|
5823
|
+
if (msg?.isLoading) {
|
|
5824
|
+
return 'loading';
|
|
5825
|
+
}
|
|
5826
|
+
const status = this.localAudioStatus() || msg?.audioStatus;
|
|
5827
|
+
if (status === 'playing') {
|
|
5828
|
+
return 'playing';
|
|
5829
|
+
}
|
|
5830
|
+
if (status === 'stopped') {
|
|
5831
|
+
return 'paused';
|
|
5832
|
+
}
|
|
5833
|
+
if (status === 'finished') {
|
|
5834
|
+
return 'finished';
|
|
5835
|
+
}
|
|
5836
|
+
if (status === 'pending') {
|
|
5837
|
+
return 'pending';
|
|
5838
|
+
}
|
|
5839
|
+
if (msg?.audioUrl) {
|
|
5840
|
+
return 'playable';
|
|
5841
|
+
}
|
|
5842
|
+
return 'idle';
|
|
5843
|
+
}, ...(ngDevMode ? [{ debugName: "iconState" }] : /* istanbul ignore next */ []));
|
|
5844
|
+
this.isPendingAudio = computed(() => !!this.message() && this.message()?.audioStatus === 'pending', ...(ngDevMode ? [{ debugName: "isPendingAudio" }] : /* istanbul ignore next */ []));
|
|
5845
|
+
this.messageText = computed(() => {
|
|
5846
|
+
const msg = this.message();
|
|
5847
|
+
return msg?.text || msg?.content || 'N/A';
|
|
5848
|
+
}, ...(ngDevMode ? [{ debugName: "messageText" }] : /* istanbul ignore next */ []));
|
|
5849
|
+
this.classTag = computed(() => this.message()?.tag, ...(ngDevMode ? [{ debugName: "classTag" }] : /* istanbul ignore next */ []));
|
|
5850
|
+
// When message is updated, check is has transcriptions whisper format.
|
|
5851
|
+
this.transcriptionTimestamps = computed(() => {
|
|
5852
|
+
const msg = this.message();
|
|
5853
|
+
if (!msg || !msg.transcription) {
|
|
5854
|
+
return [];
|
|
5855
|
+
}
|
|
5856
|
+
return matchTranscription(msg.text || msg.content, msg.transcription.words);
|
|
5857
|
+
}, ...(ngDevMode ? [{ debugName: "transcriptionTimestamps" }] : /* istanbul ignore next */ []));
|
|
5858
|
+
// if transcriptionTimestamps is calculated then hasTranscription is true.
|
|
5859
|
+
this.hasTranscription = computed(() => {
|
|
5860
|
+
return this.transcriptionTimestamps().length > 0;
|
|
5861
|
+
}, ...(ngDevMode ? [{ debugName: "hasTranscription" }] : /* istanbul ignore next */ []));
|
|
5862
|
+
effect(() => {
|
|
5863
|
+
const currentMsg = this.message();
|
|
5864
|
+
if (!currentMsg)
|
|
5865
|
+
return;
|
|
5866
|
+
const hasTranscription = this.hasTranscription();
|
|
5867
|
+
const idChanged = currentMsg.messageId !== this.currentMessageId;
|
|
5868
|
+
const audioUrlChanged = currentMsg.audioUrl !== this.lastAudioUrl;
|
|
5869
|
+
const transcriptionChanged = hasTranscription !== this.lastHasTranscription;
|
|
5870
|
+
if (idChanged || audioUrlChanged || transcriptionChanged) {
|
|
5871
|
+
this.currentMessageId = currentMsg.messageId;
|
|
5872
|
+
this.lastAudioUrl = currentMsg.audioUrl;
|
|
5873
|
+
this.lastHasTranscription = hasTranscription;
|
|
5874
|
+
this.localAudioStatus.set(null); // Reset local state on message/audio change
|
|
5875
|
+
this.initializeBasedOnMessage(currentMsg);
|
|
5876
|
+
}
|
|
5877
|
+
});
|
|
5878
|
+
effect(() => {
|
|
5879
|
+
if (this.isPendingAudio()) {
|
|
5880
|
+
this.startAudioPlayback();
|
|
5881
|
+
}
|
|
5882
|
+
});
|
|
5883
|
+
effect(() => {
|
|
5884
|
+
this.wordsWithMetaState = this.wordWithMeta();
|
|
5586
5885
|
});
|
|
5587
5886
|
}
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
// Create new signal and subject for this message if they don't exist
|
|
5598
|
-
if (!this.highlightedWordsSignalMap.has(messageId)) {
|
|
5599
|
-
this.highlightedWordsSignalMap.set(messageId, signal([]));
|
|
5887
|
+
trackByIndex(index, item) {
|
|
5888
|
+
return index;
|
|
5889
|
+
}
|
|
5890
|
+
ngOnDestroy() {
|
|
5891
|
+
this.cleanupAudio();
|
|
5892
|
+
}
|
|
5893
|
+
initializeBasedOnMessage(msg) {
|
|
5894
|
+
if (msg.audioUrl) {
|
|
5895
|
+
this.initializeAudio(msg.audioUrl);
|
|
5600
5896
|
}
|
|
5601
|
-
if (
|
|
5602
|
-
|
|
5897
|
+
if (this.hasTranscription()) {
|
|
5898
|
+
const wordsAndTimestamps = [];
|
|
5899
|
+
this.transcriptionTimestamps()?.forEach((word, index) => {
|
|
5900
|
+
wordsAndTimestamps.push({ ...word, ...this.wordsWithMetaState[index], index });
|
|
5901
|
+
});
|
|
5902
|
+
const firstState = this.transcriptionTimestamps()?.map((word, index) => ({
|
|
5903
|
+
word: word.word,
|
|
5904
|
+
index,
|
|
5905
|
+
isHighlighted: false,
|
|
5906
|
+
tag: '',
|
|
5907
|
+
marked: word.word.toLowerCase() == this.markWord()?.toLowerCase(),
|
|
5908
|
+
}));
|
|
5909
|
+
console.log('firstState', firstState);
|
|
5910
|
+
this.wordWithMeta.set(firstState);
|
|
5911
|
+
// Para mostrar las transcripciones iliminadas: el audio tiene que sonar y en la subscripción apareceran los cambios.
|
|
5603
5912
|
}
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
//
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
// Listen to timeupdate events
|
|
5913
|
+
else {
|
|
5914
|
+
this.initializePlainTextWords(msg.text || msg.content || '');
|
|
5915
|
+
}
|
|
5916
|
+
// Removal of shouldPlayAudio check - playback now handled by isPendingAudio effect
|
|
5917
|
+
}
|
|
5918
|
+
initializeAudio(audioUrl) {
|
|
5919
|
+
let audio = this.audioElement();
|
|
5920
|
+
if (!audio) {
|
|
5921
|
+
audio = new Audio(audioUrl);
|
|
5922
|
+
this.audioElement.set(audio);
|
|
5923
|
+
this.setupAudioEventListeners(audio);
|
|
5924
|
+
}
|
|
5925
|
+
else if (audio.src && audioUrl && !audio.src.endsWith(audioUrl)) {
|
|
5926
|
+
audio.src = audioUrl;
|
|
5927
|
+
}
|
|
5928
|
+
}
|
|
5929
|
+
setupAudioEventListeners(audioElement) {
|
|
5622
5930
|
fromEvent(audioElement, 'timeupdate')
|
|
5623
|
-
.pipe(takeUntilDestroyed(this.destroyRef),
|
|
5931
|
+
.pipe(takeUntilDestroyed(this.destroyRef), map(() => audioElement.currentTime || 0))
|
|
5624
5932
|
.subscribe((currentTime) => {
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
return
|
|
5933
|
+
if (currentTime === 0) {
|
|
5934
|
+
this.updateAudioStatus('playing');
|
|
5935
|
+
return;
|
|
5936
|
+
}
|
|
5937
|
+
if (this.hasTranscription()) {
|
|
5938
|
+
const wordsAndTimestamps = [];
|
|
5939
|
+
this.transcriptionTimestamps()?.forEach((word, index) => {
|
|
5940
|
+
wordsAndTimestamps.push({ ...word, ...this.wordsWithMetaState[index], index });
|
|
5941
|
+
});
|
|
5942
|
+
const updatedWords = wordsAndTimestamps.map((word, index) => ({
|
|
5628
5943
|
word: word.word,
|
|
5629
5944
|
index,
|
|
5630
|
-
isHighlighted,
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5945
|
+
isHighlighted: currentTime >= word.start - 0.15 && currentTime < word.end + 0.15,
|
|
5946
|
+
tag: word.tag,
|
|
5947
|
+
marked: word.word.toLowerCase() == this.markWord()?.toLowerCase(),
|
|
5948
|
+
}));
|
|
5949
|
+
this.wordWithMeta.set(updatedWords);
|
|
5950
|
+
}
|
|
5636
5951
|
});
|
|
5637
|
-
// Listen to ended event for cleanup
|
|
5638
5952
|
fromEvent(audioElement, 'ended')
|
|
5639
|
-
.pipe(takeUntilDestroyed(this.destroyRef)
|
|
5953
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
5640
5954
|
.subscribe(() => {
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
});
|
|
5649
|
-
}
|
|
5650
|
-
/**
|
|
5651
|
-
* Stops the sync for a specific message and cleans up resources
|
|
5652
|
-
* @param messageId The ID of the message to stop syncing
|
|
5653
|
-
*/
|
|
5654
|
-
stopSync(messageId) {
|
|
5655
|
-
if (this.activeAudioMap.has(messageId)) {
|
|
5656
|
-
// Get the cleanup subject for this message
|
|
5657
|
-
const cleanup$ = this.cleanup$Map.get(messageId);
|
|
5658
|
-
if (cleanup$) {
|
|
5659
|
-
cleanup$.next();
|
|
5660
|
-
cleanup$.complete();
|
|
5661
|
-
this.cleanup$Map.delete(messageId);
|
|
5662
|
-
}
|
|
5663
|
-
this.activeAudioMap.delete(messageId);
|
|
5664
|
-
// Reset state for this message
|
|
5665
|
-
const messageSignal = this.highlightedWordsSignalMap.get(messageId);
|
|
5666
|
-
const messageSubject = this.highlightedWords$Map.get(messageId);
|
|
5667
|
-
if (messageSignal) {
|
|
5668
|
-
messageSignal.set([]);
|
|
5669
|
-
}
|
|
5670
|
-
if (messageSubject) {
|
|
5671
|
-
messageSubject.next([]);
|
|
5955
|
+
const resetWords = this.wordWithMeta().map((word) => ({ ...word, isHighlighted: false }));
|
|
5956
|
+
this.wordWithMeta.set(resetWords);
|
|
5957
|
+
const currentMsg = this.message();
|
|
5958
|
+
if (currentMsg) {
|
|
5959
|
+
// No direct mutation of input!
|
|
5960
|
+
this.updateAudioStatus('finished');
|
|
5961
|
+
this.audioCompleted.emit(currentMsg);
|
|
5672
5962
|
}
|
|
5673
|
-
}
|
|
5963
|
+
});
|
|
5674
5964
|
}
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
//
|
|
5680
|
-
|
|
5681
|
-
|
|
5965
|
+
updateAudioStatus(status) {
|
|
5966
|
+
const currentMsg = this.message();
|
|
5967
|
+
// Update local state for immediate feedback
|
|
5968
|
+
this.localAudioStatus.set(status);
|
|
5969
|
+
// Only update if the status is actually changing relative to the current input
|
|
5970
|
+
if (currentMsg && currentMsg.audioStatus !== status) {
|
|
5971
|
+
// NOTE: We do NOT mutate currentMsg.audioStatus here.
|
|
5972
|
+
// The parent orchestration service will update the state and propagate it back via signals.
|
|
5973
|
+
this.audioStatusChanged.emit({ message: currentMsg, status });
|
|
5682
5974
|
}
|
|
5683
|
-
// Clear all maps
|
|
5684
|
-
this.highlightedWordsSignalMap.clear();
|
|
5685
|
-
this.highlightedWords$Map.clear();
|
|
5686
|
-
this.cleanup$Map.clear();
|
|
5687
|
-
this.activeAudioMap.clear();
|
|
5688
5975
|
}
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
this.highlightedWordsSignalMap.set(messageId, signal([]));
|
|
5976
|
+
cleanupAudio() {
|
|
5977
|
+
const audioElement = this.audioElement();
|
|
5978
|
+
if (audioElement) {
|
|
5979
|
+
audioElement.pause();
|
|
5980
|
+
audioElement.src = '';
|
|
5981
|
+
const resetWords = this.wordWithMeta().map((word) => ({ ...word, isHighlighted: false }));
|
|
5982
|
+
this.wordWithMeta.set(resetWords);
|
|
5697
5983
|
}
|
|
5698
|
-
return this.highlightedWordsSignalMap.get(messageId);
|
|
5699
5984
|
}
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5985
|
+
onPlayMessage() {
|
|
5986
|
+
console.log('onPlayMessage');
|
|
5987
|
+
const currentMsg = this.message();
|
|
5988
|
+
const audioElement = this.audioElement();
|
|
5989
|
+
if (audioElement) {
|
|
5990
|
+
if (audioElement.paused) {
|
|
5991
|
+
this.startAudioPlayback();
|
|
5992
|
+
}
|
|
5993
|
+
else {
|
|
5994
|
+
audioElement.pause();
|
|
5995
|
+
this.updateAudioStatus('stopped');
|
|
5996
|
+
}
|
|
5997
|
+
}
|
|
5998
|
+
else if (currentMsg?.audioUrl) {
|
|
5999
|
+
console.log('onPlayMessage initializeAudio with url');
|
|
6000
|
+
this.initializeAudio(currentMsg.audioUrl);
|
|
6001
|
+
this.startAudioPlayback();
|
|
6002
|
+
}
|
|
6003
|
+
else if (currentMsg) {
|
|
6004
|
+
console.log('onPlayMessage emit playAudio');
|
|
6005
|
+
this.playAudio.emit(currentMsg);
|
|
5708
6006
|
}
|
|
5709
|
-
return this.highlightedWords$Map.get(messageId).asObservable();
|
|
5710
6007
|
}
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
6008
|
+
initializePlainTextWords(text) {
|
|
6009
|
+
const words = extractTags(text);
|
|
6010
|
+
const plainWords = words.map((word, index) => ({
|
|
6011
|
+
word: word.word,
|
|
6012
|
+
index: index,
|
|
6013
|
+
isHighlighted: false,
|
|
6014
|
+
tag: word.tag,
|
|
6015
|
+
}));
|
|
6016
|
+
this.wordWithMeta.set(plainWords);
|
|
5719
6017
|
}
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
6018
|
+
onWordClick(wordData) {
|
|
6019
|
+
const currentMsg = this.message();
|
|
6020
|
+
if (!currentMsg)
|
|
6021
|
+
return;
|
|
6022
|
+
const clickedWord = 'isHighlighted' in wordData
|
|
6023
|
+
? { word: wordData.word, index: wordData.index, messageId: currentMsg.messageId }
|
|
6024
|
+
: { ...wordData, messageId: currentMsg.messageId };
|
|
6025
|
+
this.wordClicked.emit(clickedWord);
|
|
5727
6026
|
}
|
|
5728
|
-
|
|
5729
|
-
|
|
6027
|
+
startAudioPlayback() {
|
|
6028
|
+
const audioElement = this.audioElement();
|
|
6029
|
+
if (!audioElement)
|
|
6030
|
+
return;
|
|
6031
|
+
try {
|
|
6032
|
+
audioElement.play();
|
|
6033
|
+
console.log('Audio playing');
|
|
6034
|
+
}
|
|
6035
|
+
catch (error) {
|
|
6036
|
+
console.error('Error playing audio:', error);
|
|
6037
|
+
}
|
|
6038
|
+
// Important: we move it to playing status immediately to avoid re-triggering the effect
|
|
6039
|
+
this.updateAudioStatus('playing');
|
|
6040
|
+
}
|
|
6041
|
+
debug() {
|
|
6042
|
+
console.log('debug', this.wordWithMeta());
|
|
6043
|
+
}
|
|
6044
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageContentDisplayer, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6045
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MessageContentDisplayer, isStandalone: true, selector: "message-content-displayer", inputs: { message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: true, transformFunction: null }, markWord: { classPropertyName: "markWord", publicName: "markWord", isSignal: true, isRequired: false, transformFunction: null }, highlightColor: { classPropertyName: "highlightColor", publicName: "highlightColor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { playAudio: "playAudio", audioCompleted: "audioCompleted", audioStatusChanged: "audioStatusChanged", wordClicked: "wordClicked" }, host: { properties: { "style.--highlight-bg-color": "this.hostHighlightColor" } }, ngImport: i0, template: "<div class=\"audio-text-sync-container\" [class.is-active]=\"message().isActive\">\n <i (click)=\"onPlayMessage()\" class=\"play-button\">\n @switch (iconState()) { \n @case ('loading') { <i class=\"spin-animation pi pi-spinner-dotted\"></i> } \n @case ('playing') { <i class=\"pi pi-volume-up\"></i> }\n @case ('paused') { <i class=\"pi pi-pause-circle\"></i> } \n @case ('finished') { <i class=\"pi pi-play-circle\" style=\"color: white\"></i> } \n @case ('pending') { <i class=\"pi pi-angle-right\"></i> }\n @case ('playable') { <i class=\"pi pi-play-circle\" style=\"color: white\"></i> } \n }\n </i>\n\n <!-- Display transcription with highlighting -->\n <p style=\"width: 100%\" [class]=\"'text-content ' + classTag()\">\n @for (word of wordWithMeta(); track trackByIndex($index, word)) {\n <span\n [class]=\"word.tag\"\n [class.highlight]=\"word.isHighlighted\"\n [class.marked]=\"word.marked\"\n (click)=\"onWordClick(word)\"\n [attr.id]=\"'word-' + message().messageId + '-' + word.index\"\n >{{ word.word }}\n </span>\n }\n </p>\n</div>\n@if (message().imageUrl) {\n<div class=\"tour-image-container\" style=\"display: flex; justify-content: center\">\n <img [src]=\"message().imageUrl\" alt=\"Tour Image\" style=\"max-width: 220px; height: auto\" />\n</div>\n}\n", styles: [":host{display:block}.audio-text-sync-container{display:flex;align-items:flex-start;gap:2px;align-items:center;transition:background-color .3s ease}.audio-text-sync-container.is-active{background-color:#3cd8ff1a;border-radius:8px;padding-left:4px}.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:var(--highlight-bg-color, rgb(60, 216, 255));border-radius:4px}::ng-deep .marked{position:relative;color:#f8bfbc}::ng-deep .marked:before{content:\"\";z-index:-2;left:-.1em;top:.1em;border-width:2px;border-style:solid;border-color:#f27068;position:absolute;border-right-color:transparent;width:100%;height:1em;transform:rotate(2deg);opacity:.7;border-radius:50%;padding:.1em .25em}::ng-deep .marked:after{content:\"\";z-index:-1;left:-.1em;top:.4em;padding:.1em .25em;border-width:2px;border-style:solid;border-color:#f27068;border-left-color:transparent;border-top-color:transparent;position:absolute;width:100%;height:1em;transform:rotate(-1deg);opacity:.7;border-radius:50%}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.em{font-style:italic;color:var(--chat-message-italic-color, rgb(184, 208, 252))}.strong{font-weight:700;color:inherit}.italic{font-style:italic;color:var(--chat-message-italic-color, rgb(252, 198, 184))}.em_strong{font-weight:700;font-style:italic;color:inherit}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
5730
6046
|
}
|
|
5731
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type:
|
|
5732
|
-
type:
|
|
5733
|
-
args: [{
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
6047
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageContentDisplayer, decorators: [{
|
|
6048
|
+
type: Component,
|
|
6049
|
+
args: [{ selector: 'message-content-displayer', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"audio-text-sync-container\" [class.is-active]=\"message().isActive\">\n <i (click)=\"onPlayMessage()\" class=\"play-button\">\n @switch (iconState()) { \n @case ('loading') { <i class=\"spin-animation pi pi-spinner-dotted\"></i> } \n @case ('playing') { <i class=\"pi pi-volume-up\"></i> }\n @case ('paused') { <i class=\"pi pi-pause-circle\"></i> } \n @case ('finished') { <i class=\"pi pi-play-circle\" style=\"color: white\"></i> } \n @case ('pending') { <i class=\"pi pi-angle-right\"></i> }\n @case ('playable') { <i class=\"pi pi-play-circle\" style=\"color: white\"></i> } \n }\n </i>\n\n <!-- Display transcription with highlighting -->\n <p style=\"width: 100%\" [class]=\"'text-content ' + classTag()\">\n @for (word of wordWithMeta(); track trackByIndex($index, word)) {\n <span\n [class]=\"word.tag\"\n [class.highlight]=\"word.isHighlighted\"\n [class.marked]=\"word.marked\"\n (click)=\"onWordClick(word)\"\n [attr.id]=\"'word-' + message().messageId + '-' + word.index\"\n >{{ word.word }}\n </span>\n }\n </p>\n</div>\n@if (message().imageUrl) {\n<div class=\"tour-image-container\" style=\"display: flex; justify-content: center\">\n <img [src]=\"message().imageUrl\" alt=\"Tour Image\" style=\"max-width: 220px; height: auto\" />\n</div>\n}\n", styles: [":host{display:block}.audio-text-sync-container{display:flex;align-items:flex-start;gap:2px;align-items:center;transition:background-color .3s ease}.audio-text-sync-container.is-active{background-color:#3cd8ff1a;border-radius:8px;padding-left:4px}.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:var(--highlight-bg-color, rgb(60, 216, 255));border-radius:4px}::ng-deep .marked{position:relative;color:#f8bfbc}::ng-deep .marked:before{content:\"\";z-index:-2;left:-.1em;top:.1em;border-width:2px;border-style:solid;border-color:#f27068;position:absolute;border-right-color:transparent;width:100%;height:1em;transform:rotate(2deg);opacity:.7;border-radius:50%;padding:.1em .25em}::ng-deep .marked:after{content:\"\";z-index:-1;left:-.1em;top:.4em;padding:.1em .25em;border-width:2px;border-style:solid;border-color:#f27068;border-left-color:transparent;border-top-color:transparent;position:absolute;width:100%;height:1em;transform:rotate(-1deg);opacity:.7;border-radius:50%}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.em{font-style:italic;color:var(--chat-message-italic-color, rgb(184, 208, 252))}.strong{font-weight:700;color:inherit}.italic{font-style:italic;color:var(--chat-message-italic-color, rgb(252, 198, 184))}.em_strong{font-weight:700;font-style:italic;color:inherit}\n"] }]
|
|
6050
|
+
}], ctorParameters: () => [], propDecorators: { message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: true }] }], markWord: [{ type: i0.Input, args: [{ isSignal: true, alias: "markWord", required: false }] }], highlightColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlightColor", required: false }] }], hostHighlightColor: [{
|
|
6051
|
+
type: HostBinding,
|
|
6052
|
+
args: ['style.--highlight-bg-color']
|
|
6053
|
+
}], playAudio: [{ type: i0.Output, args: ["playAudio"] }], audioCompleted: [{ type: i0.Output, args: ["audioCompleted"] }], audioStatusChanged: [{ type: i0.Output, args: ["audioStatusChanged"] }], wordClicked: [{ type: i0.Output, args: ["wordClicked"] }] } });
|
|
5737
6054
|
|
|
5738
6055
|
class DcImmersiveChatComponent {
|
|
5739
6056
|
constructor() {
|
|
@@ -6137,16 +6454,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
6137
6454
|
|
|
6138
6455
|
class MessagesStateInspectorComponent {
|
|
6139
6456
|
constructor() {
|
|
6140
|
-
this.
|
|
6141
|
-
this.
|
|
6457
|
+
this.service = input(...(ngDevMode ? [undefined, { debugName: "service" }] : /* istanbul ignore next */ []));
|
|
6458
|
+
this.fallback = inject(MessagesStateService);
|
|
6459
|
+
this.messages = computed(() => (this.service() ?? this.fallback).getMessagesSignal()(), ...(ngDevMode ? [{ debugName: "messages" }] : /* istanbul ignore next */ []));
|
|
6142
6460
|
}
|
|
6143
6461
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessagesStateInspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6144
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MessagesStateInspectorComponent, isStandalone: true, selector: "dc-messages-state-inspector", ngImport: i0, template: "<div class=\"messages-state-inspector\">\n @for (msg of messages(); track msg.messageId; let i = $index) {\n <details class=\"message-group mb-2 border rounded p-2\">\n <summary class=\"cursor-pointer font-bold p-1 hover:bg-gray-100 flex justify-between items-center\">\n <span>\n <span class=\"text-xs text-gray-400 mr-2\">#{{ i + 1 }}</span>\n <span [class]=\"'role-badge ' + msg.role\">{{ msg.role }}</span>\n <span class=\"ml-2 text-sm truncate max-w-xs\">{{ msg.content | slice:0:50 }}{{ msg.content.length > 50 ? '...' : '' }}</span>\n </span>\n <span class=\"text-xs text-gray-400\">{{ msg.messageId }}</span>\n </summary>\n\n <div class=\"message-details mt-2 pl-4 border-l-2 border-primary\">\n <!-- Standard Properties -->\n <div class=\"grid grid-cols-2 gap-2 text-sm\">\n <div><strong>Role:</strong> {{ msg.role }}</div>\n <div><strong>Section:</strong> {{ msg.section || 'N/A' }}</div>\n <div><strong>Message ID:</strong> {{ msg.messageId }}</div>\n <div class=\"col-span-2\"><strong>Content:</strong> <pre class=\"whitespace-pre-wrap text-xs bg-gray-50 p-2 mt-1\">{{ msg.content }}</pre></div>\n @if (msg.translation) {\n <div class=\"col-span-2 text-blue-600\"><strong>Translation:</strong> {{ msg.translation }}</div>\n }\n </div>\n\n <!-- JSON Preview of other props -->\n <details class=\"mt-2\">\n <summary class=\"text-xs text-gray-500 cursor-pointer\">All Metadata</summary>\n <pre class=\"text-[10px] bg-slate-800 text-white p-2 rounded mt-1 overflow-auto max-h-64\">{{ msg | safeJson }}</pre>\n </details>\n\n <!-- MultiMessages recursive check -->\n @if (msg.multiMessages && msg.multiMessages.length > 0) {\n <div class=\"mt-3\">\n <strong class=\"text-xs uppercase text-gray-500\">Multi Messages ({{ msg.multiMessages.length }})</strong>\n <div class=\"pl-4 border-l-2 border-dashed border-gray-300\">\n @for (subMsg of msg.multiMessages; track $index) {\n <details class=\"mt-1 border rounded p-1 bg-gray-50\">\n <summary class=\"text-xs cursor-pointer truncate\">Sub: {{ subMsg.text | slice:0:30 }}...</summary>\n <pre class=\"text-[10px] mt-1 p-1 bg-white border overflow-auto\">{{ subMsg | safeJson }}</pre>\n </details>\n }\n </div>\n </div>\n }\n\n <!-- Evaluation -->\n @if (msg.evaluation) {\n <div class=\"mt-2 p-2 bg-yellow-50 rounded text-xs space-y-2\">\n <strong>Evaluation</strong>\n\n @if (msg.evaluation['flow']; as flow) {\n <div class=\"flow-eval space-y-2\">\n @if (flow.goal) {\n <div class=\"p-2 bg-white rounded border\">\n <div class=\"flex items-center justify-between\">\n <strong class=\"text-[11px] uppercase text-gray-600\">Goal</strong>\n @if (flow.goal.deltaPoints != null) {\n <span\n class=\"px-1.5 rounded text-[11px] font-bold\"\n [class.bg-green-100]=\"flow.goal.deltaPoints > 0\"\n [class.text-green-700]=\"flow.goal.deltaPoints > 0\"\n [class.bg-red-100]=\"flow.goal.deltaPoints < 0\"\n [class.text-red-700]=\"flow.goal.deltaPoints < 0\"\n [class.bg-gray-100]=\"flow.goal.deltaPoints === 0\"\n [class.text-gray-600]=\"flow.goal.deltaPoints === 0\">\n {{ flow.goal.deltaPoints > 0 ? '+' : '' }}{{ flow.goal.deltaPoints }} pts\n </span>\n }\n </div>\n @if (flow.goal.previousValue != null && flow.goal.newValue != null) {\n <div class=\"mt-1 text-[11px] text-gray-600\">\n <span>{{ flow.goal.previousValue }}</span>\n <span class=\"mx-1\">\u2192</span>\n <span class=\"font-bold text-gray-900\">{{ flow.goal.newValue }}</span>\n <span class=\"text-gray-400\"> / 100</span>\n </div>\n <div class=\"w-full bg-gray-200 rounded h-1 mt-1 overflow-hidden\">\n <div class=\"bg-primary h-1\" [style.width.%]=\"flow.goal.newValue\" [style.background-color]=\"'#3b82f6'\"></div>\n </div>\n }\n @if (flow.intervention?.message) {\n <div class=\"mt-1 text-[11px] italic text-gray-700\">\"{{ flow.intervention.message }}\"</div>\n }\n </div>\n }\n\n @if (flow.challenges?.length) {\n <div class=\"p-2 bg-white rounded border\">\n <strong class=\"text-[11px] uppercase text-gray-600\">Challenges</strong>\n <ul class=\"mt-1 space-y-0.5\">\n @for (ch of flow.challenges; track ch.name) {\n <li class=\"flex items-center gap-1 text-[11px]\">\n <span [class.text-green-600]=\"ch.value\" [class.text-gray-400]=\"!ch.value\">\n {{ ch.value ? '\u2713' : '\u25CB' }}\n </span>\n <span [class.font-semibold]=\"ch.value\">{{ ch.name }}</span>\n </li>\n }\n </ul>\n </div>\n }\n\n @if (flow.moodState?.value) {\n <div class=\"p-2 bg-white rounded border flex items-center justify-between\">\n <strong class=\"text-[11px] uppercase text-gray-600\">Mood</strong>\n <span class=\"text-[11px] font-semibold\">{{ flow.moodState.value }}</span>\n </div>\n }\n\n @if (flow.evaluatedAt) {\n <div class=\"text-[10px] text-gray-400 text-right\">{{ flow.evaluatedAt }}</div>\n }\n </div>\n }\n\n <details class=\"mt-1\">\n <summary class=\"text-[10px] text-gray-500 cursor-pointer\">Raw evaluation</summary>\n <pre class=\"text-[10px]\">{{ msg.evaluation | safeJson }}</pre>\n </details>\n </div>\n }\n\n <!-- Tags -->\n @if (msg.tags && msg.tags.length > 0) {\n <div class=\"mt-2 flex flex-wrap gap-1\">\n <strong>Tags:</strong>\n @for (tag of msg.tags; track tag) {\n <span class=\"bg-gray-200 px-1 rounded text-[10px]\">{{ tag }}</span>\n }\n </div>\n }\n </div>\n </details>\n } @empty {\n <div class=\"p-4 text-center text-gray-400\">No messages in state.</div>\n }\n</div>\n\n<style>\n .role-badge {\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 0.7rem;\n text-transform: uppercase;\n font-weight: bold;\n }\n .role-badge.user { background: #e0f2fe; color: #0369a1; }\n .role-badge.assistant { background: #f0fdf4; color: #15803d; }\n .role-badge.assistantHelper { background: #faf5ff; color: #7e22ce; }\n .role-badge.system { background: #f1f5f9; color: #475569; }\n .border-primary { border-color: var(--p-primary-color, #3b82f6); }\n</style>\n", styles: [".role-badge{padding:2px 6px;border-radius:4px;font-size:.7rem;text-transform:uppercase;font-weight:700}.role-badge.user{background:#e0f2fe;color:#0369a1}.role-badge.assistant{background:#f0fdf4;color:#15803d}.role-badge.assistantHelper{background:#faf5ff;color:#7e22ce}.role-badge.system{background:#f1f5f9;color:#475569}.border-primary{border-color:var(--p-primary-color, #3b82f6)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1.SlicePipe, name: "slice" }, { kind: "pipe", type: SafeJsonPipe, name: "safeJson" }] }); }
|
|
6462
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MessagesStateInspectorComponent, isStandalone: true, selector: "dc-messages-state-inspector", inputs: { service: { classPropertyName: "service", publicName: "service", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"messages-state-inspector\">\n @for (msg of messages(); track msg.messageId; let i = $index) {\n <details class=\"message-group mb-2 border rounded p-2\">\n <summary class=\"cursor-pointer font-bold p-1 hover:bg-gray-100 flex justify-between items-center\">\n <span>\n <span class=\"text-xs text-gray-400 mr-2\">#{{ i + 1 }}</span>\n <span [class]=\"'role-badge ' + msg.role\">{{ msg.role }}</span>\n <span class=\"ml-2 text-sm truncate max-w-xs\">{{ msg.content | slice:0:50 }}{{ msg.content.length > 50 ? '...' : '' }}</span>\n </span>\n <span class=\"text-xs text-gray-400\">{{ msg.messageId }}</span>\n </summary>\n\n <div class=\"message-details mt-2 pl-4 border-l-2 border-primary\">\n <!-- Standard Properties -->\n <div class=\"grid grid-cols-2 gap-2 text-sm\">\n <div><strong>Role:</strong> {{ msg.role }}</div>\n <div><strong>Section:</strong> {{ msg.section || 'N/A' }}</div>\n <div><strong>Message ID:</strong> {{ msg.messageId }}</div>\n <div class=\"col-span-2\"><strong>Content:</strong> <pre class=\"whitespace-pre-wrap text-xs bg-gray-50 p-2 mt-1\">{{ msg.content }}</pre></div>\n @if (msg.translation) {\n <div class=\"col-span-2 text-blue-600\"><strong>Translation:</strong> {{ msg.translation }}</div>\n }\n </div>\n\n <!-- JSON Preview of other props -->\n <details class=\"mt-2\">\n <summary class=\"text-xs text-gray-500 cursor-pointer\">All Metadata</summary>\n <pre class=\"text-[10px] bg-slate-800 text-white p-2 rounded mt-1 overflow-auto max-h-64\">{{ msg | safeJson }}</pre>\n </details>\n\n <!-- MultiMessages recursive check -->\n @if (msg.multiMessages && msg.multiMessages.length > 0) {\n <div class=\"mt-3\">\n <strong class=\"text-xs uppercase text-gray-500\">Multi Messages ({{ msg.multiMessages.length }})</strong>\n <div class=\"pl-4 border-l-2 border-dashed border-gray-300\">\n @for (subMsg of msg.multiMessages; track $index) {\n <details class=\"mt-1 border rounded p-1 bg-gray-50\">\n <summary class=\"text-xs cursor-pointer truncate\">Sub: {{ subMsg.text | slice:0:30 }}...</summary>\n <pre class=\"text-[10px] mt-1 p-1 bg-white border overflow-auto\">{{ subMsg | safeJson }}</pre>\n </details>\n }\n </div>\n </div>\n }\n\n <!-- Evaluation -->\n @if (msg.evaluation) {\n <div class=\"mt-2 p-2 bg-yellow-50 rounded text-xs space-y-2\">\n <strong>Evaluation</strong>\n\n @if (msg.evaluation['flow']; as flow) {\n <div class=\"flow-eval space-y-2\">\n @if (flow.goal) {\n <div class=\"p-2 bg-white rounded border\">\n <div class=\"flex items-center justify-between\">\n <strong class=\"text-[11px] uppercase text-gray-600\">Goal</strong>\n @if (flow.goal.deltaPoints != null) {\n <span\n class=\"px-1.5 rounded text-[11px] font-bold\"\n [class.bg-green-100]=\"flow.goal.deltaPoints > 0\"\n [class.text-green-700]=\"flow.goal.deltaPoints > 0\"\n [class.bg-red-100]=\"flow.goal.deltaPoints < 0\"\n [class.text-red-700]=\"flow.goal.deltaPoints < 0\"\n [class.bg-gray-100]=\"flow.goal.deltaPoints === 0\"\n [class.text-gray-600]=\"flow.goal.deltaPoints === 0\">\n {{ flow.goal.deltaPoints > 0 ? '+' : '' }}{{ flow.goal.deltaPoints }} pts\n </span>\n }\n </div>\n @if (flow.goal.previousValue != null && flow.goal.newValue != null) {\n <div class=\"mt-1 text-[11px] text-gray-600\">\n <span>{{ flow.goal.previousValue }}</span>\n <span class=\"mx-1\">\u2192</span>\n <span class=\"font-bold text-gray-900\">{{ flow.goal.newValue }}</span>\n <span class=\"text-gray-400\"> / 100</span>\n </div>\n <div class=\"w-full bg-gray-200 rounded h-1 mt-1 overflow-hidden\">\n <div class=\"bg-primary h-1\" [style.width.%]=\"flow.goal.newValue\" [style.background-color]=\"'#3b82f6'\"></div>\n </div>\n }\n @if (flow.intervention?.message) {\n <div class=\"mt-1 text-[11px] italic text-gray-700\">\"{{ flow.intervention.message }}\"</div>\n }\n </div>\n }\n\n @if (flow.challenges?.length) {\n <div class=\"p-2 bg-white rounded border\">\n <strong class=\"text-[11px] uppercase text-gray-600\">Challenges</strong>\n <ul class=\"mt-1 space-y-0.5\">\n @for (ch of flow.challenges; track ch.name) {\n <li class=\"flex items-center gap-1 text-[11px]\">\n <span [class.text-green-600]=\"ch.value\" [class.text-gray-400]=\"!ch.value\">\n {{ ch.value ? '\u2713' : '\u25CB' }}\n </span>\n <span [class.font-semibold]=\"ch.value\">{{ ch.name }}</span>\n </li>\n }\n </ul>\n </div>\n }\n\n @if (flow.moodState?.value) {\n <div class=\"p-2 bg-white rounded border flex items-center justify-between\">\n <strong class=\"text-[11px] uppercase text-gray-600\">Mood</strong>\n <span class=\"text-[11px] font-semibold\">{{ flow.moodState.value }}</span>\n </div>\n }\n\n @if (flow.evaluatedAt) {\n <div class=\"text-[10px] text-gray-400 text-right\">{{ flow.evaluatedAt }}</div>\n }\n </div>\n }\n\n <details class=\"mt-1\">\n <summary class=\"text-[10px] text-gray-500 cursor-pointer\">Raw evaluation</summary>\n <pre class=\"text-[10px]\">{{ msg.evaluation | safeJson }}</pre>\n </details>\n </div>\n }\n\n <!-- Tags -->\n @if (msg.tags && msg.tags.length > 0) {\n <div class=\"mt-2 flex flex-wrap gap-1\">\n <strong>Tags:</strong>\n @for (tag of msg.tags; track tag) {\n <span class=\"bg-gray-200 px-1 rounded text-[10px]\">{{ tag }}</span>\n }\n </div>\n }\n </div>\n </details>\n } @empty {\n <div class=\"p-4 text-center text-gray-400\">No messages in state.</div>\n }\n</div>\n\n<style>\n .role-badge {\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 0.7rem;\n text-transform: uppercase;\n font-weight: bold;\n }\n .role-badge.user { background: #e0f2fe; color: #0369a1; }\n .role-badge.assistant { background: #f0fdf4; color: #15803d; }\n .role-badge.assistantHelper { background: #faf5ff; color: #7e22ce; }\n .role-badge.system { background: #f1f5f9; color: #475569; }\n .border-primary { border-color: var(--p-primary-color, #3b82f6); }\n</style>\n", styles: [".role-badge{padding:2px 6px;border-radius:4px;font-size:.7rem;text-transform:uppercase;font-weight:700}.role-badge.user{background:#e0f2fe;color:#0369a1}.role-badge.assistant{background:#f0fdf4;color:#15803d}.role-badge.assistantHelper{background:#faf5ff;color:#7e22ce}.role-badge.system{background:#f1f5f9;color:#475569}.border-primary{border-color:var(--p-primary-color, #3b82f6)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1.SlicePipe, name: "slice" }, { kind: "pipe", type: SafeJsonPipe, name: "safeJson" }] }); }
|
|
6145
6463
|
}
|
|
6146
6464
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessagesStateInspectorComponent, decorators: [{
|
|
6147
6465
|
type: Component,
|
|
6148
6466
|
args: [{ selector: 'dc-messages-state-inspector', standalone: true, imports: [CommonModule, SafeJsonPipe], template: "<div class=\"messages-state-inspector\">\n @for (msg of messages(); track msg.messageId; let i = $index) {\n <details class=\"message-group mb-2 border rounded p-2\">\n <summary class=\"cursor-pointer font-bold p-1 hover:bg-gray-100 flex justify-between items-center\">\n <span>\n <span class=\"text-xs text-gray-400 mr-2\">#{{ i + 1 }}</span>\n <span [class]=\"'role-badge ' + msg.role\">{{ msg.role }}</span>\n <span class=\"ml-2 text-sm truncate max-w-xs\">{{ msg.content | slice:0:50 }}{{ msg.content.length > 50 ? '...' : '' }}</span>\n </span>\n <span class=\"text-xs text-gray-400\">{{ msg.messageId }}</span>\n </summary>\n\n <div class=\"message-details mt-2 pl-4 border-l-2 border-primary\">\n <!-- Standard Properties -->\n <div class=\"grid grid-cols-2 gap-2 text-sm\">\n <div><strong>Role:</strong> {{ msg.role }}</div>\n <div><strong>Section:</strong> {{ msg.section || 'N/A' }}</div>\n <div><strong>Message ID:</strong> {{ msg.messageId }}</div>\n <div class=\"col-span-2\"><strong>Content:</strong> <pre class=\"whitespace-pre-wrap text-xs bg-gray-50 p-2 mt-1\">{{ msg.content }}</pre></div>\n @if (msg.translation) {\n <div class=\"col-span-2 text-blue-600\"><strong>Translation:</strong> {{ msg.translation }}</div>\n }\n </div>\n\n <!-- JSON Preview of other props -->\n <details class=\"mt-2\">\n <summary class=\"text-xs text-gray-500 cursor-pointer\">All Metadata</summary>\n <pre class=\"text-[10px] bg-slate-800 text-white p-2 rounded mt-1 overflow-auto max-h-64\">{{ msg | safeJson }}</pre>\n </details>\n\n <!-- MultiMessages recursive check -->\n @if (msg.multiMessages && msg.multiMessages.length > 0) {\n <div class=\"mt-3\">\n <strong class=\"text-xs uppercase text-gray-500\">Multi Messages ({{ msg.multiMessages.length }})</strong>\n <div class=\"pl-4 border-l-2 border-dashed border-gray-300\">\n @for (subMsg of msg.multiMessages; track $index) {\n <details class=\"mt-1 border rounded p-1 bg-gray-50\">\n <summary class=\"text-xs cursor-pointer truncate\">Sub: {{ subMsg.text | slice:0:30 }}...</summary>\n <pre class=\"text-[10px] mt-1 p-1 bg-white border overflow-auto\">{{ subMsg | safeJson }}</pre>\n </details>\n }\n </div>\n </div>\n }\n\n <!-- Evaluation -->\n @if (msg.evaluation) {\n <div class=\"mt-2 p-2 bg-yellow-50 rounded text-xs space-y-2\">\n <strong>Evaluation</strong>\n\n @if (msg.evaluation['flow']; as flow) {\n <div class=\"flow-eval space-y-2\">\n @if (flow.goal) {\n <div class=\"p-2 bg-white rounded border\">\n <div class=\"flex items-center justify-between\">\n <strong class=\"text-[11px] uppercase text-gray-600\">Goal</strong>\n @if (flow.goal.deltaPoints != null) {\n <span\n class=\"px-1.5 rounded text-[11px] font-bold\"\n [class.bg-green-100]=\"flow.goal.deltaPoints > 0\"\n [class.text-green-700]=\"flow.goal.deltaPoints > 0\"\n [class.bg-red-100]=\"flow.goal.deltaPoints < 0\"\n [class.text-red-700]=\"flow.goal.deltaPoints < 0\"\n [class.bg-gray-100]=\"flow.goal.deltaPoints === 0\"\n [class.text-gray-600]=\"flow.goal.deltaPoints === 0\">\n {{ flow.goal.deltaPoints > 0 ? '+' : '' }}{{ flow.goal.deltaPoints }} pts\n </span>\n }\n </div>\n @if (flow.goal.previousValue != null && flow.goal.newValue != null) {\n <div class=\"mt-1 text-[11px] text-gray-600\">\n <span>{{ flow.goal.previousValue }}</span>\n <span class=\"mx-1\">\u2192</span>\n <span class=\"font-bold text-gray-900\">{{ flow.goal.newValue }}</span>\n <span class=\"text-gray-400\"> / 100</span>\n </div>\n <div class=\"w-full bg-gray-200 rounded h-1 mt-1 overflow-hidden\">\n <div class=\"bg-primary h-1\" [style.width.%]=\"flow.goal.newValue\" [style.background-color]=\"'#3b82f6'\"></div>\n </div>\n }\n @if (flow.intervention?.message) {\n <div class=\"mt-1 text-[11px] italic text-gray-700\">\"{{ flow.intervention.message }}\"</div>\n }\n </div>\n }\n\n @if (flow.challenges?.length) {\n <div class=\"p-2 bg-white rounded border\">\n <strong class=\"text-[11px] uppercase text-gray-600\">Challenges</strong>\n <ul class=\"mt-1 space-y-0.5\">\n @for (ch of flow.challenges; track ch.name) {\n <li class=\"flex items-center gap-1 text-[11px]\">\n <span [class.text-green-600]=\"ch.value\" [class.text-gray-400]=\"!ch.value\">\n {{ ch.value ? '\u2713' : '\u25CB' }}\n </span>\n <span [class.font-semibold]=\"ch.value\">{{ ch.name }}</span>\n </li>\n }\n </ul>\n </div>\n }\n\n @if (flow.moodState?.value) {\n <div class=\"p-2 bg-white rounded border flex items-center justify-between\">\n <strong class=\"text-[11px] uppercase text-gray-600\">Mood</strong>\n <span class=\"text-[11px] font-semibold\">{{ flow.moodState.value }}</span>\n </div>\n }\n\n @if (flow.evaluatedAt) {\n <div class=\"text-[10px] text-gray-400 text-right\">{{ flow.evaluatedAt }}</div>\n }\n </div>\n }\n\n <details class=\"mt-1\">\n <summary class=\"text-[10px] text-gray-500 cursor-pointer\">Raw evaluation</summary>\n <pre class=\"text-[10px]\">{{ msg.evaluation | safeJson }}</pre>\n </details>\n </div>\n }\n\n <!-- Tags -->\n @if (msg.tags && msg.tags.length > 0) {\n <div class=\"mt-2 flex flex-wrap gap-1\">\n <strong>Tags:</strong>\n @for (tag of msg.tags; track tag) {\n <span class=\"bg-gray-200 px-1 rounded text-[10px]\">{{ tag }}</span>\n }\n </div>\n }\n </div>\n </details>\n } @empty {\n <div class=\"p-4 text-center text-gray-400\">No messages in state.</div>\n }\n</div>\n\n<style>\n .role-badge {\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 0.7rem;\n text-transform: uppercase;\n font-weight: bold;\n }\n .role-badge.user { background: #e0f2fe; color: #0369a1; }\n .role-badge.assistant { background: #f0fdf4; color: #15803d; }\n .role-badge.assistantHelper { background: #faf5ff; color: #7e22ce; }\n .role-badge.system { background: #f1f5f9; color: #475569; }\n .border-primary { border-color: var(--p-primary-color, #3b82f6); }\n</style>\n" }]
|
|
6149
|
-
}] });
|
|
6467
|
+
}], propDecorators: { service: [{ type: i0.Input, args: [{ isSignal: true, alias: "service", required: false }] }] } });
|
|
6150
6468
|
|
|
6151
6469
|
class DcAgentCardDetailComponent extends EntityBaseDetailComponent {
|
|
6152
6470
|
constructor() {
|
|
@@ -6186,11 +6504,11 @@ class DcAgentCardDetailComponent extends EntityBaseDetailComponent {
|
|
|
6186
6504
|
this.selectedMotion.set(null);
|
|
6187
6505
|
}
|
|
6188
6506
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcAgentCardDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6189
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DcAgentCardDetailComponent, isStandalone: true, selector: "dc-agent-card-detail", inputs: { entityData: { classPropertyName: "entityData", publicName: "entityData", isSignal: true, isRequired: false, transformFunction: null } }, usesInheritance: true, ngImport: i0, template: "@if (isLoading()) {\n<div class=\"loading-state\">\n <p-progressSpinner strokeWidth=\"4\" />\n</div>\n} @else if (entity()) {\n<div class=\"preview-container\">\n\n <!-- Banner -->\n @if (bannerUrl) {\n <div class=\"preview-banner\">\n <img class=\"banner-image\" [src]=\"bannerUrl\" alt=\"banner\" />\n </div>\n }\n\n <!-- Header: image + name + meta -->\n <div class=\"preview-header\" [class.has-banner]=\"bannerUrl\">\n @if (entity()?.assets?.image?.url) {\n <img class=\"preview-image\" [src]=\"entity()!.assets!.image!.url\" [alt]=\"characterData?.name || entity()?.name\" />\n }\n <div class=\"preview-header-info\">\n <h2 class=\"preview-name\">{{ characterData?.name || entity()?.name }}</h2>\n\n <div class=\"preview-meta-tags\">\n @if (entity()?.lang) {\n <p-tag [value]=\"entity()!.lang!\" severity=\"secondary\" icon=\"pi pi-flag\" />\n }\n @if (entity()?.manageable?.status) {\n <p-tag [value]=\"entity()!.manageable!.status!\" severity=\"info\" icon=\"pi pi-circle-fill\" />\n }\n @if (characterData?.gender) {\n <p-tag [value]=\"characterData!.gender\" severity=\"secondary\" icon=\"pi pi-user\" />\n }\n @if (entity()?.metaApp?.takenCount) {\n <p-tag [value]=\"'Taken: ' + entity()!.metaApp!.takenCount\" severity=\"success\" icon=\"pi pi-users\" />\n }\n @if (learnableInfo?.level) {\n <p-tag [value]=\"'Level ' + learnableInfo.level\" severity=\"warn\" icon=\"pi pi-star\" />\n }\n @if (learnableInfo?.takenCount) {\n <p-tag [value]=\"'Played: ' + learnableInfo.takenCount\" severity=\"secondary\" icon=\"pi pi-play\" />\n }\n </div>\n\n @if (characterData?.tags?.length) {\n <div class=\"preview-tags\">\n @for (tag of characterData!.tags; track tag) {\n <p-tag [value]=\"tag\" severity=\"secondary\" />\n }\n </div>\n }\n </div>\n </div>\n\n <!-- Entity-level description -->\n @if (entity()?.description) {\n <div class=\"preview-entity-description\">\n <p>{{ entity()!.description }}</p>\n </div>\n }\n\n <!-- Motion Videos -->\n @if (motions.length) {\n <div class=\"preview-section motions-top-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-video\"></i> Motion Videos ({{ motions.length }})</h4>\n <div class=\"motions-grid\">\n @for (motion of motions; track $index) {\n @if (motion.metadata?.moodState || motion.metadata?.event) {\n <button class=\"motion-chip\" (click)=\"openMotionVideo(motion)\">\n <i class=\"pi pi-play-circle\"></i>\n <span>{{ motion.metadata?.moodState || motion.metadata?.event }}</span>\n </button>\n }\n }\n </div>\n </div>\n }\n\n <p-divider />\n\n @if (characterData?.hook) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-bookmark\"></i> Hook</h4>\n <p>{{ characterData!.hook }}</p>\n </div>\n }\n\n @if (characterData?.instructions) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-info-circle\"></i> Instructions</h4>\n <p>{{ characterData!.instructions }}</p>\n </div>\n }\n\n @if (characterData?.scenario) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-map\"></i> Scenario</h4>\n <p>{{ characterData!.scenario }}</p>\n </div>\n }\n\n @if (characterData?.creator_notes) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-file\"></i> Creator Notes</h4>\n <p>{{ characterData!.creator_notes }}</p>\n </div>\n }\n\n @if (characterData?.description) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-align-left\"></i> Description</h4>\n <p>{{ characterData!.description }}</p>\n </div>\n }\n\n @if (characterData?.greetings?.length) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-comments\"></i> Greetings</h4>\n @for (greeting of characterData!.greetings; track $index) {\n <p class=\"greeting-item\"><i class=\"pi pi-chevron-right\"></i> {{ greeting }}</p>\n }\n </div>\n }\n\n\n @if (persona) {\n <p-divider />\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-id-card\"></i> Persona</h4>\n <div class=\"persona-grid\">\n @if (persona.identity) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Identity</span>\n <span>{{ persona.identity }}</span>\n </div>\n }\n @if (persona.physical) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Physical</span>\n <span>{{ persona.physical }}</span>\n </div>\n }\n @if (persona.personality) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Personality</span>\n <span>{{ persona.personality }}</span>\n </div>\n }\n @if (persona.communication) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Communication</span>\n <span>{{ persona.communication }}</span>\n </div>\n }\n @if (persona.background) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Background</span>\n <span>{{ persona.background }}</span>\n </div>\n }\n @if (persona.psychology) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Psychology</span>\n <span>{{ persona.psychology }}</span>\n </div>\n }\n @if (persona.capabilities) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Capabilities</span>\n <span>{{ persona.capabilities }}</span>\n </div>\n }\n @if (persona.social) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Social</span>\n <span>{{ persona.social }}</span>\n </div>\n }\n @if (persona.preferences) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Preferences</span>\n <span>{{ persona.preferences }}</span>\n </div>\n }\n @if (persona.situation) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Situation</span>\n <span>{{ persona.situation }}</span>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Conversation Settings -->\n @if (entity()?.conversationSettings) {\n <p-divider />\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Conversation Settings</h4>\n <div class=\"settings-chips\">\n @if (entity()?.conversationSettings?.conversationType) {\n <p-tag [value]=\"entity()!.conversationSettings!.conversationType!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.conversationSettings?.textEngine) {\n <p-tag [value]=\"entity()!.conversationSettings!.textEngine!\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n @if (entity()?.conversationSettings?.autoStart) {\n <p-tag value=\"Auto Start\" severity=\"success\" icon=\"pi pi-play\" />\n }\n @if (entity()?.conversationSettings?.userMustStart) {\n <p-tag value=\"User Starts\" severity=\"warn\" icon=\"pi pi-user\" />\n }\n </div>\n </div>\n }\n\n <!-- Voices -->\n @if (entity()?.conversationSettings?.mainVoice?.voice) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-volume-up\"></i> Voices</h4>\n <div class=\"voice-info\">\n <p><span class=\"field-label\">Main:</span> {{ entity()!.conversationSettings!.mainVoice!.voice }}</p>\n @if (entity()?.conversationSettings?.secondaryVoice?.voice) {\n <p><span class=\"field-label\">Secondary:</span> {{ entity()!.conversationSettings!.secondaryVoice!.voice }}</p>\n }\n </div>\n </div>\n }\n\n</div>\n}\n\n@if (entity()?.manageable) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Manageable</h4>\n <div class=\"settings-chips\">\n @if (entity()?.manageable?.status) {\n <p-tag [value]=\"entity()!.manageable!.status!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.manageable?.isPublic) {\n <p-tag [value]=\"'Public'\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n </div>\n</div>\n}\n\n@if (entity()?.learnable) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Learnable</h4>\n <div class=\"settings-chips\">\n @if (entity()?.learnable?.level) {\n <p-tag [value]=\"'Level ' + entity()!.learnable!.level!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.learnable?.tags) {\n <p-tag [value]=\"'Public'\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n </div>\n</div>\n}\n\n@if(entity()?.conversationFlow) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-tags\"></i> Conversation Flow</h4>\n\n @if (entity()?.conversationFlow?.moodState) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-face-smile\"></i> Mood State</h5>\n <div class=\"settings-chips\">\n @if (entity()!.conversationFlow!.moodState!.enabled) {\n <p-tag value=\"Enabled\" severity=\"success\" icon=\"pi pi-check\" />\n } @else {\n <p-tag value=\"Disabled\" severity=\"danger\" icon=\"pi pi-times\" />\n }\n @if (entity()!.conversationFlow!.moodState!.useAssetStatesOnly) {\n <p-tag value=\"Asset States Only\" severity=\"warn\" icon=\"pi pi-images\" />\n }\n </div>\n @if (entity()!.conversationFlow!.moodState!.detectableStates?.length) {\n <div class=\"settings-chips\" style=\"margin-top: 0.5rem;\">\n @for (state of entity()!.conversationFlow!.moodState!.detectableStates; track $index) {\n <p-tag [value]=\"state\" severity=\"info\" icon=\"pi pi-tag\" />\n }\n </div>\n }\n </div>\n }\n\n @if (entity()?.conversationFlow?.dynamicConditions?.length) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-sliders-v\"></i> Dynamic Conditions</h5>\n @for (condition of entity()!.conversationFlow!.dynamicConditions!; track $index) {\n <div class=\"persona-field\" style=\"margin-bottom: 0.5rem;\">\n <div class=\"settings-chips\">\n <p-tag [value]=\"condition.what\" severity=\"info\" icon=\"pi pi-filter\" />\n <p-tag [value]=\"condition.when\" severity=\"secondary\" />\n <p-tag [value]=\"condition.value + ''\" severity=\"warn\" />\n </div>\n @if (condition.do?.length) {\n <div class=\"settings-chips\" style=\"margin-top: 0.25rem;\">\n @for (action of condition.do!; track $index) {\n <p-tag [value]=\"action.actionType\" severity=\"contrast\" icon=\"pi pi-bolt\" />\n @if (action.prompt) {\n <span class=\"field-label\" style=\"font-size: 0.75rem;\">{{ action.prompt | slice:0:60 }}\u2026</span>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n @if (entity()?.conversationFlow?.triggerTasks) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-bolt\"></i> Trigger Tasks</h5>\n @for (entry of objectEntries(entity()!.conversationFlow!.triggerTasks!); track $index) {\n <div class=\"persona-field\" style=\"margin-bottom: 0.5rem;\">\n <span class=\"field-label\">{{ entry[0] }}</span>\n <div class=\"settings-chips\" style=\"margin-top: 0.25rem;\">\n @if (entry[1].enabled) {\n <p-tag value=\"Enabled\" severity=\"success\" icon=\"pi pi-check\" />\n } @else {\n <p-tag value=\"Disabled\" severity=\"secondary\" icon=\"pi pi-times\" />\n }\n @if (entry[1].disableFeature) {\n <p-tag value=\"Feature Disabled\" severity=\"danger\" icon=\"pi pi-ban\" />\n }\n </div>\n @if (entry[1].task) {\n <p style=\"font-size: 0.8rem; margin: 0.25rem 0 0;\">{{ entry[1].task | slice:0:120 }}\u2026</p>\n }\n </div>\n }\n </div>\n }\n</div>\n}\n\n<!-- Video Dialog -->\n<p-dialog\n [visible]=\"videoDialogVisible()\"\n (visibleChange)=\"$event ? null : closeMotionVideo()\"\n [header]=\"selectedMotion()?.metadata?.moodState || selectedMotion()?.metadata?.event || 'Video'\"\n [modal]=\"true\"\n [dismissableMask]=\"true\"\n [closable]=\"true\"\n styleClass=\"video-dialog\"\n [style]=\"{ width: '560px' }\">\n\n @if (selectedMotion()?.url) {\n <video\n class=\"motion-video\"\n [src]=\"selectedMotion().url\"\n controls\n autoplay>\n </video>\n }\n</p-dialog>\n", styles: [".preview-container{padding:.5rem}.preview-banner{width:100%;height:210px;overflow:hidden;border-radius:8px;margin-bottom:0}.banner-image{width:100%;height:100%;object-fit:cover;display:block}.preview-header{display:flex;gap:1.5rem;align-items:flex-start;padding:1rem 0 .5rem}.preview-header.has-banner{padding-top:.75rem}.preview-image{width:120px;height:170px;object-fit:cover;border-radius:8px;flex-shrink:0;box-shadow:0 2px 8px #00000026}.preview-header-info{display:flex;flex-direction:column;gap:.5rem}.preview-name{margin:0 0 .25rem;font-size:1.5rem;font-weight:700}.preview-meta-tags{display:flex;flex-wrap:wrap;gap:.35rem}.preview-tags{display:flex;flex-wrap:wrap;gap:.25rem;margin-top:.25rem}.preview-entity-description{color:var(--p-text-muted-color);font-size:.9rem;margin:.5rem 0}.preview-entity-description p{margin:0}.preview-section{margin-bottom:1rem}.section-title{margin:0 0 .5rem;font-size:.95rem;font-weight:600;color:var(--p-text-muted-color);display:flex;align-items:center;gap:.4rem}.greeting-item{margin:.25rem 0;padding-left:.5rem;border-left:2px solid var(--p-primary-color)}.first-message{background:var(--p-surface-100);border-radius:6px;padding:.75rem;font-style:italic}.persona-grid{display:flex;flex-direction:column;gap:.5rem}.persona-field{display:flex;flex-direction:column;gap:.15rem}.field-label{font-size:.8rem;font-weight:600;color:var(--p-text-muted-color);text-transform:uppercase;letter-spacing:.05em}.motions-top-section{margin-top:.75rem;margin-bottom:0}.motions-grid{display:flex;flex-wrap:wrap;gap:.4rem}.motion-chip{display:inline-flex;align-items:center;gap:.35rem;padding:.3rem .65rem;border-radius:20px;border:1px solid var(--p-surface-300);background:var(--p-surface-100);color:var(--p-text-color);font-size:.8rem;font-weight:500;cursor:pointer;transition:background .15s,border-color .15s,transform .1s;text-transform:capitalize}.motion-chip i{font-size:.85rem;color:var(--p-primary-color)}.motion-chip:hover{background:var(--p-primary-50, #f0f4ff);border-color:var(--p-primary-color);transform:translateY(-1px)}.motion-chip:active{transform:translateY(0)}.motion-video{width:100%;border-radius:6px;display:block}.settings-chips{display:flex;flex-wrap:wrap;gap:.4rem}.voice-info{display:flex;flex-direction:column;gap:.25rem}.loading-state{display:flex;justify-content:center;padding:2rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: CardModule }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i1$4.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "ngmodule", type: DividerModule }, { kind: "component", type: i2$3.Divider, selector: "p-divider", inputs: ["styleClass", "layout", "type", "align"] }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i2$4.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i5$1.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "pipe", type: i1.SlicePipe, name: "slice" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6507
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DcAgentCardDetailComponent, isStandalone: true, selector: "dc-agent-card-detail", inputs: { entityData: { classPropertyName: "entityData", publicName: "entityData", isSignal: true, isRequired: false, transformFunction: null } }, usesInheritance: true, ngImport: i0, template: "@if (isLoading()) {\n<div class=\"loading-state\">\n <p-progressSpinner strokeWidth=\"4\" />\n</div>\n} @else if (entity()) {\n<div class=\"preview-container\">\n\n <!-- Banner -->\n @if (bannerUrl) {\n <div class=\"preview-banner\">\n <img class=\"banner-image\" [src]=\"bannerUrl\" alt=\"banner\" />\n </div>\n }\n\n <!-- Header: image + name + meta -->\n <div class=\"preview-header\" [class.has-banner]=\"bannerUrl\">\n @if (entity()?.assets?.image?.url) {\n <img class=\"preview-image\" [src]=\"entity()!.assets!.image!.url\" [alt]=\"characterData?.name || entity()?.name\" />\n }\n <div class=\"preview-header-info\">\n <h2 class=\"preview-name\">{{ characterData?.name || entity()?.name }}</h2>\n\n <div class=\"preview-meta-tags\">\n @if (entity()?.lang) {\n <p-tag [value]=\"entity()!.lang!\" severity=\"secondary\" icon=\"pi pi-flag\" />\n }\n @if (entity()?.manageable?.status) {\n <p-tag [value]=\"entity()!.manageable!.status!\" severity=\"info\" icon=\"pi pi-circle-fill\" />\n }\n @if (characterData?.gender) {\n <p-tag [value]=\"characterData!.gender\" severity=\"secondary\" icon=\"pi pi-user\" />\n }\n @if (entity()?.metaApp?.takenCount) {\n <p-tag [value]=\"'Taken: ' + entity()!.metaApp!.takenCount\" severity=\"success\" icon=\"pi pi-users\" />\n }\n @if (learnableInfo?.level) {\n <p-tag [value]=\"'Level ' + learnableInfo.level\" severity=\"warn\" icon=\"pi pi-star\" />\n }\n @if (learnableInfo?.takenCount) {\n <p-tag [value]=\"'Played: ' + learnableInfo.takenCount\" severity=\"secondary\" icon=\"pi pi-play\" />\n }\n </div>\n\n @if (characterData?.tags?.length) {\n <div class=\"preview-tags\">\n @for (tag of characterData!.tags; track tag) {\n <p-tag [value]=\"tag\" severity=\"secondary\" />\n }\n </div>\n }\n </div>\n </div>\n\n <!-- Entity-level description -->\n @if (entity()?.description) {\n <div class=\"preview-entity-description\">\n <p>{{ entity()!.description }}</p>\n </div>\n }\n\n <!-- Motion Videos -->\n @if (motions.length) {\n <div class=\"preview-section motions-top-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-video\"></i> Motion Videos ({{ motions.length }})</h4>\n <div class=\"motions-grid\">\n @for (motion of motions; track $index) {\n @if (motion.metadata?.moodState || motion.metadata?.event) {\n <button class=\"motion-chip\" (click)=\"openMotionVideo(motion)\">\n <i class=\"pi pi-play-circle\"></i>\n <span>{{ motion.metadata?.moodState || motion.metadata?.event }}</span>\n </button>\n }\n }\n </div>\n </div>\n }\n\n <p-divider />\n\n @if (characterData?.hook) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-bookmark\"></i> Hook</h4>\n <p>{{ characterData!.hook }}</p>\n </div>\n }\n\n @if (characterData?.instructions) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-info-circle\"></i> Instructions</h4>\n <p>{{ characterData!.instructions }}</p>\n </div>\n }\n\n @if (characterData?.scenario) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-map\"></i> Scenario</h4>\n <p>{{ characterData!.scenario }}</p>\n </div>\n }\n\n @if (characterData?.creator_notes) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-file\"></i> Creator Notes</h4>\n <p>{{ characterData!.creator_notes }}</p>\n </div>\n }\n\n @if (characterData?.description) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-align-left\"></i> Description</h4>\n <p>{{ characterData!.description }}</p>\n </div>\n }\n\n @if (characterData?.greetings?.length) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-comments\"></i> Greetings</h4>\n @for (greeting of characterData!.greetings; track $index) {\n <p class=\"greeting-item\"><i class=\"pi pi-chevron-right\"></i> {{ greeting }}</p>\n }\n </div>\n }\n\n\n @if (persona) {\n <p-divider />\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-id-card\"></i> Persona</h4>\n <div class=\"persona-grid\">\n @if (persona.identity) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Identity</span>\n <span>{{ persona.identity }}</span>\n </div>\n }\n @if (persona.physical) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Physical</span>\n <span>{{ persona.physical }}</span>\n </div>\n }\n @if (persona.personality) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Personality</span>\n <span>{{ persona.personality }}</span>\n </div>\n }\n @if (persona.communication) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Communication</span>\n <span>{{ persona.communication }}</span>\n </div>\n }\n @if (persona.background) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Background</span>\n <span>{{ persona.background }}</span>\n </div>\n }\n @if (persona.psychology) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Psychology</span>\n <span>{{ persona.psychology }}</span>\n </div>\n }\n @if (persona.capabilities) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Capabilities</span>\n <span>{{ persona.capabilities }}</span>\n </div>\n }\n @if (persona.social) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Social</span>\n <span>{{ persona.social }}</span>\n </div>\n }\n @if (persona.preferences) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Preferences</span>\n <span>{{ persona.preferences }}</span>\n </div>\n }\n @if (persona.situation) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Situation</span>\n <span>{{ persona.situation }}</span>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Conversation Settings -->\n @if (entity()?.conversationSettings) {\n <p-divider />\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Conversation Settings</h4>\n <div class=\"settings-chips\">\n @if (entity()?.conversationSettings?.conversationType) {\n <p-tag [value]=\"entity()!.conversationSettings!.conversationType!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.conversationSettings?.textEngine) {\n <p-tag [value]=\"entity()!.conversationSettings!.textEngine!\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n @if (entity()?.conversationSettings?.autoStart) {\n <p-tag value=\"Auto Start\" severity=\"success\" icon=\"pi pi-play\" />\n }\n @if (entity()?.conversationSettings?.userMustStart) {\n <p-tag value=\"User Starts\" severity=\"warn\" icon=\"pi pi-user\" />\n }\n </div>\n </div>\n }\n\n <!-- Voices -->\n @if (entity()?.voice?.main?.voice || entity()?.conversationSettings?.mainVoice?.voice) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-volume-up\"></i> Voices</h4>\n <div class=\"voice-info\">\n <p><span class=\"field-label\">Main:</span> {{ entity()?.voice?.main?.voice || entity()?.conversationSettings?.mainVoice?.voice }}</p>\n @if (entity()?.voice?.secondary?.voice || entity()?.conversationSettings?.secondaryVoice?.voice) {\n <p><span class=\"field-label\">Secondary:</span> {{ entity()?.voice?.secondary?.voice || entity()?.conversationSettings?.secondaryVoice?.voice }}</p>\n }\n </div>\n </div>\n }\n\n</div>\n}\n\n@if (entity()?.manageable) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Manageable</h4>\n <div class=\"settings-chips\">\n @if (entity()?.manageable?.status) {\n <p-tag [value]=\"entity()!.manageable!.status!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.manageable?.isPublic) {\n <p-tag [value]=\"'Public'\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n </div>\n</div>\n}\n\n@if (entity()?.learnable) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Learnable</h4>\n <div class=\"settings-chips\">\n @if (entity()?.learnable?.level) {\n <p-tag [value]=\"'Level ' + entity()!.learnable!.level!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.learnable?.tags) {\n <p-tag [value]=\"'Public'\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n </div>\n</div>\n}\n\n@if(entity()?.conversationFlow) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-tags\"></i> Conversation Flow</h4>\n\n @if (entity()?.conversationFlow?.moodState) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-face-smile\"></i> Mood State</h5>\n <div class=\"settings-chips\">\n @if (entity()!.conversationFlow!.moodState!.enabled) {\n <p-tag value=\"Enabled\" severity=\"success\" icon=\"pi pi-check\" />\n } @else {\n <p-tag value=\"Disabled\" severity=\"danger\" icon=\"pi pi-times\" />\n }\n @if (entity()!.conversationFlow!.moodState!.useAssetStatesOnly) {\n <p-tag value=\"Asset States Only\" severity=\"warn\" icon=\"pi pi-images\" />\n }\n </div>\n @if (entity()!.conversationFlow!.moodState!.detectableStates?.length) {\n <div class=\"settings-chips\" style=\"margin-top: 0.5rem;\">\n @for (state of entity()!.conversationFlow!.moodState!.detectableStates; track $index) {\n <p-tag [value]=\"state\" severity=\"info\" icon=\"pi pi-tag\" />\n }\n </div>\n }\n </div>\n }\n\n @if (entity()?.conversationFlow?.dynamicConditions?.length) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-sliders-v\"></i> Dynamic Conditions</h5>\n @for (condition of entity()!.conversationFlow!.dynamicConditions!; track $index) {\n <div class=\"persona-field\" style=\"margin-bottom: 0.5rem;\">\n <div class=\"settings-chips\">\n <p-tag [value]=\"condition.what\" severity=\"info\" icon=\"pi pi-filter\" />\n <p-tag [value]=\"condition.when\" severity=\"secondary\" />\n <p-tag [value]=\"condition.value + ''\" severity=\"warn\" />\n </div>\n @if (condition.do?.length) {\n <div class=\"settings-chips\" style=\"margin-top: 0.25rem;\">\n @for (action of condition.do!; track $index) {\n <p-tag [value]=\"action.actionType\" severity=\"contrast\" icon=\"pi pi-bolt\" />\n @if (action.prompt) {\n <span class=\"field-label\" style=\"font-size: 0.75rem;\">{{ action.prompt | slice:0:60 }}\u2026</span>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n @if (entity()?.conversationFlow?.triggerTasks) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-bolt\"></i> Trigger Tasks</h5>\n @for (entry of objectEntries(entity()!.conversationFlow!.triggerTasks!); track $index) {\n <div class=\"persona-field\" style=\"margin-bottom: 0.5rem;\">\n <span class=\"field-label\">{{ entry[0] }}</span>\n <div class=\"settings-chips\" style=\"margin-top: 0.25rem;\">\n @if (entry[1].enabled) {\n <p-tag value=\"Enabled\" severity=\"success\" icon=\"pi pi-check\" />\n } @else {\n <p-tag value=\"Disabled\" severity=\"secondary\" icon=\"pi pi-times\" />\n }\n @if (entry[1].disableFeature) {\n <p-tag value=\"Feature Disabled\" severity=\"danger\" icon=\"pi pi-ban\" />\n }\n </div>\n @if (entry[1].task) {\n <p style=\"font-size: 0.8rem; margin: 0.25rem 0 0;\">{{ entry[1].task | slice:0:120 }}\u2026</p>\n }\n </div>\n }\n </div>\n }\n</div>\n}\n\n<!-- Video Dialog -->\n<p-dialog\n [visible]=\"videoDialogVisible()\"\n (visibleChange)=\"$event ? null : closeMotionVideo()\"\n [header]=\"selectedMotion()?.metadata?.moodState || selectedMotion()?.metadata?.event || 'Video'\"\n [modal]=\"true\"\n [dismissableMask]=\"true\"\n [closable]=\"true\"\n styleClass=\"video-dialog\"\n [style]=\"{ width: '560px' }\">\n\n @if (selectedMotion()?.url) {\n <video\n class=\"motion-video\"\n [src]=\"selectedMotion().url\"\n controls\n autoplay>\n </video>\n }\n</p-dialog>\n", styles: [".preview-container{padding:.5rem}.preview-banner{width:100%;height:210px;overflow:hidden;border-radius:8px;margin-bottom:0}.banner-image{width:100%;height:100%;object-fit:cover;display:block}.preview-header{display:flex;gap:1.5rem;align-items:flex-start;padding:1rem 0 .5rem}.preview-header.has-banner{padding-top:.75rem}.preview-image{width:120px;height:170px;object-fit:cover;border-radius:8px;flex-shrink:0;box-shadow:0 2px 8px #00000026}.preview-header-info{display:flex;flex-direction:column;gap:.5rem}.preview-name{margin:0 0 .25rem;font-size:1.5rem;font-weight:700}.preview-meta-tags{display:flex;flex-wrap:wrap;gap:.35rem}.preview-tags{display:flex;flex-wrap:wrap;gap:.25rem;margin-top:.25rem}.preview-entity-description{color:var(--p-text-muted-color);font-size:.9rem;margin:.5rem 0}.preview-entity-description p{margin:0}.preview-section{margin-bottom:1rem}.section-title{margin:0 0 .5rem;font-size:.95rem;font-weight:600;color:var(--p-text-muted-color);display:flex;align-items:center;gap:.4rem}.greeting-item{margin:.25rem 0;padding-left:.5rem;border-left:2px solid var(--p-primary-color)}.first-message{background:var(--p-surface-100);border-radius:6px;padding:.75rem;font-style:italic}.persona-grid{display:flex;flex-direction:column;gap:.5rem}.persona-field{display:flex;flex-direction:column;gap:.15rem}.field-label{font-size:.8rem;font-weight:600;color:var(--p-text-muted-color);text-transform:uppercase;letter-spacing:.05em}.motions-top-section{margin-top:.75rem;margin-bottom:0}.motions-grid{display:flex;flex-wrap:wrap;gap:.4rem}.motion-chip{display:inline-flex;align-items:center;gap:.35rem;padding:.3rem .65rem;border-radius:20px;border:1px solid var(--p-surface-300);background:var(--p-surface-100);color:var(--p-text-color);font-size:.8rem;font-weight:500;cursor:pointer;transition:background .15s,border-color .15s,transform .1s;text-transform:capitalize}.motion-chip i{font-size:.85rem;color:var(--p-primary-color)}.motion-chip:hover{background:var(--p-primary-50, #f0f4ff);border-color:var(--p-primary-color);transform:translateY(-1px)}.motion-chip:active{transform:translateY(0)}.motion-video{width:100%;border-radius:6px;display:block}.settings-chips{display:flex;flex-wrap:wrap;gap:.4rem}.voice-info{display:flex;flex-direction:column;gap:.25rem}.loading-state{display:flex;justify-content:center;padding:2rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: CardModule }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i1$4.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "ngmodule", type: DividerModule }, { kind: "component", type: i2$3.Divider, selector: "p-divider", inputs: ["styleClass", "layout", "type", "align"] }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i2$4.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i5$1.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "pipe", type: i1.SlicePipe, name: "slice" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6190
6508
|
}
|
|
6191
6509
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcAgentCardDetailComponent, decorators: [{
|
|
6192
6510
|
type: Component,
|
|
6193
|
-
args: [{ selector: 'dc-agent-card-detail', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, CardModule, TagModule, DividerModule, ProgressSpinnerModule, DialogModule, ButtonModule], template: "@if (isLoading()) {\n<div class=\"loading-state\">\n <p-progressSpinner strokeWidth=\"4\" />\n</div>\n} @else if (entity()) {\n<div class=\"preview-container\">\n\n <!-- Banner -->\n @if (bannerUrl) {\n <div class=\"preview-banner\">\n <img class=\"banner-image\" [src]=\"bannerUrl\" alt=\"banner\" />\n </div>\n }\n\n <!-- Header: image + name + meta -->\n <div class=\"preview-header\" [class.has-banner]=\"bannerUrl\">\n @if (entity()?.assets?.image?.url) {\n <img class=\"preview-image\" [src]=\"entity()!.assets!.image!.url\" [alt]=\"characterData?.name || entity()?.name\" />\n }\n <div class=\"preview-header-info\">\n <h2 class=\"preview-name\">{{ characterData?.name || entity()?.name }}</h2>\n\n <div class=\"preview-meta-tags\">\n @if (entity()?.lang) {\n <p-tag [value]=\"entity()!.lang!\" severity=\"secondary\" icon=\"pi pi-flag\" />\n }\n @if (entity()?.manageable?.status) {\n <p-tag [value]=\"entity()!.manageable!.status!\" severity=\"info\" icon=\"pi pi-circle-fill\" />\n }\n @if (characterData?.gender) {\n <p-tag [value]=\"characterData!.gender\" severity=\"secondary\" icon=\"pi pi-user\" />\n }\n @if (entity()?.metaApp?.takenCount) {\n <p-tag [value]=\"'Taken: ' + entity()!.metaApp!.takenCount\" severity=\"success\" icon=\"pi pi-users\" />\n }\n @if (learnableInfo?.level) {\n <p-tag [value]=\"'Level ' + learnableInfo.level\" severity=\"warn\" icon=\"pi pi-star\" />\n }\n @if (learnableInfo?.takenCount) {\n <p-tag [value]=\"'Played: ' + learnableInfo.takenCount\" severity=\"secondary\" icon=\"pi pi-play\" />\n }\n </div>\n\n @if (characterData?.tags?.length) {\n <div class=\"preview-tags\">\n @for (tag of characterData!.tags; track tag) {\n <p-tag [value]=\"tag\" severity=\"secondary\" />\n }\n </div>\n }\n </div>\n </div>\n\n <!-- Entity-level description -->\n @if (entity()?.description) {\n <div class=\"preview-entity-description\">\n <p>{{ entity()!.description }}</p>\n </div>\n }\n\n <!-- Motion Videos -->\n @if (motions.length) {\n <div class=\"preview-section motions-top-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-video\"></i> Motion Videos ({{ motions.length }})</h4>\n <div class=\"motions-grid\">\n @for (motion of motions; track $index) {\n @if (motion.metadata?.moodState || motion.metadata?.event) {\n <button class=\"motion-chip\" (click)=\"openMotionVideo(motion)\">\n <i class=\"pi pi-play-circle\"></i>\n <span>{{ motion.metadata?.moodState || motion.metadata?.event }}</span>\n </button>\n }\n }\n </div>\n </div>\n }\n\n <p-divider />\n\n @if (characterData?.hook) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-bookmark\"></i> Hook</h4>\n <p>{{ characterData!.hook }}</p>\n </div>\n }\n\n @if (characterData?.instructions) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-info-circle\"></i> Instructions</h4>\n <p>{{ characterData!.instructions }}</p>\n </div>\n }\n\n @if (characterData?.scenario) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-map\"></i> Scenario</h4>\n <p>{{ characterData!.scenario }}</p>\n </div>\n }\n\n @if (characterData?.creator_notes) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-file\"></i> Creator Notes</h4>\n <p>{{ characterData!.creator_notes }}</p>\n </div>\n }\n\n @if (characterData?.description) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-align-left\"></i> Description</h4>\n <p>{{ characterData!.description }}</p>\n </div>\n }\n\n @if (characterData?.greetings?.length) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-comments\"></i> Greetings</h4>\n @for (greeting of characterData!.greetings; track $index) {\n <p class=\"greeting-item\"><i class=\"pi pi-chevron-right\"></i> {{ greeting }}</p>\n }\n </div>\n }\n\n\n @if (persona) {\n <p-divider />\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-id-card\"></i> Persona</h4>\n <div class=\"persona-grid\">\n @if (persona.identity) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Identity</span>\n <span>{{ persona.identity }}</span>\n </div>\n }\n @if (persona.physical) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Physical</span>\n <span>{{ persona.physical }}</span>\n </div>\n }\n @if (persona.personality) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Personality</span>\n <span>{{ persona.personality }}</span>\n </div>\n }\n @if (persona.communication) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Communication</span>\n <span>{{ persona.communication }}</span>\n </div>\n }\n @if (persona.background) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Background</span>\n <span>{{ persona.background }}</span>\n </div>\n }\n @if (persona.psychology) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Psychology</span>\n <span>{{ persona.psychology }}</span>\n </div>\n }\n @if (persona.capabilities) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Capabilities</span>\n <span>{{ persona.capabilities }}</span>\n </div>\n }\n @if (persona.social) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Social</span>\n <span>{{ persona.social }}</span>\n </div>\n }\n @if (persona.preferences) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Preferences</span>\n <span>{{ persona.preferences }}</span>\n </div>\n }\n @if (persona.situation) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Situation</span>\n <span>{{ persona.situation }}</span>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Conversation Settings -->\n @if (entity()?.conversationSettings) {\n <p-divider />\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Conversation Settings</h4>\n <div class=\"settings-chips\">\n @if (entity()?.conversationSettings?.conversationType) {\n <p-tag [value]=\"entity()!.conversationSettings!.conversationType!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.conversationSettings?.textEngine) {\n <p-tag [value]=\"entity()!.conversationSettings!.textEngine!\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n @if (entity()?.conversationSettings?.autoStart) {\n <p-tag value=\"Auto Start\" severity=\"success\" icon=\"pi pi-play\" />\n }\n @if (entity()?.conversationSettings?.userMustStart) {\n <p-tag value=\"User Starts\" severity=\"warn\" icon=\"pi pi-user\" />\n }\n </div>\n </div>\n }\n\n <!-- Voices -->\n @if (entity()?.conversationSettings?.mainVoice?.voice) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-volume-up\"></i> Voices</h4>\n <div class=\"voice-info\">\n <p><span class=\"field-label\">Main:</span> {{ entity()!.conversationSettings!.mainVoice!.voice }}</p>\n @if (entity()?.conversationSettings?.secondaryVoice?.voice) {\n <p><span class=\"field-label\">Secondary:</span> {{ entity()!.conversationSettings!.secondaryVoice!.voice }}</p>\n }\n </div>\n </div>\n }\n\n</div>\n}\n\n@if (entity()?.manageable) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Manageable</h4>\n <div class=\"settings-chips\">\n @if (entity()?.manageable?.status) {\n <p-tag [value]=\"entity()!.manageable!.status!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.manageable?.isPublic) {\n <p-tag [value]=\"'Public'\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n </div>\n</div>\n}\n\n@if (entity()?.learnable) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Learnable</h4>\n <div class=\"settings-chips\">\n @if (entity()?.learnable?.level) {\n <p-tag [value]=\"'Level ' + entity()!.learnable!.level!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.learnable?.tags) {\n <p-tag [value]=\"'Public'\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n </div>\n</div>\n}\n\n@if(entity()?.conversationFlow) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-tags\"></i> Conversation Flow</h4>\n\n @if (entity()?.conversationFlow?.moodState) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-face-smile\"></i> Mood State</h5>\n <div class=\"settings-chips\">\n @if (entity()!.conversationFlow!.moodState!.enabled) {\n <p-tag value=\"Enabled\" severity=\"success\" icon=\"pi pi-check\" />\n } @else {\n <p-tag value=\"Disabled\" severity=\"danger\" icon=\"pi pi-times\" />\n }\n @if (entity()!.conversationFlow!.moodState!.useAssetStatesOnly) {\n <p-tag value=\"Asset States Only\" severity=\"warn\" icon=\"pi pi-images\" />\n }\n </div>\n @if (entity()!.conversationFlow!.moodState!.detectableStates?.length) {\n <div class=\"settings-chips\" style=\"margin-top: 0.5rem;\">\n @for (state of entity()!.conversationFlow!.moodState!.detectableStates; track $index) {\n <p-tag [value]=\"state\" severity=\"info\" icon=\"pi pi-tag\" />\n }\n </div>\n }\n </div>\n }\n\n @if (entity()?.conversationFlow?.dynamicConditions?.length) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-sliders-v\"></i> Dynamic Conditions</h5>\n @for (condition of entity()!.conversationFlow!.dynamicConditions!; track $index) {\n <div class=\"persona-field\" style=\"margin-bottom: 0.5rem;\">\n <div class=\"settings-chips\">\n <p-tag [value]=\"condition.what\" severity=\"info\" icon=\"pi pi-filter\" />\n <p-tag [value]=\"condition.when\" severity=\"secondary\" />\n <p-tag [value]=\"condition.value + ''\" severity=\"warn\" />\n </div>\n @if (condition.do?.length) {\n <div class=\"settings-chips\" style=\"margin-top: 0.25rem;\">\n @for (action of condition.do!; track $index) {\n <p-tag [value]=\"action.actionType\" severity=\"contrast\" icon=\"pi pi-bolt\" />\n @if (action.prompt) {\n <span class=\"field-label\" style=\"font-size: 0.75rem;\">{{ action.prompt | slice:0:60 }}\u2026</span>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n @if (entity()?.conversationFlow?.triggerTasks) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-bolt\"></i> Trigger Tasks</h5>\n @for (entry of objectEntries(entity()!.conversationFlow!.triggerTasks!); track $index) {\n <div class=\"persona-field\" style=\"margin-bottom: 0.5rem;\">\n <span class=\"field-label\">{{ entry[0] }}</span>\n <div class=\"settings-chips\" style=\"margin-top: 0.25rem;\">\n @if (entry[1].enabled) {\n <p-tag value=\"Enabled\" severity=\"success\" icon=\"pi pi-check\" />\n } @else {\n <p-tag value=\"Disabled\" severity=\"secondary\" icon=\"pi pi-times\" />\n }\n @if (entry[1].disableFeature) {\n <p-tag value=\"Feature Disabled\" severity=\"danger\" icon=\"pi pi-ban\" />\n }\n </div>\n @if (entry[1].task) {\n <p style=\"font-size: 0.8rem; margin: 0.25rem 0 0;\">{{ entry[1].task | slice:0:120 }}\u2026</p>\n }\n </div>\n }\n </div>\n }\n</div>\n}\n\n<!-- Video Dialog -->\n<p-dialog\n [visible]=\"videoDialogVisible()\"\n (visibleChange)=\"$event ? null : closeMotionVideo()\"\n [header]=\"selectedMotion()?.metadata?.moodState || selectedMotion()?.metadata?.event || 'Video'\"\n [modal]=\"true\"\n [dismissableMask]=\"true\"\n [closable]=\"true\"\n styleClass=\"video-dialog\"\n [style]=\"{ width: '560px' }\">\n\n @if (selectedMotion()?.url) {\n <video\n class=\"motion-video\"\n [src]=\"selectedMotion().url\"\n controls\n autoplay>\n </video>\n }\n</p-dialog>\n", styles: [".preview-container{padding:.5rem}.preview-banner{width:100%;height:210px;overflow:hidden;border-radius:8px;margin-bottom:0}.banner-image{width:100%;height:100%;object-fit:cover;display:block}.preview-header{display:flex;gap:1.5rem;align-items:flex-start;padding:1rem 0 .5rem}.preview-header.has-banner{padding-top:.75rem}.preview-image{width:120px;height:170px;object-fit:cover;border-radius:8px;flex-shrink:0;box-shadow:0 2px 8px #00000026}.preview-header-info{display:flex;flex-direction:column;gap:.5rem}.preview-name{margin:0 0 .25rem;font-size:1.5rem;font-weight:700}.preview-meta-tags{display:flex;flex-wrap:wrap;gap:.35rem}.preview-tags{display:flex;flex-wrap:wrap;gap:.25rem;margin-top:.25rem}.preview-entity-description{color:var(--p-text-muted-color);font-size:.9rem;margin:.5rem 0}.preview-entity-description p{margin:0}.preview-section{margin-bottom:1rem}.section-title{margin:0 0 .5rem;font-size:.95rem;font-weight:600;color:var(--p-text-muted-color);display:flex;align-items:center;gap:.4rem}.greeting-item{margin:.25rem 0;padding-left:.5rem;border-left:2px solid var(--p-primary-color)}.first-message{background:var(--p-surface-100);border-radius:6px;padding:.75rem;font-style:italic}.persona-grid{display:flex;flex-direction:column;gap:.5rem}.persona-field{display:flex;flex-direction:column;gap:.15rem}.field-label{font-size:.8rem;font-weight:600;color:var(--p-text-muted-color);text-transform:uppercase;letter-spacing:.05em}.motions-top-section{margin-top:.75rem;margin-bottom:0}.motions-grid{display:flex;flex-wrap:wrap;gap:.4rem}.motion-chip{display:inline-flex;align-items:center;gap:.35rem;padding:.3rem .65rem;border-radius:20px;border:1px solid var(--p-surface-300);background:var(--p-surface-100);color:var(--p-text-color);font-size:.8rem;font-weight:500;cursor:pointer;transition:background .15s,border-color .15s,transform .1s;text-transform:capitalize}.motion-chip i{font-size:.85rem;color:var(--p-primary-color)}.motion-chip:hover{background:var(--p-primary-50, #f0f4ff);border-color:var(--p-primary-color);transform:translateY(-1px)}.motion-chip:active{transform:translateY(0)}.motion-video{width:100%;border-radius:6px;display:block}.settings-chips{display:flex;flex-wrap:wrap;gap:.4rem}.voice-info{display:flex;flex-direction:column;gap:.25rem}.loading-state{display:flex;justify-content:center;padding:2rem}\n"] }]
|
|
6511
|
+
args: [{ selector: 'dc-agent-card-detail', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, CardModule, TagModule, DividerModule, ProgressSpinnerModule, DialogModule, ButtonModule], template: "@if (isLoading()) {\n<div class=\"loading-state\">\n <p-progressSpinner strokeWidth=\"4\" />\n</div>\n} @else if (entity()) {\n<div class=\"preview-container\">\n\n <!-- Banner -->\n @if (bannerUrl) {\n <div class=\"preview-banner\">\n <img class=\"banner-image\" [src]=\"bannerUrl\" alt=\"banner\" />\n </div>\n }\n\n <!-- Header: image + name + meta -->\n <div class=\"preview-header\" [class.has-banner]=\"bannerUrl\">\n @if (entity()?.assets?.image?.url) {\n <img class=\"preview-image\" [src]=\"entity()!.assets!.image!.url\" [alt]=\"characterData?.name || entity()?.name\" />\n }\n <div class=\"preview-header-info\">\n <h2 class=\"preview-name\">{{ characterData?.name || entity()?.name }}</h2>\n\n <div class=\"preview-meta-tags\">\n @if (entity()?.lang) {\n <p-tag [value]=\"entity()!.lang!\" severity=\"secondary\" icon=\"pi pi-flag\" />\n }\n @if (entity()?.manageable?.status) {\n <p-tag [value]=\"entity()!.manageable!.status!\" severity=\"info\" icon=\"pi pi-circle-fill\" />\n }\n @if (characterData?.gender) {\n <p-tag [value]=\"characterData!.gender\" severity=\"secondary\" icon=\"pi pi-user\" />\n }\n @if (entity()?.metaApp?.takenCount) {\n <p-tag [value]=\"'Taken: ' + entity()!.metaApp!.takenCount\" severity=\"success\" icon=\"pi pi-users\" />\n }\n @if (learnableInfo?.level) {\n <p-tag [value]=\"'Level ' + learnableInfo.level\" severity=\"warn\" icon=\"pi pi-star\" />\n }\n @if (learnableInfo?.takenCount) {\n <p-tag [value]=\"'Played: ' + learnableInfo.takenCount\" severity=\"secondary\" icon=\"pi pi-play\" />\n }\n </div>\n\n @if (characterData?.tags?.length) {\n <div class=\"preview-tags\">\n @for (tag of characterData!.tags; track tag) {\n <p-tag [value]=\"tag\" severity=\"secondary\" />\n }\n </div>\n }\n </div>\n </div>\n\n <!-- Entity-level description -->\n @if (entity()?.description) {\n <div class=\"preview-entity-description\">\n <p>{{ entity()!.description }}</p>\n </div>\n }\n\n <!-- Motion Videos -->\n @if (motions.length) {\n <div class=\"preview-section motions-top-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-video\"></i> Motion Videos ({{ motions.length }})</h4>\n <div class=\"motions-grid\">\n @for (motion of motions; track $index) {\n @if (motion.metadata?.moodState || motion.metadata?.event) {\n <button class=\"motion-chip\" (click)=\"openMotionVideo(motion)\">\n <i class=\"pi pi-play-circle\"></i>\n <span>{{ motion.metadata?.moodState || motion.metadata?.event }}</span>\n </button>\n }\n }\n </div>\n </div>\n }\n\n <p-divider />\n\n @if (characterData?.hook) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-bookmark\"></i> Hook</h4>\n <p>{{ characterData!.hook }}</p>\n </div>\n }\n\n @if (characterData?.instructions) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-info-circle\"></i> Instructions</h4>\n <p>{{ characterData!.instructions }}</p>\n </div>\n }\n\n @if (characterData?.scenario) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-map\"></i> Scenario</h4>\n <p>{{ characterData!.scenario }}</p>\n </div>\n }\n\n @if (characterData?.creator_notes) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-file\"></i> Creator Notes</h4>\n <p>{{ characterData!.creator_notes }}</p>\n </div>\n }\n\n @if (characterData?.description) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-align-left\"></i> Description</h4>\n <p>{{ characterData!.description }}</p>\n </div>\n }\n\n @if (characterData?.greetings?.length) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-comments\"></i> Greetings</h4>\n @for (greeting of characterData!.greetings; track $index) {\n <p class=\"greeting-item\"><i class=\"pi pi-chevron-right\"></i> {{ greeting }}</p>\n }\n </div>\n }\n\n\n @if (persona) {\n <p-divider />\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-id-card\"></i> Persona</h4>\n <div class=\"persona-grid\">\n @if (persona.identity) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Identity</span>\n <span>{{ persona.identity }}</span>\n </div>\n }\n @if (persona.physical) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Physical</span>\n <span>{{ persona.physical }}</span>\n </div>\n }\n @if (persona.personality) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Personality</span>\n <span>{{ persona.personality }}</span>\n </div>\n }\n @if (persona.communication) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Communication</span>\n <span>{{ persona.communication }}</span>\n </div>\n }\n @if (persona.background) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Background</span>\n <span>{{ persona.background }}</span>\n </div>\n }\n @if (persona.psychology) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Psychology</span>\n <span>{{ persona.psychology }}</span>\n </div>\n }\n @if (persona.capabilities) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Capabilities</span>\n <span>{{ persona.capabilities }}</span>\n </div>\n }\n @if (persona.social) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Social</span>\n <span>{{ persona.social }}</span>\n </div>\n }\n @if (persona.preferences) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Preferences</span>\n <span>{{ persona.preferences }}</span>\n </div>\n }\n @if (persona.situation) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Situation</span>\n <span>{{ persona.situation }}</span>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Conversation Settings -->\n @if (entity()?.conversationSettings) {\n <p-divider />\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Conversation Settings</h4>\n <div class=\"settings-chips\">\n @if (entity()?.conversationSettings?.conversationType) {\n <p-tag [value]=\"entity()!.conversationSettings!.conversationType!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.conversationSettings?.textEngine) {\n <p-tag [value]=\"entity()!.conversationSettings!.textEngine!\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n @if (entity()?.conversationSettings?.autoStart) {\n <p-tag value=\"Auto Start\" severity=\"success\" icon=\"pi pi-play\" />\n }\n @if (entity()?.conversationSettings?.userMustStart) {\n <p-tag value=\"User Starts\" severity=\"warn\" icon=\"pi pi-user\" />\n }\n </div>\n </div>\n }\n\n <!-- Voices -->\n @if (entity()?.voice?.main?.voice || entity()?.conversationSettings?.mainVoice?.voice) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-volume-up\"></i> Voices</h4>\n <div class=\"voice-info\">\n <p><span class=\"field-label\">Main:</span> {{ entity()?.voice?.main?.voice || entity()?.conversationSettings?.mainVoice?.voice }}</p>\n @if (entity()?.voice?.secondary?.voice || entity()?.conversationSettings?.secondaryVoice?.voice) {\n <p><span class=\"field-label\">Secondary:</span> {{ entity()?.voice?.secondary?.voice || entity()?.conversationSettings?.secondaryVoice?.voice }}</p>\n }\n </div>\n </div>\n }\n\n</div>\n}\n\n@if (entity()?.manageable) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Manageable</h4>\n <div class=\"settings-chips\">\n @if (entity()?.manageable?.status) {\n <p-tag [value]=\"entity()!.manageable!.status!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.manageable?.isPublic) {\n <p-tag [value]=\"'Public'\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n </div>\n</div>\n}\n\n@if (entity()?.learnable) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Learnable</h4>\n <div class=\"settings-chips\">\n @if (entity()?.learnable?.level) {\n <p-tag [value]=\"'Level ' + entity()!.learnable!.level!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.learnable?.tags) {\n <p-tag [value]=\"'Public'\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n </div>\n</div>\n}\n\n@if(entity()?.conversationFlow) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-tags\"></i> Conversation Flow</h4>\n\n @if (entity()?.conversationFlow?.moodState) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-face-smile\"></i> Mood State</h5>\n <div class=\"settings-chips\">\n @if (entity()!.conversationFlow!.moodState!.enabled) {\n <p-tag value=\"Enabled\" severity=\"success\" icon=\"pi pi-check\" />\n } @else {\n <p-tag value=\"Disabled\" severity=\"danger\" icon=\"pi pi-times\" />\n }\n @if (entity()!.conversationFlow!.moodState!.useAssetStatesOnly) {\n <p-tag value=\"Asset States Only\" severity=\"warn\" icon=\"pi pi-images\" />\n }\n </div>\n @if (entity()!.conversationFlow!.moodState!.detectableStates?.length) {\n <div class=\"settings-chips\" style=\"margin-top: 0.5rem;\">\n @for (state of entity()!.conversationFlow!.moodState!.detectableStates; track $index) {\n <p-tag [value]=\"state\" severity=\"info\" icon=\"pi pi-tag\" />\n }\n </div>\n }\n </div>\n }\n\n @if (entity()?.conversationFlow?.dynamicConditions?.length) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-sliders-v\"></i> Dynamic Conditions</h5>\n @for (condition of entity()!.conversationFlow!.dynamicConditions!; track $index) {\n <div class=\"persona-field\" style=\"margin-bottom: 0.5rem;\">\n <div class=\"settings-chips\">\n <p-tag [value]=\"condition.what\" severity=\"info\" icon=\"pi pi-filter\" />\n <p-tag [value]=\"condition.when\" severity=\"secondary\" />\n <p-tag [value]=\"condition.value + ''\" severity=\"warn\" />\n </div>\n @if (condition.do?.length) {\n <div class=\"settings-chips\" style=\"margin-top: 0.25rem;\">\n @for (action of condition.do!; track $index) {\n <p-tag [value]=\"action.actionType\" severity=\"contrast\" icon=\"pi pi-bolt\" />\n @if (action.prompt) {\n <span class=\"field-label\" style=\"font-size: 0.75rem;\">{{ action.prompt | slice:0:60 }}\u2026</span>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n @if (entity()?.conversationFlow?.triggerTasks) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-bolt\"></i> Trigger Tasks</h5>\n @for (entry of objectEntries(entity()!.conversationFlow!.triggerTasks!); track $index) {\n <div class=\"persona-field\" style=\"margin-bottom: 0.5rem;\">\n <span class=\"field-label\">{{ entry[0] }}</span>\n <div class=\"settings-chips\" style=\"margin-top: 0.25rem;\">\n @if (entry[1].enabled) {\n <p-tag value=\"Enabled\" severity=\"success\" icon=\"pi pi-check\" />\n } @else {\n <p-tag value=\"Disabled\" severity=\"secondary\" icon=\"pi pi-times\" />\n }\n @if (entry[1].disableFeature) {\n <p-tag value=\"Feature Disabled\" severity=\"danger\" icon=\"pi pi-ban\" />\n }\n </div>\n @if (entry[1].task) {\n <p style=\"font-size: 0.8rem; margin: 0.25rem 0 0;\">{{ entry[1].task | slice:0:120 }}\u2026</p>\n }\n </div>\n }\n </div>\n }\n</div>\n}\n\n<!-- Video Dialog -->\n<p-dialog\n [visible]=\"videoDialogVisible()\"\n (visibleChange)=\"$event ? null : closeMotionVideo()\"\n [header]=\"selectedMotion()?.metadata?.moodState || selectedMotion()?.metadata?.event || 'Video'\"\n [modal]=\"true\"\n [dismissableMask]=\"true\"\n [closable]=\"true\"\n styleClass=\"video-dialog\"\n [style]=\"{ width: '560px' }\">\n\n @if (selectedMotion()?.url) {\n <video\n class=\"motion-video\"\n [src]=\"selectedMotion().url\"\n controls\n autoplay>\n </video>\n }\n</p-dialog>\n", styles: [".preview-container{padding:.5rem}.preview-banner{width:100%;height:210px;overflow:hidden;border-radius:8px;margin-bottom:0}.banner-image{width:100%;height:100%;object-fit:cover;display:block}.preview-header{display:flex;gap:1.5rem;align-items:flex-start;padding:1rem 0 .5rem}.preview-header.has-banner{padding-top:.75rem}.preview-image{width:120px;height:170px;object-fit:cover;border-radius:8px;flex-shrink:0;box-shadow:0 2px 8px #00000026}.preview-header-info{display:flex;flex-direction:column;gap:.5rem}.preview-name{margin:0 0 .25rem;font-size:1.5rem;font-weight:700}.preview-meta-tags{display:flex;flex-wrap:wrap;gap:.35rem}.preview-tags{display:flex;flex-wrap:wrap;gap:.25rem;margin-top:.25rem}.preview-entity-description{color:var(--p-text-muted-color);font-size:.9rem;margin:.5rem 0}.preview-entity-description p{margin:0}.preview-section{margin-bottom:1rem}.section-title{margin:0 0 .5rem;font-size:.95rem;font-weight:600;color:var(--p-text-muted-color);display:flex;align-items:center;gap:.4rem}.greeting-item{margin:.25rem 0;padding-left:.5rem;border-left:2px solid var(--p-primary-color)}.first-message{background:var(--p-surface-100);border-radius:6px;padding:.75rem;font-style:italic}.persona-grid{display:flex;flex-direction:column;gap:.5rem}.persona-field{display:flex;flex-direction:column;gap:.15rem}.field-label{font-size:.8rem;font-weight:600;color:var(--p-text-muted-color);text-transform:uppercase;letter-spacing:.05em}.motions-top-section{margin-top:.75rem;margin-bottom:0}.motions-grid{display:flex;flex-wrap:wrap;gap:.4rem}.motion-chip{display:inline-flex;align-items:center;gap:.35rem;padding:.3rem .65rem;border-radius:20px;border:1px solid var(--p-surface-300);background:var(--p-surface-100);color:var(--p-text-color);font-size:.8rem;font-weight:500;cursor:pointer;transition:background .15s,border-color .15s,transform .1s;text-transform:capitalize}.motion-chip i{font-size:.85rem;color:var(--p-primary-color)}.motion-chip:hover{background:var(--p-primary-50, #f0f4ff);border-color:var(--p-primary-color);transform:translateY(-1px)}.motion-chip:active{transform:translateY(0)}.motion-video{width:100%;border-radius:6px;display:block}.settings-chips{display:flex;flex-wrap:wrap;gap:.4rem}.voice-info{display:flex;flex-direction:column;gap:.25rem}.loading-state{display:flex;justify-content:center;padding:2rem}\n"] }]
|
|
6194
6512
|
}], ctorParameters: () => [], propDecorators: { entityData: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityData", required: false }] }] } });
|
|
6195
6513
|
|
|
6196
6514
|
class ConversationSummaryComponent {
|
|
@@ -6292,11 +6610,11 @@ class ConversationInspector {
|
|
|
6292
6610
|
this.viewMode.set(this.viewMode() === 'markdown' ? 'regular' : 'markdown');
|
|
6293
6611
|
}
|
|
6294
6612
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ConversationInspector, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6295
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ConversationInspector, isStandalone: true, selector: "dc-conversation-info", outputs: { completeEvent: "completeEvent" }, ngImport: i0, template: "<div class=\"info-content\">\n <p-tabs value=\"0\">\n <p-tablist>\n <p-tab value=\"0\">\uD83D\uDCCA Summary</p-tab>\n <p-tab value=\"1\">\uD83D\uDD00 Dynamic Flow</p-tab>\n <p-tab value=\"6\">\uD83D\uDCE6 Messages State</p-tab>\n <p-tab value=\"7\">\uD83D\uDC64 Preview Card</p-tab>\n <p-tab value=\"5\">\uD83D\uDCBE Conversation State</p-tab>\n <p-tab value=\"2\">\uD83D\uDCAC Conversation Messages</p-tab>\n <p-tab value=\"3\">\uD83D\uDCB0 Pricing</p-tab>\n <p-tab value=\"4\">\uD83D\uDC1B More</p-tab>\n </p-tablist>\n <p-tabpanels>\n <p-tabpanel value=\"0\">\n <ng-template #content>\n @if (conversationFlowStateService.flowState(); as flowState) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4 mt-2\">\n <!-- Goal State -->\n <div class=\"p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFAF</span> Goal Progress\n </h3>\n <div class=\"flex items-end gap-4 mb-3\">\n <div class=\"text-5xl font-black leading-none\">\n {{ flowState.goal?.value || 0 }}\n </div>\n @if (flowState.goal?.deltaPoints !== undefined) {\n <div class=\"text-xl font-bold mb-1\" \n [class.text-green-600]=\"flowState.goal.deltaPoints > 0\"\n [class.text-red-600]=\"flowState.goal.deltaPoints < 0\"\n [class.opacity-50]=\"flowState.goal.deltaPoints === 0\">\n {{ flowState.goal.deltaPoints > 0 ? '+' : '' }}{{ flowState.goal.deltaPoints }}\n </div>\n }\n </div>\n \n @if (flowState.intervention?.message) {\n <div class=\"mt-4 p-3 rounded border-l-4 text-sm italic shadow-sm opacity-80\">\n \"{{ flowState.intervention.message }}\"\n </div>\n }\n </div>\n\n <!-- Mood State -->\n <div class=\"p-4 border rounded-lg shadow-sm flex flex-col\">\n <h3 class=\"text-lg font-bold mb-2 flex items-center gap-2\">\n <span>\uD83C\uDFAD</span> Agent Mood\n </h3>\n <div class=\"flex-grow flex items-center justify-center\">\n @if (flowState.moodState?.value) {\n <div class=\"text-4xl font-black uppercase tracking-widest text-center drop-shadow-sm\">\n {{ flowState.moodState.value }}\n </div>\n } @else {\n <div class=\"text-xl font-medium italic opacity-50\">Neutral</div>\n }\n </div>\n </div>\n </div>\n }\n <dc-conversation-summary [agentCard]=\"agentCard\" [parentInjector]=\"config.data.injector\" (completeEvent)=\"complete()\"></dc-conversation-summary>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"1\">\n <ng-template #content>\n <!-- Dynamic Flow -->\n\n <p-divider>Conversation Flow</p-divider>\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (dynamicFlowService.conversationFlowConfig()?.goal?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Goal</p>\n </div>\n @if(dynamicFlowService.conversationFlowConfig()?.goal?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.triggerTasks?.onUserMessage?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Trigger User Message</p>\n </div>\n @if(agentCardStateService.conversationFlow$()?.triggerTasks?.onUserMessage?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.triggerTasks?.onAssistantMessage?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Trigger Assistant Message</p>\n </div>\n @if(agentCardStateService.conversationFlow$()?.triggerTasks?.onAssistantMessage?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <h3>Herramientas</h3>\n @for (tool of agentCardStateService.conversationFlow$()?.tools; track tool.name) {\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (tool.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">{{ tool.name }}</p>\n </div>\n @if(tool.description) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ tool.description }} </div>\n }\n </div>\n }\n\n <h3>Challenges Retos</h3>\n @for (challenge of dynamicFlowService.conversationFlowConfig()?.challenges; track challenge.name) {\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (challenge.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n\n <p class=\"font-semibold\"> {{ challenge.emoji }} {{ challenge.name }}</p>\n </div>\n @if(challenge.description) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ challenge.description }} </div>\n }\n </div>\n }\n <h5>Conditions</h5>\n\n @for (condition of dynamicFlowService.conversationFlowConfig()?.dynamicConditions; track $index) {\n <div class=\"pl-2 mt-1 text-sm text-gray-600 italic\"> When the {{ condition.what }} is {{ condition.when }} than {{ condition.value }} Do </div>\n <ol>\n @for (doAction of condition.do; track $index) {\n <li class=\"pl-5 mt-1 text-sm text-gray-600 italic\">\n {{ $index }})\n <span style=\"color: brown\"> {{ doAction.actionType }}</span>\n <span style=\"color: green\"> {{ doAction.systemPromptType }}</span>\n <span style=\"color: green\"> {{ doAction.dynamicFlowTaskType }}</span>\n\n to\n <span style=\"color: rgb(49, 23, 177)\"> {{ doAction.enabled }}</span>\n \"{{ doAction.prompt }}\"\n </li>\n }\n </ol>\n }\n\n <p>Challenges</p>\n <pre>{{ agentCardStateService.conversationFlow$()?.challenges | safeJson }}</pre>\n\n <p-divider>Performance Evaluation</p-divider>\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.enablePerformanceAnalysis !== false) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Performance Prompt</p>\n <span class=\"text-xs text-gray-400 ml-2\" pTooltip=\"Minimum messages required to trigger evaluation\">(MIN_MESSAGES_EVALUATE: 4 hardcoded)</span>\n </div>\n @if(agentCardStateService.conversationFlow$()?.enablePerformanceAnalysis !== false) {\n @if(agentCardStateService.conversationFlow$()?.performancePrompt; as prompt) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> \n <markdown>\n {{ prompt }} \n </markdown>\n </div>\n } @else {\n <div class=\"pl-5 mt-1 text-sm text-gray-400 italic\"> No custom performance prompt defined (using default). </div>\n }\n } @else {\n <div class=\"pl-5 mt-1 text-sm text-gray-400 italic\"> Performance analysis disabled. </div>\n }\n </div>\n\n <p-divider>System Evaluation Prompt</p-divider>\n <details>\n <summary>Show Prompt</summary>\n <pre>{{ systemPromptForFlow() }}</pre>\n </details>\n\n <!-- Dynamic Flow ends Here -->\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"5\">\n <ng-template #content>\n @if (conversationFlowStateService.flowState(); as flowState) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4 mt-2\">\n <!-- Goal State -->\n <div class=\"p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFAF</span> Goal Progress\n </h3>\n <div class=\"flex items-end gap-4 mb-3\">\n <div class=\"text-5xl font-black leading-none\">\n {{ flowState.goal?.value || 0 }}\n </div>\n @if (flowState.goal?.deltaPoints !== undefined) {\n <div class=\"text-xl font-bold mb-1\" \n [class.text-green-600]=\"flowState.goal.deltaPoints > 0\"\n [class.text-red-600]=\"flowState.goal.deltaPoints < 0\"\n [class.opacity-50]=\"flowState.goal.deltaPoints === 0\">\n {{ flowState.goal.deltaPoints > 0 ? '+' : '' }}{{ flowState.goal.deltaPoints }}\n </div>\n }\n </div>\n \n @if (flowState.intervention?.message) {\n <div class=\"mt-4 p-3 rounded border-l-4 text-sm italic shadow-sm opacity-80\">\n \"{{ flowState.intervention.message }}\"\n </div>\n }\n </div>\n\n <!-- Mood State -->\n <div class=\"p-4 border rounded-lg shadow-sm flex flex-col\">\n <h3 class=\"text-lg font-bold mb-2 flex items-center gap-2\">\n <span>\uD83C\uDFAD</span> Agent Mood\n </h3>\n <div class=\"flex-grow flex items-center justify-center\">\n @if (flowState.moodState?.value) {\n <div class=\"text-4xl font-black uppercase tracking-widest text-center drop-shadow-sm\">\n {{ flowState.moodState.value }}\n </div>\n } @else {\n <div class=\"text-xl font-medium italic opacity-50\">Neutral</div>\n }\n </div>\n </div>\n </div>\n\n @if (flowState.challenges?.length) {\n <div class=\"mb-4 p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFC6</span> Completed Challenges\n </h3>\n <ul class=\"list-disc pl-5 space-y-1\">\n @for (challenge of flowState.challenges; track challenge) {\n <li>{{ challenge }}</li>\n }\n </ul>\n </div>\n }\n\n <details class=\"mt-4\">\n <summary class=\"cursor-pointer text-sm font-medium\">Raw JSON State</summary>\n <pre class=\"mt-2 p-2 rounded text-xs overflow-auto\">{{ flowState | safeJson }}</pre>\n </details>\n } @else {\n <p class=\"italic mt-2 opacity-50\">No conversation state available.</p>\n }\n\n <details class=\"mt-2\">\n <summary class=\"cursor-pointer text-sm font-medium\">Current Prompt</summary>\n <pre class=\"mt-2 p-2 rounded text-xs overflow-auto\">{{ systemPromptForFlow() }}</pre>\n </details>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"2\">\n <ng-template #content>\n <div>\n <p-button label=\"Exportar todo\" (click)=\"exportChat('all')\" />\n <p-button label=\"Exportar conversaci\u00F3n\" (click)=\"exportChat('conversation')\" />\n <p-button [label]=\"'Vista ' + viewMode()\" (click)=\"toggleViewMode()\" />\n </div>\n <dc-prompt-preview [messages]=\"messageStateService.getMessagesSignal()()\" [view]=\"viewMode()\"></dc-prompt-preview>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"6\">\n <ng-template #content>\n <dc-messages-state-inspector></dc-messages-state-inspector>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"3\">\n <ng-template #content>\n <dc-cost-details></dc-cost-details>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"4\">\n <ng-template #content>\n <!-- MORE INFO -->\n\n <details>\n <summary>Estado Global Conversation Flow</summary>\n <p>Goal</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.goal | safeJson }}</pre>\n <p>Trigger User Message</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.triggerTasks?.onUserMessage | safeJson }}</pre>\n <p>Trigger Assistant Message</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.triggerTasks?.onAssistantMessage | safeJson }}</pre>\n <p>Conditions</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.dynamicConditions | safeJson }}</pre>\n <p>Tools</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.tools | safeJson }}</pre>\n <p>Challenges</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.challenges | safeJson }}</pre>\n </details>\n\n <details>\n <summary>Conversation Settings (Debug)</summary>\n <pre>{{ conversationService.conversationSettings$() | safeJson }}</pre>\n </details>\n\n <details>\n <summary>Agent Card</summary>\n <pre>{{ agentCard | safeJson }}</pre>\n </details>\n\n <details>\n <summary>User Settings</summary>\n <pre>{{ chatUserSettings | safeJson }}</pre>\n </details>\n\n <!-- MORE INFO ends Here -->\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"7\">\n <ng-template #content>\n <dc-agent-card-detail [entityData]=\"agentCardStateService.agentCard$()\" />\n </ng-template>\n </p-tabpanel>\n </p-tabpanels>\n </p-tabs>\n</div>\n", styles: [":host{overflow-y:scroll}.info-content{display:flex;flex-direction:column;gap:1rem;padding:1rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "ngmodule", type: SliderModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: DividerModule }, { kind: "component", type: i2$3.Divider, selector: "p-divider", inputs: ["styleClass", "layout", "type", "align"] }, { kind: "component", type: PromptPreviewComponent, selector: "dc-prompt-preview", inputs: ["messages", "jailBrake", "view"] }, { kind: "ngmodule", type: TabsModule }, { kind: "component", type: i2$5.Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i2$5.TabPanels, selector: "p-tabpanels" }, { kind: "component", type: i2$5.TabPanel, selector: "p-tabpanel", inputs: ["lazy", "value"], outputs: ["valueChange"] }, { kind: "component", type: i2$5.TabList, selector: "p-tablist" }, { kind: "component", type: i2$5.Tab, selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: CostDetailsComponent, selector: "dc-cost-details" }, { kind: "component", type: MessagesStateInspectorComponent, selector: "dc-messages-state-inspector" }, { kind: "component", type: DcAgentCardDetailComponent, selector: "dc-agent-card-detail", inputs: ["entityData"] }, { kind: "component", type: ConversationSummaryComponent, selector: "dc-conversation-summary", inputs: ["agentCard", "parentInjector"], outputs: ["completeEvent"] }, { kind: "pipe", type: SafeJsonPipe, name: "safeJson" }] }); }
|
|
6613
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ConversationInspector, isStandalone: true, selector: "dc-conversation-info", outputs: { completeEvent: "completeEvent" }, ngImport: i0, template: "<div class=\"info-content\">\n <p-tabs value=\"0\">\n <p-tablist>\n <p-tab value=\"0\">\uD83D\uDCCA Summary</p-tab>\n <p-tab value=\"1\">\uD83D\uDD00 Dynamic Flow</p-tab>\n <p-tab value=\"6\">\uD83D\uDCE6 Messages State</p-tab>\n <p-tab value=\"7\">\uD83D\uDC64 Preview Card</p-tab>\n <p-tab value=\"5\">\uD83D\uDCBE Conversation State</p-tab>\n <p-tab value=\"2\">\uD83D\uDCAC Conversation Messages</p-tab>\n <p-tab value=\"3\">\uD83D\uDCB0 Pricing</p-tab>\n <p-tab value=\"4\">\uD83D\uDC1B More</p-tab>\n </p-tablist>\n <p-tabpanels>\n <p-tabpanel value=\"0\">\n <ng-template #content>\n @if (conversationFlowStateService.flowState(); as flowState) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4 mt-2\">\n <!-- Goal State -->\n <div class=\"p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFAF</span> Goal Progress\n </h3>\n <div class=\"flex items-end gap-4 mb-3\">\n <div class=\"text-5xl font-black leading-none\">\n {{ flowState.goal?.value || 0 }}\n </div>\n @if (flowState.goal?.deltaPoints !== undefined) {\n <div class=\"text-xl font-bold mb-1\" \n [class.text-green-600]=\"flowState.goal.deltaPoints > 0\"\n [class.text-red-600]=\"flowState.goal.deltaPoints < 0\"\n [class.opacity-50]=\"flowState.goal.deltaPoints === 0\">\n {{ flowState.goal.deltaPoints > 0 ? '+' : '' }}{{ flowState.goal.deltaPoints }}\n </div>\n }\n </div>\n \n @if (flowState.intervention?.message) {\n <div class=\"mt-4 p-3 rounded border-l-4 text-sm italic shadow-sm opacity-80\">\n \"{{ flowState.intervention.message }}\"\n </div>\n }\n </div>\n\n <!-- Mood State -->\n <div class=\"p-4 border rounded-lg shadow-sm flex flex-col\">\n <h3 class=\"text-lg font-bold mb-2 flex items-center gap-2\">\n <span>\uD83C\uDFAD</span> Agent Mood\n </h3>\n <div class=\"flex-grow flex items-center justify-center\">\n @if (flowState.moodState?.value) {\n <div class=\"text-4xl font-black uppercase tracking-widest text-center drop-shadow-sm\">\n {{ flowState.moodState.value }}\n </div>\n } @else {\n <div class=\"text-xl font-medium italic opacity-50\">Neutral</div>\n }\n </div>\n </div>\n </div>\n }\n <dc-conversation-summary [agentCard]=\"agentCard\" [parentInjector]=\"config.data.injector\" (completeEvent)=\"complete()\"></dc-conversation-summary>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"1\">\n <ng-template #content>\n <!-- Dynamic Flow -->\n\n <p-divider>Conversation Flow</p-divider>\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (dynamicFlowService.conversationFlowConfig()?.goal?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Goal</p>\n </div>\n @if(dynamicFlowService.conversationFlowConfig()?.goal?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.triggerTasks?.onUserMessage?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Trigger User Message</p>\n </div>\n @if(agentCardStateService.conversationFlow$()?.triggerTasks?.onUserMessage?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.triggerTasks?.onAssistantMessage?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Trigger Assistant Message</p>\n </div>\n @if(agentCardStateService.conversationFlow$()?.triggerTasks?.onAssistantMessage?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <h3>Herramientas</h3>\n @for (tool of agentCardStateService.conversationFlow$()?.tools; track tool.name) {\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (tool.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">{{ tool.name }}</p>\n </div>\n @if(tool.description) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ tool.description }} </div>\n }\n </div>\n }\n\n <h3>Challenges Retos</h3>\n @for (challenge of dynamicFlowService.conversationFlowConfig()?.challenges; track challenge.name) {\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (challenge.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n\n <p class=\"font-semibold\"> {{ challenge.emoji }} {{ challenge.name }}</p>\n </div>\n @if(challenge.description) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ challenge.description }} </div>\n }\n </div>\n }\n <h5>Conditions</h5>\n\n @for (condition of dynamicFlowService.conversationFlowConfig()?.dynamicConditions; track $index) {\n <div class=\"pl-2 mt-1 text-sm text-gray-600 italic\"> When the {{ condition.what }} is {{ condition.when }} than {{ condition.value }} Do </div>\n <ol>\n @for (doAction of condition.do; track $index) {\n <li class=\"pl-5 mt-1 text-sm text-gray-600 italic\">\n {{ $index }})\n <span style=\"color: brown\"> {{ doAction.actionType }}</span>\n <span style=\"color: green\"> {{ doAction.systemPromptType }}</span>\n <span style=\"color: green\"> {{ doAction.dynamicFlowTaskType }}</span>\n\n to\n <span style=\"color: rgb(49, 23, 177)\"> {{ doAction.enabled }}</span>\n \"{{ doAction.prompt }}\"\n </li>\n }\n </ol>\n }\n\n <p>Challenges</p>\n <pre>{{ agentCardStateService.conversationFlow$()?.challenges | safeJson }}</pre>\n\n <p-divider>Performance Evaluation</p-divider>\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.enablePerformanceAnalysis !== false) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Performance Prompt</p>\n <span class=\"text-xs text-gray-400 ml-2\" pTooltip=\"Minimum messages required to trigger evaluation\">(MIN_MESSAGES_EVALUATE: 4 hardcoded)</span>\n </div>\n @if(agentCardStateService.conversationFlow$()?.enablePerformanceAnalysis !== false) {\n @if(agentCardStateService.conversationFlow$()?.performancePrompt; as prompt) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> \n <markdown>\n {{ prompt }} \n </markdown>\n </div>\n } @else {\n <div class=\"pl-5 mt-1 text-sm text-gray-400 italic\"> No custom performance prompt defined (using default). </div>\n }\n } @else {\n <div class=\"pl-5 mt-1 text-sm text-gray-400 italic\"> Performance analysis disabled. </div>\n }\n </div>\n\n <p-divider>System Evaluation Prompt</p-divider>\n <details>\n <summary>Show Prompt</summary>\n <pre>{{ systemPromptForFlow() }}</pre>\n </details>\n\n <!-- Dynamic Flow ends Here -->\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"5\">\n <ng-template #content>\n @if (conversationFlowStateService.flowState(); as flowState) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4 mt-2\">\n <!-- Goal State -->\n <div class=\"p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFAF</span> Goal Progress\n </h3>\n <div class=\"flex items-end gap-4 mb-3\">\n <div class=\"text-5xl font-black leading-none\">\n {{ flowState.goal?.value || 0 }}\n </div>\n @if (flowState.goal?.deltaPoints !== undefined) {\n <div class=\"text-xl font-bold mb-1\" \n [class.text-green-600]=\"flowState.goal.deltaPoints > 0\"\n [class.text-red-600]=\"flowState.goal.deltaPoints < 0\"\n [class.opacity-50]=\"flowState.goal.deltaPoints === 0\">\n {{ flowState.goal.deltaPoints > 0 ? '+' : '' }}{{ flowState.goal.deltaPoints }}\n </div>\n }\n </div>\n \n @if (flowState.intervention?.message) {\n <div class=\"mt-4 p-3 rounded border-l-4 text-sm italic shadow-sm opacity-80\">\n \"{{ flowState.intervention.message }}\"\n </div>\n }\n </div>\n\n <!-- Mood State -->\n <div class=\"p-4 border rounded-lg shadow-sm flex flex-col\">\n <h3 class=\"text-lg font-bold mb-2 flex items-center gap-2\">\n <span>\uD83C\uDFAD</span> Agent Mood\n </h3>\n <div class=\"flex-grow flex items-center justify-center\">\n @if (flowState.moodState?.value) {\n <div class=\"text-4xl font-black uppercase tracking-widest text-center drop-shadow-sm\">\n {{ flowState.moodState.value }}\n </div>\n } @else {\n <div class=\"text-xl font-medium italic opacity-50\">Neutral</div>\n }\n </div>\n </div>\n </div>\n\n @if (flowState.challenges?.length) {\n <div class=\"mb-4 p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFC6</span> Completed Challenges\n </h3>\n <ul class=\"list-disc pl-5 space-y-1\">\n @for (challenge of flowState.challenges; track challenge) {\n <li>{{ challenge }}</li>\n }\n </ul>\n </div>\n }\n\n <details class=\"mt-4\">\n <summary class=\"cursor-pointer text-sm font-medium\">Raw JSON State</summary>\n <pre class=\"mt-2 p-2 rounded text-xs overflow-auto\">{{ flowState | safeJson }}</pre>\n </details>\n } @else {\n <p class=\"italic mt-2 opacity-50\">No conversation state available.</p>\n }\n\n <details class=\"mt-2\">\n <summary class=\"cursor-pointer text-sm font-medium\">Current Prompt</summary>\n <pre class=\"mt-2 p-2 rounded text-xs overflow-auto\">{{ systemPromptForFlow() }}</pre>\n </details>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"2\">\n <ng-template #content>\n <div>\n <p-button label=\"Exportar todo\" (click)=\"exportChat('all')\" />\n <p-button label=\"Exportar conversaci\u00F3n\" (click)=\"exportChat('conversation')\" />\n <p-button [label]=\"'Vista ' + viewMode()\" (click)=\"toggleViewMode()\" />\n </div>\n <dc-prompt-preview [messages]=\"messageStateService.getMessagesSignal()()\" [view]=\"viewMode()\"></dc-prompt-preview>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"6\">\n <ng-template #content>\n <dc-messages-state-inspector [service]=\"messageStateService\"></dc-messages-state-inspector>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"3\">\n <ng-template #content>\n <dc-cost-details></dc-cost-details>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"4\">\n <ng-template #content>\n <!-- MORE INFO -->\n\n <details>\n <summary>Estado Global Conversation Flow</summary>\n <p>Goal</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.goal | safeJson }}</pre>\n <p>Trigger User Message</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.triggerTasks?.onUserMessage | safeJson }}</pre>\n <p>Trigger Assistant Message</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.triggerTasks?.onAssistantMessage | safeJson }}</pre>\n <p>Conditions</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.dynamicConditions | safeJson }}</pre>\n <p>Tools</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.tools | safeJson }}</pre>\n <p>Challenges</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.challenges | safeJson }}</pre>\n </details>\n\n <details>\n <summary>Conversation Settings (Debug)</summary>\n <pre>{{ conversationService.conversationSettings$() | safeJson }}</pre>\n </details>\n\n <details>\n <summary>Agent Card</summary>\n <pre>{{ agentCard | safeJson }}</pre>\n </details>\n\n <details>\n <summary>User Settings</summary>\n <pre>{{ chatUserSettings | safeJson }}</pre>\n </details>\n\n <!-- MORE INFO ends Here -->\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"7\">\n <ng-template #content>\n <dc-agent-card-detail [entityData]=\"agentCardStateService.agentCard$()\" />\n </ng-template>\n </p-tabpanel>\n </p-tabpanels>\n </p-tabs>\n</div>\n", styles: [":host{overflow-y:scroll}.info-content{display:flex;flex-direction:column;gap:1rem;padding:1rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "ngmodule", type: SliderModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: DividerModule }, { kind: "component", type: i2$3.Divider, selector: "p-divider", inputs: ["styleClass", "layout", "type", "align"] }, { kind: "component", type: PromptPreviewComponent, selector: "dc-prompt-preview", inputs: ["messages", "jailBrake", "view"] }, { kind: "ngmodule", type: TabsModule }, { kind: "component", type: i2$5.Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i2$5.TabPanels, selector: "p-tabpanels" }, { kind: "component", type: i2$5.TabPanel, selector: "p-tabpanel", inputs: ["lazy", "value"], outputs: ["valueChange"] }, { kind: "component", type: i2$5.TabList, selector: "p-tablist" }, { kind: "component", type: i2$5.Tab, selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: CostDetailsComponent, selector: "dc-cost-details" }, { kind: "component", type: MessagesStateInspectorComponent, selector: "dc-messages-state-inspector", inputs: ["service"] }, { kind: "component", type: DcAgentCardDetailComponent, selector: "dc-agent-card-detail", inputs: ["entityData"] }, { kind: "component", type: ConversationSummaryComponent, selector: "dc-conversation-summary", inputs: ["agentCard", "parentInjector"], outputs: ["completeEvent"] }, { kind: "pipe", type: SafeJsonPipe, name: "safeJson" }] }); }
|
|
6296
6614
|
}
|
|
6297
6615
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ConversationInspector, decorators: [{
|
|
6298
6616
|
type: Component,
|
|
6299
|
-
args: [{ selector: 'dc-conversation-info', standalone: true, imports: [CommonModule, SafeJsonPipe, MarkdownComponent, SliderModule, ButtonModule, FormsModule, DividerModule, PromptPreviewComponent, TabsModule, CostDetailsComponent, MessagesStateInspectorComponent, DcAgentCardDetailComponent, ConversationSummaryComponent], template: "<div class=\"info-content\">\n <p-tabs value=\"0\">\n <p-tablist>\n <p-tab value=\"0\">\uD83D\uDCCA Summary</p-tab>\n <p-tab value=\"1\">\uD83D\uDD00 Dynamic Flow</p-tab>\n <p-tab value=\"6\">\uD83D\uDCE6 Messages State</p-tab>\n <p-tab value=\"7\">\uD83D\uDC64 Preview Card</p-tab>\n <p-tab value=\"5\">\uD83D\uDCBE Conversation State</p-tab>\n <p-tab value=\"2\">\uD83D\uDCAC Conversation Messages</p-tab>\n <p-tab value=\"3\">\uD83D\uDCB0 Pricing</p-tab>\n <p-tab value=\"4\">\uD83D\uDC1B More</p-tab>\n </p-tablist>\n <p-tabpanels>\n <p-tabpanel value=\"0\">\n <ng-template #content>\n @if (conversationFlowStateService.flowState(); as flowState) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4 mt-2\">\n <!-- Goal State -->\n <div class=\"p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFAF</span> Goal Progress\n </h3>\n <div class=\"flex items-end gap-4 mb-3\">\n <div class=\"text-5xl font-black leading-none\">\n {{ flowState.goal?.value || 0 }}\n </div>\n @if (flowState.goal?.deltaPoints !== undefined) {\n <div class=\"text-xl font-bold mb-1\" \n [class.text-green-600]=\"flowState.goal.deltaPoints > 0\"\n [class.text-red-600]=\"flowState.goal.deltaPoints < 0\"\n [class.opacity-50]=\"flowState.goal.deltaPoints === 0\">\n {{ flowState.goal.deltaPoints > 0 ? '+' : '' }}{{ flowState.goal.deltaPoints }}\n </div>\n }\n </div>\n \n @if (flowState.intervention?.message) {\n <div class=\"mt-4 p-3 rounded border-l-4 text-sm italic shadow-sm opacity-80\">\n \"{{ flowState.intervention.message }}\"\n </div>\n }\n </div>\n\n <!-- Mood State -->\n <div class=\"p-4 border rounded-lg shadow-sm flex flex-col\">\n <h3 class=\"text-lg font-bold mb-2 flex items-center gap-2\">\n <span>\uD83C\uDFAD</span> Agent Mood\n </h3>\n <div class=\"flex-grow flex items-center justify-center\">\n @if (flowState.moodState?.value) {\n <div class=\"text-4xl font-black uppercase tracking-widest text-center drop-shadow-sm\">\n {{ flowState.moodState.value }}\n </div>\n } @else {\n <div class=\"text-xl font-medium italic opacity-50\">Neutral</div>\n }\n </div>\n </div>\n </div>\n }\n <dc-conversation-summary [agentCard]=\"agentCard\" [parentInjector]=\"config.data.injector\" (completeEvent)=\"complete()\"></dc-conversation-summary>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"1\">\n <ng-template #content>\n <!-- Dynamic Flow -->\n\n <p-divider>Conversation Flow</p-divider>\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (dynamicFlowService.conversationFlowConfig()?.goal?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Goal</p>\n </div>\n @if(dynamicFlowService.conversationFlowConfig()?.goal?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.triggerTasks?.onUserMessage?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Trigger User Message</p>\n </div>\n @if(agentCardStateService.conversationFlow$()?.triggerTasks?.onUserMessage?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.triggerTasks?.onAssistantMessage?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Trigger Assistant Message</p>\n </div>\n @if(agentCardStateService.conversationFlow$()?.triggerTasks?.onAssistantMessage?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <h3>Herramientas</h3>\n @for (tool of agentCardStateService.conversationFlow$()?.tools; track tool.name) {\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (tool.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">{{ tool.name }}</p>\n </div>\n @if(tool.description) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ tool.description }} </div>\n }\n </div>\n }\n\n <h3>Challenges Retos</h3>\n @for (challenge of dynamicFlowService.conversationFlowConfig()?.challenges; track challenge.name) {\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (challenge.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n\n <p class=\"font-semibold\"> {{ challenge.emoji }} {{ challenge.name }}</p>\n </div>\n @if(challenge.description) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ challenge.description }} </div>\n }\n </div>\n }\n <h5>Conditions</h5>\n\n @for (condition of dynamicFlowService.conversationFlowConfig()?.dynamicConditions; track $index) {\n <div class=\"pl-2 mt-1 text-sm text-gray-600 italic\"> When the {{ condition.what }} is {{ condition.when }} than {{ condition.value }} Do </div>\n <ol>\n @for (doAction of condition.do; track $index) {\n <li class=\"pl-5 mt-1 text-sm text-gray-600 italic\">\n {{ $index }})\n <span style=\"color: brown\"> {{ doAction.actionType }}</span>\n <span style=\"color: green\"> {{ doAction.systemPromptType }}</span>\n <span style=\"color: green\"> {{ doAction.dynamicFlowTaskType }}</span>\n\n to\n <span style=\"color: rgb(49, 23, 177)\"> {{ doAction.enabled }}</span>\n \"{{ doAction.prompt }}\"\n </li>\n }\n </ol>\n }\n\n <p>Challenges</p>\n <pre>{{ agentCardStateService.conversationFlow$()?.challenges | safeJson }}</pre>\n\n <p-divider>Performance Evaluation</p-divider>\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.enablePerformanceAnalysis !== false) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Performance Prompt</p>\n <span class=\"text-xs text-gray-400 ml-2\" pTooltip=\"Minimum messages required to trigger evaluation\">(MIN_MESSAGES_EVALUATE: 4 hardcoded)</span>\n </div>\n @if(agentCardStateService.conversationFlow$()?.enablePerformanceAnalysis !== false) {\n @if(agentCardStateService.conversationFlow$()?.performancePrompt; as prompt) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> \n <markdown>\n {{ prompt }} \n </markdown>\n </div>\n } @else {\n <div class=\"pl-5 mt-1 text-sm text-gray-400 italic\"> No custom performance prompt defined (using default). </div>\n }\n } @else {\n <div class=\"pl-5 mt-1 text-sm text-gray-400 italic\"> Performance analysis disabled. </div>\n }\n </div>\n\n <p-divider>System Evaluation Prompt</p-divider>\n <details>\n <summary>Show Prompt</summary>\n <pre>{{ systemPromptForFlow() }}</pre>\n </details>\n\n <!-- Dynamic Flow ends Here -->\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"5\">\n <ng-template #content>\n @if (conversationFlowStateService.flowState(); as flowState) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4 mt-2\">\n <!-- Goal State -->\n <div class=\"p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFAF</span> Goal Progress\n </h3>\n <div class=\"flex items-end gap-4 mb-3\">\n <div class=\"text-5xl font-black leading-none\">\n {{ flowState.goal?.value || 0 }}\n </div>\n @if (flowState.goal?.deltaPoints !== undefined) {\n <div class=\"text-xl font-bold mb-1\" \n [class.text-green-600]=\"flowState.goal.deltaPoints > 0\"\n [class.text-red-600]=\"flowState.goal.deltaPoints < 0\"\n [class.opacity-50]=\"flowState.goal.deltaPoints === 0\">\n {{ flowState.goal.deltaPoints > 0 ? '+' : '' }}{{ flowState.goal.deltaPoints }}\n </div>\n }\n </div>\n \n @if (flowState.intervention?.message) {\n <div class=\"mt-4 p-3 rounded border-l-4 text-sm italic shadow-sm opacity-80\">\n \"{{ flowState.intervention.message }}\"\n </div>\n }\n </div>\n\n <!-- Mood State -->\n <div class=\"p-4 border rounded-lg shadow-sm flex flex-col\">\n <h3 class=\"text-lg font-bold mb-2 flex items-center gap-2\">\n <span>\uD83C\uDFAD</span> Agent Mood\n </h3>\n <div class=\"flex-grow flex items-center justify-center\">\n @if (flowState.moodState?.value) {\n <div class=\"text-4xl font-black uppercase tracking-widest text-center drop-shadow-sm\">\n {{ flowState.moodState.value }}\n </div>\n } @else {\n <div class=\"text-xl font-medium italic opacity-50\">Neutral</div>\n }\n </div>\n </div>\n </div>\n\n @if (flowState.challenges?.length) {\n <div class=\"mb-4 p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFC6</span> Completed Challenges\n </h3>\n <ul class=\"list-disc pl-5 space-y-1\">\n @for (challenge of flowState.challenges; track challenge) {\n <li>{{ challenge }}</li>\n }\n </ul>\n </div>\n }\n\n <details class=\"mt-4\">\n <summary class=\"cursor-pointer text-sm font-medium\">Raw JSON State</summary>\n <pre class=\"mt-2 p-2 rounded text-xs overflow-auto\">{{ flowState | safeJson }}</pre>\n </details>\n } @else {\n <p class=\"italic mt-2 opacity-50\">No conversation state available.</p>\n }\n\n <details class=\"mt-2\">\n <summary class=\"cursor-pointer text-sm font-medium\">Current Prompt</summary>\n <pre class=\"mt-2 p-2 rounded text-xs overflow-auto\">{{ systemPromptForFlow() }}</pre>\n </details>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"2\">\n <ng-template #content>\n <div>\n <p-button label=\"Exportar todo\" (click)=\"exportChat('all')\" />\n <p-button label=\"Exportar conversaci\u00F3n\" (click)=\"exportChat('conversation')\" />\n <p-button [label]=\"'Vista ' + viewMode()\" (click)=\"toggleViewMode()\" />\n </div>\n <dc-prompt-preview [messages]=\"messageStateService.getMessagesSignal()()\" [view]=\"viewMode()\"></dc-prompt-preview>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"6\">\n <ng-template #content>\n <dc-messages-state-inspector></dc-messages-state-inspector>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"3\">\n <ng-template #content>\n <dc-cost-details></dc-cost-details>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"4\">\n <ng-template #content>\n <!-- MORE INFO -->\n\n <details>\n <summary>Estado Global Conversation Flow</summary>\n <p>Goal</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.goal | safeJson }}</pre>\n <p>Trigger User Message</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.triggerTasks?.onUserMessage | safeJson }}</pre>\n <p>Trigger Assistant Message</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.triggerTasks?.onAssistantMessage | safeJson }}</pre>\n <p>Conditions</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.dynamicConditions | safeJson }}</pre>\n <p>Tools</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.tools | safeJson }}</pre>\n <p>Challenges</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.challenges | safeJson }}</pre>\n </details>\n\n <details>\n <summary>Conversation Settings (Debug)</summary>\n <pre>{{ conversationService.conversationSettings$() | safeJson }}</pre>\n </details>\n\n <details>\n <summary>Agent Card</summary>\n <pre>{{ agentCard | safeJson }}</pre>\n </details>\n\n <details>\n <summary>User Settings</summary>\n <pre>{{ chatUserSettings | safeJson }}</pre>\n </details>\n\n <!-- MORE INFO ends Here -->\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"7\">\n <ng-template #content>\n <dc-agent-card-detail [entityData]=\"agentCardStateService.agentCard$()\" />\n </ng-template>\n </p-tabpanel>\n </p-tabpanels>\n </p-tabs>\n</div>\n", styles: [":host{overflow-y:scroll}.info-content{display:flex;flex-direction:column;gap:1rem;padding:1rem}\n"] }]
|
|
6617
|
+
args: [{ selector: 'dc-conversation-info', standalone: true, imports: [CommonModule, SafeJsonPipe, MarkdownComponent, SliderModule, ButtonModule, FormsModule, DividerModule, PromptPreviewComponent, TabsModule, CostDetailsComponent, MessagesStateInspectorComponent, DcAgentCardDetailComponent, ConversationSummaryComponent], template: "<div class=\"info-content\">\n <p-tabs value=\"0\">\n <p-tablist>\n <p-tab value=\"0\">\uD83D\uDCCA Summary</p-tab>\n <p-tab value=\"1\">\uD83D\uDD00 Dynamic Flow</p-tab>\n <p-tab value=\"6\">\uD83D\uDCE6 Messages State</p-tab>\n <p-tab value=\"7\">\uD83D\uDC64 Preview Card</p-tab>\n <p-tab value=\"5\">\uD83D\uDCBE Conversation State</p-tab>\n <p-tab value=\"2\">\uD83D\uDCAC Conversation Messages</p-tab>\n <p-tab value=\"3\">\uD83D\uDCB0 Pricing</p-tab>\n <p-tab value=\"4\">\uD83D\uDC1B More</p-tab>\n </p-tablist>\n <p-tabpanels>\n <p-tabpanel value=\"0\">\n <ng-template #content>\n @if (conversationFlowStateService.flowState(); as flowState) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4 mt-2\">\n <!-- Goal State -->\n <div class=\"p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFAF</span> Goal Progress\n </h3>\n <div class=\"flex items-end gap-4 mb-3\">\n <div class=\"text-5xl font-black leading-none\">\n {{ flowState.goal?.value || 0 }}\n </div>\n @if (flowState.goal?.deltaPoints !== undefined) {\n <div class=\"text-xl font-bold mb-1\" \n [class.text-green-600]=\"flowState.goal.deltaPoints > 0\"\n [class.text-red-600]=\"flowState.goal.deltaPoints < 0\"\n [class.opacity-50]=\"flowState.goal.deltaPoints === 0\">\n {{ flowState.goal.deltaPoints > 0 ? '+' : '' }}{{ flowState.goal.deltaPoints }}\n </div>\n }\n </div>\n \n @if (flowState.intervention?.message) {\n <div class=\"mt-4 p-3 rounded border-l-4 text-sm italic shadow-sm opacity-80\">\n \"{{ flowState.intervention.message }}\"\n </div>\n }\n </div>\n\n <!-- Mood State -->\n <div class=\"p-4 border rounded-lg shadow-sm flex flex-col\">\n <h3 class=\"text-lg font-bold mb-2 flex items-center gap-2\">\n <span>\uD83C\uDFAD</span> Agent Mood\n </h3>\n <div class=\"flex-grow flex items-center justify-center\">\n @if (flowState.moodState?.value) {\n <div class=\"text-4xl font-black uppercase tracking-widest text-center drop-shadow-sm\">\n {{ flowState.moodState.value }}\n </div>\n } @else {\n <div class=\"text-xl font-medium italic opacity-50\">Neutral</div>\n }\n </div>\n </div>\n </div>\n }\n <dc-conversation-summary [agentCard]=\"agentCard\" [parentInjector]=\"config.data.injector\" (completeEvent)=\"complete()\"></dc-conversation-summary>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"1\">\n <ng-template #content>\n <!-- Dynamic Flow -->\n\n <p-divider>Conversation Flow</p-divider>\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (dynamicFlowService.conversationFlowConfig()?.goal?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Goal</p>\n </div>\n @if(dynamicFlowService.conversationFlowConfig()?.goal?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.triggerTasks?.onUserMessage?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Trigger User Message</p>\n </div>\n @if(agentCardStateService.conversationFlow$()?.triggerTasks?.onUserMessage?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.triggerTasks?.onAssistantMessage?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Trigger Assistant Message</p>\n </div>\n @if(agentCardStateService.conversationFlow$()?.triggerTasks?.onAssistantMessage?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <h3>Herramientas</h3>\n @for (tool of agentCardStateService.conversationFlow$()?.tools; track tool.name) {\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (tool.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">{{ tool.name }}</p>\n </div>\n @if(tool.description) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ tool.description }} </div>\n }\n </div>\n }\n\n <h3>Challenges Retos</h3>\n @for (challenge of dynamicFlowService.conversationFlowConfig()?.challenges; track challenge.name) {\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (challenge.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n\n <p class=\"font-semibold\"> {{ challenge.emoji }} {{ challenge.name }}</p>\n </div>\n @if(challenge.description) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ challenge.description }} </div>\n }\n </div>\n }\n <h5>Conditions</h5>\n\n @for (condition of dynamicFlowService.conversationFlowConfig()?.dynamicConditions; track $index) {\n <div class=\"pl-2 mt-1 text-sm text-gray-600 italic\"> When the {{ condition.what }} is {{ condition.when }} than {{ condition.value }} Do </div>\n <ol>\n @for (doAction of condition.do; track $index) {\n <li class=\"pl-5 mt-1 text-sm text-gray-600 italic\">\n {{ $index }})\n <span style=\"color: brown\"> {{ doAction.actionType }}</span>\n <span style=\"color: green\"> {{ doAction.systemPromptType }}</span>\n <span style=\"color: green\"> {{ doAction.dynamicFlowTaskType }}</span>\n\n to\n <span style=\"color: rgb(49, 23, 177)\"> {{ doAction.enabled }}</span>\n \"{{ doAction.prompt }}\"\n </li>\n }\n </ol>\n }\n\n <p>Challenges</p>\n <pre>{{ agentCardStateService.conversationFlow$()?.challenges | safeJson }}</pre>\n\n <p-divider>Performance Evaluation</p-divider>\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.enablePerformanceAnalysis !== false) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Performance Prompt</p>\n <span class=\"text-xs text-gray-400 ml-2\" pTooltip=\"Minimum messages required to trigger evaluation\">(MIN_MESSAGES_EVALUATE: 4 hardcoded)</span>\n </div>\n @if(agentCardStateService.conversationFlow$()?.enablePerformanceAnalysis !== false) {\n @if(agentCardStateService.conversationFlow$()?.performancePrompt; as prompt) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> \n <markdown>\n {{ prompt }} \n </markdown>\n </div>\n } @else {\n <div class=\"pl-5 mt-1 text-sm text-gray-400 italic\"> No custom performance prompt defined (using default). </div>\n }\n } @else {\n <div class=\"pl-5 mt-1 text-sm text-gray-400 italic\"> Performance analysis disabled. </div>\n }\n </div>\n\n <p-divider>System Evaluation Prompt</p-divider>\n <details>\n <summary>Show Prompt</summary>\n <pre>{{ systemPromptForFlow() }}</pre>\n </details>\n\n <!-- Dynamic Flow ends Here -->\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"5\">\n <ng-template #content>\n @if (conversationFlowStateService.flowState(); as flowState) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4 mt-2\">\n <!-- Goal State -->\n <div class=\"p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFAF</span> Goal Progress\n </h3>\n <div class=\"flex items-end gap-4 mb-3\">\n <div class=\"text-5xl font-black leading-none\">\n {{ flowState.goal?.value || 0 }}\n </div>\n @if (flowState.goal?.deltaPoints !== undefined) {\n <div class=\"text-xl font-bold mb-1\" \n [class.text-green-600]=\"flowState.goal.deltaPoints > 0\"\n [class.text-red-600]=\"flowState.goal.deltaPoints < 0\"\n [class.opacity-50]=\"flowState.goal.deltaPoints === 0\">\n {{ flowState.goal.deltaPoints > 0 ? '+' : '' }}{{ flowState.goal.deltaPoints }}\n </div>\n }\n </div>\n \n @if (flowState.intervention?.message) {\n <div class=\"mt-4 p-3 rounded border-l-4 text-sm italic shadow-sm opacity-80\">\n \"{{ flowState.intervention.message }}\"\n </div>\n }\n </div>\n\n <!-- Mood State -->\n <div class=\"p-4 border rounded-lg shadow-sm flex flex-col\">\n <h3 class=\"text-lg font-bold mb-2 flex items-center gap-2\">\n <span>\uD83C\uDFAD</span> Agent Mood\n </h3>\n <div class=\"flex-grow flex items-center justify-center\">\n @if (flowState.moodState?.value) {\n <div class=\"text-4xl font-black uppercase tracking-widest text-center drop-shadow-sm\">\n {{ flowState.moodState.value }}\n </div>\n } @else {\n <div class=\"text-xl font-medium italic opacity-50\">Neutral</div>\n }\n </div>\n </div>\n </div>\n\n @if (flowState.challenges?.length) {\n <div class=\"mb-4 p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFC6</span> Completed Challenges\n </h3>\n <ul class=\"list-disc pl-5 space-y-1\">\n @for (challenge of flowState.challenges; track challenge) {\n <li>{{ challenge }}</li>\n }\n </ul>\n </div>\n }\n\n <details class=\"mt-4\">\n <summary class=\"cursor-pointer text-sm font-medium\">Raw JSON State</summary>\n <pre class=\"mt-2 p-2 rounded text-xs overflow-auto\">{{ flowState | safeJson }}</pre>\n </details>\n } @else {\n <p class=\"italic mt-2 opacity-50\">No conversation state available.</p>\n }\n\n <details class=\"mt-2\">\n <summary class=\"cursor-pointer text-sm font-medium\">Current Prompt</summary>\n <pre class=\"mt-2 p-2 rounded text-xs overflow-auto\">{{ systemPromptForFlow() }}</pre>\n </details>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"2\">\n <ng-template #content>\n <div>\n <p-button label=\"Exportar todo\" (click)=\"exportChat('all')\" />\n <p-button label=\"Exportar conversaci\u00F3n\" (click)=\"exportChat('conversation')\" />\n <p-button [label]=\"'Vista ' + viewMode()\" (click)=\"toggleViewMode()\" />\n </div>\n <dc-prompt-preview [messages]=\"messageStateService.getMessagesSignal()()\" [view]=\"viewMode()\"></dc-prompt-preview>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"6\">\n <ng-template #content>\n <dc-messages-state-inspector [service]=\"messageStateService\"></dc-messages-state-inspector>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"3\">\n <ng-template #content>\n <dc-cost-details></dc-cost-details>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"4\">\n <ng-template #content>\n <!-- MORE INFO -->\n\n <details>\n <summary>Estado Global Conversation Flow</summary>\n <p>Goal</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.goal | safeJson }}</pre>\n <p>Trigger User Message</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.triggerTasks?.onUserMessage | safeJson }}</pre>\n <p>Trigger Assistant Message</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.triggerTasks?.onAssistantMessage | safeJson }}</pre>\n <p>Conditions</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.dynamicConditions | safeJson }}</pre>\n <p>Tools</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.tools | safeJson }}</pre>\n <p>Challenges</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.challenges | safeJson }}</pre>\n </details>\n\n <details>\n <summary>Conversation Settings (Debug)</summary>\n <pre>{{ conversationService.conversationSettings$() | safeJson }}</pre>\n </details>\n\n <details>\n <summary>Agent Card</summary>\n <pre>{{ agentCard | safeJson }}</pre>\n </details>\n\n <details>\n <summary>User Settings</summary>\n <pre>{{ chatUserSettings | safeJson }}</pre>\n </details>\n\n <!-- MORE INFO ends Here -->\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"7\">\n <ng-template #content>\n <dc-agent-card-detail [entityData]=\"agentCardStateService.agentCard$()\" />\n </ng-template>\n </p-tabpanel>\n </p-tabpanels>\n </p-tabs>\n</div>\n", styles: [":host{overflow-y:scroll}.info-content{display:flex;flex-direction:column;gap:1rem;padding:1rem}\n"] }]
|
|
6300
6618
|
}], ctorParameters: () => [], propDecorators: { completeEvent: [{ type: i0.Output, args: ["completeEvent"] }] } });
|
|
6301
6619
|
|
|
6302
6620
|
class ConversationInfoService {
|
|
@@ -6347,7 +6665,9 @@ class DCChatComponent {
|
|
|
6347
6665
|
// 📥 Input Signals
|
|
6348
6666
|
this.parseDict = input({}, ...(ngDevMode ? [{ debugName: "parseDict" }] : /* istanbul ignore next */ []));
|
|
6349
6667
|
// 📤 Outputs
|
|
6668
|
+
/** @deprecated Use ChatMonitorService instead — el canal oficial es el servicio compartido. */
|
|
6350
6669
|
this.chatEvent = output(); // Notifies about various events happening inside the chat
|
|
6670
|
+
/** @deprecated Use ChatMonitorService instead — el canal oficial es el servicio compartido. */
|
|
6351
6671
|
this.goalCompleted = output(); // notifies when user completes goal (score reaches 100)
|
|
6352
6672
|
// readonly wordClicked = output<WordData>(); // Replaced by sendMessage with ChatEventType.WordClicked
|
|
6353
6673
|
// Signals States
|
|
@@ -6360,6 +6680,7 @@ class DCChatComponent {
|
|
|
6360
6680
|
effect(() => {
|
|
6361
6681
|
const score = this.conversationFlowStateService.flowState().goal.value;
|
|
6362
6682
|
if (score >= 100) {
|
|
6683
|
+
// TODO: check if i can use the monitor in the father so subscribe to this.
|
|
6363
6684
|
this.goalCompleted.emit();
|
|
6364
6685
|
}
|
|
6365
6686
|
});
|
|
@@ -6373,19 +6694,6 @@ class DCChatComponent {
|
|
|
6373
6694
|
// this.conversationService.notifyWordClicked(null);
|
|
6374
6695
|
}
|
|
6375
6696
|
});
|
|
6376
|
-
// Effect to watch for audio playback events
|
|
6377
|
-
effect(() => {
|
|
6378
|
-
const message = this.chatMonitorService.messageAudioWillPlay$();
|
|
6379
|
-
if (message) {
|
|
6380
|
-
this.chatEvent.emit({ type: ChatEventType.AudioStarted, payload: message });
|
|
6381
|
-
}
|
|
6382
|
-
});
|
|
6383
|
-
// Subscribe to mood updates
|
|
6384
|
-
this.moodSubscription = this.conversationFlowStateService.moodUpdated$.subscribe((mood) => {
|
|
6385
|
-
if (mood) {
|
|
6386
|
-
this.chatEvent.emit({ type: ChatEventType.MoodDetected, payload: mood });
|
|
6387
|
-
}
|
|
6388
|
-
});
|
|
6389
6697
|
}
|
|
6390
6698
|
async ngOnInit() {
|
|
6391
6699
|
this.conversationService.setDestroyed(false);
|
|
@@ -6406,7 +6714,6 @@ class DCChatComponent {
|
|
|
6406
6714
|
// Mark conversation as destroyed to prevent async operations
|
|
6407
6715
|
this.conversationService.setDestroyed(true);
|
|
6408
6716
|
this.chatMonitorService.cleanMonitorVars();
|
|
6409
|
-
this.moodSubscription?.unsubscribe();
|
|
6410
6717
|
// Ensure the microphone is stopped when the chat component is destroyed
|
|
6411
6718
|
this.chatFooterComponent?.stopMic();
|
|
6412
6719
|
this.checkPerformanceAnalysis();
|
|
@@ -6476,7 +6783,8 @@ class DCChatComponent {
|
|
|
6476
6783
|
ConversationFlowStateService,
|
|
6477
6784
|
DynamicFlowService,
|
|
6478
6785
|
AgentCardStateService,
|
|
6479
|
-
ChatMonitorService
|
|
6786
|
+
// ChatMonitorService removido del providers[] local: ahora la instancia la provee el
|
|
6787
|
+
// componente ancestro (ConversationCardDetailsComponent) para compartirla con dc-agent-card-avatar.
|
|
6480
6788
|
ContextEngineService,
|
|
6481
6789
|
], viewQueries: [{ propertyName: "chatFooterComponent", first: true, predicate: ChatFooterComponent, descendants: true }], ngImport: i0, template: "<div class=\"chat-container\">\n <dc-chat-header\n [agentCard]=\"agentCard\"\n [viewMode]=\"viewMode()\"\n (showInfoEvent)=\"showInfo()\"\n (settingsClickEvent)=\"changeUserChatSettings()\"\n (restartConversationEvent)=\"restartConversation($event)\"\n (completeEvent)=\"complete()\"\n (viewModeChanged)=\"viewMode.set($event)\">\n </dc-chat-header>\n\n @if (viewMode() === 'chat') {\n <dc-chat-messages-list [chatUserSettings]=\"chatUserSettings\"> </dc-chat-messages-list>\n <dc-chat-footer [micSettings]=\"micSettings\"> </dc-chat-footer>\n } @else {\n <dc-immersive-chat [micSettings]=\"micSettings\"></dc-immersive-chat>\n }\n\n <dc-polito-notification></dc-polito-notification>\n <dc-observer-intervention [customConfig]=\"observerConfig\"></dc-observer-intervention>\n</div>\n", styles: ["::ng-deep .p-drawer-content{padding:0!important}::ng-deep .p-dialog-content{display:contents}.chat-container{display:flex;flex-direction:column;height:100%;max-height:100vh;overflow:hidden;background-color:transparent;position:relative}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;padding:1rem}.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: ["alternativeConversation", "agentCard", "viewMode"], outputs: ["restartConversationEvent", "showInfoEvent", "settingsClickEvent", "viewModeChanged"] }, { kind: "component", type: ChatFooterComponent, selector: "dc-chat-footer", inputs: ["isAIThinking", "micSettings"], outputs: ["sendMessage", "textInputChanged"] }, { kind: "component", type: ChatMessagesListComponent, selector: "dc-chat-messages-list", inputs: ["chatUserSettings"] }, { kind: "ngmodule", type: ProgressBarModule }, { kind: "component", type: DcImmersiveChatComponent, selector: "dc-immersive-chat", inputs: ["micSettings"] }, { kind: "component", type: PolitoNotificationComponent, selector: "dc-polito-notification" }, { kind: "component", type: DcObserverInterventionComponent, selector: "dc-observer-intervention", inputs: ["customConfig"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6482
6790
|
}
|
|
@@ -6503,7 +6811,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
6503
6811
|
ConversationFlowStateService,
|
|
6504
6812
|
DynamicFlowService,
|
|
6505
6813
|
AgentCardStateService,
|
|
6506
|
-
ChatMonitorService
|
|
6814
|
+
// ChatMonitorService removido del providers[] local: ahora la instancia la provee el
|
|
6815
|
+
// componente ancestro (ConversationCardDetailsComponent) para compartirla con dc-agent-card-avatar.
|
|
6507
6816
|
ContextEngineService,
|
|
6508
6817
|
], template: "<div class=\"chat-container\">\n <dc-chat-header\n [agentCard]=\"agentCard\"\n [viewMode]=\"viewMode()\"\n (showInfoEvent)=\"showInfo()\"\n (settingsClickEvent)=\"changeUserChatSettings()\"\n (restartConversationEvent)=\"restartConversation($event)\"\n (completeEvent)=\"complete()\"\n (viewModeChanged)=\"viewMode.set($event)\">\n </dc-chat-header>\n\n @if (viewMode() === 'chat') {\n <dc-chat-messages-list [chatUserSettings]=\"chatUserSettings\"> </dc-chat-messages-list>\n <dc-chat-footer [micSettings]=\"micSettings\"> </dc-chat-footer>\n } @else {\n <dc-immersive-chat [micSettings]=\"micSettings\"></dc-immersive-chat>\n }\n\n <dc-polito-notification></dc-polito-notification>\n <dc-observer-intervention [customConfig]=\"observerConfig\"></dc-observer-intervention>\n</div>\n", styles: ["::ng-deep .p-drawer-content{padding:0!important}::ng-deep .p-dialog-content{display:contents}.chat-container{display:flex;flex-direction:column;height:100%;max-height:100vh;overflow:hidden;background-color:transparent;position:relative}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;padding:1rem}.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"] }]
|
|
6509
6818
|
}], ctorParameters: () => [], propDecorators: { chatFooterComponent: [{
|
|
@@ -7496,7 +7805,7 @@ class CharacterFormGroupService {
|
|
|
7496
7805
|
conversationFlow: this.createConversationFlowFormGroup(),
|
|
7497
7806
|
manageable: this.formUtils.createManageableFormGroup(),
|
|
7498
7807
|
learnable: this.formUtils.createLearnableFormGroup(),
|
|
7499
|
-
|
|
7808
|
+
voice: this.createAgentVoiceFormGroup(),
|
|
7500
7809
|
agenticConfig: this.createAgenticConfigFormGroup(),
|
|
7501
7810
|
live2d: [null],
|
|
7502
7811
|
avatarType: [null],
|
|
@@ -7504,6 +7813,22 @@ class CharacterFormGroupService {
|
|
|
7504
7813
|
}
|
|
7505
7814
|
patchFormWithConversationData(form, agentCard) {
|
|
7506
7815
|
form.patchValue(agentCard);
|
|
7816
|
+
if (!agentCard.voice) {
|
|
7817
|
+
const voicePatch = {};
|
|
7818
|
+
if (agentCard.conversationSettings?.mainVoice) {
|
|
7819
|
+
voicePatch.main = agentCard.conversationSettings.mainVoice;
|
|
7820
|
+
}
|
|
7821
|
+
if (agentCard.conversationSettings?.secondaryVoice) {
|
|
7822
|
+
voicePatch.secondary = agentCard.conversationSettings.secondaryVoice;
|
|
7823
|
+
}
|
|
7824
|
+
if (agentCard.voiceCloning) {
|
|
7825
|
+
voicePatch.cloning = {
|
|
7826
|
+
sample: agentCard.voiceCloning.sample,
|
|
7827
|
+
transcription: agentCard.voiceCloning.transcription
|
|
7828
|
+
};
|
|
7829
|
+
}
|
|
7830
|
+
form.get('voice')?.patchValue(voicePatch);
|
|
7831
|
+
}
|
|
7507
7832
|
// Patch dynamicConditions FormArray after the main form is patched
|
|
7508
7833
|
const dynamicConditionsFormArray = form.get('conversationFlow.dynamicConditions');
|
|
7509
7834
|
dynamicConditionsFormArray.clear(); // Clear any existing default items
|
|
@@ -7673,8 +7998,6 @@ class CharacterFormGroupService {
|
|
|
7673
7998
|
userMustStart: [false],
|
|
7674
7999
|
streamResponses: [false],
|
|
7675
8000
|
displayMode: ['chat'],
|
|
7676
|
-
mainVoice: this.createVoiceTTSFormGroup(),
|
|
7677
|
-
secondaryVoice: this.createVoiceTTSFormGroup(),
|
|
7678
8001
|
model: createAIModelFormGroup(),
|
|
7679
8002
|
rules: this.fb.array([]),
|
|
7680
8003
|
chatMode: ['regular'],
|
|
@@ -7777,11 +8100,14 @@ class CharacterFormGroupService {
|
|
|
7777
8100
|
emoji: [criteria?.emoji || ''],
|
|
7778
8101
|
});
|
|
7779
8102
|
}
|
|
7780
|
-
|
|
8103
|
+
createAgentVoiceFormGroup(voice) {
|
|
7781
8104
|
return this.fb.group({
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
8105
|
+
main: this.createVoiceTTSFormGroup(voice?.main),
|
|
8106
|
+
secondary: this.createVoiceTTSFormGroup(voice?.secondary),
|
|
8107
|
+
cloning: this.fb.group({
|
|
8108
|
+
sample: [voice?.cloning?.sample || null],
|
|
8109
|
+
transcription: [voice?.cloning?.transcription || ''],
|
|
8110
|
+
})
|
|
7785
8111
|
});
|
|
7786
8112
|
}
|
|
7787
8113
|
createAgenticConfigFormGroup() {
|
|
@@ -8328,47 +8654,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
8328
8654
|
type: Output
|
|
8329
8655
|
}] } });
|
|
8330
8656
|
|
|
8331
|
-
class DcVoiceTtsFormComponent {
|
|
8332
|
-
constructor() {
|
|
8333
|
-
this.dialogService = inject(DialogService);
|
|
8334
|
-
this.cdr = inject(ChangeDetectorRef);
|
|
8335
|
-
this.form = input.required(...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
|
|
8336
|
-
this.title = input('Voice TTS Settings', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
8337
|
-
this.voiceTTSOptions = input([], ...(ngDevMode ? [{ debugName: "voiceTTSOptions" }] : /* istanbul ignore next */ []));
|
|
8338
|
-
this.fullForm = input(false, ...(ngDevMode ? [{ debugName: "fullForm" }] : /* istanbul ignore next */ []));
|
|
8339
|
-
this.providerOptions = [
|
|
8340
|
-
{ label: 'Google', value: 'google' },
|
|
8341
|
-
{ label: 'Fish Audio', value: 'fish-audio' },
|
|
8342
|
-
];
|
|
8343
|
-
}
|
|
8344
|
-
openVoiceSelector() {
|
|
8345
|
-
const voiceControl = this.form().get('voice');
|
|
8346
|
-
const dialogRef = this.dialogService.open(VoiceSelectorComponent, {
|
|
8347
|
-
header: 'Select Voice',
|
|
8348
|
-
width: '50vw',
|
|
8349
|
-
maximizable: true,
|
|
8350
|
-
height: '500px',
|
|
8351
|
-
contentStyle: { 'max-height': '90vh', overflow: 'auto', 'min-height': '500px' },
|
|
8352
|
-
baseZIndex: 10000,
|
|
8353
|
-
closable: true,
|
|
8354
|
-
modal: true,
|
|
8355
|
-
data: { currentVoiceId: voiceControl?.value },
|
|
8356
|
-
});
|
|
8357
|
-
dialogRef.onClose.subscribe((selectedVoiceId) => {
|
|
8358
|
-
if (selectedVoiceId) {
|
|
8359
|
-
voiceControl?.setValue(selectedVoiceId);
|
|
8360
|
-
this.cdr.detectChanges();
|
|
8361
|
-
}
|
|
8362
|
-
});
|
|
8363
|
-
}
|
|
8364
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcVoiceTtsFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
8365
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DcVoiceTtsFormComponent, isStandalone: true, selector: "dc-voice-tts-form", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, voiceTTSOptions: { classPropertyName: "voiceTTSOptions", publicName: "voiceTTSOptions", isSignal: true, isRequired: false, transformFunction: null }, fullForm: { classPropertyName: "fullForm", publicName: "fullForm", isSignal: true, isRequired: false, transformFunction: null } }, providers: [DialogService], ngImport: i0, template: "<h3> {{ title() }}</h3>\n<div [formGroup]=\"form()\" class=\"form-grid\">\n @if (fullForm()) {\n <div class=\"form-field\">\n <label for=\"provider\">Provider <span pTooltip=\"TTS provider\">\u2139\uFE0F</span></label>\n <p-select id=\"provider\" [options]=\"providerOptions\" formControlName=\"provider\" optionLabel=\"label\" optionValue=\"value\" placeholder=\"Select Provider\" class=\"w-full\"></p-select>\n </div>\n }\n\n <div class=\"form-field\">\n <label for=\"voice\">Voice <span pTooltip=\"Select the voice for text-to-speech\">\u2139\uFE0F</span></label>\n <p-inputgroup>\n <p-inputgroup-addon>\n <p-button [rounded]=\"true\" [text]=\"true\" icon=\"pi pi-exclamation-circle\" (click)=\"openVoiceSelector()\" />\n </p-inputgroup-addon>\n <p-select\n id=\"voice\"\n [editable]=\"true\"\n [options]=\"voiceTTSOptions()\"\n formControlName=\"voice\"\n optionLabel=\"name\"\n optionValue=\"id\"\n [placeholder]=\"'Select Voice'\" />\n </p-inputgroup>\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 <br />\n <input pInputText id=\"speedRate\" type=\"number\" formControlName=\"speedRate\" step=\"0.1\" />\n </div>\n\n @if (fullForm()) {\n <div class=\"form-field\">\n <label for=\"model\">Model <span pTooltip=\"Model ID if the provider requires it\">\u2139\uFE0F</span></label>\n <input pInputText id=\"model\" type=\"text\" formControlName=\"model\" />\n </div>\n\n <div class=\"form-field\">\n <label for=\"lang\">Language <span pTooltip=\"Language code override (optional, usually included in the voice ID)\">\u2139\uFE0F</span></label>\n <input pInputText id=\"lang\" type=\"text\" formControlName=\"lang\" />\n </div>\n\n <div class=\"form-field\">\n <label for=\"effect\">Effect <span pTooltip=\"Platform audio effect\">\u2139\uFE0F</span></label>\n <input pInputText id=\"effect\" type=\"text\" formControlName=\"effect\" />\n </div>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.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$2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: DynamicDialogModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: InputGroupModule }, { kind: "component", type: i3$4.InputGroup, selector: "p-inputgroup, p-inputGroup, p-input-group", inputs: ["styleClass"] }, { kind: "ngmodule", type: InputGroupAddonModule }, { kind: "component", type: i4$4.InputGroupAddon, selector: "p-inputgroup-addon, p-inputGroupAddon", inputs: ["style", "styleClass"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i6.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i3$3.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: CardModule }] }); }
|
|
8366
|
-
}
|
|
8367
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcVoiceTtsFormComponent, decorators: [{
|
|
8368
|
-
type: Component,
|
|
8369
|
-
args: [{ selector: 'dc-voice-tts-form', standalone: true, imports: [ReactiveFormsModule, DynamicDialogModule, ButtonModule, InputGroupModule, InputGroupAddonModule, SelectModule, InputTextModule, CardModule], providers: [DialogService], template: "<h3> {{ title() }}</h3>\n<div [formGroup]=\"form()\" class=\"form-grid\">\n @if (fullForm()) {\n <div class=\"form-field\">\n <label for=\"provider\">Provider <span pTooltip=\"TTS provider\">\u2139\uFE0F</span></label>\n <p-select id=\"provider\" [options]=\"providerOptions\" formControlName=\"provider\" optionLabel=\"label\" optionValue=\"value\" placeholder=\"Select Provider\" class=\"w-full\"></p-select>\n </div>\n }\n\n <div class=\"form-field\">\n <label for=\"voice\">Voice <span pTooltip=\"Select the voice for text-to-speech\">\u2139\uFE0F</span></label>\n <p-inputgroup>\n <p-inputgroup-addon>\n <p-button [rounded]=\"true\" [text]=\"true\" icon=\"pi pi-exclamation-circle\" (click)=\"openVoiceSelector()\" />\n </p-inputgroup-addon>\n <p-select\n id=\"voice\"\n [editable]=\"true\"\n [options]=\"voiceTTSOptions()\"\n formControlName=\"voice\"\n optionLabel=\"name\"\n optionValue=\"id\"\n [placeholder]=\"'Select Voice'\" />\n </p-inputgroup>\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 <br />\n <input pInputText id=\"speedRate\" type=\"number\" formControlName=\"speedRate\" step=\"0.1\" />\n </div>\n\n @if (fullForm()) {\n <div class=\"form-field\">\n <label for=\"model\">Model <span pTooltip=\"Model ID if the provider requires it\">\u2139\uFE0F</span></label>\n <input pInputText id=\"model\" type=\"text\" formControlName=\"model\" />\n </div>\n\n <div class=\"form-field\">\n <label for=\"lang\">Language <span pTooltip=\"Language code override (optional, usually included in the voice ID)\">\u2139\uFE0F</span></label>\n <input pInputText id=\"lang\" type=\"text\" formControlName=\"lang\" />\n </div>\n\n <div class=\"form-field\">\n <label for=\"effect\">Effect <span pTooltip=\"Platform audio effect\">\u2139\uFE0F</span></label>\n <input pInputText id=\"effect\" type=\"text\" formControlName=\"effect\" />\n </div>\n }\n</div>\n" }]
|
|
8370
|
-
}], propDecorators: { form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: true }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], voiceTTSOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "voiceTTSOptions", required: false }] }], fullForm: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullForm", required: false }] }] } });
|
|
8371
|
-
|
|
8372
8657
|
class PromptConversationTypesDialogComponent {
|
|
8373
8658
|
constructor() {
|
|
8374
8659
|
this.dialogRef = inject(DynamicDialogRef);
|
|
@@ -8438,12 +8723,6 @@ class DcConversationSettingsFormComponent {
|
|
|
8438
8723
|
get rulesFormArray() {
|
|
8439
8724
|
return this.form.get('rules');
|
|
8440
8725
|
}
|
|
8441
|
-
get mainVoiceFormGroup() {
|
|
8442
|
-
return this.form.get('mainVoice');
|
|
8443
|
-
}
|
|
8444
|
-
get secondaryVoiceFormGroup() {
|
|
8445
|
-
return this.form.get('secondaryVoice');
|
|
8446
|
-
}
|
|
8447
8726
|
get modelFormGroup() {
|
|
8448
8727
|
return this.form.get('model');
|
|
8449
8728
|
}
|
|
@@ -8475,7 +8754,7 @@ class DcConversationSettingsFormComponent {
|
|
|
8475
8754
|
this.getRules();
|
|
8476
8755
|
}
|
|
8477
8756
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcConversationSettingsFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
8478
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DcConversationSettingsFormComponent, isStandalone: true, selector: "dc-conversation-settings-form", inputs: { form: "form", textEngineOptions: "textEngineOptions", conversationOptions: "conversationOptions", voiceTTSOptions: "voiceTTSOptions" }, providers: [DialogService], ngImport: i0, template: "<div [formGroup]=\"form\">\n <h3 class=\"text-xl font-bold border-b pb-2\">\n Chat Conversation Settings\n <span pTooltip=\"Additional information about the conversation use in the chat\" class=\"text-blue-500 cursor-help text-sm\">\u2139\uFE0F</span>\n </h3>\n\n <div>\n <label for=\"rules\" class=\"block text-sm font-medium\">\n Conversation Rules\n <span pTooltip=\"Add rules to the conversation\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select id=\"rules\" [options]=\"rules\" (onChange)=\"addRule($event.value)\" placeholder=\"Select a Rule\" optionLabel=\"name\" class=\"w-full\"></p-select>\n\n <div formArrayName=\"rules\" class=\"mt-4 space-y-2\">\n @for (rule of rulesFormArray.controls; track rule; let i = $index) {\n <div [formGroupName]=\"i\" class=\"flex items-center justify-between p-2 border rounded-md\">\n <span>{{ rule.value.name }}</span>\n <button pButton type=\"button\" icon=\"pi pi-trash\" class=\"p-button-danger p-button-text\" (click)=\"removeRule(i)\"></button>\n </div>\n }\n </div>\n </div>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div>\n <label for=\"conversationType\" class=\"block text-sm font-medium\">\n Conversation Type\n <span class=\"cursor-pointer text-blue-500\" (click)=\"openConversationTypeDialog()\" pTooltip=\"Choose the type of conversation interaction\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"conversationType\"\n [options]=\"conversationOptions\"\n formControlName=\"conversationType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Conversation Type'\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"textEngine\" class=\"block text-sm font-medium\">\n Text Engine\n <span\n class=\"cursor-pointer text-blue-500\"\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 <p-select\n id=\"textEngine\"\n [options]=\"textEngineOptions\"\n formControlName=\"textEngine\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Text Engine'\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n Auto Start\n <span pTooltip=\"Start conversation automatically\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"autoStart\"></p-toggleSwitch>\n </div>\n\n <div class=\"md:col-span-2\">\n <label for=\"displayMode\" class=\"block text-sm font-medium text-red-500\">\n Display Mode (Deprecated Soon)\n <span pTooltip=\"[Deprecated] Use Chat Mode and Avatar Display Mode instead.\" class=\"text-red-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"displayMode\"\n [options]=\"displayModeOptions\"\n formControlName=\"displayMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Display Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"chatMode\" class=\"block text-sm font-medium\">\n Chat Mode\n <span pTooltip=\"regular: standard scrolling message list | immersive: voice-first Heads-Up Display (HUD)\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"chatMode\"\n [options]=\"chatModeOptions\"\n formControlName=\"chatMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Chat Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"avatarDisplayMode\" class=\"block text-sm font-medium\">\n Avatar Display Mode\n <span pTooltip=\"card: enclosed box layout | transparent: floats overlaying background | fullscreen: stretches model to fill page behind chat\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"avatarDisplayMode\"\n [options]=\"avatarDisplayModeOptions\"\n formControlName=\"avatarDisplayMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Avatar Display Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n User Must Start\n <span pTooltip=\"If true the user must start the conversation, ignoring first message if any.\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"userMustStart\"></p-toggleSwitch>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n Stream Responses\n <span pTooltip=\"Enable real-time token streaming for LLM responses.\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"streamResponses\"></p-toggleSwitch>\n </div>\n </div>\n\n <hr />\n\n <dc-voice-tts-form [form]=\"mainVoiceFormGroup\" [title]=\"'Main Voice TTS Settings'\" [voiceTTSOptions]=\"voiceTTSOptions\"></dc-voice-tts-form>\n\n <dc-voice-tts-form [form]=\"secondaryVoiceFormGroup\" [title]=\"'Secondary Voice TTS Settings'\" [voiceTTSOptions]=\"voiceTTSOptions\"></dc-voice-tts-form>\n\n <hr />\n\n <dc-model-selector [modelForm]=\"modelFormGroup\" [shortForm]=\"true\"></dc-model-selector>\n <hr />\n</div>\n\n<p-popover #textEngineDialog [style]=\"{ width: '350px' }\" header=\"Text Engine Information\">\n <ng-template pTemplate=\"content\">\n <div class=\"p-4\">\n <h3 class=\"text-md font-semibold mb-3 text-gray-800\">Text Engine Types</h3>\n <ul class=\"space-y-3 text-sm text-gray-600\">\n <li>\n <strong class=\"font-semibold text-gray-900\">Texto Simple:</strong>\n La conversaci\u00F3n es como chatgpt, preguntas y responde, es la m\u00E1s b\u00E1sica.\n </li>\n <li>\n <strong class=\"font-semibold text-gray-900\">Multi Mensajes:</strong>\n Utiliza markdown (recomendable entenderlo), para dar formato al texto. El sistema puede partir dialogos con distinto formato (normal, cursiva,\n negritas) para generar distintas voces y estilos para el narrador y personaje.\n </li>\n <li>\n <strong class=\"font-semibold text-gray-900\">MD SSML:</strong>\n Markdown con SSML. Similar a Multi Mensajes, pero se presenta en un solo mensaje y la voz se genera para toda la linea. \u00DAtil para conversaciones\n biling\u00FCes.\n </li>\n </ul>\n </div>\n </ng-template>\n</p-popover>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$2.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i1$2.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "directive", type: i3$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i6.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: ToggleSwitchModule }, { kind: "component", type: i4$3.ToggleSwitch, selector: "p-toggleswitch, p-toggleSwitch, p-toggle-switch", inputs: ["styleClass", "tabindex", "inputId", "readonly", "trueValue", "falseValue", "ariaLabel", "size", "ariaLabelledBy", "autofocus"], outputs: ["onChange"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i2$2.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: DynamicDialogModule }, { kind: "component", type: ModelSelectorComponent, selector: "dc-model-selector", inputs: ["modelForm", "shortForm"] }, { kind: "component", type: DcVoiceTtsFormComponent, selector: "dc-voice-tts-form", inputs: ["form", "title", "voiceTTSOptions", "fullForm"] }] }); }
|
|
8757
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DcConversationSettingsFormComponent, isStandalone: true, selector: "dc-conversation-settings-form", inputs: { form: "form", textEngineOptions: "textEngineOptions", conversationOptions: "conversationOptions", voiceTTSOptions: "voiceTTSOptions" }, providers: [DialogService], ngImport: i0, template: "<div [formGroup]=\"form\">\n <h3 class=\"text-xl font-bold border-b pb-2\">\n Chat Conversation Settings\n <span pTooltip=\"Additional information about the conversation use in the chat\" class=\"text-blue-500 cursor-help text-sm\">\u2139\uFE0F</span>\n </h3>\n\n <div>\n <label for=\"rules\" class=\"block text-sm font-medium\">\n Conversation Rules\n <span pTooltip=\"Add rules to the conversation\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select id=\"rules\" [options]=\"rules\" (onChange)=\"addRule($event.value)\" placeholder=\"Select a Rule\" optionLabel=\"name\" class=\"w-full\"></p-select>\n\n <div formArrayName=\"rules\" class=\"mt-4 space-y-2\">\n @for (rule of rulesFormArray.controls; track rule; let i = $index) {\n <div [formGroupName]=\"i\" class=\"flex items-center justify-between p-2 border rounded-md\">\n <span>{{ rule.value.name }}</span>\n <button pButton type=\"button\" icon=\"pi pi-trash\" class=\"p-button-danger p-button-text\" (click)=\"removeRule(i)\"></button>\n </div>\n }\n </div>\n </div>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div>\n <label for=\"conversationType\" class=\"block text-sm font-medium\">\n Conversation Type\n <span class=\"cursor-pointer text-blue-500\" (click)=\"openConversationTypeDialog()\" pTooltip=\"Choose the type of conversation interaction\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"conversationType\"\n [options]=\"conversationOptions\"\n formControlName=\"conversationType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Conversation Type'\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"textEngine\" class=\"block text-sm font-medium\">\n Text Engine\n <span\n class=\"cursor-pointer text-blue-500\"\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 <p-select\n id=\"textEngine\"\n [options]=\"textEngineOptions\"\n formControlName=\"textEngine\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Text Engine'\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n Auto Start\n <span pTooltip=\"Start conversation automatically\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"autoStart\"></p-toggleSwitch>\n </div>\n\n <div class=\"md:col-span-2\">\n <label for=\"displayMode\" class=\"block text-sm font-medium text-red-500\">\n Display Mode (Deprecated Soon)\n <span pTooltip=\"[Deprecated] Use Chat Mode and Avatar Display Mode instead.\" class=\"text-red-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"displayMode\"\n [options]=\"displayModeOptions\"\n formControlName=\"displayMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Display Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"chatMode\" class=\"block text-sm font-medium\">\n Chat Mode\n <span pTooltip=\"regular: standard scrolling message list | immersive: voice-first Heads-Up Display (HUD)\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"chatMode\"\n [options]=\"chatModeOptions\"\n formControlName=\"chatMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Chat Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"avatarDisplayMode\" class=\"block text-sm font-medium\">\n Avatar Display Mode\n <span pTooltip=\"card: enclosed box layout | transparent: floats overlaying background | fullscreen: stretches model to fill page behind chat\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"avatarDisplayMode\"\n [options]=\"avatarDisplayModeOptions\"\n formControlName=\"avatarDisplayMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Avatar Display Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n User Must Start\n <span pTooltip=\"If true the user must start the conversation, ignoring first message if any.\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"userMustStart\"></p-toggleSwitch>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n Stream Responses\n <span pTooltip=\"Enable real-time token streaming for LLM responses.\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"streamResponses\"></p-toggleSwitch>\n </div>\n </div>\n\n <hr />\n\n <dc-model-selector [modelForm]=\"modelFormGroup\" [shortForm]=\"true\"></dc-model-selector>\n <hr />\n</div>\n\n<p-popover #textEngineDialog [style]=\"{ width: '350px' }\" header=\"Text Engine Information\">\n <ng-template pTemplate=\"content\">\n <div class=\"p-4\">\n <h3 class=\"text-md font-semibold mb-3 text-gray-800\">Text Engine Types</h3>\n <ul class=\"space-y-3 text-sm text-gray-600\">\n <li>\n <strong class=\"font-semibold text-gray-900\">Texto Simple:</strong>\n La conversaci\u00F3n es como chatgpt, preguntas y responde, es la m\u00E1s b\u00E1sica.\n </li>\n <li>\n <strong class=\"font-semibold text-gray-900\">Multi Mensajes:</strong>\n Utiliza markdown (recomendable entenderlo), para dar formato al texto. El sistema puede partir dialogos con distinto formato (normal, cursiva,\n negritas) para generar distintas voces y estilos para el narrador y personaje.\n </li>\n <li>\n <strong class=\"font-semibold text-gray-900\">MD SSML:</strong>\n Markdown con SSML. Similar a Multi Mensajes, pero se presenta en un solo mensaje y la voz se genera para toda la linea. \u00DAtil para conversaciones\n biling\u00FCes.\n </li>\n </ul>\n </div>\n </ng-template>\n</p-popover>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$2.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i1$2.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "directive", type: i3$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i6.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: ToggleSwitchModule }, { kind: "component", type: i4$3.ToggleSwitch, selector: "p-toggleswitch, p-toggleSwitch, p-toggle-switch", inputs: ["styleClass", "tabindex", "inputId", "readonly", "trueValue", "falseValue", "ariaLabel", "size", "ariaLabelledBy", "autofocus"], outputs: ["onChange"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i2$2.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: DynamicDialogModule }, { kind: "component", type: ModelSelectorComponent, selector: "dc-model-selector", inputs: ["modelForm", "shortForm"] }] }); }
|
|
8479
8758
|
}
|
|
8480
8759
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcConversationSettingsFormComponent, decorators: [{
|
|
8481
8760
|
type: Component,
|
|
@@ -8489,8 +8768,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
8489
8768
|
PopoverModule,
|
|
8490
8769
|
DynamicDialogModule,
|
|
8491
8770
|
ModelSelectorComponent,
|
|
8492
|
-
|
|
8493
|
-
], providers: [DialogService], template: "<div [formGroup]=\"form\">\n <h3 class=\"text-xl font-bold border-b pb-2\">\n Chat Conversation Settings\n <span pTooltip=\"Additional information about the conversation use in the chat\" class=\"text-blue-500 cursor-help text-sm\">\u2139\uFE0F</span>\n </h3>\n\n <div>\n <label for=\"rules\" class=\"block text-sm font-medium\">\n Conversation Rules\n <span pTooltip=\"Add rules to the conversation\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select id=\"rules\" [options]=\"rules\" (onChange)=\"addRule($event.value)\" placeholder=\"Select a Rule\" optionLabel=\"name\" class=\"w-full\"></p-select>\n\n <div formArrayName=\"rules\" class=\"mt-4 space-y-2\">\n @for (rule of rulesFormArray.controls; track rule; let i = $index) {\n <div [formGroupName]=\"i\" class=\"flex items-center justify-between p-2 border rounded-md\">\n <span>{{ rule.value.name }}</span>\n <button pButton type=\"button\" icon=\"pi pi-trash\" class=\"p-button-danger p-button-text\" (click)=\"removeRule(i)\"></button>\n </div>\n }\n </div>\n </div>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div>\n <label for=\"conversationType\" class=\"block text-sm font-medium\">\n Conversation Type\n <span class=\"cursor-pointer text-blue-500\" (click)=\"openConversationTypeDialog()\" pTooltip=\"Choose the type of conversation interaction\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"conversationType\"\n [options]=\"conversationOptions\"\n formControlName=\"conversationType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Conversation Type'\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"textEngine\" class=\"block text-sm font-medium\">\n Text Engine\n <span\n class=\"cursor-pointer text-blue-500\"\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 <p-select\n id=\"textEngine\"\n [options]=\"textEngineOptions\"\n formControlName=\"textEngine\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Text Engine'\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n Auto Start\n <span pTooltip=\"Start conversation automatically\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"autoStart\"></p-toggleSwitch>\n </div>\n\n <div class=\"md:col-span-2\">\n <label for=\"displayMode\" class=\"block text-sm font-medium text-red-500\">\n Display Mode (Deprecated Soon)\n <span pTooltip=\"[Deprecated] Use Chat Mode and Avatar Display Mode instead.\" class=\"text-red-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"displayMode\"\n [options]=\"displayModeOptions\"\n formControlName=\"displayMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Display Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"chatMode\" class=\"block text-sm font-medium\">\n Chat Mode\n <span pTooltip=\"regular: standard scrolling message list | immersive: voice-first Heads-Up Display (HUD)\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"chatMode\"\n [options]=\"chatModeOptions\"\n formControlName=\"chatMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Chat Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"avatarDisplayMode\" class=\"block text-sm font-medium\">\n Avatar Display Mode\n <span pTooltip=\"card: enclosed box layout | transparent: floats overlaying background | fullscreen: stretches model to fill page behind chat\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"avatarDisplayMode\"\n [options]=\"avatarDisplayModeOptions\"\n formControlName=\"avatarDisplayMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Avatar Display Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n User Must Start\n <span pTooltip=\"If true the user must start the conversation, ignoring first message if any.\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"userMustStart\"></p-toggleSwitch>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n Stream Responses\n <span pTooltip=\"Enable real-time token streaming for LLM responses.\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"streamResponses\"></p-toggleSwitch>\n </div>\n </div>\n\n <hr />\n\n <dc-voice-tts-form [form]=\"mainVoiceFormGroup\" [title]=\"'Main Voice TTS Settings'\" [voiceTTSOptions]=\"voiceTTSOptions\"></dc-voice-tts-form>\n\n <dc-voice-tts-form [form]=\"secondaryVoiceFormGroup\" [title]=\"'Secondary Voice TTS Settings'\" [voiceTTSOptions]=\"voiceTTSOptions\"></dc-voice-tts-form>\n\n <hr />\n\n <dc-model-selector [modelForm]=\"modelFormGroup\" [shortForm]=\"true\"></dc-model-selector>\n <hr />\n</div>\n\n<p-popover #textEngineDialog [style]=\"{ width: '350px' }\" header=\"Text Engine Information\">\n <ng-template pTemplate=\"content\">\n <div class=\"p-4\">\n <h3 class=\"text-md font-semibold mb-3 text-gray-800\">Text Engine Types</h3>\n <ul class=\"space-y-3 text-sm text-gray-600\">\n <li>\n <strong class=\"font-semibold text-gray-900\">Texto Simple:</strong>\n La conversaci\u00F3n es como chatgpt, preguntas y responde, es la m\u00E1s b\u00E1sica.\n </li>\n <li>\n <strong class=\"font-semibold text-gray-900\">Multi Mensajes:</strong>\n Utiliza markdown (recomendable entenderlo), para dar formato al texto. El sistema puede partir dialogos con distinto formato (normal, cursiva,\n negritas) para generar distintas voces y estilos para el narrador y personaje.\n </li>\n <li>\n <strong class=\"font-semibold text-gray-900\">MD SSML:</strong>\n Markdown con SSML. Similar a Multi Mensajes, pero se presenta en un solo mensaje y la voz se genera para toda la linea. \u00DAtil para conversaciones\n biling\u00FCes.\n </li>\n </ul>\n </div>\n </ng-template>\n</p-popover>\n" }]
|
|
8771
|
+
], providers: [DialogService], template: "<div [formGroup]=\"form\">\n <h3 class=\"text-xl font-bold border-b pb-2\">\n Chat Conversation Settings\n <span pTooltip=\"Additional information about the conversation use in the chat\" class=\"text-blue-500 cursor-help text-sm\">\u2139\uFE0F</span>\n </h3>\n\n <div>\n <label for=\"rules\" class=\"block text-sm font-medium\">\n Conversation Rules\n <span pTooltip=\"Add rules to the conversation\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select id=\"rules\" [options]=\"rules\" (onChange)=\"addRule($event.value)\" placeholder=\"Select a Rule\" optionLabel=\"name\" class=\"w-full\"></p-select>\n\n <div formArrayName=\"rules\" class=\"mt-4 space-y-2\">\n @for (rule of rulesFormArray.controls; track rule; let i = $index) {\n <div [formGroupName]=\"i\" class=\"flex items-center justify-between p-2 border rounded-md\">\n <span>{{ rule.value.name }}</span>\n <button pButton type=\"button\" icon=\"pi pi-trash\" class=\"p-button-danger p-button-text\" (click)=\"removeRule(i)\"></button>\n </div>\n }\n </div>\n </div>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div>\n <label for=\"conversationType\" class=\"block text-sm font-medium\">\n Conversation Type\n <span class=\"cursor-pointer text-blue-500\" (click)=\"openConversationTypeDialog()\" pTooltip=\"Choose the type of conversation interaction\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"conversationType\"\n [options]=\"conversationOptions\"\n formControlName=\"conversationType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Conversation Type'\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"textEngine\" class=\"block text-sm font-medium\">\n Text Engine\n <span\n class=\"cursor-pointer text-blue-500\"\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 <p-select\n id=\"textEngine\"\n [options]=\"textEngineOptions\"\n formControlName=\"textEngine\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Text Engine'\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n Auto Start\n <span pTooltip=\"Start conversation automatically\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"autoStart\"></p-toggleSwitch>\n </div>\n\n <div class=\"md:col-span-2\">\n <label for=\"displayMode\" class=\"block text-sm font-medium text-red-500\">\n Display Mode (Deprecated Soon)\n <span pTooltip=\"[Deprecated] Use Chat Mode and Avatar Display Mode instead.\" class=\"text-red-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"displayMode\"\n [options]=\"displayModeOptions\"\n formControlName=\"displayMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Display Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"chatMode\" class=\"block text-sm font-medium\">\n Chat Mode\n <span pTooltip=\"regular: standard scrolling message list | immersive: voice-first Heads-Up Display (HUD)\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"chatMode\"\n [options]=\"chatModeOptions\"\n formControlName=\"chatMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Chat Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"avatarDisplayMode\" class=\"block text-sm font-medium\">\n Avatar Display Mode\n <span pTooltip=\"card: enclosed box layout | transparent: floats overlaying background | fullscreen: stretches model to fill page behind chat\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"avatarDisplayMode\"\n [options]=\"avatarDisplayModeOptions\"\n formControlName=\"avatarDisplayMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Avatar Display Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n User Must Start\n <span pTooltip=\"If true the user must start the conversation, ignoring first message if any.\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"userMustStart\"></p-toggleSwitch>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n Stream Responses\n <span pTooltip=\"Enable real-time token streaming for LLM responses.\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"streamResponses\"></p-toggleSwitch>\n </div>\n </div>\n\n <hr />\n\n <dc-model-selector [modelForm]=\"modelFormGroup\" [shortForm]=\"true\"></dc-model-selector>\n <hr />\n</div>\n\n<p-popover #textEngineDialog [style]=\"{ width: '350px' }\" header=\"Text Engine Information\">\n <ng-template pTemplate=\"content\">\n <div class=\"p-4\">\n <h3 class=\"text-md font-semibold mb-3 text-gray-800\">Text Engine Types</h3>\n <ul class=\"space-y-3 text-sm text-gray-600\">\n <li>\n <strong class=\"font-semibold text-gray-900\">Texto Simple:</strong>\n La conversaci\u00F3n es como chatgpt, preguntas y responde, es la m\u00E1s b\u00E1sica.\n </li>\n <li>\n <strong class=\"font-semibold text-gray-900\">Multi Mensajes:</strong>\n Utiliza markdown (recomendable entenderlo), para dar formato al texto. El sistema puede partir dialogos con distinto formato (normal, cursiva,\n negritas) para generar distintas voces y estilos para el narrador y personaje.\n </li>\n <li>\n <strong class=\"font-semibold text-gray-900\">MD SSML:</strong>\n Markdown con SSML. Similar a Multi Mensajes, pero se presenta en un solo mensaje y la voz se genera para toda la linea. \u00DAtil para conversaciones\n biling\u00FCes.\n </li>\n </ul>\n </div>\n </ng-template>\n</p-popover>\n" }]
|
|
8494
8772
|
}], propDecorators: { form: [{
|
|
8495
8773
|
type: Input
|
|
8496
8774
|
}], textEngineOptions: [{
|
|
@@ -8501,6 +8779,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
8501
8779
|
type: Input
|
|
8502
8780
|
}] } });
|
|
8503
8781
|
|
|
8782
|
+
class DcVoiceTtsFormComponent {
|
|
8783
|
+
constructor() {
|
|
8784
|
+
this.dialogService = inject(DialogService);
|
|
8785
|
+
this.cdr = inject(ChangeDetectorRef);
|
|
8786
|
+
this.form = input.required(...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
|
|
8787
|
+
this.title = input('Voice TTS Settings', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
8788
|
+
this.voiceTTSOptions = input([], ...(ngDevMode ? [{ debugName: "voiceTTSOptions" }] : /* istanbul ignore next */ []));
|
|
8789
|
+
this.fullForm = input(false, ...(ngDevMode ? [{ debugName: "fullForm" }] : /* istanbul ignore next */ []));
|
|
8790
|
+
this.providerOptions = [
|
|
8791
|
+
{ label: 'Google', value: 'google' },
|
|
8792
|
+
{ label: 'Fish Audio', value: 'fish-audio' },
|
|
8793
|
+
];
|
|
8794
|
+
}
|
|
8795
|
+
openVoiceSelector() {
|
|
8796
|
+
const voiceControl = this.form().get('voice');
|
|
8797
|
+
const dialogRef = this.dialogService.open(VoiceSelectorComponent, {
|
|
8798
|
+
header: 'Select Voice',
|
|
8799
|
+
width: '50vw',
|
|
8800
|
+
maximizable: true,
|
|
8801
|
+
height: '500px',
|
|
8802
|
+
contentStyle: { 'max-height': '90vh', overflow: 'auto', 'min-height': '500px' },
|
|
8803
|
+
baseZIndex: 10000,
|
|
8804
|
+
closable: true,
|
|
8805
|
+
modal: true,
|
|
8806
|
+
data: { currentVoiceId: voiceControl?.value },
|
|
8807
|
+
});
|
|
8808
|
+
dialogRef.onClose.subscribe((selectedVoiceId) => {
|
|
8809
|
+
if (selectedVoiceId) {
|
|
8810
|
+
voiceControl?.setValue(selectedVoiceId);
|
|
8811
|
+
this.cdr.detectChanges();
|
|
8812
|
+
}
|
|
8813
|
+
});
|
|
8814
|
+
}
|
|
8815
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcVoiceTtsFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
8816
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DcVoiceTtsFormComponent, isStandalone: true, selector: "dc-voice-tts-form", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, voiceTTSOptions: { classPropertyName: "voiceTTSOptions", publicName: "voiceTTSOptions", isSignal: true, isRequired: false, transformFunction: null }, fullForm: { classPropertyName: "fullForm", publicName: "fullForm", isSignal: true, isRequired: false, transformFunction: null } }, providers: [DialogService], ngImport: i0, template: "<h3> {{ title() }}</h3>\n<div [formGroup]=\"form()\" class=\"form-grid\">\n @if (fullForm()) {\n <div class=\"form-field\">\n <label for=\"provider\">Provider <span pTooltip=\"TTS provider\">\u2139\uFE0F</span></label>\n <p-select id=\"provider\" [options]=\"providerOptions\" formControlName=\"provider\" optionLabel=\"label\" optionValue=\"value\" placeholder=\"Select Provider\" class=\"w-full\"></p-select>\n </div>\n }\n\n <div class=\"form-field\">\n <label for=\"voice\">Voice <span pTooltip=\"Select the voice for text-to-speech\">\u2139\uFE0F</span></label>\n <p-inputgroup>\n <p-inputgroup-addon>\n <p-button [rounded]=\"true\" [text]=\"true\" icon=\"pi pi-exclamation-circle\" (click)=\"openVoiceSelector()\" />\n </p-inputgroup-addon>\n <p-select\n id=\"voice\"\n [editable]=\"true\"\n [options]=\"voiceTTSOptions()\"\n formControlName=\"voice\"\n optionLabel=\"name\"\n optionValue=\"id\"\n [placeholder]=\"'Select Voice'\" />\n </p-inputgroup>\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 <br />\n <input pInputText id=\"speedRate\" type=\"number\" formControlName=\"speedRate\" step=\"0.1\" />\n </div>\n\n @if (fullForm()) {\n <div class=\"form-field\">\n <label for=\"model\">Model <span pTooltip=\"Model ID if the provider requires it\">\u2139\uFE0F</span></label>\n <input pInputText id=\"model\" type=\"text\" formControlName=\"model\" />\n </div>\n\n <div class=\"form-field\">\n <label for=\"lang\">Language <span pTooltip=\"Language code override (optional, usually included in the voice ID)\">\u2139\uFE0F</span></label>\n <input pInputText id=\"lang\" type=\"text\" formControlName=\"lang\" />\n </div>\n\n <div class=\"form-field\">\n <label for=\"effect\">Effect <span pTooltip=\"Platform audio effect\">\u2139\uFE0F</span></label>\n <input pInputText id=\"effect\" type=\"text\" formControlName=\"effect\" />\n </div>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.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$2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: DynamicDialogModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: InputGroupModule }, { kind: "component", type: i3$4.InputGroup, selector: "p-inputgroup, p-inputGroup, p-input-group", inputs: ["styleClass"] }, { kind: "ngmodule", type: InputGroupAddonModule }, { kind: "component", type: i4$4.InputGroupAddon, selector: "p-inputgroup-addon, p-inputGroupAddon", inputs: ["style", "styleClass"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i6.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i3$3.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: CardModule }] }); }
|
|
8817
|
+
}
|
|
8818
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcVoiceTtsFormComponent, decorators: [{
|
|
8819
|
+
type: Component,
|
|
8820
|
+
args: [{ selector: 'dc-voice-tts-form', standalone: true, imports: [ReactiveFormsModule, DynamicDialogModule, ButtonModule, InputGroupModule, InputGroupAddonModule, SelectModule, InputTextModule, CardModule], providers: [DialogService], template: "<h3> {{ title() }}</h3>\n<div [formGroup]=\"form()\" class=\"form-grid\">\n @if (fullForm()) {\n <div class=\"form-field\">\n <label for=\"provider\">Provider <span pTooltip=\"TTS provider\">\u2139\uFE0F</span></label>\n <p-select id=\"provider\" [options]=\"providerOptions\" formControlName=\"provider\" optionLabel=\"label\" optionValue=\"value\" placeholder=\"Select Provider\" class=\"w-full\"></p-select>\n </div>\n }\n\n <div class=\"form-field\">\n <label for=\"voice\">Voice <span pTooltip=\"Select the voice for text-to-speech\">\u2139\uFE0F</span></label>\n <p-inputgroup>\n <p-inputgroup-addon>\n <p-button [rounded]=\"true\" [text]=\"true\" icon=\"pi pi-exclamation-circle\" (click)=\"openVoiceSelector()\" />\n </p-inputgroup-addon>\n <p-select\n id=\"voice\"\n [editable]=\"true\"\n [options]=\"voiceTTSOptions()\"\n formControlName=\"voice\"\n optionLabel=\"name\"\n optionValue=\"id\"\n [placeholder]=\"'Select Voice'\" />\n </p-inputgroup>\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 <br />\n <input pInputText id=\"speedRate\" type=\"number\" formControlName=\"speedRate\" step=\"0.1\" />\n </div>\n\n @if (fullForm()) {\n <div class=\"form-field\">\n <label for=\"model\">Model <span pTooltip=\"Model ID if the provider requires it\">\u2139\uFE0F</span></label>\n <input pInputText id=\"model\" type=\"text\" formControlName=\"model\" />\n </div>\n\n <div class=\"form-field\">\n <label for=\"lang\">Language <span pTooltip=\"Language code override (optional, usually included in the voice ID)\">\u2139\uFE0F</span></label>\n <input pInputText id=\"lang\" type=\"text\" formControlName=\"lang\" />\n </div>\n\n <div class=\"form-field\">\n <label for=\"effect\">Effect <span pTooltip=\"Platform audio effect\">\u2139\uFE0F</span></label>\n <input pInputText id=\"effect\" type=\"text\" formControlName=\"effect\" />\n </div>\n }\n</div>\n" }]
|
|
8821
|
+
}], propDecorators: { form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: true }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], voiceTTSOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "voiceTTSOptions", required: false }] }], fullForm: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullForm", required: false }] }] } });
|
|
8822
|
+
|
|
8504
8823
|
class DCAgentCardFormComponent extends EntityBaseFormComponent {
|
|
8505
8824
|
constructor() {
|
|
8506
8825
|
super(...arguments);
|
|
@@ -8539,14 +8858,14 @@ class DCAgentCardFormComponent extends EntityBaseFormComponent {
|
|
|
8539
8858
|
this.isGenerating = signal(false, ...(ngDevMode ? [{ debugName: "isGenerating" }] : /* istanbul ignore next */ []));
|
|
8540
8859
|
this.isCloningVoice = signal(false, ...(ngDevMode ? [{ debugName: "isCloningVoice" }] : /* istanbul ignore next */ []));
|
|
8541
8860
|
this.canCloneVoice = computed(() => {
|
|
8542
|
-
const sampleUrl = this.form.get('
|
|
8543
|
-
const voiceId = this.form.get('
|
|
8861
|
+
const sampleUrl = this.form.get('voice.cloning.sample')?.value?.url;
|
|
8862
|
+
const voiceId = this.form.get('voice.main.voice')?.value;
|
|
8544
8863
|
const canCloneVoiceVar = !!sampleUrl && !voiceId;
|
|
8545
8864
|
return canCloneVoiceVar;
|
|
8546
8865
|
}, ...(ngDevMode ? [{ debugName: "canCloneVoice" }] : /* istanbul ignore next */ []));
|
|
8547
8866
|
this.fishModelId = computed(() => {
|
|
8548
|
-
const voiceId = this.form.get('
|
|
8549
|
-
const provider = this.form.get('
|
|
8867
|
+
const voiceId = this.form.get('voice.main.voice')?.value;
|
|
8868
|
+
const provider = this.form.get('voice.main.provider')?.value;
|
|
8550
8869
|
return voiceId && provider === 'fish-audio' ? voiceId : null;
|
|
8551
8870
|
}, ...(ngDevMode ? [{ debugName: "fishModelId" }] : /* istanbul ignore next */ []));
|
|
8552
8871
|
}
|
|
@@ -8769,15 +9088,15 @@ class DCAgentCardFormComponent extends EntityBaseFormComponent {
|
|
|
8769
9088
|
}
|
|
8770
9089
|
onFileUploaded(event) {
|
|
8771
9090
|
console.log('File uploaded', event);
|
|
8772
|
-
this.form.
|
|
9091
|
+
this.form.get('voice.cloning')?.patchValue({ sample: event });
|
|
8773
9092
|
}
|
|
8774
9093
|
async cloneVoice() {
|
|
8775
|
-
const sample = this.form.get('
|
|
9094
|
+
const sample = this.form.get('voice.cloning.sample')?.value;
|
|
8776
9095
|
if (!sample?.url) {
|
|
8777
9096
|
this.toastService?.error({ title: 'Falta el sample', subtitle: 'Sube un audio antes de crear el modelo' });
|
|
8778
9097
|
return;
|
|
8779
9098
|
}
|
|
8780
|
-
const text = this.form.get('
|
|
9099
|
+
const text = this.form.get('voice.cloning.transcription')?.value || '';
|
|
8781
9100
|
const agentName = this.form.get('name')?.value || 'Polilan';
|
|
8782
9101
|
const title = `${agentName} — voice`;
|
|
8783
9102
|
this.isCloningVoice.set(true);
|
|
@@ -8788,9 +9107,9 @@ class DCAgentCardFormComponent extends EntityBaseFormComponent {
|
|
|
8788
9107
|
text,
|
|
8789
9108
|
trainMode: 'fast',
|
|
8790
9109
|
});
|
|
8791
|
-
const
|
|
8792
|
-
|
|
8793
|
-
|
|
9110
|
+
const newMainCtrl = this.form.get('voice.main');
|
|
9111
|
+
newMainCtrl?.patchValue({ voice: res._id, provider: 'fish-audio' });
|
|
9112
|
+
newMainCtrl?.markAsDirty();
|
|
8794
9113
|
this.toastService?.success({ title: 'Modelo de voz creado', subtitle: res._id });
|
|
8795
9114
|
}
|
|
8796
9115
|
catch (e) {
|
|
@@ -8811,7 +9130,7 @@ class DCAgentCardFormComponent extends EntityBaseFormComponent {
|
|
|
8811
9130
|
this.router.navigate(['../../cards-creator'], { relativeTo: this.route });
|
|
8812
9131
|
}
|
|
8813
9132
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DCAgentCardFormComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
8814
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DCAgentCardFormComponent, isStandalone: true, selector: "dc-agent-form", inputs: { inputSettings: { classPropertyName: "inputSettings", publicName: "inputSettings", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSave: "onSave", onSelectLive2d: "onSelectLive2d" }, providers: [DialogService], usesInheritance: true, ngImport: i0, template: "<p-card>\n <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)=\"save()\" label=\"\uD83D\uDCBE Guardar cambios\"></button>\n </div>\n\n <div class=\"top-buttons\">\n <p-button [loading]=\"isGenerating()\" severity=\"help\" (click)=\"generateCharacter()\" label=\"Generar \uD83E\uDDBE\"></p-button>\n <p-button severity=\"help\" (click)=\"goToCardsCreator()\" label=\" Creador masivo \"></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 <p-button severity=\"danger\" (click)=\"deleteCard()\" icon=\"pi pi-trash\"></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 title=\"Main data\" >\n <div style=\"display: flex; gap: 15px\">\n <div class=\"form-field\">\n <label for=\"version\">Version: {{ form.get('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.get('id').value }} \u2139\uFE0F</span></label\n >\n </div>\n </div>\n\n\n <div class=\"form-field\">\n <label for=\"name\">Card Name <span pTooltip=\"Name of the Agent Card\">\u2139\uFE0F</span></label>\n <input pInputText id=\"name\" type=\"text\" formControlName=\"name\" />\n @if(form.get('name').errors?.['required'] && form.get('name').touched){\n <div class=\"error\"> Name is required </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"description\"> Description <span pTooltip=\"Description of the conversation\">\u2139\uFE0F</span></label>\n <input pInputText id=\"description\" type=\"text\" formControlName=\"description\" />\n @if(form.get('description').errors?.['required'] && form.get('description').touched){\n <div class=\"error\"> Description 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 [filter]=\"true\"\n formControlName=\"lang\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Language'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"agentType\">Agent Type <span pTooltip=\"Select the type of agent\">\u2139\uFE0F</span></label>\n <p-select id=\"agentType\" [options]=\"agentTypeOptions\" formControlName=\"agentType\" [placeholder]=\"'Select Agent Type'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"avatarType\">Avatar Type <span pTooltip=\"image: static picture | video: double looping video (ping-pong) | live2d: interactive 2D model with real-time audio lip-sync | vroid: 3D model (future)\">\u2139\uFE0F</span></label>\n <p-select\n id=\"avatarType\"\n [options]=\"avatarTypeOptions\"\n formControlName=\"avatarType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Avatar Type\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label>Avatar Live2D <span pTooltip=\"Selecciona el modelo Live2D para este agente\">\u2139\uFE0F</span></label>\n <div style=\"display: flex; align-items: center; gap: 12px; margin-top: 4px;\">\n @if (form.get('live2d')?.value; as live2d) {\n <div style=\"display: flex; align-items: center; gap: 8px; padding: 6px 12px; border: 1px solid var(--p-border-color); border-radius: 6px; background: var(--p-content-background);\">\n <img width=\"36px\" height=\"36px\" style=\"border-radius: 50%; object-fit: cover;\" [src]=\"live2d.image?.url || 'defaults/images/face-3.jpg'\" alt=\"live2d preview\" />\n <span style=\"font-weight: 500;\">{{ live2d.name }}</span>\n <p-button icon=\"pi pi-times\" [text]=\"true\" severity=\"danger\" (onClick)=\"form.patchValue({ live2d: null })\" />\n </div>\n }\n <p-button (onClick)=\"onSelectLive2d.emit()\" label=\"Seleccionar Live2D\" icon=\"pi pi-search\" [outlined]=\"true\" />\n </div>\n </div>\n </div>\n\n <p-accordion [multiple]=\"true\" >\n <p-accordion-panel value=\"manageable\">\n <p-accordion-header>\n <span>Manageable</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Controla visibilidad y acceso</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-manageable-form [form]=\"form.controls.manageable\"></dc-manageable-form>\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"learnable\">\n <p-accordion-header>\n <span>Learnable</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Aprendizaje y conocimiento</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-learnable-form [form]=\"form.controls.learnable\"></dc-learnable-form>\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"conversationSettings\">\n <p-accordion-header>\n <span>Conversation Settings</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Modelo, voz y configuraci\u00F3n del chat</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-conversation-settings-form\n [form]=\"form.controls.conversationSettings\"\n [textEngineOptions]=\"textEngineOptions\"\n [conversationOptions]=\"conversationOptions\"\n [voiceTTSOptions]=\"voiceTTSOptions\">\n </dc-conversation-settings-form>\n </p-accordion-content>\n </p-accordion-panel>\n @if(form.controls.conversationFlow){\n <p-accordion-panel value=\"conversationFlow\">\n <p-accordion-header>\n <span>Conversation Flow</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Objetivos, triggers y condiciones din\u00E1micas</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-conversation-flow-form [formGroup]=\"form.controls.conversationFlow\"></dc-conversation-flow-form>\n </p-accordion-content>\n </p-accordion-panel>\n }\n <p-accordion-panel value=\"accounts\">\n <p-accordion-header>\n <span>Gesti\u00F3n de cuentas</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Beta</span>\n </p-accordion-header>\n <p-accordion-content>\n @if(form.controls.accounts) {\n <account-platform-form [formArray]=\"form.controls.accounts\"></account-platform-form>\n }\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"voiceCloning\">\n <p-accordion-header>\n <span>Clonaci\u00F3n de voz</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Beta</span>\n </p-accordion-header>\n <p-accordion-content>\n <div formGroupName=\"voiceCloning\">\n <div class=\"form-field\">\n <label for=\"transcription\">Transcription</label>\n <textarea pTextarea id=\"transcription\" formControlName=\"transcription\" rows=\"3\"></textarea>\n </div>\n <div class=\"form-field\">\n <dc-simple-uploader\n buttonLabel=\"Subir audio\"\n [storagePath]=\"'conversation-cards/' + entityId()\"\n (fileUploaded)=\"onFileUploaded($event)\"></dc-simple-uploader>\n @if(form.get('voiceCloning.sample').value?.url){\n <audio controls [src]=\"form.get('voiceCloning.sample').value.url\"></audio>\n }\n </div>\n <div class=\"form-field voice-clone-status\">\n @if(!form.get('voiceCloning.sample').value?.url){\n <small class=\"muted\">Sube un audio para entrenar un modelo de voz.</small>\n }\n <!-- @if(canCloneVoice()){ -->\n <p-button\n icon=\"pi pi-bolt\"\n severity=\"help\"\n [loading]=\"isCloningVoice()\"\n label=\"Crear modelo en Fish Audio\"\n (click)=\"cloneVoice()\">\n </p-button>\n <!-- } -->\n @if(fishModelId()){\n <div class=\"model-tag\">\n <span>Modelo Fish Audio: <code>{{ fishModelId() }}</code></span>\n <p-button\n icon=\"pi pi-refresh\"\n severity=\"secondary\"\n [text]=\"true\"\n [loading]=\"isCloningVoice()\"\n label=\"Regenerar\"\n (click)=\"cloneVoice()\">\n </p-button>\n </div>\n }\n </div>\n <dc-voice-tts-form\n [form]=\"$any(form.get('voiceCloning.main'))\"\n [title]=\"'Voice Cloning \u2014 Main Voice'\"\n [voiceTTSOptions]=\"voiceTTSOptions\"\n [fullForm]=\"true\">\n </dc-voice-tts-form>\n </div>\n </p-accordion-content>\n </p-accordion-panel>\n @if(form.controls.agenticConfig) {\n <p-accordion-panel value=\"agenticConfig\">\n <p-accordion-header>\n <span>Configuraci\u00F3n Ag\u00E9ntica</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Motor ag\u00E9ntico, patrones y herramientas</span>\n </p-accordion-header>\n <p-accordion-content>\n <div formGroupName=\"agenticConfig\">\n <div class=\"form-field flex items-center gap-2 mb-4\">\n <p-checkbox id=\"agenticEnabled\" formControlName=\"enabled\" [binary]=\"true\"></p-checkbox>\n <label for=\"agenticEnabled\" class=\"cursor-pointer\">Habilitar Agente Aut\u00F3nomo</label>\n </div>\n\n @if(form.get('agenticConfig.enabled').value) {\n <div class=\"form-field mb-4\">\n <label for=\"agenticEngine\" class=\"block text-sm font-medium mb-1\">Motor Ag\u00E9ntico</label>\n <p-select\n id=\"agenticEngine\"\n [options]=\"agenticEngineOptions\"\n formControlName=\"engine\"\n [placeholder]=\"'Seleccionar Motor'\"\n class=\"w-full\"></p-select>\n </div>\n\n <div class=\"form-field mb-4\">\n <label for=\"agenticPattern\" class=\"block text-sm font-medium mb-1\">Patr\u00F3n Ag\u00E9ntico</label>\n <p-select\n id=\"agenticPattern\"\n [options]=\"agenticPatternOptions\"\n formControlName=\"pattern\"\n [placeholder]=\"'Seleccionar Patr\u00F3n'\"\n class=\"w-full\"></p-select>\n </div>\n\n <div class=\"form-field mb-4\">\n <label for=\"maxIterations\" class=\"block text-sm font-medium mb-1\">M\u00E1ximo de Iteraciones</label>\n <input pInputText id=\"maxIterations\" type=\"number\" formControlName=\"maxIterations\" class=\"w-full\" />\n </div>\n\n <div class=\"form-field mb-4\">\n <label class=\"block text-sm font-medium mb-2\">Herramientas Permitidas</label>\n <div class=\"flex flex-col gap-2 mt-2 pl-2\">\n @for (toolOpt of allowedToolsOptions; track toolOpt.value) {\n <div class=\"flex items-center gap-2\">\n <p-checkbox\n [inputId]=\"'tool_' + toolOpt.value\"\n [binary]=\"true\"\n [ngModel]=\"isToolAllowed(toolOpt.value)\"\n [ngModelOptions]=\"{standalone: true}\"\n (onChange)=\"toggleTool(toolOpt.value, $event.checked)\">\n </p-checkbox>\n <label [for]=\"'tool_' + toolOpt.value\" class=\"cursor-pointer text-sm\">{{ toolOpt.label }}</label>\n </div>\n }\n </div>\n </div>\n\n @if(form.get('agenticConfig.pattern').value === 'reflection') {\n <div class=\"form-field mb-4\">\n <label for=\"reflectionPrompt\" class=\"block text-sm font-medium mb-1\">Prompt de Reflexi\u00F3n</label>\n <textarea pTextarea id=\"reflectionPrompt\" formControlName=\"reflectionPrompt\" rows=\"3\" [autoResize]=\"true\" class=\"w-full\"></textarea>\n </div>\n }\n\n @if(form.get('agenticConfig.engine').value === 'os_hermes') {\n <div class=\"form-field mb-4\">\n <label for=\"connectionUrl\" class=\"block text-sm font-medium mb-1\">URL de Conexi\u00F3n</label>\n <input pInputText id=\"connectionUrl\" type=\"text\" formControlName=\"connectionUrl\" placeholder=\"ws://...\" class=\"w-full\" />\n </div>\n }\n\n <div class=\"form-field mt-6\">\n <h4 class=\"font-semibold text-base mb-2\">Modelo de Razonamiento</h4>\n <dc-model-selector [modelForm]=\"$any(form.get('agenticConfig.reasoningModel'))\" [shortForm]=\"true\"></dc-model-selector>\n </div>\n\n <div class=\"form-field mt-6\">\n <h4 class=\"font-semibold text-base mb-2\">Modelo de Ejecuci\u00F3n</h4>\n <dc-model-selector [modelForm]=\"$any(form.get('agenticConfig.executionModel'))\" [shortForm]=\"true\"></dc-model-selector>\n </div>\n }\n </div>\n </p-accordion-content>\n </p-accordion-panel>\n }\n </p-accordion>\n\n </div>\n\n <div class=\"right-column\">\n @if(entity() && entityId()){\n <assets-loader\n [assets]=\"entity().assets\"\n [storagePath]=\"'conversation-cards/' + entityId()\"\n (assetsChange)=\"onAssetsChange($event)\"\n (assetUpdate)=\"onUpdateAsset($event)\"\n (onFileSelected)=\"onImageSelected($event)\"></assets-loader>\n } @if(form.controls.characterCard){\n\n <br />\n <dc-character-card-form [characterCardForm]=\"form.controls.characterCard\" (generateMissingDataRequest)=\"generateMissingData()\">\n </dc-character-card-form>\n }\n </div>\n </div>\n </form>\n\n <div class=\"float-button\">\n <p-button icon=\"pi pi-eye\" (click)=\"inspect()\" severity=\"secondary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Inspeccionar\"> </p-button>\n\n <p-button icon=\"pi pi-save\" (click)=\"save()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n </div>\n</p-card>\n", styles: [".conversation-form{max-width:100%;padding:20px;border-radius:8px;box-shadow:0 2px 4px #0000001a}.conversation-form .card-group{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:flex;flex-wrap:wrap;gap:2rem;width:100%}.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.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;flex:1 1 calc(50% - 1rem);min-width:300px}@media(max-width:768px){.conversation-form .left-column,.conversation-form .right-column{flex:1 1 100%}}.conversation-form .card-group{padding:1rem;margin-bottom:1.5rem}.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%}.voice-clone-status{display:flex;flex-direction:column;gap:.5rem;margin:.75rem 0}.voice-clone-status .muted{color:var(--p-text-muted-color)}.voice-clone-status .model-tag{display:flex;align-items:center;gap:.75rem;padding:.5rem .75rem;background:var(--p-surface-100, #f3f4f6);border-radius:6px;font-size:.9rem}.voice-clone-status .model-tag code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.8rem}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.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$2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormArrayDirective, selector: "[formArray]", inputs: ["formArray"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$2.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "component", type: AssetsLoaderComponent, selector: "assets-loader", inputs: ["assets", "storagePath"], outputs: ["assetsChange", "assetUpdate", "onFileSelected"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i3$3.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i4$1.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i6.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: DynamicDialogModule }, { kind: "component", type: AccountPlatformForm, selector: "account-platform-form", inputs: ["formArray"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i2$6.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: AccordionModule }, { kind: "component", type: i8.Accordion, selector: "p-accordion", inputs: ["value", "multiple", "styleClass", "expandIcon", "collapseIcon", "selectOnFocus", "transitionOptions", "motionOptions"], outputs: ["valueChange", "onClose", "onOpen"] }, { kind: "component", type: i8.AccordionPanel, selector: "p-accordion-panel, p-accordionpanel", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: i8.AccordionHeader, selector: "p-accordion-header, p-accordionheader" }, { kind: "component", type: i8.AccordionContent, selector: "p-accordion-content, p-accordioncontent" }, { kind: "component", type: DCConversationFlowFormComponent, selector: "dc-conversation-flow-form", inputs: ["formGroup"] }, { kind: "component", type: DcCharacterCardFormComponent, selector: "dc-character-card-form", inputs: ["characterCardForm"], outputs: ["generateMissingDataRequest"] }, { kind: "ngmodule", type: MessageModule }, { kind: "component", type: DcConversationSettingsFormComponent, selector: "dc-conversation-settings-form", inputs: ["form", "textEngineOptions", "conversationOptions", "voiceTTSOptions"] }, { kind: "component", type: DcVoiceTtsFormComponent, selector: "dc-voice-tts-form", inputs: ["form", "title", "voiceTTSOptions", "fullForm"] }, { kind: "component", type: DcManageableFormComponent, selector: "dc-manageable-form", inputs: ["form"] }, { kind: "component", type: DcLearnableFormComponent, selector: "dc-learnable-form", inputs: ["form"] }, { kind: "component", type: SimpleUploaderComponent, selector: "dc-simple-uploader", inputs: ["storagePath", "buttonLabel", "accept", "disabled", "metadata", "provider"], outputs: ["fileUploaded", "uploadError"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i4.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["pTextareaPT", "pTextareaUnstyled", "autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "component", type: ModelSelectorComponent, selector: "dc-model-selector", inputs: ["modelForm", "shortForm"] }] }); }
|
|
9133
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DCAgentCardFormComponent, isStandalone: true, selector: "dc-agent-form", inputs: { inputSettings: { classPropertyName: "inputSettings", publicName: "inputSettings", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSave: "onSave", onSelectLive2d: "onSelectLive2d" }, providers: [DialogService], usesInheritance: true, ngImport: i0, template: "<p-card>\n <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)=\"save()\" label=\"\uD83D\uDCBE Guardar cambios\"></button>\n </div>\n\n <div class=\"top-buttons\">\n <p-button [loading]=\"isGenerating()\" severity=\"help\" (click)=\"generateCharacter()\" label=\"Generar \uD83E\uDDBE\"></p-button>\n <p-button severity=\"help\" (click)=\"goToCardsCreator()\" label=\" Creador masivo \"></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 <p-button severity=\"danger\" (click)=\"deleteCard()\" icon=\"pi pi-trash\"></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 title=\"Main data\" >\n <div style=\"display: flex; gap: 15px\">\n <div class=\"form-field\">\n <label for=\"version\">Version: {{ form.get('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.get('id').value }} \u2139\uFE0F</span></label\n >\n </div>\n </div>\n\n\n <div class=\"form-field\">\n <label for=\"name\">Card Name <span pTooltip=\"Name of the Agent Card\">\u2139\uFE0F</span></label>\n <input pInputText id=\"name\" type=\"text\" formControlName=\"name\" />\n @if(form.get('name').errors?.['required'] && form.get('name').touched){\n <div class=\"error\"> Name is required </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"description\"> Description <span pTooltip=\"Description of the conversation\">\u2139\uFE0F</span></label>\n <input pInputText id=\"description\" type=\"text\" formControlName=\"description\" />\n @if(form.get('description').errors?.['required'] && form.get('description').touched){\n <div class=\"error\"> Description 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 [filter]=\"true\"\n formControlName=\"lang\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Language'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"agentType\">Agent Type <span pTooltip=\"Select the type of agent\">\u2139\uFE0F</span></label>\n <p-select id=\"agentType\" [options]=\"agentTypeOptions\" formControlName=\"agentType\" [placeholder]=\"'Select Agent Type'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"avatarType\">Avatar Type <span pTooltip=\"image: static picture | video: double looping video (ping-pong) | live2d: interactive 2D model with real-time audio lip-sync | vroid: 3D model (future)\">\u2139\uFE0F</span></label>\n <p-select\n id=\"avatarType\"\n [options]=\"avatarTypeOptions\"\n formControlName=\"avatarType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Avatar Type\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label>Avatar Live2D <span pTooltip=\"Selecciona el modelo Live2D para este agente\">\u2139\uFE0F</span></label>\n <div style=\"display: flex; align-items: center; gap: 12px; margin-top: 4px;\">\n @if (form.get('live2d')?.value; as live2d) {\n <div style=\"display: flex; align-items: center; gap: 8px; padding: 6px 12px; border: 1px solid var(--p-border-color); border-radius: 6px; background: var(--p-content-background);\">\n <img width=\"36px\" height=\"36px\" style=\"border-radius: 50%; object-fit: cover;\" [src]=\"live2d.image?.url || 'defaults/images/face-3.jpg'\" alt=\"live2d preview\" />\n <span style=\"font-weight: 500;\">{{ live2d.name }}</span>\n <p-button icon=\"pi pi-times\" [text]=\"true\" severity=\"danger\" (onClick)=\"form.patchValue({ live2d: null })\" />\n </div>\n }\n <p-button (onClick)=\"onSelectLive2d.emit()\" label=\"Seleccionar Live2D\" icon=\"pi pi-search\" [outlined]=\"true\" />\n </div>\n </div>\n </div>\n\n <p-accordion [multiple]=\"true\" >\n <p-accordion-panel value=\"manageable\">\n <p-accordion-header>\n <span>Manageable</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Controla visibilidad y acceso</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-manageable-form [form]=\"form.controls.manageable\"></dc-manageable-form>\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"learnable\">\n <p-accordion-header>\n <span>Learnable</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Aprendizaje y conocimiento</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-learnable-form [form]=\"form.controls.learnable\"></dc-learnable-form>\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"conversationSettings\">\n <p-accordion-header>\n <span>Conversation Settings</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Modelo, voz y configuraci\u00F3n del chat</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-conversation-settings-form\n [form]=\"form.controls.conversationSettings\"\n [textEngineOptions]=\"textEngineOptions\"\n [conversationOptions]=\"conversationOptions\"\n [voiceTTSOptions]=\"voiceTTSOptions\">\n </dc-conversation-settings-form>\n </p-accordion-content>\n </p-accordion-panel>\n @if(form.controls.conversationFlow){\n <p-accordion-panel value=\"conversationFlow\">\n <p-accordion-header>\n <span>Conversation Flow</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Objetivos, triggers y condiciones din\u00E1micas</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-conversation-flow-form [formGroup]=\"form.controls.conversationFlow\"></dc-conversation-flow-form>\n </p-accordion-content>\n </p-accordion-panel>\n }\n <p-accordion-panel value=\"accounts\">\n <p-accordion-header>\n <span>Gesti\u00F3n de cuentas</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Beta</span>\n </p-accordion-header>\n <p-accordion-content>\n @if(form.controls.accounts) {\n <account-platform-form [formArray]=\"form.controls.accounts\"></account-platform-form>\n }\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"voice\">\n <p-accordion-header>\n <span>Voz</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Sintesis y Clonaci\u00F3n</span>\n\n </p-accordion-header>\n <p-accordion-content>\n <div formGroupName=\"voice\">\n <div formGroupName=\"cloning\">\n <div class=\"form-field\">\n <label for=\"new_transcription\">Transcription (Voice.Cloning)</label>\n <textarea pTextarea id=\"new_transcription\" formControlName=\"transcription\" rows=\"3\"></textarea>\n </div>\n <div class=\"form-field\">\n <dc-simple-uploader\n buttonLabel=\"Subir audio (Voice.Cloning)\"\n [storagePath]=\"'conversation-cards/' + entityId()\"\n (fileUploaded)=\"onFileUploaded($event)\"></dc-simple-uploader>\n @if(form.get('voice.cloning.sample')?.value?.url){\n <audio controls [src]=\"form.get('voice.cloning.sample').value.url\"></audio>\n }\n </div>\n </div>\n <div class=\"form-field voice-clone-status\">\n @if(!form.get('voice.cloning.sample')?.value?.url){\n <small class=\"muted\">Sube un audio para entrenar un modelo de voz en la nueva propiedad.</small>\n }\n <p-button\n icon=\"pi pi-bolt\"\n severity=\"help\"\n [loading]=\"isCloningVoice()\"\n label=\"Crear modelo en Fish Audio (Voice)\"\n (click)=\"cloneVoice()\">\n </p-button>\n </div>\n \n <dc-voice-tts-form\n [form]=\"$any(form.get('voice.main'))\"\n [title]=\"'Main Voice'\"\n [voiceTTSOptions]=\"voiceTTSOptions\"\n [fullForm]=\"true\">\n </dc-voice-tts-form>\n \n <dc-voice-tts-form\n [form]=\"$any(form.get('voice.secondary'))\"\n [title]=\"'Secondary Voice'\"\n [voiceTTSOptions]=\"voiceTTSOptions\"\n [fullForm]=\"true\">\n </dc-voice-tts-form>\n </div>\n </p-accordion-content>\n </p-accordion-panel>\n @if(form.controls.agenticConfig) {\n <p-accordion-panel value=\"agenticConfig\">\n <p-accordion-header>\n <span>Configuraci\u00F3n Ag\u00E9ntica</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Motor ag\u00E9ntico, patrones y herramientas</span>\n </p-accordion-header>\n <p-accordion-content>\n <div formGroupName=\"agenticConfig\">\n <div class=\"form-field flex items-center gap-2 mb-4\">\n <p-checkbox id=\"agenticEnabled\" formControlName=\"enabled\" [binary]=\"true\"></p-checkbox>\n <label for=\"agenticEnabled\" class=\"cursor-pointer\">Habilitar Agente Aut\u00F3nomo</label>\n </div>\n\n @if(form.get('agenticConfig.enabled').value) {\n <div class=\"form-field mb-4\">\n <label for=\"agenticEngine\" class=\"block text-sm font-medium mb-1\">Motor Ag\u00E9ntico</label>\n <p-select\n id=\"agenticEngine\"\n [options]=\"agenticEngineOptions\"\n formControlName=\"engine\"\n [placeholder]=\"'Seleccionar Motor'\"\n class=\"w-full\"></p-select>\n </div>\n\n <div class=\"form-field mb-4\">\n <label for=\"agenticPattern\" class=\"block text-sm font-medium mb-1\">Patr\u00F3n Ag\u00E9ntico</label>\n <p-select\n id=\"agenticPattern\"\n [options]=\"agenticPatternOptions\"\n formControlName=\"pattern\"\n [placeholder]=\"'Seleccionar Patr\u00F3n'\"\n class=\"w-full\"></p-select>\n </div>\n\n <div class=\"form-field mb-4\">\n <label for=\"maxIterations\" class=\"block text-sm font-medium mb-1\">M\u00E1ximo de Iteraciones</label>\n <input pInputText id=\"maxIterations\" type=\"number\" formControlName=\"maxIterations\" class=\"w-full\" />\n </div>\n\n <div class=\"form-field mb-4\">\n <label class=\"block text-sm font-medium mb-2\">Herramientas Permitidas</label>\n <div class=\"flex flex-col gap-2 mt-2 pl-2\">\n @for (toolOpt of allowedToolsOptions; track toolOpt.value) {\n <div class=\"flex items-center gap-2\">\n <p-checkbox\n [inputId]=\"'tool_' + toolOpt.value\"\n [binary]=\"true\"\n [ngModel]=\"isToolAllowed(toolOpt.value)\"\n [ngModelOptions]=\"{standalone: true}\"\n (onChange)=\"toggleTool(toolOpt.value, $event.checked)\">\n </p-checkbox>\n <label [for]=\"'tool_' + toolOpt.value\" class=\"cursor-pointer text-sm\">{{ toolOpt.label }}</label>\n </div>\n }\n </div>\n </div>\n\n @if(form.get('agenticConfig.pattern').value === 'reflection') {\n <div class=\"form-field mb-4\">\n <label for=\"reflectionPrompt\" class=\"block text-sm font-medium mb-1\">Prompt de Reflexi\u00F3n</label>\n <textarea pTextarea id=\"reflectionPrompt\" formControlName=\"reflectionPrompt\" rows=\"3\" [autoResize]=\"true\" class=\"w-full\"></textarea>\n </div>\n }\n\n @if(form.get('agenticConfig.engine').value === 'os_hermes') {\n <div class=\"form-field mb-4\">\n <label for=\"connectionUrl\" class=\"block text-sm font-medium mb-1\">URL de Conexi\u00F3n</label>\n <input pInputText id=\"connectionUrl\" type=\"text\" formControlName=\"connectionUrl\" placeholder=\"ws://...\" class=\"w-full\" />\n </div>\n }\n\n <div class=\"form-field mt-6\">\n <h4 class=\"font-semibold text-base mb-2\">Modelo de Razonamiento</h4>\n <dc-model-selector [modelForm]=\"$any(form.get('agenticConfig.reasoningModel'))\" [shortForm]=\"true\"></dc-model-selector>\n </div>\n\n <div class=\"form-field mt-6\">\n <h4 class=\"font-semibold text-base mb-2\">Modelo de Ejecuci\u00F3n</h4>\n <dc-model-selector [modelForm]=\"$any(form.get('agenticConfig.executionModel'))\" [shortForm]=\"true\"></dc-model-selector>\n </div>\n }\n </div>\n </p-accordion-content>\n </p-accordion-panel>\n }\n </p-accordion>\n\n </div>\n\n <div class=\"right-column\">\n @if(entity() && entityId()){\n <assets-loader\n [assets]=\"entity().assets\"\n [storagePath]=\"'conversation-cards/' + entityId()\"\n (assetsChange)=\"onAssetsChange($event)\"\n (assetUpdate)=\"onUpdateAsset($event)\"\n (onFileSelected)=\"onImageSelected($event)\"></assets-loader>\n } @if(form.controls.characterCard){\n\n <br />\n <dc-character-card-form [characterCardForm]=\"form.controls.characterCard\" (generateMissingDataRequest)=\"generateMissingData()\">\n </dc-character-card-form>\n }\n </div>\n </div>\n </form>\n\n <div class=\"float-button\">\n <p-button icon=\"pi pi-eye\" (click)=\"inspect()\" severity=\"secondary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Inspeccionar\"> </p-button>\n\n <p-button icon=\"pi pi-save\" (click)=\"save()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n </div>\n</p-card>\n", styles: [".conversation-form{max-width:100%;padding:20px;border-radius:8px;box-shadow:0 2px 4px #0000001a}.conversation-form .card-group{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:flex;flex-wrap:wrap;gap:2rem;width:100%}.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.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;flex:1 1 calc(50% - 1rem);min-width:300px}@media(max-width:768px){.conversation-form .left-column,.conversation-form .right-column{flex:1 1 100%}}.conversation-form .card-group{padding:1rem;margin-bottom:1.5rem}.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%}.voice-clone-status{display:flex;flex-direction:column;gap:.5rem;margin:.75rem 0}.voice-clone-status .muted{color:var(--p-text-muted-color)}.voice-clone-status .model-tag{display:flex;align-items:center;gap:.75rem;padding:.5rem .75rem;background:var(--p-surface-100, #f3f4f6);border-radius:6px;font-size:.9rem}.voice-clone-status .model-tag code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.8rem}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.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$2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormArrayDirective, selector: "[formArray]", inputs: ["formArray"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$2.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "component", type: AssetsLoaderComponent, selector: "assets-loader", inputs: ["assets", "storagePath"], outputs: ["assetsChange", "assetUpdate", "onFileSelected"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i3$3.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i4$1.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i6.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: DynamicDialogModule }, { kind: "component", type: AccountPlatformForm, selector: "account-platform-form", inputs: ["formArray"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i2$6.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: AccordionModule }, { kind: "component", type: i8.Accordion, selector: "p-accordion", inputs: ["value", "multiple", "styleClass", "expandIcon", "collapseIcon", "selectOnFocus", "transitionOptions", "motionOptions"], outputs: ["valueChange", "onClose", "onOpen"] }, { kind: "component", type: i8.AccordionPanel, selector: "p-accordion-panel, p-accordionpanel", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: i8.AccordionHeader, selector: "p-accordion-header, p-accordionheader" }, { kind: "component", type: i8.AccordionContent, selector: "p-accordion-content, p-accordioncontent" }, { kind: "component", type: DCConversationFlowFormComponent, selector: "dc-conversation-flow-form", inputs: ["formGroup"] }, { kind: "component", type: DcCharacterCardFormComponent, selector: "dc-character-card-form", inputs: ["characterCardForm"], outputs: ["generateMissingDataRequest"] }, { kind: "ngmodule", type: MessageModule }, { kind: "component", type: DcConversationSettingsFormComponent, selector: "dc-conversation-settings-form", inputs: ["form", "textEngineOptions", "conversationOptions", "voiceTTSOptions"] }, { kind: "component", type: DcVoiceTtsFormComponent, selector: "dc-voice-tts-form", inputs: ["form", "title", "voiceTTSOptions", "fullForm"] }, { kind: "component", type: DcManageableFormComponent, selector: "dc-manageable-form", inputs: ["form"] }, { kind: "component", type: DcLearnableFormComponent, selector: "dc-learnable-form", inputs: ["form"] }, { kind: "component", type: SimpleUploaderComponent, selector: "dc-simple-uploader", inputs: ["storagePath", "buttonLabel", "accept", "disabled", "metadata", "provider"], outputs: ["fileUploaded", "uploadError"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i4.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["pTextareaPT", "pTextareaUnstyled", "autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "component", type: ModelSelectorComponent, selector: "dc-model-selector", inputs: ["modelForm", "shortForm"] }] }); }
|
|
8815
9134
|
}
|
|
8816
9135
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DCAgentCardFormComponent, decorators: [{
|
|
8817
9136
|
type: Component,
|
|
@@ -8839,7 +9158,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
8839
9158
|
SimpleUploaderComponent,
|
|
8840
9159
|
TextareaModule,
|
|
8841
9160
|
ModelSelectorComponent,
|
|
8842
|
-
], template: "<p-card>\n <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)=\"save()\" label=\"\uD83D\uDCBE Guardar cambios\"></button>\n </div>\n\n <div class=\"top-buttons\">\n <p-button [loading]=\"isGenerating()\" severity=\"help\" (click)=\"generateCharacter()\" label=\"Generar \uD83E\uDDBE\"></p-button>\n <p-button severity=\"help\" (click)=\"goToCardsCreator()\" label=\" Creador masivo \"></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 <p-button severity=\"danger\" (click)=\"deleteCard()\" icon=\"pi pi-trash\"></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 title=\"Main data\" >\n <div style=\"display: flex; gap: 15px\">\n <div class=\"form-field\">\n <label for=\"version\">Version: {{ form.get('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.get('id').value }} \u2139\uFE0F</span></label\n >\n </div>\n </div>\n\n\n <div class=\"form-field\">\n <label for=\"name\">Card Name <span pTooltip=\"Name of the Agent Card\">\u2139\uFE0F</span></label>\n <input pInputText id=\"name\" type=\"text\" formControlName=\"name\" />\n @if(form.get('name').errors?.['required'] && form.get('name').touched){\n <div class=\"error\"> Name is required </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"description\"> Description <span pTooltip=\"Description of the conversation\">\u2139\uFE0F</span></label>\n <input pInputText id=\"description\" type=\"text\" formControlName=\"description\" />\n @if(form.get('description').errors?.['required'] && form.get('description').touched){\n <div class=\"error\"> Description 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 [filter]=\"true\"\n formControlName=\"lang\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Language'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"agentType\">Agent Type <span pTooltip=\"Select the type of agent\">\u2139\uFE0F</span></label>\n <p-select id=\"agentType\" [options]=\"agentTypeOptions\" formControlName=\"agentType\" [placeholder]=\"'Select Agent Type'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"avatarType\">Avatar Type <span pTooltip=\"image: static picture | video: double looping video (ping-pong) | live2d: interactive 2D model with real-time audio lip-sync | vroid: 3D model (future)\">\u2139\uFE0F</span></label>\n <p-select\n id=\"avatarType\"\n [options]=\"avatarTypeOptions\"\n formControlName=\"avatarType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Avatar Type\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label>Avatar Live2D <span pTooltip=\"Selecciona el modelo Live2D para este agente\">\u2139\uFE0F</span></label>\n <div style=\"display: flex; align-items: center; gap: 12px; margin-top: 4px;\">\n @if (form.get('live2d')?.value; as live2d) {\n <div style=\"display: flex; align-items: center; gap: 8px; padding: 6px 12px; border: 1px solid var(--p-border-color); border-radius: 6px; background: var(--p-content-background);\">\n <img width=\"36px\" height=\"36px\" style=\"border-radius: 50%; object-fit: cover;\" [src]=\"live2d.image?.url || 'defaults/images/face-3.jpg'\" alt=\"live2d preview\" />\n <span style=\"font-weight: 500;\">{{ live2d.name }}</span>\n <p-button icon=\"pi pi-times\" [text]=\"true\" severity=\"danger\" (onClick)=\"form.patchValue({ live2d: null })\" />\n </div>\n }\n <p-button (onClick)=\"onSelectLive2d.emit()\" label=\"Seleccionar Live2D\" icon=\"pi pi-search\" [outlined]=\"true\" />\n </div>\n </div>\n </div>\n\n <p-accordion [multiple]=\"true\" >\n <p-accordion-panel value=\"manageable\">\n <p-accordion-header>\n <span>Manageable</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Controla visibilidad y acceso</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-manageable-form [form]=\"form.controls.manageable\"></dc-manageable-form>\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"learnable\">\n <p-accordion-header>\n <span>Learnable</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Aprendizaje y conocimiento</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-learnable-form [form]=\"form.controls.learnable\"></dc-learnable-form>\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"conversationSettings\">\n <p-accordion-header>\n <span>Conversation Settings</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Modelo, voz y configuraci\u00F3n del chat</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-conversation-settings-form\n [form]=\"form.controls.conversationSettings\"\n [textEngineOptions]=\"textEngineOptions\"\n [conversationOptions]=\"conversationOptions\"\n [voiceTTSOptions]=\"voiceTTSOptions\">\n </dc-conversation-settings-form>\n </p-accordion-content>\n </p-accordion-panel>\n @if(form.controls.conversationFlow){\n <p-accordion-panel value=\"conversationFlow\">\n <p-accordion-header>\n <span>Conversation Flow</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Objetivos, triggers y condiciones din\u00E1micas</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-conversation-flow-form [formGroup]=\"form.controls.conversationFlow\"></dc-conversation-flow-form>\n </p-accordion-content>\n </p-accordion-panel>\n }\n <p-accordion-panel value=\"accounts\">\n <p-accordion-header>\n <span>Gesti\u00F3n de cuentas</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Beta</span>\n </p-accordion-header>\n <p-accordion-content>\n @if(form.controls.accounts) {\n <account-platform-form [formArray]=\"form.controls.accounts\"></account-platform-form>\n }\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"voiceCloning\">\n <p-accordion-header>\n <span>Clonaci\u00F3n de voz</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Beta</span>\n </p-accordion-header>\n <p-accordion-content>\n <div formGroupName=\"voiceCloning\">\n <div class=\"form-field\">\n <label for=\"transcription\">Transcription</label>\n <textarea pTextarea id=\"transcription\" formControlName=\"transcription\" rows=\"3\"></textarea>\n </div>\n <div class=\"form-field\">\n <dc-simple-uploader\n buttonLabel=\"Subir audio\"\n [storagePath]=\"'conversation-cards/' + entityId()\"\n (fileUploaded)=\"onFileUploaded($event)\"></dc-simple-uploader>\n @if(form.get('voiceCloning.sample').value?.url){\n <audio controls [src]=\"form.get('voiceCloning.sample').value.url\"></audio>\n }\n </div>\n <div class=\"form-field voice-clone-status\">\n @if(!form.get('voiceCloning.sample').value?.url){\n <small class=\"muted\">Sube un audio para entrenar un modelo de voz.</small>\n }\n <!-- @if(canCloneVoice()){ -->\n <p-button\n icon=\"pi pi-bolt\"\n severity=\"help\"\n [loading]=\"isCloningVoice()\"\n label=\"Crear modelo en Fish Audio\"\n (click)=\"cloneVoice()\">\n </p-button>\n <!-- } -->\n @if(fishModelId()){\n <div class=\"model-tag\">\n <span>Modelo Fish Audio: <code>{{ fishModelId() }}</code></span>\n <p-button\n icon=\"pi pi-refresh\"\n severity=\"secondary\"\n [text]=\"true\"\n [loading]=\"isCloningVoice()\"\n label=\"Regenerar\"\n (click)=\"cloneVoice()\">\n </p-button>\n </div>\n }\n </div>\n <dc-voice-tts-form\n [form]=\"$any(form.get('voiceCloning.main'))\"\n [title]=\"'Voice Cloning \u2014 Main Voice'\"\n [voiceTTSOptions]=\"voiceTTSOptions\"\n [fullForm]=\"true\">\n </dc-voice-tts-form>\n </div>\n </p-accordion-content>\n </p-accordion-panel>\n @if(form.controls.agenticConfig) {\n <p-accordion-panel value=\"agenticConfig\">\n <p-accordion-header>\n <span>Configuraci\u00F3n Ag\u00E9ntica</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Motor ag\u00E9ntico, patrones y herramientas</span>\n </p-accordion-header>\n <p-accordion-content>\n <div formGroupName=\"agenticConfig\">\n <div class=\"form-field flex items-center gap-2 mb-4\">\n <p-checkbox id=\"agenticEnabled\" formControlName=\"enabled\" [binary]=\"true\"></p-checkbox>\n <label for=\"agenticEnabled\" class=\"cursor-pointer\">Habilitar Agente Aut\u00F3nomo</label>\n </div>\n\n @if(form.get('agenticConfig.enabled').value) {\n <div class=\"form-field mb-4\">\n <label for=\"agenticEngine\" class=\"block text-sm font-medium mb-1\">Motor Ag\u00E9ntico</label>\n <p-select\n id=\"agenticEngine\"\n [options]=\"agenticEngineOptions\"\n formControlName=\"engine\"\n [placeholder]=\"'Seleccionar Motor'\"\n class=\"w-full\"></p-select>\n </div>\n\n <div class=\"form-field mb-4\">\n <label for=\"agenticPattern\" class=\"block text-sm font-medium mb-1\">Patr\u00F3n Ag\u00E9ntico</label>\n <p-select\n id=\"agenticPattern\"\n [options]=\"agenticPatternOptions\"\n formControlName=\"pattern\"\n [placeholder]=\"'Seleccionar Patr\u00F3n'\"\n class=\"w-full\"></p-select>\n </div>\n\n <div class=\"form-field mb-4\">\n <label for=\"maxIterations\" class=\"block text-sm font-medium mb-1\">M\u00E1ximo de Iteraciones</label>\n <input pInputText id=\"maxIterations\" type=\"number\" formControlName=\"maxIterations\" class=\"w-full\" />\n </div>\n\n <div class=\"form-field mb-4\">\n <label class=\"block text-sm font-medium mb-2\">Herramientas Permitidas</label>\n <div class=\"flex flex-col gap-2 mt-2 pl-2\">\n @for (toolOpt of allowedToolsOptions; track toolOpt.value) {\n <div class=\"flex items-center gap-2\">\n <p-checkbox\n [inputId]=\"'tool_' + toolOpt.value\"\n [binary]=\"true\"\n [ngModel]=\"isToolAllowed(toolOpt.value)\"\n [ngModelOptions]=\"{standalone: true}\"\n (onChange)=\"toggleTool(toolOpt.value, $event.checked)\">\n </p-checkbox>\n <label [for]=\"'tool_' + toolOpt.value\" class=\"cursor-pointer text-sm\">{{ toolOpt.label }}</label>\n </div>\n }\n </div>\n </div>\n\n @if(form.get('agenticConfig.pattern').value === 'reflection') {\n <div class=\"form-field mb-4\">\n <label for=\"reflectionPrompt\" class=\"block text-sm font-medium mb-1\">Prompt de Reflexi\u00F3n</label>\n <textarea pTextarea id=\"reflectionPrompt\" formControlName=\"reflectionPrompt\" rows=\"3\" [autoResize]=\"true\" class=\"w-full\"></textarea>\n </div>\n }\n\n @if(form.get('agenticConfig.engine').value === 'os_hermes') {\n <div class=\"form-field mb-4\">\n <label for=\"connectionUrl\" class=\"block text-sm font-medium mb-1\">URL de Conexi\u00F3n</label>\n <input pInputText id=\"connectionUrl\" type=\"text\" formControlName=\"connectionUrl\" placeholder=\"ws://...\" class=\"w-full\" />\n </div>\n }\n\n <div class=\"form-field mt-6\">\n <h4 class=\"font-semibold text-base mb-2\">Modelo de Razonamiento</h4>\n <dc-model-selector [modelForm]=\"$any(form.get('agenticConfig.reasoningModel'))\" [shortForm]=\"true\"></dc-model-selector>\n </div>\n\n <div class=\"form-field mt-6\">\n <h4 class=\"font-semibold text-base mb-2\">Modelo de Ejecuci\u00F3n</h4>\n <dc-model-selector [modelForm]=\"$any(form.get('agenticConfig.executionModel'))\" [shortForm]=\"true\"></dc-model-selector>\n </div>\n }\n </div>\n </p-accordion-content>\n </p-accordion-panel>\n }\n </p-accordion>\n\n </div>\n\n <div class=\"right-column\">\n @if(entity() && entityId()){\n <assets-loader\n [assets]=\"entity().assets\"\n [storagePath]=\"'conversation-cards/' + entityId()\"\n (assetsChange)=\"onAssetsChange($event)\"\n (assetUpdate)=\"onUpdateAsset($event)\"\n (onFileSelected)=\"onImageSelected($event)\"></assets-loader>\n } @if(form.controls.characterCard){\n\n <br />\n <dc-character-card-form [characterCardForm]=\"form.controls.characterCard\" (generateMissingDataRequest)=\"generateMissingData()\">\n </dc-character-card-form>\n }\n </div>\n </div>\n </form>\n\n <div class=\"float-button\">\n <p-button icon=\"pi pi-eye\" (click)=\"inspect()\" severity=\"secondary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Inspeccionar\"> </p-button>\n\n <p-button icon=\"pi pi-save\" (click)=\"save()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n </div>\n</p-card>\n", styles: [".conversation-form{max-width:100%;padding:20px;border-radius:8px;box-shadow:0 2px 4px #0000001a}.conversation-form .card-group{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:flex;flex-wrap:wrap;gap:2rem;width:100%}.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.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;flex:1 1 calc(50% - 1rem);min-width:300px}@media(max-width:768px){.conversation-form .left-column,.conversation-form .right-column{flex:1 1 100%}}.conversation-form .card-group{padding:1rem;margin-bottom:1.5rem}.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%}.voice-clone-status{display:flex;flex-direction:column;gap:.5rem;margin:.75rem 0}.voice-clone-status .muted{color:var(--p-text-muted-color)}.voice-clone-status .model-tag{display:flex;align-items:center;gap:.75rem;padding:.5rem .75rem;background:var(--p-surface-100, #f3f4f6);border-radius:6px;font-size:.9rem}.voice-clone-status .model-tag code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.8rem}\n"] }]
|
|
9161
|
+
], template: "<p-card>\n <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)=\"save()\" label=\"\uD83D\uDCBE Guardar cambios\"></button>\n </div>\n\n <div class=\"top-buttons\">\n <p-button [loading]=\"isGenerating()\" severity=\"help\" (click)=\"generateCharacter()\" label=\"Generar \uD83E\uDDBE\"></p-button>\n <p-button severity=\"help\" (click)=\"goToCardsCreator()\" label=\" Creador masivo \"></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 <p-button severity=\"danger\" (click)=\"deleteCard()\" icon=\"pi pi-trash\"></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 title=\"Main data\" >\n <div style=\"display: flex; gap: 15px\">\n <div class=\"form-field\">\n <label for=\"version\">Version: {{ form.get('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.get('id').value }} \u2139\uFE0F</span></label\n >\n </div>\n </div>\n\n\n <div class=\"form-field\">\n <label for=\"name\">Card Name <span pTooltip=\"Name of the Agent Card\">\u2139\uFE0F</span></label>\n <input pInputText id=\"name\" type=\"text\" formControlName=\"name\" />\n @if(form.get('name').errors?.['required'] && form.get('name').touched){\n <div class=\"error\"> Name is required </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"description\"> Description <span pTooltip=\"Description of the conversation\">\u2139\uFE0F</span></label>\n <input pInputText id=\"description\" type=\"text\" formControlName=\"description\" />\n @if(form.get('description').errors?.['required'] && form.get('description').touched){\n <div class=\"error\"> Description 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 [filter]=\"true\"\n formControlName=\"lang\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Language'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"agentType\">Agent Type <span pTooltip=\"Select the type of agent\">\u2139\uFE0F</span></label>\n <p-select id=\"agentType\" [options]=\"agentTypeOptions\" formControlName=\"agentType\" [placeholder]=\"'Select Agent Type'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"avatarType\">Avatar Type <span pTooltip=\"image: static picture | video: double looping video (ping-pong) | live2d: interactive 2D model with real-time audio lip-sync | vroid: 3D model (future)\">\u2139\uFE0F</span></label>\n <p-select\n id=\"avatarType\"\n [options]=\"avatarTypeOptions\"\n formControlName=\"avatarType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Avatar Type\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label>Avatar Live2D <span pTooltip=\"Selecciona el modelo Live2D para este agente\">\u2139\uFE0F</span></label>\n <div style=\"display: flex; align-items: center; gap: 12px; margin-top: 4px;\">\n @if (form.get('live2d')?.value; as live2d) {\n <div style=\"display: flex; align-items: center; gap: 8px; padding: 6px 12px; border: 1px solid var(--p-border-color); border-radius: 6px; background: var(--p-content-background);\">\n <img width=\"36px\" height=\"36px\" style=\"border-radius: 50%; object-fit: cover;\" [src]=\"live2d.image?.url || 'defaults/images/face-3.jpg'\" alt=\"live2d preview\" />\n <span style=\"font-weight: 500;\">{{ live2d.name }}</span>\n <p-button icon=\"pi pi-times\" [text]=\"true\" severity=\"danger\" (onClick)=\"form.patchValue({ live2d: null })\" />\n </div>\n }\n <p-button (onClick)=\"onSelectLive2d.emit()\" label=\"Seleccionar Live2D\" icon=\"pi pi-search\" [outlined]=\"true\" />\n </div>\n </div>\n </div>\n\n <p-accordion [multiple]=\"true\" >\n <p-accordion-panel value=\"manageable\">\n <p-accordion-header>\n <span>Manageable</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Controla visibilidad y acceso</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-manageable-form [form]=\"form.controls.manageable\"></dc-manageable-form>\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"learnable\">\n <p-accordion-header>\n <span>Learnable</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Aprendizaje y conocimiento</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-learnable-form [form]=\"form.controls.learnable\"></dc-learnable-form>\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"conversationSettings\">\n <p-accordion-header>\n <span>Conversation Settings</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Modelo, voz y configuraci\u00F3n del chat</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-conversation-settings-form\n [form]=\"form.controls.conversationSettings\"\n [textEngineOptions]=\"textEngineOptions\"\n [conversationOptions]=\"conversationOptions\"\n [voiceTTSOptions]=\"voiceTTSOptions\">\n </dc-conversation-settings-form>\n </p-accordion-content>\n </p-accordion-panel>\n @if(form.controls.conversationFlow){\n <p-accordion-panel value=\"conversationFlow\">\n <p-accordion-header>\n <span>Conversation Flow</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Objetivos, triggers y condiciones din\u00E1micas</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-conversation-flow-form [formGroup]=\"form.controls.conversationFlow\"></dc-conversation-flow-form>\n </p-accordion-content>\n </p-accordion-panel>\n }\n <p-accordion-panel value=\"accounts\">\n <p-accordion-header>\n <span>Gesti\u00F3n de cuentas</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Beta</span>\n </p-accordion-header>\n <p-accordion-content>\n @if(form.controls.accounts) {\n <account-platform-form [formArray]=\"form.controls.accounts\"></account-platform-form>\n }\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"voice\">\n <p-accordion-header>\n <span>Voz</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Sintesis y Clonaci\u00F3n</span>\n\n </p-accordion-header>\n <p-accordion-content>\n <div formGroupName=\"voice\">\n <div formGroupName=\"cloning\">\n <div class=\"form-field\">\n <label for=\"new_transcription\">Transcription (Voice.Cloning)</label>\n <textarea pTextarea id=\"new_transcription\" formControlName=\"transcription\" rows=\"3\"></textarea>\n </div>\n <div class=\"form-field\">\n <dc-simple-uploader\n buttonLabel=\"Subir audio (Voice.Cloning)\"\n [storagePath]=\"'conversation-cards/' + entityId()\"\n (fileUploaded)=\"onFileUploaded($event)\"></dc-simple-uploader>\n @if(form.get('voice.cloning.sample')?.value?.url){\n <audio controls [src]=\"form.get('voice.cloning.sample').value.url\"></audio>\n }\n </div>\n </div>\n <div class=\"form-field voice-clone-status\">\n @if(!form.get('voice.cloning.sample')?.value?.url){\n <small class=\"muted\">Sube un audio para entrenar un modelo de voz en la nueva propiedad.</small>\n }\n <p-button\n icon=\"pi pi-bolt\"\n severity=\"help\"\n [loading]=\"isCloningVoice()\"\n label=\"Crear modelo en Fish Audio (Voice)\"\n (click)=\"cloneVoice()\">\n </p-button>\n </div>\n \n <dc-voice-tts-form\n [form]=\"$any(form.get('voice.main'))\"\n [title]=\"'Main Voice'\"\n [voiceTTSOptions]=\"voiceTTSOptions\"\n [fullForm]=\"true\">\n </dc-voice-tts-form>\n \n <dc-voice-tts-form\n [form]=\"$any(form.get('voice.secondary'))\"\n [title]=\"'Secondary Voice'\"\n [voiceTTSOptions]=\"voiceTTSOptions\"\n [fullForm]=\"true\">\n </dc-voice-tts-form>\n </div>\n </p-accordion-content>\n </p-accordion-panel>\n @if(form.controls.agenticConfig) {\n <p-accordion-panel value=\"agenticConfig\">\n <p-accordion-header>\n <span>Configuraci\u00F3n Ag\u00E9ntica</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Motor ag\u00E9ntico, patrones y herramientas</span>\n </p-accordion-header>\n <p-accordion-content>\n <div formGroupName=\"agenticConfig\">\n <div class=\"form-field flex items-center gap-2 mb-4\">\n <p-checkbox id=\"agenticEnabled\" formControlName=\"enabled\" [binary]=\"true\"></p-checkbox>\n <label for=\"agenticEnabled\" class=\"cursor-pointer\">Habilitar Agente Aut\u00F3nomo</label>\n </div>\n\n @if(form.get('agenticConfig.enabled').value) {\n <div class=\"form-field mb-4\">\n <label for=\"agenticEngine\" class=\"block text-sm font-medium mb-1\">Motor Ag\u00E9ntico</label>\n <p-select\n id=\"agenticEngine\"\n [options]=\"agenticEngineOptions\"\n formControlName=\"engine\"\n [placeholder]=\"'Seleccionar Motor'\"\n class=\"w-full\"></p-select>\n </div>\n\n <div class=\"form-field mb-4\">\n <label for=\"agenticPattern\" class=\"block text-sm font-medium mb-1\">Patr\u00F3n Ag\u00E9ntico</label>\n <p-select\n id=\"agenticPattern\"\n [options]=\"agenticPatternOptions\"\n formControlName=\"pattern\"\n [placeholder]=\"'Seleccionar Patr\u00F3n'\"\n class=\"w-full\"></p-select>\n </div>\n\n <div class=\"form-field mb-4\">\n <label for=\"maxIterations\" class=\"block text-sm font-medium mb-1\">M\u00E1ximo de Iteraciones</label>\n <input pInputText id=\"maxIterations\" type=\"number\" formControlName=\"maxIterations\" class=\"w-full\" />\n </div>\n\n <div class=\"form-field mb-4\">\n <label class=\"block text-sm font-medium mb-2\">Herramientas Permitidas</label>\n <div class=\"flex flex-col gap-2 mt-2 pl-2\">\n @for (toolOpt of allowedToolsOptions; track toolOpt.value) {\n <div class=\"flex items-center gap-2\">\n <p-checkbox\n [inputId]=\"'tool_' + toolOpt.value\"\n [binary]=\"true\"\n [ngModel]=\"isToolAllowed(toolOpt.value)\"\n [ngModelOptions]=\"{standalone: true}\"\n (onChange)=\"toggleTool(toolOpt.value, $event.checked)\">\n </p-checkbox>\n <label [for]=\"'tool_' + toolOpt.value\" class=\"cursor-pointer text-sm\">{{ toolOpt.label }}</label>\n </div>\n }\n </div>\n </div>\n\n @if(form.get('agenticConfig.pattern').value === 'reflection') {\n <div class=\"form-field mb-4\">\n <label for=\"reflectionPrompt\" class=\"block text-sm font-medium mb-1\">Prompt de Reflexi\u00F3n</label>\n <textarea pTextarea id=\"reflectionPrompt\" formControlName=\"reflectionPrompt\" rows=\"3\" [autoResize]=\"true\" class=\"w-full\"></textarea>\n </div>\n }\n\n @if(form.get('agenticConfig.engine').value === 'os_hermes') {\n <div class=\"form-field mb-4\">\n <label for=\"connectionUrl\" class=\"block text-sm font-medium mb-1\">URL de Conexi\u00F3n</label>\n <input pInputText id=\"connectionUrl\" type=\"text\" formControlName=\"connectionUrl\" placeholder=\"ws://...\" class=\"w-full\" />\n </div>\n }\n\n <div class=\"form-field mt-6\">\n <h4 class=\"font-semibold text-base mb-2\">Modelo de Razonamiento</h4>\n <dc-model-selector [modelForm]=\"$any(form.get('agenticConfig.reasoningModel'))\" [shortForm]=\"true\"></dc-model-selector>\n </div>\n\n <div class=\"form-field mt-6\">\n <h4 class=\"font-semibold text-base mb-2\">Modelo de Ejecuci\u00F3n</h4>\n <dc-model-selector [modelForm]=\"$any(form.get('agenticConfig.executionModel'))\" [shortForm]=\"true\"></dc-model-selector>\n </div>\n }\n </div>\n </p-accordion-content>\n </p-accordion-panel>\n }\n </p-accordion>\n\n </div>\n\n <div class=\"right-column\">\n @if(entity() && entityId()){\n <assets-loader\n [assets]=\"entity().assets\"\n [storagePath]=\"'conversation-cards/' + entityId()\"\n (assetsChange)=\"onAssetsChange($event)\"\n (assetUpdate)=\"onUpdateAsset($event)\"\n (onFileSelected)=\"onImageSelected($event)\"></assets-loader>\n } @if(form.controls.characterCard){\n\n <br />\n <dc-character-card-form [characterCardForm]=\"form.controls.characterCard\" (generateMissingDataRequest)=\"generateMissingData()\">\n </dc-character-card-form>\n }\n </div>\n </div>\n </form>\n\n <div class=\"float-button\">\n <p-button icon=\"pi pi-eye\" (click)=\"inspect()\" severity=\"secondary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Inspeccionar\"> </p-button>\n\n <p-button icon=\"pi pi-save\" (click)=\"save()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n </div>\n</p-card>\n", styles: [".conversation-form{max-width:100%;padding:20px;border-radius:8px;box-shadow:0 2px 4px #0000001a}.conversation-form .card-group{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:flex;flex-wrap:wrap;gap:2rem;width:100%}.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.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;flex:1 1 calc(50% - 1rem);min-width:300px}@media(max-width:768px){.conversation-form .left-column,.conversation-form .right-column{flex:1 1 100%}}.conversation-form .card-group{padding:1rem;margin-bottom:1.5rem}.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%}.voice-clone-status{display:flex;flex-direction:column;gap:.5rem;margin:.75rem 0}.voice-clone-status .muted{color:var(--p-text-muted-color)}.voice-clone-status .model-tag{display:flex;align-items:center;gap:.75rem;padding:.5rem .75rem;background:var(--p-surface-100, #f3f4f6);border-radius:6px;font-size:.9rem}.voice-clone-status .model-tag code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.8rem}\n"] }]
|
|
8843
9162
|
}], propDecorators: { onSave: [{ type: i0.Output, args: ["onSave"] }], inputSettings: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputSettings", required: false }] }], onSelectLive2d: [{ type: i0.Output, args: ["onSelectLive2d"] }] } });
|
|
8844
9163
|
|
|
8845
9164
|
class TruncatePipe {
|
|
@@ -9014,7 +9333,7 @@ class AgentCardListComponent extends EntityBaseListV2Component {
|
|
|
9014
9333
|
const adminFilters = [
|
|
9015
9334
|
{
|
|
9016
9335
|
name: 'Voz Única',
|
|
9017
|
-
field: '
|
|
9336
|
+
field: 'voice.cloning.sample',
|
|
9018
9337
|
type: 'select',
|
|
9019
9338
|
operator: '$not_null',
|
|
9020
9339
|
options: [
|
|
@@ -9271,7 +9590,7 @@ class Live2dModelComponent {
|
|
|
9271
9590
|
this.playAnimation(group);
|
|
9272
9591
|
}
|
|
9273
9592
|
}
|
|
9274
|
-
speak(audioUrl, options) {
|
|
9593
|
+
async speak(audioUrl, options) {
|
|
9275
9594
|
if (!this.model)
|
|
9276
9595
|
return;
|
|
9277
9596
|
if (options?.focus !== false) {
|
|
@@ -9280,8 +9599,21 @@ class Live2dModelComponent {
|
|
|
9280
9599
|
this.viewStateChanged.emit(transform);
|
|
9281
9600
|
}
|
|
9282
9601
|
}
|
|
9602
|
+
this.stopSpeaking();
|
|
9603
|
+
const targetVolume = options?.volume !== undefined ? options.volume : 1.0;
|
|
9604
|
+
if (this.live2dConfig) {
|
|
9605
|
+
try {
|
|
9606
|
+
const live2dEngine = await this.live2dConfig.Live2DModel();
|
|
9607
|
+
if (live2dEngine && live2dEngine.SoundManager) {
|
|
9608
|
+
live2dEngine.SoundManager.volume = targetVolume;
|
|
9609
|
+
}
|
|
9610
|
+
}
|
|
9611
|
+
catch (e) {
|
|
9612
|
+
console.warn('Failed to set SoundManager volume', e);
|
|
9613
|
+
}
|
|
9614
|
+
}
|
|
9283
9615
|
this.model.speak(audioUrl, {
|
|
9284
|
-
volume:
|
|
9616
|
+
volume: targetVolume,
|
|
9285
9617
|
crossOrigin: options?.crossOrigin || 'anonymous',
|
|
9286
9618
|
});
|
|
9287
9619
|
}
|
|
@@ -9440,7 +9772,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
9440
9772
|
args: ['canvas']
|
|
9441
9773
|
}], modelPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelPath", required: true }] }], scale: [{ type: i0.Input, args: [{ isSignal: true, alias: "scale", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }], x: [{ type: i0.Input, args: [{ isSignal: true, alias: "x", required: false }] }], y: [{ type: i0.Input, args: [{ isSignal: true, alias: "y", required: false }] }], modelLoaded: [{ type: i0.Output, args: ["modelLoaded"] }], viewStateChanged: [{ type: i0.Output, args: ["viewStateChanged"] }] } });
|
|
9442
9774
|
|
|
9443
|
-
class
|
|
9775
|
+
class DcAgentCardAvatarComponent {
|
|
9444
9776
|
set isConversationActive(val) {
|
|
9445
9777
|
this._isConversationActive.set(val);
|
|
9446
9778
|
}
|
|
@@ -9462,8 +9794,8 @@ class DcAgentCardConverseComponent {
|
|
|
9462
9794
|
this.videoPlayerService = inject(VideoPlayerNativeService);
|
|
9463
9795
|
this.agentCardId = '';
|
|
9464
9796
|
this.useNativePlayer = true;
|
|
9465
|
-
this.activeAudioMessage = null;
|
|
9466
9797
|
this._isConversationActive = signal(false, ...(ngDevMode ? [{ debugName: "_isConversationActive" }] : /* istanbul ignore next */ []));
|
|
9798
|
+
/** @deprecated Use ChatMonitorService instead — el canal oficial es el servicio compartido. */
|
|
9467
9799
|
this.onStartConversation = output();
|
|
9468
9800
|
this.videoPlayerA = viewChild('videoPlayerA', ...(ngDevMode ? [{ debugName: "videoPlayerA" }] : /* istanbul ignore next */ []));
|
|
9469
9801
|
this.videoPlayerB = viewChild('videoPlayerB', ...(ngDevMode ? [{ debugName: "videoPlayerB" }] : /* istanbul ignore next */ []));
|
|
@@ -9544,25 +9876,48 @@ class DcAgentCardConverseComponent {
|
|
|
9544
9876
|
this.videoPlayerService.setAgentCard(card);
|
|
9545
9877
|
}
|
|
9546
9878
|
});
|
|
9547
|
-
// Live2D Speak (Lip-Sync) effect
|
|
9879
|
+
// Live2D Speak (Lip-Sync) effect — listening directly to ChatMonitorService's activeAudioMessage
|
|
9548
9880
|
effect(() => {
|
|
9549
|
-
const audioMsg = this.activeAudioMessage;
|
|
9881
|
+
const audioMsg = this.chatMonitorService.activeAudioMessage();
|
|
9882
|
+
console.log('DcAgentCardAvatarComponent: Speak effect triggered. activeAudioMessage:', audioMsg ? {
|
|
9883
|
+
messageId: audioMsg.messageId,
|
|
9884
|
+
audioUrl: audioMsg.audioUrl
|
|
9885
|
+
} : null);
|
|
9550
9886
|
const type = this.avatarType();
|
|
9551
9887
|
const l2d = this.live2dModel();
|
|
9552
|
-
if (type === 'live2d' &&
|
|
9553
|
-
|
|
9554
|
-
|
|
9888
|
+
if (type === 'live2d' && l2d) {
|
|
9889
|
+
if (audioMsg && audioMsg.audioUrl) {
|
|
9890
|
+
console.log('DcAgentCardAvatarComponent: Live2D speak started with URL:', audioMsg.audioUrl);
|
|
9891
|
+
l2d.stopSpeaking();
|
|
9892
|
+
l2d.speak(audioMsg.audioUrl, { volume: 0.0 });
|
|
9893
|
+
}
|
|
9894
|
+
else {
|
|
9895
|
+
console.log('DcAgentCardAvatarComponent: Live2D speak stopped (no active message or no audioUrl)');
|
|
9896
|
+
l2d.stopSpeaking();
|
|
9897
|
+
}
|
|
9555
9898
|
}
|
|
9556
9899
|
});
|
|
9557
9900
|
// Live2D Mood state emotion updates mapping
|
|
9558
9901
|
effect(() => {
|
|
9559
|
-
const mood = this.
|
|
9902
|
+
const mood = this.chatMonitorService.currentMood();
|
|
9560
9903
|
const type = this.avatarType();
|
|
9561
9904
|
const l2d = this.live2dModel();
|
|
9562
9905
|
if (type === 'live2d' && mood && l2d) {
|
|
9563
9906
|
l2d.setExpression(mood);
|
|
9564
9907
|
}
|
|
9565
9908
|
});
|
|
9909
|
+
// Native Video player mood state updates mapping
|
|
9910
|
+
effect(() => {
|
|
9911
|
+
const mood = this.chatMonitorService.currentMood();
|
|
9912
|
+
const card = this.agentCard();
|
|
9913
|
+
const type = this.avatarType();
|
|
9914
|
+
if (type === 'video' && mood && card?.assets?.motions) {
|
|
9915
|
+
const motionUrl = card.assets.motions.find((m) => m.metadata?.moodState === mood)?.url;
|
|
9916
|
+
if (motionUrl) {
|
|
9917
|
+
this.videoPlayerService.setVideoSource(motionUrl);
|
|
9918
|
+
}
|
|
9919
|
+
}
|
|
9920
|
+
});
|
|
9566
9921
|
}
|
|
9567
9922
|
ngOnDestroy() {
|
|
9568
9923
|
this.videoPlayerService.cleanUp();
|
|
@@ -9626,6 +9981,18 @@ class DcAgentCardConverseComponent {
|
|
|
9626
9981
|
cardTranslated.conversationSettings.secondaryVoice.voice = transformedSecondaryVoice;
|
|
9627
9982
|
}
|
|
9628
9983
|
}
|
|
9984
|
+
if (cardTranslated.voice?.main) {
|
|
9985
|
+
const transformedMainVoice = this._transformVoice(cardTranslated.voice.main.voice, locale);
|
|
9986
|
+
if (transformedMainVoice) {
|
|
9987
|
+
cardTranslated.voice.main.voice = transformedMainVoice;
|
|
9988
|
+
}
|
|
9989
|
+
}
|
|
9990
|
+
if (cardTranslated.voice?.secondary) {
|
|
9991
|
+
const transformedSecondaryVoice = this._transformVoice(cardTranslated.voice.secondary.voice, locale);
|
|
9992
|
+
if (transformedSecondaryVoice) {
|
|
9993
|
+
cardTranslated.voice.secondary.voice = transformedSecondaryVoice;
|
|
9994
|
+
}
|
|
9995
|
+
}
|
|
9629
9996
|
return cardTranslated;
|
|
9630
9997
|
}
|
|
9631
9998
|
_transformVoice(voice, locale) {
|
|
@@ -9656,18 +10023,16 @@ class DcAgentCardConverseComponent {
|
|
|
9656
10023
|
toggleInfoLayer() {
|
|
9657
10024
|
this.showInfoLayer.update((value) => !value);
|
|
9658
10025
|
}
|
|
9659
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type:
|
|
9660
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type:
|
|
10026
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcAgentCardAvatarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10027
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DcAgentCardAvatarComponent, isStandalone: true, selector: "dc-agent-card-avatar", inputs: { agentCardId: "agentCardId", useNativePlayer: "useNativePlayer", isConversationActive: "isConversationActive" }, outputs: { onStartConversation: "onStartConversation" }, viewQueries: [{ propertyName: "videoPlayerA", first: true, predicate: ["videoPlayerA"], descendants: true, isSignal: true }, { propertyName: "videoPlayerB", first: true, predicate: ["videoPlayerB"], descendants: true, isSignal: true }, { propertyName: "live2dModel", first: true, predicate: Live2dModelComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<ng-template #cardContent>\n <div class=\"card-container\">\n <!-- CASO 1: Renderizado por Video (Ping-pong nativo) -->\n @if (avatarType() === 'video') {\n <img class=\"card-image\" [src]=\"agentCard()?.assets?.image?.url\" [style.opacity]=\"videoPlayerService.isVideoPlaying() ? 0 : 1\" style=\"transition: opacity 0.5s ease-in-out;\" alt=\"\" />\n <video #videoPlayerA class=\"card-image\" style=\"position: absolute; top: 0; left: 0; transition: opacity 0.3s ease-in-out; opacity: 1; z-index: 1;\" playsinline></video>\n <video #videoPlayerB class=\"card-image\" style=\"position: absolute; top: 0; left: 0; transition: opacity 0.3s ease-in-out; opacity: 0; z-index: 2;\" playsinline></video>\n }\n\n <!-- CASO 2: Renderizado por Live2D (PixiJS Canvas) -->\n @else if (avatarType() === 'live2d') {\n @if (model3JsonUrl(); as modelPath) {\n <app-live2d-model\n #live2dModel\n class=\"card-image\"\n style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: block; pointer-events: all; z-index: 2;\"\n [modelPath]=\"modelPath\"\n [zoom]=\"live2dZoomState()\"\n [x]=\"live2dXState()\"\n [y]=\"live2dYState()\"\n (viewStateChanged)=\"onViewStateChanged($event)\">\n </app-live2d-model>\n } @else {\n <!-- Fallback si no tiene manifest -->\n <img class=\"card-image\" [src]=\"agentCard()?.assets?.image?.url\" alt=\"\" />\n }\n }\n\n <!-- CASO 3: Renderizado por VRoid (WebGL 3D - Placeholder) -->\n @else if (avatarType() === 'vroid') {\n <div class=\"card-image\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: #222; display: flex; align-items: center; justify-content: center; color: white; z-index: 2;\">\n <span>[3D VRoid Avatar Canvas]</span>\n </div>\n }\n\n <!-- CASO 4: Imagen Est\u00E1tica Est\u00E1ndar -->\n @else {\n <img class=\"card-image\" [src]=\"agentCard()?.assets?.image?.url\" alt=\"\" />\n }\n\n\n\n @if (!isChatActive()) {\n\n <div class=\"info-button\" (click)=\"toggleInfoLayer()\">\n <p-button icon=\"pi pi-arrow-down-left\" [rounded]=\"true\" [raised]=\"true\" severity=\"primary\" [outlined]=\"true\" />\n </div>\n \n <div style=\"position: absolute; bottom: 20px; right: 50%; transform: translateX(50%); z-index: 3\">\n <p-button\n size=\"large\"\n [label]=\"'dataclouder.agentCards.dcAgentCardDetails.startConversation' | translate\"\n [rounded]=\"true\"\n (click)=\"startConversation()\" />\n </div>\n }\n\n <div class=\"info-layer\" [class.active]=\"showInfoLayer()\">\n <div class=\"info-content\">\n <h1\n ><strong>{{ agentCard()?.name || agentCard()?.characterCard?.data?.name }}</strong></h1\n >\n\n @if (agentCard()?.characterCard?.data?.instructions) {\n <div class=\"scenario\">\n <h4>{{ 'dataclouder.agentCards.dcAgentCardDetails.instructions' | translate }}</h4>\n <p>{{ agentCard()?.characterCard?.data?.instructions | parseCard: agentCard() }}</p>\n </div>\n }\n </div>\n </div>\n </div>\n</ng-template>\n\n<div \n style=\"display: flex; justify-content: center; align-items: center\" \n [class.transparent-mode]=\"avatarDisplay() === 'transparent'\"\n [class.fullscreen-mode]=\"avatarDisplay() === 'fullscreen'\"\n [class.card-mode]=\"avatarDisplay() === 'card'\"\n>\n @if (avatarDisplay() === 'transparent' || avatarDisplay() === 'fullscreen') {\n <ng-container *ngTemplateOutlet=\"cardContent\"></ng-container>\n } @else {\n <p-card>\n <ng-container *ngTemplateOutlet=\"cardContent\"></ng-container>\n </p-card>\n }\n</div>\n", styles: ["::ng-deep .p-card{width:420px;height:700px}::ng-deep .p-card .p-card-body{width:100%;height:100%}:host{display:block;width:100%;height:100%}.transparent-mode .card-container{width:420px;height:700px;position:relative}.transparent-mode .card-image{background:transparent}.fullscreen-mode{position:absolute;top:0;left:0;width:100%;height:100%;z-index:1}.fullscreen-mode .card-container{width:100%;height:100%;position:relative}.fullscreen-mode .card-image{background:transparent}.card-image{height:100%;width:100%;object-fit:cover;object-position:center;position:absolute;top:0;left:0;transition:filter .3s ease}::ng-deep .plyr--full-ui{width:100%;object-fit:cover;object-position:center;position:absolute;top:0;left:0;transition:filter .3s ease}.info-button{position:absolute;top:15px;right:15px;z-index:3}.info-button:hover{transform:scale(1.1)}.info-layer{height:100%;width:100%;position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;z-index:2;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);background-color:var(--chat-user-message-bg);color:#fff;opacity:1;clip-path:circle(0% at top right);transition:clip-path .5s cubic-bezier(.25,1,.5,1);pointer-events:none}.info-layer.active{clip-path:circle(150% at top right);pointer-events:auto}.info-content{padding:15px;text-align:center;max-width:90%}.info-content h1{margin-top:0;font-size:18px;margin-bottom:10px}.info-content p{font-size:12px;margin:0}::ng-deep .info-button .p-button{background-color:#ffffff47}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i2$6.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Live2dModelComponent, selector: "app-live2d-model", inputs: ["modelPath", "scale", "zoom", "x", "y"], outputs: ["modelLoaded", "viewStateChanged"] }, { kind: "pipe", type: ParseCardPipe, name: "parseCard" }, { kind: "pipe", type: i3$6.TranslatePipe, name: "translate" }] }); }
|
|
9661
10028
|
}
|
|
9662
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type:
|
|
10029
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcAgentCardAvatarComponent, decorators: [{
|
|
9663
10030
|
type: Component,
|
|
9664
|
-
args: [{ selector: 'dc-agent-card-
|
|
10031
|
+
args: [{ selector: 'dc-agent-card-avatar', standalone: true, imports: [ButtonModule, CardModule, ParseCardPipe, TranslateModule, NgTemplateOutlet, Live2dModelComponent], template: "<ng-template #cardContent>\n <div class=\"card-container\">\n <!-- CASO 1: Renderizado por Video (Ping-pong nativo) -->\n @if (avatarType() === 'video') {\n <img class=\"card-image\" [src]=\"agentCard()?.assets?.image?.url\" [style.opacity]=\"videoPlayerService.isVideoPlaying() ? 0 : 1\" style=\"transition: opacity 0.5s ease-in-out;\" alt=\"\" />\n <video #videoPlayerA class=\"card-image\" style=\"position: absolute; top: 0; left: 0; transition: opacity 0.3s ease-in-out; opacity: 1; z-index: 1;\" playsinline></video>\n <video #videoPlayerB class=\"card-image\" style=\"position: absolute; top: 0; left: 0; transition: opacity 0.3s ease-in-out; opacity: 0; z-index: 2;\" playsinline></video>\n }\n\n <!-- CASO 2: Renderizado por Live2D (PixiJS Canvas) -->\n @else if (avatarType() === 'live2d') {\n @if (model3JsonUrl(); as modelPath) {\n <app-live2d-model\n #live2dModel\n class=\"card-image\"\n style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: block; pointer-events: all; z-index: 2;\"\n [modelPath]=\"modelPath\"\n [zoom]=\"live2dZoomState()\"\n [x]=\"live2dXState()\"\n [y]=\"live2dYState()\"\n (viewStateChanged)=\"onViewStateChanged($event)\">\n </app-live2d-model>\n } @else {\n <!-- Fallback si no tiene manifest -->\n <img class=\"card-image\" [src]=\"agentCard()?.assets?.image?.url\" alt=\"\" />\n }\n }\n\n <!-- CASO 3: Renderizado por VRoid (WebGL 3D - Placeholder) -->\n @else if (avatarType() === 'vroid') {\n <div class=\"card-image\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: #222; display: flex; align-items: center; justify-content: center; color: white; z-index: 2;\">\n <span>[3D VRoid Avatar Canvas]</span>\n </div>\n }\n\n <!-- CASO 4: Imagen Est\u00E1tica Est\u00E1ndar -->\n @else {\n <img class=\"card-image\" [src]=\"agentCard()?.assets?.image?.url\" alt=\"\" />\n }\n\n\n\n @if (!isChatActive()) {\n\n <div class=\"info-button\" (click)=\"toggleInfoLayer()\">\n <p-button icon=\"pi pi-arrow-down-left\" [rounded]=\"true\" [raised]=\"true\" severity=\"primary\" [outlined]=\"true\" />\n </div>\n \n <div style=\"position: absolute; bottom: 20px; right: 50%; transform: translateX(50%); z-index: 3\">\n <p-button\n size=\"large\"\n [label]=\"'dataclouder.agentCards.dcAgentCardDetails.startConversation' | translate\"\n [rounded]=\"true\"\n (click)=\"startConversation()\" />\n </div>\n }\n\n <div class=\"info-layer\" [class.active]=\"showInfoLayer()\">\n <div class=\"info-content\">\n <h1\n ><strong>{{ agentCard()?.name || agentCard()?.characterCard?.data?.name }}</strong></h1\n >\n\n @if (agentCard()?.characterCard?.data?.instructions) {\n <div class=\"scenario\">\n <h4>{{ 'dataclouder.agentCards.dcAgentCardDetails.instructions' | translate }}</h4>\n <p>{{ agentCard()?.characterCard?.data?.instructions | parseCard: agentCard() }}</p>\n </div>\n }\n </div>\n </div>\n </div>\n</ng-template>\n\n<div \n style=\"display: flex; justify-content: center; align-items: center\" \n [class.transparent-mode]=\"avatarDisplay() === 'transparent'\"\n [class.fullscreen-mode]=\"avatarDisplay() === 'fullscreen'\"\n [class.card-mode]=\"avatarDisplay() === 'card'\"\n>\n @if (avatarDisplay() === 'transparent' || avatarDisplay() === 'fullscreen') {\n <ng-container *ngTemplateOutlet=\"cardContent\"></ng-container>\n } @else {\n <p-card>\n <ng-container *ngTemplateOutlet=\"cardContent\"></ng-container>\n </p-card>\n }\n</div>\n", styles: ["::ng-deep .p-card{width:420px;height:700px}::ng-deep .p-card .p-card-body{width:100%;height:100%}:host{display:block;width:100%;height:100%}.transparent-mode .card-container{width:420px;height:700px;position:relative}.transparent-mode .card-image{background:transparent}.fullscreen-mode{position:absolute;top:0;left:0;width:100%;height:100%;z-index:1}.fullscreen-mode .card-container{width:100%;height:100%;position:relative}.fullscreen-mode .card-image{background:transparent}.card-image{height:100%;width:100%;object-fit:cover;object-position:center;position:absolute;top:0;left:0;transition:filter .3s ease}::ng-deep .plyr--full-ui{width:100%;object-fit:cover;object-position:center;position:absolute;top:0;left:0;transition:filter .3s ease}.info-button{position:absolute;top:15px;right:15px;z-index:3}.info-button:hover{transform:scale(1.1)}.info-layer{height:100%;width:100%;position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;z-index:2;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);background-color:var(--chat-user-message-bg);color:#fff;opacity:1;clip-path:circle(0% at top right);transition:clip-path .5s cubic-bezier(.25,1,.5,1);pointer-events:none}.info-layer.active{clip-path:circle(150% at top right);pointer-events:auto}.info-content{padding:15px;text-align:center;max-width:90%}.info-content h1{margin-top:0;font-size:18px;margin-bottom:10px}.info-content p{font-size:12px;margin:0}::ng-deep .info-button .p-button{background-color:#ffffff47}\n"] }]
|
|
9665
10032
|
}], ctorParameters: () => [], propDecorators: { agentCardId: [{
|
|
9666
10033
|
type: Input
|
|
9667
10034
|
}], useNativePlayer: [{
|
|
9668
10035
|
type: Input
|
|
9669
|
-
}], activeAudioMessage: [{
|
|
9670
|
-
type: Input
|
|
9671
10036
|
}], isConversationActive: [{
|
|
9672
10037
|
type: Input
|
|
9673
10038
|
}], onStartConversation: [{ type: i0.Output, args: ["onStartConversation"] }], videoPlayerA: [{ type: i0.ViewChild, args: ['videoPlayerA', { isSignal: true }] }], videoPlayerB: [{ type: i0.ViewChild, args: ['videoPlayerB', { isSignal: true }] }], live2dModel: [{ type: i0.ViewChild, args: [i0.forwardRef(() => Live2dModelComponent), { isSignal: true }] }] } });
|
|
@@ -10289,5 +10654,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
10289
10654
|
* Generated bundle index. Do not edit.
|
|
10290
10655
|
*/
|
|
10291
10656
|
|
|
10292
|
-
export { ACCDataGenerationComponent, AGENT_CARDS_STATE_SERVICE, AIGenerationService, AgentCardListComponent, AgentCardUI, AgentCardsGenerationService, AgenticEngine, AgenticPattern, AudioService, AudioSpeed, BACKGROUND_SERVICE_TOKEN, CHAT_OBSERVER_CONFIG, CONVERSATION_AI_TOKEN, CardRouteService, CardRoutesComponent, CardsCreatorComponent, ChatEngineTestComponent, ChatEventType, ChatMessage, ChatMessageOrchestratorComponent, ChatMonitorService, ChatRole, ConditionOperator, ConditionType, ContextEngineService, ContextType, ConversationDTO, ConversationEvents, ConversationFlowStateService, ConversationMessagesDTO, ConversationPromptBuilderService, ConversationRuleService, ConversationRulesComponent, ConversationStatus, ConversationType, ConversationTypeOptions, DCAgentCardFormComponent, DCChatComponent, DCConversationUserChatSettingsComponent,
|
|
10657
|
+
export { ACCDataGenerationComponent, AGENT_CARDS_STATE_SERVICE, AIGenerationService, AgentCardListComponent, AgentCardUI, AgentCardsGenerationService, AgenticEngine, AgenticPattern, AudioService, AudioSpeed, BACKGROUND_SERVICE_TOKEN, CHAT_OBSERVER_CONFIG, CONVERSATION_AI_TOKEN, CardRouteService, CardRoutesComponent, CardsCreatorComponent, ChatEngineTestComponent, ChatEventType, ChatMessage, ChatMessageOrchestratorComponent, ChatMonitorService, ChatRole, ConditionOperator, ConditionType, ContextEngineService, ContextType, ConversationDTO, ConversationEvents, ConversationFlowStateService, ConversationMessagesDTO, ConversationPromptBuilderService, ConversationRuleService, ConversationRulesComponent, ConversationStatus, ConversationType, ConversationTypeOptions, DCAgentCardFormComponent, DCChatComponent, DCConversationUserChatSettingsComponent, DcAgentCardAvatarComponent, DcAgentCardDetailComponent, DcObserverInterventionComponent, DefaultAgentCardsService, DoActionTypeOptions, DynamicFlowService, DynamicFlowTaskTypeOptions, EAccountsPlatform, EAgentType, EDoActionType, EDynamicFlowTaskType, EntityThen, EntityWhatOptions, EntityWhenOptions, EvalResultStringDefinition, GlobalToolsService, LIVE2D_CONFIG, Live2dModelComponent, MessageContent, MessageContentDisplayer, MessagesStateService, ModelSelectorComponent, PolitoNotificationComponent, PopupService, PromptPreviewComponent, SectionType, SystemPromptType, TextEngineOptions, TextEngines, VIDEO_PLAYER_SERVICE_TOKEN, VideoPlayerNativeService, VideoPlayerService, VoiceTTSOption, VoiceTTSOptions, WordTimestamps, XKillsAgentCardsService, buildObjectTTSRequest, characterCardStringDataDefinition, convertToHTML, createAIModelFormGroup, defaultconvUserSettings, extractAudioAndTranscription, extractJsonFromResponse, getMoodStateLabelsAsString, getMoodStatePrompt, markdownToHtml, matchTranscription, provideAgentCardService, provideLive2D, removeEmojis, removeEmojisAndSpecialCharacters, removeSpecialCharacters };
|
|
10293
10658
|
//# sourceMappingURL=dataclouder-ngx-agent-cards.mjs.map
|