Browse Source

feat(openai): support azure api type #475

Jacky 4 months ago
parent
commit
ad97f973ab

+ 12 - 26
api/openai/openai.go

@@ -4,14 +4,13 @@ import (
 	"context"
 	"fmt"
 	"github.com/0xJacky/Nginx-UI/internal/chatbot"
-	"github.com/0xJacky/Nginx-UI/internal/transport"
 	"github.com/0xJacky/Nginx-UI/settings"
 	"github.com/gin-gonic/gin"
 	"github.com/pkg/errors"
 	"github.com/sashabaranov/go-openai"
 	"github.com/uozi-tech/cosy"
+	"github.com/uozi-tech/cosy/logger"
 	"io"
-	"net/http"
 )
 
 const ChatGPTInitPrompt = `You are a assistant who can help users write and optimise the configurations of Nginx,
@@ -49,30 +48,18 @@ func MakeChatCompletionRequest(c *gin.Context) {
 	c.Writer.Header().Set("Connection", "keep-alive")
 	c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
 
-	config := openai.DefaultConfig(settings.OpenAISettings.Token)
-
-	if settings.OpenAISettings.Proxy != "" {
-		t, err := transport.NewTransport(transport.WithProxy(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
+	openaiClient, err := chatbot.GetClient()
+	if err != nil {
+		c.Stream(func(w io.Writer) bool {
+			c.SSEvent("message", gin.H{
+				"type":    "error",
+				"content": err.Error(),
 			})
-			return
-		}
-		config.HTTPClient = &http.Client{
-			Transport: t,
-		}
-	}
-
-	if settings.OpenAISettings.BaseUrl != "" {
-		config.BaseURL = settings.OpenAISettings.BaseUrl
+			return false
+		})
+		return
 	}
 
-	openaiClient := openai.NewClientWithConfig(config)
 	ctx := context.Background()
 
 	req := openai.ChatCompletionRequest{
@@ -82,7 +69,7 @@ func MakeChatCompletionRequest(c *gin.Context) {
 	}
 	stream, err := openaiClient.CreateChatCompletionStream(ctx, req)
 	if err != nil {
-		fmt.Printf("CompletionStream error: %v\n", err)
+		logger.Errorf("CompletionStream error: %v\n", err)
 		c.Stream(func(w io.Writer) bool {
 			c.SSEvent("message", gin.H{
 				"type":    "error",
@@ -99,12 +86,11 @@ func MakeChatCompletionRequest(c *gin.Context) {
 		for {
 			response, err := stream.Recv()
 			if errors.Is(err, io.EOF) {
-				fmt.Println()
 				return
 			}
 
 			if err != nil {
-				fmt.Printf("Stream error: %v\n", err)
+				logger.Errorf("Stream error: %v\n", err)
 				return
 			}
 

+ 1 - 0
app/src/api/settings.ts

@@ -72,6 +72,7 @@ export interface OpenaiSettings {
   base_url: string
   proxy: string
   token: string
+  api_type: string
 }
 
 export interface TerminalSettings {

+ 1 - 1
app/src/language/LINGUAS

@@ -1 +1 @@
-ar en zh_CN zh_TW fr_FR es ru_RU vi_VN ko_KR tr_TR
+en zh_CN zh_TW fr_FR es ru_RU vi_VN ko_KR tr_TR ar

File diff suppressed because it is too large
+ 204 - 175
app/src/language/ar/app.po


File diff suppressed because it is too large
+ 186 - 143
app/src/language/en/app.po


File diff suppressed because it is too large
+ 191 - 147
app/src/language/es/app.po


File diff suppressed because it is too large
+ 187 - 143
app/src/language/fr_FR/app.po


File diff suppressed because it is too large
+ 187 - 143
app/src/language/ko_KR/app.po


File diff suppressed because it is too large
+ 191 - 152
app/src/language/messages.pot


File diff suppressed because it is too large
+ 191 - 147
app/src/language/ru_RU/app.po


File diff suppressed because it is too large
+ 191 - 147
app/src/language/tr_TR/app.po


File diff suppressed because it is too large
+ 186 - 142
app/src/language/vi_VN/app.po


File diff suppressed because it is too large
+ 186 - 147
app/src/language/zh_CN/app.po


File diff suppressed because it is too large
+ 190 - 147
app/src/language/zh_TW/app.po


+ 3 - 3
app/src/views/preference/BasicSettings.vue

@@ -30,7 +30,7 @@ const errors: Record<string, Record<string, string>> = inject('errors') as Recor
       :label="$gettext('Node name')"
       :validate-status="errors?.node?.name ? 'error' : ''"
       :help="errors?.node?.name.includes('safety_text')
-        ? $gettext('The node name should only contain letters, unicode, numbers, hyphens, dashes, and dots.')
+        ? $gettext('The node name should only contain letters, unicode, numbers, hyphens, dashes, colons, and dots.')
         : $gettext('Customize the name of local node to be displayed in the environment indicator.')"
     >
       <AInput v-model:value="data.node.name" />
@@ -51,7 +51,7 @@ const errors: Record<string, Record<string, string>> = inject('errors') as Recor
       :label="$gettext('ICP Number')"
       :validate-status="errors?.node?.icp_number ? 'error' : ''"
       :help="errors?.node?.icp_number.includes('safety_text')
-        ? $gettext('The ICP Number should only contain letters, unicode, numbers, hyphens, dashes, and dots.')
+        ? $gettext('The ICP Number should only contain letters, unicode, numbers, hyphens, dashes, colons, and dots.')
         : ''"
     >
       <AInput
@@ -63,7 +63,7 @@ const errors: Record<string, Record<string, string>> = inject('errors') as Recor
       :label="$gettext('Public Security Number')"
       :validate-status="errors?.node?.public_security_number ? 'error' : ''"
       :help="errors?.node?.public_security_number.includes('safety_text')
-        ? $gettext('The Public Security Number should only contain letters, unicode, numbers, hyphens, dashes, and dots.')
+        ? $gettext('The Public Security Number should only contain letters, unicode, numbers, hyphens, dashes, colons, and dots.')
         : ''"
     >
       <AInput

+ 15 - 2
app/src/views/preference/OpenAISettings.vue

@@ -32,7 +32,7 @@ const models = shallowRef([
       :label="$gettext('Model')"
       :validate-status="errors?.openai?.model ? 'error' : ''"
       :help="errors?.openai?.model === 'safety_text'
-        ? $gettext('The model name should only contain letters, unicode, numbers, hyphens, dashes, and dots.')
+        ? $gettext('The model name should only contain letters, unicode, numbers, hyphens, dashes, colons, and dots.')
         : ''"
     >
       <AAutoComplete
@@ -45,7 +45,7 @@ const models = shallowRef([
       :validate-status="errors?.openai?.base_url ? 'error' : ''"
       :help="errors?.openai?.base_url === 'url'
         ? $gettext('The url is invalid.')
-        : $gettext('To use a local large model, deploy it with vllm or imdeploy. '
+        : $gettext('To use a local large model, deploy it with ollama, vllm or imdeploy. '
           + 'They provide an OpenAI-compatible API endpoint, so just set the baseUrl to your local API.')"
     >
       <AInput
@@ -74,6 +74,19 @@ const models = shallowRef([
     >
       <AInputPassword v-model:value="data.openai.token" />
     </AFormItem>
+    <AFormItem
+      :label="$gettext('API Type')"
+      :validate-status="errors?.openai?.apt_type ? 'error' : ''"
+    >
+      <ASelect v-model:value="data.openai.api_type">
+        <ASelectOption value="OPEN_AI">
+          OpenAI
+        </ASelectOption>
+        <ASelectOption value="AZURE">
+          Azure
+        </ASelectOption>
+      </ASelect>
+    </AFormItem>
   </AForm>
 </template>
 

+ 1 - 0
app/src/views/preference/Preference.vue

@@ -77,6 +77,7 @@ const data = ref<Settings>({
     base_url: '',
     proxy: '',
     token: '',
+    api_type: 'OPEN_AI',
   },
   terminal: {
     start_cmd: '',

+ 1 - 2
go.mod

@@ -35,7 +35,7 @@ require (
 	github.com/spf13/cast v1.7.0
 	github.com/stretchr/testify v1.10.0
 	github.com/tufanbarisyildirim/gonginx v0.0.0-20241205102811-323481085fb4
-	github.com/uozi-tech/cosy v1.12.3
+	github.com/uozi-tech/cosy v1.12.5
 	github.com/uozi-tech/cosy-driver-sqlite v0.2.0
 	go.uber.org/zap v1.27.0
 	golang.org/x/crypto v0.31.0
@@ -106,7 +106,6 @@ require (
 	github.com/dimchansky/utfbom v1.1.1 // indirect
 	github.com/dnsimple/dnsimple-go v1.7.0 // indirect
 	github.com/ebitengine/purego v0.8.1 // indirect
-	github.com/elliotchance/orderedmap/v2 v2.5.0 // indirect
 	github.com/exoscale/egoscale/v3 v3.1.7 // indirect
 	github.com/fatih/structs v1.1.0 // indirect
 	github.com/felixge/httpsnoop v1.0.4 // indirect

+ 2 - 4
go.sum

@@ -856,8 +856,6 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
 github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
 github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
 github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
-github.com/elliotchance/orderedmap/v2 v2.5.0 h1:WRPmWGChucaZ09eEd3UkU8XfVajv6ZZ6eg3+x0cLWPM=
-github.com/elliotchance/orderedmap/v2 v2.5.0/go.mod h1:85lZyVbpGaGvHvnKa7Qhx7zncAdBIBq6u56Hb1PRU5Q=
 github.com/elliotchance/orderedmap/v3 v3.0.0 h1:Yay/tDjX+vzza+Drcoo8VEbuBnOYGpgenCXWcpQSFDg=
 github.com/elliotchance/orderedmap/v3 v3.0.0/go.mod h1:G+Hc2RwaZvJMcS4JpGCOyViCnGeKf0bTYCGTO4uhjSo=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -1771,8 +1769,8 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
 github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
 github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec h1:2s/ghQ8wKE+UzD/hf3P4Gd1j0JI9ncbxv+nsypPoUYI=
 github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec/go.mod h1:BZr7Qs3ku1ckpqed8tCRSqTlp8NAeZfAVpfx4OzXMss=
-github.com/uozi-tech/cosy v1.12.3 h1:0Nii/OYKOsXOy/x6l8f0g0RuHj7t1vkqQQv6xmitZsU=
-github.com/uozi-tech/cosy v1.12.3/go.mod h1:zRYGFp//aDvrS6mOA91qWQSGPrSfVjuomnhdEhcdP8Y=
+github.com/uozi-tech/cosy v1.12.5 h1:rX7mVj4KKuI+xnpNor3BuFsnX6f8nUzeEFgA//gjywo=
+github.com/uozi-tech/cosy v1.12.5/go.mod h1:Q597nSDM8yAnW8yKfcWBcPU+fRfEpxXA0ZjsSse88Tc=
 github.com/uozi-tech/cosy-driver-mysql v0.2.2 h1:22S/XNIvuaKGqxQPsYPXN8TZ8hHjCQdcJKVQ83Vzxoo=
 github.com/uozi-tech/cosy-driver-mysql v0.2.2/go.mod h1:EZnRIbSj1V5U0gEeTobrXai/d1SV11lkl4zP9NFEmyE=
 github.com/uozi-tech/cosy-driver-postgres v0.2.1 h1:OICakGuT+omva6QOJCxTJ5Lfr7CGXLmk/zD+aS51Z2o=

+ 33 - 0
internal/chatbot/client.go

@@ -0,0 +1,33 @@
+package chatbot
+
+import (
+	"github.com/0xJacky/Nginx-UI/internal/transport"
+	"github.com/0xJacky/Nginx-UI/settings"
+	"github.com/sashabaranov/go-openai"
+	"net/http"
+)
+
+func GetClient() (*openai.Client, error) {
+	var config openai.ClientConfig
+	if openai.APIType(settings.OpenAISettings.APIType) == openai.APITypeAzure {
+		config = openai.DefaultAzureConfig(settings.OpenAISettings.Token, settings.OpenAISettings.BaseUrl)
+	} else {
+		config = openai.DefaultConfig(settings.OpenAISettings.Token)
+	}
+
+	if settings.OpenAISettings.Proxy != "" {
+		t, err := transport.NewTransport(transport.WithProxy(settings.OpenAISettings.Proxy))
+		if err != nil {
+			return nil, err
+		}
+		config.HTTPClient = &http.Client{
+			Transport: t,
+		}
+	}
+
+	if settings.OpenAISettings.BaseUrl != "" {
+		config.BaseURL = settings.OpenAISettings.BaseUrl
+	}
+
+	return openai.NewClientWithConfig(config), nil
+}

+ 14 - 15
internal/chatbot/messages.go

@@ -1,22 +1,21 @@
 package chatbot
 
 import (
-    "github.com/sashabaranov/go-openai"
+	"github.com/sashabaranov/go-openai"
 )
 
 func ChatCompletionWithContext(filename string, messages []openai.ChatCompletionMessage) []openai.ChatCompletionMessage {
-
-    for i := len(messages) - 1; i >= 0; i-- {
-        if messages[i].Role == openai.ChatMessageRoleUser {
-            // openai.ChatCompletionMessage: can't use both Content and MultiContent properties simultaneously
-            multiContent := getConfigIncludeContext(filename)
-            multiContent = append(multiContent, openai.ChatMessagePart{
-                Type: openai.ChatMessagePartTypeText,
-                Text: messages[i].Content,
-            })
-            messages[i].Content = ""
-            messages[i].MultiContent = multiContent
-        }
-    }
-    return messages
+	for i := len(messages) - 1; i >= 0; i-- {
+		if messages[i].Role == openai.ChatMessageRoleUser {
+			// openai.ChatCompletionMessage: can't use both Content and MultiContent properties simultaneously
+			multiContent := getConfigIncludeContext(filename)
+			multiContent = append(multiContent, openai.ChatMessagePart{
+				Type: openai.ChatMessagePartTypeText,
+				Text: messages[i].Content,
+			})
+			messages[i].Content = ""
+			messages[i].MultiContent = multiContent
+		}
+	}
+	return messages
 }

+ 1 - 6
internal/validation/validation.go

@@ -12,12 +12,7 @@ func Init() {
 		logger.Fatal("failed to initialize binding validator engine")
 	}
 
-	err := v.RegisterValidation("safety_text", safetyText)
-	if err != nil {
-		logger.Fatal(err)
-	}
-
-	err = v.RegisterValidation("certificate", isCertificate)
+	err := v.RegisterValidation("certificate", isCertificate)
 	if err != nil {
 		logger.Fatal(err)
 	}

+ 6 - 1
settings/openai.go

@@ -1,10 +1,15 @@
 package settings
 
+import "github.com/sashabaranov/go-openai"
+
 type OpenAI struct {
 	BaseUrl string `json:"base_url" binding:"omitempty,url"`
 	Token   string `json:"token" binding:"omitempty,safety_text"`
 	Proxy   string `json:"proxy" binding:"omitempty,url"`
 	Model   string `json:"model" binding:"omitempty,safety_text"`
+	APIType string `json:"api_type" binding:"omitempty,oneof=OPEN_AI AZURE"`
 }
 
-var OpenAISettings = &OpenAI{}
+var OpenAISettings = &OpenAI{
+	APIType: string(openai.APITypeOpenAI),
+}

Some files were not shown because too many files changed in this diff