@automattic/jetpack-ai-client 0.25.0 → 0.25.2

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +17 -3
  2. package/build/ai-client/src/components/ai-modal-footer/index.d.ts +20 -0
  3. package/build/ai-client/src/components/ai-modal-footer/index.js +27 -0
  4. package/build/ai-client/src/components/index.d.ts +1 -0
  5. package/build/ai-client/src/components/index.js +1 -0
  6. package/build/ai-client/src/components/message/index.d.ts +6 -8
  7. package/build/ai-client/src/components/message/index.js +14 -12
  8. package/build/ai-client/src/data-flow/context.d.ts +1 -2
  9. package/build/ai-client/src/hooks/use-ai-suggestions/index.d.ts +1 -2
  10. package/build/ai-client/src/logo-generator/components/fair-usage-notice.d.ts +1 -1
  11. package/build/ai-client/src/logo-generator/components/fair-usage-notice.js +1 -1
  12. package/build/ai-client/src/logo-generator/components/generator-modal.js +2 -3
  13. package/build/ai-client/src/logo-generator/components/logo-presenter.js +0 -1
  14. package/build/ai-client/src/logo-generator/components/prompt.js +2 -2
  15. package/build/ai-client/src/logo-generator/hooks/use-fair-usage-notice-message.d.ts +2 -2
  16. package/build/ai-client/src/logo-generator/lib/logo-storage.js +1 -1
  17. package/build/ai-client/src/logo-generator/store/reducer.d.ts +2 -2
  18. package/package.json +13 -13
  19. package/src/api-fetch/index.ts +1 -1
  20. package/src/ask-question/index.ts +1 -1
  21. package/src/components/ai-modal-footer/index.tsx +71 -0
  22. package/src/components/ai-modal-footer/style.scss +15 -0
  23. package/src/components/index.ts +1 -0
  24. package/src/components/message/index.tsx +25 -16
  25. package/src/data-flow/context.tsx +1 -2
  26. package/src/hooks/use-ai-suggestions/index.ts +1 -2
  27. package/src/logo-generator/components/fair-usage-notice.tsx +1 -5
  28. package/src/logo-generator/components/generator-modal.scss +1 -24
  29. package/src/logo-generator/components/generator-modal.tsx +2 -12
  30. package/src/logo-generator/components/logo-presenter.scss +14 -0
  31. package/src/logo-generator/components/logo-presenter.tsx +0 -1
  32. package/src/logo-generator/components/prompt.scss +15 -1
  33. package/src/logo-generator/components/prompt.tsx +3 -4
  34. package/src/logo-generator/hooks/use-fair-usage-notice-message.tsx +2 -2
  35. package/src/logo-generator/lib/logo-storage.ts +1 -1
  36. package/src/logo-generator/store/reducer.ts +1 -6
