Images.svelte 13.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
		updateImageSteps,
18
19
		getOpenAIConfig,
		updateOpenAIConfig
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
	let loading = false;

Timothy J. Baek's avatar
Timothy J. Baek committed
28
	let imageGenerationEngine = '';
29
	let enableImageGeneration = false;
Timothy J. Baek's avatar
Timothy J. Baek committed
30

Timothy J. Baek's avatar
Timothy J. Baek committed
31
	let AUTOMATIC1111_BASE_URL = '';
32
	let AUTOMATIC1111_API_AUTH = '';
Timothy J. Baek's avatar
Timothy J. Baek committed
33
34
	let COMFYUI_BASE_URL = '';

35
	let OPENAI_API_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
			const res = await updateImageGenerationEngineUrls(localStorage.token, {
				AUTOMATIC1111_BASE_URL: AUTOMATIC1111_BASE_URL
79
				AUTOMATIC1111_API_AUTH: AUTOMATIC1111_API_AUTH
Timothy J. Baek's avatar
Timothy J. Baek committed
80
81
82
83
84
85
86
			}).catch((error) => {
				toast.error(error);
				return null;
			});

			if (res) {
				AUTOMATIC1111_BASE_URL = res.AUTOMATIC1111_BASE_URL;
87
				AUTOMATIC1111_API_AUTH = res.AUTOMATIC1111_API_AUTH;
Timothy J. Baek's avatar
Timothy J. Baek committed
88
89
90
91
92
93
94

				await getModels();

				if (models) {
					toast.success($i18n.t('Server connection verified'));
				}
			} else {
95
				({ AUTOMATIC1111_BASE_URL,AUTOMATIC1111_API_AUTH } = await getImageGenerationEngineUrls(localStorage.token));
Timothy J. Baek's avatar
Timothy J. Baek committed
96
			}
Timothy J. Baek's avatar
Timothy J. Baek committed
97
98
		}
	};
Timothy J. Baek's avatar
Timothy J. Baek committed
99
100
101
102
103
104
105
106
107
	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
108

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

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

	onMount(async () => {
		if ($user.role === 'admin') {
Timothy J. Baek's avatar
Timothy J. Baek committed
122
123
124
125
126
127
128
129
130
			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
131
132
133
			const URLS = await getImageGenerationEngineUrls(localStorage.token);

			AUTOMATIC1111_BASE_URL = URLS.AUTOMATIC1111_BASE_URL;
134
			AUTOMATIC1111_API_AUTH = URLS.AUTOMATIC1111_API_AUTH;
Timothy J. Baek's avatar
Timothy J. Baek committed
135
136
			COMFYUI_BASE_URL = URLS.COMFYUI_BASE_URL;

137
138
139
140
			const config = await getOpenAIConfig(localStorage.token);

			OPENAI_API_KEY = config.OPENAI_API_KEY;
			OPENAI_API_BASE_URL = config.OPENAI_API_BASE_URL;
Timothy J. Baek's avatar
Timothy J. Baek committed
141

Timothy J. Baek's avatar
Timothy J. Baek committed
142
143
144
145
			imageSize = await getImageSize(localStorage.token);
			steps = await getImageSteps(localStorage.token);

			if (enableImageGeneration) {
Timothy J. Baek's avatar
Timothy J. Baek committed
146
				getModels();
Timothy J. Baek's avatar
Timothy J. Baek committed
147
148
149
150
151
152
153
154
155
			}
		}
	});
</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
156

157
		if (imageGenerationEngine === 'openai') {
158
			await updateOpenAIConfig(localStorage.token, OPENAI_API_BASE_URL, OPENAI_API_KEY);
159
		}
Timothy J. Baek's avatar
Timothy J. Baek committed
160

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

Timothy J. Baek's avatar
Timothy J. Baek committed
163
164
165
166
		await updateImageSize(localStorage.token, imageSize).catch((error) => {
			toast.error(error);
			return null;
		});
167
168
169
170
		await updateImageSteps(localStorage.token, steps).catch((error) => {
			toast.error(error);
			return null;
		});
