Просмотр исходного кода

fix(web): satisfy provider model gates

Yichen Jiang 2 месяцев назад
Родитель
Сommit
e43e4f187e

+ 37 - 23
packages/client/ui-models/src/client/ModelsSection.tsx

@@ -18,7 +18,7 @@ import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-web-react'
 import { CustomProviderCard } from './CustomProviderCard.tsx'
 import { deriveKeyRef, messageOf, protocolChoices } from './store.ts'
 import type { ModelsSettingsState, ModelsSettingsStore, ProviderRow } from './store.ts'
-import { ProviderEditor } from './ProviderEditor.tsx'
+import { ProviderEditor, type ProviderEditorProps } from './ProviderEditor.tsx'
 import type { en } from './locales.ts'
 import styles from './ModelsSection.module.css'
 
@@ -56,6 +56,26 @@ interface EditorTarget extends ProviderIdentity {
   credentialRef?: string
 }
 
+/** Values that vary around the shared provider-editor rendering. */
+interface ProviderEditorRenderProps extends Pick<
+  ProviderEditorProps,
+  'namespace' | 'api' | 't' | 'readOnly' | 'onClose'
+> {
+  target: EditorTarget
+}
+
+/** Render an editor for either the setup posture or an expanded provider row. */
+function renderProviderEditor({ target, ...props }: ProviderEditorRenderProps): ReactNode {
+  return (
+    <ProviderEditor
+      provider={target.provider}
+      displayName={target.displayName}
+      settingsPath={target.settingsPath}
+      {...props}
+    />
+  )
+}
+
 /**
  * Remove one user-added provider and its page-managed credential. Credential
  * removal comes first so a second-step failure leaves the provider row visible
@@ -232,16 +252,14 @@ function Loaded({ injected }: { injected: ModelsSectionInjected }): ReactNode {
             // setup card IS its presence on the page.
             return (
               <li key={row.entry.provider} className={styles['setupCard']}>
-                <ProviderEditor
-                  provider={target.provider}
-                  displayName={target.displayName}
-                  namespace={namespace}
-                  settingsPath={target.settingsPath}
-                  api={api}
-                  t={t}
-                  readOnly={!state.writable}
-                  onClose={(changed) => { closeEditor(changed, target) }}
-                />
+                {renderProviderEditor({
+                  target,
+                  namespace,
+                  api,
+                  t,
+                  readOnly: !state.writable,
+                  onClose: (changed) => { closeEditor(changed, target) },
+                })}
               </li>
             )
           }
@@ -312,18 +330,14 @@ function Loaded({ injected }: { injected: ModelsSectionInjected }): ReactNode {
                 </span>
               </div>
               {open
-                ? (
-                  <ProviderEditor
-                    provider={target.provider}
-                    displayName={target.displayName}
-                    namespace={namespace}
-                    settingsPath={target.settingsPath}
-                    api={api}
-                    t={t}
-                    readOnly={!state.writable}
-                    onClose={(changed) => { closeEditor(changed, target) }}
-                  />
-                )
+                ? renderProviderEditor({
+                  target,
+                  namespace,
+                  api,
+                  t,
+                  readOnly: !state.writable,
+                  onClose: (changed) => { closeEditor(changed, target) },
+                })
                 : null}
             </li>
           )

+ 17 - 1
packages/client/ui-models/tests/provider-form.spec.tsx

@@ -142,7 +142,7 @@ async function mountSection(options: Parameters<typeof scriptedFace>[0] = {}) {
     t,
   }
   render(<ModelsSection {...injected} />)
-  return scripted
+  return { ...scripted, controller }
 }
 
 /** Open the editor of one configured row and expand its customized fold. */
@@ -862,4 +862,20 @@ describe('hand-declared providers', () => {
     await waitFor(() => { expect(screen.queryByText(en.customTitle)).toBeNull() })
     expect(screen.getByRole('button', { name: en.customAdd })).toBeTruthy()
   })
+
+  it('reloads the section after creating a hand-declared provider', async () => {
+    const { controller, mutate } = await mountSection()
+    const load = vi.spyOn(controller, 'load')
+
+    fireEvent.click(screen.getByRole('button', { name: en.customAdd }))
+    fireEvent.change(screen.getByLabelText(en.customRoute), { target: { value: 'acme' } })
+    fireEvent.change(screen.getByLabelText(en.baseUrl), { target: { value: 'https://acme.test/v1' } })
+    fireEvent.click(screen.getByRole('button', { name: en.addModel }))
+    fireEvent.change(screen.getByLabelText(`${en.modelId} 1`), { target: { value: 'm' } })
+    fireEvent.click(screen.getByText(en.create))
+
+    await waitFor(() => { expect(mutate).toHaveBeenCalledOnce() })
+    await waitFor(() => { expect(load).toHaveBeenCalledOnce() })
+    expect(screen.queryByText(en.customTitle)).toBeNull()
+  })
 })