@dataclouder/ngx-lessons 0.1.14 → 0.1.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,60 +1,61 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Pipe, InjectionToken, inject, Input, Component, ChangeDetectionStrategy, signal, EventEmitter, Output, Injectable, effect, ViewChildren, input, viewChild, Renderer2, ViewContainerRef, computed, ChangeDetectorRef, output } from '@angular/core';
3
- import { DatePipe, NgComponentOutlet, KeyValuePipe, CommonModule, SlicePipe } from '@angular/common';
4
- import * as i1$4 from '@angular/router';
2
+ import { Pipe, InjectionToken, inject, Input, Component, ChangeDetectionStrategy, signal, EventEmitter, Output, Injectable, ViewContainerRef, ViewChild, input, viewChild, Renderer2, computed, effect, ChangeDetectorRef, output } from '@angular/core';
3
+ import * as i1$6 from '@angular/router';
5
4
  import { RouterModule, ActivatedRoute, RouterOutlet, RouterLink } from '@angular/router';
6
- import { EntityCommunicationService, EntityBaseListComponent, DCFilterBarComponent, QuickTableComponent, TOAST_ALERTS_TOKEN, EModelQuality, UiStateService, MobileService, LoadingBarService, FormUtilsService, EntityBaseFormComponent, PromptService, DcManageableFormComponent, DcLearnableFormComponent, HttpCoreService, PaginationBase, LangDescTranslation, getSupportedLanguageOptions } from '@dataclouder/ngx-core';
5
+ import { EntityCommunicationService, EntityBaseListComponent, DCFilterBarComponent, QuickTableComponent, TOAST_ALERTS_TOKEN, LoadingBarService, FormUtilsService, EModelQuality, UiStateService, MobileService, EntityBaseFormComponent, PromptService, DcManageableFormComponent, DcLearnableFormComponent, HttpCoreService, PaginationBase, LangDescTranslation, getSupportedLanguageOptions } from '@dataclouder/ngx-core';
7
6
  import * as i1 from '@angular/forms';
8
7
  import { FormBuilder, FormControl, FormArray, FormsModule, ReactiveFormsModule, UntypedFormControl, FormGroup, Validators } from '@angular/forms';
9
8
  import * as i1$1 from 'primeng/button';
10
9
  import { ButtonModule } from 'primeng/button';
11
10
  import * as i3 from 'primeng/inputtext';
12
11
  import { InputTextModule } from 'primeng/inputtext';
13
- import { DynamicDialogRef, DialogService } from 'primeng/dynamicdialog';
12
+ import * as i1$4 from 'primeng/dynamicdialog';
13
+ import { DynamicDialogRef, DialogService, DynamicDialogConfig } from 'primeng/dynamicdialog';
14
14
  import { nanoid } from 'nanoid';
15
15
  import * as i2 from 'primeng/select';
16
16
  import { SelectModule } from 'primeng/select';
17
- import { TtsPlaygroundComponent, getRandomQuickVoice, NgxVertexService, ChatRoleVertex } from '@dataclouder/ngx-vertex';
17
+ import { TtsPlaygroundComponent, getRandomQuickVoice, NgxAiServicesService, ChatRoleVertex } from '@dataclouder/ngx-ai-services';
18
+ import * as i1$2 from 'primeng/card';
19
+ import { CardModule } from 'primeng/card';
18
20
  import * as i4 from 'primeng/message';
19
21
  import { MessageModule } from 'primeng/message';
22
+ import { DatePipe, CommonModule, KeyValuePipe, SlicePipe } from '@angular/common';
20
23
  import { PopoverModule } from 'primeng/popover';
21
24
  import * as i4$1 from 'primeng/tag';
22
25
  import { TagModule } from 'primeng/tag';
23
26
  import * as i2$1 from 'primeng/speeddial';
24
27
  import { SpeedDialModule } from 'primeng/speeddial';
25
- import * as i1$2 from 'primeng/card';
26
- import { CardModule } from 'primeng/card';
27
28
  import { UserService } from '@dataclouder/ngx-users';
28
29
  import * as i1$3 from 'primeng/paginator';
29
30
  import { PaginatorModule } from 'primeng/paginator';
30
- import BalloonEditor from '@ckeditor/ckeditor5-build-balloon-block';
31
- import * as i3$1 from '@ckeditor/ckeditor5-angular';
32
- import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
33
- import * as i5$1 from 'primeng/selectbutton';
31
+ import * as i3$2 from 'primeng/selectbutton';
34
32
  import { SelectButtonModule } from 'primeng/selectbutton';
35
- import * as i6$1 from 'primeng/splitter';
36
- import { SplitterModule } from 'primeng/splitter';
37
- import * as i7 from 'primeng/tooltip';
33
+ import * as i4$2 from 'primeng/tooltip';
38
34
  import { TooltipModule } from 'primeng/tooltip';
39
35
  import { ResolutionType, AspectType, AssetsLoaderComponent, CropperComponentModal } from '@dataclouder/ngx-cloud-storage';
40
- import { EvalResultStringDefinition, SystemPromptType, EDoActionType, ConditionOperator, ConditionType, ConversationEvents, ChatRole, TextEngines, ConversationType, ChatEventType, DCChatComponent, CONVERSATION_AI_TOKEN } from '@dataclouder/ngx-agent-cards';
41
- import * as i2$2 from 'primeng/drawer';
42
- import { DrawerModule } from 'primeng/drawer';
43
- import { MarkdownComponent, MarkdownService } from 'ngx-markdown';
44
36
  import { DialogModule } from 'primeng/dialog';
37
+ import { CONVERSATION_AI_TOKEN, ChatRole, EvalResultStringDefinition, SystemPromptType, EDoActionType, ConditionOperator, ConditionType, ConversationEvents, TextEngines, ConversationType, ChatEventType, DCChatComponent } from '@dataclouder/ngx-agent-cards';
45
38
  import * as i5 from 'primeng/inputgroup';
46
39
  import { InputGroupModule } from 'primeng/inputgroup';
47
40
  import * as i6 from 'primeng/divider';
48
41
  import { DividerModule } from 'primeng/divider';
49
- import * as i2$3 from 'primeng/api';
42
+ import BalloonEditor from '@ckeditor/ckeditor5-build-balloon-block';
43
+ import * as i1$5 from '@ckeditor/ckeditor5-angular';
44
+ import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
45
+ import * as i3$1 from 'primeng/splitter';
46
+ import { SplitterModule } from 'primeng/splitter';
47
+ import * as i2$2 from 'primeng/drawer';
48
+ import { DrawerModule } from 'primeng/drawer';
49
+ import { MarkdownComponent, MarkdownService } from 'ngx-markdown';
50
+ import * as i4$3 from 'primeng/api';
50
51
  import { MessageService } from 'primeng/api';
51
52
  import { TableModule } from 'primeng/table';
52
- import * as i3$2 from 'primeng/panel';
53
+ import * as i3$3 from 'primeng/panel';
53
54
  import { PanelModule } from 'primeng/panel';
54
- import * as i5$2 from 'primeng/progressspinner';
55
+ import * as i5$1 from 'primeng/progressspinner';
55
56
  import { ProgressSpinnerModule } from 'primeng/progressspinner';
56
57
  import { ToastModule } from 'primeng/toast';
57
- import * as i3$3 from 'primeng/textarea';
58
+ import * as i3$4 from 'primeng/textarea';
58
59
  import { TextareaModule } from 'primeng/textarea';
59
60
  import { ChipModule } from 'primeng/chip';
60
61
 
@@ -433,7 +434,7 @@ class SpeakerBuilderComponent extends ComponentBuilder {
433
434
  return code;
434
435
  }
435
436
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: SpeakerBuilderComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
436
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.6", type: SpeakerBuilderComponent, isStandalone: true, selector: "app-speaker-builder", usesInheritance: true, ngImport: i0, template: "<div>\r\n <div>\r\n <h5>Constructor de Speaker</h5>\r\n </div>\r\n\r\n <dc-tts-playground (ttsGenerated)=\"handleTtsGenerated($event)\"></dc-tts-playground>\r\n\r\n <br />\r\n <div>\r\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\" [rounded]=\"true\"></p-button>\r\n <p-button (click)=\"showCode()\" [disabled]=\"!tts()\" label=\"Mostrar\" [rounded]=\"true\" severity=\"secondary\"></p-button>\r\n </div>\r\n</div>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: InputTextModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: TtsPlaygroundComponent, selector: "dc-tts-playground", inputs: ["settings"], outputs: ["ttsGenerated"] }] }); }
437
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.6", type: SpeakerBuilderComponent, isStandalone: true, selector: "app-speaker-builder", usesInheritance: true, ngImport: i0, template: "<div>\r\n <div>\r\n <h5>Constructor de Speaker</h5>\r\n </div>\r\n\r\n <dc-tts-playground (ttsGenerated)=\"handleTtsGenerated($event)\"></dc-tts-playground>\r\n\r\n <br />\r\n <div>\r\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\" [rounded]=\"true\"></p-button>\r\n <p-button (click)=\"showCode()\" [disabled]=\"!tts()\" label=\"Mostrar\" [rounded]=\"true\" severity=\"secondary\"></p-button>\r\n </div>\r\n</div>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: InputTextModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: TtsPlaygroundComponent, selector: "dc-tts-playground", inputs: ["settings", "emitValues"], outputs: ["ttsGenerated", "formValues"] }] }); }
437
438
  }
438
439
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: SpeakerBuilderComponent, decorators: [{
439
440
  type: Component,
@@ -452,11 +453,11 @@ class SpeakerComponent {
452
453
  console.log('should speech but will do in next version');
453
454
  }
454
455
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: SpeakerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
455
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.6", type: SpeakerComponent, isStandalone: true, selector: "app-speaker", inputs: { config: "config", tts: "tts" }, ngImport: i0, template: "<button\r\n pButton\r\n style=\"padding: 0px 2px\"\r\n severity=\"help\"\r\n size=\"small\"\r\n (click)=\"speach()\"\r\n [label]=\"config?.settings?.text\"\r\n [text]=\"true\"\r\n [rounded]=\"true\"></button>\r\n", styles: [".lisen{cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i1$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }] }); }
456
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.6", type: SpeakerComponent, isStandalone: true, selector: "app-speaker", inputs: { config: "config", tts: "tts" }, ngImport: i0, template: "<h4>Speacker component</h4>\r\n\r\n<!-- <p-button severity=\"help\" [label]=\"config?.settings?.text\" (click)=\"speach()\" /> -->\r\n\r\n<button pButton style=\"padding: 0px 2px\" severity=\"help\" size=\"small\" (click)=\"speach()\" [label]=\"config?.settings?.text\" [rounded]=\"true\"></button>\r\n", styles: [".lisen{cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i1$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }] }); }
456
457
  }
457
458
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: SpeakerComponent, decorators: [{
458
459
  type: Component,
459
- args: [{ selector: 'app-speaker', standalone: true, imports: [ButtonModule], template: "<button\r\n pButton\r\n style=\"padding: 0px 2px\"\r\n severity=\"help\"\r\n size=\"small\"\r\n (click)=\"speach()\"\r\n [label]=\"config?.settings?.text\"\r\n [text]=\"true\"\r\n [rounded]=\"true\"></button>\r\n", styles: [".lisen{cursor:pointer}\n"] }]
460
+ args: [{ selector: 'app-speaker', standalone: true, imports: [ButtonModule], template: "<h4>Speacker component</h4>\r\n\r\n<!-- <p-button severity=\"help\" [label]=\"config?.settings?.text\" (click)=\"speach()\" /> -->\r\n\r\n<button pButton style=\"padding: 0px 2px\" severity=\"help\" size=\"small\" (click)=\"speach()\" [label]=\"config?.settings?.text\" [rounded]=\"true\"></button>\r\n", styles: [".lisen{cursor:pointer}\n"] }]
460
461
  }], propDecorators: { config: [{
461
462
  type: Input
462
463
  }], tts: [{
@@ -518,12 +519,6 @@ class SelectorBuilderComponent extends ComponentBuilder {
518
519
  },
519
520
  };
520
521
  }
521
- // public formGroup = this.formBuilder.group({
522
- // options: this.formBuilder.array([]),
523
- // response: ['', Validators.required],
524
- // hint: [],
525
- // explanation: [],
526
- // });
527
522
  ngOnInit() {
528
523
  this.formGroup.get('response');
529
524
  }
@@ -540,11 +535,11 @@ class SelectorBuilderComponent extends ComponentBuilder {
540
535
  return this.formGroup.get('options');
541
536
  }
542
537
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: SelectorBuilderComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
543
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: SelectorBuilderComponent, isStandalone: true, selector: "app-selector-builder", usesInheritance: true, ngImport: i0, template: "<div>\n <div>\n <p-message>Construcci\u00F3n del componente de Selecci\u00F3n, sirve para hacer una pregunta y mostrar varias opciones, ejemplo:</p-message>\n </div>\n\n <div>\n <span>En que a\u00F1o lleg\u00F3 cristobal colon a america?</span>\n <app-selector [config]=\"sampleConfig\"></app-selector>\n </div>\n\n <hr />\n\n <div>\n <form class=\"builder-form\" [formGroup]=\"formGroup\">\n <input class=\"form-input\" type=\"text\" pInputText fullWidth formControlName=\"response\" placeholder=\"Respuesta Correcta...\" />\n <br />\n\n <input class=\"form-input\" type=\"\" pInputText fullWidth formControlName=\"hint\" placeholder=\"Escribe una pista para esta pregunta\" />\n\n <br />\n <input\n class=\"form-input\"\n type=\"text\"\n pInputText\n fullWidth\n formControlName=\"explanation\"\n placeholder=\"Excribe una explicaci\u00F3n para la respuesta\" />\n\n <hr />\n <h6>Opciones</h6>\n\n <div class=\"form-group\" formArrayName=\"options\">\n @for (item of optionsForm.controls; track item; let i = $index) {\n <div\n style=\"display: flex; gap: 10px; align-items: center; justify-content: space-between; margin-bottom: 10px; flex-direction: column\"\n >\n <div>\n <input type=\"text\" pInputText fullWidth [formControlName]=\"i\" />\n <p-button (click)=\"deleteFormArrayByIndex('options', i)\" icon=\"pi pi-times\" severity=\"danger\"></p-button>\n </div>\n </div>\n }\n </div>\n\n <p-button (click)=\"pushControlToFormArray('options')\" label=\"Agregar Opci\u00F3n\" [text]=\"true\" severity=\"help\"></p-button>\n </form>\n\n <!-- <button nbButton (click)=\"isRendered = !isRendered\"> Renderizar </button> -->\n\n @if (isRendered) {\n <div>\n <!-- TODO: probably i need to pass some params -->\n <app-selector></app-selector>\n </div>\n }\n </div>\n\n <div>\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\" [rounded]=\"true\"></p-button>\n <p-button (click)=\"showCode()\" [disabled]=\"formGroup.invalid\" label=\"Mostrar\" [rounded]=\"true\" severity=\"secondary\"></p-button>\n </div>\n </div>\n", styles: ["nb-card{width:60vw}.builder-form{padding:5px}.form-input{margin-top:10px}.mar-top{margin:5px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: SelectorComponent, selector: "app-selector", inputs: ["config"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i3.InputText, selector: "[pInputText]", inputs: ["pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: MessageModule }, { kind: "component", type: i4.Message, selector: "p-message", inputs: ["severity", "text", "escape", "style", "styleClass", "closable", "icon", "closeIcon", "life", "showTransitionOptions", "hideTransitionOptions", "size", "variant"], outputs: ["onClose"] }] }); }
538
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: SelectorBuilderComponent, isStandalone: true, selector: "app-selector-builder", usesInheritance: true, ngImport: i0, template: "<div class=\"p-fluid grid\">\n <div class=\"col-12 md:col-6\">\n <p-card header=\"Ejemplo de componente\">\n <p-message>Construcci\u00F3n del componente de Selecci\u00F3n, sirve para hacer una pregunta y mostrar varias opciones, ejemplo:</p-message>\n <div class=\"mt-4\">\n <p>En que a\u00F1o lleg\u00F3 cristobal colon a america?</p>\n <app-selector [config]=\"sampleConfig\"></app-selector>\n </div>\n </p-card>\n </div>\n\n <hr />\n\n <div class=\"col-12 md:col-6\">\n <form class=\"builder-form\" [formGroup]=\"formGroup\">\n <div class=\"field\">\n <label for=\"response\">Respuesta Correcta</label>\n <input id=\"response\" class=\"form-input\" type=\"text\" pInputText fullWidth formControlName=\"response\" placeholder=\"Respuesta Correcta...\" />\n </div>\n\n <div class=\"field\">\n <label for=\"hint\">Pista</label>\n <input id=\"hint\" class=\"form-input\" type=\"text\" pInputText fullWidth formControlName=\"hint\" placeholder=\"Escribe una pista para esta pregunta\" />\n </div>\n\n <div class=\"field\">\n <label for=\"explanation\">Explicaci\u00F3n</label>\n <input\n id=\"explanation\"\n class=\"form-input\"\n type=\"text\"\n pInputText\n fullWidth\n formControlName=\"explanation\"\n placeholder=\"Escribe una explicaci\u00F3n para la respuesta\" />\n </div>\n\n <hr />\n <h6>Opciones</h6>\n\n <div class=\"form-group\" formArrayName=\"options\">\n @for (item of optionsForm.controls; track item; let i = $index) {\n <div class=\"field grid align-items-center\">\n <div class=\"col\">\n <input type=\"text\" pInputText fullWidth [formControlName]=\"i\" />\n </div>\n <div class=\"col-fixed\" style=\"width: auto\">\n <p-button (click)=\"deleteFormArrayByIndex('options', i)\" icon=\"pi pi-times\" severity=\"danger\"></p-button>\n </div>\n </div>\n }\n </div>\n\n <p-button (click)=\"pushControlToFormArray('options')\" label=\"Agregar Opci\u00F3n\" [text]=\"true\" severity=\"help\"></p-button>\n </form>\n\n @if (isRendered) {\n <div class=\"mt-4\">\n <app-selector></app-selector>\n </div>\n }\n\n <div class=\"mt-4 flex justify-content-end gap-2\">\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\" [rounded]=\"true\"></p-button>\n <p-button (click)=\"showCode()\" [disabled]=\"formGroup.invalid\" label=\"Mostrar\" [rounded]=\"true\" severity=\"secondary\"></p-button>\n </div>\n </div>\n</div>\n", styles: ["nb-card{width:60vw}.builder-form{padding:5px}.form-input{margin-top:10px}.mar-top{margin:5px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: SelectorComponent, selector: "app-selector", inputs: ["config"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i3.InputText, selector: "[pInputText]", inputs: ["pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: MessageModule }, { kind: "component", type: i4.Message, selector: "p-message", inputs: ["severity", "text", "escape", "style", "styleClass", "closable", "icon", "closeIcon", "life", "showTransitionOptions", "hideTransitionOptions", "size", "variant"], outputs: ["onClose"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i1$2.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }] }); }
544
539
  }
545
540
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: SelectorBuilderComponent, decorators: [{
546
541
  type: Component,
547
- args: [{ selector: 'app-selector-builder', standalone: true, imports: [FormsModule, ReactiveFormsModule, SelectorComponent, InputTextModule, ButtonModule, MessageModule], template: "<div>\n <div>\n <p-message>Construcci\u00F3n del componente de Selecci\u00F3n, sirve para hacer una pregunta y mostrar varias opciones, ejemplo:</p-message>\n </div>\n\n <div>\n <span>En que a\u00F1o lleg\u00F3 cristobal colon a america?</span>\n <app-selector [config]=\"sampleConfig\"></app-selector>\n </div>\n\n <hr />\n\n <div>\n <form class=\"builder-form\" [formGroup]=\"formGroup\">\n <input class=\"form-input\" type=\"text\" pInputText fullWidth formControlName=\"response\" placeholder=\"Respuesta Correcta...\" />\n <br />\n\n <input class=\"form-input\" type=\"\" pInputText fullWidth formControlName=\"hint\" placeholder=\"Escribe una pista para esta pregunta\" />\n\n <br />\n <input\n class=\"form-input\"\n type=\"text\"\n pInputText\n fullWidth\n formControlName=\"explanation\"\n placeholder=\"Excribe una explicaci\u00F3n para la respuesta\" />\n\n <hr />\n <h6>Opciones</h6>\n\n <div class=\"form-group\" formArrayName=\"options\">\n @for (item of optionsForm.controls; track item; let i = $index) {\n <div\n style=\"display: flex; gap: 10px; align-items: center; justify-content: space-between; margin-bottom: 10px; flex-direction: column\"\n >\n <div>\n <input type=\"text\" pInputText fullWidth [formControlName]=\"i\" />\n <p-button (click)=\"deleteFormArrayByIndex('options', i)\" icon=\"pi pi-times\" severity=\"danger\"></p-button>\n </div>\n </div>\n }\n </div>\n\n <p-button (click)=\"pushControlToFormArray('options')\" label=\"Agregar Opci\u00F3n\" [text]=\"true\" severity=\"help\"></p-button>\n </form>\n\n <!-- <button nbButton (click)=\"isRendered = !isRendered\"> Renderizar </button> -->\n\n @if (isRendered) {\n <div>\n <!-- TODO: probably i need to pass some params -->\n <app-selector></app-selector>\n </div>\n }\n </div>\n\n <div>\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\" [rounded]=\"true\"></p-button>\n <p-button (click)=\"showCode()\" [disabled]=\"formGroup.invalid\" label=\"Mostrar\" [rounded]=\"true\" severity=\"secondary\"></p-button>\n </div>\n </div>\n", styles: ["nb-card{width:60vw}.builder-form{padding:5px}.form-input{margin-top:10px}.mar-top{margin:5px}\n"] }]
542
+ args: [{ selector: 'app-selector-builder', standalone: true, imports: [FormsModule, ReactiveFormsModule, SelectorComponent, InputTextModule, ButtonModule, MessageModule, CardModule], template: "<div class=\"p-fluid grid\">\n <div class=\"col-12 md:col-6\">\n <p-card header=\"Ejemplo de componente\">\n <p-message>Construcci\u00F3n del componente de Selecci\u00F3n, sirve para hacer una pregunta y mostrar varias opciones, ejemplo:</p-message>\n <div class=\"mt-4\">\n <p>En que a\u00F1o lleg\u00F3 cristobal colon a america?</p>\n <app-selector [config]=\"sampleConfig\"></app-selector>\n </div>\n </p-card>\n </div>\n\n <hr />\n\n <div class=\"col-12 md:col-6\">\n <form class=\"builder-form\" [formGroup]=\"formGroup\">\n <div class=\"field\">\n <label for=\"response\">Respuesta Correcta</label>\n <input id=\"response\" class=\"form-input\" type=\"text\" pInputText fullWidth formControlName=\"response\" placeholder=\"Respuesta Correcta...\" />\n </div>\n\n <div class=\"field\">\n <label for=\"hint\">Pista</label>\n <input id=\"hint\" class=\"form-input\" type=\"text\" pInputText fullWidth formControlName=\"hint\" placeholder=\"Escribe una pista para esta pregunta\" />\n </div>\n\n <div class=\"field\">\n <label for=\"explanation\">Explicaci\u00F3n</label>\n <input\n id=\"explanation\"\n class=\"form-input\"\n type=\"text\"\n pInputText\n fullWidth\n formControlName=\"explanation\"\n placeholder=\"Escribe una explicaci\u00F3n para la respuesta\" />\n </div>\n\n <hr />\n <h6>Opciones</h6>\n\n <div class=\"form-group\" formArrayName=\"options\">\n @for (item of optionsForm.controls; track item; let i = $index) {\n <div class=\"field grid align-items-center\">\n <div class=\"col\">\n <input type=\"text\" pInputText fullWidth [formControlName]=\"i\" />\n </div>\n <div class=\"col-fixed\" style=\"width: auto\">\n <p-button (click)=\"deleteFormArrayByIndex('options', i)\" icon=\"pi pi-times\" severity=\"danger\"></p-button>\n </div>\n </div>\n }\n </div>\n\n <p-button (click)=\"pushControlToFormArray('options')\" label=\"Agregar Opci\u00F3n\" [text]=\"true\" severity=\"help\"></p-button>\n </form>\n\n @if (isRendered) {\n <div class=\"mt-4\">\n <app-selector></app-selector>\n </div>\n }\n\n <div class=\"mt-4 flex justify-content-end gap-2\">\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\" [rounded]=\"true\"></p-button>\n <p-button (click)=\"showCode()\" [disabled]=\"formGroup.invalid\" label=\"Mostrar\" [rounded]=\"true\" severity=\"secondary\"></p-button>\n </div>\n </div>\n</div>\n", styles: ["nb-card{width:60vw}.builder-form{padding:5px}.form-input{margin-top:10px}.mar-top{margin:5px}\n"] }]
548
543
  }] });
549
544
 
550
545
  var LessonComponentEnum;
@@ -631,11 +626,11 @@ class DcLessonCardComponent {
631
626
  this.onAction.emit({ action: eventType, item: this.lesson });
632
627
  }
