Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Blog Writer</title> | |
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | |
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script> | |
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css"> | |
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> | |
<style> | |
.container { | |
text-rendering: optimizeLegibility; | |
-webkit-font-smoothing: antialiased; | |
color: rgba(0,0,0,0.8); | |
position: relative; | |
min-height: 100vh; | |
font-family: source-serif-pro, Georgia, Cambria, "Times New Roman", Times, serif; | |
font-size: 20px; | |
line-height: 1.58; | |
color: rgba(0, 0, 0, 0.8); | |
background-color: #fff; | |
max-width: 800px; | |
margin: 0 auto; | |
padding: 0 20px; | |
} | |
/* Typography */ | |
h1, h2, h3, h4, h5, h6 { | |
font-family: sohne, "Helvetica Neue", Helvetica, Arial, sans-serif; | |
font-weight: 700; | |
color: rgba(0, 0, 0, 0.84); | |
letter-spacing: -0.022em; | |
line-height: 1.2; | |
} | |
h1 { | |
font-size: 40px; | |
margin-bottom: 0.5em; | |
} | |
h2 { | |
font-size: 32px; | |
margin-top: 1.5em; | |
margin-bottom: 0.5em; | |
} | |
h3 { | |
font-size: 26px; | |
margin-top: 1.5em; | |
margin-bottom: 0.5em; | |
} | |
p { | |
margin-bottom: 32px; | |
} | |
/* Links */ | |
a { | |
color: #1a8917; | |
text-decoration: none; | |
} | |
a:hover { | |
text-decoration: underline; | |
} | |
/* Input container */ | |
#input-container { | |
margin-bottom: 40px; | |
background-color: #f9f9f9; | |
padding: 32px; | |
border-radius: 5px; | |
} | |
textarea { | |
width: 100%; | |
padding: 12px; | |
margin-bottom: 20px; | |
border: 1px solid rgba(0, 0, 0, 0.15); | |
border-radius: 4px; | |
font-size: 18px; | |
resize: vertical; | |
box-sizing: border-box; | |
font-family: inherit; | |
} | |
button { | |
padding: 12px 24px; | |
background-color: #1a8917; | |
color: white; | |
border: none; | |
border-radius: 4px; | |
cursor: pointer; | |
font-size: 18px; | |
transition: background-color 0.3s; | |
} | |
button:hover { | |
background-color: #0f6b0f; | |
} | |
/* Output container */ | |
#output-container { | |
display: flex; | |
flex-direction: column; | |
gap: 40px; | |
} | |
#report-container, #sources-container { | |
background-color: #fff; | |
padding: 32px; | |
border-radius: 5px; | |
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1); | |
overflow-wrap: break-word; | |
word-wrap: break-word; | |
word-break: break-word; | |
} | |
/* Sources */ | |
.source-item { | |
margin-bottom: 32px; | |
padding: 24px; | |
background-color: #f9f9f9; | |
border: 1px solid #e0e0e0; | |
border-radius: 5px; | |
position: relative; | |
cursor: pointer; | |
transition: box-shadow 0.3s; | |
} | |
.source-item:hover { | |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | |
} | |
.source-url { | |
color: #1a8917; | |
text-decoration: none; | |
word-break: break-all; | |
font-weight: bold; | |
display: block; | |
margin-bottom: 16px; | |
font-size: 18px; | |
} | |
.source-content { | |
margin-top: 16px; | |
position: relative; | |
overflow: hidden; | |
} | |
.source-snippet { | |
max-height: 150px; | |
overflow: hidden; | |
} | |
.source-full { | |
display: none; | |
} | |
.expand-indicator { | |
position: absolute; | |
bottom: 0; | |
left: 0; | |
right: 0; | |
height: 40px; | |
background: linear-gradient(to bottom, rgba(249,249,249,0), rgba(249,249,249,1)); | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
} | |
.expand-indicator::after { | |
content: '▼'; | |
font-size: 14px; | |
color: #666; | |
} | |
.expanded .expand-indicator::after { | |
content: '▲'; | |
} | |
/* Responsive adjustments */ | |
@media (max-width: 768px) { | |
.container { | |
padding: 0 16px; | |
} | |
h1 { | |
font-size: 32px; | |
} | |
h2 { | |
font-size: 24px; | |
} | |
#input-container, #report-container, #sources-container { | |
padding: 24px; | |
} | |
} | |
</style> | |
<link rel="stylesheet" href="/css/ai-sidebar.css"> | |
</head> | |
<body> | |
<div id="app" class="ai-sidebar__container"> | |
<sidebar-component></sidebar-component> | |
<main class="ai-sidebar__content"> | |
<div class="container"> | |
<div id="input-container"> | |
<h1>Blog Post Generator</h1> | |
<textarea id="description" rows="4" placeholder="Enter description">write a medium article on nvidia stock performance</textarea> | |
<button onclick="generateReport()">Generate Blog</button> | |
</div> | |
<div id="output-container"> | |
<div id="report-container"></div> | |
<div id="sources-container"></div> | |
<div class="button-container"> | |
<button id="downloadBtn" onclick="downloadHTML()" style="display: none;" title="Download HTML Blog"> | |
<svg xmlns="http://www.w3.org/2000/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="M3 15v4c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2v-4M17 9l-5 5-5-5M12 12.8V2.5"/> | |
</svg> | |
</button> | |
</div> | |
</div> | |
</div> | |
</main> | |
</div> | |
<script> | |
async function generateReport() { | |
const description = document.getElementById('description').value; | |
const reportContainer = document.getElementById('report-container'); | |
const sourcesContainer = document.getElementById('sources-container'); | |
reportContainer.innerHTML = 'Searching results...'; | |
sourcesContainer.innerHTML = ''; | |
try { | |
const response = await fetch('https://iresearcher-api.elevatics.cloud/generate_blog', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
'Accept': 'text/plain' | |
}, | |
body: JSON.stringify({ | |
description: description, | |
user_id: "", | |
user_name: "multi-agent-research-generate-blog", | |
internet: true, | |
output_format: "report_table", | |
data_format: "Structured data", | |
generate_charts: true, | |
output_as_md: true | |
}) | |
}); | |
if (!response.ok) { | |
throw new Error(`HTTP error! status: ${response.status}`); | |
} | |
const reader = response.body.getReader(); | |
const decoder = new TextDecoder(); | |
let markdown = ''; | |
let metadata = ''; | |
let isReadingMetadata = false; | |
while (true) { | |
const { value, done } = await reader.read(); | |
if (done) break; | |
const chunk = decoder.decode(value, { stream: true }); | |
if (chunk.includes('<report-metadata>')) { | |
isReadingMetadata = true; | |
metadata = ''; | |
} | |
if (isReadingMetadata) { | |
metadata += chunk; | |
if (chunk.includes('</report-metadata>')) { | |
isReadingMetadata = false; | |
processMetadata(metadata); | |
} | |
} else { | |
markdown += chunk; | |
renderMarkdown(markdown); | |
} | |
} | |
document.getElementById('downloadBtn').style.display = 'block'; | |
} catch (error) { | |
reportContainer.innerHTML = `Error generating blog: ${error.message}`; | |
} | |
} | |
function renderMarkdown(markdown) { | |
const reportContainer = document.getElementById('report-container'); | |
const reportContent = markdown.match(/<report>([\s\S]*)<\/report>/); | |
if (reportContent) { | |
reportContainer.innerHTML = marked.parse(reportContent[1]); | |
} else { | |
reportContainer.innerHTML = marked.parse(markdown); | |
} | |
const scripts = reportContainer.getElementsByTagName('script'); | |
Array.from(scripts).forEach(script => { | |
const newScript = document.createElement('script'); | |
newScript.textContent = script.textContent; | |
script.parentNode.replaceChild(newScript, script); | |
}); | |
// Make Plotly charts responsive | |
const plots = reportContainer.querySelectorAll('.js-plotly-plot'); | |
plots.forEach(plot => { | |
Plotly.Plots.resize(plot); | |
}); | |
} | |
function processMetadata(metadata) { | |
const sourcesContainer = document.getElementById('sources-container'); | |
const metadataMatch = metadata.match(/all-text-with-urls: (.+)/); | |
if (metadataMatch) { | |
const metadataObj = JSON.parse(metadataMatch[1]); | |
sourcesContainer.innerHTML = '<h2>Sources</h2>'; | |
metadataObj.forEach(([content, url]) => { | |
if (content.trim() !== "") { | |
const sourceItem = document.createElement('div'); | |
sourceItem.className = 'source-item'; | |
const snippet = content.length > 400 ? content.substring(0, 400) + '...' : content; | |
sourceItem.innerHTML = ` | |
<a href="${url}" target="_blank" class="source-url">${url}</a> | |
<div class="source-content"> | |
<div class="source-snippet">${marked.parse(snippet)}</div> | |
<div class="source-full">${marked.parse(content)}</div> | |
<div class="expand-indicator"></div> | |
</div> | |
`; | |
sourcesContainer.appendChild(sourceItem); | |
const sourceUrl = sourceItem.querySelector('.source-url'); | |
const sourceContent = sourceItem.querySelector('.source-content'); | |
const snippetDiv = sourceItem.querySelector('.source-snippet'); | |
const fullDiv = sourceItem.querySelector('.source-full'); | |
sourceContent.addEventListener('click', function(e) { | |
if (!sourceItem.classList.contains('expanded')) { | |
sourceItem.classList.add('expanded'); | |
snippetDiv.style.display = 'none'; | |
fullDiv.style.display = 'block'; | |
} else if (e.clientY > sourceContent.getBoundingClientRect().bottom - 30) { | |
sourceItem.classList.remove('expanded'); | |
snippetDiv.style.display = 'block'; | |
fullDiv.style.display = 'none'; | |
} | |
}); | |
sourceUrl.addEventListener('click', function(e) { | |
e.stopPropagation(); | |
}); | |
} | |
}); | |
} else { | |
sourcesContainer.innerHTML = '<h2>Sources</h2><p>No source information available.</p>'; | |
} | |
} | |
// Make Plotly charts responsive on window resize | |
window.addEventListener('resize', function() { | |
const plots = document.querySelectorAll('.js-plotly-plot'); | |
plots.forEach(plot => { | |
Plotly.Plots.resize(plot); | |
}); | |
}); | |
function sanitizeFileName(name) { | |
// Keep only alphanumeric characters and spaces | |
name = name.replace(/[^a-z0-9\s]/gi, ''); | |
// Convert to lowercase | |
name = name.toLowerCase(); | |
// Replace spaces with underscores | |
name = name.replace(/\s+/g, '_'); | |
// Limit the length to 50 characters | |
name = name.substring(0, 50); | |
// If the name is empty after sanitization, use a default name | |
return name || 'generated_report'; | |
} | |
async function downloadHTML() { | |
try { | |
// Get styles from current document | |
let css = ''; | |
const styleTags = document.getElementsByTagName('style'); | |
for (let styleTag of styleTags) { | |
css += styleTag.innerHTML; | |
} | |
// Add additional styles for expanded sources and body max-width | |
css += ` | |
body.report-body { | |
max-width: 804px; | |
margin: 0 auto; | |
} | |
.source-item { | |
margin-bottom: 20px; | |
} | |
.source-content { | |
margin-top: 10px; | |
} | |
.source-snippet, .expand-indicator { | |
display: none; | |
} | |
.source-full { | |
display: block; | |
} | |
`; | |
// Clone the document | |
const htmlContent = document.implementation.createHTMLDocument('Report'); | |
// Set up the basic structure | |
htmlContent.documentElement.lang = 'en'; | |
const head = htmlContent.head; | |
const body = htmlContent.body; | |
body.className = 'report-body'; | |
// Add meta tags | |
const meta = htmlContent.createElement('meta'); | |
meta.charset = 'UTF-8'; | |
head.appendChild(meta); | |
const viewport = htmlContent.createElement('meta'); | |
viewport.name = 'viewport'; | |
viewport.content = 'width=device-width, initial-scale=1.0'; | |
head.appendChild(viewport); | |
// Copy the report content | |
const reportContainer = document.getElementById('report-container'); | |
body.innerHTML = reportContainer.innerHTML; | |
// Generate file name from content | |
let fileName = 'generatedreport'; | |
const headings = body.querySelector('h1, h2, h3'); | |
if (headings) { | |
fileName = sanitizeFileName(headings.textContent); | |
} | |
// Add title | |
const title = htmlContent.createElement('title'); | |
title.textContent = fileName; | |
head.appendChild(title); | |
// Add style | |
const style = htmlContent.createElement('style'); | |
style.textContent = css; | |
head.appendChild(style); | |
// Add necessary scripts | |
const markedScript = htmlContent.createElement('script'); | |
markedScript.src = 'https://cdn.jsdelivr.net/npm/marked/marked.min.js'; | |
head.appendChild(markedScript); | |
const plotlyScript = htmlContent.createElement('script'); | |
plotlyScript.src = 'https://cdn.plot.ly/plotly-latest.min.js'; | |
head.appendChild(plotlyScript); | |
// Copy and modify the sources content | |
const sourcesContainer = document.getElementById('sources-container'); | |
const sourcesDiv = htmlContent.createElement('div'); | |
sourcesDiv.innerHTML = sourcesContainer.innerHTML; | |
// Expand all sources | |
const sourceItems = sourcesDiv.querySelectorAll('.source-item'); | |
sourceItems.forEach(item => { | |
item.classList.add('expanded'); | |
const snippetDiv = item.querySelector('.source-snippet'); | |
const fullDiv = item.querySelector('.source-full'); | |
if (snippetDiv) snippetDiv.style.display = 'none'; | |
if (fullDiv) fullDiv.style.display = 'block'; | |
}); | |
body.appendChild(sourcesDiv); | |
// Create blob and download | |
const blob = new Blob([htmlContent.documentElement.outerHTML], { type: 'text/html' }); | |
const url = URL.createObjectURL(blob); | |
const a = document.createElement('a'); | |
a.href = url; | |
a.download = `${fileName}.html`; | |
document.body.appendChild(a); | |
a.click(); | |
document.body.removeChild(a); | |
URL.revokeObjectURL(url); | |
} catch (error) { | |
console.error('Error downloading HTML:', error); | |
} | |
} | |
</script> | |
</body> | |
</html> |