Browse Source

打字机效果按顺序输出

ds_hxl
no99 1 week ago
parent
commit
eec1174df3
  1. 564
      src/views/AIchat.vue
  2. 103
      src/views/homePage.vue

564
src/views/AIchat.vue

@ -22,7 +22,7 @@ import katex from "katex"; // 引入 KaTeX 库
import { htmlToText } from "html-to-text";
import { Howl, Howler } from "howler";
import * as echarts from "echarts";
import _ from "lodash";
import _, { add } from "lodash";
import moment from "moment";
import AIgif1 from "@/assets/img/AIchat/AIgif1.gif";
@ -212,6 +212,150 @@ const fnGetData = (data) => {
return sz.value;
};
const typingQueue = ref([]);
const isTypingInProgress = ref(false);
// Promise
const createTypingEffect = (message, content, speed) => {
return new Promise((resolve) => {
chatStore.messages.push(message);
if (content != "") {
let index = 0;
message.content = "";
message.isTyping = true;
const typingInterval = setInterval(() => {
if (index < content.length) {
message.content += content.charAt(index);
index++;
} else {
clearInterval(typingInterval);
message.isTyping = false;
// KaTeX
nextTick(() => {
if (message.content.includes("$$")) {
message.content = message.content.replace(
katexRegex,
(match, formula) => {
try {
return katex.renderToString(formula, {
throwOnError: false,
});
} catch (error) {
console.error("KaTeX 渲染错误:", error);
return match;
}
}
);
}
resolve(); // resolve
});
}
}, speed);
} else {
if (message.kline) {
if (message.klineType == 1) {
console.log("六色罗盘消息已添加到聊天列表");
//
nextTick(() => {
console.log("nextTick开始 - 准备渲染图表");
console.log("消息列表:", chatStore.messages);
// K线
let klineIndex = -1;
for (let i = 0; i < chatStore.messages.length; i++) {
if (chatStore.messages[i].messageId === message.messageId) {
klineIndex = i;
break;
}
}
console.log("找到的K线消息索引:", klineIndex);
if (klineIndex !== -1) {
const containerId = `kline-container-${klineIndex}`;
console.log("图表容器ID:", containerId);
// DOM
setTimeout(() => {
console.log("延时执行,确保DOM已渲染");
KlineCanvsEcharts(containerId);
}, 100); // DOM
} else {
console.warn("未找到K线消息");
}
});
} else {
console.log("K线消息已添加到聊天列表");
//
nextTick(() => {
console.log("nextTick开始 - 准备渲染图表");
console.log("消息列表:", chatStore.messages);
// K线
let klineIndex = -1;
for (let i = 0; i < chatStore.messages.length; i++) {
if (chatStore.messages[i].messageId === message.messageId) {
klineIndex = i;
break;
}
}
console.log("找到的K线消息索引:", klineIndex);
if (klineIndex !== -1) {
const containerId = `kline-container-${klineIndex}`;
console.log("图表容器ID:", containerId);
// DOM
setTimeout(() => {
console.log("延时执行,确保DOM已渲染");
KlineCanvsEcharts(containerId);
}, 100); // DOM
} else {
console.warn("未找到K线消息");
}
});
}
// 1resolve
setTimeout(() => {
resolve();
}, 1000);
} else {
// 1resolve
setTimeout(() => {
resolve();
}, 1000);
}
}
});
};
//
const processTypingQueue = async () => {
if (isTypingInProgress.value || typingQueue.value.length === 0) {
return;
}
isTypingInProgress.value = true;
while (typingQueue.value.length > 0) {
const task = typingQueue.value.shift();
await createTypingEffect(task.message, task.content, task.speed);
}
isTypingInProgress.value = false;
};
//
const addTypingTask = (message, content, speed = 50) => {
typingQueue.value.push({ message, content, speed });
processTypingQueue();
};
const hasValidData = ref(false);
//
@ -352,13 +496,24 @@ watch(
//
chatStore.messages.pop();
//
chatStore.messages.push({
addTypingTask(
{
sender: "ai",
class: "title1",
type: "title1",
content: codeData.value.name + "全景作战报告",
date: moment().format("MM/DD/YYYY"),
});
},
"",
50
);
// chatStore.messages.push({
// sender: "ai",
// class: "title1",
// type: "title1",
// content: codeData.value.name + "",
// date: moment().format("MM/DD/YYYY"),
// });
//
const pc1 = marked(
@ -385,18 +540,20 @@ watch(
content: "",
isTyping: true,
});
chatStore.messages.push(aiMessage1);
// chatStore.messages.push(aiMessage1);
let index1 = 0;
const typingInterval1 = setInterval(() => {
if (index1 < ac1.length) {
aiMessage1.content += ac1.charAt(index1);
index1++;
} else {
clearInterval(typingInterval1);
aiMessage1.isTyping = false;
}
}, 50); // 50ms/
// let index1 = 0;
// const typingInterval1 = setInterval(() => {
// if (index1 < ac1.length) {
// aiMessage1.content += ac1.charAt(index1);
// index1++;
// } else {
// clearInterval(typingInterval1);
// aiMessage1.isTyping = false;
// }
// }, 50); // 50ms/
addTypingTask(aiMessage1, ac1, 50);
// chatStore.messages.push({
// sender: "ai",
@ -415,7 +572,8 @@ watch(
const klineMessageId1 = `kline-${Date.now()}`;
console.log("生成K线消息ID:", klineMessageId1);
chatStore.messages.push({
addTypingTask(
{
sender: "ai",
class: "content1",
type: "content1",
@ -424,39 +582,52 @@ watch(
messageId: klineMessageId1,
hasValidData: true,
klineType: 1,
});
console.log("六色罗盘消息已添加到聊天列表");
},
"",
50
);
// chatStore.messages.push({
// sender: "ai",
// class: "content1",
// type: "content1",
// kline: true,
// chartData: sz,
// messageId: klineMessageId1,
// hasValidData: true,
// klineType: 1,
// });
//
nextTick(() => {
console.log("nextTick开始 - 准备渲染图表");
console.log("消息列表:", chatStore.messages);
// console.log("");
// K线
let klineIndex = -1;
for (let i = 0; i < chatStore.messages.length; i++) {
if (chatStore.messages[i].messageId === klineMessageId1) {
klineIndex = i;
break;
}
}
// //
// nextTick(() => {
// console.log("nextTick - ");
// console.log(":", chatStore.messages);
// // K线
// let klineIndex = -1;
// for (let i = 0; i < chatStore.messages.length; i++) {
// if (chatStore.messages[i].messageId === klineMessageId1) {
// klineIndex = i;
// break;
// }
// }
console.log("找到的K线消息索引:", klineIndex);
// console.log("K线:", klineIndex);
if (klineIndex !== -1) {
const containerId = `kline-container-${klineIndex}`;
console.log("图表容器ID:", containerId);
// if (klineIndex !== -1) {
// const containerId = `kline-container-${klineIndex}`;
// console.log("ID:", containerId);
// DOM
setTimeout(() => {
console.log("延时执行,确保DOM已渲染");
KlineCanvsEcharts(containerId);
}, 100); // DOM
} else {
console.warn("未找到K线消息");
}
});
// // DOM
// setTimeout(() => {
// console.log("DOM");
// KlineCanvsEcharts(containerId);
// }, 100); // DOM
// } else {
// console.warn("K线");
// }
// });
// K线
const AIGoldBullData = JSON.parse(JSON.stringify(toRaw(AIGoldBull)));
@ -485,7 +656,19 @@ watch(
const klineMessageId2 = `kline-${Date.now() + 1}`;
console.log("生成K线消息ID:", klineMessageId2);
chatStore.messages.push({
// chatStore.messages.push({
// sender: "ai",
// class: "content2",
// type: "content2",
// kline: true,
// chartData: Kline20,
// messageId: klineMessageId2,
// hasValidData: true, // hasValidData
// klineType: 2,
// });
addTypingTask(
{
sender: "ai",
class: "content2",
type: "content2",
@ -494,47 +677,60 @@ watch(
messageId: klineMessageId2,
hasValidData: true, // hasValidData
klineType: 2,
});
console.log("K线消息已添加到聊天列表");
},
"",
50
);
//
nextTick(() => {
console.log("nextTick开始 - 准备渲染图表");
console.log("消息列表:", chatStore.messages);
// console.log("K线");
// K线
let klineIndex = -1;
for (let i = 0; i < chatStore.messages.length; i++) {
if (chatStore.messages[i].messageId === klineMessageId2) {
klineIndex = i;
break;
}
}
// //
// nextTick(() => {
// console.log("nextTick - ");
// console.log(":", chatStore.messages);
// // K线
// let klineIndex = -1;
// for (let i = 0; i < chatStore.messages.length; i++) {
// if (chatStore.messages[i].messageId === klineMessageId2) {
// klineIndex = i;
// break;
// }
// }
console.log("找到的K线消息索引:", klineIndex);
// console.log("K线:", klineIndex);
if (klineIndex !== -1) {
const containerId = `kline-container-${klineIndex}`;
console.log("图表容器ID:", containerId);
// if (klineIndex !== -1) {
// const containerId = `kline-container-${klineIndex}`;
// console.log("ID:", containerId);
// DOM
setTimeout(() => {
console.log("延时执行,确保DOM已渲染");
KlineCanvsEcharts(containerId);
}, 100); // DOM
} else {
console.warn("未找到K线消息");
}
});
// // DOM
// setTimeout(() => {
// console.log("DOM");
// KlineCanvsEcharts(containerId);
// }, 100); // DOM
// } else {
// console.warn("K线");
// }
// });
// 2
chatStore.messages.push({
addTypingTask(
{
sender: "ai",
class: "title2",
type: "title2",
content: "",
});
},
"",
50
);
// chatStore.messages.push({
// sender: "ai",
// class: "title2",
// type: "title2",
// content: "",
// });
// 1
const pc2 = marked(result22.data.hxjzpg);
console.log(pc2, "pc2");
@ -555,18 +751,19 @@ watch(
content: "",
isTyping: true,
});
chatStore.messages.push(aiMessage2);
// chatStore.messages.push(aiMessage2);
let index2 = 0;
const typingInterval2 = setInterval(() => {
if (index2 < ac2.length) {
aiMessage2.content += ac2.charAt(index2);
index2++;
} else {
clearInterval(typingInterval2);
aiMessage2.isTyping = false;
}
}, 50); // 50ms/
// let index2 = 0;
// const typingInterval2 = setInterval(() => {
// if (index2 < ac2.length) {
// aiMessage2.content += ac2.charAt(index2);
// index2++;
// } else {
// clearInterval(typingInterval2);
// aiMessage2.isTyping = false;
// }
// }, 50); // 50ms/
addTypingTask(aiMessage2, ac2, 50);
// chatStore.messages.push({
// sender: "ai",
@ -575,12 +772,22 @@ watch(
// content: ac2,
// });
// 3-2
chatStore.messages.push({
addTypingTask(
{
sender: "ai",
class: "title3",
type: "title3",
content: title2,
});
},
"",
50
);
// chatStore.messages.push({
// sender: "ai",
// class: "title3",
// type: "title3",
// content: title2,
// });
// 2
// const pc3 = marked(result23.data.zhuli1+'\n'+result23.data.zhuli2+'\n'+result23.data.zhuli3);
// const ac3 = pc3.replace(
@ -604,35 +811,20 @@ watch(
content: "",
isTyping: true,
});
chatStore.messages.push(aiMessage3);
// chatStore.messages.push(aiMessage3);
let index3 = 0;
const typingInterval3 = setInterval(() => {
if (index3 < ac3.length) {
aiMessage3.content += ac3.charAt(index3);
index3++;
} else {
clearInterval(typingInterval3);
aiMessage3.isTyping = false;
// let index3 = 0;
// const typingInterval3 = setInterval(() => {
// if (index3 < ac3.length) {
// aiMessage3.content += ac3.charAt(index3);
// index3++;
// } else {
// clearInterval(typingInterval3);
// aiMessage3.isTyping = false;
// KaTeXDOM
nextTick(() => {
aiMessage3.content = aiMessage3.content.replace(
katexRegex,
(match, formula) => {
try {
return katex.renderToString(formula, {
throwOnError: false,
});
} catch (error) {
console.error("KaTeX 渲染错误:", error);
return match;
}
}
);
});
}
}, 50); // 50ms/
// }
// }, 50); // 50ms/
addTypingTask(aiMessage3, ac3, 50);
// chatStore.messages.push({
// sender: "ai",
@ -641,12 +833,22 @@ watch(
// content: ac3,
// });
// 3-3
chatStore.messages.push({
addTypingTask(
{
sender: "ai",
class: "title3",
type: "title3",
content: title3,
});
},
"",
50
);
// chatStore.messages.push({
// sender: "ai",
// class: "title3",
// type: "title3",
// content: title3,
// });
// 3
const arr = result23.data.kongjian.split(",");
const kongjian = `<p style="margin:0;color:#FADC0C;display:flex;justify-content:center;font-size:28px">【空间维度】</p><p style="display:flex;justify-content:center;">${arr[0]},${arr[1]}</p><p style="display:flex;justify-content:center;">${arr[2]},${arr[3]}</p>`;
@ -678,35 +880,20 @@ watch(
content: "",
isTyping: true,
});
chatStore.messages.push(aiMessage4);
// chatStore.messages.push(aiMessage4);
let index4 = 0;
const typingInterval4 = setInterval(() => {
if (index4 < ac4.length) {
aiMessage4.content += ac4.charAt(index4);
index4++;
} else {
clearInterval(typingInterval4);
aiMessage4.isTyping = false;
// let index4 = 0;
// const typingInterval4 = setInterval(() => {
// if (index4 < ac4.length) {
// aiMessage4.content += ac4.charAt(index4);
// index4++;
// } else {
// clearInterval(typingInterval4);
// aiMessage4.isTyping = false;
// KaTeXDOM
nextTick(() => {
aiMessage4.content = aiMessage4.content.replace(
katexRegex,
(match, formula) => {
try {
return katex.renderToString(formula, {
throwOnError: false,
});
} catch (error) {
console.error("KaTeX 渲染错误:", error);
return match;
}
}
);
});
}
}, 50); // 50ms/
// }
// }, 50); // 50ms/
addTypingTask(aiMessage4, ac4, 50);
// chatStore.messages.push({
// sender: "ai",
@ -715,6 +902,16 @@ watch(
// content: ac4,
// });
// 3-4
addTypingTask(
{
sender: "ai",
class: "title3",
type: "title3",
content: title4,
},
"",
50
);
chatStore.messages.push({
sender: "ai",
class: "title3",
@ -744,35 +941,20 @@ watch(
content: "",
isTyping: true,
});
chatStore.messages.push(aiMessage5);
// chatStore.messages.push(aiMessage5);
let index5 = 0;
const typingInterval5 = setInterval(() => {
if (index5 < ac5.length) {
aiMessage5.content += ac5.charAt(index5);
index5++;
} else {
clearInterval(typingInterval5);
aiMessage5.isTyping = false;
// let index5 = 0;
// const typingInterval5 = setInterval(() => {
// if (index5 < ac5.length) {
// aiMessage5.content += ac5.charAt(index5);
// index5++;
// } else {
// clearInterval(typingInterval5);
// aiMessage5.isTyping = false;
// KaTeXDOM
nextTick(() => {
aiMessage5.content = aiMessage5.content.replace(
katexRegex,
(match, formula) => {
try {
return katex.renderToString(formula, {
throwOnError: false,
});
} catch (error) {
console.error("KaTeX 渲染错误:", error);
return match;
}
}
);
});
}
}, 50); // 50ms/
// }
// }, 50); // 50ms/
addTypingTask(aiMessage5, ac5, 50);
// chatStore.messages.push({
// sender: "ai",
@ -790,35 +972,20 @@ watch(
content: "",
isTyping: true,
});
chatStore.messages.push(aiMessage6);
// chatStore.messages.push(aiMessage6);
let index6 = 0;
const typingInterval6 = setInterval(() => {
if (index6 < ac6.length) {
aiMessage6.content += ac6.charAt(index6);
index6++;
} else {
clearInterval(typingInterval6);
aiMessage6.isTyping = false;
// let index6 = 0;
// const typingInterval6 = setInterval(() => {
// if (index6 < ac6.length) {
// aiMessage6.content += ac6.charAt(index6);
// index6++;
// } else {
// clearInterval(typingInterval6);
// aiMessage6.isTyping = false;
// KaTeXDOM
nextTick(() => {
aiMessage6.content = aiMessage6.content.replace(
katexRegex,
(match, formula) => {
try {
return katex.renderToString(formula, {
throwOnError: false,
});
} catch (error) {
console.error("KaTeX 渲染错误:", error);
return match;
}
}
);
});
}
}, 50); // 50ms/
// }
// }, 50); // 50ms/
addTypingTask(aiMessage6, ac6, 100);
// chatStore.messages.push({
// sender: "ai",
@ -921,8 +1088,9 @@ watch(
// sender: "ai",
// content: "AI... ",
// });
chatStore.setLoading(false);
// chatStore.setLoading(false);
} finally {
chatStore.setLoading(false);
await chatStore.getUserCount();
}
}

103
src/views/homePage.vue

@ -238,7 +238,7 @@ const smoothScrollToBottom = async () => {
}
};
const throttledSmoothScrollToBottom = _.throttle(smoothScrollToBottom, 500, {
const throttledSmoothScrollToBottom = _.throttle(smoothScrollToBottom, 300, {
trailing: false,
});
@ -323,7 +323,7 @@ const heightListener = () => {
//
const isBottom =
aftertop + tabContainer.offsetHeight + 50 >= tabContainer.scrollHeight;
aftertop + tabContainer.offsetHeight + 70 >= tabContainer.scrollHeight;
if (activeTab.value === "AIchat") {
if (aftertop - befortop > 0) {
@ -526,8 +526,16 @@ onMounted(async () => {
<img :src="getCountAll" class="action-btn" />
<div class="count-number">{{ UserCount }}</div>
</div>
<img :src="announcementBtn" class="announcement-btn action-btn" @click="showAnnouncement" />
<img :src="feedbackBtn" class="announcement-btn action-btn" @click="showFeedback" />
<img
:src="announcementBtn"
class="announcement-btn action-btn"
@click="showAnnouncement"
/>
<img
:src="feedbackBtn"
class="announcement-btn action-btn"
@click="showFeedback"
/>
</div>
</el-header>
@ -536,17 +544,28 @@ onMounted(async () => {
<div class="main-wrapper">
<section class="tab-section">
<div class="tab-container">
<div v-for="(tab, index) in tabs" :key="tab.name" @click="setActiveTab(tab.name, index)" :class="[
<div
v-for="(tab, index) in tabs"
:key="tab.name"
@click="setActiveTab(tab.name, index)"
:class="[
'tab-item',
{ active: activeIndex === index && !isAnnouncementVisible },
]">
]"
>
<span>{{ tab.label }}</span>
</div>
</div>
</section>
<div class="tab-content" ref="tabContent">
<component :is="activeComponent" :messages="messages" @updateMessage="updateMessage"
@sendMessage="sendMessage" @ensureAIchat="ensureAIchat" ref="aiEmotionRef" />
<component
:is="activeComponent"
:messages="messages"
@updateMessage="updateMessage"
@sendMessage="sendMessage"
@ensureAIchat="ensureAIchat"
ref="aiEmotionRef"
/>
</div>
</div>
</el-main>
@ -559,16 +578,42 @@ onMounted(async () => {
<img v-else :src="thinkNoActive" @click="toggleThink" class="action-btn" />
<img :src="languageBtn" @click="changeLanguage" class="action-btn" /> -->
<!-- 夺宝奇兵大模型按钮 -->
<img :src="activeTab === 'AIchat' ? dbqbButton01 : dbqbButton02" @click="setActiveTab('AIchat', 0)"
class="action-btn model-btn" alt="夺宝奇兵大模型" />
<img
:src="activeTab === 'AIchat' ? dbqbButton01 : dbqbButton02"
@click="setActiveTab('AIchat', 0)"
class="action-btn model-btn"
alt="夺宝奇兵大模型"
/>
<!-- AI情绪大模型按钮 -->
<img :src="activeTab === 'AiEmotion' ? emotionButton01 : emotionButton02
" @click="setActiveTab('AiEmotion', 1)" class="action-btn model-btn" alt="AI情绪大模型" />
<img v-if="isVoice" :src="voice" @click="toggleVoice" class="action-btn" />
<img v-else :src="voiceNoActive" @click="toggleVoice" class="action-btn" />
<img
:src="
activeTab === 'AiEmotion' ? emotionButton01 : emotionButton02
"
@click="setActiveTab('AiEmotion', 1)"
class="action-btn model-btn"
alt="AI情绪大模型"
/>
<img
v-if="isVoice"
:src="voice"
@click="toggleVoice"
class="action-btn"
/>
<img
v-else
:src="voiceNoActive"
@click="toggleVoice"
class="action-btn"
/>
</div>
<img v-if="!chatStore.isLoading" :src="sendBtn" @click="sendMessage" class="action-btn send-btn" />
<div v-else @click="chatStore.setLoading(false)">
<img
v-if="!chatStore.isLoading"
:src="sendBtn"
@click="sendMessage"
class="action-btn send-btn"
/>
<!-- <div v-else @click="chatStore.setLoading(false)"> -->
<div v-else>
<el-icon class="is-loading">
<Loading />
</el-icon>
@ -578,9 +623,17 @@ onMounted(async () => {
<!-- 第二行输入框 -->
<div class="footer-second-line">
<img :src="msgBtn" class="msg-icon" />
<el-input type="textarea" v-model="message" @focus="onFocus" @blur="onBlur"
:autosize="{ minRows: 1, maxRows: 4 }" placeholder="给AI小财神发消息..." class="msg-input"
@keydown.enter.exact.prevent="isLoading ? null : sendMessage()" resize="none">
<el-input
type="textarea"
v-model="message"
@focus="onFocus"
@blur="onBlur"
:autosize="{ minRows: 1, maxRows: 4 }"
placeholder="给AI小财神发消息..."
class="msg-input"
@keydown.enter.exact.prevent="isLoading ? null : sendMessage()"
resize="none"
>
</el-input>
</div>
</el-footer>
@ -598,8 +651,16 @@ onMounted(async () => {
<img :src="getCountAll" class="action-btn" />
<div class="count-number">{{ UserCount }}</div>
</div>
<img :src="announcementBtn" class="announcement-btn action-btn" @click="showAnnouncement" />
<img :src="feedbackBtn" class="announcement-btn action-btn" @click="showFeedback" />
<img
:src="announcementBtn"
class="announcement-btn action-btn"
@click="showAnnouncement"
/>
<img
:src="feedbackBtn"
class="announcement-btn action-btn"
@click="showFeedback"
/>
</div>
</el-header>

Loading…
Cancel
Save