This commit is contained in:
Timothy Jaeryang Baek
2026-08-14 23:47:00 -06:00
parent 516cf1a9a6
commit a5ea732c1e
3 changed files with 120 additions and 3 deletions
+32 -1
View File
@@ -168,6 +168,13 @@
(history.currentId && history.messages[history.currentId]?.done != true) ||
generating);
$: canCompact = !!history?.currentId;
$: canToggleTemporary =
!embedded &&
!chatId &&
($_user?.role === 'admin' ||
($_user?.role === 'user' &&
($_user?.permissions?.chat?.temporary ?? true) &&
!($_user?.permissions?.chat?.temporary_enforced ?? false)));
export let prompt = '';
export let files = [];
@@ -539,6 +546,26 @@
chatInputElement?.replaceCommandWithText(text);
};
const temporaryHandler = async () => {
if (!canToggleTemporary) return;
if (($settings?.temporaryChatByDefault ?? false) && $temporaryChatEnabled) {
await temporaryChatEnabled.set(null);
} else {
await temporaryChatEnabled.set(!$temporaryChatEnabled);
}
if (location.pathname !== '/') {
await goto('/');
}
if ($temporaryChatEnabled) {
window.history.replaceState(null, '', '?temporary-chat=true');
} else {
window.history.replaceState(null, '', location.pathname);
}
};
const insertTextAtCursor = async (text: string) => {
const chatInput = document.getElementById('chat-input');
if (!chatInput) return;
@@ -1281,7 +1308,7 @@
return;
}
if (['compact', 'fork', 'status', 'model'].includes(props?.id)) {
if (['compact', 'fork', 'status', 'model', 'settings', 'temporary'].includes(props?.id)) {
editor.chain().focus().deleteRange(range).run();
return;
}
@@ -1307,11 +1334,15 @@
!!history?.currentId &&
($_user?.role === 'admin' || ($_user?.permissions?.chat?.import ?? true)),
forkDisabled: () => isActive,
canTemporary: () => canToggleTemporary,
temporaryEnabled: () => $temporaryChatEnabled === true,
contextUsage: () => statusContextUsage,
onCompact: compactHandler,
onStatus: statusHandler,
onFork: forkHandler,
onModel: () => modelSelector?.open(),
onSettings: () => showSettings.set(true),
onTemporary: temporaryHandler,
onSelect: (e) => {
const { type, data } = e;
@@ -21,12 +21,16 @@
export let onStatus: () => void = () => {};
export let onFork: () => void = () => {};
export let onModel: () => void = () => {};
export let onSettings: () => void = () => {};
export let onTemporary: () => void = () => {};
export let insertTextHandler: (text: string) => void = () => {};
export let canCompact: boolean | (() => boolean) = false;
export let compactDisabled: boolean | (() => boolean) = false;
export let canStatus: boolean | (() => boolean) = false;
export let canFork: boolean | (() => boolean) = false;
export let forkDisabled: boolean | (() => boolean) = false;
export let canTemporary: boolean | (() => boolean) = false;
export let temporaryEnabled: boolean | (() => boolean) = false;
export let contextUsage = null;
$: compactAvailable = typeof canCompact === 'function' ? canCompact() : canCompact;
@@ -35,6 +39,9 @@
$: statusAvailable = typeof canStatus === 'function' ? canStatus() : canStatus;
$: forkAvailable = typeof canFork === 'function' ? canFork() : canFork;
$: isForkDisabled = typeof forkDisabled === 'function' ? forkDisabled() : forkDisabled;
$: temporaryAvailable = typeof canTemporary === 'function' ? canTemporary() : canTemporary;
$: isTemporaryEnabled =
typeof temporaryEnabled === 'function' ? temporaryEnabled() : temporaryEnabled;
$: resolvedContextUsage = typeof contextUsage === 'function' ? contextUsage() : contextUsage;
$: contextHasThreshold = Number(resolvedContextUsage?.threshold) > 0;
$: contextPercent = contextHasThreshold
@@ -93,6 +100,8 @@
canStatus={statusAvailable}
canFork={forkAvailable}
forkDisabled={isForkDisabled}
canTemporary={temporaryAvailable}
temporaryEnabled={isTemporaryEnabled}
{contextPercent}
{contextHasThreshold}
onSelect={(e) => {
@@ -117,6 +126,12 @@
} else if (type === 'command' && data.id === 'model') {
command({ id: data.id, label: data.id });
onModel();
} else if (type === 'command' && data.id === 'settings') {
command({ id: data.id, label: data.id });
onSettings();
} else if (type === 'command' && data.id === 'temporary') {
command({ id: data.id, label: data.id });
onTemporary();
} else if (type === 'skill') {
command({
id: `${data.id}|${data.name}`,
@@ -3,7 +3,10 @@
import { getPrompts } from '$lib/apis/prompts';
import { getSkillItems } from '$lib/apis/skills';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import ChatBubbleDotted from '$lib/components/icons/ChatBubbleDotted.svelte';
import ChatBubbleDottedChecked from '$lib/components/icons/ChatBubbleDottedChecked.svelte';
import Cube from '$lib/components/icons/Cube.svelte';
import Knobs from '$lib/components/icons/Knobs.svelte';
import Sparkles from '$lib/components/icons/Sparkles.svelte';
const i18n = getContext('i18n');
@@ -15,6 +18,8 @@
export let canStatus = false;
export let canFork = false;
export let forkDisabled = false;
export let canTemporary = false;
export let temporaryEnabled = false;
export let contextPercent = 0;
export let contextHasThreshold = false;
@@ -31,6 +36,9 @@
$: contextCircleOffset = 50.27 * (1 - contextCirclePercent / 100);
$: commandItems = [
...(canTemporary && 'temporary'.startsWith(query.toLowerCase())
? [{ type: 'command', data: { id: 'temporary' } }]
: []),
...(canCompact && 'compact'.startsWith(query.toLowerCase())
? [{ type: 'command', data: { id: 'compact' } }]
: []),
@@ -40,7 +48,12 @@
...(canStatus && 'status'.startsWith(query.toLowerCase())
? [{ type: 'command', data: { id: 'status' } }]
: []),
...('model'.startsWith(query.toLowerCase()) ? [{ type: 'command', data: { id: 'model' } }] : [])
...('model'.startsWith(query.toLowerCase())
? [{ type: 'command', data: { id: 'model' } }]
: []),
...('settings'.startsWith(query.toLowerCase())
? [{ type: 'command', data: { id: 'settings' } }]
: [])
];
$: filteredPrompts = prompts
@@ -128,7 +141,39 @@
</div>
{#each commandItems as item, commandIdx}
{#if item.data.id === 'compact'}
{#if item.data.id === 'temporary'}
<Tooltip content="Toggle temporary chat for this new chat." placement="top">
<button
type="button"
aria-label="Temporary: toggle temporary chat for this new chat."
class="slash-command-row flex items-center gap-2 w-full h-6 px-2 rounded-xl text-xs text-left transition-colors duration-75
{commandIdx === selectedIdx ? 'app-interactive-active' : ''}"
on:mousedown={(e) => e.preventDefault()}
on:click={() => {
onSelect(item);
}}
on:mouseenter={() => {
selectedIdx = commandIdx;
}}
on:focus={() => {}}
data-selected={commandIdx === selectedIdx}
>
<span class="app-icon-muted flex items-center justify-center w-4 shrink-0">
{#if temporaryEnabled}
<ChatBubbleDottedChecked className="size-3.5" strokeWidth="1.6" />
{:else}
<ChatBubbleDotted className="size-3.5" strokeWidth="1.6" />
{/if}
</span>
<span class="flex-1 min-w-0 flex items-baseline gap-1.5 overflow-hidden">
<span class="truncate">Temporary</span>
<span class="app-muted text-[0.625rem] truncate shrink-0">
{temporaryEnabled ? 'On' : 'Off'}
</span>
</span>
</button>
</Tooltip>
{:else if item.data.id === 'compact'}
<Tooltip content="Shorten older messages so this chat can keep going." placement="top">
<button
type="button"
@@ -294,6 +339,32 @@
</span>
</button>
</Tooltip>
{:else if item.data.id === 'settings'}
<Tooltip content="Open settings." placement="top">
<button
type="button"
aria-label="Settings: open settings."
class="slash-command-row flex items-center gap-2 w-full h-6 px-2 rounded-xl text-xs text-left transition-colors duration-75
{commandIdx === selectedIdx ? 'app-interactive-active' : ''}"
on:mousedown={(e) => e.preventDefault()}
on:click={() => {
onSelect(item);
}}
on:mouseenter={() => {
selectedIdx = commandIdx;
}}
on:focus={() => {}}
data-selected={commandIdx === selectedIdx}
>
<span class="app-icon-muted flex items-center justify-center w-4 shrink-0">
<Knobs className="size-3.5" />
</span>
<span class="flex-1 min-w-0 flex items-baseline gap-1.5 overflow-hidden">
<span class="truncate">Settings</span>
<span class="app-muted text-[0.625rem] truncate shrink-0">/settings</span>
</span>
</button>
</Tooltip>
{/if}
{/each}
{/if}