Generating the short description on the product editor (#39237)
* [Woo AI] Generate short description after long description is generated.
This commit is contained in:
parent
c4f00719de
commit
144bf08293
|
@ -4,4 +4,4 @@ Various code snippets you can add to your site to enable custom functionality:
|
|||
|
||||
- [Add a message above the login / register form](./before-login--register-form.md)
|
||||
- [Change number of related products output](./number-of-products-per-row.md)
|
||||
- [Unhook and remove WooCommerce emails](./unhook--remove-woocommerce-emails.md);
|
||||
- [Unhook and remove WooCommerce emails](./unhook--remove-woocommerce-emails.md)
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Returning promise from requestCompletion instead of event source.
|
|
@ -109,7 +109,16 @@ export const useCompletion = ( {
|
|||
|
||||
completionSource.current = suggestionsSource;
|
||||
|
||||
return suggestionsSource;
|
||||
return new Promise( ( resolve ) => {
|
||||
( completionSource.current as EventSource ).addEventListener(
|
||||
'message',
|
||||
( event: MessageEvent ) => {
|
||||
if ( event.data === '[DONE]' ) {
|
||||
resolve( event.data );
|
||||
}
|
||||
}
|
||||
);
|
||||
} );
|
||||
} catch ( e ) {
|
||||
throw createExtendedError(
|
||||
'An error occurred while connecting to the completion service',
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Generating short description after long description on product editor.
|
|
@ -2,3 +2,5 @@ declare module '*.svg' {
|
|||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
|
||||
declare module '@wordpress/data';
|
||||
|
|
|
@ -3,10 +3,6 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { createInterpolateElement, useState } from '@wordpress/element';
|
||||
// TODO: Re-add "@types/wordpress__data" package to resolve this, causing other issues until pnpm 8.6.0 is usable
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @woocommerce/dependency-group
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
|
||||
type ShowSnackbarProps = {
|
||||
|
@ -28,7 +24,10 @@ export const useFeedbackSnackbar = () => {
|
|||
onPositiveResponse,
|
||||
onNegativeResponse,
|
||||
}: ShowSnackbarProps ) => {
|
||||
const noticePromise: unknown = createNotice( 'info', label, {
|
||||
const noticePromise: Promise< NoticeItem > = createNotice(
|
||||
'info',
|
||||
label,
|
||||
{
|
||||
type: 'snackbar',
|
||||
explicitDismiss: true,
|
||||
actions: [
|
||||
|
@ -56,7 +55,9 @@ export const useFeedbackSnackbar = () => {
|
|||
),
|
||||
}
|
||||
),
|
||||
onClick: ( e: React.MouseEvent< HTMLButtonElement > ) => {
|
||||
onClick: (
|
||||
e: React.MouseEvent< HTMLButtonElement >
|
||||
) => {
|
||||
const response = (
|
||||
e.target as HTMLSpanElement
|
||||
).getAttribute( 'data-response' );
|
||||
|
@ -71,14 +72,12 @@ export const useFeedbackSnackbar = () => {
|
|||
},
|
||||
},
|
||||
],
|
||||
} );
|
||||
|
||||
( noticePromise as Promise< NoticeItem > ).then(
|
||||
( item: NoticeItem ) => {
|
||||
setNoticeId( item.notice.id );
|
||||
}
|
||||
);
|
||||
return noticePromise as Promise< NoticeItem >;
|
||||
|
||||
return noticePromise.then( ( item: NoticeItem ) => {
|
||||
setNoticeId( item.notice.id );
|
||||
} );
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
*/
|
||||
import { setTinyContent, getTinyContent } from '../utils/tiny-tools';
|
||||
|
||||
export const useTinyEditor = () => {
|
||||
return { setContent: setTinyContent, getContent: getTinyContent };
|
||||
export const useTinyEditor = ( editorId?: string ) => {
|
||||
return {
|
||||
setContent: ( str: string ) => setTinyContent( str, editorId ),
|
||||
getContent: () => getTinyContent( editorId ),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
__experimentalUseCompletion as useCompletion,
|
||||
UseCompletionError,
|
||||
} from '@woocommerce/ai';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -30,7 +31,7 @@ import { Attribute } from '../utils/types';
|
|||
|
||||
const DESCRIPTION_MAX_LENGTH = 300;
|
||||
|
||||
const getApiError = ( error: string ) => {
|
||||
const getApiError = ( error?: string ) => {
|
||||
switch ( error ) {
|
||||
case 'connection_error':
|
||||
return __(
|
||||
|
@ -39,7 +40,7 @@ const getApiError = ( error: string ) => {
|
|||
);
|
||||
default:
|
||||
return __(
|
||||
`❗ We're currently experiencing high demand for our experimental feature. Please check back in shortly.`,
|
||||
`❗ We encountered an issue with this experimental feature. Please check back in shortly.`,
|
||||
'woocommerce'
|
||||
);
|
||||
}
|
||||
|
@ -53,19 +54,29 @@ const recordDescriptionTracks = recordTracksFactory(
|
|||
);
|
||||
|
||||
export function WriteItForMeButtonContainer() {
|
||||
const { createWarningNotice } = useDispatch( 'core/notices' );
|
||||
|
||||
const titleEl = useRef< HTMLInputElement >(
|
||||
document.querySelector( '#title' )
|
||||
);
|
||||
const [ fetching, setFetching ] = useState< boolean >( false );
|
||||
const [ shortDescriptionGenerated, setShortDescriptionGenerated ] =
|
||||
useState< boolean >( false );
|
||||
const [ productTitle, setProductTitle ] = useState< string >(
|
||||
titleEl.current?.value || ''
|
||||
);
|
||||
const tinyEditor = useTinyEditor();
|
||||
|
||||
const handleCompletionError = ( error: UseCompletionError ) =>
|
||||
tinyEditor.setContent( getApiError( error.code ?? '' ) );
|
||||
const shortTinyEditor = useTinyEditor( 'excerpt' );
|
||||
|
||||
const { showSnackbar, removeSnackbar } = useFeedbackSnackbar();
|
||||
|
||||
const handleUseCompletionError = ( err: UseCompletionError ) => {
|
||||
createWarningNotice( getApiError( err.code ?? '' ) );
|
||||
setFetching( false );
|
||||
// eslint-disable-next-line no-console
|
||||
console.error( err );
|
||||
};
|
||||
|
||||
const { requestCompletion, completionActive, stopCompletion } =
|
||||
useCompletion( {
|
||||
feature: WOO_AI_PLUGIN_FEATURE_NAME,
|
||||
|
@ -76,7 +87,7 @@ export function WriteItForMeButtonContainer() {
|
|||
tinyEditor.setContent( content );
|
||||
}
|
||||
},
|
||||
onStreamError: handleCompletionError,
|
||||
onStreamError: handleUseCompletionError,
|
||||
onCompletionFinished: ( reason, content ) => {
|
||||
recordDescriptionTracks( 'stop', {
|
||||
reason,
|
||||
|
@ -107,6 +118,17 @@ export function WriteItForMeButtonContainer() {
|
|||
},
|
||||
} );
|
||||
|
||||
const { requestCompletion: requestShortCompletion } = useCompletion( {
|
||||
feature: WOO_AI_PLUGIN_FEATURE_NAME,
|
||||
onStreamMessage: ( content ) => shortTinyEditor.setContent( content ),
|
||||
onStreamError: handleUseCompletionError,
|
||||
onCompletionFinished: ( reason, content ) => {
|
||||
if ( reason === 'finished' ) {
|
||||
shortTinyEditor.setContent( content );
|
||||
}
|
||||
},
|
||||
} );
|
||||
|
||||
useEffect( () => {
|
||||
const title = titleEl.current;
|
||||
|
||||
|
@ -189,8 +211,23 @@ export function WriteItForMeButtonContainer() {
|
|||
|
||||
try {
|
||||
await requestCompletion( prompt );
|
||||
if ( ! shortTinyEditor.getContent() || shortDescriptionGenerated ) {
|
||||
await requestShortCompletion(
|
||||
[
|
||||
'Please write a high-converting Meta Description for the WooCommerce product description below.',
|
||||
'It should strictly adhere to the following guidelines:',
|
||||
'It should entice someone from a search results page to click on the product link.',
|
||||
'It should be no more than 155 characters so that the entire meta description fits within the space provided by the search engine result without being cut off or truncated.',
|
||||
'It should explain what users will see if they click on the product page link.',
|
||||
'Do not wrap in double quotes or use any other special characters.',
|
||||
`It should include the target keyword for the product.`,
|
||||
`Here is the full product description: \n${ tinyEditor.getContent() }`,
|
||||
].join( '\n' )
|
||||
);
|
||||
setShortDescriptionGenerated( true );
|
||||
}
|
||||
} catch ( err ) {
|
||||
handleCompletionError( err as UseCompletionError );
|
||||
handleUseCompletionError( err as UseCompletionError );
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,19 +1,28 @@
|
|||
type TinyContent = {
|
||||
getContent: () => string;
|
||||
setContent: ( str: string ) => void;
|
||||
id: string;
|
||||
};
|
||||
|
||||
declare const tinymce: { get: ( str: string ) => TinyContent };
|
||||
declare const tinymce: {
|
||||
get: ( str: string ) => TinyContent;
|
||||
editors: TinyContent[];
|
||||
};
|
||||
|
||||
const getTinyContentObject = () =>
|
||||
typeof tinymce === 'object' ? tinymce.get( 'content' ) : null;
|
||||
const getTinyContentObject = ( editorId = 'content' ) =>
|
||||
typeof tinymce === 'object'
|
||||
? tinymce.editors.find(
|
||||
( editor: { id: string } ) => editor.id === editorId
|
||||
)
|
||||
: null;
|
||||
|
||||
export const setTinyContent = ( str: string ) => {
|
||||
export const setTinyContent = ( str: string, editorId?: string ) => {
|
||||
if ( ! str.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const contentTinyMCE = getTinyContentObject();
|
||||
const contentTinyMCE = getTinyContentObject( editorId );
|
||||
|
||||
if ( contentTinyMCE ) {
|
||||
contentTinyMCE.setContent( str );
|
||||
} else {
|
||||
|
@ -28,6 +37,6 @@ export const setTinyContent = ( str: string ) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const getTinyContent = () => {
|
||||
return getTinyContentObject()?.getContent();
|
||||
export const getTinyContent = ( editorId?: string ) => {
|
||||
return getTinyContentObject( editorId )?.getContent();
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue