feat: Expose license feature flags for free AI credits feature to frontend (no-changelog) (#12363)

This commit is contained in:
Ricardo Espinoza 2024-12-27 10:23:20 -05:00 committed by GitHub
parent ac4e042231
commit 7ea6c8b144
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 57 additions and 2 deletions

View file

@ -163,6 +163,10 @@ export interface FrontendSettings {
pruneTime: number; pruneTime: number;
licensePruneTime: number; licensePruneTime: number;
}; };
aiCredits: {
enabled: boolean;
credits: number;
};
pruning?: { pruning?: {
isEnabled: boolean; isEnabled: boolean;
maxAge: number; maxAge: number;

View file

@ -93,6 +93,7 @@ export const LICENSE_FEATURES = {
AI_ASSISTANT: 'feat:aiAssistant', AI_ASSISTANT: 'feat:aiAssistant',
ASK_AI: 'feat:askAi', ASK_AI: 'feat:askAi',
COMMUNITY_NODES_CUSTOM_REGISTRY: 'feat:communityNodes:customRegistry', COMMUNITY_NODES_CUSTOM_REGISTRY: 'feat:communityNodes:customRegistry',
AI_CREDITS: 'feat:aiCredits',
} as const; } as const;
export const LICENSE_QUOTAS = { export const LICENSE_QUOTAS = {
@ -101,6 +102,7 @@ export const LICENSE_QUOTAS = {
USERS_LIMIT: 'quota:users', USERS_LIMIT: 'quota:users',
WORKFLOW_HISTORY_PRUNE_LIMIT: 'quota:workflowHistoryPrune', WORKFLOW_HISTORY_PRUNE_LIMIT: 'quota:workflowHistoryPrune',
TEAM_PROJECT_LIMIT: 'quota:maxTeamProjects', TEAM_PROJECT_LIMIT: 'quota:maxTeamProjects',
AI_CREDITS: 'quota:aiCredits',
} as const; } as const;
export const UNLIMITED_LICENSE_QUOTA = -1; export const UNLIMITED_LICENSE_QUOTA = -1;

View file

@ -100,6 +100,7 @@ export class E2EController {
[LICENSE_FEATURES.AI_ASSISTANT]: false, [LICENSE_FEATURES.AI_ASSISTANT]: false,
[LICENSE_FEATURES.COMMUNITY_NODES_CUSTOM_REGISTRY]: false, [LICENSE_FEATURES.COMMUNITY_NODES_CUSTOM_REGISTRY]: false,
[LICENSE_FEATURES.ASK_AI]: false, [LICENSE_FEATURES.ASK_AI]: false,
[LICENSE_FEATURES.AI_CREDITS]: false,
}; };
private numericFeatures: Record<NumericLicenseFeature, number> = { private numericFeatures: Record<NumericLicenseFeature, number> = {
@ -108,6 +109,7 @@ export class E2EController {
[LICENSE_QUOTAS.USERS_LIMIT]: -1, [LICENSE_QUOTAS.USERS_LIMIT]: -1,
[LICENSE_QUOTAS.WORKFLOW_HISTORY_PRUNE_LIMIT]: -1, [LICENSE_QUOTAS.WORKFLOW_HISTORY_PRUNE_LIMIT]: -1,
[LICENSE_QUOTAS.TEAM_PROJECT_LIMIT]: 0, [LICENSE_QUOTAS.TEAM_PROJECT_LIMIT]: 0,
[LICENSE_QUOTAS.AI_CREDITS]: 0,
}; };
constructor( constructor(

View file

@ -255,6 +255,10 @@ export class License {
return this.isFeatureEnabled(LICENSE_FEATURES.ASK_AI); return this.isFeatureEnabled(LICENSE_FEATURES.ASK_AI);
} }
isAiCreditsEnabled() {
return this.isFeatureEnabled(LICENSE_FEATURES.AI_CREDITS);
}
isAdvancedExecutionFiltersEnabled() { isAdvancedExecutionFiltersEnabled() {
return this.isFeatureEnabled(LICENSE_FEATURES.ADVANCED_EXECUTION_FILTERS); return this.isFeatureEnabled(LICENSE_FEATURES.ADVANCED_EXECUTION_FILTERS);
} }
@ -365,6 +369,10 @@ export class License {
return this.getFeatureValue(LICENSE_QUOTAS.VARIABLES_LIMIT) ?? UNLIMITED_LICENSE_QUOTA; return this.getFeatureValue(LICENSE_QUOTAS.VARIABLES_LIMIT) ?? UNLIMITED_LICENSE_QUOTA;
} }
getAiCredits() {
return this.getFeatureValue(LICENSE_QUOTAS.AI_CREDITS) ?? 0;
}
getWorkflowHistoryPruneLimit() { getWorkflowHistoryPruneLimit() {
return ( return (
this.getFeatureValue(LICENSE_QUOTAS.WORKFLOW_HISTORY_PRUNE_LIMIT) ?? UNLIMITED_LICENSE_QUOTA this.getFeatureValue(LICENSE_QUOTAS.WORKFLOW_HISTORY_PRUNE_LIMIT) ?? UNLIMITED_LICENSE_QUOTA

View file

@ -216,6 +216,10 @@ export class FrontendService {
askAi: { askAi: {
enabled: false, enabled: false,
}, },
aiCredits: {
enabled: false,
credits: 0,
},
workflowHistory: { workflowHistory: {
pruneTime: -1, pruneTime: -1,
licensePruneTime: -1, licensePruneTime: -1,
@ -283,6 +287,7 @@ export class FrontendService {
const isS3Licensed = this.license.isBinaryDataS3Licensed(); const isS3Licensed = this.license.isBinaryDataS3Licensed();
const isAiAssistantEnabled = this.license.isAiAssistantEnabled(); const isAiAssistantEnabled = this.license.isAiAssistantEnabled();
const isAskAiEnabled = this.license.isAskAiEnabled(); const isAskAiEnabled = this.license.isAskAiEnabled();
const isAiCreditsEnabled = this.license.isAiCreditsEnabled();
this.settings.license.planName = this.license.getPlanName(); this.settings.license.planName = this.license.getPlanName();
this.settings.license.consumerId = this.license.getConsumerId(); this.settings.license.consumerId = this.license.getConsumerId();
@ -343,6 +348,11 @@ export class FrontendService {
this.settings.askAi.enabled = isAskAiEnabled; this.settings.askAi.enabled = isAskAiEnabled;
} }
if (isAiCreditsEnabled) {
this.settings.aiCredits.enabled = isAiCreditsEnabled;
this.settings.aiCredits.credits = this.license.getAiCredits();
}
this.settings.mfa.enabled = config.get('mfa.enabled'); this.settings.mfa.enabled = config.get('mfa.enabled');
this.settings.executionMode = config.getEnv('executions.mode'); this.settings.executionMode = config.getEnv('executions.mode');

View file

@ -125,6 +125,10 @@ export const defaultSettings: FrontendSettings = {
aiAssistant: { aiAssistant: {
enabled: false, enabled: false,
}, },
aiCredits: {
enabled: false,
credits: 0,
},
betaFeatures: [], betaFeatures: [],
easyAIWorkflowOnboarded: false, easyAIWorkflowOnboarded: false,
}; };

View file

@ -1,4 +1,4 @@
import type { IRestApiContext } from '@/Interface'; import type { ICredentialsResponse, IRestApiContext } from '@/Interface';
import type { AskAiRequest, ChatRequest, ReplaceCodeRequest } from '@/types/assistant.types'; import type { AskAiRequest, ChatRequest, ReplaceCodeRequest } from '@/types/assistant.types';
import { makeRestApiRequest, streamRequest } from '@/utils/apiUtils'; import { makeRestApiRequest, streamRequest } from '@/utils/apiUtils';
import type { IDataObject } from 'n8n-workflow'; import type { IDataObject } from 'n8n-workflow';
@ -42,3 +42,12 @@ export async function generateCodeForPrompt(
forNode, forNode,
} as IDataObject); } as IDataObject);
} }
export async function claimFreeAiCredits(
ctx: IRestApiContext,
{ projectId }: { projectId?: string },
): Promise<ICredentialsResponse> {
return await makeRestApiRequest(ctx, 'POST', '/ai/free-credits', {
projectId,
} as IDataObject);
}

View file

@ -706,12 +706,19 @@ export const EASY_AI_WORKFLOW_EXPERIMENT = {
variant: 'variant', variant: 'variant',
}; };
export const AI_CREDITS_EXPERIMENT = {
name: '027_free_openai_calls',
control: 'control',
variant: 'variant',
};
export const EXPERIMENTS_TO_TRACK = [ export const EXPERIMENTS_TO_TRACK = [
TEMPLATE_CREDENTIAL_SETUP_EXPERIMENT, TEMPLATE_CREDENTIAL_SETUP_EXPERIMENT,
CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT.name, CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT.name,
AI_ASSISTANT_EXPERIMENT.name, AI_ASSISTANT_EXPERIMENT.name,
CREDENTIAL_DOCS_EXPERIMENT.name, CREDENTIAL_DOCS_EXPERIMENT.name,
EASY_AI_WORKFLOW_EXPERIMENT.name, EASY_AI_WORKFLOW_EXPERIMENT.name,
AI_CREDITS_EXPERIMENT.name,
]; ];
export const WORKFLOW_EVALUATION_EXPERIMENT = '025_workflow_evaluation'; export const WORKFLOW_EVALUATION_EXPERIMENT = '025_workflow_evaluation';

View file

@ -98,6 +98,10 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
const isCloudDeployment = computed(() => settings.value.deployment?.type === 'cloud'); const isCloudDeployment = computed(() => settings.value.deployment?.type === 'cloud');
const isAiCreditsEnabled = computed(() => settings.value.aiCredits?.enabled);
const aiCreditsQuota = computed(() => settings.value.aiCredits?.credits);
const isSmtpSetup = computed(() => userManagement.value.smtpSetup); const isSmtpSetup = computed(() => userManagement.value.smtpSetup);
const isPersonalizationSurveyEnabled = computed( const isPersonalizationSurveyEnabled = computed(
@ -425,6 +429,8 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
isCommunityPlan, isCommunityPlan,
isAskAiEnabled, isAskAiEnabled,
isCanvasV2Enabled, isCanvasV2Enabled,
isAiCreditsEnabled,
aiCreditsQuota,
reset, reset,
testLdapConnection, testLdapConnection,
getLdapConfig, getLdapConfig,

View file

@ -74,6 +74,8 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
const globalRoleName = computed(() => currentUser.value?.role ?? 'default'); const globalRoleName = computed(() => currentUser.value?.role ?? 'default');
const userClaimedAiCredits = computed(() => currentUser.value?.settings?.userClaimedAiCredits);
const isEasyAIWorkflowOnboardingDone = computed(() => const isEasyAIWorkflowOnboardingDone = computed(() =>
Boolean(currentUser.value?.settings?.easyAIWorkflowOnboarded), Boolean(currentUser.value?.settings?.easyAIWorkflowOnboarded),
); );
@ -388,6 +390,8 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
mfaEnabled, mfaEnabled,
globalRoleName, globalRoleName,
personalizedNodeTypes, personalizedNodeTypes,
userClaimedAiCredits,
isEasyAIWorkflowOnboardingDone,
addUsers, addUsers,
loginWithCookie, loginWithCookie,
initialize, initialize,
@ -420,7 +424,6 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
sendConfirmationEmail, sendConfirmationEmail,
updateGlobalRole, updateGlobalRole,
reset, reset,
isEasyAIWorkflowOnboardingDone,
setEasyAIWorkflowOnboardingDone, setEasyAIWorkflowOnboardingDone,
}; };
}); });