feat: WIP
This commit is contained in:
parent
8c55e86cba
commit
c3520f1e2c
|
|
@ -26,11 +26,12 @@
|
|||
import type { Component } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
class: string;
|
||||
onSave?: () => void;
|
||||
initialSection?: SettingsSectionTitle;
|
||||
}
|
||||
|
||||
let { onSave, initialSection }: Props = $props();
|
||||
let { class: className, onSave, initialSection }: Props = $props();
|
||||
|
||||
const settingSections: Array<{
|
||||
fields: SettingsFieldConfig[];
|
||||
|
|
@ -431,7 +432,7 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<div class="flex h-full flex-col overflow-hidden md:flex-row">
|
||||
<div class="flex h-full flex-col overflow-hidden md:flex-row {className}">
|
||||
<!-- Desktop Sidebar -->
|
||||
<div class="hidden w-64 border-r border-border/30 p-6 md:block">
|
||||
<nav class="space-y-1 py-2">
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
import { chatStore } from '$lib/stores/chat.svelte';
|
||||
import { getPreviewText } from '$lib/utils';
|
||||
import ChatSidebarActions from './ChatSidebarActions.svelte';
|
||||
import ChatSidebarFooter from './ChatSidebarFooter.svelte';
|
||||
|
||||
const sidebar = Sidebar.useSidebar();
|
||||
|
||||
|
|
@ -138,7 +139,8 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<ScrollArea>
|
||||
<div class="flex h-full flex-col">
|
||||
<ScrollArea class="flex-1">
|
||||
<Sidebar.Header class=" top-0 z-10 gap-4 bg-sidebar/50 p-3 pt-4 pb-2 backdrop-blur-lg md:sticky">
|
||||
<a href="#/" onclick={handleMobileSidebarItemClick}>
|
||||
<h1 class="inline-flex items-center gap-1 px-2 text-xl font-semibold">llama.cpp</h1>
|
||||
|
|
@ -193,6 +195,11 @@
|
|||
</Sidebar.Group>
|
||||
</ScrollArea>
|
||||
|
||||
<Sidebar.Footer>
|
||||
<ChatSidebarFooter />
|
||||
</Sidebar.Footer>
|
||||
</div>
|
||||
|
||||
<DialogConfirmation
|
||||
bind:open={showDeleteDialog}
|
||||
title="Delete Conversation"
|
||||
|
|
|
|||
|
|
@ -1,14 +1,9 @@
|
|||
<script lang="ts">
|
||||
import { Database, Search, Settings, SquarePen, X } from '@lucide/svelte';
|
||||
import { Database, Search, SquarePen, X } from '@lucide/svelte';
|
||||
import { KeyboardShortcutInfo } from '$lib/components/app';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { McpLogo } from '$lib/components/app';
|
||||
import {
|
||||
getChatSettingsDialogContext,
|
||||
getMcpServersDialogContext,
|
||||
getImportExportDialogContext
|
||||
} from '$lib/contexts';
|
||||
import { getImportExportDialogContext } from '$lib/contexts';
|
||||
|
||||
interface Props {
|
||||
handleMobileSidebarItemClick: () => void;
|
||||
|
|
@ -24,8 +19,6 @@
|
|||
|
||||
let searchInput: HTMLInputElement | null = $state(null);
|
||||
|
||||
const chatSettingsDialog = getChatSettingsDialogContext();
|
||||
const mcpServersDialog = getMcpServersDialogContext();
|
||||
const importExportDialog = getImportExportDialogContext();
|
||||
|
||||
function handleSearchModeDeactivate() {
|
||||
|
|
@ -90,20 +83,6 @@
|
|||
<KeyboardShortcutInfo keys={['cmd', 'k']} />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
class="w-full justify-between px-2 backdrop-blur-none! hover:[&>kbd]:opacity-100"
|
||||
onclick={() => {
|
||||
mcpServersDialog.open();
|
||||
}}
|
||||
variant="ghost"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<McpLogo class="h-4 w-4" />
|
||||
|
||||
MCP Servers
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
class="w-full justify-between px-2 backdrop-blur-none! hover:[&>kbd]:opacity-100"
|
||||
onclick={() => {
|
||||
|
|
@ -118,18 +97,5 @@
|
|||
</div>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
class="w-full justify-between px-2 backdrop-blur-none! hover:[&>kbd]:opacity-100"
|
||||
onclick={() => {
|
||||
chatSettingsDialog.open();
|
||||
}}
|
||||
variant="ghost"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<Settings class="h-4 w-4" />
|
||||
|
||||
Settings
|
||||
</div>
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
<script lang="ts">
|
||||
import { Settings } from '@lucide/svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { McpLogo } from '$lib/components/app';
|
||||
import { getChatSettingsDialogContext, getMcpServersDialogContext } from '$lib/contexts';
|
||||
|
||||
const chatSettingsDialog = getChatSettingsDialogContext();
|
||||
const mcpServersDialog = getMcpServersDialogContext();
|
||||
|
||||
let isMcpActive = $derived(mcpServersDialog.isActive());
|
||||
let isSettingsActive = $derived(chatSettingsDialog.isActive());
|
||||
</script>
|
||||
|
||||
<div class="space-y-1 pt-0">
|
||||
<Button
|
||||
class="w-full justify-between px-2 backdrop-blur-none! hover:[&>kbd]:opacity-100 {isMcpActive ? 'bg-accent text-accent-foreground' : ''}"
|
||||
onclick={() => {
|
||||
mcpServersDialog.open();
|
||||
}}
|
||||
variant="ghost"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<McpLogo class="h-4 w-4" />
|
||||
|
||||
MCP Servers
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
class="w-full justify-between px-2 backdrop-blur-none! hover:[&>kbd]:opacity-100 {isSettingsActive ? 'bg-accent text-accent-foreground' : ''}"
|
||||
onclick={() => {
|
||||
chatSettingsDialog.open();
|
||||
}}
|
||||
variant="ghost"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<Settings class="h-4 w-4" />
|
||||
|
||||
Settings
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -9,6 +9,12 @@
|
|||
import { MCP_SERVER_ID_PREFIX } from '$lib/constants';
|
||||
import { HealthCheckStatus } from '$lib/enums';
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
}
|
||||
|
||||
let { class: className }: Props = $props();
|
||||
|
||||
let servers = $derived(mcpStore.getServersSorted());
|
||||
|
||||
let initialLoadComplete = $state(false);
|
||||
|
|
@ -77,7 +83,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="space-y-5 md:space-y-4">
|
||||
<div class="gap-5 grid md:space-y-4 {className}">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h4 class="text-base font-semibold">Manage Servers</h4>
|
||||
|
|
@ -130,7 +136,7 @@
|
|||
{/if}
|
||||
|
||||
{#if servers.length > 0}
|
||||
<div class="space-y-3">
|
||||
<div class="grid gap-3" style="grid-template-columns: repeat(auto-fill, minmax(28rem, 1fr));">
|
||||
{#each servers as server (server.id)}
|
||||
{#if !initialLoadComplete}
|
||||
<McpServerCardSkeleton />
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
bind:this={ref}
|
||||
data-slot="sidebar-footer"
|
||||
data-sidebar="footer"
|
||||
class={cn('flex flex-col gap-2 p-2', className)}
|
||||
class={cn('flex flex-col gap-2 p-3', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import PanelLeftIcon from '@lucide/svelte/icons/panel-left';
|
||||
import type { ComponentProps } from 'svelte';
|
||||
import { useSidebar } from './context.svelte.js';
|
||||
import { PanelLeftClose } from '@lucide/svelte';
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
|
|
@ -21,7 +22,7 @@
|
|||
data-slot="sidebar-trigger"
|
||||
variant="ghost"
|
||||
size="icon-lg"
|
||||
class="rounded-full backdrop-blur-lg {className} top-1.5 md:left-[14.5rem]"
|
||||
class="rounded-full backdrop-blur-lg {className} {sidebar.open ? 'top-1.5' : 'top-0'} md:left-[14.5rem]"
|
||||
type="button"
|
||||
onclick={(e) => {
|
||||
onclick?.(e);
|
||||
|
|
@ -29,6 +30,10 @@
|
|||
}}
|
||||
{...restProps}
|
||||
>
|
||||
<PanelLeftIcon />
|
||||
{#if sidebar.open}
|
||||
<PanelLeftClose />
|
||||
{:else}
|
||||
<PanelLeftIcon />
|
||||
{/if}
|
||||
<span class="sr-only">Toggle Sidebar</span>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -78,10 +78,10 @@
|
|||
<div
|
||||
data-slot="sidebar-container"
|
||||
class={cn(
|
||||
'fixed inset-y-0 z-999 hidden w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:z-0 md:flex',
|
||||
'fixed inset-y-0 z-999 hidden w-(--sidebar-width) transition-[left,right,width,opacity] duration-200 ease-linear md:z-0 md:flex',
|
||||
side === 'left'
|
||||
? 'left-3 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
|
||||
: 'right-1 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
|
||||
? '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',
|
||||
// Adjust the padding for floating and inset variants.
|
||||
variant === 'floating' || variant === 'inset'
|
||||
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { CONTEXT_KEY_CHAT_SETTINGS_DIALOG } from '$lib/constants';
|
|||
|
||||
export interface ChatSettingsDialogContext {
|
||||
open: (initialSection?: SettingsSectionTitle) => void;
|
||||
isActive: () => boolean;
|
||||
}
|
||||
|
||||
const CHAT_SETTINGS_DIALOG_KEY = Symbol.for(CONTEXT_KEY_CHAT_SETTINGS_DIALOG);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { CONTEXT_KEY_MCP_SERVERS_DIALOG } from '$lib/constants';
|
|||
|
||||
export interface McpServersDialogContext {
|
||||
open: () => void;
|
||||
isActive: () => boolean;
|
||||
}
|
||||
|
||||
const MCP_SERVERS_DIALOG_KEY = Symbol.for(CONTEXT_KEY_MCP_SERVERS_DIALOG);
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@
|
|||
import { untrack } from 'svelte';
|
||||
import {
|
||||
ChatSidebar,
|
||||
ChatSettings,
|
||||
McpServersSettings,
|
||||
DialogConversationTitleUpdate,
|
||||
DialogChatSettings,
|
||||
DialogMcpServersSettings,
|
||||
DialogChatSettingsImportExport
|
||||
} from '$lib/components/app';
|
||||
import { isLoading } from '$lib/stores/chat.svelte';
|
||||
|
|
@ -54,22 +54,24 @@
|
|||
let titleUpdateNewTitle = $state('');
|
||||
let titleUpdateResolve: ((value: boolean) => void) | null = null;
|
||||
|
||||
let chatSettingsDialogOpen = $state(false);
|
||||
let chatSettingsDialogInitialSection = $state<SettingsSectionTitle | undefined>(undefined);
|
||||
let mcpServersDialogOpen = $state(false);
|
||||
let activePanel = $state<'chat' | 'settings' | 'mcp'>('chat');
|
||||
let chatSettingsInitialSection = $state<SettingsSectionTitle | undefined>(undefined);
|
||||
let chatSettingsRef: ChatSettings | undefined = $state();
|
||||
let importExportDialogOpen = $state(false);
|
||||
|
||||
setChatSettingsDialogContext({
|
||||
open: (initialSection?: SettingsSectionTitle) => {
|
||||
chatSettingsDialogInitialSection = initialSection;
|
||||
chatSettingsDialogOpen = true;
|
||||
}
|
||||
chatSettingsInitialSection = initialSection;
|
||||
activePanel = 'settings';
|
||||
},
|
||||
isActive: () => activePanel === 'settings'
|
||||
});
|
||||
|
||||
setMcpServersDialogContext({
|
||||
open: () => {
|
||||
mcpServersDialogOpen = true;
|
||||
}
|
||||
activePanel = 'mcp';
|
||||
},
|
||||
isActive: () => activePanel === 'mcp'
|
||||
});
|
||||
|
||||
setImportExportDialogContext({
|
||||
|
|
@ -78,6 +80,18 @@
|
|||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (activePanel === 'settings' && chatSettingsRef) {
|
||||
chatSettingsRef.reset();
|
||||
}
|
||||
});
|
||||
|
||||
// Return to chat when navigating to a new route
|
||||
$effect(() => {
|
||||
void page.url;
|
||||
activePanel = 'chat';
|
||||
});
|
||||
|
||||
// Global keyboard shortcuts
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
const isCtrlOrCmd = event.ctrlKey || event.metaKey;
|
||||
|
|
@ -249,16 +263,6 @@
|
|||
|
||||
<Toaster richColors />
|
||||
|
||||
<DialogChatSettings
|
||||
open={chatSettingsDialogOpen}
|
||||
onOpenChange={(open) => (chatSettingsDialogOpen = open)}
|
||||
initialSection={chatSettingsDialogInitialSection}
|
||||
/>
|
||||
|
||||
<DialogMcpServersSettings
|
||||
open={mcpServersDialogOpen}
|
||||
onOpenChange={(open) => (mcpServersDialogOpen = open)}
|
||||
/>
|
||||
<DialogChatSettingsImportExport
|
||||
open={importExportDialogOpen}
|
||||
onOpenChange={(open) => (importExportDialogOpen = open)}
|
||||
|
|
@ -288,7 +292,18 @@
|
|||
{/if}
|
||||
|
||||
<Sidebar.Inset class="flex flex-1 flex-col overflow-hidden">
|
||||
{@render children?.()}
|
||||
{#if activePanel === 'settings'}
|
||||
<ChatSettings
|
||||
bind:this={chatSettingsRef}
|
||||
onSave={() => (activePanel = 'chat')}
|
||||
initialSection={chatSettingsInitialSection}
|
||||
class="p-8! mx-auto h-full"
|
||||
/>
|
||||
{:else if activePanel === 'mcp'}
|
||||
<McpServersSettings class="w-full mx-auto p-8! md:translate-x-1.5" />
|
||||
{:else}
|
||||
{@render children?.()}
|
||||
{/if}
|
||||
</Sidebar.Inset>
|
||||
</div>
|
||||
</Sidebar.Provider>
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@
|
|||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>llama.cpp - AI Chat Interface</title>
|
||||
<title>llama.cpp</title>
|
||||
</svelte:head>
|
||||
|
||||
<ChatScreen showCenteredEmpty />
|
||||
|
|
|
|||
Loading…
Reference in New Issue