feat: 添加音频校正功能,新增音频校正对话框和相关逻辑
This commit is contained in:
parent
c07c776f3a
commit
7eaec22372
@ -90,6 +90,14 @@ export function flowStop(instId) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function submitAudioCorrection(nodeInstId, data) {
|
||||||
|
return request({
|
||||||
|
url: `/flow/audio-correction/${encodeURIComponent(nodeInstId)}`,
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function getArmStatus(params) {
|
export function getArmStatus(params) {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/arm/getPose',
|
url: '/api/arm/getPose',
|
||||||
|
|||||||
8
src/views/flow/audioTypes.js
Normal file
8
src/views/flow/audioTypes.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export const soundTypeOptions = [
|
||||||
|
{ label: '开机', value: 'POWER_ON' },
|
||||||
|
{ label: '关机', value: 'POWER_OFF' },
|
||||||
|
{ label: '设防', value: 'ARMED' },
|
||||||
|
{ label: '解防', value: 'DISARMED' },
|
||||||
|
{ label: '鸣笛寻车', value: 'FIND_VEHICLE_HORN' },
|
||||||
|
{ label: '未知', value: 'UNKNOWN' },
|
||||||
|
]
|
||||||
162
src/views/flow/components/AudioCorrectionDialog.vue
Normal file
162
src/views/flow/components/AudioCorrectionDialog.vue
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
:model-value="props.modelValue"
|
||||||
|
title="声音类型人工判断"
|
||||||
|
width="480px"
|
||||||
|
append-to-body
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
destroy-on-close
|
||||||
|
@update:model-value="updateVisible"
|
||||||
|
>
|
||||||
|
<el-form ref="formRef" :model="form" :rules="rules" label-width="96px">
|
||||||
|
<el-form-item label="声音类型" prop="label">
|
||||||
|
<el-select
|
||||||
|
v-model="form.label"
|
||||||
|
class="audio-correction-dialog__field"
|
||||||
|
filterable
|
||||||
|
default-first-option
|
||||||
|
clearable
|
||||||
|
placeholder="请选择声音类型"
|
||||||
|
@change="handleTypeChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in soundTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注" prop="note">
|
||||||
|
<el-input
|
||||||
|
v-model="form.note"
|
||||||
|
type="textarea"
|
||||||
|
:rows="4"
|
||||||
|
maxlength="500"
|
||||||
|
show-word-limit
|
||||||
|
placeholder="请输入人工判断备注"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="close">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="submitting" @click="submit">保存</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { nextTick, reactive, ref, watch } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { submitAudioCorrection } from '@/api/flow/flow'
|
||||||
|
import { soundTypeOptions } from '../audioTypes.js'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: { type: Boolean, default: false },
|
||||||
|
nodeInstId: { type: [String, Number], default: '' },
|
||||||
|
value: { type: Object, default: () => ({}) },
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue', 'saved'])
|
||||||
|
const formRef = ref(null)
|
||||||
|
const submitting = ref(false)
|
||||||
|
const form = reactive({ label: '', labelName: '', note: '' })
|
||||||
|
const rules = {
|
||||||
|
label: [{ required: true, message: '请选择声音类型', trigger: 'change' }],
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseObject = (value) => {
|
||||||
|
if (!value) return {}
|
||||||
|
if (typeof value === 'object' && !Array.isArray(value)) return value
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value)
|
||||||
|
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
|
||||||
|
} catch {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
const value = parseObject(props.value)
|
||||||
|
const option = soundTypeOptions.find(item => item.value === value.label)
|
||||||
|
Object.assign(form, {
|
||||||
|
label: value.label || '',
|
||||||
|
labelName: value.labelName || option?.label || value.label || '',
|
||||||
|
note: value.correctionNote || value.note || '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => props.modelValue, (visible) => {
|
||||||
|
if (!visible) return
|
||||||
|
resetForm()
|
||||||
|
nextTick(() => formRef.value?.clearValidate())
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateVisible = (value) => emit('update:modelValue', value)
|
||||||
|
const close = () => {
|
||||||
|
if (!submitting.value) updateVisible(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTypeChange = (value) => {
|
||||||
|
const option = soundTypeOptions.find(item => item.value === value)
|
||||||
|
form.labelName = option?.label || value || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseCorrection = (response) => {
|
||||||
|
const root = parseObject(response?.data ?? response)
|
||||||
|
const data = parseObject(root.data)
|
||||||
|
const source = Object.keys(data).length ? data : root
|
||||||
|
const candidates = [
|
||||||
|
source.correction,
|
||||||
|
source.audioCorrection,
|
||||||
|
source.actualResult,
|
||||||
|
source.result,
|
||||||
|
source,
|
||||||
|
]
|
||||||
|
const fields = [
|
||||||
|
'label', 'labelName', 'note', 'correctionNote', 'confidence', 'matched',
|
||||||
|
'passed', 'decisionReason', 'analysisStatus', 'analysisTimingMs', 'summary',
|
||||||
|
'conclusion', 'manualCorrected', 'manualCorrection',
|
||||||
|
]
|
||||||
|
const candidate = candidates.map(parseObject).find(item => fields.some(field => item[field] !== undefined)) || {}
|
||||||
|
const correction = {}
|
||||||
|
fields.forEach((field) => {
|
||||||
|
if (candidate[field] !== undefined) correction[field] = candidate[field]
|
||||||
|
})
|
||||||
|
correction.label ??= form.label
|
||||||
|
correction.labelName ??= form.labelName
|
||||||
|
correction.note ??= form.note
|
||||||
|
correction.correctionNote ??= form.note
|
||||||
|
correction.manualCorrected = true
|
||||||
|
return correction
|
||||||
|
}
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
if (submitting.value || !props.nodeInstId) return
|
||||||
|
let valid = true
|
||||||
|
try {
|
||||||
|
valid = await formRef.value?.validate()
|
||||||
|
} catch {
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
if (!valid) return
|
||||||
|
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
const response = await submitAudioCorrection(props.nodeInstId, {
|
||||||
|
label: form.label,
|
||||||
|
labelName: form.labelName,
|
||||||
|
note: form.note,
|
||||||
|
})
|
||||||
|
emit('saved', responseCorrection(response))
|
||||||
|
ElMessage.success('人工判断已保存')
|
||||||
|
updateVisible(false)
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.audio-correction-dialog__field { width: 100%; }
|
||||||
|
</style>
|
||||||
@ -13,6 +13,9 @@
|
|||||||
</el-icon>
|
</el-icon>
|
||||||
<span class="node-state__text">{{ stateText }}</span>
|
<span class="node-state__text">{{ stateText }}</span>
|
||||||
<span class="node-state__runtime">{{ props.runtimes }} ms</span>
|
<span class="node-state__runtime">{{ props.runtimes }} ms</span>
|
||||||
|
<div v-if="slots.actions" class="node-state__actions" @mousedown.stop @click.stop>
|
||||||
|
<slot name="actions" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-popover
|
<el-popover
|
||||||
@ -117,6 +120,13 @@ defineExpose({ closePopover });
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.node-state__actions {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.node-state__result {
|
.node-state__result {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 26px;
|
height: 26px;
|
||||||
|
|||||||
@ -35,6 +35,16 @@
|
|||||||
<el-tag :type="analysisOf(log).tagType" size="small">
|
<el-tag :type="analysisOf(log).tagType" size="small">
|
||||||
{{ analysisOf(log).title }} · {{ analysisOf(log).statusLabel }}
|
{{ analysisOf(log).title }} · {{ analysisOf(log).statusLabel }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
|
<el-button
|
||||||
|
v-if="isAudioCorrectionAvailable(log)"
|
||||||
|
class="task-log__correction-button"
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
size="small"
|
||||||
|
@click="openAudioCorrection(log)"
|
||||||
|
>
|
||||||
|
{{ audioCorrectionStateOf(log) ? '重新判断' : '人工判断' }}
|
||||||
|
</el-button>
|
||||||
<span v-if="isDebugView && analysisOf(log).modelName" class="task-log__analysis-meta">
|
<span v-if="isDebugView && analysisOf(log).modelName" class="task-log__analysis-meta">
|
||||||
模型:{{ analysisOf(log).modelName }}
|
模型:{{ analysisOf(log).modelName }}
|
||||||
</span>
|
</span>
|
||||||
@ -120,12 +130,20 @@
|
|||||||
/>
|
/>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<AudioCorrectionDialog
|
||||||
|
v-model="audioCorrectionVisible"
|
||||||
|
:node-inst-id="audioCorrectionTarget ? nodeInstIdOf(audioCorrectionTarget) : ''"
|
||||||
|
:value="audioCorrectionTarget ? outputOf(audioCorrectionTarget) : {}"
|
||||||
|
@saved="handleAudioCorrectionSaved"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, nextTick, onMounted, ref, watch } from 'vue'
|
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
|
||||||
import { ArrowRight, Bottom } from '@element-plus/icons-vue'
|
import { ArrowRight, Bottom } from '@element-plus/icons-vue'
|
||||||
import { formatDate } from '@/utils/index'
|
import { formatDate } from '@/utils/index'
|
||||||
|
import AudioCorrectionDialog from './AudioCorrectionDialog.vue'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* list: 任务或流程节点产生的日志数组。组件只负责展示,不修改日志对象。
|
* list: 任务或流程节点产生的日志数组。组件只负责展示,不修改日志对象。
|
||||||
@ -167,12 +185,65 @@ const parseObject = (value) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const correctedOutputs = reactive(new Map())
|
||||||
|
|
||||||
const outputOf = (log) => ({
|
const outputOf = (log) => ({
|
||||||
...parseObject(log?.paramsOut),
|
...parseObject(log?.paramsOut),
|
||||||
...parseObject(log?.actualResult),
|
...parseObject(log?.actualResult),
|
||||||
...parseObject(log?.extraInfo),
|
...parseObject(log?.extraInfo),
|
||||||
|
...(correctedOutputs.get(log) || {}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const audioCorrectionVisible = ref(false)
|
||||||
|
const audioCorrectionTarget = ref(null)
|
||||||
|
|
||||||
|
const nodeInstIdOf = (log) => {
|
||||||
|
const output = outputOf(log)
|
||||||
|
return log?.nodeInstId
|
||||||
|
|| log?.nodeInstanceId
|
||||||
|
|| output.nodeInstId
|
||||||
|
|| output.nodeInstanceId
|
||||||
|
|| log?.nodeId
|
||||||
|
|| output.nodeId
|
||||||
|
|| log?.id
|
||||||
|
|| ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const audioCorrectionStateOf = (log) => Boolean(
|
||||||
|
outputOf(log).manualCorrected
|
||||||
|
|| outputOf(log).manualCorrection
|
||||||
|
|| outputOf(log).correctionNote,
|
||||||
|
)
|
||||||
|
|
||||||
|
const isAudioCorrectionAvailable = (log) => {
|
||||||
|
const analysis = analysisOf(log)
|
||||||
|
return Boolean(analysis?.isAudio && nodeInstIdOf(log))
|
||||||
|
}
|
||||||
|
|
||||||
|
const openAudioCorrection = (log) => {
|
||||||
|
audioCorrectionTarget.value = log
|
||||||
|
audioCorrectionVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyAudioCorrection = (log, correction) => {
|
||||||
|
correctedOutputs.set(log, correction)
|
||||||
|
const currentActualResult = parseObject(log?.actualResult)
|
||||||
|
const mergedActualResult = { ...currentActualResult, ...correction }
|
||||||
|
if (log && typeof log.actualResult === 'object' && log.actualResult !== null) {
|
||||||
|
Object.assign(log.actualResult, correction)
|
||||||
|
} else if (log) {
|
||||||
|
log.actualResult = JSON.stringify(mergedActualResult)
|
||||||
|
}
|
||||||
|
analysisCache.delete(log)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAudioCorrectionSaved = (correction) => {
|
||||||
|
if (audioCorrectionTarget.value) {
|
||||||
|
applyAudioCorrection(audioCorrectionTarget.value, correction)
|
||||||
|
}
|
||||||
|
audioCorrectionTarget.value = null
|
||||||
|
}
|
||||||
|
|
||||||
const executionContextOf = (log) => outputOf(log)._executionContext || {}
|
const executionContextOf = (log) => outputOf(log)._executionContext || {}
|
||||||
|
|
||||||
const rawIterationPathOf = (log) => {
|
const rawIterationPathOf = (log) => {
|
||||||
@ -428,19 +499,18 @@ const analysisOf = (log) => {
|
|||||||
if (analysisCache.has(log)) return analysisCache.get(log)
|
if (analysisCache.has(log)) return analysisCache.get(log)
|
||||||
|
|
||||||
// extraInfo 后展开,因此同名字段会覆盖 actualResult;这是为了让后续补充/修正的信息优先显示。
|
// extraInfo 后展开,因此同名字段会覆盖 actualResult;这是为了让后续补充/修正的信息优先显示。
|
||||||
const output = {
|
const output = outputOf(log)
|
||||||
...parseObject(log.paramsOut),
|
|
||||||
...parseObject(log.actualResult),
|
|
||||||
...parseObject(log.extraInfo),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新日志通过 analysisType/profileCode 标识媒体类型,logType=3/4 用于兼容旧版视频/音频日志。
|
// 新日志通过 analysisType/profileCode 标识媒体类型,logType=3/4 用于兼容旧版视频/音频日志。
|
||||||
|
const action = String(log.action || '').toUpperCase()
|
||||||
const analysisType = String(output.analysisType || '').toUpperCase()
|
const analysisType = String(output.analysisType || '').toUpperCase()
|
||||||
const profileCode = String(output.profileCode || '').toLowerCase()
|
const profileCode = String(output.profileCode || '').toLowerCase()
|
||||||
const isAudio = analysisType.includes('AUDIO') || profileCode.includes('audio') || Number(log.logType) === 4
|
const isAudio = action === 'AUDIO_EVENT_CLASSIFY'
|
||||||
const isVideo = analysisType.includes('VIDEO') || profileCode.includes('video') || Number(log.logType) === 3
|
|| analysisType.includes('AUDIO') || profileCode.includes('audio') || Number(log.logType) === 4
|
||||||
|
const isVideo = action === 'VIDEO_ANALYZE'
|
||||||
|
|| analysisType.includes('VIDEO') || profileCode.includes('video') || Number(log.logType) === 3
|
||||||
// 图片分析没有历史 logType 枚举,只依赖新协议中的 analysisType/profileCode 识别。
|
// 图片分析没有历史 logType 枚举,只依赖新协议中的 analysisType/profileCode 识别。
|
||||||
const isImage = analysisType.includes('IMAGE') || profileCode.includes('image')
|
const isImage = action === 'IMAGE_ANALYZE' || analysisType.includes('IMAGE') || profileCode.includes('image')
|
||||||
|
|
||||||
// 仅有媒体类型还不足以展示分析面板,至少要存在一个分析状态或业务结果字段。
|
// 仅有媒体类型还不足以展示分析面板,至少要存在一个分析状态或业务结果字段。
|
||||||
const hasResult = Boolean(
|
const hasResult = Boolean(
|
||||||
@ -764,6 +834,10 @@ onMounted(scrollToBottom)
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.task-log__correction-button {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.task-log__analysis-meta,
|
.task-log__analysis-meta,
|
||||||
.task-log__analysis-field span,
|
.task-log__analysis-field span,
|
||||||
.task-log__analysis-label {
|
.task-log__analysis-label {
|
||||||
|
|||||||
@ -165,6 +165,7 @@
|
|||||||
import { computed, reactive, ref, watch } from 'vue'
|
import { computed, reactive, ref, watch } from 'vue'
|
||||||
import { Refresh } from '@element-plus/icons-vue'
|
import { Refresh } from '@element-plus/icons-vue'
|
||||||
import { getUpstreamInput } from '@/utils/flow'
|
import { getUpstreamInput } from '@/utils/flow'
|
||||||
|
import { soundTypeOptions } from '../../audioTypes.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* data 是流程画布当前节点模型,核心字段包括 id、properties.action 和 properties.nodeParams。
|
* data 是流程画布当前节点模型,核心字段包括 id、properties.action 和 properties.nodeParams。
|
||||||
@ -234,13 +235,6 @@ const analysisModes = [
|
|||||||
{ label: '精确', value: 'ACCURATE' },
|
{ label: '精确', value: 'ACCURATE' },
|
||||||
]
|
]
|
||||||
// 声音类型同时保存机器可读编码和中文名称;allow-create 允许现场扩展未预置类型。
|
// 声音类型同时保存机器可读编码和中文名称;allow-create 允许现场扩展未预置类型。
|
||||||
const soundTypeOptions = [
|
|
||||||
{ label: '开机', value: 'POWER_ON' },
|
|
||||||
{ label: '关机', value: 'POWER_OFF' },
|
|
||||||
{ label: '设防', value: 'ARMED' },
|
|
||||||
{ label: '解防', value: 'DISARMED' },
|
|
||||||
{ label: '鸣笛寻车', value: 'FIND_VEHICLE_HORN' },
|
|
||||||
]
|
|
||||||
const modeDescriptions = {
|
const modeDescriptions = {
|
||||||
FAST: '使用轻量模型直接判断,适合动作明显、批量执行的场景。',
|
FAST: '使用轻量模型直接判断,适合动作明显、批量执行的场景。',
|
||||||
AUTO: '先快速判断,低置信度时自动切换精确分析。',
|
AUTO: '先快速判断,低置信度时自动切换精确分析。',
|
||||||
|
|||||||
@ -5,6 +5,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="node__container" :class="props.model.id">
|
<div class="node__container" :class="props.model.id">
|
||||||
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes" ref="nodeStateRef">
|
<NodeState :state="nodeOperatingStatus" :runtimes="runtimes" ref="nodeStateRef">
|
||||||
|
<template #actions>
|
||||||
|
<el-button
|
||||||
|
v-if="audioCorrectionAvailable"
|
||||||
|
class="node-audio-correction-button nodrag nopan"
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
size="small"
|
||||||
|
@click="audioCorrectionVisible = true"
|
||||||
|
>
|
||||||
|
{{ outputJsonData.manualCorrected ? '重新判断' : '人工判断' }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
<template #input>
|
<template #input>
|
||||||
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
|
<JsonViewer :value="inputJsonData" copyable boxed sort theme="light" />
|
||||||
</template>
|
</template>
|
||||||
@ -45,6 +57,12 @@
|
|||||||
<div v-else>{{ errorInfoData }}</div>
|
<div v-else>{{ errorInfoData }}</div>
|
||||||
</template>
|
</template>
|
||||||
</NodeState>
|
</NodeState>
|
||||||
|
<AudioCorrectionDialog
|
||||||
|
v-model="audioCorrectionVisible"
|
||||||
|
:node-inst-id="nodeInstId"
|
||||||
|
:value="outputJsonData"
|
||||||
|
@saved="applyAudioCorrection"
|
||||||
|
/>
|
||||||
<NodeTitle
|
<NodeTitle
|
||||||
:icon="props.properties.icon"
|
:icon="props.properties.icon"
|
||||||
:nodeId="props.model.id"
|
:nodeId="props.model.id"
|
||||||
@ -65,6 +83,7 @@ import NodeState from "../../components/NodeState.vue";
|
|||||||
import "vue3-json-viewer/dist/index.css";
|
import "vue3-json-viewer/dist/index.css";
|
||||||
import { useFlowEvents } from "../../composables/useFlowEvents";
|
import { useFlowEvents } from "../../composables/useFlowEvents";
|
||||||
import IPlayer from "@/components/IPlayer/index.vue";
|
import IPlayer from "@/components/IPlayer/index.vue";
|
||||||
|
import AudioCorrectionDialog from "../../components/AudioCorrectionDialog.vue";
|
||||||
|
|
||||||
const { onFlowEvent } = useFlowEvents();
|
const { onFlowEvent } = useFlowEvents();
|
||||||
|
|
||||||
@ -93,6 +112,39 @@ const runtimes = ref(0);
|
|||||||
const inputJsonData = ref({});
|
const inputJsonData = ref({});
|
||||||
const outputJsonData = ref({});
|
const outputJsonData = ref({});
|
||||||
const errorInfoData = ref('');
|
const errorInfoData = ref('');
|
||||||
|
const executionLog = ref(null);
|
||||||
|
const audioCorrectionVisible = ref(false);
|
||||||
|
|
||||||
|
const nodeInstIdOf = (log) => log?.nodeInstId
|
||||||
|
|| log?.nodeInstanceId
|
||||||
|
|| log?.executionNodeInstId
|
||||||
|
|| log?.id
|
||||||
|
|| '';
|
||||||
|
|
||||||
|
const nodeInstId = computed(() => nodeInstIdOf(executionLog.value));
|
||||||
|
const audioCorrectionAvailable = computed(() => (
|
||||||
|
String(props.properties?.action || '').toUpperCase() === 'AUDIO_EVENT_CLASSIFY'
|
||||||
|
&& nodeOperatingStatus.value !== 'NORMAL'
|
||||||
|
&& Boolean(nodeInstId.value)
|
||||||
|
));
|
||||||
|
|
||||||
|
const applyAudioCorrection = (correction) => {
|
||||||
|
outputJsonData.value = { ...outputJsonData.value, ...correction };
|
||||||
|
if (!executionLog.value) return;
|
||||||
|
const currentOutput = parseRuntimeValue(executionLog.value.paramsOut);
|
||||||
|
executionLog.value.paramsOut = JSON.stringify({ ...currentOutput, ...correction });
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseRuntimeValue = (value) => {
|
||||||
|
if (!value) return {};
|
||||||
|
if (typeof value === 'object' && !Array.isArray(value)) return value;
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
|
||||||
|
} catch {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const audioUrls = computed(() => {
|
const audioUrls = computed(() => {
|
||||||
const value = outputJsonData.value?.audioUrl;
|
const value = outputJsonData.value?.audioUrl;
|
||||||
@ -116,6 +168,7 @@ const closePopover = () => {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
onFlowEvent("changeNodeState", (data) => {
|
onFlowEvent("changeNodeState", (data) => {
|
||||||
if (data.nodeId === props.model.id) {
|
if (data.nodeId === props.model.id) {
|
||||||
|
executionLog.value = data;
|
||||||
nodeOperatingStatus.value = data.status;
|
nodeOperatingStatus.value = data.status;
|
||||||
if (data.endTime && data.startTime) {
|
if (data.endTime && data.startTime) {
|
||||||
runtimes.value = data.endTime - data.startTime;
|
runtimes.value = data.endTime - data.startTime;
|
||||||
@ -141,6 +194,7 @@ onMounted(() => {
|
|||||||
nodeOperatingStatus.value = "NORMAL";
|
nodeOperatingStatus.value = "NORMAL";
|
||||||
inputJsonData.value = {};
|
inputJsonData.value = {};
|
||||||
outputJsonData.value = {};
|
outputJsonData.value = {};
|
||||||
|
executionLog.value = null;
|
||||||
emits("contentChange");
|
emits("contentChange");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -166,4 +220,8 @@ defineExpose({ closePopover })
|
|||||||
width: min(320px, 100%);
|
width: min(320px, 100%);
|
||||||
height: 36px;
|
height: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.node-audio-correction-button {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user