This commit is contained in:
Timothy Jaeryang Baek
2026-04-01 00:35:11 -05:00
parent f984c6e79a
commit 90319593d0
5 changed files with 228 additions and 3 deletions
+6
View File
@@ -1,9 +1,15 @@
import { WEBUI_API_BASE_URL } from '$lib/constants';
export type AutomationTerminalConfig = {
server_id: string;
cwd?: string;
};
export type AutomationData = {
prompt: string;
model_id: string;
rrule: string;
terminal?: AutomationTerminalConfig;
};
export type AutomationForm = {
+127 -3
View File
@@ -8,7 +8,7 @@
import XMark from '$lib/components/icons/XMark.svelte';
import Spinner from '$lib/components/common/Spinner.svelte';
import Search from '$lib/components/icons/Search.svelte';
import Check from '$lib/components/icons/Check.svelte';
import Cloud from '$lib/components/icons/Cloud.svelte';
import { WEBUI_API_BASE_URL } from '$lib/constants';
@@ -18,6 +18,7 @@
type AutomationForm,
type AutomationResponse
} from '$lib/apis/automations';
import { getTerminalServers, type TerminalServer } from '$lib/apis/terminal/index';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher();
@@ -42,9 +43,19 @@
let loading = false;
let showScheduleDropdown = false;
let showModelDropdown = false;
let showTerminalDropdown = false;
let modelSearch = '';
let customRrule = '';
// Terminal state
let terminalServers: TerminalServer[] = [];
let terminalServerId = '';
let terminalCwd = '';
$: terminalLabel = terminalServerId
? terminalServers.find((s) => s.id === terminalServerId)?.name || 'Terminal'
: $i18n.t('Terminal');
$: modelLabel = model_id
? $models.find((m) => m.id === model_id)?.name || model_id
: $i18n.t('Select model');
@@ -198,7 +209,15 @@
data: {
prompt: prompt.trim(),
model_id: model_id.trim(),
rrule: buildRrule()
rrule: buildRrule(),
...(terminalServerId
? {
terminal: {
server_id: terminalServerId,
...(terminalCwd.trim() ? { cwd: terminalCwd.trim() } : {})
}
}
: {})
},
is_active
};
@@ -219,13 +238,22 @@
}
};
const init = () => {
const init = async () => {
// Load terminal servers
try {
terminalServers = await getTerminalServers(localStorage.token);
} catch {
terminalServers = [];
}
if (automation) {
name = automation.name;
prompt = automation.data.prompt;
model_id = automation.data.model_id;
is_active = automation.is_active;
parseRrule(automation.data.rrule);
terminalServerId = automation.data.terminal?.server_id || '';
terminalCwd = automation.data.terminal?.cwd || '';
} else {
name = '';
prompt = '';
@@ -239,8 +267,11 @@
minute = 0;
selectedDays = [];
monthDay = 1;
terminalServerId = '';
terminalCwd = '';
}
showScheduleDropdown = false;
showTerminalDropdown = false;
};
$: if (show) {
@@ -513,6 +544,99 @@
</div>
</div>
</Dropdown>
<!-- Terminal dropdown -->
{#if terminalServers.length > 0}
<Dropdown bind:show={showTerminalDropdown} side="top" align="start">
<button
type="button"
class="flex items-center gap-1.5 px-2.5 py-1.5 rounded-2xl text-xs transition
{terminalServerId
? 'text-black dark:text-gray-100'
: 'text-gray-600 dark:text-gray-400'}
hover:bg-black/5 dark:hover:bg-white/5"
>
<Cloud className="size-3.5 shrink-0" strokeWidth="2" />
<span class="whitespace-nowrap max-w-32 truncate">{terminalLabel}</span>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
class="size-2.5"
>
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
</svg>
</button>
<div
slot="content"
class="rounded-2xl shadow-lg border border-gray-200 dark:border-gray-800 flex flex-col bg-white dark:bg-gray-850 min-w-56 max-w-56 p-1"
>
<div class="px-2 text-xs text-gray-500 py-1">
{$i18n.t('Terminal')}
</div>
{#each terminalServers as server (server.id)}
<button
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl {terminalServerId ===
server.id
? 'bg-gray-50 dark:bg-gray-800/50'
: 'hover:bg-gray-50 dark:hover:bg-gray-800/50'}"
type="button"
on:click={() => {
if (terminalServerId === server.id) {
terminalServerId = '';
terminalCwd = '';
} else {
terminalServerId = server.id;
}
showTerminalDropdown = false;
}}
>
<div class="flex flex-1 gap-2 items-center truncate">
<Cloud className="size-4 shrink-0" strokeWidth="2" />
<span class="truncate">{server.name || server.id}</span>
</div>
{#if terminalServerId === server.id}
<div class="shrink-0 text-emerald-600 dark:text-emerald-400">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="size-4"
>
<path
fill-rule="evenodd"
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
clip-rule="evenodd"
/>
</svg>
</div>
{/if}
</button>
{/each}
{#if terminalServerId}
<div class="border-t border-gray-100 dark:border-gray-800 mt-1 pt-1">
<div class="px-2.5 py-1 text-xs text-gray-500">
{$i18n.t('Working Directory')}
</div>
<div class="px-2">
<input
type="text"
bind:value={terminalCwd}
placeholder="/home/user/project"
class="w-full bg-transparent outline-hidden text-xs py-1.5 placeholder:text-gray-400 dark:placeholder:text-gray-600"
on:click={(e) => e.stopPropagation()}
/>
</div>
</div>
{/if}
</div>
</Dropdown>
{/if}
</div>
<div class="flex items-center gap-2 shrink-0">