|
@@ -231,7 +231,6 @@ const chatInternal = (rtn: Message, context: Message[] = messages.value) => {
|
|
|
rtn.content += resp.message
|
|
|
break
|
|
|
case 'toolres': {
|
|
|
-
|
|
|
if (showToolCalls.value) {
|
|
|
rtn.render_content += `
|
|
|
\`\`\`tools-loading
|
|
@@ -477,6 +476,30 @@ const redirectToModelManager = () => router.push('manage/model')
|
|
|
// 设置面板相关状态
|
|
|
const showSettingsPanel = ref(false)
|
|
|
const showToolCalls = ref(false)
|
|
|
+
|
|
|
+// 收藏消息功能
|
|
|
+const favoriteMessageIdx = ref(-1)
|
|
|
+const { loading: loadingFavoriteMessage, doLoading: toggleFavorite } = useLoading(async (messageIndex: number) => {
|
|
|
+ favoriteMessageIdx.value = messageIndex
|
|
|
+ const data = messages.value[messageIndex]
|
|
|
+ const active = activeConversationId.value
|
|
|
+ if (active === undefined) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const success = assist.session.message
|
|
|
+ .bookmark(active, data.id, !(data.like ?? false))
|
|
|
+ .then(() => true)
|
|
|
+ .catch(() => false)
|
|
|
+ .finally(() => (favoriteMessageIdx.value = -1))
|
|
|
+ if (!success) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ messages.value[messageIndex] = {
|
|
|
+ ...data,
|
|
|
+ like: !(data.like ?? false),
|
|
|
+ }
|
|
|
+})
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
@@ -603,19 +626,26 @@ const showToolCalls = ref(false)
|
|
|
<!-- 消息展示区域 -->
|
|
|
<div class="messages-container" ref="messagesContainer" v-loading="loadingMessage">
|
|
|
<div v-if="messages.length !== 0">
|
|
|
- <div v-for="message in messages" :key="message.id" :class="['message-wrapper', message.role]">
|
|
|
+ <div v-for="(message, idx) in messages" :key="message.id" :class="['message-wrapper', message.role]">
|
|
|
<!-- AI消息 -->
|
|
|
<div v-if="message.role === 'assistant'" class="ai-message-container">
|
|
|
<el-avatar class="message-avatar" :icon="ChatDotRound" />
|
|
|
- <div class="message-bubble ai-bubble">
|
|
|
- <Markdown v-if="message.render_content !== ''" :content="message.render_content" :plugins="plugins" class="markdown-content" />
|
|
|
- <div v-else class="loading-container">
|
|
|
- <div class="loading-dots">
|
|
|
- <span class="dot"></span>
|
|
|
- <span class="dot"></span>
|
|
|
- <span class="dot"></span>
|
|
|
+ <div class="ai-message-content">
|
|
|
+ <div class="message-bubble ai-bubble">
|
|
|
+ <Markdown v-if="message.render_content !== ''" :content="message.render_content" :plugins="plugins" class="markdown-content" />
|
|
|
+ <div v-else class="loading-container">
|
|
|
+ <div class="loading-dots">
|
|
|
+ <span class="dot"></span>
|
|
|
+ <span class="dot"></span>
|
|
|
+ <span class="dot"></span>
|
|
|
+ </div>
|
|
|
+ <span class="loading-text">AI正在思考中...</span>
|
|
|
</div>
|
|
|
- <span class="loading-text">AI正在思考中...</span>
|
|
|
+ </div>
|
|
|
+ <div class="ai-message-actions">
|
|
|
+ <el-button :loading="favoriteMessageIdx == idx && loadingFavoriteMessage" type="primary" size="small" @click="toggleFavorite(idx)" class="favorite-btn" plain :disabled="isConversationActive">
|
|
|
+ {{(message.like ?? false) ? '取消收藏' : '收藏'}}
|
|
|
+ </el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
@@ -725,7 +755,16 @@ const showToolCalls = ref(false)
|
|
|
|
|
|
<!-- 按钮组 -->
|
|
|
<div class="button-group">
|
|
|
- <el-button v-show="messages.length !== 0" type="warning" size="small" @click="clearMessage" style="margin-left: 12px" :loading="loadingClearMessage"> 清空 </el-button>
|
|
|
+ <el-button
|
|
|
+ v-show="messages.length !== 0"
|
|
|
+ type="warning"
|
|
|
+ size="small"
|
|
|
+ @click="clearMessage"
|
|
|
+ style="margin-left: 12px"
|
|
|
+ :loading="loadingClearMessage"
|
|
|
+ >
|
|
|
+ 清空
|
|
|
+ </el-button>
|
|
|
<el-button
|
|
|
v-if="!isConversationActive"
|
|
|
type="primary"
|
|
@@ -957,12 +996,37 @@ const showToolCalls = ref(false)
|
|
|
gap: 12px;
|
|
|
}
|
|
|
|
|
|
+.ai-message-content {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 8px;
|
|
|
+ width: 70%;
|
|
|
+}
|
|
|
+
|
|
|
+.ai-message-actions {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-start;
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.ai-message-container:hover .ai-message-actions {
|
|
|
+ opacity: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.favorite-btn {
|
|
|
+ font-size: 12px;
|
|
|
+ padding: 4px 12px;
|
|
|
+ height: 24px;
|
|
|
+ border-radius: 12px;
|
|
|
+}
|
|
|
+
|
|
|
.ai-bubble {
|
|
|
background: var(--el-fill-color-light);
|
|
|
color: var(--el-text-color-primary);
|
|
|
position: relative;
|
|
|
- min-width: 70%;
|
|
|
- max-width: 90%;
|
|
|
+ max-width: 100%;
|
|
|
border: 1px solid var(--el-border-color-lighter);
|
|
|
|
|
|
&::before {
|
|
@@ -991,6 +1055,7 @@ const showToolCalls = ref(false)
|
|
|
flex-direction: column;
|
|
|
align-items: flex-end;
|
|
|
gap: 8px;
|
|
|
+ width: 70%;
|
|
|
}
|
|
|
|
|
|
.user-message-actions {
|
|
@@ -1015,7 +1080,7 @@ const showToolCalls = ref(false)
|
|
|
background: var(--el-color-primary);
|
|
|
color: white;
|
|
|
position: relative;
|
|
|
- max-width: 70%;
|
|
|
+ max-width: 100%;
|
|
|
|
|
|
&::after {
|
|
|
content: '';
|