Files
ALwrity/frontend/src/components/WixTestPage/WixTestPage.tsx
ajaysi 923fa671fe feat: ContentGuardianAgent, onboarding UX, Team Activity action wiring, docs, agent help modal
ContentGuardianAgent consolidation:
- Merge 3 duplicate classes into single source in specialized/content_guardian.py
- Watchdog audit_committee() with heuristic scoring, coverage gaps, overlaps, alerts
- Remove misleading rejection_rate() helper; use acceptance_rate directly
- Integrate audit + alerts + trend signals into today_workflow_service.py

Team Activity page:
- QualityAuditPanel: health ring, per-agent critiques, coverage gaps, overlaps
- TrendSignalsPanel: opportunity cards with urgency/impact/coverage bars
- AlertBanner: persistent dismiss via POST /alerts/{id}/mark-read
- AgentHelpModal: dialog showing all 8 agents with descriptions, tools, schedule
- QualityAuditPanel action buttons: Fill gap -> /content-planning, Resolve overlap, View CTA on alerts/issues
- TrendSignalsPanel action buttons: Create content from this trend -> /blog-writer with trend context state

Onboarding system:
- Step 4 validation: no auto-pass via basic_ready; requires persona data or explicit progression
- Step 5 validation: logs warning on auto-pass without integration data
- OnboardingCompletionService: single DB session, transactional task creation, upsert pattern
- Business-without-website: nullable website_url on SIFIndexingTask and MarketTrendsTask
- DeepCompetitorAnalysisExecutor: 5-min timeout, 10-competitor cap, asyncio.wait_for
- Persona generation: async with 30s timeout, falls back to scheduler
- OnboardingProgressService.reset_onboarding(): resets session + pauses all DB tasks
- OnboardingControlService.reset_onboarding(): also cancels APScheduler jobs
- FinalStep TaskSchedulingPanel: shows scheduled/failed tasks after completion, 8s auto-redirect
- onboarding_completed agent activity event logged to feed

Documentation:
- docs-site/features/onboarding/: overview, steps, scheduler-tasks, technical-reference (4 pages)
- docs-site/mkdocs.yml: added Onboarding System nav section
- docs-site/features/sif-agents/: overview, agent-directory, committee-system, content-guardian (4 pages)
- docs-site/features/team-activity/: overview, quality-audit, trend-signals, alert-system (4 pages)
- docs-site/features/todays-workflow/: updated overview, technical-architecture, workflow-guide, api-reference
2026-06-01 12:24:31 +05:30

