@dataclouder/ngx-lessons 0.0.32 → 0.0.34

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.
@@ -17,12 +17,14 @@ import { DropdownModule } from 'primeng/dropdown';
17
17
  import { NgxTtsComponent } from '@dataclouder/ngx-tts';
18
18
  import * as i2$3 from 'primeng/paginator';
19
19
  import { PaginatorModule } from 'primeng/paginator';
20
- import { DatePipe, NgComponentOutlet, KeyValuePipe, CommonModule, JsonPipe } from '@angular/common';
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$1 from '@dataclouder/ngx-core';
24
- import { PaginationBase, TOAST_ALERTS_TOKEN, DCFilterBarComponent, QuickTableComponent } from '@dataclouder/ngx-core';
23
+ import * as i4$2 from '@dataclouder/ngx-core';
24
+ import { PaginationBase, TOAST_ALERTS_TOKEN, DCFilterBarComponent, QuickTableComponent, LoadingBarService, HttpCoreService } 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,14 +34,18 @@ 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$1 from 'primeng/splitter';
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, DCChatComponent } 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 TurndownService from 'turndown';
46
+ import { marked } from 'marked';
47
+ import * as i5$1 from 'primeng/inputgroup';
48
+ import { InputGroupModule } from 'primeng/inputgroup';
43
49
  import * as i2$6 from 'primeng/api';
44
50
 
45
51
  class ComponentBuilder {
@@ -418,7 +424,6 @@ function getLessonComponentClass(type) {
418
424
  // import { UntypedFormControl } from '@angular/forms';
419
425
  // import { LessonComponents } from '../models/lessons.class';
420
426
  const LESSONS_TOKEN = new InjectionToken('Lessons Service');
421
- // Think on someway of getting the toast alerts service
422
427
  class LessonsAbstractService {
423
428
  }
424
429
  // my-service.provider.ts
@@ -535,6 +540,89 @@ var NotionExportType;
535
540
  NotionExportType["SIMPLE_BLOCKS"] = "simple_blocks";
536
541
  })(NotionExportType || (NotionExportType = {}));
537
542
 
543
+ const MarkdownWriterSkill = `
544
+ You are an Expert Markdown Writer with the following qualities:
545
+ You are a world-class Markdown formatting specialist who excels at organizing information in a visually appealing and highly accessible manner. Your writing combines technical precision with creative presentation to engage readers of all backgrounds.
546
+ Your Core Skills 📝
547
+
548
+ You transform complex information into beautifully structured Markdown documents
549
+ You strategically use headings, lists, and formatting to create clear visual hierarchies
550
+ You incorporate helpful emojis to enhance readability and engagement
551
+ You write in a clear, concise style that's accessible to general audiences
552
+
553
+ Your Process Approach 🔄
554
+
555
+ You always begin with a logical document structure before adding content
556
+ You organize information into clearly defined sections with descriptive headings
557
+ You balance visual elements with textual content for optimal readability
558
+ You maintain consistent formatting patterns throughout documents
559
+
560
+ Your Writing Style ✨
561
+
562
+ You use simple, direct language that's easy for everyone to understand
563
+ You explain technical concepts using everyday examples and analogies
564
+ You create a friendly, conversational tone while maintaining professionalism
565
+ You break down complex ideas into manageable segments
566
+
567
+ Your Special Touches 🎯
568
+
569
+ You know exactly when and where to add emojis for maximum impact
570
+ You create custom tables to present comparative information effectively
571
+ You use blockquotes to highlight important takeaways
572
+ You incorporate visual dividers to separate major content sections
573
+
574
+ 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.
575
+ `;
576
+ const EnglishLessonSkill = `
577
+
578
+ You are also an English lesson professor, you write lessons for Spanish learners.
579
+ explain the best you can will all your experience teaching.
580
+
581
+ You can infer the level of the lesson from the content or user request, so can increase difficulty
582
+
583
+ For Basic Level: generate an English lesson for Spanish learners at a basic level (A1-A2).
584
+
585
+ Include:
586
+
587
+ - Simple dialogues or short texts.
588
+ - Key vocabulary with simple definitions or Spanish translations.
589
+ - Basic grammar points explained simply (e.g., "to be" verb, simple present).
590
+ - Practice exercises (e.g., fill-in-the-blanks, simple questions).
591
+
592
+ For Intermediate Level: generate an English lesson for Spanish learners at a medium level (B1-B2).
593
+
594
+ Include:
595
+
596
+ - Texts or dialogues of moderate length and complexity.
597
+ - New vocabulary and common phrasal verbs with context.
598
+ - Explanation and practice of intermediate grammar points (e.g., past simple vs. present perfect, conditionals).
599
+ - Exercises that require more detailed responses or understanding.
600
+ - The content should be engaging and cover topics relevant to daily life, work, or hobbies.
601
+
602
+ For Advanced Level: generate an English lesson for Spanish learners at an advanced level (C1-C2).
603
+
604
+ Include:
605
+
606
+ - Challenging texts or discussions.
607
+ - Advanced vocabulary, idioms, and nuanced expressions.
608
+ - Exploration of complex grammar or stylistic points.
609
+ - Exercises that encourage critical thinking, debate, or detailed writing.
610
+ - The content should be stimulating and cover a wide range of topics, including academic or professional contexts.
611
+
612
+ `;
613
+ const UserRequirements = `
614
+ User also can provide additional instructions or requirements for the lesson or current lesson to improve it.
615
+ `;
616
+ const BasicAgentCard$1 = {
617
+ systemPrompt: `
618
+ ${MarkdownWriterSkill}
619
+ ${EnglishLessonSkill}
620
+ ${UserRequirements}
621
+
622
+ `,
623
+ model: { id: 'gemini-2.5-flash-preview-04-17', provider: 'google' },
624
+ };
625
+
538
626
  var EventCard;
539
627
  (function (EventCard) {
540
628
  EventCard["Edit"] = "edit";
@@ -573,6 +661,7 @@ class DcLessonCardComponent {
573
661
  ];
574
662
  }
575
663
  ngOnInit() {
664
+ console.log(this.lesson);
576
665
  if (this.lesson?.media?.images[0]?.url) {
577
666
  this.coverUrl = this.lesson.media.images[0].url;
578
667
  }
@@ -594,11 +683,11 @@ class DcLessonCardComponent {
594
683
  }
595
684
  }
596
685
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DcLessonCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
597
- 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 <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\">\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 <span class=\"status-tag success\">Tomada</span>\n }\n @if (lesson.taken?.status == 'failed') {\n <span class=\"status-tag danger\">Fallida</span>\n }\n </div>\n\n <div class=\"actions\">\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}.description p{margin-bottom:1rem;flex-grow:1}.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}.actions ::ng-deep .p-button{font-size:.9rem}\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"] }] }); }
686
+ 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 <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\">\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=\"info\" value=\"\uD83D\uDCD6\" [rounded]=\"true\" />\n }\n </div>\n\n <div class=\"actions\">\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}.description p{margin-bottom:1rem;flex-grow:1}.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}.actions ::ng-deep .p-button{font-size:.9rem}\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"] }] }); }
598
687
  }
