feat: updated ChatInput to a WYSIWYG editor to support Markdown and Codeblocks
Description
Closes #1545 Closes #1592
Updated the ChatInput component from using a textarea element into using a WYSIWYG editor by tiptap.
I first of all compared and contrasted all candidate tools for this task, and found that tiptap is the most svelte compatible out of them, additionally, tiptap is well supported and maintained.
I made sure to preserve the original functionality, everything behaves the same, including placeholders, and text submit. Any data submitted by the user is regular text. I reviewed how Claude works, and made sure the features implemented are very similar. Additionally, I also made sure to only include bold, italic, and codeblocks. For future references, tiptap also supports a bunch of other functionalities, such as images, tables, and others...
I also resulted to using lowlight for syntax highlighting which is based on highlight.js, particularly because tiptap has an extension that directly supports lowlight in its code blocks! So now the code blocks look clean and flashy!
I also fixed an issue in the MarkdownRenderer where markdown was being interpreted as a string rather than rendered html:
This was being caused by the escapeHTML function in the MarkdownRenderer which wasn't really needed since sanitization was being applied later on!
Examples
I also made sure it is resizable and works on various screen sizes (if you really want to open up a codeblock and code in it on phone)
User messages:
Usage
Upgraded features include:
- Surrounding a word with **, or pressing
ctrl + bbefore writing bolds a word - Surrounding a word with *, or pressing
ctrl + ibefore writing italicizes a word - Doing three backticks "`" and pressing a space afterwards opens up a code block for you to write in!
- User messages displayed using the
MarkdownRendererand editable
Testing
To test, I recommend trying all of the above mentioned features out. Try to italicize and bold at the same time, open code blocks, etc..
This is my first time working with Svelte, so let me know if any suggestions arise!
I really like this PR, thanks a lot for the contrib, it would make a great addition!
A couple of comments:
- When we apply markdown like italic, bold or even code blocks, it looks like they get removed in the sent message and only text is sent. (
*Hi*becomesHiin the prompt) I think a lot of people expect markdown elements to be passed to the LLM, so would it be possible to pass them directly?- Love the code blocks, but i was a bit confused by the behaviour for starting them. If i start a codeblock with
and then exit it by going down with the arrow, if I start another codeblock withand then shift+enter, it doesnt start a codeblock.- Would be nice to support inline code like
thistoo if possible- Could we extract the editor component into a separate component? I think there's a few other places in the app where we would need this functionality and it would be nice to reuse the same editor everywhere.
- Would it be easy to reuse the same style we use for highlight.js already for consistency? https://github.com/Mounayer/chat-ui/blob/e06910495bf7773a9f25b4c4be416c9c8effe12a/src/styles/highlight-js.css#L1-L2
Overall this is super nice, if you think we could get those changes in that would be great, let me know if you need help on those. 🚀
Thank you for your review @nsarrazin , let me address your requests:
- For passing markdown elements to the LLM, I personally thought you'd only like to make the input field similar to
Claude, not the sent message display as well! Would you like me to figure out a way to display the sent messages as markdown as well? The editor works in such a way that it can return the data entered either as regulartextor as the createdelements-- for example, if you writeHello, what really happens is that aparagraphelement gets created! - Thank you for catching this edge case! This should not be a big deal, I'll work on it at once!
- I'm pretty sure I can figure out a way to support inline code!
- We can definitely extract the editor as its own component!
- I'm going to attempt to use the actual
atom-one-darktheme. But if that was not possible, would it be okay if I customized the colors to look exactly the same as the theme?
I'll work on these changes at once, thanks again for your review, and I'd appreciate if you could provide more information regarding the first point!
Hey @nsarrazin !
I have updated the following:
- Fixed
Shift + Enternot working, additionally, I added support for languages, i.e., you don't have to open an empty codeblock with no chosen language anymore, you can choose one such as: ```cpp, creating an empty code block with no selected language works too, this would makelowlightautomatically detect the syntax and highlight it appropriately. - I've added support to
inline code blocksand it works great! - I have extracted the editor component into its own file so that it may be reused by other components in the future.
- I have set the theme for the codeblocks to be the already setup
atom-one-dark highlight.js theme
Here are some spoilers!
Matter of fact, it now looks identical to claude's:
Now the one thing I haven't touched yet is your last comment, which is regarding sending markdown to the llm, I was under the assumption this issue is only for adding markdown support in the input element, not also displaying messages and/or history as markdown! Would you like me to try to make that work as well? or should that be another issue?
Either way, I'm really happy to help, this was so much fun! And please, should you have any suggestions and/or ideas to improve, let me know!
Hey @Mounayer thanks for adding the changes looks great! Couple more things:
-
What I meant was that if I type
hello **bold**then the prompt passed to the model should behello **bold**but right now it's onlyhello boldthat gets passed. Do you think that would be doable ? Actually rendering it as markdown is optional but at least the raw text should be passed including the tags. -
When I type a message in a conversation that already exists (so 2nd user message in a conversation) the input field does not get cleared after I send the message
Love the new style thanks :grin:
Hey @Mounayer thanks for adding the changes looks great! Couple more things:
- What I meant was that if I type
hello **bold**then the prompt passed to the model should behello **bold**but right now it's onlyhello boldthat gets passed. Do you think that would be doable ? Actually rendering it as markdown is optional but at least the raw text should be passed including the tags.- When I type a message in a conversation that already exists (so 2nd user message in a conversation) the input field does not get cleared after I send the message
Love the new style thanks 😁
Glad to help!
- I understand now, you want the text passed to the LLM be markdown syntax but in text format! I'm not sure if tiptap supports this, if it doesn't I might have to
getHTMLand then simply replace some of the elements with the appropriate markdown syntax before passing it to the LLM as a text! - This is weird, I'll investigate at once!
Hi @nsarrazin !
This was much easier than expected, gotta love how many extensions there are for tiptap ! I found one called tiptap-markdown that does precisely what you are asking for! Implementing it was relatively straightforward!
I also read through the tiptap docs and found a built-in function that not only empties the entire document, but also emits an update event! So now it should be working as expected (hopefully!)
Here are some images of the behvavior:
And the result (what is being sent):
I'm also going to try and figure out if there's anything we missed while waiting your response!
Hi @nsarrazin !
This was much easier than expected, gotta love how many extensions there are for
tiptap! I found one called tiptap-markdown that does precisely what you are asking for! Implementing it was relatively straightforward!I also read through the tiptap docs and found a built-in function that not only empties the entire document, but also emits an update event! So now it should be working as expected (hopefully!)
Here are some images of the behvavior:
![]()
![]()
And the result (what is being sent):
I'm also going to try and figure out if there's anything we missed while waiting your response!
I've tested quite a lot and used more than one model, it works great, there is just one thing I've noticed. Newlines translate to \ since tiptap is returning everything in markdown, however, once the message display component starts using some sort of a markdown parser, this problem is automatically resolved! I'd also love to work on that issue as well!


