diff --git a/api/nginx/nginx_log.go b/api/nginx/nginx_log.go index 09055c02..485e5005 100644 --- a/api/nginx/nginx_log.go +++ b/api/nginx/nginx_log.go @@ -1,305 +1,305 @@ package nginx import ( - "encoding/json" - "github.com/0xJacky/Nginx-UI/api" - "github.com/0xJacky/Nginx-UI/internal/logger" - "github.com/0xJacky/Nginx-UI/internal/nginx" - "github.com/gin-gonic/gin" - "github.com/gorilla/websocket" - "github.com/hpcloud/tail" - "github.com/pkg/errors" - "github.com/spf13/cast" - "io" - "net/http" - "os" - "strings" + "encoding/json" + "github.com/0xJacky/Nginx-UI/api" + "github.com/0xJacky/Nginx-UI/internal/logger" + "github.com/0xJacky/Nginx-UI/internal/nginx" + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + "github.com/hpcloud/tail" + "github.com/pkg/errors" + "github.com/spf13/cast" + "io" + "net/http" + "os" + "strings" ) const ( - PageSize = 128 * 1024 + PageSize = 128 * 1024 ) type controlStruct struct { - Type string `json:"type"` - ConfName string `json:"conf_name"` - ServerIdx int `json:"server_idx"` - DirectiveIdx int `json:"directive_idx"` + Type string `json:"type"` + ConfName string `json:"conf_name"` + ServerIdx int `json:"server_idx"` + DirectiveIdx int `json:"directive_idx"` } type nginxLogPageResp struct { - Content string `json:"content"` - Page int64 `json:"page"` + Content string `json:"content"` + Page int64 `json:"page"` } func GetNginxLogPage(c *gin.Context) { - page := cast.ToInt64(c.Query("page")) - if page < 0 { - page = 0 - } + page := cast.ToInt64(c.Query("page")) + if page < 0 { + page = 0 + } - var control controlStruct - if !api.BindAndValid(c, &control) { - return - } + var control controlStruct + if !api.BindAndValid(c, &control) { + return + } - logPath, err := getLogPath(&control) + logPath, err := getLogPath(&control) - if err != nil { - logger.Error(err) - return - } + if err != nil { + logger.Error(err) + return + } - logFileStat, err := os.Stat(logPath) + logFileStat, err := os.Stat(logPath) - if err != nil { - c.JSON(http.StatusOK, nginxLogPageResp{}) - logger.Error(err) - return - } + if err != nil { + c.JSON(http.StatusOK, nginxLogPageResp{}) + logger.Error(err) + return + } - if !logFileStat.Mode().IsRegular() { - c.JSON(http.StatusOK, nginxLogPageResp{}) - logger.Error("log file is not regular file:", logPath) - return - } + if !logFileStat.Mode().IsRegular() { + c.JSON(http.StatusOK, nginxLogPageResp{}) + logger.Error("log file is not regular file:", logPath) + return + } - f, err := os.Open(logPath) + f, err := os.Open(logPath) - if err != nil { - c.JSON(http.StatusOK, nginxLogPageResp{}) - logger.Error(err) - return - } + if err != nil { + c.JSON(http.StatusOK, nginxLogPageResp{}) + logger.Error(err) + return + } - totalPage := logFileStat.Size() / PageSize + totalPage := logFileStat.Size() / PageSize - if logFileStat.Size()%PageSize > 0 { - totalPage++ - } + if logFileStat.Size()%PageSize > 0 { + totalPage++ + } - var buf []byte - var offset int64 - if page == 0 { - page = totalPage - } + var buf []byte + var offset int64 + if page == 0 { + page = totalPage + } - buf = make([]byte, PageSize) - offset = (page - 1) * PageSize + buf = make([]byte, PageSize) + offset = (page - 1) * PageSize - // seek - _, err = f.Seek(offset, io.SeekStart) - if err != nil && err != io.EOF { - c.JSON(http.StatusOK, nginxLogPageResp{}) - logger.Error(err) - return - } + // seek + _, err = f.Seek(offset, io.SeekStart) + if err != nil && err != io.EOF { + c.JSON(http.StatusOK, nginxLogPageResp{}) + logger.Error(err) + return + } - n, err := f.Read(buf) + n, err := f.Read(buf) - if err != nil && err != io.EOF { - c.JSON(http.StatusOK, nginxLogPageResp{}) - logger.Error(err) - return - } + if err != nil && err != io.EOF { + c.JSON(http.StatusOK, nginxLogPageResp{}) + logger.Error(err) + return + } - c.JSON(http.StatusOK, nginxLogPageResp{ - Page: page, - Content: string(buf[:n]), - }) + c.JSON(http.StatusOK, nginxLogPageResp{ + Page: page, + Content: string(buf[:n]), + }) } func getLogPath(control *controlStruct) (logPath string, err error) { - switch control.Type { - case "site": - var config *nginx.NgxConfig - path := nginx.GetConfPath("sites-available", control.ConfName) - config, err = nginx.ParseNgxConfig(path) - if err != nil { - err = errors.Wrap(err, "error parsing ngx config") - return - } + switch control.Type { + case "site": + var config *nginx.NgxConfig + path := nginx.GetConfPath("sites-available", control.ConfName) + config, err = nginx.ParseNgxConfig(path) + if err != nil { + err = errors.Wrap(err, "error parsing ngx config") + return + } - if control.ServerIdx >= len(config.Servers) { - err = errors.New("serverIdx out of range") - return - } + if control.ServerIdx >= len(config.Servers) { + err = errors.New("serverIdx out of range") + return + } - if control.DirectiveIdx >= len(config.Servers[control.ServerIdx].Directives) { - err = errors.New("DirectiveIdx out of range") - return - } + if control.DirectiveIdx >= len(config.Servers[control.ServerIdx].Directives) { + err = errors.New("DirectiveIdx out of range") + return + } - directive := config.Servers[control.ServerIdx].Directives[control.DirectiveIdx] - switch directive.Directive { - case "access_log", "error_log": - // ok - default: - err = errors.New("directive.Params neither access_log nor error_log") - return - } + directive := config.Servers[control.ServerIdx].Directives[control.DirectiveIdx] + switch directive.Directive { + case "access_log", "error_log": + // ok + default: + err = errors.New("directive.Params neither access_log nor error_log") + return + } - if directive.Params == "" { - err = errors.New("directive.Params is empty") - return - } + if directive.Params == "" { + err = errors.New("directive.Params is empty") + return + } - // fix: access_log /var/log/test.log main; - p := strings.Split(directive.Params, " ") - if len(p) > 0 { - logPath = p[0] - } + // fix: access_log /var/log/test.log main; + p := strings.Split(directive.Params, " ") + if len(p) > 0 { + logPath = p[0] + } - case "error": - path := nginx.GetErrorLogPath() + case "error": + path := nginx.GetErrorLogPath() - if path == "" { - err = errors.New("settings.NginxLogSettings.ErrorLogPath is empty," + - " refer to https://nginxui.com/guide/config-nginx.html for more information") - return - } + if path == "" { + err = errors.New("settings.NginxLogSettings.ErrorLogPath is empty," + + " refer to https://nginxui.com/guide/config-nginx.html for more information") + return + } - logPath = path - default: - path := nginx.GetAccessLogPath() + logPath = path + default: + path := nginx.GetAccessLogPath() - if path == "" { - err = errors.New("settings.NginxLogSettings.AccessLogPath is empty," + - " refer to https://nginxui.com/guide/config-nginx.html for more information") - return - } + if path == "" { + err = errors.New("settings.NginxLogSettings.AccessLogPath is empty," + + " refer to https://nginxui.com/guide/config-nginx.html for more information") + return + } - logPath = path - } + logPath = path + } - return + return } func tailNginxLog(ws *websocket.Conn, controlChan chan controlStruct, errChan chan error) { - defer func() { - if err := recover(); err != nil { - logger.Error(err) - return - } - }() + defer func() { + if err := recover(); err != nil { + logger.Error(err) + return + } + }() - control := <-controlChan + control := <-controlChan - for { - logPath, err := getLogPath(&control) + for { + logPath, err := getLogPath(&control) - if err != nil { - errChan <- err - return - } + if err != nil { + errChan <- err + return + } - seek := tail.SeekInfo{ - Offset: 0, - Whence: io.SeekEnd, - } + seek := tail.SeekInfo{ + Offset: 0, + Whence: io.SeekEnd, + } - stat, err := os.Stat(logPath) - if os.IsNotExist(err) { - errChan <- errors.New("[error] log path not exists " + logPath) - return - } + stat, err := os.Stat(logPath) + if os.IsNotExist(err) { + errChan <- errors.New("[error] log path not exists " + logPath) + return + } - if !stat.Mode().IsRegular() { - errChan <- errors.New("[error] " + logPath + " is not a regular file. " + - "If you are using nginx-ui in docker container, please refer to " + - "https://nginxui.com/zh_CN/guide/config-nginx-log.html for more information.") - return - } + if !stat.Mode().IsRegular() { + errChan <- errors.New("[error] " + logPath + " is not a regular file. " + + "If you are using nginx-ui in docker container, please refer to " + + "https://nginxui.com/zh_CN/guide/config-nginx-log.html for more information.") + return + } - // Create a tail - t, err := tail.TailFile(logPath, tail.Config{Follow: true, - ReOpen: true, Location: &seek}) + // Create a tail + t, err := tail.TailFile(logPath, tail.Config{Follow: true, + ReOpen: true, Location: &seek}) - if err != nil { - errChan <- errors.Wrap(err, "error tailing log") - return - } + if err != nil { + errChan <- errors.Wrap(err, "error tailing log") + return + } - for { - var next = false - select { - case line := <-t.Lines: - // Print the text of each received line - if line == nil { - continue - } + for { + var next = false + select { + case line := <-t.Lines: + // Print the text of each received line + if line == nil { + continue + } - err = ws.WriteMessage(websocket.TextMessage, []byte(line.Text)) + err = ws.WriteMessage(websocket.TextMessage, []byte(line.Text)) - if err != nil && websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) { - errChan <- errors.Wrap(err, "error tailNginxLog write message") - return - } - case control = <-controlChan: - next = true - break - } - if next { - break - } - } - } + if err != nil && websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) { + errChan <- errors.Wrap(err, "error tailNginxLog write message") + return + } + case control = <-controlChan: + next = true + break + } + if next { + break + } + } + } } func handleLogControl(ws *websocket.Conn, controlChan chan controlStruct, errChan chan error) { - defer func() { - if err := recover(); err != nil { - logger.Error(err) - return - } - }() + defer func() { + if err := recover(); err != nil { + logger.Error(err) + return + } + }() - for { - msgType, payload, err := ws.ReadMessage() - if err != nil && websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) { - errChan <- errors.Wrap(err, "error handleLogControl read message") - return - } + for { + msgType, payload, err := ws.ReadMessage() + if err != nil && websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) { + errChan <- errors.Wrap(err, "error handleLogControl read message") + return + } - if msgType != websocket.TextMessage { - errChan <- errors.New("error handleLogControl message type") - return - } + if msgType != websocket.TextMessage { + errChan <- errors.New("error handleLogControl message type") + return + } - var msg controlStruct - err = json.Unmarshal(payload, &msg) - if err != nil { - errChan <- errors.Wrap(err, "error ReadWsAndWritePty json.Unmarshal") - return - } - controlChan <- msg - } + var msg controlStruct + err = json.Unmarshal(payload, &msg) + if err != nil { + errChan <- errors.Wrap(err, "error ReadWsAndWritePty json.Unmarshal") + return + } + controlChan <- msg + } } func Log(c *gin.Context) { - var upGrader = websocket.Upgrader{ - CheckOrigin: func(r *http.Request) bool { - return true - }, - } - // upgrade http to websocket - ws, err := upGrader.Upgrade(c.Writer, c.Request, nil) - if err != nil { - logger.Error(err) - return - } + var upGrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, + } + // upgrade http to websocket + ws, err := upGrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + logger.Error(err) + return + } - defer ws.Close() + defer ws.Close() - errChan := make(chan error, 1) - controlChan := make(chan controlStruct, 1) + errChan := make(chan error, 1) + controlChan := make(chan controlStruct, 1) - go tailNginxLog(ws, controlChan, errChan) - go handleLogControl(ws, controlChan, errChan) + go tailNginxLog(ws, controlChan, errChan) + go handleLogControl(ws, controlChan, errChan) - if err = <-errChan; err != nil { - logger.Error(err) - _ = ws.WriteMessage(websocket.TextMessage, []byte(err.Error())) - return - } + if err = <-errChan; err != nil { + logger.Error(err) + _ = ws.WriteMessage(websocket.TextMessage, []byte(err.Error())) + return + } } diff --git a/api/openai/openai.go b/api/openai/openai.go index 6e15bca6..a0b4fc6c 100644 --- a/api/openai/openai.go +++ b/api/openai/openai.go @@ -1,134 +1,123 @@ package openai import ( - "context" - "crypto/tls" - "fmt" - "github.com/0xJacky/Nginx-UI/api" - "github.com/0xJacky/Nginx-UI/settings" - "github.com/gin-gonic/gin" - "github.com/pkg/errors" - "github.com/sashabaranov/go-openai" - "io" - "net/http" - "net/url" - "os" + "context" + "crypto/tls" + "fmt" + "github.com/0xJacky/Nginx-UI/api" + "github.com/0xJacky/Nginx-UI/settings" + "github.com/gin-gonic/gin" + "github.com/pkg/errors" + "github.com/sashabaranov/go-openai" + "io" + "net/http" + "net/url" + "os" ) const ChatGPTInitPrompt = "You are a assistant who can help users write and optimise the configurations of Nginx, the first user message contains the content of the configuration file which is currently opened by the user and the current language code(CLC). You suppose to use the language corresponding to the CLC to give the first reply. Later the language environment depends on the user message. The first reply should involve the key information of the file and ask user what can you help them." func MakeChatCompletionRequest(c *gin.Context) { - var json struct { - Messages []openai.ChatCompletionMessage `json:"messages"` - } + var json struct { + Messages []openai.ChatCompletionMessage `json:"messages"` + } - if !api.BindAndValid(c, &json) { - return - } + if !api.BindAndValid(c, &json) { + return + } - messages := []openai.ChatCompletionMessage{ - { - Role: openai.ChatMessageRoleSystem, - Content: ChatGPTInitPrompt, - }, - } - messages = append(messages, json.Messages...) - // sse server - c.Writer.Header().Set("Content-Type", "text/event-stream; charset=utf-8") - c.Writer.Header().Set("Cache-Control", "no-cache") - c.Writer.Header().Set("Connection", "keep-alive") - c.Writer.Header().Set("Access-Control-Allow-Origin", "*") + messages := []openai.ChatCompletionMessage{ + { + Role: openai.ChatMessageRoleSystem, + Content: ChatGPTInitPrompt, + }, + } + messages = append(messages, json.Messages...) + // sse server + c.Writer.Header().Set("Content-Type", "text/event-stream; charset=utf-8") + c.Writer.Header().Set("Cache-Control", "no-cache") + c.Writer.Header().Set("Connection", "keep-alive") + c.Writer.Header().Set("Access-Control-Allow-Origin", "*") - if settings.OpenAISettings.Token == "" { - c.Stream(func(w io.Writer) bool { - c.SSEvent("message", gin.H{ - "type": "error", - "content": "[Error] OpenAI token is empty", - }) - return false - }) - return - } + config := openai.DefaultConfig(settings.OpenAISettings.Token) - config := openai.DefaultConfig(settings.OpenAISettings.Token) + if settings.OpenAISettings.Proxy != "" { + proxyUrl, err := url.Parse(settings.OpenAISettings.Proxy) + if err != nil { + c.Stream(func(w io.Writer) bool { + c.SSEvent("message", gin.H{ + "type": "error", + "content": err.Error(), + }) + return false + }) + return + } + transport := &http.Transport{ + Proxy: http.ProxyURL(proxyUrl), + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + config.HTTPClient = &http.Client{ + Transport: transport, + } + } - if settings.OpenAISettings.Proxy != "" { - proxyUrl, err := url.Parse(settings.OpenAISettings.Proxy) - if err != nil { - c.Stream(func(w io.Writer) bool { - c.SSEvent("message", gin.H{ - "type": "error", - "content": err.Error(), - }) - return false - }) - return - } - transport := &http.Transport{ - Proxy: http.ProxyURL(proxyUrl), - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - config.HTTPClient = &http.Client{ - Transport: transport, - } - } + if settings.OpenAISettings.BaseUrl != "" { + config.BaseURL = settings.OpenAISettings.BaseUrl + } - if settings.OpenAISettings.BaseUrl != "" { - config.BaseURL = settings.OpenAISettings.BaseUrl - } + openaiClient := openai.NewClientWithConfig(config) + ctx := context.Background() - openaiClient := openai.NewClientWithConfig(config) - ctx := context.Background() + req := openai.ChatCompletionRequest{ + Model: settings.OpenAISettings.Model, + Messages: messages, + Stream: true, + } + stream, err := openaiClient.CreateChatCompletionStream(ctx, req) + if err != nil { + fmt.Printf("CompletionStream error: %v\n", err) + c.Stream(func(w io.Writer) bool { + c.SSEvent("message", gin.H{ + "type": "error", + "content": err.Error(), + }) + return false + }) + return + } + defer stream.Close() + msgChan := make(chan string) + go func() { + defer close(msgChan) + for { + response, err := stream.Recv() + if errors.Is(err, io.EOF) { + fmt.Println() + return + } - req := openai.ChatCompletionRequest{ - Model: settings.OpenAISettings.Model, - Messages: messages, - Stream: true, - } - stream, err := openaiClient.CreateChatCompletionStream(ctx, req) - if err != nil { - fmt.Printf("CompletionStream error: %v\n", err) - c.Stream(func(w io.Writer) bool { - c.SSEvent("message", gin.H{ - "type": "error", - "content": err.Error(), - }) - return false - }) - return - } - defer stream.Close() - msgChan := make(chan string) - go func() { - defer close(msgChan) - for { - response, err := stream.Recv() - if errors.Is(err, io.EOF) { - fmt.Println() - return - } + if err != nil { + fmt.Printf("Stream error: %v\n", err) + return + } - if err != nil { - fmt.Printf("Stream error: %v\n", err) - return - } + message := fmt.Sprintf("%s", response.Choices[0].Delta.Content) + fmt.Printf("%s", message) + _ = os.Stdout.Sync() - message := fmt.Sprintf("%s", response.Choices[0].Delta.Content) - fmt.Printf("%s", message) - _ = os.Stdout.Sync() + msgChan <- message + } + }() - msgChan <- message - } - }() - - c.Stream(func(w io.Writer) bool { - if m, ok := <-msgChan; ok { - c.SSEvent("message", gin.H{ - "type": "message", - "content": m, - }) - return true - } - return false - }) + c.Stream(func(w io.Writer) bool { + if m, ok := <-msgChan; ok { + c.SSEvent("message", gin.H{ + "type": "message", + "content": m, + }) + return true + } + return false + }) } diff --git a/app/components.d.ts b/app/components.d.ts index fa57e402..2a34d87a 100644 --- a/app/components.d.ts +++ b/app/components.d.ts @@ -8,6 +8,7 @@ export {} declare module 'vue' { export interface GlobalComponents { AAlert: typeof import('ant-design-vue/es')['Alert'] + AAutoComplete: typeof import('ant-design-vue/es')['AutoComplete'] AAvatar: typeof import('ant-design-vue/es')['Avatar'] ABadge: typeof import('ant-design-vue/es')['Badge'] ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb'] diff --git a/app/src/views/preference/OpenAISettings.vue b/app/src/views/preference/OpenAISettings.vue index 51d6ce64..58c124ad 100644 --- a/app/src/views/preference/OpenAISettings.vue +++ b/app/src/views/preference/OpenAISettings.vue @@ -4,25 +4,30 @@ import type { Settings } from '@/views/preference/typedef' const data: Settings = inject('data')! const errors: Record> = inject('errors') as Record> + +const models = shallowRef([ + { + value: 'gpt-4-1106-preview', + }, + { + value: 'gpt-4', + }, + { + value: 'gpt-4-32k', + }, + { + value: 'gpt-3.5-turbo', + }, +])