ALwrity version 0.5.4

This commit is contained in:
ajaysi
2025-08-09 23:14:16 +05:30
parent 01fe1e0a9c
commit 5c08b6e007
42 changed files with 3514 additions and 2148 deletions

View File

@@ -71,6 +71,7 @@ import { getEducationalContent } from './ContentStrategyBuilder/utils/educationa
import CategoryList from './ContentStrategyBuilder/components/CategoryList';
import ProgressTracker from './ContentStrategyBuilder/components/ProgressTracker';
import HeaderSection from './ContentStrategyBuilder/components/HeaderSection';
import { contentPlanningApi } from '../../../services/contentPlanningApi';
const ContentStrategyBuilder: React.FC = () => {
const {
@@ -112,6 +113,10 @@ const ContentStrategyBuilder: React.FC = () => {
const [showEducationalInfo, setShowEducationalInfo] = useState<string | null>(null);
const [showAIRecommendations, setShowAIRecommendations] = useState(false);
const [showDataSourceTransparency, setShowDataSourceTransparency] = useState(false);
const [refreshMessage, setRefreshMessage] = useState<string | null>(null);
const [refreshProgress, setRefreshProgress] = useState<number>(0);
const [isRefreshing, setIsRefreshing] = useState<boolean>(false);
const [refreshError, setRefreshError] = useState<string | null>(null);
// Ref to track if we've already set the default category
const hasSetDefaultCategory = useRef(false);
@@ -310,8 +315,20 @@ const ContentStrategyBuilder: React.FC = () => {
{/* Error Alert */}
{error && (
<Alert severity="error" sx={{ mb: 3 }}>
{error}
<Alert
severity="error"
sx={{ mb: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}
action={
<Box sx={{ display: 'flex', gap: 1 }}>
<Button size="small" variant="outlined" onClick={() => autoPopulateFromOnboarding(true)} startIcon={<RefreshIcon />}>Retry</Button>
<Button size="small" variant="contained" color="primary" onClick={() => setShowDataSourceTransparency(true)} startIcon={<InfoIcon />}>Why?</Button>
</Box>
}
>
<Box>
<Typography variant="subtitle2">Real data required</Typography>
<Typography variant="body2">{error || 'We could not auto-populate because required onboarding/analysis data is missing. Connect sources or complete onboarding, then retry.'}</Typography>
</Box>
</Alert>
)}
@@ -380,7 +397,87 @@ const ContentStrategyBuilder: React.FC = () => {
aiGenerating={aiGenerating}
onShowAIRecommendations={() => setShowAIRecommendations(true)}
onShowDataSourceTransparency={() => setShowDataSourceTransparency(true)}
onRefreshData={autoPopulateFromOnboarding}
onRefreshData={() => autoPopulateFromOnboarding()}
onRefreshAI={async () => {
try {
setAIGenerating(true);
setIsRefreshing(true);
setRefreshError(null);
setRefreshMessage('Initializing refresh…');
setRefreshProgress(5);
const es = await contentPlanningApi.streamAutofillRefresh(1, true, true);
es.onmessage = (evt: MessageEvent) => {
try {
const data = JSON.parse(evt.data);
if (data.type === 'status' || data.type === 'progress') {
setRefreshMessage(data.message || 'Refreshing…');
if (typeof data.progress === 'number') setRefreshProgress(data.progress);
}
if (data.type === 'result') {
const payload = data.data || {};
const fields = payload.fields || {};
const sources = payload.sources || {};
const inputDataPoints = payload.input_data_points || {};
const meta = payload.meta || {};
const fieldValues: Record<string, any> = {};
Object.keys(fields).forEach((fieldId) => {
const fieldData = fields[fieldId];
if (fieldData && typeof fieldData === 'object' && 'value' in fieldData) {
fieldValues[fieldId] = fieldData.value;
}
});
useEnhancedStrategyStore.setState((state) => ({
autoPopulatedFields: { ...state.autoPopulatedFields, ...fieldValues },
dataSources: { ...state.dataSources, ...sources },
inputDataPoints,
formData: { ...state.formData, ...fieldValues }
}));
if (!meta.ai_used || meta.ai_overrides_count === 0) {
const msg = 'AI did not produce new values. Please try again or complete onboarding data.';
setError(msg);
setRefreshError(msg);
setRefreshMessage('No new AI values available.');
}
es.close();
setAIGenerating(false);
setIsRefreshing(false);
if (!meta || meta.ai_overrides_count > 0) {
setRefreshMessage(null);
setRefreshProgress(0);
}
}
if (data.type === 'error') {
const msg = data.message || 'AI refresh failed.';
setRefreshError(msg);
es.close();
setAIGenerating(false);
setIsRefreshing(false);
setRefreshMessage('Refresh failed.');
}
} catch (err: any) {
console.error('SSE parse error:', err);
}
};
es.onerror = (err: any) => {
console.error('SSE connection error:', err);
es.close();
setAIGenerating(false);
setIsRefreshing(false);
setRefreshError('AI refresh connection lost. Please try again.');
setRefreshMessage('Connection lost.');
};
} catch (e) {
console.error('AI refresh error', e);
setAIGenerating(false);
setIsRefreshing(false);
setRefreshError('AI refresh failed. Please try again.');
setRefreshMessage('Refresh failed.');
}
}}
refreshMessage={refreshMessage}
refreshProgress={refreshProgress}
isRefreshing={isRefreshing}
refreshError={refreshError}
/>
{/* Category Progress - Compact with Futuristic Styling */}
@@ -428,7 +525,7 @@ const ContentStrategyBuilder: React.FC = () => {
size="small"
variant="outlined"
startIcon={<RefreshIcon />}
onClick={autoPopulateFromOnboarding}
onClick={() => autoPopulateFromOnboarding(true)}
fullWidth
>
Refresh Data
@@ -518,8 +615,8 @@ const ContentStrategyBuilder: React.FC = () => {
{/* Category Fields */}
<Box sx={{ mt: 1 }}>
<Grid container spacing={2}>
{STRATEGIC_INPUT_FIELDS
.filter(field => field.category === activeCategory)
{STRATEGIC_INPUT_FIELDS
.filter(field => field.category === activeCategory)
.map((field, index) => {
// Determine grid size based on field type for better layout organization
const type = field.type;
@@ -531,30 +628,30 @@ const ContentStrategyBuilder: React.FC = () => {
const gridMd = forceFullWidth ? 12 : (isWideField ? 12 : isMediumField ? 6 : 4);
const gridLg = forceFullWidth ? 12 : (isWideField ? 12 : isMediumField ? 6 : 4);
const gridSm = 12;
return (
return (
<Grid item xs={12} sm={gridSm} md={gridMd} lg={gridLg} key={field.id}>
<motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.25, delay: index * 0.03 }}>
<StrategicInputField
fieldId={field.id}
value={formData[field.id]}
error={formErrors[field.id]}
autoPopulated={!!autoPopulatedFields[field.id]}
dataSource={dataSources[field.id]}
confidenceLevel={autoPopulatedFields[field.id] ? 0.8 : undefined}
dataQuality={autoPopulatedFields[field.id] ? 'High Quality' : undefined}
onChange={(value: any) => updateFormField(field.id, value)}
onValidate={() => validateFormField(field.id)}
onShowTooltip={() => setShowTooltip(field.id)}
<StrategicInputField
fieldId={field.id}
value={formData[field.id]}
error={formErrors[field.id]}
autoPopulated={!!autoPopulatedFields[field.id]}
dataSource={dataSources[field.id]}
confidenceLevel={autoPopulatedFields[field.id] ? 0.8 : undefined}
dataQuality={autoPopulatedFields[field.id] ? 'High Quality' : undefined}
onChange={(value: any) => updateFormField(field.id, value)}
onValidate={() => validateFormField(field.id)}
onShowTooltip={() => setShowTooltip(field.id)}
onViewDataSource={() => setShowDataSourceTransparency(true)}
accentColorKey={getCategoryColor(activeCategory) as any}
isCompact={isCompactField}
/>
/>
</motion.div>
</Grid>
);
})}
</Grid>
</Grid>
);
})}
</Grid>
</Box>
{/* Category Actions */}
@@ -567,26 +664,26 @@ const ContentStrategyBuilder: React.FC = () => {
reviewedCategories: Array.from(reviewedCategories)
});
return !isReviewed ? (
<Button
variant="contained"
<Button
variant="contained"
onClick={() => {
console.log('🔘 Button clicked! activeCategory:', activeCategory);
console.log('🔘 reviewedCategories:', Array.from(reviewedCategories));
console.log('🔘 isMarkingReviewed:', isMarkingReviewed);
handleConfirmCategoryReviewWrapper();
}}
startIcon={isMarkingReviewed ? <CircularProgress size={20} /> : <CheckCircleIcon />}
disabled={isMarkingReviewed}
>
{isMarkingReviewed ? 'Marking as Reviewed...' : 'Mark as Reviewed'}
</Button>
) : (
<Chip
label="Category Reviewed"
color="success"
icon={<CheckCircleIcon />}
sx={{ px: 2, py: 1 }}
/>
startIcon={isMarkingReviewed ? <CircularProgress size={20} /> : <CheckCircleIcon />}
disabled={isMarkingReviewed}
>
{isMarkingReviewed ? 'Marking as Reviewed...' : 'Mark as Reviewed'}
</Button>
) : (
<Chip
label="Category Reviewed"
color="success"
icon={<CheckCircleIcon />}
sx={{ px: 2, py: 1 }}
/>
);
})()}

View File

@@ -24,6 +24,12 @@ interface ProgressTrackerProps {
onShowAIRecommendations: () => void;
onShowDataSourceTransparency: () => void;
onRefreshData: () => void;
onRefreshAI?: () => void;
// New optional props for refresh feedback
refreshMessage?: string | null;
refreshProgress?: number;
isRefreshing?: boolean;
refreshError?: string | null;
}
const ProgressTracker: React.FC<ProgressTrackerProps> = ({
@@ -34,28 +40,25 @@ const ProgressTracker: React.FC<ProgressTrackerProps> = ({
aiGenerating,
onShowAIRecommendations,
onShowDataSourceTransparency,
onRefreshData
onRefreshData,
onRefreshAI,
refreshMessage,
refreshProgress = 0,
isRefreshing = false,
refreshError = null
}) => {
const effectiveProgress = isRefreshing ? Math.max(5, Math.min(100, Math.round(refreshProgress))) : Math.round(reviewProgressPercentage);
return (
<Box sx={{ mb: 1.5 }}>
{/* Compact header row with title, progress, counts and actions */}
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 0.75 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography variant="h6" sx={{ mb: 0, fontSize: '1rem' }}>
Progress
</Typography>
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
<CircularProgress
variant="determinate"
value={reviewProgressPercentage}
size={28}
thickness={4}
sx={{ color: 'primary.main', '& .MuiCircularProgress-circle': { strokeLinecap: 'round' } }}
/>
<Box sx={{ top: 0, left: 0, bottom: 0, right: 0, position: 'absolute', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Typography variant="caption" component="div" color="text.secondary" sx={{ fontSize: '0.65rem', fontWeight: 700 }}>
{`${Math.round(reviewProgressPercentage)}%`}
</Typography>
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}>Progress</Typography>
<Box sx={{ position: 'relative', width: 28, height: 28 }}>
<CircularProgress variant="determinate" value={effectiveProgress} size={28} thickness={5} />
<Box sx={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Typography variant="caption" sx={{ fontSize: 10 }}>{effectiveProgress}%</Typography>
</Box>
</Box>
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.7rem' }}>
@@ -64,49 +67,44 @@ const ProgressTracker: React.FC<ProgressTrackerProps> = ({
</Box>
{/* Actions inline in header */}
<Box sx={{ display: 'flex', gap: 0.75 }}>
<MuiTooltip title="View AI-powered recommendations and insights" placement="top">
<motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
<IconButton
onClick={onShowAIRecommendations}
sx={{ color: 'primary.main', bgcolor: 'rgba(255, 193, 7, 0.08)', border: '1px solid rgba(255, 193, 7, 0.25)', width: 32, height: 32, '&:hover': { bgcolor: 'rgba(255, 193, 7, 0.16)' } }}
>
<Badge badgeContent={5} sx={{ '& .MuiBadge-badge': { fontSize: '0.55rem', fontWeight: 700, bgcolor: '#ff6b35', color: 'white' } }}>
<AutoAwesomeIcon sx={{ fontSize: 16 }} />
</Badge>
</IconButton>
</motion.div>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<MuiTooltip title="AI Recommendations">
<IconButton size="small" onClick={onShowAIRecommendations}>
<AutoAwesomeIcon fontSize="small" />
</IconButton>
</MuiTooltip>
<MuiTooltip title="View data sources and transparency information" placement="top">
<motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
<IconButton
onClick={onShowDataSourceTransparency}
sx={{ color: 'primary.main', bgcolor: 'rgba(76, 175, 80, 0.08)', border: '1px solid rgba(76, 175, 80, 0.25)', width: 32, height: 32, '&:hover': { bgcolor: 'rgba(76, 175, 80, 0.16)' } }}
>
<Badge badgeContent={Object.keys(autoPopulatedFields || {}).length} sx={{ '& .MuiBadge-badge': { fontSize: '0.55rem', fontWeight: 700, bgcolor: '#2196f3', color: 'white' } }}>
<InfoIcon sx={{ fontSize: 16 }} />
</Badge>
</IconButton>
</motion.div>
<MuiTooltip title="Data Transparency">
<IconButton size="small" onClick={onShowDataSourceTransparency}>
<InfoIcon fontSize="small" />
</IconButton>
</MuiTooltip>
<MuiTooltip title="Refresh auto-populated data" placement="top">
<motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
<IconButton onClick={onRefreshData} sx={{ color: 'primary.main', bgcolor: 'rgba(0,0,0,0.04)', border: '1px solid rgba(0,0,0,0.12)', width: 32, height: 32, '&:hover': { bgcolor: 'rgba(0,0,0,0.08)' } }}>
<RefreshIcon sx={{ fontSize: 16 }} />
</IconButton>
</motion.div>
<MuiTooltip title="Refresh Data (AI)">
<IconButton size="small" onClick={onRefreshAI || onRefreshData}>
<RefreshIcon fontSize="small" />
</IconButton>
</MuiTooltip>
</Box>
</Box>
{/* Combined info line */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{/* Combined info line with refresh/error banner */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, minHeight: 22 }}>
<CheckCircleIcon color="success" sx={{ fontSize: 14 }} />
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.7rem' }}>
Auto-population: {Object.keys(autoPopulatedFields || {}).length} fields AI Insights: {aiGenerating ? 'Generating...' : 'Ready'}
</Typography>
{refreshError ? (
<Typography variant="caption" color="error" sx={{ fontSize: '0.72rem' }}>
{refreshError}
</Typography>
) : isRefreshing ? (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75 }}>
<CircularProgress size={12} thickness={6} />
<Typography variant="caption" color="primary" sx={{ fontSize: '0.72rem' }}>
{refreshMessage || 'Refreshing data…'}
</Typography>
</Box>
) : (
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.7rem' }}>
Auto-population: {Object.keys(autoPopulatedFields || {}).length} fields AI Insights: {aiGenerating ? 'Generating…' : 'Ready'}
</Typography>
)}
</Box>
</Box>
);