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
2 changes: 1 addition & 1 deletion ui/src/assets/iconfont.js

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions ui/src/components/COMPONENT_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,9 @@ Element Plus 的 `v-infinite-scroll`。组件通过 `v-model` 管理已经加载
不属于全局组件。内部使用 `el-scrollbar`,父级需要提供受限高度或纵向 Flex 剩余空间,
外层不再额外包裹消息滚动容器。

内容留白通过 `contentClass` 设置,例如 `content-class="p-4"`;该类作用于滚动区域内的内容层,
同时覆盖消息和空状态,使滚动条保持贴近容器边缘。`class` 仍用于组件外壳布局。

传入 `loading`、`hasMore`、`count` 和 `loadMore: () => Promise<boolean>`。页码、请求和
历史数据前插由调用方维护;`loadMore` 在历史数据更新后返回 `true`,失败或没有新增数据返回
`false`。组件不请求首页;首页由调用方加载,首次出现数据时自动定位到底部,距顶部 60px 时
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ watch(
</template>
<template #label>
<MkIcon name="icon_two-ellipses_outlined" />
<span class="-mx-1">思考中</span>
<span class="-mx-1" :class="{ 'mk-dotting': content.status === 'RUNNING' }">
{{ content.status === 'RUNNING' ? '思考中' : '思考完成' }}
</span>
</template>
<div class="text-N500! whitespace-pre-wrap break-words">
{{ content.content }}
Expand Down
127 changes: 28 additions & 99 deletions ui/src/conversation-panel/components/header/chat/index.vue
Original file line number Diff line number Diff line change
@@ -1,39 +1,5 @@
<template>
<header class="panel-header">
<!-- 左侧收起时才在顶部显示:展开左侧 + 新建对话(展开时它们在侧栏内) -->
<template v-if="!list.leftSideOpen.value">
<!-- 展开左侧 -->
<button class="header-btn" @click="list.toggleLeftSide()">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<rect x="1.5" y="2.5" width="13" height="11" rx="2" stroke="currentColor" stroke-width="1.4" />
<line x1="6" y1="2.5" x2="6" y2="13.5" stroke="currentColor" stroke-width="1.4" />
</svg>
</button>
<!-- 新建对话 -->
<button class="header-btn" @click="handleNewConversation">
<el-icon :size="18"><Plus /></el-icon>
</button>
</template>
<div class="header-info">
<img v-if="list.appInfo.value?.icon" :src="list.appInfo.value.icon" class="header-icon" />
<div class="header-text">
<span v-if="list.appInfo.value?.name" class="header-app-name">{{ list.appInfo.value.name }}</span>
<span class="header-title">{{ list.currentConversation.value?.abstract || '新对话' }}</span>
</div>
</div>
<!-- 进入分享模式,勾选要分享的对话 -->
<button v-if="shareable" class="header-btn" :disabled="!canShare" @click="emit('share')">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M9.5 3l4 4-4 4M13.5 7H7.5a5 5 0 0 0-5 5v1" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
<slot />
</header>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { Plus } from '@element-plus/icons-vue'
import { useConversationListStore } from '../../../left-sidebar/conversation-list/index'
import { useMessageListStore } from '../../message-list/index'

Expand All @@ -54,68 +20,31 @@ const canShare = computed(() => msgs.messages.value.length > 0 && !msgs.streamLo
const handleNewConversation = () => (props.newConversation ? props.newConversation() : list.newConversation())
</script>

<style scoped lang="scss">
.panel-header {
display: flex;
align-items: center;
gap: 6px;
height: 44px;
padding: 0 12px;
flex-shrink: 0;
border-bottom: 1px solid var(--bd, #dcdfe6);
}
.header-btn {
width: 32px;
height: 32px;
border: none;
background: transparent;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
color: var(--t2, #606266);
flex-shrink: 0;
}
.header-btn:hover {
background: rgba(0, 0, 0, 0.05);
}
.header-btn:disabled {
cursor: not-allowed;
opacity: 0.4;
background: transparent;
}
.header-info {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
min-width: 0;
}
.header-icon {
width: 24px;
height: 24px;
border-radius: 6px;
object-fit: cover;
flex-shrink: 0;
}
.header-text {
display: flex;
flex-direction: column;
min-width: 0;
}
.header-app-name {
font-size: 11px;
font-weight: 400;
color: var(--t3, #909399);
line-height: 1.2;
}
.header-title {
font-size: 13px;
font-weight: 500;
color: var(--t1, #303133);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
<template>
<header class="flex-align-center py-4 px-6">
<!-- 左侧收起时才在顶部显示:展开左侧 + 新建对话(展开时它们在侧栏内) -->
<template v-if="!list.leftSideOpen.value">
<!-- 展开左侧 -->
<el-button text @click="list.toggleLeftSide()">
<MkIcon name="icon_sidebar_outlined" :size="18" class="text-N900!" />
</el-button>

<!-- 新建对话 -->
<el-button text @click="handleNewConversation">
<MkIcon name="icon_new-chat_outlined" :size="18" class="text-N900!" />
</el-button>
<el-divider direction="vertical" class="ml-3! mr-4!" />
</template>

<div class="flex-1 flex-align-center gap-2">
<h4 class="truncate">{{ list.currentConversation.value?.abstract || '新建对话' }}</h4>
</div>
<!-- 进入分享模式,勾选要分享的对话 -->
<el-button v-if="shareable" text :disabled="!canShare" @click="emit('share')">
<MkIcon name="icon_share_outlined" :size="18" class="text-N900!" />
</el-button>
<slot />
</header>
</template>

<style scoped lang="scss"></style>
56 changes: 56 additions & 0 deletions ui/src/conversation-panel/components/header/portal/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useConversationListStore } from '../../../left-sidebar/conversation-list/index'
import { useMessageListStore } from '../../message-list/index'

const props = defineProps<{
/** 新建对话;不传时在当前应用中新建(门户在没有当前智能体时改为选择智能体) */
newConversation?: () => void
/** 显示分享入口;仅对话端传入 */
shareable?: boolean
}>()

const emit = defineEmits<{ share: [] }>()

const list = useConversationListStore()
const msgs = useMessageListStore()
// 回答生成中或没有消息时不可分享
const canShare = computed(() => msgs.messages.value.length > 0 && !msgs.streamLoading.value)

const handleNewConversation = () => (props.newConversation ? props.newConversation() : list.newConversation())
</script>

<template>
<header class="flex-align-center py-4 px-6">
<!-- 左侧收起时才在顶部显示:展开左侧 + 新建对话(展开时它们在侧栏内) -->
<template v-if="!list.leftSideOpen.value">
<!-- 展开左侧 -->
<el-button text @click="list.toggleLeftSide()">
<MkIcon name="icon_sidebar_outlined" :size="18" class="text-N900!" />
</el-button>

<!-- 新建对话 -->
<el-button text @click="handleNewConversation">
<MkIcon name="icon_new-chat_outlined" :size="18" class="text-N900!" />
</el-button>
<el-divider direction="vertical" class="ml-3! mr-4!" />
</template>

<div class="flex-1 flex-align-center">
<h4 class="truncate">{{ list.currentConversation.value?.abstract || '新建对话' }}</h4>
<el-tag type="info">
<div class="flex-align-center gap-1">
<ApplicationIcon :icon="list.appInfo.value?.icon" :size="18" class="shrink-0" />
<span v-if="list.appInfo.value?.name" class="text-sm">{{ list.appInfo.value.name }}</span>
</div>
</el-tag>
</div>
<!-- 进入分享模式,勾选要分享的对话 -->
<el-button v-if="shareable" text :disabled="!canShare" @click="emit('share')">
<MkIcon name="icon_share_outlined" :size="18" class="text-N900!" />
</el-button>
<slot />
</header>
</template>

<style scoped lang="scss"></style>
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ const { messages, loading, hasMore, loadMore } = useLogMessageListStore()
</script>

<template>
<ReverseInfiniteScroll class="p-6" :loading="loading" :has-more="hasMore" :count="messages.length" :load-more="loadMore">
<ReverseInfiniteScroll content-class="p-4" :loading="loading" :has-more="hasMore" :count="messages.length" :load-more="loadMore">
<template v-for="(msg, i) in messages" :key="msg.id || i">
<div :class="[msg.role === 'USER' ? 'user' : 'assistant']">
<!-- msg.role === 'USER' -->
<div>
<ContentList :content-list="msg.content" />
<!-- 助手消息底部信息栏(知识来源/执行详情/tokens/耗时) -->
<MessageFooter v-if="msg.role === 'ASSISTANT' && msg.record" :message="msg" />
Expand Down
42 changes: 4 additions & 38 deletions ui/src/conversation-panel/components/message-list/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,17 @@ onBeforeUnmount(offProgress)
</script>

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

<div v-if="streamLoading" class="msg-row assistant">
<div v-if="streamLoading">
<span class="mk-conversation-loading" />
</div>

Expand All @@ -41,24 +42,6 @@ onBeforeUnmount(offProgress)
</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__more {
// display: flex;
// justify-content: center;
// padding: 4px 0 10px;
// color: var(--t3, #909399);
// }

.welcome {
flex: 1;
display: flex;
Expand All @@ -78,21 +61,4 @@ onBeforeUnmount(offProgress)
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;
// }
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { ScrollbarInstance } from 'element-plus'
import { Scroll } from '../scroll'

export interface ReverseInfiniteScrollProps {
contentClass?: string
loading: boolean
hasMore: boolean
count: number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ defineExpose({ scrollToBottom, followBottom })
<template>
<div v-loading="initialLoading" class="min-h-0 flex-1 overflow-hidden">
<el-scrollbar ref="scrollbarRef" wrap-class="[overflow-anchor:none]" view-class="min-h-full flex-column" @scroll="onScroll">
<div class="min-h-full flex-1 flex-column">
<div class="min-h-full flex-1 flex-column" :class="contentClass">
<template v-if="count > 0">
<!-- 固定预留状态行,加载提示切换时不改变消息位置。 -->
<div v-if="loadingMore" class="flex-center py-4 shrink-0">
Expand Down
4 changes: 2 additions & 2 deletions ui/src/conversation-panel/main/portal-chat-panel/index.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import ChatHeader from '../../components/header/chat/index.vue'
import PortalHeader from '../../components/header/portal/index.vue'
import MessageList from '../../components/message-list/index.vue'
import MessageInput from '../../components/message-input/index.vue'
import { useConversationListStore } from '../../left-sidebar/conversation-list/index'
Expand All @@ -19,7 +19,7 @@ const handleDrop = (e: DragEvent) => input.addFiles(e.dataTransfer?.files)
<!-- 门户对话主面板:header + message-list + message-input -->
<main class="mk-conversation-main" @drop.prevent="handleDrop" @dragover.prevent>
<!-- 收起侧栏时标题栏的新建对话与侧栏一致:没有当前智能体时选择智能体 -->
<ChatHeader :new-conversation="portalList.newConversation" />
<PortalHeader :new-conversation="portalList.newConversation" />
<MessageList :key="list.currentChatId.value" v-if="portalList.currentApplicationId.value" :app-name="list.appInfo.value?.name || 'AI 助手'" />
<!-- 未确定智能体时不显示欢迎语:分组加载完成且没有可用智能体时显示空状态 -->
<div v-else class="flex-center min-h-0 flex-1">
Expand Down
42 changes: 21 additions & 21 deletions ui/src/conversation-panel/view/portal/index.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,3 @@
<template>
<!-- portal 视图:在 chat 视图基础上把左侧换成按智能体分组的门户历史对话 -->
<ConversationLayout
:left-open="bundle.list.leftSideOpen.value"
:right-open="bundle.detail.rightSideOpen.value"
:left-mode="leftMode"
:right-mode="rightMode"
@mask-click="closeDrawers"
>
<template #left><PortalConversationList /></template>
<template #main>
<!-- 全部智能体页显示智能体卡片,其余显示对话面板 -->
<PortalApplicationPanel v-if="bundle.portalList.isApplicationListActive.value" />
<PortalChatPanel v-else />
</template>
<template #right><ExecutionDetail /></template>
</ConversationLayout>
<!-- 没有当前智能体时新建对话,选择智能体 -->
<SelectApplicationDrawer ref="applicationSelectorRef" />
</template>

<script setup lang="ts">
import { ref, computed, watch, onMounted, onBeforeUnmount, provide } from 'vue'
import ConversationLayout from '../../core/layout/index.vue'
Expand Down Expand Up @@ -74,6 +53,27 @@ watch([() => bundle.list.currentChatId.value, () => bundle.list.composerResetSig
})
</script>

<template>
<!-- portal 视图:在 chat 视图基础上把左侧换成按智能体分组的门户历史对话 -->
<ConversationLayout
:left-open="bundle.list.leftSideOpen.value"
:right-open="bundle.detail.rightSideOpen.value"
:left-mode="leftMode"
:right-mode="rightMode"
@mask-click="closeDrawers"
>
<template #left><PortalConversationList /></template>
<template #main>
<!-- 全部智能体页显示智能体卡片,其余显示对话面板 -->
<PortalApplicationPanel v-if="bundle.portalList.isApplicationListActive.value" />
<PortalChatPanel v-else />
</template>
<template #right><ExecutionDetail /></template>
</ConversationLayout>
<!-- 没有当前智能体时新建对话,选择智能体 -->
<SelectApplicationDrawer ref="applicationSelectorRef" />
</template>

<style lang="scss">
@use '../../index.scss';
</style>
2 changes: 1 addition & 1 deletion ui/src/views/home/components/ranking/RankingCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function getRecordPercentage(record: HomeRankingRecord) {
</div>
<div class="w-25 shrink-0">
<el-progress :percentage="getRecordPercentage(record)" :show-text="false" :stroke-width="8" />
<p class="color-secondary mt-4">
<p class="mt-1 font-medium text-sm text-right">
{{ kind === 'questions' ? numberFormat(record.chat_record_count) : formatTokenNumber(record.total_tokens ?? 0) }}
</p>
</div>
Expand Down
Loading