Skip to content

Commit

Permalink
AI Assistant: Recompute Breve highlights when dictionary is loaded (#…
Browse files Browse the repository at this point in the history
…38999)

* add dictionary loading state to store

* watch for feature loading state on highlight rich text function

* changelog
  • Loading branch information
dhasilva authored Aug 21, 2024
1 parent 6ee236e commit 4a750a0
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 105 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: other

AI Assistant: Recompute Breve highlights when dictionary is loaded
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* External dependencies
*/
import { dispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import debugFactory from 'debug';
import nspell from 'nspell';
Expand All @@ -16,6 +17,7 @@ import type {
SpellingDictionaryContext,
HighlightedText,
SpellChecker,
BreveDispatch,
} from '../../types';

const debug = debugFactory( 'jetpack-ai-breve:spelling-mistakes' );
Expand All @@ -36,6 +38,10 @@ const contextRequests: {
const fetchContext = async ( language: string ) => {
debug( 'Fetching spelling context from the server' );

const { setDictionaryLoading } = dispatch( 'jetpack/ai-breve' ) as BreveDispatch;

setDictionaryLoading( SPELLING_MISTAKES.name, true );

try {
contextRequests[ language ] = { loading: true, loaded: false, failed: false };
const data = await getDictionary( SPELLING_MISTAKES.name, language );
Expand All @@ -51,6 +57,8 @@ const fetchContext = async ( language: string ) => {
debug( 'Failed to fetch spelling context', error );
contextRequests[ language ] = { loading: false, loaded: false, failed: true };
// TODO: Handle retries
} finally {
setDictionaryLoading( SPELLING_MISTAKES.name, false );
}
};

Expand Down Expand Up @@ -90,7 +98,7 @@ const getSpellchecker = ( { language = 'en' }: { language?: string } = {} ) => {
return spellcheckers[ language ];
};

export default function longSentences( text: string ): Array< HighlightedText > {
export default function spellingMistakes( text: string ): Array< HighlightedText > {
const highlightedTexts: Array< HighlightedText > = [];
// Regex to match words, including contractions and hyphenated words
// \p{L} is a Unicode property that matches any letter in any language
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,29 @@
import { fixes } from '@automattic/jetpack-ai-client';
import { useAnalytics } from '@automattic/jetpack-shared-extension-utils';
import { rawHandler } from '@wordpress/blocks';
import { getBlockContent } from '@wordpress/blocks';
import { Button, Popover, Spinner } from '@wordpress/components';
import {
dispatch as globalDispatch,
select as globalSelect,
useDispatch,
useSelect,
} from '@wordpress/data';
import { useDispatch, useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import { reusableBlock as retry } from '@wordpress/icons';
import { registerFormatType, removeFormat, RichTextValue } from '@wordpress/rich-text';
import clsx from 'clsx';
import md5 from 'crypto-js/md5';
import React from 'react';
/**
* Internal dependencies
*/
import { AiSVG } from '../../ai-icon';
import { BREVE_FEATURE_NAME } from '../constants';
import features from '../features';
import registerEvents from '../features/events';
import { LONG_SENTENCES } from '../features/long-sentences';
import { SPELLING_MISTAKES } from '../features/spelling-mistakes';
import getBreveAvailability from '../utils/get-availability';
import { getNodeTextIndex } from '../utils/get-node-text-index';
import { getNonLinkAncestor } from '../utils/get-non-link-ancestor';
import { numberToOrdinal } from '../utils/number-to-ordinal';
import highlight from './highlight';
import './style.scss';
/**
* Types
*/
import type { BreveDispatch, BreveSelect } from '../types';
import type { Block } from '@automattic/jetpack-ai-client';
import type { WPFormat } from '@wordpress/rich-text/build-types/register-format-type';
import type { RichTextFormatList } from '@wordpress/rich-text/build-types/types';

type CoreBlockEditorSelect = {
getBlock: ( clientId: string ) => Block;
Expand Down Expand Up @@ -295,89 +282,3 @@ export default function Highlight() {
</>
);
}

export function registerBreveHighlights() {
features.forEach( feature => {
const { highlight: featureHighlight, config } = feature;
const { name, ...configSettings } = config;
const formatName = `jetpack/ai-proofread-${ name }`;

const settings = {
name: formatName,
interactive: false,
edit: () => {},
...configSettings,
__experimentalGetPropsForEditableTreePreparation( _select, { blockClientId } ) {
const { getIgnoredSuggestions, isFeatureEnabled, isProofreadEnabled } = globalSelect(
'jetpack/ai-breve'
) as BreveSelect;
const { getAiAssistantFeature } = globalSelect( 'wordpress-com/plans' );
const isFreePlan = getAiAssistantFeature().currentTier?.value === 0;

return {
isProofreadEnabled: isProofreadEnabled() && getBreveAvailability( isFreePlan ),
isFeatureEnabled: isFeatureEnabled( config.name ),
ignored: getIgnoredSuggestions( { blockId: blockClientId } ),
};
},
__experimentalCreatePrepareEditableTree(
{ isProofreadEnabled, isFeatureEnabled, ignored },
{ blockClientId, richTextIdentifier }
) {
return ( formats: Array< RichTextFormatList >, text: string ) => {
const { getBlock } = globalSelect( 'core/block-editor' ) as CoreBlockEditorSelect;
const { getBlockMd5 } = globalSelect( 'jetpack/ai-breve' ) as BreveSelect;
const { invalidateSuggestions, setBlockMd5 } = globalDispatch(
'jetpack/ai-breve'
) as BreveDispatch;

const record = { formats, text } as RichTextValue;
const type = formatName;

// Ignored suggestions
let ignoredList = ignored;

// Has to be defined here, as adding it to __experimentalGetPropsForEditableTreePreparation
// causes an issue with the block inserter. ref p1721746774569699-slack-C054LN8RNVA
const currentMd5 = getBlockMd5( blockClientId );

if ( text && isProofreadEnabled && isFeatureEnabled ) {
const block = getBlock( blockClientId );
// Only use block content for complex blocks like tables
const blockContent = richTextIdentifier === 'content' ? text : getBlockContent( block );
const textMd5 = md5( blockContent ).toString();

if ( currentMd5 !== textMd5 ) {
ignoredList = [];
invalidateSuggestions( blockClientId );
setBlockMd5( blockClientId, textMd5 );
}

const highlights = featureHighlight( text );
const applied = highlight( {
ignored: ignoredList,
content: record,
type,
indexes: highlights,
attributes: {
'data-breve-type': config.name,
'data-identifier': richTextIdentifier ?? 'none',
'data-block': blockClientId,
},
} );

setTimeout( () => {
registerEvents( blockClientId );
}, 100 );

return applied.formats;
}

return removeFormat( record, type, 0, record.text.length ).formats;
};
},
} as WPFormat;

registerFormatType( formatName, settings );
} );
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ import { BreveControls } from './types';
const Breve = Controls as BreveControls;

export { Breve };
export { default as Highlight, registerBreveHighlights } from './highlight';
export { default as Highlight } from './highlight';
export { registerBreveHighlights } from './utils/register-format';
export { store };
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ export function toggleFeature( feature: string, force?: boolean ) {
};
}

export function setDictionaryLoading( feature: string, loading: boolean ) {
return {
type: 'SET_DICTIONARY_LOADING',
feature,
loading,
};
}

export function setBlockMd5( blockId: string, md5: string ) {
return {
type: 'SET_BLOCK_MD5',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const initialConfiguration = {

export function configuration(
state: BreveState[ 'configuration' ] = initialConfiguration,
action: { type: string; enabled?: boolean; feature?: string }
action: { type: string; enabled?: boolean; feature?: string; loading?: boolean }
) {
switch ( action.type ) {
case 'SET_PROOFREAD_ENABLED': {
Expand Down Expand Up @@ -65,6 +65,17 @@ export function configuration(
disabled,
};
}

case 'SET_DICTIONARY_LOADING': {
const loading = action.loading
? [ ...( state.loading ?? [] ), action.feature ]
: [ ...( state.loading ?? [] ) ].filter( feature => feature !== action.feature );

return {
...state,
loading,
};
}
}

return state;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import type { Anchor, BreveState } from '../types';

// POPOVER
// Popover

export function isHighlightHover( state: BreveState ) {
return state.popover?.isHighlightHover;
Expand All @@ -21,7 +21,7 @@ export function getPopoverLevel( state: BreveState ) {
return state.popover?.level;
}

// CONFIGURATION
// Configuration

export function isProofreadEnabled( state: BreveState ) {
return state.configuration?.enabled;
Expand All @@ -31,6 +31,10 @@ export function isFeatureEnabled( state: BreveState, feature: string ) {
return ! state.configuration?.disabled?.includes( feature );
}

export function isFeatureDictionaryLoading( state: BreveState, feature: string ) {
return state.configuration?.loading?.includes( feature );
}

export function getDisabledFeatures( state: BreveState ) {
return state.configuration?.disabled;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type BreveState = {
configuration?: {
enabled?: boolean;
disabled?: Array< string >;
loading?: Array< string >;
};
suggestions?: {
[ key: string ]: {
Expand All @@ -41,6 +42,7 @@ export type BreveSelect = {
getPopoverLevel: () => number;
isProofreadEnabled: () => boolean;
isFeatureEnabled: ( feature: string ) => boolean;
isFeatureDictionaryLoading: ( feature: string ) => boolean;
getDisabledFeatures: () => Array< string >;
getBlockMd5: ( blockId: string ) => string;
getSuggestionsLoading: ( {
Expand Down Expand Up @@ -73,6 +75,7 @@ export type BreveDispatch = {
setPopoverAnchor: ( anchor: Anchor ) => void;
toggleProofread: ( force?: boolean ) => void;
toggleFeature: ( feature: string, force?: boolean ) => void;
setDictionaryLoading( feature: string, loading: boolean ): void;
invalidateSuggestions: ( blockId: string ) => void;
invalidateSingleSuggestion: ( feature: string, blockId: string, id: string ) => void;
ignoreSuggestion: ( blockId: string, id: string ) => void;
Expand Down
Loading

0 comments on commit 4a750a0

Please sign in to comment.