File size: 7,646 Bytes
6348944
 
 
fd74d53
 
a49356e
fd74d53
6348944
 
fd74d53
6348944
e93a881
dcb554d
5443231
6348944
fd74d53
6348944
e93a881
fd74d53
 
 
5443231
 
 
6348944
dcb554d
fd74d53
b89e77c
 
5443231
b89e77c
4bc8213
6348944
6b981ec
 
 
 
d32c6ac
9e16391
6b981ec
e93a881
10f2d95
 
 
d7215d7
10f2d95
6348944
 
ef76154
64dea8b
4b1909d
6348944
64dea8b
 
 
6348944
e93a881
 
a49356e
 
 
 
 
ef08385
e93a881
a49356e
 
e93a881
 
7da9532
a49356e
 
6348944
 
 
 
e93a881
5443231
 
6348944
 
e93a881
6348944
 
64dea8b
5443231
 
ef08385
 
 
 
 
 
 
 
 
6348944
 
 
 
5443231
 
 
 
 
 
 
 
 
 
 
6348944
5443231
 
 
6348944
5443231
 
 
 
 
6348944
5443231
 
 
 
6348944
5443231
 
ef08385
 
a34bbab
a1aa607
ef08385
 
5443231
 
 
 
a49356e
5443231
 
 
dc0a560
5443231
 
 
 
 
 
 
 
 
 
6348944
5443231
 
 
 
 
ef08385
5443231
ef08385
 
 
 
 
 
 
5443231
ef08385
 
 
 
 
 
c89d3c3
ef08385
5443231
 
 
 
 
6348944
 
5443231
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
<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>
      /**
       * Format bytes as human-readable text.
       *
       * @param bytes Number of bytes.
       * @param si True to use metric (SI) units, aka powers of 1000. False to use
       *           binary (IEC), aka powers of 1024.
       * @param dp Number of decimal places to display.
       *
       * @return Formatted string.
       */
      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, // if you change this, you will need to also change in src/index.mts
          timeoutInSec: 15, // time before we determine something went wrong
          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); // count how many characters we have generated

              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>