feat:优化回答消息的返回方式与交互样式调整
This commit is contained in:
@@ -477,31 +477,62 @@ const handleWebSocketMessage = (data) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let aiMsgIndex = -1;
|
let aiMsgIndex = -1;
|
||||||
if (currentSessionMessageId && pendingMap.has(currentSessionMessageId)) {
|
// Prefer matching by replyMessageId if provided
|
||||||
aiMsgIndex = pendingMap.get(currentSessionMessageId);
|
if (data.replyMessageId) {
|
||||||
if (aiMsgIndex >= 0 && aiMsgIndex < chatMsgList.value.length) {
|
// 1) Try to find an existing AI message that already has the same replyMessageId
|
||||||
const item = chatMsgList.value[aiMsgIndex];
|
for (let i = chatMsgList.value.length - 1; i >= 0; i--) {
|
||||||
if (item && item.msgType === MessageRole.AI &&
|
const it = chatMsgList.value[i];
|
||||||
item.replyMessageId.length > 0 && data.replyMessageId &&
|
if (it && it.msgType === MessageRole.AI && it.replyMessageId === data.replyMessageId) {
|
||||||
item.replyMessageId !== data.replyMessageId) {
|
aiMsgIndex = i;
|
||||||
// 已经存在对应的AI消息项,继续使用
|
break;
|
||||||
const aiMsg = {
|
}
|
||||||
msgId: `msg_${chatMsgList.value.length}`,
|
}
|
||||||
msgType: MessageRole.AI,
|
|
||||||
msg: "",
|
// 2) If not found, check pendingMap for currentSessionMessageId
|
||||||
isLoading: false,
|
if (aiMsgIndex === -1 && currentSessionMessageId && pendingMap.has(currentSessionMessageId)) {
|
||||||
messageId: currentSessionMessageId,
|
const idx = pendingMap.get(currentSessionMessageId);
|
||||||
replyMessageId: '',
|
if (idx >= 0 && idx < chatMsgList.value.length) {
|
||||||
componentName: "",
|
const item = chatMsgList.value[idx];
|
||||||
title: "",
|
// If the pending item already has a different non-empty replyMessageId, create a new AI entry
|
||||||
finish: false,
|
if (item && item.msgType === MessageRole.AI && item.replyMessageId && item.replyMessageId !== data.replyMessageId) {
|
||||||
};
|
const aiMsg = {
|
||||||
chatMsgList.value.push(aiMsg);
|
msgId: `msg_${chatMsgList.value.length}`,
|
||||||
aiMsgIndex = chatMsgList.value.length - 1;
|
msgType: MessageRole.AI,
|
||||||
}
|
msg: "",
|
||||||
|
isLoading: false,
|
||||||
|
messageId: currentSessionMessageId,
|
||||||
|
replyMessageId: data.replyMessageId || '',
|
||||||
|
componentName: "",
|
||||||
|
title: "",
|
||||||
|
finish: false,
|
||||||
|
};
|
||||||
|
chatMsgList.value.push(aiMsg);
|
||||||
|
aiMsgIndex = chatMsgList.value.length - 1;
|
||||||
|
} else {
|
||||||
|
// Reuse the pending item
|
||||||
|
aiMsgIndex = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) If still not found, create a new AI message for this replyMessageId
|
||||||
|
if (aiMsgIndex === -1) {
|
||||||
|
const aiMsg = {
|
||||||
|
msgId: `msg_${chatMsgList.value.length}`,
|
||||||
|
msgType: MessageRole.AI,
|
||||||
|
msg: "",
|
||||||
|
isLoading: false,
|
||||||
|
messageId: currentSessionMessageId,
|
||||||
|
replyMessageId: data.replyMessageId || '',
|
||||||
|
componentName: "",
|
||||||
|
title: "",
|
||||||
|
finish: false,
|
||||||
|
};
|
||||||
|
chatMsgList.value.push(aiMsg);
|
||||||
|
aiMsgIndex = chatMsgList.value.length - 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 向后搜索最近的 AI 消息(回退逻辑)
|
// No replyMessageId: fall back to most recent AI message
|
||||||
for (let i = chatMsgList.value.length - 1; i >= 0; i--) {
|
for (let i = chatMsgList.value.length - 1; i >= 0; i--) {
|
||||||
if (chatMsgList.value[i] && chatMsgList.value[i].msgType === MessageRole.AI) {
|
if (chatMsgList.value[i] && chatMsgList.value[i].msgType === MessageRole.AI) {
|
||||||
aiMsgIndex = i;
|
aiMsgIndex = i;
|
||||||
@@ -572,7 +603,7 @@ const handleWebSocketMessage = (data) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 清理 pendingMap / timeout
|
// 清理 pendingMap / timeout
|
||||||
const ownedMessageId = chatMsgList.value[aiMsgIndex].messageId || msgId;
|
const ownedMessageId = chatMsgList.value[aiMsgIndex].messageId || null;
|
||||||
if (ownedMessageId) {
|
if (ownedMessageId) {
|
||||||
if (pendingTimeouts.has(ownedMessageId)) {
|
if (pendingTimeouts.has(ownedMessageId)) {
|
||||||
clearTimeout(pendingTimeouts.get(ownedMessageId));
|
clearTimeout(pendingTimeouts.get(ownedMessageId));
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<!-- 占位撑开 -->
|
<!-- 占位撑开 -->
|
||||||
<view class="w-vw"></view>
|
<view class="w-vw"></view>
|
||||||
<view class="flex flex-col p-16 border-box">
|
<view class="flex flex-col p-16 border-box">
|
||||||
<view class="flex flex-row flex-items-start justify-center">
|
<view class="flex flex-row flex-items-start flex-justify-start">
|
||||||
<uni-icons class="icon-active" type="fire-filled" size="18" color="opacity" />
|
<uni-icons class="icon-active" type="fire-filled" size="18" color="opacity" />
|
||||||
<text class="font-size-16 font-500 text-color-900 ml-6">游玩划重点</text>
|
<text class="font-size-16 font-500 text-color-900 ml-6">游玩划重点</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<ChatMarkdown :key="textKey" :text="processedText" />
|
<ChatMarkdown :key="textKey" :text="processedText" />
|
||||||
</view>
|
</view>
|
||||||
<!-- 超过3行时显示...提示 -->
|
<!-- 超过3行时显示...提示 -->
|
||||||
<view class="flex flex-row mt-8" v-if="isOverflow" @click="lookDetailAction">
|
<view class="flex flex-row flex-items-center mt-8" v-if="isOverflow" @click="lookDetailAction">
|
||||||
<text class="font-size-12 font-400 theme-color-500 mr-4">查看详情</text>
|
<text class="font-size-12 font-400 theme-color-500 mr-4">查看详情</text>
|
||||||
<uni-icons class="icon-active" type="right" size="14" color="opacity"></uni-icons>
|
<uni-icons class="icon-active" type="right" size="14" color="opacity"></uni-icons>
|
||||||
</view>
|
</view>
|
||||||
@@ -43,10 +43,28 @@ const props = defineProps({
|
|||||||
// 用于强制重新渲染的key
|
// 用于强制重新渲染的key
|
||||||
const textKey = ref(0);
|
const textKey = ref(0);
|
||||||
|
|
||||||
// 处理文本内容(纯计算,不应有副作用)
|
// 处理文本内容:按行截断以保证预览最多显示三行(更贴近视觉行数)
|
||||||
|
// 点击“查看详情”会跳转到完整页面(不受预览截断影响)。
|
||||||
|
const PREVIEW_LINES = 3;
|
||||||
|
const PREVIEW_CHAR_LIMIT = 100; // 作为备用,当没有换行但过长时也会截断
|
||||||
const processedText = computed(() => {
|
const processedText = computed(() => {
|
||||||
if (!props.text) return "";
|
const txt = props.text ? String(props.text) : "";
|
||||||
return String(props.text);
|
if (!txt) return "";
|
||||||
|
|
||||||
|
// 按行分割(保留空行)
|
||||||
|
const lines = txt.split(/\r?\n/);
|
||||||
|
|
||||||
|
// 如果行数超过限制,截取前 PREVIEW_LINES 行并添加省略号
|
||||||
|
if (lines.length > PREVIEW_LINES) {
|
||||||
|
return lines.slice(0, PREVIEW_LINES).join("\n") + "...";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 若虽然行数不超过,但总长度仍然很长,做字符级截断作为兜底
|
||||||
|
if (txt.length > PREVIEW_CHAR_LIMIT) {
|
||||||
|
return txt.slice(0, PREVIEW_CHAR_LIMIT) + "...";
|
||||||
|
}
|
||||||
|
|
||||||
|
return txt;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听 text 变化:更新 textKey 并同步 isOverflow(合并为单一响应函数,避免冗余)
|
// 监听 text 变化:更新 textKey 并同步 isOverflow(合并为单一响应函数,避免冗余)
|
||||||
@@ -54,7 +72,8 @@ watch(
|
|||||||
() => props.text,
|
() => props.text,
|
||||||
(newText, oldText) => {
|
(newText, oldText) => {
|
||||||
const textStr = newText ? String(newText) : "";
|
const textStr = newText ? String(newText) : "";
|
||||||
isOverflow.value = textStr.length > 100;
|
const lines = textStr.split(/\r?\n/);
|
||||||
|
isOverflow.value = lines.length > PREVIEW_LINES || textStr.length > PREVIEW_CHAR_LIMIT;
|
||||||
if (newText !== oldText) {
|
if (newText !== oldText) {
|
||||||
textKey.value++;
|
textKey.value++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,38 +111,40 @@ export default {
|
|||||||
`,
|
`,
|
||||||
// 一级标题
|
// 一级标题
|
||||||
h1: `
|
h1: `
|
||||||
margin:4px 0;
|
margin:8px 0;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
text-align: center;
|
line-height: 1.6;
|
||||||
font-weight: bold;
|
text-align: center;
|
||||||
color: ${themeColor};
|
font-weight: bold;
|
||||||
|
color: ${themeColor};
|
||||||
font-family: ${fontFamily};
|
font-family: ${fontFamily};
|
||||||
padding:3px 10px 1px;
|
padding:6px 10px 4px;
|
||||||
border-bottom: 2px solid ${themeColor};
|
border-bottom: 2px solid ${themeColor};
|
||||||
border-top-right-radius:3px;
|
border-top-right-radius:3px;
|
||||||
border-top-left-radius:3px;
|
border-top-left-radius:3px;
|
||||||
|
`,
|
||||||
`,
|
|
||||||
// 二级标题
|
// 二级标题
|
||||||
h2: `
|
h2: `
|
||||||
margin:4px 0;
|
margin:6px 0;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
text-align:center;
|
line-height: 1.55;
|
||||||
color:${themeColor};
|
text-align:center;
|
||||||
|
color:${themeColor};
|
||||||
font-family: ${fontFamily};
|
font-family: ${fontFamily};
|
||||||
font-weight:bolder;
|
font-weight:bolder;
|
||||||
padding-left:10px;
|
padding-left:10px;
|
||||||
// border:1px solid ${themeColor};
|
// border:1px solid ${themeColor};
|
||||||
`,
|
`,
|
||||||
// 三级标题
|
// 三级标题
|
||||||
h3: `
|
h3: `
|
||||||
margin:4px 0;
|
margin:6px 0;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: ${themeColor};
|
line-height: 1.5;
|
||||||
|
color: ${themeColor};
|
||||||
font-family: ${fontFamily};
|
font-family: ${fontFamily};
|
||||||
padding-left:10px;
|
padding-left:10px;
|
||||||
border-left:3px solid ${themeColor};
|
border-left:3px solid ${themeColor};
|
||||||
`,
|
`,
|
||||||
// 引用
|
// 引用
|
||||||
blockquote: `
|
blockquote: `
|
||||||
margin:4px 0;
|
margin:4px 0;
|
||||||
@@ -151,7 +153,7 @@ export default {
|
|||||||
color: #777777;
|
color: #777777;
|
||||||
border-left: 4px solid #dddddd;
|
border-left: 4px solid #dddddd;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
`,
|
`,
|
||||||
// 列表
|
// 列表
|
||||||
ul: `
|
ul: `
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -231,25 +233,28 @@ export default {
|
|||||||
`,
|
`,
|
||||||
// 一级标题
|
// 一级标题
|
||||||
h1: `
|
h1: `
|
||||||
margin:4px 0;
|
margin:8px 0;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
color: ${themeColor};
|
line-height: 1.6;
|
||||||
font-family: ${fontFamily};
|
color: ${themeColor};
|
||||||
`,
|
font-family: ${fontFamily};
|
||||||
|
`,
|
||||||
// 二级标题
|
// 二级标题
|
||||||
h2: `
|
h2: `
|
||||||
margin:4px 0;
|
margin:6px 0;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
color: ${themeColor};
|
line-height: 1.55;
|
||||||
font-family: ${fontFamily};
|
color: ${themeColor};
|
||||||
`,
|
font-family: ${fontFamily};
|
||||||
|
`,
|
||||||
// 三级标题
|
// 三级标题
|
||||||
h3: `
|
h3: `
|
||||||
margin:4x 0;
|
margin:6px 0;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: ${themeColor};
|
line-height: 1.5;
|
||||||
font-family: ${fontFamily};
|
color: ${themeColor};
|
||||||
`,
|
font-family: ${fontFamily};
|
||||||
|
`,
|
||||||
// 四级标题
|
// 四级标题
|
||||||
h4: `
|
h4: `
|
||||||
margin:4px 0;
|
margin:4px 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user