Ask Chatty!
Note: Chatty is still being improved. Expect new features to be added. However, it is good enough for daily usage.
Chatty requires Vim with Python3 support. Chatty currently only supports OPENAI (in the future, it will work with more AI providers).
You can install Chatty with Vim plugin managers. For example, if you use vim-plug:
Plug 'iggredible/chatty.vim'
Chatty requires an OPENAI API key. You can either use an environment variable or define it in Vimrc.
# Terminal
export OPENAI_API_KEY=YOUR_OPENAI_KEY
" vimrc
let g:chatty_openai_api_key = 'YOUR_OPENAI_KEY'
If you want to use g:chatty_openai_api_key
but do not want to live on the edge (exposing your API keys in your vimrc), check out vim-dotenv to store them inside .env
.
Chatty relies on data inside the chatty/
directory. There are 3 important parts: configs
, instructions
, and histories
, each stored inside a directory with the same name.
After you installed chatty, Vim should automatically generate the chatty directories in ~/.config/
(or if you defined them in let g:chatty_dir_path = '~/.config/foo/'
). If for whatever reason they are not generated (permission issue?), you can add them yourself. Copy the chatty/
directory and everything inside from chatty.vim/chatty/ Github page.
Chatty by default stores your data inside ~/.config/chatty
directory. If there isn't one created, the first time you run Vim, it will create it for you. You can override it with g:chatty_dir_path
variable. If you want to store inside ~/.config/foo
, in your vimrc, do:
let g:chatty_dir_path = '~/.config/foo/'
The configs/
directory stores the parameters to send to your provider. To use it, create a JSON file having the provider file name, in snake case, inside configs/
.
For example, inside configs/open_ai.json
:
{
"model": "gpt-3.5-turbo",
"max_tokens": 2000,
"temperature": 0.7,
"top_p": 1.0,
"frequency_penalty": 0.0,
"presence_penalty": 0.0
}
The instructions/
directory is for your conversation guides. Think of it as Persona in ChatGPT. I did not name this directory persona because it is not a universal concept (for example, Claude does not have a persona concept). I think instruction conveys a better and more universal meaning.
The convention is to store your instructions, in JSON files, for each chat provider in their directory. Chatty currently only works with open_ai
, so put all your instructions inside instructions/open_ai/
. Note that the chat provider directory name must match the config file name above. The actual JSON instruction file can be given any name.
For example, if I want to have ChatGPT to respond in a style of Shakespeare, I can create instructions/open_ai/shakey.json
. Inside it:
{
"role": "system",
"content": "Reply in the style of William Shakespeare."
}
The histories/
directory contains your chat histories. Each chat history is a chat session. It is stored inside a directory named after a chat provider, similar to instructions. Unlike configs and instructions files where you manually need to create them, histories are automatically created and updated.
For example, a chat history histories/open_ai/331965ed-73d5-405e-a063-d8fbadefd7f9.json
will look like this:
{
"id": "331965ed-73d5-405e-a063-d8fbadefd7f9",
"name": "331965ed-73d5-405e-a063-d8fbadefd7f9",
"history": [
{
"role": "system",
"content": "You are a helpful AI assistant. Any programming-related questions to create, update, or analyze code, respond with code only. Omit explanations. If the question is not related to programming, answer concisely."
},
{
"role": "user",
"content": "What is the capital of Brazil?\n"
},
{
"role": "assistant",
"content": "BrasĂlia."
},
{
"role": "user",
"content": "What is the biggest city in that country?\n"
},
{
"role": "assistant",
"content": "SĂŁo Paulo."
},
{
"role": "user",
"content": "What is the estimated population of that city?\n"
},
{
"role": "assistant",
"content": "12.33 million."
},
{
"role": "user",
"content": "What is the average GDP?\n"
},
{
"role": "assistant",
"content": "$1.12 trillion."
}
],
"instruction": "default_assistant"
}
Notes:
id
is a UUID to ensure that each history is unique.name
is to make it easier to identify and select a chat history; its value by default is the same asid
, but you can rename it with:ChattyRenameHistory
command.instruction
is the context name that you used when starting a new history.history
is a list of prompt-response.
Histories are useful for maintaining conversation contexts. If you had previously asked, "What is 3 + 2?", then you can ask "What is double of that number?". Maintaining history allows your chat provider to know what 'that' number is.
After you ask your first question to Chatty after you open Vim, Chatty will start a new history. Keep in mind that the longer your chat history grows, the more tokens it uses. Chatty sends the entire history each time you make a request. They accumulate fast, so be careful.
Because histories are just text files, they are cheap and lightweight. Don't be afraid to accumulate as many histories as needed and delete ones you don't use (just make sure you don't delete the current history).
If your conversation starts to rabbit trail, don't be afraid to start a new chat history (for how to start a new chat history, keep reading).
Chatty comes with an ask operator ga
.
Suppose you have this text:
What is 1 + 5?
What is twice that?
What is half that?
With your cursor on the first line (on the "W" in "What is 1 + 5?"), if I want to ask Chatty, I can use the line-wise Ask operator: gaa
. It will send to the AI provider the question, "What is 1 + 5?". The response will be printed on the line below the current cursor. In this case, your AI provider (should) return 6 below "What is 1 + 5?" line.
Because ga
is just an operation, motions and visuals work. Some examples:
ga$
to send the texts from the current cursor position to the end of the linegaj
to send the text from the current cursor's row and the row below itgaf?
to send the text from the current cursor's location to the first occurrence of?
("find nearest '?'")
Vim operators work with visual mode. You can use the ga
operator with visual mode. On the text that you want to ask, highlight them with v
/ Ctrl-v
/ V
, then press ga
.
Chatty doesn't have a "Chat" window type. If you want to have a conversation with Chatty, just open a new file (:new
or :vnew
) and start typing your questions.
If you want to use your open operator instead of ga
, say you want to map it to gh
operator instead:
let g:chatty_enable_operators = 0
call helper#OperatorMapper('gh', 'chatty#Ask')
The helper#OperatorMapper
is a helper function. The first argument is the operator key (gh
). The second argument is the function to execute. Have it mapped to chatty#Ask
.
Sometimes you don't want to ask questions. Sometimes you want to transform a given text. No problem, you can ask-and-transform it with the gA
operator.
Below is a lits of a few things you can do with the process operator.
If you have the following text:
she sells seashells on the seashore
To titlecase it, with your cursor at the start of the line, run gAA
to perform a line-wise ChattyAsk!
operator. Immediately after, a prompt will come up on the cmdline (bottom of your Vim window).
Prompt:
Tell Chatty what you want to do with the target text.
Prompt: Titlecase the text
It will transform the text you selected (unlike the ask operator where it displays the response below, this operator replaces the selected text).
She Sells Seashells on the Seashore
Another example. Suppose that you have this JSON:
{ "meal": "breakfast", "dishes": ["eggs", "bacon", "toast"], "beverage": "coffee" }
To make it pretty, with your cursor at the start of the row, press gAA
or gA$
, then give it the prompt:
Prompt: Prettify JSON and replace all the meat products with vegetables
Result:
{
"meal": "breakfast",
"dishes": ["eggs", "vegetables", "toast"],
"beverage": "coffee"
}
Pretty cool!
The operator can be used to generate codes too.
For example, if you want to create a Fizzbuzz code:
Generate a fizzbuzz code in Ruby. Use recursion
Type gAA
. You don't have to type anything for Prompt. Leave it blank. It will replace your original instruction "Generate a Fizzbuzz..." with the actual code!
def fizzbuzz_recursive(n)
return if n == 0
fizzbuzz_recursive(n - 1)
if n % 3 == 0 && n % 5 == 0
puts "FizzBuzz"
elsif n % 3 == 0
puts "Fizz"
elsif n % 5 == 0
puts "Buzz"
else
puts n
end
end
fizzbuzz_recursive(100)
Chatty comes with 2 commands to complement the ask and ask-and-transform operators: :ChattyAsk
and :ChattyAsk!
.
Like any commands, you can pass them a range argument. :ChattyAsk
will pass all the text in the given range to chat provider. :ChattyAsk!
will consume and transform all the text in the given range to chat provider.
For example, if I have the following text:
Peter Piper picked a peck of pickled peppers.
A peck of pickled peppers Peter Piper picked.
If Peter Piper picked a peck of pickled peppers,
Where's the peck of pickled peppers Peter Piper picked?
If my cursor is on the first line and if I run :.,+3ChattyProcess
, it will take the texts from the current cursor to 3 lines below me then it'll ask me for a prompt. If I tell it to "uppercase the given text", it will replace it with the uppercased text.
When you run :ChattyProcess
, it will ask for confirmation if you want to proceed or not. If you run it with a bang (:ChattyProcess!
), it won't ask for confirmation.
More examples:
:%ChattyAsk
: pass the text from the entire buffer to Chatty.5,10ChattyAsk!
: consume the texts on lines 5 to 10 and transform them according to prompt.:5,ChattyAsk
: pass the text from lines 5 to the current line where the cursor is as a prompt.
You can also create your own custom command. If you want to create a custom command :Chat
and :Chat!
to do the same thing as :ChattyAsk
and :ChattyAsk!
, do this in Vimrc:
command! -range -bar -bang Chat call chatty#AskCommand(<line1>, <line2>, <bang>0)
You can use ChattyAsk
and ChattyAsk!
with the global (:g
) command to ask consecutive questions.
Given a list of questions, but you don't want to answer them all. You only want to ask some lines (the TODO
lines):
TODO: What is 1 + 5?
TODO: What is twice that?
TODO: What is half that?
What is the capital of Japan?
If you run :g/TODO/ChattyAsk
, Vim will get only the TODO
lines.
TODO: What is 1 + 5?
6
TODO: What is twice that?
12
TODO: What is half that?
3
What is the capital of Japan?
Note: Due to the nature of the global command, Vim submits a prompt for each matching row. In the above example, the history looks like this:
[
{
"role": "system",
"content": "You are a helpful AI assistant. Any programming-related questions to create, update, or analyze code, respond with code only. Omit explanations. If the question is not related to programming, answer concisely."
},
{
"role": "user",
"content": "TODO: What is 1 + 5?"
},
{
"role": "assistant",
"content": "6"
},
{
"role": "user",
"content": "TODO: What is twice that?"
},
{
"role": "assistant",
"content": "12"
},
{
"role": "user",
"content": "TODO: What is half that?"
},
{
"role": "assistant",
"content": "3"
}
]
Keep that in mind so that you don't run the :g
command with 1000+ matches.
Chatty only supports openAI right now. Chatty stores the provider information with g:chatty_provider
(:echo g:chatty_provider
). In the future, you will be able to change providers. Chatty provider is spelled the same way as your config (ex: chatty/configs/open_ai.json
).
Think of histories as sessions. You can switch history with :ChattyHistories
(default mapping <Leader>ah
). Chatty will show a dropdown of all histories in that provider, each history having the format of HISTORY_NAME
.
When you switch history, Chatty will use that history from subsequent chat. If in that history you've asked "What is the capital of Brazil?", you can pick up your chat and ask, "What is the biggest city of that city?". It knows that you were talking about Brazil, so it knows what the biggest city is. It picks up where you left off. Chatty histories are basically your chat sessions.
Your history name by default is its ID, which is a UUID. So you will see d71c9e35-668b-4761-af5c-c86b21d6002b__d71c9e35-668b-4761-af5c-c86b21d6002b
, which can be hard to tell what history is this about (without looking at the histories/
directory. You probably want to have an easier-to-remember name. I mean, what the heck is "d71c9e35..."? Is that the history when I asked about Ruby Procs or when I asked about countries of the world?
This is why you can rename history with :ChattyRenameHistory
(or <Leader>ar
). It will prompt you to enter a new name. Think of it like a nickname. Now you can name one history "ruby_proc"
and another as "countries"
.
Next time you switch history, you will see:
ruby_proc
countries
That list is now easy on the eyes!
Think of history as chat session. Start a new chat session often. It keeps my token usages low. It also keeps the chat provider to focus on a topic. If I was asking about Ruby Procs, then ActiveRecord queries, then Netflix architecture, then countries of the world all in one history, your chat provider may start giving unfocused answer. For that reason, I prefer to have a session for Ruby Procs, another for ActiveRecord queries, another for Netflix architecture (system design), and another for countries of the world. If I need to go back-and-forth between Ruby Procs and ActiveRecord queries, I can just toggle histories.:w
For that reason, create a new history often. By default you can do it with <Leader>an
or :ChattyNewHistory
.
Note: each time you start Vim, Chatty starts a new history.
Remember that a history is just a JSON file. Chatty history operations are either creating or modifying a JSON file. You can always modify the JSON file (make sure you don't alter its id
, name
, and overall structure). But feel free to revise the history.
An instruction is an initial system prompt. It determine the overall behavior of the chat. If you want Chatty to act like a Senior Principal Ruby on Rails programmer, and/or make it to respond verbosely, or concisely, or like a Shakespeare, or like a pirate, you can put it here. Anything you want your AI provider to behave like.
For example, in chatty/instructions/open_ai/default_assistant.json
:
{
"role": "system",
"content": "You are a helpful AI assistant. Any programming-related questions to create, update, or analyze code, respond with code only. Omit explanations. If the question is not related to programming, answer concisely."
}
Chatty's default instruction is default_assistant
. Meaning it will look inside chatty/instructions/open_ai/default_assistant.json
.
{
"role": "system",
"content": "You are a helpful AI assistant. Any programming-related questions to create, update, or analyze code, respond with code only. Omit explanations. If the question is not related to programming, answer concisely."
}
To override the default instruction, you can just change the content
of that file into whatever you want.
Alternatively, you can create a new file inside chatty/instructions/open/ai/
, name it whatever you want (example: chatty/instructions/open_ai/ruby_developer.json
), and pass it any instruction content
you want. Then in your vimrc, add this:
let g:chatty_instruction = 'ruby_developer'
Now when you start Vim, chatty will use ruby_developer
as default instruction
Once you create multiple instructions, you can switch between any instructions. The default is <Leader>ai
or :ChattyInstructions
.
Note: when you switch an instruction, Chatty will start a new history.
You can quickly see all histories, instructions, and configs quickfix list with the :ChattyQF OPTS
command, where OPTS
represent either history / instruction / config and their longhand:
:ChattyQF history
:ChattyQF instruction
:ChattyQF config
It will open a quickfix that lists all histories / instructions / configs. When you choose one of them, it will take you to that file so you can read / configure it.
Any git diff
operation should work:
:ChattyGetGit diff
:ChattyGetGit diff -- some/file.rb
:ChattyGetGit diff main..feature_branch
There are other Vim AI plugins out there:
Ideas, suggestions, and bug fixes are welcome. Feel free to submit a PR! Your help is highly coveted and appreciated.
MIT (c) Igor Irianto