mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
feat(chat): support other local llm #331
This commit is contained in:
parent
08631437ee
commit
3b116b3654
4 changed files with 356 additions and 361 deletions
|
@ -1,305 +1,305 @@
|
||||||
package nginx
|
package nginx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/0xJacky/Nginx-UI/api"
|
"github.com/0xJacky/Nginx-UI/api"
|
||||||
"github.com/0xJacky/Nginx-UI/internal/logger"
|
"github.com/0xJacky/Nginx-UI/internal/logger"
|
||||||
"github.com/0xJacky/Nginx-UI/internal/nginx"
|
"github.com/0xJacky/Nginx-UI/internal/nginx"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/hpcloud/tail"
|
"github.com/hpcloud/tail"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PageSize = 128 * 1024
|
PageSize = 128 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
type controlStruct struct {
|
type controlStruct struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
ConfName string `json:"conf_name"`
|
ConfName string `json:"conf_name"`
|
||||||
ServerIdx int `json:"server_idx"`
|
ServerIdx int `json:"server_idx"`
|
||||||
DirectiveIdx int `json:"directive_idx"`
|
DirectiveIdx int `json:"directive_idx"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type nginxLogPageResp struct {
|
type nginxLogPageResp struct {
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
Page int64 `json:"page"`
|
Page int64 `json:"page"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetNginxLogPage(c *gin.Context) {
|
func GetNginxLogPage(c *gin.Context) {
|
||||||
page := cast.ToInt64(c.Query("page"))
|
page := cast.ToInt64(c.Query("page"))
|
||||||
if page < 0 {
|
if page < 0 {
|
||||||
page = 0
|
page = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
var control controlStruct
|
var control controlStruct
|
||||||
if !api.BindAndValid(c, &control) {
|
if !api.BindAndValid(c, &control) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logPath, err := getLogPath(&control)
|
logPath, err := getLogPath(&control)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logFileStat, err := os.Stat(logPath)
|
logFileStat, err := os.Stat(logPath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusOK, nginxLogPageResp{})
|
c.JSON(http.StatusOK, nginxLogPageResp{})
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !logFileStat.Mode().IsRegular() {
|
if !logFileStat.Mode().IsRegular() {
|
||||||
c.JSON(http.StatusOK, nginxLogPageResp{})
|
c.JSON(http.StatusOK, nginxLogPageResp{})
|
||||||
logger.Error("log file is not regular file:", logPath)
|
logger.Error("log file is not regular file:", logPath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Open(logPath)
|
f, err := os.Open(logPath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusOK, nginxLogPageResp{})
|
c.JSON(http.StatusOK, nginxLogPageResp{})
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
totalPage := logFileStat.Size() / PageSize
|
totalPage := logFileStat.Size() / PageSize
|
||||||
|
|
||||||
if logFileStat.Size()%PageSize > 0 {
|
if logFileStat.Size()%PageSize > 0 {
|
||||||
totalPage++
|
totalPage++
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf []byte
|
var buf []byte
|
||||||
var offset int64
|
var offset int64
|
||||||
if page == 0 {
|
if page == 0 {
|
||||||
page = totalPage
|
page = totalPage
|
||||||
}
|
}
|
||||||
|
|
||||||
buf = make([]byte, PageSize)
|
buf = make([]byte, PageSize)
|
||||||
offset = (page - 1) * PageSize
|
offset = (page - 1) * PageSize
|
||||||
|
|
||||||
// seek
|
// seek
|
||||||
_, err = f.Seek(offset, io.SeekStart)
|
_, err = f.Seek(offset, io.SeekStart)
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
c.JSON(http.StatusOK, nginxLogPageResp{})
|
c.JSON(http.StatusOK, nginxLogPageResp{})
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
n, err := f.Read(buf)
|
n, err := f.Read(buf)
|
||||||
|
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
c.JSON(http.StatusOK, nginxLogPageResp{})
|
c.JSON(http.StatusOK, nginxLogPageResp{})
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, nginxLogPageResp{
|
c.JSON(http.StatusOK, nginxLogPageResp{
|
||||||
Page: page,
|
Page: page,
|
||||||
Content: string(buf[:n]),
|
Content: string(buf[:n]),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLogPath(control *controlStruct) (logPath string, err error) {
|
func getLogPath(control *controlStruct) (logPath string, err error) {
|
||||||
switch control.Type {
|
switch control.Type {
|
||||||
case "site":
|
case "site":
|
||||||
var config *nginx.NgxConfig
|
var config *nginx.NgxConfig
|
||||||
path := nginx.GetConfPath("sites-available", control.ConfName)
|
path := nginx.GetConfPath("sites-available", control.ConfName)
|
||||||
config, err = nginx.ParseNgxConfig(path)
|
config, err = nginx.ParseNgxConfig(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.Wrap(err, "error parsing ngx config")
|
err = errors.Wrap(err, "error parsing ngx config")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if control.ServerIdx >= len(config.Servers) {
|
if control.ServerIdx >= len(config.Servers) {
|
||||||
err = errors.New("serverIdx out of range")
|
err = errors.New("serverIdx out of range")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if control.DirectiveIdx >= len(config.Servers[control.ServerIdx].Directives) {
|
if control.DirectiveIdx >= len(config.Servers[control.ServerIdx].Directives) {
|
||||||
err = errors.New("DirectiveIdx out of range")
|
err = errors.New("DirectiveIdx out of range")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
directive := config.Servers[control.ServerIdx].Directives[control.DirectiveIdx]
|
directive := config.Servers[control.ServerIdx].Directives[control.DirectiveIdx]
|
||||||
switch directive.Directive {
|
switch directive.Directive {
|
||||||
case "access_log", "error_log":
|
case "access_log", "error_log":
|
||||||
// ok
|
// ok
|
||||||
default:
|
default:
|
||||||
err = errors.New("directive.Params neither access_log nor error_log")
|
err = errors.New("directive.Params neither access_log nor error_log")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if directive.Params == "" {
|
if directive.Params == "" {
|
||||||
err = errors.New("directive.Params is empty")
|
err = errors.New("directive.Params is empty")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// fix: access_log /var/log/test.log main;
|
// fix: access_log /var/log/test.log main;
|
||||||
p := strings.Split(directive.Params, " ")
|
p := strings.Split(directive.Params, " ")
|
||||||
if len(p) > 0 {
|
if len(p) > 0 {
|
||||||
logPath = p[0]
|
logPath = p[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
case "error":
|
case "error":
|
||||||
path := nginx.GetErrorLogPath()
|
path := nginx.GetErrorLogPath()
|
||||||
|
|
||||||
if path == "" {
|
if path == "" {
|
||||||
err = errors.New("settings.NginxLogSettings.ErrorLogPath is empty," +
|
err = errors.New("settings.NginxLogSettings.ErrorLogPath is empty," +
|
||||||
" refer to https://nginxui.com/guide/config-nginx.html for more information")
|
" refer to https://nginxui.com/guide/config-nginx.html for more information")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logPath = path
|
logPath = path
|
||||||
default:
|
default:
|
||||||
path := nginx.GetAccessLogPath()
|
path := nginx.GetAccessLogPath()
|
||||||
|
|
||||||
if path == "" {
|
if path == "" {
|
||||||
err = errors.New("settings.NginxLogSettings.AccessLogPath is empty," +
|
err = errors.New("settings.NginxLogSettings.AccessLogPath is empty," +
|
||||||
" refer to https://nginxui.com/guide/config-nginx.html for more information")
|
" refer to https://nginxui.com/guide/config-nginx.html for more information")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logPath = path
|
logPath = path
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func tailNginxLog(ws *websocket.Conn, controlChan chan controlStruct, errChan chan error) {
|
func tailNginxLog(ws *websocket.Conn, controlChan chan controlStruct, errChan chan error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
control := <-controlChan
|
control := <-controlChan
|
||||||
|
|
||||||
for {
|
for {
|
||||||
logPath, err := getLogPath(&control)
|
logPath, err := getLogPath(&control)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- err
|
errChan <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
seek := tail.SeekInfo{
|
seek := tail.SeekInfo{
|
||||||
Offset: 0,
|
Offset: 0,
|
||||||
Whence: io.SeekEnd,
|
Whence: io.SeekEnd,
|
||||||
}
|
}
|
||||||
|
|
||||||
stat, err := os.Stat(logPath)
|
stat, err := os.Stat(logPath)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
errChan <- errors.New("[error] log path not exists " + logPath)
|
errChan <- errors.New("[error] log path not exists " + logPath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !stat.Mode().IsRegular() {
|
if !stat.Mode().IsRegular() {
|
||||||
errChan <- errors.New("[error] " + logPath + " is not a regular file. " +
|
errChan <- errors.New("[error] " + logPath + " is not a regular file. " +
|
||||||
"If you are using nginx-ui in docker container, please refer to " +
|
"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.")
|
"https://nginxui.com/zh_CN/guide/config-nginx-log.html for more information.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a tail
|
// Create a tail
|
||||||
t, err := tail.TailFile(logPath, tail.Config{Follow: true,
|
t, err := tail.TailFile(logPath, tail.Config{Follow: true,
|
||||||
ReOpen: true, Location: &seek})
|
ReOpen: true, Location: &seek})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- errors.Wrap(err, "error tailing log")
|
errChan <- errors.Wrap(err, "error tailing log")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var next = false
|
var next = false
|
||||||
select {
|
select {
|
||||||
case line := <-t.Lines:
|
case line := <-t.Lines:
|
||||||
// Print the text of each received line
|
// Print the text of each received line
|
||||||
if line == nil {
|
if line == nil {
|
||||||
continue
|
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) {
|
if err != nil && websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) {
|
||||||
errChan <- errors.Wrap(err, "error tailNginxLog write message")
|
errChan <- errors.Wrap(err, "error tailNginxLog write message")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case control = <-controlChan:
|
case control = <-controlChan:
|
||||||
next = true
|
next = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if next {
|
if next {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleLogControl(ws *websocket.Conn, controlChan chan controlStruct, errChan chan error) {
|
func handleLogControl(ws *websocket.Conn, controlChan chan controlStruct, errChan chan error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
msgType, payload, err := ws.ReadMessage()
|
msgType, payload, err := ws.ReadMessage()
|
||||||
if err != nil && websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) {
|
if err != nil && websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) {
|
||||||
errChan <- errors.Wrap(err, "error handleLogControl read message")
|
errChan <- errors.Wrap(err, "error handleLogControl read message")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if msgType != websocket.TextMessage {
|
if msgType != websocket.TextMessage {
|
||||||
errChan <- errors.New("error handleLogControl message type")
|
errChan <- errors.New("error handleLogControl message type")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg controlStruct
|
var msg controlStruct
|
||||||
err = json.Unmarshal(payload, &msg)
|
err = json.Unmarshal(payload, &msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- errors.Wrap(err, "error ReadWsAndWritePty json.Unmarshal")
|
errChan <- errors.Wrap(err, "error ReadWsAndWritePty json.Unmarshal")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
controlChan <- msg
|
controlChan <- msg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Log(c *gin.Context) {
|
func Log(c *gin.Context) {
|
||||||
var upGrader = websocket.Upgrader{
|
var upGrader = websocket.Upgrader{
|
||||||
CheckOrigin: func(r *http.Request) bool {
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// upgrade http to websocket
|
// upgrade http to websocket
|
||||||
ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer ws.Close()
|
defer ws.Close()
|
||||||
|
|
||||||
errChan := make(chan error, 1)
|
errChan := make(chan error, 1)
|
||||||
controlChan := make(chan controlStruct, 1)
|
controlChan := make(chan controlStruct, 1)
|
||||||
|
|
||||||
go tailNginxLog(ws, controlChan, errChan)
|
go tailNginxLog(ws, controlChan, errChan)
|
||||||
go handleLogControl(ws, controlChan, errChan)
|
go handleLogControl(ws, controlChan, errChan)
|
||||||
|
|
||||||
if err = <-errChan; err != nil {
|
if err = <-errChan; err != nil {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
_ = ws.WriteMessage(websocket.TextMessage, []byte(err.Error()))
|
_ = ws.WriteMessage(websocket.TextMessage, []byte(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,134 +1,123 @@
|
||||||
package openai
|
package openai
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/0xJacky/Nginx-UI/api"
|
"github.com/0xJacky/Nginx-UI/api"
|
||||||
"github.com/0xJacky/Nginx-UI/settings"
|
"github.com/0xJacky/Nginx-UI/settings"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sashabaranov/go-openai"
|
"github.com/sashabaranov/go-openai"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"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."
|
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) {
|
func MakeChatCompletionRequest(c *gin.Context) {
|
||||||
var json struct {
|
var json struct {
|
||||||
Messages []openai.ChatCompletionMessage `json:"messages"`
|
Messages []openai.ChatCompletionMessage `json:"messages"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if !api.BindAndValid(c, &json) {
|
if !api.BindAndValid(c, &json) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
messages := []openai.ChatCompletionMessage{
|
messages := []openai.ChatCompletionMessage{
|
||||||
{
|
{
|
||||||
Role: openai.ChatMessageRoleSystem,
|
Role: openai.ChatMessageRoleSystem,
|
||||||
Content: ChatGPTInitPrompt,
|
Content: ChatGPTInitPrompt,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
messages = append(messages, json.Messages...)
|
messages = append(messages, json.Messages...)
|
||||||
// sse server
|
// sse server
|
||||||
c.Writer.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
|
c.Writer.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
|
||||||
c.Writer.Header().Set("Cache-Control", "no-cache")
|
c.Writer.Header().Set("Cache-Control", "no-cache")
|
||||||
c.Writer.Header().Set("Connection", "keep-alive")
|
c.Writer.Header().Set("Connection", "keep-alive")
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
|
||||||
if settings.OpenAISettings.Token == "" {
|
config := openai.DefaultConfig(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)
|
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 != "" {
|
if settings.OpenAISettings.BaseUrl != "" {
|
||||||
proxyUrl, err := url.Parse(settings.OpenAISettings.Proxy)
|
config.BaseURL = settings.OpenAISettings.BaseUrl
|
||||||
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 != "" {
|
openaiClient := openai.NewClientWithConfig(config)
|
||||||
config.BaseURL = settings.OpenAISettings.BaseUrl
|
ctx := context.Background()
|
||||||
}
|
|
||||||
|
|
||||||
openaiClient := openai.NewClientWithConfig(config)
|
req := openai.ChatCompletionRequest{
|
||||||
ctx := context.Background()
|
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{
|
if err != nil {
|
||||||
Model: settings.OpenAISettings.Model,
|
fmt.Printf("Stream error: %v\n", err)
|
||||||
Messages: messages,
|
return
|
||||||
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 {
|
message := fmt.Sprintf("%s", response.Choices[0].Delta.Content)
|
||||||
fmt.Printf("Stream error: %v\n", err)
|
fmt.Printf("%s", message)
|
||||||
return
|
_ = os.Stdout.Sync()
|
||||||
}
|
|
||||||
|
|
||||||
message := fmt.Sprintf("%s", response.Choices[0].Delta.Content)
|
msgChan <- message
|
||||||
fmt.Printf("%s", message)
|
}
|
||||||
_ = os.Stdout.Sync()
|
}()
|
||||||
|
|
||||||
msgChan <- message
|
c.Stream(func(w io.Writer) bool {
|
||||||
}
|
if m, ok := <-msgChan; ok {
|
||||||
}()
|
c.SSEvent("message", gin.H{
|
||||||
|
"type": "message",
|
||||||
c.Stream(func(w io.Writer) bool {
|
"content": m,
|
||||||
if m, ok := <-msgChan; ok {
|
})
|
||||||
c.SSEvent("message", gin.H{
|
return true
|
||||||
"type": "message",
|
}
|
||||||
"content": m,
|
return false
|
||||||
})
|
})
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
1
app/components.d.ts
vendored
1
app/components.d.ts
vendored
|
@ -8,6 +8,7 @@ export {}
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
AAlert: typeof import('ant-design-vue/es')['Alert']
|
AAlert: typeof import('ant-design-vue/es')['Alert']
|
||||||
|
AAutoComplete: typeof import('ant-design-vue/es')['AutoComplete']
|
||||||
AAvatar: typeof import('ant-design-vue/es')['Avatar']
|
AAvatar: typeof import('ant-design-vue/es')['Avatar']
|
||||||
ABadge: typeof import('ant-design-vue/es')['Badge']
|
ABadge: typeof import('ant-design-vue/es')['Badge']
|
||||||
ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb']
|
ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb']
|
||||||
|
|
|
@ -4,25 +4,30 @@ import type { Settings } from '@/views/preference/typedef'
|
||||||
|
|
||||||
const data: Settings = inject('data')!
|
const data: Settings = inject('data')!
|
||||||
const errors: Record<string, Record<string, string>> = inject('errors') as Record<string, Record<string, string>>
|
const errors: Record<string, Record<string, string>> = inject('errors') as Record<string, Record<string, string>>
|
||||||
|
|
||||||
|
const models = shallowRef([
|
||||||
|
{
|
||||||
|
value: 'gpt-4-1106-preview',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'gpt-4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'gpt-4-32k',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'gpt-3.5-turbo',
|
||||||
|
},
|
||||||
|
])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AForm layout="vertical">
|
<AForm layout="vertical">
|
||||||
<AFormItem :label="$gettext('ChatGPT Model')">
|
<AFormItem :label="$gettext('Model')">
|
||||||
<ASelect v-model:value="data.openai.model">
|
<AAutoComplete
|
||||||
<ASelectOption value="gpt-4-1106-preview">
|
v-model:value="data.openai.model"
|
||||||
{{ $gettext('GPT-4-Turbo') }}
|
:options="models"
|
||||||
</ASelectOption>
|
/>
|
||||||
<ASelectOption value="gpt-4">
|
|
||||||
{{ $gettext('GPT-4') }}
|
|
||||||
</ASelectOption>
|
|
||||||
<ASelectOption value="gpt-4-32k">
|
|
||||||
{{ $gettext('GPT-4-32K') }}
|
|
||||||
</ASelectOption>
|
|
||||||
<ASelectOption value="gpt-3.5-turbo">
|
|
||||||
{{ $gettext('GPT-3.5-Turbo') }}
|
|
||||||
</ASelectOption>
|
|
||||||
</ASelect>
|
|
||||||
</AFormItem>
|
</AFormItem>
|
||||||
<AFormItem
|
<AFormItem
|
||||||
:label="$gettext('API Base Url')"
|
:label="$gettext('API Base Url')"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue