feat: improve code formatting and add video list handling in chat components

This commit is contained in:
wxg0103 2025-10-15 10:23:37 +08:00
parent 629c6ee3a8
commit ef0713ecc6
6 changed files with 120 additions and 53 deletions

View File

@ -1,6 +1,7 @@
import { type Dict } from '@/api/type/common'
import { type Ref } from 'vue'
import {type Dict} from '@/api/type/common'
import {type Ref} from 'vue'
import bus from '@/bus'
interface ApplicationFormType {
name?: string
desc?: string
@ -35,6 +36,7 @@ interface ApplicationFormType {
tool_ids?: string[]
mcp_output_enable?: boolean
}
interface Chunk {
real_node_id: string
chat_id: string
@ -50,6 +52,7 @@ interface Chunk {
runtime_node_id: string
child_node: any
}
interface chatType {
id: string
problem_text: string
@ -82,6 +85,7 @@ interface chatType {
document_list: Array<any>
image_list: Array<any>
audio_list: Array<any>
video_list: Array<any>
other_list: Array<any>
}
}
@ -95,6 +99,7 @@ interface Node {
index: number
is_end: boolean
}
interface WriteNodeInfo {
current_node: any
answer_text_list_index: number
@ -102,6 +107,7 @@ interface WriteNodeInfo {
divider_content?: Array<string>
divider_reasoning_content?: Array<string>
}
export class ChatRecordManage {
id?: any
ms: number
@ -112,6 +118,7 @@ export class ChatRecordManage {
loading?: Ref<boolean>
node_list: Array<any>
write_node_info?: WriteNodeInfo
constructor(chat: chatType, ms?: number, loading?: Ref<boolean>) {
this.ms = ms ? ms : 10
this.chat = chat
@ -121,6 +128,7 @@ export class ChatRecordManage {
this.write_ed = false
this.node_list = []
}
append_answer(
chunk_answer: string,
reasoning_content: string,
@ -157,8 +165,9 @@ export class ChatRecordManage {
}
}
this.chat.answer_text = this.chat.answer_text + chunk_answer
bus.emit('change:answer', { record_id: this.chat.record_id, is_end: false })
bus.emit('change:answer', {record_id: this.chat.record_id, is_end: false})
}
get_current_up_node(run_node: any) {
const index = this.node_list.findIndex((item) => item == run_node)
if (index > 0) {
@ -167,6 +176,7 @@ export class ChatRecordManage {
}
return undefined
}
get_run_node() {
if (
this.write_node_info &&
@ -225,6 +235,7 @@ export class ChatRecordManage {
}
return undefined
}
findIndex<T>(array: Array<T>, find: (item: T) => boolean, type: 'last' | 'index') {
let set_index = -1
for (let index = 0; index < array.length; index++) {
@ -238,13 +249,14 @@ export class ChatRecordManage {
}
return set_index
}
closeInterval() {
this.chat.write_ed = true
this.write_ed = true
if (this.loading) {
this.loading.value = false
}
bus.emit('change:answer', { record_id: this.chat.record_id, is_end: true })
bus.emit('change:answer', {record_id: this.chat.record_id, is_end: true})
if (this.id) {
clearInterval(this.id)
}
@ -257,6 +269,7 @@ export class ChatRecordManage {
this.chat.answer_text_list.splice(last_index, 1)
}
}
write() {
this.chat.is_stop = false
this.is_stop = false
@ -277,21 +290,21 @@ export class ChatRecordManage {
}
return
}
const { current_node, answer_text_list_index } = node_info
const {current_node, answer_text_list_index} = node_info
if (current_node.buffer.length > 20) {
const context = current_node.is_end
? current_node.buffer.splice(0)
: current_node.buffer.splice(
0,
current_node.is_end ? undefined : current_node.buffer.length - 20,
)
0,
current_node.is_end ? undefined : current_node.buffer.length - 20,
)
const reasoning_content = current_node.is_end
? current_node.reasoning_content_buffer.splice(0)
: current_node.reasoning_content_buffer.splice(
0,
current_node.is_end ? undefined : current_node.reasoning_content_buffer.length - 20,
)
0,
current_node.is_end ? undefined : current_node.reasoning_content_buffer.length - 20,
)
this.append_answer(
context.join(''),
reasoning_content.join(''),
@ -354,6 +367,7 @@ export class ChatRecordManage {
}
}, this.ms)
}
stop() {
clearInterval(this.id)
this.is_stop = true
@ -362,13 +376,16 @@ export class ChatRecordManage {
this.loading.value = false
}
}
close() {
this.is_close = true
}
open() {
this.is_close = false
this.is_stop = false
}
appendChunk(chunk: Chunk) {
let n = this.node_list.find((item) => item.real_node_id == chunk.real_node_id)
if (n) {
@ -401,6 +418,7 @@ export class ChatRecordManage {
n['is_end'] = true
}
}
append(answer_text_block: string, reasoning_content?: string) {
let set_index = this.findIndex(
this.chat.answer_text_list,
@ -425,24 +443,28 @@ export class ChatManagement {
static addChatRecord(chat: chatType, ms: number, loading?: Ref<boolean>) {
this.chatMessageContainer[chat.id] = new ChatRecordManage(chat, ms, loading)
}
static appendChunk(chatRecordId: string, chunk: Chunk) {
const chatRecord = this.chatMessageContainer[chatRecordId]
if (chatRecord) {
chatRecord.appendChunk(chunk)
}
}
static append(chatRecordId: string, content: string, reasoning_content?: string) {
const chatRecord = this.chatMessageContainer[chatRecordId]
if (chatRecord) {
chatRecord.append(content, reasoning_content)
}
}
static updateStatus(chatRecordId: string, code: number) {
const chatRecord = this.chatMessageContainer[chatRecordId]
if (chatRecord) {
chatRecord.chat.status = code
}
}
/**
*
* @param chatRecordId id
@ -453,12 +475,14 @@ export class ChatManagement {
chatRecord.write()
}
}
static open(chatRecordId: string) {
const chatRecord = this.chatMessageContainer[chatRecordId]
if (chatRecord) {
chatRecord.open()
}
}
/**
*
* @param chatRecordId id
@ -470,6 +494,7 @@ export class ChatManagement {
chatRecord.close()
}
}
/**
*
* @param chatRecordId id
@ -481,6 +506,7 @@ export class ChatManagement {
chatRecord.stop()
}
}
/**
*
* @param chatRecordId id
@ -490,6 +516,7 @@ export class ChatManagement {
const chatRecord = this.chatMessageContainer[chatRecordId]
return chatRecord ? chatRecord.is_close && chatRecord.write_ed : false
}
/**
*
* @param chatRecordId id
@ -499,6 +526,7 @@ export class ChatManagement {
const chatRecord = this.chatMessageContainer[chatRecordId]
return chatRecord ? chatRecord.is_stop : false
}
/**
* close掉的和stop的数据
*/
@ -510,4 +538,5 @@ export class ChatManagement {
}
}
}
export type { ApplicationFormType, chatType }
export type {ApplicationFormType, chatType}

View File

@ -1,6 +1,5 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.3335 3.33333C5.3335 2.59695 5.93045 2 6.66683 2H19.724C19.9008 2 20.0704 2.07024 20.1954 2.19526L26.4716 8.47141C26.5966 8.59643 26.6668 8.766 26.6668 8.94281V28.6667C26.6668 29.403 26.0699 30 25.3335 30H6.66683C5.93045 30 5.3335 29.403 5.3335 28.6667V3.33333Z" fill="#14C0FF"/>
<path d="M20 2.05988C20.072 2.09264 20.1383 2.13825 20.1953 2.19526L26.4714 8.4714C26.5284 8.52841 26.574 8.59467 26.6068 8.66666H21.3333C20.597 8.66666 20 8.06971 20 7.33333V2.05988Z" fill="#11A3D9"/>
<path d="M11.3335 16C12.4381 16 13.3335 15.1046 13.3335 14C13.3335 12.8954 12.4381 12 11.3335 12C10.2289 12 9.3335 12.8954 9.3335 14C9.3335 15.1046 10.2289 16 11.3335 16Z" fill="white"/>
<path d="M22.2785 14.9317C22.4218 14.7884 22.6668 14.8899 22.6668 15.0925V24.0645C22.6668 24.1901 22.565 24.2919 22.4394 24.2919H13.4674L13.4587 24.2918H9.56142C9.35877 24.2918 9.25728 24.0468 9.40058 23.9035L14.366 18.938C14.4549 18.8492 14.5989 18.8492 14.6877 18.938L16.48 20.7302L22.2785 14.9317Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.00018 2.5C4.00018 1.94772 4.4479 1.5 5.00018 1.5H14.7931C14.9257 1.5 15.0529 1.55268 15.1466 1.64645L19.8537 6.35355C19.9475 6.44732 20.0002 6.5745 20.0002 6.70711V21.5C20.0002 22.0523 19.5525 22.5 19.0002 22.5H5.00018C4.4479 22.5 4.00018 22.0523 4.00018 21.5V2.5Z" fill="#34C724"/>
<path d="M15 1.54492C15.054 1.56949 15.1037 1.6037 15.1464 1.64646L19.8536 6.35357C19.8963 6.39632 19.9305 6.44602 19.9551 6.50001H16C15.4477 6.50001 15 6.0523 15 5.50001V1.54492Z" fill="#2CA91F"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.5 11.5C7.5 11.2239 7.72386 11 8 11H13.3971C13.6733 11 13.8971 11.2239 13.8971 11.5V12.1723L15.8207 11.4333C15.9744 11.3742 16.1474 11.3946 16.2831 11.4879C16.4189 11.5812 16.5 11.7353 16.5 11.9V16.3C16.5 16.4647 16.4189 16.6188 16.2831 16.7121C16.1474 16.8054 15.9744 16.8258 15.8207 16.7667L13.8971 16.0277V16.7C13.8971 16.9761 13.6733 17.2 13.3971 17.2H8C7.72386 17.2 7.5 16.9761 7.5 16.7V11.5ZM9.25 13.5C9.66421 13.5 10 13.1642 10 12.75C10 12.3358 9.66421 12 9.25 12C8.83579 12 8.5 12.3358 8.5 12.75C8.5 13.1642 8.83579 13.5 9.25 13.5Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -180,8 +180,9 @@
<video
v-if="item.url"
:src="item.url"
controls style="width: 40px; height: 40px; display: block"
controls style="width: 100px; display: block"
class="border-r-6"
autoplay
/>
</div>
</template>

View File

@ -3,7 +3,7 @@
<div class="flex-between cursor" @click="data['show'] = !data['show']">
<div class="flex align-center">
<el-icon class="mr-8 arrow-icon" :class="data['show'] ? 'rotate-90' : ''">
<CaretRight />
<CaretRight/>
</el-icon>
<component
:is="iconComponent(`${data.type}-icon`)"
@ -24,14 +24,14 @@
data.type === WorkflowType.Application ||
data.type == WorkflowType.IntentNode
"
>{{ data?.message_tokens + data?.answer_tokens }} tokens</span
>{{ data?.message_tokens + data?.answer_tokens }} tokens</span
>
<span class="mr-16 color-secondary">{{ data?.run_time?.toFixed(2) || 0.0 }} s</span>
<el-icon class="color-success" :size="16" v-if="data.status === 200">
<CircleCheck />
<CircleCheck/>
</el-icon>
<el-icon class="color-danger" :size="16" v-else>
<CircleClose />
<CircleClose/>
</el-icon>
</div>
</div>
@ -64,7 +64,7 @@
<template v-for="(f, i) in data.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" />
<img :src="getImgUrl(f && f?.name)" alt="" width="24"/>
<div class="ml-4 ellipsis" :title="f && f?.name">
{{ f && f?.name }}
</div>
@ -109,7 +109,7 @@
<template v-for="(f, i) in data.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" />
<img :src="getImgUrl(f && f?.name)" alt="" width="24"/>
<div class="ml-4 ellipsis" :title="f && f?.name">
{{ f && f?.name }}
</div>
@ -448,7 +448,8 @@
<template v-if="data.type === WorkflowType.FormNode">
<div class="card-never border-r-6">
<h5 class="p-8-12">
{{ $t('common.param.outputParam')
{{
$t('common.param.outputParam')
}}<span style="color: #f54a45">{{
data.is_submit ? '' : `(${$t('chat.executionDetails.noSubmit')})`
}}</span>
@ -500,7 +501,7 @@
class="border-r-6 mr-8"
/>
<span v-else>{{ h.text }}<br /></span>
<span v-else>{{ h.text }}<br/></span>
</template>
</span>
@ -555,7 +556,7 @@
</div>
</template>
<!-- 视频理解 -->
<template v-if="data.type == WorkflowType.VideoUnderstandNode">
<template v-if="data.type == WorkflowType.VideoUnderstandNode">
<div class="card-never border-r-6" v-if="data.type !== WorkflowType.Application">
<h5 class="p-8-12">
{{ $t('views.application.form.roleSettings.label') }}
@ -577,7 +578,7 @@
<span v-if="Array.isArray(history.content)">
<template v-for="(h, i) in history.content" :key="i">
<el-image
<video
v-if="h.type === 'video_url'"
:src="h.video_url.url"
alt=""
@ -586,7 +587,7 @@
class="border-r-6 mr-8"
/>
<span v-else>{{ h.text }}<br /></span>
<span v-else>{{ h.text }}<br/></span>
</template>
</span>
@ -601,15 +602,15 @@
{{ $t('chat.executionDetails.currentChat') }}
</h5>
<div class="p-8-12 border-t-dashed lighter pre-wrap">
<div v-if="data.video_url?.length > 0">
<div v-if="data.video_list?.length > 0">
<el-space wrap>
<template v-for="(f, i) in data.video_url" :key="i">
<el-image
<template v-for="(f, i) in data.video_list" :key="i">
<video
:src="f.url"
alt=""
fit="cover"
style="width: 40px; height: 40px; display: block"
style="width: 100px; display: block"
class="border-r-6"
autoplay
controls
/>
</template>
</el-space>
@ -905,7 +906,7 @@
<template v-if="data.type === WorkflowType.LoopNode">
<el-radio-group v-model="currentLoopNode" class="app-radio-button-group mb-8">
<template v-for="(loop, loopIndex) in data.loop_node_data" :key="loopIndex">
<el-radio-button :label="loopIndex" :value="loopIndex" />
<el-radio-button :label="loopIndex" :value="loopIndex"/>
</template>
</el-radio-group>
<template
@ -993,15 +994,17 @@
</el-card>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import ParagraphCard from '@/components/ai-chat/component/knowledge-source-component/ParagraphCard.vue'
import {ref, computed} from 'vue'
import ParagraphCard
from '@/components/ai-chat/component/knowledge-source-component/ParagraphCard.vue'
import DynamicsForm from '@/components/dynamics-form/index.vue'
import { iconComponent } from '@/workflow/icons/utils'
import { WorkflowType } from '@/enums/application'
import { getImgUrl } from '@/utils/common'
import { arraySort } from '@/utils/array'
import {iconComponent} from '@/workflow/icons/utils'
import {WorkflowType} from '@/enums/application'
import {getImgUrl} from '@/utils/common'
import {arraySort} from '@/utils/array'
import {t} from '@/locales'
import { t } from '@/locales'
const props = defineProps<{
data: any
}>()

View File

@ -9,12 +9,12 @@
<el-card shadow="never" style="--el-card-padding: 8px" class="download-file cursor">
<div class="download-button flex align-center" @click="downloadFile(item)">
<el-icon class="mr-4">
<Download />
<Download/>
</el-icon>
{{ $t('chat.download') }}
</div>
<div class="show flex align-center">
<img :src="getImgUrl(item && item?.name)" alt="" width="24" />
<img :src="getImgUrl(item && item?.name)" alt="" width="24"/>
<div class="ml-4 ellipsis-1" :title="item && item?.name">
{{ item && item?.name }}
</div>
@ -57,18 +57,33 @@
</template>
</el-space>
</div>
<div class="mb-8" v-if="video_list.length">
<el-space wrap>
<template v-for="(item, index) in video_list" :key="index">
<div class="file cursor border-r-6" v-if="item.url">
<video
:src="item.url"
style="width: 170px; display: block"
class="border-r-6"
controls
autoplay
/>
</div>
</template>
</el-space>
</div>
<div class="mb-8" v-if="other_list.length">
<el-space wrap class="w-full media-file-width">
<template v-for="(item, index) in other_list" :key="index">
<el-card shadow="never" style="--el-card-padding: 8px" class="download-file cursor">
<div class="download-button flex align-center" @click="downloadFile(item)">
<el-icon class="mr-4">
<Download />
<Download/>
</el-icon>
{{ $t('chat.download') }}
</div>
<div class="show flex align-center">
<img :src="getImgUrl(item && item?.name)" alt="" width="24" />
<img :src="getImgUrl(item && item?.name)" alt="" width="24"/>
<div class="ml-4 ellipsis-1" :title="item && item?.name">
{{ item && item?.name }}
</div>
@ -89,16 +104,17 @@
style="width: 28px; height: 28px; display: block"
/>
<el-avatar v-else :size="28">
<img src="@/assets/user-icon.svg" style="width: 50%" alt="" />
<img src="@/assets/user-icon.svg" style="width: 50%" alt=""/>
</el-avatar>
</div>
</div>
</template>
<script setup lang="ts">
import { type chatType } from '@/api/type/application'
import { getImgUrl, downloadByURL } from '@/utils/common'
import { getAttrsArray } from '@/utils/array'
import { onMounted, computed } from 'vue'
import {type chatType} from '@/api/type/application'
import {getImgUrl, downloadByURL} from '@/utils/common'
import {getAttrsArray} from '@/utils/array'
import {onMounted, computed} from 'vue'
const props = defineProps<{
application: any
chatRecord: chatType
@ -127,6 +143,15 @@ const image_list = computed(() => {
)
return startNode?.image_list || []
})
const video_list = computed(() => {
if (props.chatRecord?.upload_meta) {
return props.chatRecord.upload_meta?.video_list || []
}
const startNode = props.chatRecord.execution_details?.find(
(detail) => detail.type === 'start-node',
)
return startNode?.video_list || []
})
const audio_list = computed(() => {
if (props.chatRecord?.upload_meta) {
return props.chatRecord.upload_meta?.audio_list || []
@ -154,11 +179,13 @@ const getClassName = computed(() => {
? `media_${other_list.value.length}`
: `media_0`
})
function downloadFile(item: any) {
downloadByURL(item.url, item.name)
}
onMounted(() => {})
onMounted(() => {
})
</script>
<style lang="scss" scoped>
.question-content {
@ -196,21 +223,26 @@ onMounted(() => {})
display: none;
}
}
.media-file-width {
:deep(.el-space__item) {
width: 49% !important;
}
}
.media_2 {
flex: 1;
}
.media_0 {
flex: inherit;
}
.media_1 {
width: 50%;
}
}
@media only screen and (max-width: 768px) {
.question-content {
.media-file-width {
@ -218,11 +250,13 @@ onMounted(() => {})
min-width: 100% !important;
}
}
.media_1 {
width: 100%;
}
}
}
.debug-ai-chat {
.question-content {
.media-file-width {
@ -230,6 +264,7 @@ onMounted(() => {})
min-width: 100% !important;
}
}
.media_1 {
width: 100%;
}

View File

@ -27,7 +27,7 @@ export default {
RERANKER: '重排模型',
STT: '語音辨識',
TTS: '語音合成',
IMAGE: '圖片理解',
IMAGE: '視覺理解',
TTI: '圖片生成',
TTV: '文生視頻',
ITV: '圖生視頻',