"Life is all about sharing. If we are good at something, pass it on." - Mary Berry

A simple terminal UI for ChatGPT

2023-04-01

Categories: Programming

When I started learning Linux, I immediately fell in love with the terminal. I still remember the feeling of typing commands into the terminal, pressing Enter and seeing if I got the expected result. As a system administrator and DevOps engineer, I always wanted to do everything on the terminal.

The reasons are simple: it allowed me know what was happening behind the scenes, and it was faster than switching to other tools, and then back to the terminal to continue working.

These reasons still stand today. I’m a fan of lazygit and always want to write something that uses gocui. After investigating some other TUI libraries, I decided to give tview a try for my project, which can be found at: https://github.com/quantonganh/chatgpt

By being attentive during your interaction with ChatGPT, you will notice that a conversation title will be generated on the left-hand side following the first answer. However, the API for this feature is not available. Hence, my strategy involves asking ChatGPT to produce the title for us:

1			if list.GetItemCount() == 0 || isNewChat {
2				go func() {
3					resp, err := createChatCompletion([]Message{
4						{
5							Role:    roleUser,
6							Content: prefixSuggestTitle + content,
7						},
8					}, false)

And same as the version on the web, I also want to allow to edit the title. But I was having trouble showing an InputField at the position of a list item when the e key is pressed.

Looking at the Modal feature in tview, an idea came to my mind.

My main layout is something like this:

1	mainFlex := tview.NewFlex().SetDirection(tview.FlexRow).
2		AddItem(tview.NewFlex().SetDirection(tview.FlexColumn).
3			AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
4				AddItem(button, 3, 1, false).
5				AddItem(list, 0, 1, false), 0, 1, false).
6			AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
7				AddItem(textView, 0, 1, false).
8				AddItem(textArea, 5, 1, false), 0, 3, false), 0, 1, false).
9		AddItem(help, 1, 1, false)

I plan to create a modal on top of this layout, with all items nil except for the one at the list item’s position:

 1	modal := func(p tview.Primitive, currentIndex int) tview.Primitive {
 2		return tview.NewFlex().SetDirection(tview.FlexRow).
 3			AddItem(tview.NewFlex().SetDirection(tview.FlexColumn).
 4				AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
 5					AddItem(nil, 4+(currentIndex*2), 1, false).
 6					AddItem(p, 1, 1, true).
 7					AddItem(nil, 0, 1, false), 0, 1, true).
 8				AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
 9					AddItem(nil, 0, 1, false).
10					AddItem(nil, 5, 1, false), 0, 3, false), 0, 1, true).
11			AddItem(nil, 1, 1, false)
12	}

The interesting part is:

1					AddItem(nil, 4+(currentIndex*2), 1, false).
2					AddItem(p, 1, 1, true).

The “New chat” button has height of 3, so if I want to show an InputField at the position of the first item in the list, it must be at 4. Since we have a blank line between each item, the formula to calculate the fixed size is:

14+(currentIndex*2)

Tags: golang tview chatgpt

Edit on GitHub

Related Posts: