100 done
This commit is contained in:
parent
fad3944722
commit
76c712bb5c
Binary file not shown.
@ -7,6 +7,30 @@
|
|||||||
<meta name="description" content="Generate SEO-optimized titles, keywords, and YouTube tags using AI.">
|
<meta name="description" content="Generate SEO-optimized titles, keywords, and YouTube tags using AI.">
|
||||||
<meta property="og:title" content="AI-Powered Content Generator">
|
<meta property="og:title" content="AI-Powered Content Generator">
|
||||||
<meta property="og:description" content="Generate SEO-optimized titles, keywords, and YouTube tags using AI.">
|
<meta property="og:description" content="Generate SEO-optimized titles, keywords, and YouTube tags using AI.">
|
||||||
|
<style>
|
||||||
|
.loader {
|
||||||
|
display: none;
|
||||||
|
border: 4px solid #f3f3f3;
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top: 4px solid #3498db;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
-webkit-animation: spin 2s linear infinite; /* Safari */
|
||||||
|
animation: spin 2s linear infinite;
|
||||||
|
margin: 20px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Safari */
|
||||||
|
@-webkit-keyframes spin {
|
||||||
|
0% { -webkit-transform: rotate(0deg); }
|
||||||
|
100% { -webkit-transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block structured_data %}
|
{% block structured_data %}
|
||||||
@ -47,102 +71,41 @@
|
|||||||
|
|
||||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link {% if form_type != 'youtube' %}active{% endif %}" id="website-tab" data-bs-toggle="tab" data-bs-target="#website" type="button" role="tab" aria-controls="website" aria-selected="true">Website SEO</button>
|
<button class="nav-link active" id="website-tab" data-bs-toggle="tab" data-bs-target="#website" type="button" role="tab" aria-controls="website" aria-selected="true">Website SEO</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link {% if form_type == 'youtube' %}active{% endif %}" id="youtube-tab" data-bs-toggle="tab" data-bs-target="#youtube" type="button" role="tab" aria-controls="youtube" aria-selected="false">YouTube SEO</button>
|
<button class="nav-link" id="youtube-tab" data-bs-toggle="tab" data-bs-target="#youtube" type="button" role="tab" aria-controls="youtube" aria-selected="false">YouTube SEO</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="tab-content" id="myTabContent">
|
<div class="tab-content" id="myTabContent">
|
||||||
<div class="tab-pane fade {% if form_type != 'youtube' %}show active{% endif %}" id="website" role="tabpanel" aria-labelledby="website-tab">
|
<div class="tab-pane fade show active" id="website" role="tabpanel" aria-labelledby="website-tab">
|
||||||
<form method="post" action="{% url 'home' %}" class="mt-4">
|
<form method="post" action="{% url 'home' %}" class="mt-4 content-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="form_type" value="website">
|
<input type="hidden" name="form_type" value="website">
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<input type="text" class="form-control" name="topic" placeholder="e.g., 'The future of renewable energy'" value="{% if form_type == 'website' %}{{ topic|default:'' }}{% endif %}">
|
<input type="text" class="form-control" name="topic" placeholder="e.g., 'The future of renewable energy'">
|
||||||
<button class="btn btn-primary" type="submit">Generate</button>
|
<button class="btn btn-primary" type="submit">Generate</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div class="loader"></div>
|
||||||
{% if results %}
|
<div class="results-section mt-4"></div>
|
||||||
<div class="results-section">
|
|
||||||
<h2>Your Results for "{{ topic }}"</h2>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="result-item">
|
|
||||||
<h3>Suggested Title</h3>
|
|
||||||
<div class="input-group">
|
|
||||||
<p id="generated-title" class="form-control">{{ results.title }}</p>
|
|
||||||
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('generated-title')">Copy</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="result-item">
|
|
||||||
<h3>SEO Keywords</h3>
|
|
||||||
<div class="input-group">
|
|
||||||
<p id="generated-keywords" class="form-control">{{ results.keywords|join:", " }}</p>
|
|
||||||
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('generated-keywords')">Copy</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane fade {% if form_type == 'youtube' %}show active{% endif %}" id="youtube" role="tabpanel" aria-labelledby="youtube-tab">
|
<div class="tab-pane fade" id="youtube" role="tabpanel" aria-labelledby="youtube-tab">
|
||||||
<form method="post" action="{% url 'home' %}" class="mt-4">
|
<form method="post" action="{% url 'home' %}" class="mt-4 content-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="form_type" value="youtube">
|
<input type="hidden" name="form_type" value="youtube">
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<input type="text" class="form-control" name="topic" placeholder="e.g., 'Unboxing the new iPhone'" value="{% if form_type == 'youtube' %}{{ topic|default:'' }}{% endif %}">
|
<input type="text" class="form-control" name="topic" placeholder="e.g., 'Unboxing the new iPhone'">
|
||||||
<button class="btn btn-primary" type="submit">Generate</button>
|
<button class="btn btn-primary" type="submit">Generate</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div class="loader"></div>
|
||||||
{% if youtube_results %}
|
<div class="results-section mt-4"></div>
|
||||||
<div class="results-section">
|
|
||||||
<h2>Your YouTube Results for "{{ topic }}"</h2>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="result-item">
|
|
||||||
<h3>Suggested Title</h3>
|
|
||||||
<div class="input-group">
|
|
||||||
<p id="youtube-title" class="form-control">{{ youtube_results.title }}</p>
|
|
||||||
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('youtube-title')">Copy</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="result-item">
|
|
||||||
<h3>Keywords</h3>
|
|
||||||
<div class="input-group">
|
|
||||||
<p id="youtube-keywords" class="form-control">{{ youtube_results.keywords|join:", " }}</p>
|
|
||||||
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('youtube-keywords')">Copy</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="result-item">
|
|
||||||
<h3>Hashtags</h3>
|
|
||||||
<div class="input-group">
|
|
||||||
<p id="youtube-hashtags" class="form-control">{{ youtube_results.hashtags|join:" " }}</p>
|
|
||||||
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('youtube-hashtags')">Copy</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if error %}
|
|
||||||
<div class="alert alert-danger mt-4">{{ error }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -237,8 +200,107 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
<script>
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const forms = document.querySelectorAll('.content-form');
|
||||||
|
forms.forEach(form => {
|
||||||
|
form.addEventListener('submit', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const loader = this.nextElementSibling;
|
||||||
|
const resultsSection = loader.nextElementSibling;
|
||||||
|
const formData = new FormData(this);
|
||||||
|
const formType = formData.get('form_type');
|
||||||
|
|
||||||
|
loader.style.display = 'block';
|
||||||
|
resultsSection.innerHTML = '';
|
||||||
|
|
||||||
|
fetch(this.action, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': '{{ csrf_token }}'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
loader.style.display = 'none';
|
||||||
|
if (data.error) {
|
||||||
|
resultsSection.innerHTML = `<div class="alert alert-danger">${data.error}</div>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let resultsHtml = '';
|
||||||
|
if (formType === 'website' && data.title && data.keywords) {
|
||||||
|
resultsHtml = `
|
||||||
|
<h2>Your Results for "${data.topic}"</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="result-item">
|
||||||
|
<h3>Suggested Title</h3>
|
||||||
|
<div class="input-group">
|
||||||
|
<p id="generated-title" class="form-control">${data.title}</p>
|
||||||
|
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('generated-title')">Copy</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="result-item">
|
||||||
|
<h3>SEO Keywords</h3>
|
||||||
|
<div class="input-group">
|
||||||
|
<p id="generated-keywords" class="form-control">${data.keywords.join(', ')}</p>
|
||||||
|
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('generated-keywords')">Copy</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
} else if (formType === 'youtube' && data.title && data.keywords && data.hashtags) {
|
||||||
|
resultsHtml = `
|
||||||
|
<h2>Your YouTube Results for "${data.topic}"</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="result-item">
|
||||||
|
<h3>Suggested Title</h3>
|
||||||
|
<div class="input-group">
|
||||||
|
<p id="youtube-title" class="form-control">${data.title}</p>
|
||||||
|
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('youtube-title')">Copy</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="result-item">
|
||||||
|
<h3>Keywords</h3>
|
||||||
|
<div class="input-group">
|
||||||
|
<p id="youtube-keywords" class="form-control">${data.keywords.join(', ')}</p>
|
||||||
|
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('youtube-keywords')">Copy</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="result-item">
|
||||||
|
<h3>Hashtags</h3>
|
||||||
|
<div class="input-group">
|
||||||
|
<p id="youtube-hashtags" class="form-control">${data.hashtags.join(' ')}</p>
|
||||||
|
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('youtube-hashtags')">Copy</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
resultsSection.innerHTML = resultsHtml;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
loader.style.display = 'none';
|
||||||
|
resultsSection.innerHTML = `<div class="alert alert-danger">An error occurred: ${error}</div>`;
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function copyToClipboard(elementId) {
|
function copyToClipboard(elementId) {
|
||||||
var text = document.getElementById(elementId).innerText;
|
var text = document.getElementById(elementId).innerText;
|
||||||
navigator.clipboard.writeText(text).then(function() {
|
navigator.clipboard.writeText(text).then(function() {
|
||||||
@ -247,7 +309,7 @@ function copyToClipboard(elementId) {
|
|||||||
var originalText = btn.innerText;
|
var originalText = btn.innerText;
|
||||||
btn.innerText = 'Copied!';
|
btn.innerText = 'Copied!';
|
||||||
setTimeout(function(){
|
setTimeout(function(){
|
||||||
btn.innerText = originalText;
|
btn.innerText = 'Copy';
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
/* error */
|
/* error */
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
from django.http import JsonResponse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from ai.local_ai_api import LocalAIApi
|
from ai.local_ai_api import LocalAIApi
|
||||||
import logging
|
import logging
|
||||||
@ -8,60 +9,58 @@ def home(request):
|
|||||||
"""
|
"""
|
||||||
Render the landing page and handle the content generation form.
|
Render the landing page and handle the content generation form.
|
||||||
"""
|
"""
|
||||||
context = {}
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
topic = request.POST.get('topic', '').strip()
|
topic = request.POST.get('topic', '').strip()
|
||||||
form_type = request.POST.get('form_type', 'website') # Default to website
|
form_type = request.POST.get('form_type', 'website')
|
||||||
|
|
||||||
context['topic'] = topic
|
|
||||||
context['form_type'] = form_type
|
|
||||||
|
|
||||||
if not topic:
|
if not topic:
|
||||||
context['error'] = 'Please enter a topic.'
|
return JsonResponse({'error': 'Please enter a topic.'}, status=400)
|
||||||
else:
|
|
||||||
try:
|
|
||||||
if form_type == 'website':
|
|
||||||
prompt = {
|
|
||||||
"input": [
|
|
||||||
{"role": "system", "content": "You are an expert SEO content strategist. Generate a compelling blog post title and a list of relevant SEO keywords for a given topic. Return the result as a JSON object with two keys: 'title' and 'keywords'. The 'keywords' should be a list of strings."},
|
|
||||||
{"role": "user", "content": f"The topic is: {topic}"},
|
|
||||||
],
|
|
||||||
"text": {"format": {"type": "json_object"}},
|
|
||||||
}
|
|
||||||
response = LocalAIApi.create_response(prompt)
|
|
||||||
|
|
||||||
if response.get("success"):
|
try:
|
||||||
payload = LocalAIApi.decode_json_from_response(response)
|
if form_type == 'website':
|
||||||
if payload:
|
prompt = {
|
||||||
context['results'] = payload
|
"input": [
|
||||||
else:
|
{"role": "system", "content": "You are an expert SEO content strategist. Generate a compelling blog post title and a list of relevant SEO keywords for a given topic. Return the result as a JSON object with two keys: 'title' and 'keywords'. The 'keywords' should be a list of strings."},
|
||||||
context['error'] = 'Failed to generate content. The AI response was not valid JSON.'
|
{"role": "user", "content": f"The topic is: {topic}"},
|
||||||
|
],
|
||||||
|
"text": {"format": {"type": "json_object"}},
|
||||||
|
}
|
||||||
|
response = LocalAIApi.create_response(prompt)
|
||||||
|
|
||||||
|
if response.get("success"):
|
||||||
|
payload = LocalAIApi.decode_json_from_response(response)
|
||||||
|
if payload:
|
||||||
|
payload['topic'] = topic
|
||||||
|
return JsonResponse(payload)
|
||||||
else:
|
else:
|
||||||
logger.warning("AI error: %s", response.get("error"))
|
return JsonResponse({'error': 'Failed to generate content. The AI response was not valid JSON.'}, status=500)
|
||||||
context['error'] = 'Failed to generate content due to an AI error.'
|
else:
|
||||||
|
logger.warning("AI error: %s", response.get("error"))
|
||||||
|
return JsonResponse({'error': 'Failed to generate content due to an AI error.'}, status=500)
|
||||||
|
|
||||||
elif form_type == 'youtube':
|
elif form_type == 'youtube':
|
||||||
prompt = {
|
prompt = {
|
||||||
"input": [
|
"input": [
|
||||||
{"role": "system", "content": "You are a YouTube content expert. Generate a catchy title, a list of relevant keywords, and a list of hashtags for a given video topic. Return the result as a JSON object with three keys: 'title', 'keywords', and 'hashtags'. 'keywords' and 'hashtags' should be lists of strings."},
|
{"role": "system", "content": "You are a YouTube content expert. Generate a catchy title, a list of relevant keywords, and a list of hashtags for a given video topic. Return the result as a JSON object with three keys: 'title', 'keywords', and 'hashtags'. 'keywords' and 'hashtags' should be lists of strings."},
|
||||||
{"role": "user", "content": f"The topic is: {topic}"},
|
{"role": "user", "content": f"The topic is: {topic}"},
|
||||||
],
|
],
|
||||||
"text": {"format": {"type": "json_object"}},
|
"text": {"format": {"type": "json_object"}},
|
||||||
}
|
}
|
||||||
response = LocalAIApi.create_response(prompt)
|
response = LocalAIApi.create_response(prompt)
|
||||||
|
|
||||||
if response.get("success"):
|
if response.get("success"):
|
||||||
payload = LocalAIApi.decode_json_from_response(response)
|
payload = LocalAIApi.decode_json_from_response(response)
|
||||||
if payload:
|
if payload:
|
||||||
context['youtube_results'] = payload
|
payload['topic'] = topic
|
||||||
else:
|
return JsonResponse(payload)
|
||||||
context['error'] = 'Failed to generate content. The AI response was not valid JSON.'
|
|
||||||
else:
|
else:
|
||||||
logger.warning("AI error: %s", response.get("error"))
|
return JsonResponse({'error': 'Failed to generate content. The AI response was not valid JSON.'}, status=500)
|
||||||
context['error'] = 'Failed to generate content due to an AI error.'
|
else:
|
||||||
|
logger.warning("AI error: %s", response.get("error"))
|
||||||
|
return JsonResponse({'error': 'Failed to generate content due to an AI error.'}, status=500)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("An unexpected error occurred: %s", e)
|
logger.error("An unexpected error occurred: %s", e)
|
||||||
context['error'] = 'An unexpected error occurred.'
|
return JsonResponse({'error': 'An unexpected error occurred.'}, status=500)
|
||||||
|
|
||||||
return render(request, "core/index.html", context)
|
return render(request, "core/index.html", {})
|
||||||
|
|||||||
@ -3,15 +3,26 @@ body {
|
|||||||
background-color: #000000; /* Black */
|
background-color: #000000; /* Black */
|
||||||
color: #FFFFFF; /* White */
|
color: #FFFFFF; /* White */
|
||||||
font-family: 'Playfair Display', serif;
|
font-family: 'Playfair Display', serif;
|
||||||
|
background-image: url('../pasted-20251118-024426-e59dd4cc.jpg');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-attachment: fixed;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove background image overlay */
|
|
||||||
body::before {
|
body::before {
|
||||||
display: none;
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.7); /* Dark overlay */
|
||||||
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make sure content is on top of the overlay */
|
/* Make sure content is on top of the overlay */
|
||||||
.hero-section, .results-section {
|
.hero-section, .results-section, .container {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user