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 scrollToBottom: Function = () => {};
export let acceptFiles = true; export let acceptFiles = true;
export let showFormattingButtons = true; export let showFormattingToolbar = true;
let showInputVariablesModal = false; let showInputVariablesModal = false;
let inputVariables: Record<string, any> = {}; let inputVariables: Record<string, any> = {};
@ -700,7 +700,7 @@
bind:this={chatInputElement} bind:this={chatInputElement}
json={true} json={true}
messageInput={true} messageInput={true}
{showFormattingButtons} {showFormattingToolbar}
shiftEnter={!($settings?.ctrlEnterToSend ?? false) && shiftEnter={!($settings?.ctrlEnterToSend ?? false) &&
(!$mobile || (!$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" 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" id="chat-input-container"
> >
{#key $settings?.showFormattingToolbar ?? false}
<RichTextInput <RichTextInput
bind:this={chatInputElement} bind:this={chatInputElement}
id="chat-input" id="chat-input"
@ -1095,7 +1096,8 @@
}} }}
json={true} json={true}
messageInput={true} messageInput={true}
showFormattingButtons={false} showFormattingToolbar={$settings?.showFormattingToolbar ?? false}
floatingMenuPlacement={'top-start'}
insertPromptAsRichText={$settings?.insertPromptAsRichText ?? false} insertPromptAsRichText={$settings?.insertPromptAsRichText ?? false}
shiftEnter={!($settings?.ctrlEnterToSend ?? false) && shiftEnter={!($settings?.ctrlEnterToSend ?? false) &&
(!$mobile || (!$mobile ||
@ -1183,7 +1185,9 @@
commandsElement.selectUp(); commandsElement.selectUp();
const commandOptionButton = [ const commandOptionButton = [
...document.getElementsByClassName('selected-command-option-button') ...document.getElementsByClassName(
'selected-command-option-button'
)
]?.at(-1); ]?.at(-1);
commandOptionButton.scrollIntoView({ block: 'center' }); commandOptionButton.scrollIntoView({ block: 'center' });
} }
@ -1193,7 +1197,9 @@
commandsElement.selectDown(); commandsElement.selectDown();
const commandOptionButton = [ const commandOptionButton = [
...document.getElementsByClassName('selected-command-option-button') ...document.getElementsByClassName(
'selected-command-option-button'
)
]?.at(-1); ]?.at(-1);
commandOptionButton.scrollIntoView({ block: 'center' }); commandOptionButton.scrollIntoView({ block: 'center' });
} }
@ -1202,7 +1208,9 @@
e.preventDefault(); e.preventDefault();
const commandOptionButton = [ const commandOptionButton = [
...document.getElementsByClassName('selected-command-option-button') ...document.getElementsByClassName(
'selected-command-option-button'
)
]?.at(-1); ]?.at(-1);
commandOptionButton?.click(); commandOptionButton?.click();
@ -1212,7 +1220,9 @@
e.preventDefault(); e.preventDefault();
const commandOptionButton = [ const commandOptionButton = [
...document.getElementsByClassName('selected-command-option-button') ...document.getElementsByClassName(
'selected-command-option-button'
)
]?.at(-1); ]?.at(-1);
if (commandOptionButton) { if (commandOptionButton) {
@ -1300,9 +1310,13 @@
if (text.length > PASTED_TEXT_CHARACTER_LIMIT) { if (text.length > PASTED_TEXT_CHARACTER_LIMIT) {
e.preventDefault(); e.preventDefault();
const blob = new Blob([text], { type: 'text/plain' }); 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' type: 'text/plain'
}); }
);
await uploadFileHandler(file, true); await uploadFileHandler(file, true);
} }
@ -1312,6 +1326,7 @@
} }
}} }}
/> />
{/key}
</div> </div>
{:else} {:else}
<textarea <textarea

View file

@ -38,6 +38,7 @@
let detectArtifacts = true; let detectArtifacts = true;
let richTextInput = true; let richTextInput = true;
let showFormattingToolbar = false;
let insertPromptAsRichText = false; let insertPromptAsRichText = false;
let promptAutocomplete = false; let promptAutocomplete = false;
@ -228,6 +229,11 @@
saveSettings({ richTextInput }); saveSettings({ richTextInput });
}; };
const toggleShowFormattingToolbar = async () => {
showFormattingToolbar = !showFormattingToolbar;
saveSettings({ showFormattingToolbar });
};
const toggleInsertPromptAsRichText = async () => { const toggleInsertPromptAsRichText = async () => {
insertPromptAsRichText = !insertPromptAsRichText; insertPromptAsRichText = !insertPromptAsRichText;
saveSettings({ insertPromptAsRichText }); saveSettings({ insertPromptAsRichText });
@ -335,6 +341,7 @@
chatFadeStreamingText = $settings?.chatFadeStreamingText ?? true; chatFadeStreamingText = $settings?.chatFadeStreamingText ?? true;
richTextInput = $settings?.richTextInput ?? true; richTextInput = $settings?.richTextInput ?? true;
showFormattingToolbar = $settings?.showFormattingToolbar ?? false;
insertPromptAsRichText = $settings?.insertPromptAsRichText ?? false; insertPromptAsRichText = $settings?.insertPromptAsRichText ?? false;
promptAutocomplete = $settings?.promptAutocomplete ?? false; promptAutocomplete = $settings?.promptAutocomplete ?? false;
@ -863,6 +870,29 @@
</div> </div>
{#if richTextInput} {#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>
<div class=" py-0.5 flex w-full justify-between"> <div class=" py-0.5 flex w-full justify-between">
<div id="rich-input-label" class=" self-center text-xs"> <div id="rich-input-label" class=" self-center text-xs">

View file

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

View file

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