feat:暂停动画以及暂停正确响应

This commit is contained in:
liailing1026
2026-01-24 21:11:49 +08:00
parent 5699635d1a
commit b287867069

View File

@@ -43,31 +43,6 @@ const stepExecutionStatus = ref<Record<string, StepExecutionStatus>>({})
// 用于标记暂停时的"最后动作完成"状态
const isPausing = ref(false) // 正在请求暂停(等待当前动作完成)
// ==================== 步骤版本追踪 ====================
// 步骤版本信息接口
interface StepVersionInfo {
stepId: string // 步骤ID
stepIndex: number // 步骤索引0-based
originalHash: string // 原始配置hash初始化时生成
currentHash: string // 当前配置hash编辑后更新
isModified: boolean // 是否已修改
}
// 重执行配置接口
interface ReExecuteConfig {
shouldReExecute: boolean // 是否需要重新执行
startFromStepIndex: number // 从哪个步骤开始(-1表示从头开始
modifiedSteps: string[] // 被修改的步骤ID列表
}
// 步骤版本追踪
const stepVersions = ref<Record<string, StepVersionInfo>>({})
const reExecuteConfig = ref<ReExecuteConfig>({
shouldReExecute: false,
startFromStepIndex: -1,
modifiedSteps: []
})
// Check if step is ready to execute (has TaskProcess data)
const isStepReady = (step: IRawStepTask) => {
return step.TaskProcess && step.TaskProcess.length > 0
@@ -130,155 +105,6 @@ const stepsReadyStatus = computed(() => {
}
})
// ==================== 步骤版本追踪函数 ====================
/**
* 生成步骤配置的hash用于检测修改
* @param step 步骤对象
* @returns hash字符串
*/
function generateStepHash(step: IRawStepTask): string {
// 只考虑TaskProcess中的Description字段
// 因为这是用户可以编辑的部分
const processDescriptions = step.TaskProcess.map(p => `${p.ID}:${p.Description}`).join('|')
// 简单hash算法
let hash = 0
for (let i = 0; i < processDescriptions.length; i++) {
const char = processDescriptions.charCodeAt(i)
hash = (hash << 5) - hash + char
hash = hash & hash // Convert to 32bit integer
}
return Math.abs(hash).toString(36)
}
/**
* 初始化步骤版本(在任务加载完成后调用)
*/
function initializeStepVersions() {
const steps = collaborationProcess.value
stepVersions.value = {}
steps.forEach((step, index) => {
const stepId = step.Id || step.StepName || `step-${index}`
const hash = generateStepHash(step)
stepVersions.value[stepId] = {
stepId,
stepIndex: index,
originalHash: hash,
currentHash: hash,
isModified: false
}
})
// 重置重执行配置
reExecuteConfig.value = {
shouldReExecute: false,
startFromStepIndex: -1,
modifiedSteps: []
}
}
/**
* 更新步骤版本(编辑保存后调用)
*/
function updateStepVersion(stepId: string) {
const step = collaborationProcess.value.find(s => (s.Id || s.StepName) === stepId)
if (step && stepVersions.value[stepId]) {
const newHash = generateStepHash(step)
const versionInfo = stepVersions.value[stepId]
versionInfo.currentHash = newHash
versionInfo.isModified = newHash !== versionInfo.originalHash
// 重新计算重执行配置
calculateReExecuteConfig()
}
}
/**
* 计算重执行配置
*/
function calculateReExecuteConfig() {
const modifiedSteps: string[] = []
let minModifiedIndex = Infinity
// 找出所有被修改的步骤
Object.values(stepVersions.value).forEach(versionInfo => {
if (versionInfo.isModified) {
modifiedSteps.push(versionInfo.stepId)
minModifiedIndex = Math.min(minModifiedIndex, versionInfo.stepIndex)
}
})
// 设置重执行配置
if (modifiedSteps.length > 0) {
reExecuteConfig.value = {
shouldReExecute: true,
startFromStepIndex: minModifiedIndex,
modifiedSteps
}
} else {
reExecuteConfig.value = {
shouldReExecute: false,
startFromStepIndex: -1,
modifiedSteps: []
}
}
}
/**
* 重置步骤版本标记
*/
function resetStepVersions() {
Object.values(stepVersions.value).forEach(versionInfo => {
versionInfo.originalHash = versionInfo.currentHash
versionInfo.isModified = false
})
reExecuteConfig.value = {
shouldReExecute: false,
startFromStepIndex: -1,
modifiedSteps: []
}
}
/**
* 清理指定步骤索引之后的所有执行结果
* @param fromStepIndex 起始步骤索引(该索引之后的结果将被清理)
*/
function clearExecutionResultsAfter(fromStepIndex: number) {
const steps = collaborationProcess.value
const currentResults = agentsStore.executePlan
// 找出需要清理的步骤名称
const stepsToClear = new Set<string>()
for (let i = fromStepIndex; i < steps.length; i++) {
const step = steps[i]
if (!step) continue
if (step.StepName) {
stepsToClear.add(step.StepName)
}
if (step.OutputObject) {
stepsToClear.add(step.OutputObject)
}
}
// 过滤掉需要清理的结果
const filteredResults = currentResults.filter(result => !stepsToClear.has(result.NodeId))
// 更新store
agentsStore.setExecutePlan(filteredResults)
// 重置步骤执行状态
Object.keys(stepExecutionStatus.value).forEach(stepName => {
const stepIndex = steps.findIndex(s => s.StepName === stepName)
if (stepIndex >= fromStepIndex) {
stepExecutionStatus.value[stepName] = StepExecutionStatus.READY
}
})
}
/**
* 监听步骤数据变化,更新步骤状态并动态追加新步骤
*/
@@ -317,11 +143,6 @@ watch(
const totalStepsCount = collaborationProcess.value.length
const currentStep = executionProgress.value.currentStep || 1
executionProgress.value.totalSteps = totalStepsCount
// 使用 Notification 显示追加成功通知
// info('步骤追加成功', `${stepName} (${currentStep}/${totalStepsCount})`, {
// duration: 2000
// })
} else {
console.warn(`⚠️ 追加步骤失败: ${stepName} - 执行ID不存在或已结束`)
// 追加失败,移除标记以便重试
@@ -346,11 +167,6 @@ watch(
}
}
})
// 初始化步骤版本(首次加载时)
if (newSteps.length > 0 && Object.keys(stepVersions.value).length === 0) {
initializeStepVersions()
}
},
{ deep: true }
)
@@ -396,15 +212,6 @@ function handleSaveEdit(stepId: string, processId: string, value: string) {
const process = step.TaskProcess.find(p => p.ID === processId)
if (process) {
process.Description = value
// 更新步骤版本
updateStepVersion(stepId)
// 显示修改提示
const versionInfo = stepVersions.value[stepId]
if (versionInfo?.isModified) {
warning('步骤已修改', `步骤 "${step.StepName}" 已修改,继续执行时将从该步骤重新开始`)
}
}
}
editMap[key] = false
@@ -649,9 +456,11 @@ async function executeNextReadyBatch() {
switch (event.type) {
case 'step_start':
// 使用全局步骤索引计算当前步骤
const globalStepIndex = collaborationProcess.value.findIndex(s => s.StepName === event.step_name)
executionProgress.value = {
currentStep: (event.step_index || 0) + 1,
totalSteps: event.total_steps || collaborationProcess.value.length,
currentStep: globalStepIndex >= 0 ? globalStepIndex + 1 : (event.step_index || 0) + 1,
totalSteps: collaborationProcess.value.length,
currentAction: 0,
totalActions: 0,
currentStepName: event.step_name
@@ -662,24 +471,20 @@ async function executeNextReadyBatch() {
currentProgressNotificationId.value = showProgress(
'任务执行中',
executionProgress.value.currentStep,
executionProgress.value.totalSteps || 1
executionProgress.value.totalSteps
)
updateProgressDetail(
currentProgressNotificationId.value,
`步骤 ${executionProgress.value.currentStep}/${
executionProgress.value.totalSteps || 1
}`,
`步骤 ${executionProgress.value.currentStep}/${executionProgress.value.totalSteps}`,
`正在执行: ${event.step_name}`
)
} else {
updateProgressDetail(
currentProgressNotificationId.value,
`步骤 ${executionProgress.value.currentStep}/${
executionProgress.value.totalSteps || 1
}`,
`步骤 ${executionProgress.value.currentStep}/${executionProgress.value.totalSteps}`,
`正在执行: ${event.step_name}`,
executionProgress.value.currentStep,
executionProgress.value.totalSteps || 1
executionProgress.value.totalSteps
)
}
break
@@ -689,22 +494,32 @@ async function executeNextReadyBatch() {
? ` [并行 ${event.batch_info!.batch_size} 个动作]`
: ''
const stepIndexForAction = event.step_index || 0
const totalStepsValue =
executionProgress.value.totalSteps || collaborationProcess.value.length
// 使用全局步骤索引
const globalStepIndexForAction = collaborationProcess.value.findIndex(s => s.StepName === event.step_name)
const stepIndexForAction = globalStepIndexForAction >= 0 ? globalStepIndexForAction + 1 : (event.step_index || 0) + 1
const totalStepsValue = collaborationProcess.value.length
executionProgress.value = {
...executionProgress.value,
currentAction: event.completed_actions,
totalActions: event.total_actions
}
// 检测是否正在暂停(等待当前动作完成)
if (isPausing.value) {
// 当前动作完成,完成暂停
isPaused.value = true
isPausing.value = false
success('已暂停', '已暂停执行,可稍后继续')
}
// 更新详细进度信息,显示动作级别进度
if (currentProgressNotificationId.value) {
updateProgressDetail(
currentProgressNotificationId.value,
`步骤 ${stepIndexForAction + 1}/${totalStepsValue}`,
`步骤 ${stepIndexForAction}/${totalStepsValue}`,
`${event.step_name} - 动作 ${event.completed_actions}/${event.total_actions} 完成${parallelInfo}`,
stepIndexForAction + 1,
stepIndexForAction,
totalStepsValue
)
}
@@ -880,46 +695,7 @@ function removeNotification(id: string) {
// Pause/Resume handler
async function handlePauseResume() {
if (isPaused.value) {
// Resume execution
// 检查是否需要重新执行(有步骤被修改)
if (reExecuteConfig.value.shouldReExecute) {
const startStepIndex = reExecuteConfig.value.startFromStepIndex
const startStep = collaborationProcess.value[startStepIndex]
try {
// 确认对话框
await ElMessageBox.confirm(
`检测到 ${reExecuteConfig.value.modifiedSteps.length} 个步骤已被修改\n` +
`将从步骤 "${startStep?.StepName}" 重新开始执行\n\n` +
`是否继续?`,
'检测到步骤修改',
{
confirmButtonText: '从修改步骤重新执行',
cancelButtonText: '取消',
type: 'warning'
}
)
// 清理执行结果
clearExecutionResultsAfter(startStepIndex)
// 重置暂停状态
isPaused.value = false
isPausing.value = false
isStreaming.value = false
// 从指定步骤重新执行
await reExecuteFromStep(startStepIndex)
// 重置修改标记
resetStepVersions()
} catch {
// 用户取消
info('已取消', '已取消重新执行')
}
} else {
// 没有修改,正常恢复执行
// Resume execution - 正常恢复执行
try {
if (websocket.connected) {
await websocket.send('resume_execution', {
@@ -936,7 +712,6 @@ async function handlePauseResume() {
error('恢复失败', '恢复执行失败')
// 恢复失败时,保持原状态不变(仍然是暂停状态)
}
}
} else {
// Pause execution
try {
@@ -949,10 +724,9 @@ async function handlePauseResume() {
goal: agentsStore.agentRawPlan.data?.['General Goal'] || ''
})
// 收到成功响应后,设置完全暂停状态
isPaused.value = true
isPausing.value = false
success('已暂停', '已暂停执行,可稍后继续')
// 注意:不立即设置 isPaused = true
// 而是等待当前动作完成后,在 action_complete 事件中设置
// 这样可以确保在动作真正完成后才显示"已暂停"
} else {
warning('无法暂停', 'WebSocket未连接无法暂停')
isPausing.value = false
@@ -977,35 +751,6 @@ async function handleExecuteButtonClick() {
await handleRun()
}
/**
* 从指定步骤重新执行(支持边填充边执行)
* @param fromStepIndex 起始步骤索引
*/
async function reExecuteFromStep(fromStepIndex: number) {
const steps = collaborationProcess.value
// 设置执行状态
loading.value = true
isStreaming.value = true
// 重置将要执行的步骤的状态
for (let i = fromStepIndex; i < steps.length; i++) {
const step = steps[i]
if (step) {
const stepName = step.StepName || step.Id || ''
stepExecutionStatus.value[stepName] = StepExecutionStatus.READY
}
}
// 使用批量执行模式,会自动处理依赖和边填充边执行
await executeNextReadyBatch()
success('重新执行完成', '所有步骤已重新执行完成')
loading.value = false
isPaused.value = false
isStreaming.value = false
}
async function handleRun() {
// Check if there are ready steps
const readySteps = stepsReadyStatus.value.ready
@@ -1302,15 +1047,23 @@ defineExpose({
class="animate-spin"
/>
<!-- 流式传输中且未Pause显示Pause图标 -->
<!-- 流式传输中且正在暂停等待当前动作完成显示Loading图标 -->
<svg-icon
v-else-if="isStreaming && !isPaused"
v-else-if="isStreaming && isPausing"
icon-class="loading"
size="20px"
class="btn-icon animate-spin"
/>
<!-- 流式传输中且未暂停显示Pause图标 -->
<svg-icon
v-else-if="isStreaming && !isPaused && !isPausing"
icon-class="Pause"
size="20px"
class="btn-icon"
/>
<!-- 流式传输中且已Pause显示播放/Resume图标 -->
<!-- 流式传输中且已暂停显示播放/Resume图标 -->
<svg-icon
v-else-if="isStreaming && isPaused"
icon-class="video-play"
@@ -1323,6 +1076,7 @@ defineExpose({
<span v-if="showExecuteText && !isStreaming" class="btn-text">任务执行</span>
<span v-else-if="isStreaming && isPaused" class="btn-text">继续执行</span>
<span v-else-if="isStreaming && isPausing" class="btn-text">暂停中...</span>
<span v-else-if="isStreaming" class="btn-text">暂停执行</span>
</el-button>
</template>