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,
|
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,
|
||||||
|
|
|
@ -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 './colorChoices';
|
||||||
export * from './lookAndTone';
|
export * from './lookAndTone';
|
||||||
|
export * from './fontPairings';
|
||||||
|
|
|
@ -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', () => {
|
|
@ -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
|
* 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', () => {
|
|
@ -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: {},
|
||||||
|
|
|
@ -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 >;
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: add
|
||||||
|
|
||||||
|
Add customize store AI wizard call for font pairing suggestion
|
Loading…
Reference in New Issue