Resolve spacetime client binding merge conflicts

This commit is contained in:
2026-04-24 14:44:46 +08:00
parent 4f369617c7
commit f65177b147
26 changed files with 2172 additions and 1020 deletions

View File

@@ -15,11 +15,25 @@ pub enum DraftFoundationPayloadError {
InvalidGeneratedDraft(String),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CustomWorldFoundationDraftProgress {
pub phase_label: String,
pub phase_detail: String,
pub progress: u32,
}
pub async fn generate_custom_world_foundation_draft(
llm_client: &LlmClient,
session: &CustomWorldAgentSessionRecord,
mut on_progress: impl FnMut(CustomWorldFoundationDraftProgress) + Send,
) -> Result<CustomWorldFoundationDraftResult, String> {
let setting_text = build_foundation_generation_seed_text(session);
emit_foundation_draft_progress(
&mut on_progress,
"整理世界骨架",
"正在根据创作者锚点生成第一版世界框架。",
12,
);
let mut framework = request_foundation_json_stage(
llm_client,
build_custom_world_framework_prompt(setting_text.as_str()),
@@ -36,6 +50,8 @@ pub async fn generate_custom_world_foundation_draft(
&framework,
"playable",
FOUNDATION_DRAFT_PLAYABLE_COUNT,
(16, 30),
&mut on_progress,
)
.await?;
framework["playableNpcs"] = JsonValue::Array(playable_outlines.clone());
@@ -45,6 +61,8 @@ pub async fn generate_custom_world_foundation_draft(
&framework,
"story",
FOUNDATION_DRAFT_STORY_COUNT,
(30, 44),
&mut on_progress,
)
.await?;
framework["storyNpcs"] = JsonValue::Array(story_outlines.clone());
@@ -53,6 +71,8 @@ pub async fn generate_custom_world_foundation_draft(
llm_client,
&framework,
FOUNDATION_DRAFT_LANDMARK_COUNT,
(44, 56),
&mut on_progress,
)
.await?;
framework["landmarks"] = JsonValue::Array(landmark_seeds.clone());
@@ -62,6 +82,8 @@ pub async fn generate_custom_world_foundation_draft(
&framework,
&story_outlines,
&landmark_seeds,
(56, 66),
&mut on_progress,
)
.await?;
framework["landmarks"] = JsonValue::Array(landmarks.clone());
@@ -72,6 +94,8 @@ pub async fn generate_custom_world_foundation_draft(
"playable",
&playable_outlines,
"narrative",
(66, 76),
&mut on_progress,
)
.await?;
let playable_detailed = expand_foundation_role_entries(
@@ -80,6 +104,8 @@ pub async fn generate_custom_world_foundation_draft(
"playable",
&playable_narrative,
"dossier",
(76, 84),
&mut on_progress,
)
.await?;
let story_narrative = expand_foundation_role_entries(
@@ -88,6 +114,8 @@ pub async fn generate_custom_world_foundation_draft(
"story",
&story_outlines,
"narrative",
(84, 92),
&mut on_progress,
)
.await?;
let story_detailed = expand_foundation_role_entries(
@@ -96,9 +124,18 @@ pub async fn generate_custom_world_foundation_draft(
"story",
&story_narrative,
"dossier",
(92, 96),
&mut on_progress,
)
.await?;
emit_foundation_draft_progress(
&mut on_progress,
"编译世界底稿",
"正在把分批生成结果直接整理成第一版 foundation draft并同步兼容结果快照。",
97,
);
let draft_profile = build_foundation_draft_profile_from_framework(
framework,
playable_detailed,
@@ -166,6 +203,8 @@ async fn generate_foundation_role_outline_entries(
framework: &JsonValue,
role_type: &str,
total_count: usize,
progress_range: (u32, u32),
on_progress: &mut (impl FnMut(CustomWorldFoundationDraftProgress) + Send),
) -> Result<Vec<JsonValue>, String> {
let mut merged_entries = Vec::new();
let planned_batch_count = total_count
@@ -178,6 +217,24 @@ async fn generate_foundation_role_outline_entries(
let batch_count =
(total_count - merged_entries.len()).min(FOUNDATION_ROLE_OUTLINE_BATCH_SIZE);
let forbidden_names = names_from_entries(&merged_entries);
let role_label = if role_type == "playable" {
"可扮演角色"
} else {
"场景角色"
};
emit_foundation_draft_progress(
on_progress,
format!("生成{role_label}").as_str(),
format!(
"正在生成{role_label}{} / {} 批,当前已完成 {}/{}",
batch_index + 1,
planned_batch_count,
merged_entries.len(),
total_count,
)
.as_str(),
to_batch_progress(progress_range, merged_entries.len(), total_count),
);
let raw = request_foundation_json_stage(
llm_client,
build_custom_world_role_outline_batch_prompt(
@@ -210,13 +267,27 @@ async fn generate_foundation_role_outline_entries(
let key = role_key(role_type);
merged_entries.extend(array_field(&raw, key).into_iter().take(batch_count));
}
Ok(merged_entries.into_iter().take(total_count).collect())
let merged_entries: Vec<JsonValue> = merged_entries.into_iter().take(total_count).collect();
let role_label = if role_type == "playable" {
"可扮演角色"
} else {
"场景角色"
};
emit_foundation_draft_progress(
on_progress,
format!("生成{role_label}").as_str(),
format!("{role_label}已经整理完成,共 {} 个。", merged_entries.len()).as_str(),
progress_range.1,
);
Ok(merged_entries)
}
async fn generate_foundation_landmark_seed_entries(
llm_client: &LlmClient,
framework: &JsonValue,
total_count: usize,
progress_range: (u32, u32),
on_progress: &mut (impl FnMut(CustomWorldFoundationDraftProgress) + Send),
) -> Result<Vec<JsonValue>, String> {
let mut merged_entries = Vec::new();
let planned_batch_count = total_count.div_ceil(FOUNDATION_LANDMARK_BATCH_SIZE).max(1);
@@ -226,6 +297,19 @@ async fn generate_foundation_landmark_seed_entries(
}
let batch_count = (total_count - merged_entries.len()).min(FOUNDATION_LANDMARK_BATCH_SIZE);
let forbidden_names = names_from_entries(&merged_entries);
emit_foundation_draft_progress(
on_progress,
"生成关键场景",
format!(
"正在生成关键场景第 {} / {} 批,当前已完成 {}/{}",
batch_index + 1,
planned_batch_count,
merged_entries.len(),
total_count,
)
.as_str(),
to_batch_progress(progress_range, merged_entries.len(), total_count),
);
let raw = request_foundation_json_stage(
llm_client,
build_custom_world_landmark_seed_batch_prompt(framework, batch_count, &forbidden_names),
@@ -247,7 +331,14 @@ async fn generate_foundation_landmark_seed_entries(
.await?;
merged_entries.extend(array_field(&raw, "landmarks").into_iter().take(batch_count));
}
Ok(merged_entries.into_iter().take(total_count).collect())
let merged_entries: Vec<JsonValue> = merged_entries.into_iter().take(total_count).collect();
emit_foundation_draft_progress(
on_progress,
"生成关键场景",
format!("关键场景骨架已整理完成,共 {} 个。", merged_entries.len()).as_str(),
progress_range.1,
);
Ok(merged_entries)
}
async fn expand_foundation_landmark_network_entries(
@@ -255,12 +346,28 @@ async fn expand_foundation_landmark_network_entries(
framework: &JsonValue,
story_npcs: &[JsonValue],
base_entries: &[JsonValue],
progress_range: (u32, u32),
on_progress: &mut (impl FnMut(CustomWorldFoundationDraftProgress) + Send),
) -> Result<Vec<JsonValue>, String> {
let mut merged_entries = Vec::new();
for (batch_index, batch) in base_entries
let batches: Vec<&[JsonValue]> = base_entries
.chunks(FOUNDATION_LANDMARK_BATCH_SIZE)
.enumerate()
{
.collect();
let mut processed_count = 0usize;
for (batch_index, batch) in batches.iter().enumerate() {
emit_foundation_draft_progress(
on_progress,
"建立场景连接",
format!(
"正在补全场景连接第 {} / {} 批,当前已完成 {}/{}",
batch_index + 1,
batches.len(),
processed_count,
base_entries.len(),
)
.as_str(),
to_batch_progress(progress_range, processed_count, base_entries.len()),
);
let raw = request_foundation_json_stage(
llm_client,
build_custom_world_landmark_network_batch_prompt(framework, story_npcs, batch),
@@ -284,7 +391,16 @@ async fn expand_foundation_landmark_network_entries(
)
.await?;
merged_entries.extend(array_field(&raw, "landmarks"));
processed_count = processed_count
.saturating_add(batch.len())
.min(base_entries.len());
}
emit_foundation_draft_progress(
on_progress,
"建立场景连接",
"关键场景的角色分布与路径连接已经整理完成。",
progress_range.1,
);
Ok(merge_entries_by_name(base_entries, &merged_entries))
}
@@ -294,13 +410,39 @@ async fn expand_foundation_role_entries(
role_type: &str,
base_entries: &[JsonValue],
stage: &str,
progress_range: (u32, u32),
on_progress: &mut (impl FnMut(CustomWorldFoundationDraftProgress) + Send),
) -> Result<Vec<JsonValue>, String> {
let mut merged_entries = Vec::new();
for (batch_index, batch) in base_entries
let batches: Vec<&[JsonValue]> = base_entries
.chunks(FOUNDATION_ROLE_DETAIL_BATCH_SIZE)
.enumerate()
{
.collect();
let mut processed_count = 0usize;
for (batch_index, batch) in batches.iter().enumerate() {
let expected_names = names_from_entries(batch);
let role_label = if role_type == "playable" {
"可扮演角色"
} else {
"场景角色"
};
let stage_label = if stage == "narrative" {
"叙事基础"
} else {
"档案细节"
};
emit_foundation_draft_progress(
on_progress,
format!("补全{role_label}{stage_label}").as_str(),
format!(
"正在补全{role_label}{stage_label}{} / {} 批,当前已完成 {}/{}",
batch_index + 1,
batches.len(),
processed_count,
base_entries.len(),
)
.as_str(),
to_batch_progress(progress_range, processed_count, base_entries.len()),
);
let raw = request_foundation_json_stage(
llm_client,
build_custom_world_role_batch_prompt(framework, role_type, batch, stage),
@@ -326,9 +468,51 @@ async fn expand_foundation_role_entries(
)
.await?;
merged_entries.extend(array_field(&raw, role_key(role_type)));
processed_count = processed_count
.saturating_add(batch.len())
.min(base_entries.len());
}
let role_label = if role_type == "playable" {
"可扮演角色"
} else {
"场景角色"
};
let stage_label = if stage == "narrative" {
"叙事基础"
} else {
"档案细节"
};
emit_foundation_draft_progress(
on_progress,
format!("补全{role_label}{stage_label}").as_str(),
format!("{role_label}{stage_label}已经整理完成。").as_str(),
progress_range.1,
);
Ok(merge_entries_by_name(base_entries, &merged_entries))
}
fn emit_foundation_draft_progress(
on_progress: &mut (impl FnMut(CustomWorldFoundationDraftProgress) + Send),
phase_label: &str,
phase_detail: &str,
progress: u32,
) {
on_progress(CustomWorldFoundationDraftProgress {
phase_label: phase_label.to_string(),
phase_detail: phase_detail.to_string(),
progress: progress.min(100),
});
}
fn to_batch_progress(progress_range: (u32, u32), completed: usize, total: usize) -> u32 {
if total == 0 {
return progress_range.1;
}
let start = progress_range.0 as f64;
let end = progress_range.1 as f64;
let ratio = (completed as f64 / total as f64).clamp(0.0, 1.0);
(start + (end - start) * ratio).round().clamp(0.0, 100.0) as u32
}
// foundation draft 已经由 api-server 真实生成,落库前只负责把它注入现有 action payload。
pub fn build_draft_foundation_action_payload_json(
payload: &ExecuteCustomWorldAgentActionRequest,
@@ -1528,7 +1712,7 @@ mod tests {
let llm_client = build_test_llm_client(server_url);
let session = build_test_session();
let result = generate_custom_world_foundation_draft(&llm_client, &session)
let result = generate_custom_world_foundation_draft(&llm_client, &session, |_| {})
.await
.expect("draft generation should succeed");
let draft_profile = serde_json::from_str::<JsonValue>(&result.draft_profile_json)