ALwrity version 0.5.4

This commit is contained in:
ajaysi
2025-08-08 10:50:31 +05:30
parent c5b54786f8
commit 01fe1e0a9c
14 changed files with 590 additions and 548 deletions

View File

@@ -942,13 +942,17 @@ class EnhancedStrategyService:
# Transform data into frontend-expected format
auto_populated_fields = self._transform_onboarding_data_to_fields(processed_data)
# Add detailed input data points for transparency
input_data_points = self._get_detailed_input_data_points(processed_data)
logger.info(f"Retrieved comprehensive onboarding data for user {user_id}")
return {
'fields': auto_populated_fields,
'sources': self._get_data_sources(processed_data),
'quality_scores': processed_data['data_quality_scores'],
'confidence_levels': processed_data['confidence_levels'],
'data_freshness': processed_data['data_freshness']
'data_freshness': processed_data['data_freshness'],
'input_data_points': input_data_points # Add detailed input data
}
finally:
@@ -2442,4 +2446,87 @@ class EnhancedStrategyService:
'quality_scores': {},
'confidence_levels': {},
'data_freshness': {'status': 'unknown', 'age_days': 'unknown'}
}
}
def _get_detailed_input_data_points(self, processed_data: Dict[str, Any]) -> Dict[str, Any]:
"""Get detailed input data points that were used to generate each field"""
input_data_points = {}
website_data = processed_data.get('website_analysis', {})
research_data = processed_data.get('research_preferences', {})
api_data = processed_data.get('api_keys_data', {})
# Business Objectives - from website analysis
if website_data:
input_data_points['business_objectives'] = {
'website_content': website_data.get('content_goals', 'Not available'),
'meta_description': website_data.get('meta_description', 'Not available'),
'about_page': website_data.get('about_page_content', 'Not available'),
'page_title': website_data.get('page_title', 'Not available'),
'content_analysis': website_data.get('content_analysis', {})
}
# Target Metrics - from research preferences and industry analysis
if research_data:
input_data_points['target_metrics'] = {
'research_preferences': research_data.get('target_audience', 'Not available'),
'industry_benchmarks': research_data.get('industry_benchmarks', 'Not available'),
'competitor_analysis': research_data.get('competitor_analysis', 'Not available'),
'market_research': research_data.get('market_research', 'Not available')
}
# Content Preferences - from research preferences
if research_data:
input_data_points['content_preferences'] = {
'user_preferences': research_data.get('content_types', 'Not available'),
'industry_trends': research_data.get('industry_trends', 'Not available'),
'consumption_patterns': research_data.get('consumption_patterns', 'Not available'),
'audience_research': research_data.get('audience_research', 'Not available')
}
# Preferred Formats - from website analysis and research
if website_data or research_data:
input_data_points['preferred_formats'] = {
'existing_content': website_data.get('existing_content_types', 'Not available'),
'engagement_metrics': website_data.get('engagement_metrics', 'Not available'),
'platform_analysis': research_data.get('platform_preferences', 'Not available'),
'content_performance': website_data.get('content_performance', 'Not available')
}
# Content Frequency - from research preferences
if research_data:
input_data_points['content_frequency'] = {
'audience_research': research_data.get('content_frequency_preferences', 'Not available'),
'industry_standards': research_data.get('industry_frequency', 'Not available'),
'competitor_frequency': research_data.get('competitor_frequency', 'Not available'),
'optimal_timing': research_data.get('optimal_timing', 'Not available')
}
# Content Budget - from website analysis and industry standards
if website_data:
input_data_points['content_budget'] = {
'website_analysis': website_data.get('budget_indicators', 'Not available'),
'industry_standards': website_data.get('industry_budget', 'Not available'),
'company_size': website_data.get('company_size', 'Not available'),
'market_position': website_data.get('market_position', 'Not available')
}
# Team Size - from website analysis and company profile
if website_data:
input_data_points['team_size'] = {
'company_profile': website_data.get('company_profile', 'Not available'),
'content_volume': website_data.get('content_volume', 'Not available'),
'industry_standards': website_data.get('industry_team_size', 'Not available'),
'budget_constraints': website_data.get('budget_constraints', 'Not available')
}
# Implementation Timeline - from research and industry analysis
if research_data:
input_data_points['implementation_timeline'] = {
'project_scope': research_data.get('project_scope', 'Not available'),
'resource_availability': research_data.get('resource_availability', 'Not available'),
'industry_timeline': research_data.get('industry_timeline', 'Not available'),
'complexity_assessment': research_data.get('complexity_assessment', 'Not available')
}
return input_data_points

