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:
155
frontend/src/components/optimizer/OptimizationScores.tsx
Normal file
155
frontend/src/components/optimizer/OptimizationScores.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import React from 'react';
|
||||
import { TrendingUp, TrendingDown, Minus } from 'lucide-react';
|
||||
|
||||
interface ScoreData {
|
||||
seo_score: number;
|
||||
readability_score: number;
|
||||
engagement_score: number;
|
||||
overall_score: number;
|
||||
word_count?: number;
|
||||
has_meta_title?: boolean;
|
||||
has_meta_description?: boolean;
|
||||
has_primary_keyword?: boolean;
|
||||
internal_links_count?: number;
|
||||
}
|
||||
|
||||
interface OptimizationScoresProps {
|
||||
scores: ScoreData;
|
||||
before?: ScoreData;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const OptimizationScores: React.FC<OptimizationScoresProps> = ({
|
||||
scores,
|
||||
before,
|
||||
className = '',
|
||||
}) => {
|
||||
const getScoreColor = (score: number) => {
|
||||
if (score >= 80) return 'text-green-600 dark:text-green-400';
|
||||
if (score >= 60) return 'text-yellow-600 dark:text-yellow-400';
|
||||
return 'text-red-600 dark:text-red-400';
|
||||
};
|
||||
|
||||
const getScoreBgColor = (score: number) => {
|
||||
if (score >= 80) return 'bg-green-100 dark:bg-green-900';
|
||||
if (score >= 60) return 'bg-yellow-100 dark:bg-yellow-900';
|
||||
return 'bg-red-100 dark:bg-red-900';
|
||||
};
|
||||
|
||||
const getChangeIcon = (current: number, previous?: number) => {
|
||||
if (!previous) return null;
|
||||
const diff = current - previous;
|
||||
if (diff > 0) return <TrendingUp className="w-4 h-4 text-green-600" />;
|
||||
if (diff < 0) return <TrendingDown className="w-4 h-4 text-red-600" />;
|
||||
return <Minus className="w-4 h-4 text-gray-400" />;
|
||||
};
|
||||
|
||||
const getChangeText = (current: number, previous?: number) => {
|
||||
if (!previous) return null;
|
||||
const diff = current - previous;
|
||||
if (diff > 0) return `+${diff.toFixed(1)}`;
|
||||
if (diff < 0) return diff.toFixed(1);
|
||||
return '0.0';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`grid grid-cols-1 md:grid-cols-4 gap-4 ${className}`}>
|
||||
{/* Overall Score */}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">Overall</span>
|
||||
{before && getChangeIcon(scores.overall_score, before.overall_score)}
|
||||
</div>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<span className={`text-2xl font-bold ${getScoreColor(scores.overall_score)}`}>
|
||||
{scores.overall_score.toFixed(1)}
|
||||
</span>
|
||||
{before && (
|
||||
<span className="text-xs text-gray-500">
|
||||
{getChangeText(scores.overall_score, before.overall_score)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={`mt-2 h-2 rounded-full ${getScoreBgColor(scores.overall_score)}`}>
|
||||
<div
|
||||
className={`h-2 rounded-full ${getScoreColor(scores.overall_score).replace('text-', 'bg-')}`}
|
||||
style={{ width: `${scores.overall_score}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SEO Score */}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">SEO</span>
|
||||
{before && getChangeIcon(scores.seo_score, before.seo_score)}
|
||||
</div>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<span className={`text-2xl font-bold ${getScoreColor(scores.seo_score)}`}>
|
||||
{scores.seo_score.toFixed(1)}
|
||||
</span>
|
||||
{before && (
|
||||
<span className="text-xs text-gray-500">
|
||||
{getChangeText(scores.seo_score, before.seo_score)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={`mt-2 h-2 rounded-full ${getScoreBgColor(scores.seo_score)}`}>
|
||||
<div
|
||||
className={`h-2 rounded-full ${getScoreColor(scores.seo_score).replace('text-', 'bg-')}`}
|
||||
style={{ width: `${scores.seo_score}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Readability Score */}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">Readability</span>
|
||||
{before && getChangeIcon(scores.readability_score, before.readability_score)}
|
||||
</div>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<span className={`text-2xl font-bold ${getScoreColor(scores.readability_score)}`}>
|
||||
{scores.readability_score.toFixed(1)}
|
||||
</span>
|
||||
{before && (
|
||||
<span className="text-xs text-gray-500">
|
||||
{getChangeText(scores.readability_score, before.readability_score)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={`mt-2 h-2 rounded-full ${getScoreBgColor(scores.readability_score)}`}>
|
||||
<div
|
||||
className={`h-2 rounded-full ${getScoreColor(scores.readability_score).replace('text-', 'bg-')}`}
|
||||
style={{ width: `${scores.readability_score}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Engagement Score */}
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium text-gray-600 dark:text-gray-400">Engagement</span>
|
||||
{before && getChangeIcon(scores.engagement_score, before.engagement_score)}
|
||||
</div>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<span className={`text-2xl font-bold ${getScoreColor(scores.engagement_score)}`}>
|
||||
{scores.engagement_score.toFixed(1)}
|
||||
</span>
|
||||
{before && (
|
||||
<span className="text-xs text-gray-500">
|
||||
{getChangeText(scores.engagement_score, before.engagement_score)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={`mt-2 h-2 rounded-full ${getScoreBgColor(scores.engagement_score)}`}>
|
||||
<div
|
||||
className={`h-2 rounded-full ${getScoreColor(scores.engagement_score).replace('text-', 'bg-')}`}
|
||||
style={{ width: `${scores.engagement_score}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user