Spaces:
Sleeping
Sleeping
| import { Document, Packer, Paragraph, TextRun, HeadingLevel, AlignmentType } from 'docx'; | |
| import { GeneratedQuestion } from '@/types/quiz'; | |
| export async function exportQuestionsToDocx( | |
| questions: GeneratedQuestion[], | |
| filename: string = 'quiz-questions.docx', | |
| includeAnswers: boolean = true, | |
| article?: string | |
| ) { | |
| // Create document | |
| const doc = new Document({ | |
| sections: [{ | |
| properties: {}, | |
| children: [ | |
| // Title | |
| new Paragraph({ | |
| text: 'Quiz Export', | |
| heading: HeadingLevel.HEADING_1, | |
| alignment: AlignmentType.CENTER, | |
| spacing: { | |
| after: 400, | |
| } | |
| }), | |
| // Date created | |
| new Paragraph({ | |
| text: `Generated: ${new Date().toLocaleDateString()}`, | |
| alignment: AlignmentType.CENTER, | |
| spacing: { | |
| after: 200 | |
| } | |
| }), | |
| // Total questions | |
| new Paragraph({ | |
| text: `Total Questions: ${questions.length}`, | |
| alignment: AlignmentType.CENTER, | |
| spacing: { | |
| after: 300 | |
| } | |
| }), | |
| // Source Article Section (if provided) | |
| ...(article && article.trim().length > 0 | |
| ? [ | |
| new Paragraph({ | |
| text: 'Source Article', | |
| heading: HeadingLevel.HEADING_2, | |
| spacing: { before: 300, after: 200 }, | |
| }), | |
| ...article.split(/\n{2,}/).flatMap((block) => [ | |
| new Paragraph({ | |
| children: [new TextRun({ text: block.replace(/\n/g, ' '), size: 22 })], | |
| spacing: { after: 150 }, | |
| }), | |
| ]), | |
| new Paragraph({ | |
| children: [new TextRun({ text: '─'.repeat(50), color: 'CCCCCC' })], | |
| spacing: { before: 200, after: 200 }, | |
| }), | |
| new Paragraph({ | |
| text: 'Questions', | |
| heading: HeadingLevel.HEADING_2, | |
| spacing: { before: 100, after: 200 }, | |
| }), | |
| ] | |
| : []), | |
| // Questions | |
| ...questions.map((question, index) => | |
| renderQuestion(question, index + 1, includeAnswers) | |
| ).flat() | |
| ] | |
| }] | |
| }); | |
| // Generate and download the document | |
| const blob = await Packer.toBlob(doc); | |
| const url = URL.createObjectURL(blob); | |
| const link = document.createElement('a'); | |
| link.href = url; | |
| link.download = filename; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| URL.revokeObjectURL(url); | |
| } | |
| function renderQuestion(question: GeneratedQuestion, questionNumber: number, includeAnswers: boolean = true): Paragraph[] { | |
| const elements: Paragraph[] = []; | |
| // Question number and type | |
| elements.push( | |
| new Paragraph({ | |
| children: [ | |
| new TextRun({ | |
| text: `Question ${questionNumber}`, | |
| bold: true, | |
| size: 24 | |
| }), | |
| new TextRun({ | |
| text: ` (${question.type})`, | |
| size: 20, | |
| color: '666666' | |
| }) | |
| ], | |
| spacing: { | |
| before: 300, | |
| after: 150 | |
| } | |
| }) | |
| ); | |
| // Question stem | |
| elements.push( | |
| new Paragraph({ | |
| children: [ | |
| new TextRun({ | |
| text: question.stem, | |
| size: 22 | |
| }) | |
| ], | |
| spacing: { | |
| after: 200 | |
| } | |
| }) | |
| ); | |
| // Points | |
| elements.push( | |
| new Paragraph({ | |
| children: [ | |
| new TextRun({ | |
| text: `Points: ${question.points}`, | |
| size: 18, | |
| italics: true, | |
| color: '666666' | |
| }) | |
| ], | |
| spacing: { | |
| after: 150 | |
| } | |
| }) | |
| ); | |
| // Question content | |
| elements.push(...renderQuestionContent(question, includeAnswers)); | |
| // Separator | |
| elements.push( | |
| new Paragraph({ | |
| children: [ | |
| new TextRun({ | |
| text: '─'.repeat(50), | |
| color: 'CCCCCC' | |
| }) | |
| ], | |
| spacing: { | |
| before: 200, | |
| after: 200 | |
| } | |
| }) | |
| ); | |
| return elements; | |
| } | |
| function renderQuestionContent(question: GeneratedQuestion, includeAnswers: boolean = true): Paragraph[] { | |
| const elements: Paragraph[] = []; | |
| // Handle the actual content structure for multiple choice questions | |
| if (question.content && typeof question.content === 'object') { | |
| // Handle multiple choice questions with the actual structure | |
| if ('Options' in question.content && question.content.Options) { | |
| // Options is an object with keys A, B, C, D | |
| const options = question.content.Options as { A: string; B: string; C: string; D: string; }; | |
| const correctAnswer = (question.content as { Answer?: string }).Answer; | |
| Object.entries(options).forEach(([key, value]) => { | |
| const isCorrect = key === correctAnswer; | |
| elements.push( | |
| new Paragraph({ | |
| children: [ | |
| new TextRun({ | |
| text: `${key}. ${value}`, | |
| size: 20, | |
| color: includeAnswers && isCorrect ? '008000' : '000000', | |
| bold: includeAnswers && isCorrect | |
| }) | |
| ], | |
| spacing: { | |
| after: 100 | |
| } | |
| }) | |
| ); | |
| }); | |
| // Add answer key if including answers | |
| if (includeAnswers && correctAnswer) { | |
| elements.push( | |
| new Paragraph({ | |
| children: [ | |
| new TextRun({ | |
| text: `Answer: ${correctAnswer}`, | |
| bold: true, | |
| size: 20, | |
| color: '008000' | |
| }) | |
| ], | |
| spacing: { | |
| before: 200, | |
| after: 200 | |
| } | |
| }) | |
| ); | |
| } | |
| } | |
| } | |
| return elements; | |
| } | |
| export async function exportQuestionsToJson(questions: GeneratedQuestion[], filename: string = 'quiz-questions.json') { | |
| const data = { | |
| metadata: { | |
| exportDate: new Date().toISOString(), | |
| totalQuestions: questions.length, | |
| totalPoints: questions.reduce((sum, q) => sum + q.points, 0) | |
| }, | |
| questions: questions.map(q => ({ | |
| id: q.id, | |
| type: q.type, | |
| stem: q.stem, | |
| content: q.content, | |
| points: q.points, | |
| createdAt: q.createdAt | |
| })) | |
| }; | |
| const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); | |
| const url = URL.createObjectURL(blob); | |
| const link = document.createElement('a'); | |
| link.href = url; | |
| link.download = filename; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| URL.revokeObjectURL(url); | |
| } | |
| export async function exportQuestionsToMarkdown( | |
| questions: GeneratedQuestion[], | |
| filename: string = 'quiz-questions.md', | |
| includeAnswers: boolean = true | |
| ) { | |
| let markdown = '# Quiz Questions\n\n'; | |
| markdown += `Generated: ${new Date().toLocaleDateString()}\n\n`; | |
| markdown += `Total Questions: ${questions.length}\n\n`; | |
| markdown += '---\n\n'; | |
| questions.forEach((question, index) => { | |
| markdown += `## Question ${index + 1} (${question.type})\n\n`; | |
| markdown += `**Points:** ${question.points}\n\n`; | |
| markdown += `${question.stem}\n\n`; | |
| if (question.content && typeof question.content === 'object') { | |
| if ('Options' in question.content && question.content.Options) { | |
| const options = question.content.Options as { A: string; B: string; C: string; D: string; }; | |
| const correctAnswer = (question.content as { Answer?: string }).Answer; | |
| Object.entries(options).forEach(([key, value]) => { | |
| const isCorrect = includeAnswers && key === correctAnswer; | |
| markdown += `${key}. ${value}${isCorrect ? ' ✓' : ''}\n`; | |
| }); | |
| if (includeAnswers && correctAnswer) { | |
| markdown += `\n**Answer:** ${correctAnswer}\n`; | |
| } | |
| } | |
| } | |
| markdown += '\n---\n\n'; | |
| }); | |
| const blob = new Blob([markdown], { type: 'text/markdown' }); | |
| const url = URL.createObjectURL(blob); | |
| const link = document.createElement('a'); | |
| link.href = url; | |
| link.download = filename; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| URL.revokeObjectURL(url); | |
| } | |
| export async function exportQuestionsToCsv(questions: GeneratedQuestion[], filename: string = 'quiz-questions.csv') { | |
| // CSV header | |
| let csv = 'Question Number,Type,Points,Question,Option A,Option B,Option C,Option D,Correct Answer\n'; | |
| questions.forEach((question, index) => { | |
| const row = []; | |
| row.push(index + 1); | |
| row.push(question.type); | |
| row.push(question.points); | |
| row.push(`"${question.stem.replace(/"/g, '""')}"`); | |
| if (question.content && typeof question.content === 'object' && 'Options' in question.content) { | |
| const options = question.content.Options as { A: string; B: string; C: string; D: string; }; | |
| const correctAnswer = (question.content as { Answer?: string }).Answer; | |
| row.push(`"${options.A.replace(/"/g, '""')}"`); | |
| row.push(`"${options.B.replace(/"/g, '""')}"`); | |
| row.push(`"${options.C.replace(/"/g, '""')}"`); | |
| row.push(`"${options.D.replace(/"/g, '""')}"`); | |
| row.push(correctAnswer || ''); | |
| } else { | |
| // Add empty cells for non-multiple choice questions | |
| row.push('', '', '', '', ''); | |
| } | |
| csv += row.join(',') + '\n'; | |
| }); | |
| const blob = new Blob([csv], { type: 'text/csv' }); | |
| const url = URL.createObjectURL(blob); | |
| const link = document.createElement('a'); | |
| link.href = url; | |
| link.download = filename; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| URL.revokeObjectURL(url); | |
| } |