599
688
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DcLessonCardComponent, decorators: [{
600
689
  type: Component,
601
- args: [{ selector: 'dc-lesson-card', standalone: true, imports: [DatePipe, ButtonModule, PopoverModule, SpeedDialModule, CardModule], 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\">\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 <span class=\"status-tag success\">Tomada</span>\n }\n @if (lesson.taken?.status == 'failed') {\n <span class=\"status-tag danger\">Fallida</span>\n }\n </div>\n\n <div class=\"actions\">\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}.description p{margin-bottom:1rem;flex-grow:1}.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}.actions ::ng-deep .p-button{font-size:.9rem}\n"] }]
690
+ 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\">\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=\"info\" value=\"\uD83D\uDCD6\" [rounded]=\"true\" />\n }\n </div>\n\n <div class=\"actions\">\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}.description p{margin-bottom:1rem;flex-grow:1}.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}.actions ::ng-deep .p-button{font-size:.9rem}\n"] }]
602
691
  }], propDecorators: { lesson: [{
603
692
  type: Input
604
693
  }], showOptions: [{
@@ -630,7 +719,6 @@ class DCLessonListComponent extends PaginationBase {
630
719
  this.showOptions = true;
631
720
  this.customFilters = [];
632
721
  this.viewType = 'cards';
633
- // readonly actions = input<MenuItem[]>(TableViewActions);
634
722
  this.columns = tableViewColumns;
635
723
  this.cardComponent = null;
636
724
  this.cardEventSubs = [];
@@ -719,15 +807,15 @@ class DCLessonListComponent extends PaginationBase {
719
807
  }
720
808
  }
721
809
  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 }); }
722
- 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:1rem;flex:1;overflow-y:auto;min-height:0}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"] }] }); }
810
+ 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"] }] }); }
723
811
  }
