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:
parent
cff7ee6ccc
commit
b0ee77621f
|
@ -12,6 +12,7 @@ import {
|
|||
ColorPalette,
|
||||
designWithAiStateMachineContext,
|
||||
designWithAiStateMachineEvents,
|
||||
FontPairing,
|
||||
LookAndToneCompletionResponse,
|
||||
} from './types';
|
||||
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 = () => {
|
||||
// log AI API request error
|
||||
// eslint-disable-next-line no-console
|
||||
|
@ -158,6 +177,7 @@ export const actions = {
|
|||
assignToneOfVoice,
|
||||
assignLookAndTone,
|
||||
assignDefaultColorPalette,
|
||||
assignFontPairing,
|
||||
logAIAPIRequestError,
|
||||
updateQueryStep,
|
||||
recordTracksStepViewed,
|
||||
|
|
|
@ -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,
|
||||
};
|
|
@ -1,2 +1,3 @@
|
|||
export * from './colorChoices';
|
||||
export * from './lookAndTone';
|
||||
export * from './fontPairings';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { defaultColorPalette } from '.';
|
||||
import { defaultColorPalette } from '..';
|
||||
|
||||
describe( 'colorPairing.responseValidation', () => {
|
||||
it( 'should validate a correct color palette', () => {
|
|
@ -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\\"
|
||||
}
|
||||
]"
|
||||
` );
|
||||
} );
|
||||
} );
|
|
@ -1,8 +1,8 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { LookAndToneCompletionResponse } from '../types';
|
||||
import { lookAndTone } from '.';
|
||||
import { LookAndToneCompletionResponse } from '../../types';
|
||||
import { lookAndTone } from '..';
|
||||
|
||||
describe( 'parseLookAndToneCompletionResponse', () => {
|
||||
it( 'should return a valid object when given valid JSON', () => {
|
|
@ -11,6 +11,7 @@ import {
|
|||
designWithAiStateMachineContext,
|
||||
designWithAiStateMachineEvents,
|
||||
ColorPalette,
|
||||
FontPairing,
|
||||
} from './types';
|
||||
import {
|
||||
BusinessInfoDescription,
|
||||
|
@ -20,7 +21,7 @@ import {
|
|||
} from './pages';
|
||||
import { actions } from './actions';
|
||||
import { services } from './services';
|
||||
import { defaultColorPalette } from './prompts';
|
||||
import { defaultColorPalette, fontPairings } from './prompts';
|
||||
|
||||
export const hasStepInUrl = (
|
||||
_ctx: unknown,
|
||||
|
@ -70,6 +71,7 @@ export const designWithAiStateMachineDefinition = createMachine(
|
|||
},
|
||||
aiSuggestions: {
|
||||
defaultColorPalette: {} as ColorPalette,
|
||||
fontPairing: '' as FontPairing[ 'pair_name' ],
|
||||
},
|
||||
},
|
||||
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: {},
|
||||
|
|
|
@ -5,7 +5,7 @@ import { z } from 'zod';
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { colorPaletteValidator } from './prompts';
|
||||
import { colorPaletteValidator, fontChoiceValidator } from './prompts';
|
||||
|
||||
export type designWithAiStateMachineContext = {
|
||||
businessInfoDescription: {
|
||||
|
@ -19,6 +19,7 @@ export type designWithAiStateMachineContext = {
|
|||
};
|
||||
aiSuggestions: {
|
||||
defaultColorPalette: ColorPalette;
|
||||
fontPairing: FontPairing[ 'pair_name' ];
|
||||
};
|
||||
// If we require more data from options, previously provided core profiler details,
|
||||
// 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 FontPairing = z.infer< typeof fontChoiceValidator >;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add customize store AI wizard call for font pairing suggestion
|
Loading…
Reference in New Issue