feat: WIP
This commit is contained in:
parent
7a13b4191a
commit
ec6302960e
|
|
@ -35,7 +35,7 @@
|
||||||
{size}
|
{size}
|
||||||
{disabled}
|
{disabled}
|
||||||
{onclick}
|
{onclick}
|
||||||
class="h-6 w-6 p-0 {className} flex"
|
class="h-6 w-6 p-0 {className} flex hover:bg-transparent! data-[state=open]:bg-transparent!"
|
||||||
aria-label={ariaLabel || tooltip}
|
aria-label={ariaLabel || tooltip}
|
||||||
>
|
>
|
||||||
{@const IconComponent = icon}
|
{@const IconComponent = icon}
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,12 @@
|
||||||
Monitor,
|
Monitor,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
ListRestart
|
ListRestart,
|
||||||
|
|
||||||
|
Sliders
|
||||||
|
|
||||||
} from '@lucide/svelte';
|
} from '@lucide/svelte';
|
||||||
import { ChatSettingsFooter, ChatSettingsFields } from '$lib/components/app';
|
import { ChatSettingsFooter, ChatSettingsFields } from '$lib/components/app';
|
||||||
import { ScrollArea } from '$lib/components/ui/scroll-area';
|
|
||||||
import { config, settingsStore } from '$lib/stores/settings.svelte';
|
import { config, settingsStore } from '$lib/stores/settings.svelte';
|
||||||
import {
|
import {
|
||||||
SETTINGS_SECTION_TITLES,
|
SETTINGS_SECTION_TITLES,
|
||||||
|
|
@ -40,7 +42,7 @@
|
||||||
}> = [
|
}> = [
|
||||||
{
|
{
|
||||||
title: SETTINGS_SECTION_TITLES.GENERAL,
|
title: SETTINGS_SECTION_TITLES.GENERAL,
|
||||||
icon: Settings,
|
icon: Sliders,
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
key: SETTINGS_KEYS.THEME,
|
key: SETTINGS_KEYS.THEME,
|
||||||
|
|
@ -128,13 +130,18 @@
|
||||||
type: SettingsFieldType.CHECKBOX
|
type: SettingsFieldType.CHECKBOX
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: SETTINGS_KEYS.AUTO_SHOW_SIDEBAR_ON_NEW_CHAT,
|
key: SETTINGS_KEYS.SHOW_RAW_MODEL_NAMES,
|
||||||
label: 'Auto-show sidebar on new chat',
|
label: 'Show raw model names',
|
||||||
type: SettingsFieldType.CHECKBOX
|
type: SettingsFieldType.CHECKBOX
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: SETTINGS_KEYS.SHOW_RAW_MODEL_NAMES,
|
key: SETTINGS_KEYS.ALWAYS_SHOW_AGENTIC_TURNS,
|
||||||
label: 'Show raw model names',
|
label: 'Always show agentic turns in conversation',
|
||||||
|
type: SettingsFieldType.CHECKBOX
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: SETTINGS_KEYS.SHOW_TOOL_CALL_IN_PROGRESS,
|
||||||
|
label: 'Show tool call in progress',
|
||||||
type: SettingsFieldType.CHECKBOX
|
type: SettingsFieldType.CHECKBOX
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -260,20 +267,10 @@
|
||||||
label: 'Agentic turns',
|
label: 'Agentic turns',
|
||||||
type: SettingsFieldType.INPUT
|
type: SettingsFieldType.INPUT
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: SETTINGS_KEYS.ALWAYS_SHOW_AGENTIC_TURNS,
|
|
||||||
label: 'Always show agentic turns in conversation',
|
|
||||||
type: SettingsFieldType.CHECKBOX
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: SETTINGS_KEYS.AGENTIC_MAX_TOOL_PREVIEW_LINES,
|
key: SETTINGS_KEYS.AGENTIC_MAX_TOOL_PREVIEW_LINES,
|
||||||
label: 'Max lines per tool preview',
|
label: 'Max lines per tool preview',
|
||||||
type: SettingsFieldType.INPUT
|
type: SettingsFieldType.INPUT
|
||||||
},
|
|
||||||
{
|
|
||||||
key: SETTINGS_KEYS.SHOW_TOOL_CALL_IN_PROGRESS,
|
|
||||||
label: 'Show tool call in progress',
|
|
||||||
type: SettingsFieldType.CHECKBOX
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -433,17 +430,15 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<div class="flex h-full flex-col {className} w-full">
|
<div class="flex h-full flex-col overflow-y-auto {className} w-full">
|
||||||
<div class="flex items-center gap-2 w-full md:absolute md:top-8">
|
<div class="flex flex-1 flex-col md:flex-row gap-4">
|
||||||
<Settings class="h-6 w-6" />
|
|
||||||
|
|
||||||
<h1 class="text-2xl font-semibold">Settings</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col overflow-hidden md:flex-row gap-4">
|
|
||||||
<!-- Desktop Sidebar -->
|
<!-- Desktop Sidebar -->
|
||||||
<div class="hidden w-64 pt-8 mt-16 md:block">
|
<div class="hidden w-64 md:flex flex-col sticky top-0 self-start bg-background pt-8 pb-4">
|
||||||
<nav class="space-y-1 py-2">
|
<div class="flex items-center gap-2 pb-6">
|
||||||
|
<Settings class="h-6 w-6" />
|
||||||
|
<h1 class="text-2xl font-semibold">Settings</h1>
|
||||||
|
</div>
|
||||||
|
<nav class="space-y-1">
|
||||||
{#each settingSections as section (section.title)}
|
{#each settingSections as section (section.title)}
|
||||||
<button
|
<button
|
||||||
class="flex w-full cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-left text-sm transition-colors hover:bg-accent {activeSection ===
|
class="flex w-full cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-left text-sm transition-colors hover:bg-accent {activeSection ===
|
||||||
|
|
@ -461,8 +456,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mobile Header with Horizontal Scrollable Menu -->
|
<!-- Mobile Header with Horizontal Scrollable Menu -->
|
||||||
<div class="flex flex-col pt-6 md:hidden">
|
<div class="flex flex-col md:hidden sticky top-0 z-10 bg-background">
|
||||||
<div class="border-b border-border/30 pt-4 md:py-4">
|
<div class="flex items-center gap-2 px-4 pt-6 pb-2">
|
||||||
|
<Settings class="h-6 w-6" />
|
||||||
|
<h1 class="text-2xl font-semibold">Settings</h1>
|
||||||
|
</div>
|
||||||
|
<div class="border-b border-border/30 py-2">
|
||||||
<!-- Horizontal Scrollable Category Menu with Navigation -->
|
<!-- Horizontal Scrollable Category Menu with Navigation -->
|
||||||
<div class="relative flex items-center" style="scroll-padding: 1rem;">
|
<div class="relative flex items-center" style="scroll-padding: 1rem;">
|
||||||
<button
|
<button
|
||||||
|
|
@ -512,8 +511,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ScrollArea class="flex-1 max-w-5xl mx-auto">
|
<div class="flex-1 max-w-5xl mx-auto">
|
||||||
<div class="space-y-6 p-4 md:p-6 md:mt-20">
|
<div class="space-y-6 p-4 md:p-6 md:pt-22">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="mb-6 flex hidden items-center gap-2 border-b border-border/30 pb-6 md:flex">
|
<div class="mb-6 flex hidden items-center gap-2 border-b border-border/30 pb-6 md:flex">
|
||||||
<currentSection.icon class="h-5 w-5" />
|
<currentSection.icon class="h-5 w-5" />
|
||||||
|
|
@ -537,7 +536,7 @@
|
||||||
<p class="text-xs text-muted-foreground">Settings are saved in browser's localStorage</p>
|
<p class="text-xs text-muted-foreground">Settings are saved in browser's localStorage</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex justify-between p-6">
|
<div class="flex justify-between border-t border-border/30 p-6">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<Button variant="outline" onclick={handleResetClick}>
|
<Button variant="outline" onclick={handleResetClick}>
|
||||||
<RotateCcw class="h-3 w-3" />
|
<RotateCcw class="h-3 w-3" />
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button onclick={handleSave}>Save settings</Button>
|
<Button class="sticky bottom-6 z-10" onclick={handleSave}>Save settings</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AlertDialog.Root bind:open={showResetDialog}>
|
<AlertDialog.Root bind:open={showResetDialog}>
|
||||||
|
|
|
||||||
|
|
@ -108,10 +108,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let chatSidebarActions: { activateSearch?: () => void } | undefined = $state();
|
||||||
|
|
||||||
export function activateSearchMode() {
|
export function activateSearchMode() {
|
||||||
isSearchModeActive = true;
|
chatSidebarActions?.activateSearch?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!sidebar.open) {
|
||||||
|
isSearchModeActive = false;
|
||||||
|
searchQuery = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export function editActiveConversation() {
|
export function editActiveConversation() {
|
||||||
if (currentChatId) {
|
if (currentChatId) {
|
||||||
const activeConversation = filteredConversations.find((conv) => conv.id === currentChatId);
|
const activeConversation = filteredConversations.find((conv) => conv.id === currentChatId);
|
||||||
|
|
@ -148,7 +157,7 @@
|
||||||
<h1 class="inline-flex items-center gap-1 px-2 text-xl font-semibold">llama.cpp</h1>
|
<h1 class="inline-flex items-center gap-1 px-2 text-xl font-semibold">llama.cpp</h1>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<ChatSidebarActions {handleMobileSidebarItemClick} bind:isSearchModeActive bind:searchQuery />
|
<ChatSidebarActions bind:this={chatSidebarActions} {handleMobileSidebarItemClick} bind:isSearchModeActive bind:searchQuery />
|
||||||
</Sidebar.Header>
|
</Sidebar.Header>
|
||||||
|
|
||||||
<Sidebar.Group class="mt-2 space-y-2 p-0 px-3">
|
<Sidebar.Group class="mt-2 space-y-2 p-0 px-3">
|
||||||
|
|
|
||||||
|
|
@ -20,17 +20,25 @@
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
const importExportDialog = getImportExportDialogContext();
|
const importExportDialog = getImportExportDialogContext();
|
||||||
|
let searchInputRef = $state<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
function handleSearchModeDeactivate() {
|
function handleSearchModeDeactivate() {
|
||||||
isSearchModeActive = false;
|
isSearchModeActive = false;
|
||||||
searchQuery = '';
|
searchQuery = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function activateSearch() {
|
||||||
|
isSearchModeActive = true;
|
||||||
|
// Focus after Svelte renders the input
|
||||||
|
queueMicrotask(() => searchInputRef?.focus());
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="my-1 space-y-1">
|
<div class="my-1 space-y-1">
|
||||||
{#if isSearchModeActive}
|
{#if isSearchModeActive}
|
||||||
<SearchInput
|
<SearchInput
|
||||||
bind:value={searchQuery}
|
bind:value={searchQuery}
|
||||||
|
bind:ref={searchInputRef}
|
||||||
onClose={handleSearchModeDeactivate}
|
onClose={handleSearchModeDeactivate}
|
||||||
onKeyDown={(e) => e.key === 'Escape' && handleSearchModeDeactivate()}
|
onKeyDown={(e) => e.key === 'Escape' && handleSearchModeDeactivate()}
|
||||||
placeholder="Search conversations..."
|
placeholder="Search conversations..."
|
||||||
|
|
@ -54,9 +62,7 @@
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
class="w-full justify-between px-2 backdrop-blur-none! hover:[&>kbd]:opacity-100"
|
class="w-full justify-between px-2 backdrop-blur-none! hover:[&>kbd]:opacity-100"
|
||||||
onclick={() => {
|
onclick={activateSearch}
|
||||||
isSearchModeActive = true;
|
|
||||||
}}
|
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|
|
||||||
|
|
@ -84,22 +84,23 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2 absolute left-8 top-8">
|
||||||
|
<McpLogo class="h-6 w-6" />
|
||||||
|
|
||||||
|
<h1 class="text-2xl font-semibold">MCP Servers</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-start justify-end gap-4 py-4 sticky top-0 z-10 px-8 mt-4">
|
||||||
|
{#if !isAddingServer}
|
||||||
|
<Button variant="outline" size="sm" class="shrink-0" onclick={showAddServerForm}>
|
||||||
|
<Plus class="h-4 w-4" />
|
||||||
|
|
||||||
|
Add New Server
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-5 md:space-y-4 {className}">
|
<div class="grid gap-5 md:space-y-4 {className}">
|
||||||
<div class="flex items-start justify-between gap-4">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<McpLogo class="h-6 w-6" />
|
|
||||||
|
|
||||||
<h1 class="text-2xl font-semibold">MCP Servers</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if !isAddingServer}
|
|
||||||
<Button variant="outline" size="sm" class="shrink-0" onclick={showAddServerForm}>
|
|
||||||
<Plus class="h-4 w-4" />
|
|
||||||
|
|
||||||
Add New Server
|
|
||||||
</Button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if isAddingServer}
|
{#if isAddingServer}
|
||||||
<Card.Root class="bg-muted/30 p-4">
|
<Card.Root class="bg-muted/30 p-4">
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,8 @@
|
||||||
<div
|
<div
|
||||||
data-slot="sidebar-gap"
|
data-slot="sidebar-gap"
|
||||||
class={cn(
|
class={cn(
|
||||||
'relative w-[calc(var(--sidebar-width)+0.75rem)] bg-transparent transition-[width] duration-200 ease-linear',
|
'relative bg-transparent transition-[width] duration-200 ease-linear',
|
||||||
|
variant === 'floating' ? 'w-[calc(var(--sidebar-width)+0.75rem)]' : 'w-(--sidebar-width)',
|
||||||
'group-data-[collapsible=offcanvas]:w-0',
|
'group-data-[collapsible=offcanvas]:w-0',
|
||||||
'group-data-[side=right]:rotate-180',
|
'group-data-[side=right]:rotate-180',
|
||||||
variant === 'floating' || variant === 'inset'
|
variant === 'floating' || variant === 'inset'
|
||||||
|
|
@ -78,25 +79,36 @@
|
||||||
<div
|
<div
|
||||||
data-slot="sidebar-container"
|
data-slot="sidebar-container"
|
||||||
class={cn(
|
class={cn(
|
||||||
'fixed inset-y-0 z-999 hidden w-(--sidebar-width) transition-[left,right,width,opacity] duration-200 ease-linear md:z-0 md:flex',
|
'fixed inset-y-0 z-999 hidden w-(--sidebar-width) duration-200 ease-linear md:z-0 md:flex',
|
||||||
side === 'left'
|
variant === 'floating'
|
||||||
? 'left-3 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-0.775)] group-data-[collapsible=offcanvas]:opacity-0'
|
? [
|
||||||
: 'right-1 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-0.775)] group-data-[collapsible=offcanvas]:opacity-0',
|
'transition-[left,right,width,opacity]',
|
||||||
// Adjust the padding for floating and inset variants.
|
side === 'left'
|
||||||
variant === 'floating' || variant === 'inset'
|
? 'left-3 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-0.775)] group-data-[collapsible=offcanvas]:opacity-0'
|
||||||
|
: 'right-3 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-0.775)] group-data-[collapsible=offcanvas]:opacity-0',
|
||||||
|
'my-3 overflow-hidden rounded-2xl border border-sidebar-border shadow-md',
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
'transition-[left,right,width] h-svh',
|
||||||
|
side === 'left'
|
||||||
|
? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
|
||||||
|
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
|
||||||
|
],
|
||||||
|
// Adjust the padding for inset variant.
|
||||||
|
variant === 'inset'
|
||||||
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'
|
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'
|
||||||
: 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)',
|
: variant === 'floating'
|
||||||
// Add margin and rounded corners
|
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'
|
||||||
'my-3 overflow-hidden rounded-2xl border border-sidebar-border shadow-md',
|
: 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
style="height: calc(100dvh - 1.5rem);"
|
style={variant === 'floating' ? 'height: calc(100dvh - 1.5rem);' : undefined}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
data-sidebar="sidebar"
|
data-sidebar="sidebar"
|
||||||
data-slot="sidebar-inner"
|
data-slot="sidebar-inner"
|
||||||
class="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow-sm"
|
class="flex h-full w-full flex-col bg-sidebar"
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ export const SETTING_CONFIG_DEFAULT: Record<string, string | number | boolean |
|
||||||
disableAutoScroll: false,
|
disableAutoScroll: false,
|
||||||
renderUserContentAsMarkdown: false,
|
renderUserContentAsMarkdown: false,
|
||||||
alwaysShowSidebarOnDesktop: false,
|
alwaysShowSidebarOnDesktop: false,
|
||||||
autoShowSidebarOnNewChat: true,
|
|
||||||
autoMicOnEmpty: false,
|
autoMicOnEmpty: false,
|
||||||
fullHeightCodeBlocks: false,
|
fullHeightCodeBlocks: false,
|
||||||
showRawModelNames: false,
|
showRawModelNames: false,
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ export const SETTINGS_KEYS = {
|
||||||
RENDER_USER_CONTENT_AS_MARKDOWN: 'renderUserContentAsMarkdown',
|
RENDER_USER_CONTENT_AS_MARKDOWN: 'renderUserContentAsMarkdown',
|
||||||
DISABLE_AUTO_SCROLL: 'disableAutoScroll',
|
DISABLE_AUTO_SCROLL: 'disableAutoScroll',
|
||||||
ALWAYS_SHOW_SIDEBAR_ON_DESKTOP: 'alwaysShowSidebarOnDesktop',
|
ALWAYS_SHOW_SIDEBAR_ON_DESKTOP: 'alwaysShowSidebarOnDesktop',
|
||||||
AUTO_SHOW_SIDEBAR_ON_NEW_CHAT: 'autoShowSidebarOnNewChat',
|
|
||||||
FULL_HEIGHT_CODE_BLOCKS: 'fullHeightCodeBlocks',
|
FULL_HEIGHT_CODE_BLOCKS: 'fullHeightCodeBlocks',
|
||||||
SHOW_RAW_MODEL_NAMES: 'showRawModelNames',
|
SHOW_RAW_MODEL_NAMES: 'showRawModelNames',
|
||||||
// Sampling
|
// Sampling
|
||||||
|
|
|
||||||
|
|
@ -191,12 +191,6 @@ export const SYNCABLE_PARAMETERS: SyncableParameter[] = [
|
||||||
type: SyncableParameterType.BOOLEAN,
|
type: SyncableParameterType.BOOLEAN,
|
||||||
canSync: true
|
canSync: true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'autoShowSidebarOnNewChat',
|
|
||||||
serverKey: 'autoShowSidebarOnNewChat',
|
|
||||||
type: SyncableParameterType.BOOLEAN,
|
|
||||||
canSync: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'showRawModelNames',
|
key: 'showRawModelNames',
|
||||||
serverKey: 'showRawModelNames',
|
serverKey: 'showRawModelNames',
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
DialogConversationTitleUpdate,
|
DialogConversationTitleUpdate,
|
||||||
DialogChatSettingsImportExport
|
DialogChatSettingsImportExport
|
||||||
} from '$lib/components/app';
|
} from '$lib/components/app';
|
||||||
import { Settings } from '@lucide/svelte';
|
import { Settings, Search, SquarePen } from '@lucide/svelte';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import { isLoading } from '$lib/stores/chat.svelte';
|
import { isLoading } from '$lib/stores/chat.svelte';
|
||||||
import { conversationsStore, activeMessages } from '$lib/stores/conversations.svelte';
|
import { conversationsStore, activeMessages } from '$lib/stores/conversations.svelte';
|
||||||
|
|
@ -41,7 +41,6 @@
|
||||||
let isNewChatMode = $derived(page.url.searchParams.get('new_chat') === 'true');
|
let isNewChatMode = $derived(page.url.searchParams.get('new_chat') === 'true');
|
||||||
let showSidebarByDefault = $derived(activeMessages().length > 0 || isLoading());
|
let showSidebarByDefault = $derived(activeMessages().length > 0 || isLoading());
|
||||||
let alwaysShowSidebarOnDesktop = $derived(config().alwaysShowSidebarOnDesktop);
|
let alwaysShowSidebarOnDesktop = $derived(config().alwaysShowSidebarOnDesktop);
|
||||||
let autoShowSidebarOnNewChat = $derived(config().autoShowSidebarOnNewChat);
|
|
||||||
let isMobile = new IsMobile();
|
let isMobile = new IsMobile();
|
||||||
let isDesktop = $derived(!isMobile.current);
|
let isDesktop = $derived(!isMobile.current);
|
||||||
let sidebarOpen = $state(false);
|
let sidebarOpen = $state(false);
|
||||||
|
|
@ -57,6 +56,8 @@
|
||||||
let titleUpdateResolve: ((value: boolean) => void) | null = null;
|
let titleUpdateResolve: ((value: boolean) => void) | null = null;
|
||||||
|
|
||||||
let activePanel = $state<'chat' | 'settings' | 'mcp'>('chat');
|
let activePanel = $state<'chat' | 'settings' | 'mcp'>('chat');
|
||||||
|
let isMcpActive = $derived(page.route.id === '/settings/mcp');
|
||||||
|
let isSettingsActive = $derived(page.route.id === '/settings/chat');
|
||||||
// let chatSettingsInitialSection = $state<SettingsSectionTitle | undefined>(undefined);
|
// let chatSettingsInitialSection = $state<SettingsSectionTitle | undefined>(undefined);
|
||||||
let chatSettingsRef: ChatSettings | undefined = $state();
|
let chatSettingsRef: ChatSettings | undefined = $state();
|
||||||
let importExportDialogOpen = $state(false);
|
let importExportDialogOpen = $state(false);
|
||||||
|
|
@ -141,23 +142,7 @@
|
||||||
sidebarOpen = true;
|
sidebarOpen = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Don't auto-open or auto-close sidebar during navigation - user controls it manually
|
||||||
if (isHomeRoute && !isNewChatMode) {
|
|
||||||
// Auto-collapse sidebar when navigating to home route (but not in new chat mode)
|
|
||||||
sidebarOpen = false;
|
|
||||||
} else if (isHomeRoute && isNewChatMode) {
|
|
||||||
// Keep sidebar open in new chat mode
|
|
||||||
sidebarOpen = true;
|
|
||||||
} else if (isChatRoute) {
|
|
||||||
// On chat routes, only auto-show sidebar if setting is enabled
|
|
||||||
if (autoShowSidebarOnNewChat) {
|
|
||||||
sidebarOpen = true;
|
|
||||||
}
|
|
||||||
// If setting is disabled, don't change sidebar state - let user control it manually
|
|
||||||
} else {
|
|
||||||
// Other routes follow default behavior
|
|
||||||
sidebarOpen = showSidebarByDefault;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize server properties on app load (run once)
|
// Initialize server properties on app load (run once)
|
||||||
|
|
@ -280,7 +265,7 @@
|
||||||
|
|
||||||
<Sidebar.Provider bind:open={sidebarOpen}>
|
<Sidebar.Provider bind:open={sidebarOpen}>
|
||||||
<div class="flex h-screen w-full" style:height="{innerHeight}px">
|
<div class="flex h-screen w-full" style:height="{innerHeight}px">
|
||||||
<Sidebar.Root class="h-full">
|
<Sidebar.Root variant="floating" class="h-full">
|
||||||
<ChatSidebar bind:this={chatSidebar} />
|
<ChatSidebar bind:this={chatSidebar} />
|
||||||
</Sidebar.Root>
|
</Sidebar.Root>
|
||||||
|
|
||||||
|
|
@ -293,28 +278,80 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if !sidebarOpen}
|
{#if isDesktop && !alwaysShowSidebarOnDesktop}
|
||||||
<div class="absolute bottom-3 left-3 z-[900] flex flex-col gap-1 p-2">
|
<!-- Desktop: icon strip, always rendered, transitions width/opacity -->
|
||||||
|
<aside class="hidden md:flex shrink-0 flex-col items-center justify-between overflow-hidden py-3 transition-[width,opacity] duration-200 ease-linear {sidebarOpen ? 'w-0 opacity-0 pointer-events-none' : 'w-[calc(var(--sidebar-width-icon)+1.5rem)] opacity-100'}">
|
||||||
|
<div class="mt-12 flex flex-col items-center gap-1">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon-lg"
|
||||||
|
class="rounded-full"
|
||||||
|
href="?new_chat=true#/"
|
||||||
|
>
|
||||||
|
<SquarePen class="h-4 w-4" />
|
||||||
|
<span class="sr-only">New Chat</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon-lg"
|
||||||
|
class="rounded-full"
|
||||||
|
onclick={() => {
|
||||||
|
if (chatSidebar?.activateSearchMode) {
|
||||||
|
chatSidebar.activateSearchMode();
|
||||||
|
}
|
||||||
|
sidebarOpen = true;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Search class="h-4 w-4" />
|
||||||
|
<span class="sr-only">Search</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col items-center gap-1">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon-lg"
|
||||||
|
href="#/settings/mcp"
|
||||||
|
class="rounded-full {isMcpActive ? 'bg-accent text-accent-foreground' : ''}"
|
||||||
|
>
|
||||||
|
<McpLogo class="h-4 w-4" />
|
||||||
|
<span class="sr-only">MCP Servers</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon-lg"
|
||||||
|
href="#/settings/chat"
|
||||||
|
class="rounded-full {isSettingsActive ? 'bg-accent text-accent-foreground' : ''}"
|
||||||
|
>
|
||||||
|
<Settings class="h-4 w-4" />
|
||||||
|
<span class="sr-only">Settings</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if !sidebarOpen && !isDesktop}
|
||||||
|
<!-- Mobile quick-access buttons -->
|
||||||
|
<div class="absolute bottom-3 left-3 z-[900] flex flex-col gap-1 p-2 md:hidden">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon-lg"
|
||||||
class="h-8 w-8 {activePanel === 'mcp' ? 'bg-accent text-accent-foreground' : ''}"
|
href="#/settings/mcp"
|
||||||
onclick={() => (activePanel = activePanel === 'mcp' ? 'chat' : 'mcp')}
|
class={isMcpActive ? 'bg-accent text-accent-foreground' : ''}
|
||||||
>
|
>
|
||||||
<McpLogo class="h-4 w-4" />
|
<McpLogo class="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon-lg"
|
||||||
class="h-8 w-8 {activePanel === 'settings' ? 'bg-accent text-accent-foreground' : ''}"
|
href="#/settings/chat"
|
||||||
onclick={() => (activePanel = activePanel === 'settings' ? 'chat' : 'settings')}
|
class={isSettingsActive ? 'bg-accent text-accent-foreground' : ''}
|
||||||
>
|
>
|
||||||
<Settings class="h-4 w-4" />
|
<Settings class="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Sidebar.Inset class="flex flex-1 flex-col overflow-hidden">
|
<Sidebar.Inset class="flex flex-1 flex-col overflow-auto">
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</Sidebar.Inset>
|
</Sidebar.Inset>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue