@dataclouder/ngx-agent-cards 0.2.13 → 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';
|
|
@@ -79,9 +79,6 @@ import { PaginatorModule } from 'primeng/paginator';
|
|
|
79
79
|
import * as i3$5 from 'primeng/speeddial';
|
|
80
80
|
import { SpeedDialModule } from 'primeng/speeddial';
|
|
81
81
|
import { isEmpty } from 'es-toolkit/compat';
|
|
82
|
-
import { Live2DModel } from 'untitled-pixi-live2d-engine/cubism';
|
|
83
|
-
import * as PIXI from 'pixi.js';
|
|
84
|
-
import { Application } from 'pixi.js';
|
|
85
82
|
import * as i3$6 from '@ngx-translate/core';
|
|
86
83
|
import { TranslateModule } from '@ngx-translate/core';
|
|
87
84
|
import { form, required, submit, FormField } from '@angular/forms/signals';
|
|
@@ -286,6 +283,7 @@ var ChatEventType;
|
|
|
286
283
|
ChatEventType["WordClicked"] = "wordClicked";
|
|
287
284
|
ChatEventType["MoodDetected"] = "moodDetected";
|
|
288
285
|
ChatEventType["AudioStarted"] = "audioStarted";
|
|
286
|
+
ChatEventType["AudioStopped"] = "audioStopped";
|
|
289
287
|
// Add other potential event types here as needed
|
|
290
288
|
// e.g., MessageSent = 'messageSent', SettingsChanged = 'settingsChanged', GoalCompleted = 'goalCompleted'
|
|
291
289
|
})(ChatEventType || (ChatEventType = {}));
|
|
@@ -1868,6 +1866,57 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
1868
1866
|
}]
|
|
1869
1867
|
}] });
|
|
1870
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
|
+
|
|
1871
1920
|
/**
|
|
1872
1921
|
* @description
|
|
1873
1922
|
* Service responsible for managing the real-time, dynamic state of a conversation flow.
|
|
@@ -1882,6 +1931,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
1882
1931
|
*/
|
|
1883
1932
|
class ConversationFlowStateService {
|
|
1884
1933
|
constructor() {
|
|
1934
|
+
this.chatMonitorService = inject(ChatMonitorService, { optional: true });
|
|
1885
1935
|
this.initialState = {
|
|
1886
1936
|
goal: { value: 0 },
|
|
1887
1937
|
challenges: [],
|
|
@@ -1947,6 +1997,7 @@ class ConversationFlowStateService {
|
|
|
1947
1997
|
}
|
|
1948
1998
|
if (newState.moodState?.value) {
|
|
1949
1999
|
this.moodUpdated.next(newState.moodState.value);
|
|
2000
|
+
this.chatMonitorService?.logCurrentMood(newState.moodState.value);
|
|
1950
2001
|
}
|
|
1951
2002
|
if (newState.goal && newState.goal.deltaPoints !== undefined && newState.goal.deltaPoints !== 0) {
|
|
1952
2003
|
this.goalEvaluationUpdated.next({ deltaPoints: newState.goal.deltaPoints });
|
|
@@ -2007,6 +2058,7 @@ class ConversationFlowStateService {
|
|
|
2007
2058
|
}));
|
|
2008
2059
|
if (moodState.value) {
|
|
2009
2060
|
this.moodUpdated.next(moodState.value);
|
|
2061
|
+
this.chatMonitorService?.logCurrentMood(moodState.value);
|
|
2010
2062
|
}
|
|
2011
2063
|
}
|
|
2012
2064
|
/**
|
|
@@ -2508,58 +2560,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
2508
2560
|
}]
|
|
2509
2561
|
}] });
|
|
2510
2562
|
|
|
2511
|
-
class ChatMonitorService {
|
|
2512
|
-
// Actualmente la única comunicación es con el background para cambiar el fondo.
|
|
2513
|
-
// El problema de comunicar así es si tiene que ser singleton, y en control markets tengo varias instancias de dc-chat.
|
|
2514
|
-
// Voy a utilizar el evento por ahora, y ver si combiene luego crear un servicio.
|
|
2515
|
-
// Se requiere comunicar el chat-engine con la app afuera, va a ver 2 formas de hacerlo.
|
|
2516
|
-
// Mediante eventos del chat, o estar subscrito al chat-monitor.
|
|
2517
|
-
// La complejidad del chat monitor es menor porque puedo injectarlo en cualquier lugar.
|
|
2518
|
-
// Los eventos estan bien pero si el vento viene muy interno tengo que propagarlo bastante.
|
|
2519
|
-
constructor() {
|
|
2520
|
-
this.messageAudioWillPlay = signal(null, ...(ngDevMode ? [{ debugName: "messageAudioWillPlay" }] : /* istanbul ignore next */ []));
|
|
2521
|
-
this.messageAudioWillPlay$ = this.messageAudioWillPlay.asReadonly();
|
|
2522
|
-
this.currentMood = signal(null, ...(ngDevMode ? [{ debugName: "currentMood" }] : /* istanbul ignore next */ []));
|
|
2523
|
-
this.isConversationActive = signal(false, ...(ngDevMode ? [{ debugName: "isConversationActive" }] : /* istanbul ignore next */ []));
|
|
2524
|
-
// Background
|
|
2525
|
-
this.backgroundUrl = signal(undefined, ...(ngDevMode ? [{ debugName: "backgroundUrl" }] : /* istanbul ignore next */ []));
|
|
2526
|
-
console.log(`%c ChatMonitorService instantiated: ${Math.random()}`, 'color: #00ff00; font-weight: bold; padding: 2px 4px; border: 1px solid #00ff00;');
|
|
2527
|
-
}
|
|
2528
|
-
logMessageAudioWillPlay(message) {
|
|
2529
|
-
// console.log('Audio will play....', message);
|
|
2530
|
-
this.messageAudioWillPlay.set(message);
|
|
2531
|
-
}
|
|
2532
|
-
logCurrentMood(mood) {
|
|
2533
|
-
this.currentMood.set(mood);
|
|
2534
|
-
}
|
|
2535
|
-
cleanMonitorVars() {
|
|
2536
|
-
this.currentMood.set(null);
|
|
2537
|
-
this.messageAudioWillPlay.set(null);
|
|
2538
|
-
this.isConversationActive.set(false);
|
|
2539
|
-
}
|
|
2540
|
-
setBackground(imageUrl) {
|
|
2541
|
-
if (imageUrl) {
|
|
2542
|
-
this.backgroundUrl.set(imageUrl);
|
|
2543
|
-
}
|
|
2544
|
-
else {
|
|
2545
|
-
this.backgroundUrl.set(undefined);
|
|
2546
|
-
}
|
|
2547
|
-
}
|
|
2548
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMonitorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2549
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMonitorService, providedIn: 'root' }); }
|
|
2550
|
-
}
|
|
2551
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMonitorService, decorators: [{
|
|
2552
|
-
type: Injectable,
|
|
2553
|
-
args: [{
|
|
2554
|
-
providedIn: 'root',
|
|
2555
|
-
}]
|
|
2556
|
-
}], ctorParameters: () => [] });
|
|
2557
|
-
|
|
2558
2563
|
class MessageProcessingService {
|
|
2559
2564
|
constructor() {
|
|
2560
2565
|
this.MAX_CHUNK_LENGTH = 190;
|
|
2561
2566
|
}
|
|
2562
|
-
processMessage(message, conversationSettings) {
|
|
2567
|
+
processMessage(message, conversationSettings, voiceSettings) {
|
|
2563
2568
|
const processedMessage = {
|
|
2564
2569
|
...message,
|
|
2565
2570
|
messageId: message.messageId || nanoid(),
|
|
@@ -2571,25 +2576,25 @@ class MessageProcessingService {
|
|
|
2571
2576
|
return processedMessage;
|
|
2572
2577
|
}
|
|
2573
2578
|
if (processedMessage.role === ChatRole.Assistant) {
|
|
2574
|
-
return this.processAssistantMessage(processedMessage, conversationSettings);
|
|
2579
|
+
return this.processAssistantMessage(processedMessage, conversationSettings, voiceSettings);
|
|
2575
2580
|
}
|
|
2576
2581
|
return processedMessage;
|
|
2577
2582
|
}
|
|
2578
|
-
processAssistantMessage(message, settings) {
|
|
2583
|
+
processAssistantMessage(message, settings, voiceSettings) {
|
|
2579
2584
|
if (settings.avatarImages.assistant) {
|
|
2580
2585
|
message.imgUrl = settings.avatarImages.assistant;
|
|
2581
2586
|
}
|
|
2582
|
-
const mainVoice = settings?.mainVoice?.voice || settings?.tts?.voice;
|
|
2587
|
+
const mainVoice = voiceSettings?.main?.voice || settings?.mainVoice?.voice || settings?.tts?.voice;
|
|
2583
2588
|
message.voice = this.getVoice(mainVoice);
|
|
2584
2589
|
switch (settings.textEngine) {
|
|
2585
2590
|
case TextEngines.SimpleText:
|
|
2586
2591
|
this.processSimpleText(message, settings);
|
|
2587
2592
|
break;
|
|
2588
2593
|
case TextEngines.MarkdownMultiMessages:
|
|
2589
|
-
this.processMultiMessages(message, settings);
|
|
2594
|
+
this.processMultiMessages(message, settings, voiceSettings);
|
|
2590
2595
|
break;
|
|
2591
2596
|
case TextEngines.MarkdownSSML:
|
|
2592
|
-
this.processSSML(message, settings);
|
|
2597
|
+
this.processSSML(message, settings, voiceSettings);
|
|
2593
2598
|
break;
|
|
2594
2599
|
}
|
|
2595
2600
|
return message;
|
|
@@ -2608,9 +2613,9 @@ class MessageProcessingService {
|
|
|
2608
2613
|
};
|
|
2609
2614
|
});
|
|
2610
2615
|
}
|
|
2611
|
-
processMultiMessages(message, settings) {
|
|
2616
|
+
processMultiMessages(message, settings, voiceSettings) {
|
|
2612
2617
|
const htmlSegments = convertToHTML(message.content);
|
|
2613
|
-
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';
|
|
2614
2619
|
const processedSegments = [];
|
|
2615
2620
|
for (const segment of htmlSegments) {
|
|
2616
2621
|
const isItalics = segment.tag === 'em';
|
|
@@ -2659,11 +2664,12 @@ class MessageProcessingService {
|
|
|
2659
2664
|
return `<p>${text}</p>`;
|
|
2660
2665
|
}
|
|
2661
2666
|
}
|
|
2662
|
-
processSSML(message, settings) {
|
|
2663
|
-
|
|
2667
|
+
processSSML(message, settings, voiceSettings) {
|
|
2668
|
+
const secondaryVoice = voiceSettings?.secondary?.voice || settings?.secondaryVoice?.voice || settings?.tts?.secondaryVoice;
|
|
2669
|
+
if (!secondaryVoice) {
|
|
2664
2670
|
throw new Error('Secondary voice is required for SSML');
|
|
2665
2671
|
}
|
|
2666
|
-
const content = this.subsItalicsByTag(message.content,
|
|
2672
|
+
const content = this.subsItalicsByTag(message.content, secondaryVoice);
|
|
2667
2673
|
message.ssml = `<speak>${content}</speak>`;
|
|
2668
2674
|
}
|
|
2669
2675
|
splitContent(content, maxLength) {
|
|
@@ -3472,7 +3478,7 @@ class ConversationService {
|
|
|
3472
3478
|
}
|
|
3473
3479
|
createNewUserMessage() {
|
|
3474
3480
|
const message = { content: '...', role: ChatRole.User };
|
|
3475
|
-
const processedMessage = this.messageProcessingService.processMessage(message, this.conversationSettingsState());
|
|
3481
|
+
const processedMessage = this.messageProcessingService.processMessage(message, this.conversationSettingsState(), this.agentCardState.agentCard$()?.voice);
|
|
3476
3482
|
this.messagesStateService.addMessage(processedMessage);
|
|
3477
3483
|
return processedMessage.messageId;
|
|
3478
3484
|
}
|
|
@@ -3579,7 +3585,7 @@ class ConversationService {
|
|
|
3579
3585
|
const firstAssistantMsg = conversationSettings.messages.find((message) => message.role === ChatRole.Assistant);
|
|
3580
3586
|
if (firstAssistantMsg) {
|
|
3581
3587
|
// Process the first assistant message
|
|
3582
|
-
const processedMessage = this.messageProcessingService.processMessage(firstAssistantMsg, this.conversationSettingsState());
|
|
3588
|
+
const processedMessage = this.messageProcessingService.processMessage(firstAssistantMsg, this.conversationSettingsState(), this.agentCardState.agentCard$()?.voice);
|
|
3583
3589
|
this.messagesStateService.updateMessage(firstAssistantMsg.messageId, processedMessage);
|
|
3584
3590
|
// Find the index of the message with the matching ID
|
|
3585
3591
|
const messageIndex = conversationSettings.messages.findIndex((message) => message.messageId === firstAssistantMsg.messageId);
|
|
@@ -3628,7 +3634,7 @@ class ConversationService {
|
|
|
3628
3634
|
}
|
|
3629
3635
|
else {
|
|
3630
3636
|
// Means is new meessage, Process and add the new message
|
|
3631
|
-
const processedMessage = this.messageProcessingService.processMessage(message, this.conversationSettingsState());
|
|
3637
|
+
const processedMessage = this.messageProcessingService.processMessage(message, this.conversationSettingsState(), this.agentCardState.agentCard$()?.voice);
|
|
3632
3638
|
// Ensure ID exists (processMessage should handle this, but fallback just in case)
|
|
3633
3639
|
processedMessage.messageId = processedMessage.messageId || nanoid();
|
|
3634
3640
|
this.messagesStateService.addMessage(processedMessage);
|
|
@@ -3699,7 +3705,7 @@ class ConversationService {
|
|
|
3699
3705
|
throw new Error('No message returned from AI');
|
|
3700
3706
|
}
|
|
3701
3707
|
// Process response
|
|
3702
|
-
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);
|
|
3703
3709
|
this.messagesStateService.addMessage(newMessage);
|
|
3704
3710
|
this.isThinkingSignal.set(false);
|
|
3705
3711
|
// Run Dynamic Flow Evaluations
|
|
@@ -3708,9 +3714,10 @@ class ConversationService {
|
|
|
3708
3714
|
}
|
|
3709
3715
|
async handleStreamingResponse(conversation, conversationSettings) {
|
|
3710
3716
|
const messageId = nanoid();
|
|
3711
|
-
const
|
|
3717
|
+
const agentCard = this.agentCardState.agentCard$();
|
|
3718
|
+
const mainVoice = agentCard?.voice?.main?.voice || conversationSettings?.mainVoice?.voice || conversationSettings?.tts?.voice;
|
|
3712
3719
|
const assistantVoice = this.messageProcessingService.getVoice(mainVoice);
|
|
3713
|
-
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';
|
|
3714
3721
|
let assistantMessage = {
|
|
3715
3722
|
messageId,
|
|
3716
3723
|
role: ChatRole.Assistant,
|
|
@@ -3882,7 +3889,7 @@ class ConversationService {
|
|
|
3882
3889
|
isLoading: true,
|
|
3883
3890
|
};
|
|
3884
3891
|
// Process to get initial structure and ensure it's ready for display
|
|
3885
|
-
const processedPlaceholder = this.messageProcessingService.processMessage(placeholder, this.conversationSettingsState());
|
|
3892
|
+
const processedPlaceholder = this.messageProcessingService.processMessage(placeholder, this.conversationSettingsState(), this.agentCardState.agentCard$()?.voice);
|
|
3886
3893
|
// Use passed updateId or newly generated one from processing
|
|
3887
3894
|
const messageId = updateId || processedPlaceholder.messageId || nanoid();
|
|
3888
3895
|
processedPlaceholder.messageId = messageId;
|
|
@@ -3996,10 +4003,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
3996
4003
|
const VIDEO_PLAYER_SERVICE_TOKEN = new InjectionToken('VIDEO_PLAYER_SERVICE_TOKEN');
|
|
3997
4004
|
const BACKGROUND_SERVICE_TOKEN = new InjectionToken('BACKGROUND_SERVICE_TOKEN');
|
|
3998
4005
|
const CHAT_OBSERVER_CONFIG = new InjectionToken('CHAT_OBSERVER_CONFIG');
|
|
4006
|
+
const LIVE2D_CONFIG = new InjectionToken('LIVE2D_CONFIG');
|
|
4007
|
+
function provideLive2D(config) {
|
|
4008
|
+
return {
|
|
4009
|
+
provide: LIVE2D_CONFIG,
|
|
4010
|
+
useValue: config
|
|
4011
|
+
};
|
|
4012
|
+
}
|
|
3999
4013
|
|
|
4000
4014
|
class VideoPlayerService {
|
|
4001
4015
|
constructor() {
|
|
4002
|
-
this.conversationFlowStateService = inject(ConversationFlowStateService);
|
|
4003
4016
|
this.useNativePlayer = false;
|
|
4004
4017
|
this.isRewinding = false;
|
|
4005
4018
|
this.videoQueue = [];
|
|
@@ -4009,17 +4022,6 @@ class VideoPlayerService {
|
|
|
4009
4022
|
this.agentCard = signal(undefined, ...(ngDevMode ? [{ debugName: "agentCard" }] : /* istanbul ignore next */ []));
|
|
4010
4023
|
console.log(' CONSTRUCTOR FOR VIDEO PLAYER...');
|
|
4011
4024
|
this.playAndRewindedSubscription = this.playAndRewinded$.subscribe(() => { });
|
|
4012
|
-
this.moodSubscription = this.conversationFlowStateService.moodUpdated$.subscribe((mood) => {
|
|
4013
|
-
console.log('CHANGING MOOD.. ', mood);
|
|
4014
|
-
const card = this.agentCard();
|
|
4015
|
-
if (mood && card?.assets?.motions) {
|
|
4016
|
-
const motionUrl = card.assets.motions.find((m) => m.metadata?.moodState === mood)?.url;
|
|
4017
|
-
if (motionUrl) {
|
|
4018
|
-
this.addVideosToQueue([motionUrl]);
|
|
4019
|
-
this.playAndRewind();
|
|
4020
|
-
}
|
|
4021
|
-
}
|
|
4022
|
-
});
|
|
4023
4025
|
}
|
|
4024
4026
|
setAgentCard(card) {
|
|
4025
4027
|
this.agentCard.set(card);
|
|
@@ -4151,9 +4153,6 @@ class VideoPlayerService {
|
|
|
4151
4153
|
if (this.playAndRewindedSubscription) {
|
|
4152
4154
|
this.playAndRewindedSubscription.unsubscribe();
|
|
4153
4155
|
}
|
|
4154
|
-
if (this.moodSubscription) {
|
|
4155
|
-
this.moodSubscription.unsubscribe();
|
|
4156
|
-
}
|
|
4157
4156
|
}
|
|
4158
4157
|
playNextInQueueNative() {
|
|
4159
4158
|
if (this.videoQueue.length > 0) {
|
|
@@ -4241,25 +4240,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
4241
4240
|
|
|
4242
4241
|
class VideoPlayerNativeService {
|
|
4243
4242
|
constructor() {
|
|
4244
|
-
this.conversationFlowStateService = inject(ConversationFlowStateService);
|
|
4245
4243
|
this.videoQueue = [];
|
|
4246
4244
|
this.defaultVideoUrl = null;
|
|
4247
4245
|
this.pendingPlayingHandler = null;
|
|
4248
4246
|
this.pendingPlayingTarget = null;
|
|
4249
4247
|
this.agentCard = signal(undefined, ...(ngDevMode ? [{ debugName: "agentCard" }] : /* istanbul ignore next */ []));
|
|
4250
4248
|
this.isVideoPlaying = signal(false, ...(ngDevMode ? [{ debugName: "isVideoPlaying" }] : /* istanbul ignore next */ []));
|
|
4251
|
-
this.moodSubscription = this.conversationFlowStateService.moodUpdated$.subscribe((mood) => {
|
|
4252
|
-
const card = this.agentCard();
|
|
4253
|
-
if (!mood || !card?.assets?.motions)
|
|
4254
|
-
return;
|
|
4255
|
-
const motionUrl = card.assets.motions.find((m) => m.metadata?.moodState === mood)?.url;
|
|
4256
|
-
if (!motionUrl)
|
|
4257
|
-
return;
|
|
4258
|
-
// Interrupt current playback. transitionToVideo loads the new clip
|
|
4259
|
-
// into the hidden <video> and crossfades only when its first frame
|
|
4260
|
-
// is ready, so the active clip keeps showing until then (no flash).
|
|
4261
|
-
this.transitionToVideo(motionUrl);
|
|
4262
|
-
});
|
|
4263
4249
|
this.onVideoEndedBound = this.onVideoEnded.bind(this);
|
|
4264
4250
|
}
|
|
4265
4251
|
setAgentCard(card) {
|
|
@@ -4342,7 +4328,6 @@ class VideoPlayerNativeService {
|
|
|
4342
4328
|
this.agentCard.set(undefined);
|
|
4343
4329
|
}
|
|
4344
4330
|
ngOnDestroy() {
|
|
4345
|
-
this.moodSubscription?.unsubscribe();
|
|
4346
4331
|
this.destroyPlayer();
|
|
4347
4332
|
}
|
|
4348
4333
|
onVideoEnded() {
|
|
@@ -4625,563 +4610,831 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
4625
4610
|
args: [DCMicComponent]
|
|
4626
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"] }] } });
|
|
4627
4612
|
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
//
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
}
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
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');
|
|
4647
4663
|
}
|
|
4648
|
-
|
|
4649
|
-
|
|
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');
|
|
4650
4684
|
}
|
|
4651
4685
|
}
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
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
|
+
}
|
|
4655
4766
|
}
|
|
4656
|
-
|
|
4657
|
-
|
|
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);
|
|
4658
4773
|
}
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
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([]);
|
|
4663
4780
|
}
|
|
4664
|
-
|
|
4665
|
-
|
|
4781
|
+
if (messageSubject) {
|
|
4782
|
+
messageSubject.next([]);
|
|
4666
4783
|
}
|
|
4667
4784
|
}
|
|
4668
|
-
else {
|
|
4669
|
-
// It's a word (including punctuation)
|
|
4670
|
-
const currentTag = tagStack.length > 0 ? tagStack[tagStack.length - 1] : '';
|
|
4671
|
-
result.push({ word: token, tag: currentTag });
|
|
4672
|
-
}
|
|
4673
4785
|
}
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
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');
|
|
4799
|
+
}
|
|
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);
|
|
4683
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 {
|
|
4684
4870
|
constructor() {
|
|
4685
|
-
//
|
|
4686
|
-
this.
|
|
4687
|
-
this.
|
|
4688
|
-
this.
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
this.
|
|
4692
|
-
this.
|
|
4693
|
-
this.
|
|
4694
|
-
//
|
|
4695
|
-
this.
|
|
4696
|
-
this.
|
|
4697
|
-
//
|
|
4698
|
-
this.
|
|
4699
|
-
this.
|
|
4700
|
-
this.
|
|
4701
|
-
this.
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
this.transcriptionTimestamps = computed(() => {
|
|
4733
|
-
const msg = this.message();
|
|
4734
|
-
if (!msg || !msg.transcription) {
|
|
4735
|
-
return [];
|
|
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
|
+
});
|
|
4736
4918
|
}
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
// if transcriptionTimestamps is calculated then hasTranscription is true.
|
|
4740
|
-
this.hasTranscription = computed(() => {
|
|
4741
|
-
return this.transcriptionTimestamps().length > 0;
|
|
4742
|
-
}, ...(ngDevMode ? [{ debugName: "hasTranscription" }] : /* istanbul ignore next */ []));
|
|
4743
|
-
effect(() => {
|
|
4744
|
-
const currentMsg = this.message();
|
|
4745
|
-
if (!currentMsg)
|
|
4746
|
-
return;
|
|
4747
|
-
const hasTranscription = this.hasTranscription();
|
|
4748
|
-
const idChanged = currentMsg.messageId !== this.currentMessageId;
|
|
4749
|
-
const audioUrlChanged = currentMsg.audioUrl !== this.lastAudioUrl;
|
|
4750
|
-
const transcriptionChanged = hasTranscription !== this.lastHasTranscription;
|
|
4751
|
-
if (idChanged || audioUrlChanged || transcriptionChanged) {
|
|
4752
|
-
this.currentMessageId = currentMsg.messageId;
|
|
4753
|
-
this.lastAudioUrl = currentMsg.audioUrl;
|
|
4754
|
-
this.lastHasTranscription = hasTranscription;
|
|
4755
|
-
this.localAudioStatus.set(null); // Reset local state on message/audio change
|
|
4756
|
-
this.initializeBasedOnMessage(currentMsg);
|
|
4919
|
+
else {
|
|
4920
|
+
this.messages.set([...messages]);
|
|
4757
4921
|
}
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
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
|
+
}
|
|
4762
4942
|
}
|
|
4763
4943
|
});
|
|
4764
|
-
effect(() => {
|
|
4765
|
-
this.wordsWithMetaState = this.wordWithMeta();
|
|
4766
|
-
});
|
|
4767
|
-
}
|
|
4768
|
-
trackByIndex(index, item) {
|
|
4769
|
-
return index;
|
|
4770
4944
|
}
|
|
4771
|
-
|
|
4772
|
-
this.
|
|
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
|
+
}
|
|
4773
4960
|
}
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
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
|
+
})));
|
|
4777
4982
|
}
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
this.
|
|
4781
|
-
|
|
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,
|
|
4782
4991
|
});
|
|
4783
|
-
const firstState = this.transcriptionTimestamps()?.map((word, index) => ({
|
|
4784
|
-
word: word.word,
|
|
4785
|
-
index,
|
|
4786
|
-
isHighlighted: false,
|
|
4787
|
-
tag: '',
|
|
4788
|
-
marked: word.word.toLowerCase() == this.markWord()?.toLowerCase(),
|
|
4789
|
-
}));
|
|
4790
|
-
console.log('firstState', firstState);
|
|
4791
|
-
this.wordWithMeta.set(firstState);
|
|
4792
|
-
// Para mostrar las transcripciones iliminadas: el audio tiene que sonar y en la subscripción apareceran los cambios.
|
|
4793
4992
|
}
|
|
4794
|
-
|
|
4795
|
-
|
|
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);
|
|
4796
5012
|
}
|
|
4797
|
-
// Removal of shouldPlayAudio check - playback now handled by isPendingAudio effect
|
|
4798
5013
|
}
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
audio = new Audio(audioUrl);
|
|
4803
|
-
this.audioElement.set(audio);
|
|
4804
|
-
this.setupAudioEventListeners(audio);
|
|
5014
|
+
async processNextInQueue() {
|
|
5015
|
+
if (this.audioQueue.length === 0 || this.isGenerating() || this.currentPlayingIndex() !== null) {
|
|
5016
|
+
return;
|
|
4805
5017
|
}
|
|
4806
|
-
|
|
4807
|
-
|
|
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);
|
|
4808
5028
|
}
|
|
5029
|
+
this.preGenerateNextIfNeeded();
|
|
4809
5030
|
}
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
.
|
|
4814
|
-
|
|
4815
|
-
|
|
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) {
|
|
4816
5038
|
return;
|
|
4817
5039
|
}
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
tag: word.tag,
|
|
4828
|
-
marked: word.word.toLowerCase() == this.markWord()?.toLowerCase(),
|
|
4829
|
-
}));
|
|
4830
|
-
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);
|
|
4831
5049
|
}
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4835
|
-
.subscribe(() => {
|
|
4836
|
-
const resetWords = this.wordWithMeta().map((word) => ({ ...word, isHighlighted: false }));
|
|
4837
|
-
this.wordWithMeta.set(resetWords);
|
|
4838
|
-
const currentMsg = this.message();
|
|
4839
|
-
if (currentMsg) {
|
|
4840
|
-
// No direct mutation of input!
|
|
4841
|
-
this.updateAudioStatus('finished');
|
|
4842
|
-
this.audioCompleted.emit(currentMsg);
|
|
5050
|
+
finally {
|
|
5051
|
+
this.preGenerationInProgress.set(false);
|
|
4843
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;
|
|
4844
5091
|
});
|
|
4845
5092
|
}
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
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;
|
|
4855
5103
|
}
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
const audioElement = this.audioElement();
|
|
4859
|
-
if (audioElement) {
|
|
4860
|
-
audioElement.pause();
|
|
4861
|
-
audioElement.src = '';
|
|
4862
|
-
const resetWords = this.wordWithMeta().map((word) => ({ ...word, isHighlighted: false }));
|
|
4863
|
-
this.wordWithMeta.set(resetWords);
|
|
5104
|
+
finally {
|
|
5105
|
+
this.isGenerating.set(false);
|
|
4864
5106
|
}
|
|
4865
5107
|
}
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
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
|
|
4873
5134
|
}
|
|
4874
5135
|
else {
|
|
4875
|
-
|
|
4876
|
-
this.updateAudioStatus('stopped');
|
|
5136
|
+
tagStack.push('bold italic'); // Opening bold italic
|
|
4877
5137
|
}
|
|
4878
5138
|
}
|
|
4879
|
-
else if (
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
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
|
+
}
|
|
4887
5146
|
}
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
tag: word.tag,
|
|
4896
|
-
}));
|
|
4897
|
-
this.wordWithMeta.set(plainWords);
|
|
4898
|
-
}
|
|
4899
|
-
onWordClick(wordData) {
|
|
4900
|
-
const currentMsg = this.message();
|
|
4901
|
-
if (!currentMsg)
|
|
4902
|
-
return;
|
|
4903
|
-
const clickedWord = 'isHighlighted' in wordData
|
|
4904
|
-
? { word: wordData.word, index: wordData.index, messageId: currentMsg.messageId }
|
|
4905
|
-
: { ...wordData, messageId: currentMsg.messageId };
|
|
4906
|
-
this.wordClicked.emit(clickedWord);
|
|
4907
|
-
}
|
|
4908
|
-
startAudioPlayback() {
|
|
4909
|
-
const audioElement = this.audioElement();
|
|
4910
|
-
if (!audioElement)
|
|
4911
|
-
return;
|
|
4912
|
-
try {
|
|
4913
|
-
audioElement.play();
|
|
4914
|
-
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
|
+
}
|
|
4915
5154
|
}
|
|
4916
|
-
|
|
4917
|
-
|
|
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 });
|
|
4918
5159
|
}
|
|
4919
|
-
// Important: we move it to playing status immediately to avoid re-triggering the effect
|
|
4920
|
-
this.updateAudioStatus('playing');
|
|
4921
|
-
}
|
|
4922
|
-
debug() {
|
|
4923
|
-
console.log('debug', this.wordWithMeta());
|
|
4924
5160
|
}
|
|
4925
|
-
|
|
4926
|
-
|
|
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;
|
|
4927
5164
|
}
|
|
4928
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageContentDisplayer, decorators: [{
|
|
4929
|
-
type: Component,
|
|
4930
|
-
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"] }]
|
|
4931
|
-
}], 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: [{
|
|
4932
|
-
type: HostBinding,
|
|
4933
|
-
args: ['style.--highlight-bg-color']
|
|
4934
|
-
}], playAudio: [{ type: i0.Output, args: ["playAudio"] }], audioCompleted: [{ type: i0.Output, args: ["audioCompleted"] }], audioStatusChanged: [{ type: i0.Output, args: ["audioStatusChanged"] }], wordClicked: [{ type: i0.Output, args: ["wordClicked"] }] } });
|
|
4935
5165
|
|
|
4936
5166
|
/**
|
|
4937
|
-
* @
|
|
5167
|
+
* @Component
|
|
4938
5168
|
*
|
|
4939
5169
|
* @description
|
|
4940
|
-
*
|
|
4941
|
-
* It manages a queue of messages and handles the generation and playback of audio for each message.
|
|
5170
|
+
* Variante "atada al chat" de `MessageContentDisplayer`.
|
|
4942
5171
|
*
|
|
4943
|
-
*
|
|
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)
|
|
4944
5186
|
*/
|
|
4945
|
-
class
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
this.conversationService = inject(ConversationService);
|
|
4949
|
-
this.chatMonitorService = inject(ChatMonitorService);
|
|
4950
|
-
this.userService = inject(UserService);
|
|
4951
|
-
this.messagesStateService = inject(MessagesStateService);
|
|
4952
|
-
// State Signals
|
|
4953
|
-
this.messages = signal([], ...(ngDevMode ? [{ debugName: "messages" }] : /* istanbul ignore next */ []));
|
|
4954
|
-
this.messageRole = signal(ChatRole.User, ...(ngDevMode ? [{ debugName: "messageRole" }] : /* istanbul ignore next */ []));
|
|
4955
|
-
this.messageToPlay = signal(null, ...(ngDevMode ? [{ debugName: "messageToPlay" }] : /* istanbul ignore next */ []));
|
|
4956
|
-
// Public Signals for components to consume
|
|
4957
|
-
this.messagesSignal = this.messages.asReadonly();
|
|
4958
|
-
this.messageToPlay$ = this.messageToPlay.asReadonly();
|
|
4959
|
-
// Audio queue management
|
|
4960
|
-
this.audioQueue = [];
|
|
4961
|
-
this.isGenerating = signal(false, ...(ngDevMode ? [{ debugName: "isGenerating" }] : /* istanbul ignore next */ []));
|
|
4962
|
-
this.currentPlayingIndex = signal(null, ...(ngDevMode ? [{ debugName: "currentPlayingIndex" }] : /* istanbul ignore next */ []));
|
|
4963
|
-
this.preGenerationInProgress = signal(false, ...(ngDevMode ? [{ debugName: "preGenerationInProgress" }] : /* istanbul ignore next */ []));
|
|
4964
|
-
console.log('MessageOrchestrationService initialized');
|
|
5187
|
+
class MessageContentReactive {
|
|
5188
|
+
get hostHighlightColor() {
|
|
5189
|
+
return this.highlightColor();
|
|
4965
5190
|
}
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
}
|
|
4991
|
-
return newMsg;
|
|
4992
|
-
});
|
|
4993
|
-
});
|
|
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';
|
|
4994
5215
|
}
|
|
4995
|
-
|
|
4996
|
-
|
|
5216
|
+
const status = msg?.audioStatus;
|
|
5217
|
+
if (status === 'playing') {
|
|
5218
|
+
return 'playing';
|
|
4997
5219
|
}
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
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();
|
|
5018
5268
|
}
|
|
5019
5269
|
});
|
|
5270
|
+
effect(() => {
|
|
5271
|
+
this.wordsWithMetaState = this.wordWithMeta();
|
|
5272
|
+
});
|
|
5020
5273
|
}
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
if (index !== null) {
|
|
5024
|
-
console.log(`Audio completed for index ${index}, setting status to finished`);
|
|
5025
|
-
this.updateAudioStatus(message, 'finished', index);
|
|
5026
|
-
}
|
|
5027
|
-
else {
|
|
5028
|
-
console.warn('Audio completed but currentPlayingIndex is null');
|
|
5029
|
-
}
|
|
5030
|
-
this.conversationService.currentAudioStatus.set({ message, completed: true });
|
|
5031
|
-
this.currentPlayingIndex.set(null);
|
|
5032
|
-
this.messageToPlay.set(null);
|
|
5033
|
-
if (this.audioQueue.length > 0) {
|
|
5034
|
-
this.processNextInQueue();
|
|
5035
|
-
}
|
|
5274
|
+
trackByIndex(index, item) {
|
|
5275
|
+
return index;
|
|
5036
5276
|
}
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
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');
|
|
5051
5290
|
}
|
|
5052
|
-
// Update local state: set this one as active, others as inactive
|
|
5053
|
-
this.messages.update((messages) => messages.map((m, idx) => ({
|
|
5054
|
-
...m,
|
|
5055
|
-
isActive: idx === index,
|
|
5056
|
-
// Also update the current one's status
|
|
5057
|
-
...(idx === index ? { audioStatus: status } : {}),
|
|
5058
|
-
})));
|
|
5059
|
-
}
|
|
5060
|
-
else {
|
|
5061
|
-
// For other statuses (stopped, finished), preserve isActive and update status
|
|
5062
|
-
this.changeStates(index, { ...message, audioStatus: status });
|
|
5063
5291
|
}
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
this.
|
|
5067
|
-
audioStatus: status,
|
|
5068
|
-
});
|
|
5292
|
+
else if (currentMsg?.audioUrl) {
|
|
5293
|
+
this.initializeAudio(currentMsg.audioUrl);
|
|
5294
|
+
this.startAudioPlayback();
|
|
5069
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
|
+
});
|
|
5070
5307
|
}
|
|
5308
|
+
// --- Reporte de estado directo al orquestador (sin @Output) ---
|
|
5071
5309
|
/**
|
|
5072
|
-
*
|
|
5073
|
-
*
|
|
5074
|
-
*
|
|
5075
|
-
* @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).
|
|
5076
5312
|
*/
|
|
5077
|
-
|
|
5078
|
-
this.
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
const messages = this.messages();
|
|
5082
|
-
if (messages && messages.length > 0) {
|
|
5083
|
-
this.audioQueue = messages
|
|
5084
|
-
.map((m, index) => ({ status: m.audioStatus, index }))
|
|
5085
|
-
.filter((item) => item.status !== 'finished' && item.status !== 'skip')
|
|
5086
|
-
.map((item) => item.index);
|
|
5087
|
-
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());
|
|
5088
5317
|
}
|
|
5089
5318
|
}
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5319
|
+
// --- Audio lifecycle ---
|
|
5320
|
+
initializeBasedOnMessage(msg) {
|
|
5321
|
+
if (msg.audioUrl) {
|
|
5322
|
+
this.initializeAudio(msg.audioUrl);
|
|
5093
5323
|
}
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
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);
|
|
5098
5333
|
}
|
|
5099
|
-
|
|
5100
|
-
|
|
5334
|
+
else {
|
|
5335
|
+
this.initializePlainTextWords(msg.text || msg.content || '');
|
|
5101
5336
|
}
|
|
5102
|
-
|
|
5103
|
-
|
|
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;
|
|
5104
5347
|
}
|
|
5105
|
-
this.preGenerateNextIfNeeded();
|
|
5106
5348
|
}
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
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);
|
|
5115
5366
|
}
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
this.
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
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');
|
|
5125
5380
|
}
|
|
5126
|
-
|
|
5127
|
-
|
|
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);
|
|
5128
5391
|
}
|
|
5129
|
-
}
|
|
5392
|
+
});
|
|
5130
5393
|
}
|
|
5131
|
-
|
|
5132
|
-
const
|
|
5133
|
-
if (
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
this.messageToPlay.set(messageAudio);
|
|
5138
|
-
this.currentPlayingIndex.set(index);
|
|
5394
|
+
startAudioPlayback() {
|
|
5395
|
+
const audioElement = this.audioElement();
|
|
5396
|
+
if (!audioElement)
|
|
5397
|
+
return;
|
|
5398
|
+
const currentMsg = this.message();
|
|
5399
|
+
if (!currentMsg)
|
|
5139
5400
|
return;
|
|
5140
|
-
}
|
|
5141
|
-
const message = { ...messages[index], isLoading: true };
|
|
5142
|
-
this.changeStates(index, message);
|
|
5143
5401
|
try {
|
|
5144
|
-
|
|
5145
|
-
messageAudio.isLoading = false;
|
|
5146
|
-
messageAudio.audioStatus = 'pending';
|
|
5147
|
-
this.changeStates(index, messageAudio);
|
|
5148
|
-
this.chatMonitorService.logMessageAudioWillPlay(messageAudio);
|
|
5149
|
-
this.messageToPlay.set(messageAudio);
|
|
5402
|
+
this.audioTextSyncService.playAudio(audioElement, currentMsg);
|
|
5150
5403
|
}
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
this.changeStates(index, message);
|
|
5404
|
+
catch (error) {
|
|
5405
|
+
console.error('Error playing audio:', error);
|
|
5154
5406
|
}
|
|
5155
|
-
this.
|
|
5156
|
-
}
|
|
5157
|
-
changeStates(index, messageAudio) {
|
|
5158
|
-
this.messages.update((messages) => {
|
|
5159
|
-
const newMessages = [...messages];
|
|
5160
|
-
newMessages[index] = { ...messages[index], ...messageAudio };
|
|
5161
|
-
return newMessages;
|
|
5162
|
-
});
|
|
5407
|
+
this.reportStatus('playing');
|
|
5163
5408
|
}
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
const
|
|
5170
|
-
|
|
5171
|
-
const speechAudio = await this.conversationService.getTTSFile(ttsObject);
|
|
5172
|
-
message = extractAudioAndTranscription(message, speechAudio);
|
|
5173
|
-
return message;
|
|
5174
|
-
}
|
|
5175
|
-
finally {
|
|
5176
|
-
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);
|
|
5177
5416
|
}
|
|
5178
5417
|
}
|
|
5179
|
-
|
|
5180
|
-
|
|
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 }); }
|
|
5181
5430
|
}
|
|
5182
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type:
|
|
5183
|
-
type:
|
|
5184
|
-
|
|
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
|
+
}] } });
|
|
5185
5438
|
|
|
5186
5439
|
/**
|
|
5187
5440
|
* @Component
|
|
@@ -5190,8 +5443,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
5190
5443
|
* This component orchestrates the display and audio playback of chat messages.
|
|
5191
5444
|
* It uses the `MessageOrchestrationService` to manage the message queue and audio playback.
|
|
5192
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
|
+
*
|
|
5193
5450
|
* @see MessageOrchestrationService
|
|
5194
|
-
* @see
|
|
5451
|
+
* @see MessageContentReactive
|
|
5195
5452
|
*/
|
|
5196
5453
|
class ChatMessageOrchestratorComponent {
|
|
5197
5454
|
constructor() {
|
|
@@ -5217,41 +5474,12 @@ class ChatMessageOrchestratorComponent {
|
|
|
5217
5474
|
this.orchestrationService.startOrchestration(this.messages(), this.messageRole());
|
|
5218
5475
|
});
|
|
5219
5476
|
}
|
|
5220
|
-
/**
|
|
5221
|
-
* @description
|
|
5222
|
-
* Handles the `audioCompleted` event from the `MessageContentDisplayer`.
|
|
5223
|
-
*
|
|
5224
|
-
* @param message - The `MessageContent` object whose audio has finished playing.
|
|
5225
|
-
*/
|
|
5226
|
-
onAudioCompleted(message) {
|
|
5227
|
-
this.orchestrationService.audioCompleted(message);
|
|
5228
|
-
this.audioCompleted.emit(message);
|
|
5229
|
-
}
|
|
5230
|
-
/**
|
|
5231
|
-
* @description
|
|
5232
|
-
* Handles the `audioStatusChanged` event from the `MessageContentDisplayer`.
|
|
5233
|
-
*
|
|
5234
|
-
* @param event - The event object containing the `MessageContent` and its new `AudioStatus`.
|
|
5235
|
-
* @param index - The index of the message in the array.
|
|
5236
|
-
*/
|
|
5237
|
-
onAudioStatusChanged(event, index) {
|
|
5238
|
-
this.orchestrationService.updateAudioStatus(event.message, event.status, index);
|
|
5239
|
-
}
|
|
5240
|
-
/**
|
|
5241
|
-
* @description
|
|
5242
|
-
* Handles the `wordClicked` event from the `MessageContentDisplayer`.
|
|
5243
|
-
*
|
|
5244
|
-
* @param wordData - The `WordData` object containing information about the clicked word.
|
|
5245
|
-
*/
|
|
5246
|
-
onWordClicked(wordData) {
|
|
5247
|
-
this.orchestrationService.wordClicked(wordData);
|
|
5248
|
-
}
|
|
5249
5477
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMessageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
5250
|
-
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 }); }
|
|
5251
5479
|
}
|
|
5252
5480
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMessageOrchestratorComponent, decorators: [{
|
|
5253
5481
|
type: Component,
|
|
5254
|
-
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"] }]
|
|
5255
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"] }] } });
|
|
5256
5484
|
|
|
5257
5485
|
const EMOJI_TO_LABEL = new Map(MoodStateOptions.map((o) => [o.emoji, o.label]));
|
|
@@ -5567,169 +5795,262 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
5567
5795
|
args: ['tooltipRef']
|
|
5568
5796
|
}] } });
|
|
5569
5797
|
|
|
5570
|
-
|
|
5798
|
+
// Removed AudioState as it's replaced by AudioStatus from agent.models
|
|
5799
|
+
class MessageContentDisplayer {
|
|
5800
|
+
get hostHighlightColor() {
|
|
5801
|
+
return this.highlightColor();
|
|
5802
|
+
}
|
|
5571
5803
|
constructor() {
|
|
5572
|
-
//
|
|
5573
|
-
this.
|
|
5574
|
-
this.
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
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 */ []));
|
|
5578
5820
|
this.destroyRef = inject(DestroyRef);
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
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();
|
|
5582
5885
|
});
|
|
5583
5886
|
}
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
// Create new signal and subject for this message if they don't exist
|
|
5594
|
-
if (!this.highlightedWordsSignalMap.has(messageId)) {
|
|
5595
|
-
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);
|
|
5596
5896
|
}
|
|
5597
|
-
if (
|
|
5598
|
-
|
|
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.
|
|
5599
5912
|
}
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
//
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
// 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) {
|
|
5618
5930
|
fromEvent(audioElement, 'timeupdate')
|
|
5619
|
-
.pipe(takeUntilDestroyed(this.destroyRef),
|
|
5931
|
+
.pipe(takeUntilDestroyed(this.destroyRef), map(() => audioElement.currentTime || 0))
|
|
5620
5932
|
.subscribe((currentTime) => {
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
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) => ({
|
|
5624
5943
|
word: word.word,
|
|
5625
5944
|
index,
|
|
5626
|
-
isHighlighted,
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
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
|
+
}
|
|
5632
5951
|
});
|
|
5633
|
-
// Listen to ended event for cleanup
|
|
5634
5952
|
fromEvent(audioElement, 'ended')
|
|
5635
|
-
.pipe(takeUntilDestroyed(this.destroyRef)
|
|
5953
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
5636
5954
|
.subscribe(() => {
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
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);
|
|
5962
|
+
}
|
|
5644
5963
|
});
|
|
5645
5964
|
}
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
if (
|
|
5652
|
-
//
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
cleanup$.next();
|
|
5656
|
-
cleanup$.complete();
|
|
5657
|
-
this.cleanup$Map.delete(messageId);
|
|
5658
|
-
}
|
|
5659
|
-
this.activeAudioMap.delete(messageId);
|
|
5660
|
-
// Reset state for this message
|
|
5661
|
-
const messageSignal = this.highlightedWordsSignalMap.get(messageId);
|
|
5662
|
-
const messageSubject = this.highlightedWords$Map.get(messageId);
|
|
5663
|
-
if (messageSignal) {
|
|
5664
|
-
messageSignal.set([]);
|
|
5665
|
-
}
|
|
5666
|
-
if (messageSubject) {
|
|
5667
|
-
messageSubject.next([]);
|
|
5668
|
-
}
|
|
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 });
|
|
5669
5974
|
}
|
|
5670
5975
|
}
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
this.
|
|
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);
|
|
5678
5983
|
}
|
|
5679
|
-
// Clear all maps
|
|
5680
|
-
this.highlightedWordsSignalMap.clear();
|
|
5681
|
-
this.highlightedWords$Map.clear();
|
|
5682
|
-
this.cleanup$Map.clear();
|
|
5683
|
-
this.activeAudioMap.clear();
|
|
5684
5984
|
}
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
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
|
+
}
|
|
5693
5997
|
}
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
if (!this.highlightedWords$Map.has(messageId)) {
|
|
5703
|
-
this.highlightedWords$Map.set(messageId, new BehaviorSubject([]));
|
|
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);
|
|
5704
6006
|
}
|
|
5705
|
-
return this.highlightedWords$Map.get(messageId).asObservable();
|
|
5706
6007
|
}
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
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);
|
|
5715
6017
|
}
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
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);
|
|
5723
6026
|
}
|
|
5724
|
-
|
|
5725
|
-
|
|
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 }); }
|
|
5726
6046
|
}
|
|
5727
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type:
|
|
5728
|
-
type:
|
|
5729
|
-
args: [{
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
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"] }] } });
|
|
5733
6054
|
|
|
5734
6055
|
class DcImmersiveChatComponent {
|
|
5735
6056
|
constructor() {
|
|
@@ -6133,16 +6454,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
6133
6454
|
|
|
6134
6455
|
class MessagesStateInspectorComponent {
|
|
6135
6456
|
constructor() {
|
|
6136
|
-
this.
|
|
6137
|
-
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 */ []));
|
|
6138
6460
|
}
|
|
6139
6461
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessagesStateInspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6140
|
-
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" }] }); }
|
|
6141
6463
|
}
|
|
6142
6464
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessagesStateInspectorComponent, decorators: [{
|
|
6143
6465
|
type: Component,
|
|
6144
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" }]
|
|
6145
|
-
}] });
|
|
6467
|
+
}], propDecorators: { service: [{ type: i0.Input, args: [{ isSignal: true, alias: "service", required: false }] }] } });
|
|
6146
6468
|
|
|
6147
6469
|
class DcAgentCardDetailComponent extends EntityBaseDetailComponent {
|
|
6148
6470
|
constructor() {
|
|
@@ -6182,11 +6504,11 @@ class DcAgentCardDetailComponent extends EntityBaseDetailComponent {
|
|
|
6182
6504
|
this.selectedMotion.set(null);
|
|
6183
6505
|
}
|
|
6184
6506
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcAgentCardDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6185
|
-
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 }); }
|
|
6186
6508
|
}
|
|
6187
6509
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcAgentCardDetailComponent, decorators: [{
|
|
6188
6510
|
type: Component,
|
|
6189
|
-
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"] }]
|
|
6190
6512
|
}], ctorParameters: () => [], propDecorators: { entityData: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityData", required: false }] }] } });
|
|
6191
6513
|
|
|
6192
6514
|
class ConversationSummaryComponent {
|
|
@@ -6288,11 +6610,11 @@ class ConversationInspector {
|
|
|
6288
6610
|
this.viewMode.set(this.viewMode() === 'markdown' ? 'regular' : 'markdown');
|
|
6289
6611
|
}
|
|
6290
6612
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ConversationInspector, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6291
|
-
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" }] }); }
|
|
6292
6614
|
}
|
|
6293
6615
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ConversationInspector, decorators: [{
|
|
6294
6616
|
type: Component,
|
|
6295
|
-
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"] }]
|
|
6296
6618
|
}], ctorParameters: () => [], propDecorators: { completeEvent: [{ type: i0.Output, args: ["completeEvent"] }] } });
|
|
6297
6619
|
|
|
6298
6620
|
class ConversationInfoService {
|
|
@@ -6343,7 +6665,9 @@ class DCChatComponent {
|
|
|
6343
6665
|
// 📥 Input Signals
|
|
6344
6666
|
this.parseDict = input({}, ...(ngDevMode ? [{ debugName: "parseDict" }] : /* istanbul ignore next */ []));
|
|
6345
6667
|
// 📤 Outputs
|
|
6668
|
+
/** @deprecated Use ChatMonitorService instead — el canal oficial es el servicio compartido. */
|
|
6346
6669
|
this.chatEvent = output(); // Notifies about various events happening inside the chat
|
|
6670
|
+
/** @deprecated Use ChatMonitorService instead — el canal oficial es el servicio compartido. */
|
|
6347
6671
|
this.goalCompleted = output(); // notifies when user completes goal (score reaches 100)
|
|
6348
6672
|
// readonly wordClicked = output<WordData>(); // Replaced by sendMessage with ChatEventType.WordClicked
|
|
6349
6673
|
// Signals States
|
|
@@ -6356,6 +6680,7 @@ class DCChatComponent {
|
|
|
6356
6680
|
effect(() => {
|
|
6357
6681
|
const score = this.conversationFlowStateService.flowState().goal.value;
|
|
6358
6682
|
if (score >= 100) {
|
|
6683
|
+
// TODO: check if i can use the monitor in the father so subscribe to this.
|
|
6359
6684
|
this.goalCompleted.emit();
|
|
6360
6685
|
}
|
|
6361
6686
|
});
|
|
@@ -6369,19 +6694,6 @@ class DCChatComponent {
|
|
|
6369
6694
|
// this.conversationService.notifyWordClicked(null);
|
|
6370
6695
|
}
|
|
6371
6696
|
});
|
|
6372
|
-
// Effect to watch for audio playback events
|
|
6373
|
-
effect(() => {
|
|
6374
|
-
const message = this.chatMonitorService.messageAudioWillPlay$();
|
|
6375
|
-
if (message) {
|
|
6376
|
-
this.chatEvent.emit({ type: ChatEventType.AudioStarted, payload: message });
|
|
6377
|
-
}
|
|
6378
|
-
});
|
|
6379
|
-
// Subscribe to mood updates
|
|
6380
|
-
this.moodSubscription = this.conversationFlowStateService.moodUpdated$.subscribe((mood) => {
|
|
6381
|
-
if (mood) {
|
|
6382
|
-
this.chatEvent.emit({ type: ChatEventType.MoodDetected, payload: mood });
|
|
6383
|
-
}
|
|
6384
|
-
});
|
|
6385
6697
|
}
|
|
6386
6698
|
async ngOnInit() {
|
|
6387
6699
|
this.conversationService.setDestroyed(false);
|
|
@@ -6402,7 +6714,6 @@ class DCChatComponent {
|
|
|
6402
6714
|
// Mark conversation as destroyed to prevent async operations
|
|
6403
6715
|
this.conversationService.setDestroyed(true);
|
|
6404
6716
|
this.chatMonitorService.cleanMonitorVars();
|
|
6405
|
-
this.moodSubscription?.unsubscribe();
|
|
6406
6717
|
// Ensure the microphone is stopped when the chat component is destroyed
|
|
6407
6718
|
this.chatFooterComponent?.stopMic();
|
|
6408
6719
|
this.checkPerformanceAnalysis();
|
|
@@ -6472,7 +6783,8 @@ class DCChatComponent {
|
|
|
6472
6783
|
ConversationFlowStateService,
|
|
6473
6784
|
DynamicFlowService,
|
|
6474
6785
|
AgentCardStateService,
|
|
6475
|
-
ChatMonitorService
|
|
6786
|
+
// ChatMonitorService removido del providers[] local: ahora la instancia la provee el
|
|
6787
|
+
// componente ancestro (ConversationCardDetailsComponent) para compartirla con dc-agent-card-avatar.
|
|
6476
6788
|
ContextEngineService,
|
|
6477
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 }); }
|
|
6478
6790
|
}
|
|
@@ -6499,7 +6811,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
6499
6811
|
ConversationFlowStateService,
|
|
6500
6812
|
DynamicFlowService,
|
|
6501
6813
|
AgentCardStateService,
|
|
6502
|
-
ChatMonitorService
|
|
6814
|
+
// ChatMonitorService removido del providers[] local: ahora la instancia la provee el
|
|
6815
|
+
// componente ancestro (ConversationCardDetailsComponent) para compartirla con dc-agent-card-avatar.
|
|
6503
6816
|
ContextEngineService,
|
|
6504
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"] }]
|
|
6505
6818
|
}], ctorParameters: () => [], propDecorators: { chatFooterComponent: [{
|
|
@@ -7492,7 +7805,7 @@ class CharacterFormGroupService {
|
|
|
7492
7805
|
conversationFlow: this.createConversationFlowFormGroup(),
|
|
7493
7806
|
manageable: this.formUtils.createManageableFormGroup(),
|
|
7494
7807
|
learnable: this.formUtils.createLearnableFormGroup(),
|
|
7495
|
-
|
|
7808
|
+
voice: this.createAgentVoiceFormGroup(),
|
|
7496
7809
|
agenticConfig: this.createAgenticConfigFormGroup(),
|
|
7497
7810
|
live2d: [null],
|
|
7498
7811
|
avatarType: [null],
|
|
@@ -7500,6 +7813,22 @@ class CharacterFormGroupService {
|
|
|
7500
7813
|
}
|
|
7501
7814
|
patchFormWithConversationData(form, agentCard) {
|
|
7502
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
|
+
}
|
|
7503
7832
|
// Patch dynamicConditions FormArray after the main form is patched
|
|
7504
7833
|
const dynamicConditionsFormArray = form.get('conversationFlow.dynamicConditions');
|
|
7505
7834
|
dynamicConditionsFormArray.clear(); // Clear any existing default items
|
|
@@ -7669,8 +7998,6 @@ class CharacterFormGroupService {
|
|
|
7669
7998
|
userMustStart: [false],
|
|
7670
7999
|
streamResponses: [false],
|
|
7671
8000
|
displayMode: ['chat'],
|
|
7672
|
-
mainVoice: this.createVoiceTTSFormGroup(),
|
|
7673
|
-
secondaryVoice: this.createVoiceTTSFormGroup(),
|
|
7674
8001
|
model: createAIModelFormGroup(),
|
|
7675
8002
|
rules: this.fb.array([]),
|
|
7676
8003
|
chatMode: ['regular'],
|
|
@@ -7773,11 +8100,14 @@ class CharacterFormGroupService {
|
|
|
7773
8100
|
emoji: [criteria?.emoji || ''],
|
|
7774
8101
|
});
|
|
7775
8102
|
}
|
|
7776
|
-
|
|
8103
|
+
createAgentVoiceFormGroup(voice) {
|
|
7777
8104
|
return this.fb.group({
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
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
|
+
})
|
|
7781
8111
|
});
|
|
7782
8112
|
}
|
|
7783
8113
|
createAgenticConfigFormGroup() {
|
|
@@ -8324,47 +8654,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
8324
8654
|
type: Output
|
|
8325
8655
|
}] } });
|
|
8326
8656
|
|
|
8327
|
-
class DcVoiceTtsFormComponent {
|
|
8328
|
-
constructor() {
|
|
8329
|
-
this.dialogService = inject(DialogService);
|
|
8330
|
-
this.cdr = inject(ChangeDetectorRef);
|
|
8331
|
-
this.form = input.required(...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
|
|
8332
|
-
this.title = input('Voice TTS Settings', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
8333
|
-
this.voiceTTSOptions = input([], ...(ngDevMode ? [{ debugName: "voiceTTSOptions" }] : /* istanbul ignore next */ []));
|
|
8334
|
-
this.fullForm = input(false, ...(ngDevMode ? [{ debugName: "fullForm" }] : /* istanbul ignore next */ []));
|
|
8335
|
-
this.providerOptions = [
|
|
8336
|
-
{ label: 'Google', value: 'google' },
|
|
8337
|
-
{ label: 'Fish Audio', value: 'fish-audio' },
|
|
8338
|
-
];
|
|
8339
|
-
}
|
|
8340
|
-
openVoiceSelector() {
|
|
8341
|
-
const voiceControl = this.form().get('voice');
|
|
8342
|
-
const dialogRef = this.dialogService.open(VoiceSelectorComponent, {
|
|
8343
|
-
header: 'Select Voice',
|
|
8344
|
-
width: '50vw',
|
|
8345
|
-
maximizable: true,
|
|
8346
|
-
height: '500px',
|
|
8347
|
-
contentStyle: { 'max-height': '90vh', overflow: 'auto', 'min-height': '500px' },
|
|
8348
|
-
baseZIndex: 10000,
|
|
8349
|
-
closable: true,
|
|
8350
|
-
modal: true,
|
|
8351
|
-
data: { currentVoiceId: voiceControl?.value },
|
|
8352
|
-
});
|
|
8353
|
-
dialogRef.onClose.subscribe((selectedVoiceId) => {
|
|
8354
|
-
if (selectedVoiceId) {
|
|
8355
|
-
voiceControl?.setValue(selectedVoiceId);
|
|
8356
|
-
this.cdr.detectChanges();
|
|
8357
|
-
}
|
|
8358
|
-
});
|
|
8359
|
-
}
|
|
8360
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcVoiceTtsFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
8361
|
-
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 }] }); }
|
|
8362
|
-
}
|
|
8363
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcVoiceTtsFormComponent, decorators: [{
|
|
8364
|
-
type: Component,
|
|
8365
|
-
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" }]
|
|
8366
|
-
}], 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 }] }] } });
|
|
8367
|
-
|
|
8368
8657
|
class PromptConversationTypesDialogComponent {
|
|
8369
8658
|
constructor() {
|
|
8370
8659
|
this.dialogRef = inject(DynamicDialogRef);
|
|
@@ -8434,12 +8723,6 @@ class DcConversationSettingsFormComponent {
|
|
|
8434
8723
|
get rulesFormArray() {
|
|
8435
8724
|
return this.form.get('rules');
|
|
8436
8725
|
}
|
|
8437
|
-
get mainVoiceFormGroup() {
|
|
8438
|
-
return this.form.get('mainVoice');
|
|
8439
|
-
}
|
|
8440
|
-
get secondaryVoiceFormGroup() {
|
|
8441
|
-
return this.form.get('secondaryVoice');
|
|
8442
|
-
}
|
|
8443
8726
|
get modelFormGroup() {
|
|
8444
8727
|
return this.form.get('model');
|
|
8445
8728
|
}
|
|
@@ -8471,7 +8754,7 @@ class DcConversationSettingsFormComponent {
|
|
|
8471
8754
|
this.getRules();
|
|
8472
8755
|
}
|
|
8473
8756
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcConversationSettingsFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
8474
|
-
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"] }] }); }
|
|
8475
8758
|
}
|
|
8476
8759
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcConversationSettingsFormComponent, decorators: [{
|
|
8477
8760
|
type: Component,
|
|
@@ -8485,8 +8768,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
8485
8768
|
PopoverModule,
|
|
8486
8769
|
DynamicDialogModule,
|
|
8487
8770
|
ModelSelectorComponent,
|
|
8488
|
-
|
|
8489
|
-
], 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" }]
|
|
8490
8772
|
}], propDecorators: { form: [{
|
|
8491
8773
|
type: Input
|
|
8492
8774
|
}], textEngineOptions: [{
|
|
@@ -8497,6 +8779,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
8497
8779
|
type: Input
|
|
8498
8780
|
}] } });
|
|
8499
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
|
+
|
|
8500
8823
|
class DCAgentCardFormComponent extends EntityBaseFormComponent {
|
|
8501
8824
|
constructor() {
|
|
8502
8825
|
super(...arguments);
|
|
@@ -8535,14 +8858,14 @@ class DCAgentCardFormComponent extends EntityBaseFormComponent {
|
|
|
8535
8858
|
this.isGenerating = signal(false, ...(ngDevMode ? [{ debugName: "isGenerating" }] : /* istanbul ignore next */ []));
|
|
8536
8859
|
this.isCloningVoice = signal(false, ...(ngDevMode ? [{ debugName: "isCloningVoice" }] : /* istanbul ignore next */ []));
|
|
8537
8860
|
this.canCloneVoice = computed(() => {
|
|
8538
|
-
const sampleUrl = this.form.get('
|
|
8539
|
-
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;
|
|
8540
8863
|
const canCloneVoiceVar = !!sampleUrl && !voiceId;
|
|
8541
8864
|
return canCloneVoiceVar;
|
|
8542
8865
|
}, ...(ngDevMode ? [{ debugName: "canCloneVoice" }] : /* istanbul ignore next */ []));
|
|
8543
8866
|
this.fishModelId = computed(() => {
|
|
8544
|
-
const voiceId = this.form.get('
|
|
8545
|
-
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;
|
|
8546
8869
|
return voiceId && provider === 'fish-audio' ? voiceId : null;
|
|
8547
8870
|
}, ...(ngDevMode ? [{ debugName: "fishModelId" }] : /* istanbul ignore next */ []));
|
|
8548
8871
|
}
|
|
@@ -8765,15 +9088,15 @@ class DCAgentCardFormComponent extends EntityBaseFormComponent {
|
|
|
8765
9088
|
}
|
|
8766
9089
|
onFileUploaded(event) {
|
|
8767
9090
|
console.log('File uploaded', event);
|
|
8768
|
-
this.form.
|
|
9091
|
+
this.form.get('voice.cloning')?.patchValue({ sample: event });
|
|
8769
9092
|
}
|
|
8770
9093
|
async cloneVoice() {
|
|
8771
|
-
const sample = this.form.get('
|
|
9094
|
+
const sample = this.form.get('voice.cloning.sample')?.value;
|
|
8772
9095
|
if (!sample?.url) {
|
|
8773
9096
|
this.toastService?.error({ title: 'Falta el sample', subtitle: 'Sube un audio antes de crear el modelo' });
|
|
8774
9097
|
return;
|
|
8775
9098
|
}
|
|
8776
|
-
const text = this.form.get('
|
|
9099
|
+
const text = this.form.get('voice.cloning.transcription')?.value || '';
|
|
8777
9100
|
const agentName = this.form.get('name')?.value || 'Polilan';
|
|
8778
9101
|
const title = `${agentName} — voice`;
|
|
8779
9102
|
this.isCloningVoice.set(true);
|
|
@@ -8784,9 +9107,9 @@ class DCAgentCardFormComponent extends EntityBaseFormComponent {
|
|
|
8784
9107
|
text,
|
|
8785
9108
|
trainMode: 'fast',
|
|
8786
9109
|
});
|
|
8787
|
-
const
|
|
8788
|
-
|
|
8789
|
-
|
|
9110
|
+
const newMainCtrl = this.form.get('voice.main');
|
|
9111
|
+
newMainCtrl?.patchValue({ voice: res._id, provider: 'fish-audio' });
|
|
9112
|
+
newMainCtrl?.markAsDirty();
|
|
8790
9113
|
this.toastService?.success({ title: 'Modelo de voz creado', subtitle: res._id });
|
|
8791
9114
|
}
|
|
8792
9115
|
catch (e) {
|
|
@@ -8807,7 +9130,7 @@ class DCAgentCardFormComponent extends EntityBaseFormComponent {
|
|
|
8807
9130
|
this.router.navigate(['../../cards-creator'], { relativeTo: this.route });
|
|
8808
9131
|
}
|
|
8809
9132
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DCAgentCardFormComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
8810
|
-
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"] }] }); }
|
|
8811
9134
|
}
|
|
8812
9135
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DCAgentCardFormComponent, decorators: [{
|
|
8813
9136
|
type: Component,
|
|
@@ -8835,7 +9158,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
8835
9158
|
SimpleUploaderComponent,
|
|
8836
9159
|
TextareaModule,
|
|
8837
9160
|
ModelSelectorComponent,
|
|
8838
|
-
], 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"] }]
|
|
8839
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"] }] } });
|
|
8840
9163
|
|
|
8841
9164
|
class TruncatePipe {
|
|
@@ -9010,7 +9333,7 @@ class AgentCardListComponent extends EntityBaseListV2Component {
|
|
|
9010
9333
|
const adminFilters = [
|
|
9011
9334
|
{
|
|
9012
9335
|
name: 'Voz Única',
|
|
9013
|
-
field: '
|
|
9336
|
+
field: 'voice.cloning.sample',
|
|
9014
9337
|
type: 'select',
|
|
9015
9338
|
operator: '$not_null',
|
|
9016
9339
|
options: [
|
|
@@ -9116,6 +9439,7 @@ class Live2dModelComponent {
|
|
|
9116
9439
|
// Outputs (using new Angular output API)
|
|
9117
9440
|
this.modelLoaded = output();
|
|
9118
9441
|
this.viewStateChanged = output();
|
|
9442
|
+
this.live2dConfig = inject(LIVE2D_CONFIG, { optional: true });
|
|
9119
9443
|
this.modelParameters = null;
|
|
9120
9444
|
this.modelParts = null;
|
|
9121
9445
|
this.motionGroups = [];
|
|
@@ -9156,7 +9480,12 @@ class Live2dModelComponent {
|
|
|
9156
9480
|
this.isViewInitialized.set(true);
|
|
9157
9481
|
}
|
|
9158
9482
|
async initializeCanvas() {
|
|
9159
|
-
this.
|
|
9483
|
+
if (!this.live2dConfig) {
|
|
9484
|
+
console.warn('Live2D configuration not provided. Please call provideLive2D in your app bootstrap.');
|
|
9485
|
+
return;
|
|
9486
|
+
}
|
|
9487
|
+
const PIXI = await this.live2dConfig.PIXI();
|
|
9488
|
+
this.app = new PIXI.Application();
|
|
9160
9489
|
if (typeof this.app.init === 'function') {
|
|
9161
9490
|
await this.app.init({
|
|
9162
9491
|
canvas: this.canvasRef.nativeElement,
|
|
@@ -9173,6 +9502,13 @@ class Live2dModelComponent {
|
|
|
9173
9502
|
}
|
|
9174
9503
|
}
|
|
9175
9504
|
async loadModel(modelPath) {
|
|
9505
|
+
if (!this.live2dConfig) {
|
|
9506
|
+
console.warn('Live2D configuration not provided. Please call provideLive2D in your app bootstrap.');
|
|
9507
|
+
return;
|
|
9508
|
+
}
|
|
9509
|
+
const PIXI = await this.live2dConfig.PIXI();
|
|
9510
|
+
const live2dEngine = await this.live2dConfig.Live2DModel();
|
|
9511
|
+
const Live2DModel = live2dEngine.Live2DModel;
|
|
9176
9512
|
if (this.model) {
|
|
9177
9513
|
this.app.stage.removeChild(this.model);
|
|
9178
9514
|
this.model.destroy();
|
|
@@ -9254,7 +9590,7 @@ class Live2dModelComponent {
|
|
|
9254
9590
|
this.playAnimation(group);
|
|
9255
9591
|
}
|
|
9256
9592
|
}
|
|
9257
|
-
speak(audioUrl, options) {
|
|
9593
|
+
async speak(audioUrl, options) {
|
|
9258
9594
|
if (!this.model)
|
|
9259
9595
|
return;
|
|
9260
9596
|
if (options?.focus !== false) {
|
|
@@ -9263,8 +9599,21 @@ class Live2dModelComponent {
|
|
|
9263
9599
|
this.viewStateChanged.emit(transform);
|
|
9264
9600
|
}
|
|
9265
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
|
+
}
|
|
9266
9615
|
this.model.speak(audioUrl, {
|
|
9267
|
-
volume:
|
|
9616
|
+
volume: targetVolume,
|
|
9268
9617
|
crossOrigin: options?.crossOrigin || 'anonymous',
|
|
9269
9618
|
});
|
|
9270
9619
|
}
|
|
@@ -9423,7 +9772,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
9423
9772
|
args: ['canvas']
|
|
9424
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"] }] } });
|
|
9425
9774
|
|
|
9426
|
-
class
|
|
9775
|
+
class DcAgentCardAvatarComponent {
|
|
9427
9776
|
set isConversationActive(val) {
|
|
9428
9777
|
this._isConversationActive.set(val);
|
|
9429
9778
|
}
|
|
@@ -9445,8 +9794,8 @@ class DcAgentCardConverseComponent {
|
|
|
9445
9794
|
this.videoPlayerService = inject(VideoPlayerNativeService);
|
|
9446
9795
|
this.agentCardId = '';
|
|
9447
9796
|
this.useNativePlayer = true;
|
|
9448
|
-
this.activeAudioMessage = null;
|
|
9449
9797
|
this._isConversationActive = signal(false, ...(ngDevMode ? [{ debugName: "_isConversationActive" }] : /* istanbul ignore next */ []));
|
|
9798
|
+
/** @deprecated Use ChatMonitorService instead — el canal oficial es el servicio compartido. */
|
|
9450
9799
|
this.onStartConversation = output();
|
|
9451
9800
|
this.videoPlayerA = viewChild('videoPlayerA', ...(ngDevMode ? [{ debugName: "videoPlayerA" }] : /* istanbul ignore next */ []));
|
|
9452
9801
|
this.videoPlayerB = viewChild('videoPlayerB', ...(ngDevMode ? [{ debugName: "videoPlayerB" }] : /* istanbul ignore next */ []));
|
|
@@ -9527,25 +9876,48 @@ class DcAgentCardConverseComponent {
|
|
|
9527
9876
|
this.videoPlayerService.setAgentCard(card);
|
|
9528
9877
|
}
|
|
9529
9878
|
});
|
|
9530
|
-
// Live2D Speak (Lip-Sync) effect
|
|
9879
|
+
// Live2D Speak (Lip-Sync) effect — listening directly to ChatMonitorService's activeAudioMessage
|
|
9531
9880
|
effect(() => {
|
|
9532
|
-
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);
|
|
9533
9886
|
const type = this.avatarType();
|
|
9534
9887
|
const l2d = this.live2dModel();
|
|
9535
|
-
if (type === 'live2d' &&
|
|
9536
|
-
|
|
9537
|
-
|
|
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
|
+
}
|
|
9538
9898
|
}
|
|
9539
9899
|
});
|
|
9540
9900
|
// Live2D Mood state emotion updates mapping
|
|
9541
9901
|
effect(() => {
|
|
9542
|
-
const mood = this.
|
|
9902
|
+
const mood = this.chatMonitorService.currentMood();
|
|
9543
9903
|
const type = this.avatarType();
|
|
9544
9904
|
const l2d = this.live2dModel();
|
|
9545
9905
|
if (type === 'live2d' && mood && l2d) {
|
|
9546
9906
|
l2d.setExpression(mood);
|
|
9547
9907
|
}
|
|
9548
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
|
+
});
|
|
9549
9921
|
}
|
|
9550
9922
|
ngOnDestroy() {
|
|
9551
9923
|
this.videoPlayerService.cleanUp();
|
|
@@ -9609,6 +9981,18 @@ class DcAgentCardConverseComponent {
|
|
|
9609
9981
|
cardTranslated.conversationSettings.secondaryVoice.voice = transformedSecondaryVoice;
|
|
9610
9982
|
}
|
|
9611
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
|
+
}
|
|
9612
9996
|
return cardTranslated;
|
|
9613
9997
|
}
|
|
9614
9998
|
_transformVoice(voice, locale) {
|
|
@@ -9639,18 +10023,16 @@ class DcAgentCardConverseComponent {
|
|
|
9639
10023
|
toggleInfoLayer() {
|
|
9640
10024
|
this.showInfoLayer.update((value) => !value);
|
|
9641
10025
|
}
|
|
9642
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type:
|
|
9643
|
-
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" }] }); }
|
|
9644
10028
|
}
|
|
9645
|
-
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: [{
|
|
9646
10030
|
type: Component,
|
|
9647
|
-
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"] }]
|
|
9648
10032
|
}], ctorParameters: () => [], propDecorators: { agentCardId: [{
|
|
9649
10033
|
type: Input
|
|
9650
10034
|
}], useNativePlayer: [{
|
|
9651
10035
|
type: Input
|
|
9652
|
-
}], activeAudioMessage: [{
|
|
9653
|
-
type: Input
|
|
9654
10036
|
}], isConversationActive: [{
|
|
9655
10037
|
type: Input
|
|
9656
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 }] }] } });
|
|
@@ -10272,5 +10654,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
10272
10654
|
* Generated bundle index. Do not edit.
|
|
10273
10655
|
*/
|
|
10274
10656
|
|
|
10275
|
-
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 };
|
|
10276
10658
|
//# sourceMappingURL=dataclouder-ngx-agent-cards.mjs.map
|