maxkb/ui/src/components/ai-chat/ExecutionDetailDialog.vue

483 lines
21 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<el-dialog
class="execution-details-dialog"
title="执行详情"
v-model="dialogVisible"
destroy-on-close
append-to-body
align-center
@click.stop
>
<el-scrollbar>
<div class="execution-details">
<template v-for="(item, index) in arraySort(detail, 'index')" :key="index">
<el-card class="mb-8" shadow="never" style="--el-card-padding: 12px 16px">
<div class="flex-between cursor" @click="current = current === index ? '' : index">
<div class="flex align-center">
<el-icon class="mr-8 arrow-icon" :class="current === index ? 'rotate-90' : ''"
><CaretRight
/></el-icon>
<component
:is="iconComponent(`${item.type}-icon`)"
class="mr-8"
:size="24"
:item="item.info"
/>
<h4>{{ item.name }}</h4>
</div>
<div class="flex align-center">
<span
class="mr-16 color-secondary"
v-if="item.type === WorkflowType.Question || item.type === WorkflowType.AiChat || item.type === WorkflowType.ImageUnderstandNode"
>{{ item?.message_tokens + item?.answer_tokens }} tokens</span
>
<span class="mr-16 color-secondary">{{ item?.run_time?.toFixed(2) || 0.0 }} s</span>
<el-icon class="success" :size="16" v-if="item.status === 200"
><CircleCheck
/></el-icon>
<el-icon class="danger" :size="16" v-else><CircleClose /></el-icon>
</div>
</div>
<el-collapse-transition>
<div class="mt-12" v-if="current === index">
<template v-if="item.status === 200">
<!-- 开始 -->
<template
v-if="
item.type === WorkflowType.Start || item.type === WorkflowType.Application
"
>
<div class="card-never border-r-4">
<h5 class="p-8-12">参数输入</h5>
<div class="p-8-12 border-t-dashed lighter">
<div class="mb-8">
<span class="color-secondary">用户问题:</span>
{{ item.question || '-' }}
</div>
<div v-for="(f, i) in item.global_fields" :key="i" class="mb-8">
<span class="color-secondary">{{ f.label }}:</span> {{ f.value }}
</div>
<div v-if="item.document_list?.length > 0">
<p class="mb-8 color-secondary">上传的文档:</p>
<el-space wrap>
<template v-for="(f, i) in item.document_list" :key="i">
{{ f.name }}
<el-card
shadow="never"
style="--el-card-padding: 8px"
class="file cursor"
>
<div class="flex align-center">
<img :src="getImgUrl(f && f?.name)" alt="" width="24" />
<div class="ml-4 ellipsis" :title="f && f?.name">
{{ f && f?.name }}
</div>
</div>
</el-card>
</template>
</el-space>
</div>
<div v-if="item.image_list?.length > 0">
<p class="mb-8 color-secondary">上传的图片:</p>
<el-space wrap>
<template v-for="(f, i) in item.image_list" :key="i">
<el-image
:src="f.url"
alt=""
fit="cover"
style="width: 40px; height: 40px; display: block"
class="border-r-4"
/>
</template>
</el-space>
</div>
</div>
</div>
</template>
<!-- 知识库检索 -->
<template v-if="item.type == WorkflowType.SearchDataset">
<div class="card-never border-r-4">
<h5 class="p-8-12">检索内容</h5>
<div class="p-8-12 border-t-dashed lighter">{{ item.question || '-' }}</div>
</div>
<div class="card-never border-r-4 mt-8">
<h5 class="p-8-12">检索结果</h5>
<div class="p-8-12 border-t-dashed lighter">
<template v-if="item.paragraph_list?.length > 0">
<template
v-for="(paragraph, paragraphIndex) in arraySort(
item.paragraph_list,
'similarity',
true
)"
:key="paragraphIndex"
>
<ParagraphCard :data="paragraph" :index="paragraphIndex" />
</template>
</template>
<template v-else> - </template>
</div>
</div>
</template>
<!-- 判断器 -->
<template v-if="item.type == WorkflowType.Condition">
<div class="card-never border-r-4">
<h5 class="p-8-12">判断结果</h5>
<div class="p-8-12 border-t-dashed lighter">
{{ item.branch_name || '-' }}
</div>
</div>
</template>
<!-- AI 对话 / 问题优化-->
<template
v-if="
item.type == WorkflowType.AiChat ||
item.type == WorkflowType.Question ||
item.type == WorkflowType.Application
"
>
<div
class="card-never border-r-4"
v-if="item.type !== WorkflowType.Application"
>
<h5 class="p-8-12">角色设定 (System)</h5>
<div class="p-8-12 border-t-dashed lighter">
{{ item.system || '-' }}
</div>
</div>
<div
class="card-never border-r-4 mt-8"
v-if="item.type !== WorkflowType.Application"
>
<h5 class="p-8-12">历史记录</h5>
<div class="p-8-12 border-t-dashed lighter">
<template v-if="item.history_message?.length > 0">
<p
class="mt-4 mb-4"
v-for="(history, historyIndex) in item.history_message"
:key="historyIndex"
>
<span class="color-secondary mr-4">{{ history.role }}:</span
><span>{{ history.content }}</span>
</p>
</template>
<template v-else> - </template>
</div>
</div>
<div class="card-never border-r-4 mt-8">
<h5 class="p-8-12">本次对话</h5>
<div class="p-8-12 border-t-dashed lighter pre-wrap">
{{ item.question || '-' }}
</div>
</div>
<div class="card-never border-r-4 mt-8">
<h5 class="p-8-12">
{{ item.type == WorkflowType.Application ? '参数输出' : 'AI 回答' }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<MdPreview
v-if="item.answer"
ref="editorRef"
editorId="preview-only"
:modelValue="item.answer"
style="background: none"
/>
<template v-else> - </template>
</div>
</div>
</template>
<!-- 指定回复 -->
<template v-if="item.type === WorkflowType.Reply">
<div class="card-never border-r-4">
<h5 class="p-8-12">回复内容</h5>
<div class="p-8-12 border-t-dashed lighter">
<el-scrollbar height="150">
<MdPreview
v-if="item.answer"
ref="editorRef"
editorId="preview-only"
:modelValue="item.answer"
style="background: none"
/>
<template v-else> - </template>
</el-scrollbar>
</div>
</div>
</template>
<!-- 文档内容提取 -->
<template v-if="item.type === WorkflowType.DocumentExtractNode">
<div class="card-never border-r-4">
<h5 class="p-8-12">参数输出</h5>
<div class="p-8-12 border-t-dashed lighter">
<el-scrollbar height="150">
<MdPreview
v-if="item.content"
ref="editorRef"
editorId="preview-only"
:modelValue="item.content"
style="background: none"
/>
<template v-else> - </template>
</el-scrollbar>
</div>
</div>
</template>
<!-- 函数库 -->
<template
v-if="
item.type === WorkflowType.FunctionLib ||
item.type === WorkflowType.FunctionLibCustom
"
>
<div class="card-never border-r-4 mt-8">
<h5 class="p-8-12">输入</h5>
<div class="p-8-12 border-t-dashed lighter pre-wrap">
{{ item.params || '-' }}
</div>
</div>
<div class="card-never border-r-4 mt-8">
<h5 class="p-8-12">输出</h5>
<div class="p-8-12 border-t-dashed lighter pre-wrap">
{{ item.result || '-' }}
</div>
</div>
</template>
<!-- 多路召回 -->
<template v-if="item.type == WorkflowType.RrerankerNode">
<div class="card-never border-r-4">
<h5 class="p-8-12">检索内容</h5>
<div class="p-8-12 border-t-dashed lighter">{{ item.question || '-' }}</div>
</div>
<div class="card-never border-r-4 mt-8">
<h5 class="p-8-12">重排内容</h5>
<div class="p-8-12 border-t-dashed lighter">
<template v-if="item.document_list?.length > 0">
<template
v-for="(paragraph, paragraphIndex) in item.document_list"
:key="paragraphIndex"
>
<CardBox shadow="never" title="" class="cursor mb-8" :showIcon="false">
<template #description>
<el-scrollbar max-height="150">
<MdPreview
ref="editorRef"
editorId="preview-only"
:modelValue="paragraph"
/>
</el-scrollbar>
</template>
</CardBox>
</template>
</template>
<template v-else> - </template>
</div>
</div>
<div class="card-never border-r-4 mt-8">
<h5 class="p-8-12">重排结果</h5>
<div class="p-8-12 border-t-dashed lighter">
<template v-if="item.result_list?.length > 0">
<template
v-for="(paragraph, paragraphIndex) in item.result_list"
:key="paragraphIndex"
>
<CardBox
shadow="never"
:title="`分段${paragraphIndex + 1}`"
class="paragraph-source-card cursor mb-8 paragraph-source-card-height"
:showIcon="false"
>
<div class="active-button primary">
{{ paragraph.metadata.relevance_score?.toFixed(3) }}
</div>
<template #description>
<div class="mt-8">
<el-scrollbar height="150">
<MdPreview
ref="editorRef"
editorId="preview-only"
:modelValue="paragraph.page_content"
/>
</el-scrollbar>
</div>
</template>
</CardBox>
</template>
</template>
<template v-else> - </template>
</div>
</div>
</template>
<!-- 表单收集 -->
<template v-if="item.type === WorkflowType.FormNode">
<div class="card-never border-r-4">
<h5 class="p-8-12">参数输入</h5>
<div class="p-8-12 border-t-dashed lighter">
<div v-for="(f, i) in item.form_field_list" :key="i" class="mb-8">
<span class="color-secondary">{{ f.label.label }}:</span>
{{ item.form_data[f.field] }}
</div>
</div>
</div>
</template>
<!-- 图片理解 -->
<template v-if="item.type == WorkflowType.ImageUnderstandNode">
<div
class="card-never border-r-4"
v-if="item.type !== WorkflowType.Application"
>
<h5 class="p-8-12">角色设定 (System)</h5>
<div class="p-8-12 border-t-dashed lighter">
{{ item.system || '-' }}
</div>
</div>
<div
class="card-never border-r-4 mt-8"
v-if="item.type !== WorkflowType.Application"
>
<h5 class="p-8-12">历史记录</h5>
<div class="p-8-12 border-t-dashed lighter">
<template v-if="item.history_message?.length > 0">
<p
class="mt-4 mb-4"
v-for="(history, historyIndex) in item.history_message"
:key="historyIndex"
>
<span class="color-secondary mr-4">{{ history.role }}:</span>
<span v-if="Array.isArray(history.content)">
<template v-for="(h, i) in history.content" :key="i">
<el-image
v-if="h.type === 'image_url'"
:src="h.image_url.url"
alt=""
fit="cover"
style="width: 40px; height: 40px; display: block"
class="border-r-4"
/>
<span v-else>{{ h.text }}</span>
</template>
</span>
<span v-else>{{ history.content }}</span>
</p>
</template>
<template v-else> - </template>
</div>
</div>
<div class="card-never border-r-4 mt-8">
<h5 class="p-8-12">本次对话</h5>
<div class="p-8-12 border-t-dashed lighter pre-wrap">
<div v-if="item.image_list?.length > 0">
<p class="mb-8 color-secondary">图片:</p>
<el-space wrap>
<template v-for="(f, i) in item.image_list" :key="i">
<el-image
:src="f.url"
alt=""
fit="cover"
style="width: 40px; height: 40px; display: block"
class="border-r-4"
/>
</template>
</el-space>
</div>
<div>
<p class="mb-8 color-secondary">提示词</p>
{{ item.question || '-' }}
</div>
</div>
</div>
<div class="card-never border-r-4 mt-8">
<h5 class="p-8-12">
{{ item.type == WorkflowType.Application ? '参数输出' : 'AI 回答' }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<MdPreview
v-if="item.answer"
ref="editorRef"
editorId="preview-only"
:modelValue="item.answer"
style="background: none"
/>
<template v-else> - </template>
</div>
</div>
</template>
</template>
<template v-else>
<div class="card-never border-r-4">
<h5 class="p-8-12">错误日志</h5>
<div class="p-8-12 border-t-dashed lighter">{{ item.err_message || '-' }}</div>
</div>
</template>
</div>
</el-collapse-transition>
</el-card>
</template>
</div>
</el-scrollbar>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, watch, onBeforeUnmount } from 'vue'
import { cloneDeep } from 'lodash'
import ParagraphCard from './component/ParagraphCard.vue'
import { arraySort } from '@/utils/utils'
import { iconComponent } from '@/workflow/icons/utils'
import { WorkflowType } from '@/enums/workflow'
import { getImgUrl } from '@/utils/utils'
const dialogVisible = ref(false)
const detail = ref<any[]>([])
const current = ref<number | string>('')
watch(dialogVisible, (bool) => {
if (!bool) {
detail.value = []
}
})
const open = (data: any) => {
detail.value = cloneDeep(data)
console.log(detail.value)
dialogVisible.value = true
}
onBeforeUnmount(() => {
dialogVisible.value = false
})
defineExpose({ open })
</script>
<style lang="scss">
.execution-details-dialog {
padding: 0;
.el-dialog__header {
padding: 24px 24px 0 24px;
}
.el-dialog__body {
padding: 8px !important;
}
.execution-details {
max-height: calc(100vh - 260px);
.arrow-icon {
transition: 0.2s;
}
}
}
@media only screen and (max-width: 768px) {
.execution-details-dialog {
width: 90% !important;
.footer-content {
display: block;
}
}
}
</style>