forked from boos/BoosAPI
feat: add comfyui agent attachments
This commit is contained in:
+103
-11
@@ -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!(
|
||||
|
||||
Reference in New Issue
Block a user