462 lines
16 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from 'react';
import {
Box,
Button,
Card,
CardContent,
Typography,
Alert,
CircularProgress,
TextField,
FormControl,
InputLabel,
Select,
MenuItem,
Chip,
Divider
} from '@mui/material';
import { apiClient } from '../../api/client';
import { createClient, OAuthStrategy } from '@wix/sdk';
import { categories as blogCategoriesModule, tags as blogTagsModule } from '@wix/blog';
import { WIX_CLIENT_ID, getWixRedirectOrigin } from '../../config/wixConfig';
interface WixConnectionStatus {
connected: boolean;
has_permissions: boolean;
site_info?: any;
permissions?: any;
error?: string;
}
interface BlogCategories {
categories: Array<{
id: string;
name: string;
description?: string;
}>;
}
interface BlogTags {
tags: Array<{
id: string;
label: string;
}>;
}
const WixTestPage: React.FC = () => {
const [connectionStatus, setConnectionStatus] = useState<WixConnectionStatus | null>(null);
const [loading, setLoading] = useState(false);
const [publishing, setPublishing] = useState(false);
const [categories, setCategories] = useState<BlogCategories | null>(null);
const [tags, setTags] = useState<BlogTags | null>(null);
// Blog post form state
const [blogTitle, setBlogTitle] = useState('Test Blog Post from ALwrity');
const [blogContent, setBlogContent] = useState(`# Welcome to ALwrity-Wix Integration!
This is a test blog post created from the ALwrity platform and published directly to your Wix website.
## Features
- **Seamless Integration**: Publish directly from ALwrity to Wix
- **Rich Content**: Support for headings, paragraphs, and formatting
- **Image Support**: Automatic image import to Wix Media Manager
- **Category & Tag Support**: Organize your content with Wix categories and tags
## How It Works
1. Connect your Wix account to ALwrity
2. Generate your blog content using ALwrity's AI tools
3. Click "Publish to Wix" to publish directly to your website
4. Your content appears on your Wix blog instantly!
## Next Steps
This integration opens up new possibilities for content creators who want to leverage ALwrity's AI-powered writing tools while maintaining their Wix website presence.
*Published from ALwrity on ${new Date().toLocaleDateString()}*`);
const [selectedCategory, setSelectedCategory] = useState('');
const [selectedTags, setSelectedTags] = useState<string[]>([]);
const [coverImageUrl, setCoverImageUrl] = useState('');
// Check connection status on component mount
useEffect(() => {
checkConnectionStatus();
}, []);
const checkConnectionStatus = async () => {
setLoading(true);
try {
const response = await apiClient.get('/api/wix/test/connection/status');
const connectedFlag = sessionStorage.getItem('wix_connected') === 'true';
setConnectionStatus({
...response.data,
connected: connectedFlag || response.data.connected,
});
} catch (error) {
console.error('Failed to check connection status:', error);
setConnectionStatus({
connected: false,
has_permissions: false,
error: 'Failed to check connection status'
});
} finally {
setLoading(false);
}
};
const getAuthorizationUrl = async () => {
setLoading(true);
try {
const wixClient = createClient({
auth: OAuthStrategy({ clientId: WIX_CLIENT_ID })
});
const redirectOrigin = getWixRedirectOrigin();
const redirectUri = `${redirectOrigin}/wix/callback`;
const oauthData = await wixClient.auth.generateOAuthData(redirectUri);
// Use sessionStorage to ensure data is scoped to this tab/session
sessionStorage.setItem('wix_oauth_data', JSON.stringify(oauthData));
const { authUrl } = await wixClient.auth.getAuthUrl(oauthData);
window.location.href = authUrl;
} catch (error) {
console.error('Failed to start Wix OAuth flow:', error);
} finally {
setLoading(false);
}
};
const loadCategories = async () => {
try {
const tokensRaw = sessionStorage.getItem('wix_tokens');
if (!tokensRaw) throw new Error('Missing Wix tokens');
const tokens = JSON.parse(tokensRaw);
const wixClient = createClient({ modules: { categories: blogCategoriesModule }, auth: OAuthStrategy({ clientId: WIX_CLIENT_ID }) });
wixClient.auth.setTokens(tokens);
const result = await wixClient.categories.queryCategories().find();
const cats = (result.items || []).map((c: any) => ({ id: c.id, name: c.name || '', description: c.description || '' }));
setCategories({ categories: cats });
} catch (error: any) {
console.error('Failed to load categories:', error);
alert(`Could not load categories: ${error?.message || 'Unknown error'}`);
}
};
const loadTags = async () => {
try {
const tokensRaw = sessionStorage.getItem('wix_tokens');
if (!tokensRaw) throw new Error('Missing Wix tokens');
const tokens = JSON.parse(tokensRaw);
const wixClient = createClient({ modules: { tags: blogTagsModule }, auth: OAuthStrategy({ clientId: WIX_CLIENT_ID }) });
wixClient.auth.setTokens(tokens);
const result = await wixClient.tags.queryTags().find();
const t = (result.items || []).map((it: any) => ({ id: it.id, label: it.label || '' }));
setTags({ tags: t });
} catch (error: any) {
console.error('Failed to load tags:', error);
alert(`Could not load tags: ${error?.message || 'Unknown error'}`);
}
};
const publishToWix = async () => {
if (!blogTitle.trim() || !blogContent.trim()) {
alert('Please enter both title and content');
return;
}
setPublishing(true);
try {
// Use test-real endpoint to publish using the client-side access token
const tokensRaw = sessionStorage.getItem('wix_tokens');
if (!tokensRaw) throw new Error('Missing Wix tokens. Please reconnect.');
const tokens = JSON.parse(tokensRaw);
// For member-level authentication, we don't need to extract member_id
// The Wix Blog API will automatically use the member ID from the authenticated member token
const memberIdFromToken = undefined; // Let the API use the authenticated member's ID
const response = await apiClient.post('/api/wix/test/publish/real', {
title: blogTitle,
content: blogContent,
cover_image_url: coverImageUrl || undefined,
category_ids: selectedCategory ? [selectedCategory] : undefined,
tag_ids: selectedTags.length > 0 ? selectedTags : undefined,
publish: true,
access_token: tokens?.accessToken?.value || tokens?.access_token,
member_id: memberIdFromToken
});
if (response.data.success) {
alert(`Blog post published successfully! Post ID: ${response.data.post_id}`);
} else {
alert(`Failed to publish: ${response.data.error || response.data.message}`);
}
} catch (error: any) {
console.error('Failed to publish to Wix:', error);
alert(`Failed to publish: ${error.response?.data?.detail || error.message}`);
} finally {
setPublishing(false);
}
};
const disconnectWix = async () => {
setLoading(true);
try {
await apiClient.post('/api/wix/disconnect');
setConnectionStatus({
connected: false,
has_permissions: false,
error: 'Disconnected'
});
setCategories(null);
setTags(null);
} catch (error) {
console.error('Failed to disconnect:', error);
} finally {
setLoading(false);
}
};
return (
<Box sx={{ p: 3, maxWidth: 1200, mx: 'auto' }}>
<Typography variant="h4" gutterBottom>
Wix Integration Test Page
</Typography>
<Typography variant="body1" color="textSecondary" paragraph>
This page allows you to test the Wix integration functionality. Connect your Wix account
and publish blog posts directly from ALwrity to your Wix website.
</Typography>
{/* Connection Status Card */}
<Card sx={{ mb: 3 }}>
<CardContent>
<Typography variant="h6" gutterBottom>
Wix Connection Status
</Typography>
{loading ? (
<Box display="flex" alignItems="center" gap={2}>
<CircularProgress size={20} />
<Typography>Checking connection status...</Typography>
</Box>
) : connectionStatus ? (
<Box>
{connectionStatus.connected ? (
<Alert severity="success" sx={{ mb: 2 }}>
Connected to Wix
{connectionStatus.has_permissions && (
<Typography variant="body2" sx={{ mt: 1 }}>
Permissions: Blog creation and publishing enabled
</Typography>
)}
</Alert>
) : (
<Alert severity="warning" sx={{ mb: 2 }}>
Not connected to Wix
{connectionStatus.error && (
<Typography variant="body2" sx={{ mt: 1 }}>
{connectionStatus.error}
</Typography>
)}
</Alert>
)}
<Box display="flex" gap={2} flexWrap="wrap">
{!connectionStatus.connected ? (
<Button
variant="contained"
onClick={getAuthorizationUrl}
disabled={loading}
>
Connect to Wix
</Button>
) : (
<>
<Button
variant="outlined"
onClick={checkConnectionStatus}
disabled={loading}
>
Refresh Status
</Button>
<Button
variant="outlined"
onClick={loadCategories}
disabled={loading}
>
Load Categories
</Button>
<Button
variant="outlined"
onClick={loadTags}
disabled={loading}
>
Load Tags
</Button>
<Button
variant="outlined"
color="error"
onClick={disconnectWix}
disabled={loading}
>
Disconnect
</Button>
</>
)}
</Box>
</Box>
) : null}
</CardContent>
</Card>
{/* Blog Post Form */}
<Card sx={{ mb: 3 }}>
<CardContent>
<Typography variant="h6" gutterBottom>
Publish Blog Post to Wix
</Typography>
<Box display="flex" flexDirection="column" gap={2}>
<TextField
label="Blog Title"
value={blogTitle}
onChange={(e) => setBlogTitle(e.target.value)}
fullWidth
variant="outlined"
/>
<TextField
label="Blog Content (Markdown)"
value={blogContent}
onChange={(e) => setBlogContent(e.target.value)}
fullWidth
multiline
rows={10}
variant="outlined"
/>
<TextField
label="Cover Image URL (Optional)"
value={coverImageUrl}
onChange={(e) => setCoverImageUrl(e.target.value)}
fullWidth
variant="outlined"
placeholder="https://example.com/image.jpg"
/>
{categories && (
<FormControl fullWidth>
<InputLabel>Category (Optional)</InputLabel>
<Select
value={selectedCategory ?? ''}
onChange={(e) => setSelectedCategory(e.target.value)}
label="Category (Optional)"
>
<MenuItem key="none" value="">
None
</MenuItem>
{categories.categories.map((category, idx) => (
<MenuItem key={category.id || `${category.name}-${idx}`} value={category.id}>
{category.name}
</MenuItem>
))}
</Select>
</FormControl>
)}
{tags && (
<Box>
<Typography variant="subtitle2" gutterBottom>
Tags (Optional)
</Typography>
<Box display="flex" flexWrap="wrap" gap={1}>
{tags.tags.map((tag) => (
<Chip
key={tag.id}
label={tag.label}
onClick={() => {
if (selectedTags.includes(tag.id)) {
setSelectedTags(selectedTags.filter(id => id !== tag.id));
} else {
setSelectedTags([...selectedTags, tag.id]);
}
}}
color={selectedTags.includes(tag.id) ? 'primary' : 'default'}
variant={selectedTags.includes(tag.id) ? 'filled' : 'outlined'}
/>
))}
</Box>
</Box>
)}
<Divider />
<Button
variant="contained"
size="large"
onClick={publishToWix}
disabled={publishing || !connectionStatus?.connected}
sx={{ alignSelf: 'flex-start' }}
>
{publishing ? (
<>
<CircularProgress size={20} sx={{ mr: 1 }} />
Publishing to Wix...
</>
) : (
'Publish to Wix'
)}
</Button>
{!connectionStatus?.connected && (
<Alert severity="info">
Please connect your Wix account first to publish blog posts.
</Alert>
)}
</Box>
</CardContent>
</Card>
{/* Instructions */}
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
How to Use
</Typography>
<Typography variant="body2" component="div">
<ol>
<li>
<strong>Connect to Wix:</strong> Click "Connect to Wix" to authorize ALwrity to access your Wix account.
This will open a new window where you can log in to Wix and grant permissions.
</li>
<li>
<strong>Check Status:</strong> Once connected, you'll see a green success message indicating
your Wix account is connected and has the necessary permissions.
</li>
<li>
<strong>Load Categories & Tags:</strong> Click "Load Categories" and "Load Tags" to see
available options from your Wix blog.
</li>
<li>
<strong>Create Content:</strong> Enter a title and content for your blog post.
You can use Markdown formatting.
</li>
<li>
<strong>Publish:</strong> Click "Publish to Wix" to create and publish the blog post
directly to your Wix website.
</li>
</ol>
</Typography>
<Typography variant="body2" sx={{ mt: 2 }}>
<strong>Note:</strong> This is a test page for development purposes. In the main ALwrity application,
this functionality will be integrated into the blog writing workflow.
</Typography>
</CardContent>
</Card>
</Box>
);
};
export default WixTestPage;