Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { Box } from '@contentful/f36-components';
import featureConfig, { AIFeature } from '@configs/features/featureConfig';
import { AIFeature } from '@configs/features/featureConfig';
import FeatureButton from './feature-button/FeatureButton';
import { useState } from 'react';
import useSidebarParameters from '@hooks/sidebar/useSidebarParameters';

const SidebarButtons = () => {
const [isSaving, setIsSaving] = useState(false);
const { enabledFeatures } = useSidebarParameters();

const handleSaving = (toggleTo: boolean) => {
setIsSaving(toggleTo);
};

const featureList = Object.keys(featureConfig).map((feature) => {
const featureList = enabledFeatures.map((feature) => {
return (
<FeatureButton
key={feature}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { AIFeature } from '@configs/features/featureConfig';

interface AppInstallationParameters {
accessKeyId: string;
secretAccessKey: string;
region: string;
model: string;
profile: string;
brandProfile: ProfileType;
enabledFeatures?: AIFeature[];
}

export enum ProfileFields {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { ConfigErrors, Sections } from '@components/config/configText';
import contentTypeReducer from '@components/config/contentTypeReducer';
import CostSection from '@components/config/cost-section/CostSection';
import DisclaimerSection from '@components/config/disclaimer-section/DisclaimerSection';
import FeatureSelectionSection from '@components/config/feature-selection-section/FeatureSelectionSection';
import parameterReducer, { Validator } from '@components/config/parameterReducer';
import { defaultRegionId } from '@configs/aws/bedrockRegions';
import featureConfig, { AIFeature } from '@configs/features/featureConfig';
import { Box, Heading } from '@contentful/f36-components';
import useGetContentTypes from '@hooks/config/useGetContentTypes';
import useInitializeParameters from '@hooks/config/useInitializeParameters';
Expand Down Expand Up @@ -37,6 +39,10 @@ const initialParameters: Validator<AppInstallationParameters> = {
isValid: true,
},
brandProfile: {},
enabledFeatures: {
value: Object.keys(featureConfig) as AIFeature[],
isValid: true,
},
};

const initialContentTypes: Set<string> = new Set();
Expand All @@ -61,6 +67,7 @@ const ConfigPage = () => {
tone: parameters.brandProfile.tone?.value,
values: parameters.brandProfile.values?.value,
},
enabledFeatures: parameters.enabledFeatures?.value,
};
}, [
parameters.brandProfile,
Expand All @@ -69,6 +76,7 @@ const ConfigPage = () => {
parameters.region.value,
parameters.model.value,
parameters.profile.value,
parameters.enabledFeatures?.value,
]);

const validateParams = (): string[] => {
Expand Down Expand Up @@ -121,6 +129,13 @@ const ConfigPage = () => {
dispatch={dispatchParameters}
/>
<hr css={styles.splitter} />
<FeatureSelectionSection
enabledFeatures={
parameters.enabledFeatures?.value || (Object.keys(featureConfig) as AIFeature[])
}
dispatch={dispatchParameters}
/>
<hr css={styles.splitter} />
<AddToSidebarSection
allContentTypes={allContentTypes}
selectedContentTypes={contentTypes}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ const Sections = {
'Add details about your brand to power accurate and on-brand content for all of your prompts.',
addToSidebarHeading: 'Add to sidebar views',
addToSidebarDescription: 'Assign Content Generator to content types.',
featureSelectionHeading: 'Feature selection',
featureSelectionDescription:
'Select which AI features should be available in the sidebar. At least one feature must be enabled.',
costHeading: 'Cost',
costSubheading:
'Using this app incurs AWS costs, depending on the amount of tokens you generate.',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Dispatch } from 'react';
import {
Box,
Flex,
FormControl,
Subheading,
Paragraph,
Checkbox,
} from '@contentful/f36-components';
import { Sections } from '../configText';
import { ParameterReducer, ParameterAction } from '../parameterReducer';
import featureConfig, { AIFeature } from '@configs/features/featureConfig';
import { css } from '@emotion/react';

export const styles = css({
width: '100%',
'& > *': {
marginBottom: '8px',
},
});

interface Props {
enabledFeatures: AIFeature[];
dispatch: Dispatch<ParameterReducer>;
}

const FeatureSelectionSection = ({ enabledFeatures, dispatch }: Props) => {
const allFeatures = Object.keys(featureConfig) as AIFeature[];

const handleFeatureToggle = (feature: AIFeature, isChecked: boolean) => {
if (isChecked) {
// Remove feature - but prevent if it's the last one
if (enabledFeatures.length === 1) {
return; // Prevent removing the last feature
}
const updatedFeatures = enabledFeatures.filter((f) => f !== feature);
dispatch({
type: ParameterAction.UPDATE_ENABLED_FEATURES,
value: updatedFeatures,
});
} else {
// Add feature
const updatedFeatures = [...enabledFeatures, feature];
dispatch({
type: ParameterAction.UPDATE_ENABLED_FEATURES,
value: updatedFeatures,
});
}
};

const isFeatureEnabled = (feature: AIFeature) => {
return enabledFeatures.includes(feature);
};

return (
<Flex flexDirection="column" alignItems="flex-start" fullWidth={true}>
<Subheading>{Sections.featureSelectionHeading}</Subheading>
<Paragraph>{Sections.featureSelectionDescription}</Paragraph>
<Box css={styles}>
<FormControl as="fieldset" marginBottom="none">
{allFeatures.map((feature) => {
const featureConfigItem = featureConfig[feature];
return (
<Checkbox
key={feature}
id={feature}
isChecked={isFeatureEnabled(feature)}
onChange={() => handleFeatureToggle(feature, isFeatureEnabled(feature))}
isDisabled={enabledFeatures.length === 1 && isFeatureEnabled(feature)}>
{featureConfigItem.buttonTitle}
</Checkbox>
);
})}
</FormControl>
</Box>
</Flex>
);
};

export default FeatureSelectionSection;
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import AppInstallationParameters from './appInstallationParameters';
import featureConfig, { AIFeature } from '@configs/features/featureConfig';

export enum ParameterAction {
UPDATE_CREDENTIALS = 'updateCredentials',
UPDATE_REGION = 'updateRegion',
UPDATE_MODEL = 'updateModel',
UPDATE_PROFILE = 'updateProfile',
UPDATE_BRAND_PROFILE = 'updateBrandProfile',
UPDATE_ENABLED_FEATURES = 'updateEnabledFeatures',
APPLY_CONTENTFUL_PARAMETERS = 'applyContentfulParameters',
}

Expand Down Expand Up @@ -47,13 +49,19 @@ type ParameterBrandProfileActions = {
textLimit: number;
};

type ParameterUpdateEnabledFeaturesAction = {
type: ParameterAction.UPDATE_ENABLED_FEATURES;
value: AIFeature[];
};

export type ParameterReducer =
| ParameterObjectActions
| ParameterStringActions
| ParameterProfileAction
| ParameterBrandProfileActions
| ParameterUpdateCredentialsAction
| ParameterUpdateRegionAction;
| ParameterUpdateRegionAction
| ParameterUpdateEnabledFeaturesAction;

/**
* This is a recursive type that will validate the parameter
Expand Down Expand Up @@ -129,6 +137,15 @@ const parameterReducer = (
},
};
}
case ParameterAction.UPDATE_ENABLED_FEATURES: {
return {
...state,
enabledFeatures: {
value: action.value,
isValid: action.value.length > 0,
},
};
}
case ParameterAction.APPLY_CONTENTFUL_PARAMETERS: {
const parameter = action.value as AppInstallationParameters;
return {
Expand Down Expand Up @@ -175,6 +192,14 @@ const parameterReducer = (
isValid: true,
},
},
enabledFeatures: {
value: parameter.enabledFeatures || (Object.keys(featureConfig) as AIFeature[]),
isValid: true,
},
region: {
value: parameter.region,
isValid: parameter.region?.length > 0,
},
};
}
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,33 @@ import AppInstallationParameters from '@components/config/appInstallationParamet
import { SidebarAppSDK } from '@contentful/app-sdk';
import { useSDK } from '@contentful/react-apps-toolkit';
import { useEffect, useState } from 'react';
import featureConfig, { AIFeature } from '@configs/features/featureConfig';

/**
* This hook is used to get the installation parameters from the sidebar location,
* checks to see if there is a brand profile, validates the API Key and returns any errors
*
* @returns {hasBrandProfile, apiError}
Copy link
Contributor

@sam-is-content sam-is-content Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this hook didn't actually return an apiError to begin with?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i guess not. looking at it, it's pretty clear. I removed this to get rid of a lint warning

* @returns {hasBrandProfile, enabledFeatures}
*/
const useSidebarParameters = () => {
const [hasBrandProfile, setHasBrandProfile] = useState(true);

const sdk = useSDK<SidebarAppSDK<AppInstallationParameters>>();
const { profile } = sdk.parameters.installation;
const { profile, enabledFeatures } = sdk.parameters.installation;

useEffect(() => {
setHasBrandProfile(!!profile);
}, [profile]);

// Default to all features if enabledFeatures is not set (for backward compatibility)
const features =
enabledFeatures && enabledFeatures.length > 0
? enabledFeatures
: (Object.keys(featureConfig) as AIFeature[]);

return {
hasBrandProfile,
enabledFeatures: features,
};
};

Expand Down
22 changes: 21 additions & 1 deletion apps/bedrock-content-generator/src/locations/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import CommonGenerator from '@components/app/dialog/common-generator/CommonGenerator';
import RewriteGenerator from '@components/app/dialog/rewrite-generator/RewriteGenerator';
import { AIFeature } from '@configs/features/featureConfig';
import AppInstallationParameters from '@components/config/appInstallationParameters';
import { DialogAppSDK } from '@contentful/app-sdk';
import { useAutoResizer, useSDK } from '@contentful/react-apps-toolkit';
import useDialogParameters from '@hooks/dialog/useDialogParameters';
import GeneratorProvider from '@providers/generatorProvider';
import featureConfig from '@configs/features/featureConfig';
import { Note, Text } from '@contentful/f36-components';

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

const Dialog = () => {
const { feature, entryId, isLoading, fieldLocales } = useDialogParameters();
const sdk = useSDK<DialogAppSDK>();
const sdk = useSDK<DialogAppSDK<AppInstallationParameters>>();

useAutoResizer();

if (isLoading) {
return null;
}

// Validate that the requested feature is enabled
const { enabledFeatures } = sdk.parameters.installation;
const allFeatures = Object.keys(featureConfig) as AIFeature[];
const enabledFeaturesList =
enabledFeatures && enabledFeatures.length > 0 ? enabledFeatures : allFeatures; // Default to all features for backward compatibility

if (!enabledFeaturesList.includes(feature)) {
return (
<Note variant="negative" title="Feature not available">
<Text>
The &quot;{featureConfig[feature]?.buttonTitle || feature}&quot; feature is not enabled in
the app configuration. Please contact your administrator to enable this feature.
</Text>
</Note>
);
}

const localeNames = sdk.locales.names;
const defaultLocale = sdk.locales.default;

Expand Down
Loading