修复配置保存覆盖服务选择
This commit is contained in:
@@ -3574,6 +3574,97 @@ function projectSettingsSchemaView(settingsSchemaApi, normalizedInput = {}, payl
|
|||||||
return settingsSchemaApi.buildSettingsView(normalizedSettingsState, payload);
|
return settingsSchemaApi.buildSettingsView(normalizedSettingsState, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setSettingsStatePatchValue(patch, path, value) {
|
||||||
|
let cursor = patch;
|
||||||
|
for (let index = 0; index < path.length - 1; index += 1) {
|
||||||
|
const key = path[index];
|
||||||
|
if (!isPlainObjectValue(cursor[key])) {
|
||||||
|
cursor[key] = {};
|
||||||
|
}
|
||||||
|
cursor = cursor[key];
|
||||||
|
}
|
||||||
|
cursor[path[path.length - 1]] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeSettingsStatePatch(baseValue = {}, patchValue = {}) {
|
||||||
|
if (!isPlainObjectValue(patchValue)) {
|
||||||
|
return isPlainObjectValue(baseValue) ? { ...baseValue } : {};
|
||||||
|
}
|
||||||
|
const next = {
|
||||||
|
...(isPlainObjectValue(baseValue) ? baseValue : {}),
|
||||||
|
};
|
||||||
|
Object.entries(patchValue).forEach(([key, value]) => {
|
||||||
|
next[key] = isPlainObjectValue(value)
|
||||||
|
? mergeSettingsStatePatch(next[key], value)
|
||||||
|
: value;
|
||||||
|
});
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSettingsStatePatchFromFlatUpdates(updates = {}) {
|
||||||
|
const patch = {};
|
||||||
|
const hasUpdate = (key) => Object.prototype.hasOwnProperty.call(updates, key);
|
||||||
|
const assignIfUpdated = (key, path) => {
|
||||||
|
if (hasUpdate(key)) {
|
||||||
|
setSettingsStatePatchValue(patch, path, updates[key]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assignIfUpdated('activeFlowId', ['activeFlowId']);
|
||||||
|
if (hasUpdate('openaiIntegrationTargetId') || hasUpdate('panelMode')) {
|
||||||
|
setSettingsStatePatchValue(
|
||||||
|
patch,
|
||||||
|
['flows', 'openai', 'integrationTargetId'],
|
||||||
|
hasUpdate('openaiIntegrationTargetId') ? updates.openaiIntegrationTargetId : updates.panelMode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assignIfUpdated('kiroTargetId', ['flows', 'kiro', 'targetId']);
|
||||||
|
assignIfUpdated('vpsUrl', ['flows', 'openai', 'integrationTargets', 'cpa', 'vpsUrl']);
|
||||||
|
assignIfUpdated('vpsPassword', ['flows', 'openai', 'integrationTargets', 'cpa', 'vpsPassword']);
|
||||||
|
assignIfUpdated('localCpaStep9Mode', ['flows', 'openai', 'integrationTargets', 'cpa', 'localCpaStep9Mode']);
|
||||||
|
assignIfUpdated('sub2apiUrl', ['flows', 'openai', 'integrationTargets', 'sub2api', 'sub2apiUrl']);
|
||||||
|
assignIfUpdated('sub2apiEmail', ['flows', 'openai', 'integrationTargets', 'sub2api', 'sub2apiEmail']);
|
||||||
|
assignIfUpdated('sub2apiPassword', ['flows', 'openai', 'integrationTargets', 'sub2api', 'sub2apiPassword']);
|
||||||
|
assignIfUpdated('sub2apiGroupName', ['flows', 'openai', 'integrationTargets', 'sub2api', 'sub2apiGroupName']);
|
||||||
|
assignIfUpdated('sub2apiGroupNames', ['flows', 'openai', 'integrationTargets', 'sub2api', 'sub2apiGroupNames']);
|
||||||
|
assignIfUpdated('sub2apiAccountPriority', ['flows', 'openai', 'integrationTargets', 'sub2api', 'sub2apiAccountPriority']);
|
||||||
|
assignIfUpdated('sub2apiDefaultProxyName', ['flows', 'openai', 'integrationTargets', 'sub2api', 'sub2apiDefaultProxyName']);
|
||||||
|
assignIfUpdated('codex2apiUrl', ['flows', 'openai', 'integrationTargets', 'codex2api', 'codex2apiUrl']);
|
||||||
|
assignIfUpdated('codex2apiAdminKey', ['flows', 'openai', 'integrationTargets', 'codex2api', 'codex2apiAdminKey']);
|
||||||
|
assignIfUpdated('customPassword', ['services', 'account', 'customPassword']);
|
||||||
|
assignIfUpdated('signupMethod', ['flows', 'openai', 'signup', 'signupMethod']);
|
||||||
|
assignIfUpdated('phoneVerificationEnabled', ['flows', 'openai', 'signup', 'phoneVerificationEnabled']);
|
||||||
|
assignIfUpdated('phoneSignupReloginAfterBindEmailEnabled', ['flows', 'openai', 'signup', 'phoneSignupReloginAfterBindEmailEnabled']);
|
||||||
|
assignIfUpdated('plusModeEnabled', ['flows', 'openai', 'plus', 'plusModeEnabled']);
|
||||||
|
assignIfUpdated('plusPaymentMethod', ['flows', 'openai', 'plus', 'plusPaymentMethod']);
|
||||||
|
assignIfUpdated('plusAccountAccessStrategy', ['flows', 'openai', 'plus', 'plusAccountAccessStrategy']);
|
||||||
|
assignIfUpdated('mailProvider', ['services', 'email', 'provider']);
|
||||||
|
assignIfUpdated('ipProxyEnabled', ['services', 'proxy', 'enabled']);
|
||||||
|
assignIfUpdated('ipProxyService', ['services', 'proxy', 'provider']);
|
||||||
|
assignIfUpdated('ipProxyMode', ['services', 'proxy', 'mode']);
|
||||||
|
assignIfUpdated('kiroRsUrl', ['flows', 'kiro', 'targets', 'kiro-rs', 'baseUrl']);
|
||||||
|
assignIfUpdated('kiroRsKey', ['flows', 'kiro', 'targets', 'kiro-rs', 'apiKey']);
|
||||||
|
|
||||||
|
if (hasUpdate('stepExecutionRangeByFlow') && isPlainObjectValue(updates.stepExecutionRangeByFlow)) {
|
||||||
|
if (isPlainObjectValue(updates.stepExecutionRangeByFlow.openai)) {
|
||||||
|
setSettingsStatePatchValue(
|
||||||
|
patch,
|
||||||
|
['flows', 'openai', 'autoRun', 'stepExecutionRange'],
|
||||||
|
updates.stepExecutionRangeByFlow.openai
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (isPlainObjectValue(updates.stepExecutionRangeByFlow.kiro)) {
|
||||||
|
setSettingsStatePatchValue(
|
||||||
|
patch,
|
||||||
|
['flows', 'kiro', 'autoRun', 'stepExecutionRange'],
|
||||||
|
updates.stepExecutionRangeByFlow.kiro
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return patch;
|
||||||
|
}
|
||||||
|
|
||||||
function buildPersistedSettingsStoragePayload(payload = {}) {
|
function buildPersistedSettingsStoragePayload(payload = {}) {
|
||||||
const storagePayload = {};
|
const storagePayload = {};
|
||||||
Object.entries(payload || {}).forEach(([key, value]) => {
|
Object.entries(payload || {}).forEach(([key, value]) => {
|
||||||
@@ -3924,6 +4015,10 @@ async function setPersistentSettings(updates) {
|
|||||||
? settingsSchemaApi.mergeSettingsState(currentSettingsState, nextUpdates.settingsState)
|
? settingsSchemaApi.mergeSettingsState(currentSettingsState, nextUpdates.settingsState)
|
||||||
: nextUpdates.settingsState)
|
: nextUpdates.settingsState)
|
||||||
: currentSettingsState;
|
: currentSettingsState;
|
||||||
|
mergedSettingsState = mergeSettingsStatePatch(
|
||||||
|
mergedSettingsState,
|
||||||
|
buildSettingsStatePatchFromFlatUpdates(explicitFlatUpdates)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextPayloadInput = {
|
const nextPayloadInput = {
|
||||||
|
|||||||
@@ -174,6 +174,9 @@ function resolveLegacyAutoStepDelaySeconds() { return undefined; }
|
|||||||
${extractFunction('normalizePersistentSettingValue')}
|
${extractFunction('normalizePersistentSettingValue')}
|
||||||
${extractFunction('getSettingsSchemaApi')}
|
${extractFunction('getSettingsSchemaApi')}
|
||||||
${extractFunction('projectSettingsSchemaView')}
|
${extractFunction('projectSettingsSchemaView')}
|
||||||
|
${extractFunction('setSettingsStatePatchValue')}
|
||||||
|
${extractFunction('mergeSettingsStatePatch')}
|
||||||
|
${extractFunction('buildSettingsStatePatchFromFlatUpdates')}
|
||||||
${extractFunction('buildPersistedSettingsStoragePayload')}
|
${extractFunction('buildPersistedSettingsStoragePayload')}
|
||||||
${extractFunction('buildPersistentSettingsPayload')}
|
${extractFunction('buildPersistentSettingsPayload')}
|
||||||
${extractFunction('getPersistedSettings')}
|
${extractFunction('getPersistedSettings')}
|
||||||
@@ -513,3 +516,176 @@ function getRemovedKeys() {
|
|||||||
assert.ok(api.getRemovedKeys().includes('panelMode'));
|
assert.ok(api.getRemovedKeys().includes('panelMode'));
|
||||||
assert.ok(api.getRemovedKeys().includes('kiroRsUrl'));
|
assert.ok(api.getRemovedKeys().includes('kiroRsUrl'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('setPersistentSettings mirrors flat mail provider updates into canonical settingsState', async () => {
|
||||||
|
const api = buildHarness(`
|
||||||
|
const persistedWrites = [];
|
||||||
|
const removedKeys = [];
|
||||||
|
const chrome = {
|
||||||
|
storage: {
|
||||||
|
local: {
|
||||||
|
async get() {
|
||||||
|
return {
|
||||||
|
settingsSchemaVersion: 4,
|
||||||
|
settingsState: {
|
||||||
|
activeFlowId: 'openai',
|
||||||
|
services: {
|
||||||
|
account: { customPassword: '' },
|
||||||
|
email: { provider: '163' },
|
||||||
|
proxy: { enabled: false, provider: '711proxy', mode: 'account' },
|
||||||
|
},
|
||||||
|
flows: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async remove(keys) {
|
||||||
|
removedKeys.push(...(Array.isArray(keys) ? keys : [keys]));
|
||||||
|
},
|
||||||
|
async set(payload) {
|
||||||
|
persistedWrites.push(JSON.parse(JSON.stringify(payload)));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
function getPersistedWrites() {
|
||||||
|
return persistedWrites;
|
||||||
|
}
|
||||||
|
function getRemovedKeys() {
|
||||||
|
return removedKeys;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const persisted = await api.setPersistentSettings({
|
||||||
|
mailProvider: 'cloudflare-temp-email',
|
||||||
|
});
|
||||||
|
const write = api.getPersistedWrites().at(-1);
|
||||||
|
|
||||||
|
assert.equal(persisted.mailProvider, 'cloudflare-temp-email');
|
||||||
|
assert.equal(persisted.settingsState.services.email.provider, 'cloudflare-temp-email');
|
||||||
|
assert.equal(write.settingsState.services.email.provider, 'cloudflare-temp-email');
|
||||||
|
assert.equal(Object.prototype.hasOwnProperty.call(write, 'mailProvider'), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setPersistentSettings mirrors flat schema updates without resetting other canonical settings', async () => {
|
||||||
|
const api = buildHarness(`
|
||||||
|
const persistedWrites = [];
|
||||||
|
const removedKeys = [];
|
||||||
|
const chrome = {
|
||||||
|
storage: {
|
||||||
|
local: {
|
||||||
|
async get() {
|
||||||
|
return {
|
||||||
|
settingsSchemaVersion: 4,
|
||||||
|
settingsState: {
|
||||||
|
activeFlowId: 'openai',
|
||||||
|
services: {
|
||||||
|
account: { customPassword: 'old-password' },
|
||||||
|
email: { provider: '163' },
|
||||||
|
proxy: { enabled: false, provider: '711proxy', mode: 'account' },
|
||||||
|
},
|
||||||
|
flows: {
|
||||||
|
openai: {
|
||||||
|
integrationTargetId: 'cpa',
|
||||||
|
integrationTargets: {
|
||||||
|
cpa: {
|
||||||
|
vpsUrl: 'https://old-cpa.example.com',
|
||||||
|
vpsPassword: 'old-vps-password',
|
||||||
|
localCpaStep9Mode: 'submit',
|
||||||
|
},
|
||||||
|
sub2api: {
|
||||||
|
sub2apiUrl: 'https://sub2api.example.com',
|
||||||
|
sub2apiEmail: 'owner@example.com',
|
||||||
|
sub2apiPassword: 'sub2api-secret',
|
||||||
|
sub2apiGroupName: 'kept-group',
|
||||||
|
sub2apiGroupNames: ['kept-group'],
|
||||||
|
sub2apiAccountPriority: 7,
|
||||||
|
sub2apiDefaultProxyName: 'proxy-a',
|
||||||
|
},
|
||||||
|
codex2api: {
|
||||||
|
codex2apiUrl: 'https://codex2api.example.com',
|
||||||
|
codex2apiAdminKey: 'codex-key',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
signup: {
|
||||||
|
signupMethod: 'email',
|
||||||
|
phoneVerificationEnabled: false,
|
||||||
|
phoneSignupReloginAfterBindEmailEnabled: false,
|
||||||
|
},
|
||||||
|
plus: {
|
||||||
|
plusModeEnabled: false,
|
||||||
|
plusPaymentMethod: 'paypal',
|
||||||
|
plusAccountAccessStrategy: 'oauth',
|
||||||
|
},
|
||||||
|
autoRun: {
|
||||||
|
stepExecutionRange: { enabled: false, fromStep: 1, toStep: 11 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
kiro: {
|
||||||
|
targetId: 'kiro-rs',
|
||||||
|
targets: {
|
||||||
|
'kiro-rs': {
|
||||||
|
baseUrl: 'https://kiro.example.com/admin',
|
||||||
|
apiKey: 'kiro-key',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
autoRun: {
|
||||||
|
stepExecutionRange: { enabled: false, fromStep: 1, toStep: 9 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async remove(keys) {
|
||||||
|
removedKeys.push(...(Array.isArray(keys) ? keys : [keys]));
|
||||||
|
},
|
||||||
|
async set(payload) {
|
||||||
|
persistedWrites.push(JSON.parse(JSON.stringify(payload)));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
function getPersistedWrites() {
|
||||||
|
return persistedWrites;
|
||||||
|
}
|
||||||
|
function getRemovedKeys() {
|
||||||
|
return removedKeys;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const persisted = await api.setPersistentSettings({
|
||||||
|
panelMode: 'sub2api',
|
||||||
|
mailProvider: 'cloudflare-temp-email',
|
||||||
|
ipProxyEnabled: true,
|
||||||
|
ipProxyMode: 'api',
|
||||||
|
stepExecutionRangeByFlow: {
|
||||||
|
openai: { enabled: true, fromStep: 2, toStep: 4 },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const write = api.getPersistedWrites().at(-1);
|
||||||
|
|
||||||
|
assert.equal(persisted.panelMode, 'sub2api');
|
||||||
|
assert.equal(persisted.mailProvider, 'cloudflare-temp-email');
|
||||||
|
assert.equal(persisted.ipProxyEnabled, true);
|
||||||
|
assert.equal(persisted.ipProxyMode, 'api');
|
||||||
|
assert.deepEqual(persisted.stepExecutionRangeByFlow.openai, {
|
||||||
|
enabled: true,
|
||||||
|
fromStep: 2,
|
||||||
|
toStep: 4,
|
||||||
|
});
|
||||||
|
assert.equal(write.settingsState.flows.openai.integrationTargetId, 'sub2api');
|
||||||
|
assert.equal(write.settingsState.services.email.provider, 'cloudflare-temp-email');
|
||||||
|
assert.equal(write.settingsState.services.proxy.enabled, true);
|
||||||
|
assert.equal(write.settingsState.services.proxy.mode, 'api');
|
||||||
|
assert.deepEqual(write.settingsState.flows.openai.autoRun.stepExecutionRange, {
|
||||||
|
enabled: true,
|
||||||
|
fromStep: 2,
|
||||||
|
toStep: 4,
|
||||||
|
});
|
||||||
|
assert.equal(write.settingsState.flows.openai.integrationTargets.sub2api.sub2apiUrl, 'https://sub2api.example.com');
|
||||||
|
assert.equal(write.settingsState.flows.openai.integrationTargets.sub2api.sub2apiEmail, 'owner@example.com');
|
||||||
|
assert.equal(write.settingsState.flows.kiro.targets['kiro-rs'].apiKey, 'kiro-key');
|
||||||
|
assert.equal(Object.prototype.hasOwnProperty.call(write, 'mailProvider'), false);
|
||||||
|
assert.equal(Object.prototype.hasOwnProperty.call(write, 'panelMode'), false);
|
||||||
|
assert.equal(Object.prototype.hasOwnProperty.call(write, 'ipProxyMode'), false);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user