@pushwoosh/smart-components-kit 1.12.0 → 1.14.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.
@@ -1,12 +1,14 @@
1
1
  import { type FC } from 'react';
2
2
  import type { MediaFile } from '@pushwoosh/rpc-v2-http-api-data/media_files';
3
+ import { type PromptContext } from './aiGallery';
3
4
  type GenerateImageTabProps = {
4
5
  application: string;
5
6
  aspectRatios?: string[];
6
7
  defaultAspectRatio?: string;
8
+ promptContext?: PromptContext;
7
9
  onSelect: (file: MediaFile, alt: string) => void;
8
10
  onGenerated: () => void;
9
11
  };
10
- /** Prompt → one image that onyx stores in the library; the preview is already the stored file. */
12
+ /** Prompt (+ optional message context) → 1–4 variants that onyx stores in the library; each preview is already a library file. */
11
13
  export declare const GenerateImageTab: FC<GenerateImageTabProps>;
12
14
  export {};
@@ -1,11 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from 'react';
3
- import { Button, NativeSelect } from '@pushwoosh/dumb-components';
3
+ import { Button, Checkbox, NativeSelect } from '@pushwoosh/dumb-components';
4
4
  import { useSPAContext } from '@pushwoosh/spa-context';
5
- import { GENERATE_ASPECT_RATIOS, toMediaFile } from './aiGallery';
5
+ import { GENERATE_ASPECT_RATIOS, GENERATE_VARIANT_COUNTS, storedVariants } from './aiGallery';
6
6
  import { useErrorToast } from './hooks';
7
7
  import { useLang } from './polyglot';