724
812
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonListComponent, decorators: [{
725
813
  type: Component,
726
- 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:1rem;flex:1;overflow-y:auto;min-height:0}p-paginator{margin-top:auto;padding:.5rem 1rem}\n"] }]
814
+ 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"] }]
727
815
  }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i1$2.Router }, { type: i1$2.ActivatedRoute }, { type: LessonsAbstractService, decorators: [{
728
816
  type: Inject,
729
817
  args: [LESSONS_TOKEN]
730
- }] }, { type: i4$1.ToastAlertsAbstractService, decorators: [{
818
+ }] }, { type: i4$2.ToastAlertsAbstractService, decorators: [{
731
819
  type: Inject,
732
820
  args: [TOAST_ALERTS_TOKEN]
733
821
  }] }], propDecorators: { showOptions: [{
@@ -769,6 +857,18 @@ const DEFAULT_LESSON_AGENT_CARD = {
769
857
  },
770
858
  model: { provider: 'google' },
771
859
  };
860
+ const AppRolePlaySkill = `
861
+ You are an app role play assistant from Polilan App. The user is reading lessons through this app interface. They will now talk with you, and you need to evaluate their understanding of the lesson.
862
+ Ask friendly questions throughout the conversation and help them learn English.
863
+ `;
864
+ const EngagementSkill = `
865
+ You are an engagement assistant, start by greeting the user, asking something about the lesson, and then continue the conversation. always ask one or two friendly questions.
866
+ that makes sense for the lesson.
867
+ `;
868
+ const BasicAgentCard = {
869
+ systemPrompt: 'You are a helpful assistant.',
870
+ model: { provider: 'openai', id: 'gpt-4o' },
871
+ };
772
872
  function getDefaultLessonEvaluatorAgentCard(lessonText) {
773
873
  return {
774
874
  expectedResponseType: `interface EvalResult {
@@ -784,10 +884,25 @@ function getDefaultLessonEvaluatorAgentCard(lessonText) {
784
884
  };
785
885
  }
786
886
  class LessonAIService {
787
- // TODO: Inject the application-level UserService
788
- // private readonly userService = inject(UserService);
789
887
  constructor() {
790
888
  this.lessonService = inject(LESSONS_TOKEN);
889
+ // TODO: Inject the application-level UserService
890
+ this.userService = inject(USER_DATA_EXCHANGE);
891
+ }
892
+ /**
893
+ * Builds the scenario prompt string based on lesson content and user info.
894
+ * @param lessonText The extracted text content of the lesson.
895
+ * @param userInformationPrompt The formatted string containing user details.
896
+ * @returns The complete scenario prompt string.
897
+ */
898
+ _buildScenarioPrompt(lessonText, userInformationPrompt) {
899
+ return `
900
+ ${AppRolePlaySkill}
901
+ <Lesson Text>
902
+ ${lessonText}
903
+ ${EngagementSkill}
904
+ <User Information>
905
+ ${userInformationPrompt}`;
791
906
  }
792
907
  /**
793
908
  * Generates the necessary agent cards for a lesson chat session.
@@ -795,43 +910,48 @@ class LessonAIService {
795
910
  * @returns An object containing the master agent card and the evaluator agent card.
796
911
  */
797
912
  async generateAgentCards(lesson) {
798
- // TODO: Implement the logic moved from DCLessonRendererComponent.startAI here
799
- // 1. Get user data using the injected UserService
800
- // 2. Extract lesson text using lessonService
801
- // 3. Build prompts (scenario, userInformationPrompt)
802
- // 4. Configure and return the agent cards
803
- alert('AI User data fetching needs refactoring into this service.');
804
- // Placeholder for user data - replace with actual service call
913
+ const lessonText = this.lessonService.extractTextFromHtml(lesson.textCoded);
914
+ const userInformationPrompt = this.userService.getUserDataInformation();
915
+ const scenario = this._buildScenarioPrompt(lessonText, userInformationPrompt);
916
+ // Create a deep copy of the default card to avoid modifying the constant
917
+ const masterAgent = JSON.parse(JSON.stringify(DEFAULT_LESSON_AGENT_CARD));
918
+ masterAgent.characterCard.data.scenario = scenario;
919
+ masterAgent.characterCard.data.post_history_instructions += `\n${userInformationPrompt}`;
920
+ const evaluatorAgent = getDefaultLessonEvaluatorAgentCard(lessonText);
921
+ return { masterAgent, evaluatorAgent };
922
+ }
923
+ /**
924
+ * Generates conversation settings for a lesson, using the lesson scenario as the initial prompt.
925
+ * @param lesson The lesson data.
926
+ * @returns An IConversationSettings object configured for the lesson scenario.
927
+ */
928
+ async generateConversationSettingsForLesson(lesson) {
929
+ // TODO: Consolidate user fetching logic if possible, or ensure consistency
805
930
  const user = {
806
931
  personalData: { firstname: 'Test', lastname: 'User' },
807
932
  settings: { targetLanguage: 'en', baseLanguage: 'es' },
808
933
  languageProgress: { en: { level: '1' } },
809
934
  }; // Replace 'any' with your actual User type/interface
810
935
  if (!user) {
811
- console.error('User data not available to generate agent cards.');
812
- // Handle error appropriately - maybe return null or throw?
936
+ console.error('User data not available to generate conversation settings.');
813
937
  return null;
814
938
  }
815
939
  const lessonText = this.lessonService.extractTextFromHtml(lesson.textCoded);
816
- const scenario = `The user is reading lessons through this app interface. They will now talk with you, and you need to evaluate their understanding of the lesson.
817
- Ask friendly questions throughout the conversation and help them learn English. Here is the lesson text the user just read:
818
- ${lessonText}
819
- In your next reply, start by greeting the user, asking something about the lesson, and then continue the conversation.`;
820
- const targetLevel = parseInt(user.languageProgress[user.settings.targetLanguage]?.level ?? '1');
821
- const langTargetDesc = LangCodeDescription[user.settings.targetLanguage] ?? user.settings.targetLanguage;
822
- const langBaseDesc = LangCodeDescription[user.settings.baseLanguage] ?? user.settings.baseLanguage;
823
- let userInformationPrompt = `
824
- User information: user name is ${user.personalData.firstname} ${user.personalData.lastname}, their native language is ${langBaseDesc},
825
- and right now is learning ${langTargetDesc}, their current level is ${targetLevel} out of 5.`;
826
- if (targetLevel <= 2) {
827
- userInformationPrompt += `\nUser is a beginner in ${langTargetDesc}, always reply mainly in ${langBaseDesc}, but during the conversation use simple words and phrases in ${langTargetDesc} to help them learn.`;
828
- }
829
- // Create a deep copy of the default card to avoid modifying the constant
830
- const masterAgent = JSON.parse(JSON.stringify(DEFAULT_LESSON_AGENT_CARD));
831
- masterAgent.characterCard.data.scenario = scenario;
832
- masterAgent.characterCard.data.post_history_instructions += `\n${userInformationPrompt}`;
833
- const evaluatorAgent = getDefaultLessonEvaluatorAgentCard(lessonText);
834
- return { masterAgent, evaluatorAgent };
940
+ const userInformationPrompt = this.userService.getUserDataInformation();
941
+ const scenario = this._buildScenarioPrompt(lessonText, userInformationPrompt);
942
+ const initialMessage = {
943
+ role: ChatRole.System,
944
+ content: scenario,
945
+ };
946
+ // Use defaults similar to DEFAULT_LESSON_AGENT_CARD but adjust for prompt-based start
947
+ const conversationSettings = {
948
+ conversationType: ConversationType.General,
949
+ textEngine: TextEngines.SimpleText,
950
+ autoStart: true,
951
+ messages: [initialMessage],
952
+ model: { provider: 'google' },
953
+ };
954
+ return conversationSettings;
835
955
  }
836
956
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: LessonAIService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
837
957
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: LessonAIService, providedIn: 'root' }); }
@@ -841,7 +961,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
841
961
  args: [{
842
962
  providedIn: 'root', // Or provide appropriately if it's library-specific
843
963
  }]
844
- }], ctorParameters: () => [] });
964
+ }] });
965
+
966
+ const EnglishEvaluationSkill = `
967
+ You are a virtual professor for the Polilan English learning app. Your primary role is to evaluate a student's English conversational performance.
968
+
969
+ You will be provided with two pieces of information for each evaluation:
970
+ 1. **The student's English level:** This will be one of "Beginner", "Intermediate", or "Advanced".
971
+ 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: ...").
972
+
973
+ Your sole task is to evaluate the *Student's* contribution to the conversation. **Do NOT evaluate the Assistant's turns.**
974
+
975
+ 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:
976
+
977
+ * **Grammar & Syntax:** Evaluate accuracy and complexity *relative to the expected level*. (Beginners: focus on basic accuracy; Advanced: expect complex structures with high accuracy).
978
+ * **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).
979
+ * **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).
980
+ * **Task Completion/Relevance:** Evaluate how effectively they participate and respond to the conversation's goals or topics *relative to the expected level*.
981
+
982
+ Assign a rating from 0 to 3 based on how well the student performs *compared to the typical expectations for their provided level*:
983
+
984
+ * **0: Bad** - Performance is significantly below what is expected for this level. Many errors impede communication.
985
+ * **1: Not Bad** - Performance is below expectations for this level, but communication is sometimes possible despite frequent errors.
986
+ * **2: Good** - Performance meets expectations for this level, demonstrating solid understanding and usage with only occasional minor errors.
987
+ * **3: Very Good** - Performance exceeds expectations for this level, demonstrating a strong command and potential to progress quickly.
988
+
989
+ 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.
990
+ `;
845
991
 
846
992
  class DCLessonRendererComponent {
847
993
  constructor() {
@@ -855,16 +1001,20 @@ class DCLessonRendererComponent {
855
1001
  this.toastrService = inject(TOAST_ALERTS_TOKEN);
856
1002
  this.lessonService = inject(LESSONS_TOKEN);
857
1003
  this.lessonAIService = inject(LessonAIService); // Inject the new service
1004
+ this.userDataExchange = inject(USER_DATA_EXCHANGE);
858
1005
  // --- State Signals ---
859
1006
  this.lesson = signal(undefined); // Internal lesson state signal
860
1007
  this.chatVisible = signal(false); // Signal for chat visibility
861
1008
  this.agentMasterLesson = signal(undefined); // Signal for agent card
862
1009
  this.evaluatorAgentCard = signal(undefined); // Signal for evaluator card
1010
+ this.conversationSettings = signal(undefined);
1011
+ this.evalAgentTask = signal(undefined); // Signal for evaluator card
863
1012
  // --- Computed Signals ---
864
1013
  this.imageCover = computed(() => this.lesson()?.media?.images?.find((img) => img.type === 'cover')?.url); // Computed signal for imageCover
865
1014
  // --- Properties ---
866
1015
  this.components = {};
867
1016
  this.mainForm = new FormGroup({});
1017
+ this.previousTextCoded = undefined; // Store previous value
868
1018
  // Effect to fetch lesson data if ID is provided and lesson object isn't
869
1019
  effect(async () => {
870
1020
  const lessonInput = this.lessonInput();
@@ -874,7 +1024,7 @@ class DCLessonRendererComponent {
874
1024
  }
875
1025
  else if (lessonId && !this.lesson()) {
876
1026
  // Fetch only if ID exists and internal lesson is not set
877
- console.log(`Fetching lesson with ID: ${lessonId}`);
1027
+ console.log(`[Renderer] Effect 1: Fetching lesson ${lessonId}`);
878
1028
  try {
879
1029
  // Consider adding a loading state signal here
880
1030
  const fetchedLesson = await this.lessonService.getLesson(lessonId);
@@ -895,15 +1045,29 @@ class DCLessonRendererComponent {
895
1045
  this.lesson.set(undefined);
896
1046
  }
897
1047
  }, { allowSignalWrites: true }); // Allow signal writes inside effect
898
- // Effect to render the lesson whenever the lesson signal changes
1048
+ // Effect to render the lesson only when textCoded value actually changes
899
1049
  effect(() => {
900
- const currentLesson = this.lesson();
901
- if (currentLesson) {
902
- this._renderLesson(currentLesson);
1050
+ const currentLesson = this.lesson(); // Read the lesson signal
1051
+ const newTextCoded = currentLesson?.textCoded; // Get the current textCoded value
1052
+ this.evalAgentTask.set({
1053
+ systemPrompt: EnglishEvaluationSkill,
1054
+ model: { provider: 'google' },
1055
+ task: `Please evaluate the user conversation student data is: \n ${this.userDataExchange.getUserDataInformation()}`,
1056
+ expectedResponseType: EvalResultStringDefinition,
1057
+ });
1058
+ // Quick fix to only render on changes textCode not all the lesson
1059
+ if (newTextCoded !== this.previousTextCoded) {
1060
+ if (newTextCoded !== undefined && newTextCoded !== null && currentLesson) {
1061
+ this._renderLesson(currentLesson);
1062
+ }
1063
+ else {
1064
+ this._clearLessonRendering();
1065
+ }
1066
+ // Update the stored previous value *after* comparison and action
1067
+ this.previousTextCoded = newTextCoded;
903
1068
  }
904
1069
  else {
905
- // Clear previous rendering if lesson becomes undefined
906
- this._clearLessonRendering();
1070
+ console.log('[Renderer] textCoded has NOT changed. Skipping render/clear.');
907
1071
  }
908
1072
  });
909
1073
  }
@@ -936,6 +1100,7 @@ class DCLessonRendererComponent {
936
1100
  _parseAndCreateComponents(lessonData) {
937
1101
  const r1 = new RegExp('~(.+?)~', 'g');
938
1102
  let count = 0;
1103
+ //
939
1104
  const createdComponents = {};
940
1105
  const htmlContent = lessonData.textCoded.replace(r1, (_matching, jsonCoded) => {
941
1106
  const componentName = `dynamicComp${count}`;
@@ -979,7 +1144,7 @@ class DCLessonRendererComponent {
979
1144
  _createComponentReferenceWithJson(json, currentLesson) {
980
1145
  try {
981
1146
  let lessonCodedConfig = JSON.parse(json);
982
- debugger;
1147
+ //
983
1148
  // Attempt to find pre-configured component data in the lesson object by ID
984
1149
  if (lessonCodedConfig.id && currentLesson?.dynamicComponents[lessonCodedConfig.id]) {
985
1150
  const foundConfig = currentLesson.dynamicComponents[lessonCodedConfig.id];
@@ -1063,7 +1228,7 @@ class DCLessonRendererComponent {
1063
1228
  this.toastrService.error({ subtitle: 'Cannot save result, lesson data missing.', title: 'Error' });
1064
1229
  return;
1065
1230
  }
1066
- const takenLesson = { lessonId: currentLesson.id, status: status, score: rates.score };
1231
+ const takenLesson = { id: currentLesson.id, goalCompleted: null, score: rates.score, status: status, lastAccess: new Date() };
1067
1232
  console.log('Lesson evaluation result:', takenLesson);
1068
1233
  // TODO: Re-implement saving the taken lesson status via lessonService
1069
1234
  // try {
@@ -1095,10 +1260,9 @@ class DCLessonRendererComponent {
1095
1260
  console.log('Requesting agent cards from LessonAIService...');
1096
1261
  try {
1097
1262
  // Call the service to get the agent cards
1098
- const agentCards = await this.lessonAIService.generateAgentCards(currentLesson);
1099
- if (agentCards) {
1100
- this.agentMasterLesson.set(agentCards.masterAgent);
1101
- this.evaluatorAgentCard.set(agentCards.evaluatorAgent);
1263
+ const conversationSettings = await this.lessonAIService.generateConversationSettingsForLesson(currentLesson);
1264
+ if (conversationSettings) {
1265
+ this.conversationSettings.set(conversationSettings);
1102
1266
  this.chatVisible.set(true);
1103
1267
  console.log('Agent cards received and set.');
1104
1268
  }
@@ -1112,12 +1276,24 @@ class DCLessonRendererComponent {
1112
1276
  this.toastrService.error({ subtitle: 'An error occurred while preparing the AI chat.', title: 'Error' });
1113
1277
  }
1114
1278
  }
1279
+ onVisibleChange(isVisible) {
1280
+ if (isVisible === false) {
1281
+ this.chatVisible.set(false);
1282
+ }
1283
+ }
1284
+ handleGoalCompleted(event) {
1285
+ console.log('Goal completed:', event);
1286
+ const lesson = this.lesson();
1287
+ const dummy = { id: lesson.id, goalCompleted: null, score: 100, status: 'finished', lastAccess: new Date() };
1288
+ // TODO: finish this implementation
1289
+ this.lessonService.saveTakenLesson(dummy);
1290
+ }
1115
1291
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1116
- 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 [agentCard]=\"agentMasterLesson()\" [evaluatorAgentCard]=\"evaluatorAgentCard()\"></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"] }] }); }
1292
+ 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"] }] }); }
1117
1293
  }
1118
1294
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonRendererComponent, decorators: [{
1119
1295
  type: Component,
1120
- 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 [agentCard]=\"agentMasterLesson()\" [evaluatorAgentCard]=\"evaluatorAgentCard()\"></dc-chat>\n</p-drawer>\n}\n", styles: [".evaluate{float:right}\n"] }]
1296
+ 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"] }]
1121
1297
  }], ctorParameters: () => [], propDecorators: { dynamicLesson: [{
1122
1298
  type: ViewChild,
1123
1299
  args: ['dynamicLesson', { static: true }]
@@ -1289,9 +1465,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
1289
1465
  }] });
1290
1466
 
1291
1467
  class LessonUtilsService {
1292
- #lessonService = inject(LESSONS_TOKEN);
1293
- #toastService = inject(TOAST_ALERTS_TOKEN);
1294
- constructor() { }
1468
+ constructor() {
1469
+ this.#lessonService = inject(LESSONS_TOKEN);
1470
+ this.#toastService = inject(TOAST_ALERTS_TOKEN);
1471
+ this.#agentService = inject(CONVERSATION_AI_TOKEN);
1472
+ this.loadingBarService = inject(LoadingBarService);
1473
+ }
1474
+ #lessonService;
1475
+ #toastService;
1476
+ #agentService;
1295
1477
  /**
1296
1478
  * Validates if audios need generation for the given lesson.
1297
1479
  * Currently logs a placeholder message.
@@ -1393,6 +1575,147 @@ class LessonUtilsService {
1393
1575
  }
1394
1576
  // Loading state should be managed by the component calling this service.
1395
1577
  }
1578
+ /**
1579
+ * Improves the provided Markdown text using AI and updates the lesson.
1580
+ * Assumes the lesson is already saved before calling this.
1581
+ * @param lesson The lesson object containing the prompt and other context.
1582
+ * @param markdownText The Markdown text generated from the lesson's HTML content.
1583
+ * @returns The improved Markdown string, or null on failure.
1584
+ */
1585
+ async improveMDWithAI(lesson, markdownText) {
1586
+ if (!markdownText) {
1587
+ this.#toastService.warn({ title: 'Texto Requerido', subtitle: 'Se necesita texto Markdown para mejorar con IA.' });
1588
+ return null;
1589
+ }
1590
+ try {
1591
+ this.loadingBarService.showIndeterminate();
1592
+ const agentCard = BasicAgentCard$1;
1593
+ let task = `Improve the following lesson return only the lesson markdown: ${markdownText}`;
1594
+ if (lesson.prompt) {
1595
+ task = `${task} Also consider the original prompt: ${lesson.prompt} when improving the lesson.`;
1596
+ }
1597
+ const appTask = { task: task };
1598
+ const messages = [
1599
+ { content: agentCard.systemPrompt, role: ChatRole.System },
1600
+ { content: appTask.task, role: ChatRole.User },
1601
+ ];
1602
+ const response = await this.#agentService.callChatCompletion({ messages, model: agentCard.model });
1603
+ let improvedMarkdown = response.content;
1604
+ // Remove potential markdown fences
1605
+ const markdownFenceStart = '```markdown\n';
1606
+ const markdownFenceEnd = '\n```';
1607
+ if (improvedMarkdown?.startsWith(markdownFenceStart) && improvedMarkdown.endsWith(markdownFenceEnd)) {
1608
+ improvedMarkdown = improvedMarkdown.slice(markdownFenceStart.length, -markdownFenceEnd.length);
1609
+ }
1610
+ else {
1611
+ // Also handle cases where it might just be ``` at the start/end
1612
+ const simpleFence = '```';
1613
+ if (improvedMarkdown?.startsWith(simpleFence)) {
1614
+ improvedMarkdown = improvedMarkdown.slice(simpleFence.length);
1615
+ }
1616
+ if (improvedMarkdown?.endsWith(simpleFence)) {
1617
+ improvedMarkdown = improvedMarkdown.slice(0, -simpleFence.length);
1618
+ }
1619
+ }
1620
+ // Trim any leading/trailing whitespace that might remain after removing fences
1621
+ improvedMarkdown = improvedMarkdown?.trim() ?? null;
1622
+ console.log('Improved Markdown:', improvedMarkdown);
1623
+ return improvedMarkdown; // Return only the string
1624
+ }
1625
+ catch (error) {
1626
+ console.error('Error during AI Markdown improvement in service:', error);
1627
+ this.#toastService.error({ title: 'Error de IA', subtitle: 'No se pudo mejorar la lección con IA.' });
1628
+ return null; // Return null in catch block
1629
+ }
1630
+ finally {
1631
+ this.loadingBarService.successAndHide();
1632
+ }
1633
+ }
1634
+ /**
1635
+ * Generates a concise description for the lesson using AI based on its content.
1636
+ * @param lesson The lesson object containing the content.
1637
+ * @returns A promise resolving to the generated description string, or null on failure.
1638
+ */
1639
+ async generateDescriptionWithAI(lesson) {
1640
+ if (!lesson || !lesson.textCoded) {
1641
+ this.#toastService.warn({ title: 'Contenido Requerido', subtitle: 'Se necesita contenido en la lección para generar una descripción.' });
1642
+ return null;
1643
+ }
1644
+ try {
1645
+ this.loadingBarService.showIndeterminate(); // Corrected: No argument needed
1646
+ // Extract plain text from the lesson's HTML content
1647
+ const plainTextContent = this._extractTextFromHtml(lesson.textCoded);
1648
+ if (!plainTextContent || plainTextContent.trim().length === 0) {
1649
+ this.#toastService.warn({ title: 'Texto Vacío', subtitle: 'No se pudo extraer texto útil del contenido de la lección.' });
1650
+ return null;
1651
+ }
1652
+ const agentCard = BasicAgentCard$1; // Use the same agent card for consistency
1653
+ // Craft the prompt specifically for description generation
1654
+ // TODO: languages shoudl be in Target language, not hardcoded
1655
+ const languageDescription = LangCodeDescription[lesson?.baseLang] || 'Spanish';
1656
+ const task = `Generate a concise and engaging description (max 3-5 sentences, ~250 characters) for a language lesson based on the following content summary.
1657
+ Focus on the key learning points or topic. Return ONLY the description text, without any labels or markdown formatting:
1658
+ and the description should be in ${languageDescription}.
1659
+ \n\n${plainTextContent.substring(0, 1500)}`; // Limit input text length
1660
+ const appTask = { task: task };
1661
+ const messages = [
1662
+ { content: agentCard.systemPrompt, role: ChatRole.System },
1663
+ // Optional: Add a more specific system message for description generation if needed
1664
+ // { content: "You are an AI assistant specialized in creating brief, compelling descriptions for educational language lessons.", role: ChatRole.System },
1665
+ { content: appTask.task, role: ChatRole.User },
1666
+ ];
1667
+ const response = await this.#agentService.callChatCompletion({ messages, model: agentCard.model }); // Use the model defined in BasicAgentCard
1668
+ let generatedDescription = response.content?.trim() ?? null;
1669
+ // Basic cleanup (remove potential quotes if the AI wraps the response)
1670
+ if (generatedDescription && generatedDescription.startsWith('"') && generatedDescription.endsWith('"')) {
1671
+ generatedDescription = generatedDescription.substring(1, generatedDescription.length - 1);
1672
+ }
1673
+ if (generatedDescription) {
1674
+ this.#toastService.success({ title: 'Descripción Generada', subtitle: 'Se generó la descripción con IA.' });
1675
+ return generatedDescription;
1676
+ }
1677
+ else {
1678
+ throw new Error('AI did not return a valid description.');
1679
+ }
1680
+ }
1681
+ catch (error) {
1682
+ console.error('Error during AI description generation in service:', error);
1683
+ this.#toastService.error({ title: 'Error de IA', subtitle: 'No se pudo generar la descripción con IA.' });
1684
+ return null;
1685
+ }
1686
+ finally {
1687
+ this.loadingBarService.successAndHide();
1688
+ }
1689
+ }
1690
+ /**
1691
+ * Extracts plain text from an HTML string, removing encoded JSON components.
1692
+ * @param htmlInput The HTML string potentially containing encoded JSON (~...~).
1693
+ * @returns The plain text representation.
1694
+ */
1695
+ _extractTextFromHtml(htmlInput) {
1696
+ if (!htmlInput)
1697
+ return '';
1698
+ // 1. Replace encoded JSON blocks with their 'settings.text' or an empty string
1699
+ const jsonRegex = /~(.+?)~/g;
1700
+ const textWithPlaceholders = htmlInput.replace(jsonRegex, (match, jsonCoded) => {
1701
+ try {
1702
+ const decodedJson = jsonCoded.replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
1703
+ const data = JSON.parse(decodedJson);
1704
+ return data?.settings?.text || ''; // Return text or empty string if not found
1705
+ }
1706
+ catch (error) {
1707
+ console.error('Failed to parse JSON inside HTML during text extraction:', jsonCoded, error);
1708
+ return ''; // Return empty string on error
1709
+ }
1710
+ });
1711
+ // 2. Strip remaining HTML tags to get plain text
1712
+ // Create a temporary DOM element to parse the HTML
1713
+ const tempDiv = document.createElement('div');
1714
+ tempDiv.innerHTML = textWithPlaceholders;
1715
+ // Use textContent to get the plain text, effectively stripping tags
1716
+ // Replace multiple whitespace characters (including newlines) with a single space
1717
+ return (tempDiv.textContent || tempDiv.innerText || '').replace(/\s+/g, ' ').trim();
1718
+ }
1396
1719
  /**
1397
1720
  * Cleans orphaned components from the lesson's dynamicComponents.
1398
1721
  * An orphaned component is one present in dynamicComponents but its ID is not found in the textCoded HTML.
@@ -1455,7 +1778,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
1455
1778
  args: [{
1456
1779
  providedIn: 'root', // Provide globally or in a specific module if preferred
1457
1780
  }]
1458
- }], ctorParameters: () => [] });
1781
+ }] });
1459
1782
 
1460
1783
  class DCLessonComponentAdderComponent {
1461
1784
  constructor() {
@@ -1503,16 +1826,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
1503
1826
  class DCLessonMetadataEditorComponent {
1504
1827
  constructor() {
1505
1828
  // Use signal for input lesson data
1506
- this.lesson = signal(undefined);
1507
- this.isLoadingLesson = signal(false);
1829
+ this.lesson = signal(undefined); // The lesson data itself
1830
+ this.isLoadingLesson = signal(false); // Shared loading state
1508
1831
  // Outputs for actions
1509
1832
  this.saveRequest = new EventEmitter();
1510
1833
  this.importNotionRequest = new EventEmitter();
1511
1834
  this.improveNotionRequest = new EventEmitter();
1512
- this.generateAIRequest = new EventEmitter();
1835
+ // Removed generateAIRequest Output as it's handled internally now
1513
1836
  // Output for property changes
1514
- this.propertyChange = new EventEmitter();
1837
+ this.propertyChange = new EventEmitter(); // To update parent signal
1838
+ // Injected Services
1839
+ this.#lessonUtilsService = inject(LessonUtilsService);
1840
+ this.#toastService = inject(TOAST_ALERTS_TOKEN);
1841
+ this.#lessonService = inject(LESSONS_TOKEN);
1842
+ this.#turndownService = new TurndownService(); // Instantiate TurndownService
1515
1843
  }
1844
+ // Injected Services
1845
+ #lessonUtilsService;
1846
+ #toastService;
1847
+ #lessonService;
1848
+ #turndownService; // Instantiate TurndownService
1516
1849
  // Method to emit property changes, called by ngModelChange in the template
1517
1850
  onPropertyChange(property, value) {
1518
1851
  this.propertyChange.emit({ property, value });
@@ -1527,13 +1860,108 @@ class DCLessonMetadataEditorComponent {
1527
1860
  emitImproveNotionRequest() {
1528
1861
  this.improveNotionRequest.emit();
1529
1862
  }
1530
- emitGenerateAIRequest() {
1531
- this.generateAIRequest.emit();
1863
+ /**
1864
+ * Generates lesson content using AI, saving the current state first.
1865
+ * Moved from DCLessonEditorComponent.
1866
+ */
1867
+ async generateByAI() {
1868
+ const currentLessonValue = this.lesson(); // Get current value
1869
+ if (!currentLessonValue?.id) {
1870
+ this.#toastService.warn({ title: 'Guardar primero', subtitle: 'Guarda la lección antes de usar IA.' });
1871
+ return;
1872
+ }
1873
+ this.isLoadingLesson.set(true);
1874
+ try {
1875
+ // Ensure latest changes are saved before generating
1876
+ // Clean orphaned components before saving (using the injected service)
1877
+ const lessonToSave = this.#lessonUtilsService.cleanOrphanedComponents(currentLessonValue);
1878
+ const savedLesson = await this.#lessonService.postLesson(lessonToSave);
1879
+ if (!savedLesson) {
1880
+ // Handle save error - toast is shown in saveLesson (assuming postLesson shows toast on error)
1881
+ this.#toastService.error({ title: 'Error al guardar', subtitle: 'No se pudo guardar antes de generar con IA.' });
1882
+ throw new Error('Failed to save before AI generation');
1883
+ }
1884
+ // Update the signal with the saved lesson data before proceeding
1885
+ this.lesson.set(savedLesson);
1886
+ const rawHtmlContent = savedLesson.textCoded || '';
1887
+ if (!rawHtmlContent) {
1888
+ console.warn('No HTML content found in lesson to process.');
1889
+ this.isLoadingLesson.set(false);
1890
+ return; // Exit if no content
1891
+ }
1892
+ // Replace encoded JSON with actual text before Markdown conversion
1893
+ const processedHtmlContent = this._extractTextFromEncodedJson(rawHtmlContent);
1894
+ // Convert the processed HTML (with text instead of JSON) to Markdown
1895
+ const markdownText = this.#turndownService.turndown(processedHtmlContent);
1896
+ // Use the updated lesson signal value for AI improvement
1897
+ const improvedMarkdown = await this.#lessonUtilsService.improveMDWithAI(this.lesson(), markdownText);
1898
+ if (improvedMarkdown) {
1899
+ // Convert the improved Markdown back to HTML before setting it
1900
+ const improvedHtml = marked(improvedMarkdown);
1901
+ // Update the signal directly
1902
+ this.lesson.update((current) => (current ? { ...current, textCoded: improvedHtml } : undefined));
1903
+ // Save the AI-generated content
1904
+ await this.#lessonService.postLesson(this.lesson());
1905
+ this.#toastService.success({ title: 'Contenido generado', subtitle: 'Se generó y guardó el contenido con IA.' });
1906
+ }
1907
+ else {
1908
+ // Toast error is handled by the service
1909
+ throw new Error('AI generation failed or lesson fetch failed after generation.');
1910
+ }
1911
+ }
1912
+ catch (error) {
1913
+ console.error('Error during AI generation process in metadata editor:', error);
1914
+ // Service handles specific AI error toasts, maybe add a general one here if needed
1915
+ // this.#toastService.error({ title: 'Error General', subtitle: 'Ocurrió un problema durante el proceso de IA.' });
1916
+ }
1917
+ finally {
1918
+ this.isLoadingLesson.set(false); // Stop loading
1919
+ }
1920
+ }
1921
+ /**
1922
+ * Replaces encoded JSON blocks (~...~) in HTML with their 'settings.text' value.
1923
+ * Moved from DCLessonEditorComponent.
1924
+ * @param htmlInput The HTML string containing encoded JSON.
1925
+ * @returns The processed HTML string with text replacements.
1926
+ */
1927
+ _extractTextFromEncodedJson(htmlInput) {
1928
+ const jsonRegex = /~(.+?)~/g; // Global flag to replace all occurrences
1929
+ return htmlInput.replace(jsonRegex, (match, jsonCoded) => {
1930
+ try {
1931
+ // Attempt to decode HTML entities that might be present in the JSON string
1932
+ const decodedJson = jsonCoded.replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
1933
+ const data = JSON.parse(decodedJson);
1934
+ // Return the text if available, otherwise keep the original match (or an error placeholder)
1935
+ return data?.settings?.text || match;
1936
+ }
1937
+ catch (error) {
1938
+ console.error('Failed to parse JSON inside HTML:', jsonCoded, error);
1939
+ return '<!-- invalid component data -->'; // Placeholder for parsing errors
1940
+ }
1941
+ });
1942
+ }
1943
+ /**
1944
+ * Calls the LessonUtilsService to generate a description using AI
1945
+ * and updates the lesson signal if successful.
1946
+ */
1947
+ async triggerGenerateDescriptionAI() {
1948
+ const currentLesson = this.lesson();
1949
+ if (!currentLesson) {
1950
+ this.#toastService.warn({ title: 'Lección no cargada', subtitle: 'Espera a que la lección se cargue.' });
1951
+ return;
1952
+ }
1953
+ const generatedDescription = await this.#lessonUtilsService.generateDescriptionWithAI(currentLesson);
1954
+ if (generatedDescription) {
1955
+ // Use the existing propertyChange emitter to update the parent signal
1956
+ this.onPropertyChange('description', generatedDescription);
1957
+ // this.emitSaveRequest(); // optional do something to automatically save
1958
+ }
1532
1959
  }
