piclets / prototype_index.html
Fraser's picture
swapped to Zephyr-7B!
f78c7af
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>FLUX-1 Schnell Demo (HF OAuth)</title>
<style>
:root {
font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
}
body {
margin: 0;
padding: 2rem;
background: #f5f5f5;
}
.card {
max-width: 900px;
margin: 0 auto;
background: #ffffff;
padding: 2rem;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
h1 {
margin-top: 0;
}
#signin {
background: #ff7b7b;
color: #fff;
border: none;
padding: 0.6rem 1.2rem;
border-radius: 6px;
cursor: pointer;
font-size: 1rem;
display: none; /* shown once JS decides it's needed */
}
#banner {
display: none;
margin-top: 1rem;
padding: 0.8rem 1rem;
border-radius: 6px;
background: #fff3cd;
color: #856404;
border: 1px solid #ffeeba;
}
/* Generator UI */
form {
margin-top: 2rem;
}
label {
font-weight: 600;
margin-bottom: 0.25rem;
display: block;
}
input[type="text"], input[type="number"] {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
margin-bottom: 1rem;
}
button.generate {
background: #007bff;
color: #fff;
border: none;
padding: 0.6rem 1.4rem;
border-radius: 6px;
cursor: pointer;
font-size: 1rem;
}
button.generate:disabled {
background: #9ac7ff;
cursor: not-allowed;
}
img {
max-width: 100%;
border-radius: 8px;
margin-top: 1rem;
}
</style>
</head>
<body>
<div class="card">
<h1>FLUX-1 Schnell + Joy Caption + Zephyr-7B</h1>
<p>
This static Space demonstrates how to call a remote Gradio Space while
letting <strong>each visitor&rsquo;s own Hugging Face subscription</strong> cover
the compute costs. Generate images with FLUX-1, caption them with Joy Caption, and generate text with Zephyr-7B.
</p>
<!-- 1️⃣ Sign-in button (shown only when needed) -->
<button id="signin">Sign in with Hugging Face</button>
<div id="banner"></div>
<!-- 2️⃣ Image-generation form (hidden until the app is ready) -->
<form id="generateForm" style="display:none;">
<label for="prompt">Prompt</label>
<input type="text" id="prompt" placeholder="Enter your prompt" required />
<label for="seed">Seed</label>
<input type="number" id="seed" value="0" min="0" max="2147483647" />
<label>
<input type="checkbox" id="randomize_seed" checked /> Randomize seed
</label>
<div style="display:flex; gap:1rem;">
<div style="flex:1;">
<label for="width">Width</label>
<input type="number" id="width" value="1024" min="256" max="2048" step="32" />
</div>
<div style="flex:1;">
<label for="height">Height</label>
<input type="number" id="height" value="1024" min="256" max="2048" step="32" />
</div>
</div>
<label for="steps">Number of inference steps</label>
<input type="number" id="steps" value="4" min="1" max="50" />
<button type="submit" class="generate">Generate Image</button>
</form>
<!-- 3️⃣ Caption form -->
<form id="captionForm" style="display:none; margin-top:2rem; border-top:1px solid #eee; padding-top:2rem;">
<h3>Caption Image</h3>
<label for="imageInput">Upload Image or Generate Above</label>
<input type="file" id="imageInput" accept="image/*" />
<label for="captionType">Caption Type</label>
<select id="captionType">
<option value="Descriptive">Descriptive</option>
<option value="Descriptive (Informal)">Descriptive (Informal)</option>
<option value="Training Prompt">Training Prompt</option>
<option value="MidJourney">MidJourney</option>
<option value="Booru tag list">Booru tag list</option>
<option value="Booru-like tag list">Booru-like tag list</option>
<option value="Art Critic">Art Critic</option>
<option value="Product Listing">Product Listing</option>
<option value="Social Media Post">Social Media Post</option>
</select>
<label for="captionLength">Caption Length</label>
<select id="captionLength">
<option value="any">Any</option>
<option value="very short">Very Short</option>
<option value="short">Short</option>
<option value="medium-length">Medium</option>
<option value="long" selected>Long</option>
<option value="very long">Very Long</option>
</select>
<button type="submit" class="generate">Generate Caption</button>
</form>
<!-- 4️⃣ Result container -->
<div id="result"></div>
<!-- 5️⃣ Caption result -->
<div id="captionResult" style="margin-top:1rem;"></div>
<!-- 6️⃣ Text generation form -->
<form id="textForm" style="display:none; margin-top:2rem; border-top:1px solid #eee; padding-top:2rem;">
<h3>Generate Text</h3>
<label for="textPrompt">Prompt</label>
<textarea id="textPrompt" rows="4" placeholder="Enter your prompt here..." style="width:100%; padding:0.5rem; border:1px solid #ccc; border-radius:4px; box-sizing:border-box; margin-bottom:1rem;"></textarea>
<label for="systemPrompt">System Prompt</label>
<textarea id="systemPrompt" rows="3" placeholder="You are a helpful assistant..." style="width:100%; padding:0.5rem; border:1px solid #ccc; border-radius:4px; box-sizing:border-box; margin-bottom:1rem;"></textarea>
<div style="display:flex; gap:1rem;">
<div style="flex:1;">
<label for="maxNewTokens">Max New Tokens</label>
<input type="number" id="maxNewTokens" value="1024" min="1" max="2048" step="1" />
</div>
<div style="flex:1;">
<label for="temperature">Temperature</label>
<input type="number" id="temperature" value="0.7" min="0.1" max="4.0" step="0.1" />
</div>
</div>
<div style="display:flex; gap:1rem;">
<div style="flex:1;">
<label for="topP">Top P</label>
<input type="number" id="topP" value="0.95" min="0.05" max="1.0" step="0.05" />
</div>
<div style="flex:1;">
<label for="topK">Top K</label>
<input type="number" id="topK" value="50" min="1" max="1000" step="1" />
</div>
</div>
<label for="repetitionPenalty">Repetition Penalty</label>
<input type="number" id="repetitionPenalty" value="1.0" min="1.0" max="2.0" step="0.05" />
<button type="submit" class="generate">Generate Text</button>
</form>
<!-- 7️⃣ Text generation result -->
<div id="textResult" style="margin-top:1rem;"></div>
<hr />
<p>
Source &amp; docs:
<a href="https://huggingface.co/docs/hub/spaces-oauth" target="_blank">Spaces OAuth</a>,
<a href="https://github.com/huggingface/huggingface.js" target="_blank">huggingface.js</a>,
<a href="https://js.gradio.app" target="_blank">@gradio/client</a>
</p>
</div>
<!-- 4️⃣ App logic -->
<script type="module">
import {
oauthLoginUrl,
oauthHandleRedirectIfPresent
} from "https://cdn.jsdelivr.net/npm/@huggingface/hub@0.21/+esm";
import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
// DOM helpers
const signinBtn = document.getElementById("signin");
const banner = document.getElementById("banner");
const form = document.getElementById("generateForm");
const resultDiv = document.getElementById("result");
const captionForm = document.getElementById("captionForm");
const captionResultDiv = document.getElementById("captionResult");
const textForm = document.getElementById("textForm");
const textResultDiv = document.getElementById("textResult");
const BANNER_ANON =
"⚠️ You are not logged in – anonymous users get only ~60 seconds of zero-GPU time per day. Sign in for higher limits.";
// Try to finish OAuth redirect (if we just came back from HF SSO)
let session = await oauthHandleRedirectIfPresent();
if (!session) {
// Not signed in: show banner & sign-in button
banner.textContent = BANNER_ANON;
banner.style.display = "block";
signinBtn.style.display = "inline-block";
signinBtn.onclick = async () => {
const url = await oauthLoginUrl({ scopes: ["inference-api"] });
window.location.href = url; // full-page redirect
};
// Proceed anonymously (limited quota)
startApp(null);
} else {
banner.style.display = "none";
startApp(session.accessToken, session.userInfo);
}
/**
* Main app logic: connect to remote Space & wire up the form.
* @param {string|null} hfToken
* @param {object} [userInfo]
*/
async function startApp(hfToken, userInfo = null) {
try {
banner.textContent = "Connecting to FLUX.1-schnell, Joy Caption, and Zephyr-7B…";
banner.style.display = "block";
const opts = hfToken ? { hf_token: hfToken } : {};
const flux = await Client.connect(
"black-forest-labs/FLUX.1-schnell",
opts
);
const joyCaption = await Client.connect(
"fancyfeast/joy-caption-alpha-two",
opts
);
// Connect to Zephyr-7B on Hugging Face Spaces
const zephyr = await Client.connect(
"hysts/zephyr-7b",
opts
);
banner.style.display = "none";
form.style.display = "block";
captionForm.style.display = "block";
textForm.style.display = "block";
if (userInfo) {
const greeting = document.createElement("p");
greeting.textContent = `Hello, ${userInfo.name || userInfo.preferred_username}!`;
form.parentNode.insertBefore(greeting, form);
}
form.addEventListener("submit", async (e) => {
e.preventDefault();
const prompt = document.getElementById("prompt").value;
const seed = parseInt(document.getElementById("seed").value, 10);
const randomize = document.getElementById("randomize_seed").checked;
const width = parseInt(document.getElementById("width").value, 10);
const height = parseInt(document.getElementById("height").value, 10);
const steps = parseInt(document.getElementById("steps").value, 10);
const btn = form.querySelector("button.generate");
btn.disabled = true;
btn.textContent = "Generating…";
resultDiv.innerHTML = "Generating image…";
try {
const output = await flux.predict("/infer", [
prompt,
seed,
randomize,
width,
height,
steps
]);
const [image, usedSeed] = output.data;
let url;
if (typeof image === "string") url = image;
else if (image && image.url) url = image.url;
else if (image && image.path) url = image.path;
if (url) {
resultDiv.innerHTML = `
<img src="${url}" alt="Generated" />
<p><strong>Seed</strong>: ${usedSeed}</p>
<p><strong>Prompt</strong>: ${prompt}</p>
`;
// Store image for captioning (fetch the image as blob)
try {
const response = await fetch(url);
currentImage = await response.blob();
} catch (imgErr) {
console.warn("Could not fetch generated image for captioning:", imgErr);
}
} else {
resultDiv.textContent = "Unexpected image format – open console.";
}
} catch (err) {
console.error(err);
resultDiv.textContent = `Generation failed: ${err}`;
} finally {
btn.disabled = false;
btn.textContent = "Generate Image";
}
});
// Caption form event listener
let currentImage = null;
captionForm.addEventListener("submit", async (e) => {
e.preventDefault();
const imageInput = document.getElementById("imageInput");
const captionType = document.getElementById("captionType").value;
const captionLength = document.getElementById("captionLength").value;
let imageToCaption = currentImage;
// If user uploaded an image, use that
if (imageInput.files && imageInput.files[0]) {
imageToCaption = imageInput.files[0];
}
if (!imageToCaption) {
captionResultDiv.innerHTML = "Please upload an image or generate one above.";
return;
}
const btn = captionForm.querySelector("button");
btn.disabled = true;
btn.textContent = "Generating Caption…";
captionResultDiv.innerHTML = "Generating caption…";
try {
const output = await joyCaption.predict("/stream_chat", [
imageToCaption,
captionType,
captionLength,
[], // extra_options
"", // name_input
"" // custom_prompt
]);
const [prompt, caption] = output.data;
captionResultDiv.innerHTML = `
<div style="background:#f8f9fa; padding:1rem; border-radius:6px; margin-top:1rem;">
<h4>Generated Caption</h4>
<p><strong>Type:</strong> ${captionType}</p>
<p><strong>Length:</strong> ${captionLength}</p>
<p><strong>Caption:</strong></p>
<p style="font-style:italic;">${caption}</p>
</div>
`;
} catch (err) {
console.error(err);
captionResultDiv.innerHTML = `<p style="color:red;">Caption generation failed: ${err}</p>`;
} finally {
btn.disabled = false;
btn.textContent = "Generate Caption";
}
});
// Text generation form event listener
textForm.addEventListener("submit", async (e) => {
e.preventDefault();
const prompt = document.getElementById("textPrompt").value.trim();
const systemPrompt = document.getElementById("systemPrompt").value.trim();
const maxNewTokens = parseInt(document.getElementById("maxNewTokens").value, 10);
const temperature = parseFloat(document.getElementById("temperature").value);
const topP = parseFloat(document.getElementById("topP").value);
const topK = parseInt(document.getElementById("topK").value, 10);
const repetitionPenalty = parseFloat(document.getElementById("repetitionPenalty").value);
if (!prompt) {
textResultDiv.innerHTML = "<p style='color:red;'>Please enter a prompt.</p>";
return;
}
const btn = textForm.querySelector("button");
btn.disabled = true;
btn.textContent = "Generating Text…";
textResultDiv.innerHTML = "Generating text…";
try {
// Call Zephyr-7B ChatInterface with proper parameters
// ChatInterface expects positional args: message, chat_history, then additional_inputs
const output = await zephyr.predict("/chat", [
prompt, // message
[], // chat_history (empty for single turn)
systemPrompt, // system_prompt (additional_input 1)
maxNewTokens, // max_new_tokens (additional_input 2)
temperature, // temperature (additional_input 3)
topP, // top_p (additional_input 4)
topK, // top_k (additional_input 5)
repetitionPenalty // repetition_penalty (additional_input 6)
]);
// ChatInterface returns the full response
const generatedText = output.data;
textResultDiv.innerHTML = `
<div style="background:#f8f9fa; padding:1rem; border-radius:6px; margin-top:1rem;">
<h4>Generated Text</h4>
<p><strong>Prompt:</strong> ${prompt.substring(0, 100)}${prompt.length > 100 ? '...' : ''}</p>
<p><strong>Generated:</strong></p>
<div style="white-space:pre-wrap; font-family:monospace; background:#fff; padding:1rem; border-radius:4px; border:1px solid #ddd;">${generatedText}</div>
</div>
`;
} catch (err) {
console.error(err);
textResultDiv.innerHTML = `<p style="color:red;">Text generation failed: ${err}</p>`;
} finally {
btn.disabled = false;
btn.textContent = "Generate Text";
}
});
} catch (err) {
console.error(err);
banner.textContent = `❌ Failed to connect: ${err}`;
banner.style.display = "block";
signinBtn.style.display = "none";
}
}
</script>
</body>
</html>