完善抓大鹅创作入口与运行态表现
This commit is contained in:
@@ -58,6 +58,9 @@ const MATCH3D_RUNTIME_PROVIDER: &str = "match3d-runtime";
|
||||
const MATCH3D_DEFAULT_THEME: &str = "缤纷玩具";
|
||||
const MATCH3D_DEFAULT_CLEAR_COUNT: u32 = 12;
|
||||
const MATCH3D_DEFAULT_DIFFICULTY: u32 = 4;
|
||||
const MATCH3D_QUESTION_THEME: &str = "你想创作什么题材";
|
||||
const MATCH3D_QUESTION_CLEAR_COUNT: &str = "需要消除多少次才能通关";
|
||||
const MATCH3D_QUESTION_DIFFICULTY: &str = "如果难度是从1-10,你要创作的关卡是难度几";
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -90,7 +93,7 @@ pub async fn create_match3d_agent_session(
|
||||
let Json(payload) = match3d_json(payload, &request_context, MATCH3D_AGENT_PROVIDER)?;
|
||||
let config = build_config_from_create_request(&payload);
|
||||
let seed_text = build_seed_text(&payload, &config);
|
||||
let welcome_message_text = build_match3d_assistant_reply(&config);
|
||||
let welcome_message_text = MATCH3D_QUESTION_THEME.to_string();
|
||||
|
||||
let session = state
|
||||
.spacetime_client()
|
||||
@@ -810,9 +813,10 @@ async fn submit_and_finalize_match3d_message(
|
||||
map_match3d_client_error(error),
|
||||
)
|
||||
})?;
|
||||
let next_turn = submitted.current_turn.saturating_add(1);
|
||||
let next_config = build_config_from_message(&submitted, &payload);
|
||||
let assistant_reply = build_match3d_assistant_reply(&next_config);
|
||||
let progress_percent = resolve_progress_percent(&next_config);
|
||||
let assistant_reply = build_match3d_assistant_reply_for_turn(&next_config, next_turn);
|
||||
let progress_percent = resolve_progress_percent_for_turn(next_turn);
|
||||
let stage = if progress_percent >= 100 {
|
||||
"ReadyToCompile"
|
||||
} else {
|
||||
@@ -865,6 +869,14 @@ async fn compile_match3d_draft_for_session(
|
||||
map_match3d_client_error(error),
|
||||
)
|
||||
})?;
|
||||
if session.current_turn < 3 || session.progress_percent < 100 {
|
||||
return Err(match3d_bad_request(
|
||||
request_context,
|
||||
MATCH3D_AGENT_PROVIDER,
|
||||
"match3d 创作配置尚未确认完成",
|
||||
));
|
||||
}
|
||||
|
||||
let config = resolve_config_or_default(session.config.as_ref());
|
||||
let tags_json = tags
|
||||
.as_ref()
|
||||
@@ -901,8 +913,12 @@ fn map_match3d_agent_session_response(
|
||||
session_id: session.session_id,
|
||||
current_turn: session.current_turn,
|
||||
progress_percent: session.progress_percent,
|
||||
stage: session.stage,
|
||||
anchor_pack: map_match3d_anchor_pack_response(session.anchor_pack),
|
||||
stage: session.stage.clone(),
|
||||
anchor_pack: map_match3d_anchor_pack_response_for_turn(
|
||||
session.anchor_pack,
|
||||
session.current_turn,
|
||||
session.stage.as_str(),
|
||||
),
|
||||
config: session.config.map(map_match3d_config_response),
|
||||
draft: session.draft.map(map_match3d_draft_response),
|
||||
messages: session
|
||||
@@ -916,11 +932,35 @@ fn map_match3d_agent_session_response(
|
||||
}
|
||||
}
|
||||
|
||||
fn map_match3d_anchor_pack_response(anchor: Match3DAnchorPackRecord) -> Match3DAnchorPackResponse {
|
||||
fn map_match3d_anchor_pack_response_for_turn(
|
||||
anchor: Match3DAnchorPackRecord,
|
||||
current_turn: u32,
|
||||
stage: &str,
|
||||
) -> Match3DAnchorPackResponse {
|
||||
let is_ready = matches!(
|
||||
stage,
|
||||
"ReadyToCompile"
|
||||
| "ready_to_compile"
|
||||
| "DraftCompiled"
|
||||
| "draft_compiled"
|
||||
| "draft_ready"
|
||||
| "ReadyToPublish"
|
||||
| "ready_to_publish"
|
||||
| "Published"
|
||||
| "published"
|
||||
);
|
||||
let collected_count = if is_ready { 3 } else { current_turn.min(3) };
|
||||
|
||||
Match3DAnchorPackResponse {
|
||||
theme: map_match3d_anchor_item_response(anchor.theme),
|
||||
clear_count: map_match3d_anchor_item_response(anchor.clear_count),
|
||||
difficulty: map_match3d_anchor_item_response(anchor.difficulty),
|
||||
theme: map_match3d_anchor_item_response_for_collected(anchor.theme, collected_count >= 1),
|
||||
clear_count: map_match3d_anchor_item_response_for_collected(
|
||||
anchor.clear_count,
|
||||
collected_count >= 2,
|
||||
),
|
||||
difficulty: map_match3d_anchor_item_response_for_collected(
|
||||
anchor.difficulty,
|
||||
collected_count >= 3,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -933,6 +973,22 @@ fn map_match3d_anchor_item_response(anchor: Match3DAnchorItemRecord) -> Match3DA
|
||||
}
|
||||
}
|
||||
|
||||
fn map_match3d_anchor_item_response_for_collected(
|
||||
anchor: Match3DAnchorItemRecord,
|
||||
collected: bool,
|
||||
) -> Match3DAnchorItemResponse {
|
||||
if collected {
|
||||
return map_match3d_anchor_item_response(anchor);
|
||||
}
|
||||
|
||||
Match3DAnchorItemResponse {
|
||||
key: anchor.key,
|
||||
label: anchor.label,
|
||||
value: String::new(),
|
||||
status: "missing".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_match3d_config_response(config: Match3DCreatorConfigRecord) -> Match3DCreatorConfigResponse {
|
||||
Match3DCreatorConfigResponse {
|
||||
theme_text: config.theme_text,
|
||||
@@ -1096,31 +1152,51 @@ fn build_config_from_message(
|
||||
payload: &SendMatch3DAgentMessageRequest,
|
||||
) -> Match3DConfigJson {
|
||||
let current = resolve_config_or_default(session.config.as_ref());
|
||||
if payload.quick_fill_requested.unwrap_or(false) || payload.text.contains("自动配置") {
|
||||
return Match3DConfigJson {
|
||||
theme_text: if current.theme_text.trim().is_empty() {
|
||||
let text = payload.text.trim();
|
||||
let reference_image_src = payload
|
||||
.reference_image_src
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(str::to_string)
|
||||
.or(current.reference_image_src);
|
||||
let quick_fill_requested =
|
||||
payload.quick_fill_requested.unwrap_or(false) || text.contains("自动配置");
|
||||
|
||||
let mut theme_text = current.theme_text;
|
||||
let mut clear_count = current.clear_count.max(1);
|
||||
let mut difficulty = current.difficulty.clamp(1, 10);
|
||||
|
||||
match session.current_turn {
|
||||
0 => {
|
||||
theme_text = if quick_fill_requested {
|
||||
MATCH3D_DEFAULT_THEME.to_string()
|
||||
} else {
|
||||
current.theme_text
|
||||
},
|
||||
reference_image_src: current.reference_image_src,
|
||||
clear_count: current.clear_count.max(1),
|
||||
difficulty: current.difficulty.clamp(1, 10),
|
||||
};
|
||||
parse_theme_answer(text).unwrap_or(theme_text)
|
||||
};
|
||||
}
|
||||
1 => {
|
||||
clear_count = if quick_fill_requested {
|
||||
clear_count
|
||||
} else {
|
||||
parse_number_after_keywords(text, &["消除", "次数", "clearCount"])
|
||||
.unwrap_or(clear_count)
|
||||
}
|
||||
.max(1);
|
||||
}
|
||||
_ => {
|
||||
difficulty = if quick_fill_requested {
|
||||
difficulty
|
||||
} else {
|
||||
parse_number_after_keywords(text, &["难度", "difficulty"]).unwrap_or(difficulty)
|
||||
}
|
||||
.clamp(1, 10);
|
||||
}
|
||||
}
|
||||
|
||||
let text = payload.text.trim();
|
||||
let theme_text = parse_theme_from_text(text).unwrap_or(current.theme_text);
|
||||
let clear_count = parse_number_after_keywords(text, &["消除", "次数", "clearCount"])
|
||||
.unwrap_or(current.clear_count)
|
||||
.max(1);
|
||||
let difficulty = parse_number_after_keywords(text, &["难度", "difficulty"])
|
||||
.unwrap_or(current.difficulty)
|
||||
.clamp(1, 10);
|
||||
|
||||
Match3DConfigJson {
|
||||
theme_text,
|
||||
reference_image_src: current.reference_image_src,
|
||||
reference_image_src,
|
||||
clear_count,
|
||||
difficulty,
|
||||
}
|
||||
@@ -1174,19 +1250,25 @@ fn build_match3d_assistant_reply(config: &Match3DConfigJson) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
fn resolve_progress_percent(config: &Match3DConfigJson) -> u32 {
|
||||
let completed = [
|
||||
!config.theme_text.trim().is_empty(),
|
||||
config.clear_count > 0,
|
||||
(1..=10).contains(&config.difficulty),
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|done| *done)
|
||||
.count();
|
||||
((completed as u32) * 100) / 3
|
||||
fn build_match3d_assistant_reply_for_turn(config: &Match3DConfigJson, current_turn: u32) -> String {
|
||||
match current_turn {
|
||||
0 => MATCH3D_QUESTION_THEME.to_string(),
|
||||
1 => MATCH3D_QUESTION_CLEAR_COUNT.to_string(),
|
||||
2 => MATCH3D_QUESTION_DIFFICULTY.to_string(),
|
||||
_ => build_match3d_assistant_reply(config),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_theme_from_text(text: &str) -> Option<String> {
|
||||
fn resolve_progress_percent_for_turn(current_turn: u32) -> u32 {
|
||||
match current_turn {
|
||||
0 => 0,
|
||||
1 => 33,
|
||||
2 => 66,
|
||||
_ => 100,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_theme_answer(text: &str) -> Option<String> {
|
||||
for marker in ["题材", "主题"] {
|
||||
if let Some((_, value)) = text.split_once(marker) {
|
||||
let normalized = value
|
||||
@@ -1416,3 +1498,81 @@ fn current_utc_micros() -> i64 {
|
||||
fn current_utc_ms() -> i64 {
|
||||
current_utc_micros().saturating_div(1000)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn config(theme_text: &str, clear_count: u32, difficulty: u32) -> Match3DConfigJson {
|
||||
Match3DConfigJson {
|
||||
theme_text: theme_text.to_string(),
|
||||
reference_image_src: None,
|
||||
clear_count,
|
||||
difficulty,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match3d_agent_reply_asks_three_questions_before_confirmation() {
|
||||
let current = config("水果", 4, 6);
|
||||
|
||||
assert_eq!(
|
||||
build_match3d_assistant_reply_for_turn(¤t, 0),
|
||||
MATCH3D_QUESTION_THEME
|
||||
);
|
||||
assert_eq!(
|
||||
build_match3d_assistant_reply_for_turn(¤t, 1),
|
||||
MATCH3D_QUESTION_CLEAR_COUNT
|
||||
);
|
||||
assert_eq!(
|
||||
build_match3d_assistant_reply_for_turn(¤t, 2),
|
||||
MATCH3D_QUESTION_DIFFICULTY
|
||||
);
|
||||
assert_eq!(
|
||||
build_match3d_assistant_reply_for_turn(¤t, 3),
|
||||
"已确认:水果题材,需要消除 4 次,共 12 件物品,难度 6。"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match3d_agent_progress_follows_question_turns() {
|
||||
assert_eq!(resolve_progress_percent_for_turn(0), 0);
|
||||
assert_eq!(resolve_progress_percent_for_turn(1), 33);
|
||||
assert_eq!(resolve_progress_percent_for_turn(2), 66);
|
||||
assert_eq!(resolve_progress_percent_for_turn(3), 100);
|
||||
assert_eq!(resolve_progress_percent_for_turn(8), 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match3d_anchor_pack_masks_uncollected_default_values() {
|
||||
let pack = Match3DAnchorPackRecord {
|
||||
theme: Match3DAnchorItemRecord {
|
||||
key: "theme".to_string(),
|
||||
label: "题材主题".to_string(),
|
||||
value: "缤纷玩具".to_string(),
|
||||
status: "confirmed".to_string(),
|
||||
},
|
||||
clear_count: Match3DAnchorItemRecord {
|
||||
key: "clearCount".to_string(),
|
||||
label: "需要消除次数".to_string(),
|
||||
value: "12".to_string(),
|
||||
status: "confirmed".to_string(),
|
||||
},
|
||||
difficulty: Match3DAnchorItemRecord {
|
||||
key: "difficulty".to_string(),
|
||||
label: "难度".to_string(),
|
||||
value: "4".to_string(),
|
||||
status: "confirmed".to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
let response = map_match3d_anchor_pack_response_for_turn(pack, 0, "Collecting");
|
||||
|
||||
assert_eq!(response.theme.value, "");
|
||||
assert_eq!(response.theme.status, "missing");
|
||||
assert_eq!(response.clear_count.value, "");
|
||||
assert_eq!(response.clear_count.status, "missing");
|
||||
assert_eq!(response.difficulty.value, "");
|
||||
assert_eq!(response.difficulty.status, "missing");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user