633
628
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DcLessonCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
634
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: DcLessonCardComponent, isStandalone: true, selector: "dc-lesson-card", inputs: { lesson: "lesson", showOptions: "showOptions", cardHeight: "cardHeight" }, outputs: { onAction: "onAction" }, ngImport: i0, template: "<div class=\"card-container\">\n @if(showOptions){\n <p-speeddial\n class=\"dial-button\"\n [model]=\"items\"\n [radius]=\"70\"\n type=\"quarter-circle\"\n direction=\"down-left\"\n [buttonProps]=\"{ severity: 'primary', rounded: true, outlined: true, raised: true }\"\n [tooltipOptions]=\"{ tooltipPosition: 'top' }\" />\n }\n <p-card>\n <div class=\"lesson-card\" [style.height]=\"cardHeight\">\n <div class=\"photo\">\n <img [src]=\"coverUrl\" alt=\"\" />\n </div>\n\n <span class=\"date\">{{ lesson.createdAt | date : 'dd/MM/yyyy' }}</span>\n\n <div class=\"description\">\n <h1>{{ lesson?.name || 'No title available' }}</h1>\n <p>{{ lesson.description || 'No description available' }}</p>\n <div class=\"card-footer\">\n <div class=\"status-tags\">\n @if (lesson.learnable?.level){\n <p-tag>Nivel {{ lesson.learnable?.level }}</p-tag>\n } @if (lesson.taken?.status == 'passed') {\n <p-tag severity=\"success\" value=\"Tomada\" [rounded]=\"true\" />\n } @if (lesson.taken?.status == 'failed') {\n <p-tag severity=\"danger\" value=\"Fallida\" [rounded]=\"true\" />\n } @if (lesson.taken) {\n <p-tag severity=\"success\" value=\"Tomada \uD83D\uDCD6\" [rounded]=\"true\" />\n } @if ( userService.isAdmin()) {\n <p-tag severity=\"contrast\" [value]=\"lesson.manageable?.isPublic ? 'P\u00FAblica' : 'No p\u00FAblica'\" [rounded]=\"true\" />\n <p-tag severity=\"contrast\" [value]=\"lesson.manageable?.status\" [rounded]=\"true\" />\n }\n </div>\n\n <div style=\"position: absolute; bottom: 0px; right: 0px\">\n <p-button label=\"Tomar lecci\u00F3n\" (onClick)=\"eventCard(eventType.Select)\" severity=\"primary\"> </p-button>\n </div>\n </div>\n </div>\n </div>\n </p-card>\n</div>\n", styles: [".card-container{position:relative;margin-bottom:20px;margin-left:10px;min-width:400px}.dial-button{position:absolute;top:10px;right:20px;z-index:10}.lesson-card{border-radius:.5rem;height:100%}.lesson-card .photo{position:absolute;inset:0;z-index:1}.lesson-card .photo img{width:100%;height:100%;object-fit:cover;object-position:center}.lesson-card .photo:after{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;background:linear-gradient(to bottom,rgb(0,0,0) 50%,var(--p-primary-color) 100%);opacity:.4;z-index:2;pointer-events:none}.description{position:relative;z-index:2;color:#fff;height:100%;display:flex;flex-direction:column}.description h1{margin-top:0;margin-bottom:.5rem;font-size:1.5rem;font-weight:900;text-shadow:1px 1px 2px rgba(0,0,0,.7)}.description p{margin-bottom:1rem;flex-grow:1;font-weight:500;text-shadow:1px 1px 2px rgba(0,0,0,.9)}.date{position:absolute;top:1rem;left:1rem;z-index:3;background-color:#000000b3;color:#fff;padding:.3rem .6rem;border-radius:.25rem;font-size:.8rem}.card-footer{display:flex;justify-content:space-between;align-items:center;margin-top:auto}.status-tags{display:flex;gap:.5rem}.status-tags .level-tag,.status-tags .status-tag{padding:.3rem .6rem;border-radius:.25rem;font-size:.8rem}.status-tags .level-tag{background-color:#fff3}.status-tags .status-tag.success{background-color:#28a745b3}.status-tags .status-tag.danger{background-color:#dc3545b3}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "ngmodule", type: SpeedDialModule }, { kind: "component", type: i2$1.SpeedDial, selector: "p-speeddial, p-speedDial, p-speed-dial", inputs: ["id", "model", "visible", "style", "className", "direction", "transitionDelay", "type", "radius", "mask", "disabled", "hideOnClickOutside", "buttonStyle", "buttonClassName", "maskStyle", "maskClassName", "showIcon", "hideIcon", "rotateAnimation", "ariaLabel", "ariaLabelledBy", "tooltipOptions", "buttonProps"], outputs: ["onVisibleChange", "visibleChange", "onClick", "onShow", "onHide"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i1$2.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i4$1.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "pipe", type: DatePipe, name: "date" }] }); }
629
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: DcLessonCardComponent, isStandalone: true, selector: "dc-lesson-card", inputs: { lesson: "lesson", showOptions: "showOptions", cardHeight: "cardHeight" }, outputs: { onAction: "onAction" }, ngImport: i0, template: "<div class=\"card-container\">\n @if(this.userService.isAdmin()){\n <p-speeddial\n class=\"dial-button\"\n [model]=\"items\"\n [radius]=\"70\"\n type=\"quarter-circle\"\n direction=\"down-left\"\n [buttonProps]=\"{ severity: 'primary', rounded: true, outlined: true, raised: true }\"\n [tooltipOptions]=\"{ tooltipPosition: 'top' }\" />\n }\n <p-card>\n <div class=\"lesson-card\" [style.height]=\"cardHeight\">\n <div class=\"photo\">\n <img [src]=\"coverUrl\" alt=\"\" />\n </div>\n\n <span class=\"date\">{{ lesson.createdAt | date : 'dd/MM/yyyy' }}</span>\n\n <div class=\"description\">\n <h1>{{ lesson?.name || 'No title available' }}</h1>\n <p>{{ lesson.description || 'No description available' }}</p>\n <div class=\"card-footer\">\n <div class=\"status-tags\">\n @if (lesson.learnable?.level){\n <p-tag>Nivel {{ lesson.learnable?.level }}</p-tag>\n } @if (lesson.taken?.status == 'passed') {\n <p-tag severity=\"success\" value=\"Tomada\" [rounded]=\"true\" />\n } @if (lesson.taken?.status == 'failed') {\n <p-tag severity=\"danger\" value=\"Fallida\" [rounded]=\"true\" />\n } @if (lesson.taken) {\n <p-tag severity=\"success\" value=\"Tomada \uD83D\uDCD6\" [rounded]=\"true\" />\n } @if ( userService.isAdmin()) {\n <p-tag severity=\"contrast\" [value]=\"lesson.manageable?.isPublic ? 'P\u00FAblica' : 'No p\u00FAblica'\" [rounded]=\"true\" />\n <p-tag severity=\"contrast\" [value]=\"lesson.manageable?.status || 'No status'\" [rounded]=\"true\" />\n }\n </div>\n\n <div style=\"position: absolute; bottom: 0px; right: 0px\">\n <p-button label=\"Tomar lecci\u00F3n\" (onClick)=\"eventCard(eventType.Select)\" severity=\"primary\"> </p-button>\n </div>\n </div>\n </div>\n </div>\n </p-card>\n</div>\n", styles: [".card-container{position:relative;margin-bottom:20px;margin-left:10px;min-width:400px}.dial-button{position:absolute;top:10px;right:20px;z-index:10}.lesson-card{border-radius:.5rem;height:100%}.lesson-card .photo{position:absolute;inset:0;z-index:1}.lesson-card .photo img{width:100%;height:100%;object-fit:cover;object-position:center}.lesson-card .photo:after{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;background:linear-gradient(to bottom,rgb(0,0,0) 50%,var(--p-primary-color) 100%);opacity:.4;z-index:2;pointer-events:none}.description{position:relative;z-index:2;color:#fff;height:100%;display:flex;flex-direction:column}.description h1{margin-top:0;margin-bottom:.5rem;font-size:1.5rem;font-weight:900;text-shadow:1px 1px 2px rgba(0,0,0,.7)}.description p{margin-bottom:1rem;flex-grow:1;font-weight:500;text-shadow:1px 1px 2px rgba(0,0,0,.9)}.date{position:absolute;top:1rem;left:1rem;z-index:3;background-color:#000000b3;color:#fff;padding:.3rem .6rem;border-radius:.25rem;font-size:.8rem}.card-footer{display:flex;justify-content:space-between;align-items:center;margin-top:auto}.status-tags{display:flex;gap:.5rem}.status-tags .level-tag,.status-tags .status-tag{padding:.3rem .6rem;border-radius:.25rem;font-size:.8rem}.status-tags .level-tag{background-color:#fff3}.status-tags .status-tag.success{background-color:#28a745b3}.status-tags .status-tag.danger{background-color:#dc3545b3}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "ngmodule", type: SpeedDialModule }, { kind: "component", type: i2$1.SpeedDial, selector: "p-speeddial, p-speedDial, p-speed-dial", inputs: ["id", "model", "visible", "style", "className", "direction", "transitionDelay", "type", "radius", "mask", "disabled", "hideOnClickOutside", "buttonStyle", "buttonClassName", "maskStyle", "maskClassName", "showIcon", "hideIcon", "rotateAnimation", "ariaLabel", "ariaLabelledBy", "tooltipOptions", "buttonProps"], outputs: ["onVisibleChange", "visibleChange", "onClick", "onShow", "onHide"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i1$2.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i4$1.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "pipe", type: DatePipe, name: "date" }] }); }
635
630
  }
636
631
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DcLessonCardComponent, decorators: [{
637
632
  type: Component,
638
- args: [{ selector: 'dc-lesson-card', standalone: true, imports: [DatePipe, ButtonModule, PopoverModule, SpeedDialModule, CardModule, TagModule], template: "<div class=\"card-container\">\n @if(showOptions){\n <p-speeddial\n class=\"dial-button\"\n [model]=\"items\"\n [radius]=\"70\"\n type=\"quarter-circle\"\n direction=\"down-left\"\n [buttonProps]=\"{ severity: 'primary', rounded: true, outlined: true, raised: true }\"\n [tooltipOptions]=\"{ tooltipPosition: 'top' }\" />\n }\n <p-card>\n <div class=\"lesson-card\" [style.height]=\"cardHeight\">\n <div class=\"photo\">\n <img [src]=\"coverUrl\" alt=\"\" />\n </div>\n\n <span class=\"date\">{{ lesson.createdAt | date : 'dd/MM/yyyy' }}</span>\n\n <div class=\"description\">\n <h1>{{ lesson?.name || 'No title available' }}</h1>\n <p>{{ lesson.description || 'No description available' }}</p>\n <div class=\"card-footer\">\n <div class=\"status-tags\">\n @if (lesson.learnable?.level){\n <p-tag>Nivel {{ lesson.learnable?.level }}</p-tag>\n } @if (lesson.taken?.status == 'passed') {\n <p-tag severity=\"success\" value=\"Tomada\" [rounded]=\"true\" />\n } @if (lesson.taken?.status == 'failed') {\n <p-tag severity=\"danger\" value=\"Fallida\" [rounded]=\"true\" />\n } @if (lesson.taken) {\n <p-tag severity=\"success\" value=\"Tomada \uD83D\uDCD6\" [rounded]=\"true\" />\n } @if ( userService.isAdmin()) {\n <p-tag severity=\"contrast\" [value]=\"lesson.manageable?.isPublic ? 'P\u00FAblica' : 'No p\u00FAblica'\" [rounded]=\"true\" />\n <p-tag severity=\"contrast\" [value]=\"lesson.manageable?.status\" [rounded]=\"true\" />\n }\n </div>\n\n <div style=\"position: absolute; bottom: 0px; right: 0px\">\n <p-button label=\"Tomar lecci\u00F3n\" (onClick)=\"eventCard(eventType.Select)\" severity=\"primary\"> </p-button>\n </div>\n </div>\n </div>\n </div>\n </p-card>\n</div>\n", styles: [".card-container{position:relative;margin-bottom:20px;margin-left:10px;min-width:400px}.dial-button{position:absolute;top:10px;right:20px;z-index:10}.lesson-card{border-radius:.5rem;height:100%}.lesson-card .photo{position:absolute;inset:0;z-index:1}.lesson-card .photo img{width:100%;height:100%;object-fit:cover;object-position:center}.lesson-card .photo:after{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;background:linear-gradient(to bottom,rgb(0,0,0) 50%,var(--p-primary-color) 100%);opacity:.4;z-index:2;pointer-events:none}.description{position:relative;z-index:2;color:#fff;height:100%;display:flex;flex-direction:column}.description h1{margin-top:0;margin-bottom:.5rem;font-size:1.5rem;font-weight:900;text-shadow:1px 1px 2px rgba(0,0,0,.7)}.description p{margin-bottom:1rem;flex-grow:1;font-weight:500;text-shadow:1px 1px 2px rgba(0,0,0,.9)}.date{position:absolute;top:1rem;left:1rem;z-index:3;background-color:#000000b3;color:#fff;padding:.3rem .6rem;border-radius:.25rem;font-size:.8rem}.card-footer{display:flex;justify-content:space-between;align-items:center;margin-top:auto}.status-tags{display:flex;gap:.5rem}.status-tags .level-tag,.status-tags .status-tag{padding:.3rem .6rem;border-radius:.25rem;font-size:.8rem}.status-tags .level-tag{background-color:#fff3}.status-tags .status-tag.success{background-color:#28a745b3}.status-tags .status-tag.danger{background-color:#dc3545b3}\n"] }]
633
+ args: [{ selector: 'dc-lesson-card', standalone: true, imports: [DatePipe, ButtonModule, PopoverModule, SpeedDialModule, CardModule, TagModule], template: "<div class=\"card-container\">\n @if(this.userService.isAdmin()){\n <p-speeddial\n class=\"dial-button\"\n [model]=\"items\"\n [radius]=\"70\"\n type=\"quarter-circle\"\n direction=\"down-left\"\n [buttonProps]=\"{ severity: 'primary', rounded: true, outlined: true, raised: true }\"\n [tooltipOptions]=\"{ tooltipPosition: 'top' }\" />\n }\n <p-card>\n <div class=\"lesson-card\" [style.height]=\"cardHeight\">\n <div class=\"photo\">\n <img [src]=\"coverUrl\" alt=\"\" />\n </div>\n\n <span class=\"date\">{{ lesson.createdAt | date : 'dd/MM/yyyy' }}</span>\n\n <div class=\"description\">\n <h1>{{ lesson?.name || 'No title available' }}</h1>\n <p>{{ lesson.description || 'No description available' }}</p>\n <div class=\"card-footer\">\n <div class=\"status-tags\">\n @if (lesson.learnable?.level){\n <p-tag>Nivel {{ lesson.learnable?.level }}</p-tag>\n } @if (lesson.taken?.status == 'passed') {\n <p-tag severity=\"success\" value=\"Tomada\" [rounded]=\"true\" />\n } @if (lesson.taken?.status == 'failed') {\n <p-tag severity=\"danger\" value=\"Fallida\" [rounded]=\"true\" />\n } @if (lesson.taken) {\n <p-tag severity=\"success\" value=\"Tomada \uD83D\uDCD6\" [rounded]=\"true\" />\n } @if ( userService.isAdmin()) {\n <p-tag severity=\"contrast\" [value]=\"lesson.manageable?.isPublic ? 'P\u00FAblica' : 'No p\u00FAblica'\" [rounded]=\"true\" />\n <p-tag severity=\"contrast\" [value]=\"lesson.manageable?.status || 'No status'\" [rounded]=\"true\" />\n }\n </div>\n\n <div style=\"position: absolute; bottom: 0px; right: 0px\">\n <p-button label=\"Tomar lecci\u00F3n\" (onClick)=\"eventCard(eventType.Select)\" severity=\"primary\"> </p-button>\n </div>\n </div>\n </div>\n </div>\n </p-card>\n</div>\n", styles: [".card-container{position:relative;margin-bottom:20px;margin-left:10px;min-width:400px}.dial-button{position:absolute;top:10px;right:20px;z-index:10}.lesson-card{border-radius:.5rem;height:100%}.lesson-card .photo{position:absolute;inset:0;z-index:1}.lesson-card .photo img{width:100%;height:100%;object-fit:cover;object-position:center}.lesson-card .photo:after{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;background:linear-gradient(to bottom,rgb(0,0,0) 50%,var(--p-primary-color) 100%);opacity:.4;z-index:2;pointer-events:none}.description{position:relative;z-index:2;color:#fff;height:100%;display:flex;flex-direction:column}.description h1{margin-top:0;margin-bottom:.5rem;font-size:1.5rem;font-weight:900;text-shadow:1px 1px 2px rgba(0,0,0,.7)}.description p{margin-bottom:1rem;flex-grow:1;font-weight:500;text-shadow:1px 1px 2px rgba(0,0,0,.9)}.date{position:absolute;top:1rem;left:1rem;z-index:3;background-color:#000000b3;color:#fff;padding:.3rem .6rem;border-radius:.25rem;font-size:.8rem}.card-footer{display:flex;justify-content:space-between;align-items:center;margin-top:auto}.status-tags{display:flex;gap:.5rem}.status-tags .level-tag,.status-tags .status-tag{padding:.3rem .6rem;border-radius:.25rem;font-size:.8rem}.status-tags .level-tag{background-color:#fff3}.status-tags .status-tag.success{background-color:#28a745b3}.status-tags .status-tag.danger{background-color:#dc3545b3}\n"] }]
639
634
  }], propDecorators: { lesson: [{
640
635
  type: Input
641
636
  }], showOptions: [{
@@ -754,39 +749,24 @@ class DCLessonListComponent extends EntityBaseListComponent {
754
749
  super();
755
750
  // Services
756
751
  this.userService = inject(UserService);
752
+ // Inputs
757
753
  this.customFilters = [];
758
754
  // Injected Services
759
755
  // I dont rename so use the same in EntityBaseListComponent and Pagination
760
756
  this.entityCommunicationService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
757
+ // Public properties
761
758
  this.columns = tableViewColumns;
762
- // Private properties
763
- this.cardEventSubs = [];
764
759
  this.filterConfig.returnProps = {
765
760
  title: 1,
766
761
  name: 1,
767
762
  description: 1,
768
- media: 1,
769
763
  manageable: 1,
770
764
  learnable: 1,
771
765
  assets: 1,
766
+ level: 1,
772
767
  _id: 1,
773
768
  id: 1,
774
769
  };
775
- effect(() => {
776
- // When items signal changes, re-subscribe to card events
777
- this.items(); // a little hack to make the effect run when items change
778
- setTimeout(() => this.subscribeToCardEvents());
779
- });
780
- }
781
- async ngOnInit() {
782
- this.cardComponent = this.customCardComponent || DcLessonCardComponent;
783
- await super.ngOnInit();
784
- }
785
- ngAfterViewInit() {
786
- this.outlets.changes.subscribe(() => this.subscribeToCardEvents());
787
- }
788
- ngOnDestroy() {
789
- this.clearCardEventSubs();
790
770
  }
791
771
  getCustomButtons(item) {
792
772
  return [
@@ -810,21 +790,6 @@ class DCLessonListComponent extends EntityBaseListComponent {
810
790
  this.filterConfig = { ...this.filterConfig, ...filterEvent.item };
811
791
  this.loadData();
812
792
  }
813
- subscribeToCardEvents() {
814
- this.clearCardEventSubs();
815
- this.outlets.forEach((outlet) => {
816
- const instance = outlet.componentInstance;
817
- if (instance?.onAction) {
818
- this.cardEventSubs.push(instance.onAction.subscribe((event) => {
819
- this.handleAction(event);
820
- }));
821
- }
822
- });
823
- }
824
- clearCardEventSubs() {
825
- this.cardEventSubs.forEach((sub) => sub.unsubscribe());
826
- this.cardEventSubs = [];
827
- }
828
793
  async loadData() {
829
794
  try {
830
795
  this.isLoading = true;
@@ -840,18 +805,13 @@ class DCLessonListComponent extends EntityBaseListComponent {
840
805
  }
841
806
  }
842
807
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
843
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: DCLessonListComponent, isStandalone: true, selector: "dc-lesson-list", inputs: { customCardComponent: "customCardComponent", customFilters: "customFilters" }, viewQueries: [{ propertyName: "outlets", predicate: ["outlet"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<dc-filter-bar\n [isAdmin]=\"userService.isAdmin()\"\n [options]=\"filterBarOptions\"\n [customFilters]=\"customFilters\"\n (onFilterAction)=\"applyFilterBarEvent($event)\"\n (onNew)=\"onNew()\"></dc-filter-bar>\n@if(viewType() === 'table') {\n\n<app-quick-table [tableData]=\"items()\" (onAction)=\"doAction($event)\"></app-quick-table>\n\n} @else {\n<div class=\"lesson-list-container\">\n @if (items()?.length > 0) { @for (lesson of items(); track lesson._id) {\n <ng-container\n #outlet=\"ngComponentOutlet\"\n [ngComponentOutlet]=\"cardComponent\"\n [ngComponentOutletInputs]=\"{\n lesson: lesson,\n showOptions: true\n }\">\n </ng-container>\n\n } } @else {\n <p>No se encontraron lecciones disponibles</p>\n }\n</div>\n}\n\n<!-- <p-paginator\n class=\"paginator-container\"\n currentPageReportTemplate=\"{{ totalRecords }} lecciones\"\n [showCurrentPageReport]=\"true\"\n (onPageChange)=\"onPageChange($event)\"\n [totalRecords]=\"totalRecords\"\n [first]=\"first\"\n [rows]=\"rows\"\n [rowsPerPageOptions]=\"[10, 20, 30]\">\n</p-paginator> -->\n\n<p-paginator\n [first]=\"first\"\n [rows]=\"rows\"\n [totalRecords]=\"totalRecordsSignal()\"\n (onPageChange)=\"onPageChange($event)\"\n [showCurrentPageReport]=\"true\"\n [showPageLinks]=\"false\"\n [showFirstLastIcon]=\"false\"\n [rowsPerPageOptions]=\"[10, 20, 30]\"\n currentPageReportTemplate=\" {first}- {last} de {totalRecords} \" />\n", styles: [":host{display:flex;flex-direction:column;height:100%}.lesson-list-container{padding:1.5rem;flex:1;overflow-y:auto;min-height:0}@media (max-width: 768px){.lesson-list-container{margin-top:1rem;padding:0rem}}p-paginator{margin-top:auto;padding:.5rem 1rem}.paginator-container{background:transparent}\n"], dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "component", type: DCFilterBarComponent, selector: "dc-filter-bar", inputs: ["items", "options", "customFilters", "isAdmin"], outputs: ["onFilterAction", "onNew"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: QuickTableComponent, selector: "app-quick-table", inputs: ["columns", "tableData", "actions"], outputs: ["onAction"] }, { kind: "ngmodule", type: PaginatorModule }, { kind: "component", type: i1$3.Paginator, selector: "p-paginator", inputs: ["pageLinkSize", "styleClass", "alwaysShow", "dropdownAppendTo", "templateLeft", "templateRight", "dropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showFirstLastIcon", "totalRecords", "rows", "rowsPerPageOptions", "showJumpToPageDropdown", "showJumpToPageInput", "jumpToPageItemTemplate", "showPageLinks", "locale", "dropdownItemTemplate", "first", "appendTo"], outputs: ["onPageChange"] }] }); }
808
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: DCLessonListComponent, isStandalone: true, selector: "dc-lesson-list", inputs: { customFilters: "customFilters" }, usesInheritance: true, ngImport: i0, template: "<dc-filter-bar\n [isAdmin]=\"userService.isAdmin()\"\n [options]=\"filterBarOptions\"\n [customFilters]=\"customFilters\"\n (onFilterAction)=\"applyFilterBarEvent($event)\"\n (onNew)=\"onNew()\"></dc-filter-bar>\n@if(viewType() === 'table') {\n\n<app-quick-table [tableData]=\"items()\" (onAction)=\"doAction($event)\"></app-quick-table>\n\n} @else {\n<div class=\"lesson-list-container\">\n @if (items()?.length > 0) { @for (lesson of items(); track lesson._id) {\n <dc-lesson-card [lesson]=\"lesson\" [showOptions]=\"true\" (onAction)=\"handleAction($event)\"></dc-lesson-card>\n } } @else {\n <p>No se encontraron lecciones disponibles</p>\n }\n</div>\n}\n\n<!-- <p-paginator\n class=\"paginator-container\"\n currentPageReportTemplate=\"{{ totalRecords }} lecciones\"\n [showCurrentPageReport]=\"true\"\n (onPageChange)=\"onPageChange($event)\"\n [totalRecords]=\"totalRecords\"\n [first]=\"first\"\n [rows]=\"rows\"\n [rowsPerPageOptions]=\"[10, 20, 30]\">\n</p-paginator> -->\n\n<p-paginator\n [first]=\"first\"\n [rows]=\"rows\"\n [totalRecords]=\"totalRecordsSignal()\"\n (onPageChange)=\"onPageChange($event)\"\n [showCurrentPageReport]=\"true\"\n [showPageLinks]=\"false\"\n [showFirstLastIcon]=\"false\"\n [rowsPerPageOptions]=\"[10, 20, 30]\"\n currentPageReportTemplate=\" {first}- {last} de {totalRecords} \" />\n", styles: [":host{display:flex;flex-direction:column;height:100%}.lesson-list-container{padding:1.5rem;flex:1;overflow-y:auto;min-height:0}@media (max-width: 768px){.lesson-list-container{margin-top:1rem;padding:0rem}}p-paginator{margin-top:auto;padding:.5rem 1rem}.paginator-container{background:transparent}\n"], dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "component", type: DCFilterBarComponent, selector: "dc-filter-bar", inputs: ["items", "options", "customFilters", "isAdmin"], outputs: ["onFilterAction", "onNew"] }, { kind: "component", type: QuickTableComponent, selector: "app-quick-table", inputs: ["columns", "tableData", "actions"], outputs: ["onAction"] }, { kind: "ngmodule", type: PaginatorModule }, { kind: "component", type: i1$3.Paginator, selector: "p-paginator", inputs: ["pageLinkSize", "styleClass", "alwaysShow", "dropdownAppendTo", "templateLeft", "templateRight", "dropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showFirstLastIcon", "totalRecords", "rows", "rowsPerPageOptions", "showJumpToPageDropdown", "showJumpToPageInput", "jumpToPageItemTemplate", "showPageLinks", "locale", "dropdownItemTemplate", "first", "appendTo"], outputs: ["onPageChange"] }, { kind: "component", type: DcLessonCardComponent, selector: "dc-lesson-card", inputs: ["lesson", "showOptions", "cardHeight"], outputs: ["onAction"] }] }); }
844
809
  }
845
810
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonListComponent, decorators: [{
846
811
  type: Component,
847
- args: [{ selector: 'dc-lesson-list', standalone: true, imports: [RouterModule, DCFilterBarComponent, NgComponentOutlet, QuickTableComponent, PaginatorModule], template: "<dc-filter-bar\n [isAdmin]=\"userService.isAdmin()\"\n [options]=\"filterBarOptions\"\n [customFilters]=\"customFilters\"\n (onFilterAction)=\"applyFilterBarEvent($event)\"\n (onNew)=\"onNew()\"></dc-filter-bar>\n@if(viewType() === 'table') {\n\n<app-quick-table [tableData]=\"items()\" (onAction)=\"doAction($event)\"></app-quick-table>\n\n} @else {\n<div class=\"lesson-list-container\">\n @if (items()?.length > 0) { @for (lesson of items(); track lesson._id) {\n <ng-container\n #outlet=\"ngComponentOutlet\"\n [ngComponentOutlet]=\"cardComponent\"\n [ngComponentOutletInputs]=\"{\n lesson: lesson,\n showOptions: true\n }\">\n </ng-container>\n\n } } @else {\n <p>No se encontraron lecciones disponibles</p>\n }\n</div>\n}\n\n<!-- <p-paginator\n class=\"paginator-container\"\n currentPageReportTemplate=\"{{ totalRecords }} lecciones\"\n [showCurrentPageReport]=\"true\"\n (onPageChange)=\"onPageChange($event)\"\n [totalRecords]=\"totalRecords\"\n [first]=\"first\"\n [rows]=\"rows\"\n [rowsPerPageOptions]=\"[10, 20, 30]\">\n</p-paginator> -->\n\n<p-paginator\n [first]=\"first\"\n [rows]=\"rows\"\n [totalRecords]=\"totalRecordsSignal()\"\n (onPageChange)=\"onPageChange($event)\"\n [showCurrentPageReport]=\"true\"\n [showPageLinks]=\"false\"\n [showFirstLastIcon]=\"false\"\n [rowsPerPageOptions]=\"[10, 20, 30]\"\n currentPageReportTemplate=\" {first}- {last} de {totalRecords} \" />\n", styles: [":host{display:flex;flex-direction:column;height:100%}.lesson-list-container{padding:1.5rem;flex:1;overflow-y:auto;min-height:0}@media (max-width: 768px){.lesson-list-container{margin-top:1rem;padding:0rem}}p-paginator{margin-top:auto;padding:.5rem 1rem}.paginator-container{background:transparent}\n"] }]
848
- }], ctorParameters: () => [], propDecorators: { customCardComponent: [{
849
- type: Input
850
- }], customFilters: [{
812
+ args: [{ selector: 'dc-lesson-list', standalone: true, imports: [RouterModule, DCFilterBarComponent, QuickTableComponent, PaginatorModule, DcLessonCardComponent], template: "<dc-filter-bar\n [isAdmin]=\"userService.isAdmin()\"\n [options]=\"filterBarOptions\"\n [customFilters]=\"customFilters\"\n (onFilterAction)=\"applyFilterBarEvent($event)\"\n (onNew)=\"onNew()\"></dc-filter-bar>\n@if(viewType() === 'table') {\n\n<app-quick-table [tableData]=\"items()\" (onAction)=\"doAction($event)\"></app-quick-table>\n\n} @else {\n<div class=\"lesson-list-container\">\n @if (items()?.length > 0) { @for (lesson of items(); track lesson._id) {\n <dc-lesson-card [lesson]=\"lesson\" [showOptions]=\"true\" (onAction)=\"handleAction($event)\"></dc-lesson-card>\n } } @else {\n <p>No se encontraron lecciones disponibles</p>\n }\n</div>\n}\n\n<!-- <p-paginator\n class=\"paginator-container\"\n currentPageReportTemplate=\"{{ totalRecords }} lecciones\"\n [showCurrentPageReport]=\"true\"\n (onPageChange)=\"onPageChange($event)\"\n [totalRecords]=\"totalRecords\"\n [first]=\"first\"\n [rows]=\"rows\"\n [rowsPerPageOptions]=\"[10, 20, 30]\">\n</p-paginator> -->\n\n<p-paginator\n [first]=\"first\"\n [rows]=\"rows\"\n [totalRecords]=\"totalRecordsSignal()\"\n (onPageChange)=\"onPageChange($event)\"\n [showCurrentPageReport]=\"true\"\n [showPageLinks]=\"false\"\n [showFirstLastIcon]=\"false\"\n [rowsPerPageOptions]=\"[10, 20, 30]\"\n currentPageReportTemplate=\" {first}- {last} de {totalRecords} \" />\n", styles: [":host{display:flex;flex-direction:column;height:100%}.lesson-list-container{padding:1.5rem;flex:1;overflow-y:auto;min-height:0}@media (max-width: 768px){.lesson-list-container{margin-top:1rem;padding:0rem}}p-paginator{margin-top:auto;padding:.5rem 1rem}.paginator-container{background:transparent}\n"] }]
813
+ }], ctorParameters: () => [], propDecorators: { customFilters: [{
851
814
  type: Input
852
- }], outlets: [{
853
- type: ViewChildren,
854
- args: ['outlet']
855
815
  }] } });
856
816
 
857
817
  class DCLessonFormComponent {
@@ -863,1279 +823,1419 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImpor
863
823
  args: [{ selector: 'dc-lesson-form', standalone: true, imports: [ReactiveFormsModule], template: "<div class=\"lesson-form-container\">\n <h2>Lesson Form</h2>\n <p>Esto es dentro del componente de la leccion</p>\n <!-- Form implementation will go here -->\n</div>\n", styles: [".lesson-form-container{padding:1rem}\n"] }]
864
824
  }] });
865
825
 
866
- // TODO: check LessonComponentBuilders in lessons.clases.ts for origianl implementation
867
- const DynamicComponentBuilders = {
868
- [LessonComponentEnum.Speaker]: SpeakerBuilderComponent,
869
- };
870
- const DynamicComponents = {
871
- [LessonComponentEnum.Speaker]: SpeakerComponent,
872
- };
873
- class DynamicComponentsService {
826
+ class LessonNotionService {
874
827
  constructor() {
875
- this._dynamicComponentBuilders = { ...DynamicComponentBuilders };
876
- this._dynamicComponents = { ...DynamicComponents };
828
+ this.#notionService = inject(NOTION_SERVICE_TOKEN);
829
+ this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
830
+ this.#toastService = inject(TOAST_ALERTS_TOKEN);
831
+ // Keep track of loading state specific to Notion operations
832
+ this.isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
877
833
  }
878
- registerCustomComponent(component, name) {
879
- // this._dynamicComponentBuilders[name || component.name] = component;
880
- this._dynamicComponents[name || component.name] = component;
834
+ #notionService;
835
+ #toastService;
836
+ /**
837
+ * Extracts the Notion Page ID from a URL.
838
+ * @param url The Notion page URL.
839
+ * @returns The extracted page ID or null if invalid.
840
+ */
841
+ extractNotionPageId(url) {
842
+ const notionIdRegex = /[a-f0-9]{32}(?=\?|$)/;
843
+ const match = url.match(notionIdRegex);
844
+ const notionId = match ? match[0] : null;
845
+ if (!notionId) {
846
+ this.#toastService.error({
847
+ title: 'URL inválido',
848
+ subtitle: 'Por favor ingresa una URL válida de Notion.',
849
+ });
850
+ return null;
851
+ }
852
+ return notionId;
881
853
  }
882
- registerCustomComponentBuilder(component, name) {
883
- this._dynamicComponentBuilders[name || component.name] = component;
854
+ /**
855
+ * Links an existing lesson with a Notion page ID by updating the lesson's extras.
856
+ * @param lesson The current lesson data.
857
+ * @param notionPageId The Notion page ID to link.
858
+ * @returns The updated lesson data from the backend or undefined on failure.
859
+ */
860
+ async linkLessonWithNotion(lesson, notionPageId) {
861
+ if (!lesson || !lesson.id) {
862
+ // Ensure lesson exists and has an ID
863
+ this.#toastService.warn({ title: 'No se puede enlazar', subtitle: 'La lección debe existir y tener un ID.' });
864
+ return undefined;
865
+ }
866
+ const updatedLesson = {
867
+ ...lesson,
868
+ extensions: {
869
+ ...(lesson.extensions || {}),
870
+ extras: {
871
+ ...(lesson.extensions?.['extras'] || {}),
872
+ notionPageId: notionPageId,
873
+ },
874
+ },
875
+ };
876
+ this.isLoading.set(true);
877
+ try {
878
+ const savedLesson = await this.lessonsService.postLesson(updatedLesson);
879
+ this.#toastService.success({ title: 'Listo', subtitle: 'Se enlazó la lección con Notion.' });
880
+ return savedLesson;
881
+ }
882
+ catch (error) {
883
+ // Remove explicit type, rely on tsconfig setting
884
+ console.error('Error linking with Notion:', error);
885
+ this.#toastService.error({ title: 'Error al enlazar', subtitle: 'Ocurrió un error inesperado.' });
886
+ return undefined;
887
+ }
888
+ finally {
889
+ this.isLoading.set(false);
890
+ }
884
891
  }
885
- getDynamicComponentBuilders(type) {
886
- return this._dynamicComponentBuilders[type];
892
+ /**
893
+ * Handles the process of importing lesson content from Notion.
894
+ * It prompts the user for a URL if necessary, extracts the ID, fetches content,
895
+ * and potentially links the lesson if it's an existing one.
896
+ * @param currentLesson The current lesson data (can be a new or existing lesson).
897
+ * @param lessonId The current lesson ID (null if it's a new lesson).
898
+ * @returns The fetched HTML content from Notion, or null if the process fails.
899
+ */
900
+ async importAndLinkLessonFromNotion(currentLesson, lessonId) {
901
+ if (!currentLesson)
902
+ return null;
903
+ let notionPageId = null;
904
+ if (currentLesson.extensions?.['extras']?.['notionPageId']) {
905
+ const useExisting = confirm(`Ya tenemos el id ${currentLesson.extensions?.['extras']?.['notionPageId']} ¿Quieres usar este id para importar?`);
906
+ if (useExisting) {
907
+ notionPageId = currentLesson.extensions?.['extras']?.['notionPageId'];
908
+ }
909
+ else {
910
+ const inputUrl = prompt('Ingresa la NUEVA URL de Notion para importar (este ID NO se guardará automáticamente si la lección ya existe)');
911
+ if (!inputUrl)
912
+ return null; // User cancelled
913
+ notionPageId = this.extractNotionPageId(inputUrl);
914
+ }
915
+ }
916
+ else {
917
+ const inputUrl = prompt('Ingresa el URL de Notion para importar la lección (se enlazará si la lección ya existe)');
918
+ if (!inputUrl)
919
+ return null; // User cancelled
920
+ notionPageId = this.extractNotionPageId(inputUrl);
921
+ // Link automatically only if we got a valid Notion ID AND the lesson already exists (has an ID)
922
+ if (notionPageId && lessonId) {
923
+ const linkedLesson = await this.linkLessonWithNotion(currentLesson, notionPageId);
924
+ if (!linkedLesson) {
925
+ // Linking failed, maybe stop the import? Or proceed without linking?
926
+ // For now, let's stop.
927
+ this.#toastService.error({ title: 'Error de Enlace', subtitle: 'No se pudo enlazar con Notion antes de importar.' });
928
+ return null;
929
+ }
930
+ // If linking succeeded, the lesson object might have changed, but we proceed with the import using the notionPageId.
931
+ }
932
+ }
933
+ if (!notionPageId) {
934
+ this.#toastService.warn({ title: 'Sin ID de Notion', subtitle: 'No se proporcionó un ID de Notion válido para importar.' });
935
+ return null;
936
+ }
937
+ this.isLoading.set(true);
938
+ try {
939
+ this.#toastService.info({ title: 'Importando lección...', subtitle: 'Espera unos segundos' });
940
+ const md = await this.#notionService.getPageInSpecificFormat(notionPageId, NotionExportType.HTML);
941
+ console.log('Imported MD/HTML:', md);
942
+ this.#toastService.success({ title: 'Contenido Importado', subtitle: 'Contenido de Notion obtenido.' });
943
+ return md.content; // Return the fetched content
944
+ }
945
+ catch (error) {
946
+ // Remove explicit type, rely on tsconfig setting
947
+ console.error('Error importing from Notion:', error);
948
+ this.#toastService.error({ title: 'Error de importación', subtitle: 'Ocurrió un error inesperado.' });
949
+ return null; // Return null on failure
950
+ }
951
+ finally {
952
+ this.isLoading.set(false);
953
+ }
887
954
  }
888
- getDynamicComponentClass(type) {
889
- return this._dynamicComponents[type];
955
+ /**
956
+ * Fetches content from the linked Notion page for AI improvement (placeholder).
957
+ * @param lesson The current lesson data.
958
+ */
959
+ async improveLessonWithNotionAI(lesson) {
960
+ if (!lesson)
961
+ return;
962
+ const notionId = lesson.extensions?.['extras']?.['notionPageId'];
963
+ if (!notionId) {
964
+ this.#toastService.warn({ title: 'Sin ID de Notion', subtitle: 'Enlaza la lección con Notion primero.' });
965
+ return;
966
+ }
967
+ this.isLoading.set(true);
968
+ try {
969
+ this.#toastService.info({ title: 'Mejorando con IA...', subtitle: 'Obteniendo contenido de Notion.' });
970
+ const md = await this.#notionService.getPageInSpecificFormat(notionId, NotionExportType.HTML);
971
+ console.log('Content to improve:', md);
972
+ // TODO: Add actual AI improvement logic here
973
+ // e.g., call another service: await this.aiImprovementService.improve(md.content);
974
+ // Then potentially update the lesson via lessonService or return data
975
+ this.#toastService.success({ title: 'Contenido Obtenido', subtitle: 'Listo para mejorar con IA (lógica no implementada).' });
976
+ }
977
+ catch (error) {
978
+ // Remove explicit type, rely on tsconfig setting
979
+ console.error('Error improving with AI:', error);
980
+ this.#toastService.error({ title: 'Error de IA', subtitle: 'Ocurrió un error inesperado.' });
981
+ }
982
+ finally {
983
+ this.isLoading.set(false);
984
+ }
890
985
  }
891
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
892
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsService, providedIn: 'root' }); }
986
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonNotionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
987
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonNotionService, providedIn: 'root' }); }
893
988
  }
