diff --git a/kernel/api/ai.go b/kernel/api/ai.go new file mode 100644 index 000000000..7e4a592ec --- /dev/null +++ b/kernel/api/ai.go @@ -0,0 +1,38 @@ +// SiYuan - Build Your Eternal Digital Garden +// Copyright (c) 2020-present, b3log.org +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package api + +import ( + "net/http" + + "github.com/88250/gulu" + "github.com/gin-gonic/gin" + "github.com/siyuan-note/siyuan/kernel/util" +) + +func chatGPT(c *gin.Context) { + ret := gulu.Ret.NewResult() + defer c.JSON(http.StatusOK, ret) + + arg, ok := util.JsonArg(c, ret) + if !ok { + return + } + + msg := arg["msg"].(string) + ret.Data = util.ChatGPT(msg) +} diff --git a/kernel/api/router.go b/kernel/api/router.go index 32a28127f..32ec0addb 100644 --- a/kernel/api/router.go +++ b/kernel/api/router.go @@ -327,4 +327,6 @@ func ServeAPI(ginServer *gin.Engine) { ginServer.Handle("GET", "/snippets/*filepath", serveSnippets) ginServer.Handle("POST", "/api/av/renderAttributeView", model.CheckAuth, renderAttributeView) + + ginServer.Handle("POST", "/api/ai/chatGPT", model.CheckAuth, chatGPT) } diff --git a/kernel/go.mod b/kernel/go.mod index 27b9851d3..21b189afb 100644 --- a/kernel/go.mod +++ b/kernel/go.mod @@ -40,6 +40,7 @@ require ( github.com/panjf2000/ants/v2 v2.7.1 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/radovskyb/watcher v1.0.7 + github.com/sashabaranov/go-gpt3 v1.3.3 github.com/shirou/gopsutil/v3 v3.23.1 github.com/siyuan-note/dejavu v0.0.0-20230223101417-691d54268764 github.com/siyuan-note/encryption v0.0.0-20220713091850-5ecd92177b75 diff --git a/kernel/go.sum b/kernel/go.sum index b05a3dd8c..b7d7c5a9f 100644 --- a/kernel/go.sum +++ b/kernel/go.sum @@ -266,6 +266,8 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= +github.com/sashabaranov/go-gpt3 v1.3.3 h1:S8Zd4YybnBaNMK+w+XGGWgsjQY1R+6QE2n9SLzVna9k= +github.com/sashabaranov/go-gpt3 v1.3.3/go.mod h1:BIZdbwdzxZbCrcKGMGH6u2eyGe1xFuX9Anmh3tCP8lQ= github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= github.com/shirou/gopsutil/v3 v3.23.1 h1:a9KKO+kGLKEvcPIs4W62v0nu3sciVDOOOPUD0Hz7z/4= github.com/shirou/gopsutil/v3 v3.23.1/go.mod h1:NN6mnm5/0k8jw4cBfCnJtr5L7ErOTg18tMNpgFkn0hA= diff --git a/kernel/util/openai.go b/kernel/util/openai.go new file mode 100644 index 000000000..48db08080 --- /dev/null +++ b/kernel/util/openai.go @@ -0,0 +1,111 @@ +// SiYuan - Build Your Eternal Digital Garden +// Copyright (c) 2020-present, b3log.org +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package util + +import ( + "context" + "net/http" + "net/url" + "os" + "strconv" + "strings" + "time" + + gogpt "github.com/sashabaranov/go-gpt3" + "github.com/siyuan-note/logging" +) + +var ( + OpenAIAPIKey = "" + OpenAIAPITimeout = 15 * time.Second + OpenAIAPIProxy = "" + OpenAIAPIMaxTokens = 4096 +) + +func ChatGPT(msg string) (ret string) { + if "" == OpenAIAPIKey { + return + } + + config := gogpt.DefaultConfig(OpenAIAPIKey) + if "" != OpenAIAPIProxy { + proxyUrl, err := url.Parse(OpenAIAPIProxy) + if nil != err { + logging.LogErrorf("OpenAI API proxy error: %v", err) + } else { + config.HTTPClient = &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyUrl)}} + } + } + + c := gogpt.NewClientWithConfig(config) + ctx, cancel := context.WithTimeout(context.Background(), OpenAIAPITimeout) + defer cancel() + req := gogpt.ChatCompletionRequest{ + Model: gogpt.GPT3Dot5Turbo, + MaxTokens: OpenAIAPIMaxTokens, + Messages: []gogpt.ChatCompletionMessage{ + { + Role: "user", + Content: msg, + }, + }, + } + resp, err := c.CreateChatCompletion(ctx, req) + if nil != err { + logging.LogErrorf("create chat completion failed: %s", err) + return + } + + if 0 < len(resp.Choices) { + ret = resp.Choices[0].Message.Content + ret = strings.TrimSpace(ret) + } + return +} + +func initOpenAI() { + OpenAIAPIKey = os.Getenv("SIYUAN_OPENAI_API_KEY") + if "" == OpenAIAPIKey { + return + } + + timeout := os.Getenv("SIYUAN_OPENAI_API_TIMEOUT") + if "" != timeout { + timeoutInt, err := strconv.Atoi(timeout) + if nil == err { + OpenAIAPITimeout = time.Duration(timeoutInt) * time.Second + } + } + + proxy := os.Getenv("SIYUAN_OPENAI_API_PROXY") + if "" != proxy { + OpenAIAPIProxy = proxy + } + + maxTokens := os.Getenv("SIYUAN_OPENAI_API_MAX_TOKENS") + if "" != maxTokens { + maxTokensInt, err := strconv.Atoi(maxTokens) + if nil == err { + OpenAIAPIMaxTokens = maxTokensInt + } + } + if 1 > OpenAIAPIMaxTokens { + OpenAIAPIMaxTokens = 4096 + } + + logging.LogInfof("OpenAI API enabled [maxTokens=%d, timeout=%ds, proxy=%s]", OpenAIAPIMaxTokens, int(OpenAIAPITimeout.Seconds()), OpenAIAPIProxy) +} diff --git a/kernel/util/working.go b/kernel/util/working.go index fe3771968..f7df1f374 100644 --- a/kernel/util/working.go +++ b/kernel/util/working.go @@ -118,6 +118,8 @@ func Boot() { bootBanner := figure.NewColorFigure("SiYuan", "isometric3", "green", true) logging.LogInfof("\n" + bootBanner.String()) logBootInfo() + + initOpenAI() } func setBootDetails(details string) {