diff --git a/src/airflow_wingman/static/css/wingman_chat.css b/src/airflow_wingman/static/css/wingman_chat.css
index 10080dc..1d6cdce 100644
--- a/src/airflow_wingman/static/css/wingman_chat.css
+++ b/src/airflow_wingman/static/css/wingman_chat.css
@@ -24,6 +24,11 @@
clear: both;
}
+.message p {
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+}
+
.message-user {
float: right;
background-color: #f0f7ff;
@@ -32,13 +37,22 @@
padding: 10px 15px;
}
+.message pre {
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+ padding: 0.5em;
+}
+
.message-assistant {
float: left;
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 15px 15px 15px 0;
padding: 10px 15px;
- white-space: pre-wrap;
+}
+
+.message code {
+ padding: 0.1em 0.3em;
}
#chat-messages::after {
@@ -80,6 +94,6 @@
}
.pre-formatted {
- white-space: pre-wrap;
font-family: monospace;
+ line-height: 1.2;
}
diff --git a/src/airflow_wingman/static/js/wingman_chat.js b/src/airflow_wingman/static/js/wingman_chat.js
index 821e5b5..5b83be3 100644
--- a/src/airflow_wingman/static/js/wingman_chat.js
+++ b/src/airflow_wingman/static/js/wingman_chat.js
@@ -1,5 +1,5 @@
document.addEventListener('DOMContentLoaded', function() {
- // Add title attributes for tooltips
+ // Initialize tooltips
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(function(el) {
el.title = el.getAttribute('title') || el.getAttribute('data-bs-original-title');
});
@@ -80,12 +80,26 @@ document.addEventListener('DOMContentLoaded', function() {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${isUser ? 'message-user' : 'message-assistant'}`;
- // Apply pre-formatted class to preserve whitespace and newlines
messageDiv.classList.add('pre-formatted');
-
- // Use innerText instead of textContent to preserve newlines
- messageDiv.innerText = content;
-
+
+ // Use marked.js to render markdown
+ try {
+ // Configure marked options
+ marked.use({
+ breaks: true, // Add line breaks on single newlines
+ gfm: true, // Use GitHub Flavored Markdown
+ headerIds: false, // Don't add IDs to headers
+ mangle: false, // Don't mangle email addresses
+ });
+
+ // Render markdown to HTML
+ messageDiv.innerHTML = marked.parse(content);
+ } catch (e) {
+ console.error('Error rendering markdown:', e);
+ // Fallback to innerText if markdown parsing fails
+ messageDiv.innerText = content;
+ }
+
chatMessages.appendChild(messageDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
return messageDiv;
@@ -228,12 +242,24 @@ document.addEventListener('DOMContentLoaded', function() {
console.log('Received complete response from backend');
// Use the complete response from the backend
fullResponse = parsed.content;
-
- // Update the display with the complete response
- if (!currentMessageDiv.classList.contains('pre-formatted')) {
- currentMessageDiv.classList.add('pre-formatted');
+
+ // Use marked.js to render markdown
+ try {
+ // Configure marked options
+ marked.use({
+ breaks: true, // Add line breaks on single newlines
+ gfm: true, // Use GitHub Flavored Markdown
+ headerIds: false, // Don't add IDs to headers
+ mangle: false, // Don't mangle email addresses
+ });
+
+ // Render markdown to HTML
+ currentMessageDiv.innerHTML = marked.parse(fullResponse);
+ } catch (e) {
+ console.error('Error rendering markdown:', e);
+ // Fallback to innerText if markdown parsing fails
+ currentMessageDiv.innerText = fullResponse;
}
- currentMessageDiv.innerText = fullResponse;
continue;
}
@@ -253,7 +279,7 @@ document.addEventListener('DOMContentLoaded', function() {
}
// Always rebuild the entire content from the full response
- currentMessageDiv.innerText = fullResponse;
+ currentMessageDiv.innerHTML = marked.parse(fullResponse);
}
// Scroll to bottom
chatMessages.scrollTop = chatMessages.scrollHeight;
diff --git a/src/airflow_wingman/templates/wingman_chat.html b/src/airflow_wingman/templates/wingman_chat.html
index 8424866..c1da377 100644
--- a/src/airflow_wingman/templates/wingman_chat.html
+++ b/src/airflow_wingman/templates/wingman_chat.html
@@ -112,5 +112,6 @@
+
{% endblock %}