import React, { useState, useMemo, useEffect } from 'react'; import { useSearchParams, useNavigate } from 'react-router-dom'; import { getApiBaseUrl } from '../../utils/apiUrl'; import { Box, Paper, Typography, Grid, Stack, Button, ButtonGroup, Tabs, Tab, Divider, CircularProgress, Alert, Snackbar, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Checkbox, } from '@mui/material'; import { Search, GridView, ViewList, Favorite, Download, Delete, Image as ImageIcon, Collections, History, Star, Refresh, Warning, ArrowBack, Add, InfoOutlined, } from '@mui/icons-material'; import { Tooltip } from '@mui/material'; import { ImageStudioLayout } from './ImageStudioLayout'; import { DashboardHeaderProps } from '../shared/types'; import { useContentAssets, AssetFilters, ContentAsset } from '../../hooks/useContentAssets'; import { intentResearchApi } from '../../api/intentResearchApi'; import { AssetFilters as AssetFiltersComponent } from './AssetLibraryComponents/AssetFilters'; import { AssetCard } from './AssetLibraryComponents/AssetCard'; import { AssetTableRow } from './AssetLibraryComponents/AssetTableRow'; const API_BASE_URL = getApiBaseUrl(); export const AssetLibrary: React.FC = () => { const [searchParams] = useSearchParams(); const navigate = useNavigate(); // Initialize filters from URL params if present const urlSourceModule = searchParams.get('source_module'); const urlAssetType = searchParams.get('asset_type'); const [searchQuery, setSearchQuery] = useState(''); const [idSearch, setIdSearch] = useState(''); const [modelSearch, setModelSearch] = useState(''); const [dateFilter, setDateFilter] = useState(''); const [debouncedSearch, setDebouncedSearch] = useState(''); const [viewMode, setViewMode] = useState<'grid' | 'list'>('list'); const [tabValue, setTabValue] = useState(0); const [filterType, setFilterType] = useState(() => { // Set filter type from URL if present if (urlAssetType) { return urlAssetType === 'audio' ? 'audio' : urlAssetType === 'image' ? 'images' : urlAssetType === 'video' ? 'videos' : urlAssetType === 'text' ? 'text' : 'all'; } return 'all'; }); const [statusFilter, setStatusFilter] = useState('all'); const [selectedAssets, setSelectedAssets] = useState>(new Set()); const [page, setPage] = useState(0); const [pageSize] = useState(50); const [anchorEl, setAnchorEl] = useState<{ [key: number]: HTMLElement | null }>({}); const [snackbar, setSnackbar] = useState<{ open: boolean; message: string; severity: 'success' | 'error' }>({ open: false, message: '', severity: 'success', }); const [textPreviews, setTextPreviews] = useState<{ [key: number]: { content: string; loading: boolean; expanded: boolean } }>({}); // Debounce search query useEffect(() => { const timer = setTimeout(() => { setDebouncedSearch(searchQuery); setPage(0); }, 300); return () => clearTimeout(timer); }, [searchQuery]); const onSearch = (value: string) => { setSearchQuery(value); }; // Build filters based on UI state const filters: AssetFilters = useMemo(() => { const baseFilters: AssetFilters = { limit: pageSize, offset: page * pageSize, }; // Apply source_module from URL if present if (urlSourceModule) { baseFilters.source_module = urlSourceModule as any; } // Combine all search terms const searchTerms: string[] = []; if (debouncedSearch) searchTerms.push(debouncedSearch); if (idSearch) searchTerms.push(idSearch); if (modelSearch) searchTerms.push(modelSearch); if (searchTerms.length > 0) { baseFilters.search = searchTerms.join(' '); } if (filterType !== 'all') { if (filterType === 'images') baseFilters.asset_type = 'image'; else if (filterType === 'videos') baseFilters.asset_type = 'video'; else if (filterType === 'audio') baseFilters.asset_type = 'audio'; else if (filterType === 'text') baseFilters.asset_type = 'text'; else if (filterType === 'favorites') baseFilters.favorites_only = true; } if (tabValue === 1) { baseFilters.favorites_only = true; } return baseFilters; }, [debouncedSearch, idSearch, modelSearch, filterType, tabValue, page, pageSize, urlSourceModule]); const headerProps: DashboardHeaderProps | undefined = useMemo(() => { if (!urlSourceModule) return undefined; switch (urlSourceModule) { case 'blog_writer': return { title: 'Blog Posts', }; case 'research_tools': return { title: 'Research Documents', subtitle: 'Access and manage your research projects.', }; case 'product_marketing': return { title: 'Marketing Assets', subtitle: 'Marketing content generated by Product Marketing tools.', }; default: return undefined; } }, [urlSourceModule]); const { assets, loading, error, total, toggleFavorite, deleteAsset, trackUsage, refetch } = useContentAssets(filters); // Refetch assets when component mounts with research_tools filter to show latest drafts useEffect(() => { if (urlSourceModule === 'research_tools' && urlAssetType === 'text') { console.log('[AssetLibrary] Refetching assets for research_tools/text filter'); const timer = setTimeout(() => { refetch(); }, 1000); return () => clearTimeout(timer); } }, [urlSourceModule, urlAssetType, refetch]); const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => { setTabValue(newValue); setPage(0); }; const handleSelectAll = (checked: boolean) => { if (checked) { setSelectedAssets(new Set(assets.map(a => a.id))); } else { setSelectedAssets(new Set()); } }; const handleSelectAsset = (assetId: number, checked: boolean) => { const newSelected = new Set(selectedAssets); if (checked) { newSelected.add(assetId); } else { newSelected.delete(assetId); } setSelectedAssets(newSelected); }; const handleBulkDelete = async () => { if (selectedAssets.size === 0) return; if (!window.confirm(`Delete ${selectedAssets.size} selected asset(s)?`)) return; try { await Promise.all(Array.from(selectedAssets).map(id => deleteAsset(id))); setSelectedAssets(new Set()); setSnackbar({ open: true, message: `${selectedAssets.size} asset(s) deleted`, severity: 'success' }); } catch (err) { setSnackbar({ open: true, message: 'Failed to delete assets', severity: 'error' }); } }; const handleBulkDownload = async () => { if (selectedAssets.size === 0) return; try { const selectedAssetsData = assets.filter(a => selectedAssets.has(a.id)); await Promise.all(selectedAssetsData.map(asset => trackUsage(asset.id, 'download'))); // Open each in new tab selectedAssetsData.forEach(asset => { window.open(asset.file_url, '_blank'); }); setSnackbar({ open: true, message: `Downloading ${selectedAssets.size} asset(s)`, severity: 'success' }); } catch (err) { setSnackbar({ open: true, message: 'Failed to download assets', severity: 'error' }); } }; const handleFavorite = async (assetId: number) => { try { await toggleFavorite(assetId); const asset = assets.find(a => a.id === assetId); setSnackbar({ open: true, message: asset?.is_favorite ? 'Removed from favorites' : 'Added to favorites', severity: 'success', }); } catch (err) { setSnackbar({ open: true, message: 'Failed to update favorite', severity: 'error' }); } }; const handleDelete = async (assetId: number) => { if (!window.confirm('Are you sure you want to delete this asset?')) return; try { await deleteAsset(assetId); setSnackbar({ open: true, message: 'Asset deleted', severity: 'success' }); } catch (err) { setSnackbar({ open: true, message: 'Failed to delete asset', severity: 'error' }); } }; const handleDownload = async (asset: ContentAsset) => { try { await trackUsage(asset.id, 'download'); window.open(asset.file_url, '_blank'); } catch (err) { console.error('Error downloading:', err); } }; const handleShare = async (asset: ContentAsset) => { try { await trackUsage(asset.id, 'share'); if (navigator.share) { await navigator.share({ title: asset.title || asset.filename, text: asset.description, url: asset.file_url, }); } else { await navigator.clipboard.writeText(asset.file_url); setSnackbar({ open: true, message: 'Link copied to clipboard', severity: 'success' }); } } catch (err) { console.error('Error sharing:', err); } }; const handleMenuOpen = (assetId: number, event: React.MouseEvent) => { setAnchorEl({ ...anchorEl, [assetId]: event.currentTarget }); }; const handleMenuClose = (assetId: number) => { setAnchorEl({ ...anchorEl, [assetId]: null }); }; const handleOpenBlogAsset = async (asset: ContentAsset) => { navigate('/blog-writer', { state: { restoreBlogAssetId: asset.id } }); }; const handleRestoreResearchProject = async (asset: ContentAsset) => { try { const projectId = asset.asset_metadata?.project_id; if (!projectId) { setSnackbar({ open: true, message: 'Project ID not found', severity: 'error' }); return; } const project = await intentResearchApi.getResearchProject(projectId); if (!project) { setSnackbar({ open: true, message: 'Project not found', severity: 'error' }); return; } localStorage.setItem('alwrity_research_project_id', projectId); navigate('/research-dashboard'); setSnackbar({ open: true, message: 'Research project restored', severity: 'success' }); } catch (error) { console.error('[AssetLibrary] Error restoring research project:', error); setSnackbar({ open: true, message: 'Failed to restore research project', severity: 'error' }); } }; // Fetch text content for text assets const fetchTextContent = async (asset: ContentAsset) => { if (asset.asset_type !== 'text' || textPreviews[asset.id]) return; setTextPreviews(prev => ({ ...prev, [asset.id]: { content: '', loading: true, expanded: false } })); try { const token = await (window as any).Clerk?.session?.getToken(); const headers: HeadersInit = {}; if (token) { headers['Authorization'] = `Bearer ${token}`; } const response = await fetch(`${API_BASE_URL}/api/content-assets/${asset.id}/content`, { headers }); if (response.ok) { const data = await response.json(); const content = data.content || ''; setTextPreviews(prev => ({ ...prev, [asset.id]: { content, loading: false, expanded: false } })); } else { throw new Error('Failed to fetch text content'); } } catch (error) { console.error('Error fetching text content:', error); setTextPreviews(prev => ({ ...prev, [asset.id]: { content: 'Failed to load content', loading: false, expanded: false } })); } }; const toggleTextPreview = (asset: ContentAsset) => { if (asset.asset_type !== 'text') return; if (!textPreviews[asset.id]) { fetchTextContent(asset); } else { setTextPreviews(prev => ({ ...prev, [asset.id]: { ...prev[asset.id], expanded: !prev[asset.id].expanded } })); } }; const filteredAssets = useMemo(() => { let filtered = assets; if (statusFilter !== 'all') { filtered = filtered.filter(a => (a.asset_metadata?.status || 'completed') === statusFilter); } if (dateFilter) { const filterDate = new Date(dateFilter); filtered = filtered.filter(a => { const assetDate = new Date(a.created_at); return assetDate.toDateString() === filterDate.toDateString(); }); } return filtered; }, [assets, statusFilter, dateFilter]); return ( {/* Header */} {urlSourceModule === 'blog_writer' ? 'Blog Posts' : 'Asset Library'} {/* Context-aware navigation for blog_writer source */} {urlSourceModule === 'blog_writer' && ( )} {/* Advanced Search and Filters */} {/* Bulk Actions */} {selectedAssets.size > 0 && ( {selectedAssets.size} selected )} {/* Action Buttons */} {/* Tabs */} } iconPosition="start" label="All Assets" /> } iconPosition="start" label="Favorites" /> } iconPosition="start" label="Recent" /> } iconPosition="start" label="Collections" /> {/* Content */} {loading ? ( ) : error ? ( {error} ) : filteredAssets.length === 0 ? ( No assets found {urlSourceModule === 'blog_writer' ? 'No blog posts found. Generate your first blog post in Blog Writer.' : urlSourceModule === 'research_tools' ? 'No research documents found. Start a new research project.' : urlSourceModule === 'product_marketing' ? 'No marketing assets found. Create one in Product Marketing.' : 'Generated content from all ALwrity tools will appear here.'} ) : viewMode === 'list' ? ( 0} indeterminate={selectedAssets.size > 0 && selectedAssets.size < filteredAssets.length} onChange={e => handleSelectAll(e.target.checked)} sx={{ color: 'rgba(255,255,255,0.6)' }} /> ID Model Status Outputs Created Action {filteredAssets.map(asset => ( handleSelectAsset(asset.id, checked)} onDownload={handleDownload} onShare={handleShare} onDelete={handleDelete} onFavorite={handleFavorite} onRestore={handleRestoreResearchProject} onMenuOpen={handleMenuOpen} onMenuClose={handleMenuClose} anchorEl={anchorEl[asset.id]} textPreview={textPreviews[asset.id]} onToggleTextPreview={toggleTextPreview} onCopyId={(id) => { navigator.clipboard.writeText(id); setSnackbar({ open: true, message: 'ID copied', severity: 'success' }); }} /> ))}
) : ( {filteredAssets.map(asset => ( ))} )} {/* Pagination */} {total > pageSize && ( Page {page + 1} of {Math.ceil(total / pageSize)} ({total} total) )} setSnackbar({ ...snackbar, open: false })} anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }} > setSnackbar({ ...snackbar, open: false })}> {snackbar.message}
); };