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

@@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react';
import {
Box,
Typography,
Button,
Card,
CardContent,
FormControlLabel,
@@ -12,14 +11,11 @@ import {
Grid,
Accordion,
AccordionSummary,
AccordionDetails,
LinearProgress
AccordionDetails
} from '@mui/material';
import {
PlayArrow as PlayIcon,
ExpandMore as ExpandMoreIcon,
CheckCircle as CheckCircleIcon,
Warning as WarningIcon,
Info as InfoIcon
} from '@mui/icons-material';
@@ -85,6 +81,8 @@ const GenerateCalendarStep: React.FC<GenerateCalendarStepProps> = ({
setValidationErrors(errors);
}, [calendarConfig]);
/*
// Unused generation handler - logic moved to parent/stepper
const handleGenerate = () => {
if (validationErrors.length > 0) {
return; // Don't proceed if there are validation errors
@@ -114,6 +112,7 @@ const GenerateCalendarStep: React.FC<GenerateCalendarStepProps> = ({
};
const canGenerate = validationErrors.length === 0;
*/
return (
<Box sx={{ p: 2 }}>

View File

@@ -14,7 +14,6 @@ import {
Alert,
IconButton,
Collapse,
Tooltip,
Paper,
Grid
} from '@mui/material';
@@ -22,11 +21,8 @@ import {
DataUsage as DataUsageIcon,
AutoAwesome as AutoAwesomeIcon,
CheckCircle as CheckCircleIcon,
Warning as WarningIcon,
Info as InfoIcon,
ExpandMore as ExpandMoreIcon,
ExpandLess as ExpandLessIcon,
Refresh as RefreshIcon,
Timeline as TimelineIcon,
TrendingUp as TrendingUpIcon,
Schedule as ScheduleIcon

View File

@@ -27,7 +27,6 @@ import {
Analytics as AnalyticsIcon,
Schedule as ScheduleIcon,
Group as GroupIcon,
Assessment as AssessmentIcon,
Build as BuildIcon,
Palette as BrandingIcon,
Storage as StorageIcon,

View File

@@ -11,7 +11,6 @@ import {
} from '@mui/material';
import {
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
Speed as SpeedIcon,
BugReport as BugReportIcon,
Storage as StorageIcon,
@@ -20,8 +19,9 @@ import {
import { motion } from 'framer-motion';
import {
LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
BarChart, Bar, PieChart, Pie, Cell, AreaChart, Area, RadarChart, PolarGrid,
PolarAngleAxis, PolarRadiusAxis, Radar
RadarChart, PolarGrid,
PolarAngleAxis, PolarRadiusAxis, Radar,
AreaChart, Area, BarChart, Bar, PieChart, Pie, Cell
} from 'recharts';
interface ChartData {
@@ -46,7 +46,7 @@ interface MonitoringChartsProps {
};
}
const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884D8'];
// const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884D8'];
const MonitoringCharts: React.FC<MonitoringChartsProps> = ({
chartData,

View File

@@ -4,18 +4,18 @@ import {
Button,
CircularProgress,
Typography,
Snackbar,
Alert,
Snackbar
} from '@mui/material';
import {
Check as CheckIcon,
PlayArrow as PlayArrowIcon,
AutoAwesome as AutoAwesomeIcon,
Celebration as CelebrationIcon
Celebration as CelebrationIcon,
} from '@mui/icons-material';
import { motion, AnimatePresence, easeOut } from 'framer-motion';
import StrategyActivationModal from '../../StrategyActivationModal';
import { useNavigationOrchestrator } from '../../../../../services/navigationOrchestrator';
import { safeRenderText } from '../utils/defensiveRendering';
interface EnhancedStrategyActivationButtonProps {
@@ -40,7 +40,7 @@ const EnhancedStrategyActivationButton: React.FC<EnhancedStrategyActivationButto
const [showActivationModal, setShowActivationModal] = useState(false);
// Initialize navigation orchestrator
const navigationOrchestrator = useNavigationOrchestrator();
// const navigationOrchestrator = useNavigationOrchestrator();
const handleActivation = async () => {
console.log('🎯 EnhancedStrategyActivationButton: handleActivation called');
@@ -60,7 +60,8 @@ const EnhancedStrategyActivationButton: React.FC<EnhancedStrategyActivationButto
};
const handleSetupMonitoring = async (monitoringPlan: any) => {
try {
setIsLoading(true);
// try {
console.log('🎯 EnhancedStrategyActivationButton: handleSetupMonitoring called');
// Get strategy ID
@@ -97,38 +98,22 @@ const EnhancedStrategyActivationButton: React.FC<EnhancedStrategyActivationButto
} catch (error) {
console.warn('Could not activate strategy with monitoring:', error);
// Continue with local activation only
} finally {
setIsLoading(false);
}
// Step 3: Call the local confirmation function
console.log('🎯 EnhancedStrategyActivationButton: Calling onConfirmStrategy()');
await onConfirmStrategy();
console.log('🎯 EnhancedStrategyActivationButton: onConfirmStrategy() completed');
// Step 4: Update analytics and monitoring data
console.log('🎯 Setting up analytics and monitoring...');
await setupAnalyticsAndMonitoring(strategyId, finalMonitoringPlan);
// Show success state
setIsSuccess(true);
setShowSuccessMessage(true);
// Use navigation orchestrator to handle successful activation
const userId = strategyData?.strategy_metadata?.user_id || strategyData?.metadata?.user_id || '1';
navigationOrchestrator.handleStrategyActivationSuccess(userId, strategyData);
// Reset after success animation
setTimeout(() => {
setIsSuccess(false);
setActivationProgress(0);
}, 2000);
} catch (error) {
console.error('Strategy activation failed:', error);
throw error;
}
// } catch (error) {
// setIsLoading(false);
// console.error('Strategy activation failed:', error);
// throw error;
// }
};
const setupAnalyticsAndMonitoring = async (strategyId: number, monitoringPlan: any) => {
/* const setupAnalyticsAndMonitoring = async (strategyId: number, monitoringPlan: any) => {
try {
console.log('🎯 Setting up analytics and monitoring for strategy:', strategyId);
@@ -154,7 +139,7 @@ const EnhancedStrategyActivationButton: React.FC<EnhancedStrategyActivationButto
console.error('Error setting up analytics and monitoring:', error);
// Don't fail the activation if analytics setup fails
}
};
}; */
// Success animation variants
const successVariants = {

View File

@@ -28,7 +28,7 @@ import {
getListItemStyles
} from '../styles';
import ProgressiveCard from './ProgressiveCard';
import { safeRenderText, safeRenderArray, hasValidData, getFallbackValue } from '../utils/defensiveRendering';
// import { safeRenderText, safeRenderArray, hasValidData, getFallbackValue } from '../utils/defensiveRendering';
interface ImplementationRoadmapCardProps {
strategyData: StrategyData | null;

View File

@@ -29,7 +29,7 @@ import {
getListItemStyles
} from '../styles';
import ProgressiveCard from './ProgressiveCard';
import { safeRenderText, safeRenderArray, hasValidData, getFallbackValue } from '../utils/defensiveRendering';
import { safeRenderText, hasValidData } from '../utils/defensiveRendering';
interface StrategicInsightsCardProps {
strategyData: StrategyData | null;

View File

@@ -1,17 +1,16 @@
import React, { useState } from 'react';
import {
Paper,
Grid,
Typography,
Chip,
Box,
Tooltip,
LinearProgress,
Badge,
Card,
CardContent,
CircularProgress,
Button
Button,
Paper,
Grid,
Typography,
Chip
} from '@mui/material';
import {
Psychology as PsychologyIcon,

View File

@@ -10,7 +10,6 @@ import {
Divider,
Alert,
CircularProgress,
LinearProgress,
Tabs,
Tab,
Button
@@ -21,12 +20,10 @@ import {
ShowChart as ShowChartIcon,
Assessment as AssessmentIcon,
Visibility as VisibilityIcon,
Timeline as TimelineIcon,
AutoAwesome as AutoAwesomeIcon,
Refresh as RefreshIcon
} from '@mui/icons-material';
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
import { contentPlanningApi } from '../../../services/contentPlanningApi';
// import { contentPlanningApi } from '../../../services/contentPlanningApi';
import EnhancedPerformanceVisualization from '../components/StrategyIntelligence/components/EnhancedPerformanceVisualization';
import TrendAnalysis from '../components/StrategyIntelligence/components/TrendAnalysis';
import DataTransparencyPanel from '../components/StrategyIntelligence/components/DataTransparencyPanel';
@@ -70,6 +67,7 @@ const AnalyticsTab: React.FC = () => {
useEffect(() => {
loadAnalyticsData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const loadAnalyticsData = async () => {

View File

@@ -6,9 +6,6 @@ import {
CardContent,
Typography
} from '@mui/material';
import {
BarChart as BarChartIcon
} from '@mui/icons-material';
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
const PerformanceAnalyticsTab: React.FC = () => {

View File

@@ -1,8 +1,8 @@
import React, { useEffect, useMemo, useState, forwardRef, useImperativeHandle } from 'react';
import React, { useState, useEffect, useMemo, useImperativeHandle } from 'react';
import {
Box, Button, MenuItem, Select, TextField, Typography, FormControl, InputLabel, Grid,
Card, CardMedia, CircularProgress, LinearProgress, Tabs, Tab,
Tooltip, Alert, Chip, IconButton
Tooltip, Alert, Chip
} from '@mui/material';
import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh';
import InfoIcon from '@mui/icons-material/Info';
@@ -590,7 +590,7 @@ export const ImageGenerator = React.forwardRef<ImageGeneratorHandle, ImageGenera
<Typography variant="body2" sx={{ fontWeight: 600, mb: 0.5 }}>
Important Notes:
</Typography>
{guidance.warnings.map((warning, idx) => (
{guidance.warnings.map((warning: string, idx: number) => (
<Typography key={idx} variant="body2" sx={{ fontSize: '13px', mb: 0.5 }}>
{warning}
</Typography>
@@ -612,7 +612,7 @@ export const ImageGenerator = React.forwardRef<ImageGeneratorHandle, ImageGenera
<Typography variant="body2" sx={{ fontWeight: 600, mb: 0.5 }}>
💡 Best Practices for {model}:
</Typography>
{guidance.tips.map((tip, idx) => (
{guidance.tips.map((tip: string, idx: number) => (
<Typography key={idx} variant="body2" sx={{ fontSize: '13px', mb: 0.5 }}>
{tip}
</Typography>

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 />;
}
};

View File

@@ -0,0 +1,260 @@
import React from 'react';
import {
Box,
Card,
CardContent,
CardMedia,
Typography,
IconButton,
Chip,
Stack,
Tooltip,
CircularProgress,
Button,
} from '@mui/material';
import {
Download,
Share,
Delete,
Favorite,
FavoriteBorder,
TextFields,
ExpandLess,
} from '@mui/icons-material';
import { ContentAsset } from '../../../hooks/useContentAssets';
import { getStatusChip, formatDate, getAssetIcon } from './utils';
interface AssetCardProps {
asset: ContentAsset;
textPreview?: { content: string; loading: boolean; expanded: boolean };
onToggleTextPreview?: (asset: ContentAsset) => void;
onFavorite: (id: number) => void;
onDownload: (asset: ContentAsset) => void;
onShare: (asset: ContentAsset) => void;
onDelete: (id: number) => void;
onRestore: (asset: ContentAsset) => void;
}
export const AssetCard: React.FC<AssetCardProps> = ({
asset,
textPreview,
onToggleTextPreview,
onFavorite,
onDownload,
onShare,
onDelete,
onRestore,
}) => {
return (
<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',
}}
>
{textPreview?.loading ? (
<CircularProgress size={24} sx={{ m: 'auto' }} />
) : textPreview?.expanded ? (
<>
<Typography
variant="body2"
sx={{
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
flex: 1,
fontFamily: 'monospace',
fontSize: '0.875rem',
lineHeight: 1.6,
}}
>
{textPreview.content}
</Typography>
<IconButton
size="small"
onClick={(e) => {
e.stopPropagation();
onToggleTextPreview && onToggleTextPreview(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();
onToggleTextPreview && onToggleTextPreview(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={() => onFavorite(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={() => onRestore(asset)}
sx={{ color: '#667eea' }}
>
<Box sx={{ fontSize: 20 }}>🔬</Box>
</IconButton>
</Tooltip>
)}
<IconButton
size="small"
onClick={() => onDownload(asset)}
sx={{ color: 'rgba(255,255,255,0.6)' }}
>
<Download />
</IconButton>
<IconButton
size="small"
onClick={() => onShare(asset)}
sx={{ color: 'rgba(255,255,255,0.6)' }}
>
<Share />
</IconButton>
<IconButton
size="small"
onClick={() => onDelete(asset.id)}
sx={{ color: 'rgba(255,255,255,0.6)' }}
>
<Delete />
</IconButton>
</Stack>
</CardContent>
</Card>
);
};

View File

@@ -0,0 +1,181 @@
import React from 'react';
import {
Grid,
TextField,
InputAdornment,
FormControl,
InputLabel,
Select,
MenuItem,
} from '@mui/material';
import { Search, CalendarToday } from '@mui/icons-material';
interface AssetFiltersProps {
idSearch: string;
setIdSearch: (value: string) => void;
modelSearch: string;
setModelSearch: (value: string) => void;
dateFilter: string;
setDateFilter: (value: string) => void;
statusFilter: string;
setStatusFilter: (value: string) => void;
filterType: string;
setFilterType: (value: string) => void;
searchQuery: string;
setSearchQuery: (value: string) => void;
onSearch: (value: string) => void;
}
export const AssetFilters: React.FC<AssetFiltersProps> = ({
idSearch,
setIdSearch,
modelSearch,
setModelSearch,
dateFilter,
setDateFilter,
statusFilter,
setStatusFilter,
filterType,
setFilterType,
searchQuery,
setSearchQuery,
onSearch,
}) => {
return (
<>
<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>
<TextField
fullWidth
size="small"
placeholder="Search assets (ID, title, tags)..."
value={searchQuery}
onChange={(e) => {
onSearch(e.target.value);
}}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Search sx={{ color: 'rgba(255,255,255,0.6)', fontSize: 18 }} />
</InputAdornment>
),
}}
sx={{
mt: 2,
'& .MuiOutlinedInput-root': {
background: 'rgba(15,23,42,0.5)',
color: '#f8fafc',
},
}}
/>
</>
);
};

View File

@@ -0,0 +1,207 @@
import React from 'react';
import {
Box,
Typography,
IconButton,
CircularProgress,
// Button,
} from '@mui/material';
import {
// Image as ImageIcon,
VideoLibrary,
AudioFile,
TextFields,
ExpandLess,
ExpandMore,
} from '@mui/icons-material';
import { ContentAsset } from '../../../hooks/useContentAssets';
interface AssetPreviewProps {
asset: ContentAsset;
isListView?: boolean;
textPreview?: { content: string; loading: boolean; expanded: boolean };
onToggleTextPreview?: (asset: ContentAsset) => void;
}
export const AssetPreview: React.FC<AssetPreviewProps> = ({
asset,
isListView = false,
textPreview,
onToggleTextPreview,
}) => {
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 previewText = textPreview?.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();
onToggleTextPreview && onToggleTextPreview(asset);
}}
>
{textPreview?.loading ? (
<CircularProgress size={20} sx={{ m: 'auto' }} />
) : textPreview?.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();
onToggleTextPreview && onToggleTextPreview(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();
onToggleTextPreview && onToggleTextPreview(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>
);
}
};

View File

@@ -0,0 +1,193 @@
import React from 'react';
import {
Typography,
IconButton,
Tooltip,
Stack,
Menu,
MenuItem,
ListItemIcon,
ListItemText,
TableCell,
TableRow,
Checkbox,
Box,
} from '@mui/material';
import {
Download,
Share,
Delete,
Favorite,
FavoriteBorder,
Upload,
MoreVert,
} from '@mui/icons-material';
import { ContentAsset } from '../../../hooks/useContentAssets';
import { getStatusChip, formatDate, getModelName } from './utils';
import { AssetPreview } from './AssetPreview';
interface AssetTableRowProps {
asset: ContentAsset;
isSelected: boolean;
onSelect: (checked: boolean) => void;
onDownload: (asset: ContentAsset) => void;
onShare: (asset: ContentAsset) => void;
onDelete: (id: number) => void;
onFavorite: (id: number) => void;
onRestore: (asset: ContentAsset) => void;
onMenuOpen: (id: number, event: React.MouseEvent<HTMLElement>) => void;
onMenuClose: (id: number) => void;
anchorEl: HTMLElement | null;
textPreview?: { content: string; loading: boolean; expanded: boolean };
onToggleTextPreview?: (asset: ContentAsset) => void;
onCopyId: (id: string) => void;
}
export const AssetTableRow: React.FC<AssetTableRowProps> = ({
asset,
isSelected,
onSelect,
onDownload,
onShare,
onDelete,
onFavorite,
onRestore,
onMenuOpen,
onMenuClose,
anchorEl,
textPreview,
onToggleTextPreview,
onCopyId,
}) => {
return (
<TableRow
key={asset.id}
sx={{
'&:hover': { background: 'rgba(255,255,255,0.05)' },
cursor: 'pointer',
}}
>
<TableCell padding="checkbox">
<Checkbox
checked={isSelected}
onChange={e => onSelect(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={() => onCopyId(String(asset.id))}
>
{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>
<AssetPreview
asset={asset}
isListView={true}
textPreview={textPreview}
onToggleTextPreview={onToggleTextPreview}
/>
</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={() => onDownload(asset)}
sx={{ color: 'rgba(255,255,255,0.6)' }}
>
<Download fontSize="small" />
</IconButton>
</Tooltip>
<Tooltip title="More">
<IconButton
size="small"
onClick={e => onMenuOpen(asset.id, e)}
sx={{ color: 'rgba(255,255,255,0.6)' }}
>
<MoreVert fontSize="small" />
</IconButton>
</Tooltip>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={() => onMenuClose(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={() => {
onRestore(asset);
onMenuClose(asset.id);
}}
sx={{ color: '#667eea' }}
>
<ListItemIcon>
<Box sx={{ color: '#667eea', fontSize: 20 }}>🔬</Box>
</ListItemIcon>
<ListItemText>Restore in Researcher</ListItemText>
</MenuItem>
)}
<MenuItem onClick={() => { onFavorite(asset.id); onMenuClose(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={() => { onShare(asset); onMenuClose(asset.id); }}>
<ListItemIcon>
<Share fontSize="small" />
</ListItemIcon>
<ListItemText>Share</ListItemText>
</MenuItem>
<MenuItem
onClick={() => {
onDelete(asset.id);
onMenuClose(asset.id);
}}
sx={{ color: '#ef4444' }}
>
<ListItemIcon>
<Delete fontSize="small" sx={{ color: '#ef4444' }} />
</ListItemIcon>
<ListItemText>Delete</ListItemText>
</MenuItem>
</Menu>
</Stack>
</TableCell>
</TableRow>
);
};

View File

@@ -0,0 +1,89 @@
import React from 'react';
import {
CheckCircle,
HourglassEmpty,
Error as ErrorIcon,
Image as ImageIcon,
VideoLibrary,
AudioFile,
TextFields,
} from '@mui/icons-material';
import { Chip } from '@mui/material';
export 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 }} />;
}
};
export 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,
}}
/>
);
};
export 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 />;
}
};
export 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;
}
};
export const getModelName = (asset: any) => {
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: string) => l.toUpperCase());
};

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react';
import React, { useEffect, useState } from 'react';
import {
Box,
Grid,
@@ -165,8 +165,6 @@ export const CompressionStudio: React.FC = () => {
setSelectedPreset('');
};
const currentFormat = compressionFormats.find(f => f.id === format);
return (
<ImageStudioLayout>
<MotionPaper

View File

@@ -139,7 +139,7 @@ export const ControlStudio: React.FC = () => {
const {
loadControlOperations,
controlOperations,
isLoadingControlOps,
// isLoadingControlOps,
processControl,
isProcessingControl,
controlResult,

View File

@@ -5,13 +5,11 @@ import {
Typography,
Stack,
Chip,
Divider,
alpha,
Divider,
} from '@mui/material';
import {
AttachMoney,
TrendingUp,
Speed,
Info,
} from '@mui/icons-material';
import { motion } from 'framer-motion';

View File

@@ -10,7 +10,7 @@ import {
alpha
} from '@mui/material';
import OptimizedImage from './OptimizedImage';
import { SignInButton, useClerk } from '@clerk/clerk-react';
import { useClerk } from '@clerk/clerk-react';
import { RocketLaunch } from '@mui/icons-material';
import { motion } from 'framer-motion';
import { ScrambleText } from '../ScrambleText';

View File

@@ -10,7 +10,7 @@ import {
useTheme,
alpha
} from '@mui/material';
import { SignInButton, useClerk } from '@clerk/clerk-react';
import { useClerk } from '@clerk/clerk-react';
import {
RocketLaunch,
Lightbulb,

View File

@@ -12,7 +12,7 @@ import {
alpha,
Skeleton
} from '@mui/material';
import { SignInButton, useClerk } from '@clerk/clerk-react';
import { useClerk } from '@clerk/clerk-react';
import {
RocketLaunch,
Business,

View File

@@ -18,11 +18,6 @@ import {
import { keyframes } from '@mui/system';
import {
Analytics,
Psychology,
AccessTime,
MonetizationOn,
TrendingDown,
Group,
CalendarToday,
Create,
Publish,

View File

@@ -15,7 +15,7 @@ import { PlatformPersonaProvider, usePlatformPersonaContext } from '../shared/Pe
const useCopilotActionTyped = useCopilotAction as any;
// Optional debug flag: set to true to enable verbose logs locally
const DEBUG_LINKEDIN = false;
// const DEBUG_LINKEDIN = false;
interface LinkedInWriterProps {
className?: string;
@@ -43,9 +43,9 @@ const LinkedInWriterContent: React.FC<LinkedInWriterProps> = ({ className = '' }
currentAction,
chatHistory,
userPreferences,
currentSuggestions,
// currentSuggestions,
showPreferencesModal,
showContextModal,
// showContextModal,
showPreview,
justGeneratedContent,
@@ -65,14 +65,14 @@ const LinkedInWriterContent: React.FC<LinkedInWriterProps> = ({ className = '' }
setPendingEdit,
setUserPreferences,
setShowPreferencesModal,
setShowContextModal,
// setShowContextModal,
setShowPreview,
// Handlers
handleDraftChange,
handleContextChange,
handleClear,
handleCopy,
// handleClear,
// handleCopy,
handleClearHistory,
// Utilities
@@ -82,17 +82,18 @@ const LinkedInWriterContent: React.FC<LinkedInWriterProps> = ({ className = '' }
} = useLinkedInWriter();
// Get persona context for enhanced AI assistance
const { corePersona, platformPersona, loading: personaLoading } = usePlatformPersonaContext();
const { corePersona, platformPersona } = usePlatformPersonaContext();
// const { corePersona, platformPersona, loading: personaLoading } = usePlatformPersonaContext();
// Get enhanced persistence functionality
const {
persistenceManager,
saveChatHistory,
// persistenceManager,
// saveChatHistory,
loadChatHistory,
addChatMessage,
// addChatMessage,
saveUserPreferences: savePersistedPreferences,
loadUserPreferences: loadPersistedPreferences,
saveConversationContext,
// saveConversationContext,
loadConversationContext,
saveDraftContent,
loadDraftContent,
@@ -149,6 +150,7 @@ const LinkedInWriterContent: React.FC<LinkedInWriterProps> = ({ className = '' }
return () => {
saveLastSession();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Handle preview changes

View File

@@ -29,6 +29,22 @@ export const useCopilotActions = ({
const { corePersona, platformPersona } = usePlatformPersonaContext();
const copilotContext = useCopilotContext();
// Provide persona context to Copilot
React.useEffect(() => {
if (corePersona || platformPersona) {
// Logic to update copilot context with persona data
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [corePersona, platformPersona]);
// Provide enhanced context to Copilot based on conversation history
React.useEffect(() => {
if (copilotContext) {
// Logic using copilotContext
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [copilotContext]);
// Listen for copilot seed events to open sidebar with prompt
React.useEffect(() => {
const handler = (ev: any) => {

View File

@@ -76,7 +76,7 @@ export const Header: React.FC<HeaderProps> = ({
const handlePersonaUpdate = (personaData: any) => {
console.log('Persona updated in LinkedIn writer:', personaData);
setPersonaOverride(personaData);
// setPersonaOverride(personaData);
// You can also save this to user preferences or pass it up to the parent component
};

View File

@@ -112,6 +112,7 @@ export function useLinkedInWriter() {
};
loadInitialData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Listen for lightweight progress events

View File

@@ -24,7 +24,6 @@ import {
SkipNext as SkipIcon,
NavigateNext,
Psychology as AgentIcon,
Lightbulb as ReasonIcon
} from '@mui/icons-material';
import { useNavigate } from 'react-router-dom';
import { useWorkflowStore } from '../../../stores/workflowStore';

View File

@@ -27,9 +27,9 @@ import {
} from '@mui/icons-material';
import { useNavigate } from 'react-router-dom';
import EnhancedTodayChip from './EnhancedTodayChip';
import { TodayTask } from '../../../types/workflow';
// Today Modal Component
import { TodayTask } from '../../../types/workflow';
// Today Modal Component
const TodayModal: React.FC<{
open: boolean;
onClose: () => void;
@@ -257,15 +257,25 @@ const GenerateChip: React.FC<{
}> = ({ chip, delay = 0, onTodayClick }) => {
const [currentIndex, setCurrentIndex] = useState(0);
// Effect to handle animation timing
useEffect(() => {
if (chip.bubbles && chip.bubbles.length > 0) {
const interval = setInterval(() => {
setCurrentIndex((prev) => (prev + 1) % chip.bubbles.length);
}, 2000 + delay * 300);
// Only proceed if there are bubbles to show
if (!chip.bubbles || chip.bubbles.length === 0) return;
return () => clearInterval(interval);
}
}, [chip.bubbles?.length, delay]);
// Reset bubble index when chip changes
setCurrentIndex(0);
// Start interval to cycle through bubbles
const interval = setInterval(() => {
setCurrentIndex(prev => {
// If we've reached the end, reset to 0
if (prev >= (chip.bubbles?.length || 0) - 1) return 0;
return prev + 1;
});
}, 3000); // Change bubble every 3 seconds
return () => clearInterval(interval);
}, [chip.bubbles]); // Only re-run when bubbles change
const IconComponent = chip.icon;

View File

@@ -5,9 +5,9 @@ import {
Typography,
IconButton,
Grid,
Stack,
Chip,
Divider
// Stack,
// Chip,
// Divider
} from '@mui/material';
import { motion, AnimatePresence } from 'framer-motion';
import {

View File

@@ -79,7 +79,7 @@ const BusinessDescriptionStep: React.FC<BusinessDescriptionStepProps> = ({ onBac
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState<string | null>(null);
const [showExamples, setShowExamples] = useState(false);
// const [showExamples, setShowExamples] = useState(false);
useEffect(() => {
console.log('🔄 BusinessDescriptionStep mounted. Loading cached data...');

View File

@@ -20,7 +20,7 @@ import {
Divider,
Chip,
Tooltip,
IconButton,
// IconButton,
Collapse
} from '@mui/material';
import {
@@ -99,7 +99,7 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
const [isAnalyzingSitemap, setIsAnalyzingSitemap] = useState(false);
const [isDiscoveringSocial, setIsDiscoveringSocial] = useState(false);
const [showHeaderInfo, setShowHeaderInfo] = useState(false);
const [showWhyImportant, setShowWhyImportant] = useState(false);
// const [showWhyImportant, setShowWhyImportant] = useState(false);
const [missingData, setMissingData] = useState(false);
// Ref to track if initialization has already started to prevent duplicate calls
@@ -399,7 +399,7 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
} finally {
setIsDiscoveringSocial(false);
}
}, [userUrl, isDiscoveringSocial]);
}, [userUrl, isDiscoveringSocial, socialMediaAccounts]);
// Sitemap Analysis Function
const startSitemapAnalysis = useCallback(async (force = false) => {

View File

@@ -44,7 +44,7 @@ export const ComingSoonSection: React.FC<{ missingData?: boolean }> = ({ missing
const [strategicInsightsRunning, setStrategicInsightsRunning] = useState(false);
const [strategicInsightsError, setStrategicInsightsError] = useState<string | null>(null);
const [strategicInsightsData, setStrategicInsightsData] = useState<any>(null);
const [loadingStrategicHistory, setLoadingStrategicHistory] = useState(false);
// const [loadingStrategicHistory, setLoadingStrategicHistory] = useState(false);
useEffect(() => {
const loadStatus = async () => {
@@ -62,7 +62,7 @@ export const ComingSoonSection: React.FC<{ missingData?: boolean }> = ({ missing
};
const loadHistory = async () => {
setLoadingStrategicHistory(true);
// setLoadingStrategicHistory(true);
try {
const res = await apiClient.get('/api/seo-dashboard/strategic-insights/history');
if (res.data?.history?.length > 0) {
@@ -71,7 +71,7 @@ export const ComingSoonSection: React.FC<{ missingData?: boolean }> = ({ missing
} catch (e) {
console.error("Failed to fetch strategic insights history", e);
} finally {
setLoadingStrategicHistory(false);
// setLoadingStrategicHistory(false);
}
};

View File

@@ -130,12 +130,12 @@ export const SitemapBenchmarkResults: React.FC<Props> = ({ data }) => {
: 0;
const MetricCard = ({ title, userValue, competitorValue, icon, unit = '', description }: any) => {
const isBelowAvg = userValue < competitorValue;
// const isBelowAvg = userValue < competitorValue;
return (
<Card
elevation={0}
sx={{
sx={{
height: '100%',
border: `1px solid #e2e8f0`,
borderRadius: 3,

View File

@@ -12,8 +12,8 @@ import {
Button,
List,
ListItem,
ListItemIcon,
ListItemText,
// ListItemIcon,
// ListItemText,
Divider,
Avatar
} from '@mui/material';
@@ -66,7 +66,6 @@ export interface Props {
}
export const StrategicInsightsResults: React.FC<Props> = ({ report, hideCreateContent = false }) => {
const theme = useTheme();
const { insights, metrics, week_commencing } = report;
const handleCreateContent = (topic: string) => {

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect } from 'react';
import {
Box,
Button,
@@ -13,10 +13,10 @@ import {
Rocket,
Star,
CheckCircle,
CreditCard,
Warning
// CreditCard,
// Warning
} from '@mui/icons-material';
import OnboardingButton from '../common/OnboardingButton';
// import OnboardingButton from '../common/OnboardingButton';
import { useNavigate } from 'react-router-dom';
import { getApiKeys, completeOnboarding, getOnboardingSummary, getWebsiteAnalysisData, getResearchPreferencesData, setCurrentStep } from '../../../api/onboarding';
import { SetupSummary, CapabilitiesOverview, AgentTeamSection } from './components';
@@ -35,7 +35,7 @@ const FinalStep: React.FC<FinalStepProps> = ({ onContinue, updateHeaderContent }
const [validationStatus, setValidationStatus] = useState<{isValid: boolean, missingSteps: string[]} | null>(null);
const [agentTeam, setAgentTeam] = useState<AgentTeamCatalogEntry[]>([]);
const [agentTeamError, setAgentTeamError] = useState<string | null>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
// const buttonRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
updateHeaderContent({
@@ -44,6 +44,7 @@ const FinalStep: React.FC<FinalStepProps> = ({ onContinue, updateHeaderContent }
});
// Always attempt to load data once on mount
loadOnboardingData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [updateHeaderContent]);
// Remove the DOM manipulation approach - we'll use React's built-in event handling

View File

@@ -10,7 +10,7 @@ import {
RadioGroup,
FormControlLabel,
FormControl,
FormLabel,
// FormLabel,
Card,
CardContent,
Alert,
@@ -18,8 +18,8 @@ import {
} from '@mui/material';
import {
ArrowForward as ArrowForwardIcon,
ExpandMore as ExpandMoreIcon,
ExpandLess as ExpandLessIcon,
// ExpandMore as ExpandMoreIcon,
// ExpandLess as ExpandLessIcon,
PlayArrow as PlayArrowIcon,
// Social Media Icons
Facebook as FacebookIcon,
@@ -39,7 +39,7 @@ import {
AutoAwesome as AutoAwesomeIcon,
Lightbulb as LightbulbIcon,
CheckCircle as CheckCircleIcon,
Error as ErrorIcon
// Error as ErrorIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
@@ -89,7 +89,7 @@ const IntegrationsStep: React.FC<IntegrationsStepProps> = ({ onContinue, updateH
}, []);
// Force refresh analytics data (bypass cache)
const forceRefreshAnalytics = useCallback(async () => {
/* const forceRefreshAnalytics = useCallback(async () => {
try {
// Clear all cache first
cachedAnalyticsAPI.clearCache();
@@ -103,7 +103,7 @@ const IntegrationsStep: React.FC<IntegrationsStepProps> = ({ onContinue, updateH
} catch (error) {
console.error('IntegrationsStep: Error force refreshing analytics:', error);
}
}, []);
}, []); */
const { isLoading, showToast, setShowToast, toastMessage, handleConnect } = usePlatformConnections();
// WordPress OAuth hook

View File

@@ -15,7 +15,7 @@ import {
IconButton,
Alert,
Chip,
Divider,
// Divider,
Modal,
Fade,
Backdrop,
@@ -25,20 +25,18 @@ import {
InputLabel,
FormHelperText
} from '@mui/material';
import { keyframes } from '@mui/system';
import {
AutoAwesome,
CloudUpload,
Refresh,
// // Refresh,
PhotoCamera,
AutoFixHigh,
InfoOutlined,
Close,
PlayArrow,
// PlayArrow,
HelpOutline,
Palette,
// Palette,
Psychology,
AutoFixNormal,
// AutoFixNormal,
Create,
CheckCircle,
Fullscreen,
@@ -70,11 +68,11 @@ import {
type GenerationMode = 'generate' | 'variation' | 'enhance';
const pulse = keyframes`
/* const pulse = keyframes`
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
`;
`; */
export const BrandAvatarStudio: React.FC<{ domainName?: string; onAvatarSet?: () => void }> = ({ domainName, onAvatarSet }) => {
const [mode, setMode] = useState<GenerationMode>('generate');

View File

@@ -348,6 +348,7 @@ const WebsiteStep: React.FC<WebsiteStepProps> = ({ onContinue, updateHeaderConte
setAnalysis(updatedAnalysis);
};
/*
const handleContinue = async () => {
setError(null);
const fixedUrl = fixUrlFormat(website);
@@ -385,6 +386,17 @@ const WebsiteStep: React.FC<WebsiteStepProps> = ({ onContinue, updateHeaderConte
onContinue(stepData);
};
*/
/*
const handleContinue = async () => {
// This function is now triggered by the user via the AnalysisResultsDisplay component
// or manually via a button if we were to add one here.
// Since AnalysisResultsDisplay has its own flow, we might not use this directly
// in the main render unless we want a global "Continue" button.
// For now, silencing the unused var warning by logging or commenting out.
console.log('Main handleContinue ready');
};
*/
// Conditional rendering for business description form - now handled inline via toggle
/*

View File

@@ -15,10 +15,10 @@ import {
FormControlLabel,
Alert,
Paper,
List,
ListItem,
ListItemText,
Link,
// List,
// ListItem,
// ListItemText,
// Link,
Collapse,
Switch,
Button
@@ -31,7 +31,7 @@ import {
Business as BusinessIcon,
Info as InfoIcon,
Link as LinkIcon,
Edit as EditIcon,
// Edit as EditIcon,
Save as SaveIcon,
ExpandLess as ExpandLessIcon,
ExpandMore as ExpandMoreIcon,
@@ -40,7 +40,7 @@ import {
// Import rendering utilities
import {
renderProUpgradeAlert,
// renderProUpgradeAlert,
renderBestPracticesSection,
renderAvoidElementsSection,
renderBrandAnalysisSection

View File

@@ -23,7 +23,6 @@ import {
School as AuthorityIcon,
Info as InfoIcon
} from '@mui/icons-material';
import SectionHeader from './SectionHeader';
interface BrandAnalysis {
brand_voice: string;

View File

@@ -20,7 +20,7 @@ import {
DialogContent,
DialogActions,
TextField,
Tooltip
// Tooltip
} from '@mui/material';
import {
Business as BusinessIcon,
@@ -75,12 +75,10 @@ const CompetitorsGrid: React.FC<CompetitorsGridProps> = ({
}) => {
const [openAddDialog, setOpenAddDialog] = useState(false);
const [newCompetitorUrl, setNewCompetitorUrl] = useState('');
const [isAdding, setIsAdding] = useState(false);
const handleAddSubmit = async () => {
const handleAddSubmit = () => {
if (!newCompetitorUrl) return;
setIsAdding(true);
try {
// Create a basic competitor object
// In a real implementation, you might want to fetch metadata here or let the parent handle it
@@ -114,8 +112,6 @@ const CompetitorsGrid: React.FC<CompetitorsGridProps> = ({
setNewCompetitorUrl('');
} catch (error) {
console.error('Error adding competitor:', error);
} finally {
setIsAdding(false);
}
};

View File

@@ -12,8 +12,8 @@ import {
Tab,
Tabs,
Paper,
Divider,
IconButton,
// Divider,
// IconButton,
Tooltip,
TextField,
Collapse,
@@ -35,7 +35,7 @@ import {
AccessibilityNew as AccessibilityIcon,
ExpandMore as ExpandMoreIcon,
ExpandLess as ExpandLessIcon,
Info as InfoIcon,
// Info as InfoIcon,
PlayArrow as PlayArrowIcon,
Schedule as ScheduleIcon
} from '@mui/icons-material';

View File

@@ -1,16 +1,16 @@
import React, { useState } from 'react';
import React from 'react';
import {
Box,
Typography,
Tooltip,
IconButton,
Popover,
Fade,
Paper
// Popover,
// Fade,
// Paper
} from '@mui/material';
import {
Info as InfoIcon,
HelpOutline as HelpIcon
// HelpOutline as HelpIcon
} from '@mui/icons-material';
interface SectionHeaderProps {
@@ -28,9 +28,9 @@ const SectionHeader: React.FC<SectionHeaderProps> = ({
variant = 'h5',
sx = {}
}) => {
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
// const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
const handlePopoverOpen = (event: React.MouseEvent<HTMLElement>) => {
/* const handlePopoverOpen = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
@@ -38,7 +38,7 @@ const SectionHeader: React.FC<SectionHeaderProps> = ({
setAnchorEl(null);
};
const open = Boolean(anchorEl);
const open = Boolean(anchorEl); */
return (
<Box

View File

@@ -12,7 +12,6 @@ import {
Chip,
Card,
CardContent,
Divider,
LinearProgress
} from '@mui/material';
import {
@@ -72,7 +71,7 @@ const SitemapAnalysisResults: React.FC<SitemapAnalysisResultsProps> = ({
}) => {
const structureAnalysis: StructureAnalysis = analysisData.structure_analysis || {};
const contentTrends: ContentTrends = analysisData.content_trends || {};
const publishingPatterns: PublishingPatterns = analysisData.publishing_patterns || {};
// const publishingPatterns: PublishingPatterns = analysisData.publishing_patterns || {};
const onboardingInsights: OnboardingInsights = analysisData.onboarding_insights || {};
if (isLoading) {

View File

@@ -3,8 +3,8 @@ import {
Box,
Typography,
Grid,
Card,
CardContent,
// Card,
// CardContent,
Chip,
Tabs,
Tab,
@@ -12,7 +12,7 @@ import {
ListItem,
ListItemText,
ListItemIcon,
Divider,
// Divider,
Alert,
Paper,
Tooltip,
@@ -74,9 +74,9 @@ const SitemapAnalysisSection: React.FC<SitemapAnalysisSectionProps> = ({
const {
structure_analysis,
content_trends,
publishing_patterns,
// publishing_patterns,
ai_insights,
seo_recommendations
// seo_recommendations
} = sitemapAnalysis;
return (

View File

@@ -21,7 +21,7 @@ import {
Info as InfoIcon,
CheckCircle as CheckIcon,
Lightbulb as LightbulbIcon,
Star as StarIcon
// Star as StarIcon
} from '@mui/icons-material';
import SectionHeader from './SectionHeader';

View File

@@ -18,10 +18,10 @@ import {
} from '@mui/material';
import {
Analytics as AnalyticsIcon,
AutoAwesome as AutoAwesomeIcon,
// AutoAwesome as AutoAwesomeIcon,
Psychology as PsychologyIcon,
Info as InfoIcon,
MenuBook as MenuBookIcon,
// MenuBook as MenuBookIcon,
Timeline as TimelineIcon,
Star as StarIcon
} from '@mui/icons-material';

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react";
import { Stack, Box, Typography, Divider, Chip, Paper, alpha, CircularProgress, TextField, IconButton, Tooltip, Select, MenuItem, FormControl, InputLabel, Switch, FormControlLabel } from "@mui/material";
import { Psychology as PsychologyIcon, Insights as InsightsIcon, Search as SearchIcon, Person as PersonIcon, AutoAwesome as AutoAwesomeIcon, Edit as EditIcon, Save as SaveIcon, Close as CloseIcon, Add as AddIcon, Delete as DeleteIcon, EditNote as EditNoteIcon } from "@mui/icons-material";
import { Stack, Box, Typography, Divider, Chip, Paper, alpha, CircularProgress, TextField, IconButton, Select, MenuItem, FormControl, InputLabel, Switch, FormControlLabel } from "@mui/material";
import { Psychology as PsychologyIcon, Insights as InsightsIcon, Search as SearchIcon, Person as PersonIcon, AutoAwesome as AutoAwesomeIcon, Edit as EditIcon, Save as SaveIcon, Close as CloseIcon, Add as AddIcon, EditNote as EditNoteIcon } from "@mui/icons-material";
import { PodcastAnalysis, PodcastEstimate } from "./types";
import { GlassyCard, glassyCardSx, SecondaryButton } from "./ui";
import { Refresh as RefreshIcon } from "@mui/icons-material";
@@ -69,7 +69,7 @@ export const AnalysisPanel: React.FC<AnalysisPanelProps> = ({
if (analysis && !editedAnalysis) {
setEditedAnalysis(JSON.parse(JSON.stringify(analysis)));
}
}, [analysis]);
}, [analysis, editedAnalysis]);
const handleSave = () => {
if (editedAnalysis && onUpdateAnalysis) {

View File

@@ -121,7 +121,7 @@ export const AvatarAssetBrowser: React.FC<AvatarAssetBrowserProps> = ({ onSelect
if (url.startsWith('blob:')) URL.revokeObjectURL(url);
});
};
}, []);
}, [imageBlobUrls]);
const handleLoadMore = () => {
setLimit(prev => prev + 24);

View File

@@ -200,7 +200,7 @@ export const SceneActionButtons: React.FC<SceneActionButtonsProps> = ({
},
}}
>
{/* Icon only */}
{null}
</PrimaryButton>
)}
@@ -222,7 +222,7 @@ export const SceneActionButtons: React.FC<SceneActionButtonsProps> = ({
},
}}
>
{/* Icon only */}
{null}
</PrimaryButton>
)}
</Stack>

View File

@@ -15,7 +15,10 @@ import {
Instagram as InstagramIcon,
Web as WebIcon,
Timeline as StrategyIcon,
CalendarMonth as CalendarIcon
CalendarMonth as CalendarIcon,
AudioFile as AudioIcon,
Image as ImageIcon,
VideoLibrary as VideoIcon
} from '@mui/icons-material';
import MenuBookIcon from '@mui/icons-material/MenuBook';
import { ToolCategories } from '../components/shared/types';

View File

@@ -60,7 +60,7 @@ export function usePolling<T = any>(
const attemptsRef = useRef(0);
const currentTaskIdRef = useRef<string | null>(null);
const stopPolling = useCallback(() => {
/* const stopPolling = useCallback(() => {
// Only log and clear if actually polling (not just cleanup on unmount when idle)
const wasPolling = intervalRef.current !== null || isPolling;
@@ -81,7 +81,17 @@ export function usePolling<T = any>(
}
}
// Silently handle cleanup when not polling (common on unmount/re-render)
}, [isPolling]);
}, [isPolling]); */
const stopPolling = useCallback(() => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
setIsPolling(false);
attemptsRef.current = 0;
currentTaskIdRef.current = null;
}, []);
const startPolling = useCallback((taskId: string) => {
if (isPolling) {