mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2026-06-19 07:36:59 +00:00
feat(openai): support azure api type #475
This commit is contained in:
+12
-26
@@ -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,
|
||||
}
|
||||
return false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if settings.OpenAISettings.BaseUrl != "" {
|
||||
config.BaseURL = settings.OpenAISettings.BaseUrl
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ export interface OpenaiSettings {
|
||||
base_url: string
|
||||
proxy: string
|
||||
token: string
|
||||
api_type: string
|
||||
}
|
||||
|
||||
export interface TerminalSettings {
|
||||
|
||||
@@ -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
|
||||
+568
-455
File diff suppressed because it is too large
Load Diff
+402
-325
File diff suppressed because it is too large
Load Diff
+417
-338
File diff suppressed because it is too large
Load Diff
+403
-325
File diff suppressed because it is too large
Load Diff
+403
-325
File diff suppressed because it is too large
Load Diff
+405
-337
File diff suppressed because it is too large
Load Diff
+420
-342
File diff suppressed because it is too large
Load Diff
+408
-333
File diff suppressed because it is too large
Load Diff
+405
-327
File diff suppressed because it is too large
Load Diff
+401
-337
File diff suppressed because it is too large
Load Diff
+404
-333
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ const data = ref<Settings>({
|
||||
base_url: '',
|
||||
proxy: '',
|
||||
token: '',
|
||||
api_type: 'OPEN_AI',
|
||||
},
|
||||
terminal: {
|
||||
start_cmd: '',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user