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:
alorig
2025-11-18 00:41:00 +05:00
parent 4b9e1a49a9
commit f7115190dc
60 changed files with 4932 additions and 80 deletions

View 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>
);
};

View File

@@ -0,0 +1,116 @@
import React from 'react';
import { OptimizationScores } from './OptimizationScores';
interface ScoreData {
seo_score: number;
readability_score: number;
engagement_score: number;
overall_score: number;
}
interface ScoreComparisonProps {
before: ScoreData;
after: ScoreData;
className?: string;
}
export const ScoreComparison: React.FC<ScoreComparisonProps> = ({
before,
after,
className = '',
}) => {
const calculateImprovement = (before: number, after: number) => {
const diff = after - before;
const percent = before > 0 ? ((diff / before) * 100).toFixed(1) : '0.0';
return { diff, percent };
};
const overallImprovement = calculateImprovement(before.overall_score, after.overall_score);
return (
<div className={`space-y-6 ${className}`}>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Score Comparison</h3>
<div className="flex items-center gap-2">
<span className="text-sm text-gray-600 dark:text-gray-400">Overall Improvement:</span>
<span
className={`text-lg font-bold ${
overallImprovement.diff > 0
? 'text-green-600 dark:text-green-400'
: overallImprovement.diff < 0
? 'text-red-600 dark:text-red-400'
: 'text-gray-600 dark:text-gray-400'
}`}
>
{overallImprovement.diff > 0 ? '+' : ''}
{overallImprovement.diff.toFixed(1)} ({overallImprovement.percent}%)
</span>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Before Scores */}
<div>
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">Before</h4>
<OptimizationScores scores={before} />
</div>
{/* After Scores */}
<div>
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">After</h4>
<OptimizationScores scores={after} before={before} />
</div>
</div>
</div>
{/* Detailed Breakdown */}
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-4">Detailed Breakdown</h4>
<div className="space-y-3">
{[
{ label: 'SEO Score', before: before.seo_score, after: after.seo_score },
{ label: 'Readability Score', before: before.readability_score, after: after.readability_score },
{ label: 'Engagement Score', before: before.engagement_score, after: after.engagement_score },
{ label: 'Overall Score', before: before.overall_score, after: after.overall_score },
].map(({ label, before: beforeScore, after: afterScore }) => {
const improvement = calculateImprovement(beforeScore, afterScore);
return (
<div key={label} className="flex items-center justify-between py-2 border-b border-gray-200 dark:border-gray-700">
<span className="text-sm text-gray-600 dark:text-gray-400">{label}</span>
<div className="flex items-center gap-4">
<span className="text-sm text-gray-500">{beforeScore.toFixed(1)}</span>
<span className="text-gray-400"></span>
<span
className={`text-sm font-medium ${
improvement.diff > 0
? 'text-green-600 dark:text-green-400'
: improvement.diff < 0
? 'text-red-600 dark:text-red-400'
: 'text-gray-600 dark:text-gray-400'
}`}
>
{afterScore.toFixed(1)}
</span>
<span
className={`text-xs ${
improvement.diff > 0
? 'text-green-600 dark:text-green-400'
: improvement.diff < 0
? 'text-red-600 dark:text-red-400'
: 'text-gray-500'
}`}
>
({improvement.diff > 0 ? '+' : ''}
{improvement.diff.toFixed(1)})
</span>
</div>
</div>
);
})}
</div>
</div>
</div>
);
};