@dataclouder/ngx-agent-cards 0.2.14 → 0.2.16
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';
|
|
@@ -20,11 +20,11 @@ import * as i5 from 'primeng/tooltip';
|
|
|
20
20
|
import { TooltipModule } from 'primeng/tooltip';
|
|
21
21
|
import * as i1$2 from '@angular/forms';
|
|
22
22
|
import { FormControl, ReactiveFormsModule, FormBuilder, FormsModule, FormArray, FormGroup, Validators } from '@angular/forms';
|
|
23
|
-
import { DCMicComponent } from '@dataclouder/ngx-mic';
|
|
23
|
+
import { DCMicComponent, MicRecordingService } 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';
|
|
@@ -227,12 +227,6 @@ var AgenticPattern;
|
|
|
227
227
|
AgenticPattern["REFLECTION"] = "reflection";
|
|
228
228
|
AgenticPattern["PLAN_AND_EXECUTE"] = "plan_execute"; // Planificar sub-tareas -> Ejecutar
|
|
229
229
|
})(AgenticPattern || (AgenticPattern = {}));
|
|
230
|
-
// Creo que esta @Deprecated porque feedback ahora es text.
|
|
231
|
-
const EvalResultStringDefinition = `
|
|
232
|
-
interface SimpleEvalResult {
|
|
233
|
-
score: number; // Score of the user's response 0 to 3
|
|
234
|
-
feedback: string; // Feedback of the user's understanding of the conversation
|
|
235
|
-
}`;
|
|
236
230
|
var EAccountsPlatform;
|
|
237
231
|
(function (EAccountsPlatform) {
|
|
238
232
|
EAccountsPlatform["Google"] = "google";
|
|
@@ -283,6 +277,7 @@ var ChatEventType;
|
|
|
283
277
|
ChatEventType["WordClicked"] = "wordClicked";
|
|
284
278
|
ChatEventType["MoodDetected"] = "moodDetected";
|
|
285
279
|
ChatEventType["AudioStarted"] = "audioStarted";
|
|
280
|
+
ChatEventType["AudioStopped"] = "audioStopped";
|
|
286
281
|
// Add other potential event types here as needed
|
|
287
282
|
// e.g., MessageSent = 'messageSent', SettingsChanged = 'settingsChanged', GoalCompleted = 'goalCompleted'
|
|
288
283
|
})(ChatEventType || (ChatEventType = {}));
|
|
@@ -317,6 +312,30 @@ function provideAgentCardService(serviceImplementation) {
|
|
|
317
312
|
];
|
|
318
313
|
}
|
|
319
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Traduce la preferencia de alto nivel `micMode` (de ChatUserSettings) a la
|
|
317
|
+
* configuración técnica de `MicSettings` que consume el componente de micrófono.
|
|
318
|
+
*
|
|
319
|
+
* - 'hands-free' → grabación por detección de silencio, infinita (envía y sigue escuchando).
|
|
320
|
+
* - 'push-to-talk' → grabación por detección de silencio, finita (envía y se detiene).
|
|
321
|
+
* - 'manual' → grabación continua: el silencio NO envía; el usuario presiona para enviar
|
|
322
|
+
* (máx. `maxRecordingTime`, con contador y barra). Para usuarios que pausan mucho.
|
|
323
|
+
*/
|
|
324
|
+
function buildMicSettings(micMode, lang) {
|
|
325
|
+
if (micMode === 'manual') {
|
|
326
|
+
// recordMode 'manual' activa startContinuousRecording en el componente (el silencio no envía).
|
|
327
|
+
return { micMode: 'recording', recordMode: 'manual', lang };
|
|
328
|
+
}
|
|
329
|
+
const isPushToTalk = micMode === 'push-to-talk';
|
|
330
|
+
return {
|
|
331
|
+
micMode: 'recording',
|
|
332
|
+
recordMode: 'silence',
|
|
333
|
+
infiniteSilence: !isPushToTalk,
|
|
334
|
+
// Tras enviar, bloquea el mic 4.5s para que el usuario perciba que la IA ya está respondiendo.
|
|
335
|
+
postSendCooldownMs: 4500,
|
|
336
|
+
lang,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
320
339
|
// TODO: use the default settings when the user is not set
|
|
321
340
|
const defaultconvUserSettings = {
|
|
322
341
|
realTime: false,
|
|
@@ -334,6 +353,7 @@ const defaultconvUserSettings = {
|
|
|
334
353
|
assistantMessageTask: true,
|
|
335
354
|
saveConversations: false,
|
|
336
355
|
multilingualHearing: false,
|
|
356
|
+
micMode: 'hands-free',
|
|
337
357
|
};
|
|
338
358
|
class VoiceTTSOption {
|
|
339
359
|
}
|
|
@@ -1865,6 +1885,57 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
1865
1885
|
}]
|
|
1866
1886
|
}] });
|
|
1867
1887
|
|
|
1888
|
+
class ChatMonitorService {
|
|
1889
|
+
// Actualmente la única comunicación es con el background para cambiar el fondo.
|
|
1890
|
+
// El problema de comunicar así es si tiene que ser singleton, y en control markets tengo varias instancias de dc-chat.
|
|
1891
|
+
// Voy a utilizar el evento por ahora, y ver si combiene luego crear un servicio.
|
|
1892
|
+
// Se requiere comunicar el chat-engine con la app afuera, va a ver 2 formas de hacerlo.
|
|
1893
|
+
// Mediante eventos del chat, o estar subscrito al chat-monitor.
|
|
1894
|
+
// La complejidad del chat monitor es menor porque puedo injectarlo en cualquier lugar.
|
|
1895
|
+
// Los eventos estan bien pero si el vento viene muy interno tengo que propagarlo bastante.
|
|
1896
|
+
constructor() {
|
|
1897
|
+
this.activeAudioMessage = signal(null, ...(ngDevMode ? [{ debugName: "activeAudioMessage" }] : /* istanbul ignore next */ []));
|
|
1898
|
+
this.currentMood = signal(null, ...(ngDevMode ? [{ debugName: "currentMood" }] : /* istanbul ignore next */ []));
|
|
1899
|
+
this.isConversationActive = signal(false, ...(ngDevMode ? [{ debugName: "isConversationActive" }] : /* istanbul ignore next */ []));
|
|
1900
|
+
// Background
|
|
1901
|
+
this.backgroundUrl = signal(undefined, ...(ngDevMode ? [{ debugName: "backgroundUrl" }] : /* istanbul ignore next */ []));
|
|
1902
|
+
// Audio pipeline signals — source of truth for Live2D lip-sync bridge
|
|
1903
|
+
this.messageAudioWillPlay$ = signal(null, ...(ngDevMode ? [{ debugName: "messageAudioWillPlay$" }] : /* istanbul ignore next */ []));
|
|
1904
|
+
this.messageAudioStatus$ = signal(null, ...(ngDevMode ? [{ debugName: "messageAudioStatus$" }] : /* istanbul ignore next */ []));
|
|
1905
|
+
console.log(`%c ChatMonitorService instantiated: ${Math.random()}`, 'color: #00ff00; font-weight: bold; padding: 2px 4px; border: 1px solid #00ff00;');
|
|
1906
|
+
}
|
|
1907
|
+
logCurrentMood(mood) {
|
|
1908
|
+
this.currentMood.set(mood);
|
|
1909
|
+
}
|
|
1910
|
+
cleanMonitorVars() {
|
|
1911
|
+
this.currentMood.set(null);
|
|
1912
|
+
this.activeAudioMessage.set(null);
|
|
1913
|
+
this.isConversationActive.set(false);
|
|
1914
|
+
}
|
|
1915
|
+
setBackground(imageUrl) {
|
|
1916
|
+
if (imageUrl) {
|
|
1917
|
+
this.backgroundUrl.set(imageUrl);
|
|
1918
|
+
}
|
|
1919
|
+
else {
|
|
1920
|
+
this.backgroundUrl.set(undefined);
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
logMessageAudioWillPlay(message) {
|
|
1924
|
+
this.messageAudioWillPlay$.set(message);
|
|
1925
|
+
}
|
|
1926
|
+
logMessageAudioStatus(message, status) {
|
|
1927
|
+
this.messageAudioStatus$.set({ message, status });
|
|
1928
|
+
}
|
|
1929
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMonitorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1930
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMonitorService, providedIn: 'root' }); }
|
|
1931
|
+
}
|
|
1932
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMonitorService, decorators: [{
|
|
1933
|
+
type: Injectable,
|
|
1934
|
+
args: [{
|
|
1935
|
+
providedIn: 'root',
|
|
1936
|
+
}]
|
|
1937
|
+
}], ctorParameters: () => [] });
|
|
1938
|
+
|
|
1868
1939
|
/**
|
|
1869
1940
|
* @description
|
|
1870
1941
|
* Service responsible for managing the real-time, dynamic state of a conversation flow.
|
|
@@ -1879,6 +1950,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
1879
1950
|
*/
|
|
1880
1951
|
class ConversationFlowStateService {
|
|
1881
1952
|
constructor() {
|
|
1953
|
+
this.chatMonitorService = inject(ChatMonitorService, { optional: true });
|
|
1882
1954
|
this.initialState = {
|
|
1883
1955
|
goal: { value: 0 },
|
|
1884
1956
|
challenges: [],
|
|
@@ -1944,6 +2016,7 @@ class ConversationFlowStateService {
|
|
|
1944
2016
|
}
|
|
1945
2017
|
if (newState.moodState?.value) {
|
|
1946
2018
|
this.moodUpdated.next(newState.moodState.value);
|
|
2019
|
+
this.chatMonitorService?.logCurrentMood(newState.moodState.value);
|
|
1947
2020
|
}
|
|
1948
2021
|
if (newState.goal && newState.goal.deltaPoints !== undefined && newState.goal.deltaPoints !== 0) {
|
|
1949
2022
|
this.goalEvaluationUpdated.next({ deltaPoints: newState.goal.deltaPoints });
|
|
@@ -2004,6 +2077,7 @@ class ConversationFlowStateService {
|
|
|
2004
2077
|
}));
|
|
2005
2078
|
if (moodState.value) {
|
|
2006
2079
|
this.moodUpdated.next(moodState.value);
|
|
2080
|
+
this.chatMonitorService?.logCurrentMood(moodState.value);
|
|
2007
2081
|
}
|
|
2008
2082
|
}
|
|
2009
2083
|
/**
|
|
@@ -2505,58 +2579,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
2505
2579
|
}]
|
|
2506
2580
|
}] });
|
|
2507
2581
|
|
|
2508
|
-
class ChatMonitorService {
|
|
2509
|
-
// Actualmente la única comunicación es con el background para cambiar el fondo.
|
|
2510
|
-
// El problema de comunicar así es si tiene que ser singleton, y en control markets tengo varias instancias de dc-chat.
|
|
2511
|
-
// Voy a utilizar el evento por ahora, y ver si combiene luego crear un servicio.
|
|
2512
|
-
// Se requiere comunicar el chat-engine con la app afuera, va a ver 2 formas de hacerlo.
|
|
2513
|
-
// Mediante eventos del chat, o estar subscrito al chat-monitor.
|
|
2514
|
-
// La complejidad del chat monitor es menor porque puedo injectarlo en cualquier lugar.
|
|
2515
|
-
// Los eventos estan bien pero si el vento viene muy interno tengo que propagarlo bastante.
|
|
2516
|
-
constructor() {
|
|
2517
|
-
this.messageAudioWillPlay = signal(null, ...(ngDevMode ? [{ debugName: "messageAudioWillPlay" }] : /* istanbul ignore next */ []));
|
|
2518
|
-
this.messageAudioWillPlay$ = this.messageAudioWillPlay.asReadonly();
|
|
2519
|
-
this.currentMood = signal(null, ...(ngDevMode ? [{ debugName: "currentMood" }] : /* istanbul ignore next */ []));
|
|
2520
|
-
this.isConversationActive = signal(false, ...(ngDevMode ? [{ debugName: "isConversationActive" }] : /* istanbul ignore next */ []));
|
|
2521
|
-
// Background
|
|
2522
|
-
this.backgroundUrl = signal(undefined, ...(ngDevMode ? [{ debugName: "backgroundUrl" }] : /* istanbul ignore next */ []));
|
|
2523
|
-
console.log(`%c ChatMonitorService instantiated: ${Math.random()}`, 'color: #00ff00; font-weight: bold; padding: 2px 4px; border: 1px solid #00ff00;');
|
|
2524
|
-
}
|
|
2525
|
-
logMessageAudioWillPlay(message) {
|
|
2526
|
-
// console.log('Audio will play....', message);
|
|
2527
|
-
this.messageAudioWillPlay.set(message);
|
|
2528
|
-
}
|
|
2529
|
-
logCurrentMood(mood) {
|
|
2530
|
-
this.currentMood.set(mood);
|
|
2531
|
-
}
|
|
2532
|
-
cleanMonitorVars() {
|
|
2533
|
-
this.currentMood.set(null);
|
|
2534
|
-
this.messageAudioWillPlay.set(null);
|
|
2535
|
-
this.isConversationActive.set(false);
|
|
2536
|
-
}
|
|
2537
|
-
setBackground(imageUrl) {
|
|
2538
|
-
if (imageUrl) {
|
|
2539
|
-
this.backgroundUrl.set(imageUrl);
|
|
2540
|
-
}
|
|
2541
|
-
else {
|
|
2542
|
-
this.backgroundUrl.set(undefined);
|
|
2543
|
-
}
|
|
2544
|
-
}
|
|
2545
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMonitorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2546
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMonitorService, providedIn: 'root' }); }
|
|
2547
|
-
}
|
|
2548
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMonitorService, decorators: [{
|
|
2549
|
-
type: Injectable,
|
|
2550
|
-
args: [{
|
|
2551
|
-
providedIn: 'root',
|
|
2552
|
-
}]
|
|
2553
|
-
}], ctorParameters: () => [] });
|
|
2554
|
-
|
|
2555
2582
|
class MessageProcessingService {
|
|
2556
2583
|
constructor() {
|
|
2557
2584
|
this.MAX_CHUNK_LENGTH = 190;
|
|
2558
2585
|
}
|
|
2559
|
-
processMessage(message, conversationSettings) {
|
|
2586
|
+
processMessage(message, conversationSettings, voiceSettings) {
|
|
2560
2587
|
const processedMessage = {
|
|
2561
2588
|
...message,
|
|
2562
2589
|
messageId: message.messageId || nanoid(),
|
|
@@ -2568,25 +2595,26 @@ class MessageProcessingService {
|
|
|
2568
2595
|
return processedMessage;
|
|
2569
2596
|
}
|
|
2570
2597
|
if (processedMessage.role === ChatRole.Assistant) {
|
|
2571
|
-
return this.processAssistantMessage(processedMessage, conversationSettings);
|
|
2598
|
+
return this.processAssistantMessage(processedMessage, conversationSettings, voiceSettings);
|
|
2572
2599
|
}
|
|
2573
2600
|
return processedMessage;
|
|
2574
2601
|
}
|
|
2575
|
-
processAssistantMessage(message, settings) {
|
|
2602
|
+
processAssistantMessage(message, settings, voiceSettings) {
|
|
2576
2603
|
if (settings.avatarImages.assistant) {
|
|
2577
2604
|
message.imgUrl = settings.avatarImages.assistant;
|
|
2578
2605
|
}
|
|
2579
|
-
const mainVoice =
|
|
2580
|
-
message.voice =
|
|
2606
|
+
const mainVoice = this.resolveVoiceTTS('main', voiceSettings);
|
|
2607
|
+
message.voice = mainVoice.voice;
|
|
2608
|
+
message.provider = mainVoice.provider;
|
|
2581
2609
|
switch (settings.textEngine) {
|
|
2582
2610
|
case TextEngines.SimpleText:
|
|
2583
2611
|
this.processSimpleText(message, settings);
|
|
2584
2612
|
break;
|
|
2585
2613
|
case TextEngines.MarkdownMultiMessages:
|
|
2586
|
-
this.processMultiMessages(message, settings);
|
|
2614
|
+
this.processMultiMessages(message, settings, voiceSettings);
|
|
2587
2615
|
break;
|
|
2588
2616
|
case TextEngines.MarkdownSSML:
|
|
2589
|
-
this.processSSML(message, settings);
|
|
2617
|
+
this.processSSML(message, settings, voiceSettings);
|
|
2590
2618
|
break;
|
|
2591
2619
|
}
|
|
2592
2620
|
return message;
|
|
@@ -2596,6 +2624,7 @@ class MessageProcessingService {
|
|
|
2596
2624
|
message.multiMessages = chunks.map((chunk) => {
|
|
2597
2625
|
return {
|
|
2598
2626
|
voice: message.voice,
|
|
2627
|
+
provider: message.provider,
|
|
2599
2628
|
content: chunk, // Simple text usually doesn't need HTML wrapping, or <p> if UI expects it.
|
|
2600
2629
|
text: chunk,
|
|
2601
2630
|
audioUrl: null,
|
|
@@ -2605,19 +2634,21 @@ class MessageProcessingService {
|
|
|
2605
2634
|
};
|
|
2606
2635
|
});
|
|
2607
2636
|
}
|
|
2608
|
-
processMultiMessages(message, settings) {
|
|
2637
|
+
processMultiMessages(message, settings, voiceSettings) {
|
|
2609
2638
|
const htmlSegments = convertToHTML(message.content);
|
|
2610
|
-
const secondaryVoice =
|
|
2639
|
+
const secondaryVoice = this.resolveVoiceTTS('secondary', voiceSettings);
|
|
2611
2640
|
const processedSegments = [];
|
|
2612
2641
|
for (const segment of htmlSegments) {
|
|
2613
2642
|
const isItalics = segment.tag === 'em';
|
|
2614
|
-
const voice = isItalics ? secondaryVoice : message.voice;
|
|
2643
|
+
const voice = isItalics ? secondaryVoice.voice : message.voice;
|
|
2644
|
+
const provider = isItalics ? secondaryVoice.provider : message.provider;
|
|
2615
2645
|
if (segment.text.length > this.MAX_CHUNK_LENGTH) {
|
|
2616
2646
|
// Split long segments while preserving their type
|
|
2617
2647
|
const chunks = this.splitContent(segment.text, this.MAX_CHUNK_LENGTH);
|
|
2618
2648
|
chunks.forEach((chunk) => {
|
|
2619
2649
|
processedSegments.push({
|
|
2620
2650
|
voice,
|
|
2651
|
+
provider,
|
|
2621
2652
|
content: this.wrapContentWithTag(chunk, segment.tag),
|
|
2622
2653
|
text: chunk,
|
|
2623
2654
|
audioUrl: null,
|
|
@@ -2631,6 +2662,7 @@ class MessageProcessingService {
|
|
|
2631
2662
|
// Keep short segments as is
|
|
2632
2663
|
processedSegments.push({
|
|
2633
2664
|
voice,
|
|
2665
|
+
provider,
|
|
2634
2666
|
content: segment.content,
|
|
2635
2667
|
text: segment.text,
|
|
2636
2668
|
audioUrl: null,
|
|
@@ -2656,13 +2688,23 @@ class MessageProcessingService {
|
|
|
2656
2688
|
return `<p>${text}</p>`;
|
|
2657
2689
|
}
|
|
2658
2690
|
}
|
|
2659
|
-
processSSML(message, settings) {
|
|
2660
|
-
|
|
2691
|
+
processSSML(message, settings, voiceSettings) {
|
|
2692
|
+
const secondaryVoice = this.resolveVoiceTTS('secondary', voiceSettings);
|
|
2693
|
+
if (!secondaryVoice.voice) {
|
|
2661
2694
|
throw new Error('Secondary voice is required for SSML');
|
|
2662
2695
|
}
|
|
2663
|
-
const content = this.subsItalicsByTag(message.content,
|
|
2696
|
+
const content = this.subsItalicsByTag(message.content, secondaryVoice.voice);
|
|
2664
2697
|
message.ssml = `<speak>${content}</speak>`;
|
|
2665
2698
|
}
|
|
2699
|
+
resolveVoiceTTS(role, voiceSettings) {
|
|
2700
|
+
const isMain = role === 'main';
|
|
2701
|
+
const unifiedVoice = isMain ? voiceSettings?.main : voiceSettings?.secondary;
|
|
2702
|
+
const fallbackVoice = isMain ? '' : 'en-US-News-L';
|
|
2703
|
+
return {
|
|
2704
|
+
voice: this.getVoice(unifiedVoice?.voice || fallbackVoice),
|
|
2705
|
+
provider: unifiedVoice?.provider || 'google',
|
|
2706
|
+
};
|
|
2707
|
+
}
|
|
2666
2708
|
splitContent(content, maxLength) {
|
|
2667
2709
|
const chunks = [];
|
|
2668
2710
|
let currentPos = 0;
|
|
@@ -2778,6 +2820,7 @@ function buildObjectTTSRequest(message, settings = {}) {
|
|
|
2778
2820
|
return {
|
|
2779
2821
|
text,
|
|
2780
2822
|
voice: message.voice || settings.voice,
|
|
2823
|
+
provider: message.provider || settings.provider || 'google',
|
|
2781
2824
|
generateTranscription,
|
|
2782
2825
|
speedRate,
|
|
2783
2826
|
speed,
|
|
@@ -3469,7 +3512,7 @@ class ConversationService {
|
|
|
3469
3512
|
}
|
|
3470
3513
|
createNewUserMessage() {
|
|
3471
3514
|
const message = { content: '...', role: ChatRole.User };
|
|
3472
|
-
const processedMessage = this.messageProcessingService.processMessage(message, this.conversationSettingsState());
|
|
3515
|
+
const processedMessage = this.messageProcessingService.processMessage(message, this.conversationSettingsState(), this.agentCardState.agentCard$()?.voice);
|
|
3473
3516
|
this.messagesStateService.addMessage(processedMessage);
|
|
3474
3517
|
return processedMessage.messageId;
|
|
3475
3518
|
}
|
|
@@ -3576,7 +3619,7 @@ class ConversationService {
|
|
|
3576
3619
|
const firstAssistantMsg = conversationSettings.messages.find((message) => message.role === ChatRole.Assistant);
|
|
3577
3620
|
if (firstAssistantMsg) {
|
|
3578
3621
|
// Process the first assistant message
|
|
3579
|
-
const processedMessage = this.messageProcessingService.processMessage(firstAssistantMsg, this.conversationSettingsState());
|
|
3622
|
+
const processedMessage = this.messageProcessingService.processMessage(firstAssistantMsg, this.conversationSettingsState(), this.agentCardState.agentCard$()?.voice);
|
|
3580
3623
|
this.messagesStateService.updateMessage(firstAssistantMsg.messageId, processedMessage);
|
|
3581
3624
|
// Find the index of the message with the matching ID
|
|
3582
3625
|
const messageIndex = conversationSettings.messages.findIndex((message) => message.messageId === firstAssistantMsg.messageId);
|
|
@@ -3625,7 +3668,7 @@ class ConversationService {
|
|
|
3625
3668
|
}
|
|
3626
3669
|
else {
|
|
3627
3670
|
// Means is new meessage, Process and add the new message
|
|
3628
|
-
const processedMessage = this.messageProcessingService.processMessage(message, this.conversationSettingsState());
|
|
3671
|
+
const processedMessage = this.messageProcessingService.processMessage(message, this.conversationSettingsState(), this.agentCardState.agentCard$()?.voice);
|
|
3629
3672
|
// Ensure ID exists (processMessage should handle this, but fallback just in case)
|
|
3630
3673
|
processedMessage.messageId = processedMessage.messageId || nanoid();
|
|
3631
3674
|
this.messagesStateService.addMessage(processedMessage);
|
|
@@ -3696,7 +3739,7 @@ class ConversationService {
|
|
|
3696
3739
|
throw new Error('No message returned from AI');
|
|
3697
3740
|
}
|
|
3698
3741
|
// Process response
|
|
3699
|
-
const newMessage = this.messageProcessingService.processMessage({ content: response.content, role: ChatRole.Assistant }, conversationSettings);
|
|
3742
|
+
const newMessage = this.messageProcessingService.processMessage({ content: response.content, role: ChatRole.Assistant }, conversationSettings, this.agentCardState.agentCard$()?.voice);
|
|
3700
3743
|
this.messagesStateService.addMessage(newMessage);
|
|
3701
3744
|
this.isThinkingSignal.set(false);
|
|
3702
3745
|
// Run Dynamic Flow Evaluations
|
|
@@ -3705,9 +3748,10 @@ class ConversationService {
|
|
|
3705
3748
|
}
|
|
3706
3749
|
async handleStreamingResponse(conversation, conversationSettings) {
|
|
3707
3750
|
const messageId = nanoid();
|
|
3708
|
-
const
|
|
3709
|
-
const
|
|
3710
|
-
const
|
|
3751
|
+
const agentCard = this.agentCardState.agentCard$();
|
|
3752
|
+
const mainVoice = agentCard?.voice?.main?.voice;
|
|
3753
|
+
const assistantVoice = this.messageProcessingService.getVoice(mainVoice || '');
|
|
3754
|
+
const secondaryVoice = agentCard?.voice?.secondary?.voice || 'en-US-News-L';
|
|
3711
3755
|
let assistantMessage = {
|
|
3712
3756
|
messageId,
|
|
3713
3757
|
role: ChatRole.Assistant,
|
|
@@ -3879,7 +3923,7 @@ class ConversationService {
|
|
|
3879
3923
|
isLoading: true,
|
|
3880
3924
|
};
|
|
3881
3925
|
// Process to get initial structure and ensure it's ready for display
|
|
3882
|
-
const processedPlaceholder = this.messageProcessingService.processMessage(placeholder, this.conversationSettingsState());
|
|
3926
|
+
const processedPlaceholder = this.messageProcessingService.processMessage(placeholder, this.conversationSettingsState(), this.agentCardState.agentCard$()?.voice);
|
|
3883
3927
|
// Use passed updateId or newly generated one from processing
|
|
3884
3928
|
const messageId = updateId || processedPlaceholder.messageId || nanoid();
|
|
3885
3929
|
processedPlaceholder.messageId = messageId;
|
|
@@ -4003,7 +4047,6 @@ function provideLive2D(config) {
|
|
|
4003
4047
|
|
|
4004
4048
|
class VideoPlayerService {
|
|
4005
4049
|
constructor() {
|
|
4006
|
-
this.conversationFlowStateService = inject(ConversationFlowStateService);
|
|
4007
4050
|
this.useNativePlayer = false;
|
|
4008
4051
|
this.isRewinding = false;
|
|
4009
4052
|
this.videoQueue = [];
|
|
@@ -4013,17 +4056,6 @@ class VideoPlayerService {
|
|
|
4013
4056
|
this.agentCard = signal(undefined, ...(ngDevMode ? [{ debugName: "agentCard" }] : /* istanbul ignore next */ []));
|
|
4014
4057
|
console.log(' CONSTRUCTOR FOR VIDEO PLAYER...');
|
|
4015
4058
|
this.playAndRewindedSubscription = this.playAndRewinded$.subscribe(() => { });
|
|
4016
|
-
this.moodSubscription = this.conversationFlowStateService.moodUpdated$.subscribe((mood) => {
|
|
4017
|
-
console.log('CHANGING MOOD.. ', mood);
|
|
4018
|
-
const card = this.agentCard();
|
|
4019
|
-
if (mood && card?.assets?.motions) {
|
|
4020
|
-
const motionUrl = card.assets.motions.find((m) => m.metadata?.moodState === mood)?.url;
|
|
4021
|
-
if (motionUrl) {
|
|
4022
|
-
this.addVideosToQueue([motionUrl]);
|
|
4023
|
-
this.playAndRewind();
|
|
4024
|
-
}
|
|
4025
|
-
}
|
|
4026
|
-
});
|
|
4027
4059
|
}
|
|
4028
4060
|
setAgentCard(card) {
|
|
4029
4061
|
this.agentCard.set(card);
|
|
@@ -4155,9 +4187,6 @@ class VideoPlayerService {
|
|
|
4155
4187
|
if (this.playAndRewindedSubscription) {
|
|
4156
4188
|
this.playAndRewindedSubscription.unsubscribe();
|
|
4157
4189
|
}
|
|
4158
|
-
if (this.moodSubscription) {
|
|
4159
|
-
this.moodSubscription.unsubscribe();
|
|
4160
|
-
}
|
|
4161
4190
|
}
|
|
4162
4191
|
playNextInQueueNative() {
|
|
4163
4192
|
if (this.videoQueue.length > 0) {
|
|
@@ -4245,25 +4274,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
4245
4274
|
|
|
4246
4275
|
class VideoPlayerNativeService {
|
|
4247
4276
|
constructor() {
|
|
4248
|
-
this.conversationFlowStateService = inject(ConversationFlowStateService);
|
|
4249
4277
|
this.videoQueue = [];
|
|
4250
4278
|
this.defaultVideoUrl = null;
|
|
4251
4279
|
this.pendingPlayingHandler = null;
|
|
4252
4280
|
this.pendingPlayingTarget = null;
|
|
4253
4281
|
this.agentCard = signal(undefined, ...(ngDevMode ? [{ debugName: "agentCard" }] : /* istanbul ignore next */ []));
|
|
4254
4282
|
this.isVideoPlaying = signal(false, ...(ngDevMode ? [{ debugName: "isVideoPlaying" }] : /* istanbul ignore next */ []));
|
|
4255
|
-
this.moodSubscription = this.conversationFlowStateService.moodUpdated$.subscribe((mood) => {
|
|
4256
|
-
const card = this.agentCard();
|
|
4257
|
-
if (!mood || !card?.assets?.motions)
|
|
4258
|
-
return;
|
|
4259
|
-
const motionUrl = card.assets.motions.find((m) => m.metadata?.moodState === mood)?.url;
|
|
4260
|
-
if (!motionUrl)
|
|
4261
|
-
return;
|
|
4262
|
-
// Interrupt current playback. transitionToVideo loads the new clip
|
|
4263
|
-
// into the hidden <video> and crossfades only when its first frame
|
|
4264
|
-
// is ready, so the active clip keeps showing until then (no flash).
|
|
4265
|
-
this.transitionToVideo(motionUrl);
|
|
4266
|
-
});
|
|
4267
4283
|
this.onVideoEndedBound = this.onVideoEnded.bind(this);
|
|
4268
4284
|
}
|
|
4269
4285
|
setAgentCard(card) {
|
|
@@ -4346,7 +4362,6 @@ class VideoPlayerNativeService {
|
|
|
4346
4362
|
this.agentCard.set(undefined);
|
|
4347
4363
|
}
|
|
4348
4364
|
ngOnDestroy() {
|
|
4349
|
-
this.moodSubscription?.unsubscribe();
|
|
4350
4365
|
this.destroyPlayer();
|
|
4351
4366
|
}
|
|
4352
4367
|
onVideoEnded() {
|
|
@@ -4619,573 +4634,841 @@ class ChatFooterComponent {
|
|
|
4619
4634
|
}
|
|
4620
4635
|
}
|
|
4621
4636
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4622
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ChatFooterComponent, isStandalone: true, selector: "dc-chat-footer", inputs: { isAIThinking: { classPropertyName: "isAIThinking", publicName: "isAIThinking", isSignal: true, isRequired: false, transformFunction: null }, micSettings: { classPropertyName: "micSettings", publicName: "micSettings", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sendMessage: "sendMessage", textInputChanged: "textInputChanged" }, viewQueries: [{ propertyName: "micComponent", first: true, predicate: DCMicComponent, descendants: true }], ngImport: i0, template: "<div class=\"progress-input\">\n <div class=\"input-container\">\n <dc-mic [isDone]=\"isAIThinking() || isPaused()\" (onFinished)=\"handleAudioRecorded($event)\" (onCanceled)=\"handleAudioCanceled()\"></dc-mic>\n\n <textarea pTextarea [formControl]=\"chatInputControl\" (keyup.enter)=\"prepareUserMsnAndSend()\" rows=\"1\" [disabled]=\"isPaused()\"></textarea>\n\n <p-button (click)=\"prepareUserMsnAndSend()\" [disabled]=\"isAIThinking() || isPaused() || !chatInputControl.value\" label=\"Enviar\" [rounded]=\"true\" />\n </div>\n\n @if(challenges().length > 0) {\n <div class=\"challenges-container\">\n @for(challenge of challenges(); track challenge.name) {\n <span class=\"challenge-emoji\" [title]=\"challenge.description\" [style.opacity]=\"challenge.completed ? 1 : 0.3\">\n {{ challenge.emoji }}\n </span>\n }\n </div>\n } @if(agentCardStateService.conversationFlow$()?.goal?.enabled) {\n\n <p-progressbar showValue=\"false\" [value]=\"score()\" [style]=\"{ height: '6px' }\" />\n }\n</div>\n", styles: [".progress-input{position:relative;padding:10px;background-color:#f5f5f545;border-top:1px solid #b1a8a8}.progress-input .input-container{display:flex;align-items:center;margin-bottom:5px}.progress-input .input-container textarea{flex:1;resize:none;margin:0 10px}.progress-input .input-container .send-button{background-color:#007bff;color:#fff;border:none;border-radius:4px;padding:8px 15px;cursor:pointer}.progress-input .input-container .send-button:disabled{background-color:#ccc;cursor:not-allowed}.progress-input .input-container .send-button:hover:not(:disabled){background-color:#0069d9}.progress-input .challenges-container{position:absolute;top:-20px;left:100px;border-radius:20px;padding:2px 10px;z-index:10;display:flex;gap:8px;justify-content:flex-start}.progress-input .challenges-container .challenge-emoji{font-size:1.2rem;transition:opacity .3s ease,transform .3s ease;cursor:help}.progress-input .challenges-container .challenge-emoji:hover{transform:scale(1.2)}\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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: ProgressBarModule }, { kind: "component", type: i2$1.ProgressBar, selector: "p-progressBar, p-progressbar, p-progress-bar", inputs: ["value", "showValue", "styleClass", "valueStyleClass", "unit", "mode", "color"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i4.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["pTextareaPT", "pTextareaUnstyled", "autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { 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: "component", type: DCMicComponent, selector: "dc-mic", inputs: ["targetOrBase", "micSettings", "maxRecordingTime", "isDone"], outputs: ["onInterpretedText", "onFinishedRecognition", "onFinished", "onCanceled"] }] }); }
|
|
4637
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ChatFooterComponent, isStandalone: true, selector: "dc-chat-footer", inputs: { isAIThinking: { classPropertyName: "isAIThinking", publicName: "isAIThinking", isSignal: true, isRequired: false, transformFunction: null }, micSettings: { classPropertyName: "micSettings", publicName: "micSettings", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sendMessage: "sendMessage", textInputChanged: "textInputChanged" }, viewQueries: [{ propertyName: "micComponent", first: true, predicate: DCMicComponent, descendants: true }], ngImport: i0, template: "<div class=\"progress-input\">\n <div class=\"input-container\">\n <dc-mic [isDone]=\"isAIThinking() || isPaused()\" [micSettings]=\"micSettings()\" (onFinished)=\"handleAudioRecorded($event)\" (onCanceled)=\"handleAudioCanceled()\"></dc-mic>\n\n <textarea pTextarea [formControl]=\"chatInputControl\" (keyup.enter)=\"prepareUserMsnAndSend()\" rows=\"1\" [disabled]=\"isPaused()\"></textarea>\n\n <p-button (click)=\"prepareUserMsnAndSend()\" [disabled]=\"isAIThinking() || isPaused() || !chatInputControl.value\" label=\"Enviar\" [rounded]=\"true\" />\n </div>\n\n @if(challenges().length > 0) {\n <div class=\"challenges-container\">\n @for(challenge of challenges(); track challenge.name) {\n <span class=\"challenge-emoji\" [title]=\"challenge.description\" [style.opacity]=\"challenge.completed ? 1 : 0.3\">\n {{ challenge.emoji }}\n </span>\n }\n </div>\n } @if(agentCardStateService.conversationFlow$()?.goal?.enabled) {\n\n <p-progressbar showValue=\"false\" [value]=\"score()\" [style]=\"{ height: '6px' }\" />\n }\n</div>\n", styles: [".progress-input{position:relative;padding:10px;background-color:#f5f5f545;border-top:1px solid #b1a8a8}.progress-input .input-container{display:flex;align-items:center;margin-bottom:5px}.progress-input .input-container textarea{flex:1;resize:none;margin:0 10px}.progress-input .input-container .send-button{background-color:#007bff;color:#fff;border:none;border-radius:4px;padding:8px 15px;cursor:pointer}.progress-input .input-container .send-button:disabled{background-color:#ccc;cursor:not-allowed}.progress-input .input-container .send-button:hover:not(:disabled){background-color:#0069d9}.progress-input .challenges-container{position:absolute;top:-20px;left:100px;border-radius:20px;padding:2px 10px;z-index:10;display:flex;gap:8px;justify-content:flex-start}.progress-input .challenges-container .challenge-emoji{font-size:1.2rem;transition:opacity .3s ease,transform .3s ease;cursor:help}.progress-input .challenges-container .challenge-emoji:hover{transform:scale(1.2)}\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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: ProgressBarModule }, { kind: "component", type: i2$1.ProgressBar, selector: "p-progressBar, p-progressbar, p-progress-bar", inputs: ["value", "showValue", "styleClass", "valueStyleClass", "unit", "mode", "color"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i4.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["pTextareaPT", "pTextareaUnstyled", "autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { 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: "component", type: DCMicComponent, selector: "dc-mic", inputs: ["targetOrBase", "micSettings", "maxRecordingTime", "isDone"], outputs: ["onInterpretedText", "onFinishedRecognition", "onFinished", "onCanceled", "onDiscarded"] }] }); }
|
|
4623
4638
|
}
|
|
4624
4639
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatFooterComponent, decorators: [{
|
|
4625
4640
|
type: Component,
|
|
4626
|
-
args: [{ selector: 'dc-chat-footer', standalone: true, imports: [ReactiveFormsModule, ProgressBarModule, TextareaModule, ButtonModule, DCMicComponent], template: "<div class=\"progress-input\">\n <div class=\"input-container\">\n <dc-mic [isDone]=\"isAIThinking() || isPaused()\" (onFinished)=\"handleAudioRecorded($event)\" (onCanceled)=\"handleAudioCanceled()\"></dc-mic>\n\n <textarea pTextarea [formControl]=\"chatInputControl\" (keyup.enter)=\"prepareUserMsnAndSend()\" rows=\"1\" [disabled]=\"isPaused()\"></textarea>\n\n <p-button (click)=\"prepareUserMsnAndSend()\" [disabled]=\"isAIThinking() || isPaused() || !chatInputControl.value\" label=\"Enviar\" [rounded]=\"true\" />\n </div>\n\n @if(challenges().length > 0) {\n <div class=\"challenges-container\">\n @for(challenge of challenges(); track challenge.name) {\n <span class=\"challenge-emoji\" [title]=\"challenge.description\" [style.opacity]=\"challenge.completed ? 1 : 0.3\">\n {{ challenge.emoji }}\n </span>\n }\n </div>\n } @if(agentCardStateService.conversationFlow$()?.goal?.enabled) {\n\n <p-progressbar showValue=\"false\" [value]=\"score()\" [style]=\"{ height: '6px' }\" />\n }\n</div>\n", styles: [".progress-input{position:relative;padding:10px;background-color:#f5f5f545;border-top:1px solid #b1a8a8}.progress-input .input-container{display:flex;align-items:center;margin-bottom:5px}.progress-input .input-container textarea{flex:1;resize:none;margin:0 10px}.progress-input .input-container .send-button{background-color:#007bff;color:#fff;border:none;border-radius:4px;padding:8px 15px;cursor:pointer}.progress-input .input-container .send-button:disabled{background-color:#ccc;cursor:not-allowed}.progress-input .input-container .send-button:hover:not(:disabled){background-color:#0069d9}.progress-input .challenges-container{position:absolute;top:-20px;left:100px;border-radius:20px;padding:2px 10px;z-index:10;display:flex;gap:8px;justify-content:flex-start}.progress-input .challenges-container .challenge-emoji{font-size:1.2rem;transition:opacity .3s ease,transform .3s ease;cursor:help}.progress-input .challenges-container .challenge-emoji:hover{transform:scale(1.2)}\n"] }]
|
|
4641
|
+
args: [{ selector: 'dc-chat-footer', standalone: true, imports: [ReactiveFormsModule, ProgressBarModule, TextareaModule, ButtonModule, DCMicComponent], template: "<div class=\"progress-input\">\n <div class=\"input-container\">\n <dc-mic [isDone]=\"isAIThinking() || isPaused()\" [micSettings]=\"micSettings()\" (onFinished)=\"handleAudioRecorded($event)\" (onCanceled)=\"handleAudioCanceled()\"></dc-mic>\n\n <textarea pTextarea [formControl]=\"chatInputControl\" (keyup.enter)=\"prepareUserMsnAndSend()\" rows=\"1\" [disabled]=\"isPaused()\"></textarea>\n\n <p-button (click)=\"prepareUserMsnAndSend()\" [disabled]=\"isAIThinking() || isPaused() || !chatInputControl.value\" label=\"Enviar\" [rounded]=\"true\" />\n </div>\n\n @if(challenges().length > 0) {\n <div class=\"challenges-container\">\n @for(challenge of challenges(); track challenge.name) {\n <span class=\"challenge-emoji\" [title]=\"challenge.description\" [style.opacity]=\"challenge.completed ? 1 : 0.3\">\n {{ challenge.emoji }}\n </span>\n }\n </div>\n } @if(agentCardStateService.conversationFlow$()?.goal?.enabled) {\n\n <p-progressbar showValue=\"false\" [value]=\"score()\" [style]=\"{ height: '6px' }\" />\n }\n</div>\n", styles: [".progress-input{position:relative;padding:10px;background-color:#f5f5f545;border-top:1px solid #b1a8a8}.progress-input .input-container{display:flex;align-items:center;margin-bottom:5px}.progress-input .input-container textarea{flex:1;resize:none;margin:0 10px}.progress-input .input-container .send-button{background-color:#007bff;color:#fff;border:none;border-radius:4px;padding:8px 15px;cursor:pointer}.progress-input .input-container .send-button:disabled{background-color:#ccc;cursor:not-allowed}.progress-input .input-container .send-button:hover:not(:disabled){background-color:#0069d9}.progress-input .challenges-container{position:absolute;top:-20px;left:100px;border-radius:20px;padding:2px 10px;z-index:10;display:flex;gap:8px;justify-content:flex-start}.progress-input .challenges-container .challenge-emoji{font-size:1.2rem;transition:opacity .3s ease,transform .3s ease;cursor:help}.progress-input .challenges-container .challenge-emoji:hover{transform:scale(1.2)}\n"] }]
|
|
4627
4642
|
}], ctorParameters: () => [], propDecorators: { micComponent: [{
|
|
4628
4643
|
type: ViewChild,
|
|
4629
4644
|
args: [DCMicComponent]
|
|
4630
4645
|
}], isAIThinking: [{ type: i0.Input, args: [{ isSignal: true, alias: "isAIThinking", required: false }] }], micSettings: [{ type: i0.Input, args: [{ isSignal: true, alias: "micSettings", required: false }] }], sendMessage: [{ type: i0.Output, args: ["sendMessage"] }], textInputChanged: [{ type: i0.Output, args: ["textInputChanged"] }] } });
|
|
4631
4646
|
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
//
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
}
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4647
|
+
class AudioTextSyncService {
|
|
4648
|
+
constructor() {
|
|
4649
|
+
// Maps to store message-specific signals and observables
|
|
4650
|
+
this.highlightedWordsSignalMap = new Map();
|
|
4651
|
+
this.highlightedWords$Map = new Map();
|
|
4652
|
+
// Maps for cleanup and active audio elements
|
|
4653
|
+
this.cleanup$Map = new Map();
|
|
4654
|
+
this.activeAudioMap = new Map();
|
|
4655
|
+
this.currentPlayingAudio = null;
|
|
4656
|
+
this.chatMonitorService = inject(ChatMonitorService, { optional: true });
|
|
4657
|
+
this.destroyRef = inject(DestroyRef);
|
|
4658
|
+
this.currentActiveMessage = signal(null, ...(ngDevMode ? [{ debugName: "currentActiveMessage" }] : /* istanbul ignore next */ []));
|
|
4659
|
+
// Ensure cleanup when service is destroyed
|
|
4660
|
+
this.destroyRef.onDestroy(() => {
|
|
4661
|
+
this.stopAllSyncs();
|
|
4662
|
+
});
|
|
4663
|
+
}
|
|
4664
|
+
/**
|
|
4665
|
+
* Plays the given audio element exclusively, pausing any other active audio.
|
|
4666
|
+
* @param audioElement The audio element to play.
|
|
4667
|
+
* @param message The message associated with this audio playback.
|
|
4668
|
+
*/
|
|
4669
|
+
playAudio(audioElement, message) {
|
|
4670
|
+
if (this.currentPlayingAudio && this.currentPlayingAudio !== audioElement) {
|
|
4671
|
+
this.currentPlayingAudio.pause();
|
|
4672
|
+
this.currentPlayingAudio.currentTime = 0;
|
|
4673
|
+
}
|
|
4674
|
+
// Ensure audioUrl is present on the message if it's currently empty but the element has a source
|
|
4675
|
+
if (!message.audioUrl && audioElement.src) {
|
|
4676
|
+
console.log('AudioTextSyncService: fixing empty message.audioUrl with audioElement.src:', audioElement.src);
|
|
4677
|
+
message.audioUrl = audioElement.src;
|
|
4678
|
+
}
|
|
4679
|
+
console.log('AudioTextSyncService: playAudio emitting to ChatMonitorService activeAudioMessage:', {
|
|
4680
|
+
messageId: message.messageId,
|
|
4681
|
+
audioUrl: message.audioUrl,
|
|
4682
|
+
src: audioElement.src
|
|
4683
|
+
});
|
|
4684
|
+
this.currentPlayingAudio = audioElement;
|
|
4685
|
+
this.currentActiveMessage.set(message);
|
|
4686
|
+
this.chatMonitorService?.activeAudioMessage.set(message);
|
|
4687
|
+
this.chatMonitorService?.logMessageAudioWillPlay(message);
|
|
4688
|
+
this.chatMonitorService?.logMessageAudioStatus(message, 'playing');
|
|
4689
|
+
// Setup ended listener to clear state automatically when playback finishes
|
|
4690
|
+
fromEvent(audioElement, 'ended')
|
|
4691
|
+
.pipe(take(1))
|
|
4692
|
+
.subscribe(() => {
|
|
4693
|
+
if (this.currentPlayingAudio === audioElement) {
|
|
4694
|
+
this.currentActiveMessage.set(null);
|
|
4695
|
+
this.chatMonitorService?.activeAudioMessage.set(null);
|
|
4696
|
+
this.chatMonitorService?.logMessageAudioStatus(message, 'finished');
|
|
4651
4697
|
}
|
|
4652
|
-
|
|
4653
|
-
|
|
4698
|
+
});
|
|
4699
|
+
audioElement.play().catch((error) => {
|
|
4700
|
+
console.error('Error playing audio in AudioTextSyncService:', error);
|
|
4701
|
+
this.currentActiveMessage.set(null);
|
|
4702
|
+
this.chatMonitorService?.activeAudioMessage.set(null);
|
|
4703
|
+
this.chatMonitorService?.logMessageAudioStatus(message, 'stopped');
|
|
4704
|
+
});
|
|
4705
|
+
}
|
|
4706
|
+
/**
|
|
4707
|
+
* Pauses the given audio element.
|
|
4708
|
+
* @param audioElement The audio element to pause.
|
|
4709
|
+
*/
|
|
4710
|
+
pauseAudio(audioElement) {
|
|
4711
|
+
const message = this.currentActiveMessage();
|
|
4712
|
+
audioElement.pause();
|
|
4713
|
+
if (this.currentPlayingAudio === audioElement) {
|
|
4714
|
+
this.currentActiveMessage.set(null);
|
|
4715
|
+
this.chatMonitorService?.activeAudioMessage.set(null);
|
|
4716
|
+
if (message) {
|
|
4717
|
+
this.chatMonitorService?.logMessageAudioStatus(message, 'stopped');
|
|
4654
4718
|
}
|
|
4655
4719
|
}
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4720
|
+
}
|
|
4721
|
+
/**
|
|
4722
|
+
* Synchronizes audio playback with text transcription
|
|
4723
|
+
* @param audioElement The audio element to sync with
|
|
4724
|
+
* @param transcriptionTimestamps Array of word timestamps
|
|
4725
|
+
* @param messageId Unique identifier for the message
|
|
4726
|
+
*/
|
|
4727
|
+
syncAudioWithText(audioElement, transcriptionTimestamps, messageId) {
|
|
4728
|
+
// Stop any existing sync for this message
|
|
4729
|
+
this.stopSync(messageId);
|
|
4730
|
+
// Create new signal and subject for this message if they don't exist
|
|
4731
|
+
if (!this.highlightedWordsSignalMap.has(messageId)) {
|
|
4732
|
+
this.highlightedWordsSignalMap.set(messageId, signal([]));
|
|
4733
|
+
}
|
|
4734
|
+
if (!this.highlightedWords$Map.has(messageId)) {
|
|
4735
|
+
this.highlightedWords$Map.set(messageId, new BehaviorSubject([]));
|
|
4736
|
+
}
|
|
4737
|
+
// Create cleanup subject
|
|
4738
|
+
const cleanup$ = new Subject();
|
|
4739
|
+
this.cleanup$Map.set(messageId, cleanup$);
|
|
4740
|
+
// Store the active audio element
|
|
4741
|
+
this.activeAudioMap.set(messageId, audioElement);
|
|
4742
|
+
// Get the signal and subject for this message
|
|
4743
|
+
const messageSignal = this.highlightedWordsSignalMap.get(messageId);
|
|
4744
|
+
const messageSubject = this.highlightedWords$Map.get(messageId);
|
|
4745
|
+
// Initialize the highlighted words state
|
|
4746
|
+
const initialWords = transcriptionTimestamps.map((word, index) => ({
|
|
4747
|
+
word: word.word,
|
|
4748
|
+
index,
|
|
4749
|
+
isHighlighted: false,
|
|
4750
|
+
}));
|
|
4751
|
+
// Update both signal and observable
|
|
4752
|
+
messageSignal.set(initialWords);
|
|
4753
|
+
messageSubject.next(initialWords);
|
|
4754
|
+
// Listen to timeupdate events
|
|
4755
|
+
fromEvent(audioElement, 'timeupdate')
|
|
4756
|
+
.pipe(takeUntilDestroyed(this.destroyRef), takeUntil(cleanup$), map(() => audioElement.currentTime))
|
|
4757
|
+
.subscribe((currentTime) => {
|
|
4758
|
+
const updatedWords = transcriptionTimestamps.map((word, index) => {
|
|
4759
|
+
const isHighlighted = currentTime >= word.start - 0.15 && currentTime < word.end + 0.15;
|
|
4760
|
+
return {
|
|
4761
|
+
word: word.word,
|
|
4762
|
+
index,
|
|
4763
|
+
isHighlighted,
|
|
4764
|
+
};
|
|
4765
|
+
});
|
|
4766
|
+
// Update both signal and observable for this message
|
|
4767
|
+
messageSignal.set(updatedWords);
|
|
4768
|
+
messageSubject.next(updatedWords);
|
|
4769
|
+
});
|
|
4770
|
+
// Listen to ended event for cleanup
|
|
4771
|
+
fromEvent(audioElement, 'ended')
|
|
4772
|
+
.pipe(takeUntilDestroyed(this.destroyRef), takeUntil(cleanup$))
|
|
4773
|
+
.subscribe(() => {
|
|
4774
|
+
// Reset highlighting when audio ends
|
|
4775
|
+
const resetWords = initialWords.map((word) => ({
|
|
4776
|
+
...word,
|
|
4777
|
+
isHighlighted: false,
|
|
4778
|
+
}));
|
|
4779
|
+
messageSignal.set(resetWords);
|
|
4780
|
+
messageSubject.next(resetWords);
|
|
4781
|
+
});
|
|
4782
|
+
}
|
|
4783
|
+
/**
|
|
4784
|
+
* Stops the sync for a specific message and cleans up resources
|
|
4785
|
+
* @param messageId The ID of the message to stop syncing
|
|
4786
|
+
*/
|
|
4787
|
+
stopSync(messageId) {
|
|
4788
|
+
if (this.activeAudioMap.has(messageId)) {
|
|
4789
|
+
const audio = this.activeAudioMap.get(messageId);
|
|
4790
|
+
const currentMsg = this.currentActiveMessage();
|
|
4791
|
+
if (audio && this.currentPlayingAudio === audio) {
|
|
4792
|
+
audio.pause();
|
|
4793
|
+
audio.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
|
+
}
|
|
4659
4800
|
}
|
|
4660
|
-
|
|
4661
|
-
|
|
4801
|
+
// Get the cleanup subject for this message
|
|
4802
|
+
const cleanup$ = this.cleanup$Map.get(messageId);
|
|
4803
|
+
if (cleanup$) {
|
|
4804
|
+
cleanup$.next();
|
|
4805
|
+
cleanup$.complete();
|
|
4806
|
+
this.cleanup$Map.delete(messageId);
|
|
4662
4807
|
}
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4808
|
+
this.activeAudioMap.delete(messageId);
|
|
4809
|
+
// Reset state for this message
|
|
4810
|
+
const messageSignal = this.highlightedWordsSignalMap.get(messageId);
|
|
4811
|
+
const messageSubject = this.highlightedWords$Map.get(messageId);
|
|
4812
|
+
if (messageSignal) {
|
|
4813
|
+
messageSignal.set([]);
|
|
4667
4814
|
}
|
|
4668
|
-
|
|
4669
|
-
|
|
4815
|
+
if (messageSubject) {
|
|
4816
|
+
messageSubject.next([]);
|
|
4670
4817
|
}
|
|
4671
4818
|
}
|
|
4672
|
-
else {
|
|
4673
|
-
// It's a word (including punctuation)
|
|
4674
|
-
const currentTag = tagStack.length > 0 ? tagStack[tagStack.length - 1] : '';
|
|
4675
|
-
result.push({ word: token, tag: currentTag });
|
|
4676
|
-
}
|
|
4677
4819
|
}
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4820
|
+
/**
|
|
4821
|
+
* Stops all syncs and cleans up all resources
|
|
4822
|
+
*/
|
|
4823
|
+
stopAllSyncs() {
|
|
4824
|
+
const currentMsg = this.currentActiveMessage();
|
|
4825
|
+
if (this.currentPlayingAudio) {
|
|
4826
|
+
this.currentPlayingAudio.pause();
|
|
4827
|
+
this.currentPlayingAudio.currentTime = 0;
|
|
4828
|
+
this.currentPlayingAudio = null;
|
|
4829
|
+
this.currentActiveMessage.set(null);
|
|
4830
|
+
this.chatMonitorService?.activeAudioMessage.set(null);
|
|
4831
|
+
if (currentMsg) {
|
|
4832
|
+
this.chatMonitorService?.logMessageAudioStatus(currentMsg, 'stopped');
|
|
4833
|
+
}
|
|
4834
|
+
}
|
|
4835
|
+
// Get all message IDs and stop each sync
|
|
4836
|
+
for (const messageId of this.activeAudioMap.keys()) {
|
|
4837
|
+
this.stopSync(messageId);
|
|
4838
|
+
}
|
|
4839
|
+
// Clear all maps
|
|
4840
|
+
this.highlightedWordsSignalMap.clear();
|
|
4841
|
+
this.highlightedWords$Map.clear();
|
|
4842
|
+
this.cleanup$Map.clear();
|
|
4843
|
+
this.activeAudioMap.clear();
|
|
4844
|
+
}
|
|
4845
|
+
/**
|
|
4846
|
+
* Returns the highlighted words signal for a specific message
|
|
4847
|
+
* @param messageId The ID of the message
|
|
4848
|
+
*/
|
|
4849
|
+
getHighlightedWordsSignal(messageId) {
|
|
4850
|
+
// Create a new signal for this message if it doesn't exist
|
|
4851
|
+
if (!this.highlightedWordsSignalMap.has(messageId)) {
|
|
4852
|
+
this.highlightedWordsSignalMap.set(messageId, signal([]));
|
|
4853
|
+
}
|
|
4854
|
+
return this.highlightedWordsSignalMap.get(messageId);
|
|
4855
|
+
}
|
|
4856
|
+
/**
|
|
4857
|
+
* Returns the highlighted words observable for a specific message
|
|
4858
|
+
* @param messageId The ID of the message
|
|
4859
|
+
*/
|
|
4860
|
+
getHighlightedWords$(messageId) {
|
|
4861
|
+
// Create a new subject for this message if it doesn't exist
|
|
4862
|
+
if (!this.highlightedWords$Map.has(messageId)) {
|
|
4863
|
+
this.highlightedWords$Map.set(messageId, new BehaviorSubject([]));
|
|
4864
|
+
}
|
|
4865
|
+
return this.highlightedWords$Map.get(messageId).asObservable();
|
|
4866
|
+
}
|
|
4867
|
+
/**
|
|
4868
|
+
* Checks if a word at a specific index is currently highlighted for a specific message
|
|
4869
|
+
* @param messageId The ID of the message
|
|
4870
|
+
* @param index The index of the word to check
|
|
4871
|
+
*/
|
|
4872
|
+
isWordHighlighted(messageId, index) {
|
|
4873
|
+
const messageSignal = this.getHighlightedWordsSignal(messageId);
|
|
4874
|
+
return messageSignal().some((word) => word.index === index && word.isHighlighted);
|
|
4875
|
+
}
|
|
4876
|
+
/**
|
|
4877
|
+
* Returns an observable that emits true when a word at a specific index is highlighted for a specific message
|
|
4878
|
+
* @param messageId The ID of the message
|
|
4879
|
+
* @param index The index of the word to observe
|
|
4880
|
+
*/
|
|
4881
|
+
isWordHighlighted$(messageId, index) {
|
|
4882
|
+
return this.getHighlightedWords$(messageId).pipe(map((words) => words.some((word) => word.index === index && word.isHighlighted)));
|
|
4883
|
+
}
|
|
4884
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AudioTextSyncService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4885
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AudioTextSyncService, providedIn: 'root' }); }
|
|
4681
4886
|
}
|
|
4887
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: AudioTextSyncService, decorators: [{
|
|
4888
|
+
type: Injectable,
|
|
4889
|
+
args: [{
|
|
4890
|
+
providedIn: 'root',
|
|
4891
|
+
}]
|
|
4892
|
+
}], ctorParameters: () => [] });
|
|
4682
4893
|
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4894
|
+
/**
|
|
4895
|
+
* @Injectable
|
|
4896
|
+
*
|
|
4897
|
+
* @description
|
|
4898
|
+
* This service orchestrates the playback of chat messages, ensuring that they are played sequentially.
|
|
4899
|
+
* It manages a queue of messages and handles the generation and playback of audio for each message.
|
|
4900
|
+
*
|
|
4901
|
+
* @see ChatMessageOrchestratorComponent
|
|
4902
|
+
*/
|
|
4903
|
+
class MessageOrchestrationService {
|
|
4688
4904
|
constructor() {
|
|
4689
|
-
//
|
|
4690
|
-
this.
|
|
4691
|
-
this.
|
|
4692
|
-
this.
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
this.
|
|
4696
|
-
this.
|
|
4697
|
-
this.
|
|
4698
|
-
//
|
|
4699
|
-
this.
|
|
4700
|
-
this.
|
|
4701
|
-
//
|
|
4702
|
-
this.
|
|
4703
|
-
this.
|
|
4704
|
-
this.
|
|
4705
|
-
this.
|
|
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
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
this.transcriptionTimestamps = computed(() => {
|
|
4737
|
-
const msg = this.message();
|
|
4738
|
-
if (!msg || !msg.transcription) {
|
|
4739
|
-
return [];
|
|
4905
|
+
// Services
|
|
4906
|
+
this.conversationService = inject(ConversationService);
|
|
4907
|
+
this.chatMonitorService = inject(ChatMonitorService);
|
|
4908
|
+
this.userService = inject(UserService);
|
|
4909
|
+
this.messagesStateService = inject(MessagesStateService);
|
|
4910
|
+
// State Signals
|
|
4911
|
+
this.messages = signal([], ...(ngDevMode ? [{ debugName: "messages" }] : /* istanbul ignore next */ []));
|
|
4912
|
+
this.messageRole = signal(ChatRole.User, ...(ngDevMode ? [{ debugName: "messageRole" }] : /* istanbul ignore next */ []));
|
|
4913
|
+
this.messageToPlay = signal(null, ...(ngDevMode ? [{ debugName: "messageToPlay" }] : /* istanbul ignore next */ []));
|
|
4914
|
+
// Public Signals for components to consume
|
|
4915
|
+
this.messagesSignal = this.messages.asReadonly();
|
|
4916
|
+
this.messageToPlay$ = this.messageToPlay.asReadonly();
|
|
4917
|
+
// Audio queue management
|
|
4918
|
+
this.audioQueue = [];
|
|
4919
|
+
this.isGenerating = signal(false, ...(ngDevMode ? [{ debugName: "isGenerating" }] : /* istanbul ignore next */ []));
|
|
4920
|
+
this.currentPlayingIndex = signal(null, ...(ngDevMode ? [{ debugName: "currentPlayingIndex" }] : /* istanbul ignore next */ []));
|
|
4921
|
+
this.preGenerationInProgress = signal(false, ...(ngDevMode ? [{ debugName: "preGenerationInProgress" }] : /* istanbul ignore next */ []));
|
|
4922
|
+
console.log('MessageOrchestrationService initialized');
|
|
4923
|
+
}
|
|
4924
|
+
/**
|
|
4925
|
+
* @description
|
|
4926
|
+
* Starts the orchestration process for a list of messages.
|
|
4927
|
+
*
|
|
4928
|
+
* @param messages - An array of `MessageContent` objects to be orchestrated.
|
|
4929
|
+
* @param role - The `ChatRole` of the messages' author.
|
|
4930
|
+
*/
|
|
4931
|
+
startOrchestration(messages, role) {
|
|
4932
|
+
untracked(() => {
|
|
4933
|
+
const currentMessages = this.messages();
|
|
4934
|
+
const isAssistant = role === ChatRole.Assistant;
|
|
4935
|
+
// Check if it's a continuation of the same orchestration (e.g., streaming)
|
|
4936
|
+
const isContinuation = currentMessages.length > 0 &&
|
|
4937
|
+
messages.length >= currentMessages.length &&
|
|
4938
|
+
messages[0].messageId === currentMessages[0].messageId;
|
|
4939
|
+
// Update internal state signal with new messages
|
|
4940
|
+
// If it's a continuation, we want to merge properties carefully to avoid flickering or resetting local state like 'playing' status
|
|
4941
|
+
if (isContinuation) {
|
|
4942
|
+
this.messages.update(prev => {
|
|
4943
|
+
return messages.map((newMsg, idx) => {
|
|
4944
|
+
const prevMsg = prev[idx];
|
|
4945
|
+
if (prevMsg) {
|
|
4946
|
+
// Merge properties: keep local transient state like audioStatus if it's currently managed by this service
|
|
4947
|
+
return { ...newMsg, ...prevMsg, ...newMsg }; // prioritize newMsg but keep prevMsg as base if needed
|
|
4948
|
+
}
|
|
4949
|
+
return newMsg;
|
|
4950
|
+
});
|
|
4951
|
+
});
|
|
4740
4952
|
}
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
// if transcriptionTimestamps is calculated then hasTranscription is true.
|
|
4744
|
-
this.hasTranscription = computed(() => {
|
|
4745
|
-
return this.transcriptionTimestamps().length > 0;
|
|
4746
|
-
}, ...(ngDevMode ? [{ debugName: "hasTranscription" }] : /* istanbul ignore next */ []));
|
|
4747
|
-
effect(() => {
|
|
4748
|
-
const currentMsg = this.message();
|
|
4749
|
-
if (!currentMsg)
|
|
4750
|
-
return;
|
|
4751
|
-
const hasTranscription = this.hasTranscription();
|
|
4752
|
-
const idChanged = currentMsg.messageId !== this.currentMessageId;
|
|
4753
|
-
const audioUrlChanged = currentMsg.audioUrl !== this.lastAudioUrl;
|
|
4754
|
-
const transcriptionChanged = hasTranscription !== this.lastHasTranscription;
|
|
4755
|
-
if (idChanged || audioUrlChanged || transcriptionChanged) {
|
|
4756
|
-
this.currentMessageId = currentMsg.messageId;
|
|
4757
|
-
this.lastAudioUrl = currentMsg.audioUrl;
|
|
4758
|
-
this.lastHasTranscription = hasTranscription;
|
|
4759
|
-
this.localAudioStatus.set(null); // Reset local state on message/audio change
|
|
4760
|
-
this.initializeBasedOnMessage(currentMsg);
|
|
4953
|
+
else {
|
|
4954
|
+
this.messages.set([...messages]);
|
|
4761
4955
|
}
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4956
|
+
this.messageRole.set(role);
|
|
4957
|
+
if (isAssistant) {
|
|
4958
|
+
const chatSettings = this.userService.user()?.settings?.conversation;
|
|
4959
|
+
if (chatSettings?.synthVoice) {
|
|
4960
|
+
if (!isContinuation) {
|
|
4961
|
+
console.log('Starting new orchestration');
|
|
4962
|
+
this.initializeAudioQueue();
|
|
4963
|
+
this.processNextInQueue();
|
|
4964
|
+
}
|
|
4965
|
+
else {
|
|
4966
|
+
console.log('Updating existing orchestration queue');
|
|
4967
|
+
// Update the queue with new indices
|
|
4968
|
+
for (let i = currentMessages.length; i < messages.length; i++) {
|
|
4969
|
+
if (messages[i].audioStatus !== 'finished' && !this.audioQueue.includes(i)) {
|
|
4970
|
+
this.audioQueue.push(i);
|
|
4971
|
+
}
|
|
4972
|
+
}
|
|
4973
|
+
this.processNextInQueue();
|
|
4974
|
+
}
|
|
4975
|
+
}
|
|
4766
4976
|
}
|
|
4767
4977
|
});
|
|
4768
|
-
effect(() => {
|
|
4769
|
-
this.wordsWithMetaState = this.wordWithMeta();
|
|
4770
|
-
});
|
|
4771
4978
|
}
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4979
|
+
audioCompleted(message) {
|
|
4980
|
+
const index = this.currentPlayingIndex();
|
|
4981
|
+
if (index !== null) {
|
|
4982
|
+
console.log(`Audio completed for index ${index}, setting status to finished`);
|
|
4983
|
+
this.updateAudioStatus(message, 'finished', index);
|
|
4984
|
+
}
|
|
4985
|
+
else {
|
|
4986
|
+
console.warn('Audio completed but currentPlayingIndex is null');
|
|
4987
|
+
}
|
|
4988
|
+
this.conversationService.currentAudioStatus.set({ message, completed: true });
|
|
4989
|
+
this.currentPlayingIndex.set(null);
|
|
4990
|
+
this.messageToPlay.set(null);
|
|
4991
|
+
if (this.audioQueue.length > 0) {
|
|
4992
|
+
this.processNextInQueue();
|
|
4993
|
+
}
|
|
4777
4994
|
}
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4995
|
+
/**
|
|
4996
|
+
* @description
|
|
4997
|
+
* Updates the audio status of a message.
|
|
4998
|
+
*
|
|
4999
|
+
* @param message - The `MessageContent` object whose status has changed.
|
|
5000
|
+
* @param status - The new `AudioStatus`.
|
|
5001
|
+
* @param index - The index of the message in the orchestration queue.
|
|
5002
|
+
*/
|
|
5003
|
+
updateAudioStatus(message, status, index) {
|
|
5004
|
+
console.log(`Updating audio status for index ${index} to ${status}. MessageId: ${message.messageId}`);
|
|
5005
|
+
if (status === 'playing') {
|
|
5006
|
+
if (message.messageId) {
|
|
5007
|
+
this.messagesStateService.setActiveMessage(message.messageId, index);
|
|
5008
|
+
}
|
|
5009
|
+
// Update local state: set this one as active, others as inactive
|
|
5010
|
+
this.messages.update((messages) => messages.map((m, idx) => ({
|
|
5011
|
+
...m,
|
|
5012
|
+
isActive: idx === index,
|
|
5013
|
+
// Also update the current one's status
|
|
5014
|
+
...(idx === index ? { audioStatus: status } : {}),
|
|
5015
|
+
})));
|
|
4781
5016
|
}
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
this.
|
|
4785
|
-
|
|
5017
|
+
else {
|
|
5018
|
+
// For other statuses (stopped, finished), preserve isActive and update status
|
|
5019
|
+
this.changeStates(index, { ...message, audioStatus: status });
|
|
5020
|
+
}
|
|
5021
|
+
// 4. Update global state
|
|
5022
|
+
if (message.messageId) {
|
|
5023
|
+
this.messagesStateService.updateMultiMessage(message.messageId, index, {
|
|
5024
|
+
audioStatus: status,
|
|
4786
5025
|
});
|
|
4787
|
-
const firstState = this.transcriptionTimestamps()?.map((word, index) => ({
|
|
4788
|
-
word: word.word,
|
|
4789
|
-
index,
|
|
4790
|
-
isHighlighted: false,
|
|
4791
|
-
tag: '',
|
|
4792
|
-
marked: word.word.toLowerCase() == this.markWord()?.toLowerCase(),
|
|
4793
|
-
}));
|
|
4794
|
-
console.log('firstState', firstState);
|
|
4795
|
-
this.wordWithMeta.set(firstState);
|
|
4796
|
-
// Para mostrar las transcripciones iliminadas: el audio tiene que sonar y en la subscripción apareceran los cambios.
|
|
4797
5026
|
}
|
|
4798
|
-
|
|
4799
|
-
|
|
5027
|
+
this.chatMonitorService.logMessageAudioStatus(message, status);
|
|
5028
|
+
}
|
|
5029
|
+
/**
|
|
5030
|
+
* @description
|
|
5031
|
+
* Notifies the `ConversationService` when a word is clicked.
|
|
5032
|
+
*
|
|
5033
|
+
* @param wordData - The `WordData` object containing information about the clicked word.
|
|
5034
|
+
*/
|
|
5035
|
+
wordClicked(wordData) {
|
|
5036
|
+
this.conversationService.notifyWordClicked(wordData);
|
|
5037
|
+
}
|
|
5038
|
+
initializeAudioQueue() {
|
|
5039
|
+
const messages = this.messages();
|
|
5040
|
+
if (messages && messages.length > 0) {
|
|
5041
|
+
this.audioQueue = messages
|
|
5042
|
+
.map((m, index) => ({ status: m.audioStatus, index }))
|
|
5043
|
+
.filter((item) => item.status !== 'finished' && item.status !== 'skip')
|
|
5044
|
+
.map((item) => item.index);
|
|
5045
|
+
console.log('Audio queue initialized:', this.audioQueue);
|
|
4800
5046
|
}
|
|
4801
|
-
// Removal of shouldPlayAudio check - playback now handled by isPendingAudio effect
|
|
4802
5047
|
}
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
audio = new Audio(audioUrl);
|
|
4807
|
-
this.audioElement.set(audio);
|
|
4808
|
-
this.setupAudioEventListeners(audio);
|
|
5048
|
+
async processNextInQueue() {
|
|
5049
|
+
if (this.audioQueue.length === 0 || this.isGenerating() || this.currentPlayingIndex() !== null) {
|
|
5050
|
+
return;
|
|
4809
5051
|
}
|
|
4810
|
-
|
|
4811
|
-
|
|
5052
|
+
const nextIndex = this.audioQueue.shift();
|
|
5053
|
+
this.isGenerating.set(true);
|
|
5054
|
+
try {
|
|
5055
|
+
await this.generateAudioForIndex(nextIndex);
|
|
4812
5056
|
}
|
|
5057
|
+
catch (error) {
|
|
5058
|
+
console.error('Error generating audio:', error);
|
|
5059
|
+
}
|
|
5060
|
+
finally {
|
|
5061
|
+
this.isGenerating.set(false);
|
|
5062
|
+
}
|
|
5063
|
+
this.preGenerateNextIfNeeded();
|
|
4813
5064
|
}
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
.
|
|
4818
|
-
|
|
4819
|
-
|
|
5065
|
+
async preGenerateNextIfNeeded() {
|
|
5066
|
+
if (this.audioQueue.length > 0 && !this.preGenerationInProgress()) {
|
|
5067
|
+
const nextIndex = this.audioQueue[0];
|
|
5068
|
+
const messages = this.messages();
|
|
5069
|
+
const message = messages[nextIndex];
|
|
5070
|
+
// Skip if audio is already generated or generation is not needed
|
|
5071
|
+
if (message.audioUrl || message.isLoading) {
|
|
4820
5072
|
return;
|
|
4821
5073
|
}
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
tag: word.tag,
|
|
4832
|
-
marked: word.word.toLowerCase() == this.markWord()?.toLowerCase(),
|
|
4833
|
-
}));
|
|
4834
|
-
this.wordWithMeta.set(updatedWords);
|
|
5074
|
+
this.preGenerationInProgress.set(true);
|
|
5075
|
+
const loadingMessage = { ...message, isLoading: true };
|
|
5076
|
+
this.changeStates(nextIndex, loadingMessage);
|
|
5077
|
+
try {
|
|
5078
|
+
const messageAudio = await this.generateAudio(message, null);
|
|
5079
|
+
messageAudio.isLoading = false;
|
|
5080
|
+
// Pre-generated audio stays in the background, no status update needed here or set to 'stopped'/'playable'
|
|
5081
|
+
// Actually, if it's pre-rendered it should probably stay as whatever it was but with audioUrl
|
|
5082
|
+
this.changeStates(nextIndex, messageAudio);
|
|
4835
5083
|
}
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
4839
|
-
.subscribe(() => {
|
|
4840
|
-
const resetWords = this.wordWithMeta().map((word) => ({ ...word, isHighlighted: false }));
|
|
4841
|
-
this.wordWithMeta.set(resetWords);
|
|
4842
|
-
const currentMsg = this.message();
|
|
4843
|
-
if (currentMsg) {
|
|
4844
|
-
// No direct mutation of input!
|
|
4845
|
-
this.updateAudioStatus('finished');
|
|
4846
|
-
this.audioCompleted.emit(currentMsg);
|
|
5084
|
+
finally {
|
|
5085
|
+
this.preGenerationInProgress.set(false);
|
|
4847
5086
|
}
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
5089
|
+
async generateAudioForIndex(index) {
|
|
5090
|
+
const messages = this.messages();
|
|
5091
|
+
if (messages[index].audioUrl) {
|
|
5092
|
+
const messageAudio = { ...messages[index], audioStatus: 'pending' };
|
|
5093
|
+
this.changeStates(index, messageAudio);
|
|
5094
|
+
this.chatMonitorService.logMessageAudioWillPlay(messageAudio);
|
|
5095
|
+
this.messageToPlay.set(messageAudio);
|
|
5096
|
+
this.currentPlayingIndex.set(index);
|
|
5097
|
+
return;
|
|
5098
|
+
}
|
|
5099
|
+
const message = { ...messages[index], isLoading: true };
|
|
5100
|
+
this.changeStates(index, message);
|
|
5101
|
+
try {
|
|
5102
|
+
const messageAudio = await this.generateAudio(messages[index], null);
|
|
5103
|
+
messageAudio.isLoading = false;
|
|
5104
|
+
messageAudio.audioStatus = 'pending';
|
|
5105
|
+
this.changeStates(index, messageAudio);
|
|
5106
|
+
this.chatMonitorService.logMessageAudioWillPlay(messageAudio);
|
|
5107
|
+
this.messageToPlay.set(messageAudio);
|
|
5108
|
+
}
|
|
5109
|
+
finally {
|
|
5110
|
+
// Solo limpiamos isLoading sobre el estado ACTUAL del signal (no el snapshot previo al try),
|
|
5111
|
+
// para no pisar el audioStatus:'pending' que acaba de setear el bloque try.
|
|
5112
|
+
this.messages.update(msgs => {
|
|
5113
|
+
const updated = [...msgs];
|
|
5114
|
+
updated[index] = { ...updated[index], isLoading: false };
|
|
5115
|
+
return updated;
|
|
5116
|
+
});
|
|
5117
|
+
}
|
|
5118
|
+
this.currentPlayingIndex.set(index);
|
|
5119
|
+
}
|
|
5120
|
+
changeStates(index, messageAudio) {
|
|
5121
|
+
this.messages.update((messages) => {
|
|
5122
|
+
const newMessages = [...messages];
|
|
5123
|
+
newMessages[index] = { ...messages[index], ...messageAudio };
|
|
5124
|
+
return newMessages;
|
|
4848
5125
|
});
|
|
4849
5126
|
}
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
5127
|
+
async generateAudio(message, overwriteText = null) {
|
|
5128
|
+
if (message.audioUrl) {
|
|
5129
|
+
return message;
|
|
5130
|
+
}
|
|
5131
|
+
try {
|
|
5132
|
+
const text = overwriteText || message.text || message.content;
|
|
5133
|
+
const ttsObject = buildObjectTTSRequest({ ...message, text }, { highlightWords: true });
|
|
5134
|
+
const speechAudio = await this.conversationService.getTTSFile(ttsObject);
|
|
5135
|
+
message = extractAudioAndTranscription(message, speechAudio);
|
|
5136
|
+
return message;
|
|
5137
|
+
}
|
|
5138
|
+
finally {
|
|
5139
|
+
this.isGenerating.set(false);
|
|
4859
5140
|
}
|
|
4860
5141
|
}
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
5142
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageOrchestrationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
5143
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageOrchestrationService }); }
|
|
5144
|
+
}
|
|
5145
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageOrchestrationService, decorators: [{
|
|
5146
|
+
type: Injectable
|
|
5147
|
+
}], ctorParameters: () => [] });
|
|
5148
|
+
|
|
5149
|
+
// Given a text that can be in markdown but only in asterisk like *italic* or **bold** or ***bold italic***
|
|
5150
|
+
// Example Hola que tal es *Hey What's up* en inglés
|
|
5151
|
+
// i want to extract in array every word like this.
|
|
5152
|
+
// [{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: ''}]
|
|
5153
|
+
function extractTags(text) {
|
|
5154
|
+
const result = [];
|
|
5155
|
+
const tagStack = [];
|
|
5156
|
+
// Regex to match markdown markers (***, **, *) or spaces or sequences of non-space/non-asterisk characters (words with punctuation)
|
|
5157
|
+
const regex = /(\*\*\*|\*\*|\*|\s+|[^\s\*]+)/g;
|
|
5158
|
+
let match;
|
|
5159
|
+
while ((match = regex.exec(text)) !== null) {
|
|
5160
|
+
const token = match[0];
|
|
5161
|
+
if (token.trim() === '') {
|
|
5162
|
+
// Ignore spaces
|
|
5163
|
+
continue;
|
|
4868
5164
|
}
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
const currentMsg = this.message();
|
|
4873
|
-
const audioElement = this.audioElement();
|
|
4874
|
-
if (audioElement) {
|
|
4875
|
-
if (audioElement.paused) {
|
|
4876
|
-
this.startAudioPlayback();
|
|
5165
|
+
if (token === '***') {
|
|
5166
|
+
if (tagStack.length > 0 && tagStack[tagStack.length - 1] === 'bold italic') {
|
|
5167
|
+
tagStack.pop(); // Closing bold italic
|
|
4877
5168
|
}
|
|
4878
5169
|
else {
|
|
4879
|
-
|
|
4880
|
-
this.updateAudioStatus('stopped');
|
|
5170
|
+
tagStack.push('bold italic'); // Opening bold italic
|
|
4881
5171
|
}
|
|
4882
5172
|
}
|
|
4883
|
-
else if (
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
this.playAudio.emit(currentMsg);
|
|
5173
|
+
else if (token === '**') {
|
|
5174
|
+
if (tagStack.length > 0 && tagStack[tagStack.length - 1] === 'bold') {
|
|
5175
|
+
tagStack.pop(); // Closing bold
|
|
5176
|
+
}
|
|
5177
|
+
else {
|
|
5178
|
+
tagStack.push('bold'); // Opening bold
|
|
5179
|
+
}
|
|
4891
5180
|
}
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
tag: word.tag,
|
|
4900
|
-
}));
|
|
4901
|
-
this.wordWithMeta.set(plainWords);
|
|
4902
|
-
}
|
|
4903
|
-
onWordClick(wordData) {
|
|
4904
|
-
const currentMsg = this.message();
|
|
4905
|
-
if (!currentMsg)
|
|
4906
|
-
return;
|
|
4907
|
-
const clickedWord = 'isHighlighted' in wordData
|
|
4908
|
-
? { word: wordData.word, index: wordData.index, messageId: currentMsg.messageId }
|
|
4909
|
-
: { ...wordData, messageId: currentMsg.messageId };
|
|
4910
|
-
this.wordClicked.emit(clickedWord);
|
|
4911
|
-
}
|
|
4912
|
-
startAudioPlayback() {
|
|
4913
|
-
const audioElement = this.audioElement();
|
|
4914
|
-
if (!audioElement)
|
|
4915
|
-
return;
|
|
4916
|
-
try {
|
|
4917
|
-
audioElement.play();
|
|
4918
|
-
console.log('Audio playing');
|
|
5181
|
+
else if (token === '*') {
|
|
5182
|
+
if (tagStack.length > 0 && tagStack[tagStack.length - 1] === 'italic') {
|
|
5183
|
+
tagStack.pop(); // Closing italic
|
|
5184
|
+
}
|
|
5185
|
+
else {
|
|
5186
|
+
tagStack.push('italic'); // Opening italic
|
|
5187
|
+
}
|
|
4919
5188
|
}
|
|
4920
|
-
|
|
4921
|
-
|
|
5189
|
+
else {
|
|
5190
|
+
// It's a word (including punctuation)
|
|
5191
|
+
const currentTag = tagStack.length > 0 ? tagStack[tagStack.length - 1] : '';
|
|
5192
|
+
result.push({ word: token, tag: currentTag });
|
|
4922
5193
|
}
|
|
4923
|
-
// Important: we move it to playing status immediately to avoid re-triggering the effect
|
|
4924
|
-
this.updateAudioStatus('playing');
|
|
4925
|
-
}
|
|
4926
|
-
debug() {
|
|
4927
|
-
console.log('debug', this.wordWithMeta());
|
|
4928
5194
|
}
|
|
4929
|
-
|
|
4930
|
-
|
|
5195
|
+
// Note: This implementation assumes correctly matched opening and closing tags.
|
|
5196
|
+
// Unclosed tags at the end of the string will be ignored.
|
|
5197
|
+
return result;
|
|
4931
5198
|
}
|
|
4932
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageContentDisplayer, decorators: [{
|
|
4933
|
-
type: Component,
|
|
4934
|
-
args: [{ selector: 'message-content-displayer', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"audio-text-sync-container\" [class.is-active]=\"message().isActive\">\n <i (click)=\"onPlayMessage()\" class=\"play-button\">\n @switch (iconState()) { \n @case ('loading') { <i class=\"spin-animation pi pi-spinner-dotted\"></i> } \n @case ('playing') { <i class=\"pi pi-volume-up\"></i> }\n @case ('paused') { <i class=\"pi pi-pause-circle\"></i> } \n @case ('finished') { <i class=\"pi pi-play-circle\" style=\"color: white\"></i> } \n @case ('pending') { <i class=\"pi pi-angle-right\"></i> }\n @case ('playable') { <i class=\"pi pi-play-circle\" style=\"color: white\"></i> } \n }\n </i>\n\n <!-- Display transcription with highlighting -->\n <p style=\"width: 100%\" [class]=\"'text-content ' + classTag()\">\n @for (word of wordWithMeta(); track trackByIndex($index, word)) {\n <span\n [class]=\"word.tag\"\n [class.highlight]=\"word.isHighlighted\"\n [class.marked]=\"word.marked\"\n (click)=\"onWordClick(word)\"\n [attr.id]=\"'word-' + message().messageId + '-' + word.index\"\n >{{ word.word }}\n </span>\n }\n </p>\n</div>\n@if (message().imageUrl) {\n<div class=\"tour-image-container\" style=\"display: flex; justify-content: center\">\n <img [src]=\"message().imageUrl\" alt=\"Tour Image\" style=\"max-width: 220px; height: auto\" />\n</div>\n}\n", styles: [":host{display:block}.audio-text-sync-container{display:flex;align-items:flex-start;gap:2px;align-items:center;transition:background-color .3s ease}.audio-text-sync-container.is-active{background-color:#3cd8ff1a;border-radius:8px;padding-left:4px}.play-button{cursor:pointer;display:flex;align-items:center;justify-content:center;min-width:24px;margin-top:4px}.play-button:hover{opacity:.8}.text-content{flex:1}.highlight{background-color:var(--highlight-bg-color, rgb(60, 216, 255));border-radius:4px}::ng-deep .marked{position:relative;color:#f8bfbc}::ng-deep .marked:before{content:\"\";z-index:-2;left:-.1em;top:.1em;border-width:2px;border-style:solid;border-color:#f27068;position:absolute;border-right-color:transparent;width:100%;height:1em;transform:rotate(2deg);opacity:.7;border-radius:50%;padding:.1em .25em}::ng-deep .marked:after{content:\"\";z-index:-1;left:-.1em;top:.4em;padding:.1em .25em;border-width:2px;border-style:solid;border-color:#f27068;border-left-color:transparent;border-top-color:transparent;position:absolute;width:100%;height:1em;transform:rotate(-1deg);opacity:.7;border-radius:50%}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.em{font-style:italic;color:var(--chat-message-italic-color, rgb(184, 208, 252))}.strong{font-weight:700;color:inherit}.italic{font-style:italic;color:var(--chat-message-italic-color, rgb(252, 198, 184))}.em_strong{font-weight:700;font-style:italic;color:inherit}\n"] }]
|
|
4935
|
-
}], ctorParameters: () => [], propDecorators: { message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: true }] }], markWord: [{ type: i0.Input, args: [{ isSignal: true, alias: "markWord", required: false }] }], highlightColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlightColor", required: false }] }], hostHighlightColor: [{
|
|
4936
|
-
type: HostBinding,
|
|
4937
|
-
args: ['style.--highlight-bg-color']
|
|
4938
|
-
}], playAudio: [{ type: i0.Output, args: ["playAudio"] }], audioCompleted: [{ type: i0.Output, args: ["audioCompleted"] }], audioStatusChanged: [{ type: i0.Output, args: ["audioStatusChanged"] }], wordClicked: [{ type: i0.Output, args: ["wordClicked"] }] } });
|
|
4939
5199
|
|
|
4940
5200
|
/**
|
|
4941
|
-
* @
|
|
5201
|
+
* @Component
|
|
4942
5202
|
*
|
|
4943
5203
|
* @description
|
|
4944
|
-
*
|
|
4945
|
-
* It manages a queue of messages and handles the generation and playback of audio for each message.
|
|
5204
|
+
* Variante "atada al chat" de `MessageContentDisplayer`.
|
|
4946
5205
|
*
|
|
4947
|
-
*
|
|
5206
|
+
* A diferencia del `MessageContentDisplayer` (que es agnóstico y se comunica
|
|
5207
|
+
* exclusivamente mediante `@Output()` para poder reutilizarse fuera del chat),
|
|
5208
|
+
* este componente **inyecta directamente** el `MessageOrchestrationService`
|
|
5209
|
+
* (instancia local provista por `ChatMessageOrchestratorComponent`) y le reporta
|
|
5210
|
+
* los cambios de estado de audio sin emitir eventos.
|
|
5211
|
+
*
|
|
5212
|
+
* Punto clave del refactor:
|
|
5213
|
+
* - NO mantiene un `localAudioStatus`. El `iconState` deriva ÚNICAMENTE del
|
|
5214
|
+
* input `message()`, que a su vez baja del signal del orquestador. Esto elimina
|
|
5215
|
+
* el bug donde el icono/estado de una burbuja anterior se quedaba en "playing"
|
|
5216
|
+
* al activar otro mensaje, porque el estado local divergía del global.
|
|
5217
|
+
*
|
|
5218
|
+
* @see MessageOrchestrationService
|
|
5219
|
+
* @see MessageContentDisplayer (versión desacoplada / reutilizable)
|
|
4948
5220
|
*/
|
|
4949
|
-
class
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
this.conversationService = inject(ConversationService);
|
|
4953
|
-
this.chatMonitorService = inject(ChatMonitorService);
|
|
4954
|
-
this.userService = inject(UserService);
|
|
4955
|
-
this.messagesStateService = inject(MessagesStateService);
|
|
4956
|
-
// State Signals
|
|
4957
|
-
this.messages = signal([], ...(ngDevMode ? [{ debugName: "messages" }] : /* istanbul ignore next */ []));
|
|
4958
|
-
this.messageRole = signal(ChatRole.User, ...(ngDevMode ? [{ debugName: "messageRole" }] : /* istanbul ignore next */ []));
|
|
4959
|
-
this.messageToPlay = signal(null, ...(ngDevMode ? [{ debugName: "messageToPlay" }] : /* istanbul ignore next */ []));
|
|
4960
|
-
// Public Signals for components to consume
|
|
4961
|
-
this.messagesSignal = this.messages.asReadonly();
|
|
4962
|
-
this.messageToPlay$ = this.messageToPlay.asReadonly();
|
|
4963
|
-
// Audio queue management
|
|
4964
|
-
this.audioQueue = [];
|
|
4965
|
-
this.isGenerating = signal(false, ...(ngDevMode ? [{ debugName: "isGenerating" }] : /* istanbul ignore next */ []));
|
|
4966
|
-
this.currentPlayingIndex = signal(null, ...(ngDevMode ? [{ debugName: "currentPlayingIndex" }] : /* istanbul ignore next */ []));
|
|
4967
|
-
this.preGenerationInProgress = signal(false, ...(ngDevMode ? [{ debugName: "preGenerationInProgress" }] : /* istanbul ignore next */ []));
|
|
4968
|
-
console.log('MessageOrchestrationService initialized');
|
|
5221
|
+
class MessageContentReactive {
|
|
5222
|
+
get hostHighlightColor() {
|
|
5223
|
+
return this.highlightColor();
|
|
4969
5224
|
}
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
}
|
|
4995
|
-
return newMsg;
|
|
4996
|
-
});
|
|
4997
|
-
});
|
|
5225
|
+
constructor() {
|
|
5226
|
+
// Inputs
|
|
5227
|
+
this.message = input.required(...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
|
|
5228
|
+
/** Índice del fragmento dentro del array de multiMessages del orquestador. */
|
|
5229
|
+
this.index = input.required(...(ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []));
|
|
5230
|
+
this.markWord = input(...(ngDevMode ? [undefined, { debugName: "markWord" }] : /* istanbul ignore next */ []));
|
|
5231
|
+
this.highlightColor = input(...(ngDevMode ? [undefined, { debugName: "highlightColor" }] : /* istanbul ignore next */ []));
|
|
5232
|
+
// Services (atados al chat — fuente de verdad)
|
|
5233
|
+
this.orchestrationService = inject(MessageOrchestrationService);
|
|
5234
|
+
this.audioTextSyncService = inject(AudioTextSyncService);
|
|
5235
|
+
this.destroyRef = inject(DestroyRef);
|
|
5236
|
+
// Signal States (solo presentación de palabras / karaoke)
|
|
5237
|
+
this.wordWithMeta = signal([], ...(ngDevMode ? [{ debugName: "wordWithMeta" }] : /* istanbul ignore next */ []));
|
|
5238
|
+
this.wordsWithMetaState = [];
|
|
5239
|
+
this.lastHasTranscription = false;
|
|
5240
|
+
this.audioElement = signal(null, ...(ngDevMode ? [{ debugName: "audioElement" }] : /* istanbul ignore next */ []));
|
|
5241
|
+
/**
|
|
5242
|
+
* Estado del icono derivado ÚNICAMENTE del input `message()`.
|
|
5243
|
+
* No hay estado local: el orquestador es la única fuente de verdad.
|
|
5244
|
+
*/
|
|
5245
|
+
this.iconState = computed(() => {
|
|
5246
|
+
const msg = this.message();
|
|
5247
|
+
if (msg?.isLoading) {
|
|
5248
|
+
return 'loading';
|
|
4998
5249
|
}
|
|
4999
|
-
|
|
5000
|
-
|
|
5250
|
+
const status = msg?.audioStatus;
|
|
5251
|
+
if (status === 'playing') {
|
|
5252
|
+
return 'playing';
|
|
5001
5253
|
}
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5254
|
+
if (status === 'stopped') {
|
|
5255
|
+
return 'paused';
|
|
5256
|
+
}
|
|
5257
|
+
if (status === 'finished') {
|
|
5258
|
+
return 'finished';
|
|
5259
|
+
}
|
|
5260
|
+
if (status === 'pending') {
|
|
5261
|
+
return 'pending';
|
|
5262
|
+
}
|
|
5263
|
+
if (msg?.audioUrl) {
|
|
5264
|
+
return 'playable';
|
|
5265
|
+
}
|
|
5266
|
+
return 'idle';
|
|
5267
|
+
}, ...(ngDevMode ? [{ debugName: "iconState" }] : /* istanbul ignore next */ []));
|
|
5268
|
+
this.isPendingAudio = computed(() => this.message()?.audioStatus === 'pending', ...(ngDevMode ? [{ debugName: "isPendingAudio" }] : /* istanbul ignore next */ []));
|
|
5269
|
+
this.messageText = computed(() => {
|
|
5270
|
+
const msg = this.message();
|
|
5271
|
+
return msg?.text || msg?.content || 'N/A';
|
|
5272
|
+
}, ...(ngDevMode ? [{ debugName: "messageText" }] : /* istanbul ignore next */ []));
|
|
5273
|
+
this.classTag = computed(() => this.message()?.tag, ...(ngDevMode ? [{ debugName: "classTag" }] : /* istanbul ignore next */ []));
|
|
5274
|
+
this.transcriptionTimestamps = computed(() => {
|
|
5275
|
+
const msg = this.message();
|
|
5276
|
+
if (!msg || !msg.transcription) {
|
|
5277
|
+
return [];
|
|
5278
|
+
}
|
|
5279
|
+
return matchTranscription(msg.text || msg.content, msg.transcription.words);
|
|
5280
|
+
}, ...(ngDevMode ? [{ debugName: "transcriptionTimestamps" }] : /* istanbul ignore next */ []));
|
|
5281
|
+
this.hasTranscription = computed(() => this.transcriptionTimestamps().length > 0, ...(ngDevMode ? [{ debugName: "hasTranscription" }] : /* istanbul ignore next */ []));
|
|
5282
|
+
// Re-inicializa audio/palabras cuando cambia el mensaje, su audio o su transcripción.
|
|
5283
|
+
effect(() => {
|
|
5284
|
+
const currentMsg = this.message();
|
|
5285
|
+
if (!currentMsg)
|
|
5286
|
+
return;
|
|
5287
|
+
const hasTranscription = this.hasTranscription();
|
|
5288
|
+
const idChanged = currentMsg.messageId !== this.currentMessageId;
|
|
5289
|
+
const audioUrlChanged = currentMsg.audioUrl !== this.lastAudioUrl;
|
|
5290
|
+
const transcriptionChanged = hasTranscription !== this.lastHasTranscription;
|
|
5291
|
+
if (idChanged || audioUrlChanged || transcriptionChanged) {
|
|
5292
|
+
this.currentMessageId = currentMsg.messageId;
|
|
5293
|
+
this.lastAudioUrl = currentMsg.audioUrl;
|
|
5294
|
+
this.lastHasTranscription = hasTranscription;
|
|
5295
|
+
this.initializeBasedOnMessage(currentMsg);
|
|
5296
|
+
}
|
|
5297
|
+
});
|
|
5298
|
+
// Cuando el orquestador marca este fragmento como 'pending', se reproduce.
|
|
5299
|
+
effect(() => {
|
|
5300
|
+
if (this.isPendingAudio()) {
|
|
5301
|
+
this.startAudioPlayback();
|
|
5022
5302
|
}
|
|
5023
5303
|
});
|
|
5304
|
+
effect(() => {
|
|
5305
|
+
this.wordsWithMetaState = this.wordWithMeta();
|
|
5306
|
+
});
|
|
5024
5307
|
}
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
}
|
|
5031
|
-
else {
|
|
5032
|
-
console.warn('Audio completed but currentPlayingIndex is null');
|
|
5033
|
-
}
|
|
5034
|
-
this.conversationService.currentAudioStatus.set({ message, completed: true });
|
|
5035
|
-
this.currentPlayingIndex.set(null);
|
|
5036
|
-
this.messageToPlay.set(null);
|
|
5037
|
-
if (this.audioQueue.length > 0) {
|
|
5038
|
-
this.processNextInQueue();
|
|
5039
|
-
}
|
|
5308
|
+
trackByIndex(index, item) {
|
|
5309
|
+
return index;
|
|
5310
|
+
}
|
|
5311
|
+
ngOnDestroy() {
|
|
5312
|
+
this.cleanupAudio();
|
|
5040
5313
|
}
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
console.log(`Updating audio status for index ${index} to ${status}. MessageId: ${message.messageId}`);
|
|
5052
|
-
if (status === 'playing') {
|
|
5053
|
-
if (message.messageId) {
|
|
5054
|
-
this.messagesStateService.setActiveMessage(message.messageId, index);
|
|
5314
|
+
onPlayMessage() {
|
|
5315
|
+
const currentMsg = this.message();
|
|
5316
|
+
const audioElement = this.audioElement();
|
|
5317
|
+
if (audioElement) {
|
|
5318
|
+
if (audioElement.paused) {
|
|
5319
|
+
this.startAudioPlayback();
|
|
5320
|
+
}
|
|
5321
|
+
else {
|
|
5322
|
+
this.audioTextSyncService.pauseAudio(audioElement);
|
|
5323
|
+
this.reportStatus('stopped');
|
|
5055
5324
|
}
|
|
5056
|
-
// Update local state: set this one as active, others as inactive
|
|
5057
|
-
this.messages.update((messages) => messages.map((m, idx) => ({
|
|
5058
|
-
...m,
|
|
5059
|
-
isActive: idx === index,
|
|
5060
|
-
// Also update the current one's status
|
|
5061
|
-
...(idx === index ? { audioStatus: status } : {}),
|
|
5062
|
-
})));
|
|
5063
|
-
}
|
|
5064
|
-
else {
|
|
5065
|
-
// For other statuses (stopped, finished), preserve isActive and update status
|
|
5066
|
-
this.changeStates(index, { ...message, audioStatus: status });
|
|
5067
5325
|
}
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
this.
|
|
5071
|
-
audioStatus: status,
|
|
5072
|
-
});
|
|
5326
|
+
else if (currentMsg?.audioUrl) {
|
|
5327
|
+
this.initializeAudio(currentMsg.audioUrl);
|
|
5328
|
+
this.startAudioPlayback();
|
|
5073
5329
|
}
|
|
5330
|
+
// Si no hay audioUrl, el orquestador lo generará vía la cola (status 'pending').
|
|
5074
5331
|
}
|
|
5332
|
+
onWordClick(wordData) {
|
|
5333
|
+
const currentMsg = this.message();
|
|
5334
|
+
if (!currentMsg)
|
|
5335
|
+
return;
|
|
5336
|
+
this.orchestrationService.wordClicked({
|
|
5337
|
+
word: wordData.word,
|
|
5338
|
+
index: wordData.index,
|
|
5339
|
+
messageId: currentMsg.messageId,
|
|
5340
|
+
});
|
|
5341
|
+
}
|
|
5342
|
+
// --- Reporte de estado directo al orquestador (sin @Output) ---
|
|
5075
5343
|
/**
|
|
5076
|
-
*
|
|
5077
|
-
*
|
|
5078
|
-
*
|
|
5079
|
-
* @param wordData - The `WordData` object containing information about the clicked word.
|
|
5344
|
+
* Reporta el nuevo estado de audio al orquestador, que es la fuente de verdad.
|
|
5345
|
+
* Guarda contra reportes redundantes (el evento `timeupdate` se dispara en cada tick).
|
|
5080
5346
|
*/
|
|
5081
|
-
|
|
5082
|
-
this.
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
const messages = this.messages();
|
|
5086
|
-
if (messages && messages.length > 0) {
|
|
5087
|
-
this.audioQueue = messages
|
|
5088
|
-
.map((m, index) => ({ status: m.audioStatus, index }))
|
|
5089
|
-
.filter((item) => item.status !== 'finished' && item.status !== 'skip')
|
|
5090
|
-
.map((item) => item.index);
|
|
5091
|
-
console.log('Audio queue initialized:', this.audioQueue);
|
|
5347
|
+
reportStatus(status) {
|
|
5348
|
+
const currentMsg = this.message();
|
|
5349
|
+
if (currentMsg && currentMsg.audioStatus !== status) {
|
|
5350
|
+
this.orchestrationService.updateAudioStatus(currentMsg, status, this.index());
|
|
5092
5351
|
}
|
|
5093
5352
|
}
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5353
|
+
// --- Audio lifecycle ---
|
|
5354
|
+
initializeBasedOnMessage(msg) {
|
|
5355
|
+
if (msg.audioUrl) {
|
|
5356
|
+
this.initializeAudio(msg.audioUrl);
|
|
5097
5357
|
}
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5358
|
+
if (this.hasTranscription()) {
|
|
5359
|
+
const firstState = this.transcriptionTimestamps()?.map((word, index) => ({
|
|
5360
|
+
word: word.word,
|
|
5361
|
+
index,
|
|
5362
|
+
isHighlighted: false,
|
|
5363
|
+
tag: '',
|
|
5364
|
+
marked: word.word.toLowerCase() == this.markWord()?.toLowerCase(),
|
|
5365
|
+
}));
|
|
5366
|
+
this.wordWithMeta.set(firstState);
|
|
5102
5367
|
}
|
|
5103
|
-
|
|
5104
|
-
|
|
5368
|
+
else {
|
|
5369
|
+
this.initializePlainTextWords(msg.text || msg.content || '');
|
|
5105
5370
|
}
|
|
5106
|
-
|
|
5107
|
-
|
|
5371
|
+
}
|
|
5372
|
+
initializeAudio(audioUrl) {
|
|
5373
|
+
let audio = this.audioElement();
|
|
5374
|
+
if (!audio) {
|
|
5375
|
+
audio = new Audio(audioUrl);
|
|
5376
|
+
this.audioElement.set(audio);
|
|
5377
|
+
this.setupAudioEventListeners(audio);
|
|
5378
|
+
}
|
|
5379
|
+
else if (audio.src && audioUrl && !audio.src.endsWith(audioUrl)) {
|
|
5380
|
+
audio.src = audioUrl;
|
|
5108
5381
|
}
|
|
5109
|
-
this.preGenerateNextIfNeeded();
|
|
5110
5382
|
}
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5383
|
+
setupAudioEventListeners(audioElement) {
|
|
5384
|
+
fromEvent(audioElement, 'timeupdate')
|
|
5385
|
+
.pipe(takeUntilDestroyed(this.destroyRef), map(() => audioElement.currentTime || 0))
|
|
5386
|
+
.subscribe((currentTime) => {
|
|
5387
|
+
if (this.hasTranscription()) {
|
|
5388
|
+
const wordsAndTimestamps = [];
|
|
5389
|
+
this.transcriptionTimestamps()?.forEach((word, index) => {
|
|
5390
|
+
wordsAndTimestamps.push({ ...word, ...this.wordsWithMetaState[index], index });
|
|
5391
|
+
});
|
|
5392
|
+
const updatedWords = wordsAndTimestamps.map((word, index) => ({
|
|
5393
|
+
word: word.word,
|
|
5394
|
+
index,
|
|
5395
|
+
isHighlighted: currentTime >= word.start - 0.15 && currentTime < word.end + 0.15,
|
|
5396
|
+
tag: word.tag,
|
|
5397
|
+
marked: word.word.toLowerCase() == this.markWord()?.toLowerCase(),
|
|
5398
|
+
}));
|
|
5399
|
+
this.wordWithMeta.set(updatedWords);
|
|
5119
5400
|
}
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
this.
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5401
|
+
});
|
|
5402
|
+
fromEvent(audioElement, 'play')
|
|
5403
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
5404
|
+
.subscribe(() => {
|
|
5405
|
+
this.reportStatus('playing');
|
|
5406
|
+
});
|
|
5407
|
+
fromEvent(audioElement, 'pause')
|
|
5408
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
5409
|
+
.subscribe(() => {
|
|
5410
|
+
const resetWords = this.wordWithMeta().map((word) => ({ ...word, isHighlighted: false }));
|
|
5411
|
+
this.wordWithMeta.set(resetWords);
|
|
5412
|
+
if (audioElement.currentTime !== audioElement.duration) {
|
|
5413
|
+
this.reportStatus('stopped');
|
|
5129
5414
|
}
|
|
5130
|
-
|
|
5131
|
-
|
|
5415
|
+
});
|
|
5416
|
+
fromEvent(audioElement, 'ended')
|
|
5417
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
5418
|
+
.subscribe(() => {
|
|
5419
|
+
const resetWords = this.wordWithMeta().map((word) => ({ ...word, isHighlighted: false }));
|
|
5420
|
+
this.wordWithMeta.set(resetWords);
|
|
5421
|
+
const currentMsg = this.message();
|
|
5422
|
+
if (currentMsg) {
|
|
5423
|
+
// El servicio marca 'finished' y avanza la cola en un solo paso.
|
|
5424
|
+
this.orchestrationService.audioCompleted(currentMsg);
|
|
5132
5425
|
}
|
|
5133
|
-
}
|
|
5426
|
+
});
|
|
5134
5427
|
}
|
|
5135
|
-
|
|
5136
|
-
const
|
|
5137
|
-
if (
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
this.messageToPlay.set(messageAudio);
|
|
5142
|
-
this.currentPlayingIndex.set(index);
|
|
5428
|
+
startAudioPlayback() {
|
|
5429
|
+
const audioElement = this.audioElement();
|
|
5430
|
+
if (!audioElement)
|
|
5431
|
+
return;
|
|
5432
|
+
const currentMsg = this.message();
|
|
5433
|
+
if (!currentMsg)
|
|
5143
5434
|
return;
|
|
5144
|
-
}
|
|
5145
|
-
const message = { ...messages[index], isLoading: true };
|
|
5146
|
-
this.changeStates(index, message);
|
|
5147
5435
|
try {
|
|
5148
|
-
|
|
5149
|
-
messageAudio.isLoading = false;
|
|
5150
|
-
messageAudio.audioStatus = 'pending';
|
|
5151
|
-
this.changeStates(index, messageAudio);
|
|
5152
|
-
this.chatMonitorService.logMessageAudioWillPlay(messageAudio);
|
|
5153
|
-
this.messageToPlay.set(messageAudio);
|
|
5436
|
+
this.audioTextSyncService.playAudio(audioElement, currentMsg);
|
|
5154
5437
|
}
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
this.changeStates(index, message);
|
|
5438
|
+
catch (error) {
|
|
5439
|
+
console.error('Error playing audio:', error);
|
|
5158
5440
|
}
|
|
5159
|
-
this.
|
|
5160
|
-
}
|
|
5161
|
-
changeStates(index, messageAudio) {
|
|
5162
|
-
this.messages.update((messages) => {
|
|
5163
|
-
const newMessages = [...messages];
|
|
5164
|
-
newMessages[index] = { ...messages[index], ...messageAudio };
|
|
5165
|
-
return newMessages;
|
|
5166
|
-
});
|
|
5441
|
+
this.reportStatus('playing');
|
|
5167
5442
|
}
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
const
|
|
5174
|
-
|
|
5175
|
-
const speechAudio = await this.conversationService.getTTSFile(ttsObject);
|
|
5176
|
-
message = extractAudioAndTranscription(message, speechAudio);
|
|
5177
|
-
return message;
|
|
5178
|
-
}
|
|
5179
|
-
finally {
|
|
5180
|
-
this.isGenerating.set(false);
|
|
5443
|
+
cleanupAudio() {
|
|
5444
|
+
const audioElement = this.audioElement();
|
|
5445
|
+
if (audioElement) {
|
|
5446
|
+
this.audioTextSyncService.pauseAudio(audioElement);
|
|
5447
|
+
audioElement.src = '';
|
|
5448
|
+
const resetWords = this.wordWithMeta().map((word) => ({ ...word, isHighlighted: false }));
|
|
5449
|
+
this.wordWithMeta.set(resetWords);
|
|
5181
5450
|
}
|
|
5182
5451
|
}
|
|
5183
|
-
|
|
5184
|
-
|
|
5452
|
+
initializePlainTextWords(text) {
|
|
5453
|
+
const words = extractTags(text);
|
|
5454
|
+
const plainWords = words.map((word, index) => ({
|
|
5455
|
+
word: word.word,
|
|
5456
|
+
index: index,
|
|
5457
|
+
isHighlighted: false,
|
|
5458
|
+
tag: word.tag,
|
|
5459
|
+
}));
|
|
5460
|
+
this.wordWithMeta.set(plainWords);
|
|
5461
|
+
}
|
|
5462
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageContentReactive, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
5463
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MessageContentReactive, isStandalone: true, selector: "message-content-reactive", inputs: { message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: true, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: true, transformFunction: null }, markWord: { classPropertyName: "markWord", publicName: "markWord", isSignal: true, isRequired: false, transformFunction: null }, highlightColor: { classPropertyName: "highlightColor", publicName: "highlightColor", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.--highlight-bg-color": "this.hostHighlightColor" } }, ngImport: i0, template: "<div class=\"audio-text-sync-container\" [class.is-active]=\"message().isActive\">\n <i (click)=\"onPlayMessage()\" class=\"play-button\">\n @switch (iconState()) {\n @case ('loading') { <i class=\"spin-animation pi pi-spinner-dotted\"></i> }\n @case ('playing') { <i class=\"pi pi-volume-up\"></i> }\n @case ('paused') { <i class=\"pi pi-pause-circle\"></i> }\n @case ('finished') { <i class=\"pi pi-play-circle\" style=\"color: white\"></i> }\n @case ('pending') { <i class=\"pi pi-angle-right\"></i> }\n @case ('playable') { <i class=\"pi pi-play-circle\" style=\"color: white\"></i> }\n }\n </i>\n\n <!-- Display transcription with highlighting -->\n <p style=\"width: 100%\" [class]=\"'text-content ' + classTag()\">\n @for (word of wordWithMeta(); track trackByIndex($index, word)) {\n <span\n [class]=\"word.tag\"\n [class.highlight]=\"word.isHighlighted\"\n [class.marked]=\"word.marked\"\n (click)=\"onWordClick(word)\"\n [attr.id]=\"'word-' + message().messageId + '-' + word.index\"\n >{{ word.word }}\n </span>\n }\n </p>\n</div>\n@if (message().imageUrl) {\n<div class=\"tour-image-container\" style=\"display: flex; justify-content: center\">\n <img [src]=\"message().imageUrl\" alt=\"Tour Image\" style=\"max-width: 220px; height: auto\" />\n</div>\n}\n", styles: [":host{display:block}.audio-text-sync-container{display:flex;align-items:flex-start;gap:2px;align-items:center;transition:background-color .3s ease}.audio-text-sync-container.is-active{background-color:#3cd8ff1a;border-radius:8px;padding-left:4px}.play-button{cursor:pointer;display:flex;align-items:center;justify-content:center;min-width:24px;margin-top:4px}.play-button:hover{opacity:.8}.text-content{flex:1}.highlight{background-color:var(--highlight-bg-color, rgb(60, 216, 255));border-radius:4px}::ng-deep .marked{position:relative;color:#f8bfbc}::ng-deep .marked:before{content:\"\";z-index:-2;left:-.1em;top:.1em;border-width:2px;border-style:solid;border-color:#f27068;position:absolute;border-right-color:transparent;width:100%;height:1em;transform:rotate(2deg);opacity:.7;border-radius:50%;padding:.1em .25em}::ng-deep .marked:after{content:\"\";z-index:-1;left:-.1em;top:.4em;padding:.1em .25em;border-width:2px;border-style:solid;border-color:#f27068;border-left-color:transparent;border-top-color:transparent;position:absolute;width:100%;height:1em;transform:rotate(-1deg);opacity:.7;border-radius:50%}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.em{font-style:italic;color:var(--chat-message-italic-color, rgb(184, 208, 252))}.strong{font-weight:700;color:inherit}.italic{font-style:italic;color:var(--chat-message-italic-color, rgb(252, 198, 184))}.em_strong{font-weight:700;font-style:italic;color:inherit}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
5185
5464
|
}
|
|
5186
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type:
|
|
5187
|
-
type:
|
|
5188
|
-
|
|
5465
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageContentReactive, decorators: [{
|
|
5466
|
+
type: Component,
|
|
5467
|
+
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"] }]
|
|
5468
|
+
}], 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: [{
|
|
5469
|
+
type: HostBinding,
|
|
5470
|
+
args: ['style.--highlight-bg-color']
|
|
5471
|
+
}] } });
|
|
5189
5472
|
|
|
5190
5473
|
/**
|
|
5191
5474
|
* @Component
|
|
@@ -5194,8 +5477,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
5194
5477
|
* This component orchestrates the display and audio playback of chat messages.
|
|
5195
5478
|
* It uses the `MessageOrchestrationService` to manage the message queue and audio playback.
|
|
5196
5479
|
*
|
|
5480
|
+
* Children (`MessageContentReactive`) inject the same `MessageOrchestrationService`
|
|
5481
|
+
* instance directly and report their state to it, so this component no longer needs
|
|
5482
|
+
* to relay per-segment audio events via `@Output()`.
|
|
5483
|
+
*
|
|
5197
5484
|
* @see MessageOrchestrationService
|
|
5198
|
-
* @see
|
|
5485
|
+
* @see MessageContentReactive
|
|
5199
5486
|
*/
|
|
5200
5487
|
class ChatMessageOrchestratorComponent {
|
|
5201
5488
|
constructor() {
|
|
@@ -5208,54 +5495,25 @@ class ChatMessageOrchestratorComponent {
|
|
|
5208
5495
|
this.messages = input.required(...(ngDevMode ? [{ debugName: "messages" }] : /* istanbul ignore next */ []));
|
|
5209
5496
|
/**
|
|
5210
5497
|
* The `ChatRole` of the messages' author.
|
|
5211
|
-
*/
|
|
5212
|
-
this.messageRole = input.required(...(ngDevMode ? [{ debugName: "messageRole" }] : /* istanbul ignore next */ []));
|
|
5213
|
-
// Outputs
|
|
5214
|
-
/**
|
|
5215
|
-
* Emits when the audio for a message has completed playing.
|
|
5216
|
-
*/
|
|
5217
|
-
this.audioCompleted = output();
|
|
5218
|
-
// Signals
|
|
5219
|
-
this.messagesSignal = this.orchestrationService.messagesSignal;
|
|
5220
|
-
effect(() => {
|
|
5221
|
-
this.orchestrationService.startOrchestration(this.messages(), this.messageRole());
|
|
5222
|
-
});
|
|
5223
|
-
}
|
|
5224
|
-
/**
|
|
5225
|
-
* @description
|
|
5226
|
-
* Handles the `audioCompleted` event from the `MessageContentDisplayer`.
|
|
5227
|
-
*
|
|
5228
|
-
* @param message - The `MessageContent` object whose audio has finished playing.
|
|
5229
|
-
*/
|
|
5230
|
-
onAudioCompleted(message) {
|
|
5231
|
-
this.orchestrationService.audioCompleted(message);
|
|
5232
|
-
this.audioCompleted.emit(message);
|
|
5233
|
-
}
|
|
5234
|
-
/**
|
|
5235
|
-
* @description
|
|
5236
|
-
* Handles the `audioStatusChanged` event from the `MessageContentDisplayer`.
|
|
5237
|
-
*
|
|
5238
|
-
* @param event - The event object containing the `MessageContent` and its new `AudioStatus`.
|
|
5239
|
-
* @param index - The index of the message in the array.
|
|
5240
|
-
*/
|
|
5241
|
-
onAudioStatusChanged(event, index) {
|
|
5242
|
-
this.orchestrationService.updateAudioStatus(event.message, event.status, index);
|
|
5243
|
-
}
|
|
5244
|
-
/**
|
|
5245
|
-
* @description
|
|
5246
|
-
* Handles the `wordClicked` event from the `MessageContentDisplayer`.
|
|
5247
|
-
*
|
|
5248
|
-
* @param wordData - The `WordData` object containing information about the clicked word.
|
|
5249
|
-
*/
|
|
5250
|
-
onWordClicked(wordData) {
|
|
5251
|
-
this.orchestrationService.wordClicked(wordData);
|
|
5498
|
+
*/
|
|
5499
|
+
this.messageRole = input.required(...(ngDevMode ? [{ debugName: "messageRole" }] : /* istanbul ignore next */ []));
|
|
5500
|
+
// Outputs
|
|
5501
|
+
/**
|
|
5502
|
+
* Emits when the audio for a message has completed playing.
|
|
5503
|
+
*/
|
|
5504
|
+
this.audioCompleted = output();
|
|
5505
|
+
// Signals
|
|
5506
|
+
this.messagesSignal = this.orchestrationService.messagesSignal;
|
|
5507
|
+
effect(() => {
|
|
5508
|
+
this.orchestrationService.startOrchestration(this.messages(), this.messageRole());
|
|
5509
|
+
});
|
|
5252
5510
|
}
|
|
5253
5511
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMessageOrchestratorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
5254
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ChatMessageOrchestratorComponent, isStandalone: true, selector: "dc-message-orchestrator", inputs: { messages: { classPropertyName: "messages", publicName: "messages", isSignal: true, isRequired: true, transformFunction: null }, messageRole: { classPropertyName: "messageRole", publicName: "messageRole", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { audioCompleted: "audioCompleted" }, providers: [MessageOrchestrationService], ngImport: i0, template: "<div class=\"message-orchestrator-container\">\n @for (message of messagesSignal(); track $index) {\n <message-content-
|
|
5512
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ChatMessageOrchestratorComponent, isStandalone: true, selector: "dc-message-orchestrator", inputs: { messages: { classPropertyName: "messages", publicName: "messages", isSignal: true, isRequired: true, transformFunction: null }, messageRole: { classPropertyName: "messageRole", publicName: "messageRole", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { audioCompleted: "audioCompleted" }, providers: [MessageOrchestrationService], ngImport: i0, template: "<div class=\"message-orchestrator-container\">\n @for (message of messagesSignal(); track $index) {\n <message-content-reactive [message]=\"message\" [index]=\"$index\" />\n }\n</div>\n", styles: [":host{display:block}.word-options-popup{position:absolute;border:1px solid #ccc;background:#fff;padding:5px;z-index:1000}message-content-displayer{text-shadow:1px 1px 4px rgba(255,255,255,.4)}\n"], dependencies: [{ kind: "component", type: MessageContentReactive, selector: "message-content-reactive", inputs: ["message", "index", "markWord", "highlightColor"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
5255
5513
|
}
|
|
5256
5514
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatMessageOrchestratorComponent, decorators: [{
|
|
5257
5515
|
type: Component,
|
|
5258
|
-
args: [{ selector: 'dc-message-orchestrator', imports: [
|
|
5516
|
+
args: [{ selector: 'dc-message-orchestrator', imports: [MessageContentReactive], changeDetection: ChangeDetectionStrategy.OnPush, providers: [MessageOrchestrationService], template: "<div class=\"message-orchestrator-container\">\n @for (message of messagesSignal(); track $index) {\n <message-content-reactive [message]=\"message\" [index]=\"$index\" />\n }\n</div>\n", styles: [":host{display:block}.word-options-popup{position:absolute;border:1px solid #ccc;background:#fff;padding:5px;z-index:1000}message-content-displayer{text-shadow:1px 1px 4px rgba(255,255,255,.4)}\n"] }]
|
|
5259
5517
|
}], ctorParameters: () => [], propDecorators: { messages: [{ type: i0.Input, args: [{ isSignal: true, alias: "messages", required: true }] }], messageRole: [{ type: i0.Input, args: [{ isSignal: true, alias: "messageRole", required: true }] }], audioCompleted: [{ type: i0.Output, args: ["audioCompleted"] }] } });
|
|
5260
5518
|
|
|
5261
5519
|
const EMOJI_TO_LABEL = new Map(MoodStateOptions.map((o) => [o.emoji, o.label]));
|
|
@@ -5487,6 +5745,7 @@ class DCConversationUserChatSettingsComponent {
|
|
|
5487
5745
|
superHearing: true,
|
|
5488
5746
|
fixGrammar: true,
|
|
5489
5747
|
autoTranslate: true,
|
|
5748
|
+
micMode: true,
|
|
5490
5749
|
}, ...(ngDevMode ? [{ debugName: "showFeature" }] : /* istanbul ignore next */ []));
|
|
5491
5750
|
this.onSettingsChange = output();
|
|
5492
5751
|
this.closeClicked = output();
|
|
@@ -5509,6 +5768,7 @@ class DCConversationUserChatSettingsComponent {
|
|
|
5509
5768
|
assistantMessageTask: [false],
|
|
5510
5769
|
saveConversations: [false],
|
|
5511
5770
|
multilingualHearing: [false],
|
|
5771
|
+
micMode: ['hands-free'],
|
|
5512
5772
|
voice: ['random'],
|
|
5513
5773
|
model: createAIModelFormGroup(),
|
|
5514
5774
|
});
|
|
@@ -5547,7 +5807,7 @@ class DCConversationUserChatSettingsComponent {
|
|
|
5547
5807
|
}
|
|
5548
5808
|
}
|
|
5549
5809
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DCConversationUserChatSettingsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
5550
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DCConversationUserChatSettingsComponent, isStandalone: true, selector: "dc-chat-settings-dialog", inputs: { showFeature: { classPropertyName: "showFeature", publicName: "showFeature", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSettingsChange: "onSettingsChange", closeClicked: "closeClicked", settingsApplied: "settingsApplied" }, viewQueries: [{ propertyName: "tooltipRef", first: true, predicate: ["tooltipRef"], descendants: true }], ngImport: i0, template: "<div class=\"dialog-container\">\n <form [formGroup]=\"form\">\n @if (showFeature().synthVoice) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"synthVoice\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.synthVoice.disabled\">Escuchar Voz</span>\n <br />\n <small>Desmarca si solo quieres leer texto</small>\n </p>\n </div>\n } @if (showFeature().highlightWords) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"highlightWords\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Narraci\u00F3n de texto</span>\n <br />\n <small>Remarca las palabras como se van pronuncionando</small>\n </p>\n </div>\n } @if (showFeature().speed) {\n <div class=\"settings-section\">\n <p>\n Velocidad ({{ form.controls.speed.value | speedDisplay }})\n <br />\n\n <!-- <p-rating formControlName=\"speed\" iconOnClass=\"pi pi-step-forward\" iconOffClass=\"pi pi-minus\" /> -->\n\n <p-select\n id=\"speed\"\n [options]=\"speedOptions\"\n formControlName=\"speed\"\n [placeholder]=\"'Select Language'\"\n optionLabel=\"label\"\n optionValue=\"value\"></p-select>\n </p>\n </div>\n } @if (showFeature().realTime) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"realTime\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.realTime.disabled\">Tiempo real</span>\n <br />\n <small>No tienes que presionar el microphono, comenzar\u00E1 a grabar en cuanto la AI termine de hablar, cierra el chat para finalizar conversaci\u00F3n.</small>\n </p>\n </div>\n } @if (showFeature().realTime) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"repeatRecording\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Reproducir mi grabaci\u00F3n</span>\n <br />\n <small>Escucha tu dialogo, despu\u00E9s de grabar, te ayudar\u00E1 a notar tus errores.</small>\n </p>\n </div>\n } @if (showFeature().superHearing) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"superHearing\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Super O\u00EDdo \uD83E\uDDBE</span>\n <br />\n <small>Tu audio se procesa en el servidor para mejor efectividad, si no usa el navegador.</small>\n </p>\n </div>\n }\n\n <!-- @if (showFeature().fixGrammar) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"fixGrammar\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.fixGrammar.disabled\">Corregir gram\u00E1tica</span>\n <br />\n <small>La ai corrige tu forma de hablar/escribir y te retrolimenta de tus errores</small>\n </p>\n </div>\n } -->\n\n <!-- @if (showFeature().autoTranslate) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"autoTranslate\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.autoTranslate.disabled\">Mostrar Traducciones </span>\n <br />\n <small>Texto adicional con la traducci\u00F3n</small>\n </p>\n </div>\n } -->\n\n @if (showFeature().autoTranslate) {\n <div class=\"voice-selection\">\n <span>Voz Preferencial:</span>\n <br />\n <p-radioButton value=\"random\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Aleatorio</label>\n <p-radioButton value=\"randomMan\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Hombre</label>\n <p-radioButton value=\"randomWoman\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Mujer</label>\n </div>\n }\n\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"userMessageTask\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.userMessageTask.disabled\">Procesar mensajes de usuario</span>\n <br />\n <small>Correcciones, mejoras, retroalimentaci\u00F3n y sugerencias en el texto del usuario </small>\n </p>\n </div>\n\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"assistantMessageTask\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.assistantMessageTask.disabled\">Procesar mensajes de asistente</span>\n <br />\n <small>Correcciones, traducciones, pensamientos adicionales y m\u00E1s ayuda en el texto del asistente </small>\n </p>\n </div>\n \n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"saveConversations\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.saveConversations.disabled\">Guardar conversaciones</span>\n <br />\n <small>Si marcas esta casilla, las conversaciones se guardar\u00E1n en tu historial.</small>\n </p>\n </div>\n\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"multilingualHearing\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.multilingualHearing.disabled\">Escuchar en varios idiomas</span>\n <br />\n <small>El asistente podr\u00E1 entender y responder en varios idiomas, pero puede confundir idiomas si no lo pronuncias claramente.</small>\n </p>\n </div>\n\n @if(userService.isAdmin()) {\n <div>\n <hr />\n <b> <i style=\"color: rgb(255, 149, 0)\" class=\"pi pi-key\"></i> Admin Section</b>\n <br />\n\n <b>Modelo:</b>\n\n <dc-model-selector [modelForm]=\"form.controls.model\"></dc-model-selector>\n </div>\n }\n\n <div class=\"button-group\">\n <p-button (click)=\"saveSettings()\" label=\"Guardar cambios\"></p-button>\n <p-button (click)=\"close()\" label=\"Cancelar\" styleClass=\"p-button-secondary\"></p-button>\n </div>\n </form>\n</div>\n", styles: [".dialog-container{padding:20px;border-radius:8px;min-width:300px;max-width:500px;max-height:80vh;overflow-y:auto}.dialog-content{margin:20px 0}.dialog-actions{display:flex;justify-content:flex-end}.settings-section{margin-bottom:20px}.settings-section label{display:block;margin-bottom:5px;font-weight:700}.settings-section small{display:block;margin-top:2px}.voice-selection{margin:15px 0}.voice-selection label{margin-right:15px}.button-group{margin-top:20px;display:flex;gap:10px;justify-content:flex-end}button{padding:8px 16px;border-radius:4px;border:none;cursor:pointer}button:first-child{background-color:#007bff}button:last-child{background-color:#6c757d}.space{margin-left:3px}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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: 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: SliderModule }, { kind: "ngmodule", type: RadioButtonModule }, { kind: "component", type: i3.RadioButton, selector: "p-radioButton, p-radiobutton, p-radio-button", inputs: ["value", "tabindex", "inputId", "ariaLabelledBy", "ariaLabel", "styleClass", "autofocus", "binary", "variant", "size"], outputs: ["onClick", "onFocus", "onBlur"] }, { 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: RatingModule }, { kind: "ngmodule", type: TableModule }, { kind: "ngmodule", type: BadgeModule }, { kind: "ngmodule", type: SkeletonModule }, { kind: "ngmodule", type: TooltipModule }, { kind: "component", type: ModelSelectorComponent, selector: "dc-model-selector", inputs: ["modelForm", "shortForm"] }, { 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: "pipe", type: SpeedDescPipe, name: "speedDisplay" }] }); }
|
|
5810
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DCConversationUserChatSettingsComponent, isStandalone: true, selector: "dc-chat-settings-dialog", inputs: { showFeature: { classPropertyName: "showFeature", publicName: "showFeature", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSettingsChange: "onSettingsChange", closeClicked: "closeClicked", settingsApplied: "settingsApplied" }, viewQueries: [{ propertyName: "tooltipRef", first: true, predicate: ["tooltipRef"], descendants: true }], ngImport: i0, template: "<div class=\"dialog-container\">\n <form [formGroup]=\"form\">\n @if (showFeature().synthVoice) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"synthVoice\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.synthVoice.disabled\">Escuchar Voz</span>\n <br />\n <small>Desmarca si solo quieres leer texto</small>\n </p>\n </div>\n } @if (showFeature().highlightWords) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"highlightWords\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Narraci\u00F3n de texto</span>\n <br />\n <small>Remarca las palabras como se van pronuncionando</small>\n </p>\n </div>\n } @if (showFeature().speed) {\n <div class=\"settings-section\">\n <p>\n Velocidad ({{ form.controls.speed.value | speedDisplay }})\n <br />\n\n <!-- <p-rating formControlName=\"speed\" iconOnClass=\"pi pi-step-forward\" iconOffClass=\"pi pi-minus\" /> -->\n\n <p-select\n id=\"speed\"\n [options]=\"speedOptions\"\n formControlName=\"speed\"\n [placeholder]=\"'Select Language'\"\n optionLabel=\"label\"\n optionValue=\"value\"></p-select>\n </p>\n </div>\n } @if (showFeature().realTime) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"realTime\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.realTime.disabled\">Tiempo real</span>\n <br />\n <small>No tienes que presionar el microphono, comenzar\u00E1 a grabar en cuanto la AI termine de hablar, cierra el chat para finalizar conversaci\u00F3n.</small>\n </p>\n </div>\n } @if (showFeature().realTime) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"repeatRecording\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Reproducir mi grabaci\u00F3n</span>\n <br />\n <small>Escucha tu dialogo, despu\u00E9s de grabar, te ayudar\u00E1 a notar tus errores.</small>\n </p>\n </div>\n } @if (showFeature().micMode) {\n <div class=\"settings-section\">\n <span class=\"section-title\">Modo de micr\u00F3fono \uD83C\uDF99\uFE0F</span>\n <small class=\"section-hint\">Elige c\u00F3mo prefieres grabar tu voz durante la conversaci\u00F3n.</small>\n\n <div class=\"mic-options\">\n <label\n class=\"mic-option\"\n [class.selected]=\"form.controls.micMode.value === 'hands-free'\">\n <p-radioButton value=\"hands-free\" formControlName=\"micMode\" inputId=\"micHandsFree\"></p-radioButton>\n <span class=\"mic-option-text\">\n <span class=\"mic-option-title\">Manos libres (continuo)</span>\n <small>Presionas una vez y el micr\u00F3fono se mantiene grabando; env\u00EDa al detectar silencio y vuelve a escuchar.</small>\n </span>\n </label>\n\n <label\n class=\"mic-option\"\n [class.selected]=\"form.controls.micMode.value === 'push-to-talk'\">\n <p-radioButton value=\"push-to-talk\" formControlName=\"micMode\" inputId=\"micPushToTalk\"></p-radioButton>\n <span class=\"mic-option-text\">\n <span class=\"mic-option-title\">Presionar para hablar</span>\n <small>Presionas para grabar, hablas y se env\u00EDa al detectar silencio (o vuelves a presionar). No reanuda solo.</small>\n </span>\n </label>\n\n <label\n class=\"mic-option\"\n [class.selected]=\"form.controls.micMode.value === 'manual'\">\n <p-radioButton value=\"manual\" formControlName=\"micMode\" inputId=\"micManual\"></p-radioButton>\n <span class=\"mic-option-text\">\n <span class=\"mic-option-title\">Grabaci\u00F3n manual \u00B7 sin prisa \uD83D\uDC22</span>\n <small>Presionas para grabar y el silencio no env\u00EDa nada: puedes pensar y pausar todo lo que quieras (m\u00E1x 60s, con contador y barra). Presionas de nuevo para enviar.</small>\n </span>\n </label>\n </div>\n </div>\n } @if (showFeature().superHearing) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"superHearing\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Super O\u00EDdo \uD83E\uDDBE</span>\n <br />\n <small>Tu audio se procesa en el servidor para mejor efectividad, si no usa el navegador.</small>\n </p>\n </div>\n }\n\n <!-- @if (showFeature().fixGrammar) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"fixGrammar\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.fixGrammar.disabled\">Corregir gram\u00E1tica</span>\n <br />\n <small>La ai corrige tu forma de hablar/escribir y te retrolimenta de tus errores</small>\n </p>\n </div>\n } -->\n\n <!-- @if (showFeature().autoTranslate) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"autoTranslate\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.autoTranslate.disabled\">Mostrar Traducciones </span>\n <br />\n <small>Texto adicional con la traducci\u00F3n</small>\n </p>\n </div>\n } -->\n\n @if (showFeature().autoTranslate) {\n <div class=\"voice-selection\">\n <span>Voz Preferencial:</span>\n <br />\n <p-radioButton value=\"random\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Aleatorio</label>\n <p-radioButton value=\"randomMan\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Hombre</label>\n <p-radioButton value=\"randomWoman\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Mujer</label>\n </div>\n }\n\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"userMessageTask\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.userMessageTask.disabled\">Procesar mensajes de usuario</span>\n <br />\n <small>Correcciones, mejoras, retroalimentaci\u00F3n y sugerencias en el texto del usuario </small>\n </p>\n </div>\n\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"assistantMessageTask\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.assistantMessageTask.disabled\">Procesar mensajes de asistente</span>\n <br />\n <small>Correcciones, traducciones, pensamientos adicionales y m\u00E1s ayuda en el texto del asistente </small>\n </p>\n </div>\n \n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"saveConversations\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.saveConversations.disabled\">Guardar conversaciones</span>\n <br />\n <small>Si marcas esta casilla, las conversaciones se guardar\u00E1n en tu historial.</small>\n </p>\n </div>\n\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"multilingualHearing\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.multilingualHearing.disabled\">Escuchar en varios idiomas</span>\n <br />\n <small>El asistente podr\u00E1 entender y responder en varios idiomas, pero puede confundir idiomas si no lo pronuncias claramente.</small>\n </p>\n </div>\n\n @if(userService.isAdmin()) {\n <div>\n <hr />\n <b> <i style=\"color: rgb(255, 149, 0)\" class=\"pi pi-key\"></i> Admin Section</b>\n <br />\n\n <b>Modelo:</b>\n\n <dc-model-selector [modelForm]=\"form.controls.model\"></dc-model-selector>\n </div>\n }\n\n <div class=\"button-group\">\n <p-button (click)=\"saveSettings()\" label=\"Guardar cambios\"></p-button>\n <p-button (click)=\"close()\" label=\"Cancelar\" styleClass=\"p-button-secondary\"></p-button>\n </div>\n </form>\n</div>\n", styles: [".dialog-container{padding:20px;border-radius:8px;min-width:300px;max-width:500px;max-height:80vh;overflow-y:auto}.dialog-content{margin:20px 0}.dialog-actions{display:flex;justify-content:flex-end}.settings-section{margin-bottom:20px}.settings-section label{display:block;margin-bottom:5px;font-weight:700}.settings-section small{display:block;margin-top:2px}.voice-selection{margin:15px 0}.voice-selection label{margin-right:15px}.button-group{margin-top:20px;display:flex;gap:10px;justify-content:flex-end}button{padding:8px 16px;border-radius:4px;border:none;cursor:pointer}button:first-child{background-color:#007bff}button:last-child{background-color:#6c757d}.space{margin-left:3px}.section-title{display:block;font-weight:700;margin-bottom:2px}.section-hint{display:block;margin-bottom:10px;color:var(--p-text-muted-color, #6c757d)}.mic-options{display:flex;flex-direction:column;gap:10px}.mic-option{display:flex;align-items:flex-start;gap:10px;padding:12px;border:1px solid var(--p-content-border-color, #dee2e6);border-radius:8px;cursor:pointer;transition:border-color .15s,background-color .15s;font-weight:400;margin-bottom:0}.mic-option:hover{border-color:var(--p-primary-color, #007bff)}.mic-option.selected{border-color:var(--p-primary-color, #007bff);background-color:var(--p-primary-50, rgba(0, 123, 255, .06))}.mic-option-text{display:flex;flex-direction:column}.mic-option-title{font-weight:600;margin-bottom:2px}.mic-option small{display:block;color:var(--p-text-muted-color, #6c757d);line-height:1.3}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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: 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: SliderModule }, { kind: "ngmodule", type: RadioButtonModule }, { kind: "component", type: i3.RadioButton, selector: "p-radioButton, p-radiobutton, p-radio-button", inputs: ["value", "tabindex", "inputId", "ariaLabelledBy", "ariaLabel", "styleClass", "autofocus", "binary", "variant", "size"], outputs: ["onClick", "onFocus", "onBlur"] }, { 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: RatingModule }, { kind: "ngmodule", type: TableModule }, { kind: "ngmodule", type: BadgeModule }, { kind: "ngmodule", type: SkeletonModule }, { kind: "ngmodule", type: TooltipModule }, { kind: "component", type: ModelSelectorComponent, selector: "dc-model-selector", inputs: ["modelForm", "shortForm"] }, { 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: "pipe", type: SpeedDescPipe, name: "speedDisplay" }] }); }
|
|
5551
5811
|
}
|
|
5552
5812
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DCConversationUserChatSettingsComponent, decorators: [{
|
|
5553
5813
|
type: Component,
|
|
@@ -5565,175 +5825,268 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
5565
5825
|
TooltipModule,
|
|
5566
5826
|
ModelSelectorComponent,
|
|
5567
5827
|
SelectModule,
|
|
5568
|
-
], template: "<div class=\"dialog-container\">\n <form [formGroup]=\"form\">\n @if (showFeature().synthVoice) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"synthVoice\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.synthVoice.disabled\">Escuchar Voz</span>\n <br />\n <small>Desmarca si solo quieres leer texto</small>\n </p>\n </div>\n } @if (showFeature().highlightWords) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"highlightWords\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Narraci\u00F3n de texto</span>\n <br />\n <small>Remarca las palabras como se van pronuncionando</small>\n </p>\n </div>\n } @if (showFeature().speed) {\n <div class=\"settings-section\">\n <p>\n Velocidad ({{ form.controls.speed.value | speedDisplay }})\n <br />\n\n <!-- <p-rating formControlName=\"speed\" iconOnClass=\"pi pi-step-forward\" iconOffClass=\"pi pi-minus\" /> -->\n\n <p-select\n id=\"speed\"\n [options]=\"speedOptions\"\n formControlName=\"speed\"\n [placeholder]=\"'Select Language'\"\n optionLabel=\"label\"\n optionValue=\"value\"></p-select>\n </p>\n </div>\n } @if (showFeature().realTime) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"realTime\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.realTime.disabled\">Tiempo real</span>\n <br />\n <small>No tienes que presionar el microphono, comenzar\u00E1 a grabar en cuanto la AI termine de hablar, cierra el chat para finalizar conversaci\u00F3n.</small>\n </p>\n </div>\n } @if (showFeature().realTime) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"repeatRecording\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Reproducir mi grabaci\u00F3n</span>\n <br />\n <small>Escucha tu dialogo, despu\u00E9s de grabar, te ayudar\u00E1 a notar tus errores.</small>\n </p>\n </div>\n } @if (showFeature().superHearing) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"superHearing\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Super O\u00EDdo \uD83E\uDDBE</span>\n <br />\n <small>Tu audio se procesa en el servidor para mejor efectividad, si no usa el navegador.</small>\n </p>\n </div>\n }\n\n <!-- @if (showFeature().fixGrammar) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"fixGrammar\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.fixGrammar.disabled\">Corregir gram\u00E1tica</span>\n <br />\n <small>La ai corrige tu forma de hablar/escribir y te retrolimenta de tus errores</small>\n </p>\n </div>\n } -->\n\n <!-- @if (showFeature().autoTranslate) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"autoTranslate\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.autoTranslate.disabled\">Mostrar Traducciones </span>\n <br />\n <small>Texto adicional con la traducci\u00F3n</small>\n </p>\n </div>\n } -->\n\n @if (showFeature().autoTranslate) {\n <div class=\"voice-selection\">\n <span>Voz Preferencial:</span>\n <br />\n <p-radioButton value=\"random\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Aleatorio</label>\n <p-radioButton value=\"randomMan\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Hombre</label>\n <p-radioButton value=\"randomWoman\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Mujer</label>\n </div>\n }\n\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"userMessageTask\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.userMessageTask.disabled\">Procesar mensajes de usuario</span>\n <br />\n <small>Correcciones, mejoras, retroalimentaci\u00F3n y sugerencias en el texto del usuario </small>\n </p>\n </div>\n\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"assistantMessageTask\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.assistantMessageTask.disabled\">Procesar mensajes de asistente</span>\n <br />\n <small>Correcciones, traducciones, pensamientos adicionales y m\u00E1s ayuda en el texto del asistente </small>\n </p>\n </div>\n \n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"saveConversations\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.saveConversations.disabled\">Guardar conversaciones</span>\n <br />\n <small>Si marcas esta casilla, las conversaciones se guardar\u00E1n en tu historial.</small>\n </p>\n </div>\n\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"multilingualHearing\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.multilingualHearing.disabled\">Escuchar en varios idiomas</span>\n <br />\n <small>El asistente podr\u00E1 entender y responder en varios idiomas, pero puede confundir idiomas si no lo pronuncias claramente.</small>\n </p>\n </div>\n\n @if(userService.isAdmin()) {\n <div>\n <hr />\n <b> <i style=\"color: rgb(255, 149, 0)\" class=\"pi pi-key\"></i> Admin Section</b>\n <br />\n\n <b>Modelo:</b>\n\n <dc-model-selector [modelForm]=\"form.controls.model\"></dc-model-selector>\n </div>\n }\n\n <div class=\"button-group\">\n <p-button (click)=\"saveSettings()\" label=\"Guardar cambios\"></p-button>\n <p-button (click)=\"close()\" label=\"Cancelar\" styleClass=\"p-button-secondary\"></p-button>\n </div>\n </form>\n</div>\n", styles: [".dialog-container{padding:20px;border-radius:8px;min-width:300px;max-width:500px;max-height:80vh;overflow-y:auto}.dialog-content{margin:20px 0}.dialog-actions{display:flex;justify-content:flex-end}.settings-section{margin-bottom:20px}.settings-section label{display:block;margin-bottom:5px;font-weight:700}.settings-section small{display:block;margin-top:2px}.voice-selection{margin:15px 0}.voice-selection label{margin-right:15px}.button-group{margin-top:20px;display:flex;gap:10px;justify-content:flex-end}button{padding:8px 16px;border-radius:4px;border:none;cursor:pointer}button:first-child{background-color:#007bff}button:last-child{background-color:#6c757d}.space{margin-left:3px}\n"] }]
|
|
5828
|
+
], template: "<div class=\"dialog-container\">\n <form [formGroup]=\"form\">\n @if (showFeature().synthVoice) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"synthVoice\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.synthVoice.disabled\">Escuchar Voz</span>\n <br />\n <small>Desmarca si solo quieres leer texto</small>\n </p>\n </div>\n } @if (showFeature().highlightWords) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"highlightWords\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Narraci\u00F3n de texto</span>\n <br />\n <small>Remarca las palabras como se van pronuncionando</small>\n </p>\n </div>\n } @if (showFeature().speed) {\n <div class=\"settings-section\">\n <p>\n Velocidad ({{ form.controls.speed.value | speedDisplay }})\n <br />\n\n <!-- <p-rating formControlName=\"speed\" iconOnClass=\"pi pi-step-forward\" iconOffClass=\"pi pi-minus\" /> -->\n\n <p-select\n id=\"speed\"\n [options]=\"speedOptions\"\n formControlName=\"speed\"\n [placeholder]=\"'Select Language'\"\n optionLabel=\"label\"\n optionValue=\"value\"></p-select>\n </p>\n </div>\n } @if (showFeature().realTime) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"realTime\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.realTime.disabled\">Tiempo real</span>\n <br />\n <small>No tienes que presionar el microphono, comenzar\u00E1 a grabar en cuanto la AI termine de hablar, cierra el chat para finalizar conversaci\u00F3n.</small>\n </p>\n </div>\n } @if (showFeature().realTime) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"repeatRecording\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Reproducir mi grabaci\u00F3n</span>\n <br />\n <small>Escucha tu dialogo, despu\u00E9s de grabar, te ayudar\u00E1 a notar tus errores.</small>\n </p>\n </div>\n } @if (showFeature().micMode) {\n <div class=\"settings-section\">\n <span class=\"section-title\">Modo de micr\u00F3fono \uD83C\uDF99\uFE0F</span>\n <small class=\"section-hint\">Elige c\u00F3mo prefieres grabar tu voz durante la conversaci\u00F3n.</small>\n\n <div class=\"mic-options\">\n <label\n class=\"mic-option\"\n [class.selected]=\"form.controls.micMode.value === 'hands-free'\">\n <p-radioButton value=\"hands-free\" formControlName=\"micMode\" inputId=\"micHandsFree\"></p-radioButton>\n <span class=\"mic-option-text\">\n <span class=\"mic-option-title\">Manos libres (continuo)</span>\n <small>Presionas una vez y el micr\u00F3fono se mantiene grabando; env\u00EDa al detectar silencio y vuelve a escuchar.</small>\n </span>\n </label>\n\n <label\n class=\"mic-option\"\n [class.selected]=\"form.controls.micMode.value === 'push-to-talk'\">\n <p-radioButton value=\"push-to-talk\" formControlName=\"micMode\" inputId=\"micPushToTalk\"></p-radioButton>\n <span class=\"mic-option-text\">\n <span class=\"mic-option-title\">Presionar para hablar</span>\n <small>Presionas para grabar, hablas y se env\u00EDa al detectar silencio (o vuelves a presionar). No reanuda solo.</small>\n </span>\n </label>\n\n <label\n class=\"mic-option\"\n [class.selected]=\"form.controls.micMode.value === 'manual'\">\n <p-radioButton value=\"manual\" formControlName=\"micMode\" inputId=\"micManual\"></p-radioButton>\n <span class=\"mic-option-text\">\n <span class=\"mic-option-title\">Grabaci\u00F3n manual \u00B7 sin prisa \uD83D\uDC22</span>\n <small>Presionas para grabar y el silencio no env\u00EDa nada: puedes pensar y pausar todo lo que quieras (m\u00E1x 60s, con contador y barra). Presionas de nuevo para enviar.</small>\n </span>\n </label>\n </div>\n </div>\n } @if (showFeature().superHearing) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"superHearing\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span>Super O\u00EDdo \uD83E\uDDBE</span>\n <br />\n <small>Tu audio se procesa en el servidor para mejor efectividad, si no usa el navegador.</small>\n </p>\n </div>\n }\n\n <!-- @if (showFeature().fixGrammar) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"fixGrammar\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.fixGrammar.disabled\">Corregir gram\u00E1tica</span>\n <br />\n <small>La ai corrige tu forma de hablar/escribir y te retrolimenta de tus errores</small>\n </p>\n </div>\n } -->\n\n <!-- @if (showFeature().autoTranslate) {\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"autoTranslate\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.autoTranslate.disabled\">Mostrar Traducciones </span>\n <br />\n <small>Texto adicional con la traducci\u00F3n</small>\n </p>\n </div>\n } -->\n\n @if (showFeature().autoTranslate) {\n <div class=\"voice-selection\">\n <span>Voz Preferencial:</span>\n <br />\n <p-radioButton value=\"random\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Aleatorio</label>\n <p-radioButton value=\"randomMan\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Hombre</label>\n <p-radioButton value=\"randomWoman\" formControlName=\"voice\"></p-radioButton>\n <label class=\"space\">Mujer</label>\n </div>\n }\n\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"userMessageTask\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.userMessageTask.disabled\">Procesar mensajes de usuario</span>\n <br />\n <small>Correcciones, mejoras, retroalimentaci\u00F3n y sugerencias en el texto del usuario </small>\n </p>\n </div>\n\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"assistantMessageTask\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.assistantMessageTask.disabled\">Procesar mensajes de asistente</span>\n <br />\n <small>Correcciones, traducciones, pensamientos adicionales y m\u00E1s ayuda en el texto del asistente </small>\n </p>\n </div>\n \n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"saveConversations\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.saveConversations.disabled\">Guardar conversaciones</span>\n <br />\n <small>Si marcas esta casilla, las conversaciones se guardar\u00E1n en tu historial.</small>\n </p>\n </div>\n\n <div class=\"settings-section\">\n <p>\n <p-checkbox class=\"mr-2\" formControlName=\"multilingualHearing\" [binary]=\"true\" inputId=\"binary\"></p-checkbox>\n <span [class.cross]=\"form.controls.multilingualHearing.disabled\">Escuchar en varios idiomas</span>\n <br />\n <small>El asistente podr\u00E1 entender y responder en varios idiomas, pero puede confundir idiomas si no lo pronuncias claramente.</small>\n </p>\n </div>\n\n @if(userService.isAdmin()) {\n <div>\n <hr />\n <b> <i style=\"color: rgb(255, 149, 0)\" class=\"pi pi-key\"></i> Admin Section</b>\n <br />\n\n <b>Modelo:</b>\n\n <dc-model-selector [modelForm]=\"form.controls.model\"></dc-model-selector>\n </div>\n }\n\n <div class=\"button-group\">\n <p-button (click)=\"saveSettings()\" label=\"Guardar cambios\"></p-button>\n <p-button (click)=\"close()\" label=\"Cancelar\" styleClass=\"p-button-secondary\"></p-button>\n </div>\n </form>\n</div>\n", styles: [".dialog-container{padding:20px;border-radius:8px;min-width:300px;max-width:500px;max-height:80vh;overflow-y:auto}.dialog-content{margin:20px 0}.dialog-actions{display:flex;justify-content:flex-end}.settings-section{margin-bottom:20px}.settings-section label{display:block;margin-bottom:5px;font-weight:700}.settings-section small{display:block;margin-top:2px}.voice-selection{margin:15px 0}.voice-selection label{margin-right:15px}.button-group{margin-top:20px;display:flex;gap:10px;justify-content:flex-end}button{padding:8px 16px;border-radius:4px;border:none;cursor:pointer}button:first-child{background-color:#007bff}button:last-child{background-color:#6c757d}.space{margin-left:3px}.section-title{display:block;font-weight:700;margin-bottom:2px}.section-hint{display:block;margin-bottom:10px;color:var(--p-text-muted-color, #6c757d)}.mic-options{display:flex;flex-direction:column;gap:10px}.mic-option{display:flex;align-items:flex-start;gap:10px;padding:12px;border:1px solid var(--p-content-border-color, #dee2e6);border-radius:8px;cursor:pointer;transition:border-color .15s,background-color .15s;font-weight:400;margin-bottom:0}.mic-option:hover{border-color:var(--p-primary-color, #007bff)}.mic-option.selected{border-color:var(--p-primary-color, #007bff);background-color:var(--p-primary-50, rgba(0, 123, 255, .06))}.mic-option-text{display:flex;flex-direction:column}.mic-option-title{font-weight:600;margin-bottom:2px}.mic-option small{display:block;color:var(--p-text-muted-color, #6c757d);line-height:1.3}\n"] }]
|
|
5569
5829
|
}], propDecorators: { showFeature: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFeature", required: false }] }], onSettingsChange: [{ type: i0.Output, args: ["onSettingsChange"] }], closeClicked: [{ type: i0.Output, args: ["closeClicked"] }], settingsApplied: [{ type: i0.Output, args: ["settingsApplied"] }], tooltipRef: [{
|
|
5570
5830
|
type: ViewChild,
|
|
5571
5831
|
args: ['tooltipRef']
|
|
5572
5832
|
}] } });
|
|
5573
5833
|
|
|
5574
|
-
|
|
5834
|
+
// Removed AudioState as it's replaced by AudioStatus from agent.models
|
|
5835
|
+
class MessageContentDisplayer {
|
|
5836
|
+
get hostHighlightColor() {
|
|
5837
|
+
return this.highlightColor();
|
|
5838
|
+
}
|
|
5575
5839
|
constructor() {
|
|
5576
|
-
//
|
|
5577
|
-
this.
|
|
5578
|
-
this.
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
this.
|
|
5840
|
+
// Inputs
|
|
5841
|
+
this.message = input.required(...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
|
|
5842
|
+
this.markWord = input(...(ngDevMode ? [undefined, { debugName: "markWord" }] : /* istanbul ignore next */ []));
|
|
5843
|
+
this.highlightColor = input(...(ngDevMode ? [undefined, { debugName: "highlightColor" }] : /* istanbul ignore next */ []));
|
|
5844
|
+
// Outputs
|
|
5845
|
+
this.playAudio = output();
|
|
5846
|
+
this.audioCompleted = output();
|
|
5847
|
+
this.audioStatusChanged = output();
|
|
5848
|
+
this.wordClicked = output();
|
|
5849
|
+
// Signal States
|
|
5850
|
+
this.wordWithMeta = signal([], ...(ngDevMode ? [{ debugName: "wordWithMeta" }] : /* istanbul ignore next */ []));
|
|
5851
|
+
this.localAudioStatus = signal(null, ...(ngDevMode ? [{ debugName: "localAudioStatus" }] : /* istanbul ignore next */ []));
|
|
5852
|
+
// Signals State computed from the input message
|
|
5853
|
+
this.wordsWithMetaState = [];
|
|
5854
|
+
this.lastHasTranscription = false;
|
|
5855
|
+
this.audioElement = signal(null, ...(ngDevMode ? [{ debugName: "audioElement" }] : /* istanbul ignore next */ []));
|
|
5582
5856
|
this.destroyRef = inject(DestroyRef);
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5857
|
+
this.iconState = computed(() => {
|
|
5858
|
+
const msg = this.message();
|
|
5859
|
+
if (msg?.isLoading) {
|
|
5860
|
+
return 'loading';
|
|
5861
|
+
}
|
|
5862
|
+
const status = this.localAudioStatus() || msg?.audioStatus;
|
|
5863
|
+
if (status === 'playing') {
|
|
5864
|
+
return 'playing';
|
|
5865
|
+
}
|
|
5866
|
+
if (status === 'stopped') {
|
|
5867
|
+
return 'paused';
|
|
5868
|
+
}
|
|
5869
|
+
if (status === 'finished') {
|
|
5870
|
+
return 'finished';
|
|
5871
|
+
}
|
|
5872
|
+
if (status === 'pending') {
|
|
5873
|
+
return 'pending';
|
|
5874
|
+
}
|
|
5875
|
+
if (msg?.audioUrl) {
|
|
5876
|
+
return 'playable';
|
|
5877
|
+
}
|
|
5878
|
+
return 'idle';
|
|
5879
|
+
}, ...(ngDevMode ? [{ debugName: "iconState" }] : /* istanbul ignore next */ []));
|
|
5880
|
+
this.isPendingAudio = computed(() => !!this.message() && this.message()?.audioStatus === 'pending', ...(ngDevMode ? [{ debugName: "isPendingAudio" }] : /* istanbul ignore next */ []));
|
|
5881
|
+
this.messageText = computed(() => {
|
|
5882
|
+
const msg = this.message();
|
|
5883
|
+
return msg?.text || msg?.content || 'N/A';
|
|
5884
|
+
}, ...(ngDevMode ? [{ debugName: "messageText" }] : /* istanbul ignore next */ []));
|
|
5885
|
+
this.classTag = computed(() => this.message()?.tag, ...(ngDevMode ? [{ debugName: "classTag" }] : /* istanbul ignore next */ []));
|
|
5886
|
+
// When message is updated, check is has transcriptions whisper format.
|
|
5887
|
+
this.transcriptionTimestamps = computed(() => {
|
|
5888
|
+
const msg = this.message();
|
|
5889
|
+
if (!msg || !msg.transcription) {
|
|
5890
|
+
return [];
|
|
5891
|
+
}
|
|
5892
|
+
return matchTranscription(msg.text || msg.content, msg.transcription.words);
|
|
5893
|
+
}, ...(ngDevMode ? [{ debugName: "transcriptionTimestamps" }] : /* istanbul ignore next */ []));
|
|
5894
|
+
// if transcriptionTimestamps is calculated then hasTranscription is true.
|
|
5895
|
+
this.hasTranscription = computed(() => {
|
|
5896
|
+
return this.transcriptionTimestamps().length > 0;
|
|
5897
|
+
}, ...(ngDevMode ? [{ debugName: "hasTranscription" }] : /* istanbul ignore next */ []));
|
|
5898
|
+
effect(() => {
|
|
5899
|
+
const currentMsg = this.message();
|
|
5900
|
+
if (!currentMsg)
|
|
5901
|
+
return;
|
|
5902
|
+
const hasTranscription = this.hasTranscription();
|
|
5903
|
+
const idChanged = currentMsg.messageId !== this.currentMessageId;
|
|
5904
|
+
const audioUrlChanged = currentMsg.audioUrl !== this.lastAudioUrl;
|
|
5905
|
+
const transcriptionChanged = hasTranscription !== this.lastHasTranscription;
|
|
5906
|
+
if (idChanged || audioUrlChanged || transcriptionChanged) {
|
|
5907
|
+
this.currentMessageId = currentMsg.messageId;
|
|
5908
|
+
this.lastAudioUrl = currentMsg.audioUrl;
|
|
5909
|
+
this.lastHasTranscription = hasTranscription;
|
|
5910
|
+
this.localAudioStatus.set(null); // Reset local state on message/audio change
|
|
5911
|
+
this.initializeBasedOnMessage(currentMsg);
|
|
5912
|
+
}
|
|
5913
|
+
});
|
|
5914
|
+
effect(() => {
|
|
5915
|
+
if (this.isPendingAudio()) {
|
|
5916
|
+
this.startAudioPlayback();
|
|
5917
|
+
}
|
|
5918
|
+
});
|
|
5919
|
+
effect(() => {
|
|
5920
|
+
this.wordsWithMetaState = this.wordWithMeta();
|
|
5586
5921
|
});
|
|
5587
5922
|
}
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
// Create new signal and subject for this message if they don't exist
|
|
5598
|
-
if (!this.highlightedWordsSignalMap.has(messageId)) {
|
|
5599
|
-
this.highlightedWordsSignalMap.set(messageId, signal([]));
|
|
5923
|
+
trackByIndex(index, item) {
|
|
5924
|
+
return index;
|
|
5925
|
+
}
|
|
5926
|
+
ngOnDestroy() {
|
|
5927
|
+
this.cleanupAudio();
|
|
5928
|
+
}
|
|
5929
|
+
initializeBasedOnMessage(msg) {
|
|
5930
|
+
if (msg.audioUrl) {
|
|
5931
|
+
this.initializeAudio(msg.audioUrl);
|
|
5600
5932
|
}
|
|
5601
|
-
if (
|
|
5602
|
-
|
|
5933
|
+
if (this.hasTranscription()) {
|
|
5934
|
+
const wordsAndTimestamps = [];
|
|
5935
|
+
this.transcriptionTimestamps()?.forEach((word, index) => {
|
|
5936
|
+
wordsAndTimestamps.push({ ...word, ...this.wordsWithMetaState[index], index });
|
|
5937
|
+
});
|
|
5938
|
+
const firstState = this.transcriptionTimestamps()?.map((word, index) => ({
|
|
5939
|
+
word: word.word,
|
|
5940
|
+
index,
|
|
5941
|
+
isHighlighted: false,
|
|
5942
|
+
tag: '',
|
|
5943
|
+
marked: word.word.toLowerCase() == this.markWord()?.toLowerCase(),
|
|
5944
|
+
}));
|
|
5945
|
+
console.log('firstState', firstState);
|
|
5946
|
+
this.wordWithMeta.set(firstState);
|
|
5947
|
+
// Para mostrar las transcripciones iliminadas: el audio tiene que sonar y en la subscripción apareceran los cambios.
|
|
5603
5948
|
}
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
//
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
// Listen to timeupdate events
|
|
5949
|
+
else {
|
|
5950
|
+
this.initializePlainTextWords(msg.text || msg.content || '');
|
|
5951
|
+
}
|
|
5952
|
+
// Removal of shouldPlayAudio check - playback now handled by isPendingAudio effect
|
|
5953
|
+
}
|
|
5954
|
+
initializeAudio(audioUrl) {
|
|
5955
|
+
let audio = this.audioElement();
|
|
5956
|
+
if (!audio) {
|
|
5957
|
+
audio = new Audio(audioUrl);
|
|
5958
|
+
this.audioElement.set(audio);
|
|
5959
|
+
this.setupAudioEventListeners(audio);
|
|
5960
|
+
}
|
|
5961
|
+
else if (audio.src && audioUrl && !audio.src.endsWith(audioUrl)) {
|
|
5962
|
+
audio.src = audioUrl;
|
|
5963
|
+
}
|
|
5964
|
+
}
|
|
5965
|
+
setupAudioEventListeners(audioElement) {
|
|
5622
5966
|
fromEvent(audioElement, 'timeupdate')
|
|
5623
|
-
.pipe(takeUntilDestroyed(this.destroyRef),
|
|
5967
|
+
.pipe(takeUntilDestroyed(this.destroyRef), map(() => audioElement.currentTime || 0))
|
|
5624
5968
|
.subscribe((currentTime) => {
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
return
|
|
5969
|
+
if (currentTime === 0) {
|
|
5970
|
+
this.updateAudioStatus('playing');
|
|
5971
|
+
return;
|
|
5972
|
+
}
|
|
5973
|
+
if (this.hasTranscription()) {
|
|
5974
|
+
const wordsAndTimestamps = [];
|
|
5975
|
+
this.transcriptionTimestamps()?.forEach((word, index) => {
|
|
5976
|
+
wordsAndTimestamps.push({ ...word, ...this.wordsWithMetaState[index], index });
|
|
5977
|
+
});
|
|
5978
|
+
const updatedWords = wordsAndTimestamps.map((word, index) => ({
|
|
5628
5979
|
word: word.word,
|
|
5629
5980
|
index,
|
|
5630
|
-
isHighlighted,
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5981
|
+
isHighlighted: currentTime >= word.start - 0.15 && currentTime < word.end + 0.15,
|
|
5982
|
+
tag: word.tag,
|
|
5983
|
+
marked: word.word.toLowerCase() == this.markWord()?.toLowerCase(),
|
|
5984
|
+
}));
|
|
5985
|
+
this.wordWithMeta.set(updatedWords);
|
|
5986
|
+
}
|
|
5636
5987
|
});
|
|
5637
|
-
// Listen to ended event for cleanup
|
|
5638
5988
|
fromEvent(audioElement, 'ended')
|
|
5639
|
-
.pipe(takeUntilDestroyed(this.destroyRef)
|
|
5989
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
5640
5990
|
.subscribe(() => {
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5991
|
+
const resetWords = this.wordWithMeta().map((word) => ({ ...word, isHighlighted: false }));
|
|
5992
|
+
this.wordWithMeta.set(resetWords);
|
|
5993
|
+
const currentMsg = this.message();
|
|
5994
|
+
if (currentMsg) {
|
|
5995
|
+
// No direct mutation of input!
|
|
5996
|
+
this.updateAudioStatus('finished');
|
|
5997
|
+
this.audioCompleted.emit(currentMsg);
|
|
5998
|
+
}
|
|
5648
5999
|
});
|
|
5649
6000
|
}
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
if (
|
|
5656
|
-
//
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
cleanup$.next();
|
|
5660
|
-
cleanup$.complete();
|
|
5661
|
-
this.cleanup$Map.delete(messageId);
|
|
5662
|
-
}
|
|
5663
|
-
this.activeAudioMap.delete(messageId);
|
|
5664
|
-
// Reset state for this message
|
|
5665
|
-
const messageSignal = this.highlightedWordsSignalMap.get(messageId);
|
|
5666
|
-
const messageSubject = this.highlightedWords$Map.get(messageId);
|
|
5667
|
-
if (messageSignal) {
|
|
5668
|
-
messageSignal.set([]);
|
|
5669
|
-
}
|
|
5670
|
-
if (messageSubject) {
|
|
5671
|
-
messageSubject.next([]);
|
|
5672
|
-
}
|
|
6001
|
+
updateAudioStatus(status) {
|
|
6002
|
+
const currentMsg = this.message();
|
|
6003
|
+
// Update local state for immediate feedback
|
|
6004
|
+
this.localAudioStatus.set(status);
|
|
6005
|
+
// Only update if the status is actually changing relative to the current input
|
|
6006
|
+
if (currentMsg && currentMsg.audioStatus !== status) {
|
|
6007
|
+
// NOTE: We do NOT mutate currentMsg.audioStatus here.
|
|
6008
|
+
// The parent orchestration service will update the state and propagate it back via signals.
|
|
6009
|
+
this.audioStatusChanged.emit({ message: currentMsg, status });
|
|
5673
6010
|
}
|
|
5674
6011
|
}
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
this.
|
|
6012
|
+
cleanupAudio() {
|
|
6013
|
+
const audioElement = this.audioElement();
|
|
6014
|
+
if (audioElement) {
|
|
6015
|
+
audioElement.pause();
|
|
6016
|
+
audioElement.src = '';
|
|
6017
|
+
const resetWords = this.wordWithMeta().map((word) => ({ ...word, isHighlighted: false }));
|
|
6018
|
+
this.wordWithMeta.set(resetWords);
|
|
5682
6019
|
}
|
|
5683
|
-
// Clear all maps
|
|
5684
|
-
this.highlightedWordsSignalMap.clear();
|
|
5685
|
-
this.highlightedWords$Map.clear();
|
|
5686
|
-
this.cleanup$Map.clear();
|
|
5687
|
-
this.activeAudioMap.clear();
|
|
5688
6020
|
}
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
6021
|
+
onPlayMessage() {
|
|
6022
|
+
console.log('onPlayMessage');
|
|
6023
|
+
const currentMsg = this.message();
|
|
6024
|
+
const audioElement = this.audioElement();
|
|
6025
|
+
if (audioElement) {
|
|
6026
|
+
if (audioElement.paused) {
|
|
6027
|
+
this.startAudioPlayback();
|
|
6028
|
+
}
|
|
6029
|
+
else {
|
|
6030
|
+
audioElement.pause();
|
|
6031
|
+
this.updateAudioStatus('stopped');
|
|
6032
|
+
}
|
|
5697
6033
|
}
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
if (!this.highlightedWords$Map.has(messageId)) {
|
|
5707
|
-
this.highlightedWords$Map.set(messageId, new BehaviorSubject([]));
|
|
6034
|
+
else if (currentMsg?.audioUrl) {
|
|
6035
|
+
console.log('onPlayMessage initializeAudio with url');
|
|
6036
|
+
this.initializeAudio(currentMsg.audioUrl);
|
|
6037
|
+
this.startAudioPlayback();
|
|
6038
|
+
}
|
|
6039
|
+
else if (currentMsg) {
|
|
6040
|
+
console.log('onPlayMessage emit playAudio');
|
|
6041
|
+
this.playAudio.emit(currentMsg);
|
|
5708
6042
|
}
|
|
5709
|
-
return this.highlightedWords$Map.get(messageId).asObservable();
|
|
5710
6043
|
}
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
6044
|
+
initializePlainTextWords(text) {
|
|
6045
|
+
const words = extractTags(text);
|
|
6046
|
+
const plainWords = words.map((word, index) => ({
|
|
6047
|
+
word: word.word,
|
|
6048
|
+
index: index,
|
|
6049
|
+
isHighlighted: false,
|
|
6050
|
+
tag: word.tag,
|
|
6051
|
+
}));
|
|
6052
|
+
this.wordWithMeta.set(plainWords);
|
|
5719
6053
|
}
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
6054
|
+
onWordClick(wordData) {
|
|
6055
|
+
const currentMsg = this.message();
|
|
6056
|
+
if (!currentMsg)
|
|
6057
|
+
return;
|
|
6058
|
+
const clickedWord = 'isHighlighted' in wordData
|
|
6059
|
+
? { word: wordData.word, index: wordData.index, messageId: currentMsg.messageId }
|
|
6060
|
+
: { ...wordData, messageId: currentMsg.messageId };
|
|
6061
|
+
this.wordClicked.emit(clickedWord);
|
|
5727
6062
|
}
|
|
5728
|
-
|
|
5729
|
-
|
|
6063
|
+
startAudioPlayback() {
|
|
6064
|
+
const audioElement = this.audioElement();
|
|
6065
|
+
if (!audioElement)
|
|
6066
|
+
return;
|
|
6067
|
+
try {
|
|
6068
|
+
audioElement.play();
|
|
6069
|
+
console.log('Audio playing');
|
|
6070
|
+
}
|
|
6071
|
+
catch (error) {
|
|
6072
|
+
console.error('Error playing audio:', error);
|
|
6073
|
+
}
|
|
6074
|
+
// Important: we move it to playing status immediately to avoid re-triggering the effect
|
|
6075
|
+
this.updateAudioStatus('playing');
|
|
6076
|
+
}
|
|
6077
|
+
debug() {
|
|
6078
|
+
console.log('debug', this.wordWithMeta());
|
|
6079
|
+
}
|
|
6080
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageContentDisplayer, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6081
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MessageContentDisplayer, isStandalone: true, selector: "message-content-displayer", inputs: { message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: true, transformFunction: null }, markWord: { classPropertyName: "markWord", publicName: "markWord", isSignal: true, isRequired: false, transformFunction: null }, highlightColor: { classPropertyName: "highlightColor", publicName: "highlightColor", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { playAudio: "playAudio", audioCompleted: "audioCompleted", audioStatusChanged: "audioStatusChanged", wordClicked: "wordClicked" }, host: { properties: { "style.--highlight-bg-color": "this.hostHighlightColor" } }, ngImport: i0, template: "<div class=\"audio-text-sync-container\" [class.is-active]=\"message().isActive\">\n <i (click)=\"onPlayMessage()\" class=\"play-button\">\n @switch (iconState()) { \n @case ('loading') { <i class=\"spin-animation pi pi-spinner-dotted\"></i> } \n @case ('playing') { <i class=\"pi pi-volume-up\"></i> }\n @case ('paused') { <i class=\"pi pi-pause-circle\"></i> } \n @case ('finished') { <i class=\"pi pi-play-circle\" style=\"color: white\"></i> } \n @case ('pending') { <i class=\"pi pi-angle-right\"></i> }\n @case ('playable') { <i class=\"pi pi-play-circle\" style=\"color: white\"></i> } \n }\n </i>\n\n <!-- Display transcription with highlighting -->\n <p style=\"width: 100%\" [class]=\"'text-content ' + classTag()\">\n @for (word of wordWithMeta(); track trackByIndex($index, word)) {\n <span\n [class]=\"word.tag\"\n [class.highlight]=\"word.isHighlighted\"\n [class.marked]=\"word.marked\"\n (click)=\"onWordClick(word)\"\n [attr.id]=\"'word-' + message().messageId + '-' + word.index\"\n >{{ word.word }}\n </span>\n }\n </p>\n</div>\n@if (message().imageUrl) {\n<div class=\"tour-image-container\" style=\"display: flex; justify-content: center\">\n <img [src]=\"message().imageUrl\" alt=\"Tour Image\" style=\"max-width: 220px; height: auto\" />\n</div>\n}\n", styles: [":host{display:block}.audio-text-sync-container{display:flex;align-items:flex-start;gap:2px;align-items:center;transition:background-color .3s ease}.audio-text-sync-container.is-active{background-color:#3cd8ff1a;border-radius:8px;padding-left:4px}.play-button{cursor:pointer;display:flex;align-items:center;justify-content:center;min-width:24px;margin-top:4px}.play-button:hover{opacity:.8}.text-content{flex:1}.highlight{background-color:var(--highlight-bg-color, rgb(60, 216, 255));border-radius:4px}::ng-deep .marked{position:relative;color:#f8bfbc}::ng-deep .marked:before{content:\"\";z-index:-2;left:-.1em;top:.1em;border-width:2px;border-style:solid;border-color:#f27068;position:absolute;border-right-color:transparent;width:100%;height:1em;transform:rotate(2deg);opacity:.7;border-radius:50%;padding:.1em .25em}::ng-deep .marked:after{content:\"\";z-index:-1;left:-.1em;top:.4em;padding:.1em .25em;border-width:2px;border-style:solid;border-color:#f27068;border-left-color:transparent;border-top-color:transparent;position:absolute;width:100%;height:1em;transform:rotate(-1deg);opacity:.7;border-radius:50%}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.em{font-style:italic;color:var(--chat-message-italic-color, rgb(184, 208, 252))}.strong{font-weight:700;color:inherit}.italic{font-style:italic;color:var(--chat-message-italic-color, rgb(252, 198, 184))}.em_strong{font-weight:700;font-style:italic;color:inherit}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
5730
6082
|
}
|
|
5731
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type:
|
|
5732
|
-
type:
|
|
5733
|
-
args: [{
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
6083
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessageContentDisplayer, decorators: [{
|
|
6084
|
+
type: Component,
|
|
6085
|
+
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"] }]
|
|
6086
|
+
}], 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: [{
|
|
6087
|
+
type: HostBinding,
|
|
6088
|
+
args: ['style.--highlight-bg-color']
|
|
6089
|
+
}], playAudio: [{ type: i0.Output, args: ["playAudio"] }], audioCompleted: [{ type: i0.Output, args: ["audioCompleted"] }], audioStatusChanged: [{ type: i0.Output, args: ["audioStatusChanged"] }], wordClicked: [{ type: i0.Output, args: ["wordClicked"] }] } });
|
|
5737
6090
|
|
|
5738
6091
|
class DcImmersiveChatComponent {
|
|
5739
6092
|
constructor() {
|
|
@@ -5831,10 +6184,10 @@ class DcImmersiveChatComponent {
|
|
|
5831
6184
|
<!-- 2. Bottom Mic -->
|
|
5832
6185
|
<div class="mic-section" [class.is-talking]="isUserTalking()">
|
|
5833
6186
|
<div class="mic-ripple-glow"></div>
|
|
5834
|
-
<dc-mic (onFinished)="handleMicFinished($event)"></dc-mic>
|
|
6187
|
+
<dc-mic [micSettings]="micSettings()" (onFinished)="handleMicFinished($event)"></dc-mic>
|
|
5835
6188
|
</div>
|
|
5836
6189
|
</div>
|
|
5837
|
-
`, isInline: true, styles: [":host{height:100%}.immersive-container{display:flex;flex-direction:column;height:100%;width:100%;background:radial-gradient(circle at center,#1e1e2d33,#0a0a0f1a);padding:2rem;box-sizing:border-box;overflow:hidden}.hud-section{flex:1;display:flex;flex-direction:column;align-items:center;width:100%;overflow-y:auto;min-height:0;padding:1rem 1.5rem;scrollbar-width:none}.hud-section::-webkit-scrollbar{display:none}.hud-section>div{margin:auto 0;width:100%;display:flex;flex-direction:column}.mic-section{flex-shrink:0;position:relative;height:180px;display:flex;align-items:center;justify-content:center;margin-top:1rem;padding-bottom:2rem}.mic-ripple-glow{position:absolute;width:140px;height:140px;border-radius:50%;background:radial-gradient(circle,rgba(64,156,255,.2) 0%,transparent 70%);animation:breathe 4s ease-in-out infinite;z-index:0}.mic-section.is-talking .mic-ripple-glow{background:radial-gradient(circle,rgba(64,255,156,.3) 0%,transparent 70%);animation:pulse-active 1.5s ease-in-out infinite}.mic-section dc-mic{z-index:1;transform:scale(1.5);transition:transform .3s cubic-bezier(.175,.885,.32,1.275)}.mic-section dc-mic:hover{transform:scale(1.6)}.active-sentence-hud{max-width:1000px;width:100%;transition:all .5s cubic-bezier(.4,0,.2,1);text-shadow:0 10px 30px rgba(0,0,0,.5);animation:fadeIn .8s ease-out}.active-sentence-hud ::ng-deep .audio-text-sync-container{background:transparent!important;padding:0!important;justify-content:center}.active-sentence-hud ::ng-deep .play-button{display:none!important}.active-sentence-hud ::ng-deep .text-content{font-size:clamp(1.8rem,6vw,3.5rem);font-weight:700;text-align:center;line-height:1.2;color:#ffffffe6;margin:0;letter-spacing:-.02em}.active-sentence-hud ::ng-deep .highlight{background:transparent!important;color:#fff;text-shadow:0 0 20px rgba(255,255,255,.8),0 0 40px rgba(64,156,255,.4);filter:brightness(1.2)}.user-talking-indicator,.idle-indicator,.last-message-hud,.thinking-indicator{font-size:1.8rem;color:#fff;opacity:.7;font-weight:300;text-align:center;max-width:800px;line-height:1.4}.thinking-indicator{animation:pulse-thinking 2s infinite ease-in-out}@keyframes pulse-thinking{0%,to{opacity:.5;transform:scale(.98)}50%{opacity:.9;transform:scale(1.02)}}.idle-indicator p{background:linear-gradient(90deg,transparent,rgba(255,255,255,.1),transparent);padding:1rem 2rem;border-radius:50px}@keyframes breathe{0%,to{transform:scale(1);opacity:.2}50%{transform:scale(1.2);opacity:.4}}@keyframes pulse-active{0%{transform:scale(1);opacity:.5;box-shadow:0 0 #40ff9c66}70%{transform:scale(1.5);opacity:0;box-shadow:0 0 0 40px transparent}to{transform:scale(1);opacity:0}}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DCMicComponent, selector: "dc-mic", inputs: ["targetOrBase", "micSettings", "maxRecordingTime", "isDone"], outputs: ["onInterpretedText", "onFinishedRecognition", "onFinished", "onCanceled"] }, { kind: "component", type: MessageContentDisplayer, selector: "message-content-displayer", inputs: ["message", "markWord", "highlightColor"], outputs: ["playAudio", "audioCompleted", "audioStatusChanged", "wordClicked"] }] }); }
|
|
6190
|
+
`, isInline: true, styles: [":host{height:100%}.immersive-container{display:flex;flex-direction:column;height:100%;width:100%;background:radial-gradient(circle at center,#1e1e2d33,#0a0a0f1a);padding:2rem;box-sizing:border-box;overflow:hidden}.hud-section{flex:1;display:flex;flex-direction:column;align-items:center;width:100%;overflow-y:auto;min-height:0;padding:1rem 1.5rem;scrollbar-width:none}.hud-section::-webkit-scrollbar{display:none}.hud-section>div{margin:auto 0;width:100%;display:flex;flex-direction:column}.mic-section{flex-shrink:0;position:relative;height:180px;display:flex;align-items:center;justify-content:center;margin-top:1rem;padding-bottom:2rem}.mic-ripple-glow{position:absolute;width:140px;height:140px;border-radius:50%;background:radial-gradient(circle,rgba(64,156,255,.2) 0%,transparent 70%);animation:breathe 4s ease-in-out infinite;z-index:0}.mic-section.is-talking .mic-ripple-glow{background:radial-gradient(circle,rgba(64,255,156,.3) 0%,transparent 70%);animation:pulse-active 1.5s ease-in-out infinite}.mic-section dc-mic{z-index:1;transform:scale(1.5);transition:transform .3s cubic-bezier(.175,.885,.32,1.275)}.mic-section dc-mic:hover{transform:scale(1.6)}.active-sentence-hud{max-width:1000px;width:100%;transition:all .5s cubic-bezier(.4,0,.2,1);text-shadow:0 10px 30px rgba(0,0,0,.5);animation:fadeIn .8s ease-out}.active-sentence-hud ::ng-deep .audio-text-sync-container{background:transparent!important;padding:0!important;justify-content:center}.active-sentence-hud ::ng-deep .play-button{display:none!important}.active-sentence-hud ::ng-deep .text-content{font-size:clamp(1.8rem,6vw,3.5rem);font-weight:700;text-align:center;line-height:1.2;color:#ffffffe6;margin:0;letter-spacing:-.02em}.active-sentence-hud ::ng-deep .highlight{background:transparent!important;color:#fff;text-shadow:0 0 20px rgba(255,255,255,.8),0 0 40px rgba(64,156,255,.4);filter:brightness(1.2)}.user-talking-indicator,.idle-indicator,.last-message-hud,.thinking-indicator{font-size:1.8rem;color:#fff;opacity:.7;font-weight:300;text-align:center;max-width:800px;line-height:1.4}.thinking-indicator{animation:pulse-thinking 2s infinite ease-in-out}@keyframes pulse-thinking{0%,to{opacity:.5;transform:scale(.98)}50%{opacity:.9;transform:scale(1.02)}}.idle-indicator p{background:linear-gradient(90deg,transparent,rgba(255,255,255,.1),transparent);padding:1rem 2rem;border-radius:50px}@keyframes breathe{0%,to{transform:scale(1);opacity:.2}50%{transform:scale(1.2);opacity:.4}}@keyframes pulse-active{0%{transform:scale(1);opacity:.5;box-shadow:0 0 #40ff9c66}70%{transform:scale(1.5);opacity:0;box-shadow:0 0 0 40px transparent}to{transform:scale(1);opacity:0}}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DCMicComponent, selector: "dc-mic", inputs: ["targetOrBase", "micSettings", "maxRecordingTime", "isDone"], outputs: ["onInterpretedText", "onFinishedRecognition", "onFinished", "onCanceled", "onDiscarded"] }, { kind: "component", type: MessageContentDisplayer, selector: "message-content-displayer", inputs: ["message", "markWord", "highlightColor"], outputs: ["playAudio", "audioCompleted", "audioStatusChanged", "wordClicked"] }] }); }
|
|
5838
6191
|
}
|
|
5839
6192
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcImmersiveChatComponent, decorators: [{
|
|
5840
6193
|
type: Component,
|
|
@@ -5873,7 +6226,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
5873
6226
|
<!-- 2. Bottom Mic -->
|
|
5874
6227
|
<div class="mic-section" [class.is-talking]="isUserTalking()">
|
|
5875
6228
|
<div class="mic-ripple-glow"></div>
|
|
5876
|
-
<dc-mic (onFinished)="handleMicFinished($event)"></dc-mic>
|
|
6229
|
+
<dc-mic [micSettings]="micSettings()" (onFinished)="handleMicFinished($event)"></dc-mic>
|
|
5877
6230
|
</div>
|
|
5878
6231
|
</div>
|
|
5879
6232
|
`, styles: [":host{height:100%}.immersive-container{display:flex;flex-direction:column;height:100%;width:100%;background:radial-gradient(circle at center,#1e1e2d33,#0a0a0f1a);padding:2rem;box-sizing:border-box;overflow:hidden}.hud-section{flex:1;display:flex;flex-direction:column;align-items:center;width:100%;overflow-y:auto;min-height:0;padding:1rem 1.5rem;scrollbar-width:none}.hud-section::-webkit-scrollbar{display:none}.hud-section>div{margin:auto 0;width:100%;display:flex;flex-direction:column}.mic-section{flex-shrink:0;position:relative;height:180px;display:flex;align-items:center;justify-content:center;margin-top:1rem;padding-bottom:2rem}.mic-ripple-glow{position:absolute;width:140px;height:140px;border-radius:50%;background:radial-gradient(circle,rgba(64,156,255,.2) 0%,transparent 70%);animation:breathe 4s ease-in-out infinite;z-index:0}.mic-section.is-talking .mic-ripple-glow{background:radial-gradient(circle,rgba(64,255,156,.3) 0%,transparent 70%);animation:pulse-active 1.5s ease-in-out infinite}.mic-section dc-mic{z-index:1;transform:scale(1.5);transition:transform .3s cubic-bezier(.175,.885,.32,1.275)}.mic-section dc-mic:hover{transform:scale(1.6)}.active-sentence-hud{max-width:1000px;width:100%;transition:all .5s cubic-bezier(.4,0,.2,1);text-shadow:0 10px 30px rgba(0,0,0,.5);animation:fadeIn .8s ease-out}.active-sentence-hud ::ng-deep .audio-text-sync-container{background:transparent!important;padding:0!important;justify-content:center}.active-sentence-hud ::ng-deep .play-button{display:none!important}.active-sentence-hud ::ng-deep .text-content{font-size:clamp(1.8rem,6vw,3.5rem);font-weight:700;text-align:center;line-height:1.2;color:#ffffffe6;margin:0;letter-spacing:-.02em}.active-sentence-hud ::ng-deep .highlight{background:transparent!important;color:#fff;text-shadow:0 0 20px rgba(255,255,255,.8),0 0 40px rgba(64,156,255,.4);filter:brightness(1.2)}.user-talking-indicator,.idle-indicator,.last-message-hud,.thinking-indicator{font-size:1.8rem;color:#fff;opacity:.7;font-weight:300;text-align:center;max-width:800px;line-height:1.4}.thinking-indicator{animation:pulse-thinking 2s infinite ease-in-out}@keyframes pulse-thinking{0%,to{opacity:.5;transform:scale(.98)}50%{opacity:.9;transform:scale(1.02)}}.idle-indicator p{background:linear-gradient(90deg,transparent,rgba(255,255,255,.1),transparent);padding:1rem 2rem;border-radius:50px}@keyframes breathe{0%,to{transform:scale(1);opacity:.2}50%{transform:scale(1.2);opacity:.4}}@keyframes pulse-active{0%{transform:scale(1);opacity:.5;box-shadow:0 0 #40ff9c66}70%{transform:scale(1.5);opacity:0;box-shadow:0 0 0 40px transparent}to{transform:scale(1);opacity:0}}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}\n"] }]
|
|
@@ -6137,16 +6490,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
6137
6490
|
|
|
6138
6491
|
class MessagesStateInspectorComponent {
|
|
6139
6492
|
constructor() {
|
|
6140
|
-
this.
|
|
6141
|
-
this.
|
|
6493
|
+
this.service = input(...(ngDevMode ? [undefined, { debugName: "service" }] : /* istanbul ignore next */ []));
|
|
6494
|
+
this.fallback = inject(MessagesStateService);
|
|
6495
|
+
this.messages = computed(() => (this.service() ?? this.fallback).getMessagesSignal()(), ...(ngDevMode ? [{ debugName: "messages" }] : /* istanbul ignore next */ []));
|
|
6142
6496
|
}
|
|
6143
6497
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessagesStateInspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6144
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MessagesStateInspectorComponent, isStandalone: true, selector: "dc-messages-state-inspector", ngImport: i0, template: "<div class=\"messages-state-inspector\">\n @for (msg of messages(); track msg.messageId; let i = $index) {\n <details class=\"message-group mb-2 border rounded p-2\">\n <summary class=\"cursor-pointer font-bold p-1 hover:bg-gray-100 flex justify-between items-center\">\n <span>\n <span class=\"text-xs text-gray-400 mr-2\">#{{ i + 1 }}</span>\n <span [class]=\"'role-badge ' + msg.role\">{{ msg.role }}</span>\n <span class=\"ml-2 text-sm truncate max-w-xs\">{{ msg.content | slice:0:50 }}{{ msg.content.length > 50 ? '...' : '' }}</span>\n </span>\n <span class=\"text-xs text-gray-400\">{{ msg.messageId }}</span>\n </summary>\n\n <div class=\"message-details mt-2 pl-4 border-l-2 border-primary\">\n <!-- Standard Properties -->\n <div class=\"grid grid-cols-2 gap-2 text-sm\">\n <div><strong>Role:</strong> {{ msg.role }}</div>\n <div><strong>Section:</strong> {{ msg.section || 'N/A' }}</div>\n <div><strong>Message ID:</strong> {{ msg.messageId }}</div>\n <div class=\"col-span-2\"><strong>Content:</strong> <pre class=\"whitespace-pre-wrap text-xs bg-gray-50 p-2 mt-1\">{{ msg.content }}</pre></div>\n @if (msg.translation) {\n <div class=\"col-span-2 text-blue-600\"><strong>Translation:</strong> {{ msg.translation }}</div>\n }\n </div>\n\n <!-- JSON Preview of other props -->\n <details class=\"mt-2\">\n <summary class=\"text-xs text-gray-500 cursor-pointer\">All Metadata</summary>\n <pre class=\"text-[10px] bg-slate-800 text-white p-2 rounded mt-1 overflow-auto max-h-64\">{{ msg | safeJson }}</pre>\n </details>\n\n <!-- MultiMessages recursive check -->\n @if (msg.multiMessages && msg.multiMessages.length > 0) {\n <div class=\"mt-3\">\n <strong class=\"text-xs uppercase text-gray-500\">Multi Messages ({{ msg.multiMessages.length }})</strong>\n <div class=\"pl-4 border-l-2 border-dashed border-gray-300\">\n @for (subMsg of msg.multiMessages; track $index) {\n <details class=\"mt-1 border rounded p-1 bg-gray-50\">\n <summary class=\"text-xs cursor-pointer truncate\">Sub: {{ subMsg.text | slice:0:30 }}...</summary>\n <pre class=\"text-[10px] mt-1 p-1 bg-white border overflow-auto\">{{ subMsg | safeJson }}</pre>\n </details>\n }\n </div>\n </div>\n }\n\n <!-- Evaluation -->\n @if (msg.evaluation) {\n <div class=\"mt-2 p-2 bg-yellow-50 rounded text-xs space-y-2\">\n <strong>Evaluation</strong>\n\n @if (msg.evaluation['flow']; as flow) {\n <div class=\"flow-eval space-y-2\">\n @if (flow.goal) {\n <div class=\"p-2 bg-white rounded border\">\n <div class=\"flex items-center justify-between\">\n <strong class=\"text-[11px] uppercase text-gray-600\">Goal</strong>\n @if (flow.goal.deltaPoints != null) {\n <span\n class=\"px-1.5 rounded text-[11px] font-bold\"\n [class.bg-green-100]=\"flow.goal.deltaPoints > 0\"\n [class.text-green-700]=\"flow.goal.deltaPoints > 0\"\n [class.bg-red-100]=\"flow.goal.deltaPoints < 0\"\n [class.text-red-700]=\"flow.goal.deltaPoints < 0\"\n [class.bg-gray-100]=\"flow.goal.deltaPoints === 0\"\n [class.text-gray-600]=\"flow.goal.deltaPoints === 0\">\n {{ flow.goal.deltaPoints > 0 ? '+' : '' }}{{ flow.goal.deltaPoints }} pts\n </span>\n }\n </div>\n @if (flow.goal.previousValue != null && flow.goal.newValue != null) {\n <div class=\"mt-1 text-[11px] text-gray-600\">\n <span>{{ flow.goal.previousValue }}</span>\n <span class=\"mx-1\">\u2192</span>\n <span class=\"font-bold text-gray-900\">{{ flow.goal.newValue }}</span>\n <span class=\"text-gray-400\"> / 100</span>\n </div>\n <div class=\"w-full bg-gray-200 rounded h-1 mt-1 overflow-hidden\">\n <div class=\"bg-primary h-1\" [style.width.%]=\"flow.goal.newValue\" [style.background-color]=\"'#3b82f6'\"></div>\n </div>\n }\n @if (flow.intervention?.message) {\n <div class=\"mt-1 text-[11px] italic text-gray-700\">\"{{ flow.intervention.message }}\"</div>\n }\n </div>\n }\n\n @if (flow.challenges?.length) {\n <div class=\"p-2 bg-white rounded border\">\n <strong class=\"text-[11px] uppercase text-gray-600\">Challenges</strong>\n <ul class=\"mt-1 space-y-0.5\">\n @for (ch of flow.challenges; track ch.name) {\n <li class=\"flex items-center gap-1 text-[11px]\">\n <span [class.text-green-600]=\"ch.value\" [class.text-gray-400]=\"!ch.value\">\n {{ ch.value ? '\u2713' : '\u25CB' }}\n </span>\n <span [class.font-semibold]=\"ch.value\">{{ ch.name }}</span>\n </li>\n }\n </ul>\n </div>\n }\n\n @if (flow.moodState?.value) {\n <div class=\"p-2 bg-white rounded border flex items-center justify-between\">\n <strong class=\"text-[11px] uppercase text-gray-600\">Mood</strong>\n <span class=\"text-[11px] font-semibold\">{{ flow.moodState.value }}</span>\n </div>\n }\n\n @if (flow.evaluatedAt) {\n <div class=\"text-[10px] text-gray-400 text-right\">{{ flow.evaluatedAt }}</div>\n }\n </div>\n }\n\n <details class=\"mt-1\">\n <summary class=\"text-[10px] text-gray-500 cursor-pointer\">Raw evaluation</summary>\n <pre class=\"text-[10px]\">{{ msg.evaluation | safeJson }}</pre>\n </details>\n </div>\n }\n\n <!-- Tags -->\n @if (msg.tags && msg.tags.length > 0) {\n <div class=\"mt-2 flex flex-wrap gap-1\">\n <strong>Tags:</strong>\n @for (tag of msg.tags; track tag) {\n <span class=\"bg-gray-200 px-1 rounded text-[10px]\">{{ tag }}</span>\n }\n </div>\n }\n </div>\n </details>\n } @empty {\n <div class=\"p-4 text-center text-gray-400\">No messages in state.</div>\n }\n</div>\n\n<style>\n .role-badge {\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 0.7rem;\n text-transform: uppercase;\n font-weight: bold;\n }\n .role-badge.user { background: #e0f2fe; color: #0369a1; }\n .role-badge.assistant { background: #f0fdf4; color: #15803d; }\n .role-badge.assistantHelper { background: #faf5ff; color: #7e22ce; }\n .role-badge.system { background: #f1f5f9; color: #475569; }\n .border-primary { border-color: var(--p-primary-color, #3b82f6); }\n</style>\n", styles: [".role-badge{padding:2px 6px;border-radius:4px;font-size:.7rem;text-transform:uppercase;font-weight:700}.role-badge.user{background:#e0f2fe;color:#0369a1}.role-badge.assistant{background:#f0fdf4;color:#15803d}.role-badge.assistantHelper{background:#faf5ff;color:#7e22ce}.role-badge.system{background:#f1f5f9;color:#475569}.border-primary{border-color:var(--p-primary-color, #3b82f6)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1.SlicePipe, name: "slice" }, { kind: "pipe", type: SafeJsonPipe, name: "safeJson" }] }); }
|
|
6498
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: MessagesStateInspectorComponent, isStandalone: true, selector: "dc-messages-state-inspector", inputs: { service: { classPropertyName: "service", publicName: "service", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"messages-state-inspector\">\n @for (msg of messages(); track msg.messageId; let i = $index) {\n <details class=\"message-group mb-2 border rounded p-2\">\n <summary class=\"cursor-pointer font-bold p-1 hover:bg-gray-100 flex justify-between items-center\">\n <span>\n <span class=\"text-xs text-gray-400 mr-2\">#{{ i + 1 }}</span>\n <span [class]=\"'role-badge ' + msg.role\">{{ msg.role }}</span>\n <span class=\"ml-2 text-sm truncate max-w-xs\">{{ msg.content | slice:0:50 }}{{ msg.content.length > 50 ? '...' : '' }}</span>\n </span>\n <span class=\"text-xs text-gray-400\">{{ msg.messageId }}</span>\n </summary>\n\n <div class=\"message-details mt-2 pl-4 border-l-2 border-primary\">\n <!-- Standard Properties -->\n <div class=\"grid grid-cols-2 gap-2 text-sm\">\n <div><strong>Role:</strong> {{ msg.role }}</div>\n <div><strong>Section:</strong> {{ msg.section || 'N/A' }}</div>\n <div><strong>Message ID:</strong> {{ msg.messageId }}</div>\n <div class=\"col-span-2\"><strong>Content:</strong> <pre class=\"whitespace-pre-wrap text-xs bg-gray-50 p-2 mt-1\">{{ msg.content }}</pre></div>\n @if (msg.translation) {\n <div class=\"col-span-2 text-blue-600\"><strong>Translation:</strong> {{ msg.translation }}</div>\n }\n </div>\n\n <!-- JSON Preview of other props -->\n <details class=\"mt-2\">\n <summary class=\"text-xs text-gray-500 cursor-pointer\">All Metadata</summary>\n <pre class=\"text-[10px] bg-slate-800 text-white p-2 rounded mt-1 overflow-auto max-h-64\">{{ msg | safeJson }}</pre>\n </details>\n\n <!-- MultiMessages recursive check -->\n @if (msg.multiMessages && msg.multiMessages.length > 0) {\n <div class=\"mt-3\">\n <strong class=\"text-xs uppercase text-gray-500\">Multi Messages ({{ msg.multiMessages.length }})</strong>\n <div class=\"pl-4 border-l-2 border-dashed border-gray-300\">\n @for (subMsg of msg.multiMessages; track $index) {\n <details class=\"mt-1 border rounded p-1 bg-gray-50\">\n <summary class=\"text-xs cursor-pointer truncate\">Sub: {{ subMsg.text | slice:0:30 }}...</summary>\n <pre class=\"text-[10px] mt-1 p-1 bg-white border overflow-auto\">{{ subMsg | safeJson }}</pre>\n </details>\n }\n </div>\n </div>\n }\n\n <!-- Evaluation -->\n @if (msg.evaluation) {\n <div class=\"mt-2 p-2 bg-yellow-50 rounded text-xs space-y-2\">\n <strong>Evaluation</strong>\n\n @if (msg.evaluation['flow']; as flow) {\n <div class=\"flow-eval space-y-2\">\n @if (flow.goal) {\n <div class=\"p-2 bg-white rounded border\">\n <div class=\"flex items-center justify-between\">\n <strong class=\"text-[11px] uppercase text-gray-600\">Goal</strong>\n @if (flow.goal.deltaPoints != null) {\n <span\n class=\"px-1.5 rounded text-[11px] font-bold\"\n [class.bg-green-100]=\"flow.goal.deltaPoints > 0\"\n [class.text-green-700]=\"flow.goal.deltaPoints > 0\"\n [class.bg-red-100]=\"flow.goal.deltaPoints < 0\"\n [class.text-red-700]=\"flow.goal.deltaPoints < 0\"\n [class.bg-gray-100]=\"flow.goal.deltaPoints === 0\"\n [class.text-gray-600]=\"flow.goal.deltaPoints === 0\">\n {{ flow.goal.deltaPoints > 0 ? '+' : '' }}{{ flow.goal.deltaPoints }} pts\n </span>\n }\n </div>\n @if (flow.goal.previousValue != null && flow.goal.newValue != null) {\n <div class=\"mt-1 text-[11px] text-gray-600\">\n <span>{{ flow.goal.previousValue }}</span>\n <span class=\"mx-1\">\u2192</span>\n <span class=\"font-bold text-gray-900\">{{ flow.goal.newValue }}</span>\n <span class=\"text-gray-400\"> / 100</span>\n </div>\n <div class=\"w-full bg-gray-200 rounded h-1 mt-1 overflow-hidden\">\n <div class=\"bg-primary h-1\" [style.width.%]=\"flow.goal.newValue\" [style.background-color]=\"'#3b82f6'\"></div>\n </div>\n }\n @if (flow.intervention?.message) {\n <div class=\"mt-1 text-[11px] italic text-gray-700\">\"{{ flow.intervention.message }}\"</div>\n }\n </div>\n }\n\n @if (flow.challenges?.length) {\n <div class=\"p-2 bg-white rounded border\">\n <strong class=\"text-[11px] uppercase text-gray-600\">Challenges</strong>\n <ul class=\"mt-1 space-y-0.5\">\n @for (ch of flow.challenges; track ch.name) {\n <li class=\"flex items-center gap-1 text-[11px]\">\n <span [class.text-green-600]=\"ch.value\" [class.text-gray-400]=\"!ch.value\">\n {{ ch.value ? '\u2713' : '\u25CB' }}\n </span>\n <span [class.font-semibold]=\"ch.value\">{{ ch.name }}</span>\n </li>\n }\n </ul>\n </div>\n }\n\n @if (flow.moodState?.value) {\n <div class=\"p-2 bg-white rounded border flex items-center justify-between\">\n <strong class=\"text-[11px] uppercase text-gray-600\">Mood</strong>\n <span class=\"text-[11px] font-semibold\">{{ flow.moodState.value }}</span>\n </div>\n }\n\n @if (flow.evaluatedAt) {\n <div class=\"text-[10px] text-gray-400 text-right\">{{ flow.evaluatedAt }}</div>\n }\n </div>\n }\n\n <details class=\"mt-1\">\n <summary class=\"text-[10px] text-gray-500 cursor-pointer\">Raw evaluation</summary>\n <pre class=\"text-[10px]\">{{ msg.evaluation | safeJson }}</pre>\n </details>\n </div>\n }\n\n <!-- Tags -->\n @if (msg.tags && msg.tags.length > 0) {\n <div class=\"mt-2 flex flex-wrap gap-1\">\n <strong>Tags:</strong>\n @for (tag of msg.tags; track tag) {\n <span class=\"bg-gray-200 px-1 rounded text-[10px]\">{{ tag }}</span>\n }\n </div>\n }\n </div>\n </details>\n } @empty {\n <div class=\"p-4 text-center text-gray-400\">No messages in state.</div>\n }\n</div>\n\n<style>\n .role-badge {\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 0.7rem;\n text-transform: uppercase;\n font-weight: bold;\n }\n .role-badge.user { background: #e0f2fe; color: #0369a1; }\n .role-badge.assistant { background: #f0fdf4; color: #15803d; }\n .role-badge.assistantHelper { background: #faf5ff; color: #7e22ce; }\n .role-badge.system { background: #f1f5f9; color: #475569; }\n .border-primary { border-color: var(--p-primary-color, #3b82f6); }\n</style>\n", styles: [".role-badge{padding:2px 6px;border-radius:4px;font-size:.7rem;text-transform:uppercase;font-weight:700}.role-badge.user{background:#e0f2fe;color:#0369a1}.role-badge.assistant{background:#f0fdf4;color:#15803d}.role-badge.assistantHelper{background:#faf5ff;color:#7e22ce}.role-badge.system{background:#f1f5f9;color:#475569}.border-primary{border-color:var(--p-primary-color, #3b82f6)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1.SlicePipe, name: "slice" }, { kind: "pipe", type: SafeJsonPipe, name: "safeJson" }] }); }
|
|
6145
6499
|
}
|
|
6146
6500
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: MessagesStateInspectorComponent, decorators: [{
|
|
6147
6501
|
type: Component,
|
|
6148
6502
|
args: [{ selector: 'dc-messages-state-inspector', standalone: true, imports: [CommonModule, SafeJsonPipe], template: "<div class=\"messages-state-inspector\">\n @for (msg of messages(); track msg.messageId; let i = $index) {\n <details class=\"message-group mb-2 border rounded p-2\">\n <summary class=\"cursor-pointer font-bold p-1 hover:bg-gray-100 flex justify-between items-center\">\n <span>\n <span class=\"text-xs text-gray-400 mr-2\">#{{ i + 1 }}</span>\n <span [class]=\"'role-badge ' + msg.role\">{{ msg.role }}</span>\n <span class=\"ml-2 text-sm truncate max-w-xs\">{{ msg.content | slice:0:50 }}{{ msg.content.length > 50 ? '...' : '' }}</span>\n </span>\n <span class=\"text-xs text-gray-400\">{{ msg.messageId }}</span>\n </summary>\n\n <div class=\"message-details mt-2 pl-4 border-l-2 border-primary\">\n <!-- Standard Properties -->\n <div class=\"grid grid-cols-2 gap-2 text-sm\">\n <div><strong>Role:</strong> {{ msg.role }}</div>\n <div><strong>Section:</strong> {{ msg.section || 'N/A' }}</div>\n <div><strong>Message ID:</strong> {{ msg.messageId }}</div>\n <div class=\"col-span-2\"><strong>Content:</strong> <pre class=\"whitespace-pre-wrap text-xs bg-gray-50 p-2 mt-1\">{{ msg.content }}</pre></div>\n @if (msg.translation) {\n <div class=\"col-span-2 text-blue-600\"><strong>Translation:</strong> {{ msg.translation }}</div>\n }\n </div>\n\n <!-- JSON Preview of other props -->\n <details class=\"mt-2\">\n <summary class=\"text-xs text-gray-500 cursor-pointer\">All Metadata</summary>\n <pre class=\"text-[10px] bg-slate-800 text-white p-2 rounded mt-1 overflow-auto max-h-64\">{{ msg | safeJson }}</pre>\n </details>\n\n <!-- MultiMessages recursive check -->\n @if (msg.multiMessages && msg.multiMessages.length > 0) {\n <div class=\"mt-3\">\n <strong class=\"text-xs uppercase text-gray-500\">Multi Messages ({{ msg.multiMessages.length }})</strong>\n <div class=\"pl-4 border-l-2 border-dashed border-gray-300\">\n @for (subMsg of msg.multiMessages; track $index) {\n <details class=\"mt-1 border rounded p-1 bg-gray-50\">\n <summary class=\"text-xs cursor-pointer truncate\">Sub: {{ subMsg.text | slice:0:30 }}...</summary>\n <pre class=\"text-[10px] mt-1 p-1 bg-white border overflow-auto\">{{ subMsg | safeJson }}</pre>\n </details>\n }\n </div>\n </div>\n }\n\n <!-- Evaluation -->\n @if (msg.evaluation) {\n <div class=\"mt-2 p-2 bg-yellow-50 rounded text-xs space-y-2\">\n <strong>Evaluation</strong>\n\n @if (msg.evaluation['flow']; as flow) {\n <div class=\"flow-eval space-y-2\">\n @if (flow.goal) {\n <div class=\"p-2 bg-white rounded border\">\n <div class=\"flex items-center justify-between\">\n <strong class=\"text-[11px] uppercase text-gray-600\">Goal</strong>\n @if (flow.goal.deltaPoints != null) {\n <span\n class=\"px-1.5 rounded text-[11px] font-bold\"\n [class.bg-green-100]=\"flow.goal.deltaPoints > 0\"\n [class.text-green-700]=\"flow.goal.deltaPoints > 0\"\n [class.bg-red-100]=\"flow.goal.deltaPoints < 0\"\n [class.text-red-700]=\"flow.goal.deltaPoints < 0\"\n [class.bg-gray-100]=\"flow.goal.deltaPoints === 0\"\n [class.text-gray-600]=\"flow.goal.deltaPoints === 0\">\n {{ flow.goal.deltaPoints > 0 ? '+' : '' }}{{ flow.goal.deltaPoints }} pts\n </span>\n }\n </div>\n @if (flow.goal.previousValue != null && flow.goal.newValue != null) {\n <div class=\"mt-1 text-[11px] text-gray-600\">\n <span>{{ flow.goal.previousValue }}</span>\n <span class=\"mx-1\">\u2192</span>\n <span class=\"font-bold text-gray-900\">{{ flow.goal.newValue }}</span>\n <span class=\"text-gray-400\"> / 100</span>\n </div>\n <div class=\"w-full bg-gray-200 rounded h-1 mt-1 overflow-hidden\">\n <div class=\"bg-primary h-1\" [style.width.%]=\"flow.goal.newValue\" [style.background-color]=\"'#3b82f6'\"></div>\n </div>\n }\n @if (flow.intervention?.message) {\n <div class=\"mt-1 text-[11px] italic text-gray-700\">\"{{ flow.intervention.message }}\"</div>\n }\n </div>\n }\n\n @if (flow.challenges?.length) {\n <div class=\"p-2 bg-white rounded border\">\n <strong class=\"text-[11px] uppercase text-gray-600\">Challenges</strong>\n <ul class=\"mt-1 space-y-0.5\">\n @for (ch of flow.challenges; track ch.name) {\n <li class=\"flex items-center gap-1 text-[11px]\">\n <span [class.text-green-600]=\"ch.value\" [class.text-gray-400]=\"!ch.value\">\n {{ ch.value ? '\u2713' : '\u25CB' }}\n </span>\n <span [class.font-semibold]=\"ch.value\">{{ ch.name }}</span>\n </li>\n }\n </ul>\n </div>\n }\n\n @if (flow.moodState?.value) {\n <div class=\"p-2 bg-white rounded border flex items-center justify-between\">\n <strong class=\"text-[11px] uppercase text-gray-600\">Mood</strong>\n <span class=\"text-[11px] font-semibold\">{{ flow.moodState.value }}</span>\n </div>\n }\n\n @if (flow.evaluatedAt) {\n <div class=\"text-[10px] text-gray-400 text-right\">{{ flow.evaluatedAt }}</div>\n }\n </div>\n }\n\n <details class=\"mt-1\">\n <summary class=\"text-[10px] text-gray-500 cursor-pointer\">Raw evaluation</summary>\n <pre class=\"text-[10px]\">{{ msg.evaluation | safeJson }}</pre>\n </details>\n </div>\n }\n\n <!-- Tags -->\n @if (msg.tags && msg.tags.length > 0) {\n <div class=\"mt-2 flex flex-wrap gap-1\">\n <strong>Tags:</strong>\n @for (tag of msg.tags; track tag) {\n <span class=\"bg-gray-200 px-1 rounded text-[10px]\">{{ tag }}</span>\n }\n </div>\n }\n </div>\n </details>\n } @empty {\n <div class=\"p-4 text-center text-gray-400\">No messages in state.</div>\n }\n</div>\n\n<style>\n .role-badge {\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 0.7rem;\n text-transform: uppercase;\n font-weight: bold;\n }\n .role-badge.user { background: #e0f2fe; color: #0369a1; }\n .role-badge.assistant { background: #f0fdf4; color: #15803d; }\n .role-badge.assistantHelper { background: #faf5ff; color: #7e22ce; }\n .role-badge.system { background: #f1f5f9; color: #475569; }\n .border-primary { border-color: var(--p-primary-color, #3b82f6); }\n</style>\n" }]
|
|
6149
|
-
}] });
|
|
6503
|
+
}], propDecorators: { service: [{ type: i0.Input, args: [{ isSignal: true, alias: "service", required: false }] }] } });
|
|
6150
6504
|
|
|
6151
6505
|
class DcAgentCardDetailComponent extends EntityBaseDetailComponent {
|
|
6152
6506
|
constructor() {
|
|
@@ -6186,11 +6540,11 @@ class DcAgentCardDetailComponent extends EntityBaseDetailComponent {
|
|
|
6186
6540
|
this.selectedMotion.set(null);
|
|
6187
6541
|
}
|
|
6188
6542
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcAgentCardDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6189
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DcAgentCardDetailComponent, isStandalone: true, selector: "dc-agent-card-detail", inputs: { entityData: { classPropertyName: "entityData", publicName: "entityData", isSignal: true, isRequired: false, transformFunction: null } }, usesInheritance: true, ngImport: i0, template: "@if (isLoading()) {\n<div class=\"loading-state\">\n <p-progressSpinner strokeWidth=\"4\" />\n</div>\n} @else if (entity()) {\n<div class=\"preview-container\">\n\n <!-- Banner -->\n @if (bannerUrl) {\n <div class=\"preview-banner\">\n <img class=\"banner-image\" [src]=\"bannerUrl\" alt=\"banner\" />\n </div>\n }\n\n <!-- Header: image + name + meta -->\n <div class=\"preview-header\" [class.has-banner]=\"bannerUrl\">\n @if (entity()?.assets?.image?.url) {\n <img class=\"preview-image\" [src]=\"entity()!.assets!.image!.url\" [alt]=\"characterData?.name || entity()?.name\" />\n }\n <div class=\"preview-header-info\">\n <h2 class=\"preview-name\">{{ characterData?.name || entity()?.name }}</h2>\n\n <div class=\"preview-meta-tags\">\n @if (entity()?.lang) {\n <p-tag [value]=\"entity()!.lang!\" severity=\"secondary\" icon=\"pi pi-flag\" />\n }\n @if (entity()?.manageable?.status) {\n <p-tag [value]=\"entity()!.manageable!.status!\" severity=\"info\" icon=\"pi pi-circle-fill\" />\n }\n @if (characterData?.gender) {\n <p-tag [value]=\"characterData!.gender\" severity=\"secondary\" icon=\"pi pi-user\" />\n }\n @if (entity()?.metaApp?.takenCount) {\n <p-tag [value]=\"'Taken: ' + entity()!.metaApp!.takenCount\" severity=\"success\" icon=\"pi pi-users\" />\n }\n @if (learnableInfo?.level) {\n <p-tag [value]=\"'Level ' + learnableInfo.level\" severity=\"warn\" icon=\"pi pi-star\" />\n }\n @if (learnableInfo?.takenCount) {\n <p-tag [value]=\"'Played: ' + learnableInfo.takenCount\" severity=\"secondary\" icon=\"pi pi-play\" />\n }\n </div>\n\n @if (characterData?.tags?.length) {\n <div class=\"preview-tags\">\n @for (tag of characterData!.tags; track tag) {\n <p-tag [value]=\"tag\" severity=\"secondary\" />\n }\n </div>\n }\n </div>\n </div>\n\n <!-- Entity-level description -->\n @if (entity()?.description) {\n <div class=\"preview-entity-description\">\n <p>{{ entity()!.description }}</p>\n </div>\n }\n\n <!-- Motion Videos -->\n @if (motions.length) {\n <div class=\"preview-section motions-top-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-video\"></i> Motion Videos ({{ motions.length }})</h4>\n <div class=\"motions-grid\">\n @for (motion of motions; track $index) {\n @if (motion.metadata?.moodState || motion.metadata?.event) {\n <button class=\"motion-chip\" (click)=\"openMotionVideo(motion)\">\n <i class=\"pi pi-play-circle\"></i>\n <span>{{ motion.metadata?.moodState || motion.metadata?.event }}</span>\n </button>\n }\n }\n </div>\n </div>\n }\n\n <p-divider />\n\n @if (characterData?.hook) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-bookmark\"></i> Hook</h4>\n <p>{{ characterData!.hook }}</p>\n </div>\n }\n\n @if (characterData?.instructions) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-info-circle\"></i> Instructions</h4>\n <p>{{ characterData!.instructions }}</p>\n </div>\n }\n\n @if (characterData?.scenario) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-map\"></i> Scenario</h4>\n <p>{{ characterData!.scenario }}</p>\n </div>\n }\n\n @if (characterData?.creator_notes) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-file\"></i> Creator Notes</h4>\n <p>{{ characterData!.creator_notes }}</p>\n </div>\n }\n\n @if (characterData?.description) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-align-left\"></i> Description</h4>\n <p>{{ characterData!.description }}</p>\n </div>\n }\n\n @if (characterData?.greetings?.length) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-comments\"></i> Greetings</h4>\n @for (greeting of characterData!.greetings; track $index) {\n <p class=\"greeting-item\"><i class=\"pi pi-chevron-right\"></i> {{ greeting }}</p>\n }\n </div>\n }\n\n\n @if (persona) {\n <p-divider />\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-id-card\"></i> Persona</h4>\n <div class=\"persona-grid\">\n @if (persona.identity) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Identity</span>\n <span>{{ persona.identity }}</span>\n </div>\n }\n @if (persona.physical) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Physical</span>\n <span>{{ persona.physical }}</span>\n </div>\n }\n @if (persona.personality) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Personality</span>\n <span>{{ persona.personality }}</span>\n </div>\n }\n @if (persona.communication) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Communication</span>\n <span>{{ persona.communication }}</span>\n </div>\n }\n @if (persona.background) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Background</span>\n <span>{{ persona.background }}</span>\n </div>\n }\n @if (persona.psychology) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Psychology</span>\n <span>{{ persona.psychology }}</span>\n </div>\n }\n @if (persona.capabilities) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Capabilities</span>\n <span>{{ persona.capabilities }}</span>\n </div>\n }\n @if (persona.social) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Social</span>\n <span>{{ persona.social }}</span>\n </div>\n }\n @if (persona.preferences) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Preferences</span>\n <span>{{ persona.preferences }}</span>\n </div>\n }\n @if (persona.situation) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Situation</span>\n <span>{{ persona.situation }}</span>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Conversation Settings -->\n @if (entity()?.conversationSettings) {\n <p-divider />\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Conversation Settings</h4>\n <div class=\"settings-chips\">\n @if (entity()?.conversationSettings?.conversationType) {\n <p-tag [value]=\"entity()!.conversationSettings!.conversationType!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.conversationSettings?.textEngine) {\n <p-tag [value]=\"entity()!.conversationSettings!.textEngine!\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n @if (entity()?.conversationSettings?.autoStart) {\n <p-tag value=\"Auto Start\" severity=\"success\" icon=\"pi pi-play\" />\n }\n @if (entity()?.conversationSettings?.userMustStart) {\n <p-tag value=\"User Starts\" severity=\"warn\" icon=\"pi pi-user\" />\n }\n </div>\n </div>\n }\n\n <!-- Voices -->\n @if (entity()?.conversationSettings?.mainVoice?.voice) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-volume-up\"></i> Voices</h4>\n <div class=\"voice-info\">\n <p><span class=\"field-label\">Main:</span> {{ entity()!.conversationSettings!.mainVoice!.voice }}</p>\n @if (entity()?.conversationSettings?.secondaryVoice?.voice) {\n <p><span class=\"field-label\">Secondary:</span> {{ entity()!.conversationSettings!.secondaryVoice!.voice }}</p>\n }\n </div>\n </div>\n }\n\n</div>\n}\n\n@if (entity()?.manageable) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Manageable</h4>\n <div class=\"settings-chips\">\n @if (entity()?.manageable?.status) {\n <p-tag [value]=\"entity()!.manageable!.status!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.manageable?.isPublic) {\n <p-tag [value]=\"'Public'\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n </div>\n</div>\n}\n\n@if (entity()?.learnable) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Learnable</h4>\n <div class=\"settings-chips\">\n @if (entity()?.learnable?.level) {\n <p-tag [value]=\"'Level ' + entity()!.learnable!.level!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.learnable?.tags) {\n <p-tag [value]=\"'Public'\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n </div>\n</div>\n}\n\n@if(entity()?.conversationFlow) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-tags\"></i> Conversation Flow</h4>\n\n @if (entity()?.conversationFlow?.moodState) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-face-smile\"></i> Mood State</h5>\n <div class=\"settings-chips\">\n @if (entity()!.conversationFlow!.moodState!.enabled) {\n <p-tag value=\"Enabled\" severity=\"success\" icon=\"pi pi-check\" />\n } @else {\n <p-tag value=\"Disabled\" severity=\"danger\" icon=\"pi pi-times\" />\n }\n @if (entity()!.conversationFlow!.moodState!.useAssetStatesOnly) {\n <p-tag value=\"Asset States Only\" severity=\"warn\" icon=\"pi pi-images\" />\n }\n </div>\n @if (entity()!.conversationFlow!.moodState!.detectableStates?.length) {\n <div class=\"settings-chips\" style=\"margin-top: 0.5rem;\">\n @for (state of entity()!.conversationFlow!.moodState!.detectableStates; track $index) {\n <p-tag [value]=\"state\" severity=\"info\" icon=\"pi pi-tag\" />\n }\n </div>\n }\n </div>\n }\n\n @if (entity()?.conversationFlow?.dynamicConditions?.length) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-sliders-v\"></i> Dynamic Conditions</h5>\n @for (condition of entity()!.conversationFlow!.dynamicConditions!; track $index) {\n <div class=\"persona-field\" style=\"margin-bottom: 0.5rem;\">\n <div class=\"settings-chips\">\n <p-tag [value]=\"condition.what\" severity=\"info\" icon=\"pi pi-filter\" />\n <p-tag [value]=\"condition.when\" severity=\"secondary\" />\n <p-tag [value]=\"condition.value + ''\" severity=\"warn\" />\n </div>\n @if (condition.do?.length) {\n <div class=\"settings-chips\" style=\"margin-top: 0.25rem;\">\n @for (action of condition.do!; track $index) {\n <p-tag [value]=\"action.actionType\" severity=\"contrast\" icon=\"pi pi-bolt\" />\n @if (action.prompt) {\n <span class=\"field-label\" style=\"font-size: 0.75rem;\">{{ action.prompt | slice:0:60 }}\u2026</span>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n @if (entity()?.conversationFlow?.triggerTasks) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-bolt\"></i> Trigger Tasks</h5>\n @for (entry of objectEntries(entity()!.conversationFlow!.triggerTasks!); track $index) {\n <div class=\"persona-field\" style=\"margin-bottom: 0.5rem;\">\n <span class=\"field-label\">{{ entry[0] }}</span>\n <div class=\"settings-chips\" style=\"margin-top: 0.25rem;\">\n @if (entry[1].enabled) {\n <p-tag value=\"Enabled\" severity=\"success\" icon=\"pi pi-check\" />\n } @else {\n <p-tag value=\"Disabled\" severity=\"secondary\" icon=\"pi pi-times\" />\n }\n @if (entry[1].disableFeature) {\n <p-tag value=\"Feature Disabled\" severity=\"danger\" icon=\"pi pi-ban\" />\n }\n </div>\n @if (entry[1].task) {\n <p style=\"font-size: 0.8rem; margin: 0.25rem 0 0;\">{{ entry[1].task | slice:0:120 }}\u2026</p>\n }\n </div>\n }\n </div>\n }\n</div>\n}\n\n<!-- Video Dialog -->\n<p-dialog\n [visible]=\"videoDialogVisible()\"\n (visibleChange)=\"$event ? null : closeMotionVideo()\"\n [header]=\"selectedMotion()?.metadata?.moodState || selectedMotion()?.metadata?.event || 'Video'\"\n [modal]=\"true\"\n [dismissableMask]=\"true\"\n [closable]=\"true\"\n styleClass=\"video-dialog\"\n [style]=\"{ width: '560px' }\">\n\n @if (selectedMotion()?.url) {\n <video\n class=\"motion-video\"\n [src]=\"selectedMotion().url\"\n controls\n autoplay>\n </video>\n }\n</p-dialog>\n", styles: [".preview-container{padding:.5rem}.preview-banner{width:100%;height:210px;overflow:hidden;border-radius:8px;margin-bottom:0}.banner-image{width:100%;height:100%;object-fit:cover;display:block}.preview-header{display:flex;gap:1.5rem;align-items:flex-start;padding:1rem 0 .5rem}.preview-header.has-banner{padding-top:.75rem}.preview-image{width:120px;height:170px;object-fit:cover;border-radius:8px;flex-shrink:0;box-shadow:0 2px 8px #00000026}.preview-header-info{display:flex;flex-direction:column;gap:.5rem}.preview-name{margin:0 0 .25rem;font-size:1.5rem;font-weight:700}.preview-meta-tags{display:flex;flex-wrap:wrap;gap:.35rem}.preview-tags{display:flex;flex-wrap:wrap;gap:.25rem;margin-top:.25rem}.preview-entity-description{color:var(--p-text-muted-color);font-size:.9rem;margin:.5rem 0}.preview-entity-description p{margin:0}.preview-section{margin-bottom:1rem}.section-title{margin:0 0 .5rem;font-size:.95rem;font-weight:600;color:var(--p-text-muted-color);display:flex;align-items:center;gap:.4rem}.greeting-item{margin:.25rem 0;padding-left:.5rem;border-left:2px solid var(--p-primary-color)}.first-message{background:var(--p-surface-100);border-radius:6px;padding:.75rem;font-style:italic}.persona-grid{display:flex;flex-direction:column;gap:.5rem}.persona-field{display:flex;flex-direction:column;gap:.15rem}.field-label{font-size:.8rem;font-weight:600;color:var(--p-text-muted-color);text-transform:uppercase;letter-spacing:.05em}.motions-top-section{margin-top:.75rem;margin-bottom:0}.motions-grid{display:flex;flex-wrap:wrap;gap:.4rem}.motion-chip{display:inline-flex;align-items:center;gap:.35rem;padding:.3rem .65rem;border-radius:20px;border:1px solid var(--p-surface-300);background:var(--p-surface-100);color:var(--p-text-color);font-size:.8rem;font-weight:500;cursor:pointer;transition:background .15s,border-color .15s,transform .1s;text-transform:capitalize}.motion-chip i{font-size:.85rem;color:var(--p-primary-color)}.motion-chip:hover{background:var(--p-primary-50, #f0f4ff);border-color:var(--p-primary-color);transform:translateY(-1px)}.motion-chip:active{transform:translateY(0)}.motion-video{width:100%;border-radius:6px;display:block}.settings-chips{display:flex;flex-wrap:wrap;gap:.4rem}.voice-info{display:flex;flex-direction:column;gap:.25rem}.loading-state{display:flex;justify-content:center;padding:2rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: CardModule }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i1$4.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "ngmodule", type: DividerModule }, { kind: "component", type: i2$3.Divider, selector: "p-divider", inputs: ["styleClass", "layout", "type", "align"] }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i2$4.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i5$1.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "pipe", type: i1.SlicePipe, name: "slice" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6543
|
+
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) {\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 }}</p>\n @if (entity()?.voice?.secondary?.voice) {\n <p><span class=\"field-label\">Secondary:</span> {{ entity()?.voice?.secondary?.voice }}</p>\n }\n </div>\n </div>\n }\n\n</div>\n}\n\n@if (entity()?.manageable) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Manageable</h4>\n <div class=\"settings-chips\">\n @if (entity()?.manageable?.status) {\n <p-tag [value]=\"entity()!.manageable!.status!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.manageable?.isPublic) {\n <p-tag [value]=\"'Public'\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n </div>\n</div>\n}\n\n@if (entity()?.learnable) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Learnable</h4>\n <div class=\"settings-chips\">\n @if (entity()?.learnable?.level) {\n <p-tag [value]=\"'Level ' + entity()!.learnable!.level!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.learnable?.tags) {\n <p-tag [value]=\"'Public'\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n </div>\n</div>\n}\n\n@if(entity()?.conversationFlow) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-tags\"></i> Conversation Flow</h4>\n\n @if (entity()?.conversationFlow?.moodState) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-face-smile\"></i> Mood State</h5>\n <div class=\"settings-chips\">\n @if (entity()!.conversationFlow!.moodState!.enabled) {\n <p-tag value=\"Enabled\" severity=\"success\" icon=\"pi pi-check\" />\n } @else {\n <p-tag value=\"Disabled\" severity=\"danger\" icon=\"pi pi-times\" />\n }\n @if (entity()!.conversationFlow!.moodState!.useAssetStatesOnly) {\n <p-tag value=\"Asset States Only\" severity=\"warn\" icon=\"pi pi-images\" />\n }\n </div>\n @if (entity()!.conversationFlow!.moodState!.detectableStates?.length) {\n <div class=\"settings-chips\" style=\"margin-top: 0.5rem;\">\n @for (state of entity()!.conversationFlow!.moodState!.detectableStates; track $index) {\n <p-tag [value]=\"state\" severity=\"info\" icon=\"pi pi-tag\" />\n }\n </div>\n }\n </div>\n }\n\n @if (entity()?.conversationFlow?.dynamicConditions?.length) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-sliders-v\"></i> Dynamic Conditions</h5>\n @for (condition of entity()!.conversationFlow!.dynamicConditions!; track $index) {\n <div class=\"persona-field\" style=\"margin-bottom: 0.5rem;\">\n <div class=\"settings-chips\">\n <p-tag [value]=\"condition.what\" severity=\"info\" icon=\"pi pi-filter\" />\n <p-tag [value]=\"condition.when\" severity=\"secondary\" />\n <p-tag [value]=\"condition.value + ''\" severity=\"warn\" />\n </div>\n @if (condition.do?.length) {\n <div class=\"settings-chips\" style=\"margin-top: 0.25rem;\">\n @for (action of condition.do!; track $index) {\n <p-tag [value]=\"action.actionType\" severity=\"contrast\" icon=\"pi pi-bolt\" />\n @if (action.prompt) {\n <span class=\"field-label\" style=\"font-size: 0.75rem;\">{{ action.prompt | slice:0:60 }}\u2026</span>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n @if (entity()?.conversationFlow?.triggerTasks) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-bolt\"></i> Trigger Tasks</h5>\n @for (entry of objectEntries(entity()!.conversationFlow!.triggerTasks!); track $index) {\n <div class=\"persona-field\" style=\"margin-bottom: 0.5rem;\">\n <span class=\"field-label\">{{ entry[0] }}</span>\n <div class=\"settings-chips\" style=\"margin-top: 0.25rem;\">\n @if (entry[1].enabled) {\n <p-tag value=\"Enabled\" severity=\"success\" icon=\"pi pi-check\" />\n } @else {\n <p-tag value=\"Disabled\" severity=\"secondary\" icon=\"pi pi-times\" />\n }\n @if (entry[1].disableFeature) {\n <p-tag value=\"Feature Disabled\" severity=\"danger\" icon=\"pi pi-ban\" />\n }\n </div>\n @if (entry[1].task) {\n <p style=\"font-size: 0.8rem; margin: 0.25rem 0 0;\">{{ entry[1].task | slice:0:120 }}\u2026</p>\n }\n </div>\n }\n </div>\n }\n</div>\n}\n\n<!-- Video Dialog -->\n<p-dialog\n [visible]=\"videoDialogVisible()\"\n (visibleChange)=\"$event ? null : closeMotionVideo()\"\n [header]=\"selectedMotion()?.metadata?.moodState || selectedMotion()?.metadata?.event || 'Video'\"\n [modal]=\"true\"\n [dismissableMask]=\"true\"\n [closable]=\"true\"\n styleClass=\"video-dialog\"\n [style]=\"{ width: '560px' }\">\n\n @if (selectedMotion()?.url) {\n <video\n class=\"motion-video\"\n [src]=\"selectedMotion().url\"\n controls\n autoplay>\n </video>\n }\n</p-dialog>\n", styles: [".preview-container{padding:.5rem}.preview-banner{width:100%;height:210px;overflow:hidden;border-radius:8px;margin-bottom:0}.banner-image{width:100%;height:100%;object-fit:cover;display:block}.preview-header{display:flex;gap:1.5rem;align-items:flex-start;padding:1rem 0 .5rem}.preview-header.has-banner{padding-top:.75rem}.preview-image{width:120px;height:170px;object-fit:cover;border-radius:8px;flex-shrink:0;box-shadow:0 2px 8px #00000026}.preview-header-info{display:flex;flex-direction:column;gap:.5rem}.preview-name{margin:0 0 .25rem;font-size:1.5rem;font-weight:700}.preview-meta-tags{display:flex;flex-wrap:wrap;gap:.35rem}.preview-tags{display:flex;flex-wrap:wrap;gap:.25rem;margin-top:.25rem}.preview-entity-description{color:var(--p-text-muted-color);font-size:.9rem;margin:.5rem 0}.preview-entity-description p{margin:0}.preview-section{margin-bottom:1rem}.section-title{margin:0 0 .5rem;font-size:.95rem;font-weight:600;color:var(--p-text-muted-color);display:flex;align-items:center;gap:.4rem}.greeting-item{margin:.25rem 0;padding-left:.5rem;border-left:2px solid var(--p-primary-color)}.first-message{background:var(--p-surface-100);border-radius:6px;padding:.75rem;font-style:italic}.persona-grid{display:flex;flex-direction:column;gap:.5rem}.persona-field{display:flex;flex-direction:column;gap:.15rem}.field-label{font-size:.8rem;font-weight:600;color:var(--p-text-muted-color);text-transform:uppercase;letter-spacing:.05em}.motions-top-section{margin-top:.75rem;margin-bottom:0}.motions-grid{display:flex;flex-wrap:wrap;gap:.4rem}.motion-chip{display:inline-flex;align-items:center;gap:.35rem;padding:.3rem .65rem;border-radius:20px;border:1px solid var(--p-surface-300);background:var(--p-surface-100);color:var(--p-text-color);font-size:.8rem;font-weight:500;cursor:pointer;transition:background .15s,border-color .15s,transform .1s;text-transform:capitalize}.motion-chip i{font-size:.85rem;color:var(--p-primary-color)}.motion-chip:hover{background:var(--p-primary-50, #f0f4ff);border-color:var(--p-primary-color);transform:translateY(-1px)}.motion-chip:active{transform:translateY(0)}.motion-video{width:100%;border-radius:6px;display:block}.settings-chips{display:flex;flex-wrap:wrap;gap:.4rem}.voice-info{display:flex;flex-direction:column;gap:.25rem}.loading-state{display:flex;justify-content:center;padding:2rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: CardModule }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i1$4.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "ngmodule", type: DividerModule }, { kind: "component", type: i2$3.Divider, selector: "p-divider", inputs: ["styleClass", "layout", "type", "align"] }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i2$4.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i5$1.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "pipe", type: i1.SlicePipe, name: "slice" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6190
6544
|
}
|
|
6191
6545
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcAgentCardDetailComponent, decorators: [{
|
|
6192
6546
|
type: Component,
|
|
6193
|
-
args: [{ selector: 'dc-agent-card-detail', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, CardModule, TagModule, DividerModule, ProgressSpinnerModule, DialogModule, ButtonModule], template: "@if (isLoading()) {\n<div class=\"loading-state\">\n <p-progressSpinner strokeWidth=\"4\" />\n</div>\n} @else if (entity()) {\n<div class=\"preview-container\">\n\n <!-- Banner -->\n @if (bannerUrl) {\n <div class=\"preview-banner\">\n <img class=\"banner-image\" [src]=\"bannerUrl\" alt=\"banner\" />\n </div>\n }\n\n <!-- Header: image + name + meta -->\n <div class=\"preview-header\" [class.has-banner]=\"bannerUrl\">\n @if (entity()?.assets?.image?.url) {\n <img class=\"preview-image\" [src]=\"entity()!.assets!.image!.url\" [alt]=\"characterData?.name || entity()?.name\" />\n }\n <div class=\"preview-header-info\">\n <h2 class=\"preview-name\">{{ characterData?.name || entity()?.name }}</h2>\n\n <div class=\"preview-meta-tags\">\n @if (entity()?.lang) {\n <p-tag [value]=\"entity()!.lang!\" severity=\"secondary\" icon=\"pi pi-flag\" />\n }\n @if (entity()?.manageable?.status) {\n <p-tag [value]=\"entity()!.manageable!.status!\" severity=\"info\" icon=\"pi pi-circle-fill\" />\n }\n @if (characterData?.gender) {\n <p-tag [value]=\"characterData!.gender\" severity=\"secondary\" icon=\"pi pi-user\" />\n }\n @if (entity()?.metaApp?.takenCount) {\n <p-tag [value]=\"'Taken: ' + entity()!.metaApp!.takenCount\" severity=\"success\" icon=\"pi pi-users\" />\n }\n @if (learnableInfo?.level) {\n <p-tag [value]=\"'Level ' + learnableInfo.level\" severity=\"warn\" icon=\"pi pi-star\" />\n }\n @if (learnableInfo?.takenCount) {\n <p-tag [value]=\"'Played: ' + learnableInfo.takenCount\" severity=\"secondary\" icon=\"pi pi-play\" />\n }\n </div>\n\n @if (characterData?.tags?.length) {\n <div class=\"preview-tags\">\n @for (tag of characterData!.tags; track tag) {\n <p-tag [value]=\"tag\" severity=\"secondary\" />\n }\n </div>\n }\n </div>\n </div>\n\n <!-- Entity-level description -->\n @if (entity()?.description) {\n <div class=\"preview-entity-description\">\n <p>{{ entity()!.description }}</p>\n </div>\n }\n\n <!-- Motion Videos -->\n @if (motions.length) {\n <div class=\"preview-section motions-top-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-video\"></i> Motion Videos ({{ motions.length }})</h4>\n <div class=\"motions-grid\">\n @for (motion of motions; track $index) {\n @if (motion.metadata?.moodState || motion.metadata?.event) {\n <button class=\"motion-chip\" (click)=\"openMotionVideo(motion)\">\n <i class=\"pi pi-play-circle\"></i>\n <span>{{ motion.metadata?.moodState || motion.metadata?.event }}</span>\n </button>\n }\n }\n </div>\n </div>\n }\n\n <p-divider />\n\n @if (characterData?.hook) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-bookmark\"></i> Hook</h4>\n <p>{{ characterData!.hook }}</p>\n </div>\n }\n\n @if (characterData?.instructions) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-info-circle\"></i> Instructions</h4>\n <p>{{ characterData!.instructions }}</p>\n </div>\n }\n\n @if (characterData?.scenario) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-map\"></i> Scenario</h4>\n <p>{{ characterData!.scenario }}</p>\n </div>\n }\n\n @if (characterData?.creator_notes) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-file\"></i> Creator Notes</h4>\n <p>{{ characterData!.creator_notes }}</p>\n </div>\n }\n\n @if (characterData?.description) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-align-left\"></i> Description</h4>\n <p>{{ characterData!.description }}</p>\n </div>\n }\n\n @if (characterData?.greetings?.length) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-comments\"></i> Greetings</h4>\n @for (greeting of characterData!.greetings; track $index) {\n <p class=\"greeting-item\"><i class=\"pi pi-chevron-right\"></i> {{ greeting }}</p>\n }\n </div>\n }\n\n\n @if (persona) {\n <p-divider />\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-id-card\"></i> Persona</h4>\n <div class=\"persona-grid\">\n @if (persona.identity) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Identity</span>\n <span>{{ persona.identity }}</span>\n </div>\n }\n @if (persona.physical) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Physical</span>\n <span>{{ persona.physical }}</span>\n </div>\n }\n @if (persona.personality) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Personality</span>\n <span>{{ persona.personality }}</span>\n </div>\n }\n @if (persona.communication) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Communication</span>\n <span>{{ persona.communication }}</span>\n </div>\n }\n @if (persona.background) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Background</span>\n <span>{{ persona.background }}</span>\n </div>\n }\n @if (persona.psychology) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Psychology</span>\n <span>{{ persona.psychology }}</span>\n </div>\n }\n @if (persona.capabilities) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Capabilities</span>\n <span>{{ persona.capabilities }}</span>\n </div>\n }\n @if (persona.social) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Social</span>\n <span>{{ persona.social }}</span>\n </div>\n }\n @if (persona.preferences) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Preferences</span>\n <span>{{ persona.preferences }}</span>\n </div>\n }\n @if (persona.situation) {\n <div class=\"persona-field\">\n <span class=\"field-label\">Situation</span>\n <span>{{ persona.situation }}</span>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Conversation Settings -->\n @if (entity()?.conversationSettings) {\n <p-divider />\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Conversation Settings</h4>\n <div class=\"settings-chips\">\n @if (entity()?.conversationSettings?.conversationType) {\n <p-tag [value]=\"entity()!.conversationSettings!.conversationType!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.conversationSettings?.textEngine) {\n <p-tag [value]=\"entity()!.conversationSettings!.textEngine!\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n @if (entity()?.conversationSettings?.autoStart) {\n <p-tag value=\"Auto Start\" severity=\"success\" icon=\"pi pi-play\" />\n }\n @if (entity()?.conversationSettings?.userMustStart) {\n <p-tag value=\"User Starts\" severity=\"warn\" icon=\"pi pi-user\" />\n }\n </div>\n </div>\n }\n\n <!-- Voices -->\n @if (entity()?.conversationSettings?.mainVoice?.voice) {\n <div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-volume-up\"></i> Voices</h4>\n <div class=\"voice-info\">\n <p><span class=\"field-label\">Main:</span> {{ entity()!.conversationSettings!.mainVoice!.voice }}</p>\n @if (entity()?.conversationSettings?.secondaryVoice?.voice) {\n <p><span class=\"field-label\">Secondary:</span> {{ entity()!.conversationSettings!.secondaryVoice!.voice }}</p>\n }\n </div>\n </div>\n }\n\n</div>\n}\n\n@if (entity()?.manageable) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Manageable</h4>\n <div class=\"settings-chips\">\n @if (entity()?.manageable?.status) {\n <p-tag [value]=\"entity()!.manageable!.status!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.manageable?.isPublic) {\n <p-tag [value]=\"'Public'\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n </div>\n</div>\n}\n\n@if (entity()?.learnable) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Learnable</h4>\n <div class=\"settings-chips\">\n @if (entity()?.learnable?.level) {\n <p-tag [value]=\"'Level ' + entity()!.learnable!.level!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.learnable?.tags) {\n <p-tag [value]=\"'Public'\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n </div>\n</div>\n}\n\n@if(entity()?.conversationFlow) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-tags\"></i> Conversation Flow</h4>\n\n @if (entity()?.conversationFlow?.moodState) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-face-smile\"></i> Mood State</h5>\n <div class=\"settings-chips\">\n @if (entity()!.conversationFlow!.moodState!.enabled) {\n <p-tag value=\"Enabled\" severity=\"success\" icon=\"pi pi-check\" />\n } @else {\n <p-tag value=\"Disabled\" severity=\"danger\" icon=\"pi pi-times\" />\n }\n @if (entity()!.conversationFlow!.moodState!.useAssetStatesOnly) {\n <p-tag value=\"Asset States Only\" severity=\"warn\" icon=\"pi pi-images\" />\n }\n </div>\n @if (entity()!.conversationFlow!.moodState!.detectableStates?.length) {\n <div class=\"settings-chips\" style=\"margin-top: 0.5rem;\">\n @for (state of entity()!.conversationFlow!.moodState!.detectableStates; track $index) {\n <p-tag [value]=\"state\" severity=\"info\" icon=\"pi pi-tag\" />\n }\n </div>\n }\n </div>\n }\n\n @if (entity()?.conversationFlow?.dynamicConditions?.length) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-sliders-v\"></i> Dynamic Conditions</h5>\n @for (condition of entity()!.conversationFlow!.dynamicConditions!; track $index) {\n <div class=\"persona-field\" style=\"margin-bottom: 0.5rem;\">\n <div class=\"settings-chips\">\n <p-tag [value]=\"condition.what\" severity=\"info\" icon=\"pi pi-filter\" />\n <p-tag [value]=\"condition.when\" severity=\"secondary\" />\n <p-tag [value]=\"condition.value + ''\" severity=\"warn\" />\n </div>\n @if (condition.do?.length) {\n <div class=\"settings-chips\" style=\"margin-top: 0.25rem;\">\n @for (action of condition.do!; track $index) {\n <p-tag [value]=\"action.actionType\" severity=\"contrast\" icon=\"pi pi-bolt\" />\n @if (action.prompt) {\n <span class=\"field-label\" style=\"font-size: 0.75rem;\">{{ action.prompt | slice:0:60 }}\u2026</span>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n @if (entity()?.conversationFlow?.triggerTasks) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-bolt\"></i> Trigger Tasks</h5>\n @for (entry of objectEntries(entity()!.conversationFlow!.triggerTasks!); track $index) {\n <div class=\"persona-field\" style=\"margin-bottom: 0.5rem;\">\n <span class=\"field-label\">{{ entry[0] }}</span>\n <div class=\"settings-chips\" style=\"margin-top: 0.25rem;\">\n @if (entry[1].enabled) {\n <p-tag value=\"Enabled\" severity=\"success\" icon=\"pi pi-check\" />\n } @else {\n <p-tag value=\"Disabled\" severity=\"secondary\" icon=\"pi pi-times\" />\n }\n @if (entry[1].disableFeature) {\n <p-tag value=\"Feature Disabled\" severity=\"danger\" icon=\"pi pi-ban\" />\n }\n </div>\n @if (entry[1].task) {\n <p style=\"font-size: 0.8rem; margin: 0.25rem 0 0;\">{{ entry[1].task | slice:0:120 }}\u2026</p>\n }\n </div>\n }\n </div>\n }\n</div>\n}\n\n<!-- Video Dialog -->\n<p-dialog\n [visible]=\"videoDialogVisible()\"\n (visibleChange)=\"$event ? null : closeMotionVideo()\"\n [header]=\"selectedMotion()?.metadata?.moodState || selectedMotion()?.metadata?.event || 'Video'\"\n [modal]=\"true\"\n [dismissableMask]=\"true\"\n [closable]=\"true\"\n styleClass=\"video-dialog\"\n [style]=\"{ width: '560px' }\">\n\n @if (selectedMotion()?.url) {\n <video\n class=\"motion-video\"\n [src]=\"selectedMotion().url\"\n controls\n autoplay>\n </video>\n }\n</p-dialog>\n", styles: [".preview-container{padding:.5rem}.preview-banner{width:100%;height:210px;overflow:hidden;border-radius:8px;margin-bottom:0}.banner-image{width:100%;height:100%;object-fit:cover;display:block}.preview-header{display:flex;gap:1.5rem;align-items:flex-start;padding:1rem 0 .5rem}.preview-header.has-banner{padding-top:.75rem}.preview-image{width:120px;height:170px;object-fit:cover;border-radius:8px;flex-shrink:0;box-shadow:0 2px 8px #00000026}.preview-header-info{display:flex;flex-direction:column;gap:.5rem}.preview-name{margin:0 0 .25rem;font-size:1.5rem;font-weight:700}.preview-meta-tags{display:flex;flex-wrap:wrap;gap:.35rem}.preview-tags{display:flex;flex-wrap:wrap;gap:.25rem;margin-top:.25rem}.preview-entity-description{color:var(--p-text-muted-color);font-size:.9rem;margin:.5rem 0}.preview-entity-description p{margin:0}.preview-section{margin-bottom:1rem}.section-title{margin:0 0 .5rem;font-size:.95rem;font-weight:600;color:var(--p-text-muted-color);display:flex;align-items:center;gap:.4rem}.greeting-item{margin:.25rem 0;padding-left:.5rem;border-left:2px solid var(--p-primary-color)}.first-message{background:var(--p-surface-100);border-radius:6px;padding:.75rem;font-style:italic}.persona-grid{display:flex;flex-direction:column;gap:.5rem}.persona-field{display:flex;flex-direction:column;gap:.15rem}.field-label{font-size:.8rem;font-weight:600;color:var(--p-text-muted-color);text-transform:uppercase;letter-spacing:.05em}.motions-top-section{margin-top:.75rem;margin-bottom:0}.motions-grid{display:flex;flex-wrap:wrap;gap:.4rem}.motion-chip{display:inline-flex;align-items:center;gap:.35rem;padding:.3rem .65rem;border-radius:20px;border:1px solid var(--p-surface-300);background:var(--p-surface-100);color:var(--p-text-color);font-size:.8rem;font-weight:500;cursor:pointer;transition:background .15s,border-color .15s,transform .1s;text-transform:capitalize}.motion-chip i{font-size:.85rem;color:var(--p-primary-color)}.motion-chip:hover{background:var(--p-primary-50, #f0f4ff);border-color:var(--p-primary-color);transform:translateY(-1px)}.motion-chip:active{transform:translateY(0)}.motion-video{width:100%;border-radius:6px;display:block}.settings-chips{display:flex;flex-wrap:wrap;gap:.4rem}.voice-info{display:flex;flex-direction:column;gap:.25rem}.loading-state{display:flex;justify-content:center;padding:2rem}\n"] }]
|
|
6547
|
+
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) {\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 }}</p>\n @if (entity()?.voice?.secondary?.voice) {\n <p><span class=\"field-label\">Secondary:</span> {{ entity()?.voice?.secondary?.voice }}</p>\n }\n </div>\n </div>\n }\n\n</div>\n}\n\n@if (entity()?.manageable) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Manageable</h4>\n <div class=\"settings-chips\">\n @if (entity()?.manageable?.status) {\n <p-tag [value]=\"entity()!.manageable!.status!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.manageable?.isPublic) {\n <p-tag [value]=\"'Public'\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n </div>\n</div>\n}\n\n@if (entity()?.learnable) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-sliders-h\"></i> Learnable</h4>\n <div class=\"settings-chips\">\n @if (entity()?.learnable?.level) {\n <p-tag [value]=\"'Level ' + entity()!.learnable!.level!\" severity=\"info\" icon=\"pi pi-comments\" />\n }\n @if (entity()?.learnable?.tags) {\n <p-tag [value]=\"'Public'\" severity=\"secondary\" icon=\"pi pi-code\" />\n }\n </div>\n</div>\n}\n\n@if(entity()?.conversationFlow) {\n<p-divider />\n<div class=\"preview-section\">\n <h4 class=\"section-title\"><i class=\"pi pi-tags\"></i> Conversation Flow</h4>\n\n @if (entity()?.conversationFlow?.moodState) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-face-smile\"></i> Mood State</h5>\n <div class=\"settings-chips\">\n @if (entity()!.conversationFlow!.moodState!.enabled) {\n <p-tag value=\"Enabled\" severity=\"success\" icon=\"pi pi-check\" />\n } @else {\n <p-tag value=\"Disabled\" severity=\"danger\" icon=\"pi pi-times\" />\n }\n @if (entity()!.conversationFlow!.moodState!.useAssetStatesOnly) {\n <p-tag value=\"Asset States Only\" severity=\"warn\" icon=\"pi pi-images\" />\n }\n </div>\n @if (entity()!.conversationFlow!.moodState!.detectableStates?.length) {\n <div class=\"settings-chips\" style=\"margin-top: 0.5rem;\">\n @for (state of entity()!.conversationFlow!.moodState!.detectableStates; track $index) {\n <p-tag [value]=\"state\" severity=\"info\" icon=\"pi pi-tag\" />\n }\n </div>\n }\n </div>\n }\n\n @if (entity()?.conversationFlow?.dynamicConditions?.length) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-sliders-v\"></i> Dynamic Conditions</h5>\n @for (condition of entity()!.conversationFlow!.dynamicConditions!; track $index) {\n <div class=\"persona-field\" style=\"margin-bottom: 0.5rem;\">\n <div class=\"settings-chips\">\n <p-tag [value]=\"condition.what\" severity=\"info\" icon=\"pi pi-filter\" />\n <p-tag [value]=\"condition.when\" severity=\"secondary\" />\n <p-tag [value]=\"condition.value + ''\" severity=\"warn\" />\n </div>\n @if (condition.do?.length) {\n <div class=\"settings-chips\" style=\"margin-top: 0.25rem;\">\n @for (action of condition.do!; track $index) {\n <p-tag [value]=\"action.actionType\" severity=\"contrast\" icon=\"pi pi-bolt\" />\n @if (action.prompt) {\n <span class=\"field-label\" style=\"font-size: 0.75rem;\">{{ action.prompt | slice:0:60 }}\u2026</span>\n }\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n @if (entity()?.conversationFlow?.triggerTasks) {\n <div class=\"preview-section\">\n <h5 class=\"section-title\"><i class=\"pi pi-bolt\"></i> Trigger Tasks</h5>\n @for (entry of objectEntries(entity()!.conversationFlow!.triggerTasks!); track $index) {\n <div class=\"persona-field\" style=\"margin-bottom: 0.5rem;\">\n <span class=\"field-label\">{{ entry[0] }}</span>\n <div class=\"settings-chips\" style=\"margin-top: 0.25rem;\">\n @if (entry[1].enabled) {\n <p-tag value=\"Enabled\" severity=\"success\" icon=\"pi pi-check\" />\n } @else {\n <p-tag value=\"Disabled\" severity=\"secondary\" icon=\"pi pi-times\" />\n }\n @if (entry[1].disableFeature) {\n <p-tag value=\"Feature Disabled\" severity=\"danger\" icon=\"pi pi-ban\" />\n }\n </div>\n @if (entry[1].task) {\n <p style=\"font-size: 0.8rem; margin: 0.25rem 0 0;\">{{ entry[1].task | slice:0:120 }}\u2026</p>\n }\n </div>\n }\n </div>\n }\n</div>\n}\n\n<!-- Video Dialog -->\n<p-dialog\n [visible]=\"videoDialogVisible()\"\n (visibleChange)=\"$event ? null : closeMotionVideo()\"\n [header]=\"selectedMotion()?.metadata?.moodState || selectedMotion()?.metadata?.event || 'Video'\"\n [modal]=\"true\"\n [dismissableMask]=\"true\"\n [closable]=\"true\"\n styleClass=\"video-dialog\"\n [style]=\"{ width: '560px' }\">\n\n @if (selectedMotion()?.url) {\n <video\n class=\"motion-video\"\n [src]=\"selectedMotion().url\"\n controls\n autoplay>\n </video>\n }\n</p-dialog>\n", styles: [".preview-container{padding:.5rem}.preview-banner{width:100%;height:210px;overflow:hidden;border-radius:8px;margin-bottom:0}.banner-image{width:100%;height:100%;object-fit:cover;display:block}.preview-header{display:flex;gap:1.5rem;align-items:flex-start;padding:1rem 0 .5rem}.preview-header.has-banner{padding-top:.75rem}.preview-image{width:120px;height:170px;object-fit:cover;border-radius:8px;flex-shrink:0;box-shadow:0 2px 8px #00000026}.preview-header-info{display:flex;flex-direction:column;gap:.5rem}.preview-name{margin:0 0 .25rem;font-size:1.5rem;font-weight:700}.preview-meta-tags{display:flex;flex-wrap:wrap;gap:.35rem}.preview-tags{display:flex;flex-wrap:wrap;gap:.25rem;margin-top:.25rem}.preview-entity-description{color:var(--p-text-muted-color);font-size:.9rem;margin:.5rem 0}.preview-entity-description p{margin:0}.preview-section{margin-bottom:1rem}.section-title{margin:0 0 .5rem;font-size:.95rem;font-weight:600;color:var(--p-text-muted-color);display:flex;align-items:center;gap:.4rem}.greeting-item{margin:.25rem 0;padding-left:.5rem;border-left:2px solid var(--p-primary-color)}.first-message{background:var(--p-surface-100);border-radius:6px;padding:.75rem;font-style:italic}.persona-grid{display:flex;flex-direction:column;gap:.5rem}.persona-field{display:flex;flex-direction:column;gap:.15rem}.field-label{font-size:.8rem;font-weight:600;color:var(--p-text-muted-color);text-transform:uppercase;letter-spacing:.05em}.motions-top-section{margin-top:.75rem;margin-bottom:0}.motions-grid{display:flex;flex-wrap:wrap;gap:.4rem}.motion-chip{display:inline-flex;align-items:center;gap:.35rem;padding:.3rem .65rem;border-radius:20px;border:1px solid var(--p-surface-300);background:var(--p-surface-100);color:var(--p-text-color);font-size:.8rem;font-weight:500;cursor:pointer;transition:background .15s,border-color .15s,transform .1s;text-transform:capitalize}.motion-chip i{font-size:.85rem;color:var(--p-primary-color)}.motion-chip:hover{background:var(--p-primary-50, #f0f4ff);border-color:var(--p-primary-color);transform:translateY(-1px)}.motion-chip:active{transform:translateY(0)}.motion-video{width:100%;border-radius:6px;display:block}.settings-chips{display:flex;flex-wrap:wrap;gap:.4rem}.voice-info{display:flex;flex-direction:column;gap:.25rem}.loading-state{display:flex;justify-content:center;padding:2rem}\n"] }]
|
|
6194
6548
|
}], ctorParameters: () => [], propDecorators: { entityData: [{ type: i0.Input, args: [{ isSignal: true, alias: "entityData", required: false }] }] } });
|
|
6195
6549
|
|
|
6196
6550
|
class ConversationSummaryComponent {
|
|
@@ -6292,11 +6646,11 @@ class ConversationInspector {
|
|
|
6292
6646
|
this.viewMode.set(this.viewMode() === 'markdown' ? 'regular' : 'markdown');
|
|
6293
6647
|
}
|
|
6294
6648
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ConversationInspector, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6295
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ConversationInspector, isStandalone: true, selector: "dc-conversation-info", outputs: { completeEvent: "completeEvent" }, ngImport: i0, template: "<div class=\"info-content\">\n <p-tabs value=\"0\">\n <p-tablist>\n <p-tab value=\"0\">\uD83D\uDCCA Summary</p-tab>\n <p-tab value=\"1\">\uD83D\uDD00 Dynamic Flow</p-tab>\n <p-tab value=\"6\">\uD83D\uDCE6 Messages State</p-tab>\n <p-tab value=\"7\">\uD83D\uDC64 Preview Card</p-tab>\n <p-tab value=\"5\">\uD83D\uDCBE Conversation State</p-tab>\n <p-tab value=\"2\">\uD83D\uDCAC Conversation Messages</p-tab>\n <p-tab value=\"3\">\uD83D\uDCB0 Pricing</p-tab>\n <p-tab value=\"4\">\uD83D\uDC1B More</p-tab>\n </p-tablist>\n <p-tabpanels>\n <p-tabpanel value=\"0\">\n <ng-template #content>\n @if (conversationFlowStateService.flowState(); as flowState) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4 mt-2\">\n <!-- Goal State -->\n <div class=\"p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFAF</span> Goal Progress\n </h3>\n <div class=\"flex items-end gap-4 mb-3\">\n <div class=\"text-5xl font-black leading-none\">\n {{ flowState.goal?.value || 0 }}\n </div>\n @if (flowState.goal?.deltaPoints !== undefined) {\n <div class=\"text-xl font-bold mb-1\" \n [class.text-green-600]=\"flowState.goal.deltaPoints > 0\"\n [class.text-red-600]=\"flowState.goal.deltaPoints < 0\"\n [class.opacity-50]=\"flowState.goal.deltaPoints === 0\">\n {{ flowState.goal.deltaPoints > 0 ? '+' : '' }}{{ flowState.goal.deltaPoints }}\n </div>\n }\n </div>\n \n @if (flowState.intervention?.message) {\n <div class=\"mt-4 p-3 rounded border-l-4 text-sm italic shadow-sm opacity-80\">\n \"{{ flowState.intervention.message }}\"\n </div>\n }\n </div>\n\n <!-- Mood State -->\n <div class=\"p-4 border rounded-lg shadow-sm flex flex-col\">\n <h3 class=\"text-lg font-bold mb-2 flex items-center gap-2\">\n <span>\uD83C\uDFAD</span> Agent Mood\n </h3>\n <div class=\"flex-grow flex items-center justify-center\">\n @if (flowState.moodState?.value) {\n <div class=\"text-4xl font-black uppercase tracking-widest text-center drop-shadow-sm\">\n {{ flowState.moodState.value }}\n </div>\n } @else {\n <div class=\"text-xl font-medium italic opacity-50\">Neutral</div>\n }\n </div>\n </div>\n </div>\n }\n <dc-conversation-summary [agentCard]=\"agentCard\" [parentInjector]=\"config.data.injector\" (completeEvent)=\"complete()\"></dc-conversation-summary>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"1\">\n <ng-template #content>\n <!-- Dynamic Flow -->\n\n <p-divider>Conversation Flow</p-divider>\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (dynamicFlowService.conversationFlowConfig()?.goal?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Goal</p>\n </div>\n @if(dynamicFlowService.conversationFlowConfig()?.goal?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.triggerTasks?.onUserMessage?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Trigger User Message</p>\n </div>\n @if(agentCardStateService.conversationFlow$()?.triggerTasks?.onUserMessage?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.triggerTasks?.onAssistantMessage?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Trigger Assistant Message</p>\n </div>\n @if(agentCardStateService.conversationFlow$()?.triggerTasks?.onAssistantMessage?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <h3>Herramientas</h3>\n @for (tool of agentCardStateService.conversationFlow$()?.tools; track tool.name) {\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (tool.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">{{ tool.name }}</p>\n </div>\n @if(tool.description) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ tool.description }} </div>\n }\n </div>\n }\n\n <h3>Challenges Retos</h3>\n @for (challenge of dynamicFlowService.conversationFlowConfig()?.challenges; track challenge.name) {\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (challenge.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n\n <p class=\"font-semibold\"> {{ challenge.emoji }} {{ challenge.name }}</p>\n </div>\n @if(challenge.description) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ challenge.description }} </div>\n }\n </div>\n }\n <h5>Conditions</h5>\n\n @for (condition of dynamicFlowService.conversationFlowConfig()?.dynamicConditions; track $index) {\n <div class=\"pl-2 mt-1 text-sm text-gray-600 italic\"> When the {{ condition.what }} is {{ condition.when }} than {{ condition.value }} Do </div>\n <ol>\n @for (doAction of condition.do; track $index) {\n <li class=\"pl-5 mt-1 text-sm text-gray-600 italic\">\n {{ $index }})\n <span style=\"color: brown\"> {{ doAction.actionType }}</span>\n <span style=\"color: green\"> {{ doAction.systemPromptType }}</span>\n <span style=\"color: green\"> {{ doAction.dynamicFlowTaskType }}</span>\n\n to\n <span style=\"color: rgb(49, 23, 177)\"> {{ doAction.enabled }}</span>\n \"{{ doAction.prompt }}\"\n </li>\n }\n </ol>\n }\n\n <p>Challenges</p>\n <pre>{{ agentCardStateService.conversationFlow$()?.challenges | safeJson }}</pre>\n\n <p-divider>Performance Evaluation</p-divider>\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.enablePerformanceAnalysis !== false) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Performance Prompt</p>\n <span class=\"text-xs text-gray-400 ml-2\" pTooltip=\"Minimum messages required to trigger evaluation\">(MIN_MESSAGES_EVALUATE: 4 hardcoded)</span>\n </div>\n @if(agentCardStateService.conversationFlow$()?.enablePerformanceAnalysis !== false) {\n @if(agentCardStateService.conversationFlow$()?.performancePrompt; as prompt) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> \n <markdown>\n {{ prompt }} \n </markdown>\n </div>\n } @else {\n <div class=\"pl-5 mt-1 text-sm text-gray-400 italic\"> No custom performance prompt defined (using default). </div>\n }\n } @else {\n <div class=\"pl-5 mt-1 text-sm text-gray-400 italic\"> Performance analysis disabled. </div>\n }\n </div>\n\n <p-divider>System Evaluation Prompt</p-divider>\n <details>\n <summary>Show Prompt</summary>\n <pre>{{ systemPromptForFlow() }}</pre>\n </details>\n\n <!-- Dynamic Flow ends Here -->\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"5\">\n <ng-template #content>\n @if (conversationFlowStateService.flowState(); as flowState) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4 mt-2\">\n <!-- Goal State -->\n <div class=\"p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFAF</span> Goal Progress\n </h3>\n <div class=\"flex items-end gap-4 mb-3\">\n <div class=\"text-5xl font-black leading-none\">\n {{ flowState.goal?.value || 0 }}\n </div>\n @if (flowState.goal?.deltaPoints !== undefined) {\n <div class=\"text-xl font-bold mb-1\" \n [class.text-green-600]=\"flowState.goal.deltaPoints > 0\"\n [class.text-red-600]=\"flowState.goal.deltaPoints < 0\"\n [class.opacity-50]=\"flowState.goal.deltaPoints === 0\">\n {{ flowState.goal.deltaPoints > 0 ? '+' : '' }}{{ flowState.goal.deltaPoints }}\n </div>\n }\n </div>\n \n @if (flowState.intervention?.message) {\n <div class=\"mt-4 p-3 rounded border-l-4 text-sm italic shadow-sm opacity-80\">\n \"{{ flowState.intervention.message }}\"\n </div>\n }\n </div>\n\n <!-- Mood State -->\n <div class=\"p-4 border rounded-lg shadow-sm flex flex-col\">\n <h3 class=\"text-lg font-bold mb-2 flex items-center gap-2\">\n <span>\uD83C\uDFAD</span> Agent Mood\n </h3>\n <div class=\"flex-grow flex items-center justify-center\">\n @if (flowState.moodState?.value) {\n <div class=\"text-4xl font-black uppercase tracking-widest text-center drop-shadow-sm\">\n {{ flowState.moodState.value }}\n </div>\n } @else {\n <div class=\"text-xl font-medium italic opacity-50\">Neutral</div>\n }\n </div>\n </div>\n </div>\n\n @if (flowState.challenges?.length) {\n <div class=\"mb-4 p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFC6</span> Completed Challenges\n </h3>\n <ul class=\"list-disc pl-5 space-y-1\">\n @for (challenge of flowState.challenges; track challenge) {\n <li>{{ challenge }}</li>\n }\n </ul>\n </div>\n }\n\n <details class=\"mt-4\">\n <summary class=\"cursor-pointer text-sm font-medium\">Raw JSON State</summary>\n <pre class=\"mt-2 p-2 rounded text-xs overflow-auto\">{{ flowState | safeJson }}</pre>\n </details>\n } @else {\n <p class=\"italic mt-2 opacity-50\">No conversation state available.</p>\n }\n\n <details class=\"mt-2\">\n <summary class=\"cursor-pointer text-sm font-medium\">Current Prompt</summary>\n <pre class=\"mt-2 p-2 rounded text-xs overflow-auto\">{{ systemPromptForFlow() }}</pre>\n </details>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"2\">\n <ng-template #content>\n <div>\n <p-button label=\"Exportar todo\" (click)=\"exportChat('all')\" />\n <p-button label=\"Exportar conversaci\u00F3n\" (click)=\"exportChat('conversation')\" />\n <p-button [label]=\"'Vista ' + viewMode()\" (click)=\"toggleViewMode()\" />\n </div>\n <dc-prompt-preview [messages]=\"messageStateService.getMessagesSignal()()\" [view]=\"viewMode()\"></dc-prompt-preview>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"6\">\n <ng-template #content>\n <dc-messages-state-inspector></dc-messages-state-inspector>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"3\">\n <ng-template #content>\n <dc-cost-details></dc-cost-details>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"4\">\n <ng-template #content>\n <!-- MORE INFO -->\n\n <details>\n <summary>Estado Global Conversation Flow</summary>\n <p>Goal</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.goal | safeJson }}</pre>\n <p>Trigger User Message</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.triggerTasks?.onUserMessage | safeJson }}</pre>\n <p>Trigger Assistant Message</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.triggerTasks?.onAssistantMessage | safeJson }}</pre>\n <p>Conditions</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.dynamicConditions | safeJson }}</pre>\n <p>Tools</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.tools | safeJson }}</pre>\n <p>Challenges</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.challenges | safeJson }}</pre>\n </details>\n\n <details>\n <summary>Conversation Settings (Debug)</summary>\n <pre>{{ conversationService.conversationSettings$() | safeJson }}</pre>\n </details>\n\n <details>\n <summary>Agent Card</summary>\n <pre>{{ agentCard | safeJson }}</pre>\n </details>\n\n <details>\n <summary>User Settings</summary>\n <pre>{{ chatUserSettings | safeJson }}</pre>\n </details>\n\n <!-- MORE INFO ends Here -->\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"7\">\n <ng-template #content>\n <dc-agent-card-detail [entityData]=\"agentCardStateService.agentCard$()\" />\n </ng-template>\n </p-tabpanel>\n </p-tabpanels>\n </p-tabs>\n</div>\n", styles: [":host{overflow-y:scroll}.info-content{display:flex;flex-direction:column;gap:1rem;padding:1rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "ngmodule", type: SliderModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: DividerModule }, { kind: "component", type: i2$3.Divider, selector: "p-divider", inputs: ["styleClass", "layout", "type", "align"] }, { kind: "component", type: PromptPreviewComponent, selector: "dc-prompt-preview", inputs: ["messages", "jailBrake", "view"] }, { kind: "ngmodule", type: TabsModule }, { kind: "component", type: i2$5.Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i2$5.TabPanels, selector: "p-tabpanels" }, { kind: "component", type: i2$5.TabPanel, selector: "p-tabpanel", inputs: ["lazy", "value"], outputs: ["valueChange"] }, { kind: "component", type: i2$5.TabList, selector: "p-tablist" }, { kind: "component", type: i2$5.Tab, selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: CostDetailsComponent, selector: "dc-cost-details" }, { kind: "component", type: MessagesStateInspectorComponent, selector: "dc-messages-state-inspector" }, { kind: "component", type: DcAgentCardDetailComponent, selector: "dc-agent-card-detail", inputs: ["entityData"] }, { kind: "component", type: ConversationSummaryComponent, selector: "dc-conversation-summary", inputs: ["agentCard", "parentInjector"], outputs: ["completeEvent"] }, { kind: "pipe", type: SafeJsonPipe, name: "safeJson" }] }); }
|
|
6649
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: ConversationInspector, isStandalone: true, selector: "dc-conversation-info", outputs: { completeEvent: "completeEvent" }, ngImport: i0, template: "<div class=\"info-content\">\n <p-tabs value=\"0\">\n <p-tablist>\n <p-tab value=\"0\">\uD83D\uDCCA Summary</p-tab>\n <p-tab value=\"1\">\uD83D\uDD00 Dynamic Flow</p-tab>\n <p-tab value=\"6\">\uD83D\uDCE6 Messages State</p-tab>\n <p-tab value=\"7\">\uD83D\uDC64 Preview Card</p-tab>\n <p-tab value=\"5\">\uD83D\uDCBE Conversation State</p-tab>\n <p-tab value=\"2\">\uD83D\uDCAC Conversation Messages</p-tab>\n <p-tab value=\"3\">\uD83D\uDCB0 Pricing</p-tab>\n <p-tab value=\"4\">\uD83D\uDC1B More</p-tab>\n </p-tablist>\n <p-tabpanels>\n <p-tabpanel value=\"0\">\n <ng-template #content>\n @if (conversationFlowStateService.flowState(); as flowState) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4 mt-2\">\n <!-- Goal State -->\n <div class=\"p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFAF</span> Goal Progress\n </h3>\n <div class=\"flex items-end gap-4 mb-3\">\n <div class=\"text-5xl font-black leading-none\">\n {{ flowState.goal?.value || 0 }}\n </div>\n @if (flowState.goal?.deltaPoints !== undefined) {\n <div class=\"text-xl font-bold mb-1\" \n [class.text-green-600]=\"flowState.goal.deltaPoints > 0\"\n [class.text-red-600]=\"flowState.goal.deltaPoints < 0\"\n [class.opacity-50]=\"flowState.goal.deltaPoints === 0\">\n {{ flowState.goal.deltaPoints > 0 ? '+' : '' }}{{ flowState.goal.deltaPoints }}\n </div>\n }\n </div>\n \n @if (flowState.intervention?.message) {\n <div class=\"mt-4 p-3 rounded border-l-4 text-sm italic shadow-sm opacity-80\">\n \"{{ flowState.intervention.message }}\"\n </div>\n }\n </div>\n\n <!-- Mood State -->\n <div class=\"p-4 border rounded-lg shadow-sm flex flex-col\">\n <h3 class=\"text-lg font-bold mb-2 flex items-center gap-2\">\n <span>\uD83C\uDFAD</span> Agent Mood\n </h3>\n <div class=\"flex-grow flex items-center justify-center\">\n @if (flowState.moodState?.value) {\n <div class=\"text-4xl font-black uppercase tracking-widest text-center drop-shadow-sm\">\n {{ flowState.moodState.value }}\n </div>\n } @else {\n <div class=\"text-xl font-medium italic opacity-50\">Neutral</div>\n }\n </div>\n </div>\n </div>\n }\n <dc-conversation-summary [agentCard]=\"agentCard\" [parentInjector]=\"config.data.injector\" (completeEvent)=\"complete()\"></dc-conversation-summary>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"1\">\n <ng-template #content>\n <!-- Dynamic Flow -->\n\n <p-divider>Conversation Flow</p-divider>\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (dynamicFlowService.conversationFlowConfig()?.goal?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Goal</p>\n </div>\n @if(dynamicFlowService.conversationFlowConfig()?.goal?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.triggerTasks?.onUserMessage?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Trigger User Message</p>\n </div>\n @if(agentCardStateService.conversationFlow$()?.triggerTasks?.onUserMessage?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.triggerTasks?.onAssistantMessage?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Trigger Assistant Message</p>\n </div>\n @if(agentCardStateService.conversationFlow$()?.triggerTasks?.onAssistantMessage?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <h3>Herramientas</h3>\n @for (tool of agentCardStateService.conversationFlow$()?.tools; track tool.name) {\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (tool.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">{{ tool.name }}</p>\n </div>\n @if(tool.description) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ tool.description }} </div>\n }\n </div>\n }\n\n <h3>Challenges Retos</h3>\n @for (challenge of dynamicFlowService.conversationFlowConfig()?.challenges; track challenge.name) {\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (challenge.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n\n <p class=\"font-semibold\"> {{ challenge.emoji }} {{ challenge.name }}</p>\n </div>\n @if(challenge.description) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ challenge.description }} </div>\n }\n </div>\n }\n <h5>Conditions</h5>\n\n @for (condition of dynamicFlowService.conversationFlowConfig()?.dynamicConditions; track $index) {\n <div class=\"pl-2 mt-1 text-sm text-gray-600 italic\"> When the {{ condition.what }} is {{ condition.when }} than {{ condition.value }} Do </div>\n <ol>\n @for (doAction of condition.do; track $index) {\n <li class=\"pl-5 mt-1 text-sm text-gray-600 italic\">\n {{ $index }})\n <span style=\"color: brown\"> {{ doAction.actionType }}</span>\n <span style=\"color: green\"> {{ doAction.systemPromptType }}</span>\n <span style=\"color: green\"> {{ doAction.dynamicFlowTaskType }}</span>\n\n to\n <span style=\"color: rgb(49, 23, 177)\"> {{ doAction.enabled }}</span>\n \"{{ doAction.prompt }}\"\n </li>\n }\n </ol>\n }\n\n <p>Challenges</p>\n <pre>{{ agentCardStateService.conversationFlow$()?.challenges | safeJson }}</pre>\n\n <p-divider>Performance Evaluation</p-divider>\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.enablePerformanceAnalysis !== false) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Performance Prompt</p>\n <span class=\"text-xs text-gray-400 ml-2\" pTooltip=\"Minimum messages required to trigger evaluation\">(MIN_MESSAGES_EVALUATE: 4 hardcoded)</span>\n </div>\n @if(agentCardStateService.conversationFlow$()?.enablePerformanceAnalysis !== false) {\n @if(agentCardStateService.conversationFlow$()?.performancePrompt; as prompt) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> \n <markdown>\n {{ prompt }} \n </markdown>\n </div>\n } @else {\n <div class=\"pl-5 mt-1 text-sm text-gray-400 italic\"> No custom performance prompt defined (using default). </div>\n }\n } @else {\n <div class=\"pl-5 mt-1 text-sm text-gray-400 italic\"> Performance analysis disabled. </div>\n }\n </div>\n\n <p-divider>System Evaluation Prompt</p-divider>\n <details>\n <summary>Show Prompt</summary>\n <pre>{{ systemPromptForFlow() }}</pre>\n </details>\n\n <!-- Dynamic Flow ends Here -->\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"5\">\n <ng-template #content>\n @if (conversationFlowStateService.flowState(); as flowState) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4 mt-2\">\n <!-- Goal State -->\n <div class=\"p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFAF</span> Goal Progress\n </h3>\n <div class=\"flex items-end gap-4 mb-3\">\n <div class=\"text-5xl font-black leading-none\">\n {{ flowState.goal?.value || 0 }}\n </div>\n @if (flowState.goal?.deltaPoints !== undefined) {\n <div class=\"text-xl font-bold mb-1\" \n [class.text-green-600]=\"flowState.goal.deltaPoints > 0\"\n [class.text-red-600]=\"flowState.goal.deltaPoints < 0\"\n [class.opacity-50]=\"flowState.goal.deltaPoints === 0\">\n {{ flowState.goal.deltaPoints > 0 ? '+' : '' }}{{ flowState.goal.deltaPoints }}\n </div>\n }\n </div>\n \n @if (flowState.intervention?.message) {\n <div class=\"mt-4 p-3 rounded border-l-4 text-sm italic shadow-sm opacity-80\">\n \"{{ flowState.intervention.message }}\"\n </div>\n }\n </div>\n\n <!-- Mood State -->\n <div class=\"p-4 border rounded-lg shadow-sm flex flex-col\">\n <h3 class=\"text-lg font-bold mb-2 flex items-center gap-2\">\n <span>\uD83C\uDFAD</span> Agent Mood\n </h3>\n <div class=\"flex-grow flex items-center justify-center\">\n @if (flowState.moodState?.value) {\n <div class=\"text-4xl font-black uppercase tracking-widest text-center drop-shadow-sm\">\n {{ flowState.moodState.value }}\n </div>\n } @else {\n <div class=\"text-xl font-medium italic opacity-50\">Neutral</div>\n }\n </div>\n </div>\n </div>\n\n @if (flowState.challenges?.length) {\n <div class=\"mb-4 p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFC6</span> Completed Challenges\n </h3>\n <ul class=\"list-disc pl-5 space-y-1\">\n @for (challenge of flowState.challenges; track challenge) {\n <li>{{ challenge }}</li>\n }\n </ul>\n </div>\n }\n\n <details class=\"mt-4\">\n <summary class=\"cursor-pointer text-sm font-medium\">Raw JSON State</summary>\n <pre class=\"mt-2 p-2 rounded text-xs overflow-auto\">{{ flowState | safeJson }}</pre>\n </details>\n } @else {\n <p class=\"italic mt-2 opacity-50\">No conversation state available.</p>\n }\n\n <details class=\"mt-2\">\n <summary class=\"cursor-pointer text-sm font-medium\">Current Prompt</summary>\n <pre class=\"mt-2 p-2 rounded text-xs overflow-auto\">{{ systemPromptForFlow() }}</pre>\n </details>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"2\">\n <ng-template #content>\n <div>\n <p-button label=\"Exportar todo\" (click)=\"exportChat('all')\" />\n <p-button label=\"Exportar conversaci\u00F3n\" (click)=\"exportChat('conversation')\" />\n <p-button [label]=\"'Vista ' + viewMode()\" (click)=\"toggleViewMode()\" />\n </div>\n <dc-prompt-preview [messages]=\"messageStateService.getMessagesSignal()()\" [view]=\"viewMode()\"></dc-prompt-preview>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"6\">\n <ng-template #content>\n <dc-messages-state-inspector [service]=\"messageStateService\"></dc-messages-state-inspector>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"3\">\n <ng-template #content>\n <dc-cost-details></dc-cost-details>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"4\">\n <ng-template #content>\n <!-- MORE INFO -->\n\n <details>\n <summary>Estado Global Conversation Flow</summary>\n <p>Goal</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.goal | safeJson }}</pre>\n <p>Trigger User Message</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.triggerTasks?.onUserMessage | safeJson }}</pre>\n <p>Trigger Assistant Message</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.triggerTasks?.onAssistantMessage | safeJson }}</pre>\n <p>Conditions</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.dynamicConditions | safeJson }}</pre>\n <p>Tools</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.tools | safeJson }}</pre>\n <p>Challenges</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.challenges | safeJson }}</pre>\n </details>\n\n <details>\n <summary>Conversation Settings (Debug)</summary>\n <pre>{{ conversationService.conversationSettings$() | safeJson }}</pre>\n </details>\n\n <details>\n <summary>Agent Card</summary>\n <pre>{{ agentCard | safeJson }}</pre>\n </details>\n\n <details>\n <summary>User Settings</summary>\n <pre>{{ chatUserSettings | safeJson }}</pre>\n </details>\n\n <!-- MORE INFO ends Here -->\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"7\">\n <ng-template #content>\n <dc-agent-card-detail [entityData]=\"agentCardStateService.agentCard$()\" />\n </ng-template>\n </p-tabpanel>\n </p-tabpanels>\n </p-tabs>\n</div>\n", styles: [":host{overflow-y:scroll}.info-content{display:flex;flex-direction:column;gap:1rem;padding:1rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "ngmodule", type: SliderModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: DividerModule }, { kind: "component", type: i2$3.Divider, selector: "p-divider", inputs: ["styleClass", "layout", "type", "align"] }, { kind: "component", type: PromptPreviewComponent, selector: "dc-prompt-preview", inputs: ["messages", "jailBrake", "view"] }, { kind: "ngmodule", type: TabsModule }, { kind: "component", type: i2$5.Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i2$5.TabPanels, selector: "p-tabpanels" }, { kind: "component", type: i2$5.TabPanel, selector: "p-tabpanel", inputs: ["lazy", "value"], outputs: ["valueChange"] }, { kind: "component", type: i2$5.TabList, selector: "p-tablist" }, { kind: "component", type: i2$5.Tab, selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: CostDetailsComponent, selector: "dc-cost-details" }, { kind: "component", type: MessagesStateInspectorComponent, selector: "dc-messages-state-inspector", inputs: ["service"] }, { kind: "component", type: DcAgentCardDetailComponent, selector: "dc-agent-card-detail", inputs: ["entityData"] }, { kind: "component", type: ConversationSummaryComponent, selector: "dc-conversation-summary", inputs: ["agentCard", "parentInjector"], outputs: ["completeEvent"] }, { kind: "pipe", type: SafeJsonPipe, name: "safeJson" }] }); }
|
|
6296
6650
|
}
|
|
6297
6651
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ConversationInspector, decorators: [{
|
|
6298
6652
|
type: Component,
|
|
6299
|
-
args: [{ selector: 'dc-conversation-info', standalone: true, imports: [CommonModule, SafeJsonPipe, MarkdownComponent, SliderModule, ButtonModule, FormsModule, DividerModule, PromptPreviewComponent, TabsModule, CostDetailsComponent, MessagesStateInspectorComponent, DcAgentCardDetailComponent, ConversationSummaryComponent], template: "<div class=\"info-content\">\n <p-tabs value=\"0\">\n <p-tablist>\n <p-tab value=\"0\">\uD83D\uDCCA Summary</p-tab>\n <p-tab value=\"1\">\uD83D\uDD00 Dynamic Flow</p-tab>\n <p-tab value=\"6\">\uD83D\uDCE6 Messages State</p-tab>\n <p-tab value=\"7\">\uD83D\uDC64 Preview Card</p-tab>\n <p-tab value=\"5\">\uD83D\uDCBE Conversation State</p-tab>\n <p-tab value=\"2\">\uD83D\uDCAC Conversation Messages</p-tab>\n <p-tab value=\"3\">\uD83D\uDCB0 Pricing</p-tab>\n <p-tab value=\"4\">\uD83D\uDC1B More</p-tab>\n </p-tablist>\n <p-tabpanels>\n <p-tabpanel value=\"0\">\n <ng-template #content>\n @if (conversationFlowStateService.flowState(); as flowState) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4 mt-2\">\n <!-- Goal State -->\n <div class=\"p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFAF</span> Goal Progress\n </h3>\n <div class=\"flex items-end gap-4 mb-3\">\n <div class=\"text-5xl font-black leading-none\">\n {{ flowState.goal?.value || 0 }}\n </div>\n @if (flowState.goal?.deltaPoints !== undefined) {\n <div class=\"text-xl font-bold mb-1\" \n [class.text-green-600]=\"flowState.goal.deltaPoints > 0\"\n [class.text-red-600]=\"flowState.goal.deltaPoints < 0\"\n [class.opacity-50]=\"flowState.goal.deltaPoints === 0\">\n {{ flowState.goal.deltaPoints > 0 ? '+' : '' }}{{ flowState.goal.deltaPoints }}\n </div>\n }\n </div>\n \n @if (flowState.intervention?.message) {\n <div class=\"mt-4 p-3 rounded border-l-4 text-sm italic shadow-sm opacity-80\">\n \"{{ flowState.intervention.message }}\"\n </div>\n }\n </div>\n\n <!-- Mood State -->\n <div class=\"p-4 border rounded-lg shadow-sm flex flex-col\">\n <h3 class=\"text-lg font-bold mb-2 flex items-center gap-2\">\n <span>\uD83C\uDFAD</span> Agent Mood\n </h3>\n <div class=\"flex-grow flex items-center justify-center\">\n @if (flowState.moodState?.value) {\n <div class=\"text-4xl font-black uppercase tracking-widest text-center drop-shadow-sm\">\n {{ flowState.moodState.value }}\n </div>\n } @else {\n <div class=\"text-xl font-medium italic opacity-50\">Neutral</div>\n }\n </div>\n </div>\n </div>\n }\n <dc-conversation-summary [agentCard]=\"agentCard\" [parentInjector]=\"config.data.injector\" (completeEvent)=\"complete()\"></dc-conversation-summary>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"1\">\n <ng-template #content>\n <!-- Dynamic Flow -->\n\n <p-divider>Conversation Flow</p-divider>\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (dynamicFlowService.conversationFlowConfig()?.goal?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Goal</p>\n </div>\n @if(dynamicFlowService.conversationFlowConfig()?.goal?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.triggerTasks?.onUserMessage?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Trigger User Message</p>\n </div>\n @if(agentCardStateService.conversationFlow$()?.triggerTasks?.onUserMessage?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.triggerTasks?.onAssistantMessage?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Trigger Assistant Message</p>\n </div>\n @if(agentCardStateService.conversationFlow$()?.triggerTasks?.onAssistantMessage?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <h3>Herramientas</h3>\n @for (tool of agentCardStateService.conversationFlow$()?.tools; track tool.name) {\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (tool.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">{{ tool.name }}</p>\n </div>\n @if(tool.description) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ tool.description }} </div>\n }\n </div>\n }\n\n <h3>Challenges Retos</h3>\n @for (challenge of dynamicFlowService.conversationFlowConfig()?.challenges; track challenge.name) {\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (challenge.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n\n <p class=\"font-semibold\"> {{ challenge.emoji }} {{ challenge.name }}</p>\n </div>\n @if(challenge.description) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ challenge.description }} </div>\n }\n </div>\n }\n <h5>Conditions</h5>\n\n @for (condition of dynamicFlowService.conversationFlowConfig()?.dynamicConditions; track $index) {\n <div class=\"pl-2 mt-1 text-sm text-gray-600 italic\"> When the {{ condition.what }} is {{ condition.when }} than {{ condition.value }} Do </div>\n <ol>\n @for (doAction of condition.do; track $index) {\n <li class=\"pl-5 mt-1 text-sm text-gray-600 italic\">\n {{ $index }})\n <span style=\"color: brown\"> {{ doAction.actionType }}</span>\n <span style=\"color: green\"> {{ doAction.systemPromptType }}</span>\n <span style=\"color: green\"> {{ doAction.dynamicFlowTaskType }}</span>\n\n to\n <span style=\"color: rgb(49, 23, 177)\"> {{ doAction.enabled }}</span>\n \"{{ doAction.prompt }}\"\n </li>\n }\n </ol>\n }\n\n <p>Challenges</p>\n <pre>{{ agentCardStateService.conversationFlow$()?.challenges | safeJson }}</pre>\n\n <p-divider>Performance Evaluation</p-divider>\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.enablePerformanceAnalysis !== false) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Performance Prompt</p>\n <span class=\"text-xs text-gray-400 ml-2\" pTooltip=\"Minimum messages required to trigger evaluation\">(MIN_MESSAGES_EVALUATE: 4 hardcoded)</span>\n </div>\n @if(agentCardStateService.conversationFlow$()?.enablePerformanceAnalysis !== false) {\n @if(agentCardStateService.conversationFlow$()?.performancePrompt; as prompt) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> \n <markdown>\n {{ prompt }} \n </markdown>\n </div>\n } @else {\n <div class=\"pl-5 mt-1 text-sm text-gray-400 italic\"> No custom performance prompt defined (using default). </div>\n }\n } @else {\n <div class=\"pl-5 mt-1 text-sm text-gray-400 italic\"> Performance analysis disabled. </div>\n }\n </div>\n\n <p-divider>System Evaluation Prompt</p-divider>\n <details>\n <summary>Show Prompt</summary>\n <pre>{{ systemPromptForFlow() }}</pre>\n </details>\n\n <!-- Dynamic Flow ends Here -->\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"5\">\n <ng-template #content>\n @if (conversationFlowStateService.flowState(); as flowState) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4 mt-2\">\n <!-- Goal State -->\n <div class=\"p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFAF</span> Goal Progress\n </h3>\n <div class=\"flex items-end gap-4 mb-3\">\n <div class=\"text-5xl font-black leading-none\">\n {{ flowState.goal?.value || 0 }}\n </div>\n @if (flowState.goal?.deltaPoints !== undefined) {\n <div class=\"text-xl font-bold mb-1\" \n [class.text-green-600]=\"flowState.goal.deltaPoints > 0\"\n [class.text-red-600]=\"flowState.goal.deltaPoints < 0\"\n [class.opacity-50]=\"flowState.goal.deltaPoints === 0\">\n {{ flowState.goal.deltaPoints > 0 ? '+' : '' }}{{ flowState.goal.deltaPoints }}\n </div>\n }\n </div>\n \n @if (flowState.intervention?.message) {\n <div class=\"mt-4 p-3 rounded border-l-4 text-sm italic shadow-sm opacity-80\">\n \"{{ flowState.intervention.message }}\"\n </div>\n }\n </div>\n\n <!-- Mood State -->\n <div class=\"p-4 border rounded-lg shadow-sm flex flex-col\">\n <h3 class=\"text-lg font-bold mb-2 flex items-center gap-2\">\n <span>\uD83C\uDFAD</span> Agent Mood\n </h3>\n <div class=\"flex-grow flex items-center justify-center\">\n @if (flowState.moodState?.value) {\n <div class=\"text-4xl font-black uppercase tracking-widest text-center drop-shadow-sm\">\n {{ flowState.moodState.value }}\n </div>\n } @else {\n <div class=\"text-xl font-medium italic opacity-50\">Neutral</div>\n }\n </div>\n </div>\n </div>\n\n @if (flowState.challenges?.length) {\n <div class=\"mb-4 p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFC6</span> Completed Challenges\n </h3>\n <ul class=\"list-disc pl-5 space-y-1\">\n @for (challenge of flowState.challenges; track challenge) {\n <li>{{ challenge }}</li>\n }\n </ul>\n </div>\n }\n\n <details class=\"mt-4\">\n <summary class=\"cursor-pointer text-sm font-medium\">Raw JSON State</summary>\n <pre class=\"mt-2 p-2 rounded text-xs overflow-auto\">{{ flowState | safeJson }}</pre>\n </details>\n } @else {\n <p class=\"italic mt-2 opacity-50\">No conversation state available.</p>\n }\n\n <details class=\"mt-2\">\n <summary class=\"cursor-pointer text-sm font-medium\">Current Prompt</summary>\n <pre class=\"mt-2 p-2 rounded text-xs overflow-auto\">{{ systemPromptForFlow() }}</pre>\n </details>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"2\">\n <ng-template #content>\n <div>\n <p-button label=\"Exportar todo\" (click)=\"exportChat('all')\" />\n <p-button label=\"Exportar conversaci\u00F3n\" (click)=\"exportChat('conversation')\" />\n <p-button [label]=\"'Vista ' + viewMode()\" (click)=\"toggleViewMode()\" />\n </div>\n <dc-prompt-preview [messages]=\"messageStateService.getMessagesSignal()()\" [view]=\"viewMode()\"></dc-prompt-preview>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"6\">\n <ng-template #content>\n <dc-messages-state-inspector></dc-messages-state-inspector>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"3\">\n <ng-template #content>\n <dc-cost-details></dc-cost-details>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"4\">\n <ng-template #content>\n <!-- MORE INFO -->\n\n <details>\n <summary>Estado Global Conversation Flow</summary>\n <p>Goal</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.goal | safeJson }}</pre>\n <p>Trigger User Message</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.triggerTasks?.onUserMessage | safeJson }}</pre>\n <p>Trigger Assistant Message</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.triggerTasks?.onAssistantMessage | safeJson }}</pre>\n <p>Conditions</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.dynamicConditions | safeJson }}</pre>\n <p>Tools</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.tools | safeJson }}</pre>\n <p>Challenges</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.challenges | safeJson }}</pre>\n </details>\n\n <details>\n <summary>Conversation Settings (Debug)</summary>\n <pre>{{ conversationService.conversationSettings$() | safeJson }}</pre>\n </details>\n\n <details>\n <summary>Agent Card</summary>\n <pre>{{ agentCard | safeJson }}</pre>\n </details>\n\n <details>\n <summary>User Settings</summary>\n <pre>{{ chatUserSettings | safeJson }}</pre>\n </details>\n\n <!-- MORE INFO ends Here -->\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"7\">\n <ng-template #content>\n <dc-agent-card-detail [entityData]=\"agentCardStateService.agentCard$()\" />\n </ng-template>\n </p-tabpanel>\n </p-tabpanels>\n </p-tabs>\n</div>\n", styles: [":host{overflow-y:scroll}.info-content{display:flex;flex-direction:column;gap:1rem;padding:1rem}\n"] }]
|
|
6653
|
+
args: [{ selector: 'dc-conversation-info', standalone: true, imports: [CommonModule, SafeJsonPipe, MarkdownComponent, SliderModule, ButtonModule, FormsModule, DividerModule, PromptPreviewComponent, TabsModule, CostDetailsComponent, MessagesStateInspectorComponent, DcAgentCardDetailComponent, ConversationSummaryComponent], template: "<div class=\"info-content\">\n <p-tabs value=\"0\">\n <p-tablist>\n <p-tab value=\"0\">\uD83D\uDCCA Summary</p-tab>\n <p-tab value=\"1\">\uD83D\uDD00 Dynamic Flow</p-tab>\n <p-tab value=\"6\">\uD83D\uDCE6 Messages State</p-tab>\n <p-tab value=\"7\">\uD83D\uDC64 Preview Card</p-tab>\n <p-tab value=\"5\">\uD83D\uDCBE Conversation State</p-tab>\n <p-tab value=\"2\">\uD83D\uDCAC Conversation Messages</p-tab>\n <p-tab value=\"3\">\uD83D\uDCB0 Pricing</p-tab>\n <p-tab value=\"4\">\uD83D\uDC1B More</p-tab>\n </p-tablist>\n <p-tabpanels>\n <p-tabpanel value=\"0\">\n <ng-template #content>\n @if (conversationFlowStateService.flowState(); as flowState) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4 mt-2\">\n <!-- Goal State -->\n <div class=\"p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFAF</span> Goal Progress\n </h3>\n <div class=\"flex items-end gap-4 mb-3\">\n <div class=\"text-5xl font-black leading-none\">\n {{ flowState.goal?.value || 0 }}\n </div>\n @if (flowState.goal?.deltaPoints !== undefined) {\n <div class=\"text-xl font-bold mb-1\" \n [class.text-green-600]=\"flowState.goal.deltaPoints > 0\"\n [class.text-red-600]=\"flowState.goal.deltaPoints < 0\"\n [class.opacity-50]=\"flowState.goal.deltaPoints === 0\">\n {{ flowState.goal.deltaPoints > 0 ? '+' : '' }}{{ flowState.goal.deltaPoints }}\n </div>\n }\n </div>\n \n @if (flowState.intervention?.message) {\n <div class=\"mt-4 p-3 rounded border-l-4 text-sm italic shadow-sm opacity-80\">\n \"{{ flowState.intervention.message }}\"\n </div>\n }\n </div>\n\n <!-- Mood State -->\n <div class=\"p-4 border rounded-lg shadow-sm flex flex-col\">\n <h3 class=\"text-lg font-bold mb-2 flex items-center gap-2\">\n <span>\uD83C\uDFAD</span> Agent Mood\n </h3>\n <div class=\"flex-grow flex items-center justify-center\">\n @if (flowState.moodState?.value) {\n <div class=\"text-4xl font-black uppercase tracking-widest text-center drop-shadow-sm\">\n {{ flowState.moodState.value }}\n </div>\n } @else {\n <div class=\"text-xl font-medium italic opacity-50\">Neutral</div>\n }\n </div>\n </div>\n </div>\n }\n <dc-conversation-summary [agentCard]=\"agentCard\" [parentInjector]=\"config.data.injector\" (completeEvent)=\"complete()\"></dc-conversation-summary>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"1\">\n <ng-template #content>\n <!-- Dynamic Flow -->\n\n <p-divider>Conversation Flow</p-divider>\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (dynamicFlowService.conversationFlowConfig()?.goal?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Goal</p>\n </div>\n @if(dynamicFlowService.conversationFlowConfig()?.goal?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.triggerTasks?.onUserMessage?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Trigger User Message</p>\n </div>\n @if(agentCardStateService.conversationFlow$()?.triggerTasks?.onUserMessage?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.triggerTasks?.onAssistantMessage?.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Trigger Assistant Message</p>\n </div>\n @if(agentCardStateService.conversationFlow$()?.triggerTasks?.onAssistantMessage?.task; as task) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ task }} </div>\n }\n </div>\n\n <h3>Herramientas</h3>\n @for (tool of agentCardStateService.conversationFlow$()?.tools; track tool.name) {\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (tool.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">{{ tool.name }}</p>\n </div>\n @if(tool.description) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ tool.description }} </div>\n }\n </div>\n }\n\n <h3>Challenges Retos</h3>\n @for (challenge of dynamicFlowService.conversationFlowConfig()?.challenges; track challenge.name) {\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (challenge.enabled) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n\n <p class=\"font-semibold\"> {{ challenge.emoji }} {{ challenge.name }}</p>\n </div>\n @if(challenge.description) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> {{ challenge.description }} </div>\n }\n </div>\n }\n <h5>Conditions</h5>\n\n @for (condition of dynamicFlowService.conversationFlowConfig()?.dynamicConditions; track $index) {\n <div class=\"pl-2 mt-1 text-sm text-gray-600 italic\"> When the {{ condition.what }} is {{ condition.when }} than {{ condition.value }} Do </div>\n <ol>\n @for (doAction of condition.do; track $index) {\n <li class=\"pl-5 mt-1 text-sm text-gray-600 italic\">\n {{ $index }})\n <span style=\"color: brown\"> {{ doAction.actionType }}</span>\n <span style=\"color: green\"> {{ doAction.systemPromptType }}</span>\n <span style=\"color: green\"> {{ doAction.dynamicFlowTaskType }}</span>\n\n to\n <span style=\"color: rgb(49, 23, 177)\"> {{ doAction.enabled }}</span>\n \"{{ doAction.prompt }}\"\n </li>\n }\n </ol>\n }\n\n <p>Challenges</p>\n <pre>{{ agentCardStateService.conversationFlow$()?.challenges | safeJson }}</pre>\n\n <p-divider>Performance Evaluation</p-divider>\n <div class=\"mb-2\">\n <div class=\"flex items-center gap-2\">\n @if (agentCardStateService.conversationFlow$()?.enablePerformanceAnalysis !== false) {\n <div class=\"w-3 h-3 bg-green-500 rounded-full\" pTooltip=\"Active\"></div>\n } @else {\n <div class=\"w-3 h-3 bg-red-500 rounded-full\" pTooltip=\"Inactive\"></div>\n }\n <p class=\"font-semibold\">Performance Prompt</p>\n <span class=\"text-xs text-gray-400 ml-2\" pTooltip=\"Minimum messages required to trigger evaluation\">(MIN_MESSAGES_EVALUATE: 4 hardcoded)</span>\n </div>\n @if(agentCardStateService.conversationFlow$()?.enablePerformanceAnalysis !== false) {\n @if(agentCardStateService.conversationFlow$()?.performancePrompt; as prompt) {\n <div class=\"pl-5 mt-1 text-sm text-gray-600 italic\"> \n <markdown>\n {{ prompt }} \n </markdown>\n </div>\n } @else {\n <div class=\"pl-5 mt-1 text-sm text-gray-400 italic\"> No custom performance prompt defined (using default). </div>\n }\n } @else {\n <div class=\"pl-5 mt-1 text-sm text-gray-400 italic\"> Performance analysis disabled. </div>\n }\n </div>\n\n <p-divider>System Evaluation Prompt</p-divider>\n <details>\n <summary>Show Prompt</summary>\n <pre>{{ systemPromptForFlow() }}</pre>\n </details>\n\n <!-- Dynamic Flow ends Here -->\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"5\">\n <ng-template #content>\n @if (conversationFlowStateService.flowState(); as flowState) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4 mb-4 mt-2\">\n <!-- Goal State -->\n <div class=\"p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFAF</span> Goal Progress\n </h3>\n <div class=\"flex items-end gap-4 mb-3\">\n <div class=\"text-5xl font-black leading-none\">\n {{ flowState.goal?.value || 0 }}\n </div>\n @if (flowState.goal?.deltaPoints !== undefined) {\n <div class=\"text-xl font-bold mb-1\" \n [class.text-green-600]=\"flowState.goal.deltaPoints > 0\"\n [class.text-red-600]=\"flowState.goal.deltaPoints < 0\"\n [class.opacity-50]=\"flowState.goal.deltaPoints === 0\">\n {{ flowState.goal.deltaPoints > 0 ? '+' : '' }}{{ flowState.goal.deltaPoints }}\n </div>\n }\n </div>\n \n @if (flowState.intervention?.message) {\n <div class=\"mt-4 p-3 rounded border-l-4 text-sm italic shadow-sm opacity-80\">\n \"{{ flowState.intervention.message }}\"\n </div>\n }\n </div>\n\n <!-- Mood State -->\n <div class=\"p-4 border rounded-lg shadow-sm flex flex-col\">\n <h3 class=\"text-lg font-bold mb-2 flex items-center gap-2\">\n <span>\uD83C\uDFAD</span> Agent Mood\n </h3>\n <div class=\"flex-grow flex items-center justify-center\">\n @if (flowState.moodState?.value) {\n <div class=\"text-4xl font-black uppercase tracking-widest text-center drop-shadow-sm\">\n {{ flowState.moodState.value }}\n </div>\n } @else {\n <div class=\"text-xl font-medium italic opacity-50\">Neutral</div>\n }\n </div>\n </div>\n </div>\n\n @if (flowState.challenges?.length) {\n <div class=\"mb-4 p-4 border rounded-lg shadow-sm\">\n <h3 class=\"text-lg font-bold mb-3 flex items-center gap-2\">\n <span>\uD83C\uDFC6</span> Completed Challenges\n </h3>\n <ul class=\"list-disc pl-5 space-y-1\">\n @for (challenge of flowState.challenges; track challenge) {\n <li>{{ challenge }}</li>\n }\n </ul>\n </div>\n }\n\n <details class=\"mt-4\">\n <summary class=\"cursor-pointer text-sm font-medium\">Raw JSON State</summary>\n <pre class=\"mt-2 p-2 rounded text-xs overflow-auto\">{{ flowState | safeJson }}</pre>\n </details>\n } @else {\n <p class=\"italic mt-2 opacity-50\">No conversation state available.</p>\n }\n\n <details class=\"mt-2\">\n <summary class=\"cursor-pointer text-sm font-medium\">Current Prompt</summary>\n <pre class=\"mt-2 p-2 rounded text-xs overflow-auto\">{{ systemPromptForFlow() }}</pre>\n </details>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"2\">\n <ng-template #content>\n <div>\n <p-button label=\"Exportar todo\" (click)=\"exportChat('all')\" />\n <p-button label=\"Exportar conversaci\u00F3n\" (click)=\"exportChat('conversation')\" />\n <p-button [label]=\"'Vista ' + viewMode()\" (click)=\"toggleViewMode()\" />\n </div>\n <dc-prompt-preview [messages]=\"messageStateService.getMessagesSignal()()\" [view]=\"viewMode()\"></dc-prompt-preview>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"6\">\n <ng-template #content>\n <dc-messages-state-inspector [service]=\"messageStateService\"></dc-messages-state-inspector>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"3\">\n <ng-template #content>\n <dc-cost-details></dc-cost-details>\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"4\">\n <ng-template #content>\n <!-- MORE INFO -->\n\n <details>\n <summary>Estado Global Conversation Flow</summary>\n <p>Goal</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.goal | safeJson }}</pre>\n <p>Trigger User Message</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.triggerTasks?.onUserMessage | safeJson }}</pre>\n <p>Trigger Assistant Message</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.triggerTasks?.onAssistantMessage | safeJson }}</pre>\n <p>Conditions</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.dynamicConditions | safeJson }}</pre>\n <p>Tools</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.tools | safeJson }}</pre>\n <p>Challenges</p>\n <pre>{{ dynamicFlowService.conversationFlowConfig()?.challenges | safeJson }}</pre>\n </details>\n\n <details>\n <summary>Conversation Settings (Debug)</summary>\n <pre>{{ conversationService.conversationSettings$() | safeJson }}</pre>\n </details>\n\n <details>\n <summary>Agent Card</summary>\n <pre>{{ agentCard | safeJson }}</pre>\n </details>\n\n <details>\n <summary>User Settings</summary>\n <pre>{{ chatUserSettings | safeJson }}</pre>\n </details>\n\n <!-- MORE INFO ends Here -->\n </ng-template>\n </p-tabpanel>\n <p-tabpanel value=\"7\">\n <ng-template #content>\n <dc-agent-card-detail [entityData]=\"agentCardStateService.agentCard$()\" />\n </ng-template>\n </p-tabpanel>\n </p-tabpanels>\n </p-tabs>\n</div>\n", styles: [":host{overflow-y:scroll}.info-content{display:flex;flex-direction:column;gap:1rem;padding:1rem}\n"] }]
|
|
6300
6654
|
}], ctorParameters: () => [], propDecorators: { completeEvent: [{ type: i0.Output, args: ["completeEvent"] }] } });
|
|
6301
6655
|
|
|
6302
6656
|
class ConversationInfoService {
|
|
@@ -6347,19 +6701,32 @@ class DCChatComponent {
|
|
|
6347
6701
|
// 📥 Input Signals
|
|
6348
6702
|
this.parseDict = input({}, ...(ngDevMode ? [{ debugName: "parseDict" }] : /* istanbul ignore next */ []));
|
|
6349
6703
|
// 📤 Outputs
|
|
6704
|
+
/** @deprecated Use ChatMonitorService instead — el canal oficial es el servicio compartido. */
|
|
6350
6705
|
this.chatEvent = output(); // Notifies about various events happening inside the chat
|
|
6706
|
+
/** @deprecated Use ChatMonitorService instead — el canal oficial es el servicio compartido. */
|
|
6351
6707
|
this.goalCompleted = output(); // notifies when user completes goal (score reaches 100)
|
|
6352
6708
|
// readonly wordClicked = output<WordData>(); // Replaced by sendMessage with ChatEventType.WordClicked
|
|
6353
6709
|
// Signals States
|
|
6354
6710
|
this.viewMode = signal('chat', ...(ngDevMode ? [{ debugName: "viewMode" }] : /* istanbul ignore next */ []));
|
|
6355
6711
|
// public messages = signal<ChatMessage[]>([]);
|
|
6356
6712
|
// States
|
|
6357
|
-
this.micSettings = { micMode: 'recognition', lang: 'en' };
|
|
6358
6713
|
this.isAdmin = true;
|
|
6714
|
+
const micRecordingService = inject(MicRecordingService);
|
|
6715
|
+
const orchestrationService = inject(MessageOrchestrationService);
|
|
6716
|
+
// Pause mic voice/silence detection when AI is thinking, playing audio, or conversation is paused
|
|
6717
|
+
effect(() => {
|
|
6718
|
+
const isThinking = this.conversationService.isThinking()();
|
|
6719
|
+
const isAudioPlaying = orchestrationService.messageToPlay$() !== null;
|
|
6720
|
+
const isPaused = this.conversationFlowStateService.isPaused();
|
|
6721
|
+
const shouldPauseMic = isThinking || isAudioPlaying || isPaused;
|
|
6722
|
+
micRecordingService.isPaused.set(shouldPauseMic);
|
|
6723
|
+
console.log('DCChatComponent: MicRecordingService isPaused updated to:', shouldPauseMic);
|
|
6724
|
+
});
|
|
6359
6725
|
// Subscribe to score updates using effect
|
|
6360
6726
|
effect(() => {
|
|
6361
6727
|
const score = this.conversationFlowStateService.flowState().goal.value;
|
|
6362
6728
|
if (score >= 100) {
|
|
6729
|
+
// TODO: check if i can use the monitor in the father so subscribe to this.
|
|
6363
6730
|
this.goalCompleted.emit();
|
|
6364
6731
|
}
|
|
6365
6732
|
});
|
|
@@ -6373,19 +6740,6 @@ class DCChatComponent {
|
|
|
6373
6740
|
// this.conversationService.notifyWordClicked(null);
|
|
6374
6741
|
}
|
|
6375
6742
|
});
|
|
6376
|
-
// Effect to watch for audio playback events
|
|
6377
|
-
effect(() => {
|
|
6378
|
-
const message = this.chatMonitorService.messageAudioWillPlay$();
|
|
6379
|
-
if (message) {
|
|
6380
|
-
this.chatEvent.emit({ type: ChatEventType.AudioStarted, payload: message });
|
|
6381
|
-
}
|
|
6382
|
-
});
|
|
6383
|
-
// Subscribe to mood updates
|
|
6384
|
-
this.moodSubscription = this.conversationFlowStateService.moodUpdated$.subscribe((mood) => {
|
|
6385
|
-
if (mood) {
|
|
6386
|
-
this.chatEvent.emit({ type: ChatEventType.MoodDetected, payload: mood });
|
|
6387
|
-
}
|
|
6388
|
-
});
|
|
6389
6743
|
}
|
|
6390
6744
|
async ngOnInit() {
|
|
6391
6745
|
this.conversationService.setDestroyed(false);
|
|
@@ -6393,6 +6747,10 @@ class DCChatComponent {
|
|
|
6393
6747
|
if (!this.chatUserSettings) {
|
|
6394
6748
|
this.chatUserSettings = this.userService.user()?.settings?.conversation;
|
|
6395
6749
|
}
|
|
6750
|
+
if (!this.micSettings) {
|
|
6751
|
+
const targetLang = this.agentCard?.voice?.main?.lang || this.userService.user()?.settings?.targetLanguage || 'en';
|
|
6752
|
+
this.micSettings = buildMicSettings(this.chatUserSettings?.micMode, targetLang);
|
|
6753
|
+
}
|
|
6396
6754
|
if (this.conversationSettings) {
|
|
6397
6755
|
await this.conversationService.initConversationWithSettings(this.conversationSettings, this.conversationFlow);
|
|
6398
6756
|
}
|
|
@@ -6406,7 +6764,6 @@ class DCChatComponent {
|
|
|
6406
6764
|
// Mark conversation as destroyed to prevent async operations
|
|
6407
6765
|
this.conversationService.setDestroyed(true);
|
|
6408
6766
|
this.chatMonitorService.cleanMonitorVars();
|
|
6409
|
-
this.moodSubscription?.unsubscribe();
|
|
6410
6767
|
// Ensure the microphone is stopped when the chat component is destroyed
|
|
6411
6768
|
this.chatFooterComponent?.stopMic();
|
|
6412
6769
|
this.checkPerformanceAnalysis();
|
|
@@ -6464,7 +6821,7 @@ class DCChatComponent {
|
|
|
6464
6821
|
this.goalCompleted.emit();
|
|
6465
6822
|
}
|
|
6466
6823
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DCChatComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6467
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DCChatComponent, isStandalone: true, selector: "dc-chat", inputs: { chatUserSettings: { classPropertyName: "chatUserSettings", publicName: "chatUserSettings", isSignal: false, isRequired: false, transformFunction: null }, conversationSettings: { classPropertyName: "conversationSettings", publicName: "conversationSettings", isSignal: false, isRequired: false, transformFunction: null }, conversationFlow: { classPropertyName: "conversationFlow", publicName: "conversationFlow", isSignal: false, isRequired: false, transformFunction: null }, agentCard: { classPropertyName: "agentCard", publicName: "agentCard", isSignal: false, isRequired: false, transformFunction: null }, observerConfig: { classPropertyName: "observerConfig", publicName: "observerConfig", isSignal: false, isRequired: false, transformFunction: null }, parseDict: { classPropertyName: "parseDict", publicName: "parseDict", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { chatEvent: "chatEvent", goalCompleted: "goalCompleted" }, providers: [
|
|
6824
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DCChatComponent, isStandalone: true, selector: "dc-chat", inputs: { chatUserSettings: { classPropertyName: "chatUserSettings", publicName: "chatUserSettings", isSignal: false, isRequired: false, transformFunction: null }, conversationSettings: { classPropertyName: "conversationSettings", publicName: "conversationSettings", isSignal: false, isRequired: false, transformFunction: null }, conversationFlow: { classPropertyName: "conversationFlow", publicName: "conversationFlow", isSignal: false, isRequired: false, transformFunction: null }, agentCard: { classPropertyName: "agentCard", publicName: "agentCard", isSignal: false, isRequired: false, transformFunction: null }, micSettings: { classPropertyName: "micSettings", publicName: "micSettings", isSignal: false, isRequired: false, transformFunction: null }, observerConfig: { classPropertyName: "observerConfig", publicName: "observerConfig", isSignal: false, isRequired: false, transformFunction: null }, parseDict: { classPropertyName: "parseDict", publicName: "parseDict", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { chatEvent: "chatEvent", goalCompleted: "goalCompleted" }, providers: [
|
|
6468
6825
|
ConversationService,
|
|
6469
6826
|
AIGenerationService,
|
|
6470
6827
|
EvaluationService,
|
|
@@ -6476,7 +6833,8 @@ class DCChatComponent {
|
|
|
6476
6833
|
ConversationFlowStateService,
|
|
6477
6834
|
DynamicFlowService,
|
|
6478
6835
|
AgentCardStateService,
|
|
6479
|
-
ChatMonitorService
|
|
6836
|
+
// ChatMonitorService removido del providers[] local: ahora la instancia la provee el
|
|
6837
|
+
// componente ancestro (ConversationCardDetailsComponent) para compartirla con dc-agent-card-avatar.
|
|
6480
6838
|
ContextEngineService,
|
|
6481
6839
|
], viewQueries: [{ propertyName: "chatFooterComponent", first: true, predicate: ChatFooterComponent, descendants: true }], ngImport: i0, template: "<div class=\"chat-container\">\n <dc-chat-header\n [agentCard]=\"agentCard\"\n [viewMode]=\"viewMode()\"\n (showInfoEvent)=\"showInfo()\"\n (settingsClickEvent)=\"changeUserChatSettings()\"\n (restartConversationEvent)=\"restartConversation($event)\"\n (completeEvent)=\"complete()\"\n (viewModeChanged)=\"viewMode.set($event)\">\n </dc-chat-header>\n\n @if (viewMode() === 'chat') {\n <dc-chat-messages-list [chatUserSettings]=\"chatUserSettings\"> </dc-chat-messages-list>\n <dc-chat-footer [micSettings]=\"micSettings\"> </dc-chat-footer>\n } @else {\n <dc-immersive-chat [micSettings]=\"micSettings\"></dc-immersive-chat>\n }\n\n <dc-polito-notification></dc-polito-notification>\n <dc-observer-intervention [customConfig]=\"observerConfig\"></dc-observer-intervention>\n</div>\n", styles: ["::ng-deep .p-drawer-content{padding:0!important}::ng-deep .p-dialog-content{display:contents}.chat-container{display:flex;flex-direction:column;height:100%;max-height:100vh;overflow:hidden;background-color:transparent;position:relative}dc-chat-messages-list{flex:1;overflow-y:auto;padding:.5rem}.score-container{padding:.5rem 1rem;background-color:var(--surface-card, #ffffff);border-top:1px solid var(--surface-border, #dee2e6)}.info-content{max-height:70vh;overflow-y:auto;padding:1rem}.info-content h3{margin-top:1rem;margin-bottom:.5rem;font-size:1.2rem}.info-content pre{background-color:var(--surface-hover, #f1f1f1);padding:1rem;border-radius:4px;overflow-x:auto;font-size:.9rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: ChatHeaderComponent, selector: "dc-chat-header", inputs: ["alternativeConversation", "agentCard", "viewMode"], outputs: ["restartConversationEvent", "showInfoEvent", "settingsClickEvent", "viewModeChanged"] }, { kind: "component", type: ChatFooterComponent, selector: "dc-chat-footer", inputs: ["isAIThinking", "micSettings"], outputs: ["sendMessage", "textInputChanged"] }, { kind: "component", type: ChatMessagesListComponent, selector: "dc-chat-messages-list", inputs: ["chatUserSettings"] }, { kind: "ngmodule", type: ProgressBarModule }, { kind: "component", type: DcImmersiveChatComponent, selector: "dc-immersive-chat", inputs: ["micSettings"] }, { kind: "component", type: PolitoNotificationComponent, selector: "dc-polito-notification" }, { kind: "component", type: DcObserverInterventionComponent, selector: "dc-observer-intervention", inputs: ["customConfig"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6482
6840
|
}
|
|
@@ -6503,7 +6861,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
6503
6861
|
ConversationFlowStateService,
|
|
6504
6862
|
DynamicFlowService,
|
|
6505
6863
|
AgentCardStateService,
|
|
6506
|
-
ChatMonitorService
|
|
6864
|
+
// ChatMonitorService removido del providers[] local: ahora la instancia la provee el
|
|
6865
|
+
// componente ancestro (ConversationCardDetailsComponent) para compartirla con dc-agent-card-avatar.
|
|
6507
6866
|
ContextEngineService,
|
|
6508
6867
|
], template: "<div class=\"chat-container\">\n <dc-chat-header\n [agentCard]=\"agentCard\"\n [viewMode]=\"viewMode()\"\n (showInfoEvent)=\"showInfo()\"\n (settingsClickEvent)=\"changeUserChatSettings()\"\n (restartConversationEvent)=\"restartConversation($event)\"\n (completeEvent)=\"complete()\"\n (viewModeChanged)=\"viewMode.set($event)\">\n </dc-chat-header>\n\n @if (viewMode() === 'chat') {\n <dc-chat-messages-list [chatUserSettings]=\"chatUserSettings\"> </dc-chat-messages-list>\n <dc-chat-footer [micSettings]=\"micSettings\"> </dc-chat-footer>\n } @else {\n <dc-immersive-chat [micSettings]=\"micSettings\"></dc-immersive-chat>\n }\n\n <dc-polito-notification></dc-polito-notification>\n <dc-observer-intervention [customConfig]=\"observerConfig\"></dc-observer-intervention>\n</div>\n", styles: ["::ng-deep .p-drawer-content{padding:0!important}::ng-deep .p-dialog-content{display:contents}.chat-container{display:flex;flex-direction:column;height:100%;max-height:100vh;overflow:hidden;background-color:transparent;position:relative}dc-chat-messages-list{flex:1;overflow-y:auto;padding:.5rem}.score-container{padding:.5rem 1rem;background-color:var(--surface-card, #ffffff);border-top:1px solid var(--surface-border, #dee2e6)}.info-content{max-height:70vh;overflow-y:auto;padding:1rem}.info-content h3{margin-top:1rem;margin-bottom:.5rem;font-size:1.2rem}.info-content pre{background-color:var(--surface-hover, #f1f1f1);padding:1rem;border-radius:4px;overflow-x:auto;font-size:.9rem}\n"] }]
|
|
6509
6868
|
}], ctorParameters: () => [], propDecorators: { chatFooterComponent: [{
|
|
@@ -6517,6 +6876,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
6517
6876
|
type: Input
|
|
6518
6877
|
}], agentCard: [{
|
|
6519
6878
|
type: Input
|
|
6879
|
+
}], micSettings: [{
|
|
6880
|
+
type: Input
|
|
6520
6881
|
}], observerConfig: [{
|
|
6521
6882
|
type: Input
|
|
6522
6883
|
}], parseDict: [{ type: i0.Input, args: [{ isSignal: true, alias: "parseDict", required: false }] }], chatEvent: [{ type: i0.Output, args: ["chatEvent"] }], goalCompleted: [{ type: i0.Output, args: ["goalCompleted"] }] } });
|
|
@@ -7496,7 +7857,7 @@ class CharacterFormGroupService {
|
|
|
7496
7857
|
conversationFlow: this.createConversationFlowFormGroup(),
|
|
7497
7858
|
manageable: this.formUtils.createManageableFormGroup(),
|
|
7498
7859
|
learnable: this.formUtils.createLearnableFormGroup(),
|
|
7499
|
-
|
|
7860
|
+
voice: this.createAgentVoiceFormGroup(),
|
|
7500
7861
|
agenticConfig: this.createAgenticConfigFormGroup(),
|
|
7501
7862
|
live2d: [null],
|
|
7502
7863
|
avatarType: [null],
|
|
@@ -7673,8 +8034,6 @@ class CharacterFormGroupService {
|
|
|
7673
8034
|
userMustStart: [false],
|
|
7674
8035
|
streamResponses: [false],
|
|
7675
8036
|
displayMode: ['chat'],
|
|
7676
|
-
mainVoice: this.createVoiceTTSFormGroup(),
|
|
7677
|
-
secondaryVoice: this.createVoiceTTSFormGroup(),
|
|
7678
8037
|
model: createAIModelFormGroup(),
|
|
7679
8038
|
rules: this.fb.array([]),
|
|
7680
8039
|
chatMode: ['regular'],
|
|
@@ -7777,11 +8136,14 @@ class CharacterFormGroupService {
|
|
|
7777
8136
|
emoji: [criteria?.emoji || ''],
|
|
7778
8137
|
});
|
|
7779
8138
|
}
|
|
7780
|
-
|
|
8139
|
+
createAgentVoiceFormGroup(voice) {
|
|
7781
8140
|
return this.fb.group({
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
8141
|
+
main: this.createVoiceTTSFormGroup(voice?.main),
|
|
8142
|
+
secondary: this.createVoiceTTSFormGroup(voice?.secondary),
|
|
8143
|
+
cloning: this.fb.group({
|
|
8144
|
+
sample: [voice?.cloning?.sample || null],
|
|
8145
|
+
transcription: [voice?.cloning?.transcription || ''],
|
|
8146
|
+
})
|
|
7785
8147
|
});
|
|
7786
8148
|
}
|
|
7787
8149
|
createAgenticConfigFormGroup() {
|
|
@@ -8328,47 +8690,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
8328
8690
|
type: Output
|
|
8329
8691
|
}] } });
|
|
8330
8692
|
|
|
8331
|
-
class DcVoiceTtsFormComponent {
|
|
8332
|
-
constructor() {
|
|
8333
|
-
this.dialogService = inject(DialogService);
|
|
8334
|
-
this.cdr = inject(ChangeDetectorRef);
|
|
8335
|
-
this.form = input.required(...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
|
|
8336
|
-
this.title = input('Voice TTS Settings', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
8337
|
-
this.voiceTTSOptions = input([], ...(ngDevMode ? [{ debugName: "voiceTTSOptions" }] : /* istanbul ignore next */ []));
|
|
8338
|
-
this.fullForm = input(false, ...(ngDevMode ? [{ debugName: "fullForm" }] : /* istanbul ignore next */ []));
|
|
8339
|
-
this.providerOptions = [
|
|
8340
|
-
{ label: 'Google', value: 'google' },
|
|
8341
|
-
{ label: 'Fish Audio', value: 'fish-audio' },
|
|
8342
|
-
];
|
|
8343
|
-
}
|
|
8344
|
-
openVoiceSelector() {
|
|
8345
|
-
const voiceControl = this.form().get('voice');
|
|
8346
|
-
const dialogRef = this.dialogService.open(VoiceSelectorComponent, {
|
|
8347
|
-
header: 'Select Voice',
|
|
8348
|
-
width: '50vw',
|
|
8349
|
-
maximizable: true,
|
|
8350
|
-
height: '500px',
|
|
8351
|
-
contentStyle: { 'max-height': '90vh', overflow: 'auto', 'min-height': '500px' },
|
|
8352
|
-
baseZIndex: 10000,
|
|
8353
|
-
closable: true,
|
|
8354
|
-
modal: true,
|
|
8355
|
-
data: { currentVoiceId: voiceControl?.value },
|
|
8356
|
-
});
|
|
8357
|
-
dialogRef.onClose.subscribe((selectedVoiceId) => {
|
|
8358
|
-
if (selectedVoiceId) {
|
|
8359
|
-
voiceControl?.setValue(selectedVoiceId);
|
|
8360
|
-
this.cdr.detectChanges();
|
|
8361
|
-
}
|
|
8362
|
-
});
|
|
8363
|
-
}
|
|
8364
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcVoiceTtsFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
8365
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DcVoiceTtsFormComponent, isStandalone: true, selector: "dc-voice-tts-form", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, voiceTTSOptions: { classPropertyName: "voiceTTSOptions", publicName: "voiceTTSOptions", isSignal: true, isRequired: false, transformFunction: null }, fullForm: { classPropertyName: "fullForm", publicName: "fullForm", isSignal: true, isRequired: false, transformFunction: null } }, providers: [DialogService], ngImport: i0, template: "<h3> {{ title() }}</h3>\n<div [formGroup]=\"form()\" class=\"form-grid\">\n @if (fullForm()) {\n <div class=\"form-field\">\n <label for=\"provider\">Provider <span pTooltip=\"TTS provider\">\u2139\uFE0F</span></label>\n <p-select id=\"provider\" [options]=\"providerOptions\" formControlName=\"provider\" optionLabel=\"label\" optionValue=\"value\" placeholder=\"Select Provider\" class=\"w-full\"></p-select>\n </div>\n }\n\n <div class=\"form-field\">\n <label for=\"voice\">Voice <span pTooltip=\"Select the voice for text-to-speech\">\u2139\uFE0F</span></label>\n <p-inputgroup>\n <p-inputgroup-addon>\n <p-button [rounded]=\"true\" [text]=\"true\" icon=\"pi pi-exclamation-circle\" (click)=\"openVoiceSelector()\" />\n </p-inputgroup-addon>\n <p-select\n id=\"voice\"\n [editable]=\"true\"\n [options]=\"voiceTTSOptions()\"\n formControlName=\"voice\"\n optionLabel=\"name\"\n optionValue=\"id\"\n [placeholder]=\"'Select Voice'\" />\n </p-inputgroup>\n </div>\n\n <div class=\"form-field\">\n <label for=\"speedRate\">Speed Rate <span pTooltip=\"Adjust the rate of speech delivery\">\u2139\uFE0F</span></label>\n <br />\n <input pInputText id=\"speedRate\" type=\"number\" formControlName=\"speedRate\" step=\"0.1\" />\n </div>\n\n @if (fullForm()) {\n <div class=\"form-field\">\n <label for=\"model\">Model <span pTooltip=\"Model ID if the provider requires it\">\u2139\uFE0F</span></label>\n <input pInputText id=\"model\" type=\"text\" formControlName=\"model\" />\n </div>\n\n <div class=\"form-field\">\n <label for=\"lang\">Language <span pTooltip=\"Language code override (optional, usually included in the voice ID)\">\u2139\uFE0F</span></label>\n <input pInputText id=\"lang\" type=\"text\" formControlName=\"lang\" />\n </div>\n\n <div class=\"form-field\">\n <label for=\"effect\">Effect <span pTooltip=\"Platform audio effect\">\u2139\uFE0F</span></label>\n <input pInputText id=\"effect\" type=\"text\" formControlName=\"effect\" />\n </div>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: DynamicDialogModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: InputGroupModule }, { kind: "component", type: i3$4.InputGroup, selector: "p-inputgroup, p-inputGroup, p-input-group", inputs: ["styleClass"] }, { kind: "ngmodule", type: InputGroupAddonModule }, { kind: "component", type: i4$4.InputGroupAddon, selector: "p-inputgroup-addon, p-inputGroupAddon", inputs: ["style", "styleClass"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i6.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i3$3.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: CardModule }] }); }
|
|
8366
|
-
}
|
|
8367
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcVoiceTtsFormComponent, decorators: [{
|
|
8368
|
-
type: Component,
|
|
8369
|
-
args: [{ selector: 'dc-voice-tts-form', standalone: true, imports: [ReactiveFormsModule, DynamicDialogModule, ButtonModule, InputGroupModule, InputGroupAddonModule, SelectModule, InputTextModule, CardModule], providers: [DialogService], template: "<h3> {{ title() }}</h3>\n<div [formGroup]=\"form()\" class=\"form-grid\">\n @if (fullForm()) {\n <div class=\"form-field\">\n <label for=\"provider\">Provider <span pTooltip=\"TTS provider\">\u2139\uFE0F</span></label>\n <p-select id=\"provider\" [options]=\"providerOptions\" formControlName=\"provider\" optionLabel=\"label\" optionValue=\"value\" placeholder=\"Select Provider\" class=\"w-full\"></p-select>\n </div>\n }\n\n <div class=\"form-field\">\n <label for=\"voice\">Voice <span pTooltip=\"Select the voice for text-to-speech\">\u2139\uFE0F</span></label>\n <p-inputgroup>\n <p-inputgroup-addon>\n <p-button [rounded]=\"true\" [text]=\"true\" icon=\"pi pi-exclamation-circle\" (click)=\"openVoiceSelector()\" />\n </p-inputgroup-addon>\n <p-select\n id=\"voice\"\n [editable]=\"true\"\n [options]=\"voiceTTSOptions()\"\n formControlName=\"voice\"\n optionLabel=\"name\"\n optionValue=\"id\"\n [placeholder]=\"'Select Voice'\" />\n </p-inputgroup>\n </div>\n\n <div class=\"form-field\">\n <label for=\"speedRate\">Speed Rate <span pTooltip=\"Adjust the rate of speech delivery\">\u2139\uFE0F</span></label>\n <br />\n <input pInputText id=\"speedRate\" type=\"number\" formControlName=\"speedRate\" step=\"0.1\" />\n </div>\n\n @if (fullForm()) {\n <div class=\"form-field\">\n <label for=\"model\">Model <span pTooltip=\"Model ID if the provider requires it\">\u2139\uFE0F</span></label>\n <input pInputText id=\"model\" type=\"text\" formControlName=\"model\" />\n </div>\n\n <div class=\"form-field\">\n <label for=\"lang\">Language <span pTooltip=\"Language code override (optional, usually included in the voice ID)\">\u2139\uFE0F</span></label>\n <input pInputText id=\"lang\" type=\"text\" formControlName=\"lang\" />\n </div>\n\n <div class=\"form-field\">\n <label for=\"effect\">Effect <span pTooltip=\"Platform audio effect\">\u2139\uFE0F</span></label>\n <input pInputText id=\"effect\" type=\"text\" formControlName=\"effect\" />\n </div>\n }\n</div>\n" }]
|
|
8370
|
-
}], propDecorators: { form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: true }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], voiceTTSOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "voiceTTSOptions", required: false }] }], fullForm: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullForm", required: false }] }] } });
|
|
8371
|
-
|
|
8372
8693
|
class PromptConversationTypesDialogComponent {
|
|
8373
8694
|
constructor() {
|
|
8374
8695
|
this.dialogRef = inject(DynamicDialogRef);
|
|
@@ -8438,12 +8759,6 @@ class DcConversationSettingsFormComponent {
|
|
|
8438
8759
|
get rulesFormArray() {
|
|
8439
8760
|
return this.form.get('rules');
|
|
8440
8761
|
}
|
|
8441
|
-
get mainVoiceFormGroup() {
|
|
8442
|
-
return this.form.get('mainVoice');
|
|
8443
|
-
}
|
|
8444
|
-
get secondaryVoiceFormGroup() {
|
|
8445
|
-
return this.form.get('secondaryVoice');
|
|
8446
|
-
}
|
|
8447
8762
|
get modelFormGroup() {
|
|
8448
8763
|
return this.form.get('model');
|
|
8449
8764
|
}
|
|
@@ -8475,7 +8790,7 @@ class DcConversationSettingsFormComponent {
|
|
|
8475
8790
|
this.getRules();
|
|
8476
8791
|
}
|
|
8477
8792
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcConversationSettingsFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
8478
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DcConversationSettingsFormComponent, isStandalone: true, selector: "dc-conversation-settings-form", inputs: { form: "form", textEngineOptions: "textEngineOptions", conversationOptions: "conversationOptions", voiceTTSOptions: "voiceTTSOptions" }, providers: [DialogService], ngImport: i0, template: "<div [formGroup]=\"form\">\n <h3 class=\"text-xl font-bold border-b pb-2\">\n Chat Conversation Settings\n <span pTooltip=\"Additional information about the conversation use in the chat\" class=\"text-blue-500 cursor-help text-sm\">\u2139\uFE0F</span>\n </h3>\n\n <div>\n <label for=\"rules\" class=\"block text-sm font-medium\">\n Conversation Rules\n <span pTooltip=\"Add rules to the conversation\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select id=\"rules\" [options]=\"rules\" (onChange)=\"addRule($event.value)\" placeholder=\"Select a Rule\" optionLabel=\"name\" class=\"w-full\"></p-select>\n\n <div formArrayName=\"rules\" class=\"mt-4 space-y-2\">\n @for (rule of rulesFormArray.controls; track rule; let i = $index) {\n <div [formGroupName]=\"i\" class=\"flex items-center justify-between p-2 border rounded-md\">\n <span>{{ rule.value.name }}</span>\n <button pButton type=\"button\" icon=\"pi pi-trash\" class=\"p-button-danger p-button-text\" (click)=\"removeRule(i)\"></button>\n </div>\n }\n </div>\n </div>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div>\n <label for=\"conversationType\" class=\"block text-sm font-medium\">\n Conversation Type\n <span class=\"cursor-pointer text-blue-500\" (click)=\"openConversationTypeDialog()\" pTooltip=\"Choose the type of conversation interaction\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"conversationType\"\n [options]=\"conversationOptions\"\n formControlName=\"conversationType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Conversation Type'\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"textEngine\" class=\"block text-sm font-medium\">\n Text Engine\n <span\n class=\"cursor-pointer text-blue-500\"\n (click)=\"textEngineDialog.toggle($event)\"\n pTooltip=\"Sistema de generaci\u00F3n de texto y audios. Client: el cliente llama al servidor en cada dialogo de voz/personaje, es optimo para historias, Server SSML: se sintetiza todo el audio en uno solo con los distintos cambios de voz/personaje, util para la reflexi\u00F3n porque es bilingue, utiliza dialogos en ingles y espa\u00F1ol en el mismo dialogo/audio\"\n >\u2139\uFE0F</span\n >\n </label>\n <p-select\n id=\"textEngine\"\n [options]=\"textEngineOptions\"\n formControlName=\"textEngine\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Text Engine'\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n Auto Start\n <span pTooltip=\"Start conversation automatically\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"autoStart\"></p-toggleSwitch>\n </div>\n\n <div class=\"md:col-span-2\">\n <label for=\"displayMode\" class=\"block text-sm font-medium text-red-500\">\n Display Mode (Deprecated Soon)\n <span pTooltip=\"[Deprecated] Use Chat Mode and Avatar Display Mode instead.\" class=\"text-red-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"displayMode\"\n [options]=\"displayModeOptions\"\n formControlName=\"displayMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Display Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"chatMode\" class=\"block text-sm font-medium\">\n Chat Mode\n <span pTooltip=\"regular: standard scrolling message list | immersive: voice-first Heads-Up Display (HUD)\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"chatMode\"\n [options]=\"chatModeOptions\"\n formControlName=\"chatMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Chat Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"avatarDisplayMode\" class=\"block text-sm font-medium\">\n Avatar Display Mode\n <span pTooltip=\"card: enclosed box layout | transparent: floats overlaying background | fullscreen: stretches model to fill page behind chat\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"avatarDisplayMode\"\n [options]=\"avatarDisplayModeOptions\"\n formControlName=\"avatarDisplayMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Avatar Display Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n User Must Start\n <span pTooltip=\"If true the user must start the conversation, ignoring first message if any.\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"userMustStart\"></p-toggleSwitch>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n Stream Responses\n <span pTooltip=\"Enable real-time token streaming for LLM responses.\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"streamResponses\"></p-toggleSwitch>\n </div>\n </div>\n\n <hr />\n\n <dc-voice-tts-form [form]=\"mainVoiceFormGroup\" [title]=\"'Main Voice TTS Settings'\" [voiceTTSOptions]=\"voiceTTSOptions\"></dc-voice-tts-form>\n\n <dc-voice-tts-form [form]=\"secondaryVoiceFormGroup\" [title]=\"'Secondary Voice TTS Settings'\" [voiceTTSOptions]=\"voiceTTSOptions\"></dc-voice-tts-form>\n\n <hr />\n\n <dc-model-selector [modelForm]=\"modelFormGroup\" [shortForm]=\"true\"></dc-model-selector>\n <hr />\n</div>\n\n<p-popover #textEngineDialog [style]=\"{ width: '350px' }\" header=\"Text Engine Information\">\n <ng-template pTemplate=\"content\">\n <div class=\"p-4\">\n <h3 class=\"text-md font-semibold mb-3 text-gray-800\">Text Engine Types</h3>\n <ul class=\"space-y-3 text-sm text-gray-600\">\n <li>\n <strong class=\"font-semibold text-gray-900\">Texto Simple:</strong>\n La conversaci\u00F3n es como chatgpt, preguntas y responde, es la m\u00E1s b\u00E1sica.\n </li>\n <li>\n <strong class=\"font-semibold text-gray-900\">Multi Mensajes:</strong>\n Utiliza markdown (recomendable entenderlo), para dar formato al texto. El sistema puede partir dialogos con distinto formato (normal, cursiva,\n negritas) para generar distintas voces y estilos para el narrador y personaje.\n </li>\n <li>\n <strong class=\"font-semibold text-gray-900\">MD SSML:</strong>\n Markdown con SSML. Similar a Multi Mensajes, pero se presenta en un solo mensaje y la voz se genera para toda la linea. \u00DAtil para conversaciones\n biling\u00FCes.\n </li>\n </ul>\n </div>\n </ng-template>\n</p-popover>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$2.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i1$2.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "directive", type: i3$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i6.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: ToggleSwitchModule }, { kind: "component", type: i4$3.ToggleSwitch, selector: "p-toggleswitch, p-toggleSwitch, p-toggle-switch", inputs: ["styleClass", "tabindex", "inputId", "readonly", "trueValue", "falseValue", "ariaLabel", "size", "ariaLabelledBy", "autofocus"], outputs: ["onChange"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i2$2.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: DynamicDialogModule }, { kind: "component", type: ModelSelectorComponent, selector: "dc-model-selector", inputs: ["modelForm", "shortForm"] }, { kind: "component", type: DcVoiceTtsFormComponent, selector: "dc-voice-tts-form", inputs: ["form", "title", "voiceTTSOptions", "fullForm"] }] }); }
|
|
8793
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DcConversationSettingsFormComponent, isStandalone: true, selector: "dc-conversation-settings-form", inputs: { form: "form", textEngineOptions: "textEngineOptions", conversationOptions: "conversationOptions", voiceTTSOptions: "voiceTTSOptions" }, providers: [DialogService], ngImport: i0, template: "<div [formGroup]=\"form\">\n <h3 class=\"text-xl font-bold border-b pb-2\">\n Chat Conversation Settings\n <span pTooltip=\"Additional information about the conversation use in the chat\" class=\"text-blue-500 cursor-help text-sm\">\u2139\uFE0F</span>\n </h3>\n\n <div>\n <label for=\"rules\" class=\"block text-sm font-medium\">\n Conversation Rules\n <span pTooltip=\"Add rules to the conversation\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select id=\"rules\" [options]=\"rules\" (onChange)=\"addRule($event.value)\" placeholder=\"Select a Rule\" optionLabel=\"name\" class=\"w-full\"></p-select>\n\n <div formArrayName=\"rules\" class=\"mt-4 space-y-2\">\n @for (rule of rulesFormArray.controls; track rule; let i = $index) {\n <div [formGroupName]=\"i\" class=\"flex items-center justify-between p-2 border rounded-md\">\n <span>{{ rule.value.name }}</span>\n <button pButton type=\"button\" icon=\"pi pi-trash\" class=\"p-button-danger p-button-text\" (click)=\"removeRule(i)\"></button>\n </div>\n }\n </div>\n </div>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div>\n <label for=\"conversationType\" class=\"block text-sm font-medium\">\n Conversation Type\n <span class=\"cursor-pointer text-blue-500\" (click)=\"openConversationTypeDialog()\" pTooltip=\"Choose the type of conversation interaction\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"conversationType\"\n [options]=\"conversationOptions\"\n formControlName=\"conversationType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Conversation Type'\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"textEngine\" class=\"block text-sm font-medium\">\n Text Engine\n <span\n class=\"cursor-pointer text-blue-500\"\n (click)=\"textEngineDialog.toggle($event)\"\n pTooltip=\"Sistema de generaci\u00F3n de texto y audios. Client: el cliente llama al servidor en cada dialogo de voz/personaje, es optimo para historias, Server SSML: se sintetiza todo el audio en uno solo con los distintos cambios de voz/personaje, util para la reflexi\u00F3n porque es bilingue, utiliza dialogos en ingles y espa\u00F1ol en el mismo dialogo/audio\"\n >\u2139\uFE0F</span\n >\n </label>\n <p-select\n id=\"textEngine\"\n [options]=\"textEngineOptions\"\n formControlName=\"textEngine\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Text Engine'\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n Auto Start\n <span pTooltip=\"Start conversation automatically\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"autoStart\"></p-toggleSwitch>\n </div>\n\n <div class=\"md:col-span-2\">\n <label for=\"displayMode\" class=\"block text-sm font-medium text-red-500\">\n Display Mode (Deprecated Soon)\n <span pTooltip=\"[Deprecated] Use Chat Mode and Avatar Display Mode instead.\" class=\"text-red-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"displayMode\"\n [options]=\"displayModeOptions\"\n formControlName=\"displayMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Display Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"chatMode\" class=\"block text-sm font-medium\">\n Chat Mode\n <span pTooltip=\"regular: standard scrolling message list | immersive: voice-first Heads-Up Display (HUD)\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"chatMode\"\n [options]=\"chatModeOptions\"\n formControlName=\"chatMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Chat Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"avatarDisplayMode\" class=\"block text-sm font-medium\">\n Avatar Display Mode\n <span pTooltip=\"card: enclosed box layout | transparent: floats overlaying background | fullscreen: stretches model to fill page behind chat\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"avatarDisplayMode\"\n [options]=\"avatarDisplayModeOptions\"\n formControlName=\"avatarDisplayMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Avatar Display Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n User Must Start\n <span pTooltip=\"If true the user must start the conversation, ignoring first message if any.\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"userMustStart\"></p-toggleSwitch>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n Stream Responses\n <span pTooltip=\"Enable real-time token streaming for LLM responses.\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"streamResponses\"></p-toggleSwitch>\n </div>\n </div>\n\n <hr />\n\n <dc-model-selector [modelForm]=\"modelFormGroup\" [shortForm]=\"true\"></dc-model-selector>\n <hr />\n</div>\n\n<p-popover #textEngineDialog [style]=\"{ width: '350px' }\" header=\"Text Engine Information\">\n <ng-template pTemplate=\"content\">\n <div class=\"p-4\">\n <h3 class=\"text-md font-semibold mb-3 text-gray-800\">Text Engine Types</h3>\n <ul class=\"space-y-3 text-sm text-gray-600\">\n <li>\n <strong class=\"font-semibold text-gray-900\">Texto Simple:</strong>\n La conversaci\u00F3n es como chatgpt, preguntas y responde, es la m\u00E1s b\u00E1sica.\n </li>\n <li>\n <strong class=\"font-semibold text-gray-900\">Multi Mensajes:</strong>\n Utiliza markdown (recomendable entenderlo), para dar formato al texto. El sistema puede partir dialogos con distinto formato (normal, cursiva,\n negritas) para generar distintas voces y estilos para el narrador y personaje.\n </li>\n <li>\n <strong class=\"font-semibold text-gray-900\">MD SSML:</strong>\n Markdown con SSML. Similar a Multi Mensajes, pero se presenta en un solo mensaje y la voz se genera para toda la linea. \u00DAtil para conversaciones\n biling\u00FCes.\n </li>\n </ul>\n </div>\n </ng-template>\n</p-popover>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$2.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "directive", type: i1$2.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "directive", type: i3$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i6.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: ToggleSwitchModule }, { kind: "component", type: i4$3.ToggleSwitch, selector: "p-toggleswitch, p-toggleSwitch, p-toggle-switch", inputs: ["styleClass", "tabindex", "inputId", "readonly", "trueValue", "falseValue", "ariaLabel", "size", "ariaLabelledBy", "autofocus"], outputs: ["onChange"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i2$2.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "ngmodule", type: DynamicDialogModule }, { kind: "component", type: ModelSelectorComponent, selector: "dc-model-selector", inputs: ["modelForm", "shortForm"] }] }); }
|
|
8479
8794
|
}
|
|
8480
8795
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcConversationSettingsFormComponent, decorators: [{
|
|
8481
8796
|
type: Component,
|
|
@@ -8489,8 +8804,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
8489
8804
|
PopoverModule,
|
|
8490
8805
|
DynamicDialogModule,
|
|
8491
8806
|
ModelSelectorComponent,
|
|
8492
|
-
|
|
8493
|
-
], providers: [DialogService], template: "<div [formGroup]=\"form\">\n <h3 class=\"text-xl font-bold border-b pb-2\">\n Chat Conversation Settings\n <span pTooltip=\"Additional information about the conversation use in the chat\" class=\"text-blue-500 cursor-help text-sm\">\u2139\uFE0F</span>\n </h3>\n\n <div>\n <label for=\"rules\" class=\"block text-sm font-medium\">\n Conversation Rules\n <span pTooltip=\"Add rules to the conversation\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select id=\"rules\" [options]=\"rules\" (onChange)=\"addRule($event.value)\" placeholder=\"Select a Rule\" optionLabel=\"name\" class=\"w-full\"></p-select>\n\n <div formArrayName=\"rules\" class=\"mt-4 space-y-2\">\n @for (rule of rulesFormArray.controls; track rule; let i = $index) {\n <div [formGroupName]=\"i\" class=\"flex items-center justify-between p-2 border rounded-md\">\n <span>{{ rule.value.name }}</span>\n <button pButton type=\"button\" icon=\"pi pi-trash\" class=\"p-button-danger p-button-text\" (click)=\"removeRule(i)\"></button>\n </div>\n }\n </div>\n </div>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div>\n <label for=\"conversationType\" class=\"block text-sm font-medium\">\n Conversation Type\n <span class=\"cursor-pointer text-blue-500\" (click)=\"openConversationTypeDialog()\" pTooltip=\"Choose the type of conversation interaction\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"conversationType\"\n [options]=\"conversationOptions\"\n formControlName=\"conversationType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Conversation Type'\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"textEngine\" class=\"block text-sm font-medium\">\n Text Engine\n <span\n class=\"cursor-pointer text-blue-500\"\n (click)=\"textEngineDialog.toggle($event)\"\n pTooltip=\"Sistema de generaci\u00F3n de texto y audios. Client: el cliente llama al servidor en cada dialogo de voz/personaje, es optimo para historias, Server SSML: se sintetiza todo el audio en uno solo con los distintos cambios de voz/personaje, util para la reflexi\u00F3n porque es bilingue, utiliza dialogos en ingles y espa\u00F1ol en el mismo dialogo/audio\"\n >\u2139\uFE0F</span\n >\n </label>\n <p-select\n id=\"textEngine\"\n [options]=\"textEngineOptions\"\n formControlName=\"textEngine\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Text Engine'\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n Auto Start\n <span pTooltip=\"Start conversation automatically\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"autoStart\"></p-toggleSwitch>\n </div>\n\n <div class=\"md:col-span-2\">\n <label for=\"displayMode\" class=\"block text-sm font-medium text-red-500\">\n Display Mode (Deprecated Soon)\n <span pTooltip=\"[Deprecated] Use Chat Mode and Avatar Display Mode instead.\" class=\"text-red-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"displayMode\"\n [options]=\"displayModeOptions\"\n formControlName=\"displayMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Display Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"chatMode\" class=\"block text-sm font-medium\">\n Chat Mode\n <span pTooltip=\"regular: standard scrolling message list | immersive: voice-first Heads-Up Display (HUD)\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"chatMode\"\n [options]=\"chatModeOptions\"\n formControlName=\"chatMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Chat Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"avatarDisplayMode\" class=\"block text-sm font-medium\">\n Avatar Display Mode\n <span pTooltip=\"card: enclosed box layout | transparent: floats overlaying background | fullscreen: stretches model to fill page behind chat\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"avatarDisplayMode\"\n [options]=\"avatarDisplayModeOptions\"\n formControlName=\"avatarDisplayMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Avatar Display Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n User Must Start\n <span pTooltip=\"If true the user must start the conversation, ignoring first message if any.\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"userMustStart\"></p-toggleSwitch>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n Stream Responses\n <span pTooltip=\"Enable real-time token streaming for LLM responses.\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"streamResponses\"></p-toggleSwitch>\n </div>\n </div>\n\n <hr />\n\n <dc-voice-tts-form [form]=\"mainVoiceFormGroup\" [title]=\"'Main Voice TTS Settings'\" [voiceTTSOptions]=\"voiceTTSOptions\"></dc-voice-tts-form>\n\n <dc-voice-tts-form [form]=\"secondaryVoiceFormGroup\" [title]=\"'Secondary Voice TTS Settings'\" [voiceTTSOptions]=\"voiceTTSOptions\"></dc-voice-tts-form>\n\n <hr />\n\n <dc-model-selector [modelForm]=\"modelFormGroup\" [shortForm]=\"true\"></dc-model-selector>\n <hr />\n</div>\n\n<p-popover #textEngineDialog [style]=\"{ width: '350px' }\" header=\"Text Engine Information\">\n <ng-template pTemplate=\"content\">\n <div class=\"p-4\">\n <h3 class=\"text-md font-semibold mb-3 text-gray-800\">Text Engine Types</h3>\n <ul class=\"space-y-3 text-sm text-gray-600\">\n <li>\n <strong class=\"font-semibold text-gray-900\">Texto Simple:</strong>\n La conversaci\u00F3n es como chatgpt, preguntas y responde, es la m\u00E1s b\u00E1sica.\n </li>\n <li>\n <strong class=\"font-semibold text-gray-900\">Multi Mensajes:</strong>\n Utiliza markdown (recomendable entenderlo), para dar formato al texto. El sistema puede partir dialogos con distinto formato (normal, cursiva,\n negritas) para generar distintas voces y estilos para el narrador y personaje.\n </li>\n <li>\n <strong class=\"font-semibold text-gray-900\">MD SSML:</strong>\n Markdown con SSML. Similar a Multi Mensajes, pero se presenta en un solo mensaje y la voz se genera para toda la linea. \u00DAtil para conversaciones\n biling\u00FCes.\n </li>\n </ul>\n </div>\n </ng-template>\n</p-popover>\n" }]
|
|
8807
|
+
], providers: [DialogService], template: "<div [formGroup]=\"form\">\n <h3 class=\"text-xl font-bold border-b pb-2\">\n Chat Conversation Settings\n <span pTooltip=\"Additional information about the conversation use in the chat\" class=\"text-blue-500 cursor-help text-sm\">\u2139\uFE0F</span>\n </h3>\n\n <div>\n <label for=\"rules\" class=\"block text-sm font-medium\">\n Conversation Rules\n <span pTooltip=\"Add rules to the conversation\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select id=\"rules\" [options]=\"rules\" (onChange)=\"addRule($event.value)\" placeholder=\"Select a Rule\" optionLabel=\"name\" class=\"w-full\"></p-select>\n\n <div formArrayName=\"rules\" class=\"mt-4 space-y-2\">\n @for (rule of rulesFormArray.controls; track rule; let i = $index) {\n <div [formGroupName]=\"i\" class=\"flex items-center justify-between p-2 border rounded-md\">\n <span>{{ rule.value.name }}</span>\n <button pButton type=\"button\" icon=\"pi pi-trash\" class=\"p-button-danger p-button-text\" (click)=\"removeRule(i)\"></button>\n </div>\n }\n </div>\n </div>\n\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\n <div>\n <label for=\"conversationType\" class=\"block text-sm font-medium\">\n Conversation Type\n <span class=\"cursor-pointer text-blue-500\" (click)=\"openConversationTypeDialog()\" pTooltip=\"Choose the type of conversation interaction\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"conversationType\"\n [options]=\"conversationOptions\"\n formControlName=\"conversationType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Conversation Type'\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"textEngine\" class=\"block text-sm font-medium\">\n Text Engine\n <span\n class=\"cursor-pointer text-blue-500\"\n (click)=\"textEngineDialog.toggle($event)\"\n pTooltip=\"Sistema de generaci\u00F3n de texto y audios. Client: el cliente llama al servidor en cada dialogo de voz/personaje, es optimo para historias, Server SSML: se sintetiza todo el audio en uno solo con los distintos cambios de voz/personaje, util para la reflexi\u00F3n porque es bilingue, utiliza dialogos en ingles y espa\u00F1ol en el mismo dialogo/audio\"\n >\u2139\uFE0F</span\n >\n </label>\n <p-select\n id=\"textEngine\"\n [options]=\"textEngineOptions\"\n formControlName=\"textEngine\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Text Engine'\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n Auto Start\n <span pTooltip=\"Start conversation automatically\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"autoStart\"></p-toggleSwitch>\n </div>\n\n <div class=\"md:col-span-2\">\n <label for=\"displayMode\" class=\"block text-sm font-medium text-red-500\">\n Display Mode (Deprecated Soon)\n <span pTooltip=\"[Deprecated] Use Chat Mode and Avatar Display Mode instead.\" class=\"text-red-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"displayMode\"\n [options]=\"displayModeOptions\"\n formControlName=\"displayMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Display Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"chatMode\" class=\"block text-sm font-medium\">\n Chat Mode\n <span pTooltip=\"regular: standard scrolling message list | immersive: voice-first Heads-Up Display (HUD)\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"chatMode\"\n [options]=\"chatModeOptions\"\n formControlName=\"chatMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Chat Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div>\n <label for=\"avatarDisplayMode\" class=\"block text-sm font-medium\">\n Avatar Display Mode\n <span pTooltip=\"card: enclosed box layout | transparent: floats overlaying background | fullscreen: stretches model to fill page behind chat\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-select\n id=\"avatarDisplayMode\"\n [options]=\"avatarDisplayModeOptions\"\n formControlName=\"avatarDisplayMode\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Avatar Display Mode\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n User Must Start\n <span pTooltip=\"If true the user must start the conversation, ignoring first message if any.\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"userMustStart\"></p-toggleSwitch>\n </div>\n\n <div class=\"flex items-center justify-between md:col-span-2\">\n <label class=\"text-sm font-medium\">\n Stream Responses\n <span pTooltip=\"Enable real-time token streaming for LLM responses.\" class=\"text-blue-500 cursor-help\">\u2139\uFE0F</span>\n </label>\n <p-toggleSwitch formControlName=\"streamResponses\"></p-toggleSwitch>\n </div>\n </div>\n\n <hr />\n\n <dc-model-selector [modelForm]=\"modelFormGroup\" [shortForm]=\"true\"></dc-model-selector>\n <hr />\n</div>\n\n<p-popover #textEngineDialog [style]=\"{ width: '350px' }\" header=\"Text Engine Information\">\n <ng-template pTemplate=\"content\">\n <div class=\"p-4\">\n <h3 class=\"text-md font-semibold mb-3 text-gray-800\">Text Engine Types</h3>\n <ul class=\"space-y-3 text-sm text-gray-600\">\n <li>\n <strong class=\"font-semibold text-gray-900\">Texto Simple:</strong>\n La conversaci\u00F3n es como chatgpt, preguntas y responde, es la m\u00E1s b\u00E1sica.\n </li>\n <li>\n <strong class=\"font-semibold text-gray-900\">Multi Mensajes:</strong>\n Utiliza markdown (recomendable entenderlo), para dar formato al texto. El sistema puede partir dialogos con distinto formato (normal, cursiva,\n negritas) para generar distintas voces y estilos para el narrador y personaje.\n </li>\n <li>\n <strong class=\"font-semibold text-gray-900\">MD SSML:</strong>\n Markdown con SSML. Similar a Multi Mensajes, pero se presenta en un solo mensaje y la voz se genera para toda la linea. \u00DAtil para conversaciones\n biling\u00FCes.\n </li>\n </ul>\n </div>\n </ng-template>\n</p-popover>\n" }]
|
|
8494
8808
|
}], propDecorators: { form: [{
|
|
8495
8809
|
type: Input
|
|
8496
8810
|
}], textEngineOptions: [{
|
|
@@ -8501,6 +8815,47 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
8501
8815
|
type: Input
|
|
8502
8816
|
}] } });
|
|
8503
8817
|
|
|
8818
|
+
class DcVoiceTtsFormComponent {
|
|
8819
|
+
constructor() {
|
|
8820
|
+
this.dialogService = inject(DialogService);
|
|
8821
|
+
this.cdr = inject(ChangeDetectorRef);
|
|
8822
|
+
this.form = input.required(...(ngDevMode ? [{ debugName: "form" }] : /* istanbul ignore next */ []));
|
|
8823
|
+
this.title = input('Voice TTS Settings', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
8824
|
+
this.voiceTTSOptions = input([], ...(ngDevMode ? [{ debugName: "voiceTTSOptions" }] : /* istanbul ignore next */ []));
|
|
8825
|
+
this.fullForm = input(false, ...(ngDevMode ? [{ debugName: "fullForm" }] : /* istanbul ignore next */ []));
|
|
8826
|
+
this.providerOptions = [
|
|
8827
|
+
{ label: 'Google', value: 'google' },
|
|
8828
|
+
{ label: 'Fish Audio', value: 'fish-audio' },
|
|
8829
|
+
];
|
|
8830
|
+
}
|
|
8831
|
+
openVoiceSelector() {
|
|
8832
|
+
const voiceControl = this.form().get('voice');
|
|
8833
|
+
const dialogRef = this.dialogService.open(VoiceSelectorComponent, {
|
|
8834
|
+
header: 'Select Voice',
|
|
8835
|
+
width: '50vw',
|
|
8836
|
+
maximizable: true,
|
|
8837
|
+
height: '500px',
|
|
8838
|
+
contentStyle: { 'max-height': '90vh', overflow: 'auto', 'min-height': '500px' },
|
|
8839
|
+
baseZIndex: 10000,
|
|
8840
|
+
closable: true,
|
|
8841
|
+
modal: true,
|
|
8842
|
+
data: { currentVoiceId: voiceControl?.value },
|
|
8843
|
+
});
|
|
8844
|
+
dialogRef.onClose.subscribe((selectedVoiceId) => {
|
|
8845
|
+
if (selectedVoiceId) {
|
|
8846
|
+
voiceControl?.setValue(selectedVoiceId);
|
|
8847
|
+
this.cdr.detectChanges();
|
|
8848
|
+
}
|
|
8849
|
+
});
|
|
8850
|
+
}
|
|
8851
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcVoiceTtsFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
8852
|
+
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 }] }); }
|
|
8853
|
+
}
|
|
8854
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcVoiceTtsFormComponent, decorators: [{
|
|
8855
|
+
type: Component,
|
|
8856
|
+
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" }]
|
|
8857
|
+
}], 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 }] }] } });
|
|
8858
|
+
|
|
8504
8859
|
class DCAgentCardFormComponent extends EntityBaseFormComponent {
|
|
8505
8860
|
constructor() {
|
|
8506
8861
|
super(...arguments);
|
|
@@ -8539,14 +8894,14 @@ class DCAgentCardFormComponent extends EntityBaseFormComponent {
|
|
|
8539
8894
|
this.isGenerating = signal(false, ...(ngDevMode ? [{ debugName: "isGenerating" }] : /* istanbul ignore next */ []));
|
|
8540
8895
|
this.isCloningVoice = signal(false, ...(ngDevMode ? [{ debugName: "isCloningVoice" }] : /* istanbul ignore next */ []));
|
|
8541
8896
|
this.canCloneVoice = computed(() => {
|
|
8542
|
-
const sampleUrl = this.form.get('
|
|
8543
|
-
const voiceId = this.form.get('
|
|
8897
|
+
const sampleUrl = this.form.get('voice.cloning.sample')?.value?.url;
|
|
8898
|
+
const voiceId = this.form.get('voice.main.voice')?.value;
|
|
8544
8899
|
const canCloneVoiceVar = !!sampleUrl && !voiceId;
|
|
8545
8900
|
return canCloneVoiceVar;
|
|
8546
8901
|
}, ...(ngDevMode ? [{ debugName: "canCloneVoice" }] : /* istanbul ignore next */ []));
|
|
8547
8902
|
this.fishModelId = computed(() => {
|
|
8548
|
-
const voiceId = this.form.get('
|
|
8549
|
-
const provider = this.form.get('
|
|
8903
|
+
const voiceId = this.form.get('voice.main.voice')?.value;
|
|
8904
|
+
const provider = this.form.get('voice.main.provider')?.value;
|
|
8550
8905
|
return voiceId && provider === 'fish-audio' ? voiceId : null;
|
|
8551
8906
|
}, ...(ngDevMode ? [{ debugName: "fishModelId" }] : /* istanbul ignore next */ []));
|
|
8552
8907
|
}
|
|
@@ -8769,15 +9124,15 @@ class DCAgentCardFormComponent extends EntityBaseFormComponent {
|
|
|
8769
9124
|
}
|
|
8770
9125
|
onFileUploaded(event) {
|
|
8771
9126
|
console.log('File uploaded', event);
|
|
8772
|
-
this.form.
|
|
9127
|
+
this.form.get('voice.cloning')?.patchValue({ sample: event });
|
|
8773
9128
|
}
|
|
8774
9129
|
async cloneVoice() {
|
|
8775
|
-
const sample = this.form.get('
|
|
9130
|
+
const sample = this.form.get('voice.cloning.sample')?.value;
|
|
8776
9131
|
if (!sample?.url) {
|
|
8777
9132
|
this.toastService?.error({ title: 'Falta el sample', subtitle: 'Sube un audio antes de crear el modelo' });
|
|
8778
9133
|
return;
|
|
8779
9134
|
}
|
|
8780
|
-
const text = this.form.get('
|
|
9135
|
+
const text = this.form.get('voice.cloning.transcription')?.value || '';
|
|
8781
9136
|
const agentName = this.form.get('name')?.value || 'Polilan';
|
|
8782
9137
|
const title = `${agentName} — voice`;
|
|
8783
9138
|
this.isCloningVoice.set(true);
|
|
@@ -8788,9 +9143,9 @@ class DCAgentCardFormComponent extends EntityBaseFormComponent {
|
|
|
8788
9143
|
text,
|
|
8789
9144
|
trainMode: 'fast',
|
|
8790
9145
|
});
|
|
8791
|
-
const
|
|
8792
|
-
|
|
8793
|
-
|
|
9146
|
+
const newMainCtrl = this.form.get('voice.main');
|
|
9147
|
+
newMainCtrl?.patchValue({ voice: res._id, provider: 'fish-audio' });
|
|
9148
|
+
newMainCtrl?.markAsDirty();
|
|
8794
9149
|
this.toastService?.success({ title: 'Modelo de voz creado', subtitle: res._id });
|
|
8795
9150
|
}
|
|
8796
9151
|
catch (e) {
|
|
@@ -8811,7 +9166,7 @@ class DCAgentCardFormComponent extends EntityBaseFormComponent {
|
|
|
8811
9166
|
this.router.navigate(['../../cards-creator'], { relativeTo: this.route });
|
|
8812
9167
|
}
|
|
8813
9168
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DCAgentCardFormComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
8814
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DCAgentCardFormComponent, isStandalone: true, selector: "dc-agent-form", inputs: { inputSettings: { classPropertyName: "inputSettings", publicName: "inputSettings", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSave: "onSave", onSelectLive2d: "onSelectLive2d" }, providers: [DialogService], usesInheritance: true, ngImport: i0, template: "<p-card>\n <div class=\"top-buttons\">\n <button pButton severity=\"info\" (click)=\"checkPrompt()\" label=\"\uD83D\uDC41\uFE0F Ver instrucciones finales \uD83D\uDCD3\"></button>\n\n <button pButton severity=\"info\" (click)=\"goToDetails()\" label=\"\uD83D\uDCAC Conversar\"></button>\n <button pButton severity=\"primary\" (click)=\"save()\" label=\"\uD83D\uDCBE Guardar cambios\"></button>\n </div>\n\n <div class=\"top-buttons\">\n <p-button [loading]=\"isGenerating()\" severity=\"help\" (click)=\"generateCharacter()\" label=\"Generar \uD83E\uDDBE\"></p-button>\n <p-button severity=\"help\" (click)=\"goToCardsCreator()\" label=\" Creador masivo \"></p-button>\n\n <p-button severity=\"info\" (click)=\"downloadConversation()\" label=\"\uD83D\uDCC1 Exportar \u2B07\uFE0F\"></p-button>\n <p-button severity=\"info\" (click)=\"importConversation()\" label=\"\uD83C\uDCCF Importar \u2B06\uFE0F\"></p-button>\n <p-button severity=\"danger\" (click)=\"deleteCard()\" icon=\"pi pi-trash\"></p-button>\n </div>\n\n <br />\n <br />\n <form [formGroup]=\"form\" class=\"conversation-form\">\n <div class=\"form-grid\">\n <div class=\"left-column\">\n <div title=\"Main data\" >\n <div style=\"display: flex; gap: 15px\">\n <div class=\"form-field\">\n <label for=\"version\">Version: {{ form.get('version').value }} <span pTooltip=\"Version number of the conversation\">\u2139\uFE0F</span></label>\n </div>\n\n <div class=\"form-field\">\n <label for=\"id\"\n >ID: <span pTooltip=\"Unique identifier for this conversation\"> {{ form.get('id').value }} \u2139\uFE0F</span></label\n >\n </div>\n </div>\n\n\n <div class=\"form-field\">\n <label for=\"name\">Card Name <span pTooltip=\"Name of the Agent Card\">\u2139\uFE0F</span></label>\n <input pInputText id=\"name\" type=\"text\" formControlName=\"name\" />\n @if(form.get('name').errors?.['required'] && form.get('name').touched){\n <div class=\"error\"> Name is required </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"description\"> Description <span pTooltip=\"Description of the conversation\">\u2139\uFE0F</span></label>\n <input pInputText id=\"description\" type=\"text\" formControlName=\"description\" />\n @if(form.get('description').errors?.['required'] && form.get('description').touched){\n <div class=\"error\"> Description is required </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"lang\">Language <span pTooltip=\"Select the primary language for the conversation\">\u2139\uFE0F</span></label>\n <p-select\n id=\"lang\"\n [options]=\"languageOptions\"\n [filter]=\"true\"\n formControlName=\"lang\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Language'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"agentType\">Agent Type <span pTooltip=\"Select the type of agent\">\u2139\uFE0F</span></label>\n <p-select id=\"agentType\" [options]=\"agentTypeOptions\" formControlName=\"agentType\" [placeholder]=\"'Select Agent Type'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"avatarType\">Avatar Type <span pTooltip=\"image: static picture | video: double looping video (ping-pong) | live2d: interactive 2D model with real-time audio lip-sync | vroid: 3D model (future)\">\u2139\uFE0F</span></label>\n <p-select\n id=\"avatarType\"\n [options]=\"avatarTypeOptions\"\n formControlName=\"avatarType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Avatar Type\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label>Avatar Live2D <span pTooltip=\"Selecciona el modelo Live2D para este agente\">\u2139\uFE0F</span></label>\n <div style=\"display: flex; align-items: center; gap: 12px; margin-top: 4px;\">\n @if (form.get('live2d')?.value; as live2d) {\n <div style=\"display: flex; align-items: center; gap: 8px; padding: 6px 12px; border: 1px solid var(--p-border-color); border-radius: 6px; background: var(--p-content-background);\">\n <img width=\"36px\" height=\"36px\" style=\"border-radius: 50%; object-fit: cover;\" [src]=\"live2d.image?.url || 'defaults/images/face-3.jpg'\" alt=\"live2d preview\" />\n <span style=\"font-weight: 500;\">{{ live2d.name }}</span>\n <p-button icon=\"pi pi-times\" [text]=\"true\" severity=\"danger\" (onClick)=\"form.patchValue({ live2d: null })\" />\n </div>\n }\n <p-button (onClick)=\"onSelectLive2d.emit()\" label=\"Seleccionar Live2D\" icon=\"pi pi-search\" [outlined]=\"true\" />\n </div>\n </div>\n </div>\n\n <p-accordion [multiple]=\"true\" >\n <p-accordion-panel value=\"manageable\">\n <p-accordion-header>\n <span>Manageable</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Controla visibilidad y acceso</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-manageable-form [form]=\"form.controls.manageable\"></dc-manageable-form>\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"learnable\">\n <p-accordion-header>\n <span>Learnable</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Aprendizaje y conocimiento</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-learnable-form [form]=\"form.controls.learnable\"></dc-learnable-form>\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"conversationSettings\">\n <p-accordion-header>\n <span>Conversation Settings</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Modelo, voz y configuraci\u00F3n del chat</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-conversation-settings-form\n [form]=\"form.controls.conversationSettings\"\n [textEngineOptions]=\"textEngineOptions\"\n [conversationOptions]=\"conversationOptions\"\n [voiceTTSOptions]=\"voiceTTSOptions\">\n </dc-conversation-settings-form>\n </p-accordion-content>\n </p-accordion-panel>\n @if(form.controls.conversationFlow){\n <p-accordion-panel value=\"conversationFlow\">\n <p-accordion-header>\n <span>Conversation Flow</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Objetivos, triggers y condiciones din\u00E1micas</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-conversation-flow-form [formGroup]=\"form.controls.conversationFlow\"></dc-conversation-flow-form>\n </p-accordion-content>\n </p-accordion-panel>\n }\n <p-accordion-panel value=\"accounts\">\n <p-accordion-header>\n <span>Gesti\u00F3n de cuentas</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Beta</span>\n </p-accordion-header>\n <p-accordion-content>\n @if(form.controls.accounts) {\n <account-platform-form [formArray]=\"form.controls.accounts\"></account-platform-form>\n }\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"voiceCloning\">\n <p-accordion-header>\n <span>Clonaci\u00F3n de voz</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Beta</span>\n </p-accordion-header>\n <p-accordion-content>\n <div formGroupName=\"voiceCloning\">\n <div class=\"form-field\">\n <label for=\"transcription\">Transcription</label>\n <textarea pTextarea id=\"transcription\" formControlName=\"transcription\" rows=\"3\"></textarea>\n </div>\n <div class=\"form-field\">\n <dc-simple-uploader\n buttonLabel=\"Subir audio\"\n [storagePath]=\"'conversation-cards/' + entityId()\"\n (fileUploaded)=\"onFileUploaded($event)\"></dc-simple-uploader>\n @if(form.get('voiceCloning.sample').value?.url){\n <audio controls [src]=\"form.get('voiceCloning.sample').value.url\"></audio>\n }\n </div>\n <div class=\"form-field voice-clone-status\">\n @if(!form.get('voiceCloning.sample').value?.url){\n <small class=\"muted\">Sube un audio para entrenar un modelo de voz.</small>\n }\n <!-- @if(canCloneVoice()){ -->\n <p-button\n icon=\"pi pi-bolt\"\n severity=\"help\"\n [loading]=\"isCloningVoice()\"\n label=\"Crear modelo en Fish Audio\"\n (click)=\"cloneVoice()\">\n </p-button>\n <!-- } -->\n @if(fishModelId()){\n <div class=\"model-tag\">\n <span>Modelo Fish Audio: <code>{{ fishModelId() }}</code></span>\n <p-button\n icon=\"pi pi-refresh\"\n severity=\"secondary\"\n [text]=\"true\"\n [loading]=\"isCloningVoice()\"\n label=\"Regenerar\"\n (click)=\"cloneVoice()\">\n </p-button>\n </div>\n }\n </div>\n <dc-voice-tts-form\n [form]=\"$any(form.get('voiceCloning.main'))\"\n [title]=\"'Voice Cloning \u2014 Main Voice'\"\n [voiceTTSOptions]=\"voiceTTSOptions\"\n [fullForm]=\"true\">\n </dc-voice-tts-form>\n </div>\n </p-accordion-content>\n </p-accordion-panel>\n @if(form.controls.agenticConfig) {\n <p-accordion-panel value=\"agenticConfig\">\n <p-accordion-header>\n <span>Configuraci\u00F3n Ag\u00E9ntica</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Motor ag\u00E9ntico, patrones y herramientas</span>\n </p-accordion-header>\n <p-accordion-content>\n <div formGroupName=\"agenticConfig\">\n <div class=\"form-field flex items-center gap-2 mb-4\">\n <p-checkbox id=\"agenticEnabled\" formControlName=\"enabled\" [binary]=\"true\"></p-checkbox>\n <label for=\"agenticEnabled\" class=\"cursor-pointer\">Habilitar Agente Aut\u00F3nomo</label>\n </div>\n\n @if(form.get('agenticConfig.enabled').value) {\n <div class=\"form-field mb-4\">\n <label for=\"agenticEngine\" class=\"block text-sm font-medium mb-1\">Motor Ag\u00E9ntico</label>\n <p-select\n id=\"agenticEngine\"\n [options]=\"agenticEngineOptions\"\n formControlName=\"engine\"\n [placeholder]=\"'Seleccionar Motor'\"\n class=\"w-full\"></p-select>\n </div>\n\n <div class=\"form-field mb-4\">\n <label for=\"agenticPattern\" class=\"block text-sm font-medium mb-1\">Patr\u00F3n Ag\u00E9ntico</label>\n <p-select\n id=\"agenticPattern\"\n [options]=\"agenticPatternOptions\"\n formControlName=\"pattern\"\n [placeholder]=\"'Seleccionar Patr\u00F3n'\"\n class=\"w-full\"></p-select>\n </div>\n\n <div class=\"form-field mb-4\">\n <label for=\"maxIterations\" class=\"block text-sm font-medium mb-1\">M\u00E1ximo de Iteraciones</label>\n <input pInputText id=\"maxIterations\" type=\"number\" formControlName=\"maxIterations\" class=\"w-full\" />\n </div>\n\n <div class=\"form-field mb-4\">\n <label class=\"block text-sm font-medium mb-2\">Herramientas Permitidas</label>\n <div class=\"flex flex-col gap-2 mt-2 pl-2\">\n @for (toolOpt of allowedToolsOptions; track toolOpt.value) {\n <div class=\"flex items-center gap-2\">\n <p-checkbox\n [inputId]=\"'tool_' + toolOpt.value\"\n [binary]=\"true\"\n [ngModel]=\"isToolAllowed(toolOpt.value)\"\n [ngModelOptions]=\"{standalone: true}\"\n (onChange)=\"toggleTool(toolOpt.value, $event.checked)\">\n </p-checkbox>\n <label [for]=\"'tool_' + toolOpt.value\" class=\"cursor-pointer text-sm\">{{ toolOpt.label }}</label>\n </div>\n }\n </div>\n </div>\n\n @if(form.get('agenticConfig.pattern').value === 'reflection') {\n <div class=\"form-field mb-4\">\n <label for=\"reflectionPrompt\" class=\"block text-sm font-medium mb-1\">Prompt de Reflexi\u00F3n</label>\n <textarea pTextarea id=\"reflectionPrompt\" formControlName=\"reflectionPrompt\" rows=\"3\" [autoResize]=\"true\" class=\"w-full\"></textarea>\n </div>\n }\n\n @if(form.get('agenticConfig.engine').value === 'os_hermes') {\n <div class=\"form-field mb-4\">\n <label for=\"connectionUrl\" class=\"block text-sm font-medium mb-1\">URL de Conexi\u00F3n</label>\n <input pInputText id=\"connectionUrl\" type=\"text\" formControlName=\"connectionUrl\" placeholder=\"ws://...\" class=\"w-full\" />\n </div>\n }\n\n <div class=\"form-field mt-6\">\n <h4 class=\"font-semibold text-base mb-2\">Modelo de Razonamiento</h4>\n <dc-model-selector [modelForm]=\"$any(form.get('agenticConfig.reasoningModel'))\" [shortForm]=\"true\"></dc-model-selector>\n </div>\n\n <div class=\"form-field mt-6\">\n <h4 class=\"font-semibold text-base mb-2\">Modelo de Ejecuci\u00F3n</h4>\n <dc-model-selector [modelForm]=\"$any(form.get('agenticConfig.executionModel'))\" [shortForm]=\"true\"></dc-model-selector>\n </div>\n }\n </div>\n </p-accordion-content>\n </p-accordion-panel>\n }\n </p-accordion>\n\n </div>\n\n <div class=\"right-column\">\n @if(entity() && entityId()){\n <assets-loader\n [assets]=\"entity().assets\"\n [storagePath]=\"'conversation-cards/' + entityId()\"\n (assetsChange)=\"onAssetsChange($event)\"\n (assetUpdate)=\"onUpdateAsset($event)\"\n (onFileSelected)=\"onImageSelected($event)\"></assets-loader>\n } @if(form.controls.characterCard){\n\n <br />\n <dc-character-card-form [characterCardForm]=\"form.controls.characterCard\" (generateMissingDataRequest)=\"generateMissingData()\">\n </dc-character-card-form>\n }\n </div>\n </div>\n </form>\n\n <div class=\"float-button\">\n <p-button icon=\"pi pi-eye\" (click)=\"inspect()\" severity=\"secondary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Inspeccionar\"> </p-button>\n\n <p-button icon=\"pi pi-save\" (click)=\"save()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n </div>\n</p-card>\n", styles: [".conversation-form{max-width:100%;padding:20px;border-radius:8px;box-shadow:0 2px 4px #0000001a}.conversation-form .card-group{padding:20px;border-radius:6px;margin-bottom:24px}.conversation-form .card-group h3{margin:0 0 20px;color:#2c3e50;font-size:1.25rem}.conversation-form .form-grid{display:flex;flex-wrap:wrap;gap:2rem;width:100%}.conversation-form .form-field{margin-bottom:1.5rem;display:flex;flex-direction:column;gap:.5rem}.conversation-form .form-field label{font-weight:500}.conversation-form .form-field.checkbox{flex-direction:row;align-items:center;gap:.5rem}.conversation-form .form-field.checkbox input[type=checkbox]{width:auto}.conversation-form .form-field .error{color:#dc3545;font-size:.875rem;margin-top:.25rem}.conversation-form .form-field .remove-button{position:absolute;border:none;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;cursor:pointer;top:-10px;right:-10px}.conversation-form .left-column,.conversation-form .right-column{display:flex;flex-direction:column;flex:1 1 calc(50% - 1rem);min-width:300px}@media(max-width:768px){.conversation-form .left-column,.conversation-form .right-column{flex:1 1 100%}}.conversation-form .card-group{padding:1rem;margin-bottom:1.5rem}.conversation-form .card-group h3{margin-top:0;margin-bottom:1rem}.top-buttons{display:flex;justify-content:space-between;margin-bottom:2rem;gap:1rem}.top-buttons button{flex:1}::ng-deep em{font-weight:900;color:#014a93}.float-button{position:fixed;bottom:4rem;right:2rem;z-index:1000;display:flex;gap:1px}.float-button :host ::ng-deep .p-button{width:4rem;height:4rem;border-radius:50%}.voice-clone-status{display:flex;flex-direction:column;gap:.5rem;margin:.75rem 0}.voice-clone-status .muted{color:var(--p-text-muted-color)}.voice-clone-status .model-tag{display:flex;align-items:center;gap:.75rem;padding:.5rem .75rem;background:var(--p-surface-100, #f3f4f6);border-radius:6px;font-size:.9rem}.voice-clone-status .model-tag code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.8rem}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormArrayDirective, selector: "[formArray]", inputs: ["formArray"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$2.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "component", type: AssetsLoaderComponent, selector: "assets-loader", inputs: ["assets", "storagePath"], outputs: ["assetsChange", "assetUpdate", "onFileSelected"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i3$3.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i4$1.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i6.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: DynamicDialogModule }, { kind: "component", type: AccountPlatformForm, selector: "account-platform-form", inputs: ["formArray"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i2$6.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: AccordionModule }, { kind: "component", type: i8.Accordion, selector: "p-accordion", inputs: ["value", "multiple", "styleClass", "expandIcon", "collapseIcon", "selectOnFocus", "transitionOptions", "motionOptions"], outputs: ["valueChange", "onClose", "onOpen"] }, { kind: "component", type: i8.AccordionPanel, selector: "p-accordion-panel, p-accordionpanel", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: i8.AccordionHeader, selector: "p-accordion-header, p-accordionheader" }, { kind: "component", type: i8.AccordionContent, selector: "p-accordion-content, p-accordioncontent" }, { kind: "component", type: DCConversationFlowFormComponent, selector: "dc-conversation-flow-form", inputs: ["formGroup"] }, { kind: "component", type: DcCharacterCardFormComponent, selector: "dc-character-card-form", inputs: ["characterCardForm"], outputs: ["generateMissingDataRequest"] }, { kind: "ngmodule", type: MessageModule }, { kind: "component", type: DcConversationSettingsFormComponent, selector: "dc-conversation-settings-form", inputs: ["form", "textEngineOptions", "conversationOptions", "voiceTTSOptions"] }, { kind: "component", type: DcVoiceTtsFormComponent, selector: "dc-voice-tts-form", inputs: ["form", "title", "voiceTTSOptions", "fullForm"] }, { kind: "component", type: DcManageableFormComponent, selector: "dc-manageable-form", inputs: ["form"] }, { kind: "component", type: DcLearnableFormComponent, selector: "dc-learnable-form", inputs: ["form"] }, { kind: "component", type: SimpleUploaderComponent, selector: "dc-simple-uploader", inputs: ["storagePath", "buttonLabel", "accept", "disabled", "metadata", "provider"], outputs: ["fileUploaded", "uploadError"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i4.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["pTextareaPT", "pTextareaUnstyled", "autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "component", type: ModelSelectorComponent, selector: "dc-model-selector", inputs: ["modelForm", "shortForm"] }] }); }
|
|
9169
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: DCAgentCardFormComponent, isStandalone: true, selector: "dc-agent-form", inputs: { inputSettings: { classPropertyName: "inputSettings", publicName: "inputSettings", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSave: "onSave", onSelectLive2d: "onSelectLive2d" }, providers: [DialogService], usesInheritance: true, ngImport: i0, template: "<p-card>\n <div class=\"top-buttons\">\n <button pButton severity=\"info\" (click)=\"checkPrompt()\" label=\"\uD83D\uDC41\uFE0F Ver instrucciones finales \uD83D\uDCD3\"></button>\n\n <button pButton severity=\"info\" (click)=\"goToDetails()\" label=\"\uD83D\uDCAC Conversar\"></button>\n <button pButton severity=\"primary\" (click)=\"save()\" label=\"\uD83D\uDCBE Guardar cambios\"></button>\n </div>\n\n <div class=\"top-buttons\">\n <p-button [loading]=\"isGenerating()\" severity=\"help\" (click)=\"generateCharacter()\" label=\"Generar \uD83E\uDDBE\"></p-button>\n <p-button severity=\"help\" (click)=\"goToCardsCreator()\" label=\" Creador masivo \"></p-button>\n\n <p-button severity=\"info\" (click)=\"downloadConversation()\" label=\"\uD83D\uDCC1 Exportar \u2B07\uFE0F\"></p-button>\n <p-button severity=\"info\" (click)=\"importConversation()\" label=\"\uD83C\uDCCF Importar \u2B06\uFE0F\"></p-button>\n <p-button severity=\"danger\" (click)=\"deleteCard()\" icon=\"pi pi-trash\"></p-button>\n </div>\n\n <br />\n <br />\n <form [formGroup]=\"form\" class=\"conversation-form\">\n <div class=\"form-grid\">\n <div class=\"left-column\">\n <div title=\"Main data\" >\n <div style=\"display: flex; gap: 15px\">\n <div class=\"form-field\">\n <label for=\"version\">Version: {{ form.get('version').value }} <span pTooltip=\"Version number of the conversation\">\u2139\uFE0F</span></label>\n </div>\n\n <div class=\"form-field\">\n <label for=\"id\"\n >ID: <span pTooltip=\"Unique identifier for this conversation\"> {{ form.get('id').value }} \u2139\uFE0F</span></label\n >\n </div>\n </div>\n\n\n <div class=\"form-field\">\n <label for=\"name\">Card Name <span pTooltip=\"Name of the Agent Card\">\u2139\uFE0F</span></label>\n <input pInputText id=\"name\" type=\"text\" formControlName=\"name\" />\n @if(form.get('name').errors?.['required'] && form.get('name').touched){\n <div class=\"error\"> Name is required </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"description\"> Description <span pTooltip=\"Description of the conversation\">\u2139\uFE0F</span></label>\n <input pInputText id=\"description\" type=\"text\" formControlName=\"description\" />\n @if(form.get('description').errors?.['required'] && form.get('description').touched){\n <div class=\"error\"> Description is required </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"lang\">Language <span pTooltip=\"Select the primary language for the conversation\">\u2139\uFE0F</span></label>\n <p-select\n id=\"lang\"\n [options]=\"languageOptions\"\n [filter]=\"true\"\n formControlName=\"lang\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Language'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"agentType\">Agent Type <span pTooltip=\"Select the type of agent\">\u2139\uFE0F</span></label>\n <p-select id=\"agentType\" [options]=\"agentTypeOptions\" formControlName=\"agentType\" [placeholder]=\"'Select Agent Type'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"avatarType\">Avatar Type <span pTooltip=\"image: static picture | video: double looping video (ping-pong) | live2d: interactive 2D model with real-time audio lip-sync | vroid: 3D model (future)\">\u2139\uFE0F</span></label>\n <p-select\n id=\"avatarType\"\n [options]=\"avatarTypeOptions\"\n formControlName=\"avatarType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Avatar Type\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label>Avatar Live2D <span pTooltip=\"Selecciona el modelo Live2D para este agente\">\u2139\uFE0F</span></label>\n <div style=\"display: flex; align-items: center; gap: 12px; margin-top: 4px;\">\n @if (form.get('live2d')?.value; as live2d) {\n <div style=\"display: flex; align-items: center; gap: 8px; padding: 6px 12px; border: 1px solid var(--p-border-color); border-radius: 6px; background: var(--p-content-background);\">\n <img width=\"36px\" height=\"36px\" style=\"border-radius: 50%; object-fit: cover;\" [src]=\"live2d.image?.url || 'defaults/images/face-3.jpg'\" alt=\"live2d preview\" />\n <span style=\"font-weight: 500;\">{{ live2d.name }}</span>\n <p-button icon=\"pi pi-times\" [text]=\"true\" severity=\"danger\" (onClick)=\"form.patchValue({ live2d: null })\" />\n </div>\n }\n <p-button (onClick)=\"onSelectLive2d.emit()\" label=\"Seleccionar Live2D\" icon=\"pi pi-search\" [outlined]=\"true\" />\n </div>\n </div>\n </div>\n\n <p-accordion [multiple]=\"true\" >\n <p-accordion-panel value=\"manageable\">\n <p-accordion-header>\n <span>Manageable</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Controla visibilidad y acceso</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-manageable-form [form]=\"form.controls.manageable\"></dc-manageable-form>\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"learnable\">\n <p-accordion-header>\n <span>Learnable</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Aprendizaje y conocimiento</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-learnable-form [form]=\"form.controls.learnable\"></dc-learnable-form>\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"conversationSettings\">\n <p-accordion-header>\n <span>Conversation Settings</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Modelo, voz y configuraci\u00F3n del chat</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-conversation-settings-form\n [form]=\"form.controls.conversationSettings\"\n [textEngineOptions]=\"textEngineOptions\"\n [conversationOptions]=\"conversationOptions\"\n [voiceTTSOptions]=\"voiceTTSOptions\">\n </dc-conversation-settings-form>\n </p-accordion-content>\n </p-accordion-panel>\n @if(form.controls.conversationFlow){\n <p-accordion-panel value=\"conversationFlow\">\n <p-accordion-header>\n <span>Conversation Flow</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Objetivos, triggers y condiciones din\u00E1micas</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-conversation-flow-form [formGroup]=\"form.controls.conversationFlow\"></dc-conversation-flow-form>\n </p-accordion-content>\n </p-accordion-panel>\n }\n <p-accordion-panel value=\"accounts\">\n <p-accordion-header>\n <span>Gesti\u00F3n de cuentas</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Beta</span>\n </p-accordion-header>\n <p-accordion-content>\n @if(form.controls.accounts) {\n <account-platform-form [formArray]=\"form.controls.accounts\"></account-platform-form>\n }\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"voice\">\n <p-accordion-header>\n <span>Voz</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Sintesis y Clonaci\u00F3n</span>\n\n </p-accordion-header>\n <p-accordion-content>\n <div formGroupName=\"voice\">\n <div formGroupName=\"cloning\">\n <div class=\"form-field\">\n <label for=\"new_transcription\">Transcription (Voice.Cloning)</label>\n <textarea pTextarea id=\"new_transcription\" formControlName=\"transcription\" rows=\"3\"></textarea>\n </div>\n <div class=\"form-field\">\n <dc-simple-uploader\n buttonLabel=\"Subir audio (Voice.Cloning)\"\n [storagePath]=\"'conversation-cards/' + entityId()\"\n (fileUploaded)=\"onFileUploaded($event)\"></dc-simple-uploader>\n @if(form.get('voice.cloning.sample')?.value?.url){\n <audio controls [src]=\"form.get('voice.cloning.sample').value.url\"></audio>\n }\n </div>\n </div>\n <div class=\"form-field voice-clone-status\">\n @if(!form.get('voice.cloning.sample')?.value?.url){\n <small class=\"muted\">Sube un audio para entrenar un modelo de voz en la nueva propiedad.</small>\n }\n <p-button\n icon=\"pi pi-bolt\"\n severity=\"help\"\n [loading]=\"isCloningVoice()\"\n label=\"Crear modelo en Fish Audio (Voice)\"\n (click)=\"cloneVoice()\">\n </p-button>\n </div>\n \n <dc-voice-tts-form\n [form]=\"$any(form.get('voice.main'))\"\n [title]=\"'Main Voice'\"\n [voiceTTSOptions]=\"voiceTTSOptions\"\n [fullForm]=\"true\">\n </dc-voice-tts-form>\n \n <dc-voice-tts-form\n [form]=\"$any(form.get('voice.secondary'))\"\n [title]=\"'Secondary Voice'\"\n [voiceTTSOptions]=\"voiceTTSOptions\"\n [fullForm]=\"true\">\n </dc-voice-tts-form>\n </div>\n </p-accordion-content>\n </p-accordion-panel>\n @if(form.controls.agenticConfig) {\n <p-accordion-panel value=\"agenticConfig\">\n <p-accordion-header>\n <span>Configuraci\u00F3n Ag\u00E9ntica</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Motor ag\u00E9ntico, patrones y herramientas</span>\n </p-accordion-header>\n <p-accordion-content>\n <div formGroupName=\"agenticConfig\">\n <div class=\"form-field flex items-center gap-2 mb-4\">\n <p-checkbox id=\"agenticEnabled\" formControlName=\"enabled\" [binary]=\"true\"></p-checkbox>\n <label for=\"agenticEnabled\" class=\"cursor-pointer\">Habilitar Agente Aut\u00F3nomo</label>\n </div>\n\n @if(form.get('agenticConfig.enabled').value) {\n <div class=\"form-field mb-4\">\n <label for=\"agenticEngine\" class=\"block text-sm font-medium mb-1\">Motor Ag\u00E9ntico</label>\n <p-select\n id=\"agenticEngine\"\n [options]=\"agenticEngineOptions\"\n formControlName=\"engine\"\n [placeholder]=\"'Seleccionar Motor'\"\n class=\"w-full\"></p-select>\n </div>\n\n <div class=\"form-field mb-4\">\n <label for=\"agenticPattern\" class=\"block text-sm font-medium mb-1\">Patr\u00F3n Ag\u00E9ntico</label>\n <p-select\n id=\"agenticPattern\"\n [options]=\"agenticPatternOptions\"\n formControlName=\"pattern\"\n [placeholder]=\"'Seleccionar Patr\u00F3n'\"\n class=\"w-full\"></p-select>\n </div>\n\n <div class=\"form-field mb-4\">\n <label for=\"maxIterations\" class=\"block text-sm font-medium mb-1\">M\u00E1ximo de Iteraciones</label>\n <input pInputText id=\"maxIterations\" type=\"number\" formControlName=\"maxIterations\" class=\"w-full\" />\n </div>\n\n <div class=\"form-field mb-4\">\n <label class=\"block text-sm font-medium mb-2\">Herramientas Permitidas</label>\n <div class=\"flex flex-col gap-2 mt-2 pl-2\">\n @for (toolOpt of allowedToolsOptions; track toolOpt.value) {\n <div class=\"flex items-center gap-2\">\n <p-checkbox\n [inputId]=\"'tool_' + toolOpt.value\"\n [binary]=\"true\"\n [ngModel]=\"isToolAllowed(toolOpt.value)\"\n [ngModelOptions]=\"{standalone: true}\"\n (onChange)=\"toggleTool(toolOpt.value, $event.checked)\">\n </p-checkbox>\n <label [for]=\"'tool_' + toolOpt.value\" class=\"cursor-pointer text-sm\">{{ toolOpt.label }}</label>\n </div>\n }\n </div>\n </div>\n\n @if(form.get('agenticConfig.pattern').value === 'reflection') {\n <div class=\"form-field mb-4\">\n <label for=\"reflectionPrompt\" class=\"block text-sm font-medium mb-1\">Prompt de Reflexi\u00F3n</label>\n <textarea pTextarea id=\"reflectionPrompt\" formControlName=\"reflectionPrompt\" rows=\"3\" [autoResize]=\"true\" class=\"w-full\"></textarea>\n </div>\n }\n\n @if(form.get('agenticConfig.engine').value === 'os_hermes') {\n <div class=\"form-field mb-4\">\n <label for=\"connectionUrl\" class=\"block text-sm font-medium mb-1\">URL de Conexi\u00F3n</label>\n <input pInputText id=\"connectionUrl\" type=\"text\" formControlName=\"connectionUrl\" placeholder=\"ws://...\" class=\"w-full\" />\n </div>\n }\n\n <div class=\"form-field mt-6\">\n <h4 class=\"font-semibold text-base mb-2\">Modelo de Razonamiento</h4>\n <dc-model-selector [modelForm]=\"$any(form.get('agenticConfig.reasoningModel'))\" [shortForm]=\"true\"></dc-model-selector>\n </div>\n\n <div class=\"form-field mt-6\">\n <h4 class=\"font-semibold text-base mb-2\">Modelo de Ejecuci\u00F3n</h4>\n <dc-model-selector [modelForm]=\"$any(form.get('agenticConfig.executionModel'))\" [shortForm]=\"true\"></dc-model-selector>\n </div>\n }\n </div>\n </p-accordion-content>\n </p-accordion-panel>\n }\n </p-accordion>\n\n </div>\n\n <div class=\"right-column\">\n @if(entity() && entityId()){\n <assets-loader\n [assets]=\"entity().assets\"\n [storagePath]=\"'conversation-cards/' + entityId()\"\n (assetsChange)=\"onAssetsChange($event)\"\n (assetUpdate)=\"onUpdateAsset($event)\"\n (onFileSelected)=\"onImageSelected($event)\"></assets-loader>\n } @if(form.controls.characterCard){\n\n <br />\n <dc-character-card-form [characterCardForm]=\"form.controls.characterCard\" (generateMissingDataRequest)=\"generateMissingData()\">\n </dc-character-card-form>\n }\n </div>\n </div>\n </form>\n\n <div class=\"float-button\">\n <p-button icon=\"pi pi-eye\" (click)=\"inspect()\" severity=\"secondary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Inspeccionar\"> </p-button>\n\n <p-button icon=\"pi pi-save\" (click)=\"save()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n </div>\n</p-card>\n", styles: [".conversation-form{max-width:100%;padding:20px;border-radius:8px;box-shadow:0 2px 4px #0000001a}.conversation-form .card-group{padding:20px;border-radius:6px;margin-bottom:24px}.conversation-form .card-group h3{margin:0 0 20px;color:#2c3e50;font-size:1.25rem}.conversation-form .form-grid{display:flex;flex-wrap:wrap;gap:2rem;width:100%}.conversation-form .form-field{margin-bottom:1.5rem;display:flex;flex-direction:column;gap:.5rem}.conversation-form .form-field label{font-weight:500}.conversation-form .form-field.checkbox{flex-direction:row;align-items:center;gap:.5rem}.conversation-form .form-field.checkbox input[type=checkbox]{width:auto}.conversation-form .form-field .error{color:#dc3545;font-size:.875rem;margin-top:.25rem}.conversation-form .form-field .remove-button{position:absolute;border:none;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;cursor:pointer;top:-10px;right:-10px}.conversation-form .left-column,.conversation-form .right-column{display:flex;flex-direction:column;flex:1 1 calc(50% - 1rem);min-width:300px}@media(max-width:768px){.conversation-form .left-column,.conversation-form .right-column{flex:1 1 100%}}.conversation-form .card-group{padding:1rem;margin-bottom:1.5rem}.conversation-form .card-group h3{margin-top:0;margin-bottom:1rem}.top-buttons{display:flex;justify-content:space-between;margin-bottom:2rem;gap:1rem}.top-buttons button{flex:1}::ng-deep em{font-weight:900;color:#014a93}.float-button{position:fixed;bottom:4rem;right:2rem;z-index:1000;display:flex;gap:1px}.float-button :host ::ng-deep .p-button{width:4rem;height:4rem;border-radius:50%}.voice-clone-status{display:flex;flex-direction:column;gap:.5rem;margin:.75rem 0}.voice-clone-status .muted{color:var(--p-text-muted-color)}.voice-clone-status .model-tag{display:flex;align-items:center;gap:.75rem;padding:.5rem .75rem;background:var(--p-surface-100, #f3f4f6);border-radius:6px;font-size:.9rem}.voice-clone-status .model-tag code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.8rem}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormArrayDirective, selector: "[formArray]", inputs: ["formArray"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1$2.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "component", type: AssetsLoaderComponent, selector: "assets-loader", inputs: ["assets", "storagePath"], outputs: ["assetsChange", "assetUpdate", "onFileSelected"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i2.ButtonDirective, selector: "[pButton]", inputs: ["ptButtonDirective", "pButtonPT", "pButtonUnstyled", "hostName", "text", "plain", "raised", "size", "outlined", "rounded", "iconPos", "loadingIcon", "fluid", "label", "icon", "loading", "buttonProps", "severity"] }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i3$3.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i4$1.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i6.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: DynamicDialogModule }, { kind: "component", type: AccountPlatformForm, selector: "account-platform-form", inputs: ["formArray"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i2$6.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: AccordionModule }, { kind: "component", type: i8.Accordion, selector: "p-accordion", inputs: ["value", "multiple", "styleClass", "expandIcon", "collapseIcon", "selectOnFocus", "transitionOptions", "motionOptions"], outputs: ["valueChange", "onClose", "onOpen"] }, { kind: "component", type: i8.AccordionPanel, selector: "p-accordion-panel, p-accordionpanel", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: i8.AccordionHeader, selector: "p-accordion-header, p-accordionheader" }, { kind: "component", type: i8.AccordionContent, selector: "p-accordion-content, p-accordioncontent" }, { kind: "component", type: DCConversationFlowFormComponent, selector: "dc-conversation-flow-form", inputs: ["formGroup"] }, { kind: "component", type: DcCharacterCardFormComponent, selector: "dc-character-card-form", inputs: ["characterCardForm"], outputs: ["generateMissingDataRequest"] }, { kind: "ngmodule", type: MessageModule }, { kind: "component", type: DcConversationSettingsFormComponent, selector: "dc-conversation-settings-form", inputs: ["form", "textEngineOptions", "conversationOptions", "voiceTTSOptions"] }, { kind: "component", type: DcVoiceTtsFormComponent, selector: "dc-voice-tts-form", inputs: ["form", "title", "voiceTTSOptions", "fullForm"] }, { kind: "component", type: DcManageableFormComponent, selector: "dc-manageable-form", inputs: ["form"] }, { kind: "component", type: DcLearnableFormComponent, selector: "dc-learnable-form", inputs: ["form"] }, { kind: "component", type: SimpleUploaderComponent, selector: "dc-simple-uploader", inputs: ["storagePath", "buttonLabel", "accept", "disabled", "metadata", "provider"], outputs: ["fileUploaded", "uploadError"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i4.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["pTextareaPT", "pTextareaUnstyled", "autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "component", type: ModelSelectorComponent, selector: "dc-model-selector", inputs: ["modelForm", "shortForm"] }] }); }
|
|
8815
9170
|
}
|
|
8816
9171
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DCAgentCardFormComponent, decorators: [{
|
|
8817
9172
|
type: Component,
|
|
@@ -8839,7 +9194,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
8839
9194
|
SimpleUploaderComponent,
|
|
8840
9195
|
TextareaModule,
|
|
8841
9196
|
ModelSelectorComponent,
|
|
8842
|
-
], template: "<p-card>\n <div class=\"top-buttons\">\n <button pButton severity=\"info\" (click)=\"checkPrompt()\" label=\"\uD83D\uDC41\uFE0F Ver instrucciones finales \uD83D\uDCD3\"></button>\n\n <button pButton severity=\"info\" (click)=\"goToDetails()\" label=\"\uD83D\uDCAC Conversar\"></button>\n <button pButton severity=\"primary\" (click)=\"save()\" label=\"\uD83D\uDCBE Guardar cambios\"></button>\n </div>\n\n <div class=\"top-buttons\">\n <p-button [loading]=\"isGenerating()\" severity=\"help\" (click)=\"generateCharacter()\" label=\"Generar \uD83E\uDDBE\"></p-button>\n <p-button severity=\"help\" (click)=\"goToCardsCreator()\" label=\" Creador masivo \"></p-button>\n\n <p-button severity=\"info\" (click)=\"downloadConversation()\" label=\"\uD83D\uDCC1 Exportar \u2B07\uFE0F\"></p-button>\n <p-button severity=\"info\" (click)=\"importConversation()\" label=\"\uD83C\uDCCF Importar \u2B06\uFE0F\"></p-button>\n <p-button severity=\"danger\" (click)=\"deleteCard()\" icon=\"pi pi-trash\"></p-button>\n </div>\n\n <br />\n <br />\n <form [formGroup]=\"form\" class=\"conversation-form\">\n <div class=\"form-grid\">\n <div class=\"left-column\">\n <div title=\"Main data\" >\n <div style=\"display: flex; gap: 15px\">\n <div class=\"form-field\">\n <label for=\"version\">Version: {{ form.get('version').value }} <span pTooltip=\"Version number of the conversation\">\u2139\uFE0F</span></label>\n </div>\n\n <div class=\"form-field\">\n <label for=\"id\"\n >ID: <span pTooltip=\"Unique identifier for this conversation\"> {{ form.get('id').value }} \u2139\uFE0F</span></label\n >\n </div>\n </div>\n\n\n <div class=\"form-field\">\n <label for=\"name\">Card Name <span pTooltip=\"Name of the Agent Card\">\u2139\uFE0F</span></label>\n <input pInputText id=\"name\" type=\"text\" formControlName=\"name\" />\n @if(form.get('name').errors?.['required'] && form.get('name').touched){\n <div class=\"error\"> Name is required </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"description\"> Description <span pTooltip=\"Description of the conversation\">\u2139\uFE0F</span></label>\n <input pInputText id=\"description\" type=\"text\" formControlName=\"description\" />\n @if(form.get('description').errors?.['required'] && form.get('description').touched){\n <div class=\"error\"> Description is required </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"lang\">Language <span pTooltip=\"Select the primary language for the conversation\">\u2139\uFE0F</span></label>\n <p-select\n id=\"lang\"\n [options]=\"languageOptions\"\n [filter]=\"true\"\n formControlName=\"lang\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Language'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"agentType\">Agent Type <span pTooltip=\"Select the type of agent\">\u2139\uFE0F</span></label>\n <p-select id=\"agentType\" [options]=\"agentTypeOptions\" formControlName=\"agentType\" [placeholder]=\"'Select Agent Type'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"avatarType\">Avatar Type <span pTooltip=\"image: static picture | video: double looping video (ping-pong) | live2d: interactive 2D model with real-time audio lip-sync | vroid: 3D model (future)\">\u2139\uFE0F</span></label>\n <p-select\n id=\"avatarType\"\n [options]=\"avatarTypeOptions\"\n formControlName=\"avatarType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Avatar Type\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label>Avatar Live2D <span pTooltip=\"Selecciona el modelo Live2D para este agente\">\u2139\uFE0F</span></label>\n <div style=\"display: flex; align-items: center; gap: 12px; margin-top: 4px;\">\n @if (form.get('live2d')?.value; as live2d) {\n <div style=\"display: flex; align-items: center; gap: 8px; padding: 6px 12px; border: 1px solid var(--p-border-color); border-radius: 6px; background: var(--p-content-background);\">\n <img width=\"36px\" height=\"36px\" style=\"border-radius: 50%; object-fit: cover;\" [src]=\"live2d.image?.url || 'defaults/images/face-3.jpg'\" alt=\"live2d preview\" />\n <span style=\"font-weight: 500;\">{{ live2d.name }}</span>\n <p-button icon=\"pi pi-times\" [text]=\"true\" severity=\"danger\" (onClick)=\"form.patchValue({ live2d: null })\" />\n </div>\n }\n <p-button (onClick)=\"onSelectLive2d.emit()\" label=\"Seleccionar Live2D\" icon=\"pi pi-search\" [outlined]=\"true\" />\n </div>\n </div>\n </div>\n\n <p-accordion [multiple]=\"true\" >\n <p-accordion-panel value=\"manageable\">\n <p-accordion-header>\n <span>Manageable</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Controla visibilidad y acceso</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-manageable-form [form]=\"form.controls.manageable\"></dc-manageable-form>\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"learnable\">\n <p-accordion-header>\n <span>Learnable</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Aprendizaje y conocimiento</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-learnable-form [form]=\"form.controls.learnable\"></dc-learnable-form>\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"conversationSettings\">\n <p-accordion-header>\n <span>Conversation Settings</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Modelo, voz y configuraci\u00F3n del chat</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-conversation-settings-form\n [form]=\"form.controls.conversationSettings\"\n [textEngineOptions]=\"textEngineOptions\"\n [conversationOptions]=\"conversationOptions\"\n [voiceTTSOptions]=\"voiceTTSOptions\">\n </dc-conversation-settings-form>\n </p-accordion-content>\n </p-accordion-panel>\n @if(form.controls.conversationFlow){\n <p-accordion-panel value=\"conversationFlow\">\n <p-accordion-header>\n <span>Conversation Flow</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Objetivos, triggers y condiciones din\u00E1micas</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-conversation-flow-form [formGroup]=\"form.controls.conversationFlow\"></dc-conversation-flow-form>\n </p-accordion-content>\n </p-accordion-panel>\n }\n <p-accordion-panel value=\"accounts\">\n <p-accordion-header>\n <span>Gesti\u00F3n de cuentas</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Beta</span>\n </p-accordion-header>\n <p-accordion-content>\n @if(form.controls.accounts) {\n <account-platform-form [formArray]=\"form.controls.accounts\"></account-platform-form>\n }\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"voiceCloning\">\n <p-accordion-header>\n <span>Clonaci\u00F3n de voz</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Beta</span>\n </p-accordion-header>\n <p-accordion-content>\n <div formGroupName=\"voiceCloning\">\n <div class=\"form-field\">\n <label for=\"transcription\">Transcription</label>\n <textarea pTextarea id=\"transcription\" formControlName=\"transcription\" rows=\"3\"></textarea>\n </div>\n <div class=\"form-field\">\n <dc-simple-uploader\n buttonLabel=\"Subir audio\"\n [storagePath]=\"'conversation-cards/' + entityId()\"\n (fileUploaded)=\"onFileUploaded($event)\"></dc-simple-uploader>\n @if(form.get('voiceCloning.sample').value?.url){\n <audio controls [src]=\"form.get('voiceCloning.sample').value.url\"></audio>\n }\n </div>\n <div class=\"form-field voice-clone-status\">\n @if(!form.get('voiceCloning.sample').value?.url){\n <small class=\"muted\">Sube un audio para entrenar un modelo de voz.</small>\n }\n <!-- @if(canCloneVoice()){ -->\n <p-button\n icon=\"pi pi-bolt\"\n severity=\"help\"\n [loading]=\"isCloningVoice()\"\n label=\"Crear modelo en Fish Audio\"\n (click)=\"cloneVoice()\">\n </p-button>\n <!-- } -->\n @if(fishModelId()){\n <div class=\"model-tag\">\n <span>Modelo Fish Audio: <code>{{ fishModelId() }}</code></span>\n <p-button\n icon=\"pi pi-refresh\"\n severity=\"secondary\"\n [text]=\"true\"\n [loading]=\"isCloningVoice()\"\n label=\"Regenerar\"\n (click)=\"cloneVoice()\">\n </p-button>\n </div>\n }\n </div>\n <dc-voice-tts-form\n [form]=\"$any(form.get('voiceCloning.main'))\"\n [title]=\"'Voice Cloning \u2014 Main Voice'\"\n [voiceTTSOptions]=\"voiceTTSOptions\"\n [fullForm]=\"true\">\n </dc-voice-tts-form>\n </div>\n </p-accordion-content>\n </p-accordion-panel>\n @if(form.controls.agenticConfig) {\n <p-accordion-panel value=\"agenticConfig\">\n <p-accordion-header>\n <span>Configuraci\u00F3n Ag\u00E9ntica</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Motor ag\u00E9ntico, patrones y herramientas</span>\n </p-accordion-header>\n <p-accordion-content>\n <div formGroupName=\"agenticConfig\">\n <div class=\"form-field flex items-center gap-2 mb-4\">\n <p-checkbox id=\"agenticEnabled\" formControlName=\"enabled\" [binary]=\"true\"></p-checkbox>\n <label for=\"agenticEnabled\" class=\"cursor-pointer\">Habilitar Agente Aut\u00F3nomo</label>\n </div>\n\n @if(form.get('agenticConfig.enabled').value) {\n <div class=\"form-field mb-4\">\n <label for=\"agenticEngine\" class=\"block text-sm font-medium mb-1\">Motor Ag\u00E9ntico</label>\n <p-select\n id=\"agenticEngine\"\n [options]=\"agenticEngineOptions\"\n formControlName=\"engine\"\n [placeholder]=\"'Seleccionar Motor'\"\n class=\"w-full\"></p-select>\n </div>\n\n <div class=\"form-field mb-4\">\n <label for=\"agenticPattern\" class=\"block text-sm font-medium mb-1\">Patr\u00F3n Ag\u00E9ntico</label>\n <p-select\n id=\"agenticPattern\"\n [options]=\"agenticPatternOptions\"\n formControlName=\"pattern\"\n [placeholder]=\"'Seleccionar Patr\u00F3n'\"\n class=\"w-full\"></p-select>\n </div>\n\n <div class=\"form-field mb-4\">\n <label for=\"maxIterations\" class=\"block text-sm font-medium mb-1\">M\u00E1ximo de Iteraciones</label>\n <input pInputText id=\"maxIterations\" type=\"number\" formControlName=\"maxIterations\" class=\"w-full\" />\n </div>\n\n <div class=\"form-field mb-4\">\n <label class=\"block text-sm font-medium mb-2\">Herramientas Permitidas</label>\n <div class=\"flex flex-col gap-2 mt-2 pl-2\">\n @for (toolOpt of allowedToolsOptions; track toolOpt.value) {\n <div class=\"flex items-center gap-2\">\n <p-checkbox\n [inputId]=\"'tool_' + toolOpt.value\"\n [binary]=\"true\"\n [ngModel]=\"isToolAllowed(toolOpt.value)\"\n [ngModelOptions]=\"{standalone: true}\"\n (onChange)=\"toggleTool(toolOpt.value, $event.checked)\">\n </p-checkbox>\n <label [for]=\"'tool_' + toolOpt.value\" class=\"cursor-pointer text-sm\">{{ toolOpt.label }}</label>\n </div>\n }\n </div>\n </div>\n\n @if(form.get('agenticConfig.pattern').value === 'reflection') {\n <div class=\"form-field mb-4\">\n <label for=\"reflectionPrompt\" class=\"block text-sm font-medium mb-1\">Prompt de Reflexi\u00F3n</label>\n <textarea pTextarea id=\"reflectionPrompt\" formControlName=\"reflectionPrompt\" rows=\"3\" [autoResize]=\"true\" class=\"w-full\"></textarea>\n </div>\n }\n\n @if(form.get('agenticConfig.engine').value === 'os_hermes') {\n <div class=\"form-field mb-4\">\n <label for=\"connectionUrl\" class=\"block text-sm font-medium mb-1\">URL de Conexi\u00F3n</label>\n <input pInputText id=\"connectionUrl\" type=\"text\" formControlName=\"connectionUrl\" placeholder=\"ws://...\" class=\"w-full\" />\n </div>\n }\n\n <div class=\"form-field mt-6\">\n <h4 class=\"font-semibold text-base mb-2\">Modelo de Razonamiento</h4>\n <dc-model-selector [modelForm]=\"$any(form.get('agenticConfig.reasoningModel'))\" [shortForm]=\"true\"></dc-model-selector>\n </div>\n\n <div class=\"form-field mt-6\">\n <h4 class=\"font-semibold text-base mb-2\">Modelo de Ejecuci\u00F3n</h4>\n <dc-model-selector [modelForm]=\"$any(form.get('agenticConfig.executionModel'))\" [shortForm]=\"true\"></dc-model-selector>\n </div>\n }\n </div>\n </p-accordion-content>\n </p-accordion-panel>\n }\n </p-accordion>\n\n </div>\n\n <div class=\"right-column\">\n @if(entity() && entityId()){\n <assets-loader\n [assets]=\"entity().assets\"\n [storagePath]=\"'conversation-cards/' + entityId()\"\n (assetsChange)=\"onAssetsChange($event)\"\n (assetUpdate)=\"onUpdateAsset($event)\"\n (onFileSelected)=\"onImageSelected($event)\"></assets-loader>\n } @if(form.controls.characterCard){\n\n <br />\n <dc-character-card-form [characterCardForm]=\"form.controls.characterCard\" (generateMissingDataRequest)=\"generateMissingData()\">\n </dc-character-card-form>\n }\n </div>\n </div>\n </form>\n\n <div class=\"float-button\">\n <p-button icon=\"pi pi-eye\" (click)=\"inspect()\" severity=\"secondary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Inspeccionar\"> </p-button>\n\n <p-button icon=\"pi pi-save\" (click)=\"save()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n </div>\n</p-card>\n", styles: [".conversation-form{max-width:100%;padding:20px;border-radius:8px;box-shadow:0 2px 4px #0000001a}.conversation-form .card-group{padding:20px;border-radius:6px;margin-bottom:24px}.conversation-form .card-group h3{margin:0 0 20px;color:#2c3e50;font-size:1.25rem}.conversation-form .form-grid{display:flex;flex-wrap:wrap;gap:2rem;width:100%}.conversation-form .form-field{margin-bottom:1.5rem;display:flex;flex-direction:column;gap:.5rem}.conversation-form .form-field label{font-weight:500}.conversation-form .form-field.checkbox{flex-direction:row;align-items:center;gap:.5rem}.conversation-form .form-field.checkbox input[type=checkbox]{width:auto}.conversation-form .form-field .error{color:#dc3545;font-size:.875rem;margin-top:.25rem}.conversation-form .form-field .remove-button{position:absolute;border:none;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;cursor:pointer;top:-10px;right:-10px}.conversation-form .left-column,.conversation-form .right-column{display:flex;flex-direction:column;flex:1 1 calc(50% - 1rem);min-width:300px}@media(max-width:768px){.conversation-form .left-column,.conversation-form .right-column{flex:1 1 100%}}.conversation-form .card-group{padding:1rem;margin-bottom:1.5rem}.conversation-form .card-group h3{margin-top:0;margin-bottom:1rem}.top-buttons{display:flex;justify-content:space-between;margin-bottom:2rem;gap:1rem}.top-buttons button{flex:1}::ng-deep em{font-weight:900;color:#014a93}.float-button{position:fixed;bottom:4rem;right:2rem;z-index:1000;display:flex;gap:1px}.float-button :host ::ng-deep .p-button{width:4rem;height:4rem;border-radius:50%}.voice-clone-status{display:flex;flex-direction:column;gap:.5rem;margin:.75rem 0}.voice-clone-status .muted{color:var(--p-text-muted-color)}.voice-clone-status .model-tag{display:flex;align-items:center;gap:.75rem;padding:.5rem .75rem;background:var(--p-surface-100, #f3f4f6);border-radius:6px;font-size:.9rem}.voice-clone-status .model-tag code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.8rem}\n"] }]
|
|
9197
|
+
], template: "<p-card>\n <div class=\"top-buttons\">\n <button pButton severity=\"info\" (click)=\"checkPrompt()\" label=\"\uD83D\uDC41\uFE0F Ver instrucciones finales \uD83D\uDCD3\"></button>\n\n <button pButton severity=\"info\" (click)=\"goToDetails()\" label=\"\uD83D\uDCAC Conversar\"></button>\n <button pButton severity=\"primary\" (click)=\"save()\" label=\"\uD83D\uDCBE Guardar cambios\"></button>\n </div>\n\n <div class=\"top-buttons\">\n <p-button [loading]=\"isGenerating()\" severity=\"help\" (click)=\"generateCharacter()\" label=\"Generar \uD83E\uDDBE\"></p-button>\n <p-button severity=\"help\" (click)=\"goToCardsCreator()\" label=\" Creador masivo \"></p-button>\n\n <p-button severity=\"info\" (click)=\"downloadConversation()\" label=\"\uD83D\uDCC1 Exportar \u2B07\uFE0F\"></p-button>\n <p-button severity=\"info\" (click)=\"importConversation()\" label=\"\uD83C\uDCCF Importar \u2B06\uFE0F\"></p-button>\n <p-button severity=\"danger\" (click)=\"deleteCard()\" icon=\"pi pi-trash\"></p-button>\n </div>\n\n <br />\n <br />\n <form [formGroup]=\"form\" class=\"conversation-form\">\n <div class=\"form-grid\">\n <div class=\"left-column\">\n <div title=\"Main data\" >\n <div style=\"display: flex; gap: 15px\">\n <div class=\"form-field\">\n <label for=\"version\">Version: {{ form.get('version').value }} <span pTooltip=\"Version number of the conversation\">\u2139\uFE0F</span></label>\n </div>\n\n <div class=\"form-field\">\n <label for=\"id\"\n >ID: <span pTooltip=\"Unique identifier for this conversation\"> {{ form.get('id').value }} \u2139\uFE0F</span></label\n >\n </div>\n </div>\n\n\n <div class=\"form-field\">\n <label for=\"name\">Card Name <span pTooltip=\"Name of the Agent Card\">\u2139\uFE0F</span></label>\n <input pInputText id=\"name\" type=\"text\" formControlName=\"name\" />\n @if(form.get('name').errors?.['required'] && form.get('name').touched){\n <div class=\"error\"> Name is required </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"description\"> Description <span pTooltip=\"Description of the conversation\">\u2139\uFE0F</span></label>\n <input pInputText id=\"description\" type=\"text\" formControlName=\"description\" />\n @if(form.get('description').errors?.['required'] && form.get('description').touched){\n <div class=\"error\"> Description is required </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label for=\"lang\">Language <span pTooltip=\"Select the primary language for the conversation\">\u2139\uFE0F</span></label>\n <p-select\n id=\"lang\"\n [options]=\"languageOptions\"\n [filter]=\"true\"\n formControlName=\"lang\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Language'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"agentType\">Agent Type <span pTooltip=\"Select the type of agent\">\u2139\uFE0F</span></label>\n <p-select id=\"agentType\" [options]=\"agentTypeOptions\" formControlName=\"agentType\" [placeholder]=\"'Select Agent Type'\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label for=\"avatarType\">Avatar Type <span pTooltip=\"image: static picture | video: double looping video (ping-pong) | live2d: interactive 2D model with real-time audio lip-sync | vroid: 3D model (future)\">\u2139\uFE0F</span></label>\n <p-select\n id=\"avatarType\"\n [options]=\"avatarTypeOptions\"\n formControlName=\"avatarType\"\n optionLabel=\"label\"\n optionValue=\"value\"\n placeholder=\"Select Avatar Type\"\n class=\"w-full mt-1\"></p-select>\n </div>\n\n <div class=\"form-field\">\n <label>Avatar Live2D <span pTooltip=\"Selecciona el modelo Live2D para este agente\">\u2139\uFE0F</span></label>\n <div style=\"display: flex; align-items: center; gap: 12px; margin-top: 4px;\">\n @if (form.get('live2d')?.value; as live2d) {\n <div style=\"display: flex; align-items: center; gap: 8px; padding: 6px 12px; border: 1px solid var(--p-border-color); border-radius: 6px; background: var(--p-content-background);\">\n <img width=\"36px\" height=\"36px\" style=\"border-radius: 50%; object-fit: cover;\" [src]=\"live2d.image?.url || 'defaults/images/face-3.jpg'\" alt=\"live2d preview\" />\n <span style=\"font-weight: 500;\">{{ live2d.name }}</span>\n <p-button icon=\"pi pi-times\" [text]=\"true\" severity=\"danger\" (onClick)=\"form.patchValue({ live2d: null })\" />\n </div>\n }\n <p-button (onClick)=\"onSelectLive2d.emit()\" label=\"Seleccionar Live2D\" icon=\"pi pi-search\" [outlined]=\"true\" />\n </div>\n </div>\n </div>\n\n <p-accordion [multiple]=\"true\" >\n <p-accordion-panel value=\"manageable\">\n <p-accordion-header>\n <span>Manageable</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Controla visibilidad y acceso</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-manageable-form [form]=\"form.controls.manageable\"></dc-manageable-form>\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"learnable\">\n <p-accordion-header>\n <span>Learnable</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Aprendizaje y conocimiento</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-learnable-form [form]=\"form.controls.learnable\"></dc-learnable-form>\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"conversationSettings\">\n <p-accordion-header>\n <span>Conversation Settings</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Modelo, voz y configuraci\u00F3n del chat</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-conversation-settings-form\n [form]=\"form.controls.conversationSettings\"\n [textEngineOptions]=\"textEngineOptions\"\n [conversationOptions]=\"conversationOptions\"\n [voiceTTSOptions]=\"voiceTTSOptions\">\n </dc-conversation-settings-form>\n </p-accordion-content>\n </p-accordion-panel>\n @if(form.controls.conversationFlow){\n <p-accordion-panel value=\"conversationFlow\">\n <p-accordion-header>\n <span>Conversation Flow</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Objetivos, triggers y condiciones din\u00E1micas</span>\n </p-accordion-header>\n <p-accordion-content>\n <dc-conversation-flow-form [formGroup]=\"form.controls.conversationFlow\"></dc-conversation-flow-form>\n </p-accordion-content>\n </p-accordion-panel>\n }\n <p-accordion-panel value=\"accounts\">\n <p-accordion-header>\n <span>Gesti\u00F3n de cuentas</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Beta</span>\n </p-accordion-header>\n <p-accordion-content>\n @if(form.controls.accounts) {\n <account-platform-form [formArray]=\"form.controls.accounts\"></account-platform-form>\n }\n </p-accordion-content>\n </p-accordion-panel>\n <p-accordion-panel value=\"voice\">\n <p-accordion-header>\n <span>Voz</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Sintesis y Clonaci\u00F3n</span>\n\n </p-accordion-header>\n <p-accordion-content>\n <div formGroupName=\"voice\">\n <div formGroupName=\"cloning\">\n <div class=\"form-field\">\n <label for=\"new_transcription\">Transcription (Voice.Cloning)</label>\n <textarea pTextarea id=\"new_transcription\" formControlName=\"transcription\" rows=\"3\"></textarea>\n </div>\n <div class=\"form-field\">\n <dc-simple-uploader\n buttonLabel=\"Subir audio (Voice.Cloning)\"\n [storagePath]=\"'conversation-cards/' + entityId()\"\n (fileUploaded)=\"onFileUploaded($event)\"></dc-simple-uploader>\n @if(form.get('voice.cloning.sample')?.value?.url){\n <audio controls [src]=\"form.get('voice.cloning.sample').value.url\"></audio>\n }\n </div>\n </div>\n <div class=\"form-field voice-clone-status\">\n @if(!form.get('voice.cloning.sample')?.value?.url){\n <small class=\"muted\">Sube un audio para entrenar un modelo de voz en la nueva propiedad.</small>\n }\n <p-button\n icon=\"pi pi-bolt\"\n severity=\"help\"\n [loading]=\"isCloningVoice()\"\n label=\"Crear modelo en Fish Audio (Voice)\"\n (click)=\"cloneVoice()\">\n </p-button>\n </div>\n \n <dc-voice-tts-form\n [form]=\"$any(form.get('voice.main'))\"\n [title]=\"'Main Voice'\"\n [voiceTTSOptions]=\"voiceTTSOptions\"\n [fullForm]=\"true\">\n </dc-voice-tts-form>\n \n <dc-voice-tts-form\n [form]=\"$any(form.get('voice.secondary'))\"\n [title]=\"'Secondary Voice'\"\n [voiceTTSOptions]=\"voiceTTSOptions\"\n [fullForm]=\"true\">\n </dc-voice-tts-form>\n </div>\n </p-accordion-content>\n </p-accordion-panel>\n @if(form.controls.agenticConfig) {\n <p-accordion-panel value=\"agenticConfig\">\n <p-accordion-header>\n <span>Configuraci\u00F3n Ag\u00E9ntica</span>\n <span style=\"margin-left: 8px; font-size: 0.75rem; color: var(--p-text-muted-color); font-weight: normal\">Motor ag\u00E9ntico, patrones y herramientas</span>\n </p-accordion-header>\n <p-accordion-content>\n <div formGroupName=\"agenticConfig\">\n <div class=\"form-field flex items-center gap-2 mb-4\">\n <p-checkbox id=\"agenticEnabled\" formControlName=\"enabled\" [binary]=\"true\"></p-checkbox>\n <label for=\"agenticEnabled\" class=\"cursor-pointer\">Habilitar Agente Aut\u00F3nomo</label>\n </div>\n\n @if(form.get('agenticConfig.enabled').value) {\n <div class=\"form-field mb-4\">\n <label for=\"agenticEngine\" class=\"block text-sm font-medium mb-1\">Motor Ag\u00E9ntico</label>\n <p-select\n id=\"agenticEngine\"\n [options]=\"agenticEngineOptions\"\n formControlName=\"engine\"\n [placeholder]=\"'Seleccionar Motor'\"\n class=\"w-full\"></p-select>\n </div>\n\n <div class=\"form-field mb-4\">\n <label for=\"agenticPattern\" class=\"block text-sm font-medium mb-1\">Patr\u00F3n Ag\u00E9ntico</label>\n <p-select\n id=\"agenticPattern\"\n [options]=\"agenticPatternOptions\"\n formControlName=\"pattern\"\n [placeholder]=\"'Seleccionar Patr\u00F3n'\"\n class=\"w-full\"></p-select>\n </div>\n\n <div class=\"form-field mb-4\">\n <label for=\"maxIterations\" class=\"block text-sm font-medium mb-1\">M\u00E1ximo de Iteraciones</label>\n <input pInputText id=\"maxIterations\" type=\"number\" formControlName=\"maxIterations\" class=\"w-full\" />\n </div>\n\n <div class=\"form-field mb-4\">\n <label class=\"block text-sm font-medium mb-2\">Herramientas Permitidas</label>\n <div class=\"flex flex-col gap-2 mt-2 pl-2\">\n @for (toolOpt of allowedToolsOptions; track toolOpt.value) {\n <div class=\"flex items-center gap-2\">\n <p-checkbox\n [inputId]=\"'tool_' + toolOpt.value\"\n [binary]=\"true\"\n [ngModel]=\"isToolAllowed(toolOpt.value)\"\n [ngModelOptions]=\"{standalone: true}\"\n (onChange)=\"toggleTool(toolOpt.value, $event.checked)\">\n </p-checkbox>\n <label [for]=\"'tool_' + toolOpt.value\" class=\"cursor-pointer text-sm\">{{ toolOpt.label }}</label>\n </div>\n }\n </div>\n </div>\n\n @if(form.get('agenticConfig.pattern').value === 'reflection') {\n <div class=\"form-field mb-4\">\n <label for=\"reflectionPrompt\" class=\"block text-sm font-medium mb-1\">Prompt de Reflexi\u00F3n</label>\n <textarea pTextarea id=\"reflectionPrompt\" formControlName=\"reflectionPrompt\" rows=\"3\" [autoResize]=\"true\" class=\"w-full\"></textarea>\n </div>\n }\n\n @if(form.get('agenticConfig.engine').value === 'os_hermes') {\n <div class=\"form-field mb-4\">\n <label for=\"connectionUrl\" class=\"block text-sm font-medium mb-1\">URL de Conexi\u00F3n</label>\n <input pInputText id=\"connectionUrl\" type=\"text\" formControlName=\"connectionUrl\" placeholder=\"ws://...\" class=\"w-full\" />\n </div>\n }\n\n <div class=\"form-field mt-6\">\n <h4 class=\"font-semibold text-base mb-2\">Modelo de Razonamiento</h4>\n <dc-model-selector [modelForm]=\"$any(form.get('agenticConfig.reasoningModel'))\" [shortForm]=\"true\"></dc-model-selector>\n </div>\n\n <div class=\"form-field mt-6\">\n <h4 class=\"font-semibold text-base mb-2\">Modelo de Ejecuci\u00F3n</h4>\n <dc-model-selector [modelForm]=\"$any(form.get('agenticConfig.executionModel'))\" [shortForm]=\"true\"></dc-model-selector>\n </div>\n }\n </div>\n </p-accordion-content>\n </p-accordion-panel>\n }\n </p-accordion>\n\n </div>\n\n <div class=\"right-column\">\n @if(entity() && entityId()){\n <assets-loader\n [assets]=\"entity().assets\"\n [storagePath]=\"'conversation-cards/' + entityId()\"\n (assetsChange)=\"onAssetsChange($event)\"\n (assetUpdate)=\"onUpdateAsset($event)\"\n (onFileSelected)=\"onImageSelected($event)\"></assets-loader>\n } @if(form.controls.characterCard){\n\n <br />\n <dc-character-card-form [characterCardForm]=\"form.controls.characterCard\" (generateMissingDataRequest)=\"generateMissingData()\">\n </dc-character-card-form>\n }\n </div>\n </div>\n </form>\n\n <div class=\"float-button\">\n <p-button icon=\"pi pi-eye\" (click)=\"inspect()\" severity=\"secondary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Inspeccionar\"> </p-button>\n\n <p-button icon=\"pi pi-save\" (click)=\"save()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n </div>\n</p-card>\n", styles: [".conversation-form{max-width:100%;padding:20px;border-radius:8px;box-shadow:0 2px 4px #0000001a}.conversation-form .card-group{padding:20px;border-radius:6px;margin-bottom:24px}.conversation-form .card-group h3{margin:0 0 20px;color:#2c3e50;font-size:1.25rem}.conversation-form .form-grid{display:flex;flex-wrap:wrap;gap:2rem;width:100%}.conversation-form .form-field{margin-bottom:1.5rem;display:flex;flex-direction:column;gap:.5rem}.conversation-form .form-field label{font-weight:500}.conversation-form .form-field.checkbox{flex-direction:row;align-items:center;gap:.5rem}.conversation-form .form-field.checkbox input[type=checkbox]{width:auto}.conversation-form .form-field .error{color:#dc3545;font-size:.875rem;margin-top:.25rem}.conversation-form .form-field .remove-button{position:absolute;border:none;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;cursor:pointer;top:-10px;right:-10px}.conversation-form .left-column,.conversation-form .right-column{display:flex;flex-direction:column;flex:1 1 calc(50% - 1rem);min-width:300px}@media(max-width:768px){.conversation-form .left-column,.conversation-form .right-column{flex:1 1 100%}}.conversation-form .card-group{padding:1rem;margin-bottom:1.5rem}.conversation-form .card-group h3{margin-top:0;margin-bottom:1rem}.top-buttons{display:flex;justify-content:space-between;margin-bottom:2rem;gap:1rem}.top-buttons button{flex:1}::ng-deep em{font-weight:900;color:#014a93}.float-button{position:fixed;bottom:4rem;right:2rem;z-index:1000;display:flex;gap:1px}.float-button :host ::ng-deep .p-button{width:4rem;height:4rem;border-radius:50%}.voice-clone-status{display:flex;flex-direction:column;gap:.5rem;margin:.75rem 0}.voice-clone-status .muted{color:var(--p-text-muted-color)}.voice-clone-status .model-tag{display:flex;align-items:center;gap:.75rem;padding:.5rem .75rem;background:var(--p-surface-100, #f3f4f6);border-radius:6px;font-size:.9rem}.voice-clone-status .model-tag code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.8rem}\n"] }]
|
|
8843
9198
|
}], propDecorators: { onSave: [{ type: i0.Output, args: ["onSave"] }], inputSettings: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputSettings", required: false }] }], onSelectLive2d: [{ type: i0.Output, args: ["onSelectLive2d"] }] } });
|
|
8844
9199
|
|
|
8845
9200
|
class TruncatePipe {
|
|
@@ -9014,7 +9369,7 @@ class AgentCardListComponent extends EntityBaseListV2Component {
|
|
|
9014
9369
|
const adminFilters = [
|
|
9015
9370
|
{
|
|
9016
9371
|
name: 'Voz Única',
|
|
9017
|
-
field: '
|
|
9372
|
+
field: 'voice.cloning.sample',
|
|
9018
9373
|
type: 'select',
|
|
9019
9374
|
operator: '$not_null',
|
|
9020
9375
|
options: [
|
|
@@ -9160,6 +9515,25 @@ class Live2dModelComponent {
|
|
|
9160
9515
|
this.canvasRef.nativeElement.addEventListener('wheel', this.onWheel.bind(this), { passive: false });
|
|
9161
9516
|
this.isViewInitialized.set(true);
|
|
9162
9517
|
}
|
|
9518
|
+
ngOnDestroy() {
|
|
9519
|
+
if (this.resizeObserver) {
|
|
9520
|
+
this.resizeObserver.disconnect();
|
|
9521
|
+
}
|
|
9522
|
+
if (this.model) {
|
|
9523
|
+
try {
|
|
9524
|
+
this.model.destroy();
|
|
9525
|
+
}
|
|
9526
|
+
catch (e) { }
|
|
9527
|
+
}
|
|
9528
|
+
if (this.app) {
|
|
9529
|
+
try {
|
|
9530
|
+
this.app.destroy(true);
|
|
9531
|
+
}
|
|
9532
|
+
catch (e) {
|
|
9533
|
+
console.warn('Error destroying PixiJS app', e);
|
|
9534
|
+
}
|
|
9535
|
+
}
|
|
9536
|
+
}
|
|
9163
9537
|
async initializeCanvas() {
|
|
9164
9538
|
if (!this.live2dConfig) {
|
|
9165
9539
|
console.warn('Live2D configuration not provided. Please call provideLive2D in your app bootstrap.');
|
|
@@ -9167,10 +9541,11 @@ class Live2dModelComponent {
|
|
|
9167
9541
|
}
|
|
9168
9542
|
const PIXI = await this.live2dConfig.PIXI();
|
|
9169
9543
|
this.app = new PIXI.Application();
|
|
9544
|
+
const parentEl = this.canvasRef.nativeElement.parentElement;
|
|
9170
9545
|
if (typeof this.app.init === 'function') {
|
|
9171
9546
|
await this.app.init({
|
|
9172
9547
|
canvas: this.canvasRef.nativeElement,
|
|
9173
|
-
resizeTo:
|
|
9548
|
+
resizeTo: parentEl || undefined,
|
|
9174
9549
|
backgroundColor: 0x000000,
|
|
9175
9550
|
backgroundAlpha: 0,
|
|
9176
9551
|
antialias: true,
|
|
@@ -9181,6 +9556,14 @@ class Live2dModelComponent {
|
|
|
9181
9556
|
else {
|
|
9182
9557
|
alert('Not able to load the model');
|
|
9183
9558
|
}
|
|
9559
|
+
if (parentEl && typeof ResizeObserver !== 'undefined') {
|
|
9560
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
9561
|
+
if (this.app && typeof this.app.resize === 'function') {
|
|
9562
|
+
this.app.resize();
|
|
9563
|
+
}
|
|
9564
|
+
});
|
|
9565
|
+
this.resizeObserver.observe(parentEl);
|
|
9566
|
+
}
|
|
9184
9567
|
}
|
|
9185
9568
|
async loadModel(modelPath) {
|
|
9186
9569
|
if (!this.live2dConfig) {
|
|
@@ -9271,7 +9654,7 @@ class Live2dModelComponent {
|
|
|
9271
9654
|
this.playAnimation(group);
|
|
9272
9655
|
}
|
|
9273
9656
|
}
|
|
9274
|
-
speak(audioUrl, options) {
|
|
9657
|
+
async speak(audioUrl, options) {
|
|
9275
9658
|
if (!this.model)
|
|
9276
9659
|
return;
|
|
9277
9660
|
if (options?.focus !== false) {
|
|
@@ -9280,8 +9663,21 @@ class Live2dModelComponent {
|
|
|
9280
9663
|
this.viewStateChanged.emit(transform);
|
|
9281
9664
|
}
|
|
9282
9665
|
}
|
|
9666
|
+
this.stopSpeaking();
|
|
9667
|
+
const targetVolume = options?.volume !== undefined ? options.volume : 1.0;
|
|
9668
|
+
if (this.live2dConfig) {
|
|
9669
|
+
try {
|
|
9670
|
+
const live2dEngine = await this.live2dConfig.Live2DModel();
|
|
9671
|
+
if (live2dEngine && live2dEngine.SoundManager) {
|
|
9672
|
+
live2dEngine.SoundManager.volume = targetVolume;
|
|
9673
|
+
}
|
|
9674
|
+
}
|
|
9675
|
+
catch (e) {
|
|
9676
|
+
console.warn('Failed to set SoundManager volume', e);
|
|
9677
|
+
}
|
|
9678
|
+
}
|
|
9283
9679
|
this.model.speak(audioUrl, {
|
|
9284
|
-
volume:
|
|
9680
|
+
volume: targetVolume,
|
|
9285
9681
|
crossOrigin: options?.crossOrigin || 'anonymous',
|
|
9286
9682
|
});
|
|
9287
9683
|
}
|
|
@@ -9418,7 +9814,7 @@ class Live2dModelComponent {
|
|
|
9418
9814
|
const anchorY = 0.5;
|
|
9419
9815
|
const offsetX = (faceCenterX - (localBounds.x + localBounds.width * anchorX)) * suggestedScale;
|
|
9420
9816
|
const offsetY = (faceCenterY - (localBounds.y + localBounds.height * anchorY)) * suggestedScale;
|
|
9421
|
-
const verticalFocusPoint = 0.
|
|
9817
|
+
const verticalFocusPoint = 0.20;
|
|
9422
9818
|
const targetX = (this.app.screen.width / 2) - offsetX;
|
|
9423
9819
|
const targetY = (this.app.screen.height * verticalFocusPoint) - offsetY;
|
|
9424
9820
|
const xPct = (targetX / this.app.screen.width) * 100;
|
|
@@ -9440,7 +9836,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
9440
9836
|
args: ['canvas']
|
|
9441
9837
|
}], modelPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "modelPath", required: true }] }], scale: [{ type: i0.Input, args: [{ isSignal: true, alias: "scale", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }], x: [{ type: i0.Input, args: [{ isSignal: true, alias: "x", required: false }] }], y: [{ type: i0.Input, args: [{ isSignal: true, alias: "y", required: false }] }], modelLoaded: [{ type: i0.Output, args: ["modelLoaded"] }], viewStateChanged: [{ type: i0.Output, args: ["viewStateChanged"] }] } });
|
|
9442
9838
|
|
|
9443
|
-
class
|
|
9839
|
+
class DcAgentCardAvatarComponent {
|
|
9444
9840
|
set isConversationActive(val) {
|
|
9445
9841
|
this._isConversationActive.set(val);
|
|
9446
9842
|
}
|
|
@@ -9462,8 +9858,8 @@ class DcAgentCardConverseComponent {
|
|
|
9462
9858
|
this.videoPlayerService = inject(VideoPlayerNativeService);
|
|
9463
9859
|
this.agentCardId = '';
|
|
9464
9860
|
this.useNativePlayer = true;
|
|
9465
|
-
this.activeAudioMessage = null;
|
|
9466
9861
|
this._isConversationActive = signal(false, ...(ngDevMode ? [{ debugName: "_isConversationActive" }] : /* istanbul ignore next */ []));
|
|
9862
|
+
/** @deprecated Use ChatMonitorService instead — el canal oficial es el servicio compartido. */
|
|
9467
9863
|
this.onStartConversation = output();
|
|
9468
9864
|
this.videoPlayerA = viewChild('videoPlayerA', ...(ngDevMode ? [{ debugName: "videoPlayerA" }] : /* istanbul ignore next */ []));
|
|
9469
9865
|
this.videoPlayerB = viewChild('videoPlayerB', ...(ngDevMode ? [{ debugName: "videoPlayerB" }] : /* istanbul ignore next */ []));
|
|
@@ -9544,25 +9940,48 @@ class DcAgentCardConverseComponent {
|
|
|
9544
9940
|
this.videoPlayerService.setAgentCard(card);
|
|
9545
9941
|
}
|
|
9546
9942
|
});
|
|
9547
|
-
// Live2D Speak (Lip-Sync) effect
|
|
9943
|
+
// Live2D Speak (Lip-Sync) effect — listening directly to ChatMonitorService's activeAudioMessage
|
|
9548
9944
|
effect(() => {
|
|
9549
|
-
const audioMsg = this.activeAudioMessage;
|
|
9945
|
+
const audioMsg = this.chatMonitorService.activeAudioMessage();
|
|
9946
|
+
console.log('DcAgentCardAvatarComponent: Speak effect triggered. activeAudioMessage:', audioMsg ? {
|
|
9947
|
+
messageId: audioMsg.messageId,
|
|
9948
|
+
audioUrl: audioMsg.audioUrl
|
|
9949
|
+
} : null);
|
|
9550
9950
|
const type = this.avatarType();
|
|
9551
9951
|
const l2d = this.live2dModel();
|
|
9552
|
-
if (type === 'live2d' &&
|
|
9553
|
-
|
|
9554
|
-
|
|
9952
|
+
if (type === 'live2d' && l2d) {
|
|
9953
|
+
if (audioMsg && audioMsg.audioUrl) {
|
|
9954
|
+
console.log('DcAgentCardAvatarComponent: Live2D speak started with URL:', audioMsg.audioUrl);
|
|
9955
|
+
l2d.stopSpeaking();
|
|
9956
|
+
l2d.speak(audioMsg.audioUrl, { volume: 0.0 });
|
|
9957
|
+
}
|
|
9958
|
+
else {
|
|
9959
|
+
console.log('DcAgentCardAvatarComponent: Live2D speak stopped (no active message or no audioUrl)');
|
|
9960
|
+
l2d.stopSpeaking();
|
|
9961
|
+
}
|
|
9555
9962
|
}
|
|
9556
9963
|
});
|
|
9557
9964
|
// Live2D Mood state emotion updates mapping
|
|
9558
9965
|
effect(() => {
|
|
9559
|
-
const mood = this.
|
|
9966
|
+
const mood = this.chatMonitorService.currentMood();
|
|
9560
9967
|
const type = this.avatarType();
|
|
9561
9968
|
const l2d = this.live2dModel();
|
|
9562
9969
|
if (type === 'live2d' && mood && l2d) {
|
|
9563
9970
|
l2d.setExpression(mood);
|
|
9564
9971
|
}
|
|
9565
9972
|
});
|
|
9973
|
+
// Native Video player mood state updates mapping
|
|
9974
|
+
effect(() => {
|
|
9975
|
+
const mood = this.chatMonitorService.currentMood();
|
|
9976
|
+
const card = this.agentCard();
|
|
9977
|
+
const type = this.avatarType();
|
|
9978
|
+
if (type === 'video' && mood && card?.assets?.motions) {
|
|
9979
|
+
const motionUrl = card.assets.motions.find((m) => m.metadata?.moodState === mood)?.url;
|
|
9980
|
+
if (motionUrl) {
|
|
9981
|
+
this.videoPlayerService.setVideoSource(motionUrl);
|
|
9982
|
+
}
|
|
9983
|
+
}
|
|
9984
|
+
});
|
|
9566
9985
|
}
|
|
9567
9986
|
ngOnDestroy() {
|
|
9568
9987
|
this.videoPlayerService.cleanUp();
|
|
@@ -9614,16 +10033,16 @@ class DcAgentCardConverseComponent {
|
|
|
9614
10033
|
const locale = LANGUAGES[targetLang]?.defaultLocale;
|
|
9615
10034
|
if (!locale)
|
|
9616
10035
|
return cardTranslated;
|
|
9617
|
-
if (cardTranslated.
|
|
9618
|
-
const transformedMainVoice = this._transformVoice(cardTranslated.
|
|
10036
|
+
if (cardTranslated.voice?.main) {
|
|
10037
|
+
const transformedMainVoice = this._transformVoice(cardTranslated.voice.main.voice, locale);
|
|
9619
10038
|
if (transformedMainVoice) {
|
|
9620
|
-
cardTranslated.
|
|
10039
|
+
cardTranslated.voice.main.voice = transformedMainVoice;
|
|
9621
10040
|
}
|
|
9622
10041
|
}
|
|
9623
|
-
if (cardTranslated.
|
|
9624
|
-
const transformedSecondaryVoice = this._transformVoice(cardTranslated.
|
|
10042
|
+
if (cardTranslated.voice?.secondary) {
|
|
10043
|
+
const transformedSecondaryVoice = this._transformVoice(cardTranslated.voice.secondary.voice, locale);
|
|
9625
10044
|
if (transformedSecondaryVoice) {
|
|
9626
|
-
cardTranslated.
|
|
10045
|
+
cardTranslated.voice.secondary.voice = transformedSecondaryVoice;
|
|
9627
10046
|
}
|
|
9628
10047
|
}
|
|
9629
10048
|
return cardTranslated;
|
|
@@ -9656,18 +10075,16 @@ class DcAgentCardConverseComponent {
|
|
|
9656
10075
|
toggleInfoLayer() {
|
|
9657
10076
|
this.showInfoLayer.update((value) => !value);
|
|
9658
10077
|
}
|
|
9659
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type:
|
|
9660
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type:
|
|
10078
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcAgentCardAvatarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10079
|
+
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%;position:relative}.transparent-mode .card-container{width:420px;height:700px;position:relative}.transparent-mode .card-image{background:transparent}.fullscreen-mode{position:absolute;top:0;left:0;width:100%;height:100%;z-index:1}.fullscreen-mode .card-container{width:100%;height:100%;position:relative}.fullscreen-mode .card-image{background:transparent}.card-image{height:100%;width:100%;object-fit:cover;object-position:center;position:absolute;top:0;left:0;transition:filter .3s ease}::ng-deep .plyr--full-ui{width:100%;object-fit:cover;object-position:center;position:absolute;top:0;left:0;transition:filter .3s ease}.info-button{position:absolute;top:15px;right:15px;z-index:3}.info-button:hover{transform:scale(1.1)}.info-layer{height:100%;width:100%;position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;z-index:2;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);background-color:var(--chat-user-message-bg);color:#fff;opacity:1;clip-path:circle(0% at top right);transition:clip-path .5s cubic-bezier(.25,1,.5,1);pointer-events:none}.info-layer.active{clip-path:circle(150% at top right);pointer-events:auto}.info-content{padding:15px;text-align:center;max-width:90%}.info-content h1{margin-top:0;font-size:18px;margin-bottom:10px}.info-content p{font-size:12px;margin:0}::ng-deep .info-button .p-button{background-color:#ffffff47}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i2.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i2$6.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Live2dModelComponent, selector: "app-live2d-model", inputs: ["modelPath", "scale", "zoom", "x", "y"], outputs: ["modelLoaded", "viewStateChanged"] }, { kind: "pipe", type: ParseCardPipe, name: "parseCard" }, { kind: "pipe", type: i3$6.TranslatePipe, name: "translate" }] }); }
|
|
9661
10080
|
}
|
|
9662
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type:
|
|
10081
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: DcAgentCardAvatarComponent, decorators: [{
|
|
9663
10082
|
type: Component,
|
|
9664
|
-
args: [{ selector: 'dc-agent-card-
|
|
10083
|
+
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%;position:relative}.transparent-mode .card-container{width:420px;height:700px;position:relative}.transparent-mode .card-image{background:transparent}.fullscreen-mode{position:absolute;top:0;left:0;width:100%;height:100%;z-index:1}.fullscreen-mode .card-container{width:100%;height:100%;position:relative}.fullscreen-mode .card-image{background:transparent}.card-image{height:100%;width:100%;object-fit:cover;object-position:center;position:absolute;top:0;left:0;transition:filter .3s ease}::ng-deep .plyr--full-ui{width:100%;object-fit:cover;object-position:center;position:absolute;top:0;left:0;transition:filter .3s ease}.info-button{position:absolute;top:15px;right:15px;z-index:3}.info-button:hover{transform:scale(1.1)}.info-layer{height:100%;width:100%;position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;z-index:2;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);background-color:var(--chat-user-message-bg);color:#fff;opacity:1;clip-path:circle(0% at top right);transition:clip-path .5s cubic-bezier(.25,1,.5,1);pointer-events:none}.info-layer.active{clip-path:circle(150% at top right);pointer-events:auto}.info-content{padding:15px;text-align:center;max-width:90%}.info-content h1{margin-top:0;font-size:18px;margin-bottom:10px}.info-content p{font-size:12px;margin:0}::ng-deep .info-button .p-button{background-color:#ffffff47}\n"] }]
|
|
9665
10084
|
}], ctorParameters: () => [], propDecorators: { agentCardId: [{
|
|
9666
10085
|
type: Input
|
|
9667
10086
|
}], useNativePlayer: [{
|
|
9668
10087
|
type: Input
|
|
9669
|
-
}], activeAudioMessage: [{
|
|
9670
|
-
type: Input
|
|
9671
10088
|
}], isConversationActive: [{
|
|
9672
10089
|
type: Input
|
|
9673
10090
|
}], 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 }] }] } });
|
|
@@ -10273,7 +10690,7 @@ class ChatEngineTestComponent {
|
|
|
10273
10690
|
};
|
|
10274
10691
|
}
|
|
10275
10692
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatEngineTestComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
10276
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.5", type: ChatEngineTestComponent, isStandalone: true, selector: "dc-chat-engine-test", ngImport: i0, template: "<div class=\"chat-engine-test-container\">\n <dc-chat [conversationSettings]=\"conversationSettings\"></dc-chat>\n</div>\n", styles: [".chat-engine-test-container{width:100%;height:100vh;display:flex;justify-content:center;align-items:center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DCChatComponent, selector: "dc-chat", inputs: ["chatUserSettings", "conversationSettings", "conversationFlow", "agentCard", "observerConfig", "parseDict"], outputs: ["chatEvent", "goalCompleted"] }] }); }
|
|
10693
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.5", type: ChatEngineTestComponent, isStandalone: true, selector: "dc-chat-engine-test", ngImport: i0, template: "<div class=\"chat-engine-test-container\">\n <dc-chat [conversationSettings]=\"conversationSettings\"></dc-chat>\n</div>\n", styles: [".chat-engine-test-container{width:100%;height:100vh;display:flex;justify-content:center;align-items:center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DCChatComponent, selector: "dc-chat", inputs: ["chatUserSettings", "conversationSettings", "conversationFlow", "agentCard", "micSettings", "observerConfig", "parseDict"], outputs: ["chatEvent", "goalCompleted"] }] }); }
|
|
10277
10694
|
}
|
|
10278
10695
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: ChatEngineTestComponent, decorators: [{
|
|
10279
10696
|
type: Component,
|
|
@@ -10289,5 +10706,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
|
|
|
10289
10706
|
* Generated bundle index. Do not edit.
|
|
10290
10707
|
*/
|
|
10291
10708
|
|
|
10292
|
-
export { ACCDataGenerationComponent, AGENT_CARDS_STATE_SERVICE, AIGenerationService, AgentCardListComponent, AgentCardUI, AgentCardsGenerationService, AgenticEngine, AgenticPattern, AudioService, AudioSpeed, BACKGROUND_SERVICE_TOKEN, CHAT_OBSERVER_CONFIG, CONVERSATION_AI_TOKEN, CardRouteService, CardRoutesComponent, CardsCreatorComponent, ChatEngineTestComponent, ChatEventType, ChatMessage, ChatMessageOrchestratorComponent, ChatMonitorService, ChatRole, ConditionOperator, ConditionType, ContextEngineService, ContextType, ConversationDTO, ConversationEvents, ConversationFlowStateService, ConversationMessagesDTO, ConversationPromptBuilderService, ConversationRuleService, ConversationRulesComponent, ConversationStatus, ConversationType, ConversationTypeOptions, DCAgentCardFormComponent, DCChatComponent, DCConversationUserChatSettingsComponent,
|
|
10709
|
+
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, 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, buildMicSettings, buildObjectTTSRequest, characterCardStringDataDefinition, convertToHTML, createAIModelFormGroup, defaultconvUserSettings, extractAudioAndTranscription, extractJsonFromResponse, getMoodStateLabelsAsString, getMoodStatePrompt, markdownToHtml, matchTranscription, provideAgentCardService, provideLive2D, removeEmojis, removeEmojisAndSpecialCharacters, removeSpecialCharacters };
|
|
10293
10710
|
//# sourceMappingURL=dataclouder-ngx-agent-cards.mjs.map
|