From 9f83172e1e2898fc6cf96884d2a5a71ceec298c7 Mon Sep 17 00:00:00 2001 From: godo Date: Tue, 7 Jan 2025 09:47:59 +0800 Subject: [PATCH] add ai chat with web --- frontend/components.d.ts | 1 + frontend/package-lock.json | 31 ++- .../src/components/localchat/AiChatMain.vue | 9 +- godo/ai/search/baidu_request.go | 105 +++++++++ godo/ai/search/baidu_request_test.go | 13 ++ godo/ai/search/bing_request.go | 100 +++++++++ godo/ai/search/bing_request_test.go | 13 ++ godo/ai/search/common.go | 201 ++++++++++++++++++ godo/ai/search/init.go | 28 +++ godo/ai/server/chat.go | 72 ++++++- godo/ai/server/llms.go | 34 +-- godo/cmd/main.go | 2 + godo/go.mod | 9 +- godo/go.sum | 72 +++++++ 14 files changed, 661 insertions(+), 29 deletions(-) create mode 100644 godo/ai/search/baidu_request.go create mode 100644 godo/ai/search/baidu_request_test.go create mode 100644 godo/ai/search/bing_request.go create mode 100644 godo/ai/search/bing_request_test.go create mode 100644 godo/ai/search/common.go create mode 100644 godo/ai/search/init.go diff --git a/frontend/components.d.ts b/frontend/components.d.ts index b9b837b..53ada7f 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -140,6 +140,7 @@ declare module 'vue' { MessageIcon: typeof import('./src/components/taskbar/MessageIcon.vue')['default'] Mobile: typeof import('./src/components/desktop/mobile/Mobile.vue')['default'] MobileApp: typeof import('./src/components/desktop/mobile/MobileApp.vue')['default'] + MobileTemplate: typeof import('./src/components/window/MobileTemplate.vue')['default'] MusicStore: typeof import('./src/components/builtin/MusicStore.vue')['default'] MusicViewer: typeof import('./src/components/builtin/MusicViewer.vue')['default'] NasClient: typeof import('./src/components/setting/NasClient.vue')['default'] diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 89e5c40..aec4c59 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "godoos", - "version": "1.0.3", + "version": "1.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "godoos", - "version": "1.0.3", + "version": "1.0.4", "dependencies": { "@element-plus/icons-vue": "^2.3.1", "@liripeng/vue-audio-player": "^1.6.2", @@ -22,6 +22,7 @@ "pinia": "^2.1.7", "pinia-plugin-persist": "^1.0.0", "swiper": "^11.1.15", + "vant": "^4.9.15", "vue": "^3.4.31", "vue-i18n": "^9.13.1", "vue-router": "^4.4.0", @@ -1303,6 +1304,19 @@ "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==" }, + "node_modules/@vant/popperjs": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/@vant/popperjs/-/popperjs-1.3.0.tgz", + "integrity": "sha512-hB+czUG+aHtjhaEmCJDuXOep0YTZjdlRR+4MSmIFnkCQIxJaXLQdSsR90XWvAI2yvKUI7TCGqR8pQg2RtvkMHw==" + }, + "node_modules/@vant/use": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/@vant/use/-/use-1.6.0.tgz", + "integrity": "sha512-PHHxeAASgiOpSmMjceweIrv2AxDZIkWXyaczksMoWvKV2YAYEhoizRuk/xFnKF+emUIi46TsQ+rvlm/t2BBCfA==", + "peerDependencies": { + "vue": "^3.0.0" + } + }, "node_modules/@vitejs/plugin-vue": { "version": "5.2.1", "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz", @@ -3809,6 +3823,19 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/vant": { + "version": "4.9.15", + "resolved": "https://registry.npmmirror.com/vant/-/vant-4.9.15.tgz", + "integrity": "sha512-XyIAnoGu2P/dc5R2NnjIJgKwfOTTVM2FJW/8WpsZA0FUBL6Lu+KHBaAY/x3IduwbowkaCIUUPViUopI2JSjbug==", + "dependencies": { + "@vant/popperjs": "^1.3.0", + "@vant/use": "^1.6.0", + "@vue/shared": "^3.5.13" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, "node_modules/vite": { "version": "5.4.11", "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.11.tgz", diff --git a/frontend/src/components/localchat/AiChatMain.vue b/frontend/src/components/localchat/AiChatMain.vue index d3e2f58..e9f9ef7 100644 --- a/frontend/src/components/localchat/AiChatMain.vue +++ b/frontend/src/components/localchat/AiChatMain.vue @@ -9,7 +9,7 @@ import { Vue3Lottie } from "vue3-lottie"; const chatStore = useAiChatStore(); const modelStore = useModelStore(); const isPadding = ref(false); //是否发送中 - +const webSearch = ref(false); const imageInput: any = ref(null); let imageData = ref(""); const messageContainerRef = ref>(); @@ -85,6 +85,7 @@ const createCompletion = async () => { model: chatStore.chatInfo.model, engine: chatStore.chatInfo.engine, stream: false, + webSearch: webSearch.value, options: chatConfig, }; if (imageData.value != "") { @@ -95,6 +96,7 @@ const createCompletion = async () => { //"prompt":userMessage.value, engine: img2txtModel.info.engine, stream: false, + webSearch: false, options: chatConfig, messages: [ { @@ -215,7 +217,10 @@ const uploadImage = async (event: any) => { - + + + + diff --git a/godo/ai/search/baidu_request.go b/godo/ai/search/baidu_request.go new file mode 100644 index 0000000..fe69a96 --- /dev/null +++ b/godo/ai/search/baidu_request.go @@ -0,0 +1,105 @@ +package search + +import ( + "fmt" + "log" + "net/http" + "strings" + "sync" + + "github.com/PuerkitoBio/goquery" +) + +func (baidu *Baidu) Search() []Entity { + baidu.Req.url = baidu.urlWrap() + log.Printf("req.url: %s\n", baidu.Req.url) + resp := &Resp{} + resp, _ = baidu.send() + baidu.resp = *resp + return baidu.toEntityList() +} + +func (baidu *Baidu) urlWrap() (url string) { + return fmt.Sprintf(BaiduSearch, baidu.Req.Q) +} + +func (baidu *Baidu) toEntityList() []Entity { + var resList []Entity + + if baidu.resp.doc != nil { + var wg sync.WaitGroup + results := make(chan Entity, SearchNum) + done := make(chan bool) + + baidu.resp.doc.Find("div[srcid]").EachWithBreak(func(i int, s *goquery.Selection) bool { + if i >= SearchNum { // 如果已经处理了5篇,停止遍历 + return false + } + // For each item found, get the Title + title := s.Find("h3").Find("a").Text() + url := s.AttrOr("mu", "") + tpl := s.AttrOr("tpl", "") + if tpl != "se_com_default" { + return true + } + subTitle := s.Find(".c-gap-top-small").Find("span").Text() + entity := Entity{From: BaiduFrom} + entity.Title = title + entity.SubTitle = subTitle + entity.Url = url + host := strings.ReplaceAll(url, "http://", "") + host = strings.ReplaceAll(host, "https://", "") + entity.Host = strings.Split(host, "/")[0] + + wg.Add(1) + go func(entity Entity) { + defer wg.Done() + // 获取详细页面的纯文本内容 + content, err := FetchContent(entity.Url) + if err != nil { + log.Printf("Error fetching content for URL %s: %v\n", entity.Url, err) + results <- entity + return + } + entity.Content = content + results <- entity + }(entity) + + return true + }) + + go func() { + wg.Wait() + close(results) + done <- true + }() + + for entity := range results { + resList = append(resList, entity) + } + + <-done + } + return resList +} + +func (baidu *Baidu) send() (resp *Resp, err error) { + + client := &http.Client{ + Transport: tr, + } + //提交请求 + request, err := http.NewRequest("GET", baidu.urlWrap(), nil) + if err != nil { + log.Println(err) + } + + //增加header选项 + request.Header.Add("User-Agent", UserAgent) + request.Header.Add("Host", BaiduDomain) + request.Header.Add("Cookie", BaiduCookie) + request.Header.Add("Accept", BaiduAccept) + + return SendDo(client, request) + +} diff --git a/godo/ai/search/baidu_request_test.go b/godo/ai/search/baidu_request_test.go new file mode 100644 index 0000000..81c09a6 --- /dev/null +++ b/godo/ai/search/baidu_request_test.go @@ -0,0 +1,13 @@ +package search + +import ( + "encoding/json" + "testing" +) + +func TestBaidu(t *testing.T) { + engine := &Baidu{Req: Req{Q: "godoos"}} + s := engine.Search() + marshal, _ := json.Marshal(s) + t.Log(string(marshal)) +} diff --git a/godo/ai/search/bing_request.go b/godo/ai/search/bing_request.go new file mode 100644 index 0000000..34b3c0f --- /dev/null +++ b/godo/ai/search/bing_request.go @@ -0,0 +1,100 @@ +package search + +import ( + "fmt" + "log" + "net/http" + "strings" + "sync" + + "github.com/PuerkitoBio/goquery" +) + +func (bing *Bing) Search() []Entity { + bing.Req.url = bing.urlWrap() + log.Printf("req.url: %s\n", bing.Req.url) + resp := &Resp{} + resp, _ = bing.send() + bing.resp = *resp + return bing.toEntityList() +} + +func (bing *Bing) urlWrap() (url string) { + return fmt.Sprintf(BingSearch, bing.Req.Q) +} + +func (bing *Bing) toEntityList() []Entity { + var resList []Entity + if bing.resp.doc != nil { + var wg sync.WaitGroup + results := make(chan Entity, SearchNum) + done := make(chan bool) + + bing.resp.doc.Find("ol#b_results>li[class=b_algo]").EachWithBreak(func(i int, s *goquery.Selection) bool { + if len(resList) >= SearchNum { // 如果已经处理了5篇,停止遍历 + return false + } + + // For each item found, get the Title + title := s.Find("div[class=b_title]>h2>a").Text() + url := s.Find("div[class=b_title]>h2>a").AttrOr("href", "") + subTitle := s.Find("div[class=b_caption]>p").Text() + entity := Entity{From: BingFrom} + entity.Title = title + entity.SubTitle = subTitle + entity.Url = url + host := strings.ReplaceAll(url, "http://", "") + host = strings.ReplaceAll(host, "https://", "") + entity.Host = strings.Split(host, "/")[0] + + wg.Add(1) + go func(entity Entity) { + defer wg.Done() + // 获取详细页面的纯文本内容 + content, err := FetchContent(entity.Url) + if err != nil { + log.Printf("Error fetching content for URL %s: %v\n", entity.Url, err) + results <- entity + return + } + entity.Content = content + results <- entity + }(entity) + + return true + }) + + go func() { + wg.Wait() + close(results) + done <- true + }() + + for entity := range results { + resList = append(resList, entity) + } + + <-done + } + return resList +} + +func (bing *Bing) send() (resp *Resp, err error) { + + client := &http.Client{ + Transport: tr, + } + //提交请求 + request, err := http.NewRequest("GET", bing.urlWrap(), nil) + if err != nil { + log.Println(err) + } + + //增加header选项 + request.Header.Add("User-Agent", UserAgent) + request.Header.Add("Host", BingDomain) + request.Header.Add("Cookie", BingCoolkie) + request.Header.Add("Accept", BingAccept) + + return SendDo(client, request) +} diff --git a/godo/ai/search/bing_request_test.go b/godo/ai/search/bing_request_test.go new file mode 100644 index 0000000..fb17f67 --- /dev/null +++ b/godo/ai/search/bing_request_test.go @@ -0,0 +1,13 @@ +package search + +import ( + "encoding/json" + "testing" +) + +func TestBing(t *testing.T) { + engine := &Bing{Req: Req{Q: "godoos"}} + s := engine.Search() + marshal, _ := json.Marshal(s) + t.Log(string(marshal)) +} diff --git a/godo/ai/search/common.go b/godo/ai/search/common.go new file mode 100644 index 0000000..b9982de --- /dev/null +++ b/godo/ai/search/common.go @@ -0,0 +1,201 @@ +package search + +import ( + "encoding/json" + "godo/libs" + "io" + "log" + "net/http" + "net/url" + "sync" + + "github.com/PuerkitoBio/goquery" + "jaytaylor.com/html2text" +) + +const ( + SearchNum = 5 + Debug = false + UserAgent = "user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36" + ProxyURL = "http://127.0.0.1:7890" + ProxyOpen = false + + // =================BAIDU================== + //BaiduCookie = "" + BaiduCookie = "BIDUPSID=3535F2B8A915447A4839A7DD194BA7B3; PSTM=1617718622; __yjs_duid=1_76a86308fbc1b29f8b2c852e8c9e24fd1620886035244; BD_UPN=123253; MCITY=-%3A; BDUSS=m5TRVBaQjk0NXF0SFpmMHZtN2ZWSTlyZVo2d0ZJTVhlU1BMTUpqbXRrZEl0WFZqSUFBQUFBJCQAAAAAAAAAAAEAAAC8QhUx1MbUqtauwfpoaQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEgoTmNIKE5jc; BDUSS_BFESS=m5TRVBaQjk0NXF0SFpmMHZtN2ZWSTlyZVo2d0ZJTVhlU1BMTUpqbXRrZEl0WFZqSUFBQUFBJCQAAAAAAAAAAAEAAAC8QhUx1MbUqtauwfpoaQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEgoTmNIKE5jc; BDSFRCVID=rY_OJeC62lCrte6jotU8bVRNE2SdnBRTH6aotxm4whxuChbLecyMEG0Pyf8g0KubiKd_ogKK0eOTHktF_2uxOjjg8UtVJeC6EG0Ptf8g0f5; H_BDCLCKID_SF=JJkO_D_atKvDqTrP-trf5DCShUFsyMvlB2Q-XPoO3KJnMU74M4Qtj4Fp3-ON--QiW5cpoMbgylRp8P3y0bb2DUA1y4vp5MnqQeTxoUJ2fnRJEUcGqj5Ah--ebPRiJPQ9QgbWLpQ7tt5W8ncFbT7l5hKpbt-q0x-jLTnhVn0MBCK0MC09j6KhDTPVKgTa54cbb4o2WbCQQq3O8pcN2b5oQT81jnnHatoh32jZ-pb4bb5vOPQKDpOUWfAkXpJvQnJjt2JxaqRCKhv-Sl5jDh3Me-AsLn6te6jzaIvy0hvctn5cShnc5MjrDRLbXU6BK5vPbNcZ0l8K3l02V-bIe-t2XjQhDNKtt5_jJJIsBP_8aJ7bHn7gbJK_-P4DeP6wexRZ5mAqoDQ6tbbnHRcH3noJ-lKw3MTA-55lba6naIQqa-cKDR38Kp58hPT00tb8Qnb43bRT2PPy5KJvfJo1BTrYhP-UyN3-Wh3725nlMKoaMp78jR093JO4y4Ldj4oxJpOJ5JbMonLafDD3fb7kbP6Eq4D_MfOtetJyaR3OoM7vWJ5WqR7jD5DbMP4qQfcrQxTn3Nrfb-OIJPTKShbXKxonKlLObbOGLM6EfNcZ0l8K3l02V-bRDDcfQJQDQt7JKPRMW20j0l7mWnvMsxA45J7cM4IseboJLfT-0bc4KKJxbnLWeIJEjjCKejbQDGADq6nfb5kXLn7J-J5HfbTkbITjhPrMKRrdWMT-0bFHWpO-bnK-jR7m3j7D3lDNQH5gKP6GLHn7_JjObPnVM-3d2boTM-DTbfb8WxQxtNR80DnjtpvhHRTobhnobUPUDMo9LUvWbgcdot5yBbc8eIna5hjkbfJBQttjQn3hfIkj0DKLK-oj-D8lej7P; BAIDUID=9A9CC424161261B757A36C59CFED76E5:FG=1; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; H_PS_PSSID=36542_37557_37519_36884_37627_36786_37539_37500_26350_37343_37461; BAIDUID_BFESS=9A9CC424161261B757A36C59CFED76E5:FG=1; BDSFRCVID_BFESS=rY_OJeC62lCrte6jotU8bVRNE2SdnBRTH6aotxm4whxuChbLecyMEG0Pyf8g0KubiKd_ogKK0eOTHktF_2uxOjjg8UtVJeC6EG0Ptf8g0f5; H_BDCLCKID_SF_BFESS=JJkO_D_atKvDqTrP-trf5DCShUFsyMvlB2Q-XPoO3KJnMU74M4Qtj4Fp3-ON--QiW5cpoMbgylRp8P3y0bb2DUA1y4vp5MnqQeTxoUJ2fnRJEUcGqj5Ah--ebPRiJPQ9QgbWLpQ7tt5W8ncFbT7l5hKpbt-q0x-jLTnhVn0MBCK0MC09j6KhDTPVKgTa54cbb4o2WbCQQq3O8pcN2b5oQT81jnnHatoh32jZ-pb4bb5vOPQKDpOUWfAkXpJvQnJjt2JxaqRCKhv-Sl5jDh3Me-AsLn6te6jzaIvy0hvctn5cShnc5MjrDRLbXU6BK5vPbNcZ0l8K3l02V-bIe-t2XjQhDNKtt5_jJJIsBP_8aJ7bHn7gbJK_-P4DeP6wexRZ5mAqoDQ6tbbnHRcH3noJ-lKw3MTA-55lba6naIQqa-cKDR38Kp58hPT00tb8Qnb43bRT2PPy5KJvfJo1BTrYhP-UyN3-Wh3725nlMKoaMp78jR093JO4y4Ldj4oxJpOJ5JbMonLafDD3fb7kbP6Eq4D_MfOtetJyaR3OoM7vWJ5WqR7jD5DbMP4qQfcrQxTn3Nrfb-OIJPTKShbXKxonKlLObbOGLM6EfNcZ0l8K3l02V-bRDDcfQJQDQt7JKPRMW20j0l7mWnvMsxA45J7cM4IseboJLfT-0bc4KKJxbnLWeIJEjjCKejbQDGADq6nfb5kXLn7J-J5HfbTkbITjhPrMKRrdWMT-0bFHWpO-bnK-jR7m3j7D3lDNQH5gKP6GLHn7_JjObPnVM-3d2boTM-DTbfb8WxQxtNR80DnjtpvhHRTobhnobUPUDMo9LUvWbgcdot5yBbc8eIna5hjkbfJBQttjQn3hfIkj0DKLK-oj-D8lej7P; delPer=0; BD_CK_SAM=1; PSINO=3; BA_HECTOR=8581ala48k0la181aga50fm81hm8prs1e; ZFY=MW5tgLZfxnXk8HLzWL5vKMTfHTBgdsBmZudM7p1:BE:Aw:C; sugstore=1; H_PS_645EC=6985pYm8uvzjRIBPLtrwALaCau4%2Fxj7uefaYB7sGfR5qYLEGfRZ8Eyb%2F1oc" + BaiduUrl = "https://www.baidu.com" + BaiduDomain = "www.baidu.com" + BaiduAccept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" + BaiduSearch = BaiduUrl + "/s?wd=%s" + "&usm=3&rsv_idx=2&rsv_page=1" + BaiduFrom = "百度" + + // =================BING================== + BingCoolkie = "" + //BingCoolkie = "MUID=16AE9B2FE9DC6568044F8B3EE8F2640C; MUIDB=16AE9B2FE9DC6568044F8B3EE8F2640C; SRCHD=AF=BDVEHC; SRCHUID=V=2&GUID=7321ED22410B41459C503CB6D2628196&dmnchg=1; _UR=QS=0&TQS=0; imgv=flts=20220704; _tarLang=default=zh-Hans; _TTSS_IN=hist=WyJlbiIsImF1dG8tZGV0ZWN0Il0=; _TTSS_OUT=hist=WyJ6aC1IYW5zIl0=; _HPVN=CS=eyJQbiI6eyJDbiI6MjQsIlN0IjoyLCJRcyI6MCwiUHJvZCI6IlAifSwiU2MiOnsiQ24iOjI0LCJTdCI6MCwiUXMiOjAsIlByb2QiOiJIIn0sIlF6Ijp7IkNuIjoyNCwiU3QiOjEsIlFzIjowLCJQcm9kIjoiVCJ9LCJBcCI6dHJ1ZSwiTXV0ZSI6dHJ1ZSwiTGFkIjoiMjAyMi0wOS0wNlQwMDowMDowMFoiLCJJb3RkIjowLCJHd2IiOjAsIkRmdCI6bnVsbCwiTXZzIjowLCJGbHQiOjAsIkltcCI6Nzd9; ANIMIA=FRE=1; MMCASM=ID=3FEB6F3855CC49E584F2DA61F6E5E44C; ZHCHATSTRONGATTRACT=TRUE; _SS=SID=11649FAAD94F6D9716388DF6D8296CB4&PC=U316; SRCHS=PC=U316; ABDEF=V=13&ABDV=13&MRB=0&MRNB=1668441959593; SUID=M; _EDGE_S=SID=11649FAAD94F6D9716388DF6D8296CB4&ui=zh-cn; SRCHUSR=DOB=20210406&T=1668472757000&TPC=1668472758000; ZHCHATWEAKATTRACT=TRUE; ipv6=hit=1668476360196&t=4; ZHLASTACTIVECHAT=0; ZHSEARCHCHATSTATUS=STATUS=0; SNRHOP=I=&TS=; RECSEARCH=SQs=[{\"q\":\"giac%20%E4%B8%8A%E6%B5%B7\"%2C\"c\":1%2C\"ad\":true}%2C{\"q\":\"sessioncachesize\"%2C\"c\":1%2C\"ad\":false}%2C{\"q\":\"rset\"%2C\"c\":1%2C\"ad\":false}%2C{\"q\":\"xx%3Ainitiatingheapoccupancypercent\"%2C\"c\":1%2C\"ad\":false}%2C{\"q\":\"%E7%AE%A1%E7%90%86%E7%9A%84%E5%B8%B8%E8%AF%86\"%2C\"c\":1%2C\"ad\":true}%2C{\"q\":\"%E7%AE%A1%E7%90%86%E7%9A%84%E5%B8%B8%E8%AF%86%20%E5%BE%B7%E9%B2%81%E5%85%8B\"%2C\"c\":1%2C\"ad\":true}%2C{\"q\":\"%E7%AE%A1%E7%90%86\"%2C\"c\":1%2C\"ad\":true}%2C{\"q\":\"goquery%20%E5%BE%AA%E7%8E%AF\"%2C\"c\":1%2C\"ad\":false}%2C{\"q\":\"yuanbiguo\"%2C\"c\":1%2C\"ad\":false}]; SRCHHPGUSR=SRCHLANGV2=zh-Hans&BRW=W&BRH=S&CW=1396&CH=435&DPR=2&UTC=480&DM=0&WTS=63804069557&HV=1668474094&BZA=0&SRCHLANG=zh-Hans&SW=1440&SH=900&PV=11.2.3&EXLTT=6&SCW=1381&SCH=1408&PRVCW=1396&PRVCH=764" + BingUrl = "https://cn.bing.com" + BingDomain = "cn.bing.com" + BingAccept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" + BingSearch = BingUrl + "/search?q=%s" + "&PC=U316&FORM=CHROMN" + BingFrom = "Bing" + + // =================Google================== + //GoogleCookie = "" + GoogleCookie = "CONSENT=YES+srp.gws-20220523-0-RC1.zh-CN+FX; HSID=AIDFOIfZRXMjhRznJ; SSID=ATYvcUlmXr3mPFerl; APISID=fTLh3HcYiZa0Ch2l/AiukySr7MDg_GhSRo; SAPISID=-JpKVkIJZXDgucyp/AjPiMHGTZYbtqrQbt; __Secure-1PAPISID=-JpKVkIJZXDgucyp/AjPiMHGTZYbtqrQbt; __Secure-3PAPISID=-JpKVkIJZXDgucyp/AjPiMHGTZYbtqrQbt; SEARCH_SAMESITE=CgQIk5YB; SID=Qggrdf-HuR3Im3foqYJWVZDPEay8b5U0-O_E6L489Ppxqy2bY358yp5YaaIs5NjEgyW23g.; __Secure-1PSID=Qggrdf-HuR3Im3foqYJWVZDPEay8b5U0-O_E6L489Ppxqy2brNnvCnxg8UCPQbHqZClMmA.; __Secure-3PSID=Qggrdf-HuR3Im3foqYJWVZDPEay8b5U0-O_E6L489Ppxqy2bJ0s1kXYhOOAgur0ax0qp6w.; OTZ=6777442_24_24__24_; AEC=AakniGNW8IBpZgW_-IGOh45Otu8fCK-Ty1n0eSZhgs05euV7s2hhmWLhCQ; NID=511=k96f3xX7KMB1Wvhg478KURvWSAd53Y3rTkVG3bMb1FhtdJwQUbUiHJOnVTgFgW0Mad_1X5gKnWLMt0lPHl33nQdVCTzEiitFOC2dIicYusLP1zl_L6Wh9l-XO6x9VRh4ZxGlcu1bCmlEbBYvz2eL2ioCJ3RMGZtGcr6_1tdpGZH8DRcj3c8X6FxRJqcX5peACa9pGELYmZk4TfOYiUON7p4ht5MTkfA4-hmLUD_JQOT_bK4Aub5SzhXukCFBt5qx2UMkKKtheWlK6cRlR-EIqbC0ppccy2pUdT0YyfE; DV=c77sl5f1j0hXACm1QWzNqJ3oPLjLSRge9TXKlwa5UAEAAMBLUjyomhJKegAAABD63jYjJ8URJwAAAKJBqsVeeJWmDgAAAA; 1P_JAR=2022-11-22-00; SIDCC=AIKkIs0Jk8Fs8wt-yHi9yEM8vFzc6y1bmzVm7mYb3eELJ0t_5yixpCQToLaIDwlOnk3yTvFwKw; __Secure-1PSIDCC=AIKkIs2Sk9zwJs1YfCaHpcFU4ZAKHzDO7Atrzk3b-uXf384Xuc1nQ0U41MiX_VQcEBrJ0TSR1ZY; __Secure-3PSIDCC=AIKkIs1iF4en_Ln8Jonhw8Q79QB_dwbKwcw0khXXIX0vsIwOjBgxmO8dd6kfi10evURBz7YdDg" + GoogleUrl = "https://www.google.com" + GoogleDomain = "www.google.com" + GoogleAccept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" + GoogleSearch = GoogleUrl + "/search?q=%s" + "&ie=UTF-8" + GoogleFrom = "Google" + + // =================Wx================== + WxCookie = "" + //WxCookie = "SUID=F84DCC781539960A000000006235D4E8; SUV=1647695080857391; ssuid=6792259580; weixinIndexVisited=1; IPLOC=CN3100; ABTEST=0|1668775030|v1; JSESSIONID=aaa2PBWFjHNS8hQ8_tfpy; cd=1668913493&0f942166ea05ede01cfe88195d36508d; rd=tyllllllll20WBOSYTuBqQ2iuqV0WBOqAfJbLZllll9llllxVllll5@@@@@@@@@@; ld=6Zllllllll20WBOSYTuBqQ2DdNH0WBOqAfJbLZllll9lllllVklll5@@@@@@@@@@; LSTMV=217%2C66; LCLKINT=1482; PHPSESSID=udut3pen5cml0b9849o68jch40; SNUID=9D60E0542D28C24D1776D6A42DF58CE7; ariaDefaultTheme=undefined" + WxUrl = "https://weixin.sogou.com" + WxDomain = "weixin.sogou.com" + WxAccept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" + WxSearch = WxUrl + "/weixin?type=2&s_from=input&query=%s&ie=utf8&_sug_=n&_sug_type_=" + WxFrom = "微信公众号" +) + +type SearchEngine interface { + Search() (result []Entity) + urlWrap() (url string) + toEntityList() (entityList []Entity) + send() (resp *Resp, err error) +} + +type Baidu struct { + Req Req + resp Resp +} + +type Bing struct { + Req Req + resp Resp +} + +type Req struct { + Q string + url string + userAgent string + http.Cookie +} + +type Resp struct { + code int + body string + doc *goquery.Document +} + +type Entity struct { + Title string `json:"title"` + Host string `json:"host"` + Url string `json:"url"` + SubTitle string `json:"subTitle"` + From string `json:"from"` + Content string `json:"content"` +} + +func SearchWebhandler(w http.ResponseWriter, r *http.Request) { + //log.Println(r.URL) + _ = r.ParseForm() + q := r.Form.Get("q") + if q == "" { + libs.ErrorMsg(w, "查询内容不能为空") + return + } + q = url.QueryEscape(q) + //log.Printf("查询内容:%s\n", q) + jsonResult := SearchWeb(q) + + // 构造返回 + body, err := json.Marshal(jsonResult) + if err != nil { + libs.ErrorMsg(w, "解析错误") + return + } + libs.SuccessMsg(w, body, "success") +} +func SearchWeb(q string) []Entity { + + q = url.QueryEscape(q) + //log.Printf("查询内容:%s\n", q) + jsonResult := []Entity{} + + array := [...]SearchEngine{ + &Bing{Req: Req{Q: q}}, + &Baidu{Req: Req{Q: q}}, + } + + var wg sync.WaitGroup + c := make(chan []Entity, len(array)) + + for _, engine := range array { + wg.Add(1) + go func(engine SearchEngine) { + defer wg.Done() + result := engine.Search() + c <- result + }(engine) + } + + go func() { + wg.Wait() + close(c) + }() + + for result := range c { + if result != nil { + jsonResult = append(jsonResult, result...) + } + } + + return jsonResult +} +func SendDo(client *http.Client, request *http.Request) (*Resp, error) { + resp := &Resp{code: 200} + + //处理返回结果 + response, e := client.Do(request) + if response == nil { + resp.code = 500 + log.Printf("response nil: %v\n", e) + return resp, nil + } + if response.StatusCode != 200 { + resp.code = response.StatusCode + log.Printf("status code error: %d %s\n", response.StatusCode, response.Status) + return resp, nil + } + defer response.Body.Close() + // Load the HTML document + doc, err := goquery.NewDocumentFromReader(response.Body) + if err != nil { + log.Println(err) + } + + resp.code = 200 + resp.doc = doc + + return resp, nil +} +func FetchContent(url string) (content string, err error) { + resp, err := http.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + + body, errRead := io.ReadAll(resp.Body) + if errRead != nil { + return "", err + } + text, err := html2text.FromString(string(body), html2text.Options{PrettyTables: false}) + if err != nil { + return "", err + } + // if len(text) > 1000 { + // text = text[:1000] + // } + return text, nil +} diff --git a/godo/ai/search/init.go b/godo/ai/search/init.go new file mode 100644 index 0000000..c211894 --- /dev/null +++ b/godo/ai/search/init.go @@ -0,0 +1,28 @@ +package search + +import ( + "net" + "net/http" + "time" +) + +var tr *http.Transport + +func init() { + + tr = &http.Transport{ + // 设置代理 + MaxIdleConns: 100, + Dial: func(netw, addr string) (net.Conn, error) { + conn, err := net.DialTimeout(netw, addr, time.Second*5) //设置建立连接超时 + if err != nil { + return nil, err + } + err = conn.SetDeadline(time.Now().Add(time.Second * 6)) //设置发送接受数据超时 + if err != nil { + return nil, err + } + return conn, nil + }, + } +} diff --git a/godo/ai/server/chat.go b/godo/ai/server/chat.go index 2431dbf..547aca5 100644 --- a/godo/ai/server/chat.go +++ b/godo/ai/server/chat.go @@ -2,35 +2,93 @@ package server import ( "encoding/json" + "fmt" + "godo/ai/search" "godo/libs" - "log" "net/http" + "time" ) +type ChatRequest struct { + Model string `json:"model"` + Engine string `json:"engine"` + Stream bool `json:"stream"` + WebSearch bool `json:"webSearch"` + FileContent string `json:"fileContent"` + Options map[string]interface{} `json:"options"` + Messages []Message `json:"messages"` +} + +type Message struct { + Role string `json:"role"` + Content string `json:"content"` + Images []string `json:"images"` +} + func ChatHandler(w http.ResponseWriter, r *http.Request) { - // url := GetOllamaUrl() + "/v1/chat/completions" var url string - var req map[string]interface{} + var req ChatRequest err := json.NewDecoder(r.Body).Decode(&req) if err != nil { libs.ErrorMsg(w, "the chat request error:"+err.Error()) return } + if req.WebSearch { + err = ChatWithWeb(&req) + if err != nil { + libs.ErrorMsg(w, err.Error()) + return + } + } headers, url, err := GetHeadersAndUrl(req, "chat") - log.Printf("url: %s", url) - log.Printf("headers: %v", headers) + // log.Printf("url: %s", url) + // log.Printf("headers: %v", headers) if err != nil { libs.ErrorMsg(w, "the chat request header or url errors:"+err.Error()) return } ForwardHandler(w, r, req, url, headers, "POST") } +func ChatWithWeb(req *ChatRequest) error { + if len(req.Messages) == 0 { + return fmt.Errorf("the messages is empty") + } + lastMessage := req.Messages[len(req.Messages)-1] + if lastMessage.Role != "user" { + return fmt.Errorf("the last message is not user") + } + searchRequest := search.SearchWeb(lastMessage.Content) + if len(searchRequest) == 0 { + return fmt.Errorf("the search web is empty") + } + var inputPrompt string + for _, search := range searchRequest { + inputPrompt += fmt.Sprintf("- 标题: %s\n- 内容: %s\n", search.Title, search.Content) + } + currentDate := time.Now().Format("2006-01-02") + searchPrompt := fmt.Sprintf(` +# 以下是来自互联网的信息: +%s + +# 当前日期: %s + +# 要求: +根据最新发布的信息回答用户问题。 + +# 用户问题:%s +`, inputPrompt, currentDate, lastMessage.Content) + //log.Printf("the search web is %v", searchPrompt) + // userQuestion := fmt.Sprintf("问:%s,答:", lastMessage.Content) + // req.Messages = append([]Message{}, Message{Role: "assistant", Content: searchPrompt}) + req.Messages = append([]Message{}, Message{Role: "user", Content: searchPrompt}) + return nil +} func EmbeddingHandler(w http.ResponseWriter, r *http.Request) { - var req map[string]interface{} + var req ChatRequest err := json.NewDecoder(r.Body).Decode(&req) if err != nil { - libs.ErrorMsg(w, err.Error()) + libs.ErrorMsg(w, "the chat request error:"+err.Error()) return } headers, url, err := GetHeadersAndUrl(req, "embeddings") diff --git a/godo/ai/server/llms.go b/godo/ai/server/llms.go index 3b895e1..805d155 100644 --- a/godo/ai/server/llms.go +++ b/godo/ai/server/llms.go @@ -24,38 +24,38 @@ var OpenAIApiMaps = map[string]string{ "siliconflow": "https://api.siliconflow.cn/v1", } -func GetHeadersAndUrl(req map[string]interface{}, chattype string) (map[string]string, string, error) { - engine, ok := req["engine"].(string) - if !ok { - return nil, "", fmt.Errorf("invalid engine field in request") - } - model, ok := req["model"].(string) - if !ok { - return nil, "", fmt.Errorf("invalid model field in request") - } +func GetHeadersAndUrl(req ChatRequest, chattype string) (map[string]string, string, error) { + // engine, ok := req["engine"].(string) + // if !ok { + // return nil, "", fmt.Errorf("invalid engine field in request") + // } + // model, ok := req["model"].(string) + // if !ok { + // return nil, "", fmt.Errorf("invalid model field in request") + // } // 获取url - url, has := OpenAIApiMaps[engine] + url, has := OpenAIApiMaps[req.Engine] if !has { return nil, "", fmt.Errorf("invalid engine map in request") } var err error if url == "" { - if engine == "openai" { + if req.Engine == "openai" { url = GetOpenAIUrl() - } else if engine == "cloudflare" { + } else if req.Engine == "cloudflare" { url, err = GetCloudflareUrl() if err != nil { return nil, "", err } - } else if engine == "gitee" { - url = GetGiteeUrl(model, chattype) - } else if engine == "ollama" { + } else if req.Engine == "gitee" { + url = GetGiteeUrl(req.Model, chattype) + } else if req.Engine == "ollama" { url = GetOllamaUrl() + "/v1" log.Printf("get ollama url is %v", url) } } - headers, err := GetOpenAIHeaders(engine) + headers, err := GetOpenAIHeaders(req.Engine) if err != nil { return nil, "", err } @@ -63,7 +63,7 @@ func GetHeadersAndUrl(req map[string]interface{}, chattype string) (map[string]s if chattype == "embeddings" { typeUrl = "/embeddings" } else if chattype == "text2img" { - if engine == "gitee" { + if req.Engine == "gitee" { typeUrl = "/text-to-image" } else { typeUrl = "/images/generations" diff --git a/godo/cmd/main.go b/godo/cmd/main.go index dd1caf4..2257721 100644 --- a/godo/cmd/main.go +++ b/godo/cmd/main.go @@ -20,6 +20,7 @@ package cmd import ( "context" + "godo/ai/search" model "godo/ai/server" "godo/deps" "godo/files" @@ -147,6 +148,7 @@ func OsStart() { aiRouter.HandleFunc("/refreshOllama", model.RefreshOllamaHandler).Methods(http.MethodGet) aiRouter.HandleFunc("/chat", model.ChatHandler).Methods(http.MethodPost) aiRouter.HandleFunc("/embeddings", model.EmbeddingHandler).Methods(http.MethodPost) + aiRouter.HandleFunc("/searchweb", search.SearchWebhandler).Methods(http.MethodGet) //注册浏览器路由 ieRouter := router.PathPrefix("/ie").Subrouter() ieRouter.HandleFunc("/navigate", store.HandleNavigate).Methods(http.MethodGet) diff --git a/godo/go.mod b/godo/go.mod index 6f6df44..c66af0d 100644 --- a/godo/go.mod +++ b/godo/go.mod @@ -16,7 +16,7 @@ require ( github.com/ncruces/go-sqlite3/gormlite v0.21.0 github.com/richardlehane/mscfb v1.0.4 github.com/shirou/gopsutil v3.21.11+incompatible - golang.org/x/net v0.32.0 + golang.org/x/net v0.33.0 golang.org/x/sys v0.28.0 golang.org/x/text v0.21.0 gorm.io/gorm v1.25.12 @@ -24,15 +24,22 @@ require ( require ( aead.dev/minisign v0.2.0 // indirect + github.com/PuerkitoBio/goquery v1.10.1 // indirect + github.com/andybalholm/cascadia v1.3.3 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/ncruces/julianday v1.0.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/richardlehane/msoleps v1.0.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/stretchr/testify v1.10.0 // indirect github.com/tetratelabs/wazero v1.8.2 // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect github.com/tklauser/numcpus v0.9.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect golang.org/x/crypto v0.31.0 // indirect + jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056 // indirect ) diff --git a/godo/go.sum b/godo/go.sum index 741357f..99bf89e 100644 --- a/godo/go.sum +++ b/godo/go.sum @@ -2,6 +2,10 @@ aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk= aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ= github.com/EndFirstCorp/peekingReader v0.0.0-20171012052444-257fb6f1a1a6 h1:t27CGFMv8DwGwqRPEa2VNof5I/aZwO6q2gfJhN8q0U4= github.com/EndFirstCorp/peekingReader v0.0.0-20171012052444-257fb6f1a1a6/go.mod h1:zpqkXxDsVfEIUZEWvT9yAo8OmRvSlRrcYQ3Zs8sSubA= +github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU= +github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY= +github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= +github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4= github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -13,6 +17,7 @@ github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyT github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -21,6 +26,9 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU= github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM= github.com/ncruces/go-sqlite3 v0.21.1 h1:cbzIOY3jQrXZWVsBfH9TCFj/iqqMIcJ7PLye4AAEwoQ= @@ -29,14 +37,21 @@ github.com/ncruces/go-sqlite3/gormlite v0.21.0 h1:9DsbvW9dS6uxXNFmbrNZixqAXKnIFn 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/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= github.com/richardlehane/msoleps v1.0.1 h1:RfrALnSNXzmXLbGct/P2b4xkFz4e8Gmj/0Vj9M9xC1o= github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= +github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4= @@ -45,17 +60,44 @@ github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZ github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -63,17 +105,47 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056 h1:6YFJoB+0fUH6X3xU/G2tQqCYg+PkGtnZ5nMR5rpw72g= +jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:OxvTsCwKosqQ1q7B+8FwXqg4rKZ/UG9dUW+g/VL2xH4=