VashuTheGreat2's picture
Upload folder using huggingface_hub
63de3ab verified
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Bloggig - AI Blog Writing Agent</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap"
rel="stylesheet"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css"
/>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<style>
:root {
--bg-dark: #0f1117;
--sidebar-bg: #161922;
--accent: #6366f1;
--accent-hover: #4f46e5;
--text-primary: #f8fafc;
--text-secondary: #94a3b8;
--glass-bg: rgba(255, 255, 255, 0.03);
--border: rgba(255, 255, 255, 0.1);
--card-bg: #1e293b;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Inter", sans-serif;
}
body {
background-color: var(--bg-dark);
color: var(--text-primary);
height: 100vh;
display: flex;
overflow: hidden;
}
/* Sidebar Styles */
aside {
width: 300px;
background-color: var(--sidebar-bg);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
padding: 1.5rem;
flex-shrink: 0;
transition: transform 0.3s ease;
}
.logo {
font-size: 1.5rem;
font-weight: 700;
margin-bottom: 2rem;
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--accent);
}
.new-chat-btn {
background: var(--accent);
color: white;
border: none;
padding: 0.8rem;
border-radius: 0.5rem;
cursor: pointer;
font-weight: 600;
margin-bottom: 1.5rem;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.new-chat-btn:hover {
background: var(--accent-hover);
transform: translateY(-1px);
}
.history-list {
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.history-item {
padding: 0.75rem;
border-radius: 0.4rem;
cursor: pointer;
transition: background 0.2s;
font-size: 0.9rem;
color: var(--text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
border: 1px solid transparent;
}
.history-item:hover {
background: var(--glass-bg);
color: var(--text-primary);
border-color: var(--border);
}
.history-item.active {
background: rgba(99, 102, 241, 0.15);
color: var(--accent);
border-color: rgba(99, 102, 241, 0.3);
}
/* Main Content Styles */
main {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
background: radial-gradient(
circle at bottom right,
rgba(99, 102, 241, 0.05),
transparent
);
}
header {
height: 60px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 2rem;
background: rgba(15, 17, 23, 0.8);
backdrop-filter: blur(8px);
z-index: 10;
}
.blog-actions {
display: flex;
gap: 1rem;
}
.btn-icon {
background: transparent;
border: 1px solid var(--border);
color: var(--text-secondary);
padding: 0.5rem;
border-radius: 0.4rem;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.4rem;
font-weight: 500;
transition: all 0.2s;
}
.btn-icon:hover:not(:disabled) {
background: var(--glass-bg);
color: var(--text-primary);
border-color: var(--text-secondary);
}
.btn-delete:hover:not(:disabled) {
background: rgba(239, 68, 68, 0.1);
color: #ef4444;
border-color: rgba(239, 68, 68, 0.3);
}
/* Chat/Content Area */
#content-area {
flex: 1;
overflow-y: auto;
padding: 2rem;
display: flex;
flex-direction: column;
align-items: center;
}
.welcome-screen {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
max-width: 600px;
}
.welcome-screen h1 {
font-size: 2.5rem;
margin-bottom: 1rem;
background: linear-gradient(to right, #818cf8, #6366f1);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.welcome-screen p {
color: var(--text-secondary);
font-size: 1.1rem;
line-height: 1.6;
}
/* Markdown Display */
.markdown-body {
width: 100%;
max-width: 800px;
color: var(--text-primary);
line-height: 1.7;
display: none;
}
.markdown-body h1,
.markdown-body h2,
.markdown-body h3 {
margin-top: 2rem;
margin-bottom: 1rem;
color: white;
}
.markdown-body p {
margin-bottom: 1rem;
}
.markdown-body img {
max-width: 100%;
border-radius: 0.8rem;
margin: 2rem 0;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
.markdown-body pre {
background: #1e1e1e;
padding: 1.5rem;
border-radius: 0.8rem;
overflow-x: auto;
margin-bottom: 1.5rem;
border: 1px solid var(--border);
}
/* Pipeline Progression (Console) */
#pipeline-status {
width: 100%;
max-width: 800px;
background: #000;
color: #10b981;
font-family: "Courier New", Courier, monospace;
padding: 1.5rem;
border-radius: 0.8rem;
margin-bottom: 2rem;
font-size: 0.9rem;
display: none;
border: 1px solid #10b98133;
max-height: 400px;
overflow-y: auto;
box-shadow: 0 0 20px rgba(16, 185, 129, 0.1);
}
.status-line {
margin-bottom: 0.5rem;
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Input Area at the bottom */
.input-container {
padding: 2rem;
width: 100%;
display: flex;
justify-content: center;
}
.input-wrapper {
max-width: 800px;
width: 100%;
position: relative;
background: var(--glass-bg);
border: 1px solid var(--border);
border-radius: 1rem;
padding: 0.5rem;
display: flex;
align-items: center;
transition: all 0.3s;
}
.input-wrapper:focus-within {
border-color: var(--accent);
background: rgba(255, 255, 255, 0.05);
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.1);
}
input {
flex: 1;
background: transparent;
border: none;
color: white;
padding: 0.75rem 1rem;
outline: none;
font-size: 1rem;
}
.send-btn {
background: var(--accent);
color: white;
border: none;
width: 40px;
height: 40px;
border-radius: 0.5rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.send-btn:hover {
background: var(--accent-hover);
}
.send-btn:disabled {
background: var(--text-secondary);
cursor: not-allowed;
opacity: 0.5;
}
/* Loading Spinner */
.spinner {
width: 20px;
height: 20px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<aside>
<div class="logo">
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="7 10 12 15 17 10" />
<line x1="12" y1="15" x2="12" y2="3" />
</svg>
Bloggig
</div>
<button class="new-chat-btn" onclick="startNewBlog()">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
New Blog
</button>
<div class="history-list" id="history-container">
<!-- Blogs will be loaded here -->
</div>
</aside>
<main>
<header>
<div id="blog-title-display" style="font-weight: 600">
Blog Overview
</div>
<div class="blog-actions">
<button
class="btn-icon"
id="download-btn"
onclick="downloadCurrentBlog()"
disabled
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="7 10 12 15 17 10" />
<line x1="12" y1="15" x2="12" y2="3" />
</svg>
Download
</button>
<button
class="btn-icon btn-delete"
id="delete-btn"
onclick="deleteCurrentBlog()"
disabled
>
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="3 6 5 6 21 6" />
<path
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
/>
<line x1="10" y1="11" x2="10" y2="17" />
<line x1="14" y1="11" x2="14" y2="17" />
</svg>
Delete
</button>
</div>
</header>
<div id="content-area">
<div class="welcome-screen" id="welcome-screen">
<h1>Craft something amazing.</h1>
<p>
Welcome to Bloggig. Enter a topic below to start generating a
high-quality, research-backed blog post with AI-generated visuals.
</p>
</div>
<div id="pipeline-status"></div>
<div class="markdown-body" id="blog-content"></div>
</div>
<div class="input-container">
<div class="input-wrapper">
<input
type="text"
id="topic-input"
placeholder="Enter blog topic..."
onkeypress="if (event.key === 'Enter') startGeneration();"
/>
<button
class="send-btn"
id="generate-btn"
onclick="startGeneration()"
>
<svg
id="send-icon"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="22" y1="2" x2="11" y2="13" />
<polyline points="22 2 15 22 11 13 2 9 22 2" />
</svg>
</button>
</div>
</div>
</main>
<script>
let currentBlogData = null;
let isGenerating = false;
// Initialize marked and highlight.js
marked.setOptions({
highlight: function (code, lang) {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(code, { language: lang }).value;
}
return hljs.highlightAuto(code).value;
},
breaks: true,
});
async function loadHistory() {
const res = await fetch("/blogs");
const blogs = await res.json();
const container = document.getElementById("history-container");
container.innerHTML = "";
blogs.forEach((title) => {
const div = document.createElement("div");
div.className = "history-item";
div.textContent = title;
div.onclick = () => loadBlog(title);
container.appendChild(div);
});
}
async function loadBlog(title) {
if (isGenerating) return;
const res = await fetch(`/blog/${encodeURIComponent(title)}`);
if (!res.ok) return;
const data = await res.json();
currentBlogData = {
topic: title,
plan: { blog_title: title },
final: data.content,
};
displayBlog(data.content);
document.getElementById("blog-title-display").textContent = title;
// Mark as active in sidebar
document.querySelectorAll(".history-item").forEach((item) => {
item.classList.toggle("active", item.textContent === title);
});
// Enable actions
document.getElementById("download-btn").disabled = false;
document.getElementById("delete-btn").disabled = false;
}
function displayBlog(markdown) {
const blogContent = document.getElementById("blog-content");
const welcome = document.getElementById("welcome-screen");
const pipeline = document.getElementById("pipeline-status");
welcome.style.display = "none";
pipeline.style.display = "none";
blogContent.style.display = "block";
// Rewrite image paths to use the /images static mount if they are internal
const processedMd = markdown.replace(/\(\.\.\/images\//g, "(/images/");
blogContent.innerHTML = marked.parse(processedMd);
hljs.highlightAll();
}
function startNewBlog() {
if (isGenerating) return;
currentBlogData = null;
document.getElementById("welcome-screen").style.display = "flex";
document.getElementById("blog-content").style.display = "none";
document.getElementById("pipeline-status").style.display = "none";
document.getElementById("blog-title-display").textContent =
"Blog Overview";
document.getElementById("topic-input").value = "";
document.getElementById("download-btn").disabled = true;
document.getElementById("delete-btn").disabled = true;
document.querySelectorAll(".history-item").forEach((item) => {
item.classList.remove("active");
});
}
function appendStatus(msg) {
const pipeline = document.getElementById("pipeline-status");
pipeline.style.display = "block";
const line = document.createElement("div");
line.className = "status-line";
line.innerHTML = `<span style="color: #6ee7b7;">[${new Date().toLocaleTimeString()}]</span> ${msg}`;
pipeline.appendChild(line);
pipeline.scrollTop = pipeline.scrollHeight;
}
function startGeneration() {
const topicInput = document.getElementById("topic-input");
const topic = topicInput.value.trim();
if (!topic || isGenerating) return;
isGenerating = true;
setLoading(true);
document.getElementById("welcome-screen").style.display = "none";
document.getElementById("blog-content").style.display = "none";
const pipeline = document.getElementById("pipeline-status");
pipeline.innerHTML = "";
pipeline.style.display = "block";
appendStatus(`Initializing generation for topic: "${topic}"...`);
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const ws = new WebSocket(
`${protocol}//${window.location.host}/ws/generate_blog`,
);
ws.onopen = () => {
ws.send(JSON.stringify({ topic }));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.status === "completed") {
appendStatus("Done! Finalizing blog post...");
} else if (data.final) {
// This is the final state
currentBlogData = data;
displayBlog(data.final);
loadHistory(); // Refresh history
setLoading(false);
isGenerating = false;
document.getElementById("download-btn").disabled = false;
document.getElementById("delete-btn").disabled = false;
if (data.plan && data.plan.blog_title) {
document.getElementById("blog-title-display").textContent =
data.plan.blog_title;
}
} else if (data.error) {
appendStatus(
`<span style="color: #f87171;">ERROR: ${data.error}</span>`,
);
setLoading(false);
isGenerating = false;
} else {
// Try to infer what happened from the state keys
if (data.sections && data.sections.length > 0) {
const lastSection = data.sections[data.sections.length - 1];
appendStatus(
`Generated section: ${lastSection[1].split("\n")[0].replace("## ", "")}`,
);
} else if (data.plan) {
appendStatus(
`Plan created: "${data.plan.blog_title}" with ${data.plan.tasks.length} sections.`,
);
} else if (data.queries && data.queries.length > 0) {
appendStatus(`Researching: ${data.queries.join(", ")}`);
}
}
};
ws.onerror = (err) => {
appendStatus(
`<span style="color: #f87171;">Connection error.</span>`,
);
setLoading(false);
isGenerating = false;
};
ws.onclose = () => {
if (isGenerating) {
appendStatus("Connection closed.");
setLoading(false);
isGenerating = false;
}
};
}
function setLoading(loading) {
const btn = document.getElementById("generate-btn");
const icon = document.getElementById("send-icon");
if (loading) {
btn.disabled = true;
btn.innerHTML = '<div class="spinner"></div>';
} else {
btn.disabled = false;
btn.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polyline points="22 2 15 22 11 13 2 9 22 2"/></svg>`;
}
}
async function deleteCurrentBlog() {
if (
!currentBlogData ||
!confirm(
"Are you sure you want to delete this blog and all related images?",
)
)
return;
const res = await fetch("/delete_blog", {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ data: currentBlogData }),
});
if (res.ok) {
startNewBlog();
loadHistory();
} else {
alert("Failed to delete blog.");
}
}
function downloadCurrentBlog() {
if (!currentBlogData) return;
const title = currentBlogData.plan
? currentBlogData.plan.blog_title
: currentBlogData.topic;
window.location.href = `/download_blog/${encodeURIComponent(title)}`;
}
// Initial history load
loadHistory();
</script>
</body>
</html>