Browse Source

对接历史记录详情

lihuilin/feature-20251024095243-我的
Ethereal 4 weeks ago
parent
commit
e9217e2dac
  1. 363
      pages/deepMate/deepMate.vue

363
pages/deepMate/deepMate.vue

@ -3,18 +3,29 @@
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<view class="header" :style="{ paddingTop: safeAreaInsets?.top + 'px' }"> <view class="header" :style="{ paddingTop: safeAreaInsets?.top + 'px' }">
<view class="header-left"> <view class="header-left">
<image src="https://d31zlh4on95l9h.cloudfront.net/images/f91e09b5987802185e7679055dafd272.svg" class="icon">
<image
src="https://d31zlh4on95l9h.cloudfront.net/images/f91e09b5987802185e7679055dafd272.svg"
class="icon"
>
</image> </image>
</view> </view>
<view class="header-center"> <view class="header-center">
<text class="title" :style="{ paddingTop: safeAreaInsets?.top + 'px' }">DeepMate</text>
<text class="title" :style="{ paddingTop: safeAreaInsets?.top + 'px' }"
>DeepMate</text
>
</view> </view>
<view class="header-right"> <view class="header-right">
<image src="https://d31zlh4on95l9h.cloudfront.net/images/d7c4e74201213a25dd9574e908233928.svg" class="icon">
<image
src="https://d31zlh4on95l9h.cloudfront.net/images/d7c4e74201213a25dd9574e908233928.svg"
class="icon"
>
</image> </image>
<image style="margin-left: 10px"
src="https://d31zlh4on95l9h.cloudfront.net/images/099903c4aabf5713488b5cb60815e3f7.svg" class="icon"
@click="openHistoryDrawer"></image>
<image
style="margin-left: 10px"
src="https://d31zlh4on95l9h.cloudfront.net/images/099903c4aabf5713488b5cb60815e3f7.svg"
class="icon"
@click="openHistoryDrawer"
></image>
<!-- 新增新会话按钮 <!-- 新增新会话按钮
<button class="new-chat-button" @click="newChat"> <button class="new-chat-button" @click="newChat">
<text class="new-chat-text">新会话</text> <text class="new-chat-text">新会话</text>
@ -25,15 +36,19 @@
<!-- 主要内容区域 --> <!-- 主要内容区域 -->
<view class="main-content"> <view class="main-content">
<view class="banner-panel" v-if="messages.length === 0"> <view class="banner-panel" v-if="messages.length === 0">
<image src="https://d31zlh4on95l9h.cloudfront.net/images/42e18bd7fe97d4f4f37aa70439a0990b.svg"
class="pray-banner"></image>
<image
src="https://d31zlh4on95l9h.cloudfront.net/images/42e18bd7fe97d4f4f37aa70439a0990b.svg"
class="pray-banner"
></image>
<view class="contain"> <view class="contain">
<!-- 机器人头像和欢迎语 --> <!-- 机器人头像和欢迎语 -->
<view class="robot-container" v-if="messages.length === 0"> <view class="robot-container" v-if="messages.length === 0">
<image src="/static/icons/机器人.svg" class="robot-avatar"></image> <image src="/static/icons/机器人.svg" class="robot-avatar"></image>
<view class="welcome-message"> <view class="welcome-message">
<text class="greeting">Hi, 我是您的股市随身顾问~</text> <text class="greeting">Hi, 我是您的股市随身顾问~</text>
<text class="description">个股诊断市场情绪解读都可以找我</text>
<text class="description"
>个股诊断市场情绪解读都可以找我</text
>
</view> </view>
</view> </view>
@ -45,19 +60,28 @@
</view> --> </view> -->
<!-- 特斯拉推荐卡片 --> <!-- 特斯拉推荐卡片 -->
<view class="recommend-card" v-if="messages.length === 0" @click="goBlank">
<view
class="recommend-card"
v-if="messages.length === 0"
@click="goBlank"
>
<view class="arrow" v-if="messages.length === 0"></view> <view class="arrow" v-if="messages.length === 0"></view>
<view class="card-content"> <view class="card-content">
<image src="../../static/images/tesla-logo.png" class="logo"></image>
<image
src="../../static/images/tesla-logo.png"
class="logo"
></image>
<view class="card-text"> <view class="card-text">
<text class="main-question">当前特斯拉该如何布局</text> <text class="main-question">当前特斯拉该如何布局</text>
<text class="stock-code">TSLA</text> <text class="stock-code">TSLA</text>
</view> </view>
<image src="https://d31zlh4on95l9h.cloudfront.net/images/40d94054644f6e3f1c366751f07f0010.svg"
class="arrow-icon" @click="goBlank"></image>
<image
src="https://d31zlh4on95l9h.cloudfront.net/images/40d94054644f6e3f1c366751f07f0010.svg"
class="arrow-icon"
@click="goBlank"
></image>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
@ -73,33 +97,50 @@
</view> </view>
</view> </view>
</view> --> </view> -->
<view v-if="messages.length === 0" class="welcome-section">
</view>
<view v-if="messages.length === 0" class="welcome-section"> </view>
<!-- 聊天区域 --> <!-- 聊天区域 -->
<!-- 顶部粘性欢迎块始终保留在聊天上方 --> <!-- 顶部粘性欢迎块始终保留在聊天上方 -->
<view class="chat-header" v-if="messages.length > 0"> <view class="chat-header" v-if="messages.length > 0">
<view class="robot-container"> <view class="robot-container">
<image src="/static/icons/机器人.svg" class="robot-avatar"></image> <image src="/static/icons/机器人.svg" class="robot-avatar"></image>
<view class="welcome-message"> <text class="greeting">Hi, 我是您的股市随身顾问~</text>
<view class="welcome-message">
<text class="greeting">Hi, 我是您的股市随身顾问~</text>
</view> </view>
</view> </view>
</view> </view>
<scroll-view class="chat-container" scroll-y="true" :scroll-top="chatScrollTop"
:scroll-with-animation="!isSending" @scroll="onChatScroll" v-if="messages.length > 0">
<scroll-view
class="chat-container"
scroll-y="true"
:scroll-top="chatScrollTop"
:scroll-with-animation="!isSending"
@scroll="onChatScroll"
v-if="messages.length > 0"
>
<view class="message-list" id="messageList"> <view class="message-list" id="messageList">
<view v-for="(message, index) in messages" :key="index" :class="message.isUser ? 'message user-message' : 'message bot-message'
">
<view
v-for="(message, index) in messages"
:key="index"
:class="
message.isUser ? 'message user-message' : 'message bot-message'
"
>
<!-- 会话图标 --> <!-- 会话图标 -->
<text :class="message.isUser
<text
:class="
message.isUser
? 'fa-solid fa-user message-icon' ? 'fa-solid fa-user message-icon'
: 'fa-solid fa-robot message-icon' : 'fa-solid fa-robot message-icon'
"></text>
"
></text>
<!-- 会话内容 --> <!-- 会话内容 -->
<view class="message-content"> <view class="message-content">
<!-- <text class="message-text">{{ message.content }}</text> --> <!-- <text class="message-text">{{ message.content }}</text> -->
<!-- loading --> <!-- loading -->
<view class="loading-dots" v-if="message.isThinking || !message.isUser">
<view
class="loading-dots"
v-if="message.isThinking || !message.isUser"
>
<view class="thinking-process"> <view class="thinking-process">
<view class="thinking-header"> <view class="thinking-header">
<view class="thinking-icon"></view> <view class="thinking-icon"></view>
@ -107,7 +148,10 @@
message.isTyping ? "正在思考" : "思考完成" message.isTyping ? "正在思考" : "思考完成"
}}</view> }}</view>
<view class="thinking-count"> </view> <view class="thinking-count"> </view>
<view class="thinking-toggle" @click="message.isThinking = !message.isThinking">
<view
class="thinking-toggle"
@click="message.isThinking = !message.isThinking"
>
<span v-if="message.isThinking"></span> <span v-if="message.isThinking"></span>
<span v-else></span> <span v-else></span>
</view> </view>
@ -129,8 +173,11 @@
</view> </view>
</view> </view>
<!-- 使用 rich-text 渲染 Markdown 内容 --> <!-- 使用 rich-text 渲染 Markdown 内容 -->
<rich-text v-if="!message.isUser" class="message-text"
:nodes="renderMarkdown(message.content)"></rich-text>
<rich-text
v-if="!message.isUser"
class="message-text"
:nodes="renderMarkdown(message.content)"
></rich-text>
<text v-else class="message-text">{{ message.content }}</text> <text v-else class="message-text">{{ message.content }}</text>
</view> </view>
</view> </view>
@ -141,8 +188,14 @@
<!-- 输入框区域 --> <!-- 输入框区域 -->
<view class="input-area"> <view class="input-area">
<view class="input-wrapper"> <view class="input-wrapper">
<input type="text" placeholder="请输入股票代码/名称,获取AI洞察" placeholder-style="color:#fff;opacity:1" class="input-field"
v-model="inputMessage" @confirm="sendMessage" />
<input
type="text"
placeholder="请输入股票代码/名称,获取AI洞察"
placeholder-style="color:#fff;opacity:1"
class="input-field"
v-model="inputMessage"
@confirm="sendMessage"
/>
<image class="send-button" @click="sendMessage" :disabled="isSending"> <image class="send-button" @click="sendMessage" :disabled="isSending">
<!-- <image <!-- <image
src="https://d31zlh4on95l9h.cloudfront.net/images/95f1ea2262e9157db13c93c0dc1c5d96.svg" src="https://d31zlh4on95l9h.cloudfront.net/images/95f1ea2262e9157db13c93c0dc1c5d96.svg"
@ -150,18 +203,26 @@
></image> --> ></image> -->
</image> </image>
</view> </view>
<text class="disclaimer">以上数据由AI生成不作为最终投资建议决策需独立</text>
<text class="disclaimer"
>以上数据由AI生成不作为最终投资建议决策需独立</text
>
</view> </view>
<image class="back-to-top" src="https://d31zlh4on95l9h.cloudfront.net/images/ba357635d2bb480241952bb1cabacd73.svg"
<image
class="back-to-top"
src="https://d31zlh4on95l9h.cloudfront.net/images/ba357635d2bb480241952bb1cabacd73.svg"
:style="{ :style="{
transform: 'translate3d(' + backTopX + 'px,' + backTopY + 'px,0)', transform: 'translate3d(' + backTopX + 'px,' + backTopY + 'px,0)',
}" @touchstart="onBackTopTouchStart" @touchmove="onBackTopTouchMove" @touchend="onBackTopTouchEnd"
@click="onBackTopClick"></image>
}"
@touchstart="onBackTopTouchStart"
@touchmove="onBackTopTouchMove"
@touchend="onBackTopTouchEnd"
@click="onBackTopClick"
></image>
<!-- 搜索历史侧拉框 --> <!-- 搜索历史侧拉框 -->
<view class="drawer-overlay" v-show="showHistoryDrawer"></view> <view class="drawer-overlay" v-show="showHistoryDrawer"></view>
<view class="drawer-panel" v-show="showHistoryDrawer" @click.stop @touchmove.stop.prevent
<!-- <view class="drawer-panel" v-show="showHistoryDrawer" @click.stop @touchmove.stop.prevent
:style="{ transform: 'translateY(' + drawerOffsetY + 'px)' }"> :style="{ transform: 'translateY(' + drawerOffsetY + 'px)' }">
<view class="drawer-header"> <view class="drawer-header">
@ -193,6 +254,59 @@
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
</view> -->
<view
class="drawer-panel"
v-show="showHistoryDrawer"
@click.stop
@touchmove.stop.prevent
:style="{ transform: 'translateY(' + drawerOffsetY + 'px)' }"
>
<view class="drawer-header">
<text class="drawer-title">历史对话</text>
<view class="drawer-actions">
<view class="delete-all-container">
<image
class="delete-icon"
src="/static/icons/Group_48095481.svg"
></image>
<text class="delete-all" @click="clearAllHistory">删除全部</text>
</view>
<view class="drawer-close" @click="onDrawerBackClick"
><text class="drawer-close-icon"></text
></view>
</view>
</view>
<scroll-view scroll-y="true" class="drawer-content">
<view class="drawer-inner">
<view v-if="historyList.length === 0" class="empty-history">
<text>暂无历史记录</text>
</view>
<view
v-for="(section, sIdx) in historyList"
:key="sIdx"
class="history-section"
>
<text class="section-title">{{ section.title }}</text>
<view
v-for="(item, idx) in section.items"
:key="idx"
class="history-item"
>
<view class="history-left">
<view class="flag-circle"
><text class="flag-emoji">🇺🇸</text></view
>
</view>
<view class="history-main" @click="itemClick(item)">
<text class="history-query">{{ item.stockName }}</text>
<text class="history-query">{{ item.stockCode }}</text>
</view>
<text class="history-time">{{ item.createdTime }}</text>
</view>
</view>
</view>
</scroll-view>
</view> </view>
<footerBar class="static-footer" :type="type"></footerBar> <footerBar class="static-footer" :type="type"></footerBar>
@ -206,7 +320,12 @@ import { ref, computed, onMounted, onUnmounted, watch, nextTick } from "vue";
import footerBar from "../../components/footerBar"; import footerBar from "../../components/footerBar";
import marked from "marked"; // marked import marked from "marked"; // marked
import { onPageScroll } from "@dcloudio/uni-app"; import { onPageScroll } from "@dcloudio/uni-app";
import { postStock, postIntent } from "../../api/deepMate/deepMate";
import {
postStock,
postIntent,
postHistory,
postHistoryDetail,
} from "../../api/deepMate/deepMate";
// marked // marked
marked.setOptions({ marked.setOptions({
renderer: new marked.Renderer(), renderer: new marked.Renderer(),
@ -237,6 +356,7 @@ const messages = ref([]);
const showHistoryDrawer = ref(false); const showHistoryDrawer = ref(false);
const drawerOffsetY = ref(0); const drawerOffsetY = ref(0);
const searchHistory = ref([]); const searchHistory = ref([]);
const historyList = ref([]);
const hotTopics = ref([ const hotTopics = ref([
{ {
id: 1, id: 1,
@ -289,7 +409,7 @@ onMounted(() => {
// YYYY-MM-DD // YYYY-MM-DD
const todayStr = new Date().toISOString().slice(0, 10); const todayStr = new Date().toISOString().slice(0, 10);
uni.setStorageSync('today_date', todayStr);
uni.setStorageSync("today_date", todayStr);
}); });
// UUID // UUID
@ -336,12 +456,26 @@ const goBlank = () => {
// //
const openHistoryDrawer = () => { const openHistoryDrawer = () => {
const res = postHistory({
model: 5,
});
if (res.code === 200) {
historyList.value = res.data;
}
console.log("historyList.value", historyList.value);
const hideDistance = uni.upx2px(900); const hideDistance = uni.upx2px(900);
drawerOffsetY.value = hideDistance; drawerOffsetY.value = hideDistance;
showHistoryDrawer.value = true; showHistoryDrawer.value = true;
setTimeout(() => { drawerOffsetY.value = 0; }, 10);
setTimeout(() => {
drawerOffsetY.value = 0;
}, 10);
};
const closeHistoryDrawer = () => {
showHistoryDrawer.value = false;
}; };
const closeHistoryDrawer = () => { showHistoryDrawer.value = false; };
const onDrawerBackClick = () => { const onDrawerBackClick = () => {
const hideDistance = uni.upx2px(900); const hideDistance = uni.upx2px(900);
drawerOffsetY.value = hideDistance; drawerOffsetY.value = hideDistance;
@ -363,65 +497,65 @@ const formatTime = (t) => {
return `${y}-${m}-${day} ${hh}:${mm}`; return `${y}-${m}-${day} ${hh}:${mm}`;
}; };
// ///
const groupedHistory = computed(() => {
const sections = [];
// 使
const cachedTodayStr = uni.getStorageSync('today_date');
const now = cachedTodayStr ? new Date(cachedTodayStr + 'T00:00:00') : new Date();
const startOfDay = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
const isSameDay = (a, b) => startOfDay(a).getTime() === startOfDay(b).getTime();
const isYesterday = (d) => {
const y = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
return isSameDay(d, y);
};
const isToday = (d) => isSameDay(d, now);
const withinLast7Days = (d) => {
const seven = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7);
return d >= seven && !isToday(d) && !isYesterday(d);
};
const monthLabel = (d) => `${d.getMonth() + 1}`;
const today = [];
const yesterday = [];
const last7 = [];
const byMonth = new Map();
searchHistory.value.forEach((item) => {
const dt = new Date(item.time);
if (isToday(dt)) {
today.push(item);
} else if (isYesterday(dt)) {
yesterday.push(item);
} else if (withinLast7Days(dt)) {
last7.push(item);
} else {
const year = dt.getFullYear();
const month = dt.getMonth() + 1;
const key = `${year}-${month}`;
if (!byMonth.has(key)) byMonth.set(key, { title: `${month}`, year, month, items: [] });
byMonth.get(key).items.push(item);
}
});
if (today.length) sections.push({ title: '今天', items: today });
if (yesterday.length) sections.push({ title: '昨天', items: yesterday });
if (last7.length) sections.push({ title: '近一周', items: last7 });
const monthSections = Array.from(byMonth.values()).sort((a, b) => {
if (a.year !== b.year) return b.year - a.year;
return b.month - a.month; // 10 9
});
sections.push(...monthSections);
return sections;
});
// // ///
// const groupedHistory = computed(() => {
// const sections = [];
// // 使
// const cachedTodayStr = uni.getStorageSync('today_date');
// const now = cachedTodayStr ? new Date(cachedTodayStr + 'T00:00:00') : new Date();
// const startOfDay = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate());
// const isSameDay = (a, b) => startOfDay(a).getTime() === startOfDay(b).getTime();
// const isYesterday = (d) => {
// const y = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
// return isSameDay(d, y);
// };
// const isToday = (d) => isSameDay(d, now);
// const withinLast7Days = (d) => {
// const seven = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7);
// return d >= seven && !isToday(d) && !isYesterday(d);
// };
// const monthLabel = (d) => `${d.getMonth() + 1}`;
// const today = [];
// const yesterday = [];
// const last7 = [];
// const byMonth = new Map();
// searchHistory.value.forEach((item) => {
// const dt = new Date(item.time);
// if (isToday(dt)) {
// today.push(item);
// } else if (isYesterday(dt)) {
// yesterday.push(item);
// } else if (withinLast7Days(dt)) {
// last7.push(item);
// } else {
// const year = dt.getFullYear();
// const month = dt.getMonth() + 1;
// const key = `${year}-${month}`;
// if (!byMonth.has(key)) byMonth.set(key, { title: `${month}`, year, month, items: [] });
// byMonth.get(key).items.push(item);
// }
// });
// if (today.length) sections.push({ title: '', items: today });
// if (yesterday.length) sections.push({ title: '', items: yesterday });
// if (last7.length) sections.push({ title: '', items: last7 });
// const monthSections = Array.from(byMonth.values()).sort((a, b) => {
// if (a.year !== b.year) return b.year - a.year;
// return b.month - a.month; // 10 9
// });
// sections.push(...monthSections);
// return sections;
// });
const clearAllHistory = () => { const clearAllHistory = () => {
searchHistory.value = []; searchHistory.value = [];
uni.setStorageSync('search_history', []);
uni.setStorageSync("search_history", []);
}; };
// //
@ -465,15 +599,15 @@ const simulateBotResponse = async (userMessage) => {
messages.value.push(botMsg); messages.value.push(botMsg);
await new Promise((resolve) => setTimeout(resolve, 2000));
isSending.value = true; isSending.value = true;
// //
const res = await postIntent({ const res = await postIntent({
content: userMessage, content: userMessage,
language: 'cn',
marketList: 'hk,cn,usa,my,sg,vi,in,gb',
token: "pCtw6AYK0EHAaIexoFHsbZjtsfEAIhcmwkCFm6uKko8VPfMvyDiODL9v9c0veic9fIpQbvT8zN4sH/Si6Q"
language: "cn",
marketList: "hk,cn,usa,my,sg,vi,in,gb",
token:
"pCtw6AYK0EHAaIexoFHsbZjtsfEAIhcmwkCFm6uKko8VPfMvyDiODL9v9c0veic9fIpQbvT8zN4sH/Si6Q",
}); });
console.log("res" + res); console.log("res" + res);
@ -488,13 +622,12 @@ const simulateBotResponse = async (userMessage) => {
const parentId = res.data.parentId; const parentId = res.data.parentId;
const stockId = res.data.stockId; const stockId = res.data.stockId;
await new Promise((resolve) => setTimeout(resolve, 2000));
// //
const StockInfo = await postStock({ const StockInfo = await postStock({
recordId, recordId,
parentId, parentId,
stockId, stockId,
token: memberStore.userInfo?.token || '',
token: memberStore.userInfo?.token || "",
language: "cn", language: "cn",
}); });
console.log("StockInfo", StockInfo); console.log("StockInfo", StockInfo);
@ -691,6 +824,27 @@ const onBackTopClick = () => {
if (backTopDragging.value) return; // if (backTopDragging.value) return; //
scrollToTop(); scrollToTop();
}; };
//
async function itemClick(item) {
const res = await postHistoryDetail({
recordId: item.id,
parentId: item.parentId,
model: 5,
});
if (res.code == 200) {
const message = res.data.wokeFlowData.One.markdown;
messages.value = [];
const botMsg = {
content: message,
isUser: false,
isTyping: false,
isThinking: false,
};
messages.value.push(botMsg);
}
}
</script> </script>
<style scoped> <style scoped>
@ -859,7 +1013,7 @@ const onBackTopClick = () => {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: url('https://d31zlh4on95l9h.cloudfront.net/images/eca84d9fb54712cb3bc6c6174773b83b.svg');
background: url("https://d31zlh4on95l9h.cloudfront.net/images/eca84d9fb54712cb3bc6c6174773b83b.svg");
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center top; background-position: center top;
/* 放在容器顶部,正好在灰色卡片下方 */ /* 放在容器顶部,正好在灰色卡片下方 */
@ -1007,7 +1161,7 @@ const onBackTopClick = () => {
} }
.bot-message .message-icon { .bot-message .message-icon {
background: url('/static/images/机器人 (1).svg');
background: url("/static/images/机器人 (1).svg");
color: white; color: white;
} }
@ -1074,7 +1228,6 @@ const onBackTopClick = () => {
} }
@keyframes loading { @keyframes loading {
0%, 0%,
80%, 80%,
100% { 100% {
@ -1278,8 +1431,6 @@ const onBackTopClick = () => {
gap: 14rpx; gap: 14rpx;
} }
.delete-icon { .delete-icon {
width: 45rpx; width: 45rpx;
height: 40rpx; height: 40rpx;
@ -1290,7 +1441,7 @@ const onBackTopClick = () => {
} }
.drawer-close { .drawer-close {
background: url('/static/icons/关闭2.svg');
background: url("/static/icons/关闭2.svg");
width: 48rpx; width: 48rpx;
height: 48rpx; height: 48rpx;
border-radius: 24rpx; border-radius: 24rpx;

Loading…
Cancel
Save