This commit is contained in:
Timothy Jaeryang Baek 2026-07-15 16:51:08 -04:00
parent 5b934b1f90
commit db934a3b4f
22 changed files with 412 additions and 527 deletions

View file

@ -59,6 +59,11 @@
let loading = false;
let showDeleteConfirmDialog = false;
const highContrastInputClass =
'rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
const highContrastSelectClass =
'rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500';
const verifyOllamaHandler = async () => {
// remove trailing slash from url
url = url.replace(/\/$/, '');
@ -313,7 +318,7 @@
<div class="flex-1">
<input
id="url-input"
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full text-sm ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={url}
placeholder={$i18n.t('API Base URL')}
@ -383,7 +388,7 @@
<div class="flex-shrink-0 self-start">
<select
id="select-bearer-or-session"
class={`dark:bg-gray-900 w-full text-sm bg-transparent pr-5 ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full text-sm pr-5 ${($settings?.highContrastMode ?? false) ? highContrastSelectClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700 dark:bg-gray-900'}`}
bind:value={auth_type}
>
<option value="none">{$i18n.t('None')}</option>
@ -481,7 +486,7 @@
)}
>
<input
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full text-sm ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
id="prefix-id-input"
bind:value={prefixId}
@ -529,7 +534,7 @@
<div class="flex-1">
<input
id="api-version-input"
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full text-sm ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={apiVersion}
placeholder={$i18n.t('API Version')}
@ -639,11 +644,7 @@
<div class="flex items-center">
<label class="sr-only" for="add-model-id-input">{$i18n.t('Add a model ID')}</label>
<input
class="w-full py-1 text-sm rounded-lg bg-transparent {modelId
? ''
: 'text-gray-500'} {($settings?.highContrastMode ?? false)
? 'dark:placeholder:text-gray-100 placeholder:text-gray-700'
: 'placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden'}"
class={`w-full text-sm ${($settings?.highContrastMode ?? false) ? highContrastInputClass : `rounded-lg bg-transparent py-1 placeholder:text-gray-300 dark:placeholder:text-gray-700 outline-hidden ${modelId ? '' : 'text-gray-500'}`}`}
bind:value={modelId}
id="add-model-id-input"
placeholder={$i18n.t('Add a model ID')}

View file

