Spaces:
Runtime error
Runtime error
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Novel Prompt Generator</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-2xl"> | |
<h1 class="text-2xl font-bold mb-4 text-center">Novel Prompt Generator</h1> | |
<!-- Project ID --> | |
<div class="mb-4"> | |
<label for="project_id" class="block text-sm font-medium text-gray-700">Project ID</label> | |
<input id="project_id" class="mt-1 block w-full p-2 border border-gray-300 rounded-md" placeholder="e.g., my_novel_1" value="default" required> | |
</div> | |
<!-- OpenRouter Model Selection --> | |
<div class="mb-4"> | |
<label for="model" class="block text-sm font-medium text-gray-700">Select AI Model (Prompt Generation)</label> | |
<select id="model" class="mt-1 block w-full p-2 border border-gray-300 rounded-md" required> | |
<option value="deepseek/deepseek-chat-v3-0324:free">DeepSeek Chat (Free)</option> | |
<option value="deepseek/deepseek-r1-0528:free">DeepSeek R1 (Free)</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-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> | |
<!-- Processing Model Selection --> | |
<div class="mb-4"> | |
<label for="processing_model" class="block text-sm font-medium text-gray-700">Select Processing Model</label> | |
<select id="processing_model" class="mt-1 block w-full p-2 border border-gray-300 rounded-md" required> | |
<option value="facebook/bart-large-cnn">BART Large CNN (Summarization)</option> | |
<option value="distilbart-cnn-6-6">DistilBART CNN 6-6 (Summarization)</option> | |
<option value="sentence-transformers/all-MiniLM-L6-v2">All-MiniLM-L6-v2 (Excerpt)</option> | |
<option value="sentence-transformers/all-distilroberta-v1">All-DistilRoBERTa-v1 (Excerpt)</option> | |
</select> | |
</div> | |
<!-- Summary Length (Visible only for summarization models) --> | |
<div class="mb-4" id="summary_length_container"> | |
<label for="summary_length" class="block text-sm font-medium text-gray-700">Target Summary Length (words)</label> | |
<input id="summary_length" type="number" class="mt-1 block w-full p-2 border border-gray-300 rounded-md" value="1000" min="100" max="2000" required> | |
</div> | |
<!-- Input Fields --> | |
<div class="mb-4"> | |
<label for="manuscript" class="block text-sm font-medium text-gray-700">Manuscript Pages</label> | |
<textarea id="manuscript" class="mt-1 block w-full p-2 border border-gray-300 rounded-md" rows="5" placeholder="Paste your manuscript..."></textarea> | |
<p id="manuscript_count" class="text-sm text-gray-500 mt-1">Characters: 0</p> | |
<p id="manuscript_size_warning" class="text-sm text-red-500 mt-1 hidden">Warning: Manuscript exceeds 1,000,000 characters; it will be truncated.</p> | |
</div> | |
<div class="mb-4"> | |
<label for="outline" 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="3" placeholder="Enter your novel's outline..."></textarea> | |
</div> | |
<div class="mb-4"> | |
<label for="characters" class="block text-sm font-medium text-gray-700">Character Descriptions</label> | |
<textarea id="characters" class="mt-1 block w-full p-2 border border-gray-300 rounded-md" rows="3" placeholder="Describe your characters..."></textarea> | |
</div> | |
<!-- Buttons --> | |
<div class="flex space-x-2 mb-4"> | |
<button id="load_inputs" class="flex-1 bg-gray-600 text-white p-2 rounded-md hover:bg-gray-700">Load Latest Inputs</button> | |
<button id="generate_prompt" class="flex-1 bg-purple-600 text-white p-2 rounded-md hover:bg-purple-700">Generate Prompt</button> | |
</div> | |
<!-- Prompt Output Area with Copy Button --> | |
<div class="mt-4 mb-4 p-4 border border-gray-300 rounded-md bg-gray-50 h-32 overflow-y-auto relative"> | |
<div id="prompt_output" class="h-full overflow-y-auto"></div> | |
<button id="copy_prompt" class="absolute top-2 right-2 bg-green-600 text-white p-2 rounded-md hover:bg-green-700">Copy Prompt</button> | |
</div> | |
</div> | |
<script> | |
function checkPassword() { | |
return new Promise((resolve) => { | |
const check = () => { | |
const enteredPassword = prompt("Please enter the password:"); | |
if (!enteredPassword) { | |
check(); | |
} else { | |
fetch('/check_password', { | |
method: 'POST', | |
headers: { 'Content-Type': 'application/json' }, | |
body: JSON.stringify({ password: enteredPassword }) | |
}) | |
.then(response => response.json()) | |
.then(data => { | |
if (!data.success) { | |
alert("Incorrect password. Access denied."); | |
check(); | |
} else { | |
resolve(true); | |
} | |
}) | |
.catch(error => { | |
console.error('Error checking password:', error); | |
alert("An error occurred. Please try again."); | |
check(); | |
}); | |
} | |
}; | |
check(); | |
}); | |
} | |
const manuscriptTextarea = document.getElementById("manuscript"); | |
const countDisplay = document.getElementById("manuscript_count"); | |
const sizeWarningDisplay = document.getElementById("manuscript_size_warning"); | |
const processingModelSelect = document.getElementById("processing_model"); | |
const summaryLengthContainer = document.getElementById("summary_length_container"); | |
const summaryLengthInput = document.getElementById("summary_length"); | |
// Toggle summary length visibility based on processing model | |
function toggleSummaryLength() { | |
const selectedModel = processingModelSelect.value; | |
const isSummarizer = ['facebook/bart-large-cnn', 'distilbart-cnn-6-6'].includes(selectedModel); | |
summaryLengthContainer.classList.toggle("hidden", !isSummarizer); | |
summaryLengthInput.required = isSummarizer; | |
} | |
processingModelSelect.addEventListener("change", toggleSummaryLength); | |
toggleSummaryLength(); // Initialize visibility | |
manuscriptTextarea.addEventListener("input", () => { | |
const charCount = manuscriptTextarea.value.length; | |
countDisplay.textContent = `Characters: ${charCount}`; | |
sizeWarningDisplay.classList.toggle("hidden", charCount <= 1000000); | |
}); | |
document.getElementById("load_inputs").addEventListener("click", async () => { | |
await checkPassword(); | |
const projectId = document.getElementById("project_id").value || "default"; | |
try { | |
const response = await fetch(`/inputs/${projectId}`); | |
if (!response.ok) throw new Error("Failed to load inputs"); | |
const inputs = await response.json(); | |
manuscriptTextarea.value = inputs.manuscript || ""; | |
document.getElementById("outline").value = inputs.outline || ""; | |
document.getElementById("characters").value = inputs.characters || ""; | |
const charCount = manuscriptTextarea.value.length; | |
countDisplay.textContent = `Characters: ${charCount}`; | |
sizeWarningDisplay.classList.toggle("hidden", charCount <= 1000000); | |
} catch (error) { | |
document.getElementById("prompt_output").textContent = `Error: ${error.message}`; | |
} | |
}); | |
document.getElementById("generate_prompt").addEventListener("click", async () => { | |
await checkPassword(); | |
const projectId = document.getElementById("project_id").value || "default"; | |
const model = document.getElementById("model").value; | |
const processingModel = document.getElementById("processing_model").value; | |
const summaryLength = parseInt(document.getElementById("summary_length").value) || 1000; | |
if (!model) { | |
document.getElementById("prompt_output").textContent = "Error: Please select a prompt generation model."; | |
return; | |
} | |
if (!processingModel) { | |
document.getElementById("prompt_output").textContent = "Error: Please select a processing model."; | |
return; | |
} | |
if (['facebook/bart-large-cnn', 'distilbart-cnn-6-6'].includes(processingModel) && (!summaryLength || summaryLength < 100 || summaryLength > 2000)) { | |
document.getElementById("prompt_output").textContent = "Error: Summary length must be between 100 and 2000 words for summarization models."; | |
return; | |
} | |
const manuscript = manuscriptTextarea.value; | |
const outline = document.getElementById("outline").value; | |
const characters = document.getElementById("characters").value; | |
const promptOutputDiv = document.getElementById("prompt_output"); | |
promptOutputDiv.textContent = "Generating prompt..."; | |
try { | |
const response = await fetch("/generate_prompt", { | |
method: "POST", | |
headers: { "Content-Type": "application/json" }, | |
body: JSON.stringify({ | |
project_id: projectId, | |
model, | |
processing_model: processingModel, | |
summary_length: summaryLength, | |
manuscript, | |
outline, | |
characters | |
}) | |
}); | |
if (!response.ok) { | |
const errorData = await response.json(); | |
throw new Error(errorData.detail || `HTTP error! status: ${response.status}`); | |
} | |
const prompt = await response.text(); | |
promptOutputDiv.textContent = prompt; | |
} catch (error) { | |
promptOutputDiv.textContent = `Error: ${error.message}`; | |
} | |
}); | |
document.getElementById("copy_prompt").addEventListener("click", () => { | |
const promptOutputDiv = document.getElementById("prompt_output"); | |
navigator.clipboard.writeText(promptOutputDiv.textContent) | |
.then(() => alert("Prompt copied to clipboard!")) | |
.catch(err => console.error('Failed to copy prompt: ', err)); | |
}); | |
</script> | |
</body> | |
</html> |