From 90b751d9c987228238fe36bc8ab9f88485b4ac08 Mon Sep 17 00:00:00 2001 From: Hamed Razizadeh Date: Wed, 25 Mar 2026 17:05:58 +0100 Subject: [PATCH 1/5] Task-1 --- task-1/Time.js | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/task-1/Time.js b/task-1/Time.js index 97ca7e2..cf6e0e6 100644 --- a/task-1/Time.js +++ b/task-1/Time.js @@ -1,3 +1,48 @@ export class Time { - // Your code here -} \ No newline at end of file + #secondsFromMidnight; + + constructor(hours, minutes, seconds) { + this.#secondsFromMidnight = hours * 3600 + minutes * 60 + seconds; + this.#normalize(); + } + + // Private helper to keep seconds in 0..86399 + #normalize() { + this.#secondsFromMidnight %= 86400; + if (this.#secondsFromMidnight < 0) { + this.#secondsFromMidnight += 86400; + } + } + + getHours() { + return Math.floor(this.#secondsFromMidnight / 3600); + } + + getMinutes() { + return Math.floor((this.#secondsFromMidnight % 3600) / 60); + } + + getSeconds() { + return this.#secondsFromMidnight % 60; + } + + addSeconds(seconds) { + this.#secondsFromMidnight += seconds; + this.#normalize(); + } + + addMinutes(minutes) { + this.addSeconds(minutes * 60); + } + + addHours(hours) { + this.addSeconds(hours * 3600); + } + + toString() { + const hh = String(this.getHours()).padStart(2, "0"); + const mm = String(this.getMinutes()).padStart(2, "0"); + const ss = String(this.getSeconds()).padStart(2, "0"); + return `${hh}:${mm}:${ss}`; + } +} From 9cd6d0ca3956fa27757da9b8fba89c5b0b13eee1 Mon Sep 17 00:00:00 2001 From: Hamed Razizadeh Date: Wed, 25 Mar 2026 17:31:14 +0100 Subject: [PATCH 2/5] task 2 --- task-2/.gitignore | 1 + task-2/index.js | 212 ++++++++++++++++++++++++++++++++++++++- task-2/package-lock.json | 63 ++++++++++++ task-2/package.json | 9 +- 4 files changed, 282 insertions(+), 3 deletions(-) create mode 100644 task-2/.gitignore create mode 100644 task-2/package-lock.json diff --git a/task-2/.gitignore b/task-2/.gitignore new file mode 100644 index 0000000..2eea525 --- /dev/null +++ b/task-2/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/task-2/index.js b/task-2/index.js index 02acffa..ed83578 100644 --- a/task-2/index.js +++ b/task-2/index.js @@ -1 +1,211 @@ -// Write your code here. You may create as many files as you like. +import readline from "readline"; +import chalk from "chalk"; + +// Create CLI interface for user input/output +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +// Track player's score +let score = 0; + +// Store selected topic and difficulty +let topic = ""; +let difficulty = "easy"; + +// Helper function to ask questions in CLI using Promises +function ask(text) { + return new Promise((resolve) => rl.question(text, resolve)); +} + +// Generate quiz questions (fake data for testing) +// This can later be replaced with an AI API call +function generateQuestions() { + return [ + { + question: `(${difficulty}) What is the capital of Netherlands?`, + answers: ["Rotterdam", "Amsterdam", "Utrecht", "Eindhoven"], + correct: 2, + }, + { + question: `(${difficulty}) 2 + 2 = ?`, + answers: ["3", "4", "5", "6"], + correct: 2, + }, + { + question: `(${difficulty}) Largest planet?`, + answers: ["Earth", "Mars", "Jupiter", "Venus"], + correct: 3, + }, + { + question: `(${difficulty}) HTML stands for?`, + answers: [ + "Hyper Trainer Marking Language", + "Hyper Text Markup Language", + "Hyper Text Marketing Language", + "Hyper Tool Markup Language", + ], + correct: 2, + }, + { + question: `(${difficulty}) CSS used for?`, + answers: ["Logic", "Structure", "Styling", "Database"], + correct: 3, + }, + { + question: `(${difficulty}) Node.js runs on?`, + answers: ["Browser", "Server", "Database", "OS"], + correct: 2, + }, + { + question: `(${difficulty}) Git command to clone?`, + answers: ["git push", "git clone", "git pull", "git add"], + correct: 2, + }, + { + question: `(${difficulty}) JS framework?`, + answers: ["React", "Laravel", "Django", "Flask"], + correct: 1, + }, + { + question: `(${difficulty}) Water freezes at?`, + answers: ["0°C", "10°C", "50°C", "100°C"], + correct: 1, + }, + { + question: `(${difficulty}) Color of sky?`, + answers: ["Green", "Blue", "Red", "Yellow"], + correct: 2, + }, + ]; +} + +// Ask a single question +async function askQuestion(q, index) { + // All available answer indexes + let available = [0, 1, 2, 3]; + + // Track if hint already used + let hintUsed = false; + + while (true) { + console.log(chalk.blue(`\nQuestion ${index + 1}: ${q.question}`)); + + // Show only available answers + available.forEach((i) => { + console.log(`${i + 1}. ${q.answers[i]}`); + }); + + const answer = await ask("Your answer (1-4, h for hint, q to quit): "); + + // Quit game + if (answer === "q") { + console.log(chalk.yellow("šŸ‘‹ Exiting quiz...")); + rl.close(); + process.exit(0); + } + + // Handle hint request + if (answer === "h" && !hintUsed) { + hintUsed = true; + + // Keep correct answer + one random wrong answer + const wrong = available.filter((i) => i !== q.correct - 1); + available = [ + q.correct - 1, + wrong[Math.floor(Math.random() * wrong.length)], + ]; + + console.log(chalk.yellow("šŸ’” Hint used! Two options removed.")); + continue; + } + + // Check answer + if (Number(answer) === q.correct) { + console.log(chalk.green("āœ… Correct!")); + score++; + } else { + console.log( + chalk.red( + `āŒ Wrong! Correct answer: ${q.correct}. ${q.answers[q.correct - 1]}`, + ), + ); + } + + break; + } +} + +// Ask user to choose quiz topic +async function chooseTopic() { + console.log("Choose topic:"); + console.log("1. General Knowledge"); + console.log("2. Programming"); + console.log("3. Geography"); + + const answer = await ask("Select topic (1-3 or q to quit): "); + + if (answer === "q") { + rl.close(); + process.exit(0); + } + + if (answer === "1") topic = "General Knowledge"; + else if (answer === "2") topic = "Programming"; + else topic = "Geography"; +} + +// Ask user to choose difficulty +async function chooseDifficulty() { + console.log("\nChoose difficulty:"); + console.log("1. Easy"); + console.log("2. Medium"); + console.log("3. Hard"); + + const answer = await ask("Select difficulty (1-3 or q to quit): "); + + if (answer === "q") { + rl.close(); + process.exit(0); + } + + if (answer === "1") difficulty = "easy"; + else if (answer === "2") difficulty = "medium"; + else difficulty = "hard"; +} + +// Main quiz flow +async function startQuiz() { + console.log(chalk.yellow("šŸŽ® AI Quiz Game")); + + // Choose topic and difficulty + await chooseTopic(); + await chooseDifficulty(); + + console.log( + chalk.cyan(`\nStarting quiz: ${topic} | Difficulty: ${difficulty}`), + ); + + // Generate questions + const questions = generateQuestions(); + + // Loop through questions + for (let i = 0; i < questions.length; i++) { + await askQuestion(questions[i], i); + } + + // Show final score + console.log(chalk.magenta(`\nšŸŽ‰ Final Score: ${score}/10`)); + + // Close CLI + rl.close(); +} + +// Start the game +startQuiz(); + +// Close CLI +rl.on("close", () => { + process.exit(0); +}); diff --git a/task-2/package-lock.json b/task-2/package-lock.json new file mode 100644 index 0000000..3741e8a --- /dev/null +++ b/task-2/package-lock.json @@ -0,0 +1,63 @@ +{ + "name": "task-2", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "task-2", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "chalk": "^5.6.2", + "dotenv": "^17.3.1", + "openai": "^6.32.0" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/openai": { + "version": "6.32.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.32.0.tgz", + "integrity": "sha512-j3k+BjydAf8yQlcOI7WUQMQTbbF5GEIMAE2iZYCOzwwB3S2pCheaWYp+XZRNAch4jWVc52PMDGRRjutao3lLCg==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + } + } +} diff --git a/task-2/package.json b/task-2/package.json index a992f45..53d958c 100644 --- a/task-2/package.json +++ b/task-2/package.json @@ -9,5 +9,10 @@ "keywords": [], "author": "", "license": "ISC", - "type": "module" -} \ No newline at end of file + "type": "module", + "dependencies": { + "chalk": "^5.6.2", + "dotenv": "^17.3.1", + "openai": "^6.32.0" + } +} From cfe7d10feb0d3f4d185f0b5b37c6afc4d9550d23 Mon Sep 17 00:00:00 2001 From: Hamed Razizadeh Date: Thu, 26 Mar 2026 13:29:16 +0100 Subject: [PATCH 3/5] fix LLM problem --- task-2/index.js | 163 ++++++++++++++++++++++-------------------------- 1 file changed, 75 insertions(+), 88 deletions(-) diff --git a/task-2/index.js b/task-2/index.js index ed83578..82ef062 100644 --- a/task-2/index.js +++ b/task-2/index.js @@ -1,123 +1,117 @@ import readline from "readline"; import chalk from "chalk"; +import OpenAI from "openai"; +import dotenv from "dotenv"; -// Create CLI interface for user input/output +// Load environment variables +dotenv.config(); + +// Create Groq client (OpenAI compatible) +const client = new OpenAI({ + apiKey: process.env.GROQ_API_KEY, + baseURL: "https://api.groq.com/openai/v1", +}); + +// CLI interface const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); -// Track player's score +// Track score let score = 0; -// Store selected topic and difficulty +// Selected options let topic = ""; let difficulty = "easy"; -// Helper function to ask questions in CLI using Promises +// Helper to ask CLI questions function ask(text) { return new Promise((resolve) => rl.question(text, resolve)); } -// Generate quiz questions (fake data for testing) -// This can later be replaced with an AI API call -function generateQuestions() { - return [ - { - question: `(${difficulty}) What is the capital of Netherlands?`, - answers: ["Rotterdam", "Amsterdam", "Utrecht", "Eindhoven"], - correct: 2, - }, - { - question: `(${difficulty}) 2 + 2 = ?`, - answers: ["3", "4", "5", "6"], - correct: 2, - }, - { - question: `(${difficulty}) Largest planet?`, - answers: ["Earth", "Mars", "Jupiter", "Venus"], - correct: 3, - }, - { - question: `(${difficulty}) HTML stands for?`, - answers: [ - "Hyper Trainer Marking Language", - "Hyper Text Markup Language", - "Hyper Text Marketing Language", - "Hyper Tool Markup Language", - ], - correct: 2, - }, - { - question: `(${difficulty}) CSS used for?`, - answers: ["Logic", "Structure", "Styling", "Database"], - correct: 3, - }, - { - question: `(${difficulty}) Node.js runs on?`, - answers: ["Browser", "Server", "Database", "OS"], - correct: 2, - }, - { - question: `(${difficulty}) Git command to clone?`, - answers: ["git push", "git clone", "git pull", "git add"], - correct: 2, - }, - { - question: `(${difficulty}) JS framework?`, - answers: ["React", "Laravel", "Django", "Flask"], - correct: 1, - }, - { - question: `(${difficulty}) Water freezes at?`, - answers: ["0°C", "10°C", "50°C", "100°C"], - correct: 1, - }, - { - question: `(${difficulty}) Color of sky?`, - answers: ["Green", "Blue", "Red", "Yellow"], - correct: 2, - }, - ]; +// Generate quiz questions using LLM +async function generateQuestions() { + console.log(chalk.yellow("šŸ¤– Generating questions with AI...\n")); + + const randomSeed = Math.floor(Math.random() * 100000); + + const response = await client.chat.completions.create({ + model: "llama-3.1-8b-instant", + temperature: 0.9, + messages: [ + { + role: "user", + content: `Generate 10 ${difficulty} quiz questions about ${topic}. +Avoid repeating common quiz questions. +Use randomness seed: ${randomSeed} + +Return ONLY JSON array. +No explanation. +No markdown. + +Format: +[ + { + "question": "text", + "answers": ["a","b","c","d"], + "correct": 1 + } +]`, + }, + ], + }); + + let text = response.choices[0].message.content; + + try { + // remove markdown code blocks if exist + text = text.replace(/```json/g, "").replace(/```/g, ""); + + // extract JSON array + const start = text.indexOf("["); + const end = text.lastIndexOf("]") + 1; + text = text.substring(start, end); + + return JSON.parse(text); + } catch (error) { + console.log(chalk.red("āŒ Failed to parse AI response")); + console.log(text); + process.exit(1); + } } -// Ask a single question +// Ask one question async function askQuestion(q, index) { - // All available answer indexes let available = [0, 1, 2, 3]; - - // Track if hint already used let hintUsed = false; while (true) { console.log(chalk.blue(`\nQuestion ${index + 1}: ${q.question}`)); - // Show only available answers available.forEach((i) => { console.log(`${i + 1}. ${q.answers[i]}`); }); const answer = await ask("Your answer (1-4, h for hint, q to quit): "); - // Quit game + // Quit if (answer === "q") { - console.log(chalk.yellow("šŸ‘‹ Exiting quiz...")); rl.close(); - process.exit(0); + return; } - // Handle hint request + // Hint if (answer === "h" && !hintUsed) { hintUsed = true; - // Keep correct answer + one random wrong answer const wrong = available.filter((i) => i !== q.correct - 1); available = [ q.correct - 1, wrong[Math.floor(Math.random() * wrong.length)], ]; - console.log(chalk.yellow("šŸ’” Hint used! Two options removed.")); + console.log(chalk.yellow("šŸ’” Hint used!")); continue; } @@ -137,7 +131,7 @@ async function askQuestion(q, index) { } } -// Ask user to choose quiz topic +// Choose topic async function chooseTopic() { console.log("Choose topic:"); console.log("1. General Knowledge"); @@ -148,7 +142,7 @@ async function chooseTopic() { if (answer === "q") { rl.close(); - process.exit(0); + return; } if (answer === "1") topic = "General Knowledge"; @@ -156,7 +150,7 @@ async function chooseTopic() { else topic = "Geography"; } -// Ask user to choose difficulty +// Choose difficulty async function chooseDifficulty() { console.log("\nChoose difficulty:"); console.log("1. Easy"); @@ -167,7 +161,7 @@ async function chooseDifficulty() { if (answer === "q") { rl.close(); - process.exit(0); + return; } if (answer === "1") difficulty = "easy"; @@ -179,7 +173,6 @@ async function chooseDifficulty() { async function startQuiz() { console.log(chalk.yellow("šŸŽ® AI Quiz Game")); - // Choose topic and difficulty await chooseTopic(); await chooseDifficulty(); @@ -187,25 +180,19 @@ async function startQuiz() { chalk.cyan(`\nStarting quiz: ${topic} | Difficulty: ${difficulty}`), ); - // Generate questions - const questions = generateQuestions(); + const questions = await generateQuestions(); - // Loop through questions for (let i = 0; i < questions.length; i++) { await askQuestion(questions[i], i); } - // Show final score console.log(chalk.magenta(`\nšŸŽ‰ Final Score: ${score}/10`)); - - // Close CLI rl.close(); } -// Start the game startQuiz(); -// Close CLI +// Close CLI properly rl.on("close", () => { process.exit(0); }); From 15b77b708991a960f38c1f21c505b058a3ddac83 Mon Sep 17 00:00:00 2001 From: Hamed Razizadeh Date: Thu, 26 Mar 2026 15:15:39 +0100 Subject: [PATCH 4/5] add GitHub LLM --- task-2/index.js | 299 ++++++++++++++++++++------------------- task-2/package-lock.json | 92 ++++++++++++ task-2/package.json | 1 + 3 files changed, 247 insertions(+), 145 deletions(-) diff --git a/task-2/index.js b/task-2/index.js index 82ef062..8c4538d 100644 --- a/task-2/index.js +++ b/task-2/index.js @@ -1,198 +1,207 @@ -import readline from "readline"; -import chalk from "chalk"; -import OpenAI from "openai"; import dotenv from "dotenv"; +import chalk from "chalk"; +import readline from "readline"; +import fetch from "node-fetch"; // npm i node-fetch -// Load environment variables dotenv.config(); -// Create Groq client (OpenAI compatible) -const client = new OpenAI({ - apiKey: process.env.GROQ_API_KEY, - baseURL: "https://api.groq.com/openai/v1", -}); +const GITHUB_TOKEN = process.env.GITHUB_TOKEN; + +if (!GITHUB_TOKEN) { + console.log(chalk.red("āš ļø Please set GITHUB_TOKEN in your .env file")); + process.exit(1); +} -// CLI interface const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); -// Track score -let score = 0; - -// Selected options -let topic = ""; -let difficulty = "easy"; - -// Helper to ask CLI questions -function ask(text) { - return new Promise((resolve) => rl.question(text, resolve)); +function ask(questionText) { + return new Promise((resolve) => rl.question(questionText, resolve)); } -// Generate quiz questions using LLM -async function generateQuestions() { - console.log(chalk.yellow("šŸ¤– Generating questions with AI...\n")); - - const randomSeed = Math.floor(Math.random() * 100000); - - const response = await client.chat.completions.create({ - model: "llama-3.1-8b-instant", - temperature: 0.9, - messages: [ - { - role: "user", - content: `Generate 10 ${difficulty} quiz questions about ${topic}. -Avoid repeating common quiz questions. -Use randomness seed: ${randomSeed} - -Return ONLY JSON array. -No explanation. -No markdown. - -Format: +// Fallback questions +const fallbackQuestions = [ + { + question: "Which country has the city of Kyoto?", + answers: ["China", "Japan", "South Korea", "Thailand"], + correct: 2, + }, + { + question: "What is the largest planet in our solar system?", + answers: ["Earth", "Saturn", "Jupiter", "Mars"], + correct: 3, + }, + { + question: "Who wrote 'Hamlet'?", + answers: ["Shakespeare", "Dickens", "Austen", "Hemingway"], + correct: 1, + }, +]; + +// Generate questions via GitHub LLM +async function generateQuestions(topic, difficulty) { + console.log(chalk.yellow("\nšŸŽ² Generating questions via GitHub LLM...")); + + const prompt = ` +Generate 10 ${difficulty} multiple-choice questions about ${topic}. +Return ONLY valid JSON array in this format: [ { - "question": "text", - "answers": ["a","b","c","d"], + "question": "Your question here", + "answers": ["option1", "option2", "option3", "option4"], "correct": 1 } -]`, +] +Do not include extra text or markdown. +`; + + try { + const response = await fetch( + "https://api.github.com/openai/deployments/gpt-4o-mini/chat/completions", + { + method: "POST", + headers: { + Authorization: `Bearer ${GITHUB_TOKEN}`, + "Content-Type": "application/json", + Accept: "application/vnd.github+json", + }, + body: JSON.stringify({ + model: "gpt-4o-mini", + messages: [{ role: "user", content: prompt }], + temperature: 0.7, + }), }, - ], - }); + ); - let text = response.choices[0].message.content; + if (!response.ok) + throw new Error(`${response.status} ${response.statusText}`); - try { - // remove markdown code blocks if exist - text = text.replace(/```json/g, "").replace(/```/g, ""); - - // extract JSON array - const start = text.indexOf("["); - const end = text.lastIndexOf("]") + 1; - text = text.substring(start, end); - - return JSON.parse(text); - } catch (error) { - console.log(chalk.red("āŒ Failed to parse AI response")); - console.log(text); - process.exit(1); + const data = await response.json(); + + const text = data.choices?.[0]?.message?.content; + + if (!text) throw new Error("No content in LLM response"); + + // Try parse JSON + const match = text.match(/\[.*\]/s); + if (!match) throw new Error("Failed to parse JSON"); + + const questions = JSON.parse(match[0]); + + // Validate + if (!Array.isArray(questions) || questions.length !== 10) + throw new Error("Invalid question array"); + + return questions; + } catch (err) { + console.log( + chalk.yellow("āš ļø Could not generate questions from GitHub LLM."), + ); + console.log(chalk.yellow("Using fallback questions...")); + return fallbackQuestions; } } -// Ask one question -async function askQuestion(q, index) { - let available = [0, 1, 2, 3]; - let hintUsed = false; +// Show question, get answer, check correctness +async function askQuestion(qObj, index) { + console.log(chalk.blue(`\nQuestion ${index + 1}: ${qObj.question}`)); + qObj.answers.forEach((ans, i) => console.log(`${i + 1}. ${ans}`)); while (true) { - console.log(chalk.blue(`\nQuestion ${index + 1}: ${q.question}`)); - - available.forEach((i) => { - console.log(`${i + 1}. ${q.answers[i]}`); - }); - const answer = await ask("Your answer (1-4, h for hint, q to quit): "); - // Quit - if (answer === "q") { - rl.close(); - return; + if (answer.toLowerCase() === "q") { + console.log(chalk.cyan("\nExiting quiz...")); + return null; } - // Hint - if (answer === "h" && !hintUsed) { - hintUsed = true; - - const wrong = available.filter((i) => i !== q.correct - 1); - available = [ - q.correct - 1, - wrong[Math.floor(Math.random() * wrong.length)], - ]; - - console.log(chalk.yellow("šŸ’” Hint used!")); + if (answer.toLowerCase() === "h") { + const correctIndex = qObj.correct - 1; + const hide = [0, 1, 2, 3] + .filter((i) => i !== correctIndex) + .sort(() => 0.5 - Math.random()) + .slice(0, 2); + console.log(chalk.yellow("Hint: possible answers:")); + qObj.answers.forEach((ans, i) => { + if (!hide.includes(i)) console.log(`${i + 1}. ${ans}`); + }); continue; } - // Check answer - if (Number(answer) === q.correct) { - console.log(chalk.green("āœ… Correct!")); - score++; + const num = parseInt(answer); + if ([1, 2, 3, 4].includes(num)) { + if (num === qObj.correct) { + console.log(chalk.green("āœ… Correct!")); + return 1; + } else { + console.log( + chalk.red( + `āŒ Wrong! Correct answer: ${qObj.correct}. ${qObj.answers[qObj.correct - 1]}`, + ), + ); + return 0; + } } else { - console.log( - chalk.red( - `āŒ Wrong! Correct answer: ${q.correct}. ${q.answers[q.correct - 1]}`, - ), - ); + console.log(chalk.red("āš ļø Please enter 1-4, h for hint, or q to quit.")); } - - break; } } -// Choose topic -async function chooseTopic() { - console.log("Choose topic:"); - console.log("1. General Knowledge"); - console.log("2. Programming"); - console.log("3. Geography"); - - const answer = await ask("Select topic (1-3 or q to quit): "); +// Main game +async function startQuiz() { + console.log(chalk.cyan("šŸŽ® AI Quiz Game")); - if (answer === "q") { - rl.close(); - return; + const topics = ["General Knowledge", "Programming", "Geography"]; + topics.forEach((t, i) => console.log(`${i + 1}. ${t}`)); + let topic = ""; + while (true) { + const t = await ask("Select topic (1-3 or q to quit): "); + if (t.toLowerCase() === "q") return rl.close(); + const idx = parseInt(t); + if (idx >= 1 && idx <= topics.length) { + topic = topics[idx - 1]; + break; + } } - if (answer === "1") topic = "General Knowledge"; - else if (answer === "2") topic = "Programming"; - else topic = "Geography"; -} - -// Choose difficulty -async function chooseDifficulty() { - console.log("\nChoose difficulty:"); - console.log("1. Easy"); - console.log("2. Medium"); - console.log("3. Hard"); - - const answer = await ask("Select difficulty (1-3 or q to quit): "); - - if (answer === "q") { - rl.close(); - return; + const difficulties = ["easy", "medium", "hard"]; + difficulties.forEach((d, i) => + console.log(`${i + 1}. ${d[0].toUpperCase() + d.slice(1)}`), + ); + let difficulty = ""; + while (true) { + const d = await ask("Select difficulty (1-3 or q to quit): "); + if (d.toLowerCase() === "q") return rl.close(); + const idx = parseInt(d); + if (idx >= 1 && idx <= difficulties.length) { + difficulty = difficulties[idx - 1]; + break; + } } - if (answer === "1") difficulty = "easy"; - else if (answer === "2") difficulty = "medium"; - else difficulty = "hard"; -} - -// Main quiz flow -async function startQuiz() { - console.log(chalk.yellow("šŸŽ® AI Quiz Game")); - - await chooseTopic(); - await chooseDifficulty(); - console.log( chalk.cyan(`\nStarting quiz: ${topic} | Difficulty: ${difficulty}`), ); - const questions = await generateQuestions(); + const questions = await generateQuestions(topic, difficulty); + let score = 0; for (let i = 0; i < questions.length; i++) { - await askQuestion(questions[i], i); + const result = await askQuestion(questions[i], i); + if (result === null) break; + score += result; + console.log(chalk.magenta(`Current score: ${score}`)); } - console.log(chalk.magenta(`\nšŸŽ‰ Final Score: ${score}/10`)); + console.log( + chalk.cyan( + `\nšŸ† Quiz finished! Your score: ${score} / ${questions.length}`, + ), + ); rl.close(); } +rl.on("close", () => process.exit(0)); startQuiz(); - -// Close CLI properly -rl.on("close", () => { - process.exit(0); -}); diff --git a/task-2/package-lock.json b/task-2/package-lock.json index 3741e8a..bfb6502 100644 --- a/task-2/package-lock.json +++ b/task-2/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "chalk": "^5.6.2", "dotenv": "^17.3.1", + "node-fetch": "^3.3.2", "openai": "^6.32.0" } }, @@ -26,6 +27,15 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/dotenv": { "version": "17.3.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", @@ -38,6 +48,79 @@ "url": "https://dotenvx.com" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/openai": { "version": "6.32.0", "resolved": "https://registry.npmjs.org/openai/-/openai-6.32.0.tgz", @@ -58,6 +141,15 @@ "optional": true } } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } } } } diff --git a/task-2/package.json b/task-2/package.json index 53d958c..2d3d3c0 100644 --- a/task-2/package.json +++ b/task-2/package.json @@ -13,6 +13,7 @@ "dependencies": { "chalk": "^5.6.2", "dotenv": "^17.3.1", + "node-fetch": "^3.3.2", "openai": "^6.32.0" } } From 397342818dd2eda6a6aed286cc01740d080338da Mon Sep 17 00:00:00 2001 From: Hamed Razizadeh Date: Thu, 26 Mar 2026 16:06:10 +0100 Subject: [PATCH 5/5] fix URL Problem --- task-2/index.js | 256 +++++++++++++++++++++++++++--------------------- 1 file changed, 147 insertions(+), 109 deletions(-) diff --git a/task-2/index.js b/task-2/index.js index 8c4538d..bb4cd02 100644 --- a/task-2/index.js +++ b/task-2/index.js @@ -1,207 +1,245 @@ +// =============================================== +// AI QUIZ GAME — GitHub Models Edition +// Final Clean Version with English Comments +// =============================================== + +import OpenAI from "openai"; import dotenv from "dotenv"; -import chalk from "chalk"; import readline from "readline"; -import fetch from "node-fetch"; // npm i node-fetch +import chalk from "chalk"; +// Load environment variables dotenv.config(); +// Read GitHub token const GITHUB_TOKEN = process.env.GITHUB_TOKEN; if (!GITHUB_TOKEN) { - console.log(chalk.red("āš ļø Please set GITHUB_TOKEN in your .env file")); + console.error("āš ļø Please set GITHUB_TOKEN in your .env file"); process.exit(1); } +// ---------------------------------------------------- +// Initialize GitHub Models client +// IMPORTANT: baseURL must stop at /inference +// The SDK automatically appends /chat/completions +// ---------------------------------------------------- +const client = new OpenAI({ + baseURL: "https://models.github.ai/inference", + apiKey: GITHUB_TOKEN, +}); + +// CLI interface const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); +// Helper for asking questions in CLI function ask(questionText) { return new Promise((resolve) => rl.question(questionText, resolve)); } -// Fallback questions -const fallbackQuestions = [ - { - question: "Which country has the city of Kyoto?", - answers: ["China", "Japan", "South Korea", "Thailand"], - correct: 2, - }, - { - question: "What is the largest planet in our solar system?", - answers: ["Earth", "Saturn", "Jupiter", "Mars"], - correct: 3, - }, - { - question: "Who wrote 'Hamlet'?", - answers: ["Shakespeare", "Dickens", "Austen", "Hemingway"], - correct: 1, - }, -]; - -// Generate questions via GitHub LLM +let score = 0; + +// ---------------------------------------------------- +// Generate quiz questions using GitHub LLM +// Includes JSON cleanup and validation +// ---------------------------------------------------- async function generateQuestions(topic, difficulty) { - console.log(chalk.yellow("\nšŸŽ² Generating questions via GitHub LLM...")); + console.log("\nšŸŽ² Generating questions via GitHub LLM..."); const prompt = ` -Generate 10 ${difficulty} multiple-choice questions about ${topic}. -Return ONLY valid JSON array in this format: -[ - { - "question": "Your question here", - "answers": ["option1", "option2", "option3", "option4"], - "correct": 1 - } -] -Do not include extra text or markdown. +Generate exactly 10 ${difficulty} quiz questions about ${topic}. +Format each question EXACTLY like this: + +Q1: What is ...? +A) Option 1 +B) Option 2 +C) Option 3 +D) Option 4 +Correct: B + +Do NOT use JSON. +Do NOT add explanations. +Do NOT add extra text. `; try { - const response = await fetch( - "https://api.github.com/openai/deployments/gpt-4o-mini/chat/completions", - { - method: "POST", - headers: { - Authorization: `Bearer ${GITHUB_TOKEN}`, - "Content-Type": "application/json", - Accept: "application/vnd.github+json", - }, - body: JSON.stringify({ - model: "gpt-4o-mini", - messages: [{ role: "user", content: prompt }], - temperature: 0.7, - }), - }, - ); - - if (!response.ok) - throw new Error(`${response.status} ${response.statusText}`); - - const data = await response.json(); - - const text = data.choices?.[0]?.message?.content; - - if (!text) throw new Error("No content in LLM response"); - - // Try parse JSON - const match = text.match(/\[.*\]/s); - if (!match) throw new Error("Failed to parse JSON"); - - const questions = JSON.parse(match[0]); - - // Validate - if (!Array.isArray(questions) || questions.length !== 10) - throw new Error("Invalid question array"); + const response = await client.chat.completions.create({ + model: "openai/gpt-4o-mini", + messages: [ + { role: "system", content: "Follow the format strictly." }, + { role: "user", content: prompt }, + ], + temperature: 0.8, + }); + + const text = response.choices[0].message.content; + + // --------------------------------------------- + // Parse the text into JSON + // --------------------------------------------- + const blocks = text.split(/Q\d+:/).slice(1); + + const questions = blocks.map((block, index) => { + const lines = block.trim().split("\n"); + + const question = lines[0].trim(); + + const answers = [ + lines[1].replace("A)", "").trim(), + lines[2].replace("B)", "").trim(), + lines[3].replace("C)", "").trim(), + lines[4].replace("D)", "").trim(), + ]; + + const correctLine = lines.find((l) => l.startsWith("Correct")); + const correctLetter = correctLine.split(":")[1].trim(); + + const map = { A: 1, B: 2, C: 3, D: 4 }; + + return { + question, + answers, + correctAnswer: map[correctLetter], + }; + }); + + if (questions.length !== 10) { + throw new Error("Model did not return 10 questions"); + } return questions; } catch (err) { - console.log( - chalk.yellow("āš ļø Could not generate questions from GitHub LLM."), - ); - console.log(chalk.yellow("Using fallback questions...")); - return fallbackQuestions; + console.error("āš ļø Could not generate questions from GitHub LLM."); + console.error(chalk.yellow("Using fallback questions.")); + console.error(chalk.red(err.message)); + + return [ + { + question: "Which country has the city of Kyoto?", + answers: ["China", "Japan", "South Korea", "Thailand"], + correctAnswer: 2, + }, + { + question: "What is the largest planet in our solar system?", + answers: ["Earth", "Jupiter", "Saturn", "Mars"], + correctAnswer: 2, + }, + ]; } } -// Show question, get answer, check correctness +// ---------------------------------------------------- +// Ask a single question (supports hints) +// ---------------------------------------------------- async function askQuestion(qObj, index) { - console.log(chalk.blue(`\nQuestion ${index + 1}: ${qObj.question}`)); + console.log(`\nQuestion ${index + 1}: ${qObj.question}`); qObj.answers.forEach((ans, i) => console.log(`${i + 1}. ${ans}`)); while (true) { const answer = await ask("Your answer (1-4, h for hint, q to quit): "); + // Quit if (answer.toLowerCase() === "q") { - console.log(chalk.cyan("\nExiting quiz...")); + console.log("\nExiting quiz..."); + rl.close(); return null; } + // Hint if (answer.toLowerCase() === "h") { - const correctIndex = qObj.correct - 1; + const correctIndex = qObj.correctAnswer - 1; + + // Randomly hide 2 wrong answers const hide = [0, 1, 2, 3] .filter((i) => i !== correctIndex) .sort(() => 0.5 - Math.random()) .slice(0, 2); - console.log(chalk.yellow("Hint: possible answers:")); + + console.log("Hint: possible answers:"); qObj.answers.forEach((ans, i) => { if (!hide.includes(i)) console.log(`${i + 1}. ${ans}`); }); + continue; } + // Validate answer const num = parseInt(answer); + if ([1, 2, 3, 4].includes(num)) { - if (num === qObj.correct) { + if (num === qObj.correctAnswer) { console.log(chalk.green("āœ… Correct!")); - return 1; + score++; } else { console.log( chalk.red( - `āŒ Wrong! Correct answer: ${qObj.correct}. ${qObj.answers[qObj.correct - 1]}`, + `āŒ Wrong! Correct answer: ${qObj.correctAnswer}. ${qObj.answers[qObj.correctAnswer - 1]}`, ), ); - return 0; } + break; } else { - console.log(chalk.red("āš ļø Please enter 1-4, h for hint, or q to quit.")); + console.log("āš ļø Please enter 1-4, h for hint, or q to quit."); } } } -// Main game +// ---------------------------------------------------- +// Main quiz flow +// ---------------------------------------------------- async function startQuiz() { - console.log(chalk.cyan("šŸŽ® AI Quiz Game")); + console.log("šŸŽ® AI Quiz Game"); const topics = ["General Knowledge", "Programming", "Geography"]; topics.forEach((t, i) => console.log(`${i + 1}. ${t}`)); + + // Select topic let topic = ""; while (true) { - const t = await ask("Select topic (1-3 or q to quit): "); - if (t.toLowerCase() === "q") return rl.close(); - const idx = parseInt(t); - if (idx >= 1 && idx <= topics.length) { - topic = topics[idx - 1]; + const tInput = await ask("Select topic (1-3 or q to quit): "); + if (tInput.toLowerCase() === "q") return rl.close(); + const tNum = parseInt(tInput); + if (tNum >= 1 && tNum <= topics.length) { + topic = topics[tNum - 1]; break; } } + // Select difficulty const difficulties = ["easy", "medium", "hard"]; difficulties.forEach((d, i) => console.log(`${i + 1}. ${d[0].toUpperCase() + d.slice(1)}`), ); + let difficulty = ""; while (true) { - const d = await ask("Select difficulty (1-3 or q to quit): "); - if (d.toLowerCase() === "q") return rl.close(); - const idx = parseInt(d); - if (idx >= 1 && idx <= difficulties.length) { - difficulty = difficulties[idx - 1]; + const dInput = await ask("Select difficulty (1-3 or q to quit): "); + if (dInput.toLowerCase() === "q") return rl.close(); + const dNum = parseInt(dInput); + if (dNum >= 1 && dNum <= difficulties.length) { + difficulty = difficulties[dNum - 1]; break; } } - console.log( - chalk.cyan(`\nStarting quiz: ${topic} | Difficulty: ${difficulty}`), - ); + console.log(`\nStarting quiz: ${topic} | Difficulty: ${difficulty}`); const questions = await generateQuestions(topic, difficulty); - let score = 0; for (let i = 0; i < questions.length; i++) { - const result = await askQuestion(questions[i], i); - if (result === null) break; - score += result; - console.log(chalk.magenta(`Current score: ${score}`)); + const res = await askQuestion(questions[i], i); + if (res === null) break; } - console.log( - chalk.cyan( - `\nšŸ† Quiz finished! Your score: ${score} / ${questions.length}`, - ), - ); + console.log(`\nšŸ† Quiz finished! Your score: ${score} / ${questions.length}`); rl.close(); } +// Exit handler rl.on("close", () => process.exit(0)); + +// Start the game startQuiz();