Przeglądaj źródła

fix: 修改节点运行日志获取

Co-authored-by: Copilot <copilot@github.com>
jiaxing.liao 6 dni temu
rodzic
commit
27cf9215fa

+ 1 - 1
apps/web/src/features/RunWorkflow/index.vue

@@ -401,7 +401,7 @@ const handleRunWorkflow = async () => {
 
 		const agentRunnerKey = response?.result
 		if (agentRunnerKey) {
-			runnerStore.startRunner(agentRunnerKey)
+			runnerStore.startRunner(agentRunnerKey, startNode.value.id)
 			emit('run-started', startNode.value.id)
 			return
 		}

+ 122 - 2
apps/web/src/features/setter/NodeLog.vue

@@ -4,6 +4,7 @@ import { agent } from '@repo/api-service'
 import type { IWorkflowNode } from '@repo/workflow'
 
 import { useI18n } from '@/composables/useI18n'
+import { useRunnerStore, type RunnerNodeState } from '@/store/modules/runner.store'
 
 interface Props {
 	node: IWorkflowNode | undefined
@@ -16,11 +17,101 @@ const props = withDefaults(defineProps<Props>(), {
 	active: false
 })
 const { t } = useI18n()
+const runnerStore = useRunnerStore()
 const loading = ref(false)
 const errorMessage = ref('')
 const nodeLog = ref<NodeLastRunnerLog | null>(null)
 let latestRequestId = 0
 
+const toNodeLastRunnerLog = (node: RunnerNodeState): NodeLastRunnerLog => {
+	const track = node.track || null
+	const isSuccess =
+		typeof track?.is_success === 'boolean'
+			? track.is_success
+			: node.status === 'success'
+
+	return {
+		appAgentId: '',
+		appAgentNodeId: node.nodeId,
+		appAgentRunnerId: runnerStore.currentRunnerKey || '',
+		creationTime: node.lastUpdateTime || '',
+		errorMsg: node.status === 'failed' ? String(track?.errorMsg || track?.error || '') : '',
+		id: '',
+		isDeleted: false,
+		raw: track
+			? {
+					...track,
+					agent_node_id: track.agent_node_id || node.nodeId,
+					is_success: isSuccess,
+					node_name: track.node_name || node.nodeName,
+					node_type: track.node_type || node.nodeType
+				}
+			: null,
+		status: node.status,
+		updateTime: node.lastUpdateTime || '',
+		useTime: Number(track?.use_time ?? track?.useTime ?? 0)
+	} as NodeLastRunnerLog
+}
+
+const findRunnerNodeState = (nodeId: string) => {
+	const currentExecution = runnerStore.currentRunnerKey
+		? runnerStore.executions.find((item) => item.runnerKey === runnerStore.currentRunnerKey)
+		: null
+
+	return (
+		currentExecution?.nodes.find((item) => item.nodeId === nodeId) ||
+		runnerStore.nodes.find((item) => item.nodeId === nodeId) ||
+		runnerStore.executions.find((execution) =>
+			execution.nodes.some((item) => item.nodeId === nodeId)
+		)?.nodes.find((item) => item.nodeId === nodeId) ||
+		null
+	)
+}
+
+const isCurrentNodeRunning = (nodeId: string) => {
+	const runnerRunning = runnerStore.status === 'connecting' || runnerStore.status === 'running'
+	if (!runnerRunning) return false
+
+	const runnerNodeState = findRunnerNodeState(nodeId)
+	if (runnerNodeState) return runnerNodeState.status === 'running'
+
+	return runnerStore.currentStartNodeId === nodeId
+}
+
+const createRunningNodeLog = (nodeId: string): NodeLastRunnerLog =>
+	({
+		appAgentId: '',
+		appAgentNodeId: nodeId,
+		appAgentRunnerId: runnerStore.currentRunnerKey || '',
+		creationTime: '',
+		errorMsg: '',
+		id: '',
+		isDeleted: false,
+		raw: null,
+		status: 'running',
+		updateTime: '',
+		useTime: 0
+	}) as NodeLastRunnerLog
+
+const runnerNodesVersion = computed(() =>
+	runnerStore.nodes
+		.map((item) => `${item.nodeId}:${item.status}:${item.lastUpdateTime || ''}`)
+		.join('|')
+)
+
+const runnerExecutionsVersion = computed(() =>
+	runnerStore.executions
+		.map((execution) =>
+			execution.nodes
+				.map(
+					(item) =>
+						`${execution.runnerKey}:${item.nodeId}:${item.status}:${item.lastUpdateTime || ''}`
+				)
+				.join(',')
+		)
+		.join('|')
+)
+
 const loadNodeLog = async (nodeId?: string) => {
 	if (!nodeId) {
 		latestRequestId += 1
@@ -31,6 +122,21 @@ const loadNodeLog = async (nodeId?: string) => {
 	}
 
 	const requestId = ++latestRequestId
+	if (isCurrentNodeRunning(nodeId)) {
+		nodeLog.value = createRunningNodeLog(nodeId)
+		errorMessage.value = ''
+		loading.value = false
+		return
+	}
+
+	const runnerNodeState = findRunnerNodeState(nodeId)
+	if (runnerNodeState) {
+		nodeLog.value = toNodeLastRunnerLog(runnerNodeState)
+		errorMessage.value = ''
+		loading.value = false
+		return
+	}
+
 	loading.value = true
 	errorMessage.value = ''
 	nodeLog.value = null
@@ -54,7 +160,16 @@ const loadNodeLog = async (nodeId?: string) => {
 }
 
 watch(
-	() => [props.node?.id, props.active] as const,
+	() =>
+		[
+			props.node?.id,
+			props.active,
+			runnerStore.currentRunnerKey,
+			runnerStore.currentStartNodeId,
+			runnerStore.status,
+			runnerNodesVersion.value,
+			runnerExecutionsVersion.value
+		] as const,
 	([nodeId, active]) => {
 		if (!nodeId) {
 			loadNodeLog()
@@ -80,7 +195,12 @@ const normalizedStatus = computed(() => {
 })
 
 const hasLog = computed(
-	() => !!nodeLog.value && (!!nodeLog.value.raw || !!nodeLog.value.updateTime || !!nodeLog.value.creationTime)
+	() =>
+		!!nodeLog.value &&
+		(!!nodeLog.value.raw ||
+			!!nodeLog.value.updateTime ||
+			!!nodeLog.value.creationTime ||
+			normalizedStatus.value !== 'idle')
 )
 
 const statusText = computed(() => {

+ 1 - 1
apps/web/src/features/setter/index.vue

@@ -70,7 +70,7 @@ const name = ref(node.value?.name || '')
 const remark = ref(node.value?.remark || '')
 const nodeVars = ref<NodeVar[]>([])
 const currentTab = ref<'setting' | 'last-run'>(props.activeTab || 'setting')
-const MIN_DRAWER_WIDTH = 420
+const MIN_DRAWER_WIDTH = 470
 const viewportWidth = ref(typeof window !== 'undefined' ? window.innerWidth : 1440)
 const drawerWidth = ref(MIN_DRAWER_WIDTH)
 const resizeState = reactive({

+ 1 - 1
apps/web/src/nodes/_base/condition/index.vue

@@ -9,7 +9,7 @@
 				class="absolute right-1 top-1/2 flex h-[21px] -translate-y-1/2 cursor-pointer select-none items-center rounded-md border-solid border-[0.5px] border-gray-200 px-1 text-[10px] font-semibold shadow-xs"
 				@click="toggleOperator"
 			>
-				{{ modelOperator === 'and' ? 'and' : 'or' }}
+				{{ modelOperator === 'and' ? 'AND' : 'OR' }}
 				<Icon icon="lucide:refresh-ccw" />
 			</div>
 		</div>

+ 5 - 1
apps/web/src/store/modules/runner.store.ts

@@ -110,6 +110,7 @@ const AGENT_RUNNER_WS_BASE = `wss://${import.meta.env.VITE_BASE_URL}/api/ws/agen
 
 export const useRunnerStore = defineStore('runner', () => {
 	const currentRunnerKey = ref<string | null>(null)
+	const currentStartNodeId = ref<string | null>(null)
 	const status = ref<RunnerStatus>('idle')
 	const errorMsg = ref<string | null>(null)
 	const connected = ref(false)
@@ -378,13 +379,14 @@ export const useRunnerStore = defineStore('runner', () => {
 		}, 10000)
 	}
 
-	const startRunner = (runnerKey: string) => {
+	const startRunner = (runnerKey: string, startNodeId?: string) => {
 		if (!runnerKey) return
 
 		closeSocket()
 		resetState()
 
 		currentRunnerKey.value = runnerKey
+		currentStartNodeId.value = startNodeId || null
 		status.value = 'connecting'
 
 		executions.value.unshift({
@@ -457,11 +459,13 @@ export const useRunnerStore = defineStore('runner', () => {
 	const resetRunner = () => {
 		closeSocket()
 		currentRunnerKey.value = null
+		currentStartNodeId.value = null
 		resetState()
 	}
 
 	return {
 		currentRunnerKey: readonly(currentRunnerKey),
+		currentStartNodeId: readonly(currentStartNodeId),
 		status,
 		errorMsg,
 		connected,

+ 34 - 24
apps/web/src/views/WorkflowOrchestration.vue

@@ -19,14 +19,9 @@
 
 		<!-- 编排核心概念 -->
 		<div class="core-concepts">
-			<div
-				v-for="concept in concepts"
-				:key="concept.key"
-				class="concept-card"
-				:class="concept.key"
-			>
+			<div v-for="concept in concepts" :key="concept.key" class="concept-card" :class="concept.key">
 				<div class="concept-icon">
-					<SvgIcon :name="conceptIcons[concept.key]" size="24" />
+					<SvgIcon :name="conceptIcons[concept.key] as string" size="24" />
 				</div>
 				<div class="concept-info">
 					<div class="concept-title">{{ concept.title }}</div>
@@ -129,6 +124,13 @@
 			@close="closeTemplateModal"
 			@select="selectTemplate"
 		/>
+
+		<!-- 新建工作流弹窗 -->
+		<CreateWorkflowModal
+			:visible="createModalVisible"
+			@close="createModalVisible = false"
+			@success="handleCreateSuccess"
+		/>
 	</div>
 </template>
 
@@ -137,6 +139,7 @@ import { computed, ref } from 'vue'
 import { useRouter } from 'vue-router'
 import SvgIcon from '@/components/SvgIcon/index.vue'
 import TemplateModal from '@/components/TemplateModal/index.vue'
+import CreateWorkflowModal from '@/features/createModal/index.vue'
 import { useI18n } from '@/composables/useI18n'
 import { v4 } from 'uuid'
 
@@ -169,8 +172,7 @@ const recentWorkflows = computed(
 		}>) || []
 )
 const nodeGroups = computed(
-	() =>
-		(tm('pages.orchestration.nodeGroups') as Array<{ label: string; tags: string[] }>) || []
+	() => (tm('pages.orchestration.nodeGroups') as Array<{ label: string; tags: string[] }>) || []
 )
 const templates = computed(
 	() =>
@@ -182,8 +184,16 @@ const templates = computed(
 		}>) || []
 )
 
+// 新建工作流弹窗
+const createModalVisible = ref(false)
+
 const createWorkflow = () => {
-	const id = v4()
+	createModalVisible.value = true
+}
+
+// 新建工作流成功回调
+const handleCreateSuccess = (id: string) => {
+	createModalVisible.value = false
 	router.push(`/workflow/${id}`)
 }
 
@@ -279,12 +289,12 @@ const useTemplate = (id: string) => {
 			.concept-icon {
 				font-size: 32px;
 				margin-bottom: 12px;
-			color: var(--el-color-primary);
-			display: flex;
-			align-items: center;
-			justify-content: center;
-			width: 40px;
-			height: 40px;
+				color: var(--el-color-primary);
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				width: 40px;
+				height: 40px;
 				margin-bottom: 12px;
 
 				.concept-title {
@@ -408,16 +418,16 @@ const useTemplate = (id: string) => {
 			align-items: center;
 			padding: 12px;
 			border-radius: 10px;
-				border: 1px solid var(--border-light);
+			border: 1px solid var(--border-light);
 
-				.workflow-title {
-					font-weight: 600;
-					color: var(--text-primary);
-				}
+			.workflow-title {
+				font-weight: 600;
+				color: var(--text-primary);
+			}
 
-				.workflow-meta {
-					font-size: 12px;
-					color: var(--text-secondary);
+			.workflow-meta {
+				font-size: 12px;
+				color: var(--text-secondary);
 				align-items: center;
 			}
 		}

+ 2 - 2
apps/web/src/views/editor/NodeView.vue

@@ -551,7 +551,7 @@ const handleRunNode = async (id: string) => {
 			})
 			const agentRunnerKey = response?.result
 			if (agentRunnerKey) {
-				runnerStore.startRunner(agentRunnerKey)
+				runnerStore.startRunner(agentRunnerKey, id)
 				await openRunWorkflow(id, 'trigger')
 				return
 			}
@@ -577,7 +577,7 @@ const handleRunNode = async (id: string) => {
 		})
 		const agentRunnerKey = response?.result
 		if (agentRunnerKey) {
-			runnerStore.startRunner(agentRunnerKey)
+			runnerStore.startRunner(agentRunnerKey, id)
 			openSetter(id, 'last-run')
 		}
 	} catch (error) {