Fix compilation errors and resolve ESLint warnings across multiple components

This commit is contained in:
ajaysi
2026-03-03 08:57:36 +05:30
parent cb6a3a8042
commit 2e04b8e27b
54 changed files with 1170 additions and 930 deletions

View File

@@ -4,23 +4,12 @@ import {
Box,
Paper,
Typography,
TextField,
InputAdornment,
Grid,
Card,
CardContent,
CardMedia,
Chip,
IconButton,
Stack,
Button,
ButtonGroup,
Tabs,
Tab,
FormControl,
Select,
MenuItem,
InputLabel,
Divider,
CircularProgress,
Alert,
@@ -32,94 +21,27 @@ import {
TableHead,
TableRow,
Checkbox,
Tooltip,
Menu,
ListItemIcon,
ListItemText,
} from '@mui/material';
import {
Search,
GridView,
ViewList,
Favorite,
FavoriteBorder,
Download,
Share,
Delete,
Image as ImageIcon,
VideoLibrary,
TextFields,
AudioFile,
Collections,
History,
Star,
MoreVert,
Upload,
CalendarToday,
CheckCircle,
HourglassEmpty,
Error as ErrorIcon,
Refresh,
Warning,
ExpandMore,
ExpandLess,
} from '@mui/icons-material';
import { ImageStudioLayout } from './ImageStudioLayout';
import { useContentAssets, AssetFilters, ContentAsset } from '../../hooks/useContentAssets';
import { intentResearchApi } from '../../api/intentResearchApi';
interface TabPanelProps {
children?: React.ReactNode;
index: number;
value: number;
}
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div role="tabpanel" hidden={value !== index} {...other}>
{value === index && <Box sx={{ pt: 3 }}>{children}</Box>}
</div>
);
}
const getStatusIcon = (status: string) => {
switch (status?.toLowerCase()) {
case 'completed':
return <CheckCircle sx={{ color: '#10b981', fontSize: 18 }} />;
case 'processing':
return <HourglassEmpty sx={{ color: '#f59e0b', fontSize: 18 }} />;
case 'failed':
return <ErrorIcon sx={{ color: '#ef4444', fontSize: 18 }} />;
default:
return <HourglassEmpty sx={{ color: '#6b7280', fontSize: 18 }} />;
}
};
const getStatusChip = (status: string) => {
const statusLower = status?.toLowerCase() || 'completed';
const colors: Record<string, { bg: string; color: string }> = {
completed: { bg: 'rgba(16,185,129,0.2)', color: '#10b981' },
processing: { bg: 'rgba(245,158,11,0.2)', color: '#f59e0b' },
failed: { bg: 'rgba(239,68,68,0.2)', color: '#ef4444' },
pending: { bg: 'rgba(107,114,128,0.2)', color: '#6b7280' },
};
const style = colors[statusLower] || colors.completed;
return (
<Chip
icon={getStatusIcon(status)}
label={statusLower}
size="small"
sx={{
background: style.bg,
color: style.color,
fontWeight: 600,
textTransform: 'capitalize',
height: 28,
}}
/>
);
};
import { AssetFilters as AssetFiltersComponent } from './AssetLibraryComponents/AssetFilters';
import { AssetCard } from './AssetLibraryComponents/AssetCard';
import { AssetTableRow } from './AssetLibraryComponents/AssetTableRow';
export const AssetLibrary: React.FC = () => {
const [searchParams] = useSearchParams();
@@ -134,7 +56,7 @@ export const AssetLibrary: React.FC = () => {
const [modelSearch, setModelSearch] = useState('');
const [dateFilter, setDateFilter] = useState('');
const [debouncedSearch, setDebouncedSearch] = useState('');
const [viewMode, setViewMode] = useState<'grid' | 'list'>('list'); // Default to list like reference
const [viewMode, setViewMode] = useState<'grid' | 'list'>('list');
const [tabValue, setTabValue] = useState(0);
const [filterType, setFilterType] = useState(() => {
// Set filter type from URL if present
@@ -164,6 +86,10 @@ export const AssetLibrary: React.FC = () => {
return () => clearTimeout(timer);
}, [searchQuery]);
const onSearch = (value: string) => {
setSearchQuery(value);
};
// Build filters based on UI state
const filters: AssetFilters = useMemo(() => {
const baseFilters: AssetFilters = {
@@ -207,10 +133,9 @@ export const AssetLibrary: React.FC = () => {
useEffect(() => {
if (urlSourceModule === 'research_tools' && urlAssetType === 'text') {
console.log('[AssetLibrary] Refetching assets for research_tools/text filter');
// Small delay to ensure any recent saves are complete
const timer = setTimeout(() => {
refetch();
}, 1000); // Increased delay to ensure save completes
}, 1000);
return () => clearTimeout(timer);
}
}, [urlSourceModule, urlAssetType, refetch]);
@@ -330,14 +255,12 @@ export const AssetLibrary: React.FC = () => {
const handleRestoreResearchProject = async (asset: ContentAsset) => {
try {
// Extract project_id from asset metadata
const projectId = asset.asset_metadata?.project_id;
if (!projectId) {
setSnackbar({ open: true, message: 'Project ID not found', severity: 'error' });
return;
}
// Load full project from database
const project = await intentResearchApi.getResearchProject(projectId);
if (!project) {
@@ -345,12 +268,8 @@ export const AssetLibrary: React.FC = () => {
return;
}
// Store project ID for restoration hook to pick up
localStorage.setItem('alwrity_research_project_id', projectId);
// Navigate to Research Dashboard
navigate('/research-dashboard');
setSnackbar({ open: true, message: 'Research project restored', severity: 'success' });
} catch (error) {
console.error('[AssetLibrary] Error restoring research project:', error);
@@ -358,25 +277,6 @@ export const AssetLibrary: React.FC = () => {
}
};
const formatDate = (dateString: string) => {
try {
const date = new Date(dateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
const timezoneOffset = -date.getTimezoneOffset();
const offsetHours = String(Math.floor(Math.abs(timezoneOffset) / 60)).padStart(2, '0');
const offsetMinutes = String(Math.abs(timezoneOffset) % 60).padStart(2, '0');
const offsetSign = timezoneOffset >= 0 ? '+' : '-';
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds} GMT${offsetSign}${offsetHours}.${offsetMinutes}`;
} catch {
return dateString;
}
};
// Fetch text content for text assets
const fetchTextContent = async (asset: ContentAsset) => {
if (asset.asset_type !== 'text' || textPreviews[asset.id]) return;
@@ -419,191 +319,6 @@ export const AssetLibrary: React.FC = () => {
}
};
const getAssetPreview = (asset: ContentAsset, isListView: boolean = false) => {
if (asset.asset_type === 'image') {
return (
<Box
component="img"
src={asset.file_url}
alt={asset.title || asset.filename}
sx={{
width: 80,
height: 80,
objectFit: 'cover',
borderRadius: 1,
border: '1px solid rgba(255,255,255,0.1)',
cursor: 'pointer',
}}
onClick={() => window.open(asset.file_url, '_blank')}
/>
);
} else if (asset.asset_type === 'video') {
return (
<Box
sx={{
width: 80,
height: 80,
borderRadius: 1,
background: 'rgba(99,102,241,0.2)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
border: '1px solid rgba(255,255,255,0.1)',
cursor: 'pointer',
}}
onClick={() => window.open(asset.file_url, '_blank')}
>
<VideoLibrary sx={{ color: '#c7d2fe', fontSize: 32 }} />
</Box>
);
} else if (asset.asset_type === 'audio') {
return (
<Box
sx={{
width: 80,
height: 80,
borderRadius: 1,
background: 'rgba(59,130,246,0.2)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
border: '1px solid rgba(255,255,255,0.1)',
cursor: 'pointer',
}}
onClick={() => window.open(asset.file_url, '_blank')}
>
<AudioFile sx={{ color: '#93c5fd', fontSize: 32 }} />
</Box>
);
} else if (asset.asset_type === 'text') {
const preview = textPreviews[asset.id];
const previewText = preview?.content || '';
const lines = previewText.split('\n');
const previewLines = lines.slice(0, 2).join('\n');
const hasMore = lines.length > 2 || previewText.length > 100;
return (
<Box
sx={{
width: isListView ? 'auto' : 80,
minHeight: isListView ? 'auto' : 80,
maxWidth: isListView ? 300 : 80,
borderRadius: 1,
background: 'rgba(107,114,128,0.2)',
border: '1px solid rgba(255,255,255,0.1)',
cursor: 'pointer',
p: isListView ? 1.5 : 1,
display: 'flex',
flexDirection: 'column',
position: 'relative',
}}
onClick={(e) => {
e.stopPropagation();
toggleTextPreview(asset);
}}
>
{preview?.loading ? (
<CircularProgress size={20} sx={{ m: 'auto' }} />
) : preview?.expanded ? (
<Box sx={{
flex: 1,
overflow: 'auto',
fontSize: isListView ? '0.8rem' : '0.7rem',
color: '#d1d5db',
maxHeight: isListView ? 200 : 150,
}}>
<Typography
variant="caption"
sx={{
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
fontFamily: isListView ? 'monospace' : 'inherit',
lineHeight: 1.5,
}}
>
{previewText.substring(0, isListView ? 1000 : 500)}
{previewText.length > (isListView ? 1000 : 500) && '...'}
</Typography>
<IconButton
size="small"
onClick={(e) => {
e.stopPropagation();
toggleTextPreview(asset);
}}
sx={{ position: 'absolute', bottom: 4, right: 4, p: 0.5 }}
>
<ExpandLess sx={{ fontSize: 16, color: '#d1d5db' }} />
</IconButton>
</Box>
) : (
<>
<TextFields sx={{ color: '#d1d5db', fontSize: isListView ? 28 : 24, mb: 0.5 }} />
{previewText ? (
<Typography
variant="caption"
sx={{
fontSize: isListView ? '0.75rem' : '0.65rem',
color: '#9ca3af',
overflow: 'hidden',
textOverflow: 'ellipsis',
display: '-webkit-box',
WebkitLineClamp: isListView ? 3 : 2,
WebkitBoxOrient: 'vertical',
lineHeight: 1.3,
mb: 0.5,
}}
>
{previewLines || previewText.substring(0, 100)}
</Typography>
) : (
<Typography variant="caption" sx={{ fontSize: '0.7rem', color: '#9ca3af' }}>
Click to preview
</Typography>
)}
{hasMore && (
<IconButton
size="small"
onClick={(e) => {
e.stopPropagation();
toggleTextPreview(asset);
}}
sx={{ position: 'absolute', bottom: 4, right: 4, p: 0.5 }}
>
<ExpandMore sx={{ fontSize: 16, color: '#d1d5db' }} />
</IconButton>
)}
</>
)}
</Box>
);
} else {
return (
<Box
sx={{
width: 80,
height: 80,
borderRadius: 1,
background: 'rgba(107,114,128,0.2)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
border: '1px solid rgba(255,255,255,0.1)',
cursor: 'pointer',
}}
onClick={() => window.open(asset.file_url, '_blank')}
>
<TextFields sx={{ color: '#d1d5db', fontSize: 32 }} />
</Box>
);
}
};
const getModelName = (asset: ContentAsset) => {
if (asset.model) return asset.model;
if (asset.provider) return `${asset.provider}/${asset.source_module.replace('_', ' ')}`;
return asset.source_module.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
};
const filteredAssets = useMemo(() => {
let filtered = assets;
@@ -674,115 +389,21 @@ export const AssetLibrary: React.FC = () => {
<Divider sx={{ borderColor: 'rgba(255,255,255,0.08)' }} />
{/* Advanced Search and Filters */}
<Grid container spacing={2}>
<Grid item xs={12} sm={6} md={2}>
<TextField
fullWidth
size="small"
placeholder="ID"
value={idSearch}
onChange={e => setIdSearch(e.target.value)}
sx={{
'& .MuiOutlinedInput-root': {
background: 'rgba(15,23,42,0.5)',
color: '#f8fafc',
},
}}
/>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<TextField
fullWidth
size="small"
placeholder="Search model..."
value={modelSearch}
onChange={e => setModelSearch(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Search sx={{ color: 'rgba(255,255,255,0.6)', fontSize: 18 }} />
</InputAdornment>
),
}}
sx={{
'& .MuiOutlinedInput-root': {
background: 'rgba(15,23,42,0.5)',
color: '#f8fafc',
},
}}
/>
</Grid>
<Grid item xs={12} sm={6} md={2}>
<TextField
fullWidth
size="small"
type="date"
placeholder="Pick a date"
value={dateFilter}
onChange={e => setDateFilter(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<CalendarToday sx={{ color: 'rgba(255,255,255,0.6)', fontSize: 18 }} />
</InputAdornment>
),
}}
sx={{
'& .MuiOutlinedInput-root': {
background: 'rgba(15,23,42,0.5)',
color: '#f8fafc',
},
}}
/>
</Grid>
<Grid item xs={12} sm={6} md={2}>
<FormControl fullWidth size="small">
<InputLabel sx={{ color: 'rgba(255,255,255,0.6)' }}>Status</InputLabel>
<Select
value={statusFilter}
onChange={e => setStatusFilter(e.target.value)}
label="Status"
sx={{
background: 'rgba(15,23,42,0.5)',
color: '#f8fafc',
'& .MuiOutlinedInput-notchedOutline': {
borderColor: 'rgba(255,255,255,0.2)',
},
}}
>
<MenuItem value="all">All</MenuItem>
<MenuItem value="completed">Completed</MenuItem>
<MenuItem value="processing">Processing</MenuItem>
<MenuItem value="failed">Failed</MenuItem>
<MenuItem value="pending">Pending</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<FormControl fullWidth size="small">
<InputLabel sx={{ color: 'rgba(255,255,255,0.6)' }}>Type</InputLabel>
<Select
value={filterType}
onChange={e => setFilterType(e.target.value)}
label="Type"
sx={{
background: 'rgba(15,23,42,0.5)',
color: '#f8fafc',
'& .MuiOutlinedInput-notchedOutline': {
borderColor: 'rgba(255,255,255,0.2)',
},
}}
>
<MenuItem value="all">All Assets</MenuItem>
<MenuItem value="images">Images</MenuItem>
<MenuItem value="videos">Videos</MenuItem>
<MenuItem value="audio">Audio</MenuItem>
<MenuItem value="text">Text</MenuItem>
<MenuItem value="favorites">Favorites</MenuItem>
</Select>
</FormControl>
</Grid>
</Grid>
<AssetFiltersComponent
idSearch={idSearch}
setIdSearch={setIdSearch}
modelSearch={modelSearch}
setModelSearch={setModelSearch}
dateFilter={dateFilter}
setDateFilter={setDateFilter}
statusFilter={statusFilter}
setStatusFilter={setStatusFilter}
filterType={filterType}
setFilterType={setFilterType}
searchQuery={searchQuery}
setSearchQuery={setSearchQuery}
onSearch={onSearch}
/>
{/* Bulk Actions */}
{selectedAssets.size > 0 && (
@@ -966,131 +587,26 @@ export const AssetLibrary: React.FC = () => {
</TableHead>
<TableBody>
{filteredAssets.map(asset => (
<TableRow
<AssetTableRow
key={asset.id}
sx={{
'&:hover': { background: 'rgba(255,255,255,0.05)' },
cursor: 'pointer',
asset={asset}
isSelected={selectedAssets.has(asset.id)}
onSelect={(checked) => 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' });
}}
>
<TableCell padding="checkbox">
<Checkbox
checked={selectedAssets.has(asset.id)}
onChange={e => handleSelectAsset(asset.id, e.target.checked)}
onClick={e => e.stopPropagation()}
sx={{ color: 'rgba(255,255,255,0.6)' }}
/>
</TableCell>
<TableCell>
<Typography
variant="body2"
sx={{
color: '#c7d2fe',
fontFamily: 'monospace',
fontSize: '0.75rem',
cursor: 'pointer',
'&:hover': { textDecoration: 'underline' },
}}
onClick={() => {
navigator.clipboard.writeText(String(asset.id));
setSnackbar({ open: true, message: 'ID copied', severity: 'success' });
}}
>
{String(asset.id).slice(0, 8)}...
</Typography>
</TableCell>
<TableCell>
<Typography
variant="body2"
sx={{
color: '#f8fafc',
cursor: 'pointer',
'&:hover': { textDecoration: 'underline' },
}}
>
{getModelName(asset)}
</Typography>
</TableCell>
<TableCell>{getStatusChip(asset.asset_metadata?.status || 'completed')}</TableCell>
<TableCell>{getAssetPreview(asset, true)}</TableCell>
<TableCell>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.7)', fontSize: '0.875rem' }}>
{formatDate(asset.created_at)}
</Typography>
</TableCell>
<TableCell>
<Stack direction="row" spacing={0.5}>
<Tooltip title="Upload">
<IconButton size="small" sx={{ color: 'rgba(255,255,255,0.6)' }}>
<Upload fontSize="small" />
</IconButton>
</Tooltip>
<Tooltip title="Download">
<IconButton
size="small"
onClick={() => handleDownload(asset)}
sx={{ color: 'rgba(255,255,255,0.6)' }}
>
<Download fontSize="small" />
</IconButton>
</Tooltip>
<Tooltip title="More">
<IconButton
size="small"
onClick={e => handleMenuOpen(asset.id, e)}
sx={{ color: 'rgba(255,255,255,0.6)' }}
>
<MoreVert fontSize="small" />
</IconButton>
</Tooltip>
<Menu
anchorEl={anchorEl[asset.id]}
open={Boolean(anchorEl[asset.id])}
onClose={() => handleMenuClose(asset.id)}
>
{/* Restore Research Project option for research_tools assets */}
{asset.source_module === 'research_tools' && asset.asset_type === 'text' && asset.asset_metadata?.project_type === 'research_project' && (
<MenuItem
onClick={() => {
handleRestoreResearchProject(asset);
handleMenuClose(asset.id);
}}
sx={{ color: '#667eea' }}
>
<ListItemIcon>
<Box sx={{ color: '#667eea', fontSize: 20 }}>🔬</Box>
</ListItemIcon>
<ListItemText>Restore in Researcher</ListItemText>
</MenuItem>
)}
<MenuItem onClick={() => { handleFavorite(asset.id); handleMenuClose(asset.id); }}>
<ListItemIcon>
{asset.is_favorite ? <Favorite fontSize="small" /> : <FavoriteBorder fontSize="small" />}
</ListItemIcon>
<ListItemText>{asset.is_favorite ? 'Remove Favorite' : 'Add Favorite'}</ListItemText>
</MenuItem>
<MenuItem onClick={() => { handleShare(asset); handleMenuClose(asset.id); }}>
<ListItemIcon>
<Share fontSize="small" />
</ListItemIcon>
<ListItemText>Share</ListItemText>
</MenuItem>
<MenuItem
onClick={() => {
handleDelete(asset.id);
handleMenuClose(asset.id);
}}
sx={{ color: '#ef4444' }}
>
<ListItemIcon>
<Delete fontSize="small" sx={{ color: '#ef4444' }} />
</ListItemIcon>
<ListItemText>Delete</ListItemText>
</MenuItem>
</Menu>
</Stack>
</TableCell>
</TableRow>
/>
))}
</TableBody>
</Table>
@@ -1099,216 +615,16 @@ export const AssetLibrary: React.FC = () => {
<Grid container spacing={3}>
{filteredAssets.map(asset => (
<Grid item xs={12} sm={6} md={4} lg={3} key={asset.id}>
<Card
sx={{
background: 'rgba(15,23,42,0.5)',
border: '1px solid rgba(255,255,255,0.08)',
borderRadius: 3,
overflow: 'hidden',
transition: 'transform 0.2s ease, box-shadow 0.2s ease',
'&:hover': {
transform: 'translateY(-4px)',
boxShadow: '0 10px 25px rgba(124,58,237,0.25)',
},
}}
>
<Box sx={{ position: 'relative', aspectRatio: asset.asset_type === 'video' ? '16/9' : '1' }}>
{asset.asset_type === 'image' ? (
<CardMedia
component="img"
image={asset.file_url}
alt={asset.title || asset.filename}
sx={{
width: '100%',
height: '100%',
objectFit: 'cover',
}}
/>
) : asset.asset_type === 'video' ? (
<Box
component="video"
src={asset.file_url}
controls
sx={{
width: '100%',
height: '100%',
objectFit: 'cover',
}}
/>
) : asset.asset_type === 'text' ? (
<Box
sx={{
width: '100%',
height: '100%',
p: 2,
background: 'rgba(107,114,128,0.2)',
color: '#d1d5db',
overflow: 'auto',
display: 'flex',
flexDirection: 'column',
}}
>
{textPreviews[asset.id]?.loading ? (
<CircularProgress size={24} sx={{ m: 'auto' }} />
) : textPreviews[asset.id]?.expanded ? (
<>
<Typography
variant="body2"
sx={{
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
flex: 1,
fontFamily: 'monospace',
fontSize: '0.875rem',
lineHeight: 1.6,
}}
>
{textPreviews[asset.id].content}
</Typography>
<IconButton
size="small"
onClick={(e) => {
e.stopPropagation();
toggleTextPreview(asset);
}}
sx={{ alignSelf: 'flex-end', mt: 1 }}
>
<ExpandLess />
</IconButton>
</>
) : (
<>
<TextFields sx={{ fontSize: 48, mb: 1, opacity: 0.7 }} />
<Typography variant="body2" sx={{ textAlign: 'center', mb: 1 }}>
Text Content
</Typography>
<Button
size="small"
variant="outlined"
onClick={(e) => {
e.stopPropagation();
toggleTextPreview(asset);
}}
sx={{ mt: 'auto' }}
>
Preview
</Button>
</>
)}
</Box>
) : (
<Box
sx={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'rgba(99,102,241,0.2)',
color: '#c7d2fe',
}}
>
{getAssetIcon(asset.asset_type)}
</Box>
)}
<Box
sx={{
position: 'absolute',
top: 8,
right: 8,
display: 'flex',
gap: 1,
}}
>
<IconButton
size="small"
onClick={() => handleFavorite(asset.id)}
sx={{
background: 'rgba(15,23,42,0.8)',
color: asset.is_favorite ? '#fbbf24' : 'rgba(255,255,255,0.6)',
'&:hover': { background: 'rgba(15,23,42,0.95)' },
}}
>
{asset.is_favorite ? <Favorite /> : <FavoriteBorder />}
</IconButton>
</Box>
<Box
sx={{
position: 'absolute',
top: 8,
left: 8,
}}
>
<Chip
label={asset.source_module.replace(/_/g, ' ')}
size="small"
sx={{
background: 'rgba(15,23,42,0.8)',
color: '#c7d2fe',
fontSize: '0.7rem',
}}
/>
</Box>
</Box>
<CardContent>
<Typography variant="subtitle2" fontWeight={600} gutterBottom noWrap>
{asset.title || asset.filename}
</Typography>
<Stack direction="row" spacing={1} flexWrap="wrap" sx={{ mb: 1 }}>
{getStatusChip(asset.asset_metadata?.status || 'completed')}
<Chip
label={asset.asset_type}
size="small"
sx={{ background: 'rgba(99,102,241,0.2)', color: '#c7d2fe' }}
/>
{asset.cost > 0 && (
<Chip
label={`$${asset.cost.toFixed(2)}`}
size="small"
sx={{ background: 'rgba(16,185,129,0.2)', color: '#6ee7b7' }}
/>
)}
</Stack>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.5)', display: 'block', mb: 1 }}>
{formatDate(asset.created_at)}
</Typography>
<Stack direction="row" spacing={1} justifyContent="flex-end">
{/* Restore Research Project button for research_tools assets */}
{asset.source_module === 'research_tools' && asset.asset_type === 'text' && asset.asset_metadata?.project_type === 'research_project' && (
<Tooltip title="Restore in Researcher">
<IconButton
size="small"
onClick={() => handleRestoreResearchProject(asset)}
sx={{ color: '#667eea' }}
>
<Box sx={{ fontSize: 20 }}>🔬</Box>
</IconButton>
</Tooltip>
)}
<IconButton
size="small"
onClick={() => handleDownload(asset)}
sx={{ color: 'rgba(255,255,255,0.6)' }}
>
<Download />
</IconButton>
<IconButton
size="small"
onClick={() => handleShare(asset)}
sx={{ color: 'rgba(255,255,255,0.6)' }}
>
<Share />
</IconButton>
<IconButton
size="small"
onClick={() => handleDelete(asset.id)}
sx={{ color: 'rgba(255,255,255,0.6)' }}
>
<Delete />
</IconButton>
</Stack>
</CardContent>
</Card>
<AssetCard
asset={asset}
textPreview={textPreviews[asset.id]}
onToggleTextPreview={toggleTextPreview}
onFavorite={handleFavorite}
onDownload={handleDownload}
onShare={handleShare}
onDelete={handleDelete}
onRestore={handleRestoreResearchProject}
/>
</Grid>
))}
</Grid>
@@ -1352,18 +668,3 @@ export const AssetLibrary: React.FC = () => {
</ImageStudioLayout>
);
};
const getAssetIcon = (assetType: string) => {
switch (assetType) {
case 'image':
return <ImageIcon />;
case 'video':
return <VideoLibrary />;
case 'audio':
return <AudioFile />;
case 'text':
return <TextFields />;
default:
return <ImageIcon />;
}
};