Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 41 additions & 41 deletions ui/src/conversation-panel/components/content-list/index.vue
Original file line number Diff line number Diff line change
@@ -1,60 +1,60 @@
<template>
<div class="content-list">
<div v-for="(content, index) in contents" :key="`${content.type}:${content.id ?? index}`" class="content-item">
<Content :type="content.type" :content="content" />
</div>
</div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import Content from '@/conversation-panel/components/content/index.vue'

const props = defineProps<{ contentList: Array<any> }>()
defineOptions({ name: 'ConversationContentList' })

interface ContentItem {
type: string
id?: string
content?: unknown
children?: ContentItem[]
[key: string]: unknown
}

const props = defineProps<{ contentList: ContentItem[] }>()

const contents = computed(() => {
// 隐藏审批和空正文;sort 为稳定排序,相同 id 的内容保留原始顺序。
const displayContents = computed(() => {
const sortedContents = props.contentList
.filter((item) => item.type !== 'APPROVAL')
.map((item, index) => ({ item, index }))
.sort((a, b) => {
const idA = a.item.id || ''
const idB = b.item.id || ''
if (idA !== idB) {
return idA.localeCompare(idB)
}
return a.index - b.index
.filter((content) => {
if (content.type === 'APPROVAL') return false
return !((content.type === 'TEXT' || content.type === 'HTML') && !content.content)
})
.map(({ item }) => item)
.filter((item) => !((item.type === 'TEXT' || item.type === 'HTML') && !item.content))
.sort((leftContent, rightContent) => (leftContent.id || '').localeCompare(rightContent.id || ''))

return foldToolGroups(sortedContents)
})

// 排序后相邻的 TOOL(2 个起)折叠为 GROUP_TOOL;仅为展示结构,不写回数据。
// 分组 id 取首个工具 id,流式追加时保持不变,组件得以复用
function foldToolGroups(contentItems: any[]) {
const foldedContents: any[] = []
for (const item of contentItems) {
const last = foldedContents[foldedContents.length - 1]
if (item.type === 'TOOL' && last?.type === 'GROUP_TOOL') {
last.children.push(item)
} else if (item.type === 'TOOL' && last?.type === 'TOOL') {
foldedContents[foldedContents.length - 1] = { type: 'GROUP_TOOL', id: `${last.id}_group`, children: [last, item] }
function foldToolGroups(contentItems: ContentItem[]): ContentItem[] {
const foldedContents: ContentItem[] = []

for (const content of contentItems) {
const lastContent = foldedContents[foldedContents.length - 1]

if (content.type === 'TOOL' && lastContent?.type === 'GROUP_TOOL') {
lastContent.children!.push(content)
} else if (content.type === 'TOOL' && lastContent?.type === 'TOOL') {
foldedContents[foldedContents.length - 1] = {
type: 'GROUP_TOOL',
id: `${lastContent.id}_group`,
children: [lastContent, content],
}
} else {
foldedContents.push(item)
foldedContents.push(content)
}
}

return foldedContents
}
</script>

<style scoped lang="scss">
.content-list {
display: flex;
flex-direction: column;
width: 100%;
}

.content-item {
width: 100%;
}
</style>
<template>
<div class="w-full">
<template v-for="(content, index) in displayContents" :key="`${content.type}:${content.id ?? index}`">
<Content :content="content" />
</template>
</div>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function getAttachmentDescription(file: QuestionAttachment) {
<template>
<div class="mk-conversation-question-content space-y-3">
<!-- 查看已发送附件,超出当前宽度时自动换行。 -->
<div v-if="attachments.length" class="flex max-w-full flex-wrap items-start justify-end gap-2">
<div v-if="attachments.length" class="flex max-w-full gap-2">
<template v-for="file in attachments" :key="file.key">
<div :class="file.field === 'image_list' ? 'shrink-0' : 'w-60 max-w-full'">
<!-- 查看图片 -->
Expand Down Expand Up @@ -80,6 +80,6 @@ function getAttachmentDescription(file: QuestionAttachment) {
</div>
</template>
</div>
<div v-if="content.content" class="mk-gray-card-lg ml-auto w-fit max-w-full py-3! whitespace-pre-wrap wrap-anywhere">{{ content.content }}</div>
<div v-if="content.content" class="mk-gray-card-lg max-w-full py-3! whitespace-pre-wrap wrap-anywhere">{{ content.content }}</div>
</div>
</template>
67 changes: 34 additions & 33 deletions ui/src/conversation-panel/components/log-message-list/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ const onScroll = async () => {

<template>
<!-- 只读会话记录:首页加载整块遮罩,上滑加载更多时顶部小转圈 -->
<div ref="msgBoxRef" v-loading="fullLoading" class="log-message-list p-6" @scroll="onScroll">
<div ref="msgBoxRef" v-loading="fullLoading" @scroll="onScroll">
<template v-if="messages.length > 0">
<div v-if="loading" class="log-message-list__more">
<el-icon class="is-loading" :size="18"><LoadingIcon /></el-icon>
</div>

<div v-for="(msg, i) in messages" :key="msg.id || i" :class="['msg-row', msg.role === 'USER' ? 'user' : 'assistant']">
<div v-for="(msg, i) in messages" :key="msg.id || i" :class="[msg.role === 'USER' ? 'user' : 'assistant']">
<ContentList :content-list="msg.content" />
<!-- 助手消息底部信息栏(知识来源/执行详情/tokens/耗时) -->
<MessageFooter v-if="msg.role === 'ASSISTANT' && msg.record" :message="msg" />
Expand All @@ -56,37 +56,38 @@ const onScroll = async () => {
</template>

<style scoped lang="scss">
.log-message-list {
flex: 1;
min-height: 0;
overflow-y: auto;
scrollbar-width: thin;
display: flex;
flex-direction: column;
align-items: center;
}
// .log-message-list {
// flex: 1;
// min-height: 0;
// overflow-y: auto;
// scrollbar-width: thin;
// display: flex;
// flex-direction: column;
// align-items: center;
// }

.log-message-list__more {
display: flex;
justify-content: center;
padding: 4px 0 10px;
color: var(--t3, #909399);
}
// .log-message-list__more {
// display: flex;
// justify-content: center;
// padding: 4px 0 10px;
// color: var(--t3, #909399);
// }

.msg-row {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 10px;
max-width: 800px;
width: 100%;
box-sizing: border-box;
}
.msg-row.user {
align-items: flex-end;
flex-direction: row-reverse;
}
.msg-row.assistant {
align-items: flex-start;
}
// .msg-row {
// display: flex;
// flex-direction: column;
// gap: 6px;
// margin-bottom: 10px;
// max-width: 800px;
// width: 100%;
// box-sizing: border-box;
// }
// .msg-row.user {
// align-items: flex-end;
// flex-direction: row-reverse;
// max-width: 85%;
// }
// .msg-row.assistant {
// align-items: flex-start;
// }
</style>
156 changes: 77 additions & 79 deletions ui/src/conversation-panel/components/message-list/index.vue
Original file line number Diff line number Diff line change
@@ -1,31 +1,3 @@
<template>
<!-- 接口加载:无消息时(首页)整块遮罩,有消息时(加载更多)顶部小转圈;对话加载:底部小点 -->
<div ref="msgBoxRef" v-loading="fullLoading" class="message-list" @scroll="onScroll">
<template v-if="messages.length > 0">
<div v-if="loading" class="message-list__more">
<el-icon class="is-loading" :size="18"><LoadingIcon /></el-icon>
</div>

<div v-for="(msg, i) in messages" :key="msg.id || i" :class="['msg-row', msg.role === 'USER' ? 'user' : 'assistant']">
<ContentList :content-list="msg.content" />
<!-- 助手消息结束后显示底部信息栏(知识来源/执行详情/tokens/耗时) -->
<MessageFooter v-if="msg.role === 'ASSISTANT' && (msg.write_ed || msg.record)" :message="msg" />
</div>

<div v-if="streamLoading" class="msg-row assistant">
<Loading :size="18" />
</div>
</template>

<div v-else-if="!loading" class="welcome">
<slot name="welcome">
<p class="welcome-title">{{ appName }}</p>
<p class="welcome-sub">有什么可以帮你的?</p>
</slot>
</div>
</div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
import { Loading as LoadingIcon } from '@element-plus/icons-vue'
Expand Down Expand Up @@ -65,60 +37,86 @@ const onScroll = () => {
if (el && el.scrollTop <= 60 && hasMore.value) loadMore()
}
</script>
<template>
<!-- 接口加载:无消息时(首页)整块遮罩,有消息时(加载更多)顶部小转圈;对话加载:底部小点 -->
<div ref="msgBoxRef" v-loading="fullLoading" @scroll="onScroll">
<template v-if="messages.length > 0">
<div v-if="loading" class="message-list__more">
<el-icon class="is-loading" :size="18"><LoadingIcon /></el-icon>
</div>

<div v-for="(msg, i) in messages" :key="msg.id || i" :class="[msg.role === 'USER' ? 'user' : 'assistant']">
<ContentList :content-list="msg.content" />
<!-- 助手消息结束后显示底部信息栏(知识来源/执行详情/tokens/耗时) -->
<MessageFooter v-if="msg.role === 'ASSISTANT' && (msg.write_ed || msg.record)" :message="msg" />
</div>

<div v-if="streamLoading" class="msg-row assistant">
<Loading :size="18" />
</div>
</template>

<div v-else-if="!loading" class="welcome">
<slot name="welcome">
<p class="welcome-title">{{ appName }}</p>
<p class="welcome-sub">有什么可以帮你的?</p>
</slot>
</div>
</div>
</template>
<style scoped lang="scss">
.message-list {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 14px 12px;
scrollbar-width: thin;
display: flex;
flex-direction: column;
align-items: center;
}
// .message-list {
// flex: 1;
// min-height: 0;
// overflow-y: auto;
// padding: 14px 12px;
// scrollbar-width: thin;
// display: flex;
// flex-direction: column;
// align-items: center;
// }

.message-list__more {
display: flex;
justify-content: center;
padding: 4px 0 10px;
color: var(--t3, #909399);
}
// .message-list__more {
// display: flex;
// justify-content: center;
// padding: 4px 0 10px;
// color: var(--t3, #909399);
// }

.welcome {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 32px 16px;
}
.welcome-title {
font-size: 18px;
font-weight: 600;
color: var(--t1, #303133);
margin-bottom: 8px;
}
.welcome-sub {
font-size: 14px;
color: var(--t3, #909399);
}
// .welcome {
// flex: 1;
// display: flex;
// flex-direction: column;
// align-items: center;
// justify-content: center;
// text-align: center;
// padding: 32px 16px;
// }
// .welcome-title {
// font-size: 18px;
// font-weight: 600;
// color: var(--t1, #303133);
// margin-bottom: 8px;
// }
// .welcome-sub {
// font-size: 14px;
// color: var(--t3, #909399);
// }

.msg-row {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 10px;
max-width: 800px;
width: 100%;
box-sizing: border-box;
}
.msg-row.user {
align-items: flex-end;
flex-direction: row-reverse;
}
.msg-row.assistant {
align-items: flex-start;
}
// .msg-row {
// display: flex;
// flex-direction: column;
// gap: 6px;
// margin-bottom: 10px;
// max-width: 800px;
// width: 100%;
// box-sizing: border-box;
// }
// .msg-row.user {
// align-items: flex-end;
// flex-direction: row-reverse;
// }
// .msg-row.assistant {
// align-items: flex-start;
// }
</style>
6 changes: 3 additions & 3 deletions ui/src/conversation-panel/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@

/* 用户提问容器:随内容收缩并靠右排列。 */
.mk-conversation-question-content {
// margin-left: auto;
// max-width: 85%;
// width: fit-content;
margin-left: auto;
max-width: 85%;
width: fit-content;
}
Loading
Loading