Spaces:
Sleeping
Sleeping
File size: 4,679 Bytes
4e62630 7bf1507 89cd532 b104edb d433b55 a1a6daf 612f16b 2c83099 a1a6daf 7bf1507 a1a6daf 2c83099 7bf1507 a1a6daf b104edb a1a6daf 7bf1507 a1a6daf 2c83099 7bf1507 a1a6daf b104edb a1a6daf 4e62630 94ed0cf a1a6daf 94ed0cf a1a6daf 94ed0cf b104edb a1a6daf b104edb 94ed0cf b104edb 4e62630 94ed0cf 4e62630 c3962a6 64c1807 7bf1507 4e62630 8aab1a5 7bf1507 4e62630 a1a6daf 94ed0cf 725337f 94ed0cf a1a6daf d433b55 a1a6daf 94ed0cf 2c83099 a1a6daf 94ed0cf 83ddae9 b104edb 83ddae9 b104edb 83ddae9 ca43db4 a1a6daf ca43db4 38fd55a ca43db4 7bf1507 ca43db4 a1a6daf ca43db4 a1a6daf b104edb a1a6daf 4e62630 b104edb a1a6daf 4e62630 b3ec8c3 4e62630 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
<script lang="ts">
import { onMount, tick } from "svelte";
import HoverTooltip from "$lib/components/HoverTooltip.svelte";
import IconPaperclip from "$lib/components/icons/IconPaperclip.svelte";
import { page } from "$app/state";
import { loginModalOpen } from "$lib/stores/loginModal";
import { isVirtualKeyboard } from "$lib/utils/isVirtualKeyboard";
interface Props {
files?: File[];
mimeTypes?: string[];
value?: string;
placeholder?: string;
loading?: boolean;
disabled?: boolean;
// tools removed
modelIsMultimodal?: boolean;
children?: import("svelte").Snippet;
onPaste?: (e: ClipboardEvent) => void;
focused?: boolean;
onsubmit?: () => void;
}
let {
files = $bindable([]),
mimeTypes = [],
value = $bindable(""),
placeholder = "",
loading = false,
disabled = false,
modelIsMultimodal = false,
children,
onPaste,
focused = $bindable(false),
onsubmit,
}: Props = $props();
const onFileChange = async (e: Event) => {
if (!e.target) return;
const target = e.target as HTMLInputElement;
files = [...files, ...(target.files ?? [])];
};
let textareaElement: HTMLTextAreaElement | undefined = $state();
let isCompositionOn = $state(false);
onMount(() => {
if (!isVirtualKeyboard()) {
textareaElement?.focus();
}
function onFormSubmit() {
adjustTextareaHeight();
}
const formEl = textareaElement?.closest("form");
formEl?.addEventListener("submit", onFormSubmit);
return () => {
formEl?.removeEventListener("submit", onFormSubmit);
};
});
function adjustTextareaHeight() {
if (!textareaElement) {
return;
}
textareaElement.style.height = "auto";
textareaElement.style.height = `${textareaElement.scrollHeight}px`;
if (textareaElement.selectionStart === textareaElement.value.length) {
textareaElement.scrollTop = textareaElement.scrollHeight;
}
}
function handleKeydown(event: KeyboardEvent) {
if (
event.key === "Enter" &&
!event.shiftKey &&
!isCompositionOn &&
!isVirtualKeyboard() &&
value.trim() !== ""
) {
event.preventDefault();
adjustTextareaHeight();
tick();
onsubmit?.();
}
}
// Tools removed; only show file upload when applicable
let showFileUpload = $derived(modelIsMultimodal && mimeTypes.length > 0);
let showNoTools = $derived(!showFileUpload);
</script>
<div class="flex min-h-full flex-1 flex-col" onpaste={onPaste}>
<textarea
rows="1"
tabindex="0"
inputmode="text"
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 placeholder:text-gray-400 focus:ring-0 focus-visible:ring-0 dark:placeholder:text-gray-500 sm:px-3 md:max-h-[8lh]"
class:text-gray-400={disabled}
bind:value
bind:this={textareaElement}
onkeydown={handleKeydown}
oncompositionstart={() => (isCompositionOn = true)}
oncompositionend={() => (isCompositionOn = false)}
oninput={adjustTextareaHeight}
onbeforeinput={(ev) => {
if (page.data.loginRequired) {
ev.preventDefault();
$loginModalOpen = true;
}
}}
{placeholder}
{disabled}
onfocus={() => (focused = true)}
onblur={() => (focused = false)}
></textarea>
{#if !showNoTools}
<div
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",
]}
>
{#if showFileUpload}
{@const mimeTypesString = mimeTypes
.map((m) => {
// if the mime type ends in *, grab the first part so image/* becomes image
if (m.endsWith("*")) {
return m.split("/")[0];
}
// otherwise, return the second part for example application/pdf becomes pdf
return m.split("/")[1];
})
.join(", ")}
<div class="flex items-center">
<HoverTooltip
label={mimeTypesString.includes("*")
? "Upload any file"
: `Upload ${mimeTypesString} files`}
position="top"
TooltipClassNames="text-xs !text-left !w-auto whitespace-nowrap !py-1 !mb-0 max-sm:hidden"
>
<label class="base-tool relative">
<input
disabled={loading}
class="absolute hidden size-0"
aria-label="Upload file"
type="file"
onchange={onFileChange}
accept={mimeTypes.join(",")}
/>
<IconPaperclip classNames="text-xl" />
</label>
</HoverTooltip>
</div>
{/if}
</div>
{/if}
{@render children?.()}
</div>
<style lang="postcss">
:global(pre),
:global(textarea) {
font-family: inherit;
box-sizing: border-box;
line-height: 1.5;
font-size: 16px;
}
</style>
|