From 311c8c50822b2aa5770471f951712a43cae41704 Mon Sep 17 00:00:00 2001 From: lixiaolong <702156524@qq.com> Date: Thu, 20 Aug 2026 11:41:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(flow):=20=E5=A2=9E=E5=8A=A0AGV=E5=AF=BC?= =?UTF-8?q?=E8=88=AA=E9=98=BB=E5=A1=9E=E8=B6=85=E6=97=B6=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=92=8C=E5=9B=BE=E5=83=8F=E5=88=86=E6=9E=90=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在AGV导航动作中添加blockedTimeoutMs参数配置,默认值为60000毫秒 - 实现图像分析多种模式:智能分析、描述找图、精确找图 - 增加图像分析异步执行开关功能 - 添加图像分析匹配结果相关输出参数:匹配分数、坐标、中心点等 - 完善图像分析参数验证和错误处理逻辑 - 在任务执行日志中增加失败原因显示功能 - 优化图像分析界面组件的交互和表单验证规则 --- src/utils/actionQueue.js | 2 + src/utils/flowResources.js | 25 ++- .../flow/components/TaskExecutionLog.vue | 45 +++++ .../components/params/ActionQueueParams.vue | 9 +- .../components/params/BasicNodeParams.vue | 2 +- .../components/params/ImageAnalysisParams.vue | 175 +++++++++++++++--- .../components/params/MediaAnalysisParams.vue | 16 ++ src/views/flow/config.js | 19 +- 8 files changed, 263 insertions(+), 30 deletions(-) diff --git a/src/utils/actionQueue.js b/src/utils/actionQueue.js index 9ea3c2d..33af39e 100644 --- a/src/utils/actionQueue.js +++ b/src/utils/actionQueue.js @@ -57,6 +57,7 @@ const nodeToQueueStep = (node) => { y: numberValue(params.y), theta: numberValue(params.theta), maxSpeed: numberValue(params.maxSpeed, 0.5), + blockedTimeoutMs: numberValue(params.blockedTimeoutMs, 60000), }, } } @@ -68,6 +69,7 @@ const nodeToQueueStep = (node) => { params: { stationId: params.stationId ?? '', maxSpeed: numberValue(params.maxSpeed, 0.5), + blockedTimeoutMs: numberValue(params.blockedTimeoutMs, 60000), }, } } diff --git a/src/utils/flowResources.js b/src/utils/flowResources.js index b6f8a98..bee0fcf 100644 --- a/src/utils/flowResources.js +++ b/src/utils/flowResources.js @@ -7,6 +7,15 @@ export const USER_RESOURCE_OWNER = 'USER' export const IMAGE_ANALYSIS_OUTPUT_PARAMS = Object.freeze([ { name: 'result', type: 'object', desc: '按提示词返回的结果', children: [], disabled: true }, { name: 'resultText', type: 'string', desc: '模型返回的原始文本', disabled: true }, + { name: 'matched', type: 'boolean', desc: '找图是否匹配', disabled: true }, + { name: 'matchScore', type: 'number', desc: '匹配分数或定位置信度', disabled: true }, + { name: 'coordinates', type: 'array', desc: '原图坐标 [x1,y1,x2,y2]', children: [], disabled: true }, + { name: 'center', type: 'array', desc: '目标中心点 [x,y]', children: [], disabled: true }, + { name: 'matchedDescription', type: 'string', desc: '模型实际定位的目标描述', disabled: true }, + { name: 'refinementMethod', type: 'string', desc: '坐标精修方式', disabled: true }, + { name: 'matchCount', type: 'number', desc: '匹配项数量', disabled: true }, + { name: 'matches', type: 'array', desc: '全部匹配项', children: [], disabled: true }, + { name: 'selectedMatchIndex', type: 'number', desc: '当前选择的匹配序号', disabled: true }, ]) const DEVICE_KIND_META = Object.freeze({ @@ -102,8 +111,22 @@ export const normalizeFlowResourceSchema = (sourceGraph) => { graph.edges = Array.isArray(graph.edges) ? graph.edges : [] graph.nodes.forEach((node) => { node.type = LEGACY_NODE_TYPE_ALIASES[node.type] || node.type + const properties = node.properties || (node.properties = {}) + if (['AGV_MOVE_TO_POINT', 'AGV_MOVE_TO_STATION'].includes(properties.action)) { + properties.nodeParams = Array.isArray(properties.nodeParams) ? properties.nodeParams : [] + if (!properties.nodeParams.some((param) => param?.name === 'blockedTimeoutMs')) { + properties.nodeParams.push({ + name: 'blockedTimeoutMs', + type: 'input', + componentType: 'number', + input: 60000, + min: 1, + required: true, + disabled: true, + }) + } + } if (node.type === 'imageAnalysis') { - const properties = node.properties || (node.properties = {}) properties.action = 'IMAGE_ANALYZE' properties.nodeType = 'LLM' properties.outputType = 'json' diff --git a/src/views/flow/components/TaskExecutionLog.vue b/src/views/flow/components/TaskExecutionLog.vue index c088627..05b94ce 100644 --- a/src/views/flow/components/TaskExecutionLog.vue +++ b/src/views/flow/components/TaskExecutionLog.vue @@ -8,6 +8,11 @@
{{ log.logContent }}
+
+ 失败原因 + {{ errorOf(log) }} +
+
@@ -158,6 +163,28 @@ const addMedia = (result, seen, type, value) => { // 分析结果的计算相对复杂,使用 WeakMap 保证模板多次调用 analysisOf(log) 时只解析一次。 const analysisCache = new WeakMap() +const errorCache = new WeakMap() +const errorOf = (log) => { + if (!log || typeof log !== 'object') return '' + if (errorCache.has(log)) return errorCache.get(log) + const actualResult = parseObject(log.actualResult) + const extraInfo = parseObject(log.extraInfo) + const status = String(log.status ?? log.logStatus ?? '').trim().toLowerCase() + const failed = ['2', 'failed', 'failure', 'error'].includes(status) + const candidates = [ + log.errorMessage, + log.errorStack, + actualResult.errorMessage, + actualResult.error, + extraInfo.errorMessage, + extraInfo.error, + failed ? log.message : '', + ] + const result = candidates.find(value => typeof value === 'string' && value.trim())?.trim() || '' + errorCache.set(log, result) + return result +} + /** * 把日志中的音视频分析结果转换成模板直接使用的展示模型。 * 返回 null 表示该日志不是可展示的音视频分析结果;否则返回状态、模型、置信度、结论和扩展详情等字段。 @@ -422,6 +449,24 @@ onMounted(scrollToBottom) line-height: 20px; } +.task-log__error { + display: grid; + grid-template-columns: max-content minmax(0, 1fr); + gap: 8px; + margin-top: 7px; + padding: 8px 10px; + border-left: 3px solid var(--el-color-danger); + background: var(--el-color-danger-light-9); + color: var(--el-color-danger-dark-2); + font-size: 13px; + line-height: 19px; +} + +.task-log__error span { + min-width: 0; + overflow-wrap: anywhere; +} + /* 分析结果使用左侧强调线与普通日志正文区分,但保持在同一条日志的内容列中。 */ .task-log__analysis { margin-top: 9px; diff --git a/src/views/flow/components/params/ActionQueueParams.vue b/src/views/flow/components/params/ActionQueueParams.vue index 6571a2c..2fb9e98 100644 --- a/src/views/flow/components/params/ActionQueueParams.vue +++ b/src/views/flow/components/params/ActionQueueParams.vue @@ -186,23 +186,26 @@ const fieldsByType = { AGV_NAVIGATE_TO_POSE: [ ...['x', 'y', 'theta'].map((key) => ({ key, label: key === 'theta' ? '角度' : key.toUpperCase(), number: true })), { key: 'maxSpeed', label: '最大速度', number: true, min: 0, step: 0.01, required: false }, + { key: 'blockedTimeoutMs', label: '遇障超时(毫秒)', number: true, min: 1, step: 1000 }, ], AGV_NAVIGATE_TO_STATION: [ { key: 'stationId', label: '站点 ID' }, { key: 'maxSpeed', label: '最大速度', number: true, min: 0, step: 0.01, required: false }, + { key: 'blockedTimeoutMs', label: '遇障超时(毫秒)', number: true, min: 1, step: 1000 }, ], AGV_FOLLOW_PATH: [ { key: 'path', label: '路径段(JSON 数组)', multiline: true }, { key: 'maxSpeed', label: '最大速度', number: true, min: 0, step: 0.01, required: false }, + { key: 'blockedTimeoutMs', label: '遇障超时(毫秒)', number: true, min: 1, step: 1000 }, ], } const defaultParams = { DELAY: { durationMs: 1000 }, ARM_MOVE_J: { positions: '[0, 0, 0, 0, 0, 0]', velocity: 0.1, acceleration: 0.2 }, ARM_MOVE_L: { x: 0, y: 0, z: 0, rx: 0, ry: 0, rz: 0, frame: 'BASE', velocity: 2.1, acceleration: 2.2 }, - AGV_NAVIGATE_TO_POSE: { x: 0, y: 0, theta: 0, maxSpeed: 0.5 }, - AGV_NAVIGATE_TO_STATION: { stationId: '', maxSpeed: 0.5 }, - AGV_FOLLOW_PATH: { path: '[]', maxSpeed: 0.5 }, + AGV_NAVIGATE_TO_POSE: { x: 0, y: 0, theta: 0, maxSpeed: 0.5, blockedTimeoutMs: 60000 }, + AGV_NAVIGATE_TO_STATION: { stationId: '', maxSpeed: 0.5, blockedTimeoutMs: 60000 }, + AGV_FOLLOW_PATH: { path: '[]', maxSpeed: 0.5, blockedTimeoutMs: 60000 }, } const defaultTimeoutByType = { DELAY: 5000, diff --git a/src/views/flow/components/params/BasicNodeParams.vue b/src/views/flow/components/params/BasicNodeParams.vue index bda2f92..8814472 100644 --- a/src/views/flow/components/params/BasicNodeParams.vue +++ b/src/views/flow/components/params/BasicNodeParams.vue @@ -39,7 +39,7 @@ :rules="testRule(property)" :prop="`nodeParams.${index}.input`"> diff --git a/src/views/flow/components/params/ImageAnalysisParams.vue b/src/views/flow/components/params/ImageAnalysisParams.vue index 9c1d4df..8349d46 100644 --- a/src/views/flow/components/params/ImageAnalysisParams.vue +++ b/src/views/flow/components/params/ImageAnalysisParams.vue @@ -6,7 +6,11 @@
- + + + + + @@ -21,7 +25,7 @@ @visible-change="loadQuoteOptions" @change="handleQuoteChange" /> -
上游输出为图片数组时,将按原顺序全部分析,最多 12 张。
+
上游输出为图片数组时,将按原顺序全部分析,最多 12 张。
@@ -29,27 +33,73 @@ v-model="formData.imageUrlsText" type="textarea" :rows="4" - placeholder="每行一个 HTTP/HTTPS 图片地址,最多 12 张" + :placeholder="isLocationMethod ? '输入一张目标图片的 HTTP/HTTPS 地址' : '每行一个 HTTP/HTTPS 图片地址,最多 12 张'" /> - + - + - - + + +
开启后分析在后台并行执行,流程结束前统一等待分析结果。
+
+ + + + + + + + + @@ -76,7 +126,7 @@ - + @@ -96,7 +146,7 @@