Fix Studio manual node switch state transitions so revisitable steps stay accessible after switching away.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-31 23:46:35 +08:00
parent 71e673a2ed
commit 2d0f82ee87
3 changed files with 133 additions and 21 deletions

View File

@@ -400,6 +400,60 @@ class StudioRunService:
draft = state.lastDraft or {}
return bool(draft.get("writtenEntryUid")) or state.status == "completed"
@staticmethod
def _init_bind_completed(state: StudioNodeRunState) -> bool:
draft = state.lastDraft or {}
return bool(draft.get("characterId") and draft.get("worldbookId")) or (
state.status == "completed"
)
@staticmethod
def _node_has_draft_progress(state: StudioNodeRunState) -> bool:
"""Node has user-visible progress even if not yet exported to worldbook."""
draft = state.lastDraft or {}
if state.skillId == "studio.worldbook_entry":
return bool(
(draft.get("entryContent") or "").strip()
or draft.get("writtenEntryUid")
or state.stepMessages
)
if state.skillId == "studio.init_bind":
return bool(
(draft.get("characterId") and draft.get("worldbookId"))
or draft.get("displayParams")
)
return False
@staticmethod
def _node_is_revisitable(state: StudioNodeRunState) -> bool:
if state.status in ("active", "completed"):
return True
if state.status == "pending":
return StudioRunService._node_has_draft_progress(state)
return False
@staticmethod
def _status_after_leaving_active_node(state: StudioNodeRunState) -> str:
if state.status == "completed":
return "completed"
if state.status != "active":
return state.status
if state.skillId == "studio.worldbook_entry":
if (
StudioRunService._node_has_written_entry(state)
or StudioRunService._node_has_draft_progress(state)
):
return "completed"
return "pending"
if state.skillId == "studio.init_bind":
if (
StudioRunService._init_bind_completed(state)
or StudioRunService._node_has_draft_progress(state)
):
return "completed"
return "pending"
return state.status
def _save_worldbook_entry_only(
self,
project_id: str,
@@ -603,16 +657,16 @@ class StudioRunService:
raise ValueError(f"节点不存在:{node_id}")
if not target_node.enabled:
raise ValueError("该节点已禁用,无法切换")
if target_node.skillId != "studio.worldbook_entry":
raise ValueError("仅支持切换到世界书创作步骤")
if target_node.skillId not in ("studio.worldbook_entry", "studio.init_bind"):
raise ValueError("仅支持切换到初始化绑定或世界书创作步骤")
target_state = next(
(s for s in run.nodeStates if s.nodeId == node_id), None
)
if not target_state:
raise ValueError("节点状态不存在")
if target_state.status not in ("active", "completed"):
raise ValueError("仅可切换到进行中或已完成的步骤")
if not self._node_is_revisitable(target_state):
raise ValueError("仅可切换到已有进度的步骤")
prev_node_id = run.currentNodeId
now = datetime.now().isoformat()
@@ -623,11 +677,7 @@ class StudioRunService:
if state.nodeId == node_id:
updated.status = "active"
elif prev_node_id and state.nodeId == prev_node_id and prev_node_id != node_id:
if state.skillId == "studio.worldbook_entry":
if self._node_has_written_entry(state):
updated.status = "completed"
else:
updated.status = "pending"
updated.status = self._status_after_leaving_active_node(state)
new_node_states.append(updated)
new_status = (