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 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>
|
||||||
|
|||||||
@@ -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 }}>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
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
|
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 */}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user