@dataclouder/ngx-lessons 0.0.29 → 0.0.31

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.
Files changed (50) hide show
  1. package/fesm2022/dataclouder-ngx-lessons.mjs +1205 -514
  2. package/fesm2022/dataclouder-ngx-lessons.mjs.map +1 -1
  3. package/lib/components/dc-lessons/dc-lesson-card/dc-lesson-card.component.d.ts +6 -8
  4. package/lib/components/dc-lessons/dc-lesson-component-adder/dc-lesson-component-adder.component.d.ts +11 -0
  5. package/lib/components/dc-lessons/dc-lesson-editor/dc-lesson-editor.component.d.ts +39 -38
  6. package/lib/components/dc-lessons/dc-lesson-metadata-editor/dc-lesson-metadata-editor.component.d.ts +22 -0
  7. package/lib/components/dc-lessons/dc-lesson-renderer/dc-lesson-renderer.component.d.ts +32 -37
  8. package/lib/components/dc-lessons/lesson-list/dc-lesson-list.component.d.ts +7 -17
  9. package/lib/components/lesson-mini-components/components/ComponentBuilder.d.ts +7 -2
  10. package/lib/components/lesson-mini-components/components/lessons.clases.d.ts +17 -42
  11. package/lib/components/lesson-mini-components/components/speaker/speaker-builder/speaker-builder.component.d.ts +13 -0
  12. package/lib/components/lesson-mini-components/components/speaker/speaker.component.d.ts +12 -0
  13. package/lib/services/lesson-ai.service.d.ts +18 -0
  14. package/lib/services/lesson-notion.service.d.ts +35 -0
  15. package/lib/services/lesson-utils.service.d.ts +34 -0
  16. package/package.json +3 -2
  17. package/src/lib/components/dc-lessons/dc-lesson-card/dc-lesson-card.component.html +40 -35
  18. package/src/lib/components/dc-lessons/dc-lesson-card/dc-lesson-card.component.scss +15 -2
  19. package/src/lib/components/dc-lessons/dc-lesson-card/dc-lesson-card.component.ts +16 -18
  20. package/src/lib/components/dc-lessons/dc-lesson-component-adder/dc-lesson-component-adder.component.css +1 -0
  21. package/src/lib/components/dc-lessons/dc-lesson-component-adder/dc-lesson-component-adder.component.html +46 -0
  22. package/src/lib/components/dc-lessons/dc-lesson-component-adder/dc-lesson-component-adder.component.ts +52 -0
  23. package/src/lib/components/dc-lessons/dc-lesson-editor/dc-lesson-editor.component.html +54 -92
  24. package/src/lib/components/dc-lessons/dc-lesson-editor/dc-lesson-editor.component.ts +268 -230
  25. package/src/lib/components/dc-lessons/dc-lesson-metadata-editor/dc-lesson-metadata-editor.component.css +1 -0
  26. package/src/lib/components/dc-lessons/dc-lesson-metadata-editor/dc-lesson-metadata-editor.component.html +72 -0
  27. package/src/lib/components/dc-lessons/dc-lesson-metadata-editor/dc-lesson-metadata-editor.component.ts +60 -0
  28. package/src/lib/components/dc-lessons/dc-lesson-renderer/dc-lesson-renderer.component.html +23 -27
  29. package/src/lib/components/dc-lessons/dc-lesson-renderer/dc-lesson-renderer.component.ts +247 -186
  30. package/src/lib/components/dc-lessons/lesson-form/lesson-form.component.ts +2 -2
  31. package/src/lib/components/dc-lessons/lesson-list/dc-lesson-list.component.html +3 -3
  32. package/src/lib/components/dc-lessons/lesson-list/dc-lesson-list.component.ts +28 -46
  33. package/src/lib/components/lesson-mini-components/components/ComponentBuilder.ts +23 -15
  34. package/src/lib/components/lesson-mini-components/components/lessons.clases.ts +32 -66
  35. package/src/lib/components/lesson-mini-components/components/selector/selector-builder/selector-builder.component.html +62 -58
  36. package/src/lib/components/lesson-mini-components/components/selector/selector-builder/selector-builder.component.ts +2 -2
  37. package/src/lib/components/lesson-mini-components/components/selector/selector.component.html +1 -2
  38. package/src/lib/components/lesson-mini-components/components/selector/selector.component.ts +2 -2
  39. package/src/lib/components/lesson-mini-components/components/speaker/speaker-builder/speaker-builder.component.html +5 -27
  40. package/src/lib/components/lesson-mini-components/components/speaker/speaker-builder/speaker-builder.component.ts +38 -25
  41. package/src/lib/components/lesson-mini-components/components/speaker/speaker.component.html +9 -7
  42. package/src/lib/components/lesson-mini-components/components/speaker/speaker.component.ts +30 -26
  43. package/src/lib/components/lesson-mini-components/components/translationSwitcher/translationSwitcher.component.ts +2 -2
  44. package/src/lib/components/lesson-mini-components/components/translationSwitcher/translationSwitcherBuilder/translationSwitcherBuilder.component.ts +2 -2
  45. package/src/lib/services/lesson-ai.service.ts +103 -0
  46. package/src/lib/services/lesson-notion.service.ts +161 -0
  47. package/src/lib/services/lesson-utils.service.ts +181 -0
  48. package/src/lib/components/lesson-mini-components/components/selector/selector-builder/selector-builder.component.spec.ts +0 -25
  49. package/src/lib/components/lesson-mini-components/components/speaker/speaker-builder/speaker-builder.component.spec.ts +0 -25
  50. package/src/lib/components/lesson-mini-components/components/speaker/speaker.component.spec.ts +0 -25
