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
			const res = await updateImageGenerationEngineUrls(localStorage.token, {
sihuangwlp's avatar
sihuangwlp committed
78
				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 {
Timothy J. Baek's avatar
Timothy J. Baek committed
95
96
97
				({ AUTOMATIC1111_BASE_URL, AUTOMATIC1111_API_AUTH } = await getImageGenerationEngineUrls(
					localStorage.token
				));
Timothy J. Baek's avatar
Timothy J. Baek committed
98
			}
Timothy J. Baek's avatar
Timothy J. Baek committed
99
100
		}
	};
Timothy J. Baek's avatar
Timothy J. Baek committed
101
102
103
104
105
106
107
108
109
	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
110

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

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

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

			AUTOMATIC1111_BASE_URL = URLS.AUTOMATIC1111_BASE_URL;
136
			AUTOMATIC1111_API_AUTH = URLS.AUTOMATIC1111_API_AUTH;
Timothy J. Baek's avatar
Timothy J. Baek committed
137
138
			COMFYUI_BASE_URL = URLS.COMFYUI_BASE_URL;

139
140
141
142
			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
143

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

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

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

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

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

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

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

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

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

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

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

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

			<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"
				>
Timothy J. Baek's avatar
Timothy J. Baek committed
298
					{$i18n.t('(e.g. `sh webui.sh --api --api-auth username_password`)').replace('_', ':')}
299
300
				</a>
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
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
330
331
		{: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
332
		{:else if imageGenerationEngine === 'openai'}
333
334
335
336
337
338
339
340
341
342
343
			<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
344
345
					<input
						class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
346
						placeholder={$i18n.t('API Key')}
Timothy J. Baek's avatar
Timothy J. Baek committed
347
						bind:value={OPENAI_API_KEY}
348
						required
Timothy J. Baek's avatar
Timothy J. Baek committed
349
					/>
Timothy J. Baek's avatar
Timothy J. Baek committed
350
351
				</div>
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
352
		{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
353
354

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

Timothy J. Baek's avatar
Timothy J. Baek committed
357
			<div>
358
				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Default Model')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
359
360
				<div class="flex w-full">
					<div class="flex-1 mr-2">
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
392
393
						{#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
394
395
396
					</div>
				</div>
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
397
398

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

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

	<div class="flex justify-end pt-3 text-sm font-medium">
		<button
Timothy J. Baek's avatar
Timothy J. Baek committed
428
			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
429
430
431
432
433
				? ' cursor-not-allowed'
				: ''}"
			type="submit"
			disabled={loading}
		>
Jannik Streidl's avatar
Jannik Streidl committed
434
			{$i18n.t('Save')}
Timothy J. Baek's avatar
Timothy J. Baek committed
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
464
465

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