894
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsService, decorators: [{
989
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonNotionService, decorators: [{
895
990
  type: Injectable,
896
991
  args: [{
897
992
  providedIn: 'root',
898
993
  }]
899
994
  }] });
900
995
 
901
- class LessonRendererService {
996
+ class LessonUtilsService {
902
997
  constructor() {
903
- this.dynamicComponentsService = inject(DynamicComponentsService);
904
- this.toastrService = inject(TOAST_ALERTS_TOKEN);
905
- this.components = {};
906
- this.mainForm = new FormGroup({});
998
+ this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
999
+ this.#toastService = inject(TOAST_ALERTS_TOKEN);
1000
+ this.#agentService = inject(CONVERSATION_AI_TOKEN);
1001
+ this.loadingBarService = inject(LoadingBarService);
907
1002
  }
908
- renderLesson(lessonData, viewContainerRef, dynamicLessonElement, renderer) {
909
- this.clearLessonRendering(dynamicLessonElement);
910
- console.log('Rendering lesson:', lessonData.id);
911
- const { htmlContent, components } = this.parseAndCreateComponents(lessonData, viewContainerRef);
912
- this.components = components;
913
- this.aggregateFormControls(this.components);
914
- dynamicLessonElement.innerHTML = htmlContent;
915
- this.injectComponentsIntoDom(this.components, renderer);
916
- return this.components;
1003
+ #toastService;
1004
+ #agentService;
1005
+ /**
1006
+ * Validates if audios need generation for the given lesson.
1007
+ * Currently logs a placeholder message.
1008
+ * @param lesson The lesson object (or signal) to validate.
1009
+ */
1010
+ validateAudios(lesson) {
1011
+ // Access lesson data directly
1012
+ // if (!lesson?.components) {
1013
+ // return;
1014
+ // }
1015
+ alert('I need to refactor this part');
1016
+ // Placeholder logic - adapt as needed from original component
1017
+ console.log('Validating audios for lesson:', lesson.id);
917
1018
  }
918
- clearLessonRendering(dynamicLessonElement) {
919
- Object.values(this.components).forEach((compRef) => compRef.destroy());
920
- this.components = {};
921
- this.mainForm = new FormGroup({});
922
- if (dynamicLessonElement) {
923
- dynamicLessonElement.innerHTML = '';
924
- }
1019
+ /**
1020
+ * Updates the lesson signal with a new banner image in metadata.
1021
+ * @param lessonSignal The signal holding the lesson data.
1022
+ * @param imageUploaded The image data object from the upload event. Should conform to LessonImage structure partially.
1023
+ */
1024
+ uploadCover(lessonSignal, imageUploaded) {
1025
+ lessonSignal.update((currentLesson) => {
1026
+ if (!currentLesson)
1027
+ return undefined;
1028
+ const assets = { ...(currentLesson.assets ?? {}) };
1029
+ assets.banner = imageUploaded;
1030
+ return {
1031
+ ...currentLesson,
1032
+ assets,
1033
+ };
1034
+ });
925
1035
  }
926
- parseAndCreateComponents(lessonData, viewContainerRef) {
927
- const r1 = new RegExp('~(.+?)~', 'g');
928
- let count = 0;
929
- const createdComponents = {};
930
- const htmlContent = lessonData.textCoded.replace(r1, (_matching, jsonCoded) => {
931
- const componentName = `dynamicComp${count}`;
932
- count++;
933
- const componentRef = this.createComponentReferenceWithJson(jsonCoded, lessonData, viewContainerRef);
934
- if (!componentRef) {
935
- console.error(`Failed to create component for: ${jsonCoded}`);
936
- return '<!-- component creation failed -->';
1036
+ // I want to deprecate this method
1037
+ /**
1038
+ * @deprecated Use create new method.
1039
+ * Generates lesson content using AI. Assumes the lesson is already saved.
1040
+ * @param lessonId The ID of the lesson to generate content for.
1041
+ * @returns The updated lesson object after AI generation, or null on failure.
1042
+ */
1043
+ async generateByAI(lessonId) {
1044
+ if (!lessonId) {
1045
+ this.#toastService.warn({ title: 'ID Requerido', subtitle: 'Se necesita un ID de lección para usar IA.' });
1046
+ return null;
1047
+ }
1048
+ // No need to save here, component should ensure it's saved before calling.
1049
+ try {
1050
+ await this.lessonsService.postGenerateByAI(lessonId);
1051
+ // Re-fetch the lesson data to get AI updates
1052
+ const updatedLesson = await this.lessonsService.getLesson(lessonId);
1053
+ if (updatedLesson) {
1054
+ this.#toastService.success({ title: 'IA completada', subtitle: 'Lección actualizada con IA.' });
1055
+ return updatedLesson;
937
1056
  }
938
- createdComponents[componentName] = componentRef;
939
- return `<span id="${componentName}"></span>`;
940
- });
941
- return { htmlContent, components: createdComponents };
1057
+ else {
1058
+ throw new Error('Failed to fetch lesson after AI generation');
1059
+ }
1060
+ }
1061
+ catch (error) {
1062
+ // Type the error object
1063
+ console.error('Error during AI generation in service:', error);
1064
+ this.#toastService.error({ title: 'Error de IA', subtitle: 'No se pudo generar la lección con IA.' });
1065
+ return null; // Return null in catch block
1066
+ }
1067
+ // Loading state should be managed by the component calling this service.
942
1068
  }
943
- createComponentReferenceWithJson(json, currentLesson, viewContainerRef) {
1069
+ /**
1070
+ * Improves the provided Markdown text using AI and updates the lesson.
1071
+ * Assumes the lesson is already saved before calling this.
1072
+ * @param lesson The lesson object containing the prompt and other context.
1073
+ * @param markdownText The Markdown text generated from the lesson's HTML content.
1074
+ * @returns The improved Markdown string, or null on failure.
1075
+ */
1076
+ async improveMDWithAI(lesson, markdownText) {
1077
+ if (!markdownText) {
1078
+ this.#toastService.warn({ title: 'Texto Requerido', subtitle: 'Se necesita texto Markdown para mejorar con IA.' });
1079
+ return null;
1080
+ }
944
1081
  try {
945
- let lessonCodedConfig = JSON.parse(json);
946
- if (lessonCodedConfig.id && currentLesson?.dynamicComponents[lessonCodedConfig.id]) {
947
- const foundConfig = currentLesson.dynamicComponents[lessonCodedConfig.id];
948
- if (foundConfig) {
949
- lessonCodedConfig = { ...lessonCodedConfig, ...foundConfig };
1082
+ this.loadingBarService.showIndeterminate();
1083
+ const textPrompt = this.lessonsService.getPrompts().content(lesson);
1084
+ const messages = [{ content: textPrompt, role: ChatRole.User }];
1085
+ const response = await this.#agentService.callChatCompletion({ messages, model: { provider: 'google' } });
1086
+ let improvedMarkdown = response.content?.trim() ?? null;
1087
+ // Remove potential markdown fences
1088
+ const markdownFenceStart = '```markdown\n';
1089
+ const markdownFenceEnd = '\n```';
1090
+ if (improvedMarkdown?.startsWith(markdownFenceStart) && improvedMarkdown.endsWith(markdownFenceEnd)) {
1091
+ improvedMarkdown = improvedMarkdown.slice(markdownFenceStart.length, -markdownFenceEnd.length);
1092
+ }
1093
+ else {
1094
+ // Also handle cases where it might just be ``` at the start/end
1095
+ const simpleFence = '```';
1096
+ if (improvedMarkdown?.startsWith(simpleFence)) {
1097
+ improvedMarkdown = improvedMarkdown.slice(simpleFence.length);
1098
+ }
1099
+ if (improvedMarkdown?.endsWith(simpleFence)) {
1100
+ improvedMarkdown = improvedMarkdown.slice(0, -simpleFence.length);
950
1101
  }
951
1102
  }
952
- const LessonClass = this.dynamicComponentsService.getDynamicComponentClass(lessonCodedConfig.component);
953
- if (!LessonClass) {
954
- console.error(`Component class not found for type: ${lessonCodedConfig.component}. JSON: ${json}`);
1103
+ // Trim any leading/trailing whitespace that might remain after removing fences
1104
+ improvedMarkdown = improvedMarkdown?.trim() ?? null;
1105
+ console.log('Improved Markdown:', improvedMarkdown);
1106
+ return improvedMarkdown; // Return only the string
1107
+ }
1108
+ catch (error) {
1109
+ console.error('Error during AI Markdown improvement in service:', error);
1110
+ this.#toastService.error({ title: 'Error de IA', subtitle: 'No se pudo mejorar la lección con IA.' });
1111
+ return null; // Return null in catch block
1112
+ }
1113
+ finally {
1114
+ this.loadingBarService.successAndHide();
1115
+ }
1116
+ }
1117
+ /**
1118
+ * Generates a concise description for the lesson using AI based on its content.
1119
+ * @param lesson The lesson object containing the content.
1120
+ * @returns A promise resolving to the generated description string, or null on failure.
1121
+ */
1122
+ async generateDescriptionWithAI(lesson) {
1123
+ if (!lesson || !lesson.textCoded) {
1124
+ this.#toastService.warn({ title: 'Contenido Requerido', subtitle: 'Se necesita contenido en la lección para generar una descripción.' });
1125
+ return null;
1126
+ }
1127
+ try {
1128
+ this.loadingBarService.showIndeterminate(); // Corrected: No argument needed
1129
+ // Extract plain text from the lesson's HTML content
1130
+ const plainTextContent = this._extractTextFromHtml(lesson.textCoded);
1131
+ if (!plainTextContent || plainTextContent.trim().length === 0) {
1132
+ this.#toastService.warn({ title: 'Texto Vacío', subtitle: 'No se pudo extraer texto útil del contenido de la lección.' });
955
1133
  return null;
956
1134
  }
957
- const componentRef = viewContainerRef.createComponent(LessonClass);
958
- if (lessonCodedConfig.inputs) {
959
- for (const key in lessonCodedConfig.inputs) {
960
- if (lessonCodedConfig.inputs.hasOwnProperty(key)) {
961
- componentRef.instance[key] = lessonCodedConfig.inputs[key];
962
- }
963
- }
1135
+ const descriptionPrompt = this.lessonsService.getPrompts().description(lesson);
1136
+ const messages = [{ content: descriptionPrompt, role: ChatRole.User }];
1137
+ const response = await this.#agentService.callChatCompletion({ messages, model: { provider: 'google' } });
1138
+ let generatedDescription = response.content?.trim() ?? null;
1139
+ // Basic cleanup (remove potential quotes if the AI wraps the response)
1140
+ if (generatedDescription && generatedDescription.startsWith('"') && generatedDescription.endsWith('"')) {
1141
+ generatedDescription = generatedDescription.substring(1, generatedDescription.length - 1);
964
1142
  }
965
- if (lessonCodedConfig.settings) {
966
- componentRef.instance.config = lessonCodedConfig;
1143
+ if (generatedDescription) {
1144
+ this.#toastService.success({ title: 'Descripción Generada', subtitle: 'Se generó la descripción con IA.' });
1145
+ return generatedDescription;
1146
+ }
1147
+ else {
1148
+ throw new Error('AI did not return a valid description.');
967
1149
  }
968
- return componentRef;
969
1150
  }
970
1151
  catch (error) {
971
- console.error(`Error processing component JSON: ${json}`, error);
1152
+ console.error('Error during AI description generation in service:', error);
1153
+ this.#toastService.error({ title: 'Error de IA', subtitle: 'No se pudo generar la descripción con IA.' });
972
1154
  return null;
973
1155
  }
1156
+ finally {
1157
+ this.loadingBarService.successAndHide();
1158
+ }
974
1159
  }
975
- aggregateFormControls(components) {
976
- const newFormControls = {};
977
- Object.entries(components).forEach(([name, componentRef]) => {
978
- if (componentRef.instance?.control instanceof FormControl) {
979
- newFormControls[name] = componentRef.instance.control;
980
- newFormControls[name].addValidators(Validators.required);
1160
+ /**
1161
+ * Extracts plain text from an HTML string, removing encoded JSON components.
1162
+ * @param htmlInput The HTML string potentially containing encoded JSON (~...~).
1163
+ * @returns The plain text representation.
1164
+ */
1165
+ _extractTextFromHtml(htmlInput) {
1166
+ if (!htmlInput)
1167
+ return '';
1168
+ // 1. Replace encoded JSON blocks with their 'settings.text' or an empty string
1169
+ const jsonRegex = /~(.+?)~/g;
1170
+ const textWithPlaceholders = htmlInput.replace(jsonRegex, (match, jsonCoded) => {
1171
+ try {
1172
+ const decodedJson = jsonCoded.replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
1173
+ const data = JSON.parse(decodedJson);
1174
+ return data?.settings?.text || ''; // Return text or empty string if not found
1175
+ }
1176
+ catch (error) {
1177
+ console.error('Failed to parse JSON inside HTML during text extraction:', jsonCoded, error);
1178
+ return ''; // Return empty string on error
981
1179
  }
982
1180
  });
983
- this.mainForm = new FormGroup(newFormControls);
1181
+ // 2. Strip remaining HTML tags to get plain text
1182
+ // Create a temporary DOM element to parse the HTML
1183
+ const tempDiv = document.createElement('div');
1184
+ tempDiv.innerHTML = textWithPlaceholders;
1185
+ // Use textContent to get the plain text, effectively stripping tags
1186
+ // Replace multiple whitespace characters (including newlines) with a single space
1187
+ return (tempDiv.textContent || tempDiv.innerText || '').replace(/\s+/g, ' ').trim();
984
1188
  }
985
- injectComponentsIntoDom(components, renderer) {
986
- Object.entries(components).forEach(([name, componentRef]) => {
987
- const elementRef = document.getElementById(name);
988
- if (elementRef) {
989
- this.addComponentToNode(componentRef, elementRef, renderer);
1189
+ /**
1190
+ * Cleans orphaned components from the lesson's dynamicComponents.
1191
+ * An orphaned component is one present in dynamicComponents but its ID is not found in the textCoded HTML.
1192
+ * @param lesson The lesson object to clean.
1193
+ * @returns A new lesson object with orphaned components removed from dynamicComponents.
1194
+ */
1195
+ cleanOrphanedComponents(lesson) {
1196
+ if (!lesson || !lesson.textCoded || !lesson.dynamicComponents) {
1197
+ // Return the original lesson if essential parts are missing
1198
+ return lesson;
1199
+ }
1200
+ const textCoded = lesson.textCoded;
1201
+ const existingComponents = lesson.dynamicComponents;
1202
+ const existingComponentIds = new Set(Object.keys(existingComponents));
1203
+ // Regex to find "id":"<component_id>" within the textCoded string
1204
+ const idRegex = /"id":"([^"]+)"/g;
1205
+ const foundIdsInText = new Set();
1206
+ let match;
1207
+ while ((match = idRegex.exec(textCoded)) !== null) {
1208
+ const potentialId = match[1];
1209
+ // Check if the found ID actually exists in our dynamic components map
1210
+ // This ensures we only count IDs that correspond to known components.
1211
+ if (existingComponentIds.has(potentialId)) {
1212
+ foundIdsInText.add(potentialId);
1213
+ }
1214
+ }
1215
+ const orphanedIds = [];
1216
+ const cleanedDynamicComponents = {};
1217
+ // Iterate through existing component IDs
1218
+ for (const componentId of existingComponentIds) {
1219
+ if (foundIdsInText.has(componentId)) {
1220
+ // Keep the component if its ID was found in the text
1221
+ cleanedDynamicComponents[componentId] = existingComponents[componentId];
990
1222
  }
991
1223
  else {
992
- console.warn(`Placeholder element with ID '${name}' not found in the DOM.`);
1224
+ // Mark as orphaned if not found
1225
+ orphanedIds.push(componentId);
993
1226
  }
1227
+ }
1228
+ if (orphanedIds.length > 0) {
1229
+ console.warn(`[LessonUtilsService] Orphaned components detected and will be removed from lesson data (IDs not found in textCoded):`, orphanedIds);
1230
+ this.#toastService.warn({
1231
+ title: 'Componentes Huérfanos Detectados',
1232
+ subtitle: `Se removerán ${orphanedIds.length} componentes no usados del editor.`,
1233
+ // life: 5000, // Removed 'life' property as it might not be supported by ToastData
1234
+ });
1235
+ }
1236
+ // Return a new lesson object with the cleaned components
1237
+ return {
1238
+ ...lesson,
1239
+ dynamicComponents: cleanedDynamicComponents,
1240
+ };
1241
+ }
1242
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonUtilsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1243
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonUtilsService, providedIn: 'root' }); }
1244
+ }
1245
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonUtilsService, decorators: [{
1246
+ type: Injectable,
1247
+ args: [{
1248
+ providedIn: 'root', // Provide globally or in a specific module if preferred
1249
+ }]
1250
+ }] });
1251
+
1252
+ // TODO: check LessonComponentBuilders in lessons.clases.ts for origianl implementation
1253
+ const DefaultDynamicComponentBuilders = {
1254
+ [LessonComponentEnum.Speaker]: SpeakerBuilderComponent,
1255
+ [LessonComponentEnum.Selector]: SelectorBuilderComponent,
1256
+ };
1257
+ const DefaultDynamicComponents = {
1258
+ [LessonComponentEnum.Speaker]: SpeakerComponent,
1259
+ [LessonComponentEnum.Selector]: SelectorComponent,
1260
+ };
1261
+ class DynamicComponentsRegisterService {
1262
+ constructor() {
1263
+ this._dynamicComponentBuilders = {};
1264
+ this._dynamicComponents = {};
1265
+ this.registerComponentAndBuilder(DefaultDynamicComponents, DefaultDynamicComponentBuilders);
1266
+ }
1267
+ registerComponentAndBuilder(components, builders) {
1268
+ Object.entries(components).forEach(([key, value]) => {
1269
+ this.registerCustomComponent(value, key);
1270
+ });
1271
+ Object.entries(builders).forEach(([key, value]) => {
1272
+ this.registerCustomComponentBuilder(value, key);
994
1273
  });
995
1274
  }
996
- addComponentToNode(componentRef, nodeDOM, renderer) {
997
- renderer.appendChild(nodeDOM, componentRef.location.nativeElement);
1275
+ registerCustomComponent(component, name) {
1276
+ // this._dynamicComponentBuilders[name || component.name] = component;
1277
+ this._dynamicComponents[name || component.name] = component;
998
1278
  }
999
- evaluateForms() {
1000
- this.mainForm.markAllAsTouched();
1001
- if (!this.mainForm.valid) {
1002
- Object.keys(this.mainForm.controls).forEach((controlName) => {
1003
- if (this.components[controlName]?.instance?.validate) {
1004
- this.components[controlName].instance.validate();
1005
- }
1006
- });
1007
- this.toastrService.warn({ subtitle: 'Por favor completa todos los ejercicios', title: 'Incompleto' });
1008
- return null;
1279
+ registerCustomComponentBuilder(component, name) {
1280
+ this._dynamicComponentBuilders[name || component.name] = component;
1281
+ }
1282
+ getDynamicComponentBuilders(type) {
1283
+ return this._dynamicComponentBuilders[type];
1284
+ }
1285
+ getDynamicComponentClass(type) {
1286
+ return this._dynamicComponents[type];
1287
+ }
1288
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsRegisterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1289
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsRegisterService, providedIn: 'root' }); }
1290
+ }
1291
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsRegisterService, decorators: [{
1292
+ type: Injectable,
1293
+ args: [{
1294
+ providedIn: 'root',
1295
+ }]
1296
+ }], ctorParameters: () => [] });
1297
+
1298
+ class DynamicComponentsBuilderService {
1299
+ #dialogService = inject(DialogService);
1300
+ #toastService = inject(TOAST_ALERTS_TOKEN);
1301
+ #dynamicComponentsService = inject(DynamicComponentsRegisterService);
1302
+ openComponentBuilder(componentType, data = null) {
1303
+ const componentToBuild = this.#dynamicComponentsService.getDynamicComponentBuilders(componentType);
1304
+ if (!componentToBuild) {
1305
+ console.error(`No component builder found for type: ${componentType}`);
1306
+ this.#toastService.error({ title: 'Error', subtitle: `Componente desconocido: ${componentType}` });
1307
+ return undefined;
1009
1308
  }
1010
- const rates = { correct: 0, incorrect: 0, score: 0 };
1011
- Object.keys(this.mainForm.controls).forEach((controlName) => {
1012
- const instance = this.components[controlName]?.instance;
1013
- if (instance && typeof instance.evaluate === 'function') {
1014
- try {
1015
- const result = instance.evaluate();
1016
- if (result) {
1017
- rates.correct++;
1018
- }
1019
- else {
1020
- rates.incorrect++;
1021
- }
1022
- }
1023
- catch (err) {
1024
- console.error('Error during evaluation for component:', controlName, instance, err);
1025
- rates.incorrect++;
1026
- }
1027
- }
1309
+ const dialogRef = this.#dialogService.open(componentToBuild, {
1310
+ inputValues: {
1311
+ // inputValues was removed in newer PrimeNG versions, use 'data'
1312
+ inputs: data?.inputs,
1313
+ id: data?.id,
1314
+ },
1315
+ width: '80vw',
1316
+ header: 'Agregar componente',
1317
+ closable: true,
1028
1318
  });
1029
- const totalQuestions = rates.correct + rates.incorrect;
1030
- rates.score = totalQuestions > 0 ? rates.correct / totalQuestions : 0;
1031
- const status = rates.score >= 0.7 ? 'passed' : 'failed';
1032
- if (status === 'passed') {
1033
- this.toastrService.success({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%.`, title: '¡Muy bien!' });
1034
- }
1035
- else {
1036
- this.toastrService.warn({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%. Revisa tus respuestas.`, title: 'Casi lo logras' });
1037
- }
1038
- return { rates, takenLesson: null };
1319
+ return dialogRef;
1039
1320
  }
1040
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonRendererService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1041
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonRendererService, providedIn: 'root' }); }
1321
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsBuilderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1322
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsBuilderService, providedIn: 'root' }); }
1042
1323
  }
1043
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonRendererService, decorators: [{
1324
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsBuilderService, decorators: [{
1044
1325
  type: Injectable,
1045
1326
  args: [{
1046
1327
  providedIn: 'root',
1047
1328
  }]
1048
1329
  }] });
1049
1330
 
1050
- const EnglishEvaluationSkill = `
1051
- You are a virtual professor for the Polilan English learning app. Your primary role is to evaluate a student's English conversational performance.
1052
-
1053
- You will be provided with two pieces of information for each evaluation:
1054
- 1. **The student's English level:** This will be one of "Beginner", "Intermediate", or "Advanced".
1055
- 2. **A conversation transcript:** This will be the dialogue between the Student and an Assistant, with each turn clearly labeled (e.g., "Student or User: ...", "Assistant: ...").
1056
-
1057
- Your sole task is to evaluate the *Student's* contribution to the conversation. **Do NOT evaluate the Assistant's turns.**
1058
-
1059
- Evaluate the student's performance based on the following aspects, *always keeping the student's provided level in mind* and adjusting your expectations accordingly specially with beginners give them credits just for trying:
1060
-
1061
- * **Grammar & Syntax:** Evaluate accuracy and complexity *relative to the expected level*. (Beginners: focus on basic accuracy; Advanced: expect complex structures with high accuracy).
1062
- * **Vocabulary & Usage:** Evaluate the range and appropriateness of words used *relative to the expected level*. (Beginners: focus on using basic words correctly; Advanced: expect varied and idiomatic language).
1063
- * **Cohesion & Coherence:** Evaluate how well their turns connect and make sense within the conversation *relative to the expected level*. (Beginners: focus on simple, clear responses; Advanced: expect logical flow and detailed contributions).
1064
- * **Task Completion/Relevance:** Evaluate how effectively they participate and respond to the conversation's goals or topics *relative to the expected level*.
1065
-
1066
- Assign a rating from 0 to 3 based on how well the student performs *compared to the typical expectations for their provided level*:
1067
-
1068
- * **0: Bad** - Performance is significantly below what is expected for this level. Many errors impede communication.
1069
- * **1: Not Bad** - Performance is below expectations for this level, but communication is sometimes possible despite frequent errors.
1070
- * **2: Good** - Performance meets expectations for this level, demonstrating solid understanding and usage with only occasional minor errors.
1071
- * **3: Very Good** - Performance exceeds expectations for this level, demonstrating a strong command and potential to progress quickly.
1072
-
1073
- Provide detailed, constructive feedback that explains the rating. Point out specific strengths and areas for improvement based on their performance *at their given level*. Use examples from the dialog if helpful. Maintain a supportive, encouraging, and educational tone appropriate for a virtual professor guiding a student at their specific stage.
1074
- `;
1075
-
1076
- const EngagementSkill = `
1077
- You are an engagement assistant, start by greeting the user, asking something about the lesson, and then continue the conversation. always ask one or two friendly questions.
1078
- that makes sense for the lesson.
1079
- `;
1080
- class LessonConversationService {
1081
- constructor() {
1082
- this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
1083
- this.userService = inject(UserService);
1084
- this.conversationSettings = signal(undefined, ...(ngDevMode ? [{ debugName: "conversationSettings" }] : []));
1085
- this.conversationFlow = signal(undefined, ...(ngDevMode ? [{ debugName: "conversationFlow" }] : []));
1086
- this.evalAgentTask = signal(undefined, ...(ngDevMode ? [{ debugName: "evalAgentTask" }] : []));
1087
- }
1088
- initializeConversationFlow() {
1089
- this.evalAgentTask.set({
1090
- systemPrompt: EnglishEvaluationSkill,
1091
- model: { provider: 'google' },
1092
- task: `Please evaluate the user conversation student data is: \n ${this.userService.getUserDataInformation()}`,
1093
- expectedResponseType: EvalResultStringDefinition,
1094
- });
1095
- this.conversationFlow.set({
1096
- goal: {
1097
- enabled: true,
1098
- task: `User is learning is taking a lesson about languages, evaluate how good are the responses.`,
1099
- model: { quality: EModelQuality.FAST },
1100
- },
1101
- triggerTasks: {
1102
- [ConversationEvents.OnUserMessage]: {
1103
- enabled: true,
1104
- ...this.evalAgentTask(),
1105
- },
1106
- },
1107
- challenges: null,
1108
- dynamicConditions: [
1109
- {
1110
- what: ConditionType.Goal,
1111
- when: ConditionOperator.GreaterThanOrEqual,
1112
- value: 100,
1113
- do: [
1114
- {
1115
- actionType: EDoActionType.ChangePrompt,
1116
- systemPromptType: SystemPromptType.SystemPrompt,
1117
- prompt: 'You were talking with the user, user finish the conversation, in the next message you must try to finish the conversation and say good bye.',
1118
- },
1119
- {
1120
- actionType: EDoActionType.ChangePrompt,
1121
- systemPromptType: SystemPromptType.CharacterDescription,
1122
- prompt: '',
1123
- },
1124
- {
1125
- actionType: EDoActionType.ChangePrompt,
1126
- systemPromptType: SystemPromptType.UserInformation,
1127
- prompt: '',
1128
- },
1129
- {
1130
- actionType: EDoActionType.ChangePrompt,
1131
- systemPromptType: SystemPromptType.MessageExamples,
1132
- prompt: '',
1133
- },
1134
- {
1135
- actionType: EDoActionType.ChangePrompt,
1136
- systemPromptType: SystemPromptType.ScenarioDescription,
1137
- prompt: '',
1138
- },
1139
- ],
1140
- },
1141
- ],
1142
- moodState: {
1143
- enabled: false,
1144
- useAssetStatesOnly: false,
1145
- detectableStates: [],
1146
- },
1147
- });
1331
+ class DynamicComponentBuilderDialogComponent {
1332
+ #config;
1333
+ constructor(ref) {
1334
+ this.ref = ref;
1335
+ this.#config = inject(DynamicDialogConfig);
1336
+ this.dynamicComponentsRegisterService = inject(DynamicComponentsRegisterService);
1148
1337
  }
1149
- async startAI(lesson, settings) {
1150
- console.log('Requesting agent cards from LessonAIService...');
1151
- try {
1152
- const conversationSettings = await this.generateConversationSettingsForLesson(lesson, settings);
1153
- if (conversationSettings) {
1154
- this.conversationSettings.set(conversationSettings);
1155
- console.log('Agent cards received and set.');
1156
- return conversationSettings;
1338
+ ngOnInit() {
1339
+ const componentType = this.#config.data.type;
1340
+ if (componentType) {
1341
+ const componentClass = this.dynamicComponentsRegisterService.getDynamicComponentBuilders(componentType);
1342
+ if (componentClass) {
1343
+ this.container.clear();
1344
+ this.componentRef = this.container.createComponent(componentClass);
1157
1345
  }
1158
1346
  else {
1159
- console.error('Failed to generate agent cards (service returned null).');
1160
- return null;
1347
+ console.error(`Component class not found for type: ${componentType}`);
1161
1348
  }
1162
1349
  }
1163
- catch (error) {
1164
- console.error('Error generating agent cards:', error);
1165
- return null;
1166
- }
1167
- }
1168
- /**
1169
- * Builds the scenario prompt string based on lesson content and user info.
1170
- * @param lessonText The extracted text content of the lesson.
1171
- * @param userInformationPrompt The formatted string containing user details.
1172
- * @returns The complete scenario prompt string.
1173
- */
1174
- _buildLessonInstructionsPrompt(lessonText, userInformationPrompt, settings) {
1175
- return `
1176
- ${settings.instructionsPrompt}
1177
-
1178
- <Lesson Text>
1179
- ${lessonText}
1180
- </Lesson Text>
1181
-
1182
- ${EngagementSkill}
1183
-
1184
- <User Information>
1185
- ${userInformationPrompt}`;
1186
1350
  }
1187
- /**
1188
- * Generates conversation settings for a lesson, using the lesson scenario as the initial prompt.
1189
- * @param lesson The lesson data.
1190
- * @returns An IConversationSettings object configured for the lesson scenario.
1191
- */
1192
- async generateConversationSettingsForLesson(lesson, settings) {
1193
- // TODO: Consolidate user fetching logic if possible, or ensure consistency
1194
- const baseLang = this.userService.user().settings.baseLanguage || 'en';
1195
- let lessonText = '';
1196
- if (lesson.textCoded) {
1197
- lessonText = this.lessonsService.extractTextFromHtml(lesson.textCoded);
1198
- }
1199
- else {
1200
- lessonText = lesson.markdown;
1351
+ copyCode() {
1352
+ // navigator.clipboard.writeText(this.#config.data.type);
1353
+ alert('Copied to clipboard');
1354
+ if (this.componentRef) {
1355
+ // You can now access any public property on the component instance
1356
+ // For example, if your dynamic component has a 'properties' property:
1357
+ console.log('Component properties: ', this.componentRef.instance.getComponentData());
1358
+ const dynamicObjectData = this.componentRef.instance.getComponentData();
1201
1359
  }
1202
- const userInformationPrompt = this.userService.getUserDataInformation();
1203
- let lessonInstructionsPrompt = this._buildLessonInstructionsPrompt(lessonText, userInformationPrompt, settings);
1204
- if (settings.additionalPrompt) {
1205
- lessonInstructionsPrompt += '\n\n' + settings.additionalPrompt;
1360
+ console.log(this.container);
1361
+ }
1362
+ addToEnd() {
1363
+ if (this.componentRef) {
1364
+ const dynamicObjectData = this.componentRef.instance.getComponentData();
1365
+ this.ref.close(dynamicObjectData);
1206
1366
  }
1207
- const initialMessage = {
1208
- role: ChatRole.System,
1209
- content: lessonInstructionsPrompt,
1210
- messageId: SystemPromptType.SystemPrompt,
1211
- };
1212
- // Use defaults similar to DEFAULT_LESSON_AGENT_CARD but adjust for prompt-based start
1213
- const conversationSettings = {
1214
- conversationType: ConversationType.General,
1215
- textEngine: TextEngines.SimpleText,
1216
- autoStart: true,
1217
- messages: [initialMessage],
1218
- model: { provider: 'google' },
1219
- tts: { voice: getRandomQuickVoice(baseLang || 'en', 'female') },
1220
- };
1221
- return conversationSettings;
1222
1367
  }
1223
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonConversationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1224
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonConversationService, providedIn: 'root' }); }
1368
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentBuilderDialogComponent, deps: [{ token: i1$4.DynamicDialogRef }], target: i0.ɵɵFactoryTarget.Component }); }
1369
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.6", type: DynamicComponentBuilderDialogComponent, isStandalone: true, selector: "dc-dynamic-component-builder-dialog", providers: [DialogService], viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, read: ViewContainerRef, static: true }], ngImport: i0, template: "<ng-container #container></ng-container>\n\n<section style=\"display: flex; gap: 10px; margin-top: 20px\">\n <button pButton label=\"Copy Code\" (click)=\"copyCode()\"></button>\n <button pButton label=\"Add to the end\" (click)=\"addToEnd()\"></button>\n</section>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i1$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }] }); }
1225
1370
  }
1226
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonConversationService, decorators: [{
1227
- type: Injectable,
1228
- args: [{
1229
- providedIn: 'root',
1230
- }]
1231
- }] });
1371
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentBuilderDialogComponent, decorators: [{
1372
+ type: Component,
1373
+ args: [{ selector: 'dc-dynamic-component-builder-dialog', standalone: true, imports: [CommonModule, ButtonModule], providers: [DialogService], template: "<ng-container #container></ng-container>\n\n<section style=\"display: flex; gap: 10px; margin-top: 20px\">\n <button pButton label=\"Copy Code\" (click)=\"copyCode()\"></button>\n <button pButton label=\"Add to the end\" (click)=\"addToEnd()\"></button>\n</section>\n" }]
1374
+ }], ctorParameters: () => [{ type: i1$4.DynamicDialogRef }], propDecorators: { container: [{
1375
+ type: ViewChild,
1376
+ args: ['container', { read: ViewContainerRef, static: true }]
1377
+ }] } });
1232
1378
 
