@dataclouder/ngx-lessons 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,20 +1,23 @@
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, effect, ViewChildren, ViewContainerRef, ViewChild, input, viewChild, Renderer2, computed, ChangeDetectorRef, output } from '@angular/core';
3
+ import { DatePipe, NgComponentOutlet, CommonModule, KeyValuePipe, SlicePipe } from '@angular/common';
4
+ import * as i1$6 from '@angular/router';
5
5
  import { RouterModule, ActivatedRoute, RouterOutlet, RouterLink } from '@angular/router';
6
- import { EntityCommunicationService, EntityBaseListComponent, DCFilterBarComponent, QuickTableComponent, TOAST_ALERTS_TOKEN, EModelQuality, UiStateService, LoadingBarService, FormUtilsService, EntityBaseFormComponent, PromptService, DcManageableFormComponent, DcLearnableFormComponent, HttpCoreService, PaginationBase, LangDescTranslation, getSupportedLanguageOptions } from '@dataclouder/ngx-core';
6
+ 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
7
  import * as i1 from '@angular/forms';
8
8
  import { FormBuilder, FormControl, FormArray, FormsModule, ReactiveFormsModule, UntypedFormControl, FormGroup, Validators } from '@angular/forms';
9
9
  import * as i1$1 from 'primeng/button';
10
10
  import { ButtonModule } from 'primeng/button';
11
11
  import * as i3 from 'primeng/inputtext';
12
12
  import { InputTextModule } from 'primeng/inputtext';
13
- import { DynamicDialogRef, DialogService } from 'primeng/dynamicdialog';
13
+ import * as i1$4 from 'primeng/dynamicdialog';
14
+ import { DynamicDialogRef, DialogService, DynamicDialogConfig } from 'primeng/dynamicdialog';
14
15
  import { nanoid } from 'nanoid';
15
16
  import * as i2 from 'primeng/select';
16
17
  import { SelectModule } from 'primeng/select';
17
- import { TtsPlaygroundComponent, getRandomQuickVoice, NgxVertexService, ChatRoleVertex } from '@dataclouder/ngx-vertex';
18
+ import { TtsPlaygroundComponent, getRandomQuickVoice, NgxAiServicesService, ChatRoleVertex } from '@dataclouder/ngx-ai-services';
19
+ import * as i1$2 from 'primeng/card';
20
+ import { CardModule } from 'primeng/card';
18
21
  import * as i4 from 'primeng/message';
19
22
  import { MessageModule } from 'primeng/message';
20
23
  import { PopoverModule } from 'primeng/popover';
@@ -22,44 +25,39 @@ 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';
45
- import TurndownService from 'turndown';
46
- import { marked } from 'marked';
37
+ import { CONVERSATION_AI_TOKEN, ChatRole, EvalResultStringDefinition, SystemPromptType, EDoActionType, ConditionOperator, ConditionType, ConversationEvents, TextEngines, ConversationType, ChatEventType, DCChatComponent } from '@dataclouder/ngx-agent-cards';
47
38
  import * as i5 from 'primeng/inputgroup';
48
39
  import { InputGroupModule } from 'primeng/inputgroup';
49
40
  import * as i6 from 'primeng/divider';
50
41
  import { DividerModule } from 'primeng/divider';
51
- 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';
52
51
  import { MessageService } from 'primeng/api';
53
52
  import { TableModule } from 'primeng/table';
54
- import * as i3$2 from 'primeng/panel';
53
+ import * as i3$3 from 'primeng/panel';
55
54
  import { PanelModule } from 'primeng/panel';
56
- import * as i5$2 from 'primeng/progressspinner';
55
+ import * as i5$1 from 'primeng/progressspinner';
57
56
  import { ProgressSpinnerModule } from 'primeng/progressspinner';
58
57
  import { ToastModule } from 'primeng/toast';
59
- import * as i3$3 from 'primeng/textarea';
58
+ import * as i3$4 from 'primeng/textarea';
60
59
  import { TextareaModule } from 'primeng/textarea';
61
60
  import { ChipModule } from 'primeng/chip';
62
- import { FormlyModule } from '@ngx-formly/core';
63
61
 
64
62
  // This pipe should not be here in the lessons, refactor when is posible and remove LangCodeDescription
65
63
  const LangCodeDescription = {
@@ -436,7 +434,7 @@ class SpeakerBuilderComponent extends ComponentBuilder {
436
434
  return code;
437
435
  }
438
436
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: SpeakerBuilderComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
439
- 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"] }] }); }
440
438
  }
441
439
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: SpeakerBuilderComponent, decorators: [{
442
440
  type: Component,
@@ -455,11 +453,11 @@ class SpeakerComponent {
455
453
  console.log('should speech but will do in next version');
456
454
  }
457
455
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: SpeakerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
458
- 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"] }] }); }
459
457
  }
460
458
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: SpeakerComponent, decorators: [{
461
459
  type: Component,
462
- 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"] }]
463
461
  }], propDecorators: { config: [{
464
462
  type: Input
465
463
  }], tts: [{
@@ -521,12 +519,6 @@ class SelectorBuilderComponent extends ComponentBuilder {
521
519
  },
522
520
  };
523
521
  }
524
- // public formGroup = this.formBuilder.group({
525
- // options: this.formBuilder.array([]),
526
- // response: ['', Validators.required],
527
- // hint: [],
528
- // explanation: [],
529
- // });
530
522
  ngOnInit() {
531
523
  this.formGroup.get('response');
532
524
  }
@@ -543,11 +535,11 @@ class SelectorBuilderComponent extends ComponentBuilder {
543
535
  return this.formGroup.get('options');
544
536
  }
545
537
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: SelectorBuilderComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
546
- 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"] }] }); }
547
539
  }
548
540
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: SelectorBuilderComponent, decorators: [{
549
541
  type: Component,
550
- 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"] }]
551
543
  }] });
552
544
 
553
545
  var LessonComponentEnum;
@@ -634,11 +626,11 @@ class DcLessonCardComponent {
634
626
  this.onAction.emit({ action: eventType, item: this.lesson });
635
627
  }
636
628
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DcLessonCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
637
- 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}.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(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 || '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" }] }); }
638
630
  }
639
631
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DcLessonCardComponent, decorators: [{
640
632
  type: Component,
641
- 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}.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(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 || '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"] }]
642
634
  }], propDecorators: { lesson: [{
643
635
  type: Input
644
636
  }], showOptions: [{
@@ -768,10 +760,10 @@ class DCLessonListComponent extends EntityBaseListComponent {
768
760
  title: 1,
769
761
  name: 1,
770
762
  description: 1,
771
- media: 1,
772
763
  manageable: 1,
773
764
  learnable: 1,
774
765
  assets: 1,
766
+ level: 1,
775
767
  _id: 1,
776
768
  id: 1,
777
769
  };
@@ -843,11 +835,11 @@ class DCLessonListComponent extends EntityBaseListComponent {
843
835
  }
844
836
  }
845
837
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
846
- 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", 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", "onChangeSort", "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"] }] }); }
838
+ 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"] }] }); }
847
839
  }
848
840
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonListComponent, decorators: [{
849
841
  type: Component,
850
- 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", 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"] }]
842
+ 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"] }]
851
843
  }], ctorParameters: () => [], propDecorators: { customCardComponent: [{
852
844
  type: Input
853
845
  }], customFilters: [{
@@ -866,1271 +858,1419 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImpor
866
858
  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"] }]
867
859
  }] });
868
860
 
869
- // TODO: check LessonComponentBuilders in lessons.clases.ts for origianl implementation
870
- const DynamicComponentBuilders = {
871
- [LessonComponentEnum.Speaker]: SpeakerBuilderComponent,
872
- };
873
- const DynamicComponents = {
874
- [LessonComponentEnum.Speaker]: SpeakerComponent,
875
- };
876
- class DynamicComponentsService {
861
+ class LessonNotionService {
877
862
  constructor() {
878
- this._dynamicComponentBuilders = { ...DynamicComponentBuilders };
879
- this._dynamicComponents = { ...DynamicComponents };
863
+ this.#notionService = inject(NOTION_SERVICE_TOKEN);
864
+ this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
865
+ this.#toastService = inject(TOAST_ALERTS_TOKEN);
866
+ // Keep track of loading state specific to Notion operations
867
+ this.isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
880
868
  }
881
- registerCustomComponent(component, name) {
882
- // this._dynamicComponentBuilders[name || component.name] = component;
883
- this._dynamicComponents[name || component.name] = component;
869
+ #notionService;
870
+ #toastService;
871
+ /**
872
+ * Extracts the Notion Page ID from a URL.
873
+ * @param url The Notion page URL.
874
+ * @returns The extracted page ID or null if invalid.
875
+ */
876
+ extractNotionPageId(url) {
877
+ const notionIdRegex = /[a-f0-9]{32}(?=\?|$)/;
878
+ const match = url.match(notionIdRegex);
879
+ const notionId = match ? match[0] : null;
880
+ if (!notionId) {
881
+ this.#toastService.error({
882
+ title: 'URL inválido',
883
+ subtitle: 'Por favor ingresa una URL válida de Notion.',
884
+ });
885
+ return null;
886
+ }
887
+ return notionId;
884
888
  }
885
- registerCustomComponentBuilder(component, name) {
886
- this._dynamicComponentBuilders[name || component.name] = component;
889
+ /**
890
+ * Links an existing lesson with a Notion page ID by updating the lesson's extras.
891
+ * @param lesson The current lesson data.
892
+ * @param notionPageId The Notion page ID to link.
893
+ * @returns The updated lesson data from the backend or undefined on failure.
894
+ */
895
+ async linkLessonWithNotion(lesson, notionPageId) {
896
+ if (!lesson || !lesson.id) {
897
+ // Ensure lesson exists and has an ID
898
+ this.#toastService.warn({ title: 'No se puede enlazar', subtitle: 'La lección debe existir y tener un ID.' });
899
+ return undefined;
900
+ }
901
+ const updatedLesson = {
902
+ ...lesson,
903
+ extensions: {
904
+ ...(lesson.extensions || {}),
905
+ extras: {
906
+ ...(lesson.extensions?.['extras'] || {}),
907
+ notionPageId: notionPageId,
908
+ },
909
+ },
910
+ };
911
+ this.isLoading.set(true);
912
+ try {
913
+ const savedLesson = await this.lessonsService.postLesson(updatedLesson);
914
+ this.#toastService.success({ title: 'Listo', subtitle: 'Se enlazó la lección con Notion.' });
915
+ return savedLesson;
916
+ }
917
+ catch (error) {
918
+ // Remove explicit type, rely on tsconfig setting
919
+ console.error('Error linking with Notion:', error);
920
+ this.#toastService.error({ title: 'Error al enlazar', subtitle: 'Ocurrió un error inesperado.' });
921
+ return undefined;
922
+ }
923
+ finally {
924
+ this.isLoading.set(false);
925
+ }
887
926
  }
888
- getDynamicComponentBuilders(type) {
889
- return this._dynamicComponentBuilders[type];
927
+ /**
928
+ * Handles the process of importing lesson content from Notion.
929
+ * It prompts the user for a URL if necessary, extracts the ID, fetches content,
930
+ * and potentially links the lesson if it's an existing one.
931
+ * @param currentLesson The current lesson data (can be a new or existing lesson).
932
+ * @param lessonId The current lesson ID (null if it's a new lesson).
933
+ * @returns The fetched HTML content from Notion, or null if the process fails.
934
+ */
935
+ async importAndLinkLessonFromNotion(currentLesson, lessonId) {
936
+ if (!currentLesson)
937
+ return null;
938
+ let notionPageId = null;
939
+ if (currentLesson.extensions?.['extras']?.['notionPageId']) {
940
+ const useExisting = confirm(`Ya tenemos el id ${currentLesson.extensions?.['extras']?.['notionPageId']} ¿Quieres usar este id para importar?`);
941
+ if (useExisting) {
942
+ notionPageId = currentLesson.extensions?.['extras']?.['notionPageId'];
943
+ }
944
+ else {
945
+ const inputUrl = prompt('Ingresa la NUEVA URL de Notion para importar (este ID NO se guardará automáticamente si la lección ya existe)');
946
+ if (!inputUrl)
947
+ return null; // User cancelled
948
+ notionPageId = this.extractNotionPageId(inputUrl);
949
+ }
950
+ }
951
+ else {
952
+ const inputUrl = prompt('Ingresa el URL de Notion para importar la lección (se enlazará si la lección ya existe)');
953
+ if (!inputUrl)
954
+ return null; // User cancelled
955
+ notionPageId = this.extractNotionPageId(inputUrl);
956
+ // Link automatically only if we got a valid Notion ID AND the lesson already exists (has an ID)
957
+ if (notionPageId && lessonId) {
958
+ const linkedLesson = await this.linkLessonWithNotion(currentLesson, notionPageId);
959
+ if (!linkedLesson) {
960
+ // Linking failed, maybe stop the import? Or proceed without linking?
961
+ // For now, let's stop.
962
+ this.#toastService.error({ title: 'Error de Enlace', subtitle: 'No se pudo enlazar con Notion antes de importar.' });
963
+ return null;
964
+ }
965
+ // If linking succeeded, the lesson object might have changed, but we proceed with the import using the notionPageId.
966
+ }
967
+ }
968
+ if (!notionPageId) {
969
+ this.#toastService.warn({ title: 'Sin ID de Notion', subtitle: 'No se proporcionó un ID de Notion válido para importar.' });
970
+ return null;
971
+ }
972
+ this.isLoading.set(true);
973
+ try {
974
+ this.#toastService.info({ title: 'Importando lección...', subtitle: 'Espera unos segundos' });
975
+ const md = await this.#notionService.getPageInSpecificFormat(notionPageId, NotionExportType.HTML);
976
+ console.log('Imported MD/HTML:', md);
977
+ this.#toastService.success({ title: 'Contenido Importado', subtitle: 'Contenido de Notion obtenido.' });
978
+ return md.content; // Return the fetched content
979
+ }
980
+ catch (error) {
981
+ // Remove explicit type, rely on tsconfig setting
982
+ console.error('Error importing from Notion:', error);
983
+ this.#toastService.error({ title: 'Error de importación', subtitle: 'Ocurrió un error inesperado.' });
984
+ return null; // Return null on failure
985
+ }
986
+ finally {
987
+ this.isLoading.set(false);
988
+ }
890
989
  }
891
- getDynamicComponentClass(type) {
892
- return this._dynamicComponents[type];
990
+ /**
991
+ * Fetches content from the linked Notion page for AI improvement (placeholder).
992
+ * @param lesson The current lesson data.
993
+ */
994
+ async improveLessonWithNotionAI(lesson) {
995
+ if (!lesson)
996
+ return;
997
+ const notionId = lesson.extensions?.['extras']?.['notionPageId'];
998
+ if (!notionId) {
999
+ this.#toastService.warn({ title: 'Sin ID de Notion', subtitle: 'Enlaza la lección con Notion primero.' });
1000
+ return;
1001
+ }
1002
+ this.isLoading.set(true);
1003
+ try {
1004
+ this.#toastService.info({ title: 'Mejorando con IA...', subtitle: 'Obteniendo contenido de Notion.' });
1005
+ const md = await this.#notionService.getPageInSpecificFormat(notionId, NotionExportType.HTML);
1006
+ console.log('Content to improve:', md);
1007
+ // TODO: Add actual AI improvement logic here
1008
+ // e.g., call another service: await this.aiImprovementService.improve(md.content);
1009
+ // Then potentially update the lesson via lessonService or return data
1010
+ this.#toastService.success({ title: 'Contenido Obtenido', subtitle: 'Listo para mejorar con IA (lógica no implementada).' });
1011
+ }
1012
+ catch (error) {
1013
+ // Remove explicit type, rely on tsconfig setting
1014
+ console.error('Error improving with AI:', error);
1015
+ this.#toastService.error({ title: 'Error de IA', subtitle: 'Ocurrió un error inesperado.' });
1016
+ }
1017
+ finally {
1018
+ this.isLoading.set(false);
1019
+ }
893
1020
  }
894
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
895
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsService, providedIn: 'root' }); }
1021
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonNotionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1022
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonNotionService, providedIn: 'root' }); }
896
1023
  }
897
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsService, decorators: [{
1024
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonNotionService, decorators: [{
898
1025
  type: Injectable,
899
1026
  args: [{
900
1027
  providedIn: 'root',
901
1028
  }]
902
1029
  }] });
903
1030
 
