feat: one gateway port with path-mounted frontends, markdown chat, collapsible sidebars
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import "./layout.css";
|
||||
import { ModeWatcher } from "mode-watcher";
|
||||
import { onMount } from "svelte";
|
||||
import { onMount, untrack } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
import { base } from "$app/paths";
|
||||
import { page } from "$app/state";
|
||||
@@ -12,10 +12,15 @@
|
||||
import * as Tooltip from "$lib/components/ui/tooltip";
|
||||
import { gateway } from "$lib/gateway.svelte";
|
||||
import { session } from "$lib/session.svelte";
|
||||
import { ui } from "$lib/ui.svelte";
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
const isLogin = $derived(page.url.pathname === `${base}/login`);
|
||||
const inConversations = $derived(
|
||||
page.url.pathname.startsWith(`${base}/conversations`)
|
||||
);
|
||||
let navBefore: boolean | null = null;
|
||||
|
||||
onMount(() => {
|
||||
session.check();
|
||||
@@ -38,8 +43,31 @@
|
||||
return () => gateway.stop();
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (inConversations) {
|
||||
untrack(() => {
|
||||
if (navBefore === null) {
|
||||
navBefore = ui.nav;
|
||||
ui.setNav(false);
|
||||
}
|
||||
});
|
||||
} else if (navBefore !== null) {
|
||||
ui.setNav(navBefore);
|
||||
navBefore = null;
|
||||
}
|
||||
});
|
||||
|
||||
function onKeydown(event: KeyboardEvent) {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key === "b") {
|
||||
event.preventDefault();
|
||||
ui.toggleNav();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={onKeydown} />
|
||||
|
||||
<svelte:head><link href={favicon} rel="icon"></svelte:head>
|
||||
|
||||
<ModeWatcher />
|
||||
|
||||
@@ -1,26 +1,66 @@
|
||||
<script lang="ts">
|
||||
import PanelLeftOpenIcon from "@lucide/svelte/icons/panel-left-open";
|
||||
import { page } from "$app/state";
|
||||
import ConversationList from "$lib/components/conversation-list.svelte";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { gateway } from "$lib/gateway.svelte";
|
||||
import { ui } from "$lib/ui.svelte";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
const selected = $derived(page.params.id ?? null);
|
||||
const running = $derived(gateway.running.length);
|
||||
</script>
|
||||
|
||||
<svelte:head><title>Conversations · Beaver</title></svelte:head>
|
||||
|
||||
<div class="grid min-h-0 flex-1 grid-cols-1 lg:grid-cols-[22rem_minmax(0,1fr)]">
|
||||
<div
|
||||
class={cn(
|
||||
"grid min-h-0 flex-1 grid-cols-1",
|
||||
ui.rail
|
||||
? "lg:grid-cols-[22rem_minmax(0,1fr)]"
|
||||
: "lg:grid-cols-[2.75rem_minmax(0,1fr)]"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
class={cn(
|
||||
"min-h-0 border-r bg-sidebar/40",
|
||||
selected ? "hidden lg:block" : "block"
|
||||
)}
|
||||
>
|
||||
<ConversationList {selected} />
|
||||
{#if ui.rail}
|
||||
<ConversationList {selected} />
|
||||
{:else}
|
||||
<div class="hidden h-full flex-col items-center gap-2 py-2 lg:flex">
|
||||
<Button
|
||||
aria-label="Show conversations"
|
||||
onclick={() => ui.toggleRail()}
|
||||
size="icon-sm"
|
||||
title="Show conversations"
|
||||
variant="ghost"
|
||||
>
|
||||
<PanelLeftOpenIcon class="size-4" />
|
||||
</Button>
|
||||
{#if running > 0}
|
||||
<span
|
||||
class="tabular rounded-full bg-signal/12 px-1.5 font-medium text-signal text-xs"
|
||||
title="{running} running"
|
||||
>
|
||||
{running}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="h-full lg:hidden">
|
||||
<ConversationList {selected} />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div
|
||||
class={cn("min-h-0", selected ? "flex flex-col" : "hidden lg:flex lg:flex-col")}
|
||||
class={cn(
|
||||
"min-h-0",
|
||||
selected ? "flex flex-col" : "hidden lg:flex lg:flex-col"
|
||||
)}
|
||||
>
|
||||
{@render children()}
|
||||
</div>
|
||||
|
||||
@@ -297,3 +297,60 @@
|
||||
@utility hairline {
|
||||
border-color: color-mix(in oklab, var(--border) 100%, transparent);
|
||||
}
|
||||
|
||||
/* chat markdown: typography plugin on the theme tokens, compact rhythm */
|
||||
.prose {
|
||||
--tw-prose-body: var(--foreground);
|
||||
--tw-prose-headings: var(--foreground);
|
||||
--tw-prose-lead: var(--muted-foreground);
|
||||
--tw-prose-links: var(--link);
|
||||
--tw-prose-bold: var(--foreground);
|
||||
--tw-prose-counters: var(--muted-foreground);
|
||||
--tw-prose-bullets: var(--muted-foreground);
|
||||
--tw-prose-hr: var(--border);
|
||||
--tw-prose-quotes: var(--foreground);
|
||||
--tw-prose-quote-borders: var(--border);
|
||||
--tw-prose-captions: var(--muted-foreground);
|
||||
--tw-prose-code: var(--foreground);
|
||||
--tw-prose-pre-code: var(--foreground);
|
||||
--tw-prose-pre-bg: var(--muted);
|
||||
--tw-prose-th-borders: var(--border);
|
||||
--tw-prose-td-borders: var(--border);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.prose
|
||||
:where(p, ul, ol, pre, blockquote, table):not(
|
||||
:where([class~="not-prose"] *)
|
||||
) {
|
||||
margin-block: 0.4em;
|
||||
}
|
||||
.prose :where(h1, h2, h3, h4):not(:where([class~="not-prose"] *)) {
|
||||
margin-block: 0.8em 0.4em;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
}
|
||||
.prose :where(li):not(:where([class~="not-prose"] *)) {
|
||||
margin-block: 0.15em;
|
||||
}
|
||||
.prose :where(code):not(:where([class~="not-prose"] *))::before,
|
||||
.prose :where(code):not(:where([class~="not-prose"] *))::after {
|
||||
content: none;
|
||||
}
|
||||
.prose :where(code):not(:where(pre *)):not(:where([class~="not-prose"] *)) {
|
||||
padding: 0.1em 0.3em;
|
||||
font-weight: 500;
|
||||
background: color-mix(in oklch, var(--muted) 70%, transparent);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.prose :where(pre):not(:where([class~="not-prose"] *)) {
|
||||
padding: 0.6em 0.8em;
|
||||
font-size: 0.8125rem;
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
.prose > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
.prose > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user