feat: WIP
This commit is contained in:
parent
ec6302960e
commit
2d2ef88aaf
|
|
@ -10,7 +10,6 @@
|
|||
ModelsSelector,
|
||||
ModelsSelectorSheet
|
||||
} from '$lib/components/app';
|
||||
import { getMcpServersDialogContext } from '$lib/contexts';
|
||||
import { FileTypeCategory } from '$lib/enums';
|
||||
import { IsMobile } from '$lib/hooks/is-mobile.svelte';
|
||||
import { chatStore } from '$lib/stores/chat.svelte';
|
||||
|
|
@ -20,6 +19,7 @@
|
|||
import { config } from '$lib/stores/settings.svelte';
|
||||
import { activeMessages, conversationsStore } from '$lib/stores/conversations.svelte';
|
||||
import { getFileTypeCategory } from '$lib/utils';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
interface Props {
|
||||
canSend?: boolean;
|
||||
|
|
@ -53,8 +53,6 @@
|
|||
onMcpResourcesClick
|
||||
}: Props = $props();
|
||||
|
||||
const mcpServersDialog = getMcpServersDialogContext();
|
||||
|
||||
let currentConfig = $derived(config());
|
||||
let isRouter = $derived(isRouterMode());
|
||||
let isOffline = $derived(!!serverError());
|
||||
|
|
@ -213,7 +211,7 @@
|
|||
</div>
|
||||
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<McpActiveServersAvatars onClick={() => mcpServersDialog.open()} />
|
||||
<McpActiveServersAvatars onClick={() => goto('/#/settings/mcp')} />
|
||||
|
||||
{#if isMobile.current}
|
||||
<ModelsSelectorSheet
|
||||
|
|
|
|||
|
|
@ -572,21 +572,3 @@
|
|||
open={Boolean(activeErrorDialog)}
|
||||
type={activeErrorDialog?.type ?? ErrorDialogType.SERVER}
|
||||
/>
|
||||
|
||||
<style>
|
||||
.conversation-chat-form {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 2.375rem;
|
||||
background-color: var(--background);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -8,9 +8,7 @@
|
|||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ListRestart,
|
||||
|
||||
Sliders
|
||||
|
||||
} from '@lucide/svelte';
|
||||
import { ChatSettingsFooter, ChatSettingsFields } from '$lib/components/app';
|
||||
import { config, settingsStore } from '$lib/stores/settings.svelte';
|
||||
|
|
@ -429,12 +427,11 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
|
||||
<div class="flex h-full flex-col overflow-y-auto {className} w-full">
|
||||
<div class="flex flex-1 flex-col md:flex-row gap-4">
|
||||
<div class="flex flex-1 flex-col gap-4 md:flex-row">
|
||||
<!-- Desktop Sidebar -->
|
||||
<div class="hidden w-64 md:flex flex-col sticky top-0 self-start bg-background pt-8 pb-4">
|
||||
<div class="flex items-center gap-2 pb-6">
|
||||
<div class="sticky top-0 hidden w-64 flex-col self-start bg-background pt-8 pb-4 md:flex">
|
||||
<div class="flex items-center gap-2 pb-12">
|
||||
<Settings class="h-6 w-6" />
|
||||
<h1 class="text-2xl font-semibold">Settings</h1>
|
||||
</div>
|
||||
|
|
@ -456,11 +453,13 @@
|
|||
</div>
|
||||
|
||||
<!-- Mobile Header with Horizontal Scrollable Menu -->
|
||||
<div class="flex flex-col md:hidden sticky top-0 z-10 bg-background">
|
||||
<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 class="sticky top-0 z-10 flex flex-col bg-background md:hidden">
|
||||
<div class="flex items-center gap-2 px-4 pt-4 md:pt-6 pb-2">
|
||||
<Settings class="h-5 w-5 md:h-6 md:w-6" />
|
||||
|
||||
<h1 class="text-xl md:text-2xl font-semibold">Settings</h1>
|
||||
</div>
|
||||
|
||||
<div class="border-b border-border/30 py-2">
|
||||
<!-- Horizontal Scrollable Category Menu with Navigation -->
|
||||
<div class="relative flex items-center" style="scroll-padding: 1rem;">
|
||||
|
|
@ -511,8 +510,8 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 max-w-5xl mx-auto">
|
||||
<div class="space-y-6 p-4 md:p-6 md:pt-22">
|
||||
<div class="mx-auto max-w-3xl flex-1">
|
||||
<div class="space-y-6 p-4 md:p-6 md:pt-28">
|
||||
<div class="grid">
|
||||
<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" />
|
||||
|
|
@ -536,8 +535,8 @@
|
|||
<p class="text-xs text-muted-foreground">Settings are saved in browser's localStorage</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ChatSettingsFooter onReset={handleReset} onSave={handleSave} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ChatSettingsFooter onReset={handleReset} onSave={handleSave} />
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="flex justify-between border-t border-border/30 p-6">
|
||||
<div class="sticky bottom-0 mx-auto mt-4 flex w-full max-w-4xl justify-between p-6">
|
||||
<div class="flex gap-2">
|
||||
<Button variant="outline" onclick={handleResetClick}>
|
||||
<RotateCcw class="h-3 w-3" />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { Download, Upload, Trash2 } from '@lucide/svelte';
|
||||
import { Download, Upload, Trash2, Database } from '@lucide/svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { DialogConversationSelection, DialogConfirmation } from '$lib/components/app';
|
||||
import { createMessageCountMap } from '$lib/utils';
|
||||
|
|
@ -175,9 +175,15 @@
|
|||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center gap-2 pb-4">
|
||||
<Database class="h-5 w-5 md:h-6 md:w-6" />
|
||||
|
||||
<h1 class="text-xl md:text-2xl font-semibold">Import / Export</h1>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="grid">
|
||||
<h4 class="mb-2 text-sm font-medium">Export Conversations</h4>
|
||||
<h4 class="mt-0 mb-2 text-sm font-medium">Export Conversations</h4>
|
||||
|
||||
<p class="mb-4 text-sm text-muted-foreground">
|
||||
Download all your conversations as a JSON file. This includes all messages, attachments, and
|
||||
|
|
@ -218,7 +224,7 @@
|
|||
</div>
|
||||
|
||||
<div class="grid border-t border-border/30 pt-4">
|
||||
<h4 class="mb-2 text-sm font-medium">Import Conversations</h4>
|
||||
<h4 class="mt-0 mb-2 text-sm font-medium">Import Conversations</h4>
|
||||
|
||||
<p class="mb-4 text-sm text-muted-foreground">
|
||||
Import one or more conversations from a previously exported JSON file. This will merge with
|
||||
|
|
@ -258,7 +264,7 @@
|
|||
</div>
|
||||
|
||||
<div class="grid border-t border-border/30 pt-4">
|
||||
<h4 class="mb-2 text-sm font-medium text-destructive">Delete All Conversations</h4>
|
||||
<h4 class="mt-0 mb-2 text-sm font-medium text-destructive">Delete All Conversations</h4>
|
||||
|
||||
<p class="mb-4 text-sm text-muted-foreground">
|
||||
Permanently delete all conversations and their messages. This action cannot be undone.
|
||||
|
|
|
|||
|
|
@ -157,7 +157,12 @@
|
|||
<h1 class="inline-flex items-center gap-1 px-2 text-xl font-semibold">llama.cpp</h1>
|
||||
</a>
|
||||
|
||||
<ChatSidebarActions bind:this={chatSidebarActions} {handleMobileSidebarItemClick} bind:isSearchModeActive bind:searchQuery />
|
||||
<ChatSidebarActions
|
||||
bind:this={chatSidebarActions}
|
||||
{handleMobileSidebarItemClick}
|
||||
bind:isSearchModeActive
|
||||
bind:searchQuery
|
||||
/>
|
||||
</Sidebar.Header>
|
||||
|
||||
<Sidebar.Group class="mt-2 space-y-2 p-0 px-3">
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
<script lang="ts">
|
||||
import { Database, Search, SquarePen } from '@lucide/svelte';
|
||||
import { Search, SquarePen } from '@lucide/svelte';
|
||||
import { KeyboardShortcutInfo } from '$lib/components/app';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { SearchInput } from '$lib/components/app';
|
||||
import { getImportExportDialogContext } from '$lib/contexts';
|
||||
|
||||
interface Props {
|
||||
handleMobileSidebarItemClick: () => void;
|
||||
|
|
@ -19,7 +18,6 @@
|
|||
isCancelAlwaysVisible = false
|
||||
}: Props = $props();
|
||||
|
||||
const importExportDialog = getImportExportDialogContext();
|
||||
let searchInputRef = $state<HTMLInputElement | null>(null);
|
||||
|
||||
function handleSearchModeDeactivate() {
|
||||
|
|
@ -42,7 +40,7 @@
|
|||
onClose={handleSearchModeDeactivate}
|
||||
onKeyDown={(e) => e.key === 'Escape' && handleSearchModeDeactivate()}
|
||||
placeholder="Search conversations..."
|
||||
isCancelAlwaysVisible={isCancelAlwaysVisible}
|
||||
{isCancelAlwaysVisible}
|
||||
/>
|
||||
{:else}
|
||||
<Button
|
||||
|
|
@ -73,19 +71,5 @@
|
|||
|
||||
<KeyboardShortcutInfo keys={['cmd', 'k']} />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
class="w-full justify-between px-2 backdrop-blur-none! hover:[&>kbd]:opacity-100"
|
||||
onclick={() => {
|
||||
importExportDialog.open();
|
||||
}}
|
||||
variant="ghost"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<Database class="h-4 w-4" />
|
||||
|
||||
Import / Export
|
||||
</div>
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,25 @@
|
|||
<script lang="ts">
|
||||
import { Settings } from '@lucide/svelte';
|
||||
import { Database, Settings } from '@lucide/svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { McpLogo } from '$lib/components/app';
|
||||
// import { getChatSettingsDialogContext, getMcpServersDialogContext } from '$lib/contexts';
|
||||
import { page } from '$app/state';
|
||||
import * as Sidebar from '$lib/components/ui/sidebar';
|
||||
|
||||
// const chatSettingsDialog = getChatSettingsDialogContext();
|
||||
// const mcpServersDialog = getMcpServersDialogContext();
|
||||
|
||||
const sidebar = Sidebar.useSidebar();
|
||||
|
||||
let isMcpActive = $derived(page.route.id === '/settings/mcp');
|
||||
let isSettingsActive = $derived(page.route.id === '/settings/chat');
|
||||
let isImportExportActive = $derived(page.route.id === '/settings/import-export');
|
||||
|
||||
function handleMobileSidebarItemClick() {
|
||||
if (sidebar.isMobile) {
|
||||
sidebar.toggle();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="space-y-1 pt-0">
|
||||
|
|
@ -18,6 +28,7 @@
|
|||
? 'bg-accent text-accent-foreground'
|
||||
: ''}"
|
||||
href="#/settings/mcp"
|
||||
onclick={handleMobileSidebarItemClick}
|
||||
variant="ghost"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
|
|
@ -27,11 +38,27 @@
|
|||
</div>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
class="w-full justify-between px-2 backdrop-blur-none! hover:[&>kbd]:opacity-100 {isImportExportActive
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: ''}"
|
||||
href="#/settings/import-export"
|
||||
onclick={handleMobileSidebarItemClick}
|
||||
variant="ghost"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<Database class="h-4 w-4" />
|
||||
|
||||
Import / Export
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
class="w-full justify-between px-2 backdrop-blur-none! hover:[&>kbd]:opacity-100 {isSettingsActive
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: ''}"
|
||||
href="#/settings/chat"
|
||||
onclick={handleMobileSidebarItemClick}
|
||||
variant="ghost"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
{#if showClearButton}
|
||||
<button
|
||||
type="button"
|
||||
class="cursor-pointer absolute top-1/2 right-3 -translate-y-1/2 transform text-muted-foreground transition-colors hover:text-foreground"
|
||||
class="absolute top-1/2 right-3 -translate-y-1/2 transform cursor-pointer text-muted-foreground transition-colors hover:text-foreground"
|
||||
onclick={handleClear}
|
||||
aria-label={value ? 'Clear search' : 'Close'}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -84,13 +84,13 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="flex items-center gap-2 absolute left-8 top-8">
|
||||
<McpLogo class="h-6 w-6" />
|
||||
<div class="p-4 md:p-0 md:absolute md:top-8 md:left-8 flex items-center gap-2">
|
||||
<McpLogo class="h-5 w-5 md:h-6 md:w-6" />
|
||||
|
||||
<h1 class="text-2xl font-semibold">MCP Servers</h1>
|
||||
<h1 class="text-xl md: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">
|
||||
<div class="sticky top-0 z-10 mt-4 flex items-start justify-end gap-4 px-8 py-4">
|
||||
{#if !isAddingServer}
|
||||
<Button variant="outline" size="sm" class="shrink-0" onclick={showAddServerForm}>
|
||||
<Plus class="h-4 w-4" />
|
||||
|
|
@ -101,7 +101,6 @@
|
|||
</div>
|
||||
|
||||
<div class="grid gap-5 md:space-y-4 {className}">
|
||||
|
||||
{#if isAddingServer}
|
||||
<Card.Root class="bg-muted/30 p-4">
|
||||
<div class="space-y-4">
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { Search, X } from '@lucide/svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import SearchInput from '$lib/components/app/forms/SearchInput.svelte';
|
||||
import { ScrollArea } from '$lib/components/ui/scroll-area';
|
||||
import { SvelteSet } from 'svelte/reactivity';
|
||||
|
||||
|
|
@ -111,21 +110,7 @@
|
|||
</script>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="relative">
|
||||
<Search class="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
||||
|
||||
<Input bind:value={searchQuery} placeholder="Search conversations..." class="pr-9 pl-9" />
|
||||
|
||||
{#if searchQuery}
|
||||
<button
|
||||
class="absolute top-1/2 right-3 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||
onclick={() => (searchQuery = '')}
|
||||
type="button"
|
||||
>
|
||||
<X class="h-4 w-4" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
<SearchInput bind:value={searchQuery} placeholder="Search conversations..." />
|
||||
|
||||
<div class="flex items-center justify-between text-sm text-muted-foreground">
|
||||
<span>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
variant: {
|
||||
default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
|
||||
destructive:
|
||||
'bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white',
|
||||
'bg-destructive shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white!',
|
||||
outline:
|
||||
'bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border',
|
||||
secondary:
|
||||
|
|
|
|||
|
|
@ -86,14 +86,14 @@
|
|||
side === 'left'
|
||||
? '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',
|
||||
]
|
||||
'my-3 overflow-hidden rounded-2xl border border-sidebar-border shadow-md'
|
||||
]
|
||||
: [
|
||||
'transition-[left,right,width] h-svh',
|
||||
'h-svh transition-[left,right,width]',
|
||||
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)]',
|
||||
],
|
||||
: '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)]'
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
DialogConversationTitleUpdate,
|
||||
DialogChatSettingsImportExport
|
||||
} from '$lib/components/app';
|
||||
import { Settings, Search, SquarePen } from '@lucide/svelte';
|
||||
import { Database, Settings, Search, SquarePen } from '@lucide/svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { isLoading } from '$lib/stores/chat.svelte';
|
||||
import { conversationsStore, activeMessages } from '$lib/stores/conversations.svelte';
|
||||
|
|
@ -36,10 +36,6 @@
|
|||
|
||||
let { children } = $props();
|
||||
|
||||
let isChatRoute = $derived(page.route.id === '/chat/[id]');
|
||||
let isHomeRoute = $derived(page.route.id === '/');
|
||||
let isNewChatMode = $derived(page.url.searchParams.get('new_chat') === 'true');
|
||||
let showSidebarByDefault = $derived(activeMessages().length > 0 || isLoading());
|
||||
let alwaysShowSidebarOnDesktop = $derived(config().alwaysShowSidebarOnDesktop);
|
||||
let isMobile = new IsMobile();
|
||||
let isDesktop = $derived(!isMobile.current);
|
||||
|
|
@ -57,7 +53,9 @@
|
|||
|
||||
let activePanel = $state<'chat' | 'settings' | 'mcp'>('chat');
|
||||
let isMcpActive = $derived(page.route.id === '/settings/mcp');
|
||||
let isImportExportActive = $derived(page.route.id === '/settings/import-export');
|
||||
let isSettingsActive = $derived(page.route.id === '/settings/chat');
|
||||
let isSettingsRoute = $derived(!!page.route.id?.startsWith('/settings'));
|
||||
// let chatSettingsInitialSection = $state<SettingsSectionTitle | undefined>(undefined);
|
||||
let chatSettingsRef: ChatSettings | undefined = $state();
|
||||
let importExportDialogOpen = $state(false);
|
||||
|
|
@ -269,7 +267,7 @@
|
|||
<ChatSidebar bind:this={chatSidebar} />
|
||||
</Sidebar.Root>
|
||||
|
||||
{#if !(alwaysShowSidebarOnDesktop && isDesktop)}
|
||||
{#if !(alwaysShowSidebarOnDesktop && isDesktop) && !(isSettingsRoute && !isDesktop)}
|
||||
<Sidebar.Trigger
|
||||
class="transition-left absolute left-0 z-[900] duration-200 ease-linear {sidebarOpen
|
||||
? 'md:left-[calc(var(--sidebar-width)+0.75rem)]'
|
||||
|
|
@ -280,76 +278,64 @@
|
|||
|
||||
{#if isDesktop && !alwaysShowSidebarOnDesktop}
|
||||
<!-- 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'}">
|
||||
<aside
|
||||
class="hidden shrink-0 flex-col items-center justify-between overflow-hidden py-3 transition-[width,opacity] duration-200 ease-linear md:flex {sidebarOpen
|
||||
? 'pointer-events-none w-0 opacity-0'
|
||||
: '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>
|
||||
<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/import-export"
|
||||
class="rounded-full {isImportExportActive ? 'bg-accent text-accent-foreground' : ''}"
|
||||
>
|
||||
<Database class="h-4 w-4" />
|
||||
<span class="sr-only">Import / Export</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
|
||||
variant="ghost"
|
||||
size="icon-lg"
|
||||
href="#/settings/mcp"
|
||||
class={isMcpActive ? 'bg-accent text-accent-foreground' : ''}
|
||||
>
|
||||
<McpLogo class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-lg"
|
||||
href="#/settings/chat"
|
||||
class={isSettingsActive ? 'bg-accent text-accent-foreground' : ''}
|
||||
>
|
||||
<Settings class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
<Sidebar.Inset class="flex flex-1 flex-col overflow-auto">
|
||||
{@render children?.()}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
<script lang="ts">
|
||||
import { X } from '@lucide/svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
import { ActionIcon } from '$lib/components/app';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
function handleClose() {
|
||||
if (browser && window.history.length > 1) {
|
||||
history.back();
|
||||
} else {
|
||||
goto('#/');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="relative h-full">
|
||||
<div class="fixed top-4.5 right-4 z-50 md:hidden">
|
||||
<ActionIcon icon={X} tooltip="Close" onclick={handleClose} />
|
||||
</div>
|
||||
|
||||
<div class="min-h-full">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { ChatSettingsImportExportTab } from '$lib/components/app/chat';
|
||||
</script>
|
||||
|
||||
<div class="mx-auto w-full p-4 md:p-8">
|
||||
<ChatSettingsImportExportTab />
|
||||
</div>
|
||||
Loading…
Reference in New Issue