diff --git a/.eslintrc.js b/off.eslintrc.js similarity index 100% rename from .eslintrc.js rename to off.eslintrc.js diff --git a/package.json b/package.json index b95bf71..48192b7 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "clipboard": "2.0.8", "core-js": "3.25.3", "dayjs": "^1.11.8", + "dompurify": "^3.2.6", "echarts": "5.4.0", "echarts-gl": "^2.0.9", "element-china-area-data": "^6.1.0", @@ -63,6 +64,7 @@ "jspdf": "^2.5.2", "lodash": "^4.17.21", "mapv-three": "^1.0.18", + "markdown-it": "^13.0.2", "marked": "^4.3.0", "nprogress": "0.2.0", "print-js": "^1.6.0", diff --git a/src/api/aiChat/ai_index.js b/src/api/aiChat/ai_index.js index 357a9de..a365fe7 100644 --- a/src/api/aiChat/ai_index.js +++ b/src/api/aiChat/ai_index.js @@ -21,8 +21,9 @@ export const getHistory = ({ limit } + // 如果有beforeId参数,添加到请求中(后端参数名为firstId) if (beforeId) { - params.beforeId = beforeId + params.firstId = beforeId } return request({ diff --git a/src/assets/ai/AI.png b/src/assets/ai/AI.png new file mode 100644 index 0000000..1eea8d3 Binary files /dev/null and b/src/assets/ai/AI.png differ diff --git a/src/assets/ai/good.svg b/src/assets/ai/good.svg new file mode 100644 index 0000000..b676949 --- /dev/null +++ b/src/assets/ai/good.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/ai/tread.svg b/src/assets/ai/tread.svg new file mode 100644 index 0000000..9ea37b3 --- /dev/null +++ b/src/assets/ai/tread.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/ai/yonghu.png b/src/assets/ai/yonghu.png new file mode 100644 index 0000000..f03cd4b Binary files /dev/null and b/src/assets/ai/yonghu.png differ diff --git a/src/layout/components/Aichat/ChatPopup.vue b/src/layout/components/Aichat/ChatPopup.vue index 929ac32..d305626 100644 --- a/src/layout/components/Aichat/ChatPopup.vue +++ b/src/layout/components/Aichat/ChatPopup.vue @@ -1,65 +1,82 @@ - + diff --git a/src/layout/index.vue b/src/layout/index.vue index 1b091d8..17e87ae 100644 --- a/src/layout/index.vue +++ b/src/layout/index.vue @@ -21,10 +21,11 @@
- + AI +
- + diff --git a/src/utils/ai_request.js b/src/utils/ai_request.js index b4cba65..43f9947 100644 --- a/src/utils/ai_request.js +++ b/src/utils/ai_request.js @@ -1,11 +1,11 @@ import axios from 'axios' -import { getToken } from './auth' +import { getTokenKeySessionStorage } from './auth' import { useRouter } from 'vue-router' import { showToast } from '@/utils/toast' // 请替换为你的Toast组件 // 创建axios实例 const service = axios.create({ - baseURL: process.env.VUE_APP_API_BASE_URL || 'http://localhost:8088/aitutor/aichat', + baseURL: process.env.VUE_APP_API_BASE_URL || 'http://localhost:8088', timeout: 15000, headers: { 'Content-Type': 'application/json' @@ -16,7 +16,7 @@ const service = axios.create({ service.interceptors.request.use( config => { // 从本地存储获取token - const token = getToken() + const token = getTokenKeySessionStorage() if (token) { config.headers.Authorization = `Bearer ${token}` } diff --git a/src/utils/ai_stream.js b/src/utils/ai_stream.js index a60941b..a3c19ca 100644 --- a/src/utils/ai_stream.js +++ b/src/utils/ai_stream.js @@ -1,7 +1,7 @@ -import { getToken } from '@/utils/auth' +import { getTokenKeySessionStorage } from "@/utils/auth"; // 使用环境变量配置基础URL -const BASE_URL = process.env.VUE_APP_API_BASE_URL || 'http://localhost:8088' +const BASE_URL = process.env.VUE_APP_API_BASE_URL || "http://localhost:8088"; /** * 创建聊天流式连接 @@ -13,54 +13,55 @@ const BASE_URL = process.env.VUE_APP_API_BASE_URL || 'http://localhost:8088' * @returns {Object} 包含stream和cancel方法的对象 */ export function createChatStream(params) { - const requestId = `req-${Date.now()}-${Math.random().toString(36).slice(2, 8)}` - const url = `${BASE_URL}/aitutor/aichat/stream` - const token = getToken() + const requestId = `req-${Date.now()}-${Math.random() + .toString(36) + .slice(2, 8)}`; + const url = `${BASE_URL}/aitutor/aichat/stream`; + const token = getTokenKeySessionStorage(); - if (!token) { - throw new Error('请先登录') + if (!token) { + throw new Error("请先登录"); + } + + const controller = new AbortController(); + + const fetchPromise = fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + "X-Request-ID": requestId, + }, + body: JSON.stringify({ + query: params.prompt, + user_id: params.userId, + user_name: params.userName, + user_token: params.user_token || "123", + user_role: "student", + conversation_id: params.conversationId || null, + }), + signal: controller.signal, + }).then((response) => { + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + if (!response.body) { + throw new Error("Response body is null"); } - - const controller = new AbortController() - - const fetchPromise = fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}`, - 'X-Request-ID': requestId - }, - body: JSON.stringify({ - query: params.prompt, - user_id: params.userId, - user_name: params.userName, - user_token: params.user_token || '123', - user_role: 'student', - conversation_id: params.conversationId || null, - }), - signal: controller.signal - }) - .then(response => { - if (!response.ok) { - throw new Error(`HTTP ${response.status}`) - } - if (!response.body) { - throw new Error('Response body is null') - } - return { - reader: response.body.getReader(), - decoder: new TextDecoder('utf-8') - } - }) - return { - stream: fetchPromise, - cancel: (reason) => { - if (!controller.signal.aborted) { - controller.abort(reason) - } - } - } + reader: response.body.getReader(), + decoder: new TextDecoder("utf-8"), + }; + }); + + return { + stream: fetchPromise, + cancel: (reason) => { + if (!controller.signal.aborted) { + controller.abort(reason); + } + }, + }; } /** @@ -71,40 +72,44 @@ export function createChatStream(params) { * @param {Function} onError 错误回调 * @param {Function} onComplete 完成回调 */ -export async function processStream(reader, decoder, { onMessage, onError, onComplete }) { - let buffer = '' +export async function processStream( + reader, + decoder, + { onMessage, onError, onComplete } +) { + let buffer = ""; - try { - while (true) { - const { done, value } = await reader.read() - if (done) break + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; - buffer += decoder.decode(value, { stream: true }) - const lines = buffer.split('\n') - buffer = lines.pop() || '' + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split("\n"); + buffer = lines.pop() || ""; - for (const line of lines) { - if (!line.trim()) continue + for (const line of lines) { + if (!line.trim()) continue; - try { - const data = JSON.parse(line) - if (typeof onMessage === 'function') { - onMessage(data) - } - } catch (e) { - console.warn('解析消息失败:', line, e) - } - } + try { + const data = JSON.parse(line); + if (typeof onMessage === "function") { + onMessage(data); + } + } catch (e) { + console.warn("解析消息失败:", line, e); } - - if (typeof onComplete === 'function') { - onComplete() - } - } catch (error) { - if (error.name !== 'AbortError' && typeof onError === 'function') { - onError(error) - } - } finally { - reader.releaseLock() + } } -} \ No newline at end of file + + if (typeof onComplete === "function") { + onComplete(); + } + } catch (error) { + if (error.name !== "AbortError" && typeof onError === "function") { + onError(error); + } + } finally { + reader.releaseLock(); + } +}