mirror of https://gitee.com/godoos/godoos.git
2 changed files with 1001 additions and 704 deletions
File diff suppressed because it is too large
@ -0,0 +1,325 @@ |
|||
<script setup lang="ts"> |
|||
import { useAiChatStore } from "@/stores/aichat"; |
|||
import { useModelStore } from "@/stores/model"; |
|||
import { t } from "@/i18n"; |
|||
import { notifyError } from "@/util/msg.ts"; |
|||
import { ElScrollbar } from "element-plus"; |
|||
import { getSystemConfig } from "@/system/config"; |
|||
import { Vue3Lottie } from "vue3-lottie"; |
|||
import { BrowserWindow, useSystem } from "@/system"; |
|||
import { isMobileDevice } from "@/util/device"; |
|||
const chatStore = useAiChatStore(); |
|||
const modelStore = useModelStore(); |
|||
const isPadding = ref(false); //是否发送中 |
|||
const webSearch = ref(false); |
|||
const imageInput: any = ref(null); |
|||
const win: any = inject<BrowserWindow>("browserWindow"); |
|||
let imageData = ref(""); |
|||
let fileContent = ref(""); |
|||
let fileName = ref(""); |
|||
const messageContainerRef = ref<InstanceType<typeof ElScrollbar>>(); |
|||
const messageInnerRef = ref<HTMLDivElement>(); |
|||
// User Input Message |
|||
const userMessage = ref(""); |
|||
const knowledgeId = ref(0) |
|||
const system = useSystem() |
|||
const messageList: any = ref([]) |
|||
const chatInfo: any = ref({}) |
|||
const activeId: any = ref(0) |
|||
const promptMessage = computed(() => { |
|||
return [ |
|||
{ |
|||
content: chatInfo.value.prompt || "", |
|||
chatType: "chat", |
|||
chatId: activeId.value, |
|||
role: "system", |
|||
id: Date.now(), |
|||
createAt: Date.now(), |
|||
}, |
|||
]; |
|||
}); |
|||
onMounted(async () => { |
|||
knowledgeId.value = win?.config?.knowledgeId || 0 |
|||
await chatStore.initChat(knowledgeId.value) |
|||
const res = await chatStore.getActiveChat() |
|||
messageList.value = res.messageList.value |
|||
chatInfo.value = res.chatInfo.value |
|||
activeId.value = res?.chatInfo?.value?.id || 0 |
|||
console.log(messageList.value, chatInfo.value, activeId.value) |
|||
//await aiStore.initChat() |
|||
}); |
|||
const requestMessages = computed(() => { |
|||
const contextLen = modelStore.chatConfig.chat.contextLength; |
|||
//console.log(contextLen) |
|||
if (messageList.value.length <= contextLen) { |
|||
return [...promptMessage.value, ...messageList.value]; |
|||
} else { |
|||
// 截取最新的10条信息 |
|||
const slicedMessages = messageList.value.slice(-contextLen); |
|||
return [...promptMessage.value, ...slicedMessages]; |
|||
} |
|||
}); |
|||
// async function addKnowledge(){ |
|||
// const knowledgeId = win?.config?.knowledgeId || 0; |
|||
// let msg = userMessage.value |
|||
// if (knowledgeId > 0) { |
|||
// const askData: any = { |
|||
// id: knowledgeId, |
|||
// input: userMessage.value, |
|||
// } |
|||
// const config = getSystemConfig() |
|||
// const postData: any = { |
|||
// method: "POST", |
|||
// body: JSON.stringify(askData), |
|||
// }; |
|||
// const completion = await fetch(config.apiUrl + '/ai/askknowledge', postData); |
|||
// if (!completion.ok) { |
|||
// const errorData = await completion.json(); |
|||
// //console.log(errorData) |
|||
// notifyError(errorData.message); |
|||
// isPadding.value = false; |
|||
// return; |
|||
// } |
|||
// const res = await completion.json(); |
|||
// console.log(res) |
|||
// // let prompt = await chatStore.getPrompt("knowledge") |
|||
// // if (prompt == '') { |
|||
// // notifyError("知识库prompt为空") |
|||
// // return |
|||
// // } |
|||
// if (res && res.data.length > 0) { |
|||
// let context: string = ""; |
|||
// res.data.forEach((item: any) => { |
|||
// context += "- " + item.content + "\n"; |
|||
// }) |
|||
// //prompt = prompt.replace("{content}", context) |
|||
// msg = `请对\n${context}\n的内容进行分析,给出对用户输入的回答:${userMessage.value} ` |
|||
// } |
|||
// } |
|||
// return msg |
|||
// } |
|||
const sendMessage = async () => { |
|||
if (chatStore.activeId < 1) { |
|||
notifyError(t("aichat.selectModel")); |
|||
return; |
|||
} |
|||
if (userMessage.value) { |
|||
// Add the message to the list |
|||
if (isPadding.value === true) return; |
|||
let saveMessage: any = { |
|||
content: userMessage.value, |
|||
chatId: activeId.value, |
|||
role: "user", |
|||
id: Date.now(), |
|||
createdAt: Date.now(), |
|||
}; |
|||
messageList.value.push(saveMessage); |
|||
|
|||
await chatStore.addMessages(activeId.value, saveMessage); |
|||
|
|||
// Clear the input |
|||
userMessage.value = ""; |
|||
|
|||
// Create a completion |
|||
isPadding.value = true; |
|||
await createCompletion(); |
|||
} |
|||
}; |
|||
|
|||
const createCompletion = async () => { |
|||
try { |
|||
const config = getSystemConfig() |
|||
const messageId = Date.now(); |
|||
const saveMessage: any = { |
|||
content: "", |
|||
role: "assistant", |
|||
chatType: "chat", |
|||
chatId: activeId.value, |
|||
id: messageId, |
|||
createdAt: messageId, |
|||
}; |
|||
|
|||
const chatConfig = modelStore.chatConfig.chat; |
|||
const knowledgeId = chatInfo.value.knowledgeId * 1; |
|||
let postMsg: any = { |
|||
messages: requestMessages.value, |
|||
model: chatInfo.value.model, |
|||
engine: chatInfo.value.engine, |
|||
stream: false, |
|||
webSearch: webSearch.value, |
|||
fileContent: fileContent.value, |
|||
fileName: fileName.value, |
|||
options: chatConfig, |
|||
knowledgeId: knowledgeId, |
|||
}; |
|||
if (imageData.value != "") { |
|||
const img2txtModel = await modelStore.getModel("img2txt"); |
|||
const usermsg = messageList.value[messageList.value.length - 1]; |
|||
postMsg = { |
|||
model: img2txtModel.model, |
|||
//"prompt":userMessage.value, |
|||
engine: img2txtModel.info.engine, |
|||
stream: false, |
|||
webSearch: false, |
|||
options: chatConfig, |
|||
messages: [ |
|||
{ |
|||
role: "user", |
|||
content: usermsg.content, |
|||
images: [imageData.value], |
|||
}, |
|||
], |
|||
knowledgeId: knowledgeId, |
|||
}; |
|||
} |
|||
|
|||
const postData: any = { |
|||
method: "POST", |
|||
body: JSON.stringify(postMsg), |
|||
}; |
|||
//console.log(postData) |
|||
const completion = await fetch(config.apiUrl + '/ai/chat', postData); |
|||
//const completion:any = await modelStore.getModel(postData) |
|||
imageData.value = ""; |
|||
fileContent.value = ""; |
|||
fileName.value = ""; |
|||
if (!completion.ok) { |
|||
const errorData = await completion.json(); |
|||
notifyError(errorData.error.message); |
|||
isPadding.value = false; |
|||
return; |
|||
} |
|||
const res = await completion.json(); |
|||
console.log(res) |
|||
if (res && res.choices && res.choices.length > 0) { |
|||
if (res.choices[0].message.content) { |
|||
const msg = res.choices[0].message.content; |
|||
saveMessage.content = msg; |
|||
if (res.documents && res.documents.length > 0) { |
|||
saveMessage.doc = res.documents; |
|||
} |
|||
if (res.web_search && res.web_search.length > 0) { |
|||
saveMessage.web_search = res.web_search; |
|||
} |
|||
messageList.value.push(saveMessage); |
|||
await chatStore.addMessages(activeId.value, saveMessage); |
|||
} |
|||
} |
|||
isPadding.value = false; |
|||
} catch (error: any) { |
|||
isPadding.value = false; |
|||
notifyError(error.message); |
|||
} |
|||
}; |
|||
const scrollToBottom = () => { |
|||
nextTick(() => { |
|||
if (messageContainerRef && messageContainerRef.value) { |
|||
messageContainerRef.value!.setScrollTop( |
|||
messageInnerRef.value!.clientHeight |
|||
); |
|||
} |
|||
}); |
|||
}; |
|||
watch( |
|||
() => messageList, |
|||
() => { |
|||
scrollToBottom(); |
|||
}, |
|||
{ |
|||
deep: true, |
|||
} |
|||
); |
|||
const handleKeydown = (e: any) => { |
|||
if (e.key === "Enter" && (e.altKey || e.shiftKey)) { |
|||
// 当同时按下 alt或者shift 和 enter 时,插入一个换行符 |
|||
e.preventDefault(); |
|||
userMessage.value += "\n"; |
|||
} else if (e.key === "Enter") { |
|||
// 当只按下 enter 时,发送消息 |
|||
e.preventDefault(); |
|||
sendMessage(); |
|||
} |
|||
}; |
|||
const selectImage = async () => { |
|||
imageInput.value.click(); |
|||
}; |
|||
const uploadImage = async (event: any) => { |
|||
const file = event.target.files[0]; |
|||
if (!file) { |
|||
return; |
|||
} |
|||
//console.log(file) |
|||
if (file.type.startsWith('image/')) { |
|||
const img2txtModel = await modelStore.getModel("img2txt"); |
|||
if (!img2txtModel) { |
|||
notifyError(t("aichat.notEyeModel")); |
|||
return; |
|||
} |
|||
} |
|||
const reader = new FileReader(); |
|||
reader.onload = (e: any) => { |
|||
const fileData = e.target.result.split(",")[1]; |
|||
if (file.type.startsWith('image/')) { |
|||
imageData.value = fileData; |
|||
} else { |
|||
fileContent.value = fileData; |
|||
fileName.value = file.name; |
|||
} |
|||
//console.log(fileContent.value) |
|||
}; |
|||
|
|||
reader.readAsDataURL(file); |
|||
}; |
|||
|
|||
</script> |
|||
<template> |
|||
<el-dialog v-model="chatStore.showInfo" width="600" append-to-body :fullscreen="isMobileDevice() ? true : false"> |
|||
<ai-chat-info /> |
|||
</el-dialog> |
|||
<div class="chat-bot"> |
|||
<div class="messsage-area"> |
|||
<el-scrollbar v-if="messageList.length > 0" class="message-container" ref="messageContainerRef"> |
|||
<div ref="messageInnerRef"> |
|||
<ai-chat-message v-for="message in messageList" :key="message.messageId" :content="message.content" |
|||
:link="message.link" :role="message.role" :createdAt="message.createdAt" |
|||
:doc="message.doc || []" :web_search="message.web_search || []" :system="system" /> |
|||
</div> |
|||
</el-scrollbar> |
|||
<div class="no-message-container" v-else> |
|||
<Vue3Lottie animationLink="/bot/chat.json" :height="420" :width="420" /> |
|||
</div> |
|||
</div> |
|||
<div class="input-area"> |
|||
<div class="input-panel"> |
|||
<el-row :gutter="24" style="border-bottom: none;"> |
|||
<el-col :span="2"> |
|||
<el-button class="file-btn" @click="selectImage" size="large" icon="Paperclip" circle |
|||
:class="{ 'selected-image': imageData != '' || fileContent != '' }" /> |
|||
<input type="file" ref="imageInput" |
|||
accept="image/*,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/pdf" |
|||
style="display: none" @change="uploadImage" /> |
|||
</el-col> |
|||
<el-col :span="2"> |
|||
<el-button class="websearch-btn" @click="webSearch = !webSearch" size="large" |
|||
icon="ChromeFilled" circle :type="webSearch ? 'primary' : 'default'" /> |
|||
</el-col> |
|||
<el-col :span="17"> |
|||
<el-input v-model="userMessage" :placeholder="t('aichat.askme')" size="large" clearable |
|||
@keydown="handleKeydown" :autofocus="isMobileDevice() ? false : true" |
|||
class="ai-input-area" /> |
|||
</el-col> |
|||
<el-col :span="2"> |
|||
<el-button v-if="!isPadding" @click="sendMessage" icon="Promotion" type="info" size="large" |
|||
circle /> |
|||
<el-button type="primary" size="large" v-else loading-icon="Eleme" icon="Loading" loading |
|||
circle /> |
|||
</el-col> |
|||
</el-row> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<style scoped lang="scss"> |
|||
@import "@/assets/chat.scss"; |
|||
</style> |
Loading…
Reference in new issue