@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.
- package/fesm2022/dataclouder-ngx-lessons.mjs +1205 -514
- package/fesm2022/dataclouder-ngx-lessons.mjs.map +1 -1
- package/lib/components/dc-lessons/dc-lesson-card/dc-lesson-card.component.d.ts +6 -8
- package/lib/components/dc-lessons/dc-lesson-component-adder/dc-lesson-component-adder.component.d.ts +11 -0
- package/lib/components/dc-lessons/dc-lesson-editor/dc-lesson-editor.component.d.ts +39 -38
- package/lib/components/dc-lessons/dc-lesson-metadata-editor/dc-lesson-metadata-editor.component.d.ts +22 -0
- package/lib/components/dc-lessons/dc-lesson-renderer/dc-lesson-renderer.component.d.ts +32 -37
- package/lib/components/dc-lessons/lesson-list/dc-lesson-list.component.d.ts +7 -17
- package/lib/components/lesson-mini-components/components/ComponentBuilder.d.ts +7 -2
- package/lib/components/lesson-mini-components/components/lessons.clases.d.ts +17 -42
- package/lib/components/lesson-mini-components/components/speaker/speaker-builder/speaker-builder.component.d.ts +13 -0
- package/lib/components/lesson-mini-components/components/speaker/speaker.component.d.ts +12 -0
- package/lib/services/lesson-ai.service.d.ts +18 -0
- package/lib/services/lesson-notion.service.d.ts +35 -0
- package/lib/services/lesson-utils.service.d.ts +34 -0
- package/package.json +3 -2
- package/src/lib/components/dc-lessons/dc-lesson-card/dc-lesson-card.component.html +40 -35
- package/src/lib/components/dc-lessons/dc-lesson-card/dc-lesson-card.component.scss +15 -2
- package/src/lib/components/dc-lessons/dc-lesson-card/dc-lesson-card.component.ts +16 -18
- package/src/lib/components/dc-lessons/dc-lesson-component-adder/dc-lesson-component-adder.component.css +1 -0
- package/src/lib/components/dc-lessons/dc-lesson-component-adder/dc-lesson-component-adder.component.html +46 -0
- package/src/lib/components/dc-lessons/dc-lesson-component-adder/dc-lesson-component-adder.component.ts +52 -0
- package/src/lib/components/dc-lessons/dc-lesson-editor/dc-lesson-editor.component.html +54 -92
- package/src/lib/components/dc-lessons/dc-lesson-editor/dc-lesson-editor.component.ts +268 -230
- package/src/lib/components/dc-lessons/dc-lesson-metadata-editor/dc-lesson-metadata-editor.component.css +1 -0
- package/src/lib/components/dc-lessons/dc-lesson-metadata-editor/dc-lesson-metadata-editor.component.html +72 -0
- package/src/lib/components/dc-lessons/dc-lesson-metadata-editor/dc-lesson-metadata-editor.component.ts +60 -0
- package/src/lib/components/dc-lessons/dc-lesson-renderer/dc-lesson-renderer.component.html +23 -27
- package/src/lib/components/dc-lessons/dc-lesson-renderer/dc-lesson-renderer.component.ts +247 -186
- package/src/lib/components/dc-lessons/lesson-form/lesson-form.component.ts +2 -2
- package/src/lib/components/dc-lessons/lesson-list/dc-lesson-list.component.html +3 -3
- package/src/lib/components/dc-lessons/lesson-list/dc-lesson-list.component.ts +28 -46
- package/src/lib/components/lesson-mini-components/components/ComponentBuilder.ts +23 -15
- package/src/lib/components/lesson-mini-components/components/lessons.clases.ts +32 -66
- package/src/lib/components/lesson-mini-components/components/selector/selector-builder/selector-builder.component.html +62 -58
- package/src/lib/components/lesson-mini-components/components/selector/selector-builder/selector-builder.component.ts +2 -2
- package/src/lib/components/lesson-mini-components/components/selector/selector.component.html +1 -2
- package/src/lib/components/lesson-mini-components/components/selector/selector.component.ts +2 -2
- package/src/lib/components/lesson-mini-components/components/speaker/speaker-builder/speaker-builder.component.html +5 -27
- package/src/lib/components/lesson-mini-components/components/speaker/speaker-builder/speaker-builder.component.ts +38 -25
- package/src/lib/components/lesson-mini-components/components/speaker/speaker.component.html +9 -7
- package/src/lib/components/lesson-mini-components/components/speaker/speaker.component.ts +30 -26
- package/src/lib/components/lesson-mini-components/components/translationSwitcher/translationSwitcher.component.ts +2 -2
- package/src/lib/components/lesson-mini-components/components/translationSwitcher/translationSwitcherBuilder/translationSwitcherBuilder.component.ts +2 -2
- package/src/lib/services/lesson-ai.service.ts +103 -0
- package/src/lib/services/lesson-notion.service.ts +161 -0
- package/src/lib/services/lesson-utils.service.ts +181 -0
- package/src/lib/components/lesson-mini-components/components/selector/selector-builder/selector-builder.component.spec.ts +0 -25
- package/src/lib/components/lesson-mini-components/components/speaker/speaker-builder/speaker-builder.component.spec.ts +0 -25
- package/src/lib/components/lesson-mini-components/components/speaker/speaker.component.spec.ts +0 -25
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import { Component, ComponentRef, ElementRef,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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 {
|
|
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: [
|
|
27
|
+
imports: [KeyValuePipe, ButtonModule, DCChatComponent, DrawerModule],
|
|
59
28
|
})
|
|
60
|
-
export class DCLessonRendererComponent
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
116
|
+
private _renderLesson(lessonData: ILesson): void {
|
|
117
|
+
this._clearLessonRendering(); // Clear previous state first
|
|
97
118
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
107
|
-
this.
|
|
126
|
+
// 2) Aggregate form controls from created components
|
|
127
|
+
this._aggregateFormControls(this.components);
|
|
108
128
|
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
139
|
+
const createdComponents: { [key: string]: ComponentRef<LessonComponentInterface> } = {};
|
|
120
140
|
|
|
121
|
-
const
|
|
141
|
+
const htmlContent = lessonData.textCoded.replace(r1, (_matching, jsonCoded) => {
|
|
122
142
|
const componentName = `dynamicComp${count}`;
|
|
123
143
|
count++;
|
|
124
|
-
const componentRef = this.
|
|
144
|
+
const componentRef = this._createComponentReferenceWithJson(jsonCoded, lessonData);
|
|
125
145
|
if (!componentRef) {
|
|
126
|
-
|
|
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
|
-
|
|
149
|
+
createdComponents[componentName] = componentRef;
|
|
150
|
+
return `<span id="${componentName}"></span>`; // Return span placeholder
|
|
135
151
|
});
|
|
136
152
|
|
|
137
|
-
|
|
153
|
+
return { htmlContent, components: createdComponents };
|
|
154
|
+
}
|
|
138
155
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
180
|
+
private _addComponentToNode(componentRef: ComponentRef<LessonComponentInterface>, nodeDOM: HTMLElement): void {
|
|
147
181
|
this.renderer.appendChild(nodeDOM, componentRef.location.nativeElement);
|
|
148
182
|
}
|
|
149
183
|
|
|
150
|
-
private
|
|
151
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
204
|
+
// Create component instance
|
|
205
|
+
const componentRef = this.viewContainerRef.createComponent<LessonComponentInterface>(LessonClass);
|
|
160
206
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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('
|
|
257
|
+
console.error('Error during evaluation for component:', controlName, instance, err);
|
|
258
|
+
rates.incorrect++; // Count errors as incorrect
|
|
196
259
|
}
|
|
197
260
|
}
|
|
198
|
-
}
|
|
199
|
-
|
|
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
|
|
270
|
+
const totalQuestions = rates.correct + rates.incorrect;
|
|
271
|
+
rates.score = totalQuestions > 0 ? rates.correct / totalQuestions : 0; // Avoid division by zero
|
|
205
272
|
|
|
206
|
-
const
|
|
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:
|
|
297
|
+
this.toastrService.success({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%.`, title: '¡Muy bien!' });
|
|
212
298
|
} else {
|
|
213
|
-
this.toastrService.warn({ subtitle:
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
public evaluatorAgentCard: IMiniAgentCard;
|
|
303
|
+
// --- AI Chat Logic ---
|
|
220
304
|
|
|
221
305
|
public async startAI() {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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('
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
|
|
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
|
-
|
|
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: [
|
|
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" (
|
|
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"
|
|
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
|
-
|
|
13
|
+
showOptions: showOptions
|
|
14
14
|
}">
|
|
15
15
|
</ng-container>
|
|
16
16
|
} } @else {
|