refactor: 重构 Kiro flow 配置与运行链路
This commit is contained in:
+123
-183
@@ -798,9 +798,8 @@ function getNodeDefinitionsForState(state = {}) {
|
||||
}
|
||||
return getStepDefinitionsForState(state)
|
||||
.map((definition) => ({
|
||||
legacyStepId: Number(definition?.id),
|
||||
nodeId: String(definition?.key || '').trim(),
|
||||
displayOrder: Number.isFinite(Number(definition?.order)) ? Number(definition.order) : Number(definition?.id),
|
||||
displayOrder: Number.isFinite(Number(definition?.id)) ? Number(definition.id) : Number(definition?.order),
|
||||
title: String(definition?.title || '').trim(),
|
||||
executeKey: String(definition?.key || '').trim(),
|
||||
}))
|
||||
@@ -829,17 +828,21 @@ function getLastNodeIdForState(state = {}) {
|
||||
}
|
||||
|
||||
function getNodeIdByStepForState(step, state = {}) {
|
||||
const definition = getStepDefinitionForState(step, state);
|
||||
return String(definition?.key || '').trim();
|
||||
const numericStep = Number(step);
|
||||
if (!Number.isInteger(numericStep) || numericStep <= 0) {
|
||||
return '';
|
||||
}
|
||||
const node = getNodeDefinitionsForState(state).find((definition) => Number(definition?.displayOrder) === numericStep);
|
||||
return String(node?.nodeId || '').trim();
|
||||
}
|
||||
|
||||
function getStepIdByNodeIdForState(nodeId, state = {}) {
|
||||
const normalizedNodeId = String(nodeId || '').trim();
|
||||
if (!normalizedNodeId) return null;
|
||||
const node = getNodeDefinitionForState(normalizedNodeId, state);
|
||||
const legacyStepId = Number(node?.legacyStepId);
|
||||
if (Number.isInteger(legacyStepId) && legacyStepId > 0) {
|
||||
return legacyStepId;
|
||||
const displayOrder = Number(node?.displayOrder);
|
||||
if (Number.isInteger(displayOrder) && displayOrder > 0) {
|
||||
return displayOrder;
|
||||
}
|
||||
return getStepIdByKeyForState(normalizedNodeId, state);
|
||||
}
|
||||
@@ -1074,6 +1077,39 @@ const PERSISTED_SETTING_DEFAULTS = {
|
||||
|
||||
const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);
|
||||
const PERSISTED_SETTINGS_SCHEMA_KEYS = ['settingsSchemaVersion', 'settingsState'];
|
||||
const SETTINGS_SCHEMA_VIEW_KEYS = Object.freeze([
|
||||
'activeFlowId',
|
||||
'openaiIntegrationTargetId',
|
||||
'kiroIntegrationTargetId',
|
||||
'panelMode',
|
||||
'kiroSourceId',
|
||||
'vpsUrl',
|
||||
'vpsPassword',
|
||||
'localCpaStep9Mode',
|
||||
'sub2apiUrl',
|
||||
'sub2apiEmail',
|
||||
'sub2apiPassword',
|
||||
'sub2apiGroupName',
|
||||
'sub2apiGroupNames',
|
||||
'sub2apiAccountPriority',
|
||||
'sub2apiDefaultProxyName',
|
||||
'codex2apiUrl',
|
||||
'codex2apiAdminKey',
|
||||
'customPassword',
|
||||
'signupMethod',
|
||||
'phoneVerificationEnabled',
|
||||
'phoneSignupReloginAfterBindEmailEnabled',
|
||||
'plusModeEnabled',
|
||||
'plusPaymentMethod',
|
||||
'mailProvider',
|
||||
'ipProxyEnabled',
|
||||
'ipProxyService',
|
||||
'ipProxyMode',
|
||||
'kiroRsUrl',
|
||||
'kiroRsKey',
|
||||
'stepExecutionRangeByFlow',
|
||||
]);
|
||||
const SETTINGS_SCHEMA_VIEW_KEY_SET = new Set(SETTINGS_SCHEMA_VIEW_KEYS);
|
||||
const SETTINGS_EXPORT_SCHEMA_VERSION = 1;
|
||||
const SETTINGS_EXPORT_FILENAME_PREFIX = 'multipage-settings';
|
||||
const STEP6_REGISTRATION_SUCCESS_WAIT_MS = 20000;
|
||||
@@ -1087,91 +1123,14 @@ const DEFAULT_STATE = {
|
||||
nodeStatuses: { ...DEFAULT_NODE_STATUSES },
|
||||
runtimeState: runtimeStateHelpers?.buildDefaultRuntimeState?.() || null,
|
||||
...CONTRIBUTION_RUNTIME_DEFAULTS,
|
||||
oauthUrl: null, // 运行时抓取到的 OAuth 地址,不要手动预填。
|
||||
resolvedSignupMethod: null, // 当前自动轮次冻结后的实际注册方式。
|
||||
accountIdentifierType: null,
|
||||
accountIdentifier: '',
|
||||
registrationEmailState: { ...DEFAULT_REGISTRATION_EMAIL_STATE },
|
||||
email: null, // 运行时邮箱,由程序自动获取并写入,不能手动预填。
|
||||
password: null, // 运行时实际密码,由 customPassword 或程序自动生成后写入。
|
||||
accounts: [], // 已生成账号记录:{ email, password, createdAt }。
|
||||
accountRunHistory: [], // 账号运行历史快照,实际持久化在 chrome.storage.local。
|
||||
manualAliasUsage: {},
|
||||
preservedAliases: {},
|
||||
icloudAliasCache: [],
|
||||
icloudAliasCacheAt: 0,
|
||||
lastEmailTimestamp: null, // 最近一次获取到邮箱数据的运行时时间戳。
|
||||
lastSignupCode: null, // 注册验证码,运行时由程序自动读取并写入。
|
||||
lastLoginCode: null, // 登录验证码,运行时由程序自动读取并写入。
|
||||
localhostUrl: null, // 运行时捕获到的 localhost 回调地址,不要手动预填。
|
||||
cpaOAuthState: null, // CPA OAuth state。
|
||||
cpaManagementOrigin: null, // CPA 管理接口 origin。
|
||||
sub2apiSessionId: null, // SUB2API OpenAI Auth 会话 ID。
|
||||
sub2apiOAuthState: null, // SUB2API OpenAI Auth state。
|
||||
sub2apiGroupId: null, // SUB2API 目标分组 ID。
|
||||
sub2apiGroupIds: [], // SUB2API 多目标分组 ID。
|
||||
sub2apiDraftName: null, // SUB2API 本轮预生成的账号名称。
|
||||
sub2apiProxyId: null, // SUB2API 本轮使用的代理 ID。
|
||||
codex2apiSessionId: null, // Codex2API OAuth 会话 ID。
|
||||
codex2apiOAuthState: null, // Codex2API OAuth state。
|
||||
plusCheckoutTabId: null, // Plus checkout / PayPal 标签页 ID。
|
||||
automationWindowId: null, // 当前任务锁定的浏览器窗口 ID,避免新标签页跑到其它窗口。
|
||||
plusCheckoutUrl: null, // Plus checkout 运行时短链,不写入持久配置。
|
||||
plusCheckoutCountry: 'DE',
|
||||
plusCheckoutCurrency: 'EUR',
|
||||
plusCheckoutSource: '',
|
||||
plusBillingCountryText: '',
|
||||
plusBillingAddress: null,
|
||||
plusPaypalApprovedAt: null,
|
||||
plusGoPayApprovedAt: null,
|
||||
plusReturnUrl: '',
|
||||
plusManualConfirmationPending: false,
|
||||
plusManualConfirmationRequestId: '',
|
||||
plusManualConfirmationStep: 0,
|
||||
plusManualConfirmationMethod: '',
|
||||
plusManualConfirmationTitle: '',
|
||||
plusManualConfirmationMessage: '',
|
||||
gopayHelperReferenceId: '',
|
||||
gopayHelperGoPayGuid: '',
|
||||
gopayHelperRedirectUrl: '',
|
||||
gopayHelperNextAction: '',
|
||||
gopayHelperFlowId: '',
|
||||
gopayHelperChallengeId: '',
|
||||
gopayHelperStartPayload: null,
|
||||
gopayHelperTaskId: '',
|
||||
gopayHelperTaskStatus: '',
|
||||
gopayHelperStatusText: '',
|
||||
gopayHelperRemoteStage: '',
|
||||
gopayHelperApiWaitingFor: '',
|
||||
gopayHelperApiInputDeadlineAt: '',
|
||||
gopayHelperApiInputWaitSeconds: 0,
|
||||
gopayHelperLastInputError: '',
|
||||
gopayHelperOtpInvalidCount: 0,
|
||||
gopayHelperFailureStage: '',
|
||||
gopayHelperFailureDetail: '',
|
||||
gopayHelperTaskPayload: null,
|
||||
gopayHelperOrderCreatedAt: 0,
|
||||
gopayHelperTaskProgressSignature: '',
|
||||
gopayHelperTaskProgressAt: 0,
|
||||
gopayHelperTaskProgressTaskId: '',
|
||||
gopayHelperPinPayload: null,
|
||||
gopayHelperResolvedOtp: '',
|
||||
gopayHelperOtpRequestId: '',
|
||||
gopayHelperOtpReferenceId: '',
|
||||
flowStartTime: null, // 当前流程开始时间。
|
||||
tabRegistry: {}, // 程序维护的标签页注册表。
|
||||
sourceLastUrls: {}, // 各来源页面最近一次打开的地址记录。
|
||||
logs: [], // 侧边栏展示的运行日志。
|
||||
...PERSISTED_SETTING_DEFAULTS, // 合并 chrome.storage.local 中持久化保存的用户配置。
|
||||
ipProxyApiPool: [],
|
||||
ipProxyApiCurrentIndex: 0,
|
||||
ipProxyApiCurrent: null,
|
||||
ipProxyAccountPool: [],
|
||||
ipProxyAccountCurrentIndex: 0,
|
||||
ipProxyAccountCurrent: null,
|
||||
ipProxyPool: [],
|
||||
ipProxyCurrentIndex: 0,
|
||||
ipProxyCurrent: null,
|
||||
luckmailApiKey: '',
|
||||
luckmailBaseUrl: DEFAULT_LUCKMAIL_BASE_URL,
|
||||
luckmailEmailType: DEFAULT_LUCKMAIL_EMAIL_TYPE,
|
||||
@@ -1179,23 +1138,6 @@ const DEFAULT_STATE = {
|
||||
luckmailUsedPurchases: {},
|
||||
luckmailPreserveTagId: 0,
|
||||
luckmailPreserveTagName: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
|
||||
currentLuckmailPurchase: null,
|
||||
currentLuckmailMailCursor: null,
|
||||
currentYydsMailInbox: null,
|
||||
currentPhoneActivation: null,
|
||||
phoneNumber: '',
|
||||
currentPhoneVerificationCode: '',
|
||||
currentPhoneVerificationCountdownEndsAt: 0,
|
||||
currentPhoneVerificationCountdownWindowIndex: 0,
|
||||
currentPhoneVerificationCountdownWindowTotal: 0,
|
||||
reusablePhoneActivation: null,
|
||||
freeReusablePhoneActivation: null,
|
||||
phoneReusableActivationPool: [],
|
||||
signupPhoneNumber: '',
|
||||
signupPhoneActivation: null,
|
||||
signupPhoneCompletedActivation: null,
|
||||
signupPhoneVerificationRequestedAt: null,
|
||||
signupPhoneVerificationPurpose: '',
|
||||
heroSmsLastPriceTiers: [],
|
||||
heroSmsLastPriceCountryId: 0,
|
||||
heroSmsLastPriceCountryLabel: '',
|
||||
@@ -3353,14 +3295,14 @@ function buildPersistentSettingsPayload(input = {}, options = {}) {
|
||||
const settingsSchemaApi = typeof getSettingsSchemaApi === 'function'
|
||||
? getSettingsSchemaApi()
|
||||
: null;
|
||||
if (settingsSchemaApi?.normalizeSettingsState && settingsSchemaApi?.buildLegacySettingsPayload) {
|
||||
if (settingsSchemaApi?.normalizeSettingsState && settingsSchemaApi?.buildSettingsView) {
|
||||
const settingsSchemaInput = {};
|
||||
for (const key of persistedSettingKeys) {
|
||||
if (normalizedInput[key] !== undefined) {
|
||||
settingsSchemaInput[key] = payload[key];
|
||||
}
|
||||
}
|
||||
const normalizedSettingsState = settingsSchemaApi.normalizeSettingsState({
|
||||
Object.assign(payload, projectSettingsSchemaView(settingsSchemaApi, {
|
||||
...settingsSchemaInput,
|
||||
...(isPlainObjectForSettingsSchema(normalizedInput.settingsState)
|
||||
? { settingsState: normalizedInput.settingsState }
|
||||
@@ -3368,13 +3310,7 @@ function buildPersistentSettingsPayload(input = {}, options = {}) {
|
||||
...(Object.prototype.hasOwnProperty.call(normalizedInput, 'settingsSchemaVersion')
|
||||
? { settingsSchemaVersion: normalizedInput.settingsSchemaVersion }
|
||||
: {}),
|
||||
}, {
|
||||
activeFlowId: settingsSchemaInput.activeFlowId || normalizedInput.activeFlowId || DEFAULT_ACTIVE_FLOW_ID,
|
||||
});
|
||||
Object.assign(
|
||||
payload,
|
||||
settingsSchemaApi.buildLegacySettingsPayload(normalizedSettingsState, payload)
|
||||
);
|
||||
}, payload));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3391,6 +3327,32 @@ function getSettingsSchemaApi() {
|
||||
});
|
||||
}
|
||||
|
||||
function projectSettingsSchemaView(settingsSchemaApi, normalizedInput = {}, payload = {}) {
|
||||
if (
|
||||
!settingsSchemaApi?.normalizeSettingsState
|
||||
|| !settingsSchemaApi?.buildSettingsView
|
||||
) {
|
||||
return payload;
|
||||
}
|
||||
const normalizedSettingsState = settingsSchemaApi.normalizeSettingsState(normalizedInput, {
|
||||
activeFlowId: normalizedInput?.activeFlowId || DEFAULT_ACTIVE_FLOW_ID,
|
||||
});
|
||||
return settingsSchemaApi.buildSettingsView(normalizedSettingsState, payload);
|
||||
}
|
||||
|
||||
function buildPersistedSettingsStoragePayload(payload = {}) {
|
||||
const storagePayload = {};
|
||||
Object.entries(payload || {}).forEach(([key, value]) => {
|
||||
if (SETTINGS_SCHEMA_VIEW_KEY_SET.has(key)) {
|
||||
return;
|
||||
}
|
||||
storagePayload[key] = value;
|
||||
});
|
||||
storagePayload.settingsSchemaVersion = Number(payload?.settingsSchemaVersion) || 0;
|
||||
storagePayload.settingsState = payload?.settingsState;
|
||||
return storagePayload;
|
||||
}
|
||||
|
||||
async function getPersistedSettings() {
|
||||
const stored = await chrome.storage.local.get([
|
||||
...PERSISTED_SETTING_KEYS,
|
||||
@@ -3481,6 +3443,9 @@ function buildAutoRunFreshResetSettingsState(prevState = {}, activeFlowId = DEFA
|
||||
const nextSettingsStatePatch = {
|
||||
activeFlowId,
|
||||
services: {
|
||||
account: {
|
||||
customPassword: prevState?.customPassword,
|
||||
},
|
||||
email: {
|
||||
provider: prevState?.mailProvider,
|
||||
},
|
||||
@@ -3492,9 +3457,7 @@ function buildAutoRunFreshResetSettingsState(prevState = {}, activeFlowId = DEFA
|
||||
},
|
||||
flows: {
|
||||
openai: {
|
||||
source: {
|
||||
selected: prevState?.panelMode,
|
||||
},
|
||||
integrationTargetId: prevState?.openaiIntegrationTargetId || prevState?.panelMode,
|
||||
autoRun: normalizedStepExecutionRangeByFlow.openai
|
||||
? {
|
||||
stepExecutionRange: normalizedStepExecutionRangeByFlow.openai,
|
||||
@@ -3502,9 +3465,7 @@ function buildAutoRunFreshResetSettingsState(prevState = {}, activeFlowId = DEFA
|
||||
: undefined,
|
||||
},
|
||||
kiro: {
|
||||
source: {
|
||||
selected: prevState?.kiroSourceId,
|
||||
},
|
||||
integrationTargetId: prevState?.kiroIntegrationTargetId || prevState?.kiroSourceId,
|
||||
autoRun: normalizedStepExecutionRangeByFlow.kiro
|
||||
? {
|
||||
stepExecutionRange: normalizedStepExecutionRangeByFlow.kiro,
|
||||
@@ -3651,78 +3612,58 @@ async function setPersistentSettings(updates) {
|
||||
const settingsSchemaApi = typeof getSettingsSchemaApi === 'function'
|
||||
? getSettingsSchemaApi()
|
||||
: null;
|
||||
const hasSchemaApi = Boolean(
|
||||
settingsSchemaApi?.normalizeSettingsState
|
||||
&& settingsSchemaApi?.buildSettingsView
|
||||
);
|
||||
const explicitFlatUpdates = {
|
||||
...nextUpdates,
|
||||
};
|
||||
delete explicitFlatUpdates.settingsSchemaVersion;
|
||||
delete explicitFlatUpdates.settingsState;
|
||||
|
||||
let persistedUpdates;
|
||||
if (settingsSchemaApi?.normalizeSettingsState && settingsSchemaApi?.buildLegacySettingsPayload) {
|
||||
const isPlainObjectForSettingsSchema = typeof isPlainObjectValue === 'function'
|
||||
? isPlainObjectValue
|
||||
: ((value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value));
|
||||
const cloneSettingsValue = (value) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((entry) => cloneSettingsValue(entry));
|
||||
}
|
||||
if (isPlainObjectForSettingsSchema(value)) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([key, entryValue]) => [key, cloneSettingsValue(entryValue)])
|
||||
);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
const mergeSettingsState = (baseValue, patchValue) => {
|
||||
if (Array.isArray(patchValue)) {
|
||||
return patchValue.map((entry) => cloneSettingsValue(entry));
|
||||
}
|
||||
if (!isPlainObjectForSettingsSchema(patchValue)) {
|
||||
return patchValue === undefined ? cloneSettingsValue(baseValue) : patchValue;
|
||||
}
|
||||
|
||||
const baseObject = isPlainObjectForSettingsSchema(baseValue) ? baseValue : {};
|
||||
const nextObject = {
|
||||
...cloneSettingsValue(baseObject),
|
||||
};
|
||||
for (const [key, entryValue] of Object.entries(patchValue)) {
|
||||
nextObject[key] = mergeSettingsState(baseObject[key], entryValue);
|
||||
}
|
||||
return nextObject;
|
||||
};
|
||||
|
||||
let mergedSettingsState = nextUpdates.settingsState ?? currentSettings.settingsState;
|
||||
if (hasSchemaApi) {
|
||||
const currentSettingsState = settingsSchemaApi.normalizeSettingsState(
|
||||
isPlainObjectForSettingsSchema(currentSettings?.settingsState)
|
||||
isPlainObjectValue(currentSettings?.settingsState)
|
||||
? { settingsState: currentSettings.settingsState }
|
||||
: currentSettings,
|
||||
{
|
||||
activeFlowId: currentSettings?.activeFlowId || DEFAULT_ACTIVE_FLOW_ID,
|
||||
}
|
||||
);
|
||||
const mergedSettingsState = isPlainObjectForSettingsSchema(nextUpdates.settingsState)
|
||||
? mergeSettingsState(currentSettingsState, nextUpdates.settingsState)
|
||||
mergedSettingsState = isPlainObjectValue(nextUpdates.settingsState)
|
||||
? (typeof settingsSchemaApi.mergeSettingsState === 'function'
|
||||
? settingsSchemaApi.mergeSettingsState(currentSettingsState, nextUpdates.settingsState)
|
||||
: nextUpdates.settingsState)
|
||||
: currentSettingsState;
|
||||
const explicitFlatUpdates = {
|
||||
...nextUpdates,
|
||||
};
|
||||
delete explicitFlatUpdates.settingsSchemaVersion;
|
||||
delete explicitFlatUpdates.settingsState;
|
||||
|
||||
persistedUpdates = buildPersistentSettingsPayload({
|
||||
...explicitFlatUpdates,
|
||||
settingsSchemaVersion: nextUpdates.settingsSchemaVersion ?? currentSettings.settingsSchemaVersion,
|
||||
settingsState: mergedSettingsState,
|
||||
}, {
|
||||
fillDefaults: true,
|
||||
});
|
||||
} else {
|
||||
persistedUpdates = buildPersistentSettingsPayload({
|
||||
...currentSettings,
|
||||
...nextUpdates,
|
||||
settingsSchemaVersion: nextUpdates.settingsSchemaVersion ?? currentSettings.settingsSchemaVersion,
|
||||
settingsState: nextUpdates.settingsState ?? currentSettings.settingsState,
|
||||
}, {
|
||||
fillDefaults: true,
|
||||
});
|
||||
}
|
||||
|
||||
const nextPayloadInput = {
|
||||
...currentSettings,
|
||||
...explicitFlatUpdates,
|
||||
settingsSchemaVersion: nextUpdates.settingsSchemaVersion ?? currentSettings.settingsSchemaVersion,
|
||||
settingsState: mergedSettingsState,
|
||||
};
|
||||
if (hasSchemaApi && isPlainObjectValue(nextUpdates.settingsState)) {
|
||||
for (const key of SETTINGS_SCHEMA_VIEW_KEYS) {
|
||||
delete nextPayloadInput[key];
|
||||
}
|
||||
Object.assign(nextPayloadInput, explicitFlatUpdates);
|
||||
}
|
||||
|
||||
const persistedUpdates = buildPersistentSettingsPayload(nextPayloadInput, {
|
||||
fillDefaults: true,
|
||||
});
|
||||
|
||||
if (Object.keys(persistedUpdates).length > 0) {
|
||||
await chrome.storage.local.set(persistedUpdates);
|
||||
const storagePayload = hasSchemaApi
|
||||
? buildPersistedSettingsStoragePayload(persistedUpdates)
|
||||
: persistedUpdates;
|
||||
if (hasSchemaApi && chrome.storage?.local?.remove) {
|
||||
await chrome.storage.local.remove(SETTINGS_SCHEMA_VIEW_KEYS);
|
||||
}
|
||||
await chrome.storage.local.set(storagePayload);
|
||||
}
|
||||
return persistedUpdates;
|
||||
}
|
||||
@@ -13563,9 +13504,8 @@ function buildNodeRegistry(definitions = []) {
|
||||
return self.MultiPageBackgroundStepRegistry?.createNodeRegistry(
|
||||
definitions.map((definition) => ({
|
||||
...definition,
|
||||
legacyStepId: definition.legacyStepId || definition.id,
|
||||
nodeId: definition.nodeId || definition.key,
|
||||
displayOrder: definition.displayOrder || definition.order,
|
||||
displayOrder: definition.displayOrder || definition.id || definition.order,
|
||||
executeKey: definition.executeKey || definition.key,
|
||||
execute: stepExecutorsByKey[definition.executeKey || definition.key || definition.nodeId],
|
||||
}))
|
||||
@@ -13580,15 +13520,15 @@ function buildStepRegistry(definitions = []) {
|
||||
const normalizedDefinitions = (Array.isArray(definitions) ? definitions : [])
|
||||
.map((definition) => ({
|
||||
...definition,
|
||||
legacyStepId: Number(definition?.legacyStepId ?? definition?.id) || 0,
|
||||
displayOrder: Number(definition?.displayOrder ?? definition?.id ?? definition?.order) || 0,
|
||||
nodeId: String(definition?.nodeId || definition?.key || '').trim(),
|
||||
}))
|
||||
.filter((definition) => definition.nodeId);
|
||||
const nodeRegistry = buildNodeRegistry(normalizedDefinitions);
|
||||
const stepToNodeDefinition = new Map(
|
||||
normalizedDefinitions
|
||||
.filter((definition) => Number.isInteger(definition.legacyStepId) && definition.legacyStepId > 0)
|
||||
.map((definition) => [definition.legacyStepId, definition])
|
||||
.filter((definition) => Number.isInteger(definition.displayOrder) && definition.displayOrder > 0)
|
||||
.map((definition) => [definition.displayOrder, definition])
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -13617,10 +13557,10 @@ function getStepRegistryForState(state = {}) {
|
||||
const activeFlowId = String(state?.activeFlowId || state?.flowId || DEFAULT_ACTIVE_FLOW_ID).trim().toLowerCase() || DEFAULT_ACTIVE_FLOW_ID;
|
||||
const cacheKey = `${activeFlowId}:${(Array.isArray(definitions) ? definitions : [])
|
||||
.map((definition) => [
|
||||
Number(definition?.legacyStepId ?? definition?.id) || 0,
|
||||
Number(definition?.displayOrder ?? definition?.id ?? definition?.order) || 0,
|
||||
String(definition?.nodeId || definition?.key || '').trim(),
|
||||
String(definition?.executeKey || definition?.key || '').trim(),
|
||||
Number(definition?.displayOrder ?? definition?.order) || 0,
|
||||
Number(definition?.displayOrder ?? definition?.id ?? definition?.order) || 0,
|
||||
].join(':'))
|
||||
.join('|')}`;
|
||||
|
||||
|
||||
+135
-80
@@ -244,21 +244,6 @@
|
||||
};
|
||||
}
|
||||
|
||||
function flattenFlowStateById(flowState = {}, flowId = 'openai') {
|
||||
const fieldGroups = FLOW_FIELD_GROUPS[flowId] || {};
|
||||
const scopedState = normalizePlainObject(flowState[flowId]);
|
||||
const next = {};
|
||||
for (const [groupKey, fields] of Object.entries(fieldGroups)) {
|
||||
const group = normalizePlainObject(scopedState[groupKey]);
|
||||
for (const field of fields) {
|
||||
if (Object.prototype.hasOwnProperty.call(group, field)) {
|
||||
next[field] = cloneValue(group[field]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
function buildScopedFlowState(baseFlowState = {}, state = {}, flowId = 'openai') {
|
||||
const fieldGroups = FLOW_FIELD_GROUPS[flowId] || {};
|
||||
const baseScopedState = cloneValue(normalizePlainObject(baseFlowState[flowId]));
|
||||
@@ -276,7 +261,7 @@
|
||||
return scopedState;
|
||||
}
|
||||
|
||||
function buildOpenAiFlowState(baseValue = {}, state = {}) {
|
||||
function buildFlowState(baseValue = {}, state = {}) {
|
||||
const baseFlowState = cloneValue(normalizePlainObject(baseValue));
|
||||
return {
|
||||
...baseFlowState,
|
||||
@@ -285,6 +270,126 @@
|
||||
};
|
||||
}
|
||||
|
||||
function listFlowFieldNames() {
|
||||
const fields = [];
|
||||
for (const flowGroups of Object.values(FLOW_FIELD_GROUPS)) {
|
||||
for (const groupFields of Object.values(normalizePlainObject(flowGroups))) {
|
||||
for (const field of Array.isArray(groupFields) ? groupFields : []) {
|
||||
const normalizedField = String(field || '').trim();
|
||||
if (normalizedField) {
|
||||
fields.push(normalizedField);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Array.from(new Set(fields));
|
||||
}
|
||||
|
||||
const FLOW_RUNTIME_FIELDS = Object.freeze(listFlowFieldNames());
|
||||
const RUNTIME_TOP_LEVEL_FIELDS = Object.freeze([
|
||||
...RUNTIME_SHARED_FIELDS,
|
||||
...RUNTIME_PROXY_FIELDS,
|
||||
...FLOW_RUNTIME_FIELDS,
|
||||
]);
|
||||
const RUNTIME_TOP_LEVEL_FIELD_SET = new Set(RUNTIME_TOP_LEVEL_FIELDS);
|
||||
const RUNTIME_PATCH_IGNORED_KEYS = new Set([
|
||||
'runtimeState',
|
||||
'flowState',
|
||||
'sharedState',
|
||||
'serviceState',
|
||||
'flows',
|
||||
'shared',
|
||||
'services',
|
||||
'currentStep',
|
||||
'stepStatuses',
|
||||
'legacyStepCompat',
|
||||
'flowId',
|
||||
'runId',
|
||||
'activeFlowId',
|
||||
'activeRunId',
|
||||
'currentNodeId',
|
||||
'nodeStatuses',
|
||||
...RUNTIME_TOP_LEVEL_FIELDS,
|
||||
]);
|
||||
|
||||
function projectScopedFlowFields(flowState = {}) {
|
||||
const next = {};
|
||||
for (const [flowId, fieldGroups] of Object.entries(FLOW_FIELD_GROUPS)) {
|
||||
const scopedState = normalizePlainObject(normalizePlainObject(flowState)[flowId]);
|
||||
for (const [groupKey, fields] of Object.entries(fieldGroups)) {
|
||||
const group = normalizePlainObject(scopedState[groupKey]);
|
||||
Object.assign(next, pickDefinedFields(group, fields));
|
||||
}
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
function projectRuntimeViewFields(runtimeState = {}) {
|
||||
const normalizedRuntimeState = normalizePlainObject(runtimeState);
|
||||
return {
|
||||
...pickDefinedFields(
|
||||
normalizePlainObject(normalizedRuntimeState.sharedState),
|
||||
RUNTIME_SHARED_FIELDS
|
||||
),
|
||||
...pickDefinedFields(
|
||||
normalizePlainObject(normalizePlainObject(normalizedRuntimeState.serviceState).proxy),
|
||||
RUNTIME_PROXY_FIELDS
|
||||
),
|
||||
...projectScopedFlowFields(normalizedRuntimeState.flowState),
|
||||
};
|
||||
}
|
||||
|
||||
function buildRuntimeInputFromPatch(updates = {}) {
|
||||
const normalizedUpdates = normalizePlainObject(updates);
|
||||
const runtimeStatePatch = normalizePlainObject(normalizedUpdates.runtimeState);
|
||||
const next = {
|
||||
...pickDefinedFields(normalizedUpdates, [
|
||||
'flowId',
|
||||
'runId',
|
||||
'activeFlowId',
|
||||
'activeRunId',
|
||||
'currentNodeId',
|
||||
'nodeStatuses',
|
||||
]),
|
||||
...pickDefinedFields(runtimeStatePatch, [
|
||||
'flowId',
|
||||
'runId',
|
||||
'activeFlowId',
|
||||
'activeRunId',
|
||||
'currentNodeId',
|
||||
'nodeStatuses',
|
||||
]),
|
||||
...projectRuntimeViewFields(runtimeStatePatch),
|
||||
...pickDefinedFields(normalizedUpdates, RUNTIME_TOP_LEVEL_FIELDS),
|
||||
};
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'sharedState')) {
|
||||
Object.assign(
|
||||
next,
|
||||
pickDefinedFields(normalizePlainObject(normalizedUpdates.sharedState), RUNTIME_SHARED_FIELDS)
|
||||
);
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'serviceState')) {
|
||||
Object.assign(
|
||||
next,
|
||||
pickDefinedFields(
|
||||
normalizePlainObject(normalizePlainObject(normalizedUpdates.serviceState).proxy),
|
||||
RUNTIME_PROXY_FIELDS
|
||||
)
|
||||
);
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(normalizedUpdates, 'flowState')) {
|
||||
Object.assign(next, projectScopedFlowFields(normalizedUpdates.flowState));
|
||||
}
|
||||
if (!Object.prototype.hasOwnProperty.call(next, 'flowId') && Object.prototype.hasOwnProperty.call(next, 'activeFlowId')) {
|
||||
next.flowId = next.activeFlowId;
|
||||
}
|
||||
if (!Object.prototype.hasOwnProperty.call(next, 'runId') && Object.prototype.hasOwnProperty.call(next, 'activeRunId')) {
|
||||
next.runId = next.activeRunId;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
function buildRuntimeStateDefault() {
|
||||
return {
|
||||
flowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||
@@ -366,69 +471,18 @@
|
||||
nodeStatuses,
|
||||
sharedState: buildSharedState(baseRuntimeState.sharedState, state),
|
||||
serviceState: buildServiceState(baseRuntimeState.serviceState, state),
|
||||
flowState: buildOpenAiFlowState(baseRuntimeState.flowState, state),
|
||||
flowState: buildFlowState(baseRuntimeState.flowState, state),
|
||||
};
|
||||
}
|
||||
|
||||
function buildFlattenedUpdates(updates = {}) {
|
||||
const ignoredKeys = new Set(['current' + 'Step', 'step' + 'Statuses', 'legacy' + 'StepCompat']);
|
||||
function buildPersistentPatchPayload(updates = {}) {
|
||||
const next = {};
|
||||
for (const [key, value] of Object.entries(updates || {})) {
|
||||
if (!ignoredKeys.has(key)) {
|
||||
next[key] = value;
|
||||
for (const [key, value] of Object.entries(normalizePlainObject(updates))) {
|
||||
if (RUNTIME_PATCH_IGNORED_KEYS.has(key)) {
|
||||
continue;
|
||||
}
|
||||
next[key] = cloneValue(value);
|
||||
}
|
||||
const runtimeState = normalizePlainObject(updates.runtimeState);
|
||||
const sharedState = normalizePlainObject(updates.sharedState);
|
||||
const serviceState = normalizePlainObject(updates.serviceState);
|
||||
const flowState = normalizePlainObject(updates.flowState);
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(runtimeState, 'activeFlowId')) {
|
||||
next.activeFlowId = runtimeState.activeFlowId;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(runtimeState, 'flowId')) {
|
||||
next.flowId = runtimeState.flowId;
|
||||
next.activeFlowId = runtimeState.flowId;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(runtimeState, 'activeRunId')) {
|
||||
next.activeRunId = runtimeState.activeRunId;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(runtimeState, 'runId')) {
|
||||
next.runId = runtimeState.runId;
|
||||
next.activeRunId = runtimeState.runId;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(runtimeState, 'currentNodeId')) {
|
||||
next.currentNodeId = runtimeState.currentNodeId;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(runtimeState, 'nodeStatuses')) {
|
||||
next.nodeStatuses = cloneValue(runtimeState.nodeStatuses);
|
||||
}
|
||||
Object.assign(next, pickDefinedFields(sharedState, RUNTIME_SHARED_FIELDS));
|
||||
if (Object.prototype.hasOwnProperty.call(runtimeState, 'sharedState')) {
|
||||
Object.assign(
|
||||
next,
|
||||
pickDefinedFields(normalizePlainObject(runtimeState.sharedState), RUNTIME_SHARED_FIELDS)
|
||||
);
|
||||
}
|
||||
|
||||
const serviceProxy = normalizePlainObject(serviceState.proxy);
|
||||
Object.assign(next, pickDefinedFields(serviceProxy, RUNTIME_PROXY_FIELDS));
|
||||
if (Object.prototype.hasOwnProperty.call(runtimeState, 'serviceState')) {
|
||||
const runtimeServiceState = normalizePlainObject(runtimeState.serviceState);
|
||||
Object.assign(
|
||||
next,
|
||||
pickDefinedFields(normalizePlainObject(runtimeServiceState.proxy), RUNTIME_PROXY_FIELDS)
|
||||
);
|
||||
}
|
||||
|
||||
Object.assign(next, flattenFlowStateById(flowState, 'openai'));
|
||||
Object.assign(next, flattenFlowStateById(flowState, 'kiro'));
|
||||
if (Object.prototype.hasOwnProperty.call(runtimeState, 'flowState')) {
|
||||
const runtimeFlowState = normalizePlainObject(runtimeState.flowState);
|
||||
Object.assign(next, flattenFlowStateById(runtimeFlowState, 'openai'));
|
||||
Object.assign(next, flattenFlowStateById(runtimeFlowState, 'kiro'));
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
@@ -436,6 +490,7 @@
|
||||
const runtimeState = ensureRuntimeState(state);
|
||||
return {
|
||||
...state,
|
||||
...projectRuntimeViewFields(runtimeState),
|
||||
flowId: runtimeState.flowId,
|
||||
runId: runtimeState.runId,
|
||||
activeFlowId: runtimeState.activeFlowId,
|
||||
@@ -453,15 +508,15 @@
|
||||
}
|
||||
|
||||
function buildSessionStatePatch(currentState = {}, updates = {}) {
|
||||
const flattenedUpdates = buildFlattenedUpdates(updates);
|
||||
const nextState = {
|
||||
...currentState,
|
||||
...flattenedUpdates,
|
||||
};
|
||||
const runtimeState = ensureRuntimeState(nextState);
|
||||
const currentRuntimeState = ensureRuntimeState(currentState);
|
||||
const runtimeState = ensureRuntimeState({
|
||||
runtimeState: currentRuntimeState,
|
||||
...projectRuntimeViewFields(currentRuntimeState),
|
||||
...buildRuntimeInputFromPatch(updates),
|
||||
});
|
||||
|
||||
return {
|
||||
...flattenedUpdates,
|
||||
...buildPersistentPatchPayload(updates),
|
||||
flowId: runtimeState.flowId,
|
||||
runId: runtimeState.runId,
|
||||
activeFlowId: runtimeState.activeFlowId,
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
function createNodeRegistry(definitions = []) {
|
||||
const ordered = (Array.isArray(definitions) ? definitions : [])
|
||||
.map((definition) => ({
|
||||
legacyStepId: Number(definition?.legacyStepId ?? definition?.id) || 0,
|
||||
flowId: String(definition?.flowId || '').trim(),
|
||||
nodeId: String(definition?.nodeId || definition?.key || '').trim(),
|
||||
displayOrder: Number(definition?.displayOrder ?? definition?.order),
|
||||
displayOrder: Number(definition?.displayOrder ?? definition?.order ?? definition?.id),
|
||||
executeKey: String(definition?.executeKey || definition?.key || definition?.nodeId || '').trim(),
|
||||
title: String(definition?.title || '').trim(),
|
||||
execute: definition?.execute,
|
||||
|
||||
@@ -348,11 +348,10 @@
|
||||
function cloneNodes(steps = [], options = {}, flowId = DEFAULT_ACTIVE_FLOW_ID) {
|
||||
const { builder } = getFlowDefinitionBuilder({ activeFlowId: flowId });
|
||||
return steps.map((step) => ({
|
||||
legacyStepId: Number(step.id),
|
||||
nodeId: String(step.key || '').trim(),
|
||||
flowId,
|
||||
title: builder?.resolveStepTitle ? builder.resolveStepTitle(step, options) : step.title,
|
||||
displayOrder: Number.isFinite(Number(step.order)) ? Number(step.order) : Number(step.id),
|
||||
displayOrder: Number.isFinite(Number(step.id)) ? Number(step.id) : Number(step.order),
|
||||
nodeType: 'task',
|
||||
sourceId: step.sourceId || '',
|
||||
driverId: step.driverId || '',
|
||||
|
||||
+163
-87
@@ -5,12 +5,11 @@
|
||||
const flowRegistryApi = rootScope.MultiPageFlowRegistry || {};
|
||||
const settingsSchemaApi = rootScope.MultiPageSettingsSchema || {};
|
||||
const DEFAULT_FLOW_ID = flowRegistryApi.DEFAULT_FLOW_ID || 'openai';
|
||||
const DEFAULT_PANEL_MODE = flowRegistryApi.DEFAULT_OPENAI_SOURCE_ID || 'cpa';
|
||||
const LEGACY_OPENAI_FLOW_ALIAS = String(flowRegistryApi.LEGACY_OPENAI_FLOW_ALIAS || 'codex').trim().toLowerCase();
|
||||
const DEFAULT_OPENAI_INTEGRATION_TARGET_ID = flowRegistryApi.DEFAULT_OPENAI_INTEGRATION_TARGET_ID || 'cpa';
|
||||
const SIGNUP_METHOD_EMAIL = 'email';
|
||||
const SIGNUP_METHOD_PHONE = 'phone';
|
||||
const VALID_PANEL_MODES = Array.isArray(flowRegistryApi.OPENAI_SOURCE_IDS)
|
||||
? flowRegistryApi.OPENAI_SOURCE_IDS.slice()
|
||||
const VALID_OPENAI_INTEGRATION_TARGET_IDS = Array.isArray(flowRegistryApi.OPENAI_INTEGRATION_TARGET_IDS)
|
||||
? flowRegistryApi.OPENAI_INTEGRATION_TARGET_IDS.slice()
|
||||
: ['cpa', 'sub2api', 'codex2api'];
|
||||
const REGISTERED_FLOW_IDS = Array.isArray(flowRegistryApi.getRegisteredFlowIds?.())
|
||||
? flowRegistryApi.getRegisteredFlowIds().map((flowId) => String(flowId || '').trim().toLowerCase()).filter(Boolean)
|
||||
@@ -23,7 +22,7 @@
|
||||
supportsPhoneVerificationSettings: false,
|
||||
supportsPlusMode: false,
|
||||
supportsContributionMode: false,
|
||||
supportsPlatformBinding: [],
|
||||
supportedIntegrationTargets: [],
|
||||
supportsLuckmail: false,
|
||||
supportsOauthTimeoutBudget: false,
|
||||
canSwitchFlow: true,
|
||||
@@ -48,7 +47,7 @@
|
||||
)
|
||||
);
|
||||
|
||||
const DEFAULT_PANEL_CAPABILITIES = Object.freeze({
|
||||
const DEFAULT_INTEGRATION_TARGET_CAPABILITIES = Object.freeze({
|
||||
supportsPhoneSignup: true,
|
||||
requiresPhoneSignupWarning: false,
|
||||
});
|
||||
@@ -61,9 +60,11 @@
|
||||
'plusModeEnabled',
|
||||
'signupMethod',
|
||||
'kiroSourceId',
|
||||
'openaiIntegrationTargetId',
|
||||
'kiroIntegrationTargetId',
|
||||
]);
|
||||
|
||||
const PANEL_CAPABILITIES = Object.freeze({
|
||||
const OPENAI_INTEGRATION_TARGET_CAPABILITIES = Object.freeze({
|
||||
cpa: Object.freeze({
|
||||
supportsPhoneSignup: true,
|
||||
requiresPhoneSignupWarning: true,
|
||||
@@ -88,30 +89,26 @@
|
||||
|
||||
function normalizeCapabilityFlowId(value = '', fallback = DEFAULT_FLOW_ID) {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
if (normalized === LEGACY_OPENAI_FLOW_ALIAS) {
|
||||
return DEFAULT_FLOW_ID;
|
||||
}
|
||||
if (normalized) {
|
||||
return normalized;
|
||||
}
|
||||
const normalizedFallback = String(fallback || '').trim().toLowerCase();
|
||||
if (normalizedFallback === LEGACY_OPENAI_FLOW_ALIAS) {
|
||||
return DEFAULT_FLOW_ID;
|
||||
}
|
||||
return normalizedFallback || DEFAULT_FLOW_ID;
|
||||
return normalizeFlowId(fallback, DEFAULT_FLOW_ID);
|
||||
}
|
||||
|
||||
function isRegisteredFlowId(flowId = '') {
|
||||
return REGISTERED_FLOW_ID_SET.has(normalizeCapabilityFlowId(flowId, ''));
|
||||
const normalized = String(flowId || '').trim().toLowerCase();
|
||||
return Boolean(normalized) && REGISTERED_FLOW_ID_SET.has(normalized);
|
||||
}
|
||||
|
||||
function normalizePanelMode(value = '', fallback = DEFAULT_PANEL_MODE) {
|
||||
function normalizeOpenAiIntegrationTargetId(value = '', fallback = DEFAULT_OPENAI_INTEGRATION_TARGET_ID) {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
if (VALID_PANEL_MODES.includes(normalized)) {
|
||||
if (VALID_OPENAI_INTEGRATION_TARGET_IDS.includes(normalized)) {
|
||||
return normalized;
|
||||
}
|
||||
const fallbackValue = String(fallback || '').trim().toLowerCase();
|
||||
return VALID_PANEL_MODES.includes(fallbackValue) ? fallbackValue : DEFAULT_PANEL_MODE;
|
||||
return VALID_OPENAI_INTEGRATION_TARGET_IDS.includes(fallbackValue)
|
||||
? fallbackValue
|
||||
: DEFAULT_OPENAI_INTEGRATION_TARGET_ID;
|
||||
}
|
||||
|
||||
function normalizeSignupMethod(value = '') {
|
||||
@@ -120,41 +117,50 @@
|
||||
: SIGNUP_METHOD_EMAIL;
|
||||
}
|
||||
|
||||
function normalizePanelModeList(values = []) {
|
||||
function normalizeOpenAiIntegrationTargetList(values = []) {
|
||||
if (!Array.isArray(values)) {
|
||||
return [];
|
||||
}
|
||||
const seen = new Set();
|
||||
const normalized = [];
|
||||
values.forEach((value) => {
|
||||
const mode = normalizePanelMode(value, '');
|
||||
if (!mode || seen.has(mode)) {
|
||||
const integrationTargetId = normalizeOpenAiIntegrationTargetId(value, '');
|
||||
if (!integrationTargetId || seen.has(integrationTargetId)) {
|
||||
return;
|
||||
}
|
||||
seen.add(mode);
|
||||
normalized.push(mode);
|
||||
seen.add(integrationTargetId);
|
||||
normalized.push(integrationTargetId);
|
||||
});
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function getPanelModeLabel(panelMode = '') {
|
||||
const normalized = normalizePanelMode(panelMode);
|
||||
function getIntegrationTargetLabel(flowId = DEFAULT_FLOW_ID, integrationTargetId = '') {
|
||||
if (
|
||||
isRegisteredFlowId(flowId)
|
||||
&& typeof flowRegistryApi.getIntegrationTargetLabel === 'function'
|
||||
) {
|
||||
return flowRegistryApi.getIntegrationTargetLabel(flowId, integrationTargetId);
|
||||
}
|
||||
const normalized = String(integrationTargetId || '').trim().toLowerCase();
|
||||
if (normalized === 'sub2api') {
|
||||
return 'SUB2API';
|
||||
}
|
||||
if (normalized === 'codex2api') {
|
||||
return 'Codex2API';
|
||||
}
|
||||
return 'CPA';
|
||||
if (normalized === 'cpa') {
|
||||
return 'CPA';
|
||||
}
|
||||
return normalized || String(integrationTargetId || '').trim();
|
||||
}
|
||||
|
||||
function createFlowCapabilityRegistry(deps = {}) {
|
||||
const {
|
||||
defaultFlowCapabilities = DEFAULT_FLOW_CAPABILITIES,
|
||||
defaultFlowId = DEFAULT_FLOW_ID,
|
||||
defaultPanelCapabilities = DEFAULT_PANEL_CAPABILITIES,
|
||||
defaultIntegrationTargetCapabilities = DEFAULT_INTEGRATION_TARGET_CAPABILITIES,
|
||||
flowCapabilities = FLOW_CAPABILITIES,
|
||||
panelCapabilities = PANEL_CAPABILITIES,
|
||||
integrationTargetCapabilities = OPENAI_INTEGRATION_TARGET_CAPABILITIES,
|
||||
} = deps;
|
||||
const settingsSchema = settingsSchemaApi.createSettingsSchema
|
||||
? settingsSchemaApi.createSettingsSchema({
|
||||
@@ -165,21 +171,72 @@
|
||||
function getFlowCapabilities(flowId) {
|
||||
const normalizedFlowId = normalizeCapabilityFlowId(flowId, defaultFlowId);
|
||||
const entry = flowCapabilities[normalizedFlowId] || null;
|
||||
const supportedIntegrationTargets = normalizedFlowId === 'openai'
|
||||
? normalizeOpenAiIntegrationTargetList(
|
||||
entry?.supportedIntegrationTargets || defaultFlowCapabilities.supportedIntegrationTargets
|
||||
)
|
||||
: (Array.isArray(entry?.supportedIntegrationTargets)
|
||||
? entry.supportedIntegrationTargets.map((value) => String(value || '').trim().toLowerCase()).filter(Boolean)
|
||||
: []);
|
||||
return {
|
||||
...defaultFlowCapabilities,
|
||||
...(entry || {}),
|
||||
supportsPlatformBinding: normalizePanelModeList(entry?.supportsPlatformBinding || defaultFlowCapabilities.supportsPlatformBinding),
|
||||
supportedIntegrationTargets,
|
||||
};
|
||||
}
|
||||
|
||||
function getPanelCapabilities(panelMode) {
|
||||
const normalizedPanelMode = normalizePanelMode(panelMode);
|
||||
function getOpenAiIntegrationTargetCapabilities(integrationTargetId) {
|
||||
const normalizedIntegrationTargetId = normalizeOpenAiIntegrationTargetId(integrationTargetId);
|
||||
return {
|
||||
...defaultPanelCapabilities,
|
||||
...(panelCapabilities[normalizedPanelMode] || {}),
|
||||
...defaultIntegrationTargetCapabilities,
|
||||
...(integrationTargetCapabilities[normalizedIntegrationTargetId] || {}),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeRequestedIntegrationTargetId(activeFlowId, state = {}, options = {}) {
|
||||
if (activeFlowId === 'openai') {
|
||||
return normalizeOpenAiIntegrationTargetId(
|
||||
options?.integrationTargetId
|
||||
?? options?.panelMode
|
||||
?? state?.openaiIntegrationTargetId
|
||||
?? state?.panelMode,
|
||||
DEFAULT_OPENAI_INTEGRATION_TARGET_ID
|
||||
);
|
||||
}
|
||||
|
||||
const rawIntegrationTargetId = activeFlowId === 'kiro'
|
||||
? (
|
||||
options?.integrationTargetId
|
||||
?? state?.kiroIntegrationTargetId
|
||||
?? state?.kiroSourceId
|
||||
?? flowRegistryApi.getDefaultIntegrationTargetId?.(activeFlowId)
|
||||
?? ''
|
||||
)
|
||||
: (
|
||||
options?.integrationTargetId
|
||||
?? state?.integrationTargetId
|
||||
?? state?.openaiIntegrationTargetId
|
||||
?? state?.panelMode
|
||||
?? state?.kiroIntegrationTargetId
|
||||
?? state?.kiroSourceId
|
||||
?? flowRegistryApi.getDefaultIntegrationTargetId?.(activeFlowId)
|
||||
?? ''
|
||||
);
|
||||
|
||||
if (
|
||||
isRegisteredFlowId(activeFlowId)
|
||||
&& typeof flowRegistryApi.normalizeIntegrationTargetId === 'function'
|
||||
) {
|
||||
return flowRegistryApi.normalizeIntegrationTargetId(
|
||||
activeFlowId,
|
||||
rawIntegrationTargetId,
|
||||
flowRegistryApi.getDefaultIntegrationTargetId?.(activeFlowId)
|
||||
);
|
||||
}
|
||||
|
||||
return String(rawIntegrationTargetId || '').trim().toLowerCase();
|
||||
}
|
||||
|
||||
function normalizeChangedKeys(values = []) {
|
||||
const list = Array.isArray(values) ? values : [];
|
||||
const seen = new Set();
|
||||
@@ -195,28 +252,33 @@
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function resolveEffectiveSourceId(activeFlowId, state = {}, requestedPanelMode = DEFAULT_PANEL_MODE) {
|
||||
function resolveEffectiveIntegrationTargetId(activeFlowId, state = {}, requestedIntegrationTargetId = DEFAULT_OPENAI_INTEGRATION_TARGET_ID) {
|
||||
if (!isRegisteredFlowId(activeFlowId)) {
|
||||
return normalizePanelMode(state?.panelMode || requestedPanelMode, requestedPanelMode || DEFAULT_PANEL_MODE);
|
||||
return normalizeRequestedIntegrationTargetId(activeFlowId, state, {
|
||||
integrationTargetId: requestedIntegrationTargetId,
|
||||
});
|
||||
}
|
||||
if (settingsSchema?.getSelectedSourceId) {
|
||||
const sourceFromSchema = settingsSchema.getSelectedSourceId({
|
||||
if (settingsSchema?.getSelectedIntegrationTargetId) {
|
||||
const integrationTargetId = settingsSchema.getSelectedIntegrationTargetId({
|
||||
...state,
|
||||
activeFlowId,
|
||||
}, activeFlowId);
|
||||
if (sourceFromSchema) {
|
||||
return sourceFromSchema;
|
||||
if (integrationTargetId) {
|
||||
return integrationTargetId;
|
||||
}
|
||||
}
|
||||
if (typeof flowRegistryApi.normalizeSourceId === 'function') {
|
||||
if (activeFlowId === 'openai') {
|
||||
return flowRegistryApi.normalizeSourceId('openai', state?.panelMode || requestedPanelMode, DEFAULT_PANEL_MODE);
|
||||
}
|
||||
return flowRegistryApi.normalizeSourceId(activeFlowId, state?.kiroSourceId || '', flowRegistryApi.getDefaultSourceId?.(activeFlowId));
|
||||
if (typeof flowRegistryApi.normalizeIntegrationTargetId === 'function') {
|
||||
return flowRegistryApi.normalizeIntegrationTargetId(
|
||||
activeFlowId,
|
||||
activeFlowId === 'openai'
|
||||
? (state?.openaiIntegrationTargetId || state?.panelMode || requestedIntegrationTargetId)
|
||||
: (state?.kiroIntegrationTargetId || state?.kiroSourceId || requestedIntegrationTargetId),
|
||||
flowRegistryApi.getDefaultIntegrationTargetId?.(activeFlowId)
|
||||
);
|
||||
}
|
||||
return activeFlowId === 'openai'
|
||||
? normalizePanelMode(state?.panelMode || requestedPanelMode)
|
||||
: '';
|
||||
? normalizeOpenAiIntegrationTargetId(requestedIntegrationTargetId)
|
||||
: String(requestedIntegrationTargetId || '').trim().toLowerCase();
|
||||
}
|
||||
|
||||
function resolveSidepanelCapabilities(options = {}) {
|
||||
@@ -226,19 +288,25 @@
|
||||
defaultFlowId
|
||||
);
|
||||
const flowState = getFlowCapabilities(activeFlowId);
|
||||
const requestedPanelMode = normalizePanelMode(
|
||||
options?.panelMode ?? state?.panelMode,
|
||||
DEFAULT_PANEL_MODE
|
||||
const requestedIntegrationTargetId = normalizeRequestedIntegrationTargetId(
|
||||
activeFlowId,
|
||||
state,
|
||||
options
|
||||
);
|
||||
const supportedPanelModes = normalizePanelModeList(flowState.supportsPlatformBinding);
|
||||
const panelModeSupported = supportedPanelModes.length === 0
|
||||
const supportedIntegrationTargets = activeFlowId === 'openai'
|
||||
? normalizeOpenAiIntegrationTargetList(flowState.supportedIntegrationTargets)
|
||||
: (Array.isArray(flowState.supportedIntegrationTargets)
|
||||
? flowState.supportedIntegrationTargets.slice()
|
||||
: []);
|
||||
const integrationTargetSupported = supportedIntegrationTargets.length === 0
|
||||
? true
|
||||
: supportedPanelModes.includes(requestedPanelMode);
|
||||
const effectivePanelMode = panelModeSupported
|
||||
? requestedPanelMode
|
||||
: (supportedPanelModes[0] || requestedPanelMode);
|
||||
const panelState = getPanelCapabilities(effectivePanelMode);
|
||||
const effectiveSourceId = resolveEffectiveSourceId(activeFlowId, state, effectivePanelMode);
|
||||
: supportedIntegrationTargets.includes(requestedIntegrationTargetId);
|
||||
const effectiveIntegrationTargetId = integrationTargetSupported
|
||||
? requestedIntegrationTargetId
|
||||
: (supportedIntegrationTargets[0] || requestedIntegrationTargetId);
|
||||
const integrationTargetState = activeFlowId === 'openai'
|
||||
? getOpenAiIntegrationTargetCapabilities(effectiveIntegrationTargetId)
|
||||
: defaultIntegrationTargetCapabilities;
|
||||
const runtimeLocks = {
|
||||
autoRunLocked: Boolean(options?.autoRunLocked ?? state?.autoRunLocked),
|
||||
contributionMode: activeFlowId === 'openai' && flowState.supportsContributionMode && Boolean(state?.contributionMode),
|
||||
@@ -252,7 +320,7 @@
|
||||
}
|
||||
const canSelectPhoneSignup = activeFlowId === 'openai'
|
||||
&& Boolean(flowState.supportsPhoneSignup)
|
||||
&& Boolean(panelState.supportsPhoneSignup)
|
||||
&& Boolean(integrationTargetState.supportsPhoneSignup)
|
||||
&& runtimeLocks.phoneVerificationEnabled
|
||||
&& !runtimeLocks.plusModeEnabled
|
||||
&& !runtimeLocks.contributionMode;
|
||||
@@ -272,7 +340,7 @@
|
||||
: effectiveSignupMethods[0]);
|
||||
const visibleGroupIds = typeof flowRegistryApi.getVisibleGroupIds === 'function'
|
||||
&& isRegisteredFlowId(activeFlowId)
|
||||
? flowRegistryApi.getVisibleGroupIds(activeFlowId, effectiveSourceId)
|
||||
? flowRegistryApi.getVisibleGroupIds(activeFlowId, effectiveIntegrationTargetId)
|
||||
: [];
|
||||
|
||||
return {
|
||||
@@ -283,33 +351,38 @@
|
||||
canShowPlusSettings: activeFlowId === 'openai' && Boolean(flowState.supportsPlusMode),
|
||||
canSwitchFlow: Boolean(flowState.canSwitchFlow),
|
||||
canUsePhoneSignup: canSelectPhoneSignup,
|
||||
canUseSelectedPanelMode: panelModeSupported,
|
||||
effectivePanelMode,
|
||||
canUseSelectedPanelMode: integrationTargetSupported,
|
||||
effectiveIntegrationTargetId,
|
||||
effectivePanelMode: effectiveIntegrationTargetId,
|
||||
effectiveSignupMethod,
|
||||
effectiveSignupMethods,
|
||||
effectiveSourceId,
|
||||
effectiveSourceId: effectiveIntegrationTargetId,
|
||||
flowCapabilities: flowState,
|
||||
panelCapabilities: panelState,
|
||||
panelMode: effectivePanelMode,
|
||||
requestedPanelMode,
|
||||
integrationTargetCapabilities: integrationTargetState,
|
||||
panelCapabilities: integrationTargetState,
|
||||
panelMode: effectiveIntegrationTargetId,
|
||||
requestedIntegrationTargetId,
|
||||
requestedPanelMode: requestedIntegrationTargetId,
|
||||
requestedSignupMethod,
|
||||
runtimeLocks,
|
||||
shouldWarnCpaPhoneSignup: effectiveSignupMethod === SIGNUP_METHOD_PHONE
|
||||
&& Boolean(panelState.requiresPhoneSignupWarning),
|
||||
&& Boolean(integrationTargetState.requiresPhoneSignupWarning),
|
||||
stepDefinitionOptions: {
|
||||
activeFlowId,
|
||||
panelMode: effectivePanelMode,
|
||||
integrationTargetId: effectiveIntegrationTargetId,
|
||||
panelMode: effectiveIntegrationTargetId,
|
||||
plusModeEnabled: runtimeLocks.plusModeEnabled,
|
||||
signupMethod: effectiveSignupMethod,
|
||||
},
|
||||
supportedPanelModes,
|
||||
supportedIntegrationTargets,
|
||||
supportedPanelModes: supportedIntegrationTargets,
|
||||
visibleGroupIds,
|
||||
};
|
||||
}
|
||||
|
||||
function buildPhoneSignupValidationError(capabilityState = {}) {
|
||||
const flowState = capabilityState.flowCapabilities || {};
|
||||
const panelState = capabilityState.panelCapabilities || {};
|
||||
const integrationTargetState = capabilityState.integrationTargetCapabilities || {};
|
||||
const runtimeLocks = capabilityState.runtimeLocks || {};
|
||||
|
||||
if (!flowState.supportsPhoneSignup) {
|
||||
@@ -318,10 +391,10 @@
|
||||
message: '当前 flow 不支持手机号注册。',
|
||||
};
|
||||
}
|
||||
if (!panelState.supportsPhoneSignup) {
|
||||
if (!integrationTargetState.supportsPhoneSignup) {
|
||||
return {
|
||||
code: 'phone_signup_panel_unsupported',
|
||||
message: `当前来源 ${getPanelModeLabel(capabilityState.requestedPanelMode)} 不支持手机号注册。`,
|
||||
message: `当前来源 ${getIntegrationTargetLabel(capabilityState.activeFlowId, capabilityState.requestedIntegrationTargetId)} 不支持手机号注册。`,
|
||||
};
|
||||
}
|
||||
if (!runtimeLocks.phoneVerificationEnabled) {
|
||||
@@ -354,13 +427,13 @@
|
||||
const errors = [];
|
||||
|
||||
if (
|
||||
Array.isArray(capabilityState.supportedPanelModes)
|
||||
&& capabilityState.supportedPanelModes.length > 0
|
||||
Array.isArray(capabilityState.supportedIntegrationTargets)
|
||||
&& capabilityState.supportedIntegrationTargets.length > 0
|
||||
&& capabilityState.canUseSelectedPanelMode === false
|
||||
) {
|
||||
errors.push({
|
||||
code: 'panel_mode_unsupported',
|
||||
message: `当前 flow 不支持 ${getPanelModeLabel(capabilityState.requestedPanelMode)} 来源。`,
|
||||
message: `当前 flow 不支持 ${getIntegrationTargetLabel(capabilityState.activeFlowId, capabilityState.requestedIntegrationTargetId)} 来源。`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -408,15 +481,18 @@
|
||||
const shouldReconcileSignupMethod = MODE_SWITCH_RELEVANT_KEYS.some((key) => changedKeySet.has(key));
|
||||
|
||||
if (
|
||||
changedKeySet.has('panelMode')
|
||||
&& Array.isArray(capabilityState.supportedPanelModes)
|
||||
&& capabilityState.supportedPanelModes.length > 0
|
||||
(changedKeySet.has('panelMode') || changedKeySet.has('openaiIntegrationTargetId') || changedKeySet.has('kiroIntegrationTargetId'))
|
||||
&& Array.isArray(capabilityState.supportedIntegrationTargets)
|
||||
&& capabilityState.supportedIntegrationTargets.length > 0
|
||||
&& capabilityState.canUseSelectedPanelMode === false
|
||||
) {
|
||||
normalizedUpdates.panelMode = capabilityState.effectivePanelMode;
|
||||
normalizedUpdates.panelMode = capabilityState.effectiveIntegrationTargetId;
|
||||
normalizedUpdates.openaiIntegrationTargetId = capabilityState.effectiveIntegrationTargetId;
|
||||
normalizedUpdates.kiroIntegrationTargetId = capabilityState.effectiveIntegrationTargetId;
|
||||
normalizedUpdates.kiroSourceId = capabilityState.effectiveIntegrationTargetId;
|
||||
errors.push({
|
||||
code: 'panel_mode_unsupported',
|
||||
message: `当前 flow 不支持 ${getPanelModeLabel(capabilityState.requestedPanelMode)} 来源。`,
|
||||
message: `当前 flow 不支持 ${getIntegrationTargetLabel(capabilityState.activeFlowId, capabilityState.requestedIntegrationTargetId)} 来源。`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -480,9 +556,9 @@
|
||||
return {
|
||||
canUsePhoneSignup,
|
||||
getFlowCapabilities,
|
||||
getPanelCapabilities,
|
||||
getOpenAiIntegrationTargetCapabilities,
|
||||
normalizeFlowId,
|
||||
normalizePanelMode,
|
||||
normalizeOpenAiIntegrationTargetId,
|
||||
normalizeSignupMethod,
|
||||
resolveSidepanelCapabilities,
|
||||
resolveSignupMethod,
|
||||
@@ -495,15 +571,15 @@
|
||||
createFlowCapabilityRegistry,
|
||||
DEFAULT_FLOW_CAPABILITIES,
|
||||
DEFAULT_FLOW_ID,
|
||||
DEFAULT_PANEL_CAPABILITIES,
|
||||
DEFAULT_PANEL_MODE,
|
||||
DEFAULT_INTEGRATION_TARGET_CAPABILITIES,
|
||||
DEFAULT_OPENAI_INTEGRATION_TARGET_ID,
|
||||
FLOW_CAPABILITIES,
|
||||
PANEL_CAPABILITIES,
|
||||
OPENAI_INTEGRATION_TARGET_CAPABILITIES,
|
||||
SIGNUP_METHOD_EMAIL,
|
||||
SIGNUP_METHOD_PHONE,
|
||||
VALID_PANEL_MODES,
|
||||
VALID_OPENAI_INTEGRATION_TARGET_IDS,
|
||||
normalizeFlowId,
|
||||
normalizePanelMode,
|
||||
normalizeOpenAiIntegrationTargetId,
|
||||
normalizeSignupMethod,
|
||||
};
|
||||
});
|
||||
|
||||
+105
-81
@@ -2,11 +2,11 @@
|
||||
root.MultiPageFlowRegistry = factory();
|
||||
})(typeof self !== 'undefined' ? self : globalThis, function createFlowRegistryModule() {
|
||||
const DEFAULT_FLOW_ID = 'openai';
|
||||
const LEGACY_OPENAI_FLOW_ALIAS = 'codex';
|
||||
const DEFAULT_OPENAI_SOURCE_ID = 'cpa';
|
||||
const DEFAULT_KIRO_SOURCE_ID = 'kiro-rs';
|
||||
const DEFAULT_OPENAI_INTEGRATION_TARGET_ID = 'cpa';
|
||||
const DEFAULT_KIRO_INTEGRATION_TARGET_ID = 'kiro-rs';
|
||||
const DEFAULT_KIRO_PUBLICATION_TARGET_ID = 'kiro-rs';
|
||||
const DEFAULT_KIRO_RS_URL = 'https://kiro.leftcode.xyz/admin';
|
||||
const OPENAI_SOURCE_IDS = Object.freeze(['cpa', 'sub2api', 'codex2api']);
|
||||
const OPENAI_INTEGRATION_TARGET_IDS = Object.freeze(['cpa', 'sub2api', 'codex2api']);
|
||||
const SHARED_SERVICE_IDS = Object.freeze(['account', 'email', 'proxy']);
|
||||
|
||||
const DEFAULT_FLOW_CAPABILITIES = Object.freeze({
|
||||
@@ -15,7 +15,7 @@
|
||||
supportsPhoneVerificationSettings: false,
|
||||
supportsPlusMode: false,
|
||||
supportsContributionMode: false,
|
||||
supportsPlatformBinding: [],
|
||||
supportedIntegrationTargets: [],
|
||||
supportsLuckmail: false,
|
||||
supportsOauthTimeoutBudget: false,
|
||||
canSwitchFlow: true,
|
||||
@@ -44,7 +44,7 @@
|
||||
supportsPhoneVerificationSettings: true,
|
||||
supportsPlusMode: true,
|
||||
supportsContributionMode: true,
|
||||
supportsPlatformBinding: [...OPENAI_SOURCE_IDS],
|
||||
supportedIntegrationTargets: [...OPENAI_INTEGRATION_TARGET_IDS],
|
||||
supportsLuckmail: true,
|
||||
supportsOauthTimeoutBudget: true,
|
||||
stepDefinitionMode: 'openai-dynamic',
|
||||
@@ -55,24 +55,21 @@
|
||||
'openai-oauth',
|
||||
'openai-step6',
|
||||
],
|
||||
sources: {
|
||||
integrationTargets: {
|
||||
cpa: {
|
||||
id: 'cpa',
|
||||
label: 'CPA 面板',
|
||||
legacyPanelMode: 'cpa',
|
||||
groups: ['openai-source-cpa'],
|
||||
groups: ['openai-target-cpa'],
|
||||
},
|
||||
sub2api: {
|
||||
id: 'sub2api',
|
||||
label: 'SUB2API',
|
||||
legacyPanelMode: 'sub2api',
|
||||
groups: ['openai-source-sub2api'],
|
||||
groups: ['openai-target-sub2api'],
|
||||
},
|
||||
codex2api: {
|
||||
id: 'codex2api',
|
||||
label: 'Codex2API',
|
||||
legacyPanelMode: 'codex2api',
|
||||
groups: ['openai-source-codex2api'],
|
||||
groups: ['openai-target-codex2api'],
|
||||
},
|
||||
},
|
||||
runtimeSources: {
|
||||
@@ -206,16 +203,23 @@
|
||||
services: ['account', 'email', 'proxy'],
|
||||
capabilities: {
|
||||
...DEFAULT_FLOW_CAPABILITIES,
|
||||
supportedIntegrationTargets: [DEFAULT_KIRO_INTEGRATION_TARGET_ID],
|
||||
stepDefinitionMode: 'kiro-device-auth',
|
||||
},
|
||||
baseGroups: [
|
||||
'kiro-runtime-status',
|
||||
],
|
||||
sources: {
|
||||
integrationTargets: {
|
||||
'kiro-rs': {
|
||||
id: 'kiro-rs',
|
||||
label: 'kiro.rs',
|
||||
groups: ['kiro-target-kiro-rs'],
|
||||
},
|
||||
},
|
||||
publicationTargets: {
|
||||
'kiro-rs': {
|
||||
id: 'kiro-rs',
|
||||
label: 'kiro.rs',
|
||||
groups: ['kiro-source-kiro-rs'],
|
||||
},
|
||||
},
|
||||
runtimeSources: {
|
||||
@@ -280,13 +284,13 @@
|
||||
label: 'IP 代理',
|
||||
sectionIds: ['ip-proxy-section'],
|
||||
},
|
||||
'openai-source-cpa': {
|
||||
id: 'openai-source-cpa',
|
||||
'openai-target-cpa': {
|
||||
id: 'openai-target-cpa',
|
||||
label: 'CPA 来源',
|
||||
rowIds: ['row-vps-url', 'row-vps-password', 'row-local-cpa-step9-mode'],
|
||||
},
|
||||
'openai-source-sub2api': {
|
||||
id: 'openai-source-sub2api',
|
||||
'openai-target-sub2api': {
|
||||
id: 'openai-target-sub2api',
|
||||
label: 'SUB2API 来源',
|
||||
rowIds: [
|
||||
'row-sub2api-url',
|
||||
@@ -297,17 +301,15 @@
|
||||
'row-sub2api-default-proxy',
|
||||
],
|
||||
},
|
||||
'openai-source-codex2api': {
|
||||
id: 'openai-source-codex2api',
|
||||
'openai-target-codex2api': {
|
||||
id: 'openai-target-codex2api',
|
||||
label: 'Codex2API 来源',
|
||||
rowIds: ['row-codex2api-url', 'row-codex2api-admin-key'],
|
||||
},
|
||||
'openai-plus': {
|
||||
id: 'openai-plus',
|
||||
label: 'Plus',
|
||||
rowIds: [
|
||||
'row-plus-mode',
|
||||
],
|
||||
rowIds: ['row-plus-mode'],
|
||||
},
|
||||
'openai-phone': {
|
||||
id: 'openai-phone',
|
||||
@@ -325,8 +327,8 @@
|
||||
label: '第六步',
|
||||
rowIds: ['row-step6-cookie-settings'],
|
||||
},
|
||||
'kiro-source-kiro-rs': {
|
||||
id: 'kiro-source-kiro-rs',
|
||||
'kiro-target-kiro-rs': {
|
||||
id: 'kiro-target-kiro-rs',
|
||||
label: 'kiro.rs 配置',
|
||||
rowIds: ['row-kiro-rs-url', 'row-kiro-rs-key'],
|
||||
},
|
||||
@@ -339,16 +341,10 @@
|
||||
|
||||
function normalizeFlowId(value = '', fallback = DEFAULT_FLOW_ID) {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
if (normalized === LEGACY_OPENAI_FLOW_ALIAS) {
|
||||
return DEFAULT_FLOW_ID;
|
||||
}
|
||||
if (normalized && Object.prototype.hasOwnProperty.call(FLOW_DEFINITIONS, normalized)) {
|
||||
return normalized;
|
||||
}
|
||||
const fallbackValue = String(fallback || '').trim().toLowerCase();
|
||||
if (fallbackValue === LEGACY_OPENAI_FLOW_ALIAS) {
|
||||
return DEFAULT_FLOW_ID;
|
||||
}
|
||||
return Object.prototype.hasOwnProperty.call(FLOW_DEFINITIONS, fallbackValue)
|
||||
? fallbackValue
|
||||
: DEFAULT_FLOW_ID;
|
||||
@@ -359,73 +355,92 @@
|
||||
}
|
||||
|
||||
function getFlowDefinition(flowId) {
|
||||
const normalizedFlowId = normalizeFlowId(flowId);
|
||||
return FLOW_DEFINITIONS[normalizedFlowId] || FLOW_DEFINITIONS[DEFAULT_FLOW_ID];
|
||||
return FLOW_DEFINITIONS[normalizeFlowId(flowId)] || FLOW_DEFINITIONS[DEFAULT_FLOW_ID];
|
||||
}
|
||||
|
||||
function getFlowLabel(flowId) {
|
||||
return getFlowDefinition(flowId)?.label || normalizeFlowId(flowId);
|
||||
}
|
||||
|
||||
function getDefaultSourceId(flowId) {
|
||||
function getDefaultIntegrationTargetId(flowId) {
|
||||
return normalizeFlowId(flowId) === 'kiro'
|
||||
? DEFAULT_KIRO_SOURCE_ID
|
||||
: DEFAULT_OPENAI_SOURCE_ID;
|
||||
? DEFAULT_KIRO_INTEGRATION_TARGET_ID
|
||||
: DEFAULT_OPENAI_INTEGRATION_TARGET_ID;
|
||||
}
|
||||
|
||||
function normalizeOpenAiSourceId(value = '', fallback = DEFAULT_OPENAI_SOURCE_ID) {
|
||||
function normalizeOpenAiIntegrationTargetId(value = '', fallback = DEFAULT_OPENAI_INTEGRATION_TARGET_ID) {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
if (OPENAI_SOURCE_IDS.includes(normalized)) {
|
||||
if (OPENAI_INTEGRATION_TARGET_IDS.includes(normalized)) {
|
||||
return normalized;
|
||||
}
|
||||
const fallbackValue = String(fallback || '').trim().toLowerCase();
|
||||
return OPENAI_SOURCE_IDS.includes(fallbackValue) ? fallbackValue : DEFAULT_OPENAI_SOURCE_ID;
|
||||
return OPENAI_INTEGRATION_TARGET_IDS.includes(fallbackValue)
|
||||
? fallbackValue
|
||||
: DEFAULT_OPENAI_INTEGRATION_TARGET_ID;
|
||||
}
|
||||
|
||||
function normalizeKiroSourceId(value = '', fallback = DEFAULT_KIRO_SOURCE_ID) {
|
||||
function normalizeKiroIntegrationTargetId(value = '', fallback = DEFAULT_KIRO_INTEGRATION_TARGET_ID) {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
if (normalized === DEFAULT_KIRO_SOURCE_ID) {
|
||||
if (normalized === DEFAULT_KIRO_INTEGRATION_TARGET_ID) {
|
||||
return normalized;
|
||||
}
|
||||
const fallbackValue = String(fallback || '').trim().toLowerCase();
|
||||
return fallbackValue === DEFAULT_KIRO_SOURCE_ID ? fallbackValue : DEFAULT_KIRO_SOURCE_ID;
|
||||
return fallbackValue === DEFAULT_KIRO_INTEGRATION_TARGET_ID
|
||||
? fallbackValue
|
||||
: DEFAULT_KIRO_INTEGRATION_TARGET_ID;
|
||||
}
|
||||
|
||||
function normalizeSourceId(flowId, sourceId = '', fallback = undefined) {
|
||||
function normalizeIntegrationTargetId(flowId, integrationTargetId = '', fallback = undefined) {
|
||||
const normalizedFlowId = normalizeFlowId(flowId);
|
||||
if (normalizedFlowId === 'kiro') {
|
||||
return normalizeKiroSourceId(sourceId, fallback || DEFAULT_KIRO_SOURCE_ID);
|
||||
return normalizeKiroIntegrationTargetId(
|
||||
integrationTargetId,
|
||||
fallback || DEFAULT_KIRO_INTEGRATION_TARGET_ID
|
||||
);
|
||||
}
|
||||
return normalizeOpenAiSourceId(sourceId, fallback || DEFAULT_OPENAI_SOURCE_ID);
|
||||
return normalizeOpenAiIntegrationTargetId(
|
||||
integrationTargetId,
|
||||
fallback || DEFAULT_OPENAI_INTEGRATION_TARGET_ID
|
||||
);
|
||||
}
|
||||
|
||||
function getSourceDefinitions(flowId) {
|
||||
return getFlowDefinition(flowId)?.sources || {};
|
||||
function getIntegrationTargetDefinitions(flowId) {
|
||||
return getFlowDefinition(flowId)?.integrationTargets || {};
|
||||
}
|
||||
|
||||
function getSourceDefinition(flowId, sourceId) {
|
||||
function getIntegrationTargetDefinition(flowId, integrationTargetId) {
|
||||
const normalizedFlowId = normalizeFlowId(flowId);
|
||||
const normalizedSourceId = normalizeSourceId(normalizedFlowId, sourceId, getDefaultSourceId(normalizedFlowId));
|
||||
return getSourceDefinitions(normalizedFlowId)[normalizedSourceId] || null;
|
||||
const normalizedIntegrationTargetId = normalizeIntegrationTargetId(
|
||||
normalizedFlowId,
|
||||
integrationTargetId,
|
||||
getDefaultIntegrationTargetId(normalizedFlowId)
|
||||
);
|
||||
return getIntegrationTargetDefinitions(normalizedFlowId)[normalizedIntegrationTargetId] || null;
|
||||
}
|
||||
|
||||
function getSourceOptions(flowId) {
|
||||
return Object.values(getSourceDefinitions(flowId));
|
||||
function getIntegrationTargetOptions(flowId) {
|
||||
return Object.values(getIntegrationTargetDefinitions(flowId));
|
||||
}
|
||||
|
||||
function getSourceLabel(flowId, sourceId) {
|
||||
return getSourceDefinition(flowId, sourceId)?.label || normalizeSourceId(flowId, sourceId);
|
||||
function getIntegrationTargetLabel(flowId, integrationTargetId) {
|
||||
return getIntegrationTargetDefinition(flowId, integrationTargetId)?.label
|
||||
|| normalizeIntegrationTargetId(flowId, integrationTargetId);
|
||||
}
|
||||
|
||||
function mapPanelModeToSourceId(panelMode = '', fallback = DEFAULT_OPENAI_SOURCE_ID) {
|
||||
return normalizeOpenAiSourceId(panelMode, fallback);
|
||||
function getPublicationTargetDefinitions(flowId) {
|
||||
return getFlowDefinition(flowId)?.publicationTargets || {};
|
||||
}
|
||||
|
||||
function mapSourceIdToPanelMode(flowId, sourceId = '', fallback = DEFAULT_OPENAI_SOURCE_ID) {
|
||||
if (normalizeFlowId(flowId) !== DEFAULT_FLOW_ID) {
|
||||
return normalizeOpenAiSourceId(fallback, DEFAULT_OPENAI_SOURCE_ID);
|
||||
}
|
||||
return normalizeOpenAiSourceId(sourceId, fallback || DEFAULT_OPENAI_SOURCE_ID);
|
||||
function getPublicationTargetDefinition(flowId, publicationTargetId) {
|
||||
const normalizedFlowId = normalizeFlowId(flowId);
|
||||
const normalizedPublicationTargetId = String(
|
||||
publicationTargetId || (
|
||||
normalizedFlowId === 'kiro'
|
||||
? DEFAULT_KIRO_PUBLICATION_TARGET_ID
|
||||
: ''
|
||||
)
|
||||
).trim().toLowerCase();
|
||||
return getPublicationTargetDefinitions(normalizedFlowId)[normalizedPublicationTargetId] || null;
|
||||
}
|
||||
|
||||
function getFlowCapabilities(flowId) {
|
||||
@@ -435,18 +450,27 @@
|
||||
};
|
||||
}
|
||||
|
||||
function getVisibleGroupIds(flowId, sourceId, options = {}) {
|
||||
function getVisibleGroupIds(flowId, integrationTargetId, options = {}) {
|
||||
const normalizedFlowId = normalizeFlowId(flowId);
|
||||
const flowDefinition = getFlowDefinition(normalizedFlowId);
|
||||
const normalizedSourceId = normalizeSourceId(normalizedFlowId, sourceId, getDefaultSourceId(normalizedFlowId));
|
||||
const sourceDefinition = getSourceDefinition(normalizedFlowId, normalizedSourceId);
|
||||
const normalizedIntegrationTargetId = normalizeIntegrationTargetId(
|
||||
normalizedFlowId,
|
||||
integrationTargetId,
|
||||
getDefaultIntegrationTargetId(normalizedFlowId)
|
||||
);
|
||||
const integrationTargetDefinition = getIntegrationTargetDefinition(
|
||||
normalizedFlowId,
|
||||
normalizedIntegrationTargetId
|
||||
);
|
||||
const includeSharedServices = options?.includeSharedServices !== false;
|
||||
const serviceGroups = includeSharedServices
|
||||
? (Array.isArray(flowDefinition?.services) ? flowDefinition.services.map((serviceId) => `service-${serviceId}`) : [])
|
||||
? (Array.isArray(flowDefinition?.services)
|
||||
? flowDefinition.services.map((serviceId) => `service-${serviceId}`)
|
||||
: [])
|
||||
: [];
|
||||
return Array.from(new Set([
|
||||
...(Array.isArray(flowDefinition?.baseGroups) ? flowDefinition.baseGroups : []),
|
||||
...(Array.isArray(sourceDefinition?.groups) ? sourceDefinition.groups : []),
|
||||
...(Array.isArray(integrationTargetDefinition?.groups) ? integrationTargetDefinition.groups : []),
|
||||
...serviceGroups,
|
||||
]));
|
||||
}
|
||||
@@ -479,33 +503,33 @@
|
||||
return {
|
||||
DEFAULT_FLOW_CAPABILITIES,
|
||||
DEFAULT_FLOW_ID,
|
||||
DEFAULT_KIRO_INTEGRATION_TARGET_ID,
|
||||
DEFAULT_KIRO_PUBLICATION_TARGET_ID,
|
||||
DEFAULT_KIRO_RS_URL,
|
||||
DEFAULT_KIRO_SOURCE_ID,
|
||||
DEFAULT_OPENAI_SOURCE_ID,
|
||||
DEFAULT_OPENAI_INTEGRATION_TARGET_ID,
|
||||
FLOW_DEFINITIONS,
|
||||
LEGACY_OPENAI_FLOW_ALIAS,
|
||||
OPENAI_SOURCE_IDS,
|
||||
OPENAI_INTEGRATION_TARGET_IDS,
|
||||
SETTINGS_GROUP_DEFINITIONS,
|
||||
SHARED_SERVICE_IDS,
|
||||
getDefaultSourceId,
|
||||
getDefaultIntegrationTargetId,
|
||||
getDriverDefinitions,
|
||||
getFlowCapabilities,
|
||||
getFlowDefinition,
|
||||
getFlowLabel,
|
||||
getIntegrationTargetDefinition,
|
||||
getIntegrationTargetDefinitions,
|
||||
getIntegrationTargetLabel,
|
||||
getIntegrationTargetOptions,
|
||||
getPublicationTargetDefinition,
|
||||
getPublicationTargetDefinitions,
|
||||
getRegisteredFlowIds,
|
||||
getRuntimeSourceDefinitions,
|
||||
getSettingsGroupDefinition,
|
||||
getSettingsGroupDefinitions,
|
||||
getSourceDefinition,
|
||||
getSourceDefinitions,
|
||||
getSourceLabel,
|
||||
getSourceOptions,
|
||||
getVisibleGroupIds,
|
||||
mapPanelModeToSourceId,
|
||||
mapSourceIdToPanelMode,
|
||||
normalizeFlowId,
|
||||
normalizeKiroSourceId,
|
||||
normalizeOpenAiSourceId,
|
||||
normalizeSourceId,
|
||||
normalizeIntegrationTargetId,
|
||||
normalizeKiroIntegrationTargetId,
|
||||
normalizeOpenAiIntegrationTargetId,
|
||||
};
|
||||
});
|
||||
|
||||
+233
-188
@@ -32,9 +32,11 @@
|
||||
function createSettingsSchema(deps = {}) {
|
||||
const rootScope = typeof self !== 'undefined' ? self : globalThis;
|
||||
const flowRegistry = deps.flowRegistry || rootScope.MultiPageFlowRegistry || {};
|
||||
const defaultFlowId = String(deps.defaultFlowId || flowRegistry.DEFAULT_FLOW_ID || 'openai').trim().toLowerCase() || 'openai';
|
||||
const defaultOpenAiSourceId = flowRegistry.DEFAULT_OPENAI_SOURCE_ID || 'cpa';
|
||||
const defaultKiroSourceId = flowRegistry.DEFAULT_KIRO_SOURCE_ID || 'kiro-rs';
|
||||
const defaultFlowId = String(
|
||||
deps.defaultFlowId || flowRegistry.DEFAULT_FLOW_ID || 'openai'
|
||||
).trim().toLowerCase() || 'openai';
|
||||
const defaultOpenAiIntegrationTargetId = flowRegistry.DEFAULT_OPENAI_INTEGRATION_TARGET_ID || 'cpa';
|
||||
const defaultKiroIntegrationTargetId = flowRegistry.DEFAULT_KIRO_INTEGRATION_TARGET_ID || 'kiro-rs';
|
||||
const defaultKiroRsUrl = flowRegistry.DEFAULT_KIRO_RS_URL || 'https://kiro.leftcode.xyz/admin';
|
||||
const normalizeFlowId = typeof flowRegistry.normalizeFlowId === 'function'
|
||||
? flowRegistry.normalizeFlowId
|
||||
@@ -42,19 +44,13 @@
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
return normalized || String(fallback || '').trim().toLowerCase() || defaultFlowId;
|
||||
});
|
||||
const normalizeSourceId = typeof flowRegistry.normalizeSourceId === 'function'
|
||||
? flowRegistry.normalizeSourceId
|
||||
: ((flowId, value = '', fallback = '') => String(value || fallback || '').trim().toLowerCase());
|
||||
const mapSourceIdToPanelMode = typeof flowRegistry.mapSourceIdToPanelMode === 'function'
|
||||
? flowRegistry.mapSourceIdToPanelMode
|
||||
: ((_flowId, sourceId = '', fallback = defaultOpenAiSourceId) => String(sourceId || fallback || defaultOpenAiSourceId).trim().toLowerCase());
|
||||
const mapPanelModeToSourceId = typeof flowRegistry.mapPanelModeToSourceId === 'function'
|
||||
? flowRegistry.mapPanelModeToSourceId
|
||||
: ((panelMode = '', fallback = defaultOpenAiSourceId) => String(panelMode || fallback || defaultOpenAiSourceId).trim().toLowerCase());
|
||||
const normalizeIntegrationTargetId = typeof flowRegistry.normalizeIntegrationTargetId === 'function'
|
||||
? flowRegistry.normalizeIntegrationTargetId
|
||||
: ((_flowId, value = '', fallback = '') => String(value || fallback || '').trim().toLowerCase());
|
||||
|
||||
function buildDefaultSettingsState() {
|
||||
return {
|
||||
schemaVersion: 3,
|
||||
schemaVersion: 4,
|
||||
activeFlowId: defaultFlowId,
|
||||
services: {
|
||||
account: {
|
||||
@@ -71,27 +67,25 @@
|
||||
},
|
||||
flows: {
|
||||
openai: {
|
||||
source: {
|
||||
selected: defaultOpenAiSourceId,
|
||||
entries: {
|
||||
cpa: {
|
||||
vpsUrl: '',
|
||||
vpsPassword: '',
|
||||
localCpaStep9Mode: 'submit',
|
||||
},
|
||||
sub2api: {
|
||||
sub2apiUrl: '',
|
||||
sub2apiEmail: '',
|
||||
sub2apiPassword: '',
|
||||
sub2apiGroupName: 'codex',
|
||||
sub2apiGroupNames: ['codex', 'openai-plus'],
|
||||
sub2apiAccountPriority: 1,
|
||||
sub2apiDefaultProxyName: '',
|
||||
},
|
||||
codex2api: {
|
||||
codex2apiUrl: '',
|
||||
codex2apiAdminKey: '',
|
||||
},
|
||||
integrationTargetId: defaultOpenAiIntegrationTargetId,
|
||||
integrationTargets: {
|
||||
cpa: {
|
||||
vpsUrl: '',
|
||||
vpsPassword: '',
|
||||
localCpaStep9Mode: 'submit',
|
||||
},
|
||||
sub2api: {
|
||||
sub2apiUrl: '',
|
||||
sub2apiEmail: '',
|
||||
sub2apiPassword: '',
|
||||
sub2apiGroupName: 'codex',
|
||||
sub2apiGroupNames: ['codex', 'openai-plus'],
|
||||
sub2apiAccountPriority: 1,
|
||||
sub2apiDefaultProxyName: '',
|
||||
},
|
||||
codex2api: {
|
||||
codex2apiUrl: '',
|
||||
codex2apiAdminKey: '',
|
||||
},
|
||||
},
|
||||
signup: {
|
||||
@@ -112,21 +106,13 @@
|
||||
},
|
||||
},
|
||||
kiro: {
|
||||
source: {
|
||||
selected: defaultKiroSourceId,
|
||||
entries: {
|
||||
'kiro-rs': {
|
||||
kiroRsUrl: defaultKiroRsUrl,
|
||||
kiroRsKey: '',
|
||||
},
|
||||
integrationTargetId: defaultKiroIntegrationTargetId,
|
||||
integrationTargets: {
|
||||
'kiro-rs': {
|
||||
baseUrl: defaultKiroRsUrl,
|
||||
apiKey: '',
|
||||
},
|
||||
},
|
||||
options: {
|
||||
kiroRsPriority: 0,
|
||||
kiroRsEndpoint: '',
|
||||
kiroRsAuthRegion: '',
|
||||
kiroRsApiRegion: '',
|
||||
},
|
||||
autoRun: {
|
||||
stepExecutionRange: {
|
||||
enabled: false,
|
||||
@@ -139,7 +125,7 @@
|
||||
};
|
||||
}
|
||||
|
||||
function getSourceValue(settingsState, pathGetter, fallback = {}) {
|
||||
function getIntegrationTargetValue(settingsState, pathGetter, fallback = {}) {
|
||||
return cloneValue(pathGetter(isPlainObject(settingsState) ? settingsState : {}) || fallback);
|
||||
}
|
||||
|
||||
@@ -155,20 +141,21 @@
|
||||
?? defaults.activeFlowId,
|
||||
defaults.activeFlowId
|
||||
);
|
||||
const openaiSelectedSource = normalizeSourceId(
|
||||
const openaiIntegrationTargetId = normalizeIntegrationTargetId(
|
||||
'openai',
|
||||
nested?.flows?.openai?.source?.selected
|
||||
nested?.flows?.openai?.integrationTargetId
|
||||
?? input?.openaiIntegrationTargetId
|
||||
?? input?.panelMode
|
||||
?? input?.openaiSourceId
|
||||
?? defaults.flows.openai.source.selected,
|
||||
defaults.flows.openai.source.selected
|
||||
?? defaults.flows.openai.integrationTargetId,
|
||||
defaults.flows.openai.integrationTargetId
|
||||
);
|
||||
const kiroSelectedSource = normalizeSourceId(
|
||||
const kiroIntegrationTargetId = normalizeIntegrationTargetId(
|
||||
'kiro',
|
||||
nested?.flows?.kiro?.source?.selected
|
||||
nested?.flows?.kiro?.integrationTargetId
|
||||
?? input?.kiroIntegrationTargetId
|
||||
?? input?.kiroSourceId
|
||||
?? defaults.flows.kiro.source.selected,
|
||||
defaults.flows.kiro.source.selected
|
||||
?? defaults.flows.kiro.integrationTargetId,
|
||||
defaults.flows.kiro.integrationTargetId
|
||||
);
|
||||
const stepExecutionRangeByFlow = isPlainObject(input?.stepExecutionRangeByFlow)
|
||||
? input.stepExecutionRangeByFlow
|
||||
@@ -206,58 +193,115 @@
|
||||
customPassword: String(
|
||||
input?.customPassword
|
||||
?? nested?.services?.account?.customPassword
|
||||
?? nested?.flows?.openai?.account?.customPassword
|
||||
?? defaults.services.account.customPassword
|
||||
).trim(),
|
||||
},
|
||||
},
|
||||
flows: {
|
||||
openai: {
|
||||
source: {
|
||||
selected: openaiSelectedSource,
|
||||
entries: {
|
||||
cpa: {
|
||||
...defaults.flows.openai.source.entries.cpa,
|
||||
...getSourceValue(nested, (state) => state.flows?.openai?.source?.entries?.cpa),
|
||||
vpsUrl: String(input?.vpsUrl ?? nested?.flows?.openai?.source?.entries?.cpa?.vpsUrl ?? '').trim(),
|
||||
vpsPassword: String(input?.vpsPassword ?? nested?.flows?.openai?.source?.entries?.cpa?.vpsPassword ?? ''),
|
||||
localCpaStep9Mode: String(
|
||||
input?.localCpaStep9Mode
|
||||
?? nested?.flows?.openai?.source?.entries?.cpa?.localCpaStep9Mode
|
||||
?? defaults.flows.openai.source.entries.cpa.localCpaStep9Mode
|
||||
).trim() || defaults.flows.openai.source.entries.cpa.localCpaStep9Mode,
|
||||
},
|
||||
sub2api: {
|
||||
...defaults.flows.openai.source.entries.sub2api,
|
||||
...getSourceValue(nested, (state) => state.flows?.openai?.source?.entries?.sub2api),
|
||||
sub2apiUrl: String(input?.sub2apiUrl ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiUrl ?? '').trim(),
|
||||
sub2apiEmail: String(input?.sub2apiEmail ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiEmail ?? '').trim(),
|
||||
sub2apiPassword: String(input?.sub2apiPassword ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiPassword ?? ''),
|
||||
sub2apiGroupName: String(input?.sub2apiGroupName ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiGroupName ?? defaults.flows.openai.source.entries.sub2api.sub2apiGroupName).trim() || defaults.flows.openai.source.entries.sub2api.sub2apiGroupName,
|
||||
sub2apiGroupNames: Array.isArray(input?.sub2apiGroupNames)
|
||||
? input.sub2apiGroupNames.map((entry) => String(entry || '').trim()).filter(Boolean)
|
||||
: (Array.isArray(nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiGroupNames)
|
||||
? nested.flows.openai.source.entries.sub2api.sub2apiGroupNames.map((entry) => String(entry || '').trim()).filter(Boolean)
|
||||
: [...defaults.flows.openai.source.entries.sub2api.sub2apiGroupNames]),
|
||||
sub2apiAccountPriority: Math.max(1, Number(input?.sub2apiAccountPriority ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiAccountPriority ?? defaults.flows.openai.source.entries.sub2api.sub2apiAccountPriority) || defaults.flows.openai.source.entries.sub2api.sub2apiAccountPriority),
|
||||
sub2apiDefaultProxyName: String(input?.sub2apiDefaultProxyName ?? nested?.flows?.openai?.source?.entries?.sub2api?.sub2apiDefaultProxyName ?? '').trim(),
|
||||
},
|
||||
codex2api: {
|
||||
...defaults.flows.openai.source.entries.codex2api,
|
||||
...getSourceValue(nested, (state) => state.flows?.openai?.source?.entries?.codex2api),
|
||||
codex2apiUrl: String(input?.codex2apiUrl ?? nested?.flows?.openai?.source?.entries?.codex2api?.codex2apiUrl ?? '').trim(),
|
||||
codex2apiAdminKey: String(input?.codex2apiAdminKey ?? nested?.flows?.openai?.source?.entries?.codex2api?.codex2apiAdminKey ?? '').trim(),
|
||||
},
|
||||
integrationTargetId: openaiIntegrationTargetId,
|
||||
integrationTargets: {
|
||||
cpa: {
|
||||
...defaults.flows.openai.integrationTargets.cpa,
|
||||
...getIntegrationTargetValue(nested, (state) => state.flows?.openai?.integrationTargets?.cpa),
|
||||
vpsUrl: String(
|
||||
input?.vpsUrl
|
||||
?? nested?.flows?.openai?.integrationTargets?.cpa?.vpsUrl
|
||||
?? ''
|
||||
).trim(),
|
||||
vpsPassword: String(
|
||||
input?.vpsPassword
|
||||
?? nested?.flows?.openai?.integrationTargets?.cpa?.vpsPassword
|
||||
?? ''
|
||||
),
|
||||
localCpaStep9Mode: String(
|
||||
input?.localCpaStep9Mode
|
||||
?? nested?.flows?.openai?.integrationTargets?.cpa?.localCpaStep9Mode
|
||||
?? defaults.flows.openai.integrationTargets.cpa.localCpaStep9Mode
|
||||
).trim() || defaults.flows.openai.integrationTargets.cpa.localCpaStep9Mode,
|
||||
},
|
||||
sub2api: {
|
||||
...defaults.flows.openai.integrationTargets.sub2api,
|
||||
...getIntegrationTargetValue(nested, (state) => state.flows?.openai?.integrationTargets?.sub2api),
|
||||
sub2apiUrl: String(
|
||||
input?.sub2apiUrl
|
||||
?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiUrl
|
||||
?? ''
|
||||
).trim(),
|
||||
sub2apiEmail: String(
|
||||
input?.sub2apiEmail
|
||||
?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiEmail
|
||||
?? ''
|
||||
).trim(),
|
||||
sub2apiPassword: String(
|
||||
input?.sub2apiPassword
|
||||
?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiPassword
|
||||
?? ''
|
||||
),
|
||||
sub2apiGroupName: String(
|
||||
input?.sub2apiGroupName
|
||||
?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiGroupName
|
||||
?? defaults.flows.openai.integrationTargets.sub2api.sub2apiGroupName
|
||||
).trim() || defaults.flows.openai.integrationTargets.sub2api.sub2apiGroupName,
|
||||
sub2apiGroupNames: Array.isArray(input?.sub2apiGroupNames)
|
||||
? input.sub2apiGroupNames.map((entry) => String(entry || '').trim()).filter(Boolean)
|
||||
: (Array.isArray(nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiGroupNames)
|
||||
? nested.flows.openai.integrationTargets.sub2api.sub2apiGroupNames.map((entry) => String(entry || '').trim()).filter(Boolean)
|
||||
: [...defaults.flows.openai.integrationTargets.sub2api.sub2apiGroupNames]),
|
||||
sub2apiAccountPriority: Math.max(1, Number(
|
||||
input?.sub2apiAccountPriority
|
||||
?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiAccountPriority
|
||||
?? defaults.flows.openai.integrationTargets.sub2api.sub2apiAccountPriority
|
||||
) || defaults.flows.openai.integrationTargets.sub2api.sub2apiAccountPriority),
|
||||
sub2apiDefaultProxyName: String(
|
||||
input?.sub2apiDefaultProxyName
|
||||
?? nested?.flows?.openai?.integrationTargets?.sub2api?.sub2apiDefaultProxyName
|
||||
?? ''
|
||||
).trim(),
|
||||
},
|
||||
codex2api: {
|
||||
...defaults.flows.openai.integrationTargets.codex2api,
|
||||
...getIntegrationTargetValue(nested, (state) => state.flows?.openai?.integrationTargets?.codex2api),
|
||||
codex2apiUrl: String(
|
||||
input?.codex2apiUrl
|
||||
?? nested?.flows?.openai?.integrationTargets?.codex2api?.codex2apiUrl
|
||||
?? ''
|
||||
).trim(),
|
||||
codex2apiAdminKey: String(
|
||||
input?.codex2apiAdminKey
|
||||
?? nested?.flows?.openai?.integrationTargets?.codex2api?.codex2apiAdminKey
|
||||
?? ''
|
||||
).trim(),
|
||||
},
|
||||
},
|
||||
signup: {
|
||||
signupMethod: String(input?.signupMethod ?? nested?.flows?.openai?.signup?.signupMethod ?? defaults.flows.openai.signup.signupMethod).trim().toLowerCase() === 'phone' ? 'phone' : 'email',
|
||||
phoneVerificationEnabled: Boolean(input?.phoneVerificationEnabled ?? nested?.flows?.openai?.signup?.phoneVerificationEnabled ?? defaults.flows.openai.signup.phoneVerificationEnabled),
|
||||
phoneSignupReloginAfterBindEmailEnabled: Boolean(input?.phoneSignupReloginAfterBindEmailEnabled ?? nested?.flows?.openai?.signup?.phoneSignupReloginAfterBindEmailEnabled ?? defaults.flows.openai.signup.phoneSignupReloginAfterBindEmailEnabled),
|
||||
signupMethod: String(
|
||||
input?.signupMethod
|
||||
?? nested?.flows?.openai?.signup?.signupMethod
|
||||
?? defaults.flows.openai.signup.signupMethod
|
||||
).trim().toLowerCase() === 'phone' ? 'phone' : 'email',
|
||||
phoneVerificationEnabled: Boolean(
|
||||
input?.phoneVerificationEnabled
|
||||
?? nested?.flows?.openai?.signup?.phoneVerificationEnabled
|
||||
?? defaults.flows.openai.signup.phoneVerificationEnabled
|
||||
),
|
||||
phoneSignupReloginAfterBindEmailEnabled: Boolean(
|
||||
input?.phoneSignupReloginAfterBindEmailEnabled
|
||||
?? nested?.flows?.openai?.signup?.phoneSignupReloginAfterBindEmailEnabled
|
||||
?? defaults.flows.openai.signup.phoneSignupReloginAfterBindEmailEnabled
|
||||
),
|
||||
},
|
||||
plus: {
|
||||
plusModeEnabled: Boolean(input?.plusModeEnabled ?? nested?.flows?.openai?.plus?.plusModeEnabled ?? defaults.flows.openai.plus.plusModeEnabled),
|
||||
plusPaymentMethod: String(input?.plusPaymentMethod ?? nested?.flows?.openai?.plus?.plusPaymentMethod ?? defaults.flows.openai.plus.plusPaymentMethod).trim() || defaults.flows.openai.plus.plusPaymentMethod,
|
||||
plusModeEnabled: Boolean(
|
||||
input?.plusModeEnabled
|
||||
?? nested?.flows?.openai?.plus?.plusModeEnabled
|
||||
?? defaults.flows.openai.plus.plusModeEnabled
|
||||
),
|
||||
plusPaymentMethod: String(
|
||||
input?.plusPaymentMethod
|
||||
?? nested?.flows?.openai?.plus?.plusPaymentMethod
|
||||
?? defaults.flows.openai.plus.plusPaymentMethod
|
||||
).trim() || defaults.flows.openai.plus.plusPaymentMethod,
|
||||
},
|
||||
autoRun: {
|
||||
stepExecutionRange: normalizeStepExecutionRangeEntry(
|
||||
@@ -269,47 +313,25 @@
|
||||
},
|
||||
},
|
||||
kiro: {
|
||||
source: {
|
||||
selected: kiroSelectedSource,
|
||||
entries: {
|
||||
'kiro-rs': {
|
||||
...defaults.flows.kiro.source.entries['kiro-rs'],
|
||||
...getSourceValue(nested, (state) => state.flows?.kiro?.source?.entries?.['kiro-rs']),
|
||||
kiroRsUrl: String(
|
||||
input?.kiroRsUrl
|
||||
?? nested?.flows?.kiro?.source?.entries?.['kiro-rs']?.kiroRsUrl
|
||||
?? defaults.flows.kiro.source.entries['kiro-rs'].kiroRsUrl
|
||||
).trim() || defaults.flows.kiro.source.entries['kiro-rs'].kiroRsUrl,
|
||||
kiroRsKey: String(
|
||||
input?.kiroRsKey
|
||||
?? nested?.flows?.kiro?.source?.entries?.['kiro-rs']?.kiroRsKey
|
||||
?? defaults.flows.kiro.source.entries['kiro-rs'].kiroRsKey
|
||||
),
|
||||
},
|
||||
integrationTargetId: kiroIntegrationTargetId,
|
||||
integrationTargets: {
|
||||
'kiro-rs': {
|
||||
...defaults.flows.kiro.integrationTargets['kiro-rs'],
|
||||
...getIntegrationTargetValue(nested, (state) => state.flows?.kiro?.integrationTargets?.['kiro-rs']),
|
||||
baseUrl: String(
|
||||
input?.kiroRsUrl
|
||||
?? input?.kiroRsBaseUrl
|
||||
?? nested?.flows?.kiro?.integrationTargets?.['kiro-rs']?.baseUrl
|
||||
?? defaults.flows.kiro.integrationTargets['kiro-rs'].baseUrl
|
||||
).trim() || defaults.flows.kiro.integrationTargets['kiro-rs'].baseUrl,
|
||||
apiKey: String(
|
||||
input?.kiroRsKey
|
||||
?? input?.kiroRsApiKey
|
||||
?? nested?.flows?.kiro?.integrationTargets?.['kiro-rs']?.apiKey
|
||||
?? defaults.flows.kiro.integrationTargets['kiro-rs'].apiKey
|
||||
),
|
||||
},
|
||||
},
|
||||
options: {
|
||||
kiroRsPriority: Number(
|
||||
input?.kiroRsPriority
|
||||
?? nested?.flows?.kiro?.options?.kiroRsPriority
|
||||
?? defaults.flows.kiro.options.kiroRsPriority
|
||||
) || 0,
|
||||
kiroRsEndpoint: String(
|
||||
input?.kiroRsEndpoint
|
||||
?? nested?.flows?.kiro?.options?.kiroRsEndpoint
|
||||
?? defaults.flows.kiro.options.kiroRsEndpoint
|
||||
).trim(),
|
||||
kiroRsAuthRegion: String(
|
||||
input?.kiroRsAuthRegion
|
||||
?? nested?.flows?.kiro?.options?.kiroRsAuthRegion
|
||||
?? defaults.flows.kiro.options.kiroRsAuthRegion
|
||||
).trim(),
|
||||
kiroRsApiRegion: String(
|
||||
input?.kiroRsApiRegion
|
||||
?? nested?.flows?.kiro?.options?.kiroRsApiRegion
|
||||
?? defaults.flows.kiro.options.kiroRsApiRegion
|
||||
).trim(),
|
||||
},
|
||||
autoRun: {
|
||||
stepExecutionRange: normalizeStepExecutionRangeEntry(
|
||||
nested?.flows?.kiro?.autoRun?.stepExecutionRange
|
||||
@@ -323,6 +345,53 @@
|
||||
};
|
||||
}
|
||||
|
||||
function mergeSettingsState(baseValue = {}, patchValue = {}) {
|
||||
const baseSettingsState = normalizeSettingsState(baseValue);
|
||||
const patchSettingsState = normalizeSettingsState({
|
||||
settingsState: patchValue,
|
||||
activeFlowId: patchValue?.activeFlowId ?? baseSettingsState.activeFlowId,
|
||||
});
|
||||
|
||||
function mergeRecursive(baseNode, patchNode) {
|
||||
if (Array.isArray(patchNode)) {
|
||||
return patchNode.map((entry) => cloneValue(entry));
|
||||
}
|
||||
if (!isPlainObject(patchNode)) {
|
||||
return patchNode === undefined ? cloneValue(baseNode) : patchNode;
|
||||
}
|
||||
const next = {
|
||||
...cloneValue(isPlainObject(baseNode) ? baseNode : {}),
|
||||
};
|
||||
Object.entries(patchNode).forEach(([key, value]) => {
|
||||
next[key] = mergeRecursive(baseNode?.[key], value);
|
||||
});
|
||||
return next;
|
||||
}
|
||||
|
||||
return normalizeSettingsState({
|
||||
settingsState: mergeRecursive(baseSettingsState, patchSettingsState),
|
||||
});
|
||||
}
|
||||
|
||||
function getFlowSettings(settingsState = {}, flowId) {
|
||||
const normalizedState = normalizeSettingsState(settingsState);
|
||||
const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId);
|
||||
return cloneValue(normalizedState?.flows?.[normalizedFlowId] || {});
|
||||
}
|
||||
|
||||
function getSelectedIntegrationTargetId(settingsState = {}, flowId) {
|
||||
const normalizedState = normalizeSettingsState(settingsState);
|
||||
const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId);
|
||||
const flowSettings = normalizedState?.flows?.[normalizedFlowId] || {};
|
||||
return normalizeIntegrationTargetId(
|
||||
normalizedFlowId,
|
||||
flowSettings?.integrationTargetId,
|
||||
normalizedFlowId === 'kiro'
|
||||
? defaultKiroIntegrationTargetId
|
||||
: defaultOpenAiIntegrationTargetId
|
||||
);
|
||||
}
|
||||
|
||||
function buildStepExecutionRangeByFlow(settingsState = {}) {
|
||||
const normalizedState = normalizeSettingsState(settingsState);
|
||||
return {
|
||||
@@ -337,23 +406,7 @@
|
||||
};
|
||||
}
|
||||
|
||||
function getFlowSettings(settingsState = {}, flowId) {
|
||||
const normalizedState = normalizeSettingsState(settingsState);
|
||||
const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId);
|
||||
return cloneValue(normalizedState?.flows?.[normalizedFlowId] || {});
|
||||
}
|
||||
|
||||
function getSelectedSourceId(settingsState = {}, flowId) {
|
||||
const flowSettings = getFlowSettings(settingsState, flowId);
|
||||
const normalizedFlowId = normalizeFlowId(flowId, normalizeSettingsState(settingsState).activeFlowId);
|
||||
return normalizeSourceId(
|
||||
normalizedFlowId,
|
||||
flowSettings?.source?.selected,
|
||||
normalizedFlowId === 'kiro' ? defaultKiroSourceId : defaultOpenAiSourceId
|
||||
);
|
||||
}
|
||||
|
||||
function buildLegacySettingsPayload(settingsState = {}, baseInput = {}) {
|
||||
function buildSettingsView(settingsState = {}, baseInput = {}) {
|
||||
const normalizedState = normalizeSettingsState(settingsState);
|
||||
const next = {
|
||||
...(isPlainObject(baseInput) ? cloneValue(baseInput) : {}),
|
||||
@@ -361,20 +414,22 @@
|
||||
const openaiState = normalizedState.flows.openai;
|
||||
const kiroState = normalizedState.flows.kiro;
|
||||
next.activeFlowId = normalizedState.activeFlowId;
|
||||
next.panelMode = mapSourceIdToPanelMode('openai', openaiState.source.selected, defaultOpenAiSourceId);
|
||||
next.kiroSourceId = getSelectedSourceId(normalizedState, 'kiro');
|
||||
next.vpsUrl = openaiState.source.entries.cpa.vpsUrl;
|
||||
next.vpsPassword = openaiState.source.entries.cpa.vpsPassword;
|
||||
next.localCpaStep9Mode = openaiState.source.entries.cpa.localCpaStep9Mode;
|
||||
next.sub2apiUrl = openaiState.source.entries.sub2api.sub2apiUrl;
|
||||
next.sub2apiEmail = openaiState.source.entries.sub2api.sub2apiEmail;
|
||||
next.sub2apiPassword = openaiState.source.entries.sub2api.sub2apiPassword;
|
||||
next.sub2apiGroupName = openaiState.source.entries.sub2api.sub2apiGroupName;
|
||||
next.sub2apiGroupNames = cloneValue(openaiState.source.entries.sub2api.sub2apiGroupNames);
|
||||
next.sub2apiAccountPriority = openaiState.source.entries.sub2api.sub2apiAccountPriority;
|
||||
next.sub2apiDefaultProxyName = openaiState.source.entries.sub2api.sub2apiDefaultProxyName;
|
||||
next.codex2apiUrl = openaiState.source.entries.codex2api.codex2apiUrl;
|
||||
next.codex2apiAdminKey = openaiState.source.entries.codex2api.codex2apiAdminKey;
|
||||
next.openaiIntegrationTargetId = getSelectedIntegrationTargetId(normalizedState, 'openai');
|
||||
next.kiroIntegrationTargetId = getSelectedIntegrationTargetId(normalizedState, 'kiro');
|
||||
next.panelMode = next.openaiIntegrationTargetId;
|
||||
next.kiroSourceId = next.kiroIntegrationTargetId;
|
||||
next.vpsUrl = openaiState.integrationTargets.cpa.vpsUrl;
|
||||
next.vpsPassword = openaiState.integrationTargets.cpa.vpsPassword;
|
||||
next.localCpaStep9Mode = openaiState.integrationTargets.cpa.localCpaStep9Mode;
|
||||
next.sub2apiUrl = openaiState.integrationTargets.sub2api.sub2apiUrl;
|
||||
next.sub2apiEmail = openaiState.integrationTargets.sub2api.sub2apiEmail;
|
||||
next.sub2apiPassword = openaiState.integrationTargets.sub2api.sub2apiPassword;
|
||||
next.sub2apiGroupName = openaiState.integrationTargets.sub2api.sub2apiGroupName;
|
||||
next.sub2apiGroupNames = cloneValue(openaiState.integrationTargets.sub2api.sub2apiGroupNames);
|
||||
next.sub2apiAccountPriority = openaiState.integrationTargets.sub2api.sub2apiAccountPriority;
|
||||
next.sub2apiDefaultProxyName = openaiState.integrationTargets.sub2api.sub2apiDefaultProxyName;
|
||||
next.codex2apiUrl = openaiState.integrationTargets.codex2api.codex2apiUrl;
|
||||
next.codex2apiAdminKey = openaiState.integrationTargets.codex2api.codex2apiAdminKey;
|
||||
next.customPassword = normalizedState.services.account.customPassword;
|
||||
next.signupMethod = openaiState.signup.signupMethod;
|
||||
next.phoneVerificationEnabled = openaiState.signup.phoneVerificationEnabled;
|
||||
@@ -385,13 +440,8 @@
|
||||
next.ipProxyEnabled = normalizedState.services.proxy.enabled;
|
||||
next.ipProxyService = normalizedState.services.proxy.provider;
|
||||
next.ipProxyMode = normalizedState.services.proxy.mode;
|
||||
next.kiroRsUrl = kiroState.source.entries['kiro-rs'].kiroRsUrl;
|
||||
next.kiroRsKey = kiroState.source.entries['kiro-rs'].kiroRsKey;
|
||||
next.kiroRsPriority = kiroState.options.kiroRsPriority;
|
||||
next.kiroRsEndpoint = kiroState.options.kiroRsEndpoint;
|
||||
next.kiroRsAuthRegion = kiroState.options.kiroRsAuthRegion;
|
||||
next.kiroRsApiRegion = kiroState.options.kiroRsApiRegion;
|
||||
delete next.kiroRegion;
|
||||
next.kiroRsUrl = kiroState.integrationTargets['kiro-rs'].baseUrl;
|
||||
next.kiroRsKey = kiroState.integrationTargets['kiro-rs'].apiKey;
|
||||
next.stepExecutionRangeByFlow = buildStepExecutionRangeByFlow(normalizedState);
|
||||
next.settingsSchemaVersion = normalizedState.schemaVersion;
|
||||
next.settingsState = cloneValue(normalizedState);
|
||||
@@ -401,34 +451,29 @@
|
||||
function getFlowInputState(settingsState = {}, flowId) {
|
||||
const normalizedState = normalizeSettingsState(settingsState);
|
||||
const normalizedFlowId = normalizeFlowId(flowId, normalizedState.activeFlowId);
|
||||
const integrationTargetId = getSelectedIntegrationTargetId(normalizedState, normalizedFlowId);
|
||||
if (normalizedFlowId === 'kiro') {
|
||||
return {
|
||||
activeFlowId: normalizedFlowId,
|
||||
sourceId: getSelectedSourceId(normalizedState, 'kiro'),
|
||||
kiroRsUrl: normalizedState.flows.kiro.source.entries['kiro-rs'].kiroRsUrl,
|
||||
kiroRsKey: normalizedState.flows.kiro.source.entries['kiro-rs'].kiroRsKey,
|
||||
integrationTargetId,
|
||||
kiroRsUrl: normalizedState.flows.kiro.integrationTargets['kiro-rs'].baseUrl,
|
||||
kiroRsKey: normalizedState.flows.kiro.integrationTargets['kiro-rs'].apiKey,
|
||||
};
|
||||
}
|
||||
return {
|
||||
activeFlowId: normalizedFlowId,
|
||||
sourceId: getSelectedSourceId(normalizedState, 'openai'),
|
||||
panelMode: mapSourceIdToPanelMode('openai', normalizedState.flows.openai.source.selected, defaultOpenAiSourceId),
|
||||
integrationTargetId,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeFlatInput(input = {}) {
|
||||
const state = normalizeSettingsState(input);
|
||||
return buildLegacySettingsPayload(state, input);
|
||||
}
|
||||
|
||||
return {
|
||||
buildDefaultSettingsState,
|
||||
buildLegacySettingsPayload,
|
||||
buildSettingsView,
|
||||
buildStepExecutionRangeByFlow,
|
||||
getFlowInputState,
|
||||
getFlowSettings,
|
||||
getSelectedSourceId,
|
||||
normalizeFlatInput,
|
||||
getSelectedIntegrationTargetId,
|
||||
mergeSettingsState,
|
||||
normalizeSettingsState,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -683,10 +683,10 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="setting-group setting-group-secondary step-range-controls">
|
||||
<span class="setting-caption">步骤</span>
|
||||
<input type="number" id="input-step-execution-range-from" class="data-input auto-delay-input step-range-input" value="1" min="1" step="1" title="允许执行的起始步骤" />
|
||||
<span class="setting-caption">节点</span>
|
||||
<select id="input-step-execution-range-from" class="data-select step-range-input" title="允许执行的起始节点"></select>
|
||||
<span class="data-unit">到</span>
|
||||
<input type="number" id="input-step-execution-range-to" class="data-input auto-delay-input step-range-input" value="1" min="1" step="1" title="允许执行的结束步骤" />
|
||||
<select id="input-step-execution-range-to" class="data-select step-range-input" title="允许执行的结束节点"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+81
-31
@@ -884,18 +884,17 @@ function getWorkflowNodesForMode(plusModeEnabled = false, options = {}) {
|
||||
});
|
||||
if (Array.isArray(nodes) && nodes.length) {
|
||||
return nodes.slice().sort((left, right) => {
|
||||
const leftOrder = Number.isFinite(Number(left.displayOrder)) ? Number(left.displayOrder) : Number(left.legacyStepId);
|
||||
const rightOrder = Number.isFinite(Number(right.displayOrder)) ? Number(right.displayOrder) : Number(right.legacyStepId);
|
||||
const leftOrder = Number.isFinite(Number(left.displayOrder)) ? Number(left.displayOrder) : 0;
|
||||
const rightOrder = Number.isFinite(Number(right.displayOrder)) ? Number(right.displayOrder) : 0;
|
||||
if (leftOrder !== rightOrder) return leftOrder - rightOrder;
|
||||
return String(left.nodeId || '').localeCompare(String(right.nodeId || ''));
|
||||
});
|
||||
}
|
||||
|
||||
return getStepDefinitionsForMode(plusModeEnabled, options).map((step) => ({
|
||||
legacyStepId: Number(step.id),
|
||||
nodeId: String(step.key || '').trim(),
|
||||
title: step.title,
|
||||
displayOrder: Number.isFinite(Number(step.order)) ? Number(step.order) : Number(step.id),
|
||||
displayOrder: Number.isFinite(Number(step.id)) ? Number(step.id) : Number(step.order),
|
||||
executeKey: String(step.key || '').trim(),
|
||||
})).filter((node) => node.nodeId);
|
||||
}
|
||||
@@ -911,7 +910,7 @@ function getStepIdByKeyForCurrentMode(stepKey = '') {
|
||||
|
||||
function getNodeIdByStepForCurrentMode(step) {
|
||||
const numericStep = Number(step);
|
||||
const node = (workflowNodes || []).find((candidate) => Number(candidate?.legacyStepId) === numericStep);
|
||||
const node = (workflowNodes || []).find((candidate) => Number(candidate?.displayOrder) === numericStep);
|
||||
if (node?.nodeId) {
|
||||
return String(node.nodeId).trim();
|
||||
}
|
||||
@@ -925,9 +924,9 @@ function getStepIdByNodeIdForCurrentMode(nodeId = '') {
|
||||
return 0;
|
||||
}
|
||||
const node = (workflowNodes || []).find((candidate) => String(candidate?.nodeId || '').trim() === normalizedNodeId);
|
||||
const legacyStepId = Number(node?.legacyStepId);
|
||||
if (Number.isInteger(legacyStepId) && legacyStepId > 0) {
|
||||
return legacyStepId;
|
||||
const displayOrder = Number(node?.displayOrder);
|
||||
if (Number.isInteger(displayOrder) && displayOrder > 0) {
|
||||
return displayOrder;
|
||||
}
|
||||
return getStepIdByKeyForCurrentMode(normalizedNodeId);
|
||||
}
|
||||
@@ -970,10 +969,9 @@ function rebuildStepDefinitionState(plusModeEnabled = false, options = {}) {
|
||||
phoneSignupReloginAfterBindEmailEnabled: currentPhoneSignupReloginAfterBindEmailEnabled,
|
||||
})
|
||||
: stepDefinitions.map((step) => ({
|
||||
legacyStepId: Number(step.id),
|
||||
nodeId: String(step.key || step.id || '').trim(),
|
||||
title: step.title,
|
||||
displayOrder: Number.isFinite(Number(step.order)) ? Number(step.order) : Number(step.id),
|
||||
displayOrder: Number.isFinite(Number(step.id)) ? Number(step.id) : Number(step.order),
|
||||
}));
|
||||
if (typeof workflowNodes !== 'undefined') {
|
||||
workflowNodes = nextWorkflowNodes;
|
||||
@@ -2436,24 +2434,76 @@ function getLastCurrentStepId() {
|
||||
return STEP_IDS.length ? Math.max(...STEP_IDS) : 1;
|
||||
}
|
||||
|
||||
function getStepExecutionRangeNodes() {
|
||||
return Array.isArray(workflowNodes)
|
||||
? workflowNodes.filter((node) => String(node?.nodeId || '').trim())
|
||||
: [];
|
||||
}
|
||||
|
||||
function getStepExecutionRangeNodeLabel(node = {}) {
|
||||
const nodeId = String(node?.nodeId || '').trim();
|
||||
const displayOrder = Number(node?.displayOrder);
|
||||
const title = String(node?.title || nodeId).trim();
|
||||
const orderLabel = Number.isInteger(displayOrder) && displayOrder > 0
|
||||
? `步骤 ${displayOrder}`
|
||||
: nodeId;
|
||||
return title ? `${orderLabel} · ${title}` : orderLabel;
|
||||
}
|
||||
|
||||
function getStepExecutionRangeBoundaryNodeId(stepNumber, boundary = 'start') {
|
||||
const nodes = getStepExecutionRangeNodes();
|
||||
const fallbackNode = boundary === 'end' ? nodes[nodes.length - 1] : nodes[0];
|
||||
const resolvedNodeId = getNodeIdByStepForCurrentMode(stepNumber);
|
||||
return String(resolvedNodeId || fallbackNode?.nodeId || '').trim();
|
||||
}
|
||||
|
||||
function syncStepExecutionRangeSelectOptions(selectedFromNodeId = '', selectedToNodeId = '') {
|
||||
const nodes = getStepExecutionRangeNodes();
|
||||
const fromSelect = inputStepExecutionRangeFrom;
|
||||
const toSelect = inputStepExecutionRangeTo;
|
||||
if (!fromSelect || !toSelect) {
|
||||
return;
|
||||
}
|
||||
|
||||
const buildOptions = (selectedValue) => nodes.map((node) => {
|
||||
const nodeId = String(node?.nodeId || '').trim();
|
||||
const label = getStepExecutionRangeNodeLabel(node);
|
||||
return `<option value="${escapeHtml(nodeId)}"${nodeId === selectedValue ? ' selected' : ''}>${escapeHtml(label)}</option>`;
|
||||
}).join('');
|
||||
|
||||
fromSelect.innerHTML = buildOptions(String(selectedFromNodeId || nodes[0]?.nodeId || '').trim());
|
||||
toSelect.innerHTML = buildOptions(String(selectedToNodeId || nodes[nodes.length - 1]?.nodeId || '').trim());
|
||||
}
|
||||
|
||||
function isStepExecutionRangeUiAvailable(state = latestState) {
|
||||
return getCurrentStepExecutionRangeFlowId(state) === DEFAULT_ACTIVE_FLOW_ID;
|
||||
}
|
||||
|
||||
function clampStepExecutionRangeInputs() {
|
||||
const maxStep = getLastCurrentStepId();
|
||||
const fromStep = Math.min(maxStep, Math.max(1, normalizePositiveStepNumber(inputStepExecutionRangeFrom?.value, 1)));
|
||||
const toStep = Math.min(maxStep, Math.max(1, normalizePositiveStepNumber(inputStepExecutionRangeTo?.value, maxStep)));
|
||||
const normalizedFrom = Math.min(fromStep, toStep);
|
||||
const normalizedTo = Math.max(fromStep, toStep);
|
||||
const nodes = getStepExecutionRangeNodes();
|
||||
const firstNodeId = String(nodes[0]?.nodeId || '').trim();
|
||||
const lastNodeId = String(nodes[nodes.length - 1]?.nodeId || '').trim();
|
||||
const selectedFromNodeId = String(inputStepExecutionRangeFrom?.value || firstNodeId).trim() || firstNodeId;
|
||||
const selectedToNodeId = String(inputStepExecutionRangeTo?.value || lastNodeId).trim() || lastNodeId;
|
||||
const fromStep = Math.max(1, getStepIdByNodeIdForCurrentMode(selectedFromNodeId) || 1);
|
||||
const toStep = Math.max(1, getStepIdByNodeIdForCurrentMode(selectedToNodeId) || fromStep);
|
||||
const normalizedFromStep = Math.min(fromStep, toStep);
|
||||
const normalizedToStep = Math.max(fromStep, toStep);
|
||||
const normalizedFromNodeId = getStepExecutionRangeBoundaryNodeId(normalizedFromStep, 'start');
|
||||
const normalizedToNodeId = getStepExecutionRangeBoundaryNodeId(normalizedToStep, 'end');
|
||||
syncStepExecutionRangeSelectOptions(normalizedFromNodeId, normalizedToNodeId);
|
||||
if (inputStepExecutionRangeFrom) {
|
||||
inputStepExecutionRangeFrom.max = String(maxStep);
|
||||
inputStepExecutionRangeFrom.value = String(normalizedFrom);
|
||||
inputStepExecutionRangeFrom.value = normalizedFromNodeId;
|
||||
}
|
||||
if (inputStepExecutionRangeTo) {
|
||||
inputStepExecutionRangeTo.max = String(maxStep);
|
||||
inputStepExecutionRangeTo.value = String(normalizedTo);
|
||||
inputStepExecutionRangeTo.value = normalizedToNodeId;
|
||||
}
|
||||
return {
|
||||
fromNodeId: normalizedFromNodeId,
|
||||
toNodeId: normalizedToNodeId,
|
||||
fromStep: normalizedFromStep,
|
||||
toStep: normalizedToStep,
|
||||
};
|
||||
}
|
||||
|
||||
function buildStepExecutionRangeByFlowPayload(existingConfig = latestState?.stepExecutionRangeByFlow || {}) {
|
||||
@@ -2461,12 +2511,12 @@ function buildStepExecutionRangeByFlowPayload(existingConfig = latestState?.step
|
||||
if (!isStepExecutionRangeUiAvailable(latestState)) {
|
||||
return config;
|
||||
}
|
||||
clampStepExecutionRangeInputs();
|
||||
const normalizedRange = clampStepExecutionRangeInputs();
|
||||
const flowId = getCurrentStepExecutionRangeFlowId(latestState);
|
||||
config[flowId] = normalizeStepExecutionRangeEntry({
|
||||
enabled: Boolean(inputStepExecutionRangeEnabled?.checked),
|
||||
fromStep: inputStepExecutionRangeFrom?.value,
|
||||
toStep: inputStepExecutionRangeTo?.value,
|
||||
fromStep: normalizedRange?.fromStep,
|
||||
toStep: normalizedRange?.toStep,
|
||||
});
|
||||
return config;
|
||||
}
|
||||
@@ -2493,17 +2543,15 @@ function applyStepExecutionRangeState(state = latestState) {
|
||||
}
|
||||
const available = isStepExecutionRangeUiAvailable(state);
|
||||
rowStepExecutionRange.style.display = available ? '' : 'none';
|
||||
const maxStep = getLastCurrentStepId();
|
||||
const range = getStepExecutionRangeForCurrentFlow(state);
|
||||
const fromNodeId = getStepExecutionRangeBoundaryNodeId(range.fromStep, 'start');
|
||||
const toNodeId = getStepExecutionRangeBoundaryNodeId(range.toStep, 'end');
|
||||
syncStepExecutionRangeSelectOptions(fromNodeId, toNodeId);
|
||||
if (inputStepExecutionRangeFrom) {
|
||||
inputStepExecutionRangeFrom.min = '1';
|
||||
inputStepExecutionRangeFrom.max = String(maxStep);
|
||||
inputStepExecutionRangeFrom.value = String(Math.min(maxStep, Math.max(1, normalizePositiveStepNumber(range.fromStep, 1))));
|
||||
inputStepExecutionRangeFrom.value = fromNodeId;
|
||||
}
|
||||
if (inputStepExecutionRangeTo) {
|
||||
inputStepExecutionRangeTo.min = '1';
|
||||
inputStepExecutionRangeTo.max = String(maxStep);
|
||||
inputStepExecutionRangeTo.value = String(Math.min(maxStep, Math.max(1, normalizePositiveStepNumber(range.toStep, maxStep))));
|
||||
inputStepExecutionRangeTo.value = toNodeId;
|
||||
}
|
||||
if (inputStepExecutionRangeEnabled) {
|
||||
inputStepExecutionRangeEnabled.checked = Boolean(range.enabled);
|
||||
@@ -14932,14 +14980,16 @@ selectFlow?.addEventListener('change', () => {
|
||||
});
|
||||
|
||||
[inputStepExecutionRangeFrom, inputStepExecutionRangeTo].forEach((input) => {
|
||||
input?.addEventListener('input', () => {
|
||||
const handleRangeChange = () => {
|
||||
const stepExecutionRangeByFlow = buildStepExecutionRangeByFlowPayload(latestState?.stepExecutionRangeByFlow);
|
||||
syncLatestState({ stepExecutionRangeByFlow });
|
||||
markSettingsDirty(true);
|
||||
renderStepStatuses(latestState);
|
||||
updateButtonStates();
|
||||
scheduleSettingsAutoSave();
|
||||
});
|
||||
};
|
||||
input?.addEventListener('input', handleRangeChange);
|
||||
input?.addEventListener('change', handleRangeChange);
|
||||
input?.addEventListener('blur', () => {
|
||||
clampStepExecutionRangeInputs();
|
||||
saveSettings({ silent: true }).catch(() => { });
|
||||
|
||||
@@ -124,8 +124,8 @@ test('runtime-state patch accepts nested flow and node updates without legacy st
|
||||
assert.equal(patch.activeFlowId, 'openai');
|
||||
assert.equal(patch.activeRunId, 'run-001');
|
||||
assert.equal(patch.currentNodeId, 'oauth-login');
|
||||
assert.equal(patch.oauthUrl, 'https://new.example.com/start');
|
||||
assert.equal(patch.plusCheckoutTabId, 99);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(patch, 'oauthUrl'), false);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(patch, 'plusCheckoutTabId'), false);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(patch, 'currentStep'), false);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(patch, 'stepStatuses'), false);
|
||||
assert.deepStrictEqual(patch.nodeStatuses, {
|
||||
@@ -135,6 +135,10 @@ test('runtime-state patch accepts nested flow and node updates without legacy st
|
||||
});
|
||||
assert.equal(patch.runtimeState.flowState.openai.auth.oauthUrl, 'https://new.example.com/start');
|
||||
assert.equal(patch.runtimeState.flowState.openai.plus.plusCheckoutTabId, 99);
|
||||
|
||||
const view = helpers.buildStateView(patch);
|
||||
assert.equal(view.oauthUrl, 'https://new.example.com/start');
|
||||
assert.equal(view.plusCheckoutTabId, 99);
|
||||
});
|
||||
|
||||
test('runtime-state patch prefers explicit activeFlowId over stale legacy flowId', () => {
|
||||
|
||||
@@ -57,6 +57,39 @@ ${flowRegistrySource}
|
||||
${settingsSchemaSource}
|
||||
const DEFAULT_ACTIVE_FLOW_ID = 'openai';
|
||||
const DEFAULT_SUB2API_GROUP_NAMES = ['codex', 'openai-plus'];
|
||||
const SETTINGS_SCHEMA_VIEW_KEYS = Object.freeze([
|
||||
'activeFlowId',
|
||||
'openaiIntegrationTargetId',
|
||||
'kiroIntegrationTargetId',
|
||||
'panelMode',
|
||||
'kiroSourceId',
|
||||
'vpsUrl',
|
||||
'vpsPassword',
|
||||
'localCpaStep9Mode',
|
||||
'sub2apiUrl',
|
||||
'sub2apiEmail',
|
||||
'sub2apiPassword',
|
||||
'sub2apiGroupName',
|
||||
'sub2apiGroupNames',
|
||||
'sub2apiAccountPriority',
|
||||
'sub2apiDefaultProxyName',
|
||||
'codex2apiUrl',
|
||||
'codex2apiAdminKey',
|
||||
'customPassword',
|
||||
'signupMethod',
|
||||
'phoneVerificationEnabled',
|
||||
'phoneSignupReloginAfterBindEmailEnabled',
|
||||
'plusModeEnabled',
|
||||
'plusPaymentMethod',
|
||||
'mailProvider',
|
||||
'ipProxyEnabled',
|
||||
'ipProxyService',
|
||||
'ipProxyMode',
|
||||
'kiroRsUrl',
|
||||
'kiroRsKey',
|
||||
'stepExecutionRangeByFlow',
|
||||
]);
|
||||
const SETTINGS_SCHEMA_VIEW_KEY_SET = new Set(SETTINGS_SCHEMA_VIEW_KEYS);
|
||||
const PERSISTED_SETTING_DEFAULTS = {
|
||||
activeFlowId: DEFAULT_ACTIVE_FLOW_ID,
|
||||
panelMode: 'cpa',
|
||||
@@ -77,6 +110,9 @@ const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);
|
||||
const PERSISTED_SETTINGS_SCHEMA_KEYS = ['settingsSchemaVersion', 'settingsState'];
|
||||
const LEGACY_AUTO_STEP_DELAY_KEYS = [];
|
||||
const LEGACY_VERIFICATION_RESEND_COUNT_KEYS = [];
|
||||
function isPlainObjectValue(value) {
|
||||
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
function normalizePanelMode(value = '') {
|
||||
const normalized = String(value || '').trim().toLowerCase();
|
||||
return normalized === 'sub2api' || normalized === 'codex2api' ? normalized : 'cpa';
|
||||
@@ -131,6 +167,8 @@ function resolveSignupMethod(state = {}) {
|
||||
function resolveLegacyAutoStepDelaySeconds() { return undefined; }
|
||||
${extractFunction('normalizePersistentSettingValue')}
|
||||
${extractFunction('getSettingsSchemaApi')}
|
||||
${extractFunction('projectSettingsSchemaView')}
|
||||
${extractFunction('buildPersistedSettingsStoragePayload')}
|
||||
${extractFunction('buildPersistentSettingsPayload')}
|
||||
${extractFunction('getPersistedSettings')}
|
||||
${extractFunction('setPersistentSettings')}
|
||||
@@ -141,6 +179,7 @@ return {
|
||||
setPersistentSettings,
|
||||
getRequestedKeys: typeof getRequestedKeys === 'function' ? getRequestedKeys : () => [],
|
||||
getPersistedWrites: typeof getPersistedWrites === 'function' ? getPersistedWrites : () => [],
|
||||
getRemovedKeys: typeof getRemovedKeys === 'function' ? getRemovedKeys : () => [],
|
||||
};
|
||||
`)();
|
||||
}
|
||||
@@ -159,26 +198,50 @@ test('buildPersistentSettingsPayload writes canonical settings schema into persi
|
||||
assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(payload.kiroRsKey, 'secret-key');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false);
|
||||
assert.equal(payload.settingsSchemaVersion, 3);
|
||||
assert.equal(payload.settingsSchemaVersion, 4);
|
||||
assert.equal(payload.settingsState.activeFlowId, 'kiro');
|
||||
assert.equal(payload.settingsState.flows.kiro.source.selected, 'kiro-rs');
|
||||
assert.equal(payload.settingsState.flows.kiro.integrationTargetId, 'kiro-rs');
|
||||
assert.equal(
|
||||
payload.settingsState.flows.kiro.integrationTargets['kiro-rs'].baseUrl,
|
||||
'https://kiro.example.com/admin'
|
||||
);
|
||||
});
|
||||
|
||||
test('buildPersistentSettingsPayload accepts schema-only input when requireKnownKeys is enabled', () => {
|
||||
const api = buildHarness();
|
||||
|
||||
const payload = api.buildPersistentSettingsPayload({
|
||||
settingsSchemaVersion: 3,
|
||||
settingsSchemaVersion: 4,
|
||||
settingsState: {
|
||||
activeFlowId: 'kiro',
|
||||
services: {
|
||||
account: { customPassword: '' },
|
||||
email: { provider: '163' },
|
||||
proxy: { enabled: false, provider: '711proxy', mode: 'account' },
|
||||
},
|
||||
flows: {
|
||||
openai: {
|
||||
source: { selected: 'cpa', entries: {} },
|
||||
account: { customPassword: '' },
|
||||
integrationTargetId: 'cpa',
|
||||
integrationTargets: {
|
||||
cpa: {
|
||||
vpsUrl: '',
|
||||
vpsPassword: '',
|
||||
localCpaStep9Mode: 'submit',
|
||||
},
|
||||
sub2api: {
|
||||
sub2apiUrl: '',
|
||||
sub2apiEmail: '',
|
||||
sub2apiPassword: '',
|
||||
sub2apiGroupName: 'codex',
|
||||
sub2apiGroupNames: ['codex', 'openai-plus'],
|
||||
sub2apiAccountPriority: 1,
|
||||
sub2apiDefaultProxyName: '',
|
||||
},
|
||||
codex2api: {
|
||||
codex2apiUrl: '',
|
||||
codex2apiAdminKey: '',
|
||||
},
|
||||
},
|
||||
signup: {
|
||||
signupMethod: 'email',
|
||||
phoneVerificationEnabled: false,
|
||||
@@ -193,17 +256,13 @@ test('buildPersistentSettingsPayload accepts schema-only input when requireKnown
|
||||
},
|
||||
},
|
||||
kiro: {
|
||||
source: {
|
||||
selected: 'kiro-rs',
|
||||
entries: {
|
||||
'kiro-rs': {
|
||||
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||
kiroRsKey: 'schema-only-key',
|
||||
},
|
||||
integrationTargetId: 'kiro-rs',
|
||||
integrationTargets: {
|
||||
'kiro-rs': {
|
||||
baseUrl: 'https://kiro.example.com/admin',
|
||||
apiKey: 'schema-only-key',
|
||||
},
|
||||
},
|
||||
options: {
|
||||
},
|
||||
autoRun: {
|
||||
stepExecutionRange: { enabled: true, fromStep: 1, toStep: 7 },
|
||||
},
|
||||
@@ -217,7 +276,7 @@ test('buildPersistentSettingsPayload accepts schema-only input when requireKnown
|
||||
assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(payload.kiroRsKey, 'schema-only-key');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false);
|
||||
assert.equal(payload.settingsSchemaVersion, 3);
|
||||
assert.equal(payload.settingsSchemaVersion, 4);
|
||||
});
|
||||
|
||||
test('getPersistedSettings reads schema keys alongside legacy flat settings keys', async () => {
|
||||
@@ -242,7 +301,7 @@ function getRequestedKeys() {
|
||||
|
||||
assert.ok(api.getRequestedKeys().includes('settingsSchemaVersion'));
|
||||
assert.ok(api.getRequestedKeys().includes('settingsState'));
|
||||
assert.equal(state.settingsSchemaVersion, 3);
|
||||
assert.equal(state.settingsSchemaVersion, 4);
|
||||
assert.equal(state.settingsState.activeFlowId, 'openai');
|
||||
});
|
||||
|
||||
@@ -253,20 +312,37 @@ const chrome = {
|
||||
local: {
|
||||
async get() {
|
||||
return {
|
||||
settingsSchemaVersion: 3,
|
||||
settingsSchemaVersion: 4,
|
||||
settingsState: {
|
||||
activeFlowId: 'kiro',
|
||||
services: {
|
||||
account: { customPassword: '' },
|
||||
email: { provider: 'hotmail' },
|
||||
proxy: { enabled: true, provider: '711proxy', mode: 'account' },
|
||||
},
|
||||
flows: {
|
||||
openai: {
|
||||
source: {
|
||||
selected: 'sub2api',
|
||||
entries: {},
|
||||
integrationTargetId: 'sub2api',
|
||||
integrationTargets: {
|
||||
cpa: {
|
||||
vpsUrl: '',
|
||||
vpsPassword: '',
|
||||
localCpaStep9Mode: 'submit',
|
||||
},
|
||||
sub2api: {
|
||||
sub2apiUrl: '',
|
||||
sub2apiEmail: '',
|
||||
sub2apiPassword: '',
|
||||
sub2apiGroupName: 'codex',
|
||||
sub2apiGroupNames: ['codex', 'openai-plus'],
|
||||
sub2apiAccountPriority: 1,
|
||||
sub2apiDefaultProxyName: '',
|
||||
},
|
||||
codex2api: {
|
||||
codex2apiUrl: '',
|
||||
codex2apiAdminKey: '',
|
||||
},
|
||||
},
|
||||
account: { customPassword: '' },
|
||||
signup: {
|
||||
signupMethod: 'email',
|
||||
phoneVerificationEnabled: false,
|
||||
@@ -281,17 +357,13 @@ const chrome = {
|
||||
},
|
||||
},
|
||||
kiro: {
|
||||
source: {
|
||||
selected: 'kiro-rs',
|
||||
entries: {
|
||||
'kiro-rs': {
|
||||
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||
kiroRsKey: 'stored-key',
|
||||
},
|
||||
integrationTargetId: 'kiro-rs',
|
||||
integrationTargets: {
|
||||
'kiro-rs': {
|
||||
baseUrl: 'https://kiro.example.com/admin',
|
||||
apiKey: 'stored-key',
|
||||
},
|
||||
},
|
||||
options: {
|
||||
},
|
||||
autoRun: {
|
||||
stepExecutionRange: { enabled: true, fromStep: 1, toStep: 7 },
|
||||
},
|
||||
@@ -324,12 +396,16 @@ const chrome = {
|
||||
test('setPersistentSettings materializes canonical schema keys for schema-only updates', async () => {
|
||||
const api = buildHarness(`
|
||||
const persistedWrites = [];
|
||||
const removedKeys = [];
|
||||
const chrome = {
|
||||
storage: {
|
||||
local: {
|
||||
async get() {
|
||||
return {};
|
||||
},
|
||||
async remove(keys) {
|
||||
removedKeys.push(...(Array.isArray(keys) ? keys : [keys]));
|
||||
},
|
||||
async set(payload) {
|
||||
persistedWrites.push(JSON.parse(JSON.stringify(payload)));
|
||||
},
|
||||
@@ -339,20 +415,43 @@ const chrome = {
|
||||
function getPersistedWrites() {
|
||||
return persistedWrites;
|
||||
}
|
||||
function getRemovedKeys() {
|
||||
return removedKeys;
|
||||
}
|
||||
`);
|
||||
|
||||
const persisted = await api.setPersistentSettings({
|
||||
settingsSchemaVersion: 3,
|
||||
settingsSchemaVersion: 4,
|
||||
settingsState: {
|
||||
activeFlowId: 'kiro',
|
||||
services: {
|
||||
account: { customPassword: '' },
|
||||
email: { provider: '163' },
|
||||
proxy: { enabled: false, provider: '711proxy', mode: 'account' },
|
||||
},
|
||||
flows: {
|
||||
openai: {
|
||||
source: { selected: 'cpa', entries: {} },
|
||||
account: { customPassword: '' },
|
||||
integrationTargetId: 'cpa',
|
||||
integrationTargets: {
|
||||
cpa: {
|
||||
vpsUrl: '',
|
||||
vpsPassword: '',
|
||||
localCpaStep9Mode: 'submit',
|
||||
},
|
||||
sub2api: {
|
||||
sub2apiUrl: '',
|
||||
sub2apiEmail: '',
|
||||
sub2apiPassword: '',
|
||||
sub2apiGroupName: 'codex',
|
||||
sub2apiGroupNames: ['codex', 'openai-plus'],
|
||||
sub2apiAccountPriority: 1,
|
||||
sub2apiDefaultProxyName: '',
|
||||
},
|
||||
codex2api: {
|
||||
codex2apiUrl: '',
|
||||
codex2apiAdminKey: '',
|
||||
},
|
||||
},
|
||||
signup: {
|
||||
signupMethod: 'email',
|
||||
phoneVerificationEnabled: false,
|
||||
@@ -367,17 +466,13 @@ function getPersistedWrites() {
|
||||
},
|
||||
},
|
||||
kiro: {
|
||||
source: {
|
||||
selected: 'kiro-rs',
|
||||
entries: {
|
||||
'kiro-rs': {
|
||||
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||
kiroRsKey: 'nested-only-key',
|
||||
},
|
||||
integrationTargetId: 'kiro-rs',
|
||||
integrationTargets: {
|
||||
'kiro-rs': {
|
||||
baseUrl: 'https://kiro.example.com/admin',
|
||||
apiKey: 'nested-only-key',
|
||||
},
|
||||
},
|
||||
options: {
|
||||
},
|
||||
autoRun: {
|
||||
stepExecutionRange: { enabled: true, fromStep: 1, toStep: 7 },
|
||||
},
|
||||
@@ -392,11 +487,14 @@ function getPersistedWrites() {
|
||||
assert.equal(persisted.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(persisted.kiroRsKey, 'nested-only-key');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(persisted, 'kiroRegion'), false);
|
||||
assert.equal(persisted.settingsSchemaVersion, 3);
|
||||
assert.equal(write.activeFlowId, 'kiro');
|
||||
assert.equal(write.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(write.kiroRsKey, 'nested-only-key');
|
||||
assert.equal(persisted.settingsSchemaVersion, 4);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(write, 'activeFlowId'), false);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(write, 'kiroRsUrl'), false);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(write, 'kiroRsKey'), false);
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(write, 'kiroRegion'), false);
|
||||
assert.equal(write.settingsSchemaVersion, 3);
|
||||
assert.equal(write.settingsSchemaVersion, 4);
|
||||
assert.equal(write.settingsState.activeFlowId, 'kiro');
|
||||
assert.equal(write.settingsState.flows.kiro.integrationTargetId, 'kiro-rs');
|
||||
assert.ok(api.getRemovedKeys().includes('panelMode'));
|
||||
assert.ok(api.getRemovedKeys().includes('kiroRsUrl'));
|
||||
});
|
||||
|
||||
@@ -8,9 +8,8 @@ test('background node registry preserves node metadata even before an executor i
|
||||
const registry = api.createNodeRegistry([
|
||||
{
|
||||
flowId: 'kiro',
|
||||
legacyStepId: 1,
|
||||
nodeId: 'kiro-start-device-login',
|
||||
displayOrder: 10,
|
||||
displayOrder: 1,
|
||||
executeKey: 'kiro-start-device-login',
|
||||
title: 'Start device login',
|
||||
},
|
||||
@@ -19,7 +18,7 @@ test('background node registry preserves node metadata even before an executor i
|
||||
const node = registry.getNodeDefinition('kiro-start-device-login');
|
||||
|
||||
assert.equal(node.flowId, 'kiro');
|
||||
assert.equal(node.legacyStepId, 1);
|
||||
assert.equal(node.displayOrder, 1);
|
||||
assert.equal(node.title, 'Start device login');
|
||||
assert.throws(
|
||||
() => registry.executeNode('kiro-start-device-login', {}),
|
||||
@@ -34,9 +33,8 @@ test('background node registry executes registered nodes in display order', asyn
|
||||
const registry = api.createNodeRegistry([
|
||||
{
|
||||
flowId: 'openai',
|
||||
legacyStepId: 2,
|
||||
displayOrder: 2,
|
||||
nodeId: 'submit-signup-email',
|
||||
displayOrder: 20,
|
||||
executeKey: 'submit-signup-email',
|
||||
title: 'Submit signup email',
|
||||
execute: async (state) => {
|
||||
@@ -45,9 +43,8 @@ test('background node registry executes registered nodes in display order', asyn
|
||||
},
|
||||
{
|
||||
flowId: 'openai',
|
||||
legacyStepId: 1,
|
||||
displayOrder: 1,
|
||||
nodeId: 'open-chatgpt',
|
||||
displayOrder: 10,
|
||||
executeKey: 'open-chatgpt',
|
||||
title: 'Open ChatGPT',
|
||||
},
|
||||
|
||||
@@ -21,7 +21,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run
|
||||
const enabledState = registry.resolveSidepanelCapabilities({
|
||||
state: {
|
||||
activeFlowId: 'openai',
|
||||
panelMode: 'cpa',
|
||||
openaiIntegrationTargetId: 'cpa',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: false,
|
||||
contributionMode: false,
|
||||
@@ -37,7 +37,7 @@ test('flow capability registry keeps OpenAI phone signup available only when run
|
||||
const plusLockedState = registry.resolveSidepanelCapabilities({
|
||||
state: {
|
||||
activeFlowId: 'openai',
|
||||
panelMode: 'sub2api',
|
||||
openaiIntegrationTargetId: 'sub2api',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: true,
|
||||
contributionMode: false,
|
||||
@@ -58,7 +58,7 @@ test('flow capability registry defaults unknown flows to minimal non-phone capab
|
||||
const capabilityState = registry.resolveSidepanelCapabilities({
|
||||
state: {
|
||||
activeFlowId: 'site-a',
|
||||
panelMode: 'codex2api',
|
||||
openaiIntegrationTargetId: 'codex2api',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: true,
|
||||
contributionMode: true,
|
||||
@@ -73,7 +73,7 @@ test('flow capability registry defaults unknown flows to minimal non-phone capab
|
||||
assert.equal(capabilityState.canUsePhoneSignup, false);
|
||||
assert.equal(capabilityState.effectiveSignupMethod, 'email');
|
||||
assert.equal(capabilityState.panelMode, 'codex2api');
|
||||
assert.deepEqual(capabilityState.supportedPanelModes, []);
|
||||
assert.deepEqual(capabilityState.supportedIntegrationTargets, []);
|
||||
});
|
||||
|
||||
test('flow capability registry exposes Kiro as an independent flow with its own visible groups', () => {
|
||||
@@ -83,8 +83,8 @@ test('flow capability registry exposes Kiro as an independent flow with its own
|
||||
const capabilityState = registry.resolveSidepanelCapabilities({
|
||||
state: {
|
||||
activeFlowId: 'kiro',
|
||||
kiroSourceId: 'kiro-rs',
|
||||
panelMode: 'sub2api',
|
||||
kiroIntegrationTargetId: 'kiro-rs',
|
||||
openaiIntegrationTargetId: 'sub2api',
|
||||
signupMethod: 'phone',
|
||||
plusModeEnabled: true,
|
||||
phoneVerificationEnabled: true,
|
||||
@@ -95,21 +95,21 @@ test('flow capability registry exposes Kiro as an independent flow with its own
|
||||
assert.equal(capabilityState.canShowPhoneSettings, false);
|
||||
assert.equal(capabilityState.canShowPlusSettings, false);
|
||||
assert.equal(capabilityState.effectiveSignupMethod, 'email');
|
||||
assert.equal(capabilityState.effectiveSourceId, 'kiro-rs');
|
||||
assert.equal(capabilityState.effectiveIntegrationTargetId, 'kiro-rs');
|
||||
assert.deepEqual(
|
||||
capabilityState.visibleGroupIds,
|
||||
['kiro-runtime-status', 'kiro-source-kiro-rs', 'service-account', 'service-email', 'service-proxy']
|
||||
['kiro-runtime-status', 'kiro-target-kiro-rs', 'service-account', 'service-email', 'service-proxy']
|
||||
);
|
||||
});
|
||||
|
||||
test('flow capability registry exposes shared auto-run validation for phone locks and panel support', () => {
|
||||
test('flow capability registry exposes shared auto-run validation for phone locks and target support', () => {
|
||||
const api = loadApi();
|
||||
const registry = api.createFlowCapabilityRegistry({
|
||||
flowCapabilities: {
|
||||
openai: api.FLOW_CAPABILITIES.openai,
|
||||
'site-a': {
|
||||
...api.DEFAULT_FLOW_CAPABILITIES,
|
||||
supportsPlatformBinding: ['cpa'],
|
||||
supportedIntegrationTargets: ['cpa'],
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -117,7 +117,7 @@ test('flow capability registry exposes shared auto-run validation for phone lock
|
||||
const plusLockedResult = registry.validateAutoRunStart({
|
||||
state: {
|
||||
activeFlowId: 'openai',
|
||||
panelMode: 'cpa',
|
||||
openaiIntegrationTargetId: 'cpa',
|
||||
signupMethod: 'phone',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: true,
|
||||
@@ -131,7 +131,7 @@ test('flow capability registry exposes shared auto-run validation for phone lock
|
||||
const unsupportedPanelResult = registry.validateAutoRunStart({
|
||||
state: {
|
||||
activeFlowId: 'site-a',
|
||||
panelMode: 'sub2api',
|
||||
openaiIntegrationTargetId: 'sub2api',
|
||||
signupMethod: 'email',
|
||||
},
|
||||
});
|
||||
@@ -147,7 +147,7 @@ test('flow capability registry normalizes unsupported mode switches back to the
|
||||
openai: api.FLOW_CAPABILITIES.openai,
|
||||
'site-a': {
|
||||
...api.DEFAULT_FLOW_CAPABILITIES,
|
||||
supportsPlatformBinding: ['cpa'],
|
||||
supportedIntegrationTargets: ['cpa'],
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -155,14 +155,14 @@ test('flow capability registry normalizes unsupported mode switches back to the
|
||||
const validation = registry.validateModeSwitch({
|
||||
state: {
|
||||
activeFlowId: 'site-a',
|
||||
panelMode: 'sub2api',
|
||||
openaiIntegrationTargetId: 'sub2api',
|
||||
signupMethod: 'phone',
|
||||
phoneVerificationEnabled: true,
|
||||
plusModeEnabled: true,
|
||||
contributionMode: true,
|
||||
},
|
||||
changedKeys: [
|
||||
'panelMode',
|
||||
'openaiIntegrationTargetId',
|
||||
'signupMethod',
|
||||
'phoneVerificationEnabled',
|
||||
'plusModeEnabled',
|
||||
@@ -173,6 +173,9 @@ test('flow capability registry normalizes unsupported mode switches back to the
|
||||
assert.equal(validation.ok, false);
|
||||
assert.deepEqual(validation.normalizedUpdates, {
|
||||
panelMode: 'cpa',
|
||||
openaiIntegrationTargetId: 'cpa',
|
||||
kiroIntegrationTargetId: 'cpa',
|
||||
kiroSourceId: 'cpa',
|
||||
signupMethod: 'email',
|
||||
phoneVerificationEnabled: false,
|
||||
plusModeEnabled: false,
|
||||
|
||||
@@ -13,37 +13,31 @@ function loadApis() {
|
||||
};`)(scope);
|
||||
}
|
||||
|
||||
test('flow registry exposes openai and kiro with canonical source metadata', () => {
|
||||
test('flow registry exposes canonical flow and integration target metadata', () => {
|
||||
const { flowRegistry } = loadApis();
|
||||
|
||||
assert.deepEqual(flowRegistry.getRegisteredFlowIds(), ['openai', 'kiro']);
|
||||
assert.equal(flowRegistry.getFlowLabel('codex'), 'Codex / OpenAI');
|
||||
assert.equal(flowRegistry.normalizeFlowId('codex'), 'openai');
|
||||
assert.equal(flowRegistry.normalizeSourceId('openai', 'sub2api'), 'sub2api');
|
||||
assert.equal(flowRegistry.normalizeSourceId('kiro', 'anything-else'), 'kiro-rs');
|
||||
assert.equal(flowRegistry.normalizeFlowId('kiro'), 'kiro');
|
||||
assert.equal(flowRegistry.normalizeFlowId('unknown'), 'openai');
|
||||
assert.equal(flowRegistry.getFlowLabel('openai'), 'Codex / OpenAI');
|
||||
assert.equal(flowRegistry.normalizeIntegrationTargetId('openai', 'sub2api'), 'sub2api');
|
||||
assert.equal(flowRegistry.normalizeIntegrationTargetId('kiro', 'anything-else'), 'kiro-rs');
|
||||
assert.deepEqual(
|
||||
flowRegistry.getVisibleGroupIds('openai', 'cpa'),
|
||||
['openai-plus', 'openai-phone', 'openai-oauth', 'openai-step6', 'openai-source-cpa', 'service-account', 'service-email', 'service-proxy']
|
||||
['openai-plus', 'openai-phone', 'openai-oauth', 'openai-step6', 'openai-target-cpa', 'service-account', 'service-email', 'service-proxy']
|
||||
);
|
||||
assert.deepEqual(
|
||||
flowRegistry.getVisibleGroupIds('kiro', 'kiro-rs'),
|
||||
['kiro-runtime-status', 'kiro-source-kiro-rs', 'service-account', 'service-email', 'service-proxy']
|
||||
['kiro-runtime-status', 'kiro-target-kiro-rs', 'service-account', 'service-email', 'service-proxy']
|
||||
);
|
||||
assert.deepEqual(
|
||||
flowRegistry.getSettingsGroupDefinition('openai-plus')?.rowIds || [],
|
||||
['row-plus-mode']
|
||||
);
|
||||
assert.deepEqual(
|
||||
flowRegistry.getSettingsGroupDefinition('openai-phone')?.rowIds || [],
|
||||
[]
|
||||
);
|
||||
assert.deepEqual(
|
||||
flowRegistry.getSettingsGroupDefinition('openai-step6')?.rowIds || [],
|
||||
['row-step6-cookie-settings']
|
||||
flowRegistry.getIntegrationTargetOptions('openai').map((entry) => entry.id),
|
||||
['cpa', 'sub2api', 'codex2api']
|
||||
);
|
||||
assert.equal(flowRegistry.getPublicationTargetDefinition('kiro', 'kiro-rs')?.label, 'kiro.rs');
|
||||
});
|
||||
|
||||
test('settings schema normalizes flat input into canonical flow and service namespaces', () => {
|
||||
test('settings schema normalizes view input into canonical nested namespaces', () => {
|
||||
const { settingsSchema } = loadApis();
|
||||
const schema = settingsSchema.createSettingsSchema();
|
||||
|
||||
@@ -66,10 +60,10 @@ test('settings schema normalizes flat input into canonical flow and service name
|
||||
assert.equal(normalized.services.email.provider, 'hotmail');
|
||||
assert.equal(normalized.services.proxy.enabled, true);
|
||||
assert.equal(normalized.services.account.customPassword, 'SharedSecret123!');
|
||||
assert.equal(normalized.flows.openai.source.selected, 'sub2api');
|
||||
assert.equal(normalized.flows.kiro.source.selected, 'kiro-rs');
|
||||
assert.equal(normalized.flows.kiro.source.entries['kiro-rs'].kiroRsUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(normalized.flows.kiro.source.entries['kiro-rs'].kiroRsKey, 'secret-key');
|
||||
assert.equal(normalized.flows.openai.integrationTargetId, 'sub2api');
|
||||
assert.equal(normalized.flows.kiro.integrationTargetId, 'kiro-rs');
|
||||
assert.equal(normalized.flows.kiro.integrationTargets['kiro-rs'].baseUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(normalized.flows.kiro.integrationTargets['kiro-rs'].apiKey, 'secret-key');
|
||||
assert.deepEqual(normalized.flows.kiro.autoRun.stepExecutionRange, {
|
||||
enabled: true,
|
||||
fromStep: 1,
|
||||
@@ -77,22 +71,24 @@ test('settings schema normalizes flat input into canonical flow and service name
|
||||
});
|
||||
});
|
||||
|
||||
test('settings schema can project canonical state back to legacy payload without losing flow selection', () => {
|
||||
test('settings schema can project canonical state into a read view without legacy rebuild helpers', () => {
|
||||
const { settingsSchema } = loadApis();
|
||||
const schema = settingsSchema.createSettingsSchema();
|
||||
const payload = schema.buildLegacySettingsPayload(schema.normalizeSettingsState({
|
||||
const normalized = schema.normalizeSettingsState({
|
||||
activeFlowId: 'kiro',
|
||||
kiroSourceId: 'kiro-rs',
|
||||
kiroIntegrationTargetId: 'kiro-rs',
|
||||
kiroRsUrl: 'https://kiro.example.com/admin',
|
||||
kiroRsKey: 'key-123',
|
||||
}));
|
||||
});
|
||||
const view = schema.buildSettingsView(normalized);
|
||||
|
||||
assert.equal(payload.activeFlowId, 'kiro');
|
||||
assert.equal(payload.panelMode, 'cpa');
|
||||
assert.equal(payload.kiroSourceId, 'kiro-rs');
|
||||
assert.equal(payload.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(payload.kiroRsKey, 'key-123');
|
||||
assert.equal(Object.prototype.hasOwnProperty.call(payload, 'kiroRegion'), false);
|
||||
assert.equal(payload.settingsSchemaVersion, 3);
|
||||
assert.equal(payload.settingsState.activeFlowId, 'kiro');
|
||||
assert.equal(view.activeFlowId, 'kiro');
|
||||
assert.equal(view.openaiIntegrationTargetId, 'cpa');
|
||||
assert.equal(view.kiroIntegrationTargetId, 'kiro-rs');
|
||||
assert.equal(view.panelMode, 'cpa');
|
||||
assert.equal(view.kiroSourceId, 'kiro-rs');
|
||||
assert.equal(view.kiroRsUrl, 'https://kiro.example.com/admin');
|
||||
assert.equal(view.kiroRsKey, 'key-123');
|
||||
assert.equal(view.settingsSchemaVersion, 4);
|
||||
assert.equal(view.settingsState.activeFlowId, 'kiro');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user