Images.svelte 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. <script lang="ts">
  2. import { toast } from 'svelte-sonner';
  3. import { createEventDispatcher, onMount } from 'svelte';
  4. import { config, user } from '$lib/stores';
  5. import {
  6. getAUTOMATIC1111Url,
  7. getImageGenerationModels,
  8. getDefaultImageGenerationModel,
  9. updateDefaultImageGenerationModel,
  10. getImageSize,
  11. getImageGenerationConfig,
  12. updateImageGenerationConfig,
  13. updateAUTOMATIC1111Url,
  14. updateImageSize,
  15. getImageSteps,
  16. updateImageSteps,
  17. getOpenAIKey,
  18. updateOpenAIKey
  19. } from '$lib/apis/images';
  20. import { getBackendConfig } from '$lib/apis';
  21. const dispatch = createEventDispatcher();
  22. export let saveSettings: Function;
  23. let loading = false;
  24. let imageGenerationEngine = '';
  25. let enableImageGeneration = false;
  26. let AUTOMATIC1111_BASE_URL = '';
  27. let OPENAI_API_KEY = '';
  28. let selectedModel = '';
  29. let models = null;
  30. let imageSize = '';
  31. let steps = 50;
  32. const getModels = async () => {
  33. models = await getImageGenerationModels(localStorage.token).catch((error) => {
  34. toast.error(error);
  35. return null;
  36. });
  37. selectedModel = await getDefaultImageGenerationModel(localStorage.token).catch((error) => {
  38. return '';
  39. });
  40. };
  41. const updateAUTOMATIC1111UrlHandler = async () => {
  42. const res = await updateAUTOMATIC1111Url(localStorage.token, AUTOMATIC1111_BASE_URL).catch(
  43. (error) => {
  44. toast.error(error);
  45. return null;
  46. }
  47. );
  48. if (res) {
  49. AUTOMATIC1111_BASE_URL = res;
  50. await getModels();
  51. if (models) {
  52. toast.success('Server connection verified');
  53. }
  54. } else {
  55. AUTOMATIC1111_BASE_URL = await getAUTOMATIC1111Url(localStorage.token);
  56. }
  57. };
  58. const updateImageGeneration = async () => {
  59. const res = await updateImageGenerationConfig(
  60. localStorage.token,
  61. imageGenerationEngine,
  62. enableImageGeneration
  63. ).catch((error) => {
  64. toast.error(error);
  65. return null;
  66. });
  67. if (res) {
  68. imageGenerationEngine = res.engine;
  69. enableImageGeneration = res.enabled;
  70. }
  71. if (enableImageGeneration) {
  72. config.set(await getBackendConfig(localStorage.token));
  73. getModels();
  74. }
  75. };
  76. onMount(async () => {
  77. if ($user.role === 'admin') {
  78. const res = await getImageGenerationConfig(localStorage.token).catch((error) => {
  79. toast.error(error);
  80. return null;
  81. });
  82. if (res) {
  83. imageGenerationEngine = res.engine;
  84. enableImageGeneration = res.enabled;
  85. }
  86. AUTOMATIC1111_BASE_URL = await getAUTOMATIC1111Url(localStorage.token);
  87. OPENAI_API_KEY = await getOpenAIKey(localStorage.token);
  88. imageSize = await getImageSize(localStorage.token);
  89. steps = await getImageSteps(localStorage.token);
  90. if (enableImageGeneration) {
  91. getModels();
  92. }
  93. }
  94. });
  95. </script>
  96. <form
  97. class="flex flex-col h-full justify-between space-y-3 text-sm"
  98. on:submit|preventDefault={async () => {
  99. loading = true;
  100. await updateOpenAIKey(localStorage.token, OPENAI_API_KEY);
  101. await updateDefaultImageGenerationModel(localStorage.token, selectedModel);
  102. await updateDefaultImageGenerationModel(localStorage.token, selectedModel);
  103. await updateImageSize(localStorage.token, imageSize).catch((error) => {
  104. toast.error(error);
  105. return null;
  106. });
  107. await updateImageSteps(localStorage.token, steps).catch((error) => {
  108. toast.error(error);
  109. return null;
  110. });
  111. dispatch('save');
  112. loading = false;
  113. }}
  114. >
  115. <div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[20.5rem]">
  116. <div>
  117. <div class=" mb-1 text-sm font-medium">Image Settings</div>
  118. <div class=" py-0.5 flex w-full justify-between">
  119. <div class=" self-center text-xs font-medium">Image Generation Engine</div>
  120. <div class="flex items-center relative">
  121. <select
  122. class="w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
  123. bind:value={imageGenerationEngine}
  124. placeholder="Select a mode"
  125. on:change={async () => {
  126. await updateImageGeneration();
  127. }}
  128. >
  129. <option value="">Default (Automatic1111)</option>
  130. <option value="openai">Open AI (Dall-E)</option>
  131. </select>
  132. </div>
  133. </div>
  134. <div>
  135. <div class=" py-0.5 flex w-full justify-between">
  136. <div class=" self-center text-xs font-medium">Image Generation (Experimental)</div>
  137. <button
  138. class="p-1 px-3 text-xs flex rounded transition"
  139. on:click={() => {
  140. if (imageGenerationEngine === '' && AUTOMATIC1111_BASE_URL === '') {
  141. toast.error('AUTOMATIC1111 Base URL is required.');
  142. enableImageGeneration = false;
  143. } else if (imageGenerationEngine === 'openai' && OPENAI_API_KEY === '') {
  144. toast.error('OpenAI API Key is required.');
  145. enableImageGeneration = false;
  146. } else {
  147. enableImageGeneration = !enableImageGeneration;
  148. }
  149. updateImageGeneration();
  150. }}
  151. type="button"
  152. >
  153. {#if enableImageGeneration === true}
  154. <span class="ml-2 self-center">On</span>
  155. {:else}
  156. <span class="ml-2 self-center">Off</span>
  157. {/if}
  158. </button>
  159. </div>
  160. </div>
  161. </div>
  162. <hr class=" dark:border-gray-700" />
  163. {#if imageGenerationEngine === ''}
  164. <div class=" mb-2.5 text-sm font-medium">AUTOMATIC1111 Base URL</div>
  165. <div class="flex w-full">
  166. <div class="flex-1 mr-2">
  167. <input
  168. class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
  169. placeholder="Enter URL (e.g. http://127.0.0.1:7860/)"
  170. bind:value={AUTOMATIC1111_BASE_URL}
  171. />
  172. </div>
  173. <button
  174. class="px-3 bg-gray-200 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-700 rounded-lg transition"
  175. type="button"
  176. on:click={() => {
  177. // updateOllamaAPIUrlHandler();
  178. updateAUTOMATIC1111UrlHandler();
  179. }}
  180. >
  181. <svg
  182. xmlns="http://www.w3.org/2000/svg"
  183. viewBox="0 0 20 20"
  184. fill="currentColor"
  185. class="w-4 h-4"
  186. >
  187. <path
  188. fill-rule="evenodd"
  189. 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"
  190. clip-rule="evenodd"
  191. />
  192. </svg>
  193. </button>
  194. </div>
  195. <div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
  196. Include `--api` flag when running stable-diffusion-webui
  197. <a
  198. class=" text-gray-300 font-medium"
  199. href="https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions/3734"
  200. target="_blank"
  201. >
  202. (e.g. `sh webui.sh --api`)
  203. </a>
  204. </div>
  205. {:else if imageGenerationEngine === 'openai'}
  206. <div class=" mb-2.5 text-sm font-medium">OpenAI API Key</div>
  207. <div class="flex w-full">
  208. <div class="flex-1 mr-2">
  209. <input
  210. class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
  211. placeholder="Enter API Key"
  212. bind:value={OPENAI_API_KEY}
  213. />
  214. </div>
  215. </div>
  216. {/if}
  217. {#if enableImageGeneration}
  218. <hr class=" dark:border-gray-700" />
  219. <div>
  220. <div class=" mb-2.5 text-sm font-medium">Set Default Model</div>
  221. <div class="flex w-full">
  222. <div class="flex-1 mr-2">
  223. <select
  224. class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
  225. bind:value={selectedModel}
  226. placeholder="Select a model"
  227. >
  228. {#if !selectedModel}
  229. <option value="" disabled selected>Select a model</option>
  230. {/if}
  231. {#each models ?? [] as model}
  232. <option value={model.id} class="bg-gray-100 dark:bg-gray-700">{model.name}</option>
  233. {/each}
  234. </select>
  235. </div>
  236. </div>
  237. </div>
  238. <div>
  239. <div class=" mb-2.5 text-sm font-medium">Set Image Size</div>
  240. <div class="flex w-full">
  241. <div class="flex-1 mr-2">
  242. <input
  243. class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
  244. placeholder="Enter Image Size (e.g. 512x512)"
  245. bind:value={imageSize}
  246. />
  247. </div>
  248. </div>
  249. </div>
  250. <div>
  251. <div class=" mb-2.5 text-sm font-medium">Set Steps</div>
  252. <div class="flex w-full">
  253. <div class="flex-1 mr-2">
  254. <input
  255. class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
  256. placeholder="Enter Number of Steps (e.g. 50)"
  257. bind:value={steps}
  258. />
  259. </div>
  260. </div>
  261. </div>
  262. {/if}
  263. </div>
  264. <div class="flex justify-end pt-3 text-sm font-medium">
  265. <button
  266. class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded flex flex-row space-x-1 items-center {loading
  267. ? ' cursor-not-allowed'
  268. : ''}"
  269. type="submit"
  270. disabled={loading}
  271. >
  272. Save
  273. {#if loading}
  274. <div class="ml-2 self-center">
  275. <svg
  276. class=" w-4 h-4"
  277. viewBox="0 0 24 24"
  278. fill="currentColor"
  279. xmlns="http://www.w3.org/2000/svg"
  280. ><style>
  281. .spinner_ajPY {
  282. transform-origin: center;
  283. animation: spinner_AtaB 0.75s infinite linear;
  284. }
  285. @keyframes spinner_AtaB {
  286. 100% {
  287. transform: rotate(360deg);
  288. }
  289. }
  290. </style><path
  291. 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"
  292. opacity=".25"
  293. /><path
  294. 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"
  295. class="spinner_ajPY"
  296. /></svg
  297. >
  298. </div>
  299. {/if}
  300. </button>
  301. </div>
  302. </form>