| <html> |
| <head> |
| <title>Webapp Factory π</title> |
| <link href="https://cdn.jsdelivr.net/npm/daisyui@3.1.6/dist/full.css" rel="stylesheet" type="text/css" /> |
| <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script> |
| <script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.2/iframeResizer.contentWindow.min.js"></script> |
| </head> |
| <body> |
| <div class="flex flex-col md:flex-row" x-data="app()" x-init="init()"> |
| <div |
| class="hero md:h-screen bg-stone-100 transition-[width] delay-150 ease-in-out" |
| :class="open ? 'w-full md:w-2/6' : 'w-full md:w-6/6'" |
| > |
| <div class="hero-content text-center"> |
| <div class="flex flex-col w-full md:max-w-xl space-y-3 md:space-y-6"> |
| <h1 |
| class="font-bold text-stone-600 mb-1 md:mb-3 transition-all delay-150 ease-in-out" |
| :class="open |
| ? 'text-2xl md:text-3xl lg:text-4xl' |
| : 'text-2xl md:text-3xl lg:text-6xl'" |
| > |
| Webapp Factory π |
| </h1> |
| <div |
| class="py-1 md:py-2 space-y-2 md:space-y-3 text-stone-600 transition-all delay-150 ease-in-out" |
| :class="open |
| ? 'text-lg lg:text-xl' |
| : 'text-lg lg:text-xl'" |
| > |
| <p>A space to generate tiny web apps.</p> |
| <p>In case of hallucination try generating again π²</p> |
| </div> |
| <textarea |
| name="promptDraft" |
| x-model="promptDraft" |
| rows="10" |
| placeholder="Describe your web app" |
| class="input w-full rounded text-stone-800 bg-stone-50 border-2 border-stone-400 font-mono text-md md:text-lg h-24 md:h-48" |
| ></textarea> |
| <p class="py-1 md:py-2 text-stone-700 text-italic"> |
| Examples: |
|
|
| <a href="/?prompt=a simple page to compute the BMI using metric units" class="text-bold underline">compute my BMI</a>, |
| <a href="/?prompt=app listing various types of savanna animals, with their photos" class="text-bold underline">photos of savanna animals</a> |
| </p> |
| <button |
| class="btn disabled:text-stone-400" |
| @click="open = true, prompt = promptDraft, state = state === 'stopped' ? 'loading' : 'stopped', state === 'streaming' ? stopGeneration() : true" |
| :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> |
| <div class="flex flex-col text-stone-700 space-y-1 md:space-y-2"> |
| <p class="text-stone-700"> |
| Model used: |
| <a href="https://huggingface.co/WizardLM/WizardCoder-15B-V1.0" class="underline" target="_blank"> |
| WizardCoder-15B-1.0 |
| </a> |
| </p> |
| <p>Powered by π€ <a href="https://huggingface.co/inference-endpoints" class="underline" target="_blank">Inference Endpoints</a></p> |
| <p class="text-stone-700" x-show="state === 'loading'"> |
| Waiting for the stream to begin (might take a few minutes).. |
| </p> |
| <p class="text-stone-700" x-show="state === 'streaming'"> |
| Content size: <span x-text="humanFileSize(size, true, 2)"></span>. This version generates up |
| to 1686 tokens. |
| </p> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div |
| class="flex flex-col transition-[width] delay-150 ease-in-out md:h-screen" |
| :class="open ? 'w-full md:w-4/6' : 'w-full md:w-0'" |
| > |
| <iframe |
| id="iframe" |
| class="border-none w-full md:min-h-screen" |
| :src="!open |
| ? '/placeholder.html' |
| : `/app?prompt=${encodeURIComponent(prompt)}` |
| " |
| ></iframe> |
|
|
| <div |
| x-show="state !== 'stopped'" |
| class="flex w-full -mt-20 items-end justify-center pointer-events-none"> |
| <div class="flex flex-row py-3 px-8 text-center bg-stone-200 text-stone-600 rounded-md shadow-md"> |
| <div class="animate-bounce duration-150 mr-1">π€</div> |
| <div>Generating your app..</div> |
| </div> |
| </div> |
| </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 stopGeneration() { |
| console.log("stopping generation.."); |
| document?.getElementById("iframe")?.contentWindow?.stop?.(); |
| } |
| |
| function app() { |
| return { |
| open: false, |
| promptDraft: |
| new URLSearchParams(window.location.search).get("prompt") || '', |
| prompt: "", |
| size: 0, |
| minPromptSize: 16, |
| timeoutInSec: 15, |
| 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.state = "streaming"; |
| |
| const now = +new Date(); |
| const newSize = new Blob([html]).size; |
| const hasChanged = newSize !== this.size; |
| |
| if (hasChanged) { |
| this.lastTokenAt = now; |
| } |
| |
| this.size = newSize; |
| |
| const timeSinceLastUpdate = (now - this.lastTokenAt) / 1000; |
| |
| if (timeSinceLastUpdate > this.timeoutInSec) { |
| console.log(`no changes detected for the past ${this.timeoutInSec} seconds -> considering we're done`); |
| this.state = "stopped"; |
| } |
| }, 100); |
| }, |
| }; |
| } |
| </script> |
| </body> |
| </html> |
|
|