Studio: fix edit/run UI and write worldbook on advance (R6)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-31 22:46:28 +08:00
parent 63a32bfa7c
commit 5bfe7a733f
7 changed files with 185 additions and 154 deletions

View File

@@ -30,6 +30,7 @@ from services.studio_step_respond import (
studio_step_respond,
studio_step_respond_stream,
)
from models.converters import WorldBookConverter
from services.worldbook_service import worldbook_service
logger = logging.getLogger(__name__)
@@ -278,6 +279,103 @@ class StudioRunService:
}
return workflow_variables, last_draft
@staticmethod
def _parse_keyword_field(raw: Any) -> List[str]:
if raw is None:
return []
if isinstance(raw, list):
return [str(x).strip() for x in raw if str(x).strip()]
text = str(raw).strip()
if not text:
return []
return [part.strip() for part in text.replace("", ",").split(",") if part.strip()]
def _resolve_bound_worldbook_name(
self, project_id: str, run: StudioRun
) -> str:
for state in run.nodeStates:
if state.skillId != "studio.init_bind" or state.status != "completed":
continue
draft = state.lastDraft or {}
name = (draft.get("worldbookName") or "").strip()
if name:
return name
try:
project = studio_project_service.get_project(project_id)
worldbook_id = project.meta.worldbookId
except FileNotFoundError:
worldbook_id = None
if worldbook_id:
for summary in worldbook_service.list_worldbooks():
wb_name = summary.get("name")
if not wb_name:
continue
try:
data = worldbook_service.get_worldbook(wb_name)
except FileNotFoundError:
continue
if data.get("id") == worldbook_id:
return wb_name
raise ValueError("未找到绑定的世界书,请先完成「创建并绑定」步骤")
def _build_worldbook_entry_payload(
self, node: StudioNode, draft: Dict[str, Any]
) -> Dict[str, Any]:
insertion = (node.config or {}).get("insertion") or {}
content = (draft.get("entryContent") or "").strip()
if not content:
raise ValueError("当前步骤产物为空,请先生成世界书条目内容后再进入下一步")
activation = (insertion.get("activationType") or "permanent").strip()
position = insertion.get("position", 0)
if isinstance(position, str):
position = WorldBookConverter.POSITION_MAP_ST_TO_INTERNAL.get(position, 0)
key = self._parse_keyword_field(
draft.get("insertionKey") or insertion.get("key")
)
keysecondary = self._parse_keyword_field(insertion.get("keysecondary"))
comment = (
(draft.get("insertionComment") or insertion.get("comment") or "").strip()
or f"Studio · {node.displayName}"
)
payload: Dict[str, Any] = {
"content": content,
"comment": comment,
"activationType": activation,
"position": position,
"key": key,
"keysecondary": keysecondary,
"order": insertion.get("order", 100),
"depth": insertion.get("depth", 4),
"probability": insertion.get("probability", 100),
"group": insertion.get("group") or [],
"disable": bool(insertion.get("disable", False)),
}
if activation == "rag" and insertion.get("ragConfig"):
payload["ragConfig"] = insertion["ragConfig"]
return payload
def _write_worldbook_entry_on_advance(
self,
worldbook_name: str,
node: StudioNode,
draft: Dict[str, Any],
) -> Dict[str, Any]:
entry_payload = self._build_worldbook_entry_payload(node, draft)
try:
return worldbook_service.append_entry(worldbook_name, entry_payload)
except FileNotFoundError:
raise ValueError(f"世界书「{worldbook_name}」不存在,无法写入条目")
except ValueError as exc:
raise ValueError(f"写入世界书失败:{exc}") from exc
except OSError as exc:
raise ValueError(f"写入世界书失败:{exc}") from exc
def advance_run(
self,
project_id: str,
@@ -314,6 +412,15 @@ class StudioRunService:
elif current_node.skillId == "studio.worldbook_entry":
if not current_state.lastDraft:
raise ValueError("请先生成并确认当前产物后再进入下一步")
worldbook_name = self._resolve_bound_worldbook_name(project_id, run)
written_entry = self._write_worldbook_entry_on_advance(
worldbook_name,
current_node,
current_state.lastDraft,
)
last_draft = copy.deepcopy(current_state.lastDraft)
last_draft["writtenEntryUid"] = written_entry.get("uid")
last_draft["writtenWorldbookName"] = worldbook_name
else:
raise NotImplementedError(
f"技能「{current_node.skillId}」的执行尚未实现R2+"

View File

@@ -232,6 +232,32 @@ class WorldBookService:
raise FileNotFoundError(f"Entry '{uid}' not found in worldbook '{name}'")
@staticmethod
def append_entry(name: str, entry_data: Dict[str, Any]) -> Dict[str, Any]:
"""
在世界书中追加条目(规范化后写入,与 Chat 侧条目格式一致)。
Args:
name: 世界书名称(文件名,不含 .json
entry_data: 条目字段content、comment、activationType、position 等)
Returns:
写入后的规范化条目
"""
data = WorldBookService._load_worldbook(name)
if not data:
raise FileNotFoundError(f"Worldbook '{name}' not found")
if not isinstance(data.get("entries"), list):
data["entries"] = []
normalized = WorldBookConverter.normalize_entry(entry_data)
data["entries"].append(normalized)
now = int(datetime.now().timestamp())
data["updatedAt"] = now
WorldBookService._save_worldbook(name, data)
return normalized
@staticmethod
def create_entry(name: str, entry_data: Dict[str, Any]) -> Dict[str, Any]:
"""