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:
@@ -217,7 +217,7 @@ pub async fn update_provider(
|
||||
}
|
||||
}
|
||||
|
||||
// 更新内存并保存
|
||||
// 更新内存并保存(保留/合并已有的 meta.custom_endpoints,避免丢失在编辑流程中新增的自定义端点)
|
||||
{
|
||||
let mut config = state
|
||||
.config
|
||||
@@ -226,9 +226,43 @@ pub async fn update_provider(
|
||||
let manager = config
|
||||
.get_manager_mut(&app_type)
|
||||
.ok_or_else(|| format!("应用类型不存在: {:?}", app_type))?;
|
||||
|
||||
// 若已存在旧供应商,合并其 meta(尤其是 custom_endpoints)到新对象
|
||||
let merged_provider = if let Some(existing) = manager.providers.get(&provider.id) {
|
||||
// 克隆入参作为基准
|
||||
let mut updated = provider.clone();
|
||||
|
||||
match (existing.meta.as_ref(), updated.meta.take()) {
|
||||
// 入参未携带 meta:直接沿用旧 meta
|
||||
(Some(old_meta), None) => {
|
||||
updated.meta = Some(old_meta.clone());
|
||||
}
|
||||
// 入参携带 meta:与旧 meta 合并(以旧值为准,保留新增项)
|
||||
(Some(old_meta), Some(mut new_meta)) => {
|
||||
// 合并 custom_endpoints(URL 去重,保留旧端点的时间信息,补充新增端点)
|
||||
let mut merged_map = old_meta.custom_endpoints.clone();
|
||||
for (url, ep) in new_meta.custom_endpoints.drain() {
|
||||
merged_map.entry(url).or_insert(ep);
|
||||
}
|
||||
updated.meta = Some(crate::provider::ProviderMeta {
|
||||
custom_endpoints: merged_map,
|
||||
});
|
||||
}
|
||||
// 旧 meta 不存在:使用入参(可能为 None)
|
||||
(None, maybe_new) => {
|
||||
updated.meta = maybe_new;
|
||||
}
|
||||
}
|
||||
|
||||
updated
|
||||
} else {
|
||||
// 不存在旧供应商(理论上不应发生,因为前面已校验 exists)
|
||||
provider.clone()
|
||||
};
|
||||
|
||||
manager
|
||||
.providers
|
||||
.insert(provider.id.clone(), provider.clone());
|
||||
.insert(merged_provider.id.clone(), merged_provider);
|
||||
}
|
||||
state.save()?;
|
||||
|
||||
|
||||
@@ -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(若存在)
|
||||
// - 当前选中的基础 URL(baseUrl/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);
|
||||
|
||||
Reference in New Issue
Block a user