7 changed files with 865 additions and 284 deletions
-
367src/api/deepNine.js
-
148src/components/deepNine/MessageItem.vue
-
51src/components/deepNine/ThinkingGif.vue
-
54src/store/deepNine.js
-
89src/store/deepNineAudio.js
-
12src/views/deepNine.vue
-
428src/views/homePage.vue
@ -0,0 +1,367 @@ |
|||
import request from "../utils/request"; |
|||
|
|||
const APIurl = import.meta.env.VITE_APP_API_BASE_URL; |
|||
const cozeAPIurl = import.meta.env.VITE_APP_API_BASE_CAZE_URL; |
|||
const MJAPIurl = import.meta.env.VITE_APP_MJ_API_BASE_URL; |
|||
const HWurl = import.meta.env.VITE_APP_API_BASE_HW_URL; |
|||
//各个模块权限code接口
|
|||
export const pessionAPI = function (params) { |
|||
return request({ |
|||
url: `${APIurl}/api/brain/privilege`, |
|||
method: "post", |
|||
data: new URLSearchParams(params), |
|||
headers: { |
|||
"Content-Type": "application/x-www-form-urlencoded", |
|||
}, |
|||
}); |
|||
}; |
|||
|
|||
//数据接口
|
|||
export const dataListAPI = function (params) { |
|||
// URLSearchParams只接受全部字符串的数据
|
|||
// 将传入数据转化成字符串
|
|||
const StringParams = new URLSearchParams( |
|||
Object.entries(params).map(([key, value]) => [key, String(value)]) |
|||
); |
|||
return request({ |
|||
url: `${APIurl}/api/brain/data`, |
|||
method: "post", |
|||
data: StringParams, |
|||
headers: { |
|||
"Content-Type": "application/x-www-form-urlencoded", |
|||
}, |
|||
}); |
|||
}; |
|||
|
|||
//统计用户行为接口
|
|||
export const computedUsersAPI = function (params) { |
|||
return request({ |
|||
url: `${APIurl}/BrainStatistics/getStatistic`, |
|||
method: "post", |
|||
data: new URLSearchParams(params), |
|||
headers: { |
|||
"Content-Type": "application/x-www-form-urlencoded", |
|||
}, |
|||
}); |
|||
}; |
|||
// 首次进入小财神
|
|||
export const useAiGodAPI = function (params) { |
|||
return request({ |
|||
url: `${APIurl}/api/ai_god/useAiGod`, |
|||
method: "post", |
|||
data: new URLSearchParams(params), |
|||
headers: { |
|||
"Content-Type": "application/x-www-form-urlencoded", |
|||
}, |
|||
}); |
|||
}; |
|||
// 停留时间
|
|||
export const updateStayTimeAPI = function (params) { |
|||
return request({ |
|||
url: `${APIurl}/api/ai_god/updateStayTime`, |
|||
method: "post", |
|||
data: new URLSearchParams(params), |
|||
headers: { |
|||
"Content-Type": "application/x-www-form-urlencoded", |
|||
}, |
|||
}); |
|||
}; |
|||
|
|||
// 获取新闻接口
|
|||
export const getNewsAPI = function () { |
|||
return request({ |
|||
url: `${APIurl}/api/ai_god/news`, |
|||
method: "POST", |
|||
}); |
|||
}; |
|||
// 获取引导搜索词接口
|
|||
export const getQuestionAPI = function () { |
|||
return request({ |
|||
url: `${APIurl}/api/ai_god/shows`, |
|||
method: "POST", |
|||
data: new URLSearchParams({ |
|||
type: "1", |
|||
num: "10", |
|||
state: "1", |
|||
}), |
|||
}); |
|||
}; |
|||
// 获取公告接口
|
|||
export const getAnnouncementAPI = function () { |
|||
return request({ |
|||
url: `${APIurl}/api/ai_god/shows`, |
|||
method: "POST", |
|||
data: new URLSearchParams({ |
|||
type: "3", |
|||
num: "1", |
|||
state: "1", |
|||
}), |
|||
}); |
|||
}; |
|||
// 获取用户次数接口
|
|||
export const getUserCountAPI = function (params) { |
|||
return request({ |
|||
// 'http://39.101.133.168:8828/link/api/aiEmotion/client/getRemainNum',
|
|||
url: `${APIurl}/api/aiEmotion/client/getRemainNum`, |
|||
method: "POST", |
|||
data: params, |
|||
// headers: {
|
|||
// "Content-Type": "application/x-www-form-urlencoded",
|
|||
// },
|
|||
}); |
|||
}; |
|||
// 推荐问题/每日复盘/小财神简介点击事件接口
|
|||
export const qsArpAamClickAPI = function (params) { |
|||
return request({ |
|||
url: `${APIurl}/api/ai_god/shows/click`, |
|||
method: "POST", |
|||
data: new URLSearchParams(params), |
|||
headers: { |
|||
"Content-Type": "application/x-www-form-urlencoded", |
|||
}, |
|||
}); |
|||
}; |
|||
// 财经新闻点击事件接口
|
|||
export const newsClickAPI = function (params) { |
|||
return request({ |
|||
url: `${APIurl}/api/ai_god/news/click`, |
|||
method: "POST", |
|||
data: new URLSearchParams(params), |
|||
headers: { |
|||
"Content-Type": "application/x-www-form-urlencoded", |
|||
}, |
|||
}); |
|||
}; |
|||
|
|||
// 获取回复接口
|
|||
export const getReplyAPI = function (params) { |
|||
return fetch("https://api.coze.cn/v1/workflow/run", { |
|||
method: "POST", |
|||
body: JSON.stringify({ |
|||
workflow_id: "7491496473373540363", |
|||
parameters: params, |
|||
}), |
|||
headers: { |
|||
"Content-Type": "application/json", |
|||
Authorization: |
|||
"Bearer pat_lK1fvhLn9LnWCRETP7yFeR6xQ0niwQdcHJ5ZqpnUk8BdiUWCraPLSzWhiQNp2zOl", |
|||
}, |
|||
}); |
|||
}; |
|||
// 获取回复接口流式
|
|||
export const getReplyStreamAPI = function (params) { |
|||
return fetch(`https://api.coze.cn/v1/workflow/stream_run`, { |
|||
method: "POST", |
|||
body: JSON.stringify({ |
|||
workflow_id: "7481159261435854860", |
|||
parameters: params, |
|||
}), |
|||
headers: { |
|||
"Content-Type": "application/json", |
|||
Authorization: |
|||
"Bearer pat_lK1fvhLn9LnWCRETP7yFeR6xQ0niwQdcHJ5ZqpnUk8BdiUWCraPLSzWhiQNp2zOl", |
|||
}, |
|||
}); |
|||
}; |
|||
|
|||
// 接受音频
|
|||
export const TTSAPI = function (params) { |
|||
return fetch("https://api.coze.cn/v1/workflow/run", { |
|||
method: "POST", |
|||
body: JSON.stringify({ |
|||
workflow_id: "7481639836165275702", |
|||
parameters: params, |
|||
}), |
|||
headers: { |
|||
Authorization: |
|||
"Bearer pat_lK1fvhLn9LnWCRETP7yFeR6xQ0niwQdcHJ5ZqpnUk8BdiUWCraPLSzWhiQNp2zOl", |
|||
"Content-Type": "application/json", |
|||
}, |
|||
}); |
|||
}; |
|||
|
|||
// 反馈前台-用户提交反馈接口
|
|||
export const addFeedbackAPI = function (params) { |
|||
return request({ |
|||
url: `${APIurl}/api/ai_god/feedback/add`, |
|||
method: "POST", |
|||
data: new URLSearchParams(params), |
|||
headers: { |
|||
"Content-Type": "application/x-www-form-urlencoded", |
|||
}, |
|||
}); |
|||
}; |
|||
// 反馈前台-查询该用户提交的全部反馈内容
|
|||
export const getFeedbackAPI = function (params) { |
|||
return request({ |
|||
url: `${APIurl}/api/ai_god/feedback/select`, |
|||
method: "POST", |
|||
data: new URLSearchParams(params), |
|||
headers: { |
|||
"Content-Type": "application/x-www-form-urlencoded", |
|||
}, |
|||
}); |
|||
}; |
|||
|
|||
// 公告-查询市场和股票
|
|||
export const getMarketAndCodeAPI = function (params) { |
|||
return request({ |
|||
url: `${APIurl}/api/ai_god/market/list`, |
|||
method: "POST", |
|||
data: new URLSearchParams(params), |
|||
headers: { |
|||
token: |
|||
"pCtw6AYK0EHAaIexoFHsbZjtsfEAIhcmwkCFm6uKko8VPfMvyDiODL9v9c0veic9fIpQbvT8zN4sH/Si6Q", |
|||
"Content-Type": "application/x-www-form-urlencoded", |
|||
}, |
|||
}); |
|||
}; |
|||
// 登录获取次数接口
|
|||
export const addUsageAPI = function (params) { |
|||
return request({ |
|||
url: `${APIurl}/api/ai_god/addUsage`, |
|||
method: "POST", |
|||
data: new URLSearchParams(params), |
|||
headers: { |
|||
"Content-Type": "application/x-www-form-urlencoded", |
|||
}, |
|||
}); |
|||
}; |
|||
|
|||
// duobao11
|
|||
export const dbqbFirstAPI = function (params) { |
|||
return request({ |
|||
url: `${cozeAPIurl}/api/workflow/dbqbFirst`, |
|||
method: "POST", |
|||
data: params, |
|||
}); |
|||
}; |
|||
// duobao21
|
|||
export const dbqbSecondOneAPI = function (params) { |
|||
return request({ |
|||
url: `${cozeAPIurl}/api/workflow/dbqbSecondOne`, |
|||
method: "POST", |
|||
data: params, |
|||
}); |
|||
}; |
|||
// duobao22
|
|||
export const dbqbSecondTwoAPI = function (params) { |
|||
return request({ |
|||
url: `${cozeAPIurl}/api/workflow/dbqbSecondTwo`, |
|||
method: "POST", |
|||
data: params, |
|||
}); |
|||
}; |
|||
// duobao23
|
|||
export const dbqbSecondThreeAPI = function (params) { |
|||
return request({ |
|||
url: `${cozeAPIurl}/api/workflow/dbqbSecondThree`, |
|||
method: "POST", |
|||
data: params, |
|||
}); |
|||
}; |
|||
// duobao24
|
|||
export const dbqbSecondFourAPI = function (params) { |
|||
return request({ |
|||
url: `${cozeAPIurl}/api/workflow/dbqbSecondFour`, |
|||
method: "POST", |
|||
data: params, |
|||
}); |
|||
}; |
|||
|
|||
// 历史记录 列表
|
|||
export const getHistoryListAPI = function (params) { |
|||
return request({ |
|||
url: `${APIurl}/api/workflow/listHistory`, |
|||
method: "POST", |
|||
data: params, |
|||
}); |
|||
}; |
|||
|
|||
export const changeTopAPI = function (params) { |
|||
return request({ |
|||
url: `${APIurl}/api/workflow/topRecord`, |
|||
method: "POST", |
|||
data: params, |
|||
headers: { |
|||
token: localStorage.getItem("localToken"), |
|||
}, |
|||
}); |
|||
}; |
|||
|
|||
export const deleteRecordAPI = function (params) { |
|||
return request({ |
|||
url: `${APIurl}/api/workflow/deleteRecord`, |
|||
method: "POST", |
|||
data: params, |
|||
}); |
|||
}; |
|||
|
|||
export const clickRecordAPI = function (params) { |
|||
return request({ |
|||
url: `${APIurl}/api/workflow/clickRecord`, |
|||
method: "POST", |
|||
data: params, |
|||
}); |
|||
}; |
|||
|
|||
// 8.18金币兑换Token start
|
|||
|
|||
export const showExchangeAPI = function (params) { |
|||
return request({ |
|||
url: `${APIurl}/api/showExchange`, |
|||
method: "POST", |
|||
data: params, |
|||
headers: { |
|||
token: localStorage.getItem("localToken"), |
|||
}, |
|||
}); |
|||
}; |
|||
|
|||
export const godExchangeAPI = function (params) { |
|||
return request({ |
|||
url: `${APIurl}/api/godExchange`, |
|||
method: "POST", |
|||
data: params, |
|||
headers: { |
|||
token: localStorage.getItem("localToken"), |
|||
}, |
|||
}); |
|||
}; |
|||
|
|||
export const exchangeAPI = function (params) { |
|||
return request({ |
|||
url: `${APIurl}/api/exchange`, |
|||
method: "POST", |
|||
data: params, |
|||
}); |
|||
}; |
|||
|
|||
export const getGoldCoinAPI = function (params) { |
|||
return request({ |
|||
url: `${HWurl}/api/haiwai/user/getGoldCoin`, |
|||
method: "POST", |
|||
data: params, |
|||
}); |
|||
}; |
|||
|
|||
export const getUserInfoAPI = function (params) { |
|||
return request({ |
|||
url: `${HWurl}/api/v2/member/info`, |
|||
method: "POST", |
|||
data: params, |
|||
}); |
|||
}; |
|||
|
|||
// 8.18金币兑换Token end
|
|||
|
|||
// 8.25权限控制 start
|
|||
export const checkStatusAPI = function (headers, params) { |
|||
return request({ |
|||
url: `${APIurl}/api/workflow/checkStatus`, method: "POST", |
|||
data: params, |
|||
headers: headers, |
|||
}); |
|||
}; |
|||
|
|||
// 8.25权限控制 end
|
@ -0,0 +1,148 @@ |
|||
<template> |
|||
<div |
|||
class="message-item" |
|||
:class="{ |
|||
'user-message': msg.sender === 'user', |
|||
'ai-message': msg.sender === 'ai', |
|||
[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"> |
|||
<thinking-gif |
|||
v-if="msg.gif" |
|||
:type="getGifType(msg.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 class="message-content" v-html="msg.content"></div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref } from 'vue'; |
|||
import ThinkingGif from './ThinkingGif.vue'; |
|||
import thinkingGif from "@/assets/img/gif/思考.gif"; |
|||
import analyzeGif from "@/assets/img/gif/解析.gif"; |
|||
import generateGif from "@/assets/img/gif/生成.gif"; |
|||
|
|||
const props = defineProps({ |
|||
msg: { |
|||
type: Object, |
|||
required: true |
|||
}, |
|||
index: { |
|||
type: Number, |
|||
required: true |
|||
} |
|||
}); |
|||
|
|||
// 根据GIF路径确定类型 |
|||
const getGifType = (gifPath) => { |
|||
if (gifPath === thinkingGif) return 'thinking'; |
|||
if (gifPath === analyzeGif) return 'analyze'; |
|||
if (gifPath === generateGif) return 'generate'; |
|||
return 'thinking'; |
|||
}; |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.message-item { |
|||
margin-bottom: 15px; |
|||
padding: 10px; |
|||
border-radius: 8px; |
|||
} |
|||
|
|||
.user-message { |
|||
background-color: #e6f7ff; |
|||
margin-left: 20%; |
|||
} |
|||
|
|||
.ai-message { |
|||
background-color: #f5f5f5; |
|||
margin-right: 20%; |
|||
} |
|||
|
|||
.kline-container { |
|||
width: 100%; |
|||
height: 400px; |
|||
} |
|||
|
|||
.chart-mount-point { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
|
|||
.no-data-message { |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
height: 100%; |
|||
color: #999; |
|||
} |
|||
|
|||
.ai-message-container { |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.ai-message-content { |
|||
padding: 10px; |
|||
} |
|||
|
|||
.loading-dots .dot { |
|||
animation: loading 1.4s infinite; |
|||
display: inline-block; |
|||
opacity: 0; |
|||
} |
|||
|
|||
.loading-dots .dot:nth-child(1) { |
|||
animation-delay: 0s; |
|||
} |
|||
.loading-dots .dot:nth-child(2) { |
|||
animation-delay: 0.2s; |
|||
} |
|||
.loading-dots .dot:nth-child(3) { |
|||
animation-delay: 0.4s; |
|||
} |
|||
.loading-dots .dot:nth-child(4) { |
|||
animation-delay: 0.6s; |
|||
} |
|||
.loading-dots .dot:nth-child(5) { |
|||
animation-delay: 0.8s; |
|||
} |
|||
.loading-dots .dot:nth-child(6) { |
|||
animation-delay: 1s; |
|||
} |
|||
|
|||
@keyframes loading { |
|||
0% { |
|||
opacity: 0; |
|||
} |
|||
50% { |
|||
opacity: 1; |
|||
} |
|||
100% { |
|||
opacity: 0; |
|||
} |
|||
} |
|||
</style> |
@ -0,0 +1,51 @@ |
|||
<template> |
|||
<div class="thinking-gif-container"> |
|||
<img |
|||
v-if="gifSrc" |
|||
:src="gifSrc" |
|||
alt="思考过程" |
|||
class="thinking-gif" |
|||
/> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup> |
|||
import { ref, computed } from 'vue'; |
|||
// 导入思考过程GIF |
|||
import thinkingGif from "@/assets/img/gif/思考.gif"; |
|||
import analyzeGif from "@/assets/img/gif/解析.gif"; |
|||
import generateGif from "@/assets/img/gif/生成.gif"; |
|||
|
|||
const props = defineProps({ |
|||
type: { |
|||
type: String, |
|||
default: 'thinking' |
|||
} |
|||
}); |
|||
|
|||
const gifSrc = computed(() => { |
|||
switch(props.type) { |
|||
case 'thinking': |
|||
return thinkingGif; |
|||
case 'analyze': |
|||
return analyzeGif; |
|||
case 'generate': |
|||
return generateGif; |
|||
default: |
|||
return thinkingGif; |
|||
} |
|||
}); |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.thinking-gif-container { |
|||
display: flex; |
|||
justify-content: center; |
|||
margin: 10px 0; |
|||
} |
|||
|
|||
.thinking-gif { |
|||
max-width: 100%; |
|||
height: auto; |
|||
} |
|||
</style> |
@ -0,0 +1,54 @@ |
|||
import { defineStore } from "pinia"; |
|||
import { getUserCountAPI } from "@/api/AIxiaocaishen"; |
|||
export const useDeepNineStore = defineStore("deepNine", { |
|||
state: () => ({ |
|||
messages: [], |
|||
isLoading: false, // 新增加载状态
|
|||
UserCount: 0, |
|||
chartData: [], |
|||
kLineData: [], |
|||
dbqbClickRecord: {}, |
|||
searchRecord: false, |
|||
currentUserIndex: null, |
|||
inputUserIndex:null, |
|||
announcementMsg: null, |
|||
// 调用接口状态
|
|||
aiChatCall:false, |
|||
aiEmotionCall:false, |
|||
deepNineCall:false, |
|||
// 输入框控制
|
|||
chatInput:false, |
|||
emotionInput:false, |
|||
deepNineInput:false, |
|||
|
|||
firstAPICall:false, |
|||
dbqbScrollToTop:true, |
|||
}), |
|||
actions: { |
|||
async getUserCount() { |
|||
const result = await getUserCountAPI({ |
|||
token: localStorage.getItem("localToken"), |
|||
source: "1", |
|||
}); |
|||
this.UserCount = result.data; |
|||
}, |
|||
setLoading(status) { |
|||
this.isLoading = status; |
|||
}, |
|||
isLoadingT() { |
|||
this.isLoading = true; |
|||
}, |
|||
isLoadingF() { |
|||
this.isLoading = false; |
|||
}, |
|||
addKLineData(data) { |
|||
this.kLineData.push(data); |
|||
}, |
|||
}, |
|||
|
|||
persist: { |
|||
key: "deepNine_messages", |
|||
storage: sessionStorage, |
|||
paths: ["messages", "kLineData"], |
|||
}, |
|||
}); |
@ -0,0 +1,89 @@ |
|||
import { defineStore } from 'pinia' |
|||
|
|||
export const useDeepNineAudioStore = defineStore('deepNineAudio', { |
|||
state: () => ({ |
|||
soundInstance: null, // Howl 实例
|
|||
isPlaying: false, // 播放状态
|
|||
isVoiceEnabled: true, // 新增声音开关状态
|
|||
playbackPosition: 0, // 新增播放位置存储
|
|||
lastVoiceState: null, |
|||
ttsUrl:'', |
|||
isNewInstance: false, // 新增是否是新实例的标志
|
|||
nowSound:'', |
|||
currentAudioUrl: '', // 当前音频URL
|
|||
isPaused: false, // 是否处于暂停状态
|
|||
duration: 0 // 音频总时长
|
|||
}), |
|||
actions: { |
|||
// 设置音频实例
|
|||
setAudioInstance(instance) { |
|||
this.soundInstance = instance |
|||
}, |
|||
// 播放控制
|
|||
play() { |
|||
if (this.soundInstance) { |
|||
if (this.isPaused && this.playbackPosition > 0) { |
|||
// 从暂停位置继续播放
|
|||
this.soundInstance.seek(this.playbackPosition) |
|||
} |
|||
this.soundInstance.play() |
|||
this.isPlaying = true |
|||
this.isPaused = false |
|||
} |
|||
}, |
|||
// 暂停控制
|
|||
pause() { |
|||
if (this.soundInstance && this.isPlaying) { |
|||
// 保存当前播放位置
|
|||
this.playbackPosition = this.soundInstance.seek() || 0 |
|||
this.soundInstance.pause() |
|||
this.isPlaying = false |
|||
this.isPaused = true |
|||
} |
|||
}, |
|||
// 停止播放
|
|||
stop() { |
|||
if (this.soundInstance) { |
|||
this.soundInstance.stop() |
|||
this.isPlaying = false |
|||
this.isPaused = false |
|||
this.playbackPosition = 0 |
|||
} |
|||
}, |
|||
// 切换播放/暂停
|
|||
togglePlayPause() { |
|||
if (this.isPlaying) { |
|||
this.pause() |
|||
} else { |
|||
this.play() |
|||
} |
|||
}, |
|||
// 设置当前音频URL
|
|||
setCurrentAudioUrl(url) { |
|||
if (this.currentAudioUrl !== url) { |
|||
// 如果是新的音频,重置播放状态
|
|||
this.stop() |
|||
this.currentAudioUrl = url |
|||
this.playbackPosition = 0 |
|||
this.isPaused = false |
|||
} |
|||
}, |
|||
// 语音开关控制
|
|||
toggleVoice() { |
|||
this.isVoiceEnabled = !this.isVoiceEnabled |
|||
if (!this.isVoiceEnabled) { |
|||
// 关闭语音时停止当前播放
|
|||
this.stop() |
|||
} |
|||
}, |
|||
// 重置音频状态
|
|||
resetAudioState() { |
|||
this.stop() |
|||
this.currentAudioUrl = '' |
|||
this.ttsUrl = '' |
|||
this.soundInstance = null |
|||
this.nowSound = '' |
|||
} |
|||
} |
|||
}) |
|||
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue