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 27608f79f fix(ai): return models from connection test and refresh
selector (#2387)
27608f79f is described below
commit 27608f79fe34bcae4893f9dde079c2352c27dec3
Author: aias00 <[email protected]>
AuthorDate: Wed Aug 19 17:34:44 2026 +0800
fix(ai): return models from connection test and refresh selector (#2387)
* feat(ai): return models from connection test
* test(ai): cover models returned for Ollama connection
---
.../rocketmq/studio/ops/ai/LlmConfigService.java | 9 ++++++---
.../studio/ops/ai/LlmOperationResultVO.java | 11 +++++++++--
.../studio/ops/ai/LlmConfigServiceTest.java | 1 +
web/src/api/llm.ts | 1 +
web/src/pages/settings/AiAssistantTab.tsx | 4 ++++
.../settings/__tests__/AiAssistantTab.test.tsx | 23 ++++++++++++++++++++++
6 files changed, 44 insertions(+), 5 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 7308a855d..900e45887 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
@@ -141,11 +141,11 @@ public class LlmConfigService {
"Use one of: openai, deepseek, tongyi, ollama.");
}
try {
- llmClient.listModels(normalized);
+ List<LlmModelItemVO> models = llmClient.listModels(normalized);
+ return LlmOperationResultVO.successWithModels("Connection
successful", models);
} catch (LlmGatewayException exception) {
return LlmOperationResultVO.failure(exception.getCode(),
exception.getMessage(), exception.getHint());
}
- return LlmOperationResultVO.success("Connection successful");
}
private LlmOperationResultVO testCliEngine(String engine) {
@@ -209,7 +209,10 @@ public class LlmConfigService {
}
public LlmModelsResultVO listModels() {
- LlmConfigVO config = getConfig();
+ return listModels(getConfig());
+ }
+
+ private LlmModelsResultVO listModels(LlmConfigVO config) {
String provider = config.getProvider();
// The token-plan gateway model set is curated locally; do not query
the gateway.
if (DEFAULT_PROVIDER.equals(provider)) {
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmOperationResultVO.java
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmOperationResultVO.java
index 141178d96..c7a78b459 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmOperationResultVO.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/ops/ai/LlmOperationResultVO.java
@@ -21,6 +21,8 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
+import java.util.List;
+
@Data
@NoArgsConstructor
@AllArgsConstructor
@@ -30,9 +32,14 @@ public class LlmOperationResultVO {
private String errMsg;
private String code;
private String hint;
+ private List<LlmModelItemVO> models;
public static LlmOperationResultVO success(String message) {
- return new LlmOperationResultVO(0, message, null, null, null);
+ return new LlmOperationResultVO(0, message, null, null, null, null);
+ }
+
+ public static LlmOperationResultVO successWithModels(String message,
List<LlmModelItemVO> models) {
+ return new LlmOperationResultVO(0, message, null, null, null, models);
}
public static LlmOperationResultVO failure(String message) {
@@ -40,6 +47,6 @@ public class LlmOperationResultVO {
}
public static LlmOperationResultVO failure(String code, String message,
String hint) {
- return new LlmOperationResultVO(1, null, message, code, hint);
+ return new LlmOperationResultVO(1, null, message, code, hint, null);
}
}
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 db8a5872e..07f67b62d 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
@@ -338,6 +338,7 @@ class LlmConfigServiceTest {
assertThat(result.getStatus()).isZero();
assertThat(result.getMsg()).isEqualTo("Connection successful");
+ assertThat(result.getModels()).isEmpty();
}
@Test
diff --git a/web/src/api/llm.ts b/web/src/api/llm.ts
index 3f5a24147..2c29e65aa 100644
--- a/web/src/api/llm.ts
+++ b/web/src/api/llm.ts
@@ -39,6 +39,7 @@ export interface LlmTestResult {
errMsg?: string;
code?: string;
hint?: string;
+ models?: LlmModelItem[];
}
export interface LlmModelItem {
diff --git a/web/src/pages/settings/AiAssistantTab.tsx
b/web/src/pages/settings/AiAssistantTab.tsx
index f6888c979..0d404cf3c 100644
--- a/web/src/pages/settings/AiAssistantTab.tsx
+++ b/web/src/pages/settings/AiAssistantTab.tsx
@@ -234,6 +234,10 @@ export const AiAssistantTab = () => {
const result = await testLlmConnection(payload);
if (testRequestIdRef.current === requestId) {
applyTestResult(result);
+ if (result.status === 0) {
+ const remoteModels = result.models?.map((model) => model.id ||
'').filter(Boolean) ?? [];
+ setModelOptions(buildModelOptions(payload.provider, remoteModels,
payload.model));
+ }
}
} catch {
if (testRequestIdRef.current === requestId) {
diff --git a/web/src/pages/settings/__tests__/AiAssistantTab.test.tsx
b/web/src/pages/settings/__tests__/AiAssistantTab.test.tsx
index 005f094e1..84c0d1686 100644
--- a/web/src/pages/settings/__tests__/AiAssistantTab.test.tsx
+++ b/web/src/pages/settings/__tests__/AiAssistantTab.test.tsx
@@ -145,6 +145,29 @@ describe('AiAssistantTab', () => {
).toBeInTheDocument();
});
+ it('refreshes the model list from a successful connection test', async () =>
{
+ const user = userEvent.setup();
+ llmApiMocks.testLlmConnection.mockResolvedValue({
+ status: 0,
+ msg: 'ok',
+ models: [{ id: 'qwen3.8-max' }, { id: 'qwen-plus-latest' }],
+ });
+ renderPage();
+
+ await screen.findByText('密钥已配置');
+ await user.type(screen.getByLabelText('API Key'), 'sk-preview');
+ await user.click(screen.getByRole('button', { name: /测试连接/ }));
+
+ await waitFor(() =>
expect(llmApiMocks.testLlmConnection).toHaveBeenCalledTimes(1));
+ expect(llmApiMocks.testLlmConnection).toHaveBeenCalledWith(
+ expect.objectContaining({ apiKey: 'sk-preview', provider: 'tongyi' }),
+ );
+ await user.click(screen.getAllByRole('combobox')[2]);
+ expect(
+ await screen.findByText('qwen-plus-latest', { selector:
'.ant-select-item-option-content' }),
+ ).toBeInTheDocument();
+ });
+
it('ignores a connection result after the tested configuration changes',
async () => {
let resolveTest!: (result: { status: number; msg: string }) => void;
llmApiMocks.testLlmConnection.mockImplementationOnce(