@dataclouder/ngx-lessons 0.0.33 → 0.0.35
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 +534 -169
- package/fesm2022/dataclouder-ngx-lessons.mjs.map +1 -1
- package/lib/components/dc-lessons/dc-lesson-card/dc-lesson-card.component.d.ts +2 -1
- package/lib/components/dc-lessons/dc-lesson-editor/dc-lesson-editor.component.d.ts +9 -7
- package/lib/components/dc-lessons/dc-lesson-metadata-editor/dc-lesson-metadata-editor.component.d.ts +22 -9
- package/lib/components/dc-lessons/dc-lesson-renderer/dc-lesson-renderer.component.d.ts +5 -1
- package/lib/components/dc-lessons/dc-lesson-renderer/eval-agents-skills.d.ts +1 -0
- package/lib/components/lesson-mini-components/components/lessons.clases.d.ts +26 -0
- package/lib/models/prompts.d.ts +6 -0
- package/lib/models/simple-agents.d.ts +1 -1
- package/lib/services/default-lessons.service.d.ts +20 -0
- package/lib/services/lesson-utils.service.d.ts +15 -2
- package/package.json +1 -1
- package/public-api.d.ts +1 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Component, Input, ChangeDetectionStrategy, signal, InjectionToken, Pipe, EventEmitter, Output, ViewChildren, Inject, inject, Injectable, input, Renderer2, ViewContainerRef, computed, effect, ViewChild } from '@angular/core';
|
|
2
|
+
import { Component, Input, ChangeDetectionStrategy, signal, InjectionToken, Pipe, EventEmitter, Output, ViewChildren, Inject, inject, Injectable, input, Renderer2, ViewContainerRef, computed, effect, ViewChild, ChangeDetectorRef } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/forms';
|
|
4
4
|
import { FormControl, UntypedFormControl, FormsModule, ReactiveFormsModule, Validators, FormGroup } from '@angular/forms';
|
|
5
5
|
import * as i4 from 'primeng/inputtext';
|
|
@@ -20,9 +20,11 @@ import { PaginatorModule } from 'primeng/paginator';
|
|
|
20
20
|
import { DatePipe, NgComponentOutlet, KeyValuePipe, CommonModule } from '@angular/common';
|
|
21
21
|
import * as i1$2 from '@angular/router';
|
|
22
22
|
import { RouterModule, ActivatedRoute, Router } from '@angular/router';
|
|
23
|
-
import * as i4$
|
|
24
|
-
import { PaginationBase, TOAST_ALERTS_TOKEN, DCFilterBarComponent, QuickTableComponent, LoadingBarService } from '@dataclouder/ngx-core';
|
|
23
|
+
import * as i4$2 from '@dataclouder/ngx-core';
|
|
24
|
+
import { PaginationBase, TOAST_ALERTS_TOKEN, DCFilterBarComponent, QuickTableComponent, LoadingBarService, HttpCoreService, PromptService } from '@dataclouder/ngx-core';
|
|
25
25
|
import { PopoverModule } from 'primeng/popover';
|
|
26
|
+
import * as i4$1 from 'primeng/tag';
|
|
27
|
+
import { TagModule } from 'primeng/tag';
|
|
26
28
|
import * as i2$2 from 'primeng/speeddial';
|
|
27
29
|
import { SpeedDialModule } from 'primeng/speeddial';
|
|
28
30
|
import * as i3 from 'primeng/card';
|
|
@@ -32,16 +34,21 @@ import { map } from 'rxjs';
|
|
|
32
34
|
import BalloonEditor from '@ckeditor/ckeditor5-build-balloon-block';
|
|
33
35
|
import * as i3$1 from '@ckeditor/ckeditor5-angular';
|
|
34
36
|
import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
|
|
35
|
-
import * as i5$
|
|
37
|
+
import * as i5$2 from 'primeng/splitter';
|
|
36
38
|
import { SplitterModule } from 'primeng/splitter';
|
|
37
39
|
import * as i2$5 from 'primeng/tooltip';
|
|
38
40
|
import { TooltipModule } from 'primeng/tooltip';
|
|
39
41
|
import { ResolutionType, AspectType, CropperComponentModal } from '@dataclouder/ngx-cloud-storage';
|
|
40
|
-
import { TextEngines, ConversationType, USER_DATA_EXCHANGE, ChatRole, DCChatComponent, CONVERSATION_AI_TOKEN } from '@dataclouder/ngx-agent-cards';
|
|
42
|
+
import { TextEngines, ConversationType, USER_DATA_EXCHANGE, ChatRole, EvalResultStringDefinition, DCChatComponent, CONVERSATION_AI_TOKEN } from '@dataclouder/ngx-agent-cards';
|
|
41
43
|
import * as i2$4 from 'primeng/drawer';
|
|
42
44
|
import { DrawerModule } from 'primeng/drawer';
|
|
45
|
+
import * as i7 from 'primeng/dialog';
|
|
46
|
+
import { DialogModule } from 'primeng/dialog';
|
|
43
47
|
import TurndownService from 'turndown';
|
|
44
48
|
import { marked } from 'marked';
|
|
49
|
+
import * as i5$1 from 'primeng/inputgroup';
|
|
50
|
+
import { InputGroupModule } from 'primeng/inputgroup';
|
|
51
|
+
import { NgxVertexService, ChatRoleVertex } from '@dataclouder/ngx-vertex';
|
|
45
52
|
import * as i2$6 from 'primeng/api';
|
|
46
53
|
|
|
47
54
|
class ComponentBuilder {
|
|
@@ -420,7 +427,6 @@ function getLessonComponentClass(type) {
|
|
|
420
427
|
// import { UntypedFormControl } from '@angular/forms';
|
|
421
428
|
// import { LessonComponents } from '../models/lessons.class';
|
|
422
429
|
const LESSONS_TOKEN = new InjectionToken('Lessons Service');
|
|
423
|
-
// Think on someway of getting the toast alerts service
|
|
424
430
|
class LessonsAbstractService {
|
|
425
431
|
}
|
|
426
432
|
// my-service.provider.ts
|
|
@@ -570,23 +576,26 @@ You incorporate visual dividers to separate major content sections
|
|
|
570
576
|
|
|
571
577
|
When responding to requests, you'll first understand the subject matter, then organize it into a logical structure with clear headings, appropriate formatting elements, and helpful visual enhancements. Your goal is always to create content that's not only informative but also visually engaging and accessible to all readers.
|
|
572
578
|
`;
|
|
573
|
-
const
|
|
579
|
+
const UserRequirements = `
|
|
580
|
+
User also can provide additional instructions or requirements for the lesson or current lesson to improve it.
|
|
581
|
+
`;
|
|
582
|
+
const LanguageLessonSkill = (langBase, langTarget) => `
|
|
574
583
|
|
|
575
|
-
You are also
|
|
576
|
-
explain the best you can will all your experience teaching.
|
|
584
|
+
You are also a ${langTarget} lesson professor, you write lessons for ${langBase} learners.
|
|
585
|
+
explain the best you can will all your experience teaching.
|
|
577
586
|
|
|
578
587
|
You can infer the level of the lesson from the content or user request, so can increase difficulty
|
|
579
588
|
|
|
580
|
-
For Basic Level: generate
|
|
589
|
+
For Basic Level: generate a ${langTarget} lesson for ${langBase} learners at a basic level (A1-A2).
|
|
581
590
|
|
|
582
591
|
Include:
|
|
583
592
|
|
|
584
593
|
- Simple dialogues or short texts.
|
|
585
|
-
- Key vocabulary with simple definitions or
|
|
594
|
+
- Key vocabulary with simple definitions or ${langBase} translations.
|
|
586
595
|
- Basic grammar points explained simply (e.g., "to be" verb, simple present).
|
|
587
596
|
- Practice exercises (e.g., fill-in-the-blanks, simple questions).
|
|
588
597
|
|
|
589
|
-
For Intermediate Level: generate
|
|
598
|
+
For Intermediate Level: generate a ${langTarget} lesson for ${langBase} learners at a medium level (B1-B2).
|
|
590
599
|
|
|
591
600
|
Include:
|
|
592
601
|
|
|
@@ -596,7 +605,7 @@ Include:
|
|
|
596
605
|
- Exercises that require more detailed responses or understanding.
|
|
597
606
|
- The content should be engaging and cover topics relevant to daily life, work, or hobbies.
|
|
598
607
|
|
|
599
|
-
For Advanced Level: generate
|
|
608
|
+
For Advanced Level: generate a ${langTarget} lesson for ${langBase} learners at an advanced level (C1-C2).
|
|
600
609
|
|
|
601
610
|
Include:
|
|
602
611
|
|
|
@@ -605,19 +614,17 @@ Include:
|
|
|
605
614
|
- Exploration of complex grammar or stylistic points.
|
|
606
615
|
- Exercises that encourage critical thinking, debate, or detailed writing.
|
|
607
616
|
- The content should be stimulating and cover a wide range of topics, including academic or professional contexts.
|
|
608
|
-
|
|
609
|
-
`;
|
|
610
|
-
const UserRequirements = `
|
|
611
|
-
User also can provide additional instructions or requirements for the lesson or current lesson to improve it.
|
|
612
617
|
`;
|
|
613
|
-
const
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
618
|
+
const getLanguageSimpleAgent = (langBase, langTarget) => {
|
|
619
|
+
return {
|
|
620
|
+
systemPrompt: `
|
|
621
|
+
${MarkdownWriterSkill}
|
|
622
|
+
${LanguageLessonSkill(langBase, langTarget)}
|
|
623
|
+
${UserRequirements}
|
|
624
|
+
|
|
625
|
+
`,
|
|
626
|
+
model: { id: 'gemini-2.5-flash-preview-04-17', provider: 'google' },
|
|
627
|
+
};
|
|
621
628
|
};
|
|
622
629
|
|
|
623
630
|
var EventCard;
|
|
@@ -630,6 +637,7 @@ var EventCard;
|
|
|
630
637
|
class DcLessonCardComponent {
|
|
631
638
|
constructor() {
|
|
632
639
|
this.showOptions = true;
|
|
640
|
+
this.cardHeight = '200px';
|
|
633
641
|
this.onAction = new EventEmitter();
|
|
634
642
|
this.coverUrl = 'assets/background/default-background.webp';
|
|
635
643
|
this.eventType = EventCard;
|
|
@@ -658,9 +666,8 @@ class DcLessonCardComponent {
|
|
|
658
666
|
];
|
|
659
667
|
}
|
|
660
668
|
ngOnInit() {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
}
|
|
669
|
+
console.log(this.lesson);
|
|
670
|
+
this.coverUrl = this.lesson?.banner?.url || this.lesson?.media?.images?.[0]?.url || 'assets/background/default-background.webp';
|
|
664
671
|
}
|
|
665
672
|
eventCard(eventType) {
|
|
666
673
|
switch (eventType) {
|
|
@@ -679,15 +686,17 @@ class DcLessonCardComponent {
|
|
|
679
686
|
}
|
|
680
687
|
}
|
|
681
688
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DcLessonCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
682
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: DcLessonCardComponent, isStandalone: true, selector: "dc-lesson-card", inputs: { lesson: "lesson", showOptions: "showOptions" }, outputs: { onAction: "onAction" }, ngImport: i0, template: "<div class=\"card-container\">\n @if(showOptions){\n
|
|
689
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", 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 }\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.createdDate | date : 'dd/MM/yyyy' }}</span>\n\n <div class=\"description\">\n <h1>{{ lesson.title }}</h1>\n <p>{{ lesson.description }}</p>\n <div class=\"card-footer\">\n <div class=\"status-tags\">\n <span class=\"level-tag\">Nivel {{ lesson.level }}</span>\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 }\n </div>\n\n <div style=\"position: absolute; bottom: 0px; right: 0px\">\n <p-button label=\"Tomar lecci\u00F3n\" (onClick)=\"eventCard(eventType.Select)\" severity=\"primary\"> </p-button>\n </div>\n </div>\n </div>\n </div>\n </p-card>\n</div>\n", styles: [".card-container{position:relative;margin-bottom:20px;margin-left:10px}.dial-button{position:absolute;top:10px;right:20px;z-index:10}.lesson-card{border-radius:.5rem;height:100%}.lesson-card .photo{position:absolute;inset:0;z-index:1}.lesson-card .photo img{width:100%;height:100%;object-fit:cover;object-position:center}.lesson-card .photo:after{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;background:linear-gradient(to bottom,rgb(0,0,0) 50%,var(--p-primary-color) 100%);opacity:.4;z-index:2;pointer-events:none}.description{position:relative;z-index:2;color:#fff;height:100%;display:flex;flex-direction:column}.description h1{margin-top:0;margin-bottom:.5rem;font-size:1.5rem;font-weight:900}.description p{margin-bottom:1rem;flex-grow:1;font-weight:600}.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: "pipe", type: DatePipe, name: "date" }, { 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", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "ngmodule", type: SpeedDialModule }, { kind: "component", type: i2$2.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: i3.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i4$1.Tag, selector: "p-tag", inputs: ["style", "styleClass", "severity", "value", "icon", "rounded"] }] }); }
|
|
683
690
|
}
|
|
684
691
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DcLessonCardComponent, decorators: [{
|
|
685
692
|
type: Component,
|
|
686
|
-
args: [{ selector: 'dc-lesson-card', standalone: true, imports: [DatePipe, ButtonModule, PopoverModule, SpeedDialModule, CardModule], template: "<div class=\"card-container\">\n @if(showOptions){\n
|
|
693
|
+
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 }\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.createdDate | date : 'dd/MM/yyyy' }}</span>\n\n <div class=\"description\">\n <h1>{{ lesson.title }}</h1>\n <p>{{ lesson.description }}</p>\n <div class=\"card-footer\">\n <div class=\"status-tags\">\n <span class=\"level-tag\">Nivel {{ lesson.level }}</span>\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 }\n </div>\n\n <div style=\"position: absolute; bottom: 0px; right: 0px\">\n <p-button label=\"Tomar lecci\u00F3n\" (onClick)=\"eventCard(eventType.Select)\" severity=\"primary\"> </p-button>\n </div>\n </div>\n </div>\n </div>\n </p-card>\n</div>\n", styles: [".card-container{position:relative;margin-bottom:20px;margin-left:10px}.dial-button{position:absolute;top:10px;right:20px;z-index:10}.lesson-card{border-radius:.5rem;height:100%}.lesson-card .photo{position:absolute;inset:0;z-index:1}.lesson-card .photo img{width:100%;height:100%;object-fit:cover;object-position:center}.lesson-card .photo:after{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;background:linear-gradient(to bottom,rgb(0,0,0) 50%,var(--p-primary-color) 100%);opacity:.4;z-index:2;pointer-events:none}.description{position:relative;z-index:2;color:#fff;height:100%;display:flex;flex-direction:column}.description h1{margin-top:0;margin-bottom:.5rem;font-size:1.5rem;font-weight:900}.description p{margin-bottom:1rem;flex-grow:1;font-weight:600}.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"] }]
|
|
687
694
|
}], propDecorators: { lesson: [{
|
|
688
695
|
type: Input
|
|
689
696
|
}], showOptions: [{
|
|
690
697
|
type: Input
|
|
698
|
+
}], cardHeight: [{
|
|
699
|
+
type: Input
|
|
691
700
|
}], onAction: [{
|
|
692
701
|
type: Output
|
|
693
702
|
}] } });
|
|
@@ -803,15 +812,15 @@ class DCLessonListComponent extends PaginationBase {
|
|
|
803
812
|
}
|
|
804
813
|
}
|
|
805
814
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonListComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i1$2.Router }, { token: i1$2.ActivatedRoute }, { token: LESSONS_TOKEN }, { token: TOAST_ALERTS_TOKEN }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
806
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: DCLessonListComponent, isStandalone: true, selector: "dc-lesson-list", inputs: { showOptions: "showOptions", customCardComponent: "customCardComponent", customFilters: "customFilters", viewType: "viewType" }, viewQueries: [{ propertyName: "outlets", predicate: ["outlet"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<dc-filter-bar [customFilters]=\"customFilters\" (onFilterAction)=\"applyFilterBarEvent($event)\" (onNew)=\"newLesson()\"></dc-filter-bar>\n\n@if(viewType === 'table') {\n<app-quick-table [columns]=\"columns\" [tableData]=\"lessons\" (onAction)=\"doOrEmitAction($event)\"></app-quick-table>\n} @else {\n<div class=\"lesson-list-container\">\n @if (!isLoadingLessons && lessons?.length > 0) { @for (lesson of lessons; track lesson._id) {\n <ng-container\n #outlet=\"ngComponentOutlet\"\n [ngComponentOutlet]=\"cardComponent\"\n [ngComponentOutletInputs]=\"{\n lesson: lesson,\n showOptions: showOptions\n }\">\n </ng-container>\n } } @else {\n <p>No se encontraron lecciones disponibles</p>\n }\n</div>\n}\n\n<p-paginator\n currentPageReportTemplate=\"{{ totalRecords }} lecciones\"\n [showCurrentPageReport]=\"true\"\n (onPageChange)=\"onPageChange($event)\"\n [first]=\"paginatorFirst\"\n [rows]=\"paginatorRows\"\n [totalRecords]=\"totalRecords\"\n [rowsPerPageOptions]=\"[10, 20, 30]\">\n</p-paginator>\n", styles: [":host{display:flex;flex-direction:column;height:100%}.lesson-list-container{padding:
|
|
815
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: DCLessonListComponent, isStandalone: true, selector: "dc-lesson-list", inputs: { showOptions: "showOptions", customCardComponent: "customCardComponent", customFilters: "customFilters", viewType: "viewType" }, viewQueries: [{ propertyName: "outlets", predicate: ["outlet"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<dc-filter-bar [customFilters]=\"customFilters\" (onFilterAction)=\"applyFilterBarEvent($event)\" (onNew)=\"newLesson()\"></dc-filter-bar>\n\n@if(viewType === 'table') {\n<app-quick-table [columns]=\"columns\" [tableData]=\"lessons\" (onAction)=\"doOrEmitAction($event)\"></app-quick-table>\n} @else {\n<div class=\"lesson-list-container\">\n @if (!isLoadingLessons && lessons?.length > 0) { @for (lesson of lessons; track lesson._id) {\n <ng-container\n #outlet=\"ngComponentOutlet\"\n [ngComponentOutlet]=\"cardComponent\"\n [ngComponentOutletInputs]=\"{\n lesson: lesson,\n showOptions: showOptions\n }\">\n </ng-container>\n } } @else {\n <p>No se encontraron lecciones disponibles</p>\n }\n</div>\n}\n\n<p-paginator\n currentPageReportTemplate=\"{{ totalRecords }} lecciones\"\n [showCurrentPageReport]=\"true\"\n (onPageChange)=\"onPageChange($event)\"\n [first]=\"paginatorFirst\"\n [rows]=\"paginatorRows\"\n [totalRecords]=\"totalRecords\"\n [rowsPerPageOptions]=\"[10, 20, 30]\">\n</p-paginator>\n", styles: [":host{display:flex;flex-direction:column;height:100%}.lesson-list-container{padding:1.5rem;flex:1;overflow-y:auto;min-height:0}@media (max-width: 768px){.lesson-list-container{margin-top:1rem;padding:0rem}}p-paginator{margin-top:auto;padding:.5rem 1rem}\n"], dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "component", type: DCFilterBarComponent, selector: "dc-filter-bar", inputs: ["isAdmin", "items", "customFilters"], outputs: ["onFilterAction", "onChangeSort", "onNew"] }, { kind: "ngmodule", type: PaginatorModule }, { kind: "component", type: i2$3.Paginator, selector: "p-paginator", inputs: ["pageLinkSize", "style", "styleClass", "alwaysShow", "dropdownAppendTo", "templateLeft", "templateRight", "appendTo", "dropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showFirstLastIcon", "totalRecords", "rows", "rowsPerPageOptions", "showJumpToPageDropdown", "showJumpToPageInput", "jumpToPageItemTemplate", "showPageLinks", "locale", "dropdownItemTemplate", "first"], outputs: ["onPageChange"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: QuickTableComponent, selector: "app-quick-table", inputs: ["columns", "tableData", "actions"], outputs: ["onAction"] }] }); }
|
|
807
816
|
}
|
|
808
817
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonListComponent, decorators: [{
|
|
809
818
|
type: Component,
|
|
810
|
-
args: [{ selector: 'dc-lesson-list', standalone: true, imports: [RouterModule, DCFilterBarComponent, PaginatorModule, NgComponentOutlet, QuickTableComponent], template: "<dc-filter-bar [customFilters]=\"customFilters\" (onFilterAction)=\"applyFilterBarEvent($event)\" (onNew)=\"newLesson()\"></dc-filter-bar>\n\n@if(viewType === 'table') {\n<app-quick-table [columns]=\"columns\" [tableData]=\"lessons\" (onAction)=\"doOrEmitAction($event)\"></app-quick-table>\n} @else {\n<div class=\"lesson-list-container\">\n @if (!isLoadingLessons && lessons?.length > 0) { @for (lesson of lessons; track lesson._id) {\n <ng-container\n #outlet=\"ngComponentOutlet\"\n [ngComponentOutlet]=\"cardComponent\"\n [ngComponentOutletInputs]=\"{\n lesson: lesson,\n showOptions: showOptions\n }\">\n </ng-container>\n } } @else {\n <p>No se encontraron lecciones disponibles</p>\n }\n</div>\n}\n\n<p-paginator\n currentPageReportTemplate=\"{{ totalRecords }} lecciones\"\n [showCurrentPageReport]=\"true\"\n (onPageChange)=\"onPageChange($event)\"\n [first]=\"paginatorFirst\"\n [rows]=\"paginatorRows\"\n [totalRecords]=\"totalRecords\"\n [rowsPerPageOptions]=\"[10, 20, 30]\">\n</p-paginator>\n", styles: [":host{display:flex;flex-direction:column;height:100%}.lesson-list-container{padding:
|
|
819
|
+
args: [{ selector: 'dc-lesson-list', standalone: true, imports: [RouterModule, DCFilterBarComponent, PaginatorModule, NgComponentOutlet, QuickTableComponent], template: "<dc-filter-bar [customFilters]=\"customFilters\" (onFilterAction)=\"applyFilterBarEvent($event)\" (onNew)=\"newLesson()\"></dc-filter-bar>\n\n@if(viewType === 'table') {\n<app-quick-table [columns]=\"columns\" [tableData]=\"lessons\" (onAction)=\"doOrEmitAction($event)\"></app-quick-table>\n} @else {\n<div class=\"lesson-list-container\">\n @if (!isLoadingLessons && lessons?.length > 0) { @for (lesson of lessons; track lesson._id) {\n <ng-container\n #outlet=\"ngComponentOutlet\"\n [ngComponentOutlet]=\"cardComponent\"\n [ngComponentOutletInputs]=\"{\n lesson: lesson,\n showOptions: showOptions\n }\">\n </ng-container>\n } } @else {\n <p>No se encontraron lecciones disponibles</p>\n }\n</div>\n}\n\n<p-paginator\n currentPageReportTemplate=\"{{ totalRecords }} lecciones\"\n [showCurrentPageReport]=\"true\"\n (onPageChange)=\"onPageChange($event)\"\n [first]=\"paginatorFirst\"\n [rows]=\"paginatorRows\"\n [totalRecords]=\"totalRecords\"\n [rowsPerPageOptions]=\"[10, 20, 30]\">\n</p-paginator>\n", styles: [":host{display:flex;flex-direction:column;height:100%}.lesson-list-container{padding:1.5rem;flex:1;overflow-y:auto;min-height:0}@media (max-width: 768px){.lesson-list-container{margin-top:1rem;padding:0rem}}p-paginator{margin-top:auto;padding:.5rem 1rem}\n"] }]
|
|
811
820
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i1$2.Router }, { type: i1$2.ActivatedRoute }, { type: LessonsAbstractService, decorators: [{
|
|
812
821
|
type: Inject,
|
|
813
822
|
args: [LESSONS_TOKEN]
|
|
814
|
-
}] }, { type: i4$
|
|
823
|
+
}] }, { type: i4$2.ToastAlertsAbstractService, decorators: [{
|
|
815
824
|
type: Inject,
|
|
816
825
|
args: [TOAST_ALERTS_TOKEN]
|
|
817
826
|
}] }], propDecorators: { showOptions: [{
|
|
@@ -959,6 +968,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
|
|
|
959
968
|
}]
|
|
960
969
|
}] });
|
|
961
970
|
|
|
971
|
+
const EnglishEvaluationSkill = `
|
|
972
|
+
You are a virtual professor for the Polilan English learning app. Your primary role is to evaluate a student's English conversational performance.
|
|
973
|
+
|
|
974
|
+
You will be provided with two pieces of information for each evaluation:
|
|
975
|
+
1. **The student's English level:** This will be one of "Beginner", "Intermediate", or "Advanced".
|
|
976
|
+
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: ...").
|
|
977
|
+
|
|
978
|
+
Your sole task is to evaluate the *Student's* contribution to the conversation. **Do NOT evaluate the Assistant's turns.**
|
|
979
|
+
|
|
980
|
+
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:
|
|
981
|
+
|
|
982
|
+
* **Grammar & Syntax:** Evaluate accuracy and complexity *relative to the expected level*. (Beginners: focus on basic accuracy; Advanced: expect complex structures with high accuracy).
|
|
983
|
+
* **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).
|
|
984
|
+
* **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).
|
|
985
|
+
* **Task Completion/Relevance:** Evaluate how effectively they participate and respond to the conversation's goals or topics *relative to the expected level*.
|
|
986
|
+
|
|
987
|
+
Assign a rating from 0 to 3 based on how well the student performs *compared to the typical expectations for their provided level*:
|
|
988
|
+
|
|
989
|
+
* **0: Bad** - Performance is significantly below what is expected for this level. Many errors impede communication.
|
|
990
|
+
* **1: Not Bad** - Performance is below expectations for this level, but communication is sometimes possible despite frequent errors.
|
|
991
|
+
* **2: Good** - Performance meets expectations for this level, demonstrating solid understanding and usage with only occasional minor errors.
|
|
992
|
+
* **3: Very Good** - Performance exceeds expectations for this level, demonstrating a strong command and potential to progress quickly.
|
|
993
|
+
|
|
994
|
+
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.
|
|
995
|
+
`;
|
|
996
|
+
|
|
962
997
|
class DCLessonRendererComponent {
|
|
963
998
|
constructor() {
|
|
964
999
|
// --- Signal Inputs ---
|
|
@@ -971,12 +1006,14 @@ class DCLessonRendererComponent {
|
|
|
971
1006
|
this.toastrService = inject(TOAST_ALERTS_TOKEN);
|
|
972
1007
|
this.lessonService = inject(LESSONS_TOKEN);
|
|
973
1008
|
this.lessonAIService = inject(LessonAIService); // Inject the new service
|
|
1009
|
+
this.userDataExchange = inject(USER_DATA_EXCHANGE);
|
|
974
1010
|
// --- State Signals ---
|
|
975
1011
|
this.lesson = signal(undefined); // Internal lesson state signal
|
|
976
1012
|
this.chatVisible = signal(false); // Signal for chat visibility
|
|
977
1013
|
this.agentMasterLesson = signal(undefined); // Signal for agent card
|
|
978
1014
|
this.evaluatorAgentCard = signal(undefined); // Signal for evaluator card
|
|
979
1015
|
this.conversationSettings = signal(undefined);
|
|
1016
|
+
this.evalAgentTask = signal(undefined); // Signal for evaluator card
|
|
980
1017
|
// --- Computed Signals ---
|
|
981
1018
|
this.imageCover = computed(() => this.lesson()?.media?.images?.find((img) => img.type === 'cover')?.url); // Computed signal for imageCover
|
|
982
1019
|
// --- Properties ---
|
|
@@ -1017,6 +1054,12 @@ class DCLessonRendererComponent {
|
|
|
1017
1054
|
effect(() => {
|
|
1018
1055
|
const currentLesson = this.lesson(); // Read the lesson signal
|
|
1019
1056
|
const newTextCoded = currentLesson?.textCoded; // Get the current textCoded value
|
|
1057
|
+
this.evalAgentTask.set({
|
|
1058
|
+
systemPrompt: EnglishEvaluationSkill,
|
|
1059
|
+
model: { provider: 'google' },
|
|
1060
|
+
task: `Please evaluate the user conversation student data is: \n ${this.userDataExchange.getUserDataInformation()}`,
|
|
1061
|
+
expectedResponseType: EvalResultStringDefinition,
|
|
1062
|
+
});
|
|
1020
1063
|
// Quick fix to only render on changes textCode not all the lesson
|
|
1021
1064
|
if (newTextCoded !== this.previousTextCoded) {
|
|
1022
1065
|
if (newTextCoded !== undefined && newTextCoded !== null && currentLesson) {
|
|
@@ -1190,7 +1233,7 @@ class DCLessonRendererComponent {
|
|
|
1190
1233
|
this.toastrService.error({ subtitle: 'Cannot save result, lesson data missing.', title: 'Error' });
|
|
1191
1234
|
return;
|
|
1192
1235
|
}
|
|
1193
|
-
const takenLesson = {
|
|
1236
|
+
const takenLesson = { id: currentLesson.id, goalCompleted: null, score: rates.score, status: status, lastAccess: new Date() };
|
|
1194
1237
|
console.log('Lesson evaluation result:', takenLesson);
|
|
1195
1238
|
// TODO: Re-implement saving the taken lesson status via lessonService
|
|
1196
1239
|
// try {
|
|
@@ -1224,8 +1267,6 @@ class DCLessonRendererComponent {
|
|
|
1224
1267
|
// Call the service to get the agent cards
|
|
1225
1268
|
const conversationSettings = await this.lessonAIService.generateConversationSettingsForLesson(currentLesson);
|
|
1226
1269
|
if (conversationSettings) {
|
|
1227
|
-
// this.agentMasterLesson.set(agentCards);
|
|
1228
|
-
// this.evaluatorAgentCard.set(agentCards.evaluatorAgent);
|
|
1229
1270
|
this.conversationSettings.set(conversationSettings);
|
|
1230
1271
|
this.chatVisible.set(true);
|
|
1231
1272
|
console.log('Agent cards received and set.');
|
|
@@ -1240,12 +1281,24 @@ class DCLessonRendererComponent {
|
|
|
1240
1281
|
this.toastrService.error({ subtitle: 'An error occurred while preparing the AI chat.', title: 'Error' });
|
|
1241
1282
|
}
|
|
1242
1283
|
}
|
|
1284
|
+
onVisibleChange(isVisible) {
|
|
1285
|
+
if (isVisible === false) {
|
|
1286
|
+
this.chatVisible.set(false);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
handleGoalCompleted(event) {
|
|
1290
|
+
console.log('Goal completed:', event);
|
|
1291
|
+
const lesson = this.lesson();
|
|
1292
|
+
const dummy = { id: lesson.id, goalCompleted: true, score: 100, status: 'finished', lastAccess: new Date() };
|
|
1293
|
+
this.lessonService.saveTakenLesson(dummy);
|
|
1294
|
+
this.toastrService.success({ subtitle: '¡Has completado la lección! , pero puedes seguir conversando', title: '¡Muy bien, guardaremos tu progreso!' });
|
|
1295
|
+
}
|
|
1243
1296
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1244
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", 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 }, test: { classPropertyName: "test", publicName: "test", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "dynamicLesson", first: true, predicate: ["dynamicLesson"], descendants: true, static: true }], ngImport: i0, template: "<div>\n <div #dynamicLesson class=\"targetclass\">\n <ng-template #target></ng-template>\n </div>\n</div>\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(chatVisible()) {\n<p-drawer header=\"Conversation\" [visible]=\"chatVisible()\" position=\"bottom\" styleClass=\"app-bottom-overlay\">\n <dc-chat [conversationSettings]=\"conversationSettings()\"></dc-chat>\n</p-drawer>\n}\n", styles: [".evaluate{float:right}\n"], dependencies: [{ kind: "pipe", type: KeyValuePipe, name: "keyvalue" }, { 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", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: DCChatComponent, selector: "dc-chat", inputs: ["chatUserSettings", "conversationSettings", "agentCard", "evaluatorAgentCard", "parseDict"], outputs: ["sendMessage", "goalCompleted"] }, { kind: "ngmodule", type: DrawerModule }, { kind: "component", type: i2$4.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"] }] }); }
|
|
1297
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", 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 }, test: { classPropertyName: "test", publicName: "test", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "dynamicLesson", first: true, predicate: ["dynamicLesson"], descendants: true, static: true }], ngImport: i0, template: "<div>\n <div #dynamicLesson class=\"targetclass\">\n <ng-template #target></ng-template>\n </div>\n</div>\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(chatVisible()) {\n<p-drawer header=\"Conversation\" [visible]=\"chatVisible()\" (visibleChange)=\"onVisibleChange($event)\" position=\"bottom\" styleClass=\"app-bottom-overlay\">\n <dc-chat [appAgentTask]=\"evalAgentTask()\" [conversationSettings]=\"conversationSettings()\" (goalCompleted)=\"handleGoalCompleted($event)\"></dc-chat>\n</p-drawer>\n}\n", styles: [".evaluate{float:right}\n"], dependencies: [{ kind: "pipe", type: KeyValuePipe, name: "keyvalue" }, { 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", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: DCChatComponent, selector: "dc-chat", inputs: ["chatUserSettings", "conversationSettings", "agentCard", "evaluatorAgentCard", "appAgentTask", "parseDict"], outputs: ["sendMessage", "goalCompleted"] }, { kind: "ngmodule", type: DrawerModule }, { kind: "component", type: i2$4.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"] }] }); }
|
|
1245
1298
|
}
|
|
1246
1299
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonRendererComponent, decorators: [{
|
|
1247
1300
|
type: Component,
|
|
1248
|
-
args: [{ selector: 'dc-lesson-renderer', standalone: true, imports: [KeyValuePipe, ButtonModule, DCChatComponent, DrawerModule], template: "<div>\n <div #dynamicLesson class=\"targetclass\">\n <ng-template #target></ng-template>\n </div>\n</div>\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(chatVisible()) {\n<p-drawer header=\"Conversation\" [visible]=\"chatVisible()\" position=\"bottom\" styleClass=\"app-bottom-overlay\">\n <dc-chat [conversationSettings]=\"conversationSettings()\"></dc-chat>\n</p-drawer>\n}\n", styles: [".evaluate{float:right}\n"] }]
|
|
1301
|
+
args: [{ selector: 'dc-lesson-renderer', standalone: true, imports: [KeyValuePipe, ButtonModule, DCChatComponent, DrawerModule], template: "<div>\n <div #dynamicLesson class=\"targetclass\">\n <ng-template #target></ng-template>\n </div>\n</div>\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(chatVisible()) {\n<p-drawer header=\"Conversation\" [visible]=\"chatVisible()\" (visibleChange)=\"onVisibleChange($event)\" position=\"bottom\" styleClass=\"app-bottom-overlay\">\n <dc-chat [appAgentTask]=\"evalAgentTask()\" [conversationSettings]=\"conversationSettings()\" (goalCompleted)=\"handleGoalCompleted($event)\"></dc-chat>\n</p-drawer>\n}\n", styles: [".evaluate{float:right}\n"] }]
|
|
1249
1302
|
}], ctorParameters: () => [], propDecorators: { dynamicLesson: [{
|
|
1250
1303
|
type: ViewChild,
|
|
1251
1304
|
args: ['dynamicLesson', { static: true }]
|
|
@@ -1416,6 +1469,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
|
|
|
1416
1469
|
type: Injectable
|
|
1417
1470
|
}] });
|
|
1418
1471
|
|
|
1472
|
+
// import { UserDataExchangeService } from '@dataclouder/ngx-agent-cards';
|
|
1419
1473
|
class LessonUtilsService {
|
|
1420
1474
|
constructor() {
|
|
1421
1475
|
this.#lessonService = inject(LESSONS_TOKEN);
|
|
@@ -1438,15 +1492,6 @@ class LessonUtilsService {
|
|
|
1438
1492
|
}
|
|
1439
1493
|
// Placeholder logic - adapt as needed from original component
|
|
1440
1494
|
console.log('Validating audios for lesson:', lesson.id);
|
|
1441
|
-
// Original logic commented out - requires SpeakerCompConfiguration type
|
|
1442
|
-
// const needsGeneration = lesson.components.some((component: any) => // Use 'any' or define SpeakerCompConfiguration
|
|
1443
|
-
// component.component === 'speaker' && component.settings?.voice && !component.audio
|
|
1444
|
-
// );
|
|
1445
|
-
// if (needsGeneration) {
|
|
1446
|
-
// this.#toastService.warn({ title: 'Audios por generar', subtitle: 'Se encontraron audios pendientes' });
|
|
1447
|
-
// // Consider calling generation service here or returning a flag
|
|
1448
|
-
// // this.#lessonService.generateAudiosForLesson(lesson.id).then(res => { ... });
|
|
1449
|
-
// }
|
|
1450
1495
|
}
|
|
1451
1496
|
/**
|
|
1452
1497
|
* Updates the lesson signal with a new cover image.
|
|
@@ -1496,7 +1541,9 @@ class LessonUtilsService {
|
|
|
1496
1541
|
});
|
|
1497
1542
|
// Note: Saving the lesson should be triggered from the component after calling this.
|
|
1498
1543
|
}
|
|
1544
|
+
// I want to deprecate this method
|
|
1499
1545
|
/**
|
|
1546
|
+
* @deprecated Use create new method.
|
|
1500
1547
|
* Generates lesson content using AI. Assumes the lesson is already saved.
|
|
1501
1548
|
* @param lessonId The ID of the lesson to generate content for.
|
|
1502
1549
|
* @returns The updated lesson object after AI generation, or null on failure.
|
|
@@ -1530,9 +1577,9 @@ class LessonUtilsService {
|
|
|
1530
1577
|
/**
|
|
1531
1578
|
* Improves the provided Markdown text using AI and updates the lesson.
|
|
1532
1579
|
* Assumes the lesson is already saved before calling this.
|
|
1533
|
-
* @param
|
|
1580
|
+
* @param lesson The lesson object containing the prompt and other context.
|
|
1534
1581
|
* @param markdownText The Markdown text generated from the lesson's HTML content.
|
|
1535
|
-
* @returns The
|
|
1582
|
+
* @returns The improved Markdown string, or null on failure.
|
|
1536
1583
|
*/
|
|
1537
1584
|
async improveMDWithAI(lesson, markdownText) {
|
|
1538
1585
|
if (!markdownText) {
|
|
@@ -1541,38 +1588,30 @@ class LessonUtilsService {
|
|
|
1541
1588
|
}
|
|
1542
1589
|
try {
|
|
1543
1590
|
this.loadingBarService.showIndeterminate();
|
|
1544
|
-
const
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
}
|
|
1549
|
-
const appTask = { task: task };
|
|
1550
|
-
const messages = [
|
|
1551
|
-
{ content: agentCard.systemPrompt, role: ChatRole.System },
|
|
1552
|
-
{ content: appTask.task, role: ChatRole.User },
|
|
1553
|
-
];
|
|
1554
|
-
const response = await this.#agentService.callChatCompletion({ messages, model: agentCard.model });
|
|
1555
|
-
let improvedMarkdown = response.content;
|
|
1591
|
+
const textPrompt = this.#lessonService.getPrompts().content(lesson);
|
|
1592
|
+
const messages = [{ content: textPrompt, role: ChatRole.User }];
|
|
1593
|
+
const response = await this.#agentService.callChatCompletion({ messages, model: { provider: 'google' } });
|
|
1594
|
+
let improvedMarkdown = response.content?.trim() ?? null;
|
|
1556
1595
|
// Remove potential markdown fences
|
|
1557
1596
|
const markdownFenceStart = '```markdown\n';
|
|
1558
1597
|
const markdownFenceEnd = '\n```';
|
|
1559
|
-
if (improvedMarkdown
|
|
1598
|
+
if (improvedMarkdown?.startsWith(markdownFenceStart) && improvedMarkdown.endsWith(markdownFenceEnd)) {
|
|
1560
1599
|
improvedMarkdown = improvedMarkdown.slice(markdownFenceStart.length, -markdownFenceEnd.length);
|
|
1561
1600
|
}
|
|
1562
1601
|
else {
|
|
1563
1602
|
// Also handle cases where it might just be ``` at the start/end
|
|
1564
1603
|
const simpleFence = '```';
|
|
1565
|
-
if (improvedMarkdown
|
|
1604
|
+
if (improvedMarkdown?.startsWith(simpleFence)) {
|
|
1566
1605
|
improvedMarkdown = improvedMarkdown.slice(simpleFence.length);
|
|
1567
1606
|
}
|
|
1568
|
-
if (improvedMarkdown
|
|
1607
|
+
if (improvedMarkdown?.endsWith(simpleFence)) {
|
|
1569
1608
|
improvedMarkdown = improvedMarkdown.slice(0, -simpleFence.length);
|
|
1570
1609
|
}
|
|
1571
1610
|
}
|
|
1572
1611
|
// Trim any leading/trailing whitespace that might remain after removing fences
|
|
1573
|
-
improvedMarkdown = improvedMarkdown
|
|
1612
|
+
improvedMarkdown = improvedMarkdown?.trim() ?? null;
|
|
1574
1613
|
console.log('Improved Markdown:', improvedMarkdown);
|
|
1575
|
-
return improvedMarkdown;
|
|
1614
|
+
return improvedMarkdown; // Return only the string
|
|
1576
1615
|
}
|
|
1577
1616
|
catch (error) {
|
|
1578
1617
|
console.error('Error during AI Markdown improvement in service:', error);
|
|
@@ -1582,7 +1621,78 @@ class LessonUtilsService {
|
|
|
1582
1621
|
finally {
|
|
1583
1622
|
this.loadingBarService.successAndHide();
|
|
1584
1623
|
}
|
|
1585
|
-
|
|
1624
|
+
}
|
|
1625
|
+
/**
|
|
1626
|
+
* Generates a concise description for the lesson using AI based on its content.
|
|
1627
|
+
* @param lesson The lesson object containing the content.
|
|
1628
|
+
* @returns A promise resolving to the generated description string, or null on failure.
|
|
1629
|
+
*/
|
|
1630
|
+
async generateDescriptionWithAI(lesson) {
|
|
1631
|
+
if (!lesson || !lesson.textCoded) {
|
|
1632
|
+
this.#toastService.warn({ title: 'Contenido Requerido', subtitle: 'Se necesita contenido en la lección para generar una descripción.' });
|
|
1633
|
+
return null;
|
|
1634
|
+
}
|
|
1635
|
+
try {
|
|
1636
|
+
this.loadingBarService.showIndeterminate(); // Corrected: No argument needed
|
|
1637
|
+
// Extract plain text from the lesson's HTML content
|
|
1638
|
+
const plainTextContent = this._extractTextFromHtml(lesson.textCoded);
|
|
1639
|
+
if (!plainTextContent || plainTextContent.trim().length === 0) {
|
|
1640
|
+
this.#toastService.warn({ title: 'Texto Vacío', subtitle: 'No se pudo extraer texto útil del contenido de la lección.' });
|
|
1641
|
+
return null;
|
|
1642
|
+
}
|
|
1643
|
+
const descriptionPrompt = this.#lessonService.getPrompts().description(lesson);
|
|
1644
|
+
const messages = [{ content: descriptionPrompt, role: ChatRole.User }];
|
|
1645
|
+
const response = await this.#agentService.callChatCompletion({ messages, model: { provider: 'google' } });
|
|
1646
|
+
let generatedDescription = response.content?.trim() ?? null;
|
|
1647
|
+
// Basic cleanup (remove potential quotes if the AI wraps the response)
|
|
1648
|
+
if (generatedDescription && generatedDescription.startsWith('"') && generatedDescription.endsWith('"')) {
|
|
1649
|
+
generatedDescription = generatedDescription.substring(1, generatedDescription.length - 1);
|
|
1650
|
+
}
|
|
1651
|
+
if (generatedDescription) {
|
|
1652
|
+
this.#toastService.success({ title: 'Descripción Generada', subtitle: 'Se generó la descripción con IA.' });
|
|
1653
|
+
return generatedDescription;
|
|
1654
|
+
}
|
|
1655
|
+
else {
|
|
1656
|
+
throw new Error('AI did not return a valid description.');
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
catch (error) {
|
|
1660
|
+
console.error('Error during AI description generation in service:', error);
|
|
1661
|
+
this.#toastService.error({ title: 'Error de IA', subtitle: 'No se pudo generar la descripción con IA.' });
|
|
1662
|
+
return null;
|
|
1663
|
+
}
|
|
1664
|
+
finally {
|
|
1665
|
+
this.loadingBarService.successAndHide();
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Extracts plain text from an HTML string, removing encoded JSON components.
|
|
1670
|
+
* @param htmlInput The HTML string potentially containing encoded JSON (~...~).
|
|
1671
|
+
* @returns The plain text representation.
|
|
1672
|
+
*/
|
|
1673
|
+
_extractTextFromHtml(htmlInput) {
|
|
1674
|
+
if (!htmlInput)
|
|
1675
|
+
return '';
|
|
1676
|
+
// 1. Replace encoded JSON blocks with their 'settings.text' or an empty string
|
|
1677
|
+
const jsonRegex = /~(.+?)~/g;
|
|
1678
|
+
const textWithPlaceholders = htmlInput.replace(jsonRegex, (match, jsonCoded) => {
|
|
1679
|
+
try {
|
|
1680
|
+
const decodedJson = jsonCoded.replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1681
|
+
const data = JSON.parse(decodedJson);
|
|
1682
|
+
return data?.settings?.text || ''; // Return text or empty string if not found
|
|
1683
|
+
}
|
|
1684
|
+
catch (error) {
|
|
1685
|
+
console.error('Failed to parse JSON inside HTML during text extraction:', jsonCoded, error);
|
|
1686
|
+
return ''; // Return empty string on error
|
|
1687
|
+
}
|
|
1688
|
+
});
|
|
1689
|
+
// 2. Strip remaining HTML tags to get plain text
|
|
1690
|
+
// Create a temporary DOM element to parse the HTML
|
|
1691
|
+
const tempDiv = document.createElement('div');
|
|
1692
|
+
tempDiv.innerHTML = textWithPlaceholders;
|
|
1693
|
+
// Use textContent to get the plain text, effectively stripping tags
|
|
1694
|
+
// Replace multiple whitespace characters (including newlines) with a single space
|
|
1695
|
+
return (tempDiv.textContent || tempDiv.innerText || '').replace(/\s+/g, ' ').trim();
|
|
1586
1696
|
}
|
|
1587
1697
|
/**
|
|
1588
1698
|
* Cleans orphaned components from the lesson's dynamicComponents.
|
|
@@ -1623,7 +1733,6 @@ class LessonUtilsService {
|
|
|
1623
1733
|
orphanedIds.push(componentId);
|
|
1624
1734
|
}
|
|
1625
1735
|
}
|
|
1626
|
-
// Log a warning if any orphaned components were found
|
|
1627
1736
|
if (orphanedIds.length > 0) {
|
|
1628
1737
|
console.warn(`[LessonUtilsService] Orphaned components detected and will be removed from lesson data (IDs not found in textCoded):`, orphanedIds);
|
|
1629
1738
|
this.#toastService.warn({
|
|
@@ -1691,22 +1800,95 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
|
|
|
1691
1800
|
type: Output
|
|
1692
1801
|
}] } });
|
|
1693
1802
|
|
|
1803
|
+
// TODO @DEPRECATED in favor to lesson appingles prompts. for english.
|
|
1804
|
+
const PROMPTS = {
|
|
1805
|
+
generateLesson: ``,
|
|
1806
|
+
GetImageSuggestion: ``,
|
|
1807
|
+
};
|
|
1808
|
+
const getImageSuggestion = (language, lessonTopic) => `
|
|
1809
|
+
Create a prompt for a visually engaging banner image for a language learning app. The image should:
|
|
1810
|
+
|
|
1811
|
+
- Feature vibrant colors and clean design that draws attention
|
|
1812
|
+
- Include subtle visual elements representing language learning (books, speech bubbles, or writing implements)
|
|
1813
|
+
- Incorporate one or more of the following focal elements:
|
|
1814
|
+
* A friendly animal character (like an owl, fox, or parrot) that could serve as a mascot
|
|
1815
|
+
* A scenic landscape that represents cultural context (like iconic landmarks or natural settings)
|
|
1816
|
+
* Key objects that relate to the specific lesson topics
|
|
1817
|
+
- Have a balanced composition with room for text overlay
|
|
1818
|
+
- Evoke a sense of curiosity and learning
|
|
1819
|
+
- Use a modern, minimalist art style with defined shapes
|
|
1820
|
+
|
|
1821
|
+
The banner relates to lessons about ${lessonTopic}, this lesson is to learn ${language}.
|
|
1822
|
+
|
|
1823
|
+
return only the text for the description, no explanation, no comments, just the prompt to directly generate the image
|
|
1824
|
+
`;
|
|
1825
|
+
const getPromptGenerateLesson = (baseLang, targetLang, lessonTopic, aditionalPrompt) => `
|
|
1826
|
+
Create a prompt for a Lesson for a language learning app.
|
|
1827
|
+
- the topic is ${lessonTopic},
|
|
1828
|
+
- the language to learn is ${targetLang}
|
|
1829
|
+
- the lesson is for beginners
|
|
1830
|
+
- write lesson in ${baseLang}, but show a lot of vocabulary in ${targetLang}
|
|
1831
|
+
- create sections
|
|
1832
|
+
- use markdown to format it, sections and titles
|
|
1833
|
+
- Create exercises for each section
|
|
1834
|
+
|
|
1835
|
+
${aditionalPrompt}
|
|
1836
|
+
`;
|
|
1837
|
+
|
|
1694
1838
|
class DCLessonMetadataEditorComponent {
|
|
1695
1839
|
constructor() {
|
|
1696
1840
|
// Use signal for input lesson data
|
|
1697
|
-
this.lesson = signal(undefined);
|
|
1698
|
-
this.isLoadingLesson = signal(false);
|
|
1841
|
+
this.lesson = signal(undefined); // The lesson data itself
|
|
1842
|
+
this.isLoadingLesson = signal(false); // Shared loading state
|
|
1699
1843
|
// Outputs for actions
|
|
1700
1844
|
this.saveRequest = new EventEmitter();
|
|
1701
1845
|
this.importNotionRequest = new EventEmitter();
|
|
1702
1846
|
this.improveNotionRequest = new EventEmitter();
|
|
1703
|
-
|
|
1704
|
-
// Output
|
|
1705
|
-
|
|
1847
|
+
// Removed generateAIRequest Output as it's handled internally now
|
|
1848
|
+
// Output removed as the component now updates the input signal directly.
|
|
1849
|
+
// @Output() propertyChange = new EventEmitter<{ propertyPath: string; value: any }>();
|
|
1850
|
+
// Injected Services
|
|
1851
|
+
this.#lessonUtilsService = inject(LessonUtilsService);
|
|
1852
|
+
this.#toastService = inject(TOAST_ALERTS_TOKEN);
|
|
1853
|
+
this.#lessonService = inject(LESSONS_TOKEN);
|
|
1854
|
+
this.#turndownService = new TurndownService(); // Instantiate TurndownService
|
|
1706
1855
|
}
|
|
1707
|
-
//
|
|
1708
|
-
|
|
1709
|
-
|
|
1856
|
+
// Removed generateAIRequest Output as it's handled internally now
|
|
1857
|
+
// Output removed as the component now updates the input signal directly.
|
|
1858
|
+
// @Output() propertyChange = new EventEmitter<{ propertyPath: string; value: any }>();
|
|
1859
|
+
// Injected Services
|
|
1860
|
+
#lessonUtilsService;
|
|
1861
|
+
#toastService;
|
|
1862
|
+
#lessonService;
|
|
1863
|
+
#turndownService; // Instantiate TurndownService
|
|
1864
|
+
// Method to handle property changes for ROOT properties (e.g., level)
|
|
1865
|
+
onRootPropertyChange(property, value) {
|
|
1866
|
+
this.lesson.update((current) => {
|
|
1867
|
+
if (!current)
|
|
1868
|
+
return undefined;
|
|
1869
|
+
// Avoid updating metadata directly here
|
|
1870
|
+
if (property === 'metadata') {
|
|
1871
|
+
console.warn('Direct metadata updates should use onMetadataPropertyChange');
|
|
1872
|
+
return current;
|
|
1873
|
+
}
|
|
1874
|
+
return { ...current, [property]: value };
|
|
1875
|
+
});
|
|
1876
|
+
// Emit removed - signal is updated directly above.
|
|
1877
|
+
// this.propertyChange.emit({ propertyPath: property, value });
|
|
1878
|
+
}
|
|
1879
|
+
// Method to handle property changes for METADATA properties
|
|
1880
|
+
onMetadataPropertyChange(property, value) {
|
|
1881
|
+
this.lesson.update((current) => {
|
|
1882
|
+
if (!current)
|
|
1883
|
+
return undefined;
|
|
1884
|
+
// Ensure metadata object exists and update the specific property
|
|
1885
|
+
// Note: Ensure ContentMetadata interface is imported or defined locally if not already.
|
|
1886
|
+
// Assuming ContentMetadata is imported from '../../lesson-mini-components/components/lessons.clases'
|
|
1887
|
+
const updatedMetadata = { ...(current.metadata ?? {}), [property]: value };
|
|
1888
|
+
return { ...current, metadata: updatedMetadata };
|
|
1889
|
+
});
|
|
1890
|
+
// Emit removed - signal is updated directly above.
|
|
1891
|
+
// this.propertyChange.emit({ propertyPath: `metadata.${String(property)}`, value });
|
|
1710
1892
|
}
|
|
1711
1893
|
// Methods to emit action requests
|
|
1712
1894
|
emitSaveRequest() {
|
|
@@ -1718,13 +1900,110 @@ class DCLessonMetadataEditorComponent {
|
|
|
1718
1900
|
emitImproveNotionRequest() {
|
|
1719
1901
|
this.improveNotionRequest.emit();
|
|
1720
1902
|
}
|
|
1721
|
-
|
|
1722
|
-
|
|
1903
|
+
/**
|
|
1904
|
+
* Generates lesson content using AI, saving the current state first.
|
|
1905
|
+
* Moved from DCLessonEditorComponent.
|
|
1906
|
+
*/
|
|
1907
|
+
async generateByAI() {
|
|
1908
|
+
const currentLesson = this.lesson(); // Get current value
|
|
1909
|
+
if (!currentLesson?.id) {
|
|
1910
|
+
this.#toastService.warn({ title: 'Guardar primero', subtitle: 'Guarda la lección antes de usar IA.' });
|
|
1911
|
+
return;
|
|
1912
|
+
}
|
|
1913
|
+
this.isLoadingLesson.set(true);
|
|
1914
|
+
try {
|
|
1915
|
+
const rawHtmlContent = currentLesson.textCoded || '';
|
|
1916
|
+
if (!rawHtmlContent) {
|
|
1917
|
+
console.warn('No HTML content found in lesson to process. taking just description');
|
|
1918
|
+
this.#toastService.info({ title: 'Contenido lección desde 0', subtitle: 'Solo se usará el prompt' });
|
|
1919
|
+
// this.us
|
|
1920
|
+
// Use metadata description, provide empty string if metadata or description is missing
|
|
1921
|
+
const prompt = getPromptGenerateLesson('portugues', 'espanhol', currentLesson.metadata?.description ?? '', '');
|
|
1922
|
+
debugger;
|
|
1923
|
+
await this.#lessonUtilsService.generateByAI(currentLesson.id);
|
|
1924
|
+
}
|
|
1925
|
+
else {
|
|
1926
|
+
// Clean orphaned and Save before Improve
|
|
1927
|
+
const lessonToSave = this.#lessonUtilsService.cleanOrphanedComponents(currentLesson);
|
|
1928
|
+
const savedLesson = await this.#lessonService.postLesson(lessonToSave);
|
|
1929
|
+
if (!savedLesson) {
|
|
1930
|
+
this.#toastService.error({ title: 'Error al guardar', subtitle: 'No se pudo guardar antes de generar con IA.' });
|
|
1931
|
+
throw new Error('Failed to save before AI generation');
|
|
1932
|
+
}
|
|
1933
|
+
this.lesson.set(savedLesson);
|
|
1934
|
+
// Replace encoded JSON with actual text before Markdown conversion
|
|
1935
|
+
const processedHtmlContent = this._extractTextFromEncodedJson(rawHtmlContent);
|
|
1936
|
+
// Convert the processed HTML (with text instead of JSON) to Markdown
|
|
1937
|
+
const markdownText = this.#turndownService.turndown(processedHtmlContent);
|
|
1938
|
+
// Use the updated lesson signal value for AI improvement
|
|
1939
|
+
const improvedMarkdown = await this.#lessonUtilsService.improveMDWithAI(this.lesson(), markdownText);
|
|
1940
|
+
if (improvedMarkdown) {
|
|
1941
|
+
// Convert the improved Markdown back to HTML before setting it
|
|
1942
|
+
const improvedHtml = marked(improvedMarkdown);
|
|
1943
|
+
// Update the signal directly
|
|
1944
|
+
this.lesson.update((current) => (current ? { ...current, textCoded: improvedHtml } : undefined));
|
|
1945
|
+
// Save the AI-generated content
|
|
1946
|
+
await this.#lessonService.postLesson(this.lesson());
|
|
1947
|
+
this.#toastService.success({ title: 'Contenido generado', subtitle: 'Se generó y guardó el contenido con IA.' });
|
|
1948
|
+
}
|
|
1949
|
+
else {
|
|
1950
|
+
// Toast error is handled by the service
|
|
1951
|
+
throw new Error('AI generation failed or lesson fetch failed after generation.');
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
catch (error) {
|
|
1956
|
+
console.error('Error during AI generation process in metadata editor:', error);
|
|
1957
|
+
// Service handles specific AI error toasts, maybe add a general one here if needed
|
|
1958
|
+
// this.#toastService.error({ title: 'Error General', subtitle: 'Ocurrió un problema durante el proceso de IA.' });
|
|
1959
|
+
}
|
|
1960
|
+
finally {
|
|
1961
|
+
this.isLoadingLesson.set(false); // Stop loading
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
/**
|
|
1965
|
+
* Replaces encoded JSON blocks (~...~) in HTML with their 'settings.text' value.
|
|
1966
|
+
* Moved from DCLessonEditorComponent.
|
|
1967
|
+
* @param htmlInput The HTML string containing encoded JSON.
|
|
1968
|
+
* @returns The processed HTML string with text replacements.
|
|
1969
|
+
*/
|
|
1970
|
+
_extractTextFromEncodedJson(htmlInput) {
|
|
1971
|
+
const jsonRegex = /~(.+?)~/g; // Global flag to replace all occurrences
|
|
1972
|
+
return htmlInput.replace(jsonRegex, (match, jsonCoded) => {
|
|
1973
|
+
try {
|
|
1974
|
+
// Attempt to decode HTML entities that might be present in the JSON string
|
|
1975
|
+
const decodedJson = jsonCoded.replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
1976
|
+
const data = JSON.parse(decodedJson);
|
|
1977
|
+
// Return the text if available, otherwise keep the original match (or an error placeholder)
|
|
1978
|
+
return data?.settings?.text || match;
|
|
1979
|
+
}
|
|
1980
|
+
catch (error) {
|
|
1981
|
+
console.error('Failed to parse JSON inside HTML:', jsonCoded, error);
|
|
1982
|
+
return '<!-- invalid component data -->'; // Placeholder for parsing errors
|
|
1983
|
+
}
|
|
1984
|
+
});
|
|
1985
|
+
}
|
|
1986
|
+
/**
|
|
1987
|
+
* Calls the LessonUtilsService to generate a description using AI
|
|
1988
|
+
* and updates the lesson signal if successful.
|
|
1989
|
+
*/
|
|
1990
|
+
async triggerGenerateDescriptionAI() {
|
|
1991
|
+
const currentLesson = this.lesson();
|
|
1992
|
+
if (!currentLesson) {
|
|
1993
|
+
this.#toastService.warn({ title: 'Lección no cargada', subtitle: 'Espera a que la lección se cargue.' });
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1996
|
+
const generatedDescription = await this.#lessonUtilsService.generateDescriptionWithAI(currentLesson);
|
|
1997
|
+
if (generatedDescription) {
|
|
1998
|
+
// Use the new method for metadata properties
|
|
1999
|
+
this.onMetadataPropertyChange('description', generatedDescription);
|
|
2000
|
+
}
|
|
1723
2001
|
}
|
|
1724
2002
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonMetadataEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1725
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: DCLessonMetadataEditorComponent, isStandalone: true, selector: "dc-lesson-metadata-editor", inputs: { lesson: "lesson", isLoadingLesson: "isLoadingLesson" }, outputs: { saveRequest: "saveRequest", importNotionRequest: "importNotionRequest", improveNotionRequest: "improveNotionRequest"
|
|
2003
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: DCLessonMetadataEditorComponent, isStandalone: true, selector: "dc-lesson-metadata-editor", inputs: { lesson: "lesson", isLoadingLesson: "isLoadingLesson" }, outputs: { saveRequest: "saveRequest", importNotionRequest: "importNotionRequest", improveNotionRequest: "improveNotionRequest" }, ngImport: i0, template: "@if (lesson(); as currentLesson) {\n<div>\n <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 <!-- Use one-way binding and ngModelChange for signals -->\n <div>\n <input\n pInputText\n style=\"width: 100%\"\n [ngModel]=\"currentLesson.metadata?.title\"\n (ngModelChange)=\"onMetadataPropertyChange('title', $event)\"\n type=\"text\"\n placeholder=\"Agrega un t\u00EDtulo\" />\n </div>\n <div style=\"margin-top: 4px\">\n <p-inputgroup>\n <input\n pInputText\n style=\"width: 100%\"\n [ngModel]=\"currentLesson.metadata?.description\"\n (ngModelChange)=\"onMetadataPropertyChange('description', $event)\"\n type=\"text\"\n 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\n <div style=\"display: flex; align-items: center; margin-top: 10px\">\n <input\n pInputText\n style=\"flex: auto; margin-right: 5px\"\n [ngModel]=\"currentLesson.metadata?.prompt\"\n (ngModelChange)=\"onMetadataPropertyChange('prompt', $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 <div style=\"margin-top: 10px\">\n <label class=\"checkbox-container\" style=\"margin-right: 15px\">\n <input\n type=\"checkbox\"\n [ngModel]=\"currentLesson.metadata?.isPublished\"\n (ngModelChange)=\"onMetadataPropertyChange('isPublished', $event)\"\n title=\"Cuando termines la edici\u00F3n marca esta casilla\" />\n <span class=\"checkmark\"></span>\n Publicada\n </label>\n\n <input\n pInputText\n [ngModel]=\"currentLesson.level\"\n (ngModelChange)=\"onRootPropertyChange('level', $event)\"\n type=\"number\"\n placeholder=\"Nivel\"\n style=\"width: 80px\" />\n </div>\n\n <!-- Access signal values -->\n <div style=\"margin-top: 10px; font-size: 0.9em; color: var(--text-color-secondary)\">\n {{ currentLesson.baseLang | flagEmoji }} -> {{ currentLesson.targetLang | flagEmoji }} Lecci\u00F3n para hablantes de\n {{ currentLesson.baseLang | langDesc : 'es' }} que aprenden\n {{ currentLesson.targetLang | langDesc : 'es' }}\n </div>\n </div>\n</div>\n} @else {\n<!-- Optional: Show a loading state or placeholder if lesson is undefined -->\n<p>Cargando datos de la lecci\u00F3n...</p>\n}\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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { 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: 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", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i2$5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "pipe", type: // Added TooltipModule
|
|
1726
2004
|
FlagLanguagePipe, name: "flagEmoji" }, { kind: "pipe", type: // Added Pipe
|
|
1727
|
-
LangDescTranslationPipe, name: "langDesc" }
|
|
2005
|
+
LangDescTranslationPipe, name: "langDesc" }, { kind: "ngmodule", type: // Added Pipe
|
|
2006
|
+
InputGroupModule }, { kind: "component", type: i5$1.InputGroup, selector: "p-inputgroup, p-inputGroup, p-input-group", inputs: ["style", "styleClass"] }] }); }
|
|
1728
2007
|
}
|
|
1729
2008
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonMetadataEditorComponent, decorators: [{
|
|
1730
2009
|
type: Component,
|
|
@@ -1736,7 +2015,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
|
|
|
1736
2015
|
TooltipModule, // Added TooltipModule
|
|
1737
2016
|
FlagLanguagePipe, // Added Pipe
|
|
1738
2017
|
LangDescTranslationPipe, // Added Pipe
|
|
1739
|
-
|
|
2018
|
+
InputGroupModule,
|
|
2019
|
+
], template: "@if (lesson(); as currentLesson) {\n<div>\n <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 <!-- Use one-way binding and ngModelChange for signals -->\n <div>\n <input\n pInputText\n style=\"width: 100%\"\n [ngModel]=\"currentLesson.metadata?.title\"\n (ngModelChange)=\"onMetadataPropertyChange('title', $event)\"\n type=\"text\"\n placeholder=\"Agrega un t\u00EDtulo\" />\n </div>\n <div style=\"margin-top: 4px\">\n <p-inputgroup>\n <input\n pInputText\n style=\"width: 100%\"\n [ngModel]=\"currentLesson.metadata?.description\"\n (ngModelChange)=\"onMetadataPropertyChange('description', $event)\"\n type=\"text\"\n 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\n <div style=\"display: flex; align-items: center; margin-top: 10px\">\n <input\n pInputText\n style=\"flex: auto; margin-right: 5px\"\n [ngModel]=\"currentLesson.metadata?.prompt\"\n (ngModelChange)=\"onMetadataPropertyChange('prompt', $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 <div style=\"margin-top: 10px\">\n <label class=\"checkbox-container\" style=\"margin-right: 15px\">\n <input\n type=\"checkbox\"\n [ngModel]=\"currentLesson.metadata?.isPublished\"\n (ngModelChange)=\"onMetadataPropertyChange('isPublished', $event)\"\n title=\"Cuando termines la edici\u00F3n marca esta casilla\" />\n <span class=\"checkmark\"></span>\n Publicada\n </label>\n\n <input\n pInputText\n [ngModel]=\"currentLesson.level\"\n (ngModelChange)=\"onRootPropertyChange('level', $event)\"\n type=\"number\"\n placeholder=\"Nivel\"\n style=\"width: 80px\" />\n </div>\n\n <!-- Access signal values -->\n <div style=\"margin-top: 10px; font-size: 0.9em; color: var(--text-color-secondary)\">\n {{ currentLesson.baseLang | flagEmoji }} -> {{ currentLesson.targetLang | flagEmoji }} Lecci\u00F3n para hablantes de\n {{ currentLesson.baseLang | langDesc : 'es' }} que aprenden\n {{ currentLesson.targetLang | langDesc : 'es' }}\n </div>\n </div>\n</div>\n} @else {\n<!-- Optional: Show a loading state or placeholder if lesson is undefined -->\n<p>Cargando datos de la lecci\u00F3n...</p>\n}\n" }]
|
|
1740
2020
|
}], propDecorators: { lesson: [{
|
|
1741
2021
|
type: Input,
|
|
1742
2022
|
args: [{ required: true }]
|
|
@@ -1749,12 +2029,112 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
|
|
|
1749
2029
|
type: Output
|
|
1750
2030
|
}], improveNotionRequest: [{
|
|
1751
2031
|
type: Output
|
|
1752
|
-
}], generateAIRequest: [{
|
|
1753
|
-
type: Output
|
|
1754
|
-
}], propertyChange: [{
|
|
1755
|
-
type: Output
|
|
1756
2032
|
}] } });
|
|
1757
2033
|
|
|
2034
|
+
// Define placeholder endpoints - these should be configured appropriately
|
|
2035
|
+
const LESSONS_BASE_PATH = 'api/lesson'; // Example base path
|
|
2036
|
+
class DefaultLessonsService {
|
|
2037
|
+
constructor() {
|
|
2038
|
+
this.httpCoreService = inject(HttpCoreService);
|
|
2039
|
+
// --- Endpoint Definitions (Hardcoded as requested) ---
|
|
2040
|
+
this.endpoints = {
|
|
2041
|
+
queryLessons: `${LESSONS_BASE_PATH}/query`,
|
|
2042
|
+
getLesson: (id) => `${LESSONS_BASE_PATH}/${id}`,
|
|
2043
|
+
saveLesson: `api/user/saveLesson`,
|
|
2044
|
+
updateLesson: (id) => `${LESSONS_BASE_PATH}/${id}`, // Assuming PUT to /lessons/:id
|
|
2045
|
+
deleteLesson: (id) => `${LESSONS_BASE_PATH}/${id}`, // Assuming DELETE to /lessons/:id
|
|
2046
|
+
generateLesson: `${LESSONS_BASE_PATH}/generate`, // Placeholder
|
|
2047
|
+
generateByAI: `${LESSONS_BASE_PATH}/generate-ai`, // Placeholder
|
|
2048
|
+
improveMDWithAI: `${LESSONS_BASE_PATH}/improve-markdown-ai`, // Placeholder
|
|
2049
|
+
QueryLessons: 'api/lesson/query',
|
|
2050
|
+
Lesson: 'api/lesson',
|
|
2051
|
+
SaveLesson: 'api/lesson-polilan',
|
|
2052
|
+
GetPublicLessons: 'api/lesson/publicLessons',
|
|
2053
|
+
GetUnpublishedLessons: 'api/lesson/unpublished',
|
|
2054
|
+
TakenLesson: 'api/lesson/taken',
|
|
2055
|
+
DeleteLesson: 'api/lesson',
|
|
2056
|
+
Base: 'api/lesson',
|
|
2057
|
+
GenerateBanner: 'api/lesson/generate-banner',
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
saveTakenLesson(lesson) {
|
|
2061
|
+
// Not sure how to implement this yet.
|
|
2062
|
+
return this.httpCoreService.post(this.endpoints.saveLesson, lesson);
|
|
2063
|
+
}
|
|
2064
|
+
// --- Method Implementations ---
|
|
2065
|
+
async getLessons(paginator) {
|
|
2066
|
+
// Assuming paginator is the body for a POST request based on the example
|
|
2067
|
+
return this.httpCoreService.post(this.endpoints.queryLessons, paginator || {});
|
|
2068
|
+
}
|
|
2069
|
+
async getLesson(id) {
|
|
2070
|
+
return this.httpCoreService.get(this.endpoints.getLesson(id));
|
|
2071
|
+
}
|
|
2072
|
+
async postLesson(lesson) {
|
|
2073
|
+
return this.httpCoreService.post(this.endpoints.saveLesson, lesson);
|
|
2074
|
+
}
|
|
2075
|
+
async updateLesson(lesson) {
|
|
2076
|
+
if (!lesson._id) {
|
|
2077
|
+
throw new Error('Lesson ID is required for update.');
|
|
2078
|
+
}
|
|
2079
|
+
// Assuming _id is the identifier
|
|
2080
|
+
return this.httpCoreService.put(this.endpoints.updateLesson(lesson._id), lesson);
|
|
2081
|
+
}
|
|
2082
|
+
async deleteLesson(id) {
|
|
2083
|
+
return this.httpCoreService.delete(this.endpoints.deleteLesson(id));
|
|
2084
|
+
}
|
|
2085
|
+
async generateLesson(lesson) {
|
|
2086
|
+
// This endpoint might need specific data or structure
|
|
2087
|
+
return this.httpCoreService.post(this.endpoints.generateLesson, lesson);
|
|
2088
|
+
}
|
|
2089
|
+
async postGenerateByAI(id) {
|
|
2090
|
+
return this.httpCoreService.post(this.endpoints.generateByAI, { id });
|
|
2091
|
+
}
|
|
2092
|
+
async postImproveMDWithAI(lessonId, markdownText) {
|
|
2093
|
+
return this.httpCoreService.post(this.endpoints.improveMDWithAI, { id: lessonId, markdown: markdownText });
|
|
2094
|
+
}
|
|
2095
|
+
extractTextFromHtml(html) {
|
|
2096
|
+
// Copied from src/app/core/data-services/lessons.service.ts
|
|
2097
|
+
const r1 = new RegExp('~(.+?)~', 'g');
|
|
2098
|
+
const lessonHtml = html.replace(r1, (_matching, jsonCoded) => {
|
|
2099
|
+
try {
|
|
2100
|
+
const data = JSON.parse(jsonCoded);
|
|
2101
|
+
return `<span>${data?.settings?.text || ''}</span>`; // Added default empty string
|
|
2102
|
+
}
|
|
2103
|
+
catch (e) {
|
|
2104
|
+
console.error('Error parsing JSON in extractTextFromHtml:', jsonCoded, e);
|
|
2105
|
+
return ''; // Return empty string on error
|
|
2106
|
+
}
|
|
2107
|
+
});
|
|
2108
|
+
// Remove HTML tags
|
|
2109
|
+
let text = lessonHtml.replace(/<[^>]*>/g, ' ');
|
|
2110
|
+
// Remove style and script content
|
|
2111
|
+
text = text.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, ' ');
|
|
2112
|
+
text = text.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, ' ');
|
|
2113
|
+
// Decode HTML entities
|
|
2114
|
+
text = text.replace(/ /g, ' ');
|
|
2115
|
+
text = text.replace(/&/g, '&');
|
|
2116
|
+
text = text.replace(/</g, '<');
|
|
2117
|
+
text = text.replace(/>/g, '>');
|
|
2118
|
+
// Remove extra whitespace
|
|
2119
|
+
text = text.replace(/\s+/g, ' ').trim();
|
|
2120
|
+
return text;
|
|
2121
|
+
}
|
|
2122
|
+
generateBanner(prompt, lessonId) {
|
|
2123
|
+
return this.httpCoreService.post(this.endpoints.GenerateBanner, { prompt, lessonId });
|
|
2124
|
+
}
|
|
2125
|
+
getPrompts() {
|
|
2126
|
+
return null;
|
|
2127
|
+
}
|
|
2128
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DefaultLessonsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2129
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DefaultLessonsService, providedIn: 'root' }); }
|
|
2130
|
+
}
|
|
2131
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DefaultLessonsService, decorators: [{
|
|
2132
|
+
type: Injectable,
|
|
2133
|
+
args: [{
|
|
2134
|
+
providedIn: 'root',
|
|
2135
|
+
}]
|
|
2136
|
+
}] });
|
|
2137
|
+
|
|
1758
2138
|
class DCLessonEditorComponent {
|
|
1759
2139
|
// Services
|
|
1760
2140
|
#activatedRoute; // Re-inject as it's needed for navigation
|
|
@@ -1763,6 +2143,7 @@ class DCLessonEditorComponent {
|
|
|
1763
2143
|
#router;
|
|
1764
2144
|
#lessonService;
|
|
1765
2145
|
#toastService;
|
|
2146
|
+
#loadingBarService;
|
|
1766
2147
|
constructor() {
|
|
1767
2148
|
// Services
|
|
1768
2149
|
this.#activatedRoute = inject(ActivatedRoute); // Re-inject as it's needed for navigation
|
|
@@ -1771,15 +2152,31 @@ class DCLessonEditorComponent {
|
|
|
1771
2152
|
this.#router = inject(Router);
|
|
1772
2153
|
this.#lessonService = inject(LESSONS_TOKEN);
|
|
1773
2154
|
this.#toastService = inject(TOAST_ALERTS_TOKEN);
|
|
2155
|
+
this.#loadingBarService = inject(LoadingBarService);
|
|
2156
|
+
this.defaultLessonsService = inject(DefaultLessonsService);
|
|
2157
|
+
this.promptService = inject(PromptService);
|
|
2158
|
+
this.agentChatService = inject(CONVERSATION_AI_TOKEN);
|
|
2159
|
+
this.ngxVertexService = inject(NgxVertexService);
|
|
2160
|
+
this.cdr = inject(ChangeDetectorRef);
|
|
1774
2161
|
// Signals States
|
|
1775
2162
|
this.lessonId = toSignal(inject(ActivatedRoute).paramMap.pipe(map((params) => params.get('id'))));
|
|
1776
2163
|
this.lesson = signal(undefined); // Initialize as undefined
|
|
1777
2164
|
this.isLoadingLesson = signal(false);
|
|
1778
2165
|
// Computed Signals
|
|
1779
2166
|
this.coverImageUrl = computed(() => {
|
|
2167
|
+
// Priority Order 1 Metadata Banner, 2 Banner, 3 Media First Images, 4 Default Banner TODO: reveme banner after migration to Content
|
|
1780
2168
|
const currentLesson = this.lesson();
|
|
1781
|
-
|
|
1782
|
-
|
|
2169
|
+
if (currentLesson?.metadata?.banner?.url) {
|
|
2170
|
+
return currentLesson.metadata.banner.url;
|
|
2171
|
+
}
|
|
2172
|
+
else if (currentLesson?.banner?.url) {
|
|
2173
|
+
return currentLesson.banner.url;
|
|
2174
|
+
}
|
|
2175
|
+
else if (currentLesson?.media?.images?.find((img) => img.type === 'cover')) {
|
|
2176
|
+
// 3 Media First Images
|
|
2177
|
+
return currentLesson.media.images.find((img) => img.type === 'cover')?.url;
|
|
2178
|
+
}
|
|
2179
|
+
return '/assets/images/default_banner.webp';
|
|
1783
2180
|
});
|
|
1784
2181
|
// Computed signal to get dynamic components as an array for easier iteration in the template
|
|
1785
2182
|
this.dynamicComponentsArray = computed(() => {
|
|
@@ -1798,6 +2195,7 @@ class DCLessonEditorComponent {
|
|
|
1798
2195
|
fileName: 'cover',
|
|
1799
2196
|
cropSettings: { resizeToWidth: 850, aspectRatio: AspectType.RectangleLarge, resolutions: [ResolutionType.Medium] },
|
|
1800
2197
|
};
|
|
2198
|
+
this.promptsVisible = false;
|
|
1801
2199
|
// Effect to fetch lesson data when ID changes
|
|
1802
2200
|
effect(async () => {
|
|
1803
2201
|
const id = this.lessonId();
|
|
@@ -1921,15 +2319,6 @@ class DCLessonEditorComponent {
|
|
|
1921
2319
|
const newComponent = result?.obj;
|
|
1922
2320
|
if (newComponent?.id) {
|
|
1923
2321
|
console.log('Component builder closed, result received in editor:', newComponent);
|
|
1924
|
-
// // Transform LessonComponentConfiguration to DynamicContentComponent
|
|
1925
|
-
// const dynamicComponentToAdd: DynamicContentComponent = {
|
|
1926
|
-
// id: componentConfig.id,
|
|
1927
|
-
// component: componentConfig.component,
|
|
1928
|
-
// inputs: {
|
|
1929
|
-
// config: componentConfig, // Pass the original config object as an input named 'config'
|
|
1930
|
-
// // Add other potential inputs if needed based on component type later
|
|
1931
|
-
// },
|
|
1932
|
-
// };
|
|
1933
2322
|
// Update the lesson signal, adding the transformed component to the dynamicComponents object
|
|
1934
2323
|
this.lesson.update((currentLesson) => {
|
|
1935
2324
|
if (!currentLesson)
|
|
@@ -1949,75 +2338,8 @@ class DCLessonEditorComponent {
|
|
|
1949
2338
|
}
|
|
1950
2339
|
}
|
|
1951
2340
|
// isLoadingLesson signal is used directly
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
if (!currentId) {
|
|
1955
|
-
this.#toastService.warn({ title: 'Guardar primero', subtitle: 'Guarda la lección antes de usar IA.' });
|
|
1956
|
-
return;
|
|
1957
|
-
}
|
|
1958
|
-
this.isLoadingLesson.set(true);
|
|
1959
|
-
try {
|
|
1960
|
-
// Ensure latest changes are saved before generating
|
|
1961
|
-
const savedLesson = await this.saveLesson();
|
|
1962
|
-
if (!savedLesson) {
|
|
1963
|
-
// Handle save error - toast is shown in saveLesson
|
|
1964
|
-
throw new Error('Failed to save before AI generation');
|
|
1965
|
-
}
|
|
1966
|
-
// // Removed debugger
|
|
1967
|
-
const rawHtmlContent = this.lesson()?.textCoded || '';
|
|
1968
|
-
if (!rawHtmlContent) {
|
|
1969
|
-
console.warn('No HTML content found in lesson to process.');
|
|
1970
|
-
this.isLoadingLesson.set(false);
|
|
1971
|
-
return; // Exit if no content
|
|
1972
|
-
}
|
|
1973
|
-
// Replace encoded JSON with actual text before Markdown conversion
|
|
1974
|
-
const processedHtmlContent = this._extractTextFromEncodedJson(rawHtmlContent);
|
|
1975
|
-
const turndownService = new TurndownService();
|
|
1976
|
-
// Convert the processed HTML (with text instead of JSON) to Markdown
|
|
1977
|
-
const markdownText = turndownService.turndown(processedHtmlContent);
|
|
1978
|
-
const improvedMarkdown = await this.#lessonUtilsService.improveMDWithAI(this.lesson(), markdownText);
|
|
1979
|
-
if (improvedMarkdown) {
|
|
1980
|
-
// Convert the improved Markdown back to HTML before setting it
|
|
1981
|
-
const improvedHtml = marked(improvedMarkdown);
|
|
1982
|
-
this.lesson.set({ ...this.lesson(), textCoded: improvedHtml }); // Update the signal with the converted HTML
|
|
1983
|
-
// Toast success is handled by the service
|
|
1984
|
-
}
|
|
1985
|
-
else {
|
|
1986
|
-
// Toast error is handled by the service
|
|
1987
|
-
throw new Error('AI generation failed or lesson fetch failed after generation.');
|
|
1988
|
-
}
|
|
1989
|
-
}
|
|
1990
|
-
catch (error) {
|
|
1991
|
-
// Type error
|
|
1992
|
-
console.error('Error during AI generation process in component:', error);
|
|
1993
|
-
// Service handles specific AI error toasts, maybe add a general one here if needed
|
|
1994
|
-
// this.#toastService.error({ title: 'Error General', subtitle: 'Ocurrió un problema durante el proceso de IA.' });
|
|
1995
|
-
}
|
|
1996
|
-
finally {
|
|
1997
|
-
this.isLoadingLesson.set(false); // Stop loading
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
/**
|
|
2001
|
-
* Replaces encoded JSON blocks (~...~) in HTML with their 'settings.text' value.
|
|
2002
|
-
* @param htmlInput The HTML string containing encoded JSON.
|
|
2003
|
-
* @returns The processed HTML string with text replacements.
|
|
2004
|
-
*/
|
|
2005
|
-
_extractTextFromEncodedJson(htmlInput) {
|
|
2006
|
-
const jsonRegex = /~(.+?)~/g; // Global flag to replace all occurrences
|
|
2007
|
-
return htmlInput.replace(jsonRegex, (match, jsonCoded) => {
|
|
2008
|
-
try {
|
|
2009
|
-
// Attempt to decode HTML entities that might be present in the JSON string
|
|
2010
|
-
const decodedJson = jsonCoded.replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
2011
|
-
const data = JSON.parse(decodedJson);
|
|
2012
|
-
// Return the text if available, otherwise keep the original match (or an error placeholder)
|
|
2013
|
-
return data?.settings?.text || match;
|
|
2014
|
-
}
|
|
2015
|
-
catch (error) {
|
|
2016
|
-
console.error('Failed to parse JSON inside HTML:', jsonCoded, error);
|
|
2017
|
-
return '<!-- invalid component data -->'; // Placeholder for parsing errors
|
|
2018
|
-
}
|
|
2019
|
-
});
|
|
2020
|
-
}
|
|
2341
|
+
// Removed generateByAI and _extractTextFromEncodedJson methods.
|
|
2342
|
+
// This logic is now handled within DCLessonMetadataEditorComponent.
|
|
2021
2343
|
/**
|
|
2022
2344
|
* Handles the image upload event, updates the lesson signal via the service, and saves.
|
|
2023
2345
|
* @param event The image upload event data.
|
|
@@ -2059,9 +2381,51 @@ class DCLessonEditorComponent {
|
|
|
2059
2381
|
showComponentDetails(data) {
|
|
2060
2382
|
alert('showComponentDetails' + JSON.stringify(data));
|
|
2061
2383
|
}
|
|
2384
|
+
async generateBanner() {
|
|
2385
|
+
this.#toastService.info({ title: 'Generando prompt de sugerencia', subtitle: 'Por favor, espera' });
|
|
2386
|
+
const prompt = getImageSuggestion('portugues', this.lesson().description);
|
|
2387
|
+
const geminiRes = await this.ngxVertexService.generateText([{ role: ChatRoleVertex.User, content: prompt }]);
|
|
2388
|
+
this.promptService
|
|
2389
|
+
.openPrompt({
|
|
2390
|
+
title: 'Generar Banner',
|
|
2391
|
+
initialValue: geminiRes.content || '',
|
|
2392
|
+
message: 'Ingresa una idea de lo que quieres ver',
|
|
2393
|
+
acceptText: 'Generar',
|
|
2394
|
+
cancelText: 'Cancelar',
|
|
2395
|
+
})
|
|
2396
|
+
.then((promptResult) => {
|
|
2397
|
+
if (promptResult) {
|
|
2398
|
+
this.#loadingBarService.showIndeterminate();
|
|
2399
|
+
this.defaultLessonsService.generateBanner(promptResult, this.lessonId()).then((result) => {
|
|
2400
|
+
if (result) {
|
|
2401
|
+
this.updateLessonProperty('banner', result.banner);
|
|
2402
|
+
}
|
|
2403
|
+
this.#loadingBarService.successAndHide();
|
|
2404
|
+
});
|
|
2405
|
+
}
|
|
2406
|
+
});
|
|
2407
|
+
// const imagePrompt = prompt('alguna idea de lo que quieres ver?');
|
|
2408
|
+
// this.#loadingBarService.showIndeterminate();
|
|
2409
|
+
// const result = await this.defaultLessonsService.generateBanner(imagePrompt, this.lessonId());
|
|
2410
|
+
// if (result) {
|
|
2411
|
+
// this.updateLessonProperty('banner', result.banner);
|
|
2412
|
+
// }
|
|
2413
|
+
// console.log('Generated banner:', result);
|
|
2414
|
+
//
|
|
2415
|
+
// this.#loadingBarService.successAndHide();
|
|
2416
|
+
}
|
|
2417
|
+
showPrompts() {
|
|
2418
|
+
this.promptsVisible = true;
|
|
2419
|
+
const promptsFn = this.#lessonService.getPrompts();
|
|
2420
|
+
this.prompts = { banner: promptsFn.banner(this.lesson()), content: promptsFn.content(this.lesson()), description: promptsFn.description(this.lesson()) };
|
|
2421
|
+
debugger;
|
|
2422
|
+
console.log(this.prompts);
|
|
2423
|
+
this.cdr.markForCheck();
|
|
2424
|
+
}
|
|
2062
2425
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2063
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: DCLessonEditorComponent, isStandalone: true, selector: "dc-lesson-editor", providers: [LessonNotionService], viewQueries: [{ propertyName: "target", first: true, predicate: ["target"], descendants: true, read: ViewContainerRef }, { propertyName: "dhtml", first: true, predicate: ["dhtml"], descendants: true, static: true }], ngImport: i0, template: "<div style=\"position: relative; margin-bottom: 20px\">\n <img class=\"header-cover\" [src]=\"coverImageUrl()\" alt=\"Lesson Cover Image\" />\n\n <dc-cropper-modal\n style=\"position: absolute; top: 10px; left: 20px\"\n [buttonLabel]=\"'Carga una portada'\"\n [imgStorageSettings]=\"coverStorageSettings\"\n (imageUploaded)=\"onImageUploaded($event)\"></dc-cropper-modal>\n</div>\n\n<br />\n\n<!-- Lesson Metadata Editor -->\n<dc-lesson-metadata-editor\n [lesson]=\"lesson\"\n [isLoadingLesson]=\"isLoadingLesson\"\n (saveRequest)=\"saveLesson()\"\n (importNotionRequest)=\"importFromNotion()\"\n (improveNotionRequest)=\"improveNotionWithAI()\"
|
|
2064
|
-
DCLessonMetadataEditorComponent, selector: "dc-lesson-metadata-editor", inputs: ["lesson", "isLoadingLesson"], outputs: ["saveRequest", "importNotionRequest", "improveNotionRequest", "
|
|
2426
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: DCLessonEditorComponent, isStandalone: true, selector: "dc-lesson-editor", providers: [LessonNotionService], viewQueries: [{ propertyName: "target", first: true, predicate: ["target"], descendants: true, read: ViewContainerRef }, { propertyName: "dhtml", first: true, predicate: ["dhtml"], descendants: true, static: true }], ngImport: i0, template: "<div style=\"position: relative; margin-bottom: 20px\">\n <img class=\"header-cover\" [src]=\"coverImageUrl()\" alt=\"Lesson Cover Image\" />\n\n <dc-cropper-modal\n style=\"position: absolute; top: 10px; left: 20px\"\n [buttonLabel]=\"'Carga una portada'\"\n [imgStorageSettings]=\"coverStorageSettings\"\n (imageUploaded)=\"onImageUploaded($event)\"></dc-cropper-modal>\n\n <p-button\n (click)=\"generateBanner()\"\n class=\"generate-banner-btn\"\n icon=\"pi pi-sparkles\"\n severity=\"primary\"\n [rounded]=\"true\"\n [raised]=\"true\"\n pTooltip=\"Generar Banner AI\"\n tooltipPosition=\"left\"></p-button>\n\n <p-button class=\"prompt-visual\" icon=\"pi pi-info\" label=\"Ver Prompts\" [link]=\"true\" (click)=\"showPrompts()\" />\n</div>\n\n<br />\n\n<!-- Lesson Metadata Editor -->\n<dc-lesson-metadata-editor\n [lesson]=\"lesson\"\n [isLoadingLesson]=\"isLoadingLesson\"\n (saveRequest)=\"saveLesson()\"\n (importNotionRequest)=\"importFromNotion()\"\n (improveNotionRequest)=\"improveNotionWithAI()\">\n</dc-lesson-metadata-editor>\n\n<hr />\n\n<!-- Component Adder -->\n<dc-lesson-component-adder (componentAdded)=\"onComponentAdded($event)\"></dc-lesson-component-adder>\n\n<!-- Display Added Components -->\n<div class=\"added-components-list\" style=\"margin-top: 15px; margin-bottom: 15px\">\n <h4>Componentes Agregados:</h4>\n @if (dynamicComponentsArray().length > 0) {\n <ul>\n @for (comp of dynamicComponentsArray(); track comp.id) {\n <li>ID: {{ comp.id }} - Tipo: {{ comp.component }}<button pButton icon=\"pi pi-info\" (click)=\"showComponentDetails(comp)\"></button></li>\n }\n </ul>\n } @else {\n <p>A\u00FAn no se han agregado componentes.</p>\n }\n</div>\n\n<hr />\n\n<!-- Text Editor and Renderer -->\n<p-splitter [style]=\"{ height: '80vh' }\">\n <ng-template pTemplate>\n <ckeditor\n (keydown.control.s)=\"saveLesson($event)\"\n class=\"text-editor\"\n [editor]=\"editor\"\n [ngModel]=\"lesson()?.textCoded\"\n (ngModelChange)=\"updateLessonProperty('textCoded', $event)\">\n </ckeditor>\n </ng-template>\n\n <ng-template pTemplate>\n <dc-lesson-renderer class=\"text-editor\" [lessonInput]=\"lesson()\" [test]=\"true\"></dc-lesson-renderer>\n </ng-template>\n</p-splitter>\n\n<div class=\"float-button\">\n <!-- Removed p-speeddial -->\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\n<div>\n <h4>Aqui estan los prompts</h4>\n</div>\n\n<p-dialog header=\"Prompts\" [modal]=\"true\" [(visible)]=\"promptsVisible\" [style]=\"{ width: '70%' }\">\n <div>\n <h1>Banner</h1>\n <p>{{ prompts?.banner }}</p>\n <h1>Contenido</h1>\n <p>{{ prompts?.content }}</p>\n <h1>Descripci\u00F3n</h1>\n <p>{{ prompts?.description }}</p>\n </div>\n</p-dialog>\n", styles: [".btn{padding:.5rem 1rem;border-radius:4px;border:1px solid transparent;cursor:pointer}.generate-banner-btn{position:absolute;right:10px;top:10px}.prompt-visual{position:absolute;left:10px;bottom:10px}.btn-primary{background-color:#007bff;color:#fff}.btn-outline-primary{border-color:#007bff;color:#007bff}.btn-secondary{background-color:#6c757d;color:#fff}.btn-outline-secondary{border-color:#6c757d;color:#6c757d}.btn-rounded{border-radius:50%}.form-control{padding:.375rem .75rem;border:1px solid #ced4da;border-radius:.25rem}.splitter{display:flex;gap:1rem}.splitter-panel{flex:1}.checkbox-container{display:inline-flex;align-items:center;gap:.5rem;cursor:pointer}.mr-2{margin-right:.5rem}.header-cover{width:100%;height:250px;object-fit:cover;position:relative;border-radius:8px}.float-button{position:fixed;bottom:3.5rem;right:2rem;z-index:1000;display:flex;gap:1px}.float-button :host ::ng-deep .p-button{width:4rem;height:4rem;border-radius:50%}.text-editor{width:-webkit-fill-available;overflow-y:auto}:host ::ng-deep .p-inputtext{background:#fff3}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i1$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i2$6.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: CKEditorModule }, { kind: "component", type: i3$1.CKEditorComponent, selector: "ckeditor", inputs: ["editor", "config", "data", "tagName", "watchdog", "editorWatchdogConfig", "disableTwoWayDataBinding", "disabled"], outputs: ["ready", "change", "blur", "focus", "error"] }, { kind: "component", type: CropperComponentModal, selector: "dc-cropper-modal", inputs: ["imgStorageSettings", "buttonLabel", "currentStorage"], outputs: ["imageUploaded", "onImageCropped", "onFileSelected"] }, { kind: "component", type: DCLessonRendererComponent, selector: "dc-lesson-renderer", inputs: ["lessonInput", "lessonIdInput", "test"] }, { 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: InputTextModule }, { kind: "ngmodule", type: SplitterModule }, { kind: "component", type: i5$2.Splitter, selector: "p-splitter", inputs: ["styleClass", "panelStyleClass", "style", "panelStyle", "stateStorage", "stateKey", "layout", "gutterSize", "step", "minSizes", "panelSizes"], outputs: ["onResizeEnd", "onResizeStart"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i2$5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "component", type: DCLessonComponentAdderComponent, selector: "dc-lesson-component-adder", outputs: ["componentAdded"] }, { kind: "component", type: // Add the component adder here
|
|
2427
|
+
DCLessonMetadataEditorComponent, selector: "dc-lesson-metadata-editor", inputs: ["lesson", "isLoadingLesson"], outputs: ["saveRequest", "importNotionRequest", "improveNotionRequest"] }, { kind: "ngmodule", type: // Add the metadata editor here
|
|
2428
|
+
DialogModule }, { kind: "component", type: i7.Dialog, selector: "p-dialog", inputs: ["header", "draggable", "resizable", "positionLeft", "positionTop", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "responsive", "appendTo", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "breakpoint", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }] }); }
|
|
2065
2429
|
}
|
|
2066
2430
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonEditorComponent, decorators: [{
|
|
2067
2431
|
type: Component,
|
|
@@ -2076,7 +2440,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
|
|
|
2076
2440
|
TooltipModule,
|
|
2077
2441
|
DCLessonComponentAdderComponent, // Add the component adder here
|
|
2078
2442
|
DCLessonMetadataEditorComponent, // Add the metadata editor here
|
|
2079
|
-
|
|
2443
|
+
DialogModule,
|
|
2444
|
+
], providers: [LessonNotionService], template: "<div style=\"position: relative; margin-bottom: 20px\">\n <img class=\"header-cover\" [src]=\"coverImageUrl()\" alt=\"Lesson Cover Image\" />\n\n <dc-cropper-modal\n style=\"position: absolute; top: 10px; left: 20px\"\n [buttonLabel]=\"'Carga una portada'\"\n [imgStorageSettings]=\"coverStorageSettings\"\n (imageUploaded)=\"onImageUploaded($event)\"></dc-cropper-modal>\n\n <p-button\n (click)=\"generateBanner()\"\n class=\"generate-banner-btn\"\n icon=\"pi pi-sparkles\"\n severity=\"primary\"\n [rounded]=\"true\"\n [raised]=\"true\"\n pTooltip=\"Generar Banner AI\"\n tooltipPosition=\"left\"></p-button>\n\n <p-button class=\"prompt-visual\" icon=\"pi pi-info\" label=\"Ver Prompts\" [link]=\"true\" (click)=\"showPrompts()\" />\n</div>\n\n<br />\n\n<!-- Lesson Metadata Editor -->\n<dc-lesson-metadata-editor\n [lesson]=\"lesson\"\n [isLoadingLesson]=\"isLoadingLesson\"\n (saveRequest)=\"saveLesson()\"\n (importNotionRequest)=\"importFromNotion()\"\n (improveNotionRequest)=\"improveNotionWithAI()\">\n</dc-lesson-metadata-editor>\n\n<hr />\n\n<!-- Component Adder -->\n<dc-lesson-component-adder (componentAdded)=\"onComponentAdded($event)\"></dc-lesson-component-adder>\n\n<!-- Display Added Components -->\n<div class=\"added-components-list\" style=\"margin-top: 15px; margin-bottom: 15px\">\n <h4>Componentes Agregados:</h4>\n @if (dynamicComponentsArray().length > 0) {\n <ul>\n @for (comp of dynamicComponentsArray(); track comp.id) {\n <li>ID: {{ comp.id }} - Tipo: {{ comp.component }}<button pButton icon=\"pi pi-info\" (click)=\"showComponentDetails(comp)\"></button></li>\n }\n </ul>\n } @else {\n <p>A\u00FAn no se han agregado componentes.</p>\n }\n</div>\n\n<hr />\n\n<!-- Text Editor and Renderer -->\n<p-splitter [style]=\"{ height: '80vh' }\">\n <ng-template pTemplate>\n <ckeditor\n (keydown.control.s)=\"saveLesson($event)\"\n class=\"text-editor\"\n [editor]=\"editor\"\n [ngModel]=\"lesson()?.textCoded\"\n (ngModelChange)=\"updateLessonProperty('textCoded', $event)\">\n </ckeditor>\n </ng-template>\n\n <ng-template pTemplate>\n <dc-lesson-renderer class=\"text-editor\" [lessonInput]=\"lesson()\" [test]=\"true\"></dc-lesson-renderer>\n </ng-template>\n</p-splitter>\n\n<div class=\"float-button\">\n <!-- Removed p-speeddial -->\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\n<div>\n <h4>Aqui estan los prompts</h4>\n</div>\n\n<p-dialog header=\"Prompts\" [modal]=\"true\" [(visible)]=\"promptsVisible\" [style]=\"{ width: '70%' }\">\n <div>\n <h1>Banner</h1>\n <p>{{ prompts?.banner }}</p>\n <h1>Contenido</h1>\n <p>{{ prompts?.content }}</p>\n <h1>Descripci\u00F3n</h1>\n <p>{{ prompts?.description }}</p>\n </div>\n</p-dialog>\n", styles: [".btn{padding:.5rem 1rem;border-radius:4px;border:1px solid transparent;cursor:pointer}.generate-banner-btn{position:absolute;right:10px;top:10px}.prompt-visual{position:absolute;left:10px;bottom:10px}.btn-primary{background-color:#007bff;color:#fff}.btn-outline-primary{border-color:#007bff;color:#007bff}.btn-secondary{background-color:#6c757d;color:#fff}.btn-outline-secondary{border-color:#6c757d;color:#6c757d}.btn-rounded{border-radius:50%}.form-control{padding:.375rem .75rem;border:1px solid #ced4da;border-radius:.25rem}.splitter{display:flex;gap:1rem}.splitter-panel{flex:1}.checkbox-container{display:inline-flex;align-items:center;gap:.5rem;cursor:pointer}.mr-2{margin-right:.5rem}.header-cover{width:100%;height:250px;object-fit:cover;position:relative;border-radius:8px}.float-button{position:fixed;bottom:3.5rem;right:2rem;z-index:1000;display:flex;gap:1px}.float-button :host ::ng-deep .p-button{width:4rem;height:4rem;border-radius:50%}.text-editor{width:-webkit-fill-available;overflow-y:auto}:host ::ng-deep .p-inputtext{background:#fff3}\n"] }]
|
|
2080
2445
|
}], ctorParameters: () => [], propDecorators: { target: [{
|
|
2081
2446
|
type: ViewChild,
|
|
2082
2447
|
args: ['target', { read: ViewContainerRef }]
|
|
@@ -2112,5 +2477,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
|
|
|
2112
2477
|
* Generated bundle index. Do not edit.
|
|
2113
2478
|
*/
|
|
2114
2479
|
|
|
2115
|
-
export {
|
|
2480
|
+
export { ComponentBuilder, ComponentWithForm, DCLessonEditorComponent, DCLessonFormComponent, DCLessonListComponent, DCLessonRendererComponent, DcLessonCardComponent, DefaultLessonsService, FlagLanguagePipe, LESSONS_TOKEN, LangCodeDescription, LangCodeDescriptionEs, LangDescTranslationPipe, LessonComponentBuilders, LessonComponentEnum, LessonComponents, LessonDynamicComponent, LessonsAbstractService, NOTION_SERVICE_TOKEN, NotionAbstractService, NotionExportType, SelectorBuilderComponent, SelectorComponent, TextWriterBuiderComponent, TextWriterComponent, TranslationSwitcherBuilderComponent, TranslationSwitcherComponent, getLanguageSimpleAgent, getLessonComponentClass, provideLessonsService, provideNotionService };
|
|
2116
2481
|
//# sourceMappingURL=dataclouder-ngx-lessons.mjs.map
|