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 54e9462..4f28e38 100644 --- a/src/utils/flowResources.js +++ b/src/utils/flowResources.js @@ -8,6 +8,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({ @@ -104,6 +113,21 @@ export const normalizeFlowResourceSchema = (sourceGraph) => { 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' diff --git a/src/views/flow/components/TaskExecutionLog.vue b/src/views/flow/components/TaskExecutionLog.vue index 2e08500..ac73bba 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 表示该日志不是可展示的音视频分析结果;否则返回状态、模型、置信度、结论和扩展详情等字段。 @@ -424,6 +451,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 67e3156..9540b51 100644 --- a/src/views/flow/components/params/ImageAnalysisParams.vue +++ b/src/views/flow/components/params/ImageAnalysisParams.vue @@ -10,7 +10,11 @@
- + + + + + @@ -25,7 +29,7 @@ @visible-change="loadQuoteOptions" @change="handleQuoteChange" /> -
上游输出为图片数组时,将按原顺序全部分析,最多 12 张。
+
上游输出为图片数组时,将按原顺序全部分析,最多 12 张。
@@ -33,27 +37,73 @@ v-model="formData.imageUrlsText" type="textarea" :rows="4" - placeholder="每行一个 HTTP/HTTPS 图片地址,最多 12 张" + :placeholder="isLocationMethod ? '输入一张目标图片的 HTTP/HTTPS 地址' : '每行一个 HTTP/HTTPS 图片地址,最多 12 张'" /> - + - + - - + + +
开启后分析在后台并行执行,流程结束前统一等待分析结果。
+
+ + + + + + + + + @@ -80,7 +130,7 @@ - + @@ -100,7 +150,7 @@