1533
1960
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonMetadataEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1534
- 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", generateAIRequest: "generateAIRequest", propertyChange: "propertyChange" }, 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.title\"\n (ngModelChange)=\"onPropertyChange('title', $event)\"\n type=\"text\"\n placeholder=\"Agrega un t\u00EDtulo\" />\n </div>\n <div style=\"margin-top: 4px\">\n <input\n pInputText\n style=\"width: 100%\"\n [ngModel]=\"currentLesson.description\"\n (ngModelChange)=\"onPropertyChange('description', $event)\"\n type=\"text\"\n placeholder=\"Agrega una descripci\u00F3n\" />\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.prompt\"\n (ngModelChange)=\"onPropertyChange('prompt', $event)\"\n type=\"text\"\n placeholder=\"Prompt para IA (opcional)\" />\n <p-button severity=\"primary\" [disabled]=\"isLoadingLesson()\" (click)=\"emitGenerateAIRequest()\"> Generar con IA </p-button>\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.isPublished\"\n (ngModelChange)=\"onPropertyChange('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)=\"onPropertyChange('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: "pipe", type: // Added TooltipModule
1961
+ 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", propertyChange: "propertyChange" }, 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.title\"\n (ngModelChange)=\"onPropertyChange('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.description\"\n (ngModelChange)=\"onPropertyChange('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.prompt\"\n (ngModelChange)=\"onPropertyChange('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.isPublished\"\n (ngModelChange)=\"onPropertyChange('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)=\"onPropertyChange('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
1535
1962
  FlagLanguagePipe, name: "flagEmoji" }, { kind: "pipe", type: // Added Pipe
1536
- LangDescTranslationPipe, name: "langDesc" }] }); }
1963
+ LangDescTranslationPipe, name: "langDesc" }, { kind: "ngmodule", type: // Added Pipe
1964
+ InputGroupModule }, { kind: "component", type: i5$1.InputGroup, selector: "p-inputgroup, p-inputGroup, p-input-group", inputs: ["style", "styleClass"] }] }); }
1537
1965
  }