@ -61,6 +61,11 @@
let loadingPolicy = false;
let policyLoadError = '';
const highContrastInputClass =
'rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
const highContrastSelectClass =
'rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500';
const stringifyJson = (value: object | null | undefined) => {
return JSON.stringify(value && Object.keys(value).length ? value : {}, null, 2);
};
@ -404,7 +409,7 @@
<div class="flex flex-1 items-center">
<input
id="terminal-name"
class={`w-full flex-1 text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full flex-1 text-sm ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={name}
placeholder={$i18n.t('My Terminal')}
@ -425,7 +430,7 @@
<div class="flex flex-1 items-center">
<input
id="terminal-id"
class={`w-full flex-1 text-sm bg-transparent font-mono ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full flex-1 text-sm font-mono ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={id}
placeholder="auto"
@ -449,7 +454,7 @@
<div class="flex flex-1 items-center">
<input
id="terminal-url"
class={`w-full flex-1 text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full flex-1 text-sm ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={url}
placeholder="http://localhost:9900"
@ -523,7 +528,7 @@
<div class="flex flex-1 items-center">
<input
id="policy-id"
class={`w-full flex-1 text-sm bg-transparent font-mono ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full flex-1 text-sm font-mono ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={policyId}
placeholder="python-ds"
@ -554,7 +559,7 @@
<div class="flex flex-1 items-center">
<input
id="policy-image"
class={`w-full flex-1 text-sm bg-transparent font-mono ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full flex-1 text-sm font-mono ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={policyImage}
placeholder="ghcr.io/open-webui/open-terminal:latest"
@ -576,7 +581,7 @@
<div class="flex flex-1 items-center">
<input
id="policy-cpu"
class={`w-full flex-1 text-sm bg-transparent font-mono ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full flex-1 text-sm font-mono ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={policyCpu}
placeholder="1"
@ -595,7 +600,7 @@
<div class="flex flex-1 items-center">
<input
id="policy-memory"
class={`w-full flex-1 text-sm bg-transparent font-mono ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full flex-1 text-sm font-mono ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={policyMemory}
placeholder="1Gi"
@ -617,7 +622,7 @@
<div class="flex gap-2">
<div class="flex-shrink-0 self-start">
<select
class={`dark:bg-gray-900 w-full text-sm bg-transparent pr-5 ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full text-sm pr-5 ${($settings?.highContrastMode ?? false) ? highContrastSelectClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700 dark:bg-gray-900'}`}
bind:value={policyStorage}
>
<option value="ephemeral">{$i18n.t('Ephemeral')}</option>
@ -628,7 +633,7 @@
<div class="flex flex-1 items-center">
<input
id="policy-storage-size"
class={`w-full flex-1 text-sm bg-transparent font-mono ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full flex-1 text-sm font-mono ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={policyStorageSize}
placeholder="5Gi"
@ -651,7 +656,7 @@
<div class="flex flex-1 items-center">
<input
id="idle-timeout"
class={`w-full flex-1 text-sm bg-transparent font-mono ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full flex-1 text-sm font-mono ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="number"
min="0"
bind:value={policyIdleTimeout}
@ -683,13 +688,13 @@
{#each policyEnvPairs as pair, idx}
<div class="flex gap-1.5 mb-1">
<input
class={`flex-1 text-sm bg-transparent font-mono ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`flex-1 text-sm font-mono ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={pair.key}
placeholder="KEY"
/>
<input
class={`flex-[2] text-sm bg-transparent font-mono ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`flex-[2] text-sm font-mono ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={pair.value}
placeholder="value"
@ -718,7 +723,7 @@
</div>
<textarea
id="lifecycle-json"
class={`w-full min-h-24 resize-y text-xs bg-transparent font-mono rounded-lg p-2 border border-gray-200 dark:border-gray-800 ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full min-h-24 resize-y text-xs font-mono ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'rounded-lg border border-gray-200 bg-transparent p-2 outline-hidden placeholder:text-gray-300 dark:border-gray-800 dark:placeholder:text-gray-700'}`}
bind:value={lifecycleJson}
spellcheck="false"
placeholder={`{\n "reset": {\n "schedule": "@weekly",\n "timezone": "UTC"\n }\n}`}
@ -804,7 +809,7 @@
>{$i18n.t('openapi.json URL or Path')}</label
>
<input
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full text-sm ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
id="openapi-path"
bind:value={path}
@ -844,7 +849,7 @@
<div class="flex gap-2">
<div class="flex-shrink-0 self-start">
<select
class={`dark:bg-gray-900 w-full text-sm bg-transparent pr-5 ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full text-sm pr-5 ${($settings?.highContrastMode ?? false) ? highContrastSelectClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700 dark:bg-gray-900'}`}
bind:value={auth_type}
>
<option value="none">{$i18n.t('None')}</option>

View file

@ -70,6 +70,11 @@
let showAccessControlModal = false;
let showDeleteConfirmDialog = false;
const highContrastInputClass =
'rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
const highContrastSelectClass =
'rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500';
const registerOAuthClientHandler = async () => {
if (url === '') {
toast.error($i18n.t('Please enter a valid URL'));
@ -543,7 +548,7 @@
<div class="flex flex-1 items-center">
<input
id="enter-name"
class={`w-full flex-1 text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full flex-1 text-sm ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={name}
placeholder={$i18n.t('Enter name')}
@ -565,7 +570,7 @@
<div class="flex flex-1 items-center">
<input
id="enter-id"
class={`w-full flex-1 text-sm bg-transparent font-mono ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full flex-1 text-sm font-mono ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={id}
placeholder="auto"
@ -587,7 +592,7 @@
<div class="flex-1">
<input
id="description"
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full text-sm ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={description}
placeholder={$i18n.t('Enter description')}
@ -609,7 +614,7 @@
<div class="flex flex-1 items-center">
<input
id="api-base-url"
class={`w-full flex-1 text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full flex-1 text-sm ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={url}
placeholder={$i18n.t('API Base URL')}
@ -705,7 +710,7 @@
<div class="flex-shrink-0 self-start">
<select
id="select-bearer-or-session"
class={`dark:bg-gray-900 w-full text-sm bg-transparent pr-5 ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full text-sm pr-5 ${($settings?.highContrastMode ?? false) ? highContrastSelectClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700 dark:bg-gray-900'}`}
bind:value={auth_type}
>
<option value="none">{$i18n.t('None')}</option>
@ -768,7 +773,7 @@
/>
<div class="flex flex-1 items-center">
<input
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full text-sm ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={oauthServerUrl}
placeholder={$i18n.t('OAuth Server URL')}
@ -832,7 +837,7 @@
<div class="flex-shrink-0 self-start">
<select
id="select-bearer-or-session"
class={`dark:bg-gray-900 w-full text-sm bg-transparent pr-5 ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full text-sm pr-5 ${($settings?.highContrastMode ?? false) ? highContrastSelectClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700 dark:bg-gray-900'}`}
bind:value={spec_type}
>
<option value="url">{$i18n.t('URL')}</option>
@ -847,7 +852,7 @@
>{$i18n.t('openapi.json URL or Path')}</label
>
<input
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full text-sm ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
id="url-or-path"
bind:value={path}
@ -862,7 +867,7 @@
>
<label for="url-or-path" class="sr-only">{$i18n.t('JSON Spec')}</label>
<textarea
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700 text-black dark:text-white'}`}
class={`w-full text-sm ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700 text-black dark:text-white'}`}
bind:value={spec}
placeholder={$i18n.t('JSON Spec')}
autocomplete="off"
@ -901,7 +906,7 @@
<div class="flex flex-1 items-center">
<input
id="oauth-scope"
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full text-sm ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={oauthScope}
placeholder={$i18n.t('Use discovered scopes')}
@ -922,7 +927,7 @@
<div class="flex flex-1 items-center">
<select
id="oauth-resource-parameter"
class={`dark:bg-gray-900 w-full text-sm bg-transparent pr-5 ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full text-sm pr-5 ${($settings?.highContrastMode ?? false) ? highContrastSelectClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700 dark:bg-gray-900'}`}
bind:value={oauthResourceParameter}
>
<option value="auto">{$i18n.t('Automatic')}</option>
@ -970,14 +975,14 @@
<div class="flex flex-col w-full mt-2">
<label
for="function-name-filter-list"
class={`mb-1 text-xs ${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100 placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700 text-gray-500'}`}
class={`mb-1 text-xs ${($settings?.highContrastMode ?? false) ? 'text-gray-800 dark:text-gray-100' : 'text-gray-500'}`}
>{$i18n.t('Function Name Filter List')}</label
>
<div class="flex-1">
<input
id="function-name-filter-list"
class={`w-full text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full text-sm ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={functionNameFilterList}
placeholder={$i18n.t('Enter function name filter list (e.g. func1, !func2)')}

View file

@ -19,7 +19,6 @@
toggleGlobalById
} from '$lib/apis/functions';
import Download from '../icons/Download.svelte';
import Tooltip from '../common/Tooltip.svelte';
import ConfirmDialog from '../common/ConfirmDialog.svelte';
import { getModels } from '$lib/apis';
@ -32,15 +31,14 @@
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
import GarbageBin from '../icons/GarbageBin.svelte';
import Search from '../icons/Search.svelte';
import Plus from '../icons/Plus.svelte';
import XMark from '../icons/XMark.svelte';
import AddFunctionMenu from './Functions/AddFunctionMenu.svelte';
import ImportModal from '../ImportModal.svelte';
import ViewSelector from '../workspace/common/ViewSelector.svelte';
import TagSelector from '../workspace/common/TagSelector.svelte';
import CommunityDiscover from '../workspace/common/CommunityDiscover.svelte';
import { capitalizeFirstLetter } from '$lib/utils';
import Spinner from '../common/Spinner.svelte';
import SplitCreateButton from '../common/SplitCreateButton.svelte';
const i18n = getContext('i18n');
@ -78,6 +76,20 @@
}, 300);
};
const downloadFunctions = async () => {
const _functions = await exportFunctions(localStorage.token).catch((error) => {
toast.error(`${error}`);
return null;
});
if (_functions) {
let blob = new Blob([JSON.stringify(_functions)], {
type: 'application/json'
});
saveAs(blob, `functions-export-${Date.now()}.json`);
}
};
$: if (functions && selectedType !== undefined && viewOption !== undefined) {
setFilteredItems();
}
@ -269,7 +281,7 @@
{#if loaded}
<div class="px-4.5 w-full">
<div class="flex flex-col gap-1 px-1 mt-2.5 mb-2">
<div class="flex flex-col gap-1 px-1 mt-0.5 mb-1">
<div class="flex justify-between items-center mb-1 w-full">
<input
id="documents-import-input"
@ -285,68 +297,45 @@
/>
<div class="flex justify-between items-center w-full">
<div class="flex items-center md:self-center text-xl font-normal px-0.5 gap-2 shrink-0">
<div class="flex items-center md:self-center text-sm font-normal px-0.5 gap-1.5 shrink-0">
<div>
{$i18n.t('Functions')}
</div>
<div class="text-lg font-normal text-gray-500 dark:text-gray-500">
<div class="text-sm font-normal text-gray-500 dark:text-gray-500 opacity-60">
{filteredItems.length}
</div>
</div>
<div class="flex w-full justify-end gap-1.5">
{#if $user?.role === 'admin'}
<button
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-200 transition"
on:click={() => {
functionsImportInputElement.click();
}}
>
<div class=" self-center font-normal line-clamp-1">
{$i18n.t('Import')}
</div>
</button>
{#if functions.length}
<button
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-200 transition"
on:click={async () => {
const _functions = await exportFunctions(localStorage.token).catch((error) => {
toast.error(`${error}`);
return null;
});
if (_functions) {
let blob = new Blob([JSON.stringify(_functions)], {
type: 'application/json'
});
saveAs(blob, `functions-export-${Date.now()}.json`);
}
}}
>
<div class=" self-center font-normal line-clamp-1">
{$i18n.t('Export')}
</div>
</button>
{/if}
{/if}
<AddFunctionMenu
createHandler={() => {
goto('/admin/functions/create');
}}
importFromLinkHandler={() => {
showImportModal = true;
}}
>
<div
class="cursor-pointer px-2 py-1.5 rounded-xl bg-black text-white dark:bg-white dark:text-black transition font-normal text-sm flex items-center"
>
<Plus className="size-3" strokeWidth="2.5" />
<div class=" hidden md:block md:ml-1 text-xs">{$i18n.t('New Function')}</div>
</div>
</AddFunctionMenu>
<div class="flex w-full justify-end">
<SplitCreateButton
actions={[
{
id: 'functions-new',
label: $i18n.t('Create'),
href: '/admin/functions/create'
},
{
id: 'functions-import-link',
label: $i18n.t('Import From Link'),
onClick: () => {
showImportModal = true;
}
},
{
id: 'functions-import',
label: $i18n.t('Import JSON'),
onClick: () => functionsImportInputElement?.click(),
visible: $user?.role === 'admin'
},
{
id: 'functions-export',
label: $i18n.t('Export JSON'),
onClick: downloadFunctions,
visible: $user?.role === 'admin' && functions.length > 0
}
]}
/>
</div>
</div>
</div>

View file

@ -51,6 +51,9 @@
events: ['*'] as string[]
};
const highContrastInputClass =
'rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
$: events = eventItems.map((item) => item.event);
$: eventDetails = Object.fromEntries(eventItems.map((item) => [item.event, item]));
$: eventFilter = pattern.trim().toLowerCase().replace(/\*$/, '');
@ -417,7 +420,7 @@
>
<input
id="event-webhook-name"
class={`w-full text-xs bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full text-xs ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
placeholder={$i18n.t('Identity audit')}
autocomplete="off"
@ -436,7 +439,7 @@
<div class="flex items-center gap-2">
<input
id="event-webhook-url"
class={`w-full text-xs bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full text-xs ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="url"
placeholder="https://example.com/events"
autocomplete="off"
@ -521,7 +524,7 @@
<div class="relative">
<input
class={`w-full text-xs bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full text-xs ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
placeholder={$i18n.t('Search users or groups')}
autocomplete="off"
@ -607,7 +610,7 @@
<div class="flex gap-2">
<input
class={`w-full flex-1 text-xs bg-transparent font-mono ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full flex-1 text-xs font-mono ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
placeholder={$i18n.t('Search or add pattern')}
autocomplete="off"

View file

@ -14,6 +14,9 @@
let url = '';
const highContrastInputClass =
'rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
const submitHandler = () => {
let urls = url
.split('\n')
@ -68,7 +71,7 @@
<textarea
id="webpage-url"
class={`w-full flex-1 text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full flex-1 text-sm ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
type="text"
bind:value={url}
rows="3"

View file

@ -19,6 +19,9 @@
export let config = {};
let showConfigModal = false;
const highContrastInputClass =
'rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
</script>
<AddConnectionModal
@ -58,7 +61,7 @@
<div class="flex w-full gap-2">
<div class="flex-1 relative">
<input
class={`w-full bg-transparent ${($settings?.highContrastMode ?? false) ? '' : 'outline-hidden'} ${pipeline ? 'pr-8' : ''}`}
class={`w-full ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden'} ${pipeline ? 'pr-8' : ''}`}
placeholder={$i18n.t('API Base URL')}
bind:value={url}
autocomplete="off"

View file

@ -28,6 +28,8 @@
'w-full resize-vertical rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
const compactSystemPromptTextareaClass =
'w-full resize-vertical appearance-none bg-transparent text-sm outline-hidden focus:bg-transparent disabled:bg-transparent dark:text-gray-300';
const highContrastSelectClass =
'rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500';
const toggleNotification = async () => {
const permission = await Notification.requestPermission();
@ -223,9 +225,7 @@
<div class=" self-center text-xs font-normal">{$i18n.t('Theme')}</div>
<div class="flex items-center relative">
<select
class="w-fit pr-8 rounded-sm py-2 px-2 text-xs bg-transparent text-right {$settings.highContrastMode
? ''
: 'outline-hidden'}"
class={`w-fit pr-8 text-xs text-right ${$settings.highContrastMode ? highContrastSelectClass : 'rounded-sm bg-transparent px-2 py-2 outline-hidden'}`}
bind:value={selectedTheme}
placeholder={$i18n.t('Select a theme')}
on:change={() => themeChangeHandler(selectedTheme)}
@ -245,9 +245,7 @@
<div class=" self-center text-xs font-normal">{$i18n.t('Language')}</div>
<div class="flex items-center relative">
<select
class="w-fit pr-8 rounded-sm py-2 px-2 text-xs bg-transparent text-right {$settings.highContrastMode
? ''
: 'outline-hidden'}"
class={`w-fit pr-8 text-xs text-right ${$settings.highContrastMode ? highContrastSelectClass : 'rounded-sm bg-transparent px-2 py-2 outline-hidden'}`}
bind:value={lang}
placeholder={$i18n.t('Select a language')}
on:change={(e) => {

View file

@ -2,26 +2,92 @@
import Switch from '$lib/components/common/Switch.svelte';
import { config, settings } from '$lib/stores';
import { createEventDispatcher, onMount, getContext } from 'svelte';
import ManageModal from './Personalization/ManageModal.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Spinner from '$lib/components/common/Spinner.svelte';
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
import MemoryModal from './Personalization/MemoryModal.svelte';
import { deleteMemoriesByUserId, deleteMemoryById, getMemories } from '$lib/apis/memories';
import { toast } from 'svelte-sonner';
const dispatch = createEventDispatcher();
const i18n = getContext('i18n');
export let saveSettings: Function;
let showManageModal = false;
// Addons
let enableMemory = false;
let memories: Memory[] = [];
let loadingMemories = true;
let showMemoryModal = false;
let selectedMemory: Memory | null = null;
let showClearConfirmDialog = false;
let showDeleteConfirm = false;
let query = '';
type Memory = {
id: string;
content: string;
type?: string;
path?: string;
updated_at?: number;
};
const loadMemories = async () => {
loadingMemories = true;
memories =
(await getMemories(localStorage.token).catch((error) => {
toast.error(`${error}`);
return [];
})) ?? [];
loadingMemories = false;
};
const confirmDeleteMemory = (memory: Memory) => {
selectedMemory = memory;
showDeleteConfirm = true;
};
const editMemory = (memory: Memory) => {
selectedMemory = memory;
showMemoryModal = true;
};
let onClearConfirmed = async () => {
const res = await deleteMemoriesByUserId(localStorage.token).catch((error) => {
toast.error(`${error}`);
return null;
});
if (res && memories.length > 0) {
toast.success($i18n.t('Memory cleared successfully'));
memories = [];
}
showClearConfirmDialog = false;
};
$: filteredMemories = query
? memories.filter((memory) => {
const value = query.toLowerCase();
return (
memory.content?.toLowerCase().includes(value) ||
memory.path?.toLowerCase().includes(value) ||
memory.type?.toLowerCase().includes(value)
);
})
: memories;
$: sortedMemories = [...filteredMemories].sort(
(a, b) => (b.updated_at ?? 0) - (a.updated_at ?? 0)
);
onMount(async () => {
enableMemory = $settings?.memory ?? $config?.features?.enable_memories ?? false;
await loadMemories();
});
</script>
<ManageModal bind:show={showManageModal} />
<form
id="tab-personalization"
class="flex flex-col h-full justify-between space-y-3 text-sm"
@ -44,7 +110,7 @@
<div class="flex items-center gap-2 text-xs text-gray-400 dark:text-gray-600">
{$i18n.t('Memory')}
<span
class="text-[0.65rem] font-normal uppercase px-1.5 py-0.5 rounded-full bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400"
class="inline-flex items-center text-[0.625rem] font-normal uppercase leading-none text-gray-400 dark:text-gray-600"
>{$i18n.t('Experimental')}</span
>
</div>
@ -59,27 +125,122 @@
/>
</div>
</div>
<p class="-mt-1 text-[0.6875rem] text-gray-400 dark:text-gray-600">
{$i18n
.t(
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you."
)
.replace($i18n.t('Manage'), $i18n.t('Add Memory'))}
</p>
</div>
<div class="text-xs text-gray-600 dark:text-gray-400">
<div>
{$i18n.t(
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you."
)}
{#if enableMemory}
<div class="mt-5">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center gap-2">
<h3 class="text-xs text-gray-400 dark:text-gray-600">
{$i18n.t('Memory')}
</h3>
{#if !loadingMemories}
<div class="text-xs text-gray-400 dark:text-gray-600">{memories.length}</div>
{/if}
</div>
<button
type="button"
class="text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition"
on:click={() => {
selectedMemory = null;
showMemoryModal = true;
}}
>
{$i18n.t('Add Memory')}
</button>
</div>
{#if loadingMemories}
<div class="w-full flex justify-center items-center min-h-16">
<Spinner className="size-4" />
</div>
{:else}
{#if memories.length > 0}
<div class="flex items-center gap-2 mb-3">
<input
class="w-full text-xs py-1 bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700"
bind:value={query}
placeholder={$i18n.t('Search Memories')}
maxlength="500"
/>
{#if query}
<button
type="button"
class="shrink-0 text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"
on:click={() => {
query = '';
}}
>
{$i18n.t('Clear')}
</button>
{/if}
</div>
{/if}
{#if sortedMemories.length === 0}
<div class="text-xs text-gray-500 dark:text-gray-400 min-h-16 flex items-center">
{#if memories.length === 0}
{$i18n.t('Memories accessible by LLMs will be shown here.')}
{:else}
{$i18n.t('No results found')}
{/if}
</div>
{:else}
<div class="flex flex-col gap-2.5">
{#each sortedMemories as memory (memory.id)}
<div class="flex items-start justify-between gap-3">
<div
class="min-w-0 whitespace-pre-wrap break-words text-xs text-gray-600 dark:text-gray-400"
>
{memory.content}
</div>
<div class="flex shrink-0 items-center gap-2">
<button
type="button"
class="text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"
on:click={() => editMemory(memory)}
>
{$i18n.t('Edit')}
</button>
<button
type="button"
class="text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"
on:click={() => confirmDeleteMemory(memory)}
>
{$i18n.t('Remove')}
</button>
</div>
</div>
{/each}
</div>
{/if}
<button
type="button"
class="mt-3 text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 hover:underline transition"
on:click={() => {
if (memories.length > 0) {
showClearConfirmDialog = true;
} else {
toast.error($i18n.t('No memories to clear'));
}
}}
>
{$i18n.t('Clear memory')}
</button>
{/if}
</div>
</div>
<div class="mt-3 mb-1 ml-1">
<button
type="button"
class="px-3.5 py-1.5 font-normal hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-gray-300 dark:outline-gray-800 rounded-3xl"
on:click={() => {
showManageModal = true;
}}
>
{$i18n.t('Manage')}
</button>
</div>
{/if}
</div>
<div class="shrink-0 flex justify-end text-sm font-normal">
@ -91,3 +252,52 @@
</button>
</div>
</form>
<ConfirmDialog
title={$i18n.t('Clear Memory')}
message={$i18n.t('Are you sure you want to clear all memories? This action cannot be undone.')}
show={showClearConfirmDialog}
on:confirm={onClearConfirmed}
on:cancel={() => {
showClearConfirmDialog = false;
}}
/>
<ConfirmDialog
title={$i18n.t('Delete Memory?')}
show={showDeleteConfirm}
on:confirm={async () => {
if (!selectedMemory) return;
const res = await deleteMemoryById(localStorage.token, selectedMemory.id).catch((error) => {
toast.error(`${error}`);
return null;
});
if (res) {
toast.success($i18n.t('Memory deleted successfully'));
await loadMemories();
}
showDeleteConfirm = false;
}}
on:cancel={() => {
showDeleteConfirm = false;
}}
>
<div class="text-sm text-gray-500 flex-1">
{$i18n.t('Are you sure you want to delete this memory? This action cannot be undone.')}
<div
class="mt-2 bg-gray-50 dark:bg-gray-900 p-3 rounded-xl border border-gray-100 dark:border-gray-800 text-black dark:text-white whitespace-pre-wrap break-words max-h-32 overflow-y-auto"
>
{selectedMemory?.content}
</div>
</div>
</ConfirmDialog>
<MemoryModal
bind:show={showMemoryModal}
memory={selectedMemory}
on:save={async () => {
await loadMemories();
}}
/>

View file

@ -1,306 +0,0 @@
<script lang="ts">
import { toast } from 'svelte-sonner';
import dayjs from 'dayjs';
import { getContext } from 'svelte';
import Modal from '$lib/components/common/Modal.svelte';
import MemoryModal from './MemoryModal.svelte';
import { deleteMemoriesByUserId, deleteMemoryById, getMemories } from '$lib/apis/memories';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import localizedFormat from 'dayjs/plugin/localizedFormat';
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
import Spinner from '$lib/components/common/Spinner.svelte';
import XMark from '$lib/components/icons/XMark.svelte';
import Pencil from '$lib/components/icons/Pencil.svelte';
import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
import Search from '$lib/components/icons/Search.svelte';
const i18n = getContext('i18n');
dayjs.extend(localizedFormat);
export let show = false;
type Memory = {
id: string;
content: string;
type?: string;
path?: string;
updated_at?: number;
};
let memories: Memory[] = [];
let loading = true;
let loaded = false;
let query = '';
let showMemoryModal = false;
let selectedMemory: Memory | null = null;
let showClearConfirmDialog = false;
let showDeleteConfirm = false;
const loadMemories = async () => {
loading = true;
memories = (await getMemories(localStorage.token)) ?? [];
loading = false;
};
const editMemory = (memory: Memory) => {
selectedMemory = memory;
showMemoryModal = true;
};
const confirmDeleteMemory = (memory: Memory) => {
selectedMemory = memory;
showDeleteConfirm = true;
};
const memoryTypeLabel = (memory: Memory) =>
(memory.type ?? 'user') === 'user' ? $i18n.t('User') : $i18n.t('Context');
const memoryUpdatedAt = (memory: Memory) =>
memory.updated_at ? dayjs(memory.updated_at * 1000).format('MMM D, h:mm A') : '';
$: filteredMemories = query
? memories.filter((memory) => {
const value = query.toLowerCase();
return (
memory.content?.toLowerCase().includes(value) ||
memory.path?.toLowerCase().includes(value) ||
memory.type?.toLowerCase().includes(value)
);
})
: memories;
$: sortedMemories = [...filteredMemories].sort(
(a, b) => (b.updated_at ?? 0) - (a.updated_at ?? 0)
);
let onClearConfirmed = async () => {
const res = await deleteMemoriesByUserId(localStorage.token).catch((error) => {
toast.error(`${error}`);
return null;
});
if (res && memories.length > 0) {
toast.success($i18n.t('Memory cleared successfully'));
memories = [];
}
showClearConfirmDialog = false;
};
$: if (show && !loaded) {
loaded = true;
loadMemories();
}
$: if (!show) {
query = '';
loaded = false;
}
</script>
<Modal size="lg" bind:show>
<div>
<div class="flex justify-between dark:text-gray-300 px-4 pt-3 pb-1">
<div class="flex items-center gap-2">
<div class="text-sm font-medium">{$i18n.t('Memory')}</div>
{#if !loading}
<div class="text-sm font-medium text-gray-500 dark:text-gray-500">
{memories.length}
</div>
{/if}
</div>
<button
class="self-center rounded-lg p-1 text-gray-500 transition hover:bg-gray-50 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-200"
on:click={() => (show = false)}
>
<XMark className="size-4" />
</button>
</div>
<div class="flex flex-col w-full px-5 pb-4 dark:text-gray-200">
<div class="flex flex-1 items-center w-full mb-1">
<div class="self-center ml-1 mr-3">
<Search className="size-3.5" />
</div>
<input
class="w-full text-sm py-1 rounded-r-xl outline-hidden bg-transparent"
bind:value={query}
placeholder={$i18n.t('Search Memories')}
maxlength="500"
/>
{#if query}
<div class="self-center pl-1.5 translate-y-[0.5px] bg-transparent">
<button
class="p-0.5 rounded-full hover:bg-gray-100 dark:hover:bg-gray-900 transition"
on:click={() => {
query = '';
}}
>
<XMark className="size-3" strokeWidth="2" />
</button>
</div>
{/if}
</div>
<div class="flex flex-col w-full">
{#if !loading}
{#if sortedMemories.length === 0}
<div
class="text-xs text-gray-500 dark:text-gray-400 text-center px-5 min-h-20 w-full flex justify-center items-center"
>
{#if memories.length === 0}
{$i18n.t('Memories accessible by LLMs will be shown here.')}
{:else}
{$i18n.t('No results found')}
{/if}
</div>
{:else}
<div class="text-left text-sm w-full max-h-[28rem] overflow-y-auto space-y-0.5">
{#each sortedMemories as memory (memory.id)}
<div class="group w-full flex justify-between items-center text-sm py-1 px-1">
<button
type="button"
class="flex-1 min-w-0 text-left flex items-center gap-1.5 pr-2 text-gray-900 dark:text-gray-100 hover:text-black dark:hover:text-white transition leading-5"
on:click={() => editMemory(memory)}
>
<div class="truncate leading-5">
{memory.content}
</div>
<span class="shrink-0 text-[11px] leading-5 text-gray-400 dark:text-gray-500">
{memoryTypeLabel(memory)}
</span>
{#if memory.path}
<span
class="hidden sm:block shrink min-w-0 truncate text-[11px] leading-5 text-gray-400 dark:text-gray-500"
>
{memory.path}
</span>
{/if}
{#if memoryUpdatedAt(memory)}
<div
class="hidden sm:block shrink-0 text-[11px] leading-5 text-gray-400 dark:text-gray-500"
>
{memoryUpdatedAt(memory)}
</div>
{/if}
</button>
<div class="flex items-center shrink-0">
<div
class="flex items-center text-gray-500 dark:text-gray-400 opacity-70 group-hover:opacity-100 focus-within:opacity-100 transition"
>
<Tooltip content={$i18n.t('Edit')}>
<button
class="self-center w-fit text-sm p-1 leading-none hover:text-gray-900 dark:hover:text-gray-100 transition"
on:click={(e) => {
e.stopPropagation();
editMemory(memory);
}}
>
<Pencil className="size-4" />
</button>
</Tooltip>
<Tooltip content={$i18n.t('Delete')}>
<button
class="self-center w-fit text-sm p-1 leading-none hover:text-gray-900 dark:hover:text-gray-100 transition"
on:click={(e) => {
e.stopPropagation();
confirmDeleteMemory(memory);
}}
>
<GarbageBin className="size-4" strokeWidth="1.5" />
</button>
</Tooltip>
</div>
</div>
</div>
{/each}
</div>
{/if}
{:else}
<div class="w-full flex justify-center items-center min-h-20">
<Spinner className="size-4" />
</div>
{/if}
</div>
<div class="flex justify-between items-center text-sm mt-2">
<button
class="px-2 py-1 text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 hover:underline transition"
on:click={() => {
if (memories.length > 0) {
showClearConfirmDialog = true;
} else {
toast.error($i18n.t('No memories to clear'));
}
}}>{$i18n.t('Clear memory')}</button
>
<button
class="px-3.5 py-1.5 font-normal hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-gray-100 dark:outline-gray-800 rounded-3xl"
on:click={() => {
selectedMemory = null;
showMemoryModal = true;
}}>{$i18n.t('Add Memory')}</button
>
</div>
</div>
</div>
</Modal>
<ConfirmDialog
title={$i18n.t('Clear Memory')}
message={$i18n.t('Are you sure you want to clear all memories? This action cannot be undone.')}
show={showClearConfirmDialog}
on:confirm={onClearConfirmed}
on:cancel={() => {
showClearConfirmDialog = false;
}}
/>
<ConfirmDialog
title={$i18n.t('Delete Memory?')}
show={showDeleteConfirm}
on:confirm={async () => {
if (!selectedMemory) return;
const res = await deleteMemoryById(localStorage.token, selectedMemory.id).catch((error) => {
toast.error(`${error}`);
return null;
});
if (res) {
toast.success($i18n.t('Memory deleted successfully'));
await loadMemories();
}
showDeleteConfirm = false;
}}
on:cancel={() => {
showDeleteConfirm = false;
}}
>
<div class="text-sm text-gray-500 flex-1">
{$i18n.t('Are you sure you want to delete this memory? This action cannot be undone.')}
<div
class="mt-2 bg-gray-50 dark:bg-gray-900 p-3 rounded-xl border border-gray-100 dark:border-gray-800 text-black dark:text-white whitespace-pre-wrap break-words max-h-32 overflow-y-auto"
>
{selectedMemory?.content}
</div>
</div>
</ConfirmDialog>
<MemoryModal
bind:show={showMemoryModal}
memory={selectedMemory}
on:save={async () => {
await loadMemories();
}}
/>

View file

@ -52,6 +52,9 @@
const i18n: Writable<any> = getContext('i18n');
const highContrastInputClass =
'rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
export let show: boolean | string | SettingsModalRequest = false;
let modalShow = false;
let lastShow: boolean | string | SettingsModalRequest = false;
@ -851,8 +854,7 @@
</div>
<label class="sr-only" for="search-input-settings-modal">{$i18n.t('Search')}</label>
<input
class={`w-full py-1 text-xs bg-transparent dark:text-gray-300 outline-hidden
${($settings?.highContrastMode ?? false) ? 'placeholder-gray-800' : ''}`}
class={`w-full text-xs ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent py-1 outline-hidden dark:text-gray-300'}`}
bind:value={search}
id="search-input-settings-modal"
on:input={searchDebounceHandler}

View file

@ -16,6 +16,9 @@
export let name: string | undefined = undefined;
let show = false;
const highContrastInputClass =
'rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
</script>
<div class={outerClassName}>
@ -24,7 +27,7 @@
{/if}
<input
{id}
class={`${inputClassName} ${show ? '' : 'password'} ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : ' outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-600'}`}
class={`${inputClassName} ${show ? '' : 'password'} ${($settings?.highContrastMode ?? false) ? highContrastInputClass : ' outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-600'}`}
{placeholder}
type={type === 'password' && !show ? 'password' : 'text'}
bind:value

View file

@ -996,7 +996,7 @@
? `${$mobile ? 'bg-gray-50 dark:bg-gray-950' : 'bg-gray-50/70 dark:bg-gray-950/70'} z-50`
: ' bg-transparent z-0 '} {$isApp
? `ml-[4.5rem] md:ml-0 `
: ' transition-all duration-300 '} shrink-0 text-gray-700 dark:text-gray-300 text-sm fixed top-0 left-0 overflow-x-hidden
: ' transition-all duration-300 '} shrink-0 text-gray-700 dark:text-gray-300 text-[13px] leading-5 fixed top-0 left-0 overflow-x-hidden
"
transition:slide={{ duration: 250, axis: 'x' }}
data-state={$showSidebar}
@ -1077,12 +1077,12 @@
on:click={newChatHandler}
aria-label={$i18n.t('New Chat')}
>
<div class="self-center">
<div class="self-center flex size-4 shrink-0 items-center justify-center">
<EditPencilIcon className=" size-4" strokeWidth="1.5" />
</div>
<div class="flex flex-1 self-center translate-y-[0.5px]">
<div class=" self-center text-sm">{$i18n.t('New Chat')}</div>
<div class=" self-center text-[13px] leading-5">{$i18n.t('New Chat')}</div>
</div>
<HotkeyHint name="newChat" className=" group-hover:visible invisible" />
@ -1099,12 +1099,12 @@
draggable="false"
aria-label={$i18n.t('Search')}
>
<div class="self-center">
<div class="self-center flex size-4 shrink-0 items-center justify-center">
<SearchIcon strokeWidth="1.5" className="size-4" />
</div>
<div class="flex flex-1 self-center translate-y-[0.5px]">
<div class=" self-center text-sm">{$i18n.t('Search')}</div>
<div class=" self-center text-[13px] leading-5">{$i18n.t('Search')}</div>
</div>
<HotkeyHint name="search" className=" group-hover:visible invisible" />
</button>
@ -1131,7 +1131,7 @@
draggable="false"
aria-label={$i18n.t(meta.label)}
>
<div class="self-center">
<div class="self-center flex size-4 shrink-0 items-center justify-center">
{#if itemId === 'notes'}
<NotesIcon className="size-4" strokeWidth="1.5" />
{:else if itemId === 'workspace'}
@ -1146,7 +1146,7 @@
</div>
<div class="flex self-center translate-y-[0.5px]">
<div class=" self-center text-sm">{$i18n.t(meta.label)}</div>
<div class=" self-center text-[13px] leading-5">{$i18n.t(meta.label)}</div>
</div>
</a>
</div>

View file

@ -11,9 +11,9 @@
import ChannelModal from './ChannelModal.svelte';
import Emoji from '$lib/components/common/Emoji.svelte';
import HashtagIcon from './icons/Hashtag.svelte';
import LockIcon from './icons/Lock.svelte';
import SettingsIcon from './icons/Settings.svelte';
import HashtagIcon from '$lib/components/icons/Hashtag.svelte';
import LockIcon from '$lib/components/icons/Lock.svelte';
import EditPencilIcon from './icons/EditPencil.svelte';
import UserIcon from './icons/User.svelte';
import XMarkIcon from './icons/XMark.svelte';
@ -144,9 +144,9 @@
{:else}
<div class=" size-4 justify-center flex items-center ml-1">
{#if isPublicChannel(channel)}
<HashtagIcon className="size-3.5" strokeWidth="1.5" />
<HashtagIcon className="size-3" strokeWidth="1.8" />
{:else}
<LockIcon className="size-[15px]" strokeWidth="1.5" />
<LockIcon className="size-3.5" strokeWidth="1.7" />
{/if}
</div>
{/if}
@ -242,7 +242,7 @@
showEditChannelModal = true;
}}
>
<SettingsIcon className="size-3.5" />
<EditPencilIcon className="size-3.5" />
</button>
</div>
{/if}

View file

@ -38,11 +38,11 @@
import Tooltip from '$lib/components/common/Tooltip.svelte';
import DragGhost from '$lib/components/common/DragGhost.svelte';
import Spinner from '$lib/components/common/Spinner.svelte';
import ArchiveIcon from './icons/Archive.svelte';
import ChatIcon from './icons/Chat.svelte';
import MoreHorizontalIcon from './icons/MoreHorizontal.svelte';
import SparklesIcon from './icons/Sparkles.svelte';
import TrashIcon from './icons/Trash.svelte';
import ArchiveBoxIcon from '$lib/components/icons/ArchiveBox.svelte';
import GarbageBinIcon from '$lib/components/icons/GarbageBin.svelte';
import { generateTitle } from '$lib/apis';
import { createMessagesList } from '$lib/utils';
import { getOutputText } from '$lib/components/chat/Messages/structuredOutput';
@ -106,6 +106,7 @@
id !== $chatId &&
!$activeChatIds.has(id) &&
(effectiveReadAt === null || (updatedAt !== null && updatedAt > effectiveReadAt));
$: showInlineActions = id === $chatId || confirmEdit || mouseOver || selected;
const loadChat = async () => {
if (!chat) {
@ -454,12 +455,12 @@
id="sidebar-chat-item"
class=" w-full flex justify-between rounded-xl px-2 py-[6px] {id === $chatId || confirmEdit
? ($settings?.highContrastMode ?? false)
? 'bg-gray-100 dark:bg-gray-800 selected'
: 'bg-gray-100 dark:bg-gray-900 selected'
? 'bg-black/[0.035] dark:bg-white/[0.06] selected'
: 'bg-black/[0.035] dark:bg-white/[0.045] selected'
: selected
? ($settings?.highContrastMode ?? false)
? 'bg-gray-100 dark:bg-gray-900 selected'
: 'bg-gray-100 dark:bg-gray-900 selected'
? 'bg-black/[0.035] dark:bg-white/[0.055] selected'
: 'bg-black/[0.035] dark:bg-white/[0.045] selected'
: 'hover:bg-gray-50 dark:hover:bg-gray-900 group-hover:bg-gray-50 dark:group-hover:bg-gray-900'} whitespace-nowrap text-ellipsis relative transition {generating
? 'cursor-not-allowed'
: ''}"
@ -493,12 +494,12 @@
id="sidebar-chat-item"
class=" w-full flex justify-between rounded-xl px-2 py-[6px] {id === $chatId || confirmEdit
? ($settings?.highContrastMode ?? false)
? 'bg-gray-100 dark:bg-gray-800 selected'
: 'bg-gray-100 dark:bg-gray-900 selected'
? 'bg-black/[0.035] dark:bg-white/[0.06] selected'
: 'bg-black/[0.035] dark:bg-white/[0.045] selected'
: selected
? ($settings?.highContrastMode ?? false)
? 'bg-gray-100 dark:bg-gray-900 selected'
: 'bg-gray-100 dark:bg-gray-900 selected'
? 'bg-black/[0.035] dark:bg-white/[0.055] selected'
: 'bg-black/[0.035] dark:bg-white/[0.045] selected'
: ' hover:bg-gray-50 dark:hover:bg-gray-900 group-hover:bg-gray-50 dark:group-hover:bg-gray-900'} whitespace-nowrap text-ellipsis transition"
href="/c/{id}"
on:click={() => {
@ -560,14 +561,14 @@
dir="auto"
class="text-left self-center overflow-hidden w-full h-[20px] truncate {unread
? 'font-normal text-gray-800 dark:text-gray-200'
: ''}"
: ''} {showInlineActions && !readonly ? 'pr-12' : ''}"
>
{title}
</div>
</div>
<!-- Time ago indicator -->
{#if (updatedAt ?? createdAt) && !mouseOver}
{#if (updatedAt ?? createdAt) && !showInlineActions}
<div class="shrink-0 self-center text-[10px] text-gray-400 dark:text-gray-500 pl-2">
{formatTimeAgo(updatedAt ?? createdAt)}
</div>
@ -579,21 +580,11 @@
{#if !readonly}
<div
id="sidebar-chat-item-menu"
class="
{id === $chatId || confirmEdit
? ($settings?.highContrastMode ?? false)
? 'from-gray-100 dark:from-gray-800 selected'
: 'from-gray-100 dark:from-gray-900 selected'
: selected
? ($settings?.highContrastMode ?? false)
? 'from-gray-100 dark:from-gray-900 selected'
: 'from-gray-100 dark:from-gray-900 selected'
: 'invisible group-hover:visible from-gray-50 dark:from-gray-900'}
absolute {className === 'pr-2'
class="{showInlineActions
? 'selected'
: 'invisible group-hover:visible'} absolute {className === 'pr-2'
? 'right-[8px]'
: 'right-1'} top-[4px] py-1 pr-0.5 mr-1.5 pl-5 bg-linear-to-l from-80%
to-transparent"
: 'right-1'} inset-y-0 mr-1.5 flex items-center"
on:mouseenter={(e) => {
mouseOver = true;
}}
@ -607,7 +598,7 @@
>
<Tooltip content={$i18n.t('Generate')}>
<button
class=" self-center dark:hover:text-white transition disabled:cursor-not-allowed"
class="flex size-5 items-center justify-center self-center dark:hover:text-white transition disabled:cursor-not-allowed"
id="generate-title-button"
disabled={generating}
on:click={() => {
@ -622,14 +613,14 @@
<div class=" flex items-center self-center space-x-1.5">
<Tooltip content={$i18n.t('Archive')} className="flex items-center">
<button
class=" self-center dark:hover:text-white transition disabled:cursor-not-allowed"
class="flex size-5 items-center justify-center self-center dark:hover:text-white transition disabled:cursor-not-allowed"
disabled={archiving}
on:click={() => {
archiveChatHandler(id);
}}
type="button"
>
<ArchiveIcon className="size-4 translate-y-[0.5px]" strokeWidth="1.5" />
<ArchiveBoxIcon className="size-3.5" strokeWidth="1.7" />
</button>
</Tooltip>
@ -642,7 +633,7 @@
}}
type="button"
>
<TrashIcon strokeWidth="1.5" />
<GarbageBinIcon className="size-3.5" strokeWidth="1.7" />
</button>
</Tooltip>
</div>
@ -673,12 +664,12 @@
>
<button
aria-label="Chat Menu"
class=" self-center dark:hover:text-white transition m-0"
class="flex size-5 items-center justify-center self-center dark:hover:text-white transition m-0"
on:click={() => {
dispatch('select');
}}
>
<MoreHorizontalIcon className="w-4 h-4" />
<MoreHorizontalIcon className="size-3.5" strokeWidth="2" />
</button>
</ChatMenu>
@ -691,7 +682,7 @@
showDeleteConfirm = true;
}}
>
<MoreHorizontalIcon className="w-4 h-4" />
<MoreHorizontalIcon className="size-3.5" strokeWidth="2" />
</button>
{/if}
</div>

View file

@ -18,7 +18,7 @@
import { createMessagesList } from '$lib/utils';
import { getOutputText } from '$lib/components/chat/Messages/structuredOutput';
import { downloadChatAsPDF } from '$lib/apis/utils';
import ArchiveIcon from './icons/Archive.svelte';
import ArchiveBoxIcon from '$lib/components/icons/ArchiveBox.svelte';
import CopyIcon from './icons/Copy.svelte';
import DownloadIcon from './icons/Download.svelte';
import EditPencilIcon from './icons/EditPencil.svelte';
@ -438,7 +438,7 @@
archiveChatHandler();
}}
>
<ArchiveIcon className="size-3.5" strokeWidth="1.5" />
<ArchiveBoxIcon className="size-3.5" strokeWidth="1.7" />
<div class="flex items-center">{$i18n.t('Archive')}</div>
</button>

View file

@ -37,7 +37,7 @@
<div class="self-center shrink-0">
<img
src={`${WEBUI_API_BASE_URL}/models/model/profile/image?id=${model.id}&lang=${$i18n.language}`}
class=" size-5 rounded-full -translate-x-[0.5px]"
class=" size-4 rounded-full"
alt="logo"
on:error={(e) => {
e.currentTarget.src = '/favicon.png';
@ -46,7 +46,7 @@
</div>
<div class="flex self-center translate-y-[0.5px]">
<div class=" self-center text-sm line-clamp-1">
<div class=" self-center text-[13px] leading-5 line-clamp-1">
{model?.name ?? model.id}
</div>
</div>

View file

@ -67,7 +67,7 @@
data-id={note.id}
>
<a
class="grow flex items-center gap-2.5 text-sm"
class="grow flex items-center gap-2.5 text-[13px] leading-5"
href={`/notes/${note.id}`}
on:click={() => {
selectedChatId = null;

View file

@ -24,6 +24,9 @@
let loading = false;
const highContrastInputClass =
'rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
const submitHandler = async () => {
loading = true;
const res = await updateUserStatus(localStorage.token, {
@ -123,7 +126,7 @@
id="status-message"
type="text"
bind:value={message}
class={`w-full flex-1 text-sm bg-transparent ${($settings?.highContrastMode ?? false) ? 'placeholder:text-gray-700 dark:placeholder:text-gray-100' : 'outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
class={`w-full flex-1 text-sm ${($settings?.highContrastMode ?? false) ? highContrastInputClass : 'bg-transparent outline-hidden placeholder:text-gray-300 dark:placeholder:text-gray-700'}`}
placeholder={$i18n.t("What's on your mind?")}
autocomplete="off"
required

View file

@ -729,12 +729,12 @@
</div>
{:else}
<div class="flex min-h-[calc(100dvh-13rem)] w-full flex-col items-center justify-center">
<div class="text-center">
<div class=" text-sm text-gray-400 dark:text-gray-600">
<div class="max-w-sm text-center text-gray-900 dark:text-gray-100">
<div class="mb-1.5 text-sm">
{$i18n.t('No Notes')}
</div>
<div class="mt-1 text-xs text-gray-300 dark:text-gray-700">
<div class="text-xs leading-5 text-gray-500">
{$i18n.t('Create your first note from the Create menu.')}
</div>
</div>

View file

@ -1,5 +1,7 @@
<script lang="ts">
import { getContext, onMount } from 'svelte';
import Hashtag from '$lib/components/icons/Hashtag.svelte';
import Lock from '$lib/components/icons/Lock.svelte';
const i18n = getContext('i18n');
@ -15,35 +17,9 @@
<div>
<div class=" p-2 bg-black/5 dark:bg-white/5 rounded-full">
{#if state === 'private'}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z"
/>
</svg>
<Lock className="w-5 h-5" strokeWidth="1.8" />
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6.115 5.19l.319 1.913A6 6 0 008.11 10.36L9.75 12l-.387.775c-.217.433-.132.956.21 1.298l1.348 1.348c.21.21.329.497.329.795v1.089c0 .426.24.815.622 1.006l.153.076c.433.217.956.132 1.298-.21l.723-.723a8.7 8.7 0 002.288-4.042 1.087 1.087 0 00-.358-1.099l-1.33-1.108c-.251-.21-.582-.299-.905-.245l-1.17.195a1.125 1.125 0 01-.98-.314l-.295-.295a1.125 1.125 0 010-1.591l.13-.132a1.125 1.125 0 011.3-.21l.603.302a.809.809 0 001.086-1.086L14.25 7.5l1.256-.837a4.5 4.5 0 001.528-1.732l.146-.292M6.115 5.19A9 9 0 1017.18 4.64M6.115 5.19A8.965 8.965 0 0112 3c1.929 0 3.716.607 5.18 1.64"
/>
</svg>
<Hashtag className="w-5 h-5" strokeWidth="1.8" />
{/if}
</div>
</div>

View file

@ -527,12 +527,11 @@
<div
class="flex min-h-[calc(100dvh-13rem)] w-full flex-col items-center justify-center"
>
<div class="max-w-md text-center">
<div class="text-3xl mb-3"></div>
<div class="text-lg font-normal mb-1">
<div class="max-w-sm text-center text-gray-900 dark:text-gray-100">
<div class="mb-1.5 text-sm">
{query ? $i18n.t('No results found') : $i18n.t('No automations found')}
</div>
<div class="text-gray-500 text-center text-xs">
<div class="text-center text-xs leading-5 text-gray-500">
{query
? $i18n.t(
'Try adjusting your search or filter to find what you are looking for.'