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 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 */
|
||||
|
||||
@ -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", {})
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user