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
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>