From 71feae4bbd9579a100a9c1d63e37b60f08bf7d5a Mon Sep 17 00:00:00 2001 From: lixiaolong <702156524@qq.com> Date: Wed, 19 Aug 2026 15:00:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(flow):=20=E6=B7=BB=E5=8A=A0=E9=80=9A?= =?UTF-8?q?=E7=94=A8=E5=9B=BE=E7=89=87=E5=88=86=E6=9E=90=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=B9=B6=E4=BC=98=E5=8C=96=E5=AA=92=E4=BD=93=E5=88=86=E6=9E=90?= =?UTF-8?q?=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增通用图片分析节点类型,支持按提示词分析图片并返回指定内容 - 添加详细的图片分析参数配置界面,包括图片源选择、提示词输入等功能 - 实现图片分析节点的输出参数定义和处理逻辑 - 更新视频分析节点,增加详细输出选项和结果字段 - 重构错误状态恢复功能,更名为运行状态恢复并更新相关UI - 添加图片分析结果的日志展示支持 - 优化流程资源配置,增加图片分析相关的常量定义 - 更新单节点执行参数构建逻辑,适配新的图片分析功能 --- src/api/device/system.js | 11 + src/utils/flowResources.js | 25 ++ src/views/flow/components/ParamsDrawer.vue | 2 + .../flow/components/TaskExecutionLog.vue | 20 +- .../components/params/ImageAnalysisParams.vue | 274 ++++++++++++++++++ .../components/params/MediaAnalysisParams.vue | 23 ++ src/views/flow/config.js | 23 ++ src/views/flow/index.vue | 133 ++++++--- .../intelligenceTest/runningTask/task-log.vue | 2 + 9 files changed, 467 insertions(+), 46 deletions(-) create mode 100644 src/views/flow/components/params/ImageAnalysisParams.vue diff --git a/src/api/device/system.js b/src/api/device/system.js index 18508dc..55356a9 100644 --- a/src/api/device/system.js +++ b/src/api/device/system.js @@ -8,6 +8,17 @@ export function getSafetyState(robotId) { }) } +export function restoreOperationalState(data) { + return request({ + url: '/api/system/restoreOperationalState', + method: 'post', + data, + }) +} + +/** + * @deprecated Use restoreOperationalState. + */ export function recoverSafetyState(data) { return request({ url: '/api/system/recoverSafetyState', diff --git a/src/utils/flowResources.js b/src/utils/flowResources.js index a761b30..b6f8a98 100644 --- a/src/utils/flowResources.js +++ b/src/utils/flowResources.js @@ -4,6 +4,10 @@ export const FLOW_SCHEMA_VERSION = 2 export const DEFAULT_ROLE_KEY = 'default_executor' export const FLOW_NODE_RESOURCE_OWNER = 'FLOW_NODE' 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 }, +]) const DEVICE_KIND_META = Object.freeze({ 1: { key: 'agv', name: '移动底盘' }, @@ -98,6 +102,27 @@ 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 + if (node.type === 'imageAnalysis') { + const properties = node.properties || (node.properties = {}) + properties.action = 'IMAGE_ANALYZE' + properties.nodeType = 'LLM' + properties.outputType = 'json' + properties.outputParams = clone(IMAGE_ANALYSIS_OUTPUT_PARAMS) + } + if (node.properties?.action === 'VIDEO_ANALYZE') { + const outputParams = Array.isArray(node.properties.outputParams) + ? node.properties.outputParams + : [] + if (!outputParams.some((param) => param?.name === 'result')) { + outputParams.unshift({ name: 'result', type: 'string', desc: '分析结果', disabled: true }) + } + if (!outputParams.some((param) => param?.name === 'detailedOutput')) { + outputParams.push({ + name: 'detailedOutput', type: 'boolean', desc: '是否返回详细事件证据', disabled: true, + }) + } + node.properties.outputParams = outputParams + } }) const start = getStartNode(graph) if (!start) return graph diff --git a/src/views/flow/components/ParamsDrawer.vue b/src/views/flow/components/ParamsDrawer.vue index 6b307bf..c33a11f 100644 --- a/src/views/flow/components/ParamsDrawer.vue +++ b/src/views/flow/components/ParamsDrawer.vue @@ -40,6 +40,7 @@ import DeviceUniversal from './params/DeviceUniversal.vue' import SwitchNodeParams from './params/SwitchNodeParams.vue' import ActionQueueParams from './params/ActionQueueParams.vue' import MediaAnalysisParams from './params/MediaAnalysisParams.vue' +import ImageAnalysisParams from './params/ImageAnalysisParams.vue' const props = defineProps({ drawer: Boolean, @@ -68,6 +69,7 @@ const currentComponent = computed(() => { if (['deviceUniversal', 'device_universal'].includes(type)) return DeviceUniversal if (type === 'actionQueue') return ActionQueueParams if (type === 'mediaAnalysis') return MediaAnalysisParams + if (type === 'imageAnalysis') return ImageAnalysisParams if (type === 'branch') return SwitchNodeParams return null }) diff --git a/src/views/flow/components/TaskExecutionLog.vue b/src/views/flow/components/TaskExecutionLog.vue index 00e1846..c088627 100644 --- a/src/views/flow/components/TaskExecutionLog.vue +++ b/src/views/flow/components/TaskExecutionLog.vue @@ -178,6 +178,7 @@ const analysisOf = (log) => { const profileCode = String(output.profileCode || '').toLowerCase() const isAudio = analysisType.includes('AUDIO') || profileCode.includes('audio') || Number(log.logType) === 4 const isVideo = analysisType.includes('VIDEO') || profileCode.includes('video') || Number(log.logType) === 3 + const isImage = analysisType.includes('IMAGE') || profileCode.includes('image') // 仅有媒体类型还不足以展示分析面板,至少要存在一个分析状态或业务结果字段。 const hasResult = Boolean( @@ -188,11 +189,13 @@ const analysisOf = (log) => { || output.labelName || output.summary || output.conclusion + || output.resultText + || output.result !== undefined || output.passed !== undefined ) // 非分析日志也缓存 null,避免之后每次渲染都重复执行同一套识别逻辑。 - if (!hasResult || (!isAudio && !isVideo)) { + if (!hasResult || (!isAudio && !isVideo && !isImage)) { analysisCache.set(log, null) return null } @@ -223,9 +226,9 @@ const analysisOf = (log) => { // 它与已经结构化展示的分析字段一起从“详情”中移除,避免用户看到重复且难以理解的底层上下文。 ;[ 'analysisStatus', 'analysisType', 'profileCode', 'analysisModel', 'analysisTimingMs', - 'mediaUrl', 'audioUrl', 'videoUrl', 'imageUrl', 'label', 'labelName', 'matched', + 'mediaUrl', 'mediaUrls', 'audioUrl', 'videoUrl', 'imageUrl', 'label', 'labelName', 'matched', 'confidence', 'passed', 'summary', 'conclusion', 'events', '_executionContext', - 'expectedLabel', 'expectedLabelName', 'decisionReason', + 'expectedLabel', 'expectedLabelName', 'decisionReason', 'resultText', 'result', ].forEach((key) => delete detailSource[key]) // 所有已知字段删除后还有剩余内容时,才生成详情 JSON,避免显示空对象。 @@ -233,7 +236,7 @@ const analysisOf = (log) => { // 统一生成展示模型,模板不需要了解各版本日志字段和状态枚举的差异。 const result = { - title: isAudio ? '声音类型检测' : '视频分析', + title: isAudio ? '声音类型检测' : isImage ? '通用图片分析' : '视频分析', statusLabel, // 分析失败或业务判断未通过使用红色;仅匹配失败使用警告色,其余使用成功色。 @@ -249,7 +252,10 @@ const analysisOf = (log) => { matchLabel: isAudio ? '' : matched === true ? '匹配' : matched === false ? '未匹配' : '', confidence, passedLabel: passed === true ? '通过' : passed === false ? '未通过' : '', - summary: output.decisionReason || output.summary || output.conclusion || '', + summary: output.summary || output.conclusion || output.resultText + || (typeof output.result === 'string' ? output.result : '') + || (/^[A-Z_]+$/.test(String(output.decisionReason || '')) ? '' : output.decisionReason) + || '', isAudio, events: Array.isArray(output.events) ? output.events.slice(0, 20).map((event) => typeof event === 'string' ? event : JSON.stringify(event)) @@ -289,6 +295,10 @@ const mediaOf = (log) => { // mediaUrl 本身没有类型,必须结合分析类型或 profileCode 后才能作为视频/音频加入。 if (analysisType.includes('VIDEO') || profileCode.includes('video')) addMedia(result, seen, 'video', output.mediaUrl) if (analysisType.includes('AUDIO') || profileCode.includes('audio')) addMedia(result, seen, 'audio', output.mediaUrl) + if (analysisType.includes('IMAGE') || profileCode.includes('image')) { + addMedia(result, seen, 'image', output.mediaUrls) + addMedia(result, seen, 'image', output.mediaUrl) + } addMedia(result, seen, 'image', log.screenshotUrl) addMedia(result, seen, 'video', log.videoUrl) diff --git a/src/views/flow/components/params/ImageAnalysisParams.vue b/src/views/flow/components/params/ImageAnalysisParams.vue new file mode 100644 index 0000000..9c1d4df --- /dev/null +++ b/src/views/flow/components/params/ImageAnalysisParams.vue @@ -0,0 +1,274 @@ + + + + + diff --git a/src/views/flow/components/params/MediaAnalysisParams.vue b/src/views/flow/components/params/MediaAnalysisParams.vue index e8674c8..d3fd061 100644 --- a/src/views/flow/components/params/MediaAnalysisParams.vue +++ b/src/views/flow/components/params/MediaAnalysisParams.vue @@ -100,6 +100,16 @@
{{ modeDescription }}
+ + +
精简结果响应更快;需要逐步事件和时间范围时再启用详细输出。
+
+