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 cdbd1fa8f fix(settings): report a failed AI configuration load instead 
of an empty form (#6550)
cdbd1fa8f is described below

commit cdbd1fa8f1dbe9360d708922195d140b73cdc3a6
Author: Apulupie <[email protected]>
AuthorDate: Thu Oct 15 18:15:28 2026 +0800

    fix(settings): report a failed AI configuration load instead of an empty 
form (#6550)
    
    The AI assistant settings tab reported a failed configuration load as a 
transient toast and then
    rendered the form anyway, with every field empty and the API key slot 
prompting for a key - the tab
    claimed that nothing is configured. The load request is admin-only on the 
server
    (`AuthInterceptor.isAdminOnlyGetPath` covers `/api/llm/config` and 
`/api/llm/models`), so a reader
    opening Settings sees that blank configuration on every visit, and an 
administrator's transient
    failure is indistinguishable from an empty configuration as well.
    
    The tab keeps the server's reason and offers a retry: a failed load now 
renders an error notice
    above the form with the message the request failed with ("Admin permission 
required" for a reader)
    and a retry button that re-runs the load, clearing the notice once it 
succeeds. `AlertRuleAssetList`
    already uses this shape for its own list, and the home page's assistant 
load failure is handled the
    same way in #6082. No new copy: the notice and the button reuse 
`llm.loadFailed` and `common.retry`.
---
 web/src/pages/settings/AiAssistantTab.tsx          | 33 ++++++++++++++++++++--
 .../settings/__tests__/AiAssistantTab.test.tsx     | 20 +++++++++++++
 2 files changed, 50 insertions(+), 3 deletions(-)

diff --git a/web/src/pages/settings/AiAssistantTab.tsx 
b/web/src/pages/settings/AiAssistantTab.tsx
index 9e87e4d0c..56d33e674 100644
--- a/web/src/pages/settings/AiAssistantTab.tsx
+++ b/web/src/pages/settings/AiAssistantTab.tsx
@@ -44,6 +44,7 @@ import {
   type LlmTestResult,
 } from '../../api/llm';
 import { fallbackModelOptions } from '../studio/llmModelOptions';
+import { describeThrownMessage } from '../../utils/apiError';
 
 const DEFAULT_BASE_URL: Record<string, string> = {
   tongyi: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
@@ -118,6 +119,8 @@ export const AiAssistantTab = () => {
   const [testResult, setTestResult] = useState<TestState | null>(null);
   const testRequestIdRef = useRef(0);
   const configGenerationRef = useRef(0);
+  const [loadError, setLoadError] = useState<string | null>(null);
+  const [reloadNonce, setReloadNonce] = useState(0);
 
   const buildModelOptions = (
     nextProvider: string,
@@ -157,10 +160,16 @@ export const AiAssistantTab = () => {
     Promise.all([getLlmConfig(), getLlmModels().catch(() => null)])
       .then(([config, models]) => {
         if (cancelled) return;
+        setLoadError(null);
         applyConfig(config, models?.data?.map((m) => m.id || 
'').filter(Boolean) ?? []);
       })
-      .catch(() => {
-        if (!cancelled) message.error(t('llm.loadFailed'));
+      .catch((error) => {
+        if (cancelled) return;
+        // An unreadable configuration must not be presented as an empty one: 
the form below would
+        // then claim that no provider, model or API key is configured. The 
endpoint is admin-only,
+        // so a reader always ends up here, and the server's reason is what 
says so.
+        setLoadError(describeThrownMessage(error) || t('llm.loadFailed'));
+        message.error(t('llm.loadFailed'));
       })
       .finally(() => {
         if (!cancelled) setLoading(false);
@@ -171,7 +180,7 @@ export const AiAssistantTab = () => {
       configGenerationRef.current += 1;
     };
     // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, []);
+  }, [reloadNonce]);
 
   const invalidateTestRequest = () => {
     testRequestIdRef.current += 1;
@@ -330,6 +339,24 @@ export const AiAssistantTab = () => {
 
   return (
     <Flex vertical gap={24} style={{ maxWidth: 860 }}>
+      {loadError && (
+        <Alert
+          type="error"
+          showIcon
+          message={loadError}
+          action={
+            <Button
+              size="small"
+              onClick={() => {
+                setLoading(true);
+                setReloadNonce((nonce) => nonce + 1);
+              }}
+            >
+              {t('common.retry')}
+            </Button>
+          }
+        />
+      )}
       <Form
         form={form}
         layout="vertical"
diff --git a/web/src/pages/settings/__tests__/AiAssistantTab.test.tsx 
b/web/src/pages/settings/__tests__/AiAssistantTab.test.tsx
index 643d1012c..a279b3eef 100644
--- a/web/src/pages/settings/__tests__/AiAssistantTab.test.tsx
+++ b/web/src/pages/settings/__tests__/AiAssistantTab.test.tsx
@@ -85,6 +85,26 @@ describe('AiAssistantTab', () => {
     expect(screen.getByDisplayValue('qwen3.8-max')).toBeInTheDocument();
   });
 
+  it('reports a failed configuration load with its reason and can retry it', 
async () => {
+    llmApiMocks.getLlmConfig.mockRejectedValueOnce(new Error('Admin permission 
required'));
+    const user = userEvent.setup();
+    renderPage();
+
+    // A configuration the tab could not read must not be presented as an 
empty one: the request
+    // answered 403 (the endpoint is admin-only) and the form would otherwise 
claim nothing is set.
+    expect(await screen.findByText('Admin permission 
required')).toBeInTheDocument();
+    expect(llmApiMocks.getLlmConfig).toHaveBeenCalledTimes(1);
+
+    // antd inserts a space between the two characters of a CJK button label.
+    await user.click(screen.getByRole('button', { name: /重\s*试/ }));
+    await waitFor(() => 
expect(llmApiMocks.getLlmConfig).toHaveBeenCalledTimes(2));
+    // The retry resolves, so the loaded configuration replaces the failure 
notice.
+    expect(await screen.findByText('密钥已配置')).toBeInTheDocument();
+    await waitFor(() =>
+      expect(screen.queryByText('Admin permission 
required')).not.toBeInTheDocument(),
+    );
+  });
+
   it('renders assistant configuration labels in English when English is 
selected', async () => {
     localStorage.setItem(LANGUAGE_STORAGE_KEY, 'en');
     renderPage();

Reply via email to