Browse Source

add ai chat

master
godo 6 months ago
parent
commit
137f80fca3
  1. 1
      README.md
  2. 10
      frontend/public/markdown/scripts/dialog.css
  3. 38
      frontend/src/assets/chat.scss
  4. 18
      frontend/src/components/localchat/AiChatInfo.vue
  5. 29
      frontend/src/components/localchat/AiChatLeft.vue
  6. 12
      frontend/src/components/localchat/AiChatMain.vue
  7. 39
      frontend/src/stores/aichat.ts
  8. 2
      frontend/src/stores/db.ts
  9. 42
      frontend/src/util/markdown.ts

1
README.md

@ -26,6 +26,7 @@
- 修复word格式问题以及导出名字不对
- markdown新增ai优化/续写/纠错/翻译/总结,生成大纲,根据大纲一键创建文章
- 更改文档存储方式,支持选择文件夹
- 内网聊天新增ai对话,可保存对话历史,可更换模型和prompt
## 🏭 第三阶段目标(十二月底发布)
1. **文档处理与Markdown智能升级**

10
frontend/public/markdown/scripts/dialog.css

@ -102,20 +102,12 @@
font-size: 14px; /* 调整字体大小 */
color: #333333; /* 文字颜色调整为深色 */
cursor: pointer;
transition: background 0.3s, box-shadow 0.2s, transform 0.2s; /* 平滑过渡效果 */
}
.ai-dialog-button:hover {
background: linear-gradient(145deg, #f0f8ff, #e6f3ff); /* 悬停时的更亮渐变背景 */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 略微加深阴影 */
transform: scale(1.02); /* 微微放大 */
}
.ai-dialog-button:active {
background: linear-gradient(145deg, #e6f3ff, #f0f8ff); /* 按下时的更亮渐变背景 */
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); /* 减轻阴影 */
transform: scale(0.98); /* 微微缩小 */
}
/* 模态对话框的基本样式 */
.modal {
@ -197,8 +189,6 @@
.modal-button-ok:hover {
background: linear-gradient(145deg, #f0f8ff, #e6f3ff); /* 悬停时的更亮渐变背景 */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 略微加深阴影 */
transform: scale(1.02); /* 微微放大 */
}
/* 取消按钮 */

38
frontend/src/assets/chat.scss

@ -12,19 +12,19 @@
height: 100%;
/* 确保messsage-area占据其父元素的全部高度 */
}
.input-area {
padding: 0.6rem;
align-items: center;
position: absolute;
width: 100%;
bottom: 0;
.input-panel {
border-radius: 5px;
max-width: 90%;
margin: 0 auto;
.ai-input-area{
margin-left: 10px;
}
}
}
}
@ -50,11 +50,6 @@
padding-bottom: 120px;
}
// @media (max-height: 900px) {
// .message-container {
// height: calc(100vh - 300px); /* 或者根据实际情况调整 */
// }
// }
.no-message-container {
height: 100%;
@ -71,4 +66,29 @@
:deep(.md-editor-preview-wrapper) {
padding: 0px;
}
}
.top-menu {
display: flex;
justify-content: flex-end; /* 将按钮放在右边 */
align-items: center; /* 垂直居中对齐 */
gap: 10px; /* 按钮之间的间距 */
padding: 10px; /* 内边距,可以根据需要调整 */
}
.top-menu-button {
display: flex;
align-items: center;
justify-content: center;
width: 30px; /* 按钮的宽度 */
height: 30px; /* 按钮的高度 */
border-radius: 50%; /* 圆形按钮 */
background-color: #ffffff; /* 白色背景 */
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); /* 轻微的阴影效果 */
cursor: pointer;
transition: background-color 0.3s, box-shadow 0.2s; /* 平滑过渡效果 */
}
.top-menu-button:hover {
background-color: #f0f8ff; /* 悬停时的背景颜色 */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 悬停时的阴影效果 */
}

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

