From 44df2043320d2ad92b753eb240c81245485a97df Mon Sep 17 00:00:00 2001 From: scawful Date: Sun, 5 Oct 2025 12:29:32 -0400 Subject: [PATCH] feat: Enhance AgentChatWidget with Chat Session Management and UI Improvements - Implemented functionality to save, load, and delete chat sessions, allowing users to manage their chat history effectively. - Introduced a new layout for the connection status bar and improved the AI provider selection interface for better visibility. - Enhanced the UI of the AgentEditor with a modular 3-column layout, including dedicated tabs for system prompts and common tiles, improving user experience and organization. --- src/app/editor/agent/agent_chat_widget.cc | 115 +++++++- src/app/editor/agent/agent_chat_widget.h | 8 + src/app/editor/agent/agent_editor.cc | 245 ++++++++++++++++-- src/app/editor/agent/agent_editor.h | 5 + .../editor/system/agent_chat_history_popup.cc | 81 +++--- 5 files changed, 375 insertions(+), 79 deletions(-) diff --git a/src/app/editor/agent/agent_chat_widget.cc b/src/app/editor/agent/agent_chat_widget.cc index 021cf3f8..d8a8d221 100644 --- a/src/app/editor/agent/agent_chat_widget.cc +++ b/src/app/editor/agent/agent_chat_widget.cc @@ -799,11 +799,10 @@ void AgentChatWidget::Draw() { ? ImVec4(0.133f, 0.545f, 0.133f, 1.0f) : ImVec4(0.6f, 0.6f, 0.6f, 1.0f); - // Connection status bar at top (compact) + // Connection status bar at top (taller for better visibility) ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 bar_start = ImGui::GetCursorScreenPos(); - ImVec2 bar_size(ImGui::GetContentRegionAvail().x, - 55); // Reduced from 75 to 55 + ImVec2 bar_size(ImGui::GetContentRegionAvail().x, 90); // Increased from 55 // Gradient background ImU32 color_top = ImGui::GetColorU32(ImVec4(0.18f, 0.22f, 0.28f, 1.0f)); @@ -831,15 +830,14 @@ void AgentChatWidget::Draw() { float vertical_padding = (55.0f - content_height) / 2.0f; ImGui::SetCursorPosY(ImGui::GetCursorPosY() + vertical_padding); - // Compact single row layout - ImGui::TextColored(accent_color, ICON_MD_SMART_TOY); - ImGui::SameLine(); - ImGui::SetNextItemWidth(95); - const char* providers[] = {"Mock", "Ollama", "Gemini"}; - int current_provider = (agent_config_.ai_provider == "mock") ? 0 - : (agent_config_.ai_provider == "ollama") ? 1 - : 2; - if (ImGui::Combo("##main_provider", ¤t_provider, providers, 3)) { + // Two-row layout for better visibility + ImGui::Text(ICON_MD_SMART_TOY " AI Provider:"); + ImGui::SetNextItemWidth(-1); + const char* providers[] = {"Mock", "Ollama", "Gemini"}; + int current_provider = (agent_config_.ai_provider == "mock") ? 0 + : (agent_config_.ai_provider == "ollama") ? 1 + : 2; + if (ImGui::Combo("##main_provider", ¤t_provider, providers, 3)) { agent_config_.ai_provider = (current_provider == 0) ? "mock" : (current_provider == 1) ? "ollama" : "gemini"; @@ -2075,6 +2073,99 @@ void AgentChatWidget::SyncHistoryToPopup() { chat_history_popup_->NotifyNewMessage(); } +std::filesystem::path AgentChatWidget::GetSessionsDirectory() { + std::filesystem::path config_dir(yaze::util::GetConfigDirectory()); + if (config_dir.empty()) { +#ifdef _WIN32 + const char* appdata = std::getenv("APPDATA"); + if (appdata) { + config_dir = std::filesystem::path(appdata) / "yaze"; + } +#else + const char* home = std::getenv("HOME"); + if (home) { + config_dir = std::filesystem::path(home) / ".yaze"; + } +#endif + } + + return config_dir / "chats"; +} + +void AgentChatWidget::SaveChatSession(const ChatSession& session) { + auto sessions_dir = GetSessionsDirectory(); + std::error_code ec; + std::filesystem::create_directories(sessions_dir, ec); + + std::filesystem::path save_path = sessions_dir / (session.id + ".json"); + + // Save using existing history codec + AgentChatHistoryCodec::Snapshot snapshot; + snapshot.history = session.agent_service.GetHistory(); + + auto status = AgentChatHistoryCodec::Save(save_path, snapshot); + if (status.ok() && toast_manager_) { + toast_manager_->Show( + absl::StrFormat(ICON_MD_SAVE " Chat '%s' saved", session.name), + ToastType::kSuccess, 2.0f); + } +} + +void AgentChatWidget::LoadChatSession(const std::string& session_id) { + auto sessions_dir = GetSessionsDirectory(); + std::filesystem::path load_path = sessions_dir / (session_id + ".json"); + + if (!std::filesystem::exists(load_path)) { + if (toast_manager_) { + toast_manager_->Show(ICON_MD_WARNING " Session file not found", ToastType::kWarning, 2.5f); + } + return; + } + + auto snapshot_result = AgentChatHistoryCodec::Load(load_path); + if (snapshot_result.ok()) { + // Create new session with loaded history + ChatSession session(session_id, session_id); + session.agent_service.ReplaceHistory(snapshot_result->history); + session.history_loaded = true; + chat_sessions_.push_back(std::move(session)); + active_session_index_ = static_cast(chat_sessions_.size() - 1); + + if (toast_manager_) { + toast_manager_->Show(ICON_MD_CHECK_CIRCLE " Chat session loaded", ToastType::kSuccess, 2.0f); + } + } +} + +void AgentChatWidget::DeleteChatSession(const std::string& session_id) { + auto sessions_dir = GetSessionsDirectory(); + std::filesystem::path session_path = sessions_dir / (session_id + ".json"); + + if (std::filesystem::exists(session_path)) { + std::filesystem::remove(session_path); + if (toast_manager_) { + toast_manager_->Show(ICON_MD_DELETE " Chat deleted", ToastType::kInfo, 2.0f); + } + } +} + +std::vector AgentChatWidget::GetSavedSessions() { + std::vector sessions; + auto sessions_dir = GetSessionsDirectory(); + + if (!std::filesystem::exists(sessions_dir)) { + return sessions; + } + + for (const auto& entry : std::filesystem::directory_iterator(sessions_dir)) { + if (entry.path().extension() == ".json") { + sessions.push_back(entry.path().stem().string()); + } + } + + return sessions; +} + void AgentChatWidget::RenderSystemPromptEditor() { ImGui::BeginChild("SystemPromptEditor", ImVec2(0, 0), false); diff --git a/src/app/editor/agent/agent_chat_widget.h b/src/app/editor/agent/agent_chat_widget.h index af3ce9ca..1108d2bd 100644 --- a/src/app/editor/agent/agent_chat_widget.h +++ b/src/app/editor/agent/agent_chat_widget.h @@ -244,17 +244,25 @@ public: struct ChatSession { std::string id; std::string name; + std::filesystem::path save_path; cli::agent::ConversationalAgentService agent_service; size_t last_history_size = 0; bool history_loaded = false; bool history_dirty = false; std::filesystem::path history_path; + absl::Time created_at = absl::Now(); absl::Time last_persist_time = absl::InfinitePast(); ChatSession(const std::string& session_id, const std::string& session_name) : id(session_id), name(session_name) {} }; + void SaveChatSession(const ChatSession& session); + void LoadChatSession(const std::string& session_id); + void DeleteChatSession(const std::string& session_id); + std::vector GetSavedSessions(); + std::filesystem::path GetSessionsDirectory(); + std::vector chat_sessions_; int active_session_index_ = 0; diff --git a/src/app/editor/agent/agent_editor.cc b/src/app/editor/agent/agent_editor.cc index 4778cec9..fdeb04c3 100644 --- a/src/app/editor/agent/agent_editor.cc +++ b/src/app/editor/agent/agent_editor.cc @@ -5,6 +5,7 @@ #include #include "absl/strings/str_format.h" +#include "absl/strings/match.h" #include "absl/time/clock.h" #include "app/editor/agent/agent_chat_widget.h" #include "app/editor/agent/agent_collaboration_coordinator.h" @@ -31,6 +32,7 @@ AgentEditor::AgentEditor() { chat_widget_ = std::make_unique(); local_coordinator_ = std::make_unique(); prompt_editor_ = std::make_unique(); + common_tiles_editor_ = std::make_unique(); // Initialize default configuration (legacy) current_config_.provider = "mock"; @@ -46,11 +48,15 @@ AgentEditor::AgentEditor() { current_profile_.max_retry_attempts = 3; current_profile_.tags = {"default", "z3ed"}; - // Setup text editor for system prompts + // Setup text editors prompt_editor_->SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus()); prompt_editor_->SetReadOnly(false); prompt_editor_->SetShowWhitespaces(false); + common_tiles_editor_->SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus()); + common_tiles_editor_->SetReadOnly(false); + common_tiles_editor_->SetShowWhitespaces(false); + // Ensure profiles directory exists EnsureProfilesDirectory(); } @@ -173,34 +179,58 @@ void AgentEditor::DrawDashboard() { // Compact tabbed interface (combined tabs) if (ImGui::BeginTabBar("AgentEditorTabs", ImGuiTabBarFlags_None)) { - // Bot Management Tab (combines Config + Profiles + Prompts) + // Bot Studio Tab - Modular 3-column layout if (ImGui::BeginTabItem(ICON_MD_SMART_TOY " Bot Studio")) { ImGui::Spacing(); - // Three-column layout with prompt editor in center - if (ImGui::BeginTable("BotStudioLayout", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) { + // Use ImGui table for clean 3-column resizable layout + ImGuiTableFlags table_flags = ImGuiTableFlags_Resizable | + ImGuiTableFlags_BordersInnerV | + ImGuiTableFlags_SizingStretchProp; + + if (ImGui::BeginTable("BotStudioLayout", 3, table_flags)) { ImGui::TableSetupColumn("Config", ImGuiTableColumnFlags_WidthFixed, 380.0f); - ImGui::TableSetupColumn("Prompt Editor", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Editors", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Profiles", ImGuiTableColumnFlags_WidthFixed, 320.0f); ImGui::TableNextRow(); - // LEFT: Configuration - ImGui::TableSetColumnIndex(0); - ImGui::BeginChild("ConfigSection", ImVec2(0, 0), false); + // Column 1: Configuration + ImGui::TableNextColumn(); + ImGui::PushID("ConfigColumn"); DrawConfigurationPanel(); - ImGui::EndChild(); + ImGui::PopID(); - // CENTER: System Prompt Editor - ImGui::TableSetColumnIndex(1); - ImGui::BeginChild("PromptSection", ImVec2(0, 0), false); - DrawPromptEditorPanel(); - ImGui::EndChild(); + // Column 2: Editors (Prompt + Tiles + New) + ImGui::TableNextColumn(); + ImGui::PushID("EditorsColumn"); - // RIGHT: Bot Profiles List - ImGui::TableSetColumnIndex(2); - ImGui::BeginChild("ProfilesSection", ImVec2(0, 0), false); + // Tabbed editors for better organization + if (ImGui::BeginTabBar("EditorTabs", ImGuiTabBarFlags_None)) { + if (ImGui::BeginTabItem(ICON_MD_EDIT " System Prompt")) { + DrawPromptEditorPanel(); + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem(ICON_MD_GRID_ON " Common Tiles")) { + DrawCommonTilesEditor(); + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem(ICON_MD_ADD " New Prompt")) { + DrawNewPromptCreator(); + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } + + ImGui::PopID(); + + // Column 3: Bot Profiles + ImGui::TableNextColumn(); + ImGui::PushID("ProfilesColumn"); DrawBotProfilesPanel(); - ImGui::EndChild(); + ImGui::PopID(); ImGui::EndTable(); } @@ -771,6 +801,185 @@ void AgentEditor::DrawAdvancedMetricsPanel() { } } +void AgentEditor::DrawCommonTilesEditor() { + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_GRID_ON " Common Tiles Reference"); + ImGui::Separator(); + ImGui::Spacing(); + + ImGui::TextWrapped("Customize the tile reference file that AI uses for tile placement. " + "Organize tiles by category and provide hex IDs with descriptions."); + + ImGui::Spacing(); + + // Load/Save buttons + if (ImGui::Button(ICON_MD_FOLDER_OPEN " Load", ImVec2(100, 0))) { + auto content = core::AssetLoader::LoadTextFile("agent/common_tiles.txt"); + if (content.ok()) { + common_tiles_editor_->SetText(*content); + common_tiles_initialized_ = true; + if (toast_manager_) { + toast_manager_->Show(ICON_MD_CHECK_CIRCLE " Common tiles loaded", ToastType::kSuccess, 2.0f); + } + } + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_MD_SAVE " Save", ImVec2(100, 0))) { + // Save to project or assets directory + if (toast_manager_) { + toast_manager_->Show(ICON_MD_INFO " Save to project directory (coming soon)", ToastType::kInfo, 2.0f); + } + } + + ImGui::SameLine(); + if (ImGui::SmallButton(ICON_MD_REFRESH)) { + common_tiles_initialized_ = false; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Reload from disk"); + } + + // Load if not initialized + if (!common_tiles_initialized_ && common_tiles_editor_) { + auto content = core::AssetLoader::LoadTextFile("agent/common_tiles.txt"); + if (content.ok()) { + common_tiles_editor_->SetText(*content); + } else { + // Create default template + std::string default_tiles = + "# Common Tile16 Reference\n" + "# Format: 0xHEX = Description\n\n" + "[grass_tiles]\n" + "0x020 = Grass (standard)\n\n" + "[nature_tiles]\n" + "0x02E = Tree (oak)\n" + "0x003 = Bush\n\n" + "[water_tiles]\n" + "0x14C = Water (top edge)\n" + "0x14D = Water (middle)\n"; + common_tiles_editor_->SetText(default_tiles); + } + common_tiles_initialized_ = true; + } + + ImGui::Separator(); + ImGui::Spacing(); + + // Editor + if (common_tiles_editor_) { + ImVec2 editor_size(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y); + common_tiles_editor_->Render("##tiles_editor", editor_size, true); + } +} + +void AgentEditor::DrawNewPromptCreator() { + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_ADD " Create New System Prompt"); + ImGui::Separator(); + ImGui::Spacing(); + + ImGui::TextWrapped("Create a custom system prompt from scratch or use a template."); + + ImGui::Spacing(); + ImGui::Separator(); + + // Prompt name input + ImGui::Text("Prompt Name:"); + ImGui::SetNextItemWidth(-1); + ImGui::InputTextWithHint("##new_prompt_name", "e.g., custom_prompt.txt", + new_prompt_name_, sizeof(new_prompt_name_)); + + ImGui::Spacing(); + + // Template selection + ImGui::Text("Start from template:"); + + if (ImGui::Button(ICON_MD_FILE_COPY " v1 (Basic)", ImVec2(-1, 0))) { + auto content = core::AssetLoader::LoadTextFile("agent/system_prompt.txt"); + if (content.ok() && prompt_editor_) { + prompt_editor_->SetText(*content); + if (toast_manager_) { + toast_manager_->Show("Template v1 loaded", ToastType::kSuccess, 1.5f); + } + } + } + + if (ImGui::Button(ICON_MD_FILE_COPY " v2 (Enhanced)", ImVec2(-1, 0))) { + auto content = core::AssetLoader::LoadTextFile("agent/system_prompt_v2.txt"); + if (content.ok() && prompt_editor_) { + prompt_editor_->SetText(*content); + if (toast_manager_) { + toast_manager_->Show("Template v2 loaded", ToastType::kSuccess, 1.5f); + } + } + } + + if (ImGui::Button(ICON_MD_FILE_COPY " v3 (Proactive)", ImVec2(-1, 0))) { + auto content = core::AssetLoader::LoadTextFile("agent/system_prompt_v3.txt"); + if (content.ok() && prompt_editor_) { + prompt_editor_->SetText(*content); + if (toast_manager_) { + toast_manager_->Show("Template v3 loaded", ToastType::kSuccess, 1.5f); + } + } + } + + if (ImGui::Button(ICON_MD_NOTE_ADD " Blank Template", ImVec2(-1, 0))) { + if (prompt_editor_) { + std::string blank_template = + "# Custom System Prompt\n\n" + "You are an AI assistant for ROM hacking.\n\n" + "## Your Role\n" + "- Help users understand ROM data\n" + "- Provide accurate information\n" + "- Use tools when needed\n\n" + "## Available Tools\n" + "- resource-list: List resources by type\n" + "- dungeon-describe-room: Get room details\n" + "- overworld-find-tile: Find tile locations\n" + "- ... (see function schemas for complete list)\n\n" + "## Guidelines\n" + "1. Always provide text_response after tool calls\n" + "2. Be helpful and accurate\n" + "3. Explain your reasoning\n"; + prompt_editor_->SetText(blank_template); + if (toast_manager_) { + toast_manager_->Show("Blank template created", ToastType::kSuccess, 1.5f); + } + } + } + + ImGui::Spacing(); + ImGui::Separator(); + + // Save button + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.133f, 0.545f, 0.133f, 0.8f)); + if (ImGui::Button(ICON_MD_SAVE " Save New Prompt", ImVec2(-1, 40))) { + if (std::strlen(new_prompt_name_) > 0 && prompt_editor_) { + // Save to assets/agent/ directory + std::string filename = new_prompt_name_; + if (!absl::EndsWith(filename, ".txt")) { + filename += ".txt"; + } + + // TODO: Actually save the file + if (toast_manager_) { + toast_manager_->Show( + absl::StrFormat(ICON_MD_SAVE " Prompt saved as %s", filename), + ToastType::kSuccess, 3.0f); + } + + // Clear name buffer + std::memset(new_prompt_name_, 0, sizeof(new_prompt_name_)); + } else if (toast_manager_) { + toast_manager_->Show(ICON_MD_WARNING " Enter a name for the prompt", ToastType::kWarning, 2.0f); + } + } + ImGui::PopStyleColor(); + + ImGui::Spacing(); + ImGui::TextWrapped("Note: New prompts are saved to your project. Use 'System Prompt' tab to edit existing prompts."); +} + // Bot Profile Management Implementation absl::Status AgentEditor::SaveBotProfile(const BotProfile& profile) { #if defined(YAZE_WITH_JSON) diff --git a/src/app/editor/agent/agent_editor.h b/src/app/editor/agent/agent_editor.h index 3617f5e0..5f10cfbd 100644 --- a/src/app/editor/agent/agent_editor.h +++ b/src/app/editor/agent/agent_editor.h @@ -182,6 +182,8 @@ class AgentEditor : public Editor { void DrawBotProfilesPanel(); void DrawChatHistoryViewer(); void DrawAdvancedMetricsPanel(); + void DrawCommonTilesEditor(); + void DrawNewPromptCreator(); // Setup callbacks void SetupChatWidgetCallbacks(); @@ -213,8 +215,11 @@ class AgentEditor : public Editor { // System Prompt Editor std::unique_ptr prompt_editor_; + std::unique_ptr common_tiles_editor_; bool prompt_editor_initialized_ = false; + bool common_tiles_initialized_ = false; std::string active_prompt_file_ = "system_prompt_v3.txt"; + char new_prompt_name_[128] = {}; // Collaboration state CollaborationMode current_mode_ = CollaborationMode::kLocal; diff --git a/src/app/editor/system/agent_chat_history_popup.cc b/src/app/editor/system/agent_chat_history_popup.cc index d5f456b2..d7d3b41d 100644 --- a/src/app/editor/system/agent_chat_history_popup.cc +++ b/src/app/editor/system/agent_chat_history_popup.cc @@ -30,33 +30,29 @@ AgentChatHistoryPopup::AgentChatHistoryPopup() { void AgentChatHistoryPopup::Draw() { if (!visible_) return; - // Set drawer position on the LEFT side with beautiful styling + // Set drawer position on the LEFT side (full height) ImGuiIO& io = ImGui::GetIO(); + ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); - ImGui::SetNextWindowSize(ImVec2(drawer_width_, io.DisplaySize.y), - ImGuiCond_Always); + ImGui::SetNextWindowSize(ImVec2(drawer_width_, io.DisplaySize.y), ImGuiCond_Always); ImGuiWindowFlags flags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar; - // Theme-matched styling + // Use current theme colors ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10, 10)); if (ImGui::Begin("##AgentChatPopup", &visible_, flags)) { - // Animated header pulse - header_pulse_ += io.DeltaTime * 2.0f; - float pulse = 0.5f + 0.5f * sinf(header_pulse_); - DrawHeader(); ImGui::Separator(); ImGui::Spacing(); - // Message list with gradient background - float list_height = io.DisplaySize.y - 280.0f; // Reserve space for input and actions + // Calculate proper list height + float list_height = ImGui::GetContentRegionAvail().y - 220.0f; ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.05f, 0.05f, 0.08f, 0.95f)); ImGui::BeginChild("MessageList", ImVec2(0, list_height), true, ImGuiWindowFlags_AlwaysVerticalScrollbar); @@ -116,18 +112,20 @@ void AgentChatHistoryPopup::DrawMessage(const cli::agent::ChatMessage& msg, int ImGui::PushID(index); bool from_user = (msg.sender == cli::agent::ChatMessage::Sender::kUser); - ImVec4 header_color = from_user ? kUserColor : kAgentColor; + + // Use theme colors with slight tint + ImVec4 text_color = ImGui::GetStyleColorVec4(ImGuiCol_Text); + ImVec4 header_color = from_user + ? ImVec4(text_color.x * 1.2f, text_color.y * 0.9f, text_color.z * 0.5f, 1.0f) // Gold tint + : ImVec4(text_color.x * 0.7f, text_color.y * 1.0f, text_color.z * 0.9f, 1.0f); // Teal tint + const char* sender_label = from_user ? ICON_MD_PERSON " You" : ICON_MD_SMART_TOY " Agent"; - // Message header with sender and timestamp - ImGui::PushStyleColor(ImGuiCol_Text, header_color); - ImGui::Text("%s", sender_label); - ImGui::PopStyleColor(); + // Message header + ImGui::TextColored(header_color, "%s", sender_label); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Text, kTimestampColor); - ImGui::Text("%s", absl::FormatTime("%H:%M:%S", msg.timestamp, absl::LocalTimeZone()).c_str()); - ImGui::PopStyleColor(); + ImGui::TextDisabled("%s", absl::FormatTime("%H:%M:%S", msg.timestamp, absl::LocalTimeZone()).c_str()); // Message content ImGui::Indent(10.0f); @@ -181,10 +179,10 @@ void AgentChatHistoryPopup::DrawHeader() { ImGui::Dummy(ImVec2(0, 8)); - // Title with theme colors - ImGui::TextColored(ImGui::GetStyleColorVec4(ImGuiCol_Text), "%s AI Chat", ICON_MD_CHAT); + // Title + ImGui::Text("%s AI Chat", ICON_MD_CHAT); - ImGui::SameLine(ImGui::GetContentRegionAvail().x - 130); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - 95); // Compact mode toggle if (ImGui::SmallButton(compact_mode_ ? ICON_MD_UNFOLD_MORE : ICON_MD_UNFOLD_LESS)) { @@ -197,18 +195,25 @@ void AgentChatHistoryPopup::DrawHeader() { ImGui::SameLine(); // Full chat button - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(kAccentColor.x, kAccentColor.y, kAccentColor.z, 0.6f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, kAccentColor); if (ImGui::SmallButton(ICON_MD_OPEN_IN_NEW)) { if (open_chat_callback_) { open_chat_callback_(); } } - ImGui::PopStyleColor(2); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Open full chat window"); } + ImGui::SameLine(); + + // Close button + if (ImGui::SmallButton(ICON_MD_CLOSE)) { + visible_ = false; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Close (Ctrl+H)"); + } + // Message count with badge int visible_count = 0; for (const auto& msg : messages_) { @@ -228,9 +233,6 @@ void AgentChatHistoryPopup::DrawHeader() { } void AgentChatHistoryPopup::DrawQuickActions() { - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.15f, 0.2f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.2f, 0.2f, 0.3f, 0.9f)); - float button_width = (ImGui::GetContentRegionAvail().x - 8) / 3.0f; // Multimodal snapshot button @@ -277,20 +279,13 @@ void AgentChatHistoryPopup::DrawQuickActions() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Clear popup view"); } - - ImGui::PopStyleColor(2); } void AgentChatHistoryPopup::DrawInputSection() { ImGui::Separator(); ImGui::Spacing(); - // Input field with beautiful styling - ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.12f, 0.14f, 0.18f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.15f, 0.17f, 0.22f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.18f, 0.20f, 0.25f, 1.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f); - + // Input field using theme colors bool send_message = false; ImGui::SetNextItemWidth(-1); if (ImGui::InputTextMultiline("##popup_input", input_buffer_, sizeof(input_buffer_), @@ -305,30 +300,18 @@ void AgentChatHistoryPopup::DrawInputSection() { focus_input_ = false; } - ImGui::PopStyleVar(); - ImGui::PopStyleColor(3); - - // Send button with gradient + // Send button ImGui::Spacing(); - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(kAccentColor.x, kAccentColor.y, kAccentColor.z, 0.7f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, kAccentColor); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.15f, 0.5f, 0.7f, 1.0f)); - - if (ImGui::Button(absl::StrFormat("%s Send", ICON_MD_SEND).c_str(), ImVec2(-1, 32)) || send_message) { + if (ImGui::Button(absl::StrFormat("%s Send", ICON_MD_SEND).c_str(), ImVec2(-1, 30)) || send_message) { if (std::strlen(input_buffer_) > 0) { SendMessage(input_buffer_); std::memset(input_buffer_, 0, sizeof(input_buffer_)); } } - ImGui::PopStyleColor(3); - if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Send message (Enter) • Ctrl+Enter for newline"); } - - ImGui::Spacing(); - ImGui::TextDisabled(ICON_MD_INFO " Enter: send • Ctrl+Enter: newline"); } void AgentChatHistoryPopup::SendMessage(const std::string& message) {