mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-10 18:05:48 +02:00
feat(openai): support azure api type #475
This commit is contained in:
parent
835349c33f
commit
ad97f973ab
23 changed files with 4726 additions and 3837 deletions
|
@ -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
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
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: '',
|
||||
|
|
3
go.mod
3
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
|
||||
|
|
6
go.sum
6
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
internal/chatbot/client.go
Normal file
33
internal/chatbot/client.go
Normal file
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue