Images.svelte 11.9 KB
Newer Older
Timothy J. Baek's avatar
Timothy J. Baek committed
1
<script lang="ts">
Jannik Streidl's avatar
Jannik Streidl committed
2
	import { toast } from 'svelte-sonner';
Timothy J. Baek's avatar
Timothy J. Baek committed
3

4
	import { createEventDispatcher, onMount, getContext } from 'svelte';
5
	import { config, user } from '$lib/stores';
Timothy J. Baek's avatar
Timothy J. Baek committed
6
	import {
Timothy J. Baek's avatar
Timothy J. Baek committed
7
8
9
		getImageGenerationModels,
		getDefaultImageGenerationModel,
		updateDefaultImageGenerationModel,
Timothy J. Baek's avatar
Timothy J. Baek committed
10
		getImageSize,
Timothy J. Baek's avatar
Timothy J. Baek committed
11
12
		getImageGenerationConfig,
		updateImageGenerationConfig,
Timothy J. Baek's avatar
Timothy J. Baek committed
13
14
		getImageGenerationEngineUrls,
		updateImageGenerationEngineUrls,
15
		updateImageSize,
16
		getImageSteps,
Timothy J. Baek's avatar
Timothy J. Baek committed
17
18
19
		updateImageSteps,
		getOpenAIKey,
		updateOpenAIKey
Timothy J. Baek's avatar
Timothy J. Baek committed
20
	} from '$lib/apis/images';
21
	import { getBackendConfig } from '$lib/apis';
Timothy J. Baek's avatar
Timothy J. Baek committed
22
23
	const dispatch = createEventDispatcher();

24
25
	const i18n = getContext('i18n');

Timothy J. Baek's avatar
Timothy J. Baek committed
26
27
28
29
	export let saveSettings: Function;

	let loading = false;

Timothy J. Baek's avatar
Timothy J. Baek committed
30
	let imageGenerationEngine = '';
31
	let enableImageGeneration = false;
Timothy J. Baek's avatar
Timothy J. Baek committed
32

Timothy J. Baek's avatar
Timothy J. Baek committed
33
	let AUTOMATIC1111_BASE_URL = '';
Timothy J. Baek's avatar
Timothy J. Baek committed
34
35
	let COMFYUI_BASE_URL = '';

Timothy J. Baek's avatar
Timothy J. Baek committed
36
	let OPENAI_API_KEY = '';
Timothy J. Baek's avatar
Timothy J. Baek committed
37
38

	let selectedModel = '';
Timothy J. Baek's avatar
Timothy J. Baek committed
39
	let models = null;
Timothy J. Baek's avatar
Timothy J. Baek committed
40

Timothy J. Baek's avatar
Timothy J. Baek committed
41
	let imageSize = '';
42
	let steps = 50;
Timothy J. Baek's avatar
Timothy J. Baek committed
43

Timothy J. Baek's avatar
Timothy J. Baek committed
44
	const getModels = async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
45
		models = await getImageGenerationModels(localStorage.token).catch((error) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
46
			toast.error(error);
Timothy J. Baek's avatar
Timothy J. Baek committed
47
			return null;
Timothy J. Baek's avatar
Timothy J. Baek committed
48
		});
