diff --git a/frontend/src/views/QuizView.vue b/frontend/src/views/QuizView.vue index 6fcaa06..6f7e8f0 100644 --- a/frontend/src/views/QuizView.vue +++ b/frontend/src/views/QuizView.vue @@ -205,7 +205,7 @@ -
+
+ +
+ +
+

AI 채점 실패

+

{{ gradingError }}

+
+
+
+ @@ -358,8 +358,16 @@ marked.use({ function renderMarkdown(text: string): string { if (!text) return '' + // 0. Extract and protect Mermaid blocks + const mermaidBlocks: string[] = [] + let processed = text.replace(/```mermaid\r?\\n([\s\S]*?)\r?\\n```/g, (match, code) => { + const placeholder = `MERMAIDBLOCKPLACEHOLDER${mermaidBlocks.length}` + mermaidBlocks.push(code.trim()) + return placeholder + }) + // 1. Render display math $$...$$ - let processed = text.replace(/\$\$([\s\S]+?)\$\$/g, (match, math) => { + processed = processed.replace(/\$\$([\s\S]+?)\$\$/g, (match, math) => { try { return `
${katex.renderToString(math.trim(), { displayMode: true, throwOnError: false })}
` } catch (e) { @@ -377,7 +385,16 @@ function renderMarkdown(text: string): string { }) // 3. Render markdown - return marked.parse(processed) as string + let html = marked.parse(processed) as string + + // 5. Restore protected Mermaid blocks + mermaidBlocks.forEach((code, idx) => { + const placeholder = `MERMAIDBLOCKPLACEHOLDER${idx}` + const replacement = `
${code}
` + html = html.replace(new RegExp(`

\\s*${placeholder}\\s*

|${placeholder}`, 'g'), replacement) + }) + + return html } const renderedContent = computed(() => { @@ -581,8 +598,8 @@ function printSingleElement(se: StudyElement) { } .math-display { margin: 16px 0; overflow-x: auto; text-align: center; } .math-inline { padding: 0 4px; } - .prose table { width: 100%; border-collapse: collapse; margin: 16px 0; } - .prose th, .prose td { border: 1px solid #e2e8f0; padding: 10px 14px; text-align: left; } + .prose table { min-width: 100%; width: max-content; max-width: none; border-collapse: collapse; margin: 16px 0; } + .prose th, .prose td { border: 1px solid #e2e8f0; padding: 10px 14px; text-align: left; word-break: keep-all; word-wrap: break-word; min-width: 120px; } .prose th { background-color: #f1f5f9; font-weight: bold; } .mermaid { margin: 20px 0; text-align: center; } @@ -666,8 +683,8 @@ function printAllElements() { } .math-display { margin: 16px 0; overflow-x: auto; text-align: center; } .math-inline { padding: 0 4px; } - .prose table { width: 100%; border-collapse: collapse; margin: 16px 0; } - .prose th, .prose td { border: 1px solid #e2e8f0; padding: 10px 14px; text-align: left; } + .prose table { min-width: 100%; width: max-content; max-width: none; border-collapse: collapse; margin: 16px 0; } + .prose th, .prose td { border: 1px solid #e2e8f0; padding: 10px 14px; text-align: left; word-break: keep-all; word-wrap: break-word; min-width: 120px; } .prose th { background-color: #f1f5f9; font-weight: bold; } .mermaid { margin: 20px 0; text-align: center; } diff --git a/quiz_service.go b/quiz_service.go index 1b468cb..e48e348 100644 --- a/quiz_service.go +++ b/quiz_service.go @@ -1802,6 +1802,13 @@ func (s *QuizService) GenerateGoPracticalExams(count int, useGeminiAPI bool, del continue } + // Force correctAnswerStr to "주관식" for descriptive questions to guarantee descriptive UI layout + if examType == "서술형" { + gq.CorrectAnswerStr = "주관식" + } else if examType == "혼합형" && i%2 == 0 { + gq.CorrectAnswerStr = "주관식" + } + // Save to DB (Mutex Lock) s.dbMu.Lock() // Generate next ID