From fcfe3f220d6b86208db1135db0b4d64568234119 Mon Sep 17 00:00:00 2001 From: Martin Bielik Date: Tue, 28 Mar 2023 22:16:39 +0200 Subject: AIRedo command --- plugin/vim-ai.vim | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/plugin/vim-ai.vim b/plugin/vim-ai.vim index 5105abd..c9f48e1 100644 --- a/plugin/vim-ai.vim +++ b/plugin/vim-ai.vim @@ -37,6 +37,11 @@ let s:plugin_root = expand(':p:h:h') let s:complete_py = s:plugin_root . "/py/complete.py" let s:chat_py = s:plugin_root . "/py/chat.py" +" remembers last command parameters to be used in AIRedoRun +let s:last_is_selection = 0 +let s:last_instruction = "" +let s:last_command = "" + function! ScratchWindow() below new setlocal buftype=nofile @@ -55,8 +60,14 @@ function! MakePrompt(is_selection, lines, instruction) endfunction function! AIRun(is_selection, ...) range + let instruction = a:0 ? a:1 : "" let lines = getline(a:firstline, a:lastline) - let prompt = MakePrompt(a:is_selection, lines, a:0 ? a:1 : "") + let prompt = MakePrompt(a:is_selection, lines, instruction) + + let s:last_command = "complete" + let s:last_instruction = instruction + let s:last_is_selection = a:is_selection + let options_default = g:vim_ai_complete_default['options'] let options = g:vim_ai_complete['options'] let cursor_on_empty_line = trim(join(lines, "\n")) == "" @@ -72,7 +83,13 @@ function! AIRun(is_selection, ...) range endfunction function! AIEditRun(is_selection, ...) range - let prompt = MakePrompt(a:is_selection, getline(a:firstline, a:lastline), a:0 ? a:1 : "") + let instruction = a:0 ? a:1 : "" + let prompt = MakePrompt(a:is_selection, getline(a:firstline, a:lastline), instruction) + + let s:last_command = "edit" + let s:last_instruction = instruction + let s:last_is_selection = a:is_selection + let options_default = g:vim_ai_edit_default['options'] let options = g:vim_ai_edit['options'] set paste @@ -82,6 +99,7 @@ function! AIEditRun(is_selection, ...) range endfunction function! AIChatRun(is_selection, ...) range + let instruction = "" let lines = getline(a:firstline, a:lastline) set paste let is_outside_of_chat_window = search('^>>> user$', 'nw') == 0 @@ -89,17 +107,36 @@ function! AIChatRun(is_selection, ...) range call ScratchWindow() let prompt = "" if a:0 || a:is_selection - let prompt = MakePrompt(a:is_selection, lines, a:0 ? a:1 : "") + let instruction = a:0 ? a:1 : "" + let prompt = MakePrompt(a:is_selection, lines, instruction) endif - execute "normal i>>> user\n\n" . prompt + execute "normal! i>>> user\n\n" . prompt endif + let s:last_command = "chat" + let s:last_instruction = instruction + let s:last_is_selection = a:is_selection + let options_default = g:vim_ai_chat_default['options'] let options = g:vim_ai_chat['options'] execute "py3file " . s:chat_py set nopaste endfunction +function! AIRedoRun() + execute "normal! u" + if s:last_command == "complete" + '<,'>call AIRun(s:last_is_selection, s:last_instruction) + endif + if s:last_command == "edit" + '<,'>call AIEditRun(s:last_is_selection, s:last_instruction) + endif + if s:last_command == "chat" + call AIChatRun(s:last_is_selection, s:last_instruction) + endif +endfunction + command! -range -nargs=? AI ,call AIRun(, ) command! -range -nargs=? AIEdit ,call AIEditRun(, ) command! -range -nargs=? AIChat ,call AIChatRun(, ) +command! AIRedo call AIRedoRun() -- cgit v1.2.3 From 2f8c9804878f3bdbea63ac78cee38a778158aa06 Mon Sep 17 00:00:00 2001 From: Martin Bielik Date: Tue, 28 Mar 2023 22:54:33 +0200 Subject: fixed visual redo call --- plugin/vim-ai.vim | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/plugin/vim-ai.vim b/plugin/vim-ai.vim index c9f48e1..501dc2c 100644 --- a/plugin/vim-ai.vim +++ b/plugin/vim-ai.vim @@ -126,10 +126,18 @@ endfunction function! AIRedoRun() execute "normal! u" if s:last_command == "complete" - '<,'>call AIRun(s:last_is_selection, s:last_instruction) + if s:last_is_selection + '<,'>call AIRun(s:last_is_selection, s:last_instruction) + else + call AIRun(s:last_is_selection, s:last_instruction) + endif endif if s:last_command == "edit" - '<,'>call AIEditRun(s:last_is_selection, s:last_instruction) + if s:last_is_selection + '<,'>call AIEditRun(s:last_is_selection, s:last_instruction) + else + call AIEditRun(s:last_is_selection, s:last_instruction) + endif endif if s:last_command == "chat" call AIChatRun(s:last_is_selection, s:last_instruction) -- cgit v1.2.3 From 4ab7019b1c9e2379ad114d40aedd0d1f55477674 Mon Sep 17 00:00:00 2001 From: Martin Bielik Date: Tue, 28 Mar 2023 23:02:52 +0200 Subject: redo command docu --- README.md | 11 ++++++++++- doc/vim-ai.txt | 9 ++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dc997a3..a197b55 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,6 @@ Plug 'madox2/vim-ai', { 'do': './install.sh' } ### :AIChat - `:AIChat` - continue or start a new conversation. `(visual selection)? :AIChat {instruction}?` - start a new conversation given the selection, the instruction or both @@ -76,6 +75,13 @@ You are a Clean Code expert, I have the following code, please refactor it in a Supported chat roles are **`>>> system`**, **`>>> user`** and **`<<< assistant`** +### :AIRedo + +Use this immediately after `AI`/`AIEdit`/`AIChat` command in order to re-try or get an alternative completion. +Note that the randomness of responses heavily depends on the [`temperature`](https://platform.openai.com/docs/api-reference/completions/create#completions/create-temperature) parameter. + +`:AIRedo` - repeat last AI command + ## Configuration ### Key bindings @@ -94,6 +100,9 @@ nnoremap s :AIEdit fix grammar and spelling " trigger chat xnoremap c :AIChat nnoremap c :AIChat + +" redo last AI command +nnoremap r :AIRedo ``` ### Completion configuration diff --git a/doc/vim-ai.txt b/doc/vim-ai.txt index 24a1ebe..d0519fd 100644 --- a/doc/vim-ai.txt +++ b/doc/vim-ai.txt @@ -54,7 +54,6 @@ https://platform.openai.com/docs/api-reference/completions *:AIChat* - :AIChat continue or start a new conversation. (selection)? :AIChat {instruction}? start a new conversation given the selection, the instruction or both @@ -72,6 +71,11 @@ Options: > Check OpenAI docs for more infomration: https://platform.openai.com/docs/api-reference/chat + *:AIRedo* + +:AIRedo repeat last AI command in order to re-try + or get an alternative completion. + CONFIGURATION *vim-ai-config* To customize the default configuration, initialize the config variable with @@ -100,6 +104,9 @@ Examples how configure key bindins and customize commands: > xnoremap c :AIChat nnoremap c :AIChat + " redo last AI command + nnoremap r :AIRedo + " command with custom context (vim-ai functions: AIRun, AIEditRun, AIChatRun) command! -range -nargs=? AICode ,call AIRun(, "Programming syntax is " . &filetype . ", " . ) -- cgit v1.2.3 From b5c8ebc5647d54e192d266fc81cabcbacd93496e Mon Sep 17 00:00:00 2001 From: Martin Bielik Date: Tue, 28 Mar 2023 23:05:31 +0200 Subject: redo command docu --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a197b55..8900f2f 100644 --- a/README.md +++ b/README.md @@ -77,11 +77,11 @@ Supported chat roles are **`>>> system`**, **`>>> user`** and **`<<< assistant`* ### :AIRedo +`:AIRedo` - repeat last AI command + Use this immediately after `AI`/`AIEdit`/`AIChat` command in order to re-try or get an alternative completion. Note that the randomness of responses heavily depends on the [`temperature`](https://platform.openai.com/docs/api-reference/completions/create#completions/create-temperature) parameter. -`:AIRedo` - repeat last AI command - ## Configuration ### Key bindings -- cgit v1.2.3 From 541fb8637f725c49e9cf3bf736387d0578c3af45 Mon Sep 17 00:00:00 2001 From: Martin Bielik Date: Sun, 2 Apr 2023 17:29:00 +0200 Subject: updated tags --- doc/tags | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/tags b/doc/tags index 20ddc90..469a9be 100644 --- a/doc/tags +++ b/doc/tags @@ -1,7 +1,9 @@ :AI vim-ai.txt /*:AI* :AIChat vim-ai.txt /*:AIChat* :AIEdit vim-ai.txt /*:AIEdit* +:AIRedo vim-ai.txt /*:AIRedo* vim-ai vim-ai.txt /*vim-ai* vim-ai-about vim-ai.txt /*vim-ai-about* vim-ai-commands vim-ai.txt /*vim-ai-commands* +vim-ai-config vim-ai.txt /*vim-ai-config* vim-ai.txt vim-ai.txt /*vim-ai.txt* -- cgit v1.2.3 From 779067beb18ddce9143a5fa9abf8608d921feeb4 Mon Sep 17 00:00:00 2001 From: Martin Bielik Date: Sun, 2 Apr 2023 22:25:03 +0200 Subject: open chat command config --- autoload/vim_ai.vim | 6 ++++++ plugin/vim-ai.vim | 17 ++++++----------- 2 files changed, 12 insertions(+), 11 deletions(-) create mode 100644 autoload/vim_ai.vim diff --git a/autoload/vim_ai.vim b/autoload/vim_ai.vim new file mode 100644 index 0000000..4957c5b --- /dev/null +++ b/autoload/vim_ai.vim @@ -0,0 +1,6 @@ +function! vim_ai#MakeScratchWindow() + setlocal buftype=nofile + setlocal bufhidden=hide + setlocal noswapfile + setlocal ft=aichat +endfunction diff --git a/plugin/vim-ai.vim b/plugin/vim-ai.vim index 501dc2c..c32fb2f 100644 --- a/plugin/vim-ai.vim +++ b/plugin/vim-ai.vim @@ -21,6 +21,9 @@ let g:vim_ai_chat_default = { \ "temperature": 1, \ "request_timeout": 20, \ }, +\ "ui": { +\ "open_chat_command": "below new | call vim_ai#MakeScratchWindow()" +\ }, \} if !exists('g:vim_ai_complete') let g:vim_ai_complete = {"options":{}} @@ -29,10 +32,9 @@ if !exists('g:vim_ai_edit') let g:vim_ai_edit = {"options":{}} endif if !exists('g:vim_ai_chat') - let g:vim_ai_chat = {"options":{}} + let g:vim_ai_chat = {"options":{}, "ui": {}} endif - let s:plugin_root = expand(':p:h:h') let s:complete_py = s:plugin_root . "/py/complete.py" let s:chat_py = s:plugin_root . "/py/chat.py" @@ -42,14 +44,6 @@ let s:last_is_selection = 0 let s:last_instruction = "" let s:last_command = "" -function! ScratchWindow() - below new - setlocal buftype=nofile - setlocal bufhidden=hide - setlocal noswapfile - setlocal ft=aichat -endfunction - function! MakePrompt(is_selection, lines, instruction) let lines = trim(join(a:lines, "\n")) let instruction = trim(a:instruction) @@ -104,7 +98,8 @@ function! AIChatRun(is_selection, ...) range set paste let is_outside_of_chat_window = search('^>>> user$', 'nw') == 0 if is_outside_of_chat_window - call ScratchWindow() + let ui_options = extend(copy(g:vim_ai_chat_default['ui']), g:vim_ai_chat['ui']) + execute ui_options['open_chat_command'] let prompt = "" if a:0 || a:is_selection let instruction = a:0 ? a:1 : "" -- cgit v1.2.3 From 3b04fa1b764129395f54ab1b1297a4ce45e4105e Mon Sep 17 00:00:00 2001 From: Martin Bielik Date: Sun, 2 Apr 2023 23:05:12 +0200 Subject: extending config programatically --- plugin/vim-ai.vim | 37 +++++++++++++++++++++++-------------- py/utils.py | 4 +--- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/plugin/vim-ai.vim b/plugin/vim-ai.vim index c32fb2f..9232d47 100644 --- a/plugin/vim-ai.vim +++ b/plugin/vim-ai.vim @@ -25,15 +25,28 @@ let g:vim_ai_chat_default = { \ "open_chat_command": "below new | call vim_ai#MakeScratchWindow()" \ }, \} -if !exists('g:vim_ai_complete') - let g:vim_ai_complete = {"options":{}} -endif -if !exists('g:vim_ai_edit') - let g:vim_ai_edit = {"options":{}} -endif -if !exists('g:vim_ai_chat') - let g:vim_ai_chat = {"options":{}, "ui": {}} -endif + +function! s:ExtendDeep(defaults, override) abort + let l:result = a:defaults + for [l:key, l:value] in items(a:override) + if type(get(l:result, l:key)) is v:t_dict && type(l:value) is v:t_dict + call s:ExtendDeep(l:result[l:key], l:value) + else + let l:result[l:key] = l:value + endif + endfor + return l:result +endfun + +function! s:MakeConfig(config_name) abort + let l:defaults = copy(g:[a:config_name . "_default"]) + let l:override = exists("g:" . a:config_name) ? g:[a:config_name] : {} + let g:[a:config_name] = s:ExtendDeep(l:defaults, l:override) +endfun + +call s:MakeConfig("vim_ai_chat") +call s:MakeConfig("vim_ai_complete") +call s:MakeConfig("vim_ai_edit") let s:plugin_root = expand(':p:h:h') let s:complete_py = s:plugin_root . "/py/complete.py" @@ -62,7 +75,6 @@ function! AIRun(is_selection, ...) range let s:last_instruction = instruction let s:last_is_selection = a:is_selection - let options_default = g:vim_ai_complete_default['options'] let options = g:vim_ai_complete['options'] let cursor_on_empty_line = trim(join(lines, "\n")) == "" set paste @@ -84,7 +96,6 @@ function! AIEditRun(is_selection, ...) range let s:last_instruction = instruction let s:last_is_selection = a:is_selection - let options_default = g:vim_ai_edit_default['options'] let options = g:vim_ai_edit['options'] set paste execute "normal! " . a:firstline . "GV" . a:lastline . "Gc" @@ -98,8 +109,7 @@ function! AIChatRun(is_selection, ...) range set paste let is_outside_of_chat_window = search('^>>> user$', 'nw') == 0 if is_outside_of_chat_window - let ui_options = extend(copy(g:vim_ai_chat_default['ui']), g:vim_ai_chat['ui']) - execute ui_options['open_chat_command'] + execute g:vim_ai_chat['ui']['open_chat_command'] let prompt = "" if a:0 || a:is_selection let instruction = a:0 ? a:1 : "" @@ -112,7 +122,6 @@ function! AIChatRun(is_selection, ...) range let s:last_instruction = instruction let s:last_is_selection = a:is_selection - let options_default = g:vim_ai_chat_default['options'] let options = g:vim_ai_chat['options'] execute "py3file " . s:chat_py set nopaste diff --git a/py/utils.py b/py/utils.py index 8366eec..814a4f0 100644 --- a/py/utils.py +++ b/py/utils.py @@ -12,9 +12,7 @@ def load_api_key(): return api_key.strip() def make_options(): - options_default = vim.eval("options_default") - options_user = vim.eval("options") - options = {**options_default, **options_user} + options = {**vim.eval("options")} options['request_timeout'] = float(options['request_timeout']) options['temperature'] = float(options['temperature']) options['max_tokens'] = int(options['max_tokens']) -- cgit v1.2.3 From b209554bf26d4662d126aa664875fd91dc803f62 Mon Sep 17 00:00:00 2001 From: Martin Bielik Date: Sun, 2 Apr 2023 23:18:36 +0200 Subject: restore chat support --- plugin/vim-ai.vim | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugin/vim-ai.vim b/plugin/vim-ai.vim index 9232d47..6192f1d 100644 --- a/plugin/vim-ai.vim +++ b/plugin/vim-ai.vim @@ -109,13 +109,21 @@ function! AIChatRun(is_selection, ...) range set paste let is_outside_of_chat_window = search('^>>> user$', 'nw') == 0 if is_outside_of_chat_window + " open chat window execute g:vim_ai_chat['ui']['open_chat_command'] let prompt = "" if a:0 || a:is_selection let instruction = a:0 ? a:1 : "" let prompt = MakePrompt(a:is_selection, lines, instruction) endif - execute "normal! i>>> user\n\n" . prompt + let is_outside_of_chat_window = search('^>>> user$', 'nw') == 0 + if is_outside_of_chat_window + " write an user prompt + execute "normal! i>>> user\n\n" . prompt + else + " appending prompt into restored conversation + execute "normal! Gi" . prompt + endif endif let s:last_command = "chat" -- cgit v1.2.3 From 923746952c3d4794343f0505f4a6389fb6926bdb Mon Sep 17 00:00:00 2001 From: Martin Bielik Date: Sun, 2 Apr 2023 23:41:48 +0200 Subject: open chat command docu --- README.md | 31 +++++++++++++++++++++++++++++++ doc/vim-ai.txt | 3 +++ 2 files changed, 34 insertions(+) diff --git a/README.md b/README.md index 8900f2f..f92377c 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,37 @@ nnoremap c :AIChat nnoremap r :AIRedo ``` + +### Interface configuration + +Default interface configuration: + +```vim +let g:vim_ai_chat = { +\ "ui": { +\ "open_chat_command": "below new | call vim_ai#MakeScratchWindow()", +\ }, +\} +``` + +Tips: + +```vim +" restore conversation from the file +let g:vim_ai_chat = { +\ "ui": { +\ "open_chat_command": "below new /tmp/last_conversation.aichat", +\ }, +\} + +" open chat in a new tab +let g:vim_ai_chat = { +\ "ui": { +\ "open_chat_command": "tabnew | call vim_ai#MakeScratchWindow()", +\ }, +\} +``` + ### Completion configuration Request to the OpenAI API can be configured for each command. diff --git a/doc/vim-ai.txt b/doc/vim-ai.txt index d0519fd..d9c3eeb 100644 --- a/doc/vim-ai.txt +++ b/doc/vim-ai.txt @@ -66,6 +66,9 @@ Options: > \ "temperature": 1, \ "request_timeout": 10, \ }, + \ "ui": { + \ "open_chat_command": "below new | call vim_ai#MakeScratchWindow()", + \ }, \} Check OpenAI docs for more infomration: -- cgit v1.2.3 From 4029d16d1fb58e56b7eb67bb037e9644838fef24 Mon Sep 17 00:00:00 2001 From: Martin Bielik Date: Mon, 3 Apr 2023 00:25:59 +0200 Subject: trim newlines from the prompt, fixes #5 --- py/chat.py | 4 ++++ py/complete.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/py/chat.py b/py/chat.py index 0104014..f8345b4 100644 --- a/py/chat.py +++ b/py/chat.py @@ -30,6 +30,10 @@ if not messages: file_content = ">>> user\n\n" + file_content messages.append({"role": "user", "content": file_content }) +for message in messages: + # strip newlines from the content as it causes empty responses + message["content"] = message["content"].strip() + try: if messages[-1]["content"].strip(): vim.command("normal! Go\n<<< assistant\n\n") diff --git a/py/complete.py b/py/complete.py index e474580..14667b0 100644 --- a/py/complete.py +++ b/py/complete.py @@ -4,13 +4,13 @@ import openai plugin_root = vim.eval("s:plugin_root") vim.command(f"py3file {plugin_root}/py/utils.py") -prompt = vim.eval("prompt") +prompt = vim.eval("prompt").strip() options = make_options() openai.api_key = load_api_key() try: - if prompt.strip(): + if prompt: print('Completing...') vim.command("redraw") -- cgit v1.2.3 From 8b738e33887e9cf956aeb3db2fe28c7f5069a643 Mon Sep 17 00:00:00 2001 From: Martin Bielik Date: Mon, 3 Apr 2023 20:14:48 +0200 Subject: handle roles in python --- plugin/vim-ai.vim | 9 +-------- py/chat.py | 44 +++++++++++++++++++++++++------------------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/plugin/vim-ai.vim b/plugin/vim-ai.vim index 6192f1d..dba5998 100644 --- a/plugin/vim-ai.vim +++ b/plugin/vim-ai.vim @@ -116,14 +116,7 @@ function! AIChatRun(is_selection, ...) range let instruction = a:0 ? a:1 : "" let prompt = MakePrompt(a:is_selection, lines, instruction) endif - let is_outside_of_chat_window = search('^>>> user$', 'nw') == 0 - if is_outside_of_chat_window - " write an user prompt - execute "normal! i>>> user\n\n" . prompt - else - " appending prompt into restored conversation - execute "normal! Gi" . prompt - endif + execute "normal! Gi" . prompt endif let s:last_command = "chat" diff --git a/py/chat.py b/py/chat.py index f8345b4..6b5ed67 100644 --- a/py/chat.py +++ b/py/chat.py @@ -5,30 +5,36 @@ plugin_root = vim.eval("s:plugin_root") vim.command(f"py3file {plugin_root}/py/utils.py") options = make_options() -file_content = vim.eval('trim(join(getline(1, "$"), "\n"))') openai.api_key = load_api_key() -lines = file_content.splitlines() -messages = [] - -for line in lines: - if line.startswith(">>> system"): - messages.append({"role": "system", "content": ""}) - continue - if line.startswith(">>> user"): - messages.append({"role": "user", "content": ""}) - continue - if line.startswith("<<< assistant"): - messages.append({"role": "assistant", "content": ""}) - continue - if not messages: - continue - messages[-1]["content"] += "\n" + line +def parse_messages(): + file_content = vim.eval('trim(join(getline(1, "$"), "\n"))') + lines = file_content.splitlines() + messages = [] + for line in lines: + if line.startswith(">>> system"): + messages.append({"role": "system", "content": ""}) + continue + if line.startswith(">>> user"): + messages.append({"role": "user", "content": ""}) + continue + if line.startswith("<<< assistant"): + messages.append({"role": "assistant", "content": ""}) + continue + if not messages: + continue + messages[-1]["content"] += "\n" + line + return messages + +messages = parse_messages() if not messages: - file_content = ">>> user\n\n" + file_content - messages.append({"role": "user", "content": file_content }) + # roles not found, put whole file content as an user prompt + vim.command("normal! ggO>>> user\n") + vim.command("normal! G") + vim.command("redraw") + messages = parse_messages() for message in messages: # strip newlines from the content as it causes empty responses -- cgit v1.2.3 From 95d1df263e27a06d3bc6f50f345c79ca0a3cc0b7 Mon Sep 17 00:00:00 2001 From: Martin Bielik Date: Mon, 3 Apr 2023 20:36:54 +0200 Subject: break undo sequence after initial prompt --- py/chat.py | 1 + 1 file changed, 1 insertion(+) diff --git a/py/chat.py b/py/chat.py index 6b5ed67..7614fb5 100644 --- a/py/chat.py +++ b/py/chat.py @@ -33,6 +33,7 @@ if not messages: # roles not found, put whole file content as an user prompt vim.command("normal! ggO>>> user\n") vim.command("normal! G") + vim.command("let &ul=&ul") # breaks undo sequence (https://vi.stackexchange.com/a/29087) vim.command("redraw") messages = parse_messages() -- cgit v1.2.3