repos
/ ai-art master

ai-art

mirror archived upstream

Art generation with VQGAN + CLIP in Docker, with a simple web UI for anyone with a GPU. A simplified and expanded take on Kevin Costa's work.

aiai-artclipdockerdocker-composegpuhandcodedimagenetpythonpytorchtorchtorchvisionvqganvqgan-clip

6.5 KB · 136 lines · HTML Raw History
  1<!doctype html>
  2<html lang="en">
  3  <head>
  4    <meta charset="utf-8">
  5    <meta name="viewport" content="width=device-width, initial-scale=1">
  6
  7    <title>AI-Art</title>
  8
  9    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
 10
 11    <style>
 12      body {
 13        min-height: 100vh;
 14      }
 15
 16      #output {
 17        width: 100%;
 18      }
 19    </style>
 20  </head>
 21  <body class="d-flex flex-column">
 22    <div class="container-fluid d-flex flex-grow-1">
 23      <div class="row flex-grow-1">
 24        <div class="col-sm-4 bg-black overflow-auto d-flex flex-column">
 25          <h1 class="display-5 text-white py-2 d-flex justify-content-between align-items-center">
 26            <span>🖼️ AI-Art</span>
 27            <a href="https://github.com/overshard/ai-art" target="_blank" class="text-white" style="line-height: 0;">
 28              <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" fill="currentColor" class="bi bi-github" viewBox="0 0 16 16">
 29                <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
 30              </svg>
 31            </a>
 32          </h1>
 33          <form method="POST" action="/update_config" class="my-4" enctype="multipart/form-data">
 34            <div class="form-floating mb-3">
 35              <textarea type="text" id="prompts" name="prompts" class="form-control" style="height: 150px;">{{ current_config.prompts }}</textarea>
 36              <label for="prompts">Prompts</label>
 37            </div>
 38            <div class="mb-3">
 39              <label for="formFile" class="form-label text-white">Initial image</label>
 40              <input class="form-control mb-1" type="file" id="init_image" name="init_image" accept="image/*">
 41            </div>
 42            {% if current_config.init_image != "" %}
 43            <div class="mb-3 row">
 44              <div class="small text-white mb-2 col"><span class="badge bg-primary">Using</span> {{ current_config.init_image }}</div>
 45              <label class="text-white d-flex align-items-center small col">
 46                <input type="checkbox" name="clear_init_image" value="true" class="me-2"> Clear image
 47              </label>
 48            </div>
 49            {% endif %}
 50            <div class="row">
 51              <div class="col">
 52                <div class="form-floating mb-3">
 53                  <input type="number" id="width" name="width" value="{{ current_config.size[0] }}" class="form-control" placeholder="Width">
 54                  <label for="width">Width</label>
 55                </div>
 56              </div>
 57              <div class="col">
 58                <div class="form-floating mb-3">
 59                  <input type="number" id="height" name="height" value="{{ current_config.size[1] }}" class="form-control" placeholder="Height">
 60                  <label for="height">Height</label>
 61                </div>
 62              </div>
 63              <div class="col">
 64                <div class="form-floating mb-3">
 65                  <input type="number" id="max_iterations" name="max_iterations" value="{{ current_config.max_iterations }}" class="form-control" placeholder="Iterations">
 66                  <label for="max_iterations">Iterations</label>
 67                </div>
 68              </div>
 69            </div>
 70            <div class="ms-auto d-flex justify-content-between">
 71              <button id="update" type="submit" class="btn btn-danger">Update</button>
 72              <label class="text-white d-flex align-items-center">
 73                <input type="checkbox" id="continuous-gen" value="true" class="me-2"> Continuous generation
 74              </label>
 75              <button id="generate" type="button" class="btn btn-success">Generate</button>
 76            </div>
 77          </form>
 78          <div class="w-100 mb-3 d-flex align-items-center justify-content-center flex-grow-1">
 79            <img id="latest-output" class="img-fluid" src="/latest_output">
 80          </div>
 81        </div>
 82        <div class="col-sm-8 d-flex p-0">
 83          <iframe id="output" src="/outputs"></iframe>
 84        </div>
 85      </div>
 86    </div>
 87
 88
 89    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-pprn3073KE6tl6bjs2QrFaJGz5/SUsLqktiwsUTF55Jfv3qYSDhgCecCxMW52nD2" crossorigin="anonymous"></script>
 90    <script>
 91      /**
 92       * On clicking #generate disable the button, change the text to "Generating...", and
 93       * and when it's complete take the output and display it in #generate-output and
 94       * enable the button again.
 95       */
 96
 97      document.addEventListener('DOMContentLoaded', function() {
 98        const generateImage = () => {
 99          document.querySelector('#generate').disabled = true;
100          document.querySelector('#generate').innerText = 'Generating...';
101          document.querySelector('#update').disabled = true;
102          const start = new Date().getTime();
103          fetch('/generate')
104            .then(function(response) {
105              return response.text();
106            })
107            .then(function(data) {
108              document.querySelector('#generate').disabled = false;
109              document.querySelector('#generate').innerText = 'Generate';
110              document.querySelector('#update').disabled = false;
111              document.querySelector('#output').src = '/outputs';
112              if (document.querySelector('#continuous-gen').checked) {
113                generateImage();
114              }
115            });
116        };
117
118        document.querySelector('#generate').addEventListener('click', function() {
119          generateImage();
120        });
121      });
122    </script>
123    <script>
124      /**
125       * Refresh the latest output every 5 seconds.
126       */
127      document.addEventListener('DOMContentLoaded', function() {
128        setInterval(function() {
129          // add random number to prevent caching
130          document.querySelector('#latest-output').src = '/latest_output?r=' + Math.random();
131        }, 5000);
132      });
133      </script>
134  </body>
135</html>