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

@@ -6,7 +6,7 @@ import {
buildWorkflowVariableLabelMap,
getWorkflowVariableLabel,
} from './edit/workflowVariableLabels';
import { resolveBoundCharacterName, canUndoRun, canRerollRun, canAdvanceNextStep, getRunControls, hasRunControl, shouldShowFirstExport, shouldShowSaveAppendOverwrite, canSaveWorldbookEntry, canSwitchToNode } from './studioRunUtils';
import { resolveBoundCharacterName, canUndoRun, canRerollRun, canAdvanceNextStep, getRunControls, hasRunControl, shouldShowFirstExport, shouldShowSaveAppendOverwrite, canSaveWorldbookEntry, canSwitchToNode, isInitBindCompleted } from './studioRunUtils';
import useCharacterStore from '../../Store/SideBarLeft/CharacterSlice';
import StudioContextBlockPopup from './StudioContextBlockPopup';
@@ -584,9 +584,10 @@ function StudioRunPage() {
);
}, [currentRun]);
const isInitBindActive =
const isInitBindPending =
activeNodeState?.status === 'active' &&
activeNodeState?.skillId === 'studio.init_bind';
activeNodeState?.skillId === 'studio.init_bind' &&
!isInitBindCompleted(activeNodeState);
const runControls = useMemo(
() => getRunControls(activeNodeState?.skillId, skillTemplates),
@@ -702,7 +703,7 @@ function StudioRunPage() {
};
const handleChatSend = async (text) => {
if (!text || isInitBindActive || !isWorldbookActive || runMessaging) return;
if (!text || isInitBindPending || !isWorldbookActive || runMessaging) return;
setChatInput('');
@@ -774,13 +775,25 @@ function StudioRunPage() {
const handleGraphNodeSelect = useCallback(
async (nodeId) => {
if (!runSessionEntered || !currentRun || nodeId === currentRun.currentNodeId) return;
if (!currentRun || nodeId === currentRun.currentNodeId) return;
if (runLoading || runAdvancing || runMessaging) return;
const nodeState = currentRun.nodeStates?.find((n) => n.nodeId === nodeId);
if (!canSwitchToNode(nodeState)) return;
clearConfirmPending();
await switchRunNode(nodeId);
const run = await switchRunNode(nodeId);
if (run) {
setChatInput('');
setToolOptionIndex(0);
}
},
[runSessionEntered, currentRun, switchRunNode, clearConfirmPending]
[
currentRun,
runLoading,
runAdvancing,
runMessaging,
switchRunNode,
clearConfirmPending,
]
);
const handleDeleteRun = async (runId) => {
@@ -887,7 +900,7 @@ function StudioRunPage() {
nodeStates={graphNodeStates}
currentNodeId={graphCurrentNodeId}
variant="sidebar"
onNodeSelect={runSessionEntered ? handleGraphNodeSelect : undefined}
onNodeSelect={currentRun ? handleGraphNodeSelect : undefined}
canSelectNode={canSwitchToNode}
/>
);
@@ -915,6 +928,13 @@ function StudioRunPage() {
activeNodeState={activeNodeState}
isWorldbookActive={isWorldbookActive}
/>
<section className="studio-run-left-session__graph">
<h2 className="studio-run-section-title">节点进度</h2>
<p className="studio-run-hint studio-run-hint--inline">
点击已完成进行中或已有草稿的步骤可切换当前焦点
</p>
{renderNodeProgress()}
</section>
<PromptBlocksDebug
blocks={promptBlocks}
onBlockClick={setContextBlockPopup}
@@ -991,7 +1011,7 @@ function StudioRunPage() {
};
const renderSessionCenter = () => {
if (isInitBindActive && activePipelineNode) {
if (isInitBindPending && activePipelineNode) {
return (
<div className="studio-run-chat-card">
<InitBindGuidanceForm
@@ -1004,6 +1024,18 @@ function StudioRunPage() {
);
}
if (
activeNodeState?.skillId === 'studio.init_bind' &&
isInitBindCompleted(activeNodeState)
) {
return (
<div className="studio-run-chat-empty">
<p>创建并绑定步骤已完成</p>
<p className="studio-run-hint">绑定信息见左侧目前产物</p>
</div>
);
}
return (
<div className="studio-run-chat-panel">
<StudioRunChat
@@ -1015,7 +1047,7 @@ function StudioRunPage() {
onSend={handleChatSend}
onInterrupt={canInterrupt ? interruptRun : undefined}
canInterrupt={canInterrupt}
disabled={!isWorldbookActive || runAdvancing || isInitBindActive}
disabled={!isWorldbookActive || runAdvancing || isInitBindPending}
sending={runMessaging}
streamOutput={streamOutput}
onStreamOutputChange={setStreamOutput}