@automattic/jetpack-ai-client 0.25.2 → 0.25.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/build/ai-client/src/components/ai-feedback/index.d.ts +26 -0
- package/build/ai-client/src/components/ai-feedback/index.js +70 -0
- package/build/ai-client/src/components/index.d.ts +1 -0
- package/build/ai-client/src/components/index.js +1 -0
- package/build/ai-client/src/logo-generator/components/logo-presenter.js +25 -1
- package/build/ai-client/src/logo-generator/hooks/use-logo-generator.js +3 -0
- package/build/ai-client/src/logo-generator/lib/logo-storage.d.ts +9 -8
- package/build/ai-client/src/logo-generator/lib/logo-storage.js +12 -8
- package/build/ai-client/src/logo-generator/store/types.d.ts +2 -0
- package/build/ai-client/src/logo-generator/types.d.ts +1 -0
- package/package.json +2 -1
- package/src/components/ai-feedback/index.tsx +133 -0
- package/src/components/ai-feedback/style.scss +16 -0
- package/src/components/index.ts +1 -0
- package/src/logo-generator/components/logo-presenter.tsx +41 -0
- package/src/logo-generator/hooks/use-logo-generator.ts +4 -0
- package/src/logo-generator/lib/logo-storage.ts +18 -8
- package/src/logo-generator/store/types.ts +2 -0
- package/src/logo-generator/types.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.25.3] - 2024-12-23
|
|
9
|
+
### Added
|
|
10
|
+
- Jetpack AI: Add thumbs up/down component to AI logo generator [#40610]
|
|
11
|
+
|
|
8
12
|
## [0.25.2] - 2024-12-16
|
|
9
13
|
### Changed
|
|
10
14
|
- Updated package dependencies. [#40564]
|
|
@@ -483,6 +487,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
483
487
|
- AI Client: stop using smart document visibility handling on the fetchEventSource library, so it does not restart the completion when changing tabs. [#32004]
|
|
484
488
|
- Updated package dependencies. [#31468] [#31659] [#31785]
|
|
485
489
|
|
|
490
|
+
[0.25.3]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.2...v0.25.3
|
|
486
491
|
[0.25.2]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.1...v0.25.2
|
|
487
492
|
[0.25.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.0...v0.25.1
|
|
488
493
|
[0.25.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.24.3...v0.25.0
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import './style.scss';
|
|
2
|
+
/**
|
|
3
|
+
* Types
|
|
4
|
+
*/
|
|
5
|
+
import type React from 'react';
|
|
6
|
+
type AiFeedbackThumbsProps = {
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
iconSize?: number;
|
|
9
|
+
ratedItem?: string;
|
|
10
|
+
feature?: string;
|
|
11
|
+
savedRatings?: Record<string, string>;
|
|
12
|
+
options?: {
|
|
13
|
+
mediaLibraryId?: number;
|
|
14
|
+
prompt?: string;
|
|
15
|
+
revisedPrompt?: string;
|
|
16
|
+
};
|
|
17
|
+
onRate?: (rating: string) => void;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* AiFeedbackThumbs component.
|
|
21
|
+
*
|
|
22
|
+
* @param {AiFeedbackThumbsProps} props - component props.
|
|
23
|
+
* @return {React.ReactElement} - rendered component.
|
|
24
|
+
*/
|
|
25
|
+
export default function AiFeedbackThumbs({ disabled, iconSize, ratedItem, feature, savedRatings, options, onRate, }: AiFeedbackThumbsProps): React.ReactElement;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* External dependencies
|
|
4
|
+
*/
|
|
5
|
+
import { useAnalytics, getJetpackExtensionAvailability, } from '@automattic/jetpack-shared-extension-utils';
|
|
6
|
+
import { Button, Tooltip } from '@wordpress/components';
|
|
7
|
+
import { useEffect, useState } from '@wordpress/element';
|
|
8
|
+
import { __ } from '@wordpress/i18n';
|
|
9
|
+
import { thumbsUp, thumbsDown } from '@wordpress/icons';
|
|
10
|
+
import clsx from 'clsx';
|
|
11
|
+
/*
|
|
12
|
+
* Internal dependencies
|
|
13
|
+
*/
|
|
14
|
+
import './style.scss';
|
|
15
|
+
/**
|
|
16
|
+
* Get the availability of a feature.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} feature - The feature to check availability for.
|
|
19
|
+
* @return {boolean} - Whether the feature is available.
|
|
20
|
+
*/
|
|
21
|
+
function getFeatureAvailability(feature) {
|
|
22
|
+
return getJetpackExtensionAvailability(feature).available === true;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* AiFeedbackThumbs component.
|
|
26
|
+
*
|
|
27
|
+
* @param {AiFeedbackThumbsProps} props - component props.
|
|
28
|
+
* @return {React.ReactElement} - rendered component.
|
|
29
|
+
*/
|
|
30
|
+
export default function AiFeedbackThumbs({ disabled = false, iconSize = 24, ratedItem = '', feature = '', savedRatings = {}, options = {}, onRate, }) {
|
|
31
|
+
if (!getFeatureAvailability('ai-response-feedback')) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const [itemsRated, setItemsRated] = useState({});
|
|
35
|
+
const { tracks } = useAnalytics();
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const newItemsRated = { ...savedRatings, ...itemsRated };
|
|
38
|
+
if (JSON.stringify(newItemsRated) !== JSON.stringify(itemsRated)) {
|
|
39
|
+
setItemsRated(newItemsRated);
|
|
40
|
+
}
|
|
41
|
+
}, [savedRatings]);
|
|
42
|
+
const checkThumb = (thumbValue) => {
|
|
43
|
+
if (!itemsRated[ratedItem]) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return itemsRated[ratedItem] === thumbValue;
|
|
47
|
+
};
|
|
48
|
+
const rateAI = (isThumbsUp) => {
|
|
49
|
+
const aiRating = isThumbsUp ? 'thumbs-up' : 'thumbs-down';
|
|
50
|
+
if (!checkThumb(aiRating)) {
|
|
51
|
+
setItemsRated({
|
|
52
|
+
...itemsRated,
|
|
53
|
+
[ratedItem]: aiRating,
|
|
54
|
+
});
|
|
55
|
+
onRate?.(aiRating);
|
|
56
|
+
tracks.recordEvent('jetpack_ai_feedback', {
|
|
57
|
+
type: feature,
|
|
58
|
+
rating: aiRating,
|
|
59
|
+
mediaLibraryId: options.mediaLibraryId || null,
|
|
60
|
+
prompt: options.prompt || null,
|
|
61
|
+
revisedPrompt: options.revisedPrompt || null,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
return (_jsxs("div", { className: "ai-assistant-feedback__selection", children: [_jsx(Tooltip, { text: __('I like this', 'jetpack-ai-client'), children: _jsx(Button, { disabled: disabled, icon: thumbsUp, onClick: () => rateAI(true), iconSize: iconSize, showTooltip: false, className: clsx({
|
|
66
|
+
'ai-assistant-feedback__thumb-selected': checkThumb('thumbs-up'),
|
|
67
|
+
}) }) }), _jsx(Tooltip, { text: __("I don't find this useful", 'jetpack-ai-client'), children: _jsx(Button, { disabled: disabled, icon: thumbsDown, onClick: () => rateAI(false), iconSize: iconSize, showTooltip: false, className: clsx({
|
|
68
|
+
'ai-assistant-feedback__thumb-selected': checkThumb('thumbs-down'),
|
|
69
|
+
}) }) })] }));
|
|
70
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { AIControl, BlockAIControl, ExtensionAIControl } from './ai-control/index.js';
|
|
2
|
+
export { default as AiFeedbackThumbs } from './ai-feedback/index.js';
|
|
2
3
|
export { default as AiStatusIndicator } from './ai-status-indicator/index.js';
|
|
3
4
|
export { default as AudioDurationDisplay } from './audio-duration-display/index.js';
|
|
4
5
|
export { default as AiModalFooter } from './ai-modal-footer/index.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { AIControl, BlockAIControl, ExtensionAIControl } from './ai-control/index.js';
|
|
2
|
+
export { default as AiFeedbackThumbs } from './ai-feedback/index.js';
|
|
2
3
|
export { default as AiStatusIndicator } from './ai-status-indicator/index.js';
|
|
3
4
|
export { default as AudioDurationDisplay } from './audio-duration-display/index.js';
|
|
4
5
|
export { default as AiModalFooter } from './ai-modal-footer/index.js';
|
|
@@ -10,6 +10,7 @@ import debugFactory from 'debug';
|
|
|
10
10
|
/**
|
|
11
11
|
* Internal dependencies
|
|
12
12
|
*/
|
|
13
|
+
import AiFeedbackThumbs from '../../components/ai-feedback/index.js';
|
|
13
14
|
import CheckIcon from '../assets/icons/check.js';
|
|
14
15
|
import LogoIcon from '../assets/icons/logo.js';
|
|
15
16
|
import MediaIcon from '../assets/icons/media.js';
|
|
@@ -80,8 +81,31 @@ const LogoFetching = () => {
|
|
|
80
81
|
const LogoEmpty = () => {
|
|
81
82
|
return (_jsxs(_Fragment, { children: [_jsx("div", { style: { width: 0, height: '229px' } }), _jsx("span", { className: "jetpack-ai-logo-generator-modal-presenter__loading-text", children: __('Once you generate a logo, it will show up here', 'jetpack-ai-client') })] }));
|
|
82
83
|
};
|
|
84
|
+
const RateLogo = ({ disabled, ratedItem, onRate }) => {
|
|
85
|
+
const { logos, selectedLogo } = useLogoGenerator();
|
|
86
|
+
const savedRatings = logos
|
|
87
|
+
.filter(logo => logo.rating)
|
|
88
|
+
.reduce((acc, logo) => {
|
|
89
|
+
acc[logo.url] = logo.rating;
|
|
90
|
+
return acc;
|
|
91
|
+
}, {});
|
|
92
|
+
return (_jsx(AiFeedbackThumbs, { disabled: disabled, ratedItem: ratedItem, feature: "logo-generator", savedRatings: savedRatings, options: {
|
|
93
|
+
mediaLibraryId: selectedLogo.mediaId,
|
|
94
|
+
prompt: selectedLogo.description,
|
|
95
|
+
}, onRate: onRate }));
|
|
96
|
+
};
|
|
83
97
|
const LogoReady = ({ siteId, logo, onApplyLogo }) => {
|
|
84
|
-
|
|
98
|
+
const handleRateLogo = (rating) => {
|
|
99
|
+
// Update localStorage
|
|
100
|
+
updateLogo({
|
|
101
|
+
siteId,
|
|
102
|
+
url: logo.url,
|
|
103
|
+
newUrl: logo.url,
|
|
104
|
+
mediaId: logo.mediaId,
|
|
105
|
+
rating,
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
return (_jsxs(_Fragment, { children: [_jsx("img", { src: logo.url, alt: logo.description, className: "jetpack-ai-logo-generator-modal-presenter__logo" }), _jsxs("div", { className: "jetpack-ai-logo-generator-modal-presenter__action-wrapper", children: [_jsx("span", { className: "jetpack-ai-logo-generator-modal-presenter__description", children: logo.description }), _jsxs("div", { className: "jetpack-ai-logo-generator-modal-presenter__actions", children: [_jsx(SaveInLibraryButton, { siteId: siteId }), _jsx(UseOnSiteButton, { onApplyLogo: onApplyLogo }), _jsx(RateLogo, { ratedItem: logo.url, disabled: false, onRate: handleRateLogo })] })] })] }));
|
|
85
109
|
};
|
|
86
110
|
const LogoUpdated = ({ logo }) => {
|
|
87
111
|
return (_jsxs(_Fragment, { children: [_jsx("img", { src: logo.url, alt: logo.description, className: "jetpack-ai-logo-generator-modal-presenter__logo" }), _jsxs("div", { className: "jetpack-ai-logo-generator-modal-presenter__success-wrapper", children: [_jsx(Icon, { icon: _jsx(CheckIcon, {}) }), _jsx("span", { children: __('Your new logo was set to the block!', 'jetpack-ai-client') })] })] }));
|
|
@@ -285,10 +285,12 @@ User request:${prompt}`;
|
|
|
285
285
|
increaseAiAssistantRequestsCount(-logoGenerationCost);
|
|
286
286
|
throw error;
|
|
287
287
|
}
|
|
288
|
+
const revisedPrompt = image.data[0].revised_prompt || null;
|
|
288
289
|
// response_format=url returns object with url, otherwise b64_json
|
|
289
290
|
const logo = {
|
|
290
291
|
url: 'data:image/png;base64,' + image.data[0].b64_json,
|
|
291
292
|
description: prompt,
|
|
293
|
+
revisedPrompt,
|
|
292
294
|
};
|
|
293
295
|
try {
|
|
294
296
|
const savedLogo = await saveLogo(logo);
|
|
@@ -296,6 +298,7 @@ User request:${prompt}`;
|
|
|
296
298
|
url: savedLogo.mediaURL,
|
|
297
299
|
description: prompt,
|
|
298
300
|
mediaId: savedLogo.mediaId,
|
|
301
|
+
revisedPrompt,
|
|
299
302
|
});
|
|
300
303
|
}
|
|
301
304
|
catch (error) {
|
|
@@ -6,15 +6,15 @@ import { RemoveFromStorageProps, SaveToStorageProps, UpdateInStorageProps } from
|
|
|
6
6
|
/**
|
|
7
7
|
* Add an entry to the site's logo history.
|
|
8
8
|
*
|
|
9
|
-
* @param {SaveToStorageProps}
|
|
10
|
-
* @param {SaveToStorageProps.siteId}
|
|
11
|
-
* @param {SaveToStorageProps.url}
|
|
12
|
-
* @param {SaveToStorageProps.description}
|
|
13
|
-
* @param {SaveToStorageProps.mediaId}
|
|
14
|
-
*
|
|
9
|
+
* @param {SaveToStorageProps} saveToStorageProps - The properties to save to storage
|
|
10
|
+
* @param {SaveToStorageProps.siteId} saveToStorageProps.siteId - The site ID
|
|
11
|
+
* @param {SaveToStorageProps.url} saveToStorageProps.url - The URL of the logo
|
|
12
|
+
* @param {SaveToStorageProps.description} saveToStorageProps.description - The description of the logo, based on the prompt used to generate it
|
|
13
|
+
* @param {SaveToStorageProps.mediaId} saveToStorageProps.mediaId - The media ID of the logo on the backend
|
|
14
|
+
* @param {SaveToStorageProps.revisedPrompt} saveToStorageProps.revisedPrompt - The revised prompt of the logo
|
|
15
15
|
* @return {Logo} The logo that was saved
|
|
16
16
|
*/
|
|
17
|
-
export declare function stashLogo({ siteId, url, description, mediaId }: SaveToStorageProps): Logo;
|
|
17
|
+
export declare function stashLogo({ siteId, url, description, mediaId, revisedPrompt, }: SaveToStorageProps): Logo;
|
|
18
18
|
/**
|
|
19
19
|
* Update an entry in the site's logo history.
|
|
20
20
|
*
|
|
@@ -23,9 +23,10 @@ export declare function stashLogo({ siteId, url, description, mediaId }: SaveToS
|
|
|
23
23
|
* @param {UpdateInStorageProps.url} updateInStorageProps.url - The URL of the logo to update
|
|
24
24
|
* @param {UpdateInStorageProps.newUrl} updateInStorageProps.newUrl - The new URL of the logo
|
|
25
25
|
* @param {UpdateInStorageProps.mediaId} updateInStorageProps.mediaId - The new media ID of the logo
|
|
26
|
+
* @param {UpdateInStorageProps.rating} updateInStorageProps.rating - The new rating of the logo
|
|
26
27
|
* @return {Logo} The logo that was updated
|
|
27
28
|
*/
|
|
28
|
-
export declare function updateLogo({ siteId, url, newUrl, mediaId }: UpdateInStorageProps): Logo;
|
|
29
|
+
export declare function updateLogo({ siteId, url, newUrl, mediaId, rating }: UpdateInStorageProps): Logo;
|
|
29
30
|
/**
|
|
30
31
|
* Get the logo history for a site.
|
|
31
32
|
*
|
|
@@ -3,20 +3,21 @@ const MAX_LOGOS = 10;
|
|
|
3
3
|
/**
|
|
4
4
|
* Add an entry to the site's logo history.
|
|
5
5
|
*
|
|
6
|
-
* @param {SaveToStorageProps}
|
|
7
|
-
* @param {SaveToStorageProps.siteId}
|
|
8
|
-
* @param {SaveToStorageProps.url}
|
|
9
|
-
* @param {SaveToStorageProps.description}
|
|
10
|
-
* @param {SaveToStorageProps.mediaId}
|
|
11
|
-
*
|
|
6
|
+
* @param {SaveToStorageProps} saveToStorageProps - The properties to save to storage
|
|
7
|
+
* @param {SaveToStorageProps.siteId} saveToStorageProps.siteId - The site ID
|
|
8
|
+
* @param {SaveToStorageProps.url} saveToStorageProps.url - The URL of the logo
|
|
9
|
+
* @param {SaveToStorageProps.description} saveToStorageProps.description - The description of the logo, based on the prompt used to generate it
|
|
10
|
+
* @param {SaveToStorageProps.mediaId} saveToStorageProps.mediaId - The media ID of the logo on the backend
|
|
11
|
+
* @param {SaveToStorageProps.revisedPrompt} saveToStorageProps.revisedPrompt - The revised prompt of the logo
|
|
12
12
|
* @return {Logo} The logo that was saved
|
|
13
13
|
*/
|
|
14
|
-
export function stashLogo({ siteId, url, description, mediaId }) {
|
|
14
|
+
export function stashLogo({ siteId, url, description, mediaId, revisedPrompt, }) {
|
|
15
15
|
const storedContent = getSiteLogoHistory(siteId);
|
|
16
16
|
const logo = {
|
|
17
17
|
url,
|
|
18
18
|
description,
|
|
19
19
|
mediaId,
|
|
20
|
+
revisedPrompt,
|
|
20
21
|
};
|
|
21
22
|
storedContent.push(logo);
|
|
22
23
|
localStorage.setItem(`logo-history-${siteId}`, JSON.stringify(storedContent.slice(-MAX_LOGOS)));
|
|
@@ -30,14 +31,16 @@ export function stashLogo({ siteId, url, description, mediaId }) {
|
|
|
30
31
|
* @param {UpdateInStorageProps.url} updateInStorageProps.url - The URL of the logo to update
|
|
31
32
|
* @param {UpdateInStorageProps.newUrl} updateInStorageProps.newUrl - The new URL of the logo
|
|
32
33
|
* @param {UpdateInStorageProps.mediaId} updateInStorageProps.mediaId - The new media ID of the logo
|
|
34
|
+
* @param {UpdateInStorageProps.rating} updateInStorageProps.rating - The new rating of the logo
|
|
33
35
|
* @return {Logo} The logo that was updated
|
|
34
36
|
*/
|
|
35
|
-
export function updateLogo({ siteId, url, newUrl, mediaId }) {
|
|
37
|
+
export function updateLogo({ siteId, url, newUrl, mediaId, rating }) {
|
|
36
38
|
const storedContent = getSiteLogoHistory(siteId);
|
|
37
39
|
const index = storedContent.findIndex(logo => logo.url === url);
|
|
38
40
|
if (index > -1) {
|
|
39
41
|
storedContent[index].url = newUrl;
|
|
40
42
|
storedContent[index].mediaId = mediaId;
|
|
43
|
+
storedContent[index].rating = rating;
|
|
41
44
|
}
|
|
42
45
|
localStorage.setItem(`logo-history-${siteId}`, JSON.stringify(storedContent.slice(-MAX_LOGOS)));
|
|
43
46
|
return storedContent[index];
|
|
@@ -68,6 +71,7 @@ export function getSiteLogoHistory(siteId) {
|
|
|
68
71
|
url: logo.url,
|
|
69
72
|
description: logo.description,
|
|
70
73
|
mediaId: logo.mediaId,
|
|
74
|
+
rating: logo.rating,
|
|
71
75
|
}));
|
|
72
76
|
return storedContent;
|
|
73
77
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"private": false,
|
|
3
3
|
"name": "@automattic/jetpack-ai-client",
|
|
4
|
-
"version": "0.25.
|
|
4
|
+
"version": "0.25.3",
|
|
5
5
|
"description": "A JS client for consuming Jetpack AI services",
|
|
6
6
|
"homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/ai-client/#readme",
|
|
7
7
|
"bugs": {
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"@types/react": "18.3.12",
|
|
52
52
|
"@types/wordpress__block-editor": "11.5.15",
|
|
53
53
|
"@wordpress/api-fetch": "7.14.0",
|
|
54
|
+
"@wordpress/base-styles": "5.14.0",
|
|
54
55
|
"@wordpress/blob": "4.14.0",
|
|
55
56
|
"@wordpress/block-editor": "14.9.0",
|
|
56
57
|
"@wordpress/components": "29.0.0",
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
useAnalytics,
|
|
6
|
+
getJetpackExtensionAvailability,
|
|
7
|
+
} from '@automattic/jetpack-shared-extension-utils';
|
|
8
|
+
import { Button, Tooltip } from '@wordpress/components';
|
|
9
|
+
import { useEffect, useState } from '@wordpress/element';
|
|
10
|
+
import { __ } from '@wordpress/i18n';
|
|
11
|
+
import { thumbsUp, thumbsDown } from '@wordpress/icons';
|
|
12
|
+
import clsx from 'clsx';
|
|
13
|
+
/*
|
|
14
|
+
* Internal dependencies
|
|
15
|
+
*/
|
|
16
|
+
import './style.scss';
|
|
17
|
+
/**
|
|
18
|
+
* Types
|
|
19
|
+
*/
|
|
20
|
+
import type React from 'react';
|
|
21
|
+
|
|
22
|
+
type AiFeedbackThumbsProps = {
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
iconSize?: number;
|
|
25
|
+
ratedItem?: string;
|
|
26
|
+
feature?: string;
|
|
27
|
+
savedRatings?: Record< string, string >;
|
|
28
|
+
options?: {
|
|
29
|
+
mediaLibraryId?: number;
|
|
30
|
+
prompt?: string;
|
|
31
|
+
revisedPrompt?: string;
|
|
32
|
+
};
|
|
33
|
+
onRate?: ( rating: string ) => void;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get the availability of a feature.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} feature - The feature to check availability for.
|
|
40
|
+
* @return {boolean} - Whether the feature is available.
|
|
41
|
+
*/
|
|
42
|
+
function getFeatureAvailability( feature: string ): boolean {
|
|
43
|
+
return getJetpackExtensionAvailability( feature ).available === true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* AiFeedbackThumbs component.
|
|
48
|
+
*
|
|
49
|
+
* @param {AiFeedbackThumbsProps} props - component props.
|
|
50
|
+
* @return {React.ReactElement} - rendered component.
|
|
51
|
+
*/
|
|
52
|
+
export default function AiFeedbackThumbs( {
|
|
53
|
+
disabled = false,
|
|
54
|
+
iconSize = 24,
|
|
55
|
+
ratedItem = '',
|
|
56
|
+
feature = '',
|
|
57
|
+
savedRatings = {},
|
|
58
|
+
options = {},
|
|
59
|
+
onRate,
|
|
60
|
+
}: AiFeedbackThumbsProps ): React.ReactElement {
|
|
61
|
+
if ( ! getFeatureAvailability( 'ai-response-feedback' ) ) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const [ itemsRated, setItemsRated ] = useState( {} );
|
|
66
|
+
const { tracks } = useAnalytics();
|
|
67
|
+
|
|
68
|
+
useEffect( () => {
|
|
69
|
+
const newItemsRated = { ...savedRatings, ...itemsRated };
|
|
70
|
+
|
|
71
|
+
if ( JSON.stringify( newItemsRated ) !== JSON.stringify( itemsRated ) ) {
|
|
72
|
+
setItemsRated( newItemsRated );
|
|
73
|
+
}
|
|
74
|
+
}, [ savedRatings ] );
|
|
75
|
+
|
|
76
|
+
const checkThumb = ( thumbValue: string ) => {
|
|
77
|
+
if ( ! itemsRated[ ratedItem ] ) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return itemsRated[ ratedItem ] === thumbValue;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const rateAI = ( isThumbsUp: boolean ) => {
|
|
85
|
+
const aiRating = isThumbsUp ? 'thumbs-up' : 'thumbs-down';
|
|
86
|
+
|
|
87
|
+
if ( ! checkThumb( aiRating ) ) {
|
|
88
|
+
setItemsRated( {
|
|
89
|
+
...itemsRated,
|
|
90
|
+
[ ratedItem ]: aiRating,
|
|
91
|
+
} );
|
|
92
|
+
|
|
93
|
+
onRate?.( aiRating );
|
|
94
|
+
|
|
95
|
+
tracks.recordEvent( 'jetpack_ai_feedback', {
|
|
96
|
+
type: feature,
|
|
97
|
+
rating: aiRating,
|
|
98
|
+
mediaLibraryId: options.mediaLibraryId || null,
|
|
99
|
+
prompt: options.prompt || null,
|
|
100
|
+
revisedPrompt: options.revisedPrompt || null,
|
|
101
|
+
} );
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div className="ai-assistant-feedback__selection">
|
|
107
|
+
<Tooltip text={ __( 'I like this', 'jetpack-ai-client' ) }>
|
|
108
|
+
<Button
|
|
109
|
+
disabled={ disabled }
|
|
110
|
+
icon={ thumbsUp }
|
|
111
|
+
onClick={ () => rateAI( true ) }
|
|
112
|
+
iconSize={ iconSize }
|
|
113
|
+
showTooltip={ false }
|
|
114
|
+
className={ clsx( {
|
|
115
|
+
'ai-assistant-feedback__thumb-selected': checkThumb( 'thumbs-up' ),
|
|
116
|
+
} ) }
|
|
117
|
+
/>
|
|
118
|
+
</Tooltip>
|
|
119
|
+
<Tooltip text={ __( "I don't find this useful", 'jetpack-ai-client' ) }>
|
|
120
|
+
<Button
|
|
121
|
+
disabled={ disabled }
|
|
122
|
+
icon={ thumbsDown }
|
|
123
|
+
onClick={ () => rateAI( false ) }
|
|
124
|
+
iconSize={ iconSize }
|
|
125
|
+
showTooltip={ false }
|
|
126
|
+
className={ clsx( {
|
|
127
|
+
'ai-assistant-feedback__thumb-selected': checkThumb( 'thumbs-down' ),
|
|
128
|
+
} ) }
|
|
129
|
+
/>
|
|
130
|
+
</Tooltip>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
@import "@wordpress/base-styles/colors";
|
|
2
|
+
|
|
3
|
+
.ai-assistant-feedback {
|
|
4
|
+
&__selection {
|
|
5
|
+
display: flex;
|
|
6
|
+
|
|
7
|
+
.components-button.has-icon {
|
|
8
|
+
padding: 0;
|
|
9
|
+
min-width: 28px;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
&__thumb-selected {
|
|
14
|
+
color: var( --wp-components-color-accent, var( --wp-admin-theme-color, #3858e9 ) );
|
|
15
|
+
}
|
|
16
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { AIControl, BlockAIControl, ExtensionAIControl } from './ai-control/index.js';
|
|
2
|
+
export { default as AiFeedbackThumbs } from './ai-feedback/index.js';
|
|
2
3
|
export { default as AiStatusIndicator } from './ai-status-indicator/index.js';
|
|
3
4
|
export { default as AudioDurationDisplay } from './audio-duration-display/index.js';
|
|
4
5
|
export { default as AiModalFooter } from './ai-modal-footer/index.js';
|
|
@@ -9,6 +9,7 @@ import debugFactory from 'debug';
|
|
|
9
9
|
/**
|
|
10
10
|
* Internal dependencies
|
|
11
11
|
*/
|
|
12
|
+
import AiFeedbackThumbs from '../../components/ai-feedback/index.js';
|
|
12
13
|
import CheckIcon from '../assets/icons/check.js';
|
|
13
14
|
import LogoIcon from '../assets/icons/logo.js';
|
|
14
15
|
import MediaIcon from '../assets/icons/media.js';
|
|
@@ -152,11 +153,50 @@ const LogoEmpty: React.FC = () => {
|
|
|
152
153
|
);
|
|
153
154
|
};
|
|
154
155
|
|
|
156
|
+
const RateLogo: React.FC< {
|
|
157
|
+
disabled: boolean;
|
|
158
|
+
ratedItem: string;
|
|
159
|
+
onRate: ( rating: string ) => void;
|
|
160
|
+
} > = ( { disabled, ratedItem, onRate } ) => {
|
|
161
|
+
const { logos, selectedLogo } = useLogoGenerator();
|
|
162
|
+
const savedRatings = logos
|
|
163
|
+
.filter( logo => logo.rating )
|
|
164
|
+
.reduce( ( acc, logo ) => {
|
|
165
|
+
acc[ logo.url ] = logo.rating;
|
|
166
|
+
return acc;
|
|
167
|
+
}, {} );
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<AiFeedbackThumbs
|
|
171
|
+
disabled={ disabled }
|
|
172
|
+
ratedItem={ ratedItem }
|
|
173
|
+
feature="logo-generator"
|
|
174
|
+
savedRatings={ savedRatings }
|
|
175
|
+
options={ {
|
|
176
|
+
mediaLibraryId: selectedLogo.mediaId,
|
|
177
|
+
prompt: selectedLogo.description,
|
|
178
|
+
} }
|
|
179
|
+
onRate={ onRate }
|
|
180
|
+
/>
|
|
181
|
+
);
|
|
182
|
+
};
|
|
183
|
+
|
|
155
184
|
const LogoReady: React.FC< {
|
|
156
185
|
siteId: string;
|
|
157
186
|
logo: Logo;
|
|
158
187
|
onApplyLogo: ( mediaId: number ) => void;
|
|
159
188
|
} > = ( { siteId, logo, onApplyLogo } ) => {
|
|
189
|
+
const handleRateLogo = ( rating: string ) => {
|
|
190
|
+
// Update localStorage
|
|
191
|
+
updateLogo( {
|
|
192
|
+
siteId,
|
|
193
|
+
url: logo.url,
|
|
194
|
+
newUrl: logo.url,
|
|
195
|
+
mediaId: logo.mediaId,
|
|
196
|
+
rating,
|
|
197
|
+
} );
|
|
198
|
+
};
|
|
199
|
+
|
|
160
200
|
return (
|
|
161
201
|
<>
|
|
162
202
|
<img
|
|
@@ -171,6 +211,7 @@ const LogoReady: React.FC< {
|
|
|
171
211
|
<div className="jetpack-ai-logo-generator-modal-presenter__actions">
|
|
172
212
|
<SaveInLibraryButton siteId={ siteId } />
|
|
173
213
|
<UseOnSiteButton onApplyLogo={ onApplyLogo } />
|
|
214
|
+
<RateLogo ratedItem={ logo.url } disabled={ false } onRate={ handleRateLogo } />
|
|
174
215
|
</div>
|
|
175
216
|
</div>
|
|
176
217
|
</>
|
|
@@ -412,10 +412,13 @@ User request:${ prompt }`;
|
|
|
412
412
|
throw error;
|
|
413
413
|
}
|
|
414
414
|
|
|
415
|
+
const revisedPrompt = image.data[ 0 ].revised_prompt || null;
|
|
416
|
+
|
|
415
417
|
// response_format=url returns object with url, otherwise b64_json
|
|
416
418
|
const logo: Logo = {
|
|
417
419
|
url: 'data:image/png;base64,' + image.data[ 0 ].b64_json,
|
|
418
420
|
description: prompt,
|
|
421
|
+
revisedPrompt,
|
|
419
422
|
};
|
|
420
423
|
|
|
421
424
|
try {
|
|
@@ -424,6 +427,7 @@ User request:${ prompt }`;
|
|
|
424
427
|
url: savedLogo.mediaURL,
|
|
425
428
|
description: prompt,
|
|
426
429
|
mediaId: savedLogo.mediaId,
|
|
430
|
+
revisedPrompt,
|
|
427
431
|
} );
|
|
428
432
|
} catch ( error ) {
|
|
429
433
|
storeLogo( logo );
|
|
@@ -10,21 +10,28 @@ const MAX_LOGOS = 10;
|
|
|
10
10
|
/**
|
|
11
11
|
* Add an entry to the site's logo history.
|
|
12
12
|
*
|
|
13
|
-
* @param {SaveToStorageProps}
|
|
14
|
-
* @param {SaveToStorageProps.siteId}
|
|
15
|
-
* @param {SaveToStorageProps.url}
|
|
16
|
-
* @param {SaveToStorageProps.description}
|
|
17
|
-
* @param {SaveToStorageProps.mediaId}
|
|
18
|
-
*
|
|
13
|
+
* @param {SaveToStorageProps} saveToStorageProps - The properties to save to storage
|
|
14
|
+
* @param {SaveToStorageProps.siteId} saveToStorageProps.siteId - The site ID
|
|
15
|
+
* @param {SaveToStorageProps.url} saveToStorageProps.url - The URL of the logo
|
|
16
|
+
* @param {SaveToStorageProps.description} saveToStorageProps.description - The description of the logo, based on the prompt used to generate it
|
|
17
|
+
* @param {SaveToStorageProps.mediaId} saveToStorageProps.mediaId - The media ID of the logo on the backend
|
|
18
|
+
* @param {SaveToStorageProps.revisedPrompt} saveToStorageProps.revisedPrompt - The revised prompt of the logo
|
|
19
19
|
* @return {Logo} The logo that was saved
|
|
20
20
|
*/
|
|
21
|
-
export function stashLogo( {
|
|
21
|
+
export function stashLogo( {
|
|
22
|
+
siteId,
|
|
23
|
+
url,
|
|
24
|
+
description,
|
|
25
|
+
mediaId,
|
|
26
|
+
revisedPrompt,
|
|
27
|
+
}: SaveToStorageProps ) {
|
|
22
28
|
const storedContent = getSiteLogoHistory( siteId );
|
|
23
29
|
|
|
24
30
|
const logo: Logo = {
|
|
25
31
|
url,
|
|
26
32
|
description,
|
|
27
33
|
mediaId,
|
|
34
|
+
revisedPrompt,
|
|
28
35
|
};
|
|
29
36
|
|
|
30
37
|
storedContent.push( logo );
|
|
@@ -45,9 +52,10 @@ export function stashLogo( { siteId, url, description, mediaId }: SaveToStorageP
|
|
|
45
52
|
* @param {UpdateInStorageProps.url} updateInStorageProps.url - The URL of the logo to update
|
|
46
53
|
* @param {UpdateInStorageProps.newUrl} updateInStorageProps.newUrl - The new URL of the logo
|
|
47
54
|
* @param {UpdateInStorageProps.mediaId} updateInStorageProps.mediaId - The new media ID of the logo
|
|
55
|
+
* @param {UpdateInStorageProps.rating} updateInStorageProps.rating - The new rating of the logo
|
|
48
56
|
* @return {Logo} The logo that was updated
|
|
49
57
|
*/
|
|
50
|
-
export function updateLogo( { siteId, url, newUrl, mediaId }: UpdateInStorageProps ) {
|
|
58
|
+
export function updateLogo( { siteId, url, newUrl, mediaId, rating }: UpdateInStorageProps ) {
|
|
51
59
|
const storedContent = getSiteLogoHistory( siteId );
|
|
52
60
|
|
|
53
61
|
const index = storedContent.findIndex( logo => logo.url === url );
|
|
@@ -55,6 +63,7 @@ export function updateLogo( { siteId, url, newUrl, mediaId }: UpdateInStoragePro
|
|
|
55
63
|
if ( index > -1 ) {
|
|
56
64
|
storedContent[ index ].url = newUrl;
|
|
57
65
|
storedContent[ index ].mediaId = mediaId;
|
|
66
|
+
storedContent[ index ].rating = rating;
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
localStorage.setItem(
|
|
@@ -96,6 +105,7 @@ export function getSiteLogoHistory( siteId: string ) {
|
|
|
96
105
|
url: logo.url,
|
|
97
106
|
description: logo.description,
|
|
98
107
|
mediaId: logo.mediaId,
|
|
108
|
+
rating: logo.rating,
|
|
99
109
|
} ) );
|
|
100
110
|
|
|
101
111
|
return storedContent;
|