View File

@@ -1,13 +1,13 @@
{
"files": {
"main.css": "/static/css/main.c9966057.css",
"main.js": "/static/js/main.28afa9ad.js",
"main.js": "/static/js/main.ba50e996.js",
"index.html": "/index.html",
"main.c9966057.css.map": "/static/css/main.c9966057.css.map",
"main.28afa9ad.js.map": "/static/js/main.28afa9ad.js.map"
"main.ba50e996.js.map": "/static/js/main.ba50e996.js.map"
},
"entrypoints": [
"static/css/main.c9966057.css",
"static/js/main.28afa9ad.js"
"static/js/main.ba50e996.js"
]
}

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Alwrity - AI Content Creation Platform"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Alwrity - AI Content Creation Platform</title><script defer="defer" src="/static/js/main.28afa9ad.js"></script><link href="/static/css/main.c9966057.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Alwrity - AI Content Creation Platform"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Alwrity - AI Content Creation Platform</title><script defer="defer" src="/static/js/main.ba50e996.js"></script><link href="/static/css/main.c9966057.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

View File

@@ -1,80 +0,0 @@
/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-is.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @remix-run/router v1.13.1
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
/**
* React Router v6.20.1
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
/** @license React v16.13.1
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

File diff suppressed because one or more lines are too long

View File

@@ -78,6 +78,7 @@ const ContentStrategyBuilder: React.FC = () => {
formErrors,
autoPopulatedFields,
dataSources,
inputDataPoints, // Add inputDataPoints from store
loading,
error,
saving,
@@ -369,7 +370,7 @@ const ContentStrategyBuilder: React.FC = () => {
<Grid container spacing={3}>
{/* Category Overview Panel */}
<Grid item xs={12} md={4}>
<Paper sx={{ p: 3, height: 'fit-content', position: 'sticky', top: 20 }}>
<Paper sx={{ p: 3, height: 'fit-content', position: 'sticky', top: 20, background: 'linear-gradient(180deg, #f7f9fc, #eef3fb)' }}>
{/* Enhanced Completion Tracker - Integrated into Category List */}
<ProgressTracker
reviewProgressPercentage={reviewProgressPercentage}
@@ -439,7 +440,7 @@ const ContentStrategyBuilder: React.FC = () => {
{/* Main Content Area */}
<Grid item xs={12} md={8}>
<Paper sx={{ p: 3, minHeight: '600px' }}>
<Paper sx={{ p: 3, minHeight: '600px', background: 'linear-gradient(180deg, #faf7ff, #f1f0ff)' }}>
{activeCategory ? (
<motion.div
initial={{ opacity: 0, y: 20 }}
@@ -515,38 +516,46 @@ const ContentStrategyBuilder: React.FC = () => {
</Dialog>
{/* Category Fields */}
<Grid container spacing={1.5}>
{STRATEGIC_INPUT_FIELDS
.filter(field => field.category === activeCategory)
.map((field) => {
// Group number-based fields together
const isNumberField = field.type === 'number' ||
field.id.includes('budget') ||
field.id.includes('size') ||
field.id.includes('timeline') ||
field.id.includes('metrics');
// Determine grid size based on field type
const gridSize = isNumberField ? 6 : 12;
return (
<Grid item xs={12} md={gridSize} key={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)}
/>
</Grid>
);
})}
</Grid>
<Box sx={{ mt: 1 }}>
<Grid container spacing={2}>
{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;
const isWideField = type === 'json';
const isMediumField = type === 'multiselect' || type === 'select' || type === 'text';
const isCompactField = type === 'number' || type === 'boolean';
const forceFullWidth = field.id === 'content_budget' || field.id === 'team_size';
const gridMd = forceFullWidth ? 12 : (isWideField ? 12 : isMediumField ? 6 : 4);
const gridLg = forceFullWidth ? 12 : (isWideField ? 12 : isMediumField ? 6 : 4);
const gridSm = 12;
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)}
onViewDataSource={() => setShowDataSourceTransparency(true)}
accentColorKey={getCategoryColor(activeCategory) as any}
isCompact={isCompactField}
/>
</motion.div>
</Grid>
);
})}
</Grid>
</Box>
{/* Category Actions */}
<Box sx={{ mt: 3, display: 'flex', gap: 2 }}>
@@ -681,6 +690,7 @@ const ContentStrategyBuilder: React.FC = () => {
<DataSourceTransparency
autoPopulatedFields={autoPopulatedFields}
dataSources={dataSources}
inputDataPoints={inputDataPoints} // Use real input data points from store
/>
</DialogContent>
<DialogActions>

View File

@@ -0,0 +1,24 @@
/* Subtle, modern styles for strategy input cards */
:root {
--csb-card-radius: 12px;
--csb-card-border: 1px;
--csb-card-shadow: 0 6px 18px rgba(0,0,0,0.06);
}
.csb-card {
border-radius: var(--csb-card-radius);
border: var(--csb-card-border) solid rgba(148, 163, 184, 0.25);
background: linear-gradient(180deg, rgba(255,255,255,0.9) 0%, rgba(249,250,251,0.9) 100%);
box-shadow: var(--csb-card-shadow);
transition: box-shadow .2s ease, border-color .2s ease, transform .15s ease;
}
.csb-card:hover {
box-shadow: 0 8px 24px rgba(0,0,0,0.08);
border-color: rgba(59,130,246,0.35);
}
.csb-section {
padding: 8px 12px;
}

View File

@@ -14,7 +14,8 @@ import {
Typography,
Alert,
Autocomplete,
InputAdornment
InputAdornment,
Button
} from '@mui/material';
import {
Help as HelpIcon,
@@ -37,6 +38,9 @@ interface StrategicInputFieldProps {
onChange: (value: any) => void;
onValidate: () => boolean;
onShowTooltip: () => void;
onViewDataSource?: () => void; // Add callback for viewing data source
accentColorKey?: 'primary' | 'secondary' | 'success' | 'warning' | 'info' | 'error';
isCompact?: boolean;
}
// Define proper types for field configurations
@@ -78,10 +82,15 @@ const StrategicInputField: React.FC<StrategicInputFieldProps> = ({
dataQuality,
onChange,
onValidate,
onShowTooltip
onShowTooltip,
onViewDataSource,
accentColorKey = 'primary',
isCompact = false
}) => {
const { getTooltipData } = useEnhancedStrategyStore();
const [isEditing, setIsEditing] = useState(false);
const getAccent = (theme: any) => (theme?.palette?.[accentColorKey] ?? theme?.palette?.primary);
// Get field configuration from store with proper null checking
const tooltipData = getTooltipData(fieldId);
@@ -484,16 +493,17 @@ const StrategicInputField: React.FC<StrategicInputFieldProps> = ({
return (
<Box sx={{
position: 'relative',
mb: 1.5,
p: 1.5,
borderRadius: 1.5,
bgcolor: 'background.paper',
mb: isCompact ? 1.25 : 2,
p: isCompact ? 1 : 1.5,
borderRadius: 2,
bgcolor: 'rgba(255,255,255,0.9)',
border: '1px solid',
borderColor: error ? 'error.main' : autoPopulated ? 'info.main' : 'divider',
borderColor: error ? 'error.main' : 'rgba(148, 163, 184, 0.35)',
boxShadow: '0 6px 18px rgba(0,0,0,0.06)',
transition: 'box-shadow 0.2s ease, border-color 0.2s ease',
'&:hover': {
borderColor: 'primary.main',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
transition: 'all 0.2s ease'
borderColor: (theme) => getAccent(theme).main,
boxShadow: (theme) => `0 10px 24px rgba(0,0,0,0.08), 0 0 0 2px ${getAccent(theme).main}22`
}
}}>
{/* Field input - Enhanced styling */}
@@ -501,19 +511,28 @@ const StrategicInputField: React.FC<StrategicInputFieldProps> = ({
'& .MuiTextField-root, & .MuiFormControl-root': {
'& .MuiInputBase-root': {
borderRadius: 1,
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
borderColor: (theme) => getAccent(theme).main,
boxShadow: (theme) => `0 0 0 2px ${getAccent(theme).main}22`
},
'&:hover': {
'& .MuiOutlinedInput-notchedOutline': {
borderColor: 'primary.main'
borderColor: (theme) => getAccent(theme).main
}
}
},
'& .MuiInputLabel-root': {
fontSize: '0.8rem',
fontWeight: 500
fontSize: '0.9rem',
fontWeight: 600,
letterSpacing: '0.15px',
color: (theme) => theme.palette.text.primary,
'&.Mui-focused': {
color: (theme) => getAccent(theme).main
}
},
'& .MuiInputBase-input': {
fontSize: '0.85rem',
padding: '8px 12px'
fontSize: '0.92rem',
padding: isCompact ? '7px 10px' : '8px 12px'
}
}
}}>
@@ -534,7 +553,7 @@ const StrategicInputField: React.FC<StrategicInputFieldProps> = ({
{/* Validation status */}
{value && !error && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<CheckCircleIcon color="success" sx={{ fontSize: 14 }} />
<CheckCircleIcon sx={{ fontSize: 14, color: (theme) => getAccent(theme).main }} />
<Typography variant="caption" color="success.main" sx={{ fontSize: '0.7rem' }}>
Valid
</Typography>
@@ -548,7 +567,7 @@ const StrategicInputField: React.FC<StrategicInputFieldProps> = ({
label={dataQuality}
size="small"
variant="outlined"
color="primary"
color={accentColorKey as any}
sx={{
fontSize: '0.6rem',
height: 20,
@@ -557,8 +576,8 @@ const StrategicInputField: React.FC<StrategicInputFieldProps> = ({
/>
)}
{/* Confidence Level indicator */}
{confidenceLevel && (
{/* Confidence Level indicator - REMOVED (Area 1) */}
{/* {confidenceLevel && (
<Chip
label={`${Math.round(confidenceLevel * 100)}% confidence`}
size="small"
@@ -570,11 +589,11 @@ const StrategicInputField: React.FC<StrategicInputFieldProps> = ({
'& .MuiChip-label': { px: 1 }
}}
/>
)}
)} */}
</Box>
{/* Right side - Auto-population indicator */}
{autoPopulated && (
{/* Right side - Auto-population indicator - REMOVED (Area 2) */}
{/* {autoPopulated && (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<Chip
icon={<AutoAwesomeIcon sx={{ fontSize: 12 }} />}
@@ -596,6 +615,58 @@ const StrategicInputField: React.FC<StrategicInputFieldProps> = ({
</Tooltip>
)}
</Box>
)} */}
{/* Enhanced Data Source Information */}
{autoPopulated && dataSource && (
<Box sx={{
display: 'flex',
alignItems: 'center',
gap: 0.5,
mt: 0.5,
p: 0.5,
bgcolor: (theme) => `${getAccent(theme).main}0D`,
borderRadius: 1,
border: (theme) => `1px solid ${getAccent(theme).main}33`
}}>
<InfoIcon sx={{ fontSize: 12, color: (theme) => getAccent(theme).main }} />
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.6rem' }}>
Data from: {dataSource.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}
</Typography>
{confidenceLevel && (
<Chip
label={`${Math.round(confidenceLevel * 100)}% confidence`}
size="small"
variant="outlined"
color={confidenceLevel > 0.8 ? 'success' : confidenceLevel > 0.6 ? 'warning' : 'error'}
sx={{
fontSize: '0.5rem',
height: 16,
'& .MuiChip-label': { px: 0.5 }
}}
/>
)}
{onViewDataSource && (
<Button
size="small"
variant="text"
onClick={onViewDataSource}
sx={{
fontSize: '0.6rem',
minWidth: 'auto',
px: 1,
py: 0.25,
color: (theme) => getAccent(theme).main,
textTransform: 'none',
'&:hover': {
bgcolor: (theme) => `${getAccent(theme).main}1A`
}
}}
>
View details
</Button>
)}
</Box>
)}
</Box>

View File

@@ -73,8 +73,8 @@ const CategoryList: React.FC<CategoryListProps> = ({
>
<ListItem
sx={{
p: 1.5, // 50% more compact padding
mb: 0.5, // Reduced margin
p: 1.25,
mb: 0.4,
borderRadius: 2,
bgcolor: isSelected ? 'action.hover' : isNextInSequenceCategory ? 'rgba(25, 118, 210, 0.08)' : 'transparent',
border: isSelected ? '2px solid' : isNextInSequenceCategory ? '1px solid' : '1px solid',
@@ -106,7 +106,7 @@ const CategoryList: React.FC<CategoryListProps> = ({
}}
>
{/* Category Header - Compact */}
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%', mb: 0.5, position: 'relative', zIndex: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%', mb: 0.4, position: 'relative', zIndex: 1 }}>
<ListItemIcon sx={{ minWidth: 32 }}>
{getCategoryIcon(categoryId)}
</ListItemIcon>
@@ -120,8 +120,8 @@ const CategoryList: React.FC<CategoryListProps> = ({
size="small"
color="primary"
sx={{
height: 16,
fontSize: '0.6rem',
height: 15,
fontSize: '0.58rem',
'& .MuiChip-label': { px: 0.5 }
}}
/>
@@ -131,8 +131,8 @@ const CategoryList: React.FC<CategoryListProps> = ({
secondary={`${Math.round(percentageValue)}% complete`}
sx={{
flex: 1,
'& .MuiListItemText-primary': { fontSize: '0.9rem', fontWeight: 500 },
'& .MuiListItemText-secondary': { fontSize: '0.7rem' }
'& .MuiListItemText-primary': { fontSize: '0.88rem', fontWeight: 500 },
'& .MuiListItemText-secondary': { fontSize: '0.68rem' }
}}
/>
<Chip

View File

@@ -37,44 +37,23 @@ const ProgressTracker: React.FC<ProgressTrackerProps> = ({
onRefreshData
}) => {
return (
<Box sx={{ mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1.5 }}>
<Typography variant="h6" gutterBottom sx={{ mb: 0 }}>
Progress
</Typography>
{/* Spiral Progress - Moved from Region 2 to Region 3 */}
<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', 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={40}
size={28}
thickness={4}
sx={{
color: 'primary.main',
'& .MuiCircularProgress-circle': {
strokeLinecap: 'round',
}
}}
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.7rem', fontWeight: 'bold' }}
>
<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>
</Box>
@@ -83,132 +62,51 @@ const ProgressTracker: React.FC<ProgressTrackerProps> = ({
{reviewedCategoriesCount}/{totalCategories}
</Typography>
</Box>
</Box>
{/* Status Indicators - Compact */}
<Box sx={{ mb: 1.5 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mb: 0.5 }}>
<CheckCircleIcon color="success" fontSize="small" sx={{ fontSize: 14 }} />
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.7rem' }}>
Auto-population: {Object.keys(autoPopulatedFields || {}).length} fields
</Typography>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<AutoAwesomeIcon color="primary" fontSize="small" sx={{ fontSize: 14 }} />
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.7rem' }}>
AI Insights: {aiGenerating ? 'Generating...' : 'Ready'}
</Typography>
{/* 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>
</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>
<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>
</Box>
</Box>
{/* Icons moved from Region A to Region B - Now integrated into Progress title area */}
<Box sx={{ display: 'flex', gap: 1, justifyContent: 'center', mt: 1.5 }}>
{/* AI Recommendations Button - Compact */}
<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.1)',
border: '1px solid rgba(255, 193, 7, 0.3)',
'&:hover': {
bgcolor: 'rgba(255, 193, 7, 0.2)',
transform: 'translateY(-1px)',
boxShadow: '0 4px 12px rgba(255, 193, 7, 0.3)'
},
transition: 'all 0.3s ease',
width: 36,
height: 36
}}
>
<Badge
badgeContent={5}
sx={{
'& .MuiBadge-badge': {
fontSize: '0.6rem',
fontWeight: 'bold',
animation: 'pulse 2s infinite',
bgcolor: '#ff6b35',
color: 'white'
}
}}
>
<AutoAwesomeIcon sx={{ fontSize: 16 }} />
</Badge>
</IconButton>
</motion.div>
</MuiTooltip>
{/* Data Source Transparency Button - Compact */}
<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.1)',
border: '1px solid rgba(76, 175, 80, 0.3)',
'&:hover': {
bgcolor: 'rgba(76, 175, 80, 0.2)',
transform: 'translateY(-1px)',
boxShadow: '0 4px 12px rgba(76, 175, 80, 0.3)'
},
transition: 'all 0.3s ease',
width: 36,
height: 36
}}
>
<Badge
badgeContent={Object.keys(autoPopulatedFields || {}).length}
sx={{
'& .MuiBadge-badge': {
fontSize: '0.6rem',
fontWeight: 'bold',
animation: 'pulse 2s infinite',
bgcolor: '#2196f3',
color: 'white'
}
}}
>
<InfoIcon sx={{ fontSize: 16 }} />
</Badge>
</IconButton>
</motion.div>
</MuiTooltip>
{/* Refresh Button - Compact */}
<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.05)',
border: '1px solid rgba(0,0,0,0.1)',
'&:hover': {
bgcolor: 'rgba(0,0,0,0.1)',
transform: 'translateY(-1px)',
boxShadow: '0 4px 12px rgba(0,0,0,0.2)'
},
transition: 'all 0.3s ease',
width: 36,
height: 36
}}
>
<RefreshIcon sx={{ fontSize: 16 }} />
</IconButton>
</motion.div>
</MuiTooltip>
{/* Combined info line */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<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>
</Box>
</Box>
);

View File

@@ -26,7 +26,7 @@ export const getCategoryColor = (categoryId: string): string => {
case 'competitive_intelligence': return 'success';
case 'content_strategy': return 'warning';
case 'performance_analytics': return 'info';
default: return 'default';
default: return 'primary';
}
};

View File

@@ -14,7 +14,9 @@ import {
Alert,
IconButton,
Collapse,
Tooltip
Tooltip,
Paper,
Grid
} from '@mui/material';
import {
DataUsage as DataUsageIcon,
@@ -24,19 +26,26 @@ import {
Info as InfoIcon,
ExpandMore as ExpandMoreIcon,
ExpandLess as ExpandLessIcon,
Refresh as RefreshIcon
Refresh as RefreshIcon,
Timeline as TimelineIcon,
TrendingUp as TrendingUpIcon,
Schedule as ScheduleIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
interface DataSourceTransparencyProps {
autoPopulatedFields: Record<string, any>;
dataSources: Record<string, string>;
inputDataPoints?: Record<string, any>; // Actual input data used to generate each field
}
const DataSourceTransparency: React.FC<DataSourceTransparencyProps> = ({
autoPopulatedFields,
dataSources
dataSources,
inputDataPoints = {}
}) => {
const [expanded, setExpanded] = React.useState(true);
const [showDataFlow, setShowDataFlow] = React.useState(false);
const getDataSourceIcon = (source: string) => {
const icons = {
@@ -81,11 +90,99 @@ const DataSourceTransparency: React.FC<DataSourceTransparencyProps> = ({
return 'Low Quality';
};
const getDataFreshness = (source: string) => {
// Mock data freshness (in hours)
const freshness = {
website_analysis: 2,
research_preferences: 24,
api_keys: 168, // 1 week
onboarding_session: 48
};
return freshness[source as keyof typeof freshness] || 24;
};
const getFreshnessColor = (hours: number) => {
if (hours <= 6) return 'success';
if (hours <= 24) return 'warning';
return 'error';
};
const getFreshnessLabel = (hours: number) => {
if (hours <= 6) return 'Very Fresh';
if (hours <= 24) return 'Fresh';
if (hours <= 168) return 'Recent';
return 'Stale';
};
// Get input data points for a specific field
const getInputDataPoints = (fieldId: string) => {
return inputDataPoints[fieldId] || null;
};
// Format input data points for display
const formatInputDataPoints = (dataPoints: any) => {
if (!dataPoints) return null;
if (typeof dataPoints === 'string') {
return dataPoints;
}
if (Array.isArray(dataPoints)) {
return dataPoints.join(', ');
}
if (typeof dataPoints === 'object') {
return Object.entries(dataPoints)
.map(([key, value]) => `${key}: ${value}`)
.join(', ');
}
return String(dataPoints);
};
// Get data transformation info
const getDataTransformationInfo = (fieldId: string, source: string) => {
const transformations = {
business_objectives: {
from: 'website_analysis',
transformation: 'Extracted business goals from website content and meta descriptions',
inputData: 'Website content, meta descriptions, about page'
},
target_metrics: {
from: 'research_preferences',
transformation: 'Derived KPIs from research preferences and industry standards',
inputData: 'Research preferences, industry benchmarks, competitor analysis'
},
content_preferences: {
from: 'onboarding_session',
transformation: 'Inferred from user preferences and industry analysis',
inputData: 'User preferences, industry trends, content consumption patterns'
},
preferred_formats: {
from: 'website_analysis',
transformation: 'Analyzed existing content formats and user engagement',
inputData: 'Existing content types, engagement metrics, platform analysis'
},
content_frequency: {
from: 'research_preferences',
transformation: 'Calculated optimal frequency based on audience and industry',
inputData: 'Audience research, industry standards, competitor frequency'
}
};
return transformations[fieldId as keyof typeof transformations] || {
from: source,
transformation: 'Data processed and transformed for optimal strategy',
inputData: 'Various data sources combined'
};
};
const autoPopulatedFieldsList = Object.entries(autoPopulatedFields).map(([fieldId, value]) => ({
fieldId,
value,
source: dataSources[fieldId] || 'unknown',
qualityScore: getDataQualityScore(dataSources[fieldId] || 'unknown')
qualityScore: getDataQualityScore(dataSources[fieldId] || 'unknown'),
freshness: getDataFreshness(dataSources[fieldId] || 'unknown')
}));
const sourceSummary = Object.entries(dataSources).reduce((acc, [fieldId, source]) => {
@@ -106,7 +203,7 @@ const DataSourceTransparency: React.FC<DataSourceTransparencyProps> = ({
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}>
<DataUsageIcon color="primary" />
<Typography variant="h6">
Data Sources
Data Sources & Transparency
</Typography>
<Chip
icon={<AutoAwesomeIcon />}
@@ -130,6 +227,96 @@ const DataSourceTransparency: React.FC<DataSourceTransparencyProps> = ({
</Typography>
</Alert>
{/* Visual Data Flow Diagram */}
<Paper sx={{ p: 2, mb: 2, bgcolor: 'background.default' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<TimelineIcon color="primary" />
<Typography variant="subtitle2">Data Flow Visualization</Typography>
<IconButton
size="small"
onClick={() => setShowDataFlow(!showDataFlow)}
>
{showDataFlow ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</IconButton>
</Box>
<Collapse in={showDataFlow}>
<Grid container spacing={2}>
{Object.entries(sourceSummary).map(([source, fields], index) => (
<Grid item xs={12} md={6} key={source}>
<motion.div
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: index * 0.1 }}
>
<Paper sx={{ p: 2, bgcolor: 'background.paper' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<Typography variant="h6" sx={{ fontSize: '1.2rem' }}>
{getDataSourceIcon(source)}
</Typography>
<Typography variant="subtitle2" fontWeight="medium">
{getDataSourceLabel(source)}
</Typography>
</Box>
{/* Data Flow Path */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<Box sx={{
width: 20,
height: 20,
borderRadius: '50%',
bgcolor: 'primary.main',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<Typography variant="caption" color="white" fontWeight="bold">
{fields.length}
</Typography>
</Box>
<TrendingUpIcon color="primary" sx={{ fontSize: 16 }} />
<Typography variant="caption" color="text.secondary">
{fields.length} fields populated
</Typography>
</Box>
{/* Sample Input Data */}
<Box sx={{ mb: 1 }}>
<Typography variant="caption" color="text.secondary" sx={{ fontWeight: 500 }}>
Sample Input Data:
</Typography>
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', fontSize: '0.6rem' }}>
{source === 'website_analysis' && 'Website content, meta tags, page structure'}
{source === 'research_preferences' && 'User preferences, industry research, competitor data'}
{source === 'api_keys' && 'API configurations, service integrations, authentication'}
{source === 'onboarding_session' && 'User responses, preferences, business information'}
</Typography>
</Box>
{/* Quality & Freshness */}
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
<Chip
label={`${Math.round(getDataQualityScore(source) * 100)}% quality`}
size="small"
color={getDataQualityColor(getDataQualityScore(source))}
sx={{ fontSize: '0.6rem' }}
/>
<Chip
label={getFreshnessLabel(getDataFreshness(source))}
size="small"
color={getFreshnessColor(getDataFreshness(source))}
icon={<ScheduleIcon sx={{ fontSize: 12 }} />}
sx={{ fontSize: '0.6rem' }}
/>
</Box>
</Paper>
</motion.div>
</Grid>
))}
</Grid>
</Collapse>
</Paper>
{/* Data Sources Breakdown */}
<Box sx={{ mb: 2 }}>
<Typography variant="subtitle2" gutterBottom>
@@ -169,9 +356,17 @@ const DataSourceTransparency: React.FC<DataSourceTransparencyProps> = ({
{Math.round(getDataQualityScore(source) * 100)}%
</Typography>
</Box>
<Typography variant="caption" color="text.secondary">
{getDataQualityLabel(getDataQualityScore(source))}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography variant="caption" color="text.secondary">
{getDataQualityLabel(getDataQualityScore(source))}
</Typography>
<Typography variant="caption" color="text.secondary">
</Typography>
<Typography variant="caption" color="text.secondary">
{getFreshnessLabel(getDataFreshness(source))} ({getDataFreshness(source)}h ago)
</Typography>
</Box>
</Box>
}
/>
@@ -188,35 +383,73 @@ const DataSourceTransparency: React.FC<DataSourceTransparencyProps> = ({
Auto-populated Fields
</Typography>
<List dense>
{autoPopulatedFieldsList.map((field, index) => (
<React.Fragment key={field.fieldId}>
<ListItem sx={{ px: 0 }}>
<ListItemIcon sx={{ minWidth: 40 }}>
<CheckCircleIcon color="success" fontSize="small" />
</ListItemIcon>
<ListItemText
primary={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography variant="body2" fontWeight="medium">
{field.fieldId.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}
</Typography>
<Chip
label={getDataSourceLabel(field.source)}
size="small"
variant="outlined"
/>
</Box>
}
secondary={
<Typography variant="caption" color="text.secondary">
Source: {getDataSourceLabel(field.source)} Quality: {getDataQualityLabel(field.qualityScore)}
</Typography>
}
/>
</ListItem>
{index < autoPopulatedFieldsList.length - 1 && <Divider />}
</React.Fragment>
))}
{autoPopulatedFieldsList.map((field, index) => {
const inputData = getInputDataPoints(field.fieldId);
const transformationInfo = getDataTransformationInfo(field.fieldId, field.source);
return (
<React.Fragment key={field.fieldId}>
<ListItem sx={{ px: 0 }}>
<ListItemIcon sx={{ minWidth: 40 }}>
<CheckCircleIcon color="success" fontSize="small" />
</ListItemIcon>
<ListItemText
primary={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography variant="body2" fontWeight="medium">
{field.fieldId.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}
</Typography>
<Chip
label={getDataSourceLabel(field.source)}
size="small"
variant="outlined"
/>
</Box>
}
secondary={
<Box sx={{ mt: 0.5 }}>
<Typography variant="caption" color="text.secondary">
Source: {getDataSourceLabel(field.source)} Quality: {getDataQualityLabel(field.qualityScore)}
</Typography>
{/* Data Transformation Info */}
<Box sx={{ mt: 0.5, p: 1, bgcolor: 'rgba(76, 175, 80, 0.05)', borderRadius: 1 }}>
<Typography variant="caption" color="text.secondary" sx={{ fontWeight: 500 }}>
🔄 Transformation: {transformationInfo.transformation}
</Typography>
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.5 }}>
📊 Input Data: {transformationInfo.inputData}
</Typography>
{inputData && (
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.5 }}>
📝 Actual Input: {formatInputDataPoints(inputData)}
</Typography>
)}
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mt: 0.5 }}>
<Chip
label={`${Math.round(field.qualityScore * 100)}% confidence`}
size="small"
color={field.qualityScore > 0.8 ? 'success' : field.qualityScore > 0.6 ? 'warning' : 'error'}
sx={{ fontSize: '0.5rem', height: 16 }}
/>
<Chip
label={getFreshnessLabel(field.freshness)}
size="small"
color={getFreshnessColor(field.freshness)}
icon={<ScheduleIcon sx={{ fontSize: 10 }} />}
sx={{ fontSize: '0.5rem', height: 16 }}
/>
</Box>
</Box>
}
/>
</ListItem>
{index < autoPopulatedFieldsList.length - 1 && <Divider />}
</React.Fragment>
);
})}
</List>
</Box>

View File

@@ -158,6 +158,7 @@ interface EnhancedStrategyStore {
formErrors: Record<string, string>;
autoPopulatedFields: Record<string, any>;
dataSources: Record<string, string>;
inputDataPoints: Record<string, any>; // Detailed input data points from backend
// UI State
loading: boolean;
@@ -600,6 +601,7 @@ export const useEnhancedStrategyStore = create<EnhancedStrategyStore>((set, get)
formErrors: {},
autoPopulatedFields: {},
dataSources: {},
inputDataPoints: {}, // Initialize inputDataPoints
// UI State
loading: false,
@@ -719,6 +721,7 @@ export const useEnhancedStrategyStore = create<EnhancedStrategyStore>((set, get)
formErrors: {},
autoPopulatedFields: {},
dataSources: {},
inputDataPoints: {}, // Reset inputDataPoints
currentStep: 0,
completedSteps: []
});
@@ -768,9 +771,11 @@ export const useEnhancedStrategyStore = create<EnhancedStrategyStore>((set, get)
// Extract field values and sources from the new backend format
const fields = response.data?.fields || {};
const sources = response.data?.sources || {};
const inputDataPoints = response.data?.input_data_points || {};
console.log('📋 Extracted fields:', fields);
console.log('🔗 Data sources:', sources);
console.log('📝 Input data points:', inputDataPoints);
// Transform the fields object to extract values for formData
const fieldValues: Record<string, any> = {};
@@ -795,6 +800,7 @@ export const useEnhancedStrategyStore = create<EnhancedStrategyStore>((set, get)
set((state) => ({
autoPopulatedFields,
dataSources: sources,
inputDataPoints, // Store the detailed input data points
formData: { ...state.formData, ...fieldValues }
}));