Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
377755c
Create a hook for getting the SIG preview properly
gmjuhasz Nov 25, 2025
288f746
Move utils from GeneratedImagePreview to the newly created hook
gmjuhasz Nov 25, 2025
a3349ae
Add tests for SIG hook
gmjuhasz Nov 26, 2025
3556045
changelog
gmjuhasz Nov 26, 2025
cce8eca
Refactor GeneratedImagePreview to use the useSigPreview hook
gmjuhasz Nov 26, 2025
b1f4d19
Add new media section and preview + dropdown components
gmjuhasz Nov 25, 2025
2f4d636
changelog
gmjuhasz Nov 25, 2025
60686c9
Hook up components + connect with new feature flag
gmjuhasz Nov 25, 2025
fd4c016
Hook up SIG preview hook with the new component
gmjuhasz Nov 25, 2025
6df34c2
Move utils from GeneratedImagePreview to the newly created hook
gmjuhasz Nov 25, 2025
dc44786
Update styling
gmjuhasz Nov 26, 2025
a8a9871
Alignment changes
gmjuhasz Nov 26, 2025
1c772d3
Merge branch 'trunk' into add/social/new-media-selector-ui
gmjuhasz Nov 26, 2025
54e8787
Merge branch 'trunk' into add/social/new-media-selector-ui
gmjuhasz Nov 27, 2025
ea204e0
Fix race conditions with changes
gmjuhasz Nov 27, 2025
814bcf4
Add tests
gmjuhasz Nov 27, 2025
dd1b671
Merge branch 'trunk' into add/social/new-media-selector-ui
gmjuhasz Nov 28, 2025
a00ce2a
Refactor logic + add extra toggle
gmjuhasz Dec 1, 2025
3df3acd
changelog
gmjuhasz Dec 1, 2025
3db835c
Handle empty featured image
gmjuhasz Dec 1, 2025
2fd68b0
Fix test
gmjuhasz Dec 1, 2025
d631ef2
Merge branch 'trunk' into add/social/new-media-selector-ui
gmjuhasz Dec 1, 2025
9ef10a3
Reuse GeneralPurposeImage component in the social sidebar
gmjuhasz Dec 1, 2025
6e61216
Merge branch 'trunk' into add/social/ai-image-generation-modal-poc
gmjuhasz Dec 2, 2025
0876eee
changelog
gmjuhasz Dec 2, 2025
466133f
Use new naming
gmjuhasz Dec 2, 2025
1fd994b
Fix AI image selection after generation
gmjuhasz Dec 4, 2025
174e635
If url is available use that for preview
gmjuhasz Dec 4, 2025
a394ac1
Revert unintended change
gmjuhasz Dec 4, 2025
7c66dd2
Merge branch 'trunk' into add/social/ai-image-generation-modal-poc
gmjuhasz Dec 4, 2025
afefa38
Fix type declarations
gmjuhasz Dec 4, 2025
18e31a4
Fix minification issue
gmjuhasz Dec 4, 2025
e8b184a
changelog
gmjuhasz Dec 4, 2025
e4d1448
Simplify logic + add comment
gmjuhasz Dec 8, 2025
efc7a83
Extend image type
gmjuhasz Dec 8, 2025
24f3b23
Rename mime_type to mime and use WPMediaObject
gmjuhasz Dec 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fixed

Fixed minification issue with production build.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import type { ReactElement } from 'react';
type SetImageCallbackProps = {
id: number;
url: string;
mime?: string;
};