package/CHANGELOG.md CHANGED
@@ -5,17 +5,29 @@ 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.2] - 2024-12-16
9
+ ### Changed
10
+ - Updated package dependencies. [#40564]
11
+
12
+ ### Fixed
13
+ - Fixed lints following ESLint rule changes for TS. [#40584]
14
+
15
+ ## [0.25.1] - 2024-12-09
16
+ ### Changed
17
+ - AI Assistant: Add disclaimer to image generation modals. [#40397]
18
+ - Updated package dependencies. [#40363]
19
+
8
20
  ## [0.25.0] - 2024-11-25
9
21
  ### Added
10
- - AI Client: split disabled prop to allow disabling input and action button separately [#40210]
22
+ - AI Client: split disabled prop to allow disabling input and action button separately. [#40210]
11
23
 
12
24
  ### Changed
13
- - AI Client: fix prompt cursor to text when editable [#40247]
25
+ - AI Client: fix prompt cursor to text when editable. [#40247]
14
26
  - Updated package dependencies. [#40288]
15
27
 
16
28
  ## [0.24.3] - 2024-11-18
17
29
  ### Changed
18
- - AI Client: add effect on AiModalInputPrompt to update/set prompt on prop update [#40113]
30
+ - AI Client: add effect on AiModalInputPrompt to update/set prompt on prop update. [#40113]
19
31
 
20
32
  ## [0.24.2] - 2024-11-11
21
33
  ### Changed
@@ -471,6 +483,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
471
483
  - AI Client: stop using smart document visibility handling on the fetchEventSource library, so it does not restart the completion when changing tabs. [#32004]
472
484
  - Updated package dependencies. [#31468] [#31659] [#31785]
473
485
 
486
+ [0.25.2]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.1...v0.25.2
487
+ [0.25.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.25.0...v0.25.1
474
488
  [0.25.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.24.3...v0.25.0
475
489
  [0.24.3]: https://github.com/Automattic/jetpack-ai-client/compare/v0.24.2...v0.24.3
476
490
  [0.24.2]: https://github.com/Automattic/jetpack-ai-client/compare/v0.24.1...v0.24.2
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Internal dependencies
3
+ */
4
+ import './style.scss';
5
+ /**
6
+ * Types
7
+ */
8
+ import type React from 'react';
9
+ type AiModalFooterProps = {
10
+ onGuidelinesClick?: () => void;
11
+ onFeedbackClick?: () => void;
12
+ };
13
+ /**
14
+ * AiModalFooter component.
15
+ *
16
+ * @param {AiModalFooterProps} props - component props.
17
+ * @return {React.ReactElement} - rendered component.
18
+ */
19
+ export default function AiModalFooter({ onGuidelinesClick, onFeedbackClick, }: AiModalFooterProps): React.ReactElement;
20
+ export {};
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * External dependencies
4
+ */
5
+ import { Button } from '@wordpress/components';
6
+ import { useCallback } from '@wordpress/element';
7
+ import { __ } from '@wordpress/i18n';
8
+ import { Icon, info } from '@wordpress/icons';
9
+ /**
10
+ * Internal dependencies
11
+ */
12
+ import './style.scss';
13
+ /**
14
+ * AiModalFooter component.
15
+ *
16
+ * @param {AiModalFooterProps} props - component props.
17
+ * @return {React.ReactElement} - rendered component.
18
+ */
19
+ export default function AiModalFooter({ onGuidelinesClick, onFeedbackClick, }) {
20
+ const handleGuidelinesClick = useCallback(() => {
21
+ onGuidelinesClick?.();
22
+ }, [onGuidelinesClick]);
23
+ const handleFeedbackClick = useCallback(() => {
24
+ onFeedbackClick?.();
25
+ }, [onFeedbackClick]);
26
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "ai-image-modal__footer-disclaimer", children: [_jsx(Icon, { icon: info }), _jsx("span", { children: __('Generated images could be inaccurate, biased or include text.', 'jetpack-ai-client') }), _jsx(Button, { variant: "link", className: "ai-image-modal__guidelines-button", href: "https://jetpack.com/redirect/?source=ai-guidelines", target: "_blank", onClick: handleGuidelinesClick, children: _jsxs("span", { children: [__('Guidelines', 'jetpack-ai-client'), " \u2197"] }) })] }), _jsx(Button, { variant: "link", className: "ai-image-modal__feedback-button", href: "https://jetpack.com/redirect/?source=jetpack-ai-feedback", target: "_blank", onClick: handleFeedbackClick, children: _jsxs("span", { children: [__('Give feedback', 'jetpack-ai-client'), " \u2197"] }) })] }));
27
+ }
@@ -1,4 +1,5 @@
1
1
  export { AIControl, BlockAIControl, ExtensionAIControl } from './ai-control/index.js';
2
2
  export { default as AiStatusIndicator } from './ai-status-indicator/index.js';
3
3
  export { default as AudioDurationDisplay } from './audio-duration-display/index.js';
4
+ export { default as AiModalFooter } from './ai-modal-footer/index.js';
4
5
  export { GuidelineMessage, UpgradeMessage, ErrorMessage, default as FooterMessage, } from './message/index.js';
@@ -1,4 +1,5 @@
1
1
  export { AIControl, BlockAIControl, ExtensionAIControl } from './ai-control/index.js';
2
2
  export { default as AiStatusIndicator } from './ai-status-indicator/index.js';
3
3
  export { default as AudioDurationDisplay } from './audio-duration-display/index.js';
4
+ export { default as AiModalFooter } from './ai-modal-footer/index.js';
4
5
  export { GuidelineMessage, UpgradeMessage, ErrorMessage, default as FooterMessage, } from './message/index.js';
@@ -11,8 +11,7 @@ export declare const MESSAGE_SEVERITY_WARNING = "warning";
11
11
  export declare const MESSAGE_SEVERITY_ERROR = "error";
12
12
  export declare const MESSAGE_SEVERITY_SUCCESS = "success";
13
13
  export declare const MESSAGE_SEVERITY_INFO = "info";
14
- declare const messageSeverityTypes: readonly ["warning", "error", "success", "info"];
15
- export type MessageSeverityProp = (typeof messageSeverityTypes)[number] | null;
14
+ export type MessageSeverityProp = typeof MESSAGE_SEVERITY_WARNING | typeof MESSAGE_SEVERITY_ERROR | typeof MESSAGE_SEVERITY_SUCCESS | typeof MESSAGE_SEVERITY_INFO | null;
16
15
  export type MessageProps = {
17
16
  icon?: React.ReactNode;
18
17
  severity?: MessageSeverityProp;
@@ -38,33 +37,32 @@ export type ErrorMessageProps = {
38
37
  * React component to render a block message.
39
38
  *
40
39
  * @param {MessageProps} props - Component props.
41
- * @return {React.ReactElement } Banner component.
40
+ * @return {React.ReactElement} Banner component.
42
41
  */
43
42
  export default function Message({ severity, icon, showSidebarIcon, onSidebarIconClick, children, }: MessageProps): React.ReactElement;
44
43
  /**
45
44
  * React component to render a guideline message.
46
45
  *
47
- * @return {React.ReactElement } - Message component.
46
+ * @return {React.ReactElement} - Message component.
48
47
  */
49
48
  export declare function GuidelineMessage(): React.ReactElement;
50
49
  /**
51
50
  * React component to render a fair usage limit message.
52
51
  *
53
- * @return {React.ReactElement } - Message component.
52
+ * @return {React.ReactElement} - Message component.
54
53
  */
55
54
  export declare function FairUsageLimitMessage(): React.ReactElement;
56
55
  /**
57
56
  * React component to render an upgrade message for free tier users
58
57
  *
59
58
  * @param {number} requestsRemaining - Number of requests remaining.
60
- * @return {React.ReactElement } - Message component.
59
+ * @return {React.ReactElement} - Message component.
61
60
  */
62
61
  export declare function UpgradeMessage({ requestsRemaining, severity, onUpgradeClick, upgradeUrl, }: UpgradeMessageProps): React.ReactElement;
63
62
  /**
64
63
  * React component to render an error message
65
64
  *
66
65
  * @param {number} requestsRemaining - Number of requests remaining.
67
- * @return {React.ReactElement } - Message component.
66
+ * @return {React.ReactElement} - Message component.
68
67
  */
69
68
  export declare function ErrorMessage({ error, code, onTryAgainClick, onUpgradeClick, upgradeUrl, }: ErrorMessageProps): React.ReactElement;
70
- export {};
@@ -17,12 +17,6 @@ export const MESSAGE_SEVERITY_WARNING = 'warning';
17
17
  export const MESSAGE_SEVERITY_ERROR = 'error';
18
18
  export const MESSAGE_SEVERITY_SUCCESS = 'success';
19
19
  export const MESSAGE_SEVERITY_INFO = 'info';
20
- const messageSeverityTypes = [
21
- MESSAGE_SEVERITY_WARNING,
22
- MESSAGE_SEVERITY_ERROR,
23
- MESSAGE_SEVERITY_SUCCESS,
24
- MESSAGE_SEVERITY_INFO,
25
- ];
26
20
  const messageIconsMap = {
27
21
  [MESSAGE_SEVERITY_INFO]: null,
28
22
  [MESSAGE_SEVERITY_WARNING]: null,
@@ -33,23 +27,31 @@ const messageIconsMap = {
33
27
  * React component to render a block message.
34
28
  *
35
29
  * @param {MessageProps} props - Component props.
36
- * @return {React.ReactElement } Banner component.
30
+ * @return {React.ReactElement} Banner component.
37
31
  */
38
32
  export default function Message({ severity = MESSAGE_SEVERITY_INFO, icon = null, showSidebarIcon = false, onSidebarIconClick = () => { }, children, }) {
39
33
  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 }) }))] }));
40
34
  }
35
+ /**
36
+ * React component to render a learn more link.
37
+ *
38
+ * @return {React.ReactElement} - Learn more link component.
39
+ */
40
+ function LearnMoreLink() {
41
+ return (_jsx(ExternalLink, { href: "https://jetpack.com/redirect/?source=ai-guidelines", children: __('Learn more', 'jetpack-ai-client') }));
42
+ }
41
43
  /**
42
44
  * React component to render a guideline message.
43
45
  *
44
- * @return {React.ReactElement } - Message component.
46
+ * @return {React.ReactElement} - Message component.
45
47
  */
46
48
  export function GuidelineMessage() {
47
- return (_jsxs(Message, { children: [_jsx("span", { children: __('AI-generated content could be inaccurate or biased.', 'jetpack-ai-client') }), _jsx(ExternalLink, { href: "https://automattic.com/ai-guidelines", children: __('Learn more', 'jetpack-ai-client') })] }));
49
+ return (_jsxs(Message, { children: [_jsx("span", { children: __('AI-generated content could be inaccurate or biased.', 'jetpack-ai-client') }), _jsx(LearnMoreLink, {})] }));
48
50
  }
49
51
  /**
50
52
  * React component to render a fair usage limit message.
51
53
  *
52
- * @return {React.ReactElement } - Message component.
54
+ * @return {React.ReactElement} - Message component.
53
55
  */
54
56
  export function FairUsageLimitMessage() {
55
57
  const message = __("You've reached this month's request limit, per our <link>fair usage policy</link>", 'jetpack-ai-client');
@@ -62,7 +64,7 @@ export function FairUsageLimitMessage() {
62
64
  * React component to render an upgrade message for free tier users
63
65
  *
64
66
  * @param {number} requestsRemaining - Number of requests remaining.
65
- * @return {React.ReactElement } - Message component.
67
+ * @return {React.ReactElement} - Message component.
66
68
  */
67
69
  export function UpgradeMessage({ requestsRemaining, severity, onUpgradeClick, upgradeUrl, }) {
68
70
  let messageSeverity = severity;
@@ -77,7 +79,7 @@ export function UpgradeMessage({ requestsRemaining, severity, onUpgradeClick, up
77
79
  * React component to render an error message
78
80
  *
79
81
  * @param {number} requestsRemaining - Number of requests remaining.
80
- * @return {React.ReactElement } - Message component.
82
+ * @return {React.ReactElement} - Message component.
81
83
  */
82
84
  export function ErrorMessage({ error, code, onTryAgainClick, onUpgradeClick, upgradeUrl, }) {
83
85
  const errorMessage = error || __('Something went wrong', 'jetpack-ai-client');
@@ -8,8 +8,7 @@ import React from 'react';
8
8
  import SuggestionsEventSource from '../suggestions-event-source/index.js';
9
9
  import type { AskQuestionOptionsArgProps } from '../ask-question/index.js';
10
10
  import type { RequestingErrorProps } from '../hooks/use-ai-suggestions/index.js';
11
- import type { PromptProp } from '../types.js';
12
- import type { RequestingStateProp } from '../types.js';
11
+ import type { PromptProp, RequestingStateProp } from '../types.js';
13
12
  export type AiDataContextProps = {
14
13
  suggestion: string;
15
14
  requestingError: RequestingErrorProps;
@@ -3,8 +3,7 @@
3
3
  */
4
4
  import type { AskQuestionOptionsArgProps } from '../../ask-question/index.js';
5
5
  import type SuggestionsEventSource from '../../suggestions-event-source/index.js';
6
- import type { PromptProp, SuggestionErrorCode } from '../../types.js';
7
- import type { RequestingStateProp } from '../../types.js';
6
+ import type { PromptProp, SuggestionErrorCode, RequestingStateProp } from '../../types.js';
8
7
  export type RequestingErrorProps = {
9
8
  code: SuggestionErrorCode;
10
9
  message: string;
@@ -5,7 +5,7 @@ type FairUsageNoticeProps = {
5
5
  * The fair usage notice component.
6
6
  * @param {FairUsageNoticeProps} props - Fair usage notice component props.
7
7
  * @param {FairUsageNoticeProps.variant} props.variant - The variant of the notice to render.
8
- * @return {ReactElement} the Notice component with the fair usage message.
8
+ * @return the Notice component with the fair usage message.
9
9
  */
10
10
  export declare const FairUsageNotice: ({ variant }: FairUsageNoticeProps) => import("react/jsx-runtime").JSX.Element;
11
11
  export {};
@@ -5,7 +5,7 @@ import useFairUsageNoticeMessage from '../hooks/use-fair-usage-notice-message.js
5
5
  * The fair usage notice component.
6
6
  * @param {FairUsageNoticeProps} props - Fair usage notice component props.
7
7
  * @param {FairUsageNoticeProps.variant} props.variant - The variant of the notice to render.
8
- * @return {ReactElement} the Notice component with the fair usage message.
8
+ * @return the Notice component with the fair usage message.
9
9
  */
10
10
  export const FairUsageNotice = ({ variant = 'error' }) => {
11
11
  const useFairUsageNoticeMessageElement = useFairUsageNoticeMessage();
@@ -6,20 +6,19 @@ import { useAnalytics } from '@automattic/jetpack-shared-extension-utils';
6
6
  import { Modal, Button } from '@wordpress/components';
7
7
  import { useDispatch, select } from '@wordpress/data';
8
8
  import { __ } from '@wordpress/i18n';
9
- import { external, Icon } from '@wordpress/icons';
10
9
  import clsx from 'clsx';
11
10
  import debugFactory from 'debug';
12
11
  import { useState, useEffect, useCallback, useRef } from 'react';
13
12
  /**
14
13
  * Internal dependencies
15
14
  */
15
+ import AiModalFooter from '../../components/ai-modal-footer/index.js';
16
16
  import { DEFAULT_LOGO_COST, EVENT_MODAL_OPEN, EVENT_FEEDBACK, EVENT_MODAL_CLOSE, EVENT_GENERATE, } from '../constants.js';
17
17
  import { useCheckout } from '../hooks/use-checkout.js';
18
18
  import useLogoGenerator from '../hooks/use-logo-generator.js';
19
19
  import useRequestErrors from '../hooks/use-request-errors.js';
20
20
  import { isLogoHistoryEmpty, clearDeletedMedia } from '../lib/logo-storage.js';
21
21
  import { STORE_NAME } from '../store/index.js';
22
- // import { FairUsageNotice } from './fair-usage-notice.js';
23
22
  import { FeatureFetchFailureScreen } from './feature-fetch-failure-screen.js';
24
23
  import { FirstLoadScreen } from './first-load-screen.js';
25
24
  import { HistoryCarousel } from './history-carousel.js';
@@ -213,7 +212,7 @@ export const GeneratorModal = ({ isOpen, onClose, onApplyLogo, onReload = null,
213
212
  body = (_jsx(UpgradeScreen, { onCancel: closeModal, upgradeURL: upgradeURL, reason: needsFeature ? 'feature' : 'requests' }));
214
213
  }
215
214
  else {
216
- body = (_jsxs(_Fragment, { children: [!logoAccepted && _jsx(Prompt, { initialPrompt: initialPrompt }), _jsx(LogoPresenter, { logo: selectedLogo, onApplyLogo: handleApplyLogo, logoAccepted: logoAccepted, siteId: String(siteId) }), logoAccepted ? (_jsxs("div", { className: "jetpack-ai-logo-generator__accept", children: [_jsx(VisitSiteBanner, {}), _jsx("div", { className: "jetpack-ai-logo-generator__accept-actions", children: _jsx(Button, { variant: "primary", onClick: closeModal, children: __('Close', 'jetpack-ai-client') }) })] })) : (_jsxs(_Fragment, { children: [_jsx(HistoryCarousel, {}), _jsx("div", { className: "jetpack-ai-logo-generator__footer", children: _jsxs(Button, { variant: "link", className: "jetpack-ai-logo-generator__feedback-button", href: "https://jetpack.com/redirect/?source=jetpack-ai-feedback", target: "_blank", onClick: handleFeedbackClick, children: [_jsx("span", { children: __('Provide feedback', 'jetpack-ai-client') }), _jsx(Icon, { icon: external, className: "icon" })] }) })] }))] }));
215
+ body = (_jsxs(_Fragment, { children: [!logoAccepted && _jsx(Prompt, { initialPrompt: initialPrompt }), _jsx(LogoPresenter, { logo: selectedLogo, onApplyLogo: handleApplyLogo, logoAccepted: logoAccepted, siteId: String(siteId) }), logoAccepted ? (_jsxs("div", { className: "jetpack-ai-logo-generator__accept", children: [_jsx(VisitSiteBanner, {}), _jsx("div", { className: "jetpack-ai-logo-generator__accept-actions", children: _jsx(Button, { variant: "primary", onClick: closeModal, children: __('Close', 'jetpack-ai-client') }) })] })) : (_jsxs(_Fragment, { children: [_jsx(HistoryCarousel, {}), _jsx("div", { className: "jetpack-ai-logo-generator__footer", children: _jsx(AiModalFooter, { onFeedbackClick: handleFeedbackClick }) })] }))] }));
217
216
  }
218
217
  return (_jsx(_Fragment, { children: isOpen && (_jsx(Modal, { className: "jetpack-ai-logo-generator-modal", onRequestClose: closeModal, shouldCloseOnClickOutside: false, shouldCloseOnEsc: false, title: __('Jetpack AI Logo Generator', 'jetpack-ai-client'), children: _jsx("div", { className: clsx('jetpack-ai-logo-generator-modal__body', {
219
218
  'notice-modal': needsFeature || needsMoreRequests || featureFetchError || firstLogoPromptFetchError,
@@ -87,7 +87,6 @@ const LogoUpdated = ({ logo }) => {
87
87
  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') })] })] }));
88
88
  };
89
89
  export const LogoPresenter = ({ logo = null, loading = false, onApplyLogo, logoAccepted = false, siteId, }) => {
90
- // eslint-disable-next-line @wordpress/no-unused-vars-before-return -- @todo Start extending jetpack-js-tools/eslintrc/react in eslintrc, then we can remove this disable comment.
91
90
  const { isRequestingImage } = useLogoGenerator();
92
91
  const { saveToLibraryError, logoUpdateError } = useRequestErrors();
93
92
  let logoContent;
@@ -60,9 +60,9 @@ export const AiModalPromptInput = ({ prompt = '', setPrompt = () => { }, disable
60
60
  inputRef.current.innerHTML = '';
61
61
  }
62
62
  };
63
- return (_jsxs("div", { className: "jetpack-ai-logo-generator__prompt-query", children: [_jsx("div", { role: "textbox", tabIndex: 0, ref: inputRef, contentEditable: !disabled,
63
+ return (_jsxs("div", { className: "jetpack-ai-image-generator__prompt-query", children: [_jsx("div", { role: "textbox", tabIndex: 0, ref: inputRef, contentEditable: !disabled,
64
64
  // The content editable div is expected to be updated by the enhance prompt, so warnings are suppressed
65
- suppressContentEditableWarning: true, className: "prompt-query__input", onInput: onPromptInput, onPaste: onPromptPaste, onKeyDown: onKeyDown, onKeyUp: onKeyUp, "data-placeholder": placeholder }), _jsx(Button, { variant: "primary", className: "jetpack-ai-logo-generator__prompt-submit", onClick: generateHandler, disabled: actionDisabled, children: buttonLabel || __('Generate', 'jetpack-ai-client') })] }));
65
+ suppressContentEditableWarning: true, className: "prompt-query__input", onInput: onPromptInput, onPaste: onPromptPaste, onKeyDown: onKeyDown, onKeyUp: onKeyUp, "data-placeholder": placeholder }), _jsx(Button, { variant: "primary", className: "jetpack-ai-image-generator__prompt-submit", onClick: generateHandler, disabled: actionDisabled, children: buttonLabel || __('Generate', 'jetpack-ai-client') })] }));
66
66
  };
67
67
  export const Prompt = ({ initialPrompt = '' }) => {
68
68
  const { tracks } = useAnalytics();
@@ -1,3 +1,3 @@
1
- /// <reference types="react" resolution-mode="require"/>
2
- declare const useFairUsageNoticeMessage: () => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
1
+ import { type Element } from '@wordpress/element';
2
+ declare const useFairUsageNoticeMessage: () => Element;
3
3
  export default useFairUsageNoticeMessage;
@@ -119,7 +119,7 @@ export async function clearDeletedMedia(siteId) {
119
119
  .filter(({ exists }) => !exists)
120
120
  .forEach(({ mediaId }) => removeLogo({ siteId, mediaId }));
121
121
  }
122
- catch (error) {
122
+ catch {
123
123
  // Assume that the media exists if there was a network error and do nothing to avoid data loss.
124
124
  }
125
125
  }
@@ -1,4 +1,4 @@
1
- import type { AiFeatureStateProps, LogoGeneratorStateProp, RequestError } from './types.js';
1
+ import type { AiFeatureStateProps, RequestError } from './types.js';
2
2
  import type { SiteDetails } from '../types.js';
3
3
  /**
4
4
  * Reducer for the Logo Generator store.
@@ -27,7 +27,7 @@ import type { SiteDetails } from '../types.js';
27
27
  * @param {boolean} action.isLoadingHistory - Whether the history is being loaded
28
28
  * @return {LogoGeneratorStateProp} The new state
29
29
  */
30
- export default function reducer(state: LogoGeneratorStateProp, action: {
30
+ export default function reducer(state: import("./types.js").LogoGeneratorStateProp, action: {
31
31
  type: string;
32
32
  feature?: AiFeatureStateProps;
33
33
  count?: number;
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.0",
4
+ "version": "0.25.2",
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": {
@@ -44,21 +44,21 @@
44
44
  "main": "./build/index.js",
45
45
  "types": "./build/index.d.ts",
46
46
  "dependencies": {
47
- "@automattic/jetpack-base-styles": "^0.6.37",
48
- "@automattic/jetpack-connection": "^0.35.19",
49
- "@automattic/jetpack-shared-extension-utils": "^0.15.18",
47
+ "@automattic/jetpack-base-styles": "^0.6.39",
48
+ "@automattic/jetpack-connection": "^0.36.2",
49
+ "@automattic/jetpack-shared-extension-utils": "^0.16.2",
50
50
  "@microsoft/fetch-event-source": "2.0.1",
51
51
  "@types/react": "18.3.12",
52
52
  "@types/wordpress__block-editor": "11.5.15",
53
- "@wordpress/api-fetch": "7.12.0",
54
- "@wordpress/blob": "4.12.0",
55
- "@wordpress/block-editor": "14.7.0",
56
- "@wordpress/components": "28.12.0",
57
- "@wordpress/compose": "7.12.0",
58
- "@wordpress/data": "10.12.0",
59
- "@wordpress/element": "6.12.0",
60
- "@wordpress/i18n": "5.12.0",
61
- "@wordpress/icons": "10.12.0",
53
+ "@wordpress/api-fetch": "7.14.0",
54
+ "@wordpress/blob": "4.14.0",
55
+ "@wordpress/block-editor": "14.9.0",
56
+ "@wordpress/components": "29.0.0",
57
+ "@wordpress/compose": "7.14.0",
58
+ "@wordpress/data": "10.14.0",
59
+ "@wordpress/element": "6.14.0",
60
+ "@wordpress/i18n": "5.14.0",
61
+ "@wordpress/icons": "10.14.0",
62
62
  "clsx": "2.1.1",
63
63
  "debug": "4.3.4",
64
64
  "markdown-it": "14.0.0",
@@ -8,7 +8,7 @@ import apiFetchMod from '@wordpress/api-fetch';
8
8
  // See https://arethetypeswrong.github.io/?p=@wordpress/api-fetch@6.47.0
9
9
  // This is a helper to simplify the usage of the api-fetch module on the ai-client package.
10
10
  const apiFetch = 'default' in apiFetchMod ? apiFetchMod.default : apiFetchMod;
11
- // eslint-disable-next-line @typescript-eslint/ban-types
11
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
12
12
  type ApiFetchType = typeof apiFetch extends Function ? typeof apiFetch : typeof apiFetchMod;
13
13
 
14
14
  export default apiFetch as ApiFetchType;
@@ -35,7 +35,7 @@ export type AskQuestionOptionsArgProps = {
35
35
  functions?: Array< {
36
36
  name?: string;
37
37
  arguments?: string;
38
- // eslint-disable-next-line @typescript-eslint/ban-types
38
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
39
39
  implementation?: Function;
40
40
  } >;
41
41
  };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { Button } from '@wordpress/components';
5
+ import { useCallback } from '@wordpress/element';
6
+ import { __ } from '@wordpress/i18n';
7
+ import { Icon, info } from '@wordpress/icons';
8
+ /**
9
+ * Internal dependencies
10
+ */
11
+ import './style.scss';
12
+ /**
13
+ * Types
14
+ */
15
+ import type React from 'react';
16
+
17
+ type AiModalFooterProps = {
18
+ onGuidelinesClick?: () => void;
19
+ onFeedbackClick?: () => void;
20
+ };
21
+
22
+ /**
23
+ * AiModalFooter component.
24
+ *
25
+ * @param {AiModalFooterProps} props - component props.
26
+ * @return {React.ReactElement} - rendered component.
27
+ */
28
+ export default function AiModalFooter( {
29
+ onGuidelinesClick,
30
+ onFeedbackClick,
31
+ }: AiModalFooterProps ): React.ReactElement {
32
+ const handleGuidelinesClick = useCallback( () => {
33
+ onGuidelinesClick?.();
34
+ }, [ onGuidelinesClick ] );
35
+
36
+ const handleFeedbackClick = useCallback( () => {
37
+ onFeedbackClick?.();
38
+ }, [ onFeedbackClick ] );
39
+
40
+ return (
41
+ <>
42
+ <div className="ai-image-modal__footer-disclaimer">
43
+ <Icon icon={ info } />
44
+ <span>
45
+ { __(
46
+ 'Generated images could be inaccurate, biased or include text.',
47
+ 'jetpack-ai-client'
48
+ ) }
49
+ </span>
50
+ <Button
51
+ variant="link"
52
+ className="ai-image-modal__guidelines-button"
53
+ href="https://jetpack.com/redirect/?source=ai-guidelines"
54
+ target="_blank"
55
+ onClick={ handleGuidelinesClick }
56
+ >
57
+ <span>{ __( 'Guidelines', 'jetpack-ai-client' ) } ↗</span>
58
+ </Button>
59
+ </div>
60
+ <Button
61
+ variant="link"
62
+ className="ai-image-modal__feedback-button"
63
+ href="https://jetpack.com/redirect/?source=jetpack-ai-feedback"
64
+ target="_blank"
65
+ onClick={ handleFeedbackClick }
66
+ >
67
+ <span>{ __( 'Give feedback', 'jetpack-ai-client' ) } ↗</span>
68
+ </Button>
69
+ </>
70
+ );
71
+ }
@@ -0,0 +1,15 @@
1
+ .ai-image-modal__footer-disclaimer {
2
+ display: flex;
3
+ align-items: center;
4
+ gap: 4px;
5
+ color: var( --jp-gray-50 );
6
+ fill: var( --jp-gray-50 );
7
+
8
+ .components-button.is-link {
9
+ color: var( --jp-gray-50 );
10
+ }
11
+ }
12
+
13
+ .ai-image-modal__feedback-button.components-button.is-link {
14
+ text-decoration: none;
15
+ }
@@ -1,6 +1,7 @@
1
1
  export { AIControl, BlockAIControl, ExtensionAIControl } from './ai-control/index.js';
2
2
  export { default as AiStatusIndicator } from './ai-status-indicator/index.js';
3
3
  export { default as AudioDurationDisplay } from './audio-duration-display/index.js';
4
+ export { default as AiModalFooter } from './ai-modal-footer/index.js';
4
5
  export {
5
6
  GuidelineMessage,
6
7
  UpgradeMessage,
@@ -23,14 +23,12 @@ export const MESSAGE_SEVERITY_ERROR = 'error';
23
23
  export const MESSAGE_SEVERITY_SUCCESS = 'success';
24
24
  export const MESSAGE_SEVERITY_INFO = 'info';
25
25
 
26
- const messageSeverityTypes = [
27
- MESSAGE_SEVERITY_WARNING,
28
- MESSAGE_SEVERITY_ERROR,
29
- MESSAGE_SEVERITY_SUCCESS,
30
- MESSAGE_SEVERITY_INFO,
31
- ] as const;
32
-
33
- export type MessageSeverityProp = ( typeof messageSeverityTypes )[ number ] | null;
26
+ export type MessageSeverityProp =
27
+ | typeof MESSAGE_SEVERITY_WARNING
28
+ | typeof MESSAGE_SEVERITY_ERROR
29
+ | typeof MESSAGE_SEVERITY_SUCCESS
30
+ | typeof MESSAGE_SEVERITY_INFO
31
+ | null;
34
32
 
35
33
  export type MessageProps = {
36
34
  icon?: React.ReactNode;
@@ -68,7 +66,7 @@ const messageIconsMap = {
68
66
  * React component to render a block message.
69
67
  *
70
68
  * @param {MessageProps} props - Component props.
71
- * @return {React.ReactElement } Banner component.
69
+ * @return {React.ReactElement} Banner component.
72
70
  */
73
71
  export default function Message( {
74
72
  severity = MESSAGE_SEVERITY_INFO,
@@ -97,10 +95,23 @@ export default function Message( {
97
95
  );
98
96
  }
99
97
 
98
+ /**
99
+ * React component to render a learn more link.
100
+ *
101
+ * @return {React.ReactElement} - Learn more link component.
102
+ */
103
+ function LearnMoreLink(): React.ReactElement {
104
+ return (
105
+ <ExternalLink href="https://jetpack.com/redirect/?source=ai-guidelines">
106
+ { __( 'Learn more', 'jetpack-ai-client' ) }
107
+ </ExternalLink>
108
+ );
109
+ }
110
+
100
111
  /**
101
112
  * React component to render a guideline message.
102
113
  *
103
- * @return {React.ReactElement } - Message component.
114
+ * @return {React.ReactElement} - Message component.
104
115
  */
105
116
  export function GuidelineMessage(): React.ReactElement {
106
117
  return (
@@ -108,9 +119,7 @@ export function GuidelineMessage(): React.ReactElement {
108
119
  <span>
109
120
  { __( 'AI-generated content could be inaccurate or biased.', 'jetpack-ai-client' ) }
110
121
  </span>
111
- <ExternalLink href="https://automattic.com/ai-guidelines">
112
- { __( 'Learn more', 'jetpack-ai-client' ) }
113
- </ExternalLink>
122
+ <LearnMoreLink />
114
123
  </Message>
115
124
  );
116
125
  }
@@ -118,7 +127,7 @@ export function GuidelineMessage(): React.ReactElement {
118
127
  /**
119
128
  * React component to render a fair usage limit message.
120
129
  *
121
- * @return {React.ReactElement } - Message component.
130
+ * @return {React.ReactElement} - Message component.
122
131
  */
123
132
  export function FairUsageLimitMessage(): React.ReactElement {
124
133
  const message = __(
@@ -138,7 +147,7 @@ export function FairUsageLimitMessage(): React.ReactElement {
138
147
  * React component to render an upgrade message for free tier users
139
148
  *
140
149
  * @param {number} requestsRemaining - Number of requests remaining.
141
- * @return {React.ReactElement } - Message component.
150
+ * @return {React.ReactElement} - Message component.
142
151
  */
143
152
  export function UpgradeMessage( {
144
153
  requestsRemaining,
@@ -177,7 +186,7 @@ export function UpgradeMessage( {
177
186
  * React component to render an error message
178
187
  *
179
188
  * @param {number} requestsRemaining - Number of requests remaining.
180
- * @return {React.ReactElement } - Message component.
189
+ * @return {React.ReactElement} - Message component.
181
190
  */
182
191
  export function ErrorMessage( {
183
192
  error,
@@ -8,8 +8,7 @@ import React, { createContext } from 'react';
8
8
  import SuggestionsEventSource from '../suggestions-event-source/index.js';
9
9
  import type { AskQuestionOptionsArgProps } from '../ask-question/index.js';
10
10
  import type { RequestingErrorProps } from '../hooks/use-ai-suggestions/index.js';
11
- import type { PromptProp } from '../types.js';
12
- import type { RequestingStateProp } from '../types.js';
11
+ import type { PromptProp, RequestingStateProp } from '../types.js';
13
12
 
14
13
  export type AiDataContextProps = {
15
14
  /*
@@ -21,8 +21,7 @@ import {
21
21
  */
22
22
  import type { AskQuestionOptionsArgProps } from '../../ask-question/index.js';
23
23
  import type SuggestionsEventSource from '../../suggestions-event-source/index.js';
24
- import type { PromptProp, SuggestionErrorCode } from '../../types.js';
25
- import type { RequestingStateProp } from '../../types.js';
24
+ import type { PromptProp, SuggestionErrorCode, RequestingStateProp } from '../../types.js';
26
25
 
27
26
  export type RequestingErrorProps = {
28
27
  /*
@@ -1,9 +1,5 @@
1
1
  import { Notice } from '@wordpress/components';
2
2
  import useFairUsageNoticeMessage from '../hooks/use-fair-usage-notice-message.js';
3
- /**
4
- * Types
5
- */
6
- import type { ReactElement } from 'react';
7
3
 
8
4
  type FairUsageNoticeProps = {
9
5
  variant?: 'error' | 'muted';
@@ -13,7 +9,7 @@ type FairUsageNoticeProps = {
13
9
  * The fair usage notice component.
14
10
  * @param {FairUsageNoticeProps} props - Fair usage notice component props.
15
11
  * @param {FairUsageNoticeProps.variant} props.variant - The variant of the notice to render.
16
- * @return {ReactElement} the Notice component with the fair usage message.
12
+ * @return the Notice component with the fair usage message.
17
13
  */
18
14
  export const FairUsageNotice = ( { variant = 'error' }: FairUsageNoticeProps ) => {
19
15
  const useFairUsageNoticeMessageElement = useFairUsageNoticeMessage();
@@ -18,19 +18,6 @@
18
18
  height: 100%;
19
19
  }
20
20
  }
21
-
22
- .components-button {
23
- &:focus:not(:disabled):not(.is-primary) {
24
- box-shadow: 0 0 0 2px var(--color-link, #3858e9);
25
- }
26
-
27
- &.is-link {
28
- text-decoration: none;
29
- &:not(:disabled) {
30
- color: var(--color-link, #3858e9);
31
- }
32
- }
33
- }
34
21
  }
35
22
 
36
23
  .jetpack-ai-logo-generator-modal__body {
@@ -52,17 +39,7 @@
52
39
 
53
40
  .jetpack-ai-logo-generator__footer {
54
41
  display: flex;
55
-
56
- .jetpack-ai-logo-generator__feedback-button {
57
- display: flex;
58
- gap: 4px;
59
- align-items: center;
60
- margin-top: 8px;
61
-
62
- .icon {
63
- color: var(--studio-gray-20);
64
- }
65
- }
42
+ justify-content: space-between;
66
43
  }
67
44
 
68
45
  .jetpack-ai-logo-generator-modal__notice-message-wrapper {
@@ -5,13 +5,13 @@ import { useAnalytics } from '@automattic/jetpack-shared-extension-utils';
5
5
  import { Modal, Button } from '@wordpress/components';
6
6
  import { useDispatch, select } from '@wordpress/data';
7
7
  import { __ } from '@wordpress/i18n';
8
- import { external, Icon } from '@wordpress/icons';
9
8
  import clsx from 'clsx';
10
9
  import debugFactory from 'debug';
11
10
  import { useState, useEffect, useCallback, useRef } from 'react';
12
11
  /**
13
12
  * Internal dependencies
14
13
  */
14
+ import AiModalFooter from '../../components/ai-modal-footer/index.js';
15
15
  import {
16
16
  DEFAULT_LOGO_COST,
17
17
  EVENT_MODAL_OPEN,
@@ -24,7 +24,6 @@ import useLogoGenerator from '../hooks/use-logo-generator.js';
24
24
  import useRequestErrors from '../hooks/use-request-errors.js';
25
25
  import { isLogoHistoryEmpty, clearDeletedMedia } from '../lib/logo-storage.js';
26
26
  import { STORE_NAME } from '../store/index.js';
27
- // import { FairUsageNotice } from './fair-usage-notice.js';
28
27
  import { FeatureFetchFailureScreen } from './feature-fetch-failure-screen.js';
29
28
  import { FirstLoadScreen } from './first-load-screen.js';
30
29
  import { HistoryCarousel } from './history-carousel.js';
@@ -303,16 +302,7 @@ export const GeneratorModal: React.FC< GeneratorModalProps > = ( {
303
302
  <>
304
303
  <HistoryCarousel />
305
304
  <div className="jetpack-ai-logo-generator__footer">
306
- <Button
307
- variant="link"
308
- className="jetpack-ai-logo-generator__feedback-button"
309
- href="https://jetpack.com/redirect/?source=jetpack-ai-feedback"
310
- target="_blank"
311
- onClick={ handleFeedbackClick }
312
- >
313
- <span>{ __( 'Provide feedback', 'jetpack-ai-client' ) }</span>
314
- <Icon icon={ external } className="icon" />
315
- </Button>
305
+ <AiModalFooter onFeedbackClick={ handleFeedbackClick } />
316
306
  </div>
317
307
  </>
318
308
  ) }
@@ -4,6 +4,20 @@
4
4
  display: flex;
5
5
  flex-direction: column;
6
6
  gap: 8px;
7
+
8
+ .components-button {
9
+ &:focus:not(:disabled):not(.is-primary) {
10
+ box-shadow: 0 0 0 2px var(--color-link, #3858e9);
11
+ }
12
+
13
+ &.is-link {
14
+ text-decoration: none;
15
+
16
+ &:not(:disabled) {
17
+ color: var(--color-link, #3858e9);
18
+ }
19
+ }
20
+ }
7
21
  }
8
22
 
9
23
  .jetpack-ai-logo-generator-modal-presenter {
@@ -200,7 +200,6 @@ export const LogoPresenter: React.FC< LogoPresenterProps > = ( {
200
200
  logoAccepted = false,
201
201
  siteId,
202
202
  } ) => {
203
- // eslint-disable-next-line @wordpress/no-unused-vars-before-return -- @todo Start extending jetpack-js-tools/eslintrc/react in eslintrc, then we can remove this disable comment.
204
203
  const { isRequestingImage } = useLogoGenerator();
205
204
  const { saveToLibraryError, logoUpdateError } = useRequestErrors();
206
205
 
@@ -5,6 +5,20 @@
5
5
  flex-direction: column;
6
6
  gap: 8px;
7
7
  font-size: var(--font-body-small);
8
+
9
+ .components-button {
10
+ &:focus:not(:disabled):not(.is-primary) {
11
+ box-shadow: 0 0 0 2px var(--color-link, #3858e9);
12
+ }
13
+
14
+ &.is-link {
15
+ text-decoration: none;
16
+
17
+ &:not(:disabled) {
18
+ color: var(--color-link, #3858e9);
19
+ }
20
+ }
21
+ }
8
22
  }
9
23
 
10
24
  .jetpack-ai-logo-generator__prompt-header {
@@ -30,7 +44,7 @@
30
44
  }
31
45
  }
32
46
 
33
- .jetpack-ai-logo-generator__prompt-query {
47
+ .jetpack-ai-image-generator__prompt-query {
34
48
  display: flex;
35
49
  padding: 8px 8px 8px var(--grid-unit-15, 16px);
36
50
  justify-content: space-between;
@@ -6,8 +6,7 @@ import { Button, Tooltip, SelectControl } from '@wordpress/components';
6
6
  import { __, sprintf } from '@wordpress/i18n';
7
7
  import { Icon, info } from '@wordpress/icons';
8
8
  import debugFactory from 'debug';
9
- import { useCallback, useEffect, useState, useRef } from 'react';
10
- import { Dispatch, SetStateAction } from 'react';
9
+ import { useCallback, useEffect, useState, useRef, Dispatch, SetStateAction } from 'react';
11
10
  /**
12
11
  * Internal dependencies
13
12
  */
@@ -104,7 +103,7 @@ export const AiModalPromptInput = ( {
104
103
  };
105
104
 
106
105
  return (
107
- <div className="jetpack-ai-logo-generator__prompt-query">
106
+ <div className="jetpack-ai-image-generator__prompt-query">
108
107
  <div
109
108
  role="textbox"
110
109
  tabIndex={ 0 }
@@ -121,7 +120,7 @@ export const AiModalPromptInput = ( {
121
120
  ></div>
122
121
  <Button
123
122
  variant="primary"
124
- className="jetpack-ai-logo-generator__prompt-submit"
123
+ className="jetpack-ai-image-generator__prompt-submit"
125
124
  onClick={ generateHandler }
126
125
  disabled={ actionDisabled }
127
126
  >
@@ -1,5 +1,5 @@
1
1
  import { useSelect } from '@wordpress/data';
2
- import { createInterpolateElement } from '@wordpress/element';
2
+ import { createInterpolateElement, type Element } from '@wordpress/element';
3
3
  import { __, sprintf } from '@wordpress/i18n';
4
4
  import getRedirectUrl from '../../../../components/tools/jp-redirect/index.js';
5
5
  /**
@@ -11,7 +11,7 @@ import { STORE_NAME } from '../store/index.js';
11
11
  */
12
12
  import type { Selectors } from '../store/types.js';
13
13
 
14
- const useFairUsageNoticeMessage = () => {
14
+ const useFairUsageNoticeMessage = (): Element => {
15
15
  const { usagePeriod } = useSelect( select => {
16
16
  const selectors: Selectors = select( STORE_NAME );
17
17
  return {
@@ -162,7 +162,7 @@ export async function clearDeletedMedia( siteId: string ) {
162
162
  responses
163
163
  .filter( ( { exists } ) => ! exists )
164
164
  .forEach( ( { mediaId } ) => removeLogo( { siteId, mediaId } ) );
165
- } catch ( error ) {
165
+ } catch {
166
166
  // Assume that the media exists if there was a network error and do nothing to avoid data loss.
167
167
  }
168
168
  }
@@ -30,12 +30,7 @@ import {
30
30
  ACTION_SET_IS_LOADING_HISTORY,
31
31
  } from './constants.js';
32
32
  import INITIAL_STATE from './initial-state.js';
33
- import type {
34
- AiFeatureStateProps,
35
- LogoGeneratorStateProp,
36
- RequestError,
37
- TierLimitProp,
38
- } from './types.js';
33
+ import type { AiFeatureStateProps, RequestError, TierLimitProp } from './types.js';
39
34
  import type { SiteDetails } from '../types.js';
40
35
 
41
36
  /**