clean code
This commit is contained in:
@@ -4,6 +4,9 @@ use crate::openai_image_generation::GPT_IMAGE_2_MODEL;
|
||||
use module_editor_agent::agent::tool::Tool;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use axum::http::StatusCode;
|
||||
use serde_json::json;
|
||||
use crate::http_error::AppError;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct EditorToolContext {
|
||||
@@ -44,3 +47,10 @@ pub(crate) fn editor_agent_image_mud_points(
|
||||
};
|
||||
pricing.image_generation_mud_points(kind, Some(GPT_IMAGE_2_MODEL), Some(normalized_image_size))
|
||||
}
|
||||
|
||||
pub fn image_not_found(image_id: &ImageId) -> AppError {
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "editor-agent",
|
||||
"message": format!("asset {image_id} not found in context"),
|
||||
}))
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::editor_agent::editor_tools::common::{
|
||||
EditorAgentPricedTool, EditorToolContext, editor_agent_image_mud_points,
|
||||
editor_agent_image_mud_points, EditorAgentPricedTool, EditorToolContext,
|
||||
};
|
||||
use crate::editor_agent::utils::ImageId;
|
||||
use crate::editor_generation_config::EditorGenerationPricingConfig;
|
||||
use crate::editor_project::{
|
||||
EditorGenerationCaller, EditorImageEditRequest, edit_editor_image_for_owner,
|
||||
edit_editor_image_for_owner, EditorGenerationCaller, EditorImageEditRequest,
|
||||
};
|
||||
use crate::http_error::AppError;
|
||||
use crate::request_context::RequestContext;
|
||||
@@ -12,11 +12,12 @@ use crate::state::AppState;
|
||||
use axum::http::StatusCode;
|
||||
use module_editor_agent::agent::tool::{Tool, ToolFailure, ToolFailureKind};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
use serde_json::{json, Value};
|
||||
use shared_contracts::api::ApiSuccessEnvelope;
|
||||
use shared_contracts::assets::EditorCanvasGenerationCompletionPayload;
|
||||
use std::error::Error;
|
||||
use std::fmt::Display;
|
||||
use crate::editor_agent::editor_tools::common;
|
||||
|
||||
pub struct EditImageTool {
|
||||
pub context: EditorToolContext,
|
||||
@@ -178,7 +179,6 @@ impl EditImageTool {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO should share with format in run.rs
|
||||
pub fn format_execute_message(
|
||||
&self,
|
||||
args: <Self as Tool>::Args,
|
||||
@@ -199,83 +199,5 @@ impl EditImageTool {
|
||||
"[tool_call:{name}] args: {arg_json} output: edited result saved as image: {image_id}"
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn execute(
|
||||
&self,
|
||||
state: &AppState,
|
||||
request_context: &RequestContext,
|
||||
caller: EditorGenerationCaller,
|
||||
args: EditImageToolArgs,
|
||||
model: Option<String>,
|
||||
project_id: Option<String>,
|
||||
generation_inputs: Option<Value>,
|
||||
asset_label: Option<String>,
|
||||
source_resource_id: Option<String>,
|
||||
canvas_completion: Option<EditorCanvasGenerationCompletionPayload>,
|
||||
) -> Result<EditorImageEditResult, AppError> {
|
||||
let source_image_src = self
|
||||
.context
|
||||
.image_data_key(&args.object_image_id)
|
||||
.ok_or_else(|| image_not_found(&args.object_image_id))?
|
||||
.to_string();
|
||||
let reference_image_srcs: Result<Vec<String>, AppError> = args
|
||||
.reference_image_ids
|
||||
.into_iter()
|
||||
.map(|image_id| {
|
||||
self.context
|
||||
.image_data_key(&image_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.ok_or_else(|| image_not_found(&image_id))
|
||||
})
|
||||
.collect();
|
||||
let reference_image_srcs = reference_image_srcs?;
|
||||
|
||||
let result = edit_editor_image_for_owner(
|
||||
state,
|
||||
request_context,
|
||||
caller,
|
||||
EditorImageEditRequest {
|
||||
prompt: args.prompt,
|
||||
source_image_src,
|
||||
size: None,
|
||||
model,
|
||||
reference_image_srcs: Some(reference_image_srcs),
|
||||
project_id,
|
||||
asset_kind: Some("editor_agent_edit_image".to_string()),
|
||||
generation_inputs,
|
||||
asset_folder_id: Some("project".to_string()),
|
||||
asset_label,
|
||||
source_resource_id,
|
||||
target_layer_id: None,
|
||||
canvas_completion,
|
||||
},
|
||||
)
|
||||
.await?
|
||||
.0;
|
||||
|
||||
let data = if request_context.wants_envelope() {
|
||||
serde_json::from_value::<ApiSuccessEnvelope<Value>>(result)
|
||||
.map_err(|e| {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(json!({
|
||||
"message": format!("failed to parse success envelope: {e}"),
|
||||
}))
|
||||
})?
|
||||
.data
|
||||
} else {
|
||||
result
|
||||
};
|
||||
|
||||
serde_json::from_value::<EditorImageEditResult>(data).map_err(|e| {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(json!({
|
||||
"message": format!("failed to deserialize edit image result: {e}"),
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn image_not_found(image_id: &ImageId) -> AppError {
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "editor-agent",
|
||||
"message": format!("asset {image_id} not found in context"),
|
||||
}))
|
||||
}
|
||||
|
||||
+4
-51
@@ -1,23 +1,11 @@
|
||||
use crate::editor_agent::editor_tools::common::EditorAgentPricedTool;
|
||||
use crate::editor_agent::editor_tools::generate_sound_effect::{
|
||||
map_media_response_error, parse_media_response, pending_message,
|
||||
};
|
||||
use crate::editor_generation_config::{
|
||||
EDITOR_BACKGROUND_MUSIC_MODEL_SUNO, EditorGenerationPricingConfig,
|
||||
EditorGenerationPricingConfig, EDITOR_BACKGROUND_MUSIC_MODEL_SUNO,
|
||||
};
|
||||
use crate::http_error::AppError;
|
||||
use crate::request_context::RequestContext;
|
||||
use crate::state::AppState;
|
||||
use crate::vector_engine_audio_generation::generate_editor_background_music_for_owner;
|
||||
use axum::Json;
|
||||
use axum::http::StatusCode;
|
||||
use module_editor_agent::agent::tool::{Tool, ToolFailure};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
use shared_contracts::assets::{
|
||||
EditorAudioGenerateResponse, EditorBackgroundMusicGenerateRequest,
|
||||
EditorCanvasGenerationCompletionPayload,
|
||||
};
|
||||
use serde_json::{json, Value};
|
||||
use shared_contracts::assets::EditorAudioGenerateResponse;
|
||||
use std::error::Error;
|
||||
use std::fmt::Display;
|
||||
|
||||
@@ -74,7 +62,7 @@ impl Tool for GenerateBackgroundMusicTool {
|
||||
return Err(GenerateBackgroundMusicError::PromptNotProvided);
|
||||
}
|
||||
Ok(GenerateBackgroundMusicToolOutput {
|
||||
message: pending_message(),
|
||||
message: "this tool call is pending user confirmation. if all is pending, just end this turn".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -94,41 +82,6 @@ impl EditorAgentPricedTool for GenerateBackgroundMusicTool {
|
||||
}
|
||||
|
||||
impl GenerateBackgroundMusicTool {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn execute(
|
||||
&self,
|
||||
state: &AppState,
|
||||
request_context: &RequestContext,
|
||||
owner_user_id: String,
|
||||
args: GenerateBackgroundMusicToolArgs,
|
||||
project_id: String,
|
||||
generation_inputs: Option<Value>,
|
||||
asset_label: Option<String>,
|
||||
canvas_completion: EditorCanvasGenerationCompletionPayload,
|
||||
) -> Result<EditorAudioGenerateResponse, AppError> {
|
||||
if args.prompt.trim().is_empty() {
|
||||
return Err(AppError::from_status(StatusCode::BAD_REQUEST).with_details(
|
||||
json!({ "provider": "editor-agent", "message": "背景音乐提示词不能为空" }),
|
||||
));
|
||||
}
|
||||
let response = generate_editor_background_music_for_owner(
|
||||
state.clone(),
|
||||
request_context.clone(),
|
||||
owner_user_id,
|
||||
Ok(Json(EditorBackgroundMusicGenerateRequest {
|
||||
gpt_description_prompt: args.prompt,
|
||||
make_instrumental: args.make_instrumental,
|
||||
project_id: Some(project_id),
|
||||
canvas_completion: Some(canvas_completion),
|
||||
generation_inputs,
|
||||
asset_folder_id: Some("project".to_string()),
|
||||
asset_label,
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.map_err(map_media_response_error)?;
|
||||
parse_media_response(request_context, response.0)
|
||||
}
|
||||
pub fn format_execute_message(
|
||||
&self,
|
||||
args: &GenerateBackgroundMusicToolArgs,
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
use crate::editor_agent::editor_tools::common::{
|
||||
EditorAgentPricedTool, EditorToolContext, editor_agent_image_mud_points,
|
||||
editor_agent_image_mud_points, EditorAgentPricedTool, EditorToolContext,
|
||||
};
|
||||
use crate::editor_agent::editor_tools::generate_image::{
|
||||
EditorImageGenerationResult, GenerateImageError, GenerateImageTool, GenerateImageToolArgs,
|
||||
GenerateImageToolOutput,
|
||||
EditorImageGenerationResult, GenerateImageError, GenerateImageTool, GenerateImageToolArgs,
|
||||
GenerateImageToolOutput,
|
||||
};
|
||||
use crate::editor_generation_config::EditorGenerationPricingConfig;
|
||||
use crate::editor_project::EditorGenerationCaller;
|
||||
use crate::http_error::AppError;
|
||||
use crate::request_context::RequestContext;
|
||||
use crate::state::AppState;
|
||||
use module_editor_agent::agent::tool::{Tool, ToolFailure};
|
||||
use serde_json::{Value, json};
|
||||
use shared_contracts::assets::EditorCanvasGenerationCompletionPayload;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
pub struct GenerateCharacterTool {
|
||||
pub context: EditorToolContext,
|
||||
@@ -75,60 +70,23 @@ impl EditorAgentPricedTool for GenerateCharacterTool {
|
||||
}
|
||||
|
||||
impl GenerateCharacterTool {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn execute(
|
||||
&self,
|
||||
state: &AppState,
|
||||
request_context: &RequestContext,
|
||||
caller: EditorGenerationCaller,
|
||||
args: GenerateImageToolArgs,
|
||||
project_id: String,
|
||||
generation_inputs: Option<Value>,
|
||||
asset_label: Option<String>,
|
||||
canvas_completion: EditorCanvasGenerationCompletionPayload,
|
||||
) -> Result<EditorImageGenerationResult, AppError> {
|
||||
GenerateImageTool {
|
||||
context: self.context.clone(),
|
||||
}
|
||||
.execute_with_kind(
|
||||
state,
|
||||
request_context,
|
||||
caller,
|
||||
args,
|
||||
project_id,
|
||||
generation_inputs,
|
||||
asset_label,
|
||||
canvas_completion,
|
||||
Some("character"),
|
||||
"character",
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn format_execute_message(
|
||||
&self,
|
||||
args: &GenerateImageToolArgs,
|
||||
result: &EditorImageGenerationResult,
|
||||
) -> String {
|
||||
format_tool_message(Self::NAME, args, result)
|
||||
let tool_name = Self::NAME;
|
||||
let args = serde_json::to_string(args).unwrap_or_default();
|
||||
let data_key = result
|
||||
.object_key
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(|value| value.trim_start_matches('/').to_string())
|
||||
.unwrap_or_else(|| result.image_src.clone());
|
||||
let image_id = crate::editor_agent::utils::ImageId::from_data_key(data_key);
|
||||
format!(
|
||||
"[tool_call:{tool_name}] args: {args} output: generated result saved as image: {image_id}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn format_tool_message(
|
||||
tool_name: &str,
|
||||
args: &GenerateImageToolArgs,
|
||||
result: &EditorImageGenerationResult,
|
||||
) -> String {
|
||||
let args = serde_json::to_string(args).unwrap_or_default();
|
||||
let data_key = result
|
||||
.object_key
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(|value| value.trim_start_matches('/').to_string())
|
||||
.unwrap_or_else(|| result.image_src.clone());
|
||||
let image_id = crate::editor_agent::utils::ImageId::from_data_key(data_key);
|
||||
format!(
|
||||
"[tool_call:{tool_name}] args: {args} output: generated result saved as image: {image_id}"
|
||||
)
|
||||
}
|
||||
|
||||
+4
-81
@@ -1,21 +1,13 @@
|
||||
use crate::editor_agent::editor_tools::common::{
|
||||
EditorAgentPricedTool, EditorToolContext, editor_agent_image_mud_points,
|
||||
editor_agent_image_mud_points, EditorAgentPricedTool, EditorToolContext,
|
||||
};
|
||||
use crate::editor_agent::utils::ImageId;
|
||||
use crate::editor_generation_config::EditorGenerationPricingConfig;
|
||||
use crate::editor_project::{
|
||||
EditorGenerationCaller, EditorIconSpritesheetGenerationRequest,
|
||||
generate_editor_icon_spritesheet_for_owner,
|
||||
};
|
||||
use crate::http_error::AppError;
|
||||
use crate::request_context::RequestContext;
|
||||
use crate::state::AppState;
|
||||
use axum::http::StatusCode;
|
||||
use module_editor_agent::agent::tool::{Tool, ToolFailure, ToolFailureKind};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
use shared_contracts::api::ApiSuccessEnvelope;
|
||||
use shared_contracts::assets::EditorCanvasGenerationCompletionPayload;
|
||||
use serde_json::{json, Value};
|
||||
use std::error::Error;
|
||||
use std::fmt::Display;
|
||||
|
||||
@@ -160,79 +152,12 @@ impl GenerateIconSpritesheetTool {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn execute(
|
||||
&self,
|
||||
state: &AppState,
|
||||
request_context: &RequestContext,
|
||||
caller: EditorGenerationCaller,
|
||||
args: GenerateIconSpritesheetToolArgs,
|
||||
project_id: String,
|
||||
generation_inputs: Option<Value>,
|
||||
canvas_completion: EditorCanvasGenerationCompletionPayload,
|
||||
) -> Result<EditorIconSpritesheetResult, AppError> {
|
||||
self.validate_args(&args).map_err(|error| {
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "editor-agent",
|
||||
"message": error.to_string(),
|
||||
}))
|
||||
})?;
|
||||
let result = generate_editor_icon_spritesheet_for_owner(
|
||||
state,
|
||||
request_context,
|
||||
caller,
|
||||
EditorIconSpritesheetGenerationRequest {
|
||||
reference_image_src: self
|
||||
.context
|
||||
.image_data_key(&args.reference_image_id)
|
||||
.ok_or_else(|| image_not_found(&args.reference_image_id))?
|
||||
.to_string(),
|
||||
reference_image_srcs: Some(
|
||||
args.reference_image_ids
|
||||
.into_iter()
|
||||
.map(|image_id| {
|
||||
self.context
|
||||
.image_data_key(&image_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.ok_or_else(|| image_not_found(&image_id))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
),
|
||||
icon_descriptions: args.icon_descriptions,
|
||||
model: None,
|
||||
screen_color: Some("auto".to_string()),
|
||||
seg_model: Some("birefnet".to_string()),
|
||||
aspect_ratio: args.aspect_ratio,
|
||||
image_size: args.image_size,
|
||||
project_id: Some(project_id),
|
||||
generation_inputs,
|
||||
asset_folder_id: Some("project".to_string()),
|
||||
canvas_completion: Some(canvas_completion),
|
||||
},
|
||||
)
|
||||
.await?
|
||||
.0;
|
||||
let data = if request_context.wants_envelope() {
|
||||
serde_json::from_value::<ApiSuccessEnvelope<Value>>(result)
|
||||
.map_err(|error| {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(
|
||||
json!({ "message": format!("failed to parse success envelope: {error}") }),
|
||||
)
|
||||
})?
|
||||
.data
|
||||
} else {
|
||||
result
|
||||
};
|
||||
serde_json::from_value(data).map_err(|error| AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.with_details(json!({ "message": format!("failed to deserialize icon spritesheet result: {error}") })))
|
||||
}
|
||||
|
||||
pub fn format_execute_message(
|
||||
&self,
|
||||
args: &GenerateIconSpritesheetToolArgs,
|
||||
result: &EditorIconSpritesheetResult,
|
||||
) -> String {
|
||||
let tool_name = Self::NAME;
|
||||
let args = serde_json::to_string(args).unwrap_or_default();
|
||||
let data_key = result
|
||||
.spritesheet_resource
|
||||
@@ -245,9 +170,7 @@ impl GenerateIconSpritesheetTool {
|
||||
.unwrap_or_else(|| result.spritesheet_image_src.clone());
|
||||
let image_id = ImageId::from_data_key(data_key);
|
||||
format!(
|
||||
"[tool_call:{}] args: {args} output: generated spritesheet saved as image: {}",
|
||||
Self::NAME,
|
||||
image_id
|
||||
"[tool_call:{tool_name}] args: {args} output: generated spritesheet saved as image: {image_id}",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,119 +180,6 @@ impl GenerateImageTool {
|
||||
Self::NAME,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn execute(
|
||||
&self,
|
||||
state: &AppState,
|
||||
request_context: &RequestContext,
|
||||
caller: EditorGenerationCaller,
|
||||
args: GenerateImageToolArgs,
|
||||
project_id: String,
|
||||
generation_inputs: Option<Value>,
|
||||
asset_label: Option<String>,
|
||||
canvas_completion: EditorCanvasGenerationCompletionPayload,
|
||||
) -> Result<EditorImageGenerationResult, AppError> {
|
||||
self.execute_with_kind(
|
||||
state,
|
||||
request_context,
|
||||
caller,
|
||||
args,
|
||||
project_id,
|
||||
generation_inputs,
|
||||
asset_label,
|
||||
canvas_completion,
|
||||
None,
|
||||
"editor_agent_generated_image",
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) async fn execute_with_kind(
|
||||
&self,
|
||||
state: &AppState,
|
||||
request_context: &RequestContext,
|
||||
caller: EditorGenerationCaller,
|
||||
args: GenerateImageToolArgs,
|
||||
project_id: String,
|
||||
generation_inputs: Option<Value>,
|
||||
asset_label: Option<String>,
|
||||
canvas_completion: EditorCanvasGenerationCompletionPayload,
|
||||
kind: Option<&str>,
|
||||
asset_kind: &str,
|
||||
) -> Result<EditorImageGenerationResult, AppError> {
|
||||
self.validate_args(&args).map_err(|error| {
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "editor-agent",
|
||||
"message": error.to_string(),
|
||||
}))
|
||||
})?;
|
||||
|
||||
let result = generate_editor_image_for_owner(
|
||||
state,
|
||||
request_context,
|
||||
caller,
|
||||
EditorImageGenerationRequest {
|
||||
prompt: args.prompt,
|
||||
size: None,
|
||||
kind: kind.map(ToOwned::to_owned),
|
||||
model: None,
|
||||
screen_color: kind
|
||||
.is_some_and(|value| value == "character")
|
||||
.then(|| "auto".to_string()),
|
||||
seg_model: kind
|
||||
.is_some_and(|value| value == "character")
|
||||
.then(|| "birefnet".to_string()),
|
||||
aspect_ratio: args.aspect_ratio,
|
||||
image_size: args.image_size,
|
||||
reference_image_srcs: Some(self.image_data_keys(args.reference_image_ids)?),
|
||||
project_id: Some(project_id),
|
||||
asset_kind: Some(asset_kind.to_string()),
|
||||
generation_inputs,
|
||||
asset_folder_id: Some("project".to_string()),
|
||||
asset_label,
|
||||
source_resource_id: None,
|
||||
canvas_completion: Some(canvas_completion),
|
||||
},
|
||||
)
|
||||
.await?
|
||||
.0;
|
||||
|
||||
let data = if request_context.wants_envelope() {
|
||||
serde_json::from_value::<ApiSuccessEnvelope<Value>>(result)
|
||||
.map_err(|error| {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(json!({
|
||||
"message": format!("failed to parse success envelope: {error}"),
|
||||
}))
|
||||
})?
|
||||
.data
|
||||
} else {
|
||||
result
|
||||
};
|
||||
|
||||
serde_json::from_value::<EditorImageGenerationResult>(data).map_err(|error| {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(json!({
|
||||
"message": format!("failed to deserialize image generation result: {error}"),
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
fn image_data_keys(&self, image_ids: Vec<ImageId>) -> Result<Vec<String>, AppError> {
|
||||
image_ids
|
||||
.into_iter()
|
||||
.map(|image_id| {
|
||||
self.context
|
||||
.image_data_key(&image_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.ok_or_else(|| {
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "editor-agent",
|
||||
"message": format!("asset {image_id} not found in context"),
|
||||
}))
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,22 +1,11 @@
|
||||
use crate::editor_agent::editor_tools::common::EditorAgentPricedTool;
|
||||
use crate::editor_generation_config::{
|
||||
EDITOR_SOUND_EFFECT_MODEL_VIDU, EditorGenerationPricingConfig,
|
||||
EditorGenerationPricingConfig, EDITOR_SOUND_EFFECT_MODEL_VIDU,
|
||||
};
|
||||
use crate::http_error::AppError;
|
||||
use crate::request_context::RequestContext;
|
||||
use crate::state::AppState;
|
||||
use crate::vector_engine_audio_generation::generate_editor_sound_effect_for_owner;
|
||||
use axum::Json;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::Response;
|
||||
use module_editor_agent::agent::tool::{Tool, ToolFailure};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
use shared_contracts::api::ApiSuccessEnvelope;
|
||||
use shared_contracts::assets::{
|
||||
EditorAudioGenerateResponse, EditorCanvasGenerationCompletionPayload,
|
||||
EditorSoundEffectGenerateRequest,
|
||||
};
|
||||
use serde_json::{json, Value};
|
||||
use shared_contracts::assets::EditorAudioGenerateResponse;
|
||||
use std::error::Error;
|
||||
use std::fmt::Display;
|
||||
|
||||
@@ -81,7 +70,7 @@ impl Tool for GenerateSoundEffectTool {
|
||||
return Err(GenerateSoundEffectError::PromptNotProvided);
|
||||
}
|
||||
Ok(GenerateSoundEffectToolOutput {
|
||||
message: pending_message(),
|
||||
message: "this tool call is pending user confirmation. if all is pending, just end this turn".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -108,43 +97,6 @@ impl EditorAgentPricedTool for GenerateSoundEffectTool {
|
||||
}
|
||||
|
||||
impl GenerateSoundEffectTool {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn execute(
|
||||
&self,
|
||||
state: &AppState,
|
||||
request_context: &RequestContext,
|
||||
owner_user_id: String,
|
||||
args: GenerateSoundEffectToolArgs,
|
||||
project_id: String,
|
||||
generation_inputs: Option<Value>,
|
||||
asset_label: Option<String>,
|
||||
canvas_completion: EditorCanvasGenerationCompletionPayload,
|
||||
) -> Result<EditorAudioGenerateResponse, AppError> {
|
||||
if args.prompt.trim().is_empty() {
|
||||
return Err(AppError::from_status(StatusCode::BAD_REQUEST).with_details(
|
||||
json!({ "provider": "editor-agent", "message": "音效提示词不能为空" }),
|
||||
));
|
||||
}
|
||||
let response = generate_editor_sound_effect_for_owner(
|
||||
state.clone(),
|
||||
request_context.clone(),
|
||||
owner_user_id,
|
||||
Ok(Json(EditorSoundEffectGenerateRequest {
|
||||
prompt: args.prompt,
|
||||
model: args.model,
|
||||
duration: args.duration.unwrap_or(3),
|
||||
project_id: Some(project_id),
|
||||
canvas_completion: Some(canvas_completion),
|
||||
generation_inputs,
|
||||
asset_folder_id: Some("project".to_string()),
|
||||
asset_label,
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.map_err(map_media_response_error)?;
|
||||
parse_media_response(request_context, response.0)
|
||||
}
|
||||
|
||||
pub fn format_execute_message(
|
||||
&self,
|
||||
args: &GenerateSoundEffectToolArgs,
|
||||
@@ -158,36 +110,3 @@ impl GenerateSoundEffectTool {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn pending_message() -> String {
|
||||
"this tool call is pending user confirmation. if all is pending, just end this turn".to_string()
|
||||
}
|
||||
|
||||
pub(crate) fn map_media_response_error(response: Response) -> AppError {
|
||||
AppError::from_status(response.status()).with_details(json!({
|
||||
"provider": "editor-agent",
|
||||
"message": "媒体生成请求失败",
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn parse_media_response<T: for<'de> Deserialize<'de>>(
|
||||
request_context: &RequestContext,
|
||||
value: Value,
|
||||
) -> Result<T, AppError> {
|
||||
let data = if request_context.wants_envelope() {
|
||||
serde_json::from_value::<ApiSuccessEnvelope<Value>>(value)
|
||||
.map_err(|error| {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(
|
||||
json!({ "message": format!("failed to parse success envelope: {error}") }),
|
||||
)
|
||||
})?
|
||||
.data
|
||||
} else {
|
||||
value
|
||||
};
|
||||
serde_json::from_value(data).map_err(|error| {
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_details(
|
||||
json!({ "message": format!("failed to deserialize media generation result: {error}") }),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
use crate::editor_agent::editor_tools::common::{
|
||||
EditorAgentPricedTool, EditorToolContext, editor_agent_image_mud_points,
|
||||
};
|
||||
use crate::editor_agent::editor_tools::generate_character::format_tool_message;
|
||||
use crate::editor_agent::editor_tools::generate_image::{
|
||||
EditorImageGenerationResult, GenerateImageError, GenerateImageTool, GenerateImageToolArgs,
|
||||
GenerateImageToolOutput,
|
||||
};
|
||||
use crate::editor_generation_config::EditorGenerationPricingConfig;
|
||||
use crate::editor_project::EditorGenerationCaller;
|
||||
use crate::http_error::AppError;
|
||||
use crate::request_context::RequestContext;
|
||||
use crate::state::AppState;
|
||||
use module_editor_agent::agent::tool::{Tool, ToolFailure};
|
||||
use serde_json::{Value, json};
|
||||
use shared_contracts::assets::EditorCanvasGenerationCompletionPayload;
|
||||
|
||||
pub struct GenerateUiDesignTool {
|
||||
pub context: EditorToolContext,
|
||||
@@ -75,41 +69,23 @@ impl EditorAgentPricedTool for GenerateUiDesignTool {
|
||||
}
|
||||
|
||||
impl GenerateUiDesignTool {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn execute(
|
||||
&self,
|
||||
state: &AppState,
|
||||
request_context: &RequestContext,
|
||||
caller: EditorGenerationCaller,
|
||||
args: GenerateImageToolArgs,
|
||||
project_id: String,
|
||||
generation_inputs: Option<Value>,
|
||||
asset_label: Option<String>,
|
||||
canvas_completion: EditorCanvasGenerationCompletionPayload,
|
||||
) -> Result<EditorImageGenerationResult, AppError> {
|
||||
GenerateImageTool {
|
||||
context: self.context.clone(),
|
||||
}
|
||||
.execute_with_kind(
|
||||
state,
|
||||
request_context,
|
||||
caller,
|
||||
args,
|
||||
project_id,
|
||||
generation_inputs,
|
||||
asset_label,
|
||||
canvas_completion,
|
||||
Some("ui-design"),
|
||||
"ui-design",
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn format_execute_message(
|
||||
&self,
|
||||
args: &GenerateImageToolArgs,
|
||||
result: &EditorImageGenerationResult,
|
||||
) -> String {
|
||||
format_tool_message(Self::NAME, args, result)
|
||||
let tool_name = Self::NAME;
|
||||
let args = serde_json::to_string(args).unwrap_or_default();
|
||||
let data_key = result
|
||||
.object_key
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(|value| value.trim_start_matches('/').to_string())
|
||||
.unwrap_or_else(|| result.image_src.clone());
|
||||
let image_id = crate::editor_agent::utils::ImageId::from_data_key(data_key);
|
||||
format!(
|
||||
"[tool_call:{tool_name}] args: {args} output: generated result saved as image: {image_id}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
use crate::character_animation_assets::generate_editor_video_for_owner;
|
||||
use crate::editor_agent::editor_tools::common::EditorAgentPricedTool;
|
||||
use crate::editor_agent::editor_tools::generate_sound_effect::{
|
||||
map_media_response_error, parse_media_response, pending_message,
|
||||
};
|
||||
use crate::editor_agent::utils::ImageId;
|
||||
use crate::editor_generation_config::EditorGenerationPricingConfig;
|
||||
use crate::http_error::AppError;
|
||||
use crate::request_context::RequestContext;
|
||||
use crate::state::AppState;
|
||||
use axum::Json;
|
||||
use axum::http::StatusCode;
|
||||
use module_editor_agent::agent::tool::{Tool, ToolFailure, ToolFailureKind};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
use shared_contracts::assets::{
|
||||
EditorCanvasGenerationCompletionPayload, EditorVideoGenerateRequest,
|
||||
EditorVideoGenerateResponse,
|
||||
};
|
||||
use serde_json::{json, Value};
|
||||
use shared_contracts::assets::EditorVideoGenerateResponse;
|
||||
use std::error::Error;
|
||||
use std::fmt::Display;
|
||||
|
||||
@@ -99,7 +87,7 @@ impl Tool for GenerateVideoTool {
|
||||
}
|
||||
}
|
||||
Ok(GenerateVideoToolOutput {
|
||||
message: pending_message(),
|
||||
message: "this tool call is pending user confirmation. if all is pending, just end this turn".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -139,63 +127,6 @@ impl EditorAgentPricedTool for GenerateVideoTool {
|
||||
}
|
||||
|
||||
impl GenerateVideoTool {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn execute(
|
||||
&self,
|
||||
state: &AppState,
|
||||
request_context: &RequestContext,
|
||||
owner_user_id: String,
|
||||
args: GenerateVideoToolArgs,
|
||||
project_id: String,
|
||||
generation_inputs: Option<Value>,
|
||||
asset_label: Option<String>,
|
||||
canvas_completion: EditorCanvasGenerationCompletionPayload,
|
||||
) -> Result<EditorVideoGenerateResponse, AppError> {
|
||||
if args.prompt.trim().is_empty() {
|
||||
return Err(AppError::from_status(StatusCode::BAD_REQUEST).with_details(
|
||||
json!({ "provider": "editor-agent", "message": "视频提示词不能为空" }),
|
||||
));
|
||||
}
|
||||
let reference_image_srcs: Result<Vec<String>, AppError> = args
|
||||
.reference_image_ids
|
||||
.into_iter()
|
||||
.map(|image_id| {
|
||||
self.context
|
||||
.image_data_key(&image_id)
|
||||
.map(ToOwned::to_owned)
|
||||
.ok_or_else(|| image_not_found(&image_id))
|
||||
})
|
||||
.collect();
|
||||
let reference_image_srcs = reference_image_srcs?;
|
||||
let response = generate_editor_video_for_owner(
|
||||
state.clone(),
|
||||
request_context.clone(),
|
||||
owner_user_id,
|
||||
Ok(Json(EditorVideoGenerateRequest {
|
||||
prompt: args.prompt,
|
||||
model: args.model.unwrap_or_else(|| "seedance2.0-fast".to_string()),
|
||||
aspect_ratio: args.aspect_ratio.unwrap_or_else(|| "16:9".to_string()),
|
||||
duration_seconds: args.duration_seconds.unwrap_or(4),
|
||||
resolution: args.resolution.unwrap_or_else(|| "720p".to_string()),
|
||||
mode: "std".to_string(),
|
||||
sound: args.sound.unwrap_or_else(|| "off".to_string()),
|
||||
web_search_enabled: false,
|
||||
reference_image_srcs,
|
||||
reference_video_srcs: Vec::new(),
|
||||
reference_audio_srcs: Vec::new(),
|
||||
project_id: Some(project_id),
|
||||
canvas_completion: Some(canvas_completion),
|
||||
generation_inputs,
|
||||
source_resource_id: None,
|
||||
asset_kind: Some("video".to_string()),
|
||||
asset_folder_id: Some("project".to_string()),
|
||||
asset_label,
|
||||
})),
|
||||
)
|
||||
.await
|
||||
.map_err(map_media_response_error)?;
|
||||
parse_media_response(request_context, response.0)
|
||||
}
|
||||
pub fn format_execute_message(
|
||||
&self,
|
||||
args: &GenerateVideoToolArgs,
|
||||
@@ -209,11 +140,4 @@ impl GenerateVideoTool {
|
||||
video_id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn image_not_found(image_id: &ImageId) -> AppError {
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": "editor-agent",
|
||||
"message": format!("asset {image_id} not found in context"),
|
||||
}))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user