Add cys ai header/footer (#40276)
* Add cys ai header suggestion * Add cys ai footer suggestion * Add changefile(s) from automation for the following project(s): woocommerce * Fix types --------- Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
parent
b9b4dc852c
commit
48342fbc66
|
@ -14,6 +14,8 @@ import {
|
|||
designWithAiStateMachineEvents,
|
||||
FontPairing,
|
||||
LookAndToneCompletionResponse,
|
||||
Header,
|
||||
Footer,
|
||||
} from './types';
|
||||
import { aiWizardClosedBeforeCompletionEvent } from './events';
|
||||
import {
|
||||
|
@ -110,6 +112,42 @@ const assignFontPairing = assign<
|
|||
},
|
||||
} );
|
||||
|
||||
const assignHeader = assign<
|
||||
designWithAiStateMachineContext,
|
||||
designWithAiStateMachineEvents
|
||||
>( {
|
||||
aiSuggestions: ( context, event: unknown ) => {
|
||||
return {
|
||||
...context.aiSuggestions,
|
||||
header: (
|
||||
event as {
|
||||
data: {
|
||||
response: Header;
|
||||
};
|
||||
}
|
||||
).data.response.slug,
|
||||
};
|
||||
},
|
||||
} );
|
||||
|
||||
const assignFooter = assign<
|
||||
designWithAiStateMachineContext,
|
||||
designWithAiStateMachineEvents
|
||||
>( {
|
||||
aiSuggestions: ( context, event: unknown ) => {
|
||||
return {
|
||||
...context.aiSuggestions,
|
||||
footer: (
|
||||
event as {
|
||||
data: {
|
||||
response: Footer;
|
||||
};
|
||||
}
|
||||
).data.response.slug,
|
||||
};
|
||||
},
|
||||
} );
|
||||
|
||||
const logAIAPIRequestError = () => {
|
||||
// log AI API request error
|
||||
// eslint-disable-next-line no-console
|
||||
|
@ -178,6 +216,8 @@ export const actions = {
|
|||
assignLookAndTone,
|
||||
assignDefaultColorPalette,
|
||||
assignFontPairing,
|
||||
assignHeader,
|
||||
assignFooter,
|
||||
logAIAPIRequestError,
|
||||
updateQueryStep,
|
||||
recordTracksStepViewed,
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { z } from 'zod';
|
||||
|
||||
const footerChoices = [
|
||||
{
|
||||
slug: 'woocommerce-blocks/footer-simple-menu-and-cart',
|
||||
label: 'Footer with Simple Menu and Cart',
|
||||
},
|
||||
{
|
||||
slug: 'woocommerce-blocks/footer-with-3-menus',
|
||||
label: 'Footer with 3 Menus',
|
||||
},
|
||||
{
|
||||
slug: 'woocommerce-blocks/footer-large',
|
||||
label: 'Large Footer',
|
||||
},
|
||||
];
|
||||
|
||||
const allowedFooter: string[] = footerChoices.map( ( footer ) => footer.slug );
|
||||
|
||||
export const footerValidator = z.object( {
|
||||
slug: z.string().refine( ( slug ) => allowedFooter.includes( slug ), {
|
||||
message: 'Footer not part of allowed list',
|
||||
} ),
|
||||
} );
|
||||
|
||||
export const defaultFooter = {
|
||||
queryId: 'default_footer',
|
||||
|
||||
// make sure version is updated every time the prompt is changed
|
||||
version: '2023-09-19',
|
||||
prompt: ( businessDescription: string, look: string, tone: string ) => {
|
||||
return `
|
||||
You are a WordPress theme expert. Analyse the following store description, merchant's chosen look and tone, and determine the most appropriate footer.
|
||||
Respond only with one footer and only its JSON.
|
||||
|
||||
Chosen look and tone: ${ look } look, ${ tone } tone.
|
||||
Business description: ${ businessDescription }
|
||||
|
||||
Footer to choose from:
|
||||
${ JSON.stringify( footerChoices ) }
|
||||
`;
|
||||
},
|
||||
responseValidation: footerValidator.parse,
|
||||
};
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { z } from 'zod';
|
||||
|
||||
const headerChoices = [
|
||||
{
|
||||
slug: 'woocommerce-blocks/header-essential',
|
||||
label: 'Essential Header',
|
||||
},
|
||||
{
|
||||
slug: 'woocommerce-blocks/header-centered-menu-with-search',
|
||||
label: 'Centered Menu with search Header',
|
||||
},
|
||||
{
|
||||
slug: 'woocommerce-blocks/header-minimal',
|
||||
label: 'Minimal Header',
|
||||
},
|
||||
{
|
||||
slug: 'woocommerce-blocks/header-large',
|
||||
label: 'Large Header',
|
||||
},
|
||||
];
|
||||
|
||||
const allowedHeaders: string[] = headerChoices.map( ( header ) => header.slug );
|
||||
|
||||
export const headerValidator = z.object( {
|
||||
slug: z.string().refine( ( slug ) => allowedHeaders.includes( slug ), {
|
||||
message: 'Header not part of allowed list',
|
||||
} ),
|
||||
} );
|
||||
|
||||
export const defaultHeader = {
|
||||
queryId: 'default_header',
|
||||
|
||||
// make sure version is updated every time the prompt is changed
|
||||
version: '2023-09-19',
|
||||
prompt: ( businessDescription: string, look: string, tone: string ) => {
|
||||
return `
|
||||
You are a WordPress theme expert. Analyse the following store description, merchant's chosen look and tone, and determine the most appropriate header.
|
||||
Respond only with one header and only its JSON.
|
||||
|
||||
Chosen look and tone: ${ look } look, ${ tone } tone.
|
||||
Business description: ${ businessDescription }
|
||||
|
||||
Headers to choose from:
|
||||
${ JSON.stringify( headerChoices ) }
|
||||
`;
|
||||
},
|
||||
responseValidation: headerValidator.parse,
|
||||
};
|
|
@ -1,3 +1,5 @@
|
|||
export * from './colorChoices';
|
||||
export * from './lookAndTone';
|
||||
export * from './fontPairings';
|
||||
export * from './header';
|
||||
export * from './footer';
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { footerValidator } from '..';
|
||||
|
||||
describe( 'footerValidator', () => {
|
||||
it( 'should validate when footer is part of the allowed list', () => {
|
||||
const validFooter = { slug: 'woocommerce-blocks/footer-large' };
|
||||
expect( () => footerValidator.parse( validFooter ) ).not.toThrow();
|
||||
} );
|
||||
|
||||
it( 'should not validate when footer is not part of the allowed list', () => {
|
||||
const invalidFooter = {
|
||||
slug: 'woocommerce-blocks/footer-large-invalid',
|
||||
};
|
||||
expect( () => footerValidator.parse( invalidFooter ) )
|
||||
.toThrowErrorMatchingInlineSnapshot( `
|
||||
"[
|
||||
{
|
||||
\\"code\\": \\"custom\\",
|
||||
\\"message\\": \\"Footer not part of allowed list\\",
|
||||
\\"path\\": [
|
||||
\\"slug\\"
|
||||
]
|
||||
}
|
||||
]"
|
||||
` );
|
||||
} );
|
||||
|
||||
it( 'should not validate when slug is not a string', () => {
|
||||
const invalidType = { slug: 123 };
|
||||
expect( () => footerValidator.parse( invalidType ) )
|
||||
.toThrowErrorMatchingInlineSnapshot( `
|
||||
"[
|
||||
{
|
||||
\\"code\\": \\"invalid_type\\",
|
||||
\\"expected\\": \\"string\\",
|
||||
\\"received\\": \\"number\\",
|
||||
\\"path\\": [
|
||||
\\"slug\\"
|
||||
],
|
||||
\\"message\\": \\"Expected string, received number\\"
|
||||
}
|
||||
]"
|
||||
` );
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { headerValidator } from '..';
|
||||
|
||||
describe( 'headerValidator', () => {
|
||||
it( 'should validate when header is part of the allowed list', () => {
|
||||
const validHeader = { slug: 'woocommerce-blocks/header-large' };
|
||||
expect( () => headerValidator.parse( validHeader ) ).not.toThrow();
|
||||
} );
|
||||
|
||||
it( 'should not validate when header is not part of the allowed list', () => {
|
||||
const invalidHeader = {
|
||||
slug: 'woocommerce-blocks/header-large-invalid',
|
||||
};
|
||||
expect( () => headerValidator.parse( invalidHeader ) )
|
||||
.toThrowErrorMatchingInlineSnapshot( `
|
||||
"[
|
||||
{
|
||||
\\"code\\": \\"custom\\",
|
||||
\\"message\\": \\"Header not part of allowed list\\",
|
||||
\\"path\\": [
|
||||
\\"slug\\"
|
||||
]
|
||||
}
|
||||
]"
|
||||
` );
|
||||
} );
|
||||
|
||||
it( 'should not validate when slug is not a string', () => {
|
||||
const invalidType = { slug: 123 };
|
||||
expect( () => headerValidator.parse( invalidType ) )
|
||||
.toThrowErrorMatchingInlineSnapshot( `
|
||||
"[
|
||||
{
|
||||
\\"code\\": \\"invalid_type\\",
|
||||
\\"expected\\": \\"string\\",
|
||||
\\"received\\": \\"number\\",
|
||||
\\"path\\": [
|
||||
\\"slug\\"
|
||||
],
|
||||
\\"message\\": \\"Expected string, received number\\"
|
||||
}
|
||||
]"
|
||||
` );
|
||||
} );
|
||||
} );
|
|
@ -11,6 +11,8 @@ import {
|
|||
designWithAiStateMachineContext,
|
||||
designWithAiStateMachineEvents,
|
||||
FontPairing,
|
||||
Header,
|
||||
Footer,
|
||||
ColorPaletteResponse,
|
||||
} from './types';
|
||||
import {
|
||||
|
@ -21,7 +23,12 @@ import {
|
|||
} from './pages';
|
||||
import { actions } from './actions';
|
||||
import { services } from './services';
|
||||
import { defaultColorPalette, fontPairings } from './prompts';
|
||||
import {
|
||||
defaultColorPalette,
|
||||
fontPairings,
|
||||
defaultHeader,
|
||||
defaultFooter,
|
||||
} from './prompts';
|
||||
|
||||
export const hasStepInUrl = (
|
||||
_ctx: unknown,
|
||||
|
@ -72,6 +79,8 @@ export const designWithAiStateMachineDefinition = createMachine(
|
|||
aiSuggestions: {
|
||||
defaultColorPalette: {} as ColorPaletteResponse,
|
||||
fontPairing: '' as FontPairing[ 'pair_name' ],
|
||||
header: '' as Header[ 'slug' ],
|
||||
footer: '' as Footer[ 'slug' ],
|
||||
},
|
||||
},
|
||||
initial: 'navigate',
|
||||
|
@ -312,6 +321,44 @@ export const designWithAiStateMachineDefinition = createMachine(
|
|||
},
|
||||
},
|
||||
},
|
||||
chooseHeader: {
|
||||
invoke: {
|
||||
src: 'queryAiEndpoint',
|
||||
data: ( context ) => {
|
||||
return {
|
||||
...defaultHeader,
|
||||
prompt: defaultHeader.prompt(
|
||||
context.businessInfoDescription
|
||||
.descriptionText,
|
||||
context.lookAndFeel.choice,
|
||||
context.toneOfVoice.choice
|
||||
),
|
||||
};
|
||||
},
|
||||
onDone: {
|
||||
actions: [ 'assignHeader' ],
|
||||
},
|
||||
},
|
||||
},
|
||||
chooseFooter: {
|
||||
invoke: {
|
||||
src: 'queryAiEndpoint',
|
||||
data: ( context ) => {
|
||||
return {
|
||||
...defaultFooter,
|
||||
prompt: defaultFooter.prompt(
|
||||
context.businessInfoDescription
|
||||
.descriptionText,
|
||||
context.lookAndFeel.choice,
|
||||
context.toneOfVoice.choice
|
||||
),
|
||||
};
|
||||
},
|
||||
onDone: {
|
||||
actions: [ 'assignFooter' ],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
postApiCallLoader: {},
|
||||
|
|
|
@ -7,8 +7,10 @@ import { z } from 'zod';
|
|||
*/
|
||||
import {
|
||||
colorPaletteValidator,
|
||||
colorPaletteResponseValidator,
|
||||
fontChoiceValidator,
|
||||
headerValidator,
|
||||
footerValidator,
|
||||
colorPaletteResponseValidator,
|
||||
} from './prompts';
|
||||
|
||||
export type designWithAiStateMachineContext = {
|
||||
|
@ -24,6 +26,8 @@ export type designWithAiStateMachineContext = {
|
|||
aiSuggestions: {
|
||||
defaultColorPalette: ColorPaletteResponse;
|
||||
fontPairing: FontPairing[ 'pair_name' ];
|
||||
header: Header[ 'slug' ];
|
||||
footer: Footer[ 'slug' ];
|
||||
};
|
||||
// If we require more data from options, previously provided core profiler details,
|
||||
// we can retrieve them in preBusinessInfoDescription and then assign them here
|
||||
|
@ -60,3 +64,7 @@ export type ColorPaletteResponse = z.infer<
|
|||
>;
|
||||
|
||||
export type FontPairing = z.infer< typeof fontChoiceValidator >;
|
||||
|
||||
export type Header = z.infer< typeof headerValidator >;
|
||||
|
||||
export type Footer = z.infer< typeof footerValidator >;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add cys ai header/footer
|
Loading…
Reference in New Issue