Browse Source

第一版修改

hongxilin
donghaolin 3 months ago
parent
commit
0e76a4892a
  1. 20
      package-lock.json
  2. 1
      package.json
  3. 22
      src/store/userPermissionCode.js
  4. 473
      src/views/aaa.vue
  5. 131
      src/views/chat.vue

20
package-lock.json

@ -11,6 +11,7 @@
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.1",
"axios": "^1.7.9", "axios": "^1.7.9",
"element-plus": "^2.9.4", "element-plus": "^2.9.4",
"stream": "^0.0.3",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-router": "^4.5.0" "vue-router": "^4.5.0"
}, },
@ -1100,6 +1101,17 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/component-emitter": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/component-emitter/-/component-emitter-2.0.0.tgz",
"integrity": "sha512-4m5s3Me2xxlVKG9PkZpQqHQR7bgpnN7joDMJ4yvVkVXngjoITG76IaZmzmywSeRTeTpc6N6r3H3+KyUurV8OYw==",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
@ -1598,6 +1610,14 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/stream": {
"version": "0.0.3",
"resolved": "https://registry.npmmirror.com/stream/-/stream-0.0.3.tgz",
"integrity": "sha512-aMsbn7VKrl4A2T7QAQQbzgN7NVc70vgF5INQrBXqn4dCXN1zy3L9HGgLO5s7PExmdrzTJ8uR/27aviW8or8/+A==",
"dependencies": {
"component-emitter": "^2.0.0"
}
},
"node_modules/vite": { "node_modules/vite": {
"version": "6.1.1", "version": "6.1.1",
"resolved": "https://registry.npmmirror.com/vite/-/vite-6.1.1.tgz", "resolved": "https://registry.npmmirror.com/vite/-/vite-6.1.1.tgz",

1
package.json

@ -12,6 +12,7 @@
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.1",
"axios": "^1.7.9", "axios": "^1.7.9",
"element-plus": "^2.9.4", "element-plus": "^2.9.4",
"stream": "^0.0.3",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-router": "^4.5.0" "vue-router": "^4.5.0"
}, },

22
src/store/userPermissionCode.js

@ -23,6 +23,23 @@ export const useUserInfo = () => {
return '' return ''
} }
const getSessionId = (sessionID,data) => {
const query = data
const vars = query.split(',')
// console.log('vars', vars)
for (let i = 0; i < vars.length; i++) {
const pair = vars[i].split(':')
// console.log('pair', pair)
if (pair[0].includes(sessionID)) {
const sessionId = pair[1].split('"')[1];
// console.log('sessionId', sessionId)
return sessionId
}
}
return ''
}
const fetchUserInfo = async () => { const fetchUserInfo = async () => {
getAppToken.value = localStorage.getItem('localToken') getAppToken.value = localStorage.getItem('localToken')
? String(localStorage.getItem('localToken')) ? String(localStorage.getItem('localToken'))
@ -51,7 +68,7 @@ export const useUserInfo = () => {
} }
onMounted(() => { onMounted(() => {
init()
}) })
return { return {
@ -60,6 +77,7 @@ export const useUserInfo = () => {
isReady, isReady,
init, init,
fetchUserInfo, fetchUserInfo,
getQueryVariable
getQueryVariable,
getSessionId
} }
} }

473
src/views/aaa.vue