Timothy J. Baek's avatar
Timothy J. Baek committed
49
		selectedModel = await getDefaultImageGenerationModel(localStorage.token).catch((error) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
50
			return '';
Timothy J. Baek's avatar
Timothy J. Baek committed
51
52
53
		});
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
54
55
56
57
58
	const updateUrlHandler = async () => {
		if (imageGenerationEngine === 'comfyui') {
			const res = await updateImageGenerationEngineUrls(localStorage.token, {
				COMFYUI_BASE_URL: COMFYUI_BASE_URL
			}).catch((error) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
59
				toast.error(error);
Timothy J. Baek's avatar
Timothy J. Baek committed
60
61

				console.log(error);
Timothy J. Baek's avatar
Timothy J. Baek committed
62
				return null;
Timothy J. Baek's avatar
Timothy J. Baek committed
63
			});
Timothy J. Baek's avatar
Timothy J. Baek committed
64

Timothy J. Baek's avatar
Timothy J. Baek committed
65
66
			if (res) {
				COMFYUI_BASE_URL = res.COMFYUI_BASE_URL;
Timothy J. Baek's avatar
Timothy J. Baek committed
67

Timothy J. Baek's avatar
Timothy J. Baek committed
68
				await getModels();
Timothy J. Baek's avatar
Timothy J. Baek committed
69

Timothy J. Baek's avatar
Timothy J. Baek committed
70
71
72
73
74
				if (models) {
					toast.success($i18n.t('Server connection verified'));
				}
			} else {
				({ COMFYUI_BASE_URL } = await getImageGenerationEngineUrls(localStorage.token));
Timothy J. Baek's avatar
Timothy J. Baek committed
75
			}
Timothy J. Baek's avatar
Timothy J. Baek committed
76
		} else {
Timothy J. Baek's avatar
Timothy J. Baek committed
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
			const res = await updateImageGenerationEngineUrls(localStorage.token, {
				AUTOMATIC1111_BASE_URL: AUTOMATIC1111_BASE_URL
			}).catch((error) => {
				toast.error(error);
				return null;
			});

			if (res) {
				AUTOMATIC1111_BASE_URL = res.AUTOMATIC1111_BASE_URL;

				await getModels();

				if (models) {
					toast.success($i18n.t('Server connection verified'));
				}
			} else {
				({ AUTOMATIC1111_BASE_URL } = await getImageGenerationEngineUrls(localStorage.token));
			}
Timothy J. Baek's avatar
Timothy J. Baek committed
95
96
		}
	};
Timothy J. Baek's avatar
Timothy J. Baek committed
97
98
99
100
101
102
103
104
105
	const updateImageGeneration = async () => {
		const res = await updateImageGenerationConfig(
			localStorage.token,
			imageGenerationEngine,
			enableImageGeneration
		).catch((error) => {
			toast.error(error);
			return null;
		});
Timothy J. Baek's avatar
Timothy J. Baek committed
106

Timothy J. Baek's avatar
Timothy J. Baek committed
107
108
109
110
111
112
113
114
		if (res) {
			imageGenerationEngine = res.engine;
			enableImageGeneration = res.enabled;
		}

		if (enableImageGeneration) {
			config.set(await getBackendConfig(localStorage.token));
			getModels();
115
		}
Timothy J. Baek's avatar
Timothy J. Baek committed
116
117
118
119
	};

	onMount(async () => {
		if ($user.role === 'admin') {
Timothy J. Baek's avatar
Timothy J. Baek committed
120
121
122
123
124
125
126
127
128
			const res = await getImageGenerationConfig(localStorage.token).catch((error) => {
				toast.error(error);
				return null;
			});

			if (res) {
				imageGenerationEngine = res.engine;
				enableImageGeneration = res.enabled;
			}
Timothy J. Baek's avatar
Timothy J. Baek committed
129
130
131
132
133
			const URLS = await getImageGenerationEngineUrls(localStorage.token);

			AUTOMATIC1111_BASE_URL = URLS.AUTOMATIC1111_BASE_URL;
			COMFYUI_BASE_URL = URLS.COMFYUI_BASE_URL;

Timothy J. Baek's avatar
Timothy J. Baek committed
134
			OPENAI_API_KEY = await getOpenAIKey(localStorage.token);
Timothy J. Baek's avatar
Timothy J. Baek committed
135

Timothy J. Baek's avatar
Timothy J. Baek committed
136
137
138
139
			imageSize = await getImageSize(localStorage.token);
			steps = await getImageSteps(localStorage.token);

			if (enableImageGeneration) {
Timothy J. Baek's avatar
Timothy J. Baek committed
140
				getModels();
Timothy J. Baek's avatar
Timothy J. Baek committed
141
142
143
144
145
146
147
148
149
			}
		}
	});
