Spaces:
Paused
add mobile menu (#64)
Browse files* add mobile menu
* fix
* Auto-naming convos by summarizing them (#58)
* Auto-naming convos by summarizing them
* Ok let's do it only on first message then
* revamp
* location.reload for now
* avoid huge titles
* fix messages width
* add community feedback to nav
* fix tokens keeping coming even when changing conversation (#62)
* favicon
* 🐛 Fix generating bug (#68)
* 🐛 Fix redirect after delete
* ✨ Remove endoftext (#70)
* 🐛 Remove sanitized < (#71)
* 🩹 Change 301 to 302 in case we want to use the route for something else
* 🩹 Use passed fetch cc
@julien-c
When using @huggingface/infernece in the backend we'll need to make it support custom fetch as well
* refactor mobile menu + improve accessibility
* fix missing menu on md size + regression share button on hover
* fix chat title truncate on mobile
* fix layout max-width on mobile
* use a single event dispatcher instead of 2
* add missing type="button"
* remove duplicated wrapper after merge conflict
---------
Co-authored-by: Victor Mustar <victor.mustar@gmail.com>
Co-authored-by: Julien Chaumond <julien@huggingface.co>
Co-authored-by: Eliott C <coyotte508@gmail.com>
- src/lib/components/MobileNav.svelte +60 -0
- src/lib/components/NavMenu.svelte +84 -0
- src/lib/components/chat/ChatWindow.svelte +1 -6
- src/lib/switchTheme.ts +10 -0
- src/routes/+layout.svelte +21 -79
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<script lang="ts">
|
| 2 |
+
import { navigating } from "$app/stores";
|
| 3 |
+
import { createEventDispatcher } from "svelte";
|
| 4 |
+
import { browser } from "$app/environment";
|
| 5 |
+
import { base } from "$app/paths";
|
| 6 |
+
|
| 7 |
+
import CarbonClose from "~icons/carbon/close";
|
| 8 |
+
import CarbonAdd from "~icons/carbon/add";
|
| 9 |
+
import CarbonTextAlignJustify from "~icons/carbon/text-align-justify";
|
| 10 |
+
|
| 11 |
+
export let isOpen = false;
|
| 12 |
+
export let title: string;
|
| 13 |
+
|
| 14 |
+
$: title = title || "New Chat";
|
| 15 |
+
|
| 16 |
+
let closeEl: HTMLButtonElement;
|
| 17 |
+
let openEl: HTMLButtonElement;
|
| 18 |
+
|
| 19 |
+
const dispatch = createEventDispatcher();
|
| 20 |
+
|
| 21 |
+
$: if ($navigating) {
|
| 22 |
+
dispatch("toggle", false);
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
$: if (isOpen && closeEl) {
|
| 26 |
+
closeEl.focus();
|
| 27 |
+
} else if (!isOpen && browser && document.activeElement === closeEl) {
|
| 28 |
+
openEl.focus();
|
| 29 |
+
}
|
| 30 |
+
</script>
|
| 31 |
+
|
| 32 |
+
<nav class="md:hidden flex items-center h-12 border-b px-4 justify-between dark:border-gray-800">
|
| 33 |
+
<button
|
| 34 |
+
type="button"
|
| 35 |
+
class="flex items-center justify-center w-9 h-9 -ml-3 shrink-0"
|
| 36 |
+
on:click={() => dispatch("toggle", true)}
|
| 37 |
+
aria-label="Open menu"
|
| 38 |
+
bind:this={openEl}><CarbonTextAlignJustify /></button
|
| 39 |
+
>
|
| 40 |
+
<span class="px-4 truncate">{title}</span>
|
| 41 |
+
<a href={base || "/"} class="flex items-center justify-center w-9 h-9 -mr-3 shrink-0"
|
| 42 |
+
><CarbonAdd /></a
|
| 43 |
+
>
|
| 44 |
+
</nav>
|
| 45 |
+
<nav
|
| 46 |
+
class="fixed inset-0 z-50 grid grid-rows-[auto,auto,1fr,auto] grid-cols-1 max-h-screen bg-white dark:bg-gray-900 bg-gradient-to-l from-gray-50 dark:from-gray-800/30 {isOpen
|
| 47 |
+
? 'block'
|
| 48 |
+
: 'hidden'}"
|
| 49 |
+
>
|
| 50 |
+
<div class="flex items-center px-4 h-12">
|
| 51 |
+
<button
|
| 52 |
+
type="button"
|
| 53 |
+
class="flex items-center justify-center ml-auto w-9 h-9 -mr-3"
|
| 54 |
+
on:click={() => dispatch("toggle", false)}
|
| 55 |
+
aria-label="Close menu"
|
| 56 |
+
bind:this={closeEl}><CarbonClose /></button
|
| 57 |
+
>
|
| 58 |
+
</div>
|
| 59 |
+
<slot />
|
| 60 |
+
</nav>
|
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<script lang="ts">
|
| 2 |
+
import { base } from "$app/paths";
|
| 3 |
+
import { page } from "$app/stores";
|
| 4 |
+
import { createEventDispatcher } from "svelte";
|
| 5 |
+
|
| 6 |
+
import CarbonTrashCan from "~icons/carbon/trash-can";
|
| 7 |
+
import CarbonExport from "~icons/carbon/export";
|
| 8 |
+
import { switchTheme } from "$lib/switchTheme";
|
| 9 |
+
|
| 10 |
+
const dispatch = createEventDispatcher<{
|
| 11 |
+
shareConversation: { id: string; title: string };
|
| 12 |
+
deleteConversation: string;
|
| 13 |
+
}>();
|
| 14 |
+
|
| 15 |
+
export let conversations: Array<{
|
| 16 |
+
id: string;
|
| 17 |
+
title: string;
|
| 18 |
+
}> = [];
|
| 19 |
+
</script>
|
| 20 |
+
|
| 21 |
+
<div class="flex-none sticky top-0 p-3 flex flex-col">
|
| 22 |
+
<a
|
| 23 |
+
href={base || "/"}
|
| 24 |
+
class="border px-12 py-2.5 rounded-lg shadow bg-white dark:bg-gray-700 dark:border-gray-600 text-center"
|
| 25 |
+
>
|
| 26 |
+
New Chat
|
| 27 |
+
</a>
|
| 28 |
+
</div>
|
| 29 |
+
<div class="flex flex-col overflow-y-auto p-3 -mt-3 gap-1">
|
| 30 |
+
{#each conversations as conv}
|
| 31 |
+
<a
|
| 32 |
+
data-sveltekit-noscroll
|
| 33 |
+
href="{base}/conversation/{conv.id}"
|
| 34 |
+
class="group pl-3 pr-2 h-11 rounded-lg flex-none text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-1.5 {conv.id ===
|
| 35 |
+
$page.params.id
|
| 36 |
+
? 'bg-gray-100 dark:bg-gray-700'
|
| 37 |
+
: ''}"
|
| 38 |
+
>
|
| 39 |
+
<div class="flex-1 truncate">{conv.title}</div>
|
| 40 |
+
|
| 41 |
+
<button
|
| 42 |
+
type="button"
|
| 43 |
+
class="flex md:hidden md:group-hover:flex w-5 h-5 items-center justify-center rounded"
|
| 44 |
+
title="Share conversation"
|
| 45 |
+
on:click|preventDefault={() =>
|
| 46 |
+
dispatch("shareConversation", { id: conv.id, title: conv.title })}
|
| 47 |
+
>
|
| 48 |
+
<CarbonExport class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300 text-xs" />
|
| 49 |
+
</button>
|
| 50 |
+
|
| 51 |
+
<button
|
| 52 |
+
type="button"
|
| 53 |
+
class="flex md:hidden md:group-hover:flex w-5 h-5 items-center justify-center rounded"
|
| 54 |
+
title="Delete conversation"
|
| 55 |
+
on:click|preventDefault={() => dispatch("deleteConversation", conv.id)}
|
| 56 |
+
>
|
| 57 |
+
<CarbonTrashCan
|
| 58 |
+
class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300 text-xs"
|
| 59 |
+
/>
|
| 60 |
+
</button>
|
| 61 |
+
</a>
|
| 62 |
+
{/each}
|
| 63 |
+
</div>
|
| 64 |
+
<div class="flex flex-col p-3 gap-2">
|
| 65 |
+
<button
|
| 66 |
+
on:click={switchTheme}
|
| 67 |
+
type="button"
|
| 68 |
+
class="text-left flex items-center first-letter:capitalize truncate py-3 px-3 rounded-lg flex-none text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700"
|
| 69 |
+
>
|
| 70 |
+
Theme
|
| 71 |
+
</button>
|
| 72 |
+
<a
|
| 73 |
+
href="https://huggingface.co/spaces/huggingchat/chat-ui/discussions"
|
| 74 |
+
class="text-left flex items-center first-letter:capitalize truncate py-3 px-3 rounded-lg flex-none text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700"
|
| 75 |
+
>
|
| 76 |
+
Community feedback
|
| 77 |
+
</a>
|
| 78 |
+
<a
|
| 79 |
+
href={base}
|
| 80 |
+
class="truncate py-3 px-3 rounded-lg flex-none text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700"
|
| 81 |
+
>
|
| 82 |
+
Settings
|
| 83 |
+
</a>
|
| 84 |
+
</div>
|
|
@@ -20,12 +20,7 @@
|
|
| 20 |
const dispatch = createEventDispatcher<{ message: string; share: void }>();
|
| 21 |
</script>
|
| 22 |
|
| 23 |
-
<div class="relative h-
|
| 24 |
-
<nav class="sm:hidden flex items-center h-12 border-b px-4 justify-between dark:border-gray-800">
|
| 25 |
-
<button><CarbonTextAlignJustify /></button>
|
| 26 |
-
<button>New Chat</button>
|
| 27 |
-
<button><CarbonAdd /></button>
|
| 28 |
-
</nav>
|
| 29 |
<ChatMessages {loading} {pending} {messages} on:message />
|
| 30 |
<div
|
| 31 |
class="flex flex-col max-md:border-t dark:border-gray-800 items-center max-md:dark:bg-gray-900 max-md:bg-white bg-gradient-to-t from-white to-white/0 dark:from-gray-900 dark:to-gray-900/0 justify-center absolute inset-x-0 max-w-3xl xl:max-w-4xl mx-auto px-5 bottom-0 py-4 md:py-8 w-full"
|
|
|
|
| 20 |
const dispatch = createEventDispatcher<{ message: string; share: void }>();
|
| 21 |
</script>
|
| 22 |
|
| 23 |
+
<div class="relative min-h-0">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
<ChatMessages {loading} {pending} {messages} on:message />
|
| 25 |
<div
|
| 26 |
class="flex flex-col max-md:border-t dark:border-gray-800 items-center max-md:dark:bg-gray-900 max-md:bg-white bg-gradient-to-t from-white to-white/0 dark:from-gray-900 dark:to-gray-900/0 justify-center absolute inset-x-0 max-w-3xl xl:max-w-4xl mx-auto px-5 bottom-0 py-4 md:py-8 w-full"
|
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export function switchTheme() {
|
| 2 |
+
const { classList } = document.querySelector("html") as HTMLElement;
|
| 3 |
+
if (classList.contains("dark")) {
|
| 4 |
+
classList.remove("dark");
|
| 5 |
+
localStorage.theme = "light";
|
| 6 |
+
} else {
|
| 7 |
+
classList.add("dark");
|
| 8 |
+
localStorage.theme = "dark";
|
| 9 |
+
}
|
| 10 |
+
}
|
|
@@ -3,25 +3,16 @@
|
|
| 3 |
import { page } from "$app/stores";
|
| 4 |
import "../styles/main.css";
|
| 5 |
import type { LayoutData } from "./$types";
|
| 6 |
-
|
| 7 |
-
import CarbonTrashCan from "~icons/carbon/trash-can";
|
| 8 |
-
import CarbonExport from "~icons/carbon/export";
|
| 9 |
import { base } from "$app/paths";
|
| 10 |
import { shareConversation } from "$lib/shareConversation";
|
| 11 |
import { UrlDependency } from "$lib/types/UrlDependency";
|
| 12 |
|
|
|
|
|
|
|
|
|
|
| 13 |
export let data: LayoutData;
|
| 14 |
|
| 15 |
-
|
| 16 |
-
const { classList } = document.querySelector("html") as HTMLElement;
|
| 17 |
-
if (classList.contains("dark")) {
|
| 18 |
-
classList.remove("dark");
|
| 19 |
-
localStorage.theme = "light";
|
| 20 |
-
} else {
|
| 21 |
-
classList.add("dark");
|
| 22 |
-
localStorage.theme = "dark";
|
| 23 |
-
}
|
| 24 |
-
}
|
| 25 |
|
| 26 |
async function deleteConversation(id: string) {
|
| 27 |
try {
|
|
@@ -50,76 +41,27 @@
|
|
| 50 |
</script>
|
| 51 |
|
| 52 |
<div
|
| 53 |
-
class="grid h-screen w-screen md:grid-cols-[280px,1fr] overflow-hidden text-smd dark:text-gray-300"
|
| 54 |
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
<nav
|
| 56 |
class="max-md:hidden grid grid-rows-[auto,1fr,auto] grid-cols-1 max-h-screen bg-gradient-to-l from-gray-50 dark:from-gray-800/30 rounded-r-xl"
|
| 57 |
>
|
| 58 |
-
<
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
New Chat
|
| 64 |
-
</a>
|
| 65 |
-
</div>
|
| 66 |
-
<div class="flex flex-col overflow-y-auto p-3 -mt-3 gap-1">
|
| 67 |
-
{#each data.conversations as conv}
|
| 68 |
-
<a
|
| 69 |
-
data-sveltekit-noscroll
|
| 70 |
-
href="{base}/conversation/{conv.id}"
|
| 71 |
-
class="pl-3 pr-2 h-11 group rounded-lg flex-none text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-1.5 {conv.id ===
|
| 72 |
-
$page.params.id
|
| 73 |
-
? 'bg-gray-100 dark:bg-gray-700'
|
| 74 |
-
: ''}"
|
| 75 |
-
>
|
| 76 |
-
<div class="flex-1 truncate">{conv.title}</div>
|
| 77 |
-
|
| 78 |
-
<button
|
| 79 |
-
type="button"
|
| 80 |
-
class="w-5 h-5 items-center justify-center hidden group-hover:flex rounded"
|
| 81 |
-
title="Share conversation"
|
| 82 |
-
on:click|preventDefault={() => shareConversation(conv.id, conv.title)}
|
| 83 |
-
>
|
| 84 |
-
<CarbonExport
|
| 85 |
-
class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300 text-xs"
|
| 86 |
-
/>
|
| 87 |
-
</button>
|
| 88 |
-
|
| 89 |
-
<button
|
| 90 |
-
type="button"
|
| 91 |
-
class="w-5 h-5 items-center justify-center hidden group-hover:flex rounded"
|
| 92 |
-
title="Delete conversation"
|
| 93 |
-
on:click|preventDefault={() => deleteConversation(conv.id)}
|
| 94 |
-
>
|
| 95 |
-
<CarbonTrashCan
|
| 96 |
-
class="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300 text-xs"
|
| 97 |
-
/>
|
| 98 |
-
</button>
|
| 99 |
-
</a>
|
| 100 |
-
{/each}
|
| 101 |
-
</div>
|
| 102 |
-
<div class="flex flex-col p-3 gap-2">
|
| 103 |
-
<button
|
| 104 |
-
on:click={switchTheme}
|
| 105 |
-
type="button"
|
| 106 |
-
class="text-left flex items-center first-letter:capitalize truncate py-3 px-3 rounded-lg flex-none text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700"
|
| 107 |
-
>
|
| 108 |
-
Theme
|
| 109 |
-
</button>
|
| 110 |
-
<a
|
| 111 |
-
href="https://huggingface.co/spaces/huggingchat/chat-ui/discussions"
|
| 112 |
-
class="text-left flex items-center first-letter:capitalize truncate py-3 px-3 rounded-lg flex-none text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700"
|
| 113 |
-
>
|
| 114 |
-
Community feedback
|
| 115 |
-
</a>
|
| 116 |
-
<a
|
| 117 |
-
href={base}
|
| 118 |
-
class="truncate py-3 px-3 rounded-lg flex-none text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700"
|
| 119 |
-
>
|
| 120 |
-
Settings
|
| 121 |
-
</a>
|
| 122 |
-
</div>
|
| 123 |
</nav>
|
| 124 |
<slot />
|
| 125 |
</div>
|
|
|
|
| 3 |
import { page } from "$app/stores";
|
| 4 |
import "../styles/main.css";
|
| 5 |
import type { LayoutData } from "./$types";
|
|
|
|
|
|
|
|
|
|
| 6 |
import { base } from "$app/paths";
|
| 7 |
import { shareConversation } from "$lib/shareConversation";
|
| 8 |
import { UrlDependency } from "$lib/types/UrlDependency";
|
| 9 |
|
| 10 |
+
import MobileNav from "$lib/components/MobileNav.svelte";
|
| 11 |
+
import NavMenu from "$lib/components/NavMenu.svelte";
|
| 12 |
+
|
| 13 |
export let data: LayoutData;
|
| 14 |
|
| 15 |
+
let isNavOpen = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
async function deleteConversation(id: string) {
|
| 18 |
try {
|
|
|
|
| 41 |
</script>
|
| 42 |
|
| 43 |
<div
|
| 44 |
+
class="grid h-screen w-screen grid-cols-1 grid-rows-[auto,1fr] md:grid-rows-[1fr] md:grid-cols-[280px,1fr] overflow-hidden text-smd dark:text-gray-300"
|
| 45 |
>
|
| 46 |
+
<MobileNav
|
| 47 |
+
isOpen={isNavOpen}
|
| 48 |
+
on:toggle={(ev) => (isNavOpen = ev.detail)}
|
| 49 |
+
title={data.conversations.find((conv) => conv.id === $page.params.id)?.title}
|
| 50 |
+
>
|
| 51 |
+
<NavMenu
|
| 52 |
+
conversations={data.conversations}
|
| 53 |
+
on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)}
|
| 54 |
+
on:deleteConversation={(ev) => deleteConversation(ev.detail)}
|
| 55 |
+
/>
|
| 56 |
+
</MobileNav>
|
| 57 |
<nav
|
| 58 |
class="max-md:hidden grid grid-rows-[auto,1fr,auto] grid-cols-1 max-h-screen bg-gradient-to-l from-gray-50 dark:from-gray-800/30 rounded-r-xl"
|
| 59 |
>
|
| 60 |
+
<NavMenu
|
| 61 |
+
conversations={data.conversations}
|
| 62 |
+
on:shareConversation={(ev) => shareConversation(ev.detail.id, ev.detail.title)}
|
| 63 |
+
on:deleteConversation={(ev) => deleteConversation(ev.detail)}
|
| 64 |
+
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
</nav>
|
| 66 |
<slot />
|
| 67 |
</div>
|