8
- import { GenerateControls, GeneratedPreview, GenerateForm, PromptField } from './styles';
8
+ import { GenerateControls, GeneratedGrid, GeneratedPreview, GeneratedVariant, GenerateForm, PromptField } from './styles';
9
9
  const ASPECT_LABELS = {
10
10
  '1:1': 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_SQUARE',
11
11
  '4:3': 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_LANDSCAPE',
@@ -19,11 +19,12 @@ const generateButtonMessage = (busy, hasResult) => {
19
19
  if (busy) return 'SMART.MEDIA_STORAGE.GENERATE.GENERATING';
20
20
  return hasResult ? 'SMART.MEDIA_STORAGE.GENERATE.AGAIN' : 'SMART.MEDIA_STORAGE.GENERATE.BUTTON';
21
21
  };
22
- /** Prompt → one image that onyx stores in the library; the preview is already the stored file. */
22
+ /** Prompt (+ optional message context) → 1–4 variants that onyx stores in the library; each preview is already a library file. */
23
23
  export const GenerateImageTab = ({
24
24
  application,
25
25
  aspectRatios,
26
26
  defaultAspectRatio,
27
+ promptContext,
27
28
  onSelect,
28
29
  onGenerated
29
30
  }) => {
@@ -36,6 +37,9 @@ export const GenerateImageTab = ({
36
37
  const [prompt, setPrompt] = useState('');
37
38
  const [aspect, setAspect] = useState(defaultAspectRatio && ratios.includes(defaultAspectRatio) ? defaultAspectRatio : ratios[0]);
38
39
  const [busy, setBusy] = useState(false);
40
+ const [count, setCount] = useState(1);
41
+ const hasContext = Boolean(promptContext?.title?.trim() || promptContext?.body?.trim());
42
+ const [useContext, setUseContext] = useState(true);
39
43
  const [generated, setGenerated] = useState(null);
40
44
  const aspectLabel = ratio => {
41
45
  return isLabelledAspect(ratio) ? lang({
@@ -48,13 +52,19 @@ export const GenerateImageTab = ({
48
52
  setGenerated(null);
49
53
  setBusy(true);
50
54
  try {
51
- const res = await rpcGatewayAIAssistant.aiGen.GenerateMediaImage({
55
+ const request = {
52
56
  application,
53
57
  prompt: text,
54
- aspectRatio: aspect
55
- });
58
+ aspectRatio: aspect,
59
+ count,
60
+ ...(hasContext && useContext ? {
61
+ contextTitle: promptContext?.title ?? '',
62
+ contextBody: promptContext?.body ?? ''
63
+ } : {})
64
+ };
65
+ const res = await rpcGatewayAIAssistant.aiGen.GenerateMediaImage(request);
56
66
  setGenerated({
57
- file: toMediaFile(res, 'ai', `${res.mediaUuid}.png`),
67
+ files: storedVariants(res),
58
68
  alt: text
59
69
  });
60
70
  onGenerated();
@@ -80,6 +90,16 @@ export const GenerateImageTab = ({
80
90
  value: ratio,
81
91
  children: aspectLabel(ratio)
82
92
  }, ratio))
93
+ }), _jsx(NativeSelect, {
94
+ value: count,
95
+ onChange: event => setCount(Number(event.target.value)),
96
+ children: GENERATE_VARIANT_COUNTS.map(n => _jsx("option", {
97
+ value: n,
98
+ children: lang({
99
+ message: 'SMART.MEDIA_STORAGE.GENERATE.VARIANTS',
100
+ count: n
101
+ })
102
+ }, n))
83
103
  }), _jsx(Button, {
84
104
  color: "primary",
85
105
  size: "field",
@@ -88,17 +108,28 @@ export const GenerateImageTab = ({
88
108
  children: lang({
89
109
  message: generateButtonMessage(busy, generated !== null)
90
110
  })
91
- }), generated && _jsx(Button, {
92
- color: "secondary",
93
- size: "field",
94
- onClick: () => onSelect(generated.file, generated.alt),
95
- children: lang({
96
- message: 'SMART.MEDIA_STORAGE.GENERATE.SELECT'
97
- })
98
111
  })]
99
- }), generated && _jsx(GeneratedPreview, {
100
- src: generated.file.url,
101
- alt: generated.alt
112
+ }), hasContext && _jsx(Checkbox, {
113
+ label: lang({
114
+ message: 'SMART.MEDIA_STORAGE.GENERATE.USE_CONTEXT'
115
+ }),
116
+ isChecked: useContext,
117
+ onChange: () => setUseContext(value => !value)
118
+ }), generated && _jsx(GeneratedGrid, {
119
+ "$single": generated.files.length === 1,
120
+ children: generated.files.map(file => _jsxs(GeneratedVariant, {
121
+ children: [_jsx(GeneratedPreview, {
122
+ src: file.url,
123
+ alt: generated.alt
124
+ }), _jsx(Button, {
125
+ color: "secondary",
126
+ size: "field",
127
+ onClick: () => onSelect(file, generated.alt),
128
+ children: lang({
129
+ message: 'SMART.MEDIA_STORAGE.GENERATE.SELECT'
130
+ })
131
+ })]
132
+ }, file.uuid))
102
133
  })]
103
134
  });
104
135
  };
@@ -14,7 +14,7 @@ import { useCanvaEdit, useDeleteMediaFile, useDeleteMediaFiles } from './hooks';
14
14
  import { formatBytes, getCropCaption, getSourceLabel, getThumbUrl, hasVersions } from './mediaFile.utils';
15
15
  import { Lang, useLang } from './polyglot';
16
16
  import { StockPhotosTab } from './StockPhotosTab';
17
- import { CanvaActions, CountBadge, Dropzone, DropzoneRow, EmptyState, Grid, HiddenInput, SelectionBar, SelectionCount, SourceBadge, TabBody, Tile, TileActions, TileBadges, TileCheckbox, TileImage, UsageLine, WarningBadge } from './styles';
17
+ import { CanvaActions, CountBadge, Dropzone, DropzoneRow, EmptyState, Grid, HiddenInput, SelectionBar, SearchField, SearchRow, SelectionCount, SourceBadge, TabBody, Tile, TileActions, TileBadges, TileCheckbox, TileImage, UsageLine, WarningBadge } from './styles';
18
18
  const UPLOAD_PATH = '/api/media/upload';
19
19
  const ALLOWED_IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp'];
20
20
  const TAB_MESSAGES = {
@@ -42,6 +42,7 @@ export const MediaStorage = ({
42
42
  sources,
43
43
  aspectRatios,
44
44
  defaultAspectRatio,
45
+ promptContext,
45
46
  onSuggestAlt
46
47
  }) => {
47
48
  const lang = useLang();
@@ -76,6 +77,8 @@ export const MediaStorage = ({
76
77
  const [selectedUuids, setSelectedUuids] = useState([]);
77
78
  const [isBulkDeleteOpen, setIsBulkDeleteOpen] = useState(false);
78
79
  const [isBulkDeleting, setIsBulkDeleting] = useState(false);
80
+ const [search, setSearch] = useState('');
81
+ const [searchDraft, setSearchDraft] = useState('');
79
82
  // Format filtering belongs to the query, not the response: `total` and the page count come
80
83
  // from the filtered set, so a client-side filter would paginate over files it then hides.
81
84
  const listAsync = useAsync(async () => {
@@ -83,14 +86,15 @@ export const MediaStorage = ({
83
86
  application,
84
87
  page,
85
88
  perPage,
86
- extensions
89
+ extensions,
90
+ searchByName: search || undefined
87
91
  });
88
92
  // protobuf JSON omits empty `repeated` fields, so `files` can be undefined.
89
93
  return {
90
94
  files: response.files ?? [],
91
95
  total: response.total
92
96
  };
93
- }, [rpcGateway, application, page, perPage, extensions, reloadToken]);
97
+ }, [rpcGateway, application, page, perPage, extensions, search, reloadToken]);
94
98
  const usageAsync = useAsync(() => rpcGateway.mediaFiles.GetUsage({
95
99
  application
96
100
  }), [rpcGateway, application, reloadToken, usageToken]);
@@ -174,6 +178,14 @@ export const MediaStorage = ({
174
178
  cancelled = true;
175
179
  };
176
180
  }, [listAsync.value, rpcGateway, application, extensions]);
181
+ const applySearch = value => {
182
+ if (value === search) return;
183
+ setSearch(value);
184
+ if (pageRef.current !== 0) {
185
+ onPageChange(0);
186
+ }
187
+ };
188
+ const commitSearch = () => applySearch(searchDraft.trim());
177
189
  const uploadFiles = async selected => {
178
190
  if (selected.length === 0) return;
179
191
  const images = selected.filter(isAllowedImage);
@@ -346,6 +358,24 @@ export const MediaStorage = ({
346
358
  accept: ALLOWED_IMAGE_EXTENSIONS.join(','),
347
359
  multiple: true,
348
360
  onChange: event => void uploadFiles(Array.from(event.target.files ?? []))
361
+ }), _jsx(SearchRow, {
362
+ children: _jsx(SearchField, {
363
+ type: "search",
364
+ value: searchDraft,
365
+ placeholder: lang({
366
+ message: 'SMART.MEDIA_STORAGE.SEARCH_PLACEHOLDER'
367
+ }),
368
+ onChange: event => {
369
+ setSearchDraft(event.target.value);
370
+ if (event.target.value === '') {
371
+ applySearch('');
372
+ }
373
+ },
374
+ onKeyDown: event => {
375
+ if (event.key === 'Enter') commitSearch();
376
+ },
377
+ onBlur: commitSearch
378
+ })
349
379
  }), loading && _jsx(EmptyState, {
350
380
  children: lang({
351
381
  message: 'SMART.MEDIA_STORAGE.LOADING'
@@ -563,6 +593,7 @@ export const MediaStorage = ({
563
593
  application: application,
564
594
  aspectRatios: aspectRatios,
565
595
  defaultAspectRatio: defaultAspectRatio,
596
+ promptContext: promptContext,
566
597
  onGenerated: showImportedFirst,
567
598
  onSelect: pickStored
568
599
  })
@@ -22,7 +22,15 @@ export type AiGenStoredImage = {
22
22
  width: number;
23
23
  height: number;
24
24
  file?: AiGenMediaFile;
25
+ files?: AiGenMediaFile[];
25
26
  };
27
+ /** The message an image is generated for; onyx draws for it and keeps its text out of the picture. */
28
+ export type PromptContext = {
29
+ title?: string;
30
+ body?: string;
31
+ };
32
+ /** Variant counts the Generate tab offers; onyx caps a request at 4 and charges each one. */
33
+ export declare const GENERATE_VARIANT_COUNTS: number[];
26
34
  /** Aspect ratios onyx accepts for a generated image, in picker order. */
27
35
  export declare const GENERATE_ASPECT_RATIOS: string[];
28
36
  /** One Pexels hit from `aiGen.SearchStockPhotos`. */
@@ -47,3 +55,5 @@ export declare const isStockUnconfigured: (error: unknown) => boolean;
47
55
  export declare const visibleSources: (sources: MediaSource[] | undefined, extensions: string[] | undefined) => MediaSource[];
48
56
  /** Media-library file for an image onyx just stored, so `onSelect` gets the same shape as a library pick. */
49
57
  export declare const toMediaFile: (stored: AiGenStoredImage, source: "ai" | "stock", fallbackName: string) => MediaFile;
58
+ /** Every image a Generate call stored, as library files; older onyx returns a single image. */
59
+ export declare const storedVariants: (res: AiGenStoredImage) => MediaFile[];
@@ -1,3 +1,5 @@
1
+ /** Variant counts the Generate tab offers; onyx caps a request at 4 and charges each one. */
2
+ export const GENERATE_VARIANT_COUNTS = [1, 2, 4];
1
3
  /** Aspect ratios onyx accepts for a generated image, in picker order. */
2
4
  export const GENERATE_ASPECT_RATIOS = ['1:1', '4:3', '3:2', '16:9', '21:9', '5:4', '4:5', '3:4', '2:3', '9:16'];
3
5
  /** Longest stock query onyx accepts (`stockphotos.MaxQueryChars`); capping the input keeps 400 meaning "not configured". */
@@ -52,4 +54,17 @@ export const toMediaFile = (stored, source, fallbackName) => {
52
54
  }
53
55
  }
54
56
  };
57
+ };
58
+ /** Every image a Generate call stored, as library files; older onyx returns a single image. */
59
+ export const storedVariants = res => {
60
+ if (!res.files?.length) {
61
+ return [toMediaFile(res, 'ai', `${res.mediaUuid}.png`)];
62
+ }
63
+ return res.files.map(file => toMediaFile({
64
+ url: file.url,
65
+ mediaUuid: file.uuid,
66
+ width: file.width,
67
+ height: file.height,
68
+ file
69
+ }, 'ai', file.filename));
55
70
  };
@@ -105,5 +105,8 @@ export const messages = {
105
105
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_WIDE': 'عريض (16:9)',
106
106
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_BANNER': 'بانر (21:9)',
107
107
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_PORTRAIT': 'بورتريه (3:4)',
108
- 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'طويل (9:16)'
108
+ 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'طويل (9:16)',
109
+ 'SMART.MEDIA_STORAGE.SEARCH_PLACEHOLDER': 'ابحث باسم الملف…',
110
+ 'SMART.MEDIA_STORAGE.GENERATE.USE_CONTEXT': 'الرسم وفق عنوان الرسالة ونصها',
111
+ 'SMART.MEDIA_STORAGE.GENERATE.VARIANTS': '{{count}} × صورة'
109
112
  };
@@ -105,5 +105,8 @@ export const messages = {
105
105
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_WIDE': 'চওড়া (16:9)',
106
106
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_BANNER': 'ব্যানার (21:9)',
107
107
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_PORTRAIT': 'পোর্ট্রেট (3:4)',
108
- 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'লম্বা (9:16)'
108
+ 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'লম্বা (9:16)',
109
+ 'SMART.MEDIA_STORAGE.SEARCH_PLACEHOLDER': 'ফাইলের নাম দিয়ে অনুসন্ধান করুন…',
110
+ 'SMART.MEDIA_STORAGE.GENERATE.USE_CONTEXT': 'বার্তার শিরোনাম ও টেক্সট অনুযায়ী আঁকুন',
111
+ 'SMART.MEDIA_STORAGE.GENERATE.VARIANTS': '{{count}} × ছবি'
109
112
  };
@@ -105,5 +105,8 @@ export const messages = {
105
105
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_WIDE': 'Breit (16:9)',
106
106
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_BANNER': 'Banner (21:9)',
107
107
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_PORTRAIT': 'Hochformat (3:4)',
108
- 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'Hoch (9:16)'
108
+ 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'Hoch (9:16)',
109
+ 'SMART.MEDIA_STORAGE.SEARCH_PLACEHOLDER': 'Nach Dateinamen suchen…',
110
+ 'SMART.MEDIA_STORAGE.GENERATE.USE_CONTEXT': 'Passend zu Titel und Text der Nachricht zeichnen',
111
+ 'SMART.MEDIA_STORAGE.GENERATE.VARIANTS': '{{count}} × Bild'
109
112
  };
@@ -105,5 +105,8 @@ export const messages = {
105
105
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_WIDE': 'Wide (16:9)',
106
106
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_BANNER': 'Banner (21:9)',
107
107
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_PORTRAIT': 'Portrait (3:4)',
108
- 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'Tall (9:16)'
108
+ 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'Tall (9:16)',
109
+ 'SMART.MEDIA_STORAGE.SEARCH_PLACEHOLDER': 'Search by file name…',
110
+ 'SMART.MEDIA_STORAGE.GENERATE.USE_CONTEXT': 'Draw for the message title and text',
111
+ 'SMART.MEDIA_STORAGE.GENERATE.VARIANTS': '{{count}} × image'
109
112
  };
@@ -105,5 +105,8 @@ export const messages = {
105
105
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_WIDE': 'Ancho (16:9)',
106
106
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_BANNER': 'Banner (21:9)',
107
107
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_PORTRAIT': 'Vertical (3:4)',
108
- 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'Alto (9:16)'
108
+ 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'Alto (9:16)',
109
+ 'SMART.MEDIA_STORAGE.SEARCH_PLACEHOLDER': 'Buscar por nombre de archivo…',
110
+ 'SMART.MEDIA_STORAGE.GENERATE.USE_CONTEXT': 'Dibujar según el título y el texto del mensaje',
111
+ 'SMART.MEDIA_STORAGE.GENERATE.VARIANTS': '{{count}} × imagen'
109
112
  };
@@ -105,5 +105,8 @@ export const messages = {
105
105
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_WIDE': 'Large (16:9)',
106
106
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_BANNER': 'Bannière (21:9)',
107
107
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_PORTRAIT': 'Portrait (3:4)',
108
- 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'Haut (9:16)'
108
+ 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'Haut (9:16)',
109
+ 'SMART.MEDIA_STORAGE.SEARCH_PLACEHOLDER': 'Rechercher par nom de fichier…',
110
+ 'SMART.MEDIA_STORAGE.GENERATE.USE_CONTEXT': 'Illustrer le titre et le texte du message',
111
+ 'SMART.MEDIA_STORAGE.GENERATE.VARIANTS': '{{count}} × image'
109
112
  };
@@ -105,5 +105,8 @@ export const messages = {
105
105
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_WIDE': 'רחב (16:9)',
106
106
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_BANNER': 'באנר (21:9)',
107
107
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_PORTRAIT': 'לאורך (3:4)',
108
- 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'גבוה (9:16)'
108
+ 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'גבוה (9:16)',
109
+ 'SMART.MEDIA_STORAGE.SEARCH_PLACEHOLDER': 'חיפוש לפי שם קובץ…',
110
+ 'SMART.MEDIA_STORAGE.GENERATE.USE_CONTEXT': 'לצייר לפי הכותרת והטקסט של ההודעה',
111
+ 'SMART.MEDIA_STORAGE.GENERATE.VARIANTS': '{{count}} × תמונה'
109
112
  };
@@ -105,5 +105,8 @@ export const messages = {
105
105
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_WIDE': 'वाइड (16:9)',
106
106
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_BANNER': 'बैनर (21:9)',
107
107
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_PORTRAIT': 'पोर्ट्रेट (3:4)',
108
- 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'टॉल (9:16)'
108
+ 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'टॉल (9:16)',
109
+ 'SMART.MEDIA_STORAGE.SEARCH_PLACEHOLDER': 'फ़ाइल नाम से खोजें…',
110
+ 'SMART.MEDIA_STORAGE.GENERATE.USE_CONTEXT': 'संदेश के शीर्षक और टेक्स्ट के अनुसार बनाएं',
111
+ 'SMART.MEDIA_STORAGE.GENERATE.VARIANTS': '{{count}} × छवि'
109
112
  };
@@ -105,5 +105,8 @@ export const messages = {
105
105
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_WIDE': 'ワイド (16:9)',
106
106
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_BANNER': 'バナー (21:9)',
107
107
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_PORTRAIT': '縦長 (3:4)',
108
- 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': '縦長 (9:16)'
108
+ 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': '縦長 (9:16)',
109
+ 'SMART.MEDIA_STORAGE.SEARCH_PLACEHOLDER': 'ファイル名で検索…',
110
+ 'SMART.MEDIA_STORAGE.GENERATE.USE_CONTEXT': 'メッセージのタイトルと本文に合わせて描く',
111
+ 'SMART.MEDIA_STORAGE.GENERATE.VARIANTS': '{{count}} × 画像'
109
112
  };
@@ -105,5 +105,8 @@ export const messages = {
105
105
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_WIDE': '와이드 (16:9)',
106
106
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_BANNER': '배너 (21:9)',
107
107
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_PORTRAIT': '세로 (3:4)',
108
- 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': '세로형 (9:16)'
108
+ 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': '세로형 (9:16)',
109
+ 'SMART.MEDIA_STORAGE.SEARCH_PLACEHOLDER': '파일 이름으로 검색…',
110
+ 'SMART.MEDIA_STORAGE.GENERATE.USE_CONTEXT': '메시지 제목과 본문에 맞춰 그리기',
111
+ 'SMART.MEDIA_STORAGE.GENERATE.VARIANTS': '{{count}} × 이미지'
109
112
  };
@@ -105,5 +105,8 @@ export const messages = {
105
105
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_WIDE': 'Largo (16:9)',
106
106
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_BANNER': 'Banner (21:9)',
107
107
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_PORTRAIT': 'Retrato (3:4)',
108
- 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'Alto (9:16)'
108
+ 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'Alto (9:16)',
109
+ 'SMART.MEDIA_STORAGE.SEARCH_PLACEHOLDER': 'Buscar por nome do arquivo…',
110
+ 'SMART.MEDIA_STORAGE.GENERATE.USE_CONTEXT': 'Desenhar para o título e o texto da mensagem',
111
+ 'SMART.MEDIA_STORAGE.GENERATE.VARIANTS': '{{count}} × imagem'
109
112
  };
@@ -105,5 +105,8 @@ export const messages = {
105
105
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_WIDE': 'Широкая (16:9)',
106
106
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_BANNER': 'Баннер (21:9)',
107
107
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_PORTRAIT': 'Портретная (3:4)',
108
- 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'Вертикальная (9:16)'
108
+ 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'Вертикальная (9:16)',
109
+ 'SMART.MEDIA_STORAGE.SEARCH_PLACEHOLDER': 'Поиск по имени файла…',
110
+ 'SMART.MEDIA_STORAGE.GENERATE.USE_CONTEXT': 'Рисовать под заголовок и текст сообщения',
111
+ 'SMART.MEDIA_STORAGE.GENERATE.VARIANTS': '{{count}} × картинка'
109
112
  };
@@ -105,5 +105,8 @@ export const messages = {
105
105
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_WIDE': 'แนวนอนกว้าง (16:9)',
106
106
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_BANNER': 'แบนเนอร์ (21:9)',
107
107
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_PORTRAIT': 'แนวตั้ง (3:4)',
108
- 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'แนวตั้งสูง (9:16)'
108
+ 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': 'แนวตั้งสูง (9:16)',
109
+ 'SMART.MEDIA_STORAGE.SEARCH_PLACEHOLDER': 'ค้นหาตามชื่อไฟล์…',
110
+ 'SMART.MEDIA_STORAGE.GENERATE.USE_CONTEXT': 'วาดตามหัวข้อและข้อความของข้อความ',
111
+ 'SMART.MEDIA_STORAGE.GENERATE.VARIANTS': '{{count}} × รูปภาพ'
109
112
  };
@@ -105,5 +105,8 @@ export const messages = {
105
105
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_WIDE': '宽 (16:9)',
106
106
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_BANNER': '横幅 (21:9)',
107
107
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_PORTRAIT': '纵向 (3:4)',
108
- 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': '高 (9:16)'
108
+ 'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': '高 (9:16)',
109
+ 'SMART.MEDIA_STORAGE.SEARCH_PLACEHOLDER': '按文件名搜索…',
110
+ 'SMART.MEDIA_STORAGE.GENERATE.USE_CONTEXT': '根据消息标题和正文绘制',
111
+ 'SMART.MEDIA_STORAGE.GENERATE.VARIANTS': '{{count}} × 图片'
109
112
  };
@@ -140,4 +140,9 @@ export type MediaStorageMessages = {
140
140
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_BANNER': NoParams;
141
141
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_PORTRAIT': NoParams;
142
142
  'SMART.MEDIA_STORAGE.GENERATE.ASPECT_TALL': NoParams;
143
+ 'SMART.MEDIA_STORAGE.SEARCH_PLACEHOLDER': NoParams;
144
+ 'SMART.MEDIA_STORAGE.GENERATE.USE_CONTEXT': NoParams;
145
+ 'SMART.MEDIA_STORAGE.GENERATE.VARIANTS': {
146
+ count: number;
147
+ };
143
148
  };
@@ -164,3 +164,11 @@ export declare const PromptField: import("styled-components/dist/types").IStyled
164
164
  [key: `--${string}`]: string | number | undefined;
165
165
  }) | undefined;
166
166
  }> & string;
167
+ export declare const GeneratedGrid: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Merged<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style"> & {
168
+ style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
169
+ }, {
170
+ $single?: boolean;
171
+ }>> & string;
172
+ export declare const GeneratedVariant: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style"> & {
173
+ style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
174
+ }> & string;
@@ -187,4 +187,14 @@ export const SearchField = styled(NativeInput).withConfig({
187
187
  export const PromptField = styled(NativeTextarea).withConfig({
188
188
  displayName: "PromptField",
189
189
  componentId: "sc-uu3cbo-40"
190
- })(["font-family:inherit;resize:vertical;"]);
190
+ })(["font-family:inherit;resize:vertical;"]);
191
+ export const GeneratedGrid = styled.div.withConfig({
192
+ displayName: "GeneratedGrid",
193
+ componentId: "sc-uu3cbo-41"
194
+ })(["display:grid;grid-template-columns:", ";gap:16px;"], ({
195
+ $single
196
+ }) => $single ? '1fr' : 'repeat(2, minmax(0, 1fr))');
197
+ export const GeneratedVariant = styled.div.withConfig({
198
+ displayName: "GeneratedVariant",
199
+ componentId: "sc-uu3cbo-42"
200
+ })(["display:flex;flex-direction:column;align-items:flex-start;gap:8px;"]);
@@ -24,6 +24,11 @@ type MediaFileChannelOptions = {
24
24
  aspectRatios?: string[];
25
25
  /** Pre-selected aspect ratio in the Generate tab. */
26
26
  defaultAspectRatio?: string;
27
+ /** Title and text of the message being composed; Generate offers to draw for it. */
28
+ promptContext?: {
29
+ title?: string;
30
+ body?: string;
31
+ };
27
32
  /** Alt text suggested for a stock or generated pick (provider alt or the prompt). */
28
33
  onSuggestAlt?: (file: MediaFile, alt: string) => void;
29
34
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushwoosh/smart-components-kit",
3
- "version": "1.12.0",
3
+ "version": "1.14.0",
4
4
  "description": "Stateful, data-connected React components shared by Pushwoosh micro-frontends",
5
5
  "repository": {
6
6
  "type": "git",