NotebookMg / templates /index.html
TheM1N9
Refactor authentication handling and enhance UI components. Removed authentication check from segment regeneration function. Updated login form to include tabbed interface for login and signup, improved styling with transitions and animations, and added new elements for better user experience.
6f51f68
<!DOCTYPE html>
<html>
<head>
<title>NotebookMg - PDF to Podcast Converter</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
/>
<link rel="stylesheet" href="/static/styles.css" />
</head>
<body>
{% if not is_authenticated %}
<div class="login-container">
<div class="login-card">
<div class="auth-tabs">
<button class="tab-btn active" onclick="switchTab('login')">
<i class="fas fa-sign-in-alt"></i> Login
</button>
<button class="tab-btn" onclick="switchTab('signup')">
<i class="fas fa-user-plus"></i> Sign Up
</button>
</div>
<div id="login-content" class="tab-content active">
<h2><i class="fas fa-lock"></i> Login</h2>
<form action="/login" method="POST" class="login-form">
<div class="input-group">
<label for="username"><i class="fas fa-user"></i> Username</label>
<input
type="text"
id="username"
name="username"
placeholder="Enter your username"
required
/>
</div>
<div class="input-group">
<label for="password"><i class="fas fa-key"></i> Password</label>
<input
type="password"
id="password"
name="password"
placeholder="Enter your password"
required
/>
</div>
<button type="submit">
<i class="fas fa-sign-in-alt"></i> Login
</button>
</form>
</div>
<div id="signup-content" class="tab-content">
<h2><i class="fas fa-user-plus"></i> Sign Up</h2>
<div class="signup-section">
<i class="fas fa-envelope-open-text fa-3x"></i>
<p>
Due to skyrocketing API costs, access to NotebookMg is only for
the chosen ones.
</p>
<p class="contact-text">
To see if you're worthy of entry, hit up
<span>Ronit</span>
or
<span>Manikanta K</span>
on Slack—they're the gatekeepers 🔮
</p>
</div>
</div>
</div>
</div>
<script>
function switchTab(tab) {
// Update tab buttons
document.querySelectorAll(".tab-btn").forEach((btn) => {
btn.classList.remove("active");
});
event.currentTarget.classList.add("active");
// Update content
document.querySelectorAll(".tab-content").forEach((content) => {
content.classList.remove("active");
});
document.getElementById(`${tab}-content`).classList.add("active");
}
</script>
{% else %}
<div class="hero">
<h1>NotebookMg</h1>
<p>Transform your PDFs into engaging podcasts with AI-powered voices</p>
</div>
<div class="container">
<div class="card">
<div class="features">
<div class="feature">
<i class="fas fa-file-pdf"></i>
<h3>PDF Processing</h3>
<p>Smart text extraction and cleaning</p>
</div>
<div class="feature">
<i class="fas fa-microphone-alt"></i>
<h3>Natural Voices</h3>
<p>Realistic AI-powered conversations</p>
</div>
<div class="feature">
<i class="fas fa-podcast"></i>
<h3>Podcast Generation</h3>
<p>Engaging audio content creation</p>
</div>
</div>
<form id="uploadForm">
<div class="upload-section" id="dropZone">
<i
class="fas fa-cloud-upload-alt fa-3x"
style="color: #4caf50; margin-bottom: 15px"
></i>
<h3>Upload your PDF</h3>
<p>Drag and drop your file here or click to browse</p>
<input
type="file"
id="pdfFile"
accept=".pdf"
required
style="display: none"
/>
<button
type="button"
onclick="document.getElementById('pdfFile').click()"
>
Choose File
</button>
<p id="selectedFile" style="margin-top: 10px; color: #888"></p>
</div>
<div class="voice-inputs">
<div class="input-group">
<label for="tharunVoiceId">Tharun Voice ID</label>
<input
type="text"
id="tharunVoiceId"
placeholder="Enter Tharun voice ID"
required
/>
</div>
<div class="input-group">
<label for="aksharaVoiceId">Akshara Voice ID</label>
<input
type="text"
id="aksharaVoiceId"
placeholder="Enter Akshara voice ID"
required
/>
</div>
</div>
<button type="submit">Generate Podcast</button>
</form>
<div
id="error"
class="error"
style="color: #ff4444; margin: 10px 0; display: none"
></div>
<div id="audio-result" class="audio-container">
<div class="audio-header">
<h3>Your Podcast</h3>
<audio id="podcast-player" controls>
Your browser does not support the audio element.
</audio>
</div>
</div>
<details
id="segments-container"
class="segments-container"
style="display: none"
>
<summary class="segments-summary">
<i class="fas fa-chevron-right"></i>
Individual Segments
<span class="segment-count"></span>
</summary>
<div id="segments-list"></div>
</details>
</div>
</div>
<script>
document.getElementById("uploadForm").onsubmit = async (e) => {
e.preventDefault();
// Get authentication status from URL
const urlParams = new URLSearchParams(window.location.search);
const isAuthenticated = urlParams.get("authenticated") === "true";
if (!isAuthenticated) {
alert("Please log in first");
return;
}
// Add authentication to FormData
const formData = new FormData();
formData.append("authenticated", "true");
formData.append("file", document.getElementById("pdfFile").files[0]);
formData.append(
"tharun_voice_id",
document.getElementById("tharunVoiceId").value
);
formData.append(
"akshara_voice_id",
document.getElementById("aksharaVoiceId").value
);
console.log("Form submission started");
const submitButton = e.target.querySelector("button[type='submit']");
const audioResult = document.getElementById("audio-result");
const segmentsContainer = document.getElementById("segments-container");
const error = document.getElementById("error");
const inputs = e.target.querySelectorAll("input");
const pdfFile = document.getElementById("pdfFile");
// Check if file is selected
if (!pdfFile || !pdfFile.files || pdfFile.files.length === 0) {
if (error) {
error.textContent = "Please select a PDF file";
error.style.display = "block";
}
return;
}
// Clear previous results if elements exist
if (audioResult) audioResult.style.display = "none";
if (segmentsContainer) segmentsContainer.style.display = "none";
if (document.getElementById("segments-list")) {
document.getElementById("segments-list").innerHTML = "";
}
if (document.getElementById("podcast-player")) {
document.getElementById("podcast-player").src = "";
}
if (error) error.style.display = "none";
// Update button state
if (submitButton) {
submitButton.disabled = true;
submitButton.innerHTML =
'<i class="fas fa-spinner fa-spin"></i> Generating Podcast... May take few minutes...';
}
// Disable inputs
inputs.forEach((input) => {
if (input) input.disabled = true;
});
console.log("Sending request to server...");
const response = await fetch("/upload-pdf/", {
method: "POST",
body: formData,
});
console.log("Server response received:", response.status);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log("Response data:", data);
if (audioResult && data.podcast_file) {
const audioPlayer = document.getElementById("podcast-player");
if (audioPlayer) {
audioPlayer.src = `/download/${data.podcast_file}`;
audioResult.style.display = "block";
}
const segmentsList = document.getElementById("segments-list");
if (segmentsList && data.segments) {
segmentsList.innerHTML = "";
const segmentCount = document.querySelector(".segment-count");
if (segmentCount) {
segmentCount.textContent = `(${data.segments.length} segments)`;
}
data.segments.forEach((segment, index) => {
const segmentDiv = document.createElement("div");
segmentDiv.className = "segment";
segmentDiv.id = `segment-${index}`;
segmentDiv.innerHTML = `
<div class="segment-info">
<div class="segment-header">
<div class="segment-speaker">${segment.speaker}</div>
<button class="edit-btn" onclick="makeEditable(${index})">
<i class="fas fa-edit"></i> Edit
</button>
</div>
<div class="segment-text">
<div class="segment-text-content">${segment.text}</div>
</div>
</div>
<div class="segment-controls">
<audio controls src="/download/${segment.file}"></audio>
<button class="regenerate-btn" onclick="regenerateSegment(${index})">
<i class="fas fa-redo"></i> Regenerate
</button>
</div>
`;
segmentsList.appendChild(segmentDiv);
});
if (segmentsContainer) {
segmentsContainer.style.display = "block";
}
}
}
};
async function regenerateSegment(index, newText = null) {
// Get authentication status from URL
const urlParams = new URLSearchParams(window.location.search);
const isAuthenticated = urlParams.get("authenticated") === "true";
if (!isAuthenticated) {
alert("Please log in first");
return;
}
const segment = document.querySelector(`#segment-${index}`);
const speaker = segment.querySelector(".segment-speaker").textContent;
const text =
newText || segment.querySelector(".segment-text-content").textContent;
const audio = segment.querySelector("audio");
const button = segment.querySelector(".regenerate-btn");
const mainPodcastPlayer = document.getElementById("podcast-player");
const currentMainTime = mainPodcastPlayer
? mainPodcastPlayer.currentTime
: 0;
// Disable the button and show loading state
button.disabled = true;
button.innerHTML =
'<i class="fas fa-spinner fa-spin"></i> Regenerating...';
try {
const formData = new FormData();
formData.append("authenticated", "true");
formData.append("speaker", speaker);
formData.append("text", text);
formData.append(
"tharun_voice_id",
document.getElementById("tharunVoiceId").value
);
formData.append(
"akshara_voice_id",
document.getElementById("aksharaVoiceId").value
);
const response = await fetch(`/regenerate-segment/${index}`, {
method: "POST",
body: formData,
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.success) {
// Update the segment audio
if (audio) {
const newSegmentSrc = `/download/${data.segment_file}`;
audio.src = newSegmentSrc;
await audio.load(); // Wait for the audio to load
}
// Update the main podcast player
if (mainPodcastPlayer && data.podcast_file) {
const newPodcastSrc = `/download/${data.podcast_file}`;
mainPodcastPlayer.src = newPodcastSrc;
await mainPodcastPlayer.load(); // Wait for the audio to load
// Try to restore the previous playback position
try {
mainPodcastPlayer.currentTime = currentMainTime;
} catch (e) {
console.warn("Couldn't restore playback position:", e);
}
}
// Show success state briefly
button.innerHTML = '<i class="fas fa-check"></i> Success!';
setTimeout(() => {
button.innerHTML = '<i class="fas fa-redo"></i> Regenerate';
button.disabled = false;
}, 2000);
} else {
throw new Error(data.detail || "Regeneration failed");
}
} catch (error) {
console.error("Error:", error);
button.innerHTML =
'<i class="fas fa-exclamation-triangle"></i> Failed';
setTimeout(() => {
button.innerHTML = '<i class="fas fa-redo"></i> Regenerate';
button.disabled = false;
}, 2000);
}
}
// Add drag and drop functionality
const dropZone = document.getElementById("dropZone");
const pdfFile = document.getElementById("pdfFile");
const selectedFile = document.getElementById("selectedFile");
["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
dropZone.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
["dragenter", "dragover"].forEach((eventName) => {
dropZone.addEventListener(eventName, highlight, false);
});
["dragleave", "drop"].forEach((eventName) => {
dropZone.addEventListener(eventName, unhighlight, false);
});
function highlight(e) {
dropZone.classList.add("highlight");
}
function unhighlight(e) {
dropZone.classList.remove("highlight");
}
dropZone.addEventListener("drop", handleDrop, false);
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
pdfFile.files = files;
updateFileName();
}
pdfFile.addEventListener("change", updateFileName);
function updateFileName() {
if (pdfFile.files.length > 0) {
selectedFile.textContent = `Selected file: ${pdfFile.files[0].name}`;
}
}
function makeEditable(index) {
const segment = document.querySelector(`#segment-${index}`);
const textDiv = segment.querySelector(".segment-text");
const originalText = textDiv.querySelector(
".segment-text-content"
).textContent;
const editButton = segment.querySelector(".edit-btn");
// Hide the edit button
editButton.style.display = "none";
// Add textarea and controls
textDiv.innerHTML = `
<textarea>${originalText}</textarea>
<div class="edit-controls" style="justify-content: flex-end;">
<button class="save-btn" onclick="saveEdit(${index})">
<i class="fas fa-save"></i> Save
</button>
<button class="cancel-btn" onclick="cancelEdit(${index}, '${originalText.replace(
/'/g,
"\\'"
)}')">
<i class="fas fa-times"></i> Cancel
</button>
</div>
`;
}
function cancelEdit(index, originalText) {
const segment = document.querySelector(`#segment-${index}`);
const textDiv = segment.querySelector(".segment-text");
const editButton = segment.querySelector(".edit-btn");
// Show the edit button again
editButton.style.display = "flex";
// Restore original content
textDiv.innerHTML = `
<div class="segment-text-content">${originalText}</div>
`;
}
async function saveEdit(index) {
const segment = document.querySelector(`#segment-${index}`);
const textarea = segment.querySelector("textarea");
const newText = textarea.value;
const editButton = segment.querySelector(".edit-btn");
// Show loading state in save button
const saveBtn = segment.querySelector(".save-btn");
saveBtn.disabled = true;
saveBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Saving...';
try {
await regenerateSegment(index, newText);
// Show the edit button again
editButton.style.display = "flex";
// Update the text display
const textDiv = segment.querySelector(".segment-text");
textDiv.innerHTML = `<div class="segment-text-content">${newText}</div>`;
} catch (error) {
console.error("Error saving edit:", error);
alert("Failed to save changes. Please try again.");
// Reset save button
saveBtn.disabled = false;
saveBtn.innerHTML = '<i class="fas fa-save"></i> Save';
}
}
</script>
{% endif %}
</body>
</html>