ALwrity SEO CopilotKit Implementation Plan
This commit is contained in:
@@ -7,6 +7,7 @@ import Wizard from './components/OnboardingWizard/Wizard';
|
||||
import MainDashboard from './components/MainDashboard/MainDashboard';
|
||||
import SEODashboard from './components/SEODashboard/SEODashboard';
|
||||
import ContentPlanningDashboard from './components/ContentPlanningDashboard/ContentPlanningDashboard';
|
||||
import FacebookWriter from './components/FacebookWriter/FacebookWriter';
|
||||
|
||||
import { apiClient } from './api/client';
|
||||
|
||||
@@ -180,7 +181,9 @@ const App: React.FC = () => {
|
||||
<Route path="/onboarding" element={<Wizard />} />
|
||||
<Route path="/dashboard" element={<MainDashboard />} />
|
||||
<Route path="/seo" element={<SEODashboard />} />
|
||||
<Route path="/seo-dashboard" element={<SEODashboard />} />
|
||||
<Route path="/content-planning" element={<ContentPlanningDashboard />} />
|
||||
<Route path="/facebook-writer" element={<FacebookWriter />} />
|
||||
</Routes>
|
||||
</ConditionalCopilotKit>
|
||||
</Router>
|
||||
|
||||
@@ -622,8 +622,8 @@ const ContentStrategyBuilder: React.FC = () => {
|
||||
suggestions={suggestions}
|
||||
observabilityHooks={{
|
||||
onChatExpanded: () => console.log("Strategy assistant opened"),
|
||||
onMessageSent: (message) => console.log("Strategy message sent", { message }),
|
||||
onFeedbackGiven: (messageId, type) => console.log("Strategy feedback", { messageId, type })
|
||||
onMessageSent: (message: any) => console.log("Strategy message sent", { message }),
|
||||
onFeedbackGiven: (messageId: string, type: string) => console.log("Strategy feedback", { messageId, type })
|
||||
}}
|
||||
>
|
||||
<Box sx={{ p: 3 }}>
|
||||
|
||||
@@ -23,8 +23,7 @@ import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
Avatar,
|
||||
Badge
|
||||
Avatar
|
||||
} from '@mui/material';
|
||||
import {
|
||||
CheckCircle as HealthyIcon,
|
||||
@@ -98,6 +97,7 @@ const SystemStatusIndicator: React.FC<SystemStatusIndicatorProps> = ({ className
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [dashboardOpen, setDashboardOpen] = useState(false);
|
||||
const [chartData, setChartData] = useState<any[]>([]);
|
||||
const [cachePerf, setCachePerf] = useState<{ hits: number; misses: number; hit_rate: number } | null>(null);
|
||||
|
||||
const fetchStatus = async () => {
|
||||
setLoading(true);
|
||||
@@ -137,6 +137,9 @@ const SystemStatusIndicator: React.FC<SystemStatusIndicatorProps> = ({ className
|
||||
const result = await response.json();
|
||||
if (result.status === 'success') {
|
||||
setDetailedStats(result.data);
|
||||
if (result.data?.cache_performance) {
|
||||
setCachePerf(result.data.cache_performance);
|
||||
}
|
||||
|
||||
// Generate chart data
|
||||
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(() => {
|
||||
fetchStatus();
|
||||
|
||||
// Prime cache performance occasionally even when dashboard is closed
|
||||
fetchDetailedStats();
|
||||
|
||||
// Refresh every 30 seconds
|
||||
const interval = setInterval(fetchStatus, 30000);
|
||||
return () => clearInterval(interval);
|
||||
const cacheInterval = setInterval(fetchDetailedStats, 60000);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
clearInterval(cacheInterval);
|
||||
};
|
||||
}, []);
|
||||
|
||||
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 (
|
||||
<>
|
||||
<Box className={className} sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Tooltip title={tooltipContent} arrow placement="bottom">
|
||||
<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')}
|
||||
<Tooltip title={tooltipContent} arrow placement="bottom">
|
||||
<Button
|
||||
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}
|
||||
sx={{ p: 0.5, color: 'primary.main' }}
|
||||
title="Open Dashboard (Debug)"
|
||||
sx={{
|
||||
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 }} />
|
||||
</IconButton>
|
||||
|
||||
{error && (
|
||||
<Alert severity="error" sx={{ fontSize: '0.75rem', py: 0 }}>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
</Box>
|
||||
{statusData ? getStatusIcon(statusData.status) : <UnknownIcon sx={{ color: 'grey.200' }} />}
|
||||
<Chip
|
||||
label={`System • ${(statusData?.status || 'unknown').toUpperCase()}`}
|
||||
size="small"
|
||||
color={getStatusColor(statusData?.status || 'unknown')}
|
||||
sx={{ height: 22, fontSize: '0.70rem' }}
|
||||
/>
|
||||
<IconButton
|
||||
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 */}
|
||||
<Dialog
|
||||
|
||||
118
frontend/src/components/FacebookWriter/FacebookWriter.tsx
Normal file
118
frontend/src/components/FacebookWriter/FacebookWriter.tsx
Normal 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;
|
||||
|
||||
|
||||
@@ -9,9 +9,11 @@ import {
|
||||
useMediaQuery
|
||||
} from '@mui/material';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
// Shared components
|
||||
import DashboardHeader from '../shared/DashboardHeader';
|
||||
import SystemStatusIndicator from '../ContentPlanningDashboard/components/SystemStatusIndicator';
|
||||
import SearchFilter from '../shared/SearchFilter';
|
||||
import ToolCard from '../shared/ToolCard';
|
||||
import CategoryHeader from '../shared/CategoryHeader';
|
||||
@@ -33,6 +35,7 @@ import { toolCategories } from '../../data/toolCategories';
|
||||
const MainDashboard: React.FC = () => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Zustand store hooks
|
||||
const {
|
||||
@@ -56,19 +59,10 @@ const MainDashboard: React.FC = () => {
|
||||
|
||||
const handleToolClick = (tool: Tool) => {
|
||||
console.log('Navigating to tool:', tool.path);
|
||||
|
||||
// Handle SEO Dashboard navigation
|
||||
if (tool.path === '/seo-dashboard') {
|
||||
window.location.href = '/seo-dashboard';
|
||||
if (tool.path) {
|
||||
navigate(tool.path);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle Content Planning Dashboard navigation
|
||||
if (tool.path === '/content-planning') {
|
||||
window.location.href = '/content-planning';
|
||||
return;
|
||||
}
|
||||
|
||||
showSnackbar(`Launching ${tool.name}...`, 'info');
|
||||
};
|
||||
|
||||
@@ -128,18 +122,8 @@ const MainDashboard: React.FC = () => {
|
||||
<DashboardHeader
|
||||
title="🚀 Alwrity Content Hub"
|
||||
subtitle="Your AI-powered content creation suite"
|
||||
statusChips={[
|
||||
{
|
||||
label: 'Active',
|
||||
color: '#4CAF50',
|
||||
icon: <span>✓</span>,
|
||||
},
|
||||
{
|
||||
label: 'Premium',
|
||||
color: '#FFD700',
|
||||
icon: <span>★</span>,
|
||||
},
|
||||
]}
|
||||
statusChips={[]}
|
||||
rightContent={<SystemStatusIndicator />}
|
||||
/>
|
||||
|
||||
{/* Search and Filter */}
|
||||
|
||||
@@ -29,6 +29,22 @@ const SEOCopilotKitProvider: React.FC<SEOCopilotKitProviderProps> = ({
|
||||
// Get the CopilotKit API key from environment variables
|
||||
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
|
||||
const topLevelGroups = useMemo(() => ([
|
||||
{ title: 'Content analysis', message: 'Content analysis' },
|
||||
@@ -115,9 +131,12 @@ const SEOCopilotKitProvider: React.FC<SEOCopilotKitProviderProps> = ({
|
||||
return (
|
||||
<CopilotKit publicApiKey={publicApiKey}>
|
||||
<CopilotSidebar
|
||||
className="alwrity-copilot-sidebar"
|
||||
labels={{
|
||||
title: "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?",
|
||||
title: domainRootName ? `ALwrity SEO • ${domainRootName}` : "ALwrity SEO Assistant",
|
||||
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}
|
||||
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 guidance = `
|
||||
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.
|
||||
Focus on actionable recommendations and use the registered tools.
|
||||
`.trim();
|
||||
@@ -202,6 +222,164 @@ Focus on actionable recommendations and use the registered tools.
|
||||
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 {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
||||
@@ -6,7 +6,8 @@ import { DashboardHeaderProps } from './types';
|
||||
const DashboardHeader: React.FC<DashboardHeaderProps> = ({
|
||||
title,
|
||||
subtitle,
|
||||
statusChips = []
|
||||
statusChips = [],
|
||||
rightContent
|
||||
}) => {
|
||||
return (
|
||||
<ShimmerHeader sx={{ mb: 5 }}>
|
||||
@@ -33,23 +34,26 @@ const DashboardHeader: React.FC<DashboardHeaderProps> = ({
|
||||
{subtitle}
|
||||
</Typography>
|
||||
</Box>
|
||||
{statusChips.length > 0 && (
|
||||
<Box sx={{ display: 'flex', gap: 1.5 }}>
|
||||
{statusChips.map((chip, index) => (
|
||||
<Chip
|
||||
key={index}
|
||||
icon={chip.icon}
|
||||
label={chip.label}
|
||||
sx={{
|
||||
background: `${chip.color}20`,
|
||||
border: `1px solid ${chip.color}40`,
|
||||
color: chip.color,
|
||||
fontWeight: 700,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
||||
{statusChips.length > 0 && (
|
||||
<Box sx={{ display: 'flex', gap: 1.5 }}>
|
||||
{statusChips.map((chip, index) => (
|
||||
<Chip
|
||||
key={index}
|
||||
icon={chip.icon}
|
||||
label={chip.label}
|
||||
sx={{
|
||||
background: `${chip.color}20`,
|
||||
border: `1px solid ${chip.color}40`,
|
||||
color: chip.color,
|
||||
fontWeight: 700,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
{rightContent}
|
||||
</Box>
|
||||
</Box>
|
||||
</ShimmerHeader>
|
||||
);
|
||||
|
||||
@@ -83,6 +83,7 @@ export interface DashboardHeaderProps {
|
||||
color: string;
|
||||
icon: React.ReactElement;
|
||||
}>;
|
||||
rightContent?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface LoadingSkeletonProps {
|
||||
|
||||
Reference in New Issue
Block a user