Save local changes (GSC/Bing integrations) before merging PR #354

This commit is contained in:
ajaysi
2026-02-13 13:11:27 +05:30
parent 43e66835ac
commit 08a1f4a1d8
144 changed files with 8310 additions and 2748 deletions

View File

@@ -35,6 +35,8 @@ export interface ImagePreset {
style: ImageStyle;
renderingSpeed: RenderingSpeed;
aspectRatio: AspectRatio;
model?: ImageModel;
image?: string; // Path to example image
}
// Model option for the model selector

View File

@@ -99,6 +99,60 @@ export const PODCAST_THEME: ImageModalTheme = {
warningAccent: '#f59e0b',
};
// ============================================
// Brand Avatar Presets
// ============================================
export const BRAND_AVATAR_PRESETS: ImagePreset[] = [
{
key: 'professionalHeadshot',
title: 'Professional Headshot',
subtitle: 'Clean, corporate-ready professional portrait',
prompt: 'Professional business headshot, confident expression, soft studio lighting, neutral background, sharp focus, high resolution, corporate attire, trustworthy demeanor',
style: 'Realistic',
renderingSpeed: 'Quality',
aspectRatio: '1:1',
image: '/assets/examples/professional_headshot.png',
},
{
key: 'creativeMascot',
title: 'Creative Mascot',
subtitle: 'Stylized 3D character for brand identity',
prompt: '3D character mascot, friendly and approachable, vibrant brand colors, soft rendering, pixar-style, expressive features, clean background, memorable design',
style: 'Fiction',
renderingSpeed: 'Quality',
aspectRatio: '1:1',
image: '/assets/examples/creative_mascot.png',
},
{
key: 'techVisionary',
title: 'Tech Visionary',
subtitle: 'Modern, forward-looking tech aesthetic',
prompt: 'Modern tech entrepreneur, futuristic lighting, smart casual attire, innovative atmosphere, clean tech background, confident gaze, professional but approachable',
style: 'Realistic',
renderingSpeed: 'Quality',
aspectRatio: '1:1',
image: '/assets/examples/tech_visionary.png',
},
{
key: 'artisticPortrait',
title: 'Artistic Portrait',
subtitle: 'Unique, hand-drawn or painted style avatar',
prompt: 'Digital art portrait, expressive brushstrokes, unique artistic style, vibrant color palette, creative composition, abstract background elements, distinct personality',
style: 'Fiction',
renderingSpeed: 'Quality',
aspectRatio: '1:1',
image: '/assets/examples/artistic_portrait.png',
},
];
export const BRAND_AVATAR_THEME: ImageModalTheme = {
dialogBackground: 'rgba(20, 20, 30, 0.98)',
primaryAccent: '#7C3AED', // Violet
secondaryAccent: '#EC4899', // Pink
warningAccent: '#F59E0B',
};
// ============================================
// YouTube-specific Recommendations
// ============================================
@@ -145,3 +199,24 @@ export const PODCAST_RECOMMENDATIONS: CustomRecommendations = {
</>,
};
// ============================================
// Brand Avatar-specific Recommendations
// ============================================
export const BRAND_AVATAR_RECOMMENDATIONS: CustomRecommendations = {
style: <>
<strong>Realistic:</strong> Best for professional personal brands and executive headshots.<br />
<strong>Fiction:</strong> Ideal for creative agencies, gaming brands, or friendly mascots.
</>,
speed: <>
<strong>Quality:</strong> Recommended for avatars as they are long-term brand assets.<br />
<strong>Turbo:</strong> Good for exploring concepts quickly.
</>,
aspectRatio: <>
<strong>1:1 (Square)</strong> is the standard for profile pictures across all social platforms (LinkedIn, Twitter, Instagram).
</>,
model: <>
<strong>Ideogram V3 Turbo:</strong> Superior text rendering and photorealism (Recommended).<br />
<strong>Qwen Image:</strong> Fast and cost-effective for iterations.
</>,
};

View File

