Implement Stage 3: Enhance content generation and metadata features
- Updated AI prompts to include metadata context, cluster roles, and product attributes for improved content generation. - Enhanced GenerateContentFunction to incorporate taxonomy and keyword objects for richer context. - Introduced new metadata fields in frontend components for better content organization and filtering. - Added cluster match, taxonomy match, and relevance score to LinkResults for improved link management. - Implemented metadata completeness scoring and recommended actions in AnalysisPreview for better content optimization. - Updated API services to support new metadata structures and site progress tracking.
This commit is contained in:
@@ -5,6 +5,9 @@ interface Link {
|
||||
anchor_text: string;
|
||||
target_content_id: number;
|
||||
target_url?: string;
|
||||
cluster_match?: boolean; // Stage 3: Cluster match flag
|
||||
taxonomy_match?: boolean; // Stage 3: Taxonomy match flag
|
||||
relevance_score?: number; // Stage 3: Relevance score
|
||||
}
|
||||
|
||||
interface LinkResultsProps {
|
||||
@@ -39,17 +42,70 @@ export const LinkResults: React.FC<LinkResultsProps> = ({
|
||||
|
||||
<div className="border-t border-gray-200 dark:border-gray-700 pt-4">
|
||||
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Added Links:</h4>
|
||||
<ul className="space-y-2">
|
||||
{links.map((link, index) => (
|
||||
<li key={index} className="flex items-center gap-2 text-sm">
|
||||
<span className="text-gray-600 dark:text-gray-400">"{link.anchor_text}"</span>
|
||||
<span className="text-gray-400">→</span>
|
||||
<span className="text-blue-600 dark:text-blue-400">
|
||||
Content #{link.target_content_id}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{/* Stage 3: Group links by cluster match */}
|
||||
{links.some(l => l.cluster_match) && (
|
||||
<div className="mb-4">
|
||||
<div className="text-xs font-semibold text-blue-600 dark:text-blue-400 mb-2">
|
||||
Cluster Matches (High Priority)
|
||||
</div>
|
||||
<ul className="space-y-2">
|
||||
{links.filter(l => l.cluster_match).map((link, index) => (
|
||||
<li key={`cluster-${index}`} className="flex items-center gap-2 text-sm pl-2 border-l-2 border-blue-500">
|
||||
<span className="text-gray-600 dark:text-gray-400">"{link.anchor_text}"</span>
|
||||
<span className="text-gray-400">→</span>
|
||||
<span className="text-blue-600 dark:text-blue-400">
|
||||
Content #{link.target_content_id}
|
||||
</span>
|
||||
{link.relevance_score && (
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400">
|
||||
(Score: {link.relevance_score})
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Other links */}
|
||||
{links.filter(l => !l.cluster_match || l.cluster_match === false).length > 0 && (
|
||||
<div>
|
||||
<div className="text-xs font-semibold text-gray-600 dark:text-gray-400 mb-2">
|
||||
Other Links
|
||||
</div>
|
||||
<ul className="space-y-2">
|
||||
{links.filter(l => !l.cluster_match || l.cluster_match === false).map((link, index) => (
|
||||
<li key={`other-${index}`} className="flex items-center gap-2 text-sm">
|
||||
<span className="text-gray-600 dark:text-gray-400">"{link.anchor_text}"</span>
|
||||
<span className="text-gray-400">→</span>
|
||||
<span className="text-blue-600 dark:text-blue-400">
|
||||
Content #{link.target_content_id}
|
||||
</span>
|
||||
{link.relevance_score && (
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400">
|
||||
(Score: {link.relevance_score})
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Fallback if no grouping */}
|
||||
{!links.some(l => l.cluster_match !== undefined) && (
|
||||
<ul className="space-y-2">
|
||||
{links.map((link, index) => (
|
||||
<li key={index} className="flex items-center gap-2 text-sm">
|
||||
<span className="text-gray-600 dark:text-gray-400">"{link.anchor_text}"</span>
|
||||
<span className="text-gray-400">→</span>
|
||||
<span className="text-blue-600 dark:text-blue-400">
|
||||
Content #{link.target_content_id}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
262
frontend/src/components/sites/SiteProgressWidget.tsx
Normal file
262
frontend/src/components/sites/SiteProgressWidget.tsx
Normal file
@@ -0,0 +1,262 @@
|
||||
/**
|
||||
* Site Progress Widget - Stage 3
|
||||
* Displays cluster-level completion bars for hub/supporting/attribute pages
|
||||
*/
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Card } from '../ui/card';
|
||||
import Badge from '../ui/badge/Badge';
|
||||
import { fetchSiteProgress, SiteProgress } from '../../services/api';
|
||||
import { CheckCircleIcon, XCircleIcon, AlertCircleIcon, ArrowRightIcon } from 'lucide-react';
|
||||
|
||||
interface SiteProgressWidgetProps {
|
||||
blueprintId: number;
|
||||
siteId?: number;
|
||||
}
|
||||
|
||||
export default function SiteProgressWidget({ blueprintId, siteId }: SiteProgressWidgetProps) {
|
||||
const navigate = useNavigate();
|
||||
const [progress, setProgress] = useState<SiteProgress | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
loadProgress();
|
||||
}, [blueprintId]);
|
||||
|
||||
const loadProgress = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await fetchSiteProgress(blueprintId);
|
||||
setProgress(data);
|
||||
} catch (error: any) {
|
||||
console.error('Failed to load site progress:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Card className="p-4">
|
||||
<div className="text-center py-4 text-gray-500 dark:text-gray-400">
|
||||
Loading progress...
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (!progress) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'complete':
|
||||
return 'success';
|
||||
case 'blocked':
|
||||
return 'error';
|
||||
default:
|
||||
return 'warning';
|
||||
}
|
||||
};
|
||||
|
||||
const getRoleColor = (role: string) => {
|
||||
switch (role) {
|
||||
case 'hub':
|
||||
return 'primary';
|
||||
case 'supporting':
|
||||
return 'success';
|
||||
case 'attribute':
|
||||
return 'warning';
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
Site Progress: {progress.blueprint_name}
|
||||
</h3>
|
||||
<Badge color={getStatusColor(progress.overall_status)} size="sm">
|
||||
{progress.overall_status ? progress.overall_status.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase()) : 'Unknown'}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Overall Stats */}
|
||||
<div className="grid grid-cols-3 gap-4 mb-6">
|
||||
<div className="text-center p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{progress.cluster_coverage.covered_clusters}/{progress.cluster_coverage.total_clusters}
|
||||
</div>
|
||||
<div className="text-xs text-gray-600 dark:text-gray-400 mt-1">Clusters Covered</div>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{progress.taxonomy_coverage.defined_taxonomies}/{progress.taxonomy_coverage.total_taxonomies}
|
||||
</div>
|
||||
<div className="text-xs text-gray-600 dark:text-gray-400 mt-1">Taxonomies Defined</div>
|
||||
</div>
|
||||
<div className="text-center p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
{progress.validation_flags.all_pages_generated ? '✓' : '✗'}
|
||||
</div>
|
||||
<div className="text-xs text-gray-600 dark:text-gray-400 mt-1">All Pages Generated</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Cluster Progress */}
|
||||
<div className="space-y-4">
|
||||
<h4 className="text-sm font-semibold text-gray-700 dark:text-gray-300">
|
||||
Cluster Coverage
|
||||
</h4>
|
||||
{progress.cluster_coverage.details.map((cluster) => {
|
||||
const totalPages = cluster.hub_pages + cluster.supporting_pages + cluster.attribute_pages;
|
||||
const completionPercent = totalPages > 0 ? Math.min(100, (cluster.content_count / totalPages) * 100) : 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={cluster.cluster_id}
|
||||
className={`p-4 rounded-lg border-2 ${
|
||||
cluster.is_complete
|
||||
? 'border-green-200 dark:border-green-800 bg-green-50 dark:bg-green-900/20'
|
||||
: 'border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h5 className="font-medium text-gray-900 dark:text-white">
|
||||
{cluster.cluster_name}
|
||||
</h5>
|
||||
<Badge color={getRoleColor(cluster.role)} size="sm" variant="light">
|
||||
{cluster.role}
|
||||
</Badge>
|
||||
{cluster.is_complete ? (
|
||||
<CheckCircleIcon className="w-4 h-4 text-green-600 dark:text-green-400" />
|
||||
) : (
|
||||
<AlertCircleIcon className="w-4 h-4 text-amber-600 dark:text-amber-400" />
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-gray-600 dark:text-gray-400">
|
||||
{cluster.content_count} content / {totalPages} pages
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => navigate(`/planner/clusters/${cluster.cluster_id}`)}
|
||||
className="text-xs text-blue-600 dark:text-blue-400 hover:underline flex items-center gap-1"
|
||||
>
|
||||
View <ArrowRightIcon className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Progress Bar */}
|
||||
<div className="mt-2">
|
||||
<div className="flex items-center justify-between text-xs text-gray-600 dark:text-gray-400 mb-1">
|
||||
<span>Completion</span>
|
||||
<span>{completionPercent.toFixed(0)}%</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full ${
|
||||
cluster.is_complete
|
||||
? 'bg-green-500 dark:bg-green-400'
|
||||
: 'bg-blue-500 dark:bg-blue-400'
|
||||
}`}
|
||||
style={{ width: `${completionPercent}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Page Type Breakdown */}
|
||||
<div className="grid grid-cols-3 gap-2 mt-3 text-xs">
|
||||
<div className="text-center p-2 bg-white dark:bg-gray-700 rounded">
|
||||
<div className="font-medium text-gray-900 dark:text-white">{cluster.hub_pages}</div>
|
||||
<div className="text-gray-600 dark:text-gray-400">Hub</div>
|
||||
</div>
|
||||
<div className="text-center p-2 bg-white dark:bg-gray-700 rounded">
|
||||
<div className="font-medium text-gray-900 dark:text-white">{cluster.supporting_pages}</div>
|
||||
<div className="text-gray-600 dark:text-gray-400">Supporting</div>
|
||||
</div>
|
||||
<div className="text-center p-2 bg-white dark:bg-gray-700 rounded">
|
||||
<div className="font-medium text-gray-900 dark:text-white">{cluster.attribute_pages}</div>
|
||||
<div className="text-gray-600 dark:text-gray-400">Attribute</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Validation Messages */}
|
||||
{cluster.validation_messages && cluster.validation_messages.length > 0 && (
|
||||
<div className="mt-3 pt-3 border-t border-gray-200 dark:border-gray-700">
|
||||
<div className="text-xs font-medium text-red-600 dark:text-red-400 mb-1">
|
||||
Issues:
|
||||
</div>
|
||||
<ul className="text-xs text-gray-600 dark:text-gray-400 space-y-1">
|
||||
{cluster.validation_messages.map((msg, idx) => (
|
||||
<li key={idx} className="flex items-start gap-1">
|
||||
<XCircleIcon className="w-3 h-3 text-red-500 mt-0.5 flex-shrink-0" />
|
||||
<span>{msg}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Validation Flags Summary */}
|
||||
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
|
||||
<h4 className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">
|
||||
Validation Status
|
||||
</h4>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
{progress.validation_flags.clusters_attached ? (
|
||||
<CheckCircleIcon className="w-4 h-4 text-green-600 dark:text-green-400" />
|
||||
) : (
|
||||
<XCircleIcon className="w-4 h-4 text-red-600 dark:text-red-400" />
|
||||
)}
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Clusters Attached</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{progress.validation_flags.taxonomies_defined ? (
|
||||
<CheckCircleIcon className="w-4 h-4 text-green-600 dark:text-green-400" />
|
||||
) : (
|
||||
<XCircleIcon className="w-4 h-4 text-red-600 dark:text-red-400" />
|
||||
)}
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Taxonomies Defined</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{progress.validation_flags.sitemap_generated ? (
|
||||
<CheckCircleIcon className="w-4 h-4 text-green-600 dark:text-green-400" />
|
||||
) : (
|
||||
<XCircleIcon className="w-4 h-4 text-red-600 dark:text-red-400" />
|
||||
)}
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Sitemap Generated</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{progress.validation_flags.all_pages_generated ? (
|
||||
<CheckCircleIcon className="w-4 h-4 text-green-600 dark:text-green-400" />
|
||||
) : (
|
||||
<XCircleIcon className="w-4 h-4 text-red-600 dark:text-red-400" />
|
||||
)}
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">All Pages Generated</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Deep Link to Blueprint */}
|
||||
<div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
|
||||
<button
|
||||
onClick={() => navigate(`/sites/builder/workflow/${blueprintId}`)}
|
||||
className="w-full px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg text-sm font-medium transition-colors"
|
||||
>
|
||||
Continue Site Builder Workflow →
|
||||
</button>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user