Skip to content

Commit

Permalink
AI Assistant: Rename useSuggestionsFromOpenAI to useAIAssistant and d…
Browse files Browse the repository at this point in the history
…eduplicate suggestion logic (#36869)

* remove unused function calling feature

* add large context error to use-ai-suggestions getErrorData function

* use getErrorData on use-suggestions-from-openai

* fix double error event on network error

* add handler for any error

* add initialRequestingState

* expose handleErrorQuotaExceededError

* clear error on request and expose error to caller

* add onStop callback

* adjust AI Assistant to useAiSuggestions hook

* remove duplicated and wrong debug messages

* remove unnecessary default value

* rename useSuggestionsFromOpenAI hook to useAIAssistant

* changelog

* bump version
  • Loading branch information
dhasilva authored Apr 12, 2024
1 parent 3e02270 commit 65caa26
Show file tree
Hide file tree
Showing 10 changed files with 368 additions and 526 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: added

AI Client: Add callbacks, initial requesting state and change error handling
2 changes: 1 addition & 1 deletion projects/js-packages/ai-client/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": false,
"name": "@automattic/jetpack-ai-client",
"version": "0.12.0",
"version": "0.12.1-alpha",
"description": "A JS client for consuming Jetpack AI services",
"homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/ai-client/#readme",
"bugs": {
Expand Down
114 changes: 78 additions & 36 deletions projects/js-packages/ai-client/src/hooks/use-ai-suggestions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@
*/
import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import debugFactory from 'debug';
/**
* Internal dependencies
*/
import askQuestion from '../../ask-question/index.js';
import {
ERROR_CONTEXT_TOO_LARGE,
ERROR_MODERATION,
ERROR_NETWORK,
ERROR_QUOTA_EXCEEDED,
ERROR_SERVICE_UNAVAILABLE,
ERROR_UNCLEAR_PROMPT,
ERROR_RESPONSE,
} from '../../types.js';
/**
* Types & constants
Expand Down Expand Up @@ -56,6 +57,11 @@ type useAiSuggestionsOptions = {
*/
askQuestionOptions?: AskQuestionOptionsArgProps;

/*
* Initial requesting state.
*/
initialRequestingState?: RequestingStateProp;

/*
* onSuggestion callback.
*/
Expand All @@ -66,10 +72,20 @@ type useAiSuggestionsOptions = {
*/
onDone?: ( content: string ) => void;

/*
* onStop callback.
*/
onStop?: () => void;

/*
* onError callback.
*/
onError?: ( error: RequestingErrorProps ) => void;

/*
* Error callback common for all errors.
*/
onAllErrors?: ( error: RequestingErrorProps ) => void;
};

type useAiSuggestionsProps = {
Expand Down Expand Up @@ -107,9 +123,12 @@ type useAiSuggestionsProps = {
* The handler to stop a suggestion.
*/
stopSuggestion: () => void;
};

const debug = debugFactory( 'jetpack-ai-client:use-suggestion' );
/*
* The handler to handle the quota exceeded error.
*/
handleErrorQuotaExceededError: () => void;
};

/**
* Get the error data for a given error code.
Expand Down Expand Up @@ -149,6 +168,15 @@ export function getErrorData( errorCode: SuggestionErrorCode ): RequestingErrorP
),
severity: 'info',
};
case ERROR_CONTEXT_TOO_LARGE:
return {
code: ERROR_CONTEXT_TOO_LARGE,
message: __(
'The content is too large to be processed all at once. Please try to shorten it or divide it into smaller parts.',
'jetpack-ai-client'
),
severity: 'info',
};
case ERROR_NETWORK:
default:
return {
Expand All @@ -173,11 +201,15 @@ export default function useAiSuggestions( {
prompt,
autoRequest = false,
askQuestionOptions = {},
initialRequestingState = 'init',
onSuggestion,
onDone,
onStop,
onError,
onAllErrors,
}: useAiSuggestionsOptions = {} ): useAiSuggestionsProps {
const [ requestingState, setRequestingState ] = useState< RequestingStateProp >( 'init' );
const [ requestingState, setRequestingState ] =
useState< RequestingStateProp >( initialRequestingState );
const [ suggestion, setSuggestion ] = useState< string >( '' );
const [ error, setError ] = useState< RequestingErrorProps >();

Expand Down Expand Up @@ -206,12 +238,20 @@ export default function useAiSuggestions( {
*/
const handleDone = useCallback(
( event: CustomEvent ) => {
closeEventSource();
onDone?.( event?.detail );
setRequestingState( 'done' );
},
[ onDone ]
);

const handleAnyError = useCallback(
( event: CustomEvent ) => {
onAllErrors?.( event?.detail );
},
[ onAllErrors ]
);

const handleError = useCallback(
( errorCode: SuggestionErrorCode ) => {
eventSourceRef?.current?.close();
Expand Down Expand Up @@ -250,43 +290,34 @@ export default function useAiSuggestions( {
promptArg: PromptProp,
options: AskQuestionOptionsArgProps = { ...askQuestionOptions }
) => {
if ( Array.isArray( promptArg ) && promptArg?.length ) {
promptArg.forEach( ( { role, content: promptContent }, i ) =>
debug( '(%s/%s) %o\n%s', i + 1, promptArg.length, `[${ role }]`, promptContent )
);
} else {
debug( '%o', promptArg );
}
// Clear any error.
setError( undefined );

// Set the request status.
setRequestingState( 'requesting' );

try {
eventSourceRef.current = await askQuestion( promptArg, options );
eventSourceRef.current = await askQuestion( promptArg, options );

if ( ! eventSourceRef?.current ) {
return;
}
if ( ! eventSourceRef?.current ) {
return;
}

// Alias
const eventSource = eventSourceRef.current;
// Alias
const eventSource = eventSourceRef.current;

// Set the request status.
setRequestingState( 'suggesting' );
// Set the request status.
setRequestingState( 'suggesting' );

eventSource.addEventListener( 'suggestion', handleSuggestion );
eventSource.addEventListener( 'suggestion', handleSuggestion );

eventSource.addEventListener( ERROR_QUOTA_EXCEEDED, handleErrorQuotaExceededError );
eventSource.addEventListener( ERROR_UNCLEAR_PROMPT, handleUnclearPromptError );
eventSource.addEventListener( ERROR_SERVICE_UNAVAILABLE, handleServiceUnavailableError );
eventSource.addEventListener( ERROR_MODERATION, handleModerationError );
eventSource.addEventListener( ERROR_NETWORK, handleNetworkError );
eventSource.addEventListener( ERROR_QUOTA_EXCEEDED, handleErrorQuotaExceededError );
eventSource.addEventListener( ERROR_UNCLEAR_PROMPT, handleUnclearPromptError );
eventSource.addEventListener( ERROR_SERVICE_UNAVAILABLE, handleServiceUnavailableError );
eventSource.addEventListener( ERROR_MODERATION, handleModerationError );
eventSource.addEventListener( ERROR_NETWORK, handleNetworkError );
eventSource.addEventListener( ERROR_RESPONSE, handleAnyError );

eventSource.addEventListener( 'done', handleDone );
} catch ( e ) {
// eslint-disable-next-line no-console
console.error( e );
}
eventSource.addEventListener( 'done', handleDone );
},
[
handleDone,
Expand All @@ -311,11 +342,11 @@ export default function useAiSuggestions( {
}, [] );

/**
* Stop suggestion handler.
* Close the event source connection.
*
* @returns {void}
*/
const stopSuggestion = useCallback( () => {
const closeEventSource = useCallback( () => {
if ( ! eventSourceRef?.current ) {
return;
}
Expand All @@ -336,9 +367,6 @@ export default function useAiSuggestions( {
eventSource.removeEventListener( ERROR_NETWORK, handleNetworkError );

eventSource.removeEventListener( 'done', handleDone );

// Set requesting state to done since the suggestion stopped.
setRequestingState( 'done' );
}, [
eventSourceRef,
handleSuggestion,
Expand All @@ -350,6 +378,17 @@ export default function useAiSuggestions( {
handleDone,
] );

/**
* Stop suggestion handler.
*
* @returns {void}
*/
const stopSuggestion = useCallback( () => {
closeEventSource();
onStop?.();
setRequestingState( 'done' );
}, [ onStop ] );

// Request suggestions automatically when ready.
useEffect( () => {
// Check if there is a prompt to request.
Expand Down Expand Up @@ -379,6 +418,9 @@ export default function useAiSuggestions( {
stopSuggestion,
reset,

// Error handlers
handleErrorQuotaExceededError,

// SuggestionsEventSource
eventSource: eventSourceRef.current,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ export default function useTranscriptionPostProcessing( {
);

const { request, stopSuggestion } = useAiSuggestions( {
autoRequest: false,
onSuggestion: handleOnSuggestion,
onDone: handleOnDone,
onError: handleOnError,
Expand Down
2 changes: 1 addition & 1 deletion projects/js-packages/ai-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export { default as transcribeAudio } from './audio-transcription/index.js';
/*
* Hooks
*/
export { default as useAiSuggestions } from './hooks/use-ai-suggestions/index.js';
export { default as useAiSuggestions, getErrorData } from './hooks/use-ai-suggestions/index.js';
export { default as useMediaRecording } from './hooks/use-media-recording/index.js';
export { default as useAudioTranscription } from './hooks/use-audio-transcription/index.js';
export { default as useTranscriptionPostProcessing } from './hooks/use-transcription-post-processing/index.js';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,9 @@ export default class SuggestionsEventSource extends EventTarget {
response.status <= 500 &&
! [ 413, 422, 429 ].includes( response.status )
) {
this.processConnectionError( response );
debug( 'Connection error: %o', response );
errorCode = ERROR_NETWORK;
this.dispatchEvent( new CustomEvent( ERROR_NETWORK, { detail: response } ) );
}

/*
Expand Down Expand Up @@ -358,16 +360,6 @@ export default class SuggestionsEventSource extends EventTarget {
}
}

processConnectionError( response ) {
debug( 'Connection error: %o', response );
this.dispatchEvent( new CustomEvent( ERROR_NETWORK, { detail: response } ) );
this.dispatchEvent(
new CustomEvent( ERROR_RESPONSE, {
detail: getErrorData( ERROR_NETWORK ),
} )
);
}

processErrorEvent( e ) {
debug( 'onerror: %o', e );

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: other

AI Assistant: Rename useSuggestionsFromOpenAI to useAIAssistant and deduplicate suggestion logic
Loading

0 comments on commit 65caa26

Please sign in to comment.