diff --git a/src/web/pages/Accounts.tsx b/src/web/pages/Accounts.tsx index 3c80459..78ae898 100644 --- a/src/web/pages/Accounts.tsx +++ b/src/web/pages/Accounts.tsx @@ -11,6 +11,7 @@ import { MobileCard, MobileField } from '../components/MobileCard.js'; import { useIsMobile } from '../components/useIsMobile.js'; import DeleteConfirmModal from '../components/DeleteConfirmModal.js'; import SiteBadgeLink from '../components/SiteBadgeLink.js'; +import AccountModelsModal from './accounts/AccountModelsModal.js'; import { buildAddAccountPrereqHint, buildVerifyFailureHint, @@ -2006,209 +2007,24 @@ export default function Accounts() { )} - {/* Model management modal */} - - - - - } - > - {modelModal.loading ? ( -
- - 加载模型列表... -
- ) : ( -
- {modelModal.models.length === 0 ? ( -
-
🤖
-
暂无可用模型
-
请先点击账号操作栏中的「刷新」或「模型」按钮获取模型
- -
- ) : ( - <> -
- -
- - - - -
-
- -
- {modelModal.models.map((model, idx) => { - const isDisabled = modelModal.pendingDisabled.has(model.name); - return ( - - ); - })} -
-
- 💡 禁用的模型将对整个站点生效,该站点下所有连接都不会使用这些模型进行代理。 -
- - )} - -
-
手动添加可用模型
-
- 如果您的账号支持某些未在上方列表中显示的模型,可以在此手动添加(多个以英文逗号分隔)。 -
-
- setModelModal(s => ({ ...s, manualModelsInput: e.target.value }))} - style={{ ...inputStyle, flex: 1, fontFamily: 'var(--font-mono)' }} - onKeyDown={(e) => { - if (e.key === 'Enter' && !modelModal.addingManualModels) { - handleAddManualModels(); - } - }} - /> - -
-
-
- )} -
+ onSave={saveModelDisabled} + onRefresh={async () => { + if (!modelModal.account) return; + await loadModelModalModels(modelModal.account, { + refreshUpstream: true, + successMessage: '模型列表已刷新', + errorMessage: '刷新失败', + }); + }} + onToggleModelDisabled={toggleModelDisabled} + onSetPendingDisabled={(pendingDisabled) => setModelModal((state) => ({ ...state, pendingDisabled }))} + onManualInputChange={(value) => setModelModal((state) => ({ ...state, manualModelsInput: value }))} + onAddManualModels={handleAddManualModels} + /> ); } diff --git a/src/web/pages/accounts.model-modal.architecture.test.ts b/src/web/pages/accounts.model-modal.architecture.test.ts new file mode 100644 index 0000000..4dd73cf --- /dev/null +++ b/src/web/pages/accounts.model-modal.architecture.test.ts @@ -0,0 +1,12 @@ +import { describe, expect, it } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +describe('Accounts model modal extraction', () => { + it('delegates the account model management modal to a dedicated accounts component', () => { + const source = readFileSync(resolve(process.cwd(), 'src/web/pages/Accounts.tsx'), 'utf8').replace(/\r\n/g, '\n'); + + expect(source).toContain("import AccountModelsModal from './accounts/AccountModelsModal.js'"); + expect(source).not.toContain('open={modelModal.open}'); + }); +}); diff --git a/src/web/pages/accounts/AccountModelsModal.tsx b/src/web/pages/accounts/AccountModelsModal.tsx new file mode 100644 index 0000000..a530262 --- /dev/null +++ b/src/web/pages/accounts/AccountModelsModal.tsx @@ -0,0 +1,231 @@ +import React from 'react'; +import CenteredModal from '../../components/CenteredModal.js'; + +type AccountModelRow = { + name: string; + latencyMs: number | null; + disabled: boolean; + isManual?: boolean; +}; + +type AccountModelModalState = { + open: boolean; + account: any | null; + models: AccountModelRow[]; + pendingDisabled: Set; + loading: boolean; + saving: boolean; + siteName: string; + manualModelsInput: string; + addingManualModels: boolean; +}; + +type AccountModelsModalProps = { + modelModal: AccountModelModalState; + inputStyle: React.CSSProperties; + onClose: () => void; + onSave: () => void; + onRefresh: () => Promise | void; + onToggleModelDisabled: (modelName: string) => void; + onSetPendingDisabled: (pendingDisabled: Set) => void; + onManualInputChange: (value: string) => void; + onAddManualModels: () => Promise | void; +}; + +export default function AccountModelsModal({ + modelModal, + inputStyle, + onClose, + onSave, + onRefresh, + onToggleModelDisabled, + onSetPendingDisabled, + onManualInputChange, + onAddManualModels, +}: AccountModelsModalProps) { + return ( + + + + + )} + > + {modelModal.loading ? ( +
+ + 加载模型列表... +
+ ) : ( +
+ {modelModal.models.length === 0 ? ( +
+
🤖
+
暂无可用模型
+
请先点击账号操作栏中的「刷新」或「模型」按钮获取模型
+ +
+ ) : ( + <> +
+ +
+ + + + +
+
+ +
+ {modelModal.models.map((model, idx) => { + const isDisabled = modelModal.pendingDisabled.has(model.name); + return ( + + ); + })} +
+
+ 💡 禁用的模型将对整个站点生效,该站点下所有连接都不会使用这些模型进行代理。 +
+ + )} + +
+
手动添加可用模型
+
+ 如果您的账号支持某些未在上方列表中显示的模型,可以在此手动添加(多个以英文逗号分隔)。 +
+
+ onManualInputChange(e.target.value)} + style={{ ...inputStyle, flex: 1, fontFamily: 'var(--font-mono)' }} + onKeyDown={(e) => { + if (e.key === 'Enter' && !modelModal.addingManualModels) { + void onAddManualModels(); + } + }} + /> + +
+
+
+ )} +
+ ); +}