feat: upgrade multi-flow account contributions
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
(function attachMultiPageContributionRegistry(root, factory) {
|
||||
root.MultiPageContributionRegistry = factory(root);
|
||||
})(typeof self !== 'undefined' ? self : globalThis, function createContributionRegistryModule(root) {
|
||||
const flowRegistryApi = root?.MultiPageFlowRegistry || {};
|
||||
const DEFAULT_FLOW_ID = flowRegistryApi.DEFAULT_FLOW_ID || 'openai';
|
||||
const DEFAULT_KIRO_TARGET_ID = flowRegistryApi.DEFAULT_KIRO_TARGET_ID || 'kiro-rs';
|
||||
const DEFAULT_OPENAI_TARGET_ID = flowRegistryApi.DEFAULT_OPENAI_TARGET_ID || 'cpa';
|
||||
|
||||
const ADAPTER_DEFINITIONS = Object.freeze({
|
||||
'openai-oauth': Object.freeze({
|
||||
id: 'openai-oauth',
|
||||
flowId: 'openai',
|
||||
artifactKind: 'openai-oauth',
|
||||
trigger: 'interactive-oauth',
|
||||
label: 'OpenAI OAuth 贡献',
|
||||
defaultTargetId: DEFAULT_OPENAI_TARGET_ID,
|
||||
sensitiveFieldPaths: Object.freeze([
|
||||
'credentials.access_token',
|
||||
'credentials.refresh_token',
|
||||
'credentials.id_token',
|
||||
]),
|
||||
}),
|
||||
'openai-codex-file': Object.freeze({
|
||||
id: 'openai-codex-file',
|
||||
flowId: 'openai',
|
||||
artifactKind: 'codex',
|
||||
trigger: 'manual-upload',
|
||||
label: 'OpenAI Codex 文件贡献',
|
||||
defaultTargetId: 'codex2api',
|
||||
sensitiveFieldPaths: Object.freeze([
|
||||
'credentials.access_token',
|
||||
'credentials.refresh_token',
|
||||
'credentials.id_token',
|
||||
]),
|
||||
}),
|
||||
'openai-sub2api-file': Object.freeze({
|
||||
id: 'openai-sub2api-file',
|
||||
flowId: 'openai',
|
||||
artifactKind: 'sub2api',
|
||||
trigger: 'manual-upload',
|
||||
label: 'OpenAI Sub2API 文件贡献',
|
||||
defaultTargetId: 'sub2api',
|
||||
sensitiveFieldPaths: Object.freeze([
|
||||
'credentials.accounts[].credentials.access_token',
|
||||
'credentials.accounts[].credentials.refresh_token',
|
||||
'credentials.accounts[].credentials.id_token',
|
||||
]),
|
||||
}),
|
||||
'kiro-builder-id': Object.freeze({
|
||||
id: 'kiro-builder-id',
|
||||
flowId: 'kiro',
|
||||
artifactKind: 'kiro-builder-id',
|
||||
trigger: 'after-desktop-authorize',
|
||||
label: 'Kiro Builder ID 贡献',
|
||||
defaultTargetId: DEFAULT_KIRO_TARGET_ID,
|
||||
sensitiveFieldPaths: Object.freeze([
|
||||
'credentials.refreshToken',
|
||||
'credentials.clientSecret',
|
||||
'metadata.proxyPassword',
|
||||
]),
|
||||
}),
|
||||
});
|
||||
|
||||
const FLOW_ADAPTER_IDS = Object.freeze({
|
||||
openai: Object.freeze(['openai-oauth', 'openai-codex-file', 'openai-sub2api-file']),
|
||||
kiro: Object.freeze(['kiro-builder-id']),
|
||||
});
|
||||
|
||||
const CONTRIBUTION_TUTORIAL_ENTRIES = Object.freeze({
|
||||
openai: Object.freeze({
|
||||
id: 'openai-contribution-tutorial',
|
||||
flowId: 'openai',
|
||||
label: '贡献/使用教程',
|
||||
portalPath: '/tutorial',
|
||||
defaultTargetId: DEFAULT_OPENAI_TARGET_ID,
|
||||
contributionAdapterId: 'openai-oauth',
|
||||
action: 'open-portal-and-enable-contribution',
|
||||
}),
|
||||
kiro: Object.freeze({
|
||||
id: 'kiro-contribution-tutorial',
|
||||
flowId: 'kiro',
|
||||
label: '贡献/使用教程',
|
||||
portalPath: '/tutorial',
|
||||
defaultTargetId: DEFAULT_KIRO_TARGET_ID,
|
||||
contributionAdapterId: 'kiro-builder-id',
|
||||
action: 'open-portal-and-enable-contribution',
|
||||
}),
|
||||
});
|
||||
|
||||
function normalizeString(value = '') {
|
||||
return String(value || '').trim();
|
||||
}
|
||||
|
||||
function normalizeFlowId(value = '', fallback = DEFAULT_FLOW_ID) {
|
||||
const normalized = normalizeString(value).toLowerCase();
|
||||
if (normalized && Object.prototype.hasOwnProperty.call(FLOW_ADAPTER_IDS, normalized)) {
|
||||
return normalized;
|
||||
}
|
||||
if (!normalized && typeof flowRegistryApi.normalizeFlowId === 'function') {
|
||||
return flowRegistryApi.normalizeFlowId(value, fallback);
|
||||
}
|
||||
return normalized || normalizeString(fallback).toLowerCase() || DEFAULT_FLOW_ID;
|
||||
}
|
||||
|
||||
function normalizeAdapterId(value = '') {
|
||||
return normalizeString(value).toLowerCase();
|
||||
}
|
||||
|
||||
function normalizeTargetId(flowId = DEFAULT_FLOW_ID, targetId = '') {
|
||||
if (typeof flowRegistryApi.normalizeTargetId === 'function') {
|
||||
return flowRegistryApi.normalizeTargetId(flowId, targetId);
|
||||
}
|
||||
const normalizedFlowId = normalizeFlowId(flowId, DEFAULT_FLOW_ID);
|
||||
const normalizedTargetId = normalizeString(targetId).toLowerCase();
|
||||
if (normalizedTargetId) {
|
||||
return normalizedTargetId;
|
||||
}
|
||||
return normalizedFlowId === 'kiro' ? DEFAULT_KIRO_TARGET_ID : DEFAULT_OPENAI_TARGET_ID;
|
||||
}
|
||||
|
||||
function cloneAdapter(adapter) {
|
||||
if (!adapter) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
...adapter,
|
||||
sensitiveFieldPaths: Array.isArray(adapter.sensitiveFieldPaths)
|
||||
? adapter.sensitiveFieldPaths.slice()
|
||||
: [],
|
||||
};
|
||||
}
|
||||
|
||||
function getAdapterDefinition(adapterId = '') {
|
||||
return cloneAdapter(ADAPTER_DEFINITIONS[normalizeAdapterId(adapterId)] || null);
|
||||
}
|
||||
|
||||
function getContributionAdapterIds(flowId = DEFAULT_FLOW_ID) {
|
||||
const normalizedFlowId = normalizeFlowId(flowId, DEFAULT_FLOW_ID);
|
||||
return (FLOW_ADAPTER_IDS[normalizedFlowId] || []).slice();
|
||||
}
|
||||
|
||||
function getContributionAdapters(flowId = DEFAULT_FLOW_ID) {
|
||||
return getContributionAdapterIds(flowId)
|
||||
.map((adapterId) => getAdapterDefinition(adapterId))
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function getDefaultContributionAdapterId(flowId = DEFAULT_FLOW_ID) {
|
||||
return getContributionAdapterIds(flowId)[0] || '';
|
||||
}
|
||||
|
||||
function buildPortalPageUrl(portalBaseUrl = '', portalPath = '/tutorial', params = {}) {
|
||||
const baseUrl = normalizeString(portalBaseUrl).replace(/\/+$/, '') || 'https://flowpilot.qlhazycoder.top';
|
||||
const path = normalizeString(portalPath) || '/tutorial';
|
||||
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
||||
const query = Object.entries(params)
|
||||
.map(([key, value]) => [normalizeString(key), normalizeString(value)])
|
||||
.filter(([key, value]) => key && value)
|
||||
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
||||
.join('&');
|
||||
return `${baseUrl}${normalizedPath}${query ? `?${query}` : ''}`;
|
||||
}
|
||||
|
||||
function getContributionTutorialEntry(flowId = DEFAULT_FLOW_ID, options = {}) {
|
||||
const normalizedFlowId = normalizeFlowId(flowId, DEFAULT_FLOW_ID);
|
||||
const definition = CONTRIBUTION_TUTORIAL_ENTRIES[normalizedFlowId] || null;
|
||||
if (!definition) {
|
||||
return null;
|
||||
}
|
||||
const requestedAdapterId = normalizeAdapterId(options.adapterId);
|
||||
const adapterId = requestedAdapterId && hasContributionAdapter(normalizedFlowId, requestedAdapterId)
|
||||
? requestedAdapterId
|
||||
: definition.contributionAdapterId;
|
||||
const targetId = normalizeTargetId(
|
||||
normalizedFlowId,
|
||||
options.targetId || definition.defaultTargetId
|
||||
);
|
||||
return {
|
||||
...definition,
|
||||
targetId,
|
||||
contributionAdapterId: adapterId,
|
||||
portalUrl: buildPortalPageUrl(options.portalBaseUrl, definition.portalPath, {
|
||||
flow: normalizedFlowId,
|
||||
target: targetId,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function hasContributionAdapter(flowId = DEFAULT_FLOW_ID, adapterId = '') {
|
||||
const normalizedAdapterId = normalizeAdapterId(adapterId);
|
||||
return Boolean(normalizedAdapterId) && getContributionAdapterIds(flowId).includes(normalizedAdapterId);
|
||||
}
|
||||
|
||||
function assertPublishedFlowsHaveContributionAdapters(flowIds = undefined) {
|
||||
const ids = Array.isArray(flowIds)
|
||||
? flowIds
|
||||
: (typeof flowRegistryApi.getRegisteredFlowIds === 'function'
|
||||
? flowRegistryApi.getRegisteredFlowIds()
|
||||
: Object.keys(FLOW_ADAPTER_IDS));
|
||||
const missing = ids
|
||||
.map((flowId) => normalizeString(flowId).toLowerCase())
|
||||
.filter(Boolean)
|
||||
.filter((flowId) => getContributionAdapterIds(flowId).length === 0);
|
||||
if (missing.length) {
|
||||
throw new Error(`缺少账号贡献适配器:${missing.join(', ')}`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return {
|
||||
ADAPTER_DEFINITIONS,
|
||||
CONTRIBUTION_TUTORIAL_ENTRIES,
|
||||
FLOW_ADAPTER_IDS,
|
||||
assertPublishedFlowsHaveContributionAdapters,
|
||||
getContributionTutorialEntry,
|
||||
getAdapterDefinition,
|
||||
getContributionAdapterIds,
|
||||
getContributionAdapters,
|
||||
getDefaultContributionAdapterId,
|
||||
hasContributionAdapter,
|
||||
normalizeAdapterId,
|
||||
normalizeFlowId,
|
||||
};
|
||||
});
|
||||
@@ -3,6 +3,7 @@
|
||||
})(typeof self !== 'undefined' ? self : globalThis, function createFlowCapabilitiesModule() {
|
||||
const rootScope = typeof self !== 'undefined' ? self : globalThis;
|
||||
const flowRegistryApi = rootScope.MultiPageFlowRegistry || {};
|
||||
const contributionRegistryApi = rootScope.MultiPageContributionRegistry || {};
|
||||
const settingsSchemaApi = rootScope.MultiPageSettingsSchema || {};
|
||||
const DEFAULT_FLOW_ID = flowRegistryApi.DEFAULT_FLOW_ID || 'openai';
|
||||
const DEFAULT_OPENAI_TARGET_ID = flowRegistryApi.DEFAULT_OPENAI_TARGET_ID || 'cpa';
|
||||
@@ -25,6 +26,9 @@
|
||||
supportsPhoneVerificationSettings: false,
|
||||
supportsPlusMode: false,
|
||||
supportsContributionMode: false,
|
||||
supportsAccountContribution: false,
|
||||
supportsOpenAiOAuthContribution: false,
|
||||
contributionAdapterIds: [],
|
||||
supportedTargetIds: [],
|
||||
supportsLuckmail: false,
|
||||
supportsOauthTimeoutBudget: false,
|
||||
@@ -58,7 +62,7 @@
|
||||
|
||||
const MODE_SWITCH_RELEVANT_KEYS = Object.freeze([
|
||||
'activeFlowId',
|
||||
'contributionMode',
|
||||
'accountContributionEnabled',
|
||||
'panelMode',
|
||||
'phoneVerificationEnabled',
|
||||
'plusModeEnabled',
|
||||
@@ -195,6 +199,14 @@
|
||||
function getFlowCapabilities(flowId) {
|
||||
const normalizedFlowId = normalizeCapabilityFlowId(flowId, defaultFlowId);
|
||||
const entry = flowCapabilities[normalizedFlowId] || null;
|
||||
const registryAdapterIds = typeof contributionRegistryApi.getContributionAdapterIds === 'function'
|
||||
? contributionRegistryApi.getContributionAdapterIds(normalizedFlowId)
|
||||
: [];
|
||||
const contributionAdapterIds = registryAdapterIds.length
|
||||
? registryAdapterIds
|
||||
: (Array.isArray(entry?.contributionAdapterIds)
|
||||
? entry.contributionAdapterIds.map((value) => String(value || '').trim()).filter(Boolean)
|
||||
: []);
|
||||
const supportedTargetIds = normalizedFlowId === 'openai'
|
||||
? normalizeOpenAiTargetList(
|
||||
entry?.supportedTargetIds || defaultFlowCapabilities.supportedTargetIds
|
||||
@@ -206,6 +218,8 @@
|
||||
...defaultFlowCapabilities,
|
||||
...(entry || {}),
|
||||
supportedTargetIds,
|
||||
contributionAdapterIds,
|
||||
supportsAccountContribution: Boolean(entry?.supportsAccountContribution || contributionAdapterIds.length > 0),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -332,7 +346,7 @@
|
||||
: defaultTargetCapabilities;
|
||||
const runtimeLocks = {
|
||||
autoRunLocked: Boolean(options?.autoRunLocked ?? state?.autoRunLocked),
|
||||
contributionMode: activeFlowId === 'openai' && flowState.supportsContributionMode && Boolean(state?.contributionMode),
|
||||
accountContribution: Boolean(flowState.supportsAccountContribution) && Boolean(state?.accountContributionEnabled),
|
||||
phoneVerificationEnabled: activeFlowId === 'openai' && flowState.supportsPhoneVerificationSettings && Boolean(state?.phoneVerificationEnabled),
|
||||
plusModeEnabled: activeFlowId === 'openai' && flowState.supportsPlusMode && Boolean(state?.plusModeEnabled),
|
||||
settingsMenuLocked: Boolean(options?.settingsMenuLocked ?? state?.settingsMenuLocked),
|
||||
@@ -346,7 +360,7 @@
|
||||
&& Boolean(targetState.supportsPhoneSignup)
|
||||
&& runtimeLocks.phoneVerificationEnabled
|
||||
&& !runtimeLocks.plusModeEnabled
|
||||
&& !runtimeLocks.contributionMode;
|
||||
&& !runtimeLocks.accountContribution;
|
||||
if (canSelectPhoneSignup) {
|
||||
effectiveSignupMethods.push(SIGNUP_METHOD_PHONE);
|
||||
}
|
||||
@@ -390,7 +404,7 @@
|
||||
|
||||
return {
|
||||
activeFlowId,
|
||||
canShowContributionMode: activeFlowId === 'openai' && Boolean(flowState.supportsContributionMode),
|
||||
canShowContributionMode: Boolean(flowState.supportsAccountContribution),
|
||||
canShowLuckmail: activeFlowId === 'openai' && Boolean(flowState.supportsLuckmail),
|
||||
canShowPhoneSettings: activeFlowId === 'openai' && Boolean(flowState.supportsPhoneVerificationSettings),
|
||||
canShowPlusSettings: activeFlowId === 'openai' && Boolean(flowState.supportsPlusMode),
|
||||
@@ -459,7 +473,7 @@
|
||||
message: 'Plus 模式开启时不能使用手机号注册。',
|
||||
};
|
||||
}
|
||||
if (runtimeLocks.contributionMode) {
|
||||
if (runtimeLocks.accountContribution) {
|
||||
return {
|
||||
code: 'phone_signup_contribution_mode_locked',
|
||||
message: '贡献模式开启时不能使用手机号注册。',
|
||||
@@ -494,7 +508,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
if (Boolean(state?.contributionMode) && !capabilityState.flowCapabilities?.supportsContributionMode) {
|
||||
if (Boolean(state?.accountContributionEnabled) && !capabilityState.flowCapabilities?.supportsAccountContribution) {
|
||||
errors.push({
|
||||
code: 'contribution_mode_unsupported',
|
||||
message: '当前 flow 不支持贡献模式。',
|
||||
@@ -553,8 +567,12 @@
|
||||
});
|
||||
}
|
||||
|
||||
if (changedKeySet.has('contributionMode') && Boolean(state?.contributionMode) && !flowState.supportsContributionMode) {
|
||||
normalizedUpdates.contributionMode = false;
|
||||
if (
|
||||
changedKeySet.has('accountContributionEnabled')
|
||||
&& Boolean(state?.accountContributionEnabled)
|
||||
&& !flowState.supportsAccountContribution
|
||||
) {
|
||||
normalizedUpdates.accountContributionEnabled = false;
|
||||
errors.push({
|
||||
code: 'contribution_mode_unsupported',
|
||||
message: '当前 flow 不支持贡献模式。',
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
supportsPhoneVerificationSettings: false,
|
||||
supportsPlusMode: false,
|
||||
supportsContributionMode: false,
|
||||
supportsAccountContribution: false,
|
||||
supportsOpenAiOAuthContribution: false,
|
||||
contributionAdapterIds: [],
|
||||
supportedTargetIds: [],
|
||||
supportsLuckmail: false,
|
||||
supportsOauthTimeoutBudget: false,
|
||||
@@ -44,6 +47,9 @@
|
||||
supportsPhoneVerificationSettings: true,
|
||||
supportsPlusMode: true,
|
||||
supportsContributionMode: true,
|
||||
supportsAccountContribution: true,
|
||||
supportsOpenAiOAuthContribution: true,
|
||||
contributionAdapterIds: ['openai-oauth', 'openai-codex-file', 'openai-sub2api-file'],
|
||||
supportedTargetIds: [...OPENAI_TARGET_IDS],
|
||||
supportsLuckmail: true,
|
||||
supportsOauthTimeoutBudget: true,
|
||||
@@ -203,6 +209,8 @@
|
||||
services: ['account', 'email', 'proxy'],
|
||||
capabilities: {
|
||||
...DEFAULT_FLOW_CAPABILITIES,
|
||||
supportsAccountContribution: true,
|
||||
contributionAdapterIds: ['kiro-builder-id'],
|
||||
supportedTargetIds: [DEFAULT_KIRO_TARGET_ID],
|
||||
stepDefinitionMode: 'kiro',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user