Skip to content

Commit

Permalink
AI Assistant: Add thumbs feedback on AI Assistant (#40728)
Browse files Browse the repository at this point in the history
* add thumbs feedfback on Message component

* add thumbs feedback to AI Assistant and extensions

* changelog

Committed via a GitHub action: https://github.com/Automattic/jetpack/actions/runs/12504957892

Upstream-Ref: Automattic/jetpack@91bde80
  • Loading branch information
dhasilva authored and matticbot committed Dec 26, 2024
1 parent 25b33a0 commit bff33ba
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 30 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.25.4-alpha] - unreleased

This is an alpha version! The changes listed here are not final.

### Added
- AI Client: Add thumbs feedback on AI Assistant

## [0.25.3] - 2024-12-23
### Added
- Jetpack AI: Add thumbs up/down component to AI logo generator [#40610]
Expand Down Expand Up @@ -487,6 +494,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- AI Client: stop using smart document visibility handling on the fetchEventSource library, so it does not restart the completion when changing tabs. [#32004]
- Updated package dependencies. [#31468] [#31659] [#31785]

[0.25.4-alpha]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.3...v0.25.4-alpha
[0.25.3]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.2...v0.25.3
[0.25.2]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.1...v0.25.2
[0.25.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.0...v0.25.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,10 @@ export function BlockAIControl({ disabled = false, value = '', placeholder = '',
target: promptUserInputRef,
});
const actions = (_jsxs(_Fragment, { children: [(!showAccept || editRequest) && (_jsx("div", { className: "jetpack-components-ai-control__controls-prompt_button_wrapper", children: !loading ? (_jsxs(_Fragment, { children: [editRequest && (_jsx(Button, { className: "jetpack-components-ai-control__controls-prompt_button", onClick: cancelEdit, variant: "secondary", label: __('Cancel', 'jetpack-ai-client'), children: showButtonLabels ? (__('Cancel', 'jetpack-ai-client')) : (_jsx(Icon, { icon: closeSmall })) })), showRemove && !editRequest && !value?.length && onDiscard && (_jsx(Button, { className: "jetpack-components-ai-control__controls-prompt_button", onClick: discardHandler, variant: "secondary", label: __('Cancel', 'jetpack-ai-client'), children: showButtonLabels ? (__('Cancel', 'jetpack-ai-client')) : (_jsx(Icon, { icon: closeSmall })) })), value?.length > 0 && (_jsx(Button, { className: "jetpack-components-ai-control__controls-prompt_button", onClick: sendHandler, variant: "primary", disabled: !value?.length || disabled, label: __('Send request', 'jetpack-ai-client'), children: showButtonLabels ? (__('Generate', 'jetpack-ai-client')) : (_jsx(Icon, { icon: arrowUp })) }))] })) : (_jsx(Button, { className: "jetpack-components-ai-control__controls-prompt_button", onClick: onStop, variant: "secondary", label: __('Stop request', 'jetpack-ai-client'), children: showButtonLabels ? (__('Stop', 'jetpack-ai-client')) : (_jsx(Icon, { icon: closeSmall })) })) })), showAccept && !editRequest && (_jsxs("div", { className: "jetpack-components-ai-control__controls-prompt_button_wrapper", children: [(value?.length > 0 || lastValue === null) && (_jsxs(ButtonGroup, { children: [_jsx(Button, { className: "jetpack-components-ai-control__controls-prompt_button", label: __('Discard', 'jetpack-ai-client'), onClick: discardHandler, tooltipPosition: "top", children: _jsx(Icon, { icon: trash }) }), _jsx(Button, { className: "jetpack-components-ai-control__controls-prompt_button", label: __('Regenerate', 'jetpack-ai-client'), onClick: () => onSend?.(value), tooltipPosition: "top", disabled: !value?.length || value === null || disabled, children: _jsx(Icon, { icon: regenerate }) })] })), _jsx(Button, { className: "jetpack-components-ai-control__controls-prompt_button", onClick: onAccept, variant: "primary", label: acceptLabel, children: showButtonLabels ? acceptLabel : _jsx(Icon, { icon: check }) })] }))] }));
const message = showGuideLine && !loading && !editRequest && (customFooter || _jsx(GuidelineMessage, {}));
const message = showGuideLine &&
!loading &&
!editRequest &&
(customFooter || (_jsx(GuidelineMessage, { showAIFeedbackThumbs: true, ratedItem: 'ai-assistant', prompt: value })));
return (_jsx(AIControl, { disabled: disabled || loading, value: value, placeholder: placeholder, isTransparent: isTransparent, state: state, onChange: changeHandler, banner: banner, error: error, actions: actions, message: message, promptUserInputRef: promptUserInputRef }));
}
export default forwardRef(BlockAIControl);
14 changes: 12 additions & 2 deletions build/ai-client/src/components/ai-control/extension-ai-control.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,25 @@ export function ExtensionAIControl({ className, disabled = false, value = '', pl
const [editRequest, setEditRequest] = useState(false);
const [lastValue, setLastValue] = useState(value || null);
const promptUserInputRef = useRef(null);
const isDone = value?.length <= 0 && state === 'done';
const [initialPlaceholder] = useState(placeholder);
const [prompt, setPrompt] = useState(null);
// Pass the ref to forwardRef.
useImperativeHandle(ref, () => promptUserInputRef.current);
useEffect(() => {
if (editRequest) {
promptUserInputRef?.current?.focus();
}
}, [editRequest]);
useEffect(() => {
if (placeholder !== initialPlaceholder) {
// The prompt is used to determine if there was a toolbar action
setPrompt(placeholder);
}
}, [placeholder]);
const sendHandler = useCallback(() => {
setLastValue(value);
setPrompt(value);
setEditRequest(false);
onSend?.(value);
}, [onSend, value]);
Expand Down Expand Up @@ -73,7 +83,7 @@ export function ExtensionAIControl({ className, disabled = false, value = '', pl
}, {
target: promptUserInputRef,
});
const actions = (_jsx(_Fragment, { children: loading ? (_jsx(Button, { className: "jetpack-components-ai-control__controls-prompt_button", onClick: stopHandler, variant: "secondary", label: __('Stop request', 'jetpack-ai-client'), children: showButtonLabels ? __('Stop', 'jetpack-ai-client') : _jsx(Icon, { icon: closeSmall }) })) : (_jsxs(_Fragment, { children: [value?.length > 0 && (_jsx("div", { className: "jetpack-components-ai-control__controls-prompt_button_wrapper", children: _jsx(Button, { className: "jetpack-components-ai-control__controls-prompt_button", onClick: sendHandler, variant: "primary", disabled: !value?.length || disabled, label: __('Send request', 'jetpack-ai-client'), children: showButtonLabels ? (__('Generate', 'jetpack-ai-client')) : (_jsx(Icon, { icon: arrowUp })) }) })), value?.length <= 0 && state === 'done' && (_jsx("div", { className: "jetpack-components-ai-control__controls-prompt_button_wrapper", children: _jsxs(ButtonGroup, { children: [_jsx(Button, { className: "jetpack-components-ai-control__controls-prompt_button", label: __('Undo', 'jetpack-ai-client'), onClick: undoHandler, tooltipPosition: "top", children: _jsx(Icon, { icon: undo }) }), _jsx(Button, { className: "jetpack-components-ai-control__controls-prompt_button", label: __('Close', 'jetpack-ai-client'), onClick: closeHandler, variant: "tertiary", children: __('Close', 'jetpack-ai-client') })] }) }))] })) }));
const actions = (_jsx(_Fragment, { children: loading ? (_jsx(Button, { className: "jetpack-components-ai-control__controls-prompt_button", onClick: stopHandler, variant: "secondary", label: __('Stop request', 'jetpack-ai-client'), children: showButtonLabels ? __('Stop', 'jetpack-ai-client') : _jsx(Icon, { icon: closeSmall }) })) : (_jsxs(_Fragment, { children: [value?.length > 0 && (_jsx("div", { className: "jetpack-components-ai-control__controls-prompt_button_wrapper", children: _jsx(Button, { className: "jetpack-components-ai-control__controls-prompt_button", onClick: sendHandler, variant: "primary", disabled: !value?.length || disabled, label: __('Send request', 'jetpack-ai-client'), children: showButtonLabels ? (__('Generate', 'jetpack-ai-client')) : (_jsx(Icon, { icon: arrowUp })) }) })), isDone && (_jsx("div", { className: "jetpack-components-ai-control__controls-prompt_button_wrapper", children: _jsxs(ButtonGroup, { children: [_jsx(Button, { className: "jetpack-components-ai-control__controls-prompt_button", label: __('Undo', 'jetpack-ai-client'), onClick: undoHandler, tooltipPosition: "top", children: _jsx(Icon, { icon: undo }) }), _jsx(Button, { className: "jetpack-components-ai-control__controls-prompt_button", label: __('Close', 'jetpack-ai-client'), onClick: closeHandler, variant: "tertiary", children: __('Close', 'jetpack-ai-client') })] }) }))] })) }));
let message = null;
if (error?.message) {
message = (_jsx(ErrorMessage, { error: error.message, code: error.code, onTryAgainClick: tryAgainHandler, onUpgradeClick: upgradeHandler, upgradeUrl: upgradeUrl }));
Expand All @@ -85,7 +95,7 @@ export function ExtensionAIControl({ className, disabled = false, value = '', pl
message = (_jsx(UpgradeMessage, { requestsRemaining: requestsRemaining, onUpgradeClick: upgradeHandler, upgradeUrl: upgradeUrl }));
}
else if (showGuideLine) {
message = _jsx(GuidelineMessage, {});
message = isDone ? (_jsx(GuidelineMessage, { showAIFeedbackThumbs: true, ratedItem: 'ai-assistant', prompt: prompt })) : (_jsx(GuidelineMessage, {}));
}
return (_jsx(AIControl, { className: className, disabled: disabled || loading, value: value, placeholder: placeholder, isTransparent: isTransparent, state: state, onChange: changeHandler, actions: actions, message: message, promptUserInputRef: promptUserInputRef, wrapperRef: wrapperRef }));
}
Expand Down
19 changes: 14 additions & 5 deletions build/ai-client/src/components/message/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,20 @@ export declare const MESSAGE_SEVERITY_ERROR = "error";
export declare const MESSAGE_SEVERITY_SUCCESS = "success";
export declare const MESSAGE_SEVERITY_INFO = "info";
export type MessageSeverityProp = typeof MESSAGE_SEVERITY_WARNING | typeof MESSAGE_SEVERITY_ERROR | typeof MESSAGE_SEVERITY_SUCCESS | typeof MESSAGE_SEVERITY_INFO | null;
type RateProps = {
ratedItem?: string;
prompt?: string;
onRate?: (rating: string) => void;
};
export type MessageProps = {
icon?: React.ReactNode;
severity?: MessageSeverityProp;
showSidebarIcon?: boolean;
onSidebarIconClick?: () => void;
showAIFeedbackThumbs?: boolean;
children: React.ReactNode;
};
} & RateProps;
export type GuidelineMessageProps = {
showAIFeedbackThumbs?: boolean;
} & RateProps;
export type OnUpgradeClick = (event?: React.MouseEvent<HTMLButtonElement>) => void;
export type UpgradeMessageProps = {
requestsRemaining: number;
Expand All @@ -39,13 +46,14 @@ export type ErrorMessageProps = {
* @param {MessageProps} props - Component props.
* @return {React.ReactElement} Banner component.
*/
export default function Message({ severity, icon, showSidebarIcon, onSidebarIconClick, children, }: MessageProps): React.ReactElement;
export default function Message({ severity, icon, showAIFeedbackThumbs, ratedItem, prompt, onRate, children, }: MessageProps): React.ReactElement;
/**
* React component to render a guideline message.
*
* @param {GuidelineMessageProps} props - Component props.
* @return {React.ReactElement} - Message component.
*/
export declare function GuidelineMessage(): React.ReactElement;
export declare function GuidelineMessage({ showAIFeedbackThumbs, ...props }: GuidelineMessageProps): React.ReactElement;
/**
* React component to render a fair usage limit message.
*
Expand All @@ -66,3 +74,4 @@ export declare function UpgradeMessage({ requestsRemaining, severity, onUpgradeC
* @return {React.ReactElement} - Message component.
*/
export declare function ErrorMessage({ error, code, onTryAgainClick, onUpgradeClick, upgradeUrl, }: ErrorMessageProps): React.ReactElement;
export {};
14 changes: 9 additions & 5 deletions build/ai-client/src/components/message/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { ExternalLink, Button } from '@wordpress/components';
import { createInterpolateElement } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { Icon, check, arrowRight } from '@wordpress/icons';
import { Icon, check } from '@wordpress/icons';
import clsx from 'clsx';
/**
* Internal dependencies
*/
import './style.scss';
import errorExclamation from '../../icons/error-exclamation.js';
import { ERROR_QUOTA_EXCEEDED } from '../../types.js';
import AiFeedbackThumbs from '../ai-feedback/index.js';
export const MESSAGE_SEVERITY_WARNING = 'warning';
export const MESSAGE_SEVERITY_ERROR = 'error';
export const MESSAGE_SEVERITY_SUCCESS = 'success';
Expand All @@ -29,8 +30,10 @@ const messageIconsMap = {
* @param {MessageProps} props - Component props.
* @return {React.ReactElement} Banner component.
*/
export default function Message({ severity = MESSAGE_SEVERITY_INFO, icon = null, showSidebarIcon = false, onSidebarIconClick = () => { }, children, }) {
return (_jsxs("div", { className: clsx('jetpack-ai-assistant__message', `jetpack-ai-assistant__message-severity-${severity}`), children: [(messageIconsMap[severity] || icon) && (_jsx(Icon, { icon: messageIconsMap[severity] || icon })), _jsx("div", { className: "jetpack-ai-assistant__message-content", children: children }), showSidebarIcon && (_jsx(Button, { className: "jetpack-ai-assistant__message-sidebar", onClick: onSidebarIconClick, children: _jsx(Icon, { size: 20, icon: arrowRight }) }))] }));
export default function Message({ severity = MESSAGE_SEVERITY_INFO, icon = null, showAIFeedbackThumbs = false, ratedItem = '', prompt = '', onRate = () => { }, children, }) {
return (_jsxs("div", { className: clsx('jetpack-ai-assistant__message', `jetpack-ai-assistant__message-severity-${severity}`), children: [(messageIconsMap[severity] || icon) && (_jsx(Icon, { icon: messageIconsMap[severity] || icon })), _jsx("div", { className: "jetpack-ai-assistant__message-content", children: children }), showAIFeedbackThumbs && (_jsx(AiFeedbackThumbs, { disabled: false, ratedItem: ratedItem, feature: "ai-assistant", options: {
prompt,
}, onRate: onRate }))] }));
}
/**
* React component to render a learn more link.
Expand All @@ -43,10 +46,11 @@ function LearnMoreLink() {
/**
* React component to render a guideline message.
*
* @param {GuidelineMessageProps} props - Component props.
* @return {React.ReactElement} - Message component.
*/
export function GuidelineMessage() {
return (_jsxs(Message, { children: [_jsx("span", { children: __('AI-generated content could be inaccurate or biased.', 'jetpack-ai-client') }), _jsx(LearnMoreLink, {})] }));
export function GuidelineMessage({ showAIFeedbackThumbs = false, ...props }) {
return (_jsxs(Message, { showAIFeedbackThumbs: showAIFeedbackThumbs, ...props, children: [_jsx("span", { children: __('AI-generated content could be inaccurate or biased.', 'jetpack-ai-client') }), _jsx(LearnMoreLink, {})] }));
}
/**
* React component to render a fair usage limit message.
Expand Down
2 changes: 1 addition & 1 deletion 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.25.3",
"version": "0.25.4-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
11 changes: 10 additions & 1 deletion src/components/ai-control/block-ai-control.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,16 @@ export function BlockAIControl(
);

const message =
showGuideLine && ! loading && ! editRequest && ( customFooter || <GuidelineMessage /> );
showGuideLine &&
! loading &&
! editRequest &&
( customFooter || (
<GuidelineMessage
showAIFeedbackThumbs={ true }
ratedItem={ 'ai-assistant' }
prompt={ value }
/>
) );

return (
<AIControl
Expand Down
23 changes: 21 additions & 2 deletions src/components/ai-control/extension-ai-control.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ export function ExtensionAIControl(
const [ editRequest, setEditRequest ] = useState( false );
const [ lastValue, setLastValue ] = useState( value || null );
const promptUserInputRef = useRef( null );
const isDone = value?.length <= 0 && state === 'done';
const [ initialPlaceholder ] = useState( placeholder );
const [ prompt, setPrompt ] = useState( null );

// Pass the ref to forwardRef.
useImperativeHandle( ref, () => promptUserInputRef.current );
Expand All @@ -95,8 +98,16 @@ export function ExtensionAIControl(
}
}, [ editRequest ] );

useEffect( () => {
if ( placeholder !== initialPlaceholder ) {
// The prompt is used to determine if there was a toolbar action
setPrompt( placeholder );
}
}, [ placeholder ] );

const sendHandler = useCallback( () => {
setLastValue( value );
setPrompt( value );
setEditRequest( false );
onSend?.( value );
}, [ onSend, value ] );
Expand Down Expand Up @@ -183,7 +194,7 @@ export function ExtensionAIControl(
</Button>
</div>
) }
{ value?.length <= 0 && state === 'done' && (
{ isDone && (
<div className="jetpack-components-ai-control__controls-prompt_button_wrapper">
<ButtonGroup>
<Button
Expand Down Expand Up @@ -233,7 +244,15 @@ export function ExtensionAIControl(
/>
);
} else if ( showGuideLine ) {
message = <GuidelineMessage />;
message = isDone ? (
<GuidelineMessage
showAIFeedbackThumbs={ true }
ratedItem={ 'ai-assistant' }
prompt={ prompt }
/>
) : (
<GuidelineMessage />
);
}

return (
Expand Down
48 changes: 35 additions & 13 deletions src/components/message/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
import { ExternalLink, Button } from '@wordpress/components';
import { createInterpolateElement } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { Icon, check, arrowRight } from '@wordpress/icons';
import { Icon, check } from '@wordpress/icons';
import clsx from 'clsx';
/**
* Internal dependencies
*/
import './style.scss';
import errorExclamation from '../../icons/error-exclamation.js';
import { ERROR_QUOTA_EXCEEDED } from '../../types.js';
import AiFeedbackThumbs from '../ai-feedback/index.js';
/**
* Types
*/
Expand All @@ -30,13 +31,22 @@ export type MessageSeverityProp =
| typeof MESSAGE_SEVERITY_INFO
| null;

type RateProps = {
ratedItem?: string;
prompt?: string;
onRate?: ( rating: string ) => void;
};

export type MessageProps = {
icon?: React.ReactNode;
severity?: MessageSeverityProp;
showSidebarIcon?: boolean;
onSidebarIconClick?: () => void;
showAIFeedbackThumbs?: boolean;
children: React.ReactNode;
};
} & RateProps;

export type GuidelineMessageProps = {
showAIFeedbackThumbs?: boolean;
} & RateProps;

export type OnUpgradeClick = ( event?: React.MouseEvent< HTMLButtonElement > ) => void;

Expand Down Expand Up @@ -71,8 +81,10 @@ const messageIconsMap = {
export default function Message( {
severity = MESSAGE_SEVERITY_INFO,
icon = null,
showSidebarIcon = false,
onSidebarIconClick = () => {},
showAIFeedbackThumbs = false,
ratedItem = '',
prompt = '',
onRate = () => {},
children,
}: MessageProps ): React.ReactElement {
return (
Expand All @@ -85,11 +97,17 @@ export default function Message( {
{ ( messageIconsMap[ severity ] || icon ) && (
<Icon icon={ messageIconsMap[ severity ] || icon } />
) }
<div className="jetpack-ai-assistant__message-content">{ children }</div>
{ showSidebarIcon && (
<Button className="jetpack-ai-assistant__message-sidebar" onClick={ onSidebarIconClick }>
<Icon size={ 20 } icon={ arrowRight } />
</Button>
{ <div className="jetpack-ai-assistant__message-content">{ children }</div> }
{ showAIFeedbackThumbs && (
<AiFeedbackThumbs
disabled={ false }
ratedItem={ ratedItem }
feature="ai-assistant"
options={ {
prompt,
} }
onRate={ onRate }
/>
) }
</div>
);
Expand All @@ -111,11 +129,15 @@ function LearnMoreLink(): React.ReactElement {
/**
* React component to render a guideline message.
*
* @param {GuidelineMessageProps} props - Component props.
* @return {React.ReactElement} - Message component.
*/
export function GuidelineMessage(): React.ReactElement {
export function GuidelineMessage( {
showAIFeedbackThumbs = false,
...props
}: GuidelineMessageProps ): React.ReactElement {
return (
<Message>
<Message showAIFeedbackThumbs={ showAIFeedbackThumbs } { ...props }>
<span>
{ __( 'AI-generated content could be inaccurate or biased.', 'jetpack-ai-client' ) }
</span>
Expand Down

0 comments on commit bff33ba

Please sign in to comment.