/** * Resolve bound character display name from workflow variable and/or project meta. */ export function resolveBoundCharacterName({ boundCharacterVar, characterId, characters = [] }) { if (boundCharacterVar) { const nameMatch = String(boundCharacterVar).match(/名称[::]\s*(.+?)(?:\n|$)/); if (nameMatch?.[1]?.trim()) { return nameMatch[1].trim(); } } if (characterId && characters.length > 0) { const byId = characters.find((c) => c.id === characterId); if (byId?.name) return byId.name; const byName = characters.find((c) => c.name === characterId); if (byName?.name) return byName.name; } return null; } /** Index of the last user message in stepMessages, or -1. */ export function findLastUserMessageIndex(stepMessages = []) { for (let i = stepMessages.length - 1; i >= 0; i -= 1) { if (stepMessages[i]?.role === 'user') return i; } 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); }