diff --git a/frontend/src/config/pages/images.config.tsx b/frontend/src/config/pages/images.config.tsx
index 6b9663d9..63f23641 100644
--- a/frontend/src/config/pages/images.config.tsx
+++ b/frontend/src/config/pages/images.config.tsx
@@ -52,6 +52,7 @@ export const createImagesPageConfig = (
setCurrentPage: (page: number) => void;
maxInArticleImages?: number; // Optional: max in-article images to display
onGenerateImages?: (contentId: number) => void; // Handler for generate images button
+ onImageClick?: (contentId: number, imageType: 'featured' | 'in_article', position?: number) => void; // Handler for image click
}
): ImagesPageConfig => {
const maxImages = handlers.maxInArticleImages || 5; // Default to 5 in-article images
@@ -84,7 +85,10 @@ export const createImagesPageConfig = (
sortable: false,
width: '200px',
render: (_value: any, row: ContentImagesGroup) => (
-
+ handlers.onImageClick!(row.content_id, 'featured') : undefined}
+ />
),
},
];
@@ -98,7 +102,12 @@ export const createImagesPageConfig = (
width: '200px',
render: (_value: any, row: ContentImagesGroup) => {
const image = row.in_article_images.find(img => img.position === i);
- return ;
+ return (
+ handlers.onImageClick!(row.content_id, 'in_article', i) : undefined}
+ />
+ );
},
});
}
diff --git a/frontend/src/pages/Writer/Images.tsx b/frontend/src/pages/Writer/Images.tsx
index 6707f505..ce5dc000 100644
--- a/frontend/src/pages/Writer/Images.tsx
+++ b/frontend/src/pages/Writer/Images.tsx
@@ -12,6 +12,7 @@ import {
fetchImageGenerationSettings,
generateImages,
bulkUpdateImagesStatus,
+ ContentImage,
} from '../../services/api';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { FileIcon, DownloadIcon, BoltIcon } from '../../icons';
@@ -20,6 +21,7 @@ import ImageQueueModal, { ImageQueueItem } from '../../components/common/ImageQu
import SingleRecordStatusUpdateModal from '../../components/common/SingleRecordStatusUpdateModal';
import { useResourceDebug } from '../../hooks/useResourceDebug';
import PageHeader from '../../components/common/PageHeader';
+import { Modal } from '../../components/ui/modal';
export default function Images() {
const toast = useToast();
@@ -85,6 +87,10 @@ export default function Images() {
const [statusUpdateRecordName, setStatusUpdateRecordName] = useState('');
const [isUpdatingStatus, setIsUpdatingStatus] = useState(false);
+ // Image modal state
+ const [isImageModalOpen, setIsImageModalOpen] = useState(false);
+ const [modalImageUrl, setModalImageUrl] = useState(null);
+
// Load images - wrapped in useCallback
const loadImages = useCallback(async () => {
setLoading(true);
@@ -353,6 +359,58 @@ export default function Images() {
}
}, [toast, images, buildImageQueue]);
+ // Helper function to convert image_path to web-accessible URL
+ const getImageUrl = useCallback((image: ContentImage | null): string | null => {
+ if (!image || !image.image_path) return null;
+
+ // Check if image_path is a valid local file path (not a URL)
+ const isValidLocalPath = (imagePath: string): boolean => {
+ if (imagePath.startsWith('http://') || imagePath.startsWith('https://')) {
+ return false;
+ }
+ return imagePath.includes('ai-images');
+ };
+
+ if (!isValidLocalPath(image.image_path)) return null;
+
+ // Convert local file path to web-accessible URL
+ if (image.image_path.includes('ai-images')) {
+ const filename = image.image_path.split('ai-images/')[1] || image.image_path.split('ai-images\\')[1];
+ if (filename) {
+ return `/images/ai-images/${filename}`;
+ }
+ }
+
+ if (image.image_path.startsWith('/images/')) {
+ return image.image_path;
+ }
+
+ const filename = image.image_path.split('/').pop() || image.image_path.split('\\').pop();
+ return filename ? `/images/ai-images/${filename}` : null;
+ }, []);
+
+ // Handle image click - open modal with single image
+ const handleImageClick = useCallback((contentId: number, imageType: 'featured' | 'in_article', position?: number) => {
+ const contentGroup = images.find(g => g.content_id === contentId);
+ if (!contentGroup) return;
+
+ let image: ContentImage | null = null;
+
+ if (imageType === 'featured' && contentGroup.featured_image) {
+ image = contentGroup.featured_image;
+ } else if (imageType === 'in_article' && position) {
+ image = contentGroup.in_article_images.find(img => img.position === position) || null;
+ }
+
+ if (image && image.status === 'generated') {
+ const url = getImageUrl(image);
+ if (url) {
+ setModalImageUrl(url);
+ setIsImageModalOpen(true);
+ }
+ }
+ }, [images, getImageUrl]);
+
// Get max in-article images from the data (to determine column count)
const maxInArticleImages = useMemo(() => {
if (images.length === 0) return 5; // Default
@@ -370,8 +428,9 @@ export default function Images() {
setCurrentPage,
maxInArticleImages,
onGenerateImages: handleGenerateImages,
+ onImageClick: handleImageClick,
});
- }, [searchTerm, statusFilter, maxInArticleImages, handleGenerateImages]);
+ }, [searchTerm, statusFilter, maxInArticleImages, handleGenerateImages, handleImageClick]);
// Calculate header metrics
const headerMetrics = useMemo(() => {
@@ -479,6 +538,27 @@ export default function Images() {
isLoading={isUpdatingStatus}
/>
+ {/* Image Modal - 800px wide, auto height */}
+ {
+ setIsImageModalOpen(false);
+ setModalImageUrl(null);
+ }}
+ className="max-w-[800px] w-full mx-4"
+ >
+ {modalImageUrl && (
+
+

+
+ )}
+
+
{/* AI Function Logs - Display below table (only when Resource Debug is enabled) */}
{resourceDebugEnabled && aiLogs.length > 0 && (