Plugin Directory

source: jetpack/trunk/extensions/plugins/ai-content-lens/extend/ai-post-excerpt/index.tsx @ 3056649

Last change on this file since 3056649 was 3056649, checked in by zinigor, 4 months ago

Updating trunk to version 13.2.2

File size: 8.7 KB
Line 
1/**
2 * External dependencies
3 */
4import { useAiSuggestions } from '@automattic/jetpack-ai-client';
5import {
6        isAtomicSite,
7        isSimpleSite,
8        useAnalytics,
9} from '@automattic/jetpack-shared-extension-utils';
10import { TextareaControl, ExternalLink, Button, Notice, BaseControl } from '@wordpress/components';
11import { useDispatch, useSelect } from '@wordpress/data';
12import { PluginDocumentSettingPanel } from '@wordpress/edit-post';
13import { store as editorStore, PostTypeSupportCheck } from '@wordpress/editor';
14import { useState, useEffect, useCallback } from '@wordpress/element';
15import { __, sprintf, _n } from '@wordpress/i18n';
16import { count } from '@wordpress/wordcount';
17/**
18 * Internal dependencies
19 */
20import UpgradePrompt from '../../../../blocks/ai-assistant/components/upgrade-prompt';
21import useAiFeature from '../../../../blocks/ai-assistant/hooks/use-ai-feature';
22import { isBetaExtension } from '../../../../editor';
23import { AiExcerptControl } from '../../components/ai-excerpt-control';
24/**
25 * Types and constants
26 */
27import type { LanguageProp } from '../../../../blocks/ai-assistant/components/i18n-dropdown-control';
28import type { ToneProp } from '../../../../blocks/ai-assistant/components/tone-dropdown-control';
29import type { AiModelTypeProp, PromptProp } from '@automattic/jetpack-ai-client';
30
31import './style.scss';
32
33type ContentLensMessageContextProps = {
34        type: 'ai-content-lens';
35        contentType: 'post-excerpt';
36        postId: number;
37        words?: number;
38        request?: string;
39        content?: string;
40        language?: LanguageProp;
41        tone?: ToneProp;
42        model?: AiModelTypeProp;
43};
44
45function AiPostExcerpt() {
46        const { excerpt, postId } = useSelect( select => {
47                const { getEditedPostAttribute, getCurrentPostId } = select( editorStore );
48
49                return {
50                        excerpt: getEditedPostAttribute( 'excerpt' ) ?? '',
51                        postId: getCurrentPostId() ?? 0,
52                };
53        }, [] );
54
55        const { tracks } = useAnalytics();
56
57        const { editPost } = useDispatch( 'core/editor' );
58
59        const { dequeueAiAssistantFeatureAyncRequest, increaseAiAssistantRequestsCount } =
60                useDispatch( 'wordpress-com/plans' );
61
62        // Post excerpt words number
63        const [ excerptWordsNumber, setExcerptWordsNumber ] = useState( 50 );
64
65        const [ reenable, setReenable ] = useState( false );
66        const [ language, setLanguage ] = useState< LanguageProp >();
67        const [ tone, setTone ] = useState< ToneProp >();
68        const [ model, setModel ] = useState< AiModelTypeProp >( null );
69
70        const { request, stopSuggestion, suggestion, requestingState, error, reset } = useAiSuggestions( {
71                onDone: useCallback( () => {
72                        /*
73                         * Increase the AI Suggestion counter.
74                         * @todo: move this at store level.
75                         */
76                        increaseAiAssistantRequestsCount();
77                }, [ increaseAiAssistantRequestsCount ] ),
78                onError: useCallback(
79                        suggestionError => {
80                                /*
81                                 * Incrses AI Suggestion counter
82                                 * only for valid errors.
83                                 * @todo: move this at store level.
84                                 */
85                                if (
86                                        suggestionError.code === 'error_network' ||
87                                        suggestionError.code === 'error_quota_exceeded'
88                                ) {
89                                        return;
90                                }
91
92                                // Increase the AI Suggestion counter.
93                                increaseAiAssistantRequestsCount();
94                        },
95                        [ increaseAiAssistantRequestsCount ]
96                ),
97        } );
98
99        // Cancel and reset AI suggestion when the component is unmounted
100        useEffect( () => {
101                return () => {
102                        stopSuggestion();
103                        reset();
104                };
105        }, [ stopSuggestion, reset ] );
106
107        // Pick raw post content
108        const postContent = useSelect(
109                select => {
110                        const content = select( editorStore ).getEditedPostContent();
111                        if ( ! content ) {
112                                return '';
113                        }
114
115                        // return turndownService.turndown( content );
116                        const document = new window.DOMParser().parseFromString( content, 'text/html' );
117
118                        const documentRawText = document.body.textContent || document.body.innerText || '';
119
120                        // Keep only one break line (\n) between blocks.
121                        return documentRawText.replace( /\n{2,}/g, '\n' ).trim();
122                },
123                [ postId ]
124        );
125
126        // Show custom prompt number of words
127        const currentExcerpt = suggestion || excerpt;
128        const numberOfWords = count( currentExcerpt, 'words' );
129        const helpNumberOfWords = sprintf(
130                // Translators: %1$s is the number of words in the excerpt.
131                _n( '%1$s word', '%1$s words', numberOfWords, 'jetpack' ),
132                numberOfWords
133        );
134
135        const isGenerateButtonDisabled =
136                requestingState === 'requesting' ||
137                requestingState === 'suggesting' ||
138                ( requestingState === 'done' && ! reenable );
139
140        const isBusy = requestingState === 'requesting' || requestingState === 'suggesting';
141        const isTextAreaDisabled = isBusy || requestingState === 'done';
142
143        /**
144         * Request AI for a new excerpt.
145         *
146         * @returns {void}
147         */
148        function requestExcerpt(): void {
149                // Enable Generate button
150                setReenable( false );
151
152                // Reset suggestion state
153                reset();
154
155                const messageContext: ContentLensMessageContextProps = {
156                        type: 'ai-content-lens',
157                        contentType: 'post-excerpt',
158                        postId,
159                        words: excerptWordsNumber,
160                        language,
161                        tone,
162                        content: `Post content:
163${ postContent }
164`,
165                };
166
167                const prompt: PromptProp = [
168                        {
169                                role: 'jetpack-ai',
170                                context: messageContext,
171                        },
172                ];
173
174                /*
175                 * Always dequeue/cancel the AI Assistant feature async request,
176                 * in case there is one pending,
177                 * when performing a new AI suggestion request.
178                 */
179                dequeueAiAssistantFeatureAyncRequest();
180
181                request( prompt, { feature: 'jetpack-ai-content-lens', model } );
182                tracks.recordEvent( 'jetpack_ai_assistant_block_generate', {
183                        feature: 'jetpack-ai-content-lens',
184                } );
185��       }
186
187        function setExcerpt() {
188                editPost( { excerpt: suggestion } );
189                tracks.recordEvent( 'jetpack_ai_assistant_block_accept', {
190                        feature: 'jetpack-ai-content-lens',
191                } );
192                reset();
193        }
194
195        function discardExcerpt() {
196                editPost( { excerpt: excerpt } );
197                tracks.recordEvent( 'jetpack_ai_assistant_block_discard', {
198                        feature: 'jetpack-ai-content-lens',
199                } );
200                reset();
201        }
202
203        const { requireUpgrade, isOverLimit } = useAiFeature();
204
205        // Set the docs link depending on the site type
206        const docsLink =
207                isAtomicSite() || isSimpleSite()
208                        ? __( 'https://wordpress.com/support/excerpts/', 'jetpack' )
209                        : __( 'https://jetpack.com/support/create-better-post-excerpts-with-ai/', 'jetpack' );
210
211        return (
212                <div className="jetpack-ai-post-excerpt">
213                        <TextareaControl
214                                __nextHasNoMarginBottom
215                                label={ __( 'Write an excerpt (optional)', 'jetpack' ) }
216                                onChange={ value => editPost( { excerpt: value } ) }
217                                help={ numberOfWords ? helpNumberOfWords : null }
218                                value={ currentExcerpt }
219                                disabled={ isTextAreaDisabled }
220                        />
221
222                        <ExternalLink href={ docsLink }>
223                                { __( 'Learn more about manual excerpts', 'jetpack' ) }
224                        </ExternalLink>
225
226                        <div className="jetpack-generated-excerpt__ai-container">
227                                { error?.code && error.code !== 'error_quota_exceeded' && (
228                                        <Notice
229                                                status={ error.severity }
230                                                isDismissible={ false }
231                                                className="jetpack-ai-assistant__error"
232                                        >
233                                                { error.message }
234                                        </Notice>
235                                ) }
236
237                                { isOverLimit && <UpgradePrompt placement="excerpt-panel" /> }
238
239                                <AiExcerptControl
240                                        words={ excerptWordsNumber }
241                                        onWordsNumberChange={ wordsNumber => {
242                                                setExcerptWordsNumber( wordsNumber );
243                                                setReenable( true );
244                                        } }
245                                        language={ language }
246                                        onLanguageChange={ newLang => {
247                                                setLanguage( newLang );
248                                                setReenable( true );
249                                        } }
250                                        tone={ tone }
251                                        onToneChange={ newTone => {
252                                                setTone( newTone );
253                                                setReenable( true );
254                                        } }
255                                        model={ model }
256                                        onModelChange={ newModel => {
257                                                setModel( newModel );
258                                                setReenable( true );
259                                        } }
260                                        disabled={ isBusy || requireUpgrade }
261                                />
262
263                                <BaseControl
264                                        help={
265                                                ! postContent?.length ? __( 'Add content to generate an excerpt.', 'jetpack' ) : null
266                                        }
267                                >
268                                        <div className="jetpack-generated-excerpt__generate-buttons-container">
269                                                <Button
270                                                        onClick={ discardExcerpt }
271                                                        variant="secondary"
272                                                        isDestructive
273                                                        disabled={ requestingState !== 'done' || requireUpgrade }
274                                                >
275                                                        { __( 'Discard', 'jetpack' ) }
276                                                </Button>
277                                                <Button
278                                                        onClick={ setExcerpt }
279                                                        variant="secondary"
280                                                        disabled={ requestingState !== 'done' || requireUpgrade }
281                                                >
282                                                        { __( 'Accept', 'jetpack' ) }
283                                                </Button>
284                                                <Button
285                                                        onClick={ requestExcerpt }
286                                                        variant="secondary"
287                                                        isBusy={ isBusy }
288                                                        disabled={ isGenerateButtonDisabled || requireUpgrade || ! postContent }
289                                                >
290                                                        { __( 'Generate', 'jetpack' ) }
291                                                </Button>
292                                        </div>
293                                </BaseControl>
294                        </div>
295                </div>
296        );
297}
298
299export const PluginDocumentSettingPanelAiExcerpt = () => (
300        <PostTypeSupportCheck supportKeys="excerpt">
301                <PluginDocumentSettingPanel
302                        className={ isBetaExtension( 'ai-content-lens' ) ? 'is-beta-extension inset-shadow' : '' }
303                        name="ai-content-lens-plugin"
304                        title={ __( 'Excerpt', 'jetpack' ) }
305                >
306                        <AiPostExcerpt />
307                </PluginDocumentSettingPanel>
308        </PostTypeSupportCheck>
309);
Note: See TracBrowser for help on using the repository browser.