Browse Source

change chat ai

master
godo 6 months ago
parent
commit
214ff84f71
  1. 2
      README.md
  2. 1
      frontend/components.d.ts
  3. 2
      frontend/src/assets/chat.scss
  4. 93
      frontend/src/components/localchat/AiChatInfo.vue
  5. 145
      frontend/src/components/localchat/AiChatLeft.vue
  6. 76
      frontend/src/components/localchat/AiChatMain.vue
  7. 41
      frontend/src/stores/aichat.ts
  8. 2
      packages/kanban/package.json

2
README.md

@ -24,6 +24,8 @@
- 新增文件密码箱(系统设置里),可根据不同文件进行加密存储
- 美化日程提醒弹窗
- 修复word格式问题以及导出名字不对
- markdown新增ai优化/续写/纠错/翻译/总结,生成大纲,根据大纲一键创建文章
- 更改文档存储方式,支持选择文件夹
## 🏭 第三阶段目标(十二月底发布)
1. **文档处理与Markdown智能升级**

1
frontend/components.d.ts

@ -76,6 +76,7 @@ declare module 'vue' {
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElImage: typeof import('element-plus/es')['ElImage']
ElInput: typeof import('element-plus/es')['ElInput']

2
frontend/src/assets/chat.scss

@ -4,7 +4,7 @@
display: flex;
flex-direction: column;
position: relative;
background: #fff;
.messsage-area {
display: flex;
flex-direction: column;

93
frontend/src/components/localchat/AiChatInfo.vue

@ -1,58 +1,75 @@
<script setup lang="ts">
// import { useKnowStore } from "@/stores/know.ts";
// import {getModelList,getPromptList} from "@/stores/config";
import { onMounted,ref } from "vue";
import { useAiChatStore } from "@/stores/aichat";
import { t } from "@/i18n/index";
// const knowStore = useKnowStore();
const props = defineProps<{
dataInfo: any
}>()
const modelList:any = ref([])
const knowList:any = ref([])
const promptList:any = ref([])
const emit = defineEmits(['saveFn'])
function changPrompt(promptName:string) {
const promptData:any = promptList.value.find((item:any) => {
return item.name == promptName;
import { notifyInfo,notifySuccess } from "@/util/msg.ts";
const chatStore = useAiChatStore();
function changPrompt(id:number) {
const promptData:any = chatStore.promptList.find((item:any) => {
return item.id == id;
});
console.log(promptData)
if(promptData) {
props.dataInfo.prompt = promptData.prompt;
chatStore.editInfo.prompt = promptData.prompt;
}
}
function saveData(){
emit('saveFn', props.dataInfo)
}
onMounted(async () => {
// modelList.value = await getModelList('chat')
//knowList.value = await knowStore.getKnowAll()
// promptList.value = await getPromptList('chat')
})
const changeInfo = async () => {
const info = chatStore.editInfo;
if (!info.title) {
notifyInfo(t("chat.inputTitle"));
return;
}
if (!info.model) {
notifyInfo(t("chat.selectModel"));
return;
}
if (!info.prompt) {
info.prompt = "";
}
delete info.id;
if (chatStore.isEditor) {
//console.log(info);
await chatStore.updateChat(info, chatStore.activeId);
chatStore.chatInfo = info;
notifySuccess(t("chat.editsuccess"));
} else {
const promptData = {
prompt: info.prompt,
promptName: info.promptName,
};
await chatStore.addChat(info.title, info.model, promptData, info.kid);
notifySuccess(t("chat.addsuccess"));
}
await chatStore.getActiveChat();
chatStore.showInfo = false;
};
</script>
<template>
<el-form label-width="150px" style="margin-top: 12px">
<el-form-item :label="t('common.title')">
<el-input
v-model="props.dataInfo.title"
:placeholder="t('chat.inputTitle')"
v-model="chatStore.editInfo.title"
:placeholder="t('aichat.inputTitle')"
prefix-icon="Notification"
clearable
:autofocus="true"
></el-input>
</el-form-item>
<el-form-item :label="t('chat.model')">
<el-select v-model="props.dataInfo.model">
<el-form-item :label="t('aichat.model')">
<el-select v-model="chatStore.editInfo.model">
<el-option
v-for="(item, key) in modelList"
v-for="(item, key) in chatStore.modelList"
:key="key"
:label="item.model"
:value="item.model"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('chat.refknow')">
<!-- <el-form-item :label="t('chat.refknow')">
<el-select
v-model="props.dataInfo.kid"
v-model="chatStore.editInfo.kid"
:clearable="true"
:filterable="true"
>
@ -63,17 +80,17 @@ onMounted(async () => {
:value="item.uuid"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('chat.role')">
</el-form-item> -->
<el-form-item :label="t('aichat.role')">
<el-select
v-model="props.dataInfo.promptName"
v-model="chatStore.editInfo.promptId"
@change="changPrompt"
>
<el-option
v-for="(item, key) in promptList"
v-for="(item, key) in chatStore.promptList"
:key="key"
:label="item.name"
:value="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
@ -81,12 +98,12 @@ onMounted(async () => {
<el-form-item>
<el-input
type="textarea"
v-model="props.dataInfo.prompt"
v-model="chatStore.editInfo.prompt"
:rows="6"
></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveData">{{
<el-button type="primary" @click="changeInfo">{{
t("common.confim")
}}</el-button>
</el-form-item>

145
frontend/src/components/localchat/AiChatLeft.vue

@ -1,34 +1,121 @@
<script setup lang="ts">
import { useAiChatStore } from "@/stores/aichat";
import { Search } from "@element-plus/icons-vue";
// import { t } from "@/i18n";
// import { notifyInfo,notifySuccess } from "@/util/msg.ts";
const chatStore = useAiChatStore();
const showBox = (flag: any) => {
chatStore.isEditor = flag;
if (flag === true) {
chatStore.editInfo = toRaw(chatStore.chatInfo);
} else {
chatStore.editInfo = {
title: "",
model: "",
prompt: "",
promptName: "",
};
}
chatStore.showInfo = true;
};
</script>
<template>
<el-scrollbar>
<el-space direction="vertical">
<el-card
v-for="(item, key) in chatStore.chatList"
:key="key"
class="box-card"
style="width: 190px"
:shadow="chatStore.activeId == item.id ? 'always' : 'hover'"
>
<el-row type="flex" justify="space-between">
<el-button
type="info"
text
@click.stop="chatStore.setActiveId(item.id)"
size="small"
>
{{ item.title }}
</el-button>
<el-button
icon="Delete"
size="small"
@click.stop="chatStore.deleteChat(item.id)"
circle
></el-button>
</el-row>
</el-card>
</el-space>
</el-scrollbar>
</template>
<el-dialog v-model="chatStore.showInfo" width="600" append-to-body>
<ai-chat-info />
</el-dialog>
<el-scrollbar>
<el-header class="search">
<el-input placeholder="搜索" :prefix-icon="Search" class="search-input" v-model="chatStore.searchInput" />
<button class="add-chat" @click="showBox(false)">
<el-icon>
<Plus />
</el-icon>
</button>
</el-header>
<div v-for="(item, key) in chatStore.chatList" :key="key">
<div class="list-item">
<el-row justify="space-around">
<el-col :span="17" @click.stop="chatStore.setActiveId(item.id)" class="chat-title">
{{ item.title }}
</el-col>
<el-col :span="7" class="iconlist">
<el-icon size="15">
<Edit />
</el-icon>
<el-icon size="15" @click.stop="chatStore.deleteChat(item.id)">
<Delete />
</el-icon>
</el-col>
</el-row>
</div>
</div>
</el-scrollbar>
</template>
<style scoped lang="scss">
.search {
display: flex;
align-items: center;
justify-content: space-evenly;
width: 100%;
height: 50px;
padding: 0;
padding-right: 10px;
-webkit-app-region: drag;
border-bottom: 1px solid #e8e8e8;
border-left: 1px solid #e8e8e8;
}
.add-chat {
width: 40px;
height: 30px;
border: none;
border-radius: 4px;
background-color: #f0f0f0;
}
.search-input {
width: calc(100% - 20px);
margin: 10px;
height: 30px;
font-size: 0.7rem;
-webkit-app-region: no-drag;
--el-input-placeholder-color: #bfbfbf !important;
--el-input-icon-color: #bfbfbf !important;
}
.list-item {
width: 95%;
height: 60px;
transition: all 0.5s;
margin: 0 auto;
border-radius: 4px;
margin-bottom: 5px;
overflow: hidden;
margin-top: 5px;
background-color: #fff;
}
.list-item:hover,.list-item .active {
background-color: #e8f3ff;
}
.chat-title {
display: flex;
align-items: center;
justify-content: space-between;
height: 60px;
padding-left: 10px;
}
.iconlist {
display: flex;
justify-content: space-between;
align-items: center;
padding-right: 10px;
}
.iconlist .el-icon{
cursor: pointer;
}
</style>

76
frontend/src/components/localchat/AiChatMain.vue

@ -9,6 +9,7 @@ import { Vue3Lottie } from "vue3-lottie";
const chatStore = useAiChatStore();
const modelStore = useModelStore();
const isPadding = ref(false); //
const imageInput: any = ref(null);
let imageData = ref("");
const messageContainerRef = ref<InstanceType<typeof ElScrollbar>>();
@ -77,7 +78,7 @@ const createCompletion = async () => {
id: messageId,
createdAt: messageId,
};
const chatConfig = modelStore.chatConfig.chat;
let postMsg: any = {
messages: requestMessages.value,
@ -106,7 +107,7 @@ const createCompletion = async () => {
method: "POST",
body: JSON.stringify(postMsg),
};
const completion = await fetch(config.apiUrl + '/ai/chat', postData);
//const completion:any = await modelStore.getModel(postData)
imageData.value = "";
@ -118,8 +119,8 @@ const createCompletion = async () => {
}
const res = await completion.json();
//console.log(res)
if(res && res.choices && res.choices.length > 0){
if(res.choices[0].message.content){
if (res && res.choices && res.choices.length > 0) {
if (res.choices[0].message.content) {
const msg = res.choices[0].message.content;
saveMessage.content = msg;
chatStore.messageList.push(saveMessage);
@ -127,7 +128,7 @@ const createCompletion = async () => {
}
}
isPadding.value = false;
} catch (error:any) {
} catch (error: any) {
isPadding.value = false;
notifyError(error.message);
}
@ -150,7 +151,7 @@ watch(
deep: true,
}
);
const handleKeydown = (e:any) => {
const handleKeydown = (e: any) => {
if (e.key === "Enter" && (e.altKey || e.shiftKey)) {
// altshift enter
e.preventDefault();
@ -182,43 +183,40 @@ const uploadImage = async (event: any) => {
reader.readAsDataURL(file);
};
</script>
<template>
<div class="chat-bot">
<div class="messsage-area">
<el-scrollbar v-if="chatStore.messageList.length > 0" class="message-container" ref="messageContainerRef">
<div ref="messageInnerRef">
<ai-chat-message v-for="message in chatStore.messageList" :key="message.messageId"
:content="message.content" :link="message.link" :role="message.role"
:createdAt="message.createdAt" />
</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 d-flex align-end pa-1">
<el-row :gutter="24" style="border-bottom: none;">
<el-col :span="2">
<el-button @click="selectImage" size="large" icon="Paperclip" circle />
<input type="file" ref="imageInput" accept="image/*" style="display: none"
@change="uploadImage" />
</el-col>
<el-col :span="19">
<el-input v-model="userMessage" :placeholder="t('chat.askme')" size="large" clearable
@keydown="handleKeydown" autofocus />
</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 class="chat-bot">
<div class="messsage-area">
<el-scrollbar v-if="chatStore.messageList.length > 0" class="message-container" ref="messageContainerRef">
<div ref="messageInnerRef">
<ai-chat-message v-for="message in chatStore.messageList" :key="message.messageId" :content="message.content"
:link="message.link" :role="message.role" :createdAt="message.createdAt" />
</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 d-flex align-end pa-1">
<el-row :gutter="24" style="border-bottom: none;">
<el-col :span="2">
<el-button @click="selectImage" size="large" icon="Paperclip" circle />
<input type="file" ref="imageInput" accept="image/*" style="display: none" @change="uploadImage" />
</el-col>
<el-col :span="19">
<el-input v-model="userMessage" :placeholder="t('aichat.askme')" size="large" clearable
@keydown="handleKeydown" autofocus />
</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">

41
frontend/src/stores/aichat.ts

@ -1,21 +1,26 @@
import { defineStore } from "pinia"
import { db } from "./db.js"
import { t } from "@/i18n/index.ts"
import {useAssistantStore} from "./assistant.ts";
import { useAssistantStore } from "./assistant.ts";
import { useModelStore } from "@/stores/model";
import { ref } from "vue";
export const useAiChatStore = defineStore('aichat', () => {
const modelStore = useModelStore()
const promptStore = useAssistantStore()
const activeId: any = ref(0)
const currentMessage:any = ref({})
const currentMessage: any = ref({})
// 聊天列表
const chatList: any = ref([])
const chatInfo:any = ref({})
const messageList : any = ref([])
const modelList = ref([])
const promptList:any = ref([])
const newChat = async() => {
const chatInfo: any = ref({})
const messageList: any = ref([])
const modelList:any = ref([])
const promptList: any = ref([])
const searchInput: any = ref('')
const showInfo = ref(false)
const editInfo: any = ref({}); //编辑聊天信息
const isEditor = ref(true);
const newChat = async () => {
const currentModel = await modelStore.getModel('chat')
if (!currentModel) {
return false
@ -28,7 +33,9 @@ export const useAiChatStore = defineStore('aichat', () => {
return await newChat()
}
modelList.value = await modelStore.getModelAction('chat')
promptList.value = await promptStore.getPrompts('chat')
const promptRes = await promptStore.getPrompts('chat')
promptList.value = promptRes.list
}
const getActiveChat = async () => {
chatInfo.value = await db.getOne('aichatlist', activeId.value)
@ -41,7 +48,7 @@ export const useAiChatStore = defineStore('aichat', () => {
return chatList
}
// 添加聊天
async function addChat(title: string, modelData: any, promptData: any, knowledgeId:string) {
async function addChat(title: string, modelData: any, promptData: any, knowledgeId: string) {
const newChat = {
title,
prompt: promptData.prompt,
@ -63,16 +70,16 @@ export const useAiChatStore = defineStore('aichat', () => {
// 删除单个聊天
async function deleteChat(chatId: number) {
await db.delete('aichatlist', chatId)
await db.deleteByField('aichatmsg','chatId', chatId)
await db.deleteByField('aichatmsg', 'chatId', chatId)
//如果删除的id是当前id
let id;
if (chatId == activeId.value) {
//
const list = await db.getAll('aichatlist')
if(list.length > 0) {
if (list.length > 0) {
id = list[0]['id']
}else{
} else {
id = await newChat()
}
setActiveId(id)
@ -82,7 +89,7 @@ export const useAiChatStore = defineStore('aichat', () => {
// 更新聊天菜单标题
async function updateTitle(chatId: number, title: string) {
await db.update('aichatlist', chatId, {title})
await db.update('aichatlist', chatId, { title })
}
// 清空所有Chat
@ -122,7 +129,7 @@ export const useAiChatStore = defineStore('aichat', () => {
// 更新聊天配置
async function updateChat(config: any, chatId: number) {
//console.log(config)
return await db.update('aichatlist',chatId, config)
return await db.update('aichatlist', chatId, config)
}
return {
activeId,
@ -130,6 +137,10 @@ export const useAiChatStore = defineStore('aichat', () => {
messageList,
chatInfo,
currentMessage,
searchInput,
showInfo,
editInfo,
isEditor,
initChat,
setActiveId,
getActiveChat,

2
packages/kanban/package.json

@ -30,7 +30,7 @@
"web-vitals": "^0.2.4"
},
"scripts": {
"start": "react-scripts start",
"dev": "react-scripts start",
"build": "react-scripts build && npm run cpfile",
"cpfile": "shx cp -rf ./build/* ../../frontend/public/kanban",
"test": "react-scripts test",

Loading…
Cancel
Save