@automattic/jetpack-ai-client 0.31.2 → 0.32.1

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 CHANGED
@@ -5,6 +5,21 @@ 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.32.1] - 2025-06-24
9
+ ### Added
10
+ - Improve error handling on Chrome AI events. [#44048]
11
+
12
+ ## [0.32.0] - 2025-06-23
13
+ ### Changed
14
+ - AI Assistant: Add A/B test for Chrome AI API features. [#43690]
15
+ - Scripts: Change imports for hosting checks. [#43972]
16
+ - Update package dependencies. [#44020]
17
+
18
+ ### Fixed
19
+ - Fix Chrome experiment assignment group. [#44018]
20
+ - Fix async function call on Chrome experiment availability. [#44026]
21
+ - Invert feature flag check. [#44011]
22
+
8
23
  ## [0.31.2] - 2025-06-16
9
24
  ### Changed
10
25
  - Update dependencies. [#43878]
@@ -635,6 +650,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
635
650
  - AI Client: stop using smart document visibility handling on the fetchEventSource library, so it does not restart the completion when changing tabs. [#32004]
636
651
  - Updated package dependencies. [#31468] [#31659] [#31785]
637
652
 
653
+ [0.32.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.32.0...v0.32.1
654
+ [0.32.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.31.2...v0.32.0
638
655
  [0.31.2]: https://github.com/Automattic/jetpack-ai-client/compare/v0.31.1...v0.31.2
639
656
  [0.31.1]: https://github.com/Automattic/jetpack-ai-client/compare/v0.31.0...v0.31.1
640
657
  [0.31.0]: https://github.com/Automattic/jetpack-ai-client/compare/v0.30.0...v0.31.0
@@ -1,6 +1,8 @@
1
+ import debugFactory from 'debug';
1
2
  import { PROMPT_TYPE_CHANGE_LANGUAGE, PROMPT_TYPE_SUMMARIZE } from "../constants.js";
2
3
  import { isChromeAIAvailable } from "./get-availability.js";
3
4
  import ChromeAISuggestionsEventSource from "./suggestions.js";
5
+ const debug = debugFactory('ai-client:chrome-ai-factory');
4
6
  /**
5
7
  * This will return an instance of ChromeAISuggestionsEventSource or false.
6
8
  *
@@ -8,7 +10,8 @@ import ChromeAISuggestionsEventSource from "./suggestions.js";
8
10
  * @return ChromeAISuggestionsEventSource | bool
9
11
  */
10
12
  export default async function ChromeAIFactory(promptArg) {
11
- if (!isChromeAIAvailable()) {
13
+ if (!(await isChromeAIAvailable())) {
14
+ debug('Chrome AI is not available');
12
15
  return false;
13
16
  }
14
17
  const context = {
@@ -18,6 +21,7 @@ export default async function ChromeAIFactory(promptArg) {
18
21
  let promptType = '';
19
22
  let tone = null;
20
23
  let wordCount = null;
24
+ debug('promptArg', promptArg);
21
25
  if (Array.isArray(promptArg)) {
22
26
  for (let i = 0; i < promptArg.length; i++) {
23
27
  const prompt = promptArg[i];
@@ -45,9 +49,11 @@ export default async function ChromeAIFactory(promptArg) {
45
49
  }
46
50
  }
47
51
  }
52
+ debug('promptType', promptType);
48
53
  // Early return if the prompt type is not supported.
49
54
  if (!promptType.startsWith('ai-assistant-change-language') &&
50
55
  !promptType.startsWith('ai-content-lens')) {
56
+ debug('promptType is not supported');
51
57
  return false;
52
58
  }
53
59
  // If the languageDetector is not available, we can't use the translation or summary features—it's safer to fall back
@@ -55,14 +61,17 @@ export default async function ChromeAIFactory(promptArg) {
55
61
  if (!('LanguageDetector' in self) ||
56
62
  !self.LanguageDetector.create ||
57
63
  !self.LanguageDetector.availability) {
64
+ debug('LanguageDetector is not available');
58
65
  return false;
59
66
  }
60
67
  const languageDetectorAvailability = await self.LanguageDetector.availability();
61
68
  if (languageDetectorAvailability === 'unavailable') {
69
+ debug('LanguageDetector is unavailable');
62
70
  return false;
63
71
  }
64
72
  const detector = await self.LanguageDetector.create();
65
73
  if (languageDetectorAvailability !== 'available') {
74
+ debug('awaiting detector ready');
66
75
  await detector.ready;
67
76
  }
68
77
  if (promptType.startsWith('ai-assistant-change-language')) {
@@ -70,6 +79,7 @@ export default async function ChromeAIFactory(promptArg) {
70
79
  if (!('Translator' in self) ||
71
80
  !self.Translator.create ||
72
81
  !self.Translator.availability) {
82
+ debug('Translator is not available');
73
83
  return false;
74
84
  }
75
85
  const languageOpts = {
@@ -87,8 +97,11 @@ export default async function ChromeAIFactory(promptArg) {
87
97
  break;
88
98
  }
89
99
  }
100
+ debug('languageOpts', languageOpts);
90
101
  const translationAvailability = await self.Translator.availability(languageOpts);
102
+ debug('translationAvailability', translationAvailability);
91
103
  if (translationAvailability === 'unavailable') {
104
+ debug('Translator is unavailable');
92
105
  return false;
93
106
  }
94
107
  const chromeAI = new ChromeAISuggestionsEventSource({
@@ -100,11 +113,14 @@ export default async function ChromeAIFactory(promptArg) {
100
113
  }
101
114
  if (promptType.startsWith('ai-content-lens')) {
102
115
  if (!('Summarizer' in self)) {
116
+ debug('Summarizer is not available');
103
117
  return false;
104
118
  }
105
119
  if (context.language && context.language !== 'en (English)') {
120
+ debug('Summary is not English');
106
121
  return false;
107
122
  }
123
+ debug('awaiting detector detect');
108
124
  const confidences = await detector.detect(context.content);
109
125
  // if it doesn't look like the content is in English, we can't use the summary feature
110
126
  for (const confidence of confidences) {
@@ -113,6 +129,7 @@ export default async function ChromeAIFactory(promptArg) {
113
129
  // required for the translator to work at all, which is also
114
130
  // why en is the default language.
115
131
  if (confidence.confidence > 0.75 && confidence.detectedLanguage !== 'en') {
132
+ debug('Confidence for non-English content');
116
133
  return false;
117
134
  }
118
135
  }
@@ -120,11 +137,14 @@ export default async function ChromeAIFactory(promptArg) {
120
137
  tone: tone,
121
138
  wordCount: wordCount,
122
139
  };
123
- return new ChromeAISuggestionsEventSource({
140
+ debug('summaryOpts', summaryOpts);
141
+ const chromeAiEventSourceOpts = {
124
142
  content: context.content,
125
143
  promptType: PROMPT_TYPE_SUMMARIZE,
126
144
  options: summaryOpts,
127
- });
145
+ };
146
+ debug('chromeAiEventSourceOpts', chromeAiEventSourceOpts);
147
+ return new ChromeAISuggestionsEventSource(chromeAiEventSourceOpts);
128
148
  }
129
149
  return false;
130
150
  }
@@ -3,5 +3,5 @@
3
3
  *
4
4
  * @return {boolean} Whether Chrome AI can be enabled.
5
5
  */
6
- export declare function isChromeAIAvailable(): boolean;
6
+ export declare function isChromeAIAvailable(): Promise<boolean>;
7
7
  export default isChromeAIAvailable;
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { getJetpackExtensionAvailability } from '@automattic/jetpack-shared-extension-utils';
4
+ import { initializeExPlat, loadExperimentAssignmentWithAuth } from '@automattic/jetpack-explat';
5
5
  import { select } from '@wordpress/data';
6
+ import debugFactory from 'debug';
7
+ const debug = debugFactory('ai-client:chrome-ai-availability');
6
8
  /**
7
9
  * Get the AI Assistant feature.
8
10
  *
@@ -17,9 +19,17 @@ function getAiAssistantFeature() {
17
19
  *
18
20
  * @return {boolean} Whether Chrome AI can be enabled.
19
21
  */
20
- export function isChromeAIAvailable() {
22
+ export async function isChromeAIAvailable() {
21
23
  const { featuresControl } = getAiAssistantFeature();
22
- return (featuresControl?.['chrome-ai']?.enabled !== false &&
23
- getJetpackExtensionAvailability('ai-use-chrome-ai-sometimes').available !== false);
24
+ // Extra check if we want to control this via the feature flag for now
25
+ if (featuresControl?.['chrome-ai']?.enabled !== true) {
26
+ debug('feature is disabled for this site/user');
27
+ return false;
28
+ }
29
+ initializeExPlat();
30
+ debug('initialized explat');
31
+ const { variationName } = await loadExperimentAssignmentWithAuth('calypso_jetpack_ai_gemini_api_202503_v1');
32
+ debug('variationName', variationName);
33
+ return variationName === 'treatment';
24
34
  }
25
35
  export default isChromeAIAvailable;
@@ -1,7 +1,9 @@
1
+ import debugFactory from 'debug';
1
2
  import { PROMPT_TYPE_CHANGE_LANGUAGE, PROMPT_TYPE_SUMMARIZE } from "../constants.js";
2
3
  import { getErrorData } from "../hooks/use-ai-suggestions/index.js";
3
4
  import { renderHTMLFromMarkdown, renderMarkdownFromHTML } from "../libs/markdown/index.js";
4
5
  import { ERROR_RESPONSE, ERROR_NETWORK } from "../types.js";
6
+ const debug = debugFactory('ai-client:chrome-ai-suggestions');
5
7
  export default class ChromeAISuggestionsEventSource extends EventTarget {
6
8
  fullMessage;
7
9
  fullFunctionCall;
@@ -20,6 +22,7 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
20
22
  this.initSource(data);
21
23
  }
22
24
  initSource({ content, promptType, options = {}, }) {
25
+ debug('initSource', content, promptType, options);
23
26
  if (promptType === PROMPT_TYPE_CHANGE_LANGUAGE) {
24
27
  this.translate(content, options.targetLanguage, options.sourceLanguage);
25
28
  }
@@ -32,6 +35,7 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
32
35
  checkForUnclearPrompt() { }
33
36
  processEvent(e) {
34
37
  let data;
38
+ debug('processEvent', e);
35
39
  try {
36
40
  data = JSON.parse(e.data);
37
41
  }
@@ -47,6 +51,7 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
47
51
  }
48
52
  }
49
53
  processErrorEvent(e) {
54
+ debug('processErrorEvent', e);
50
55
  // Dispatch a generic network error event
51
56
  this.dispatchEvent(new CustomEvent(ERROR_NETWORK, { detail: e }));
52
57
  this.dispatchEvent(new CustomEvent(ERROR_RESPONSE, {
@@ -58,11 +63,25 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
58
63
  if (!('Translator' in self)) {
59
64
  return;
60
65
  }
66
+ const translatorAvailability = await self.Translator.availability({
67
+ sourceLanguage: source,
68
+ targetLanguage: target,
69
+ });
70
+ if (translatorAvailability === 'unavailable') {
71
+ debug('awaiting translator ready');
72
+ this.processErrorEvent({
73
+ message: 'Translator is unavailable',
74
+ });
75
+ return;
76
+ }
61
77
  const translator = await self.Translator.create({
62
78
  sourceLanguage: source,
63
79
  targetLanguage: target,
64
80
  });
65
81
  if (!translator) {
82
+ this.processErrorEvent({
83
+ message: 'Translator failed to initialize',
84
+ });
66
85
  return;
67
86
  }
68
87
  try {
@@ -96,24 +115,32 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
96
115
  }
97
116
  // use the Chrome AI summarizer
98
117
  async summarize(text, tone, wordCount) {
118
+ debug('summarize', text, tone, wordCount);
99
119
  if (!('Summarizer' in self)) {
100
120
  return;
101
121
  }
102
122
  const availability = await self.Summarizer.availability();
103
123
  if (availability === 'unavailable') {
124
+ this.processErrorEvent({
125
+ data: { message: 'Summarizer is unavailable' },
126
+ });
104
127
  return;
105
128
  }
106
129
  const summarizerOptions = this.getSummarizerOptions(tone, wordCount);
107
130
  const summarizer = await self.Summarizer.create(summarizerOptions);
108
131
  if (availability !== 'available') {
132
+ debug('awaiting summarizer ready');
109
133
  await summarizer.ready;
110
134
  }
111
135
  try {
112
136
  const context = `Write with a ${tone} tone.`;
137
+ debug('context', context);
113
138
  let summary = await summarizer.summarize(text, { context: context });
139
+ debug('summary', summary);
114
140
  wordCount = wordCount ?? 50;
115
141
  // gemini-nano has a tendency to exceed the word count, so we need to check and summarize again if necessary
116
142
  if (summary.split(' ').length > wordCount) {
143
+ debug('summary exceeds word count');
117
144
  summary = await summarizer.summarize(summary, { context: context });
118
145
  }
119
146
  this.processEvent({
@@ -126,6 +153,7 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
126
153
  });
127
154
  }
128
155
  catch (error) {
156
+ debug('error', error);
129
157
  this.processErrorEvent(error);
130
158
  }
131
159
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { isAtomicSite, isSimpleSite } from '@automattic/jetpack-shared-extension-utils';
4
+ import { isWoASite, isSimpleSite } from '@automattic/jetpack-script-data';
5
5
  import { useState } from '@wordpress/element';
6
6
  /**
7
7
  * Hook to get the type of site.
@@ -10,7 +10,7 @@ import { useState } from '@wordpress/element';
10
10
  */
11
11
  export default function useSiteType() {
12
12
  const getSiteType = () => {
13
- if (isAtomicSite()) {
13
+ if (isWoASite()) {
14
14
  return 'atomic';
15
15
  }
16
16
  if (isSimpleSite()) {
@@ -2,7 +2,8 @@
2
2
  * External dependencies
3
3
  */
4
4
  import getRedirectUrl from '@automattic/jetpack-components/tools/jp-redirect';
5
- import { isAtomicSite, isSimpleSite, getSiteFragment, useAutosaveAndRedirect, } from '@automattic/jetpack-shared-extension-utils';
5
+ import { isWpcomPlatformSite, isSimpleSite } from '@automattic/jetpack-script-data';
6
+ import { getSiteFragment, useAutosaveAndRedirect, } from '@automattic/jetpack-shared-extension-utils';
6
7
  import useAiFeature from "../use-ai-feature/index.js";
7
8
  const getWPComRedirectToURL = () => {
8
9
  const searchParams = new URLSearchParams(window.location.search);
@@ -31,7 +32,7 @@ export default function useAICheckout() {
31
32
  site: getSiteFragment(),
32
33
  path: 'jetpack_ai_yearly',
33
34
  });
34
- const checkoutUrl = isAtomicSite() || isSimpleSite() ? wpcomCheckoutUrl : jetpackCheckoutUrl;
35
+ const checkoutUrl = isWpcomPlatformSite() ? wpcomCheckoutUrl : jetpackCheckoutUrl;
35
36
  const { autosaveAndRedirect, isRedirecting } = useAutosaveAndRedirect(checkoutUrl);
36
37
  return {
37
38
  checkoutUrl,
@@ -3,12 +3,14 @@
3
3
  */
4
4
  import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
5
5
  import { __ } from '@wordpress/i18n';
6
+ import debugFactory from 'debug';
6
7
  /**
7
8
  * Internal dependencies
8
9
  */
9
10
  import askQuestion from "../../ask-question/index.js";
10
11
  import ChromeAIFactory from "../../chrome-ai/factory.js";
11
12
  import { ERROR_CONTEXT_TOO_LARGE, ERROR_MODERATION, ERROR_NETWORK, ERROR_QUOTA_EXCEEDED, ERROR_SERVICE_UNAVAILABLE, ERROR_UNCLEAR_PROMPT, ERROR_RESPONSE, AI_MODEL_DEFAULT, AI_MODEL_GEMINI_NANO, } from "../../types.js";
13
+ const debug = debugFactory('ai-client:use-ai-suggestions');
12
14
  /**
13
15
  * Get the error data for a given error code.
14
16
  *
@@ -91,8 +93,10 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
91
93
  * @return {void}
92
94
  */
93
95
  const handleSuggestion = useCallback((event) => {
96
+ debug('handleSuggestion', event);
94
97
  const partialSuggestion = removeLlamaArtifact(event?.detail);
95
98
  if (!partialSuggestion) {
99
+ debug('no partial suggestion');
96
100
  return;
97
101
  }
98
102
  setSuggestion(partialSuggestion);
@@ -138,6 +142,7 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
138
142
  setRequestingState('requesting');
139
143
  // check if we can (or should) use Chrome AI
140
144
  const chromeAI = await ChromeAIFactory(promptArg);
145
+ debug('chromeAI', chromeAI !== false);
141
146
  if (chromeAI !== false) {
142
147
  setModelAndRef(AI_MODEL_GEMINI_NANO);
143
148
  eventSourceRef.current = chromeAI;
@@ -147,6 +152,7 @@ export default function useAiSuggestions({ prompt, autoRequest = false, askQuest
147
152
  eventSourceRef.current = await askQuestion(promptArg, options);
148
153
  }
149
154
  if (!eventSourceRef?.current) {
155
+ debug('no event source');
150
156
  return;
151
157
  }
152
158
  // Alias
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { isSimpleSite } from '@automattic/jetpack-shared-extension-utils';
4
+ import { isSimpleSite } from '@automattic/jetpack-script-data';
5
5
  import debugFactory from 'debug';
6
6
  /**
7
7
  * Internal dependencies
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { isAtomicSite, isSimpleSite, getSiteFragment, } from '@automattic/jetpack-shared-extension-utils';
4
+ import { isWpcomPlatformSite } from '@automattic/jetpack-script-data';
5
+ import { getSiteFragment } from '@automattic/jetpack-shared-extension-utils';
5
6
  import { useSelect } from '@wordpress/data';
6
7
  /**
7
8
  * Internal dependencies
@@ -15,7 +16,7 @@ export const useCheckout = () => {
15
16
  tierPlansEnabled: selectors.getAiAssistantFeature().tierPlansEnabled,
16
17
  };
17
18
  }, []);
18
- const isJetpackSite = !isAtomicSite() && !isSimpleSite();
19
+ const isJetpackSite = !isWpcomPlatformSite();
19
20
  const redirectSource = isJetpackSite
20
21
  ? 'jetpack-ai-upgrade-url-for-jetpack-sites'
21
22
  : 'jetpack-ai-yearly-tier-upgrade-nudge';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@automattic/jetpack-ai-client",
4
- "version": "0.31.2",
4
+ "version": "0.32.1",
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": {
@@ -31,8 +31,8 @@
31
31
  "@storybook/react": "8.6.7",
32
32
  "@types/markdown-it": "14.1.2",
33
33
  "@types/turndown": "5.0.5",
34
- "jest": "^29.6.2",
35
- "jest-environment-jsdom": "29.7.0",
34
+ "jest": "30.0.0",
35
+ "jest-environment-jsdom": "30.0.0",
36
36
  "storybook": "8.6.7",
37
37
  "typescript": "5.8.3"
38
38
  },
@@ -45,28 +45,30 @@
45
45
  "main": "./build/index.js",
46
46
  "types": "./build/index.d.ts",
47
47
  "dependencies": {
48
- "@automattic/jetpack-base-styles": "^1.0.0",
49
- "@automattic/jetpack-components": "^1.1.2",
50
- "@automattic/jetpack-connection": "^1.2.2",
51
- "@automattic/jetpack-shared-extension-utils": "^1.1.0",
48
+ "@automattic/jetpack-base-styles": "^1.0.1",
49
+ "@automattic/jetpack-components": "^1.1.6",
50
+ "@automattic/jetpack-connection": "^1.2.6",
51
+ "@automattic/jetpack-script-data": "^0.4.4",
52
+ "@automattic/jetpack-explat": "workspace:*",
53
+ "@automattic/jetpack-shared-extension-utils": "^1.2.1",
52
54
  "@microsoft/fetch-event-source": "2.0.1",
53
- "@types/jest": "29.5.14",
55
+ "@types/jest": "30.0.0",
54
56
  "@types/react": "18.3.23",
55
57
  "@types/wordpress__block-editor": "11.5.16",
56
- "@wordpress/api-fetch": "7.24.0",
57
- "@wordpress/base-styles": "6.0.0",
58
- "@wordpress/blob": "4.24.0",
59
- "@wordpress/blocks": "14.13.0",
60
- "@wordpress/block-editor": "14.19.0",
61
- "@wordpress/components": "29.10.0",
62
- "@wordpress/compose": "7.24.0",
63
- "@wordpress/data": "10.24.0",
64
- "@wordpress/editor": "14.24.0",
65
- "@wordpress/element": "6.24.0",
66
- "@wordpress/i18n": "5.24.0",
67
- "@wordpress/icons": "10.24.0",
68
- "@wordpress/primitives": "4.24.0",
69
- "@wordpress/url": "4.24.0",
58
+ "@wordpress/api-fetch": "7.25.0",
59
+ "@wordpress/base-styles": "6.1.0",
60
+ "@wordpress/blob": "4.25.0",
61
+ "@wordpress/block-editor": "14.20.0",
62
+ "@wordpress/blocks": "14.14.0",
63
+ "@wordpress/components": "29.11.0",
64
+ "@wordpress/compose": "7.25.0",
65
+ "@wordpress/data": "10.25.0",
66
+ "@wordpress/editor": "14.25.0",
67
+ "@wordpress/element": "6.25.0",
68
+ "@wordpress/i18n": "5.25.0",
69
+ "@wordpress/icons": "10.25.0",
70
+ "@wordpress/primitives": "4.25.0",
71
+ "@wordpress/url": "4.25.0",
70
72
  "clsx": "2.1.1",
71
73
  "debug": "4.4.1",
72
74
  "markdown-it": "14.1.0",
@@ -1,8 +1,11 @@
1
+ import debugFactory from 'debug';
1
2
  import { PROMPT_TYPE_CHANGE_LANGUAGE, PROMPT_TYPE_SUMMARIZE } from '../constants.ts';
2
3
  import { PromptProp, PromptItemProps } from '../types.ts';
3
4
  import { isChromeAIAvailable } from './get-availability.ts';
4
5
  import ChromeAISuggestionsEventSource from './suggestions.ts';
5
6
 
7
+ const debug = debugFactory( 'ai-client:chrome-ai-factory' );
8
+
6
9
  interface PromptContext {
7
10
  type?: string;
8
11
  content?: string;
@@ -18,7 +21,8 @@ interface PromptContext {
18
21
  * @return ChromeAISuggestionsEventSource | bool
19
22
  */
20
23
  export default async function ChromeAIFactory( promptArg: PromptProp ) {
21
- if ( ! isChromeAIAvailable() ) {
24
+ if ( ! ( await isChromeAIAvailable() ) ) {
25
+ debug( 'Chrome AI is not available' );
22
26
  return false;
23
27
  }
24
28
 
@@ -31,6 +35,7 @@ export default async function ChromeAIFactory( promptArg: PromptProp ) {
31
35
  let tone = null;
32
36
  let wordCount = null;
33
37
 
38
+ debug( 'promptArg', promptArg );
34
39
  if ( Array.isArray( promptArg ) ) {
35
40
  for ( let i = 0; i < promptArg.length; i++ ) {
36
41
  const prompt: PromptItemProps = promptArg[ i ];
@@ -66,11 +71,13 @@ export default async function ChromeAIFactory( promptArg: PromptProp ) {
66
71
  }
67
72
  }
68
73
 
74
+ debug( 'promptType', promptType );
69
75
  // Early return if the prompt type is not supported.
70
76
  if (
71
77
  ! promptType.startsWith( 'ai-assistant-change-language' ) &&
72
78
  ! promptType.startsWith( 'ai-content-lens' )
73
79
  ) {
80
+ debug( 'promptType is not supported' );
74
81
  return false;
75
82
  }
76
83
 
@@ -81,16 +88,19 @@ export default async function ChromeAIFactory( promptArg: PromptProp ) {
81
88
  ! self.LanguageDetector.create ||
82
89
  ! self.LanguageDetector.availability
83
90
  ) {
91
+ debug( 'LanguageDetector is not available' );
84
92
  return false;
85
93
  }
86
94
 
87
95
  const languageDetectorAvailability = await self.LanguageDetector.availability();
88
96
  if ( languageDetectorAvailability === 'unavailable' ) {
97
+ debug( 'LanguageDetector is unavailable' );
89
98
  return false;
90
99
  }
91
100
 
92
101
  const detector = await self.LanguageDetector.create();
93
102
  if ( languageDetectorAvailability !== 'available' ) {
103
+ debug( 'awaiting detector ready' );
94
104
  await detector.ready;
95
105
  }
96
106
 
@@ -102,6 +112,7 @@ export default async function ChromeAIFactory( promptArg: PromptProp ) {
102
112
  ! self.Translator.create ||
103
113
  ! self.Translator.availability
104
114
  ) {
115
+ debug( 'Translator is not available' );
105
116
  return false;
106
117
  }
107
118
 
@@ -123,9 +134,12 @@ export default async function ChromeAIFactory( promptArg: PromptProp ) {
123
134
  }
124
135
  }
125
136
 
137
+ debug( 'languageOpts', languageOpts );
126
138
  const translationAvailability = await self.Translator.availability( languageOpts );
127
139
 
140
+ debug( 'translationAvailability', translationAvailability );
128
141
  if ( translationAvailability === 'unavailable' ) {
142
+ debug( 'Translator is unavailable' );
129
143
  return false;
130
144
  }
131
145
 
@@ -140,13 +154,16 @@ export default async function ChromeAIFactory( promptArg: PromptProp ) {
140
154
 
141
155
  if ( promptType.startsWith( 'ai-content-lens' ) ) {
142
156
  if ( ! ( 'Summarizer' in self ) ) {
157
+ debug( 'Summarizer is not available' );
143
158
  return false;
144
159
  }
145
160
 
146
161
  if ( context.language && context.language !== 'en (English)' ) {
162
+ debug( 'Summary is not English' );
147
163
  return false;
148
164
  }
149
165
 
166
+ debug( 'awaiting detector detect' );
150
167
  const confidences = await detector.detect( context.content );
151
168
 
152
169
  // if it doesn't look like the content is in English, we can't use the summary feature
@@ -156,6 +173,7 @@ export default async function ChromeAIFactory( promptArg: PromptProp ) {
156
173
  // required for the translator to work at all, which is also
157
174
  // why en is the default language.
158
175
  if ( confidence.confidence > 0.75 && confidence.detectedLanguage !== 'en' ) {
176
+ debug( 'Confidence for non-English content' );
159
177
  return false;
160
178
  }
161
179
  }
@@ -165,11 +183,16 @@ export default async function ChromeAIFactory( promptArg: PromptProp ) {
165
183
  wordCount: wordCount,
166
184
  };
167
185
 
168
- return new ChromeAISuggestionsEventSource( {
186
+ debug( 'summaryOpts', summaryOpts );
187
+
188
+ const chromeAiEventSourceOpts = {
169
189
  content: context.content,
170
190
  promptType: PROMPT_TYPE_SUMMARIZE,
171
191
  options: summaryOpts,
172
- } );
192
+ };
193
+
194
+ debug( 'chromeAiEventSourceOpts', chromeAiEventSourceOpts );
195
+ return new ChromeAISuggestionsEventSource( chromeAiEventSourceOpts );
173
196
  }
174
197
 
175
198
  return false;
@@ -1,8 +1,11 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { getJetpackExtensionAvailability } from '@automattic/jetpack-shared-extension-utils';
4
+ import { initializeExPlat, loadExperimentAssignmentWithAuth } from '@automattic/jetpack-explat';
5
5
  import { select } from '@wordpress/data';
6
+ import debugFactory from 'debug';
7
+
8
+ const debug = debugFactory( 'ai-client:chrome-ai-availability' );
6
9
 
7
10
  /**
8
11
  * Types
@@ -33,12 +36,25 @@ function getAiAssistantFeature() {
33
36
  *
34
37
  * @return {boolean} Whether Chrome AI can be enabled.
35
38
  */
36
- export function isChromeAIAvailable() {
39
+ export async function isChromeAIAvailable() {
37
40
  const { featuresControl } = getAiAssistantFeature();
38
- return (
39
- featuresControl?.[ 'chrome-ai' ]?.enabled !== false &&
40
- getJetpackExtensionAvailability( 'ai-use-chrome-ai-sometimes' ).available !== false
41
+
42
+ // Extra check if we want to control this via the feature flag for now
43
+ if ( featuresControl?.[ 'chrome-ai' ]?.enabled !== true ) {
44
+ debug( 'feature is disabled for this site/user' );
45
+ return false;
46
+ }
47
+
48
+ initializeExPlat();
49
+ debug( 'initialized explat' );
50
+
51
+ const { variationName } = await loadExperimentAssignmentWithAuth(
52
+ 'calypso_jetpack_ai_gemini_api_202503_v1'
41
53
  );
54
+
55
+ debug( 'variationName', variationName );
56
+
57
+ return variationName === 'treatment';
42
58
  }
43
59
 
44
60
  export default isChromeAIAvailable;
@@ -1,4 +1,5 @@
1
1
  import { EventSourceMessage } from '@microsoft/fetch-event-source';
2
+ import debugFactory from 'debug';
2
3
  import { PROMPT_TYPE_CHANGE_LANGUAGE, PROMPT_TYPE_SUMMARIZE } from '../constants.ts';
3
4
  import { getErrorData } from '../hooks/use-ai-suggestions/index.ts';
4
5
  import { renderHTMLFromMarkdown, renderMarkdownFromHTML } from '../libs/markdown/index.ts';
@@ -36,6 +37,8 @@ type FunctionCallProps = {
36
37
  arguments?: string;
37
38
  };
38
39
 
40
+ const debug = debugFactory( 'ai-client:chrome-ai-suggestions' );
41
+
39
42
  export default class ChromeAISuggestionsEventSource extends EventTarget {
40
43
  fullMessage: string;
41
44
  fullFunctionCall: FunctionCallProps;
@@ -63,6 +66,7 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
63
66
  promptType,
64
67
  options = {},
65
68
  }: ChromeAISuggestionsEventSourceConstructorArgs ) {
69
+ debug( 'initSource', content, promptType, options );
66
70
  if ( promptType === PROMPT_TYPE_CHANGE_LANGUAGE ) {
67
71
  this.translate( content, options.targetLanguage, options.sourceLanguage );
68
72
  }
@@ -80,6 +84,7 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
80
84
 
81
85
  processEvent( e: EventSourceMessage ) {
82
86
  let data: ChromeAIEvent;
87
+ debug( 'processEvent', e );
83
88
  try {
84
89
  data = JSON.parse( e.data );
85
90
  } catch ( err ) {
@@ -99,6 +104,7 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
99
104
  }
100
105
 
101
106
  processErrorEvent( e ) {
107
+ debug( 'processErrorEvent', e );
102
108
  // Dispatch a generic network error event
103
109
  this.dispatchEvent( new CustomEvent( ERROR_NETWORK, { detail: e } ) );
104
110
  this.dispatchEvent(
@@ -114,12 +120,28 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
114
120
  return;
115
121
  }
116
122
 
123
+ const translatorAvailability = await self.Translator.availability( {
124
+ sourceLanguage: source,
125
+ targetLanguage: target,
126
+ } );
127
+
128
+ if ( translatorAvailability === 'unavailable' ) {
129
+ debug( 'awaiting translator ready' );
130
+ this.processErrorEvent( {
131
+ message: 'Translator is unavailable',
132
+ } );
133
+ return;
134
+ }
135
+
117
136
  const translator = await self.Translator.create( {
118
137
  sourceLanguage: source,
119
138
  targetLanguage: target,
120
139
  } );
121
140
 
122
141
  if ( ! translator ) {
142
+ this.processErrorEvent( {
143
+ message: 'Translator failed to initialize',
144
+ } );
123
145
  return;
124
146
  }
125
147
 
@@ -161,6 +183,7 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
161
183
 
162
184
  // use the Chrome AI summarizer
163
185
  async summarize( text: string, tone?: string, wordCount?: number ) {
186
+ debug( 'summarize', text, tone, wordCount );
164
187
  if ( ! ( 'Summarizer' in self ) ) {
165
188
  return;
166
189
  }
@@ -168,6 +191,9 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
168
191
  const availability = await self.Summarizer.availability();
169
192
 
170
193
  if ( availability === 'unavailable' ) {
194
+ this.processErrorEvent( {
195
+ data: { message: 'Summarizer is unavailable' },
196
+ } );
171
197
  return;
172
198
  }
173
199
 
@@ -176,17 +202,20 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
176
202
  const summarizer = await self.Summarizer.create( summarizerOptions );
177
203
 
178
204
  if ( availability !== 'available' ) {
205
+ debug( 'awaiting summarizer ready' );
179
206
  await summarizer.ready;
180
207
  }
181
208
 
182
209
  try {
183
210
  const context = `Write with a ${ tone } tone.`;
211
+ debug( 'context', context );
184
212
  let summary = await summarizer.summarize( text, { context: context } );
185
-
213
+ debug( 'summary', summary );
186
214
  wordCount = wordCount ?? 50;
187
215
 
188
216
  // gemini-nano has a tendency to exceed the word count, so we need to check and summarize again if necessary
189
217
  if ( summary.split( ' ' ).length > wordCount ) {
218
+ debug( 'summary exceeds word count' );
190
219
  summary = await summarizer.summarize( summary, { context: context } );
191
220
  }
192
221
 
@@ -199,6 +228,7 @@ export default class ChromeAISuggestionsEventSource extends EventTarget {
199
228
  } ),
200
229
  } );
201
230
  } catch ( error ) {
231
+ debug( 'error', error );
202
232
  this.processErrorEvent( error );
203
233
  }
204
234
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { isAtomicSite, isSimpleSite } from '@automattic/jetpack-shared-extension-utils';
4
+ import { isWoASite, isSimpleSite } from '@automattic/jetpack-script-data';
5
5
  import { useState } from '@wordpress/element';
6
6
 
7
7
  /**
@@ -11,7 +11,7 @@ import { useState } from '@wordpress/element';
11
11
  */
12
12
  export default function useSiteType() {
13
13
  const getSiteType = () => {
14
- if ( isAtomicSite() ) {
14
+ if ( isWoASite() ) {
15
15
  return 'atomic';
16
16
  }
17
17
  if ( isSimpleSite() ) {
@@ -2,9 +2,8 @@
2
2
  * External dependencies
3
3
  */
4
4
  import getRedirectUrl from '@automattic/jetpack-components/tools/jp-redirect';
5
+ import { isWpcomPlatformSite, isSimpleSite } from '@automattic/jetpack-script-data';
5
6
  import {
6
- isAtomicSite,
7
- isSimpleSite,
8
7
  getSiteFragment,
9
8
  useAutosaveAndRedirect,
10
9
  } from '@automattic/jetpack-shared-extension-utils';
@@ -53,7 +52,7 @@ export default function useAICheckout(): UseAICheckoutReturn {
53
52
  path: 'jetpack_ai_yearly',
54
53
  } );
55
54
 
56
- const checkoutUrl = isAtomicSite() || isSimpleSite() ? wpcomCheckoutUrl : jetpackCheckoutUrl;
55
+ const checkoutUrl = isWpcomPlatformSite() ? wpcomCheckoutUrl : jetpackCheckoutUrl;
57
56
 
58
57
  const { autosaveAndRedirect, isRedirecting } = useAutosaveAndRedirect( checkoutUrl );
59
58
 
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
5
5
  import { __ } from '@wordpress/i18n';
6
+ import debugFactory from 'debug';
6
7
  /**
7
8
  * Internal dependencies
8
9
  */
@@ -31,6 +32,8 @@ import type {
31
32
  AiModelTypeProp,
32
33
  } from '../../types.ts';
33
34
 
35
+ const debug = debugFactory( 'ai-client:use-ai-suggestions' );
36
+
34
37
  export type RequestingErrorProps = {
35
38
  /*
36
39
  * A string code to refer to the error.
@@ -252,9 +255,11 @@ export default function useAiSuggestions( {
252
255
  */
253
256
  const handleSuggestion = useCallback(
254
257
  ( event: CustomEvent ) => {
258
+ debug( 'handleSuggestion', event );
255
259
  const partialSuggestion = removeLlamaArtifact( event?.detail );
256
260
 
257
261
  if ( ! partialSuggestion ) {
262
+ debug( 'no partial suggestion' );
258
263
  return;
259
264
  }
260
265
 
@@ -335,6 +340,7 @@ export default function useAiSuggestions( {
335
340
 
336
341
  // check if we can (or should) use Chrome AI
337
342
  const chromeAI = await ChromeAIFactory( promptArg );
343
+ debug( 'chromeAI', chromeAI !== false );
338
344
 
339
345
  if ( chromeAI !== false ) {
340
346
  setModelAndRef( AI_MODEL_GEMINI_NANO );
@@ -345,6 +351,7 @@ export default function useAiSuggestions( {
345
351
  }
346
352
 
347
353
  if ( ! eventSourceRef?.current ) {
354
+ debug( 'no event source' );
348
355
  return;
349
356
  }
350
357
 
package/src/jwt/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import { isSimpleSite } from '@automattic/jetpack-shared-extension-utils';
4
+ import { isSimpleSite } from '@automattic/jetpack-script-data';
5
5
  import debugFactory from 'debug';
6
6
  /**
7
7
  * Internal dependencies
@@ -1,11 +1,8 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- import {
5
- isAtomicSite,
6
- isSimpleSite,
7
- getSiteFragment,
8
- } from '@automattic/jetpack-shared-extension-utils';
4
+ import { isWpcomPlatformSite } from '@automattic/jetpack-script-data';
5
+ import { getSiteFragment } from '@automattic/jetpack-shared-extension-utils';
9
6
  import { useSelect } from '@wordpress/data';
10
7
  /**
11
8
  * Internal dependencies
@@ -25,7 +22,7 @@ export const useCheckout = () => {
25
22
  };
26
23
  }, [] );
27
24
 
28
- const isJetpackSite = ! isAtomicSite() && ! isSimpleSite();
25
+ const isJetpackSite = ! isWpcomPlatformSite();
29
26
  const redirectSource = isJetpackSite
30
27
  ? 'jetpack-ai-upgrade-url-for-jetpack-sites'
31
28
  : 'jetpack-ai-yearly-tier-upgrade-nudge';