feat:任务大纲编辑文字悬浮边框重构

This commit is contained in:
liailing1026
2026-02-27 11:45:16 +08:00
parent c009db12a6
commit c14430cfae
6 changed files with 263 additions and 81 deletions

View File

@@ -7,12 +7,14 @@ import websocket from '@/utils/websocket'
import { changeBriefs } from '@/utils/collaboration_Brief_FrontEnd.ts' import { changeBriefs } from '@/utils/collaboration_Brief_FrontEnd.ts'
import { useNotification } from '@/composables/useNotification' import { useNotification } from '@/composables/useNotification'
import AssignmentButton from './TaskTemplate/TaskSyllabus/components/AssignmentButton.vue' import AssignmentButton from './TaskTemplate/TaskSyllabus/components/AssignmentButton.vue'
import HistoryList from './TaskTemplate/HistoryList/index.vue'
import { withRetry } from '@/utils/retry' import { withRetry } from '@/utils/retry'
import { Clock } from '@element-plus/icons-vue' import UnifiedSettingsPanel from './TaskTemplate/UnifiedSettingsPanel.vue'
import { Setting } from '@element-plus/icons-vue'
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'search-start'): void (e: 'search-start'): void
(e: 'search', value: string): void (e: 'search', value: string): void
(e: 'open-history'): void
}>() }>()
const agentsStore = useAgentsStore() const agentsStore = useAgentsStore()
@@ -32,13 +34,11 @@ const currentGenerationId = ref('')
const currentTaskID = ref('') // 后端数据库主键 const currentTaskID = ref('') // 后端数据库主键
// 监听 currentTaskID 变化,同步到全局变量(供分支保存使用) // 监听 currentTaskID 变化,同步到全局变量(供分支保存使用)
watch(currentTaskID, (newVal) => { watch(currentTaskID, newVal => {
;(window as any).__CURRENT_TASK_ID__ = newVal || '' ;(window as any).__CURRENT_TASK_ID__ = newVal || ''
console.log('♻️ [Task] currentTaskID 同步到全局变量:', (window as any).__CURRENT_TASK_ID__)
}) })
const currentPlanOutline = ref<any>(null) // 用于恢复的完整大纲数据 const currentPlanOutline = ref<any>(null) // 用于恢复的完整大纲数据
const historyDialogVisible = ref(false) // 历史记录弹窗控制
// 解析URL参数 // 解析URL参数
function getUrlParam(param: string): string | null { function getUrlParam(param: string): string | null {
@@ -165,11 +165,14 @@ async function handleStop() {
// 发送停止请求(不等待响应,后端设置 should_stop = True // 发送停止请求(不等待响应,后端设置 should_stop = True
if (websocket.connected && currentGenerationId.value) { if (websocket.connected && currentGenerationId.value) {
websocket.send('stop_generation', { websocket
.send('stop_generation', {
generation_id: currentGenerationId.value generation_id: currentGenerationId.value
}).then((result: any) => { })
.then((result: any) => {
console.log('停止生成响应:', result) console.log('停止生成响应:', result)
}).catch((error: any) => { })
.catch((error: any) => {
console.log('停止生成请求失败(可能已经停止):', error?.message) console.log('停止生成请求失败(可能已经停止):', error?.message)
}) })
} }
@@ -229,7 +232,12 @@ async function handleSearch() {
outlineData = response.data.basePlan outlineData = response.data.basePlan
currentGenerationId.value = response.data.generation_id || '' currentGenerationId.value = response.data.generation_id || ''
currentTaskID.value = response.data.task_id || '' currentTaskID.value = response.data.task_id || ''
console.log('📋 WebSocket格式: 从basePlan提取数据, generation_id:', currentGenerationId.value, 'TaskID:', currentTaskID.value) console.log(
'📋 WebSocket格式: 从basePlan提取数据, generation_id:',
currentGenerationId.value,
'TaskID:',
currentTaskID.value
)
} else if (response?.data) { } else if (response?.data) {
// 可能是WebSocket旧格式或REST API格式 // 可能是WebSocket旧格式或REST API格式
outlineData = response.data outlineData = response.data
@@ -285,10 +293,10 @@ async function handleSearch() {
TaskContent: step.TaskContent, TaskContent: step.TaskContent,
InputObject_List: step.InputObject_List, InputObject_List: step.InputObject_List,
OutputObject: step.OutputObject, OutputObject: step.OutputObject,
Id: step.Id, // 传递步骤ID用于后端定位 Id: step.Id // 传递步骤ID用于后端定位
}, },
generation_id: fillTaskGenerationId, generation_id: fillTaskGenerationId,
TaskID: fillTaskTaskID, // 后端数据库主键 TaskID: fillTaskTaskID // 后端数据库主键
}) })
console.log(`📥 fillStepTask 返回完成: 步骤=${step.StepName}`) console.log(`📥 fillStepTask 返回完成: 步骤=${step.StepName}`)
updateStepDetail(step.StepName, detailedStep) updateStepDetail(step.StepName, detailedStep)
@@ -296,8 +304,8 @@ async function handleSearch() {
{ {
maxRetries: 2, // 减少重试次数,因为是串行填充 maxRetries: 2, // 减少重试次数,因为是串行填充
initialDelayMs: 1000, // 使用较小的延迟 initialDelayMs: 1000, // 使用较小的延迟
shouldRetry: () => isFillingSteps.value && !agentsStore.isStopping, // 可取消的重试 shouldRetry: () => isFillingSteps.value && !agentsStore.isStopping // 可取消的重试
}, }
) )
} }
} finally { } finally {
@@ -367,17 +375,12 @@ onUnmounted(() => {
// 打开历史记录弹窗 // 打开历史记录弹窗
const openHistoryDialog = () => { const openHistoryDialog = () => {
historyDialogVisible.value = true emit('open-history')
} }
// 处理历史任务恢复 // 从历史记录恢复任务
const handleRestorePlan = (plan: any) => { const restoreFromHistory = (plan: any) => {
console.log('♻️ [Task] 恢复历史任务:', plan) // 1. 设置搜索值为历史任务的目标
// 1. 关闭弹窗
historyDialogVisible.value = false
// 2. 设置搜索值为历史任务的目标
searchValue.value = plan.general_goal searchValue.value = plan.general_goal
// 3. 设置当前任务的 task_id供分支保存使用 // 3. 设置当前任务的 task_id供分支保存使用
@@ -402,7 +405,10 @@ const handleRestorePlan = (plan: any) => {
// 5. 如果有智能体评分数据,更新到 store // 5. 如果有智能体评分数据,更新到 store
if (plan.agent_scores && currentPlanOutline.value) { if (plan.agent_scores && currentPlanOutline.value) {
const tasks = currentPlanOutline.value['Collaboration Process'] || [] const tasks = currentPlanOutline.value['Collaboration Process'] || []
console.log('🔍 [Task] 所有步骤名称:', tasks.map((t: any) => t.StepName)) console.log(
'🔍 [Task] 所有步骤名称:',
tasks.map((t: any) => t.StepName)
)
console.log('🔍 [Task] agent_scores 的 key:', Object.keys(plan.agent_scores)) console.log('🔍 [Task] agent_scores 的 key:', Object.keys(plan.agent_scores))
for (const task of tasks) { for (const task of tasks) {
@@ -473,8 +479,13 @@ const handleRestorePlan = (plan: any) => {
} }
// 恢复任务过程分支 // 恢复任务过程分支
if (plan.branches.task_process_branches && typeof plan.branches.task_process_branches === 'object') { if (
console.log('♻️ [Task] 恢复任务过程分支:', { stepCount: Object.keys(plan.branches.task_process_branches).length }) plan.branches.task_process_branches &&
typeof plan.branches.task_process_branches === 'object'
) {
console.log('♻️ [Task] 恢复任务过程分支:', {
stepCount: Object.keys(plan.branches.task_process_branches).length
})
selectionStore.restoreTaskProcessBranchesFromDB(plan.branches.task_process_branches) selectionStore.restoreTaskProcessBranchesFromDB(plan.branches.task_process_branches)
} }
} }
@@ -498,10 +509,20 @@ const handleRestorePlan = (plan: any) => {
success('成功', '已恢复历史任务') success('成功', '已恢复历史任务')
} }
// 设置面板引用
const unifiedSettingsPanelRef = ref<InstanceType<typeof UnifiedSettingsPanel> | null>(null)
// 打开设置面板
const openSettingsPanel = () => {
unifiedSettingsPanelRef.value?.open()
}
// 暴露给父组件 // 暴露给父组件
defineExpose({ defineExpose({
currentTaskID, currentTaskID,
openHistoryDialog openHistoryDialog,
restoreFromHistory,
openSettingsPanel
}) })
</script> </script>
@@ -566,29 +587,14 @@ defineExpose({
</el-button> </el-button>
</div> </div>
<AssignmentButton v-if="planReady" @click="openAgentAllocationDialog" /> <AssignmentButton v-if="planReady" @click="openAgentAllocationDialog" />
<!-- 历史记录按钮 --> <!-- 设置按钮 -->
<el-button <el-button class="setting-button" circle title="设置" @click="openSettingsPanel">
class="history-button" <el-icon size="18px"><Setting /></el-icon>
circle
:title="'历史记录'"
@click.stop="openHistoryDialog"
>
<el-icon size="18px"><Clock /></el-icon>
</el-button> </el-button>
<!-- 统一设置面板 -->
<UnifiedSettingsPanel ref="unifiedSettingsPanelRef" />
</div> </div>
</el-tooltip> </el-tooltip>
<!-- 历史记录弹窗 -->
<el-dialog
v-model="historyDialogVisible"
title="历史任务"
width="600px"
:close-on-click-modal="true"
destroy-on-close
append-to-body
>
<HistoryList @restore="handleRestorePlan" />
</el-dialog>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -728,8 +734,8 @@ defineExpose({
} }
} }
// 历史记录按钮 // 设置按钮
.history-button { .setting-button {
position: absolute; position: absolute;
right: 70px; right: 70px;
top: 28px; top: 28px;
@@ -741,7 +747,7 @@ defineExpose({
&:hover { &:hover {
background: var(--color-bg-hover); background: var(--color-bg-hover);
color: var(--color-text-hover); color: var(--color-text-taskbar);
} }
} }
</style> </style>

View File

@@ -1197,9 +1197,9 @@ const processBtnClass = computed(() => {
const executeBtnClass = computed(() => { const executeBtnClass = computed(() => {
if (buttonHoverState.value === 'process' || buttonHoverState.value === 'refresh') { if (buttonHoverState.value === 'process' || buttonHoverState.value === 'refresh') {
return 'circle' return 'circle task-execute-btn'
} }
return agentsStore.agentRawPlan.data ? 'ellipse' : 'circle' return agentsStore.agentRawPlan.data ? 'ellipse task-execute-btn' : 'circle task-execute-btn'
}) })
const refreshBtnClass = computed(() => { const refreshBtnClass = computed(() => {
@@ -1295,7 +1295,6 @@ defineExpose({
<template #reference> <template #reference>
<el-button <el-button
:class="executeBtnClass" :class="executeBtnClass"
:color="variables.tertiary"
:title="isStreaming ? (isPaused ? '点击继续执行' : '点击暂停执行') : executeBtnTitle" :title="isStreaming ? (isPaused ? '点击继续执行' : '点击暂停执行') : executeBtnTitle"
:disabled=" :disabled="
!agentsStore.agentRawPlan.data || (!isStreaming && loading) || isButtonLoading !agentsStore.agentRawPlan.data || (!isStreaming && loading) || isButtonLoading
@@ -1338,7 +1337,7 @@ defineExpose({
/> />
<!-- 默认状态显示 action 图标 --> <!-- 默认状态显示 action 图标 -->
<svg-icon v-else icon-class="action" /> <svg-icon v-else icon-class="action" color="var(--color-text-title-header)" />
<span v-if="showExecuteText && !isStreaming" class="btn-text">任务执行</span> <span v-if="showExecuteText && !isStreaming" class="btn-text">任务执行</span>
<span v-else-if="isStreaming && isPaused" class="btn-text">继续执行</span> <span v-else-if="isStreaming && isPaused" class="btn-text">继续执行</span>
@@ -1773,15 +1772,23 @@ defineExpose({
animation: fadeInRight 0.3s ease forwards; animation: fadeInRight 0.3s ease forwards;
} }
} }
}
// .btn-text { // 任务执行按钮渐变样式
// display: inline-block !important; .task-execute-btn {
// font-size: 14px; background: linear-gradient(to right, #00c5d1, #305ab3) !important;
// font-weight: 500; background-color: transparent !important;
// margin-left: 4px; background-origin: border-box !important;
// opacity: 1; background-clip: border-box !important;
// animation: fadeIn 0.3s ease forwards; border-radius: 20px !important;
// } transition: background 0.3s ease !important;
&:hover {
background: linear-gradient(to right, #6431b4, #315ab4) !important;
background-color: transparent !important;
background-origin: border-box !important;
background-clip: border-box !important;
}
} }
@keyframes fadeInLeft { @keyframes fadeInLeft {

View File

@@ -2,6 +2,8 @@
import AgentRepo from './AgentRepo/index.vue' import AgentRepo from './AgentRepo/index.vue'
import TaskSyllabus from './TaskSyllabus/index.vue' import TaskSyllabus from './TaskSyllabus/index.vue'
import TaskResult from './TaskResult/index.vue' import TaskResult from './TaskResult/index.vue'
import HistoryList from './HistoryList/index.vue'
import SvgIcon from '@/components/SvgIcon/index.vue'
import { Jsplumb } from './utils.ts' import { Jsplumb } from './utils.ts'
import { type IRawStepTask, useAgentsStore } from '@/stores' import { type IRawStepTask, useAgentsStore } from '@/stores'
import { BezierConnector } from '@jsplumb/browser-ui' import { BezierConnector } from '@jsplumb/browser-ui'
@@ -12,8 +14,29 @@ const props = defineProps<{
TaskID?: string // 任务唯一标识,用于写入数据库 TaskID?: string // 任务唯一标识,用于写入数据库
}>() }>()
// 定义事件
const emit = defineEmits<{
(e: 'restore', plan: any): void
}>()
const agentsStore = useAgentsStore() const agentsStore = useAgentsStore()
// 历史记录弹窗控制
const historyDialogVisible = ref(false)
// 打开历史记录弹窗
const openHistoryDialog = () => {
historyDialogVisible.value = true
}
// 处理历史任务恢复
const handleRestorePlan = (plan: any) => {
// 关闭弹窗
historyDialogVisible.value = false
// 发送恢复事件给父组件
emit('restore', plan)
}
// 智能体库 // 智能体库
const agentRepoJsplumb = new Jsplumb('task-template', { const agentRepoJsplumb = new Jsplumb('task-template', {
connector: { connector: {
@@ -78,6 +101,7 @@ function clear() {
} }
defineExpose({ defineExpose({
openHistoryDialog,
changeTask, changeTask,
resetAgentRepoLine, resetAgentRepoLine,
clear clear
@@ -111,6 +135,22 @@ defineExpose({
@set-current-task="handleTaskResultCurrentTask" @set-current-task="handleTaskResultCurrentTask"
/> />
</div> </div>
<!-- 历史记录弹窗 -->
<div v-if="historyDialogVisible" class="history-drawer">
<div class="history-drawer-content">
<div class="history-drawer-header">
<span class="history-drawer-title">历史记录</span>
<button class="history-drawer-close" @click="historyDialogVisible = false">
<SvgIcon icon-class="close" size="20px" />
</button>
</div>
<div class="h-[1px] w-full bg-[var(--color-border-separate)] my-[8px]"></div>
<div class="history-drawer-body">
<HistoryList @restore="handleRestorePlan" />
</div>
</div>
</div>
</div> </div>
</template> </template>
@@ -125,4 +165,86 @@ defineExpose({
padding-bottom: 20px; padding-bottom: 20px;
} }
} }
// 历史记录 Drawer 样式
:deep(.history-drawer) {
// 强制使用绝对定位,相对于父容器
position: absolute !important;
top: 0 !important;
bottom: 0 !important;
z-index: 1000;
// 高度由 top/bottom 控制
height: auto !important;
background-color: var(--color-bg-three);
border: 1px solid var(--color-card-border-three);
border-radius: 24px;
box-shadow: var(--color-card-shadow-three);
padding-top: 20px;
padding-bottom: 20px;
// 动画
animation: drawer-slide-in 0.5s ease-out;
// 头部
.history-drawer-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 16px;
background-color: var(--color-bg-three);
}
// 标题
.history-drawer-title {
color: var(--color-text-primary);
font-size: 16px;
font-weight: 600;
padding-left: 8px;
}
// 关闭按钮
.history-drawer-close {
background: none;
border: none;
cursor: pointer;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
color: var(--color-text-regular);
transition: transform 0.3s ease;
&:hover {
transform: rotate(360deg);
}
}
:deep(.el-drawer__body) {
background-color: var(--color-bg-three);
padding: 0;
opacity: 0;
animation: drawer-fade-in 0.5s ease-out forwards;
}
}
@keyframes drawer-slide-in {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
@keyframes drawer-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style> </style>

View File

@@ -3,8 +3,12 @@ import Task from './Task.vue'
import TaskTemplate from './TaskTemplate/index.vue' import TaskTemplate from './TaskTemplate/index.vue'
import { nextTick, ref } from 'vue' import { nextTick, ref } from 'vue'
const taskRef = ref<{ currentTaskID: string }>() const taskRef = ref<{ currentTaskID: string; restoreFromHistory: (plan: any) => void }>()
const taskTemplateRef = ref<{ changeTask: () => void; clear: () => void }>() const taskTemplateRef = ref<{
changeTask: () => void
clear: () => void
openHistoryDialog: () => void
}>()
function handleSearch() { function handleSearch() {
nextTick(() => { nextTick(() => {
@@ -17,16 +21,34 @@ function getTaskID(): string {
return taskRef.value?.currentTaskID || '' return taskRef.value?.currentTaskID || ''
} }
// 打开历史记录弹窗
function openHistoryDialog() {
taskTemplateRef.value?.openHistoryDialog()
}
// 处理历史任务恢复
function handleRestorePlan(plan: any) {
taskRef.value?.restoreFromHistory(plan)
}
defineExpose({ defineExpose({
getTaskID getTaskID,
openHistoryDialog
}) })
</script> </script>
<template> <template>
<div class="p-[24px] h-[calc(100%-60px)]"> <div class="p-[24px] h-[calc(100%-60px)]">
<Task ref="taskRef" @search="handleSearch" @search-start="taskTemplateRef?.clear" /> <Task
<TaskTemplate ref="taskTemplateRef" :TaskID="taskRef?.currentTaskID" /> ref="taskRef"
@search="handleSearch"
@search-start="taskTemplateRef?.clear"
@open-history="openHistoryDialog"
/>
<TaskTemplate
ref="taskTemplateRef"
:TaskID="taskRef?.currentTaskID"
@restore="handleRestorePlan"
/>
</div> </div>
</template> </template>
<style scoped lang="scss"></style>

View File

@@ -7,15 +7,16 @@ import PlanModification from './components/Main/TaskTemplate/TaskSyllabus/Branch
import { useAgentsStore } from '@/stores/modules/agents' import { useAgentsStore } from '@/stores/modules/agents'
import PlanTask from './components/Main/TaskTemplate/TaskProcess/components/PlanTask.vue' import PlanTask from './components/Main/TaskTemplate/TaskProcess/components/PlanTask.vue'
import AgentAllocation from './components/Main/TaskTemplate/TaskSyllabus/components/AgentAllocation.vue' import AgentAllocation from './components/Main/TaskTemplate/TaskSyllabus/components/AgentAllocation.vue'
import { Clock } from '@element-plus/icons-vue'
const isDarkMode = ref(false) const isDarkMode = ref(false)
const agentsStore = useAgentsStore() const agentsStore = useAgentsStore()
const mainRef = ref<{ currentTaskID: string } | null>(null) const mainRef = ref<{ currentTaskID: string; openHistoryDialog: () => void } | null>(null)
// 获取当前的 taskId // 打开历史记录
const currentTaskId = computed(() => { const openHistory = () => {
return mainRef.value?.currentTaskID || '' mainRef.value?.openHistoryDialog()
}) }
onMounted(() => { onMounted(() => {
// 等待 Main 组件挂载后获取 taskId // 等待 Main 组件挂载后获取 taskId
@@ -76,7 +77,23 @@ onMounted(() => {
<template> <template>
<div class="h-full relative"> <div class="h-full relative">
<!-- 主题切换按钮 - 放置在页面右上角 --> <!-- 左上角按钮组 -->
<div class="absolute top-4 left-4 z-50 flex gap-3">
<!-- 历史记录按钮 -->
<button
@click="openHistory"
class="w-10 h-10 rounded-full transition-all duration-300 flex items-center justify-center"
:class="{
'bg-[#ebebeb] hover:bg-[var(--color-bg-hover)]': !isDarkMode,
'bg-[#0e131c] hover:bg-[var(--color-bg-hover)]': isDarkMode
}"
title="历史记录"
>
<el-icon size="20px"><Clock /></el-icon>
</button>
</div>
<!-- 右上角按钮 -->
<div class="absolute top-4 right-4 z-50"> <div class="absolute top-4 right-4 z-50">
<button <button
@click="toggleTheme" @click="toggleTheme"
@@ -107,7 +124,7 @@ onMounted(() => {
</div> </div>
<Header /> <Header />
<Main /> <Main ref="mainRef" />
<FloatWindow <FloatWindow
v-if="agentsStore.planModificationWindow" v-if="agentsStore.planModificationWindow"

View File

@@ -114,6 +114,10 @@
--color-tab-text: #FDFFFF; --color-tab-text: #FDFFFF;
// 智能体标签文字颜色(未选中) // 智能体标签文字颜色(未选中)
--color-tab-text-inactive: rgba(253, 255, 255, 0.4); --color-tab-text-inactive: rgba(253, 255, 255, 0.4);
// 任务编辑框悬浮边框颜色
--color-task-edit-hover-border: #D6D6D6;
// 编辑图标背景色
--color-edit-icon-bg: rgba(255, 255, 255, 0.5);
} }
@@ -233,6 +237,10 @@ html.dark {
--color-tab-text: #FDFFFF; --color-tab-text: #FDFFFF;
// 智能体标签文字颜色(未选中) // 智能体标签文字颜色(未选中)
--color-tab-text-inactive: rgba(253, 255, 255, 0.4); --color-tab-text-inactive: rgba(253, 255, 255, 0.4);
// 任务编辑框悬浮边框颜色
--color-task-edit-hover-border: #ADA9A7;
// 编辑图标背景色
--color-edit-icon-bg: rgba(255, 255, 255, 0.5);
--el-fill-color-blank: transparent !important; --el-fill-color-blank: transparent !important;
} }