Update index.html
Browse files- index.html +101 -16
index.html
CHANGED
|
@@ -1,19 +1,104 @@
|
|
| 1 |
<!doctype html>
|
| 2 |
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</html>
|
|
|
|
| 1 |
<!doctype html>
|
| 2 |
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
| 6 |
+
<title>WebGPU · Transformers.js · Image Captioning</title>
|
| 7 |
+
<style>
|
| 8 |
+
body { font: 16px/1.45 system-ui, sans-serif; margin: 24px auto; max-width: 900px; padding: 0 16px; }
|
| 9 |
+
.card { border:1px solid #4443; border-radius:12px; padding:16px; }
|
| 10 |
+
.log { white-space:pre-wrap; background:#111; color:#0f0; padding:12px; border-radius:8px; min-height:80px; }
|
| 11 |
+
img { max-width:100%; border-radius:8px; margin-top:10px; }
|
| 12 |
+
.muted { opacity:.75; font-size:14px; }
|
| 13 |
+
button,input { font:inherit; }
|
| 14 |
+
</style>
|
| 15 |
+
</head>
|
| 16 |
+
<body>
|
| 17 |
+
<h2>Image → Text in your browser (Transformers.js + WebGPU)</h2>
|
| 18 |
+
<p id="env">Probing environment…</p>
|
| 19 |
+
|
| 20 |
+
<div class="card">
|
| 21 |
+
<h3>Caption an image (file upload)</h3>
|
| 22 |
+
<input id="file" type="file" accept="image/*" />
|
| 23 |
+
<button id="run" disabled>Caption</button>
|
| 24 |
+
<div><img id="preview" alt="" /></div>
|
| 25 |
+
<h4>Output</h4>
|
| 26 |
+
<div id="log" class="log">Loading model…</div>
|
| 27 |
+
<p class="muted">
|
| 28 |
+
Model: <code>Xenova/blip-image-captioning-base</code><br />
|
| 29 |
+
Backend: <span id="backend">…</span>
|
| 30 |
+
</p>
|
| 31 |
+
</div>
|
| 32 |
+
|
| 33 |
+
<script type="module">
|
| 34 |
+
const envEl = document.getElementById('env');
|
| 35 |
+
const fileEl = document.getElementById('file');
|
| 36 |
+
const runBtn = document.getElementById('run');
|
| 37 |
+
const logEl = document.getElementById('log');
|
| 38 |
+
const imgEl = document.getElementById('preview');
|
| 39 |
+
const backendEl = document.getElementById('backend');
|
| 40 |
+
|
| 41 |
+
// 1) WebGPU probe (will use WASM if unavailable)
|
| 42 |
+
const hasWebGPU = 'gpu' in navigator;
|
| 43 |
+
const device = hasWebGPU ? 'webgpu' : 'wasm';
|
| 44 |
+
backendEl.textContent = device.toUpperCase();
|
| 45 |
+
envEl.textContent = hasWebGPU
|
| 46 |
+
? '✅ WebGPU detected. Using GPU when possible (falls back to FP32 automatically if no shader-f16).'
|
| 47 |
+
: '⚠️ No WebGPU, falling back to WASM (CPU).';
|
| 48 |
+
|
| 49 |
+
// Optional: show if shader-f16 exists (info only)
|
| 50 |
+
if (hasWebGPU) {
|
| 51 |
+
try {
|
| 52 |
+
const adapter = await navigator.gpu.requestAdapter();
|
| 53 |
+
if (adapter && !adapter.features.has('shader-f16')) {
|
| 54 |
+
envEl.textContent += ' (no shader-f16; running in FP32)';
|
| 55 |
+
}
|
| 56 |
+
} catch { /* ignore */ }
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
// 2) Load Transformers.js v3 from CDN
|
| 60 |
+
let pipeline;
|
| 61 |
+
try {
|
| 62 |
+
({ pipeline } = await import('https://cdn.jsdelivr.net/npm/@huggingface/transformers@3'));
|
| 63 |
+
} catch (e) {
|
| 64 |
+
logEl.textContent = 'Failed to load Transformers.js: ' + e;
|
| 65 |
+
throw e;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
// 3) Build the captioning pipeline (FP16 not required)
|
| 69 |
+
let captioner;
|
| 70 |
+
try {
|
| 71 |
+
captioner = await pipeline('image-to-text', 'Xenova/blip-image-captioning-base', { device });
|
| 72 |
+
logEl.textContent = `Model ready · device=${device}`;
|
| 73 |
+
runBtn.disabled = false;
|
| 74 |
+
} catch (e) {
|
| 75 |
+
logEl.textContent = 'Error loading model: ' + e;
|
| 76 |
+
console.error(e);
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
// 4) Preview selected image
|
| 80 |
+
let imgURL = null;
|
| 81 |
+
fileEl.addEventListener('change', () => {
|
| 82 |
+
if (imgURL) URL.revokeObjectURL(imgURL);
|
| 83 |
+
const f = fileEl.files?.[0];
|
| 84 |
+
if (!f) return;
|
| 85 |
+
imgURL = URL.createObjectURL(f);
|
| 86 |
+
imgEl.src = imgURL;
|
| 87 |
+
});
|
| 88 |
+
|
| 89 |
+
// 5) Run captioning
|
| 90 |
+
runBtn.addEventListener('click', async () => {
|
| 91 |
+
if (!captioner) return;
|
| 92 |
+
if (!imgURL) { logEl.textContent = 'Pick an image first.'; return; }
|
| 93 |
+
logEl.textContent = 'Running…';
|
| 94 |
+
try {
|
| 95 |
+
const out = await captioner(imgURL); // [{ generated_text }]
|
| 96 |
+
logEl.textContent = out[0].generated_text;
|
| 97 |
+
} catch (e) {
|
| 98 |
+
logEl.textContent = 'Inference error: ' + e;
|
| 99 |
+
console.error(e);
|
| 100 |
+
}
|
| 101 |
+
});
|
| 102 |
+
</script>
|
| 103 |
+
</body>
|
| 104 |
</html>
|