Skip to content

Commit b026a17

Browse files
committed
feat: Improves chat sidebar and header layout & structure
Refactors the chat sidebar to improve layout and responsiveness, especially on mobile. Moves sidebar actions into a separate component for better organization. The header is adjusted to position correctly regardless of sidebar state. Adds more audio mime types
1 parent ccf4bef commit b026a17

File tree

5 files changed

+103
-83
lines changed

5 files changed

+103
-83
lines changed

tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreenHeader.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
</script>
1212

1313
<header
14-
class="md:background-transparent bg-background/40 pointer-events-none fixed left-[var(--sidebar-width)] right-0 top-0 z-50 flex items-center justify-end p-4 backdrop-blur-xl"
14+
class="md:background-transparent bg-background/40 pointer-events-none fixed left-0 md:left-[var(--sidebar-width)] right-0 top-0 z-50 flex items-center justify-end p-4 backdrop-blur-xl"
1515
>
1616
<div class="pointer-events-auto flex items-center space-x-2">
1717
<Button variant="ghost" size="sm" onclick={toggleSettings}>
Lines changed: 44 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,18 @@
11
<script lang="ts">
22
import * as Sidebar from '$lib/components/ui/sidebar/index.js';
3-
import { Button } from '$lib/components/ui/button';
4-
import { Input } from '$lib/components/ui/input';
5-
import { Search, SquarePen, X } from '@lucide/svelte';
63
import { ChatSidebarConversationItem } from '$lib/components/app';
74
import { conversations, deleteConversation } from '$lib/stores/chat.svelte';
85
import { goto } from '$app/navigation';
96
import { page } from '$app/state';
107
import { useSidebar } from '$lib/components/ui/sidebar';
118
import ScrollArea from '$lib/components/ui/scroll-area/scroll-area.svelte';
9+
import ChatSidebarActions from './ChatSidebarActions.svelte';
1210
1311
const sidebar = useSidebar();
1412
15-
export function handleMobileSidebarItemClick() {
16-
if (sidebar.isMobile) {
17-
sidebar.toggle();
18-
}
19-
}
20-
2113
let currentChatId = $derived(page.params.id);
22-
let searchQuery = $state('');
23-
2414
let isSearchModeActive = $state(false);
15+
let searchQuery = $state('');
2516
2617
let filteredConversations = $derived.by(() => {
2718
if (isSearchModeActive && searchQuery.trim().length > 0) {
@@ -37,6 +28,20 @@
3728
return conversations();
3829
});
3930
31+
async function editConversation(id: string) {
32+
console.log('Editing conversation:', id);
33+
}
34+
35+
async function handleDeleteConversation(id: string) {
36+
await deleteConversation(id);
37+
}
38+
39+
export function handleMobileSidebarItemClick() {
40+
if (sidebar.isMobile) {
41+
sidebar.toggle();
42+
}
43+
}
44+
4045
async function selectConversation(id: string) {
4146
if (isSearchModeActive) {
4247
isSearchModeActive = false;
@@ -45,70 +50,30 @@
4550
4651
await goto(`/chat/${id}`);
4752
}
48-
49-
async function editConversation(id: string) {
50-
console.log('Editing conversation:', id);
51-
}
52-
53-
async function handleDeleteConversation(id: string) {
54-
await deleteConversation(id);
55-
}
5653
</script>
5754

58-
<Sidebar.Header class="px-1 pb-2 pt-4">
59-
<a href="/" onclick={handleMobileSidebarItemClick}>
60-
<h1 class="inline-flex items-center gap-1 text-xl font-semibold">llama.cpp</h1>
61-
</a>
62-
</Sidebar.Header>
63-
64-
<div class="space-y-0.5 py-4">
65-
{#if isSearchModeActive}
66-
<div class="relative">
67-
<Search class="text-muted-foreground absolute left-2 top-2.5 h-4 w-4" />
68-
69-
<Input bind:value={searchQuery} placeholder="Search conversations..." class="pl-8" />
70-
71-
<X
72-
class="cursor-pointertext-muted-foreground absolute right-2 top-2.5 h-4 w-4"
73-
onclick={() => (isSearchModeActive = false)}
74-
/>
75-
</div>
76-
{:else}
77-
<Button
78-
class="w-full justify-start gap-2"
79-
href="/?new_chat=true"
80-
variant="ghost"
81-
onclick={handleMobileSidebarItemClick}
82-
>
83-
<SquarePen class="h-4 w-4" />
84-
85-
New chat
86-
</Button>
87-
88-
<Button
89-
class="w-full justify-start gap-2"
90-
variant="ghost"
91-
onclick={() => {
92-
isSearchModeActive = true;
93-
}}
94-
>
95-
<Search class="h-4 w-4" />
96-
97-
Search conversations
98-
</Button>
99-
{/if}
100-
</div>
101-
102-
<Sidebar.Group class="space-y-2 p-0">
103-
{#if (filteredConversations.length > 0 && isSearchModeActive) || !isSearchModeActive}
104-
<Sidebar.GroupLabel>
105-
{isSearchModeActive ? 'Search results' : 'Conversations'}
106-
</Sidebar.GroupLabel>
107-
{/if}
108-
109-
<Sidebar.GroupContent>
110-
<Sidebar.Menu>
111-
<ScrollArea>
55+
<ScrollArea class="h-[100vh] px-4">
56+
<Sidebar.Header class="pb-2 pt-4 md:sticky top-0 bg-sidebar z-10 gap-6">
57+
<a href="/" onclick={handleMobileSidebarItemClick}>
58+
<h1 class="inline-flex items-center gap-1 text-xl font-semibold">llama.cpp</h1>
59+
</a>
60+
61+
<ChatSidebarActions
62+
{handleMobileSidebarItemClick}
63+
{isSearchModeActive}
64+
{searchQuery}
65+
/>
66+
</Sidebar.Header>
67+
68+
<Sidebar.Group class="space-y-2 mt-4 p-0">
69+
{#if (filteredConversations.length > 0 && isSearchModeActive) || !isSearchModeActive}
70+
<Sidebar.GroupLabel>
71+
{isSearchModeActive ? 'Search results' : 'Conversations'}
72+
</Sidebar.GroupLabel>
73+
{/if}
74+
75+
<Sidebar.GroupContent>
76+
<Sidebar.Menu>
11277
{#each filteredConversations as conversation (conversation.id)}
11378
<Sidebar.MenuItem class="mb-1" onclick={handleMobileSidebarItemClick}>
11479
<ChatSidebarConversationItem
@@ -137,15 +102,14 @@
137102
</p>
138103
</div>
139104
{/if}
140-
</ScrollArea>
141-
</Sidebar.Menu>
142-
</Sidebar.GroupContent>
143-
</Sidebar.Group>
144105

145-
<Sidebar.Footer>
146-
<div class="px-2 py-2">
106+
</Sidebar.Menu>
107+
</Sidebar.GroupContent>
108+
</Sidebar.Group>
109+
110+
<div class="md:sticky bottom-0 bg-sidebar z-10 px-2 py-4">
147111
<p class="text-muted-foreground text-xs">
148112
Conversations are stored locally in your browser.
149113
</p>
150114
</div>
151-
</Sidebar.Footer>
115+
</ScrollArea>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<script lang="ts">
2+
import { Button } from '$lib/components/ui/button';
3+
import { Input } from '$lib/components/ui/input';
4+
import { Search, SquarePen, X } from '@lucide/svelte';
5+
6+
interface Props {
7+
handleMobileSidebarItemClick: () => void;
8+
isSearchModeActive: boolean;
9+
searchQuery: string;
10+
}
11+
12+
let { handleMobileSidebarItemClick, isSearchModeActive, searchQuery }: Props = $props();
13+
</script>
14+
15+
<div class="space-y-0.5">
16+
{#if isSearchModeActive}
17+
<div class="relative">
18+
<Search class="text-muted-foreground absolute left-2 top-2.5 h-4 w-4" />
19+
20+
<Input bind:value={searchQuery} placeholder="Search conversations..." class="pl-8" />
21+
22+
<X
23+
class="cursor-pointertext-muted-foreground absolute right-2 top-2.5 h-4 w-4"
24+
onclick={() => (isSearchModeActive = false)}
25+
/>
26+
</div>
27+
{:else}
28+
<Button
29+
class="w-full justify-start gap-2"
30+
href="/?new_chat=true"
31+
variant="ghost"
32+
onclick={handleMobileSidebarItemClick}
33+
>
34+
<SquarePen class="h-4 w-4" />
35+
36+
New chat
37+
</Button>
38+
39+
<Button
40+
class="w-full justify-start gap-2"
41+
variant="ghost"
42+
onclick={() => {
43+
isSearchModeActive = true;
44+
}}
45+
>
46+
<Search class="h-4 w-4" />
47+
48+
Search conversations
49+
</Button>
50+
{/if}
51+
</div>

tools/server/webui/src/lib/components/ui/sidebar/sidebar.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
<Sheet.Title>Sidebar</Sheet.Title>
5151
<Sheet.Description>Displays the mobile sidebar.</Sheet.Description>
5252
</Sheet.Header>
53-
<div class="flex h-full w-full flex-col px-4">
53+
<div class="flex h-full w-full flex-col">
5454
{@render children?.()}
5555
</div>
5656
</Sheet.Content>

tools/server/webui/src/lib/utils/convert-files-to-extra.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ function readFileAsBase64(file: File): Promise<string> {
2222
}
2323

2424
function isAudioMimeType(mimeType: string): boolean {
25-
return mimeType === 'audio/mpeg' || mimeType === 'audio/wav' || mimeType === 'audio/mp3';
25+
return mimeType === 'audio/mpeg' ||
26+
mimeType === 'audio/wav' ||
27+
mimeType === 'audio/mp3' ||
28+
mimeType === 'audio/webm' ||
29+
mimeType === 'audio/ogg' ||
30+
mimeType === 'audio/m4a';
2631
}
2732

2833
export async function parseFilesToMessageExtras(

0 commit comments

Comments
 (0)