enh: formatting toolbar for chat

This commit is contained in:
Timothy Jaeryang Baek 2025-08-06 12:21:18 +04:00
parent 0acd78049d
commit bfa42c6277
5 changed files with 256 additions and 211 deletions

View file

@ -60,7 +60,7 @@
export let scrollToBottom: Function = () => {};
export let acceptFiles = true;
export let showFormattingButtons = true;
export let showFormattingToolbar = true;
let showInputVariablesModal = false;
let inputVariables: Record<string, any> = {};
@ -700,7 +700,7 @@
bind:this={chatInputElement}
json={true}
messageInput={true}
{showFormattingButtons}
{showFormattingToolbar}
shiftEnter={!($settings?.ctrlEnterToSend ?? false) &&
(!$mobile ||
!(

View file

@ -1086,6 +1086,7 @@
class="scrollbar-hidden rtl:text-right ltr:text-left bg-transparent dark:text-gray-100 outline-hidden w-full pt-2.5 pb-[5px] px-1 resize-none h-fit max-h-80 overflow-auto"
id="chat-input-container"
>
{#key $settings?.showFormattingToolbar ?? false}
<RichTextInput
bind:this={chatInputElement}
id="chat-input"
@ -1095,7 +1096,8 @@
}}
json={true}
messageInput={true}
showFormattingButtons={false}
showFormattingToolbar={$settings?.showFormattingToolbar ?? false}
floatingMenuPlacement={'top-start'}
insertPromptAsRichText={$settings?.insertPromptAsRichText ?? false}
shiftEnter={!($settings?.ctrlEnterToSend ?? false) &&
(!$mobile ||
@ -1183,7 +1185,9 @@
commandsElement.selectUp();
const commandOptionButton = [
...document.getElementsByClassName('selected-command-option-button')
...document.getElementsByClassName(
'selected-command-option-button'
)
]?.at(-1);
commandOptionButton.scrollIntoView({ block: 'center' });
}
@ -1193,7 +1197,9 @@
commandsElement.selectDown();
const commandOptionButton = [
...document.getElementsByClassName('selected-command-option-button')
...document.getElementsByClassName(
'selected-command-option-button'
)
]?.at(-1);
commandOptionButton.scrollIntoView({ block: 'center' });
}
@ -1202,7 +1208,9 @@
e.preventDefault();
const commandOptionButton = [
...document.getElementsByClassName('selected-command-option-button')
...document.getElementsByClassName(
'selected-command-option-button'
)
]?.at(-1);
commandOptionButton?.click();
@ -1212,7 +1220,9 @@
e.preventDefault();
const commandOptionButton = [
...document.getElementsByClassName('selected-command-option-button')
...document.getElementsByClassName(
'selected-command-option-button'
)
]?.at(-1);
if (commandOptionButton) {
@ -1300,9 +1310,13 @@
if (text.length > PASTED_TEXT_CHARACTER_LIMIT) {
e.preventDefault();
const blob = new Blob([text], { type: 'text/plain' });
const file = new File([blob], `Pasted_Text_${Date.now()}.txt`, {
const file = new File(
[blob],
`Pasted_Text_${Date.now()}.txt`,
{
type: 'text/plain'
});
}
);
await uploadFileHandler(file, true);
}
@ -1312,6 +1326,7 @@
}
}}
/>
{/key}
</div>
{:else}
<textarea

View file

@ -38,6 +38,7 @@
let detectArtifacts = true;
let richTextInput = true;
let showFormattingToolbar = false;
let insertPromptAsRichText = false;
let promptAutocomplete = false;
@ -228,6 +229,11 @@
saveSettings({ richTextInput });
};
const toggleShowFormattingToolbar = async () => {
showFormattingToolbar = !showFormattingToolbar;
saveSettings({ showFormattingToolbar });
};
const toggleInsertPromptAsRichText = async () => {
insertPromptAsRichText = !insertPromptAsRichText;
saveSettings({ insertPromptAsRichText });
@ -335,6 +341,7 @@
chatFadeStreamingText = $settings?.chatFadeStreamingText ?? true;
richTextInput = $settings?.richTextInput ?? true;
showFormattingToolbar = $settings?.showFormattingToolbar ?? false;
insertPromptAsRichText = $settings?.insertPromptAsRichText ?? false;
promptAutocomplete = $settings?.promptAutocomplete ?? false;
@ -863,6 +870,29 @@
</div>
{#if richTextInput}
<div>
<div class=" py-0.5 flex w-full justify-between">
<div id="rich-input-label" class=" self-center text-xs">
{$i18n.t('Show Formatting Toolbar')}
</div>
<button
aria-labelledby="rich-input-label"
class="p-1 px-3 text-xs flex rounded-sm transition"
on:click={() => {
toggleShowFormattingToolbar();
}}
type="button"
>
{#if showFormattingToolbar === true}
<span class="ml-2 self-center">{$i18n.t('On')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Off')}</span>
{/if}
</button>
</div>
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
<div id="rich-input-label" class=" self-center text-xs">

View file

@ -222,7 +222,7 @@
export let editable = true;
export let collaboration = false;
export let showFormattingButtons = true;
export let showFormattingToolbar = true;
export let preserveBreaks = false;
export let generateAutoCompletion: Function = async () => null;
@ -1003,7 +1003,7 @@
]
: []),
...(showFormattingButtons
...(showFormattingToolbar
? [
BubbleMenu.configure({
element: bubbleMenuElement,
@ -1316,7 +1316,7 @@
};
</script>
{#if showFormattingButtons}
{#if showFormattingToolbar}
<div bind:this={bubbleMenuElement} id="bubble-menu" class="p-0">
<FormattingButtons {editor} />
</div>

View file

@ -390,7 +390,7 @@ Based on the user's instruction, update and enhance the existing notes or select
bind:chatInputElement
acceptFiles={false}
inputLoading={loading}
showFormattingButtons={false}
showFormattingToolbar={false}
onSubmit={submitHandler}
{onStop}
>