qnixsynapse / rich-chat

Python console application designed to provide an engaging and visually appealing LLM chat experience on Unix-like consoles or Terminals.
MIT License
18 stars 2 forks source link

Resizing window breaks the chat UI #7

Open teleprint-me opened 9 months ago

teleprint-me commented 9 months ago

Not sure what level priority this should have, but this is pretty high for me. Resizing the window breaks the formatting and makes it impossible to read the output in the terminal.

Screenshot from 2024-02-25 11-11-07

We should prioritize fixing this sooner than later if we don't know how this will affect the overall structure of the code. This way, we still have some flexibility before going deeper into specific UI/UX details.

qnixsynapse commented 9 months ago

This is expected because we have no means to repaint the entire UI with the increase or decrease of the rows and columns of the terminal(i.e change in dimensions of the terminal app). But yes. I will give it a thought and I think we will end up using Textual eventually.

qnixsynapse commented 9 months ago

Update: I tried using Textual and it is a bit of a learning curve for me. Also it has CSS elements for styling. However I can't get it to work the "LLM token streaming generation" for some reason. For now I think we should remove the frame. In this way it won't break if the window is resized.

teleprint-me commented 9 months ago

Wow! It's like you read my mind! haha.

I tried playing around with textual a few days ago and it's not something we'll pick up and integrate over night. I think we should focus on core features and keep the UI light and we can slowly enhance the UI/UX over time.

I found textual to be completely unintuitive with respect to input and output. I know it's possible, I just don't have any experience with it.

What I did learn is that we'll need to implement our own custom widgets and labels.

So, for output we would use Label and could use Label.update, but I could not for the life of me figure out how to gracefully handle input.

There are the Input and TextArea widgets, but they don't necessarily behave as expected and really depend on events as a result.

The CSS seems to be a custom, watered down, implementation as well. So instead of referencing an HTML element, we would reference a class identifier instead.

I also have been running into limitations with rich as well. There's no option to remove a border at all. A Box must be defined and passed to a Panel. After multiple attempts at it on my own and spending a couple of days on it, I finally threw the towel in and broke down and just asked ChatGPT. It had no idea how to do it either. After a bit of back and forth, this what ChatGPT and I came up with.

# Define a custom Box style with no visible characters
no_border_box = box.Box(
    "    \n"  # top
    "    \n"  # head
    "    \n"  # head_row
    "    \n"  # mid
    "    \n"  # row
    "    \n"  # foot_row
    "    \n"  # foot
    "    \n",  # bottom
    ascii=True,  # Setting ascii to True to emphasize the lack of box characters
)

Then we would just instantiate any panels with our custom border.

self.console.print(
    Panel(
        Markdown(message["content"]),
        title=title.upper(),
        title_align="left",
        box=no_border_box,
    )
)

I really don't like this approach because it feels really hacky, but the following is a direct copy and paste from a test I just finished running.


11:39:26 | /mnt/valerie/rich-chat (.venv) git:(textualize | Δ) λ python -m rich_chat.rich_chat_cli --server 'http://localhost:8080' -m 'My name is StableLM and I am your helpful assistant!' --temperature 0.7 --topp 0.9 --topk 50 --minp 0.1 --n-predict -1 -n "stablelm-1b-test" SYSTEM
My name is StableLM and I am your helpful assistant!

HUMAN
Hello! My name is Austin. What is your name?

ASSISTANT
Hi there, my name is StableLM and it's a pleasure to meet you, Austin! How can I assist you today?

HUMAN
Can you show me an example of how to get a list of prime numbers in Python?

ASSISTANT
Sure! Here's an alternative approach using Python:

   def is_prime(n):                                                                                                                                           
       if n <= 1:                                                                                                                                             
           return False                                                                                                                                       
       for i in range(2, int(n**0.5) + 1):                                                                                                                    
           if n % i == 0:                                                                                                                                     
               return False                                                                                                                                   
       return True                                                                                                                                            

   primes = [n for n in range(2, 100) if is_prime(n)]                                                                                                         
   print(primes)                                                                                                                                              

In this solution, the is_prime function checks whether a given number is prime by iterating from 2 to the square root of the number and checking if
there's any divisor. If it finds any divisor, it returns False; otherwise, it returns True.

Then, we iterate through numbers starting from 2 up to 100 (since you mentioned the smallest prime number as 2) using a list comprehension. For each
number, we check if it is prime by calling the is_prime function and append it to the primes list if it's a prime number.

Finally, when the script runs, it prints out this list containing all the prime numbers up to 100.


Not completely ideal because we can't remove the padding, otherwise it just results in a ValueError due to the missing spaces rich expects.

I'm honestly considering just building a custom interface that places LLM's as first class citizens. All of the existing frameworks were not built with any of this tech in mind so it's all very convoluted and difficult to implement and manage. I'd prefer to not repeat the same mistakes I made with my pygptprompt project.

Something else to keep in mind is that all of the above does nothing to resolve the resizing issue. We would need to clear the screen and render everything again, but I don't like that idea either. There's most likely a better way.

qnixsynapse commented 9 months ago

I have got an idea. Will come back to you after trying.

qnixsynapse commented 9 months ago

Update:

Oh well!

upload.webm

Update 2: This is without using Textual. But need to figure out how to do the streaming and input. upload.webm