Files
avante.nvim/lua/avante/history/render.lua
2025-07-23 23:47:23 +08:00

172 lines
5.5 KiB
Lua

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({ { "![image](" .. item.source.media_type .. ": " .. item.source.data .. ")" } }) }
elseif item.type == "tool_use" and item.name then
local ok, llm_tool = pcall(require, "avante.llm_tools." .. item.name)
if ok then
local tool_result_message = Helpers.get_tool_result_message(item.id, 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