</script>

<form
	class="flex flex-col h-full justify-between space-y-3 text-sm"
	on:submit|preventDefault={async () => {
		loading = true;
Timothy J. Baek's avatar
Timothy J. Baek committed
150

151
152
153
		if (imageGenerationEngine === 'openai') {
			await updateOpenAIKey(localStorage.token, OPENAI_API_KEY);
		}
Timothy J. Baek's avatar
Timothy J. Baek committed
154

Timothy J. Baek's avatar
Timothy J. Baek committed
155
		await updateDefaultImageGenerationModel(localStorage.token, selectedModel);
156

Timothy J. Baek's avatar
Timothy J. Baek committed
157
158
159
160
		await updateImageSize(localStorage.token, imageSize).catch((error) => {
			toast.error(error);
			return null;
		});
161
162
163
164
		await updateImageSteps(localStorage.token, steps).catch((error) => {
			toast.error(error);
			return null;
		});
Timothy J. Baek's avatar
Timothy J. Baek committed
165
166
167
168
169

		dispatch('save');
		loading = false;
	}}
>
Timothy J. Baek's avatar
Timothy J. Baek committed
170
	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[24rem]">
Timothy J. Baek's avatar
Timothy J. Baek committed
171
		<div>
172
			<div class=" mb-1 text-sm font-medium">{$i18n.t('Image Settings')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
173

Timothy J. Baek's avatar
Timothy J. Baek committed
174
			<div class=" py-0.5 flex w-full justify-between">
Ased Mammad's avatar
Ased Mammad committed
175
				<div class=" self-center text-xs font-medium">{$i18n.t('Image Generation Engine')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
176
177
178
179
				<div class="flex items-center relative">
					<select
						class="w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
						bind:value={imageGenerationEngine}
Ased Mammad's avatar
Ased Mammad committed
180
						placeholder={$i18n.t('Select a mode')}
Timothy J. Baek's avatar
Timothy J. Baek committed
181
182
183
184
						on:change={async () => {
							await updateImageGeneration();
						}}
					>
Ased Mammad's avatar
Ased Mammad committed
185
						<option value="">{$i18n.t('Default (Automatic1111)')}</option>
Timothy J. Baek's avatar
Timothy J. Baek committed
186
						<option value="comfyui">{$i18n.t('ComfyUI')}</option>
Ased Mammad's avatar
Ased Mammad committed
187
						<option value="openai">{$i18n.t('Open AI (Dall-E)')}</option>
Timothy J. Baek's avatar
Timothy J. Baek committed
188
189
190
191
					</select>
				</div>
			</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
192
193
			<div>
				<div class=" py-0.5 flex w-full justify-between">
194
195
196
					<div class=" self-center text-xs font-medium">
						{$i18n.t('Image Generation (Experimental)')}
					</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
197
198
199
200

					<button
						class="p-1 px-3 text-xs flex rounded transition"
						on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
201
							if (imageGenerationEngine === '' && AUTOMATIC1111_BASE_URL === '') {
Ased Mammad's avatar
Ased Mammad committed
202
								toast.error($i18n.t('AUTOMATIC1111 Base URL is required.'));
Timothy J. Baek's avatar
Timothy J. Baek committed
203
								enableImageGeneration = false;
Timothy J. Baek's avatar
Timothy J. Baek committed
204
205
206
							} else if (imageGenerationEngine === 'comfyui' && COMFYUI_BASE_URL === '') {
								toast.error($i18n.t('ComfyUI Base URL is required.'));
								enableImageGeneration = false;
207
							} else if (imageGenerationEngine === 'openai' && OPENAI_API_KEY === '') {
Ased Mammad's avatar
Ased Mammad committed
208
								toast.error($i18n.t('OpenAI API Key is required.'));
209
								enableImageGeneration = false;
Timothy J. Baek's avatar
Timothy J. Baek committed
210
211
212
							} else {
								enableImageGeneration = !enableImageGeneration;
							}
Timothy J. Baek's avatar
Timothy J. Baek committed
213
214

							updateImageGeneration();
Timothy J. Baek's avatar
Timothy J. Baek committed
215
216
217
218
						}}
						type="button"
					>
						{#if enableImageGeneration === true}
219
							<span class="ml-2 self-center">{$i18n.t('On')}</span>
Timothy J. Baek's avatar
Timothy J. Baek committed
220
						{:else}
221
							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
Timothy J. Baek's avatar
Timothy J. Baek committed
222
223
224
225
226
227
228
						{/if}
					</button>
				</div>
			</div>
		</div>
		<hr class=" dark:border-gray-700" />

Timothy J. Baek's avatar
Timothy J. Baek committed
229
		{#if imageGenerationEngine === ''}
230
			<div class=" mb-2.5 text-sm font-medium">{$i18n.t('AUTOMATIC1111 Base URL')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
231
232
233
			<div class="flex w-full">
				<div class="flex-1 mr-2">
					<input
Timothy J. Baek's avatar
Timothy J. Baek committed
234
						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
Ased Mammad's avatar
Ased Mammad committed
235
						placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
Timothy J. Baek's avatar
Timothy J. Baek committed
236
						bind:value={AUTOMATIC1111_BASE_URL}
Timothy J. Baek's avatar
Timothy J. Baek committed
237
					/>
Timothy J. Baek's avatar
Timothy J. Baek committed
238
239
				</div>
				<button
Timothy J. Baek's avatar
Timothy J. Baek committed
240
					class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
Timothy J. Baek's avatar
Timothy J. Baek committed
241
242
					type="button"
					on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
243
						updateUrlHandler();
Timothy J. Baek's avatar
Timothy J. Baek committed
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
					}}
				>
					<svg
						xmlns="http://www.w3.org/2000/svg"
						viewBox="0 0 20 20"
						fill="currentColor"
						class="w-4 h-4"
					>
						<path
							fill-rule="evenodd"
							d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
							clip-rule="evenodd"
						/>
					</svg>
				</button>
Timothy J. Baek's avatar
Timothy J. Baek committed
259
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
260
261

			<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
Ased Mammad's avatar
Ased Mammad committed
262
				{$i18n.t('Include `--api` flag when running stable-diffusion-webui')}
Timothy J. Baek's avatar
Timothy J. Baek committed
263
264
265
266
				<a
					class=" text-gray-300 font-medium"
					href="https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions/3734"
					target="_blank"
Timothy J. Baek's avatar
Timothy J. Baek committed
267
				>
Ased Mammad's avatar
Ased Mammad committed
268
					{$i18n.t('(e.g. `sh webui.sh --api`)')}
Timothy J. Baek's avatar
Timothy J. Baek committed
269
270
				</a>
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
		{:else if imageGenerationEngine === 'comfyui'}
			<div class=" mb-2.5 text-sm font-medium">{$i18n.t('ComfyUI Base URL')}</div>
			<div class="flex w-full">
				<div class="flex-1 mr-2">
					<input
						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
						placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
						bind:value={COMFYUI_BASE_URL}
					/>
				</div>
				<button
					class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
					type="button"
					on:click={() => {
						updateUrlHandler();
					}}
				>
					<svg
						xmlns="http://www.w3.org/2000/svg"
						viewBox="0 0 20 20"
						fill="currentColor"
						class="w-4 h-4"
					>
						<path
							fill-rule="evenodd"
							d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
							clip-rule="evenodd"
						/>
					</svg>
				</button>
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
302
		{:else if imageGenerationEngine === 'openai'}
Ased Mammad's avatar
Ased Mammad committed
303
			<div class=" mb-2.5 text-sm font-medium">{$i18n.t('OpenAI API Key')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
304
305
306
307
			<div class="flex w-full">
				<div class="flex-1 mr-2">
					<input
						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
Ased Mammad's avatar
Ased Mammad committed
308
						placeholder={$i18n.t('Enter API Key')}
Timothy J. Baek's avatar
Timothy J. Baek committed
309
						bind:value={OPENAI_API_KEY}
Timothy J. Baek's avatar
Timothy J. Baek committed
310
					/>
Timothy J. Baek's avatar
Timothy J. Baek committed
311
312
				</div>
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
313
		{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
314
315
316
317

		{#if enableImageGeneration}
			<hr class=" dark:border-gray-700" />

Timothy J. Baek's avatar
Timothy J. Baek committed
318
			<div>
319
				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Default Model')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
320
321
322
				<div class="flex w-full">
					<div class="flex-1 mr-2">
						<select
Timothy J. Baek's avatar
Timothy J. Baek committed
323
							class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
Timothy J. Baek's avatar
Timothy J. Baek committed
324
							bind:value={selectedModel}
325
							placeholder={$i18n.t('Select a model')}
Timothy J. Baek's avatar
Timothy J. Baek committed
326
							required
Timothy J. Baek's avatar
Timothy J. Baek committed
327
328
						>
							{#if !selectedModel}
329
								<option value="" disabled selected>{$i18n.t('Select a model')}</option>
Timothy J. Baek's avatar
Timothy J. Baek committed
330
							{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
331
							{#each models ?? [] as model}
Timothy J. Baek's avatar
Timothy J. Baek committed
332
								<option value={model.id} class="bg-gray-100 dark:bg-gray-700">{model.name}</option>
Timothy J. Baek's avatar
Timothy J. Baek committed
333
334
335
336
337
							{/each}
						</select>
					</div>
				</div>
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
338
339

			<div>
340
				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Image Size')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
341
342
343
				<div class="flex w-full">
					<div class="flex-1 mr-2">
						<input
Timothy J. Baek's avatar
Timothy J. Baek committed
344
							class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
Ased Mammad's avatar
Ased Mammad committed
345
							placeholder={$i18n.t('Enter Image Size (e.g. 512x512)')}
Timothy J. Baek's avatar
Timothy J. Baek committed
346
347
348
349
350
							bind:value={imageSize}
						/>
					</div>
				</div>
			</div>
351
352

			<div>
353
				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Steps')}</div>
354
355
356
				<div class="flex w-full">
					<div class="flex-1 mr-2">
						<input
Timothy J. Baek's avatar
Timothy J. Baek committed
357
							class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
Ased Mammad's avatar
Ased Mammad committed
358
							placeholder={$i18n.t('Enter Number of Steps (e.g. 50)')}
359
360
361
362
363
							bind:value={steps}
						/>
					</div>
				</div>
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
364
365
366
367
368
		{/if}
	</div>

	<div class="flex justify-end pt-3 text-sm font-medium">
		<button
Timothy J. Baek's avatar
Timothy J. Baek committed
369
			class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg flex flex-row space-x-1 items-center {loading
Timothy J. Baek's avatar
Timothy J. Baek committed
370
371
372
373
374
				? ' cursor-not-allowed'
				: ''}"
			type="submit"
			disabled={loading}
		>
Jannik Streidl's avatar
Jannik Streidl committed
375
			{$i18n.t('Save')}
Timothy J. Baek's avatar
Timothy J. Baek committed
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406

			{#if loading}
				<div class="ml-2 self-center">
					<svg
						class=" w-4 h-4"
						viewBox="0 0 24 24"
						fill="currentColor"
						xmlns="http://www.w3.org/2000/svg"
						><style>
							.spinner_ajPY {
								transform-origin: center;
								animation: spinner_AtaB 0.75s infinite linear;
							}
							@keyframes spinner_AtaB {
								100% {
									transform: rotate(360deg);
								}
							}
						</style><path
							d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
							opacity=".25"
						/><path
							d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
							class="spinner_ajPY"
						/></svg
					>
				</div>
			{/if}
		</button>
	</div>
</form>