fix: own implementation of stream parser

This commit is contained in:
Per Stark
2025-04-10 08:23:55 +02:00
parent 435547de66
commit 233df1b79a
8 changed files with 359 additions and 378 deletions

View File

@@ -21,7 +21,6 @@ axum_typed_multipart = "0.12.1"
futures = "0.3.31"
tempfile = "3.12.0"
async-stream = "0.3.6"
json-stream-parser = "0.1.4"
minijinja = { version = "2.5.0", features = ["loader", "multi_template"] }
minijinja-autoreload = "2.5.0"
minijinja-embed = { version = "2.8.0" }

View File

@@ -21,7 +21,6 @@ use futures::{
stream::{self, once},
Stream, StreamExt, TryStreamExt,
};
use json_stream_parser::JsonStreamParser;
use minijinja::Value;
use serde::{Deserialize, Serialize};
use serde_json::from_str;
@@ -29,14 +28,17 @@ use surrealdb::{engine::any::Any, Surreal};
use tokio::sync::{mpsc::channel, Mutex};
use tracing::{debug, error};
use common::storage::{
db::SurrealDbClient,
types::{
conversation::Conversation,
message::{Message, MessageRole},
system_settings::SystemSettings,
user::User,
use common::{
storage::{
db::SurrealDbClient,
types::{
conversation::Conversation,
message::{Message, MessageRole},
system_settings::SystemSettings,
user::User,
},
},
utils::stream_parser::JsonStreamParser,
};
use crate::html_state::HtmlState;

View File

@@ -24,4 +24,37 @@
}
}
)
// Add listener for after content is settled
document.body.addEventListener('htmx:afterSettle', function (evt) {
// Check if the settled element has our specific class
// evt.detail.target might be the container, elt is often the element *making* the request
// We need the element *receiving* the swap. Let's target specifically.
const messageId = "{{user_message.id}}"; // Get the ID from the template context
const targetBubble = document.querySelector(`.ai-message-content-${messageId}`);
// Ensure we have the marked library and the target exists
if (targetBubble && typeof marked !== 'undefined') {
// Get the raw text content (which includes previously streamed parts)
// Exclude the spinner if it's still somehow there, though it should be hidden.
let rawContent = '';
targetBubble.childNodes.forEach(node => {
if (node.nodeType === Node.TEXT_NODE) {
rawContent += node.textContent;
} else if (node.nodeType === Node.ELEMENT_NODE && !node.classList.contains('loading')) {
// In case HTMX wraps text in spans or something unexpected later
rawContent += node.textContent;
}
});
console.log(rawContent);
// Sanitize BEFORE inserting potentially harmful HTML from Markdown
// It's better to sanitize *after* rendering if using DOMPurify
targetBubble.innerHTML = marked.parse(rawContent);
// Optional: Sanitize with DOMPurify *after* parsing for security
// if (typeof DOMPurify !== 'undefined') {
// targetBubble.innerHTML = DOMPurify.sanitize(marked.parse(rawContent));
// } else {
// targetBubble.innerHTML = marked.parse(rawContent); // Use with caution if markdown source isn't trusted
// }
}
});
</script>

View File

@@ -19,6 +19,7 @@
<script src="/assets/htmx-ext-sse.js" defer></script>
<script src="/assets/theme-toggle.js" defer></script>
<script src="/assets/toast.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- Icons -->
<link rel="icon" href="/assets/icon/favicon.ico">