diff --git a/lua/chatgpt_nvim/config.lua b/lua/chatgpt_nvim/config.lua index e239044..ebd5366 100644 --- a/lua/chatgpt_nvim/config.lua +++ b/lua/chatgpt_nvim/config.lua @@ -2,7 +2,70 @@ local M = {} local uv = vim.loop local ok_yaml, lyaml = pcall(require, "lyaml") -local prompts = require("chatgpt_nvim.prompts") + +local prompt_blocks = { + ["go-development"] = [[ + You are a coding assistant specialized in Go development. + You will receive a project’s context and user instructions related to Go code, + and you must return the requested modifications or guidance. + When returning modifications, follow the specified YAML structure. + Keep your suggestions aligned with Go best practices and idiomatic Go. + ]], + ["typo3-development"] = [[ + You are a coding assistant specialized in TYPO3 development. + You have access to the project’s context and the user’s instructions. + Your answers should focus on TYPO3 coding guidelines, extension development best practices, + and TSconfig or TypoScript recommendations. + ]], + ["rust-development"] = [[ + You are a coding assistant specialized in Rust development. + You will receive a project’s context and user instructions related to Rust code, + and you must return the requested modifications or guidance. + When returning modifications, follow the specified YAML structure. + Keep your suggestions aligned with Rust best practices and idiomatic Rust. + ]], + ["basic-prompt"] = [[ + You are a coding assistant who receives a project's context and user instructions. + The user will provide a prompt, and you will guide them through a workflow: + 1. First, you should analyse which files you need to solve the request. + You can see which files are present in the provided project structure. + Additionally if presented you could also ask for files of a library which is provided in for example composer.json. + 2. If file contents is needed provide a yaml which asks for the file contents. + For example: + project_name: example_project + files: + - path: "relative/path/to/file" + 3. If more information or context is needed, ask the user (outside of the YAML) to provide that. + 4. When all necessary information is gathered, provide the final YAML with the + project's name and a list of files to be created or modified. + Also explain the changes you made below the yaml. + + The final YAML must have a top-level key named 'project_name' that matches the project's configured name, + and a top-level key named 'files', which is a list of file changes. Each element in 'files' must be a mapping with: + - 'path' for the file path relative to the project’s root directory. + - either 'content' with a multiline string for new content, or 'delete: true' if the file should be deleted. + Important: dont use comments in the code to explain which steps you have taken. + Comments should just explain the code and not your thought process. + You can explain your thought process outside of the YAML. + + If more context is needed at any point before providing the final YAML, request it outside of the YAML. + Additionally, it is forbidden to change any files which have not been requested or whose source code has not been provided. + ]], + ["secure-coding"] = [[ + You are a coding assistant specialized in secure software development. + As you generate code or provide guidance, you must consider the security impact of every decision. + You will write and review code with a focus on minimizing vulnerabilities and following best security practices, + such as validating all user inputs, avoiding unsafe libraries or functions, and following secure coding standards. + ]], + ["workflow-prompt"] = [[ + You are a coding assistant focusing on making the Neovim ChatGPT workflow straightforward and user-friendly. + Provide a concise set of steps or guidance, reminding the user: + - How to list needed files for further context + - How to request additional information outside of the YAML + - How to finalize changes with a YAML response containing project_name and files + Always ensure that prompts and explanations remain clear and minimal, reducing user errors. + ]] +} local function get_project_root() local current_file = vim.fn.expand("%:p") @@ -41,7 +104,7 @@ function M.load() directories = { "." }, default_prompt_blocks = {}, -- Changed default from 128000 to 16384 as requested - prompt_char_limit = 300000, + token_limit = 16384, project_name = "", debug = false, initial_files = {}, @@ -71,8 +134,8 @@ function M.load() if type(result.default_prompt_blocks) == "table" then config.default_prompt_blocks = result.default_prompt_blocks end - if type(result.prompt_char_limit) == "number" then - config.prompt_char_limit = result.prompt_char_limit + if type(result.token_limit) == "number" then + config.token_limit = result.token_limit end if type(result.project_name) == "string" then config.project_name = result.project_name @@ -115,8 +178,8 @@ function M.load() if type(config.default_prompt_blocks) == "table" and #config.default_prompt_blocks > 0 then local merged_prompt = {} for _, block_name in ipairs(config.default_prompt_blocks) do - if prompts[block_name] then - table.insert(merged_prompt, prompts[block_name]) + if prompt_blocks[block_name] then + table.insert(merged_prompt, prompt_blocks[block_name]) end end if #merged_prompt > 0 then diff --git a/lua/chatgpt_nvim/init.lua b/lua/chatgpt_nvim/init.lua index 871b694..fd83eb6 100644 --- a/lua/chatgpt_nvim/init.lua +++ b/lua/chatgpt_nvim/init.lua @@ -4,7 +4,6 @@ local context = require('chatgpt_nvim.context') local handler = require('chatgpt_nvim.handler') local config = require('chatgpt_nvim.config') local ui = require('chatgpt_nvim.ui') -local prompts = require('chatgpt_nvim.prompts') local ok_yaml, lyaml = pcall(require, "lyaml") @@ -54,13 +53,42 @@ local function is_directory(path) return stat and stat.type == "directory" end -local function handle_step_by_step_if_needed(prompt) +local function estimate_tokens_basic(text) + local approx_chars_per_token = 4 + local length = #text + return math.floor(length / approx_chars_per_token) +end + +local function estimate_tokens_improved(text) + local words = #vim.split(text, "%s+") + local approximate_tokens = math.floor(words * 0.75) + ui.debug_log("Using improved token estimate: " .. approximate_tokens .. " tokens") + return approximate_tokens +end + +local function get_estimate_fn() local conf = config.load() - local length = #prompt - if not conf.enable_step_by_step or length <= (conf.prompt_char_limit or 8000) then + if conf.improved_debug then + return estimate_tokens_improved + else + return estimate_tokens_basic + end +end + +local function handle_step_by_step_if_needed(prompt, estimate_fn) + local conf = config.load() + local token_count = estimate_fn(prompt) + if not conf.enable_step_by_step or token_count <= (conf.token_limit or 8000) then return { prompt } end - return { prompts["step-prompt"] } + + local step_prompt = [[ + It appears this request might exceed the model's token limit if done all at once. + Please break down the tasks into smaller steps and handle them one by one. + At each step, we'll provide relevant files or context if needed. + Thank you! + ]] + return { step_prompt } end local function close_existing_buffer_by_name(pattern) @@ -366,13 +394,30 @@ function M.run_chatgpt_command() if conf.enable_debug_commands then table.insert(initial_sections, "\n### Debug Commands Info:\n") - table.insert(initial_sections, prompts["debug-commands-info"]) + table.insert(initial_sections, [[ + If you need debugging commands, include them in your YAML response as follows: + + ```yaml + commands: + - command: "ls" + dir: "some/directory" + + - command: "grep" + pattern: "searchString" + target: "path/to/file/or/directory" + ``` + + The "ls" command uses the system's 'ls' command to list directory contents. + When these commands are present and enable_debug_commands is true, I'll execute them and return the results in the clipboard. + ]]) end local prompt = table.concat(initial_sections, "\n") store_prompt_for_reference(prompt) - local chunks = handle_step_by_step_if_needed(prompt) + local estimate_fn = get_estimate_fn() + local chunks = handle_step_by_step_if_needed(prompt, estimate_fn) + copy_to_clipboard(chunks[1]) if #chunks == 1 then vim.api.nvim_out_write("Prompt copied to clipboard! Paste into ChatGPT.\n") @@ -512,16 +557,21 @@ function M.run_chatgpt_paste_command() } local prompt = table.concat(sections, "\n") - local length = #prompt - ui.debug_log("Returning requested files. Character count: " .. length) + local estimate_fn = get_estimate_fn() + local token_count = estimate_fn(prompt) + ui.debug_log("Returning requested files. Token count: " .. token_count) - if length > (conf.prompt_char_limit or 8000) and conf.enable_step_by_step then - local large_step = prompts["step-prompt"] - copy_to_clipboard(large_step) + if token_count > (conf.token_limit or 8000) and conf.enable_step_by_step then + local step_message = [[ + It appears this requested data is quite large. Please split the task into smaller steps + and continue step by step. + Which files would you need for the first step? + ]] + copy_to_clipboard(step_message) print("Step-by-step guidance copied to clipboard!") return - elseif length > (conf.prompt_char_limit or 8000) then - vim.api.nvim_err_writeln("Requested files exceed prompt character limit. No step-by-step support enabled.") + elseif token_count > (conf.token_limit or 8000) then + vim.api.nvim_err_writeln("Requested files exceed token limit. No step-by-step support enabled.") return end @@ -550,26 +600,6 @@ function M.run_chatgpt_current_buffer_command() local initial_files = conf.initial_files or {} local included_sections = {} - local function is_directory(path) - local stat = vim.loop.fs_stat(path) - return stat and stat.type == "directory" - end - - local function read_file(path) - local fd = vim.loop.fs_open(path, "r", 438) - if not fd then - return nil - end - local stat = vim.loop.fs_fstat(fd) - if not stat then - vim.loop.fs_close(fd) - return nil - end - local data = vim.loop.fs_read(fd, stat.size, 0) - vim.loop.fs_close(fd) - return data - end - for _, item in ipairs(initial_files) do local root = vim.fn.getcwd() local full_path = root .. "/" .. item @@ -604,34 +634,30 @@ function M.run_chatgpt_current_buffer_command() if conf.enable_debug_commands then table.insert(initial_sections, "\n### Debug Commands Info:\n") - table.insert(initial_sections, prompts["debug-commands-info"]) + table.insert(initial_sections, [[ + If you need debugging commands, include them in your YAML response as follows: + + ```yaml + commands: + - command: "list" + dir: "some/directory" + + - command: "grep" + pattern: "searchString" + target: "path/to/file/or/directory" + ``` + + The "list" command uses the system's 'ls' command to list directory contents. + When these commands are present and enable_debug_commands is true, I'll execute them and return the results in the clipboard. + ]]) end local prompt = table.concat(initial_sections, "\n") - - local function store_prompt_for_reference(pr) - close_existing_buffer_by_name("ChatGPT_Generated_Prompt$") - local bufnr_ref = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_name(bufnr_ref, "ChatGPT_Generated_Prompt") - vim.api.nvim_buf_set_option(bufnr_ref, "filetype", "markdown") - - local lines_ref = { - "# Below is the generated prompt. You can keep it for reference:", - "" - } - local pr_lines = vim.split(pr, "\n") - for _, line in ipairs(pr_lines) do - table.insert(lines_ref, line) - end - - vim.api.nvim_buf_set_lines(bufnr_ref, 0, -1, false, lines_ref) - vim.cmd("vsplit") - vim.cmd("buffer " .. bufnr_ref) - end - store_prompt_for_reference(prompt) - local chunks = handle_step_by_step_if_needed(prompt) + local estimate_fn = get_estimate_fn() + local chunks = handle_step_by_step_if_needed(prompt, estimate_fn) + copy_to_clipboard(chunks[1]) if #chunks == 1 then vim.api.nvim_out_write("Prompt (from current buffer) copied to clipboard! Paste into ChatGPT.\n") diff --git a/lua/chatgpt_nvim/prompts.lua b/lua/chatgpt_nvim/prompts.lua deleted file mode 100644 index 282557a..0000000 --- a/lua/chatgpt_nvim/prompts.lua +++ /dev/null @@ -1,90 +0,0 @@ -local M = { - ["go-development"] = [[ - You are a coding assistant specialized in Go development. - You will receive a project’s context and user instructions related to Go code, - and you must return the requested modifications or guidance. - When returning modifications, follow the specified YAML structure. - Keep your suggestions aligned with Go best practices and idiomatic Go. - ]], - ["typo3-development"] = [[ - You are a coding assistant specialized in TYPO3 development. - You have access to the project’s context and the user’s instructions. - Your answers should focus on TYPO3 coding guidelines, extension development best practices, - and TSconfig or TypoScript recommendations. - ]], - ["rust-development"] = [[ - You are a coding assistant specialized in Rust development. - You will receive a project’s context and user instructions related to Rust code, - and you must return the requested modifications or guidance. - When returning modifications, follow the specified YAML structure. - Keep your suggestions aligned with Rust best practices and idiomatic Rust. - ]], - ["basic-prompt"] = [[ - You are a coding assistant who receives a project's context and user instructions. - The user will provide a prompt, and you will guide them through a workflow: - 1. First, you should analyse which files you need to solve the request. - You can see which files are present in the provided project structure. - Additionally if presented you could also ask for files of a library which is provided in for example composer.json. - 2. If file contents is needed provide a yaml which asks for the file contents. - For example: - project_name: example_project - files: - - path: "relative/path/to/file" - 3. If more information or context is needed, ask the user (outside of the YAML) to provide that. - 4. When all necessary information is gathered, provide the final YAML with the - project's name and a list of files to be created or modified. - Also explain the changes you made below the yaml. - - The final YAML must have a top-level key named 'project_name' that matches the project's configured name, - and a top-level key named 'files', which is a list of file changes. Each element in 'files' must be a mapping with: - - 'path' for the file path relative to the project’s root directory. - - either 'content' with a multiline string for new content, or 'delete: true' if the file should be deleted. - Important: dont use comments in the code to explain which steps you have taken. - Comments should just explain the code and not your thought process. - You can explain your thought process outside of the YAML. - - If more context is needed at any point before providing the final YAML, request it outside of the YAML. - Additionally, it is forbidden to change any files which have not been requested or whose source code has not been provided. - ]], - ["secure-coding"] = [[ - You are a coding assistant specialized in secure software development. - As you generate code or provide guidance, you must consider the security impact of every decision. - You will write and review code with a focus on minimizing vulnerabilities and following best security practices, - such as validating all user inputs, avoiding unsafe libraries or functions, and following secure coding standards. - ]], - ["workflow-prompt"] = [[ - You are a coding assistant focusing on making the Neovim ChatGPT workflow straightforward and user-friendly. - Provide a concise set of steps or guidance, reminding the user: - - How to list needed files for further context - - How to request additional information outside of the YAML - - How to finalize changes with a YAML response containing project_name and files - Always ensure that prompts and explanations remain clear and minimal, reducing user errors. - ]], - ["step-prompt"] = [[ - It appears this request might exceed the model's prompt character limit if done all at once. - Please break down the tasks into smaller steps and handle them one by one. - At each step, we'll provide relevant files or context if needed. - Thank you! - ]], - ["file-selection-instructions"] = [[ - Delete lines for directories you do NOT want, then save & close (e.g. :wq, :x, or :bd) - ]], - ["debug-commands-info"] = [[ - If you need debugging commands, include them in your YAML response as follows: - - ```yaml - commands: - - command: "list" - dir: "some/directory" - - - command: "grep" - pattern: "searchString" - target: "path/to/file/or/directory" - ``` - - The "ls" command uses the system's 'ls' command to list directory contents. - When these commands are present and enable_debug_commands is true, I'll execute them and return the results in the clipboard. - ]] -} - -return M diff --git a/lua/chatgpt_nvim/ui.lua b/lua/chatgpt_nvim/ui.lua index 9cc5ca5..cca92d1 100644 --- a/lua/chatgpt_nvim/ui.lua +++ b/lua/chatgpt_nvim/ui.lua @@ -1,7 +1,6 @@ local M = {} local config = require('chatgpt_nvim.config') local conf = config.load() -local prompts = require('chatgpt_nvim.prompts') local debug_bufnr = nil if conf.improved_debug then @@ -30,9 +29,7 @@ end function M.pick_directories(dirs) local selected_dirs = {} - local selection_instructions = prompts["file-selection-instructions"] - local lines = { selection_instructions } - + local lines = { "Delete lines for directories you do NOT want, then save & close (e.g. :wq, :x, or :bd)" } for _, d in ipairs(dirs) do table.insert(lines, d) end @@ -91,4 +88,8 @@ function M.pick_directories(dirs) return selected_dirs end -return M +-------------------------------------------------------------------------------- +-- The old chunkify method has been removed, since we now rely on step-by-step +-------------------------------------------------------------------------------- + +return M \ No newline at end of file