1233
- class DCLessonRendererComponent {
1379
+ class DCLessonComponentAdderComponent {
1234
1380
  constructor() {
1235
- // --- Signal Inputs ---
1236
- this.lessonInput = input(...(ngDevMode ? [undefined, { debugName: "lessonInput" }] : [])); // Input signal for lesson object
1237
- this.lessonIdInput = input(...(ngDevMode ? [undefined, { debugName: "lessonIdInput" }] : [])); // Input signal for lesson ID
1238
- this.settings = input(...(ngDevMode ? [undefined, { debugName: "settings" }] : []));
1239
- // --- Outputs ---
1240
- this.wordClicked = new EventEmitter(); // New output event
1241
- // --- View Childs ---
1242
- this.dynamicLesson = viewChild('dynamicLesson', ...(ngDevMode ? [{ debugName: "dynamicLesson" }] : []));
1243
- // --- Services ---
1244
- this.renderer = inject(Renderer2);
1245
- this.viewContainerRef = inject(ViewContainerRef);
1246
- this.toastrService = inject(TOAST_ALERTS_TOKEN);
1247
- this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
1248
- this.uiStateService = inject(UiStateService);
1249
- this.lessonRendererService = inject(LessonRendererService);
1250
- this.lessonConversationService = inject(LessonConversationService);
1251
- this.mobileService = inject(MobileService);
1252
- // --- State Signals ---
1253
- this.lesson = signal(undefined, ...(ngDevMode ? [{ debugName: "lesson" }] : [])); // Internal lesson state signal
1254
- // --- Computed Signals ---
1255
- this.imageCover = computed(() => this.lesson()?.media?.images?.find((img) => img.type === 'cover')?.url, ...(ngDevMode ? [{ debugName: "imageCover" }] : [])); // Computed signal for imageCover
1256
- // --- Properties ---
1257
- this.components = {};
1258
- this.mainForm = new FormGroup({});
1259
- this.previousTextCoded = undefined; // Store previous value
1260
- // Effect to fetch lesson data if ID is provided and lesson object isn't
1261
- effect(async () => {
1262
- const lessonInput = this.lessonInput();
1263
- const lessonId = this.lessonIdInput();
1264
- if (lessonInput) {
1265
- this.lesson.set(lessonInput); // Use input lesson directly
1266
- }
1267
- else if (lessonId && !this.lesson()) {
1268
- // Fetch only if ID exists and internal lesson is not set
1269
- console.log(`[Renderer] Effect 1: Fetching lesson ${lessonId}`);
1270
- try {
1271
- // Consider adding a loading state signal here
1272
- const fetchedLesson = await this.lessonsService.getLesson(lessonId);
1273
- this.lesson.set(fetchedLesson);
1274
- console.log('Fetched lesson:', fetchedLesson);
1275
- }
1276
- catch (error) {
1277
- console.error(`Failed to fetch lesson with ID: ${lessonId}`, error);
1278
- this.toastrService.error({ subtitle: 'Failed to load lesson data.', title: 'Error' });
1279
- this.lesson.set(undefined); // Reset lesson on error
1280
- }
1281
- finally {
1282
- // Reset loading state signal here
1283
- }
1284
- }
1285
- else if (!lessonInput && !lessonId) {
1286
- // Handle case where neither input is provided, maybe clear the lesson?
1287
- this.lesson.set(undefined);
1288
- }
1289
- }, { allowSignalWrites: true }); // Allow signal writes inside effect
1290
- // Effect to render the lesson only when textCoded value actually changes
1291
- effect(() => {
1292
- const dynamicLessonEl = this.dynamicLesson();
1293
- const currentLesson = this.lesson(); // Read the lesson signal
1294
- if (!dynamicLessonEl || !currentLesson) {
1295
- return;
1296
- }
1297
- if (currentLesson?.format === 'markdown') {
1298
- // If markdown content exists, we clear any dynamically rendered components
1299
- // and prevent the textCoded logic from running.
1300
- if (this.previousTextCoded) {
1301
- this.lessonRendererService.clearLessonRendering(dynamicLessonEl.nativeElement);
1302
- this.mainForm = this.lessonRendererService.mainForm;
1303
- this.previousTextCoded = undefined;
1304
- }
1305
- return;
1306
- }
1307
- const newTextCoded = currentLesson?.textCoded; // Get the current textCoded value
1308
- // Quick fix to only render on changes textCode not all the lesson
1309
- if (newTextCoded !== this.previousTextCoded) {
1310
- if (newTextCoded !== undefined && newTextCoded !== null && currentLesson) {
1311
- this.components = this.lessonRendererService.renderLesson(currentLesson, this.viewContainerRef, dynamicLessonEl.nativeElement, this.renderer);
1312
- this.mainForm = this.lessonRendererService.mainForm;
1313
- }
1314
- else {
1315
- this.lessonRendererService.clearLessonRendering(dynamicLessonEl.nativeElement);
1316
- this.mainForm = this.lessonRendererService.mainForm;
1317
- }
1318
- // Update the stored previous value *after* comparison and action
1319
- this.previousTextCoded = newTextCoded;
1320
- }
1321
- else {
1322
- console.log('[Renderer] textCoded has NOT changed. Skipping render/clear.');
1323
- }
1324
- });
1325
- }
1326
- ngOnInit() {
1327
- this.lessonConversationService.initializeConversationFlow();
1328
- }
1329
- async evaluateForms() {
1330
- const result = this.lessonRendererService.evaluateForms();
1331
- if (!result) {
1332
- return;
1333
- }
1334
- const currentLesson = this.lesson();
1335
- if (!currentLesson) {
1336
- console.error('Cannot save result, lesson data is missing.');
1337
- this.toastrService.error({ subtitle: 'Cannot save result, lesson data missing.', title: 'Error' });
1338
- return;
1339
- }
1340
- const status = result.rates.score >= 0.7 ? 'passed' : 'failed';
1341
- const takenLesson = {
1342
- id: currentLesson.id,
1343
- goalCompleted: null,
1344
- score: result.rates.score,
1345
- status: status,
1346
- lastAccess: new Date(),
1347
- };
1348
- console.log('Lesson evaluation result:', takenLesson);
1349
- // TODO: Re-implement saving the taken lesson status via lessonService
1381
+ // Services
1382
+ this.dynamicComponentsBuilderService = inject(DynamicComponentsBuilderService);
1383
+ this.dialogService = inject(DialogService);
1384
+ this.componentAdded = new EventEmitter(); // Changed Output name and type
1385
+ this.onNewDynamicComponent = new EventEmitter();
1386
+ // Expose enum to the template
1387
+ this.lessonComponentEnum = LessonComponentEnum;
1350
1388
  }
1351
- // --- AI Chat Logic ---
1352
- async startAI() {
1353
- const currentLesson = this.lesson();
1354
- if (!currentLesson) {
1355
- console.error('Cannot start AI without a lesson.');
1356
- this.toastrService.error({ subtitle: 'Lesson data not available.', title: 'Cannot Start Chat' });
1357
- return;
1358
- }
1359
- const conversationSettings = await this.lessonConversationService.startAI(currentLesson, this.settings());
1360
- if (conversationSettings) {
1361
- this.uiStateService.chatDrawerVisible.set(true);
1389
+ // Logic to open component builder, now utilizing DynamicComponentsBuilderService
1390
+ openComponentBuilder(type) {
1391
+ const dialogRef = this.dynamicComponentsBuilderService.openComponentBuilder(type);
1392
+ if (dialogRef) {
1393
+ // Handle the result and emit the new event
1394
+ dialogRef.onClose.subscribe((result) => {
1395
+ if (result) {
1396
+ console.log('Component builder closed:', result);
1397
+ this.componentAdded.emit(result); // Emit the result when dialog closes successfully
1398
+ }
1399
+ });
1362
1400
  }
1363
1401
  else {
1364
- this.toastrService.error({ subtitle: 'Could not prepare the AI chat session.', title: 'Error' });
1365
- }
1366
- }
1367
- onVisibleChange(isVisible) {
1368
- if (isVisible === false) {
1369
- this.uiStateService.chatDrawerVisible.set(false);
1370
- }
1371
- }
1372
- async handleGoalCompleted(event = {}) {
1373
- console.log('Goal completed:', event);
1374
- const lesson = this.lesson();
1375
- const takenLesson = { id: lesson.id, goalCompleted: true, score: 100, status: 'finished', lastAccess: new Date() };
1376
- const res = await this.lessonsService.saveTakenLesson(takenLesson);
1377
- if (res) {
1378
- // this.lessonsMemStateService.updateUserState(res);
1402
+ // Optional: Log if the dialog couldn't be opened, though the service already logs an error.
1403
+ console.warn(`Dialog could not be opened for type via component: ${type}`);
1379
1404
  }
1380
- this.toastrService.success({ subtitle: '¡Has completado la lección! , pero puedes seguir conversando', title: '¡Muy bien, guardaremos tu progreso!' });
1381
1405
  }
1382
- onChatMessage(event) {
1383
- console.log('Received chat event:', event);
1384
- switch (event.type) {
1385
- case ChatEventType.WordClicked: {
1386
- const wordClickedData = event.payload;
1387
- console.log('Word clicked event received, emitting output:', wordClickedData);
1388
- // Removed: this.wordPopupService.showWordPopup(wordClicked); // Use the service
1389
- this.wordClicked.emit(wordClickedData); // Emit the event instead
1390
- break;
1406
+ openComponentBuilder2(type) {
1407
+ this.ref = this.dialogService.open(DynamicComponentBuilderDialogComponent, {
1408
+ header: 'Select a Component',
1409
+ width: '60vw',
1410
+ modal: true,
1411
+ closable: true,
1412
+ data: {
1413
+ type: type,
1414
+ },
1415
+ });
1416
+ this.ref.onClose.subscribe((result) => {
1417
+ if (result) {
1418
+ this.onNewDynamicComponent.emit(result);
1391
1419
  }
1392
- default:
1393
- console.log('Unhandled chat event type:', event.type);
1394
- }
1420
+ });
1395
1421
  }
1396
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1397
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: DCLessonRendererComponent, isStandalone: true, selector: "dc-lesson-renderer", inputs: { lessonInput: { classPropertyName: "lessonInput", publicName: "lessonInput", isSignal: true, isRequired: false, transformFunction: null }, lessonIdInput: { classPropertyName: "lessonIdInput", publicName: "lessonIdInput", isSignal: true, isRequired: false, transformFunction: null }, settings: { classPropertyName: "settings", publicName: "settings", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { wordClicked: "wordClicked" }, viewQueries: [{ propertyName: "dynamicLesson", first: true, predicate: ["dynamicLesson"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (lesson()?.format === 'markdown' || !lesson()?.textCoded) {\n<h3>Mostrando markdown</h3>\n\n<markdown>\n {{ lesson()?.markdown }}\n</markdown>\n} @else {\n<h5>Lesson not available</h5>\n<div>\n <div #dynamicLesson class=\"targetclass\">\n <ng-template #target></ng-template>\n </div>\n</div>\n}\n\n<br />\n<div style=\"display: flex; gap: 10px\">\n @if ((mainForm.controls | keyvalue)?.length) {\n <div>\n <p-button label=\"Calificar Lecci\u00F3n\" icon=\"pi pi-check-circle\" (click)=\"evaluateForms()\" [rounded]=\"true\"></p-button>\n </div>\n }\n\n <p-button icon=\"pi pi-verified\" [rounded]=\"true\" (click)=\"startAI()\" label=\"Repasar con IA\" />\n</div>\n<br /><br />\n\n@if(uiStateService?.chatDrawerVisible()) {\n<p-drawer\n header=\"Conversation\"\n [visible]=\"uiStateService?.chatDrawerVisible()\"\n (visibleChange)=\"onVisibleChange($event)\"\n position=\"bottom\"\n [styleClass]=\"mobileService.hasReponsiveView() ? 'p-drawer-bottom-chat-mobile' : 'p-drawer-bottom-chat'\">\n <dc-chat\n [conversationFlow]=\"lessonConversationService.conversationFlow()\"\n [conversationSettings]=\"lessonConversationService.conversationSettings()\"\n (goalCompleted)=\"handleGoalCompleted($event)\"\n (chatEvent)=\"onChatMessage($event)\"></dc-chat>\n</p-drawer>\n}\n", styles: ["::ng-deep .targetclass *:not(h1,h2,h3,h4,h5,h6){font-family:\"math\",sans-serif,system-ui,monospace!important}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: DCChatComponent, selector: "dc-chat", inputs: ["chatUserSettings", "conversationSettings", "conversationFlow", "agentCard", "parseDict"], outputs: ["chatEvent", "goalCompleted"] }, { kind: "ngmodule", type: DrawerModule }, { kind: "component", type: i2$2.Drawer, selector: "p-drawer", inputs: ["appendTo", "blockScroll", "style", "styleClass", "ariaCloseLabel", "autoZIndex", "baseZIndex", "modal", "closeButtonProps", "dismissible", "showCloseIcon", "closeOnEscape", "transitionOptions", "visible", "position", "fullScreen", "header", "maskStyle", "closable"], outputs: ["onShow", "onHide", "visibleChange"] }, { 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: "pipe", type: KeyValuePipe, name: "keyvalue" }] }); }
1422
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonComponentAdderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1423
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.6", type: DCLessonComponentAdderComponent, isStandalone: true, selector: "dc-lesson-component-adder", outputs: { componentAdded: "componentAdded", onNewDynamicComponent: "onNewDynamicComponent" }, providers: [DialogService], ngImport: i0, template: "<span>Componentes: </span>\n<div style=\"display: flex; gap: 10px; flex-wrap: wrap\">\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder2(lessonComponentEnum.Selector)\"\n pTooltip=\"Agrega un selector con multiples opciones\"\n tooltipPosition=\"bottom\">\n Selector\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder2(lessonComponentEnum.Speaker)\"\n pTooltip=\"Para que una palabra o frase sea reproducible\"\n tooltipPosition=\"bottom\">\n Speaker\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.TextWriter)\"\n pTooltip=\"Escribe una respuesta en un cuadro de texto\"\n tooltipPosition=\"bottom\">\n Text\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.VerbSummary)\"\n pTooltip=\"Muestra la informaci\u00F3n de un verbo\"\n tooltipPosition=\"bottom\">\n Verb\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.WordSummary)\"\n pTooltip=\"Muestra la informaci\u00F3n de una palabra\"\n tooltipPosition=\"bottom\">\n Palabra\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.TranslationSwitcher)\"\n pTooltip=\"Muestra el texto pero al pica cambia de idioma\"\n tooltipPosition=\"bottom\">\n Traducci\u00F3n\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.PlayWord)\"\n pTooltip=\"Muestra el texto pero al pica cambia de idioma\"\n tooltipPosition=\"bottom\">\n Play Word\n </p-button>\n <!-- Add other buttons here if needed, following the same pattern -->\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i4$2.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo"] }] }); }
1398
1424
  }
1399
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonRendererComponent, decorators: [{
1425
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonComponentAdderComponent, decorators: [{
1400
1426
  type: Component,
1401
- args: [{ selector: 'dc-lesson-renderer', standalone: true, imports: [KeyValuePipe, ButtonModule, DCChatComponent, DrawerModule, MarkdownComponent], template: "@if (lesson()?.format === 'markdown' || !lesson()?.textCoded) {\n<h3>Mostrando markdown</h3>\n\n<markdown>\n {{ lesson()?.markdown }}\n</markdown>\n} @else {\n<h5>Lesson not available</h5>\n<div>\n <div #dynamicLesson class=\"targetclass\">\n <ng-template #target></ng-template>\n </div>\n</div>\n}\n\n<br />\n<div style=\"display: flex; gap: 10px\">\n @if ((mainForm.controls | keyvalue)?.length) {\n <div>\n <p-button label=\"Calificar Lecci\u00F3n\" icon=\"pi pi-check-circle\" (click)=\"evaluateForms()\" [rounded]=\"true\"></p-button>\n </div>\n }\n\n <p-button icon=\"pi pi-verified\" [rounded]=\"true\" (click)=\"startAI()\" label=\"Repasar con IA\" />\n</div>\n<br /><br />\n\n@if(uiStateService?.chatDrawerVisible()) {\n<p-drawer\n header=\"Conversation\"\n [visible]=\"uiStateService?.chatDrawerVisible()\"\n (visibleChange)=\"onVisibleChange($event)\"\n position=\"bottom\"\n [styleClass]=\"mobileService.hasReponsiveView() ? 'p-drawer-bottom-chat-mobile' : 'p-drawer-bottom-chat'\">\n <dc-chat\n [conversationFlow]=\"lessonConversationService.conversationFlow()\"\n [conversationSettings]=\"lessonConversationService.conversationSettings()\"\n (goalCompleted)=\"handleGoalCompleted($event)\"\n (chatEvent)=\"onChatMessage($event)\"></dc-chat>\n</p-drawer>\n}\n", styles: ["::ng-deep .targetclass *:not(h1,h2,h3,h4,h5,h6){font-family:\"math\",sans-serif,system-ui,monospace!important}\n"] }]
1402
- }], ctorParameters: () => [], propDecorators: { wordClicked: [{
1427
+ args: [{ selector: 'dc-lesson-component-adder', standalone: true, imports: [CommonModule, ButtonModule, TooltipModule], providers: [DialogService], template: "<span>Componentes: </span>\n<div style=\"display: flex; gap: 10px; flex-wrap: wrap\">\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder2(lessonComponentEnum.Selector)\"\n pTooltip=\"Agrega un selector con multiples opciones\"\n tooltipPosition=\"bottom\">\n Selector\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder2(lessonComponentEnum.Speaker)\"\n pTooltip=\"Para que una palabra o frase sea reproducible\"\n tooltipPosition=\"bottom\">\n Speaker\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.TextWriter)\"\n pTooltip=\"Escribe una respuesta en un cuadro de texto\"\n tooltipPosition=\"bottom\">\n Text\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.VerbSummary)\"\n pTooltip=\"Muestra la informaci\u00F3n de un verbo\"\n tooltipPosition=\"bottom\">\n Verb\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.WordSummary)\"\n pTooltip=\"Muestra la informaci\u00F3n de una palabra\"\n tooltipPosition=\"bottom\">\n Palabra\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.TranslationSwitcher)\"\n pTooltip=\"Muestra el texto pero al pica cambia de idioma\"\n tooltipPosition=\"bottom\">\n Traducci\u00F3n\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.PlayWord)\"\n pTooltip=\"Muestra el texto pero al pica cambia de idioma\"\n tooltipPosition=\"bottom\">\n Play Word\n </p-button>\n <!-- Add other buttons here if needed, following the same pattern -->\n</div>\n" }]
1428
+ }], propDecorators: { componentAdded: [{
1429
+ type: Output
1430
+ }], onNewDynamicComponent: [{
1403
1431
  type: Output
1404
1432
  }] } });
1405
1433
 
1406
- class LessonNotionService {
1434
+ class DCLessonMetadataEditorComponent {
1407
1435
  constructor() {
1408
- this.#notionService = inject(NOTION_SERVICE_TOKEN);
1409
- this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
1436
+ // Outputs for actions
1437
+ this.saveRequest = new EventEmitter();
1438
+ this.importNotionRequest = new EventEmitter();
1439
+ this.improveNotionRequest = new EventEmitter();
1440
+ // Injected Services
1441
+ this.#lessonUtilsService = inject(LessonUtilsService);
1410
1442
  this.#toastService = inject(TOAST_ALERTS_TOKEN);
1411
- // Keep track of loading state specific to Notion operations
1412
- this.isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
1443
+ this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
1413
1444
  }
1414
- #notionService;
1445
+ // Injected Services
1446
+ #lessonUtilsService;
1415
1447
  #toastService;
1416
- /**
1417
- * Extracts the Notion Page ID from a URL.
1418
- * @param url The Notion page URL.
1419
- * @returns The extracted page ID or null if invalid.
1420
- */
1421
- extractNotionPageId(url) {
1422
- const notionIdRegex = /[a-f0-9]{32}(?=\?|$)/;
1423
- const match = url.match(notionIdRegex);
1424
- const notionId = match ? match[0] : null;
1425
- if (!notionId) {
1426
- this.#toastService.error({
1427
- title: 'URL inválido',
1428
- subtitle: 'Por favor ingresa una URL válida de Notion.',
1429
- });
1430
- return null;
1431
- }
1432
- return notionId;
1448
+ // private turndownService = new TurndownService(); // Instantiate TurndownService
1449
+ ngOnInit() {
1450
+ // console.log(this.lesson(), this.form);
1451
+ }
1452
+ onManageablePropertyChange(property, value) {
1453
+ // this.lesson.update((current) => {
1454
+ // if (!current) return undefined;
1455
+ // const updatedManageable = { ...(current.manageable ?? {}), [property]: value };
1456
+ // return { ...current, manageable: updatedManageable as IManageable };
1457
+ // });
1458
+ }
1459
+ onAuditablePropertyChange(property, value) {
1460
+ // this.lesson.update((current) => {
1461
+ // if (!current) return undefined;
1462
+ // const updatedAuditable = { ...(current.auditable ?? {}), [property]: value };
1463
+ // return { ...current, auditable: updatedAuditable as IAuditable };
1464
+ // });
1465
+ }
1466
+ // New methods to handle events with proper casting
1467
+ handlePromptInputChange(event) {
1468
+ const target = event.target;
1469
+ this.onAuditablePropertyChange('prompt', target.value);
1470
+ }
1471
+ handleStatusChange(event) {
1472
+ const target = event.target;
1473
+ this.onManageablePropertyChange('status', target.checked ? 'published' : 'draft');
1433
1474
  }
1434
1475
  /**
1435
- * Links an existing lesson with a Notion page ID by updating the lesson's extras.
1436
- * @param lesson The current lesson data.
1437
- * @param notionPageId The Notion page ID to link.
1438
- * @returns The updated lesson data from the backend or undefined on failure.
1476
+ * Generates lesson content using AI, saving the current state first.
1477
+ * Moved from DCLessonEditorComponent.
1439
1478
  */
1440
- async linkLessonWithNotion(lesson, notionPageId) {
1441
- if (!lesson || !lesson.id) {
1442
- // Ensure lesson exists and has an ID
1443
- this.#toastService.warn({ title: 'No se puede enlazar', subtitle: 'La lección debe existir y tener un ID.' });
1444
- return undefined;
1479
+ async generateByAI() {
1480
+ const currentLesson = this.lesson; // Get current value
1481
+ if (!currentLesson?.id) {
1482
+ this.#toastService.warn({ title: 'Guardar primero', subtitle: 'Guarda la lección antes de usar IA.' });
1483
+ return;
1445
1484
  }
1446
- const updatedLesson = {
1447
- ...lesson,
1448
- extensions: {
1449
- ...(lesson.extensions || {}),
1450
- extras: {
1451
- ...(lesson.extensions?.['extras'] || {}),
1452
- notionPageId: notionPageId,
1453
- },
1454
- },
1455
- };
1456
- this.isLoading.set(true);
1485
+ this.isLoadingLesson = true;
1457
1486
  try {
1458
- const savedLesson = await this.lessonsService.postLesson(updatedLesson);
1459
- this.#toastService.success({ title: 'Listo', subtitle: 'Se enlazó la lección con Notion.' });
1460
- return savedLesson;
1487
+ const rawHtmlContent = currentLesson.textCoded || '';
1488
+ if (!rawHtmlContent) {
1489
+ console.warn('No HTML content found in lesson to process. taking just description');
1490
+ this.#toastService.info({ title: 'Contenido lección desde 0', subtitle: 'Solo se usará el prompt' });
1491
+ const improvedMarkdown = await this.#lessonUtilsService.improveMDWithAI(this.lesson, 'Create content from description');
1492
+ // Convert and save the generated content
1493
+ await this._convertMarkdownToHtmlAndSave(improvedMarkdown); // Use extracted method
1494
+ }
1495
+ else {
1496
+ // Clean orphaned and Save before Improve
1497
+ const lessonToSave = this.#lessonUtilsService.cleanOrphanedComponents(currentLesson);
1498
+ const savedLesson = await this.lessonsService.postLesson(lessonToSave);
1499
+ if (!savedLesson) {
1500
+ this.#toastService.error({ title: 'Error al guardar', subtitle: 'No se pudo guardar antes de generar con IA.' });
1501
+ throw new Error('Failed to save before AI generation');
1502
+ }
1503
+ this.lesson = savedLesson;
1504
+ // Replace encoded JSON with actual text before Markdown conversion
1505
+ const processedHtmlContent = this._extractTextFromEncodedJson(rawHtmlContent);
1506
+ // Convert the processed HTML (with text instead of JSON) to Markdown
1507
+ // const markdownText = this.turndownService.turndown(processedHtmlContent);
1508
+ // Use the updated lesson signal value for AI improvement
1509
+ // const improvedMarkdown = await this.#lessonUtilsService.improveMDWithAI(this.lesson, markdownText);
1510
+ // Convert and save the improved content
1511
+ // await this._convertMarkdownToHtmlAndSave(improvedMarkdown); // Use extracted method
1512
+ }
1461
1513
  }
1462
1514
  catch (error) {
1463
- // Remove explicit type, rely on tsconfig setting
1464
- console.error('Error linking with Notion:', error);
1465
- this.#toastService.error({ title: 'Error al enlazar', subtitle: 'Ocurrió un error inesperado.' });
1466
- return undefined;
1515
+ console.error('Error during AI generation process in metadata editor:', error);
1516
+ // Service handles specific AI error toasts, maybe add a general one here if needed
1517
+ // this.#toastService.error({ title: 'Error General', subtitle: 'Ocurrió un problema durante el proceso de IA.' });
1467
1518
  }
1468
1519
  finally {
1469
- this.isLoading.set(false);
1520
+ this.isLoadingLesson = false; // Stop loading
1470
1521
  }
1471
1522
  }
1472
1523
  /**
1473
- * Handles the process of importing lesson content from Notion.
1474
- * It prompts the user for a URL if necessary, extracts the ID, fetches content,
1475
- * and potentially links the lesson if it's an existing one.
1476
- * @param currentLesson The current lesson data (can be a new or existing lesson).
1477
- * @param lessonId The current lesson ID (null if it's a new lesson).
1478
- * @returns The fetched HTML content from Notion, or null if the process fails.
1524
+ * Replaces encoded JSON blocks (~...~) in HTML with their 'settings.text' value.
1525
+ * Moved from DCLessonEditorComponent.
1526
+ * @param htmlInput The HTML string containing encoded JSON.
1527
+ * @returns The processed HTML string with text replacements.
1479
1528
  */
1480
- async importAndLinkLessonFromNotion(currentLesson, lessonId) {
1481
- if (!currentLesson)
1482
- return null;
1483
- let notionPageId = null;
1484
- if (currentLesson.extensions?.['extras']?.['notionPageId']) {
1485
- const useExisting = confirm(`Ya tenemos el id ${currentLesson.extensions?.['extras']?.['notionPageId']} ¿Quieres usar este id para importar?`);
1486
- if (useExisting) {
1487
- notionPageId = currentLesson.extensions?.['extras']?.['notionPageId'];
1529
+ _extractTextFromEncodedJson(htmlInput) {
1530
+ const jsonRegex = /~(.+?)~/g; // Global flag to replace all occurrences
1531
+ return htmlInput.replace(jsonRegex, (match, jsonCoded) => {
1532
+ try {
1533
+ // Attempt to decode HTML entities that might be present in the JSON string
1534
+ const decodedJson = jsonCoded.replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
1535
+ const data = JSON.parse(decodedJson);
1536
+ // Return the text if available, otherwise keep the original match (or an error placeholder)
1537
+ return data?.settings?.text || match;
1538
+ }
1539
+ catch (error) {
1540
+ console.error('Failed to parse JSON inside HTML:', jsonCoded, error);
1541
+ return '<!-- invalid component data -->'; // Placeholder for parsing errors
1542
+ }
1543
+ });
1544
+ }
1545
+ /**
1546
+ * Converts improved Markdown content to HTML, updates the lesson signal,
1547
+ * and saves the lesson.
1548
+ * @param improvedMarkdown The Markdown content generated by AI.
1549
+ * @throws Error if the markdown is empty/null or if saving fails.
1550
+ */
1551
+ async _convertMarkdownToHtmlAndSave(improvedMarkdown) {
1552
+ if (improvedMarkdown) {
1553
+ // Convert the improved Markdown back to HTML before setting it
1554
+ const improvedHtml = improvedMarkdown;
1555
+ // Update the signal directly
1556
+ // this.lesson.update((current) => (current ? { ...current, textCoded: improvedHtml } : undefined));
1557
+ // Save the AI-generated content
1558
+ // Ensure lesson() is not undefined before saving
1559
+ const lessonToSave = this.lesson;
1560
+ if (lessonToSave) {
1561
+ await this.lessonsService.postLesson(lessonToSave);
1562
+ this.#toastService.success({ title: 'Contenido generado', subtitle: 'Se generó y guardó el contenido con IA.' });
1488
1563
  }
1489
1564
  else {
1490
- const inputUrl = prompt('Ingresa la NUEVA URL de Notion para importar (este ID NO se guardará automáticamente si la lección ya existe)');
1491
- if (!inputUrl)
1492
- return null; // User cancelled
1493
- notionPageId = this.extractNotionPageId(inputUrl);
1565
+ console.error('Lesson signal is undefined, cannot save.');
1566
+ this.#toastService.error({ title: 'Error Interno', subtitle: 'No se pudo guardar la lección.' });
1567
+ // Throw an error to be caught by the calling method's try/catch
1568
+ throw new Error('Lesson signal is undefined during save operation.');
1494
1569
  }
1495
1570
  }
1496
1571
  else {
1497
- const inputUrl = prompt('Ingresa el URL de Notion para importar la lección (se enlazará si la lección ya existe)');
1498
- if (!inputUrl)
1499
- return null; // User cancelled
1500
- notionPageId = this.extractNotionPageId(inputUrl);
1501
- // Link automatically only if we got a valid Notion ID AND the lesson already exists (has an ID)
1502
- if (notionPageId && lessonId) {
1503
- const linkedLesson = await this.linkLessonWithNotion(currentLesson, notionPageId);
1504
- if (!linkedLesson) {
1505
- // Linking failed, maybe stop the import? Or proceed without linking?
1506
- // For now, let's stop.
1507
- this.#toastService.error({ title: 'Error de Enlace', subtitle: 'No se pudo enlazar con Notion antes de importar.' });
1508
- return null;
1509
- }
1510
- // If linking succeeded, the lesson object might have changed, but we proceed with the import using the notionPageId.
1511
- }
1512
- }
1513
- if (!notionPageId) {
1514
- this.#toastService.warn({ title: 'Sin ID de Notion', subtitle: 'No se proporcionó un ID de Notion válido para importar.' });
1515
- return null;
1516
- }
1517
- this.isLoading.set(true);
1518
- try {
1519
- this.#toastService.info({ title: 'Importando lección...', subtitle: 'Espera unos segundos' });
1520
- const md = await this.#notionService.getPageInSpecificFormat(notionPageId, NotionExportType.HTML);
1521
- console.log('Imported MD/HTML:', md);
1522
- this.#toastService.success({ title: 'Contenido Importado', subtitle: 'Contenido de Notion obtenido.' });
1523
- return md.content; // Return the fetched content
1524
- }
1525
- catch (error) {
1526
- // Remove explicit type, rely on tsconfig setting
1527
- console.error('Error importing from Notion:', error);
1528
- this.#toastService.error({ title: 'Error de importación', subtitle: 'Ocurrió un error inesperado.' });
1529
- return null; // Return null on failure
1530
- }
1531
- finally {
1532
- this.isLoading.set(false);
1572
+ // Log the error, the toast might be handled by the calling service/context
1573
+ console.error('AI generation failed or provided markdown was empty.');
1574
+ // Let the caller handle the specific user feedback if needed
1575
+ // Throw an error to be caught by the calling method's try/catch
1576
+ throw new Error('AI generation failed or the resulting markdown was empty.');
1533
1577
  }
1534
1578
  }
1535
1579
  /**
1536
- * Fetches content from the linked Notion page for AI improvement (placeholder).
1537
- * @param lesson The current lesson data.
1580
+ * Calls the LessonUtilsService to generate a description using AI
1581
+ * and updates the lesson signal if successful.
1538
1582
  */
1539
- async improveLessonWithNotionAI(lesson) {
1540
- if (!lesson)
1541
- return;
1542
- const notionId = lesson.extensions?.['extras']?.['notionPageId'];
1543
- if (!notionId) {
1544
- this.#toastService.warn({ title: 'Sin ID de Notion', subtitle: 'Enlaza la lección con Notion primero.' });
1583
+ async triggerGenerateDescriptionAI() {
1584
+ const currentLesson = this.lesson;
1585
+ if (!currentLesson) {
1586
+ this.#toastService.warn({ title: 'Lección no cargada', subtitle: 'Espera a que la lección se cargue.' });
1545
1587
  return;
1546
1588
  }
1547
- this.isLoading.set(true);
1548
- try {
1549
- this.#toastService.info({ title: 'Mejorando con IA...', subtitle: 'Obteniendo contenido de Notion.' });
1550
- const md = await this.#notionService.getPageInSpecificFormat(notionId, NotionExportType.HTML);
1551
- console.log('Content to improve:', md);
1552
- // TODO: Add actual AI improvement logic here
1553
- // e.g., call another service: await this.aiImprovementService.improve(md.content);
1554
- // Then potentially update the lesson via lessonService or return data
1555
- this.#toastService.success({ title: 'Contenido Obtenido', subtitle: 'Listo para mejorar con IA (lógica no implementada).' });
1556
- }
1557
- catch (error) {
1558
- // Remove explicit type, rely on tsconfig setting
1559
- console.error('Error improving with AI:', error);
1560
- this.#toastService.error({ title: 'Error de IA', subtitle: 'Ocurrió un error inesperado.' });
1561
- }
1562
- finally {
1563
- this.isLoading.set(false);
1589
+ const generatedDescription = await this.#lessonUtilsService.generateDescriptionWithAI(currentLesson);
1590
+ if (generatedDescription) {
1591
+ // this.form.controls['description'].setValue(generatedDescription);
1564
1592
  }
1565
1593
  }
1566
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonNotionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1567
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonNotionService, providedIn: 'root' }); }
1594
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonMetadataEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1595
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.6", type: DCLessonMetadataEditorComponent, isStandalone: true, selector: "dc-lesson-metadata-editor", inputs: { form: "form", lesson: "lesson", isLoadingLesson: "isLoadingLesson" }, outputs: { saveRequest: "saveRequest", importNotionRequest: "importNotionRequest", improveNotionRequest: "improveNotionRequest" }, ngImport: i0, template: "<div>\n <!-- <div style=\"display: flex; gap: 10px; padding: 10px\">\n <p-button label=\"Guardar\" severity=\"primary\" (click)=\"emitSaveRequest()\" />\n <p-button label=\"Importar de Notion\" severity=\"help\" (click)=\"emitImportNotionRequest()\" />\n <p-button label=\"Mejorar Notion con AI\" severity=\"help\" (click)=\"emitImproveNotionRequest()\" />\n </div> -->\n\n <div>\n <div>\n <span>Nombre de La lecci\u00F3n</span>\n <input pInputText style=\"width: 100%\" [formControl]=\"form.controls['name']\" type=\"text\" placeholder=\"Agrega un nombre\" />\n </div>\n <!-- <div>\n <span>T\u00EDtulo </span>\n <input pInputText style=\"width: 100%\" [formControl]=\"form.controls['title']\" type=\"text\" placeholder=\"Agrega un t\u00EDtulo\" />\n </div> -->\n\n <div style=\"margin-top: 4px\">\n <span>Descripci\u00F3n </span>\n <p-inputgroup>\n <input pInputText style=\"width: 100%\" [formControl]=\"form.controls['description']\" type=\"text\" placeholder=\"Agrega una descripci\u00F3n\" />\n <p-button\n icon=\"pi pi-sparkles\"\n styleClass=\"p-button-secondary p-button-outlined\"\n pTooltip=\"Generar descripci\u00F3n con IA\"\n tooltipPosition=\"top\"\n [disabled]=\"isLoadingLesson\"\n (click)=\"triggerGenerateDescriptionAI()\" />\n </p-inputgroup>\n </div>\n </div>\n\n <div style=\"display: flex; align-items: center; margin-top: 10px\">\n <input\n pInputText\n style=\"flex: auto; margin-right: 5px\"\n [value]=\"lesson?.auditable?.prompt || ''\"\n (input)=\"handlePromptInputChange($event)\"\n type=\"text\"\n placeholder=\"Prompt para IA (opcional)\" />\n <p-button severity=\"primary\" label=\"Generar con IA\" icon=\"pi pi-sparkles\" [disabled]=\"isLoadingLesson\" (click)=\"generateByAI()\" />\n </div>\n\n <p-divider />\n\n <!-- <div style=\"display: flex; align-items: center; margin-top: 10px; gap: 10px\">\n <input pInputText [value]=\"lesson?.extensions?.['level'] || ''\" type=\"number\" placeholder=\"Nivel\" style=\"width: 80px\" />\n\n @if (lesson?.extensions) {\n <div>\n {{ lesson?.extensions?.['baseLang'] | flagEmoji }} -> {{ lesson?.extensions?.['targetLang'] | flagEmoji }} Lecci\u00F3n para hablantes de\n {{ lesson?.extensions?.['baseLang'] | langDesc : 'es' }} que aprenden\n {{ lesson?.extensions?.['targetLang'] | langDesc : 'es' }}\n </div>\n }\n </div> -->\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i3.InputText, selector: "[pInputText]", inputs: ["pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i4$2.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo"] }, { kind: "ngmodule", type: // Added TooltipModule
1596
+ InputGroupModule }, { kind: "component", type: i5.InputGroup, selector: "p-inputgroup, p-inputGroup, p-input-group", inputs: ["styleClass"] }, { kind: "ngmodule", type: DividerModule }, { kind: "component", type: i6.Divider, selector: "p-divider", inputs: ["styleClass", "layout", "type", "align"] }] }); }
1568
1597
  }
1569
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonNotionService, decorators: [{
1598
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonMetadataEditorComponent, decorators: [{
1599
+ type: Component,
1600
+ args: [{ selector: 'dc-lesson-metadata-editor', standalone: true, imports: [
1601
+ CommonModule,
1602
+ FormsModule,
1603
+ ButtonModule,
1604
+ InputTextModule,
1605
+ ReactiveFormsModule,
1606
+ TooltipModule, // Added TooltipModule
1607
+ InputGroupModule,
1608
+ DividerModule,
1609
+ ], template: "<div>\n <!-- <div style=\"display: flex; gap: 10px; padding: 10px\">\n <p-button label=\"Guardar\" severity=\"primary\" (click)=\"emitSaveRequest()\" />\n <p-button label=\"Importar de Notion\" severity=\"help\" (click)=\"emitImportNotionRequest()\" />\n <p-button label=\"Mejorar Notion con AI\" severity=\"help\" (click)=\"emitImproveNotionRequest()\" />\n </div> -->\n\n <div>\n <div>\n <span>Nombre de La lecci\u00F3n</span>\n <input pInputText style=\"width: 100%\" [formControl]=\"form.controls['name']\" type=\"text\" placeholder=\"Agrega un nombre\" />\n </div>\n <!-- <div>\n <span>T\u00EDtulo </span>\n <input pInputText style=\"width: 100%\" [formControl]=\"form.controls['title']\" type=\"text\" placeholder=\"Agrega un t\u00EDtulo\" />\n </div> -->\n\n <div style=\"margin-top: 4px\">\n <span>Descripci\u00F3n </span>\n <p-inputgroup>\n <input pInputText style=\"width: 100%\" [formControl]=\"form.controls['description']\" type=\"text\" placeholder=\"Agrega una descripci\u00F3n\" />\n <p-button\n icon=\"pi pi-sparkles\"\n styleClass=\"p-button-secondary p-button-outlined\"\n pTooltip=\"Generar descripci\u00F3n con IA\"\n tooltipPosition=\"top\"\n [disabled]=\"isLoadingLesson\"\n (click)=\"triggerGenerateDescriptionAI()\" />\n </p-inputgroup>\n </div>\n </div>\n\n <div style=\"display: flex; align-items: center; margin-top: 10px\">\n <input\n pInputText\n style=\"flex: auto; margin-right: 5px\"\n [value]=\"lesson?.auditable?.prompt || ''\"\n (input)=\"handlePromptInputChange($event)\"\n type=\"text\"\n placeholder=\"Prompt para IA (opcional)\" />\n <p-button severity=\"primary\" label=\"Generar con IA\" icon=\"pi pi-sparkles\" [disabled]=\"isLoadingLesson\" (click)=\"generateByAI()\" />\n </div>\n\n <p-divider />\n\n <!-- <div style=\"display: flex; align-items: center; margin-top: 10px; gap: 10px\">\n <input pInputText [value]=\"lesson?.extensions?.['level'] || ''\" type=\"number\" placeholder=\"Nivel\" style=\"width: 80px\" />\n\n @if (lesson?.extensions) {\n <div>\n {{ lesson?.extensions?.['baseLang'] | flagEmoji }} -> {{ lesson?.extensions?.['targetLang'] | flagEmoji }} Lecci\u00F3n para hablantes de\n {{ lesson?.extensions?.['baseLang'] | langDesc : 'es' }} que aprenden\n {{ lesson?.extensions?.['targetLang'] | langDesc : 'es' }}\n </div>\n }\n </div> -->\n</div>\n" }]
1610
+ }], propDecorators: { form: [{
1611
+ type: Input,
1612
+ args: [{ required: true }]
1613
+ }], lesson: [{
1614
+ type: Input,
1615
+ args: [{ required: true }]
1616
+ }], isLoadingLesson: [{
1617
+ type: Input,
1618
+ args: [{ required: true }]
1619
+ }], saveRequest: [{
1620
+ type: Output
1621
+ }], importNotionRequest: [{
1622
+ type: Output
1623
+ }], improveNotionRequest: [{
1624
+ type: Output
1625
+ }] } });
1626
+
1627
+ class LessonFormEditorService {
1628
+ constructor() {
1629
+ this.fb = inject(FormBuilder);
1630
+ this.formUtils = inject(FormUtilsService);
1631
+ this.formatOptions = [
1632
+ { label: 'HTML', value: 'html' },
1633
+ { label: 'Markdown', value: 'markdown' },
1634
+ ];
1635
+ }
1636
+ createLessonForm() {
1637
+ return this.fb.group({
1638
+ version: ['1.0'],
1639
+ id: [''],
1640
+ name: [''],
1641
+ description: [''],
1642
+ format: ['html'],
1643
+ lang: [''],
1644
+ characterCard: [],
1645
+ conversationSettings: [],
1646
+ metaApp: [],
1647
+ conversationFlow: [],
1648
+ textCoded: [''],
1649
+ manageable: this.formUtils.createManageableFormGroup(),
1650
+ learnable: this.formUtils.createLearnableFormGroup(),
1651
+ });
1652
+ }
1653
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonFormEditorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1654
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonFormEditorService, providedIn: 'root' }); }
1655
+ }
1656
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonFormEditorService, decorators: [{
1570
1657
  type: Injectable,
1571
1658
  args: [{
1572
1659
  providedIn: 'root',
1573
1660
  }]
1574
1661
  }] });
1575
1662
 
1576
- class LessonUtilsService {
1663
+ class LessonRendererService {
1577
1664
  constructor() {
1578
- this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
1579
- this.#toastService = inject(TOAST_ALERTS_TOKEN);
1580
- this.#agentService = inject(CONVERSATION_AI_TOKEN);
1581
- this.loadingBarService = inject(LoadingBarService);
1665
+ this.dynamicComponentsService = inject(DynamicComponentsRegisterService);
1666
+ this.toastrService = inject(TOAST_ALERTS_TOKEN);
1667
+ this.components = {};
1668
+ this.mainForm = new FormGroup({});
1582
1669
  }
1583
- #toastService;
1584
- #agentService;
1585
- /**
1586
- * Validates if audios need generation for the given lesson.
1587
- * Currently logs a placeholder message.
1588
- * @param lesson The lesson object (or signal) to validate.
1589
- */
1590
- validateAudios(lesson) {
1591
- // Access lesson data directly
1592
- if (!lesson?.components) {
1593
- return;
1594
- }
1595
- // Placeholder logic - adapt as needed from original component
1596
- console.log('Validating audios for lesson:', lesson.id);
1597
- }
1598
- /**
1599
- * Updates the lesson signal with a new banner image in metadata.
1600
- * @param lessonSignal The signal holding the lesson data.
1601
- * @param imageUploaded The image data object from the upload event. Should conform to LessonImage structure partially.
1602
- */
1603
- uploadCover(lessonSignal, imageUploaded) {
1604
- lessonSignal.update((currentLesson) => {
1605
- if (!currentLesson)
1606
- return undefined;
1607
- const assets = { ...(currentLesson.assets ?? {}) };
1608
- assets.banner = imageUploaded;
1609
- return {
1610
- ...currentLesson,
1611
- assets,
1612
- };
1613
- });
1670
+ renderLesson(lessonData, viewContainerRef, dynamicLessonElement, renderer) {
1671
+ this.clearLessonRendering(dynamicLessonElement);
1672
+ console.log('Rendering lesson:', lessonData.id);
1673
+ const { htmlContent, components } = this.parseAndCreateComponents(lessonData, viewContainerRef);
1674
+ this.components = components;
1675
+ this.aggregateFormControls(this.components);
1676
+ dynamicLessonElement.innerHTML = htmlContent;
1677
+ this.injectComponentsIntoDom(this.components, renderer);
1678
+ return this.components;
1614
1679
  }
1615
- // I want to deprecate this method
1616
- /**
1617
- * @deprecated Use create new method.
1618
- * Generates lesson content using AI. Assumes the lesson is already saved.
1619
- * @param lessonId The ID of the lesson to generate content for.
1620
- * @returns The updated lesson object after AI generation, or null on failure.
1621
- */
1622
- async generateByAI(lessonId) {
1623
- if (!lessonId) {
1624
- this.#toastService.warn({ title: 'ID Requerido', subtitle: 'Se necesita un ID de lección para usar IA.' });
1625
- return null;
1680
+ clearLessonRendering(dynamicLessonElement) {
1681
+ Object.values(this.components).forEach((compRef) => compRef.destroy());
1682
+ this.components = {};
1683
+ this.mainForm = new FormGroup({});
1684
+ if (dynamicLessonElement) {
1685
+ dynamicLessonElement.innerHTML = '';
1626
1686
  }
1627
- // No need to save here, component should ensure it's saved before calling.
1628
- try {
1629
- await this.lessonsService.postGenerateByAI(lessonId);
1630
- // Re-fetch the lesson data to get AI updates
1631
- const updatedLesson = await this.lessonsService.getLesson(lessonId);
1632
- if (updatedLesson) {
1633
- this.#toastService.success({ title: 'IA completada', subtitle: 'Lección actualizada con IA.' });
1634
- return updatedLesson;
1635
- }
1636
- else {
1637
- throw new Error('Failed to fetch lesson after AI generation');
1687
+ }
1688
+ parseAndCreateComponents(lessonData, viewContainerRef) {
1689
+ const r1 = new RegExp('~(.+?)~', 'g');
1690
+ let count = 0;
1691
+ const createdComponents = {};
1692
+ const htmlContent = lessonData.textCoded.replace(r1, (_matching, jsonCoded) => {
1693
+ const componentName = `dynamicComp${count}`;
1694
+ count++;
1695
+ const componentRef = this.createComponentReferenceWithJson(jsonCoded, lessonData, viewContainerRef);
1696
+ if (!componentRef) {
1697
+ console.error(`Failed to create component for: ${jsonCoded}`);
1698
+ return '<!-- component creation failed -->';
1638
1699
  }
1639
- }
1640
- catch (error) {
1641
- // Type the error object
1642
- console.error('Error during AI generation in service:', error);
1643
- this.#toastService.error({ title: 'Error de IA', subtitle: 'No se pudo generar la lección con IA.' });
1644
- return null; // Return null in catch block
1645
- }
1646
- // Loading state should be managed by the component calling this service.
1700
+ createdComponents[componentName] = componentRef;
1701
+ return `<span id="${componentName}"></span>`;
1702
+ });
1703
+ return { htmlContent, components: createdComponents };
1647
1704
  }
1648
- /**
1649
- * Improves the provided Markdown text using AI and updates the lesson.
1650
- * Assumes the lesson is already saved before calling this.
1651
- * @param lesson The lesson object containing the prompt and other context.
1652
- * @param markdownText The Markdown text generated from the lesson's HTML content.
1653
- * @returns The improved Markdown string, or null on failure.
1654
- */
1655
- async improveMDWithAI(lesson, markdownText) {
1656
- if (!markdownText) {
1657
- this.#toastService.warn({ title: 'Texto Requerido', subtitle: 'Se necesita texto Markdown para mejorar con IA.' });
1658
- return null;
1659
- }
1705
+ createComponentReferenceWithJson(json, currentLesson, viewContainerRef) {
1660
1706
  try {
1661
- this.loadingBarService.showIndeterminate();
1662
- const textPrompt = this.lessonsService.getPrompts().content(lesson);
1663
- const messages = [{ content: textPrompt, role: ChatRole.User }];
1664
- const response = await this.#agentService.callChatCompletion({ messages, model: { provider: 'google' } });
1665
- let improvedMarkdown = response.content?.trim() ?? null;
1666
- // Remove potential markdown fences
1667
- const markdownFenceStart = '```markdown\n';
1668
- const markdownFenceEnd = '\n```';
1669
- if (improvedMarkdown?.startsWith(markdownFenceStart) && improvedMarkdown.endsWith(markdownFenceEnd)) {
1670
- improvedMarkdown = improvedMarkdown.slice(markdownFenceStart.length, -markdownFenceEnd.length);
1671
- }
1672
- else {
1673
- // Also handle cases where it might just be ``` at the start/end
1674
- const simpleFence = '```';
1675
- if (improvedMarkdown?.startsWith(simpleFence)) {
1676
- improvedMarkdown = improvedMarkdown.slice(simpleFence.length);
1677
- }
1678
- if (improvedMarkdown?.endsWith(simpleFence)) {
1679
- improvedMarkdown = improvedMarkdown.slice(0, -simpleFence.length);
1707
+ let lessonCodedConfig = JSON.parse(json);
1708
+ if (lessonCodedConfig.id && currentLesson?.dynamicComponents[lessonCodedConfig.id]) {
1709
+ const foundConfig = currentLesson.dynamicComponents[lessonCodedConfig.id];
1710
+ if (foundConfig) {
1711
+ lessonCodedConfig = { ...lessonCodedConfig, ...foundConfig };
1680
1712
  }
1681
1713
  }
1682
- // Trim any leading/trailing whitespace that might remain after removing fences
1683
- improvedMarkdown = improvedMarkdown?.trim() ?? null;
1684
- console.log('Improved Markdown:', improvedMarkdown);
1685
- return improvedMarkdown; // Return only the string
1686
- }
1687
- catch (error) {
1688
- console.error('Error during AI Markdown improvement in service:', error);
1689
- this.#toastService.error({ title: 'Error de IA', subtitle: 'No se pudo mejorar la lección con IA.' });
1690
- return null; // Return null in catch block
1691
- }
1692
- finally {
1693
- this.loadingBarService.successAndHide();
1694
- }
1695
- }
1696
- /**
1697
- * Generates a concise description for the lesson using AI based on its content.
1698
- * @param lesson The lesson object containing the content.
1699
- * @returns A promise resolving to the generated description string, or null on failure.
1700
- */
1701
- async generateDescriptionWithAI(lesson) {
1702
- if (!lesson || !lesson.textCoded) {
1703
- this.#toastService.warn({ title: 'Contenido Requerido', subtitle: 'Se necesita contenido en la lección para generar una descripción.' });
1704
- return null;
1705
- }
1706
- try {
1707
- this.loadingBarService.showIndeterminate(); // Corrected: No argument needed
1708
- // Extract plain text from the lesson's HTML content
1709
- const plainTextContent = this._extractTextFromHtml(lesson.textCoded);
1710
- if (!plainTextContent || plainTextContent.trim().length === 0) {
1711
- this.#toastService.warn({ title: 'Texto Vacío', subtitle: 'No se pudo extraer texto útil del contenido de la lección.' });
1714
+ const LessonClass = this.dynamicComponentsService.getDynamicComponentClass(lessonCodedConfig.component);
1715
+ if (!LessonClass) {
1716
+ console.error(`Component class not found for type: ${lessonCodedConfig.component}. JSON: ${json}`);
1712
1717
  return null;
1713
1718
  }
1714
- const descriptionPrompt = this.lessonsService.getPrompts().description(lesson);
1715
- const messages = [{ content: descriptionPrompt, role: ChatRole.User }];
1716
- const response = await this.#agentService.callChatCompletion({ messages, model: { provider: 'google' } });
1717
- let generatedDescription = response.content?.trim() ?? null;
1718
- // Basic cleanup (remove potential quotes if the AI wraps the response)
1719
- if (generatedDescription && generatedDescription.startsWith('"') && generatedDescription.endsWith('"')) {
1720
- generatedDescription = generatedDescription.substring(1, generatedDescription.length - 1);
1721
- }
1722
- if (generatedDescription) {
1723
- this.#toastService.success({ title: 'Descripción Generada', subtitle: 'Se generó la descripción con IA.' });
1724
- return generatedDescription;
1719
+ const componentRef = viewContainerRef.createComponent(LessonClass);
1720
+ if (lessonCodedConfig.inputs) {
1721
+ for (const key in lessonCodedConfig.inputs) {
1722
+ if (lessonCodedConfig.inputs.hasOwnProperty(key)) {
1723
+ componentRef.instance[key] = lessonCodedConfig.inputs[key];
1724
+ }
1725
+ }
1725
1726
  }
1726
- else {
1727
- throw new Error('AI did not return a valid description.');
1727
+ if (lessonCodedConfig.settings) {
1728
+ componentRef.instance.config = lessonCodedConfig;
1728
1729
  }
1730
+ return componentRef;
1729
1731
  }
1730
1732
  catch (error) {
1731
- console.error('Error during AI description generation in service:', error);
1732
- this.#toastService.error({ title: 'Error de IA', subtitle: 'No se pudo generar la descripción con IA.' });
1733
+ console.error(`Error processing component JSON: ${json}`, error);
1733
1734
  return null;
1734
1735
  }
1735
- finally {
1736
- this.loadingBarService.successAndHide();
1737
- }
1738
1736
  }
1739
- /**
1740
- * Extracts plain text from an HTML string, removing encoded JSON components.
1741
- * @param htmlInput The HTML string potentially containing encoded JSON (~...~).
1742
- * @returns The plain text representation.
1743
- */
1744
- _extractTextFromHtml(htmlInput) {
1745
- if (!htmlInput)
1746
- return '';
1747
- // 1. Replace encoded JSON blocks with their 'settings.text' or an empty string
1748
- const jsonRegex = /~(.+?)~/g;
1749
- const textWithPlaceholders = htmlInput.replace(jsonRegex, (match, jsonCoded) => {
1750
- try {
1751
- const decodedJson = jsonCoded.replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
1752
- const data = JSON.parse(decodedJson);
1753
- return data?.settings?.text || ''; // Return text or empty string if not found
1754
- }
1755
- catch (error) {
1756
- console.error('Failed to parse JSON inside HTML during text extraction:', jsonCoded, error);
1757
- return ''; // Return empty string on error
1737
+ aggregateFormControls(components) {
1738
+ const newFormControls = {};
1739
+ Object.entries(components).forEach(([name, componentRef]) => {
1740
+ if (componentRef.instance?.control instanceof FormControl) {
1741
+ newFormControls[name] = componentRef.instance.control;
1742
+ newFormControls[name].addValidators(Validators.required);
1758
1743
  }
1759
1744
  });
1760
- // 2. Strip remaining HTML tags to get plain text
1761
- // Create a temporary DOM element to parse the HTML
1762
- const tempDiv = document.createElement('div');
1763
- tempDiv.innerHTML = textWithPlaceholders;
1764
- // Use textContent to get the plain text, effectively stripping tags
1765
- // Replace multiple whitespace characters (including newlines) with a single space
1766
- return (tempDiv.textContent || tempDiv.innerText || '').replace(/\s+/g, ' ').trim();
1745
+ this.mainForm = new FormGroup(newFormControls);
1767
1746
  }
1768
- /**
1769
- * Cleans orphaned components from the lesson's dynamicComponents.
1770
- * An orphaned component is one present in dynamicComponents but its ID is not found in the textCoded HTML.
1771
- * @param lesson The lesson object to clean.
1772
- * @returns A new lesson object with orphaned components removed from dynamicComponents.
1773
- */
1774
- cleanOrphanedComponents(lesson) {
1775
- if (!lesson || !lesson.textCoded || !lesson.dynamicComponents) {
1776
- // Return the original lesson if essential parts are missing
1777
- return lesson;
1778
- }
1779
- const textCoded = lesson.textCoded;
1780
- const existingComponents = lesson.dynamicComponents;
1781
- const existingComponentIds = new Set(Object.keys(existingComponents));
1782
- // Regex to find "id":"<component_id>" within the textCoded string
1783
- const idRegex = /"id":"([^"]+)"/g;
1784
- const foundIdsInText = new Set();
1785
- let match;
1786
- while ((match = idRegex.exec(textCoded)) !== null) {
1787
- const potentialId = match[1];
1788
- // Check if the found ID actually exists in our dynamic components map
1789
- // This ensures we only count IDs that correspond to known components.
1790
- if (existingComponentIds.has(potentialId)) {
1791
- foundIdsInText.add(potentialId);
1792
- }
1793
- }
1794
- const orphanedIds = [];
1795
- const cleanedDynamicComponents = {};
1796
- // Iterate through existing component IDs
1797
- for (const componentId of existingComponentIds) {
1798
- if (foundIdsInText.has(componentId)) {
1799
- // Keep the component if its ID was found in the text
1800
- cleanedDynamicComponents[componentId] = existingComponents[componentId];
1747
+ injectComponentsIntoDom(components, renderer) {
1748
+ Object.entries(components).forEach(([name, componentRef]) => {
1749
+ const elementRef = document.getElementById(name); // Uses Document Object to Find the Node Ref if exits.
1750
+ if (elementRef) {
1751
+ this.addComponentToNode(componentRef, elementRef, renderer);
1801
1752
  }
1802
1753
  else {
1803
- // Mark as orphaned if not found
1804
- orphanedIds.push(componentId);
1754
+ console.warn(`Placeholder element with ID '${name}' not found in the DOM.`);
1805
1755
  }
1806
- }
1807
- if (orphanedIds.length > 0) {
1808
- console.warn(`[LessonUtilsService] Orphaned components detected and will be removed from lesson data (IDs not found in textCoded):`, orphanedIds);
1809
- this.#toastService.warn({
1810
- title: 'Componentes Huérfanos Detectados',
1811
- subtitle: `Se removerán ${orphanedIds.length} componentes no usados del editor.`,
1812
- // life: 5000, // Removed 'life' property as it might not be supported by ToastData
1756
+ });
1757
+ }
1758
+ addComponentToNode(componentRef, nodeDOM, renderer) {
1759
+ renderer.appendChild(nodeDOM, componentRef.location.nativeElement);
1760
+ }
1761
+ evaluateForms() {
1762
+ this.mainForm.markAllAsTouched();
1763
+ if (!this.mainForm.valid) {
1764
+ Object.keys(this.mainForm.controls).forEach((controlName) => {
1765
+ if (this.components[controlName]?.instance?.validate) {
1766
+ this.components[controlName].instance.validate();
1767
+ }
1813
1768
  });
1769
+ this.toastrService.warn({ subtitle: 'Por favor completa todos los ejercicios', title: 'Incompleto' });
1770
+ return null;
1814
1771
  }
1815
- // Return a new lesson object with the cleaned components
1816
- return {
1817
- ...lesson,
1818
- dynamicComponents: cleanedDynamicComponents,
1819
- };
1772
+ const rates = { correct: 0, incorrect: 0, score: 0 };
1773
+ Object.keys(this.mainForm.controls).forEach((controlName) => {
1774
+ const instance = this.components[controlName]?.instance;
1775
+ if (instance && typeof instance.evaluate === 'function') {
1776
+ try {
1777
+ const result = instance.evaluate();
1778
+ if (result) {
1779
+ rates.correct++;
1780
+ }
1781
+ else {
1782
+ rates.incorrect++;
1783
+ }
1784
+ }
1785
+ catch (err) {
1786
+ console.error('Error during evaluation for component:', controlName, instance, err);
1787
+ rates.incorrect++;
1788
+ }
1789
+ }
1790
+ });
1791
+ const totalQuestions = rates.correct + rates.incorrect;
1792
+ rates.score = totalQuestions > 0 ? rates.correct / totalQuestions : 0;
1793
+ const status = rates.score >= 0.7 ? 'passed' : 'failed';
1794
+ if (status === 'passed') {
1795
+ this.toastrService.success({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%.`, title: '¡Muy bien!' });
1796
+ }
1797
+ else {
1798
+ this.toastrService.warn({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%. Revisa tus respuestas.`, title: 'Casi lo logras' });
1799
+ }
1800
+ return { rates, takenLesson: null };
1820
1801
  }
1821
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonUtilsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1822
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonUtilsService, providedIn: 'root' }); }
1802
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonRendererService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1803
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonRendererService, providedIn: 'root' }); }
1823
1804
  }
1824
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonUtilsService, decorators: [{
1805
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonRendererService, decorators: [{
1825
1806
  type: Injectable,
1826
1807
  args: [{
1827
- providedIn: 'root', // Provide globally or in a specific module if preferred
1808
+ providedIn: 'root',
1828
1809
  }]
1829
1810
  }] });
1830
1811
 
1831
- class DynamicComponentsBuilderService {
1832
- #dialogService = inject(DialogService);
1833
- #toastService = inject(TOAST_ALERTS_TOKEN);
1834
- #dynamicComponentsService = inject(DynamicComponentsService);
1835
- openComponentBuilder(componentType, data = null) {
1836
- const componentToBuild = this.#dynamicComponentsService.getDynamicComponentBuilders(componentType);
1837
- if (!componentToBuild) {
1838
- console.error(`No component builder found for type: ${componentType}`);
1839
- this.#toastService.error({ title: 'Error', subtitle: `Componente desconocido: ${componentType}` });
1840
- return undefined;
1841
- }
1842
- const dialogRef = this.#dialogService.open(componentToBuild, {
1843
- inputValues: {
1844
- // inputValues was removed in newer PrimeNG versions, use 'data'
1845
- inputs: data.inputs,
1846
- id: data.id,
1812
+ const EnglishEvaluationSkill = `
1813
+ You are a virtual professor for the Polilan English learning app. Your primary role is to evaluate a student's English conversational performance.
1814
+
1815
+ You will be provided with two pieces of information for each evaluation:
1816
+ 1. **The student's English level:** This will be one of "Beginner", "Intermediate", or "Advanced".
1817
+ 2. **A conversation transcript:** This will be the dialogue between the Student and an Assistant, with each turn clearly labeled (e.g., "Student or User: ...", "Assistant: ...").
1818
+
1819
+ Your sole task is to evaluate the *Student's* contribution to the conversation. **Do NOT evaluate the Assistant's turns.**
1820
+
1821
+ Evaluate the student's performance based on the following aspects, *always keeping the student's provided level in mind* and adjusting your expectations accordingly specially with beginners give them credits just for trying:
1822
+
1823
+ * **Grammar & Syntax:** Evaluate accuracy and complexity *relative to the expected level*. (Beginners: focus on basic accuracy; Advanced: expect complex structures with high accuracy).
1824
+ * **Vocabulary & Usage:** Evaluate the range and appropriateness of words used *relative to the expected level*. (Beginners: focus on using basic words correctly; Advanced: expect varied and idiomatic language).
1825
+ * **Cohesion & Coherence:** Evaluate how well their turns connect and make sense within the conversation *relative to the expected level*. (Beginners: focus on simple, clear responses; Advanced: expect logical flow and detailed contributions).
1826
+ * **Task Completion/Relevance:** Evaluate how effectively they participate and respond to the conversation's goals or topics *relative to the expected level*.
1827
+
1828
+ Assign a rating from 0 to 3 based on how well the student performs *compared to the typical expectations for their provided level*:
1829
+
1830
+ * **0: Bad** - Performance is significantly below what is expected for this level. Many errors impede communication.
1831
+ * **1: Not Bad** - Performance is below expectations for this level, but communication is sometimes possible despite frequent errors.
1832
+ * **2: Good** - Performance meets expectations for this level, demonstrating solid understanding and usage with only occasional minor errors.
1833
+ * **3: Very Good** - Performance exceeds expectations for this level, demonstrating a strong command and potential to progress quickly.
1834
+
1835
+ Provide detailed, constructive feedback that explains the rating. Point out specific strengths and areas for improvement based on their performance *at their given level*. Use examples from the dialog if helpful. Maintain a supportive, encouraging, and educational tone appropriate for a virtual professor guiding a student at their specific stage.
1836
+ `;
1837
+
1838
+ const EngagementSkill = `
1839
+ You are an engagement assistant, start by greeting the user, asking something about the lesson, and then continue the conversation. always ask one or two friendly questions.
1840
+ that makes sense for the lesson.
1841
+ `;
1842
+ class LessonConversationService {
1843
+ constructor() {
1844
+ this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
1845
+ this.userService = inject(UserService);
1846
+ this.conversationSettings = signal(undefined, ...(ngDevMode ? [{ debugName: "conversationSettings" }] : []));
1847
+ this.conversationFlow = signal(undefined, ...(ngDevMode ? [{ debugName: "conversationFlow" }] : []));
1848
+ this.evalAgentTask = signal(undefined, ...(ngDevMode ? [{ debugName: "evalAgentTask" }] : []));
1849
+ }
1850
+ initializeConversationFlow() {
1851
+ this.evalAgentTask.set({
1852
+ systemPrompt: EnglishEvaluationSkill,
1853
+ model: { provider: 'google' },
1854
+ task: `Please evaluate the user conversation student data is: \n ${this.userService.getUserDataInformation()}`,
1855
+ expectedResponseType: EvalResultStringDefinition,
1856
+ });
1857
+ this.conversationFlow.set({
1858
+ goal: {
1859
+ enabled: true,
1860
+ task: `User is learning is taking a lesson about languages, evaluate how good are the responses.`,
1861
+ model: { quality: EModelQuality.FAST },
1862
+ },
1863
+ triggerTasks: {
1864
+ [ConversationEvents.OnUserMessage]: {
1865
+ enabled: true,
1866
+ ...this.evalAgentTask(),
1867
+ },
1868
+ },
1869
+ challenges: null,
1870
+ dynamicConditions: [
1871
+ {
1872
+ what: ConditionType.Goal,
1873
+ when: ConditionOperator.GreaterThanOrEqual,
1874
+ value: 100,
1875
+ do: [
1876
+ {
1877
+ actionType: EDoActionType.ChangePrompt,
1878
+ systemPromptType: SystemPromptType.SystemPrompt,
1879
+ prompt: 'You were talking with the user, user finish the conversation, in the next message you must try to finish the conversation and say good bye.',
1880
+ },
1881
+ {
1882
+ actionType: EDoActionType.ChangePrompt,
1883
+ systemPromptType: SystemPromptType.CharacterDescription,
1884
+ prompt: '',
1885
+ },
1886
+ {
1887
+ actionType: EDoActionType.ChangePrompt,
1888
+ systemPromptType: SystemPromptType.UserInformation,
1889
+ prompt: '',
1890
+ },
1891
+ {
1892
+ actionType: EDoActionType.ChangePrompt,
1893
+ systemPromptType: SystemPromptType.MessageExamples,
1894
+ prompt: '',
1895
+ },
1896
+ {
1897
+ actionType: EDoActionType.ChangePrompt,
1898
+ systemPromptType: SystemPromptType.ScenarioDescription,
1899
+ prompt: '',
1900
+ },
1901
+ ],
1902
+ },
1903
+ ],
1904
+ moodState: {
1905
+ enabled: false,
1906
+ useAssetStatesOnly: false,
1907
+ detectableStates: [],
1847
1908
  },
1848
- width: '80vw',
1849
- header: 'Agregar componente',
1850
- closable: true,
1851
1909
  });
1852
- return dialogRef;
1853
1910
  }
1854
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsBuilderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1855
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsBuilderService, providedIn: 'root' }); }
1856
- }
1857
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsBuilderService, decorators: [{
1858
- type: Injectable,
1859
- args: [{
1860
- providedIn: 'root',
1861
- }]
1862
- }] });
1911
+ async startAI(lesson, settings) {
1912
+ console.log('Requesting agent cards from LessonAIService...');
1913
+ try {
1914
+ const conversationSettings = await this.generateConversationSettingsForLesson(lesson, settings);
1915
+ if (conversationSettings) {
1916
+ this.conversationSettings.set(conversationSettings);
1917
+ console.log('Agent cards received and set.');
1918
+ return conversationSettings;
1919
+ }
1920
+ else {
1921
+ console.error('Failed to generate agent cards (service returned null).');
1922
+ return null;
1923
+ }
1924
+ }
1925
+ catch (error) {
1926
+ console.error('Error generating agent cards:', error);
1927
+ return null;
1928
+ }
1929
+ }
1930
+ /**
1931
+ * Builds the scenario prompt string based on lesson content and user info.
1932
+ * @param lessonText The extracted text content of the lesson.
1933
+ * @param userInformationPrompt The formatted string containing user details.
1934
+ * @returns The complete scenario prompt string.
1935
+ */
1936
+ _buildLessonInstructionsPrompt(lessonText, userInformationPrompt, settings) {
1937
+ return `
1938
+ ${settings.instructionsPrompt}
1863
1939
 
1864
- class DCLessonComponentAdderComponent {
1865
- constructor() {
1866
- // Services
1867
- this.#dynamicComponentsBuilderService = inject(DynamicComponentsBuilderService);
1868
- this.componentAdded = new EventEmitter(); // Changed Output name and type
1869
- // Expose enum to the template
1870
- this.lessonComponentEnum = LessonComponentEnum;
1940
+ <Lesson Text>
1941
+ ${lessonText}
1942
+ </Lesson Text>
1943
+
1944
+ ${EngagementSkill}
1945
+
1946
+ <User Information>
1947
+ ${userInformationPrompt}`;
1871
1948
  }
1872
- // Services
1873
- #dynamicComponentsBuilderService;
1874
- // Logic to open component builder, now utilizing DynamicComponentsBuilderService
1875
- openComponentBuilder(type) {
1876
- const dialogRef = this.#dynamicComponentsBuilderService.openComponentBuilder(type);
1877
- if (dialogRef) {
1878
- // Handle the result and emit the new event
1879
- dialogRef.onClose.subscribe((result) => {
1880
- if (result) {
1881
- console.log('Component builder closed:', result);
1882
- this.componentAdded.emit(result); // Emit the result when dialog closes successfully
1883
- }
1884
- });
1949
+ /**
1950
+ * Generates conversation settings for a lesson, using the lesson scenario as the initial prompt.
1951
+ * @param lesson The lesson data.
1952
+ * @returns An IConversationSettings object configured for the lesson scenario.
1953
+ */
1954
+ async generateConversationSettingsForLesson(lesson, settings) {
1955
+ // TODO: Consolidate user fetching logic if possible, or ensure consistency
1956
+ const baseLang = this.userService.user().settings.baseLanguage || 'en';
1957
+ let lessonText = '';
1958
+ if (lesson.textCoded) {
1959
+ lessonText = this.lessonsService.extractTextFromHtml(lesson.textCoded);
1885
1960
  }
1886
1961
  else {
1887
- // Optional: Log if the dialog couldn't be opened, though the service already logs an error.
1888
- console.warn(`Dialog could not be opened for type via component: ${type}`);
1962
+ lessonText = lesson.markdown;
1963
+ }
1964
+ const userInformationPrompt = this.userService.getUserDataInformation();
1965
+ let lessonInstructionsPrompt = this._buildLessonInstructionsPrompt(lessonText, userInformationPrompt, settings);
1966
+ if (settings.additionalPrompt) {
1967
+ lessonInstructionsPrompt += '\n\n' + settings.additionalPrompt;
1889
1968
  }
1969
+ const initialMessage = {
1970
+ role: ChatRole.System,
1971
+ content: lessonInstructionsPrompt,
1972
+ messageId: SystemPromptType.SystemPrompt,
1973
+ };
1974
+ // Use defaults similar to DEFAULT_LESSON_AGENT_CARD but adjust for prompt-based start
1975
+ const conversationSettings = {
1976
+ conversationType: ConversationType.General,
1977
+ textEngine: TextEngines.SimpleText,
1978
+ autoStart: true,
1979
+ messages: [initialMessage],
1980
+ model: { provider: 'google' },
1981
+ tts: { voice: getRandomQuickVoice(baseLang || 'en', 'female') },
1982
+ };
1983
+ return conversationSettings;
1890
1984
  }
1891
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonComponentAdderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1892
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.6", type: DCLessonComponentAdderComponent, isStandalone: true, selector: "dc-lesson-component-adder", outputs: { componentAdded: "componentAdded" }, providers: [DialogService], ngImport: i0, template: "<span>Componentes: </span>\n<div style=\"display: flex; gap: 10px; flex-wrap: wrap\">\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.Selector)\"\n pTooltip=\"Agrega un selector con multiples opciones\"\n tooltipPosition=\"bottom\">\n Selector\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.Speaker)\"\n pTooltip=\"Para que una palabra o frase sea reproducible\"\n tooltipPosition=\"bottom\">\n Speaker\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.TextWriter)\"\n pTooltip=\"Escribe una respuesta en un cuadro de texto\"\n tooltipPosition=\"bottom\">\n Text\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.VerbSummary)\"\n pTooltip=\"Muestra la informaci\u00F3n de un verbo\"\n tooltipPosition=\"bottom\">\n Verb\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.WordSummary)\"\n pTooltip=\"Muestra la informaci\u00F3n de una palabra\"\n tooltipPosition=\"bottom\">\n Palabra\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.TranslationSwitcher)\"\n pTooltip=\"Muestra el texto pero al pica cambia de idioma\"\n tooltipPosition=\"bottom\">\n Traducci\u00F3n\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.PlayWord)\"\n pTooltip=\"Muestra el texto pero al pica cambia de idioma\"\n tooltipPosition=\"bottom\">\n Play Word\n </p-button>\n <!-- Add other buttons here if needed, following the same pattern -->\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i7.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo"] }] }); }
1985
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonConversationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1986
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonConversationService, providedIn: 'root' }); }
1893
1987
  }
1894
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonComponentAdderComponent, decorators: [{
1895
- type: Component,
1896
- args: [{ selector: 'dc-lesson-component-adder', standalone: true, imports: [CommonModule, ButtonModule, TooltipModule], providers: [DialogService], template: "<span>Componentes: </span>\n<div style=\"display: flex; gap: 10px; flex-wrap: wrap\">\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.Selector)\"\n pTooltip=\"Agrega un selector con multiples opciones\"\n tooltipPosition=\"bottom\">\n Selector\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.Speaker)\"\n pTooltip=\"Para que una palabra o frase sea reproducible\"\n tooltipPosition=\"bottom\">\n Speaker\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.TextWriter)\"\n pTooltip=\"Escribe una respuesta en un cuadro de texto\"\n tooltipPosition=\"bottom\">\n Text\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.VerbSummary)\"\n pTooltip=\"Muestra la informaci\u00F3n de un verbo\"\n tooltipPosition=\"bottom\">\n Verb\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.WordSummary)\"\n pTooltip=\"Muestra la informaci\u00F3n de una palabra\"\n tooltipPosition=\"bottom\">\n Palabra\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.TranslationSwitcher)\"\n pTooltip=\"Muestra el texto pero al pica cambia de idioma\"\n tooltipPosition=\"bottom\">\n Traducci\u00F3n\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.PlayWord)\"\n pTooltip=\"Muestra el texto pero al pica cambia de idioma\"\n tooltipPosition=\"bottom\">\n Play Word\n </p-button>\n <!-- Add other buttons here if needed, following the same pattern -->\n</div>\n" }]
1897
- }], propDecorators: { componentAdded: [{
1898
- type: Output
1899
- }] } });
1988
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonConversationService, decorators: [{
1989
+ type: Injectable,
1990
+ args: [{
1991
+ providedIn: 'root',
1992
+ }]
1993
+ }] });
1900
1994
 
