open-webui/src/lib/components/AutomationModal.svelte
Timothy Jaeryang Baek f798d05586
Some checks are pending
Create and publish Docker images with specific build args / merge (map[name:cuda126 suffix:-cuda126]) (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge (map[name:main suffix:]) (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge (map[name:ollama suffix:-ollama]) (push) Blocked by required conditions
Create and publish Docker images with specific build args / merge (map[name:slim suffix:-slim]) (push) Blocked by required conditions
Python CI / Ruff Format (3.11) (push) Waiting to run
Python CI / Ruff Format (3.12) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args: free_disk:false name:main suffix:]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args:USE_CUDA=true USE_CUDA_VER=cu126 free_disk:true name:cuda126 suffix:-cuda126]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args:USE_CUDA=true free_disk:true name:cuda suffix:-cuda]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args:USE_OLLAMA=true free_disk:false name:ollama suffix:-ollama]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/amd64 runner:ubuntu-latest], map[build_args:USE_SLIM=true free_disk:false name:slim suffix:-slim]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args: free_disk:false name:main suffix:]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args:USE_CUDA=true USE_CUDA_VER=cu126 free_disk:true name:cuda126 suffix:-cuda126]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args:USE_CUDA=true free_disk:true name:cuda suffix:-cuda]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args:USE_OLLAMA=true free_disk:false name:ollama suffix:-ollama]) (push) Waiting to run
Create and publish Docker images with specific build args / build (map[arch:linux/arm64 runner:ubuntu-24.04-arm], map[build_args:USE_SLIM=true free_disk:false name:slim suffix:-slim]) (push) Waiting to run
Create and publish Docker images with specific build args / merge (map[name:cuda suffix:-cuda]) (push) Blocked by required conditions
Create and publish Docker images with specific build args / notify-helm-charts (push) Blocked by required conditions
Create and publish Docker images with specific build args / copy-to-dockerhub (, main) (push) Blocked by required conditions
Create and publish Docker images with specific build args / copy-to-dockerhub (-cuda, cuda) (push) Blocked by required conditions
Create and publish Docker images with specific build args / copy-to-dockerhub (-cuda126, cuda126) (push) Blocked by required conditions
Create and publish Docker images with specific build args / copy-to-dockerhub (-ollama, ollama) (push) Blocked by required conditions
Create and publish Docker images with specific build args / copy-to-dockerhub (-slim, slim) (push) Blocked by required conditions
Frontend Build / Format & Build (push) Waiting to run
Frontend Build / Unit Tests (push) Waiting to run
refac
2026-07-26 19:34:41 -04:00

192 lines
5.4 KiB
Svelte

