|
@@ -7,14 +7,27 @@
|
|
|
|
|
|
<div class="header">
|
|
|
<div class="user-profile-bar">
|
|
|
+ <div class="logo">
|
|
|
+ <img
|
|
|
+ src="../assets/logos.JPG"
|
|
|
+
|
|
|
+ alt="logo"
|
|
|
+ class="logo-img"
|
|
|
+ />
|
|
|
+ <!-- <span>Raycos Tech</span> -->
|
|
|
+ </div>
|
|
|
+ <div class="user-info-bar">
|
|
|
<div class="user-info" @click="toggleDropdown">
|
|
|
- <div class="user-name">{{ userInfo.username || '未登录用户' }}</div>
|
|
|
+ <div class="user-name">商户名称:{{ userInfo.merchant?.merchant_name || '未登录用户' }}</div>
|
|
|
+ <div class="user-name">当前用户:{{ userInfo.username || '未登录用户' }}</div>
|
|
|
<!-- <div class="user-role">{{ userInfo.role || '未设置角色' }}</div> -->
|
|
|
</div>
|
|
|
<div class="avatar" @click="toggleDropdown">
|
|
|
<img src="../assets/avter.png" alt="用户头像" />
|
|
|
</div>
|
|
|
-
|
|
|
+ </div>
|
|
|
+ <!-- Logo :draggable="false"-->
|
|
|
+
|
|
|
<!-- 下拉菜单 -->
|
|
|
<div class="dropdown-menu" v-show="showDropdown">
|
|
|
<div class="dropdown-item" @click="handleModifyPassword">
|
|
@@ -47,16 +60,7 @@
|
|
|
<!-- 添加收缩按钮 -->
|
|
|
|
|
|
|
|
|
- <!-- Logo -->
|
|
|
- <div class="logo">
|
|
|
- <img
|
|
|
- src="../assets/logos.JPG"
|
|
|
- :draggable="false"
|
|
|
- alt="logo"
|
|
|
- class="logo-img"
|
|
|
- />
|
|
|
- <!-- <span>Raycos Tech</span> -->
|
|
|
- </div>
|
|
|
+
|
|
|
|
|
|
<!-- New Conversation 按钮 -->
|
|
|
<button class="new-conversation-btn" @click="addNewConversation">
|
|
@@ -811,7 +815,12 @@ const fetchDocumentSummary = async () => {
|
|
|
isLoadingSummary.value = true;
|
|
|
try {
|
|
|
const response = await axios.get(
|
|
|
- `${import.meta.env.VITE_BASE_AI_API}/api/document/summary`
|
|
|
+ `${import.meta.env.VITE_BASE_AI_API}/api/document/summary`,
|
|
|
+ {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `JWT ${localStorage.getItem('token')}`
|
|
|
+ }
|
|
|
+ }
|
|
|
);
|
|
|
console.log("API Response:", response.data);
|
|
|
|
|
@@ -1086,10 +1095,10 @@ const sendMessage = async () => {
|
|
|
// 根据开启的搜索类型执行相应的搜索
|
|
|
if (enableWebSearch.value) {
|
|
|
currentSearchQuery.value = currentMessage;
|
|
|
- // 执行互联网搜索
|
|
|
+ /* // 执行互联网搜索
|
|
|
sendMessageS(currentMessage).catch((error) => {
|
|
|
console.error("Web search failed:", error);
|
|
|
- });
|
|
|
+ }); */
|
|
|
} else if (enableKnowledgeSearch.value) {
|
|
|
currentSearchQuery.value = currentMessage;
|
|
|
// 执行知识库搜索
|
|
@@ -1127,7 +1136,7 @@ const sendMessage = async () => {
|
|
|
displayContent: messageContent,
|
|
|
files: attachedFiles.value.map((file) => ({
|
|
|
name: file.name,
|
|
|
- size: file.sizeFormatted, // 使用格式化后的大小
|
|
|
+ size: file.sizeFormatted,
|
|
|
type: file.type,
|
|
|
url: file.url,
|
|
|
})),
|
|
@@ -1164,54 +1173,47 @@ const sendMessage = async () => {
|
|
|
merchant_id:JSON.parse(localStorage.getItem('userInfo')).merchant.merchant_id,
|
|
|
model_type: selectedModelInfo.value.type,
|
|
|
model_name: selectedModelInfo.value.name
|
|
|
- });
|
|
|
- if(!enableWebSearch.value){
|
|
|
- // 更新 session_id
|
|
|
- if (response.data.session_id) {
|
|
|
- session_id.value = response.data.session_id;
|
|
|
+ },
|
|
|
+ {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `JWT ${localStorage.getItem('token')}`
|
|
|
}
|
|
|
+ }
|
|
|
+ );
|
|
|
|
|
|
- // 移除时消息并添加AI响应
|
|
|
- messages.value = messages.value.filter(
|
|
|
- (msg) => msg.id !== tempAiMessage.id
|
|
|
- );
|
|
|
- const aiMessage = reactive({
|
|
|
- id: Date.now() + 2,
|
|
|
- role: "assistant",
|
|
|
- content:response.data.choices[0].message.content,
|
|
|
- displayContent: "",
|
|
|
- audioData: response.data.audio_info?.audio || null, // 保存音频数据
|
|
|
- });
|
|
|
- messages.value.push(aiMessage);
|
|
|
- // 如果有音频数据,自动播放
|
|
|
- if (aiMessage.audioData) {
|
|
|
- playAudioMessage(aiMessage.audioData);
|
|
|
- }
|
|
|
- await typeMessage(aiMessage);
|
|
|
- }else{
|
|
|
-
|
|
|
- if (response.data.data.session_id) {
|
|
|
- session_id.value = response.data.data.session_id;
|
|
|
- }
|
|
|
+ // 移除临时消息
|
|
|
+ messages.value = messages.value.filter(
|
|
|
+ (msg) => msg.id !== tempAiMessage.id
|
|
|
+ );
|
|
|
|
|
|
- // 移除时消息并添加AI响应
|
|
|
- messages.value = messages.value.filter(
|
|
|
- (msg) => msg.id !== tempAiMessage.id
|
|
|
- );
|
|
|
- const aiMessage = reactive({
|
|
|
- id: Date.now() + 2,
|
|
|
- role: "assistant",
|
|
|
- content:response.data.data.answer,
|
|
|
- displayContent: "",
|
|
|
- audioData: response.data.data.audio_info?.audio || null, // 保存音频数据
|
|
|
- });
|
|
|
- messages.value.push(aiMessage);
|
|
|
- // 如果有音频数据,自动播放
|
|
|
- if (aiMessage.audioData) {
|
|
|
- playAudioMessage(aiMessage.audioData);
|
|
|
- }
|
|
|
- await typeMessage(aiMessage);
|
|
|
+ // 统一处理响应数据
|
|
|
+ const responseData = enableWebSearch.value ? response.data.data : response.data;
|
|
|
+ const messageContent = enableWebSearch.value ? responseData.answer : responseData.choices[0].message.content;
|
|
|
+ const audioInfo = enableWebSearch.value ? responseData.audio_info?.audio : responseData.audio_info?.audio;
|
|
|
+ const sessionId = enableWebSearch.value ? responseData.session_id : responseData.session_id;
|
|
|
+
|
|
|
+ // 更新 session_id
|
|
|
+ if (sessionId) {
|
|
|
+ session_id.value = sessionId;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建新的AI消息
|
|
|
+ const aiMessage = reactive({
|
|
|
+ id: Date.now() + 2,
|
|
|
+ role: "assistant",
|
|
|
+ content: messageContent,
|
|
|
+ displayContent: "",
|
|
|
+ audioData: audioInfo || null,
|
|
|
+ });
|
|
|
+ messages.value.push(aiMessage);
|
|
|
+
|
|
|
+ // 如果有音频数据,自动播放
|
|
|
+ if (aiMessage.audioData && !isPlaying.value) {
|
|
|
+ await playAudioMessage(aiMessage.audioData, aiMessage.id);
|
|
|
}
|
|
|
+
|
|
|
+ // 显示打字效果
|
|
|
+ await typeMessage(aiMessage);
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error("发送消息失败:", error);
|
|
@@ -1391,18 +1393,27 @@ const handleAudioClick = (message) => {
|
|
|
}
|
|
|
}else{
|
|
|
// 用户消息的文本转语音处理
|
|
|
- playUserMessage(message.content, message.id);
|
|
|
+ if (currentPlayingId.value === message.id) {
|
|
|
+ stopAudio();
|
|
|
+ } else {
|
|
|
+ playUserMessage(message.content, message.id);
|
|
|
+ }
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 添加播放用户消息的方法
|
|
|
const playUserMessage = async (text, messageId) => {
|
|
|
try {
|
|
|
- // 如果当前有音频在播放,先停止
|
|
|
- if (currentPlayingId.value) {
|
|
|
+ // 如果当前有其他音频在播放,先停止
|
|
|
+ if (currentPlayingId.value && currentPlayingId.value !== messageId) {
|
|
|
stopAudio();
|
|
|
}
|
|
|
|
|
|
+ // 如果已经在播放这条消息,直接返回(这种情况应该不会发生,因为会被上面的 handleAudioClick 处理)
|
|
|
+ if (currentPlayingId.value === messageId) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
const response = await axios.post(`${import.meta.env.VITE_BASE_AI_API}/api/voices/play_text`, {
|
|
|
text: text
|
|
|
}, {
|
|
@@ -1418,11 +1429,22 @@ const playUserMessage = async (text, messageId) => {
|
|
|
// 保存当前播放的音频和ID
|
|
|
currentAudioSource.value = audio;
|
|
|
currentPlayingId.value = messageId;
|
|
|
+ isPlaying.value = true;
|
|
|
|
|
|
// 播放结束时清理
|
|
|
audio.onended = () => {
|
|
|
currentPlayingId.value = null;
|
|
|
currentAudioSource.value = null;
|
|
|
+ isPlaying.value = false;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 播放错误时清理
|
|
|
+ audio.onerror = (e) => {
|
|
|
+ console.error('播放文本失败:', e);
|
|
|
+ message.error('播放失败,请稍后重试');
|
|
|
+ currentPlayingId.value = null;
|
|
|
+ currentAudioSource.value = null;
|
|
|
+ isPlaying.value = false;
|
|
|
};
|
|
|
|
|
|
// 开始播放
|
|
@@ -1431,46 +1453,9 @@ const playUserMessage = async (text, messageId) => {
|
|
|
} catch (error) {
|
|
|
console.error('播放文本失败:', error);
|
|
|
message.error('播放失败,请稍后重试');
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-/* // 停止音频播放的函数
|
|
|
-const stopAudio = () => {
|
|
|
- if (currentAudioSource.value) {
|
|
|
- currentAudioSource.value.pause();
|
|
|
- currentAudioSource.value.currentTime = 0;
|
|
|
- currentAudioSource.value = null;
|
|
|
currentPlayingId.value = null;
|
|
|
- }
|
|
|
-} */
|
|
|
-
|
|
|
-// 修改音频播放方法
|
|
|
-const playAudioMessage = async (audioBase64, messageId) => {
|
|
|
- try {
|
|
|
- // 停止当前播放的音频
|
|
|
- stopAudio();
|
|
|
-
|
|
|
- // 创建新的音频元素
|
|
|
- const audio = new Audio(audioBase64);
|
|
|
- audioPlayer.value = audio;
|
|
|
- currentPlayingId.value = messageId;
|
|
|
-
|
|
|
- // 添加事件监听
|
|
|
- audio.addEventListener("ended", () => {
|
|
|
- stopAudio();
|
|
|
- });
|
|
|
-
|
|
|
- audio.addEventListener("error", (e) => {
|
|
|
- console.error("音频播放错误:", e);
|
|
|
- stopAudio();
|
|
|
- });
|
|
|
-
|
|
|
- // 播放音频
|
|
|
- await audio.play();
|
|
|
- isPlaying.value = true;
|
|
|
- } catch (error) {
|
|
|
- console.error("音频播放失败:", error);
|
|
|
- stopAudio();
|
|
|
+ currentAudioSource.value = null;
|
|
|
+ isPlaying.value = false;
|
|
|
}
|
|
|
};
|
|
|
|
|
@@ -1478,6 +1463,7 @@ const playAudioMessage = async (audioBase64, messageId) => {
|
|
|
const stopAudio = () => {
|
|
|
if (audioPlayer.value) {
|
|
|
audioPlayer.value.pause();
|
|
|
+ audioPlayer.value.currentTime = 0;
|
|
|
audioPlayer.value = null;
|
|
|
}
|
|
|
if (currentAudioSource.value) {
|
|
@@ -1579,9 +1565,9 @@ const handleInputChange = () => {
|
|
|
|
|
|
// 根据当前选中的搜索类型执行相应的搜索
|
|
|
if (enableWebSearch.value) {
|
|
|
- sendMessageS(inputContent.value).catch((error) => {
|
|
|
+ /* sendMessageS(inputContent.value).catch((error) => {
|
|
|
console.error("Web search failed:", error);
|
|
|
- });
|
|
|
+ }); */
|
|
|
} else if (enableKnowledgeSearch.value) {
|
|
|
handleKnowledgeSearch({
|
|
|
query: inputContent.value,
|
|
@@ -1675,13 +1661,18 @@ const updateCollapsedState = () => {
|
|
|
isKnowledgeLoading.value = true;
|
|
|
try {
|
|
|
const response = await axios.post(
|
|
|
- `${import.meta.env.VITE_BASE_AI_F_API}/chataids/mysql_fuzzy_search/`,
|
|
|
+ `${import.meta.env.VITE_BASE_AI_API}/chataids/mysql_fuzzy_search/`,
|
|
|
{
|
|
|
- collection_names: ["25"], // 固定使用 "25" 作为 collection name
|
|
|
+ // collection_names: ["25"], // 固定使用 "25" 作为 collection name
|
|
|
keywords: params.query, // 使用查询关键词
|
|
|
documents: [], // 保持空数组
|
|
|
document_ids: [], // 保持空数组
|
|
|
limit: 5 // 固定返回5条结果
|
|
|
+ },
|
|
|
+ {
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `JWT ${localStorage.getItem('token')}`
|
|
|
+ }
|
|
|
}
|
|
|
);
|
|
|
console.log(response);
|
|
@@ -1709,9 +1700,9 @@ const updateCollapsedState = () => {
|
|
|
// 如果有输入内容,执行互联网搜索
|
|
|
if (inputContent.value.trim()) {
|
|
|
currentSearchQuery.value = inputContent.value;
|
|
|
- sendMessageS(inputContent.value).catch((error) => {
|
|
|
+ /* sendMessageS(inputContent.value).catch((error) => {
|
|
|
console.error("Web search failed:", error);
|
|
|
- });
|
|
|
+ }); */
|
|
|
}
|
|
|
}
|
|
|
};
|
|
@@ -1888,6 +1879,54 @@ const updateCollapsedState = () => {
|
|
|
/* const currentAudioSource = ref(null);
|
|
|
const currentPlayingId = ref(null); */
|
|
|
|
|
|
+ // 添加播放音频消息的方法
|
|
|
+ const playAudioMessage = async (audioData, messageId) => {
|
|
|
+ try {
|
|
|
+ // 如果当前有其他音频在播放,先停止
|
|
|
+ if (currentPlayingId.value && currentPlayingId.value !== messageId) {
|
|
|
+ stopAudio();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果已经在播放这条消息,直接返回
|
|
|
+ if (currentPlayingId.value === messageId) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建新的音频元素
|
|
|
+ const audio = new Audio(audioData);
|
|
|
+
|
|
|
+ // 保存当前播放的音频和ID
|
|
|
+ currentAudioSource.value = audio;
|
|
|
+ currentPlayingId.value = messageId;
|
|
|
+ isPlaying.value = true;
|
|
|
+
|
|
|
+ // 播放结束时清理
|
|
|
+ audio.onended = () => {
|
|
|
+ currentPlayingId.value = null;
|
|
|
+ currentAudioSource.value = null;
|
|
|
+ isPlaying.value = false;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 播放错误时清理
|
|
|
+ audio.onerror = (e) => {
|
|
|
+ console.error('播放音频失败:', e);
|
|
|
+ message.error('播放失败,请稍后重试');
|
|
|
+ currentPlayingId.value = null;
|
|
|
+ currentAudioSource.value = null;
|
|
|
+ isPlaying.value = false;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 开始播放
|
|
|
+ await audio.play();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('播放音频失败:', error);
|
|
|
+ message.error('播放失败,请稍后重试');
|
|
|
+ currentPlayingId.value = null;
|
|
|
+ currentAudioSource.value = null;
|
|
|
+ isPlaying.value = false;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
@@ -1915,12 +1954,20 @@ const updateCollapsedState = () => {
|
|
|
position: relative;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- justify-content: flex-end;
|
|
|
+ justify-content: space-between;
|
|
|
cursor: pointer;
|
|
|
- padding: 8px;
|
|
|
+
|
|
|
+
|
|
|
+}
|
|
|
+.user-info-bar{
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: flex-end;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background-color: #f7f7f8;
|
|
|
|
|
|
}
|
|
|
-
|
|
|
.avatar {
|
|
|
width: 40px;
|
|
|
height: 40px;
|