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) {