chore: update dependencies and formatting
All checks were successful
BotServer CI / build (push) Successful in 7m30s
All checks were successful
BotServer CI / build (push) Successful in 7m30s
This commit is contained in:
parent
affb702e90
commit
1856215d05
26 changed files with 226 additions and 280 deletions
|
|
@ -1015,7 +1015,7 @@ impl AppGenerator {
|
|||
};
|
||||
|
||||
// Mark "Analyzing Request" as completed
|
||||
info!("[PHASE1->2] Marking Analyzing Request as Completed");
|
||||
info!(" Marking Analyzing Request as Completed");
|
||||
self.update_manifest_section(SectionType::Validation, SectionStatus::Completed);
|
||||
self.broadcast_manifest_update();
|
||||
|
||||
|
|
@ -1069,7 +1069,7 @@ impl AppGenerator {
|
|||
let tables = Self::convert_llm_tables(&llm_app.tables);
|
||||
|
||||
if !tables.is_empty() {
|
||||
info!("[PHASE2] Setting Database & Models section to Running");
|
||||
info!(" Setting Database & Models section to Running");
|
||||
self.update_manifest_section(SectionType::DatabaseModels, SectionStatus::Running);
|
||||
self.broadcast_manifest_update();
|
||||
self.update_manifest_child(
|
||||
|
|
@ -1282,7 +1282,7 @@ impl AppGenerator {
|
|||
|
||||
// Debug: List all sections before update
|
||||
if let Some(ref manifest) = self.manifest {
|
||||
info!("[PHASE2B] Current manifest sections:");
|
||||
info!(" Current manifest sections:");
|
||||
for (i, s) in manifest.sections.iter().enumerate() {
|
||||
info!(
|
||||
"[PHASE2B] [{}] {:?} = '{}' status={:?}",
|
||||
|
|
@ -1301,7 +1301,7 @@ impl AppGenerator {
|
|||
for idx in files_needing_content {
|
||||
let filename = llm_app.files[idx].filename.clone();
|
||||
generated_count += 1;
|
||||
info!("[PHASE2B] Starting generation for file: {}", filename);
|
||||
info!(" Starting generation for file: {}", filename);
|
||||
self.add_terminal_output(
|
||||
&format!("Generating `{filename}`..."),
|
||||
TerminalLineType::Info,
|
||||
|
|
@ -1783,11 +1783,11 @@ impl AppGenerator {
|
|||
|
||||
match std::fs::read_to_string(&prompt_path) {
|
||||
Ok(content) => {
|
||||
info!("[APP_GENERATOR] Loaded prompt from {:?} ({} chars)", prompt_path, content.len());
|
||||
info!("Loaded prompt from {:?} ({} chars)", prompt_path, content.len());
|
||||
content
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("[APP_GENERATOR] Failed to load APP_GENERATOR_PROMPT.md: {}, using fallback", e);
|
||||
warn!("Failed to load APP_GENERATOR_PROMPT.md: {}, using fallback", e);
|
||||
Self::get_fallback_prompt().to_string()
|
||||
}
|
||||
}
|
||||
|
|
@ -1876,7 +1876,7 @@ RESPOND ONLY WITH THE PLAN STRUCTURE. NO QUESTIONS."#
|
|||
intent_preview
|
||||
);
|
||||
let response = self.call_llm(&prompt, bot_id).await?;
|
||||
info!("[PHASE1] Project plan received, parsing...");
|
||||
info!(" Project plan received, parsing...");
|
||||
|
||||
Self::parse_project_plan(&response, intent)
|
||||
}
|
||||
|
|
@ -1894,7 +1894,7 @@ RESPOND ONLY WITH THE PLAN STRUCTURE. NO QUESTIONS."#
|
|||
"[PHASE1_PARSE] Response preview: {}",
|
||||
response_preview.replace('\n', "\\n")
|
||||
);
|
||||
info!("[PHASE1_PARSE] Has APP_START: {}, Has TABLES_START: {}, Has FILES_PLAN: {}, Has TOOLS_PLAN: {}",
|
||||
trace!(" Has APP_START: {}, Has TABLES_START: {}, Has FILES_PLAN: {}, Has TOOLS_PLAN: {}",
|
||||
response.contains("<<<APP_START>>>"),
|
||||
response.contains("<<<TABLES_START>>>"),
|
||||
response.contains("<<<FILES_PLAN>>>"),
|
||||
|
|
@ -1976,7 +1976,7 @@ RESPOND ONLY WITH THE PLAN STRUCTURE. NO QUESTIONS."#
|
|||
}
|
||||
}
|
||||
} else {
|
||||
info!("[PHASE1_PARSE] TABLES_START not found, trying <<<TABLE:>>> delimiters...");
|
||||
trace!(" TABLES_START not found, trying <<<TABLE:>>> delimiters...");
|
||||
for table_match in response.match_indices("<<<TABLE:") {
|
||||
let start = table_match.0;
|
||||
if let Some(rest) = response.get(start..) {
|
||||
|
|
@ -1984,7 +1984,7 @@ RESPOND ONLY WITH THE PLAN STRUCTURE. NO QUESTIONS."#
|
|||
if let Some(table_name) = rest.get(9..end_offset) {
|
||||
let table_name = table_name.trim();
|
||||
if !table_name.is_empty() {
|
||||
info!("[PHASE1_PARSE] Found table from delimiter: {}", table_name);
|
||||
trace!(" Found table from delimiter: {}", table_name);
|
||||
app.tables.push(LlmTable {
|
||||
name: table_name.to_string(),
|
||||
fields: Vec::new(),
|
||||
|
|
@ -2019,13 +2019,13 @@ RESPOND ONLY WITH THE PLAN STRUCTURE. NO QUESTIONS."#
|
|||
|| filename.ends_with(".bas")
|
||||
|| filename.ends_with(".json"))
|
||||
{
|
||||
info!("[PHASE1_PARSE] Adding file: {}", filename);
|
||||
trace!(" Adding file: {}", filename);
|
||||
app.files.push(LlmFile {
|
||||
filename,
|
||||
content: String::new(), // Content will be generated in Phase 2
|
||||
});
|
||||
} else if !filename.is_empty() {
|
||||
info!("[PHASE1_PARSE] Skipped file (unknown ext): {}", filename);
|
||||
trace!(" Skipped file (unknown ext): {}", filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2041,7 +2041,7 @@ RESPOND ONLY WITH THE PLAN STRUCTURE. NO QUESTIONS."#
|
|||
if let Some(filename) = rest.get(8..end_offset) {
|
||||
let filename = filename.trim();
|
||||
if !filename.is_empty() {
|
||||
info!("[PHASE1_PARSE] Found file from delimiter: {}", filename);
|
||||
trace!(" Found file from delimiter: {}", filename);
|
||||
app.files.push(LlmFile {
|
||||
filename: filename.to_string(),
|
||||
content: String::new(),
|
||||
|
|
@ -2078,7 +2078,7 @@ RESPOND ONLY WITH THE PLAN STRUCTURE. NO QUESTIONS."#
|
|||
}
|
||||
}
|
||||
} else {
|
||||
info!("[PHASE1_PARSE] TOOLS_PLAN not found, trying <<<TOOL:>>> delimiters...");
|
||||
trace!(" TOOLS_PLAN not found, trying <<<TOOL:>>> delimiters...");
|
||||
for tool_match in response.match_indices("<<<TOOL:") {
|
||||
let start = tool_match.0;
|
||||
if let Some(rest) = response.get(start..) {
|
||||
|
|
@ -2091,7 +2091,7 @@ RESPOND ONLY WITH THE PLAN STRUCTURE. NO QUESTIONS."#
|
|||
} else {
|
||||
format!("{}.bas", tool_name)
|
||||
};
|
||||
info!("[PHASE1_PARSE] Found tool from delimiter: {}", filename);
|
||||
trace!(" Found tool from delimiter: {}", filename);
|
||||
app.tools.push(LlmFile {
|
||||
filename,
|
||||
content: String::new(),
|
||||
|
|
@ -2802,7 +2802,7 @@ NO QUESTIONS. JUST BUILD."#
|
|||
&& !detected_tables.contains(&table_name.to_string())
|
||||
{
|
||||
detected_tables.push(table_name.to_string());
|
||||
info!("[LLM_STREAM] Detected table: {table_name}");
|
||||
info!("Detected table: {table_name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2819,7 +2819,7 @@ NO QUESTIONS. JUST BUILD."#
|
|||
&& !detected_files.contains(&file_name.to_string())
|
||||
{
|
||||
detected_files.push(file_name.to_string());
|
||||
info!("[LLM_STREAM] Detected file: {file_name}");
|
||||
info!("Detected file: {file_name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2836,7 +2836,7 @@ NO QUESTIONS. JUST BUILD."#
|
|||
&& !detected_tools.contains(&tool_name.to_string())
|
||||
{
|
||||
detected_tools.push(tool_name.to_string());
|
||||
info!("[LLM_STREAM] Detected tool: {tool_name}");
|
||||
info!("Detected tool: {tool_name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2865,7 +2865,7 @@ NO QUESTIONS. JUST BUILD."#
|
|||
|| name.ends_with(".bas"))
|
||||
&& !detected_files.contains(&name.to_string()) {
|
||||
detected_files.push(name.to_string());
|
||||
info!("[LLM_STREAM] Detected planned file: {name}");
|
||||
info!("Detected planned file: {name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2894,7 +2894,7 @@ NO QUESTIONS. JUST BUILD."#
|
|||
};
|
||||
if !detected_tools.contains(&tool_name) {
|
||||
detected_tools.push(tool_name.clone());
|
||||
info!("[LLM_STREAM] Detected planned tool: {tool_name}");
|
||||
info!("Detected planned tool: {tool_name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2924,7 +2924,7 @@ NO QUESTIONS. JUST BUILD."#
|
|||
};
|
||||
if !detected_tools.contains(&sched_name) {
|
||||
detected_tools.push(sched_name.clone());
|
||||
info!("[LLM_STREAM] Detected planned scheduler: {sched_name}");
|
||||
info!("Detected planned scheduler: {sched_name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2962,7 +2962,7 @@ NO QUESTIONS. JUST BUILD."#
|
|||
chars_received
|
||||
)
|
||||
};
|
||||
info!("[LLM_STREAM] Progress: {}", progress_msg);
|
||||
info!("Progress: {}", progress_msg);
|
||||
let event = crate::core::shared::state::TaskProgressEvent::new(
|
||||
tid,
|
||||
"llm_generating",
|
||||
|
|
@ -3002,7 +3002,7 @@ NO QUESTIONS. JUST BUILD."#
|
|||
} else {
|
||||
format!("AI complete: {} chars generated", full_response.len())
|
||||
};
|
||||
info!("[LLM_STREAM] {}", final_msg);
|
||||
info!("{}", final_msg);
|
||||
let event = crate::core::shared::state::TaskProgressEvent::new(
|
||||
tid,
|
||||
"llm_complete",
|
||||
|
|
|
|||
|
|
@ -346,18 +346,18 @@ pub async fn create_and_execute_handler(
|
|||
|
||||
// Spawn background task to do the actual work
|
||||
let spawn_result = tokio::spawn(async move {
|
||||
info!("[AUTOTASK] *** Background task STARTED for task_id={} ***", task_id_str);
|
||||
info!(" *** Background task STARTED for task_id={} ***", task_id_str);
|
||||
|
||||
// Use IntentClassifier to classify and process with task tracking
|
||||
let classifier = IntentClassifier::new(state_clone.clone());
|
||||
|
||||
info!("[AUTOTASK] Calling classify_and_process_with_task_id for task_id={}", task_id_str);
|
||||
info!(" Calling classify_and_process_with_task_id for task_id={}", task_id_str);
|
||||
|
||||
let result = classifier
|
||||
.classify_and_process_with_task_id(&intent, &session_clone, Some(task_id_str.clone()))
|
||||
.await;
|
||||
|
||||
info!("[AUTOTASK] classify_and_process_with_task_id returned for task_id={}", task_id_str);
|
||||
info!(" classify_and_process_with_task_id returned for task_id={}", task_id_str);
|
||||
|
||||
match result {
|
||||
Ok(result) => {
|
||||
|
|
@ -382,10 +382,10 @@ pub async fn create_and_execute_handler(
|
|||
}
|
||||
});
|
||||
|
||||
info!("[AUTOTASK] Spawn result: {:?}", spawn_result);
|
||||
info!(" Spawn result: {:?}", spawn_result);
|
||||
|
||||
// Return immediately with task_id - client will poll for status
|
||||
info!("[AUTOTASK] Returning immediately with task_id={}", task_id);
|
||||
info!(" Returning immediately with task_id={}", task_id);
|
||||
(
|
||||
StatusCode::ACCEPTED,
|
||||
Json(CreateAndExecuteResponse {
|
||||
|
|
|
|||
|
|
@ -281,12 +281,12 @@ Respond with JSON only:
|
|||
}}"#
|
||||
);
|
||||
|
||||
info!("[INTENT_CLASSIFIER] Starting LLM call for classification, prompt_len={} chars", prompt.len());
|
||||
info!("Starting LLM call for classification, prompt_len={} chars", prompt.len());
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let response = self.call_llm(&prompt, bot_id).await?;
|
||||
let elapsed = start.elapsed();
|
||||
info!("[INTENT_CLASSIFIER] LLM classification completed in {:?}, response_len={} chars", elapsed, response.len());
|
||||
info!("LLM classification completed in {:?}, response_len={} chars", elapsed, response.len());
|
||||
trace!("LLM classification response: {}", &response[..response.len().min(500)]);
|
||||
Self::parse_classification_response(&response, intent)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,17 +179,17 @@ async fn handle_task_progress_websocket(
|
|||
match sender.send(Message::Text(json_str)).await {
|
||||
Ok(()) => {
|
||||
if is_manifest {
|
||||
info!("[WS_HANDLER] manifest_update SENT successfully to WebSocket");
|
||||
info!("manifest_update SENT successfully to WebSocket");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("[WS_HANDLER] Failed to send to WebSocket: {:?}", e);
|
||||
error!("Failed to send to WebSocket: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("[WS_HANDLER] Failed to serialize event: {:?}", e);
|
||||
error!("Failed to serialize event: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ pub fn convert_mail_line_with_substitution(line: &str) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
info!("[TOOL] Converted mail line: '{}' → '{}'", line, result);
|
||||
info!("Converted mail line: '{}' → '{}'", line, result);
|
||||
result
|
||||
}
|
||||
|
||||
|
|
@ -138,6 +138,6 @@ pub fn convert_mail_block(recipient: &str, lines: &[String]) -> String {
|
|||
};
|
||||
result.push_str(&format!("send_mail({}, \"{}\", {}, []);\n", recipient_expr, subject, body_expr));
|
||||
|
||||
info!("[TOOL] Converted MAIL block → {}", result);
|
||||
info!("Converted MAIL block → {}", result);
|
||||
result
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,14 +23,14 @@ pub fn convert_begin_blocks(script: &str) -> String {
|
|||
}
|
||||
|
||||
if upper == "BEGIN TALK" {
|
||||
info!("[TOOL] Converting BEGIN TALK statement");
|
||||
info!("Converting BEGIN TALK statement");
|
||||
in_talk_block = true;
|
||||
talk_block_lines.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
if upper == "END TALK" {
|
||||
info!("[TOOL] Converting END TALK statement, processing {} lines", talk_block_lines.len());
|
||||
info!("Converting END TALK statement, processing {} lines", talk_block_lines.len());
|
||||
in_talk_block = false;
|
||||
let converted = convert_talk_block(&talk_block_lines);
|
||||
result.push_str(&converted);
|
||||
|
|
@ -45,7 +45,7 @@ pub fn convert_begin_blocks(script: &str) -> String {
|
|||
|
||||
if upper.starts_with("BEGIN MAIL ") {
|
||||
let recipient = &trimmed[11..].trim();
|
||||
info!("[TOOL] Converting BEGIN MAIL statement: recipient='{}'", recipient);
|
||||
info!("Converting BEGIN MAIL statement: recipient='{}'", recipient);
|
||||
mail_recipient = recipient.to_string();
|
||||
in_mail_block = true;
|
||||
mail_block_lines.clear();
|
||||
|
|
@ -53,7 +53,7 @@ pub fn convert_begin_blocks(script: &str) -> String {
|
|||
}
|
||||
|
||||
if upper == "END MAIL" {
|
||||
info!("[TOOL] Converting END MAIL statement, processing {} lines", mail_block_lines.len());
|
||||
info!("Converting END MAIL statement, processing {} lines", mail_block_lines.len());
|
||||
in_mail_block = false;
|
||||
let converted = convert_mail_block(&mail_recipient, &mail_block_lines);
|
||||
result.push_str(&converted);
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ pub fn convert_talk_line_with_substitution(line: &str) -> String {
|
|||
result = "TALK \"\"".to_string();
|
||||
}
|
||||
|
||||
info!("[TOOL] Converted TALK line: '{}' → '{}'", line, result);
|
||||
info!("Converted TALK line: '{}' → '{}'", line, result);
|
||||
result
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -676,7 +676,7 @@ impl BasicCompiler {
|
|||
let table_name = table_name.trim_matches('"');
|
||||
|
||||
// Debug log to see what we're querying
|
||||
log::info!("[SAVE] Converting SAVE for table: '{}' (original: '{}')", table_name, &parts[0]);
|
||||
log::info!("Converting SAVE for table: '{}' (original: '{}')", table_name, &parts[0]);
|
||||
|
||||
// Get column names from TABLE definition (preserves order from .bas file)
|
||||
let column_names = self.get_table_columns_for_save(table_name, bot_id)?;
|
||||
|
|
@ -685,7 +685,7 @@ impl BasicCompiler {
|
|||
let values: Vec<&String> = parts.iter().skip(1).collect();
|
||||
let mut map_pairs = Vec::new();
|
||||
|
||||
log::info!("[SAVE] Matching {} variables to {} columns", values.len(), column_names.len());
|
||||
log::info!("Matching {} variables to {} columns", values.len(), column_names.len());
|
||||
|
||||
for value_var in values.iter() {
|
||||
// Find the column that matches this variable (case-insensitive)
|
||||
|
|
@ -694,7 +694,7 @@ impl BasicCompiler {
|
|||
if let Some(column_name) = column_names.iter().find(|col| col.to_lowercase() == value_lower) {
|
||||
map_pairs.push(format!("{}: {}", column_name, value_var));
|
||||
} else {
|
||||
log::warn!("[SAVE] No matching column for variable '{}'", value_var);
|
||||
log::warn!("No matching column for variable '{}'", value_var);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -401,7 +401,7 @@ pub fn get_suggestions(
|
|||
}
|
||||
}
|
||||
info!(
|
||||
"[SUGGESTIONS] Retrieved {} suggestions for session {}",
|
||||
"Retrieved {} suggestions for session {}",
|
||||
suggestions.len(),
|
||||
session_id
|
||||
);
|
||||
|
|
@ -413,7 +413,7 @@ pub fn get_suggestions(
|
|||
// .arg(&redis_key)
|
||||
// .query(&mut conn);
|
||||
// info!(
|
||||
// "[SUGGESTIONS] Cleared {} suggestions from Redis for session {}",
|
||||
// "Cleared {} suggestions from Redis for session {}",
|
||||
// suggestions.len(),
|
||||
// session_id
|
||||
// );
|
||||
|
|
@ -422,7 +422,7 @@ pub fn get_suggestions(
|
|||
Err(e) => error!("Failed to get suggestions from Redis: {}", e),
|
||||
}
|
||||
} else {
|
||||
info!("[SUGGESTIONS] No cache configured, cannot retrieve suggestions");
|
||||
info!("No cache configured, cannot retrieve suggestions");
|
||||
}
|
||||
|
||||
suggestions
|
||||
|
|
|
|||
|
|
@ -198,21 +198,13 @@ fn associate_tool_with_session(
|
|||
tool_name: &str,
|
||||
) -> Result<String, String> {
|
||||
use crate::core::shared::models::schema::session_tool_associations;
|
||||
use std::fs;
|
||||
|
||||
// Check if tool's .mcp.json file exists in work directory
|
||||
// Use relative path from botserver binary location
|
||||
// Use relative path from botserver binary current directory
|
||||
let gb_dir = std::env::current_dir()
|
||||
.unwrap_or_else(|_| PathBuf::from("."))
|
||||
.join("botserver-stack/data/system");
|
||||
|
||||
// Ensure work directory exists (create if not)
|
||||
let work_base = gb_dir.join("work");
|
||||
if !work_base.exists() {
|
||||
fs::create_dir_all(&work_base).map_err(|e| format!("Failed to create work directory {:?}: {}", work_base, e))?;
|
||||
info!("Created work directory at: {:?}", work_base);
|
||||
}
|
||||
|
||||
// Get bot name to construct the path
|
||||
let bot_name = get_bot_name_from_id(state, &user.bot_id)?;
|
||||
let work_path = Path::new(&gb_dir)
|
||||
|
|
|
|||
|
|
@ -587,7 +587,7 @@ impl ScriptService {
|
|||
.collect::<Vec<&str>>()
|
||||
.join("\n");
|
||||
|
||||
info!("[TOOL] Filtered tool metadata: {} -> {} chars", script.len(), executable_script.len());
|
||||
info!("Filtered tool metadata: {} -> {} chars", script.len(), executable_script.len());
|
||||
|
||||
// Apply minimal preprocessing for tools (skip variable normalization to avoid breaking multi-line strings)
|
||||
let script = preprocess_switch(&executable_script);
|
||||
|
|
@ -597,7 +597,7 @@ impl ScriptService {
|
|||
// let script = Self::convert_format_syntax(&script);
|
||||
// Skip normalize_variables_to_lowercase for tools - it breaks multi-line strings
|
||||
|
||||
info!("[TOOL] Preprocessed tool script for Rhai compilation");
|
||||
info!("Preprocessed tool script for Rhai compilation");
|
||||
// Convert SAVE statements with field lists to map-based SAVE (simplified version for tools)
|
||||
let script = Self::convert_save_for_tools(&script);
|
||||
// Convert BEGIN TALK and BEGIN MAIL blocks to single calls
|
||||
|
|
@ -986,7 +986,7 @@ impl ScriptService {
|
|||
};
|
||||
result.push_str(&format!("send_mail({}, \"{}\", {}, []);\n", recipient_expr, subject, body_expr));
|
||||
|
||||
log::info!("[TOOL] Converted MAIL block → {}", result);
|
||||
log::info!("Converted MAIL block → {}", result);
|
||||
result
|
||||
}
|
||||
|
||||
|
|
@ -1086,7 +1086,7 @@ impl ScriptService {
|
|||
let mut mail_block_lines: Vec<String> = Vec::new();
|
||||
let mut in_line_continuation = false;
|
||||
|
||||
log::info!("[TOOL] Converting IF/THEN syntax, input has {} lines", script.lines().count());
|
||||
log::info!("Converting IF/THEN syntax, input has {} lines", script.lines().count());
|
||||
|
||||
for line in script.lines() {
|
||||
let trimmed = line.trim();
|
||||
|
|
@ -1119,7 +1119,7 @@ impl ScriptService {
|
|||
} else {
|
||||
condition.to_string()
|
||||
};
|
||||
log::info!("[TOOL] Converting IF statement: condition='{}'", condition);
|
||||
log::info!("Converting IF statement: condition='{}'", condition);
|
||||
result.push_str("if ");
|
||||
result.push_str(&condition);
|
||||
result.push_str(" {\n");
|
||||
|
|
@ -1129,7 +1129,7 @@ impl ScriptService {
|
|||
|
||||
// Handle ELSE
|
||||
if upper == "ELSE" {
|
||||
log::info!("[TOOL] Converting ELSE statement");
|
||||
log::info!("Converting ELSE statement");
|
||||
result.push_str("} else {\n");
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1152,7 +1152,7 @@ impl ScriptService {
|
|||
} else {
|
||||
condition.to_string()
|
||||
};
|
||||
log::info!("[TOOL] Converting ELSEIF statement: condition='{}'", condition);
|
||||
log::info!("Converting ELSEIF statement: condition='{}'", condition);
|
||||
result.push_str("} else if ");
|
||||
result.push_str(&condition);
|
||||
result.push_str(" {\n");
|
||||
|
|
@ -1161,7 +1161,7 @@ impl ScriptService {
|
|||
|
||||
// Handle END IF
|
||||
if upper == "END IF" {
|
||||
log::info!("[TOOL] Converting END IF statement");
|
||||
log::info!("Converting END IF statement");
|
||||
if if_stack.pop().is_some() {
|
||||
result.push_str("}\n");
|
||||
}
|
||||
|
|
@ -1171,7 +1171,7 @@ impl ScriptService {
|
|||
// Handle WITH ... END WITH (BASIC object creation)
|
||||
if upper.starts_with("WITH ") {
|
||||
let object_name = &trimmed[5..].trim();
|
||||
log::info!("[TOOL] Converting WITH statement: object='{}'", object_name);
|
||||
log::info!("Converting WITH statement: object='{}'", object_name);
|
||||
// Convert WITH obj → let obj = #{ (start object literal)
|
||||
result.push_str("let ");
|
||||
result.push_str(object_name);
|
||||
|
|
@ -1181,7 +1181,7 @@ impl ScriptService {
|
|||
}
|
||||
|
||||
if upper == "END WITH" {
|
||||
log::info!("[TOOL] Converting END WITH statement");
|
||||
log::info!("Converting END WITH statement");
|
||||
result.push_str("};\n");
|
||||
in_with_block = false;
|
||||
continue;
|
||||
|
|
@ -1189,14 +1189,14 @@ impl ScriptService {
|
|||
|
||||
// Handle BEGIN TALK ... END TALK (multi-line TALK with ${} substitution)
|
||||
if upper == "BEGIN TALK" {
|
||||
log::info!("[TOOL] Converting BEGIN TALK statement");
|
||||
log::info!("Converting BEGIN TALK statement");
|
||||
in_talk_block = true;
|
||||
talk_block_lines.clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
if upper == "END TALK" {
|
||||
log::info!("[TOOL] Converting END TALK statement, processing {} lines", talk_block_lines.len());
|
||||
log::info!("Converting END TALK statement, processing {} lines", talk_block_lines.len());
|
||||
in_talk_block = false;
|
||||
|
||||
// Split into multiple TALK statements to avoid expression complexity limit
|
||||
|
|
@ -1239,7 +1239,7 @@ impl ScriptService {
|
|||
// Handle BEGIN MAIL ... END MAIL (multi-line email with ${} substitution)
|
||||
if upper.starts_with("BEGIN MAIL ") {
|
||||
let recipient = &trimmed[11..].trim(); // Skip "BEGIN MAIL "
|
||||
log::info!("[TOOL] Converting BEGIN MAIL statement: recipient='{}'", recipient);
|
||||
log::info!("Converting BEGIN MAIL statement: recipient='{}'", recipient);
|
||||
mail_recipient = recipient.to_string();
|
||||
in_mail_block = true;
|
||||
mail_block_lines.clear();
|
||||
|
|
@ -1247,7 +1247,7 @@ impl ScriptService {
|
|||
}
|
||||
|
||||
if upper == "END MAIL" {
|
||||
log::info!("[TOOL] Converting END MAIL statement, processing {} lines", mail_block_lines.len());
|
||||
log::info!("Converting END MAIL statement, processing {} lines", mail_block_lines.len());
|
||||
in_mail_block = false;
|
||||
|
||||
// Process the mail block and convert to SEND EMAIL
|
||||
|
|
@ -1287,11 +1287,11 @@ impl ScriptService {
|
|||
|
||||
// Handle SAVE table, field1, field2, ... → INSERT "table", #{field1: value1, field2: value2, ...}
|
||||
if upper.starts_with("SAVE") && upper.contains(',') {
|
||||
log::info!("[TOOL] Processing SAVE line: '{}'", trimmed);
|
||||
log::info!("Processing SAVE line: '{}'", trimmed);
|
||||
// Extract the part after "SAVE"
|
||||
let after_save = &trimmed[4..].trim(); // Skip "SAVE"
|
||||
let parts: Vec<&str> = after_save.split(',').collect();
|
||||
log::info!("[TOOL] SAVE parts: {:?}", parts);
|
||||
log::info!("SAVE parts: {:?}", parts);
|
||||
|
||||
if parts.len() >= 2 {
|
||||
// First part is the table name (in quotes)
|
||||
|
|
@ -1301,7 +1301,7 @@ impl ScriptService {
|
|||
if parts.len() == 2 {
|
||||
let object_name = parts[1].trim().trim_end_matches(';');
|
||||
let converted = format!("INSERT \"{}\", {};\n", table, object_name);
|
||||
log::info!("[TOOL] Converted SAVE to INSERT (old syntax): '{}'", converted);
|
||||
log::info!("Converted SAVE to INSERT (old syntax): '{}'", converted);
|
||||
result.push_str(&converted);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1310,7 +1310,7 @@ impl ScriptService {
|
|||
// The runtime SAVE handler will match them to database columns by position
|
||||
let values = parts[1..].join(", ");
|
||||
let converted = format!("SAVE \"{}\", {};\n", table, values);
|
||||
log::info!("[TOOL] Keeping SAVE syntax (modern): '{}'", converted);
|
||||
log::info!("Keeping SAVE syntax (modern): '{}'", converted);
|
||||
result.push_str(&converted);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1319,17 +1319,17 @@ impl ScriptService {
|
|||
// Handle SEND EMAIL → send_mail (function call style)
|
||||
// Syntax: SEND EMAIL to, subject, body → send_mail(to, subject, body, [])
|
||||
if upper.starts_with("SEND EMAIL") {
|
||||
log::info!("[TOOL] Processing SEND EMAIL line: '{}'", trimmed);
|
||||
log::info!("Processing SEND EMAIL line: '{}'", trimmed);
|
||||
let after_send = &trimmed[11..].trim(); // Skip "SEND EMAIL " (10 chars + space = 11)
|
||||
let parts: Vec<&str> = after_send.split(',').collect();
|
||||
log::info!("[TOOL] SEND EMAIL parts: {:?}", parts);
|
||||
log::info!("SEND EMAIL parts: {:?}", parts);
|
||||
if parts.len() == 3 {
|
||||
let to = parts[0].trim();
|
||||
let subject = parts[1].trim();
|
||||
let body = parts[2].trim().trim_end_matches(';');
|
||||
// Convert to send_mail(to, subject, body, []) function call
|
||||
let converted = format!("send_mail({}, {}, {}, []);\n", to, subject, body);
|
||||
log::info!("[TOOL] Converted SEND EMAIL to: '{}'", converted);
|
||||
log::info!("Converted SEND EMAIL to: '{}'", converted);
|
||||
result.push_str(&converted);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1397,7 +1397,7 @@ impl ScriptService {
|
|||
}
|
||||
}
|
||||
|
||||
log::info!("[TOOL] IF/THEN conversion complete, output has {} lines", result.lines().count());
|
||||
log::info!("IF/THEN conversion complete, output has {} lines", result.lines().count());
|
||||
|
||||
// Convert BASIC <> (not equal) to Rhai != globally
|
||||
|
||||
|
|
@ -1417,7 +1417,7 @@ impl ScriptService {
|
|||
let lines: Vec<&str> = script.lines().collect();
|
||||
let mut i = 0;
|
||||
|
||||
log::info!("[TOOL] Converting SELECT/CASE syntax to if-else chains");
|
||||
log::info!("Converting SELECT/CASE syntax to if-else chains");
|
||||
|
||||
// Helper function to strip 'let ' from the beginning of a line
|
||||
// This is needed because convert_if_then_syntax adds 'let' to all assignments,
|
||||
|
|
@ -1441,7 +1441,7 @@ impl ScriptService {
|
|||
if upper.starts_with("SELECT ") && !upper.contains(" THEN") {
|
||||
// Extract the variable being selected
|
||||
let select_var = trimmed[7..].trim(); // Skip "SELECT "
|
||||
log::info!("[TOOL] Converting SELECT statement for variable: '{}'", select_var);
|
||||
log::info!("Converting SELECT statement for variable: '{}'", select_var);
|
||||
|
||||
// Skip the SELECT line
|
||||
i += 1;
|
||||
|
|
|
|||
|
|
@ -440,9 +440,6 @@ impl BotOrchestrator {
|
|||
|
||||
let config_manager = ConfigManager::new(state_clone.conn.clone());
|
||||
|
||||
// DEBUG: Log which bot we're getting config for
|
||||
info!("[CONFIG_TRACE] Getting LLM config for bot_id: {}", session.bot_id);
|
||||
|
||||
// For local LLM server, use the actual model name
|
||||
// Default to DeepSeek model if not configured
|
||||
let model = config_manager
|
||||
|
|
@ -453,12 +450,6 @@ impl BotOrchestrator {
|
|||
.get_config(&session.bot_id, "llm-key", Some(""))
|
||||
.unwrap_or_default();
|
||||
|
||||
// DEBUG: Log the exact config values retrieved
|
||||
info!("[CONFIG_TRACE] Model: '{}'", model);
|
||||
info!("[CONFIG_TRACE] API Key: '{}' ({} chars)", key, key.len());
|
||||
info!("[CONFIG_TRACE] API Key first 10 chars: '{}'", &key.chars().take(10).collect::<String>());
|
||||
info!("[CONFIG_TRACE] API Key last 10 chars: '{}'", &key.chars().rev().take(10).collect::<String>());
|
||||
|
||||
Ok((session, context_data, history, model, key))
|
||||
},
|
||||
)
|
||||
|
|
@ -508,12 +499,10 @@ impl BotOrchestrator {
|
|||
let data_dir = "/opt/gbo/data";
|
||||
let start_script_path = format!("{}/{}.gbai/{}.gbdialog/start.bas", data_dir, bot_name_for_context, bot_name_for_context);
|
||||
|
||||
info!("[START_BAS] Executing start.bas for session {} at: {}", actual_session_id, start_script_path);
|
||||
trace!("Executing start.bas for session {} at: {}", actual_session_id, start_script_path);
|
||||
|
||||
if let Ok(metadata) = tokio::fs::metadata(&start_script_path).await {
|
||||
if metadata.is_file() {
|
||||
info!("[START_BAS] Found start.bas, executing for session {}", actual_session_id);
|
||||
|
||||
if let Ok(start_script) = tokio::fs::read_to_string(&start_script_path).await {
|
||||
let state_clone = self.state.clone();
|
||||
let actual_session_id_for_task = session.id;
|
||||
|
|
@ -551,7 +540,7 @@ impl BotOrchestrator {
|
|||
|
||||
match result {
|
||||
Ok(Ok(())) => {
|
||||
info!("[START_BAS] start.bas completed successfully for session {}", actual_session_id);
|
||||
trace!("start.bas completed successfully for session {}", actual_session_id);
|
||||
|
||||
// Mark start.bas as executed for this session to prevent re-running
|
||||
if let Some(cache) = &self.state.cache {
|
||||
|
|
@ -563,15 +552,14 @@ impl BotOrchestrator {
|
|||
.arg("86400") // Expire after 24 hours
|
||||
.query_async(&mut conn)
|
||||
.await;
|
||||
info!("[START_BAS] Marked start.bas as executed for session {}", actual_session_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
error!("[START_BAS] start.bas error for session {}: {}", actual_session_id, e);
|
||||
error!("start.bas error for session {}: {}", actual_session_id, e);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("[START_BAS] start.bas task error for session {}: {}", actual_session_id, e);
|
||||
error!("start.bas task error for session {}: {}", actual_session_id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -613,12 +601,7 @@ impl BotOrchestrator {
|
|||
}));
|
||||
}
|
||||
|
||||
// DEBUG: Log messages before sending to LLM
|
||||
info!("[LLM_CALL] Messages before LLM: {}", serde_json::to_string_pretty(&messages).unwrap_or_default());
|
||||
info!("[LLM_CALL] message_content: '{}'", message_content);
|
||||
|
||||
let (stream_tx, mut stream_rx) = mpsc::channel::<String>(100);
|
||||
info!("[STREAM_SETUP] Channel created, starting LLM stream");
|
||||
let llm = self.state.llm_provider.clone();
|
||||
|
||||
let model_clone = model.clone();
|
||||
|
|
@ -629,15 +612,13 @@ impl BotOrchestrator {
|
|||
let tools_for_llm = match session_tools {
|
||||
Ok(tools) => {
|
||||
if !tools.is_empty() {
|
||||
info!("[TOOLS] Loaded {} tools for session {}", tools.len(), session.id);
|
||||
Some(tools)
|
||||
} else {
|
||||
info!("[TOOLS] No tools associated with session {}", session.id);
|
||||
None
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("[TOOLS] Failed to load session tools: {}", e);
|
||||
warn!("Failed to load session tools: {}", e);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
|
@ -645,24 +626,13 @@ impl BotOrchestrator {
|
|||
// Clone messages for the async task
|
||||
let messages_clone = messages.clone();
|
||||
|
||||
// DEBUG: Log exact values being passed to LLM
|
||||
info!("[LLM_CALL] Calling generate_stream with:");
|
||||
info!("[LLM_CALL] Model: '{}'", model_clone);
|
||||
info!("[LLM_CALL] Key length: {} chars", key_clone.len());
|
||||
info!("[LLM_CALL] Key preview: '{}...{}'",
|
||||
&key_clone.chars().take(8).collect::<String>(),
|
||||
&key_clone.chars().rev().take(8).collect::<String>()
|
||||
);
|
||||
|
||||
tokio::spawn(async move {
|
||||
info!("[SPAWN_TASK] LLM stream task started");
|
||||
if let Err(e) = llm
|
||||
.generate_stream("", &messages_clone, stream_tx, &model_clone, &key_clone, tools_for_llm.as_ref())
|
||||
.await
|
||||
{
|
||||
error!("LLM streaming error: {}", e);
|
||||
}
|
||||
info!("[SPAWN_TASK] LLM stream task completed");
|
||||
});
|
||||
|
||||
let mut full_response = String::new();
|
||||
|
|
@ -673,9 +643,6 @@ impl BotOrchestrator {
|
|||
let mut tool_was_executed = false; // Track if a tool was executed to avoid duplicate final message
|
||||
let handler = llm_models::get_handler(&model);
|
||||
|
||||
info!("[STREAM_START] Entering stream processing loop for model: {}", model);
|
||||
info!("[STREAM_START] About to enter while loop, stream_rx is valid");
|
||||
|
||||
trace!("Using model handler for {}", model);
|
||||
|
||||
#[cfg(feature = "nvidia")]
|
||||
|
|
@ -700,7 +667,6 @@ impl BotOrchestrator {
|
|||
}
|
||||
|
||||
while let Some(chunk) = stream_rx.recv().await {
|
||||
info!("[STREAM_DEBUG] Received chunk: '{}', len: {}", chunk, chunk.len());
|
||||
trace!("Received LLM chunk: {:?}", chunk);
|
||||
|
||||
// ===== GENERIC TOOL EXECUTION =====
|
||||
|
|
@ -724,7 +690,6 @@ impl BotOrchestrator {
|
|||
// Send the part before { as regular content
|
||||
let regular_part = &chunk[..pos];
|
||||
if !regular_part.trim().is_empty() {
|
||||
info!("[STREAM_CONTENT] Sending regular part before JSON: '{}', len: {}", regular_part, regular_part.len());
|
||||
full_response.push_str(regular_part);
|
||||
|
||||
let response = BotResponse {
|
||||
|
|
@ -774,11 +739,6 @@ impl BotOrchestrator {
|
|||
};
|
||||
|
||||
if let Some(tc) = tool_call {
|
||||
info!(
|
||||
"[TOOL_CALL] Detected tool '{}' from LLM, executing...",
|
||||
tc.tool_name
|
||||
);
|
||||
|
||||
let execution_result = ToolExecutor::execute_tool_call(
|
||||
&self.state,
|
||||
&bot_name_for_context,
|
||||
|
|
@ -862,7 +822,6 @@ impl BotOrchestrator {
|
|||
// Increased limit to 50000 to handle large tool calls with many parameters
|
||||
if tool_call_buffer.len() > 50000 {
|
||||
// Flush accumulated content to client since it's too large to be a tool call
|
||||
info!("[TOOL_EXEC] Flushing tool_call_buffer (too large, assuming not a tool call)");
|
||||
full_response.push_str(&tool_call_buffer);
|
||||
|
||||
let response = BotResponse {
|
||||
|
|
@ -934,13 +893,9 @@ impl BotOrchestrator {
|
|||
|
||||
if in_analysis && handler.is_analysis_complete(&analysis_buffer) {
|
||||
in_analysis = false;
|
||||
info!(
|
||||
"[ANALYSIS] Detected end of thinking for model {}. Buffer: '{}'",
|
||||
model, analysis_buffer
|
||||
);
|
||||
trace!("Detected end of thinking for model {}", model);
|
||||
|
||||
let processed = handler.process_content(&analysis_buffer);
|
||||
info!("[ANALYSIS] Processed content: '{}'", processed);
|
||||
if !processed.is_empty() {
|
||||
full_response.push_str(&processed);
|
||||
|
||||
|
|
@ -975,7 +930,6 @@ impl BotOrchestrator {
|
|||
}
|
||||
|
||||
if !in_analysis {
|
||||
info!("[STREAM_CONTENT] Sending chunk: '{}', len: {}", chunk, chunk.len());
|
||||
full_response.push_str(&chunk);
|
||||
|
||||
let response = BotResponse {
|
||||
|
|
@ -1000,8 +954,6 @@ impl BotOrchestrator {
|
|||
}
|
||||
}
|
||||
|
||||
info!("[STREAM_END] While loop exited. full_response length: {}", full_response.len());
|
||||
|
||||
let state_for_save = self.state.clone();
|
||||
let full_response_clone = full_response.clone();
|
||||
tokio::task::spawn_blocking(
|
||||
|
|
@ -1366,18 +1318,14 @@ async fn handle_websocket(
|
|||
while let Some(Ok(msg)) = receiver.next().await {
|
||||
match msg {
|
||||
Message::Text(text) => {
|
||||
info!("Received WebSocket message: {}", text);
|
||||
if let Ok(user_msg) = serde_json::from_str::<UserMessage>(&text) {
|
||||
let orchestrator = BotOrchestrator::new(state_clone.clone());
|
||||
info!("[WS_DEBUG] Looking up response channel for session: {}", session_id);
|
||||
if let Some(tx_clone) = state_clone
|
||||
.response_channels
|
||||
.lock()
|
||||
.await
|
||||
.get(&session_id.to_string())
|
||||
{
|
||||
info!("[WS_DEBUG] Response channel found, calling stream_response");
|
||||
|
||||
// Ensure session exists - create if not
|
||||
let session_result = {
|
||||
let mut sm = state_clone.session_manager.lock().await;
|
||||
|
|
@ -1385,27 +1333,20 @@ async fn handle_websocket(
|
|||
};
|
||||
|
||||
let session = match session_result {
|
||||
Ok(Some(sess)) => {
|
||||
info!("[WS_DEBUG] Session exists: {}", session_id);
|
||||
sess
|
||||
}
|
||||
Ok(Some(sess)) => sess,
|
||||
Ok(None) => {
|
||||
info!("[WS_DEBUG] Session not found, creating via session manager");
|
||||
// Use session manager to create session (will generate new UUID)
|
||||
let mut sm = state_clone.session_manager.lock().await;
|
||||
match sm.create_session(user_id, bot_id, "WebSocket Chat") {
|
||||
Ok(new_session) => {
|
||||
info!("[WS_DEBUG] Session created: {} (note: different from WebSocket session_id)", new_session.id);
|
||||
new_session
|
||||
}
|
||||
Ok(new_session) => new_session,
|
||||
Err(e) => {
|
||||
error!("[WS_DEBUG] Failed to create session: {}", e);
|
||||
error!("Failed to create session: {}", e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("[WS_DEBUG] Error getting session: {}", e);
|
||||
error!("Error getting session: {}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
|
@ -1424,14 +1365,14 @@ async fn handle_websocket(
|
|||
error!("Failed to stream response: {}", e);
|
||||
}
|
||||
} else {
|
||||
warn!("[WS_DEBUG] Response channel NOT found for session: {}", session_id);
|
||||
warn!("Response channel NOT found for session: {}", session_id);
|
||||
}
|
||||
} else {
|
||||
warn!("[WS_DEBUG] Failed to parse UserMessage from: {}", text);
|
||||
warn!("Failed to parse UserMessage from: {}", text);
|
||||
}
|
||||
}
|
||||
Message::Close(_) => {
|
||||
info!("WebSocket close message received");
|
||||
trace!("WebSocket close message received");
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/// Generic tool executor for LLM tool calls
|
||||
/// Works across all LLM providers (GLM, OpenAI, Claude, etc.)
|
||||
use log::{error, info, warn};
|
||||
use log::{error, info, trace, warn};
|
||||
use serde_json::Value;
|
||||
// use std::collections::HashMap;
|
||||
use std::fs::OpenOptions;
|
||||
|
|
@ -60,7 +60,7 @@ impl ToolExecutor {
|
|||
}
|
||||
|
||||
// Also log to system logger
|
||||
error!("[TOOL_ERROR] Bot: {}, Tool: {}, Error: {}", bot_name, tool_name, error_msg);
|
||||
error!("Tool error in {} (bot: {}): {}", tool_name, bot_name, error_msg);
|
||||
}
|
||||
|
||||
/// Convert internal errors to user-friendly messages for browser
|
||||
|
|
@ -278,7 +278,7 @@ impl ToolExecutor {
|
|||
|
||||
// Set variable in script scope
|
||||
if let Err(e) = script_service.set_variable(key, &value_str) {
|
||||
warn!("[TOOL_EXEC] Failed to set variable '{}': {}", key, e);
|
||||
warn!("Failed to set variable '{}': {}", key, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -302,7 +302,7 @@ impl ToolExecutor {
|
|||
// Run the script
|
||||
match script_service.run(&ast) {
|
||||
Ok(result) => {
|
||||
info!("[TOOL_EXEC] Tool '{}' executed successfully", tool_name);
|
||||
trace!("Tool '{}' executed successfully", tool_name);
|
||||
|
||||
// Convert result to string
|
||||
let result_str = result.to_string();
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ pub fn is_embedding_server_ready() -> bool {
|
|||
pub fn set_embedding_server_ready(ready: bool) {
|
||||
EMBEDDING_SERVER_READY.store(ready, Ordering::SeqCst);
|
||||
if ready {
|
||||
info!("[EMBEDDING] Embedding server marked as ready");
|
||||
info!("Embedding server marked as ready");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -285,13 +285,13 @@ impl KbEmbeddingGenerator {
|
|||
is_healthy
|
||||
}
|
||||
_ => {
|
||||
warn!("[EMBEDDING] Health check failed: {}", e);
|
||||
warn!("Health check failed: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
warn!("[EMBEDDING] Health check timed out");
|
||||
warn!("Health check timed out");
|
||||
false
|
||||
}
|
||||
}
|
||||
|
|
@ -301,18 +301,18 @@ impl KbEmbeddingGenerator {
|
|||
let start = std::time::Instant::now();
|
||||
let max_wait = Duration::from_secs(max_wait_secs);
|
||||
|
||||
info!("[EMBEDDING] Waiting for embedding server at {} (max {}s)...",
|
||||
info!("Waiting for embedding server at {} (max {}s)...",
|
||||
self.config.embedding_url, max_wait_secs);
|
||||
|
||||
while start.elapsed() < max_wait {
|
||||
if self.check_health().await {
|
||||
info!("[EMBEDDING] Embedding server is ready after {:?}", start.elapsed());
|
||||
info!("Embedding server is ready after {:?}", start.elapsed());
|
||||
return true;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
}
|
||||
|
||||
warn!("[EMBEDDING] Embedding server not available after {}s", max_wait_secs);
|
||||
warn!("Embedding server not available after {}s", max_wait_secs);
|
||||
false
|
||||
}
|
||||
|
||||
|
|
@ -325,7 +325,7 @@ impl KbEmbeddingGenerator {
|
|||
}
|
||||
|
||||
if !is_embedding_server_ready() {
|
||||
info!("[EMBEDDING] Server not marked ready, checking health...");
|
||||
trace!("Server not marked ready, checking health...");
|
||||
if !self.wait_for_server(30).await {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Embedding server not available at {}. Skipping embedding generation.",
|
||||
|
|
@ -335,7 +335,7 @@ impl KbEmbeddingGenerator {
|
|||
}
|
||||
|
||||
let start_mem = MemoryStats::current();
|
||||
trace!("[EMBEDDING] Generating embeddings for {} chunks, RSS={}",
|
||||
trace!("Generating embeddings for {} chunks, RSS={}",
|
||||
chunks.len(), MemoryStats::format_bytes(start_mem.rss_bytes));
|
||||
|
||||
let mut results = Vec::with_capacity(chunks.len());
|
||||
|
|
@ -343,7 +343,7 @@ impl KbEmbeddingGenerator {
|
|||
|
||||
for (batch_num, batch) in chunks.chunks(self.config.batch_size).enumerate() {
|
||||
let batch_start = MemoryStats::current();
|
||||
trace!("[EMBEDDING] Processing batch {}/{} ({} items), RSS={}",
|
||||
trace!("Processing batch {}/{} ({} items), RSS={}",
|
||||
batch_num + 1,
|
||||
total_batches,
|
||||
batch.len(),
|
||||
|
|
@ -355,11 +355,11 @@ impl KbEmbeddingGenerator {
|
|||
).await {
|
||||
Ok(Ok(embeddings)) => embeddings,
|
||||
Ok(Err(e)) => {
|
||||
warn!("[EMBEDDING] Batch {} failed: {}", batch_num + 1, e);
|
||||
warn!("Batch {} failed: {}", batch_num + 1, e);
|
||||
break;
|
||||
}
|
||||
Err(_) => {
|
||||
warn!("[EMBEDDING] Batch {} timed out after {}s",
|
||||
warn!("Batch {} timed out after {}s",
|
||||
batch_num + 1, self.config.timeout_seconds);
|
||||
break;
|
||||
}
|
||||
|
|
@ -367,14 +367,14 @@ impl KbEmbeddingGenerator {
|
|||
|
||||
let batch_end = MemoryStats::current();
|
||||
let delta = batch_end.rss_bytes.saturating_sub(batch_start.rss_bytes);
|
||||
trace!("[EMBEDDING] Batch {} complete: {} embeddings, RSS={} (delta={})",
|
||||
trace!("Batch {} complete: {} embeddings, RSS={} (delta={})",
|
||||
batch_num + 1,
|
||||
batch_embeddings.len(),
|
||||
MemoryStats::format_bytes(batch_end.rss_bytes),
|
||||
MemoryStats::format_bytes(delta));
|
||||
|
||||
if delta > 100 * 1024 * 1024 {
|
||||
warn!("[EMBEDDING] Excessive memory growth detected ({}), stopping early",
|
||||
warn!("Excessive memory growth detected ({}), stopping early",
|
||||
MemoryStats::format_bytes(delta));
|
||||
for (chunk, embedding) in batch.iter().zip(batch_embeddings.iter()) {
|
||||
results.push((chunk.clone(), embedding.clone()));
|
||||
|
|
@ -392,7 +392,7 @@ impl KbEmbeddingGenerator {
|
|||
}
|
||||
|
||||
let end_mem = MemoryStats::current();
|
||||
trace!("[EMBEDDING] Generated {} embeddings, RSS={} (total delta={})",
|
||||
trace!("Generated {} embeddings, RSS={} (total delta={})",
|
||||
results.len(),
|
||||
MemoryStats::format_bytes(end_mem.rss_bytes),
|
||||
MemoryStats::format_bytes(end_mem.rss_bytes.saturating_sub(start_mem.rss_bytes)));
|
||||
|
|
@ -408,16 +408,16 @@ impl KbEmbeddingGenerator {
|
|||
let texts: Vec<String> = chunks.iter().map(|c| c.content.clone()).collect();
|
||||
let total_chars: usize = texts.iter().map(|t| t.len()).sum();
|
||||
|
||||
info!("[EMBEDDING] generate_batch_embeddings: {} texts, {} total chars",
|
||||
trace!("generate_batch_embeddings: {} texts, {} total chars",
|
||||
texts.len(), total_chars);
|
||||
|
||||
match self.generate_local_embeddings(&texts).await {
|
||||
Ok(embeddings) => {
|
||||
info!("[EMBEDDING] Local embeddings succeeded: {} vectors", embeddings.len());
|
||||
trace!("Local embeddings succeeded: {} vectors", embeddings.len());
|
||||
Ok(embeddings)
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("[EMBEDDING] Local embedding service failed: {}", e);
|
||||
warn!("Local embedding service failed: {}", e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
|
|
@ -437,7 +437,7 @@ impl KbEmbeddingGenerator {
|
|||
let request_size = serde_json::to_string(&request)
|
||||
.map(|s| s.len())
|
||||
.unwrap_or(0);
|
||||
info!("[EMBEDDING] Sending request to {} (size: {} bytes)",
|
||||
trace!("Sending request to {} (size: {} bytes)",
|
||||
self.config.embedding_url, request_size);
|
||||
|
||||
let response = self
|
||||
|
|
@ -462,7 +462,7 @@ impl KbEmbeddingGenerator {
|
|||
let response_bytes = response.bytes().await
|
||||
.context("Failed to read embedding response bytes")?;
|
||||
|
||||
info!("[EMBEDDING] Received response: {} bytes", response_bytes.len());
|
||||
trace!("Received response: {} bytes", response_bytes.len());
|
||||
|
||||
if response_bytes.len() > 50 * 1024 * 1024 {
|
||||
return Err(anyhow::anyhow!(
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ impl KbIndexer {
|
|||
log_jemalloc_stats();
|
||||
|
||||
if !is_embedding_server_ready() {
|
||||
info!("[KB_INDEXER] Embedding server not ready yet, waiting up to 60s...");
|
||||
info!("Embedding server not ready yet, waiting up to 60s...");
|
||||
if !self.embedding_generator.wait_for_server(60).await {
|
||||
warn!(
|
||||
"Embedding server is not available. KB indexing skipped. \
|
||||
|
|
@ -155,13 +155,13 @@ impl KbIndexer {
|
|||
self.ensure_collection_exists(&collection_name).await?;
|
||||
|
||||
let before_docs = MemoryStats::current();
|
||||
trace!("[KB_INDEXER] Before process_kb_folder RSS={}",
|
||||
trace!("Before process_kb_folder RSS={}",
|
||||
MemoryStats::format_bytes(before_docs.rss_bytes));
|
||||
|
||||
let documents = self.document_processor.process_kb_folder(kb_path).await?;
|
||||
|
||||
let after_docs = MemoryStats::current();
|
||||
trace!("[KB_INDEXER] After process_kb_folder: {} documents, RSS={} (delta={})",
|
||||
trace!("After process_kb_folder: {} documents, RSS={} (delta={})",
|
||||
documents.len(),
|
||||
MemoryStats::format_bytes(after_docs.rss_bytes),
|
||||
MemoryStats::format_bytes(after_docs.rss_bytes.saturating_sub(before_docs.rss_bytes)));
|
||||
|
|
@ -176,7 +176,7 @@ impl KbIndexer {
|
|||
|
||||
for (doc_path, chunks) in doc_iter {
|
||||
if chunks.is_empty() {
|
||||
debug!("[KB_INDEXER] Skipping document with no chunks: {}", doc_path);
|
||||
debug!("Skipping document with no chunks: {}", doc_path);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -198,7 +198,7 @@ impl KbIndexer {
|
|||
// Memory pressure check - more aggressive
|
||||
let current_mem = MemoryStats::current();
|
||||
if current_mem.rss_bytes > 1_500_000_000 { // 1.5GB threshold (reduced)
|
||||
warn!("[KB_INDEXER] High memory usage detected: {}, forcing cleanup",
|
||||
warn!("High memory usage detected: {}, forcing cleanup",
|
||||
MemoryStats::format_bytes(current_mem.rss_bytes));
|
||||
|
||||
// Force garbage collection hint
|
||||
|
|
@ -220,7 +220,7 @@ impl KbIndexer {
|
|||
self.update_collection_metadata(&collection_name, bot_name, kb_name, total_chunks)?;
|
||||
|
||||
let end_mem = MemoryStats::current();
|
||||
trace!("[KB_INDEXER] Indexing complete: {} docs, {} chunks, RSS={} (total delta={})",
|
||||
trace!("Indexing complete: {} docs, {} chunks, RSS={} (total delta={})",
|
||||
indexed_documents, total_chunks,
|
||||
MemoryStats::format_bytes(end_mem.rss_bytes),
|
||||
MemoryStats::format_bytes(end_mem.rss_bytes.saturating_sub(start_mem.rss_bytes)));
|
||||
|
|
@ -245,7 +245,7 @@ impl KbIndexer {
|
|||
while let Some((doc_path, chunks)) = batch_docs.pop() {
|
||||
let before_embed = MemoryStats::current();
|
||||
trace!(
|
||||
"[KB_INDEXER] Processing document: {} ({} chunks) RSS={}",
|
||||
"Processing document: {} ({} chunks) RSS={}",
|
||||
doc_path,
|
||||
chunks.len(),
|
||||
MemoryStats::format_bytes(before_embed.rss_bytes)
|
||||
|
|
@ -253,7 +253,7 @@ impl KbIndexer {
|
|||
|
||||
// Re-validate embedding server is still available
|
||||
if !is_embedding_server_ready() {
|
||||
warn!("[KB_INDEXER] Embedding server became unavailable during indexing, aborting batch");
|
||||
warn!("Embedding server became unavailable during indexing, aborting batch");
|
||||
return Err(anyhow::anyhow!(
|
||||
"Embedding server became unavailable during KB indexing. Processed {} documents before failure.",
|
||||
processed_count
|
||||
|
|
@ -265,7 +265,7 @@ impl KbIndexer {
|
|||
let chunk_batches = chunks.chunks(CHUNK_BATCH_SIZE);
|
||||
|
||||
for chunk_batch in chunk_batches {
|
||||
trace!("[KB_INDEXER] Processing chunk batch of {} chunks", chunk_batch.len());
|
||||
trace!("Processing chunk batch of {} chunks", chunk_batch.len());
|
||||
|
||||
let embeddings = match self
|
||||
.embedding_generator
|
||||
|
|
@ -274,7 +274,7 @@ impl KbIndexer {
|
|||
{
|
||||
Ok(emb) => emb,
|
||||
Err(e) => {
|
||||
warn!("[KB_INDEXER] Embedding generation failed for {}: {}", doc_path, e);
|
||||
warn!("Embedding generation failed for {}: {}", doc_path, e);
|
||||
break; // Skip to next document
|
||||
}
|
||||
};
|
||||
|
|
@ -287,7 +287,7 @@ impl KbIndexer {
|
|||
}
|
||||
|
||||
let after_embed = MemoryStats::current();
|
||||
trace!("[KB_INDEXER] After processing document: RSS={} (delta={})",
|
||||
trace!("After processing document: RSS={} (delta={})",
|
||||
MemoryStats::format_bytes(after_embed.rss_bytes),
|
||||
MemoryStats::format_bytes(after_embed.rss_bytes.saturating_sub(before_embed.rss_bytes)));
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::core::kb::KnowledgeBaseManager;
|
|||
use crate::core::shared::state::AppState;
|
||||
use crate::core::shared::utils::DbPool;
|
||||
use diesel::prelude::*;
|
||||
use log::{error, info, warn};
|
||||
use log::{error, trace, warn};
|
||||
use regex;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
|
|
@ -35,7 +35,7 @@ impl WebsiteCrawlerService {
|
|||
let service = Arc::clone(&self);
|
||||
|
||||
tokio::spawn(async move {
|
||||
info!("Website crawler service started");
|
||||
trace!("Website crawler service started");
|
||||
|
||||
let mut ticker = interval(service.check_interval);
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ impl WebsiteCrawlerService {
|
|||
}
|
||||
|
||||
async fn check_and_crawl_websites(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
info!("Checking for websites that need recrawling");
|
||||
trace!("Checking for websites that need recrawling");
|
||||
|
||||
// First, scan for new USE WEBSITE commands in .bas files
|
||||
self.scan_and_register_websites_from_scripts()?;
|
||||
|
|
@ -75,9 +75,9 @@ impl WebsiteCrawlerService {
|
|||
)
|
||||
.load(&mut conn)?;
|
||||
|
||||
info!("Total websites in database: {}", all_websites.len());
|
||||
trace!("Total websites in database: {}", all_websites.len());
|
||||
for ws in &all_websites {
|
||||
info!(" - URL: {}, status: {:?}, refresh: {:?}", ws.url, ws.crawl_status, ws.refresh_policy);
|
||||
trace!(" - URL: {}, status: {:?}, refresh: {:?}", ws.url, ws.crawl_status, ws.refresh_policy);
|
||||
}
|
||||
|
||||
let websites = diesel::sql_query(
|
||||
|
|
@ -90,7 +90,7 @@ impl WebsiteCrawlerService {
|
|||
)
|
||||
.load::<WebsiteCrawlRecord>(&mut conn)?;
|
||||
|
||||
info!("Found {} websites to recrawl (next_crawl <= NOW())", websites.len());
|
||||
trace!("Found {} websites to recrawl (next_crawl <= NOW())", websites.len());
|
||||
|
||||
// Process websites sequentially to prevent memory exhaustion
|
||||
for website in websites {
|
||||
|
|
@ -115,11 +115,11 @@ impl WebsiteCrawlerService {
|
|||
let db_pool = self.db_pool.clone();
|
||||
let active_crawls = Arc::clone(&self.active_crawls);
|
||||
|
||||
info!("Processing website: {}", website.url);
|
||||
trace!("Processing website: {}", website.url);
|
||||
|
||||
match Self::crawl_website(website, kb_manager, db_pool, active_crawls).await {
|
||||
Ok(_) => {
|
||||
info!("Successfully processed website crawl");
|
||||
trace!("Successfully processed website crawl");
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to crawl website: {}", e);
|
||||
|
|
@ -165,7 +165,7 @@ impl WebsiteCrawlerService {
|
|||
});
|
||||
};
|
||||
|
||||
info!("Starting crawl for website: {}", website.url);
|
||||
trace!("Starting crawl for website: {}", website.url);
|
||||
|
||||
let config_manager = ConfigManager::new(db_pool.clone());
|
||||
|
||||
|
|
@ -200,7 +200,7 @@ impl WebsiteCrawlerService {
|
|||
|
||||
match crawler.crawl().await {
|
||||
Ok(pages) => {
|
||||
info!("Crawled {} pages from {}", pages.len(), website.url);
|
||||
trace!("Crawled {} pages from {}", pages.len(), website.url);
|
||||
|
||||
let mut conn = db_pool.get()?;
|
||||
#[derive(QueryableByName)]
|
||||
|
|
@ -228,7 +228,7 @@ impl WebsiteCrawlerService {
|
|||
let total_pages = pages.len();
|
||||
|
||||
for (batch_idx, batch) in pages.chunks(BATCH_SIZE).enumerate() {
|
||||
info!("Processing batch {} of {} pages", batch_idx + 1, total_pages.div_ceil(BATCH_SIZE));
|
||||
trace!("Processing batch {} of {} pages", batch_idx + 1, total_pages.div_ceil(BATCH_SIZE));
|
||||
|
||||
for (idx, page) in batch.iter().enumerate() {
|
||||
let global_idx = batch_idx * BATCH_SIZE + idx;
|
||||
|
|
@ -258,7 +258,7 @@ impl WebsiteCrawlerService {
|
|||
if batch_idx == 0 || (batch_idx + 1) % 2 == 0 {
|
||||
// Index every 2 batches to prevent memory buildup
|
||||
match kb_manager.index_kb_folder(&bot_name, &kb_name, &work_path).await {
|
||||
Ok(_) => info!("Indexed batch {} successfully", batch_idx + 1),
|
||||
Ok(_) => trace!("Indexed batch {} successfully", batch_idx + 1),
|
||||
Err(e) => warn!("Failed to index batch {}: {}", batch_idx + 1, e),
|
||||
}
|
||||
|
||||
|
|
@ -290,7 +290,7 @@ impl WebsiteCrawlerService {
|
|||
.bind::<diesel::sql_types::Uuid, _>(&website.id)
|
||||
.execute(&mut conn)?;
|
||||
|
||||
info!(
|
||||
trace!(
|
||||
"Successfully recrawled {}, next crawl: {:?}",
|
||||
website.url, config.next_crawl
|
||||
);
|
||||
|
|
@ -318,7 +318,7 @@ impl WebsiteCrawlerService {
|
|||
}
|
||||
|
||||
fn scan_and_register_websites_from_scripts(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
info!("Scanning .bas files for USE WEBSITE commands");
|
||||
trace!("Scanning .bas files for USE WEBSITE commands");
|
||||
|
||||
let work_dir = std::path::Path::new("work");
|
||||
if !work_dir.exists() {
|
||||
|
|
@ -416,7 +416,7 @@ impl WebsiteCrawlerService {
|
|||
.unwrap_or(0);
|
||||
|
||||
if exists == 0 {
|
||||
info!("Auto-registering website {} for bot {} with refresh: {}", url_str, bot_id, refresh_str);
|
||||
trace!("Auto-registering website {} for bot {} with refresh: {}", url_str, bot_id, refresh_str);
|
||||
|
||||
// Register website for crawling with refresh policy
|
||||
crate::basic::keywords::use_website::register_website_for_crawling_with_refresh(
|
||||
|
|
@ -480,7 +480,7 @@ pub async fn ensure_crawler_service_running(
|
|||
|
||||
drop(service.start());
|
||||
|
||||
info!("Website crawler service initialized");
|
||||
trace!("Website crawler service initialized");
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1080,7 +1080,7 @@ EOF"#.to_string(),
|
|||
component.name,
|
||||
rendered_cmd
|
||||
);
|
||||
trace!("[START] Working dir: {}", bin_path.display());
|
||||
trace!("Working dir: {}", bin_path.display());
|
||||
let child = SafeCommand::new("sh")
|
||||
.and_then(|c| c.arg("-c"))
|
||||
.and_then(|c| c.trusted_shell_script_arg(&rendered_cmd))
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ component: component.to_string(),
|
|||
if let Ok(mut registry) = THREAD_REGISTRY.write() {
|
||||
registry.insert(name.to_string(), info);
|
||||
}
|
||||
trace!("[THREAD] Registered: {} (component: {})", name, component);
|
||||
trace!("Registered: {} (component: {})", name, component);
|
||||
}
|
||||
|
||||
pub fn record_thread_activity(name: &str) {
|
||||
|
|
@ -47,12 +47,12 @@ pub fn unregister_thread(name: &str) {
|
|||
if let Ok(mut registry) = THREAD_REGISTRY.write() {
|
||||
registry.remove(name);
|
||||
}
|
||||
info!("[THREAD] Unregistered: {}", name);
|
||||
info!("Unregistered: {}", name);
|
||||
}
|
||||
|
||||
pub fn log_thread_stats() {
|
||||
if let Ok(registry) = THREAD_REGISTRY.read() {
|
||||
info!("[THREADS] Active thread count: {}", registry.len());
|
||||
info!("Active thread count: {}", registry.len());
|
||||
for (name, info) in registry.iter() {
|
||||
let uptime = info.started_at.elapsed().as_secs();
|
||||
let idle = info.last_activity.elapsed().as_secs();
|
||||
|
|
@ -185,7 +185,7 @@ if let Some(stats) = get_jemalloc_stats() {
|
|||
stats.log();
|
||||
let frag = stats.fragmentation_ratio();
|
||||
if frag > 1.5 {
|
||||
warn!("[JEMALLOC] High fragmentation detected: {:.2}x", frag);
|
||||
warn!("High fragmentation detected: {:.2}x", frag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -446,7 +446,7 @@ tokio::spawn(async move {
|
|||
record_component("global");
|
||||
|
||||
if let Some(warning) = detector.check() {
|
||||
warn!("[MONITOR] {}", warning);
|
||||
warn!("{}", warning);
|
||||
stats.log();
|
||||
log_component_stats();
|
||||
log_thread_stats();
|
||||
|
|
@ -454,7 +454,7 @@ tokio::spawn(async move {
|
|||
|
||||
// Switch to normal interval after startup period
|
||||
if tick_count == startup_ticks {
|
||||
trace!("[MONITOR] Switching to normal interval ({}s)", interval_secs);
|
||||
trace!("Switching to normal interval ({}s)", interval_secs);
|
||||
interval = tokio::time::interval(normal_interval);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -505,7 +505,7 @@ impl std::fmt::Debug for AppState {
|
|||
impl AppState {
|
||||
pub fn broadcast_task_progress(&self, event: TaskProgressEvent) {
|
||||
log::info!(
|
||||
"[TASK_PROGRESS] Broadcasting: task_id={}, step={}, message={}",
|
||||
"Broadcasting: task_id={}, step={}, message={}",
|
||||
event.task_id,
|
||||
event.step,
|
||||
event.message
|
||||
|
|
@ -513,19 +513,19 @@ impl AppState {
|
|||
if let Some(tx) = &self.task_progress_broadcast {
|
||||
let receiver_count = tx.receiver_count();
|
||||
log::info!(
|
||||
"[TASK_PROGRESS] Broadcast channel has {} receivers",
|
||||
"Broadcast channel has {} receivers",
|
||||
receiver_count
|
||||
);
|
||||
match tx.send(event) {
|
||||
Ok(_) => {
|
||||
log::info!("[TASK_PROGRESS] Event sent successfully");
|
||||
log::info!("Event sent successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("[TASK_PROGRESS] No listeners for task progress: {e}");
|
||||
log::warn!("No listeners for task progress: {e}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::warn!("[TASK_PROGRESS] No broadcast channel configured!");
|
||||
log::warn!("No broadcast channel configured!");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ impl DriveMonitor {
|
|||
|
||||
// Check if already processing to prevent duplicate monitoring
|
||||
if self.is_processing.load(std::sync::atomic::Ordering::Acquire) {
|
||||
warn!("[DRIVE_MONITOR] Already processing for bot {}, skipping", self.bot_id);
|
||||
warn!("Already processing for bot {}, skipping", self.bot_id);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
|
@ -234,18 +234,18 @@ impl DriveMonitor {
|
|||
.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
trace!("start_monitoring: calling check_for_changes...");
|
||||
info!("[DRIVE_MONITOR] Calling initial check_for_changes...");
|
||||
info!("Calling initial check_for_changes...");
|
||||
|
||||
match tokio::time::timeout(Duration::from_secs(300), self.check_for_changes()).await {
|
||||
Ok(Ok(_)) => {
|
||||
self.consecutive_failures.store(0, Ordering::Relaxed);
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
warn!("[DRIVE_MONITOR] Initial check failed (will retry): {}", e);
|
||||
warn!("Initial check failed (will retry): {}", e);
|
||||
self.consecutive_failures.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
Err(_) => {
|
||||
error!("[DRIVE_MONITOR] Initial check timed out after 5 minutes");
|
||||
error!("Initial check timed out after 5 minutes");
|
||||
self.consecutive_failures.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
|
@ -260,15 +260,15 @@ impl DriveMonitor {
|
|||
|
||||
// Force enable periodic monitoring regardless of initial check result
|
||||
self.is_processing.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||
info!("[DRIVE_MONITOR] Forced is_processing to true for periodic monitoring");
|
||||
info!("Forced is_processing to true for periodic monitoring");
|
||||
|
||||
let self_clone = self.clone(); // Don't wrap in Arc::new - that creates a copy
|
||||
tokio::spawn(async move {
|
||||
let mut consecutive_processing_failures = 0;
|
||||
info!("[DRIVE_MONITOR] Starting periodic monitoring loop for bot {}", self_clone.bot_id);
|
||||
info!("Starting periodic monitoring loop for bot {}", self_clone.bot_id);
|
||||
|
||||
let is_processing_state = self_clone.is_processing.load(std::sync::atomic::Ordering::SeqCst);
|
||||
info!("[DRIVE_MONITOR] is_processing state at loop start: {} for bot {}", is_processing_state, self_clone.bot_id);
|
||||
info!("is_processing state at loop start: {} for bot {}", is_processing_state, self_clone.bot_id);
|
||||
|
||||
while self_clone
|
||||
.is_processing
|
||||
|
|
@ -288,7 +288,7 @@ impl DriveMonitor {
|
|||
.fetch_add(1, Ordering::Relaxed)
|
||||
+ 1;
|
||||
if failures % 10 == 1 {
|
||||
warn!("[DRIVE_MONITOR] S3/MinIO unavailable for bucket {} (failures: {}), backing off to {:?}",
|
||||
warn!("S3/MinIO unavailable for bucket {} (failures: {}), backing off to {:?}",
|
||||
self_clone.bucket_name, failures, self_clone.calculate_backoff());
|
||||
}
|
||||
continue;
|
||||
|
|
@ -302,7 +302,7 @@ impl DriveMonitor {
|
|||
self_clone.consecutive_failures.swap(0, Ordering::Relaxed);
|
||||
consecutive_processing_failures = 0;
|
||||
if prev_failures > 0 {
|
||||
info!("[DRIVE_MONITOR] S3/MinIO recovered for bucket {} after {} failures",
|
||||
info!("S3/MinIO recovered for bucket {} after {} failures",
|
||||
self_clone.bucket_name, prev_failures);
|
||||
}
|
||||
}
|
||||
|
|
@ -315,18 +315,18 @@ impl DriveMonitor {
|
|||
|
||||
// If too many consecutive failures, stop processing temporarily
|
||||
if consecutive_processing_failures > 10 {
|
||||
error!("[DRIVE_MONITOR] Too many consecutive failures ({}), stopping processing for bot {}",
|
||||
error!("Too many consecutive failures ({}), stopping processing for bot {}",
|
||||
consecutive_processing_failures, self_clone.bot_id);
|
||||
self_clone.is_processing.store(false, std::sync::atomic::Ordering::SeqCst);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
error!("[DRIVE_MONITOR] check_for_changes timed out for bot {}", self_clone.bot_id);
|
||||
error!("check_for_changes timed out for bot {}", self_clone.bot_id);
|
||||
consecutive_processing_failures += 1;
|
||||
|
||||
if consecutive_processing_failures > 5 {
|
||||
error!("[DRIVE_MONITOR] Too many timeouts, stopping processing for bot {}", self_clone.bot_id);
|
||||
error!("Too many timeouts, stopping processing for bot {}", self_clone.bot_id);
|
||||
self_clone.is_processing.store(false, std::sync::atomic::Ordering::SeqCst);
|
||||
break;
|
||||
}
|
||||
|
|
@ -334,7 +334,7 @@ impl DriveMonitor {
|
|||
}
|
||||
}
|
||||
|
||||
info!("[DRIVE_MONITOR] Monitoring loop ended for bot {}", self_clone.bot_id);
|
||||
info!("Monitoring loop ended for bot {}", self_clone.bot_id);
|
||||
});
|
||||
|
||||
info!("DriveMonitor started for bot {}", self.bot_id);
|
||||
|
|
@ -373,7 +373,7 @@ impl DriveMonitor {
|
|||
if !self.check_drive_health().await {
|
||||
let failures = self.consecutive_failures.fetch_add(1, Ordering::Relaxed) + 1;
|
||||
if failures % 10 == 1 {
|
||||
warn!("[DRIVE_MONITOR] S3/MinIO unavailable for bucket {} (failures: {}), backing off to {:?}",
|
||||
warn!("S3/MinIO unavailable for bucket {} (failures: {}), backing off to {:?}",
|
||||
self.bucket_name, failures, self.calculate_backoff());
|
||||
}
|
||||
continue;
|
||||
|
|
@ -385,7 +385,7 @@ impl DriveMonitor {
|
|||
Ok(_) => {
|
||||
let prev_failures = self.consecutive_failures.swap(0, Ordering::Relaxed);
|
||||
if prev_failures > 0 {
|
||||
info!("[DRIVE_MONITOR] S3/MinIO recovered for bucket {} after {} failures",
|
||||
info!("S3/MinIO recovered for bucket {} after {} failures",
|
||||
self.bucket_name, prev_failures);
|
||||
}
|
||||
}
|
||||
|
|
@ -408,12 +408,12 @@ impl DriveMonitor {
|
|||
);
|
||||
|
||||
let Some(client) = &self.state.drive else {
|
||||
warn!("[DRIVE_MONITOR] No drive client available for bot {}, skipping file monitoring", self.bot_id);
|
||||
warn!("No drive client available for bot {}, skipping file monitoring", self.bot_id);
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
trace!("check_for_changes: calling check_gbdialog_changes...");
|
||||
trace!("[DRIVE_MONITOR] Checking gbdialog...");
|
||||
trace!("Checking gbdialog...");
|
||||
self.check_gbdialog_changes(client).await?;
|
||||
trace!("check_for_changes: check_gbdialog_changes done");
|
||||
let after_dialog = MemoryStats::current();
|
||||
|
|
@ -424,7 +424,7 @@ impl DriveMonitor {
|
|||
);
|
||||
|
||||
trace!("check_for_changes: calling check_gbot...");
|
||||
trace!("[DRIVE_MONITOR] Checking gbot...");
|
||||
trace!("Checking gbot...");
|
||||
self.check_gbot(client).await?;
|
||||
trace!("check_for_changes: check_gbot done");
|
||||
let after_gbot = MemoryStats::current();
|
||||
|
|
@ -435,7 +435,7 @@ impl DriveMonitor {
|
|||
);
|
||||
|
||||
trace!("check_for_changes: calling check_gbkb_changes...");
|
||||
trace!("[DRIVE_MONITOR] Checking gbkb...");
|
||||
trace!("Checking gbkb...");
|
||||
self.check_gbkb_changes(client).await?;
|
||||
trace!("check_for_changes: check_gbkb_changes done");
|
||||
let after_gbkb = MemoryStats::current();
|
||||
|
|
@ -531,7 +531,7 @@ impl DriveMonitor {
|
|||
let self_clone = Arc::new(self.clone());
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = self_clone.save_file_states().await {
|
||||
warn!("[DRIVE_MONITOR] Failed to save file states: {}", e);
|
||||
warn!("Failed to save file states: {}", e);
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
|
|
@ -1145,7 +1145,7 @@ impl DriveMonitor {
|
|||
#[cfg(any(feature = "research", feature = "llm"))]
|
||||
{
|
||||
if !is_embedding_server_ready() {
|
||||
info!("[DRIVE_MONITOR] Embedding server not ready, deferring KB indexing for {}", kb_folder_path.display());
|
||||
info!("Embedding server not ready, deferring KB indexing for {}", kb_folder_path.display());
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -1256,7 +1256,7 @@ impl DriveMonitor {
|
|||
let self_clone = Arc::new(self.clone());
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = self_clone.save_file_states().await {
|
||||
warn!("[DRIVE_MONITOR] Failed to save file states: {}", e);
|
||||
warn!("Failed to save file states: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ impl LocalFileMonitor {
|
|||
// Use botserver/work as the work directory for generated files
|
||||
let work_root = PathBuf::from("work");
|
||||
|
||||
info!("[LOCAL_MONITOR] Initializing with data_dir: {:?}, work_root: {:?}", data_dir, work_root);
|
||||
info!("Initializing with data_dir: {:?}, work_root: {:?}", data_dir, work_root);
|
||||
|
||||
Self {
|
||||
state,
|
||||
|
|
@ -48,11 +48,11 @@ impl LocalFileMonitor {
|
|||
}
|
||||
|
||||
pub async fn start_monitoring(&self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
info!("[LOCAL_MONITOR] Starting local file monitor for /opt/gbo/data/*.gbai directories");
|
||||
info!("Starting local file monitor for /opt/gbo/data/*.gbai directories");
|
||||
|
||||
// Create data directory if it doesn't exist
|
||||
if let Err(e) = tokio::fs::create_dir_all(&self.data_dir).await {
|
||||
warn!("[LOCAL_MONITOR] Failed to create data directory: {}", e);
|
||||
warn!("Failed to create data directory: {}", e);
|
||||
}
|
||||
|
||||
// Initial scan of all .gbai directories
|
||||
|
|
@ -66,12 +66,12 @@ impl LocalFileMonitor {
|
|||
monitor.monitoring_loop().await;
|
||||
});
|
||||
|
||||
info!("[LOCAL_MONITOR] Local file monitor started");
|
||||
info!("Local file monitor started");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn monitoring_loop(&self) {
|
||||
info!("[LOCAL_MONITOR] Starting monitoring loop");
|
||||
info!("Starting monitoring loop");
|
||||
|
||||
// Try to create a file system watcher
|
||||
let (tx, mut rx) = tokio::sync::mpsc::channel(100);
|
||||
|
|
@ -88,7 +88,7 @@ impl LocalFileMonitor {
|
|||
) {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
error!("[LOCAL_MONITOR] Failed to create watcher: {}. Falling back to polling.", e);
|
||||
error!("Failed to create watcher: {}. Falling back to polling.", e);
|
||||
// Fall back to polling if watcher creation fails
|
||||
self.polling_loop().await;
|
||||
return;
|
||||
|
|
@ -97,13 +97,13 @@ impl LocalFileMonitor {
|
|||
|
||||
// Watch the data directory
|
||||
if let Err(e) = watcher.watch(&self.data_dir, RecursiveMode::Recursive) {
|
||||
warn!("[LOCAL_MONITOR] Failed to watch directory {:?}: {}. Using polling fallback.", self.data_dir, e);
|
||||
warn!("Failed to watch directory {:?}: {}. Using polling fallback.", self.data_dir, e);
|
||||
drop(watcher);
|
||||
self.polling_loop().await;
|
||||
return;
|
||||
}
|
||||
|
||||
info!("[LOCAL_MONITOR] Watching directory: {:?}", self.data_dir);
|
||||
info!("Watching directory: {:?}", self.data_dir);
|
||||
|
||||
while self.is_processing.load(Ordering::SeqCst) {
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
|
|
@ -114,9 +114,9 @@ impl LocalFileMonitor {
|
|||
EventKind::Create(_) | EventKind::Modify(_) | EventKind::Any => {
|
||||
for path in &event.paths {
|
||||
if self.is_gbdialog_file(path) {
|
||||
info!("[LOCAL_MONITOR] Detected change: {:?}", path);
|
||||
info!("Detected change: {:?}", path);
|
||||
if let Err(e) = self.compile_local_file(path).await {
|
||||
error!("[LOCAL_MONITOR] Failed to compile {:?}: {}", path, e);
|
||||
error!("Failed to compile {:?}: {}", path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -124,7 +124,7 @@ impl LocalFileMonitor {
|
|||
EventKind::Remove(_) => {
|
||||
for path in &event.paths {
|
||||
if self.is_gbdialog_file(path) {
|
||||
info!("[LOCAL_MONITOR] File removed: {:?}", path);
|
||||
info!("File removed: {:?}", path);
|
||||
self.remove_file_state(path).await;
|
||||
}
|
||||
}
|
||||
|
|
@ -135,21 +135,21 @@ impl LocalFileMonitor {
|
|||
|
||||
// Periodic scan to catch any missed changes
|
||||
if let Err(e) = self.scan_and_compile_all().await {
|
||||
error!("[LOCAL_MONITOR] Scan failed: {}", e);
|
||||
error!("Scan failed: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
info!("[LOCAL_MONITOR] Monitoring loop ended");
|
||||
info!("Monitoring loop ended");
|
||||
}
|
||||
|
||||
async fn polling_loop(&self) {
|
||||
info!("[LOCAL_MONITOR] Using polling fallback (checking every 10s)");
|
||||
info!("Using polling fallback (checking every 10s)");
|
||||
|
||||
while self.is_processing.load(Ordering::SeqCst) {
|
||||
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||
|
||||
if let Err(e) = self.scan_and_compile_all().await {
|
||||
error!("[LOCAL_MONITOR] Scan failed: {}", e);
|
||||
error!("Scan failed: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -229,9 +229,9 @@ impl LocalFileMonitor {
|
|||
};
|
||||
|
||||
if should_compile {
|
||||
info!("[LOCAL_MONITOR] Compiling: {:?}", path);
|
||||
info!("Compiling: {:?}", path);
|
||||
if let Err(e) = self.compile_local_file(&path).await {
|
||||
error!("[LOCAL_MONITOR] Failed to compile {:?}: {}", path, e);
|
||||
error!("Failed to compile {:?}: {}", path, e);
|
||||
}
|
||||
|
||||
// Update state
|
||||
|
|
@ -304,7 +304,7 @@ impl LocalFileMonitor {
|
|||
})
|
||||
.await??;
|
||||
|
||||
info!("[LOCAL_MONITOR] Successfully compiled: {:?}", file_path);
|
||||
info!("Successfully compiled: {:?}", file_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -315,7 +315,7 @@ impl LocalFileMonitor {
|
|||
}
|
||||
|
||||
pub async fn stop_monitoring(&self) {
|
||||
info!("[LOCAL_MONITOR] Stopping local file monitor");
|
||||
info!("Stopping local file monitor");
|
||||
self.is_processing.store(false, Ordering::SeqCst);
|
||||
self.file_states.write().await.clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -421,7 +421,7 @@ impl ClaudeClient {
|
|||
|
||||
if !status.is_success() {
|
||||
let error_text = response.text().await.unwrap_or_default();
|
||||
error!("[CLAUDE] Error response: {}", error_text);
|
||||
error!("Claude API error response: {}", error_text);
|
||||
return Err(format!("HTTP {}: {}", status, error_text).into());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@ impl LLMProvider for GLMClient {
|
|||
match tx.send(tool_call_json).await {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
error!("[GLM_TX] Failed to send tool_call to channel: {}", e);
|
||||
error!("Failed to send tool_call to channel: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -318,7 +318,7 @@ impl LLMProvider for GLMClient {
|
|||
match tx.send(content.to_string()).await {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
error!("[GLM_TX] Failed to send to channel: {}", e);
|
||||
error!("Failed to send to channel: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -407,6 +407,19 @@ impl LLMProvider for OpenAIClient {
|
|||
let _ = tx.send(processed).await;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle standard OpenAI tool_calls
|
||||
if let Some(tool_calls) = data["choices"][0]["delta"]["tool_calls"].as_array() {
|
||||
for tool_call in tool_calls {
|
||||
// We send the tool_call object as a JSON string so stream_response
|
||||
// can buffer it and parse it using ToolExecutor::parse_tool_call
|
||||
if let Some(func) = tool_call.get("function") {
|
||||
if let Some(args) = func.get("arguments").and_then(|a| a.as_str()) {
|
||||
let _ = tx.send(args.to_string()).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ pub async fn handle_task_list(
|
|||
|
||||
let result = tokio::task::spawn_blocking(move || {
|
||||
let mut db_conn = conn.get().map_err(|e| {
|
||||
error!("[TASK_LIST] DB connection error: {}", e);
|
||||
error!("DB connection error: {}", e);
|
||||
diesel::result::Error::DatabaseError(
|
||||
diesel::result::DatabaseErrorKind::UnableToSendCommand,
|
||||
Box::new(e.to_string()),
|
||||
|
|
@ -95,7 +95,7 @@ pub async fn handle_task_list(
|
|||
)
|
||||
.load::<AutoTaskRow>(&mut db_conn)
|
||||
.map_err(|e| {
|
||||
error!("[TASK_LIST] Query error: {}", e);
|
||||
error!("Query error: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
|
|
@ -106,11 +106,11 @@ pub async fn handle_task_list(
|
|||
match result {
|
||||
Ok(Ok(tasks)) => (StatusCode::OK, axum::Json(tasks)).into_response(),
|
||||
Ok(Err(e)) => {
|
||||
error!("[TASK_LIST] DB error: {}", e);
|
||||
error!("DB error: {}", e);
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, "Database error").into_response()
|
||||
}
|
||||
Err(e) => {
|
||||
error!("[TASK_LIST] Task join error: {}", e);
|
||||
error!("Task join error: {}", e);
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, "Internal server error").into_response()
|
||||
}
|
||||
}
|
||||
|
|
@ -122,7 +122,7 @@ pub async fn handle_task_get(
|
|||
Path(id): Path<String>,
|
||||
headers: axum::http::HeaderMap,
|
||||
) -> impl IntoResponse {
|
||||
info!("[TASK_GET] *** Handler called for task: {} ***", id);
|
||||
info!("*** Handler called for task: {} ***", id);
|
||||
|
||||
// Check if client wants JSON (for polling) vs HTML (for HTMX)
|
||||
let wants_json = headers
|
||||
|
|
@ -138,7 +138,7 @@ pub async fn handle_task_get(
|
|||
let mut db_conn = conn
|
||||
.get()
|
||||
.map_err(|e| {
|
||||
error!("[TASK_GET] DB connection error: {}", e);
|
||||
error!("DB connection error: {}", e);
|
||||
format!("DB connection error: {}", e)
|
||||
})?;
|
||||
|
||||
|
|
@ -176,11 +176,11 @@ pub async fn handle_task_get(
|
|||
|
||||
let parsed_uuid = match Uuid::parse_str(&task_id) {
|
||||
Ok(u) => {
|
||||
info!("[TASK_GET] Parsed UUID: {}", u);
|
||||
info!("Parsed UUID: {}", u);
|
||||
u
|
||||
}
|
||||
Err(e) => {
|
||||
error!("[TASK_GET] Invalid task ID '{}': {}", task_id, e);
|
||||
error!("Invalid task ID '{}': {}", task_id, e);
|
||||
return Err(format!("Invalid task ID: {}", task_id));
|
||||
}
|
||||
};
|
||||
|
|
@ -192,12 +192,12 @@ pub async fn handle_task_get(
|
|||
.bind::<diesel::sql_types::Uuid, _>(parsed_uuid)
|
||||
.get_result(&mut db_conn)
|
||||
.map_err(|e| {
|
||||
error!("[TASK_GET] Query error for {}: {}", parsed_uuid, e);
|
||||
error!("Query error for {}: {}", parsed_uuid, e);
|
||||
e
|
||||
})
|
||||
.ok();
|
||||
|
||||
info!("[TASK_GET] Query result for {}: found={}", parsed_uuid, task.is_some());
|
||||
info!("Query result for {}: found={}", parsed_uuid, task.is_some());
|
||||
Ok::<_, String>(task)
|
||||
})
|
||||
.await
|
||||
|
|
@ -208,7 +208,7 @@ pub async fn handle_task_get(
|
|||
|
||||
match result {
|
||||
Ok(Some(task)) => {
|
||||
info!("[TASK_GET] Returning task: {} - {} (wants_json={})", task.id, task.title, wants_json);
|
||||
info!("Returning task: {} - {} (wants_json={})", task.id, task.title, wants_json);
|
||||
|
||||
// Return JSON for API polling clients
|
||||
if wants_json {
|
||||
|
|
@ -371,11 +371,11 @@ pub async fn handle_task_get(
|
|||
(StatusCode::OK, axum::response::Html(html)).into_response()
|
||||
}
|
||||
Ok(None) => {
|
||||
warn!("[TASK_GET] Task not found: {}", id);
|
||||
warn!("Task not found: {}", id);
|
||||
(StatusCode::NOT_FOUND, axum::response::Html("<div class='error'>Task not found</div>".to_string())).into_response()
|
||||
}
|
||||
Err(e) => {
|
||||
error!("[TASK_GET] Error fetching task {}: {}", id, e);
|
||||
error!("Error fetching task {}: {}", id, e);
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, axum::response::Html(format!("<div class='error'>{}</div>", e))).into_response()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,12 +62,12 @@ pub fn build_terminal_html(step_results: &Option<serde_json::Value>, status: &st
|
|||
}
|
||||
|
||||
pub fn build_taskmd_html(state: &Arc<AppState>, task_id: &str, title: &str, runtime: &str, db_manifest: Option<&serde_json::Value>) -> (String, String) {
|
||||
log::info!("[TASKMD_HTML] Building TASK.md view for task_id: {}", task_id);
|
||||
log::info!("Building TASK.md view for task_id: {}", task_id);
|
||||
|
||||
// First, try to get manifest from in-memory cache (for active/running tasks)
|
||||
if let Ok(manifests) = state.task_manifests.read() {
|
||||
if let Some(manifest) = manifests.get(task_id) {
|
||||
log::info!("[TASKMD_HTML] Found manifest in memory for task: {} with {} sections", manifest.app_name, manifest.sections.len());
|
||||
log::info!("Found manifest in memory for task: {} with {} sections", manifest.app_name, manifest.sections.len());
|
||||
let status_html = build_status_section_html(manifest, title, runtime);
|
||||
let progress_html = build_progress_log_html(manifest);
|
||||
return (status_html, progress_html);
|
||||
|
|
@ -76,25 +76,25 @@ pub fn build_taskmd_html(state: &Arc<AppState>, task_id: &str, title: &str, runt
|
|||
|
||||
// If not in memory, try to load from database (for completed/historical tasks)
|
||||
if let Some(manifest_json) = db_manifest {
|
||||
log::info!("[TASKMD_HTML] Found manifest in database for task: {}", task_id);
|
||||
log::info!("Found manifest in database for task: {}", task_id);
|
||||
if let Ok(manifest) = serde_json::from_value::<TaskManifest>(manifest_json.clone()) {
|
||||
log::info!("[TASKMD_HTML] Parsed DB manifest for task: {} with {} sections", manifest.app_name, manifest.sections.len());
|
||||
log::info!("Parsed DB manifest for task: {} with {} sections", manifest.app_name, manifest.sections.len());
|
||||
let status_html = build_status_section_html(&manifest, title, runtime);
|
||||
let progress_html = build_progress_log_html(&manifest);
|
||||
return (status_html, progress_html);
|
||||
} else {
|
||||
// Try parsing as web JSON format (the format we store)
|
||||
if let Some(web_manifest) = super::utils::parse_web_manifest_json(manifest_json) {
|
||||
log::info!("[TASKMD_HTML] Parsed web manifest from DB for task: {}", task_id);
|
||||
log::info!("Parsed web manifest from DB for task: {}", task_id);
|
||||
let status_html = build_status_section_from_web_json(&web_manifest, title, runtime);
|
||||
let progress_html = build_progress_log_from_web_json(&web_manifest);
|
||||
return (status_html, progress_html);
|
||||
}
|
||||
log::warn!("[TASKMD_HTML] Failed to parse manifest JSON for task: {}", task_id);
|
||||
log::warn!("Failed to parse manifest JSON for task: {}", task_id);
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("[TASKMD_HTML] No manifest found for task: {}", task_id);
|
||||
log::info!("No manifest found for task: {}", task_id);
|
||||
|
||||
let default_status = format!(r#"
|
||||
<div class="status-row">
|
||||
|
|
@ -326,10 +326,10 @@ fn build_progress_log_html(manifest: &TaskManifest) -> String {
|
|||
|
||||
let total_steps = manifest.total_steps;
|
||||
|
||||
log::info!("[PROGRESS_HTML] Building progress log, {} sections, total_steps={}", manifest.sections.len(), total_steps);
|
||||
log::info!("Building progress log, {} sections, total_steps={}", manifest.sections.len(), total_steps);
|
||||
|
||||
for section in &manifest.sections {
|
||||
log::info!("[PROGRESS_HTML] Section '{}': children={}, items={}, item_groups={}",
|
||||
log::info!("Section '{}': children={}, items={}, item_groups={}",
|
||||
section.name, section.children.len(), section.items.len(), section.item_groups.len());
|
||||
let section_class = match section.status {
|
||||
crate::auto_task::SectionStatus::Completed => "completed expanded",
|
||||
|
|
@ -362,7 +362,7 @@ fn build_progress_log_html(manifest: &TaskManifest) -> String {
|
|||
"#, section_class, section.id, section.name, global_current, total_steps, section_class, status_text, section_class));
|
||||
|
||||
for child in §ion.children {
|
||||
log::info!("[PROGRESS_HTML] Child '{}': items={}, item_groups={}",
|
||||
log::info!(" Child '{}': items={}, item_groups={}",
|
||||
child.name, child.items.len(), child.item_groups.len());
|
||||
let child_class = match child.status {
|
||||
crate::auto_task::SectionStatus::Completed => "completed expanded",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue