feat(prompt_logger): Save prompts to disk and add buffer navigation via keymaps (#2075)
This commit is contained in:
13
README.md
13
README.md
@@ -391,6 +391,19 @@ _See [config.lua#L9](./lua/avante/config.lua) for the full config_
|
|||||||
-- auto_approve_tool_permissions = true, -- Auto-approve all tools (no prompts)
|
-- auto_approve_tool_permissions = true, -- Auto-approve all tools (no prompts)
|
||||||
-- auto_approve_tool_permissions = {"bash", "replace_in_file"}, -- Auto-approve specific tools only
|
-- auto_approve_tool_permissions = {"bash", "replace_in_file"}, -- Auto-approve specific tools only
|
||||||
},
|
},
|
||||||
|
prompt_logger = { -- logs prompts to disk (timestamped, for replay/debugging)
|
||||||
|
enabled = true, -- toggle logging entirely
|
||||||
|
log_dir = vim.fn.stdpath("cache") .. "/avante_prompts", -- directory where logs are saved
|
||||||
|
fortune_cookie_on_success = false, -- shows a random fortune after each logged prompt (requires `fortune` installed)
|
||||||
|
next_prompt = {
|
||||||
|
normal = "<C-n>", -- load the next (newer) prompt log in normal mode
|
||||||
|
insert = "<C-n>",
|
||||||
|
},
|
||||||
|
prev_prompt = {
|
||||||
|
normal = "<C-p>", -- load the previous (older) prompt log in normal mode
|
||||||
|
insert = "<C-p>",
|
||||||
|
},
|
||||||
|
},
|
||||||
mappings = {
|
mappings = {
|
||||||
--- @class AvanteConflictMappings
|
--- @class AvanteConflictMappings
|
||||||
diff = {
|
diff = {
|
||||||
|
|||||||
@@ -431,6 +431,19 @@ M._defaults = {
|
|||||||
auto_approve_tool_permissions = false, -- Default: show permission prompts for all tools
|
auto_approve_tool_permissions = false, -- Default: show permission prompts for all tools
|
||||||
auto_check_diagnostics = true,
|
auto_check_diagnostics = true,
|
||||||
},
|
},
|
||||||
|
prompt_logger = { -- logs prompts to disk (timestamped, for replay/debugging)
|
||||||
|
enabled = true, -- toggle logging entirely
|
||||||
|
log_dir = Utils.join_paths(vim.fn.stdpath("cache"), "avante_prompts"), -- directory where logs are saved
|
||||||
|
fortune_cookie_on_success = false, -- shows a random fortune after each logged prompt (requires `fortune` installed)
|
||||||
|
next_prompt = {
|
||||||
|
normal = "<C-n>", -- load the next (newer) prompt log in normal mode
|
||||||
|
insert = "<C-n>",
|
||||||
|
},
|
||||||
|
prev_prompt = {
|
||||||
|
normal = "<C-p>", -- load the previous (older) prompt log in normal mode
|
||||||
|
insert = "<C-p>",
|
||||||
|
},
|
||||||
|
},
|
||||||
history = {
|
history = {
|
||||||
max_tokens = 4096,
|
max_tokens = 4096,
|
||||||
carried_entry_count = nil,
|
carried_entry_count = nil,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ local Config = require("avante.config")
|
|||||||
local Diff = require("avante.diff")
|
local Diff = require("avante.diff")
|
||||||
local Llm = require("avante.llm")
|
local Llm = require("avante.llm")
|
||||||
local Utils = require("avante.utils")
|
local Utils = require("avante.utils")
|
||||||
|
local PromptLogger = require("avante.utils.promptLogger")
|
||||||
local Highlights = require("avante.highlights")
|
local Highlights = require("avante.highlights")
|
||||||
local RepoMap = require("avante.repo_map")
|
local RepoMap = require("avante.repo_map")
|
||||||
local FileSelector = require("avante.file_selector")
|
local FileSelector = require("avante.file_selector")
|
||||||
@@ -2562,6 +2563,8 @@ function Sidebar:create_input_container()
|
|||||||
|
|
||||||
---@param request string
|
---@param request string
|
||||||
local function handle_submit(request)
|
local function handle_submit(request)
|
||||||
|
if Config.prompt_logger.enabled then PromptLogger.log_prompt(request) end
|
||||||
|
|
||||||
if self.is_generating then
|
if self.is_generating then
|
||||||
self:add_history_messages({
|
self:add_history_messages({
|
||||||
HistoryMessage:new({ role = "user", content = request }),
|
HistoryMessage:new({ role = "user", content = request }),
|
||||||
@@ -2845,6 +2848,10 @@ function Sidebar:create_input_container()
|
|||||||
|
|
||||||
self.input_container:map("n", Config.mappings.submit.normal, on_submit)
|
self.input_container:map("n", Config.mappings.submit.normal, on_submit)
|
||||||
self.input_container:map("i", Config.mappings.submit.insert, on_submit)
|
self.input_container:map("i", Config.mappings.submit.insert, on_submit)
|
||||||
|
self.input_container:map("n", Config.prompt_logger.next_prompt.normal, PromptLogger.on_log_retrieve(-1))
|
||||||
|
self.input_container:map("i", Config.prompt_logger.next_prompt.insert, PromptLogger.on_log_retrieve(-1))
|
||||||
|
self.input_container:map("n", Config.prompt_logger.prev_prompt.normal, PromptLogger.on_log_retrieve(1))
|
||||||
|
self.input_container:map("i", Config.prompt_logger.prev_prompt.insert, PromptLogger.on_log_retrieve(1))
|
||||||
|
|
||||||
if Config.mappings.sidebar.close_from_input ~= nil then
|
if Config.mappings.sidebar.close_from_input ~= nil then
|
||||||
if Config.mappings.sidebar.close_from_input.normal ~= nil then
|
if Config.mappings.sidebar.close_from_input.normal ~= nil then
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ local lsp = vim.lsp
|
|||||||
---@field path avante.utils.path
|
---@field path avante.utils.path
|
||||||
---@field environment avante.utils.environment
|
---@field environment avante.utils.environment
|
||||||
---@field lsp avante.utils.lsp
|
---@field lsp avante.utils.lsp
|
||||||
|
---@field logger avante.utils.promptLogger
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
setmetatable(M, {
|
setmetatable(M, {
|
||||||
|
|||||||
99
lua/avante/utils/promptLogger.lua
Normal file
99
lua/avante/utils/promptLogger.lua
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
local Config = require("avante.config")
|
||||||
|
local Utils = require("avante.utils")
|
||||||
|
|
||||||
|
---@class avante.utils.promptLogger
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
function M.log_prompt(request)
|
||||||
|
local log_dir = Config.prompt_logger.log_dir
|
||||||
|
local log_file = Utils.join_paths(log_dir, "avante_prompt_" .. os.date("%Y%m%d_%H%M%S") .. ".log")
|
||||||
|
|
||||||
|
if vim.fn.isdirectory(log_dir) == 0 then vim.fn.mkdir(log_dir, "p") end
|
||||||
|
|
||||||
|
local file = io.open(log_file, "w")
|
||||||
|
if file then
|
||||||
|
file:write(request)
|
||||||
|
file:close()
|
||||||
|
if Config.prompt_logger and Config.prompt_logger.fortune_cookie_on_success then
|
||||||
|
local handle = io.popen("fortune -s -n 100")
|
||||||
|
if handle then
|
||||||
|
local fortune_msg = handle:read("*a")
|
||||||
|
handle:close()
|
||||||
|
if fortune_msg and #fortune_msg > 0 then vim.notify(fortune_msg, vim.log.levels.INFO, { title = "" }) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
vim.notify("Failed to log prompt", vim.log.levels.ERROR)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Cache + helper
|
||||||
|
local logs, idx = {}, 0
|
||||||
|
|
||||||
|
local function refresh_logs()
|
||||||
|
local dir = Config.prompt_logger.log_dir
|
||||||
|
logs = vim.fn.glob(Utils.join_paths(dir, "avante_prompt_*.log"), false, true)
|
||||||
|
table.sort(logs, function(a, b) -- newest first
|
||||||
|
return a > b
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param step integer 0 = keep | 1 = newer | -1 = older
|
||||||
|
local function load_log(step)
|
||||||
|
if #logs == 0 then refresh_logs() end
|
||||||
|
if #logs == 0 then
|
||||||
|
vim.notify("No prompt logs found 🤷", vim.log.levels.WARN)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
idx = (idx + step) -- turn wheel
|
||||||
|
if idx < 1 then idx = #logs end -- wrap around
|
||||||
|
if idx > #logs then idx = 1 end
|
||||||
|
|
||||||
|
local fp = io.open(logs[idx], "r")
|
||||||
|
if not fp then
|
||||||
|
vim.notify("Could not open " .. logs[idx], vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local content = fp:read("*a")
|
||||||
|
fp:close()
|
||||||
|
|
||||||
|
local buf = vim.api.nvim_get_current_buf()
|
||||||
|
vim.api.nvim_buf_set_lines(buf, 0, -1, false, vim.split(content, "\n", { plain = true }))
|
||||||
|
vim.bo[buf].modifiable = true
|
||||||
|
vim.b[buf].avante_logpath = logs[idx]
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.next_log() load_log(1) end
|
||||||
|
|
||||||
|
function M.prev_log() load_log(-1) end
|
||||||
|
|
||||||
|
local function _read_log(delta)
|
||||||
|
if #logs == 0 then refresh_logs() end
|
||||||
|
if #logs == 0 then return nil end
|
||||||
|
|
||||||
|
local target = idx + delta
|
||||||
|
if target < 1 then target = 1 end
|
||||||
|
if target > #logs then target = #logs end
|
||||||
|
|
||||||
|
idx = target
|
||||||
|
|
||||||
|
local fp = io.open(logs[idx], "r")
|
||||||
|
if not fp then return nil end
|
||||||
|
local txt = fp:read("*a")
|
||||||
|
fp:close()
|
||||||
|
return { txt = txt, path = logs[idx] }
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.on_log_retrieve(delta)
|
||||||
|
return function()
|
||||||
|
local res = _read_log(delta)
|
||||||
|
if not res then
|
||||||
|
vim.notify("No logs available", vim.log.levels.WARN)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
vim.api.nvim_buf_set_lines(0, 0, -1, false, vim.split(res.txt, "\n", { plain = true }))
|
||||||
|
vim.api.nvim_win_set_cursor(0, { 1, 0 })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
Reference in New Issue
Block a user