1901
- class DCLessonMetadataEditorComponent {
1995
+ class DCLessonRendererComponent {
1902
1996
  constructor() {
1903
- // Outputs for actions
1904
- this.saveRequest = new EventEmitter();
1905
- this.importNotionRequest = new EventEmitter();
1906
- this.improveNotionRequest = new EventEmitter();
1907
- // Injected Services
1908
- this.#lessonUtilsService = inject(LessonUtilsService);
1909
- this.#toastService = inject(TOAST_ALERTS_TOKEN);
1997
+ // --- Signal Inputs ---
1998
+ this.lessonInput = input(...(ngDevMode ? [undefined, { debugName: "lessonInput" }] : [])); // Input signal for lesson object
1999
+ this.lessonIdInput = input(...(ngDevMode ? [undefined, { debugName: "lessonIdInput" }] : [])); // Input signal for lesson ID
2000
+ this.settings = input(...(ngDevMode ? [undefined, { debugName: "settings" }] : []));
2001
+ // --- Outputs ---
2002
+ this.wordClicked = new EventEmitter(); // New output event
2003
+ // --- View Childs ---
2004
+ this.dynamicLessonRef = viewChild('dynamicLessonRef', ...(ngDevMode ? [{ debugName: "dynamicLessonRef" }] : []));
2005
+ // --- Services ---
2006
+ this.renderer = inject(Renderer2);
2007
+ this.viewContainerRef = inject(ViewContainerRef);
2008
+ this.toastrService = inject(TOAST_ALERTS_TOKEN);
1910
2009
  this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
1911
- }
1912
- // Injected Services
1913
- #lessonUtilsService;
1914
- #toastService;
1915
- // private turndownService = new TurndownService(); // Instantiate TurndownService
1916
- ngOnInit() {
1917
- // console.log(this.lesson(), this.form);
1918
- }
1919
- onManageablePropertyChange(property, value) {
1920
- // this.lesson.update((current) => {
1921
- // if (!current) return undefined;
1922
- // const updatedManageable = { ...(current.manageable ?? {}), [property]: value };
1923
- // return { ...current, manageable: updatedManageable as IManageable };
1924
- // });
1925
- }
1926
- onAuditablePropertyChange(property, value) {
1927
- // this.lesson.update((current) => {
1928
- // if (!current) return undefined;
1929
- // const updatedAuditable = { ...(current.auditable ?? {}), [property]: value };
1930
- // return { ...current, auditable: updatedAuditable as IAuditable };
1931
- // });
1932
- }
1933
- // New methods to handle events with proper casting
1934
- handlePromptInputChange(event) {
1935
- const target = event.target;
1936
- this.onAuditablePropertyChange('prompt', target.value);
1937
- }
1938
- handleStatusChange(event) {
1939
- const target = event.target;
1940
- this.onManageablePropertyChange('status', target.checked ? 'published' : 'draft');
1941
- }
1942
- /**
1943
- * Generates lesson content using AI, saving the current state first.
1944
- * Moved from DCLessonEditorComponent.
1945
- */
1946
- async generateByAI() {
1947
- const currentLesson = this.lesson; // Get current value
1948
- if (!currentLesson?.id) {
1949
- this.#toastService.warn({ title: 'Guardar primero', subtitle: 'Guarda la lección antes de usar IA.' });
1950
- return;
1951
- }
1952
- this.isLoadingLesson = true;
1953
- try {
1954
- const rawHtmlContent = currentLesson.textCoded || '';
1955
- if (!rawHtmlContent) {
1956
- console.warn('No HTML content found in lesson to process. taking just description');
1957
- this.#toastService.info({ title: 'Contenido lección desde 0', subtitle: 'Solo se usará el prompt' });
1958
- const improvedMarkdown = await this.#lessonUtilsService.improveMDWithAI(this.lesson, 'Create content from description');
1959
- // Convert and save the generated content
1960
- await this._convertMarkdownToHtmlAndSave(improvedMarkdown); // Use extracted method
2010
+ this.uiStateService = inject(UiStateService);
2011
+ this.lessonRendererService = inject(LessonRendererService);
2012
+ this.lessonConversationService = inject(LessonConversationService);
2013
+ this.mobileService = inject(MobileService);
2014
+ // --- State Signals ---
2015
+ this.lesson = signal(undefined, ...(ngDevMode ? [{ debugName: "lesson" }] : [])); // Internal lesson state signal
2016
+ // --- Computed Signals ---
2017
+ this.imageCover = computed(() => this.lesson()?.media?.images?.find((img) => img.type === 'cover')?.url, ...(ngDevMode ? [{ debugName: "imageCover" }] : [])); // Computed signal for imageCover
2018
+ // --- Properties ---
2019
+ this.components = {};
2020
+ this.mainForm = new FormGroup({});
2021
+ this.previousTextCoded = undefined; // Store previous value
2022
+ // Effect to fetch lesson data if ID is provided and lesson object isn't
2023
+ effect(async () => {
2024
+ const lessonInput = this.lessonInput();
2025
+ const lessonId = this.lessonIdInput();
2026
+ if (lessonInput) {
2027
+ this.lesson.set(lessonInput); // Use input lesson directly
2028
+ }
2029
+ else if (lessonId && !this.lesson()) {
2030
+ // Fetch only if ID exists and internal lesson is not set
2031
+ console.log(`[Renderer] Effect 1: Fetching lesson ${lessonId}`);
2032
+ try {
2033
+ // Consider adding a loading state signal here
2034
+ const fetchedLesson = await this.lessonsService.getLesson(lessonId);
2035
+ this.lesson.set(fetchedLesson);
2036
+ console.log('Fetched lesson:', fetchedLesson);
2037
+ }
2038
+ catch (error) {
2039
+ console.error(`Failed to fetch lesson with ID: ${lessonId}`, error);
2040
+ this.toastrService.error({ subtitle: 'Failed to load lesson data.', title: 'Error' });
2041
+ this.lesson.set(undefined); // Reset lesson on error
2042
+ }
2043
+ finally {
2044
+ // Reset loading state signal here
2045
+ }
2046
+ }
2047
+ else if (!lessonInput && !lessonId) {
2048
+ // Handle case where neither input is provided, maybe clear the lesson?
2049
+ this.lesson.set(undefined);
2050
+ }
2051
+ }, { allowSignalWrites: true }); // Allow signal writes inside effect
2052
+ // Effect to render the lesson only when textCoded value actually changes
2053
+ effect(() => {
2054
+ const dynamicLessonRefEl = this.dynamicLessonRef();
2055
+ const currentLesson = this.lesson(); // Read the lesson signal
2056
+ if (!dynamicLessonRefEl || !currentLesson) {
2057
+ return;
2058
+ }
2059
+ if (currentLesson?.format === 'markdown') {
2060
+ // If markdown content exists, we clear any dynamically rendered components
2061
+ // and prevent the textCoded logic from running.
2062
+ if (this.previousTextCoded) {
2063
+ this.lessonRendererService.clearLessonRendering(dynamicLessonRefEl.nativeElement);
2064
+ this.mainForm = this.lessonRendererService.mainForm;
2065
+ this.previousTextCoded = undefined;
2066
+ }
2067
+ return;
2068
+ }
2069
+ const newTextCoded = currentLesson?.textCoded; // Get the current textCoded value
2070
+ // Quick fix to only render on changes textCode not all the lesson
2071
+ if (newTextCoded !== this.previousTextCoded) {
2072
+ if (newTextCoded !== undefined && newTextCoded !== null && currentLesson) {
2073
+ this.components = this.lessonRendererService.renderLesson(currentLesson, this.viewContainerRef, dynamicLessonRefEl.nativeElement, this.renderer);
2074
+ this.mainForm = this.lessonRendererService.mainForm;
2075
+ }
2076
+ else {
2077
+ this.lessonRendererService.clearLessonRendering(dynamicLessonRefEl.nativeElement);
2078
+ this.mainForm = this.lessonRendererService.mainForm;
2079
+ }
2080
+ // Update the stored previous value *after* comparison and action
2081
+ this.previousTextCoded = newTextCoded;
1961
2082
  }
1962
2083
  else {
1963
- // Clean orphaned and Save before Improve
1964
- const lessonToSave = this.#lessonUtilsService.cleanOrphanedComponents(currentLesson);
1965
- const savedLesson = await this.lessonsService.postLesson(lessonToSave);
1966
- if (!savedLesson) {
1967
- this.#toastService.error({ title: 'Error al guardar', subtitle: 'No se pudo guardar antes de generar con IA.' });
1968
- throw new Error('Failed to save before AI generation');
1969
- }
1970
- this.lesson = savedLesson;
1971
- // Replace encoded JSON with actual text before Markdown conversion
1972
- const processedHtmlContent = this._extractTextFromEncodedJson(rawHtmlContent);
1973
- // Convert the processed HTML (with text instead of JSON) to Markdown
1974
- // const markdownText = this.turndownService.turndown(processedHtmlContent);
1975
- // Use the updated lesson signal value for AI improvement
1976
- // const improvedMarkdown = await this.#lessonUtilsService.improveMDWithAI(this.lesson, markdownText);
1977
- // Convert and save the improved content
1978
- // await this._convertMarkdownToHtmlAndSave(improvedMarkdown); // Use extracted method
2084
+ console.log('[Renderer] textCoded has NOT changed. Skipping render/clear.');
1979
2085
  }
2086
+ });
2087
+ }
2088
+ ngOnInit() {
2089
+ this.lessonConversationService.initializeConversationFlow();
2090
+ }
2091
+ async evaluateForms() {
2092
+ const result = this.lessonRendererService.evaluateForms();
2093
+ if (!result) {
2094
+ return;
1980
2095
  }
1981
- catch (error) {
1982
- console.error('Error during AI generation process in metadata editor:', error);
1983
- // Service handles specific AI error toasts, maybe add a general one here if needed
1984
- // this.#toastService.error({ title: 'Error General', subtitle: 'Ocurrió un problema durante el proceso de IA.' });
2096
+ const currentLesson = this.lesson();
2097
+ if (!currentLesson) {
2098
+ console.error('Cannot save result, lesson data is missing.');
2099
+ this.toastrService.error({ subtitle: 'Cannot save result, lesson data missing.', title: 'Error' });
2100
+ return;
1985
2101
  }
1986
- finally {
1987
- this.isLoadingLesson = false; // Stop loading
2102
+ const status = result.rates.score >= 0.7 ? 'passed' : 'failed';
2103
+ const takenLesson = {
2104
+ id: currentLesson.id,
2105
+ goalCompleted: null,
2106
+ score: result.rates.score,
2107
+ status: status,
2108
+ lastAccess: new Date(),
2109
+ };
2110
+ console.log('Lesson evaluation result:', takenLesson);
2111
+ // TODO: Re-implement saving the taken lesson status via lessonService
2112
+ }
2113
+ // --- AI Chat Logic ---
2114
+ async startAI() {
2115
+ const currentLesson = this.lesson();
2116
+ if (!currentLesson) {
2117
+ console.error('Cannot start AI without a lesson.');
2118
+ this.toastrService.error({ subtitle: 'Lesson data not available.', title: 'Cannot Start Chat' });
2119
+ return;
2120
+ }
2121
+ const conversationSettings = await this.lessonConversationService.startAI(currentLesson, this.settings());
2122
+ if (conversationSettings) {
2123
+ this.uiStateService.chatDrawerVisible.set(true);
2124
+ }
2125
+ else {
2126
+ this.toastrService.error({ subtitle: 'Could not prepare the AI chat session.', title: 'Error' });
1988
2127
  }
1989
2128
  }
1990
- /**
1991
- * Replaces encoded JSON blocks (~...~) in HTML with their 'settings.text' value.
1992
- * Moved from DCLessonEditorComponent.
1993
- * @param htmlInput The HTML string containing encoded JSON.
1994
- * @returns The processed HTML string with text replacements.
1995
- */
1996
- _extractTextFromEncodedJson(htmlInput) {
1997
- const jsonRegex = /~(.+?)~/g; // Global flag to replace all occurrences
1998
- return htmlInput.replace(jsonRegex, (match, jsonCoded) => {
1999
- try {
2000
- // Attempt to decode HTML entities that might be present in the JSON string
2001
- const decodedJson = jsonCoded.replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
2002
- const data = JSON.parse(decodedJson);
2003
- // Return the text if available, otherwise keep the original match (or an error placeholder)
2004
- return data?.settings?.text || match;
2129
+ onVisibleChange(isVisible) {
2130
+ if (isVisible === false) {
2131
+ this.uiStateService.chatDrawerVisible.set(false);
2132
+ }
2133
+ }
2134
+ async handleGoalCompleted(event = {}) {
2135
+ console.log('Goal completed:', event);
2136
+ const lesson = this.lesson();
2137
+ const takenLesson = { id: lesson.id, goalCompleted: true, score: 100, status: 'finished', lastAccess: new Date() };
2138
+ const res = await this.lessonsService.saveTakenLesson(takenLesson);
2139
+ if (res) {
2140
+ // this.lessonsMemStateService.updateUserState(res);
2141
+ }
2142
+ this.toastrService.success({ subtitle: '¡Has completado la lección! , pero puedes seguir conversando', title: '¡Muy bien, guardaremos tu progreso!' });
2143
+ }
2144
+ onChatMessage(event) {
2145
+ console.log('Received chat event:', event);
2146
+ switch (event.type) {
2147
+ case ChatEventType.WordClicked: {
2148
+ const wordClickedData = event.payload;
2149
+ console.log('Word clicked event received, emitting output:', wordClickedData);
2150
+ // Removed: this.wordPopupService.showWordPopup(wordClicked); // Use the service
2151
+ this.wordClicked.emit(wordClickedData); // Emit the event instead
2152
+ break;
2005
2153
  }
2006
- catch (error) {
2007
- console.error('Failed to parse JSON inside HTML:', jsonCoded, error);
2008
- return '<!-- invalid component data -->'; // Placeholder for parsing errors
2154
+ default:
2155
+ console.log('Unhandled chat event type:', event.type);
2156
+ }
2157
+ }
2158
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2159
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: DCLessonRendererComponent, isStandalone: true, selector: "dc-lesson-renderer", inputs: { lessonInput: { classPropertyName: "lessonInput", publicName: "lessonInput", isSignal: true, isRequired: false, transformFunction: null }, lessonIdInput: { classPropertyName: "lessonIdInput", publicName: "lessonIdInput", isSignal: true, isRequired: false, transformFunction: null }, settings: { classPropertyName: "settings", publicName: "settings", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { wordClicked: "wordClicked" }, viewQueries: [{ propertyName: "dynamicLessonRef", first: true, predicate: ["dynamicLessonRef"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (lesson()?.format === 'markdown' || !lesson()?.textCoded) {\n<h3>Mostrando markdown</h3>\n\n<markdown>\n {{ lesson()?.markdown }}\n</markdown>\n} @else {\n<h5>Lesson not available</h5>\n<div>\n <div #dynamicLessonRef class=\"targetclass\">\n <ng-template #target></ng-template>\n </div>\n</div>\n}\n\n<br />\n<div style=\"display: flex; gap: 10px\">\n @if ((mainForm.controls | keyvalue)?.length) {\n <div>\n <p-button label=\"Calificar Lecci\u00F3n\" icon=\"pi pi-check-circle\" (click)=\"evaluateForms()\" [rounded]=\"true\"></p-button>\n </div>\n }\n\n <p-button icon=\"pi pi-verified\" [rounded]=\"true\" (click)=\"startAI()\" label=\"Repasar con IA\" />\n</div>\n<br /><br />\n\n@if(uiStateService?.chatDrawerVisible()) {\n<p-drawer\n header=\"Conversation\"\n [visible]=\"uiStateService?.chatDrawerVisible()\"\n (visibleChange)=\"onVisibleChange($event)\"\n position=\"bottom\"\n [styleClass]=\"mobileService.hasReponsiveView() ? 'p-drawer-bottom-chat-mobile' : 'p-drawer-bottom-chat'\">\n <dc-chat\n [conversationFlow]=\"lessonConversationService.conversationFlow()\"\n [conversationSettings]=\"lessonConversationService.conversationSettings()\"\n (goalCompleted)=\"handleGoalCompleted($event)\"\n (chatEvent)=\"onChatMessage($event)\"></dc-chat>\n</p-drawer>\n}\n", styles: ["::ng-deep .targetclass *:not(h1,h2,h3,h4,h5,h6){font-family:\"math\",sans-serif,system-ui,monospace!important}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: DCChatComponent, selector: "dc-chat", inputs: ["chatUserSettings", "conversationSettings", "conversationFlow", "agentCard", "parseDict"], outputs: ["chatEvent", "goalCompleted"] }, { kind: "ngmodule", type: DrawerModule }, { kind: "component", type: i2$2.Drawer, selector: "p-drawer", inputs: ["appendTo", "blockScroll", "style", "styleClass", "ariaCloseLabel", "autoZIndex", "baseZIndex", "modal", "closeButtonProps", "dismissible", "showCloseIcon", "closeOnEscape", "transitionOptions", "visible", "position", "fullScreen", "header", "maskStyle", "closable"], outputs: ["onShow", "onHide", "visibleChange"] }, { 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: "pipe", type: KeyValuePipe, name: "keyvalue" }] }); }
2160
+ }
2161
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonRendererComponent, decorators: [{
2162
+ type: Component,
2163
+ args: [{ selector: 'dc-lesson-renderer', standalone: true, imports: [KeyValuePipe, ButtonModule, DCChatComponent, DrawerModule, MarkdownComponent], template: "@if (lesson()?.format === 'markdown' || !lesson()?.textCoded) {\n<h3>Mostrando markdown</h3>\n\n<markdown>\n {{ lesson()?.markdown }}\n</markdown>\n} @else {\n<h5>Lesson not available</h5>\n<div>\n <div #dynamicLessonRef class=\"targetclass\">\n <ng-template #target></ng-template>\n </div>\n</div>\n}\n\n<br />\n<div style=\"display: flex; gap: 10px\">\n @if ((mainForm.controls | keyvalue)?.length) {\n <div>\n <p-button label=\"Calificar Lecci\u00F3n\" icon=\"pi pi-check-circle\" (click)=\"evaluateForms()\" [rounded]=\"true\"></p-button>\n </div>\n }\n\n <p-button icon=\"pi pi-verified\" [rounded]=\"true\" (click)=\"startAI()\" label=\"Repasar con IA\" />\n</div>\n<br /><br />\n\n@if(uiStateService?.chatDrawerVisible()) {\n<p-drawer\n header=\"Conversation\"\n [visible]=\"uiStateService?.chatDrawerVisible()\"\n (visibleChange)=\"onVisibleChange($event)\"\n position=\"bottom\"\n [styleClass]=\"mobileService.hasReponsiveView() ? 'p-drawer-bottom-chat-mobile' : 'p-drawer-bottom-chat'\">\n <dc-chat\n [conversationFlow]=\"lessonConversationService.conversationFlow()\"\n [conversationSettings]=\"lessonConversationService.conversationSettings()\"\n (goalCompleted)=\"handleGoalCompleted($event)\"\n (chatEvent)=\"onChatMessage($event)\"></dc-chat>\n</p-drawer>\n}\n", styles: ["::ng-deep .targetclass *:not(h1,h2,h3,h4,h5,h6){font-family:\"math\",sans-serif,system-ui,monospace!important}\n"] }]
2164
+ }], ctorParameters: () => [], propDecorators: { wordClicked: [{
2165
+ type: Output
2166
+ }] } });
2167
+
2168
+ class DcLessonTextEditorComponent {
2169
+ constructor() {
2170
+ this.lessonInput = input.required(...(ngDevMode ? [{ debugName: "lessonInput" }] : []));
2171
+ this.lesson = signal({}, ...(ngDevMode ? [{ debugName: "lesson" }] : []));
2172
+ this.markdownService = inject(MarkdownService);
2173
+ this.saveLesson = new EventEmitter();
2174
+ this.updateLesson = new EventEmitter();
2175
+ this.editor = BalloonEditor;
2176
+ effect(() => {
2177
+ this.lesson.set(this.lessonInput());
2178
+ if (this.lessonInput()) {
2179
+ this.parseHttp(this.lessonInput());
2009
2180
  }
2010
2181
  });
2011
2182
  }
2012
- /**
2013
- * Converts improved Markdown content to HTML, updates the lesson signal,
2014
- * and saves the lesson.
2015
- * @param improvedMarkdown The Markdown content generated by AI.
2016
- * @throws Error if the markdown is empty/null or if saving fails.
2017
- */
2018
- async _convertMarkdownToHtmlAndSave(improvedMarkdown) {
2019
- if (improvedMarkdown) {
2020
- // Convert the improved Markdown back to HTML before setting it
2021
- const improvedHtml = improvedMarkdown;
2022
- // Update the signal directly
2023
- // this.lesson.update((current) => (current ? { ...current, textCoded: improvedHtml } : undefined));
2024
- // Save the AI-generated content
2025
- // Ensure lesson() is not undefined before saving
2026
- const lessonToSave = this.lesson;
2027
- if (lessonToSave) {
2028
- await this.lessonsService.postLesson(lessonToSave);
2029
- this.#toastService.success({ title: 'Contenido generado', subtitle: 'Se generó y guardó el contenido con IA.' });
2030
- }
2031
- else {
2032
- console.error('Lesson signal is undefined, cannot save.');
2033
- this.#toastService.error({ title: 'Error Interno', subtitle: 'No se pudo guardar la lección.' });
2034
- // Throw an error to be caught by the calling method's try/catch
2035
- throw new Error('Lesson signal is undefined during save operation.');
2036
- }
2037
- }
2038
- else {
2039
- // Log the error, the toast might be handled by the calling service/context
2040
- console.error('AI generation failed or provided markdown was empty.');
2041
- // Let the caller handle the specific user feedback if needed
2042
- // Throw an error to be caught by the calling method's try/catch
2043
- throw new Error('AI generation failed or the resulting markdown was empty.');
2044
- }
2183
+ updateHtmlTextCoded(key, value) {
2184
+ this.lesson.update((lesson) => ({ ...lesson, [key]: value }));
2185
+ this.updateLesson.emit(this.lesson());
2045
2186
  }
2046
- /**
2047
- * Calls the LessonUtilsService to generate a description using AI
2048
- * and updates the lesson signal if successful.
2049
- */
2050
- async triggerGenerateDescriptionAI() {
2051
- const currentLesson = this.lesson;
2052
- if (!currentLesson) {
2053
- this.#toastService.warn({ title: 'Lección no cargada', subtitle: 'Espera a que la lección se cargue.' });
2054
- return;
2187
+ parseHttp(entity) {
2188
+ if (this.lessonInput().format === 'markdown') {
2189
+ this.httpTemporalModel = this.markdownService.parse(entity.markdown);
2055
2190
  }
2056
- const generatedDescription = await this.#lessonUtilsService.generateDescriptionWithAI(currentLesson);
2057
- if (generatedDescription) {
2058
- // this.form.controls['description'].setValue(generatedDescription);
2191
+ else {
2192
+ this.httpTemporalModel = entity.textCoded;
2059
2193
  }
2060
2194
  }
2061
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonMetadataEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2062
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.6", type: DCLessonMetadataEditorComponent, isStandalone: true, selector: "dc-lesson-metadata-editor", inputs: { form: "form", lesson: "lesson", isLoadingLesson: "isLoadingLesson" }, outputs: { saveRequest: "saveRequest", importNotionRequest: "importNotionRequest", improveNotionRequest: "improveNotionRequest" }, ngImport: i0, template: "<div>\n <!-- <div style=\"display: flex; gap: 10px; padding: 10px\">\n <p-button label=\"Guardar\" severity=\"primary\" (click)=\"emitSaveRequest()\" />\n <p-button label=\"Importar de Notion\" severity=\"help\" (click)=\"emitImportNotionRequest()\" />\n <p-button label=\"Mejorar Notion con AI\" severity=\"help\" (click)=\"emitImproveNotionRequest()\" />\n </div> -->\n\n <div>\n <div>\n <span>Nombre de La lecci\u00F3n</span>\n <input pInputText style=\"width: 100%\" [formControl]=\"form.controls['name']\" type=\"text\" placeholder=\"Agrega un nombre\" />\n </div>\n <!-- <div>\n <span>T\u00EDtulo </span>\n <input pInputText style=\"width: 100%\" [formControl]=\"form.controls['title']\" type=\"text\" placeholder=\"Agrega un t\u00EDtulo\" />\n </div> -->\n\n <div style=\"margin-top: 4px\">\n <span>Descripci\u00F3n </span>\n <p-inputgroup>\n <input pInputText style=\"width: 100%\" [formControl]=\"form.controls['description']\" type=\"text\" placeholder=\"Agrega una descripci\u00F3n\" />\n <p-button\n icon=\"pi pi-sparkles\"\n styleClass=\"p-button-secondary p-button-outlined\"\n pTooltip=\"Generar descripci\u00F3n con IA\"\n tooltipPosition=\"top\"\n [disabled]=\"isLoadingLesson\"\n (click)=\"triggerGenerateDescriptionAI()\" />\n </p-inputgroup>\n </div>\n </div>\n\n <div style=\"display: flex; align-items: center; margin-top: 10px\">\n <input\n pInputText\n style=\"flex: auto; margin-right: 5px\"\n [value]=\"lesson?.auditable?.prompt || ''\"\n (input)=\"handlePromptInputChange($event)\"\n type=\"text\"\n placeholder=\"Prompt para IA (opcional)\" />\n <p-button severity=\"primary\" label=\"Generar con IA\" icon=\"pi pi-sparkles\" [disabled]=\"isLoadingLesson\" (click)=\"generateByAI()\" />\n </div>\n\n <p-divider />\n\n <!-- <div style=\"display: flex; align-items: center; margin-top: 10px; gap: 10px\">\n <input pInputText [value]=\"lesson?.extensions?.['level'] || ''\" type=\"number\" placeholder=\"Nivel\" style=\"width: 80px\" />\n\n @if (lesson?.extensions) {\n <div>\n {{ lesson?.extensions?.['baseLang'] | flagEmoji }} -> {{ lesson?.extensions?.['targetLang'] | flagEmoji }} Lecci\u00F3n para hablantes de\n {{ lesson?.extensions?.['baseLang'] | langDesc : 'es' }} que aprenden\n {{ lesson?.extensions?.['targetLang'] | langDesc : 'es' }}\n </div>\n }\n </div> -->\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i3.InputText, selector: "[pInputText]", inputs: ["pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i7.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo"] }, { kind: "ngmodule", type: // Added TooltipModule
2063
- InputGroupModule }, { kind: "component", type: i5.InputGroup, selector: "p-inputgroup, p-inputGroup, p-input-group", inputs: ["styleClass"] }, { kind: "ngmodule", type: DividerModule }, { kind: "component", type: i6.Divider, selector: "p-divider", inputs: ["styleClass", "layout", "type", "align"] }] }); }
2195
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DcLessonTextEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2196
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.1.6", type: DcLessonTextEditorComponent, isStandalone: true, selector: "dc-lesson-text-editor", inputs: { lessonInput: { classPropertyName: "lessonInput", publicName: "lessonInput", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { saveLesson: "saveLesson", updateLesson: "updateLesson" }, ngImport: i0, template: "<p-splitter [style]=\"{ height: '80vh' }\">\n <ng-template pTemplate>\n <ckeditor\n (keydown.control.s)=\"saveLesson.emit($event)\"\n class=\"text-editor\"\n [editor]=\"editor\"\n [(ngModel)]=\"httpTemporalModel\"\n (ngModelChange)=\"updateHtmlTextCoded('textCoded', $event)\">\n </ckeditor>\n </ng-template>\n\n <ng-template pTemplate>\n <dc-lesson-renderer class=\"text-editor\" [lessonInput]=\"lesson()\"></dc-lesson-renderer>\n </ng-template>\n</p-splitter>\n", dependencies: [{ kind: "ngmodule", type: CKEditorModule }, { kind: "component", type: i1$5.CKEditorComponent, selector: "ckeditor", inputs: ["editor", "config", "data", "tagName", "watchdog", "editorWatchdogConfig", "disableTwoWayDataBinding", "disabled"], outputs: ["ready", "change", "blur", "focus", "error"] }, { kind: "component", type: DCLessonRendererComponent, selector: "dc-lesson-renderer", inputs: ["lessonInput", "lessonIdInput", "settings"], outputs: ["wordClicked"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: SplitterModule }, { kind: "component", type: i3$1.Splitter, selector: "p-splitter", inputs: ["styleClass", "panelStyleClass", "panelStyle", "stateStorage", "stateKey", "layout", "gutterSize", "step", "minSizes", "panelSizes"], outputs: ["onResizeEnd", "onResizeStart"] }, { kind: "directive", type: i4$3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }] }); }
2064
2197
  }
2065
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonMetadataEditorComponent, decorators: [{
2198
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DcLessonTextEditorComponent, decorators: [{
2066
2199
  type: Component,
2067
- args: [{ selector: 'dc-lesson-metadata-editor', standalone: true, imports: [
2068
- CommonModule,
2069
- FormsModule,
2070
- ButtonModule,
2071
- InputTextModule,
2072
- ReactiveFormsModule,
2073
- TooltipModule, // Added TooltipModule
2074
- InputGroupModule,
2075
- DividerModule,
2076
- ], template: "<div>\n <!-- <div style=\"display: flex; gap: 10px; padding: 10px\">\n <p-button label=\"Guardar\" severity=\"primary\" (click)=\"emitSaveRequest()\" />\n <p-button label=\"Importar de Notion\" severity=\"help\" (click)=\"emitImportNotionRequest()\" />\n <p-button label=\"Mejorar Notion con AI\" severity=\"help\" (click)=\"emitImproveNotionRequest()\" />\n </div> -->\n\n <div>\n <div>\n <span>Nombre de La lecci\u00F3n</span>\n <input pInputText style=\"width: 100%\" [formControl]=\"form.controls['name']\" type=\"text\" placeholder=\"Agrega un nombre\" />\n </div>\n <!-- <div>\n <span>T\u00EDtulo </span>\n <input pInputText style=\"width: 100%\" [formControl]=\"form.controls['title']\" type=\"text\" placeholder=\"Agrega un t\u00EDtulo\" />\n </div> -->\n\n <div style=\"margin-top: 4px\">\n <span>Descripci\u00F3n </span>\n <p-inputgroup>\n <input pInputText style=\"width: 100%\" [formControl]=\"form.controls['description']\" type=\"text\" placeholder=\"Agrega una descripci\u00F3n\" />\n <p-button\n icon=\"pi pi-sparkles\"\n styleClass=\"p-button-secondary p-button-outlined\"\n pTooltip=\"Generar descripci\u00F3n con IA\"\n tooltipPosition=\"top\"\n [disabled]=\"isLoadingLesson\"\n (click)=\"triggerGenerateDescriptionAI()\" />\n </p-inputgroup>\n </div>\n </div>\n\n <div style=\"display: flex; align-items: center; margin-top: 10px\">\n <input\n pInputText\n style=\"flex: auto; margin-right: 5px\"\n [value]=\"lesson?.auditable?.prompt || ''\"\n (input)=\"handlePromptInputChange($event)\"\n type=\"text\"\n placeholder=\"Prompt para IA (opcional)\" />\n <p-button severity=\"primary\" label=\"Generar con IA\" icon=\"pi pi-sparkles\" [disabled]=\"isLoadingLesson\" (click)=\"generateByAI()\" />\n </div>\n\n <p-divider />\n\n <!-- <div style=\"display: flex; align-items: center; margin-top: 10px; gap: 10px\">\n <input pInputText [value]=\"lesson?.extensions?.['level'] || ''\" type=\"number\" placeholder=\"Nivel\" style=\"width: 80px\" />\n\n @if (lesson?.extensions) {\n <div>\n {{ lesson?.extensions?.['baseLang'] | flagEmoji }} -> {{ lesson?.extensions?.['targetLang'] | flagEmoji }} Lecci\u00F3n para hablantes de\n {{ lesson?.extensions?.['baseLang'] | langDesc : 'es' }} que aprenden\n {{ lesson?.extensions?.['targetLang'] | langDesc : 'es' }}\n </div>\n }\n </div> -->\n</div>\n" }]
2077
- }], propDecorators: { form: [{
2078
- type: Input,
2079
- args: [{ required: true }]
2080
- }], lesson: [{
2081
- type: Input,
2082
- args: [{ required: true }]
2083
- }], isLoadingLesson: [{
2084
- type: Input,
2085
- args: [{ required: true }]
2086
- }], saveRequest: [{
2087
- type: Output
2088
- }], importNotionRequest: [{
2200
+ args: [{ selector: 'dc-lesson-text-editor', standalone: true, imports: [CKEditorModule, DCLessonRendererComponent, FormsModule, SplitterModule], template: "<p-splitter [style]=\"{ height: '80vh' }\">\n <ng-template pTemplate>\n <ckeditor\n (keydown.control.s)=\"saveLesson.emit($event)\"\n class=\"text-editor\"\n [editor]=\"editor\"\n [(ngModel)]=\"httpTemporalModel\"\n (ngModelChange)=\"updateHtmlTextCoded('textCoded', $event)\">\n </ckeditor>\n </ng-template>\n\n <ng-template pTemplate>\n <dc-lesson-renderer class=\"text-editor\" [lessonInput]=\"lesson()\"></dc-lesson-renderer>\n </ng-template>\n</p-splitter>\n" }]
2201
+ }], ctorParameters: () => [], propDecorators: { saveLesson: [{
2089
2202
  type: Output
2090
- }], improveNotionRequest: [{
2203
+ }], updateLesson: [{
2091
2204
  type: Output
2092
2205
  }] } });
2093
2206
 
2094
- class LessonFormEditorService {
2207
+ class DcDynamicComponentPreviewComponent {
2095
2208
  constructor() {
2096
- this.fb = inject(FormBuilder);
2097
- this.formUtils = inject(FormUtilsService);
2098
- this.formatOptions = [
2099
- { label: 'HTML', value: 'html' },
2100
- { label: 'Markdown', value: 'markdown' },
2101
- ];
2209
+ this.showComponentDetails = new EventEmitter();
2210
+ this.editComponent = new EventEmitter();
2102
2211
  }
2103
- createLessonForm() {
2104
- return this.fb.group({
2105
- version: ['1.0'],
2106
- id: [''],
2107
- name: [''],
2108
- description: [''],
2109
- format: ['html'],
2110
- lang: [''],
2111
- characterCard: [],
2112
- conversationSettings: [],
2113
- metaApp: [],
2114
- conversationFlow: [],
2115
- textCoded: [''],
2116
- manageable: this.formUtils.createManageableFormGroup(),
2117
- learnable: this.formUtils.createLearnableFormGroup(),
2118
- });
2212
+ onShowComponentDetails(component) {
2213
+ this.showComponentDetails.emit(component);
2119
2214
  }
2120
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonFormEditorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2121
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonFormEditorService, providedIn: 'root' }); }
2215
+ onEditComponent(component) {
2216
+ this.editComponent.emit(component);
2217
+ }
2218
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DcDynamicComponentPreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2219
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: DcDynamicComponentPreviewComponent, isStandalone: true, selector: "dc-dynamic-component-preview", inputs: { dynamicComponentsArray: "dynamicComponentsArray" }, outputs: { showComponentDetails: "showComponentDetails", editComponent: "editComponent" }, ngImport: i0, template: "<!-- Display Added Components -->\n<div class=\"added-components-list\" style=\"margin-top: 15px; margin-bottom: 15px\">\n <h4>Componentes Agregados:</h4>\n @if (dynamicComponentsArray().length > 0) {\n <ul>\n @for (comp of dynamicComponentsArray(); track comp.id) {\n <li>\n ID: {{ comp.id }} - Tipo: {{ comp.component }}\n\n <button pButton icon=\"pi pi-info\" (click)=\"onShowComponentDetails(comp)\"></button>\n <p-button icon=\"pi pi-pencil\" [rounded]=\"true\" severity=\"warn\" (click)=\"onEditComponent(comp)\"></p-button>\n </li>\n }\n </ul>\n } @else {\n <p>A\u00FAn no se han agregado componentes.</p>\n }\n</div>\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i1$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }] }); }
2122
2220
  }
2123
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonFormEditorService, decorators: [{
2124
- type: Injectable,
2125
- args: [{
2126
- providedIn: 'root',
2127
- }]
2128
- }] });
2221
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DcDynamicComponentPreviewComponent, decorators: [{
2222
+ type: Component,
2223
+ args: [{ selector: 'dc-dynamic-component-preview', standalone: true, imports: [CommonModule, ButtonModule], template: "<!-- Display Added Components -->\n<div class=\"added-components-list\" style=\"margin-top: 15px; margin-bottom: 15px\">\n <h4>Componentes Agregados:</h4>\n @if (dynamicComponentsArray().length > 0) {\n <ul>\n @for (comp of dynamicComponentsArray(); track comp.id) {\n <li>\n ID: {{ comp.id }} - Tipo: {{ comp.component }}\n\n <button pButton icon=\"pi pi-info\" (click)=\"onShowComponentDetails(comp)\"></button>\n <p-button icon=\"pi pi-pencil\" [rounded]=\"true\" severity=\"warn\" (click)=\"onEditComponent(comp)\"></p-button>\n </li>\n }\n </ul>\n } @else {\n <p>A\u00FAn no se han agregado componentes.</p>\n }\n</div>\n", styles: [":host{display:block}\n"] }]
2224
+ }], propDecorators: { dynamicComponentsArray: [{
2225
+ type: Input
2226
+ }], showComponentDetails: [{
2227
+ type: Output
2228
+ }], editComponent: [{
2229
+ type: Output
2230
+ }] } });
2129
2231
 
2130
2232
  class DCLessonEditorComponent extends EntityBaseFormComponent {
2131
2233
  constructor() {
2132
2234
  super(...arguments);
2133
2235
  this.lessonFormEditorService = inject(LessonFormEditorService);
2134
- this.markdownService = inject(MarkdownService);
2135
2236
  this.form = this.lessonFormEditorService.createLessonForm();
2136
2237
  this.formatOptions = this.lessonFormEditorService.formatOptions;
2137
2238
  this.entityCommunicationService = inject(LESSONS_TOKEN);
2138
- this.htmlTemporal = '';
2139
2239
  // Services
2140
2240
  this.activatedRoute = inject(ActivatedRoute); // Re-inject as it's needed for navigation
2141
2241
  this.lessonNotionService = inject(LessonNotionService);
@@ -2144,20 +2244,19 @@ class DCLessonEditorComponent extends EntityBaseFormComponent {
2144
2244
  this.loadingBarService = inject(LoadingBarService);
2145
2245
  this.defaultLessonsService = inject(DefaultLessonsService);
2146
2246
  this.promptService = inject(PromptService);
2147
- this.ngxVertexService = inject(NgxVertexService);
2247
+ this.ngxVertexService = inject(NgxAiServicesService);
2148
2248
  this.dynamicComponentsBuilderService = inject(DynamicComponentsBuilderService);
2149
2249
  this.isLoadingLesson = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingLesson" }] : []));
2150
2250
  // Computed signal to get dynamic components as an array for easier iteration in the template
2151
2251
  this.dynamicComponentsArray = computed(() => {
2152
- // const currentLesson = this.lesson();
2153
- // if (currentLesson?.dynamicComponents) {
2154
- // return Object.values(currentLesson.dynamicComponents);
2155
- // }
2252
+ const currentLesson = this.entity();
2253
+ if (currentLesson?.dynamicComponents) {
2254
+ return Object.values(currentLesson.dynamicComponents);
2255
+ }
2156
2256
  return []; // Return empty array if no lesson or no dynamic components
2157
2257
  }, ...(ngDevMode ? [{ debugName: "dynamicComponentsArray" }] : []));
2158
2258
  // States
2159
2259
  this.components = {}; // Current Dynamic components
2160
- this.editor = BalloonEditor;
2161
2260
  this.lessonComponentEnum = LessonComponentEnum;
2162
2261
  this.coverStorageSettings = {
2163
2262
  path: `lessons/${this.entityId()}/covers`,
@@ -2166,14 +2265,6 @@ class DCLessonEditorComponent extends EntityBaseFormComponent {
2166
2265
  };
2167
2266
  }
2168
2267
  patchForm(entity) {
2169
- console.log(this.form);
2170
- if (this.entity().format === 'markdown') {
2171
- this.htmlTemporal = this.markdownService.parse(entity.markdown);
2172
- }
2173
- else {
2174
- this.htmlTemporal = entity.textCoded;
2175
- }
2176
- // console.log(this.htmlTemporal);
2177
2268
  this.form.patchValue(entity);
2178
2269
  }
2179
2270
  /**
@@ -2190,20 +2281,19 @@ class DCLessonEditorComponent extends EntityBaseFormComponent {
2190
2281
  return { ...currentLesson, [property]: value };
2191
2282
  });
2192
2283
  }
2193
- updateHtmlTextCoded(_, value) {
2284
+ onAssetsChange(updatedAssets) {
2285
+ console.log(updatedAssets);
2194
2286
  this.entity.update((currentLesson) => {
2195
2287
  if (!currentLesson)
2196
2288
  return undefined;
2197
- return { ...currentLesson, textCoded: value };
2289
+ return { ...currentLesson, assets: updatedAssets };
2198
2290
  });
2199
- this.form.controls.textCoded.setValue(value);
2200
2291
  }
2201
- onAssetsChange(updatedAssets) {
2202
- console.log(updatedAssets);
2292
+ updateLesson(event) {
2203
2293
  this.entity.update((currentLesson) => {
2204
2294
  if (!currentLesson)
2205
2295
  return undefined;
2206
- return { ...currentLesson, assets: updatedAssets };
2296
+ return { ...currentLesson, ...event };
2207
2297
  });
2208
2298
  }
2209
2299
  async saveLesson(event) {
@@ -2278,6 +2368,28 @@ class DCLessonEditorComponent extends EntityBaseFormComponent {
2278
2368
  async improveNotionWithAI() {
2279
2369
  await this.lessonNotionService.improveLessonWithNotionAI(this.entity());
2280
2370
  }
2371
+ onNewDynamicComponent(result) {
2372
+ const newComponentConfig = result?.obj;
2373
+ if (newComponentConfig?.id) {
2374
+ this.entity.update((currentLesson) => {
2375
+ if (!currentLesson)
2376
+ return undefined;
2377
+ const newDynamicComponent = {
2378
+ id: newComponentConfig.id,
2379
+ component: newComponentConfig.component,
2380
+ inputs: newComponentConfig.inputs || {},
2381
+ config: newComponentConfig,
2382
+ };
2383
+ const currentDynamicComponents = currentLesson.dynamicComponents || {};
2384
+ const updatedDynamicComponents = {
2385
+ ...currentDynamicComponents,
2386
+ [newComponentConfig.id]: newDynamicComponent,
2387
+ };
2388
+ const updatedTextCoded = (currentLesson.textCoded || '') + result.str;
2389
+ return { ...currentLesson, dynamicComponents: updatedDynamicComponents, textCoded: updatedTextCoded };
2390
+ });
2391
+ }
2392
+ }
2281
2393
  showComponentDetails(data) {
2282
2394
  alert('showComponentDetails' + JSON.stringify(data));
2283
2395
  }
@@ -2316,20 +2428,17 @@ class DCLessonEditorComponent extends EntityBaseFormComponent {
2316
2428
  this.entityCommunicationService.partialUpdate(this.entityId(), { assets: event.assets });
2317
2429
  }
2318
2430
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonEditorComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
2319
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: DCLessonEditorComponent, isStandalone: true, selector: "dc-lesson-editor", providers: [LessonNotionService], usesInheritance: true, ngImport: i0, template: "<div class=\"p-grid\">\n <div class=\"p-col-4\">\n <assets-loader\n [assets]=\"entity()?.assets\"\n storagePath=\"lessons/{{ entityId() }}\"\n (assetsChange)=\"onAssetsChange($event)\"\n (assetUpdate)=\"onAssetUpdate($event)\"></assets-loader>\n </div>\n\n <!-- <div class=\"p-col-4\">\n <h3>Auditable</h3>\n <dc-auditable-viewer [data]=\"entity()?.auditable\"></dc-auditable-viewer>\n </div> -->\n <div class=\"p-col-4\">\n <h3>Gesti\u00F3n</h3>\n <dc-manageable-form [form]=\"form.controls.manageable\"></dc-manageable-form>\n </div>\n <div class=\"p-col-4\">\n <h3>Categorias y Etiquetas</h3>\n <dc-learnable-form [form]=\"form.controls.learnable\"></dc-learnable-form>\n </div>\n <!-- <div class=\"p-col-4\">\n <h3>Reactions</h3>\n <dc-reactions-viewer [data]=\"entity()?.reactions\"></dc-reactions-viewer>\n </div> -->\n\n <!-- <div class=\"p-col-4\">\n <h3>Extensions</h3>\n <dc-extensions-viewer [data]=\"entity()?.extensions\"></dc-extensions-viewer>\n </div> -->\n</div>\n\n<!-- Lesson Metadata Editor -->\n<br />\n<div [formGroup]=\"form\">\n <p-selectButton [options]=\"formatOptions\" formControlName=\"format\" optionLabel=\"label\" optionValue=\"value\" />\n</div>\n<dc-lesson-metadata-editor [lesson]=\"entity()\" [form]=\"form\" [isLoadingLesson]=\"isLoadingLesson()\"></dc-lesson-metadata-editor>\n\n<div style=\"margin-top: 30px\"></div>\n\n<!-- Component Adder -->\n<dc-lesson-component-adder (componentAdded)=\"onComponentAdded($event)\"></dc-lesson-component-adder>\n\n<!-- Display Added Components -->\n<div class=\"added-components-list\" style=\"margin-top: 15px; margin-bottom: 15px\">\n <h4>Componentes Agregados:</h4>\n @if (dynamicComponentsArray().length > 0) {\n <ul>\n @for (comp of dynamicComponentsArray(); track comp.id) {\n <li\n >ID: {{ comp.id }} - Tipo: {{ comp.component }}\n\n <button pButton icon=\"pi pi-info\" (click)=\"showComponentDetails(comp)\"></button>\n <p-button icon=\"pi pi-pencil\" [rounded]=\"true\" severity=\"warn\" (click)=\"editComponent(comp)\"></p-button>\n </li>\n }\n </ul>\n } @else {\n <p>A\u00FAn no se han agregado componentes.</p>\n }\n</div>\n\n<hr />\n\n<!-- Text Editor and Renderer -->\n<p-splitter [style]=\"{ height: '80vh' }\">\n <ng-template pTemplate>\n <ckeditor\n (keydown.control.s)=\"saveLesson($event)\"\n class=\"text-editor\"\n [editor]=\"editor\"\n [ngModel]=\"htmlTemporal\"\n (ngModelChange)=\"updateHtmlTextCoded('textCoded', $event)\">\n </ckeditor>\n </ng-template>\n\n <ng-template pTemplate>\n <dc-lesson-renderer class=\"text-editor\" [lessonInput]=\"entity()\"></dc-lesson-renderer>\n </ng-template>\n</p-splitter>\n\n<div class=\"float-button\">\n <p-button icon=\"pi pi-save\" (click)=\"save()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n</div>\n\n<hr />\n\n<!-- <p-dialog header=\"Prompts\" [modal]=\"true\" [(visible)]=\"promptsVisible\" [style]=\"{ width: '70%' }\">\n <div>\n <h1>Banner</h1>\n <p>{{ prompts?.banner }}</p>\n <h1>Contenido</h1>\n <p>{{ prompts?.content }}</p>\n <h1>Descripci\u00F3n</h1>\n <p>{{ prompts?.description }}</p>\n </div>\n</p-dialog> -->\n", styles: [".btn{padding:.5rem 1rem;border-radius:4px;border:1px solid transparent;cursor:pointer}.generate-banner-btn{position:absolute;right:10px;top:10px}.prompt-visual{position:absolute;left:10px;bottom:10px}.btn-primary{background-color:#007bff;color:#fff}.btn-outline-primary{border-color:#007bff;color:#007bff}.btn-secondary{background-color:#6c757d;color:#fff}.btn-outline-secondary{border-color:#6c757d;color:#6c757d}.btn-rounded{border-radius:50%}.form-control{padding:.375rem .75rem;border:1px solid #ced4da;border-radius:.25rem}.splitter{display:flex;gap:1rem}.splitter-panel{flex:1}.checkbox-container{display:inline-flex;align-items:center;gap:.5rem;cursor:pointer}.mr-2{margin-right:.5rem}.header-cover{width:100%;height:250px;object-fit:cover;position:relative;border-radius:8px}.float-button{position:fixed;bottom:3.5rem;right:2rem;z-index:1000;display:flex;gap:1px}.float-button :host ::ng-deep .p-button{width:4rem;height:4rem;border-radius:50%}.text-editor{width:-webkit-fill-available;overflow-y:auto}:host ::ng-deep .p-inputtext{background:#fff3}::ng-deep .p-splitter .p-splitterpanel{overflow:auto!important}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i1$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i2$3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: CKEditorModule }, { kind: "component", type: i3$1.CKEditorComponent, selector: "ckeditor", inputs: ["editor", "config", "data", "tagName", "watchdog", "editorWatchdogConfig", "disableTwoWayDataBinding", "disabled"], outputs: ["ready", "change", "blur", "focus", "error"] }, { kind: "component", type: DCLessonRendererComponent, selector: "dc-lesson-renderer", inputs: ["lessonInput", "lessonIdInput", "settings"], outputs: ["wordClicked"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: SelectButtonModule }, { kind: "component", type: i5$1.SelectButton, selector: "p-selectButton, p-selectbutton, p-select-button", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "unselectable", "tabindex", "multiple", "allowEmpty", "styleClass", "ariaLabelledBy", "dataKey", "autofocus", "size", "fluid"], outputs: ["onOptionClick", "onChange"] }, { kind: "ngmodule", type: SplitterModule }, { kind: "component", type: i6$1.Splitter, selector: "p-splitter", inputs: ["styleClass", "panelStyleClass", "panelStyle", "stateStorage", "stateKey", "layout", "gutterSize", "step", "minSizes", "panelSizes"], outputs: ["onResizeEnd", "onResizeStart"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i7.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo"] }, { kind: "component", type: DCLessonComponentAdderComponent, selector: "dc-lesson-component-adder", outputs: ["componentAdded"] }, { kind: "ngmodule", type: // Add the component adder here
2320
- DialogModule }, { kind: "component", type: AssetsLoaderComponent, selector: "assets-loader", inputs: ["assets", "storagePath"], outputs: ["assetsChange", "assetUpdate", "onFileSelected"] }, { kind: "component", type: DCLessonMetadataEditorComponent, selector: "dc-lesson-metadata-editor", inputs: ["form", "lesson", "isLoadingLesson"], outputs: ["saveRequest", "importNotionRequest", "improveNotionRequest"] }, { kind: "component", type: DcManageableFormComponent, selector: "dc-manageable-form", inputs: ["form"] }, { kind: "component", type: DcLearnableFormComponent, selector: "dc-learnable-form", inputs: ["form"] }] }); }
2431
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.6", type: DCLessonEditorComponent, isStandalone: true, selector: "dc-lesson-editor", usesInheritance: true, ngImport: i0, template: "<div class=\"p-grid\">\n <div class=\"p-col-4\">\n <assets-loader\n [assets]=\"entity()?.assets\"\n storagePath=\"lessons/{{ entityId() }}\"\n (assetsChange)=\"onAssetsChange($event)\"\n (assetUpdate)=\"onAssetUpdate($event)\"></assets-loader>\n </div>\n\n <div class=\"p-col-4\">\n <h3>Gesti\u00F3n</h3>\n <dc-manageable-form [form]=\"form.controls.manageable\"></dc-manageable-form>\n </div>\n <div class=\"p-col-4\">\n <h3>Categorias y Etiquetas</h3>\n <dc-learnable-form [form]=\"form.controls.learnable\"></dc-learnable-form>\n </div>\n</div>\n\n<!-- Lesson Metadata Editor -->\n<br />\n<div [formGroup]=\"form\">\n <p-selectButton [options]=\"formatOptions\" formControlName=\"format\" optionLabel=\"label\" optionValue=\"value\" />\n</div>\n<dc-lesson-metadata-editor [lesson]=\"entity()\" [form]=\"form\" [isLoadingLesson]=\"isLoadingLesson()\"></dc-lesson-metadata-editor>\n\n<div style=\"margin-top: 30px\"></div>\n\n<!-- Component Adder -->\n<dc-lesson-component-adder (componentAdded)=\"onComponentAdded($event)\" (onNewDynamicComponent)=\"onNewDynamicComponent($event)\"></dc-lesson-component-adder>\n\n<dc-dynamic-component-preview\n [dynamicComponentsArray]=\"dynamicComponentsArray\"\n (showComponentDetails)=\"showComponentDetails($event)\"\n (editComponent)=\"editComponent($event)\"></dc-dynamic-component-preview>\n\n<hr />\n\n<!-- Text Editor and Renderer -->\n<dc-lesson-text-editor [lessonInput]=\"entity()\" (updateLesson)=\"updateLesson($event)\"></dc-lesson-text-editor>\n\n<div class=\"float-button\">\n <p-button icon=\"pi pi-save\" (click)=\"saveLesson()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n</div>\n\n<hr />\n", styles: [".btn{padding:.5rem 1rem;border-radius:4px;border:1px solid transparent;cursor:pointer}.generate-banner-btn{position:absolute;right:10px;top:10px}.prompt-visual{position:absolute;left:10px;bottom:10px}.btn-primary{background-color:#007bff;color:#fff}.btn-outline-primary{border-color:#007bff;color:#007bff}.btn-secondary{background-color:#6c757d;color:#fff}.btn-outline-secondary{border-color:#6c757d;color:#6c757d}.btn-rounded{border-radius:50%}.form-control{padding:.375rem .75rem;border:1px solid #ced4da;border-radius:.25rem}.splitter{display:flex;gap:1rem}.splitter-panel{flex:1}.checkbox-container{display:inline-flex;align-items:center;gap:.5rem;cursor:pointer}.mr-2{margin-right:.5rem}.header-cover{width:100%;height:250px;object-fit:cover;position:relative;border-radius:8px}.float-button{position:fixed;bottom:3.5rem;right:2rem;z-index:1000;display:flex;gap:1px}.float-button :host ::ng-deep .p-button{width:4rem;height:4rem;border-radius:50%}.text-editor{width:-webkit-fill-available;overflow-y:auto}:host ::ng-deep .p-inputtext{background:#fff3}::ng-deep .p-splitter .p-splitterpanel{overflow:auto!important}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: InputTextModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: SelectButtonModule }, { kind: "component", type: i3$2.SelectButton, selector: "p-selectButton, p-selectbutton, p-select-button", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "unselectable", "tabindex", "multiple", "allowEmpty", "styleClass", "ariaLabelledBy", "dataKey", "autofocus", "size", "fluid"], outputs: ["onOptionClick", "onChange"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i4$2.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo"] }, { kind: "component", type: DCLessonComponentAdderComponent, selector: "dc-lesson-component-adder", outputs: ["componentAdded", "onNewDynamicComponent"] }, { kind: "ngmodule", type: // Add the component adder here
2432
+ DialogModule }, { kind: "component", type: AssetsLoaderComponent, selector: "assets-loader", inputs: ["assets", "storagePath"], outputs: ["assetsChange", "assetUpdate", "onFileSelected"] }, { kind: "component", type: DCLessonMetadataEditorComponent, selector: "dc-lesson-metadata-editor", inputs: ["form", "lesson", "isLoadingLesson"], outputs: ["saveRequest", "importNotionRequest", "improveNotionRequest"] }, { kind: "component", type: DcManageableFormComponent, selector: "dc-manageable-form", inputs: ["form"] }, { kind: "component", type: DcLearnableFormComponent, selector: "dc-learnable-form", inputs: ["form"] }, { kind: "component", type: DcLessonTextEditorComponent, selector: "dc-lesson-text-editor", inputs: ["lessonInput"], outputs: ["saveLesson", "updateLesson"] }, { kind: "component", type: DcDynamicComponentPreviewComponent, selector: "dc-dynamic-component-preview", inputs: ["dynamicComponentsArray"], outputs: ["showComponentDetails", "editComponent"] }] }); }
2321
2433
  }
2322
2434
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonEditorComponent, decorators: [{
2323
2435
  type: Component,
2324
2436
  args: [{ selector: 'dc-lesson-editor', standalone: true, imports: [
2325
2437
  ButtonModule,
2326
- CKEditorModule,
2327
- DCLessonRendererComponent,
2328
2438
  FormsModule,
2329
2439
  InputTextModule,
2330
2440
  ReactiveFormsModule,
2331
2441
  SelectButtonModule,
2332
- SplitterModule,
2333
2442
  TooltipModule,
2334
2443
  DCLessonComponentAdderComponent, // Add the component adder here
2335
2444
  DialogModule,
@@ -2337,7 +2446,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImpor
2337
2446
  DCLessonMetadataEditorComponent,
2338
2447
  DcManageableFormComponent,
2339
2448
  DcLearnableFormComponent,
2340
- ], providers: [LessonNotionService], template: "<div class=\"p-grid\">\n <div class=\"p-col-4\">\n <assets-loader\n [assets]=\"entity()?.assets\"\n storagePath=\"lessons/{{ entityId() }}\"\n (assetsChange)=\"onAssetsChange($event)\"\n (assetUpdate)=\"onAssetUpdate($event)\"></assets-loader>\n </div>\n\n <!-- <div class=\"p-col-4\">\n <h3>Auditable</h3>\n <dc-auditable-viewer [data]=\"entity()?.auditable\"></dc-auditable-viewer>\n </div> -->\n <div class=\"p-col-4\">\n <h3>Gesti\u00F3n</h3>\n <dc-manageable-form [form]=\"form.controls.manageable\"></dc-manageable-form>\n </div>\n <div class=\"p-col-4\">\n <h3>Categorias y Etiquetas</h3>\n <dc-learnable-form [form]=\"form.controls.learnable\"></dc-learnable-form>\n </div>\n <!-- <div class=\"p-col-4\">\n <h3>Reactions</h3>\n <dc-reactions-viewer [data]=\"entity()?.reactions\"></dc-reactions-viewer>\n </div> -->\n\n <!-- <div class=\"p-col-4\">\n <h3>Extensions</h3>\n <dc-extensions-viewer [data]=\"entity()?.extensions\"></dc-extensions-viewer>\n </div> -->\n</div>\n\n<!-- Lesson Metadata Editor -->\n<br />\n<div [formGroup]=\"form\">\n <p-selectButton [options]=\"formatOptions\" formControlName=\"format\" optionLabel=\"label\" optionValue=\"value\" />\n</div>\n<dc-lesson-metadata-editor [lesson]=\"entity()\" [form]=\"form\" [isLoadingLesson]=\"isLoadingLesson()\"></dc-lesson-metadata-editor>\n\n<div style=\"margin-top: 30px\"></div>\n\n<!-- Component Adder -->\n<dc-lesson-component-adder (componentAdded)=\"onComponentAdded($event)\"></dc-lesson-component-adder>\n\n<!-- Display Added Components -->\n<div class=\"added-components-list\" style=\"margin-top: 15px; margin-bottom: 15px\">\n <h4>Componentes Agregados:</h4>\n @if (dynamicComponentsArray().length > 0) {\n <ul>\n @for (comp of dynamicComponentsArray(); track comp.id) {\n <li\n >ID: {{ comp.id }} - Tipo: {{ comp.component }}\n\n <button pButton icon=\"pi pi-info\" (click)=\"showComponentDetails(comp)\"></button>\n <p-button icon=\"pi pi-pencil\" [rounded]=\"true\" severity=\"warn\" (click)=\"editComponent(comp)\"></p-button>\n </li>\n }\n </ul>\n } @else {\n <p>A\u00FAn no se han agregado componentes.</p>\n }\n</div>\n\n<hr />\n\n<!-- Text Editor and Renderer -->\n<p-splitter [style]=\"{ height: '80vh' }\">\n <ng-template pTemplate>\n <ckeditor\n (keydown.control.s)=\"saveLesson($event)\"\n class=\"text-editor\"\n [editor]=\"editor\"\n [ngModel]=\"htmlTemporal\"\n (ngModelChange)=\"updateHtmlTextCoded('textCoded', $event)\">\n </ckeditor>\n </ng-template>\n\n <ng-template pTemplate>\n <dc-lesson-renderer class=\"text-editor\" [lessonInput]=\"entity()\"></dc-lesson-renderer>\n </ng-template>\n</p-splitter>\n\n<div class=\"float-button\">\n <p-button icon=\"pi pi-save\" (click)=\"save()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n</div>\n\n<hr />\n\n<!-- <p-dialog header=\"Prompts\" [modal]=\"true\" [(visible)]=\"promptsVisible\" [style]=\"{ width: '70%' }\">\n <div>\n <h1>Banner</h1>\n <p>{{ prompts?.banner }}</p>\n <h1>Contenido</h1>\n <p>{{ prompts?.content }}</p>\n <h1>Descripci\u00F3n</h1>\n <p>{{ prompts?.description }}</p>\n </div>\n</p-dialog> -->\n", styles: [".btn{padding:.5rem 1rem;border-radius:4px;border:1px solid transparent;cursor:pointer}.generate-banner-btn{position:absolute;right:10px;top:10px}.prompt-visual{position:absolute;left:10px;bottom:10px}.btn-primary{background-color:#007bff;color:#fff}.btn-outline-primary{border-color:#007bff;color:#007bff}.btn-secondary{background-color:#6c757d;color:#fff}.btn-outline-secondary{border-color:#6c757d;color:#6c757d}.btn-rounded{border-radius:50%}.form-control{padding:.375rem .75rem;border:1px solid #ced4da;border-radius:.25rem}.splitter{display:flex;gap:1rem}.splitter-panel{flex:1}.checkbox-container{display:inline-flex;align-items:center;gap:.5rem;cursor:pointer}.mr-2{margin-right:.5rem}.header-cover{width:100%;height:250px;object-fit:cover;position:relative;border-radius:8px}.float-button{position:fixed;bottom:3.5rem;right:2rem;z-index:1000;display:flex;gap:1px}.float-button :host ::ng-deep .p-button{width:4rem;height:4rem;border-radius:50%}.text-editor{width:-webkit-fill-available;overflow-y:auto}:host ::ng-deep .p-inputtext{background:#fff3}::ng-deep .p-splitter .p-splitterpanel{overflow:auto!important}\n"] }]
2449
+ DcLessonTextEditorComponent,
2450
+ DcDynamicComponentPreviewComponent,
2451
+ ], template: "<div class=\"p-grid\">\n <div class=\"p-col-4\">\n <assets-loader\n [assets]=\"entity()?.assets\"\n storagePath=\"lessons/{{ entityId() }}\"\n (assetsChange)=\"onAssetsChange($event)\"\n (assetUpdate)=\"onAssetUpdate($event)\"></assets-loader>\n </div>\n\n <div class=\"p-col-4\">\n <h3>Gesti\u00F3n</h3>\n <dc-manageable-form [form]=\"form.controls.manageable\"></dc-manageable-form>\n </div>\n <div class=\"p-col-4\">\n <h3>Categorias y Etiquetas</h3>\n <dc-learnable-form [form]=\"form.controls.learnable\"></dc-learnable-form>\n </div>\n</div>\n\n<!-- Lesson Metadata Editor -->\n<br />\n<div [formGroup]=\"form\">\n <p-selectButton [options]=\"formatOptions\" formControlName=\"format\" optionLabel=\"label\" optionValue=\"value\" />\n</div>\n<dc-lesson-metadata-editor [lesson]=\"entity()\" [form]=\"form\" [isLoadingLesson]=\"isLoadingLesson()\"></dc-lesson-metadata-editor>\n\n<div style=\"margin-top: 30px\"></div>\n\n<!-- Component Adder -->\n<dc-lesson-component-adder (componentAdded)=\"onComponentAdded($event)\" (onNewDynamicComponent)=\"onNewDynamicComponent($event)\"></dc-lesson-component-adder>\n\n<dc-dynamic-component-preview\n [dynamicComponentsArray]=\"dynamicComponentsArray\"\n (showComponentDetails)=\"showComponentDetails($event)\"\n (editComponent)=\"editComponent($event)\"></dc-dynamic-component-preview>\n\n<hr />\n\n<!-- Text Editor and Renderer -->\n<dc-lesson-text-editor [lessonInput]=\"entity()\" (updateLesson)=\"updateLesson($event)\"></dc-lesson-text-editor>\n\n<div class=\"float-button\">\n <p-button icon=\"pi pi-save\" (click)=\"saveLesson()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n</div>\n\n<hr />\n", styles: [".btn{padding:.5rem 1rem;border-radius:4px;border:1px solid transparent;cursor:pointer}.generate-banner-btn{position:absolute;right:10px;top:10px}.prompt-visual{position:absolute;left:10px;bottom:10px}.btn-primary{background-color:#007bff;color:#fff}.btn-outline-primary{border-color:#007bff;color:#007bff}.btn-secondary{background-color:#6c757d;color:#fff}.btn-outline-secondary{border-color:#6c757d;color:#6c757d}.btn-rounded{border-radius:50%}.form-control{padding:.375rem .75rem;border:1px solid #ced4da;border-radius:.25rem}.splitter{display:flex;gap:1rem}.splitter-panel{flex:1}.checkbox-container{display:inline-flex;align-items:center;gap:.5rem;cursor:pointer}.mr-2{margin-right:.5rem}.header-cover{width:100%;height:250px;object-fit:cover;position:relative;border-radius:8px}.float-button{position:fixed;bottom:3.5rem;right:2rem;z-index:1000;display:flex;gap:1px}.float-button :host ::ng-deep .p-button{width:4rem;height:4rem;border-radius:50%}.text-editor{width:-webkit-fill-available;overflow-y:auto}:host ::ng-deep .p-inputtext{background:#fff3}::ng-deep .p-splitter .p-splitterpanel{overflow:auto!important}\n"] }]
2341
2452
  }] });
2342
2453
 
2343
2454
  class LessonsV2Component {
@@ -2593,7 +2704,7 @@ class CourseDetailComponent {
2593
2704
  this.course.set(course);
2594
2705
  }
2595
2706
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: CourseDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2596
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: CourseDetailComponent, isStandalone: true, selector: "app-course-detail", providers: [MessageService], ngImport: i0, template: "@if (course(); as course) {\n<div class=\"course-detail-container p-4\">\n <p-card>\n <ng-template pTemplate=\"title\">\n <div class=\"flex justify-content-between align-items-center\">\n <span>{{ course.name }}</span>\n </div>\n </ng-template>\n <ng-template pTemplate=\"subtitle\">\n <div class=\"flex align-items-center gap-2\">\n <i class=\"pi pi-book\"></i>\n <span>{{ course.moduleCount }} Modules / {{ course.totalLessons }} Lessons</span>\n </div>\n </ng-template>\n <ng-template pTemplate=\"content\">\n <p>{{ course.description }}</p>\n\n <div class=\"grid mt-3\">\n @for (module of course.modules; track module.id) {\n <div class=\"col-12 md:col-6\">\n <p-panel [toggleable]=\"true\">\n <ng-template pTemplate=\"header\">\n <div class=\"flex align-items-center gap-2 w-full\">\n <i class=\"pi pi-list\"></i>\n <span class=\"font-bold white-space-nowrap\">{{ module.title }} {{ module.name }}</span>\n <span class=\"text-sm text-color-secondary ml-auto\">{{ module.lessonCount }} lessons</span>\n </div>\n </ng-template>\n <ng-template pTemplate=\"content\">\n <p>{{ module.description }}</p>\n <ul class=\"list-none p-0 m-0 lessons-list\">\n @for (lesson of module.lessons; track lesson.id) {\n <li class=\"flex align-items-center justify-content-between p-2 border-bottom-1 surface-border\">\n <div class=\"flex align-items-center gap-2\">\n @if(lesson.generated) {\n <i class=\"pi pi-play-circle text-green-500\"></i>\n } @else {\n <i class=\"pi pi-lock text-gray-500\"></i>\n }\n <span>{{ lesson.title }} {{ lesson.name }}</span>\n </div>\n @if(lesson.generated) {\n <p-button icon=\"pi pi-chevron-right\" [text]=\"true\" [rounded]=\"true\"></p-button>\n } @else {\n <p-button label=\"Generate\" icon=\"pi pi-cog\" [text]=\"true\" size=\"small\"></p-button>\n }\n </li>\n }\n </ul>\n </ng-template>\n </p-panel>\n </div>\n }\n </div>\n </ng-template>\n <ng-template pTemplate=\"footer\">\n <div class=\"flex justify-content-end\">\n <p-button label=\"Generate Pending Lessons\" icon=\"pi pi-cogs\" (onClick)=\"generatePendingLessons()\" [loading]=\"generatingLessons()\"></p-button>\n </div>\n </ng-template>\n </p-card>\n</div>\n} @else {\n<div class=\"flex justify-content-center align-items-center h-full\">\n <p-progressSpinner></p-progressSpinner>\n</div>\n}\n", styles: [":host{display:block;height:100%}.course-detail-container{max-width:960px;margin:auto}.lessons-list li:last-child{border-bottom:none!important}.modules-container{display:flex;flex-direction:row;flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;padding-bottom:1rem}.modules-container .p-panel{flex:0 0 48%;max-width:48%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i1$2.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "directive", type: i2$3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: PanelModule }, { kind: "component", type: i3$2.Panel, selector: "p-panel", inputs: ["toggleable", "header", "collapsed", "id", "styleClass", "iconPos", "showHeader", "toggler", "transitionOptions", "toggleButtonProps"], outputs: ["collapsedChange", "onBeforeToggle", "onAfterToggle"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i5$2.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }, { kind: "ngmodule", type: ToastModule }, { kind: "ngmodule", type: TagModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2707
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: CourseDetailComponent, isStandalone: true, selector: "app-course-detail", providers: [MessageService], ngImport: i0, template: "@if (course(); as course) {\n<div class=\"course-detail-container p-4\">\n <p-card>\n <ng-template pTemplate=\"title\">\n <div class=\"flex justify-content-between align-items-center\">\n <span>{{ course.name }}</span>\n </div>\n </ng-template>\n <ng-template pTemplate=\"subtitle\">\n <div class=\"flex align-items-center gap-2\">\n <i class=\"pi pi-book\"></i>\n <span>{{ course.moduleCount }} Modules / {{ course.totalLessons }} Lessons</span>\n </div>\n </ng-template>\n <ng-template pTemplate=\"content\">\n <p>{{ course.description }}</p>\n\n <div class=\"grid mt-3\">\n @for (module of course.modules; track module.id) {\n <div class=\"col-12 md:col-6\">\n <p-panel [toggleable]=\"true\">\n <ng-template pTemplate=\"header\">\n <div class=\"flex align-items-center gap-2 w-full\">\n <i class=\"pi pi-list\"></i>\n <span class=\"font-bold white-space-nowrap\">{{ module.title }} {{ module.name }}</span>\n <span class=\"text-sm text-color-secondary ml-auto\">{{ module.lessonCount }} lessons</span>\n </div>\n </ng-template>\n <ng-template pTemplate=\"content\">\n <p>{{ module.description }}</p>\n <ul class=\"list-none p-0 m-0 lessons-list\">\n @for (lesson of module.lessons; track lesson.id) {\n <li class=\"flex align-items-center justify-content-between p-2 border-bottom-1 surface-border\">\n <div class=\"flex align-items-center gap-2\">\n @if(lesson.generated) {\n <i class=\"pi pi-play-circle text-green-500\"></i>\n } @else {\n <i class=\"pi pi-lock text-gray-500\"></i>\n }\n <span>{{ lesson.title }} {{ lesson.name }}</span>\n </div>\n @if(lesson.generated) {\n <p-button icon=\"pi pi-chevron-right\" [text]=\"true\" [rounded]=\"true\"></p-button>\n } @else {\n <p-button label=\"Generate\" icon=\"pi pi-cog\" [text]=\"true\" size=\"small\"></p-button>\n }\n </li>\n }\n </ul>\n </ng-template>\n </p-panel>\n </div>\n }\n </div>\n </ng-template>\n <ng-template pTemplate=\"footer\">\n <div class=\"flex justify-content-end\">\n <p-button label=\"Generate Pending Lessons\" icon=\"pi pi-cogs\" (onClick)=\"generatePendingLessons()\" [loading]=\"generatingLessons()\"></p-button>\n </div>\n </ng-template>\n </p-card>\n</div>\n} @else {\n<div class=\"flex justify-content-center align-items-center h-full\">\n <p-progressSpinner></p-progressSpinner>\n</div>\n}\n", styles: [":host{display:block;height:100%}.course-detail-container{max-width:960px;margin:auto}.lessons-list li:last-child{border-bottom:none!important}.modules-container{display:flex;flex-direction:row;flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;padding-bottom:1rem}.modules-container .p-panel{flex:0 0 48%;max-width:48%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i1$2.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "directive", type: i4$3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: PanelModule }, { kind: "component", type: i3$3.Panel, selector: "p-panel", inputs: ["toggleable", "header", "collapsed", "id", "styleClass", "iconPos", "showHeader", "toggler", "transitionOptions", "toggleButtonProps"], outputs: ["collapsedChange", "onBeforeToggle", "onAfterToggle"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i5$1.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }, { kind: "ngmodule", type: ToastModule }, { kind: "ngmodule", type: TagModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2597
2708
  }
2598
2709
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: CourseDetailComponent, decorators: [{
2599
2710
  type: Component,
@@ -2658,7 +2769,7 @@ class CourseFormComponent extends EntityBaseFormComponent {
2658
2769
  }
2659
2770
  }
2660
2771
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: CourseFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2661
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: CourseFormComponent, isStandalone: true, selector: "app-source-form", outputs: { onSave: "onSave" }, usesInheritance: true, ngImport: i0, template: "<h3>Courses Form</h3>\n\n<div class=\"source-form-card\">\n <p-card [header]=\"entityId() ? 'Edit Course' : 'New Course'\">\n <form [formGroup]=\"form\">\n <div class=\"form-field\">\n <label for=\"baseLang\">Base Language <span pTooltip=\"Select the primary language for the conversation\">\u2139\uFE0F</span></label>\n <p-select\n id=\"baseLang\"\n [options]=\"languageOptions\"\n [filter]=\"true\"\n formControlName=\"baseLang\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Language'\"></p-select>\n </div>\n <div class=\"form-field\">\n <label for=\"targetLang\">Target Language <span pTooltip=\"Select the primary language for the conversation\">\u2139\uFE0F</span></label>\n <p-select\n id=\"targetLang\"\n [filter]=\"true\"\n [options]=\"languageOptions\"\n formControlName=\"targetLang\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Language'\"></p-select>\n </div>\n\n <div style=\"display: flex; gap: 10px\">\n <div class=\"form-field\">\n <label class=\"block\" pTooltip=\"Image should be handle after upload\">Subir imagen</label>\n <img width=\"218px\" src=\"assets/images/face-3.jpg\" />\n <dc-cropper-modal [imgStorageSettings]=\"storageImgSettings\" (imageUploaded)=\"handleImageUpload($event)\"></dc-cropper-modal>\n </div>\n\n <div style=\"width: 100%\">\n <div class=\"form-field\">\n <label for=\"name\" class=\"block\">Name</label>\n <input pInputText id=\"name\" type=\"text\" formControlName=\"name\" placeholder=\"Enter source name\" />\n </div>\n\n <div class=\"form-field\">\n <label for=\"description\" class=\"block\">Description</label>\n <textarea id=\"description\" pTextarea formControlName=\"description\" rows=\"5\" class=\"w-full\" placeholder=\"Enter source content\"> </textarea>\n </div>\n </div>\n </div>\n </form>\n\n <div>\n @if (entity()?.modules?.length > 0) {\n <p-message severity=\"info\"\n >El curso ya tiene modulos,\n\n <a routerLink=\"../../details/{{ entity()?.id }}\"> <p-button icon=\"pi pi-eye\" label=\"Ver a los detalles\" [link]=\"true\"></p-button></a>\n </p-message>\n } @else {\n <p-message severity=\"warn\">Este curso no tiene modulos intentega generalos abajo.</p-message>\n }\n </div>\n\n <div style=\"display: flex; justify-content: flex-end\">\n <p-button\n (click)=\"generateCourse()\"\n label=\"Generate Course\"\n [disabled]=\"!form.controls['baseLang'].valid || !form.controls['targetLang'].valid\"\n icon=\"pi pi-sparkles\"\n iconPos=\"right\"\n styleClass=\"p-button-secondary\"></p-button>\n <p-button (click)=\"save()\" label=\"Save Course\" [disabled]=\"!form.valid\" icon=\"pi pi-check\" iconPos=\"right\"> </p-button>\n </div>\n </p-card>\n</div>\n", styles: [":host{display:block;padding:1rem}.source-form-card{max-width:800px;margin:0 auto}.form-field{margin-bottom:1.5rem;display:flex;flex-direction:column}.form-field label{margin-bottom:.5rem;font-weight:500;color:#495057}.form-field input,.form-field textarea,.form-field ::ng-deep .p-element{margin-top:.25rem}:host ::ng-deep .p-card .p-card-content>div:last-child{margin-top:1.5rem;display:flex;justify-content:flex-end}:host ::ng-deep .p-card .p-card-header{background-color:#f8f9fa;padding:1rem;border-bottom:1px solid #dee2e6}h3{color:#495057;margin-bottom:1.5rem;text-align:center}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i1$2.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i3$3.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i2.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"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i3.InputText, selector: "[pInputText]", inputs: ["pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: ChipModule }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i7.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo"] }, { kind: "component", type: CropperComponentModal, selector: "dc-cropper-modal", inputs: ["imgStorageSettings", "buttonLabel", "currentStorage"], outputs: ["imageUploaded", "onImageCropped", "onFileSelected"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: MessageModule }, { kind: "component", type: i4.Message, selector: "p-message", inputs: ["severity", "text", "escape", "style", "styleClass", "closable", "icon", "closeIcon", "life", "showTransitionOptions", "hideTransitionOptions", "size", "variant"], outputs: ["onClose"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2772
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: CourseFormComponent, isStandalone: true, selector: "app-source-form", outputs: { onSave: "onSave" }, usesInheritance: true, ngImport: i0, template: "<h3>Courses Form</h3>\n\n<div class=\"source-form-card\">\n <p-card [header]=\"entityId() ? 'Edit Course' : 'New Course'\">\n <form [formGroup]=\"form\">\n <div class=\"form-field\">\n <label for=\"baseLang\">Base Language <span pTooltip=\"Select the primary language for the conversation\">\u2139\uFE0F</span></label>\n <p-select\n id=\"baseLang\"\n [options]=\"languageOptions\"\n [filter]=\"true\"\n formControlName=\"baseLang\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Language'\"></p-select>\n </div>\n <div class=\"form-field\">\n <label for=\"targetLang\">Target Language <span pTooltip=\"Select the primary language for the conversation\">\u2139\uFE0F</span></label>\n <p-select\n id=\"targetLang\"\n [filter]=\"true\"\n [options]=\"languageOptions\"\n formControlName=\"targetLang\"\n optionLabel=\"label\"\n optionValue=\"value\"\n [placeholder]=\"'Select Language'\"></p-select>\n </div>\n\n <div style=\"display: flex; gap: 10px\">\n <div class=\"form-field\">\n <label class=\"block\" pTooltip=\"Image should be handle after upload\">Subir imagen</label>\n <img width=\"218px\" src=\"assets/images/face-3.jpg\" />\n <dc-cropper-modal [imgStorageSettings]=\"storageImgSettings\" (imageUploaded)=\"handleImageUpload($event)\"></dc-cropper-modal>\n </div>\n\n <div style=\"width: 100%\">\n <div class=\"form-field\">\n <label for=\"name\" class=\"block\">Name</label>\n <input pInputText id=\"name\" type=\"text\" formControlName=\"name\" placeholder=\"Enter source name\" />\n </div>\n\n <div class=\"form-field\">\n <label for=\"description\" class=\"block\">Description</label>\n <textarea id=\"description\" pTextarea formControlName=\"description\" rows=\"5\" class=\"w-full\" placeholder=\"Enter source content\"> </textarea>\n </div>\n </div>\n </div>\n </form>\n\n <div>\n @if (entity()?.modules?.length > 0) {\n <p-message severity=\"info\"\n >El curso ya tiene modulos,\n\n <a routerLink=\"../../details/{{ entity()?.id }}\"> <p-button icon=\"pi pi-eye\" label=\"Ver a los detalles\" [link]=\"true\"></p-button></a>\n </p-message>\n } @else {\n <p-message severity=\"warn\">Este curso no tiene modulos intentega generalos abajo.</p-message>\n }\n </div>\n\n <div style=\"display: flex; justify-content: flex-end\">\n <p-button\n (click)=\"generateCourse()\"\n label=\"Generate Course\"\n [disabled]=\"!form.controls['baseLang'].valid || !form.controls['targetLang'].valid\"\n icon=\"pi pi-sparkles\"\n iconPos=\"right\"\n styleClass=\"p-button-secondary\"></p-button>\n <p-button (click)=\"save()\" label=\"Save Course\" [disabled]=\"!form.valid\" icon=\"pi pi-check\" iconPos=\"right\"> </p-button>\n </div>\n </p-card>\n</div>\n", styles: [":host{display:block;padding:1rem}.source-form-card{max-width:800px;margin:0 auto}.form-field{margin-bottom:1.5rem;display:flex;flex-direction:column}.form-field label{margin-bottom:.5rem;font-weight:500;color:#495057}.form-field input,.form-field textarea,.form-field ::ng-deep .p-element{margin-top:.25rem}:host ::ng-deep .p-card .p-card-content>div:last-child{margin-top:1.5rem;display:flex;justify-content:flex-end}:host ::ng-deep .p-card .p-card-header{background-color:#f8f9fa;padding:1rem;border-bottom:1px solid #dee2e6}h3{color:#495057;margin-bottom:1.5rem;text-align:center}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i1$2.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: TextareaModule }, { kind: "directive", type: i3$4.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["autoResize", "pSize", "variant", "fluid", "invalid"], outputs: ["onResize"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i2.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"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i3.InputText, selector: "[pInputText]", inputs: ["pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: ChipModule }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i4$2.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo"] }, { kind: "component", type: CropperComponentModal, selector: "dc-cropper-modal", inputs: ["imgStorageSettings", "buttonLabel", "currentStorage"], outputs: ["imageUploaded", "onImageCropped", "onFileSelected"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: MessageModule }, { kind: "component", type: i4.Message, selector: "p-message", inputs: ["severity", "text", "escape", "style", "styleClass", "closable", "icon", "closeIcon", "life", "showTransitionOptions", "hideTransitionOptions", "size", "variant"], outputs: ["onClose"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2662
2773
  }
2663
2774
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: CourseFormComponent, decorators: [{
2664
2775
  type: Component,
@@ -2706,7 +2817,7 @@ const COURSES_ROUTES = [
2706
2817
  class CoursesComponent {
2707
2818
  static { this.routes = COURSES_ROUTES; }
2708
2819
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: CoursesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2709
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.6", type: CoursesComponent, isStandalone: true, selector: "app-courses", ngImport: i0, template: "<router-outlet />\n", styles: [":host{display:block;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$4.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2820
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.6", type: CoursesComponent, isStandalone: true, selector: "app-courses", ngImport: i0, template: "<router-outlet />\n", styles: [":host{display:block;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$6.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2710
2821
  }
2711
2822
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: CoursesComponent, decorators: [{
2712
2823
  type: Component,
@@ -2721,5 +2832,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImpor
2721
2832
  * Generated bundle index. Do not edit.
2722
2833
  */
2723
2834
 
2724
- export { ComponentBuilder, ComponentWithForm, CourseDetailComponent, CourseFormComponent, CourseListComponent, CourseService, CoursesAdminComponent, CoursesComponent, CoursesService, DCLessonEditorComponent, DCLessonFormComponent, DCLessonListComponent, DCLessonRendererComponent, DcLessonCardComponent, DefaultLessonsService, DynamicComponentBuilders, DynamicComponents, DynamicComponentsService, FlagLanguagePipe, LESSONS_TOKEN, LangCodeDescription, LangCodeDescriptionEs, LessonComponentBuilders, LessonComponentEnum, LessonComponents, LessonConversationService, LessonDynamicComponent, LessonRendererService, LessonsV2Component, NOTION_SERVICE_TOKEN, NotionAbstractService, NotionExportType, SelectorBuilderComponent, SelectorComponent, TextWriterBuiderComponent, TextWriterComponent, TranslationSwitcherBuilderComponent, TranslationSwitcherComponent, getLanguageSimpleAgent, getLessonComponentClass, provideLessonsService, provideNotionService };
2835
+ export { ComponentBuilder, ComponentWithForm, CourseDetailComponent, CourseFormComponent, CourseListComponent, CourseService, CoursesAdminComponent, CoursesComponent, CoursesService, DCLessonEditorComponent, DCLessonFormComponent, DCLessonListComponent, DCLessonRendererComponent, DcLessonCardComponent, DefaultDynamicComponentBuilders, DefaultDynamicComponents, DefaultLessonsService, DynamicComponentsRegisterService, FlagLanguagePipe, LESSONS_TOKEN, LangCodeDescription, LangCodeDescriptionEs, LessonComponentBuilders, LessonComponentEnum, LessonComponents, LessonConversationService, LessonDynamicComponent, LessonRendererService, LessonsV2Component, NOTION_SERVICE_TOKEN, NotionAbstractService, NotionExportType, SelectorBuilderComponent, SelectorComponent, TextWriterBuiderComponent, TextWriterComponent, TranslationSwitcherBuilderComponent, TranslationSwitcherComponent, getLanguageSimpleAgent, getLessonComponentClass, provideLessonsService, provideNotionService };
2725
2836
  //# sourceMappingURL=dataclouder-ngx-lessons.mjs.map