@atlaskit/emoji 71.6.5 → 71.7.0
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 +32 -0
- package/afm-cc/tsconfig.json +3 -0
- package/afm-products/tsconfig.json +3 -0
- package/dist/cjs/api/ai/generateEmojiImage.js +101 -0
- package/dist/cjs/components/common/CreateEmojiWithRovo.compiled.css +12 -0
- package/dist/cjs/components/common/CreateEmojiWithRovo.js +150 -0
- package/dist/cjs/components/common/EmojiActions.js +6 -2
- package/dist/cjs/components/common/EmojiUploadPicker.js +30 -1
- package/dist/cjs/components/i18n.js +25 -0
- package/dist/cjs/components/picker/EmojiPickerComponent.compiled.css +3 -0
- package/dist/cjs/components/picker/EmojiPickerComponent.js +22 -3
- package/dist/cjs/components/picker/EmojiPickerList.js +6 -2
- package/dist/cjs/components/uploader/EmojiUploadComponent.js +5 -2
- package/dist/cjs/util/ai-emoji.js +72 -0
- package/dist/cjs/util/analytics/analytics.js +14 -2
- package/dist/cjs/util/analytics/index.js +18 -0
- package/dist/es2019/api/ai/generateEmojiImage.js +64 -0
- package/dist/es2019/components/common/CreateEmojiWithRovo.compiled.css +12 -0
- package/dist/es2019/components/common/CreateEmojiWithRovo.js +113 -0
- package/dist/es2019/components/common/EmojiActions.js +6 -2
- package/dist/es2019/components/common/EmojiUploadPicker.js +28 -1
- package/dist/es2019/components/i18n.js +25 -0
- package/dist/es2019/components/picker/EmojiPickerComponent.compiled.css +3 -0
- package/dist/es2019/components/picker/EmojiPickerComponent.js +22 -3
- package/dist/es2019/components/picker/EmojiPickerList.js +6 -2
- package/dist/es2019/components/uploader/EmojiUploadComponent.js +5 -2
- package/dist/es2019/util/ai-emoji.js +64 -0
- package/dist/es2019/util/analytics/analytics.js +5 -1
- package/dist/es2019/util/analytics/index.js +1 -1
- package/dist/esm/api/ai/generateEmojiImage.js +94 -0
- package/dist/esm/components/common/CreateEmojiWithRovo.compiled.css +12 -0
- package/dist/esm/components/common/CreateEmojiWithRovo.js +141 -0
- package/dist/esm/components/common/EmojiActions.js +6 -2
- package/dist/esm/components/common/EmojiUploadPicker.js +30 -1
- package/dist/esm/components/i18n.js +25 -0
- package/dist/esm/components/picker/EmojiPickerComponent.compiled.css +3 -0
- package/dist/esm/components/picker/EmojiPickerComponent.js +22 -3
- package/dist/esm/components/picker/EmojiPickerList.js +6 -2
- package/dist/esm/components/uploader/EmojiUploadComponent.js +5 -2
- package/dist/esm/util/ai-emoji.js +66 -0
- package/dist/esm/util/analytics/analytics.js +13 -1
- package/dist/esm/util/analytics/index.js +1 -1
- package/dist/types/api/ai/generateEmojiImage.d.ts +29 -0
- package/dist/types/components/common/CreateEmojiWithRovo.d.ts +24 -0
- package/dist/types/components/common/EmojiActions.d.ts +11 -0
- package/dist/types/components/common/EmojiUploadPicker.d.ts +9 -0
- package/dist/types/components/i18n.d.ts +25 -0
- package/dist/types/components/picker/EmojiPicker.d.ts +6 -0
- package/dist/types/components/picker/EmojiPickerComponent.d.ts +6 -0
- package/dist/types/components/picker/EmojiPickerList.d.ts +5 -0
- package/dist/types/components/uploader/EmojiUploadComponent.d.ts +2 -0
- package/dist/types/util/ai-emoji.d.ts +41 -0
- package/dist/types/util/analytics/analytics.d.ts +7 -0
- package/dist/types/util/analytics/index.d.ts +1 -1
- package/package.json +7 -2
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.wrapEmojiPrompt = exports.slugifyPrompt = exports.MAX_SHORTNAME_LENGTH = exports.EMOJI_PROMPT_PREFIX = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Pure utilities for the "Create an emoji with Rovo" (AI emoji generation) flow.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Maximum length of an auto-generated shortname. Matches the `maxNameLength`
|
|
13
|
+
* used by the manual emoji upload flow (see EmojiUploadPicker).
|
|
14
|
+
*/
|
|
15
|
+
var MAX_SHORTNAME_LENGTH = exports.MAX_SHORTNAME_LENGTH = 50;
|
|
16
|
+
var LEADING_ARTICLES = ['a', 'an', 'the'];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Convert a free-text emoji description into a slugified shortname suitable for
|
|
20
|
+
* the `:shortname:` field expected by pf-emoji-service.
|
|
21
|
+
*
|
|
22
|
+
* Rules (v1):
|
|
23
|
+
* - Strip leading articles (a, an, the)
|
|
24
|
+
* - Lowercase
|
|
25
|
+
* - Replace runs of whitespace with a single underscore
|
|
26
|
+
* - Remove any character that is not a-z, 0-9 or underscore
|
|
27
|
+
* - Collapse repeated underscores and trim leading/trailing underscores
|
|
28
|
+
* - Truncate to {@link MAX_SHORTNAME_LENGTH} characters
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* slugifyPrompt('a cat wearing a hard hat') // => 'cat_wearing_a_hard_hat'
|
|
32
|
+
*/
|
|
33
|
+
var slugifyPrompt = exports.slugifyPrompt = function slugifyPrompt(prompt) {
|
|
34
|
+
if (!prompt) {
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
var words = prompt.trim().toLowerCase().split(/\s+/);
|
|
38
|
+
|
|
39
|
+
// Strip a single leading article only (e.g. "a cat" -> "cat").
|
|
40
|
+
if (words.length > 1 && LEADING_ARTICLES.includes(words[0])) {
|
|
41
|
+
words = words.slice(1);
|
|
42
|
+
}
|
|
43
|
+
var slug = words.join('_')
|
|
44
|
+
// remove everything that is not a word char (letters, digits, underscore)
|
|
45
|
+
.replace(/[^a-z0-9_]/g, '')
|
|
46
|
+
// collapse repeated underscores introduced by removed characters
|
|
47
|
+
.replace(/_+/g, '_')
|
|
48
|
+
// trim leading/trailing underscores
|
|
49
|
+
.replace(/^_+|_+$/g, '');
|
|
50
|
+
return slug.slice(0, MAX_SHORTNAME_LENGTH);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Prefix prepended to the user's description so the header-image backend
|
|
55
|
+
* produces clean, emoji-style output instead of a scene/photo.
|
|
56
|
+
*
|
|
57
|
+
* Kept as a single line (no newlines) — newlines in the prompt can degrade the
|
|
58
|
+
* model's output. Learnings from ShipIt 56 ("Emoji Pop") showed that a simple
|
|
59
|
+
* prompt prefix significantly improved emoji quality.
|
|
60
|
+
*/
|
|
61
|
+
var EMOJI_PROMPT_PREFIX = exports.EMOJI_PROMPT_PREFIX = 'Generate a clean, simple, emoji-style icon of ';
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Wrap the raw user prompt with the emoji-style prefix.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* wrapEmojiPrompt('a cat wearing a hard hat')
|
|
68
|
+
* // => 'Generate a clean, simple, emoji-style icon of a cat wearing a hard hat'
|
|
69
|
+
*/
|
|
70
|
+
var wrapEmojiPrompt = exports.wrapEmojiPrompt = function wrapEmojiPrompt(userPrompt) {
|
|
71
|
+
return "".concat(EMOJI_PROMPT_PREFIX).concat(userPrompt.trim());
|
|
72
|
+
};
|
|
@@ -4,7 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
|
-
exports.uploadSucceededEvent = exports.uploadFailedEvent = exports.uploadConfirmButton = exports.uploadCancelButton = exports.uploadBeginButton = exports.typeaheadSelectedEvent = exports.typeaheadRenderedEvent = exports.typeaheadCancelledEvent = exports.toneSelectorOpenedEvent = exports.toneSelectorClosedEvent = exports.toneSelectedEvent = exports.selectedFileEvent = exports.recordSucceededEmoji = exports.recordSucceeded = exports.recordSelectionSucceededSli = exports.recordSelectionFailedSli = exports.recordFailedEmoji = exports.recordFailed = exports.pickerSearchedEvent = exports.pickerClickedEvent = exports.openedPickerEvent = exports.extractErrorInfo = exports.deleteConfirmEvent = exports.deleteCancelEvent = exports.deleteBeginEvent = exports.createAndFireEventInElementsChannel = exports.closedPickerEvent = exports.categoryClickedEvent = void 0;
|
|
7
|
+
exports.uploadSucceededEvent = exports.uploadFailedEvent = exports.uploadConfirmButton = exports.uploadCancelButton = exports.uploadBeginButton = exports.typeaheadSelectedEvent = exports.typeaheadRenderedEvent = exports.typeaheadCancelledEvent = exports.toneSelectorOpenedEvent = exports.toneSelectorClosedEvent = exports.toneSelectedEvent = exports.selectedFileEvent = exports.recordSucceededEmoji = exports.recordSucceeded = exports.recordSelectionSucceededSli = exports.recordSelectionFailedSli = exports.recordFailedEmoji = exports.recordFailed = exports.pickerSearchedEvent = exports.pickerClickedEvent = exports.openedPickerEvent = exports.extractErrorInfo = exports.deleteConfirmEvent = exports.deleteCancelEvent = exports.deleteBeginEvent = exports.createAndFireEventInElementsChannel = exports.closedPickerEvent = exports.categoryClickedEvent = exports.aiGenerationStartedEvent = exports.aiGenerationFailedEvent = exports.aiGenerationCompletedEvent = void 0;
|
|
8
8
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
9
|
var _analyticsNext = require("@atlaskit/analytics-next");
|
|
10
10
|
var _types = require("../../types");
|
|
@@ -20,7 +20,7 @@ var createEvent = function createEvent(eventType, action, actionSubject, actionS
|
|
|
20
20
|
actionSubjectId: actionSubjectId,
|
|
21
21
|
attributes: _objectSpread({
|
|
22
22
|
packageName: "@atlaskit/emoji",
|
|
23
|
-
packageVersion: "71.6.
|
|
23
|
+
packageVersion: "71.6.6"
|
|
24
24
|
}, attributes)
|
|
25
25
|
};
|
|
26
26
|
};
|
|
@@ -138,6 +138,18 @@ var uploadSucceededEvent = exports.uploadSucceededEvent = function uploadSucceed
|
|
|
138
138
|
var uploadFailedEvent = exports.uploadFailedEvent = function uploadFailedEvent(attributes) {
|
|
139
139
|
return createEvent('operational', 'failed', 'emojiUploader', undefined, attributes);
|
|
140
140
|
};
|
|
141
|
+
var aiEmojiGenerationEvent = function aiEmojiGenerationEvent(action, actionSubjectId, attributes) {
|
|
142
|
+
return createEvent('ui', action, 'emojiPickerAiGeneration', actionSubjectId, attributes);
|
|
143
|
+
};
|
|
144
|
+
var aiGenerationStartedEvent = exports.aiGenerationStartedEvent = function aiGenerationStartedEvent(attributes) {
|
|
145
|
+
return aiEmojiGenerationEvent('started', 'generateButton', attributes);
|
|
146
|
+
};
|
|
147
|
+
var aiGenerationCompletedEvent = exports.aiGenerationCompletedEvent = function aiGenerationCompletedEvent(attributes) {
|
|
148
|
+
return createEvent('operational', 'completed', 'emojiPickerAiGeneration', undefined, attributes);
|
|
149
|
+
};
|
|
150
|
+
var aiGenerationFailedEvent = exports.aiGenerationFailedEvent = function aiGenerationFailedEvent(attributes) {
|
|
151
|
+
return createEvent('operational', 'failed', 'emojiPickerAiGeneration', undefined, attributes);
|
|
152
|
+
};
|
|
141
153
|
var deleteBeginEvent = exports.deleteBeginEvent = function deleteBeginEvent(attributes) {
|
|
142
154
|
return createEvent('ui', 'clicked', 'emojiPicker', 'deleteEmojiTrigger', attributes);
|
|
143
155
|
};
|
|
@@ -3,6 +3,24 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
+
Object.defineProperty(exports, "aiGenerationCompletedEvent", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function get() {
|
|
9
|
+
return _analytics.aiGenerationCompletedEvent;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(exports, "aiGenerationFailedEvent", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function get() {
|
|
15
|
+
return _analytics.aiGenerationFailedEvent;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(exports, "aiGenerationStartedEvent", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function get() {
|
|
21
|
+
return _analytics.aiGenerationStartedEvent;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
6
24
|
Object.defineProperty(exports, "categoryClickedEvent", {
|
|
7
25
|
enumerable: true,
|
|
8
26
|
get: function get() {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calls the Confluence "header image" AI backend to generate an emoji-style
|
|
3
|
+
* image (base64) from a text description.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { wrapEmojiPrompt } from '../../util/ai-emoji';
|
|
7
|
+
import debug from '../../util/logger';
|
|
8
|
+
const HEADER_IMAGE_GENERATION_ENDPOINT = '/gateway/api/assist/api/ai/v2/ai-feature/confluence-header-image-generation';
|
|
9
|
+
|
|
10
|
+
/** Emojis are square, so we always request a 1:1 image from the BE. */
|
|
11
|
+
const EMOJI_ASPECT_RATIO = '1:1';
|
|
12
|
+
|
|
13
|
+
/** Shape of the `ai_feature_output` envelope returned by the BE. */
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate an emoji-style image and return its base64 image data.
|
|
17
|
+
*
|
|
18
|
+
* @throws Error if the request fails, the BE returns a body-level error, or no
|
|
19
|
+
* image data is returned.
|
|
20
|
+
*/
|
|
21
|
+
export const generateEmojiImage = async ({
|
|
22
|
+
contentId,
|
|
23
|
+
prompt
|
|
24
|
+
}) => {
|
|
25
|
+
const wrappedPrompt = wrapEmojiPrompt(prompt);
|
|
26
|
+
const response = await fetch(HEADER_IMAGE_GENERATION_ENDPOINT, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: {
|
|
29
|
+
'content-type': 'application/json;charset=UTF-8',
|
|
30
|
+
'X-Experience-Id': 'confluence-ai-first-creation',
|
|
31
|
+
'X-Product': 'confluence'
|
|
32
|
+
},
|
|
33
|
+
body: JSON.stringify({
|
|
34
|
+
ai_feature_input: {
|
|
35
|
+
adfContent: wrappedPrompt,
|
|
36
|
+
contentId,
|
|
37
|
+
useRawPrompt: true,
|
|
38
|
+
// Emojis are square — request a 1:1 image from the BE.
|
|
39
|
+
aspectRatio: EMOJI_ASPECT_RATIO
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
});
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
debug('generateEmojiImage failed', response.status);
|
|
45
|
+
throw new Error(`Emoji image generation failed with status ${response.status}`);
|
|
46
|
+
}
|
|
47
|
+
const json = await response.json();
|
|
48
|
+
const output = json === null || json === void 0 ? void 0 : json.ai_feature_output;
|
|
49
|
+
if (!output) {
|
|
50
|
+
throw new Error('Emoji image generation returned no content');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The BE can return 200 OK with an error in the body (e.g. moderation).
|
|
54
|
+
if (output.error) {
|
|
55
|
+
throw new Error(output.error.error_message || output.error.error_reason || 'Emoji image generation failed');
|
|
56
|
+
}
|
|
57
|
+
if (!output.imageData) {
|
|
58
|
+
throw new Error('Emoji image generation did not return image data');
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
imageData: output.imageData,
|
|
62
|
+
mediaFileId: output.mediaFileId
|
|
63
|
+
};
|
|
64
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
|
|
2
|
+
._zulp12x7{gap:var(--ds-space-075,6px)}
|
|
3
|
+
._zulpu2gc{gap:var(--ds-space-100,8px)}
|
|
4
|
+
._x3doia51{border-top:var(--ds-border-width,1px) solid var(--ds-border,#0b120e24)}._16jlkb7n{flex-grow:1}
|
|
5
|
+
._19pku2gc{margin-top:var(--ds-space-100,8px)}
|
|
6
|
+
._1e0c1txw{display:flex}
|
|
7
|
+
._1o9zkb7n{flex-shrink:1}
|
|
8
|
+
._2lx21bp4{flex-direction:column}
|
|
9
|
+
._4cvr1h6o{align-items:center}
|
|
10
|
+
._4cvr1y6m{align-items:flex-start}
|
|
11
|
+
._ca0qutpp{padding-top:var(--ds-space-150,9pt)}
|
|
12
|
+
._i0dlf1ug{flex-basis:0%}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/* CreateEmojiWithRovo.tsx generated by @compiled/babel-plugin v0.39.1 */
|
|
2
|
+
import "./CreateEmojiWithRovo.compiled.css";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { ax, ix } from "@compiled/react/runtime";
|
|
5
|
+
import { useCallback, useState } from 'react';
|
|
6
|
+
import { FormattedMessage, useIntl } from 'react-intl';
|
|
7
|
+
import { IconButton } from '@atlaskit/button/new';
|
|
8
|
+
import TextField from '@atlaskit/textfield';
|
|
9
|
+
import ArrowUpIcon from '@atlaskit/icon/core/arrow-up';
|
|
10
|
+
import { RovoIcon } from '@atlaskit/logo';
|
|
11
|
+
import { Text } from '@atlaskit/primitives/compiled';
|
|
12
|
+
import { generateEmojiImage } from '../../api/ai/generateEmojiImage';
|
|
13
|
+
import { slugifyPrompt } from '../../util/ai-emoji';
|
|
14
|
+
import { aiGenerationStartedEvent, aiGenerationCompletedEvent, aiGenerationFailedEvent } from '../../util/analytics';
|
|
15
|
+
import { messages } from '../i18n';
|
|
16
|
+
import EmojiErrorMessage from './EmojiErrorMessage';
|
|
17
|
+
export const createEmojiWithRovoTestId = 'create-emoji-with-rovo';
|
|
18
|
+
export const createEmojiWithRovoPromptTestId = 'create-emoji-with-rovo-prompt';
|
|
19
|
+
export const createEmojiWithRovoGenerateTestId = 'create-emoji-with-rovo-generate';
|
|
20
|
+
const sectionStyles = null;
|
|
21
|
+
const headerStyles = null;
|
|
22
|
+
const promptRowStyles = null;
|
|
23
|
+
const promptInputStyles = null;
|
|
24
|
+
const CreateEmojiWithRovo = props => {
|
|
25
|
+
const {
|
|
26
|
+
contentId,
|
|
27
|
+
fireAnalytics,
|
|
28
|
+
onEmojiGenerated
|
|
29
|
+
} = props;
|
|
30
|
+
const {
|
|
31
|
+
formatMessage
|
|
32
|
+
} = useIntl();
|
|
33
|
+
const [prompt, setPrompt] = useState('');
|
|
34
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
35
|
+
const [hasError, setHasError] = useState(false);
|
|
36
|
+
const onPromptChange = useCallback(event => {
|
|
37
|
+
setPrompt(event.target.value);
|
|
38
|
+
// Clear any previous error as soon as the user edits the prompt to retry.
|
|
39
|
+
setHasError(false);
|
|
40
|
+
}, []);
|
|
41
|
+
const onGenerate = useCallback(async () => {
|
|
42
|
+
const trimmedPrompt = prompt.trim();
|
|
43
|
+
if (!trimmedPrompt || isGenerating) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
setHasError(false);
|
|
47
|
+
setIsGenerating(true);
|
|
48
|
+
fireAnalytics === null || fireAnalytics === void 0 ? void 0 : fireAnalytics(aiGenerationStartedEvent({
|
|
49
|
+
promptLength: trimmedPrompt.length
|
|
50
|
+
}));
|
|
51
|
+
const startTime = Date.now();
|
|
52
|
+
try {
|
|
53
|
+
const {
|
|
54
|
+
imageData
|
|
55
|
+
} = await generateEmojiImage({
|
|
56
|
+
contentId,
|
|
57
|
+
prompt: trimmedPrompt
|
|
58
|
+
});
|
|
59
|
+
const dataURL = `data:image/png;base64,${imageData}`;
|
|
60
|
+
const suggestedName = slugifyPrompt(trimmedPrompt);
|
|
61
|
+
fireAnalytics === null || fireAnalytics === void 0 ? void 0 : fireAnalytics(aiGenerationCompletedEvent({
|
|
62
|
+
duration: Date.now() - startTime
|
|
63
|
+
}));
|
|
64
|
+
// Hand the generated image to the parent upload form.
|
|
65
|
+
onEmojiGenerated(dataURL, suggestedName);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
setHasError(true);
|
|
68
|
+
fireAnalytics === null || fireAnalytics === void 0 ? void 0 : fireAnalytics(aiGenerationFailedEvent({
|
|
69
|
+
errorType: error instanceof Error ? error.message : 'generation_failed'
|
|
70
|
+
}));
|
|
71
|
+
} finally {
|
|
72
|
+
setIsGenerating(false);
|
|
73
|
+
}
|
|
74
|
+
}, [contentId, fireAnalytics, isGenerating, onEmojiGenerated, prompt]);
|
|
75
|
+
const generateDisabled = !prompt.trim() || isGenerating;
|
|
76
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
77
|
+
"data-testid": createEmojiWithRovoTestId,
|
|
78
|
+
className: ax(["_zulpu2gc _x3doia51 _1e0c1txw _2lx21bp4 _ca0qutpp _19pku2gc"])
|
|
79
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
80
|
+
className: ax(["_zulp12x7 _1e0c1txw _4cvr1h6o"])
|
|
81
|
+
}, /*#__PURE__*/React.createElement(RovoIcon, {
|
|
82
|
+
appearance: "brand",
|
|
83
|
+
size: "xsmall",
|
|
84
|
+
label: formatMessage(messages.createEmojiWithRovoTitle)
|
|
85
|
+
}), /*#__PURE__*/React.createElement(Text, {
|
|
86
|
+
size: "medium",
|
|
87
|
+
weight: "bold"
|
|
88
|
+
}, /*#__PURE__*/React.createElement(FormattedMessage, messages.createEmojiWithRovoTitle))), /*#__PURE__*/React.createElement("div", {
|
|
89
|
+
className: ax(["_zulpu2gc _1e0c1txw _4cvr1y6m"])
|
|
90
|
+
}, /*#__PURE__*/React.createElement("span", {
|
|
91
|
+
className: ax(["_16jlkb7n _1o9zkb7n _i0dlf1ug"])
|
|
92
|
+
}, /*#__PURE__*/React.createElement(TextField, {
|
|
93
|
+
value: prompt,
|
|
94
|
+
onChange: onPromptChange,
|
|
95
|
+
placeholder: formatMessage(messages.createEmojiWithRovoPromptPlaceholder),
|
|
96
|
+
"aria-label": formatMessage(messages.createEmojiWithRovoPromptAriaLabel),
|
|
97
|
+
isCompact: true,
|
|
98
|
+
isDisabled: isGenerating,
|
|
99
|
+
testId: createEmojiWithRovoPromptTestId
|
|
100
|
+
})), /*#__PURE__*/React.createElement(IconButton, {
|
|
101
|
+
appearance: "primary",
|
|
102
|
+
icon: ArrowUpIcon,
|
|
103
|
+
label: formatMessage(messages.createEmojiWithRovoGenerateLabel),
|
|
104
|
+
isDisabled: generateDisabled,
|
|
105
|
+
isLoading: isGenerating,
|
|
106
|
+
onClick: onGenerate,
|
|
107
|
+
testId: createEmojiWithRovoGenerateTestId
|
|
108
|
+
})), hasError && /*#__PURE__*/React.createElement(EmojiErrorMessage, {
|
|
109
|
+
errorStyle: "chooseFile",
|
|
110
|
+
message: /*#__PURE__*/React.createElement(FormattedMessage, messages.createEmojiWithRovoError)
|
|
111
|
+
}));
|
|
112
|
+
};
|
|
113
|
+
export default CreateEmojiWithRovo;
|
|
@@ -308,7 +308,9 @@ export const EmojiActions = props => {
|
|
|
308
308
|
onUploadEmoji: onUploadEmoji,
|
|
309
309
|
onFileChooserClicked: onFileChooserClicked,
|
|
310
310
|
errorMessage: uploadErrorMessage,
|
|
311
|
-
initialUploadName: initialUploadName
|
|
311
|
+
initialUploadName: initialUploadName,
|
|
312
|
+
contentId: props.contentId,
|
|
313
|
+
fireAnalytics: props.fireAnalytics
|
|
312
314
|
})) : /*#__PURE__*/React.createElement("div", {
|
|
313
315
|
className: ax(["_16jlidpf _1o9zidpf _i0dl1wug _n7zl1uh4 _16qsjgpa"])
|
|
314
316
|
}, /*#__PURE__*/React.createElement(EmojiUploadPicker, {
|
|
@@ -316,7 +318,9 @@ export const EmojiActions = props => {
|
|
|
316
318
|
onUploadEmoji: onUploadEmoji,
|
|
317
319
|
onFileChooserClicked: onFileChooserClicked,
|
|
318
320
|
errorMessage: uploadErrorMessage,
|
|
319
|
-
initialUploadName: initialUploadName
|
|
321
|
+
initialUploadName: initialUploadName,
|
|
322
|
+
contentId: props.contentId,
|
|
323
|
+
fireAnalytics: props.fireAnalytics
|
|
320
324
|
}));
|
|
321
325
|
}
|
|
322
326
|
if (emojiToDelete) {
|
|
@@ -10,6 +10,7 @@ import AkButton from '@atlaskit/button/standard-button';
|
|
|
10
10
|
import { Text } from '@atlaskit/primitives/compiled';
|
|
11
11
|
import FocusLock from 'react-focus-lock';
|
|
12
12
|
import * as ImageUtil from '../../util/image';
|
|
13
|
+
import CreateEmojiWithRovo from './CreateEmojiWithRovo';
|
|
13
14
|
import debug from '../../util/logger';
|
|
14
15
|
import { messages } from '../i18n';
|
|
15
16
|
import EmojiErrorMessage from './EmojiErrorMessage';
|
|
@@ -18,6 +19,7 @@ import FileChooser from './FileChooser';
|
|
|
18
19
|
import { UploadStatus } from './internal-types';
|
|
19
20
|
import { fg } from '@atlaskit/platform-feature-flags';
|
|
20
21
|
import FeatureGates from '@atlaskit/feature-gate-js-client';
|
|
22
|
+
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
21
23
|
import Button from '@atlaskit/button/new';
|
|
22
24
|
import { Box } from '@atlaskit/primitives/compiled';
|
|
23
25
|
import { getDocument } from '@atlaskit/browser-apis';
|
|
@@ -86,6 +88,7 @@ const ChooseEmojiFile = /*#__PURE__*/memo(props => {
|
|
|
86
88
|
nameErrorMessage,
|
|
87
89
|
previewImage,
|
|
88
90
|
uploadStatus,
|
|
91
|
+
aiSection,
|
|
89
92
|
intl
|
|
90
93
|
} = props;
|
|
91
94
|
const {
|
|
@@ -135,7 +138,7 @@ const ChooseEmojiFile = /*#__PURE__*/memo(props => {
|
|
|
135
138
|
}, errorMessage && /*#__PURE__*/React.createElement(EmojiErrorMessage, {
|
|
136
139
|
errorStyle: "chooseFile",
|
|
137
140
|
message: errorMessage
|
|
138
|
-
}))), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("label", {
|
|
141
|
+
}))), aiSection, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("label", {
|
|
139
142
|
htmlFor: "new-emoji-name-input",
|
|
140
143
|
className: ax(["_1w90azsu", "_11c8wadc _k48p1pd9"])
|
|
141
144
|
}, /*#__PURE__*/React.createElement(FormattedMessage, messages.emojiNameLabel)), /*#__PURE__*/React.createElement(TextField, {
|
|
@@ -231,6 +234,8 @@ const EmojiUploadPicker = props => {
|
|
|
231
234
|
onFileChooserClicked,
|
|
232
235
|
onUploadCancelled,
|
|
233
236
|
disableFocusLock = false,
|
|
237
|
+
contentId,
|
|
238
|
+
fireAnalytics,
|
|
234
239
|
intl
|
|
235
240
|
} = props;
|
|
236
241
|
const [uploadStatus, setUploadStatus] = useState(errorMessage ? UploadStatus.Error : UploadStatus.Waiting);
|
|
@@ -302,6 +307,16 @@ const EmojiUploadPicker = props => {
|
|
|
302
307
|
const cancelChooseFile = useCallback(() => {
|
|
303
308
|
setPreviewImage(undefined);
|
|
304
309
|
}, []);
|
|
310
|
+
|
|
311
|
+
// When the Rovo section generates an image, feed it into the existing upload
|
|
312
|
+
// form (same preview, name field and "Add emoji" button as a manual upload).
|
|
313
|
+
const onEmojiGenerated = useCallback((dataURL, suggestedName) => {
|
|
314
|
+
setFilename('rovo-emoji.png');
|
|
315
|
+
setPreviewImage(dataURL);
|
|
316
|
+
setChooseEmojiErrorMessage(undefined);
|
|
317
|
+
// Only auto-populate the name if the user hasn't already typed one.
|
|
318
|
+
setName(current => current || sanitizeName(suggestedName));
|
|
319
|
+
}, []);
|
|
305
320
|
const errorOnUpload = useCallback(event => {
|
|
306
321
|
debug('File load error: ', event);
|
|
307
322
|
setChooseEmojiErrorMessage( /*#__PURE__*/React.createElement(FormattedMessage, messages.emojiUploadFailed));
|
|
@@ -364,6 +379,17 @@ const EmojiUploadPicker = props => {
|
|
|
364
379
|
onFileChooserClicked && onFileChooserClicked();
|
|
365
380
|
};
|
|
366
381
|
const isDuplicateNameError = errorMessage !== null && errorMessage !== undefined && isRefreshEmojiPickerEnabled();
|
|
382
|
+
|
|
383
|
+
// "Create an emoji with Rovo" AI generation section. Only rendered when the
|
|
384
|
+
// experiment is on AND a page content id is available (the image generation
|
|
385
|
+
// backend requires it). It is rendered inside ChooseEmojiFile (between the
|
|
386
|
+
// drop area and the Emoji name field) so the generated image reuses the
|
|
387
|
+
// single shared name field and "Add emoji" button.
|
|
388
|
+
const aiSection = contentId && expValEquals('confluence_ai_generated_emojis', 'isEnabled', true) ? /*#__PURE__*/React.createElement(CreateEmojiWithRovo, {
|
|
389
|
+
contentId: contentId,
|
|
390
|
+
fireAnalytics: fireAnalytics,
|
|
391
|
+
onEmojiGenerated: onEmojiGenerated
|
|
392
|
+
}) : null;
|
|
367
393
|
const content = name && previewImage && !isRefreshEmojiPickerEnabled() ? /*#__PURE__*/React.createElement(EmojiUploadPreview, {
|
|
368
394
|
errorMessage: errorMessage,
|
|
369
395
|
name: name,
|
|
@@ -382,6 +408,7 @@ const EmojiUploadPicker = props => {
|
|
|
382
408
|
uploadStatus: uploadStatus,
|
|
383
409
|
errorMessage: chooseEmojiErrorMessage,
|
|
384
410
|
nameErrorMessage: isDuplicateNameError ? errorMessage : undefined,
|
|
411
|
+
aiSection: aiSection,
|
|
385
412
|
intl: intl
|
|
386
413
|
});
|
|
387
414
|
return disableFocusLock || isRefreshEmojiPickerEnabled() ? content : /*#__PURE__*/React.createElement(FocusLock, {
|
|
@@ -100,6 +100,31 @@ export const messages = defineMessages({
|
|
|
100
100
|
defaultMessage: 'Add emoji',
|
|
101
101
|
description: 'Label for the submit button in the custom emoji upload panel that saves the new emoji to the workspace.'
|
|
102
102
|
},
|
|
103
|
+
createEmojiWithRovoTitle: {
|
|
104
|
+
id: 'fabric.emoji.ai.create.title',
|
|
105
|
+
defaultMessage: 'Create an emoji with Rovo',
|
|
106
|
+
description: 'Section heading for the AI emoji generation area where users describe an emoji and generate it with AI.'
|
|
107
|
+
},
|
|
108
|
+
createEmojiWithRovoPromptPlaceholder: {
|
|
109
|
+
id: 'fabric.emoji.ai.prompt.placeholder',
|
|
110
|
+
defaultMessage: 'Describe your emoji...',
|
|
111
|
+
description: 'Placeholder text for the input where the user describes the emoji they want the AI to generate.'
|
|
112
|
+
},
|
|
113
|
+
createEmojiWithRovoPromptAriaLabel: {
|
|
114
|
+
id: 'fabric.emoji.ai.prompt.ariaLabel',
|
|
115
|
+
defaultMessage: 'Describe the emoji you want to generate',
|
|
116
|
+
description: 'Accessible label for the AI emoji description input field.'
|
|
117
|
+
},
|
|
118
|
+
createEmojiWithRovoGenerateLabel: {
|
|
119
|
+
id: 'fabric.emoji.ai.generate.label',
|
|
120
|
+
defaultMessage: 'Generate',
|
|
121
|
+
description: 'Label for the button that triggers AI generation of the described emoji.'
|
|
122
|
+
},
|
|
123
|
+
createEmojiWithRovoError: {
|
|
124
|
+
id: 'fabric.emoji.ai.error',
|
|
125
|
+
defaultMessage: 'Something went wrong. Try a different description.',
|
|
126
|
+
description: 'Inline error message shown when AI emoji generation or upload fails, prompting the user to try again.'
|
|
127
|
+
},
|
|
103
128
|
retryLabel: {
|
|
104
129
|
id: 'fabric.emoji.retry.label',
|
|
105
130
|
defaultMessage: 'Retry',
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
._2rkofajl{border-radius:var(--ds-radius-small,3px)}._16jlkb7n{flex-grow:1}
|
|
5
5
|
._16qs130s{box-shadow:var(--ds-shadow-overlay,0 8px 9pt #1e1f2126,0 0 1px #1e1f214f)}
|
|
6
6
|
._18m915vq{overflow-y:hidden}
|
|
7
|
+
._18m91wug{overflow-y:auto}
|
|
7
8
|
._1bah1yb4{justify-content:space-between}
|
|
8
9
|
._1bsb1edt{width:350px}
|
|
9
10
|
._1e0c1txw{display:flex}
|
|
@@ -27,10 +28,12 @@
|
|
|
27
28
|
._4t3ibqjm{height:310px}
|
|
28
29
|
._4t3iihnn{height:434px}
|
|
29
30
|
._4t3iixjv{height:375px}
|
|
31
|
+
._4t3ikbql{height:450px}
|
|
30
32
|
._4t3iqbeb{height:339px}
|
|
31
33
|
._4t3ivixp{height:349px}
|
|
32
34
|
._4t3ixt2k{height:509px}
|
|
33
35
|
._bfhk1bhr{background-color:var(--ds-surface-overlay,#fff)}
|
|
34
36
|
._c71l1y6z{max-height:calc(80vh - 86px)}
|
|
37
|
+
._c71l8k0t{max-height:calc(100vh - 86px)}
|
|
35
38
|
._i0dlf1ug{flex-basis:0%}
|
|
36
39
|
._kqswh2mm{position:relative}
|
|
@@ -28,6 +28,7 @@ import { useIsMounted } from '../../hooks/useIsMounted';
|
|
|
28
28
|
import { messages } from '../i18n';
|
|
29
29
|
import { defaultProductivityColor, getStoredProductivityColor, storeProductivityColor } from '../../util/productivity-colors';
|
|
30
30
|
import { filterHiddenEmojis } from '../../util/hidden-emojis';
|
|
31
|
+
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
|
|
31
32
|
const isRefreshEmojiPickerEnabled = () => {
|
|
32
33
|
if (!FeatureGates.initializeCompleted()) {
|
|
33
34
|
return false;
|
|
@@ -48,6 +49,17 @@ const heightOffset = 80;
|
|
|
48
49
|
const emojiPicker = null;
|
|
49
50
|
const emojiPickerNew = null;
|
|
50
51
|
const emojiPickerWrapper = null;
|
|
52
|
+
|
|
53
|
+
// When the "Create an emoji with Rovo" section is shown in the upload panel the
|
|
54
|
+
// picker needs more vertical room than the fixed upload height. Make it tall
|
|
55
|
+
// enough to fit the whole section without scrolling, but still let it scroll as
|
|
56
|
+
// a safety net on very short viewports.
|
|
57
|
+
const emojiPickerWrapperScrollable = null;
|
|
58
|
+
|
|
59
|
+
// When the AI emoji experiment is on, grow the upload panel from its default
|
|
60
|
+
// height to 450px so the manual upload form + the slim "Create an emoji with
|
|
61
|
+
// Rovo" section are both visible without scrolling.
|
|
62
|
+
const withAiUploadHeight = null;
|
|
51
63
|
const withPreviewHeight = {
|
|
52
64
|
small: "_4t3ivixp _1tkegx0z",
|
|
53
65
|
medium: "_4t3i2300 _1tke5x59",
|
|
@@ -80,6 +92,7 @@ const EmojiPickerComponent = ({
|
|
|
80
92
|
onPickerRef,
|
|
81
93
|
hideToneSelector,
|
|
82
94
|
createAnalyticsEvent,
|
|
95
|
+
contentId,
|
|
83
96
|
size = defaultEmojiPickerSize
|
|
84
97
|
}) => {
|
|
85
98
|
const {
|
|
@@ -574,19 +587,23 @@ const EmojiPickerComponent = ({
|
|
|
574
587
|
const showPreview = isRefreshEmojiPickerEnabled() ? !uploading : selectedEmoji && !uploading;
|
|
575
588
|
const shouldRenderFooter = showPreview && !(emojiToDelete && isRefreshEmojiPickerEnabled()) && !(query && filteredEmojis.length === 0 && isRefreshEmojiPickerEnabled()) && (Boolean(selectedEmoji) || uploadEnabled || !isRefreshEmojiPickerEnabled());
|
|
576
589
|
const useFooterSpaceForList = showPreview && !selectedEmoji && !uploadEnabled && !emojiToDelete && !(query && filteredEmojis.length === 0) && isRefreshEmojiPickerEnabled();
|
|
590
|
+
|
|
591
|
+
// When the AI emoji section is shown in the upload panel, grow the picker so
|
|
592
|
+
// the section isn't clipped by the fixed upload height.
|
|
593
|
+
const showAiUpload = uploading && !!contentId && expValEquals('confluence_ai_generated_emojis', 'isEnabled', true);
|
|
577
594
|
return /*#__PURE__*/React.createElement("div", {
|
|
578
595
|
ref: setPickerRef,
|
|
579
596
|
"data-emoji-picker-container": true,
|
|
580
597
|
role: "dialog",
|
|
581
598
|
"aria-label": formatMessage(messages.emojiPickerTitle),
|
|
582
599
|
"aria-modal": true,
|
|
583
|
-
className: ax([isRefreshEmojiPickerEnabled() ? "_19itahnd _2rko1mok _1e0c1txw _2lx21bp4 _1bah1yb4 _bfhk1bhr _16qs130s _4t3iaq3k _1bsb1edt _1ul91edt _c71l1y6z _kqswh2mm" : "_19itahnd _2rkofajl _1e0c1txw _2lx21bp4 _1bah1yb4 _bfhk1bhr _16qs130s _4t3iaq3k _1bsb1edt _1ul91edt _c71l1y6z _kqswh2mm", !!emojiToDelete && isRefreshEmojiPickerEnabled() ? withDeleteRefreshHeight[size] : uploading && isRefreshEmojiPickerEnabled() ? withUploadRefreshHeight[size] : query && filteredEmojis.length === 0 && isRefreshEmojiPickerEnabled() ? withNoResultsRefreshHeight[size] : showPreview ? withPreviewHeight[size] : withoutPreviewHeight[size]])
|
|
600
|
+
className: ax([isRefreshEmojiPickerEnabled() ? "_19itahnd _2rko1mok _1e0c1txw _2lx21bp4 _1bah1yb4 _bfhk1bhr _16qs130s _4t3iaq3k _1bsb1edt _1ul91edt _c71l1y6z _kqswh2mm" : "_19itahnd _2rkofajl _1e0c1txw _2lx21bp4 _1bah1yb4 _bfhk1bhr _16qs130s _4t3iaq3k _1bsb1edt _1ul91edt _c71l1y6z _kqswh2mm", !!emojiToDelete && isRefreshEmojiPickerEnabled() ? withDeleteRefreshHeight[size] : uploading && isRefreshEmojiPickerEnabled() ? withUploadRefreshHeight[size] : query && filteredEmojis.length === 0 && isRefreshEmojiPickerEnabled() ? withNoResultsRefreshHeight[size] : showPreview ? withPreviewHeight[size] : withoutPreviewHeight[size], showAiUpload && "_4t3ikbql _c71l8k0t"])
|
|
584
601
|
}, /*#__PURE__*/React.createElement("div", {
|
|
585
602
|
role: "presentation",
|
|
586
603
|
onKeyPress: suppressKeyPress,
|
|
587
604
|
onKeyUp: suppressKeyPress,
|
|
588
605
|
onKeyDown: suppressKeyPress,
|
|
589
|
-
className: ax(["_16jlkb7n _1o9zkb7n _i0dlf1ug _1reo15vq _18m915vq _1e0c1txw _2lx21bp4 _1bah1yb4 _1tkeidpf"])
|
|
606
|
+
className: ax(["_16jlkb7n _1o9zkb7n _i0dlf1ug _1reo15vq _18m915vq _1e0c1txw _2lx21bp4 _1bah1yb4 _1tkeidpf", showAiUpload && "_1reo15vq _18m91wug"])
|
|
590
607
|
}, /*#__PURE__*/React.createElement(CategorySelector, {
|
|
591
608
|
activeCategoryId: (uploading || emojiToDelete) && isRefreshEmojiPickerEnabled() ? null : activeCategory,
|
|
592
609
|
dynamicCategories: dynamicCategories,
|
|
@@ -623,7 +640,9 @@ const EmojiPickerComponent = ({
|
|
|
623
640
|
onOpenUpload: onOpenUpload,
|
|
624
641
|
size: size,
|
|
625
642
|
activeCategoryId: activeCategory,
|
|
626
|
-
useFooterSpaceForList: useFooterSpaceForList
|
|
643
|
+
useFooterSpaceForList: useFooterSpaceForList,
|
|
644
|
+
contentId: contentId,
|
|
645
|
+
fireAnalytics: fireAnalytics
|
|
627
646
|
}), shouldRenderFooter && /*#__PURE__*/React.createElement(EmojiPickerFooter, {
|
|
628
647
|
selectedEmoji: selectedEmoji,
|
|
629
648
|
uploadEnabled: uploadEnabled,
|
|
@@ -77,7 +77,9 @@ export const EmojiPickerVirtualListInternal = /*#__PURE__*/React.forwardRef((pro
|
|
|
77
77
|
onProductivityColorSelected,
|
|
78
78
|
activeCategoryId,
|
|
79
79
|
selectedProductivityColor,
|
|
80
|
-
useFooterSpaceForList
|
|
80
|
+
useFooterSpaceForList,
|
|
81
|
+
contentId,
|
|
82
|
+
fireAnalytics
|
|
81
83
|
} = props;
|
|
82
84
|
const {
|
|
83
85
|
formatMessage
|
|
@@ -468,7 +470,9 @@ export const EmojiPickerVirtualListInternal = /*#__PURE__*/React.forwardRef((pro
|
|
|
468
470
|
onProductivityColorSelected: isTeamojiExperimentEnabled ? onProductivityColorSelected : undefined,
|
|
469
471
|
query: query,
|
|
470
472
|
onChange: onSearch,
|
|
471
|
-
resultsCount: visibleEmojis.length
|
|
473
|
+
resultsCount: visibleEmojis.length,
|
|
474
|
+
contentId: contentId,
|
|
475
|
+
fireAnalytics: fireAnalytics
|
|
472
476
|
}), /*#__PURE__*/React.createElement(EmojiPickerListContextProvider, {
|
|
473
477
|
initialEmojisFocus: {
|
|
474
478
|
rowIndex: 1,
|
|
@@ -18,7 +18,8 @@ const EmojiUploadComponent = props => {
|
|
|
18
18
|
emojiProvider,
|
|
19
19
|
createAnalyticsEvent,
|
|
20
20
|
onUploaderRef,
|
|
21
|
-
disableFocusLock
|
|
21
|
+
disableFocusLock,
|
|
22
|
+
contentId
|
|
22
23
|
} = props;
|
|
23
24
|
const [uploadErrorMessage, setUploadErrorMessage] = useState();
|
|
24
25
|
useEffect(() => {
|
|
@@ -91,7 +92,9 @@ const EmojiUploadComponent = props => {
|
|
|
91
92
|
onUploadCancelled: onUploadCancelled,
|
|
92
93
|
onUploadEmoji: onUploadEmoji,
|
|
93
94
|
errorMessage: uploadErrorMessage ? /*#__PURE__*/React.createElement(FormattedMessage, uploadErrorMessage) : null,
|
|
94
|
-
disableFocusLock: disableFocusLock
|
|
95
|
+
disableFocusLock: disableFocusLock,
|
|
96
|
+
contentId: contentId,
|
|
97
|
+
fireAnalytics: fireAnalytics
|
|
95
98
|
})));
|
|
96
99
|
};
|
|
97
100
|
const _default_1 = /*#__PURE__*/memo(EmojiUploadComponent);
|