add: cys ai font pairing (#40240)

* add: cys ai font pairing suggestion

* Move cys ai tests to test folder

* Update plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/fontPairings.ts

Co-authored-by: Chi-Hsuan Huang <chihsuan.tw@gmail.com>

---------

Co-authored-by: Chi-Hsuan Huang <chihsuan.tw@gmail.com>
This commit is contained in:
RJ 2023-09-20 14:26:15 +10:00 committed by GitHub
parent cff7ee6ccc
commit b0ee77621f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 221 additions and 5 deletions

View File

@ -12,6 +12,7 @@ import {
ColorPalette, ColorPalette,
designWithAiStateMachineContext, designWithAiStateMachineContext,
designWithAiStateMachineEvents, designWithAiStateMachineEvents,
FontPairing,
LookAndToneCompletionResponse, LookAndToneCompletionResponse,
} from './types'; } from './types';
import { aiWizardClosedBeforeCompletionEvent } from './events'; import { aiWizardClosedBeforeCompletionEvent } from './events';
@ -91,6 +92,24 @@ const assignDefaultColorPalette = assign<
}, },
} ); } );
const assignFontPairing = assign<
designWithAiStateMachineContext,
designWithAiStateMachineEvents
>( {
aiSuggestions: ( context, event: unknown ) => {
return {
...context.aiSuggestions,
fontPairing: (
event as {
data: {
response: FontPairing;
};
}
).data.response.pair_name,
};
},
} );
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
@ -158,6 +177,7 @@ export const actions = {
assignToneOfVoice, assignToneOfVoice,
assignLookAndTone, assignLookAndTone,
assignDefaultColorPalette, assignDefaultColorPalette,
assignFontPairing,
logAIAPIRequestError, logAIAPIRequestError,
updateQueryStep, updateQueryStep,
recordTracksStepViewed, recordTracksStepViewed,

View File

@ -0,0 +1,120 @@
/**
* External dependencies
*/
import { z } from 'zod';
/** This block below was generated by ChatGPT using GPT-4 on 2023-09-18 */
/** Original source: plugins/woocommerce-admin/client/customize-store/assembler-hub/sidebar/global-styles/font-pairing-variations/constants.ts */
const fontChoices = [
{
pair_name: 'Bodoni Moda + Overpass',
fonts: {
'Bodoni Moda':
'A modern serif font with high contrast between thick and thin lines, commonly used for headings.',
Overpass:
'A clean, modern sans-serif, originally inspired by Highway Gothic. Good for text and UI elements.',
},
settings:
'Overpass is used for buttons and general typography, while Bodoni Moda is specified for headings and some core blocks like site title and post navigation link.',
},
{
pair_name: 'Commissioner + Crimson Pro',
fonts: {
Commissioner:
'A low-contrast, geometric sans-serif, designed for legibility and readability in long texts.',
'Crimson Pro':
'A serif typeface designed for readability and long-form text.',
},
settings:
'Commissioner dominates elements like buttons, headings, and core blocks, while Crimson Pro is set for general typography.',
},
{
pair_name: 'Libre Baskerville + DM Sans',
fonts: {
'Libre Baskerville':
'A serif typeface with a classic feel, good for long reading and often used for body text in books.',
'DM Sans':
'A clean, geometric sans-serif, often used for UI and short text.',
},
settings:
'Libre Baskerville is used for headings and core blocks, whereas DM Sans is used for buttons and general typography.',
},
{
pair_name: 'Libre Franklin + EB Garamond',
fonts: {
'Libre Franklin':
'A sans-serif that offers readability, suitable for both text and display.',
'EB Garamond':
"A revival of the classical 'Garamond' typefaces, suitable for long-form text.",
},
settings:
'Libre Franklin is predominantly used for elements like buttons, headings, and core blocks. EB Garamond is set for general typography.',
},
{
pair_name: 'Montserrat + Arvo',
fonts: {
Montserrat:
'A geometric sans-serif, popular for its modern clean lines.',
Arvo: 'A slab-serif font with a more traditional feel, suitable for print and screen.',
},
settings:
'Montserrat is used for buttons, headings, and core blocks. Arvo is used for general typography.',
},
{
pair_name: 'Playfair Display + Fira Sans',
fonts: {
'Playfair Display':
'A high-contrast serif designed for headings and offers a modern take on older serif fonts.',
'Fira Sans':
'A sans-serif designed for readability at small sizes, making it suitable for both UI and text.',
},
settings:
'Playfair Display is used in italics for headings and core blocks, while Fira Sans is used for buttons and general typography.',
},
{
pair_name: 'Rubik + Inter',
fonts: {
Rubik: 'A sans-serif with slightly rounded corners, designed for a softer, more modern look.',
Inter: 'A highly legible sans-serif, optimized for UI design.',
},
settings:
'Rubik is applied for headings and core blocks. Inter is used for buttons and general typography.',
},
{
pair_name: 'Space Mono + Roboto',
fonts: {
'Space Mono': 'A monospace typeface with a futuristic vibe.',
Roboto: 'A neo-grotesque sans-serif, known for its flexibility and modern design.',
},
settings:
'Space Mono is used for headings, while Roboto takes care of buttons and general typography.',
},
];
const allowedFontChoices = fontChoices.map( ( config ) => config.pair_name );
export const fontChoiceValidator = z.object( {
pair_name: z
.string()
.refine( ( name ) => allowedFontChoices.includes( name ), {
message: 'Font choice not part of allowed list',
} ),
} );
export const fontPairings = {
queryId: 'font_pairings',
version: '2023-09-18',
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 font pairing.
Respond only with one font pairing and and in the format: '{"pair_name":"font 1 + font 2"}'.
Chosen look and tone: ${ look } look, ${ tone } tone.
Business description: ${ businessDescription }
Font pairings to choose from:
${ JSON.stringify( fontChoices ) }
`;
},
responseValidation: fontChoiceValidator.parse,
};

View File

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

View File

@ -1,7 +1,7 @@
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { defaultColorPalette } from '.'; import { defaultColorPalette } from '..';
describe( 'colorPairing.responseValidation', () => { describe( 'colorPairing.responseValidation', () => {
it( 'should validate a correct color palette', () => { it( 'should validate a correct color palette', () => {

View File

@ -0,0 +1,47 @@
/**
* Internal dependencies
*/
import { fontChoiceValidator } from '..';
describe( 'fontChoiceValidator', () => {
it( 'should validate when font choice is part of the allowed list', () => {
const validFontChoice = { pair_name: 'Montserrat + Arvo' };
expect( () =>
fontChoiceValidator.parse( validFontChoice )
).not.toThrow();
} );
it( 'should not validate when font choice is not part of the allowed list', () => {
const invalidFontChoice = { pair_name: 'Comic Sans' };
expect( () => fontChoiceValidator.parse( invalidFontChoice ) )
.toThrowErrorMatchingInlineSnapshot( `
"[
{
\\"code\\": \\"custom\\",
\\"message\\": \\"Font choice not part of allowed list\\",
\\"path\\": [
\\"pair_name\\"
]
}
]"
` );
} );
it( 'should not validate when pair_name is not a string', () => {
const invalidType = { pair_name: 123 };
expect( () => fontChoiceValidator.parse( invalidType ) )
.toThrowErrorMatchingInlineSnapshot( `
"[
{
\\"code\\": \\"invalid_type\\",
\\"expected\\": \\"string\\",
\\"received\\": \\"number\\",
\\"path\\": [
\\"pair_name\\"
],
\\"message\\": \\"Expected string, received number\\"
}
]"
` );
} );
} );

View File

@ -1,8 +1,8 @@
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { LookAndToneCompletionResponse } from '../types'; import { LookAndToneCompletionResponse } from '../../types';
import { lookAndTone } from '.'; import { lookAndTone } from '..';
describe( 'parseLookAndToneCompletionResponse', () => { describe( 'parseLookAndToneCompletionResponse', () => {
it( 'should return a valid object when given valid JSON', () => { it( 'should return a valid object when given valid JSON', () => {

View File

@ -11,6 +11,7 @@ import {
designWithAiStateMachineContext, designWithAiStateMachineContext,
designWithAiStateMachineEvents, designWithAiStateMachineEvents,
ColorPalette, ColorPalette,
FontPairing,
} from './types'; } from './types';
import { import {
BusinessInfoDescription, BusinessInfoDescription,
@ -20,7 +21,7 @@ import {
} from './pages'; } from './pages';
import { actions } from './actions'; import { actions } from './actions';
import { services } from './services'; import { services } from './services';
import { defaultColorPalette } from './prompts'; import { defaultColorPalette, fontPairings } from './prompts';
export const hasStepInUrl = ( export const hasStepInUrl = (
_ctx: unknown, _ctx: unknown,
@ -70,6 +71,7 @@ export const designWithAiStateMachineDefinition = createMachine(
}, },
aiSuggestions: { aiSuggestions: {
defaultColorPalette: {} as ColorPalette, defaultColorPalette: {} as ColorPalette,
fontPairing: '' as FontPairing[ 'pair_name' ],
}, },
}, },
initial: 'navigate', initial: 'navigate',
@ -291,6 +293,25 @@ export const designWithAiStateMachineDefinition = createMachine(
}, },
}, },
}, },
chooseFontPairing: {
invoke: {
src: 'queryAiEndpoint',
data: ( context ) => {
return {
...fontPairings,
prompt: fontPairings.prompt(
context.businessInfoDescription
.descriptionText,
context.lookAndFeel.choice,
context.toneOfVoice.choice
),
};
},
onDone: {
actions: [ 'assignFontPairing' ],
},
},
},
}, },
}, },
postApiCallLoader: {}, postApiCallLoader: {},

View File

@ -5,7 +5,7 @@ import { z } from 'zod';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { colorPaletteValidator } from './prompts'; import { colorPaletteValidator, fontChoiceValidator } from './prompts';
export type designWithAiStateMachineContext = { export type designWithAiStateMachineContext = {
businessInfoDescription: { businessInfoDescription: {
@ -19,6 +19,7 @@ export type designWithAiStateMachineContext = {
}; };
aiSuggestions: { aiSuggestions: {
defaultColorPalette: ColorPalette; defaultColorPalette: ColorPalette;
fontPairing: FontPairing[ 'pair_name' ];
}; };
// 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
@ -50,3 +51,5 @@ export interface LookAndToneCompletionResponse {
} }
export type ColorPalette = z.infer< typeof colorPaletteValidator >; export type ColorPalette = z.infer< typeof colorPaletteValidator >;
export type FontPairing = z.infer< typeof fontChoiceValidator >;

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add customize store AI wizard call for font pairing suggestion