@ -7,20 +7,20 @@ function changPrompt(id:number) {
const promptData:any = chatStore.promptList.find((item:any) => {
return item.id == id;
});
console.log(promptData)
//console.log(promptData)
if(promptData) {
chatStore.editInfo.prompt = promptData.prompt;
}
}
const changeInfo = async () => {
const info = chatStore.editInfo;
console.log(info)
if (!info.title) {
notifyInfo(t("chat.inputTitle"));
notifyInfo(t("aichat.inputTitle"));
return;
}
if (!info.model) {
notifyInfo(t("chat.selectModel"));
notifyInfo(t("aichat.selectModel"));
return;
}
if (!info.prompt) {
@ -29,17 +29,15 @@ const changeInfo = async () => {
delete info.id;
if (chatStore.isEditor) {
//console.log(info);
await chatStore.updateChat(info, chatStore.activeId);
chatStore.chatInfo = info;
notifySuccess(t("chat.editsuccess"));
notifySuccess(t("aichat.editsuccess"));
} else {
const promptData = {
prompt: info.prompt,
promptName: info.promptName,
promptId: info.promptId,
};
await chatStore.addChat(info.title, info.model, promptData, info.kid);
notifySuccess(t("chat.addsuccess"));
await chatStore.addChat(info.title, info.model, promptData, "");
notifySuccess(t("aichat.addsuccess"));
}
await chatStore.getActiveChat();
chatStore.showInfo = false;

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

@ -4,20 +4,7 @@ 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>
@ -27,22 +14,22 @@ const showBox = (flag: any) => {
<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)">
<button class="add-chat" @click="chatStore.showBox(false)">
<el-icon>
<Plus />
</el-icon>
</button>
</el-header>
<div v-for="(item, key) in chatStore.chatList" :key="key">
<div class="list-item">
<div :class="['list-item', { active: item.id === chatStore.activeId }]">
<el-row justify="space-around">
<el-col :span="17" @click.stop="chatStore.setActiveId(item.id)" class="chat-title">
<el-col :span="20" @click.stop="chatStore.setActiveId(item.id)" class="chat-title">
{{ item.title }}
</el-col>
<el-col :span="7" class="iconlist">
<el-icon size="15">
<el-col :span="4" class="iconlist">
<!-- <el-icon size="15">
<Edit />
</el-icon>
</el-icon> -->
<el-icon size="15" @click.stop="chatStore.deleteChat(item.id)">
<Delete />
</el-icon>
@ -97,7 +84,7 @@ const showBox = (flag: any) => {
background-color: #fff;
}
.list-item:hover,.list-item .active {
.list-item:hover,.list-item.active {
background-color: #e8f3ff;
}

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

@ -187,6 +187,14 @@ const uploadImage = async (event: any) => {
</script>
<template>
<div class="chat-bot">
<div class="top-menu">
<el-icon size="15" @click.stop="chatStore.showBox(true)" class="top-menu-button">
<Tools />
</el-icon>
<el-icon size="15" @click.stop="chatStore.clearChatHistory" class="top-menu-button">
<DeleteFilled />
</el-icon>
</div>
<div class="messsage-area">
<el-scrollbar v-if="chatStore.messageList.length > 0" class="message-container" ref="messageContainerRef">
<div ref="messageInnerRef">
@ -199,7 +207,7 @@ const uploadImage = async (event: any) => {
</div>
</div>
<div class="input-area">
<div class="input-panel d-flex align-end pa-1">
<div class="input-panel">
<el-row :gutter="24" style="border-bottom: none;">
<el-col :span="2">
<el-button @click="selectImage" size="large" icon="Paperclip" circle />
@ -207,7 +215,7 @@ const uploadImage = async (event: any) => {
</el-col>
<el-col :span="19">
<el-input v-model="userMessage" :placeholder="t('aichat.askme')" size="large" clearable
@keydown="handleKeydown" autofocus />
@keydown="handleKeydown" autofocus class="ai-input-area"/>
</el-col>
<el-col :span="2">
<el-button v-if="!isPadding" @click="sendMessage" icon="Promotion" type="info" size="large" circle />

39
frontend/src/stores/aichat.ts

@ -26,7 +26,7 @@ export const useAiChatStore = defineStore('aichat', () => {
return false
}
const promptData = await promptStore.getPrompt('chat')
return await addChat(t('chat.newchat'), currentModel, promptData, "")
return await addChat(t('aichat.newchat'), currentModel.model, promptData, "")
}
const initChat = async () => {
if (activeId.value === 0) {
@ -35,7 +35,11 @@ export const useAiChatStore = defineStore('aichat', () => {
modelList.value = await modelStore.getModelAction('chat')
const promptRes = await promptStore.getPrompts('chat')
promptList.value = promptRes.list
chatList.value = await db.getAll('aichatlist')
if(activeId.value > 0){
messageList.value = await db.getByField('aichatmsg', 'chatId', activeId.value)
chatInfo.value = await db.getOne('aichatlist', activeId.value)
}
}
const getActiveChat = async () => {
chatInfo.value = await db.getOne('aichatlist', activeId.value)
@ -48,12 +52,12 @@ export const useAiChatStore = defineStore('aichat', () => {
return chatList
}
// 添加聊天
async function addChat(title: string, modelData: any, promptData: any, knowledgeId: string) {
async function addChat(title: string, model: any, promptData: any, knowledgeId: string) {
const newChat = {
title,
prompt: promptData.prompt,
promptId: promptData.id,
modelId: modelData.id,
model,
createdAt: Date.now(),
knowledgeId
}
@ -122,8 +126,12 @@ export const useAiChatStore = defineStore('aichat', () => {
}
// 删除指定id的聊天的历史记录
async function clearChatHistory(chatId: number) {
await db.deleteByField('aichatmsg', 'chatId', chatId)
async function clearChatHistory() {
if(activeId.value > 0){
await db.deleteByField('aichatmsg', 'chatId', activeId.value)
messageList.value = []
}
}
// 更新聊天配置
@ -131,6 +139,20 @@ export const useAiChatStore = defineStore('aichat', () => {
//console.log(config)
return await db.update('aichatlist', chatId, config)
}
const showBox = (flag: any) => {
isEditor.value = flag;
if (flag === true) {
editInfo.value = toRaw(chatInfo.value);
} else {
editInfo.value = {
title: "",
model: "",
prompt: "",
promptId: "",
};
}
showInfo.value = true;
};
return {
activeId,
chatList,
@ -141,6 +163,8 @@ export const useAiChatStore = defineStore('aichat', () => {
showInfo,
editInfo,
isEditor,
modelList,
promptList,
initChat,
setActiveId,
getActiveChat,
@ -154,8 +178,7 @@ export const useAiChatStore = defineStore('aichat', () => {
getChatHistory,
clearChatHistory,
updateChat,
modelList,
promptList
showBox
}
}, {

2
frontend/src/stores/db.ts

@ -11,7 +11,7 @@ dbInit.version(1).stores({
// 模型列表
modelslist: '++id,model,label,action,status,params,type,isdef,info,created_at',
// ai对话列表
aichatlist: '++id,title,modelId,promptId,prompt,ext,knowledgeId,createdAt',
aichatlist: '++id,title,model,promptId,prompt,knowledgeId,createdAt',
// ai对话消息
aichatmsg: '++id,chatId,role,content,createdAt',
// 用户列表

42
frontend/src/util/markdown.ts

@ -1,38 +1,12 @@
import 'cherry-markdown/dist/cherry-markdown.css';
import Cherry from 'cherry-markdown';
const cherryConfig:any = {
editor: {
height: 'auto',
defaultModel: 'previewOnly',
},
engine: {
global: {
// 开启流式模式 (默认 true)
flowSessionContext: true,
},
syntax: {
codeBlock: {
selfClosing: false,
},
header: {
anchorStyle: 'none',
},
table: {
selfClosing: false,
},
fontEmphasis: {
selfClosing: false,
}
}
},
previewer: {
enablePreviewerBubble: false,
},
isPreviewOnly: true,
};
import CherryEngine from 'cherry-markdown/dist/cherry-markdown.engine.core';
export function renderMarkdown(currentText: string) {
const currentCherry = new Cherry(cherryConfig);
currentCherry.setMarkdown(currentText);
return currentCherry.getHtml();
console.log(currentText)
const cherryEngineInstance:any = new CherryEngine({});
return cherryEngineInstance.makeHtml(currentText);
// const currentCherry = new Cherry(cherryConfig);
// currentCherry.setMarkdown(currentText);
// console.log(currentCherry.getHtml())
// return currentCherry.getHtml();
}
Loading…
Cancel
Save