type GeneralPurposeImageProps = {
Expand Down Expand Up @@ -214,8 +215,8 @@ export default function GeneralPurposeImage( {
site_type: siteType,
} );

const setImage = image => {
onSetImage?.( { id: image.id, url: image.url } );
const setImage = ( { id, url, mime } ) => {
onSetImage?.( { id, url, mime } );
handleModalClose();
};

Expand All @@ -225,10 +226,16 @@ export default function GeneralPurposeImage( {
setImage( {
id: currentImage?.libraryId,
url: currentImage?.libraryUrl,
// Default to image/png for cached images (AI generates PNG)
mime: 'image/png',
} );
} else {
saveToMediaLibrary( currentImage?.image ).then( image => {
setImage( image );
setImage( {
id: image.id,
url: image.url,
mime: image.mime,
} );
} );
}
}, [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@ import type { BlockEditorStore } from '../types.ts';

const debug = debugFactory( 'jetpack-ai-client:save-to-media-library' );

type SavedMediaItem = {
id: string;
url: string;
mime?: string;
};

type UseSaveToMediaLibraryReturn = {
isLoading: boolean;
saveToMediaLibrary: ( url: string, name: string ) => Promise< { id: string; url: string } >;
saveToMediaLibrary: ( url: string, name?: string ) => Promise< SavedMediaItem >;
};

/**
Expand All @@ -29,10 +35,7 @@ export default function useSaveToMediaLibrary() {
[]
) as BlockEditorStore[ 'selectors' ];

const saveToMediaLibrary = (
url: string,
name: string = null
): Promise< { id: string; url: string } > => {
const saveToMediaLibrary = ( url: string, name: string = null ): Promise< SavedMediaItem > => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const settings = getSettings() as any;

Expand Down Expand Up @@ -67,7 +70,11 @@ export default function useSaveToMediaLibrary() {

if ( image ) {
debug( 'Image uploaded to media library', image );
resolve( image );
resolve( {
id: image.id,
url: image.url,
mime: image.mime,
} );
}

setIsLoading( false );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { useAnalytics } from '@automattic/jetpack-shared-extension-utils';
import { Button, Icon } from '@wordpress/components';
import { useDispatch } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import { __, _x } from '@wordpress/i18n';
import debugFactory from 'debug';
/**
* Internal dependencies
Expand Down Expand Up @@ -71,7 +71,7 @@ const SaveInLibraryButton: FC< { siteId: string } > = ( { siteId } ) => {
}
};

const savingLabel = __( 'Saving…', 'jetpack-ai-client' );
const savingLabel = _x( 'Saving…', 'Logo save button', 'jetpack-ai-client' );
const savedLabel = __( 'Saved', 'jetpack-ai-client' );

return ! saving && ! saved ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Reuse AI image generation in media section
1 change: 1 addition & 0 deletions projects/js-packages/publicize-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@automattic/jetpack-ai-client": "workspace:*",
"@automattic/jetpack-analytics": "workspace:*",
"@automattic/jetpack-components": "workspace:*",
"@automattic/jetpack-connection": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
* Unified media selection interface for social posts
*/

import { GeneralPurposeImage } from '@automattic/jetpack-ai-client';
import { ThemeProvider } from '@automattic/jetpack-components';
import { useAnalytics } from '@automattic/jetpack-shared-extension-utils';
import { MediaUpload } from '@wordpress/block-editor';
import { BaseControl, Button, Notice } from '@wordpress/components';
import { useCallback, useMemo, useRef } from '@wordpress/element';
import { useCallback, useMemo, useRef, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import useFeaturedImage from '../../hooks/use-featured-image';
import useImageGeneratorConfig from '../../hooks/use-image-generator-config';
Expand Down Expand Up @@ -85,6 +86,9 @@ export default function MediaSectionV2( {
// Ref to store the MediaUpload open function
const openMediaLibraryRef = useRef< () => void >( () => {} );

// State for AI image generation modal
const [ showAiImageModal, setShowAiImageModal ] = useState( false );

// Determine current media source
// Priority 1: Explicit user choice (if media_source is set)
// Priority 2: Detect from existing data (backward compatibility)
Expand Down Expand Up @@ -160,12 +164,12 @@ export default function MediaSectionV2( {
// Handle media selection from Media Library
const handleMediaLibrarySelect = useCallback(
( media: WPMediaObject ) => {
const { id, url, mime: type } = media;
const { id, url, mime } = media;

// Single batch update with explicit media_source
updateJetpackSocialOptions( {
media_source: 'media-library',
attached_media: [ { id, url, type } ],
attached_media: [ { id, url, type: mime } ],
image_generator_settings: { ...imageGeneratorSettings, enabled: false },
} );

Expand All @@ -183,6 +187,37 @@ export default function MediaSectionV2( {
}, 0 );
}, [] );

// Handle AI image generation click
const handleAiImageClick = useCallback( () => {
setShowAiImageModal( true );
}, [] );

// Handle AI image modal close
const handleAiImageModalClose = useCallback( () => {
setShowAiImageModal( false );
}, [] );
Comment on lines +190 to +198
Copy link
Member

@manzoorwanijk manzoorwanijk Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You won't need to define these two separately if you use useReducer above instead of useState.

const [ showAiImageModal, toggleShowAiImageModal ] = useReducer( ( state ) => ! state, false );

You can then directly pass toggleShowAiImageModal for both open and close callbacks.


// Handle AI image selection
const handleAiImageSelect = useCallback(
( { id, url, mime }: WPMediaObject ) => {
// Use 'media-library' as the source since the AI image is uploaded to the media library
updateJetpackSocialOptions( {
media_source: 'media-library',
attached_media: [ { id, url, type: mime || 'image/png' } ],
image_generator_settings: { ...imageGeneratorSettings, enabled: false },
} );

// Track as 'ai-image' in analytics to distinguish from regular media library selections
recordEvent( 'jetpack_social_media_source_changed', {
...analyticsData,
source: 'ai-image',
} );

setShowAiImageModal( false );
},
[ updateJetpackSocialOptions, imageGeneratorSettings, recordEvent, analyticsData ]
);

const renderMediaUpload = useCallback( ( { open }: { open: () => void } ) => {
openMediaLibraryRef.current = open;
return null;
Expand Down Expand Up @@ -275,6 +310,7 @@ export default function MediaSectionV2( {
currentSource={ currentSource }
onSelect={ handleSourceSelect }
onMediaLibraryClick={ handleMediaLibraryClick }
onAiImageClick={ handleAiImageClick }
disabled={ disabled }
>
{ ( { open } ) => (
Expand Down Expand Up @@ -313,6 +349,7 @@ export default function MediaSectionV2( {
currentSource={ currentSource }
onSelect={ handleSourceSelect }
onMediaLibraryClick={ handleMediaLibraryClick }
onAiImageClick={ handleAiImageClick }
disabled={ disabled }
/>
{ currentSource === 'featured-image' && ! featuredImageId && (
Expand All @@ -327,6 +364,13 @@ export default function MediaSectionV2( {
) }
</BaseControl>
</div>
{ showAiImageModal && (
<GeneralPurposeImage
placement="social-media-dropdown"
onClose={ handleAiImageModalClose }
onSetImage={ handleAiImageSelect }
/>
) }
</ThemeProvider>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Displays a dropdown menu with grouped media source options
*/

import { AiSVG } from '@automattic/jetpack-ai-client';
import { Button, Dropdown, MenuGroup, MenuItem } from '@wordpress/components';
import { useCallback } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
Expand Down Expand Up @@ -50,6 +51,17 @@ const MEDIA_SOURCE_OPTIONS: MediaSourceOption[] = [
icon: video,
group: 'attachment',
},
{
id: 'ai-image',
label: __( 'Generate image', 'jetpack-publicize-components' ),
description: __( 'You are using an AI-generated image.', 'jetpack-publicize-components' ),
icon: AiSVG,
group: 'attachment',
attachmentDescription: __(
'Shares your AI-generated image as an attachment for higher engagement.',
'jetpack-publicize-components'
),
},
];

/**
Expand Down Expand Up @@ -91,6 +103,7 @@ interface MediaSourceMenuItemProps {
onSelect: ( optionId: MediaSourceType ) => void;
onClose: () => void;
onMediaLibraryClick?: () => void;
onAiImageClick?: () => void;
}

/**
Expand All @@ -102,6 +115,7 @@ interface MediaSourceMenuItemProps {
* @param {Function} props.onSelect - Selection handler
* @param {Function} props.onClose - Close dropdown handler
* @param {Function} props.onMediaLibraryClick - Media library click handler
* @param {Function} props.onAiImageClick - AI image generation click handler
* @return {object} MediaSourceMenuItem component
*/
function MediaSourceMenuItem( {
Expand All @@ -110,15 +124,18 @@ function MediaSourceMenuItem( {
onSelect,
onClose,
onMediaLibraryClick,
onAiImageClick,
}: MediaSourceMenuItemProps ) {
const handleClick = useCallback( () => {
if ( option.id === 'media-library' ) {
onMediaLibraryClick?.();
} else if ( option.id === 'ai-image' ) {
onAiImageClick?.();
} else {
onSelect( option.id );
}
onClose();
}, [ option.id, onSelect, onClose, onMediaLibraryClick ] );
}, [ option.id, onSelect, onClose, onMediaLibraryClick, onAiImageClick ] );

return (
<MenuItem
Expand All @@ -139,6 +156,7 @@ function MediaSourceMenuItem( {
* @param {string} props.currentSource - Currently selected media source
* @param {Function} props.onSelect - Callback when a source is selected
* @param {Function} props.onMediaLibraryClick - Callback when Media Library option is clicked
* @param {Function} props.onAiImageClick - Callback when Generate with AI option is clicked
* @param {boolean} props.disabled - Whether the menu is disabled
* @param {Function} props.children - Optional children render function that receives open function
* @return {object} MediaSourceMenu component
Expand All @@ -147,6 +165,7 @@ export default function MediaSourceMenu( {
currentSource,
onSelect,
onMediaLibraryClick,
onAiImageClick,
disabled = false,
children,
}: MediaSourceMenuProps ) {
Expand Down Expand Up @@ -197,12 +216,20 @@ export default function MediaSourceMenu( {
onSelect={ onSelect }
onClose={ onClose }
onMediaLibraryClick={ onMediaLibraryClick }
onAiImageClick={ onAiImageClick }
/>
) ) }
</MenuGroup>
</>
),
[ linkPreviewOptions, attachmentOptions, currentSource, onSelect, onMediaLibraryClick ]
[
linkPreviewOptions,
attachmentOptions,
currentSource,
onSelect,
onMediaLibraryClick,
onAiImageClick,
]
);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ jest.mock( '@automattic/jetpack-shared-extension-utils', () => ( {
} ),
} ) );

jest.mock( '@automattic/jetpack-ai-client', () => ( {
GeneralPurposeImage: () => null,
AiSVG: 'svg',
} ) );

jest.mock( '@wordpress/block-editor', () => ( {
MediaUpload: ( {
render: renderProp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
*/
export type MediaSourceType = 'featured-image' | 'media-library' | 'upload-video' | 'sig' | null;

/**
* Menu option IDs - includes all menu items including 'ai-image' which is handled specially
*/
export type MenuOptionId = MediaSourceType | 'ai-image';

/**
* WordPress media object from MediaUpload
*/
Expand All @@ -25,7 +30,7 @@ export type MenuGroupType = 'link-preview' | 'attachment';
* Media source option definition
*/
export interface MediaSourceOption {
id: MediaSourceType;
id: MenuOptionId;
label: string;
description: string;
icon: JSX.Element;
Expand Down Expand Up @@ -78,6 +83,11 @@ export interface MediaSourceMenuProps {
*/
onMediaLibraryClick?: () => void;

/**
* Callback when Generate with AI option is clicked
*/
onAiImageClick?: () => void;

/**
* Whether the menu is disabled
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ export function usePostData() {
};
};

for ( const { id } of attachedMedia ) {
const mediaDetails = getMediaDetails( id );
for ( const attachedItem of attachedMedia ) {
const mediaDetails = getMediaDetails( attachedItem.id );
Comment on lines +62 to +63
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for this change?

if ( mediaDetails ) {
media.push( mediaDetails );
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: enhancement

Reuse AI image generation in media section
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Reuse AI image generation in media section
Loading
Loading