refac
This commit is contained in:
@@ -33,7 +33,6 @@
|
||||
currentChatPage,
|
||||
temporaryChatEnabled,
|
||||
mobile,
|
||||
showOverview,
|
||||
chatTitle,
|
||||
showArtifacts,
|
||||
artifactContents,
|
||||
@@ -704,31 +703,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Persist controls panel state for desktop only
|
||||
if (!$mobile) {
|
||||
localStorage.setItem('showControls', JSON.stringify(value));
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
showCallOverlay.set(false);
|
||||
showOverview.set(false);
|
||||
showArtifacts.set(false);
|
||||
showEmbeds.set(false);
|
||||
}
|
||||
});
|
||||
|
||||
// Restore controls panel state on desktop
|
||||
if (!$mobile) {
|
||||
try {
|
||||
const savedShowControls = localStorage.getItem('showControls');
|
||||
if (savedShowControls !== null && JSON.parse(savedShowControls)) {
|
||||
showControls.set(true);
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
selectedFolderSubscribe = selectedFolder.subscribe(async (folder) => {
|
||||
if (
|
||||
folder?.data?.model_ids &&
|
||||
@@ -1110,7 +1091,6 @@
|
||||
await showControls.set(false);
|
||||
}
|
||||
await showCallOverlay.set(false);
|
||||
await showOverview.set(false);
|
||||
await showArtifacts.set(false);
|
||||
|
||||
if ($page.url.pathname.includes('/c/')) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script context="module">
|
||||
let savedTab = 'controls';
|
||||
let savedTab: 'controls' | 'files' | 'overview' = 'controls';
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -13,7 +13,6 @@
|
||||
mobile,
|
||||
showControls,
|
||||
showCallOverlay,
|
||||
showOverview,
|
||||
showArtifacts,
|
||||
showEmbeds,
|
||||
settings
|
||||
@@ -28,6 +27,7 @@
|
||||
import Artifacts from './Artifacts.svelte';
|
||||
import Embeds from './ChatControls/Embeds.svelte';
|
||||
import FileNav from './FileNav.svelte';
|
||||
import Overview from './Overview.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
@@ -52,11 +52,14 @@
|
||||
let largeScreen = false;
|
||||
let dragged = false;
|
||||
let minSize = 0;
|
||||
let paneReady = false;
|
||||
|
||||
// Tab state for Controls+Files panel
|
||||
let activeTab: 'controls' | 'files' = savedTab as 'controls' | 'files';
|
||||
let activeTab: 'controls' | 'files' | 'overview' = savedTab;
|
||||
$: savedTab = activeTab;
|
||||
$: hasTerminal = !!($settings?.terminalServers ?? []).find((s) => s.enabled)?.url;
|
||||
$: hasMessages = history?.messages && Object.keys(history.messages).length > 0;
|
||||
$: if (!hasMessages && activeTab === 'overview') activeTab = 'controls';
|
||||
|
||||
// Attach a terminal file to the chat input
|
||||
const handleTerminalAttach = async (blob: Blob, name: string, contentType: string) => {
|
||||
@@ -166,9 +169,20 @@
|
||||
|
||||
document.addEventListener('mousedown', onMouseDown);
|
||||
document.addEventListener('mouseup', onMouseUp);
|
||||
|
||||
// Delay enabling onCollapse so the Pane's initial collapsed state
|
||||
// (defaultSize=0) doesn't reset showControls
|
||||
setTimeout(() => {
|
||||
paneReady = true;
|
||||
// If controls were persisted as open, expand the pane now that it's ready
|
||||
if ($showControls && pane) {
|
||||
openPane();
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
paneReady = false;
|
||||
if (!largeScreen) {
|
||||
showControls.set(false);
|
||||
}
|
||||
@@ -178,8 +192,9 @@
|
||||
});
|
||||
|
||||
const closeHandler = () => {
|
||||
showControls.set(false);
|
||||
showOverview.set(false);
|
||||
if (!largeScreen) {
|
||||
showControls.set(false);
|
||||
}
|
||||
showArtifacts.set(false);
|
||||
showEmbeds.set(false);
|
||||
if ($showCallOverlay) showCallOverlay.set(false);
|
||||
@@ -188,7 +203,7 @@
|
||||
$: if (!chatId) closeHandler();
|
||||
|
||||
// Helper: is a "special" full-screen panel active?
|
||||
$: specialPanel = $showCallOverlay || $showOverview || $showArtifacts || $showEmbeds;
|
||||
$: specialPanel = $showCallOverlay || $showArtifacts || $showEmbeds;
|
||||
</script>
|
||||
|
||||
{#if !largeScreen}
|
||||
@@ -198,7 +213,7 @@
|
||||
onClose={() => showControls.set(false)}
|
||||
className="min-h-[100dvh] !bg-white dark:!bg-gray-850"
|
||||
>
|
||||
<div class="h-full flex flex-col">
|
||||
<div class="h-[100dvh] flex flex-col">
|
||||
{#if $showCallOverlay}
|
||||
<div
|
||||
class="h-full max-h-[100dvh] bg-white text-gray-700 dark:bg-black dark:text-gray-300 flex justify-center"
|
||||
@@ -217,17 +232,6 @@
|
||||
<Embeds />
|
||||
{:else if $showArtifacts}
|
||||
<Artifacts {history} />
|
||||
{:else if $showOverview}
|
||||
{#await import('./Overview.svelte') then { default: Overview }}
|
||||
<Overview
|
||||
{history}
|
||||
onNodeClick={(e) => {
|
||||
const node = e.node;
|
||||
showMessage(node.data.message, true);
|
||||
}}
|
||||
onClose={() => showControls.set(false)}
|
||||
/>
|
||||
{/await}
|
||||
{:else}
|
||||
<!-- Controls + Files tabs -->
|
||||
<div class="flex flex-col h-full min-h-0">
|
||||
@@ -252,6 +256,16 @@
|
||||
{$i18n.t('Files')}
|
||||
</button>
|
||||
{/if}
|
||||
{#if hasMessages}
|
||||
<button
|
||||
class="px-2.5 py-1 text-sm rounded-lg transition {activeTab === 'overview'
|
||||
? 'bg-gray-100 dark:bg-gray-800 font-medium text-gray-900 dark:text-white'
|
||||
: 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'}"
|
||||
on:click={() => (activeTab = 'overview')}
|
||||
>
|
||||
{$i18n.t('Overview')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<button
|
||||
class="p-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition text-gray-500 dark:text-gray-400"
|
||||
@@ -272,9 +286,22 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex-1 overflow-y-auto min-h-0 {activeTab === 'controls' ? 'px-3 pt-1' : ''}"
|
||||
class="flex-1 min-h-0 {activeTab === 'overview'
|
||||
? 'h-full'
|
||||
: activeTab === 'controls'
|
||||
? 'overflow-y-auto px-3 pt-1'
|
||||
: 'overflow-y-auto'}"
|
||||
>
|
||||
{#if activeTab === 'files' && hasTerminal}
|
||||
{#if activeTab === 'overview'}
|
||||
<Overview
|
||||
{history}
|
||||
onNodeClick={(e) => {
|
||||
const node = e.node;
|
||||
showMessage(node.data.message, true);
|
||||
}}
|
||||
onClose={() => showControls.set(false)}
|
||||
/>
|
||||
{:else if activeTab === 'files' && hasTerminal}
|
||||
<FileNav onAttach={handleTerminalAttach} />
|
||||
{:else}
|
||||
<Controls embed={true} {models} bind:chatFiles bind:params />
|
||||
@@ -311,7 +338,9 @@
|
||||
}
|
||||
}
|
||||
}}
|
||||
onCollapse={() => showControls.set(false)}
|
||||
onCollapse={() => {
|
||||
if (paneReady) showControls.set(false);
|
||||
}}
|
||||
collapsible={true}
|
||||
class="z-10 bg-white dark:bg-gray-850"
|
||||
>
|
||||
@@ -339,22 +368,6 @@
|
||||
<Embeds overlay={dragged} />
|
||||
{:else if $showArtifacts}
|
||||
<Artifacts {history} overlay={dragged} />
|
||||
{:else if $showOverview}
|
||||
{#await import('./Overview.svelte') then { default: Overview }}
|
||||
<Overview
|
||||
{history}
|
||||
onNodeClick={(e) => {
|
||||
const node = e.node;
|
||||
if (node?.data?.message?.favorite) {
|
||||
history.messages[node.data.message.id].favorite = true;
|
||||
} else {
|
||||
history.messages[node.data.message.id].favorite = null;
|
||||
}
|
||||
showMessage(node.data.message, true);
|
||||
}}
|
||||
onClose={() => showControls.set(false)}
|
||||
/>
|
||||
{/await}
|
||||
{:else}
|
||||
<!-- Controls + Files tabs -->
|
||||
<div class="flex flex-col h-full min-h-0">
|
||||
@@ -379,6 +392,16 @@
|
||||
{$i18n.t('Files')}
|
||||
</button>
|
||||
{/if}
|
||||
{#if hasMessages}
|
||||
<button
|
||||
class="px-2.5 py-1 text-sm rounded-lg transition {activeTab === 'overview'
|
||||
? 'bg-gray-100 dark:bg-gray-800 font-medium text-gray-900 dark:text-white'
|
||||
: 'text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300'}"
|
||||
on:click={() => (activeTab = 'overview')}
|
||||
>
|
||||
{$i18n.t('Overview')}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<button
|
||||
class="p-1 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition text-gray-500 dark:text-gray-400"
|
||||
@@ -399,9 +422,27 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex-1 overflow-y-auto min-h-0 {activeTab === 'controls' ? 'px-3 pt-1' : ''}"
|
||||
class="flex-1 min-h-0 {activeTab === 'overview'
|
||||
? 'h-full'
|
||||
: activeTab === 'controls'
|
||||
? 'overflow-y-auto px-3 pt-1'
|
||||
: 'overflow-y-auto'}"
|
||||
>
|
||||
{#if activeTab === 'files' && hasTerminal}
|
||||
{#if activeTab === 'overview'}
|
||||
<Overview
|
||||
{history}
|
||||
onNodeClick={(e) => {
|
||||
const node = e.node;
|
||||
if (node?.data?.message?.favorite) {
|
||||
history.messages[node.data.message.id].favorite = true;
|
||||
} else {
|
||||
history.messages[node.data.message.id].favorite = null;
|
||||
}
|
||||
showMessage(node.data.message, true);
|
||||
}}
|
||||
onClose={() => showControls.set(false)}
|
||||
/>
|
||||
{:else if activeTab === 'files' && hasTerminal}
|
||||
<FileNav onAttach={handleTerminalAttach} />
|
||||
{:else}
|
||||
<Controls embed={true} {models} bind:chatFiles bind:params />
|
||||
|
||||
@@ -15,7 +15,19 @@
|
||||
export let params = {};
|
||||
export let embed = false;
|
||||
|
||||
let showValves = false;
|
||||
// Persist collapsible section open/close state
|
||||
const getOpen = (key: string, fallback = true): boolean => {
|
||||
const v = localStorage.getItem(`chatControls.${key}`);
|
||||
return v !== null ? v === 'true' : fallback;
|
||||
};
|
||||
const setOpen = (key: string) => (open: boolean) => {
|
||||
localStorage.setItem(`chatControls.${key}`, String(open));
|
||||
};
|
||||
|
||||
let showFiles = getOpen('files');
|
||||
let showValves = getOpen('valves', false);
|
||||
let showSystemPrompt = getOpen('systemPrompt');
|
||||
let showAdvancedParams = getOpen('advancedParams');
|
||||
</script>
|
||||
|
||||
<div class=" dark:text-white">
|
||||
@@ -37,7 +49,12 @@
|
||||
{#if $user?.role === 'admin' || ($user?.permissions.chat?.controls ?? true)}
|
||||
<div class=" dark:text-gray-200 text-sm py-0.5 px-0.5">
|
||||
{#if chatFiles.length > 0}
|
||||
<Collapsible title={$i18n.t('Files')} open={true} buttonClassName="w-full">
|
||||
<Collapsible
|
||||
title={$i18n.t('Files')}
|
||||
bind:open={showFiles}
|
||||
onChange={setOpen('files')}
|
||||
buttonClassName="w-full"
|
||||
>
|
||||
<div class="flex flex-col gap-1 mt-1.5" slot="content">
|
||||
{#each chatFiles as file, fileIdx}
|
||||
<FileItem
|
||||
@@ -68,7 +85,12 @@
|
||||
{/if}
|
||||
|
||||
{#if $user?.role === 'admin' || ($user?.permissions.chat?.valves ?? true)}
|
||||
<Collapsible bind:open={showValves} title={$i18n.t('Valves')} buttonClassName="w-full">
|
||||
<Collapsible
|
||||
bind:open={showValves}
|
||||
onChange={setOpen('valves')}
|
||||
title={$i18n.t('Valves')}
|
||||
buttonClassName="w-full"
|
||||
>
|
||||
<div class="text-sm" slot="content">
|
||||
<Valves show={showValves} />
|
||||
</div>
|
||||
@@ -78,7 +100,12 @@
|
||||
{/if}
|
||||
|
||||
{#if $user?.role === 'admin' || ($user?.permissions.chat?.system_prompt ?? true)}
|
||||
<Collapsible title={$i18n.t('System Prompt')} open={true} buttonClassName="w-full">
|
||||
<Collapsible
|
||||
title={$i18n.t('System Prompt')}
|
||||
bind:open={showSystemPrompt}
|
||||
onChange={setOpen('systemPrompt')}
|
||||
buttonClassName="w-full"
|
||||
>
|
||||
<div class="" slot="content">
|
||||
<textarea
|
||||
bind:value={params.system}
|
||||
@@ -95,7 +122,12 @@
|
||||
{/if}
|
||||
|
||||
{#if $user?.role === 'admin' || ($user?.permissions.chat?.params ?? true)}
|
||||
<Collapsible title={$i18n.t('Advanced Params')} open={true} buttonClassName="w-full">
|
||||
<Collapsible
|
||||
title={$i18n.t('Advanced Params')}
|
||||
bind:open={showAdvancedParams}
|
||||
onChange={setOpen('advancedParams')}
|
||||
buttonClassName="w-full"
|
||||
>
|
||||
<div class="text-sm mt-1.5" slot="content">
|
||||
<div>
|
||||
<AdvancedParams admin={$user?.role === 'admin'} custom={true} bind:params />
|
||||
|
||||
@@ -253,7 +253,6 @@
|
||||
|
||||
onDestroy(() => {
|
||||
if (fileImageUrl) URL.revokeObjectURL(fileImageUrl);
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -524,30 +523,30 @@
|
||||
align="end"
|
||||
transition={flyAndScale}
|
||||
>
|
||||
{#if entry.type !== "directory"}
|
||||
<DropdownMenu.Item
|
||||
type="button"
|
||||
class="select-none flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition items-center gap-2 text-sm"
|
||||
on:click={(e) => {
|
||||
e.stopPropagation();
|
||||
downloadFile(`${currentPath}${entry.name}`);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="size-4"
|
||||
{#if entry.type !== 'directory'}
|
||||
<DropdownMenu.Item
|
||||
type="button"
|
||||
class="select-none flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition items-center gap-2 text-sm"
|
||||
on:click={(e) => {
|
||||
e.stopPropagation();
|
||||
downloadFile(`${currentPath}${entry.name}`);
|
||||
}}
|
||||
>
|
||||
<path
|
||||
d="M10.75 2.75a.75.75 0 0 0-1.5 0v8.614L6.295 8.235a.75.75 0 1 0-1.09 1.03l4.25 4.5a.75.75 0 0 0 1.09 0l4.25-4.5a.75.75 0 0 0-1.09-1.03l-2.955 3.129V2.75Z"
|
||||
/>
|
||||
<path
|
||||
d="M3.5 12.75a.75.75 0 0 0-1.5 0v2.5A2.75 2.75 0 0 0 4.75 18h10.5A2.75 2.75 0 0 0 18 15.25v-2.5a.75.75 0 0 0-1.5 0v2.5c0 .69-.56 1.25-1.25 1.25H4.75c-.69 0-1.25-.56-1.25-1.25v-2.5Z"
|
||||
/>
|
||||
</svg>
|
||||
<div class="flex items-center">{$i18n.t('Download')}</div>
|
||||
</DropdownMenu.Item>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="size-4"
|
||||
>
|
||||
<path
|
||||
d="M10.75 2.75a.75.75 0 0 0-1.5 0v8.614L6.295 8.235a.75.75 0 1 0-1.09 1.03l4.25 4.5a.75.75 0 0 0 1.09 0l4.25-4.5a.75.75 0 0 0-1.09-1.03l-2.955 3.129V2.75Z"
|
||||
/>
|
||||
<path
|
||||
d="M3.5 12.75a.75.75 0 0 0-1.5 0v2.5A2.75 2.75 0 0 0 4.75 18h10.5A2.75 2.75 0 0 0 18 15.25v-2.5a.75.75 0 0 0-1.5 0v2.5c0 .69-.56 1.25-1.25 1.25H4.75c-.69 0-1.25-.56-1.25-1.25v-2.5Z"
|
||||
/>
|
||||
</svg>
|
||||
<div class="flex items-center">{$i18n.t('Download')}</div>
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
|
||||
<DropdownMenu.Item
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
settings,
|
||||
showArtifacts,
|
||||
showControls,
|
||||
showEmbeds,
|
||||
showOverview
|
||||
showEmbeds
|
||||
} from '$lib/stores';
|
||||
import FloatingButtons from '../ContentRenderer/FloatingButtons.svelte';
|
||||
import { createMessagesList } from '$lib/utils';
|
||||
@@ -195,7 +194,6 @@
|
||||
await artifactCode.set(value);
|
||||
await showControls.set(true);
|
||||
await showArtifacts.set(true);
|
||||
await showOverview.set(false);
|
||||
await showEmbeds.set(false);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="px-4 py-3 shadow-md rounded-xl dark:bg-black bg-white border dark:border-gray-900 w-60 h-20 group"
|
||||
class="px-4 py-3 shadow-md rounded-xl dark:bg-black bg-white border border-gray-100 dark:border-gray-900 w-60 h-20 group"
|
||||
>
|
||||
<Tooltip
|
||||
content={data?.message?.error ? data.message.error.content : data.message.content}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { onMount, tick } from 'svelte';
|
||||
|
||||
import { writable } from 'svelte/store';
|
||||
import { models, showOverview, theme, user } from '$lib/stores';
|
||||
import { models, theme, user } from '$lib/stores';
|
||||
|
||||
import '@xyflow/svelte/dist/style.css';
|
||||
|
||||
@@ -172,31 +172,6 @@
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full relative">
|
||||
<div class=" absolute z-50 w-full flex justify-between dark:text-gray-100 px-4 py-3">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<button
|
||||
class="self-center p-0.5"
|
||||
aria-label={$i18n.t('Back')}
|
||||
on:click={() => {
|
||||
showOverview.set(false);
|
||||
}}
|
||||
>
|
||||
<ArrowLeft className="size-3.5" />
|
||||
</button>
|
||||
<div class=" text-lg font-medium self-center font-primary">{$i18n.t('Chat Overview')}</div>
|
||||
</div>
|
||||
<button
|
||||
class="self-center p-0.5"
|
||||
aria-label={$i18n.t('Close overview')}
|
||||
on:click={() => {
|
||||
onClose();
|
||||
showOverview.set(false);
|
||||
}}
|
||||
>
|
||||
<XMark className="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if $nodes.length > 0}
|
||||
<Flow
|
||||
{nodes}
|
||||
|
||||
@@ -11,6 +11,14 @@
|
||||
class={className}
|
||||
>
|
||||
<path d="M18 6H20M22 6H20M20 6V4M20 6V8" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M21.4 20H2.6C2.26863 20 2 19.7314 2 19.4V11H21.4C21.7314 11 22 11.2686 22 11.6V19.4C22 19.7314 21.7314 20 21.4 20Z" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M2 11V4.6C2 4.26863 2.26863 4 2.6 4H8.77805C8.92127 4 9.05977 4.05124 9.16852 4.14445L12.3315 6.85555C12.4402 6.94876 12.5787 7 12.722 7H14" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path
|
||||
d="M21.4 20H2.6C2.26863 20 2 19.7314 2 19.4V11H21.4C21.7314 11 22 11.2686 22 11.6V19.4C22 19.7314 21.7314 20 21.4 20Z"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M2 11V4.6C2 4.26863 2.26863 4 2.6 4H8.77805C8.92127 4 9.05977 4.05124 9.16852 4.14445L12.3315 6.85555C12.4402 6.94876 12.5787 7 12.722 7H14"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
import { copyToClipboard, createMessagesList } from '$lib/utils';
|
||||
|
||||
import {
|
||||
showOverview,
|
||||
showControls,
|
||||
showArtifacts,
|
||||
mobile,
|
||||
@@ -27,7 +26,6 @@
|
||||
|
||||
import Dropdown from '$lib/components/common/Dropdown.svelte';
|
||||
import Tags from '$lib/components/chat/Tags.svelte';
|
||||
import Map from '$lib/components/icons/Map.svelte';
|
||||
import Clipboard from '$lib/components/icons/Clipboard.svelte';
|
||||
import AdjustmentsHorizontal from '$lib/components/icons/AdjustmentsHorizontal.svelte';
|
||||
import Cube from '$lib/components/icons/Cube.svelte';
|
||||
@@ -320,7 +318,6 @@
|
||||
id="chat-controls-button"
|
||||
on:click={async () => {
|
||||
await showControls.set(true);
|
||||
await showOverview.set(false);
|
||||
await showArtifacts.set(false);
|
||||
await showEmbeds.set(false);
|
||||
}}
|
||||
@@ -329,31 +326,14 @@
|
||||
<div class="flex items-center">{$i18n.t('Controls')}</div>
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
|
||||
<DropdownMenu.Item
|
||||
draggable="false"
|
||||
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl select-none w-full"
|
||||
id="chat-overview-button"
|
||||
on:click={async () => {
|
||||
await showControls.set(true);
|
||||
await showOverview.set(true);
|
||||
await showArtifacts.set(false);
|
||||
await showEmbeds.set(false);
|
||||
}}
|
||||
>
|
||||
<Map className=" size-4" strokeWidth="1.5" />
|
||||
<div class="flex items-center">{$i18n.t('Overview')}</div>
|
||||
</DropdownMenu.Item>
|
||||
|
||||
{#if ($artifactContents ?? []).length > 0}
|
||||
<DropdownMenu.Item
|
||||
draggable="false"
|
||||
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl select-none w-full"
|
||||
id="chat-overview-button"
|
||||
id="chat-artifacts-button"
|
||||
on:click={async () => {
|
||||
await showControls.set(true);
|
||||
await showArtifacts.set(true);
|
||||
await showOverview.set(false);
|
||||
await showEmbeds.set(false);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -34,7 +34,9 @@
|
||||
toolServers,
|
||||
terminalServers,
|
||||
showSearch,
|
||||
showSidebar
|
||||
showSidebar,
|
||||
showControls,
|
||||
mobile
|
||||
} from '$lib/stores';
|
||||
|
||||
import Sidebar from '$lib/components/layout/Sidebar.svelte';
|
||||
@@ -322,6 +324,14 @@
|
||||
checkForVersionUpdates();
|
||||
}
|
||||
}
|
||||
// Persist showControls via chatControlsSize (0 = closed, >0 = open at that size)
|
||||
await showControls.set(!$mobile ? parseInt(localStorage.chatControlsSize || '0') > 0 : false);
|
||||
showControls.subscribe((value) => {
|
||||
if (!value) {
|
||||
localStorage.chatControlsSize = '0';
|
||||
}
|
||||
});
|
||||
|
||||
await tick();
|
||||
|
||||
loaded = true;
|
||||
|
||||
Reference in New Issue
Block a user