@@ -109,9 +109,14 @@ export const OperationButton: React.FC<OperationButtonProps> = ({
// Format cost as currency
const formattedCost = useMemo(() => {
if (!showCost || estimatedCost === 0) {
// Show cost even if 0 if explicitly requested, but usually 0 means "free" or "included"
// The issue reported is showing "0" when it should show real cost
// If estimatedCost is undefined, don't show
if (!showCost || estimatedCost === undefined) {
return null;
}
// Always format, even if 0
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',

View File

@@ -96,7 +96,6 @@ const PlatformAnalytics: React.FC<PlatformAnalyticsComponentProps> = ({
// Method to force refresh (bypass cache)
const forceRefresh = useCallback(async () => {
console.log('🔄 PlatformAnalytics: Force refresh requested');
setLoading(true);
setError(null);
@@ -107,9 +106,8 @@ const PlatformAnalytics: React.FC<PlatformAnalyticsComponentProps> = ({
// Reload data
await loadData();
console.log('✅ PlatformAnalytics: Force refresh completed');
} catch (err) {
console.error('PlatformAnalytics: Force refresh failed:', err);
console.error('PlatformAnalytics: Force refresh failed:', err);
setError(err instanceof Error ? err.message : 'Failed to refresh data');
} finally {
setLoading(false);

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import {
Box,
Chip,
@@ -9,18 +9,17 @@ import {
IconButton,
Menu,
MenuItem,
Divider,
LinearProgress
} from '@mui/material';
import {
TrendingUp,
TrendingDown,
Warning,
CheckCircle,
Refresh,
MoreVert,
Dashboard
} from '@mui/icons-material';
import { useUser } from '@clerk/clerk-react';
import { apiClient } from '../../api/client';
import { useSubscription } from '../../contexts/SubscriptionContext';
import { usePriority2Alerts } from '../../hooks/usePriority2Alerts';
@@ -84,7 +83,8 @@ const UsageDashboard: React.FC<UsageDashboardProps> = ({
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
const userId = localStorage.getItem('user_id');
const { user } = useUser();
const userId = localStorage.getItem('user_id') || user?.id;
// Priority 2 Alerts - automatically appears in all tool headers
const { alerts: priority2Alerts, dismissAlert: dismissPriority2Alert } = usePriority2Alerts({
@@ -93,7 +93,7 @@ const UsageDashboard: React.FC<UsageDashboardProps> = ({
checkInterval: 120000, // Check every 2 minutes
});
const fetchUsageData = async () => {
const fetchUsageData = useCallback(async () => {
if (!userId) return;
setLoading(true);
@@ -109,11 +109,23 @@ const UsageDashboard: React.FC<UsageDashboardProps> = ({
} finally {
setLoading(false);
}
};
}, [userId]);
useEffect(() => {
fetchUsageData();
}, [userId]);
// Listen for custom event to refresh usage data
const handleUsageRefresh = () => {
console.log('UsageDashboard: Refreshing usage data due to event');
fetchUsageData();
};
window.addEventListener('alwrity:refresh-usage', handleUsageRefresh);
return () => {
window.removeEventListener('alwrity:refresh-usage', handleUsageRefresh);
};
}, [fetchUsageData, userId]);
const handleRefresh = () => {
fetchUsageData();
@@ -159,12 +171,13 @@ const UsageDashboard: React.FC<UsageDashboardProps> = ({
'serper': 'Serper',
'metaphor': 'Metaphor',
'firecrawl': 'Firecrawl',
'stability': 'Stability'
'stability': 'Stability',
'wavespeed': 'WaveSpeed'
};
return names[provider] || provider;
};
if (!subscription || !dashboardData) {
if (!dashboardData) {
if (loading) {
return (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
@@ -184,7 +197,13 @@ const UsageDashboard: React.FC<UsageDashboardProps> = ({
);
}
return <Box />; // Return empty box instead of null
// If no data and not loading/error, try to fetch again or show placeholder
if (userId && !dashboardData) {
// Optional: could auto-trigger another fetch here if needed, but useEffect handles it
return <Box />;
}
return <Box />;
}
if (compact) {

View File

@@ -34,14 +34,14 @@ const UserBadge: React.FC<UserBadgeProps> = ({ colorMode = 'light' }) => {
setSystemStatus(result.data.status || 'unknown');
}
} catch (err) {
console.error('Error fetching system status:', err);
// Silently fail for system status to avoid console noise
setSystemStatus('unknown');
}
};
fetchSystemStatus();
// Refresh every 30 seconds
const interval = setInterval(fetchSystemStatus, 30000);
// Refresh every 120 seconds (2 minutes) to reduce load and avoid timeouts
const interval = setInterval(fetchSystemStatus, 120000);
return () => clearInterval(interval);
}, []);

View File

@@ -0,0 +1,214 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Typography,
LinearProgress,
Paper,
Stack,
Fade,
useTheme,
Chip
} from '@mui/material';
import {
MovieFilter,
Campaign,
CurrencyExchange,
RecordVoiceOver,
AutoAwesome
} from '@mui/icons-material';
interface TipSlide {
title: string;
description: string;
icon: React.ReactNode;
tags?: string[];
}
const EDUCATIONAL_SLIDES: TipSlide[] = [
{
title: "Powering Your Creativity",
description: "ALwrity integrates state-of-the-art video models like HunyuanVideo, LTX-2 Pro, and WAN 2.5. Create cinematic scenes from simple text or images.",
icon: <MovieFilter fontSize="large" />,
tags: ["Hunyuan", "LTX-2 Pro", "WAN 2.5"]
},
{
title: "Beyond Talking Heads",
description: "Visit the Creative Scenes Studio to generate b-roll, product showcases, and dynamic social media clips. Perfect for filling gaps in your narrative.",
icon: <AutoAwesome fontSize="large" />,
tags: ["B-Roll", "Product Video", "Social Clips"]
},
{
title: "Multi-Platform Ready",
description: "Generate vertical videos for TikTok, Reels, and Shorts, or horizontal formats for YouTube and LinkedIn. One idea, everywhere.",
icon: <Campaign fontSize="large" />,
tags: ["9:16 Vertical", "16:9 Horizontal"]
},
{
title: "Studio Quality, Micro Cost",
description: "Skip the expensive equipment, actors, and studio time. Create professional marketing assets for cents, not thousands.",
icon: <CurrencyExchange fontSize="large" />,
tags: ["Cost Effective", "High ROI"]
},
{
title: "Your Voice, Scaled",
description: "Clone your voice once and generate unlimited audio content. Perfect for podcasts, voiceovers, and consistent brand messaging.",
icon: <RecordVoiceOver fontSize="large" />,
tags: ["Voice Cloning", "Podcasts"]
}
];
export const VideoGenerationLoader: React.FC = () => {
const [activeSlide, setActiveSlide] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setActiveSlide((prev) => (prev + 1) % EDUCATIONAL_SLIDES.length);
}, 6000); // Rotate every 6 seconds
return () => clearInterval(interval);
}, []);
const slide = EDUCATIONAL_SLIDES[activeSlide];
return (
<Box
sx={{
width: '100%',
height: '100%',
minHeight: 400,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
overflow: 'hidden',
bgcolor: '#f8fafc',
borderRadius: 3,
border: '1px solid #e2e8f0',
p: 4
}}
>
{/* Background decoration */}
<Box
sx={{
position: 'absolute',
top: -100,
right: -100,
width: 300,
height: 300,
borderRadius: '50%',
background: 'radial-gradient(circle, rgba(37,99,235,0.05) 0%, rgba(255,255,255,0) 70%)',
zIndex: 0
}}
/>
<Box
sx={{
position: 'absolute',
bottom: -50,
left: -50,
width: 200,
height: 200,
borderRadius: '50%',
background: 'radial-gradient(circle, rgba(236,72,153,0.05) 0%, rgba(255,255,255,0) 70%)',
zIndex: 0
}}
/>
<Stack spacing={4} alignItems="center" sx={{ zIndex: 1, maxWidth: 600, width: '100%' }}>
{/* Loading Indicator */}
<Box sx={{ width: '100%', textAlign: 'center' }}>
<Typography variant="h6" fontWeight={700} color="primary" gutterBottom>
Generating Your Video...
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
This usually takes 2-4 minutes. While you wait, learn what else ALwrity can do.
</Typography>
<LinearProgress
sx={{
height: 8,
borderRadius: 4,
bgcolor: '#eff6ff',
'& .MuiLinearProgress-bar': {
borderRadius: 4,
background: 'linear-gradient(90deg, #2563eb 0%, #7c3aed 100%)'
}
}}
/>
</Box>
{/* Carousel Card */}
<Fade in={true} key={activeSlide} timeout={800}>
<Paper
elevation={0}
sx={{
p: 4,
width: '100%',
bgcolor: 'white',
borderRadius: 3,
border: '1px solid #e2e8f0',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
textAlign: 'center',
boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.05)'
}}
>
<Box
sx={{
p: 2,
borderRadius: '50%',
bgcolor: '#eff6ff',
color: '#2563eb',
mb: 2,
boxShadow: '0 4px 6px -1px rgba(37, 99, 235, 0.1)'
}}
>
{slide.icon}
</Box>
<Typography variant="h6" fontWeight={800} color="#0f172a" gutterBottom>
{slide.title}
</Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 3, lineHeight: 1.6 }}>
{slide.description}
</Typography>
<Stack direction="row" spacing={1} flexWrap="wrap" justifyContent="center" gap={1}>
{slide.tags?.map((tag) => (
<Chip
key={tag}
label={tag}
size="small"
sx={{
bgcolor: '#f1f5f9',
color: '#475569',
fontWeight: 600,
borderRadius: 1.5
}}
/>
))}
</Stack>
</Paper>
</Fade>
{/* Progress Dots */}
<Stack direction="row" spacing={1}>
{EDUCATIONAL_SLIDES.map((_, index) => (
<Box
key={index}
sx={{
width: 8,
height: 8,
borderRadius: '50%',
bgcolor: index === activeSlide ? '#2563eb' : '#cbd5e1',
transition: 'all 0.3s ease',
transform: index === activeSlide ? 'scale(1.2)' : 'scale(1)'
}}
/>
))}
</Stack>
</Stack>
</Box>
);
};