Add Linker and Optimizer modules with API integration and frontend components
- Added Linker and Optimizer apps to `INSTALLED_APPS` in `settings.py`. - Configured API endpoints for Linker and Optimizer in `urls.py`. - Implemented `OptimizeContentFunction` for content optimization in the AI module. - Created prompts for content optimization and site structure generation. - Updated `OptimizerService` to utilize the new AI function for content optimization. - Developed frontend components including dashboards and content lists for Linker and Optimizer. - Integrated new routes and sidebar navigation for Linker and Optimizer in the frontend. - Enhanced content management with source and sync status filters in the Writer module. - Comprehensive test coverage added for new features and components.
This commit is contained in:
148
frontend/src/pages/Optimizer/AnalysisPreview.tsx
Normal file
148
frontend/src/pages/Optimizer/AnalysisPreview.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router';
|
||||
import PageMeta from '../../components/common/PageMeta';
|
||||
import PageHeader from '../../components/common/PageHeader';
|
||||
import { optimizerApi } from '../../api/optimizer.api';
|
||||
import { fetchContent, Content as ContentType } from '../../services/api';
|
||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||
import { OptimizationScores } from '../../components/optimizer/OptimizationScores';
|
||||
import { Loader2, ArrowLeft } from 'lucide-react';
|
||||
|
||||
export default function AnalysisPreview() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const toast = useToast();
|
||||
|
||||
const [content, setContent] = useState<ContentType | null>(null);
|
||||
const [scores, setScores] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [analyzing, setAnalyzing] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
loadContent();
|
||||
analyzeContent();
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
const loadContent = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
// Note: fetchContent by ID would need to be implemented or use a different endpoint
|
||||
// For now, we'll fetch and filter
|
||||
const data = await fetchContent({ page_size: 1000 });
|
||||
const found = data.results?.find((c: ContentType) => c.id === parseInt(id || '0'));
|
||||
if (found) {
|
||||
setContent(found);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Error loading content:', error);
|
||||
toast.error(`Failed to load content: ${error.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const analyzeContent = async () => {
|
||||
if (!id) return;
|
||||
|
||||
try {
|
||||
setAnalyzing(true);
|
||||
const result = await optimizerApi.analyze(parseInt(id));
|
||||
setScores(result.scores);
|
||||
} catch (error: any) {
|
||||
console.error('Error analyzing content:', error);
|
||||
toast.error(`Failed to analyze content: ${error.message}`);
|
||||
} finally {
|
||||
setAnalyzing(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageMeta title="Content Analysis" description="Preview content optimization scores" />
|
||||
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
title="Content Analysis"
|
||||
description="Preview optimization scores without optimizing"
|
||||
actions={
|
||||
<button
|
||||
onClick={() => navigate(-1)}
|
||||
className="inline-flex items-center gap-2 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
Back
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
|
||||
{loading || analyzing ? (
|
||||
<div className="text-center py-12">
|
||||
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
|
||||
<p className="mt-2 text-gray-600 dark:text-gray-400">
|
||||
{loading ? 'Loading content...' : 'Analyzing content...'}
|
||||
</p>
|
||||
</div>
|
||||
) : content && scores ? (
|
||||
<div className="space-y-6">
|
||||
{/* Content Info */}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">
|
||||
{content.title || 'Untitled'}
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
Word Count: {content.word_count || 0} |
|
||||
Source: {content.source} |
|
||||
Status: {content.sync_status}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Scores */}
|
||||
<OptimizationScores scores={scores} />
|
||||
|
||||
{/* Score Details */}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">Score Details</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<span className="text-sm text-gray-600 dark:text-gray-400">Word Count:</span>
|
||||
<span className="ml-2 font-medium text-gray-900 dark:text-white">{scores.word_count || 0}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm text-gray-600 dark:text-gray-400">Has Meta Title:</span>
|
||||
<span className="ml-2 font-medium text-gray-900 dark:text-white">
|
||||
{scores.has_meta_title ? 'Yes' : 'No'}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm text-gray-600 dark:text-gray-400">Has Meta Description:</span>
|
||||
<span className="ml-2 font-medium text-gray-900 dark:text-white">
|
||||
{scores.has_meta_description ? 'Yes' : 'No'}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm text-gray-600 dark:text-gray-400">Has Primary Keyword:</span>
|
||||
<span className="ml-2 font-medium text-gray-900 dark:text-white">
|
||||
{scores.has_primary_keyword ? 'Yes' : 'No'}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm text-gray-600 dark:text-gray-400">Internal Links:</span>
|
||||
<span className="ml-2 font-medium text-gray-900 dark:text-white">
|
||||
{scores.internal_links_count || 0}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-gray-600 dark:text-gray-400">Content not found</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user