fix: buffer HTML chunks to avoid flashing, flush on closing tags
All checks were successful
BotServer CI/CD / build (push) Successful in 8m7s
All checks were successful
BotServer CI/CD / build (push) Successful in 8m7s
This commit is contained in:
parent
f06c071b2c
commit
73d9531563
15 changed files with 143 additions and 32 deletions
6
migrations/6.3.1-01-drive-files/down.sql
Normal file
6
migrations/6.3.1-01-drive-files/down.sql
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
-- ============================================
|
||||
-- Drive Files State Table - Rollback
|
||||
-- Version: 6.3.1
|
||||
-- ============================================
|
||||
|
||||
DROP TABLE IF EXISTS drive_files;
|
||||
|
|
@ -833,6 +833,7 @@ impl BotOrchestrator {
|
|||
let mut in_analysis = false;
|
||||
let mut tool_call_buffer = String::new(); // Accumulate potential tool call JSON chunks
|
||||
let mut accumulating_tool_call = false; // Track if we're currently accumulating a tool call
|
||||
let mut html_buffer = String::new(); // Buffer for HTML content
|
||||
let _handler = llm_models::get_handler(&model);
|
||||
|
||||
trace!("Using model handler for {}", model);
|
||||
|
|
@ -1149,25 +1150,47 @@ impl BotOrchestrator {
|
|||
|
||||
if !in_analysis {
|
||||
full_response.push_str(&chunk);
|
||||
html_buffer.push_str(&chunk);
|
||||
|
||||
let response = BotResponse {
|
||||
bot_id: message.bot_id.clone(),
|
||||
user_id: message.user_id.clone(),
|
||||
session_id: message.session_id.clone(),
|
||||
channel: message.channel.clone(),
|
||||
content: chunk.clone(),
|
||||
message_type: MessageType::BOT_RESPONSE,
|
||||
stream_token: None,
|
||||
is_complete: false,
|
||||
suggestions: Vec::new(),
|
||||
context_name: None,
|
||||
context_length: 0,
|
||||
context_max_length: 0,
|
||||
};
|
||||
// Check if we should flush the buffer:
|
||||
// 1. HTML tag pair completed (e.g., </div>, </h1>, </p>, </ul>, </li>)
|
||||
// 2. Buffer is large enough (> 500 chars)
|
||||
// 3. This is the last chunk (is_complete will be true next iteration)
|
||||
let should_flush = html_buffer.len() > 500
|
||||
|| html_buffer.contains("</div>")
|
||||
|| html_buffer.contains("</h1>")
|
||||
|| html_buffer.contains("</h2>")
|
||||
|| html_buffer.contains("</p>")
|
||||
|| html_buffer.contains("</ul>")
|
||||
|| html_buffer.contains("</ol>")
|
||||
|| html_buffer.contains("</li>")
|
||||
|| html_buffer.contains("</section>")
|
||||
|| html_buffer.contains("</header>")
|
||||
|| html_buffer.contains("</footer>");
|
||||
|
||||
if response_tx.send(response).await.is_err() {
|
||||
warn!("Response channel closed");
|
||||
break;
|
||||
if should_flush {
|
||||
let content_to_send = html_buffer.clone();
|
||||
html_buffer.clear();
|
||||
|
||||
let response = BotResponse {
|
||||
bot_id: message.bot_id.clone(),
|
||||
user_id: message.user_id.clone(),
|
||||
session_id: message.session_id.clone(),
|
||||
channel: message.channel.clone(),
|
||||
content: content_to_send,
|
||||
message_type: MessageType::BOT_RESPONSE,
|
||||
stream_token: None,
|
||||
is_complete: false,
|
||||
suggestions: Vec::new(),
|
||||
context_name: None,
|
||||
context_length: 0,
|
||||
context_max_length: 0,
|
||||
};
|
||||
|
||||
if response_tx.send(response).await.is_err() {
|
||||
warn!("Response channel closed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1208,6 +1231,27 @@ impl BotOrchestrator {
|
|||
#[cfg(not(feature = "chat"))]
|
||||
let suggestions: Vec<crate::core::shared::models::Suggestion> = Vec::new();
|
||||
|
||||
// Flush any remaining HTML buffer before sending final response
|
||||
if !html_buffer.is_empty() {
|
||||
trace!("Flushing remaining {} chars in HTML buffer", html_buffer.len());
|
||||
let final_chunk = BotResponse {
|
||||
bot_id: message.bot_id.clone(),
|
||||
user_id: message.user_id.clone(),
|
||||
session_id: message.session_id.clone(),
|
||||
channel: message.channel.clone(),
|
||||
content: html_buffer.clone(),
|
||||
message_type: MessageType::BOT_RESPONSE,
|
||||
stream_token: None,
|
||||
is_complete: false,
|
||||
suggestions: Vec::new(),
|
||||
context_name: None,
|
||||
context_length: 0,
|
||||
context_max_length: 0,
|
||||
};
|
||||
let _ = response_tx.send(final_chunk).await;
|
||||
html_buffer.clear();
|
||||
}
|
||||
|
||||
// Content was already sent as streaming chunks.
|
||||
// Sending full_response again would duplicate it (especially for WhatsApp which accumulates buffer).
|
||||
// The final response is just a signal that streaming is complete - it should not contain content.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::shared::schema::core::{organizations, bots};
|
||||
use crate::core::shared::schema::core::{bots, organizations};
|
||||
|
||||
diesel::table! {
|
||||
dashboards (id) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::shared::schema::core::{organizations, bots};
|
||||
use crate::core::shared::schema::core::{bots, organizations};
|
||||
|
||||
diesel::table! {
|
||||
attendant_queues (id) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::shared::schema::core::{organizations, bots};
|
||||
use crate::core::shared::schema::core::{bots, organizations};
|
||||
|
||||
diesel::table! {
|
||||
billing_invoices (id) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::shared::schema::core::{organizations, bots};
|
||||
use crate::core::shared::schema::core::{bots, organizations};
|
||||
|
||||
diesel::table! {
|
||||
calendars (id) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::shared::schema::core::{organizations, bots};
|
||||
use crate::core::shared::schema::core::{bots, organizations};
|
||||
|
||||
diesel::table! {
|
||||
canvases (id) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::shared::schema::core::{organizations, bots};
|
||||
use crate::core::shared::schema::core::{bots, organizations};
|
||||
|
||||
diesel::table! {
|
||||
legal_documents (id) {
|
||||
|
|
|
|||
61
src/core/shared/schema/drive.rs
Normal file
61
src/core/shared/schema/drive.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use diesel::prelude::*;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Queryable, Insertable, AsChangeset, Debug, Clone)]
|
||||
#[diesel(table_name = drive_files)]
|
||||
pub struct DriveFile {
|
||||
pub id: Uuid,
|
||||
pub bot_id: Uuid,
|
||||
pub file_path: String,
|
||||
pub file_type: String,
|
||||
pub etag: Option<String>,
|
||||
pub last_modified: Option<DateTime<Utc>>,
|
||||
pub file_size: Option<i64>,
|
||||
pub indexed: bool,
|
||||
pub fail_count: i32,
|
||||
pub last_failed_at: Option<DateTime<Utc>>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Debug)]
|
||||
#[diesel(table_name = drive_files)]
|
||||
pub struct NewDriveFile {
|
||||
pub bot_id: Uuid,
|
||||
pub file_path: String,
|
||||
pub file_type: String,
|
||||
pub etag: Option<String>,
|
||||
pub last_modified: Option<DateTime<Utc>>,
|
||||
pub file_size: Option<i64>,
|
||||
pub indexed: bool,
|
||||
}
|
||||
|
||||
#[derive(AsChangeset, Debug)]
|
||||
#[diesel(table_name = drive_files)]
|
||||
pub struct DriveFileUpdate {
|
||||
pub etag: Option<String>,
|
||||
pub last_modified: Option<DateTime<Utc>>,
|
||||
pub file_size: Option<i64>,
|
||||
pub indexed: Option<bool>,
|
||||
pub fail_count: Option<i32>,
|
||||
pub last_failed_at: Option<DateTime<Utc>>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
drive_files (id) {
|
||||
id -> Uuid,
|
||||
bot_id -> Uuid,
|
||||
file_path -> Text,
|
||||
file_type -> Varchar,
|
||||
etag -> Nullable<Text>,
|
||||
last_modified -> Nullable<Timestamptz>,
|
||||
file_size -> Nullable<Int8>,
|
||||
indexed -> Bool,
|
||||
fail_count -> Int4,
|
||||
last_failed_at -> Nullable<Timestamptz>,
|
||||
created_at -> Timestamptz,
|
||||
updated_at -> Timestamptz,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::shared::schema::core::{organizations, bots};
|
||||
use crate::core::shared::schema::core::{bots, organizations};
|
||||
|
||||
diesel::table! {
|
||||
okr_objectives (id) {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,4 @@ diesel::joinable!(kb_group_associations -> kb_collections (kb_id));
|
|||
diesel::joinable!(kb_group_associations -> rbac_groups (group_id));
|
||||
diesel::joinable!(kb_group_associations -> users (granted_by));
|
||||
|
||||
diesel::allow_tables_to_appear_in_same_query!(
|
||||
kb_collections,
|
||||
kb_group_associations,
|
||||
);
|
||||
diesel::allow_tables_to_appear_in_same_query!(kb_collections, kb_group_associations,);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::shared::schema::core::{organizations, bots};
|
||||
use crate::core::shared::schema::core::{bots, organizations};
|
||||
|
||||
diesel::table! {
|
||||
meeting_rooms (id) {
|
||||
|
|
|
|||
|
|
@ -89,7 +89,10 @@ pub mod project;
|
|||
#[cfg(feature = "dashboards")]
|
||||
pub mod dashboards;
|
||||
|
||||
// Drive (always available - used by DriveMonitor)
|
||||
pub mod drive;
|
||||
pub use self::drive::*;
|
||||
|
||||
// Email integration (always available)
|
||||
pub mod email_integration;
|
||||
pub use self::email_integration::*;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::shared::schema::core::{organizations, bots};
|
||||
use crate::core::shared::schema::core::{bots, organizations};
|
||||
|
||||
diesel::table! {
|
||||
social_communities (id) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::core::shared::schema::core::{organizations, bots};
|
||||
use crate::core::shared::schema::core::{bots, organizations};
|
||||
|
||||
diesel::table! {
|
||||
workspaces (id) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue