refactor(endpoints): implement deferred submission and fix clear-all bug
Implement Solution A (complete deferred submission) for custom endpoint
management, replacing the dual-mode system with unified local staging.
Changes:
- Remove immediate backend saves from EndpointSpeedTest
* handleAddEndpoint: local state update only
* handleRemoveEndpoint: local state update only
* handleSelect: remove lastUsed timestamp update
- Add explicit clear detection in ProviderForm
* Distinguish "user cleared endpoints" from "user didn't modify"
* Pass empty object {} as clear signal vs null for no-change
- Fix mergeProviderMeta to handle three distinct cases:
* null/undefined: don't modify endpoints (no meta sent)
* empty object {}: explicitly clear endpoints (send empty meta)
* with data: add/update endpoints (overwrite)
Fixed Critical Bug:
When users deleted all custom endpoints, changes were not saved because:
- draftCustomEndpoints=[] resulted in customEndpointsToSave=null
- mergeProviderMeta(meta, null) returned undefined
- Backend interpreted missing meta as "don't modify", preserving old values
Solution:
Detect when user had endpoints and cleared them (hadEndpoints && length===0),
then pass empty object to mergeProviderMeta as explicit clear signal.
Architecture Improvements:
- Transaction atomicity: all fields submitted together on form save
- UX consistency: add/edit modes behave identically
- Cancel button: true rollback with no immediate saves
- Code simplification: removed ~40 lines of immediate save error handling
Testing:
- TypeScript type check: passed
- Rust backend tests: 10/10 passed
- Build: successful
This commit is contained in:
@@ -2,9 +2,10 @@ import type { CustomEndpoint, ProviderMeta } from "@/types";
|
||||
|
||||
/**
|
||||
* 合并供应商元数据中的自定义端点。
|
||||
* - 当 customEndpoints 为空对象或 null 时,移除自定义端点但保留其它元数据。
|
||||
* - 当 customEndpoints 为空对象时,明确删除自定义端点但保留其它元数据。
|
||||
* - 当 customEndpoints 为 null/undefined 时,不修改端点(保留原有端点)。
|
||||
* - 当 customEndpoints 存在时,覆盖原有自定义端点。
|
||||
* - 若结果为空对象则返回 undefined,避免写入空 meta。
|
||||
* - 若结果为空对象且非明确清空场景则返回 undefined,避免写入空 meta。
|
||||
*/
|
||||
export function mergeProviderMeta(
|
||||
initialMeta: ProviderMeta | undefined,
|
||||
@@ -13,6 +14,12 @@ export function mergeProviderMeta(
|
||||
const hasCustomEndpoints =
|
||||
!!customEndpoints && Object.keys(customEndpoints).length > 0;
|
||||
|
||||
// 明确清空:传入空对象(非 null/undefined)表示用户想要删除所有端点
|
||||
const isExplicitClear =
|
||||
customEndpoints !== null &&
|
||||
customEndpoints !== undefined &&
|
||||
Object.keys(customEndpoints).length === 0;
|
||||
|
||||
if (hasCustomEndpoints) {
|
||||
return {
|
||||
...(initialMeta ? { ...initialMeta } : {}),
|
||||
@@ -20,6 +27,25 @@ export function mergeProviderMeta(
|
||||
};
|
||||
}
|
||||
|
||||
// 明确清空端点
|
||||
if (isExplicitClear) {
|
||||
if (!initialMeta) {
|
||||
// 新供应商且用户没有添加端点(理论上不会到这里)
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if ("custom_endpoints" in initialMeta) {
|
||||
const { custom_endpoints, ...rest } = initialMeta;
|
||||
// 保留其他字段(如 usage_script)
|
||||
// 即使 rest 为空,也要返回空对象(让后端知道要清空 meta)
|
||||
return Object.keys(rest).length > 0 ? rest : {};
|
||||
}
|
||||
|
||||
// initialMeta 中本来就没有 custom_endpoints
|
||||
return { ...initialMeta };
|
||||
}
|
||||
|
||||
// null/undefined:用户没有修改端点,保持不变
|
||||
if (!initialMeta) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user