8#include "absl/strings/str_format.h"
9#include "absl/time/time.h"
15#include "imgui/imgui.h"
16#include "imgui/misc/cpp/imgui_stdlib.h"
21#include "nlohmann/json.hpp"
32 return (*agent_dir /
"agent_chat_history.json").string();
36 return (*docs_dir /
"agent_chat_history.json").string();
40 return (*temp_dir /
"agent_chat_history.json").string();
42 return (std::filesystem::current_path() /
"agent_chat_history.json").string();
94 const absl::StatusOr<cli::agent::ChatMessage>& response) {
99 "Agent Error: " + std::string(response.status().message()),
102 LOG_ERROR(
"AgentChat",
"Agent Error: %s",
103 response.status().ToString().c_str());
114 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 4));
121 float input_height = ImGui::GetTextLineHeightWithSpacing() * 4 + 20.0f;
122 float toolbar_height = ImGui::GetFrameHeightWithSpacing() + 8.0f;
123 float history_height =
124 available_height > 0 ? (available_height - input_height - toolbar_height)
125 : -input_height - toolbar_height;
127 if (ImGui::BeginChild(
"##ChatHistory", ImVec2(0, history_height),
true)) {
131 (
auto_scroll_ && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) {
132 ImGui::SetScrollHereY(1.0f);
141 ImGui::PopStyleVar();
151 std::string filepath = ResolveAgentChatHistoryPath();
152 if (
auto status =
SaveHistory(filepath); !status.ok()) {
155 "Failed to save history: " + std::string(status.message()),
167 std::string filepath = ResolveAgentChatHistoryPath();
168 if (
auto status =
LoadHistory(filepath); !status.ok()) {
171 "Failed to load history: " + std::string(status.message()),
194 if (history.empty()) {
195 ImGui::TextDisabled(
"Start a conversation with the agent...");
198 for (
size_t i = 0; i < history.size(); ++i) {
213 ImGui::PushID(index);
216 float wrap_width = ImGui::GetContentRegionAvail().x * 0.85f;
217 ImGui::SetCursorPosX(
218 is_user ? (ImGui::GetWindowContentRegionMax().x - wrap_width - 10) : 10);
224 std::string timestamp =
225 absl::FormatTime(
"%H:%M:%S", msg.
timestamp, absl::LocalTimeZone());
226 ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
"[%s]",
233 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
"%s You",
236 ImGui::TextColored(ImVec4(0.6f, 1.0f, 0.6f, 1.0f),
"%s Agent",
241 ImVec4 bg_col = is_user ? ImVec4(0.2f, 0.2f, 0.25f, 1.0f)
242 : ImVec4(0.25f, 0.25f, 0.25f, 1.0f);
243 ImGui::PushStyleColor(ImGuiCol_ChildBg, bg_col);
244 ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 8.0f);
246 std::string content_id =
"msg_content_" + std::to_string(index);
247 if (ImGui::BeginChild(content_id.c_str(), ImVec2(wrap_width, 0),
true,
248 ImGuiWindowFlags_AlwaysUseWindowPadding)) {
252 }
else if (!is_user && msg.
json_pretty.has_value()) {
253 ImGui::TextWrapped(
"%s", msg.
json_pretty.value().c_str());
257 for (
const auto& block : blocks) {
261 ImGui::TextWrapped(
"%s", block.content.c_str());
278 ImGui::PopStyleVar();
279 ImGui::PopStyleColor();
309 ImGuiInputTextFlags flags = ImGuiInputTextFlags_EnterReturnsTrue |
310 ImGuiInputTextFlags_CtrlEnterForNewLine;
312 ImGui::PushItemWidth(-1);
313 if (ImGui::IsWindowAppearing()) {
314 ImGui::SetKeyboardFocusHere();
317 bool submit = ImGui::InputTextMultiline(
323 while (!msg.empty() && std::isspace(msg.back()))
329 ImGui::SetKeyboardFocusHere(-1);
333 ImGui::PopItemWidth();
340 if (msg.
message.find(
"Proposal:") != std::string::npos) {
342 if (ImGui::Button(
"View Proposal")) {
352 const std::string& language,
int msg_index) {
353 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.1f, 0.1f, 0.1f, 1.0f));
354 if (ImGui::BeginChild(absl::StrCat(
"code_", msg_index).c_str(), ImVec2(0, 0),
355 true, ImGuiWindowFlags_AlwaysAutoResize)) {
356 if (!language.empty()) {
357 ImGui::TextDisabled(
"%s", language.c_str());
361 ImGui::SetClipboardText(code.c_str());
366 ImGui::TextUnformatted(code.c_str());
369 ImGui::PopStyleColor();
385 const std::string& content) {
386 std::vector<ContentBlock> blocks;
390 while (pos < content.length()) {
391 size_t code_start = content.find(
"```", pos);
392 if (code_start == std::string::npos) {
399 if (code_start > pos) {
401 content.substr(pos, code_start - pos),
""});
404 size_t code_end = content.find(
"```", code_start + 3);
405 if (code_end == std::string::npos) {
413 std::string language;
414 size_t newline = content.find(
'\n', code_start + 3);
415 size_t content_start = code_start + 3;
416 if (newline != std::string::npos && newline < code_end) {
417 language = content.substr(code_start + 3, newline - (code_start + 3));
418 content_start = newline + 1;
421 std::string code = content.substr(content_start, code_end - content_start);
437 if (ImGui::BeginTable(
"ToolResultTable",
438 static_cast<int>(table.
headers.size()),
439 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
440 ImGuiTableFlags_ScrollY)) {
442 for (
const auto& header : table.
headers) {
443 ImGui::TableSetupColumn(header.c_str());
445 ImGui::TableHeadersRow();
448 for (
const auto& row : table.
rows) {
449 ImGui::TableNextRow();
450 for (
size_t col = 0; col < std::min(row.size(), table.
headers.size());
452 ImGui::TableSetColumnIndex(
static_cast<int>(col));
453 ImGui::TextWrapped(
"%s", row[col].c_str());
470 if (meta.tool_names.empty() && meta.tool_iterations == 0) {
478 ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.15f, 0.15f, 0.18f, 1.0f));
479 ImGui::PushStyleColor(ImGuiCol_HeaderHovered,
480 ImVec4(0.2f, 0.2f, 0.25f, 1.0f));
484 meta.tool_iterations, meta.latency_seconds);
486 if (ImGui::TreeNode(
"##ToolTimeline",
"%s", header.c_str())) {
488 if (!meta.tool_names.empty()) {
489 ImGui::TextDisabled(
"Tools called:");
490 for (
const auto& tool : meta.tool_names) {
491 ImGui::BulletText(
"%s", tool.c_str());
497 ImGui::TextDisabled(
"Provider: %s", meta.provider.c_str());
498 if (!meta.model.empty()) {
499 ImGui::TextDisabled(
"Model: %s", meta.model.c_str());
505 ImGui::PopStyleColor(2);
510 std::ifstream file(filepath);
511 if (!file.is_open()) {
512 return absl::NotFoundError(
513 absl::StrFormat(
"Could not open file: %s", filepath));
525 return absl::OkStatus();
526 }
catch (
const nlohmann::json::exception& e) {
527 return absl::InvalidArgumentError(
528 absl::StrFormat(
"Failed to parse JSON: %s", e.what()));
531 return absl::UnimplementedError(
"JSON support not available");
538 std::filesystem::path path(filepath);
539 if (path.has_parent_path()) {
541 std::filesystem::create_directories(path.parent_path(), ec);
543 return absl::InternalError(absl::StrFormat(
544 "Failed to create history directory: %s", ec.message()));
548 std::ofstream file(filepath);
549 if (!file.is_open()) {
550 return absl::InternalError(
551 absl::StrFormat(
"Could not create file: %s", filepath));
559 j[
"messages"] = nlohmann::json::array();
561 for (
const auto& msg : history) {
562 nlohmann::json msg_json;
566 msg_json[
"message"] = msg.message;
567 msg_json[
"timestamp"] = absl::FormatTime(msg.timestamp);
568 j[
"messages"].push_back(msg_json);
573 return absl::OkStatus();
574 }
catch (
const nlohmann::json::exception& e) {
575 return absl::InternalError(
576 absl::StrFormat(
"Failed to serialize JSON: %s", e.what()));
579 return absl::UnimplementedError(
"JSON support not available");
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
absl::StatusOr< ChatMessage > SendMessage(const std::string &message)
const std::vector< ChatMessage > & GetHistory() const
std::vector< ContentBlock > ParseMessageContent(const std::string &content)
float thinking_animation_
void RenderCodeBlock(const std::string &code, const std::string &language, int msg_index)
absl::Status LoadHistory(const std::string &filepath)
void RenderThinkingIndicator()
void SendMessage(const std::string &message)
void RenderTableData(const cli::agent::ChatMessage::TableData &table)
void SetContext(AgentUIContext *context)
void RenderToolTimeline(const cli::agent::ChatMessage &msg)
std::string last_plan_summary_
void Initialize(ToastManager *toast_manager, ProposalDrawer *proposal_drawer)
std::vector< AutomationTelemetry > telemetry_history_
ToastManager * toast_manager_
AgentUIContext * context_
void UpdateHarnessTelemetry(const AutomationTelemetry &telemetry)
void SetRomContext(Rom *rom)
void SetLastPlanSummary(const std::string &summary)
void Draw(float available_height=0.0f)
bool waiting_for_response_
cli::agent::ConversationalAgentService agent_service_
void RenderMessage(const cli::agent::ChatMessage &msg, int index)
ProposalDrawer * proposal_drawer_
void RenderProposalQuickActions(const cli::agent::ChatMessage &msg, int index)
void HandleAgentResponse(const absl::StatusOr< cli::agent::ChatMessage > &response)
cli::agent::ConversationalAgentService * GetAgentService()
absl::Status SaveHistory(const std::string &filepath)
Unified context for agent UI components.
ImGui drawer for displaying and managing agent proposals.
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
#define ICON_MD_FOLDER_OPEN
#define ICON_MD_BUILD_CIRCLE
#define ICON_MD_CONTENT_COPY
#define ICON_MD_DELETE_FOREVER
#define ICON_MD_SMART_TOY
#define LOG_ERROR(category, format,...)
std::string ResolveAgentChatHistoryPath()
std::vector< std::string > headers
std::vector< std::vector< std::string > > rows
std::optional< ModelMetadata > model_metadata
std::optional< TableData > table_data
std::optional< std::string > json_pretty