feat: integrate Atlas Cloud provider (#1674)

This commit is contained in:
lucaszhu-hue
2026-05-15 23:47:54 +08:00
committed by GitHub
parent 32c1fadc0c
commit d87e7785ad
16 changed files with 261 additions and 24 deletions
+2 -2
View File
@@ -37,12 +37,12 @@ If you find this project helpful, please consider sponsoring us to support ongoi
### AI Infrastructure Support
<p align="center">
<a href="https://www.atlascloud.ai/" target="_blank">
<a href="https://www.atlascloud.ai/?utm_source=github&utm_medium=link&utm_campaign=nginx-ui" target="_blank">
<img src="resources/atlas-cloud-logo.svg" alt="Atlas Cloud Logo" width="240">
</a>
</p>
Atlas Cloud is a full-modal AI inference platform that gives developers a single AI API to access the world's best video generation API, image generation API, LLM API.
[Atlas Cloud](https://www.atlascloud.ai/?utm_source=github&utm_medium=link&utm_campaign=nginx-ui) is a full-modal AI inference platform that gives developers a single AI API to access video generation, image generation, and LLM APIs.
### Official Community Group
+12 -1
View File
@@ -17,6 +17,13 @@ import (
"github.com/uozi-tech/cosy/logger"
)
func getStreamDeltaContent(response openai.ChatCompletionStreamResponse) string {
if len(response.Choices) == 0 {
return ""
}
return response.Choices[0].Delta.Content
}
func MakeChatCompletionRequest(c *gin.Context) {
var json struct {
@@ -121,7 +128,11 @@ func MakeChatCompletionRequest(c *gin.Context) {
logger.Errorf("Stream error: %v\n", err)
return
}
messageCh <- response.Choices[0].Delta.Content
content := getStreamDeltaContent(response)
if content == "" {
continue
}
messageCh <- content
}
}()
+30
View File
@@ -0,0 +1,30 @@
package llm
import (
"testing"
"github.com/sashabaranov/go-openai"
"github.com/stretchr/testify/assert"
)
func TestGetStreamDeltaContent(t *testing.T) {
t.Run("returns empty string when choices are missing", func(t *testing.T) {
response := openai.ChatCompletionStreamResponse{}
assert.Empty(t, getStreamDeltaContent(response))
})
t.Run("returns delta content from first choice", func(t *testing.T) {
response := openai.ChatCompletionStreamResponse{
Choices: []openai.ChatCompletionStreamChoice{
{
Delta: openai.ChatCompletionStreamChoiceDelta{
Content: "atlas",
},
},
},
}
assert.Equal(t, "atlas", getStreamDeltaContent(response))
})
}
+4
View File
@@ -53,6 +53,10 @@ func buildSettingsResponse() gin.H {
node["secret"] = redactedSensitiveValue
openai := cloneSettingsSection(settings.OpenAISettings)
openai["provider"] = settings.OpenAISettings.GetProvider()
if baseURL := settings.OpenAISettings.GetBaseURL(); openai["base_url"] == "" && baseURL != "" {
openai["base_url"] = baseURL
}
openai["token"] = redactedSensitiveValue
return gin.H{
+1
View File
@@ -90,6 +90,7 @@ SkipInstallation = false
Demo = false
[openai]
Provider = openai
BaseUrl =
Token =
Proxy =
+1
View File
@@ -88,6 +88,7 @@ export interface NodeSettings {
}
export interface OpenaiSettings {
provider: string
model: string
base_url: string
proxy: string
+26 -1
View File
@@ -1,4 +1,12 @@
export interface LLMProviderPreset {
value: string
label: string
baseUrl?: string
}
export const LLM_MODELS = [
'deepseek-ai/deepseek-v4-flash',
'deepseek-v3',
'o3-mini',
'o1',
'deepseek-reasoner',
@@ -11,8 +19,25 @@ export const LLM_MODELS = [
'gpt-3.5-turbo',
]
export const LLM_PROVIDERS = [
export const LLM_PROVIDERS: LLMProviderPreset[] = [
{
value: 'openai',
label: 'OpenAI',
},
{
value: 'atlas_cloud',
label: 'Atlas Cloud',
baseUrl: 'https://api.atlascloud.ai/v1',
},
{
value: 'custom',
label: 'Custom',
},
]
export const LLM_PROVIDER_BASE_URLS = [
'https://api.openai.com',
'https://api.atlascloud.ai/v1',
'https://api.deepseek.com',
'http://localhost:11434',
]
+1 -1
View File
@@ -98,7 +98,7 @@ onMounted(() => {
</ATabPane>
<ATabPane
key="openai"
:tab="$gettext('OpenAI')"
:tab="$gettext('LLM')"
>
<OpenAISettings />
</ATabPane>
+1
View File
@@ -88,6 +88,7 @@ const useSystemSettingsStore = defineStore('systemSettings', () => {
public_security_number: '',
},
openai: {
provider: 'openai',
model: '',
base_url: '',
proxy: '',
@@ -1,22 +1,85 @@
<script setup lang="ts">
import { SensitiveInput } from '@/components/SensitiveString'
import { LLM_MODELS, LLM_PROVIDERS } from '@/constants/llm'
import { LLM_MODELS, LLM_PROVIDER_BASE_URLS, LLM_PROVIDERS } from '@/constants/llm'
import useSystemSettingsStore from '../store'
const systemSettingsStore = useSystemSettingsStore()
const { data, errors } = storeToRefs(systemSettingsStore)
const models = LLM_MODELS.map(model => ({
const modelOptions = LLM_MODELS.map(model => ({
value: model,
}))
const providers = LLM_PROVIDERS.map(provider => ({
value: provider,
const providerOptions = LLM_PROVIDERS.map(provider => ({
label: provider.label,
value: provider.value,
}))
const baseUrlOptions = LLM_PROVIDER_BASE_URLS.map(baseUrl => ({
value: baseUrl,
}))
const providerBaseUrlMap = LLM_PROVIDERS.reduce<Record<string, string>>((acc, provider) => {
if (provider.baseUrl)
acc[provider.value] = provider.baseUrl
return acc
}, {})
const baseUrlPlaceholder = computed(() => {
if (data.value?.openai.provider === 'atlas_cloud')
return $gettext('Leave blank to use the Atlas Cloud endpoint: https://api.atlascloud.ai/v1')
return $gettext('Leave blank for the default: https://api.openai.com/')
})
const baseUrlHelp = computed(() => {
if (errors.value?.openai?.base_url === 'url')
return $gettext('The url is invalid.')
if (data.value?.openai.provider === 'atlas_cloud') {
return $gettext('Atlas Cloud is OpenAI-compatible. Use https://api.atlascloud.ai/v1 and an Atlas Cloud API key.')
}
return $gettext('To use a local large model, deploy it with ollama, vllm or lmdeploy. '
+ 'They provide an OpenAI-compatible API endpoint, so just set the baseUrl to your local API.')
})
watch(
() => data.value?.openai.provider,
(provider, previousProvider) => {
if (!data.value || !provider)
return
const nextBaseUrl = providerBaseUrlMap[provider]
if (!nextBaseUrl)
return
const currentBaseUrl = data.value.openai.base_url?.trim()
const previousBaseUrl = previousProvider ? providerBaseUrlMap[previousProvider] : ''
if (!currentBaseUrl || currentBaseUrl === previousBaseUrl)
data.value.openai.base_url = nextBaseUrl
},
)
</script>
<template>
<AForm layout="vertical">
<AFormItem
:label="$gettext('Provider')"
:validate-status="errors?.openai?.provider ? 'error' : ''"
>
<ASelect v-model:value="data.openai.provider">
<ASelectOption
v-for="provider in providerOptions"
:key="provider.value"
:value="provider.value"
>
{{ provider.label }}
</ASelectOption>
</ASelect>
</AFormItem>
<AFormItem
:label="$gettext('Model')"
:validate-status="errors?.openai?.model ? 'error' : ''"
@@ -26,21 +89,18 @@ const providers = LLM_PROVIDERS.map(provider => ({
>
<AAutoComplete
v-model:value="data.openai.model"
:options="models"
:options="modelOptions"
/>
</AFormItem>
<AFormItem
:label="$gettext('API Base Url')"
: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 ollama, vllm or lmdeploy. '
+ 'They provide an OpenAI-compatible API endpoint, so just set the baseUrl to your local API.')"
:help="baseUrlHelp"
>
<AAutoComplete
v-model:value="data.openai.base_url"
:placeholder="$gettext('Leave blank for the default: https://api.openai.com/')"
:options="providers"
:placeholder="baseUrlPlaceholder"
:options="baseUrlOptions"
/>
</AFormItem>
<AFormItem
@@ -95,7 +155,7 @@ const providers = LLM_PROVIDERS.map(provider => ({
>
<AAutoComplete
v-model:value="data.openai.code_completion_model"
:options="models"
:options="modelOptions"
/>
</AFormItem>
</AForm>
+16 -1
View File
@@ -4,11 +4,26 @@ This section is for setting up ChatGPT configurations. Please be aware that we d
information you provide. If the configuration is incorrect, it might cause API request failures, making the ChatGPT
assistant unusable.
## Provider
- Type: `string`
- Default: `openai`
This option selects a preset for OpenAI-compatible providers.
- `openai`: Use the default OpenAI endpoint.
- `atlas_cloud`: Use the Atlas Cloud endpoint `https://api.atlascloud.ai/v1`.
- `custom`: Keep using the custom `BaseUrl` value.
## BaseUrl
- Type: `string`
This option is used to set the base url of the api of Open AI, leave it blank if you do not need to change the url.
This option is used to set the base URL of the API. Leave it blank if you do not need to change the URL.
For Atlas Cloud, use `https://api.atlascloud.ai/v1`. Atlas Cloud is OpenAI-compatible, so the existing chat and code
completion features work without additional backend changes. You can find the Atlas Cloud model guide at
<https://www.atlascloud.ai/docs/models/get-start>.
## Token
+1
View File
@@ -116,6 +116,7 @@ Applicable for version v2.0.0-beta.37 and above.
## OpenAI
| Configuration Setting | Environment Variable |
|-----------------------|--------------------------|
| Provider | NGINX_UI_OPENAI_PROVIDER |
| Model | NGINX_UI_OPENAI_MODEL |
| BaseUrl | NGINX_UI_OPENAI_BASE_URL |
| Proxy | NGINX_UI_OPENAI_PROXY |
+4 -3
View File
@@ -9,8 +9,9 @@ import (
func GetClient() (*openai.Client, error) {
var config openai.ClientConfig
baseURL := settings.OpenAISettings.GetBaseURL()
if openai.APIType(settings.OpenAISettings.APIType) == openai.APITypeAzure {
config = openai.DefaultAzureConfig(settings.OpenAISettings.Token, settings.OpenAISettings.BaseUrl)
config = openai.DefaultAzureConfig(settings.OpenAISettings.Token, baseURL)
} else {
config = openai.DefaultConfig(settings.OpenAISettings.Token)
}
@@ -25,8 +26,8 @@ func GetClient() (*openai.Client, error) {
}
}
if settings.OpenAISettings.BaseUrl != "" {
config.BaseURL = settings.OpenAISettings.BaseUrl
if baseURL != "" {
config.BaseURL = baseURL
}
return openai.NewClientWithConfig(config), nil
+1
View File
@@ -66,6 +66,7 @@ SkipInstallation = false
Demo = true
[openai]
Provider = openai
BaseUrl =
Token =
Proxy =
+52 -1
View File
@@ -1,8 +1,20 @@
package settings
import "github.com/sashabaranov/go-openai"
import (
"strings"
"github.com/sashabaranov/go-openai"
)
const (
OpenAIProviderOpenAI = "openai"
OpenAIProviderAtlasCloud = "atlas_cloud"
OpenAIProviderCustom = "custom"
AtlasCloudBaseURL = "https://api.atlascloud.ai/v1"
)
type OpenAI struct {
Provider string `json:"provider" binding:"omitempty,oneof=openai atlas_cloud custom"`
BaseUrl string `json:"base_url" binding:"omitempty,url"`
Token string `json:"token" binding:"omitempty,safety_text"`
Proxy string `json:"proxy" binding:"omitempty,url"`
@@ -13,6 +25,7 @@ type OpenAI struct {
}
var OpenAISettings = &OpenAI{
Provider: OpenAIProviderOpenAI,
APIType: string(openai.APITypeOpenAI),
}
@@ -22,3 +35,41 @@ func (o *OpenAI) GetCodeCompletionModel() string {
}
return o.CodeCompletionModel
}
func (o *OpenAI) GetProvider() string {
if o == nil {
return OpenAIProviderOpenAI
}
switch normalizeOpenAIBaseURL(o.BaseUrl) {
case AtlasCloudBaseURL:
return OpenAIProviderAtlasCloud
}
if o.Provider == "" {
return OpenAIProviderOpenAI
}
return o.Provider
}
func (o *OpenAI) GetBaseURL() string {
if o == nil {
return ""
}
if baseURL := normalizeOpenAIBaseURL(o.BaseUrl); baseURL != "" {
return baseURL
}
switch o.GetProvider() {
case OpenAIProviderAtlasCloud:
return AtlasCloudBaseURL
default:
return ""
}
}
func normalizeOpenAIBaseURL(baseURL string) string {
return strings.TrimRight(strings.TrimSpace(baseURL), "/")
}
+35
View File
@@ -0,0 +1,35 @@
package settings
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestOpenAIGetProvider(t *testing.T) {
t.Run("defaults to openai", func(t *testing.T) {
cfg := &OpenAI{}
assert.Equal(t, OpenAIProviderOpenAI, cfg.GetProvider())
})
t.Run("infers atlas cloud from base url", func(t *testing.T) {
cfg := &OpenAI{BaseUrl: "https://api.atlascloud.ai/v1/"}
assert.Equal(t, OpenAIProviderAtlasCloud, cfg.GetProvider())
})
}
func TestOpenAIGetBaseURL(t *testing.T) {
t.Run("returns atlas default base url from provider preset", func(t *testing.T) {
cfg := &OpenAI{Provider: OpenAIProviderAtlasCloud}
assert.Equal(t, AtlasCloudBaseURL, cfg.GetBaseURL())
})
t.Run("normalizes custom base url", func(t *testing.T) {
cfg := &OpenAI{Provider: OpenAIProviderCustom, BaseUrl: "https://example.com/v1/"}
assert.Equal(t, "https://example.com/v1", cfg.GetBaseURL())
})
}