904
- class LessonRendererService {
1031
+ class LessonUtilsService {
905
1032
  constructor() {
906
- this.dynamicComponentsService = inject(DynamicComponentsService);
907
- this.toastrService = inject(TOAST_ALERTS_TOKEN);
908
- this.components = {};
909
- this.mainForm = new FormGroup({});
910
- }
911
- renderLesson(lessonData, viewContainerRef, dynamicLessonElement, renderer) {
912
- this.clearLessonRendering(dynamicLessonElement);
913
- console.log('Rendering lesson:', lessonData.id);
914
- const { htmlContent, components } = this.parseAndCreateComponents(lessonData, viewContainerRef);
915
- this.components = components;
916
- this.aggregateFormControls(this.components);
917
- dynamicLessonElement.innerHTML = htmlContent;
918
- this.injectComponentsIntoDom(this.components, renderer);
919
- return this.components;
1033
+ this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
1034
+ this.#toastService = inject(TOAST_ALERTS_TOKEN);
1035
+ this.#agentService = inject(CONVERSATION_AI_TOKEN);
1036
+ this.loadingBarService = inject(LoadingBarService);
920
1037
  }
921
- clearLessonRendering(dynamicLessonElement) {
922
- Object.values(this.components).forEach((compRef) => compRef.destroy());
923
- this.components = {};
924
- this.mainForm = new FormGroup({});
925
- if (dynamicLessonElement) {
926
- dynamicLessonElement.innerHTML = '';
927
- }
1038
+ #toastService;
1039
+ #agentService;
1040
+ /**
1041
+ * Validates if audios need generation for the given lesson.
1042
+ * Currently logs a placeholder message.
1043
+ * @param lesson The lesson object (or signal) to validate.
1044
+ */
1045
+ validateAudios(lesson) {
1046
+ // Access lesson data directly
1047
+ // if (!lesson?.components) {
1048
+ // return;
1049
+ // }
1050
+ alert('I need to refactor this part');
1051
+ // Placeholder logic - adapt as needed from original component
1052
+ console.log('Validating audios for lesson:', lesson.id);
928
1053
  }
929
- parseAndCreateComponents(lessonData, viewContainerRef) {
930
- const r1 = new RegExp('~(.+?)~', 'g');
931
- let count = 0;
932
- const createdComponents = {};
933
- const htmlContent = lessonData.textCoded.replace(r1, (_matching, jsonCoded) => {
934
- const componentName = `dynamicComp${count}`;
935
- count++;
936
- const componentRef = this.createComponentReferenceWithJson(jsonCoded, lessonData, viewContainerRef);
937
- if (!componentRef) {
938
- console.error(`Failed to create component for: ${jsonCoded}`);
939
- return '<!-- component creation failed -->';
940
- }
941
- createdComponents[componentName] = componentRef;
942
- return `<span id="${componentName}"></span>`;
1054
+ /**
1055
+ * Updates the lesson signal with a new banner image in metadata.
1056
+ * @param lessonSignal The signal holding the lesson data.
1057
+ * @param imageUploaded The image data object from the upload event. Should conform to LessonImage structure partially.
1058
+ */
1059
+ uploadCover(lessonSignal, imageUploaded) {
1060
+ lessonSignal.update((currentLesson) => {
1061
+ if (!currentLesson)
1062
+ return undefined;
1063
+ const assets = { ...(currentLesson.assets ?? {}) };
1064
+ assets.banner = imageUploaded;
1065
+ return {
1066
+ ...currentLesson,
1067
+ assets,
1068
+ };
943
1069
  });
944
- return { htmlContent, components: createdComponents };
945
1070
  }
946
- createComponentReferenceWithJson(json, currentLesson, viewContainerRef) {
947
- try {
948
- let lessonCodedConfig = JSON.parse(json);
949
- if (lessonCodedConfig.id && currentLesson?.dynamicComponents[lessonCodedConfig.id]) {
950
- const foundConfig = currentLesson.dynamicComponents[lessonCodedConfig.id];
951
- if (foundConfig) {
952
- lessonCodedConfig = { ...lessonCodedConfig, ...foundConfig };
1071
+ // I want to deprecate this method
1072
+ /**
1073
+ * @deprecated Use create new method.
1074
+ * Generates lesson content using AI. Assumes the lesson is already saved.
1075
+ * @param lessonId The ID of the lesson to generate content for.
1076
+ * @returns The updated lesson object after AI generation, or null on failure.
1077
+ */
1078
+ async generateByAI(lessonId) {
1079
+ if (!lessonId) {
1080
+ this.#toastService.warn({ title: 'ID Requerido', subtitle: 'Se necesita un ID de lección para usar IA.' });
1081
+ return null;
1082
+ }
1083
+ // No need to save here, component should ensure it's saved before calling.
1084
+ try {
1085
+ await this.lessonsService.postGenerateByAI(lessonId);
1086
+ // Re-fetch the lesson data to get AI updates
1087
+ const updatedLesson = await this.lessonsService.getLesson(lessonId);
1088
+ if (updatedLesson) {
1089
+ this.#toastService.success({ title: 'IA completada', subtitle: 'Lección actualizada con IA.' });
1090
+ return updatedLesson;
1091
+ }
1092
+ else {
1093
+ throw new Error('Failed to fetch lesson after AI generation');
1094
+ }
1095
+ }
1096
+ catch (error) {
1097
+ // Type the error object
1098
+ console.error('Error during AI generation in service:', error);
1099
+ this.#toastService.error({ title: 'Error de IA', subtitle: 'No se pudo generar la lección con IA.' });
1100
+ return null; // Return null in catch block
1101
+ }
1102
+ // Loading state should be managed by the component calling this service.
1103
+ }
1104
+ /**
1105
+ * Improves the provided Markdown text using AI and updates the lesson.
1106
+ * Assumes the lesson is already saved before calling this.
1107
+ * @param lesson The lesson object containing the prompt and other context.
1108
+ * @param markdownText The Markdown text generated from the lesson's HTML content.
1109
+ * @returns The improved Markdown string, or null on failure.
1110
+ */
1111
+ async improveMDWithAI(lesson, markdownText) {
1112
+ if (!markdownText) {
1113
+ this.#toastService.warn({ title: 'Texto Requerido', subtitle: 'Se necesita texto Markdown para mejorar con IA.' });
1114
+ return null;
1115
+ }
1116
+ try {
1117
+ this.loadingBarService.showIndeterminate();
1118
+ const textPrompt = this.lessonsService.getPrompts().content(lesson);
1119
+ const messages = [{ content: textPrompt, role: ChatRole.User }];
1120
+ const response = await this.#agentService.callChatCompletion({ messages, model: { provider: 'google' } });
1121
+ let improvedMarkdown = response.content?.trim() ?? null;
1122
+ // Remove potential markdown fences
1123
+ const markdownFenceStart = '```markdown\n';
1124
+ const markdownFenceEnd = '\n```';
1125
+ if (improvedMarkdown?.startsWith(markdownFenceStart) && improvedMarkdown.endsWith(markdownFenceEnd)) {
1126
+ improvedMarkdown = improvedMarkdown.slice(markdownFenceStart.length, -markdownFenceEnd.length);
1127
+ }
1128
+ else {
1129
+ // Also handle cases where it might just be ``` at the start/end
1130
+ const simpleFence = '```';
1131
+ if (improvedMarkdown?.startsWith(simpleFence)) {
1132
+ improvedMarkdown = improvedMarkdown.slice(simpleFence.length);
1133
+ }
1134
+ if (improvedMarkdown?.endsWith(simpleFence)) {
1135
+ improvedMarkdown = improvedMarkdown.slice(0, -simpleFence.length);
953
1136
  }
954
1137
  }
955
- const LessonClass = this.dynamicComponentsService.getDynamicComponentClass(lessonCodedConfig.component);
956
- if (!LessonClass) {
957
- console.error(`Component class not found for type: ${lessonCodedConfig.component}. JSON: ${json}`);
1138
+ // Trim any leading/trailing whitespace that might remain after removing fences
1139
+ improvedMarkdown = improvedMarkdown?.trim() ?? null;
1140
+ console.log('Improved Markdown:', improvedMarkdown);
1141
+ return improvedMarkdown; // Return only the string
1142
+ }
1143
+ catch (error) {
1144
+ console.error('Error during AI Markdown improvement in service:', error);
1145
+ this.#toastService.error({ title: 'Error de IA', subtitle: 'No se pudo mejorar la lección con IA.' });
1146
+ return null; // Return null in catch block
1147
+ }
1148
+ finally {
1149
+ this.loadingBarService.successAndHide();
1150
+ }
1151
+ }
1152
+ /**
1153
+ * Generates a concise description for the lesson using AI based on its content.
1154
+ * @param lesson The lesson object containing the content.
1155
+ * @returns A promise resolving to the generated description string, or null on failure.
1156
+ */
1157
+ async generateDescriptionWithAI(lesson) {
1158
+ if (!lesson || !lesson.textCoded) {
1159
+ this.#toastService.warn({ title: 'Contenido Requerido', subtitle: 'Se necesita contenido en la lección para generar una descripción.' });
1160
+ return null;
1161
+ }
1162
+ try {
1163
+ this.loadingBarService.showIndeterminate(); // Corrected: No argument needed
1164
+ // Extract plain text from the lesson's HTML content
1165
+ const plainTextContent = this._extractTextFromHtml(lesson.textCoded);
1166
+ if (!plainTextContent || plainTextContent.trim().length === 0) {
1167
+ this.#toastService.warn({ title: 'Texto Vacío', subtitle: 'No se pudo extraer texto útil del contenido de la lección.' });
958
1168
  return null;
959
1169
  }
960
- const componentRef = viewContainerRef.createComponent(LessonClass);
961
- if (lessonCodedConfig.inputs) {
962
- for (const key in lessonCodedConfig.inputs) {
963
- if (lessonCodedConfig.inputs.hasOwnProperty(key)) {
964
- componentRef.instance[key] = lessonCodedConfig.inputs[key];
965
- }
966
- }
1170
+ const descriptionPrompt = this.lessonsService.getPrompts().description(lesson);
1171
+ const messages = [{ content: descriptionPrompt, role: ChatRole.User }];
1172
+ const response = await this.#agentService.callChatCompletion({ messages, model: { provider: 'google' } });
1173
+ let generatedDescription = response.content?.trim() ?? null;
1174
+ // Basic cleanup (remove potential quotes if the AI wraps the response)
1175
+ if (generatedDescription && generatedDescription.startsWith('"') && generatedDescription.endsWith('"')) {
1176
+ generatedDescription = generatedDescription.substring(1, generatedDescription.length - 1);
967
1177
  }
968
- if (lessonCodedConfig.settings) {
969
- componentRef.instance.config = lessonCodedConfig;
1178
+ if (generatedDescription) {
1179
+ this.#toastService.success({ title: 'Descripción Generada', subtitle: 'Se generó la descripción con IA.' });
1180
+ return generatedDescription;
1181
+ }
1182
+ else {
1183
+ throw new Error('AI did not return a valid description.');
970
1184
  }
971
- return componentRef;
972
1185
  }
973
1186
  catch (error) {
974
- console.error(`Error processing component JSON: ${json}`, error);
1187
+ console.error('Error during AI description generation in service:', error);
1188
+ this.#toastService.error({ title: 'Error de IA', subtitle: 'No se pudo generar la descripción con IA.' });
975
1189
  return null;
976
1190
  }
1191
+ finally {
1192
+ this.loadingBarService.successAndHide();
1193
+ }
977
1194
  }
978
- aggregateFormControls(components) {
979
- const newFormControls = {};
980
- Object.entries(components).forEach(([name, componentRef]) => {
981
- if (componentRef.instance?.control instanceof FormControl) {
982
- newFormControls[name] = componentRef.instance.control;
983
- newFormControls[name].addValidators(Validators.required);
1195
+ /**
1196
+ * Extracts plain text from an HTML string, removing encoded JSON components.
1197
+ * @param htmlInput The HTML string potentially containing encoded JSON (~...~).
1198
+ * @returns The plain text representation.
1199
+ */
1200
+ _extractTextFromHtml(htmlInput) {
1201
+ if (!htmlInput)
1202
+ return '';
1203
+ // 1. Replace encoded JSON blocks with their 'settings.text' or an empty string
1204
+ const jsonRegex = /~(.+?)~/g;
1205
+ const textWithPlaceholders = htmlInput.replace(jsonRegex, (match, jsonCoded) => {
1206
+ try {
1207
+ const decodedJson = jsonCoded.replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
1208
+ const data = JSON.parse(decodedJson);
1209
+ return data?.settings?.text || ''; // Return text or empty string if not found
1210
+ }
1211
+ catch (error) {
1212
+ console.error('Failed to parse JSON inside HTML during text extraction:', jsonCoded, error);
1213
+ return ''; // Return empty string on error
984
1214
  }
985
1215
  });
986
- this.mainForm = new FormGroup(newFormControls);
1216
+ // 2. Strip remaining HTML tags to get plain text
1217
+ // Create a temporary DOM element to parse the HTML
1218
+ const tempDiv = document.createElement('div');
1219
+ tempDiv.innerHTML = textWithPlaceholders;
1220
+ // Use textContent to get the plain text, effectively stripping tags
1221
+ // Replace multiple whitespace characters (including newlines) with a single space
1222
+ return (tempDiv.textContent || tempDiv.innerText || '').replace(/\s+/g, ' ').trim();
987
1223
  }
988
- injectComponentsIntoDom(components, renderer) {
989
- Object.entries(components).forEach(([name, componentRef]) => {
990
- const elementRef = document.getElementById(name);
991
- if (elementRef) {
992
- this.addComponentToNode(componentRef, elementRef, renderer);
1224
+ /**
1225
+ * Cleans orphaned components from the lesson's dynamicComponents.
1226
+ * An orphaned component is one present in dynamicComponents but its ID is not found in the textCoded HTML.
1227
+ * @param lesson The lesson object to clean.
1228
+ * @returns A new lesson object with orphaned components removed from dynamicComponents.
1229
+ */
1230
+ cleanOrphanedComponents(lesson) {
1231
+ if (!lesson || !lesson.textCoded || !lesson.dynamicComponents) {
1232
+ // Return the original lesson if essential parts are missing
1233
+ return lesson;
1234
+ }
1235
+ const textCoded = lesson.textCoded;
1236
+ const existingComponents = lesson.dynamicComponents;
1237
+ const existingComponentIds = new Set(Object.keys(existingComponents));
1238
+ // Regex to find "id":"<component_id>" within the textCoded string
1239
+ const idRegex = /"id":"([^"]+)"/g;
1240
+ const foundIdsInText = new Set();
1241
+ let match;
1242
+ while ((match = idRegex.exec(textCoded)) !== null) {
1243
+ const potentialId = match[1];
1244
+ // Check if the found ID actually exists in our dynamic components map
1245
+ // This ensures we only count IDs that correspond to known components.
1246
+ if (existingComponentIds.has(potentialId)) {
1247
+ foundIdsInText.add(potentialId);
1248
+ }
1249
+ }
1250
+ const orphanedIds = [];
1251
+ const cleanedDynamicComponents = {};
1252
+ // Iterate through existing component IDs
1253
+ for (const componentId of existingComponentIds) {
1254
+ if (foundIdsInText.has(componentId)) {
1255
+ // Keep the component if its ID was found in the text
1256
+ cleanedDynamicComponents[componentId] = existingComponents[componentId];
993
1257
  }
994
1258
  else {
995
- console.warn(`Placeholder element with ID '${name}' not found in the DOM.`);
1259
+ // Mark as orphaned if not found
1260
+ orphanedIds.push(componentId);
996
1261
  }
1262
+ }
1263
+ if (orphanedIds.length > 0) {
1264
+ console.warn(`[LessonUtilsService] Orphaned components detected and will be removed from lesson data (IDs not found in textCoded):`, orphanedIds);
1265
+ this.#toastService.warn({
1266
+ title: 'Componentes Huérfanos Detectados',
1267
+ subtitle: `Se removerán ${orphanedIds.length} componentes no usados del editor.`,
1268
+ // life: 5000, // Removed 'life' property as it might not be supported by ToastData
1269
+ });
1270
+ }
1271
+ // Return a new lesson object with the cleaned components
1272
+ return {
1273
+ ...lesson,
1274
+ dynamicComponents: cleanedDynamicComponents,
1275
+ };
1276
+ }
1277
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonUtilsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1278
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonUtilsService, providedIn: 'root' }); }
1279
+ }
1280
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonUtilsService, decorators: [{
1281
+ type: Injectable,
1282
+ args: [{
1283
+ providedIn: 'root', // Provide globally or in a specific module if preferred
1284
+ }]
1285
+ }] });
1286
+
1287
+ // TODO: check LessonComponentBuilders in lessons.clases.ts for origianl implementation
1288
+ const DefaultDynamicComponentBuilders = {
1289
+ [LessonComponentEnum.Speaker]: SpeakerBuilderComponent,
1290
+ [LessonComponentEnum.Selector]: SelectorBuilderComponent,
1291
+ };
1292
+ const DefaultDynamicComponents = {
1293
+ [LessonComponentEnum.Speaker]: SpeakerComponent,
1294
+ [LessonComponentEnum.Selector]: SelectorComponent,
1295
+ };
1296
+ class DynamicComponentsRegisterService {
1297
+ constructor() {
1298
+ this._dynamicComponentBuilders = {};
1299
+ this._dynamicComponents = {};
1300
+ this.registerComponentAndBuilder(DefaultDynamicComponents, DefaultDynamicComponentBuilders);
1301
+ }
1302
+ registerComponentAndBuilder(components, builders) {
1303
+ Object.entries(components).forEach(([key, value]) => {
1304
+ this.registerCustomComponent(value, key);
1305
+ });
1306
+ Object.entries(builders).forEach(([key, value]) => {
1307
+ this.registerCustomComponentBuilder(value, key);
997
1308
  });
998
1309
  }
999
- addComponentToNode(componentRef, nodeDOM, renderer) {
1000
- renderer.appendChild(nodeDOM, componentRef.location.nativeElement);
1310
+ registerCustomComponent(component, name) {
1311
+ // this._dynamicComponentBuilders[name || component.name] = component;
1312
+ this._dynamicComponents[name || component.name] = component;
1001
1313
  }
1002
- evaluateForms() {
1003
- this.mainForm.markAllAsTouched();
1004
- if (!this.mainForm.valid) {
1005
- Object.keys(this.mainForm.controls).forEach((controlName) => {
1006
- if (this.components[controlName]?.instance?.validate) {
1007
- this.components[controlName].instance.validate();
1008
- }
1009
- });
1010
- this.toastrService.warn({ subtitle: 'Por favor completa todos los ejercicios', title: 'Incompleto' });
1011
- return null;
1012
- }
1013
- const rates = { correct: 0, incorrect: 0, score: 0 };
1014
- Object.keys(this.mainForm.controls).forEach((controlName) => {
1015
- const instance = this.components[controlName]?.instance;
1016
- if (instance && typeof instance.evaluate === 'function') {
1017
- try {
1018
- const result = instance.evaluate();
1019
- if (result) {
1020
- rates.correct++;
1021
- }
1022
- else {
1023
- rates.incorrect++;
1024
- }
1025
- }
1026
- catch (err) {
1027
- console.error('Error during evaluation for component:', controlName, instance, err);
1028
- rates.incorrect++;
1029
- }
1030
- }
1031
- });
1032
- const totalQuestions = rates.correct + rates.incorrect;
1033
- rates.score = totalQuestions > 0 ? rates.correct / totalQuestions : 0;
1034
- const status = rates.score >= 0.7 ? 'passed' : 'failed';
1035
- if (status === 'passed') {
1036
- this.toastrService.success({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%.`, title: '¡Muy bien!' });
1037
- }
1038
- else {
1039
- this.toastrService.warn({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%. Revisa tus respuestas.`, title: 'Casi lo logras' });
1040
- }
1041
- return { rates, takenLesson: null };
1314
+ registerCustomComponentBuilder(component, name) {
1315
+ this._dynamicComponentBuilders[name || component.name] = component;
1042
1316
  }
1043
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonRendererService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1044
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonRendererService, providedIn: 'root' }); }
1317
+ getDynamicComponentBuilders(type) {
1318
+ return this._dynamicComponentBuilders[type];
1319
+ }
1320
+ getDynamicComponentClass(type) {
1321
+ return this._dynamicComponents[type];
1322
+ }
1323
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsRegisterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1324
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsRegisterService, providedIn: 'root' }); }
1045
1325
  }
1046
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonRendererService, decorators: [{
1326
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsRegisterService, decorators: [{
1047
1327
  type: Injectable,
1048
1328
  args: [{
1049
1329
  providedIn: 'root',
1050
1330
  }]
1051
- }] });
1052
-
1053
- const EnglishEvaluationSkill = `
1054
- You are a virtual professor for the Polilan English learning app. Your primary role is to evaluate a student's English conversational performance.
1055
-
1056
- You will be provided with two pieces of information for each evaluation:
1057
- 1. **The student's English level:** This will be one of "Beginner", "Intermediate", or "Advanced".
1058
- 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: ...").
1059
-
1060
- Your sole task is to evaluate the *Student's* contribution to the conversation. **Do NOT evaluate the Assistant's turns.**
1061
-
1062
- 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:
1063
-
1064
- * **Grammar & Syntax:** Evaluate accuracy and complexity *relative to the expected level*. (Beginners: focus on basic accuracy; Advanced: expect complex structures with high accuracy).
1065
- * **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).
1066
- * **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).
1067
- * **Task Completion/Relevance:** Evaluate how effectively they participate and respond to the conversation's goals or topics *relative to the expected level*.
1068
-
1069
- Assign a rating from 0 to 3 based on how well the student performs *compared to the typical expectations for their provided level*:
1070
-
1071
- * **0: Bad** - Performance is significantly below what is expected for this level. Many errors impede communication.
1072
- * **1: Not Bad** - Performance is below expectations for this level, but communication is sometimes possible despite frequent errors.
1073
- * **2: Good** - Performance meets expectations for this level, demonstrating solid understanding and usage with only occasional minor errors.
1074
- * **3: Very Good** - Performance exceeds expectations for this level, demonstrating a strong command and potential to progress quickly.
1075
-
1076
- 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.
1077
- `;
1331
+ }], ctorParameters: () => [] });
1078
1332
 
1079
- const EngagementSkill = `
1080
- 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.
1081
- that makes sense for the lesson.
1082
- `;
1083
- class LessonConversationService {
1084
- constructor() {
1085
- this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
1086
- this.userService = inject(UserService);
1087
- this.conversationSettings = signal(undefined, ...(ngDevMode ? [{ debugName: "conversationSettings" }] : []));
1088
- this.conversationFlow = signal(undefined, ...(ngDevMode ? [{ debugName: "conversationFlow" }] : []));
1089
- this.evalAgentTask = signal(undefined, ...(ngDevMode ? [{ debugName: "evalAgentTask" }] : []));
1090
- }
1091
- initializeConversationFlow() {
1092
- this.evalAgentTask.set({
1093
- systemPrompt: EnglishEvaluationSkill,
1094
- model: { provider: 'google' },
1095
- task: `Please evaluate the user conversation student data is: \n ${this.userService.getUserDataInformation()}`,
1096
- expectedResponseType: EvalResultStringDefinition,
1097
- });
1098
- this.conversationFlow.set({
1099
- goal: {
1100
- enabled: true,
1101
- task: `User is learning is taking a lesson about languages, evaluate how good are the responses.`,
1102
- model: { quality: EModelQuality.FAST },
1103
- },
1104
- triggerTasks: {
1105
- [ConversationEvents.OnUserMessage]: {
1106
- enabled: true,
1107
- ...this.evalAgentTask(),
1108
- },
1333
+ class DynamicComponentsBuilderService {
1334
+ #dialogService = inject(DialogService);
1335
+ #toastService = inject(TOAST_ALERTS_TOKEN);
1336
+ #dynamicComponentsService = inject(DynamicComponentsRegisterService);
1337
+ openComponentBuilder(componentType, data = null) {
1338
+ const componentToBuild = this.#dynamicComponentsService.getDynamicComponentBuilders(componentType);
1339
+ if (!componentToBuild) {
1340
+ console.error(`No component builder found for type: ${componentType}`);
1341
+ this.#toastService.error({ title: 'Error', subtitle: `Componente desconocido: ${componentType}` });
1342
+ return undefined;
1343
+ }
1344
+ const dialogRef = this.#dialogService.open(componentToBuild, {
1345
+ inputValues: {
1346
+ // inputValues was removed in newer PrimeNG versions, use 'data'
1347
+ inputs: data?.inputs,
1348
+ id: data?.id,
1109
1349
  },
1110
- challenges: null,
1111
- dynamicConditions: [
1112
- {
1113
- what: ConditionType.Goal,
1114
- when: ConditionOperator.GreaterThanOrEqual,
1115
- value: 100,
1116
- do: [
1117
- {
1118
- actionType: EDoActionType.ChangePrompt,
1119
- systemPromptType: SystemPromptType.SystemPrompt,
1120
- 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.',
1121
- },
1122
- {
1123
- actionType: EDoActionType.ChangePrompt,
1124
- systemPromptType: SystemPromptType.CharacterDescription,
1125
- prompt: '',
1126
- },
1127
- {
1128
- actionType: EDoActionType.ChangePrompt,
1129
- systemPromptType: SystemPromptType.UserInformation,
1130
- prompt: '',
1131
- },
1132
- {
1133
- actionType: EDoActionType.ChangePrompt,
1134
- systemPromptType: SystemPromptType.MessageExamples,
1135
- prompt: '',
1136
- },
1137
- {
1138
- actionType: EDoActionType.ChangePrompt,
1139
- systemPromptType: SystemPromptType.ScenarioDescription,
1140
- prompt: '',
1141
- },
1142
- ],
1143
- },
1144
- ],
1145
- moodStateEnabled: false,
1350
+ width: '80vw',
1351
+ header: 'Agregar componente',
1352
+ closable: true,
1146
1353
  });
1354
+ return dialogRef;
1147
1355
  }
1148
- async startAI(lesson, settings) {
1149
- console.log('Requesting agent cards from LessonAIService...');
1150
- try {
1151
- const conversationSettings = await this.generateConversationSettingsForLesson(lesson, settings);
1152
- if (conversationSettings) {
1153
- this.conversationSettings.set(conversationSettings);
1154
- console.log('Agent cards received and set.');
1155
- return conversationSettings;
1356
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsBuilderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1357
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsBuilderService, providedIn: 'root' }); }
1358
+ }
1359
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsBuilderService, decorators: [{
1360
+ type: Injectable,
1361
+ args: [{
1362
+ providedIn: 'root',
1363
+ }]
1364
+ }] });
1365
+
1366
+ class DynamicComponentBuilderDialogComponent {
1367
+ #config;
1368
+ constructor(ref) {
1369
+ this.ref = ref;
1370
+ this.#config = inject(DynamicDialogConfig);
1371
+ this.dynamicComponentsRegisterService = inject(DynamicComponentsRegisterService);
1372
+ }
1373
+ ngOnInit() {
1374
+ const componentType = this.#config.data.type;
1375
+ if (componentType) {
1376
+ const componentClass = this.dynamicComponentsRegisterService.getDynamicComponentBuilders(componentType);
1377
+ if (componentClass) {
1378
+ this.container.clear();
1379
+ this.componentRef = this.container.createComponent(componentClass);
1156
1380
  }
1157
1381
  else {
1158
- console.error('Failed to generate agent cards (service returned null).');
1159
- return null;
1382
+ console.error(`Component class not found for type: ${componentType}`);
1160
1383
  }
1161
1384
  }
1162
- catch (error) {
1163
- console.error('Error generating agent cards:', error);
1164
- return null;
1385
+ }
1386
+ copyCode() {
1387
+ // navigator.clipboard.writeText(this.#config.data.type);
1388
+ alert('Copied to clipboard');
1389
+ if (this.componentRef) {
1390
+ // You can now access any public property on the component instance
1391
+ // For example, if your dynamic component has a 'properties' property:
1392
+ console.log('Component properties: ', this.componentRef.instance.getComponentData());
1393
+ const dynamicObjectData = this.componentRef.instance.getComponentData();
1165
1394
  }
1395
+ console.log(this.container);
1166
1396
  }
1167
- /**
1168
- * Builds the scenario prompt string based on lesson content and user info.
1169
- * @param lessonText The extracted text content of the lesson.
1170
- * @param userInformationPrompt The formatted string containing user details.
1171
- * @returns The complete scenario prompt string.
1172
- */
1173
- _buildLessonInstructionsPrompt(lessonText, userInformationPrompt, settings) {
1174
- return `
1175
- ${settings.instructionsPrompt}
1176
-
1177
- <Lesson Text>
1178
- ${lessonText}
1179
- </Lesson Text>
1180
-
1181
- ${EngagementSkill}
1397
+ addToEnd() {
1398
+ if (this.componentRef) {
1399
+ const dynamicObjectData = this.componentRef.instance.getComponentData();
1400
+ this.ref.close(dynamicObjectData);
1401
+ }
1402
+ }
1403
+ 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 }); }
1404
+ 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"] }] }); }
1405
+ }
1406
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentBuilderDialogComponent, decorators: [{
1407
+ type: Component,
1408
+ 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" }]
1409
+ }], ctorParameters: () => [{ type: i1$4.DynamicDialogRef }], propDecorators: { container: [{
1410
+ type: ViewChild,
1411
+ args: ['container', { read: ViewContainerRef, static: true }]
1412
+ }] } });
1182
1413
 
1183
- <User Information>
1184
- ${userInformationPrompt}`;
1414
+ class DCLessonComponentAdderComponent {
1415
+ constructor() {
1416
+ // Services
1417
+ this.dynamicComponentsBuilderService = inject(DynamicComponentsBuilderService);
1418
+ this.dialogService = inject(DialogService);
1419
+ this.componentAdded = new EventEmitter(); // Changed Output name and type
1420
+ this.onNewDynamicComponent = new EventEmitter();
1421
+ // Expose enum to the template
1422
+ this.lessonComponentEnum = LessonComponentEnum;
1185
1423
  }
1186
- /**
1187
- * Generates conversation settings for a lesson, using the lesson scenario as the initial prompt.
1188
- * @param lesson The lesson data.
1189
- * @returns An IConversationSettings object configured for the lesson scenario.
1190
- */
1191
- async generateConversationSettingsForLesson(lesson, settings) {
1192
- // TODO: Consolidate user fetching logic if possible, or ensure consistency
1193
- const baseLang = this.userService.user().settings.baseLanguage || 'en';
1194
- let lessonText = '';
1195
- if (lesson.textCoded) {
1196
- lessonText = this.lessonsService.extractTextFromHtml(lesson.textCoded);
1424
+ // Logic to open component builder, now utilizing DynamicComponentsBuilderService
1425
+ openComponentBuilder(type) {
1426
+ const dialogRef = this.dynamicComponentsBuilderService.openComponentBuilder(type);
1427
+ if (dialogRef) {
1428
+ // Handle the result and emit the new event
1429
+ dialogRef.onClose.subscribe((result) => {
1430
+ if (result) {
1431
+ console.log('Component builder closed:', result);
1432
+ this.componentAdded.emit(result); // Emit the result when dialog closes successfully
1433
+ }
1434
+ });
1197
1435
  }
1198
1436
  else {
1199
- lessonText = lesson.markdown;
1200
- }
1201
- const userInformationPrompt = this.userService.getUserDataInformation();
1202
- let lessonInstructionsPrompt = this._buildLessonInstructionsPrompt(lessonText, userInformationPrompt, settings);
1203
- if (settings.additionalPrompt) {
1204
- lessonInstructionsPrompt += '\n\n' + settings.additionalPrompt;
1437
+ // Optional: Log if the dialog couldn't be opened, though the service already logs an error.
1438
+ console.warn(`Dialog could not be opened for type via component: ${type}`);
1205
1439
  }
1206
- const initialMessage = {
1207
- role: ChatRole.System,
1208
- content: lessonInstructionsPrompt,
1209
- messageId: SystemPromptType.SystemPrompt,
1210
- };
1211
- // Use defaults similar to DEFAULT_LESSON_AGENT_CARD but adjust for prompt-based start
1212
- const conversationSettings = {
1213
- conversationType: ConversationType.General,
1214
- textEngine: TextEngines.SimpleText,
1215
- autoStart: true,
1216
- messages: [initialMessage],
1217
- model: { provider: 'google' },
1218
- tts: { voice: getRandomQuickVoice(baseLang || 'en', 'female') },
1219
- };
1220
- return conversationSettings;
1221
1440
  }
1222
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonConversationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1223
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonConversationService, providedIn: 'root' }); }
1224
- }
1225
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonConversationService, decorators: [{
1226
- type: Injectable,
1227
- args: [{
1228
- providedIn: 'root',
1229
- }]
1230
- }] });
1231
-
1232
- class DCLessonRendererComponent {
1233
- constructor() {
1234
- // --- Signal Inputs ---
1235
- this.lessonInput = input(...(ngDevMode ? [undefined, { debugName: "lessonInput" }] : [])); // Input signal for lesson object
1236
- this.lessonIdInput = input(...(ngDevMode ? [undefined, { debugName: "lessonIdInput" }] : [])); // Input signal for lesson ID
1237
- this.settings = input(...(ngDevMode ? [undefined, { debugName: "settings" }] : []));
1238
- // --- Outputs ---
1239
- this.wordClicked = new EventEmitter(); // New output event
1240
- // --- View Childs ---
1241
- this.dynamicLesson = viewChild('dynamicLesson', ...(ngDevMode ? [{ debugName: "dynamicLesson" }] : []));
1242
- // --- Services ---
1243
- this.renderer = inject(Renderer2);
1244
- this.viewContainerRef = inject(ViewContainerRef);
1245
- this.toastrService = inject(TOAST_ALERTS_TOKEN);
1246
- this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
1247
- this.uiStateService = inject(UiStateService);
1248
- this.lessonRendererService = inject(LessonRendererService);
1249
- this.lessonConversationService = inject(LessonConversationService);
1250
- // --- State Signals ---
1251
- this.lesson = signal(undefined, ...(ngDevMode ? [{ debugName: "lesson" }] : [])); // Internal lesson state signal
1252
- // --- Computed Signals ---
1253
- this.imageCover = computed(() => this.lesson()?.media?.images?.find((img) => img.type === 'cover')?.url, ...(ngDevMode ? [{ debugName: "imageCover" }] : [])); // Computed signal for imageCover
1254
- // --- Properties ---
1255
- this.components = {};
1256
- this.mainForm = new FormGroup({});
1257
- this.previousTextCoded = undefined; // Store previous value
1258
- // Effect to fetch lesson data if ID is provided and lesson object isn't
1259
- effect(async () => {
1260
- const lessonInput = this.lessonInput();
1261
- const lessonId = this.lessonIdInput();
1262
- if (lessonInput) {
1263
- this.lesson.set(lessonInput); // Use input lesson directly
1264
- }
1265
- else if (lessonId && !this.lesson()) {
1266
- // Fetch only if ID exists and internal lesson is not set
1267
- console.log(`[Renderer] Effect 1: Fetching lesson ${lessonId}`);
1268
- try {
1269
- // Consider adding a loading state signal here
1270
- const fetchedLesson = await this.lessonsService.getLesson(lessonId);
1271
- this.lesson.set(fetchedLesson);
1272
- console.log('Fetched lesson:', fetchedLesson);
1273
- }
1274
- catch (error) {
1275
- console.error(`Failed to fetch lesson with ID: ${lessonId}`, error);
1276
- this.toastrService.error({ subtitle: 'Failed to load lesson data.', title: 'Error' });
1277
- this.lesson.set(undefined); // Reset lesson on error
1278
- }
1279
- finally {
1280
- // Reset loading state signal here
1281
- }
1282
- }
1283
- else if (!lessonInput && !lessonId) {
1284
- // Handle case where neither input is provided, maybe clear the lesson?
1285
- this.lesson.set(undefined);
1286
- }
1287
- }, { allowSignalWrites: true }); // Allow signal writes inside effect
1288
- // Effect to render the lesson only when textCoded value actually changes
1289
- effect(() => {
1290
- const dynamicLessonEl = this.dynamicLesson();
1291
- const currentLesson = this.lesson(); // Read the lesson signal
1292
- if (!dynamicLessonEl || !currentLesson) {
1293
- return;
1294
- }
1295
- if (currentLesson?.format === 'markdown') {
1296
- // If markdown content exists, we clear any dynamically rendered components
1297
- // and prevent the textCoded logic from running.
1298
- if (this.previousTextCoded) {
1299
- this.lessonRendererService.clearLessonRendering(dynamicLessonEl.nativeElement);
1300
- this.mainForm = this.lessonRendererService.mainForm;
1301
- this.previousTextCoded = undefined;
1302
- }
1303
- return;
1304
- }
1305
- const newTextCoded = currentLesson?.textCoded; // Get the current textCoded value
1306
- // Quick fix to only render on changes textCode not all the lesson
1307
- if (newTextCoded !== this.previousTextCoded) {
1308
- if (newTextCoded !== undefined && newTextCoded !== null && currentLesson) {
1309
- this.components = this.lessonRendererService.renderLesson(currentLesson, this.viewContainerRef, dynamicLessonEl.nativeElement, this.renderer);
1310
- this.mainForm = this.lessonRendererService.mainForm;
1311
- }
1312
- else {
1313
- this.lessonRendererService.clearLessonRendering(dynamicLessonEl.nativeElement);
1314
- this.mainForm = this.lessonRendererService.mainForm;
1315
- }
1316
- // Update the stored previous value *after* comparison and action
1317
- this.previousTextCoded = newTextCoded;
1318
- }
1319
- else {
1320
- console.log('[Renderer] textCoded has NOT changed. Skipping render/clear.');
1321
- }
1441
+ openComponentBuilder2(type) {
1442
+ this.ref = this.dialogService.open(DynamicComponentBuilderDialogComponent, {
1443
+ header: 'Select a Component',
1444
+ width: '60vw',
1445
+ modal: true,
1446
+ closable: true,
1447
+ data: {
1448
+ type: type,
1449
+ },
1322
1450
  });
1323
- }
1324
- ngOnInit() {
1325
- this.lessonConversationService.initializeConversationFlow();
1326
- }
1327
- async evaluateForms() {
1328
- const result = this.lessonRendererService.evaluateForms();
1329
- if (!result) {
1330
- return;
1331
- }
1332
- const currentLesson = this.lesson();
1333
- if (!currentLesson) {
1334
- console.error('Cannot save result, lesson data is missing.');
1335
- this.toastrService.error({ subtitle: 'Cannot save result, lesson data missing.', title: 'Error' });
1336
- return;
1337
- }
1338
- const status = result.rates.score >= 0.7 ? 'passed' : 'failed';
1339
- const takenLesson = {
1340
- id: currentLesson.id,
1341
- goalCompleted: null,
1342
- score: result.rates.score,
1343
- status: status,
1344
- lastAccess: new Date(),
1345
- };
1346
- console.log('Lesson evaluation result:', takenLesson);
1347
- // TODO: Re-implement saving the taken lesson status via lessonService
1348
- }
1349
- // --- AI Chat Logic ---
1350
- async startAI() {
1351
- const currentLesson = this.lesson();
1352
- if (!currentLesson) {
1353
- console.error('Cannot start AI without a lesson.');
1354
- this.toastrService.error({ subtitle: 'Lesson data not available.', title: 'Cannot Start Chat' });
1355
- return;
1356
- }
1357
- const conversationSettings = await this.lessonConversationService.startAI(currentLesson, this.settings());
1358
- if (conversationSettings) {
1359
- this.uiStateService.chatDrawerVisible.set(true);
1360
- }
1361
- else {
1362
- this.toastrService.error({ subtitle: 'Could not prepare the AI chat session.', title: 'Error' });
1363
- }
1364
- }
1365
- onVisibleChange(isVisible) {
1366
- if (isVisible === false) {
1367
- this.uiStateService.chatDrawerVisible.set(false);
1368
- }
1369
- }
1370
- async handleGoalCompleted(event = {}) {
1371
- console.log('Goal completed:', event);
1372
- const lesson = this.lesson();
1373
- const takenLesson = { id: lesson.id, goalCompleted: true, score: 100, status: 'finished', lastAccess: new Date() };
1374
- const res = await this.lessonsService.saveTakenLesson(takenLesson);
1375
- if (res) {
1376
- // this.lessonsMemStateService.updateUserState(res);
1377
- }
1378
- this.toastrService.success({ subtitle: '¡Has completado la lección! , pero puedes seguir conversando', title: '¡Muy bien, guardaremos tu progreso!' });
1379
- }
1380
- onChatMessage(event) {
1381
- console.log('Received chat event:', event);
1382
- switch (event.type) {
1383
- case ChatEventType.WordClicked: {
1384
- const wordClickedData = event.payload;
1385
- console.log('Word clicked event received, emitting output:', wordClickedData);
1386
- // Removed: this.wordPopupService.showWordPopup(wordClicked); // Use the service
1387
- this.wordClicked.emit(wordClickedData); // Emit the event instead
1388
- break;
1451
+ this.ref.onClose.subscribe((result) => {
1452
+ if (result) {
1453
+ this.onNewDynamicComponent.emit(result);
1389
1454
  }
1390
- default:
1391
- console.log('Unhandled chat event type:', event.type);
1392
- }
1455
+ });
1393
1456
  }
1394
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1395
- 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=\"app-bottom-overlay\">\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" }] }); }
1457
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonComponentAdderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1458
+ 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"] }] }); }
1396
1459
  }
1397
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonRendererComponent, decorators: [{
1460
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonComponentAdderComponent, decorators: [{
1398
1461
  type: Component,
1399
- 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=\"app-bottom-overlay\">\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"] }]
1400
- }], ctorParameters: () => [], propDecorators: { wordClicked: [{
1462
+ 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" }]
1463
+ }], propDecorators: { componentAdded: [{
1464
+ type: Output
1465
+ }], onNewDynamicComponent: [{
1401
1466
  type: Output
1402
1467
  }] } });
1403
1468
 
1404
- class LessonNotionService {
1469
+ class DCLessonMetadataEditorComponent {
1405
1470
  constructor() {
1406
- this.#notionService = inject(NOTION_SERVICE_TOKEN);
1407
- this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
1471
+ // Outputs for actions
1472
+ this.saveRequest = new EventEmitter();
1473
+ this.importNotionRequest = new EventEmitter();
1474
+ this.improveNotionRequest = new EventEmitter();
1475
+ // Injected Services
1476
+ this.#lessonUtilsService = inject(LessonUtilsService);
1408
1477
  this.#toastService = inject(TOAST_ALERTS_TOKEN);
1409
- // Keep track of loading state specific to Notion operations
1410
- this.isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
1478
+ this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
1411
1479
  }
1412
- #notionService;
1480
+ // Injected Services
1481
+ #lessonUtilsService;
1413
1482
  #toastService;
1414
- /**
1415
- * Extracts the Notion Page ID from a URL.
1416
- * @param url The Notion page URL.
1417
- * @returns The extracted page ID or null if invalid.
1418
- */
1419
- extractNotionPageId(url) {
1420
- const notionIdRegex = /[a-f0-9]{32}(?=\?|$)/;
1421
- const match = url.match(notionIdRegex);
1422
- const notionId = match ? match[0] : null;
1423
- if (!notionId) {
1424
- this.#toastService.error({
1425
- title: 'URL inválido',
1426
- subtitle: 'Por favor ingresa una URL válida de Notion.',
1427
- });
1428
- return null;
1429
- }
1430
- return notionId;
1483
+ // private turndownService = new TurndownService(); // Instantiate TurndownService
1484
+ ngOnInit() {
1485
+ // console.log(this.lesson(), this.form);
1486
+ }
1487
+ onManageablePropertyChange(property, value) {
1488
+ // this.lesson.update((current) => {
1489
+ // if (!current) return undefined;
1490
+ // const updatedManageable = { ...(current.manageable ?? {}), [property]: value };
1491
+ // return { ...current, manageable: updatedManageable as IManageable };
1492
+ // });
1493
+ }
1494
+ onAuditablePropertyChange(property, value) {
1495
+ // this.lesson.update((current) => {
1496
+ // if (!current) return undefined;
1497
+ // const updatedAuditable = { ...(current.auditable ?? {}), [property]: value };
1498
+ // return { ...current, auditable: updatedAuditable as IAuditable };
1499
+ // });
1500
+ }
1501
+ // New methods to handle events with proper casting
1502
+ handlePromptInputChange(event) {
1503
+ const target = event.target;
1504
+ this.onAuditablePropertyChange('prompt', target.value);
1505
+ }
1506
+ handleStatusChange(event) {
1507
+ const target = event.target;
1508
+ this.onManageablePropertyChange('status', target.checked ? 'published' : 'draft');
1431
1509
  }
1432
1510
  /**
1433
- * Links an existing lesson with a Notion page ID by updating the lesson's extras.
1434
- * @param lesson The current lesson data.
1435
- * @param notionPageId The Notion page ID to link.
1436
- * @returns The updated lesson data from the backend or undefined on failure.
1511
+ * Generates lesson content using AI, saving the current state first.
1512
+ * Moved from DCLessonEditorComponent.
1437
1513
  */
1438
- async linkLessonWithNotion(lesson, notionPageId) {
1439
- if (!lesson || !lesson.id) {
1440
- // Ensure lesson exists and has an ID
1441
- this.#toastService.warn({ title: 'No se puede enlazar', subtitle: 'La lección debe existir y tener un ID.' });
1442
- return undefined;
1514
+ async generateByAI() {
1515
+ const currentLesson = this.lesson; // Get current value
1516
+ if (!currentLesson?.id) {
1517
+ this.#toastService.warn({ title: 'Guardar primero', subtitle: 'Guarda la lección antes de usar IA.' });
1518
+ return;
1443
1519
  }
1444
- const updatedLesson = {
1445
- ...lesson,
1446
- extensions: {
1447
- ...(lesson.extensions || {}),
1448
- extras: {
1449
- ...(lesson.extensions?.['extras'] || {}),
1450
- notionPageId: notionPageId,
1451
- },
1452
- },
1453
- };
1454
- this.isLoading.set(true);
1520
+ this.isLoadingLesson = true;
1455
1521
  try {
1456
- const savedLesson = await this.lessonsService.postLesson(updatedLesson);
1457
- this.#toastService.success({ title: 'Listo', subtitle: 'Se enlazó la lección con Notion.' });
1458
- return savedLesson;
1459
- }
1460
- catch (error) {
1461
- // Remove explicit type, rely on tsconfig setting
1462
- console.error('Error linking with Notion:', error);
1463
- this.#toastService.error({ title: 'Error al enlazar', subtitle: 'Ocurrió un error inesperado.' });
1464
- return undefined;
1465
- }
1466
- finally {
1467
- this.isLoading.set(false);
1468
- }
1469
- }
1470
- /**
1471
- * Handles the process of importing lesson content from Notion.
1472
- * It prompts the user for a URL if necessary, extracts the ID, fetches content,
1473
- * and potentially links the lesson if it's an existing one.
1474
- * @param currentLesson The current lesson data (can be a new or existing lesson).
1475
- * @param lessonId The current lesson ID (null if it's a new lesson).
1476
- * @returns The fetched HTML content from Notion, or null if the process fails.
1477
- */
1478
- async importAndLinkLessonFromNotion(currentLesson, lessonId) {
1479
- if (!currentLesson)
1480
- return null;
1481
- let notionPageId = null;
1482
- if (currentLesson.extensions?.['extras']?.['notionPageId']) {
1483
- const useExisting = confirm(`Ya tenemos el id ${currentLesson.extensions?.['extras']?.['notionPageId']} ¿Quieres usar este id para importar?`);
1484
- if (useExisting) {
1485
- notionPageId = currentLesson.extensions?.['extras']?.['notionPageId'];
1522
+ const rawHtmlContent = currentLesson.textCoded || '';
1523
+ if (!rawHtmlContent) {
1524
+ console.warn('No HTML content found in lesson to process. taking just description');
1525
+ this.#toastService.info({ title: 'Contenido lección desde 0', subtitle: 'Solo se usará el prompt' });
1526
+ const improvedMarkdown = await this.#lessonUtilsService.improveMDWithAI(this.lesson, 'Create content from description');
1527
+ // Convert and save the generated content
1528
+ await this._convertMarkdownToHtmlAndSave(improvedMarkdown); // Use extracted method
1486
1529
  }
1487
1530
  else {
1488
- const inputUrl = prompt('Ingresa la NUEVA URL de Notion para importar (este ID NO se guardará automáticamente si la lección ya existe)');
1489
- if (!inputUrl)
1490
- return null; // User cancelled
1491
- notionPageId = this.extractNotionPageId(inputUrl);
1492
- }
1493
- }
1494
- else {
1495
- const inputUrl = prompt('Ingresa el URL de Notion para importar la lección (se enlazará si la lección ya existe)');
1496
- if (!inputUrl)
1497
- return null; // User cancelled
1498
- notionPageId = this.extractNotionPageId(inputUrl);
1499
- // Link automatically only if we got a valid Notion ID AND the lesson already exists (has an ID)
1500
- if (notionPageId && lessonId) {
1501
- const linkedLesson = await this.linkLessonWithNotion(currentLesson, notionPageId);
1502
- if (!linkedLesson) {
1503
- // Linking failed, maybe stop the import? Or proceed without linking?
1504
- // For now, let's stop.
1505
- this.#toastService.error({ title: 'Error de Enlace', subtitle: 'No se pudo enlazar con Notion antes de importar.' });
1506
- return null;
1531
+ // Clean orphaned and Save before Improve
1532
+ const lessonToSave = this.#lessonUtilsService.cleanOrphanedComponents(currentLesson);
1533
+ const savedLesson = await this.lessonsService.postLesson(lessonToSave);
1534
+ if (!savedLesson) {
1535
+ this.#toastService.error({ title: 'Error al guardar', subtitle: 'No se pudo guardar antes de generar con IA.' });
1536
+ throw new Error('Failed to save before AI generation');
1507
1537
  }
1508
- // If linking succeeded, the lesson object might have changed, but we proceed with the import using the notionPageId.
1538
+ this.lesson = savedLesson;
1539
+ // Replace encoded JSON with actual text before Markdown conversion
1540
+ const processedHtmlContent = this._extractTextFromEncodedJson(rawHtmlContent);
1541
+ // Convert the processed HTML (with text instead of JSON) to Markdown
1542
+ // const markdownText = this.turndownService.turndown(processedHtmlContent);
1543
+ // Use the updated lesson signal value for AI improvement
1544
+ // const improvedMarkdown = await this.#lessonUtilsService.improveMDWithAI(this.lesson, markdownText);
1545
+ // Convert and save the improved content
1546
+ // await this._convertMarkdownToHtmlAndSave(improvedMarkdown); // Use extracted method
1509
1547
  }
1510
1548
  }
1511
- if (!notionPageId) {
1512
- this.#toastService.warn({ title: 'Sin ID de Notion', subtitle: 'No se proporcionó un ID de Notion válido para importar.' });
1513
- return null;
1514
- }
1515
- this.isLoading.set(true);
1516
- try {
1517
- this.#toastService.info({ title: 'Importando lección...', subtitle: 'Espera unos segundos' });
1518
- const md = await this.#notionService.getPageInSpecificFormat(notionPageId, NotionExportType.HTML);
1519
- console.log('Imported MD/HTML:', md);
1520
- this.#toastService.success({ title: 'Contenido Importado', subtitle: 'Contenido de Notion obtenido.' });
1521
- return md.content; // Return the fetched content
1522
- }
1523
- catch (error) {
1524
- // Remove explicit type, rely on tsconfig setting
1525
- console.error('Error importing from Notion:', error);
1526
- this.#toastService.error({ title: 'Error de importación', subtitle: 'Ocurrió un error inesperado.' });
1527
- return null; // Return null on failure
1528
- }
1529
- finally {
1530
- this.isLoading.set(false);
1531
- }
1532
- }
1533
- /**
1534
- * Fetches content from the linked Notion page for AI improvement (placeholder).
1535
- * @param lesson The current lesson data.
1536
- */
1537
- async improveLessonWithNotionAI(lesson) {
1538
- if (!lesson)
1539
- return;
1540
- const notionId = lesson.extensions?.['extras']?.['notionPageId'];
1541
- if (!notionId) {
1542
- this.#toastService.warn({ title: 'Sin ID de Notion', subtitle: 'Enlaza la lección con Notion primero.' });
1543
- return;
1544
- }
1545
- this.isLoading.set(true);
1546
- try {
1547
- this.#toastService.info({ title: 'Mejorando con IA...', subtitle: 'Obteniendo contenido de Notion.' });
1548
- const md = await this.#notionService.getPageInSpecificFormat(notionId, NotionExportType.HTML);
1549
- console.log('Content to improve:', md);
1550
- // TODO: Add actual AI improvement logic here
1551
- // e.g., call another service: await this.aiImprovementService.improve(md.content);
1552
- // Then potentially update the lesson via lessonService or return data
1553
- this.#toastService.success({ title: 'Contenido Obtenido', subtitle: 'Listo para mejorar con IA (lógica no implementada).' });
1554
- }
1555
1549
  catch (error) {
1556
- // Remove explicit type, rely on tsconfig setting
1557
- console.error('Error improving with AI:', error);
1558
- this.#toastService.error({ title: 'Error de IA', subtitle: 'Ocurrió un error inesperado.' });
1550
+ console.error('Error during AI generation process in metadata editor:', error);
1551
+ // Service handles specific AI error toasts, maybe add a general one here if needed
1552
+ // this.#toastService.error({ title: 'Error General', subtitle: 'Ocurrió un problema durante el proceso de IA.' });
1559
1553
  }
1560
1554
  finally {
1561
- this.isLoading.set(false);
1562
- }
1563
- }
1564
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonNotionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1565
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonNotionService }); }
1566
- }
1567
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonNotionService, decorators: [{
1568
- type: Injectable
1569
- }] });
1570
-
1571
- class LessonUtilsService {
1572
- constructor() {
1573
- this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
1574
- this.#toastService = inject(TOAST_ALERTS_TOKEN);
1575
- this.#agentService = inject(CONVERSATION_AI_TOKEN);
1576
- this.loadingBarService = inject(LoadingBarService);
1577
- }
1578
- #toastService;
1579
- #agentService;
1580
- /**
1581
- * Validates if audios need generation for the given lesson.
1582
- * Currently logs a placeholder message.
1583
- * @param lesson The lesson object (or signal) to validate.
1584
- */
1585
- validateAudios(lesson) {
1586
- // Access lesson data directly
1587
- if (!lesson?.components) {
1588
- return;
1555
+ this.isLoadingLesson = false; // Stop loading
1589
1556
  }
1590
- // Placeholder logic - adapt as needed from original component
1591
- console.log('Validating audios for lesson:', lesson.id);
1592
1557
  }
1593
1558
  /**
1594
- * Updates the lesson signal with a new banner image in metadata.
1595
- * @param lessonSignal The signal holding the lesson data.
1596
- * @param imageUploaded The image data object from the upload event. Should conform to LessonImage structure partially.
1559
+ * Replaces encoded JSON blocks (~...~) in HTML with their 'settings.text' value.
1560
+ * Moved from DCLessonEditorComponent.
1561
+ * @param htmlInput The HTML string containing encoded JSON.
1562
+ * @returns The processed HTML string with text replacements.
1597
1563
  */
1598
- uploadCover(lessonSignal, imageUploaded) {
1599
- lessonSignal.update((currentLesson) => {
1600
- if (!currentLesson)
1601
- return undefined;
1602
- const assets = { ...(currentLesson.assets ?? {}) };
1603
- assets.banner = imageUploaded;
1604
- return {
1605
- ...currentLesson,
1606
- assets,
1607
- };
1564
+ _extractTextFromEncodedJson(htmlInput) {
1565
+ const jsonRegex = /~(.+?)~/g; // Global flag to replace all occurrences
1566
+ return htmlInput.replace(jsonRegex, (match, jsonCoded) => {
1567
+ try {
1568
+ // Attempt to decode HTML entities that might be present in the JSON string
1569
+ const decodedJson = jsonCoded.replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
1570
+ const data = JSON.parse(decodedJson);
1571
+ // Return the text if available, otherwise keep the original match (or an error placeholder)
1572
+ return data?.settings?.text || match;
1573
+ }
1574
+ catch (error) {
1575
+ console.error('Failed to parse JSON inside HTML:', jsonCoded, error);
1576
+ return '<!-- invalid component data -->'; // Placeholder for parsing errors
1577
+ }
1608
1578
  });
1609
1579
  }
1610
- // I want to deprecate this method
1611
1580
  /**
1612
- * @deprecated Use create new method.
1613
- * Generates lesson content using AI. Assumes the lesson is already saved.
1614
- * @param lessonId The ID of the lesson to generate content for.
1615
- * @returns The updated lesson object after AI generation, or null on failure.
1581
+ * Converts improved Markdown content to HTML, updates the lesson signal,
1582
+ * and saves the lesson.
1583
+ * @param improvedMarkdown The Markdown content generated by AI.
1584
+ * @throws Error if the markdown is empty/null or if saving fails.
1616
1585
  */
1617
- async generateByAI(lessonId) {
1618
- if (!lessonId) {
1619
- this.#toastService.warn({ title: 'ID Requerido', subtitle: 'Se necesita un ID de lección para usar IA.' });
1620
- return null;
1621
- }
1622
- // No need to save here, component should ensure it's saved before calling.
1623
- try {
1624
- await this.lessonsService.postGenerateByAI(lessonId);
1625
- // Re-fetch the lesson data to get AI updates
1626
- const updatedLesson = await this.lessonsService.getLesson(lessonId);
1627
- if (updatedLesson) {
1628
- this.#toastService.success({ title: 'IA completada', subtitle: 'Lección actualizada con IA.' });
1629
- return updatedLesson;
1586
+ async _convertMarkdownToHtmlAndSave(improvedMarkdown) {
1587
+ if (improvedMarkdown) {
1588
+ // Convert the improved Markdown back to HTML before setting it
1589
+ const improvedHtml = improvedMarkdown;
1590
+ // Update the signal directly
1591
+ // this.lesson.update((current) => (current ? { ...current, textCoded: improvedHtml } : undefined));
1592
+ // Save the AI-generated content
1593
+ // Ensure lesson() is not undefined before saving
1594
+ const lessonToSave = this.lesson;
1595
+ if (lessonToSave) {
1596
+ await this.lessonsService.postLesson(lessonToSave);
1597
+ this.#toastService.success({ title: 'Contenido generado', subtitle: 'Se generó y guardó el contenido con IA.' });
1630
1598
  }
1631
1599
  else {
1632
- throw new Error('Failed to fetch lesson after AI generation');
1600
+ console.error('Lesson signal is undefined, cannot save.');
1601
+ this.#toastService.error({ title: 'Error Interno', subtitle: 'No se pudo guardar la lección.' });
1602
+ // Throw an error to be caught by the calling method's try/catch
1603
+ throw new Error('Lesson signal is undefined during save operation.');
1633
1604
  }
1634
1605
  }
1635
- catch (error) {
1636
- // Type the error object
1637
- console.error('Error during AI generation in service:', error);
1638
- this.#toastService.error({ title: 'Error de IA', subtitle: 'No se pudo generar la lección con IA.' });
1639
- return null; // Return null in catch block
1606
+ else {
1607
+ // Log the error, the toast might be handled by the calling service/context
1608
+ console.error('AI generation failed or provided markdown was empty.');
1609
+ // Let the caller handle the specific user feedback if needed
1610
+ // Throw an error to be caught by the calling method's try/catch
1611
+ throw new Error('AI generation failed or the resulting markdown was empty.');
1640
1612
  }
1641
- // Loading state should be managed by the component calling this service.
1642
1613
  }
1643
1614
  /**
1644
- * Improves the provided Markdown text using AI and updates the lesson.
1645
- * Assumes the lesson is already saved before calling this.
1646
- * @param lesson The lesson object containing the prompt and other context.
1647
- * @param markdownText The Markdown text generated from the lesson's HTML content.
1648
- * @returns The improved Markdown string, or null on failure.
1615
+ * Calls the LessonUtilsService to generate a description using AI
1616
+ * and updates the lesson signal if successful.
1649
1617
  */
1650
- async improveMDWithAI(lesson, markdownText) {
1651
- if (!markdownText) {
1652
- this.#toastService.warn({ title: 'Texto Requerido', subtitle: 'Se necesita texto Markdown para mejorar con IA.' });
1653
- return null;
1618
+ async triggerGenerateDescriptionAI() {
1619
+ const currentLesson = this.lesson;
1620
+ if (!currentLesson) {
1621
+ this.#toastService.warn({ title: 'Lección no cargada', subtitle: 'Espera a que la lección se cargue.' });
1622
+ return;
1654
1623
  }
1655
- try {
1656
- this.loadingBarService.showIndeterminate();
1657
- const textPrompt = this.lessonsService.getPrompts().content(lesson);
1658
- const messages = [{ content: textPrompt, role: ChatRole.User }];
1659
- const response = await this.#agentService.callChatCompletion({ messages, model: { provider: 'google' } });
1660
- let improvedMarkdown = response.content?.trim() ?? null;
1661
- // Remove potential markdown fences
1662
- const markdownFenceStart = '```markdown\n';
1663
- const markdownFenceEnd = '\n```';
1664
- if (improvedMarkdown?.startsWith(markdownFenceStart) && improvedMarkdown.endsWith(markdownFenceEnd)) {
1665
- improvedMarkdown = improvedMarkdown.slice(markdownFenceStart.length, -markdownFenceEnd.length);
1666
- }
1667
- else {
1668
- // Also handle cases where it might just be ``` at the start/end
1669
- const simpleFence = '```';
1670
- if (improvedMarkdown?.startsWith(simpleFence)) {
1671
- improvedMarkdown = improvedMarkdown.slice(simpleFence.length);
1672
- }
1673
- if (improvedMarkdown?.endsWith(simpleFence)) {
1674
- improvedMarkdown = improvedMarkdown.slice(0, -simpleFence.length);
1675
- }
1676
- }
1677
- // Trim any leading/trailing whitespace that might remain after removing fences
1678
- improvedMarkdown = improvedMarkdown?.trim() ?? null;
1679
- console.log('Improved Markdown:', improvedMarkdown);
1680
- return improvedMarkdown; // Return only the string
1681
- }
1682
- catch (error) {
1683
- console.error('Error during AI Markdown improvement in service:', error);
1684
- this.#toastService.error({ title: 'Error de IA', subtitle: 'No se pudo mejorar la lección con IA.' });
1685
- return null; // Return null in catch block
1686
- }
1687
- finally {
1688
- this.loadingBarService.successAndHide();
1624
+ const generatedDescription = await this.#lessonUtilsService.generateDescriptionWithAI(currentLesson);
1625
+ if (generatedDescription) {
1626
+ // this.form.controls['description'].setValue(generatedDescription);
1689
1627
  }
1690
1628
  }
1691
- /**
1692
- * Generates a concise description for the lesson using AI based on its content.
1693
- * @param lesson The lesson object containing the content.
1694
- * @returns A promise resolving to the generated description string, or null on failure.
1695
- */
1696
- async generateDescriptionWithAI(lesson) {
1697
- if (!lesson || !lesson.textCoded) {
1698
- this.#toastService.warn({ title: 'Contenido Requerido', subtitle: 'Se necesita contenido en la lección para generar una descripción.' });
1699
- return null;
1629
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonMetadataEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1630
+ 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
1631
+ 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"] }] }); }
1632
+ }
1633
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonMetadataEditorComponent, decorators: [{
1634
+ type: Component,
1635
+ args: [{ selector: 'dc-lesson-metadata-editor', standalone: true, imports: [
1636
+ CommonModule,
1637
+ FormsModule,
1638
+ ButtonModule,
1639
+ InputTextModule,
1640
+ ReactiveFormsModule,
1641
+ TooltipModule, // Added TooltipModule
1642
+ InputGroupModule,
1643
+ DividerModule,
1644
+ ], 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" }]
1645
+ }], propDecorators: { form: [{
1646
+ type: Input,
1647
+ args: [{ required: true }]
1648
+ }], lesson: [{
1649
+ type: Input,
1650
+ args: [{ required: true }]
1651
+ }], isLoadingLesson: [{
1652
+ type: Input,
1653
+ args: [{ required: true }]
1654
+ }], saveRequest: [{
1655
+ type: Output
1656
+ }], importNotionRequest: [{
1657
+ type: Output
1658
+ }], improveNotionRequest: [{
1659
+ type: Output
1660
+ }] } });
1661
+
1662
+ class LessonFormEditorService {
1663
+ constructor() {
1664
+ this.fb = inject(FormBuilder);
1665
+ this.formUtils = inject(FormUtilsService);
1666
+ this.formatOptions = [
1667
+ { label: 'HTML', value: 'html' },
1668
+ { label: 'Markdown', value: 'markdown' },
1669
+ ];
1670
+ }
1671
+ createLessonForm() {
1672
+ return this.fb.group({
1673
+ version: ['1.0'],
1674
+ id: [''],
1675
+ name: [''],
1676
+ description: [''],
1677
+ format: ['html'],
1678
+ lang: [''],
1679
+ characterCard: [],
1680
+ conversationSettings: [],
1681
+ metaApp: [],
1682
+ conversationFlow: [],
1683
+ textCoded: [''],
1684
+ manageable: this.formUtils.createManageableFormGroup(),
1685
+ learnable: this.formUtils.createLearnableFormGroup(),
1686
+ });
1687
+ }
1688
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonFormEditorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1689
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonFormEditorService, providedIn: 'root' }); }
1690
+ }
1691
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonFormEditorService, decorators: [{
1692
+ type: Injectable,
1693
+ args: [{
1694
+ providedIn: 'root',
1695
+ }]
1696
+ }] });
1697
+
1698
+ class LessonRendererService {
1699
+ constructor() {
1700
+ this.dynamicComponentsService = inject(DynamicComponentsRegisterService);
1701
+ this.toastrService = inject(TOAST_ALERTS_TOKEN);
1702
+ this.components = {};
1703
+ this.mainForm = new FormGroup({});
1704
+ }
1705
+ renderLesson(lessonData, viewContainerRef, dynamicLessonElement, renderer) {
1706
+ this.clearLessonRendering(dynamicLessonElement);
1707
+ console.log('Rendering lesson:', lessonData.id);
1708
+ const { htmlContent, components } = this.parseAndCreateComponents(lessonData, viewContainerRef);
1709
+ this.components = components;
1710
+ this.aggregateFormControls(this.components);
1711
+ dynamicLessonElement.innerHTML = htmlContent;
1712
+ this.injectComponentsIntoDom(this.components, renderer);
1713
+ return this.components;
1714
+ }
1715
+ clearLessonRendering(dynamicLessonElement) {
1716
+ Object.values(this.components).forEach((compRef) => compRef.destroy());
1717
+ this.components = {};
1718
+ this.mainForm = new FormGroup({});
1719
+ if (dynamicLessonElement) {
1720
+ dynamicLessonElement.innerHTML = '';
1700
1721
  }
1722
+ }
1723
+ parseAndCreateComponents(lessonData, viewContainerRef) {
1724
+ const r1 = new RegExp('~(.+?)~', 'g');
1725
+ let count = 0;
1726
+ const createdComponents = {};
1727
+ const htmlContent = lessonData.textCoded.replace(r1, (_matching, jsonCoded) => {
1728
+ const componentName = `dynamicComp${count}`;
1729
+ count++;
1730
+ const componentRef = this.createComponentReferenceWithJson(jsonCoded, lessonData, viewContainerRef);
1731
+ if (!componentRef) {
1732
+ console.error(`Failed to create component for: ${jsonCoded}`);
1733
+ return '<!-- component creation failed -->';
1734
+ }
1735
+ createdComponents[componentName] = componentRef;
1736
+ return `<span id="${componentName}"></span>`;
1737
+ });
1738
+ return { htmlContent, components: createdComponents };
1739
+ }
1740
+ createComponentReferenceWithJson(json, currentLesson, viewContainerRef) {
1701
1741
  try {
1702
- this.loadingBarService.showIndeterminate(); // Corrected: No argument needed
1703
- // Extract plain text from the lesson's HTML content
1704
- const plainTextContent = this._extractTextFromHtml(lesson.textCoded);
1705
- if (!plainTextContent || plainTextContent.trim().length === 0) {
1706
- this.#toastService.warn({ title: 'Texto Vacío', subtitle: 'No se pudo extraer texto útil del contenido de la lección.' });
1707
- return null;
1742
+ let lessonCodedConfig = JSON.parse(json);
1743
+ if (lessonCodedConfig.id && currentLesson?.dynamicComponents[lessonCodedConfig.id]) {
1744
+ const foundConfig = currentLesson.dynamicComponents[lessonCodedConfig.id];
1745
+ if (foundConfig) {
1746
+ lessonCodedConfig = { ...lessonCodedConfig, ...foundConfig };
1747
+ }
1708
1748
  }
1709
- const descriptionPrompt = this.lessonsService.getPrompts().description(lesson);
1710
- const messages = [{ content: descriptionPrompt, role: ChatRole.User }];
1711
- const response = await this.#agentService.callChatCompletion({ messages, model: { provider: 'google' } });
1712
- let generatedDescription = response.content?.trim() ?? null;
1713
- // Basic cleanup (remove potential quotes if the AI wraps the response)
1714
- if (generatedDescription && generatedDescription.startsWith('"') && generatedDescription.endsWith('"')) {
1715
- generatedDescription = generatedDescription.substring(1, generatedDescription.length - 1);
1749
+ const LessonClass = this.dynamicComponentsService.getDynamicComponentClass(lessonCodedConfig.component);
1750
+ if (!LessonClass) {
1751
+ console.error(`Component class not found for type: ${lessonCodedConfig.component}. JSON: ${json}`);
1752
+ return null;
1716
1753
  }
1717
- if (generatedDescription) {
1718
- this.#toastService.success({ title: 'Descripción Generada', subtitle: 'Se generó la descripción con IA.' });
1719
- return generatedDescription;
1754
+ const componentRef = viewContainerRef.createComponent(LessonClass);
1755
+ if (lessonCodedConfig.inputs) {
1756
+ for (const key in lessonCodedConfig.inputs) {
1757
+ if (lessonCodedConfig.inputs.hasOwnProperty(key)) {
1758
+ componentRef.instance[key] = lessonCodedConfig.inputs[key];
1759
+ }
1760
+ }
1720
1761
  }
1721
- else {
1722
- throw new Error('AI did not return a valid description.');
1762
+ if (lessonCodedConfig.settings) {
1763
+ componentRef.instance.config = lessonCodedConfig;
1723
1764
  }
1765
+ return componentRef;
1724
1766
  }
1725
1767
  catch (error) {
1726
- console.error('Error during AI description generation in service:', error);
1727
- this.#toastService.error({ title: 'Error de IA', subtitle: 'No se pudo generar la descripción con IA.' });
1768
+ console.error(`Error processing component JSON: ${json}`, error);
1728
1769
  return null;
1729
1770
  }
1730
- finally {
1731
- this.loadingBarService.successAndHide();
1732
- }
1733
1771
  }
1734
- /**
1735
- * Extracts plain text from an HTML string, removing encoded JSON components.
1736
- * @param htmlInput The HTML string potentially containing encoded JSON (~...~).
1737
- * @returns The plain text representation.
1738
- */
1739
- _extractTextFromHtml(htmlInput) {
1740
- if (!htmlInput)
1741
- return '';
1742
- // 1. Replace encoded JSON blocks with their 'settings.text' or an empty string
1743
- const jsonRegex = /~(.+?)~/g;
1744
- const textWithPlaceholders = htmlInput.replace(jsonRegex, (match, jsonCoded) => {
1745
- try {
1746
- const decodedJson = jsonCoded.replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
1747
- const data = JSON.parse(decodedJson);
1748
- return data?.settings?.text || ''; // Return text or empty string if not found
1749
- }
1750
- catch (error) {
1751
- console.error('Failed to parse JSON inside HTML during text extraction:', jsonCoded, error);
1752
- return ''; // Return empty string on error
1772
+ aggregateFormControls(components) {
1773
+ const newFormControls = {};
1774
+ Object.entries(components).forEach(([name, componentRef]) => {
1775
+ if (componentRef.instance?.control instanceof FormControl) {
1776
+ newFormControls[name] = componentRef.instance.control;
1777
+ newFormControls[name].addValidators(Validators.required);
1753
1778
  }
1754
1779
  });
1755
- // 2. Strip remaining HTML tags to get plain text
1756
- // Create a temporary DOM element to parse the HTML
1757
- const tempDiv = document.createElement('div');
1758
- tempDiv.innerHTML = textWithPlaceholders;
1759
- // Use textContent to get the plain text, effectively stripping tags
1760
- // Replace multiple whitespace characters (including newlines) with a single space
1761
- return (tempDiv.textContent || tempDiv.innerText || '').replace(/\s+/g, ' ').trim();
1780
+ this.mainForm = new FormGroup(newFormControls);
1762
1781
  }
1763
- /**
1764
- * Cleans orphaned components from the lesson's dynamicComponents.
1765
- * An orphaned component is one present in dynamicComponents but its ID is not found in the textCoded HTML.
1766
- * @param lesson The lesson object to clean.
1767
- * @returns A new lesson object with orphaned components removed from dynamicComponents.
1768
- */
1769
- cleanOrphanedComponents(lesson) {
1770
- if (!lesson || !lesson.textCoded || !lesson.dynamicComponents) {
1771
- // Return the original lesson if essential parts are missing
1772
- return lesson;
1773
- }
1774
- const textCoded = lesson.textCoded;
1775
- const existingComponents = lesson.dynamicComponents;
1776
- const existingComponentIds = new Set(Object.keys(existingComponents));
1777
- // Regex to find "id":"<component_id>" within the textCoded string
1778
- const idRegex = /"id":"([^"]+)"/g;
1779
- const foundIdsInText = new Set();
1780
- let match;
1781
- while ((match = idRegex.exec(textCoded)) !== null) {
1782
- const potentialId = match[1];
1783
- // Check if the found ID actually exists in our dynamic components map
1784
- // This ensures we only count IDs that correspond to known components.
1785
- if (existingComponentIds.has(potentialId)) {
1786
- foundIdsInText.add(potentialId);
1787
- }
1788
- }
1789
- const orphanedIds = [];
1790
- const cleanedDynamicComponents = {};
1791
- // Iterate through existing component IDs
1792
- for (const componentId of existingComponentIds) {
1793
- if (foundIdsInText.has(componentId)) {
1794
- // Keep the component if its ID was found in the text
1795
- cleanedDynamicComponents[componentId] = existingComponents[componentId];
1782
+ injectComponentsIntoDom(components, renderer) {
1783
+ Object.entries(components).forEach(([name, componentRef]) => {
1784
+ const elementRef = document.getElementById(name); // Uses Document Object to Find the Node Ref if exits.
1785
+ if (elementRef) {
1786
+ this.addComponentToNode(componentRef, elementRef, renderer);
1796
1787
  }
1797
1788
  else {
1798
- // Mark as orphaned if not found
1799
- orphanedIds.push(componentId);
1789
+ console.warn(`Placeholder element with ID '${name}' not found in the DOM.`);
1800
1790
  }
1801
- }
1802
- if (orphanedIds.length > 0) {
1803
- console.warn(`[LessonUtilsService] Orphaned components detected and will be removed from lesson data (IDs not found in textCoded):`, orphanedIds);
1804
- this.#toastService.warn({
1805
- title: 'Componentes Huérfanos Detectados',
1806
- subtitle: `Se removerán ${orphanedIds.length} componentes no usados del editor.`,
1807
- // life: 5000, // Removed 'life' property as it might not be supported by ToastData
1791
+ });
1792
+ }
1793
+ addComponentToNode(componentRef, nodeDOM, renderer) {
1794
+ renderer.appendChild(nodeDOM, componentRef.location.nativeElement);
1795
+ }
1796
+ evaluateForms() {
1797
+ this.mainForm.markAllAsTouched();
1798
+ if (!this.mainForm.valid) {
1799
+ Object.keys(this.mainForm.controls).forEach((controlName) => {
1800
+ if (this.components[controlName]?.instance?.validate) {
1801
+ this.components[controlName].instance.validate();
1802
+ }
1808
1803
  });
1804
+ this.toastrService.warn({ subtitle: 'Por favor completa todos los ejercicios', title: 'Incompleto' });
1805
+ return null;
1809
1806
  }
1810
- // Return a new lesson object with the cleaned components
1811
- return {
1812
- ...lesson,
1813
- dynamicComponents: cleanedDynamicComponents,
1814
- };
1807
+ const rates = { correct: 0, incorrect: 0, score: 0 };
1808
+ Object.keys(this.mainForm.controls).forEach((controlName) => {
1809
+ const instance = this.components[controlName]?.instance;
1810
+ if (instance && typeof instance.evaluate === 'function') {
1811
+ try {
1812
+ const result = instance.evaluate();
1813
+ if (result) {
1814
+ rates.correct++;
1815
+ }
1816
+ else {
1817
+ rates.incorrect++;
1818
+ }
1819
+ }
1820
+ catch (err) {
1821
+ console.error('Error during evaluation for component:', controlName, instance, err);
1822
+ rates.incorrect++;
1823
+ }
1824
+ }
1825
+ });
1826
+ const totalQuestions = rates.correct + rates.incorrect;
1827
+ rates.score = totalQuestions > 0 ? rates.correct / totalQuestions : 0;
1828
+ const status = rates.score >= 0.7 ? 'passed' : 'failed';
1829
+ if (status === 'passed') {
1830
+ this.toastrService.success({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%.`, title: '¡Muy bien!' });
1831
+ }
1832
+ else {
1833
+ this.toastrService.warn({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%. Revisa tus respuestas.`, title: 'Casi lo logras' });
1834
+ }
1835
+ return { rates, takenLesson: null };
1815
1836
  }
1816
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonUtilsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1817
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonUtilsService, providedIn: 'root' }); }
1837
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonRendererService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1838
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonRendererService, providedIn: 'root' }); }
1818
1839
  }
1819
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonUtilsService, decorators: [{
1840
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonRendererService, decorators: [{
1820
1841
  type: Injectable,
1821
1842
  args: [{
1822
- providedIn: 'root', // Provide globally or in a specific module if preferred
1843
+ providedIn: 'root',
1823
1844
  }]
1824
1845
  }] });
1825
1846
 
1826
- class DynamicComponentsBuilderService {
1827
- #dialogService = inject(DialogService);
1828
- #toastService = inject(TOAST_ALERTS_TOKEN);
1829
- #dynamicComponentsService = inject(DynamicComponentsService);
1830
- openComponentBuilder(componentType, data = null) {
1831
- const componentToBuild = this.#dynamicComponentsService.getDynamicComponentBuilders(componentType);
1832
- if (!componentToBuild) {
1833
- console.error(`No component builder found for type: ${componentType}`);
1834
- this.#toastService.error({ title: 'Error', subtitle: `Componente desconocido: ${componentType}` });
1835
- return undefined;
1836
- }
1837
- const dialogRef = this.#dialogService.open(componentToBuild, {
1838
- inputValues: {
1839
- // inputValues was removed in newer PrimeNG versions, use 'data'
1840
- inputs: data.inputs,
1841
- id: data.id,
1847
+ const EnglishEvaluationSkill = `
1848
+ You are a virtual professor for the Polilan English learning app. Your primary role is to evaluate a student's English conversational performance.
1849
+
1850
+ You will be provided with two pieces of information for each evaluation:
1851
+ 1. **The student's English level:** This will be one of "Beginner", "Intermediate", or "Advanced".
1852
+ 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: ...").
1853
+
1854
+ Your sole task is to evaluate the *Student's* contribution to the conversation. **Do NOT evaluate the Assistant's turns.**
1855
+
1856
+ 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:
1857
+
1858
+ * **Grammar & Syntax:** Evaluate accuracy and complexity *relative to the expected level*. (Beginners: focus on basic accuracy; Advanced: expect complex structures with high accuracy).
1859
+ * **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).
1860
+ * **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).
1861
+ * **Task Completion/Relevance:** Evaluate how effectively they participate and respond to the conversation's goals or topics *relative to the expected level*.
1862
+
1863
+ Assign a rating from 0 to 3 based on how well the student performs *compared to the typical expectations for their provided level*:
1864
+
1865
+ * **0: Bad** - Performance is significantly below what is expected for this level. Many errors impede communication.
1866
+ * **1: Not Bad** - Performance is below expectations for this level, but communication is sometimes possible despite frequent errors.
1867
+ * **2: Good** - Performance meets expectations for this level, demonstrating solid understanding and usage with only occasional minor errors.
1868
+ * **3: Very Good** - Performance exceeds expectations for this level, demonstrating a strong command and potential to progress quickly.
1869
+
1870
+ 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.
1871
+ `;
1872
+
1873
+ const EngagementSkill = `
1874
+ 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.
1875
+ that makes sense for the lesson.
1876
+ `;
1877
+ class LessonConversationService {
1878
+ constructor() {
1879
+ this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
1880
+ this.userService = inject(UserService);
1881
+ this.conversationSettings = signal(undefined, ...(ngDevMode ? [{ debugName: "conversationSettings" }] : []));
1882
+ this.conversationFlow = signal(undefined, ...(ngDevMode ? [{ debugName: "conversationFlow" }] : []));
1883
+ this.evalAgentTask = signal(undefined, ...(ngDevMode ? [{ debugName: "evalAgentTask" }] : []));
1884
+ }
1885
+ initializeConversationFlow() {
1886
+ this.evalAgentTask.set({
1887
+ systemPrompt: EnglishEvaluationSkill,
1888
+ model: { provider: 'google' },
1889
+ task: `Please evaluate the user conversation student data is: \n ${this.userService.getUserDataInformation()}`,
1890
+ expectedResponseType: EvalResultStringDefinition,
1891
+ });
1892
+ this.conversationFlow.set({
1893
+ goal: {
1894
+ enabled: true,
1895
+ task: `User is learning is taking a lesson about languages, evaluate how good are the responses.`,
1896
+ model: { quality: EModelQuality.FAST },
1897
+ },
1898
+ triggerTasks: {
1899
+ [ConversationEvents.OnUserMessage]: {
1900
+ enabled: true,
1901
+ ...this.evalAgentTask(),
1902
+ },
1903
+ },
1904
+ challenges: null,
1905
+ dynamicConditions: [
1906
+ {
1907
+ what: ConditionType.Goal,
1908
+ when: ConditionOperator.GreaterThanOrEqual,
1909
+ value: 100,
1910
+ do: [
1911
+ {
1912
+ actionType: EDoActionType.ChangePrompt,
1913
+ systemPromptType: SystemPromptType.SystemPrompt,
1914
+ 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.',
1915
+ },
1916
+ {
1917
+ actionType: EDoActionType.ChangePrompt,
1918
+ systemPromptType: SystemPromptType.CharacterDescription,
1919
+ prompt: '',
1920
+ },
1921
+ {
1922
+ actionType: EDoActionType.ChangePrompt,
1923
+ systemPromptType: SystemPromptType.UserInformation,
1924
+ prompt: '',
1925
+ },
1926
+ {
1927
+ actionType: EDoActionType.ChangePrompt,
1928
+ systemPromptType: SystemPromptType.MessageExamples,
1929
+ prompt: '',
1930
+ },
1931
+ {
1932
+ actionType: EDoActionType.ChangePrompt,
1933
+ systemPromptType: SystemPromptType.ScenarioDescription,
1934
+ prompt: '',
1935
+ },
1936
+ ],
1937
+ },
1938
+ ],
1939
+ moodState: {
1940
+ enabled: false,
1941
+ useAssetStatesOnly: false,
1942
+ detectableStates: [],
1842
1943
  },
1843
- width: '80vw',
1844
- header: 'Agregar componente',
1845
- closable: true,
1846
1944
  });
1847
- return dialogRef;
1848
1945
  }
1849
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsBuilderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1850
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsBuilderService, providedIn: 'root' }); }
1851
- }
1852
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsBuilderService, decorators: [{
1853
- type: Injectable,
1854
- args: [{
1855
- providedIn: 'root',
1856
- }]
1857
- }] });
1946
+ async startAI(lesson, settings) {
1947
+ console.log('Requesting agent cards from LessonAIService...');
1948
+ try {
1949
+ const conversationSettings = await this.generateConversationSettingsForLesson(lesson, settings);
1950
+ if (conversationSettings) {
1951
+ this.conversationSettings.set(conversationSettings);
1952
+ console.log('Agent cards received and set.');
1953
+ return conversationSettings;
1954
+ }
1955
+ else {
1956
+ console.error('Failed to generate agent cards (service returned null).');
1957
+ return null;
1958
+ }
1959
+ }
1960
+ catch (error) {
1961
+ console.error('Error generating agent cards:', error);
1962
+ return null;
1963
+ }
1964
+ }
1965
+ /**
1966
+ * Builds the scenario prompt string based on lesson content and user info.
1967
+ * @param lessonText The extracted text content of the lesson.
1968
+ * @param userInformationPrompt The formatted string containing user details.
1969
+ * @returns The complete scenario prompt string.
1970
+ */
1971
+ _buildLessonInstructionsPrompt(lessonText, userInformationPrompt, settings) {
1972
+ return `
1973
+ ${settings.instructionsPrompt}
1858
1974
 
1859
- class DCLessonComponentAdderComponent {
1860
- constructor() {
1861
- // Services
1862
- this.#dynamicComponentsBuilderService = inject(DynamicComponentsBuilderService);
1863
- this.componentAdded = new EventEmitter(); // Changed Output name and type
1864
- // Expose enum to the template
1865
- this.lessonComponentEnum = LessonComponentEnum;
1975
+ <Lesson Text>
1976
+ ${lessonText}
1977
+ </Lesson Text>
1978
+
1979
+ ${EngagementSkill}
1980
+
1981
+ <User Information>
1982
+ ${userInformationPrompt}`;
1866
1983
  }
1867
- // Services
1868
- #dynamicComponentsBuilderService;
1869
- // Logic to open component builder, now utilizing DynamicComponentsBuilderService
1870
- openComponentBuilder(type) {
1871
- const dialogRef = this.#dynamicComponentsBuilderService.openComponentBuilder(type);
1872
- if (dialogRef) {
1873
- // Handle the result and emit the new event
1874
- dialogRef.onClose.subscribe((result) => {
1875
- if (result) {
1876
- console.log('Component builder closed:', result);
1877
- this.componentAdded.emit(result); // Emit the result when dialog closes successfully
1878
- }
1879
- });
1984
+ /**
1985
+ * Generates conversation settings for a lesson, using the lesson scenario as the initial prompt.
1986
+ * @param lesson The lesson data.
1987
+ * @returns An IConversationSettings object configured for the lesson scenario.
1988
+ */
1989
+ async generateConversationSettingsForLesson(lesson, settings) {
1990
+ // TODO: Consolidate user fetching logic if possible, or ensure consistency
1991
+ const baseLang = this.userService.user().settings.baseLanguage || 'en';
1992
+ let lessonText = '';
1993
+ if (lesson.textCoded) {
1994
+ lessonText = this.lessonsService.extractTextFromHtml(lesson.textCoded);
1880
1995
  }
1881
1996
  else {
1882
- // Optional: Log if the dialog couldn't be opened, though the service already logs an error.
1883
- console.warn(`Dialog could not be opened for type via component: ${type}`);
1997
+ lessonText = lesson.markdown;
1884
1998
  }
1999
+ const userInformationPrompt = this.userService.getUserDataInformation();
2000
+ let lessonInstructionsPrompt = this._buildLessonInstructionsPrompt(lessonText, userInformationPrompt, settings);
2001
+ if (settings.additionalPrompt) {
2002
+ lessonInstructionsPrompt += '\n\n' + settings.additionalPrompt;
2003
+ }
2004
+ const initialMessage = {
2005
+ role: ChatRole.System,
2006
+ content: lessonInstructionsPrompt,
2007
+ messageId: SystemPromptType.SystemPrompt,
2008
+ };
2009
+ // Use defaults similar to DEFAULT_LESSON_AGENT_CARD but adjust for prompt-based start
2010
+ const conversationSettings = {
2011
+ conversationType: ConversationType.General,
2012
+ textEngine: TextEngines.SimpleText,
2013
+ autoStart: true,
2014
+ messages: [initialMessage],
2015
+ model: { provider: 'google' },
2016
+ tts: { voice: getRandomQuickVoice(baseLang || 'en', 'female') },
2017
+ };
2018
+ return conversationSettings;
1885
2019
  }
1886
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonComponentAdderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1887
- 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"] }] }); }
2020
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonConversationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2021
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonConversationService, providedIn: 'root' }); }
1888
2022
  }
1889
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonComponentAdderComponent, decorators: [{
1890
- type: Component,
1891
- 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" }]
1892
- }], propDecorators: { componentAdded: [{
1893
- type: Output
1894
- }] } });
2023
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonConversationService, decorators: [{
2024
+ type: Injectable,
2025
+ args: [{
2026
+ providedIn: 'root',
2027
+ }]
2028
+ }] });
1895
2029
 
1896
- class DCLessonMetadataEditorComponent {
2030
+ class DCLessonRendererComponent {
1897
2031
  constructor() {
1898
- // Outputs for actions
1899
- this.saveRequest = new EventEmitter();
1900
- this.importNotionRequest = new EventEmitter();
1901
- this.improveNotionRequest = new EventEmitter();
1902
- // Injected Services
1903
- this.#lessonUtilsService = inject(LessonUtilsService);
1904
- this.#toastService = inject(TOAST_ALERTS_TOKEN);
2032
+ // --- Signal Inputs ---
2033
+ this.lessonInput = input(...(ngDevMode ? [undefined, { debugName: "lessonInput" }] : [])); // Input signal for lesson object
2034
+ this.lessonIdInput = input(...(ngDevMode ? [undefined, { debugName: "lessonIdInput" }] : [])); // Input signal for lesson ID
2035
+ this.settings = input(...(ngDevMode ? [undefined, { debugName: "settings" }] : []));
2036
+ // --- Outputs ---
2037
+ this.wordClicked = new EventEmitter(); // New output event
2038
+ // --- View Childs ---
2039
+ this.dynamicLessonRef = viewChild('dynamicLessonRef', ...(ngDevMode ? [{ debugName: "dynamicLessonRef" }] : []));
2040
+ // --- Services ---
2041
+ this.renderer = inject(Renderer2);
2042
+ this.viewContainerRef = inject(ViewContainerRef);
2043
+ this.toastrService = inject(TOAST_ALERTS_TOKEN);
1905
2044
  this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
1906
- this.turndownService = new TurndownService(); // Instantiate TurndownService
1907
- }
1908
- // Injected Services
1909
- #lessonUtilsService;
1910
- #toastService;
1911
- ngOnInit() {
1912
- // console.log(this.lesson(), this.form);
1913
- }
1914
- onManageablePropertyChange(property, value) {
1915
- // this.lesson.update((current) => {
1916
- // if (!current) return undefined;
1917
- // const updatedManageable = { ...(current.manageable ?? {}), [property]: value };
1918
- // return { ...current, manageable: updatedManageable as IManageable };
1919
- // });
1920
- }
1921
- onAuditablePropertyChange(property, value) {
1922
- // this.lesson.update((current) => {
1923
- // if (!current) return undefined;
1924
- // const updatedAuditable = { ...(current.auditable ?? {}), [property]: value };
1925
- // return { ...current, auditable: updatedAuditable as IAuditable };
1926
- // });
1927
- }
1928
- // New methods to handle events with proper casting
1929
- handlePromptInputChange(event) {
1930
- const target = event.target;
1931
- this.onAuditablePropertyChange('prompt', target.value);
1932
- }
1933
- handleStatusChange(event) {
1934
- const target = event.target;
1935
- this.onManageablePropertyChange('status', target.checked ? 'published' : 'draft');
1936
- }
1937
- /**
1938
- * Generates lesson content using AI, saving the current state first.
1939
- * Moved from DCLessonEditorComponent.
1940
- */
1941
- async generateByAI() {
1942
- const currentLesson = this.lesson; // Get current value
1943
- if (!currentLesson?.id) {
1944
- this.#toastService.warn({ title: 'Guardar primero', subtitle: 'Guarda la lección antes de usar IA.' });
1945
- return;
1946
- }
1947
- this.isLoadingLesson = true;
1948
- try {
1949
- const rawHtmlContent = currentLesson.textCoded || '';
1950
- if (!rawHtmlContent) {
1951
- console.warn('No HTML content found in lesson to process. taking just description');
1952
- this.#toastService.info({ title: 'Contenido lección desde 0', subtitle: 'Solo se usará el prompt' });
1953
- const improvedMarkdown = await this.#lessonUtilsService.improveMDWithAI(this.lesson, 'Create content from description');
1954
- // Convert and save the generated content
1955
- await this._convertMarkdownToHtmlAndSave(improvedMarkdown); // Use extracted method
2045
+ this.uiStateService = inject(UiStateService);
2046
+ this.lessonRendererService = inject(LessonRendererService);
2047
+ this.lessonConversationService = inject(LessonConversationService);
2048
+ this.mobileService = inject(MobileService);
2049
+ // --- State Signals ---
2050
+ this.lesson = signal(undefined, ...(ngDevMode ? [{ debugName: "lesson" }] : [])); // Internal lesson state signal
2051
+ // --- Computed Signals ---
2052
+ this.imageCover = computed(() => this.lesson()?.media?.images?.find((img) => img.type === 'cover')?.url, ...(ngDevMode ? [{ debugName: "imageCover" }] : [])); // Computed signal for imageCover
2053
+ // --- Properties ---
2054
+ this.components = {};
2055
+ this.mainForm = new FormGroup({});
2056
+ this.previousTextCoded = undefined; // Store previous value
2057
+ // Effect to fetch lesson data if ID is provided and lesson object isn't
2058
+ effect(async () => {
2059
+ const lessonInput = this.lessonInput();
2060
+ const lessonId = this.lessonIdInput();
2061
+ if (lessonInput) {
2062
+ this.lesson.set(lessonInput); // Use input lesson directly
2063
+ }
2064
+ else if (lessonId && !this.lesson()) {
2065
+ // Fetch only if ID exists and internal lesson is not set
2066
+ console.log(`[Renderer] Effect 1: Fetching lesson ${lessonId}`);
2067
+ try {
2068
+ // Consider adding a loading state signal here
2069
+ const fetchedLesson = await this.lessonsService.getLesson(lessonId);
2070
+ this.lesson.set(fetchedLesson);
2071
+ console.log('Fetched lesson:', fetchedLesson);
2072
+ }
2073
+ catch (error) {
2074
+ console.error(`Failed to fetch lesson with ID: ${lessonId}`, error);
2075
+ this.toastrService.error({ subtitle: 'Failed to load lesson data.', title: 'Error' });
2076
+ this.lesson.set(undefined); // Reset lesson on error
2077
+ }
2078
+ finally {
2079
+ // Reset loading state signal here
2080
+ }
2081
+ }
2082
+ else if (!lessonInput && !lessonId) {
2083
+ // Handle case where neither input is provided, maybe clear the lesson?
2084
+ this.lesson.set(undefined);
2085
+ }
2086
+ }, { allowSignalWrites: true }); // Allow signal writes inside effect
2087
+ // Effect to render the lesson only when textCoded value actually changes
2088
+ effect(() => {
2089
+ const dynamicLessonRefEl = this.dynamicLessonRef();
2090
+ const currentLesson = this.lesson(); // Read the lesson signal
2091
+ if (!dynamicLessonRefEl || !currentLesson) {
2092
+ return;
2093
+ }
2094
+ if (currentLesson?.format === 'markdown') {
2095
+ // If markdown content exists, we clear any dynamically rendered components
2096
+ // and prevent the textCoded logic from running.
2097
+ if (this.previousTextCoded) {
2098
+ this.lessonRendererService.clearLessonRendering(dynamicLessonRefEl.nativeElement);
2099
+ this.mainForm = this.lessonRendererService.mainForm;
2100
+ this.previousTextCoded = undefined;
2101
+ }
2102
+ return;
2103
+ }
2104
+ const newTextCoded = currentLesson?.textCoded; // Get the current textCoded value
2105
+ // Quick fix to only render on changes textCode not all the lesson
2106
+ if (newTextCoded !== this.previousTextCoded) {
2107
+ if (newTextCoded !== undefined && newTextCoded !== null && currentLesson) {
2108
+ this.components = this.lessonRendererService.renderLesson(currentLesson, this.viewContainerRef, dynamicLessonRefEl.nativeElement, this.renderer);
2109
+ this.mainForm = this.lessonRendererService.mainForm;
2110
+ }
2111
+ else {
2112
+ this.lessonRendererService.clearLessonRendering(dynamicLessonRefEl.nativeElement);
2113
+ this.mainForm = this.lessonRendererService.mainForm;
2114
+ }
2115
+ // Update the stored previous value *after* comparison and action
2116
+ this.previousTextCoded = newTextCoded;
1956
2117
  }
1957
2118
  else {
1958
- // Clean orphaned and Save before Improve
1959
- const lessonToSave = this.#lessonUtilsService.cleanOrphanedComponents(currentLesson);
1960
- const savedLesson = await this.lessonsService.postLesson(lessonToSave);
1961
- if (!savedLesson) {
1962
- this.#toastService.error({ title: 'Error al guardar', subtitle: 'No se pudo guardar antes de generar con IA.' });
1963
- throw new Error('Failed to save before AI generation');
1964
- }
1965
- this.lesson = savedLesson;
1966
- // Replace encoded JSON with actual text before Markdown conversion
1967
- const processedHtmlContent = this._extractTextFromEncodedJson(rawHtmlContent);
1968
- // Convert the processed HTML (with text instead of JSON) to Markdown
1969
- const markdownText = this.turndownService.turndown(processedHtmlContent);
1970
- // Use the updated lesson signal value for AI improvement
1971
- const improvedMarkdown = await this.#lessonUtilsService.improveMDWithAI(this.lesson, markdownText);
1972
- // Convert and save the improved content
1973
- await this._convertMarkdownToHtmlAndSave(improvedMarkdown); // Use extracted method
2119
+ console.log('[Renderer] textCoded has NOT changed. Skipping render/clear.');
1974
2120
  }
2121
+ });
2122
+ }
2123
+ ngOnInit() {
2124
+ this.lessonConversationService.initializeConversationFlow();
2125
+ }
2126
+ async evaluateForms() {
2127
+ const result = this.lessonRendererService.evaluateForms();
2128
+ if (!result) {
2129
+ return;
1975
2130
  }
1976
- catch (error) {
1977
- console.error('Error during AI generation process in metadata editor:', error);
1978
- // Service handles specific AI error toasts, maybe add a general one here if needed
1979
- // this.#toastService.error({ title: 'Error General', subtitle: 'Ocurrió un problema durante el proceso de IA.' });
2131
+ const currentLesson = this.lesson();
2132
+ if (!currentLesson) {
2133
+ console.error('Cannot save result, lesson data is missing.');
2134
+ this.toastrService.error({ subtitle: 'Cannot save result, lesson data missing.', title: 'Error' });
2135
+ return;
1980
2136
  }
1981
- finally {
1982
- this.isLoadingLesson = false; // Stop loading
2137
+ const status = result.rates.score >= 0.7 ? 'passed' : 'failed';
2138
+ const takenLesson = {
2139
+ id: currentLesson.id,
2140
+ goalCompleted: null,
2141
+ score: result.rates.score,
2142
+ status: status,
2143
+ lastAccess: new Date(),
2144
+ };
2145
+ console.log('Lesson evaluation result:', takenLesson);
2146
+ // TODO: Re-implement saving the taken lesson status via lessonService
2147
+ }
2148
+ // --- AI Chat Logic ---
2149
+ async startAI() {
2150
+ const currentLesson = this.lesson();
2151
+ if (!currentLesson) {
2152
+ console.error('Cannot start AI without a lesson.');
2153
+ this.toastrService.error({ subtitle: 'Lesson data not available.', title: 'Cannot Start Chat' });
2154
+ return;
2155
+ }
2156
+ const conversationSettings = await this.lessonConversationService.startAI(currentLesson, this.settings());
2157
+ if (conversationSettings) {
2158
+ this.uiStateService.chatDrawerVisible.set(true);
2159
+ }
2160
+ else {
2161
+ this.toastrService.error({ subtitle: 'Could not prepare the AI chat session.', title: 'Error' });
1983
2162
  }
1984
2163
  }
1985
- /**
1986
- * Replaces encoded JSON blocks (~...~) in HTML with their 'settings.text' value.
1987
- * Moved from DCLessonEditorComponent.
1988
- * @param htmlInput The HTML string containing encoded JSON.
1989
- * @returns The processed HTML string with text replacements.
1990
- */
1991
- _extractTextFromEncodedJson(htmlInput) {
1992
- const jsonRegex = /~(.+?)~/g; // Global flag to replace all occurrences
1993
- return htmlInput.replace(jsonRegex, (match, jsonCoded) => {
1994
- try {
1995
- // Attempt to decode HTML entities that might be present in the JSON string
1996
- const decodedJson = jsonCoded.replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
1997
- const data = JSON.parse(decodedJson);
1998
- // Return the text if available, otherwise keep the original match (or an error placeholder)
1999
- return data?.settings?.text || match;
2164
+ onVisibleChange(isVisible) {
2165
+ if (isVisible === false) {
2166
+ this.uiStateService.chatDrawerVisible.set(false);
2167
+ }
2168
+ }
2169
+ async handleGoalCompleted(event = {}) {
2170
+ console.log('Goal completed:', event);
2171
+ const lesson = this.lesson();
2172
+ const takenLesson = { id: lesson.id, goalCompleted: true, score: 100, status: 'finished', lastAccess: new Date() };
2173
+ const res = await this.lessonsService.saveTakenLesson(takenLesson);
2174
+ if (res) {
2175
+ // this.lessonsMemStateService.updateUserState(res);
2176
+ }
2177
+ this.toastrService.success({ subtitle: '¡Has completado la lección! , pero puedes seguir conversando', title: '¡Muy bien, guardaremos tu progreso!' });
2178
+ }
2179
+ onChatMessage(event) {
2180
+ console.log('Received chat event:', event);
2181
+ switch (event.type) {
2182
+ case ChatEventType.WordClicked: {
2183
+ const wordClickedData = event.payload;
2184
+ console.log('Word clicked event received, emitting output:', wordClickedData);
2185
+ // Removed: this.wordPopupService.showWordPopup(wordClicked); // Use the service
2186
+ this.wordClicked.emit(wordClickedData); // Emit the event instead
2187
+ break;
2000
2188
  }
2001
- catch (error) {
2002
- console.error('Failed to parse JSON inside HTML:', jsonCoded, error);
2003
- return '<!-- invalid component data -->'; // Placeholder for parsing errors
2189
+ default:
2190
+ console.log('Unhandled chat event type:', event.type);
2191
+ }
2192
+ }
2193
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2194
+ 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" }] }); }
2195
+ }
2196
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonRendererComponent, decorators: [{
2197
+ type: Component,
2198
+ 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"] }]
2199
+ }], ctorParameters: () => [], propDecorators: { wordClicked: [{
2200
+ type: Output
2201
+ }] } });
2202
+
2203
+ class DcLessonTextEditorComponent {
2204
+ constructor() {
2205
+ this.lessonInput = input.required(...(ngDevMode ? [{ debugName: "lessonInput" }] : []));
2206
+ this.lesson = signal({}, ...(ngDevMode ? [{ debugName: "lesson" }] : []));
2207
+ this.markdownService = inject(MarkdownService);
2208
+ this.saveLesson = new EventEmitter();
2209
+ this.updateLesson = new EventEmitter();
2210
+ this.editor = BalloonEditor;
2211
+ effect(() => {
2212
+ this.lesson.set(this.lessonInput());
2213
+ if (this.lessonInput()) {
2214
+ this.parseHttp(this.lessonInput());
2004
2215
  }
2005
2216
  });
2006
2217
  }
2007
- /**
2008
- * Converts improved Markdown content to HTML, updates the lesson signal,
2009
- * and saves the lesson.
2010
- * @param improvedMarkdown The Markdown content generated by AI.
2011
- * @throws Error if the markdown is empty/null or if saving fails.
2012
- */
2013
- async _convertMarkdownToHtmlAndSave(improvedMarkdown) {
2014
- if (improvedMarkdown) {
2015
- // Convert the improved Markdown back to HTML before setting it
2016
- const improvedHtml = marked(improvedMarkdown);
2017
- // Update the signal directly
2018
- // this.lesson.update((current) => (current ? { ...current, textCoded: improvedHtml } : undefined));
2019
- // Save the AI-generated content
2020
- // Ensure lesson() is not undefined before saving
2021
- const lessonToSave = this.lesson;
2022
- if (lessonToSave) {
2023
- await this.lessonsService.postLesson(lessonToSave);
2024
- this.#toastService.success({ title: 'Contenido generado', subtitle: 'Se generó y guardó el contenido con IA.' });
2025
- }
2026
- else {
2027
- console.error('Lesson signal is undefined, cannot save.');
2028
- this.#toastService.error({ title: 'Error Interno', subtitle: 'No se pudo guardar la lección.' });
2029
- // Throw an error to be caught by the calling method's try/catch
2030
- throw new Error('Lesson signal is undefined during save operation.');
2031
- }
2032
- }
2033
- else {
2034
- // Log the error, the toast might be handled by the calling service/context
2035
- console.error('AI generation failed or provided markdown was empty.');
2036
- // Let the caller handle the specific user feedback if needed
2037
- // Throw an error to be caught by the calling method's try/catch
2038
- throw new Error('AI generation failed or the resulting markdown was empty.');
2039
- }
2218
+ updateHtmlTextCoded(key, value) {
2219
+ this.lesson.update((lesson) => ({ ...lesson, [key]: value }));
2220
+ this.updateLesson.emit(this.lesson());
2040
2221
  }
2041
- /**
2042
- * Calls the LessonUtilsService to generate a description using AI
2043
- * and updates the lesson signal if successful.
2044
- */
2045
- async triggerGenerateDescriptionAI() {
2046
- const currentLesson = this.lesson;
2047
- if (!currentLesson) {
2048
- this.#toastService.warn({ title: 'Lección no cargada', subtitle: 'Espera a que la lección se cargue.' });
2049
- return;
2222
+ parseHttp(entity) {
2223
+ if (this.lessonInput().format === 'markdown') {
2224
+ this.httpTemporalModel = this.markdownService.parse(entity.markdown);
2050
2225
  }
2051
- const generatedDescription = await this.#lessonUtilsService.generateDescriptionWithAI(currentLesson);
2052
- if (generatedDescription) {
2053
- // this.form.controls['description'].setValue(generatedDescription);
2226
+ else {
2227
+ this.httpTemporalModel = entity.textCoded;
2054
2228
  }
2055
2229
  }
2056
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonMetadataEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2057
- 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
2058
- 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"] }] }); }
2230
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DcLessonTextEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2231
+ 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"] }] }); }
2059
2232
  }
2060
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonMetadataEditorComponent, decorators: [{
2233
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DcLessonTextEditorComponent, decorators: [{
2061
2234
  type: Component,
2062
- args: [{ selector: 'dc-lesson-metadata-editor', standalone: true, imports: [
2063
- CommonModule,
2064
- FormsModule,
2065
- ButtonModule,
2066
- InputTextModule,
2067
- ReactiveFormsModule,
2068
- TooltipModule, // Added TooltipModule
2069
- InputGroupModule,
2070
- DividerModule,
2071
- ], 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" }]
2072
- }], propDecorators: { form: [{
2073
- type: Input,
2074
- args: [{ required: true }]
2075
- }], lesson: [{
2076
- type: Input,
2077
- args: [{ required: true }]
2078
- }], isLoadingLesson: [{
2079
- type: Input,
2080
- args: [{ required: true }]
2081
- }], saveRequest: [{
2082
- type: Output
2083
- }], importNotionRequest: [{
2235
+ 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" }]
2236
+ }], ctorParameters: () => [], propDecorators: { saveLesson: [{
2084
2237
  type: Output
2085
- }], improveNotionRequest: [{
2238
+ }], updateLesson: [{
2086
2239
  type: Output
2087
2240
  }] } });
2088
2241
 
2089
- class LessonFormEditorService {
2242
+ class DcDynamicComponentPreviewComponent {
2090
2243
  constructor() {
2091
- this.fb = inject(FormBuilder);
2092
- this.formUtils = inject(FormUtilsService);
2093
- this.formatOptions = [
2094
- { label: 'HTML', value: 'html' },
2095
- { label: 'Markdown', value: 'markdown' },
2096
- ];
2244
+ this.showComponentDetails = new EventEmitter();
2245
+ this.editComponent = new EventEmitter();
2097
2246
  }
2098
- createLessonForm() {
2099
- return this.fb.group({
2100
- version: ['1.0'],
2101
- id: [''],
2102
- name: [''],
2103
- description: [''],
2104
- format: ['html'],
2105
- lang: [''],
2106
- characterCard: [],
2107
- conversationSettings: [],
2108
- metaApp: [],
2109
- conversationFlow: [],
2110
- textCoded: [''],
2111
- manageable: this.formUtils.createManageableFormGroup(),
2112
- learnable: this.formUtils.createLearnableFormGroup(),
2113
- });
2247
+ onShowComponentDetails(component) {
2248
+ this.showComponentDetails.emit(component);
2114
2249
  }
2115
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonFormEditorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2116
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonFormEditorService, providedIn: 'root' }); }
2250
+ onEditComponent(component) {
2251
+ this.editComponent.emit(component);
2252
+ }
2253
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DcDynamicComponentPreviewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2254
+ 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"] }] }); }
2117
2255
  }
2118
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonFormEditorService, decorators: [{
2119
- type: Injectable,
2120
- args: [{
2121
- providedIn: 'root',
2122
- }]
2123
- }] });
2256
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DcDynamicComponentPreviewComponent, decorators: [{
2257
+ type: Component,
2258
+ 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"] }]
2259
+ }], propDecorators: { dynamicComponentsArray: [{
2260
+ type: Input
2261
+ }], showComponentDetails: [{
2262
+ type: Output
2263
+ }], editComponent: [{
2264
+ type: Output
2265
+ }] } });
2124
2266
 
2125
2267
  class DCLessonEditorComponent extends EntityBaseFormComponent {
2126
2268
  constructor() {
2127
2269
  super(...arguments);
2128
2270
  this.lessonFormEditorService = inject(LessonFormEditorService);
2129
- this.markdownService = inject(MarkdownService);
2130
2271
  this.form = this.lessonFormEditorService.createLessonForm();
2131
2272
  this.formatOptions = this.lessonFormEditorService.formatOptions;
2132
2273
  this.entityCommunicationService = inject(LESSONS_TOKEN);
2133
- this.htmlTemporal = '';
2134
2274
  // Services
2135
2275
  this.activatedRoute = inject(ActivatedRoute); // Re-inject as it's needed for navigation
2136
2276
  this.lessonNotionService = inject(LessonNotionService);
@@ -2139,20 +2279,19 @@ class DCLessonEditorComponent extends EntityBaseFormComponent {
2139
2279
  this.loadingBarService = inject(LoadingBarService);
2140
2280
  this.defaultLessonsService = inject(DefaultLessonsService);
2141
2281
  this.promptService = inject(PromptService);
2142
- this.ngxVertexService = inject(NgxVertexService);
2282
+ this.ngxVertexService = inject(NgxAiServicesService);
2143
2283
  this.dynamicComponentsBuilderService = inject(DynamicComponentsBuilderService);
2144
2284
  this.isLoadingLesson = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingLesson" }] : []));
2145
2285
  // Computed signal to get dynamic components as an array for easier iteration in the template
2146
2286
  this.dynamicComponentsArray = computed(() => {
2147
- // const currentLesson = this.lesson();
2148
- // if (currentLesson?.dynamicComponents) {
2149
- // return Object.values(currentLesson.dynamicComponents);
2150
- // }
2287
+ const currentLesson = this.entity();
2288
+ if (currentLesson?.dynamicComponents) {
2289
+ return Object.values(currentLesson.dynamicComponents);
2290
+ }
2151
2291
  return []; // Return empty array if no lesson or no dynamic components
2152
2292
  }, ...(ngDevMode ? [{ debugName: "dynamicComponentsArray" }] : []));
2153
2293
  // States
2154
2294
  this.components = {}; // Current Dynamic components
2155
- this.editor = BalloonEditor;
2156
2295
  this.lessonComponentEnum = LessonComponentEnum;
2157
2296
  this.coverStorageSettings = {
2158
2297
  path: `lessons/${this.entityId()}/covers`,
@@ -2161,14 +2300,6 @@ class DCLessonEditorComponent extends EntityBaseFormComponent {
2161
2300
  };
2162
2301
  }
2163
2302
  patchForm(entity) {
2164
- console.log(this.form);
2165
- if (this.entity().format === 'markdown') {
2166
- this.htmlTemporal = this.markdownService.parse(entity.markdown);
2167
- }
2168
- else {
2169
- this.htmlTemporal = entity.textCoded;
2170
- }
2171
- // console.log(this.htmlTemporal);
2172
2303
  this.form.patchValue(entity);
2173
2304
  }
2174
2305
  /**
@@ -2185,20 +2316,19 @@ class DCLessonEditorComponent extends EntityBaseFormComponent {
2185
2316
  return { ...currentLesson, [property]: value };
2186
2317
  });
2187
2318
  }
2188
- updateHtmlTextCoded(_, value) {
2319
+ onAssetsChange(updatedAssets) {
2320
+ console.log(updatedAssets);
2189
2321
  this.entity.update((currentLesson) => {
2190
2322
  if (!currentLesson)
2191
2323
  return undefined;
2192
- return { ...currentLesson, textCoded: value };
2324
+ return { ...currentLesson, assets: updatedAssets };
2193
2325
  });
2194
- this.form.controls.textCoded.setValue(value);
2195
2326
  }
2196
- onAssetsChange(updatedAssets) {
2197
- console.log(updatedAssets);
2327
+ updateLesson(event) {
2198
2328
  this.entity.update((currentLesson) => {
2199
2329
  if (!currentLesson)
2200
2330
  return undefined;
2201
- return { ...currentLesson, assets: updatedAssets };
2331
+ return { ...currentLesson, ...event };
2202
2332
  });
2203
2333
  }
2204
2334
  async saveLesson(event) {
@@ -2273,6 +2403,28 @@ class DCLessonEditorComponent extends EntityBaseFormComponent {
2273
2403
  async improveNotionWithAI() {
2274
2404
  await this.lessonNotionService.improveLessonWithNotionAI(this.entity());
2275
2405
  }
2406
+ onNewDynamicComponent(result) {
2407
+ const newComponentConfig = result?.obj;
2408
+ if (newComponentConfig?.id) {
2409
+ this.entity.update((currentLesson) => {
2410
+ if (!currentLesson)
2411
+ return undefined;
2412
+ const newDynamicComponent = {
2413
+ id: newComponentConfig.id,
2414
+ component: newComponentConfig.component,
2415
+ inputs: newComponentConfig.inputs || {},
2416
+ config: newComponentConfig,
2417
+ };
2418
+ const currentDynamicComponents = currentLesson.dynamicComponents || {};
2419
+ const updatedDynamicComponents = {
2420
+ ...currentDynamicComponents,
2421
+ [newComponentConfig.id]: newDynamicComponent,
2422
+ };
2423
+ const updatedTextCoded = (currentLesson.textCoded || '') + result.str;
2424
+ return { ...currentLesson, dynamicComponents: updatedDynamicComponents, textCoded: updatedTextCoded };
2425
+ });
2426
+ }
2427
+ }
2276
2428
  showComponentDetails(data) {
2277
2429
  alert('showComponentDetails' + JSON.stringify(data));
2278
2430
  }
@@ -2311,20 +2463,17 @@ class DCLessonEditorComponent extends EntityBaseFormComponent {
2311
2463
  this.entityCommunicationService.partialUpdate(this.entityId(), { assets: event.assets });
2312
2464
  }
2313
2465
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonEditorComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
2314
- 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
2315
- 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"] }] }); }
2466
+ 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
2467
+ 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"] }] }); }
2316
2468
  }
2317
2469
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonEditorComponent, decorators: [{
2318
2470
  type: Component,
2319
2471
  args: [{ selector: 'dc-lesson-editor', standalone: true, imports: [
2320
2472
  ButtonModule,
2321
- CKEditorModule,
2322
- DCLessonRendererComponent,
2323
2473
  FormsModule,
2324
2474
  InputTextModule,
2325
2475
  ReactiveFormsModule,
2326
2476
  SelectButtonModule,
2327
- SplitterModule,
2328
2477
  TooltipModule,
2329
2478
  DCLessonComponentAdderComponent, // Add the component adder here
2330
2479
  DialogModule,
@@ -2332,7 +2481,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImpor
2332
2481
  DCLessonMetadataEditorComponent,
2333
2482
  DcManageableFormComponent,
2334
2483
  DcLearnableFormComponent,
2335
- ], 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"] }]
2484
+ DcLessonTextEditorComponent,
2485
+ DcDynamicComponentPreviewComponent,
2486
+ ], 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"] }]
2336
2487
  }] });
2337
2488
 
2338
2489
  class LessonsV2Component {
@@ -2519,7 +2670,7 @@ class CourseListComponent extends PaginationBase {
2519
2670
  }
2520
2671
  }
2521
2672
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: CourseListComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
2522
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: CourseListComponent, isStandalone: true, selector: "app-course-list", inputs: { viewType: { classPropertyName: "viewType", publicName: "viewType", isSignal: false, isRequired: false, transformFunction: null }, onlyView: { classPropertyName: "onlyView", publicName: "onlyView", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect" }, usesInheritance: true, ngImport: i0, template: "@if (!onlyView()) {\n<p-button [icon]=\"viewType === 'card' ? 'pi pi-table' : 'pi pi-list'\" label=\"Change View\" [link]=\"true\" (click)=\"toggleView()\" />\n}\n<div class=\"course-list-container\">\n <dc-filter-bar [isAdmin]=\"userService.isAdmin()\" [options]=\"filterBarOptions\" (onNew)=\"onNew()\" (onFilterAction)=\"doAction($event)\"></dc-filter-bar>\n\n @if (viewType === 'card') {\n <div class=\"course-list-content\">\n @for (course of courses(); track course.id) {\n <div class=\"card-source\">\n <div style=\"position: absolute; top: 4px; right: 4px; z-index: 1000\">\n <p-speeddial\n [model]=\"getCustomButtons(course)\"\n [radius]=\"70\"\n type=\"quarter-circle\"\n direction=\"down-left\"\n [buttonProps]=\"{ severity: 'primary', rounded: true, outlined: true }\"\n [tooltipOptions]=\"{ tooltipPosition: 'top' }\" />\n </div>\n <p-card [header]=\"course.name\">\n <p class=\"m-0\">{{ course.description | slice : 0 : 250 }}...</p>\n <span>{{ course?.auditable?.createdAt | date : 'dd/MM/yyyy HH:mm' }}</span>\n\n <p-tag severity=\"success\" [value]=\"course.baseLang | langDesc : 'es'\" [rounded]=\"true\" />\n ->\n <p-tag severity=\"info\" [value]=\"course.targetLang | langDesc : 'es'\" [rounded]=\"true\" />\n </p-card>\n </div>\n } @if (courses().length === 0) {\n <p-card>\n <p>No courses found</p>\n </p-card>\n }\n </div>\n } @else if ( viewType == 'table'){\n\n <app-quick-table [tableData]=\"courses()\"></app-quick-table>\n\n }\n\n <div class=\"paginator-container\">\n <p-paginator\n currentPageReportTemplate=\"{{ totalRecords }} conversations\"\n [showCurrentPageReport]=\"true\"\n (onPageChange)=\"onPageChange($event)\"\n [totalRecords]=\"totalRecords\"\n [rowsPerPageOptions]=\"[10, 20, 30]\">\n </p-paginator>\n </div>\n</div>\n", styles: [":host{display:block;height:100%}.course-list-container{display:flex;flex-direction:column;height:100%}.course-list-content{margin:20px;flex:1;overflow-y:auto;padding-bottom:10px}.card-source{margin:20px;position:relative}.paginator-container{margin-top:auto;padding-top:10px}\n"], dependencies: [{ kind: "ngmodule", type: CardModule }, { kind: "component", type: i1$2.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { 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: DCFilterBarComponent, selector: "dc-filter-bar", inputs: ["items", "options", "customFilters", "isAdmin"], outputs: ["onFilterAction", "onChangeSort", "onNew"] }, { 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: 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: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: TableModule }, { kind: "component", type: QuickTableComponent, selector: "app-quick-table", inputs: ["columns", "tableData", "actions"], outputs: ["onAction"] }, { 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" }, { kind: "pipe", type: SlicePipe, name: "slice" }, { kind: "pipe", type: LangDescTranslation, name: "langDesc" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2673
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: CourseListComponent, isStandalone: true, selector: "app-course-list", inputs: { viewType: { classPropertyName: "viewType", publicName: "viewType", isSignal: false, isRequired: false, transformFunction: null }, onlyView: { classPropertyName: "onlyView", publicName: "onlyView", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSelect: "onSelect" }, usesInheritance: true, ngImport: i0, template: "@if (!onlyView()) {\n<p-button [icon]=\"viewType === 'card' ? 'pi pi-table' : 'pi pi-list'\" label=\"Change View\" [link]=\"true\" (click)=\"toggleView()\" />\n}\n<div class=\"course-list-container\">\n <dc-filter-bar [isAdmin]=\"userService.isAdmin()\" [options]=\"filterBarOptions\" (onNew)=\"onNew()\" (onFilterAction)=\"doAction($event)\"></dc-filter-bar>\n\n @if (viewType === 'card') {\n <div class=\"course-list-content\">\n @for (course of courses(); track course.id) {\n <div class=\"card-source\">\n <div style=\"position: absolute; top: 4px; right: 4px; z-index: 1000\">\n <p-speeddial\n [model]=\"getCustomButtons(course)\"\n [radius]=\"70\"\n type=\"quarter-circle\"\n direction=\"down-left\"\n [buttonProps]=\"{ severity: 'primary', rounded: true, outlined: true }\"\n [tooltipOptions]=\"{ tooltipPosition: 'top' }\" />\n </div>\n <p-card [header]=\"course.name\">\n <p class=\"m-0\">{{ course.description | slice : 0 : 250 }}...</p>\n <span>{{ course?.auditable?.createdAt | date : 'dd/MM/yyyy HH:mm' }}</span>\n\n <p-tag severity=\"success\" [value]=\"course.baseLang | langDesc : 'es'\" [rounded]=\"true\" />\n ->\n <p-tag severity=\"info\" [value]=\"course.targetLang | langDesc : 'es'\" [rounded]=\"true\" />\n </p-card>\n </div>\n } @if (courses().length === 0) {\n <p-card>\n <p>No courses found</p>\n </p-card>\n }\n </div>\n } @else if ( viewType == 'table'){\n\n <app-quick-table [tableData]=\"courses()\"></app-quick-table>\n\n }\n\n <div class=\"paginator-container\">\n <p-paginator\n currentPageReportTemplate=\"{{ totalRecords }} conversations\"\n [showCurrentPageReport]=\"true\"\n (onPageChange)=\"onPageChange($event)\"\n [totalRecords]=\"totalRecords\"\n [rowsPerPageOptions]=\"[10, 20, 30]\">\n </p-paginator>\n </div>\n</div>\n", styles: [":host{display:block;height:100%}.course-list-container{display:flex;flex-direction:column;height:100%}.course-list-content{margin:20px;flex:1;overflow-y:auto;padding-bottom:10px}.card-source{margin:20px;position:relative}.paginator-container{margin-top:auto;padding-top:10px}\n"], dependencies: [{ kind: "ngmodule", type: CardModule }, { kind: "component", type: i1$2.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { 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: DCFilterBarComponent, selector: "dc-filter-bar", inputs: ["items", "options", "customFilters", "isAdmin"], outputs: ["onFilterAction", "onNew"] }, { 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: 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: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: TableModule }, { kind: "component", type: QuickTableComponent, selector: "app-quick-table", inputs: ["columns", "tableData", "actions"], outputs: ["onAction"] }, { 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" }, { kind: "pipe", type: SlicePipe, name: "slice" }, { kind: "pipe", type: LangDescTranslation, name: "langDesc" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2523
2674
  }
2524
2675
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: CourseListComponent, decorators: [{
2525
2676
  type: Component,
@@ -2588,11 +2739,11 @@ class CourseDetailComponent {
2588
2739
  this.course.set(course);
2589
2740
  }
2590
2741
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: CourseDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2591
- 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 }}</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 }}</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 }); }
2742
+ 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 }); }
2592
2743
  }
2593
2744
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: CourseDetailComponent, decorators: [{
2594
2745
  type: Component,
2595
- args: [{ selector: 'app-course-detail', imports: [CommonModule, CardModule, PanelModule, ButtonModule, ProgressSpinnerModule, ToastModule, TagModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [MessageService], 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 }}</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 }}</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"] }]
2746
+ args: [{ selector: 'app-course-detail', imports: [CommonModule, CardModule, PanelModule, ButtonModule, ProgressSpinnerModule, ToastModule, TagModule], changeDetection: ChangeDetectionStrategy.OnPush, providers: [MessageService], 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"] }]
2596
2747
  }] });
2597
2748
 
2598
2749
  class CourseFormComponent extends EntityBaseFormComponent {
@@ -2615,29 +2766,23 @@ class CourseFormComponent extends EntityBaseFormComponent {
2615
2766
  path: `courses`,
2616
2767
  cropSettings: { aspectRatio: AspectType.Square, resolutions: [ResolutionType.MediumLarge], resizeToWidth: 700 },
2617
2768
  };
2618
- this.extraFields = [
2619
- { key: 'title', type: 'input', props: { label: 'Title', placeholder: 'Title', required: false } },
2620
- { key: 'content', type: 'textarea', props: { label: 'Content', placeholder: 'Content', required: false } },
2621
- ];
2622
2769
  effect(() => {
2623
2770
  console.log(this.entity());
2624
2771
  });
2625
2772
  }
2626
2773
  ngOnInit() {
2627
- this.form.patchValue({ baseLang: this.userService.user()?.['baseLang'] });
2628
- this.form.patchValue({ targetLang: this.userService.user()?.['targetLang'] });
2774
+ const baseLang = this.userService.user()?.['settings']?.['baseLanguage'];
2775
+ const targetLang = this.userService.user()?.['settings']?.['targetLanguage'];
2776
+ this.form.patchValue({ baseLang });
2777
+ this.form.patchValue({ targetLang });
2629
2778
  }
2630
2779
  patchForm(entity) {
2631
2780
  this.form.patchValue(entity);
2632
2781
  }
2633
2782
  async save() {
2634
- this.toastService.success({ title: 'Course', subtitle: 'Data was saved' });
2635
2783
  const result = await super.save();
2636
2784
  if (result) {
2637
2785
  this.onSave.emit(result);
2638
- if (this.toastService) {
2639
- this.toastService.success({ title: 'Course', subtitle: 'Data was saved' });
2640
- }
2641
2786
  }
2642
2787
  return result;
2643
2788
  }
@@ -2651,6 +2796,7 @@ class CourseFormComponent extends EntityBaseFormComponent {
2651
2796
  this.loadingBarService.showIndeterminate();
2652
2797
  const course = await this.entityCommunicationService.generateLanguageCourse(baseLang, targetLang, this.entity()?.id);
2653
2798
  this.form.patchValue(course);
2799
+ this.entity.set(course);
2654
2800
  if (this.toastService) {
2655
2801
  this.toastService.success({ title: 'Course Generated', subtitle: 'Course content has been generated successfully.' });
2656
2802
  }
@@ -2658,7 +2804,7 @@ class CourseFormComponent extends EntityBaseFormComponent {
2658
2804
  }
2659
2805
  }
2660
2806
  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: FormlyModule }, { 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 }); }
2807
+ 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
2808
  }
2663
2809
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: CourseFormComponent, decorators: [{
2664
2810
  type: Component,
@@ -2672,7 +2818,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImpor
2672
2818
  ChipModule,
2673
2819
  TooltipModule,
2674
2820
  CropperComponentModal,
2675
- FormlyModule,
2676
2821
  DialogModule,
2677
2822
  MessageModule,
2678
2823
  RouterLink,
@@ -2707,7 +2852,7 @@ const COURSES_ROUTES = [
2707
2852
  class CoursesComponent {
2708
2853
  static { this.routes = COURSES_ROUTES; }
2709
2854
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: CoursesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
2710
- 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 }); }
2855
+ 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 }); }
2711
2856
  }
2712
2857
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: CoursesComponent, decorators: [{
2713
2858
  type: Component,
@@ -2722,5 +2867,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImpor
2722
2867
  * Generated bundle index. Do not edit.
2723
2868
  */
2724
2869
 
2725
- 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 };
2870
+ 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 };
2726
2871
  //# sourceMappingURL=dataclouder-ngx-lessons.mjs.map