diff --git a/package.json b/package.json index f5270c5..ad450c7 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,6 @@ }, "dependencies": { "@kangc/v-md-editor": "^2.3.15", - "axios": "^1.4.0", "element-plus": "^2.3.4", "sass": "^1.62.1", "sass-loader": "^13.2.2", diff --git a/src/App.vue b/src/App.vue index d4d9d2b..7ca58a3 100644 --- a/src/App.vue +++ b/src/App.vue @@ -19,7 +19,8 @@ export default { return { showSettings: false, setting: { - model: new ChatGPT() + model: new ChatGPT(), + stream: true }, } }, diff --git a/src/components/chat.vue b/src/components/chat.vue index fd0edcd..b2f98a1 100644 --- a/src/components/chat.vue +++ b/src/components/chat.vue @@ -17,7 +17,7 @@ - + 发送 @@ -46,7 +46,7 @@ export default { methods: { handleSend() { if (!!this.inputText) { - const ai = this.settings.model + const ai = this.config.model const myMsg = { avatar: USER_AVATAR, name: "我", @@ -54,6 +54,7 @@ export default { msg: this.inputText, chatType: 0, //信息类型,0文字,1图片 uid: "me", //uid + role: "user" } this.chatHistory.push(myMsg) this.loading = true @@ -61,63 +62,58 @@ export default { avatar: ai.avatar, name: ai.name, time: dayjs().format("YYYY-MM-DD HH:mm:ss"), - msg: '', + msg: "", chatType: 0, //信息类型,0文字,1图片 uid: "ai", //uid + role: "assistant" } this.chatHistory.push(aiMsg) - ai.chat(this.chatHistory).finally(() => this.loading = false) + if (this.config.stream) { + ai.chatStream(this.chatHistory).then(reader => this.streamOutput(reader, this.chatHistory.at(-1))).finally(() => this.loading = false) + } else { + ai.chat(this.chatHistory).then(reply => { + const decodeArr = reply.split("") + decodeArr.forEach(e => this.chatHistory.at(-1).msg += e) + }).finally(() => this.loading = false) + } } else { this.$message.error("请不要发送空消息!") } }, - readStream(reader, _this, currentResLocation, type) { + streamOutput(reader, chat) { return reader.read().then(({done, value}) => { if (done) { return; } - if (!_this.chatList[currentResLocation].reminder) { - _this.chatList[currentResLocation].reminder = ""; + if (!chat.reminder) { + chat.reminder = "" } - let decoded = new TextDecoder().decode(value); - decoded = _this.chatList[currentResLocation].reminder + decoded; - let decodedArray = decoded.split("data: "); + let decode = new TextDecoder().decode(value) + decode = chat.reminder + decode + let decodedArray = decode.split("data: "); let longstr = ""; decodedArray.forEach(decoded => { + decoded = decoded.trim(); try { - decoded = decoded.trim(); - if (longstr == "") { - JSON.parse(decoded); - } else { + if (longstr != "") { decoded = longstr + decoded; longstr = ""; - JSON.parse(decoded); } } catch (e) { longstr = decoded; decoded = ""; } - if (decoded !== "") { - if (decoded.trim() === "[DONE]") { - return; - } else { - const choices = JSON.parse(decoded).choices - if (choices && choices.length > 0) { - if (type === "chat") { - const response = choices[0].delta.content ? choices[0].delta.content : ""; - _this.chatList[currentResLocation].msg = _this.chatList[currentResLocation].msg + response - _this.scrollBottom(); - } else { - const response = choices[0].text; - _this.chatList[currentResLocation].msg = _this.chatList[currentResLocation].msg + response - } - } + if (!!decoded && decoded !== "[DONE]") { + const choices = JSON.parse(decoded).choices + if (choices?.length > 0) { + const response = choices[0].delta.content || ""; + chat.msg += response } } }) - return this.readStream(reader, _this, currentResLocation, type); - }); - }, + return this.streamOutput(reader, chat) + }) + } } } diff --git a/src/components/chatContent.vue b/src/components/chatContent.vue index b40848c..f1ec4e2 100644 --- a/src/components/chatContent.vue +++ b/src/components/chatContent.vue @@ -188,5 +188,9 @@ export default { } } } + + :deep(.vuepress-markdown-body) { + padding: 2px 0 !important; + } } diff --git a/src/components/chatInput.vue b/src/components/chatInput.vue index 1b5f3ac..2b04c22 100644 --- a/src/components/chatInput.vue +++ b/src/components/chatInput.vue @@ -1,7 +1,8 @@ @@ -17,6 +18,15 @@ export default { text: "" } }, + methods: { + handleShortKey(e) { + if (e.ctrlKey && e.keyCode == 13) { + this.$emit('update:modelValue', this.text) + this.text = "" + this.$emit('enter') + } + } + } } diff --git a/src/utils/axios.js b/src/utils/axios.js index a10c119..b4d07cc 100644 --- a/src/utils/axios.js +++ b/src/utils/axios.js @@ -1,11 +1,5 @@ -import axios from "axios"; - -const ins = axios.create({ - headers: { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST', - 'Access-Control-Allow-Headers': 'Content-Type, Access-Control-Allow-Headers, Authorization' - }, - responseType: 'json' -}) +const ins = { + post: (url, body, config) => fetch(url, {...config, method: "POST", body}), + get: (url, config) => fetch(url, {...config, method: "GET"}), +} export default ins diff --git a/src/utils/models.js b/src/utils/models.js index 1193602..9ecd4dc 100644 --- a/src/utils/models.js +++ b/src/utils/models.js @@ -1,5 +1,5 @@ import axios from "./axios"; -import {AI_AVATAR} from "./env"; +import {AI_AVATAR, OPEN_AI_KEY} from "./env"; class BaseModel { constructor(props) { @@ -14,16 +14,33 @@ export class ChatGPT extends BaseModel { constructor() { super({ - avatar: AI_AVATAR, - name: 'ChatGPT', - id: "gpt-3.5-turbo", - desc: "ChatGPT-3.5所基于的模型" + avatar: AI_AVATAR, name: 'ChatGPT', id: "gpt-3.5-turbo", desc: "ChatGPT-3.5所基于的模型", }); + this.apiKey = OPEN_AI_KEY } - async chat(history, callback) { - return await axios.post(ChatGPT.base + "/v1/chat/completions") + setApiKey(key) { + this.apiKey = key } + + async chat(history) { + const messages = history.map(e => ({role: e.role, content: e.msg})) + return await axios.post(ChatGPT.base + "/v1/chat/completions", JSON.stringify({messages, model: this.id}), { + headers: { + Authorization: 'Bearer ' + this.apiKey, "Content-Type": "application/json", Accept: "application/json", + }, + }).then(res => res.json()).then(data => data?.choices?.[0]?.message?.content || "key无效或网络波动,请重新尝试"); + } + + async chatStream(history) { + const messages = history.map(e => ({role: e.role, content: e.msg})) + return await axios.post(ChatGPT.base + "/v1/chat/completions", JSON.stringify({messages, model: this.id, stream: true}), { + headers: { + Authorization: 'Bearer ' + this.apiKey, "Content-Type": "application/json", Accept: "application/json", + }, + }).then(res => res?.body?.getReader()); + } + } export class ChatGLM extends BaseModel { @@ -31,10 +48,7 @@ export class ChatGLM extends BaseModel { constructor() { super({ - avatar: AI_AVATAR, - name: 'ChatGLM', - id: "chatglm-6b", - desc: "ChatGLM-6B所基于的模型" + avatar: AI_AVATAR, name: 'ChatGLM', id: "chatglm-6b", desc: "ChatGLM-6B所基于的模型" }); }