fix: preserve meta.custom_endpoints on update and persist preset candidates on create

- Preserve and merge meta.custom_endpoints in update_provider to avoid losing custom endpoints added via Tauri commands during edit/save. Merge old and incoming meta; keep existing entries and timestamps, add new URLs only.
- Persist endpoint candidates when creating a provider: union of user-added custom endpoints, selected base URL (Claude/Codex), and preset.endpointCandidates; normalize and de-duplicate. Ensures PackyCode keeps all 5 nodes after saving.

Files:
- src-tauri/src/commands.rs
- src/components/ProviderForm.tsx

Validation:
- cargo check passes
- Manual: create from PackyCode preset -> save -> reopen edit -> Manage & Test lists all preset nodes; edit existing provider -> add endpoint -> save -> reopen -> endpoint persists.
This commit is contained in:
Jason
2025-10-10 20:20:08 +08:00
parent c350e64687
commit bfdf7d4ad5
2 changed files with 89 additions and 13 deletions

View File

@@ -615,19 +615,61 @@ const ProviderForm: React.FC<ProviderFormProps> = ({
...(category ? { category } : {}),
};
// 若为"新建供应商"且已在弹窗中添加了自定义端点,则随提交一并落盘
if (!initialData && draftCustomEndpoints.length > 0) {
const now = Date.now();
const customMap: Record<string, CustomEndpoint> = {};
for (const raw of draftCustomEndpoints) {
const url = raw.trim().replace(/\/+$/, "");
if (!url) continue;
if (!customMap[url]) {
customMap[url] = { url, addedAt: now, lastUsed: undefined };
// 若为"新建供应商"将端点候选一并随提交落盘到 meta.custom_endpoints
// - 用户在弹窗中新增的自定义端点draftCustomEndpoints已去重
// - 预设中的 endpointCandidates若存在
// - 当前选中的基础 URLbaseUrl/codexBaseUrl
if (!initialData) {
const urlSet = new Set<string>();
const push = (raw?: string) => {
const url = (raw || "").trim().replace(/\/+$/, "");
if (url) urlSet.add(url);
};
// 自定义端点(仅来自用户新增)
for (const u of draftCustomEndpoints) push(u);
// 预设端点候选
if (!isCodex) {
if (
selectedPreset !== null &&
selectedPreset >= 0 &&
selectedPreset < providerPresets.length
) {
const preset = providerPresets[selectedPreset] as any;
if (Array.isArray(preset?.endpointCandidates)) {
for (const u of preset.endpointCandidates as string[]) push(u);
}
}
// 当前 Claude 基础地址
push(baseUrl);
} else {
if (
selectedCodexPreset !== null &&
selectedCodexPreset >= 0 &&
selectedCodexPreset < codexProviderPresets.length
) {
const preset = codexProviderPresets[selectedCodexPreset] as any;
if (Array.isArray(preset?.endpointCandidates)) {
for (const u of preset.endpointCandidates as string[]) push(u);
}
}
// 当前 Codex 基础地址
push(codexBaseUrl);
}
const urls = Array.from(urlSet.values());
if (urls.length > 0) {
const now = Date.now();
const customMap: Record<string, CustomEndpoint> = {};
for (const url of urls) {
if (!customMap[url]) {
customMap[url] = { url, addedAt: now, lastUsed: undefined };
}
}
onSubmit({ ...basePayload, meta: { custom_endpoints: customMap } });
return;
}
onSubmit({ ...basePayload, meta: { custom_endpoints: customMap } });
return;
}
onSubmit(basePayload);