feat(ai): add Atlas Cloud provider preset (#915)

## Background

GoNavi supports OpenAI-compatible providers, but Atlas Cloud currently
requires users to enter the endpoint and model manually. This adds a
first-class preset while keeping the existing OpenAI-compatible runtime
and remote model discovery path.

## Changes

- add an Atlas Cloud provider preset with the default API endpoint and
`qwen/qwen3.8-max`
- recognize saved Atlas Cloud endpoints when reopening AI settings
- add localized provider labels and descriptions for all supported
locales
- cover preset metadata and endpoint matching with focused tests

## Impact

The change is additive. Existing providers and custom OpenAI-compatible
configurations are unchanged. Users still supply their own API key.

## Validation

- frontend focused tests: 39 passed
- frontend full test suite: passed
- frontend production build: passed
- `go test ./internal/ai/provider ./internal/ai/service`: passed
- Atlas Cloud `/models`: HTTP 200 and `qwen/qwen3.8-max` present
- Atlas Cloud Chat Completions smoke test: HTTP 200
- `git diff --check`: passed

## Risk and rollback

Risk is limited to the additive preset metadata and endpoint matching.
Reverting this commit removes the preset without changing persisted
provider schema or backend behavior.
This commit is contained in:
Syngnat
2026-08-11 10:29:48 +08:00
committed by GitHub
10 changed files with 44 additions and 0 deletions

View File

@@ -1,6 +1,8 @@
import { describe, expect, it } from 'vitest';
import {
ATLAS_CLOUD_BASE_URL,
ATLAS_CLOUD_DEFAULT_MODEL,
QWEN_CODING_PLAN_ANTHROPIC_BASE_URL,
resolvePresetBaseURL,
resolvePresetTransport,
@@ -53,6 +55,21 @@ describe('aiSettingsModalConfig', () => {
expect(preset.key).toBe('cursor');
});
it('exposes and recognizes the Atlas Cloud preset', () => {
const preset = findPreset('atlascloud');
expect(preset).toMatchObject({
label: 'Atlas Cloud',
backendType: 'openai',
defaultBaseUrl: ATLAS_CLOUD_BASE_URL,
defaultModel: ATLAS_CLOUD_DEFAULT_MODEL,
});
expect(matchProviderPreset({
type: 'openai',
baseUrl: ATLAS_CLOUD_BASE_URL,
}).key).toBe('atlascloud');
});
it('supports every configured MiniMax region and protocol endpoint', () => {
const preset = findPreset('minimax');
@@ -125,6 +142,7 @@ describe('aiSettingsModalConfig', () => {
});
it('keeps the provider preset list available for the settings modal', () => {
expect(PROVIDER_PRESETS.some((item) => item.key === 'atlascloud')).toBe(true);
expect(PROVIDER_PRESETS.some((item) => item.key === 'codex')).toBe(true);
expect(PROVIDER_PRESETS.some((item) => item.key === 'claude-subscription')).toBe(true);
expect(PROVIDER_PRESETS.some((item) => item.key === 'codebuddy')).toBe(true);

View File

@@ -16,6 +16,8 @@ import type {
AIUserPromptSettings,
} from '../../types';
import {
ATLAS_CLOUD_BASE_URL,
ATLAS_CLOUD_DEFAULT_MODEL,
QWEN_BAILIAN_ANTHROPIC_BASE_URL,
QWEN_CODING_PLAN_ANTHROPIC_BASE_URL,
QWEN_CODING_PLAN_MODELS,
@@ -50,6 +52,7 @@ export const MINIMAX_ENDPOINTS: ProviderPresetEndpoint[] = [
export const PROVIDER_PRESETS: ProviderPreset[] = [
{ key: 'openai', label: 'OpenAI', labelKey: 'ai_settings.provider_preset.openai.label', icon: <ApiOutlined />, desc: 'GPT-5.4 / 5.3 series', descKey: 'ai_settings.provider_preset.openai.desc', color: '#10b981', backendType: 'openai', defaultBaseUrl: 'https://api.openai.com/v1', defaultModel: 'gpt-4o', models: [] },
{ key: 'atlascloud', label: 'Atlas Cloud', labelKey: 'ai_settings.provider_preset.atlascloud.label', icon: <CloudOutlined />, desc: 'Qwen3.8 Max / OpenAI-compatible', descKey: 'ai_settings.provider_preset.atlascloud.desc', color: '#0891b2', backendType: 'openai', defaultBaseUrl: ATLAS_CLOUD_BASE_URL, defaultModel: ATLAS_CLOUD_DEFAULT_MODEL, models: [] },
{ key: 'codex', label: 'Codex Subscription', labelKey: 'ai_settings.provider_preset.codex.label', icon: <ApiOutlined />, desc: 'Local Codex CLI / ChatGPT subscription login', descKey: 'ai_settings.provider_preset.codex.desc', color: '#111827', backendType: 'custom', fixedApiFormat: 'codex-cli', authMode: 'local-cli', defaultBaseUrl: '', defaultModel: '', models: [] },
{ key: 'deepseek', label: 'DeepSeek', labelKey: 'ai_settings.provider_preset.deepseek.label', icon: <ThunderboltOutlined />, desc: 'DeepSeek-V4 / R1', descKey: 'ai_settings.provider_preset.deepseek.desc', color: '#3b82f6', backendType: 'openai', defaultBaseUrl: 'https://api.deepseek.com/v1', defaultModel: 'deepseek-chat', models: [] },
{ key: 'qwen-bailian', label: 'Qwen (Bailian General)', labelKey: 'ai_settings.provider_preset.qwen_bailian.label', icon: <CloudOutlined />, desc: 'Bailian Anthropic-compatible endpoint / remote model list', descKey: 'ai_settings.provider_preset.qwen_bailian.desc', color: '#6366f1', backendType: 'anthropic', defaultBaseUrl: QWEN_BAILIAN_ANTHROPIC_BASE_URL, defaultModel: '', models: [] },

View File

@@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest';
import type { AIProviderAuthMode, AIProviderType } from '../types';
import {
ATLAS_CLOUD_BASE_URL,
LEGACY_QWEN_CODING_PLAN_OPENAI_BASE_URL,
QWEN_BAILIAN_ANTHROPIC_BASE_URL,
QWEN_BAILIAN_MODELS_BASE_URL,
@@ -24,6 +25,7 @@ type PresetMatcher = {
const PRESETS: PresetMatcher[] = [
{ key: 'openai', backendType: 'openai', defaultBaseUrl: 'https://api.openai.com/v1' },
{ key: 'atlascloud', backendType: 'openai', defaultBaseUrl: ATLAS_CLOUD_BASE_URL },
{ key: 'qwen-bailian', backendType: 'anthropic', defaultBaseUrl: QWEN_BAILIAN_ANTHROPIC_BASE_URL },
{
key: 'qwen-coding-plan',
@@ -212,6 +214,13 @@ describe('ai provider preset helpers', () => {
});
describe('resolveProviderPresetKey', () => {
it('recognizes Atlas Cloud by its OpenAI-compatible endpoint', () => {
expect(resolveProviderPresetKey({
type: 'openai',
baseUrl: `${ATLAS_CLOUD_BASE_URL}/`,
}, PRESETS, 'custom')).toBe('atlascloud');
});
it('不会把自定义 OpenAI 端点误识别成千问 Coding Plan', () => {
const key = resolveProviderPresetKey(
{

View File

@@ -5,6 +5,8 @@ export const LEGACY_QWEN_CODING_PLAN_OPENAI_BASE_URL = 'https://coding.dashscope
export const QWEN_BAILIAN_ANTHROPIC_BASE_URL = 'https://dashscope.aliyuncs.com/apps/anthropic';
export const QWEN_CODING_PLAN_ANTHROPIC_BASE_URL = 'https://coding.dashscope.aliyuncs.com/apps/anthropic';
export const QWEN_BAILIAN_MODELS_BASE_URL = LEGACY_QWEN_BAILIAN_OPENAI_BASE_URL;
export const ATLAS_CLOUD_BASE_URL = 'https://api.atlascloud.ai/v1';
export const ATLAS_CLOUD_DEFAULT_MODEL = 'qwen/qwen3.8-max';
export const QWEN_CODING_PLAN_MODELS = [
'qwen3.5-plus',

View File

@@ -2352,6 +2352,8 @@
"ai_settings.provider.no_model": "Kein Modell ausgewählt",
"ai_settings.provider_preset.anthropic.desc": "Claude Opus/Sonnet Modelle",
"ai_settings.provider_preset.anthropic.label": "Claude",
"ai_settings.provider_preset.atlascloud.desc": "Qwen3.8 Max / OpenAI-kompatibel",
"ai_settings.provider_preset.atlascloud.label": "Atlas Cloud",
"ai_settings.provider_preset.claude_subscription.desc": "Lokale Claude Code CLI / Claude-Abonnementanmeldung",
"ai_settings.provider_preset.claude_subscription.label": "Claude-Abonnement",
"ai_settings.provider_preset.codex.desc": "Lokale Codex CLI / ChatGPT-Abonnementanmeldung",

View File

@@ -2352,6 +2352,8 @@
"ai_settings.provider.no_model": "No model selected",
"ai_settings.provider_preset.anthropic.desc": "Claude Opus/Sonnet",
"ai_settings.provider_preset.anthropic.label": "Claude",
"ai_settings.provider_preset.atlascloud.desc": "Qwen3.8 Max / OpenAI-compatible",
"ai_settings.provider_preset.atlascloud.label": "Atlas Cloud",
"ai_settings.provider_preset.claude_subscription.desc": "Local Claude Code CLI / Claude subscription login",
"ai_settings.provider_preset.claude_subscription.label": "Claude Subscription",
"ai_settings.provider_preset.codex.desc": "Local Codex CLI / ChatGPT subscription login",

View File

@@ -2352,6 +2352,8 @@
"ai_settings.provider.no_model": "モデル未選択",
"ai_settings.provider_preset.anthropic.desc": "Claude Opus/Sonnet モデル",
"ai_settings.provider_preset.anthropic.label": "Claude",
"ai_settings.provider_preset.atlascloud.desc": "Qwen3.8 Max / OpenAI 互換",
"ai_settings.provider_preset.atlascloud.label": "Atlas Cloud",
"ai_settings.provider_preset.claude_subscription.desc": "ローカル Claude Code CLI / Claude サブスクリプションログイン",
"ai_settings.provider_preset.claude_subscription.label": "Claude サブスクリプション",
"ai_settings.provider_preset.codex.desc": "ローカル Codex CLI / ChatGPT サブスクリプションログイン",

View File

@@ -2352,6 +2352,8 @@
"ai_settings.provider.no_model": "Модель не выбрана",
"ai_settings.provider_preset.anthropic.desc": "Модели Claude Opus/Sonnet",
"ai_settings.provider_preset.anthropic.label": "Claude",
"ai_settings.provider_preset.atlascloud.desc": "Qwen3.8 Max / совместимо с OpenAI",
"ai_settings.provider_preset.atlascloud.label": "Atlas Cloud",
"ai_settings.provider_preset.claude_subscription.desc": "Локальный Claude Code CLI / вход по подписке Claude",
"ai_settings.provider_preset.claude_subscription.label": "Подписка Claude",
"ai_settings.provider_preset.codex.desc": "Локальный Codex CLI / вход по подписке ChatGPT",

View File

@@ -2352,6 +2352,8 @@
"ai_settings.provider.no_model": "未选择模型",
"ai_settings.provider_preset.anthropic.desc": "Claude Opus/Sonnet 模型",
"ai_settings.provider_preset.anthropic.label": "Claude",
"ai_settings.provider_preset.atlascloud.desc": "Qwen3.8 Max / OpenAI 兼容",
"ai_settings.provider_preset.atlascloud.label": "Atlas Cloud",
"ai_settings.provider_preset.claude_subscription.desc": "本机 Claude Code CLI / Claude 订阅登录态",
"ai_settings.provider_preset.claude_subscription.label": "Claude 订阅",
"ai_settings.provider_preset.codex.desc": "本机 Codex CLI / ChatGPT 订阅登录态",

View File

@@ -2352,6 +2352,8 @@
"ai_settings.provider.no_model": "尚未選擇模型",
"ai_settings.provider_preset.anthropic.desc": "Claude Opus/Sonnet 模型",
"ai_settings.provider_preset.anthropic.label": "Claude",
"ai_settings.provider_preset.atlascloud.desc": "Qwen3.8 Max / OpenAI 相容",
"ai_settings.provider_preset.atlascloud.label": "Atlas Cloud",
"ai_settings.provider_preset.claude_subscription.desc": "本機 Claude Code CLI / Claude 訂閱登入狀態",
"ai_settings.provider_preset.claude_subscription.label": "Claude 訂閱",
"ai_settings.provider_preset.codex.desc": "本機 Codex CLI / ChatGPT 訂閱登入狀態",