@ -0,0 +1,473 @@
<script setup>
// 使
import { ref, nextTick, watch, onMounted } from 'vue'
import { useUserInfo } from '../store/userPermissionCode'
const { getQueryVariable } = useUserInfo()
const isTokenValid = ref(false)
const fnGetToken = () => {
localStorage.setItem('localToken', decodeURIComponent(String(getQueryVariable('token'))))
console.log(localStorage.getItem('localToken'));
}
setTimeout(() => {
fnGetToken()
}, 800)
// token
const validateToken = async () => {
const token = localStorage.getItem('localToken')
if (!token) {
console.error('未找到 token,请重新登录')
return false
}
return true
}
// token
validateToken().then((isValid) => {
isTokenValid.value = isValid
if (!isValid) {
//
console.error('Token 验证失败,请重新登录')
}
})
// Props
const props = defineProps({
apiUrl: {
type: String,
default: 'http://localhost:5000/ask'
},
initialGreeting: {
type: String,
default: '您好!请问有什么可以帮助您?'
}
})
//
const messages = ref([
{
content: props.initialGreeting,
sender: 'bot',
timestamp: new Date()
}
])
const inputMessage = ref('')
const messageContainer = ref(null)
const isLoading = ref(false) //
//
const scrollToBottom = () => {
nextTick(() => {
if (messageContainer.value) {
messageContainer.value.scrollTop = messageContainer.value.scrollHeight
}
})
}
//
const handleStreamResponse = async (response, onChunk) => {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let done = false;
while (!done) {
const { value, done: readerDone } = await reader.read();
done = readerDone;
if (value) {
const chunk = decoder.decode(value, { stream: true });
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data:')) {
const data = line.slice(5).trim();
if (data && data!== '[DONE]') {
try {
const jsonData = JSON.parse(data);
const answerChunk = jsonData.data.answer.replace(/<think>|<\/think>/g, '');
onChunk(answerChunk);
} catch (error) {
console.error('解析 JSON 数据时出错:', error);
}
}
}
}
}
}
};
//
const sendMessage = async () => {
if (!isTokenValid.value) {
console.error('Token 验证失败,无法发送消息')
return
}
if (isLoading.value) return;
const content = inputMessage.value.trim()
if (!content) return
//
messages.value.push({
content,
sender: 'user',
timestamp: new Date()
})
//
inputMessage.value = ''
scrollToBottom()
//
isLoading.value = true
const loadingMessage = {
content: '我正在思考...',
sender: 'bot',
timestamp: new Date(),
isLoading: true
};
messages.value.push(loadingMessage);
scrollToBottom()
try {
// API
const response = await fetch("http://192.168.1.140/api/v1/chats/89fcb72aeffc11ef98100242ac120006/completions", {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: localStorage.getItem('localToken') },
body: JSON.stringify({ question: content, stream: true, session_id: "750a8af4f00611ef9e130242ac120006" })
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
//
messages.value = messages.value.filter(msg => !msg.isLoading);
const botMessage = {
content: '',
sender: 'bot',
timestamp: new Date()
};
messages.value.push(botMessage);
await handleStreamResponse(response, (chunk) => {
botMessage.content += chunk;
const grayText = `<span style="color: gray;">${botMessage.content}</span>`;
botMessage.content = grayText;
scrollToBottom();
});
} catch (error) {
console.error('API请求失败:', error)
//
messages.value = messages.value.filter(msg => !msg.isLoading);
messages.value.push({
content: '服务暂时不可用,请稍后再试',
sender: 'bot',
timestamp: new Date()
})
scrollToBottom()
} finally {
//
isLoading.value = false
}
}
//
const formatTime = (date) => {
return new Date(date).toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit'
})
}
//
const adjustInputHeight = () => {
const textarea = document.querySelector('.message-input')
textarea.style.height = 'auto'
textarea.style.height = `${textarea.scrollHeight}px`
}
//
watch(inputMessage, adjustInputHeight)
//
onMounted(async () => {
// token
fnGetToken()
// token
const isValid = await validateToken()
isTokenValid.value = isValid
if (!isValid) {
console.error('Token 验证失败,请重新登录')
}
})
</script>
<template>
<!-- 聊天容器 -->
<div class="chat-container">
<!-- 聊天框头部 -->
<div class="chat-header">夺宝奇兵智能客服</div>
<!-- 消息展示区域 -->
<div class="message-list" ref="messageContainer">
<div v-for="(message, index) in messages" :key="index" class="message-item" :class="[message.sender]">
<!-- 机器人头像 -->
<div v-if="message.sender === 'bot'" class="bot-avatar">
<img src="/src/assets/img/avatar/超级云脑按钮.png" alt="Bot Avatar">
</div>
<div class="message-bubble">
<div class="message-content">
<!-- 显示加载动画 -->
<span v-if="message.isLoading">
{{ message.content }}
<el-icon class="is-loading">
<Loading />
</el-icon>
</span>
<span v-else v-html="message.content"></span>
</div>
<div class="message-time">{{ formatTime(message.timestamp) }}</div>
</div>
<!-- 用户头像 -->
<div v-if="message.sender === 'user'" class="user-avatar">
<img src="/src/assets/img/avatar/小柒.png" alt="User Avatar">
</div>
</div>
</div>
<!-- 输入区域 -->
<div class="input-area">
<textarea v-model="inputMessage" @keydown.enter.exact.prevent="isLoading ? null : sendMessage()"
placeholder="输入您的问题..." rows="1" class="message-input"></textarea>
<el-tooltip content="机器人正在思考" :disabled="!isLoading">
<template #content>
机器人正在思考
</template>
<button @click="sendMessage" :disabled="!isTokenValid || isLoading" class="send-button">
<!-- 使用ElementPlus的发送图标 -->
<span v-if="isLoading">
<el-icon class="is-loading">
<Loading />
</el-icon>
</span>
<span v-else class="send-button-content">
<el-icon>
<Position />
</el-icon>
<span> 发送</span>
</span>
</button>
</el-tooltip>
</div>
<!-- 未登录覆盖层 -->
<div v-if="!isTokenValid" class="overlay">
<div class="overlay-content">用户未登录</div>
</div>
</div>
</template>
<style scoped>
.chat-container {
display: flex;
flex-direction: column;
height: 90vh;
width: 90vw;
max-width: 800px;
margin: 0;
border: 1px solid #e0e0e0;
border-radius: 12px;
background: #f8f9fa;
overflow: hidden;
/* 新增样式,实现水平和垂直居中 */
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* 聊天框头部样式 */
.chat-header {
background-color: #007bff;
color: white;
padding: 16px;
font-size: 1.2rem;
text-align: center;
}
.message-list {
flex: 1;
padding: 20px;
overflow-y: auto;
background: white;
}
.message-item {
display: flex;
margin-bottom: 16px;
}
.message-item.user {
justify-content: flex-end;
}
.bot-avatar {
margin-right: 10px;
}
.bot-avatar img {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.user-avatar {
margin-left: 10px;
}
.user-avatar img {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.message-bubble {
max-width: 80%;
padding: 12px 16px;
border-radius: 15px;
position: relative;
}
.message-content span {
display: block;
/* 确保元素显示 */
}
.message-item.user .message-bubble {
background: #007bff;
color: white;
border-bottom-right-radius: 4px;
}
.message-item.bot .message-bubble {
background: #f1f3f5;
color: #212529;
border-bottom-left-radius: 4px;
}
.message-time {
font-size: 0.75rem;
color: rgba(255, 255, 255, 0.8);
margin-top: 4px;
text-align: right;
}
.message-item.bot .message-time {
color: rgba(0, 0, 0, 0.6);
}
.input-area {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
border-top: 1px solid #e0e0e0;
background: white;
}
.message-input {
flex: 1;
padding: 10px 16px;
border: 1px solid #e0e0e0;
border-radius: 20px;
resize: none;
max-height: 120px;
font-family: inherit;
}
.send-button {
display: flex;
align-items: center;
justify-content: center;
width: 100px;
/* 调整宽度以适应文字 */
height: 40px;
border: none;
border-radius: 20px;
/* 调整圆角 */
background: #007bff;
color: white;
cursor: pointer;
transition: all 0.3s ease;
/* 添加过渡效果 */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
/* 添加阴影 */
font-size: 16px;
/* 调整字体大小 */
font-weight: 600;
/* 调整字体粗细 */
}
.send-button:hover {
background: #0056b3;
transform: translateY(-2px);
/* 悬停时向上移动 */
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
/* 悬停时增加阴影 */
}
/* 新增加载状态样式 */
.loading-state {
background: #ccc;
cursor: not-allowed;
}
.send-button-content {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
/* 调整文字和图标间距 */
}
/* .send-button {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border: none;
border-radius: 50%;
background: #007bff;
color: white;
cursor: pointer;
transition: background 0.2s;
}
.send-button:hover {
background: #0056b3;
} */
.send-button svg {
width: 20px;
height: 20px;
}
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.8);
/* 透明度 50% 的白色背景 */
display: flex;
justify-content: center;
align-items: center;
z-index: 1;
/* 确保覆盖层在聊天框上方 */
}
.overlay-content {
font-size: 36px;
font-weight: bold;
color: #f60707;
}
</style>

131
src/views/chat.vue

@ -1,9 +1,14 @@
<script setup> <script setup>
// 使
import { ref, nextTick, watch,onMounted } from 'vue'
import { useAppBridge } from '../assets/js/useAppBridge'
import { ref, nextTick, watch, onMounted } from 'vue'
import { useUserInfo } from '../store/userPermissionCode' import { useUserInfo } from '../store/userPermissionCode'
import axios from 'axios'
import { ElMessage } from 'element-plus';
import { Loading, Position } from '@element-plus/icons-vue'; //
const { getQueryVariable } = useUserInfo() const { getQueryVariable } = useUserInfo()
// getSessionId
const { getSessionId } = useUserInfo()
const isTokenValid = ref(false) const isTokenValid = ref(false)
const fnGetToken = () => { const fnGetToken = () => {
localStorage.setItem('localToken', decodeURIComponent(String(getQueryVariable('token')))) localStorage.setItem('localToken', decodeURIComponent(String(getQueryVariable('token'))))
@ -12,38 +17,60 @@ const fnGetToken = () => {
setTimeout(() => { setTimeout(() => {
fnGetToken() fnGetToken()
}, 800) }, 800)
//
const commonParams = {
token: localStorage.getItem('localToken'),
}
const token = localStorage.getItem('localToken')
// token // token
const validateToken = async () => { const validateToken = async () => {
const token = localStorage.getItem('localToken') const token = localStorage.getItem('localToken')
console.log('token',token);
if (!token) { if (!token) {
console.error('未找到 token,请重新登录') console.error('未找到 token,请重新登录')
return false return false
} }
// const isValid = await validateTokenAPI(token)
// if (!isValid) {
// console.error('Token ')
// return false
// }
return true return true
} }
//
const sessionId = ref(localStorage.getItem('sessionId') || {});
const add = async () => {
try {
const result = await axios.post(
"http://t3zf7v.natappfree.cc/api/v1/chats/8b37cd9cf0c811efa4210242ac120003/completions",
{},
{
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ragflow-hkNjEwYjcwZjBlMDExZWZiYjYzMDI0Mm'
}
}
);
const data = result.data;
console.log(data);
console.log(data, 'data11111111');
// data
if (Object.keys(data).length === 0) {
return;
}
// getSessionId
const sss = String(getSessionId("session_id", data));
sessionId.value = sss;
localStorage.setItem('sessionId', sss);
console.log('sss', sss);
} catch (error) {
console.log("请求失败", error);
ElMessage.error("添加失败,请检查输入内容是否正确");
}
};
// token // token
validateToken().then((isValid) => { validateToken().then((isValid) => {
isTokenValid.value = isValid isTokenValid.value = isValid
if (!isValid) {
//
console.error('Token 验证失败,请重新登录')
if (isValid) {
console.log('Token 验证成功')
if (!localStorage.getItem('sessionId')) {
console.log('没有sessionId,创建新对话')
add()
}
} }
}) })
// Props // Props
const props = defineProps({ const props = defineProps({
apiUrl: { apiUrl: {
@ -52,7 +79,7 @@ const props = defineProps({
}, },
initialGreeting: { initialGreeting: {
type: String, type: String,
default: '您好!请问有什么可以帮助您?'
default: '您好!我是夺宝奇兵ai智能客服,有什么可以帮助您的?'
} }
}) })
// //
@ -66,7 +93,6 @@ const messages = ref([
const inputMessage = ref('') const inputMessage = ref('')
const messageContainer = ref(null) const messageContainer = ref(null)
const isLoading = ref(false) // const isLoading = ref(false) //
// //
const scrollToBottom = () => { const scrollToBottom = () => {
nextTick(() => { nextTick(() => {
@ -108,22 +134,42 @@ const sendMessage = async () => {
try { try {
// API // API
const response = await fetch(props.apiUrl, {
const response = await fetch("http://t3zf7v.natappfree.cc/api/v1/chats/8b37cd9cf0c811efa4210242ac120003/completions", {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question: content })
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ragflow-hkNjEwYjcwZjBlMDExZWZiYjYzMDI0Mm' },
body: JSON.stringify({ question: content, stream: false, session_id: sessionId.value })
}) })
const data = await response.json() const data = await response.json()
// //
messages.value = messages.value.filter(msg => !msg.isLoading) messages.value = messages.value.filter(msg => !msg.isLoading)
//
const regex = /<think>(.*?)<\/think>(.*)/s;
const match = data.data.answer.match(regex);
let thinking = '';
let result = data.data.answer;
if (match) {
thinking = match[1].trim();
result = match[2].trim();
}
// HTML
let combinedContent = '';
// if (thinking) {
//
// combinedContent += `<span class="thinking-content">${thinking}</span><br>`;
// }
combinedContent += `<span class="result-content">${result}</span>`;
//
messages.value.push({ messages.value.push({
content: data.answer,
content: combinedContent,
sender: 'bot', sender: 'bot',
timestamp: new Date() timestamp: new Date()
}) })
scrollToBottom() scrollToBottom()
} catch (error) { } catch (error) {
console.error('API请求失败:', error) console.error('API请求失败:', error)
@ -156,7 +202,6 @@ const adjustInputHeight = () => {
} }
// //
watch(inputMessage, adjustInputHeight) watch(inputMessage, adjustInputHeight)
// //
onMounted(async () => { onMounted(async () => {
// token // token
@ -168,7 +213,6 @@ onMounted(async () => {
console.error('Token 验证失败,请重新登录') console.error('Token 验证失败,请重新登录')
} }
}) })
</script> </script>
<template> <template>
<!-- 聊天容器 --> <!-- 聊天容器 -->
@ -183,15 +227,8 @@ onMounted(async () => {
<img src="/src/assets/img/avatar/超级云脑按钮.png" alt="Bot Avatar"> <img src="/src/assets/img/avatar/超级云脑按钮.png" alt="Bot Avatar">
</div> </div>
<div class="message-bubble"> <div class="message-bubble">
<div class="message-content">
<!-- 显示加载动画 -->
<span v-if="message.isLoading">
{{ message.content }}
<el-icon class="is-loading">
<Loading />
</el-icon>
</span>
<span v-else>{{ message.content }}</span>
<div class="message-content" v-html="message.content">
</div> </div>
<div class="message-time">{{ formatTime(message.timestamp) }}</div> <div class="message-time">{{ formatTime(message.timestamp) }}</div>
</div> </div>
@ -232,7 +269,17 @@ onMounted(async () => {
</div> </div>
</template> </template>
<style scoped> <style scoped>
/* 使用更具体的选择器 */
.message-bubble .message-content .thinking-content {
color: #c8c4c4;
font-style: italic;
}
/* 其他样式保持不变 */
.message-item.bot {
justify-content: flex-start; /* 让机器人消息靠左对齐 */
}
.chat-container { .chat-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -437,8 +484,10 @@ onMounted(async () => {
} }
.overlay-content { .overlay-content {
font-size: 36px;
font-weight: bold;
color: #f60707;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
text-align: center;
} }
</style> </style>
Loading…
Cancel
Save