ALwrity SEO CopilotKit Implementation Plan

This commit is contained in:
ajaysi
2025-08-30 20:07:55 +05:30
parent 0f16b855e1
commit 1e0a13e204
8 changed files with 381 additions and 94 deletions

View File

@@ -7,6 +7,7 @@ import Wizard from './components/OnboardingWizard/Wizard';
import MainDashboard from './components/MainDashboard/MainDashboard'; import MainDashboard from './components/MainDashboard/MainDashboard';
import SEODashboard from './components/SEODashboard/SEODashboard'; import SEODashboard from './components/SEODashboard/SEODashboard';
import ContentPlanningDashboard from './components/ContentPlanningDashboard/ContentPlanningDashboard'; import ContentPlanningDashboard from './components/ContentPlanningDashboard/ContentPlanningDashboard';
import FacebookWriter from './components/FacebookWriter/FacebookWriter';
import { apiClient } from './api/client'; import { apiClient } from './api/client';
@@ -180,7 +181,9 @@ const App: React.FC = () => {
<Route path="/onboarding" element={<Wizard />} /> <Route path="/onboarding" element={<Wizard />} />
<Route path="/dashboard" element={<MainDashboard />} /> <Route path="/dashboard" element={<MainDashboard />} />
<Route path="/seo" element={<SEODashboard />} /> <Route path="/seo" element={<SEODashboard />} />
<Route path="/seo-dashboard" element={<SEODashboard />} />
<Route path="/content-planning" element={<ContentPlanningDashboard />} /> <Route path="/content-planning" element={<ContentPlanningDashboard />} />
<Route path="/facebook-writer" element={<FacebookWriter />} />
</Routes> </Routes>
</ConditionalCopilotKit> </ConditionalCopilotKit>
</Router> </Router>

View File

@@ -622,8 +622,8 @@ const ContentStrategyBuilder: React.FC = () => {
suggestions={suggestions} suggestions={suggestions}
observabilityHooks={{ observabilityHooks={{
onChatExpanded: () => console.log("Strategy assistant opened"), onChatExpanded: () => console.log("Strategy assistant opened"),
onMessageSent: (message) => console.log("Strategy message sent", { message }), onMessageSent: (message: any) => console.log("Strategy message sent", { message }),
onFeedbackGiven: (messageId, type) => console.log("Strategy feedback", { messageId, type }) onFeedbackGiven: (messageId: string, type: string) => console.log("Strategy feedback", { messageId, type })
}} }}
> >
<Box sx={{ p: 3 }}> <Box sx={{ p: 3 }}>

View File

@@ -23,8 +23,7 @@ import {
Card, Card,
CardContent, CardContent,
CardHeader, CardHeader,
Avatar, Avatar
Badge
} from '@mui/material'; } from '@mui/material';
import { import {
CheckCircle as HealthyIcon, CheckCircle as HealthyIcon,
@@ -98,6 +97,7 @@ const SystemStatusIndicator: React.FC<SystemStatusIndicatorProps> = ({ className
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [dashboardOpen, setDashboardOpen] = useState(false); const [dashboardOpen, setDashboardOpen] = useState(false);
const [chartData, setChartData] = useState<any[]>([]); const [chartData, setChartData] = useState<any[]>([]);
const [cachePerf, setCachePerf] = useState<{ hits: number; misses: number; hit_rate: number } | null>(null);
const fetchStatus = async () => { const fetchStatus = async () => {
setLoading(true); setLoading(true);
@@ -137,6 +137,9 @@ const SystemStatusIndicator: React.FC<SystemStatusIndicatorProps> = ({ className
const result = await response.json(); const result = await response.json();
if (result.status === 'success') { if (result.status === 'success') {
setDetailedStats(result.data); setDetailedStats(result.data);
if (result.data?.cache_performance) {
setCachePerf(result.data.cache_performance);
}
// Generate chart data // Generate chart data
const chartData = result.data.top_endpoints.slice(0, 5).map((endpoint: any, index: number) => ({ const chartData = result.data.top_endpoints.slice(0, 5).map((endpoint: any, index: number) => ({
@@ -156,10 +159,16 @@ const SystemStatusIndicator: React.FC<SystemStatusIndicatorProps> = ({ className
useEffect(() => { useEffect(() => {
fetchStatus(); fetchStatus();
// Prime cache performance occasionally even when dashboard is closed
fetchDetailedStats();
// Refresh every 30 seconds // Refresh every 30 seconds
const interval = setInterval(fetchStatus, 30000); const interval = setInterval(fetchStatus, 30000);
return () => clearInterval(interval); const cacheInterval = setInterval(fetchDetailedStats, 60000);
return () => {
clearInterval(interval);
clearInterval(cacheInterval);
};
}, []); }, []);
useEffect(() => { useEffect(() => {
@@ -239,56 +248,46 @@ const SystemStatusIndicator: React.FC<SystemStatusIndicatorProps> = ({ className
); );
} }
const total = statusData?.recent_requests ?? 0;
const failed = statusData?.recent_errors ?? 0;
const passed = Math.max(0, total - failed);
return ( return (
<> <>
<Box className={className} sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Tooltip title={tooltipContent} arrow placement="bottom">
<Tooltip title={tooltipContent} arrow placement="bottom"> <Button
<Box
sx={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }}
onClick={handleDashboardClick}
>
<motion.div
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
>
{statusData ? getStatusIcon(statusData.status) : <UnknownIcon />}
</motion.div>
</Box>
</Tooltip>
<Chip
label={statusData?.status || 'Unknown'}
size="small"
color={getStatusColor(statusData?.status || 'unknown')}
variant="outlined" variant="outlined"
sx={{ height: 24, fontSize: '0.75rem' }}
/>
<IconButton
size="small"
onClick={fetchStatus}
disabled={loading}
sx={{ p: 0.5 }}
>
<RefreshIcon sx={{ fontSize: 16 }} />
</IconButton>
{/* Debug button to test dashboard opening */}
<IconButton
size="small"
onClick={handleDashboardClick} onClick={handleDashboardClick}
sx={{ p: 0.5, color: 'primary.main' }} sx={{
title="Open Dashboard (Debug)" display: 'flex',
alignItems: 'center',
gap: 1,
px: 1,
py: 0.5,
minHeight: 34,
borderRadius: 2,
background: 'linear-gradient(180deg, rgba(255,255,255,0.18), rgba(255,255,255,0.1))',
borderColor: 'rgba(255,255,255,0.35)',
color: 'white',
boxShadow: '0 10px 28px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.2)'
}}
> >
<AnalyticsIcon sx={{ fontSize: 16 }} /> {statusData ? getStatusIcon(statusData.status) : <UnknownIcon sx={{ color: 'grey.200' }} />}
</IconButton> <Chip
label={`System • ${(statusData?.status || 'unknown').toUpperCase()}`}
{error && ( size="small"
<Alert severity="error" sx={{ fontSize: '0.75rem', py: 0 }}> color={getStatusColor(statusData?.status || 'unknown')}
{error} sx={{ height: 22, fontSize: '0.70rem' }}
</Alert> />
)} <IconButton
</Box> size="small"
onClick={(e) => { e.stopPropagation(); fetchStatus(); fetchDetailedStats(); }}
sx={{ ml: 0.5, color: 'rgba(255,255,255,0.9)' }}
>
<RefreshIcon sx={{ fontSize: 16 }} />
</IconButton>
</Button>
</Tooltip>
{/* Enhanced Monitoring Dashboard */} {/* Enhanced Monitoring Dashboard */}
<Dialog <Dialog

View File

@@ -0,0 +1,118 @@
import React from 'react';
import { Box, Container, Typography, TextField, Paper } from '@mui/material';
import { CopilotSidebar } from '@copilotkit/react-ui';
import { useCopilotReadable, useCopilotAction } from '@copilotkit/react-core';
import '@copilotkit/react-ui/styles.css';
const useCopilotActionTyped = useCopilotAction as any;
const FacebookWriter: React.FC = () => {
const [postDraft, setPostDraft] = React.useState<string>('');
const [notes, setNotes] = React.useState<string>('');
// Share current draft and notes with Copilot
useCopilotReadable({
description: 'Current Facebook post draft text the user is editing',
value: postDraft,
categories: ['social', 'facebook', 'draft']
});
useCopilotReadable({
description: 'User notes/context for the next Facebook post',
value: notes,
categories: ['social', 'facebook', 'context']
});
// Allow Copilot to update the draft directly (predictive state-like edit)
useCopilotActionTyped({
name: 'updateFacebookPostDraft',
description: 'Replace the Facebook post draft with provided content',
parameters: [
{ name: 'content', type: 'string', description: 'The full post content to set', required: true }
],
handler: async ({ content }: { content: string }) => {
setPostDraft(content);
return { success: true, message: 'Draft updated' };
}
});
// Let Copilot append text to the draft (collaborative editing)
useCopilotActionTyped({
name: 'appendToFacebookPostDraft',
description: 'Append text to the current Facebook post draft',
parameters: [
{ name: 'content', type: 'string', description: 'The text to append', required: true }
],
handler: async ({ content }: { content: string }) => {
setPostDraft(prev => (prev ? `${prev}\n\n${content}` : content));
return { success: true, message: 'Text appended' };
}
});
const suggestions = [
{ title: '🎉 Launch teaser', message: 'Write a short Facebook post announcing our new feature launch' },
{ title: '💡 Benefit-first', message: 'Draft a Facebook post highlighting a key user benefit with a CTA' },
{ title: '🔁 Variations', message: 'Generate 3 alternative versions of this post to A/B test' },
{ title: '🏷️ Hashtags', message: 'Suggest 5 relevant hashtags for this post' }
];
return (
<CopilotSidebar
className="alwrity-copilot-sidebar"
labels={{
title: 'ALwrity • Facebook Writer',
initial: 'Tell me what you want to post. I can draft, refine, and generate variants. I can also update the draft directly for you.'
}}
suggestions={suggestions}
>
<Box sx={{ py: 4 }}>
<Container maxWidth="md">
<Typography variant="h4" sx={{ color: 'white', fontWeight: 800, mb: 2 }}>
Facebook Writer (Preview)
</Typography>
<Typography variant="body1" sx={{ color: 'rgba(255,255,255,0.85)', mb: 3 }}>
Collaborate with the Copilot to craft your post. The assistant can update the draft directly.
</Typography>
<Paper sx={{ p: 2, background: 'rgba(255,255,255,0.1)', border: '1px solid rgba(255,255,255,0.3)' }}>
<Typography variant="subtitle2" sx={{ color: 'rgba(255,255,255,0.9)', mb: 1 }}>
Context/Notes (optional)
</Typography>
<TextField
fullWidth
multiline
minRows={2}
value={notes}
onChange={e => setNotes(e.target.value)}
placeholder="Audience, campaign, tone, key points..."
sx={{
mb: 2,
'& .MuiInputBase-root': { color: 'white' },
'& .MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(255,255,255,0.3)' }
}}
/>
<Typography variant="subtitle2" sx={{ color: 'rgba(255,255,255,0.9)', mb: 1 }}>
Post Draft
</Typography>
<TextField
fullWidth
multiline
minRows={6}
value={postDraft}
onChange={e => setPostDraft(e.target.value)}
placeholder="Your Facebook post will appear here. Ask the Copilot to draft or update it."
sx={{
'& .MuiInputBase-root': { color: 'white' },
'& .MuiOutlinedInput-notchedOutline': { borderColor: 'rgba(255,255,255,0.3)' }
}}
/>
</Paper>
</Container>
</Box>
</CopilotSidebar>
);
};
export default FacebookWriter;

View File

@@ -9,9 +9,11 @@ import {
useMediaQuery useMediaQuery
} from '@mui/material'; } from '@mui/material';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import { useNavigate } from 'react-router-dom';
// Shared components // Shared components
import DashboardHeader from '../shared/DashboardHeader'; import DashboardHeader from '../shared/DashboardHeader';
import SystemStatusIndicator from '../ContentPlanningDashboard/components/SystemStatusIndicator';
import SearchFilter from '../shared/SearchFilter'; import SearchFilter from '../shared/SearchFilter';
import ToolCard from '../shared/ToolCard'; import ToolCard from '../shared/ToolCard';
import CategoryHeader from '../shared/CategoryHeader'; import CategoryHeader from '../shared/CategoryHeader';
@@ -33,6 +35,7 @@ import { toolCategories } from '../../data/toolCategories';
const MainDashboard: React.FC = () => { const MainDashboard: React.FC = () => {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md')); const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const navigate = useNavigate();
// Zustand store hooks // Zustand store hooks
const { const {
@@ -56,19 +59,10 @@ const MainDashboard: React.FC = () => {
const handleToolClick = (tool: Tool) => { const handleToolClick = (tool: Tool) => {
console.log('Navigating to tool:', tool.path); console.log('Navigating to tool:', tool.path);
if (tool.path) {
// Handle SEO Dashboard navigation navigate(tool.path);
if (tool.path === '/seo-dashboard') {
window.location.href = '/seo-dashboard';
return; return;
} }
// Handle Content Planning Dashboard navigation
if (tool.path === '/content-planning') {
window.location.href = '/content-planning';
return;
}
showSnackbar(`Launching ${tool.name}...`, 'info'); showSnackbar(`Launching ${tool.name}...`, 'info');
}; };
@@ -128,18 +122,8 @@ const MainDashboard: React.FC = () => {
<DashboardHeader <DashboardHeader
title="🚀 Alwrity Content Hub" title="🚀 Alwrity Content Hub"
subtitle="Your AI-powered content creation suite" subtitle="Your AI-powered content creation suite"
statusChips={[ statusChips={[]}
{ rightContent={<SystemStatusIndicator />}
label: 'Active',
color: '#4CAF50',
icon: <span></span>,
},
{
label: 'Premium',
color: '#FFD700',
icon: <span></span>,
},
]}
/> />
{/* Search and Filter */} {/* Search and Filter */}

View File

@@ -29,6 +29,22 @@ const SEOCopilotKitProvider: React.FC<SEOCopilotKitProviderProps> = ({
// Get the CopilotKit API key from environment variables // Get the CopilotKit API key from environment variables
const publicApiKey = process.env.REACT_APP_COPILOTKIT_API_KEY; const publicApiKey = process.env.REACT_APP_COPILOTKIT_API_KEY;
// Derive a friendly site/brand name from the URL for personalization
const domainRootName = useMemo(() => {
const url = analysisData?.url;
if (!url) return '';
try {
const withProto = url.startsWith('http') ? url : `https://${url}`;
const host = new URL(withProto).hostname;
const parts = host.split('.').filter(Boolean);
const root = parts.length >= 2 ? parts[parts.length - 2] : parts[0] || '';
if (!root) return '';
return root.charAt(0).toUpperCase() + root.slice(1);
} catch {
return '';
}
}, [analysisData?.url]);
// Suggestions model: progressive disclosure // Suggestions model: progressive disclosure
const topLevelGroups = useMemo(() => ([ const topLevelGroups = useMemo(() => ([
{ title: 'Content analysis', message: 'Content analysis' }, { title: 'Content analysis', message: 'Content analysis' },
@@ -115,9 +131,12 @@ const SEOCopilotKitProvider: React.FC<SEOCopilotKitProviderProps> = ({
return ( return (
<CopilotKit publicApiKey={publicApiKey}> <CopilotKit publicApiKey={publicApiKey}>
<CopilotSidebar <CopilotSidebar
className="alwrity-copilot-sidebar"
labels={{ labels={{
title: "SEO Assistant", title: domainRootName ? `ALwrity SEO • ${domainRootName}` : "ALwrity SEO Assistant",
initial: "Hi! 👋 I'm your SEO expert assistant. I can help you analyze your website, generate meta descriptions, check page speed, and much more. What would you like to work on today?", initial: domainRootName
? `Hi! 👋 I'm your SEO assistant for ${domainRootName}. I can analyze your site, generate meta descriptions, check page speed, and more. What would you like to work on today?`
: "Hi! 👋 I'm your SEO expert assistant. I can help you analyze your website, generate meta descriptions, check page speed, and much more. What would you like to work on today?",
}} }}
suggestions={displayedSuggestions} suggestions={displayedSuggestions}
makeSystemMessage={(context: string, additionalInstructions?: string) => { makeSystemMessage={(context: string, additionalInstructions?: string) => {
@@ -125,6 +144,7 @@ const SEOCopilotKitProvider: React.FC<SEOCopilotKitProviderProps> = ({
const urlLine = websiteUrl ? `The user's current website URL is ${websiteUrl}. If the user does not provide a URL explicitly, default to this URL.` : ''; const urlLine = websiteUrl ? `The user's current website URL is ${websiteUrl}. If the user does not provide a URL explicitly, default to this URL.` : '';
const guidance = ` const guidance = `
You are ALwrity's SEO Expert Assistant. ${urlLine} You are ALwrity's SEO Expert Assistant. ${urlLine}
When greeting the user, personalize messages${domainRootName ? ` for ${domainRootName}` : ''} and keep a professional, friendly tone.
Never ask for the URL if you already have it in context unless the user wants to switch URLs. Never ask for the URL if you already have it in context unless the user wants to switch URLs.
Focus on actionable recommendations and use the registered tools. Focus on actionable recommendations and use the registered tools.
`.trim(); `.trim();
@@ -202,6 +222,164 @@ Focus on actionable recommendations and use the registered tools.
height: 100%; height: 100%;
} }
/* ALwrity glassomorphic styling for Copilot sidebar */
.alwrity-copilot-sidebar {
--alwrity-bg: linear-gradient(180deg, rgba(255,255,255,0.16), rgba(255,255,255,0.08));
--alwrity-border: rgba(255,255,255,0.22);
--alwrity-shadow: 0 18px 50px rgba(0,0,0,0.35);
--alwrity-accent: #667eea;
--alwrity-accent2: #764ba2;
--alwrity-text: rgba(255,255,255,0.92);
--alwrity-subtext: rgba(255,255,255,0.7);
}
.alwrity-copilot-sidebar * {
font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, 'Helvetica Neue', Arial, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
}
.alwrity-copilot-sidebar .copilot-sidebar-container,
.alwrity-copilot-sidebar .copilotkit-sidebar,
.alwrity-copilot-sidebar .copilotkit-chat-container {
background: var(--alwrity-bg) !important;
backdrop-filter: blur(22px) !important;
-webkit-backdrop-filter: blur(22px) !important;
border: 1px solid var(--alwrity-border) !important;
box-shadow: var(--alwrity-shadow) !important;
color: var(--alwrity-text) !important;
}
.alwrity-copilot-sidebar .copilotkit-title,
.alwrity-copilot-sidebar .copilot-title {
color: var(--alwrity-text) !important;
font-weight: 700 !important;
letter-spacing: 0.2px !important;
}
.alwrity-copilot-sidebar .copilotkit-sidebar,
.alwrity-copilot-sidebar .copilot-sidebar-container {
z-index: 1200 !important;
}
.alwrity-copilot-sidebar .copilotkit-subtitle,
.alwrity-copilot-sidebar .copilot-subtitle,
.alwrity-copilot-sidebar .copilotkit-initial-message {
color: var(--alwrity-subtext) !important;
}
/* Suggestions: border, glow, depth, enterprise look */
.alwrity-copilot-sidebar .copilot-suggestion,
.alwrity-copilot-sidebar .copilotkit-suggestion,
.alwrity-copilot-sidebar .copilotkit-suggestions button,
.alwrity-copilot-sidebar .copilot-suggestions button {
position: relative;
background: linear-gradient(180deg, rgba(255,255,255,0.16), rgba(255,255,255,0.08)) !important;
border: 1.5px solid rgba(255,255,255,0.32) !important;
color: var(--alwrity-text) !important;
box-shadow: 0 10px 28px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.2) !important;
transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
border-radius: 12px !important;
overflow: hidden;
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
}
.alwrity-copilot-sidebar .copilot-suggestion::before,
.alwrity-copilot-sidebar .copilotkit-suggestion::before,
.alwrity-copilot-sidebar .copilotkit-suggestions button::before,
.alwrity-copilot-sidebar .copilot-suggestions button::before {
content: "";
position: absolute;
top: 0;
left: -120%;
width: 120%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.18), transparent);
transition: left 0.6s ease;
}
.alwrity-copilot-sidebar .copilot-suggestion:hover::before,
.alwrity-copilot-sidebar .copilotkit-suggestion:hover::before,
.alwrity-copilot-sidebar .copilotkit-suggestions button:hover::before,
.alwrity-copilot-sidebar .copilot-suggestions button:hover::before {
left: 120%;
}
.alwrity-copilot-sidebar .copilot-suggestion:hover,
.alwrity-copilot-sidebar .copilotkit-suggestion:hover,
.alwrity-copilot-sidebar .copilotkit-suggestions button:hover,
.alwrity-copilot-sidebar .copilot-suggestions button:hover {
transform: translateY(-4px) scale(1.015);
box-shadow: 0 18px 44px rgba(0,0,0,0.35), 0 0 0 1px rgba(255,255,255,0.18) inset !important;
border-color: rgba(255,255,255,0.45) !important;
}
.alwrity-copilot-sidebar .copilot-suggestion:active,
.alwrity-copilot-sidebar .copilotkit-suggestion:active,
.alwrity-copilot-sidebar .copilotkit-suggestions button:active,
.alwrity-copilot-sidebar .copilot-suggestions button:active {
transform: translateY(-1px) scale(0.995);
box-shadow: 0 8px 20px rgba(0,0,0,0.3) !important;
}
.alwrity-copilot-sidebar .copilot-suggestion:focus-visible,
.alwrity-copilot-sidebar .copilotkit-suggestion:focus-visible,
.alwrity-copilot-sidebar .copilotkit-suggestions button:focus-visible,
.alwrity-copilot-sidebar .copilot-suggestions button:focus-visible {
outline: none !important;
box-shadow: 0 0 0 2px rgba(255,255,255,0.45), 0 0 0 4px rgba(102,126,234,0.35) !important;
}
.alwrity-copilot-sidebar .copilot-suggestion .icon,
.alwrity-copilot-sidebar .copilotkit-suggestion .icon,
.alwrity-copilot-sidebar .copilotkit-suggestions button .icon {
filter: drop-shadow(0 2px 6px rgba(0,0,0,0.25));
}
.alwrity-copilot-sidebar .copilotkit-suggestions,
.alwrity-copilot-sidebar .copilot-suggestions {
display: grid;
grid-template-columns: 1fr;
gap: 10px;
}
@media (min-width: 420px) {
.alwrity-copilot-sidebar .copilotkit-suggestions,
.alwrity-copilot-sidebar .copilot-suggestions {
grid-template-columns: 1fr 1fr;
gap: 12px;
}
}
/* Reduce motion for users who prefer it */
@media (prefers-reduced-motion: reduce) {
.alwrity-copilot-sidebar .copilot-suggestion,
.alwrity-copilot-sidebar .copilotkit-suggestion,
.alwrity-copilot-sidebar .copilotkit-suggestions button,
.alwrity-copilot-sidebar .copilot-suggestions button {
transition: none !important;
}
.alwrity-copilot-sidebar .copilot-suggestion::before,
.alwrity-copilot-sidebar .copilotkit-suggestion::before,
.alwrity-copilot-sidebar .copilotkit-suggestions button::before,
.alwrity-copilot-sidebar .copilot-suggestions button::before {
display: none !important;
}
}
/* Scrollbar styling (webkit) */
.alwrity-copilot-sidebar ::-webkit-scrollbar {
width: 10px;
height: 10px;
}
.alwrity-copilot-sidebar ::-webkit-scrollbar-thumb {
background: rgba(255,255,255,0.25);
border: 2px solid rgba(0,0,0,0.1);
border-radius: 10px;
}
.alwrity-copilot-sidebar ::-webkit-scrollbar-track {
background: rgba(255,255,255,0.08);
border-radius: 10px;
}
.alwrity-copilot-sidebar .copilot-primary,
.alwrity-copilot-sidebar .copilotkit-primary-button,
.alwrity-copilot-sidebar button[type="submit"] {
background: linear-gradient(90deg, var(--alwrity-accent), var(--alwrity-accent2)) !important;
border: 1px solid rgba(255,255,255,0.35) !important;
color: white !important;
}
.alwrity-copilot-sidebar .copilot-input,
.alwrity-copilot-sidebar .copilotkit-input {
background: rgba(255,255,255,0.14) !important;
color: var(--alwrity-text) !important;
border: 1px solid rgba(255,255,255,0.22) !important;
}
.seo-copilotkit-loading { .seo-copilotkit-loading {
position: fixed; position: fixed;
top: 0; top: 0;

View File

@@ -6,7 +6,8 @@ import { DashboardHeaderProps } from './types';
const DashboardHeader: React.FC<DashboardHeaderProps> = ({ const DashboardHeader: React.FC<DashboardHeaderProps> = ({
title, title,
subtitle, subtitle,
statusChips = [] statusChips = [],
rightContent
}) => { }) => {
return ( return (
<ShimmerHeader sx={{ mb: 5 }}> <ShimmerHeader sx={{ mb: 5 }}>
@@ -33,23 +34,26 @@ const DashboardHeader: React.FC<DashboardHeaderProps> = ({
{subtitle} {subtitle}
</Typography> </Typography>
</Box> </Box>
{statusChips.length > 0 && ( <Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Box sx={{ display: 'flex', gap: 1.5 }}> {statusChips.length > 0 && (
{statusChips.map((chip, index) => ( <Box sx={{ display: 'flex', gap: 1.5 }}>
<Chip {statusChips.map((chip, index) => (
key={index} <Chip
icon={chip.icon} key={index}
label={chip.label} icon={chip.icon}
sx={{ label={chip.label}
background: `${chip.color}20`, sx={{
border: `1px solid ${chip.color}40`, background: `${chip.color}20`,
color: chip.color, border: `1px solid ${chip.color}40`,
fontWeight: 700, color: chip.color,
}} fontWeight: 700,
/> }}
))} />
</Box> ))}
)} </Box>
)}
{rightContent}
</Box>
</Box> </Box>
</ShimmerHeader> </ShimmerHeader>
); );

View File

@@ -83,6 +83,7 @@ export interface DashboardHeaderProps {
color: string; color: string;
icon: React.ReactElement; icon: React.ReactElement;
}>; }>;
rightContent?: React.ReactNode;
} }
export interface LoadingSkeletonProps { export interface LoadingSkeletonProps {