refactor: executionDetailContent

This commit is contained in:
teukkk 2025-06-27 20:27:18 +08:00
parent a1abf33fa2
commit 4a737556a9
12 changed files with 853 additions and 763 deletions

View File

@ -9,7 +9,7 @@
在[博查开放平台](https://open.bochaai.com/overview) 上申请 API 密钥。 在[博查开放平台](https://open.bochaai.com/overview) 上申请 API 密钥。
![API Key](/ui/tool/img/bocha_APIKey.jpg) ![API Key](/ui/tool/img/bocha_APIKey.jpg)
2. 在函数库中配置 2. 在函数库中配置
在函数库的博查函数面板中,点击 … > 启参数,填写 API 密钥,并启用该函数。 在函数库的博查函数面板中,点击 … > 启参数,填写 API 密钥,并启用该函数。
![启动参数](/ui/tool/img/bocha_setting.jpg) ![启动参数](/ui/tool/img/bocha_setting.jpg)
3. 在应用中使用 3. 在应用中使用
在高级编排应用中,点击添加组件->函数库->博查,设置使用参数。 在高级编排应用中,点击添加组件->函数库->博查,设置使用参数。

View File

@ -9,7 +9,7 @@ LangSearch 是一个提供免费Web Search API和Rerank API的服务支持新
在[LangSearch](https://langsearch.com/overview) 上申请 API 密钥。 在[LangSearch](https://langsearch.com/overview) 上申请 API 密钥。
![API Key](/ui/tool/img/langsearch_APIKey.jpg) ![API Key](/ui/tool/img/langsearch_APIKey.jpg)
2. 在函数库中配置 2. 在函数库中配置
在函数库的LangSearch函数面板中点击 … > 启参数,填写 API 密钥,并启用该函数。 在函数库的LangSearch函数面板中点击 … > 启参数,填写 API 密钥,并启用该函数。
![启动参数](/ui/tool/img/langsearch_setting.jpg) ![启动参数](/ui/tool/img/langsearch_setting.jpg)
3. 在应用中使用 3. 在应用中使用
在高级编排应用中,点击添加组件->函数库->LangSearch设置使用参数。 在高级编排应用中,点击添加组件->函数库->LangSearch设置使用参数。

View File

@ -6,7 +6,7 @@ MySQL查询是一个连接MySQL数据库执行SQL查询的工具。
## 配置 ## 配置
   
1. 在函数库中配置启动参数 1. 在函数库中配置启动参数
在函数库的MySQL函数面板中点击 … > 启参数,填写数据库连接参数,并启用该函数。 在函数库的MySQL函数面板中点击 … > 启参数,填写数据库连接参数,并启用该函数。
![启动参数](/ui/tool/img/MySQL_setting.jpg) ![启动参数](/ui/tool/img/MySQL_setting.jpg)
2. 在应用中使用 2. 在应用中使用
在高级编排应用中,点击添加组件->函数库->MySQL查询设置查询内容。 在高级编排应用中,点击添加组件->函数库->MySQL查询设置查询内容。

View File

@ -6,7 +6,7 @@ PostgreSQL查询是一个连接PostgreSQL数据库执行SQL查询的工具。
## 配置 ## 配置
   
1. 在函数库中配置启动参数 1. 在函数库中配置启动参数
在函数库的PostgreSQL函数面板中点击 … > 启参数,填写数据库连接参数,并启用该函数。 在函数库的PostgreSQL函数面板中点击 … > 启参数,填写数据库连接参数,并启用该函数。
![启动参数](/ui/tool/img/PostgreSQL_setting.jpg) ![启动参数](/ui/tool/img/PostgreSQL_setting.jpg)
2. 在应用中使用 2. 在应用中使用
在高级编排应用中,点击添加组件->函数库->PostgreSQL查询设置查询内容。 在高级编排应用中,点击添加组件->函数库->PostgreSQL查询设置查询内容。

View File

@ -8,723 +8,17 @@
align-center align-center
@click.stop @click.stop
> >
<el-scrollbar> <ExecutionDetailContent :detail="detail" />
<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.type === WorkflowType.ImageGenerateNode ||
item.type === WorkflowType.Application
"
>{{ 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="color-success" :size="16" v-if="item.status === 200">
<CircleCheck />
</el-icon>
<el-icon class="color-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-6">
<h5 class="p-8-12">
{{ $t('common.param.inputParam') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<div class="mb-8">
<span class="color-secondary">
{{ $t('chat.paragraphSource.question') }}:</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">
{{ $t('common.fileUpload.document') }}:
</p>
<el-space wrap>
<template v-for="(f, i) in item.document_list" :key="i">
<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">{{ $t('common.fileUpload.image') }}:</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-6"
/>
</template>
</el-space>
</div>
<div v-if="item.audio_list?.length > 0">
<p class="mb-8 color-secondary">
{{ $t('chat.executionDetails.audioFile') }}:
</p>
<el-space wrap>
<template v-for="(f, i) in item.audio_list" :key="i">
<audio
:src="f.url"
controls
style="width: 300px; height: 43px"
class="border-r-6"
/>
</template>
</el-space>
</div>
<div v-if="item.other_list?.length > 0">
<p class="mb-8 color-secondary">
{{ $t('common.fileUpload.document') }}:
</p>
<el-space wrap>
<template v-for="(f, i) in item.other_list" :key="i">
<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>
</div>
</template>
<!-- 知识库检索 -->
<template v-if="item.type == WorkflowType.SearchKnowledge">
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('chat.executionDetails.searchContent') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">{{ item.question || '-' }}</div>
</div>
<div class="card-never border-r-6 mt-8">
<h5 class="p-8-12">
{{ $t('chat.executionDetails.searchResult') }}
</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"
:content="paragraph.content"
:index="paragraphIndex"
/>
</template>
</template>
<template v-else> -</template>
</div>
</div>
</template>
<!-- 判断器 -->
<template v-if="item.type == WorkflowType.Condition">
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('chat.executionDetails.conditionResult') }}
</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-6"
v-if="item.type !== WorkflowType.Application"
>
<h5 class="p-8-12">
{{ $t('views.application.applicationForm.form.roleSettings.label') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
{{ item.system || '-' }}
</div>
</div>
<div
class="card-never border-r-6 mt-8"
v-if="item.type !== WorkflowType.Application"
>
<h5 class="p-8-12">{{ $t('chat.history') }}</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-6 mt-8"
v-if="item.type !== WorkflowType.Application"
>
<h5 class="p-8-12">
{{ $t('chat.executionDetails.currentChat') }}
</h5>
<div class="p-8-12 border-t-dashed lighter pre-wrap">
{{ item.question || '-' }}
</div>
</div>
<div class="card-never border-r-6 mt-8" v-if="item.type == WorkflowType.AiChat">
<h5 class="p-8-12">
{{ $t('views.applicationWorkflow.nodes.aiChatNode.think') }}
</h5>
<div class="p-8-12 border-t-dashed lighter pre-wrap">
{{ item.reasoning_content || '-' }}
</div>
</div>
<div class="card-never border-r-6 mt-8">
<h5 class="p-8-12">
{{
item.type == WorkflowType.Application
? $t('common.param.outputParam')
: $t('chat.executionDetails.answer')
}}
</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"
noImgZoomIn
/>
<template v-else> -</template>
</div>
</div>
</template>
<!-- 指定回复 -->
<template v-if="item.type === WorkflowType.Reply">
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('chat.executionDetails.replyContent') }}
</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"
noImgZoomIn
/>
<template v-else> -</template>
</el-scrollbar>
</div>
</div>
</template>
<!-- 文档内容提取 -->
<template v-if="item.type === WorkflowType.DocumentExtractNode">
<div class="card-never border-r-6">
<h5 class="p-8-12 flex align-center">
<span class="mr-4"> {{ $t('common.param.outputParam') }}</span>
<el-tooltip
effect="dark"
:content="$t('chat.executionDetails.paramOutputTooltip')"
placement="right"
>
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
</el-tooltip>
</h5>
<div class="p-8-12 border-t-dashed lighter">
<el-scrollbar height="150">
<el-card
shadow="never"
style="--el-card-padding: 8px"
v-for="(file_content, index) in item.content"
:key="index"
class="mb-8"
>
<MdPreview
v-if="file_content"
ref="editorRef"
editorId="preview-only"
:modelValue="file_content"
style="background: none"
noImgZoomIn
/>
<template v-else> -</template>
</el-card>
</el-scrollbar>
</div>
</div>
</template>
<template v-if="item.type === WorkflowType.SpeechToTextNode">
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('common.param.inputParam') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<div class="mb-8">
<div v-if="item.audio_list?.length > 0">
<p class="mb-8 color-secondary">
{{ $t('chat.executionDetails.audioFile') }}:
</p>
<el-space wrap>
<template v-for="(f, i) in item.audio_list" :key="i">
<audio
:src="f.url"
controls
style="width: 300px; height: 43px"
class="border-r-6"
/>
</template>
</el-space>
</div>
</div>
</div>
</div>
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('common.param.outputParam') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<el-card
shadow="never"
style="--el-card-padding: 8px"
v-for="(file_content, index) in item.content"
:key="index"
class="mb-8"
>
<MdPreview
v-if="file_content"
ref="editorRef"
editorId="preview-only"
:modelValue="file_content"
style="background: none"
noImgZoomIn
/>
<template v-else> -</template>
</el-card>
</div>
</div>
</template>
<template v-if="item.type === WorkflowType.TextToSpeechNode">
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('common.param.inputParam') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<div class="p-8-12 border-t-dashed lighter">
<p class="mb-8 color-secondary">
{{ $t('chat.executionDetails.textContent') }}:
</p>
<div v-if="item.content">
<MdPreview
ref="editorRef"
editorId="preview-only"
:modelValue="item.content"
style="background: none"
noImgZoomIn
/>
</div>
</div>
</div>
</div>
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('common.param.outputParam') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<p class="mb-8 color-secondary">
{{ $t('chat.executionDetails.audioFile') }}:
</p>
<div v-if="item.answer" v-html="item.answer"></div>
</div>
</div>
</template>
<!-- 函数库 -->
<template
v-if="
item.type === WorkflowType.ToolLib ||
item.type === WorkflowType.ToolLibCustom
"
>
<div class="card-never border-r-6 mt-8">
<h5 class="p-8-12">{{ $t('chat.executionDetails.input') }}</h5>
<div class="p-8-12 border-t-dashed lighter pre-wrap">
{{ item.params || '-' }}
</div>
</div>
<div class="card-never border-r-6 mt-8">
<h5 class="p-8-12">{{ $t('chat.executionDetails.output') }}</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-6">
<h5 class="p-8-12">
{{ $t('chat.executionDetails.searchContent') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">{{ item.question || '-' }}</div>
</div>
<div class="card-never border-r-6 mt-8">
<h5 class="p-8-12">
{{ $t('chat.executionDetails.rerankerContent') }}
</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"
>
<ParagraphCard
:data="paragraph.metadata"
:content="paragraph.page_content"
:index="paragraphIndex"
/>
</template>
</template>
<template v-else> -</template>
</div>
</div>
<div class="card-never border-r-6 mt-8">
<h5 class="p-8-12">
{{ $t('chat.executionDetails.rerankerResult') }}
</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"
>
<ParagraphCard
:data="paragraph.metadata"
:content="paragraph.page_content"
:index="paragraphIndex"
:score="paragraph.metadata?.relevance_score"
/>
</template>
</template>
<template v-else> -</template>
</div>
</div>
</template>
<!-- 表单收集 -->
<template v-if="item.type === WorkflowType.FormNode">
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('common.param.outputParam')
}}<span style="color: #f54a45">{{
item.is_submit ? '' : `(${$t('chat.executionDetails.noSubmit')})`
}}</span>
</h5>
<div class="p-8-12 border-t-dashed lighter">
<DynamicsForm
:disabled="true"
label-position="top"
require-asterisk-position="right"
ref="dynamicsFormRef"
:render_data="item.form_field_list"
label-suffix=":"
v-model="item.form_data"
:model="item.form_data"
></DynamicsForm>
</div>
</div>
</template>
<!-- 图片理解 -->
<template v-if="item.type == WorkflowType.ImageUnderstandNode">
<div
class="card-never border-r-6"
v-if="item.type !== WorkflowType.Application"
>
<h5 class="p-8-12">
{{ $t('views.application.applicationForm.form.roleSettings.label') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
{{ item.system || '-' }}
</div>
</div>
<div
class="card-never border-r-6 mt-8"
v-if="item.type !== WorkflowType.Application"
>
<h5 class="p-8-12">{{ $t('chat.history') }}</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: inline-block"
class="border-r-6 mr-8"
/>
<span v-else>{{ h.text }}<br /></span>
</template>
</span>
<span v-else>{{ history.content }}</span>
</p>
</template>
<template v-else> -</template>
</div>
</div>
<div class="card-never border-r-6 mt-8">
<h5 class="p-8-12">
{{ $t('chat.executionDetails.currentChat') }}
</h5>
<div class="p-8-12 border-t-dashed lighter pre-wrap">
<div v-if="item.image_list?.length > 0">
<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-6"
/>
</template>
</el-space>
</div>
<div>
{{ item.question || '-' }}
</div>
</div>
</div>
<div class="card-never border-r-6 mt-8">
<h5 class="p-8-12">
{{
item.type == WorkflowType.Application
? $t('common.param.outputParam')
: $t('chat.executionDetails.answer')
}}
</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"
noImgZoomIn
/>
<template v-else> -</template>
</div>
</div>
</template>
<!-- 图片生成 -->
<template v-if="item.type == WorkflowType.ImageGenerateNode">
<div class="card-never border-r-6 mt-8">
<h5 class="p-8-12">
{{ $t('chat.executionDetails.currentChat') }}
</h5>
<div class="p-8-12 border-t-dashed lighter pre-wrap">
{{ item.question || '-' }}
</div>
</div>
<div class="card-never border-r-6 mt-8">
<h5 class="p-8-12">
{{
item.type == WorkflowType.Application
? $t('common.param.outputParam')
: $t('chat.executionDetails.answer')
}}
</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"
noImgZoomIn
/>
<template v-else> -</template>
</div>
</div>
</template>
<!-- 变量赋值 -->
<template v-if="item.type === WorkflowType.VariableAssignNode">
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('common.param.inputParam') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<div v-for="(f, i) in item.result_list" :key="i" class="mb-8">
<span class="color-secondary">{{ f.name }}:</span> {{ f.input_value }}
</div>
</div>
</div>
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('common.param.outputParam') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<div v-for="(f, i) in item.result_list" :key="i" class="mb-8">
<span class="color-secondary">{{ f.name }}:</span> {{ f.output_value }}
</div>
</div>
</div>
</template>
<!-- MCP 节点 -->
<template v-if="item.type === WorkflowType.McpNode">
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('views.applicationWorkflow.nodes.mcpNode.tool') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<div class="mb-8">
<span class="color-secondary"> {{ $t('views.applicationWorkflow.nodes.mcpNode.tool') }}: </span> {{ item.mcp_tool }}
</div>
</div>
</div>
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('views.applicationWorkflow.nodes.mcpNode.toolParam') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<div v-for="(value, name) in item.tool_params" :key="name" class="mb-8">
<span class="color-secondary">{{ name }}:</span> {{ value }}
</div>
</div>
</div>
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('common.param.outputParam') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<div v-for="(f, i) in item.result" :key="i" class="mb-8">
<span class="color-secondary">result:</span> {{ f }}
</div>
</div>
</div>
</template>
</template>
<template v-else>
<div class="card-never border-r-6">
<h5 class="p-8-12">{{ $t('chat.executionDetails.errMessage') }}</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> </el-dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch, onBeforeUnmount } from 'vue' import { ref, watch, onBeforeUnmount } from 'vue'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
import ParagraphCard from './component/ParagraphCard.vue' import ExecutionDetailContent from './component/ExecutionDetailContent.vue'
import { arraySort } from '@/utils/utils'
import { iconComponent } from '@/workflow/icons/utils'
import { WorkflowType } from '@/enums/application'
import { getImgUrl } from '@/utils/utils'
import DynamicsForm from '@/components/dynamics-form/index.vue'
const dialogVisible = ref(false) const dialogVisible = ref(false)
const detail = ref<any[]>([]) const detail = ref<any[]>([])
const current = ref<number | string>('')
watch(dialogVisible, (bool) => { watch(dialogVisible, (bool) => {
if (!bool) { if (!bool) {
detail.value = [] detail.value = []
@ -755,6 +49,4 @@ defineExpose({ open })
} }
} }
} }
</style> </style>

View File

@ -81,15 +81,25 @@ const props = defineProps({
type: { type: {
type: String, type: String,
default: '' default: ''
},
executionIsRightPanel: {
type: Boolean,
required: false,
} }
}) })
const emit = defineEmits(['openExecutionDetail'])
const ParagraphSourceDialogRef = ref() const ParagraphSourceDialogRef = ref()
const ExecutionDetailDialogRef = ref() const ExecutionDetailDialogRef = ref()
function openParagraph(row: any, id?: string) { function openParagraph(row: any, id?: string) {
ParagraphSourceDialogRef.value.open(row, id) ParagraphSourceDialogRef.value.open(row, id)
} }
function openExecutionDetail(row: any) { function openExecutionDetail(row: any) {
if(props.executionIsRightPanel){
emit('openExecutionDetail')
return
}
ExecutionDetailDialogRef.value.open(row) ExecutionDetailDialogRef.value.open(row)
} }
const uniqueParagraphList = computed(() => { const uniqueParagraphList = computed(() => {

View File

@ -0,0 +1,725 @@
<template>
<el-scrollbar>
<div class="execution-details">
<template v-for="(item, index) in arraySort(props.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.type === WorkflowType.ImageGenerateNode ||
item.type === WorkflowType.Application
"
>{{ 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="color-success" :size="16" v-if="item.status === 200">
<CircleCheck />
</el-icon>
<el-icon class="color-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-6">
<h5 class="p-8-12">
{{ $t('common.param.inputParam') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<div class="mb-8">
<span class="color-secondary">
{{ $t('chat.paragraphSource.question') }}:</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">
{{ $t('common.fileUpload.document') }}:
</p>
<el-space wrap>
<template v-for="(f, i) in item.document_list" :key="i">
<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">{{ $t('common.fileUpload.image') }}:</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-6"
/>
</template>
</el-space>
</div>
<div v-if="item.audio_list?.length > 0">
<p class="mb-8 color-secondary">
{{ $t('chat.executionDetails.audioFile') }}:
</p>
<el-space wrap>
<template v-for="(f, i) in item.audio_list" :key="i">
<audio
:src="f.url"
controls
style="width: 300px; height: 43px"
class="border-r-6"
/>
</template>
</el-space>
</div>
<div v-if="item.other_list?.length > 0">
<p class="mb-8 color-secondary">
{{ $t('common.fileUpload.document') }}:
</p>
<el-space wrap>
<template v-for="(f, i) in item.other_list" :key="i">
<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>
</div>
</template>
<!-- 知识库检索 -->
<template v-if="item.type == WorkflowType.SearchKnowledge">
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('chat.executionDetails.searchContent') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">{{ item.question || '-' }}</div>
</div>
<div class="card-never border-r-6 mt-8">
<h5 class="p-8-12">
{{ $t('chat.executionDetails.searchResult') }}
</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"
:content="paragraph.content"
:index="paragraphIndex"
/>
</template>
</template>
<template v-else> -</template>
</div>
</div>
</template>
<!-- 判断器 -->
<template v-if="item.type == WorkflowType.Condition">
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('chat.executionDetails.conditionResult') }}
</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-6"
v-if="item.type !== WorkflowType.Application"
>
<h5 class="p-8-12">
{{ $t('views.application.applicationForm.form.roleSettings.label') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
{{ item.system || '-' }}
</div>
</div>
<div
class="card-never border-r-6 mt-8"
v-if="item.type !== WorkflowType.Application"
>
<h5 class="p-8-12">{{ $t('chat.history') }}</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-6 mt-8"
v-if="item.type !== WorkflowType.Application"
>
<h5 class="p-8-12">
{{ $t('chat.executionDetails.currentChat') }}
</h5>
<div class="p-8-12 border-t-dashed lighter pre-wrap">
{{ item.question || '-' }}
</div>
</div>
<div class="card-never border-r-6 mt-8" v-if="item.type == WorkflowType.AiChat">
<h5 class="p-8-12">
{{ $t('views.applicationWorkflow.nodes.aiChatNode.think') }}
</h5>
<div class="p-8-12 border-t-dashed lighter pre-wrap">
{{ item.reasoning_content || '-' }}
</div>
</div>
<div class="card-never border-r-6 mt-8">
<h5 class="p-8-12">
{{
item.type == WorkflowType.Application
? $t('common.param.outputParam')
: $t('chat.executionDetails.answer')
}}
</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"
noImgZoomIn
/>
<template v-else> -</template>
</div>
</div>
</template>
<!-- 指定回复 -->
<template v-if="item.type === WorkflowType.Reply">
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('chat.executionDetails.replyContent') }}
</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"
noImgZoomIn
/>
<template v-else> -</template>
</el-scrollbar>
</div>
</div>
</template>
<!-- 文档内容提取 -->
<template v-if="item.type === WorkflowType.DocumentExtractNode">
<div class="card-never border-r-6">
<h5 class="p-8-12 flex align-center">
<span class="mr-4"> {{ $t('common.param.outputParam') }}</span>
<el-tooltip
effect="dark"
:content="$t('chat.executionDetails.paramOutputTooltip')"
placement="right"
>
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
</el-tooltip>
</h5>
<div class="p-8-12 border-t-dashed lighter">
<el-scrollbar height="150">
<el-card
shadow="never"
style="--el-card-padding: 8px"
v-for="(file_content, index) in item.content"
:key="index"
class="mb-8"
>
<MdPreview
v-if="file_content"
ref="editorRef"
editorId="preview-only"
:modelValue="file_content"
style="background: none"
noImgZoomIn
/>
<template v-else> -</template>
</el-card>
</el-scrollbar>
</div>
</div>
</template>
<template v-if="item.type === WorkflowType.SpeechToTextNode">
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('common.param.inputParam') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<div class="mb-8">
<div v-if="item.audio_list?.length > 0">
<p class="mb-8 color-secondary">
{{ $t('chat.executionDetails.audioFile') }}:
</p>
<el-space wrap>
<template v-for="(f, i) in item.audio_list" :key="i">
<audio
:src="f.url"
controls
style="width: 300px; height: 43px"
class="border-r-6"
/>
</template>
</el-space>
</div>
</div>
</div>
</div>
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('common.param.outputParam') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<el-card
shadow="never"
style="--el-card-padding: 8px"
v-for="(file_content, index) in item.content"
:key="index"
class="mb-8"
>
<MdPreview
v-if="file_content"
ref="editorRef"
editorId="preview-only"
:modelValue="file_content"
style="background: none"
noImgZoomIn
/>
<template v-else> -</template>
</el-card>
</div>
</div>
</template>
<template v-if="item.type === WorkflowType.TextToSpeechNode">
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('common.param.inputParam') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<div class="p-8-12 border-t-dashed lighter">
<p class="mb-8 color-secondary">
{{ $t('chat.executionDetails.textContent') }}:
</p>
<div v-if="item.content">
<MdPreview
ref="editorRef"
editorId="preview-only"
:modelValue="item.content"
style="background: none"
noImgZoomIn
/>
</div>
</div>
</div>
</div>
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('common.param.outputParam') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<p class="mb-8 color-secondary">
{{ $t('chat.executionDetails.audioFile') }}:
</p>
<div v-if="item.answer" v-html="item.answer"></div>
</div>
</div>
</template>
<!-- 函数库 -->
<template
v-if="
item.type === WorkflowType.ToolLib ||
item.type === WorkflowType.ToolLibCustom
"
>
<div class="card-never border-r-6 mt-8">
<h5 class="p-8-12">{{ $t('chat.executionDetails.input') }}</h5>
<div class="p-8-12 border-t-dashed lighter pre-wrap">
{{ item.params || '-' }}
</div>
</div>
<div class="card-never border-r-6 mt-8">
<h5 class="p-8-12">{{ $t('chat.executionDetails.output') }}</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-6">
<h5 class="p-8-12">
{{ $t('chat.executionDetails.searchContent') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">{{ item.question || '-' }}</div>
</div>
<div class="card-never border-r-6 mt-8">
<h5 class="p-8-12">
{{ $t('chat.executionDetails.rerankerContent') }}
</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"
>
<ParagraphCard
:data="paragraph.metadata"
:content="paragraph.page_content"
:index="paragraphIndex"
/>
</template>
</template>
<template v-else> -</template>
</div>
</div>
<div class="card-never border-r-6 mt-8">
<h5 class="p-8-12">
{{ $t('chat.executionDetails.rerankerResult') }}
</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"
>
<ParagraphCard
:data="paragraph.metadata"
:content="paragraph.page_content"
:index="paragraphIndex"
:score="paragraph.metadata?.relevance_score"
/>
</template>
</template>
<template v-else> -</template>
</div>
</div>
</template>
<!-- 表单收集 -->
<template v-if="item.type === WorkflowType.FormNode">
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('common.param.outputParam')
}}<span style="color: #f54a45">{{
item.is_submit ? '' : `(${$t('chat.executionDetails.noSubmit')})`
}}</span>
</h5>
<div class="p-8-12 border-t-dashed lighter">
<DynamicsForm
:disabled="true"
label-position="top"
require-asterisk-position="right"
ref="dynamicsFormRef"
:render_data="item.form_field_list"
label-suffix=":"
v-model="item.form_data"
:model="item.form_data"
></DynamicsForm>
</div>
</div>
</template>
<!-- 图片理解 -->
<template v-if="item.type == WorkflowType.ImageUnderstandNode">
<div
class="card-never border-r-6"
v-if="item.type !== WorkflowType.Application"
>
<h5 class="p-8-12">
{{ $t('views.application.applicationForm.form.roleSettings.label') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
{{ item.system || '-' }}
</div>
</div>
<div
class="card-never border-r-6 mt-8"
v-if="item.type !== WorkflowType.Application"
>
<h5 class="p-8-12">{{ $t('chat.history') }}</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: inline-block"
class="border-r-6 mr-8"
/>
<span v-else>{{ h.text }}<br /></span>
</template>
</span>
<span v-else>{{ history.content }}</span>
</p>
</template>
<template v-else> -</template>
</div>
</div>
<div class="card-never border-r-6 mt-8">
<h5 class="p-8-12">
{{ $t('chat.executionDetails.currentChat') }}
</h5>
<div class="p-8-12 border-t-dashed lighter pre-wrap">
<div v-if="item.image_list?.length > 0">
<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-6"
/>
</template>
</el-space>
</div>
<div>
{{ item.question || '-' }}
</div>
</div>
</div>
<div class="card-never border-r-6 mt-8">
<h5 class="p-8-12">
{{
item.type == WorkflowType.Application
? $t('common.param.outputParam')
: $t('chat.executionDetails.answer')
}}
</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"
noImgZoomIn
/>
<template v-else> -</template>
</div>
</div>
</template>
<!-- 图片生成 -->
<template v-if="item.type == WorkflowType.ImageGenerateNode">
<div class="card-never border-r-6 mt-8">
<h5 class="p-8-12">
{{ $t('chat.executionDetails.currentChat') }}
</h5>
<div class="p-8-12 border-t-dashed lighter pre-wrap">
{{ item.question || '-' }}
</div>
</div>
<div class="card-never border-r-6 mt-8">
<h5 class="p-8-12">
{{
item.type == WorkflowType.Application
? $t('common.param.outputParam')
: $t('chat.executionDetails.answer')
}}
</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"
noImgZoomIn
/>
<template v-else> -</template>
</div>
</div>
</template>
<!-- 变量赋值 -->
<template v-if="item.type === WorkflowType.VariableAssignNode">
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('common.param.inputParam') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<div v-for="(f, i) in item.result_list" :key="i" class="mb-8">
<span class="color-secondary">{{ f.name }}:</span> {{ f.input_value }}
</div>
</div>
</div>
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('common.param.outputParam') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<div v-for="(f, i) in item.result_list" :key="i" class="mb-8">
<span class="color-secondary">{{ f.name }}:</span> {{ f.output_value }}
</div>
</div>
</div>
</template>
<!-- MCP 节点 -->
<template v-if="item.type === WorkflowType.McpNode">
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('views.applicationWorkflow.nodes.mcpNode.tool') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<div class="mb-8">
<span class="color-secondary"> {{ $t('views.applicationWorkflow.nodes.mcpNode.tool') }}: </span> {{ item.mcp_tool }}
</div>
</div>
</div>
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('views.applicationWorkflow.nodes.mcpNode.toolParam') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<div v-for="(value, name) in item.tool_params" :key="name" class="mb-8">
<span class="color-secondary">{{ name }}:</span> {{ value }}
</div>
</div>
</div>
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('common.param.outputParam') }}
</h5>
<div class="p-8-12 border-t-dashed lighter">
<div v-for="(f, i) in item.result" :key="i" class="mb-8">
<span class="color-secondary">result:</span> {{ f }}
</div>
</div>
</div>
</template>
</template>
<template v-else>
<div class="card-never border-r-6">
<h5 class="p-8-12">{{ $t('chat.executionDetails.errMessage') }}</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>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import ParagraphCard from '@/components/ai-chat/component/ParagraphCard.vue'
import { arraySort } from '@/utils/utils'
import { iconComponent } from '@/workflow/icons/utils'
import { WorkflowType } from '@/enums/application'
import { getImgUrl } from '@/utils/utils'
import DynamicsForm from '@/components/dynamics-form/index.vue'
const props = defineProps<{
detail?: any[]
}>()
const current = ref<number | string>('')
</script>
<style lang="scss" scoped>
.execution-details {
.arrow-icon {
transition: 0.2s;
}
}
</style>

View File

@ -43,6 +43,8 @@
<KnowledgeSource <KnowledgeSource
:data="chatRecord" :data="chatRecord"
:type="application.type" :type="application.type"
:executionIsRightPanel="props.executionIsRightPanel"
@open-execution-detail="emit('openExecutionDetail')"
v-if="showSource(chatRecord) && index === chatRecord.answer_text_list.length - 1" v-if="showSource(chatRecord) && index === chatRecord.answer_text_list.length - 1"
/> />
</el-card> </el-card>
@ -83,11 +85,12 @@ const props = defineProps<{
sendMessage: (question: string, other_params_data?: any, chat?: chatType) => Promise<boolean> sendMessage: (question: string, other_params_data?: any, chat?: chatType) => Promise<boolean>
chatManagement: any chatManagement: any
type: 'log' | 'ai-chat' | 'debug-ai-chat' type: 'log' | 'ai-chat' | 'debug-ai-chat'
executionIsRightPanel?: boolean
}>() }>()
const { user } = useStore() const { user } = useStore()
const emit = defineEmits(['update:chatRecord']) const emit = defineEmits(['update:chatRecord', 'openExecutionDetail'])
const showAvatar = computed(() => { const showAvatar = computed(() => {
return user.isEnterprise() ? props.application.show_avatar : true return user.isEnterprise() ? props.application.show_avatar : true

View File

@ -48,6 +48,8 @@
:type="type" :type="type"
:send-message="sendMessage" :send-message="sendMessage"
:chat-management="ChatManagement" :chat-management="ChatManagement"
:executionIsRightPanel="props.executionIsRightPanel"
@open-execution-detail="emit('openExecutionDetail', chatList[index])"
></AnswerContent> ></AnswerContent>
</template> </template>
<TransitionContent <TransitionContent
@ -119,6 +121,7 @@ const props = withDefaults(
record?: Array<chatType> record?: Array<chatType>
available?: boolean available?: boolean
chatId?: string chatId?: string
executionIsRightPanel?: boolean
}>(), }>(),
{ {
applicationDetails: () => ({}), applicationDetails: () => ({}),
@ -126,7 +129,7 @@ const props = withDefaults(
type: 'ai-chat', type: 'ai-chat',
}, },
) )
const emit = defineEmits(['refresh', 'scroll']) const emit = defineEmits(['refresh', 'scroll', 'openExecutionDetail'])
const { application, common } = useStore() const { application, common } = useStore()
const isMobile = computed(() => { const isMobile = computed(() => {
return common.isMobile() || mode === 'embed' || mode === 'mobile' return common.isMobile() || mode === 'embed' || mode === 'mobile'

View File

@ -1,4 +1,7 @@
export default { export default {
mine: 'Mine',
logoutContent: 'Logging out will not lose any data. You can still log in to this account.',
confirmModification: 'Confirm modification',
noHistory: 'No Chat History', noHistory: 'No Chat History',
createChat: 'New Chat', createChat: 'New Chat',
history: 'Chat History', history: 'Chat History',

View File

@ -1,4 +1,7 @@
export default { export default {
mine: '我的',
logoutContent: '退出登入不會遺失任何資料,您仍可登入此帳號。',
confirmModification: '確認修改',
noHistory: '暫無歷史記錄', noHistory: '暫無歷史記錄',
createChat: '新建對話', createChat: '新建對話',
history: '歷史記錄', history: '歷史記錄',

View File

@ -183,54 +183,69 @@
</el-button> </el-button>
</div> </div>
<div class="chat-pc__right"> <div class="chat-pc__right">
<div class="mb-24 p-16-24 flex-between"> <el-splitter>
<h4 class="ellipsis-1" style="width: 66%"> <el-splitter-panel>
{{ currentChatName }} <div class="mb-24 p-16-24 flex-between">
</h4> <h4 class="ellipsis-1" style="width: 66%">
{{ currentChatName }}
</h4>
<span class="flex align-center" v-if="currentRecordList.length"> <span class="flex align-center" v-if="currentRecordList.length">
<AppIcon <AppIcon
v-if="paginationConfig.total" v-if="paginationConfig.total"
iconName="app-chat-record" iconName="app-chat-record"
class="info mr-8" class="info mr-8"
style="font-size: 16px" style="font-size: 16px"
></AppIcon> ></AppIcon>
<span v-if="paginationConfig.total" class="lighter"> <span v-if="paginationConfig.total" class="lighter">
{{ paginationConfig.total }} {{ $t('chat.question_count') }} {{ paginationConfig.total }} {{ $t('chat.question_count') }}
</span> </span>
<el-dropdown class="ml-8"> <el-dropdown class="ml-8">
<AppIcon <AppIcon
iconName="app-export" iconName="app-export"
class="cursor" class="cursor"
:title="$t('chat.exportRecords')" :title="$t('chat.exportRecords')"
></AppIcon> ></AppIcon>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item @click="exportMarkdown" <el-dropdown-item @click="exportMarkdown"
>{{ $t('common.export') }} Markdown</el-dropdown-item >{{ $t('common.export') }} Markdown</el-dropdown-item
> >
<el-dropdown-item @click="exportHTML" <el-dropdown-item @click="exportHTML"
>{{ $t('common.export') }} HTML</el-dropdown-item >{{ $t('common.export') }} HTML</el-dropdown-item
> >
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
</span> </span>
</div> </div>
<div class="right-height chat-width"> <div class="right-height chat-width">
<AiChat <AiChat
ref="AiChatRef" ref="AiChatRef"
v-model:applicationDetails="applicationDetail" v-model:applicationDetails="applicationDetail"
:available="applicationAvailable" :available="applicationAvailable"
type="ai-chat" type="ai-chat"
:appId="applicationDetail?.id" :appId="applicationDetail?.id"
:record="currentRecordList" :record="currentRecordList"
:chatId="currentChatId" :chatId="currentChatId"
@refresh="refresh" executionIsRightPanel
@scroll="handleScroll" @refresh="refresh"
> @scroll="handleScroll"
</AiChat> @open-execution-detail="openExecutionDetail"
</div> >
</AiChat>
</div>
</el-splitter-panel>
<el-splitter-panel class="execution-detail-panel" v-model:size="rightPanelSize" :resizable="false" collapsible>
<div class="p-16 flex-between border-b">
<h4 class="medium">{{ $t('chat.executionDetails.title') }}</h4>
<el-icon size="20" class="cursor" @click="closeExecutionDetail"><Close /></el-icon>
</div>
<div class="execution-detail-content" v-loading="executionLoading">
<ExecutionDetailContent :detail="executionDetail" />
</div>
</el-splitter-panel>
</el-splitter>
</div> </div>
</div> </div>
<div class="collapse"> <div class="collapse">
@ -258,6 +273,8 @@ import { useRouter } from 'vue-router'
import ResetPassword from '@/layout/layout-header/avatar/ResetPassword.vue' import ResetPassword from '@/layout/layout-header/avatar/ResetPassword.vue'
import { t } from '@/locales' import { t } from '@/locales'
import type { ResetCurrentUserPasswordRequest } from '@/api/type/user' import type { ResetCurrentUserPasswordRequest } from '@/api/type/user'
import ExecutionDetailContent from '@/components/ai-chat/component/ExecutionDetailContent.vue'
import { cloneDeep } from 'lodash'
useResize() useResize()
@ -511,6 +528,22 @@ const init = () => {
onMounted(() => { onMounted(() => {
init() init()
}) })
const rightPanelSize = ref(0)
const executionDetail = ref<any[]>([])
const executionLoading = ref(false)
async function openExecutionDetail(row: any) {
rightPanelSize.value = 400
if (row.execution_details) {
executionDetail.value = cloneDeep(row.execution_details)
} else {
const res = await chatAPI.getChatRecord(row.chat_id, row.record_id, executionLoading)
executionDetail.value = cloneDeep(res.data.execution_details)
}
}
function closeExecutionDetail() {
rightPanelSize.value = 0
}
</script> </script>
<style lang="scss"> <style lang="scss">
.chat-pc { .chat-pc {
@ -627,6 +660,24 @@ onMounted(() => {
.right-height { .right-height {
height: calc(100vh - 85px); height: calc(100vh - 85px);
} }
.el-splitter-bar__collapse-icon, .el-splitter-bar__dragger {
display: none;
}
.execution-detail-panel {
background: #ffffff;
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
.execution-detail-content {
flex: 1;
overflow: hidden;
.execution-details {
padding: 16px;
}
}
}
} }
.gradient-divider { .gradient-divider {