|
1 | 1 | <script lang="ts">
|
2 | 2 | import { browser } from "$app/environment";
|
3 |
| - import { createEventDispatcher, onMount, tick } from "svelte"; |
| 3 | + import { createEventDispatcher, onMount } from "svelte"; |
4 | 4 |
|
5 | 5 | import HoverTooltip from "$lib/components/HoverTooltip.svelte";
|
6 | 6 | import IconInternet from "$lib/components/icons/IconInternet.svelte";
|
|
31 | 31 | export let placeholder = "";
|
32 | 32 | export let loading = false;
|
33 | 33 | export let disabled = false;
|
34 |
| -
|
35 | 34 | export let assistant: Assistant | undefined = undefined;
|
36 | 35 |
|
37 | 36 | export let modelHasTools = false;
|
|
54 | 53 |
|
55 | 54 | const dispatch = createEventDispatcher<{ submit: void }>();
|
56 | 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 | +
|
57 | 71 | function isVirtualKeyboard(): boolean {
|
58 | 72 | if (!browser) return false;
|
59 | 73 |
|
|
70 | 84 | }
|
71 | 85 |
|
72 | 86 | function adjustTextareaHeight() {
|
73 |
| - if (!textareaElement) return; |
74 | 87 | textareaElement.style.height = "auto";
|
75 |
| - const newHeight = Math.min(textareaElement.scrollHeight, parseInt("96em")); |
76 |
| - textareaElement.style.height = `${newHeight}px`; |
77 |
| - if (!textareaElement.parentElement) return; |
78 |
| - textareaElement.parentElement.style.height = `${newHeight}px`; |
| 88 | + textareaElement.style.height = `${textareaElement.scrollHeight}px`; |
| 89 | +
|
| 90 | + if (textareaElement.selectionStart === textareaElement.value.length) { |
| 91 | + textareaElement.scrollTop = textareaElement.scrollHeight; |
| 92 | + } |
79 | 93 | }
|
80 | 94 |
|
81 |
| - async function handleKeydown(event: KeyboardEvent) { |
82 |
| - if (event.key === "Enter" && !event.shiftKey && !isCompositionOn) { |
| 95 | + function handleKeydown(event: KeyboardEvent) { |
| 96 | + if ( |
| 97 | + event.key === "Enter" && |
| 98 | + !event.shiftKey && |
| 99 | + !isCompositionOn && |
| 100 | + !isVirtualKeyboard() && |
| 101 | + value.trim() !== "" |
| 102 | + ) { |
83 | 103 | event.preventDefault();
|
84 |
| - if (isVirtualKeyboard()) { |
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 |
| - } |
| 104 | + dispatch("submit"); |
97 | 105 | }
|
98 | 106 | }
|
99 | 107 |
|
|
110 | 118 | $: documentParserIsOn =
|
111 | 119 | modelHasTools && files.length > 0 && files.some((file) => file.type.startsWith("application/"));
|
112 | 120 |
|
113 |
| - onMount(() => { |
114 |
| - if (!isVirtualKeyboard()) { |
115 |
| - textareaElement.focus(); |
116 |
| - } |
117 |
| - adjustTextareaHeight(); |
118 |
| - }); |
119 |
| -
|
120 | 121 | $: extraTools = $page.data.tools
|
121 | 122 | .filter((t: ToolFront) => $settings.tools?.includes(t._id))
|
122 | 123 | .filter(
|
|
125 | 126 | ) satisfies ToolFront[];
|
126 | 127 | </script>
|
127 | 128 |
|
128 |
| -<div class="min-h-full flex-1" on:paste> |
129 |
| - <div class="relative w-full min-w-0"> |
130 |
| - <textarea |
131 |
| - enterkeyhint={!isVirtualKeyboard() ? "enter" : "send"} |
132 |
| - tabindex="0" |
133 |
| - rows="1" |
134 |
| - class="scrollbar-custom max-h-[96em] w-full resize-none scroll-p-3 overflow-y-auto overflow-x-hidden border-0 bg-transparent px-3 py-2.5 outline-none focus:ring-0 focus-visible:ring-0 max-sm:p-2.5 max-sm:text-[16px]" |
135 |
| - class:text-gray-400={disabled} |
136 |
| - bind:value |
137 |
| - bind:this={textareaElement} |
138 |
| - {disabled} |
139 |
| - on:keydown={handleKeydown} |
140 |
| - on:compositionstart={() => (isCompositionOn = true)} |
141 |
| - on:compositionend={() => (isCompositionOn = false)} |
142 |
| - on:input={adjustTextareaHeight} |
143 |
| - on:beforeinput |
144 |
| - {placeholder} |
145 |
| - /> |
146 |
| - </div> |
| 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 | 147 | {#if !assistant}
|
148 | 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-0.5 text-gray-500 |
150 |
| - dark:text-gray-400 max-md:flex-nowrap max-md:overflow-x-auto sm:gap-2.5" |
| 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" |
151 | 150 | >
|
152 | 151 | <HoverTooltip
|
153 | 152 | label="Search the web"
|
|
299 | 298 | TooltipClassNames="text-xs !text-left !w-auto whitespace-nowrap !py-1 max-sm:hidden"
|
300 | 299 | >
|
301 | 300 | <a
|
302 |
| - class="base-tool flex !size-[20px] items-center justify-center rounded-full bg-white/10" |
| 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" |
303 | 302 | href={`${base}/tools`}
|
304 | 303 | title="Browse more tools"
|
305 | 304 | >
|
|
321 | 320 | }
|
322 | 321 |
|
323 | 322 | .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; |
| 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; |
325 | 324 | }
|
326 | 325 |
|
327 | 326 | .active-tool {
|
328 |
| - @apply rounded-full bg-purple-500/15 pl-1 pr-2 text-purple-600 hover:text-purple-600 dark:bg-purple-600/40 dark:text-purple-300; |
| 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; |
329 | 328 | }
|
330 | 329 | </style>
|
0 commit comments