Skip to content

Commit 2a3ffef

Browse files
authored
feat: Add feature selection capability to Bedrock Content Generator [] (#10276)
* Add feature selection capability to Bedrock Content Generator - Add enabledFeatures parameter to installation configuration - Create FeatureSelectionSection component for config page - Filter sidebar buttons to show only enabled features - Add validation in Dialog to prevent access to disabled features - Ensure backward compatibility (defaults to all features if not configured) * Enhance feature toggle logic in FeatureSelectionSection
1 parent 9101745 commit 2a3ffef

File tree

8 files changed

+162
-6
lines changed

8 files changed

+162
-6
lines changed

apps/bedrock-content-generator/src/components/app/sidebar/SidebarButtons.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import { Box } from '@contentful/f36-components';
2-
import featureConfig, { AIFeature } from '@configs/features/featureConfig';
2+
import { AIFeature } from '@configs/features/featureConfig';
33
import FeatureButton from './feature-button/FeatureButton';
44
import { useState } from 'react';
5+
import useSidebarParameters from '@hooks/sidebar/useSidebarParameters';
56

67
const SidebarButtons = () => {
78
const [isSaving, setIsSaving] = useState(false);
9+
const { enabledFeatures } = useSidebarParameters();
810

911
const handleSaving = (toggleTo: boolean) => {
1012
setIsSaving(toggleTo);
1113
};
1214

13-
const featureList = Object.keys(featureConfig).map((feature) => {
15+
const featureList = enabledFeatures.map((feature) => {
1416
return (
1517
<FeatureButton
1618
key={feature}

apps/bedrock-content-generator/src/components/config/appInstallationParameters.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
import { AIFeature } from '@configs/features/featureConfig';
2+
13
interface AppInstallationParameters {
24
accessKeyId: string;
35
secretAccessKey: string;
46
region: string;
57
model: string;
68
profile: string;
79
brandProfile: ProfileType;
10+
enabledFeatures?: AIFeature[];
811
}
912

1013
export enum ProfileFields {

apps/bedrock-content-generator/src/components/config/config-page/ConfigPage.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import { ConfigErrors, Sections } from '@components/config/configText';
55
import contentTypeReducer from '@components/config/contentTypeReducer';
66
import CostSection from '@components/config/cost-section/CostSection';
77
import DisclaimerSection from '@components/config/disclaimer-section/DisclaimerSection';
8+
import FeatureSelectionSection from '@components/config/feature-selection-section/FeatureSelectionSection';
89
import parameterReducer, { Validator } from '@components/config/parameterReducer';
910
import { defaultRegionId } from '@configs/aws/bedrockRegions';
11+
import featureConfig, { AIFeature } from '@configs/features/featureConfig';
1012
import { Box, Heading } from '@contentful/f36-components';
1113
import useGetContentTypes from '@hooks/config/useGetContentTypes';
1214
import useInitializeParameters from '@hooks/config/useInitializeParameters';
@@ -37,6 +39,10 @@ const initialParameters: Validator<AppInstallationParameters> = {
3739
isValid: true,
3840
},
3941
brandProfile: {},
42+
enabledFeatures: {
43+
value: Object.keys(featureConfig) as AIFeature[],
44+
isValid: true,
45+
},
4046
};
4147

4248
const initialContentTypes: Set<string> = new Set();
@@ -61,6 +67,7 @@ const ConfigPage = () => {
6167
tone: parameters.brandProfile.tone?.value,
6268
values: parameters.brandProfile.values?.value,
6369
},
70+
enabledFeatures: parameters.enabledFeatures?.value,
6471
};
6572
}, [
6673
parameters.brandProfile,
@@ -69,6 +76,7 @@ const ConfigPage = () => {
6976
parameters.region.value,
7077
parameters.model.value,
7178
parameters.profile.value,
79+
parameters.enabledFeatures?.value,
7280
]);
7381

7482
const validateParams = (): string[] => {
@@ -121,6 +129,13 @@ const ConfigPage = () => {
121129
dispatch={dispatchParameters}
122130
/>
123131
<hr css={styles.splitter} />
132+
<FeatureSelectionSection
133+
enabledFeatures={
134+
parameters.enabledFeatures?.value || (Object.keys(featureConfig) as AIFeature[])
135+
}
136+
dispatch={dispatchParameters}
137+
/>
138+
<hr css={styles.splitter} />
124139
<AddToSidebarSection
125140
allContentTypes={allContentTypes}
126141
selectedContentTypes={contentTypes}

apps/bedrock-content-generator/src/components/config/configText.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ const Sections = {
9191
'Add details about your brand to power accurate and on-brand content for all of your prompts.',
9292
addToSidebarHeading: 'Add to sidebar views',
9393
addToSidebarDescription: 'Assign Content Generator to content types.',
94+
featureSelectionHeading: 'Feature selection',
95+
featureSelectionDescription:
96+
'Select which AI features should be available in the sidebar. At least one feature must be enabled.',
9497
costHeading: 'Cost',
9598
costSubheading:
9699
'Using this app incurs AWS costs, depending on the amount of tokens you generate.',
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { Dispatch } from 'react';
2+
import {
3+
Box,
4+
Flex,
5+
FormControl,
6+
Subheading,
7+
Paragraph,
8+
Checkbox,
9+
} from '@contentful/f36-components';
10+
import { Sections } from '../configText';
11+
import { ParameterReducer, ParameterAction } from '../parameterReducer';
12+
import featureConfig, { AIFeature } from '@configs/features/featureConfig';
13+
import { css } from '@emotion/react';
14+
15+
export const styles = css({
16+
width: '100%',
17+
'& > *': {
18+
marginBottom: '8px',
19+
},
20+
});
21+
22+
interface Props {
23+
enabledFeatures: AIFeature[];
24+
dispatch: Dispatch<ParameterReducer>;
25+
}
26+
27+
const FeatureSelectionSection = ({ enabledFeatures, dispatch }: Props) => {
28+
const allFeatures = Object.keys(featureConfig) as AIFeature[];
29+
30+
const handleFeatureToggle = (feature: AIFeature, isChecked: boolean) => {
31+
if (isChecked) {
32+
// Remove feature - but prevent if it's the last one
33+
if (enabledFeatures.length === 1) {
34+
return; // Prevent removing the last feature
35+
}
36+
const updatedFeatures = enabledFeatures.filter((f) => f !== feature);
37+
dispatch({
38+
type: ParameterAction.UPDATE_ENABLED_FEATURES,
39+
value: updatedFeatures,
40+
});
41+
} else {
42+
// Add feature
43+
const updatedFeatures = [...enabledFeatures, feature];
44+
dispatch({
45+
type: ParameterAction.UPDATE_ENABLED_FEATURES,
46+
value: updatedFeatures,
47+
});
48+
}
49+
};
50+
51+
const isFeatureEnabled = (feature: AIFeature) => {
52+
return enabledFeatures.includes(feature);
53+
};
54+
55+
return (
56+
<Flex flexDirection="column" alignItems="flex-start" fullWidth={true}>
57+
<Subheading>{Sections.featureSelectionHeading}</Subheading>
58+
<Paragraph>{Sections.featureSelectionDescription}</Paragraph>
59+
<Box css={styles}>
60+
<FormControl as="fieldset" marginBottom="none">
61+
{allFeatures.map((feature) => {
62+
const featureConfigItem = featureConfig[feature];
63+
return (
64+
<Checkbox
65+
key={feature}
66+
id={feature}
67+
isChecked={isFeatureEnabled(feature)}
68+
onChange={() => handleFeatureToggle(feature, isFeatureEnabled(feature))}
69+
isDisabled={enabledFeatures.length === 1 && isFeatureEnabled(feature)}>
70+
{featureConfigItem.buttonTitle}
71+
</Checkbox>
72+
);
73+
})}
74+
</FormControl>
75+
</Box>
76+
</Flex>
77+
);
78+
};
79+
80+
export default FeatureSelectionSection;

apps/bedrock-content-generator/src/components/config/parameterReducer.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import AppInstallationParameters from './appInstallationParameters';
2+
import featureConfig, { AIFeature } from '@configs/features/featureConfig';
23

34
export enum ParameterAction {
45
UPDATE_CREDENTIALS = 'updateCredentials',
56
UPDATE_REGION = 'updateRegion',
67
UPDATE_MODEL = 'updateModel',
78
UPDATE_PROFILE = 'updateProfile',
89
UPDATE_BRAND_PROFILE = 'updateBrandProfile',
10+
UPDATE_ENABLED_FEATURES = 'updateEnabledFeatures',
911
APPLY_CONTENTFUL_PARAMETERS = 'applyContentfulParameters',
1012
}
1113

@@ -47,13 +49,19 @@ type ParameterBrandProfileActions = {
4749
textLimit: number;
4850
};
4951

52+
type ParameterUpdateEnabledFeaturesAction = {
53+
type: ParameterAction.UPDATE_ENABLED_FEATURES;
54+
value: AIFeature[];
55+
};
56+
5057
export type ParameterReducer =
5158
| ParameterObjectActions
5259
| ParameterStringActions
5360
| ParameterProfileAction
5461
| ParameterBrandProfileActions
5562
| ParameterUpdateCredentialsAction
56-
| ParameterUpdateRegionAction;
63+
| ParameterUpdateRegionAction
64+
| ParameterUpdateEnabledFeaturesAction;
5765

5866
/**
5967
* This is a recursive type that will validate the parameter
@@ -129,6 +137,15 @@ const parameterReducer = (
129137
},
130138
};
131139
}
140+
case ParameterAction.UPDATE_ENABLED_FEATURES: {
141+
return {
142+
...state,
143+
enabledFeatures: {
144+
value: action.value,
145+
isValid: action.value.length > 0,
146+
},
147+
};
148+
}
132149
case ParameterAction.APPLY_CONTENTFUL_PARAMETERS: {
133150
const parameter = action.value as AppInstallationParameters;
134151
return {
@@ -175,6 +192,14 @@ const parameterReducer = (
175192
isValid: true,
176193
},
177194
},
195+
enabledFeatures: {
196+
value: parameter.enabledFeatures || (Object.keys(featureConfig) as AIFeature[]),
197+
isValid: true,
198+
},
199+
region: {
200+
value: parameter.region,
201+
isValid: parameter.region?.length > 0,
202+
},
178203
};
179204
}
180205
default:

apps/bedrock-content-generator/src/hooks/sidebar/useSidebarParameters.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,33 @@ import AppInstallationParameters from '@components/config/appInstallationParamet
22
import { SidebarAppSDK } from '@contentful/app-sdk';
33
import { useSDK } from '@contentful/react-apps-toolkit';
44
import { useEffect, useState } from 'react';
5+
import featureConfig, { AIFeature } from '@configs/features/featureConfig';
56

67
/**
78
* This hook is used to get the installation parameters from the sidebar location,
89
* checks to see if there is a brand profile, validates the API Key and returns any errors
910
*
10-
* @returns {hasBrandProfile, apiError}
11+
* @returns {hasBrandProfile, enabledFeatures}
1112
*/
1213
const useSidebarParameters = () => {
1314
const [hasBrandProfile, setHasBrandProfile] = useState(true);
1415

1516
const sdk = useSDK<SidebarAppSDK<AppInstallationParameters>>();
16-
const { profile } = sdk.parameters.installation;
17+
const { profile, enabledFeatures } = sdk.parameters.installation;
1718

1819
useEffect(() => {
1920
setHasBrandProfile(!!profile);
2021
}, [profile]);
2122

23+
// Default to all features if enabledFeatures is not set (for backward compatibility)
24+
const features =
25+
enabledFeatures && enabledFeatures.length > 0
26+
? enabledFeatures
27+
: (Object.keys(featureConfig) as AIFeature[]);
28+
2229
return {
2330
hasBrandProfile,
31+
enabledFeatures: features,
2432
};
2533
};
2634

apps/bedrock-content-generator/src/locations/Dialog.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import CommonGenerator from '@components/app/dialog/common-generator/CommonGenerator';
22
import RewriteGenerator from '@components/app/dialog/rewrite-generator/RewriteGenerator';
33
import { AIFeature } from '@configs/features/featureConfig';
4+
import AppInstallationParameters from '@components/config/appInstallationParameters';
45
import { DialogAppSDK } from '@contentful/app-sdk';
56
import { useAutoResizer, useSDK } from '@contentful/react-apps-toolkit';
67
import useDialogParameters from '@hooks/dialog/useDialogParameters';
78
import GeneratorProvider from '@providers/generatorProvider';
9+
import featureConfig from '@configs/features/featureConfig';
10+
import { Note, Text } from '@contentful/f36-components';
811

912
export interface FieldLocales {
1013
[key: string]: string[];
@@ -18,14 +21,31 @@ type DialogInvocationParameters = {
1821

1922
const Dialog = () => {
2023
const { feature, entryId, isLoading, fieldLocales } = useDialogParameters();
21-
const sdk = useSDK<DialogAppSDK>();
24+
const sdk = useSDK<DialogAppSDK<AppInstallationParameters>>();
2225

2326
useAutoResizer();
2427

2528
if (isLoading) {
2629
return null;
2730
}
2831

32+
// Validate that the requested feature is enabled
33+
const { enabledFeatures } = sdk.parameters.installation;
34+
const allFeatures = Object.keys(featureConfig) as AIFeature[];
35+
const enabledFeaturesList =
36+
enabledFeatures && enabledFeatures.length > 0 ? enabledFeatures : allFeatures; // Default to all features for backward compatibility
37+
38+
if (!enabledFeaturesList.includes(feature)) {
39+
return (
40+
<Note variant="negative" title="Feature not available">
41+
<Text>
42+
The &quot;{featureConfig[feature]?.buttonTitle || feature}&quot; feature is not enabled in
43+
the app configuration. Please contact your administrator to enable this feature.
44+
</Text>
45+
</Note>
46+
);
47+
}
48+
2949
const localeNames = sdk.locales.names;
3050
const defaultLocale = sdk.locales.default;
3151

0 commit comments

Comments
 (0)