refactor(history): move message rendering into history module
Move code related to converting history messages into UI representation from utils into history/render.lua for better code organization and clean up the implementation.
This commit is contained in:
171
lua/avante/history/render.lua
Normal file
171
lua/avante/history/render.lua
Normal file
@@ -0,0 +1,171 @@
|
||||
local Helpers = require("avante.history.helpers")
|
||||
local Line = require("avante.ui.line")
|
||||
local Utils = require("avante.utils")
|
||||
|
||||
local M = {}
|
||||
|
||||
---Converts text into format suitable for UI
|
||||
---@param text string
|
||||
---@return avante.ui.Line[]
|
||||
local function text_to_lines(text)
|
||||
local text_lines = vim.split(text, "\n")
|
||||
local lines = {}
|
||||
for _, text_line in ipairs(text_lines) do
|
||||
table.insert(lines, Line:new({ { text_line } }))
|
||||
end
|
||||
return lines
|
||||
end
|
||||
|
||||
---Converts "thinking" item into format suitable for UI
|
||||
---@param item AvanteLLMMessageContentItem
|
||||
---@return avante.ui.Line[]
|
||||
local function thinking_to_lines(item)
|
||||
local text = item.thinking or item.data or ""
|
||||
local text_lines = vim.split(text, "\n")
|
||||
local ui_lines = {}
|
||||
table.insert(ui_lines, Line:new({ { Utils.icon("🤔 ") .. "Thought content:" } }))
|
||||
table.insert(ui_lines, Line:new({ { "" } }))
|
||||
for _, text_line in ipairs(text_lines) do
|
||||
table.insert(ui_lines, Line:new({ { "> " .. text_line } }))
|
||||
end
|
||||
return ui_lines
|
||||
end
|
||||
|
||||
---Converts logs generated by a tool during execution into format suitable for UI
|
||||
---@param tool_name string
|
||||
---@param logs string[]
|
||||
---@return avante.ui.Line[]
|
||||
local function tool_logs_to_lines(tool_name, logs)
|
||||
local ui_lines = {}
|
||||
local num_logs = #logs
|
||||
|
||||
for log_idx = 1, num_logs do
|
||||
local log_lines = vim.split(logs[log_idx]:gsub("^%[" .. tool_name .. "%]: ", "", 1), "\n")
|
||||
local num_lines = #log_lines
|
||||
|
||||
for line_idx = 1, num_lines do
|
||||
local decoration = (log_idx == num_logs and line_idx == num_lines) and "╰─ " or "│ "
|
||||
table.insert(ui_lines, Line:new({ { decoration }, { " " .. log_lines[line_idx] } }))
|
||||
end
|
||||
end
|
||||
return ui_lines
|
||||
end
|
||||
|
||||
local STATE_TO_HL = {
|
||||
generating = "AvanteStateSpinnerToolCalling",
|
||||
failed = "AvanteStateSpinnerFailed",
|
||||
succeeded = "AvanteStateSpinnerSucceeded",
|
||||
}
|
||||
|
||||
---Converts a tool invocation into format suitable for UI
|
||||
---@param item AvanteLLMMessageContentItem
|
||||
---@param messages avante.HistoryMessage[]
|
||||
---@param logs string[]|nil
|
||||
---@return avante.ui.Line[]
|
||||
local function tool_to_lines(item, messages, logs)
|
||||
local lines = {}
|
||||
|
||||
local result = Helpers.get_tool_result(item.id, messages)
|
||||
local state
|
||||
if not result then
|
||||
state = "generating"
|
||||
elseif result.is_error then
|
||||
state = "failed"
|
||||
else
|
||||
state = "succeeded"
|
||||
end
|
||||
table.insert(
|
||||
lines,
|
||||
Line:new({
|
||||
{ "╭─ " },
|
||||
{ " " .. item.name .. " ", STATE_TO_HL[state] },
|
||||
{ " " .. state },
|
||||
})
|
||||
)
|
||||
if logs then vim.list_extend(lines, tool_logs_to_lines(item.name, logs)) end
|
||||
return lines
|
||||
end
|
||||
|
||||
---Converts a message item into representation suitable for UI
|
||||
---@param item AvanteLLMMessageContentItem
|
||||
---@param message avante.HistoryMessage
|
||||
---@param messages avante.HistoryMessage[]
|
||||
---@return avante.ui.Line[]
|
||||
local function message_content_item_to_lines(item, message, messages)
|
||||
if type(item) == "string" then
|
||||
return text_to_lines(item)
|
||||
elseif type(item) == "table" then
|
||||
if item.type == "thinking" or item.type == "redacted_thinking" then
|
||||
return thinking_to_lines(item.thinking or item.data or "")
|
||||
elseif item.type == "text" then
|
||||
return text_to_lines(item.text)
|
||||
elseif item.type == "image" then
|
||||
return { Line:new({ { "" } }) }
|
||||
elseif item.type == "tool_use" then
|
||||
local ok, llm_tool = pcall(require, "avante.llm_tools." .. item.name)
|
||||
if ok then
|
||||
local tool_result_message = Helpers.get_tool_result_message(message, messages)
|
||||
---@cast llm_tool AvanteLLMTool
|
||||
if llm_tool.on_render then
|
||||
return llm_tool.on_render(item.input, {
|
||||
logs = message.tool_use_logs,
|
||||
state = message.state,
|
||||
store = message.tool_use_store,
|
||||
result_message = tool_result_message,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return tool_to_lines(item, messages, message.tool_use_logs)
|
||||
end
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
---Converts a message into representation suitable for UI
|
||||
---@param message avante.HistoryMessage
|
||||
---@param messages avante.HistoryMessage[]
|
||||
---@return avante.ui.Line[]
|
||||
function M.message_to_lines(message, messages)
|
||||
if message.displayed_content then return text_to_lines(message.displayed_content) end
|
||||
local content = message.message.content
|
||||
if type(content) == "string" then return text_to_lines(content) end
|
||||
if vim.islist(content) then
|
||||
local lines = {}
|
||||
for _, item in ipairs(content) do
|
||||
local item_lines = message_content_item_to_lines(item, message, messages)
|
||||
lines = vim.list_extend(lines, item_lines)
|
||||
end
|
||||
return lines
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
---Converts a message item into text representation
|
||||
---@param item AvanteLLMMessageContentItem
|
||||
---@param message avante.HistoryMessage
|
||||
---@param messages avante.HistoryMessage[]
|
||||
---@return string
|
||||
local function message_content_item_to_text(item, message, messages)
|
||||
local lines = message_content_item_to_lines(item, message, messages)
|
||||
return vim.iter(lines):map(function(line) return tostring(line) end):join("\n")
|
||||
end
|
||||
|
||||
---Converts a message into text representation
|
||||
---@param message avante.HistoryMessage
|
||||
---@param messages avante.HistoryMessage[]
|
||||
---@return string
|
||||
function M.message_to_text(message, messages)
|
||||
local content = message.message.content
|
||||
if type(content) == "string" then return content end
|
||||
if vim.islist(content) then
|
||||
return vim
|
||||
.iter(content)
|
||||
:map(function(item) return message_content_item_to_text(item, message, messages) end)
|
||||
:filter(function(text) return text ~= "" end)
|
||||
:join("\n")
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -46,9 +46,10 @@ function M.summarize_memory(prev_memory, history_messages, cb)
|
||||
cb(nil)
|
||||
return
|
||||
end
|
||||
local Render = require("avante.history.render")
|
||||
local conversation_items = vim
|
||||
.iter(history_messages)
|
||||
:map(function(msg) return msg.message.role .. ": " .. Utils.message_to_text(msg, history_messages) end)
|
||||
:map(function(msg) return msg.message.role .. ": " .. Render.message_to_text(msg, history_messages) end)
|
||||
:totable()
|
||||
local conversation_text = table.concat(conversation_items, "\n")
|
||||
local user_prompt = "Here is the conversation so far:\n"
|
||||
|
||||
@@ -17,6 +17,7 @@ local RepoMap = require("avante.repo_map")
|
||||
local FileSelector = require("avante.file_selector")
|
||||
local LLMTools = require("avante.llm_tools")
|
||||
local History = require("avante.history")
|
||||
local Render = require("avante.history.render")
|
||||
local Line = require("avante.ui.line")
|
||||
local LRUCache = require("avante.utils.lru_cache")
|
||||
|
||||
@@ -1694,7 +1695,7 @@ end
|
||||
---@return avante.ui.Line[]
|
||||
local function _get_message_lines(message, messages, ctx)
|
||||
if message.visible == false then return {} end
|
||||
local lines = Utils.message_to_lines(message, messages)
|
||||
local lines = Render.message_to_lines(message, messages)
|
||||
if message.is_user_submission then
|
||||
ctx.selected_filepaths = message.selected_filepaths
|
||||
local text = table.concat(vim.tbl_map(function(line) return tostring(line) end, lines), "\n")
|
||||
@@ -1793,7 +1794,7 @@ end
|
||||
---@return string | nil
|
||||
local function render_message(message, messages, ctx)
|
||||
if message.visible == false then return nil end
|
||||
local text = Utils.message_to_text(message, messages)
|
||||
local text = Render.message_to_text(message, messages)
|
||||
if text == "" then return nil end
|
||||
if message.is_user_submission then
|
||||
ctx.selected_filepaths = message.selected_filepaths
|
||||
@@ -2070,7 +2071,7 @@ function Sidebar:add_history_messages(messages)
|
||||
and messages[1].just_for_display ~= true
|
||||
and messages[1].state == "generated"
|
||||
then
|
||||
local first_msg_text = Utils.message_to_text(messages[1], messages)
|
||||
local first_msg_text = Render.message_to_text(messages[1], messages)
|
||||
local lines_ = vim.iter(vim.split(first_msg_text, "\n")):filter(function(line) return line ~= "" end):totable()
|
||||
if #lines_ > 0 then
|
||||
self.chat_history.title = lines_[1]
|
||||
|
||||
@@ -1520,155 +1520,6 @@ function M.is_edit_func_call_tool_use(tool_use)
|
||||
return is_replace_func_call, is_str_replace_editor_func_call, is_str_replace_based_edit_tool_func_call, path
|
||||
end
|
||||
|
||||
---@param text string
|
||||
---@param hl string | nil
|
||||
---@return avante.ui.Line[]
|
||||
function M.text_to_lines(text, hl)
|
||||
local Line = require("avante.ui.line")
|
||||
local text_lines = vim.split(text, "\n")
|
||||
local lines = {}
|
||||
for _, text_line in ipairs(text_lines) do
|
||||
local piece = { text_line }
|
||||
if hl then table.insert(piece, hl) end
|
||||
table.insert(lines, Line:new({ piece }))
|
||||
end
|
||||
return lines
|
||||
end
|
||||
|
||||
---@param thinking_text string
|
||||
---@param hl string | nil
|
||||
---@return avante.ui.Line[]
|
||||
function M.thinking_to_lines(thinking_text, hl)
|
||||
local Line = require("avante.ui.line")
|
||||
local text_lines = vim.split(thinking_text, "\n")
|
||||
local lines = {}
|
||||
table.insert(lines, Line:new({ { M.icon("🤔 ") .. "Thought content:" } }))
|
||||
table.insert(lines, Line:new({ { "" } }))
|
||||
for _, text_line in ipairs(text_lines) do
|
||||
local piece = { "> " .. text_line }
|
||||
if hl then table.insert(piece, hl) end
|
||||
table.insert(lines, Line:new({ piece }))
|
||||
end
|
||||
return lines
|
||||
end
|
||||
|
||||
---@param item AvanteLLMMessageContentItem
|
||||
---@param message avante.HistoryMessage
|
||||
---@param messages avante.HistoryMessage[]
|
||||
---@return avante.ui.Line[]
|
||||
function M.message_content_item_to_lines(item, message, messages)
|
||||
local Line = require("avante.ui.line")
|
||||
local HistoryHelpers = require("avante.history.helpers")
|
||||
if type(item) == "string" then return M.text_to_lines(item) end
|
||||
if type(item) == "table" then
|
||||
if item.type == "thinking" or item.type == "redacted_thinking" then
|
||||
return M.thinking_to_lines(item.thinking or item.data or "")
|
||||
end
|
||||
if item.type == "text" then return M.text_to_lines(item.text) end
|
||||
if item.type == "image" then
|
||||
return { Line:new({ { "" } }) }
|
||||
end
|
||||
if item.type == "tool_use" then
|
||||
local tool_result_message = HistoryHelpers.get_tool_result_message(message, messages)
|
||||
local lines = {}
|
||||
local state = "generating"
|
||||
local hl = "AvanteStateSpinnerToolCalling"
|
||||
local ok, llm_tool = pcall(require, "avante.llm_tools." .. item.name)
|
||||
if ok then
|
||||
---@cast llm_tool AvanteLLMTool
|
||||
if llm_tool.on_render then
|
||||
return llm_tool.on_render(item.input, {
|
||||
logs = message.tool_use_logs,
|
||||
state = message.state,
|
||||
store = message.tool_use_store,
|
||||
result_message = tool_result_message,
|
||||
})
|
||||
end
|
||||
end
|
||||
if tool_result_message then
|
||||
local tool_result = tool_result_message.message.content[1]
|
||||
if tool_result.is_error then
|
||||
state = "failed"
|
||||
hl = "AvanteStateSpinnerFailed"
|
||||
else
|
||||
state = "succeeded"
|
||||
hl = "AvanteStateSpinnerSucceeded"
|
||||
end
|
||||
end
|
||||
table.insert(
|
||||
lines,
|
||||
Line:new({ { "╭─" }, { " " }, { string.format(" %s ", item.name), hl }, { string.format(" %s", state) } })
|
||||
)
|
||||
if message.tool_use_logs then
|
||||
for idx, log in ipairs(message.tool_use_logs) do
|
||||
local log_ = M.trim(log, { prefix = string.format("[%s]: ", item.name) })
|
||||
local lines_ = vim.split(log_, "\n")
|
||||
if idx ~= #(message.tool_use_logs or {}) then
|
||||
for _, line_ in ipairs(lines_) do
|
||||
table.insert(lines, Line:new({ { "│" }, { string.format(" %s", line_) } }))
|
||||
end
|
||||
else
|
||||
for idx_, line_ in ipairs(lines_) do
|
||||
if idx_ ~= #lines_ then
|
||||
table.insert(lines, Line:new({ { "│" }, { string.format(" %s", line_) } }))
|
||||
else
|
||||
table.insert(lines, Line:new({ { "╰─" }, { string.format(" %s", line_) } }))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return lines
|
||||
end
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
---@param message avante.HistoryMessage
|
||||
---@param messages avante.HistoryMessage[]
|
||||
---@return avante.ui.Line[]
|
||||
function M.message_to_lines(message, messages)
|
||||
if message.displayed_content then return M.text_to_lines(message.displayed_content) end
|
||||
local content = message.message.content
|
||||
if type(content) == "string" then return M.text_to_lines(content) end
|
||||
if vim.islist(content) then
|
||||
local lines = {}
|
||||
for _, item in ipairs(content) do
|
||||
local lines_ = M.message_content_item_to_lines(item, message, messages)
|
||||
lines = vim.list_extend(lines, lines_)
|
||||
end
|
||||
return lines
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
---@param item AvanteLLMMessageContentItem
|
||||
---@param message avante.HistoryMessage
|
||||
---@param messages avante.HistoryMessage[]
|
||||
---@return string
|
||||
function M.message_content_item_to_text(item, message, messages)
|
||||
local lines = M.message_content_item_to_lines(item, message, messages)
|
||||
if #lines == 0 then return "" end
|
||||
return table.concat(vim.tbl_map(function(line) return tostring(line) end, lines), "\n")
|
||||
end
|
||||
|
||||
---@param message avante.HistoryMessage
|
||||
---@param messages avante.HistoryMessage[]
|
||||
---@return string
|
||||
function M.message_to_text(message, messages)
|
||||
local content = message.message.content
|
||||
if type(content) == "string" then return content end
|
||||
if vim.islist(content) then
|
||||
local pieces = {}
|
||||
for _, item in ipairs(content) do
|
||||
local text = M.message_content_item_to_text(item, message, messages)
|
||||
if text ~= "" then table.insert(pieces, text) end
|
||||
end
|
||||
return table.concat(pieces, "\n")
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
---Counts number of strings in text, accounting for possibility of a trailing newline
|
||||
---@param str string | nil
|
||||
---@return integer
|
||||
|
||||
Reference in New Issue
Block a user