|
<html> |
|
<head> |
|
<title>Webapp Factory π</title> |
|
<link href="/css/daisyui@2.6.0.css" rel="stylesheet" type="text/css"> |
|
</head> |
|
<body> |
|
<div class="flex flex-row" x-data="app()" x-init="init()"> |
|
<div |
|
class="hero min-h-screen bg-stone-100 transition-[width] delay-150 ease-in-out" |
|
:class="open ? 'w-2/6' : 'w-6/6'"> |
|
<div class="hero-content text-center"> |
|
<div class="flex flex-col max-w-xl space-y-6"> |
|
<h1 |
|
class="font-bold text-stone-600 mb-4 transition-all delay-150 ease-in-out" |
|
:class="open ? 'md:text-3xl lg:text-4xl' : 'text-6xl'" |
|
>Webapp Factory π</h1> |
|
<div |
|
class="py-2 space-y-4 text-stone-600 transition-all delay-150 ease-in-out" |
|
:class="open ? 'md:text-lg lg:text-xl' : 'text-2xl'"> |
|
<p>A Hugging Face space to generate web applications using a local LLM (Airoboros-13B).</p> |
|
<p>No 3rd party service or token needed: feel free to duplicate and create interesting forks of this space π§</p> |
|
</div> |
|
<textarea |
|
name="promptDraft" |
|
x-model="promptDraft" |
|
rows="10" |
|
placeholder="a pong clone made using the canvas.." |
|
class="input input-bordered w-full rounded text-lg text-stone-500 bg-stone-300 font-mono h-48" |
|
></textarea> |
|
<button |
|
class="btn disabled:text-stone-400" |
|
@click="open = true, prompt = promptDraft, state = state === 'stopped' ? 'loading' : 'stopped'" |
|
:class="promptDraft.length < minPromptSize ? 'btn-neutral' : state === 'stopped' ? 'btn-accent' : 'btn-warning'" |
|
:disabled="promptDraft.length < minPromptSize" |
|
> |
|
<span x-show="promptDraft.length < minPromptSize">Prompt too short to generate</span> |
|
<span x-show="promptDraft.length >= minPromptSize && state !== 'stopped'">Stop now</span> |
|
<span x-show="promptDraft.length >= minPromptSize && state === 'stopped'">Generate!</span> |
|
</button> |
|
<span class="py-3" x-show="state === 'loading'">Waiting for the stream to begin (might take a few minutes)..</span> |
|
<span class="py-3" x-show="state === 'streaming'"> |
|
Streamed <span x-text="humanFileSize(size, true, 2)"></span> so far<br/> (hang on, this may take 5-15 minutes β)</span> |
|
</div> |
|
</div> |
|
</div> |
|
<div |
|
class="flex transition-[width] delay-150 ease-in-out" |
|
:class="open ? 'w-4/6' : 'w-0'"> |
|
<iframe |
|
id="iframe" |
|
class="border-none w-full h-screen" |
|
:src="!open |
|
? '/placeholder.html' |
|
: `/app?prompt=${encodeURIComponent(prompt)}` |
|
"></iframe> |
|
</div> |
|
</div> |
|
<script> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function humanFileSize(bytes, si=false, dp=1) { |
|
const thresh = si ? 1000 : 1024; |
|
|
|
if (Math.abs(bytes) < thresh) { |
|
return bytes + ' B'; |
|
} |
|
|
|
const units = si |
|
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] |
|
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; |
|
let u = -1; |
|
const r = 10**dp; |
|
|
|
do { |
|
bytes /= thresh; |
|
++u; |
|
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1); |
|
|
|
|
|
return bytes.toFixed(dp) + ' ' + units[u]; |
|
} |
|
function app() { |
|
return { |
|
open: false, |
|
promptDraft: new URLSearchParams(window.location.search).get('prompt') || '', |
|
prompt: '', |
|
size: 0, |
|
minPromptSize: 16, |
|
timeoutInSec: 3 * 60, |
|
state: 'stopped', |
|
lastTokenAt: + new Date(), |
|
init() { |
|
setInterval(() => { |
|
if (this.state === 'stopped') { |
|
this.lastTokenAt = +new Date() |
|
return |
|
} |
|
const html = document?.getElementById('iframe')?.contentWindow?.document?.documentElement?.outerHTML |
|
const size = Number(html?.length) |
|
|
|
if (isNaN(size) || !isFinite(size)) { |
|
this.size = 0 |
|
this.state = 'loading' |
|
return |
|
} |
|
this.size = new Blob([html]).size |
|
this.state = 'streaming' |
|
const lastTokenAt = + new Date() |
|
const elapsed = (lastTokenAt - this.lastTokenAt) / 1000 |
|
this.lastTokenAt = lastTokenAt |
|
if (elapsed > this.timeoutInSec) { |
|
console.log(`Something went wrong, it too more than ${this.timeoutInSec} seconds to generate a token.`) |
|
this.state = 'stopped' |
|
return |
|
} |
|
if (html.endsWith('</html>')) { |
|
|
|
|
|
return |
|
} |
|
}, 100) |
|
}, |
|
} |
|
} |
|
</script> |
|
<script src="/js/alpinejs@3.12.2.js"></script> |
|
<script src="/js/tailwindcss@3.3.2.js"></script> |
|
</body> |
|
</html> |
|
|