feat: add comfyui agent attachments

This commit is contained in:
boos
2026-05-22 01:16:37 +08:00
parent 7c6364a160
commit 8de175a8c5
2 changed files with 426 additions and 72 deletions
+103 -11
View File
@@ -76,6 +76,17 @@ struct AgentChatRequest {
struct AgentMessage {
role: String,
content: String,
#[serde(default)]
attachments: Vec<AgentAttachment>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct AgentAttachment {
name: String,
mime_type: Option<String>,
text: Option<String>,
data_url: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -285,6 +296,15 @@ async fn status_handler(
) -> Result<Json<Value>, (axum::http::StatusCode, Json<Value>)> {
let auth = resolve_agent_auth(&headers, &state).await?;
let (mode, upstream_base, _, key_name) = upstream_config(&state, &auth);
let upstream_label = match mode {
AgentMode::Local => "BoosAPI 本地路由".to_string(),
AgentMode::FallbackRemote => {
let parsed = reqwest::Url::parse(&upstream_base).ok();
parsed
.and_then(|url| url.host_str().map(ToOwned::to_owned))
.unwrap_or_else(|| "外部上游".to_string())
}
};
Ok(Json(json!({
"success": true,
"model": state.config.comfyui.agent_model,
@@ -292,7 +312,7 @@ async fn status_handler(
"keyName": key_name,
"userId": auth.user_id,
"role": auth.role,
"upstreamBase": upstream_base,
"upstreamLabel": upstream_label,
"comfyui": state.comfyui.status_response().await,
})))
}
@@ -558,6 +578,83 @@ fn send_sse(tx: &UnboundedSender<Result<Event, Infallible>>, payload: Value) ->
.is_ok()
}
fn attachment_summary(attachment: &AgentAttachment) -> String {
let mime = attachment
.mime_type
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
.unwrap_or("application/octet-stream");
let name = attachment.name.trim();
let name = if name.is_empty() { "untitled" } else { name };
if let Some(text) = attachment
.text
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
{
format!("\n\n[附件: {name}, 类型: {mime}]\n{text}")
} else {
format!("\n\n[附件: {name}, 类型: {mime}]")
}
}
fn agent_message_to_upstream(message: AgentMessage) -> Value {
let role = message.role;
let mut text = message.content;
let mut content_parts = Vec::new();
for attachment in &message.attachments {
let mime = attachment
.mime_type
.as_deref()
.unwrap_or_default()
.to_ascii_lowercase();
if mime.starts_with("image/") {
if let Some(data_url) = attachment
.data_url
.as_deref()
.map(str::trim)
.filter(|value| value.starts_with("data:image/"))
{
if !content_parts.iter().any(|part: &Value| part["type"] == "text") {
content_parts.push(json!({
"type": "text",
"text": text,
}));
text = String::new();
}
content_parts.push(json!({
"type": "image_url",
"image_url": { "url": data_url },
}));
continue;
}
}
text.push_str(&attachment_summary(attachment));
}
if !content_parts.is_empty() {
if !text.trim().is_empty() {
content_parts.push(json!({
"type": "text",
"text": text,
}));
}
json!({
"role": role,
"content": content_parts,
})
} else {
json!({
"role": role,
"content": text,
})
}
}
async fn chat_handler(
State(state): State<AppState>,
headers: HeaderMap,
@@ -567,10 +664,10 @@ async fn chat_handler(
let auth = resolve_agent_auth(&headers, &state).await?;
if body.messages.is_empty()
|| body
.messages
.iter()
.any(|message| message.role.trim().is_empty() || message.content.trim().is_empty())
|| body.messages.iter().any(|message| {
message.role.trim().is_empty()
|| (message.content.trim().is_empty() && message.attachments.is_empty())
})
{
return Err((
axum::http::StatusCode::BAD_REQUEST,
@@ -588,12 +685,7 @@ async fn chat_handler(
let messages: Vec<Value> = body
.messages
.into_iter()
.map(|message| {
json!({
"role": message.role,
"content": message.content,
})
})
.map(agent_message_to_upstream)
.collect();
let (mode, api_base, api_key, key_name) = upstream_config(&state, &auth);
info!(