+
@@ -268,4 +302,4 @@
{/if}
-
+
diff --git a/src/lib/components/ui/sidebar/sidebar.svelte b/src/lib/components/ui/sidebar/sidebar.svelte
index 2404c37..609a561 100644
--- a/src/lib/components/ui/sidebar/sidebar.svelte
+++ b/src/lib/components/ui/sidebar/sidebar.svelte
@@ -4,9 +4,17 @@
import { useSidebar } from './sidebar.svelte.js';
import { shortcut } from '$lib/actions/shortcut.svelte.js';
- let { children, ...rest }: HTMLAttributes
= $props();
+ let {
+ open = $bindable(false),
+ children,
+ ...rest
+ }: HTMLAttributes & { open?: boolean } = $props();
const sidebar = useSidebar();
+
+ $effect(() => {
+ open = sidebar.showSidebar;
+ });
diff --git a/src/lib/components/ui/sidebar/sidebar.svelte.ts b/src/lib/components/ui/sidebar/sidebar.svelte.ts
index 7c85f44..9ef455b 100644
--- a/src/lib/components/ui/sidebar/sidebar.svelte.ts
+++ b/src/lib/components/ui/sidebar/sidebar.svelte.ts
@@ -44,11 +44,16 @@ export class SidebarSidebarState {
export class SidebarControlState {
constructor(readonly root: SidebarRootState) {
this.closeMobile = this.closeMobile.bind(this);
+ this.toggle = this.toggle.bind(this);
}
closeMobile() {
this.root.closeMobile();
}
+
+ toggle() {
+ this.root.toggle();
+ }
}
export const ctx = new Context('sidebar-root-context');
diff --git a/src/routes/api/generate-message/call.ts b/src/routes/api/generate-message/call.ts
index ae28391..466db37 100644
--- a/src/routes/api/generate-message/call.ts
+++ b/src/routes/api/generate-message/call.ts
@@ -3,15 +3,25 @@ import type { GenerateMessageRequestBody, GenerateMessageResponse } from './+ser
export async function callGenerateMessage(args: GenerateMessageRequestBody) {
const res = ResultAsync.fromPromise(
- fetch('/api/generate-message', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(args),
- }),
- (e) => e
- ).map((r) => r.json() as Promise);
+ (async () => {
+ const res = await fetch('/api/generate-message', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(args),
+ });
+
+ if (!res.ok) {
+ const { message } = await res.json();
+
+ throw new Error(message as string);
+ }
+
+ return res.json() as Promise;
+ })(),
+ (e) => `${e}`
+ );
return res;
}
diff --git a/src/routes/chat/+layout.svelte b/src/routes/chat/+layout.svelte
index 18a1b67..0d831b4 100644
--- a/src/routes/chat/+layout.svelte
+++ b/src/routes/chat/+layout.svelte
@@ -32,25 +32,25 @@
import CheckIcon from '~icons/lucide/check';
import ChevronDownIcon from '~icons/lucide/chevron-down';
import ImageIcon from '~icons/lucide/image';
- import LockIcon from '~icons/lucide/lock';
- import LockOpenIcon from '~icons/lucide/lock-open';
import PanelLeftIcon from '~icons/lucide/panel-left';
- import SearchIcon from '~icons/lucide/search';
import Settings2Icon from '~icons/lucide/settings-2';
- import ShareIcon from '~icons/lucide/share';
- import StopIcon from '~icons/lucide/square';
import UploadIcon from '~icons/lucide/upload';
import XIcon from '~icons/lucide/x';
- import { callCancelGeneration } from '../api/cancel-generation/call.js';
+ import SearchIcon from '~icons/lucide/search';
import { callGenerateMessage } from '../api/generate-message/call.js';
+ import { callCancelGeneration } from '../api/cancel-generation/call.js';
import ModelPicker from './model-picker.svelte';
+ import ShareIcon from '~icons/lucide/share';
+ import { fade } from 'svelte/transition';
+ import LockIcon from '~icons/lucide/lock';
+ import LockOpenIcon from '~icons/lucide/lock-open';
+ import StopIcon from '~icons/lucide/square';
import SearchModal from './search-modal.svelte';
const client = useConvexClient();
let { children } = $props();
- let form = $state();
let textarea = $state();
let abortController = $state(null);
@@ -89,11 +89,20 @@
let loading = $state(false);
- const textareaDisabled = $derived(isGenerating || loading);
+ const textareaDisabled = $derived(
+ isGenerating ||
+ loading ||
+ (currentConversationQuery.data &&
+ currentConversationQuery.data.user_id !== session.current?.user.id)
+ );
+
+ let error = $state(null);
async function handleSubmit() {
if (isGenerating) return;
+ error = null;
+
// TODO: Re-use zod here from server endpoint for better error messages?
if (message.current === '' || !session.current?.user.id || !settings.modelId) return;
@@ -113,7 +122,8 @@
});
if (res.isErr()) {
- return; // TODO: Handle error
+ error = res._unsafeUnwrapErr() ?? 'An unknown error occurred';
+ return;
}
const cid = res.value.conversation_id;
@@ -384,6 +394,14 @@
() => !scrollState.arrived.bottom,
() => (mounted.current ? 250 : 0)
);
+
+ let searchModalOpen = $state(false);
+
+ function openSearchModal() {
+ searchModalOpen = true;
+ }
+
+ let sidebarOpen = $state(false);
@@ -391,41 +409,58 @@
-
+
-
- {#snippet trigger(tooltip)}
-
-
-
- {/snippet}
- {cmdOrCtrl} + B
-
-
- {#if page.params.id}
+
+
{#snippet trigger(tooltip)}
-
- {#if currentConversationQuery.data?.public}
-
- {:else}
-
- {/if}
-
+
+
+
{/snippet}
- {currentConversationQuery.data?.public ? 'Public' : 'Private'}
+ Toggle Sidebar ({cmdOrCtrl} + B)
- {/if}
-
-
+
+
+
{#if page.params.id}
{#snippet trigger(tooltip)}
@@ -454,7 +489,22 @@
Share
{/if}
-
+
+ {#snippet trigger(tooltip)}
+
+ {/snippet}
+ Search ({cmdOrCtrl} + K)
+
+
{#snippet trigger(tooltip)}