1
This commit is contained in:
@@ -1,18 +1,16 @@
|
||||
use module_puzzle::{
|
||||
use module_puzzle::{
|
||||
PUZZLE_MAX_TAG_COUNT, PuzzleAgentMessageKind, PuzzleAgentMessageRole,
|
||||
PuzzleAgentMessageSnapshot, PuzzleAgentSessionCreateInput, PuzzleAgentSessionGetInput,
|
||||
PuzzleAgentSessionProcedureResult, PuzzleAgentSessionSnapshot, PuzzleAgentStage,
|
||||
PuzzleAnchorPack, PuzzleDraftCompileInput, PuzzleGeneratedImageCandidate,
|
||||
PuzzleGeneratedImagesSaveInput, PuzzlePublicationStatus, PuzzlePublishInput,
|
||||
PuzzleResultDraft, PuzzleRunDragInput, PuzzleRunGetInput, PuzzleRunNextLevelInput,
|
||||
PuzzleRunProcedureResult, PuzzleRunSnapshot, PuzzleRunStartInput, PuzzleRunSwapInput,
|
||||
PuzzleRuntimeLevelStatus, PuzzleSelectCoverImageInput, PuzzleWorkGetInput,
|
||||
PuzzleWorkProcedureResult, PuzzleWorkProfile, PuzzleWorkUpsertInput,
|
||||
PuzzleWorksListInput, PuzzleWorksProcedureResult, apply_publish_overrides_to_draft,
|
||||
apply_selected_candidate,
|
||||
build_result_preview, compile_result_draft, create_work_profile, infer_anchor_pack,
|
||||
normalize_theme_tags, publish_work_profile, resolve_puzzle_grid_size,
|
||||
select_next_profile, start_run, swap_pieces,
|
||||
PuzzleGeneratedImagesSaveInput, PuzzlePublicationStatus, PuzzlePublishInput, PuzzleResultDraft,
|
||||
PuzzleRunDragInput, PuzzleRunGetInput, PuzzleRunNextLevelInput, PuzzleRunProcedureResult,
|
||||
PuzzleRunSnapshot, PuzzleRunStartInput, PuzzleRunSwapInput, PuzzleRuntimeLevelStatus,
|
||||
PuzzleSelectCoverImageInput, PuzzleWorkGetInput, PuzzleWorkProcedureResult, PuzzleWorkProfile,
|
||||
PuzzleWorkUpsertInput, PuzzleWorksListInput, PuzzleWorksProcedureResult,
|
||||
apply_publish_overrides_to_draft, apply_selected_candidate, build_result_preview,
|
||||
compile_result_draft, create_work_profile, infer_anchor_pack, normalize_theme_tags,
|
||||
publish_work_profile, resolve_puzzle_grid_size, select_next_profile, start_run, swap_pieces,
|
||||
};
|
||||
use serde_json::from_str as json_from_str;
|
||||
use serde_json::to_string as json_to_string;
|
||||
@@ -488,7 +486,10 @@ fn submit_puzzle_agent_message_tx(
|
||||
text: input.user_message_text.clone(),
|
||||
created_at: submitted_at,
|
||||
});
|
||||
let assistant_message_id = format!("{}assistant-{}", input.session_id, input.submitted_at_micros);
|
||||
let assistant_message_id = format!(
|
||||
"{}assistant-{}",
|
||||
input.session_id, input.submitted_at_micros
|
||||
);
|
||||
ctx.db.puzzle_agent_message().insert(PuzzleAgentMessageRow {
|
||||
message_id: assistant_message_id,
|
||||
session_id: input.session_id.clone(),
|
||||
@@ -547,7 +548,9 @@ fn compile_puzzle_agent_draft_tx(
|
||||
stage: PuzzleAgentStage::DraftReady,
|
||||
anchor_pack_json: serialize_json(&anchor_pack),
|
||||
draft_json: Some(serialize_json(&draft)),
|
||||
last_assistant_reply: Some("拼图结果页草稿已经生成,可以开始出图并确认标签。".to_string()),
|
||||
last_assistant_reply: Some(
|
||||
"拼图结果页草稿已经生成,可以开始出图并确认标签。".to_string(),
|
||||
),
|
||||
published_profile_id: row.published_profile_id.clone(),
|
||||
created_at: row.created_at,
|
||||
updated_at: compiled_at,
|
||||
@@ -574,14 +577,19 @@ fn save_puzzle_generated_images_tx(
|
||||
) -> Result<PuzzleAgentSessionSnapshot, String> {
|
||||
let row = get_owned_session_row(ctx, &input.session_id, &input.owner_user_id)?;
|
||||
let mut draft = deserialize_draft_required(&row.draft_json)?;
|
||||
let candidates: Vec<PuzzleGeneratedImageCandidate> =
|
||||
json_from_str(&input.candidates_json).map_err(|error| format!("拼图候选图 JSON 非法: {error}"))?;
|
||||
let candidates: Vec<PuzzleGeneratedImageCandidate> = json_from_str(&input.candidates_json)
|
||||
.map_err(|error| format!("拼图候选图 JSON 非法: {error}"))?;
|
||||
if candidates.is_empty() {
|
||||
return Err("拼图候选图不能为空".to_string());
|
||||
}
|
||||
draft.candidates = candidates;
|
||||
draft.generation_status = "ready".to_string();
|
||||
if let Some(selected) = draft.candidates.iter().find(|entry| entry.selected).cloned() {
|
||||
if let Some(selected) = draft
|
||||
.candidates
|
||||
.iter()
|
||||
.find(|entry| entry.selected)
|
||||
.cloned()
|
||||
{
|
||||
draft.selected_candidate_id = Some(selected.candidate_id);
|
||||
draft.cover_image_src = Some(selected.image_src);
|
||||
draft.cover_asset_id = Some(selected.asset_id);
|
||||
@@ -626,7 +634,8 @@ fn select_puzzle_cover_image_tx(
|
||||
) -> Result<PuzzleAgentSessionSnapshot, String> {
|
||||
let row = get_owned_session_row(ctx, &input.session_id, &input.owner_user_id)?;
|
||||
let draft = deserialize_draft_required(&row.draft_json)?;
|
||||
let draft = apply_selected_candidate(draft, &input.candidate_id).map_err(|error| error.to_string())?;
|
||||
let draft =
|
||||
apply_selected_candidate(draft, &input.candidate_id).map_err(|error| error.to_string())?;
|
||||
let selected_at = Timestamp::from_micros_since_unix_epoch(input.selected_at_micros);
|
||||
let next_stage = if build_result_preview(&draft, Some("创作者")).publish_ready {
|
||||
PuzzleAgentStage::ReadyToPublish
|
||||
@@ -814,7 +823,13 @@ fn start_puzzle_run_tx(
|
||||
ctx: &TxContext,
|
||||
input: PuzzleRunStartInput,
|
||||
) -> Result<PuzzleRunSnapshot, String> {
|
||||
if ctx.db.puzzle_runtime_run().run_id().find(&input.run_id).is_some() {
|
||||
if ctx
|
||||
.db
|
||||
.puzzle_runtime_run()
|
||||
.run_id()
|
||||
.find(&input.run_id)
|
||||
.is_some()
|
||||
{
|
||||
return Err("拼图 run 已存在".to_string());
|
||||
}
|
||||
let entry_profile_row = ctx
|
||||
@@ -827,7 +842,8 @@ fn start_puzzle_run_tx(
|
||||
return Err("入口拼图作品未发布".to_string());
|
||||
}
|
||||
let entry_profile = build_puzzle_work_profile_from_row(&entry_profile_row)?;
|
||||
let mut run = start_run(input.run_id.clone(), &entry_profile, 0).map_err(|error| error.to_string())?;
|
||||
let mut run =
|
||||
start_run(input.run_id.clone(), &entry_profile, 0).map_err(|error| error.to_string())?;
|
||||
run.recommended_next_profile_id = select_next_profile(
|
||||
&entry_profile,
|
||||
&run.played_profile_ids,
|
||||
@@ -854,8 +870,8 @@ fn swap_puzzle_pieces_tx(
|
||||
) -> Result<PuzzleRunSnapshot, String> {
|
||||
let row = get_owned_run_row(ctx, &input.run_id, &input.owner_user_id)?;
|
||||
let current_run = deserialize_run(&row.snapshot_json)?;
|
||||
let mut next_run =
|
||||
swap_pieces(¤t_run, &input.first_piece_id, &input.second_piece_id).map_err(|error| error.to_string())?;
|
||||
let mut next_run = swap_pieces(¤t_run, &input.first_piece_id, &input.second_piece_id)
|
||||
.map_err(|error| error.to_string())?;
|
||||
refresh_next_profile_recommendation(ctx, &mut next_run)?;
|
||||
replace_puzzle_runtime_run(ctx, &row, &next_run, input.swapped_at_micros);
|
||||
Ok(next_run)
|
||||
@@ -900,17 +916,18 @@ fn advance_puzzle_next_level_tx(
|
||||
.ok_or_else(|| "当前拼图作品不存在".to_string())?,
|
||||
)?;
|
||||
let candidates = list_published_puzzle_profiles(ctx)?;
|
||||
let next_profile = select_next_profile(¤t_profile, ¤t_run.played_profile_ids, &candidates)
|
||||
.ok_or_else(|| "没有可用的下一关候选".to_string())?
|
||||
.clone();
|
||||
let mut next_run =
|
||||
module_puzzle::advance_next_level(¤t_run, &next_profile).map_err(|error| error.to_string())?;
|
||||
next_run.recommended_next_profile_id = select_next_profile(
|
||||
&next_profile,
|
||||
&next_run.played_profile_ids,
|
||||
let next_profile = select_next_profile(
|
||||
¤t_profile,
|
||||
¤t_run.played_profile_ids,
|
||||
&candidates,
|
||||
)
|
||||
.map(|value| value.profile_id.clone());
|
||||
.ok_or_else(|| "没有可用的下一关候选".to_string())?
|
||||
.clone();
|
||||
let mut next_run = module_puzzle::advance_next_level(¤t_run, &next_profile)
|
||||
.map_err(|error| error.to_string())?;
|
||||
next_run.recommended_next_profile_id =
|
||||
select_next_profile(&next_profile, &next_run.played_profile_ids, &candidates)
|
||||
.map(|value| value.profile_id.clone());
|
||||
|
||||
if let Some(next_profile_row) = ctx
|
||||
.db
|
||||
@@ -954,7 +971,9 @@ fn build_puzzle_agent_session_snapshot(
|
||||
})
|
||||
}
|
||||
|
||||
fn build_puzzle_work_profile_from_row(row: &PuzzleWorkProfileRow) -> Result<PuzzleWorkProfile, String> {
|
||||
fn build_puzzle_work_profile_from_row(
|
||||
row: &PuzzleWorkProfileRow,
|
||||
) -> Result<PuzzleWorkProfile, String> {
|
||||
Ok(PuzzleWorkProfile {
|
||||
work_id: row.work_id.clone(),
|
||||
profile_id: row.profile_id.clone(),
|
||||
@@ -968,7 +987,9 @@ fn build_puzzle_work_profile_from_row(row: &PuzzleWorkProfileRow) -> Result<Puzz
|
||||
cover_asset_id: row.cover_asset_id.clone(),
|
||||
publication_status: row.publication_status,
|
||||
updated_at_micros: row.updated_at.to_micros_since_unix_epoch(),
|
||||
published_at_micros: row.published_at.map(|value| value.to_micros_since_unix_epoch()),
|
||||
published_at_micros: row
|
||||
.published_at
|
||||
.map(|value| value.to_micros_since_unix_epoch()),
|
||||
play_count: row.play_count,
|
||||
publish_ready: row.publish_ready,
|
||||
anchor_pack: deserialize_anchor_pack(&row.anchor_pack_json)?,
|
||||
@@ -994,7 +1015,9 @@ fn list_session_messages(ctx: &TxContext, session_id: &str) -> Vec<PuzzleAgentMe
|
||||
items
|
||||
}
|
||||
|
||||
fn build_puzzle_suggested_actions(stage: PuzzleAgentStage) -> Vec<module_puzzle::PuzzleAgentSuggestedAction> {
|
||||
fn build_puzzle_suggested_actions(
|
||||
stage: PuzzleAgentStage,
|
||||
) -> Vec<module_puzzle::PuzzleAgentSuggestedAction> {
|
||||
match stage {
|
||||
PuzzleAgentStage::CollectingAnchors => vec![module_puzzle::PuzzleAgentSuggestedAction {
|
||||
id: "compile-draft".to_string(),
|
||||
@@ -1051,14 +1074,26 @@ fn append_system_message(
|
||||
}
|
||||
|
||||
fn ensure_session_missing(ctx: &TxContext, session_id: &str) -> Result<(), String> {
|
||||
if ctx.db.puzzle_agent_session().session_id().find(&session_id.to_string()).is_some() {
|
||||
if ctx
|
||||
.db
|
||||
.puzzle_agent_session()
|
||||
.session_id()
|
||||
.find(&session_id.to_string())
|
||||
.is_some()
|
||||
{
|
||||
return Err("拼图 session 已存在".to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_message_missing(ctx: &TxContext, message_id: &str) -> Result<(), String> {
|
||||
if ctx.db.puzzle_agent_message().message_id().find(&message_id.to_string()).is_some() {
|
||||
if ctx
|
||||
.db
|
||||
.puzzle_agent_message()
|
||||
.message_id()
|
||||
.find(&message_id.to_string())
|
||||
.is_some()
|
||||
{
|
||||
return Err("拼图消息已存在".to_string());
|
||||
}
|
||||
Ok(())
|
||||
@@ -1122,10 +1157,7 @@ fn replace_puzzle_work_profile(
|
||||
ctx.db.puzzle_work_profile().insert(next);
|
||||
}
|
||||
|
||||
fn upsert_puzzle_work_profile(
|
||||
ctx: &TxContext,
|
||||
profile: PuzzleWorkProfile,
|
||||
) -> Result<(), String> {
|
||||
fn upsert_puzzle_work_profile(ctx: &TxContext, profile: PuzzleWorkProfile) -> Result<(), String> {
|
||||
if let Some(existing) = ctx
|
||||
.db
|
||||
.puzzle_work_profile()
|
||||
@@ -1219,10 +1251,7 @@ fn replace_puzzle_runtime_run(
|
||||
run: &PuzzleRunSnapshot,
|
||||
updated_at_micros: i64,
|
||||
) {
|
||||
ctx.db
|
||||
.puzzle_runtime_run()
|
||||
.run_id()
|
||||
.delete(¤t.run_id);
|
||||
ctx.db.puzzle_runtime_run().run_id().delete(¤t.run_id);
|
||||
ctx.db.puzzle_runtime_run().insert(PuzzleRuntimeRunRow {
|
||||
run_id: run.run_id.clone(),
|
||||
owner_user_id: current.owner_user_id.clone(),
|
||||
@@ -1414,6 +1443,9 @@ mod tests {
|
||||
author_display_name: "作者".to_string(),
|
||||
summary: String::new(),
|
||||
};
|
||||
assert!(recommendation_score(&left, &right) > tag_similarity_score(&left.theme_tags, &right.theme_tags));
|
||||
assert!(
|
||||
recommendation_score(&left, &right)
|
||||
> tag_similarity_score(&left.theme_tags, &right.theme_tags)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user