mirror of
https://github.com/open-webui/open-webui
synced 2025-05-24 23:04:36 +00:00
267 lines
8.2 KiB
Svelte
267 lines
8.2 KiB
Svelte
<script lang="ts">
|
|
import { toast } from 'svelte-sonner';
|
|
import { tick, getContext } from 'svelte';
|
|
|
|
const i18n = getContext('i18n');
|
|
|
|
import { mobile, settings } from '$lib/stores';
|
|
|
|
import Tooltip from '../common/Tooltip.svelte';
|
|
import RichTextInput from '../common/RichTextInput.svelte';
|
|
import VoiceRecording from '../chat/MessageInput/VoiceRecording.svelte';
|
|
|
|
export let placeholder = $i18n.t('Send a Message');
|
|
export let transparentBackground = false;
|
|
|
|
let recording = false;
|
|
|
|
let content = '';
|
|
|
|
export let onSubmit: Function;
|
|
|
|
let submitHandler = async () => {
|
|
if (content === '') {
|
|
return;
|
|
}
|
|
|
|
onSubmit({
|
|
content
|
|
});
|
|
|
|
content = '';
|
|
await tick();
|
|
|
|
const chatInputElement = document.getElementById('chat-input');
|
|
chatInputElement?.focus();
|
|
};
|
|
</script>
|
|
|
|
<div class="{transparentBackground ? 'bg-transparent' : 'bg-white dark:bg-gray-900'} ">
|
|
<div class="max-w-6xl px-2.5 mx-auto inset-x-0">
|
|
<div class="">
|
|
{#if recording}
|
|
<VoiceRecording
|
|
bind:recording
|
|
on:cancel={async () => {
|
|
recording = false;
|
|
|
|
await tick();
|
|
document.getElementById('chat-input')?.focus();
|
|
}}
|
|
on:confirm={async (e) => {
|
|
const { text, filename } = e.detail;
|
|
content = `${content}${text} `;
|
|
recording = false;
|
|
|
|
await tick();
|
|
document.getElementById('chat-input')?.focus();
|
|
}}
|
|
/>
|
|
{:else}
|
|
<form
|
|
class="w-full flex gap-1.5"
|
|
on:submit|preventDefault={() => {
|
|
submitHandler();
|
|
}}
|
|
>
|
|
<div
|
|
class="flex-1 flex flex-col relative w-full rounded-3xl px-1 bg-gray-50 dark:bg-gray-400/5 dark:text-gray-100"
|
|
dir={$settings?.chatDirection ?? 'LTR'}
|
|
>
|
|
<div class=" flex">
|
|
<div class="ml-1 self-end mb-1.5 flex space-x-1">
|
|
<button
|
|
class="bg-transparent hover:bg-white/80 text-gray-800 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-2 outline-none focus:outline-none"
|
|
type="button"
|
|
aria-label="More"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 20 20"
|
|
fill="currentColor"
|
|
class="size-5"
|
|
>
|
|
<path
|
|
d="M10.75 4.75a.75.75 0 0 0-1.5 0v4.5h-4.5a.75.75 0 0 0 0 1.5h4.5v4.5a.75.75 0 0 0 1.5 0v-4.5h4.5a.75.75 0 0 0 0-1.5h-4.5v-4.5Z"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
{#if $settings?.richTextInput ?? true}
|
|
<div
|
|
class="scrollbar-hidden text-left bg-transparent dark:text-gray-100 outline-none w-full py-2.5 px-1 rounded-xl resize-none h-fit max-h-80 overflow-auto"
|
|
>
|
|
<RichTextInput
|
|
bind:value={content}
|
|
id="chat-input"
|
|
messageInput={true}
|
|
shiftEnter={!$mobile ||
|
|
!(
|
|
'ontouchstart' in window ||
|
|
navigator.maxTouchPoints > 0 ||
|
|
navigator.msMaxTouchPoints > 0
|
|
)}
|
|
{placeholder}
|
|
largeTextAsFile={$settings?.largeTextAsFile ?? false}
|
|
on:keydown={async (e) => {
|
|
e = e.detail.event;
|
|
const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac
|
|
if (
|
|
!$mobile ||
|
|
!(
|
|
'ontouchstart' in window ||
|
|
navigator.maxTouchPoints > 0 ||
|
|
navigator.msMaxTouchPoints > 0
|
|
)
|
|
) {
|
|
// Prevent Enter key from creating a new line
|
|
// Uses keyCode '13' for Enter key for chinese/japanese keyboards
|
|
if (e.keyCode === 13 && !e.shiftKey) {
|
|
e.preventDefault();
|
|
}
|
|
|
|
// Submit the content when Enter key is pressed
|
|
if (content !== '' && e.keyCode === 13 && !e.shiftKey) {
|
|
submitHandler();
|
|
}
|
|
}
|
|
|
|
if (e.key === 'Escape') {
|
|
console.log('Escape');
|
|
}
|
|
}}
|
|
on:paste={async (e) => {
|
|
e = e.detail.event;
|
|
console.log(e);
|
|
}}
|
|
/>
|
|
</div>
|
|
{:else}
|
|
<textarea
|
|
id="chat-input"
|
|
class="scrollbar-hidden bg-transparent dark:text-gray-100 outline-none w-full py-3 px-1 rounded-xl resize-none h-[48px]"
|
|
{placeholder}
|
|
bind:value={content}
|
|
on:keydown={async (e) => {
|
|
e = e.detail.event;
|
|
const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac
|
|
if (
|
|
!$mobile ||
|
|
!(
|
|
'ontouchstart' in window ||
|
|
navigator.maxTouchPoints > 0 ||
|
|
navigator.msMaxTouchPoints > 0
|
|
)
|
|
) {
|
|
// Prevent Enter key from creating a new line
|
|
// Uses keyCode '13' for Enter key for chinese/japanese keyboards
|
|
if (e.keyCode === 13 && !e.shiftKey) {
|
|
e.preventDefault();
|
|
}
|
|
|
|
// Submit the content when Enter key is pressed
|
|
if (content !== '' && e.keyCode === 13 && !e.shiftKey) {
|
|
submitHandler();
|
|
}
|
|
}
|
|
|
|
if (e.key === 'Escape') {
|
|
console.log('Escape');
|
|
}
|
|
}}
|
|
rows="1"
|
|
on:input={async (e) => {
|
|
e.target.style.height = '';
|
|
e.target.style.height = Math.min(e.target.scrollHeight, 320) + 'px';
|
|
}}
|
|
on:focus={async (e) => {
|
|
e.target.style.height = '';
|
|
e.target.style.height = Math.min(e.target.scrollHeight, 320) + 'px';
|
|
}}
|
|
/>
|
|
{/if}
|
|
|
|
<div class="self-end mb-1.5 flex space-x-1 mr-1">
|
|
{#if content === ''}
|
|
<Tooltip content={$i18n.t('Record voice')}>
|
|
<button
|
|
id="voice-input-button"
|
|
class=" text-gray-600 dark:text-gray-300 hover:text-gray-700 dark:hover:text-gray-200 transition rounded-full p-1.5 mr-0.5 self-center"
|
|
type="button"
|
|
on:click={async () => {
|
|
try {
|
|
let stream = await navigator.mediaDevices
|
|
.getUserMedia({ audio: true })
|
|
.catch(function (err) {
|
|
toast.error(
|
|
$i18n.t(`Permission denied when accessing microphone: {{error}}`, {
|
|
error: err
|
|
})
|
|
);
|
|
return null;
|
|
});
|
|
|
|
if (stream) {
|
|
recording = true;
|
|
const tracks = stream.getTracks();
|
|
tracks.forEach((track) => track.stop());
|
|
}
|
|
stream = null;
|
|
} catch {
|
|
toast.error($i18n.t('Permission denied when accessing microphone'));
|
|
}
|
|
}}
|
|
aria-label="Voice Input"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 20 20"
|
|
fill="currentColor"
|
|
class="w-5 h-5 translate-y-[0.5px]"
|
|
>
|
|
<path d="M7 4a3 3 0 016 0v6a3 3 0 11-6 0V4z" />
|
|
<path
|
|
d="M5.5 9.643a.75.75 0 00-1.5 0V10c0 3.06 2.29 5.585 5.25 5.954V17.5h-1.5a.75.75 0 000 1.5h4.5a.75.75 0 000-1.5h-1.5v-1.546A6.001 6.001 0 0016 10v-.357a.75.75 0 00-1.5 0V10a4.5 4.5 0 01-9 0v-.357z"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</Tooltip>
|
|
{/if}
|
|
|
|
<div class=" flex items-center">
|
|
<div class=" flex items-center">
|
|
<Tooltip content={$i18n.t('Send message')}>
|
|
<button
|
|
id="send-message-button"
|
|
class="{content !== ''
|
|
? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
|
|
: 'text-white bg-gray-200 dark:text-gray-900 dark:bg-gray-700 disabled'} transition rounded-full p-1.5 self-center"
|
|
type="submit"
|
|
disabled={content === ''}
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 16 16"
|
|
fill="currentColor"
|
|
class="size-6"
|
|
>
|
|
<path
|
|
fill-rule="evenodd"
|
|
d="M8 14a.75.75 0 0 1-.75-.75V4.56L4.03 7.78a.75.75 0 0 1-1.06-1.06l4.5-4.5a.75.75 0 0 1 1.06 0l4.5 4.5a.75.75 0 0 1-1.06 1.06L8.75 4.56v8.69A.75.75 0 0 1 8 14Z"
|
|
clip-rule="evenodd"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</Tooltip>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|