This commit is contained in:
Flatlogic Bot 2025-11-18 03:39:51 +00:00
parent fad3944722
commit 76c712bb5c
4 changed files with 195 additions and 123 deletions

View File

@ -7,6 +7,30 @@
<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: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 %}
{% block structured_data %}
@ -47,102 +71,41 @@
<ul class="nav nav-tabs" id="myTab" role="tablist">
<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 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>
</ul>
<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">
<form method="post" action="{% url 'home' %}" class="mt-4">
<div class="tab-pane fade show active" id="website" role="tabpanel" aria-labelledby="website-tab">
<form method="post" action="{% url 'home' %}" class="mt-4 content-form">
{% csrf_token %}
<input type="hidden" name="form_type" value="website">
<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>
</div>
</form>
{% if results %}
<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 class="loader"></div>
<div class="results-section mt-4"></div>
</div>
<div class="tab-pane fade {% if form_type == 'youtube' %}show active{% endif %}" id="youtube" role="tabpanel" aria-labelledby="youtube-tab">
<form method="post" action="{% url 'home' %}" class="mt-4">
<div class="tab-pane fade" id="youtube" role="tabpanel" aria-labelledby="youtube-tab">
<form method="post" action="{% url 'home' %}" class="mt-4 content-form">
{% csrf_token %}
<input type="hidden" name="form_type" value="youtube">
<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>
</div>
</form>
{% if youtube_results %}
<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 class="loader"></div>
<div class="results-section mt-4"></div>
</div>
</div>
</div>
{% if error %}
<div class="alert alert-danger mt-4">{{ error }}</div>
{% endif %}
</div>
</div>
@ -237,8 +200,107 @@
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<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) {
var text = document.getElementById(elementId).innerText;
navigator.clipboard.writeText(text).then(function() {
@ -247,7 +309,7 @@ function copyToClipboard(elementId) {
var originalText = btn.innerText;
btn.innerText = 'Copied!';
setTimeout(function(){
btn.innerText = originalText;
btn.innerText = 'Copy';
}, 2000);
}, function(err) {
/* error */

View File

@ -1,3 +1,4 @@
from django.http import JsonResponse
from django.shortcuts import render
from ai.local_ai_api import LocalAIApi
import logging
@ -8,60 +9,58 @@ def home(request):
"""
Render the landing page and handle the content generation form.
"""
context = {}
if request.method == 'POST':
topic = request.POST.get('topic', '').strip()
form_type = request.POST.get('form_type', 'website') # Default to website
context['topic'] = topic
context['form_type'] = form_type
form_type = request.POST.get('form_type', 'website')
if not topic:
context['error'] = 'Please enter a topic.'
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)
return JsonResponse({'error': 'Please enter a topic.'}, status=400)
if response.get("success"):
payload = LocalAIApi.decode_json_from_response(response)
if payload:
context['results'] = payload
else:
context['error'] = 'Failed to generate content. The AI response was not valid JSON.'
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"):
payload = LocalAIApi.decode_json_from_response(response)
if payload:
payload['topic'] = topic
return JsonResponse(payload)
else:
logger.warning("AI error: %s", response.get("error"))
context['error'] = 'Failed to generate content due to an AI error.'
return JsonResponse({'error': 'Failed to generate content. The AI response was not valid JSON.'}, status=500)
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':
prompt = {
"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": "user", "content": f"The topic is: {topic}"},
],
"text": {"format": {"type": "json_object"}},
}
response = LocalAIApi.create_response(prompt)
elif form_type == 'youtube':
prompt = {
"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": "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:
context['youtube_results'] = payload
else:
context['error'] = 'Failed to generate content. The AI response was not valid JSON.'
if response.get("success"):
payload = LocalAIApi.decode_json_from_response(response)
if payload:
payload['topic'] = topic
return JsonResponse(payload)
else:
logger.warning("AI error: %s", response.get("error"))
context['error'] = 'Failed to generate content due to an AI error.'
return JsonResponse({'error': 'Failed to generate content. The AI response was not valid JSON.'}, status=500)
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:
logger.error("An unexpected error occurred: %s", e)
context['error'] = 'An unexpected error occurred.'
except Exception as e:
logger.error("An unexpected error occurred: %s", e)
return JsonResponse({'error': 'An unexpected error occurred.'}, status=500)
return render(request, "core/index.html", context)
return render(request, "core/index.html", {})

View File

@ -3,15 +3,26 @@ body {
background-color: #000000; /* Black */
color: #FFFFFF; /* White */
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 {
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 */
.hero-section, .results-section {
.hero-section, .results-section, .container {
position: relative;
z-index: 1;
}