feat: add Markdown rendering support for summaries
Browse files- Add Marked.js library for proper Markdown-to-HTML conversion
- Replace manual regex-based Markdown parser with robust library
- Update CSS styling for rendered HTML elements (headers, lists, code blocks)
- Configure Marked.js with secure options (breaks, GFM, no header IDs)
- Create test file demonstrating Markdown rendering capabilities
The summary output now properly renders Markdown formatting including:
- Headers (# ## ###)
- Bold and italic text
- Lists (ordered/unordered)
- Code blocks and inline code
- Links
- Proper HTML structure with styling
- frontend/app.js +15 -1
- frontend/index.html +2 -1
- frontend/styles.css +64 -1
- test_markdown.html +77 -0
frontend/app.js
CHANGED
|
@@ -65,6 +65,20 @@ const SUMMARY_FORMATS = ['Markdown', 'Plain Text'];
|
|
| 65 |
let activeTab = 'podcast-tab';
|
| 66 |
let activeUtteranceIndex = -1;
|
| 67 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
function setStatus(message, tone = 'info') {
|
| 69 |
elements.statusText.textContent = message;
|
| 70 |
elements.statusText.dataset.tone = tone;
|
|
@@ -508,7 +522,7 @@ async function handleSummaryGeneration() {
|
|
| 508 |
if (!line.trim()) continue;
|
| 509 |
const event = JSON.parse(line);
|
| 510 |
if (event.type === 'partial' && event.content) {
|
| 511 |
-
elements.summaryOutput.
|
| 512 |
}
|
| 513 |
}
|
| 514 |
}
|
|
|
|
| 65 |
let activeTab = 'podcast-tab';
|
| 66 |
let activeUtteranceIndex = -1;
|
| 67 |
|
| 68 |
+
// Configuration de Marked pour un rendu sécurisé
|
| 69 |
+
marked.setOptions({
|
| 70 |
+
breaks: true, // Convertir les sauts de ligne simples en <br>
|
| 71 |
+
gfm: true, // GitHub Flavored Markdown
|
| 72 |
+
headerIds: false, // Pas d'IDs automatiques sur les headers
|
| 73 |
+
mangle: false, // Pas de mangling des emails
|
| 74 |
+
});
|
| 75 |
+
|
| 76 |
+
// Fonction simple pour convertir Markdown en HTML
|
| 77 |
+
function renderMarkdown(markdown) {
|
| 78 |
+
if (!markdown) return '';
|
| 79 |
+
return marked.parse(markdown);
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
function setStatus(message, tone = 'info') {
|
| 83 |
elements.statusText.textContent = message;
|
| 84 |
elements.statusText.dataset.tone = tone;
|
|
|
|
| 522 |
if (!line.trim()) continue;
|
| 523 |
const event = JSON.parse(line);
|
| 524 |
if (event.type === 'partial' && event.content) {
|
| 525 |
+
elements.summaryOutput.innerHTML = renderMarkdown(event.content);
|
| 526 |
}
|
| 527 |
}
|
| 528 |
}
|
frontend/index.html
CHANGED
|
@@ -4,7 +4,8 @@
|
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
<title>VoxSum Studio</title>
|
| 7 |
-
|
|
|
|
| 8 |
</head>
|
| 9 |
<body>
|
| 10 |
<header class="app-header">
|
|
|
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
<title>VoxSum Studio</title>
|
| 7 |
+
<link rel="stylesheet" href="/styles.css" />
|
| 8 |
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
| 9 |
</head>
|
| 10 |
<body>
|
| 11 |
<header class="app-header">
|
frontend/styles.css
CHANGED
|
@@ -406,7 +406,70 @@ button:hover {
|
|
| 406 |
border-radius: 12px;
|
| 407 |
padding: 1rem;
|
| 408 |
border: 1px solid rgba(148, 163, 184, 0.15);
|
| 409 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 410 |
}
|
| 411 |
|
| 412 |
.export-grid {
|
|
|
|
| 406 |
border-radius: 12px;
|
| 407 |
padding: 1rem;
|
| 408 |
border: 1px solid rgba(148, 163, 184, 0.15);
|
| 409 |
+
line-height: 1.6;
|
| 410 |
+
}
|
| 411 |
+
|
| 412 |
+
.summary h1, .summary h2, .summary h3 {
|
| 413 |
+
margin-top: 1.5rem;
|
| 414 |
+
margin-bottom: 0.5rem;
|
| 415 |
+
color: #e2e8f0;
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
.summary h1 { font-size: 1.5rem; }
|
| 419 |
+
.summary h2 { font-size: 1.25rem; }
|
| 420 |
+
.summary h3 { font-size: 1.1rem; }
|
| 421 |
+
|
| 422 |
+
.summary p {
|
| 423 |
+
margin-bottom: 1rem;
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
+
.summary strong {
|
| 427 |
+
font-weight: 600;
|
| 428 |
+
color: #f1f5f9;
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
.summary em {
|
| 432 |
+
font-style: italic;
|
| 433 |
+
color: #cbd5e1;
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
.summary code {
|
| 437 |
+
background: rgba(100, 116, 139, 0.3);
|
| 438 |
+
padding: 0.125rem 0.25rem;
|
| 439 |
+
border-radius: 4px;
|
| 440 |
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
| 441 |
+
font-size: 0.9em;
|
| 442 |
+
}
|
| 443 |
+
|
| 444 |
+
.summary pre {
|
| 445 |
+
background: rgba(15, 23, 42, 0.8);
|
| 446 |
+
padding: 1rem;
|
| 447 |
+
border-radius: 8px;
|
| 448 |
+
overflow-x: auto;
|
| 449 |
+
margin: 1rem 0;
|
| 450 |
+
}
|
| 451 |
+
|
| 452 |
+
.summary pre code {
|
| 453 |
+
background: none;
|
| 454 |
+
padding: 0;
|
| 455 |
+
}
|
| 456 |
+
|
| 457 |
+
.summary ul, .summary ol {
|
| 458 |
+
margin: 1rem 0;
|
| 459 |
+
padding-left: 1.5rem;
|
| 460 |
+
}
|
| 461 |
+
|
| 462 |
+
.summary li {
|
| 463 |
+
margin-bottom: 0.25rem;
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
.summary a {
|
| 467 |
+
color: #60a5fa;
|
| 468 |
+
text-decoration: underline;
|
| 469 |
+
}
|
| 470 |
+
|
| 471 |
+
.summary a:hover {
|
| 472 |
+
color: #93c5fd;
|
| 473 |
}
|
| 474 |
|
| 475 |
.export-grid {
|
test_markdown.html
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<title>Test Markdown Rendering with Marked.js</title>
|
| 6 |
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
| 7 |
+
<style>
|
| 8 |
+
.summary {
|
| 9 |
+
min-height: 120px;
|
| 10 |
+
background: rgba(15, 23, 42, 0.5);
|
| 11 |
+
border-radius: 12px;
|
| 12 |
+
padding: 1rem;
|
| 13 |
+
border: 1px solid rgba(148, 163, 184, 0.15);
|
| 14 |
+
line-height: 1.6;
|
| 15 |
+
color: white;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
.summary h1, .summary h2, .summary h3 {
|
| 19 |
+
margin-top: 1.5rem;
|
| 20 |
+
margin-bottom: 0.5rem;
|
| 21 |
+
color: #e2e8f0;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
.summary strong { font-weight: 600; color: #f1f5f9; }
|
| 25 |
+
.summary em { font-style: italic; color: #cbd5e1; }
|
| 26 |
+
.summary code {
|
| 27 |
+
background: rgba(100, 116, 139, 0.3);
|
| 28 |
+
padding: 0.125rem 0.25rem;
|
| 29 |
+
border-radius: 4px;
|
| 30 |
+
}
|
| 31 |
+
.summary pre {
|
| 32 |
+
background: rgba(15, 23, 42, 0.8);
|
| 33 |
+
padding: 1rem;
|
| 34 |
+
border-radius: 8px;
|
| 35 |
+
overflow-x: auto;
|
| 36 |
+
}
|
| 37 |
+
.summary ul, .summary ol { margin: 1rem 0; padding-left: 1.5rem; }
|
| 38 |
+
.summary a { color: #60a5fa; text-decoration: underline; }
|
| 39 |
+
</style>
|
| 40 |
+
</head>
|
| 41 |
+
<body style="background: #0f172a; padding: 20px;">
|
| 42 |
+
<h1 style="color: white;">Test du rendu Markdown avec Marked.js</h1>
|
| 43 |
+
<div id="summary-output" class="summary"></div>
|
| 44 |
+
|
| 45 |
+
<script>
|
| 46 |
+
// Configuration de Marked
|
| 47 |
+
marked.setOptions({
|
| 48 |
+
breaks: true,
|
| 49 |
+
gfm: true,
|
| 50 |
+
headerIds: false,
|
| 51 |
+
mangle: false,
|
| 52 |
+
});
|
| 53 |
+
|
| 54 |
+
// Test avec du contenu Markdown
|
| 55 |
+
const testMarkdown = `# Résumé du Podcast
|
| 56 |
+
|
| 57 |
+
Voici un **résumé important** avec du *texte en italique*.
|
| 58 |
+
|
| 59 |
+
## Points clés
|
| 60 |
+
|
| 61 |
+
- Premier point important
|
| 62 |
+
- **Deuxième point** en gras
|
| 63 |
+
- \`Code inline\` et blocs de code:
|
| 64 |
+
|
| 65 |
+
\`\`\`python
|
| 66 |
+
def hello():
|
| 67 |
+
print("Hello World!")
|
| 68 |
+
\`\`\`
|
| 69 |
+
|
| 70 |
+
## Conclusion
|
| 71 |
+
|
| 72 |
+
Le podcast traite de sujets intéressants. [Lien vers plus d'infos](https://example.com)`;
|
| 73 |
+
|
| 74 |
+
document.getElementById('summary-output').innerHTML = marked.parse(testMarkdown);
|
| 75 |
+
</script>
|
| 76 |
+
</body>
|
| 77 |
+
</html>
|