Updated iamge prompt flow adn frotnend backend
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
* Displays content from Content table with filters and pagination
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
||||
import TablePageTemplate from '../../templates/TablePageTemplate';
|
||||
import {
|
||||
fetchContent,
|
||||
@@ -16,6 +16,8 @@ import { FileIcon } from '../../icons';
|
||||
import { createContentPageConfig } from '../../config/pages/content.config';
|
||||
import { useSectorStore } from '../../store/sectorStore';
|
||||
import { usePageSizeStore } from '../../store/pageSizeStore';
|
||||
import ProgressModal from '../../components/common/ProgressModal';
|
||||
import { useProgressModal } from '../../hooks/useProgressModal';
|
||||
|
||||
export default function Content() {
|
||||
const toast = useToast();
|
||||
@@ -41,6 +43,10 @@ export default function Content() {
|
||||
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
|
||||
const [showContent, setShowContent] = useState(false);
|
||||
|
||||
// Progress modal for AI functions
|
||||
const progressModal = useProgressModal();
|
||||
const hasReloadedRef = useRef(false);
|
||||
|
||||
// Load content - wrapped in useCallback
|
||||
const loadContent = useCallback(async () => {
|
||||
setLoading(true);
|
||||
@@ -152,9 +158,16 @@ export default function Content() {
|
||||
const result = await generateImagePrompts([row.id]);
|
||||
if (result.success) {
|
||||
if (result.task_id) {
|
||||
toast.success('Image prompts generation started');
|
||||
// Open progress modal for async task
|
||||
progressModal.openModal(
|
||||
result.task_id,
|
||||
'Generate Image Prompts',
|
||||
'ai-generate-image-prompts-01-desktop'
|
||||
);
|
||||
} else {
|
||||
// Synchronous completion
|
||||
toast.success(`Image prompts generated: ${result.prompts_created || 0} prompt${(result.prompts_created || 0) === 1 ? '' : 's'} created`);
|
||||
loadContent(); // Reload to show new prompts
|
||||
}
|
||||
} else {
|
||||
toast.error(result.error || 'Failed to generate image prompts');
|
||||
@@ -163,7 +176,7 @@ export default function Content() {
|
||||
toast.error(`Failed to generate prompts: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}, [toast]);
|
||||
}, [toast, progressModal, loadContent]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -207,6 +220,30 @@ export default function Content() {
|
||||
onRowAction={handleRowAction}
|
||||
getItemDisplayName={(row: ContentType) => row.meta_title || row.title || `Content #${row.id}`}
|
||||
/>
|
||||
|
||||
{/* Progress Modal for AI Functions */}
|
||||
<ProgressModal
|
||||
isOpen={progressModal.isOpen}
|
||||
title={progressModal.title}
|
||||
percentage={progressModal.progress.percentage}
|
||||
status={progressModal.progress.status}
|
||||
message={progressModal.progress.message}
|
||||
details={progressModal.progress.details}
|
||||
taskId={progressModal.taskId || undefined}
|
||||
functionId={progressModal.functionId}
|
||||
onClose={() => {
|
||||
const wasCompleted = progressModal.progress.status === 'completed';
|
||||
progressModal.closeModal();
|
||||
// Reload data after modal closes (if completed)
|
||||
if (wasCompleted && !hasReloadedRef.current) {
|
||||
hasReloadedRef.current = true;
|
||||
loadContent();
|
||||
setTimeout(() => {
|
||||
hasReloadedRef.current = false;
|
||||
}, 1000);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
/**
|
||||
* Images Page - Built with TablePageTemplate
|
||||
* Consistent with Keywords page layout, structure and design
|
||||
* Shows content images grouped by content - one row per content
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import TablePageTemplate from '../../templates/TablePageTemplate';
|
||||
import {
|
||||
fetchTaskImages,
|
||||
deleteTaskImage,
|
||||
bulkDeleteTaskImages,
|
||||
autoGenerateImages,
|
||||
TaskImage,
|
||||
TaskImageFilters,
|
||||
fetchContentImages,
|
||||
ContentImagesGroup,
|
||||
ContentImagesResponse,
|
||||
} from '../../services/api';
|
||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||
import { FileIcon, DownloadIcon } from '../../icons';
|
||||
@@ -21,23 +18,23 @@ export default function Images() {
|
||||
const toast = useToast();
|
||||
|
||||
// Data state
|
||||
const [images, setImages] = useState<TaskImage[]>([]);
|
||||
const [images, setImages] = useState<ContentImagesGroup[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
// Filter state
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [imageTypeFilter, setImageTypeFilter] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState('');
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||
|
||||
// Pagination state
|
||||
// Pagination state (client-side for now)
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
const [totalCount, setTotalCount] = useState(0);
|
||||
const pageSize = 10;
|
||||
|
||||
// Sorting state
|
||||
const [sortBy, setSortBy] = useState<string>('created_at');
|
||||
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
|
||||
const [sortBy, setSortBy] = useState<string>('content_title');
|
||||
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
|
||||
const [showContent, setShowContent] = useState(false);
|
||||
|
||||
// Load images - wrapped in useCallback
|
||||
@@ -45,30 +42,46 @@ export default function Images() {
|
||||
setLoading(true);
|
||||
setShowContent(false);
|
||||
try {
|
||||
const ordering = sortBy ? `${sortDirection === 'desc' ? '-' : ''}${sortBy}` : '-created_at';
|
||||
|
||||
const filters: TaskImageFilters = {
|
||||
...(imageTypeFilter && { image_type: imageTypeFilter }),
|
||||
...(statusFilter && { status: statusFilter }),
|
||||
page: currentPage,
|
||||
ordering,
|
||||
};
|
||||
|
||||
// Note: TaskImages API doesn't support search by task title yet
|
||||
// We'll filter client-side for now
|
||||
const data = await fetchTaskImages(filters);
|
||||
const data: ContentImagesResponse = await fetchContentImages();
|
||||
let filteredResults = data.results || [];
|
||||
|
||||
// Client-side search filter
|
||||
if (searchTerm) {
|
||||
filteredResults = filteredResults.filter(img =>
|
||||
img.task_title?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
filteredResults = filteredResults.filter(group =>
|
||||
group.content_title?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
setImages(filteredResults);
|
||||
// Client-side status filter
|
||||
if (statusFilter) {
|
||||
filteredResults = filteredResults.filter(group =>
|
||||
group.overall_status === statusFilter
|
||||
);
|
||||
}
|
||||
|
||||
// Client-side sorting
|
||||
filteredResults.sort((a, b) => {
|
||||
let aVal: any = a.content_title;
|
||||
let bVal: any = b.content_title;
|
||||
|
||||
if (sortBy === 'overall_status') {
|
||||
aVal = a.overall_status;
|
||||
bVal = b.overall_status;
|
||||
}
|
||||
|
||||
if (aVal < bVal) return sortDirection === 'asc' ? -1 : 1;
|
||||
if (aVal > bVal) return sortDirection === 'asc' ? 1 : -1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Client-side pagination
|
||||
const startIndex = (currentPage - 1) * pageSize;
|
||||
const endIndex = startIndex + pageSize;
|
||||
const paginatedResults = filteredResults.slice(startIndex, endIndex);
|
||||
|
||||
setImages(paginatedResults);
|
||||
setTotalCount(filteredResults.length);
|
||||
setTotalPages(Math.ceil(filteredResults.length / 10));
|
||||
setTotalPages(Math.ceil(filteredResults.length / pageSize));
|
||||
|
||||
setTimeout(() => {
|
||||
setShowContent(true);
|
||||
@@ -80,7 +93,7 @@ export default function Images() {
|
||||
setShowContent(true);
|
||||
setLoading(false);
|
||||
}
|
||||
}, [currentPage, imageTypeFilter, statusFilter, sortBy, sortDirection, searchTerm]);
|
||||
}, [currentPage, statusFilter, sortBy, sortDirection, searchTerm, toast]);
|
||||
|
||||
useEffect(() => {
|
||||
loadImages();
|
||||
@@ -101,7 +114,7 @@ export default function Images() {
|
||||
|
||||
// Handle sorting
|
||||
const handleSort = (field: string, direction: 'asc' | 'desc') => {
|
||||
setSortBy(field || 'created_at');
|
||||
setSortBy(field || 'content_title');
|
||||
setSortDirection(direction);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
@@ -116,37 +129,31 @@ export default function Images() {
|
||||
} catch (error: any) {
|
||||
throw error;
|
||||
}
|
||||
}, []);
|
||||
}, [toast]);
|
||||
|
||||
// Bulk action handler
|
||||
const handleBulkAction = useCallback(async (action: string, ids: string[]) => {
|
||||
if (action === 'generate_images') {
|
||||
try {
|
||||
const numIds = ids.map(id => parseInt(id));
|
||||
// Note: autoGenerateImages expects task_ids, not image_ids
|
||||
// This would need to be adjusted based on API design
|
||||
toast.info(`Generate images for ${ids.length} items`);
|
||||
// await autoGenerateImages(numIds);
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to generate images: ${error.message}`);
|
||||
}
|
||||
} else {
|
||||
toast.info(`Bulk action "${action}" for ${ids.length} items`);
|
||||
}
|
||||
}, []);
|
||||
toast.info(`Bulk action "${action}" for ${ids.length} items`);
|
||||
}, [toast]);
|
||||
|
||||
// Get max in-article images from the data (to determine column count)
|
||||
const maxInArticleImages = useMemo(() => {
|
||||
if (images.length === 0) return 5; // Default
|
||||
const max = Math.max(...images.map(group => group.in_article_images.length));
|
||||
return Math.max(max, 5); // At least 5 columns
|
||||
}, [images]);
|
||||
|
||||
// Create page config
|
||||
const pageConfig = useMemo(() => {
|
||||
return createImagesPageConfig({
|
||||
searchTerm,
|
||||
setSearchTerm,
|
||||
imageTypeFilter,
|
||||
setImageTypeFilter,
|
||||
statusFilter,
|
||||
setStatusFilter,
|
||||
setCurrentPage,
|
||||
maxInArticleImages,
|
||||
});
|
||||
}, [searchTerm, imageTypeFilter, statusFilter]);
|
||||
}, [searchTerm, statusFilter, maxInArticleImages]);
|
||||
|
||||
// Calculate header metrics
|
||||
const headerMetrics = useMemo(() => {
|
||||
@@ -160,9 +167,9 @@ export default function Images() {
|
||||
|
||||
return (
|
||||
<TablePageTemplate
|
||||
title="Task Images"
|
||||
title="Content Images"
|
||||
titleIcon={<FileIcon className="text-purple-500 size-5" />}
|
||||
subtitle="Manage images for content tasks"
|
||||
subtitle="Manage images for content articles"
|
||||
columns={pageConfig.columns}
|
||||
data={images}
|
||||
loading={loading}
|
||||
@@ -170,40 +177,25 @@ export default function Images() {
|
||||
filters={pageConfig.filters}
|
||||
filterValues={{
|
||||
search: searchTerm,
|
||||
image_type: imageTypeFilter,
|
||||
status: statusFilter,
|
||||
}}
|
||||
onFilterChange={(key, value) => {
|
||||
const stringValue = value === null || value === undefined ? '' : String(value);
|
||||
if (key === 'search') {
|
||||
setSearchTerm(stringValue);
|
||||
} else if (key === 'image_type') {
|
||||
setImageTypeFilter(stringValue);
|
||||
} else if (key === 'status') {
|
||||
setStatusFilter(stringValue);
|
||||
}
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
onDelete={async (id: number) => {
|
||||
await deleteTaskImage(id);
|
||||
loadImages();
|
||||
}}
|
||||
onBulkDelete={async (ids: number[]) => {
|
||||
// Note: bulkDeleteTaskImages doesn't exist yet, using individual deletes
|
||||
for (const id of ids) {
|
||||
await deleteTaskImage(id);
|
||||
}
|
||||
loadImages();
|
||||
return { deleted_count: ids.length };
|
||||
}}
|
||||
onBulkExport={handleBulkExport}
|
||||
onBulkAction={handleBulkAction}
|
||||
getItemDisplayName={(row: TaskImage) => row.task_title || `Image ${row.id}`}
|
||||
getItemDisplayName={(row: ContentImagesGroup) => row.content_title || `Content #${row.content_id}`}
|
||||
onExport={async () => {
|
||||
toast.info('Export functionality coming soon');
|
||||
}}
|
||||
onExportIcon={<DownloadIcon />}
|
||||
selectionLabel="image"
|
||||
selectionLabel="content"
|
||||
pagination={{
|
||||
currentPage,
|
||||
totalPages,
|
||||
@@ -222,7 +214,6 @@ export default function Images() {
|
||||
headerMetrics={headerMetrics}
|
||||
onFilterReset={() => {
|
||||
setSearchTerm('');
|
||||
setImageTypeFilter('');
|
||||
setStatusFilter('');
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user