feat: upgrade multi-flow account contributions

This commit is contained in:
QLHazycoder
2026-05-19 13:20:06 +00:00
parent 68c1f387e1
commit 4687d07e2d
36 changed files with 2960 additions and 442 deletions
+224
View File
@@ -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,
};
});
+26 -8
View File
@@ -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 不支持贡献模式。',
+8
View File
@@ -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',
},