fix: 修复ComfyUI队列API响应格式解析问题

- 修复WebSocket错误消息解析失败问题,支持更多错误字段格式
- 改进执行错误处理,收到错误时立即中断而不是等待超时
- 修复ComfyUI队列API响应格式不匹配问题,支持数组格式的队列数据
- 添加灵活的队列项解析逻辑,支持整数和字符串类型的prompt_id
- 在EventEmitter中添加错误存储功能,支持实时错误状态检查
- 更新所有相关的队列状态转换代码

解决的问题:
1. WebSocket消息解析错误:missing field 'message'
2. 执行错误后仍等待超时的问题
3. 队列API响应解析错误:invalid type integer/string expected
4. 批量处理中错误统计不准确的问题
This commit is contained in:
imeepos
2025-08-11 11:09:27 +08:00
parent b1e7191c10
commit 2075a731d2
9 changed files with 224 additions and 17 deletions

View File

@@ -178,6 +178,9 @@ impl ComfyUIClient {
prompt_id: &str,
timeout: Option<Duration>,
) -> Result<HashMap<String, serde_json::Value>> {
// Clear any previous errors before starting
self.ws_client.clear_last_error().await;
let timeout_duration = timeout.unwrap_or(Duration::from_secs(300)); // 5 minutes default
let start_time = Instant::now();
let check_interval = Duration::from_millis(1000); // Check every second
@@ -190,6 +193,13 @@ impl ComfyUIClient {
)));
}
// Check for WebSocket execution errors first
if let Some(error) = self.ws_client.get_last_error().await {
return Err(ComfyUIError::execution(format!(
"Execution failed: {}", error.message
)));
}
// Check history for completion
match self.http_client.get_history_by_prompt(prompt_id).await {
Ok(history) => {

View File

@@ -167,8 +167,15 @@ impl WebSocketClient {
/// Handles incoming WebSocket messages
async fn handle_message(text: &str, event_emitter: &EventEmitter) -> Result<()> {
// 打印原始消息用于调试
log::debug!("Received WebSocket message: {}", text);
let message: WSMessage = serde_json::from_str(text)
.map_err(|e| ComfyUIError::new(format!("Failed to parse WebSocket message: {e}")))?;
.map_err(|e| {
log::error!("Failed to parse WebSocket message: {}", e);
log::error!("Raw message content: {}", text);
ComfyUIError::new(format!("Failed to parse WebSocket message: {e}"))
})?;
match message {
WSMessage::Progress { data } => {
@@ -195,12 +202,40 @@ impl WebSocketClient {
event_emitter.emit_executed(result).await;
}
WSMessage::ExecutionError { data } => {
// 构建错误消息,尝试多个可能的字段
let error_message = data.message
.or(data.error)
.or(data.exception_message)
.or_else(|| {
// 尝试从extra字段中提取错误信息
data.extra.get("exception_message")
.and_then(|v| v.as_str())
.map(|s| s.to_string())
})
.or_else(|| {
data.extra.get("error")
.and_then(|v| v.as_str())
.map(|s| s.to_string())
})
.unwrap_or_else(|| "Unknown execution error".to_string());
// 获取节点ID
let node_id = data.node_id
.or(data.node)
.or_else(|| {
data.extra.get("node")
.and_then(|v| v.as_str())
.map(|s| s.to_string())
});
let error = ExecutionError {
node_id: data.node_id,
message: data.message,
node_id,
message: error_message,
details: data.details,
timestamp: Utc::now(),
};
log::error!("ComfyUI execution error: {}", error.message);
event_emitter.emit_error(error).await;
}
WSMessage::Unknown => {
@@ -215,6 +250,16 @@ impl WebSocketClient {
pub fn event_emitter(&self) -> &EventEmitter {
&self.event_emitter
}
/// Gets the last execution error and clears it
pub async fn get_last_error(&self) -> Option<ExecutionError> {
self.event_emitter.get_last_error().await
}
/// Clears the last error
pub async fn clear_last_error(&self) {
self.event_emitter.clear_last_error().await
}
}
impl Drop for WebSocketClient {

View File

@@ -6,18 +6,59 @@ use std::collections::HashMap;
/// Queue status response
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QueueStatus {
pub queue_running: Vec<QueueItem>,
pub queue_pending: Vec<QueueItem>,
pub queue_running: Vec<serde_json::Value>,
pub queue_pending: Vec<serde_json::Value>,
}
/// Queue item
#[derive(Debug, Clone, Serialize, Deserialize)]
/// Queue item (parsed from array format)
#[derive(Debug, Clone)]
pub struct QueueItem {
pub prompt_id: String,
pub number: u32,
pub outputs: HashMap<String, serde_json::Value>,
}
impl QueueStatus {
/// Parse running queue items from raw JSON values
pub fn get_running_items(&self) -> Vec<QueueItem> {
self.queue_running.iter()
.filter_map(|item| Self::parse_queue_item(item))
.collect()
}
/// Parse pending queue items from raw JSON values
pub fn get_pending_items(&self) -> Vec<QueueItem> {
self.queue_pending.iter()
.filter_map(|item| Self::parse_queue_item(item))
.collect()
}
/// Parse a single queue item from JSON value
fn parse_queue_item(value: &serde_json::Value) -> Option<QueueItem> {
if let serde_json::Value::Array(arr) = value {
if arr.len() >= 2 {
let number = match &arr[0] {
serde_json::Value::Number(n) => n.as_u64()? as u32,
_ => return None,
};
let prompt_id = match &arr[1] {
serde_json::Value::String(s) => s.clone(),
serde_json::Value::Number(n) => n.to_string(),
_ => return None,
};
return Some(QueueItem {
prompt_id,
number,
outputs: HashMap::new(), // ComfyUI queue format doesn't include outputs
});
}
}
None
}
}
/// Prompt request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptRequest {
@@ -137,3 +178,59 @@ pub struct ViewMetadata {
#[serde(flatten)]
pub metadata: HashMap<String, serde_json::Value>,
}
/// Custom deserializer that can handle both string and number types
fn deserialize_string_or_number<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{self, Visitor};
use std::fmt;
struct StringOrNumberVisitor;
impl<'de> Visitor<'de> for StringOrNumberVisitor {
type Value = String;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string or number")
}
fn visit_str<E>(self, value: &str) -> Result<String, E>
where
E: de::Error,
{
Ok(value.to_string())
}
fn visit_string<E>(self, value: String) -> Result<String, E>
where
E: de::Error,
{
Ok(value)
}
fn visit_i64<E>(self, value: i64) -> Result<String, E>
where
E: de::Error,
{
Ok(value.to_string())
}
fn visit_u64<E>(self, value: u64) -> Result<String, E>
where
E: de::Error,
{
Ok(value.to_string())
}
fn visit_f64<E>(self, value: f64) -> Result<String, E>
where
E: de::Error,
{
Ok(value.to_string())
}
}
deserializer.deserialize_any(StringOrNumberVisitor)
}

View File

@@ -97,8 +97,17 @@ pub struct WSExecutedData {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WSErrorData {
pub node_id: Option<String>,
pub message: String,
pub node: Option<String>,
pub message: Option<String>,
pub details: Option<serde_json::Value>,
pub error: Option<String>,
pub exception_message: Option<String>,
pub exception_type: Option<String>,
pub traceback: Option<Vec<String>>,
pub prompt_id: Option<String>,
// 使用 flatten 来捕获任何其他字段
#[serde(flatten)]
pub extra: HashMap<String, serde_json::Value>,
}
/// Execution options

View File

@@ -9,6 +9,7 @@ use crate::types::{ExecutionProgress, ExecutionResult, ExecutionError, Execution
#[derive(Clone)]
pub struct EventEmitter {
callbacks: Arc<RwLock<HashMap<String, Arc<dyn ExecutionCallbacks>>>>,
last_error: Arc<RwLock<Option<ExecutionError>>>,
}
impl EventEmitter {
@@ -16,6 +17,7 @@ impl EventEmitter {
pub fn new() -> Self {
Self {
callbacks: Arc::new(RwLock::new(HashMap::new())),
last_error: Arc::new(RwLock::new(None)),
}
}
@@ -57,6 +59,12 @@ impl EventEmitter {
/// Emits an error event
pub async fn emit_error(&self, error: ExecutionError) {
// Store the error for later retrieval
{
let mut last_error_guard = self.last_error.write().await;
*last_error_guard = Some(error.clone());
}
let callbacks_map = self.callbacks.read().await;
for callback in callbacks_map.values() {
callback.on_error(error.clone());
@@ -74,6 +82,18 @@ impl EventEmitter {
let mut callbacks_map = self.callbacks.write().await;
callbacks_map.clear();
}
/// Gets the last execution error and clears it
pub async fn get_last_error(&self) -> Option<ExecutionError> {
let mut error_guard = self.last_error.write().await;
error_guard.take()
}
/// Clears the last error
pub async fn clear_last_error(&self) {
let mut error_guard = self.last_error.write().await;
*error_guard = None;
}
}
impl Default for EventEmitter {