mirror of
https://github.com/open-webui/open-webui.git
synced 2026-08-28 05:27:35 +00:00
refac
This commit is contained in:
parent
5b934b1f90
commit
db934a3b4f
22 changed files with 412 additions and 527 deletions
|
|
@ -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')}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)')}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}}
|
||||
/>
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue