Browse Source

change chat

master
godo 5 months ago
parent
commit
7eb5350c7d
  1. 1
      README.md
  2. 13
      frontend/components.d.ts
  3. 61
      frontend/src/components/builtin/FileList.vue
  4. 47
      frontend/src/components/localchat/AiChatMain.vue
  5. 6
      frontend/src/components/localchat/LocalChat.vue
  6. 4
      frontend/src/stores/aichat.ts
  7. 1
      frontend/src/system/core/FileSystem.ts
  8. 15
      godo/files/os.go
  9. 19
      godo/vector/go.mod
  10. 20
      godo/vector/go.sum
  11. 54
      godo/vector/run.go

1
README.md

@ -18,6 +18,7 @@
- 新增配置本地代理和远程代理,本地代理可实现本机ip映射外部域名,远程代理内嵌frpc设置后可实现内网用户外网域名访问。
- 修改锁屏机制,确保外网访问安全。
- 支持本地聊天ai对话文件和联网搜索。
- 支持知识库根据文件智能生成,一键生成知识库索引,一键搜索知识库。
## 🏭 第三阶段目标(一月底发布)
1. **文档处理与Markdown智能升级**:(已完成)

13
frontend/components.d.ts

@ -64,24 +64,37 @@ declare module 'vue' {
DownModelInfo: typeof import('./src/components/ai/DownModelInfo.vue')['default']
EditFileName: typeof import('./src/components/builtin/EditFileName.vue')['default']
EditType: typeof import('./src/components/builtin/EditType.vue')['default']
ElAside: typeof import('element-plus/es')['ElAside']
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBadge: typeof import('element-plus/es')['ElBadge']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCarousel: typeof import('element-plus/es')['ElCarousel']
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
ElCol: typeof import('element-plus/es')['ElCol']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
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']
ElOption: typeof import('element-plus/es')['ElOption']
ElPageHeader: typeof import('element-plus/es')['ElPageHeader']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElPopover: typeof import('element-plus/es')['ElPopover']
ElProgress: typeof import('element-plus/es')['ElProgress']
ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSpace: typeof import('element-plus/es')['ElSpace']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTag: typeof import('element-plus/es')['ElTag']
ElText: typeof import('element-plus/es')['ElText']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
Error: typeof import('./src/components/taskbar/Error.vue')['default']
FileIcon: typeof import('./src/components/builtin/FileIcon.vue')['default']
FileIconImg: typeof import('./src/components/builtin/FileIconImg.vue')['default']

61
frontend/src/components/builtin/FileList.vue

@ -344,26 +344,47 @@ function handleRightClick(
},
});
}
menuArr.push({
label: "加入知识库",
click: () => {
console.log(item)
addKnowledge(item.path).then((res:any) => {
console.log(res)
if(res.code != 0){
new Notify({
title: t("tips"),
content: res.message,
});
}else{
new Notify({
title: t("tips"),
content: "添加成功",
});
}
})
},
});
if (!item.knowledgeId) {
menuArr.push({
label: "加入知识库",
click: () => {
console.log(item)
addKnowledge(item.path).then((res: any) => {
console.log(res)
if (res.code != 0) {
new Notify({
title: t("tips"),
content: res.message,
});
} else {
new Notify({
title: t("tips"),
content: "添加成功",
});
}
})
},
});
}else{
menuArr.push({
label: "对话知识库",
click: () => {
const win = new BrowserWindow({
title: "对话知识库",
content: "AiChatMain",
config: {
path: item.path,
knowledgeId: item.knowledgeId,
},
width: 700,
height: 600,
center: true,
});
win.show();
},
});
}
}
if (choose.ifShow) {
menuArr.push({

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

@ -6,13 +6,14 @@ import { notifyError } from "@/util/msg.ts";
import { ElScrollbar } from "element-plus";
import { getSystemConfig } from "@/system/config";
import { Vue3Lottie } from "vue3-lottie";
import { file } from "jszip";
import { BrowserWindow } 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("");
@ -32,6 +33,10 @@ const promptMessage = computed(() => {
},
];
});
onMounted(async () => {
await chatStore.initChat()
//await aiStore.initChat()
});
const requestMessages = computed(() => {
const contextLen = modelStore.chatConfig.chat.contextLength;
//console.log(contextLen)
@ -45,9 +50,44 @@ const requestMessages = computed(() => {
});
const sendMessage = async () => {
if (chatStore.activeId < 1) {
notifyError(t("index.notFindChatModel"));
notifyError(t("aichat.selectModel"));
return;
}
const knowledgeId = win?.config?.knowledgeId || 0;
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)
chatStore.chatInfo.prompt = prompt
}
}
if (userMessage.value) {
// Add the message to the list
if (isPadding.value === true) return;
@ -207,6 +247,9 @@ const uploadImage = async (event: any) => {
</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="top-menu">
<el-icon size="15" @click.stop="chatStore.showBox(true)" class="top-menu-button">

6
frontend/src/components/localchat/LocalChat.vue

@ -22,17 +22,17 @@
<script setup lang="ts">
import { onMounted } from "vue";
import { useAiChatStore } from "@/stores/aichat";
//import { useAiChatStore } from "@/stores/aichat";
import { useLocalChatStore } from "@/stores/localchat";
import { isMobileDevice } from "@/util/device";
const store = useLocalChatStore();
const aiStore = useAiChatStore();
//const aiStore = useAiChatStore();
const showLeft = ref(false)
//let source:any;
onMounted(async () => {
await store.init()
await aiStore.initChat()
//await aiStore.initChat()
});
</script>

4
frontend/src/stores/aichat.ts

@ -28,6 +28,9 @@ export const useAiChatStore = defineStore('aichat', () => {
const promptData = await promptStore.getPrompt('chat')
return await addChat(t('aichat.newchat'), currentModel, promptData, "")
}
const getPrompt = async (type: string) => {
return await promptStore.getPrompt(type)
}
const initChat = async () => {
if (activeId.value === 0) {
await newChat()
@ -165,6 +168,7 @@ export const useAiChatStore = defineStore('aichat', () => {
showInfo,
editInfo,
isEditor,
getPrompt,
modelList,
promptList,
initChat,

1
frontend/src/system/core/FileSystem.ts

@ -94,6 +94,7 @@ class OsFileInfo {
class OsFile extends OsFileInfo {
name = ''; // 文件名
path: string; // 文件路径
knowledgeId?:number;//知识库id
oldPath?: string; // 旧的文件路径
parentPath: string; // 父目录路径
content: any; // 文件内容

15
godo/files/os.go

@ -26,6 +26,7 @@ import (
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
)
@ -36,7 +37,7 @@ type OsFileInfo struct {
IsFile bool `json:"isFile"`
IsDir bool `json:"isDirectory"`
IsSymlink bool `json:"isSymlink"`
IsKnowledge bool `json:"isKnowledge"`
KnowledgeId uint `json:"knowledgeId"`
Size int64 `json:"size"`
ModTime time.Time `json:"modTime"`
AccessTime time.Time `json:"atime"`
@ -276,7 +277,17 @@ func GetFileInfo(entry interface{}, basePath, parentPath string) (*OsFileInfo, e
if osFileInfo.IsDir {
knowledgeFilePath := filepath.Join(filePath, ".knowledge")
if libs.PathExists(knowledgeFilePath) {
osFileInfo.IsKnowledge = true
file, err := os.Open(knowledgeFilePath)
if err == nil {
fileData, err := io.ReadAll(file)
if err == nil {
idstr := string(fileData)
id, err := strconv.Atoi(idstr)
if err == nil {
osFileInfo.KnowledgeId = uint(id)
}
}
}
}
}

19
godo/vector/go.mod

@ -1,19 +0,0 @@
module godovec
go 1.23.3
require (
github.com/asg017/sqlite-vec-go-bindings v0.1.6
github.com/ncruces/go-sqlite3/gormlite v0.21.0
gorm.io/gorm v1.25.12
)
require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/ncruces/go-sqlite3 v0.21.0 // indirect
github.com/ncruces/julianday v1.0.0 // indirect
github.com/tetratelabs/wazero v1.8.2 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
)

20
godo/vector/go.sum

@ -1,20 +0,0 @@
github.com/asg017/sqlite-vec-go-bindings v0.1.6 h1:Nx0jAzyS38XpkKznJ9xQjFXz2X9tI7KqjwVxV8RNoww=
github.com/asg017/sqlite-vec-go-bindings v0.1.6/go.mod h1:A8+cTt/nKFsYCQF6OgzSNpKZrzNo5gQsXBTfsXHXY0Q=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/ncruces/go-sqlite3 v0.21.0 h1:EwKFoy1hHEopN4sFZarmi+McXdbCcbTuLixhEayXVbQ=
github.com/ncruces/go-sqlite3 v0.21.0/go.mod h1:zxMOaSG5kFYVFK4xQa0pdwIszqxqJ0W0BxBgwdrNjuA=
github.com/ncruces/go-sqlite3/gormlite v0.21.0 h1:9DsbvW9dS6uxXNFmbrNZixqAXKnIFnLM8oZmKqp8vcI=
github.com/ncruces/go-sqlite3/gormlite v0.21.0/go.mod h1:rP4JXD6jlpOSsg2Ed++kzJIAZZCIBirVYqIpwaLW88E=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4=
github.com/tetratelabs/wazero v1.8.2/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=

54
godo/vector/run.go

@ -1,54 +0,0 @@
package godovec
import (
"fmt"
_ "embed"
_ "github.com/asg017/sqlite-vec-go-bindings/ncruces"
"github.com/ncruces/go-sqlite3/gormlite"
"gorm.io/gorm"
)
var VecDb *gorm.DB
type VectorList struct {
ID int `json:"id" gorm:"primaryKey"`
FilePath string `json:"file_path" gorm:"not null"`
Engine string `json:"engine" gorm:"not null"`
EmbeddingModel string `json:"model" gorm:"not null"`
}
type VectorDoc struct {
ID int `json:"id" gorm:"primaryKey"`
Content string `json:"content"`
FilePath string `json:"file_path" gorm:"not null"`
ListID int `json:"list_id"`
}
func main() {
InitVector()
}
func InitVector() error {
db, err := gorm.Open(gormlite.Open("./data.db"), &gorm.Config{})
if err != nil {
return fmt.Errorf("failed to open vector db: %w", err)
}
// Enable PRAGMAs
// - busy_timeout (ms) to prevent db lockups as we're accessing the DB from multiple separate processes in otto8
tx := db.Exec(`
PRAGMA busy_timeout = 5000;
`)
if tx.Error != nil {
return fmt.Errorf("failed to execute pragma busy_timeout: %w", tx.Error)
}
err = db.AutoMigrate(&VectorList{}, &VectorDoc{})
if err != nil {
return fmt.Errorf("failed to auto migrate tables: %w", err)
}
VecDb = db
return nil
}
Loading…
Cancel
Save