@dataclouder/ngx-lessons 0.1.14 → 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.
- package/fesm2022/dataclouder-ngx-lessons.mjs +1302 -1156
- package/fesm2022/dataclouder-ngx-lessons.mjs.map +1 -1
- package/index.d.ts +23 -25
- package/package.json +1 -1
|
@@ -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,
|
|
3
|
-
import { DatePipe, NgComponentOutlet,
|
|
4
|
-
import * as i1$
|
|
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,
|
|
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
|
|
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,
|
|
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,39 +25,37 @@ 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
|
|
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
|
|
36
|
-
import { SplitterModule } from 'primeng/splitter';
|
|
37
|
-
import * as i7 from 'primeng/tooltip';
|
|
33
|
+
import * as i4$2 from 'primeng/tooltip';
|
|
38
34
|
import { TooltipModule } from 'primeng/tooltip';
|
|
39
35
|
import { ResolutionType, AspectType, AssetsLoaderComponent, CropperComponentModal } from '@dataclouder/ngx-cloud-storage';
|
|
40
|
-
import { EvalResultStringDefinition, SystemPromptType, EDoActionType, ConditionOperator, ConditionType, ConversationEvents, ChatRole, TextEngines, ConversationType, ChatEventType, DCChatComponent, CONVERSATION_AI_TOKEN } from '@dataclouder/ngx-agent-cards';
|
|
41
|
-
import * as i2$2 from 'primeng/drawer';
|
|
42
|
-
import { DrawerModule } from 'primeng/drawer';
|
|
43
|
-
import { MarkdownComponent, MarkdownService } from 'ngx-markdown';
|
|
44
36
|
import { DialogModule } from 'primeng/dialog';
|
|
37
|
+
import { CONVERSATION_AI_TOKEN, ChatRole, EvalResultStringDefinition, SystemPromptType, EDoActionType, ConditionOperator, ConditionType, ConversationEvents, TextEngines, ConversationType, ChatEventType, DCChatComponent } from '@dataclouder/ngx-agent-cards';
|
|
45
38
|
import * as i5 from 'primeng/inputgroup';
|
|
46
39
|
import { InputGroupModule } from 'primeng/inputgroup';
|
|
47
40
|
import * as i6 from 'primeng/divider';
|
|
48
41
|
import { DividerModule } from 'primeng/divider';
|
|
49
|
-
import
|
|
42
|
+
import BalloonEditor from '@ckeditor/ckeditor5-build-balloon-block';
|
|
43
|
+
import * as i1$5 from '@ckeditor/ckeditor5-angular';
|
|
44
|
+
import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
|
|
45
|
+
import * as i3$1 from 'primeng/splitter';
|
|
46
|
+
import { SplitterModule } from 'primeng/splitter';
|
|
47
|
+
import * as i2$2 from 'primeng/drawer';
|
|
48
|
+
import { DrawerModule } from 'primeng/drawer';
|
|
49
|
+
import { MarkdownComponent, MarkdownService } from 'ngx-markdown';
|
|
50
|
+
import * as i4$3 from 'primeng/api';
|
|
50
51
|
import { MessageService } from 'primeng/api';
|
|
51
52
|
import { TableModule } from 'primeng/table';
|
|
52
|
-
import * as i3$
|
|
53
|
+
import * as i3$3 from 'primeng/panel';
|
|
53
54
|
import { PanelModule } from 'primeng/panel';
|
|
54
|
-
import * as i5$
|
|
55
|
+
import * as i5$1 from 'primeng/progressspinner';
|
|
55
56
|
import { ProgressSpinnerModule } from 'primeng/progressspinner';
|
|
56
57
|
import { ToastModule } from 'primeng/toast';
|
|
57
|
-
import * as i3$
|
|
58
|
+
import * as i3$4 from 'primeng/textarea';
|
|
58
59
|
import { TextareaModule } from 'primeng/textarea';
|
|
59
60
|
import { ChipModule } from 'primeng/chip';
|
|
60
61
|
|
|
@@ -433,7 +434,7 @@ class SpeakerBuilderComponent extends ComponentBuilder {
|
|
|
433
434
|
return code;
|
|
434
435
|
}
|
|
435
436
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: SpeakerBuilderComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
436
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.6", type: SpeakerBuilderComponent, isStandalone: true, selector: "app-speaker-builder", usesInheritance: true, ngImport: i0, template: "<div>\r\n <div>\r\n <h5>Constructor de Speaker</h5>\r\n </div>\r\n\r\n <dc-tts-playground (ttsGenerated)=\"handleTtsGenerated($event)\"></dc-tts-playground>\r\n\r\n <br />\r\n <div>\r\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\" [rounded]=\"true\"></p-button>\r\n <p-button (click)=\"showCode()\" [disabled]=\"!tts()\" label=\"Mostrar\" [rounded]=\"true\" severity=\"secondary\"></p-button>\r\n </div>\r\n</div>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: InputTextModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: TtsPlaygroundComponent, selector: "dc-tts-playground", inputs: ["settings"], outputs: ["ttsGenerated"] }] }); }
|
|
437
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.6", type: SpeakerBuilderComponent, isStandalone: true, selector: "app-speaker-builder", usesInheritance: true, ngImport: i0, template: "<div>\r\n <div>\r\n <h5>Constructor de Speaker</h5>\r\n </div>\r\n\r\n <dc-tts-playground (ttsGenerated)=\"handleTtsGenerated($event)\"></dc-tts-playground>\r\n\r\n <br />\r\n <div>\r\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\" [rounded]=\"true\"></p-button>\r\n <p-button (click)=\"showCode()\" [disabled]=\"!tts()\" label=\"Mostrar\" [rounded]=\"true\" severity=\"secondary\"></p-button>\r\n </div>\r\n</div>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: InputTextModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: TtsPlaygroundComponent, selector: "dc-tts-playground", inputs: ["settings", "emitValues"], outputs: ["ttsGenerated", "formValues"] }] }); }
|
|
437
438
|
}
|
|
438
439
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: SpeakerBuilderComponent, decorators: [{
|
|
439
440
|
type: Component,
|
|
@@ -452,11 +453,11 @@ class SpeakerComponent {
|
|
|
452
453
|
console.log('should speech but will do in next version');
|
|
453
454
|
}
|
|
454
455
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: SpeakerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
455
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.6", type: SpeakerComponent, isStandalone: true, selector: "app-speaker", inputs: { config: "config", tts: "tts" }, ngImport: i0, template: "<button\r\n
|
|
456
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.6", type: SpeakerComponent, isStandalone: true, selector: "app-speaker", inputs: { config: "config", tts: "tts" }, ngImport: i0, template: "<h4>Speacker component</h4>\r\n\r\n<!-- <p-button severity=\"help\" [label]=\"config?.settings?.text\" (click)=\"speach()\" /> -->\r\n\r\n<button pButton style=\"padding: 0px 2px\" severity=\"help\" size=\"small\" (click)=\"speach()\" [label]=\"config?.settings?.text\" [rounded]=\"true\"></button>\r\n", styles: [".lisen{cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i1$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }] }); }
|
|
456
457
|
}
|
|
457
458
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: SpeakerComponent, decorators: [{
|
|
458
459
|
type: Component,
|
|
459
|
-
args: [{ selector: 'app-speaker', standalone: true, imports: [ButtonModule], template: "<button\r\n
|
|
460
|
+
args: [{ selector: 'app-speaker', standalone: true, imports: [ButtonModule], template: "<h4>Speacker component</h4>\r\n\r\n<!-- <p-button severity=\"help\" [label]=\"config?.settings?.text\" (click)=\"speach()\" /> -->\r\n\r\n<button pButton style=\"padding: 0px 2px\" severity=\"help\" size=\"small\" (click)=\"speach()\" [label]=\"config?.settings?.text\" [rounded]=\"true\"></button>\r\n", styles: [".lisen{cursor:pointer}\n"] }]
|
|
460
461
|
}], propDecorators: { config: [{
|
|
461
462
|
type: Input
|
|
462
463
|
}], tts: [{
|
|
@@ -518,12 +519,6 @@ class SelectorBuilderComponent extends ComponentBuilder {
|
|
|
518
519
|
},
|
|
519
520
|
};
|
|
520
521
|
}
|
|
521
|
-
// public formGroup = this.formBuilder.group({
|
|
522
|
-
// options: this.formBuilder.array([]),
|
|
523
|
-
// response: ['', Validators.required],
|
|
524
|
-
// hint: [],
|
|
525
|
-
// explanation: [],
|
|
526
|
-
// });
|
|
527
522
|
ngOnInit() {
|
|
528
523
|
this.formGroup.get('response');
|
|
529
524
|
}
|
|
@@ -540,11 +535,11 @@ class SelectorBuilderComponent extends ComponentBuilder {
|
|
|
540
535
|
return this.formGroup.get('options');
|
|
541
536
|
}
|
|
542
537
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: SelectorBuilderComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
543
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: SelectorBuilderComponent, isStandalone: true, selector: "app-selector-builder", usesInheritance: true, ngImport: i0, template: "<div>\n <div>\n <p-message>Construcci\u00F3n del componente de Selecci\u00F3n, sirve para hacer una pregunta y mostrar varias opciones, ejemplo:</p-message>\n
|
|
538
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: SelectorBuilderComponent, isStandalone: true, selector: "app-selector-builder", usesInheritance: true, ngImport: i0, template: "<div class=\"p-fluid grid\">\n <div class=\"col-12 md:col-6\">\n <p-card header=\"Ejemplo de componente\">\n <p-message>Construcci\u00F3n del componente de Selecci\u00F3n, sirve para hacer una pregunta y mostrar varias opciones, ejemplo:</p-message>\n <div class=\"mt-4\">\n <p>En que a\u00F1o lleg\u00F3 cristobal colon a america?</p>\n <app-selector [config]=\"sampleConfig\"></app-selector>\n </div>\n </p-card>\n </div>\n\n <hr />\n\n <div class=\"col-12 md:col-6\">\n <form class=\"builder-form\" [formGroup]=\"formGroup\">\n <div class=\"field\">\n <label for=\"response\">Respuesta Correcta</label>\n <input id=\"response\" class=\"form-input\" type=\"text\" pInputText fullWidth formControlName=\"response\" placeholder=\"Respuesta Correcta...\" />\n </div>\n\n <div class=\"field\">\n <label for=\"hint\">Pista</label>\n <input id=\"hint\" class=\"form-input\" type=\"text\" pInputText fullWidth formControlName=\"hint\" placeholder=\"Escribe una pista para esta pregunta\" />\n </div>\n\n <div class=\"field\">\n <label for=\"explanation\">Explicaci\u00F3n</label>\n <input\n id=\"explanation\"\n class=\"form-input\"\n type=\"text\"\n pInputText\n fullWidth\n formControlName=\"explanation\"\n placeholder=\"Escribe una explicaci\u00F3n para la respuesta\" />\n </div>\n\n <hr />\n <h6>Opciones</h6>\n\n <div class=\"form-group\" formArrayName=\"options\">\n @for (item of optionsForm.controls; track item; let i = $index) {\n <div class=\"field grid align-items-center\">\n <div class=\"col\">\n <input type=\"text\" pInputText fullWidth [formControlName]=\"i\" />\n </div>\n <div class=\"col-fixed\" style=\"width: auto\">\n <p-button (click)=\"deleteFormArrayByIndex('options', i)\" icon=\"pi pi-times\" severity=\"danger\"></p-button>\n </div>\n </div>\n }\n </div>\n\n <p-button (click)=\"pushControlToFormArray('options')\" label=\"Agregar Opci\u00F3n\" [text]=\"true\" severity=\"help\"></p-button>\n </form>\n\n @if (isRendered) {\n <div class=\"mt-4\">\n <app-selector></app-selector>\n </div>\n }\n\n <div class=\"mt-4 flex justify-content-end gap-2\">\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\" [rounded]=\"true\"></p-button>\n <p-button (click)=\"showCode()\" [disabled]=\"formGroup.invalid\" label=\"Mostrar\" [rounded]=\"true\" severity=\"secondary\"></p-button>\n </div>\n </div>\n</div>\n", styles: ["nb-card{width:60vw}.builder-form{padding:5px}.form-input{margin-top:10px}.mar-top{margin:5px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: SelectorComponent, selector: "app-selector", inputs: ["config"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i3.InputText, selector: "[pInputText]", inputs: ["pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: MessageModule }, { kind: "component", type: i4.Message, selector: "p-message", inputs: ["severity", "text", "escape", "style", "styleClass", "closable", "icon", "closeIcon", "life", "showTransitionOptions", "hideTransitionOptions", "size", "variant"], outputs: ["onClose"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i1$2.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }] }); }
|
|
544
539
|
}
|
|
545
540
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: SelectorBuilderComponent, decorators: [{
|
|
546
541
|
type: Component,
|
|
547
|
-
args: [{ selector: 'app-selector-builder', standalone: true, imports: [FormsModule, ReactiveFormsModule, SelectorComponent, InputTextModule, ButtonModule, MessageModule], template: "<div>\n <div>\n <p-message>Construcci\u00F3n del componente de Selecci\u00F3n, sirve para hacer una pregunta y mostrar varias opciones, ejemplo:</p-message>\n
|
|
542
|
+
args: [{ selector: 'app-selector-builder', standalone: true, imports: [FormsModule, ReactiveFormsModule, SelectorComponent, InputTextModule, ButtonModule, MessageModule, CardModule], template: "<div class=\"p-fluid grid\">\n <div class=\"col-12 md:col-6\">\n <p-card header=\"Ejemplo de componente\">\n <p-message>Construcci\u00F3n del componente de Selecci\u00F3n, sirve para hacer una pregunta y mostrar varias opciones, ejemplo:</p-message>\n <div class=\"mt-4\">\n <p>En que a\u00F1o lleg\u00F3 cristobal colon a america?</p>\n <app-selector [config]=\"sampleConfig\"></app-selector>\n </div>\n </p-card>\n </div>\n\n <hr />\n\n <div class=\"col-12 md:col-6\">\n <form class=\"builder-form\" [formGroup]=\"formGroup\">\n <div class=\"field\">\n <label for=\"response\">Respuesta Correcta</label>\n <input id=\"response\" class=\"form-input\" type=\"text\" pInputText fullWidth formControlName=\"response\" placeholder=\"Respuesta Correcta...\" />\n </div>\n\n <div class=\"field\">\n <label for=\"hint\">Pista</label>\n <input id=\"hint\" class=\"form-input\" type=\"text\" pInputText fullWidth formControlName=\"hint\" placeholder=\"Escribe una pista para esta pregunta\" />\n </div>\n\n <div class=\"field\">\n <label for=\"explanation\">Explicaci\u00F3n</label>\n <input\n id=\"explanation\"\n class=\"form-input\"\n type=\"text\"\n pInputText\n fullWidth\n formControlName=\"explanation\"\n placeholder=\"Escribe una explicaci\u00F3n para la respuesta\" />\n </div>\n\n <hr />\n <h6>Opciones</h6>\n\n <div class=\"form-group\" formArrayName=\"options\">\n @for (item of optionsForm.controls; track item; let i = $index) {\n <div class=\"field grid align-items-center\">\n <div class=\"col\">\n <input type=\"text\" pInputText fullWidth [formControlName]=\"i\" />\n </div>\n <div class=\"col-fixed\" style=\"width: auto\">\n <p-button (click)=\"deleteFormArrayByIndex('options', i)\" icon=\"pi pi-times\" severity=\"danger\"></p-button>\n </div>\n </div>\n }\n </div>\n\n <p-button (click)=\"pushControlToFormArray('options')\" label=\"Agregar Opci\u00F3n\" [text]=\"true\" severity=\"help\"></p-button>\n </form>\n\n @if (isRendered) {\n <div class=\"mt-4\">\n <app-selector></app-selector>\n </div>\n }\n\n <div class=\"mt-4 flex justify-content-end gap-2\">\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\" [rounded]=\"true\"></p-button>\n <p-button (click)=\"showCode()\" [disabled]=\"formGroup.invalid\" label=\"Mostrar\" [rounded]=\"true\" severity=\"secondary\"></p-button>\n </div>\n </div>\n</div>\n", styles: ["nb-card{width:60vw}.builder-form{padding:5px}.form-input{margin-top:10px}.mar-top{margin:5px}\n"] }]
|
|
548
543
|
}] });
|
|
549
544
|
|
|
550
545
|
var LessonComponentEnum;
|
|
@@ -631,11 +626,11 @@ class DcLessonCardComponent {
|
|
|
631
626
|
this.onAction.emit({ action: eventType, item: this.lesson });
|
|
632
627
|
}
|
|
633
628
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DcLessonCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
634
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: DcLessonCardComponent, isStandalone: true, selector: "dc-lesson-card", inputs: { lesson: "lesson", showOptions: "showOptions", cardHeight: "cardHeight" }, outputs: { onAction: "onAction" }, ngImport: i0, template: "<div class=\"card-container\">\n @if(showOptions){\n <p-speeddial\n class=\"dial-button\"\n [model]=\"items\"\n [radius]=\"70\"\n type=\"quarter-circle\"\n direction=\"down-left\"\n [buttonProps]=\"{ severity: 'primary', rounded: true, outlined: true, raised: true }\"\n [tooltipOptions]=\"{ tooltipPosition: 'top' }\" />\n }\n <p-card>\n <div class=\"lesson-card\" [style.height]=\"cardHeight\">\n <div class=\"photo\">\n <img [src]=\"coverUrl\" alt=\"\" />\n </div>\n\n <span class=\"date\">{{ lesson.createdAt | date : 'dd/MM/yyyy' }}</span>\n\n <div class=\"description\">\n <h1>{{ lesson?.name || 'No title available' }}</h1>\n <p>{{ lesson.description || 'No description available' }}</p>\n <div class=\"card-footer\">\n <div class=\"status-tags\">\n @if (lesson.learnable?.level){\n <p-tag>Nivel {{ lesson.learnable?.level }}</p-tag>\n } @if (lesson.taken?.status == 'passed') {\n <p-tag severity=\"success\" value=\"Tomada\" [rounded]=\"true\" />\n } @if (lesson.taken?.status == 'failed') {\n <p-tag severity=\"danger\" value=\"Fallida\" [rounded]=\"true\" />\n } @if (lesson.taken) {\n <p-tag severity=\"success\" value=\"Tomada \uD83D\uDCD6\" [rounded]=\"true\" />\n } @if ( userService.isAdmin()) {\n <p-tag severity=\"contrast\" [value]=\"lesson.manageable?.isPublic ? 'P\u00FAblica' : 'No p\u00FAblica'\" [rounded]=\"true\" />\n <p-tag severity=\"contrast\" [value]=\"lesson.manageable?.status\" [rounded]=\"true\" />\n }\n </div>\n\n <div style=\"position: absolute; bottom: 0px; right: 0px\">\n <p-button label=\"Tomar lecci\u00F3n\" (onClick)=\"eventCard(eventType.Select)\" severity=\"primary\"> </p-button>\n </div>\n </div>\n </div>\n </div>\n </p-card>\n</div>\n", styles: [".card-container{position:relative;margin-bottom:20px;margin-left:10px;min-width:400px}.dial-button{position:absolute;top:10px;right:20px;z-index:10}.lesson-card{border-radius:.5rem;height:100%}.lesson-card .photo{position:absolute;inset:0;z-index:1}.lesson-card .photo img{width:100%;height:100%;object-fit:cover;object-position:center}.lesson-card .photo:after{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;background:linear-gradient(to bottom,rgb(0,0,0) 50%,var(--p-primary-color) 100%);opacity:.4;z-index:2;pointer-events:none}.description{position:relative;z-index:2;color:#fff;height:100%;display:flex;flex-direction:column}.description h1{margin-top:0;margin-bottom:.5rem;font-size:1.5rem;font-weight:900;text-shadow:1px 1px 2px rgba(0,0,0,.7)}.description p{margin-bottom:1rem;flex-grow:1;font-weight:500;text-shadow:1px 1px 2px rgba(0,0,0,.9)}.date{position:absolute;top:1rem;left:1rem;z-index:3;background-color:#000000b3;color:#fff;padding:.3rem .6rem;border-radius:.25rem;font-size:.8rem}.card-footer{display:flex;justify-content:space-between;align-items:center;margin-top:auto}.status-tags{display:flex;gap:.5rem}.status-tags .level-tag,.status-tags .status-tag{padding:.3rem .6rem;border-radius:.25rem;font-size:.8rem}.status-tags .level-tag{background-color:#fff3}.status-tags .status-tag.success{background-color:#28a745b3}.status-tags .status-tag.danger{background-color:#dc3545b3}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "buttonProps", "autofocus", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "ngmodule", type: SpeedDialModule }, { kind: "component", type: i2$1.SpeedDial, selector: "p-speeddial, p-speedDial, p-speed-dial", inputs: ["id", "model", "visible", "style", "className", "direction", "transitionDelay", "type", "radius", "mask", "disabled", "hideOnClickOutside", "buttonStyle", "buttonClassName", "maskStyle", "maskClassName", "showIcon", "hideIcon", "rotateAnimation", "ariaLabel", "ariaLabelledBy", "tooltipOptions", "buttonProps"], outputs: ["onVisibleChange", "visibleChange", "onClick", "onShow", "onHide"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i1$2.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i4$1.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "pipe", type: DatePipe, name: "date" }] }); }
|
|
629
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: DcLessonCardComponent, isStandalone: true, selector: "dc-lesson-card", inputs: { lesson: "lesson", showOptions: "showOptions", cardHeight: "cardHeight" }, outputs: { onAction: "onAction" }, ngImport: i0, template: "<div class=\"card-container\">\n @if(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" }] }); }
|
|
635
630
|
}
|
|
636
631
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DcLessonCardComponent, decorators: [{
|
|
637
632
|
type: Component,
|
|
638
|
-
args: [{ selector: 'dc-lesson-card', standalone: true, imports: [DatePipe, ButtonModule, PopoverModule, SpeedDialModule, CardModule, TagModule], template: "<div class=\"card-container\">\n @if(showOptions){\n <p-speeddial\n class=\"dial-button\"\n [model]=\"items\"\n [radius]=\"70\"\n type=\"quarter-circle\"\n direction=\"down-left\"\n [buttonProps]=\"{ severity: 'primary', rounded: true, outlined: true, raised: true }\"\n [tooltipOptions]=\"{ tooltipPosition: 'top' }\" />\n }\n <p-card>\n <div class=\"lesson-card\" [style.height]=\"cardHeight\">\n <div class=\"photo\">\n <img [src]=\"coverUrl\" alt=\"\" />\n </div>\n\n <span class=\"date\">{{ lesson.createdAt | date : 'dd/MM/yyyy' }}</span>\n\n <div class=\"description\">\n <h1>{{ lesson?.name || 'No title available' }}</h1>\n <p>{{ lesson.description || 'No description available' }}</p>\n <div class=\"card-footer\">\n <div class=\"status-tags\">\n @if (lesson.learnable?.level){\n <p-tag>Nivel {{ lesson.learnable?.level }}</p-tag>\n } @if (lesson.taken?.status == 'passed') {\n <p-tag severity=\"success\" value=\"Tomada\" [rounded]=\"true\" />\n } @if (lesson.taken?.status == 'failed') {\n <p-tag severity=\"danger\" value=\"Fallida\" [rounded]=\"true\" />\n } @if (lesson.taken) {\n <p-tag severity=\"success\" value=\"Tomada \uD83D\uDCD6\" [rounded]=\"true\" />\n } @if ( userService.isAdmin()) {\n <p-tag severity=\"contrast\" [value]=\"lesson.manageable?.isPublic ? 'P\u00FAblica' : 'No p\u00FAblica'\" [rounded]=\"true\" />\n <p-tag severity=\"contrast\" [value]=\"lesson.manageable?.status\" [rounded]=\"true\" />\n }\n </div>\n\n <div style=\"position: absolute; bottom: 0px; right: 0px\">\n <p-button label=\"Tomar lecci\u00F3n\" (onClick)=\"eventCard(eventType.Select)\" severity=\"primary\"> </p-button>\n </div>\n </div>\n </div>\n </div>\n </p-card>\n</div>\n", styles: [".card-container{position:relative;margin-bottom:20px;margin-left:10px;min-width:400px}.dial-button{position:absolute;top:10px;right:20px;z-index:10}.lesson-card{border-radius:.5rem;height:100%}.lesson-card .photo{position:absolute;inset:0;z-index:1}.lesson-card .photo img{width:100%;height:100%;object-fit:cover;object-position:center}.lesson-card .photo:after{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;background:linear-gradient(to bottom,rgb(0,0,0) 50%,var(--p-primary-color) 100%);opacity:.4;z-index:2;pointer-events:none}.description{position:relative;z-index:2;color:#fff;height:100%;display:flex;flex-direction:column}.description h1{margin-top:0;margin-bottom:.5rem;font-size:1.5rem;font-weight:900;text-shadow:1px 1px 2px rgba(0,0,0,.7)}.description p{margin-bottom:1rem;flex-grow:1;font-weight:500;text-shadow:1px 1px 2px rgba(0,0,0,.9)}.date{position:absolute;top:1rem;left:1rem;z-index:3;background-color:#000000b3;color:#fff;padding:.3rem .6rem;border-radius:.25rem;font-size:.8rem}.card-footer{display:flex;justify-content:space-between;align-items:center;margin-top:auto}.status-tags{display:flex;gap:.5rem}.status-tags .level-tag,.status-tags .status-tag{padding:.3rem .6rem;border-radius:.25rem;font-size:.8rem}.status-tags .level-tag{background-color:#fff3}.status-tags .status-tag.success{background-color:#28a745b3}.status-tags .status-tag.danger{background-color:#dc3545b3}\n"] }]
|
|
633
|
+
args: [{ selector: 'dc-lesson-card', standalone: true, imports: [DatePipe, ButtonModule, PopoverModule, SpeedDialModule, CardModule, TagModule], template: "<div class=\"card-container\">\n @if(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"] }]
|
|
639
634
|
}], propDecorators: { lesson: [{
|
|
640
635
|
type: Input
|
|
641
636
|
}], showOptions: [{
|
|
@@ -765,10 +760,10 @@ class DCLessonListComponent extends EntityBaseListComponent {
|
|
|
765
760
|
title: 1,
|
|
766
761
|
name: 1,
|
|
767
762
|
description: 1,
|
|
768
|
-
media: 1,
|
|
769
763
|
manageable: 1,
|
|
770
764
|
learnable: 1,
|
|
771
765
|
assets: 1,
|
|
766
|
+
level: 1,
|
|
772
767
|
_id: 1,
|
|
773
768
|
id: 1,
|
|
774
769
|
};
|
|
@@ -863,1279 +858,1419 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImpor
|
|
|
863
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"] }]
|
|
864
859
|
}] });
|
|
865
860
|
|
|
866
|
-
|
|
867
|
-
const DynamicComponentBuilders = {
|
|
868
|
-
[LessonComponentEnum.Speaker]: SpeakerBuilderComponent,
|
|
869
|
-
};
|
|
870
|
-
const DynamicComponents = {
|
|
871
|
-
[LessonComponentEnum.Speaker]: SpeakerComponent,
|
|
872
|
-
};
|
|
873
|
-
class DynamicComponentsService {
|
|
861
|
+
class LessonNotionService {
|
|
874
862
|
constructor() {
|
|
875
|
-
this
|
|
876
|
-
this.
|
|
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" }] : []));
|
|
877
868
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
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;
|
|
881
888
|
}
|
|
882
|
-
|
|
883
|
-
|
|
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
|
+
}
|
|
884
926
|
}
|
|
885
|
-
|
|
886
|
-
|
|
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
|
+
}
|
|
887
989
|
}
|
|
888
|
-
|
|
889
|
-
|
|
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
|
+
}
|
|
890
1020
|
}
|
|
891
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type:
|
|
892
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type:
|
|
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' }); }
|
|
893
1023
|
}
|
|
894
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type:
|
|
1024
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonNotionService, decorators: [{
|
|
895
1025
|
type: Injectable,
|
|
896
1026
|
args: [{
|
|
897
1027
|
providedIn: 'root',
|
|
898
1028
|
}]
|
|
899
1029
|
}] });
|
|
900
1030
|
|
|
901
|
-
class
|
|
1031
|
+
class LessonUtilsService {
|
|
902
1032
|
constructor() {
|
|
903
|
-
this.
|
|
904
|
-
this
|
|
905
|
-
this
|
|
906
|
-
this.
|
|
907
|
-
}
|
|
908
|
-
renderLesson(lessonData, viewContainerRef, dynamicLessonElement, renderer) {
|
|
909
|
-
this.clearLessonRendering(dynamicLessonElement);
|
|
910
|
-
console.log('Rendering lesson:', lessonData.id);
|
|
911
|
-
const { htmlContent, components } = this.parseAndCreateComponents(lessonData, viewContainerRef);
|
|
912
|
-
this.components = components;
|
|
913
|
-
this.aggregateFormControls(this.components);
|
|
914
|
-
dynamicLessonElement.innerHTML = htmlContent;
|
|
915
|
-
this.injectComponentsIntoDom(this.components, renderer);
|
|
916
|
-
return this.components;
|
|
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);
|
|
917
1037
|
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
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);
|
|
925
1053
|
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
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
|
+
};
|
|
940
1069
|
});
|
|
941
|
-
return { htmlContent, components: createdComponents };
|
|
942
1070
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
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);
|
|
950
1136
|
}
|
|
951
1137
|
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
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.' });
|
|
955
1168
|
return null;
|
|
956
1169
|
}
|
|
957
|
-
const
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
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);
|
|
964
1177
|
}
|
|
965
|
-
if (
|
|
966
|
-
|
|
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.');
|
|
967
1184
|
}
|
|
968
|
-
return componentRef;
|
|
969
1185
|
}
|
|
970
1186
|
catch (error) {
|
|
971
|
-
console.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.' });
|
|
972
1189
|
return null;
|
|
973
1190
|
}
|
|
1191
|
+
finally {
|
|
1192
|
+
this.loadingBarService.successAndHide();
|
|
1193
|
+
}
|
|
974
1194
|
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
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
|
|
981
1214
|
}
|
|
982
1215
|
});
|
|
983
|
-
|
|
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();
|
|
984
1223
|
}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
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];
|
|
990
1257
|
}
|
|
991
1258
|
else {
|
|
992
|
-
|
|
1259
|
+
// Mark as orphaned if not found
|
|
1260
|
+
orphanedIds.push(componentId);
|
|
993
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);
|
|
994
1308
|
});
|
|
995
1309
|
}
|
|
996
|
-
|
|
997
|
-
|
|
1310
|
+
registerCustomComponent(component, name) {
|
|
1311
|
+
// this._dynamicComponentBuilders[name || component.name] = component;
|
|
1312
|
+
this._dynamicComponents[name || component.name] = component;
|
|
998
1313
|
}
|
|
999
|
-
|
|
1000
|
-
this.
|
|
1001
|
-
if (!this.mainForm.valid) {
|
|
1002
|
-
Object.keys(this.mainForm.controls).forEach((controlName) => {
|
|
1003
|
-
if (this.components[controlName]?.instance?.validate) {
|
|
1004
|
-
this.components[controlName].instance.validate();
|
|
1005
|
-
}
|
|
1006
|
-
});
|
|
1007
|
-
this.toastrService.warn({ subtitle: 'Por favor completa todos los ejercicios', title: 'Incompleto' });
|
|
1008
|
-
return null;
|
|
1009
|
-
}
|
|
1010
|
-
const rates = { correct: 0, incorrect: 0, score: 0 };
|
|
1011
|
-
Object.keys(this.mainForm.controls).forEach((controlName) => {
|
|
1012
|
-
const instance = this.components[controlName]?.instance;
|
|
1013
|
-
if (instance && typeof instance.evaluate === 'function') {
|
|
1014
|
-
try {
|
|
1015
|
-
const result = instance.evaluate();
|
|
1016
|
-
if (result) {
|
|
1017
|
-
rates.correct++;
|
|
1018
|
-
}
|
|
1019
|
-
else {
|
|
1020
|
-
rates.incorrect++;
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
catch (err) {
|
|
1024
|
-
console.error('Error during evaluation for component:', controlName, instance, err);
|
|
1025
|
-
rates.incorrect++;
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
});
|
|
1029
|
-
const totalQuestions = rates.correct + rates.incorrect;
|
|
1030
|
-
rates.score = totalQuestions > 0 ? rates.correct / totalQuestions : 0;
|
|
1031
|
-
const status = rates.score >= 0.7 ? 'passed' : 'failed';
|
|
1032
|
-
if (status === 'passed') {
|
|
1033
|
-
this.toastrService.success({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%.`, title: '¡Muy bien!' });
|
|
1034
|
-
}
|
|
1035
|
-
else {
|
|
1036
|
-
this.toastrService.warn({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%. Revisa tus respuestas.`, title: 'Casi lo logras' });
|
|
1037
|
-
}
|
|
1038
|
-
return { rates, takenLesson: null };
|
|
1314
|
+
registerCustomComponentBuilder(component, name) {
|
|
1315
|
+
this._dynamicComponentBuilders[name || component.name] = component;
|
|
1039
1316
|
}
|
|
1040
|
-
|
|
1041
|
-
|
|
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' }); }
|
|
1042
1325
|
}
|
|
1043
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type:
|
|
1326
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DynamicComponentsRegisterService, decorators: [{
|
|
1044
1327
|
type: Injectable,
|
|
1045
1328
|
args: [{
|
|
1046
1329
|
providedIn: 'root',
|
|
1047
1330
|
}]
|
|
1048
|
-
}] });
|
|
1049
|
-
|
|
1050
|
-
const EnglishEvaluationSkill = `
|
|
1051
|
-
You are a virtual professor for the Polilan English learning app. Your primary role is to evaluate a student's English conversational performance.
|
|
1052
|
-
|
|
1053
|
-
You will be provided with two pieces of information for each evaluation:
|
|
1054
|
-
1. **The student's English level:** This will be one of "Beginner", "Intermediate", or "Advanced".
|
|
1055
|
-
2. **A conversation transcript:** This will be the dialogue between the Student and an Assistant, with each turn clearly labeled (e.g., "Student or User: ...", "Assistant: ...").
|
|
1056
|
-
|
|
1057
|
-
Your sole task is to evaluate the *Student's* contribution to the conversation. **Do NOT evaluate the Assistant's turns.**
|
|
1058
|
-
|
|
1059
|
-
Evaluate the student's performance based on the following aspects, *always keeping the student's provided level in mind* and adjusting your expectations accordingly specially with beginners give them credits just for trying:
|
|
1060
|
-
|
|
1061
|
-
* **Grammar & Syntax:** Evaluate accuracy and complexity *relative to the expected level*. (Beginners: focus on basic accuracy; Advanced: expect complex structures with high accuracy).
|
|
1062
|
-
* **Vocabulary & Usage:** Evaluate the range and appropriateness of words used *relative to the expected level*. (Beginners: focus on using basic words correctly; Advanced: expect varied and idiomatic language).
|
|
1063
|
-
* **Cohesion & Coherence:** Evaluate how well their turns connect and make sense within the conversation *relative to the expected level*. (Beginners: focus on simple, clear responses; Advanced: expect logical flow and detailed contributions).
|
|
1064
|
-
* **Task Completion/Relevance:** Evaluate how effectively they participate and respond to the conversation's goals or topics *relative to the expected level*.
|
|
1065
|
-
|
|
1066
|
-
Assign a rating from 0 to 3 based on how well the student performs *compared to the typical expectations for their provided level*:
|
|
1067
|
-
|
|
1068
|
-
* **0: Bad** - Performance is significantly below what is expected for this level. Many errors impede communication.
|
|
1069
|
-
* **1: Not Bad** - Performance is below expectations for this level, but communication is sometimes possible despite frequent errors.
|
|
1070
|
-
* **2: Good** - Performance meets expectations for this level, demonstrating solid understanding and usage with only occasional minor errors.
|
|
1071
|
-
* **3: Very Good** - Performance exceeds expectations for this level, demonstrating a strong command and potential to progress quickly.
|
|
1072
|
-
|
|
1073
|
-
Provide detailed, constructive feedback that explains the rating. Point out specific strengths and areas for improvement based on their performance *at their given level*. Use examples from the dialog if helpful. Maintain a supportive, encouraging, and educational tone appropriate for a virtual professor guiding a student at their specific stage.
|
|
1074
|
-
`;
|
|
1331
|
+
}], ctorParameters: () => [] });
|
|
1075
1332
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
task: `Please evaluate the user conversation student data is: \n ${this.userService.getUserDataInformation()}`,
|
|
1093
|
-
expectedResponseType: EvalResultStringDefinition,
|
|
1094
|
-
});
|
|
1095
|
-
this.conversationFlow.set({
|
|
1096
|
-
goal: {
|
|
1097
|
-
enabled: true,
|
|
1098
|
-
task: `User is learning is taking a lesson about languages, evaluate how good are the responses.`,
|
|
1099
|
-
model: { quality: EModelQuality.FAST },
|
|
1100
|
-
},
|
|
1101
|
-
triggerTasks: {
|
|
1102
|
-
[ConversationEvents.OnUserMessage]: {
|
|
1103
|
-
enabled: true,
|
|
1104
|
-
...this.evalAgentTask(),
|
|
1105
|
-
},
|
|
1106
|
-
},
|
|
1107
|
-
challenges: null,
|
|
1108
|
-
dynamicConditions: [
|
|
1109
|
-
{
|
|
1110
|
-
what: ConditionType.Goal,
|
|
1111
|
-
when: ConditionOperator.GreaterThanOrEqual,
|
|
1112
|
-
value: 100,
|
|
1113
|
-
do: [
|
|
1114
|
-
{
|
|
1115
|
-
actionType: EDoActionType.ChangePrompt,
|
|
1116
|
-
systemPromptType: SystemPromptType.SystemPrompt,
|
|
1117
|
-
prompt: 'You were talking with the user, user finish the conversation, in the next message you must try to finish the conversation and say good bye.',
|
|
1118
|
-
},
|
|
1119
|
-
{
|
|
1120
|
-
actionType: EDoActionType.ChangePrompt,
|
|
1121
|
-
systemPromptType: SystemPromptType.CharacterDescription,
|
|
1122
|
-
prompt: '',
|
|
1123
|
-
},
|
|
1124
|
-
{
|
|
1125
|
-
actionType: EDoActionType.ChangePrompt,
|
|
1126
|
-
systemPromptType: SystemPromptType.UserInformation,
|
|
1127
|
-
prompt: '',
|
|
1128
|
-
},
|
|
1129
|
-
{
|
|
1130
|
-
actionType: EDoActionType.ChangePrompt,
|
|
1131
|
-
systemPromptType: SystemPromptType.MessageExamples,
|
|
1132
|
-
prompt: '',
|
|
1133
|
-
},
|
|
1134
|
-
{
|
|
1135
|
-
actionType: EDoActionType.ChangePrompt,
|
|
1136
|
-
systemPromptType: SystemPromptType.ScenarioDescription,
|
|
1137
|
-
prompt: '',
|
|
1138
|
-
},
|
|
1139
|
-
],
|
|
1140
|
-
},
|
|
1141
|
-
],
|
|
1142
|
-
moodState: {
|
|
1143
|
-
enabled: false,
|
|
1144
|
-
useAssetStatesOnly: false,
|
|
1145
|
-
detectableStates: [],
|
|
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,
|
|
1146
1349
|
},
|
|
1350
|
+
width: '80vw',
|
|
1351
|
+
header: 'Agregar componente',
|
|
1352
|
+
closable: true,
|
|
1147
1353
|
});
|
|
1354
|
+
return dialogRef;
|
|
1148
1355
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
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);
|
|
1157
1380
|
}
|
|
1158
1381
|
else {
|
|
1159
|
-
console.error(
|
|
1160
|
-
return null;
|
|
1382
|
+
console.error(`Component class not found for type: ${componentType}`);
|
|
1161
1383
|
}
|
|
1162
1384
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
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();
|
|
1166
1394
|
}
|
|
1395
|
+
console.log(this.container);
|
|
1167
1396
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
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
|
+
}] } });
|
|
1183
1413
|
|
|
1184
|
-
|
|
1185
|
-
|
|
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;
|
|
1186
1423
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
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
|
+
});
|
|
1198
1435
|
}
|
|
1199
1436
|
else {
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
const userInformationPrompt = this.userService.getUserDataInformation();
|
|
1203
|
-
let lessonInstructionsPrompt = this._buildLessonInstructionsPrompt(lessonText, userInformationPrompt, settings);
|
|
1204
|
-
if (settings.additionalPrompt) {
|
|
1205
|
-
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}`);
|
|
1206
1439
|
}
|
|
1207
|
-
const initialMessage = {
|
|
1208
|
-
role: ChatRole.System,
|
|
1209
|
-
content: lessonInstructionsPrompt,
|
|
1210
|
-
messageId: SystemPromptType.SystemPrompt,
|
|
1211
|
-
};
|
|
1212
|
-
// Use defaults similar to DEFAULT_LESSON_AGENT_CARD but adjust for prompt-based start
|
|
1213
|
-
const conversationSettings = {
|
|
1214
|
-
conversationType: ConversationType.General,
|
|
1215
|
-
textEngine: TextEngines.SimpleText,
|
|
1216
|
-
autoStart: true,
|
|
1217
|
-
messages: [initialMessage],
|
|
1218
|
-
model: { provider: 'google' },
|
|
1219
|
-
tts: { voice: getRandomQuickVoice(baseLang || 'en', 'female') },
|
|
1220
|
-
};
|
|
1221
|
-
return conversationSettings;
|
|
1222
1440
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
class DCLessonRendererComponent {
|
|
1234
|
-
constructor() {
|
|
1235
|
-
// --- Signal Inputs ---
|
|
1236
|
-
this.lessonInput = input(...(ngDevMode ? [undefined, { debugName: "lessonInput" }] : [])); // Input signal for lesson object
|
|
1237
|
-
this.lessonIdInput = input(...(ngDevMode ? [undefined, { debugName: "lessonIdInput" }] : [])); // Input signal for lesson ID
|
|
1238
|
-
this.settings = input(...(ngDevMode ? [undefined, { debugName: "settings" }] : []));
|
|
1239
|
-
// --- Outputs ---
|
|
1240
|
-
this.wordClicked = new EventEmitter(); // New output event
|
|
1241
|
-
// --- View Childs ---
|
|
1242
|
-
this.dynamicLesson = viewChild('dynamicLesson', ...(ngDevMode ? [{ debugName: "dynamicLesson" }] : []));
|
|
1243
|
-
// --- Services ---
|
|
1244
|
-
this.renderer = inject(Renderer2);
|
|
1245
|
-
this.viewContainerRef = inject(ViewContainerRef);
|
|
1246
|
-
this.toastrService = inject(TOAST_ALERTS_TOKEN);
|
|
1247
|
-
this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
|
|
1248
|
-
this.uiStateService = inject(UiStateService);
|
|
1249
|
-
this.lessonRendererService = inject(LessonRendererService);
|
|
1250
|
-
this.lessonConversationService = inject(LessonConversationService);
|
|
1251
|
-
this.mobileService = inject(MobileService);
|
|
1252
|
-
// --- State Signals ---
|
|
1253
|
-
this.lesson = signal(undefined, ...(ngDevMode ? [{ debugName: "lesson" }] : [])); // Internal lesson state signal
|
|
1254
|
-
// --- Computed Signals ---
|
|
1255
|
-
this.imageCover = computed(() => this.lesson()?.media?.images?.find((img) => img.type === 'cover')?.url, ...(ngDevMode ? [{ debugName: "imageCover" }] : [])); // Computed signal for imageCover
|
|
1256
|
-
// --- Properties ---
|
|
1257
|
-
this.components = {};
|
|
1258
|
-
this.mainForm = new FormGroup({});
|
|
1259
|
-
this.previousTextCoded = undefined; // Store previous value
|
|
1260
|
-
// Effect to fetch lesson data if ID is provided and lesson object isn't
|
|
1261
|
-
effect(async () => {
|
|
1262
|
-
const lessonInput = this.lessonInput();
|
|
1263
|
-
const lessonId = this.lessonIdInput();
|
|
1264
|
-
if (lessonInput) {
|
|
1265
|
-
this.lesson.set(lessonInput); // Use input lesson directly
|
|
1266
|
-
}
|
|
1267
|
-
else if (lessonId && !this.lesson()) {
|
|
1268
|
-
// Fetch only if ID exists and internal lesson is not set
|
|
1269
|
-
console.log(`[Renderer] Effect 1: Fetching lesson ${lessonId}`);
|
|
1270
|
-
try {
|
|
1271
|
-
// Consider adding a loading state signal here
|
|
1272
|
-
const fetchedLesson = await this.lessonsService.getLesson(lessonId);
|
|
1273
|
-
this.lesson.set(fetchedLesson);
|
|
1274
|
-
console.log('Fetched lesson:', fetchedLesson);
|
|
1275
|
-
}
|
|
1276
|
-
catch (error) {
|
|
1277
|
-
console.error(`Failed to fetch lesson with ID: ${lessonId}`, error);
|
|
1278
|
-
this.toastrService.error({ subtitle: 'Failed to load lesson data.', title: 'Error' });
|
|
1279
|
-
this.lesson.set(undefined); // Reset lesson on error
|
|
1280
|
-
}
|
|
1281
|
-
finally {
|
|
1282
|
-
// Reset loading state signal here
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
else if (!lessonInput && !lessonId) {
|
|
1286
|
-
// Handle case where neither input is provided, maybe clear the lesson?
|
|
1287
|
-
this.lesson.set(undefined);
|
|
1288
|
-
}
|
|
1289
|
-
}, { allowSignalWrites: true }); // Allow signal writes inside effect
|
|
1290
|
-
// Effect to render the lesson only when textCoded value actually changes
|
|
1291
|
-
effect(() => {
|
|
1292
|
-
const dynamicLessonEl = this.dynamicLesson();
|
|
1293
|
-
const currentLesson = this.lesson(); // Read the lesson signal
|
|
1294
|
-
if (!dynamicLessonEl || !currentLesson) {
|
|
1295
|
-
return;
|
|
1296
|
-
}
|
|
1297
|
-
if (currentLesson?.format === 'markdown') {
|
|
1298
|
-
// If markdown content exists, we clear any dynamically rendered components
|
|
1299
|
-
// and prevent the textCoded logic from running.
|
|
1300
|
-
if (this.previousTextCoded) {
|
|
1301
|
-
this.lessonRendererService.clearLessonRendering(dynamicLessonEl.nativeElement);
|
|
1302
|
-
this.mainForm = this.lessonRendererService.mainForm;
|
|
1303
|
-
this.previousTextCoded = undefined;
|
|
1304
|
-
}
|
|
1305
|
-
return;
|
|
1306
|
-
}
|
|
1307
|
-
const newTextCoded = currentLesson?.textCoded; // Get the current textCoded value
|
|
1308
|
-
// Quick fix to only render on changes textCode not all the lesson
|
|
1309
|
-
if (newTextCoded !== this.previousTextCoded) {
|
|
1310
|
-
if (newTextCoded !== undefined && newTextCoded !== null && currentLesson) {
|
|
1311
|
-
this.components = this.lessonRendererService.renderLesson(currentLesson, this.viewContainerRef, dynamicLessonEl.nativeElement, this.renderer);
|
|
1312
|
-
this.mainForm = this.lessonRendererService.mainForm;
|
|
1313
|
-
}
|
|
1314
|
-
else {
|
|
1315
|
-
this.lessonRendererService.clearLessonRendering(dynamicLessonEl.nativeElement);
|
|
1316
|
-
this.mainForm = this.lessonRendererService.mainForm;
|
|
1317
|
-
}
|
|
1318
|
-
// Update the stored previous value *after* comparison and action
|
|
1319
|
-
this.previousTextCoded = newTextCoded;
|
|
1320
|
-
}
|
|
1321
|
-
else {
|
|
1322
|
-
console.log('[Renderer] textCoded has NOT changed. Skipping render/clear.');
|
|
1323
|
-
}
|
|
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
|
+
},
|
|
1324
1450
|
});
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
}
|
|
1329
|
-
async evaluateForms() {
|
|
1330
|
-
const result = this.lessonRendererService.evaluateForms();
|
|
1331
|
-
if (!result) {
|
|
1332
|
-
return;
|
|
1333
|
-
}
|
|
1334
|
-
const currentLesson = this.lesson();
|
|
1335
|
-
if (!currentLesson) {
|
|
1336
|
-
console.error('Cannot save result, lesson data is missing.');
|
|
1337
|
-
this.toastrService.error({ subtitle: 'Cannot save result, lesson data missing.', title: 'Error' });
|
|
1338
|
-
return;
|
|
1339
|
-
}
|
|
1340
|
-
const status = result.rates.score >= 0.7 ? 'passed' : 'failed';
|
|
1341
|
-
const takenLesson = {
|
|
1342
|
-
id: currentLesson.id,
|
|
1343
|
-
goalCompleted: null,
|
|
1344
|
-
score: result.rates.score,
|
|
1345
|
-
status: status,
|
|
1346
|
-
lastAccess: new Date(),
|
|
1347
|
-
};
|
|
1348
|
-
console.log('Lesson evaluation result:', takenLesson);
|
|
1349
|
-
// TODO: Re-implement saving the taken lesson status via lessonService
|
|
1350
|
-
}
|
|
1351
|
-
// --- AI Chat Logic ---
|
|
1352
|
-
async startAI() {
|
|
1353
|
-
const currentLesson = this.lesson();
|
|
1354
|
-
if (!currentLesson) {
|
|
1355
|
-
console.error('Cannot start AI without a lesson.');
|
|
1356
|
-
this.toastrService.error({ subtitle: 'Lesson data not available.', title: 'Cannot Start Chat' });
|
|
1357
|
-
return;
|
|
1358
|
-
}
|
|
1359
|
-
const conversationSettings = await this.lessonConversationService.startAI(currentLesson, this.settings());
|
|
1360
|
-
if (conversationSettings) {
|
|
1361
|
-
this.uiStateService.chatDrawerVisible.set(true);
|
|
1362
|
-
}
|
|
1363
|
-
else {
|
|
1364
|
-
this.toastrService.error({ subtitle: 'Could not prepare the AI chat session.', title: 'Error' });
|
|
1365
|
-
}
|
|
1366
|
-
}
|
|
1367
|
-
onVisibleChange(isVisible) {
|
|
1368
|
-
if (isVisible === false) {
|
|
1369
|
-
this.uiStateService.chatDrawerVisible.set(false);
|
|
1370
|
-
}
|
|
1371
|
-
}
|
|
1372
|
-
async handleGoalCompleted(event = {}) {
|
|
1373
|
-
console.log('Goal completed:', event);
|
|
1374
|
-
const lesson = this.lesson();
|
|
1375
|
-
const takenLesson = { id: lesson.id, goalCompleted: true, score: 100, status: 'finished', lastAccess: new Date() };
|
|
1376
|
-
const res = await this.lessonsService.saveTakenLesson(takenLesson);
|
|
1377
|
-
if (res) {
|
|
1378
|
-
// this.lessonsMemStateService.updateUserState(res);
|
|
1379
|
-
}
|
|
1380
|
-
this.toastrService.success({ subtitle: '¡Has completado la lección! , pero puedes seguir conversando', title: '¡Muy bien, guardaremos tu progreso!' });
|
|
1381
|
-
}
|
|
1382
|
-
onChatMessage(event) {
|
|
1383
|
-
console.log('Received chat event:', event);
|
|
1384
|
-
switch (event.type) {
|
|
1385
|
-
case ChatEventType.WordClicked: {
|
|
1386
|
-
const wordClickedData = event.payload;
|
|
1387
|
-
console.log('Word clicked event received, emitting output:', wordClickedData);
|
|
1388
|
-
// Removed: this.wordPopupService.showWordPopup(wordClicked); // Use the service
|
|
1389
|
-
this.wordClicked.emit(wordClickedData); // Emit the event instead
|
|
1390
|
-
break;
|
|
1451
|
+
this.ref.onClose.subscribe((result) => {
|
|
1452
|
+
if (result) {
|
|
1453
|
+
this.onNewDynamicComponent.emit(result);
|
|
1391
1454
|
}
|
|
1392
|
-
|
|
1393
|
-
console.log('Unhandled chat event type:', event.type);
|
|
1394
|
-
}
|
|
1455
|
+
});
|
|
1395
1456
|
}
|
|
1396
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type:
|
|
1397
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
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"] }] }); }
|
|
1398
1459
|
}
|
|
1399
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type:
|
|
1460
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonComponentAdderComponent, decorators: [{
|
|
1400
1461
|
type: Component,
|
|
1401
|
-
args: [{ selector: 'dc-lesson-
|
|
1402
|
-
}],
|
|
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: [{
|
|
1403
1466
|
type: Output
|
|
1404
1467
|
}] } });
|
|
1405
1468
|
|
|
1406
|
-
class
|
|
1469
|
+
class DCLessonMetadataEditorComponent {
|
|
1407
1470
|
constructor() {
|
|
1408
|
-
|
|
1409
|
-
this.
|
|
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);
|
|
1410
1477
|
this.#toastService = inject(TOAST_ALERTS_TOKEN);
|
|
1411
|
-
|
|
1412
|
-
this.isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
1478
|
+
this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
|
|
1413
1479
|
}
|
|
1414
|
-
|
|
1480
|
+
// Injected Services
|
|
1481
|
+
#lessonUtilsService;
|
|
1415
1482
|
#toastService;
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
const
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
}
|
|
1432
|
-
|
|
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');
|
|
1433
1509
|
}
|
|
1434
1510
|
/**
|
|
1435
|
-
*
|
|
1436
|
-
*
|
|
1437
|
-
* @param notionPageId The Notion page ID to link.
|
|
1438
|
-
* @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.
|
|
1439
1513
|
*/
|
|
1440
|
-
async
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
this.#toastService.warn({ title: '
|
|
1444
|
-
return
|
|
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;
|
|
1445
1519
|
}
|
|
1446
|
-
|
|
1447
|
-
...lesson,
|
|
1448
|
-
extensions: {
|
|
1449
|
-
...(lesson.extensions || {}),
|
|
1450
|
-
extras: {
|
|
1451
|
-
...(lesson.extensions?.['extras'] || {}),
|
|
1452
|
-
notionPageId: notionPageId,
|
|
1453
|
-
},
|
|
1454
|
-
},
|
|
1455
|
-
};
|
|
1456
|
-
this.isLoading.set(true);
|
|
1520
|
+
this.isLoadingLesson = true;
|
|
1457
1521
|
try {
|
|
1458
|
-
const
|
|
1459
|
-
|
|
1460
|
-
|
|
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
|
|
1529
|
+
}
|
|
1530
|
+
else {
|
|
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');
|
|
1537
|
+
}
|
|
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
|
|
1547
|
+
}
|
|
1461
1548
|
}
|
|
1462
1549
|
catch (error) {
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
this.#toastService.error({ title: 'Error
|
|
1466
|
-
return undefined;
|
|
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.' });
|
|
1467
1553
|
}
|
|
1468
1554
|
finally {
|
|
1469
|
-
this.
|
|
1555
|
+
this.isLoadingLesson = false; // Stop loading
|
|
1470
1556
|
}
|
|
1471
1557
|
}
|
|
1472
1558
|
/**
|
|
1473
|
-
*
|
|
1474
|
-
*
|
|
1475
|
-
*
|
|
1476
|
-
* @
|
|
1477
|
-
* @param lessonId The current lesson ID (null if it's a new lesson).
|
|
1478
|
-
* @returns The fetched HTML content from Notion, or null if the process fails.
|
|
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.
|
|
1479
1563
|
*/
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
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
|
+
}
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
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.
|
|
1585
|
+
*/
|
|
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.' });
|
|
1488
1598
|
}
|
|
1489
1599
|
else {
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
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.');
|
|
1494
1604
|
}
|
|
1495
1605
|
}
|
|
1496
1606
|
else {
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
if (notionPageId && lessonId) {
|
|
1503
|
-
const linkedLesson = await this.linkLessonWithNotion(currentLesson, notionPageId);
|
|
1504
|
-
if (!linkedLesson) {
|
|
1505
|
-
// Linking failed, maybe stop the import? Or proceed without linking?
|
|
1506
|
-
// For now, let's stop.
|
|
1507
|
-
this.#toastService.error({ title: 'Error de Enlace', subtitle: 'No se pudo enlazar con Notion antes de importar.' });
|
|
1508
|
-
return null;
|
|
1509
|
-
}
|
|
1510
|
-
// If linking succeeded, the lesson object might have changed, but we proceed with the import using the notionPageId.
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
if (!notionPageId) {
|
|
1514
|
-
this.#toastService.warn({ title: 'Sin ID de Notion', subtitle: 'No se proporcionó un ID de Notion válido para importar.' });
|
|
1515
|
-
return null;
|
|
1516
|
-
}
|
|
1517
|
-
this.isLoading.set(true);
|
|
1518
|
-
try {
|
|
1519
|
-
this.#toastService.info({ title: 'Importando lección...', subtitle: 'Espera unos segundos' });
|
|
1520
|
-
const md = await this.#notionService.getPageInSpecificFormat(notionPageId, NotionExportType.HTML);
|
|
1521
|
-
console.log('Imported MD/HTML:', md);
|
|
1522
|
-
this.#toastService.success({ title: 'Contenido Importado', subtitle: 'Contenido de Notion obtenido.' });
|
|
1523
|
-
return md.content; // Return the fetched content
|
|
1524
|
-
}
|
|
1525
|
-
catch (error) {
|
|
1526
|
-
// Remove explicit type, rely on tsconfig setting
|
|
1527
|
-
console.error('Error importing from Notion:', error);
|
|
1528
|
-
this.#toastService.error({ title: 'Error de importación', subtitle: 'Ocurrió un error inesperado.' });
|
|
1529
|
-
return null; // Return null on failure
|
|
1530
|
-
}
|
|
1531
|
-
finally {
|
|
1532
|
-
this.isLoading.set(false);
|
|
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.');
|
|
1533
1612
|
}
|
|
1534
1613
|
}
|
|
1535
1614
|
/**
|
|
1536
|
-
*
|
|
1537
|
-
*
|
|
1615
|
+
* Calls the LessonUtilsService to generate a description using AI
|
|
1616
|
+
* and updates the lesson signal if successful.
|
|
1538
1617
|
*/
|
|
1539
|
-
async
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
if (!notionId) {
|
|
1544
|
-
this.#toastService.warn({ title: 'Sin ID de Notion', subtitle: 'Enlaza la lección con Notion primero.' });
|
|
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.' });
|
|
1545
1622
|
return;
|
|
1546
1623
|
}
|
|
1547
|
-
this.
|
|
1548
|
-
|
|
1549
|
-
this
|
|
1550
|
-
const md = await this.#notionService.getPageInSpecificFormat(notionId, NotionExportType.HTML);
|
|
1551
|
-
console.log('Content to improve:', md);
|
|
1552
|
-
// TODO: Add actual AI improvement logic here
|
|
1553
|
-
// e.g., call another service: await this.aiImprovementService.improve(md.content);
|
|
1554
|
-
// Then potentially update the lesson via lessonService or return data
|
|
1555
|
-
this.#toastService.success({ title: 'Contenido Obtenido', subtitle: 'Listo para mejorar con IA (lógica no implementada).' });
|
|
1556
|
-
}
|
|
1557
|
-
catch (error) {
|
|
1558
|
-
// Remove explicit type, rely on tsconfig setting
|
|
1559
|
-
console.error('Error improving with AI:', error);
|
|
1560
|
-
this.#toastService.error({ title: 'Error de IA', subtitle: 'Ocurrió un error inesperado.' });
|
|
1561
|
-
}
|
|
1562
|
-
finally {
|
|
1563
|
-
this.isLoading.set(false);
|
|
1624
|
+
const generatedDescription = await this.#lessonUtilsService.generateDescriptionWithAI(currentLesson);
|
|
1625
|
+
if (generatedDescription) {
|
|
1626
|
+
// this.form.controls['description'].setValue(generatedDescription);
|
|
1564
1627
|
}
|
|
1565
1628
|
}
|
|
1566
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type:
|
|
1567
|
-
static { this.ɵ
|
|
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"] }] }); }
|
|
1568
1632
|
}
|
|
1569
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type:
|
|
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: [{
|
|
1570
1692
|
type: Injectable,
|
|
1571
1693
|
args: [{
|
|
1572
1694
|
providedIn: 'root',
|
|
1573
1695
|
}]
|
|
1574
1696
|
}] });
|
|
1575
1697
|
|
|
1576
|
-
class
|
|
1698
|
+
class LessonRendererService {
|
|
1577
1699
|
constructor() {
|
|
1578
|
-
this.
|
|
1579
|
-
this
|
|
1580
|
-
this
|
|
1581
|
-
this.
|
|
1700
|
+
this.dynamicComponentsService = inject(DynamicComponentsRegisterService);
|
|
1701
|
+
this.toastrService = inject(TOAST_ALERTS_TOKEN);
|
|
1702
|
+
this.components = {};
|
|
1703
|
+
this.mainForm = new FormGroup({});
|
|
1582
1704
|
}
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
if (!lesson?.components) {
|
|
1593
|
-
return;
|
|
1594
|
-
}
|
|
1595
|
-
// Placeholder logic - adapt as needed from original component
|
|
1596
|
-
console.log('Validating audios for lesson:', lesson.id);
|
|
1597
|
-
}
|
|
1598
|
-
/**
|
|
1599
|
-
* Updates the lesson signal with a new banner image in metadata.
|
|
1600
|
-
* @param lessonSignal The signal holding the lesson data.
|
|
1601
|
-
* @param imageUploaded The image data object from the upload event. Should conform to LessonImage structure partially.
|
|
1602
|
-
*/
|
|
1603
|
-
uploadCover(lessonSignal, imageUploaded) {
|
|
1604
|
-
lessonSignal.update((currentLesson) => {
|
|
1605
|
-
if (!currentLesson)
|
|
1606
|
-
return undefined;
|
|
1607
|
-
const assets = { ...(currentLesson.assets ?? {}) };
|
|
1608
|
-
assets.banner = imageUploaded;
|
|
1609
|
-
return {
|
|
1610
|
-
...currentLesson,
|
|
1611
|
-
assets,
|
|
1612
|
-
};
|
|
1613
|
-
});
|
|
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;
|
|
1614
1714
|
}
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
*/
|
|
1622
|
-
async generateByAI(lessonId) {
|
|
1623
|
-
if (!lessonId) {
|
|
1624
|
-
this.#toastService.warn({ title: 'ID Requerido', subtitle: 'Se necesita un ID de lección para usar IA.' });
|
|
1625
|
-
return null;
|
|
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 = '';
|
|
1626
1721
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
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 -->';
|
|
1638
1734
|
}
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
this.#toastService.error({ title: 'Error de IA', subtitle: 'No se pudo generar la lección con IA.' });
|
|
1644
|
-
return null; // Return null in catch block
|
|
1645
|
-
}
|
|
1646
|
-
// Loading state should be managed by the component calling this service.
|
|
1735
|
+
createdComponents[componentName] = componentRef;
|
|
1736
|
+
return `<span id="${componentName}"></span>`;
|
|
1737
|
+
});
|
|
1738
|
+
return { htmlContent, components: createdComponents };
|
|
1647
1739
|
}
|
|
1648
|
-
|
|
1649
|
-
* Improves the provided Markdown text using AI and updates the lesson.
|
|
1650
|
-
* Assumes the lesson is already saved before calling this.
|
|
1651
|
-
* @param lesson The lesson object containing the prompt and other context.
|
|
1652
|
-
* @param markdownText The Markdown text generated from the lesson's HTML content.
|
|
1653
|
-
* @returns The improved Markdown string, or null on failure.
|
|
1654
|
-
*/
|
|
1655
|
-
async improveMDWithAI(lesson, markdownText) {
|
|
1656
|
-
if (!markdownText) {
|
|
1657
|
-
this.#toastService.warn({ title: 'Texto Requerido', subtitle: 'Se necesita texto Markdown para mejorar con IA.' });
|
|
1658
|
-
return null;
|
|
1659
|
-
}
|
|
1740
|
+
createComponentReferenceWithJson(json, currentLesson, viewContainerRef) {
|
|
1660
1741
|
try {
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
// Remove potential markdown fences
|
|
1667
|
-
const markdownFenceStart = '```markdown\n';
|
|
1668
|
-
const markdownFenceEnd = '\n```';
|
|
1669
|
-
if (improvedMarkdown?.startsWith(markdownFenceStart) && improvedMarkdown.endsWith(markdownFenceEnd)) {
|
|
1670
|
-
improvedMarkdown = improvedMarkdown.slice(markdownFenceStart.length, -markdownFenceEnd.length);
|
|
1671
|
-
}
|
|
1672
|
-
else {
|
|
1673
|
-
// Also handle cases where it might just be ``` at the start/end
|
|
1674
|
-
const simpleFence = '```';
|
|
1675
|
-
if (improvedMarkdown?.startsWith(simpleFence)) {
|
|
1676
|
-
improvedMarkdown = improvedMarkdown.slice(simpleFence.length);
|
|
1677
|
-
}
|
|
1678
|
-
if (improvedMarkdown?.endsWith(simpleFence)) {
|
|
1679
|
-
improvedMarkdown = improvedMarkdown.slice(0, -simpleFence.length);
|
|
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 };
|
|
1680
1747
|
}
|
|
1681
1748
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
return improvedMarkdown; // Return only the string
|
|
1686
|
-
}
|
|
1687
|
-
catch (error) {
|
|
1688
|
-
console.error('Error during AI Markdown improvement in service:', error);
|
|
1689
|
-
this.#toastService.error({ title: 'Error de IA', subtitle: 'No se pudo mejorar la lección con IA.' });
|
|
1690
|
-
return null; // Return null in catch block
|
|
1691
|
-
}
|
|
1692
|
-
finally {
|
|
1693
|
-
this.loadingBarService.successAndHide();
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
|
-
/**
|
|
1697
|
-
* Generates a concise description for the lesson using AI based on its content.
|
|
1698
|
-
* @param lesson The lesson object containing the content.
|
|
1699
|
-
* @returns A promise resolving to the generated description string, or null on failure.
|
|
1700
|
-
*/
|
|
1701
|
-
async generateDescriptionWithAI(lesson) {
|
|
1702
|
-
if (!lesson || !lesson.textCoded) {
|
|
1703
|
-
this.#toastService.warn({ title: 'Contenido Requerido', subtitle: 'Se necesita contenido en la lección para generar una descripción.' });
|
|
1704
|
-
return null;
|
|
1705
|
-
}
|
|
1706
|
-
try {
|
|
1707
|
-
this.loadingBarService.showIndeterminate(); // Corrected: No argument needed
|
|
1708
|
-
// Extract plain text from the lesson's HTML content
|
|
1709
|
-
const plainTextContent = this._extractTextFromHtml(lesson.textCoded);
|
|
1710
|
-
if (!plainTextContent || plainTextContent.trim().length === 0) {
|
|
1711
|
-
this.#toastService.warn({ title: 'Texto Vacío', subtitle: 'No se pudo extraer texto útil del contenido de la lección.' });
|
|
1749
|
+
const LessonClass = this.dynamicComponentsService.getDynamicComponentClass(lessonCodedConfig.component);
|
|
1750
|
+
if (!LessonClass) {
|
|
1751
|
+
console.error(`Component class not found for type: ${lessonCodedConfig.component}. JSON: ${json}`);
|
|
1712
1752
|
return null;
|
|
1713
1753
|
}
|
|
1714
|
-
const
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
}
|
|
1722
|
-
if (generatedDescription) {
|
|
1723
|
-
this.#toastService.success({ title: 'Descripción Generada', subtitle: 'Se generó la descripción con IA.' });
|
|
1724
|
-
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
|
+
}
|
|
1725
1761
|
}
|
|
1726
|
-
|
|
1727
|
-
|
|
1762
|
+
if (lessonCodedConfig.settings) {
|
|
1763
|
+
componentRef.instance.config = lessonCodedConfig;
|
|
1728
1764
|
}
|
|
1765
|
+
return componentRef;
|
|
1729
1766
|
}
|
|
1730
1767
|
catch (error) {
|
|
1731
|
-
console.error(
|
|
1732
|
-
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);
|
|
1733
1769
|
return null;
|
|
1734
1770
|
}
|
|
1735
|
-
finally {
|
|
1736
|
-
this.loadingBarService.successAndHide();
|
|
1737
|
-
}
|
|
1738
1771
|
}
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
if (!htmlInput)
|
|
1746
|
-
return '';
|
|
1747
|
-
// 1. Replace encoded JSON blocks with their 'settings.text' or an empty string
|
|
1748
|
-
const jsonRegex = /~(.+?)~/g;
|
|
1749
|
-
const textWithPlaceholders = htmlInput.replace(jsonRegex, (match, jsonCoded) => {
|
|
1750
|
-
try {
|
|
1751
|
-
const decodedJson = jsonCoded.replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1752
|
-
const data = JSON.parse(decodedJson);
|
|
1753
|
-
return data?.settings?.text || ''; // Return text or empty string if not found
|
|
1754
|
-
}
|
|
1755
|
-
catch (error) {
|
|
1756
|
-
console.error('Failed to parse JSON inside HTML during text extraction:', jsonCoded, error);
|
|
1757
|
-
return ''; // Return empty string on error
|
|
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);
|
|
1758
1778
|
}
|
|
1759
1779
|
});
|
|
1760
|
-
|
|
1761
|
-
// Create a temporary DOM element to parse the HTML
|
|
1762
|
-
const tempDiv = document.createElement('div');
|
|
1763
|
-
tempDiv.innerHTML = textWithPlaceholders;
|
|
1764
|
-
// Use textContent to get the plain text, effectively stripping tags
|
|
1765
|
-
// Replace multiple whitespace characters (including newlines) with a single space
|
|
1766
|
-
return (tempDiv.textContent || tempDiv.innerText || '').replace(/\s+/g, ' ').trim();
|
|
1780
|
+
this.mainForm = new FormGroup(newFormControls);
|
|
1767
1781
|
}
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
*/
|
|
1774
|
-
cleanOrphanedComponents(lesson) {
|
|
1775
|
-
if (!lesson || !lesson.textCoded || !lesson.dynamicComponents) {
|
|
1776
|
-
// Return the original lesson if essential parts are missing
|
|
1777
|
-
return lesson;
|
|
1778
|
-
}
|
|
1779
|
-
const textCoded = lesson.textCoded;
|
|
1780
|
-
const existingComponents = lesson.dynamicComponents;
|
|
1781
|
-
const existingComponentIds = new Set(Object.keys(existingComponents));
|
|
1782
|
-
// Regex to find "id":"<component_id>" within the textCoded string
|
|
1783
|
-
const idRegex = /"id":"([^"]+)"/g;
|
|
1784
|
-
const foundIdsInText = new Set();
|
|
1785
|
-
let match;
|
|
1786
|
-
while ((match = idRegex.exec(textCoded)) !== null) {
|
|
1787
|
-
const potentialId = match[1];
|
|
1788
|
-
// Check if the found ID actually exists in our dynamic components map
|
|
1789
|
-
// This ensures we only count IDs that correspond to known components.
|
|
1790
|
-
if (existingComponentIds.has(potentialId)) {
|
|
1791
|
-
foundIdsInText.add(potentialId);
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
1794
|
-
const orphanedIds = [];
|
|
1795
|
-
const cleanedDynamicComponents = {};
|
|
1796
|
-
// Iterate through existing component IDs
|
|
1797
|
-
for (const componentId of existingComponentIds) {
|
|
1798
|
-
if (foundIdsInText.has(componentId)) {
|
|
1799
|
-
// Keep the component if its ID was found in the text
|
|
1800
|
-
cleanedDynamicComponents[componentId] = existingComponents[componentId];
|
|
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);
|
|
1801
1787
|
}
|
|
1802
1788
|
else {
|
|
1803
|
-
|
|
1804
|
-
orphanedIds.push(componentId);
|
|
1789
|
+
console.warn(`Placeholder element with ID '${name}' not found in the DOM.`);
|
|
1805
1790
|
}
|
|
1806
|
-
}
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
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
|
+
}
|
|
1813
1803
|
});
|
|
1804
|
+
this.toastrService.warn({ subtitle: 'Por favor completa todos los ejercicios', title: 'Incompleto' });
|
|
1805
|
+
return null;
|
|
1814
1806
|
}
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
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 };
|
|
1820
1836
|
}
|
|
1821
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type:
|
|
1822
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type:
|
|
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' }); }
|
|
1823
1839
|
}
|
|
1824
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type:
|
|
1840
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: LessonRendererService, decorators: [{
|
|
1825
1841
|
type: Injectable,
|
|
1826
1842
|
args: [{
|
|
1827
|
-
providedIn: 'root',
|
|
1843
|
+
providedIn: 'root',
|
|
1828
1844
|
}]
|
|
1829
1845
|
}] });
|
|
1830
1846
|
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
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: [],
|
|
1847
1943
|
},
|
|
1848
|
-
width: '80vw',
|
|
1849
|
-
header: 'Agregar componente',
|
|
1850
|
-
closable: true,
|
|
1851
1944
|
});
|
|
1852
|
-
return dialogRef;
|
|
1853
1945
|
}
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
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}
|
|
1863
1974
|
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1975
|
+
<Lesson Text>
|
|
1976
|
+
${lessonText}
|
|
1977
|
+
</Lesson Text>
|
|
1978
|
+
|
|
1979
|
+
${EngagementSkill}
|
|
1980
|
+
|
|
1981
|
+
<User Information>
|
|
1982
|
+
${userInformationPrompt}`;
|
|
1871
1983
|
}
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
}
|
|
1884
|
-
});
|
|
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);
|
|
1885
1995
|
}
|
|
1886
1996
|
else {
|
|
1887
|
-
|
|
1888
|
-
|
|
1997
|
+
lessonText = lesson.markdown;
|
|
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;
|
|
1889
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;
|
|
1890
2019
|
}
|
|
1891
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type:
|
|
1892
|
-
static { this.ɵ
|
|
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' }); }
|
|
1893
2022
|
}
|
|
1894
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type:
|
|
1895
|
-
type:
|
|
1896
|
-
args: [{
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
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
|
+
}] });
|
|
1900
2029
|
|
|
1901
|
-
class
|
|
2030
|
+
class DCLessonRendererComponent {
|
|
1902
2031
|
constructor() {
|
|
1903
|
-
//
|
|
1904
|
-
this.
|
|
1905
|
-
this.
|
|
1906
|
-
this.
|
|
1907
|
-
//
|
|
1908
|
-
this
|
|
1909
|
-
|
|
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);
|
|
1910
2044
|
this.lessonsService = inject(LESSONS_TOKEN, { optional: true }) ?? inject(DefaultLessonsService);
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
//
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
//
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
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;
|
|
1961
2117
|
}
|
|
1962
2118
|
else {
|
|
1963
|
-
|
|
1964
|
-
const lessonToSave = this.#lessonUtilsService.cleanOrphanedComponents(currentLesson);
|
|
1965
|
-
const savedLesson = await this.lessonsService.postLesson(lessonToSave);
|
|
1966
|
-
if (!savedLesson) {
|
|
1967
|
-
this.#toastService.error({ title: 'Error al guardar', subtitle: 'No se pudo guardar antes de generar con IA.' });
|
|
1968
|
-
throw new Error('Failed to save before AI generation');
|
|
1969
|
-
}
|
|
1970
|
-
this.lesson = savedLesson;
|
|
1971
|
-
// Replace encoded JSON with actual text before Markdown conversion
|
|
1972
|
-
const processedHtmlContent = this._extractTextFromEncodedJson(rawHtmlContent);
|
|
1973
|
-
// Convert the processed HTML (with text instead of JSON) to Markdown
|
|
1974
|
-
// const markdownText = this.turndownService.turndown(processedHtmlContent);
|
|
1975
|
-
// Use the updated lesson signal value for AI improvement
|
|
1976
|
-
// const improvedMarkdown = await this.#lessonUtilsService.improveMDWithAI(this.lesson, markdownText);
|
|
1977
|
-
// Convert and save the improved content
|
|
1978
|
-
// await this._convertMarkdownToHtmlAndSave(improvedMarkdown); // Use extracted method
|
|
2119
|
+
console.log('[Renderer] textCoded has NOT changed. Skipping render/clear.');
|
|
1979
2120
|
}
|
|
2121
|
+
});
|
|
2122
|
+
}
|
|
2123
|
+
ngOnInit() {
|
|
2124
|
+
this.lessonConversationService.initializeConversationFlow();
|
|
2125
|
+
}
|
|
2126
|
+
async evaluateForms() {
|
|
2127
|
+
const result = this.lessonRendererService.evaluateForms();
|
|
2128
|
+
if (!result) {
|
|
2129
|
+
return;
|
|
1980
2130
|
}
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
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;
|
|
1985
2136
|
}
|
|
1986
|
-
|
|
1987
|
-
|
|
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' });
|
|
1988
2162
|
}
|
|
1989
2163
|
}
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
const
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
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;
|
|
2005
2188
|
}
|
|
2006
|
-
|
|
2007
|
-
console.
|
|
2008
|
-
|
|
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());
|
|
2009
2215
|
}
|
|
2010
2216
|
});
|
|
2011
2217
|
}
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
* @param improvedMarkdown The Markdown content generated by AI.
|
|
2016
|
-
* @throws Error if the markdown is empty/null or if saving fails.
|
|
2017
|
-
*/
|
|
2018
|
-
async _convertMarkdownToHtmlAndSave(improvedMarkdown) {
|
|
2019
|
-
if (improvedMarkdown) {
|
|
2020
|
-
// Convert the improved Markdown back to HTML before setting it
|
|
2021
|
-
const improvedHtml = improvedMarkdown;
|
|
2022
|
-
// Update the signal directly
|
|
2023
|
-
// this.lesson.update((current) => (current ? { ...current, textCoded: improvedHtml } : undefined));
|
|
2024
|
-
// Save the AI-generated content
|
|
2025
|
-
// Ensure lesson() is not undefined before saving
|
|
2026
|
-
const lessonToSave = this.lesson;
|
|
2027
|
-
if (lessonToSave) {
|
|
2028
|
-
await this.lessonsService.postLesson(lessonToSave);
|
|
2029
|
-
this.#toastService.success({ title: 'Contenido generado', subtitle: 'Se generó y guardó el contenido con IA.' });
|
|
2030
|
-
}
|
|
2031
|
-
else {
|
|
2032
|
-
console.error('Lesson signal is undefined, cannot save.');
|
|
2033
|
-
this.#toastService.error({ title: 'Error Interno', subtitle: 'No se pudo guardar la lección.' });
|
|
2034
|
-
// Throw an error to be caught by the calling method's try/catch
|
|
2035
|
-
throw new Error('Lesson signal is undefined during save operation.');
|
|
2036
|
-
}
|
|
2037
|
-
}
|
|
2038
|
-
else {
|
|
2039
|
-
// Log the error, the toast might be handled by the calling service/context
|
|
2040
|
-
console.error('AI generation failed or provided markdown was empty.');
|
|
2041
|
-
// Let the caller handle the specific user feedback if needed
|
|
2042
|
-
// Throw an error to be caught by the calling method's try/catch
|
|
2043
|
-
throw new Error('AI generation failed or the resulting markdown was empty.');
|
|
2044
|
-
}
|
|
2218
|
+
updateHtmlTextCoded(key, value) {
|
|
2219
|
+
this.lesson.update((lesson) => ({ ...lesson, [key]: value }));
|
|
2220
|
+
this.updateLesson.emit(this.lesson());
|
|
2045
2221
|
}
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
*/
|
|
2050
|
-
async triggerGenerateDescriptionAI() {
|
|
2051
|
-
const currentLesson = this.lesson;
|
|
2052
|
-
if (!currentLesson) {
|
|
2053
|
-
this.#toastService.warn({ title: 'Lección no cargada', subtitle: 'Espera a que la lección se cargue.' });
|
|
2054
|
-
return;
|
|
2222
|
+
parseHttp(entity) {
|
|
2223
|
+
if (this.lessonInput().format === 'markdown') {
|
|
2224
|
+
this.httpTemporalModel = this.markdownService.parse(entity.markdown);
|
|
2055
2225
|
}
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
// this.form.controls['description'].setValue(generatedDescription);
|
|
2226
|
+
else {
|
|
2227
|
+
this.httpTemporalModel = entity.textCoded;
|
|
2059
2228
|
}
|
|
2060
2229
|
}
|
|
2061
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type:
|
|
2062
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
2063
|
-
InputGroupModule }, { kind: "component", type: i5.InputGroup, selector: "p-inputgroup, p-inputGroup, p-input-group", inputs: ["styleClass"] }, { kind: "ngmodule", type: DividerModule }, { kind: "component", type: i6.Divider, selector: "p-divider", inputs: ["styleClass", "layout", "type", "align"] }] }); }
|
|
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"] }] }); }
|
|
2064
2232
|
}
|
|
2065
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type:
|
|
2233
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DcLessonTextEditorComponent, decorators: [{
|
|
2066
2234
|
type: Component,
|
|
2067
|
-
args: [{ selector: 'dc-lesson-
|
|
2068
|
-
|
|
2069
|
-
FormsModule,
|
|
2070
|
-
ButtonModule,
|
|
2071
|
-
InputTextModule,
|
|
2072
|
-
ReactiveFormsModule,
|
|
2073
|
-
TooltipModule, // Added TooltipModule
|
|
2074
|
-
InputGroupModule,
|
|
2075
|
-
DividerModule,
|
|
2076
|
-
], template: "<div>\n <!-- <div style=\"display: flex; gap: 10px; padding: 10px\">\n <p-button label=\"Guardar\" severity=\"primary\" (click)=\"emitSaveRequest()\" />\n <p-button label=\"Importar de Notion\" severity=\"help\" (click)=\"emitImportNotionRequest()\" />\n <p-button label=\"Mejorar Notion con AI\" severity=\"help\" (click)=\"emitImproveNotionRequest()\" />\n </div> -->\n\n <div>\n <div>\n <span>Nombre de La lecci\u00F3n</span>\n <input pInputText style=\"width: 100%\" [formControl]=\"form.controls['name']\" type=\"text\" placeholder=\"Agrega un nombre\" />\n </div>\n <!-- <div>\n <span>T\u00EDtulo </span>\n <input pInputText style=\"width: 100%\" [formControl]=\"form.controls['title']\" type=\"text\" placeholder=\"Agrega un t\u00EDtulo\" />\n </div> -->\n\n <div style=\"margin-top: 4px\">\n <span>Descripci\u00F3n </span>\n <p-inputgroup>\n <input pInputText style=\"width: 100%\" [formControl]=\"form.controls['description']\" type=\"text\" placeholder=\"Agrega una descripci\u00F3n\" />\n <p-button\n icon=\"pi pi-sparkles\"\n styleClass=\"p-button-secondary p-button-outlined\"\n pTooltip=\"Generar descripci\u00F3n con IA\"\n tooltipPosition=\"top\"\n [disabled]=\"isLoadingLesson\"\n (click)=\"triggerGenerateDescriptionAI()\" />\n </p-inputgroup>\n </div>\n </div>\n\n <div style=\"display: flex; align-items: center; margin-top: 10px\">\n <input\n pInputText\n style=\"flex: auto; margin-right: 5px\"\n [value]=\"lesson?.auditable?.prompt || ''\"\n (input)=\"handlePromptInputChange($event)\"\n type=\"text\"\n placeholder=\"Prompt para IA (opcional)\" />\n <p-button severity=\"primary\" label=\"Generar con IA\" icon=\"pi pi-sparkles\" [disabled]=\"isLoadingLesson\" (click)=\"generateByAI()\" />\n </div>\n\n <p-divider />\n\n <!-- <div style=\"display: flex; align-items: center; margin-top: 10px; gap: 10px\">\n <input pInputText [value]=\"lesson?.extensions?.['level'] || ''\" type=\"number\" placeholder=\"Nivel\" style=\"width: 80px\" />\n\n @if (lesson?.extensions) {\n <div>\n {{ lesson?.extensions?.['baseLang'] | flagEmoji }} -> {{ lesson?.extensions?.['targetLang'] | flagEmoji }} Lecci\u00F3n para hablantes de\n {{ lesson?.extensions?.['baseLang'] | langDesc : 'es' }} que aprenden\n {{ lesson?.extensions?.['targetLang'] | langDesc : 'es' }}\n </div>\n }\n </div> -->\n</div>\n" }]
|
|
2077
|
-
}], propDecorators: { form: [{
|
|
2078
|
-
type: Input,
|
|
2079
|
-
args: [{ required: true }]
|
|
2080
|
-
}], lesson: [{
|
|
2081
|
-
type: Input,
|
|
2082
|
-
args: [{ required: true }]
|
|
2083
|
-
}], isLoadingLesson: [{
|
|
2084
|
-
type: Input,
|
|
2085
|
-
args: [{ required: true }]
|
|
2086
|
-
}], saveRequest: [{
|
|
2087
|
-
type: Output
|
|
2088
|
-
}], importNotionRequest: [{
|
|
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: [{
|
|
2089
2237
|
type: Output
|
|
2090
|
-
}],
|
|
2238
|
+
}], updateLesson: [{
|
|
2091
2239
|
type: Output
|
|
2092
2240
|
}] } });
|
|
2093
2241
|
|
|
2094
|
-
class
|
|
2242
|
+
class DcDynamicComponentPreviewComponent {
|
|
2095
2243
|
constructor() {
|
|
2096
|
-
this.
|
|
2097
|
-
this.
|
|
2098
|
-
this.formatOptions = [
|
|
2099
|
-
{ label: 'HTML', value: 'html' },
|
|
2100
|
-
{ label: 'Markdown', value: 'markdown' },
|
|
2101
|
-
];
|
|
2244
|
+
this.showComponentDetails = new EventEmitter();
|
|
2245
|
+
this.editComponent = new EventEmitter();
|
|
2102
2246
|
}
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
version: ['1.0'],
|
|
2106
|
-
id: [''],
|
|
2107
|
-
name: [''],
|
|
2108
|
-
description: [''],
|
|
2109
|
-
format: ['html'],
|
|
2110
|
-
lang: [''],
|
|
2111
|
-
characterCard: [],
|
|
2112
|
-
conversationSettings: [],
|
|
2113
|
-
metaApp: [],
|
|
2114
|
-
conversationFlow: [],
|
|
2115
|
-
textCoded: [''],
|
|
2116
|
-
manageable: this.formUtils.createManageableFormGroup(),
|
|
2117
|
-
learnable: this.formUtils.createLearnableFormGroup(),
|
|
2118
|
-
});
|
|
2247
|
+
onShowComponentDetails(component) {
|
|
2248
|
+
this.showComponentDetails.emit(component);
|
|
2119
2249
|
}
|
|
2120
|
-
|
|
2121
|
-
|
|
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"] }] }); }
|
|
2122
2255
|
}
|
|
2123
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type:
|
|
2124
|
-
type:
|
|
2125
|
-
args: [{
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
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
|
+
}] } });
|
|
2129
2266
|
|
|
2130
2267
|
class DCLessonEditorComponent extends EntityBaseFormComponent {
|
|
2131
2268
|
constructor() {
|
|
2132
2269
|
super(...arguments);
|
|
2133
2270
|
this.lessonFormEditorService = inject(LessonFormEditorService);
|
|
2134
|
-
this.markdownService = inject(MarkdownService);
|
|
2135
2271
|
this.form = this.lessonFormEditorService.createLessonForm();
|
|
2136
2272
|
this.formatOptions = this.lessonFormEditorService.formatOptions;
|
|
2137
2273
|
this.entityCommunicationService = inject(LESSONS_TOKEN);
|
|
2138
|
-
this.htmlTemporal = '';
|
|
2139
2274
|
// Services
|
|
2140
2275
|
this.activatedRoute = inject(ActivatedRoute); // Re-inject as it's needed for navigation
|
|
2141
2276
|
this.lessonNotionService = inject(LessonNotionService);
|
|
@@ -2144,20 +2279,19 @@ class DCLessonEditorComponent extends EntityBaseFormComponent {
|
|
|
2144
2279
|
this.loadingBarService = inject(LoadingBarService);
|
|
2145
2280
|
this.defaultLessonsService = inject(DefaultLessonsService);
|
|
2146
2281
|
this.promptService = inject(PromptService);
|
|
2147
|
-
this.ngxVertexService = inject(
|
|
2282
|
+
this.ngxVertexService = inject(NgxAiServicesService);
|
|
2148
2283
|
this.dynamicComponentsBuilderService = inject(DynamicComponentsBuilderService);
|
|
2149
2284
|
this.isLoadingLesson = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingLesson" }] : []));
|
|
2150
2285
|
// Computed signal to get dynamic components as an array for easier iteration in the template
|
|
2151
2286
|
this.dynamicComponentsArray = computed(() => {
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2287
|
+
const currentLesson = this.entity();
|
|
2288
|
+
if (currentLesson?.dynamicComponents) {
|
|
2289
|
+
return Object.values(currentLesson.dynamicComponents);
|
|
2290
|
+
}
|
|
2156
2291
|
return []; // Return empty array if no lesson or no dynamic components
|
|
2157
2292
|
}, ...(ngDevMode ? [{ debugName: "dynamicComponentsArray" }] : []));
|
|
2158
2293
|
// States
|
|
2159
2294
|
this.components = {}; // Current Dynamic components
|
|
2160
|
-
this.editor = BalloonEditor;
|
|
2161
2295
|
this.lessonComponentEnum = LessonComponentEnum;
|
|
2162
2296
|
this.coverStorageSettings = {
|
|
2163
2297
|
path: `lessons/${this.entityId()}/covers`,
|
|
@@ -2166,14 +2300,6 @@ class DCLessonEditorComponent extends EntityBaseFormComponent {
|
|
|
2166
2300
|
};
|
|
2167
2301
|
}
|
|
2168
2302
|
patchForm(entity) {
|
|
2169
|
-
console.log(this.form);
|
|
2170
|
-
if (this.entity().format === 'markdown') {
|
|
2171
|
-
this.htmlTemporal = this.markdownService.parse(entity.markdown);
|
|
2172
|
-
}
|
|
2173
|
-
else {
|
|
2174
|
-
this.htmlTemporal = entity.textCoded;
|
|
2175
|
-
}
|
|
2176
|
-
// console.log(this.htmlTemporal);
|
|
2177
2303
|
this.form.patchValue(entity);
|
|
2178
2304
|
}
|
|
2179
2305
|
/**
|
|
@@ -2190,20 +2316,19 @@ class DCLessonEditorComponent extends EntityBaseFormComponent {
|
|
|
2190
2316
|
return { ...currentLesson, [property]: value };
|
|
2191
2317
|
});
|
|
2192
2318
|
}
|
|
2193
|
-
|
|
2319
|
+
onAssetsChange(updatedAssets) {
|
|
2320
|
+
console.log(updatedAssets);
|
|
2194
2321
|
this.entity.update((currentLesson) => {
|
|
2195
2322
|
if (!currentLesson)
|
|
2196
2323
|
return undefined;
|
|
2197
|
-
return { ...currentLesson,
|
|
2324
|
+
return { ...currentLesson, assets: updatedAssets };
|
|
2198
2325
|
});
|
|
2199
|
-
this.form.controls.textCoded.setValue(value);
|
|
2200
2326
|
}
|
|
2201
|
-
|
|
2202
|
-
console.log(updatedAssets);
|
|
2327
|
+
updateLesson(event) {
|
|
2203
2328
|
this.entity.update((currentLesson) => {
|
|
2204
2329
|
if (!currentLesson)
|
|
2205
2330
|
return undefined;
|
|
2206
|
-
return { ...currentLesson,
|
|
2331
|
+
return { ...currentLesson, ...event };
|
|
2207
2332
|
});
|
|
2208
2333
|
}
|
|
2209
2334
|
async saveLesson(event) {
|
|
@@ -2278,6 +2403,28 @@ class DCLessonEditorComponent extends EntityBaseFormComponent {
|
|
|
2278
2403
|
async improveNotionWithAI() {
|
|
2279
2404
|
await this.lessonNotionService.improveLessonWithNotionAI(this.entity());
|
|
2280
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
|
+
}
|
|
2281
2428
|
showComponentDetails(data) {
|
|
2282
2429
|
alert('showComponentDetails' + JSON.stringify(data));
|
|
2283
2430
|
}
|
|
@@ -2316,20 +2463,17 @@ class DCLessonEditorComponent extends EntityBaseFormComponent {
|
|
|
2316
2463
|
this.entityCommunicationService.partialUpdate(this.entityId(), { assets: event.assets });
|
|
2317
2464
|
}
|
|
2318
2465
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonEditorComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
2319
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
2320
|
-
DialogModule }, { kind: "component", type: AssetsLoaderComponent, selector: "assets-loader", inputs: ["assets", "storagePath"], outputs: ["assetsChange", "assetUpdate", "onFileSelected"] }, { kind: "component", type: DCLessonMetadataEditorComponent, selector: "dc-lesson-metadata-editor", inputs: ["form", "lesson", "isLoadingLesson"], outputs: ["saveRequest", "importNotionRequest", "improveNotionRequest"] }, { kind: "component", type: DcManageableFormComponent, selector: "dc-manageable-form", inputs: ["form"] }, { kind: "component", type: DcLearnableFormComponent, selector: "dc-learnable-form", inputs: ["form"] }] }); }
|
|
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"] }] }); }
|
|
2321
2468
|
}
|
|
2322
2469
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: DCLessonEditorComponent, decorators: [{
|
|
2323
2470
|
type: Component,
|
|
2324
2471
|
args: [{ selector: 'dc-lesson-editor', standalone: true, imports: [
|
|
2325
2472
|
ButtonModule,
|
|
2326
|
-
CKEditorModule,
|
|
2327
|
-
DCLessonRendererComponent,
|
|
2328
2473
|
FormsModule,
|
|
2329
2474
|
InputTextModule,
|
|
2330
2475
|
ReactiveFormsModule,
|
|
2331
2476
|
SelectButtonModule,
|
|
2332
|
-
SplitterModule,
|
|
2333
2477
|
TooltipModule,
|
|
2334
2478
|
DCLessonComponentAdderComponent, // Add the component adder here
|
|
2335
2479
|
DialogModule,
|
|
@@ -2337,7 +2481,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImpor
|
|
|
2337
2481
|
DCLessonMetadataEditorComponent,
|
|
2338
2482
|
DcManageableFormComponent,
|
|
2339
2483
|
DcLearnableFormComponent,
|
|
2340
|
-
|
|
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"] }]
|
|
2341
2487
|
}] });
|
|
2342
2488
|
|
|
2343
2489
|
class LessonsV2Component {
|
|
@@ -2593,7 +2739,7 @@ class CourseDetailComponent {
|
|
|
2593
2739
|
this.course.set(course);
|
|
2594
2740
|
}
|
|
2595
2741
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: CourseDetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2596
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.6", type: CourseDetailComponent, isStandalone: true, selector: "app-course-detail", providers: [MessageService], ngImport: i0, template: "@if (course(); as course) {\n<div class=\"course-detail-container p-4\">\n <p-card>\n <ng-template pTemplate=\"title\">\n <div class=\"flex justify-content-between align-items-center\">\n <span>{{ course.name }}</span>\n </div>\n </ng-template>\n <ng-template pTemplate=\"subtitle\">\n <div class=\"flex align-items-center gap-2\">\n <i class=\"pi pi-book\"></i>\n <span>{{ course.moduleCount }} Modules / {{ course.totalLessons }} Lessons</span>\n </div>\n </ng-template>\n <ng-template pTemplate=\"content\">\n <p>{{ course.description }}</p>\n\n <div class=\"grid mt-3\">\n @for (module of course.modules; track module.id) {\n <div class=\"col-12 md:col-6\">\n <p-panel [toggleable]=\"true\">\n <ng-template pTemplate=\"header\">\n <div class=\"flex align-items-center gap-2 w-full\">\n <i class=\"pi pi-list\"></i>\n <span class=\"font-bold white-space-nowrap\">{{ module.title }} {{ module.name }}</span>\n <span class=\"text-sm text-color-secondary ml-auto\">{{ module.lessonCount }} lessons</span>\n </div>\n </ng-template>\n <ng-template pTemplate=\"content\">\n <p>{{ module.description }}</p>\n <ul class=\"list-none p-0 m-0 lessons-list\">\n @for (lesson of module.lessons; track lesson.id) {\n <li class=\"flex align-items-center justify-content-between p-2 border-bottom-1 surface-border\">\n <div class=\"flex align-items-center gap-2\">\n @if(lesson.generated) {\n <i class=\"pi pi-play-circle text-green-500\"></i>\n } @else {\n <i class=\"pi pi-lock text-gray-500\"></i>\n }\n <span>{{ lesson.title }} {{ lesson.name }}</span>\n </div>\n @if(lesson.generated) {\n <p-button icon=\"pi pi-chevron-right\" [text]=\"true\" [rounded]=\"true\"></p-button>\n } @else {\n <p-button label=\"Generate\" icon=\"pi pi-cog\" [text]=\"true\" size=\"small\"></p-button>\n }\n </li>\n }\n </ul>\n </ng-template>\n </p-panel>\n </div>\n }\n </div>\n </ng-template>\n <ng-template pTemplate=\"footer\">\n <div class=\"flex justify-content-end\">\n <p-button label=\"Generate Pending Lessons\" icon=\"pi pi-cogs\" (onClick)=\"generatePendingLessons()\" [loading]=\"generatingLessons()\"></p-button>\n </div>\n </ng-template>\n </p-card>\n</div>\n} @else {\n<div class=\"flex justify-content-center align-items-center h-full\">\n <p-progressSpinner></p-progressSpinner>\n</div>\n}\n", styles: [":host{display:block;height:100%}.course-detail-container{max-width:960px;margin:auto}.lessons-list li:last-child{border-bottom:none!important}.modules-container{display:flex;flex-direction:row;flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;padding-bottom:1rem}.modules-container .p-panel{flex:0 0 48%;max-width:48%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i1$2.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "directive", type:
|
|
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 }); }
|
|
2597
2743
|
}
|
|
2598
2744
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: CourseDetailComponent, decorators: [{
|
|
2599
2745
|
type: Component,
|
|
@@ -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$
|
|
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,
|
|
@@ -2706,7 +2852,7 @@ const COURSES_ROUTES = [
|
|
|
2706
2852
|
class CoursesComponent {
|
|
2707
2853
|
static { this.routes = COURSES_ROUTES; }
|
|
2708
2854
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: CoursesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2709
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.1.6", type: CoursesComponent, isStandalone: true, selector: "app-courses", ngImport: i0, template: "<router-outlet />\n", styles: [":host{display:block;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$
|
|
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 }); }
|
|
2710
2856
|
}
|
|
2711
2857
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImport: i0, type: CoursesComponent, decorators: [{
|
|
2712
2858
|
type: Component,
|
|
@@ -2721,5 +2867,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.6", ngImpor
|
|
|
2721
2867
|
* Generated bundle index. Do not edit.
|
|
2722
2868
|
*/
|
|
2723
2869
|
|
|
2724
|
-
export { ComponentBuilder, ComponentWithForm, CourseDetailComponent, CourseFormComponent, CourseListComponent, CourseService, CoursesAdminComponent, CoursesComponent, CoursesService, DCLessonEditorComponent, DCLessonFormComponent, DCLessonListComponent, DCLessonRendererComponent, DcLessonCardComponent,
|
|
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 };
|
|
2725
2871
|
//# sourceMappingURL=dataclouder-ngx-lessons.mjs.map
|