1538
1966
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonMetadataEditorComponent, decorators: [{
1539
1967
  type: Component,
@@ -1545,7 +1973,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
1545
1973
  TooltipModule, // Added TooltipModule
1546
1974
  FlagLanguagePipe, // Added Pipe
1547
1975
  LangDescTranslationPipe, // Added Pipe
1548
- ], 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.title\"\n (ngModelChange)=\"onPropertyChange('title', $event)\"\n type=\"text\"\n placeholder=\"Agrega un t\u00EDtulo\" />\n </div>\n <div style=\"margin-top: 4px\">\n <input\n pInputText\n style=\"width: 100%\"\n [ngModel]=\"currentLesson.description\"\n (ngModelChange)=\"onPropertyChange('description', $event)\"\n type=\"text\"\n placeholder=\"Agrega una descripci\u00F3n\" />\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.prompt\"\n (ngModelChange)=\"onPropertyChange('prompt', $event)\"\n type=\"text\"\n placeholder=\"Prompt para IA (opcional)\" />\n <p-button severity=\"primary\" [disabled]=\"isLoadingLesson()\" (click)=\"emitGenerateAIRequest()\"> Generar con IA </p-button>\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.isPublished\"\n (ngModelChange)=\"onPropertyChange('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)=\"onPropertyChange('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" }]
1976
+ InputGroupModule,
1977
+ ], 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.title\"\n (ngModelChange)=\"onPropertyChange('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.description\"\n (ngModelChange)=\"onPropertyChange('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.prompt\"\n (ngModelChange)=\"onPropertyChange('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.isPublished\"\n (ngModelChange)=\"onPropertyChange('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)=\"onPropertyChange('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" }]
1549
1978
  }], propDecorators: { lesson: [{
1550
1979
  type: Input,
1551
1980
  args: [{ required: true }]
@@ -1558,38 +1987,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
1558
1987
  type: Output
1559
1988
  }], improveNotionRequest: [{
1560
1989
  type: Output
1561
- }], generateAIRequest: [{
1562
- type: Output
1563
1990
  }], propertyChange: [{
1564
1991
  type: Output
1565
1992
  }] } });
