diff --git a/api/libs/handlers/src/chats/post_chat_handler.rs b/api/libs/handlers/src/chats/post_chat_handler.rs index efee3712f..e334f1ee3 100644 --- a/api/libs/handlers/src/chats/post_chat_handler.rs +++ b/api/libs/handlers/src/chats/post_chat_handler.rs @@ -2236,6 +2236,15 @@ fn transform_assistant_tool_message( parser.process_dashboard_chunk(tool_id.clone(), &tool_call.function.arguments) }; + // +++ Add Debug Logging +++ + tracing::debug!( + tool_name = %tool_name, + arguments = %tool_call.function.arguments, + parse_result = ?parse_result, + "Processing InProgress chunk for file tool" + ); + // +++ End Debug Logging +++ + // If parser returns a reasoning message (File type expected) if let Ok(Some(BusterReasoningMessage::File(mut file_reasoning))) = parse_result { // Added missing variable initializations @@ -2255,7 +2264,15 @@ fn transform_assistant_tool_message( // Update file detail with delta file_detail.file.text_chunk = Some(delta); file_detail.file.text = None; // Ensure full text is cleared when chunking - file_detail.status = "In Progress".to_string(); // Set status to in progress + // Determine the correct status based on the tool name + let status_text = if tool_name.starts_with("create_") { + "Creating".to_string() + } else if tool_name.starts_with("update_") { + "Modifying".to_string() + } else { + "In Progress".to_string() // Fallback + }; + file_detail.status = status_text; // Set status dynamically has_updates = true; updated_files_map.insert(file_map_id.clone(), file_detail.clone()); // Clone file_detail } else { @@ -2270,6 +2287,15 @@ fn transform_assistant_tool_message( // Update only the files that had changes if has_updates { + // Update the title based on the tool type + let title_text = if tool_name.starts_with("create_") { + format!("Creating {} files...", file_type) + } else if tool_name.starts_with("update_") { + format!("Modifying {} files...", file_type) + } else { + format!("Processing {} files...", file_type) // Fallback + }; + file_reasoning.title = title_text; // Set title dynamically file_reasoning.files = updated_files_map; // Replace with updated files all_results.push(ToolTransformResult::Reasoning( BusterReasoningMessage::File(file_reasoning), diff --git a/api/libs/handlers/src/chats/streaming_parser.rs b/api/libs/handlers/src/chats/streaming_parser.rs index c68583f6b..7966638bf 100644 --- a/api/libs/handlers/src/chats/streaming_parser.rs +++ b/api/libs/handlers/src/chats/streaming_parser.rs @@ -235,27 +235,40 @@ impl StreamingParser { if let Some(files) = value.get("files").and_then(Value::as_array) { let mut files_map = std::collections::HashMap::new(); let mut file_ids = Vec::new(); + let mut is_update_operation = false; // Flag to track if we found an 'id' for file in files { if let Some(file_obj) = file.as_object() { - let has_name = file_obj.get("name").and_then(Value::as_str).is_some(); - let has_yml_content = file_obj.get("yml_content").is_some(); + let yml_content_opt = file_obj.get("yml_content").and_then(Value::as_str); + let id_opt = file_obj.get("id").and_then(Value::as_str); + let name_opt = file_obj.get("name").and_then(Value::as_str); - if has_name && has_yml_content { - let name = file_obj.get("name").and_then(Value::as_str).unwrap_or(""); - let yml_content = file_obj - .get("yml_content") - .and_then(Value::as_str) - .unwrap_or(""); + if let Some(yml_content) = yml_content_opt { + let file_id_str: String; + let file_name_str: String; - // Generate deterministic UUID based on tool call ID, file name, and type - let file_id = generate_deterministic_uuid(&id, name, &file_type)?; + if let Some(provided_id_str) = id_opt { + // --- Update Operation --- + is_update_operation = true; // Mark as update + file_id_str = provided_id_str.to_string(); + // Name is not available during streaming for updates, use ID as placeholder + file_name_str = format!("{}...", provided_id_str.chars().take(8).collect::()); + } else if let Some(provided_name) = name_opt { + // --- Create Operation --- + // Generate deterministic UUID based on tool call ID, file name, and type + let generated_id = generate_deterministic_uuid(&id, provided_name, &file_type)?; + file_id_str = generated_id.to_string(); + file_name_str = provided_name.to_string(); + } else { + // Neither id nor name found, skip this file object + continue; + } let buster_file = BusterFile { - id: file_id.to_string(), + id: file_id_str.clone(), file_type: file_type.clone(), - file_name: name.to_string(), - version_number: 1, + file_name: file_name_str, // Use determined name (actual or placeholder) + version_number: 1, // Initial version for streaming, final message will have correct one status: "loading".to_string(), file: BusterFileContent { text: None, @@ -265,17 +278,24 @@ impl StreamingParser { metadata: None, }; - file_ids.push(file_id.to_string()); - files_map.insert(file_id.to_string(), buster_file); + file_ids.push(file_id_str.clone()); + files_map.insert(file_id_str, buster_file); } } } if !files_map.is_empty() { + // Determine title based on whether it was a create or update operation + let title = if is_update_operation { + format!("Modifying {} files...", file_type) + } else { + format!("Creating {} files...", file_type) + }; + return Ok(Some(BusterReasoningMessage::File(BusterReasoningFile { - id, + id, // Use the overall tool call ID message_type: "files".to_string(), - title: format!("Creating {} files...", file_type), + title, // Use dynamic title secondary_title: String::new(), status: "loading".to_string(), file_ids,