@defikitdotnet/education-module-ai
Version:
AI Education Module using Agent Framework
581 lines (580 loc) • 41.3 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AiContentService = void 0;
var axios_1 = __importDefault(require("axios"));
/**
* Service for generating educational content using AI
*/
var AiContentService = /** @class */ (function () {
function AiContentService() {
this.apiKey = process.env.AI_API_KEY || "";
this.apiUrlBase =
process.env.AI_API_URL_BASE ||
"https://generativelanguage.googleapis.com/v1beta/models";
this.modelName = process.env.AI_MODEL_NAME || "gemini-2.0-flash";
// Log the API key being used (masking most of it for security)
var maskedKey = this.apiKey
? "".concat(this.apiKey.substring(0, 5), "...").concat(this.apiKey.substring(this.apiKey.length - 4))
: "undefined/empty";
console.log("[AiContentService] Initializing with API Key: ".concat(maskedKey, " for Model: ").concat(this.modelName, " at Base URL: ").concat(this.apiUrlBase));
if (!this.apiKey) {
console.warn("No AI API key provided. Content generation will not work.");
}
}
/**
* Generate a complete course structure with lectures and quizzes
*/
AiContentService.prototype.generateCourse = function (request) {
return __awaiter(this, void 0, void 0, function () {
var prompt_1, payload, fullApiUrl, response, candidates, content, course, error_1;
var _a, _b, _c;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
_d.trys.push([0, 2, , 3]);
if (!this.apiKey || this.apiKey.startsWith("sk-sample")) {
console.warn("Using sample course data since no valid API key is configured");
return [2 /*return*/, this.generateSampleCourse(request)];
}
prompt_1 = this.createCourseGenerationPrompt(request);
payload = {
contents: [
{
// No role needed if only one user message
parts: [{ text: prompt_1 }],
},
],
// Remove generationConfig and safetySettings for strict cURL adherence
};
fullApiUrl = "".concat(this.apiUrlBase, "/").concat(this.modelName, ":generateContent?key=").concat(this.apiKey);
console.log("[AiContentService] Sending request to:", fullApiUrl); // Log the full URL
return [4 /*yield*/, axios_1.default.post(fullApiUrl, // Use the explicitly constructed URL
payload, // Send simplified payload
{
headers: {
// No Authorization header needed for Gemini with key in URL
"Content-Type": "application/json",
},
})];
case 1:
response = _d.sent();
candidates = (_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.candidates;
if (!candidates ||
candidates.length === 0 ||
!((_c = (_b = candidates[0]) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.parts) ||
candidates[0].content.parts.length === 0) {
throw new Error("Invalid response structure received from Gemini API");
}
content = candidates[0].content.parts[0].text;
course = this.parseCourseResponse(content, request);
return [2 /*return*/, course];
case 2:
error_1 = _d.sent();
console.error("Error generating course:", error_1);
console.warn("Falling back to sample course data");
return [2 /*return*/, this.generateSampleCourse(request)];
case 3: return [2 /*return*/];
}
});
});
};
/**
* Generate a sample course when API access is not available
*/
AiContentService.prototype.generateSampleCourse = function (request) {
return {
title: request.title || "Sample Course",
description: "A comprehensive sample course on ".concat(request.title || "Sample Course", " for ").concat(request.level, " learners, covering ").concat(request.subject || "General", ". This includes introductory concepts and core principles."),
level: request.level,
sections: [
{
title: "Getting Started",
description: "Introduction to the fundamental concepts",
orderIndex: 0,
lectures: [
{
title: "Introduction",
content: "# Introduction\n\nWelcome to this course! In this section, we'll explore the basic concepts needed to understand the subject matter.\n\n## What you'll learn\n\n- Core principles\n- Basic techniques\n- Foundational knowledge",
type: "text",
orderIndex: 0,
},
{
title: "Setting Up Your Environment",
content: "# Setting Up Your Environment\n\nIn this lecture, we'll go through the step-by-step process of setting up your development environment.\n\n## Required Tools\n\n1. Text editor or IDE\n2. Command line interface\n3. Version control system",
type: "text",
orderIndex: 1,
},
],
quiz: {
title: "Section 1 Quiz",
description: "Test your knowledge of the introductory concepts",
questions: [
{
question: "What is the main purpose of this course?",
options: [
"To introduce advanced concepts",
"To provide foundational knowledge",
"To explore theoretical frameworks",
"To demonstrate practical applications",
],
answer: "To provide foundational knowledge",
explanation: "This course aims to provide learners with the foundational knowledge needed to understand the subject.",
},
{
question: "Which of the following is NOT a required tool for this course?",
options: [
"Text editor",
"Command line interface",
"Version control system",
"Database server",
],
answer: "Database server",
explanation: "While text editors, command line interfaces, and version control systems are essential, a database server wasn't listed as a required tool.",
},
],
},
},
{
title: "Core Concepts",
description: "Exploring the fundamental principles",
orderIndex: 1,
lectures: [
{
title: "Key Principles",
content: "# Key Principles\n\nIn this lecture, we'll discuss the core principles that form the foundation of our subject.\n\n## Main Principles\n\n1. Principle One: Description and explanation\n2. Principle Two: Description and explanation\n3. Principle Three: Description and explanation",
type: "text",
orderIndex: 0,
},
{
title: "Practical Applications",
content: "# Practical Applications\n\nNow that we understand the key principles, let's look at how they're applied in real-world scenarios.\n\n## Case Studies\n\n1. Case Study 1: Description and learnings\n2. Case Study 2: Description and learnings",
type: "text",
orderIndex: 1,
},
],
quiz: {
title: "Section 2 Quiz",
description: "Test your understanding of core concepts",
questions: [
{
question: "Which of the following best describes Principle One?",
options: [
"A theoretical framework",
"A practical methodology",
"A foundational concept",
"An advanced technique",
],
answer: "A foundational concept",
explanation: "Principle One is described as a foundational concept that forms the basis of our understanding.",
},
{
question: "What is the main benefit of studying case studies?",
options: [
"They provide theoretical knowledge",
"They demonstrate practical applications",
"They simplify complex concepts",
"They challenge existing frameworks",
],
answer: "They demonstrate practical applications",
explanation: "Case studies help us understand how principles are applied in real-world scenarios.",
},
],
},
},
],
};
};
/**
* Generate just a single section with lectures and quiz
*/
AiContentService.prototype.generateSection = function (courseId, title, description, orderIndex, numLectures, includeQuiz, numQuestions) {
return __awaiter(this, void 0, void 0, function () {
var prompt_2, payload, fullApiUrl, response, candidates, content, section, error_2;
var _a, _b, _c;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
_d.trys.push([0, 2, , 3]);
if (!this.apiKey || this.apiKey.startsWith("sk-sample")) {
console.warn("Using sample section data since no valid API key is configured");
return [2 /*return*/, this.generateSampleSection(title, description, orderIndex, numLectures, includeQuiz, numQuestions)];
}
prompt_2 = this.createSectionGenerationPrompt(title, description, numLectures, includeQuiz, numQuestions);
payload = {
contents: [
{
parts: [{ text: prompt_2 }],
},
],
// Remove generationConfig and safetySettings
};
fullApiUrl = "".concat(this.apiUrlBase, "/").concat(this.modelName, ":generateContent?key=").concat(this.apiKey);
console.log("[AiContentService] Sending request to:", fullApiUrl); // Log the full URL
return [4 /*yield*/, axios_1.default.post(fullApiUrl, payload, {
headers: {
// No Authorization header needed for Gemini with key in URL
"Content-Type": "application/json",
},
})];
case 1:
response = _d.sent();
candidates = (_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.candidates;
if (!candidates ||
candidates.length === 0 ||
!((_c = (_b = candidates[0]) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.parts) ||
candidates[0].content.parts.length === 0) {
throw new Error("Invalid response structure received from Gemini API for section generation");
}
content = candidates[0].content.parts[0].text;
section = this.parseSectionResponse(content, orderIndex);
return [2 /*return*/, section];
case 2:
error_2 = _d.sent();
console.error("Error generating section:", error_2);
console.warn("Falling back to sample section data");
return [2 /*return*/, this.generateSampleSection(title, description, orderIndex, numLectures, includeQuiz, numQuestions)];
case 3: return [2 /*return*/];
}
});
});
};
/**
* Generate a sample section when API access is not available
*/
AiContentService.prototype.generateSampleSection = function (title, description, orderIndex, numLectures, includeQuiz, numQuestions) {
if (numLectures === void 0) { numLectures = 3; }
if (includeQuiz === void 0) { includeQuiz = true; }
if (numQuestions === void 0) { numQuestions = 5; }
var lectures = [];
// Generate sample lectures
for (var i = 0; i < numLectures; i++) {
lectures.push({
title: "Lecture ".concat(i + 1, ": ").concat(title, " - Part ").concat(i + 1),
content: "# ".concat(title, " - Part ").concat(i + 1, "\n\nThis is sample content for lecture ").concat(i + 1, " of the ").concat(title, " section.\n\n## Key Points\n\n- Important point 1\n- Important point 2\n- Important point 3\n\n## Summary\n\nIn this lecture, we covered the key points of ").concat(title, " - Part ").concat(i + 1, "."),
type: "text",
orderIndex: i,
});
}
// Create the section object
var section = {
title: title,
description: description || "This section covers all aspects of ".concat(title),
orderIndex: orderIndex,
lectures: lectures,
};
// Add quiz if needed
if (includeQuiz) {
var questions = [];
// Generate sample questions
for (var i = 0; i < numQuestions; i++) {
questions.push({
question: "Sample question ".concat(i + 1, " about ").concat(title, "?"),
options: [
"Answer option A for question ".concat(i + 1),
"Answer option B for question ".concat(i + 1),
"Answer option C for question ".concat(i + 1),
"Answer option D for question ".concat(i + 1),
],
answer: "Answer option A for question ".concat(i + 1),
explanation: "This is the explanation for why option A is correct for question ".concat(i + 1, "."),
});
}
section.quiz = {
title: "".concat(title, " Quiz"),
description: "Test your knowledge of ".concat(title),
questions: questions,
};
}
return section;
};
/**
* Create a prompt for course generation
*/
AiContentService.prototype.createCourseGenerationPrompt = function (request) {
// Updated prompt instructions for AI to generate title and description
// Removed direct usage of request.title and request.subject for generation description
return "\n Generate a comprehensive online course based on the following user request and details:\n - User Prompt/Description: \"".concat(request.description, "\" \n - Target Level: ").concat(request.level, "\n ").concat(request.topics && request.topics.length > 0 ? "- Key Topics: ".concat(request.topics.join(", ")) : "", "\n\n **Your Task:**\n 1. **Generate a suitable and engaging Course Title** based on the user prompt and level.\n 2. **Generate a detailed and engaging Course Description** expanding on the user prompt, outlining what the course covers, target audience, and learning objectives.\n 3. Determine a relevant **Subject** for the course (e.g., Programming, History, Art, etc.).\n\n The course structure should be:\n - ").concat(request.numSections, " logical sections that build on each other.\n - Each section should have ").concat(request.numLecturesPerSection, " lectures.\n ").concat(request.includeQuizzes ? "- Each section should end with a quiz (".concat(request.numQuestionsPerQuiz, " questions with multiple-choice options).") : "", "\n\n Provide your response strictly in JSON format.\n **CRITICAL**: The entire response MUST be a single, valid JSON object parseable by standard JSON parsers. NO MARKDOWN FORMATTING OUTSIDE THE JSON BLOCK. START WITH { and END WITH }.\n - Pay EXTREME attention to escaping special characters within ALL string values.\n - **Newline characters MUST be escaped as \\\\n.** Example: \"This is line 1\\\\nThis is line 2\". LITERAL newlines are FORBIDDEN within strings.\n - **Double quotes MUST be escaped as \\\\\".** Example: \"He said \\\"Hello!\\\"\".\n - **Backslashes MUST be escaped as \\\\\\\\.** Example: \"The path is C:\\\\\\\\Users\\\\\\\\Example\".\n - **ESPECIALLY IMPORTANT**: Ensure that any quotes (\") or backslashes (\\) inside code examples, markdown, or complex text within the 'content', 'description', 'question', 'answer', 'explanation' fields are properly escaped. FAILURE TO DO SO WILL RESULT IN AN INVALID JSON.\n\n {\n \"title\": \"[Generated Course Title]\",\n \"description\": \"[Generated detailed course description]\",\n \"subject\": \"[Generated Subject]\", // Add the generated subject here\n \"level\": \"").concat(request.level, "\",\n \"sections\": [\n {\n \"title\": \"Section Title\",\n \"description\": \"Section description. Must be properly escaped.\",\n \"lectures\": [\n {\n \"title\": \"Lecture Title\",\n \"content\": \"Detailed lecture content in markdown format. ALL special characters (newlines, quotes, backslashes) MUST be properly escaped according to JSON rules (e.g., use \\\\n for newlines, \\\\\\\" for quotes).\",\n \"type\": \"text\" // or h5p, presentation\n }\n // ... more lectures\n ],\n \"quiz\": { // Omit this field entirely if request.includeQuizzes is false\n \"title\": \"Section Quiz\",\n \"description\": \"Test your knowledge. Must be properly escaped.\",\n \"questions\": [\n {\n \"question\": \"Question text. Must be properly escaped.\",\n \"options\": [\"Option A\", \"Option B\", \"Option C\", \"Option D\"],\n // \"correctAnswer\" field was used before, now aligning with section prompt to use \"answer\"\n \"answer\": \"Option A\", // Ensure this exactly matches one of the options. Must be properly escaped.\n \"explanation\": \"Why this is the correct answer. Must be properly escaped.\"\n }\n // ... more questions\n ]\n }\n }\n // ... more sections\n ]\n }");
};
/**
* Create a prompt for section generation
*/
AiContentService.prototype.createSectionGenerationPrompt = function (title, description, numLectures, includeQuiz, numQuestions) {
// Added more explicit examples and warnings for escaping
return "\n Create a detailed section for a course with the following details:\n\n - Section title: ".concat(title, "\n - Section description: ").concat(description, "\n\n The section should have ").concat(numLectures, " lectures.\n ").concat(includeQuiz ? "The section should end with a quiz containing ".concat(numQuestions, " multiple-choice questions.") : "Do not include a quiz.", "\n\n Please output the section in the following JSON structure:\n **CRITICAL**: The entire response MUST be a single, valid JSON object parseable by standard JSON parsers. NO MARKDOWN FORMATTING OUTSIDE THE JSON BLOCK. START WITH { and END WITH }.\n - Pay EXTREME attention to escaping special characters within ALL string values.\n - **Newline characters MUST be escaped as \\\\n.** Example: \"This is line 1\\\\nThis is line 2\". LITERAL newlines are FORBIDDEN within strings.\n - **Double quotes MUST be escaped as \\\\\".** Example: \"He said \\\"Hello!\\\"\".\n - **Backslashes MUST be escaped as \\\\\\\\.** Example: \"The path is C:\\\\\\\\Users\\\\\\\\Example\".\n - **ESPECIALLY IMPORTANT**: Ensure that any quotes (\") or backslashes (\\) inside code examples, markdown, or complex text within the 'content', 'description', 'question', 'answer', 'explanation' fields are properly escaped. FAILURE TO DO SO WILL RESULT IN AN INVALID JSON.\n\n {\n \"title\": \"Section Title\",\n \"description\": \"Section description. Must be properly escaped.\",\n \"lectures\": [\n {\n \"title\": \"Lecture Title\",\n \"content\": \"Detailed lecture content in markdown format. ALL special characters (newlines, quotes, backslashes) MUST be properly escaped according to JSON rules (e.g., use \\\\n for newlines, \\\\\\\" for quotes).\",\n \"type\": \"text\" // or h5p, presentation\n }\n // ... more lectures following the same format\n ],\n \"quiz\": { // Omit this field entirely if includeQuiz is false\n \"title\": \"Quiz Title\",\n \"description\": \"Quiz description. Must be properly escaped.\",\n \"questions\": [\n {\n \"question\": \"Question text. Must be properly escaped.\",\n \"options\": [\"Option A\", \"Option B\", \"Option C\", \"Option D\"],\n \"answer\": \"Correct option (exactly matching one of the options). Must be properly escaped.\",\n \"explanation\": \"Explanation of the correct answer. Must be properly escaped.\"\n }\n // ... more questions following the same format\n ]\n }\n }\n\n Ensure the content is educational, accurate, and appropriate for the section title and description.\n Make lecture content detailed and informative with examples and explanations.\n Each lecture should be substantial, with at least 3-4 paragraphs of content.\n Format lecture content using markdown for headings, lists, code blocks, etc., ensuring all markdown special characters are correctly represented within the escaped JSON string.\n ");
};
/**
* Parse the AI response into a structured course
*/
AiContentService.prototype.parseCourseResponse = function (content, request) {
var jsonString = ""; // Initialize jsonString
try {
console.log("[AiContentService] Raw AI Response for Course:", content); // Log raw response
// Revised JSON Extraction: Find the first '{' and the last '}'
var firstBrace = content.indexOf("{");
var lastBrace = content.lastIndexOf("}");
if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
jsonString = content.substring(firstBrace, lastBrace + 1).trim();
console.log("[AiContentService] Extracted JSON using Braces for Course.");
// Basic validation check on extracted string
if (!jsonString.startsWith("{") || !jsonString.endsWith("}")) {
console.error("[AiContentService] Extracted string using braces doesn't look like JSON object:", jsonString);
throw new Error("Extracted content using braces does not appear to be a valid JSON object.");
}
}
else {
// Fallback if braces aren't found - indicates a major deviation from expected format
console.error("[AiContentService] Failed to find JSON object using braces '{' and '}' in AI response:", content);
throw new Error("Could not extract JSON object from AI response (Could not find matching {}). Try checking the raw AI response log.");
}
console.log("[AiContentService] Attempting to parse JSON String for Course:", jsonString);
var parsedData = JSON.parse(jsonString);
// Add validation for essential fields
if (!parsedData.title ||
!parsedData.sections ||
!Array.isArray(parsedData.sections)) {
console.error("[AiContentService] Parsed course data missing essential fields:", parsedData);
throw new Error("Parsed course data is missing essential fields (title, sections)");
}
return {
title: parsedData.title,
description: parsedData.description,
subject: parsedData.subject,
level: parsedData.level || request.level,
sections: parsedData.sections
.map(function (section, index) {
if (!section || typeof section !== "object" || !section.title) {
console.warn("[AiContentService] Skipping invalid section structure at index ".concat(index, ":"), section);
return null;
}
var lectures = (Array.isArray(section.lectures) ? section.lectures : [])
.map(function (lecture, lectureIndex) {
if (!lecture || typeof lecture !== "object" || !lecture.title) {
console.warn("[AiContentService] Skipping invalid lecture structure in section ".concat(section.title, " at index ").concat(lectureIndex, ":"), lecture);
return null;
}
return {
title: lecture.title,
content: lecture.content || "",
type: lecture.type || "text",
orderIndex: lectureIndex,
};
})
.filter(function (lecture) { return lecture !== null; });
var quiz = undefined;
// Only process quiz if includeQuizzes was true in the request and the AI provided a quiz object
if (request.includeQuizzes &&
section.quiz &&
typeof section.quiz === "object") {
var questions = (Array.isArray(section.quiz.questions)
? section.quiz.questions
: [])
.map(function (q) {
// Added check for q.options being an array and having elements
if (!q ||
typeof q !== "object" ||
!q.question ||
!Array.isArray(q.options) ||
q.options.length === 0 ||
!q.answer) {
console.warn("[AiContentService] Skipping invalid question structure in quiz \"".concat(section.quiz.title || "Untitled Quiz", "\":"), q);
return null;
}
return {
question: q.question,
options: q.options,
// Use 'answer' field as specified in the updated prompt
answer: q.answer || "",
explanation: q.explanation || "",
};
})
.filter(function (question) { return question !== null; });
if (questions.length > 0) {
quiz = {
title: section.quiz.title || "".concat(section.title, " Quiz"),
description: section.quiz.description || "Test your knowledge",
questions: questions,
};
}
else {
console.warn("[AiContentService] Quiz \"".concat(section.quiz.title || "Untitled Quiz", "\" has no valid questions, omitting quiz."));
}
}
else if (section.quiz) {
// Log if AI provided a quiz when it wasn't requested or if the structure is invalid
console.warn("[AiContentService] Invalid or unexpected quiz structure in section ".concat(section.title, ", omitting quiz:"), section.quiz);
}
return {
title: section.title,
description: section.description || "",
orderIndex: index,
lectures: lectures,
quiz: quiz, // Assign the potentially undefined quiz
};
})
.filter(function (section) { return section !== null; }),
};
}
catch (error) {
console.error("[AiContentService] Error parsing AI course response:", error.message);
// Log the problematic string that caused the error
console.error("[AiContentService] Problematic JSON String causing parse error:", jsonString);
// Log the original raw content as well
console.error("[AiContentService] Original Raw Content leading to parse error:", content);
console.warn("[AiContentService] Falling back to sample course data due to parsing error");
return this.generateSampleCourse(request); // Fallback to sample
}
};
/**
* Parse the AI response into a structured section
*/
AiContentService.prototype.parseSectionResponse = function (content, orderIndex) {
var jsonString = ""; // Initialize jsonString
try {
console.log("[AiContentService] Raw AI Response for Section:", content); // Log raw response
// Revised JSON Extraction: Find the first '{' and the last '}'
var firstBrace = content.indexOf("{");
var lastBrace = content.lastIndexOf("}");
if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
jsonString = content.substring(firstBrace, lastBrace + 1).trim();
console.log("[AiContentService] Extracted JSON using Braces for Section.");
// Basic validation check on extracted string
if (!jsonString.startsWith("{") || !jsonString.endsWith("}")) {
console.error("[AiContentService] Extracted string using braces doesn't look like JSON object:", jsonString);
throw new Error("Extracted content using braces does not appear to be a valid JSON object.");
}
}
else {
// Fallback if braces aren't found
console.error("[AiContentService] Failed to find JSON object using braces '{' and '}' in AI response for section:", content);
throw new Error("Could not extract JSON object from AI section response (Could not find matching {}). Try checking the raw AI response log.");
}
console.log("[AiContentService] Attempting to parse JSON String for Section:", jsonString);
var sectionData_1 = JSON.parse(jsonString);
// Add validation for essential fields
if (!sectionData_1.title ||
!sectionData_1.lectures ||
!Array.isArray(sectionData_1.lectures)) {
console.error("[AiContentService] Parsed section data missing essential fields:", sectionData_1);
throw new Error("Parsed section data is missing essential fields (title, lectures)");
}
var lectures = sectionData_1.lectures
.map(function (lecture, index) {
if (!lecture || typeof lecture !== "object" || !lecture.title) {
console.warn("[AiContentService] Skipping invalid lecture structure in section ".concat(sectionData_1.title || "Untitled", " at index ").concat(index, ":"), lecture);
return null;
}
return {
title: lecture.title || "Lecture ".concat(index + 1),
content: lecture.content || "",
type: lecture.type || "text",
orderIndex: index,
};
})
.filter(function (lecture) { return lecture !== null; });
var quiz = undefined;
// Process quiz only if it exists and is a valid object
if (sectionData_1.quiz && typeof sectionData_1.quiz === "object") {
var questions = (Array.isArray(sectionData_1.quiz.questions)
? sectionData_1.quiz.questions
: [])
.map(function (q) {
// Added check for q.options being an array and having elements
if (!q ||
typeof q !== "object" ||
!q.question ||
!Array.isArray(q.options) ||
q.options.length === 0 ||
!q.answer) {
console.warn("[AiContentService] Skipping invalid question structure in quiz \"".concat(sectionData_1.quiz.title || "Untitled Quiz", "\":"), q);
return null;
}
return {
question: q.question || "",
options: q.options,
answer: q.answer || "", // Use 'answer' field
explanation: q.explanation || "",
};
})
.filter(function (question) { return question !== null; });
if (questions.length > 0) {
quiz = {
title: sectionData_1.quiz.title ||
"Generated Section ".concat(orderIndex + 1, " Quiz"),
description: sectionData_1.quiz.description || "Test your knowledge",
questions: questions,
};
}
else {
console.warn("[AiContentService] Quiz \"".concat(sectionData_1.quiz.title || "Untitled Quiz", "\" has no valid questions, omitting quiz."));
}
}
else if (sectionData_1.quiz) {
console.warn("[AiContentService] Invalid quiz structure in section ".concat(sectionData_1.title || "Untitled", ", omitting quiz:"), sectionData_1.quiz);
}
// Create the final section object
var section = {
title: sectionData_1.title, // Use title from parsed data
description: sectionData_1.description || "",
orderIndex: orderIndex,
lectures: lectures,
quiz: quiz, // Assign the potentially undefined quiz
};
return section;
}
catch (error) {
console.error("[AiContentService] Error parsing section response:", error.message);
// Log the problematic string that caused the error
console.error("[AiContentService] Problematic JSON String causing parse error:", jsonString);
// Log the original raw content as well
console.error("[AiContentService] Original Raw Content leading to parse error:", content);
// Fallback to generating a sample section with a clear title indicating failure
console.warn("[AiContentService] Falling back to sample section data for index ".concat(orderIndex, " due to parsing error."));
// Regenerate sample section with correct parameters based on what might have been intended
// Note: We don't have the original request details here easily, so generate a generic sample section
return this.generateSampleSection("Section ".concat(orderIndex + 1, " (Parsing Failed)"), "Error parsing content from AI. Displaying sample content.", orderIndex, 3, // Default number of lectures for sample
true // Default to include quiz for sample
);
}
};
return AiContentService;
}());
exports.AiContentService = AiContentService;