@@ -1,14 +1,12 @@
1
- import { Component, ComponentRef, ElementRef, Inject, Input, OnInit, Renderer2, ViewChild, ViewContainerRef } from '@angular/core';
2
- import { UntypedFormGroup, Validators } from '@angular/forms';
3
- import { ActivatedRoute } from '@angular/router';
4
- import { NgIf, KeyValuePipe } from '@angular/common';
5
-
1
+ import { Component, ComponentRef, ElementRef, Renderer2, ViewChild, ViewContainerRef, computed, effect, inject, input, signal } from '@angular/core'; // Added signal imports
2
+ import { FormGroup, FormControl, Validators } from '@angular/forms'; // Use typed forms
3
+ import { KeyValuePipe } from '@angular/common';
6
4
  import { ButtonModule } from 'primeng/button';
5
+ import { LessonAIService } from '../../../services/lesson-ai.service'; // Import the new service
7
6
 
8
7
  import {
9
8
  getLessonComponentClass,
10
9
  ILesson,
11
- LangCodeDescription,
12
10
  LessonComponentConfiguration,
13
11
  LessonComponentsType,
14
12
  LESSONS_TOKEN,
@@ -17,173 +15,237 @@ import {
17
15
 
18
16
  import { TOAST_ALERTS_TOKEN, ToastAlertsAbstractService } from '@dataclouder/ngx-core';
19
17
  import { LessonComponentInterface } from '../../lesson-mini-components/components/lessons.clases';
20
- import { ComponentWithForm } from '../../lesson-mini-components/components/ComponentWithForm';
21
- import {
22
- CONVERSATION_AI_TOKEN,
23
- AgentCardsAbstractService,
24
- DCConversationPromptBuilderService,
25
- DCChatComponent,
26
- IAgentCard,
27
- ConversationType,
28
- TextEngines,
29
- IMiniAgentCard,
30
- } from '@dataclouder/ngx-agent-cards';
18
+ import { DCChatComponent, IAgentCard, IMiniAgentCard } from '@dataclouder/ngx-agent-cards';
31
19
 
32
20
  import { DrawerModule } from 'primeng/drawer';
33
21
 
34
- // TODO: i need strategy to create prompt dinamically dpending on user language and level.
35
- const DefaultLessonAgentCard: IAgentCard = {
36
- conversationSettings: {
37
- conversationType: ConversationType.General,
38
- textEngine: TextEngines.SimpleText,
39
- autoStart: true,
40
- },
41
- characterCard: {
42
- data: {
43
- name: 'Lesson Master',
44
- description: 'You are an AI developed by Polilan Company, you are a lesson master, you are going to help the user to learn english',
45
- tags: ['lesson', 'master', 'ai'],
46
- post_history_instructions:
47
- 'Your reply should be always short, 1 or 2 paragraphs at most, and to the point, and you should ask friendly questions all the time',
48
- },
49
- },
50
- model: { provider: 'google' },
51
- };
52
-
53
22
  @Component({
54
23
  selector: 'dc-lesson-renderer',
55
24
  templateUrl: './dc-lesson-renderer.component.html',
56
25
  styleUrls: ['./dc-lesson-renderer.component.scss'],
57
26
  standalone: true,
58
- imports: [NgIf, KeyValuePipe, ButtonModule, DCChatComponent, DrawerModule],
27
+ imports: [KeyValuePipe, ButtonModule, DCChatComponent, DrawerModule],
59
28
  })
60
- export class DCLessonRendererComponent implements OnInit {
61
- @Input() public lesson: ILesson;
62
- @Input() public test: boolean;
63
-
64
- @ViewChild('dynamicLesson', { static: true }) dynamicLesson: ElementRef;
65
-
66
- public lessonId: string = this.activatedRoute.snapshot.paramMap.get('id');
67
- public dataText: string = `<h1>Nueva lección </h1> <p> Texto aquí</p>`;
68
- public components: { [key: string]: ComponentRef<LessonComponentInterface> } = {};
69
- public mainForm: UntypedFormGroup = new UntypedFormGroup({});
70
- public imageCover: string;
71
-
72
- public chatVisible: boolean = false;
73
-
74
- constructor(
75
- private renderer: Renderer2,
76
- private viewContainerRef: ViewContainerRef,
77
- private activatedRoute: ActivatedRoute,
78
- @Inject(TOAST_ALERTS_TOKEN) private toastrService: ToastAlertsAbstractService,
79
- @Inject(LESSONS_TOKEN) private lessonService: LessonsAbstractService,
80
- @Inject(CONVERSATION_AI_TOKEN) private agentCardsService: AgentCardsAbstractService,
81
- private conversationBuilder: DCConversationPromptBuilderService, // private userService: UserService,
82
- ) {}
83
-
84
- ngOnInit(): void {
85
- this.initLesson();
29
+ export class DCLessonRendererComponent {
30
+ // --- Signal Inputs ---
31
+ lessonInput = input<ILesson>(); // Input signal for lesson object
32
+ lessonIdInput = input<string>(); // Input signal for lesson ID
33
+ test = input<boolean>(false);
34
+
35
+ // --- View Childs ---
36
+ @ViewChild('dynamicLesson', { static: true }) dynamicLesson: ElementRef<HTMLElement>;
37
+
38
+ // --- Injected Services (using inject function) ---
39
+ private readonly renderer = inject(Renderer2);
40
+ private readonly viewContainerRef = inject(ViewContainerRef);
41
+ private readonly toastrService = inject<ToastAlertsAbstractService>(TOAST_ALERTS_TOKEN);
42
+ private readonly lessonService = inject<LessonsAbstractService>(LESSONS_TOKEN);
43
+ private readonly lessonAIService = inject(LessonAIService); // Inject the new service
44
+
45
+ // --- State Signals ---
46
+ lesson = signal<ILesson | undefined>(undefined); // Internal lesson state signal
47
+ chatVisible = signal(false); // Signal for chat visibility
48
+ agentMasterLesson = signal<IAgentCard | undefined>(undefined); // Signal for agent card
49
+ evaluatorAgentCard = signal<IMiniAgentCard | undefined>(undefined); // Signal for evaluator card
50
+
51
+ // --- Computed Signals ---
52
+ imageCover = computed(() => this.lesson()?.media?.images?.find((img) => img.type === 'cover')?.url); // Computed signal for imageCover
53
+
54
+ // --- Properties ---
55
+ private components: { [key: string]: ComponentRef<LessonComponentInterface> } = {};
56
+ public mainForm: FormGroup<{ [key: string]: FormControl<any> }> = new FormGroup({});
57
+
58
+ constructor() {
59
+ // Effect to fetch lesson data if ID is provided and lesson object isn't
60
+ effect(
61
+ async () => {
62
+ const lessonInput = this.lessonInput();
63
+ const lessonId = this.lessonIdInput();
64
+
65
+ if (lessonInput) {
66
+ this.lesson.set(lessonInput); // Use input lesson directly
67
+ } else if (lessonId && !this.lesson()) {
68
+ // Fetch only if ID exists and internal lesson is not set
69
+ console.log(`Fetching lesson with ID: ${lessonId}`);
70
+ try {
71
+ // Consider adding a loading state signal here
72
+ const fetchedLesson = await this.lessonService.getLesson(lessonId);
73
+ this.lesson.set(fetchedLesson);
74
+ console.log('Fetched lesson:', fetchedLesson);
75
+ } catch (error) {
76
+ console.error(`Failed to fetch lesson with ID: ${lessonId}`, error);
77
+ this.toastrService.error({ subtitle: 'Failed to load lesson data.', title: 'Error' });
78
+ this.lesson.set(undefined); // Reset lesson on error
79
+ } finally {
80
+ // Reset loading state signal here
81
+ }
82
+ } else if (!lessonInput && !lessonId) {
83
+ // Handle case where neither input is provided, maybe clear the lesson?
84
+ this.lesson.set(undefined);
85
+ }
86
+ },
87
+ { allowSignalWrites: true },
88
+ ); // Allow signal writes inside effect
89
+
90
+ // Effect to render the lesson whenever the lesson signal changes
91
+ effect(() => {
92
+ const currentLesson = this.lesson();
93
+ if (currentLesson) {
94
+ this._renderLesson(currentLesson);
95
+ } else {
96
+ // Clear previous rendering if lesson becomes undefined
97
+ this._clearLessonRendering();
98
+ }
99
+ });
86
100
  }
87
101
 
88
- public async initLesson() {
89
- await this.getLessonIfId();
90
- this.renderLesson();
102
+ // --- Rendering Logic ---
103
+
104
+ private _clearLessonRendering(): void {
105
+ // Destroy previously created dynamic components
106
+ Object.values(this.components).forEach((compRef) => compRef.destroy());
107
+ this.components = {};
108
+ // Clear the form
109
+ this.mainForm = new FormGroup({});
110
+ // Clear the HTML content
111
+ if (this.dynamicLesson?.nativeElement) {
112
+ this.dynamicLesson.nativeElement.innerHTML = '';
113
+ }
91
114
  }
92
115
 
93
- public async getLessonIfId() {
94
- if (this.lesson) {
95
- return;
96
- }
116
+ private _renderLesson(lessonData: ILesson): void {
117
+ this._clearLessonRendering(); // Clear previous state first
97
118
 
98
- if (!this.lessonId) {
99
- return;
100
- }
101
- this.dataText = `<h1>Cargando </h1> <p>...</p>`;
102
- this.lesson = await this.lessonService.getLesson(this.lessonId);
103
- console.log('lesson', this.lesson);
104
- }
119
+ console.log('Rendering lesson:', lessonData.id);
120
+ // console.log('Image cover URL:', this.imageCover()); // Access computed signal
121
+
122
+ // 1) Parse textCoded, create components, and build HTML structure
123
+ const { htmlContent, components } = this._parseAndCreateComponents(lessonData);
124
+ this.components = components;
105
125
 
106
- private renderLesson(): void {
107
- this.imageCover = this.lesson.media?.images?.find((img) => img.type === 'cover')?.url;
126
+ // 2) Aggregate form controls from created components
127
+ this._aggregateFormControls(this.components);
108
128
 
109
- console.log('imageCover', this.imageCover);
110
- // Pasos para renderizar,
111
- // 0) Usar la expresión regular para sustitur los componentes por un span con un id
112
- // 1) En el mismo proceso de remplazar se contruyen dinamicamente los componentes
113
- // 2) Agregar componentes al DOM en su respectivo spanid
129
+ // 3) Set the innerHTML of the target element
130
+ this.dynamicLesson.nativeElement.innerHTML = htmlContent;
114
131
 
115
- // TODO Optimización, es posible agregar el componente al DOM en el mismo paso en que se crea el span?
132
+ // 4) Inject the component views into the DOM
133
+ this._injectComponentsIntoDom(this.components);
134
+ }
116
135
 
136
+ private _parseAndCreateComponents(lessonData: ILesson): { htmlContent: string; components: { [key: string]: ComponentRef<LessonComponentInterface> } } {
117
137
  const r1 = new RegExp('~(.+?)~', 'g');
118
138
  let count = 0;
119
- this.components = {}; //Si se destruye la lección, se destruyen todos los componentes dinamicis? tODO necesito reiniciar?
139
+ const createdComponents: { [key: string]: ComponentRef<LessonComponentInterface> } = {};
120
140
 
121
- const lessonHtml = this.lesson.textCoded.replace(r1, (_matching, jsonCoded) => {
141
+ const htmlContent = lessonData.textCoded.replace(r1, (_matching, jsonCoded) => {
122
142
  const componentName = `dynamicComp${count}`;
123
143
  count++;
124
- const componentRef = this.createComponentReferenceWithJson(jsonCoded);
144
+ const componentRef = this._createComponentReferenceWithJson(jsonCoded, lessonData);
125
145
  if (!componentRef) {
126
- return null;
127
- }
128
- this.components[componentName] = componentRef;
129
- if (componentRef.instance.control) {
130
- // Algunos componentes tienen formularios, estos se copian en mainForm como un unico control
131
- this.mainForm.addControl(componentName, componentRef.instance.control);
132
- this.mainForm.get(componentName).setValidators([Validators.required]);
146
+ console.error(`Failed to create component for: ${jsonCoded}`);
147
+ return '<!-- component creation failed -->'; // Placeholder in HTML
133
148
  }
134
- return `<span id="${componentName}"></span>`;
149
+ createdComponents[componentName] = componentRef;
150
+ return `<span id="${componentName}"></span>`; // Return span placeholder
135
151
  });
136
152
 
137
- this.dynamicLesson.nativeElement.innerHTML = lessonHtml;
153
+ return { htmlContent, components: createdComponents };
154
+ }
138
155
 
139
- for (let compName of Object.keys(this.components)) {
140
- // Interpolar componentes dentro del lugar correspondiente
141
- const elementRef = document.getElementById(compName);
142
- this.addComponentToNode(this.components[compName], elementRef);
143
- }
156
+ private _aggregateFormControls(components: { [key: string]: ComponentRef<LessonComponentInterface> }): void {
157
+ const newFormControls: { [key: string]: FormControl<any> } = {};
158
+ Object.entries(components).forEach(([name, componentRef]) => {
159
+ // Check if the instance has a control property that is a FormControl
160
+ if (componentRef.instance?.control instanceof FormControl) {
161
+ newFormControls[name] = componentRef.instance.control;
162
+ // Add required validator (consider making this configurable via component config?)
163
+ newFormControls[name].addValidators(Validators.required);
164
+ }
165
+ });
166
+ this.mainForm = new FormGroup(newFormControls); // Create the typed FormGroup
167
+ }
168
+
169
+ private _injectComponentsIntoDom(components: { [key: string]: ComponentRef<LessonComponentInterface> }): void {
170
+ Object.entries(components).forEach(([name, componentRef]) => {
171
+ const elementRef = document.getElementById(name); // Find the placeholder span
172
+ if (elementRef) {
173
+ this._addComponentToNode(componentRef, elementRef);
174
+ } else {
175
+ console.warn(`Placeholder element with ID '${name}' not found in the DOM.`);
176
+ }
177
+ });
144
178
  }
145
179
 
146
- private addComponentToNode(componentRef: ComponentRef<LessonComponentInterface>, nodeDOM: any) {
180
+ private _addComponentToNode(componentRef: ComponentRef<LessonComponentInterface>, nodeDOM: HTMLElement): void {
147
181
  this.renderer.appendChild(nodeDOM, componentRef.location.nativeElement);
148
182
  }
149
183
 
150
- private createComponentReferenceWithJson(json: string): ComponentRef<LessonComponentInterface> {
151
- const lessonCodedConfig: LessonComponentConfiguration = JSON.parse(json);
184
+ private _createComponentReferenceWithJson(json: string, currentLesson: ILesson): ComponentRef<LessonComponentInterface> | null {
185
+ try {
186
+ let lessonCodedConfig: LessonComponentConfiguration = JSON.parse(json);
187
+ debugger;
188
+ // Attempt to find pre-configured component data in the lesson object by ID
189
+ if (lessonCodedConfig.id && currentLesson?.dynamicComponents[lessonCodedConfig.id]) {
190
+ const foundConfig = currentLesson.dynamicComponents[lessonCodedConfig.id];
191
+ if (foundConfig) {
192
+ // Merge configurations: Start with coded, override/add with found lesson config
193
+ lessonCodedConfig = { ...lessonCodedConfig, ...foundConfig };
194
+ }
195
+ }
152
196
 
153
- let componentConfig;
154
- if (this.lesson.components && lessonCodedConfig.id) {
155
- // TODO: retomar, Creo que esta linea es para reutilziar los componentes por id, pero si no tiene,
156
- componentConfig = this.lesson.components.find((comp) => comp.id === lessonCodedConfig.id);
157
- }
197
+ const LessonClass = getLessonComponentClass(<LessonComponentsType>lessonCodedConfig.component);
198
+
199
+ if (!LessonClass) {
200
+ console.error(`Component class not found for type: ${lessonCodedConfig.component}. JSON: ${json}`);
201
+ return null; // Return null if class doesn't exist
202
+ }
158
203
 
159
- const LessonClass = getLessonComponentClass(<LessonComponentsType>lessonCodedConfig.component);
204
+ // Create component instance
205
+ const componentRef = this.viewContainerRef.createComponent<LessonComponentInterface>(LessonClass);
160
206
 
161
- if (!LessonClass) {
162
- console.error('No existe este componente, revisa el codigo insertado' + json);
163
- // throw new Error('No existe este com revisa el codigo insertado');
164
- return null;
207
+ if (lessonCodedConfig.inputs) {
208
+ for (const key in lessonCodedConfig.inputs) {
209
+ if (lessonCodedConfig.inputs.hasOwnProperty(key)) {
210
+ componentRef.instance[key] = lessonCodedConfig.inputs[key];
211
+ }
212
+ }
213
+ }
214
+ // i think i can improve this to pass only settings not all config, Assign the configuration to the component instance
215
+ // settings data i defined in form interface, config will be all the component data including id and component type
216
+ if (lessonCodedConfig.settings) {
217
+ componentRef.instance.config = lessonCodedConfig;
218
+ }
219
+ return componentRef;
220
+ } catch (error) {
221
+ console.error(`Error processing component JSON: ${json}`, error);
222
+ return null; // Return null on JSON parsing or other errors
165
223
  }
166
-
167
- const componentRef = this.viewContainerRef.createComponent<LessonComponentInterface>(LessonClass);
168
- componentRef.instance.config = componentConfig || lessonCodedConfig;
169
- return componentRef;
170
224
  }
171
225
 
226
+ // --- Evaluation Logic ---
227
+
172
228
  public async evaluateForms() {
229
+ this.mainForm.markAllAsTouched(); // Mark all controls for validation feedback
230
+
173
231
  if (!this.mainForm.valid) {
174
- for (let controlName of Object.keys(this.mainForm.controls)) {
175
- this.components[controlName].instance.validate();
176
- }
177
- this.toastrService.warn({ subtitle: 'Por favor completa todos los formularios', title: 'Espera' });
232
+ // Trigger validation feedback on individual components visually if needed
233
+ Object.keys(this.mainForm.controls).forEach((controlName) => {
234
+ if (this.components[controlName]?.instance?.validate) {
235
+ this.components[controlName].instance.validate(); // Assuming validate method handles visual feedback
236
+ }
237
+ });
238
+ this.toastrService.warn({ subtitle: 'Por favor completa todos los ejercicios', title: 'Incompleto' });
178
239
  return;
179
240
  }
180
241
 
181
242
  const rates = { correct: 0, incorrect: 0, score: 0 };
182
243
 
183
- for (let controlName of Object.keys(this.mainForm.controls)) {
184
- const instance = this.components[controlName].instance;
185
- if (instance instanceof ComponentWithForm) {
186
- // TODO: quizá no necesito validar instanceof ComponentWithForm ya que se garantiza que todo en mainform tiene un control.
244
+ // Evaluate each component associated with a form control
245
+ Object.keys(this.mainForm.controls).forEach((controlName) => {
246
+ const instance = this.components[controlName]?.instance;
247
+ // Check if the instance has an evaluate method (duck typing is okay here)
248
+ if (instance && typeof instance.evaluate === 'function') {
187
249
  try {
188
250
  const result = instance.evaluate();
189
251
  if (result) {
@@ -192,80 +254,79 @@ export class DCLessonRendererComponent implements OnInit {
192
254
  rates.incorrect++;
193
255
  }
194
256
  } catch (err) {
195
- console.error('Validation method for instance is failing', instance);
257
+ console.error('Error during evaluation for component:', controlName, instance, err);
258
+ rates.incorrect++; // Count errors as incorrect
196
259
  }
197
260
  }
198
- }
199
- if (this.test) {
261
+ });
262
+
263
+ if (this.test()) {
264
+ // Access signal value
265
+ console.log('Test mode: Evaluation skipped saving.');
266
+ this.toastrService.info({ subtitle: `Test Results: ${rates.correct} correct, ${rates.incorrect} incorrect`, title: 'Test Mode' });
200
267
  return;
201
268
  }
202
- rates.score = rates.correct / (rates.correct + rates.incorrect);
203
269
 
204
- const status = rates.score > 0.7 ? 'passed' : 'failed';
270
+ const totalQuestions = rates.correct + rates.incorrect;
271
+ rates.score = totalQuestions > 0 ? rates.correct / totalQuestions : 0; // Avoid division by zero
205
272
 
206
- const takenLesson = { lessonId: this.lesson.id, status: status, score: rates.score };
207
- // TODO: implementar el servicio para guardar la lección tomada
208
- // await this.lessonService.saveTakenLesson(takenLesson);
273
+ const status = rates.score >= 0.7 ? 'passed' : 'failed'; // Use >= for threshold
209
274
 
275
+ const currentLesson = this.lesson();
276
+ if (!currentLesson) {
277
+ console.error('Cannot save result, lesson data is missing.');
278
+ this.toastrService.error({ subtitle: 'Cannot save result, lesson data missing.', title: 'Error' });
279
+ return;
280
+ }
281
+ const takenLesson = { lessonId: currentLesson.id, status: status, score: rates.score };
282
+
283
+ console.log('Lesson evaluation result:', takenLesson);
284
+ // TODO: Re-implement saving the taken lesson status via lessonService
285
+ // try {
286
+ // await this.lessonService.saveTakenLesson(takenLesson);
287
+ // if (status === 'passed') {
288
+ // this.toastrService.success({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%. Se guardó tu progreso.`, title: '¡Muy bien!' });
289
+ // } else {
290
+ // this.toastrService.warn({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%. Revisa tus respuestas.`, title: 'Casi lo logras' });
291
+ // }
292
+ // } catch (error) {
293
+ // console.error('Failed to save taken lesson', error);
294
+ // this.toastrService.error({ subtitle: 'No se pudo guardar tu progreso.', title: 'Error' });
295
+ // }
210
296
  if (status === 'passed') {
211
- this.toastrService.success({ subtitle: 'Aprobaste, se guardó tu calificación', title: 'Muy bien' });
297
+ this.toastrService.success({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%.`, title: '¡Muy bien!' });
212
298
  } else {
213
- this.toastrService.warn({ subtitle: 'Revisa tus respuestas', title: 'Algunas preguntas no son correctas' });
299
+ this.toastrService.warn({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%. Revisa tus respuestas.`, title: 'Casi lo logras' });
214
300
  }
215
301
  }
216
302
 
217
- public agentMasterLesson: IAgentCard;
218
-
219
- public evaluatorAgentCard: IMiniAgentCard;
303
+ // --- AI Chat Logic ---
220
304
 
221
305
  public async startAI() {
222
- // TODO: para refactorizar y utilizar las lecciones en otro componente, voy a necesitar pasar esto al servicio abstracto asi cada aplicación lo implementa.
223
- const lessonText = this.lessonService.extractTextFromHtml(this.lesson.textCoded);
224
-
225
- 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.
226
- Ask friendly questions throughout the conversation and help them learn English. Here is the lesson text the user just read:
227
- ${lessonText}
228
- In your next reply, start by greeting the user, asking something about the lesson, and then continue the conversation.`;
229
-
230
- // const user = this.userService.getUserSnapshot();
231
- alert('i need to fix this, need to find a way to pass user throug the service not the library, i think i already started something.');
232
- const user = {} as any;
233
- const targetLevel = parseInt(user.languageProgress[user.settings.targetLanguage].level);
234
-
235
- const langTargetDesc = LangCodeDescription[user.settings.targetLanguage];
236
- const langBaseDesc = LangCodeDescription[user.settings.baseLanguage];
237
-
238
- let userInformationPrompt = `
239
- User information: user name is ${user.personalData.firstname} ${user.personalData.lastname}, their native language is ${langBaseDesc},
240
- and right now is learning ${langTargetDesc}, their current level is ${user.languageProgress[user.settings.targetLanguage].level} out of 5.`;
241
-
242
- if (targetLevel <= 2) {
243
- userInformationPrompt += `\nUser is a beginner in english, always reply in ${langBaseDesc}, but during the conversation use simple words in ${langTargetDesc}`;
306
+ const currentLesson = this.lesson();
307
+ if (!currentLesson) {
308
+ console.error('Cannot start AI without a lesson.');
309
+ this.toastrService.error({ subtitle: 'Lesson data not available.', title: 'Cannot Start Chat' });
310
+ return;
244
311
  }
245
312
 
246
- console.log('lessonText', lessonText);
247
-
248
- const lessonAgentCard: IAgentCard = DefaultLessonAgentCard;
249
- lessonAgentCard.characterCard.data.scenario = scenario;
250
- lessonAgentCard.characterCard.data.post_history_instructions += `\n${userInformationPrompt}`;
251
- this.agentMasterLesson = lessonAgentCard;
252
-
253
- this.evaluatorAgentCard = this.getDefualtLessonEvaluatorAgentCard(lessonText);
254
- this.chatVisible = true;
255
- }
256
-
257
- private getDefualtLessonEvaluatorAgentCard(lessonText: string): IMiniAgentCard {
258
- return {
259
- expectedResponseType: `interface EvalResult {
260
- score: number; // Score of the user's response 0 to 3
261
- feedback: string; // Feedback of the user's understanding of the conversation
262
- }`,
263
- messages: [],
264
- model: { id: 'gpt-4o-mini', provider: 'openai' },
265
- sources: [lessonText],
266
- task: `User is reading a taking a lesson, now their are having a conversation,
267
- you have to evaluate the current conversation, and give a feedback of the user understanding of the lesson,
268
- this is the lesson: ${lessonText}`,
269
- };
313
+ console.log('Requesting agent cards from LessonAIService...');
314
+ try {
315
+ // Call the service to get the agent cards
316
+ const agentCards = await this.lessonAIService.generateAgentCards(currentLesson);
317
+
318
+ if (agentCards) {
319
+ this.agentMasterLesson.set(agentCards.masterAgent);
320
+ this.evaluatorAgentCard.set(agentCards.evaluatorAgent);
321
+ this.chatVisible.set(true);
322
+ console.log('Agent cards received and set.');
323
+ } else {
324
+ console.error('Failed to generate agent cards (service returned null).');
325
+ this.toastrService.error({ subtitle: 'Could not prepare the AI chat session.', title: 'Error' });
326
+ }
327
+ } catch (error) {
328
+ console.error('Error generating agent cards:', error);
329
+ this.toastrService.error({ subtitle: 'An error occurred while preparing the AI chat.', title: 'Error' });
330
+ }
270
331
  }
271
332
  }
@@ -1,5 +1,5 @@
1
1
  import { Component } from '@angular/core';
2
- import { CommonModule } from '@angular/common';
2
+
3
3
  import { ReactiveFormsModule } from '@angular/forms';
4
4
 
5
5
  @Component({
@@ -7,7 +7,7 @@ import { ReactiveFormsModule } from '@angular/forms';
7
7
  templateUrl: './lesson-form.component.html',
8
8
  styleUrls: ['./lesson-form.component.scss'],
9
9
  standalone: true,
10
- imports: [CommonModule, ReactiveFormsModule],
10
+ imports: [ReactiveFormsModule],
11
11
  })
12
12
  export class DCLessonFormComponent {
13
13
  // Form implementation will go here
@@ -1,7 +1,7 @@
1
- <dc-filter-bar [customFilters]="customFilters" (onSearch)="search($event)" (onFilterChange)="filterChanged($event)" (onNew)="newLesson()"></dc-filter-bar>
1
+ <dc-filter-bar [customFilters]="customFilters" (onFilterAction)="applyFilterBarEvent($event)" (onNew)="newLesson()"></dc-filter-bar>
2
2
 
3
3
  @if(viewType === 'table') {
4
- <app-quick-table [columns]="columns" [tableData]="lessons" [actions]="actions" (onAction)="doAction($event)"></app-quick-table>
4
+ <app-quick-table [columns]="columns" [tableData]="lessons" (onAction)="doOrEmitAction($event)"></app-quick-table>
5
5
  } @else {
6
6
  <div class="lesson-list-container">
7
7
  @if (!isLoadingLessons && lessons?.length > 0) { @for (lesson of lessons; track lesson._id) {
@@ -10,7 +10,7 @@
10
10
  [ngComponentOutlet]="cardComponent"
11
11
  [ngComponentOutletInputs]="{
12
12
  lesson: lesson,
13
- isAdmin: isAdmin
13
+ showOptions: showOptions
14
14
  }">
15
15
  </ng-container>
16
16
  } } @else {