Spaces:
Sleeping
Sleeping
File size: 8,741 Bytes
37f3c9b d049735 37f3c9b d049735 37f3c9b d049735 37f3c9b fa500c1 37f3c9b 5499b11 37f3c9b 5499b11 37f3c9b 5499b11 37f3c9b 21bddc4 37f3c9b d049735 37f3c9b 21bddc4 37f3c9b 21bddc4 b33f725 37f3c9b b33f725 37f3c9b b33f725 fa500c1 37f3c9b 21bddc4 37f3c9b b33f725 37f3c9b b33f725 37f3c9b 21bddc4 37f3c9b b33f725 37f3c9b b33f725 2ea6c41 37f3c9b b33f725 37f3c9b b33f725 21bddc4 37f3c9b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Podcast to Dark Romance Novel Converter</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 flex items-center justify-center min-h-screen">
<div class="bg-white p-6 rounded-lg shadow-lg w-full max-w-4xl">
<h1 class="text-2xl font-bold mb-4 text-center">Podcast to Dark Romance Novel Converter</h1>
<p class="text-center text-gray-600 mb-4">Generate explicit dark romance structures (open marriages, BDSM, swinging) from transcripts.</p>
<!-- Model Selection -->
<div class="mb-4">
<label for="model" class="block text-sm font-medium text-gray-700">Select AI Model (Explicit Content)</label>
<select id="model" class="mt-1 block w-full p-2 border border-gray-300 rounded-md" required>
<option value="nousresearch/hermes-3-llama-3.1-70b">Hermes 3 Llama 3.1 70B (Uncensored, Explicit)</option>
<option value="deepseek/deepseek-chat-v3-0324:free">DeepSeek Chat (Free)</option>
<option value="deepseek/deepseek-chat-v3-0324">DeepSeek Chat (Paid)</option>
<option value="deepseek/deepseek-r1-0528:free">DeepSeek R1 (Free)</option>
<option value="deepseek/deepseek-r1-0528">DeepSeek R1 (Paid)</option>
<option value="tngtech/deepseek-r1t2-chimera:free">DeepSeek TNG (Free)</option>
<option value="x-ai/grok-4">Grok 4</option>
<option value="x-ai/grok-4-fast">Grok 4</option>
<option value="x-ai/grok-3">Grok 3</option>
<option value="anthropic/claude-3.7-sonnet">Claude Sonnet 3.7</option>
<option value="anthropic/claude-3.5-sonnet">Claude 3.5 Sonnet</option>
<option value="openai/gpt-4o-mini">GPT-4o Mini</option>
<option value="meta-ai/llama-3.1-8b-instruct">LLaMA 3.1 8B Instruct</option>
</select>
</div>
<!-- Structure Config -->
<div class="mb-4 grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label for="chapter_count" class="block text-sm font-medium text-gray-700">Chapter Count</label>
<input type="number" id="chapter_count" value="10" min="1" max="50" class="mt-1 block w-full p-2 border border-gray-300 rounded-md">
</div>
<div>
<label for="word_count" class="block text-sm font-medium text-gray-700">Total Word Count</label>
<input type="number" id="word_count" value="90000" min="1000" max="500000" class="mt-1 block w-full p-2 border border-gray-300 rounded-md">
</div>
<div>
<label for="custom_prompt" class="block text-sm font-medium text-gray-700">Custom Prompt (Optional)</label>
<textarea id="custom_prompt" class="mt-1 block w-full p-2 border border-gray-300 rounded-md" rows="2" placeholder="e.g., Make the outline more gothic horror themed with vampire elements..."></textarea>
</div>
</div>
<!-- File Upload or Transcript Input -->
<div class="mb-4">
<label for="file" class="block text-sm font-medium text-gray-700">Upload Transcript File (TXT or PDF)</label>
<input type="file" id="file" accept=".txt,.pdf" class="mt-1 block w-full p-2 border border-gray-300 rounded-md">
<p class="text-sm text-gray-500 mt-1">Or paste below:</p>
<label for="transcript" class="block text-sm font-medium text-gray-700 mt-2">Podcast Transcript (Explicit Content OK)</label>
<textarea id="transcript" class="mt-1 block w-full p-2 border border-gray-300 rounded-md" rows="10" placeholder="Paste your podcast transcript here... (Themes: open marriages, BDSM, swinging)"></textarea>
<p id="transcript_count" class="text-sm text-gray-500 mt-1">Characters: 0</p>
</div>
<!-- Generate Button -->
<div class="mb-4">
<button id="generate_novel" class="w-full bg-purple-600 text-white p-2 rounded-md hover:bg-purple-700">Generate Novel Structure</button>
</div>
<!-- Outputs -->
<div id="outputs" class="space-y-4 hidden">
<div>
<label class="block text-sm font-medium text-gray-700">Outline</label>
<textarea id="outline" class="mt-1 block w-full p-2 border border-gray-300 rounded-md" rows="20" readonly placeholder="Streaming generation..."></textarea>
</div>
<button id="copy_all" class="mt-2 bg-green-600 text-white p-2 rounded-md hover:bg-green-700">Copy Outline for NovelCrafter</button>
</div>
</div>
<script>
const transcriptTextarea = document.getElementById("transcript");
const fileInput = document.getElementById("file");
const countDisplay = document.getElementById("transcript_count");
const generateButton = document.getElementById("generate_novel");
const outputsDiv = document.getElementById("outputs");
const outlineTextarea = document.getElementById("outline");
// Handle file upload: read and populate textarea
fileInput.addEventListener("change", (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
transcriptTextarea.value = e.target.result;
updateCount();
};
reader.onerror = () => {
alert("Error reading file. Please paste manually.");
};
if (file.type === 'application/pdf') {
alert("PDF files will be processed on the server. Paste or upload will work, but preview not available in textarea.");
transcriptTextarea.value = "[PDF content will be extracted on server]";
} else {
reader.readAsText(file);
}
updateCount();
}
});
function updateCount() {
const charCount = transcriptTextarea.value.length;
countDisplay.textContent = `Characters: ${charCount}`;
}
transcriptTextarea.addEventListener("input", updateCount);
generateButton.addEventListener("click", async () => {
const model = document.getElementById("model").value;
const transcript = transcriptTextarea.value.trim();
const file = fileInput.files[0];
const chapterCount = document.getElementById("chapter_count").value;
const wordCount = document.getElementById("word_count").value;
const customPrompt = document.getElementById("custom_prompt").value.trim();
if (!transcript && !file) {
alert("Please enter a transcript or select a file.");
return;
}
outlineTextarea.value = ""; // Clear previous
const statusDiv = document.createElement("div");
statusDiv.textContent = "Streaming: Summarizing transcript & generating outline...";
statusDiv.className = "text-center text-blue-600 mb-4";
outputsDiv.insertBefore(statusDiv, outputsDiv.firstChild);
outputsDiv.classList.remove("hidden");
try {
const formData = new FormData();
formData.append('model', model);
formData.append('chapter_count', chapterCount);
formData.append('word_count', wordCount);
if (customPrompt) {
formData.append('custom_prompt', customPrompt);
}
if (file) {
formData.append('file', file);
} else {
formData.append('transcript', transcript);
}
const response = await fetch("/generate_novel", {
method: "POST",
body: formData
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let done = false;
while (!done) {
const { value, done: readerDone } = await reader.read();
done = readerDone;
if (value) {
const chunk = decoder.decode(value, { stream: true });
outlineTextarea.value += chunk; // Append in real-time
outlineTextarea.scrollTop = outlineTextarea.scrollHeight; // Auto-scroll
}
}
statusDiv.remove();
if (outlineTextarea.value.includes("Error:")) {
statusDiv.textContent = outlineTextarea.value;
statusDiv.className = "text-center text-red-600 mb-4";
}
} catch (error) {
statusDiv.textContent = `Error: ${error.message}`;
statusDiv.className = "text-center text-red-600 mb-4";
}
});
document.getElementById("copy_all").addEventListener("click", () => {
const outline = document.getElementById("outline").value;
navigator.clipboard.writeText(outline)
.then(() => alert("Outline copied to clipboard! Paste into NovelCrafter for full manuscript."))
.catch(err => console.error('Failed to copy: ', err));
});
</script>
</body>
</html> |