Timothy J. Baek's avatar
Timothy J. Baek committed
171
172
173
174
175

		dispatch('save');
		loading = false;
	}}
>
Timothy J. Baek's avatar
Timothy J. Baek committed
176
	<div class=" space-y-3 overflow-y-scroll scrollbar-hidden">
Timothy J. Baek's avatar
Timothy J. Baek committed
177
		<div>
178
			<div class=" mb-1 text-sm font-medium">{$i18n.t('Image Settings')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
179

Timothy J. Baek's avatar
Timothy J. Baek committed
180
			<div class=" py-0.5 flex w-full justify-between">
Ased Mammad's avatar
Ased Mammad committed
181
				<div class=" self-center text-xs font-medium">{$i18n.t('Image Generation Engine')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
182
183
184
185
				<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
186
						placeholder={$i18n.t('Select a mode')}
Timothy J. Baek's avatar
Timothy J. Baek committed
187
188
189
190
						on:change={async () => {
							await updateImageGeneration();
						}}
					>
Ased Mammad's avatar
Ased Mammad committed
191
						<option value="">{$i18n.t('Default (Automatic1111)')}</option>
Timothy J. Baek's avatar
Timothy J. Baek committed
192
						<option value="comfyui">{$i18n.t('ComfyUI')}</option>
Ased Mammad's avatar
Ased Mammad committed
193
						<option value="openai">{$i18n.t('Open AI (Dall-E)')}</option>
Timothy J. Baek's avatar
Timothy J. Baek committed
194
195
196
197
					</select>
				</div>
			</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
198
199
			<div>
				<div class=" py-0.5 flex w-full justify-between">
200
201
202
					<div class=" self-center text-xs font-medium">
						{$i18n.t('Image Generation (Experimental)')}
					</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
203
204
205
206

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

							updateImageGeneration();
Timothy J. Baek's avatar
Timothy J. Baek committed
221
222
223
224
						}}
						type="button"
					>
						{#if enableImageGeneration === true}
225
							<span class="ml-2 self-center">{$i18n.t('On')}</span>
Timothy J. Baek's avatar
Timothy J. Baek committed
226
						{:else}
227
							<span class="ml-2 self-center">{$i18n.t('Off')}</span>
Timothy J. Baek's avatar
Timothy J. Baek committed
228
229
230
231
232
						{/if}
					</button>
				</div>
			</div>
		</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
233
		<hr class=" dark:border-gray-850" />
Timothy J. Baek's avatar
Timothy J. Baek committed
234

Timothy J. Baek's avatar
Timothy J. Baek committed
235
		{#if imageGenerationEngine === ''}
236
			<div class=" mb-2.5 text-sm font-medium">{$i18n.t('AUTOMATIC1111 Base URL')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
237
238
239
			<div class="flex w-full">
				<div class="flex-1 mr-2">
					<input
Timothy J. Baek's avatar
Timothy J. Baek committed
240
						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
241
						placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
Timothy J. Baek's avatar
Timothy J. Baek committed
242
						bind:value={AUTOMATIC1111_BASE_URL}
Timothy J. Baek's avatar
Timothy J. Baek committed
243
					/>
Timothy J. Baek's avatar
Timothy J. Baek committed
244
245
				</div>
				<button
Timothy J. Baek's avatar
Timothy J. Baek committed
246
					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
247
248
					type="button"
					on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
249
						updateUrlHandler();
Timothy J. Baek's avatar
Timothy J. Baek committed
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
					}}
				>
					<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
265
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
266
267

			<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
Ased Mammad's avatar
Ased Mammad committed
268
				{$i18n.t('Include `--api` flag when running stable-diffusion-webui')}
Timothy J. Baek's avatar
Timothy J. Baek committed
269
270
271
272
				<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
273
				>
Ased Mammad's avatar
Ased Mammad committed
274
					{$i18n.t('(e.g. `sh webui.sh --api`)')}
Timothy J. Baek's avatar
Timothy J. Baek committed
275
276
				</a>
			</div>
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298

			<div class=" mb-2.5 text-sm font-medium">{$i18n.t('AUTOMATIC1111 Api Auth String')}</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 api auth string (e.g. username:password)')}
						bind:value={AUTOMATIC1111_API_AUTH}
					/>
				</div>
			</div>

			<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
				{$i18n.t('Include `--api-auth` flag when running stable-diffusion-webui')}
				<a
					class=" text-gray-300 font-medium"
					href="https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions/13993"
					target="_blank"
				>
					{$i18n.t('(e.g. `sh webui.sh --api --api-auth username:password`)')}
				</a>
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
		{: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
330
		{:else if imageGenerationEngine === 'openai'}
331
332
333
334
335
336
337
338
339
340
341
			<div>
				<div class=" mb-1.5 text-sm font-medium">{$i18n.t('OpenAI API Config')}</div>

				<div class="flex gap-2 mb-1">
					<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('API Base URL')}
						bind:value={OPENAI_API_BASE_URL}
						required
					/>

Timothy J. Baek's avatar
Timothy J. Baek committed
342
343
					<input
						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
344
						placeholder={$i18n.t('API Key')}
Timothy J. Baek's avatar
Timothy J. Baek committed
345
						bind:value={OPENAI_API_KEY}
346
						required
Timothy J. Baek's avatar
Timothy J. Baek committed
347
					/>
Timothy J. Baek's avatar
Timothy J. Baek committed
348
349
				</div>
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
350
		{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
351
352

		{#if enableImageGeneration}
Timothy J. Baek's avatar
Timothy J. Baek committed
353
			<hr class=" dark:border-gray-850" />
Timothy J. Baek's avatar
Timothy J. Baek committed
354

Timothy J. Baek's avatar
Timothy J. Baek committed
355
			<div>
356
				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Default Model')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
357
358
				<div class="flex w-full">
					<div class="flex-1 mr-2">
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
						{#if imageGenerationEngine === 'openai' && !OPENAI_API_BASE_URL.includes('https://api.openai.com')}
							<div class="flex w-full">
								<div class="flex-1">
									<input
										list="model-list"
										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
										bind:value={selectedModel}
										placeholder="Select a model"
									/>

									<datalist id="model-list">
										{#each models ?? [] as model}
											<option value={model.id}>{model.name}</option>
										{/each}
									</datalist>
								</div>
							</div>
						{:else}
							<select
								class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
								bind:value={selectedModel}
								placeholder={$i18n.t('Select a model')}
								required
							>
								{#if !selectedModel}
									<option value="" disabled selected>{$i18n.t('Select a model')}</option>
								{/if}
								{#each models ?? [] as model}
									<option value={model.id} class="bg-gray-100 dark:bg-gray-700">{model.name}</option
									>
								{/each}
							</select>
						{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
392
393
394
					</div>
				</div>
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
395
396

			<div>
397
				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Image Size')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
398
399
400
				<div class="flex w-full">
					<div class="flex-1 mr-2">
						<input
Timothy J. Baek's avatar
Timothy J. Baek committed
401
							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
402
							placeholder={$i18n.t('Enter Image Size (e.g. 512x512)')}
Timothy J. Baek's avatar
Timothy J. Baek committed
403
404
405
406
407
							bind:value={imageSize}
						/>
					</div>
				</div>
			</div>
408
409

			<div>
410
				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Steps')}</div>
411
412
413
				<div class="flex w-full">
					<div class="flex-1 mr-2">
						<input
Timothy J. Baek's avatar
Timothy J. Baek committed
414
							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
415
							placeholder={$i18n.t('Enter Number of Steps (e.g. 50)')}
416
417
418
419
420
							bind:value={steps}
						/>
					</div>
				</div>
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
421
422
423
424
425
		{/if}
	</div>

	<div class="flex justify-end pt-3 text-sm font-medium">
		<button
Timothy J. Baek's avatar
Timothy J. Baek committed
426
			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
427
428
429
430
431
				? ' cursor-not-allowed'
				: ''}"
			type="submit"
			disabled={loading}
		>
Jannik Streidl's avatar
Jannik Streidl committed
432
			{$i18n.t('Save')}
Timothy J. Baek's avatar
Timothy J. Baek committed
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463

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