1566
1993
 
1567
- const GradientCss = 'linear-gradient(to bottom,var(--primary-color), rgba(213, 238, 239, 0.31))';
1568
1994
  class DCLessonEditorComponent {
1569
1995
  // Services
1570
1996
  #activatedRoute; // Re-inject as it's needed for navigation
1571
1997
  #lessonNotionService;
1572
1998
  #lessonUtilsService;
1573
1999
  #router;
1574
- // Removed #dialogService injection
1575
2000
  #lessonService;
1576
2001
  #toastService;
1577
2002
  constructor() {
1578
- // Removed Speed Dial Items initialization
1579
2003
  // Services
1580
2004
  this.#activatedRoute = inject(ActivatedRoute); // Re-inject as it's needed for navigation
1581
2005
  this.#lessonNotionService = inject(LessonNotionService);
1582
2006
  this.#lessonUtilsService = inject(LessonUtilsService);
1583
2007
  this.#router = inject(Router);
1584
- // Removed #dialogService injection
1585
2008
  this.#lessonService = inject(LESSONS_TOKEN);
1586
2009
  this.#toastService = inject(TOAST_ALERTS_TOKEN);
1587
2010
  // Signals States
1588
2011
  this.lessonId = toSignal(inject(ActivatedRoute).paramMap.pipe(map((params) => params.get('id'))));
1589
2012
  this.lesson = signal(undefined); // Initialize as undefined
1590
- this.isCropperVisible = signal(false);
1591
2013
  this.isLoadingLesson = signal(false);
1592
- // Removed items signal
1593
2014
  // Computed Signals
1594
2015
  this.coverImageUrl = computed(() => {
1595
2016
  const currentLesson = this.lesson();
@@ -1643,6 +2064,7 @@ class DCLessonEditorComponent {
1643
2064
  else {
1644
2065
  // Handle case for new lesson (ID is null/undefined)
1645
2066
  this.lesson.set({ textCoded: `<h1>Nueva lección </h1> <p> Texto aquí</p>`, tags: [] }); // Set default new lesson structure
2067
+ this.saveLesson();
1646
2068
  this.isLoadingLesson.set(false); // Ensure loading is off
1647
2069
  }
1648
2070
  });
@@ -1654,6 +2076,7 @@ class DCLessonEditorComponent {
1654
2076
  * @param value The new value for the property.
1655
2077
  */
1656
2078
  updateLessonProperty(property, value) {
2079
+ console.log('Updating property:', property, value);
1657
2080
  this.lesson.update((currentLesson) => {
1658
2081
  if (!currentLesson)
1659
2082
  return undefined;
@@ -1703,7 +2126,7 @@ class DCLessonEditorComponent {
1703
2126
  // It was a new lesson, now it has an ID. Navigate.
1704
2127
  this.#toastService.success({ title: 'Se creó la lección', subtitle: 'Éxito' });
1705
2128
  // The effect should automatically fetch the lesson again after navigation due to paramMap change.
1706
- this.#router.navigate(['../', savedLesson.id], { relativeTo: this.#activatedRoute });
2129
+ this.#router.navigate(['./', savedLesson.id], { relativeTo: this.#activatedRoute });
1707
2130
  }
1708
2131
  else {
1709
2132
  // It was an existing lesson, update the signal with the potentially updated data from the backend.
@@ -1730,7 +2153,6 @@ class DCLessonEditorComponent {
1730
2153
  * @param result The configuration data returned from the component builder dialog. Expected format: { obj: LessonComponentConfiguration }
1731
2154
  */
1732
2155
  onComponentAdded(result) {
1733
- debugger;
1734
2156
  // Check if result and result.obj.id exist
1735
2157
  const newComponent = result?.obj;
1736
2158
  if (newComponent?.id) {
@@ -1762,46 +2184,9 @@ class DCLessonEditorComponent {
1762
2184
  // this.saveLesson();
1763
2185
  }
1764
2186
  }
1765
- openCropper() {
1766
- // Correctly define openCropper
1767
- this.isCropperVisible.set(true);
1768
- }
1769
2187
  // isLoadingLesson signal is used directly
1770
- async generateByAI() {
1771
- const currentId = this.lessonId();
1772
- if (!currentId) {
1773
- this.#toastService.warn({ title: 'Guardar primero', subtitle: 'Guarda la lección antes de usar IA.' });
1774
- return;
1775
- }
1776
- this.isLoadingLesson.set(true);
1777
- try {
1778
- // Ensure latest changes are saved before generating
1779
- const savedLesson = await this.saveLesson();
1780
- if (!savedLesson) {
1781
- // Handle save error - toast is shown in saveLesson
1782
- throw new Error('Failed to save before AI generation');
1783
- }
1784
- // Call the service method
1785
- const updatedLesson = await this.#lessonUtilsService.generateByAI(currentId);
1786
- if (updatedLesson) {
1787
- this.lesson.set(updatedLesson); // Update the signal with AI changes from service
1788
- // Toast success is handled by the service
1789
- }
1790
- else {
1791
- // Toast error is handled by the service
1792
- throw new Error('AI generation failed or lesson fetch failed after generation.');
1793
- }
1794
- }
1795
- catch (error) {
1796
- // Type error
1797
- console.error('Error during AI generation process in component:', error);
1798
- // Service handles specific AI error toasts, maybe add a general one here if needed
1799
- // this.#toastService.error({ title: 'Error General', subtitle: 'Ocurrió un problema durante el proceso de IA.' });
1800
- }
1801
- finally {
1802
- this.isLoadingLesson.set(false); // Stop loading
1803
- }
1804
- }
2188
+ // Removed generateByAI and _extractTextFromEncodedJson methods.
2189
+ // This logic is now handled within DCLessonMetadataEditorComponent.
1805
2190
  /**
1806
2191
  * Handles the image upload event, updates the lesson signal via the service, and saves.
1807
2192
  * @param event The image upload event data.
@@ -1844,10 +2229,8 @@ class DCLessonEditorComponent {
1844
2229
  alert('showComponentDetails' + JSON.stringify(data));
1845
2230
  }
1846
2231
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1847
- 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()\"\n (generateAIRequest)=\"generateByAI()\"\n (propertyChange)=\"updateLessonProperty($event.property, $event.value)\">\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' }\" styleClass=\"mb-8\">\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", styles: [".btn{padding:.5rem 1rem;border-radius:4px;border:1px solid transparent;cursor:pointer}.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;background-position:center;background-repeat:no-repeat;background-size:cover;position:relative}.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$1.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:
1848
- // Removed SpeedDialModule
1849
- DCLessonComponentAdderComponent, selector: "dc-lesson-component-adder", outputs: ["componentAdded"] }, { kind: "component", type: // Add the component adder here
1850
- DCLessonMetadataEditorComponent, selector: "dc-lesson-metadata-editor", inputs: ["lesson", "isLoadingLesson"], outputs: ["saveRequest", "importNotionRequest", "improveNotionRequest", "generateAIRequest", "propertyChange"] }] }); }
2232
+ 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()\"\n (propertyChange)=\"updateLessonProperty($event.property, $event.value)\">\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", styles: [".btn{padding:.5rem 1rem;border-radius:4px;border:1px solid transparent;cursor:pointer}.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;background-position:center;background-repeat:no-repeat;background-size:cover;position:relative}.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
2233
+ DCLessonMetadataEditorComponent, selector: "dc-lesson-metadata-editor", inputs: ["lesson", "isLoadingLesson"], outputs: ["saveRequest", "importNotionRequest", "improveNotionRequest", "propertyChange"] }] }); }
1851
2234
  }
1852
2235
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonEditorComponent, decorators: [{
1853
2236
  type: Component,
@@ -1860,11 +2243,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
1860
2243
  InputTextModule,
1861
2244
  SplitterModule,
1862
2245
  TooltipModule,
1863
- // Removed SpeedDialModule
1864
2246
  DCLessonComponentAdderComponent, // Add the component adder here
1865
2247
  DCLessonMetadataEditorComponent, // Add the metadata editor here
1866
- JsonPipe,
1867
- ], 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</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 (generateAIRequest)=\"generateByAI()\"\n (propertyChange)=\"updateLessonProperty($event.property, $event.value)\">\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' }\" styleClass=\"mb-8\">\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", styles: [".btn{padding:.5rem 1rem;border-radius:4px;border:1px solid transparent;cursor:pointer}.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;background-position:center;background-repeat:no-repeat;background-size:cover;position:relative}.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"] }]
2248
+ ], 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</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 (propertyChange)=\"updateLessonProperty($event.property, $event.value)\">\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", styles: [".btn{padding:.5rem 1rem;border-radius:4px;border:1px solid transparent;cursor:pointer}.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;background-position:center;background-repeat:no-repeat;background-size:cover;position:relative}.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"] }]
1868
2249
  }], ctorParameters: () => [], propDecorators: { target: [{
1869
2250
  type: ViewChild,
1870
2251
  args: ['target', { read: ViewContainerRef }]
@@ -1873,6 +2254,103 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
1873
2254
  args: ['dhtml', { static: true }]
1874
2255
  }] } });
1875
2256
 
2257
+ // Define placeholder endpoints - these should be configured appropriately
2258
+ const LESSONS_BASE_PATH = 'api/lesson'; // Example base path
2259
+ class DefaultLessonsService {
2260
+ constructor() {
2261
+ this.httpCoreService = inject(HttpCoreService);
2262
+ // --- Endpoint Definitions (Hardcoded as requested) ---
2263
+ this.endpoints = {
2264
+ queryLessons: `${LESSONS_BASE_PATH}/query`,
2265
+ getLesson: (id) => `${LESSONS_BASE_PATH}/${id}`,
2266
+ saveLesson: `api/user/saveLesson`,
2267
+ updateLesson: (id) => `${LESSONS_BASE_PATH}/${id}`, // Assuming PUT to /lessons/:id
2268
+ deleteLesson: (id) => `${LESSONS_BASE_PATH}/${id}`, // Assuming DELETE to /lessons/:id
2269
+ generateLesson: `${LESSONS_BASE_PATH}/generate`, // Placeholder
2270
+ generateByAI: `${LESSONS_BASE_PATH}/generate-ai`, // Placeholder
2271
+ improveMDWithAI: `${LESSONS_BASE_PATH}/improve-markdown-ai`, // Placeholder
2272
+ QueryLessons: 'api/lesson/query',
2273
+ Lesson: 'api/lesson',
2274
+ SaveLesson: 'api/lesson-polilan',
2275
+ GetPublicLessons: 'api/lesson/publicLessons',
2276
+ GetUnpublishedLessons: 'api/lesson/unpublished',
2277
+ TakenLesson: 'api/lesson/taken',
2278
+ DeleteLesson: 'api/lesson',
2279
+ Base: 'api/lesson',
2280
+ };
2281
+ }
2282
+ saveTakenLesson(lesson) {
2283
+ // Not sure how to implement this yet.
2284
+ return this.httpCoreService.post(this.endpoints.saveLesson, lesson);
2285
+ }
2286
+ // --- Method Implementations ---
2287
+ async getLessons(paginator) {
2288
+ // Assuming paginator is the body for a POST request based on the example
2289
+ return this.httpCoreService.post(this.endpoints.queryLessons, paginator || {});
2290
+ }
2291
+ async getLesson(id) {
2292
+ return this.httpCoreService.get(this.endpoints.getLesson(id));
2293
+ }
2294
+ async postLesson(lesson) {
2295
+ return this.httpCoreService.post(this.endpoints.saveLesson, lesson);
2296
+ }
2297
+ async updateLesson(lesson) {
2298
+ if (!lesson._id) {
2299
+ throw new Error('Lesson ID is required for update.');
2300
+ }
2301
+ // Assuming _id is the identifier
2302
+ return this.httpCoreService.put(this.endpoints.updateLesson(lesson._id), lesson);
2303
+ }
2304
+ async deleteLesson(id) {
2305
+ return this.httpCoreService.delete(this.endpoints.deleteLesson(id));
2306
+ }
2307
+ async generateLesson(lesson) {
2308
+ // This endpoint might need specific data or structure
2309
+ return this.httpCoreService.post(this.endpoints.generateLesson, lesson);
2310
+ }
2311
+ async postGenerateByAI(id) {
2312
+ return this.httpCoreService.post(this.endpoints.generateByAI, { id });
2313
+ }
2314
+ async postImproveMDWithAI(lessonId, markdownText) {
2315
+ return this.httpCoreService.post(this.endpoints.improveMDWithAI, { id: lessonId, markdown: markdownText });
2316
+ }
2317
+ extractTextFromHtml(html) {
2318
+ // Copied from src/app/core/data-services/lessons.service.ts
2319
+ const r1 = new RegExp('~(.+?)~', 'g');
2320
+ const lessonHtml = html.replace(r1, (_matching, jsonCoded) => {
2321
+ try {
2322
+ const data = JSON.parse(jsonCoded);
2323
+ return `<span>${data?.settings?.text || ''}</span>`; // Added default empty string
2324
+ }
2325
+ catch (e) {
2326
+ console.error('Error parsing JSON in extractTextFromHtml:', jsonCoded, e);
2327
+ return ''; // Return empty string on error
2328
+ }
2329
+ });
2330
+ // Remove HTML tags
2331
+ let text = lessonHtml.replace(/<[^>]*>/g, ' ');
2332
+ // Remove style and script content
2333
+ text = text.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, ' ');
2334
+ text = text.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, ' ');
2335
+ // Decode HTML entities
2336
+ text = text.replace(/&nbsp;/g, ' ');
2337
+ text = text.replace(/&/g, '&');
2338
+ text = text.replace(/</g, '<');
2339
+ text = text.replace(/>/g, '>');
2340
+ // Remove extra whitespace
2341
+ text = text.replace(/\s+/g, ' ').trim();
2342
+ return text;
2343
+ }
2344
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DefaultLessonsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2345
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DefaultLessonsService, providedIn: 'root' }); }
2346
+ }
2347
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DefaultLessonsService, decorators: [{
2348
+ type: Injectable,
2349
+ args: [{
2350
+ providedIn: 'root',
2351
+ }]
2352
+ }] });
2353
+
1876
2354
  // This is the base class for all the components that are going to be used in the lessons.
1877
2355
  class LessonDynamicComponent {
1878
2356
  constructor() {
@@ -1900,5 +2378,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImpor
1900
2378
  * Generated bundle index. Do not edit.
1901
2379
  */
1902
2380
 
1903
- export { ComponentBuilder, ComponentWithForm, DCLessonEditorComponent, DCLessonFormComponent, DCLessonListComponent, DCLessonRendererComponent, DcLessonCardComponent, FlagLanguagePipe, LESSONS_TOKEN, LangCodeDescription, LangCodeDescriptionEs, LangDescTranslationPipe, LessonComponentBuilders, LessonComponentEnum, LessonComponents, LessonDynamicComponent, LessonsAbstractService, NOTION_SERVICE_TOKEN, NotionAbstractService, NotionExportType, SelectorBuilderComponent, SelectorComponent, TextWriterBuiderComponent, TextWriterComponent, TranslationSwitcherBuilderComponent, TranslationSwitcherComponent, getLessonComponentClass, provideLessonsService, provideNotionService };
2381
+ export { BasicAgentCard$1 as BasicAgentCard, 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, getLessonComponentClass, provideLessonsService, provideNotionService };
1904
2382
  //# sourceMappingURL=dataclouder-ngx-lessons.mjs.map