This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new 265527e02 feat(ai): refresh the model catalog and add the anthropic 
provider (#2483)
265527e02 is described below

commit 265527e02abc066b096a9540a1d1794575826bf9
Author: lizhimins <[email protected]>
AuthorDate: Fri Aug 21 16:01:17 2026 +0800

    feat(ai): refresh the model catalog and add the anthropic provider (#2483)
    
    The catalog only listed the models available when it was written, and an
    anthropic-compatible endpoint could not be configured at all. Refresh the
    per-provider model lists, allow a custom model id, add the anthropic
    provider with base URL presets for the official and token-plan gateways,
    and keep the stored api key when a config is saved without retyping it.
---
 .../rocketmq/studio/ops/ai/LlmConfigService.java   | 28 ++++++++++++++++------
 .../persistence/MybatisPlusSettingsRepository.java |  4 ++--
 .../studio/ops/ai/LlmConfigServiceTest.java        |  2 +-
 web/src/pages/settings/AiAssistantTab.tsx          | 22 +++++++++++++++--
 .../settings/__tests__/AiAssistantTab.test.tsx     | 10 +++++---
 web/src/pages/studio/__tests__/LlmSettings.test.ts |  7 +++---
 web/src/pages/studio/llmModelOptions.ts            | 22 ++++++++++++++---
 7 files changed, 73 insertions(+), 22 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmConfigService.java 
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmConfigService.java
index 900e45887..fe6107e47 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmConfigService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmConfigService.java
@@ -40,6 +40,7 @@ import java.util.Map;
 public class LlmConfigService {
 
     private static final String OPENAI = "openai";
+    private static final String ANTHROPIC = "anthropic";
     private static final String DEFAULT_PROVIDER = "tongyi";
     private static final String CHAT_COMPLETIONS_PATH = "/chat/completions";
     private static final int DEFAULT_MAX_TOKENS = 4096;
@@ -47,12 +48,21 @@ public class LlmConfigService {
     private static final int MAX_TOKENS_LIMIT = 200_000;
     private static final Map<String, List<LlmModelItemVO>> PROVIDER_MODELS = 
Map.of(
             OPENAI, List.of(
-                    new LlmModelItemVO("gpt-4o", "GPT-4o"),
-                    new LlmModelItemVO("gpt-4-turbo", "GPT-4 Turbo"),
-                    new LlmModelItemVO("gpt-4", "GPT-4")),
+                    new LlmModelItemVO("gpt-5.6-sol", "GPT-5.6 Sol"),
+                    new LlmModelItemVO("gpt-5.6-terra", "GPT-5.6 Terra"),
+                    new LlmModelItemVO("gpt-5.6-luna", "GPT-5.6 Luna")),
             "azure", List.of(
-                    new LlmModelItemVO("gpt-4o", "GPT-4o"),
-                    new LlmModelItemVO("gpt-4", "GPT-4")),
+                    new LlmModelItemVO("gpt-5.6-sol", "GPT-5.6 Sol"),
+                    new LlmModelItemVO("gpt-5.6-terra", "GPT-5.6 Terra"),
+                    new LlmModelItemVO("gpt-5.6-luna", "GPT-5.6 Luna")),
+            ANTHROPIC, List.of(
+                    new LlmModelItemVO("claude-fable-5", "Claude Fable 5"),
+                    new LlmModelItemVO("claude-opus-5", "Claude Opus 5"),
+                    new LlmModelItemVO("claude-opus-4-8", "Claude Opus 4.8"),
+                    new LlmModelItemVO("claude-opus-4-7", "Claude Opus 4.7"),
+                    new LlmModelItemVO("claude-sonnet-5", "Claude Sonnet 5"),
+                    new LlmModelItemVO("claude-sonnet-4-6", "Claude Sonnet 
4.6"),
+                    new LlmModelItemVO("claude-haiku-4-5", "Claude Haiku 
4.5")),
             "deepseek", List.of(
                     new LlmModelItemVO("deepseek-chat", "DeepSeek Chat"),
                     new LlmModelItemVO("deepseek-reasoner", "DeepSeek 
Reasoner")),
@@ -69,8 +79,11 @@ public class LlmConfigService {
                     new LlmModelItemVO("mistral", "Mistral"),
                     new LlmModelItemVO("qwen2.5", "Qwen 2.5")),
             "bedrock", List.of(
-                    new LlmModelItemVO("anthropic.claude-3-sonnet", "Claude 3 
Sonnet"),
-                    new LlmModelItemVO("anthropic.claude-3-haiku", "Claude 3 
Haiku"),
+                    new LlmModelItemVO("anthropic.claude-fable-5", "Claude 
Fable 5"),
+                    new LlmModelItemVO("anthropic.claude-opus-5", "Claude Opus 
5"),
+                    new LlmModelItemVO("anthropic.claude-opus-4-8", "Claude 
Opus 4.8"),
+                    new LlmModelItemVO("anthropic.claude-sonnet-5", "Claude 
Sonnet 5"),
+                    new LlmModelItemVO("anthropic.claude-haiku-4-5", "Claude 
Haiku 4.5"),
                     new LlmModelItemVO("meta.llama3-70b", "Llama 3 70B")));
 
     private final SettingsService settingsService;
@@ -332,6 +345,7 @@ public class LlmConfigService {
         return switch (provider) {
             case "deepseek" -> "https://api.deepseek.com/v1";;
             case "tongyi" -> 
"https://dashscope.aliyuncs.com/compatible-mode/v1";;
+            case ANTHROPIC -> "https://api.anthropic.com";;
             case "ollama" -> "http://localhost:11434/v1";;
             default -> "https://api.openai.com/v1";;
         };
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/persistence/MybatisPlusSettingsRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/MybatisPlusSettingsRepository.java
index 7bf0be11e..d4b427d5a 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/persistence/MybatisPlusSettingsRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/persistence/MybatisPlusSettingsRepository.java
@@ -72,9 +72,9 @@ public class MybatisPlusSettingsRepository implements 
SettingsRepository {
                     .notifySound(false)
                     .sessionTimeout(30)
                     .requireLogin(false)
-                    .llmProvider("openai")
+                    .llmProvider("tongyi")
                     .apiKey("")
-                    .model("gpt-4")
+                    .model("qwen3.8-max")
                     .baseUrl("")
                     .build();
         }
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/ai/LlmConfigServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/ops/ai/LlmConfigServiceTest.java
index 07f67b62d..56f3e8dc2 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/ops/ai/LlmConfigServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/ops/ai/LlmConfigServiceTest.java
@@ -571,7 +571,7 @@ class LlmConfigServiceTest {
         LlmModelsResultVO result = llmConfigService.listModels();
 
         assertThat(result.getStatus()).isZero();
-        assertThat(result.getData()).extracting("id").contains("gpt-4o", 
"gpt-4");
+        assertThat(result.getData()).extracting("id").contains("gpt-5.6-sol", 
"gpt-5.6-luna");
         
assertThat(result.getSource()).isEqualTo(LlmModelsResultVO.SOURCE_FALLBACK);
         assertThat(result.getWarningCode()).isEqualTo("llm.provider.io_error");
         assertThat(result.getWarning()).contains("Failed to list LLM provider 
models");
diff --git a/web/src/pages/settings/AiAssistantTab.tsx 
b/web/src/pages/settings/AiAssistantTab.tsx
index 0d404cf3c..e920ec3bd 100644
--- a/web/src/pages/settings/AiAssistantTab.tsx
+++ b/web/src/pages/settings/AiAssistantTab.tsx
@@ -48,6 +48,7 @@ import { fallbackModelOptions } from 
'../studio/llmModelOptions';
 const PROVIDER_OPTIONS = [
   { value: 'tongyi', label: '通义千问(DashScope)' },
   { value: 'openai', label: 'OpenAI' },
+  { value: 'anthropic', label: 'Anthropic(Claude)' },
   { value: 'azure', label: 'Azure OpenAI' },
   { value: 'deepseek', label: 'DeepSeek' },
   { value: 'ollama', label: 'Ollama(本地)' },
@@ -63,6 +64,7 @@ const ENGINE_OPTIONS = [
 const DEFAULT_BASE_URL: Record<string, string> = {
   tongyi: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
   openai: 'https://api.openai.com/v1',
+  anthropic: 'https://api.anthropic.com',
   deepseek: 'https://api.deepseek.com/v1',
   ollama: 'http://localhost:11434/v1',
 };
@@ -83,6 +85,13 @@ const BASE_URL_PRESETS: Record<string, { value: string; 
label: string }[]> = {
     },
   ],
   openai: [{ value: 'https://api.openai.com/v1', label: 'OpenAI 官方' }],
+  anthropic: [
+    { value: 'https://api.anthropic.com', label: 'Anthropic 官方' },
+    {
+      value: 'https://token-plan.cn-beijing.maas.aliyuncs.com/apps/anthropic',
+      label: 'Token Plan 网关(Anthropic 兼容)',
+    },
+  ],
   deepseek: [{ value: 'https://api.deepseek.com/v1', label: 'DeepSeek 官方' }],
   ollama: [{ value: 'http://localhost:11434/v1', label: 'Ollama 本地' }],
 };
@@ -321,10 +330,19 @@ export const AiAssistantTab = () => {
               label="模型"
               name="model"
               rules={[{ required: true, message: '请选择或输入模型' }]}
-              extra="默认使用 qwen3.8-max"
+              extra="可从下拉选择,也可直接输入自定义模型 ID"
               style={{ marginBottom: 0 }}
             >
-              <Select showSearch options={modelOptions} placeholder="选择模型" />
+              <AutoComplete
+                options={modelOptions}
+                placeholder="选择或输入模型"
+                filterOption={(input, option) =>
+                  String(option?.value ?? '')
+                    .toLowerCase()
+                    .includes(input.toLowerCase())
+                }
+                allowClear
+              />
             </Form.Item>
           </Card>
 
diff --git a/web/src/pages/settings/__tests__/AiAssistantTab.test.tsx 
b/web/src/pages/settings/__tests__/AiAssistantTab.test.tsx
index 84c0d1686..bf2050050 100644
--- a/web/src/pages/settings/__tests__/AiAssistantTab.test.tsx
+++ b/web/src/pages/settings/__tests__/AiAssistantTab.test.tsx
@@ -80,7 +80,7 @@ describe('AiAssistantTab', () => {
     renderPage();
 
     expect(await screen.findByText('密钥已配置')).toBeInTheDocument();
-    expect(screen.getByText('qwen3.8-max')).toBeInTheDocument();
+    expect(screen.getByDisplayValue('qwen3.8-max')).toBeInTheDocument();
   });
 
   it('saves without sending an apiKey when the input stays empty', async () => 
{
@@ -139,7 +139,9 @@ describe('AiAssistantTab', () => {
     await user.click(screen.getByRole('button', { name: /保\s*存/ }));
 
     await waitFor(() => 
expect(llmApiMocks.getLlmModels).toHaveBeenCalledTimes(2));
-    await user.click(screen.getAllByRole('combobox')[2]);
+    const modelBox = screen.getAllByRole('combobox')[2];
+    await user.click(modelBox);
+    await user.clear(modelBox);
     expect(
       await screen.findByText('qwen-plus-latest', { selector: 
'.ant-select-item-option-content' }),
     ).toBeInTheDocument();
@@ -162,7 +164,9 @@ describe('AiAssistantTab', () => {
     expect(llmApiMocks.testLlmConnection).toHaveBeenCalledWith(
       expect.objectContaining({ apiKey: 'sk-preview', provider: 'tongyi' }),
     );
-    await user.click(screen.getAllByRole('combobox')[2]);
+    const modelBox = screen.getAllByRole('combobox')[2];
+    await user.click(modelBox);
+    await user.clear(modelBox);
     expect(
       await screen.findByText('qwen-plus-latest', { selector: 
'.ant-select-item-option-content' }),
     ).toBeInTheDocument();
diff --git a/web/src/pages/studio/__tests__/LlmSettings.test.ts 
b/web/src/pages/studio/__tests__/LlmSettings.test.ts
index 691fbdeac..a00b44867 100644
--- a/web/src/pages/studio/__tests__/LlmSettings.test.ts
+++ b/web/src/pages/studio/__tests__/LlmSettings.test.ts
@@ -50,10 +50,9 @@ describe('LlmSettingsPage', () => {
 
   it('keeps provider fallback models available before config is saved', () => {
     expect(fallbackModelOptions('openai')).toEqual([
-      { value: 'gpt-4o', label: 'gpt-4o' },
-      { value: 'gpt-4-turbo', label: 'gpt-4-turbo' },
-      { value: 'gpt-4', label: 'gpt-4' },
-      { value: 'gpt-3.5-turbo', label: 'gpt-3.5-turbo' },
+      { value: 'gpt-5.6-sol', label: 'gpt-5.6-sol' },
+      { value: 'gpt-5.6-terra', label: 'gpt-5.6-terra' },
+      { value: 'gpt-5.6-luna', label: 'gpt-5.6-luna' },
     ]);
   });
 
diff --git a/web/src/pages/studio/llmModelOptions.ts 
b/web/src/pages/studio/llmModelOptions.ts
index ad1ebd30a..ba94e032f 100644
--- a/web/src/pages/studio/llmModelOptions.ts
+++ b/web/src/pages/studio/llmModelOptions.ts
@@ -16,8 +16,17 @@
  */
 
 export const FALLBACK_MODELS: Record<string, string[]> = {
-  openai: ['gpt-4o', 'gpt-4-turbo', 'gpt-4', 'gpt-3.5-turbo'],
-  azure: ['gpt-4o', 'gpt-4', 'gpt-3.5-turbo'],
+  openai: ['gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna'],
+  azure: ['gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.6-luna'],
+  anthropic: [
+    'claude-fable-5',
+    'claude-opus-5',
+    'claude-opus-4-8',
+    'claude-opus-4-7',
+    'claude-sonnet-5',
+    'claude-sonnet-4-6',
+    'claude-haiku-4-5',
+  ],
   deepseek: ['deepseek-chat', 'deepseek-reasoner'],
   tongyi: [
     'qwen3.8-max',
@@ -29,7 +38,14 @@ export const FALLBACK_MODELS: Record<string, string[]> = {
     'glm-5.2',
   ],
   ollama: ['llama3', 'mistral', 'gemma2', 'qwen2.5'],
-  bedrock: ['anthropic.claude-3-sonnet', 'anthropic.claude-3-haiku', 
'meta.llama3-70b'],
+  bedrock: [
+    'anthropic.claude-fable-5',
+    'anthropic.claude-opus-5',
+    'anthropic.claude-opus-4-8',
+    'anthropic.claude-sonnet-5',
+    'anthropic.claude-haiku-4-5',
+    'meta.llama3-70b',
+  ],
 };
 
 export function fallbackModelOptions(provider: string, model?: string) {

Reply via email to