refac: images

This commit is contained in:
Timothy Jaeryang Baek 2025-11-05 00:54:25 -05:00
parent 314cac0113
commit 72900cd686
12 changed files with 660 additions and 477 deletions

View file

@ -210,7 +210,7 @@
<div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
<div class="flex flex-col gap-3">
<div>
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Speech-to-Text')}</div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('Speech-to-Text')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
@ -498,7 +498,7 @@
</div>
<div>
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Text-to-Speech')}</div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('Text-to-Speech')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />

View file

@ -41,7 +41,7 @@
{#if config}
<div>
<div class="mb-3.5">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
@ -164,7 +164,7 @@
</div>
<div class="mb-3.5">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Code Interpreter')}</div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('Code Interpreter')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />

View file

@ -219,7 +219,7 @@
<div class=" overflow-y-scroll scrollbar-hidden h-full">
{#if ENABLE_OPENAI_API !== null && ENABLE_OLLAMA_API !== null && connectionsConfig !== null}
<div class="mb-3.5">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />

View file

@ -317,7 +317,7 @@
<div class=" space-y-2.5 overflow-y-scroll scrollbar-hidden h-full pr-1.5">
<div class="">
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
@ -914,7 +914,7 @@
{#if !RAGConfig.BYPASS_EMBEDDING_AND_RETRIEVAL}
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Embedding')}</div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('Embedding')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
@ -1089,7 +1089,7 @@
</div>
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Retrieval')}</div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('Retrieval')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
@ -1332,7 +1332,7 @@
{/if}
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Files')}</div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('Files')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
@ -1444,7 +1444,7 @@
</div>
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Integration')}</div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('Integration')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
@ -1464,7 +1464,7 @@
</div>
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Danger Zone')}</div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('Danger Zone')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />

View file

@ -104,7 +104,7 @@
{#if evaluationConfig !== null}
<div class="">
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
@ -119,7 +119,7 @@
{#if evaluationConfig.ENABLE_EVALUATION_ARENA_MODELS}
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium flex justify-between items-center">
<div class=" mt-0.5 mb-2.5 text-base font-medium flex justify-between items-center">
<div>
{$i18n.t('Manage')}
</div>

View file

@ -118,11 +118,11 @@
updateHandler();
}}
>
<div class="mt-0.5 space-y-3 overflow-y-scroll scrollbar-hidden h-full">
<div class="space-y-3 overflow-y-scroll scrollbar-hidden h-full">
{#if adminConfig !== null}
<div class="">
<div class="mb-3.5">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
@ -280,7 +280,7 @@
</div>
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Authentication')}</div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('Authentication')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
@ -637,7 +637,7 @@
</div>
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Features')}</div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('Features')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />

View file

@ -18,6 +18,7 @@
import Switch from '$lib/components/common/Switch.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Textarea from '$lib/components/common/Textarea.svelte';
import CodeEditorModal from '$lib/components/common/CodeEditorModal.svelte';
const dispatch = createEventDispatcher();
const i18n = getContext('i18n');
@ -27,6 +28,7 @@
let models = null;
let config = null;
let showComfyUIWorkflowEditor = false;
let requiredWorkflowNodes = [
{
type: 'prompt',
@ -68,24 +70,48 @@
};
const updateConfigHandler = async () => {
const res = await updateConfig(localStorage.token, config)
.catch((error) => {
toast.error(`${error}`);
if (
config.IMAGE_GENERATION_ENGINE === 'automatic1111' &&
config.AUTOMATIC1111_BASE_URL === ''
) {
toast.error($i18n.t('AUTOMATIC1111 Base URL is required.'));
config.ENABLE_IMAGE_GENERATION = false;
return null;
})
.catch((error) => {
} else if (config.IMAGE_GENERATION_ENGINE === 'comfyui' && config.COMFYUI_BASE_URL === '') {
toast.error($i18n.t('ComfyUI Base URL is required.'));
config.ENABLE_IMAGE_GENERATION = false;
return null;
} else if (config.IMAGE_GENERATION_ENGINE === 'openai' && config.OPENAI_API_KEY === '') {
toast.error($i18n.t('OpenAI API Key is required.'));
config.ENABLE_IMAGE_GENERATION = false;
return null;
} else if (config.IMAGE_GENERATION_ENGINE === 'gemini' && config.GEMINI_API_KEY === '') {
toast.error($i18n.t('Gemini API Key is required.'));
config.ENABLE_IMAGE_GENERATION = false;
return null;
}
const res = await updateConfig(localStorage.token, config).catch((error) => {
toast.error(`${error}`);
return null;
});
if (res) {
config = res;
}
if (config.enabled) {
if (config.ENABLE_IMAGE_GENERATION) {
backendConfig.set(await getBackendConfig());
getModels();
}
return config;
}
return null;
};
const validateJSON = (json) => {
@ -121,15 +147,11 @@
});
}
await updateConfig(localStorage.token, config).catch((error) => {
toast.error(`${error}`);
loading = false;
return null;
});
getModels();
const res = await updateConfigHandler();
if (res) {
dispatch('save');
}
loading = false;
};
@ -179,76 +201,49 @@
<div class=" space-y-3 overflow-y-scroll scrollbar-hidden pr-2">
{#if config}
<div>
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Create Image')}</div>
<div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('Create Image')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
<div>
<div class=" py-1 flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Image Generation (Experimental)')}
</div>
<div class="px-1">
<Switch
bind:state={config.ENABLE_IMAGE_GENERATION}
on:change={(e) => {
const enabled = e.detail;
if (enabled) {
if (
config.IMAGE_GENERATION_ENGINE === 'automatic1111' &&
config.AUTOMATIC1111_BASE_URL === ''
) {
toast.error($i18n.t('AUTOMATIC1111 Base URL is required.'));
config.ENABLE_IMAGE_GENERATION = false;
} else if (
config.IMAGE_GENERATION_ENGINE === 'comfyui' &&
config.COMFYUI_BASE_URL === ''
) {
toast.error($i18n.t('ComfyUI Base URL is required.'));
config.ENABLE_IMAGE_GENERATION = false;
} else if (
config.IMAGE_GENERATION_ENGINE === 'openai' &&
config.OPENAI_API_KEY === ''
) {
toast.error($i18n.t('OpenAI API Key is required.'));
config.ENABLE_IMAGE_GENERATION = false;
} else if (
config.IMAGE_GENERATION_ENGINE === 'gemini' &&
config.GEMINI_API_KEY === ''
) {
toast.error($i18n.t('Gemini API Key is required.'));
config.ENABLE_IMAGE_GENERATION = false;
}
}
updateConfigHandler();
}}
/>
</div>
<div class="mb-2.5">
<div class="flex w-full justify-between items-center">
<div class="text-xs pr-2">
<div class="">
{$i18n.t('Image Generation')}
</div>
</div>
<Switch bind:state={config.ENABLE_IMAGE_GENERATION} />
</div>
</div>
{#if config.ENABLE_IMAGE_GENERATION}
<div class="mb-2.5">
<div class="flex w-full justify-between items-center">
<div class="text-xs pr-2">
<div class="">
{$i18n.t('Image Prompt Generation')}
</div>
</div>
{#if config.enabled}
<div class=" py-1 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Image Prompt Generation')}</div>
<div class="px-1">
<Switch bind:state={config.ENABLE_IMAGE_PROMPT_GENERATION} />
</div>
</div>
{/if}
<div class=" py-1 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Image Generation Engine')}</div>
<div class="flex items-center relative">
<div class="mb-2.5">
<div class="flex w-full justify-between items-center">
<div class="text-xs pr-2">
<div class="">
{$i18n.t('Image Generation Engine')}
</div>
</div>
<select
class=" dark:bg-gray-900 w-fit pr-8 cursor-pointer rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
class=" dark:bg-gray-900 w-fit pr-8 cursor-pointer rounded-sm px-2 text-xs bg-transparent outline-hidden text-right"
bind:value={config.IMAGE_GENERATION_ENGINE}
placeholder={$i18n.t('Select Engine')}
on:change={async () => {
updateConfigHandler();
}}
>
<option value="openai">{$i18n.t('Default (Open AI)')}</option>
<option value="comfyui">{$i18n.t('ComfyUI')}</option>
@ -257,23 +252,158 @@
</select>
</div>
</div>
</div>
<hr class=" border-gray-100 dark:border-gray-850" />
<div class="flex flex-col gap-2">
{#if (config?.IMAGE_GENERATION_ENGINE ?? 'automatic1111') === 'automatic1111'}
<div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('AUTOMATIC1111 Base URL')}</div>
{#if config.ENABLE_IMAGE_GENERATION}
<div class="mb-2.5">
<div class="flex w-full justify-between items-center">
<div class="text-xs pr-2">
<div class="">
{$i18n.t('Default Model')}
</div>
</div>
<Tooltip content={$i18n.t('Enter Model ID')} placement="top-start">
<input
list="model-list"
class=" rounded-lg text-right text-sm bg-transparent outline-hidden"
bind:value={config.IMAGE_GENERATION_MODEL}
placeholder={$i18n.t('Select a model')}
required
/>
<datalist id="model-list">
{#each models ?? [] as model}
<option value={model.id}>{model.name}</option>
{/each}
</datalist>
</Tooltip>
</div>
</div>
<div class="mb-2.5">
<div class="flex w-full justify-between items-center">
<div class="text-xs pr-2">
<div class="">
{$i18n.t('Image Size')}
</div>
</div>
<Tooltip content={$i18n.t('Enter Image Size (e.g. 512x512)')} placement="top-start">
<input
class=" rounded-lg text-right text-sm bg-transparent outline-hidden"
placeholder={$i18n.t('Enter Image Size (e.g. 512x512)')}
bind:value={config.IMAGE_SIZE}
required
/>
</Tooltip>
</div>
</div>
{#if ['comfyui', 'automatic1111', ''].includes(config?.IMAGE_GENERATION_ENGINE)}
<div class="mb-2.5">
<div class="flex w-full justify-between items-center">
<div class="text-xs pr-2">
<div class="">
{$i18n.t('Steps')}
</div>
</div>
<Tooltip
content={$i18n.t('Enter Number of Steps (e.g. 50)')}
placement="top-start"
>
<input
class=" rounded-lg text-right text-sm bg-transparent outline-hidden"
placeholder={$i18n.t('Enter Number of Steps (e.g. 50)')}
bind:value={config.IMAGE_STEPS}
required
/>
</Tooltip>
</div>
</div>
{/if}
{/if}
{#if config?.IMAGE_GENERATION_ENGINE === 'openai'}
<div class="mb-2.5">
<div class="flex w-full justify-between items-center">
<div class="text-xs pr-2 shrink-0">
<div class="">
{$i18n.t('OpenAI API Base URL')}
</div>
</div>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full text-sm bg-transparent outline-hidden text-right"
placeholder={$i18n.t('API Base URL')}
bind:value={config.IMAGES_OPENAI_API_BASE_URL}
/>
</div>
</div>
</div>
</div>
<div class="mb-2.5">
<div class="flex w-full justify-between items-center">
<div class="text-xs pr-2 shrink-0">
<div class="">
{$i18n.t('OpenAI API Key')}
</div>
</div>
<div class="flex w-full">
<div class="flex-1">
<SensitiveInput
inputClassName="text-right w-full"
placeholder={$i18n.t('API Key')}
bind:value={config.IMAGES_OPENAI_API_KEY}
required={false}
/>
</div>
</div>
</div>
</div>
<div class="mb-2.5">
<div class="flex w-full justify-between items-center">
<div class="text-xs pr-2 shrink-0">
<div class="">
{$i18n.t('OpenAI API Version')}
</div>
</div>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full text-sm bg-transparent outline-hidden text-right"
placeholder={$i18n.t('API Version')}
bind:value={config.IMAGES_OPENAI_API_VERSION}
/>
</div>
</div>
</div>
</div>
{:else if (config?.IMAGE_GENERATION_ENGINE ?? 'automatic1111') === 'automatic1111'}
<div class="mb-2.5">
<div class="flex w-full justify-between items-center">
<div class="text-xs pr-2 shrink-0">
<div class="">
{$i18n.t('AUTOMATIC1111 Base URL')}
</div>
</div>
<div class="flex w-full">
<div class="flex-1 mr-2">
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
class="w-full text-sm bg-transparent outline-hidden text-right"
placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
bind:value={config.AUTOMATIC1111_BASE_URL}
/>
</div>
<button
class="px-2.5 bg-gray-50 hover:bg-gray-100 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
class=" rounded-lg transition"
type="button"
aria-label="verify connection"
on:click={async () => {
@ -302,8 +432,9 @@
</svg>
</button>
</div>
</div>
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
<div class="mt-1 text-xs text-gray-400 dark:text-gray-500">
{$i18n.t('Include `--api` flag when running stable-diffusion-webui')}
<a
class=" text-gray-300 font-medium"
@ -315,17 +446,27 @@
</div>
</div>
<div>
<div class=" mb-2 text-sm font-medium">
<div class="mb-2.5">
<div class="flex w-full justify-between items-center">
<div class="text-xs pr-2 shrink-0">
<div class="">
{$i18n.t('AUTOMATIC1111 Api Auth String')}
</div>
</div>
<div class="flex w-full">
<div class="flex-1">
<SensitiveInput
inputClassName="text-right w-full"
placeholder={$i18n.t('Enter api auth string (e.g. username:password)')}
bind:value={config.AUTOMATIC1111_API_AUTH}
required={false}
/>
</div>
</div>
</div>
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
<div class="mt-1 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"
@ -339,13 +480,18 @@
</div>
</div>
<div class="mb-1 text-xs text-gray-400 dark:text-gray-500">
<div class="w-full">
<div class=" mb-1.5 text-xs font-medium">{$i18n.t('Additional Parameters')}</div>
<div class="flex w-full">
<div class="flex-1">
<div class="mb-2.5">
<div class="flex w-full justify-between items-center">
<div class="text-xs pr-2 shrink-0">
<div class="">
{$i18n.t('Additional Parameters')}
</div>
</div>
</div>
<div class="mt-1.5 flex w-full">
<div class="flex-1 mr-2">
<Textarea
className="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
className="w-full rounded-lg py-2 px-3 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
bind:value={config.AUTOMATIC1111_PARAMS}
placeholder={$i18n.t('Enter additional parameters in JSON format')}
minSize={100}
@ -353,20 +499,25 @@
</div>
</div>
</div>
</div>
{:else if config?.IMAGE_GENERATION_ENGINE === 'comfyui'}
<div class="mb-2.5">
<div class="flex w-full justify-between items-center">
<div class="text-xs pr-2 shrink-0">
<div class="">
<div class=" mb-2 text-sm font-medium">{$i18n.t('ComfyUI Base URL')}</div>
{$i18n.t('ComfyUI Base URL')}
</div>
</div>
<div class="flex w-full">
<div class="flex-1 mr-2">
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
class="w-full text-sm bg-transparent outline-hidden text-right"
placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
bind:value={config.COMFYUI_BASE_URL}
/>
</div>
<button
class="px-2.5 bg-gray-50 hover:bg-gray-100 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
class=" rounded-lg transition"
type="button"
aria-label="verify connection"
on:click={async () => {
@ -396,12 +547,20 @@
</button>
</div>
</div>
</div>
<div class="mb-2.5">
<div class="flex w-full justify-between items-center">
<div class="text-xs pr-2 shrink-0">
<div class="">
<div class=" mb-2 text-sm font-medium">{$i18n.t('ComfyUI API Key')}</div>
{$i18n.t('ComfyUI API Key')}
</div>
</div>
<div class="flex w-full">
<div class="flex-1 mr-2">
<div class="flex-1">
<SensitiveInput
inputClassName="text-right w-full"
placeholder={$i18n.t('sk-1234')}
bind:value={config.COMFYUI_API_KEY}
required={false}
@ -409,21 +568,9 @@
</div>
</div>
</div>
</div>
<div class="">
<div class=" mb-2 text-sm font-medium">{$i18n.t('ComfyUI Workflow')}</div>
{#if config.COMFYUI_WORKFLOW}
<Textarea
class="w-full rounded-lg mb-1 py-2 px-4 text-xs bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden disabled:text-gray-600 resize-none"
rows="10"
bind:value={config.COMFYUI_WORKFLOW}
required
/>
{/if}
<div class="flex w-full">
<div class="flex-1">
<div class="mb-2.5">
<input
id="upload-comfyui-workflow-input"
hidden
@ -441,42 +588,93 @@
reader.readAsText(file);
}}
/>
<div class="flex w-full justify-between items-center">
<div class="text-xs pr-2 shrink-0">
<div class="">
{$i18n.t('ComfyUI Workflow')}
</div>
</div>
<div class="flex w-full">
<div class="flex-1 mr-2 justify-end flex gap-1">
{#if config.COMFYUI_WORKFLOW}
<button
class="w-full text-sm font-medium py-2 bg-transparent hover:bg-gray-50 border border-dashed border-gray-50 dark:border-gray-850 dark:hover:bg-gray-850 text-center rounded-xl"
class="text-xs text-gray-700 dark:text-gray-400 underline"
type="button"
aria-label={$i18n.t('Edit workflow.json content')}
on:click={() => {
// open code editor modal
showComfyUIWorkflowEditor = true;
}}
>
{$i18n.t('Edit')}
</button>
{/if}
<Tooltip content={$i18n.t('Click here to upload a workflow.json file.')}>
<button
class="text-xs text-gray-700 dark:text-gray-400 underline"
type="button"
aria-label={$i18n.t('Click here to upload a workflow.json file.')}
on:click={() => {
document.getElementById('upload-comfyui-workflow-input')?.click();
}}
>
{$i18n.t('Click here to upload a workflow.json file.')}
{$i18n.t('Upload')}
</button>
</Tooltip>
</div>
</div>
</div>
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
<div class="mt-1 text-xs text-gray-400 dark:text-gray-500">
<CodeEditorModal
bind:show={showComfyUIWorkflowEditor}
value={config.COMFYUI_WORKFLOW}
lang="json"
onChange={(e) => {
config.COMFYUI_WORKFLOW = e;
}}
onSave={() => {
console.log('Saved');
}}
/>
<!-- {#if config.COMFYUI_WORKFLOW}
<Textarea
class="w-full rounded-lg my-1 py-2 px-3 text-xs bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden disabled:text-gray-600 resize-none"
rows="10"
bind:value={config.COMFYUI_WORKFLOW}
required
/>
{/if} -->
{$i18n.t('Make sure to export a workflow.json file as API format from ComfyUI.')}
</div>
</div>
{#if config.COMFYUI_WORKFLOW}
<div class="mb-2.5">
<div class="flex w-full justify-between items-center">
<div class="text-xs pr-2 shrink-0">
<div class="">
<div class=" mb-2 text-sm font-medium">{$i18n.t('ComfyUI Workflow Nodes')}</div>
{$i18n.t('ComfyUI Workflow Nodes')}
</div>
</div>
</div>
<div class="text-xs flex flex-col gap-1.5">
<div class="mt-1 text-xs flex flex-col gap-1.5">
{#each requiredWorkflowNodes as node}
<div class="flex w-full items-center">
<div class="flex w-full flex-col">
<div class="shrink-0">
<div
class=" capitalize line-clamp-1 font-medium px-3 py-1 w-20 text-center bg-green-500/10 text-green-700 dark:text-green-200"
>
<div class=" capitalize line-clamp-1 w-20 text-gray-400 dark:text-gray-500">
{node.type}{node.type === 'prompt' ? '*' : ''}
</div>
</div>
<div class="flex mt-0.5 items-center">
<div class="">
<Tooltip content={$i18n.t('Input Key (e.g. text, unet_name, steps)')}>
<input
class="py-1 px-3 w-24 text-xs text-center bg-transparent outline-hidden border-r border-gray-50 dark:border-gray-850"
class="py-1 w-24 text-xs bg-transparent outline-hidden"
placeholder={$i18n.t('Key')}
bind:value={node.key}
required
@ -484,152 +682,74 @@
</Tooltip>
</div>
<div class="px-2 text-gray-400 dark:text-gray-500">:</div>
<div class="w-full">
<Tooltip
content={$i18n.t('Comma separated Node Ids (e.g. 1 or 1,2)')}
placement="top-start"
>
<input
class="w-full py-1 px-4 text-xs bg-transparent outline-hidden"
class="w-full py-1 text-xs bg-transparent outline-hidden"
placeholder={$i18n.t('Node Ids')}
bind:value={node.node_ids}
/>
</Tooltip>
</div>
</div>
</div>
{/each}
</div>
<div class="mt-2 text-xs text-right text-gray-400 dark:text-gray-500">
<div class="mt-1 text-xs text-gray-400 dark:text-gray-500">
{$i18n.t('*Prompt node ID(s) are required for image generation')}
</div>
</div>
{/if}
{:else if config?.IMAGE_GENERATION_ENGINE === 'openai'}
<div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('OpenAI API Config')}</div>
<div class="flex w-full">
<div class="flex-1 mr-2">
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
placeholder={$i18n.t('API Base URL')}
bind:value={config.IMAGES_OPENAI_API_BASE_URL}
required
/>
</div>
</div>
</div>
<div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('API Key')}</div>
<div class="flex w-full">
<div class="flex-1 mr-2">
<SensitiveInput
placeholder={$i18n.t('API Key')}
bind:value={config.IMAGES_OPENAI_API_KEY}
required
/>
</div>
</div>
</div>
<div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('API Version')}</div>
<div class="flex w-full">
<div class="flex-1 mr-2">
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
placeholder={$i18n.t('API Version')}
bind:value={config.IMAGES_OPENAI_API_VERSION}
/>
</div>
</div>
</div>
{:else if config?.IMAGE_GENERATION_ENGINE === 'gemini'}
<div>
<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Gemini API Config')}</div>
<div class="flex gap-2 mb-1">
<input
class="flex-1 w-full text-sm bg-transparent outline-none"
placeholder={$i18n.t('API Base URL')}
bind:value={config.IMAGES_GEMINI_API_BASE_URL}
required
/>
<SensitiveInput
placeholder={$i18n.t('API Key')}
bind:value={config.IMAGES_GEMINI_API_KEY}
/>
<div class="mb-2.5">
<div class="flex w-full justify-between items-center">
<div class="text-xs pr-2 shrink-0">
<div class="">
{$i18n.t('Gemini Base URL')}
</div>
</div>
{/if}
</div>
{#if config?.ENABLE_IMAGE_GENERATION}
<hr class=" border-gray-100 dark:border-gray-850" />
<div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Default Model')}</div>
<div class="flex w-full">
<div class="flex-1 mr-2">
<div class="flex w-full">
<div class="flex-1">
<Tooltip content={$i18n.t('Enter Model ID')} placement="top-start">
<input
list="model-list"
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
bind:value={config.IMAGE_GENERATION_MODEL}
placeholder={$i18n.t('Select a model')}
required
class="w-full text-sm bg-transparent outline-hidden text-right"
placeholder={$i18n.t('API Base URL')}
bind:value={config.IMAGES_GEMINI_API_BASE_URL}
/>
<datalist id="model-list">
{#each models ?? [] as model}
<option value={model.id}>{model.name}</option>
{/each}
</datalist>
</Tooltip>
</div>
</div>
</div>
</div>
</div>
<div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Image Size')}</div>
<div class="mb-2.5">
<div class="flex w-full justify-between items-center">
<div class="text-xs pr-2 shrink-0">
<div class="">
{$i18n.t('Gemini API Key')}
</div>
</div>
<div class="flex w-full">
<div class="flex-1 mr-2">
<Tooltip content={$i18n.t('Enter Image Size (e.g. 512x512)')} placement="top-start">
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
placeholder={$i18n.t('Enter Image Size (e.g. 512x512)')}
bind:value={config.IMAGE_SIZE}
required
<div class="flex-1">
<SensitiveInput
inputClassName="text-right w-full"
placeholder={$i18n.t('API Key')}
bind:value={config.IMAGES_GEMINI_API_KEY}
required={true}
/>
</Tooltip>
</div>
</div>
</div>
{#if ['comfyui', 'automatic1111', ''].includes(config?.IMAGE_GENERATION_ENGINE)}
<div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Steps')}</div>
<div class="flex w-full">
<div class="flex-1 mr-2">
<Tooltip content={$i18n.t('Enter Number of Steps (e.g. 50)')} placement="top-start">
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
placeholder={$i18n.t('Enter Number of Steps (e.g. 50)')}
bind:value={config.IMAGE_STEPS}
required
/>
</Tooltip>
</div>
</div>
</div>
{/if}
{/if}
</div>
</div>
{/if}
</div>

View file

@ -111,7 +111,7 @@
>
<div class=" overflow-y-scroll scrollbar-hidden h-full pr-1.5">
<div class="mb-3.5">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Tasks')}</div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('Tasks')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
@ -384,7 +384,7 @@
</div>
<div class="mb-3.5">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('UI')}</div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('UI')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />

View file

@ -59,7 +59,7 @@
{#if servers !== null}
<div class="">
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />

View file

@ -95,7 +95,7 @@
{#if webConfig}
<div class="">
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
@ -724,7 +724,7 @@
</div>
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Loader')}</div>
<div class=" mt-0.5 mb-2.5 text-base font-medium">{$i18n.t('Loader')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />

View file

@ -0,0 +1,65 @@
<script lang="ts">
import { onMount, getContext } from 'svelte';
import CodeEditor from './CodeEditor.svelte';
import Drawer from './Drawer.svelte';
const i18n = getContext('i18n');
let {
show = $bindable(),
value = $bindable(),
lang = 'python',
onChange = () => {},
onSave = () => {}
} = $props();
let boilerplate = ``;
let codeEditor = $state(null);
let _content = $state(value);
$effect(() => {
if (_content) {
value = _content;
}
});
</script>
<Drawer bind:show>
<div class="flex h-full flex-col">
<div
class=" sticky top-0 z-30 flex justify-between bg-white px-4.5 pt-3 pb-3 dark:bg-gray-900 dark:text-gray-100"
>
<div class=" font-primary self-center text-lg font-medium">
{$i18n.t('Code Editor')}
</div>
<button
class="self-center"
aria-label="Close"
onclick={() => {
show = false;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="h-5 w-5"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
<div
class="flex h-full w-full flex-1 flex-col md:flex-row md:space-x-4 dark:text-gray-200 overflow-y-auto"
>
<div class=" flex h-full w-full flex-col sm:flex-row sm:justify-center sm:space-x-6">
<CodeEditor bind:this={codeEditor} {value} {boilerplate} {lang} {onChange} {onSave} />
</div>
</div>
</div>
</Drawer>

View file

@ -2,7 +2,6 @@
import { onDestroy, onMount } from 'svelte';
import { flyAndScale } from '$lib/utils/transitions';
import { fade, fly, slide } from 'svelte/transition';
import { isApp } from '$lib/stores';
export let show = false;
export let className = '';
@ -54,26 +53,25 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
{#if show}
<div
bind:this={modalElement}
class="modal fixed right-0 {$isApp
? ' ml-[4.5rem] max-w-[calc(100%-4.5rem)]'
: ''} left-0 bottom-0 bg-black/60 w-full h-screen max-h-[100dvh] flex justify-center z-999 overflow-hidden overscroll-contain"
class="modal fixed right-0 bottom-0 left-0 z-999 flex h-screen max-h-[100dvh] w-full justify-center overflow-hidden overscroll-contain bg-black/60"
in:fly={{ y: 100, duration: 100 }}
on:mousedown={() => {
show = false;
}}
>
>
<div
class=" mt-auto w-full bg-gray-50 dark:bg-gray-900 dark:text-gray-100 {className} max-h-[100dvh] overflow-y-auto scrollbar-hidden"
class=" mt-auto w-full bg-gray-50 dark:bg-gray-900 dark:text-gray-100 {className} scrollbar-hidden max-h-[100dvh] overflow-y-auto"
on:mousedown={(e) => {
e.stopPropagation();
}}
>
<slot />
</div>
</div>
</div>
{/if}
<style>
.modal-content {