From b0f7e3b8d92289d1a8086e3eed206f8b5757328b Mon Sep 17 00:00:00 2001 From: Martin Bielik Date: Sat, 9 Mar 2024 18:06:14 +0100 Subject: read role prompt from config --- py/chat.py | 9 ++++++--- py/complete.py | 8 ++++++-- py/utils.py | 24 ++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 5 deletions(-) (limited to 'py') diff --git a/py/chat.py b/py/chat.py index ff70904..85adfec 100644 --- a/py/chat.py +++ b/py/chat.py @@ -4,10 +4,13 @@ import vim plugin_root = vim.eval("s:plugin_root") vim.command(f"py3file {plugin_root}/py/utils.py") +prompt, role_options = parse_prompt_and_role(vim.eval("l:prompt")) config = normalize_config(vim.eval("l:config")) -config_options = config['options'] +config_options = { + **config['options'], + **role_options, +} config_ui = config['ui'] -prompt = vim.eval("l:prompt").strip() def initialize_chat_window(): lines = vim.eval('getline(1, "$")') @@ -72,7 +75,7 @@ try: **openai_options } printDebug("[chat] request: {}", request) - url = config_options['endpoint_url'] + url = options['endpoint_url'] response = openai_request(url, request, http_options) def map_chunk(resp): printDebug("[chat] response: {}", resp) diff --git a/py/complete.py b/py/complete.py index debe275..453ef46 100644 --- a/py/complete.py +++ b/py/complete.py @@ -6,11 +6,15 @@ vim.command(f"py3file {plugin_root}/py/utils.py") config = normalize_config(vim.eval("l:config")) engine = config['engine'] -config_options = config['options'] + +prompt, role_options = parse_prompt_and_role(vim.eval("l:prompt")) +config_options = { + **config['options'], + **role_options, +} openai_options = make_openai_options(config_options) http_options = make_http_options(config_options) -prompt = vim.eval("l:prompt").strip() is_selection = vim.eval("l:is_selection") def complete_engine(prompt): diff --git a/py/utils.py b/py/utils.py index 19de7c4..7a1a574 100644 --- a/py/utils.py +++ b/py/utils.py @@ -11,6 +11,7 @@ import re from urllib.error import URLError from urllib.error import HTTPError import traceback +import configparser is_debugging = vim.eval("g:vim_ai_debug") == "1" debug_log_file = vim.eval("g:vim_ai_debug_log_file") @@ -261,3 +262,26 @@ def handle_completion_error(error): def clear_echo_message(): # https://neovim.discourse.group/t/how-to-clear-the-echo-message-in-the-command-line/268/3 vim.command("call feedkeys(':','nx')") + +def load_role_config(role): + roles_config_path = os.path.expanduser('~/.vim/roles.ini') # TODO configure + roles = configparser.ConfigParser() + roles.read(roles_config_path) + if not role in roles: + raise KnownError(f"Role {role} not found") # TODO handle errors + return roles[role] + +def parse_prompt_and_role(raw_prompt): + prompt = raw_prompt.strip() + role = re.split(' |:', prompt)[0] + if not role.startswith('/'): + # does not require role + return (prompt, {}) + + prompt = prompt[len(role):].strip() + role = role[1:] + + role_config = load_role_config(role) + delim = '' if prompt.startswith(':') else ':\n' + prompt = role_config['prompt'] + delim + prompt + return (prompt, {}) -- cgit v1.2.3 From bdd1069562967bd07921e75873d54eb75d62144d Mon Sep 17 00:00:00 2001 From: Martin Bielik Date: Sat, 9 Mar 2024 18:32:45 +0100 Subject: parse role options --- py/chat.py | 3 ++- py/complete.py | 3 ++- py/utils.py | 28 +++++++++++++++++++++++----- 3 files changed, 27 insertions(+), 7 deletions(-) (limited to 'py') diff --git a/py/chat.py b/py/chat.py index 85adfec..f996800 100644 --- a/py/chat.py +++ b/py/chat.py @@ -8,7 +8,8 @@ prompt, role_options = parse_prompt_and_role(vim.eval("l:prompt")) config = normalize_config(vim.eval("l:config")) config_options = { **config['options'], - **role_options, + **role_options['options_default'], + **role_options['options_chat'], } config_ui = config['ui'] diff --git a/py/complete.py b/py/complete.py index 453ef46..f340e96 100644 --- a/py/complete.py +++ b/py/complete.py @@ -10,7 +10,8 @@ engine = config['engine'] prompt, role_options = parse_prompt_and_role(vim.eval("l:prompt")) config_options = { **config['options'], - **role_options, + **role_options['options_default'], + **role_options['options_complete'], } openai_options = make_openai_options(config_options) http_options = make_http_options(config_options) diff --git a/py/utils.py b/py/utils.py index 7a1a574..52e538d 100644 --- a/py/utils.py +++ b/py/utils.py @@ -269,19 +269,37 @@ def load_role_config(role): roles.read(roles_config_path) if not role in roles: raise KnownError(f"Role {role} not found") # TODO handle errors - return roles[role] + + options = roles[f"{role}.options"] if f"{role}.options" in roles else {} + options_complete =roles[f"{role}.options-complete"] if f"{role}.options-complete" in roles else {} + options_chat = roles[f"{role}.options-chat"] if f"{role}.options-chat" in roles else {} + + return { + 'role': dict(roles[role]), + 'options': { + 'options_default': dict(options), + 'options_complete': dict(options_complete), + 'options_chat': dict(options_chat), + }, + } + +empty_role_options = { + 'options_default': {}, + 'options_complete': {}, + 'options_chat': {}, +} def parse_prompt_and_role(raw_prompt): prompt = raw_prompt.strip() role = re.split(' |:', prompt)[0] if not role.startswith('/'): # does not require role - return (prompt, {}) + return (prompt, empty_role_options) prompt = prompt[len(role):].strip() role = role[1:] - role_config = load_role_config(role) + config = load_role_config(role) delim = '' if prompt.startswith(':') else ':\n' - prompt = role_config['prompt'] + delim + prompt - return (prompt, {}) + prompt = config['role']['prompt'] + delim + prompt + return (prompt, config['options']) -- cgit v1.2.3 From 9db190c977d1f76e2f574882e4edde062eecbb0d Mon Sep 17 00:00:00 2001 From: Martin Bielik Date: Sat, 9 Mar 2024 19:08:34 +0100 Subject: roles completion --- py/roles.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 py/roles.py (limited to 'py') diff --git a/py/roles.py b/py/roles.py new file mode 100644 index 0000000..8f7f161 --- /dev/null +++ b/py/roles.py @@ -0,0 +1,16 @@ +import vim +import os +import configparser + +roles_config_path = os.path.expanduser('~/.vim/roles.ini') # TODO configure +roles = configparser.ConfigParser() +roles.read(roles_config_path) + +role_names = [name for name in roles.sections() if not '.' in name] + +role_list = [f'"{name}"' for name in role_names] +role_list = ", ".join(role_list) + +role_list = f"[{role_list}]" + +vim.command(f'let l:role_list = {role_list}') -- cgit v1.2.3 From f4130feb986760a8d956983cb5cfe8d7106e7d4a Mon Sep 17 00:00:00 2001 From: Martin Bielik Date: Sat, 9 Mar 2024 19:37:55 +0100 Subject: roles example file --- py/roles.py | 2 +- py/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'py') diff --git a/py/roles.py b/py/roles.py index 8f7f161..7c7cf13 100644 --- a/py/roles.py +++ b/py/roles.py @@ -2,7 +2,7 @@ import vim import os import configparser -roles_config_path = os.path.expanduser('~/.vim/roles.ini') # TODO configure +roles_config_path = os.path.expanduser(vim.eval("g:vim_ai_roles_config_file")) roles = configparser.ConfigParser() roles.read(roles_config_path) diff --git a/py/utils.py b/py/utils.py index 52e538d..6600fc3 100644 --- a/py/utils.py +++ b/py/utils.py @@ -264,7 +264,7 @@ def clear_echo_message(): vim.command("call feedkeys(':','nx')") def load_role_config(role): - roles_config_path = os.path.expanduser('~/.vim/roles.ini') # TODO configure + roles_config_path = os.path.expanduser(vim.eval("g:vim_ai_roles_config_file")) roles = configparser.ConfigParser() roles.read(roles_config_path) if not role in roles: -- cgit v1.2.3 From 155d0c8eda5e5effdd7a234da46db18fbe32e17a Mon Sep 17 00:00:00 2001 From: Martin Bielik Date: Sat, 9 Mar 2024 19:48:17 +0100 Subject: fix using role in existing chat --- py/chat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'py') diff --git a/py/chat.py b/py/chat.py index f996800..67f2d5d 100644 --- a/py/chat.py +++ b/py/chat.py @@ -42,7 +42,7 @@ def initialize_chat_window(): vim.command("normal! i\n>>> user\n\n") if prompt: - vim.command("normal! a" + prompt) + vim.command("normal! i" + prompt) vim_break_undo_sequence() vim.command("redraw") -- cgit v1.2.3 From 188d07f5efa8a4cde3407e71ca2013c10c051bba Mon Sep 17 00:00:00 2001 From: Martin Bielik Date: Sat, 9 Mar 2024 20:02:24 +0100 Subject: simple error handling --- py/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'py') diff --git a/py/utils.py b/py/utils.py index 6600fc3..9ef8b72 100644 --- a/py/utils.py +++ b/py/utils.py @@ -265,10 +265,14 @@ def clear_echo_message(): def load_role_config(role): roles_config_path = os.path.expanduser(vim.eval("g:vim_ai_roles_config_file")) + if not os.path.exists(roles_config_path): + raise Exception(f"Role config file does not exist: {roles_config_path}") + roles = configparser.ConfigParser() roles.read(roles_config_path) + if not role in roles: - raise KnownError(f"Role {role} not found") # TODO handle errors + raise Exception(f"Role `{role}` not found") options = roles[f"{role}.options"] if f"{role}.options" in roles else {} options_complete =roles[f"{role}.options-complete"] if f"{role}.options-complete" in roles else {} -- cgit v1.2.3 From cb4c9e94b2a500f38161227df24feed762000095 Mon Sep 17 00:00:00 2001 From: Martin Bielik Date: Sat, 9 Mar 2024 22:18:36 +0100 Subject: supprot config only roles --- py/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'py') diff --git a/py/utils.py b/py/utils.py index 9ef8b72..6bd90a1 100644 --- a/py/utils.py +++ b/py/utils.py @@ -304,6 +304,7 @@ def parse_prompt_and_role(raw_prompt): role = role[1:] config = load_role_config(role) - delim = '' if prompt.startswith(':') else ':\n' - prompt = config['role']['prompt'] + delim + prompt + if 'prompt' in config['role'] and config['role']['prompt']: + delim = '' if prompt.startswith(':') else ':\n' + prompt = config['role']['prompt'] + delim + prompt return (prompt, config['options']) -- cgit v1.2.3 From af52f039513b11e9820ce0bf1e46596a454ebd44 Mon Sep 17 00:00:00 2001 From: Konfekt Date: Sun, 10 Mar 2024 21:28:54 +0100 Subject: Ensure role config file exists before loading to prevent errors The problem was that the application tried to load a roles configuration file without checking whether it actually exists, potentially leading to unhandled exceptions if the file is missing. Ensure that the roles configuration file exists before attempting to read from it; raise an exception with a clear message if the file is not found. diff --git a/py/roles.py b/py/roles.py: -if not os.path.exists(roles_config_path): - raise Exception(f"Role config file does not exist: {roles_config_path}") - --- py/roles.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'py') diff --git a/py/roles.py b/py/roles.py index 7c7cf13..a8689f2 100644 --- a/py/roles.py +++ b/py/roles.py @@ -3,6 +3,9 @@ import os import configparser roles_config_path = os.path.expanduser(vim.eval("g:vim_ai_roles_config_file")) +if not os.path.exists(roles_config_path): + raise Exception(f"Role config file does not exist: {roles_config_path}") + roles = configparser.ConfigParser() roles.read(roles_config_path) -- cgit v1.2.3 From 881fd24a6d2c0de387256b4d6e05f5d0a53cc8e2 Mon Sep 17 00:00:00 2001 From: Konfekt Date: Sun, 10 Mar 2024 21:29:32 +0100 Subject: optionally supplement roles dict by vim function source The application was restricted to loading role configurations only from a predefined config file, which limited extensibility. Enable dynamic role configuration by invoking a custom Vim function if it is defined. This allows users to extend the role configurations beyond the static file. diff --git a/doc/vim-ai.txt b/doc/vim-ai.txt: -The roles in g:vim_ai_roles_config_file are converted to a Vim dictionary. -Optionally, additional roles can be added by defining a function VimAIRoleParser() -whose output is a dictionary of the same format as g:vim_ai_roles_config_file. - diff --git a/py/roles.py b/py/roles.py: -if vim.eval('exists("*VimAIRoleParser")'): - roles.update(vim.eval('VimAIRoleParser()')) - diff --git a/py/utils.py b/py/utils.py: - if vim.eval('exists("*VimAIRoleParser")'): - roles.update(vim.eval('VimAIRoleParser()')) - --- py/roles.py | 7 +++++++ py/utils.py | 7 +++++++ 2 files changed, 14 insertions(+) (limited to 'py') diff --git a/py/roles.py b/py/roles.py index a8689f2..6e0a52e 100644 --- a/py/roles.py +++ b/py/roles.py @@ -9,6 +9,13 @@ if not os.path.exists(roles_config_path): roles = configparser.ConfigParser() roles.read(roles_config_path) +if vim.eval("exists('g:vim_ai_roles_config_function')") == '1': + roles_config_function = vim.eval("g:vim_ai_roles_config_function") + if not vim.eval("exists('*" + roles_config_function + "')"): + raise Exception(f"Role config function does not exist: {roles_config_function}") + else: + roles.update(vim.eval(roles_config_function + "()")) + role_names = [name for name in roles.sections() if not '.' in name] role_list = [f'"{name}"' for name in role_names] diff --git a/py/utils.py b/py/utils.py index 6bd90a1..471b5c4 100644 --- a/py/utils.py +++ b/py/utils.py @@ -271,6 +271,13 @@ def load_role_config(role): roles = configparser.ConfigParser() roles.read(roles_config_path) + if vim.eval("exists('g:vim_ai_roles_config_function')") == '1': + roles_config_function = vim.eval("g:vim_ai_roles_config_function") + if not vim.eval("exists('*" + roles_config_function + "')"): + raise Exception(f"Role config function does not exist: {roles_config_function}") + else: + roles.update(vim.eval(roles_config_function + "()")) + if not role in roles: raise Exception(f"Role `{role}` not found") -- cgit v1.2.3 From 6a053767af08d6edfb46b4be72f05a9b3bc7be04 Mon Sep 17 00:00:00 2001 From: Martin Bielik Date: Sun, 24 Mar 2024 11:29:22 +0100 Subject: reusing parsing code --- py/roles.py | 13 +++++-------- py/utils.py | 15 +++++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) (limited to 'py') diff --git a/py/roles.py b/py/roles.py index 6e0a52e..b7d19e1 100644 --- a/py/roles.py +++ b/py/roles.py @@ -1,6 +1,8 @@ import vim -import os -import configparser + +# import utils +plugin_root = vim.eval("s:plugin_root") +vim.command(f"py3file {plugin_root}/py/utils.py") roles_config_path = os.path.expanduser(vim.eval("g:vim_ai_roles_config_file")) if not os.path.exists(roles_config_path): @@ -9,12 +11,7 @@ if not os.path.exists(roles_config_path): roles = configparser.ConfigParser() roles.read(roles_config_path) -if vim.eval("exists('g:vim_ai_roles_config_function')") == '1': - roles_config_function = vim.eval("g:vim_ai_roles_config_function") - if not vim.eval("exists('*" + roles_config_function + "')"): - raise Exception(f"Role config function does not exist: {roles_config_function}") - else: - roles.update(vim.eval(roles_config_function + "()")) +enhance_roles_with_custom_function(roles) role_names = [name for name in roles.sections() if not '.' in name] diff --git a/py/utils.py b/py/utils.py index 471b5c4..963377d 100644 --- a/py/utils.py +++ b/py/utils.py @@ -263,6 +263,14 @@ def clear_echo_message(): # https://neovim.discourse.group/t/how-to-clear-the-echo-message-in-the-command-line/268/3 vim.command("call feedkeys(':','nx')") +def enhance_roles_with_custom_function(roles): + if vim.eval("exists('g:vim_ai_roles_config_function')") == '1': + roles_config_function = vim.eval("g:vim_ai_roles_config_function") + if not vim.eval("exists('*" + roles_config_function + "')"): + raise Exception(f"Role config function does not exist: {roles_config_function}") + else: + roles.update(vim.eval(roles_config_function + "()")) + def load_role_config(role): roles_config_path = os.path.expanduser(vim.eval("g:vim_ai_roles_config_file")) if not os.path.exists(roles_config_path): @@ -271,12 +279,7 @@ def load_role_config(role): roles = configparser.ConfigParser() roles.read(roles_config_path) - if vim.eval("exists('g:vim_ai_roles_config_function')") == '1': - roles_config_function = vim.eval("g:vim_ai_roles_config_function") - if not vim.eval("exists('*" + roles_config_function + "')"): - raise Exception(f"Role config function does not exist: {roles_config_function}") - else: - roles.update(vim.eval(roles_config_function + "()")) + enhance_roles_with_custom_function(roles) if not role in roles: raise Exception(f"Role `{role}` not found") -- cgit v1.2.3