Instagram-Downloader / index.html
amalsp's picture
Update index.html to POST to Gradio backend API with progress/error handling
f323a9e verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Instagram Downloader</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; background: linear-gradient(135deg,#667eea 0%,#764ba2 100%); min-height: 100vh; display: flex; justify-content: center; align-items: center; padding: 20px; }
.container { background:#fff; border-radius:20px; box-shadow:0 20px 60px rgba(0,0,0,.3); padding:40px; max-width:560px; width:100%; animation: fadeIn .5s ease-in; }
@keyframes fadeIn { from {opacity:0; transform: translateY(20px);} to {opacity:1; transform: translateY(0);} }
.logo{ text-align:center; margin-bottom:30px; }
.logo svg{ width:60px; height:60px; margin-bottom:10px; }
h1{ color:#333; text-align:center; font-size:32px; margin-bottom:10px; font-weight:700; }
.gradient-text{ background:linear-gradient(135deg,#667eea 0%, #764ba2 100%); -webkit-background-clip:text; -webkit-text-fill-color:transparent; background-clip:text; }
.instructions{ text-align:center; color:#666; font-size:14px; margin-bottom:24px; line-height:1.6; }
.input-group{ margin-bottom:16px; }
label{ display:block; color:#333; font-weight:600; margin-bottom:10px; font-size:14px; }
.row { display:flex; gap:12px; align-items:center; }
input[type="text"]{ flex:1; padding:14px 44px 14px 14px; border:2px solid #e0e0e0; border-radius:10px; font-size:16px; transition: all .25s ease; outline:none; }
input[type="text"]:focus{ border-color:#667eea; box-shadow:0 0 0 4px rgba(102,126,234,.1); }
input[type="text"]::placeholder{ color:#aaa; }
.copy{ width:40px; height:40px; border:none; border-radius:10px; background:#f3f4f6; cursor:pointer; display:flex; align-items:center; justify-content:center; color:#555; }
.copy:hover{ background:#e5e7eb; }
.download-btn{ width:100%; padding:14px; background:linear-gradient(135deg,#667eea 0%, #764ba2 100%); color:#fff; border:none; border-radius:10px; font-size:18px; font-weight:600; cursor:pointer; transition: all .25s ease; box-shadow:0 4px 15px rgba(102,126,234,.4); display:inline-flex; align-items:center; justify-content:center; gap:10px; }
.download-btn:hover{ transform: translateY(-2px); box-shadow:0 6px 20px rgba(102,126,234,.6); }
.download-btn:disabled{ opacity:0.6; cursor:not-allowed; }
.status { margin-top:12px; min-height:22px; font-size:14px; }
.status .info{ color:#555; }
.status .error{ color:#b00020; }
.status .success{ color:#166534; }
.progress { display:none; margin-top:12px; height:8px; background:#eee; border-radius:999px; overflow:hidden; }
.progress > span { display:block; height:100%; width:0%; background:linear-gradient(90deg,#667eea,#764ba2); transition: width .2s ease; }
.preview { display:none; margin-top:16px; border:1px solid #eee; border-radius:10px; padding:10px; }
.features{ margin-top:26px; padding-top:20px; border-top:1px solid #e0e0e0; }
.feature-list{ list-style:none; columns:2; gap:16px; }
.feature-list li{ color:#666; font-size:14px; margin-bottom:10px; padding-left:25px; position:relative; }
.feature-list li:before{ content:"✓"; position:absolute; left:0; color:#667eea; font-weight:bold; font-size:18px; }
@media(max-width:600px){ .container{ padding:30px 20px; } h1{ font-size:28px; } .feature-list{ columns:1; } }
.note { background:#fff7ed; border:1px solid #fed7aa; color:#7c2d12; padding:10px; border-radius:8px; margin-top:12px; font-size:13px; }
</style>
</head>
<body>
<div class="container">
<div class="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="url(#gradient)">
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#667eea;stop-opacity:1"/>
<stop offset="100%" style="stop-color:#764ba2;stop-opacity:1"/>
</linearGradient>
</defs>
<path d="M34,4H14C8.5,4,4,8.5,4,14v20c0,5.5,4.5,10,10,10h20c5.5,0,10-4.5,10-10V14C44,8.5,39.5,4,34,4z M24,33c-5,0-9-4-9-9s4-9,9-9s9,4,9,9S29,33,24,33z M35,13c-1.1,0-2-0.9-2-2s0.9-2,2-2s2,0.9,2,2S36.1,13,35,13z"/>
<circle cx="24" cy="24" r="6"/>
</svg>
</div>
<h1><span class="gradient-text">Instagram Downloader</span></h1>
<p class="instructions">Paste any public Instagram image or video link below and click Download.</p>
<form id="downloadForm">
<div class="input-group">
<label for="linkInput">Instagram Link</label>
<div class="row">
<input type="text" id="linkInput" name="link" placeholder="https://www.instagram.com/p/..." required />
<button type="button" class="copy" aria-label="Paste from clipboard" title="Paste" id="pasteBtn">📋</button>
</div>
</div>
<button type="submit" class="download-btn" id="downloadBtn">⬇️ Download</button>
<div class="progress" id="progress"><span id="bar"></span></div>
<div class="status" id="status" aria-live="polite"></div>
<div class="note" id="warning" style="display:none"></div>
</form>
<div class="features">
<ul class="feature-list">
<li>Download Instagram photos in high quality</li>
<li>Download Instagram videos easily</li>
<li>Progress and error feedback</li>
<li>Mobile-friendly interface</li>
</ul>
</div>
</div>
<script>
const form = document.getElementById('downloadForm');
const input = document.getElementById('linkInput');
const statusEl = document.getElementById('status');
const progress = document.getElementById('progress');
const bar = document.getElementById('bar');
const downloadBtn = document.getElementById('downloadBtn');
const pasteBtn = document.getElementById('pasteBtn');
const warning = document.getElementById('warning');
// Backend API endpoint (Gradio)
const API_ENDPOINT = '/api/predict';
function setStatus(type, msg){
statusEl.innerHTML = msg ? `<span class="${type}">${msg}</span>` : '';
}
function showWarning(msg){
warning.style.display = 'block';
warning.innerHTML = msg;
}
function resetUI(){
setStatus('info','');
bar.style.width = '0%';
progress.style.display = 'none';
warning.style.display = 'none';
warning.innerHTML = '';
}
function startLoading(){
progress.style.display = 'block';
setStatus('info','Processing...');
bar.style.width = '20%';
}
function sanitizeUrl(u){
try{
const url = new URL(u.trim());
if(!url.hostname.replace(/^www\./,'').endsWith('instagram.com'))
throw new Error('Not an Instagram link');
return url.toString();
} catch{
throw new Error('Please enter a valid Instagram URL');
}
}
pasteBtn?.addEventListener('click', async () => {
try {
const text = await navigator.clipboard.readText();
if(text) input.value = text.trim();
} catch {
setStatus('error','Clipboard blocked by browser');
}
});
form.addEventListener('submit', async (e)=>{
e.preventDefault();
resetUI();
try {
const url = sanitizeUrl(input.value);
downloadBtn.disabled = true;
startLoading();
setStatus('info','Sending request to backend...');
bar.style.width = '30%';
// POST to Gradio API endpoint
const response = await fetch(API_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
data: [url]
})
});
if (!response.ok) {
throw new Error(`Server error: ${response.status}`);
}
bar.style.width = '60%';
setStatus('info','Receiving response...');
const result = await response.json();
bar.style.width = '80%';
setStatus('info','Processing download...');
// Gradio returns data in result.data array
// data[0] is the file path/blob, data[1] is the status message
if (result.data && result.data[0]) {
const fileData = result.data[0];
const statusMessage = result.data[1] || '';
// If we have a file URL or blob
if (fileData && fileData.name) {
// Create download link
const fileUrl = fileData.url || `/file=${fileData.path}`;
const fileName = fileData.orig_name || fileData.name || 'instagram-media';
// Fetch the file and trigger download
const fileResponse = await fetch(fileUrl);
const blob = await fileResponse.blob();
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = fileName;
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(() => URL.revokeObjectURL(a.href), 100);
bar.style.width = '100%';
setStatus('success', statusMessage || '✅ Download complete!');
} else {
throw new Error(statusMessage || 'No file returned from backend');
}
} else {
// Check if there's an error message
const errorMsg = result.data && result.data[1] ? result.data[1] : 'Failed to download media';
throw new Error(errorMsg);
}
} catch (err){
bar.style.width = '0%';
setStatus('error', err.message || 'Something went wrong');
showWarning(
`❌ Error: ${err.message}. Make sure the Instagram URL is valid and the post is public.`
);
} finally {
downloadBtn.disabled = false;
}
});
</script>
</body>
</html>