You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

4173 lines
120 KiB

<script setup>
import { ref, onMounted, watch, nextTick, reactive, onUnmounted } from "vue";
import { ElDialog, ElMessage } from "element-plus";
import MessageItem from "@/components/deepNine/MessageItem.vue";
import ThinkingGif from "@/components/deepNine/ThinkingGif.vue";
import {
dbqbFirstAPI,
dbqbSecondOneAPI,
dbqbSecondTwoAPI,
dbqbSecondThreeAPI,
dbqbSecondFourAPI,
dataListAPI,
} from "../api/AIxiaocaishen";
import {
getNineTurnsAPI,
deepNineFirstAPI,
deepNineSecondOneAPI,
deepNineSecondTwoAPI,
deepNineSecondThreeAPI,
} from "../api/deepNine";
import { useUserStore } from "../store/userPessionCode";
import { useDeepNineStore } from "../store/deepNine";
import { useDeepNineAudioStore } from "../store/deepNineAudio";
import { useDataStore } from "@/store/dataList.js";
import { marked } from "marked"; // 引入marked库
// 导入思考过程GIF
import thinkingGif from "@/assets/img/gif/思考.gif";
import analyzeGif from "@/assets/img/gif/解析.gif";
import generateGif from "@/assets/img/gif/生成.gif";
import katex from "katex"; // 引入 KaTeX 库
import { htmlToText } from "html-to-text";
import { Howl, Howler } from "howler";
import * as echarts from "echarts";
import _, { add } from "lodash";
import moment from "moment";
import title1 from "@/assets/img/AIchat/核心价值评估.png";
import title2 from "@/assets/img/AIchat/主力作战.png";
import title3 from "@/assets/img/AIchat/攻防三维.png";
import title4 from "@/assets/img/AIchat/综合作战.png";
import logo2 from "@/assets/img/AIchat/开启无限财富.png";
import getCountAll from "../assets/img/homePage/get-count-all.png";
import voice from "../assets/img/homePage/tail/voice.png";
import voiceNoActive from "../assets/img/homePage/tail/voice-no-active.png";
import { useChatStore } from "../store/chat";
const homepageChatStore = useChatStore();
const chatStore = useDeepNineStore();
const audioStore = useDeepNineAudioStore();
const dataStore = useDataStore();
// 将这些变量移到全局作用域
const audioQueue = ref([]);
const isPlayingAudio = ref(false);
let currentPlayIndex = 0;
let isCallingPlayNext = false;
// 音频预加载状态
const audioPreloadStatus = {
one: { loaded: false, url: null },
two: { loaded: false, url: null },
three: { loaded: false, url: null },
four: { loaded: false, url: null },
five: { loaded: false, url: null },
};
// 音频队列顺序管理
const audioQueueOrder = {
"API1-第一个": 1, // 第一个接口的第一个音频 (link1)
"API1-第二个": 2, // 第一个接口的第二个音频 (link)
"API2-第一个": 3, // 第二个接口的第一个音频 (link3)
"API2-第二个": 4, // 第二个接口的第二个音频 (link1)
"API3-第一个": 5, // 第三个接口的音频 (link)
};
// 播放下一个音频的函数
const playNextAudio = () => {
if (isCallingPlayNext) {
console.log("playNextAudio已在执行中,跳过重复调用");
return;
}
if (currentPlayIndex >= audioQueue.value.length) {
console.log(
"所有音频播放完成,重置到第一个音频 currentPlayIndex",
currentPlayIndex
);
// 播放完成后重置到第一个音频,但不自动播放
currentPlayIndex = 0;
audioStore.isPlaying = false;
audioStore.isPaused = false;
audioStore.playbackPosition = 0;
// 清除音频实例,确保下次点击从头开始
audioStore.soundInstance = null;
audioStore.nowSound = null;
if (audioQueue.value.length > 0) {
audioStore.setCurrentAudioUrl(audioQueue.value[0]);
}
return;
}
isCallingPlayNext = true;
const audioInfo = audioQueue.value[currentPlayIndex];
if (!audioInfo || !audioInfo.url) {
console.warn(`音频信息无效,跳过索引 ${currentPlayIndex}`);
currentPlayIndex++;
isCallingPlayNext = false;
playNextAudio();
return;
}
console.log(`开始播放 ${audioInfo.name},索引: ${currentPlayIndex}`);
const audio = new Howl({
src: [audioInfo.url],
html5: false,
format: ["mp3", "acc"],
// rate: 2,
retryCount: 0,
onplay: () => {
audioStore.isPlaying = true;
isPlayingAudio.value = true;
isCallingPlayNext = false;
console.log(`${audioInfo.name}音频开始播放111`);
},
onpause: () => {
audioStore.isPlaying = false;
audioStore.isPaused = true;
audioStore.playbackPosition = audio.seek() || 0;
console.log(`${audioInfo.name}音频已暂停`);
},
onresume: () => {
audioStore.isPlaying = true;
audioStore.isPaused = false;
console.log(`${audioInfo.name}音频继续播放`);
},
onend: () => {
console.log(`${audioInfo.name}音频播放完成,准备播放下一个`);
audioStore.isPlaying = false;
audioStore.isPaused = false;
audioStore.playbackPosition = 0;
isPlayingAudio.value = false;
currentPlayIndex++;
console.log(
"currentPlayIndex",
currentPlayIndex,
"audioQueue.value.length",
audioQueue.value.length
);
if (currentPlayIndex < audioQueue.value.length) {
console.log(
`队列中还有音频,500ms后播放下一个 (索引:${currentPlayIndex})`
);
setTimeout(() => {
isCallingPlayNext = false;
playNextAudio();
}, 200);
} else {
console.log("🎉 所有音频播放完成,清除音频实例");
chatStore.messages[chatStore.currentUserIndex].audioStatus = false;
audioStore.nowSound = null;
audioStore.soundInstance = null;
isCallingPlayNext = false;
}
},
onstop: () => {
console.log(`${audioInfo.name}音频被停止`);
audioStore.isPlaying = false;
audioStore.isPaused = false;
audioStore.playbackPosition = 0;
},
onloaderror: (id, err) => {
console.error(`${audioInfo.name}音频播放失败:`, err);
isPlayingAudio.value = false;
isCallingPlayNext = false;
setTimeout(() => {
playNextAudio();
}, 100);
},
});
audioStore.setCurrentAudioUrl(audioInfo.url);
audioStore.nowSound = audio;
audioStore.setAudioInstance(audio);
console.log(`尝试播放${audioInfo.name}音频`);
audio.play();
};
// 添加音频到播放队列(确保顺序)
const addToAudioQueue = (url, name) => {
console.log(`=== 添加音频到队列 ===`);
console.log("URL:", url);
console.log("Name:", name);
console.log("音频启用状态:", audioStore.isVoiceEnabled);
if (url && audioStore.isVoiceEnabled) {
const audioItem = {
url,
name,
order: audioQueueOrder[name] || 999,
};
audioQueue.value.push(audioItem);
// 按顺序排序队列
audioQueue.value.sort((a, b) => a.order - b.order);
console.log(`音频${name}已添加到播放队列,顺序:${audioItem.order}`);
console.log(
"当前队列顺序:",
audioQueue.value.map((item) => `${item.name}(${item.order})`)
);
// 只有在确实没有音频在播放且这是第一个音频时才开始播放
if (
!isPlayingAudio.value &&
!audioStore.isPlaying &&
audioQueue.value.length === 1
) {
console.log("✅ 条件满足:没有音频在播放且这是第一个音频,立即开始播放", {
isPlayingAudio: isPlayingAudio.value,
audioStoreIsPlaying: audioStore.isPlaying,
queueLength: audioQueue.value.length,
});
playNextAudio();
} else {
console.log("⏳ 等待条件:", {
isPlayingAudio: isPlayingAudio.value,
audioStoreIsPlaying: audioStore.isPlaying,
queueLength: audioQueue.value.length,
reason:
audioQueue.value.length > 1 ? "队列中已有其他音频" : "有音频正在播放",
});
}
} else {
console.log("❌ 跳过添加音频:", {
hasUrl: !!url,
voiceEnabled: audioStore.isVoiceEnabled,
});
}
console.log(`=== 添加音频完成 ===`);
};
// 语音播放控制函数
const toggleVoiceForUser = (index) => {
console.log(
"上一个按钮坐标",
chatStore.currentUserIndex,
"当前按钮坐标",
index
);
if (
!chatStore.messages[index].audioArray[0] ||
!chatStore.messages[index].audioArray[1] ||
!chatStore.messages[index].audioArray[2] ||
!chatStore.messages[index].audioArray[3]
) {
return;
}
// 先把当前按钮状态修改
chatStore.messages[index].audioStatus =
!chatStore.messages[index].audioStatus;
// 如果当前按钮和之前的按钮不是同一个则再修改之前的按钮的状态
if (chatStore.currentUserIndex != index) {
if (chatStore.currentUserIndex != null) {
if (audioStore.isPlaying) {
audioStore.togglePlayPause();
}
chatStore.messages[chatStore.currentUserIndex].audioStatus = false;
}
// 强制停止所有音频实例(移动端兼容)
if (audioStore.soundInstance) {
audioStore.soundInstance.stop();
audioStore.soundInstance = null;
}
audioPreloadStatus.one = { loaded: false, url: null };
audioPreloadStatus.two = { loaded: false, url: null };
audioPreloadStatus.three = { loaded: false, url: null };
audioPreloadStatus.four = { loaded: false, url: null };
audioPreloadStatus.five = { loaded: false, url: null };
if (chatStore.messages[index].audioArray[0]) {
audioPreloadStatus.one.loaded = true;
audioPreloadStatus.one.url = chatStore.messages[index].audioArray[0];
}
if (chatStore.messages[index].audioArray[1]) {
audioPreloadStatus.two.loaded = true;
audioPreloadStatus.two.url = chatStore.messages[index].audioArray[1];
}
if (chatStore.messages[index].audioArray[2]) {
audioPreloadStatus.three.loaded = true;
audioPreloadStatus.three.url = chatStore.messages[index].audioArray[2];
}
if (chatStore.messages[index].audioArray[3]) {
audioPreloadStatus.four.loaded = true;
audioPreloadStatus.four.url = chatStore.messages[index].audioArray[3];
}
chatStore.currentUserIndex = index;
audioQueue.value = [];
isPlayingAudio.value = false;
audioStore.soundInstance = null;
currentPlayIndex = 0;
isCallingPlayNext = false;
setTimeout(() => {
addToAudioQueue(chatStore.messages[index].audioArray[0], "API1-第一个");
addToAudioQueue(chatStore.messages[index].audioArray[1], "API2-第二个");
addToAudioQueue(chatStore.messages[index].audioArray[2], "API3-第三个");
addToAudioQueue(chatStore.messages[index].audioArray[3], "API4-第四个");
if (!audioStore.isVoiceEnabled) {
audioStore.toggleVoice();
} else {
if (audioStore.currentAudioUrl || audioStore.ttsUrl) {
// audioStore.togglePlayPause();
} else {
audioStore.toggleVoice();
}
}
}, 100); // 100ms延迟足够移动端清理音频实例
} else {
if (!audioStore.isVoiceEnabled) {
console.log("1111");
audioStore.toggleVoice();
} else {
if (currentPlayIndex >= audioQueue.value.length) {
console.log("重新开始播放音频序列");
currentPlayIndex = 0;
isPlayingAudio.value = false;
isCallingPlayNext = false;
audioStore.soundInstance = null;
// 重新开始播放
if (audioQueue.value.length > 0) {
playNextAudio();
}
} else if (audioStore.currentAudioUrl || audioStore.ttsUrl) {
console.log("2222");
audioStore.togglePlayPause();
} else {
console.log("3333");
audioStore.toggleVoice();
}
}
}
};
// 计算属性:判断语音是否启用
const isVoice = computed(() => {
return audioStore.isVoiceEnabled;
});
// 随机GIF
const currentGif = ref("");
const renderer = new marked.Renderer();
// 重写 del 方法,让删除线不生效
renderer.del = function (text) {
// 处理各种数据类型
console.log("text", text);
return "~" + text.tokens[0].raw + "<br>" + text.tokens[2].raw + "~";
};
// 定义自定义事件
const emit = defineEmits([
"updateMessage",
"sendMessage",
"enableInput",
"ensureAIchat",
"scrollToBottom",
"showCount",
]);
// 音频播放方法
const playAudio = (url) => {
// 添加空值校验
if (!url) {
console.warn("音频URL为空,跳过播放");
audioStore.isPlaying = false;
return;
}
const handlePlay = () => {
if (audioStore.isNewInstance) {
const newSound = new Howl({
src: [url],
html5: true, // 强制HTML5 Audio解决iOS兼容问题
format: ["mp3", "acc"],
rate: 1.2, // 调整播放速度
onplay: () => {
audioStore.isPlaying = true; // 改为更新store状态
newSound.volume(1); // 添加音量设置
},
onend: () => (audioStore.isPlaying = false),
onstop: () => (audioStore.isPlaying = false),
onloaderror: (id, err) => {
console.error("音频加载失败:", err);
ElMessage.error("音频播放失败,请检查网络连接");
},
});
if (audioStore.nowSound) {
audioStore.nowSound.stop();
}
audioStore.nowSound = newSound;
audioStore.isNewInstance = false;
console.log("新音频");
} else {
console.log("已经有音频");
}
const newSound = audioStore.nowSound;
// 添加立即播放逻辑
newSound.play();
audioStore.setAudioInstance(newSound);
Howler._howls.push(newSound); // 强制注册到全局管理
};
handlePlay();
};
// 新增暂停方法
const pauseAudio = () => {
if (audioStore.soundInstance) {
audioStore.soundInstance.pause();
audioStore.isPlaying = false;
}
};
// 音频轮流播放方法
const playAudioSequence = (audioUrls) => {
console.log("playAudioSequence被调用,参数:", audioUrls);
if (!audioUrls || audioUrls.length === 0) {
console.warn("音频URL列表为空,跳过播放");
return;
}
let currentIndex = 0;
let audioSequence = [...audioUrls]; // 保存音频序列
const playNext = () => {
if (currentIndex >= audioSequence.length) {
console.log("所有音频播放完成,重置到第一个音频");
// 播放完成后重置到第一个音频,但不自动播放
currentIndex = 0;
audioStore.isPlaying = false;
audioStore.isPaused = false;
audioStore.playbackPosition = 0;
// 清除音频实例,确保下次点击从头开始
audioStore.soundInstance = null;
audioStore.nowSound = null;
if (audioSequence.length > 0) {
audioStore.setCurrentAudioUrl(audioSequence[0]);
}
return;
}
const currentUrl = audioSequence[currentIndex];
console.log(`正在播放第${currentIndex + 1}个音频:`, currentUrl);
console.log(
"音频URL有效性检查:",
!!currentUrl,
"长度:",
currentUrl?.length
);
// 增强URL验证
if (
!currentUrl ||
typeof currentUrl !== "string" ||
currentUrl.trim() === ""
) {
console.error(`音频 ${currentIndex + 1} URL无效,跳过该音频`);
currentIndex++;
setTimeout(() => {
playNext();
}, 100);
return;
}
// 检查URL格式
try {
new URL(currentUrl);
} catch (e) {
console.error(`音频 ${currentIndex + 1} URL格式错误:`, currentUrl);
currentIndex++;
setTimeout(() => {
playNext();
}, 100);
return;
}
// 设置当前音频URL
audioStore.setCurrentAudioUrl(currentUrl);
// 停止当前播放的音频
if (audioStore.nowSound) {
audioStore.nowSound.stop();
}
const sound = new Howl({
src: [currentUrl],
html5: true,
format: ["mp3", "acc"],
rate: 1.2,
onplay: () => {
audioStore.isPlaying = true;
audioStore.isPaused = false;
console.log(`开始播放音频 ${currentIndex + 1}`);
console.log("音频播放状态:", {
duration: sound.duration(),
state: sound.state(),
playing: sound.playing(),
});
},
onpause: () => {
audioStore.isPlaying = false;
audioStore.isPaused = true;
audioStore.playbackPosition = sound.seek() || 0;
console.log(`音频 ${currentIndex + 1} 已暂停`);
},
onend: () => {
audioStore.isPlaying = false;
audioStore.isPaused = false;
audioStore.playbackPosition = 0;
console.log(`音频 ${currentIndex + 1} 播放完成`);
currentIndex++;
// 如果是最后一个音频播放完成,立即清除实例
if (currentIndex >= audioSequence.length) {
console.log("最后一个音频播放完成,清除音频实例");
audioStore.soundInstance = null;
audioStore.nowSound = null;
currentIndex = 0; // 立即重置索引
}
// 播放下一个音频
setTimeout(() => {
playNext();
}, 500); // 间隔500ms播放下一个
},
onstop: () => {
audioStore.isPlaying = false;
audioStore.isPaused = false;
audioStore.playbackPosition = 0;
console.log(`音频 ${currentIndex + 1} 已停止`);
},
onloaderror: (id, err) => {
console.error(`音频 ${currentIndex + 1} 加载失败:`, err);
console.error("失败的音频URL:", currentUrl);
console.error("错误详情:", { id, err });
// 增加重试机制
if (!sound.retryCount) {
sound.retryCount = 0;
}
if (sound.retryCount < 2) {
sound.retryCount++;
console.log(
`音频 ${currentIndex + 1}${sound.retryCount}次重试加载`
);
setTimeout(() => {
sound.load();
}, 1000 * sound.retryCount); // 递增延时重试
} else {
console.warn(`音频 ${currentIndex + 1} 重试失败,跳过该音频`);
currentIndex++;
// 跳过失败的音频,播放下一个
setTimeout(() => {
playNext();
}, 100);
}
},
});
audioStore.nowSound = sound;
audioStore.setAudioInstance(sound);
// 添加播放超时检测
const playTimeout = setTimeout(() => {
if (!audioStore.isPlaying && sound.state() === "loading") {
console.warn(`音频 ${currentIndex + 1} 播放超时,可能网络问题`);
sound.stop();
currentIndex++;
playNext();
}
}, 10000); // 10秒超时
// 播放成功后清除超时
sound.once("play", () => {
clearTimeout(playTimeout);
});
console.log(`尝试播放音频 ${currentIndex + 1},URL: ${currentUrl}`);
sound.play();
};
// 重写togglePlayPause方法以支持音频序列控制
audioStore.togglePlayPause = () => {
console.log("音频控制按钮被点击 11111111111");
console.log("当前播放状态:", audioStore.isPlaying);
console.log("当前暂停状态:", audioStore.isPaused);
console.log("当前音频实例:", audioStore.soundInstance);
console.log(
"当前索引:",
currentIndex,
"音频序列长度:",
audioSequence.length
);
if (audioStore.soundInstance) {
if (audioStore.isPlaying) {
// 暂停当前音频
console.log("暂停当前音频");
audioStore.pause();
} else if (audioStore.isPaused) {
// 从暂停位置继续播放
console.log("从暂停位置继续播放");
audioStore.play();
} else {
// 重新开始播放当前音频或从头开始播放序列
console.log("重新开始播放,当前索引:", currentIndex);
if (currentIndex >= audioSequence.length) {
console.log("所有音频已播放完成,从头开始");
currentIndex = 0; // 重置到第一个音频
}
playNext();
}
} else {
// 没有音频实例时,从头开始播放
console.log("没有音频实例,从头开始播放");
currentIndex = 0;
playNext();
}
};
// 开始播放第一个音频
playNext();
};
// 获取消息
const chatMsg = computed(() => chatStore.messages);
const props = defineProps({
messages: Array,
chartData: {
type: Object,
default: null,
},
index: {
type: Number,
required: true,
},
});
// 打字机效果
const typewriterContent = ref("");
const isTyping = ref(false);
const typeWriter = (text, callback) => {
let index = 0;
isTyping.value = true;
typewriterContent.value = "";
const typingInterval = setInterval(() => {
if (index < text.length) {
typewriterContent.value += text.charAt(index);
index++;
// 自动滚动到底部
nextTick(() => {
const container = document.querySelector(".message-area");
if (container) container.scrollTop = container.scrollHeight;
});
} else {
clearInterval(typingInterval);
isTyping.value = false;
if (callback) callback();
}
}, 50); // 调整速度(毫秒)
};
const typingQueue = ref([]);
const isTypingInProgress = ref(false);
// 创建打字机效果的Promise函数
const createTypingEffect = (message, content, speed) => {
return new Promise((resolve) => {
chatStore.messages.push(message);
if (Array.isArray(content) && content.length > 0) {
message.content = "";
message.isTyping = true;
let currentIndex = 0;
const processNextElement = () => {
if (currentIndex >= content.length) {
if (message.isEnd) {
if (message.isEnd == "1") {
apiStatus.one.isEnd = true;
} else if (message.isEnd == "2") {
apiStatus.two.isEnd = true;
} else if (message.isEnd == "3") {
apiStatus.three.isEnd = true;
}
}
if (message.error) {
chatStore.messages.push({
class: "ing",
type: "ing",
flag: false,
content: "系统正在为您努力加载中,请稍后再试",
});
chatStore.isLoading = false;
chatStore.chatInput = false;
emit("enableInput");
if (message.error == "2") {
apiStatus.two.isError = true;
} else if (message.error == "3") {
apiStatus.three.isError = true;
}
}
if (message.end) {
homepageChatStore.getUserCount();
chatStore.isLoading = false;
console.log("打印完毕,接触输入框禁用状态");
chatStore.chatInput = false;
emit("enableInput");
}
message.isTyping = false;
nextTick(() => {
resolve(); // 完成后resolve
});
return;
}
if (currentIndex % 2 === 0) {
// 偶数下标:直接加入
message.content += content[currentIndex];
currentIndex++;
processNextElement(); // 立即处理下一个元素
} else {
// 奇数下标:打字机效果
const text = content[currentIndex];
let charIndex = 0;
const typingInterval = setInterval(() => {
if (charIndex < text.length) {
message.content += text.charAt(charIndex);
charIndex++;
} else {
clearInterval(typingInterval);
currentIndex++;
processNextElement(); // 处理下一个元素
}
}, speed);
}
};
processNextElement(); // 开始处理
} else {
if (message.kline) {
if (message.klineType == 2) {
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线消息");
}
});
}
if (message.isEnd) {
if (message.isEnd == "1") {
apiStatus.one.isEnd = true;
} else if (message.isEnd == "2") {
apiStatus.two.isEnd = true;
} else if (message.isEnd == "3") {
apiStatus.three.isEnd = true;
}
}
if (message.error) {
chatStore.messages.push({
class: "ing",
type: "ing",
flag: false,
content: "系统正在为您努力加载中,请稍后再试",
});
chatStore.isLoading = false;
chatStore.chatInput = false;
emit("enableInput");
if (message.error == "2") {
apiStatus.two.isError = true;
} else if (message.error == "3") {
apiStatus.three.isError = true;
}
}
// 延时1秒后resolve
setTimeout(() => {
resolve();
}, 1000);
} else {
if (message.isEnd) {
if (message.isEnd == "1") {
apiStatus.one.isEnd = true;
} else if (message.isEnd == "2") {
apiStatus.two.isEnd = true;
} else if (message.isEnd == "3") {
apiStatus.three.isEnd = true;
}
}
if (message.error) {
chatStore.messages.push({
class: "ing",
type: "ing",
flag: false,
content: "系统正在为您努力加载中,请稍后再试",
});
chatStore.isLoading = false;
chatStore.chatInput = false;
emit("enableInput");
if (message.error == "2") {
apiStatus.two.isError = true;
} else if (message.error == "3") {
apiStatus.three.isError = true;
}
}
// 延时1秒后resolve
setTimeout(() => {
resolve();
}, 1000);
}
}
});
};
let apiStatus = {};
// 队列处理函数
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) => {
typingQueue.value.push({ message, content, speed });
processTypingQueue();
};
// 显示思考过程
async function showThinkingProcess(stockName = null) {
// 第一步:正在思考
const thinkingMessage1 = reactive({
sender: "ai",
class: "ing",
type: "ing",
flag: true,
content: "深度九大模型正在思考",
gif: thinkingGif,
nowrap: true,
});
chatStore.messages.push(thinkingMessage1);
await new Promise((resolve) => setTimeout(resolve, 1500));
chatStore.messages.pop();
// 第二步:正在解析关键数据(持续显示直到获取到股票名称)
const thinkingMessage2 = reactive({
sender: "ai",
class: "ing",
type: "ing",
flag: true,
content: "正在解析关键数据",
gif: analyzeGif,
nowrap: true,
});
chatStore.messages.push(thinkingMessage2);
// 如果没有股票名称,保持第二步显示
if (!stockName) {
return thinkingMessage2; // 返回消息引用,以便后续更新
}
// 有股票名称后,继续后续步骤
await new Promise((resolve) => setTimeout(resolve, 1500));
chatStore.messages.pop();
// 第三步:生成具体股票的深度共振分析图谱
const thinkingMessage3 = reactive({
sender: "ai",
class: "ing",
type: "ing",
flag: true,
content: `正在生成${stockName}深度共振分析图谱`,
gif: generateGif,
nowrap: true,
});
chatStore.messages.push(thinkingMessage3);
await new Promise((resolve) => setTimeout(resolve, 1500));
chatStore.messages.pop();
// 第四步:报告已生成
const thinkingMessage4 = reactive({
sender: "ai",
class: "ing",
type: "ing",
content: "报告已生成!",
nowrap: true,
});
chatStore.messages.push(thinkingMessage4);
await new Promise((resolve) => setTimeout(resolve, 1500));
chatStore.messages.pop();
return null;
}
// 继续思考过程(当获取到股票名称后调用)
async function continueThinkingProcess(thinkingMessageRef, stockName) {
if (!thinkingMessageRef || !stockName) return;
// 等待一段时间后继续
await new Promise((resolve) => setTimeout(resolve, 1500));
// 移除第二步消息
const index = chatStore.messages.indexOf(thinkingMessageRef);
if (index > -1) {
chatStore.messages.splice(index, 1);
}
// 第三步:生成具体股票的深度共振分析图谱
const thinkingMessage3 = reactive({
sender: "ai",
class: "ing",
type: "ing",
flag: true,
content: `正在生成${stockName}深度共振分析图谱`,
gif: generateGif,
});
chatStore.messages.push(thinkingMessage3);
await new Promise((resolve) => setTimeout(resolve, 1500));
chatStore.messages.pop();
// 第四步:报告已生成
const thinkingMessage4 = reactive({
sender: "ai",
class: "ing",
type: "ing",
content: "报告已生成!",
});
chatStore.messages.push(thinkingMessage4);
await new Promise((resolve) => setTimeout(resolve, 1500));
chatStore.messages.pop();
}
const hasValidData = ref(false);
// 创建一个非响应式的对象来存储图表实例
const chartInstancesMap = {};
// 存储上一次的消息的length
const previousMessagesLength = ref(0);
watch(
() => props.messages,
async (newVal, oldVal) => {
// // 添加空值判断
if (!newVal?.length || newVal === previousMessagesLength.value) return;
chatStore.firstAPICall = true;
console.log("第一阶段,意图识别,获取回复,历史记录禁止点击");
previousMessagesLength.value = newVal.length;
if (newVal.length > 0) {
// 清理语音下标
console.log("chatStore.currentUserIndex", chatStore.currentUserIndex);
if (chatStore.currentUserIndex != null) {
chatStore.messages[chatStore.currentUserIndex].audioStatus = false;
}
chatStore.currentUserIndex = null;
audioStore.stop(); // 暂停语音
// 🔧 新增:重置音频队列状态,确保新音频能够自动播放
audioQueue.value = [];
isPlayingAudio.value = false;
currentPlayIndex = 0;
isCallingPlayNext = false;
// 重置音频预加载状态
audioPreloadStatus.one = { loaded: false, url: null };
audioPreloadStatus.two = { loaded: false, url: null };
audioPreloadStatus.three = { loaded: false, url: null };
audioPreloadStatus.four = { loaded: false, url: null };
// 清除音频实例
audioStore.soundInstance = null;
audioStore.nowSound = null;
audioStore.isPlaying = false;
audioStore.isPaused = false;
audioStore.playbackPosition = 0;
console.log("消息列表已更新,最新消息:", newVal[newVal.length - 1]);
chatStore.messages.push(newVal[newVal.length - 1]);
chatStore.currentUserIndex = chatStore.messages.length - 1;
chatStore.inputUserIndex = chatStore.messages.length - 1;
console.log(
"消息列表已更新,最新消息:",
chatStore.messages[chatStore.messages.length - 1],
"最新用户坐标",
chatStore.currentUserIndex
);
// 获取权限
const userStore = useUserStore();
const params1 = {
language: "cn",
marketList: "usa,sg,my,hk,cn,can,vi,th,in",
content: newVal[newVal.length - 1].content,
token: localStorage.getItem("localToken"),
model: 1,
// language: "cn",
// marketList: "hk,cn,usa,my,sg,vi,in,gb"
// token: "+SsksARQgUHIbIG3rRnnbZi0+fEeMx8pywnIlrmTxo5EOPR/wjWDV7w7+ZUseiBtf9kFa/atmNx6QfSpv5w",
};
// 标志
let flag = true;
const codeData = ref();
// 开始思考过程(不带股票名称)
const thinkingMessageRef = await showThinkingProcess();
// 第一阶段,意图识别
try {
// 调用工作流获取回复
const result = await deepNineFirstAPI(params1);
codeData.value = result.data;
console.log(codeData.value, "codeData");
// 根据意图识别结果判断
if (result.code == 200) {
// 意图识别成功后,更新历史记录状态
chatStore.searchRecord = true;
// 获取到股票名称后,继续思考过程
if (thinkingMessageRef && codeData.value.name) {
await continueThinkingProcess(
thinkingMessageRef,
codeData.value.name
);
}
// for (let i = chatStore.messages.length - 1; i >= 0; --i) {
// if (chatStore.messages[i].sender == "user") {
// chatStore.messages[i].audioStatus = true;
// break;
// }
// }
chatStore.messages.push({
// class: "ing",
// type: "ing",
// flag: flag,
// content: result.data.kaishi,
});
} else {
// 意图识别失败,先清理思考过程消息
if (thinkingMessageRef) {
const index = chatStore.messages.indexOf(thinkingMessageRef);
if (index > -1) {
chatStore.messages.splice(index, 1);
}
}
flag = false;
console.log("执行回绝话术");
const AIcontent = ref(result.msg);
// 修改后的消息处理逻辑
const processedContent = marked(AIcontent.value);
const katexRegex = /\$\$(.*?)\$\$/g;
let aiContent = processedContent.replace(
katexRegex,
(match, formula) => {
try {
return katex.renderToString(formula, { throwOnError: false });
} catch (error) {
console.error("KaTeX 渲染错误:", error);
return match;
}
}
);
console.log(AIcontent, "AIcontent");
if (result.code == 406) {
AIcontent.value = `<p>尊敬的用户您好,您当前的“深度九大模型专属Token”数量为0,无法进行股票查询,可联系客服团队进行充值,感谢您的理解与支持</p>`;
}
const aiMsg = {
class: "ing",
type: "ing",
flag: flag,
content: AIcontent,
};
chatStore.messages.push(aiMsg);
chatStore.isLoading = false;
chatStore.chatInput = false;
chatStore.firstAPICall = false;
console.log("历史记录可以点击");
emit("enableInput");
}
} catch (e) {
// 意图识别异常,先清理思考过程消息
if (thinkingMessageRef) {
const index = chatStore.messages.indexOf(thinkingMessageRef);
if (index > -1) {
chatStore.messages.splice(index, 1);
}
}
console.log(e, "意图识别失败");
chatStore.messages.push({
class: "ing",
type: "ing",
flag: false,
content: "系统正在为您努力加载中,请稍后再试",
});
chatStore.isLoading = false;
chatStore.chatInput = false;
chatStore.firstAPICall = false;
console.log("历史记录可以点击");
emit("enableInput");
}
if (flag) {
const params2 = {
language: "cn",
token: localStorage.getItem("localToken"),
parentId: codeData.value.parentId,
stockId: codeData.value.stockId,
recordId: codeData.value.recordId,
// content: newVal[newVal.length - 1].content,
// marketList: "usa,sg,my,hk,cn,can,vi,th,in",
// name: codeData.value.name,
// code: codeData.value.code,
// market: codeData.value.market,
// language: "cn",
// marketList: "hk,cn,usa,my,sg,vi,in,gb"
// token: "+SsksARQgUHIbIG3rRnnbZi0+fEeMx8pywnIlrmTxo5EOPR/wjWDV7w7+ZUseiBtf9kFa/atmNx6QfSpv5w",
};
try {
const env = import.meta.env.VITE_ENV;
const result20 = await getNineTurnsAPI(
{
token:
env == "development" || env == "test"
? "8Csj5VVX1UbIb4C3oxrnbZi0+fEeMx8pywnIlrmTm45Cb/EllzWACLto9J9+fCFsfdgBOvKvyY94FvqlvM0"
: "8nkj4QBV1RPIb4CzoRTnbZi0+fEeMx8pywnIlrmTxdwROKkuwWqAWu9orpkpeXVqL98DPfeonNYpHv+mucA",
},
{
market: codeData.value.market,
language: "cn",
code: codeData.value.code,
}
);
// 添加空值检查防止访问null对象的属性
const nineTurns = result20.data ? result20.data : null;
const isNineTurns =
nineTurns &&
nineTurns.DXT &&
nineTurns.JZJG &&
nineTurns.KLine20 &&
nineTurns.StockInformation &&
nineTurns.ZJQS
? true
: false;
const katexRegex = /\$\$(.*?)\$\$/g;
let result21;
let result22;
let result23;
let result24;
// 用于跟踪API完成状态和结果
apiStatus = {
one: {
completed: false,
result: null,
error: null,
isError: false,
isEnd: false,
},
two: {
completed: false,
result: null,
error: null,
isError: false,
isEnd: false,
},
three: {
completed: false,
result: null,
error: null,
isError: false,
isEnd: false,
},
four: {
completed: false,
result: null,
error: null,
isError: false,
isEnd: false,
},
};
// 预加载音频函数
const preloadAudio = (url, apiKey) => {
if (!url || !audioStore.isVoiceEnabled) {
audioPreloadStatus[apiKey].loaded = true;
return Promise.resolve();
}
// 立即设置URL,确保即使预加载失败也能使用
audioPreloadStatus[apiKey].url = url;
console.log(`设置音频${apiKey}的URL:`, url);
return new Promise((resolve) => {
const audio = new Howl({
src: [url],
html5: true,
format: ["mp3", "acc"],
rate: 1.2,
preload: true,
onload: () => {
console.log(`音频${apiKey}预加载完成:`, url);
audioPreloadStatus[apiKey].loaded = true;
resolve();
},
onloaderror: (id, err) => {
console.error(`音频${apiKey}预加载失败:`, err);
audioPreloadStatus[apiKey].loaded = true; // 标记为已处理,避免阻塞
// URL已经在上面设置了,即使预加载失败也保留URL
resolve();
},
});
});
};
// 检查第一个接口是否可以开始输出(文本和音频都准备好)
const canStartFirstOutput = () => {
return apiStatus.one.completed && audioPreloadStatus.one.loaded;
};
// 检查并按顺序执行代码的函数
const checkAndExecuteInOrder = () => {
// 检查OneAPI - 只有当文本和音频都准备好时才开始输出
if (canStartFirstOutput() && !apiStatus.one.executed) {
if (apiStatus.one.result) {
apiStatus.one.executed = true;
console.log(
"执行OneAPI代码(文本和音频同步开始):",
apiStatus.one.result
);
// 将第一个音频添加到播放队列(确保顺序:API1)
if (audioPreloadStatus.one.url) {
chatStore.messages[chatStore.inputUserIndex].audioArray.push(
audioPreloadStatus.one.url
);
if (chatStore.currentUserIndex == chatStore.inputUserIndex) {
chatStore.messages[
chatStore.inputUserIndex
].audioStatus = true;
addToAudioQueue(audioPreloadStatus.one.url, "API1-第一个");
} else {
chatStore.messages[
chatStore.inputUserIndex
].audioStatus = false;
}
console.log(
"音频队列:添加API1-1音频(link1),当前队列长度:",
audioQueue.value.length
);
}
// 添加第二个音频(link)到队列
if (audioPreloadStatus.two.url) {
chatStore.messages[chatStore.inputUserIndex].audioArray.push(
audioPreloadStatus.two.url
);
if (chatStore.currentUserIndex == chatStore.inputUserIndex) {
addToAudioQueue(audioPreloadStatus.two.url, "API1-第二个");
} else {
chatStore.messages[
chatStore.inputUserIndex
].audioStatus = false;
}
console.log(
"音频队列:添加API1-2音频(link),当前队列长度:",
audioQueue.value.length
);
}
// 在这里添加OneAPI成功后需要执行的代码
// 删除正在为您生成信息
chatStore.messages.pop();
// 添加报告头和时间
addTypingTask(
{
sender: "ai",
class: "title1",
type: "title1",
content: codeData.value.name + "深度共振分析图谱",
date: result21.data.date,
},
"",
50
);
chatStore.firstAPICall = false;
console.log("历史记录可以点击");
const pc1 = marked(
result21.data.name +
"\n" +
result21.data.price +
"\n" +
result21.data.date
);
const ac1 = pc1.replace(katexRegex, (match, formula) => {
try {
return katex.renderToString(formula, {
throwOnError: false,
});
} catch (error) {
console.error("KaTeX 渲染错误:", error);
return match;
}
});
// 先推送初始消息
const aiMessage1 = reactive({
sender: "ai",
class: "content1",
type: "content1",
content: "",
isTyping: true,
});
addTypingTask(aiMessage1, ["", ac1], 130);
// 九转结构K线图
if (
nineTurns &&
nineTurns.DXT &&
nineTurns.JZJG &&
nineTurns.KLine20 &&
nineTurns.StockInformation &&
nineTurns.ZJQS
) {
const nineTurnsData = JSON.parse(
JSON.stringify(toRaw(nineTurns))
);
console.log("处理 K 线数据 - 开始");
console.log("nineTurnsData", nineTurnsData);
const Kline20 = {
name: nineTurnsData.StockInformation.Name,
Kline: nineTurnsData,
};
// 打印K线数据结构
console.log("K线数据结构:", Kline20);
console.log("K线数据名称:", Kline20.name);
console.log("K线数据:", Kline20.Kline ? Kline20.Kline : null);
// 设置数据有效标志
hasValidData.value = true;
console.log("hasValidData设置为:", hasValidData.value);
// 先推送K线图消息
const klineMessageId2 = `kline-${Date.now() + 1}`;
console.log("生成K线消息ID:", klineMessageId2);
// 添加九转结构图表
addTypingTask(
{
sender: "ai",
class: "content2",
type: "content2",
kline: true,
chartData: Kline20,
messageId: klineMessageId2,
hasValidData: true, // 添加hasValidData标志
klineType: 2,
},
"",
50
);
// 添加标题-数据分析时代下的认知变现
addTypingTask(
{
sender: "ai",
class: "title2",
type: "title2",
content: "",
},
"",
50
);
// 添加图片-数据分析时代下的认知变现
addTypingTask(
{
sender: "ai",
class: "content3",
type: "img1",
content:
"https://d31zlh4on95l9h.cloudfront.net/images/5baa0a449cf74fb6a1afb1c909a21194.png",
},
"",
50
);
// 添加标题-结构框架分析
addTypingTask(
{
sender: "ai",
class: "title3",
type: "title3",
content:
"https://d31zlh4on95l9h.cloudfront.net/images/9ab9d76b6906eb914fa1842dbcd56841.png",
},
"",
50
);
// 添加内容框1
const ac2 = `<p>${result21.data.jgkjfx}</p>`;
// 先推送初始消息
const aiMessage2 = reactive({
sender: "ai",
class: "content3",
type: "content3",
content: "",
isTyping: true,
error: apiStatus.two.error ? "2" : "",
isEnd: "1",
});
addTypingTask(aiMessage2, ["", ac2], 130);
}
} else {
chatStore.messages.push({
class: "ing",
type: "ing",
flag: false,
content: "系统正在为您努力加载中,请稍后再试",
});
chatStore.isLoading = false;
chatStore.chatInput = false;
emit("enableInput");
}
}
// 检查TwoAPI(需要OneAPI已执行)
if (
apiStatus.one.executed &&
apiStatus.two.completed &&
!apiStatus.two.executed
) {
if (apiStatus.two.result) {
apiStatus.two.executed = true;
console.log("执行TwoAPI代码:", apiStatus.two.result);
// 将第二个接口的音频添加到播放队列(确保顺序:API2)
if (audioPreloadStatus.three.url) {
chatStore.messages[chatStore.inputUserIndex].audioArray.push(
audioPreloadStatus.three.url
);
if (chatStore.currentUserIndex == chatStore.inputUserIndex) {
addToAudioQueue(
audioPreloadStatus.three.url,
"API2-第一个"
);
} else {
chatStore.messages[
chatStore.inputUserIndex
].audioStatus = false;
}
console.log(
"音频队列:添加API2-1音频(link3),当前队列长度:",
audioQueue.value.length
);
}
if (audioPreloadStatus.four.url) {
chatStore.messages[chatStore.inputUserIndex].audioArray.push(
audioPreloadStatus.four.url
);
if (chatStore.currentUserIndex == chatStore.inputUserIndex) {
addToAudioQueue(audioPreloadStatus.four.url, "API2-第二个");
} else {
chatStore.messages[
chatStore.inputUserIndex
].audioStatus = false;
}
console.log(
"音频队列:添加API2-2音频(link1),当前队列长度:",
audioQueue.value.length
);
}
// 在这里添加TwoAPI成功后需要执行的代码
// 添加标题-资金动向监控
addTypingTask(
{
sender: "ai",
class: "title3",
type: "title3",
content:
" https://d31zlh4on95l9h.cloudfront.net/images/f95c44f83b3e3c52e88964631c199060.png",
},
"",
50
);
const ac31 = `<p style="margin:0;color:#FFD700;font-weight:bold;display:flex;justify-content:center;font-size:22px">【资金异动信号】</p><p>`;
const ac32 = result22.data.dxtsc;
// const ac33 = result22.data.zjqssc1;
const ac34 = `<p style="margin:0;color:#FFD700;font-weight:bold;display:flex;justify-content:center;font-size:22px">【资金趋势导航】</p><p>`;
const ac35 = result22.data.zjqssc1;
// const ac3 = `<p>${result23.data.DXTSC}</p><p>${result23.data.DXTSC2}</p><p>${result23.data.ZJQSSC1}</p>`;
const ac3Arr = [];
ac3Arr.push(ac31);
if (ac32 != "") {
ac3Arr.push(`<p>${ac32}</p>`);
}
// if (ac33 != "") {
// ac3Arr.push("");
// ac3Arr.push(`<p>${ac33}</p>`);
// }
ac3Arr.push(ac34);
if (ac35 != "") {
ac3Arr.push(`<p>${ac35}</p>`);
}
// 先推送初始消息
const aiMessage3 = reactive({
sender: "ai",
class: "content3",
type: "content3",
content: "",
isTyping: true,
error: apiStatus.three.error ? "3" : "",
isEnd: "2",
});
addTypingTask(aiMessage3, ac3Arr, 200);
} else {
if (
apiStatus.one.isEnd &&
apiStatus.two.error &&
!apiStatus.two.isError
) {
apiStatus.two.isError = true;
chatStore.messages.push({
class: "ing",
type: "ing",
flag: false,
content: "系统正在为您努力加载中,请稍后再试",
});
chatStore.isLoading = false;
chatStore.chatInput = false;
emit("enableInput");
}
}
}
// 检查ThreeAPI(需要TwoAPI已执行)
if (
apiStatus.two.executed &&
apiStatus.three.completed &&
!apiStatus.three.executed
) {
if (apiStatus.three.result) {
apiStatus.three.executed = true;
console.log("执行ThreeAPI代码:", apiStatus.three.result);
// 将第三个接口的音频添加到播放队列(确保顺序:API3)
if (audioPreloadStatus.five.url) {
chatStore.messages[chatStore.inputUserIndex].audioArray.push(
audioPreloadStatus.five.url
);
if (chatStore.currentUserIndex == chatStore.inputUserIndex) {
addToAudioQueue(audioPreloadStatus.five.url, "API3-第一个");
} else {
chatStore.messages[
chatStore.inputUserIndex
].audioStatus = false;
}
console.log(
"音频队列:添加API3音频(link),当前队列长度:",
audioQueue.value.length
);
}
// 在这里添加ThreeAPI成功后需要执行的代码
// 添加标题-策略共振决策模型
addTypingTask(
{
sender: "ai",
class: "title3",
type: "title3",
content:
"https://d31zlh4on95l9h.cloudfront.net/images/d1fa1f4cbd6452796a4c5368d9f57c4d.png",
},
"",
50
);
// 添加内容框4
const ac5 = `<p>${result23.data.zjqssc2}</p>`;
// 先推送初始消息
const aiMessage5 = reactive({
sender: "ai",
class: "content3",
type: "content3",
content: "",
isTyping: true,
});
addTypingTask(aiMessage5, ["", ac5], 240);
const ac6 = "该内容由AI生成,请注意甄别";
// 先推送初始消息
const aiMessage6 = reactive({
sender: "ai",
class: "mianze",
type: "mianze",
content: "",
isTyping: true,
end: true,
});
addTypingTask(aiMessage6, ["", ac6], 210);
} else {
if (
apiStatus.two.isEnd &&
apiStatus.three.error &&
!apiStatus.three.isError
) {
apiStatus.three.isError = true;
chatStore.messages.push({
class: "ing",
type: "ing",
flag: false,
content: "系统正在为您努力加载中,请稍后再试",
});
chatStore.isLoading = false;
chatStore.chatInput = false;
emit("enableInput");
}
}
}
// 检查是否所有API都已完成并执行
if (
apiStatus.one.completed &&
apiStatus.two.completed &&
apiStatus.three.completed
) {
console.log("所有API已完成,开始收集预加载的音频URL");
// 收集所有预加载的音频URL
const audioUrls = [];
console.log("预加载音频状态检查:");
console.log("audioPreloadStatus:", audioPreloadStatus);
if (audioPreloadStatus.one.url) {
console.log(
"添加预加载音频URL one:",
audioPreloadStatus.one.url
);
audioUrls.push(audioPreloadStatus.one.url);
}
if (audioPreloadStatus.two.url) {
console.log(
"添加预加载音频URL two:",
audioPreloadStatus.two.url
);
audioUrls.push(audioPreloadStatus.two.url);
}
if (audioPreloadStatus.three.url) {
console.log(
"添加预加载音频URL three:",
audioPreloadStatus.three.url
);
audioUrls.push(audioPreloadStatus.three.url);
}
if (audioPreloadStatus.four.url) {
console.log(
"添加预加载音频URL four:",
audioPreloadStatus.four.url
);
audioUrls.push(audioPreloadStatus.four.url);
}
console.log("收集到的预加载音频URLs:", audioUrls);
console.log("语音是否启用:", audioStore.isVoiceEnabled);
// 音频播放逻辑已移至各个接口的执行代码中
console.log("所有接口执行完成,音频已在各接口中单独播放");
}
};
const handleOneAPI = async () => {
try {
result21 = await deepNineSecondOneAPI(params2);
if (result21.code == 400) {
throw new Error("API返回错误码400,请求失败");
}
console.log("OneAPI成功返回:", result21);
apiStatus.one.completed = true;
apiStatus.one.result = result21;
// 预加载第一个接口的音频 - link1和link
if (result21?.data?.link1) {
await preloadAudio(result21.data.link1.trim(), "one");
} else {
audioPreloadStatus.one.loaded = true;
}
if (result21?.data?.link) {
await preloadAudio(result21.data.link.trim(), "two");
} else {
audioPreloadStatus.two.loaded = true;
}
// 检查是否可以执行
checkAndExecuteInOrder();
} catch (error) {
console.error("OneAPI失败:", error);
apiStatus.one.completed = true;
apiStatus.one.error = error;
audioPreloadStatus.one.loaded = true; // 失败时也标记为已处理
audioPreloadStatus.two.loaded = true; // 失败时也标记为已处理
// 即使失败也要检查后续执行
checkAndExecuteInOrder();
}
};
const handleTwoAPI = async () => {
try {
result22 = await deepNineSecondTwoAPI(params2);
if (result22.code == 400) {
throw new Error("API返回错误码400,请求失败");
}
console.log("TwoAPI成功返回:", result22);
apiStatus.two.completed = true;
apiStatus.two.result = result22;
// 预加载第二个接口的音频 - link3和link1
if (result22?.data?.link3) {
await preloadAudio(result22.data.link3.trim(), "three");
} else {
audioPreloadStatus.three.loaded = true;
}
if (result22?.data?.link1) {
await preloadAudio(result22.data.link1.trim(), "four");
} else {
audioPreloadStatus.four.loaded = true;
}
// 检查是否可以执行
checkAndExecuteInOrder();
} catch (error) {
console.error("TwoAPI失败:", error);
apiStatus.two.completed = true;
apiStatus.two.error = error;
audioPreloadStatus.three.loaded = true;
audioPreloadStatus.four.loaded = true;
checkAndExecuteInOrder();
}
};
const handleThreeAPI = async () => {
try {
result23 = await deepNineSecondThreeAPI(params2);
if (result23.code == 400) {
throw new Error("API返回错误码400,请求失败");
}
// result23 = await dbqbSecondThreeAPI();
console.log("ThreeAPI成功返回:", result23);
apiStatus.three.completed = true;
apiStatus.three.result = result23;
// 预加载第三个接口的音频 - 只有link字段
if (result23?.data?.link) {
await preloadAudio(result23.data.link.trim(), "five");
} else {
audioPreloadStatus.three.loaded = true;
}
// 检查是否可以执行
checkAndExecuteInOrder();
} catch (error) {
console.error("ThreeAPI失败:", error);
apiStatus.three.completed = true;
apiStatus.three.error = error;
audioPreloadStatus.three.loaded = true;
checkAndExecuteInOrder();
}
};
if (isNineTurns) {
handleOneAPI();
handleTwoAPI();
handleThreeAPI();
} else {
chatStore.messages.pop();
chatStore.messages.push({
class: "ing",
type: "ing",
flag: false,
content: "数据缺失,请稍后重试",
});
chatStore.isLoading = false;
chatStore.chatInput = false;
chatStore.firstAPICall = false;
emit("enableInput");
}
} catch (e) {
console.error("请求失败:", e);
chatStore.firstAPICall = false;
hasValidData.value = false; // 请求失败时设置数据无效
} finally {
// chatStore.setLoading(false);
await homepageChatStore.getUserCount();
}
}
}
},
{ deep: false }
);
// 点击历史记录
watch(
() => chatStore.dbqbClickRecord,
(newValue, oldValue) => {
console.log("new", newValue);
if (!newValue || Object.keys(newValue).length === 0) {
return;
}
const clickRecord = ref(newValue);
console.log("dbqbClickRecord 发生变化:", clickRecord.value);
// 🔧 新增:完整的任务停止逻辑
try {
// 1. 停止所有音频相关任务
chatStore.currentUserIndex = null;
audioStore.stop(); // 暂停语音
// 🔧 新增:重置音频队列状态,确保新音频能够自动播放
audioQueue.value = [];
isPlayingAudio.value = false;
currentPlayIndex = 0;
isCallingPlayNext = false;
// 🔧 新增:重置音频预加载状态
audioPreloadStatus.one = { loaded: false, url: null };
audioPreloadStatus.two = { loaded: false, url: null };
audioPreloadStatus.three = { loaded: false, url: null };
audioPreloadStatus.four = { loaded: false, url: null };
audioPreloadStatus.five = { loaded: false, url: null };
// 🔧 新增:清理音频实例
if (audioStore.soundInstance) {
audioStore.soundInstance.stop();
audioStore.soundInstance.unload();
audioStore.soundInstance = null;
}
audioStore.nowSound = null;
// 2. 停止API调用和状态重置
// 🔧 新增:重置API状态
apiStatus.one = {
completed: false,
result: null,
error: null,
isError: false,
isEnd: false,
};
apiStatus.two = {
completed: false,
result: null,
error: null,
isError: false,
isEnd: false,
};
apiStatus.three = {
completed: false,
result: null,
error: null,
isError: false,
isEnd: false,
};
// 🔧 新增:重置数据有效性标志
hasValidData.value = false;
// 3. 停止打字机效果
typingQueue.value = [];
isTypingInProgress.value = false;
// 4. 重置加载状态
chatStore.isLoading = false;
chatStore.chatInput = false;
emit("enableInput");
// 在下一个事件循环中清空 dbqbClickRecord
setTimeout(() => {
chatStore.dbqbClickRecord = {};
console.log("dbqbClickRecord 已清空");
}, 0);
} catch (error) {
console.error("停止任务时发生错误:", error);
}
if (
!clickRecord.value.wokeFlowData.One ||
!clickRecord.value.wokeFlowData.Two ||
!clickRecord.value.wokeFlowData.Three
) {
return;
}
try {
// 清空聊天框内容
chatStore.messages = [];
const userAudioArray = [
clickRecord.value.wokeFlowData.One.link1,
clickRecord.value.wokeFlowData.One.link,
clickRecord.value.wokeFlowData.Two.link3,
];
// if (clickRecord.value.wokeFlowData.Two.link2) {
// userAudioArray.push(clickRecord.value.wokeFlowData.Two.link2);
// }
userAudioArray.push(clickRecord.value.wokeFlowData.Two.link1);
userAudioArray.push(clickRecord.value.wokeFlowData.Three.link);
const userContent = {
sender: "user",
timestamp: clickRecord.value.createdTime,
content: clickRecord.value.keyword,
audioArray: userAudioArray,
audioStatus: false,
};
chatStore.messages.push(userContent);
chatStore.messages.push({
sender: "ai",
class: "title1",
type: "title1",
content: clickRecord.value.stockName + "深度共振分析图谱",
date: clickRecord.value.wokeFlowData.One.date,
});
const pc1 = marked(
clickRecord.value.wokeFlowData.One.name +
"\n" +
clickRecord.value.wokeFlowData.One.price +
"\n" +
clickRecord.value.wokeFlowData.One.date
);
chatStore.messages.push({
sender: "ai",
class: "content1",
type: "content1",
content: pc1,
});
const nineTurns = clickRecord.value.stockData.data;
// 度牛尺K线图
if (
nineTurns &&
nineTurns.DXT &&
nineTurns.JZJG &&
nineTurns.KLine20 &&
nineTurns.StockInformation &&
nineTurns.ZJQS
) {
const nineTurnsData = JSON.parse(JSON.stringify(toRaw(nineTurns)));
console.log("处理 K 线数据 - 开始");
console.log("nineTurnsData", nineTurnsData);
const Kline20 = {
name: nineTurnsData.StockInformation.Name,
Kline: nineTurnsData,
};
// 打印K线数据结构
console.log("K线数据结构:", Kline20);
console.log("K线数据名称:", Kline20.name);
console.log("K线数据:", Kline20.Kline ? Kline20.Kline : null);
// 设置数据有效标志
hasValidData.value = true;
console.log("hasValidData设置为:", hasValidData.value);
// chatStore.messages.pop();
// 先推送K线图消息
const klineMessageId2 = `kline-${Date.now() + 1}`;
console.log("生成K线消息ID:", klineMessageId2);
chatStore.messages.push({
sender: "ai",
class: "content2",
type: "content2",
kline: true,
chartData: Kline20,
messageId: klineMessageId2,
hasValidData: true, // 添加hasValidData标志
klineType: 2,
});
// 在渲染完成后初始化图表
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);
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线消息");
}
});
}
// 添加标题-数据分析时代下的认知变现
chatStore.messages.push({
sender: "ai",
class: "title2",
type: "title2",
content: "",
});
// 添加图片-数据分析时代下的认知变现
chatStore.messages.push({
sender: "ai",
class: "content3",
type: "img1",
content:
"https://d31zlh4on95l9h.cloudfront.net/images/5baa0a449cf74fb6a1afb1c909a21194.png",
});
// 添加标题-结构框架分析
chatStore.messages.push({
sender: "ai",
class: "title3",
type: "title3",
content:
"https://d31zlh4on95l9h.cloudfront.net/images/9ab9d76b6906eb914fa1842dbcd56841.png",
});
// 添加内容框1
const pc2 = marked(clickRecord.value.wokeFlowData.One.jgkjfx);
// 先推送初始消息
chatStore.messages.push({
sender: "ai",
class: "content3",
type: "content3",
content: pc2,
});
// 添加标题-资金动向监控
chatStore.messages.push({
sender: "ai",
class: "title3",
type: "title3",
content:
" https://d31zlh4on95l9h.cloudfront.net/images/f95c44f83b3e3c52e88964631c199060.png",
});
const ac3 = `<p style="margin:0;color:#FFD700;font-weight:bold;display:flex;justify-content:center;font-size:22px">【资金异动信号】</p><p>${clickRecord.value.wokeFlowData.Two.dxtsc}</p><p style="margin:0;color:#FFD700;font-weight:bold;display:flex;justify-content:center;font-size:22px">【资金趋势导航】</p><p>${clickRecord.value.wokeFlowData.Two.zjqssc1}</p>`;
// 先推送初始消息
chatStore.messages.push({
sender: "ai",
class: "content3",
type: "content3",
content: ac3,
isTyping: true,
});
// 添加标题-策略共振决策模型
chatStore.messages.push({
sender: "ai",
class: "title3",
type: "title3",
content:
"https://d31zlh4on95l9h.cloudfront.net/images/d1fa1f4cbd6452796a4c5368d9f57c4d.png",
});
// 添加内容框4
const ac5 = `<p>${clickRecord.value.wokeFlowData.Three.zjqssc2}</p>`;
// 先推送初始消息
chatStore.messages.push({
sender: "ai",
class: "content3",
type: "content3",
content: ac5,
});
chatStore.messages.push({
sender: "ai",
class: "mianze",
type: "mianze",
content: "该内容由AI生成,请注意甄别",
end: true,
});
} catch (e) {
ElMessage.error("历史数据获取出错!");
console.error("e", e);
}
},
{
deep: true, // 深度监听对象内部属性的变化
immediate: true, // 立即执行一次回调
}
);
function KlineCanvsEcharts(containerId) {
function vwToPx(vw) {
console.log((window.innerWidth * vw) / 100, "vwToPx");
return (window.innerWidth * vw) / 100;
}
// console.log("KLine渲染: 开始处理数据, 容器ID:", containerId);
// 从 chatStore 中获取数据
const messages = chatStore.messages;
// console.log("KLine渲染: 获取到的消息:", messages);
let klineMessageIndex = -1;
let klineData = null;
klineMessageIndex = containerId.split("-")[2];
// console.log("KLine渲染: 找到K线消息索引:", klineMessageIndex);
if (
messages[klineMessageIndex].kline &&
messages[klineMessageIndex].chartData
) {
klineData = messages[klineMessageIndex].chartData;
}
var KlineOption = {};
// 检测设备类型
const isMobile = window.innerWidth < 768;
const isTablet = window.innerWidth >= 768 && window.innerWidth < 1024;
console.log(
"KLine渲染: 设备类型",
isMobile ? "移动设备" : isTablet ? "平板设备" : "桌面设备"
);
if (messages[klineMessageIndex].klineType == 2) {
if (!klineData || !klineData.Kline) {
// console.warn("KLine渲染: 数据无效 - 在chatStore中找不到有效的K线数据");
return;
}
// 获取容器元素
const container = document.getElementById(containerId);
if (!container) {
// console.error("KLine渲染: 找不到容器元素:", containerId);
return;
}
// 创建图表实例
// console.log("KLine渲染: 创建图表实例");
try {
// 如果已有实例,先销毁
if (chartInstancesMap[containerId]) {
// console.log("KLine渲染: 销毁已有图表实例");
chartInstancesMap[containerId].dispose();
delete chartInstancesMap[containerId];
}
// 使用普通变量存储实例
chartInstancesMap[containerId] = echarts.init(container);
// console.log("KLine渲染: 图表实例创建成功");
} catch (error) {
// console.error("KLine渲染: 图表实例创建失败:", error);
return;
}
const nineTurns = klineData.Kline;
console.log("KLine渲染: Kline数据", nineTurns);
// 拿到相应的数据
const splitData = (b) => {
const a = JSON.parse(JSON.stringify(b));
let categoryData = [];
let values = [];
for (let i = 0; i < a.length; i++) {
categoryData.push(a[i].splice(0, 1)[0]);
values.push(a[i]);
}
return {
categoryData,
values,
};
};
function vwToPx(vw) {
return (window.innerWidth * vw) / 100;
}
// k线的数据
var dealData = splitData(nineTurns.KLine20);
var markPointData = [];
const getNineNum = (KLine20, JZJG) => {
JZJG.forEach((item, index) => {
let low = KLine20[index][3];
let high = KLine20[index][4];
if (item[1] != -1) {
if (item[1] == 9) {
markPointData.push({
name: "low",
coord: [index, low],
itemStyle: {
normal: {
color: "rgba(0,0,0,0)", // 标记点透明
},
},
label: {
normal: {
show: true,
position: "bottom",
formatter: `${item[1]}`,
textStyle: {
color: "green",
fontSize: window.innerWidth > 769 ? 18 : 15,
textBorderColor: "#FFFFFF",
textBorderWidth: 2,
fontWeight: "bold",
},
},
},
});
} else {
markPointData.push({
name: "low",
coord: [index, low],
itemStyle: {
normal: {
color: "rgba(0,0,0,0)", // 标记点透明
},
},
label: {
normal: {
show: true,
position: "bottom",
formatter: `${item[1]}`,
textStyle: {
color: "green",
fontSize: window.innerWidth > 769 ? 12 : 9,
textBorderColor: "#FFFFFF",
textBorderWidth: 2,
fontWeight: "bold",
},
},
},
});
}
}
if (item[2] != -1) {
if (item[2] == 9) {
markPointData.push({
name: "high",
coord: [index, high],
itemStyle: {
normal: {
color: "rgba(0,0,0,0)", // 标记点透明
},
},
label: {
normal: {
show: true,
position: "top",
formatter: `${item[2]}`,
textStyle: {
color: "#0099FF",
fontSize: window.innerWidth > 769 ? 18 : 15,
textBorderColor: "#FFFFFF",
textBorderWidth: 2,
fontWeight: "bold",
},
},
},
});
} else {
markPointData.push({
name: "high",
coord: [index, high],
itemStyle: {
normal: {
color: "rgba(0,0,0,0)", // 标记点透明
},
},
label: {
normal: {
show: true,
position: "top",
formatter: `${item[2]}`,
textStyle: {
color: "#0099FF",
fontSize: window.innerWidth > 769 ? 12 : 9,
textBorderColor: "#FFFFFF",
textBorderWidth: 2,
fontWeight: "bold",
},
},
},
});
}
}
});
};
getNineNum(nineTurns.KLine20, nineTurns.JZJG);
// console.log("markPointData", markPointData);
var arrRange = [];
var arrSwing = [];
var arrDXTBar = [];
var arrInvisibleBar = [];
var markPointDXT = [];
var arr5DXTBar = [];
var arr6DXTBar = [];
var arr7DXTBar = [];
var DXTmin = 0;
var DXTmax = 100;
const getDXT = (DXT) => {
DXT.forEach((item, index) => {
arrRange.push(item[1]);
arrSwing.push(item[2]);
if (item[2] > 0) {
arrDXTBar.push(item[2] * 2);
arrInvisibleBar.push(50);
DXTmax = Math.max(DXTmax, item[2] * 2 + 50);
} else {
arrDXTBar.push(-item[2] * 2);
arrInvisibleBar.push(item[2] * 2 + 50);
}
if (item[5] == 1) {
arr5DXTBar.push(100);
} else {
arr5DXTBar.push(0);
}
if (item[6] == 1) {
arr6DXTBar.push(70);
} else {
arr6DXTBar.push(0);
}
if (item[7] == 1) {
arr7DXTBar.push(40);
} else {
arr7DXTBar.push(0);
}
if (item[8] == 1) {
markPointDXT.push({
name: "DTX-8",
coord: [index, 30],
symbol: "triangle", // 三角形符号
symbolSize: 10, // 符号大小
symbolRotate: 0, // 向上的三角形(0度)
itemStyle: {
normal: {
color: "#FFFF00", // 标记点透明
borderColor: "#000000", // 边框颜色
borderWidth: 1,
},
},
label: {
normal: {
show: false,
},
},
});
}
if (item[9] == 1) {
markPointDXT.push({
name: "DTX-9",
coord: [index, 20],
symbolSize: 7, // 正方形大小
itemStyle: {
normal: {
color: "red", // 标记点透明
// borderColor: "#000000", // 边框颜色
// borderWidth: 1,
},
},
label: {
normal: {
show: false,
},
},
});
}
if (item[10] == 1) {
markPointDXT.push({
name: "DTX-10",
coord: [index, 15],
symbol: "rect", // 设置为正方形
symbolSize: 7, // 正方形大小
itemStyle: {
normal: {
color: "grey", // 标记点透明
// borderColor: "#000000", // 边框颜色
// borderWidth: 1,
},
},
label: {
normal: {
show: false,
},
},
});
}
});
};
getDXT(nineTurns.DXT);
var arrBlue = [];
var arrRed = [];
var arrWhite = [];
var arrYellow = [];
const getZJQS = (ZJQS) => {
ZJQS.forEach((item, index) => {
arrBlue.push(item[1]);
arrRed.push(item[2]);
arrWhite.push(item[3]);
arrYellow.push(item[4]);
});
};
getZJQS(nineTurns.ZJQS);
// console.log("arrBlue", arrBlue);
// console.log("arrRed", arrRed);
// console.log("arrWhite", arrWhite);
// console.log("arrYellow", arrYellow);
KlineOption = {
// 手放上去显示的内容
tooltip: {
position: function (point, params) {
if (params[0].seriesIndex == 1) {
return window.innerWidth > 768 ? ["12%", "42%"] : ["18%", "40%"];
}
},
// 调用接口之后方法
formatter: function (a, b, d) {
if (a[0].seriesIndex == 0) {
const KlineTag = ref([]); // 判断几根K线
const AIBullTag = ref([]);
// 找到第一个满足条件的数据
KlineTag.value = a.find((item) => item.data[1])?.data || [];
// 找到第一个满足条件的非 '-' 数据
AIBullTag.value =
a.slice(4).find((item) => item.data[1] !== "-")?.data || [];
return (
a[0].name +
"<br/>" +
"开" +
":" +
KlineTag.value[1] +
"<br/>" +
"收" +
":" +
KlineTag.value[2] +
"<br/>" +
"低" +
":" +
KlineTag.value[3] +
"<br/>" +
"高" +
":" +
KlineTag.value[4]
);
} else if (a[0].seriesIndex == 1) {
return (
`<span style='color:red;'>RANGE: </span>` +
a[0].data +
" <span style='color:yellow'>SWING: </span>" +
a[1].data
);
} else {
return null;
// 格式化成交量显示
let formattedVolume;
if (a[0].data.value >= 10000) {
formattedVolume = (a[0].data.value / 10000).toFixed(2) + "w";
} else {
formattedVolume = a[0].data.value;
}
return a[0].name + "<br/>" + "量" + ":" + formattedVolume;
}
},
trigger: "axis",
axisPointer: {
//坐标轴指示器配置项
type: "cross", //‘line’直线指示器,‘cross’十字准星指示器,‘shadow’阴影指示器
},
backgroundColor: "rgba(119, 120, 125, 0.6)", // 提示框浮层的边框颜色。
borderWidth: 1, // 提示框浮层的边框宽。
borderColor: "#77787D", // 提示框浮层的边框颜色。
padding: 10, // 提示框浮层内边距,
textStyle: {
fontSize: window.innerWidth > 768 ? 12 : 8,
//提示框浮层上的文字样式
color: "#fff",
},
},
// 手放上去时拉的框
axisPointer: {
link: [
{
xAxisIndex: "all", // 同时触发所有图形的 x 坐标轴指示器
},
],
label: {
backgroundColor: "#77787D", // 文本标签的背景颜色
},
},
toolbox: {
show: false,
},
grid: [
{
left: window.innerWidth > 768 ? "12%" : "18%",
right: window.innerWidth > 768 ? "10%" : "12%",
top: window.innerWidth > 768 ? "8%" : "8%",
height: window.innerWidth > 768 ? "34%" : "32%",
containLabel: false,
},
{
left: window.innerWidth > 768 ? "12%" : "18%",
right: window.innerWidth > 768 ? "10%" : "12%",
top: window.innerWidth > 768 ? "48%" : "46%",
height: window.innerWidth > 768 ? "18%" : "20%",
containLabel: false,
},
{
left: window.innerWidth > 768 ? "12%" : "18%",
right: window.innerWidth > 768 ? "10%" : "12%",
top: window.innerWidth > 768 ? "68%" : "68%",
height: window.innerWidth > 768 ? "18%" : "20%",
containLabel: false,
},
{
left: window.innerWidth > 768 ? "12%" : "18%",
right: window.innerWidth > 768 ? "10%" : "12%",
top: window.innerWidth > 768 ? "48%" : "46%",
height: window.innerWidth > 768 ? "18%" : "20%",
containLabel: false,
},
],
xAxis: [
{
type: "category",
data: dealData.categoryData,
boundaryGap: true, // 坐标轴两边是否留空,false表示不留空(通常用于K线图)
axisLine: { onZero: false }, // 设置坐标轴是否通过零点,onZero:false表示不强制穿过零点
splitLine: { show: false }, // 是否显示分隔线,false表示不显示
min: "dataMin", // 坐标轴最小值,'dataMin'表示从数据的最小值开始
max: "dataMax", // 坐标轴最大值,'dataMax'表示从数据的最大值开始
axisPointer: {
label: {
show: false,
},
z: 100, // 坐标轴指示器的层级,较大的值会让它显示在其他元素上方
},
axisLine: {
lineStyle: {
normal: {
color: "black", // 坐标轴线的颜色
},
},
}, //
axisLabel: { show: false }, // 隐藏刻度标签
axisTick: { show: false }, // 隐藏刻度线
},
// 短线通1
{
type: "category",
gridIndex: 1,
data: dealData.categoryData,
boundaryGap: true,
axisPointer: {
label: {
show: false,
},
},
axisLine: { lineStyle: { normal: { color: "black" } } },
axisLabel: {
show: false,
interval: "auto",
},
axisTick: { show: true }, // 隐藏刻度线
},
// 下方成交量图的X轴
{
type: "category",
gridIndex: 2,
data: dealData.categoryData,
boundaryGap: true,
axisLine: { lineStyle: { normal: { color: "black" } } },
axisLabel: {
show: true,
fontSize: window.innerWidth > 768 ? 12 : 9,
interval: "auto",
},
axisTick: { show: false }, // 隐藏刻度线
},
// 短线通2
{
type: "category",
gridIndex: 3,
data: dealData.categoryData,
boundaryGap: true,
axisPointer: {
label: {
show: false,
},
},
axisLine: { show: false },
axisLabel: { show: false },
axisTick: { show: false }, // 隐藏刻度线
},
],
// 控制纵坐标展示数据
yAxis: [
{
scale: true,
gridIndex: 0,
position: "left",
axisLabel: {
inside: false,
align: "right",
fontSize: window.innerWidth > 768 ? 12 : 9,
},
axisLine: {
show: true,
lineStyle: {
normal: {
color: "black",
},
},
},
axisTick: { show: false },
splitLine: { show: false },
},
{
scale: true,
gridIndex: 1,
min: DXTmin,
max: DXTmax,
axisLabel: {
show: true,
fontSize: window.innerWidth > 768 ? 12 : 9,
margin: 8, // 添加边距以获得更好的间距
},
axisLine: { show: true, lineStyle: { normal: { color: "black" } } },
axisTick: { show: false },
splitLine: { show: true, lineStyle: { normal: { type: "dashed" } } }, // 添加分割线以提高可读性
boundaryGap: ["20%", "20%"], // 为坐标轴边界添加内边距
},
{
scale: true,
gridIndex: 2,
splitNumber: 4, // 增加分割数以获得更好的间距
minInterval: 1, // 确保标签之间的最小间隔
axisLabel: {
show: true,
fontSize: window.innerWidth > 768 ? 12 : 9,
margin: 8, // 添加边距以获得更好的间距
},
axisLine: { show: true, lineStyle: { normal: { color: "black" } } },
axisTick: { show: false },
splitLine: { show: true, lineStyle: { normal: { type: "dashed" } } }, // 添加分割线以提高可读性
boundaryGap: ["20%", "20%"], // 为坐标轴边界添加内边距
},
{
scale: true,
gridIndex: 3,
min: DXTmin,
max: DXTmax,
axisLabel: { show: false },
axisLine: { show: false },
axisTick: { show: false },
splitLine: { show: false }, // 添加分割线以提高可读性
boundaryGap: ["20%", "20%"], // 为坐标轴边界添加内边距
},
],
// 下拉条
dataZoom: [
{
type: "inside",
xAxisIndex: [0, 1, 2, 3],
},
{
show: true,
xAxisIndex: [0, 1, 2, 3],
type: "slider",
top: window.innerWidth > 768 ? "90%" : "92%",
height: window.innerWidth > 768 ? "25" : "20",
// left: window.innerWidth > 768 ? "10%" : "8%",
start: 98,
end: 100,
},
],
series: [
{
name: "九转结构",
type: "candlestick",
barWidth: "50%", // 设置和上方图表一致的柱子宽度
data: dealData.values,
xAxisIndex: 0, // 使用第一个 X 轴
yAxisIndex: 0, // 使用第一个 Y 轴
markPoint: {
symbol: "circle",
symbolSize: 10,
data: markPointData,
z: 5, // 确保标记显示在最上层
},
itemStyle: {
normal: {
color: "red", // 默认颜色
color0: "green",
borderColor: "red",
borderColor0: "green",
},
},
gridIndex: 0,
},
{
name: "短线通-RANGE",
type: "line",
xAxisIndex: 1,
yAxisIndex: 1,
data: arrRange,
smooth: false,
symbol: "none",
itemStyle: {
normal: {
color: "#FA0096",
},
},
lineStyle: {
normal: {
width: 2,
type: "solid",
},
},
},
{
name: "短线通-SWING",
type: "line",
xAxisIndex: 1,
yAxisIndex: 1,
data: arrSwing,
smooth: false,
symbol: "none",
show: false,
itemStyle: {
normal: {
color: "transparent", // 设置为透明
opacity: 0, // 透明度为0
},
},
lineStyle: {
normal: {
width: 0, // 线宽为0
opacity: 0, // 透明度为0
},
},
},
{
name: "短线通-隐形柱",
type: "bar",
stack: "Total",
xAxisIndex: 1,
yAxisIndex: 1,
data: arrInvisibleBar,
barWidth: "2", // 设置柱子宽度
// barGap: "-100%", // 完全重叠
z: 5, // 确保标记显示在最上层
itemStyle: {
normal: {
color: "transparent",
// color: "#000",
},
},
lineStyle: {
normal: {
width: 2,
type: "solid",
},
},
},
{
name: "短线通-红蓝柱",
type: "bar",
stack: "Total",
xAxisIndex: 1,
yAxisIndex: 1,
data: arrDXTBar,
barWidth: "20%", // 设置柱子宽度
barGap: "-40%", // 完全重叠
z: 5, // 确保标记显示在最上层
itemStyle: {
normal: {
color: function (params) {
// console.log(params.dataIndex);
if (nineTurns.DXT[params.dataIndex][4] == 1) {
return "#FA0096";
} else if (nineTurns.DXT[params.dataIndex][4] == 2) {
return "#0099FF";
}
return "transparent";
},
},
},
lineStyle: {
normal: {
width: 2,
type: "solid",
},
},
},
{
name: "短线通-黄柱",
type: "bar",
xAxisIndex: 3,
yAxisIndex: 3,
data: arr5DXTBar,
barWidth: "40%", // 设置柱子宽度
z: 6, // 确保标记显示在最上层
itemStyle: {
normal: {
color: "rgb(154,154,16)",
},
},
lineStyle: {
normal: {
width: 2,
type: "solid",
},
},
},
{
name: "短线通-灰柱",
type: "bar",
xAxisIndex: 3,
yAxisIndex: 3,
data: arr6DXTBar,
barWidth: "40%", // 设置柱子宽度
z: 6, // 确保标记显示在最上层
itemStyle: {
normal: {
color: "rgb(140,140,140)",
},
},
lineStyle: {
normal: {
width: 2,
type: "solid",
},
},
},
{
name: "短线通-蓝柱",
type: "bar",
xAxisIndex: 3,
yAxisIndex: 3,
data: arr7DXTBar,
barWidth: "40%", // 设置柱子宽度
barGap: "-100%", // 完全重叠
z: 6, // 确保标记显示在最上层
itemStyle: {
normal: {
color: "rgb(110,200,255)",
},
},
lineStyle: {
normal: {
width: 2,
type: "solid",
},
},
},
{
name: "短线通-其他指标",
type: "line",
xAxisIndex: 1,
yAxisIndex: 1,
markPoint: {
symbol: "circle",
symbolSize: 10,
data: markPointDXT,
z: 7, // 确保标记显示在最上层
},
data: [],
smooth: false,
symbol: "none",
itemStyle: {
normal: {
color: function (params) {
// params.value 是当前数据点的值
// params.dataIndex 是当前数据点的索引
return params.value > 0 ? "#FA0096" : "#0099FF";
},
},
},
lineStyle: {
normal: {
width: 2,
type: "solid",
},
},
},
{
name: "资金趋势-蓝色线",
type: "line",
xAxisIndex: 2,
yAxisIndex: 2,
data: arrBlue,
smooth: false,
symbol: "none",
itemStyle: {
normal: {
color: "#00A2A2",
},
},
lineStyle: {
normal: {
width: 2,
type: "solid",
},
},
},
{
name: "资金趋势-红色线",
type: "line",
xAxisIndex: 2,
yAxisIndex: 2,
data: arrRed,
smooth: false,
symbol: "none",
itemStyle: {
normal: {
color: "#FF0000",
},
},
lineStyle: {
normal: {
width: 2,
type: "solid",
},
},
},
{
name: "资金趋势-白色线",
type: "line",
xAxisIndex: 2,
yAxisIndex: 2,
data: arrWhite,
smooth: false,
symbol: "none",
itemStyle: {
normal: {
color: "grey",
},
},
lineStyle: {
normal: {
width: 2,
type: "solid",
},
},
},
{
name: "资金趋势-黄色线",
type: "line",
xAxisIndex: 2,
yAxisIndex: 2,
data: arrYellow,
smooth: false,
symbol: "none",
itemStyle: {
normal: {
color: "#FFFF00",
},
},
lineStyle: {
normal: {
width: 2,
type: "solid",
},
},
},
],
};
}
// console.log("KLine渲染: 图表配置完成");
try {
// 应用配置
// console.log("KLine渲染: 开始设置图表选项");
chartInstancesMap[containerId].setOption(KlineOption);
// console.log("KLine渲染: 图表选项设置成功");
// 窗口大小变化时重新渲染图表
const resizeFunc = _.throttle(
function () {
console.log("窗口大小改变,调整图表大小");
if (
chartInstancesMap[containerId] &&
!chartInstancesMap[containerId].isDisposed()
) {
// 如果设备类型发生变化,重新渲染
const newIsMobile = window.innerWidth < 768;
const newIsTablet =
window.innerWidth >= 768 && window.innerWidth < 1024;
if (newIsMobile !== isMobile || newIsTablet !== isTablet) {
console.log("设备类型变化,重新渲染图表");
KlineCanvsEcharts(containerId);
return;
}
chartInstancesMap[containerId].resize();
}
},
1000,
{ trailing: false }
);
// 给resize事件绑定一个特定的函数名,便于后续移除
window[`resize_${containerId}`] = resizeFunc;
// 绑定resize事件
window.removeEventListener("resize", window[`resize_${containerId}`]);
window.addEventListener("resize", window[`resize_${containerId}`]);
// console.log("KLine渲染: 图表渲染完成");
} catch (error) {
// console.error("KLine渲染: 图表渲染出错", error);
}
}
watch(
() => audioStore.isVoiceEnabled,
(newVal) => {
// 添加状态锁定逻辑
if (newVal === audioStore.lastVoiceState) return;
audioStore.lastVoiceState = newVal;
if (newVal) {
console.log("开启语音播放");
// 添加重试机制
const tryPlay = () => {
if (!audioStore.ttsUrl) return; // 新增空值判断
if (audioStore.soundInstance?.playing()) return;
playAudio(audioStore.ttsUrl);
setTimeout(() => {
if (!audioStore.soundInstance?.playing()) {
Howler.unload();
}
}, 1000);
};
tryPlay();
} else {
console.log("关闭语音播放");
pauseAudio();
}
},
{ immediate: true }
);
watch(
() => dataStore.activeTabIndex,
(newVal) => {
setTimeout(() => {
console.log("activeTabIndex变化:", newVal);
// 当标签页切换回来时,重新渲染所有图表
if (newVal === 0) {
console.log("切换到AI聊天页,重新渲染图表");
// 延迟执行以确保DOM已渲染
renderAllKlineCharts();
}
}, 1000);
},
{ immediate: true } // 添加immediate属性,确保初始化时执行一次
);
const scrollToTop = () => {
homepageChatStore.dbqbScrollToTop = !homepageChatStore.dbqbScrollToTop;
};
// 添加渲染所有K线图的方法
async function renderAllKlineCharts() {
console.log("重新渲染所有K线图");
// 查找所有K线消息
const messages = chatStore.messages;
for (let i = 0; i < messages.length; i++) {
if (messages[i].kline && messages[i].chartData) {
const containerId = `kline-container-${i}`;
console.log(`尝试渲染K线图: ${containerId}`);
// 确保DOM已经渲染
const container = document.getElementById(containerId);
console.log("container", container);
await nextTick();
if (container) {
// 渲染图表
KlineCanvsEcharts(containerId);
} else {
console.warn(`找不到容器: ${containerId}`);
}
}
}
}
const clearAudio = () => {
// 停止音频播放
if (audioStore.isPlaying) {
audioStore.stop();
console.log("组件卸载,音频已停止");
}
// 清理队列系统
audioQueue.value = [];
isPlayingAudio.value = false;
currentPlayIndex = 0;
isCallingPlayNext = false;
// 清理音频实例
audioStore.soundInstance = null;
audioStore.nowSound = null;
// 停止所有 Howler 实例
Howler.stop();
Howler.unload();
// 重置音频状态
audioStore.isPlaying = false;
audioStore.isPaused = false;
audioStore.playbackPosition = 0;
// 清理预加载状态
Object.keys(audioPreloadStatus).forEach((key) => {
audioPreloadStatus[key] = { loaded: false, url: null };
});
chatStore.currentUserIndex = -1;
};
// 初始化随机GIF
onMounted(() => {
clearAudio();
// 检测移动设备
const isMobile =
/Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
);
if (isMobile) {
// 强制移动设备使用 Web Audio API
Howler.html5PoolSize = 1; // 限制HTML5音频池大小
Howler.autoSuspend = false; // 禁用自动挂起
Howler.usingWebAudio = true; // 强制使用Web Audio API
// 激活音频上下文
const activateAudioContext = () => {
if (Howler.ctx && Howler.ctx.state === "suspended") {
Howler.ctx.resume();
console.log("音频上下文已激活");
}
};
// 监听用户交互以激活音频上下文
document.addEventListener("touchstart", activateAudioContext, {
once: true,
});
document.addEventListener("click", activateAudioContext, { once: true });
}
// 初始化marked组件
marked.setOptions({
breaks: true, // 支持换行符转换为 <br>
gfm: true, // 启用 GitHub Flavored Markdown
sanitize: false, // 不清理 HTML(谨慎使用)
smartLists: true, // 智能列表
smartypants: true, // 智能标点符号
xhtml: false, // 不使用 XHTML 输出
renderer: renderer,
});
renderAllKlineCharts();
console.log("组件挂载完成");
// 重置音频下标
chatStore.currentUserIndex = null;
chatStore.messages.forEach((item) => {
if (item.sender == "user") {
item.audioStatus = false;
}
});
// 添加页面可见性变化监听器
document.addEventListener("visibilitychange", handleVisibilityChange);
// 添加DOM变化监听器
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === "childList" && mutation.addedNodes.length) {
// 检查是否添加了图表容器
const containers = document.querySelectorAll(
'[id^="kline-container-"]'
);
if (containers.length) {
// console.log("DOM变化监听到K线容器:", Array.from(containers).map(el => el.id));
}
}
});
});
// 开始监听DOM变化
observer.observe(document.body, { childList: true, subtree: true });
});
// 页面可见性变化处理
let wasPlayingBeforeHidden = false;
const handleVisibilityChange = () => {
if (document.hidden) {
// 页面被隐藏时,如果音频正在播放,则暂停并记录状态
if (audioStore.isPlaying) {
wasPlayingBeforeHidden = true;
audioStore.pause();
console.log("页面切换离开,音频已暂停");
} else {
wasPlayingBeforeHidden = false;
}
} else {
// 页面重新可见时,如果之前在播放,则恢复播放
if (wasPlayingBeforeHidden && !audioStore.isPlaying) {
audioStore.play();
console.log("页面切换回来,音频已恢复播放");
wasPlayingBeforeHidden = false;
}
}
};
// 组件卸载时清理所有图表实例和事件监听器
onUnmounted(() => {
// 移除页面可见性变化监听器
document.removeEventListener("visibilitychange", handleVisibilityChange);
clearAudio();
// 清理所有图表实例
Object.keys(chartInstancesMap).forEach((key) => {
if (chartInstancesMap[key]) {
// 移除resize事件监听
if (window[`resize_${key}`]) {
window.removeEventListener("resize", window[`resize_${key}`]);
delete window[`resize_${key}`];
}
// 销毁图表实例
chartInstancesMap[key].dispose();
delete chartInstancesMap[key];
}
});
});
</script>
<template>
<div class="chat-container">
<div id="deepNine-top-anchor"></div>
<!-- GIF区域 -->
<div class="gif-area">
<img
src="https://d31zlh4on95l9h.cloudfront.net/images/a686991d0a26bdbafd938a15da93d5b4.png"
alt="深度九大模型logo"
class="bgc"
/>
<img
src="https://d31zlh4on95l9h.cloudfront.net/images/35bf808538183be0062e4647570a9abd.png"
alt="深度九大模型logo"
class="logo1"
/>
<img
src="https://d31zlh4on95l9h.cloudfront.net/images/0c09c892051e7ae16cbff6091075aee9.png"
alt="深度九大模型标题logo"
class="logo2"
/>
</div>
<div v-for="(msg, index) in chatMsg" :key="index">
<!-- 用户消息容器包含喇叭按钮 -->
<div v-if="msg.sender === 'user'" class="user-message-container">
<div class="user-msg">
<div class="user-content">
<img
:src="msg.audioStatus ? voice : voiceNoActive"
class="user-message-speaker"
:class="{
'speaker-active': msg.audioStatus,
}"
@click="toggleVoiceForUser(index)"
alt="喇叭"
/>
<div
:class="{
'message-bubble': true,
[msg.sender]: msg.sender,
[msg.class]: msg.class,
}"
>
<div v-html="msg.content"></div>
</div>
</div>
<div v-if="msg.timestamp" class="user-sendTime">
{{ moment(msg.timestamp).format("YYYY-MM-DD HH:mm:ss") }}
</div>
</div>
</div>
<!-- AI消息和其他类型消息 -->
<div
v-else
:class="{
'message-bubble': true,
[msg.sender]: msg.sender,
[msg.class]: msg.class,
}"
>
<div v-if="msg.type === 'kline'" class="kline-container">
<div :id="'kline-container-' + index" class="chart-mount-point">
<div v-if="!msg.hasValidData" class="no-data-message">
<p>暂无K线数据</p>
</div>
</div>
</div>
<div v-else-if="msg.type == 'ing'" class="ai-message-container">
<img
v-if="msg.gif"
:src="msg.gif"
alt="思考过程"
class="thinking-gif"
/>
<div class="ai-message-content" :class="{ fourStep: msg.nowrap }">
<div v-if="msg.flag">
<span>{{ msg.content }}</span>
<span class="loading-dots">
<span class="dot">.</span>
<span class="dot">.</span>
<span class="dot">.</span>
<span class="dot">.</span>
<span class="dot">.</span>
<span class="dot">.</span>
</span>
</div>
<div v-else v-html="msg.content"></div>
</div>
</div>
<div
v-else-if="msg.type == 'title1'"
style="display: flex; width: 100%"
>
<div class="mainTitle">
{{ msg.content }}
</div>
<div class="date">
{{ msg.date }}
</div>
</div>
<div v-else-if="msg.type == 'title2'" class="title2">
<img
class="title1Img"
src="https://d31zlh4on95l9h.cloudfront.net/images/c5c2887c56cde033c362cecae4cda4b4.png"
alt="出错了"
/>
</div>
<div v-else-if="msg.type == 'title3'" class="title3">
<img class="title2Img" :src="msg.content" alt="出错了" />
</div>
<div v-else-if="msg.type == 'content1'" class="content1">
<div v-if="msg.kline" class="kline-container content1chart">
<div :id="'kline-container-' + index" class="chart-mount-point">
<div v-if="!msg.hasValidData" class="no-data-message">
<p>暂无数据</p>
</div>
</div>
</div>
<div v-else class="content1Text">
<div v-html="msg.content" class="text1"></div>
</div>
</div>
<div v-else-if="msg.type == 'content2'" class="content2">
<div class="kline-container content2chart">
<div :id="'kline-container-' + index" class="chart-mount-pointJN">
<div v-if="!msg.hasValidData" class="no-data-message">
<p>暂无数据</p>
</div>
</div>
</div>
</div>
<div v-else-if="msg.type == 'img1'" class="img1">
<div class="img1Bk">
<img class="img1Img" :src="msg.content" alt="出错了" />
</div>
</div>
<div v-else-if="msg.type == 'content3'" class="content3">
<div class="content3Text">
<div v-html="msg.content" class="text3"></div>
</div>
</div>
<div v-else-if="msg.type == 'mianze'" class="mianze">
<div v-html="msg.content"></div>
</div>
<div v-else v-html="msg.content"></div>
</div>
</div>
</div>
<!-- 全局返回顶部按钮 -->
<div v-if="chatMsg.length > 0" class="back-to-top" @click="scrollToTop">
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 4L12 20M12 4L6 10M12 4L18 10"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</div>
</template>
<style scoped>
p {
font-size: 20px;
}
.bgc {
position: absolute;
z-index: -1;
max-width: 440px;
min-width: 300px;
top: -15px;
width: 40%;
height: auto;
/* 添加旋转动画 */
animation: rotate 10s linear infinite reverse;
}
/* 定义旋转动画 */
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.logo1 {
max-width: 350px;
min-width: 200px;
width: 15%;
}
.logo2 {
margin-top: 20px;
max-width: 350px;
min-width: 200px;
width: 30%;
/* position: relative; */
}
.chat-container {
display: flex;
flex-direction: column;
overflow: hidden;
}
.gif-area {
padding: 70px 0px;
position: relative;
/* height: 30vh; */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex-shrink: 0;
/* 防止GIF区域被压缩 */
}
.message-area {
margin-top: 2%;
flex: 1;
/* 消息区域占据剩余空间 */
overflow-y: auto;
padding: 20px;
display: flex;
flex-direction: column;
gap: 15px;
}
.marquee-container {
/* position: absolute; */
bottom: 0;
width: 100%;
/* ga */
}
.marquee-row {
white-space: nowrap;
overflow: visible;
padding: 8px 0;
width: 100%;
}
.marquee-item {
display: inline-block;
margin: 0 15px;
padding: 8px 20px;
background: rgba(255, 255, 255, 0.9);
/* 白色背景 */
border-radius: 10px;
/* 圆角矩形 */
color: #333;
/* 文字颜色改为深色 */
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
/* 添加阴影 */
transition: all 0.3s;
transition: color 0.3s;
}
.top {
animation: marquee 25s linear infinite;
/* 默认动画是运行状态 */
animation-play-state: running;
}
.bottom {
animation: marquee 15s linear infinite reverse;
/* 默认动画是运行状态 */
animation-play-state: running;
}
/* 返回顶部按钮样式 */
.back-to-top {
position: sticky !important;
bottom: 20px !important;
left: calc(100% - 70px) !important;
width: 50px !important;
height: 50px !important;
background: linear-gradient(135deg, #00d4ff 0%, #0066cc 100%) !important;
border-radius: 50% !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
cursor: pointer !important;
transition: all 0.3s ease !important;
z-index: 100 !important;
color: white !important;
opacity: 1 !important;
visibility: visible !important;
margin-top: 20px !important;
margin-bottom: 20px !important;
}
.back-to-top:hover {
transform: translateY(-3px);
box-shadow: 0 6px 20px rgba(0, 212, 255, 0.5);
background: linear-gradient(135deg, #00e6ff 0%, #0077dd 100%);
}
.back-to-top:active {
transform: translateY(-1px);
}
/* 添加PC端专用速度 */
@media (min-width: 768px) {
.top {
animation-duration: 35s;
/* PC端改为35秒 */
}
.bottom {
animation-duration: 35s;
/* PC端改为35秒 */
}
}
@keyframes marquee {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(-250%);
}
}
.loading-dots {
display: inline-block;
}
.dot {
opacity: 0.4;
animation: loading 1.4s infinite;
}
.dot:nth-child(1) {
animation-delay: 0s;
}
.dot:nth-child(2) {
animation-delay: 0.2s;
}
.dot:nth-child(3) {
animation-delay: 0.4s;
}
.dot:nth-child(4) {
animation-delay: 0.6s;
}
.dot:nth-child(5) {
animation-delay: 0.8s;
}
.dot:nth-child(6) {
animation-delay: 1s;
}
@keyframes loading {
0%,
60%,
100% {
opacity: 0.4;
}
30% {
opacity: 1;
}
}
.message-bubble {
max-width: 80%;
margin: 10px 0px;
padding: 15px 20px;
position: relative;
}
/* 用户消息容器样式 */
.user-message-container {
display: flex;
align-items: flex-end;
margin: 10px 0px;
justify-content: flex-end;
gap: 10px;
/* align-items: center; */
flex-direction: column;
}
.user-msg {
margin-left: auto;
display: flex;
flex-direction: column;
}
.user-content {
display: flex;
height: 100%;
align-items: center;
margin-right: 5px;
justify-content: flex-end;
padding: 0 20px;
}
.user-sendTime {
width: 100%;
text-align: center;
color: rgba(255, 255, 255, 0.6);
font-size: 0.8rem;
}
.user-message-speaker {
width: 32px;
height: 32px;
object-fit: contain;
margin-right: 5px;
cursor: pointer;
transition: all 0.3s ease;
}
.user-message-speaker:hover {
transform: scale(1.1);
}
.user-message-speaker.speaker-active {
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
.message-bubble.user {
color: #6d22f8;
background: white;
font-weight: bold;
border-radius: 10px;
margin: 0;
display: flex;
align-items: center;
word-break: break-word; /* 启用强制换行 */
}
.message-bubble.ai {
background: #2b378d;
color: #ffffff;
margin: 0 auto;
/* border-bottom-left-radius: 5px; */
}
.message-bubble.ing {
background: #ffffff;
color: #000000;
font-weight: bold;
border-radius: 10px;
margin-left: 20px;
margin-right: auto;
display: flex;
align-items: center;
width: fit-content;
}
.message-bubble.ai.title1 {
width: 100%;
display: flex;
border-radius: 10px 10px 0px 0px;
/* border-bottom-left-radius: 5px; */
}
.mainTitle {
font-size: 16px;
font-weight: bold;
background-image: url("@/assets/img/AiEmotion/bk01.png");
background-repeat: no-repeat;
background-size: 100% 100%;
min-width: 200px;
width: 20vw;
max-width: 50%;
height: 50px;
padding: 15px 10px;
text-align: center;
line-height: 20px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
box-sizing: border-box;
}
.date {
font-size: 1.5rem;
font-weight: bold;
margin-left: auto;
/* width: 100px; */
display: flex;
justify-content: center;
align-items: center;
}
.message-bubble.ai.title2 {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.title1Img {
max-width: 500px;
width: 90vw;
}
.message-bubble.ai.title3 {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.title2Img {
max-width: 500px;
width: 90vw;
}
.message-bubble.ai.content1 {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.content1chart {
background-image: url("@/assets/img/AIchat/罗盘边框.png");
background-repeat: no-repeat;
background-size: 100% 100%;
width: 50vw;
min-width: 350px;
display: flex;
justify-content: center;
align-items: center;
}
.content1Text {
background-image: url("@/assets/img/AIchat/框.png");
background-repeat: no-repeat;
background-size: 100% 100%;
width: 50vw;
min-width: 350px;
/* height: 20vw; */
/* max-height: 400px; */
padding: 5% 0;
}
.text1 {
font-weight: bold;
/* margin-left: 6%; */
/* margin-bottom: 10px; */
margin: 0px 6% 10px 6%;
font-size: 20px;
}
.message-bubble.ai.content2 {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.content2chart {
background-image: url("@/assets/img/AIchat/PCbackPic.png");
background-repeat: no-repeat;
background-size: 100% 100%;
width: 50vw;
min-width: 350px;
display: flex;
justify-content: center;
align-items: center;
height: calc(500px + 10vw) !important;
}
.message-bubble.ai.img1 {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.img1Bk {
background-image: url("@/assets/img/AIchat/边框.png");
background-repeat: no-repeat;
background-size: 100% 100%;
width: 50vw;
min-width: 350px;
/* height: 20vw; */
/* max-height: 400px; */
padding: 5% 0px;
display: flex;
justify-content: center;
align-items: center;
}
.img1Img {
width: 90%;
}
.message-bubble.ai.content3 {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.content3Text {
background-image: url("@/assets/img/AIchat/边框.png");
background-repeat: no-repeat;
background-size: 100% 100%;
width: 50vw;
min-width: 350px;
/* height: 20vw; */
/* max-height: 400px; */
padding: 5% 0px;
}
.text3 {
/* font-weight: bold; */
/* margin-left: 6%; */
/* margin-bottom: 10px; */
margin: 0px 6% 10px 6%;
font-size: 20px;
}
.message-bubble.ai.mianze {
width: 100%;
text-align: center;
font-weight: bold;
font-size: 24px;
border-radius: 0px 0px 10px 10px;
}
.kline-container {
margin-top: 10px;
/* 最小高度 */
min-height: 320px;
/* 视口高度单位 */
height: 40vh;
width: 50vw;
}
@media (max-width: 768px) {
.gif-area {
padding: 0px 0px;
position: relative;
/* height: 30vh; */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex-shrink: 0;
}
.logo1 {
max-width: 350px;
min-width: 200px;
width: 15%;
scale: 0.8;
}
.logo2 {
margin-top: 20px;
max-width: 350px;
min-width: 200px;
width: 80%;
/* position: relative; */
}
.kline-container {
min-width: 75vw;
}
.content1Text {
width: 77vw;
min-width: 0px;
/* height: 20vw; */
/* min-height: 150px; */
}
.date {
font-size: 14px;
text-align: end;
}
.text1 {
font-size: 20px;
}
.content2chart {
background-image: url("@/assets/img/AIchat/new-app-bgc.png") !important;
height: 100vw;
}
.img1Bk {
width: 77vw;
min-width: 0px;
/* height: 20vw; */
/* min-height: 150px; */
}
.content3Text {
width: 77vw;
min-width: 0px;
/* height: 20vw; */
/* min-height: 150px; */
}
.text3 {
font-size: 20px;
}
.message-bubble.ai.mianze {
font-size: 18px;
}
.back-to-top {
left: calc(100% - 65px) !important;
width: 45px !important;
height: 45px !important;
}
}
.kline-container .chart-mount-point {
display: flex;
justify-content: center;
align-items: center;
height: 80%;
width: 90%;
}
.kline-container .chart-mount-pointJN {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
}
/* AI消息容器样式 */
.ai-message-container {
display: flex;
align-items: center;
gap: 10px;
margin-right: auto;
/* max-width: 80%; */
}
/* 思考过程动图样式 */
.thinking-gif {
width: 40px;
height: 40px;
object-fit: contain;
margin-top: 5px;
border-radius: 8px;
animation: float 2s ease-in-out infinite;
}
@keyframes float {
0%,
100% {
transform: translateY(0px);
}
50% {
transform: translateY(-5px);
}
}
/* AI消息内容样式 */
.ai-message-content {
display: flex;
align-items: center;
/* white-space: nowrap; */
width: fit-content;
overflow: visible;
}
.fourStep {
white-space: nowrap;
}
@media only screen and (max-width: 480px) {
.back-to-top {
left: calc(100% - 60px) !important;
width: 40px !important;
height: 40px !important;
}
.back-to-top svg {
width: 20px;
height: 20px;
}
}
</style>