删除后端未使用的历史 helper、mapper、handler 和 re-export 将仅测试使用的导入、常量和辅助函数收口到 cfg(test) 补齐 Jump Hop 测试构造体字段并对齐 Match3D 当前素材表测试契约 验证后端 workspace cargo check 与 Match3D、Puzzle 相关测试
126 lines
4.3 KiB
Rust
126 lines
4.3 KiB
Rust
use super::*;
|
|
|
|
pub(super) fn build_match3d_vector_engine_gemini_image_request_body(
|
|
prompt: &str,
|
|
negative_prompt: &str,
|
|
aspect_ratio: &str,
|
|
) -> Value {
|
|
json!({
|
|
"contents": [{
|
|
"role": "user",
|
|
"parts": [{
|
|
"text": build_match3d_vector_engine_gemini_prompt(prompt, negative_prompt),
|
|
}],
|
|
}],
|
|
"generationConfig": {
|
|
"responseModalities": ["TEXT", "IMAGE"],
|
|
"imageConfig": {
|
|
"aspectRatio": aspect_ratio,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
pub(super) fn build_match3d_vector_engine_gemini_generate_content_url(
|
|
settings: &Match3DVectorEngineGeminiImageSettings,
|
|
) -> String {
|
|
let base_url = settings.base_url.trim_end_matches("/v1");
|
|
format!(
|
|
"{}/v1beta/models/{}:generateContent",
|
|
base_url, MATCH3D_MATERIAL_VECTOR_ENGINE_GEMINI_MODEL
|
|
)
|
|
}
|
|
|
|
fn build_match3d_vector_engine_gemini_prompt(prompt: &str, negative_prompt: &str) -> String {
|
|
let prompt = prompt.trim();
|
|
let negative_prompt = negative_prompt.trim();
|
|
if negative_prompt.is_empty() {
|
|
return prompt.to_string();
|
|
}
|
|
|
|
format!("{prompt}\n避免:{negative_prompt}")
|
|
}
|
|
|
|
pub(super) fn extract_match3d_b64_images(payload: &Value) -> Vec<String> {
|
|
let mut values = Vec::new();
|
|
collect_match3d_strings_by_key(payload, "b64_json", &mut values);
|
|
collect_match3d_inline_image_data(payload, &mut values);
|
|
values
|
|
}
|
|
|
|
fn collect_match3d_inline_image_data(payload: &Value, results: &mut Vec<String>) {
|
|
match payload {
|
|
Value::Array(entries) => {
|
|
for entry in entries {
|
|
collect_match3d_inline_image_data(entry, results);
|
|
}
|
|
}
|
|
Value::Object(object) => {
|
|
for key in ["inlineData", "inline_data"] {
|
|
if let Some(Value::Object(inline_data)) = object.get(key) {
|
|
let mime_type = inline_data
|
|
.get("mimeType")
|
|
.or_else(|| inline_data.get("mime_type"))
|
|
.and_then(Value::as_str)
|
|
.map(str::trim)
|
|
.unwrap_or("image/png")
|
|
.to_ascii_lowercase();
|
|
if !mime_type.is_empty() && !mime_type.starts_with("image/") {
|
|
continue;
|
|
}
|
|
if let Some(data) = inline_data
|
|
.get("data")
|
|
.and_then(Value::as_str)
|
|
.map(str::trim)
|
|
.filter(|value| !value.is_empty())
|
|
{
|
|
results.push(data.to_string());
|
|
}
|
|
}
|
|
}
|
|
for nested_value in object.values() {
|
|
collect_match3d_inline_image_data(nested_value, results);
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
fn collect_match3d_strings_by_key(payload: &Value, target_key: &str, results: &mut Vec<String>) {
|
|
match payload {
|
|
Value::Array(entries) => {
|
|
for entry in entries {
|
|
collect_match3d_strings_by_key(entry, target_key, results);
|
|
}
|
|
}
|
|
Value::Object(object) => {
|
|
for (key, nested_value) in object {
|
|
if key == target_key {
|
|
match nested_value {
|
|
Value::String(text) => {
|
|
let text = text.trim();
|
|
if !text.is_empty() {
|
|
results.push(text.to_string());
|
|
}
|
|
}
|
|
Value::Array(entries) => {
|
|
for entry in entries {
|
|
if let Some(text) = entry
|
|
.as_str()
|
|
.map(str::trim)
|
|
.filter(|value| !value.is_empty())
|
|
{
|
|
results.push(text.to_string());
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
collect_match3d_strings_by_key(nested_value, target_key, results);
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|