<script lang="ts">
import { createEventDispatcher, getContext, tick } from 'svelte';
import { toast } from 'svelte-sonner';
import Modal from '$lib/components/common/Modal.svelte';
import XMark from '$lib/components/icons/XMark.svelte';
import Spinner from '$lib/components/common/Spinner.svelte';
import ScheduleDropdown from '$lib/components/automations/ScheduleDropdown.svelte';
import ModelDropdown from '$lib/components/automations/ModelDropdown.svelte';
import FolderDropdown from '$lib/components/automations/FolderDropdown.svelte';
import { getFolders } from '$lib/apis/folders';
import { folders } from '$lib/stores';
import {
createAutomation,
updateAutomationById,
type AutomationForm,
type AutomationResponse
} from '$lib/apis/automations';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher();
export let show = false;
export let automation: AutomationResponse | null = null;
export let cloneFrom: AutomationResponse | null = null;
let name = '';
let prompt = '';
let model_id = '';
let folder_id = '';
let is_active = true;
let loading = false;
let foldersLoaded = false;
// Schedule dropdown ref
let scheduleDropdown: ScheduleDropdown;
const submitHandler = async () => {
if (!name.trim() || !prompt.trim() || !model_id.trim()) {
toast.error($i18n.t('Name, prompt, and model are required'));
return;
}
if (scheduleDropdown?.frequency === 'ONCE') {
const scheduled = new Date(`${scheduleDropdown.onceDate}T${scheduleDropdown.onceTime}`);
if (scheduled <= new Date()) {
toast.error($i18n.t('Scheduled time must be in the future'));
return;
}
}
loading = true;
try {
const form: AutomationForm = {
name: name.trim(),
folder_id: folder_id || null,
data: {
prompt: prompt.trim(),
model_id: model_id.trim(),
rrule: scheduleDropdown.buildRrule()
},
is_active
};
if (automation) {
await updateAutomationById(localStorage.token, automation.id, form);
toast.success($i18n.t('Automation updated'));
show = false;
dispatch('save', { id: automation.id });
} else {
const created = await createAutomation(localStorage.token, form);
toast.success($i18n.t('Automation created'));
show = false;
dispatch('save', { id: created?.id });
}
} catch (e: any) {
toast.error(e?.detail ?? `${e}` ?? 'Failed to save');
} finally {
loading = false;
}
};
const init = async () => {
await tick();
if (!foldersLoaded && ($folders ?? []).length === 0) {
const res = await getFolders(localStorage.token).catch(() => null);
if (res) folders.set(res);
foldersLoaded = true;
}
if (automation) {
name = automation.name;
prompt = automation.data.prompt;
model_id = automation.data.model_id;
folder_id = automation.folder_id ?? '';
is_active = automation.is_active;
if (scheduleDropdown) {
scheduleDropdown.parseRrule(automation.data.rrule);
}
} else if (cloneFrom) {
name = cloneFrom.name;
prompt = cloneFrom.data.prompt;
model_id = cloneFrom.data.model_id;
folder_id = ($folders ?? []).some((folder) => folder.id === cloneFrom.folder_id)
? (cloneFrom.folder_id ?? '')
: '';
is_active = true;
if (scheduleDropdown) {
scheduleDropdown.parseRrule(cloneFrom.data.rrule);
}
} else {
name = '';
prompt = '';
model_id = '';
folder_id = '';
is_active = true;
}
};
$: if (show) {
init();
}
</script>
<Modal size="md" bind:show>
<div>
<!-- Header -->
<div class="flex justify-between dark:text-gray-100 px-4 pt-3 pb-1">
<input
class="w-full text-sm font-medium bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700"
type="text"
bind:value={name}
placeholder={$i18n.t('Automation title')}
/>
<button
class="self-center shrink-0 ml-2"
aria-label={$i18n.t('Close')}
on:click={() => (show = false)}
>
<XMark className="size-4" />
</button>
</div>
<!-- Prompt -->
<div class="px-5 pb-2">
<div class="mb-1 text-xs text-gray-500">{$i18n.t('Instructions')}</div>
<textarea
class="w-full text-sm bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700 resize-none min-h-[12rem]"
bind:value={prompt}
rows={8}
placeholder={$i18n.t('Enter prompt here.')}
/>
</div>
<!-- Bottom toolbar -->
<div
class="flex flex-col sm:flex-row sm:items-center sm:justify-between px-4 pb-3.5 pt-1 gap-2"
>
<div class="flex items-center gap-0.5 flex-wrap min-w-0 sm:flex-1">
<ScheduleDropdown bind:this={scheduleDropdown} side="top" align="start" />
<ModelDropdown bind:model_id side="top" align="start" />
<FolderDropdown bind:folder_id side="top" align="start" />
</div>
<div class="flex items-center justify-end gap-2 shrink-0">
<button
class="px-3 py-1 text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-200 transition"
type="button"
on:click={() => (show = false)}
>
{$i18n.t('Cancel')}
</button>
<button
class="px-3.5 py-1.5 text-sm font-normal bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full flex items-center gap-2 {loading
? 'cursor-not-allowed'
: ''}"
on:click={submitHandler}
type="button"
disabled={loading}
>
{automation ? $i18n.t('Save') : $i18n.t('Create')}
{#if loading}
<span class="shrink-0"><Spinner /></span>
{/if}
</button>
</div>
</div>
</div>
</Modal>