| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Multimodal Assistant</title> |
| <style> |
| body { font-family: 'Segoe UI', sans-serif; max-width: 750px; margin: 30px auto; background:#0f0f0f; color:#eee; } |
| h2 { text-align:center; } |
| #chat { border: 1px solid #333; border-radius: 8px; padding: 15px; height: 450px; overflow-y: auto; background:#1a1a1a; } |
| .msg { margin: 10px 0; line-height:1.4; } |
| .msg b { color:#7dd3fc; } |
| .msg.user b { color:#fca5a5; } |
| .msg img { max-width: 250px; border-radius:6px; display:block; margin-top:6px; } |
| #controls { display:flex; gap:8px; margin-top:12px; flex-wrap:wrap; } |
| #userInput { flex:1; padding:10px; border-radius:6px; border:1px solid #333; background:#1a1a1a; color:#eee; min-width:200px; } |
| button, select { padding:10px 14px; border-radius:6px; border:none; background:#3b82f6; color:white; cursor:pointer; } |
| button:hover { background:#2563eb; } |
| #status { font-size:12px; color:#888; margin-top:6px; } |
| #tokenBar { display:flex; gap:8px; margin-bottom:12px; } |
| #tokenInput { flex:1; padding:8px; border-radius:6px; border:1px solid #333; background:#1a1a1a; color:#eee; } |
| </style> |
| </head> |
| <body> |
| <h2>🧠 My Multimodal Assistant</h2> |
|
|
| <div id="tokenBar"> |
| <input type="password" id="tokenInput" placeholder="Paste your HF token here (not saved, session only)"> |
| <button onclick="saveToken()">Set Token</button> |
| </div> |
|
|
| <div id="chat"></div> |
|
|
| <div id="controls"> |
| <select id="mode"> |
| <option value="chat">💬 Chat / Ask about image</option> |
| <option value="image">🎨 Generate an image</option> |
| </select> |
| <input type="text" id="userInput" placeholder="Type your message..."> |
| <input type="file" id="imageInput" accept="image/*"> |
| <button onclick="send()">Send</button> |
| </div> |
| <div id="status"></div> |
|
|
| <script> |
| let HF_TOKEN = ""; |
| |
| function saveToken() { |
| HF_TOKEN = document.getElementById("tokenInput").value.trim(); |
| document.getElementById("tokenInput").value = ""; |
| document.getElementById("tokenInput").placeholder = HF_TOKEN |
| ? "✅ Token set for this session" |
| : "Paste your HF token here"; |
| } |
| |
| const chatBox = document.getElementById("chat"); |
| const statusBox = document.getElementById("status"); |
| let history = []; |
| |
| function addMessage(role, html) { |
| const div = document.createElement("div"); |
| div.className = "msg" + (role === "You" ? " user" : ""); |
| div.innerHTML = `<b>${role}:</b> ${html}`; |
| chatBox.appendChild(div); |
| chatBox.scrollTop = chatBox.scrollHeight; |
| } |
| |
| function toBase64(file) { |
| return new Promise((resolve, reject) => { |
| const reader = new FileReader(); |
| reader.onload = () => resolve(reader.result); |
| reader.onerror = reject; |
| reader.readAsDataURL(file); |
| }); |
| } |
| |
| async function send() { |
| if (!HF_TOKEN) { |
| alert("Please set your Hugging Face token first (top of the page)."); |
| return; |
| } |
| const text = document.getElementById("userInput").value.trim(); |
| const file = document.getElementById("imageInput").files[0]; |
| const mode = document.getElementById("mode").value; |
| if (!text && !file) return; |
| |
| if (mode === "image") { |
| await generateImage(text); |
| } else { |
| await chatWithModel(text, file); |
| } |
| document.getElementById("userInput").value = ""; |
| document.getElementById("imageInput").value = ""; |
| } |
| |
| async function chatWithModel(text, file) { |
| let content = [{ type: "text", text: text || "Describe this image." }]; |
| let userDisplay = text; |
| |
| if (file) { |
| const base64 = await toBase64(file); |
| content.push({ type: "image_url", image_url: { url: base64 } }); |
| userDisplay += `<img src="${base64}">`; |
| } |
| addMessage("You", userDisplay); |
| statusBox.textContent = "Thinking..."; |
| |
| const model = file |
| ? "Qwen/Qwen2.5-VL-7B-Instruct" |
| : "meta-llama/Llama-3.3-70B-Instruct"; |
| |
| history.push({ role: "user", content: content }); |
| |
| try { |
| const res = await fetch("https://router.huggingface.co/v1/chat/completions", { |
| method: "POST", |
| headers: { |
| "Authorization": `Bearer ${HF_TOKEN}`, |
| "Content-Type": "application/json" |
| }, |
| body: JSON.stringify({ model: model, messages: history }) |
| }); |
| const data = await res.json(); |
| if (data.error) throw new Error(data.error.message || JSON.stringify(data.error)); |
| |
| const reply = data.choices[0].message.content; |
| addMessage("AI", reply); |
| history.push({ role: "assistant", content: reply }); |
| statusBox.textContent = ""; |
| } catch (err) { |
| addMessage("AI", "⚠️ Error: " + err.message); |
| statusBox.textContent = ""; |
| } |
| } |
| |
| async function generateImage(prompt) { |
| addMessage("You", prompt); |
| statusBox.textContent = "Generating image (can take 10–30s)..."; |
| |
| try { |
| const res = await fetch( |
| "https://router.huggingface.co/hf-inference/models/black-forest-labs/FLUX.1-schnell", |
| { |
| method: "POST", |
| headers: { |
| "Authorization": `Bearer ${HF_TOKEN}`, |
| "Content-Type": "application/json" |
| }, |
| body: JSON.stringify({ inputs: prompt }) |
| } |
| ); |
| if (!res.ok) { |
| const errText = await res.text(); |
| throw new Error(errText); |
| } |
| const blob = await res.blob(); |
| const url = URL.createObjectURL(blob); |
| addMessage("AI", `<img src="${url}">`); |
| statusBox.textContent = ""; |
| } catch (err) { |
| addMessage("AI", "⚠️ Error: " + err.message); |
| statusBox.textContent = ""; |
| } |
| } |
| |
| document.getElementById("userInput").addEventListener("keydown", e => { |
| if (e.key === "Enter") send(); |
| }); |
| </script> |
| </body> |
| </html> |