From 922365c486d361ca3b2f93dfe9c0125ceea82c50 Mon Sep 17 00:00:00 2001 From: AlphaCat Date: Wed, 2 Sep 2026 14:56:15 +0800 Subject: [PATCH] =?UTF-8?q?refactor(settings):=20=E4=BE=9B=E5=BA=94?= =?UTF-8?q?=E5=95=86=E6=A8=A1=E5=9E=8B=E5=88=97=E8=A1=A8=E7=94=A8=E8=A1=A8?= =?UTF-8?q?=E5=A4=B4=E6=80=BB=E5=BC=80=E5=85=B3=E5=8F=96=E4=BB=A3=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E9=80=89=E6=8B=A9=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原交互需要 进入批量选择 -> 全部选中 -> 移到底部点启用 -> 点完成 四步。 现在列表头常驻一个总开关,一次点击即可启用/禁用当前可见(含搜索过滤) 的全部模型,同时显示已启用数量;逐个启用仍用每行原有开关。 - 删除批量选择模式及其全选/清空行、底部操作条 - 移除 getModelBulkActionCounts/applyModelBulkActiveState, 新增更简单的 applyModelsActiveState - 拖拽排序不再受批量模式限制,仅搜索过滤时禁用 - i18n 新增 enable/disableAllModels 与启用计数文案, 删除 modelReorderDisabledBulk --- .../settings/provider-models-fetch.test.mjs | 12 +- .../src/i18n/translations/enUSSettings.ts | 5 +- .../src/i18n/translations/zhCNSettings.ts | 5 +- .../src/pages/settings/ProviderModal.tsx | 93 ++------- .../src/pages/settings/ProviderModalView.tsx | 195 +++--------------- .../src/pages/settings/providerUtils.ts | 20 +- 6 files changed, 70 insertions(+), 260 deletions(-) diff --git a/crates/agent-gui/test/settings/provider-models-fetch.test.mjs b/crates/agent-gui/test/settings/provider-models-fetch.test.mjs index b050c407a..3ec6bc0b0 100644 --- a/crates/agent-gui/test/settings/provider-models-fetch.test.mjs +++ b/crates/agent-gui/test/settings/provider-models-fetch.test.mjs @@ -616,20 +616,16 @@ test("mergeFetchedModels never overwrites a user-edited stored value with a fres assert.equal(model.limitsSource, "user"); }); -test("model bulk helpers count and apply only selected active states", () => { +test("applyModelsActiveState applies enable/disable to target models only", () => { const activeModels = new Set(["enabled-model", "untouched-model"]); - const selectedModels = new Set(["enabled-model", "disabled-model"]); + const targetModels = ["enabled-model", "disabled-model"]; - assert.deepEqual(providerUtils.getModelBulkActionCounts(selectedModels, activeModels), { - enableCount: 1, - disableCount: 1, - }); assert.deepEqual( - [...providerUtils.applyModelBulkActiveState(activeModels, selectedModels, true)].sort(), + [...providerUtils.applyModelsActiveState(activeModels, targetModels, true)].sort(), ["disabled-model", "enabled-model", "untouched-model"], ); assert.deepEqual( - [...providerUtils.applyModelBulkActiveState(activeModels, selectedModels, false)].sort(), + [...providerUtils.applyModelsActiveState(activeModels, targetModels, false)].sort(), ["untouched-model"], ); assert.deepEqual([...activeModels].sort(), ["enabled-model", "untouched-model"]); diff --git a/crates/agent-ui/src/i18n/translations/enUSSettings.ts b/crates/agent-ui/src/i18n/translations/enUSSettings.ts index 9695ff156..7bd7703d0 100644 --- a/crates/agent-ui/src/i18n/translations/enUSSettings.ts +++ b/crates/agent-ui/src/i18n/translations/enUSSettings.ts @@ -626,9 +626,12 @@ export const EN_US_SETTINGS_TRANSLATIONS = { "settings.reorderModel": "Reorder model", "settings.reorderProvider": "Reorder provider", "settings.reorderVerticalHint": "Drag to reorder; touch and hold on mobile, or use Up/Down", - "settings.modelReorderDisabledBulk": "Reordering is unavailable in bulk selection mode", "settings.modelReorderDisabledSearch": "Reordering is unavailable while filtering", "settings.reorderNeedsTwoItems": "At least two items are required to reorder", + "settings.enableAllModels": "Enable all models", + "settings.disableAllModels": "Disable all models", + "settings.modelsEnabledCount": "{enabled} / {total} models enabled", + "settings.matchedModelsEnabledCount": "{enabled} / {total} matched models enabled", "settings.fetching": "Fetching…", "settings.deselectAll": "Deselect All", "settings.selectAll": "Select All", diff --git a/crates/agent-ui/src/i18n/translations/zhCNSettings.ts b/crates/agent-ui/src/i18n/translations/zhCNSettings.ts index 79999fb39..5fbc05a69 100644 --- a/crates/agent-ui/src/i18n/translations/zhCNSettings.ts +++ b/crates/agent-ui/src/i18n/translations/zhCNSettings.ts @@ -600,9 +600,12 @@ export const ZH_CN_SETTINGS_TRANSLATIONS = { "settings.reorderModel": "调整模型排序", "settings.reorderProvider": "调整供应商排序", "settings.reorderVerticalHint": "拖动排序;手机端长按后拖动,或聚焦后按上下方向键移动", - "settings.modelReorderDisabledBulk": "批量选择模式下不能拖拽排序", "settings.modelReorderDisabledSearch": "搜索过滤时不能拖拽排序", "settings.reorderNeedsTwoItems": "至少需要两项才能排序", + "settings.enableAllModels": "启用全部模型", + "settings.disableAllModels": "禁用全部模型", + "settings.modelsEnabledCount": "已启用 {enabled} / {total} 个模型", + "settings.matchedModelsEnabledCount": "匹配的模型已启用 {enabled} / {total} 个", "settings.fetching": "获取中…", "settings.deselectAll": "取消全选", "settings.selectAll": "全选", diff --git a/crates/agent-ui/src/pages/settings/ProviderModal.tsx b/crates/agent-ui/src/pages/settings/ProviderModal.tsx index fb67c59cf..b8dcf790f 100644 --- a/crates/agent-ui/src/pages/settings/ProviderModal.tsx +++ b/crates/agent-ui/src/pages/settings/ProviderModal.tsx @@ -33,8 +33,8 @@ import { findNewModelIds, } from "@liveagent/ui/lib/providers/modelVendor"; import { - applyModelBulkActiveState, applyModelInputModalitiesMode, + applyModelsActiveState, applyUsageQueryModePreset, buildProviderModelsFetchKey, clampUsageQueryTimeoutSecs, @@ -42,7 +42,6 @@ import { createUsageQueryDraft, detectCodingPlanProvider, fetchModelsFromApi, - getModelBulkActionCounts, getModelInputModalitiesMode, getPersistedUsageQueryProviderId, isGatewayWebuiRuntime, @@ -220,10 +219,6 @@ function useProviderModalController({ providerType, initialData, onSave, onClose const [addingModel, setAddingModel] = useState(false); const [newModelName, setNewModelName] = useState(""); const [modelSearch, setModelSearch] = useState(""); - const [modelBulkMode, setModelBulkMode] = useState(false); - const [modelBulkSelection, setModelBulkSelection] = useState>( - () => new Set(), - ); const [editingModel, setEditingModel] = useState(null); const [showApiKey, setShowApiKey] = useState(false); const [activePanel, setActivePanel] = useState("general"); @@ -504,44 +499,6 @@ function useProviderModalController({ providerType, initialData, onSave, onClose }); } - function exitModelBulkMode() { - setModelBulkMode(false); - setModelBulkSelection(new Set()); - } - - function toggleModelBulkMode() { - if (modelBulkMode) { - exitModelBulkMode(); - return; - } - setEditingModel(null); - setAddingModel(false); - setModelBulkSelection(new Set()); - setModelBulkMode(true); - } - - function toggleModelBulkSelection(modelId: string) { - setModelBulkSelection((prev) => { - const next = new Set(prev); - if (next.has(modelId)) next.delete(modelId); - else next.add(modelId); - return next; - }); - } - - function selectVisibleModels() { - setModelBulkSelection((prev) => { - const next = new Set(prev); - for (const model of visibleModels) next.add(model.id); - return next; - }); - } - - function applyModelBulkState(enabled: boolean) { - setActiveModels((prev) => applyModelBulkActiveState(prev, modelBulkSelection, enabled)); - setModelBulkSelection(new Set()); - } - function handleAddModel() { const model = newModelName.trim(); if (!model) return; @@ -568,12 +525,6 @@ function useProviderModalController({ providerType, initialData, onSave, onClose next.delete(model); return next; }); - setModelBulkSelection((prev) => { - if (!prev.has(model)) return prev; - const next = new Set(prev); - next.delete(model); - return next; - }); setEditingModel((prev) => (prev?.model.id === model ? null : prev)); } @@ -738,7 +689,6 @@ function useProviderModalController({ providerType, initialData, onSave, onClose ); if (invalidHeaderIndex >= 0) { setHeaderValidationSubmitted(true); - exitModelBulkMode(); setActivePanel("request"); // 导入视图会顶掉请求头列表,先切回列表再聚焦,否则目标输入框尚未挂载。 setHeaderImportOpen(false); @@ -869,15 +819,24 @@ function useProviderModalController({ providerType, initialData, onSave, onClose : orderedModels, [orderedModels, modelSearchQuery], ); - const allVisibleModelsSelected = - visibleModels.length > 0 && visibleModels.every((model) => modelBulkSelection.has(model.id)); - const { enableCount: modelBulkEnableCount, disableCount: modelBulkDisableCount } = useMemo( - () => getModelBulkActionCounts(modelBulkSelection, activeModels), - [modelBulkSelection, activeModels], + // 表头总开关:作用于当前可见(含搜索过滤)的模型。全部启用时视为“开”, + // 再点一次全部禁用;部分启用时点击补全为全部启用。 + const visibleActiveCount = useMemo( + () => visibleModels.reduce((count, model) => count + (activeModels.has(model.id) ? 1 : 0), 0), + [visibleModels, activeModels], ); - const modelReorderDisabledHint = modelBulkMode - ? t("settings.modelReorderDisabledBulk") - : modelSearchQuery + const allVisibleModelsActive = + visibleModels.length > 0 && visibleActiveCount === visibleModels.length; + function toggleVisibleModelsActive() { + setActiveModels((prev) => + applyModelsActiveState( + prev, + visibleModels.map((model) => model.id), + !allVisibleModelsActive, + ), + ); + } + const modelReorderDisabledHint = modelSearchQuery ? t("settings.modelReorderDisabledSearch") : t("settings.reorderNeedsTwoItems"); const handleModelReorder = useCallback((nextIds: string[]) => { @@ -896,7 +855,7 @@ function useProviderModalController({ providerType, initialData, onSave, onClose scrollContainerRef: modelScrollContainerRef, } = useVerticalListReorder({ itemIds: orderedModels.map((model) => model.id), - canReorder: !modelBulkMode && !modelSearchQuery, + canReorder: !modelSearchQuery, reorderLabel: t("settings.reorderModel"), reorderHint: t("settings.reorderVerticalHint"), disabledHint: modelReorderDisabledHint, @@ -966,13 +925,12 @@ function useProviderModalController({ providerType, initialData, onSave, onClose activePanel, addCustomHeader, addingModel, - allVisibleModelsSelected, + allVisibleModelsActive, apiKey, apiKeyForRequest, apiKeyIsRedactedDisplay, applyHeaderSuggestion, applyCliIdentityHeaders, - applyModelBulkState, baseUrl, canSaveEditingModel, canOverrideModelInputModalities, @@ -984,7 +942,6 @@ function useProviderModalController({ providerType, initialData, onSave, onClose editingModelContextWindow, editingModelInputModalitiesMode, editingModelMaxOutputToken, - exitModelBulkMode, fetchError, fetchingModels, focusCustomHeader, @@ -1010,10 +967,6 @@ function useProviderModalController({ providerType, initialData, onSave, onClose isFullUrl, isGatewayWebui, matchedBalanceProviders, - modelBulkDisableCount, - modelBulkEnableCount, - modelBulkMode, - modelBulkSelection, modelListRef, modelScrollContainerRef, modelSearch, @@ -1037,7 +990,6 @@ function useProviderModalController({ providerType, initialData, onSave, onClose requestClose, requestFormat, saveInlineModelSettings, - selectVisibleModels, setActivePanel, setAddingModel, setApiKey, @@ -1051,7 +1003,6 @@ function useProviderModalController({ providerType, initialData, onSave, onClose setHeaderSuggest, setHeaderSuggestActive, setIsFullUrl, - setModelBulkSelection, setModelSearch, setModelsUrl, setName, @@ -1074,8 +1025,7 @@ function useProviderModalController({ providerType, initialData, onSave, onClose commitStreamRetryCountInput, t, toggleModel, - toggleModelBulkMode, - toggleModelBulkSelection, + toggleVisibleModelsActive, typeLabel, updateCustomHeader, usageQuery, @@ -1085,6 +1035,7 @@ function useProviderModalController({ providerType, initialData, onSave, onClose usageVariableApiKey, usageVariableBaseUrl, useSystemProxy, + visibleActiveCount, visibleModels, }; return viewModel; diff --git a/crates/agent-ui/src/pages/settings/ProviderModalView.tsx b/crates/agent-ui/src/pages/settings/ProviderModalView.tsx index fa105b255..4a3337510 100644 --- a/crates/agent-ui/src/pages/settings/ProviderModalView.tsx +++ b/crates/agent-ui/src/pages/settings/ProviderModalView.tsx @@ -8,7 +8,6 @@ import { } from "@liveagent/app/lib/settings"; import { AudioLines, - Check, ClipboardPaste, ExternalLink, Eye, @@ -102,13 +101,12 @@ export function ProviderModalView({ viewModel }: { viewModel: ProviderModalViewM activePanel, addCustomHeader, addingModel, - allVisibleModelsSelected, + allVisibleModelsActive, apiKey, apiKeyForRequest, apiKeyIsRedactedDisplay, applyHeaderSuggestion, applyCliIdentityHeaders, - applyModelBulkState, baseUrl, canOverrideModelInputModalities, canSaveEditingModel, @@ -120,7 +118,6 @@ export function ProviderModalView({ viewModel }: { viewModel: ProviderModalViewM editingModelContextWindow, editingModelInputModalitiesMode, editingModelMaxOutputToken, - exitModelBulkMode, fetchError, fetchingModels, focusCustomHeader, @@ -146,10 +143,6 @@ export function ProviderModalView({ viewModel }: { viewModel: ProviderModalViewM isFullUrl, isGatewayWebui, matchedBalanceProviders, - modelBulkDisableCount, - modelBulkEnableCount, - modelBulkMode, - modelBulkSelection, modelListRef, modelScrollContainerRef, modelSearch, @@ -173,7 +166,6 @@ export function ProviderModalView({ viewModel }: { viewModel: ProviderModalViewM requestClose, requestFormat, saveInlineModelSettings, - selectVisibleModels, setActivePanel, setAddingModel, setApiKey, @@ -187,7 +179,6 @@ export function ProviderModalView({ viewModel }: { viewModel: ProviderModalViewM setHeaderSuggest, setHeaderSuggestActive, setIsFullUrl, - setModelBulkSelection, setModelSearch, setModelsUrl, setName, @@ -210,8 +201,7 @@ export function ProviderModalView({ viewModel }: { viewModel: ProviderModalViewM commitStreamRetryCountInput, t, toggleModel, - toggleModelBulkMode, - toggleModelBulkSelection, + toggleVisibleModelsActive, typeLabel, updateCustomHeader, usageQuery, @@ -221,6 +211,7 @@ export function ProviderModalView({ viewModel }: { viewModel: ProviderModalViewM usageVariableApiKey, usageVariableBaseUrl, useSystemProxy, + visibleActiveCount, visibleModels, } = viewModel; return ( @@ -279,7 +270,6 @@ export function ProviderModalView({ viewModel }: { viewModel: ProviderModalViewM activePanel === "request" && "bg-primary/10 font-medium text-primary", )} onClick={() => { - exitModelBulkMode(); setActivePanel("request"); }} aria-current={activePanel === "request" ? "page" : undefined} @@ -306,7 +296,6 @@ export function ProviderModalView({ viewModel }: { viewModel: ProviderModalViewM activePanel === "usage" && "bg-primary/10 font-medium text-primary", )} onClick={() => { - exitModelBulkMode(); setActivePanel("usage"); }} aria-current={activePanel === "usage" ? "page" : undefined} @@ -470,24 +459,6 @@ export function ProviderModalView({ viewModel }: { viewModel: ProviderModalViewM ) : null} - - + {visibleModels.length > 0 ? ( +
+
+ {/* w-5 占位与行内拖拽把手同宽,保证总开关和每行开关纵向对齐。 */} +
+ + {modelSearchQuery + ? t("settings.matchedModelsEnabledCount") + .replace("{enabled}", String(visibleActiveCount)) + .replace("{total}", String(visibleModels.length)) + : t("settings.modelsEnabledCount") + .replace("{enabled}", String(visibleActiveCount)) + .replace("{total}", String(visibleModels.length))} +
) : null} @@ -592,7 +565,6 @@ export function ProviderModalView({ viewModel }: { viewModel: ProviderModalViewM inputModalities?.includes(modality), ); return ( - // biome-ignore lint/a11y/noStaticElementInteractions lint/a11y/useAriaPropsSupportedByRole: The row becomes an accessible checkbox only while bulk mode is active.
{ - if (modelBulkMode) toggleModelBulkSelection(model.id); - }} - onKeyDown={(event) => { - if ( - !modelBulkMode || - event.target !== event.currentTarget || - (event.key !== "Enter" && event.key !== " ") - ) { - return; - } - event.preventDefault(); - toggleModelBulkSelection(model.id); - }} >
{renderModelDragHandle(model.id, model.id)} - {modelBulkMode ? ( - - ) : ( - toggleModel(model.id)} - ariaLabel={model.id} - /> - )} + toggleModel(model.id)} + ariaLabel={model.id} + />
@@ -713,7 +634,6 @@ export function ProviderModalView({ viewModel }: { viewModel: ProviderModalViewM "h-10 w-10 shrink-0 text-muted-foreground hover:text-foreground max-[720px]:col-start-3 max-[720px]:row-start-2", isEditingModel && "bg-primary/10 text-primary", )} - disabled={modelBulkMode} onClick={(event) => { event.stopPropagation(); openModelSettings(model.id); @@ -728,7 +648,6 @@ export function ProviderModalView({ viewModel }: { viewModel: ProviderModalViewM variant="ghost" size="icon" className="h-10 w-10 shrink-0 text-muted-foreground hover:bg-destructive/10 hover:text-destructive max-[720px]:col-start-4 max-[720px]:row-start-2" - disabled={modelBulkMode} onClick={(event) => { event.stopPropagation(); removeModel(model.id); @@ -2011,56 +1930,6 @@ export function ProviderModalView({ viewModel }: { viewModel: ProviderModalViewM
- {modelBulkMode && activePanel === "general" ? ( -
- - {t("settings.skillsBulkSelectedCount").replace( - "{count}", - String(modelBulkSelection.size), - )} - - - - - - - -
- ) : null} -