This commit is contained in:
Timothy Jaeryang Baek
2026-07-15 04:52:06 -04:00
parent 1760b073c7
commit 343eb1d659
22 changed files with 572 additions and 308 deletions
@@ -9,7 +9,7 @@
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
import XMark from '$lib/components/icons/XMark.svelte';
import AccessControlModal from '$lib/components/workspace/common/AccessControlModal.svelte';
import LockClosed from '$lib/components/icons/LockClosed.svelte';
import AccessButton from '$lib/components/common/AccessButton.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
import {
@@ -775,19 +775,12 @@
</button>
{#if !direct}
<button
class="bg-gray-50 hover:bg-gray-100 text-black dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white transition px-2 py-1 object-cover rounded-full flex gap-1 items-center mt-2"
type="button"
<AccessButton
className="mt-2"
on:click={() => {
showAccessControlModal = true;
}}
>
<LockClosed strokeWidth="2.5" className="size-3.5 shrink-0" />
<div class="text-xs font-normal shrink-0">
{$i18n.t('Access')}
</div>
</button>
/>
{/if}
</div>
+4 -11
View File
@@ -20,7 +20,7 @@
import { getToolServerData } from '$lib/apis';
import { verifyToolServerConnection, registerOAuthClient } from '$lib/apis/configs';
import AccessControlModal from '$lib/components/workspace/common/AccessControlModal.svelte';
import LockClosed from '$lib/components/icons/LockClosed.svelte';
import AccessButton from '$lib/components/common/AccessButton.svelte';
import Spinner from '$lib/components/common/Spinner.svelte';
import XMark from '$lib/components/icons/XMark.svelte';
import Textarea from './common/Textarea.svelte';
@@ -804,19 +804,12 @@
</button>
{#if !direct}
<button
class="bg-gray-50 hover:bg-gray-100 text-black dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white transition px-2 py-1 object-cover rounded-full flex gap-1 items-center mt-2"
type="button"
<AccessButton
className="mt-2"
on:click={() => {
showAccessControlModal = true;
}}
>
<LockClosed strokeWidth="2.5" className="size-3.5 shrink-0" />
<div class="text-xs font-normal shrink-0">
{$i18n.t('Access')}
</div>
</button>
/>
{/if}
</div>
+2 -2
View File
@@ -63,6 +63,7 @@
import { WEBUI_BASE_URL, WEBUI_API_BASE_URL, PASTED_TEXT_CHARACTER_LIMIT } from '$lib/constants';
import { initiateOAuthRedirect } from '$lib/apis/configs';
import { matchKeybinding, Shortcut } from '$lib/shortcuts';
import { createNoteHandler } from '../notes/utils';
import { getSuggestionRenderer } from '../common/RichTextInput/suggestions';
@@ -1009,8 +1010,7 @@
shiftKey = true;
}
// Cmd/Ctrl+Shift+L to toggle dictation
if (e.key.toLowerCase() === 'l' && (e.metaKey || e.ctrlKey) && e.shiftKey) {
if (matchKeybinding(e) === Shortcut.TOGGLE_DICTATION) {
e.preventDefault();
if (recording) {
// Confirm and stop recording
+131 -15
View File
@@ -1,47 +1,116 @@
<script lang="ts">
import { getContext, onMount } from 'svelte';
import type { Writable } from 'svelte/store';
import { shortcuts } from '$lib/shortcuts';
import { updateUserSettings } from '$lib/apis/users';
import {
DEFAULT_KEYBINDINGS,
eventToChord,
formatChord,
isConfigurableShortcut,
keybindings,
resetKeybindings,
Shortcut,
shortcuts
} from '$lib/shortcuts';
import type { KeybindingsMap, ShortcutDefinition } from '$lib/shortcuts';
import { settings } from '$lib/stores';
import ShortcutItem from '../ShortcutItem.svelte';
type ShortcutDefinition = NonNullable<(typeof shortcuts)[keyof typeof shortcuts]>;
type CategorizedShortcuts = {
[category: string]: ShortcutDefinition[];
type ShortcutEntry = {
id: Shortcut;
shortcut: ShortcutDefinition;
};
type CategorizedShortcuts = {
[category: string]: ShortcutEntry[];
};
type I18nStore = Writable<{
t: (key: string, options?: Record<string, unknown>) => string;
}>;
const i18n: Writable<any> = getContext('i18n');
const i18n: I18nStore = getContext('i18n');
let categorizedShortcuts: CategorizedShortcuts = {};
let isMac = false;
let recordingShortcut: Shortcut | null = null;
onMount(() => {
isMac = /Mac/i.test(navigator.userAgent);
});
$: {
const allShortcuts = Object.values(shortcuts).filter((shortcut) => {
const allShortcuts = Object.entries(shortcuts).filter(([, shortcut]) => {
if (!shortcut.setting) {
return true;
}
return ($settings as Record<string, any>)[shortcut.setting.id] === shortcut.setting.value;
});
return ($settings as Record<string, unknown>)[shortcut.setting.id] === shortcut.setting.value;
}) as [Shortcut, ShortcutDefinition][];
categorizedShortcuts = allShortcuts.reduce<CategorizedShortcuts>((acc, shortcut) => {
categorizedShortcuts = allShortcuts.reduce<CategorizedShortcuts>((acc, [id, shortcut]) => {
const category = shortcut.category;
if (!acc[category]) {
acc[category] = [];
}
acc[category].push(shortcut);
acc[category].push({ id, shortcut });
return acc;
}, {});
}
const saveKeybindings = async (next: KeybindingsMap) => {
keybindings.set(next);
await updateUserSettings(localStorage.token, { keybindings: next });
};
const setBinding = async (id: Shortcut, chord: string) => {
if (!isConfigurableShortcut(id)) return;
await saveKeybindings({ ...$keybindings, [id]: chord });
};
const resetBindings = async () => {
resetKeybindings();
await updateUserSettings(localStorage.token, { keybindings: DEFAULT_KEYBINDINGS });
};
const getConflict = (id: Shortcut, chord: string): Shortcut | null => {
if (!isConfigurableShortcut(id) || !chord) return null;
for (const [otherId, otherChord] of Object.entries($keybindings)) {
if (otherId !== id && otherChord === chord) return otherId as Shortcut;
}
return null;
};
const handleRecordingKeydown = async (event: KeyboardEvent) => {
if (!recordingShortcut) return;
event.preventDefault();
event.stopPropagation();
if (event.key === 'Escape') {
recordingShortcut = null;
return;
}
const chord = eventToChord(event);
if (!chord) return;
await setBinding(recordingShortcut, chord);
recordingShortcut = null;
};
</script>
<svelte:window on:keydown={handleRecordingKeydown} />
<div id="tab-shortcuts" class="flex h-full flex-col text-sm">
<h2 class="text-sm font-medium text-gray-900 dark:text-white mb-4">
{$i18n.t('Keyboard Shortcuts')}
</h2>
<div class="mb-4 flex items-center justify-between">
<h2 class="text-sm font-medium text-gray-900 dark:text-white">
{$i18n.t('Keyboard Shortcuts')}
</h2>
<button
class="text-[0.625rem] text-gray-400 transition hover:text-gray-600 dark:hover:text-gray-300"
on:click={resetBindings}
>
{$i18n.t('Reset Defaults')}
</button>
</div>
<div class="flex-1 min-h-0 overflow-y-auto scrollbar-hover pr-1.5">
<div class="flex items-center gap-2 px-1 pb-1">
@@ -99,8 +168,55 @@
<!-- {$i18n.t('Voice')} -->
<!-- {$i18n.t('Toggle Mute')} -->
<!-- {$i18n.t('Only active during Voice Mode.')} -->
{#each items as shortcut}
<ShortcutItem {shortcut} {isMac} />
<!-- {$i18n.t('Reset Defaults')} -->
<!-- {$i18n.t('Press keys...')} -->
<!-- {$i18n.t('Unassigned')} -->
<!-- {$i18n.t('Also bound to {{action}}')} -->
{#each items as { id, shortcut }}
{@const configurable = isConfigurableShortcut(id)}
{@const chord = configurable ? $keybindings[id] : ''}
{@const conflict = configurable ? getConflict(id, chord) : null}
<div class="flex min-h-8 w-full items-center gap-3 px-1 py-1.5">
<ShortcutItem {id} {shortcut} {isMac} compact />
<div class="flex w-[9.5rem] shrink-0 items-center justify-end gap-1">
{#if configurable}
{#if recordingShortcut === id}
<span
class="inline-flex min-h-[1.125rem] items-center rounded-full bg-gray-100 px-[0.4375rem] py-0.5 text-[0.625rem] font-medium leading-none text-gray-500 dark:bg-white/6 dark:text-gray-400"
>
{$i18n.t('Press keys...')}
</span>
{:else if chord}
<button
class="inline-flex min-h-[1.125rem] items-center rounded-full bg-gray-100 px-[0.4375rem] py-0.5 text-center text-[0.625rem] font-medium leading-none text-gray-500 dark:bg-white/6 dark:text-gray-400"
title={$i18n.t('Click to rebind')}
on:click={() => (recordingShortcut = id)}
>
{formatChord(chord)}
</button>
{:else}
<button
class="text-[0.625rem] text-gray-500 transition hover:text-gray-400 dark:text-gray-600"
on:click={() => (recordingShortcut = id)}
>
{$i18n.t('Unassigned')}
</button>
{/if}
{#if conflict}
<span
class="text-[0.5625rem] text-amber-500"
title={$i18n.t('Also bound to {{action}}', {
action: $i18n.t(shortcuts[conflict]?.name ?? conflict)
})}>!</span
>
{/if}
{:else}
<ShortcutItem {id} {shortcut} {isMac} keysOnly />
{/if}
</div>
</div>
{/each}
</div>
</div>
+49 -17
View File
@@ -2,14 +2,21 @@
import { getContext, onMount } from 'svelte';
import type { Writable } from 'svelte/store';
import Tooltip from '../common/Tooltip.svelte';
import { shortcuts } from '$lib/shortcuts';
import { formatChord, isConfigurableShortcut, keybindings, shortcuts } from '$lib/shortcuts';
import type { Shortcut } from '$lib/shortcuts';
type ShortcutDefinition = NonNullable<(typeof shortcuts)[keyof typeof shortcuts]>;
type I18nStore = Writable<{
t: (key: string, options?: Record<string, unknown>) => string;
}>;
export let id: Shortcut | undefined = undefined;
export let shortcut: ShortcutDefinition;
export let isMac: boolean;
export let compact = false;
export let keysOnly = false;
const i18n: Writable<any> = getContext('i18n');
const i18n: I18nStore = getContext('i18n');
let keyboardLayoutMap: Map<string, string> | undefined;
onMount(async () => {
@@ -87,24 +94,49 @@
.map(formatKey)
.join(isMac ? '' : ' + ');
}
function displayKeys(): string {
if (id && isConfigurableShortcut(id)) {
return formatChord($keybindings[id]) || $i18n.t('Unassigned');
}
return formatKeys(shortcut.keys);
}
</script>
<div class="flex min-h-8 w-full items-center gap-3 px-1 py-1.5">
<div class="min-w-0 flex-1 text-[0.71875rem] leading-tight text-gray-700 dark:text-gray-300">
{#if shortcut.tooltip}
<Tooltip content={$i18n.t(shortcut.tooltip)}>
<span class="inline-flex max-w-full items-baseline gap-1">
<span class="truncate whitespace-pre-line">{$i18n.t(shortcut.name)}</span>
<span class="text-[0.625rem] text-gray-400 dark:text-gray-600">*</span>
</span>
</Tooltip>
{:else}
<span class="whitespace-pre-line">{$i18n.t(shortcut.name)}</span>
{/if}
</div>
{#if keysOnly}
<span
class="inline-flex min-h-[1.125rem] max-w-[9.5rem] shrink-0 items-center justify-center rounded-full bg-gray-100 px-[0.4375rem] py-0.5 text-center text-[0.625rem] font-medium leading-none text-gray-500 dark:bg-white/6 dark:text-gray-400"
>
{formatKeys(shortcut.keys)}
{displayKeys()}
</span>
</div>
{:else}
<div
class={compact
? 'min-w-0 flex-1 text-[0.71875rem] leading-tight text-gray-700 dark:text-gray-300'
: 'flex min-h-8 w-full items-center gap-3 px-1 py-1.5'}
>
<div
class={compact
? ''
: 'min-w-0 flex-1 text-[0.71875rem] leading-tight text-gray-700 dark:text-gray-300'}
>
{#if shortcut.tooltip}
<Tooltip content={$i18n.t(shortcut.tooltip)}>
<span class="inline-flex max-w-full items-baseline gap-1">
<span class="truncate whitespace-pre-line">{$i18n.t(shortcut.name)}</span>
<span class="text-[0.625rem] text-gray-400 dark:text-gray-600">*</span>
</span>
</Tooltip>
{:else}
<span class="whitespace-pre-line">{$i18n.t(shortcut.name)}</span>
{/if}
</div>
{#if !compact}
<span
class="inline-flex min-h-[1.125rem] max-w-[9.5rem] shrink-0 items-center justify-center rounded-full bg-gray-100 px-[0.4375rem] py-0.5 text-center text-[0.625rem] font-medium leading-none text-gray-500 dark:bg-white/6 dark:text-gray-400"
>
{displayKeys()}
</span>
{/if}
</div>
{/if}
@@ -0,0 +1,22 @@
<script lang="ts">
import { getContext } from 'svelte';
import type { Writable } from 'svelte/store';
import type { i18n as i18nType } from 'i18next';
import LockClosed from '$lib/components/icons/LockClosed.svelte';
export let className = '';
export let disabled = false;
const i18n = getContext<Writable<i18nType>>('i18n');
</script>
<button
type="button"
class="flex shrink-0 items-center gap-1 rounded-lg bg-gray-50 px-2 py-1 text-xs font-normal text-gray-900 transition ring-1 ring-gray-200 hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-gray-850 dark:text-gray-100 dark:ring-gray-800 dark:hover:bg-gray-800 {className}"
{disabled}
on:click
>
<LockClosed strokeWidth="2.5" className="size-3 shrink-0" />
<span class="shrink-0">{$i18n.t('Access')}</span>
</button>
+12 -5
View File
@@ -1,19 +1,17 @@
<script lang="ts">
import { onMount } from 'svelte';
import { shortcuts } from '$lib/shortcuts';
import { settings } from '$lib/stores';
import { formatChord, isConfigurableShortcut, keybindings, shortcuts } from '$lib/shortcuts';
import type { Shortcut } from '$lib/shortcuts';
export let name: string;
export let className = '';
let isMac = false;
let mounted = false;
let keys: string[] = [];
let isVisible = true;
onMount(() => {
isMac = /Mac/i.test(navigator.userAgent);
keys = shortcuts[name]?.keys ?? [];
mounted = true;
});
@@ -26,12 +24,21 @@
return key;
}
function displayKeys(): string {
const id = name as Shortcut;
if (isConfigurableShortcut(id)) {
return formatChord($keybindings[id]);
}
const keys = shortcuts[id]?.keys ?? [];
return keys.map(formatKey).join(isMac ? '' : '+');
}
</script>
{#if mounted && isVisible}
<div
class="hidden md:flex items-center self-center text-xs text-gray-400 dark:text-gray-600 {className}"
>
<span>{keys.map(formatKey).join(isMac ? '' : '+')}</span>
<span>{displayKeys()}</span>
</div>
{/if}
+1 -1
View File
@@ -792,7 +792,7 @@
{#if !$mobile && !$showSidebar}
<div
class=" w-[42px] py-1 px-1 flex flex-col justify-between text-black dark:text-white hover:bg-gray-50/30 dark:hover:bg-gray-950/30 h-full z-10 transition-all border-e-[0.5px] border-gray-50 dark:border-gray-850/30"
class=" w-[42px] shrink-0 py-1 px-1 flex flex-col justify-between text-black dark:text-white hover:bg-gray-50/30 dark:hover:bg-gray-950/30 h-full z-10 transition-all border-e-[0.5px] border-gray-50 dark:border-gray-850/30"
id="sidebar"
>
<button
+4 -8
View File
@@ -80,7 +80,7 @@
import Calendar from '../icons/Calendar.svelte';
import Users from '../icons/Users.svelte';
import LockClosed from '../icons/LockClosed.svelte';
import AccessButton from '../common/AccessButton.svelte';
import Image from '../common/Image.svelte';
import FileItem from '../common/FileItem.svelte';
@@ -996,7 +996,7 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
</div>
{/if}
<div class="flex items-center gap-0.5 shrink-0 translate-x-1">
<div class="flex items-center gap-0.5 shrink-0">
{#if note?.write_access}
{#if editor}
<div>
@@ -1107,16 +1107,12 @@ Provide the enhanced notes in markdown format. Use markdown syntax for headings,
</NoteMenu>
{#if note?.write_access}
<button
class="shrink-0 bg-gray-50 hover:bg-gray-100 text-black dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white transition px-2.5 py-1 rounded-full flex gap-1.5 items-center text-sm"
<AccessButton
on:click={() => {
showAccessControlModal = true;
}}
disabled={note?.user_id !== $user?.id && $user?.role !== 'admin'}
>
<LockClosed strokeWidth="2.5" className="size-3.5" />
{$i18n.t('Access')}
</button>
/>
{:else}
<div class="shrink-0 text-xs text-gray-500 px-2 py-1">
{$i18n.t('Read-Only Access')}
+21 -2
View File
@@ -46,6 +46,9 @@
};
};
export let showCreateOnMount = false;
export let createModalCloseHref = '';
let loaded = false;
let showDeleteConfirm = false;
let showCreateModal = false;
@@ -162,6 +165,14 @@
}
};
const closeCreateModal = async () => {
showCreateModal = false;
if (createModalCloseHref) {
await goto(createModalCloseHref);
}
};
const exportHandler = async (item: KnowledgeListItem) => {
try {
const blob = await exportKnowledgeById(localStorage.token, item.id);
@@ -185,6 +196,10 @@
viewOption = localStorage?.workspaceViewOption || '';
sourceOption = localStorage?.workspaceKnowledgeSourceOption || '';
loaded = true;
if (showCreateOnMount) {
showCreateModal = true;
}
});
</script>
@@ -202,11 +217,15 @@
}}
/>
<Modal bind:show={showCreateModal} size="sm">
<Modal
bind:show={showCreateModal}
size="full"
className="!w-[calc(100vw-2rem)] sm:!w-[calc(100vw-3rem)] lg:!w-[calc(100vw-4rem)] !max-w-[80rem] h-[min(54rem,calc(100dvh-4rem))] max-h-[calc(100dvh-4rem)] flex flex-col bg-white dark:bg-gray-900 rounded-4xl"
>
<CreateKnowledgeBase
modal={true}
onBack={() => {
showCreateModal = false;
closeCreateModal();
}}
/>
</Modal>
@@ -51,7 +51,7 @@
};
</script>
<div class="w-full max-h-full">
<div class="w-full max-h-full {modal ? 'h-full flex flex-col' : ''}">
{#if modal}
<div class="flex justify-between items-center dark:text-gray-100 px-5 pt-4 pb-2">
<h3 class="text-base font-normal">{$i18n.t('Create a knowledge base')}</h3>
@@ -92,20 +92,20 @@
{/if}
<form
class="flex flex-col {modal ? 'px-5 pb-3' : 'max-w-lg mx-auto mt-10 mb-10'}"
class="flex flex-col {modal ? 'px-5 pb-3 flex-1 min-h-0' : 'max-w-lg mx-auto mt-10 mb-10'}"
on:submit|preventDefault={() => {
submitHandler();
}}
>
<div class=" w-full flex flex-col justify-center">
<div class="w-full flex flex-col {modal ? 'flex-1 min-h-0' : 'justify-center'}">
{#if !modal}
<div class=" text-2xl font-normal mb-2.5">
{$i18n.t('Create a knowledge base')}
</div>
{/if}
<div class="w-full flex flex-col gap-2.5">
<div class="w-full">
<div class="w-full flex flex-col gap-2.5 {modal ? 'flex-1 min-h-0' : ''}">
<div class="w-full shrink-0">
<div class=" text-sm mb-2">{$i18n.t('What are you working on?')}</div>
<div class="w-full mt-1">
@@ -119,12 +119,14 @@
</div>
</div>
<div>
<div class={modal ? 'flex-1 min-h-0 flex flex-col' : ''}>
<div class="text-sm mb-2">{$i18n.t('What are you trying to achieve?')}</div>
<div class=" w-full mt-1">
<div class="w-full mt-1 {modal ? 'flex-1 min-h-0 flex' : ''}">
<textarea
class="w-full resize-none rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
class="w-full {modal
? 'flex-1 min-h-0'
: ''} resize-none rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
rows="4"
bind:value={description}
placeholder={$i18n.t('Describe your knowledge base and objectives')}
@@ -135,7 +137,7 @@
</div>
</div>
<div class="mt-2">
<div class="mt-2 shrink-0">
<AccessControl
bind:accessGrants
accessRoles={['read', 'write']}
@@ -146,7 +148,7 @@
/>
</div>
<div class="flex justify-end {modal ? 'pt-3 gap-2' : 'mt-2'}">
<div class="flex justify-end {modal ? 'pt-3 gap-2 shrink-0' : 'mt-2'}">
{#if modal}
<button
class="px-3 py-1 text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-200 transition"
@@ -65,7 +65,7 @@
import ConfirmDialog from '../../common/ConfirmDialog.svelte';
import Drawer from '$lib/components/common/Drawer.svelte';
import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte';
import LockClosed from '$lib/components/icons/LockClosed.svelte';
import AccessButton from '$lib/components/common/AccessButton.svelte';
import AccessControlModal from '../common/AccessControlModal.svelte';
import Search from '$lib/components/icons/Search.svelte';
import FilesOverlay from '$lib/components/chat/MessageInput/FilesOverlay.svelte';
@@ -1264,19 +1264,11 @@
{#if knowledge?.write_access}
<div class="self-center shrink-0">
<button
class="bg-gray-50 hover:bg-gray-100 text-black dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white transition px-2 py-1 rounded-full flex gap-1 items-center"
type="button"
<AccessButton
on:click={() => {
showAccessControlModal = true;
}}
>
<LockClosed strokeWidth="2.5" className="size-3.5" />
<div class="text-sm font-normal shrink-0">
{$i18n.t('Access')}
</div>
</button>
/>
</div>
{:else}
<div class="text-xs shrink-0 text-gray-500">
@@ -32,7 +32,7 @@
import TerminalSelector from './TerminalSelector.svelte';
import TTSVoiceInput from './TTSVoiceInput.svelte';
import AccessControlModal from '../common/AccessControlModal.svelte';
import LockClosed from '$lib/components/icons/LockClosed.svelte';
import AccessButton from '$lib/components/common/AccessButton.svelte';
const i18n = getContext('i18n');
@@ -587,19 +587,11 @@
required
/>
<button
class="flex h-7 shrink-0 items-center gap-1 rounded-lg bg-gray-50 px-2 text-xs text-gray-700 transition hover:bg-gray-100 dark:bg-white/[0.03] dark:text-gray-300 dark:hover:bg-white/[0.06]"
type="button"
<AccessButton
on:click={() => {
showAccessControlModal = true;
}}
>
<LockClosed strokeWidth="2.5" className="size-3.5 shrink-0" />
<div class="font-normal shrink-0">
{$i18n.t('Access')}
</div>
</button>
/>
</div>
<input
+21 -5
View File
@@ -48,6 +48,9 @@
is_production?: boolean;
};
export let showCreateOnMount = false;
export let createModalCloseHref = '';
let shiftKey = false;
const i18n = getContext<Writable<i18nType>>('i18n');
@@ -188,6 +191,15 @@
showCreateModal = true;
};
const closeCreateModal = async () => {
showCreateModal = false;
createPrompt = null;
if (createModalCloseHref) {
await goto(createModalCloseHref);
}
};
const createPromptHandler = async (prompt: PromptDraft) => {
const res = await createNewPrompt(localStorage.token, prompt).catch((error) => {
toast.error(`${error}`);
@@ -196,10 +208,9 @@
if (res) {
toast.success($i18n.t('Prompt created successfully'));
showCreateModal = false;
createPrompt = null;
page = 1;
await getPromptList();
await closeCreateModal();
}
};
@@ -274,6 +285,8 @@
const prompt = JSON.parse(sessionStorage.prompt);
sessionStorage.removeItem('prompt');
openCreateModal(toPromptDraft(prompt));
} else if (showCreateOnMount) {
openCreateModal();
}
const onKeyDown = (event: KeyboardEvent) => {
@@ -329,7 +342,11 @@
</div>
</DeleteConfirmDialog>
<Modal bind:show={showCreateModal} size="md">
<Modal
bind:show={showCreateModal}
size="full"
className="!w-[calc(100vw-2rem)] sm:!w-[calc(100vw-3rem)] lg:!w-[calc(100vw-4rem)] !max-w-[80rem] h-[min(54rem,calc(100dvh-4rem))] max-h-[calc(100dvh-4rem)] flex flex-col bg-white dark:bg-gray-900 rounded-4xl"
>
{#key createPrompt}
<PromptEditor
modal={true}
@@ -337,8 +354,7 @@
clone={createPrompt !== null}
onSubmit={createPromptHandler}
onCancel={() => {
showCreateModal = false;
createPrompt = null;
closeCreateModal();
}}
/>
{/key}
@@ -6,7 +6,7 @@
import Textarea from '$lib/components/common/Textarea.svelte';
import { toast } from 'svelte-sonner';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import LockClosed from '$lib/components/icons/LockClosed.svelte';
import AccessButton from '$lib/components/common/AccessButton.svelte';
import Clipboard from '$lib/components/icons/Clipboard.svelte';
import Check from '$lib/components/icons/Check.svelte';
import AccessControlModal from '../common/AccessControlModal.svelte';
@@ -416,13 +416,7 @@
{$i18n.t('Edit')}
</button>
<button
class="bg-gray-50 hover:bg-gray-100 text-black dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white transition px-2.5 py-1 rounded-full flex gap-1.5 items-center text-sm border border-gray-100 dark:border-gray-800"
on:click={() => (showAccessControlModal = true)}
>
<LockClosed strokeWidth="2.5" className="size-3.5" />
{$i18n.t('Access')}
</button>
<AccessButton on:click={() => (showAccessControlModal = true)} />
{:else}
<span class="text-xs text-gray-500 bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded-full"
>{$i18n.t('Read Only')}</span
@@ -537,7 +531,7 @@
</div>
{:else}
<!-- Create mode: Form -->
<div class="w-full max-h-full">
<div class="w-full max-h-full {modal ? 'h-full flex flex-col' : ''}">
{#if modal}
<div class="flex justify-between items-center dark:text-gray-100 px-5 pt-4 pb-2">
<h3 class="text-base font-normal">{$i18n.t('Create Prompt')}</h3>
@@ -555,10 +549,10 @@
{/if}
<form
class="flex flex-col w-full {modal ? 'px-5 pb-3' : 'mb-10'}"
class="flex flex-col w-full {modal ? 'px-5 pb-3 flex-1 min-h-0' : 'mb-10'}"
on:submit|preventDefault={submitHandler}
>
<div class="mb-2">
<div class="mb-2 shrink-0">
<Tooltip
content={`${$i18n.t('Only alphanumeric characters and hyphens are allowed')} - ${$i18n.t('Activate this command by typing "/{{COMMAND}}" to chat input.', { COMMAND: command })}`}
placement="bottom-start"
@@ -572,16 +566,9 @@
required
/>
<div class="self-center shrink-0">
<button
class="bg-gray-50 hover:bg-gray-100 text-black dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white transition px-2 py-1 rounded-full flex gap-1 items-center"
type="button"
on:click={() => (showAccessControlModal = true)}
>
<LockClosed strokeWidth="2.5" className="size-3.5" />
<div class="text-sm font-normal shrink-0">{$i18n.t('Access')}</div>
</button>
<AccessButton on:click={() => (showAccessControlModal = true)} />
</div>
</div>
</div>
<div class="flex gap-0.5 items-center text-xs text-gray-500">
<div>/</div>
<input
@@ -609,16 +596,25 @@
</Tooltip>
</div>
<div class="my-2">
<div class={modal ? 'my-2 flex-1 min-h-0 flex flex-col' : 'my-2'}>
<div class="text-gray-500 text-xs">{$i18n.t('Prompt Content')}</div>
<div class="mt-1">
<Textarea
className="text-sm w-full bg-transparent outline-hidden overflow-y-hidden resize-none"
placeholder={$i18n.t('Write a summary in 50 words that summarizes {{topic}}.')}
bind:value={content}
rows={6}
required
/>
<div class={modal ? 'mt-1 flex-1 min-h-0 flex flex-col' : 'mt-1'}>
{#if modal}
<textarea
class="text-sm w-full flex-1 min-h-0 bg-transparent outline-hidden resize-none"
placeholder={$i18n.t('Write a summary in 50 words that summarizes {{topic}}.')}
bind:value={content}
required
></textarea>
{:else}
<Textarea
className="text-sm w-full bg-transparent outline-hidden overflow-y-hidden resize-none"
placeholder={$i18n.t('Write a summary in 50 words that summarizes {{topic}}.')}
bind:value={content}
rows={6}
required
/>
{/if}
<div class="text-xs text-gray-400 dark:text-gray-500">
ⓘ {$i18n.t('Use')}
<span class="font-normal text-gray-600 dark:text-gray-300"
@@ -629,7 +625,7 @@
</div>
</div>
<div class="flex justify-end {modal ? 'pt-3 gap-2' : 'my-4 pb-20'}">
<div class="flex justify-end {modal ? 'pt-3 gap-2 shrink-0' : 'my-4 pb-20'}">
{#if modal}
<button
class="px-3 py-1 text-xs text-gray-500 hover:text-gray-700 dark:hover:text-gray-200 transition"
@@ -4,7 +4,7 @@
import Textarea from '$lib/components/common/Textarea.svelte';
import { toast } from 'svelte-sonner';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import LockClosed from '$lib/components/icons/LockClosed.svelte';
import AccessButton from '$lib/components/common/AccessButton.svelte';
import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte';
import AccessControlModal from '../common/AccessControlModal.svelte';
import { user } from '$lib/stores';
@@ -134,17 +134,7 @@
<div class="self-center shrink-0">
{#if !disabled}
<button
class="bg-gray-50 hover:bg-gray-100 text-black dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white transition px-2 py-1 rounded-full flex gap-1 items-center"
type="button"
on:click={() => (showAccessControlModal = true)}
>
<LockClosed strokeWidth="2.5" className="size-3.5" />
<div class="text-sm font-normal shrink-0">
{$i18n.t('Access')}
</div>
</button>
<AccessButton on:click={() => (showAccessControlModal = true)} />
{:else}
<span
class="text-xs text-gray-500 bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded-full"
@@ -13,7 +13,7 @@
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import LockClosed from '$lib/components/icons/LockClosed.svelte';
import AccessButton from '$lib/components/common/AccessButton.svelte';
import AccessControlModal from '../common/AccessControlModal.svelte';
let formElement = null;
@@ -252,19 +252,11 @@ class Tools:
</div>
<div class="self-center shrink-0">
<button
class="bg-gray-50 hover:bg-gray-100 text-black dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white transition px-2 py-1 rounded-full flex gap-1 items-center"
type="button"
<AccessButton
on:click={() => {
showAccessControlModal = true;
}}
>
<LockClosed strokeWidth="2.5" className="size-3.5" />
<div class="text-sm font-normal shrink-0">
{$i18n.t('Access')}
</div>
</button>
/>
</div>
</div>