feat(studio): 回退/重roll 与 R5 用户确认下一步
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -49,17 +49,15 @@ function StudioRunChat({
|
||||
|
||||
const lastUserIdx = findLastUserMessageIndex(stepMessages);
|
||||
const historyMessages =
|
||||
lastUserIdx > 0 ? stepMessages.slice(0, lastUserIdx) : [];
|
||||
lastUserIdx > 0
|
||||
? stepMessages.slice(0, lastUserIdx).filter((msg) => msg.role === 'user')
|
||||
: [];
|
||||
const lastUserMessage =
|
||||
lastUserIdx >= 0 ? stepMessages[lastUserIdx] : pendingUserText
|
||||
? { role: 'user', content: pendingUserText }
|
||||
: null;
|
||||
|
||||
const summaryText =
|
||||
evaluation ||
|
||||
(lastUserIdx >= 0 && stepMessages[lastUserIdx + 1]?.role === 'assistant'
|
||||
? stepMessages[lastUserIdx + 1].content
|
||||
: null);
|
||||
const summaryText = evaluation || null;
|
||||
|
||||
const hasFocusContent =
|
||||
thinking || summaryText || pendingUserText || sending || lastUserMessage;
|
||||
@@ -124,11 +122,9 @@ function StudioRunChat({
|
||||
{historyMessages.map((msg) => (
|
||||
<div
|
||||
key={msg.id}
|
||||
className={`studio-run-chat-history-item role-${msg.role}`}
|
||||
className="studio-run-chat-history-item role-user"
|
||||
>
|
||||
<span className="studio-run-chat-history-role">
|
||||
{msg.role === 'user' ? '你' : '助手'}
|
||||
</span>
|
||||
<span className="studio-run-chat-history-role">你</span>
|
||||
<span className="studio-run-chat-history-text">{msg.content}</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -752,6 +752,43 @@
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.studio-run-turn-actions__row {
|
||||
display: flex;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.studio-run-turn-btn {
|
||||
flex: 1;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border: 1px solid var(--color-border-light);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-bg-secondary);
|
||||
color: var(--color-text-primary);
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.studio-run-turn-btn:hover:not(:disabled) {
|
||||
background: var(--color-bg-tertiary);
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.studio-run-turn-btn:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.studio-run-turn-btn--reroll {
|
||||
border-color: var(--color-accent);
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.studio-run-turn-btn--reroll:hover:not(:disabled) {
|
||||
background: color-mix(in srgb, var(--color-accent) 12%, var(--color-bg-secondary));
|
||||
}
|
||||
|
||||
.studio-run-stub-note {
|
||||
margin: 0;
|
||||
font-size: 0.65rem;
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
buildWorkflowVariableLabelMap,
|
||||
getWorkflowVariableLabel,
|
||||
} from './edit/workflowVariableLabels';
|
||||
import { resolveBoundCharacterName } from './studioRunUtils';
|
||||
import { resolveBoundCharacterName, canUndoRun, canRerollRun, canAdvanceNextStep } from './studioRunUtils';
|
||||
import useCharacterStore from '../../Store/SideBarLeft/CharacterSlice';
|
||||
|
||||
import StudioContextBlockPopup from './StudioContextBlockPopup';
|
||||
@@ -469,6 +469,8 @@ function StudioRunPage() {
|
||||
selectRun,
|
||||
advanceRun,
|
||||
sendRunMessage,
|
||||
undoRun,
|
||||
rerollRun,
|
||||
deleteRun,
|
||||
renameRun,
|
||||
} = useStudioStore();
|
||||
@@ -629,8 +631,26 @@ function StudioRunPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleNextStep = () => {
|
||||
window.alert('(R5 占位:确认当前产物后将进入下一步)');
|
||||
const handleUndo = async () => {
|
||||
if (!isWorldbookActive || runMessaging || !canUndoRun(activeNodeState)) return;
|
||||
await undoRun();
|
||||
};
|
||||
|
||||
const handleReroll = async () => {
|
||||
if (!isWorldbookActive || runMessaging || !canRerollRun(activeNodeState)) return;
|
||||
await rerollRun({ stream: streamOutput });
|
||||
};
|
||||
|
||||
const handleNextStep = async () => {
|
||||
if (!isWorldbookActive || runAdvancing || runMessaging || !canAdvanceNextStep(activeNodeState)) {
|
||||
return;
|
||||
}
|
||||
const run = await advanceRun({});
|
||||
if (run) {
|
||||
setChatInput('');
|
||||
setToolOptionIndex(0);
|
||||
setPendingUserText(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteRun = async (runId) => {
|
||||
@@ -659,6 +679,10 @@ function StudioRunPage() {
|
||||
const inSession = runSessionEntered && !!currentRun;
|
||||
const showToolCarousel = inSession && isWorldbookActive && toolQuestions.length > 0;
|
||||
const showNextStep = inSession && isWorldbookActive;
|
||||
const canUndo = canUndoRun(activeNodeState);
|
||||
const canReroll = canRerollRun(activeNodeState);
|
||||
const canNextStep = canAdvanceNextStep(activeNodeState);
|
||||
const showTurnActions = inSession && isWorldbookActive;
|
||||
|
||||
const renderRunList = () => (
|
||||
<>
|
||||
@@ -881,13 +905,40 @@ function StudioRunPage() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{showTurnActions && (
|
||||
<div className="studio-run-right-section studio-run-turn-actions">
|
||||
<h2 className="studio-run-section-title">回合控制</h2>
|
||||
<div className="studio-run-turn-actions__row">
|
||||
<button
|
||||
type="button"
|
||||
className="studio-run-turn-btn studio-run-turn-btn--undo"
|
||||
onClick={handleUndo}
|
||||
disabled={runAdvancing || runMessaging || !canUndo}
|
||||
title={canUndo ? '回退到上一回合' : '无可回退的回合'}
|
||||
>
|
||||
回退
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="studio-run-turn-btn studio-run-turn-btn--reroll"
|
||||
onClick={handleReroll}
|
||||
disabled={runAdvancing || runMessaging || !canReroll}
|
||||
title={canReroll ? '用相同输入重新生成' : '尚无用户消息'}
|
||||
>
|
||||
重roll
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showNextStep && (
|
||||
<div className="studio-run-right-section studio-run-right-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="studio-run-next-btn"
|
||||
onClick={handleNextStep}
|
||||
disabled={runAdvancing || runMessaging}
|
||||
disabled={runAdvancing || runMessaging || !canNextStep}
|
||||
title={canNextStep ? '确认当前产物并进入下一节点' : '请先生成当前步骤产物'}
|
||||
>
|
||||
下一步
|
||||
</button>
|
||||
|
||||
@@ -26,3 +26,18 @@ export function findLastUserMessageIndex(stepMessages = []) {
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/** Whether undo is available for the active node state. */
|
||||
export function canUndoRun(activeNodeState) {
|
||||
return (activeNodeState?.turnHistory?.length ?? 0) > 0;
|
||||
}
|
||||
|
||||
/** Whether reroll is available (at least one user message in current step). */
|
||||
export function canRerollRun(activeNodeState) {
|
||||
return findLastUserMessageIndex(activeNodeState?.stepMessages) >= 0;
|
||||
}
|
||||
|
||||
/** Whether the user may advance to the next pipeline node (R5). */
|
||||
export function canAdvanceNextStep(activeNodeState) {
|
||||
return Boolean(activeNodeState?.lastDraft);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user