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:
Chi-Hsuan Huang 2023-09-21 09:49:00 +08:00 committed by GitHub
parent b9b4dc852c
commit 48342fbc66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 295 additions and 2 deletions

View File

@ -14,6 +14,8 @@ import {
designWithAiStateMachineEvents, designWithAiStateMachineEvents,
FontPairing, FontPairing,
LookAndToneCompletionResponse, LookAndToneCompletionResponse,
Header,
Footer,
} from './types'; } from './types';
import { aiWizardClosedBeforeCompletionEvent } from './events'; import { aiWizardClosedBeforeCompletionEvent } from './events';
import { 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 = () => { const logAIAPIRequestError = () => {
// log AI API request error // log AI API request error
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -178,6 +216,8 @@ export const actions = {
assignLookAndTone, assignLookAndTone,
assignDefaultColorPalette, assignDefaultColorPalette,
assignFontPairing, assignFontPairing,
assignHeader,
assignFooter,
logAIAPIRequestError, logAIAPIRequestError,
updateQueryStep, updateQueryStep,
recordTracksStepViewed, recordTracksStepViewed,

View File

@ -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,
};

View File

@ -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,
};

View File

@ -1,3 +1,5 @@
export * from './colorChoices'; export * from './colorChoices';
export * from './lookAndTone'; export * from './lookAndTone';
export * from './fontPairings'; export * from './fontPairings';
export * from './header';
export * from './footer';

View File

@ -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\\"
}
]"
` );
} );
} );

View File

@ -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\\"
}
]"
` );
} );
} );

View File

@ -11,6 +11,8 @@ import {
designWithAiStateMachineContext, designWithAiStateMachineContext,
designWithAiStateMachineEvents, designWithAiStateMachineEvents,
FontPairing, FontPairing,
Header,
Footer,
ColorPaletteResponse, ColorPaletteResponse,
} from './types'; } from './types';
import { import {
@ -21,7 +23,12 @@ import {
} from './pages'; } from './pages';
import { actions } from './actions'; import { actions } from './actions';
import { services } from './services'; import { services } from './services';
import { defaultColorPalette, fontPairings } from './prompts'; import {
defaultColorPalette,
fontPairings,
defaultHeader,
defaultFooter,
} from './prompts';
export const hasStepInUrl = ( export const hasStepInUrl = (
_ctx: unknown, _ctx: unknown,
@ -72,6 +79,8 @@ export const designWithAiStateMachineDefinition = createMachine(
aiSuggestions: { aiSuggestions: {
defaultColorPalette: {} as ColorPaletteResponse, defaultColorPalette: {} as ColorPaletteResponse,
fontPairing: '' as FontPairing[ 'pair_name' ], fontPairing: '' as FontPairing[ 'pair_name' ],
header: '' as Header[ 'slug' ],
footer: '' as Footer[ 'slug' ],
}, },
}, },
initial: 'navigate', 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: {}, postApiCallLoader: {},

View File

@ -7,8 +7,10 @@ import { z } from 'zod';
*/ */
import { import {
colorPaletteValidator, colorPaletteValidator,
colorPaletteResponseValidator,
fontChoiceValidator, fontChoiceValidator,
headerValidator,
footerValidator,
colorPaletteResponseValidator,
} from './prompts'; } from './prompts';
export type designWithAiStateMachineContext = { export type designWithAiStateMachineContext = {
@ -24,6 +26,8 @@ export type designWithAiStateMachineContext = {
aiSuggestions: { aiSuggestions: {
defaultColorPalette: ColorPaletteResponse; defaultColorPalette: ColorPaletteResponse;
fontPairing: FontPairing[ 'pair_name' ]; fontPairing: FontPairing[ 'pair_name' ];
header: Header[ 'slug' ];
footer: Footer[ 'slug' ];
}; };
// If we require more data from options, previously provided core profiler details, // If we require more data from options, previously provided core profiler details,
// we can retrieve them in preBusinessInfoDescription and then assign them here // 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 FontPairing = z.infer< typeof fontChoiceValidator >;
export type Header = z.infer< typeof headerValidator >;
export type Footer = z.infer< typeof footerValidator >;

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add cys ai header/footer