From 38915a5162c981e544ab56fd25b94dc31d7b34b9 Mon Sep 17 00:00:00 2001 From: scawful Date: Sun, 5 Oct 2025 05:51:53 -0400 Subject: [PATCH] feat: Implement Z3ED Command Callbacks for Proposal Management in EditorManager - Added Z3ED command callbacks for managing proposals within the AgentChatWidget, enhancing user interaction with proposal acceptance, rejection, listing, and diff viewing functionalities. - Updated the AgentChatWidget to initialize default chat sessions and improved the chat history popup with new message sending and snapshot capturing capabilities. - Enhanced the UI of the AgentEditor with a compact tabbed interface for better organization of bot management and session history, improving overall user experience. --- src/app/editor/agent/agent_chat_widget.cc | 84 +++++- src/app/editor/agent/agent_editor.cc | 127 +++++--- src/app/editor/editor_manager.cc | 41 +++ .../editor/system/agent_chat_history_popup.cc | 279 ++++++++++++++---- .../editor/system/agent_chat_history_popup.h | 33 ++- 5 files changed, 447 insertions(+), 117 deletions(-) diff --git a/src/app/editor/agent/agent_chat_widget.cc b/src/app/editor/agent/agent_chat_widget.cc index 68c498b5..e36db44d 100644 --- a/src/app/editor/agent/agent_chat_widget.cc +++ b/src/app/editor/agent/agent_chat_widget.cc @@ -105,6 +105,12 @@ AgentChatWidget::AgentChatWidget() { memset(input_buffer_, 0, sizeof(input_buffer_)); history_path_ = ResolveHistoryPath(); history_supported_ = AgentChatHistoryCodec::Available(); + + // Initialize default session + if (chat_sessions_.empty()) { + chat_sessions_.emplace_back("default", "Main Session"); + active_session_index_ = 0; + } } void AgentChatWidget::SetRomContext(Rom* rom) { @@ -126,15 +132,42 @@ void AgentChatWidget::SetProposalDrawer(ProposalDrawer* drawer) { void AgentChatWidget::SetChatHistoryPopup(AgentChatHistoryPopup* popup) { chat_history_popup_ = popup; + if (!chat_history_popup_) return; + // Set up callback to open this chat window - if (chat_history_popup_) { - chat_history_popup_->SetOpenChatCallback([this]() { - this->set_active(true); - }); - - // Initial sync - SyncHistoryToPopup(); - } + chat_history_popup_->SetOpenChatCallback([this]() { + this->set_active(true); + }); + + // Set up callback to send messages from popup + chat_history_popup_->SetSendMessageCallback([this](const std::string& message) { + // Send message through the agent service + auto response = agent_service_.SendMessage(message); + HandleAgentResponse(response); + PersistHistory(); + }); + + // Set up callback to capture snapshots from popup + chat_history_popup_->SetCaptureSnapshotCallback([this]() { + if (multimodal_callbacks_.capture_snapshot) { + std::filesystem::path output_path; + auto status = multimodal_callbacks_.capture_snapshot(&output_path); + if (status.ok()) { + multimodal_state_.last_capture_path = output_path; + multimodal_state_.last_updated = absl::Now(); + if (toast_manager_) { + toast_manager_->Show(ICON_MD_PHOTO " Screenshot captured", ToastType::kSuccess, 2.5f); + } + } else if (toast_manager_) { + toast_manager_->Show( + absl::StrFormat("Capture failed: %s", status.message()), + ToastType::kError, 3.0f); + } + } + }); + + // Initial sync + SyncHistoryToPopup(); } void AgentChatWidget::EnsureHistoryLoaded() { @@ -647,6 +680,7 @@ void AgentChatWidget::Draw() { if (ImGui::BeginMenu(ICON_MD_MENU " Actions")) { if (ImGui::MenuItem(ICON_MD_DELETE_FOREVER " Clear History")) { agent_service_.ResetConversation(); + SyncHistoryToPopup(); if (toast_manager_) { toast_manager_->Show("Chat history cleared", ToastType::kInfo, 2.5f); } @@ -654,6 +688,7 @@ void AgentChatWidget::Draw() { ImGui::Separator(); if (ImGui::MenuItem(ICON_MD_REFRESH " Reset Conversation")) { agent_service_.ResetConversation(); + SyncHistoryToPopup(); if (toast_manager_) { toast_manager_->Show("Conversation reset", ToastType::kInfo, 2.5f); } @@ -664,9 +699,42 @@ void AgentChatWidget::Draw() { toast_manager_->Show("Export not yet implemented", ToastType::kWarning); } } + ImGui::Separator(); + if (ImGui::MenuItem(ICON_MD_ADD " New Session Tab")) { + // Create new session + if (!chat_sessions_.empty()) { + ChatSession new_session( + absl::StrFormat("session_%d", chat_sessions_.size()), + absl::StrFormat("Session %d", chat_sessions_.size() + 1)); + chat_sessions_.push_back(std::move(new_session)); + active_session_index_ = chat_sessions_.size() - 1; + if (toast_manager_) { + toast_manager_->Show("New session created", ToastType::kSuccess); + } + } + } ImGui::EndMenu(); } + // Session tabs in menu bar (if multiple sessions) + if (!chat_sessions_.empty() && chat_sessions_.size() > 1) { + ImGui::Separator(); + for (size_t i = 0; i < chat_sessions_.size(); ++i) { + bool is_active = (i == active_session_index_); + if (is_active) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2f, 0.8f, 0.4f, 1.0f)); + } + if (ImGui::MenuItem(chat_sessions_[i].name.c_str(), nullptr, is_active)) { + active_session_index_ = i; + history_loaded_ = false; // Trigger reload + SyncHistoryToPopup(); + } + if (is_active) { + ImGui::PopStyleColor(); + } + } + } + ImGui::EndMenuBar(); } diff --git a/src/app/editor/agent/agent_editor.cc b/src/app/editor/agent/agent_editor.cc index 6dd1cab2..6e8e49d6 100644 --- a/src/app/editor/agent/agent_editor.cc +++ b/src/app/editor/agent/agent_editor.cc @@ -12,6 +12,7 @@ #include "app/editor/system/toast_manager.h" #include "app/rom.h" #include "app/gui/icons.h" +#include "app/core/platform/asset_loader.h" #include "util/file_util.h" #ifdef YAZE_WITH_GRPC @@ -170,40 +171,64 @@ void AgentEditor::DrawDashboard() { ImGui::EndMenuBar(); } - // Tabbed interface for dense organization + // Compact tabbed interface (combined tabs) if (ImGui::BeginTabBar("AgentEditorTabs", ImGuiTabBarFlags_None)) { - // Configuration Tab - if (ImGui::BeginTabItem(ICON_MD_SETTINGS " Configuration")) { + // Bot Management Tab (combines Config + Profiles + Prompts) + if (ImGui::BeginTabItem(ICON_MD_SMART_TOY " Bot Studio")) { ImGui::Spacing(); - DrawConfigurationPanel(); + + // Three-column layout with prompt editor in center + if (ImGui::BeginTable("BotStudioLayout", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) { + ImGui::TableSetupColumn("Config", ImGuiTableColumnFlags_WidthFixed, 380.0f); + ImGui::TableSetupColumn("Prompt Editor", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Profiles", ImGuiTableColumnFlags_WidthFixed, 320.0f); + ImGui::TableNextRow(); + + // LEFT: Configuration + ImGui::TableSetColumnIndex(0); + ImGui::BeginChild("ConfigSection", ImVec2(0, 0), false); + DrawConfigurationPanel(); + ImGui::EndChild(); + + // CENTER: System Prompt Editor + ImGui::TableSetColumnIndex(1); + ImGui::BeginChild("PromptSection", ImVec2(0, 0), false); + DrawPromptEditorPanel(); + ImGui::EndChild(); + + // RIGHT: Bot Profiles List + ImGui::TableSetColumnIndex(2); + ImGui::BeginChild("ProfilesSection", ImVec2(0, 0), false); + DrawBotProfilesPanel(); + ImGui::EndChild(); + + ImGui::EndTable(); + } + ImGui::EndTabItem(); } - // System Prompts Tab - if (ImGui::BeginTabItem(ICON_MD_EDIT " System Prompts")) { + // Session Manager Tab (combines History + Metrics) + if (ImGui::BeginTabItem(ICON_MD_HISTORY " Sessions & History")) { ImGui::Spacing(); - DrawPromptEditorPanel(); - ImGui::EndTabItem(); - } - - // Bot Profiles Tab - if (ImGui::BeginTabItem(ICON_MD_FOLDER " Bot Profiles")) { - ImGui::Spacing(); - DrawBotProfilesPanel(); - ImGui::EndTabItem(); - } - - // Chat History Tab - if (ImGui::BeginTabItem(ICON_MD_HISTORY " Chat History")) { - ImGui::Spacing(); - DrawChatHistoryViewer(); - ImGui::EndTabItem(); - } - - // Metrics Tab - if (ImGui::BeginTabItem(ICON_MD_ANALYTICS " Metrics")) { - ImGui::Spacing(); - DrawAdvancedMetricsPanel(); + + // Two-column layout + if (ImGui::BeginTable("SessionLayout", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) { + ImGui::TableSetupColumn("History", ImGuiTableColumnFlags_WidthStretch, 0.6f); + ImGui::TableSetupColumn("Metrics", ImGuiTableColumnFlags_WidthStretch, 0.4f); + ImGui::TableNextRow(); + + // LEFT: Chat History + ImGui::TableSetColumnIndex(0); + DrawChatHistoryViewer(); + + // RIGHT: Metrics + ImGui::TableSetColumnIndex(1); + DrawAdvancedMetricsPanel(); + + ImGui::EndTable(); + } + ImGui::EndTabItem(); } @@ -469,13 +494,13 @@ void AgentEditor::DrawMetricsPanel() { } void AgentEditor::DrawPromptEditorPanel() { - ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_EDIT " System Prompt Editor"); + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_EDIT " Prompt Editor"); ImGui::Separator(); ImGui::Spacing(); - // Prompt file selector - ImGui::Text("Active Prompt:"); - ImGui::SameLine(); + // Compact prompt file selector + ImGui::Text("File:"); + ImGui::SetNextItemWidth(-45); if (ImGui::BeginCombo("##prompt_file", active_prompt_file_.c_str())) { if (ImGui::Selectable("system_prompt.txt", active_prompt_file_ == "system_prompt.txt")) { active_prompt_file_ = "system_prompt.txt"; @@ -493,30 +518,34 @@ void AgentEditor::DrawPromptEditorPanel() { } ImGui::SameLine(); - if (ImGui::Button(ICON_MD_REFRESH " Reload")) { + if (ImGui::SmallButton(ICON_MD_REFRESH)) { prompt_editor_initialized_ = false; } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Reload from disk"); + } // Load prompt file if not initialized if (!prompt_editor_initialized_ && prompt_editor_) { - // Build path to assets/agent directory - std::filesystem::path base_path; -#ifdef __APPLE__ - base_path = std::filesystem::path(yaze::util::GetBundleResourcePath()) / "Contents" / "Resources" / "agent"; -#else - base_path = std::filesystem::path("assets") / "agent"; -#endif + std::string asset_path = "agent/" + active_prompt_file_; + auto content_result = core::AssetLoader::LoadTextFile(asset_path); - std::filesystem::path prompt_path = base_path / active_prompt_file_; - if (std::filesystem::exists(prompt_path)) { - std::ifstream file(prompt_path); - if (file.is_open()) { - std::string content((std::istreambuf_iterator(file)), - std::istreambuf_iterator()); - prompt_editor_->SetText(content); - current_profile_.system_prompt = content; - prompt_editor_initialized_ = true; + if (content_result.ok()) { + prompt_editor_->SetText(*content_result); + current_profile_.system_prompt = *content_result; + prompt_editor_initialized_ = true; + } else { + // Only show error on first attempt (don't spam) + static bool error_shown = false; + if (!error_shown && toast_manager_) { + toast_manager_->Show( + absl::StrFormat("Prompt file not found: %s", active_prompt_file_), + ToastType::kWarning, 3.0f); + error_shown = true; } + // Set placeholder text + prompt_editor_->SetText("# System prompt file not found\n# Please check assets/agent/ directory"); + prompt_editor_initialized_ = true; // Don't retry every frame } } diff --git a/src/app/editor/editor_manager.cc b/src/app/editor/editor_manager.cc index 9f8da319..9c30e08e 100644 --- a/src/app/editor/editor_manager.cc +++ b/src/app/editor/editor_manager.cc @@ -329,6 +329,47 @@ void EditorManager::Initialize(const std::string& filename) { return absl::OkStatus(); }; agent_editor_.GetChatWidget()->SetMultimodalCallbacks(multimodal_callbacks); + + // Set up Z3ED command callbacks for proposal management + AgentChatWidget::Z3EDCommandCallbacks z3ed_callbacks; + + z3ed_callbacks.accept_proposal = [this](const std::string& proposal_id) -> absl::Status { + // Use ProposalDrawer's existing logic + proposal_drawer_.Show(); + proposal_drawer_.FocusProposal(proposal_id); + + toast_manager_.Show( + absl::StrFormat("%s View proposal %s in drawer to accept", ICON_MD_PREVIEW, proposal_id), + ToastType::kInfo, 3.5f); + + return absl::OkStatus(); + }; + + z3ed_callbacks.reject_proposal = [this](const std::string& proposal_id) -> absl::Status { + // Use ProposalDrawer's existing logic + proposal_drawer_.Show(); + proposal_drawer_.FocusProposal(proposal_id); + + toast_manager_.Show( + absl::StrFormat("%s View proposal %s in drawer to reject", ICON_MD_PREVIEW, proposal_id), + ToastType::kInfo, 3.0f); + + return absl::OkStatus(); + }; + + z3ed_callbacks.list_proposals = []() -> absl::StatusOr> { + // Return empty for now - ProposalDrawer handles the real list + return std::vector{}; + }; + + z3ed_callbacks.diff_proposal = [this](const std::string& proposal_id) -> absl::StatusOr { + // Open drawer to show diff + proposal_drawer_.Show(); + proposal_drawer_.FocusProposal(proposal_id); + return "See diff in proposal drawer"; + }; + + agent_editor_.GetChatWidget()->SetZ3EDCommandCallbacks(z3ed_callbacks); #endif // Load critical user settings first diff --git a/src/app/editor/system/agent_chat_history_popup.cc b/src/app/editor/system/agent_chat_history_popup.cc index d0fedaa2..dc2ea380 100644 --- a/src/app/editor/system/agent_chat_history_popup.cc +++ b/src/app/editor/system/agent_chat_history_popup.cc @@ -1,5 +1,7 @@ #include "app/editor/system/agent_chat_history_popup.h" +#include + #include "absl/strings/str_format.h" #include "absl/time/time.h" #include "imgui/imgui.h" @@ -14,15 +16,20 @@ namespace { const ImVec4 kUserColor = ImVec4(0.88f, 0.76f, 0.36f, 1.0f); const ImVec4 kAgentColor = ImVec4(0.56f, 0.82f, 0.62f, 1.0f); const ImVec4 kTimestampColor = ImVec4(0.6f, 0.6f, 0.6f, 1.0f); +const ImVec4 kAccentColor = ImVec4(0.196f, 0.6f, 0.8f, 1.0f); +const ImVec4 kBackgroundColor = ImVec4(0.08f, 0.08f, 0.12f, 0.98f); +const ImVec4 kHeaderColor = ImVec4(0.12f, 0.14f, 0.18f, 1.0f); } // namespace -AgentChatHistoryPopup::AgentChatHistoryPopup() {} +AgentChatHistoryPopup::AgentChatHistoryPopup() { + std::memset(input_buffer_, 0, sizeof(input_buffer_)); +} void AgentChatHistoryPopup::Draw() { if (!visible_) return; - // Set drawer position on the LEFT side + // Set drawer position on the LEFT side with beautiful styling ImGuiIO& io = ImGui::GetIO(); ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); ImGui::SetNextWindowSize(ImVec2(drawer_width_, io.DisplaySize.y), @@ -30,61 +37,30 @@ void AgentChatHistoryPopup::Draw() { ImGuiWindowFlags flags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoCollapse; + ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoTitleBar; - ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.08f, 0.10f, 0.95f)); + // Beautiful gradient background + ImGui::PushStyleColor(ImGuiCol_WindowBg, kBackgroundColor); + ImGui::PushStyleColor(ImGuiCol_Border, kAccentColor); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12, 12)); - if (ImGui::Begin(ICON_MD_CHAT " Chat History", &visible_, flags)) { - // Header with controls - if (ImGui::Button(ICON_MD_OPEN_IN_NEW " Open Full Chat")) { - if (open_chat_callback_) { - open_chat_callback_(); - } - } + if (ImGui::Begin("##AgentChatPopup", &visible_, flags)) { + // Animated header pulse + header_pulse_ += io.DeltaTime * 2.0f; + float pulse = 0.5f + 0.5f * sinf(header_pulse_); - ImGui::SameLine(); - if (ImGui::Button(ICON_MD_REFRESH " Refresh")) { - // Trigger external refresh through callback - if (toast_manager_) { - toast_manager_->Show("Refreshing chat history...", ToastType::kInfo, 1.5f); - } - } + DrawHeader(); ImGui::Separator(); - - // Filter dropdown - const char* filter_labels[] = {"All Messages", "User Only", "Agent Only"}; - int current_filter = static_cast(message_filter_); - ImGui::SetNextItemWidth(-1); - if (ImGui::Combo("##filter", ¤t_filter, filter_labels, 3)) { - message_filter_ = static_cast(current_filter); - } - ImGui::Spacing(); - // Auto-scroll checkbox - ImGui::Checkbox("Auto-scroll", &auto_scroll_); + // Message list with gradient background + float list_height = io.DisplaySize.y - 280.0f; // Reserve space for input and actions - ImGui::Separator(); - - // Message count indicator - int visible_count = 0; - for (const auto& msg : messages_) { - if (msg.is_internal) continue; - if (message_filter_ == MessageFilter::kUserOnly && - msg.sender != cli::agent::ChatMessage::Sender::kUser) continue; - if (message_filter_ == MessageFilter::kAgentOnly && - msg.sender != cli::agent::ChatMessage::Sender::kAgent) continue; - visible_count++; - } - - ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), - "%d message%s", visible_count, visible_count == 1 ? "" : "s"); - - ImGui::Separator(); - - // Message list - ImGui::BeginChild("MessageList", ImVec2(0, -45), true); + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.05f, 0.05f, 0.08f, 0.95f)); + ImGui::BeginChild("MessageList", ImVec2(0, list_height), true, ImGuiWindowFlags_AlwaysVerticalScrollbar); DrawMessageList(); if (needs_scroll_) { @@ -93,14 +69,23 @@ void AgentChatHistoryPopup::Draw() { } ImGui::EndChild(); + ImGui::PopStyleColor(); - // Action buttons at bottom - ImGui::Separator(); - DrawActionButtons(); + ImGui::Spacing(); + + // Quick actions bar + if (show_quick_actions_) { + DrawQuickActions(); + ImGui::Spacing(); + } + + // Input section at bottom + DrawInputSection(); } ImGui::End(); - ImGui::PopStyleColor(); + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(2); } void AgentChatHistoryPopup::DrawMessageList() { @@ -175,13 +160,193 @@ void AgentChatHistoryPopup::DrawMessage(const cli::agent::ChatMessage& msg, int ImGui::PopID(); } -void AgentChatHistoryPopup::DrawActionButtons() { - if (ImGui::Button(ICON_MD_DELETE " Clear History", ImVec2(-1, 0))) { - ClearHistory(); +void AgentChatHistoryPopup::DrawHeader() { + // Beautiful header with gradient accent + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 header_start = ImGui::GetCursorScreenPos(); + ImVec2 header_size(ImGui::GetContentRegionAvail().x, 60); + + // Gradient background + ImU32 color_top = ImGui::GetColorU32(ImVec4(0.15f, 0.18f, 0.22f, 1.0f)); + ImU32 color_bottom = ImGui::GetColorU32(kHeaderColor); + draw_list->AddRectFilledMultiColor( + header_start, + ImVec2(header_start.x + header_size.x, header_start.y + header_size.y), + color_top, color_top, color_bottom, color_bottom); + + // Accent line with pulse effect + float pulse = 0.7f + 0.3f * sinf(header_pulse_); + ImVec4 accent_pulse = ImVec4(kAccentColor.x, kAccentColor.y, kAccentColor.z, pulse); + ImU32 accent_color = ImGui::GetColorU32(accent_pulse); + draw_list->AddLine( + ImVec2(header_start.x, header_start.y + header_size.y), + ImVec2(header_start.x + header_size.x, header_start.y + header_size.y), + accent_color, 3.0f); + + ImGui::Dummy(ImVec2(0, 10)); + + // Title with icon + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); // Default font (bold if available) + ImGui::TextColored(kAccentColor, "%s AI Chat", ICON_MD_CHAT); + ImGui::PopFont(); + + ImGui::SameLine(ImGui::GetContentRegionAvail().x - 130); + + // Compact mode toggle + if (ImGui::SmallButton(compact_mode_ ? ICON_MD_UNFOLD_MORE : ICON_MD_UNFOLD_LESS)) { + compact_mode_ = !compact_mode_; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip(compact_mode_ ? "Expand view" : "Compact view"); } + 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("Clear all messages from the popup view\n(Full history preserved in chat window)"); + ImGui::SetTooltip("Open full chat window"); + } + + // Message count with badge + int visible_count = 0; + for (const auto& msg : messages_) { + if (msg.is_internal) continue; + if (message_filter_ == MessageFilter::kUserOnly && + msg.sender != cli::agent::ChatMessage::Sender::kUser) continue; + if (message_filter_ == MessageFilter::kAgentOnly && + msg.sender != cli::agent::ChatMessage::Sender::kAgent) continue; + visible_count++; + } + + ImGui::Spacing(); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 0.9f), + "%d message%s", visible_count, visible_count == 1 ? "" : "s"); + + ImGui::Dummy(ImVec2(0, 5)); +} + +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 + if (ImGui::Button(ICON_MD_CAMERA, ImVec2(button_width, 32))) { + if (capture_snapshot_callback_) { + capture_snapshot_callback_(); + } + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Capture screenshot for Gemini analysis"); + } + + ImGui::SameLine(); + + // Filter button + const char* filter_icon = ICON_MD_FILTER_LIST; + if (ImGui::Button(filter_icon, ImVec2(button_width, 32))) { + ImGui::OpenPopup("FilterPopup"); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Filter messages"); + } + + // Filter popup + if (ImGui::BeginPopup("FilterPopup")) { + if (ImGui::Selectable("All Messages", message_filter_ == MessageFilter::kAll)) { + message_filter_ = MessageFilter::kAll; + } + if (ImGui::Selectable("User Only", message_filter_ == MessageFilter::kUserOnly)) { + message_filter_ = MessageFilter::kUserOnly; + } + if (ImGui::Selectable("Agent Only", message_filter_ == MessageFilter::kAgentOnly)) { + message_filter_ = MessageFilter::kAgentOnly; + } + ImGui::EndPopup(); + } + + ImGui::SameLine(); + + // Clear button + if (ImGui::Button(ICON_MD_DELETE, ImVec2(button_width, 32))) { + ClearHistory(); + } + 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); + + bool send_message = false; + ImGui::SetNextItemWidth(-1); + if (ImGui::InputTextMultiline("##popup_input", input_buffer_, sizeof(input_buffer_), + ImVec2(-1, 60), + ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CtrlEnterForNewLine)) { + send_message = true; + } + + // Focus input on first show + if (focus_input_) { + ImGui::SetKeyboardFocusHere(-1); + focus_input_ = false; + } + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(3); + + // Send button with gradient + 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 (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) { + if (send_message_callback_) { + send_message_callback_(message); + + if (toast_manager_) { + toast_manager_->Show(ICON_MD_SEND " Message sent", ToastType::kSuccess, 1.5f); + } + + // Auto-scroll to see response + needs_scroll_ = true; } } diff --git a/src/app/editor/system/agent_chat_history_popup.h b/src/app/editor/system/agent_chat_history_popup.h index c095af14..d593acca 100644 --- a/src/app/editor/system/agent_chat_history_popup.h +++ b/src/app/editor/system/agent_chat_history_popup.h @@ -55,12 +55,27 @@ class AgentChatHistoryPopup { void SetOpenChatCallback(OpenChatCallback callback) { open_chat_callback_ = std::move(callback); } + + // Set callback for sending messages + using SendMessageCallback = std::function; + void SetSendMessageCallback(SendMessageCallback callback) { + send_message_callback_ = std::move(callback); + } + + // Set callback for capturing snapshots + using CaptureSnapshotCallback = std::function; + void SetCaptureSnapshotCallback(CaptureSnapshotCallback callback) { + capture_snapshot_callback_ = std::move(callback); + } private: + void DrawHeader(); void DrawMessageList(); void DrawMessage(const cli::agent::ChatMessage& msg, int index); - void DrawActionButtons(); + void DrawInputSection(); + void DrawQuickActions(); + void SendMessage(const std::string& message); void ClearHistory(); void ExportHistory(); void ScrollToBottom(); @@ -68,15 +83,21 @@ class AgentChatHistoryPopup { bool visible_ = false; bool needs_scroll_ = false; bool auto_scroll_ = true; + bool compact_mode_ = true; + bool show_quick_actions_ = true; // History state std::vector messages_; int display_limit_ = 50; // Show last 50 messages + // Input state + char input_buffer_[512] = {}; + bool focus_input_ = false; + // UI state - float drawer_width_ = 400.0f; + float drawer_width_ = 420.0f; float min_drawer_width_ = 300.0f; - float max_drawer_width_ = 600.0f; + float max_drawer_width_ = 700.0f; bool is_resizing_ = false; // Filter state @@ -87,9 +108,15 @@ class AgentChatHistoryPopup { }; MessageFilter message_filter_ = MessageFilter::kAll; + // Visual state + float header_pulse_ = 0.0f; + int unread_count_ = 0; + // Dependencies ToastManager* toast_manager_ = nullptr; OpenChatCallback open_chat_callback_; + SendMessageCallback send_message_callback_; + CaptureSnapshotCallback capture_snapshot_callback_; }; } // namespace editor