Spaces:
Sleeping
Sleeping
Chat input UI updates (#1641)
Browse files* remove extra margin
* chatinput
* add missing auto
* fix: lint
* feat: add fixed to parent container to prevent bouncy scroll on ios
---------
Co-authored-by: Nathan Sarrazin <[email protected]>
src/lib/components/chat/ChatInput.svelte
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
<script lang="ts">
|
2 |
import { browser } from "$app/environment";
|
3 |
-
import { createEventDispatcher, onMount
|
4 |
|
5 |
import HoverTooltip from "$lib/components/HoverTooltip.svelte";
|
6 |
import IconInternet from "$lib/components/icons/IconInternet.svelte";
|
@@ -31,7 +31,6 @@
|
|
31 |
export let placeholder = "";
|
32 |
export let loading = false;
|
33 |
export let disabled = false;
|
34 |
-
|
35 |
export let assistant: Assistant | undefined = undefined;
|
36 |
|
37 |
export let modelHasTools = false;
|
@@ -54,6 +53,21 @@
|
|
54 |
|
55 |
const dispatch = createEventDispatcher<{ submit: void }>();
|
56 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
function isVirtualKeyboard(): boolean {
|
58 |
if (!browser) return false;
|
59 |
|
@@ -70,30 +84,24 @@
|
|
70 |
}
|
71 |
|
72 |
function adjustTextareaHeight() {
|
73 |
-
if (!textareaElement) return;
|
74 |
textareaElement.style.height = "auto";
|
75 |
-
|
76 |
-
|
77 |
-
if (
|
78 |
-
|
|
|
79 |
}
|
80 |
|
81 |
-
|
82 |
-
if (
|
|
|
|
|
|
|
|
|
|
|
|
|
83 |
event.preventDefault();
|
84 |
-
|
85 |
-
// Insert a newline at the cursor position
|
86 |
-
const start = textareaElement.selectionStart;
|
87 |
-
const end = textareaElement.selectionEnd;
|
88 |
-
value = value.substring(0, start) + "\n" + value.substring(end);
|
89 |
-
textareaElement.selectionStart = textareaElement.selectionEnd = start + 1;
|
90 |
-
} else {
|
91 |
-
if (value.trim() !== "") {
|
92 |
-
dispatch("submit");
|
93 |
-
await tick();
|
94 |
-
adjustTextareaHeight();
|
95 |
-
}
|
96 |
-
}
|
97 |
}
|
98 |
}
|
99 |
|
@@ -110,13 +118,6 @@
|
|
110 |
$: documentParserIsOn =
|
111 |
modelHasTools && files.length > 0 && files.some((file) => file.type.startsWith("application/"));
|
112 |
|
113 |
-
onMount(() => {
|
114 |
-
if (!isVirtualKeyboard()) {
|
115 |
-
textareaElement.focus();
|
116 |
-
}
|
117 |
-
adjustTextareaHeight();
|
118 |
-
});
|
119 |
-
|
120 |
$: extraTools = $page.data.tools
|
121 |
.filter((t: ToolFront) => $settings.tools?.includes(t._id))
|
122 |
.filter(
|
@@ -125,29 +126,27 @@
|
|
125 |
) satisfies ToolFront[];
|
126 |
</script>
|
127 |
|
128 |
-
<div class="min-h-full flex-1" on:paste>
|
129 |
-
<
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
</div>
|
147 |
{#if !assistant}
|
148 |
<div
|
149 |
-
class="scrollbar-custom -ml-0.5 flex max-w-[calc(100%-40px)] flex-wrap items-center justify-start gap-2 px-3 pb-2.5 pt-
|
150 |
-
dark:text-gray-400 max-md:flex-nowrap max-md:overflow-x-auto sm:gap-2.5"
|
151 |
>
|
152 |
<HoverTooltip
|
153 |
label="Search the web"
|
@@ -299,7 +298,7 @@
|
|
299 |
TooltipClassNames="text-xs !text-left !w-auto whitespace-nowrap !py-1 max-sm:hidden"
|
300 |
>
|
301 |
<a
|
302 |
-
class="base-tool flex !size-[20px] items-center justify-center rounded-full bg-white
|
303 |
href={`${base}/tools`}
|
304 |
title="Browse more tools"
|
305 |
>
|
@@ -321,10 +320,10 @@
|
|
321 |
}
|
322 |
|
323 |
.base-tool {
|
324 |
-
@apply flex h-[1.6rem] items-center gap-[.2rem] whitespace-nowrap text-xs outline-none transition-all focus:outline-none active:outline-none dark:hover:text-gray-300 sm:hover:text-purple-600;
|
325 |
}
|
326 |
|
327 |
.active-tool {
|
328 |
-
@apply rounded-full bg-purple-
|
329 |
}
|
330 |
</style>
|
|
|
1 |
<script lang="ts">
|
2 |
import { browser } from "$app/environment";
|
3 |
+
import { createEventDispatcher, onMount } from "svelte";
|
4 |
|
5 |
import HoverTooltip from "$lib/components/HoverTooltip.svelte";
|
6 |
import IconInternet from "$lib/components/icons/IconInternet.svelte";
|
|
|
31 |
export let placeholder = "";
|
32 |
export let loading = false;
|
33 |
export let disabled = false;
|
|
|
34 |
export let assistant: Assistant | undefined = undefined;
|
35 |
|
36 |
export let modelHasTools = false;
|
|
|
53 |
|
54 |
const dispatch = createEventDispatcher<{ submit: void }>();
|
55 |
|
56 |
+
onMount(() => {
|
57 |
+
if (!isVirtualKeyboard()) {
|
58 |
+
textareaElement.focus();
|
59 |
+
}
|
60 |
+
function onFormSubmit() {
|
61 |
+
adjustTextareaHeight();
|
62 |
+
}
|
63 |
+
|
64 |
+
const formEl = textareaElement.closest("form");
|
65 |
+
formEl?.addEventListener("submit", onFormSubmit);
|
66 |
+
return () => {
|
67 |
+
formEl?.removeEventListener("submit", onFormSubmit);
|
68 |
+
};
|
69 |
+
});
|
70 |
+
|
71 |
function isVirtualKeyboard(): boolean {
|
72 |
if (!browser) return false;
|
73 |
|
|
|
84 |
}
|
85 |
|
86 |
function adjustTextareaHeight() {
|
|
|
87 |
textareaElement.style.height = "auto";
|
88 |
+
textareaElement.style.height = `${textareaElement.scrollHeight}px`;
|
89 |
+
|
90 |
+
if (textareaElement.selectionStart === textareaElement.value.length) {
|
91 |
+
textareaElement.scrollTop = textareaElement.scrollHeight;
|
92 |
+
}
|
93 |
}
|
94 |
|
95 |
+
function handleKeydown(event: KeyboardEvent) {
|
96 |
+
if (
|
97 |
+
event.key === "Enter" &&
|
98 |
+
!event.shiftKey &&
|
99 |
+
!isCompositionOn &&
|
100 |
+
!isVirtualKeyboard() &&
|
101 |
+
value.trim() !== ""
|
102 |
+
) {
|
103 |
event.preventDefault();
|
104 |
+
dispatch("submit");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
}
|
106 |
}
|
107 |
|
|
|
118 |
$: documentParserIsOn =
|
119 |
modelHasTools && files.length > 0 && files.some((file) => file.type.startsWith("application/"));
|
120 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
121 |
$: extraTools = $page.data.tools
|
122 |
.filter((t: ToolFront) => $settings.tools?.includes(t._id))
|
123 |
.filter(
|
|
|
126 |
) satisfies ToolFront[];
|
127 |
</script>
|
128 |
|
129 |
+
<div class="flex min-h-full flex-1 flex-col" on:paste>
|
130 |
+
<textarea
|
131 |
+
rows="1"
|
132 |
+
tabindex="0"
|
133 |
+
inputmode="text"
|
134 |
+
class="scrollbar-custom max-h-[4lh] w-full resize-none overflow-y-auto overflow-x-hidden border-0 bg-transparent px-2.5 py-2.5 outline-none focus:ring-0 focus-visible:ring-0 max-sm:text-[16px] sm:px-3"
|
135 |
+
class:text-gray-400={disabled}
|
136 |
+
bind:value
|
137 |
+
bind:this={textareaElement}
|
138 |
+
on:keydown={handleKeydown}
|
139 |
+
on:compositionstart={() => (isCompositionOn = true)}
|
140 |
+
on:compositionend={() => (isCompositionOn = false)}
|
141 |
+
on:input={adjustTextareaHeight}
|
142 |
+
on:beforeinput
|
143 |
+
{placeholder}
|
144 |
+
{disabled}
|
145 |
+
/>
|
146 |
+
|
|
|
147 |
{#if !assistant}
|
148 |
<div
|
149 |
+
class="scrollbar-custom -ml-0.5 flex max-w-[calc(100%-40px)] flex-wrap items-center justify-start gap-2.5 px-3 pb-2.5 pt-1.5 text-gray-500 dark:text-gray-400 max-md:flex-nowrap max-md:overflow-x-auto sm:gap-2"
|
|
|
150 |
>
|
151 |
<HoverTooltip
|
152 |
label="Search the web"
|
|
|
298 |
TooltipClassNames="text-xs !text-left !w-auto whitespace-nowrap !py-1 max-sm:hidden"
|
299 |
>
|
300 |
<a
|
301 |
+
class="base-tool flex !size-[20px] items-center justify-center rounded-full border !border-gray-200 !bg-white !transition-none dark:!border-gray-500 dark:!bg-transparent"
|
302 |
href={`${base}/tools`}
|
303 |
title="Browse more tools"
|
304 |
>
|
|
|
320 |
}
|
321 |
|
322 |
.base-tool {
|
323 |
+
@apply flex h-[1.6rem] items-center gap-[.2rem] whitespace-nowrap border border-transparent text-xs outline-none transition-all focus:outline-none active:outline-none dark:hover:text-gray-300 sm:hover:text-purple-600;
|
324 |
}
|
325 |
|
326 |
.active-tool {
|
327 |
+
@apply rounded-full !border-purple-200 bg-purple-100 pl-1 pr-2 text-purple-600 hover:text-purple-600 dark:!border-purple-700 dark:bg-purple-600/40 dark:text-purple-200;
|
328 |
}
|
329 |
</style>
|
src/lib/components/chat/ChatWindow.svelte
CHANGED
@@ -2,7 +2,6 @@
|
|
2 |
import type { Message, MessageFile } from "$lib/types/Message";
|
3 |
import { createEventDispatcher, onDestroy, tick } from "svelte";
|
4 |
|
5 |
-
import CarbonSendAltFilled from "~icons/carbon/send-alt-filled";
|
6 |
import CarbonExport from "~icons/carbon/export";
|
7 |
import CarbonCheckmark from "~icons/carbon/checkmark";
|
8 |
import CarbonCaretDown from "~icons/carbon/caret-down";
|
@@ -376,7 +375,7 @@
|
|
376 |
{/if}
|
377 |
|
378 |
<div class="w-full">
|
379 |
-
<div class="flex w-full
|
380 |
{#if loading}
|
381 |
<StopGeneratingBtn classNames="ml-auto" on:click={() => dispatch("stop")} />
|
382 |
{:else if lastIsError}
|
@@ -390,19 +389,17 @@
|
|
390 |
}
|
391 |
}}
|
392 |
/>
|
393 |
-
{:else}
|
394 |
<div class="ml-auto gap-2">
|
395 |
-
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
-
|
403 |
-
|
404 |
-
/>
|
405 |
-
{/if}
|
406 |
</div>
|
407 |
{/if}
|
408 |
</div>
|
@@ -410,7 +407,7 @@
|
|
410 |
tabindex="-1"
|
411 |
aria-label={isFileUploadEnabled ? "file dropzone" : undefined}
|
412 |
on:submit|preventDefault={handleSubmit}
|
413 |
-
class="relative flex w-full max-w-4xl flex-1 items-center rounded-xl border bg-gray-100
|
414 |
{isReadOnly ? 'opacity-30' : ''}"
|
415 |
>
|
416 |
{#if onDrag && isFileUploadEnabled}
|
@@ -453,13 +450,26 @@
|
|
453 |
</button>
|
454 |
{:else}
|
455 |
<button
|
456 |
-
class="btn absolute bottom-
|
457 |
disabled={!message || isReadOnly}
|
458 |
type="submit"
|
459 |
aria-label="Send message"
|
460 |
name="submit"
|
461 |
>
|
462 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
463 |
</button>
|
464 |
{/if}
|
465 |
</div>
|
|
|
2 |
import type { Message, MessageFile } from "$lib/types/Message";
|
3 |
import { createEventDispatcher, onDestroy, tick } from "svelte";
|
4 |
|
|
|
5 |
import CarbonExport from "~icons/carbon/export";
|
6 |
import CarbonCheckmark from "~icons/carbon/checkmark";
|
7 |
import CarbonCaretDown from "~icons/carbon/caret-down";
|
|
|
375 |
{/if}
|
376 |
|
377 |
<div class="w-full">
|
378 |
+
<div class="flex w-full *:mb-3">
|
379 |
{#if loading}
|
380 |
<StopGeneratingBtn classNames="ml-auto" on:click={() => dispatch("stop")} />
|
381 |
{:else if lastIsError}
|
|
|
389 |
}
|
390 |
}}
|
391 |
/>
|
392 |
+
{:else if messages && lastMessage && lastMessage.interrupted && !isReadOnly}
|
393 |
<div class="ml-auto gap-2">
|
394 |
+
<ContinueBtn
|
395 |
+
on:click={() => {
|
396 |
+
if (lastMessage && lastMessage.ancestors) {
|
397 |
+
dispatch("continue", {
|
398 |
+
id: lastMessage?.id,
|
399 |
+
});
|
400 |
+
}
|
401 |
+
}}
|
402 |
+
/>
|
|
|
|
|
403 |
</div>
|
404 |
{/if}
|
405 |
</div>
|
|
|
407 |
tabindex="-1"
|
408 |
aria-label={isFileUploadEnabled ? "file dropzone" : undefined}
|
409 |
on:submit|preventDefault={handleSubmit}
|
410 |
+
class="relative flex w-full max-w-4xl flex-1 items-center rounded-xl border bg-gray-100 dark:border-gray-600 dark:bg-gray-700
|
411 |
{isReadOnly ? 'opacity-30' : ''}"
|
412 |
>
|
413 |
{#if onDrag && isFileUploadEnabled}
|
|
|
450 |
</button>
|
451 |
{:else}
|
452 |
<button
|
453 |
+
class="btn absolute bottom-2 right-2 size-7 self-end rounded-full border bg-white text-black shadow transition-none enabled:hover:bg-white enabled:hover:shadow-inner disabled:opacity-60 dark:border-gray-600 dark:bg-gray-900 dark:text-white dark:hover:enabled:bg-black"
|
454 |
disabled={!message || isReadOnly}
|
455 |
type="submit"
|
456 |
aria-label="Send message"
|
457 |
name="submit"
|
458 |
>
|
459 |
+
<svg
|
460 |
+
width="1em"
|
461 |
+
height="1em"
|
462 |
+
viewBox="0 0 32 32"
|
463 |
+
fill="none"
|
464 |
+
xmlns="http://www.w3.org/2000/svg"
|
465 |
+
>
|
466 |
+
<path
|
467 |
+
fill-rule="evenodd"
|
468 |
+
clip-rule="evenodd"
|
469 |
+
d="M17.0606 4.23197C16.4748 3.64618 15.525 3.64618 14.9393 4.23197L5.68412 13.4871C5.09833 14.0729 5.09833 15.0226 5.68412 15.6084C6.2699 16.1942 7.21965 16.1942 7.80544 15.6084L14.4999 8.91395V26.7074C14.4999 27.5359 15.1715 28.2074 15.9999 28.2074C16.8283 28.2074 17.4999 27.5359 17.4999 26.7074V8.91395L24.1944 15.6084C24.7802 16.1942 25.7299 16.1942 26.3157 15.6084C26.9015 15.0226 26.9015 14.0729 26.3157 13.4871L17.0606 4.23197Z"
|
470 |
+
fill="currentColor"
|
471 |
+
/>
|
472 |
+
</svg>
|
473 |
</button>
|
474 |
{/if}
|
475 |
</div>
|
src/routes/+layout.svelte
CHANGED
@@ -234,7 +234,7 @@
|
|
234 |
/>
|
235 |
|
236 |
<div
|
237 |
-
class="grid h-full w-screen grid-cols-1 grid-rows-[auto,1fr] overflow-hidden text-smd {!isNavCollapsed
|
238 |
? 'md:grid-cols-[290px,1fr]'
|
239 |
: 'md:grid-cols-[0px,1fr]'} transition-[300ms] [transition-property:grid-template-columns] dark:text-gray-300 md:grid-rows-[1fr]"
|
240 |
>
|
|
|
234 |
/>
|
235 |
|
236 |
<div
|
237 |
+
class="fixed grid h-full w-screen grid-cols-1 grid-rows-[auto,1fr] overflow-hidden text-smd {!isNavCollapsed
|
238 |
? 'md:grid-cols-[290px,1fr]'
|
239 |
: 'md:grid-cols-[0px,1fr]'} transition-[300ms] [transition-property:grid-template-columns] dark:text-gray-300 md:grid-rows-[1fr]"
|
240 |
>
|