From b0ee77621fe0d097a0d91eaed072ff6125f440c2 Mon Sep 17 00:00:00 2001 From: RJ <27843274+rjchow@users.noreply.github.com> Date: Wed, 20 Sep 2023 14:26:15 +1000 Subject: [PATCH] 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 --------- Co-authored-by: Chi-Hsuan Huang --- .../customize-store/design-with-ai/actions.ts | 20 +++ .../design-with-ai/prompts/fontPairings.ts | 120 ++++++++++++++++++ .../design-with-ai/prompts/index.ts | 1 + .../prompts/{ => test}/colorChoices.test.ts | 2 +- .../prompts/test/fontPairings.test.ts | 47 +++++++ .../prompts/{ => test}/lookAndTone.test.ts | 4 +- .../design-with-ai/state-machine.tsx | 23 +++- .../customize-store/design-with-ai/types.ts | 5 +- .../add-customize-store-font-pairing | 4 + 9 files changed, 221 insertions(+), 5 deletions(-) create mode 100644 plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/fontPairings.ts rename plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/{ => test}/colorChoices.test.ts (98%) create mode 100644 plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/fontPairings.test.ts rename plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/{ => test}/lookAndTone.test.ts (89%) create mode 100644 plugins/woocommerce/changelog/add-customize-store-font-pairing diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/actions.ts b/plugins/woocommerce-admin/client/customize-store/design-with-ai/actions.ts index 661504275c1..ed7d6312f73 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/actions.ts +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/actions.ts @@ -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, diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/fontPairings.ts b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/fontPairings.ts new file mode 100644 index 00000000000..e8af1cb7549 --- /dev/null +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/fontPairings.ts @@ -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, +}; diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/index.ts b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/index.ts index ee134ed5941..b0533ec1dc8 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/index.ts +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/index.ts @@ -1,2 +1,3 @@ export * from './colorChoices'; export * from './lookAndTone'; +export * from './fontPairings'; diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/colorChoices.test.ts b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/colorChoices.test.ts similarity index 98% rename from plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/colorChoices.test.ts rename to plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/colorChoices.test.ts index 91c7d0df06e..3903c3fce3e 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/colorChoices.test.ts +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/colorChoices.test.ts @@ -1,7 +1,7 @@ /** * Internal dependencies */ -import { defaultColorPalette } from '.'; +import { defaultColorPalette } from '..'; describe( 'colorPairing.responseValidation', () => { it( 'should validate a correct color palette', () => { diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/fontPairings.test.ts b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/fontPairings.test.ts new file mode 100644 index 00000000000..dfb132a8e24 --- /dev/null +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/fontPairings.test.ts @@ -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\\" + } + ]" + ` ); + } ); +} ); diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/lookAndTone.test.ts b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/lookAndTone.test.ts similarity index 89% rename from plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/lookAndTone.test.ts rename to plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/lookAndTone.test.ts index cea15cc91b2..a5957d25a3e 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/lookAndTone.test.ts +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/prompts/test/lookAndTone.test.ts @@ -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', () => { diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/state-machine.tsx b/plugins/woocommerce-admin/client/customize-store/design-with-ai/state-machine.tsx index 58dd0367583..1f1ff61d994 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/state-machine.tsx +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/state-machine.tsx @@ -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: {}, diff --git a/plugins/woocommerce-admin/client/customize-store/design-with-ai/types.ts b/plugins/woocommerce-admin/client/customize-store/design-with-ai/types.ts index 743d094d1c4..b44a81cd4be 100644 --- a/plugins/woocommerce-admin/client/customize-store/design-with-ai/types.ts +++ b/plugins/woocommerce-admin/client/customize-store/design-with-ai/types.ts @@ -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 >; diff --git a/plugins/woocommerce/changelog/add-customize-store-font-pairing b/plugins/woocommerce/changelog/add-customize-store-font-pairing new file mode 100644 index 00000000000..e5724ead763 --- /dev/null +++ b/plugins/woocommerce/changelog/add-customize-store-font-pairing @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add customize store AI wizard call for font pairing suggestion