This commit is contained in:
2026-04-23 00:22:57 +08:00
parent 84dc92646a
commit cd207dc237
452 changed files with 10980 additions and 6574 deletions

View File

@@ -41,6 +41,7 @@ use module_combat::{
use module_custom_world::{
CustomWorldAgentActionExecuteInput, CustomWorldAgentActionExecuteResult,
CustomWorldAgentCardDetailGetInput, CustomWorldAgentMessageSnapshot,
CustomWorldAgentMessageFinalizeInput,
CustomWorldAgentMessageSubmitInput, CustomWorldAgentOperationGetInput,
CustomWorldAgentOperationProcedureResult, CustomWorldAgentOperationSnapshot,
CustomWorldAgentSessionCreateInput, CustomWorldAgentSessionGetInput,
@@ -61,7 +62,9 @@ use module_custom_world::{
build_custom_world_published_profile_compile_snapshot,
validate_custom_world_agent_action_execute_input,
validate_custom_world_agent_card_detail_get_input,
validate_custom_world_agent_message_finalize_input,
validate_custom_world_agent_message_submit_input,
validate_custom_world_agent_operation_fields,
validate_custom_world_agent_operation_get_input,
validate_custom_world_agent_session_create_input,
validate_custom_world_agent_session_get_input, validate_custom_world_gallery_detail_input,
@@ -878,6 +881,7 @@ pub struct CustomWorldAgentMessage {
created_at: Timestamp,
}
#[derive(Clone)]
#[spacetimedb::table(
accessor = custom_world_agent_operation,
index(accessor = by_custom_world_agent_operation_session_id, btree(columns = [session_id]))
@@ -1809,6 +1813,25 @@ pub fn submit_custom_world_agent_message(
}
}
#[spacetimedb::procedure]
pub fn finalize_custom_world_agent_message_turn(
ctx: &mut ProcedureContext,
input: CustomWorldAgentMessageFinalizeInput,
) -> CustomWorldAgentOperationProcedureResult {
match ctx.try_with_tx(|tx| finalize_custom_world_agent_message_turn_tx(tx, input.clone())) {
Ok(operation) => CustomWorldAgentOperationProcedureResult {
ok: true,
operation: Some(operation),
error_message: None,
},
Err(message) => CustomWorldAgentOperationProcedureResult {
ok: false,
operation: None,
error_message: Some(message),
},
}
}
#[spacetimedb::procedure]
pub fn get_custom_world_agent_operation(
ctx: &mut ProcedureContext,
@@ -2514,16 +2537,6 @@ fn submit_custom_world_agent_message_tx(
let submitted_at = Timestamp::from_micros_since_unix_epoch(input.submitted_at_micros);
let user_message_text = input.user_message_text.trim().to_string();
let assistant_message_id = format!("assistant-{}", input.operation_id);
if ctx
.db
.custom_world_agent_message()
.message_id()
.find(&assistant_message_id)
.is_some()
{
return Err("custom_world_agent_message.assistant_message_id 已存在".to_string());
}
ctx.db
.custom_world_agent_message()
@@ -2537,96 +2550,21 @@ fn submit_custom_world_agent_message_tx(
created_at: submitted_at,
});
let user_message_count = ctx
.db
.custom_world_agent_message()
.iter()
.filter(|row| {
row.session_id == input.session_id && matches!(row.role, RpgAgentMessageRole::User)
})
.count() as u32;
let next_turn = session.current_turn.saturating_add(1);
let (next_stage, next_progress_percent, next_readiness_json, next_pending_clarifications_json) =
if user_message_count >= 2 {
(
RpgAgentStage::FoundationReview,
100,
r#"{"isReady":true,"completedKeys":["seed_input"],"missingKeys":[]}"#.to_string(),
"[]".to_string(),
)
} else {
(
RpgAgentStage::Clarifying,
session.progress_percent.max(20),
session.creator_intent_readiness_json.clone(),
format!(
r#"[{{"id":"clarify-{next_turn}","label":"补充核心设定","question":"请继续补充这个世界的玩家身份、主题氛围或核心冲突。","targetKey":"core_conflict","priority":1}}]"#
),
)
};
let assistant_reply = "已记录这条设定。我会先把它当作新的世界线索收进当前草稿,你可以继续补充玩家身份、主题氛围、核心冲突、关键关系或标志性元素。".to_string();
ctx.db
.custom_world_agent_operation()
.insert(CustomWorldAgentOperation {
operation_id: input.operation_id.clone(),
session_id: input.session_id.clone(),
operation_type: RpgAgentOperationType::ProcessMessage,
status: RpgAgentOperationStatus::Completed,
phase_label: "消息处理".to_string(),
phase_detail: if next_stage == RpgAgentStage::FoundationReview {
"当前上下文已达到最小 foundation_review 门槛。".to_string()
} else {
"当前上下文已记录,继续收集世界关键锚点。".to_string()
},
progress: 100,
status: RpgAgentOperationStatus::Running,
phase_label: "消息处理".to_string(),
phase_detail: "已记录用户消息,等待大模型生成本轮回复。".to_string(),
progress: 10,
error_message: None,
created_at: submitted_at,
updated_at: submitted_at,
});
ctx.db
.custom_world_agent_message()
.insert(CustomWorldAgentMessage {
message_id: assistant_message_id,
session_id: input.session_id.clone(),
role: RpgAgentMessageRole::Assistant,
kind: RpgAgentMessageKind::Chat,
text: assistant_reply.clone(),
related_operation_id: Some(input.operation_id.clone()),
created_at: submitted_at,
});
ctx.db
.custom_world_agent_session()
.session_id()
.update(CustomWorldAgentSession {
session_id: session.session_id.clone(),
owner_user_id: session.owner_user_id.clone(),
seed_text: session.seed_text.clone(),
current_turn: next_turn,
progress_percent: next_progress_percent,
stage: next_stage,
focus_card_id: session.focus_card_id.clone(),
anchor_content_json: session.anchor_content_json.clone(),
creator_intent_json: session.creator_intent_json.clone(),
creator_intent_readiness_json: next_readiness_json,
anchor_pack_json: session.anchor_pack_json.clone(),
lock_state_json: session.lock_state_json.clone(),
draft_profile_json: session.draft_profile_json.clone(),
last_assistant_reply: Some(assistant_reply),
publish_gate_json: session.publish_gate_json.clone(),
result_preview_json: session.result_preview_json.clone(),
pending_clarifications_json: next_pending_clarifications_json,
quality_findings_json: session.quality_findings_json.clone(),
suggested_actions_json: session.suggested_actions_json.clone(),
recommended_replies_json: session.recommended_replies_json.clone(),
asset_coverage_json: session.asset_coverage_json.clone(),
checkpoints_json: session.checkpoints_json.clone(),
created_at: session.created_at,
updated_at: submitted_at,
});
get_custom_world_agent_operation_tx(
ctx,
CustomWorldAgentOperationGetInput {
@@ -2661,6 +2599,93 @@ fn get_custom_world_agent_operation_tx(
Ok(build_custom_world_agent_operation_snapshot(&operation))
}
fn finalize_custom_world_agent_message_turn_tx(
ctx: &ReducerContext,
input: CustomWorldAgentMessageFinalizeInput,
) -> Result<CustomWorldAgentOperationSnapshot, String> {
validate_custom_world_agent_message_finalize_input(&input)
.map_err(|error| error.to_string())?;
let session = ctx
.db
.custom_world_agent_session()
.session_id()
.find(&input.session_id)
.filter(|row| row.owner_user_id == input.owner_user_id)
.ok_or_else(|| "custom_world_agent_session 不存在".to_string())?;
let operation = ctx
.db
.custom_world_agent_operation()
.operation_id()
.find(&input.operation_id)
.filter(|row| row.session_id == input.session_id)
.ok_or_else(|| "custom_world_agent_operation 不存在".to_string())?;
if ctx
.db
.custom_world_agent_message()
.message_id()
.find(&input.assistant_message_id)
.is_some()
{
return Err("custom_world_agent_message.assistant_message_id 已存在".to_string());
}
let updated_at = Timestamp::from_micros_since_unix_epoch(input.updated_at_micros);
ctx.db
.custom_world_agent_message()
.insert(CustomWorldAgentMessage {
message_id: input.assistant_message_id.clone(),
session_id: input.session_id.clone(),
role: RpgAgentMessageRole::Assistant,
kind: RpgAgentMessageKind::Chat,
text: input.assistant_reply_text.clone(),
related_operation_id: Some(input.operation_id.clone()),
created_at: updated_at,
});
let next_session = rebuild_custom_world_agent_session_row(
&session,
CustomWorldAgentSessionPatch {
current_turn: Some(session.current_turn.saturating_add(1)),
progress_percent: Some(input.progress_percent),
stage: Some(input.stage),
focus_card_id: Some(input.focus_card_id.clone()),
anchor_content_json: Some(input.anchor_content_json.clone()),
creator_intent_json: Some(input.creator_intent_json.clone()),
creator_intent_readiness_json: Some(input.creator_intent_readiness_json.clone()),
anchor_pack_json: Some(input.anchor_pack_json.clone()),
draft_profile_json: Some(input.draft_profile_json.clone()),
last_assistant_reply: Some(Some(input.assistant_reply_text.clone())),
pending_clarifications_json: Some(input.pending_clarifications_json.clone()),
quality_findings_json: Some(input.quality_findings_json.clone()),
suggested_actions_json: Some(input.suggested_actions_json.clone()),
recommended_replies_json: Some(input.recommended_replies_json.clone()),
asset_coverage_json: Some(input.asset_coverage_json.clone()),
updated_at_micros: Some(input.updated_at_micros),
..CustomWorldAgentSessionPatch::default()
},
)?;
replace_custom_world_agent_session(ctx, &session, next_session);
let next_operation = rebuild_custom_world_agent_operation_row(
&operation,
CustomWorldAgentOperationPatch {
status: Some(input.operation_status),
phase_label: Some(input.phase_label.clone()),
phase_detail: Some(input.phase_detail.clone()),
progress: Some(input.operation_progress),
error_message: Some(input.error_message.clone()),
updated_at_micros: Some(input.updated_at_micros),
},
)?;
replace_custom_world_agent_operation(ctx, &operation, next_operation.clone());
Ok(build_custom_world_agent_operation_snapshot(&next_operation))
}
// AI 任务当前先固定成 private 真相表,后续由 Axum / platform-llm 再往外包一层 HTTP 与 SSE 协议。
#[spacetimedb::reducer]
pub fn create_ai_task(ctx: &ReducerContext, input: AiTaskCreateInput) -> Result<(), String> {
@@ -5027,6 +5052,7 @@ fn execute_placeholder_custom_world_action(
#[derive(Clone, Debug, Default)]
struct CustomWorldAgentSessionPatch {
current_turn: Option<u32>,
progress_percent: Option<u32>,
stage: Option<RpgAgentStage>,
focus_card_id: Option<Option<String>>,
@@ -5048,6 +5074,16 @@ struct CustomWorldAgentSessionPatch {
updated_at_micros: Option<i64>,
}
#[derive(Clone, Debug, Default)]
struct CustomWorldAgentOperationPatch {
status: Option<RpgAgentOperationStatus>,
phase_label: Option<String>,
phase_detail: Option<String>,
progress: Option<u32>,
error_message: Option<Option<String>>,
updated_at_micros: Option<i64>,
}
fn build_custom_world_publish_gate_from_session(
session: &CustomWorldAgentSession,
) -> CustomWorldPublishGateSnapshot {
@@ -5467,7 +5503,7 @@ fn rebuild_custom_world_agent_session_row(
session_id: current.session_id.clone(),
owner_user_id: current.owner_user_id.clone(),
seed_text: current.seed_text.clone(),
current_turn: current.current_turn,
current_turn: patch.current_turn.unwrap_or(current.current_turn),
progress_percent: patch.progress_percent.unwrap_or(current.progress_percent),
stage: patch.stage.unwrap_or(current.stage),
focus_card_id: patch
@@ -5527,6 +5563,44 @@ fn rebuild_custom_world_agent_session_row(
})
}
fn rebuild_custom_world_agent_operation_row(
current: &CustomWorldAgentOperation,
patch: CustomWorldAgentOperationPatch,
) -> Result<CustomWorldAgentOperation, String> {
let phase_label = patch
.phase_label
.unwrap_or_else(|| current.phase_label.clone());
let progress = patch.progress.unwrap_or(current.progress);
validate_custom_world_agent_operation_fields(
&current.operation_id,
&current.session_id,
&phase_label,
progress,
)
.map_err(|error| error.to_string())?;
Ok(CustomWorldAgentOperation {
operation_id: current.operation_id.clone(),
session_id: current.session_id.clone(),
operation_type: current.operation_type,
status: patch.status.unwrap_or(current.status),
phase_label,
phase_detail: patch
.phase_detail
.unwrap_or_else(|| current.phase_detail.clone()),
progress,
error_message: patch
.error_message
.unwrap_or_else(|| current.error_message.clone()),
created_at: current.created_at,
updated_at: Timestamp::from_micros_since_unix_epoch(
patch
.updated_at_micros
.unwrap_or_else(|| current.updated_at.to_micros_since_unix_epoch()),
),
})
}
fn replace_custom_world_agent_session(
ctx: &ReducerContext,
current: &CustomWorldAgentSession,
@@ -5539,6 +5613,18 @@ fn replace_custom_world_agent_session(
ctx.db.custom_world_agent_session().insert(next);
}
fn replace_custom_world_agent_operation(
ctx: &ReducerContext,
current: &CustomWorldAgentOperation,
next: CustomWorldAgentOperation,
) {
ctx.db
.custom_world_agent_operation()
.operation_id()
.delete(&current.operation_id);
ctx.db.custom_world_agent_operation().insert(next);
}
fn replace_custom_world_draft_card(
ctx: &ReducerContext,
current: &CustomWorldDraftCard,