diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/big-sky-icon.svg b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/big-sky-icon.svg new file mode 100644 index 0000000000000..9ae8660e1f429 --- /dev/null +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/big-sky-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/index.tsx b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/index.tsx index 6bda862f650d7..51a2b0f86b80f 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/index.tsx +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/index.tsx @@ -1,5 +1,5 @@ import { useModuleStatus } from '@automattic/jetpack-shared-extension-utils'; -import { Button, TextControl, SVG, Circle } from '@wordpress/components'; +import { Button, TextControl, SVG, Circle, Icon } from '@wordpress/components'; import { useState, useCallback, @@ -8,11 +8,13 @@ import { createInterpolateElement, } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; +import { arrowRight } from '@wordpress/icons'; import clsx from 'clsx'; import debugFactory from 'debug'; import { SeoPlaceholder } from '../../../../plugins/seo/components/placeholder'; import usePostContent from '../../hooks/use-post-content'; import './style.scss'; +import bigSkyIcon from './big-sky-icon.svg'; type StepType = 'input' | 'options' | 'completion'; @@ -20,6 +22,7 @@ interface Message { id: string; content: string | React.ReactNode; isUser?: boolean; + showIcon?: boolean; } interface Option { @@ -31,7 +34,7 @@ interface Option { interface BaseStep { id: string; title: string; - messages: string[] | React.ReactNode[]; + messages: StepMessage[]; type: StepType; onStart?: () => void; } @@ -76,6 +79,11 @@ const TypingMessage = () => { ); }; +interface StepMessage { + content: string | React.ReactNode; + showIcon?: boolean; +} + export default function SeoAssistant( { disabled, onStep }: SeoAssistantProps ) { const [ isOpen, setIsOpen ] = useState( false ); const [ currentStep, setCurrentStep ] = useState( 0 ); @@ -99,13 +107,14 @@ export default function SeoAssistant( { disabled, onStep }: SeoAssistantProps ) scrollToBottom(); }, [ messages ] ); - const addMessage = ( content: string | React.ReactNode, isUser = false ) => { + const addMessage = ( content: string | React.ReactNode, isUser = false, showIcon = ! isUser ) => { setMessages( prev => [ ...prev, { id: `message-${ prev.length }`, content, isUser, + showIcon, }, ] ); }; @@ -296,15 +305,30 @@ export default function SeoAssistant( { disabled, onStep }: SeoAssistantProps ) id: 'keywords', title: __( 'Optimise for SEO', 'jetpack' ), messages: [ - __( "Hi there! 👋 Let's optimise your blog post for SEO.", 'jetpack' ), - createInterpolateElement( - __( - "Here's what we can improve:
1. Keywords
2. Title
3. Meta description", + { + content: createInterpolateElement( + __( "Hi there! 👋 Let's optimise your blog post for SEO.", 'jetpack' ), + { b: } + ), + showIcon: true, + }, + { + content: createInterpolateElement( + __( + "Here's what we can improve:
1. Keywords
2. Title
3. Meta description", + 'jetpack' + ), + { br:
} + ), + showIcon: false, + }, + { + content: __( + 'To start, please enter 1–3 focus keywords that describe your blog post.', 'jetpack' ), - { br:
} - ), - __( 'To start, please enter 1–3 focus keywords that describe your blog post.', 'jetpack' ), + showIcon: true, + }, ], type: 'input', placeholder: __( 'Photography, plants', 'jetpack' ), @@ -313,7 +337,12 @@ export default function SeoAssistant( { disabled, onStep }: SeoAssistantProps ) { id: 'title', title: __( 'Optimise Title', 'jetpack' ), - messages: [ __( "Let's optimise your title.", 'jetpack' ) ], + messages: [ + { + content: __( "Let's optimise your title.", 'jetpack' ), + showIcon: true, + }, + ], type: 'options', options: titleOptions, onSelect: handleTitleSelect, @@ -326,32 +355,50 @@ export default function SeoAssistant( { disabled, onStep }: SeoAssistantProps ) { id: 'meta', title: __( 'Add meta description', 'jetpack' ), - messages: [ __( "Now, let's optimize your meta description.", 'jetpack' ) ], + messages: [ + { + content: __( "Now, let's optimize your meta description.", 'jetpack' ), + showIcon: true, + }, + ], type: 'options', options: metaDescriptionOptions, onSelect: handleMetaDescriptionSelect, onSubmit: handleMetaDescriptionSubmit, submitCtaLabel: __( 'Insert', 'jetpack' ), - onRetry: handleMetaDescriptionRegenerate, // Reuse the same handler for now + onRetry: handleMetaDescriptionRegenerate, onRetryCtaLabel: __( 'Regenerate', 'jetpack' ), - onStart: handleMetaDescriptionGenerate, // Reuse the same handler for now + onStart: handleMetaDescriptionGenerate, }, { id: 'completion', title: __( 'Your post is SEO-ready', 'jetpack' ), messages: [ - __( "Here's your updated checklist:", 'jetpack' ), - createInterpolateElement( - __( '✅ Keywords
✅ Title
✅ Meta description', 'jetpack' ), - { br:
} - ), - createInterpolateElement( - __( - 'SEO optimization complete! 🎉
Your blog post is now search-engine friendly.
Happy blogging! 😊', - 'jetpack' + { + content: __( "Here's your updated checklist:", 'jetpack' ), + showIcon: true, + }, + { + content: createInterpolateElement( + __( '✅ Keywords
✅ Title
✅ Meta description', 'jetpack' ), + { br:
} + ), + showIcon: false, + }, + { + content: createInterpolateElement( + __( + 'SEO optimization complete! 🎉
Your blog post is now search-engine friendly.', + 'jetpack' + ), + { br:
} ), - { br:
} - ), + showIcon: true, + }, + { + content: __( 'Happy blogging! 😊', 'jetpack' ), + showIcon: false, + }, ], type: 'completion', }, @@ -362,7 +409,9 @@ export default function SeoAssistant( { disabled, onStep }: SeoAssistantProps ) useEffect( () => { if ( isOpen && messages.length === 0 ) { // Initialize with first step messages - currentStepData.messages.forEach( message => addMessage( message ) ); + currentStepData.messages.forEach( message => + addMessage( message.content, false, message.showIcon ) + ); } }, [ isOpen, currentStepData.messages, messages ] ); @@ -371,7 +420,9 @@ export default function SeoAssistant( { disabled, onStep }: SeoAssistantProps ) debug( 'moving to ' + ( currentStep + 1 ), steps[ currentStep + 1 ] ); setCurrentStep( currentStep + 1 ); // Add next step messages - steps[ currentStep + 1 ].messages.forEach( message => addMessage( message ) ); + steps[ currentStep + 1 ].messages.forEach( message => + addMessage( message.content, false, message.showIcon ) + ); steps[ currentStep + 1 ].onStart?.(); } }; @@ -380,7 +431,9 @@ export default function SeoAssistant( { disabled, onStep }: SeoAssistantProps ) if ( currentStep > 0 ) { setCurrentStep( currentStep - 1 ); // Re-add previous step messages - steps[ currentStep - 1 ].messages.forEach( message => addMessage( message ) ); + steps[ currentStep - 1 ].messages.forEach( message => + addMessage( message.content, false, message.showIcon ) + ); } }; @@ -406,8 +459,9 @@ export default function SeoAssistant( { disabled, onStep }: SeoAssistantProps ) currentStepData.onSubmit?.( keywords ); handleNext(); } } + size="small" > - { __( '↑', 'jetpack' ) } + ↑ ); @@ -415,36 +469,23 @@ export default function SeoAssistant( { disabled, onStep }: SeoAssistantProps ) if ( currentStepData.type === 'options' ) { const selectedOption = currentStepData.options.find( opt => opt.selected ); - return ( -
- { currentStepData.options.map( option => ( - - ) ) } -
- - { selectedOption && ( - - ) } -
+
+ + +
); } @@ -461,7 +502,51 @@ export default function SeoAssistant( { disabled, onStep }: SeoAssistantProps ) return null; }; - debug( isModuleActive, isLoadingModules ); + + const renderMessages = () => { + return messages.map( message => ( +
+
+ { message.showIcon && ( + { + ) } +
+
{ message.content }
+
+ ) ); + }; + + const renderOptions = () => { + if ( currentStepData.type !== 'options' || ! currentStepData.options.length ) { + return null; + } + + return ( +
+
+
+
+ { currentStepData.options.map( option => ( + + ) ) } +
+
+
+ ); + }; return (
@@ -473,6 +558,8 @@ export default function SeoAssistant( { disabled, onStep }: SeoAssistantProps ) disabled={ isLoadingModules || isOpen || ! postContent.trim?.() || disabled } isBusy={ isLoadingModules || isOpen } > + { +   { __( 'SEO Assistant', 'jetpack' ) } ) } @@ -497,16 +584,8 @@ export default function SeoAssistant( { disabled, onStep }: SeoAssistantProps )
- { messages.map( message => ( -
- { message.content } -
- ) ) } + { renderMessages() } + { renderOptions() }
diff --git a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/style.scss b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/style.scss index 5bb12113e5fe3..c1db241e30f3b 100644 --- a/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/style.scss +++ b/projects/plugins/jetpack/extensions/plugins/ai-assistant-plugin/components/seo-assistant/style.scss @@ -3,12 +3,11 @@ bottom: 32px; left: 50%; transform: translateX(-50%); - width: 90%; - max-width: 600px; - height: 600px; + width: 384px; + height: 434px; background: white; - border-radius: 16px; - box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + border-radius: 24px; + outline: 0.5px solid var( --jp-gray-5 ); z-index: 1000; display: flex; flex-direction: column; @@ -19,7 +18,7 @@ align-items: center; justify-content: space-between; padding: 16px; - border-bottom: 1px solid #e5e7eb; + // border-bottom: 1px solid #e5e7eb; background: white; border-radius: 16px 16px 0 0; @@ -55,7 +54,7 @@ display: flex; flex-direction: column; gap: 16px; - padding: 16px; + padding: 16px 24px; overflow-y: auto; scroll-behavior: smooth; align-items: flex-start; @@ -68,46 +67,90 @@ /* Hide scrollbar for IE, Edge and Firefox */ -ms-overflow-style: none; scrollbar-width: none; + + mask-image: linear-gradient( 180deg, transparent, white 24px ); } &__message { - padding: 12px 16px; + // padding: 12px 16px; border-radius: 16px; - background: #e0e7ff; + // background: #e0e7ff; white-space: pre-line; animation: messageAppear 0.3s ease-out; - font-size: 14px; + font-size: 13px; line-height: 1.5; + display: flex; + align-items: center; + min-width: 48px; + + .seo-assistant-wizard__message-icon { + // height: 26px; + // width: 26px; + // text-align: center; + // line-height: 4; + // flex: 0 1 26px; + flex-shrink: 0; + align-self: center; + flex-basis: 26px; + + img { + vertical-align: middle; + } + } + + .seo-assistant-wizard__message-text { + padding: 8px 12px; + flex: 1 0 200px; + } &.is-user { background: #f3f4f6; align-self: flex-end; - border-bottom-right-radius: 4px; + + .seo-assistant-wizard__message-icon { + flex-basis: unset; + } } - &:not(.is-user) { - border-bottom-left-radius: 4px; - } + // &:not(.is-user) { + // border-bottom-left-radius: 4px; + // } } &__input-container { flex: 0 0 auto; padding: 16px; background: white; - border-top: 1px solid #e5e7eb; + // border-top: 1px solid #e5e7eb; border-radius: 0 0 16px 16px; + border-top: 1px solid var( --jp-gray-5, #e5e7eb ); } &__input { display: flex; gap: 8px; + border-radius: 12px; + outline: 1px solid var( --jp-gray-10, #c3c3c3 ); + align-items: center; + padding-right: 6px; + height: 44px; + + &:focus-within { + outline-width: 2px; + outline-color: var( --wp-components-color-accent, var( --wp-admin-theme-color, #007cba ) ); + } .components-base-control { flex-grow: 1; } - .components-text-control__input { - border-radius: 24px; - padding: 8px 16px; + .components-text-control__input, + .components-text-control__input:focus { + padding: 8px; + border: 0; + border-radius: 12px; + box-shadow: none; + outline: 0; + // .components-text-control__input[type=text] } } @@ -141,54 +184,55 @@ &.is-selected { background: #fff; - border-color: #4f46e5; - box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1); + border-color: var( --wp-components-color-accent, var( --wp-admin-theme-color, #007cba ) ); + // box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1); } } &__actions { display: flex; - justify-content: space-between; + justify-content: flex-end; align-items: center; - margin-top: 16px; - padding-top: 16px; - border-top: 1px solid #e5e7eb; + // margin-top: 16px; + // padding-top: 16px; + // border-top: 1px solid #e5e7eb; + gap: 16px; .components-button { - height: 40px; - padding: 8px 16px; + // height: 40px; + // padding: 8px 16px; border-radius: 20px; - font-size: 14px; + // font-size: 14px; - &.is-secondary { - color: #4b5563; - } + // &.is-secondary { + // color: #4b5563; + // } - &.is-primary { - background: #4f46e5; + // &.is-primary { + // background: #4f46e5; - &:hover { - background: #4338ca; - } - } + // &:hover { + // background: #4338ca; + // } + // } } } &__completion { display: flex; justify-content: flex-end; - padding-top: 16px; - border-top: 1px solid #e5e7eb; + // padding-top: 16px; + // border-top: 1px solid #e5e7eb; .components-button { - height: 40px; - padding: 8px 24px; + // height: 40px; + // padding: 8px 24px; border-radius: 20px; - font-size: 14px; - background: #4f46e5; + // font-size: 14px; + // background: #4f46e5; &:hover { - background: #4338ca; + // background: #4338ca; } } }