feat: more completion for M4

This commit is contained in:
2026-04-13 21:20:41 +02:00
parent eca5fafc1c
commit a96a6ee31c
15 changed files with 527 additions and 46 deletions

View File

@@ -302,7 +302,7 @@ pub fn run_one_shot(
let settings = load_ai_settings(conn, offline_mode)?;
let endpoint = active_endpoint(conn, offline_mode)?;
let model = select_model(&settings, &endpoint, &request.operation)?;
let prompt = build_one_shot_prompt(request)?;
let user_content = build_one_shot_user_content(request)?;
let schema = response_schema(&request.operation);
let payload = json!({
"model": model,
@@ -313,7 +313,7 @@ pub fn run_one_shot(
},
{
"role": "user",
"content": prompt,
"content": user_content,
}
],
"response_format": {
@@ -412,37 +412,63 @@ fn build_system_prompt(base_prompt: &str, operation: &OneShotOperation) -> Strin
}
}
fn build_one_shot_prompt(request: &OneShotRequest) -> EngineResult<String> {
fn build_one_shot_user_content(request: &OneShotRequest) -> EngineResult<Value> {
match &request.operation {
OneShotOperation::AnalyzeTaxonomy => Ok(format!(
"Suggest tags and categories for this post: {}",
serde_json::to_string(&request.content)?
)),
).into()),
OneShotOperation::AnalyzePost => Ok(format!(
"Analyze this post and suggest title, excerpt, and slug: {}",
serde_json::to_string(&request.content)?
)),
).into()),
OneShotOperation::DetectLanguage => Ok(format!(
"Detect the language of this text: {}",
serde_json::to_string(&request.content)?
)),
).into()),
OneShotOperation::TranslatePost { target_language } => Ok(format!(
"Translate this post to {}: {}",
target_language,
serde_json::to_string(&request.content)?
)),
OneShotOperation::AnalyzeImage => Ok(format!(
"Analyze this image metadata and return title, alt, and caption suggestions: {}",
serde_json::to_string(&request.content)?
)),
).into()),
OneShotOperation::AnalyzeImage => build_image_analysis_user_content(&request.content),
OneShotOperation::TranslateMedia { target_language } => Ok(format!(
"Translate this media metadata to {}: {}",
target_language,
serde_json::to_string(&request.content)?
)),
).into()),
}
}
fn build_image_analysis_user_content(content: &Value) -> EngineResult<Value> {
let image_data_url = content
.get("image_data_url")
.and_then(Value::as_str)
.filter(|value| !value.trim().is_empty())
.ok_or_else(|| EngineError::Validation("image analysis requires image data".to_string()))?;
let mut metadata = content.clone();
if let Some(object) = metadata.as_object_mut() {
object.remove("image_data_url");
}
Ok(json!([
{
"type": "text",
"text": format!(
"Analyze this image and return title, alt, and caption suggestions. Metadata: {}",
serde_json::to_string(&metadata)?
)
},
{
"type": "image_url",
"image_url": {
"url": image_data_url
}
}
]))
}
fn response_schema(operation: &OneShotOperation) -> (&'static str, Value) {
match operation {
OneShotOperation::AnalyzeTaxonomy => (
@@ -731,6 +757,23 @@ mod tests {
);
}
#[test]
fn image_analysis_user_content_is_multimodal() {
let content = build_image_analysis_user_content(&json!({
"title": "Existing title",
"alt": "Existing alt",
"image_data_url": "data:image/jpeg;base64,abc123"
}))
.unwrap();
let parts = content.as_array().unwrap();
assert_eq!(parts.len(), 2);
assert_eq!(parts[0]["type"], "text");
assert!(parts[0]["text"].as_str().unwrap().contains("Existing title"));
assert_eq!(parts[1]["type"], "image_url");
assert_eq!(parts[1]["image_url"]["url"], "data:image/jpeg;base64,abc123");
}
fn spawn_test_server(handler: impl Fn(String) -> String + Send + 'static) -> String {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();