From a7c384a37dd8215b577f8413f48ba84086606efb Mon Sep 17 00:00:00 2001 From: scawful Date: Sun, 5 Oct 2025 12:08:11 -0400 Subject: [PATCH] feat: Enhance AgentChatWidget with Improved Message Handling and UI Updates - Updated the AgentChatWidget to manage AI response states, including a new mechanism for handling pending messages and a thinking animation indicator. - Improved the initialization of embedded labels for resource tools, ensuring labels are loaded only when necessary, enhancing performance and user experience. - Refined the chat history synchronization process to ensure timely updates in the chat history popup. - Enhanced the UI layout for better readability and interaction, including adjustments to button styles and spacing for a more polished appearance. --- src/app/editor/agent/agent_chat_widget.cc | 933 ++++++++++-------- src/app/editor/agent/agent_chat_widget.h | 5 + src/cli/handlers/agent/tool_commands.cc | 19 +- .../resources/resource_context_builder.cc | 6 +- 4 files changed, 561 insertions(+), 402 deletions(-) diff --git a/src/app/editor/agent/agent_chat_widget.cc b/src/app/editor/agent/agent_chat_widget.cc index e058a53d..021cf3f8 100644 --- a/src/app/editor/agent/agent_chat_widget.cc +++ b/src/app/editor/agent/agent_chat_widget.cc @@ -4,27 +4,27 @@ #include #include #include +#include #include #include #include #include -#include #include "absl/status/status.h" #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" #include "absl/time/clock.h" #include "absl/time/time.h" -#include "util/file_util.h" +#include "app/core/project.h" #include "app/editor/agent/agent_chat_history_codec.h" -#include "app/editor/system/proposal_drawer.h" #include "app/editor/system/agent_chat_history_popup.h" +#include "app/editor/system/proposal_drawer.h" #include "app/editor/system/toast_manager.h" #include "app/gui/icons.h" -#include "app/core/project.h" #include "app/rom.h" #include "imgui/imgui.h" #include "imgui/misc/cpp/imgui_stdlib.h" +#include "util/file_util.h" namespace { @@ -106,7 +106,7 @@ 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"); @@ -115,28 +115,32 @@ AgentChatWidget::AgentChatWidget() { } void AgentChatWidget::SetRomContext(Rom* rom) { - agent_service_.SetRomContext(rom); + // Track if we've already initialized labels for this ROM instance + static Rom* last_rom_initialized = nullptr; - // ALWAYS initialize embedded labels for resource tools (default Zelda3 labels) - if (rom && rom->is_loaded() && rom->resource_label()) { - if (!rom->resource_label()->labels_loaded_) { - core::YazeProject project; - - // Initialize embedded default labels (all Zelda3 resources) - auto labels_status = project.InitializeEmbeddedLabels(); - if (labels_status.ok()) { - rom->resource_label()->labels_ = project.resource_labels; - rom->resource_label()->labels_loaded_ = true; - - if (toast_manager_) { - toast_manager_->Show( - ICON_MD_CHECK_CIRCLE " Default Zelda3 labels loaded for AI tools", - ToastType::kSuccess, 2.5f); - } - } else if (toast_manager_) { + agent_service_.SetRomContext(rom); + + // Only initialize labels ONCE per ROM instance + if (rom && rom->is_loaded() && rom->resource_label() && last_rom_initialized != rom) { + core::YazeProject project; + project.use_embedded_labels = true; + + auto labels_status = project.InitializeEmbeddedLabels(); + + if (labels_status.ok()) { + rom->resource_label()->labels_ = project.resource_labels; + rom->resource_label()->labels_loaded_ = true; + last_rom_initialized = rom; // Mark as initialized + + int total_count = 0; + for (const auto& [category, labels] : project.resource_labels) { + total_count += labels.size(); + } + + if (toast_manager_) { toast_manager_->Show( - ICON_MD_WARNING " Warning: Could not load default labels", - ToastType::kWarning, 3.0f); + absl::StrFormat(ICON_MD_CHECK_CIRCLE " %d labels ready for AI", total_count), + ToastType::kSuccess, 2.0f); } } } @@ -156,22 +160,23 @@ void AgentChatWidget::SetProposalDrawer(ProposalDrawer* drawer) { void AgentChatWidget::SetChatHistoryPopup(AgentChatHistoryPopup* popup) { chat_history_popup_ = popup; - - if (!chat_history_popup_) return; - + + if (!chat_history_popup_) + return; + // Set up callback to open this chat window - chat_history_popup_->SetOpenChatCallback([this]() { - this->set_active(true); - }); - + 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(); - }); - + 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) { @@ -181,7 +186,8 @@ void AgentChatWidget::SetChatHistoryPopup(AgentChatHistoryPopup* popup) { 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); + toast_manager_->Show(ICON_MD_PHOTO " Screenshot captured", + ToastType::kSuccess, 2.5f); } } else if (toast_manager_) { toast_manager_->Show( @@ -190,7 +196,7 @@ void AgentChatWidget::SetChatHistoryPopup(AgentChatHistoryPopup* popup) { } } }); - + // Initial sync SyncHistoryToPopup(); } @@ -294,7 +300,7 @@ void AgentChatWidget::PersistHistory() { snapshot.collaboration.active = collaboration_state_.active; snapshot.collaboration.session_id = collaboration_state_.session_id; snapshot.collaboration.session_name = collaboration_state_.session_name; - + // Sync to popup when persisting SyncHistoryToPopup(); snapshot.collaboration.participants = collaboration_state_.participants; @@ -397,7 +403,7 @@ void AgentChatWidget::HandleAgentResponse( NotifyProposalCreated(message, total); } last_proposal_count_ = std::max(last_proposal_count_, total); - + // Sync history to popup after response SyncHistoryToPopup(); } @@ -407,7 +413,7 @@ void AgentChatWidget::RenderMessage(const ChatMessage& msg, int index) { if (msg.is_internal) { return; } - + ImGui::PushID(index); const bool from_user = (msg.sender == ChatMessage::Sender::kUser); @@ -431,10 +437,10 @@ void AgentChatWidget::RenderMessage(const ChatMessage& msg, int index) { copy_text = *msg.json_pretty; } ImGui::SetClipboardText(copy_text.c_str()); - if (toast_manager_) { + if (toast_manager_) { toast_manager_->Show("Message copied", ToastType::kSuccess, 2.0f); - } } + } ImGui::PopStyleColor(2); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Copy to clipboard"); @@ -524,10 +530,12 @@ void AgentChatWidget::RenderHistory() { ImGui::SetCursorPosX((avail.x - text_size.x) / 2); ImGui::SetCursorPosY((avail.y - text_size.y) / 2); ImGui::TextDisabled(ICON_MD_CHAT " No messages yet"); - ImGui::SetCursorPosX((avail.x - ImGui::CalcTextSize("Start typing below to begin").x) / 2); + ImGui::SetCursorPosX( + (avail.x - ImGui::CalcTextSize("Start typing below to begin").x) / 2); ImGui::TextDisabled("Start typing below to begin"); } else { - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8, 12)); // More spacing between messages + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, + ImVec2(8, 12)); // More spacing between messages for (size_t index = 0; index < history.size(); ++index) { RenderMessage(history[index], static_cast(index)); } @@ -545,7 +553,8 @@ void AgentChatWidget::RenderHistory() { void AgentChatWidget::RenderInputBox() { ImGui::Separator(); - ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_EDIT " Message:"); + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), + ICON_MD_EDIT " Message:"); bool submitted = ImGui::InputTextMultiline( "##agent_input", input_buffer_, sizeof(input_buffer_), ImVec2(-1, 60.0f), @@ -560,20 +569,27 @@ void AgentChatWidget::RenderInputBox() { } ImGui::Spacing(); - + // Send button row ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.5f, 0.7f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.196f, 0.6f, 0.8f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(0.196f, 0.6f, 0.8f, 1.0f)); if (ImGui::Button(absl::StrFormat("%s Send", ICON_MD_SEND).c_str(), ImVec2(140, 0)) || send) { - if (std::strlen(input_buffer_) > 0) { + if (std::strlen(input_buffer_) > 0 && !waiting_for_response_) { history_dirty_ = true; EnsureHistoryLoaded(); - auto response = agent_service_.SendMessage(input_buffer_); + pending_message_ = input_buffer_; + waiting_for_response_ = true; memset(input_buffer_, 0, sizeof(input_buffer_)); + + // Send in next frame to avoid blocking + // For now, send synchronously but show thinking indicator + auto response = agent_service_.SendMessage(pending_message_); HandleAgentResponse(response); PersistHistory(); + waiting_for_response_ = false; ImGui::SetKeyboardFocusHere(-1); } } @@ -584,10 +600,11 @@ void AgentChatWidget::RenderInputBox() { ImGui::SameLine(); ImGui::TextDisabled(ICON_MD_INFO " Ctrl+Enter: send • Enter: newline"); - + // Action buttons row below ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.5f, 0.0f, 0.7f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 0.843f, 0.0f, 0.9f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(1.0f, 0.843f, 0.0f, 0.9f)); if (ImGui::SmallButton(ICON_MD_DELETE_FOREVER " Clear")) { agent_service_.ResetConversation(); if (toast_manager_) { @@ -598,10 +615,11 @@ void AgentChatWidget::RenderInputBox() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Clear all messages from conversation"); } - + ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.35f, 0.6f, 0.7f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.502f, 0.0f, 0.502f, 0.9f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(0.502f, 0.0f, 0.502f, 0.9f)); if (ImGui::SmallButton(ICON_MD_PREVIEW " Proposals")) { if (proposal_drawer_) { // Focus proposal drawer @@ -611,11 +629,12 @@ void AgentChatWidget::RenderInputBox() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("View code proposals"); } - + // Multimodal Vision controls integrated ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.5f, 0.7f, 0.7f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.196f, 0.6f, 0.8f, 0.9f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(0.196f, 0.6f, 0.8f, 0.9f)); if (ImGui::SmallButton(ICON_MD_PHOTO_CAMERA " Capture")) { // Quick capture with current mode if (multimodal_callbacks_.capture_snapshot) { @@ -627,7 +646,9 @@ void AgentChatWidget::RenderInputBox() { toast_manager_->Show("Screenshot captured", ToastType::kSuccess); } } else if (toast_manager_) { - toast_manager_->Show(absl::StrFormat("Capture failed: %s", status.message()), ToastType::kError); + toast_manager_->Show( + absl::StrFormat("Capture failed: %s", status.message()), + ToastType::kError); } } } @@ -635,10 +656,11 @@ void AgentChatWidget::RenderInputBox() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Capture screenshot for vision analysis"); } - + ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.5f, 0.3f, 0.3f, 0.7f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.863f, 0.078f, 0.235f, 0.9f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(0.863f, 0.078f, 0.235f, 0.9f)); if (ImGui::SmallButton(ICON_MD_STOP " Stop")) { // Stop generation (if implemented) if (toast_manager_) { @@ -649,10 +671,11 @@ void AgentChatWidget::RenderInputBox() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Stop current generation"); } - + ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.5f, 0.3f, 0.7f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.133f, 0.545f, 0.133f, 0.9f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(0.133f, 0.545f, 0.133f, 0.9f)); if (ImGui::SmallButton(ICON_MD_SAVE " Export")) { // Export conversation if (toast_manager_) { @@ -663,23 +686,29 @@ void AgentChatWidget::RenderInputBox() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Export conversation history"); } - + // Vision prompt (inline when image captured) if (multimodal_state_.last_capture_path.has_value()) { ImGui::Spacing(); - ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f), ICON_MD_IMAGE " Vision prompt:"); + ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f), + ICON_MD_IMAGE " Vision prompt:"); ImGui::SetNextItemWidth(-200); - ImGui::InputTextWithHint("##quick_vision_prompt", "Ask about the screenshot...", - multimodal_prompt_buffer_, sizeof(multimodal_prompt_buffer_)); + ImGui::InputTextWithHint( + "##quick_vision_prompt", "Ask about the screenshot...", + multimodal_prompt_buffer_, sizeof(multimodal_prompt_buffer_)); ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.5f, 0.7f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.196f, 0.6f, 0.8f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(0.196f, 0.6f, 0.8f, 1.0f)); if (ImGui::Button(ICON_MD_SEND " Analyze##vision_send", ImVec2(180, 0))) { - if (multimodal_callbacks_.send_to_gemini && !multimodal_state_.last_capture_path->empty()) { + if (multimodal_callbacks_.send_to_gemini && + !multimodal_state_.last_capture_path->empty()) { std::string prompt = multimodal_prompt_buffer_; - auto status = multimodal_callbacks_.send_to_gemini(*multimodal_state_.last_capture_path, prompt); + auto status = multimodal_callbacks_.send_to_gemini( + *multimodal_state_.last_capture_path, prompt); if (status.ok() && toast_manager_) { - toast_manager_->Show("Vision analysis requested", ToastType::kSuccess); + toast_manager_->Show("Vision analysis requested", + ToastType::kSuccess); } } } @@ -699,7 +728,7 @@ void AgentChatWidget::Draw() { ImGui::SetNextWindowSize(ImVec2(1400, 1000), ImGuiCond_FirstUseEver); ImGui::Begin(title_.c_str(), &active_, ImGuiWindowFlags_MenuBar); - + // Simplified menu bar if (ImGui::BeginMenuBar()) { if (ImGui::BeginMenu(ICON_MD_MENU " Actions")) { @@ -721,7 +750,8 @@ void AgentChatWidget::Draw() { ImGui::Separator(); if (ImGui::MenuItem(ICON_MD_SAVE " Export History")) { if (toast_manager_) { - toast_manager_->Show("Export not yet implemented", ToastType::kWarning); + toast_manager_->Show("Export not yet implemented", + ToastType::kWarning); } } ImGui::Separator(); @@ -740,7 +770,7 @@ void AgentChatWidget::Draw() { } ImGui::EndMenu(); } - + // Session tabs in menu bar (if multiple sessions) if (!chat_sessions_.empty() && chat_sessions_.size() > 1) { ImGui::Separator(); @@ -749,7 +779,8 @@ void AgentChatWidget::Draw() { 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)) { + if (ImGui::MenuItem(chat_sessions_[i].name.c_str(), nullptr, + is_active)) { active_session_index_ = i; history_loaded_ = false; // Trigger reload SyncHistoryToPopup(); @@ -759,76 +790,89 @@ void AgentChatWidget::Draw() { } } } - + ImGui::EndMenuBar(); } - + // Update reactive status color - collaboration_status_color_ = collaboration_state_.active ? - ImVec4(0.133f, 0.545f, 0.133f, 1.0f) : ImVec4(0.6f, 0.6f, 0.6f, 1.0f); - + collaboration_status_color_ = collaboration_state_.active + ? ImVec4(0.133f, 0.545f, 0.133f, 1.0f) + : ImVec4(0.6f, 0.6f, 0.6f, 1.0f); + // Connection status bar at top (compact) 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, + 55); // Reduced from 75 to 55 + // Gradient background ImU32 color_top = ImGui::GetColorU32(ImVec4(0.18f, 0.22f, 0.28f, 1.0f)); ImU32 color_bottom = ImGui::GetColorU32(ImVec4(0.12f, 0.16f, 0.22f, 1.0f)); - draw_list->AddRectFilledMultiColor(bar_start, - ImVec2(bar_start.x + bar_size.x, bar_start.y + bar_size.y), + draw_list->AddRectFilledMultiColor( + bar_start, ImVec2(bar_start.x + bar_size.x, bar_start.y + bar_size.y), color_top, color_top, color_bottom, color_bottom); - + // Colored accent bar based on provider - ImVec4 accent_color = (agent_config_.ai_provider == "ollama") ? ImVec4(0.2f, 0.8f, 0.4f, 1.0f) : - (agent_config_.ai_provider == "gemini") ? ImVec4(0.196f, 0.6f, 0.8f, 1.0f) : - ImVec4(0.6f, 0.6f, 0.6f, 1.0f); - draw_list->AddRectFilled(bar_start, ImVec2(bar_start.x + bar_size.x, bar_start.y + 3), - ImGui::GetColorU32(accent_color)); - - ImGui::BeginChild("AgentChat_ConnectionBar", bar_size, false, ImGuiWindowFlags_NoScrollbar); + ImVec4 accent_color = (agent_config_.ai_provider == "ollama") + ? ImVec4(0.2f, 0.8f, 0.4f, 1.0f) + : (agent_config_.ai_provider == "gemini") + ? ImVec4(0.196f, 0.6f, 0.8f, 1.0f) + : ImVec4(0.6f, 0.6f, 0.6f, 1.0f); + draw_list->AddRectFilled(bar_start, + ImVec2(bar_start.x + bar_size.x, bar_start.y + 3), + ImGui::GetColorU32(accent_color)); + + ImGui::BeginChild("AgentChat_ConnectionBar", bar_size, false, + ImGuiWindowFlags_NoScrollbar); ImGui::PushID("ConnectionBar"); { // Center content vertically in the 55px bar float content_height = ImGui::GetFrameHeight(); 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; + 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"; + agent_config_.ai_provider = (current_provider == 0) ? "mock" + : (current_provider == 1) ? "ollama" + : "gemini"; // Auto-populate default models if (agent_config_.ai_provider == "ollama") { - strncpy(agent_config_.model_buffer, "qwen2.5-coder:7b", sizeof(agent_config_.model_buffer) - 1); + strncpy(agent_config_.model_buffer, "qwen2.5-coder:7b", + sizeof(agent_config_.model_buffer) - 1); agent_config_.ai_model = agent_config_.model_buffer; } else if (agent_config_.ai_provider == "gemini") { - strncpy(agent_config_.model_buffer, "gemini-2.5-flash", sizeof(agent_config_.model_buffer) - 1); + strncpy(agent_config_.model_buffer, "gemini-2.5-flash", + sizeof(agent_config_.model_buffer) - 1); agent_config_.ai_model = agent_config_.model_buffer; } } - + ImGui::SameLine(); if (agent_config_.ai_provider != "mock") { ImGui::SetNextItemWidth(150); - ImGui::InputTextWithHint("##main_model", "Model name...", agent_config_.model_buffer, sizeof(agent_config_.model_buffer)); + ImGui::InputTextWithHint("##main_model", "Model name...", + agent_config_.model_buffer, + sizeof(agent_config_.model_buffer)); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("AI model name"); } } - + // Gemini API key input ImGui::SameLine(); if (agent_config_.ai_provider == "gemini") { ImGui::SetNextItemWidth(200); - if (ImGui::InputTextWithHint("##main_api_key", "API Key (or load from env)...", - agent_config_.gemini_key_buffer, + if (ImGui::InputTextWithHint("##main_api_key", + "API Key (or load from env)...", + agent_config_.gemini_key_buffer, sizeof(agent_config_.gemini_key_buffer), ImGuiInputTextFlags_Password)) { agent_config_.gemini_api_key = agent_config_.gemini_key_buffer; @@ -836,24 +880,28 @@ void AgentChatWidget::Draw() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Gemini API Key (hidden)"); } - + ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.5f, 0.7f, 0.7f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.196f, 0.6f, 0.8f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(0.196f, 0.6f, 0.8f, 1.0f)); if (ImGui::SmallButton(ICON_MD_REFRESH)) { const char* gemini_key = nullptr; #ifdef _WIN32 char* env_key = nullptr; size_t len = 0; - if (_dupenv_s(&env_key, &len, "GEMINI_API_KEY") == 0 && env_key != nullptr) { - strncpy(agent_config_.gemini_key_buffer, env_key, sizeof(agent_config_.gemini_key_buffer) - 1); + if (_dupenv_s(&env_key, &len, "GEMINI_API_KEY") == 0 && + env_key != nullptr) { + strncpy(agent_config_.gemini_key_buffer, env_key, + sizeof(agent_config_.gemini_key_buffer) - 1); agent_config_.gemini_api_key = env_key; free(env_key); } #else gemini_key = std::getenv("GEMINI_API_KEY"); if (gemini_key) { - strncpy(agent_config_.gemini_key_buffer, gemini_key, sizeof(agent_config_.gemini_key_buffer) - 1); + strncpy(agent_config_.gemini_key_buffer, gemini_key, + sizeof(agent_config_.gemini_key_buffer) - 1); agent_config_.gemini_api_key = gemini_key; } #endif @@ -866,33 +914,40 @@ void AgentChatWidget::Draw() { ImGui::SetTooltip("Load from GEMINI_API_KEY"); } } - + ImGui::SameLine(); ImGui::Checkbox(ICON_MD_VISIBILITY, &agent_config_.show_reasoning); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Show reasoning"); } - + // Session management button ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.4f, 0.6f, 0.7f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.5f, 0.5f, 0.7f, 0.9f)); - if (ImGui::SmallButton(absl::StrFormat("%s %d", ICON_MD_TAB, static_cast(chat_sessions_.size())).c_str())) { + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(0.5f, 0.5f, 0.7f, 0.9f)); + if (ImGui::SmallButton( + absl::StrFormat("%s %d", ICON_MD_TAB, + static_cast(chat_sessions_.size())) + .c_str())) { ImGui::OpenPopup("SessionsPopup"); } ImGui::PopStyleColor(2); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Manage chat sessions"); } - + // Sessions popup if (ImGui::BeginPopup("SessionsPopup")) { - ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_TAB " Chat Sessions"); + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), + ICON_MD_TAB " Chat Sessions"); ImGui::Separator(); - + if (ImGui::Button(ICON_MD_ADD " New Session", ImVec2(200, 0))) { - std::string session_id = absl::StrFormat("session_%d", static_cast(chat_sessions_.size() + 1)); - std::string session_name = absl::StrFormat("Chat %d", static_cast(chat_sessions_.size() + 1)); + std::string session_id = absl::StrFormat( + "session_%d", static_cast(chat_sessions_.size() + 1)); + std::string session_name = absl::StrFormat( + "Chat %d", static_cast(chat_sessions_.size() + 1)); chat_sessions_.emplace_back(session_id, session_name); active_session_index_ = static_cast(chat_sessions_.size() - 1); if (toast_manager_) { @@ -900,7 +955,7 @@ void AgentChatWidget::Draw() { } ImGui::CloseCurrentPopup(); } - + if (!chat_sessions_.empty()) { ImGui::Spacing(); ImGui::TextDisabled("Active Sessions:"); @@ -908,9 +963,10 @@ void AgentChatWidget::Draw() { for (size_t i = 0; i < chat_sessions_.size(); ++i) { ImGui::PushID(static_cast(i)); bool is_active = (active_session_index_ == static_cast(i)); - if (ImGui::Selectable(absl::StrFormat("%s %s%s", ICON_MD_CHAT, - chat_sessions_[i].name, - is_active ? " (active)" : "").c_str(), + if (ImGui::Selectable(absl::StrFormat("%s %s%s", ICON_MD_CHAT, + chat_sessions_[i].name, + is_active ? " (active)" : "") + .c_str(), is_active)) { active_session_index_ = static_cast(i); ImGui::CloseCurrentPopup(); @@ -920,7 +976,7 @@ void AgentChatWidget::Draw() { } ImGui::EndPopup(); } - + // Session status (right side) if (collaboration_state_.active) { ImGui::SameLine(ImGui::GetContentRegionAvail().x - 25); @@ -929,64 +985,70 @@ void AgentChatWidget::Draw() { } ImGui::PopID(); ImGui::EndChild(); - + ImGui::Spacing(); - + // Main layout: Chat area (left, 70%) + Control panels (right, 30%) - if (ImGui::BeginTable("AgentChat_MainLayout", 2, - ImGuiTableFlags_Resizable | - ImGuiTableFlags_Reorderable | - ImGuiTableFlags_BordersInnerV | - ImGuiTableFlags_ContextMenuInBody)) { + if (ImGui::BeginTable("AgentChat_MainLayout", 2, + ImGuiTableFlags_Resizable | + ImGuiTableFlags_Reorderable | + ImGuiTableFlags_BordersInnerV | + ImGuiTableFlags_ContextMenuInBody)) { ImGui::TableSetupColumn("Chat", ImGuiTableColumnFlags_WidthStretch, 0.7f); - ImGui::TableSetupColumn("Controls", ImGuiTableColumnFlags_WidthStretch, 0.3f); - ImGui::TableHeadersRow(); - ImGui::TableNextRow(); - + ImGui::TableSetupColumn("Controls", ImGuiTableColumnFlags_WidthStretch, + 0.3f); + ImGui::TableHeadersRow(); + ImGui::TableNextRow(); + // LEFT: Chat area with ROM sync below ImGui::TableSetColumnIndex(0); ImGui::PushID("ChatColumn"); - + // Chat history and input (main area) RenderHistory(); RenderInputBox(); - + // ROM Sync inline below chat (when active) - if (collaboration_state_.active || !rom_sync_state_.current_rom_hash.empty()) { + if (collaboration_state_.active || + !rom_sync_state_.current_rom_hash.empty()) { ImGui::Spacing(); ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 4)); - ImGui::TextColored(ImVec4(1.0f, 0.647f, 0.0f, 1.0f), ICON_MD_SYNC " ROM Sync"); + ImGui::TextColored(ImVec4(1.0f, 0.647f, 0.0f, 1.0f), + ICON_MD_SYNC " ROM Sync"); ImGui::SameLine(); if (!rom_sync_state_.current_rom_hash.empty()) { - ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "%s", - rom_sync_state_.current_rom_hash.substr(0, 12).c_str()); + ImGui::TextColored( + ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "%s", + rom_sync_state_.current_rom_hash.substr(0, 12).c_str()); } ImGui::PopStyleVar(); } - + ImGui::PopID(); - + // RIGHT: Control panels (collapsible sections) ImGui::TableSetColumnIndex(1); ImGui::PushID("ControlsColumn"); ImGui::BeginChild("AgentChat_ControlPanels", ImVec2(0, 0), false); - + // All panels always visible (dense layout) - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6, 6)); // Tighter spacing - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4, 3)); // Compact padding - + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, + ImVec2(6, 6)); // Tighter spacing + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, + ImVec2(4, 3)); // Compact padding + RenderAgentConfigPanel(); RenderZ3EDCommandPanel(); RenderMultimodalPanel(); RenderCollaborationPanel(); RenderProposalManagerPanel(); - + ImGui::PopStyleVar(2); - + ImGui::EndChild(); ImGui::PopID(); - + ImGui::EndTable(); } @@ -995,73 +1057,82 @@ void AgentChatWidget::Draw() { void AgentChatWidget::RenderCollaborationPanel() { ImGui::PushID("CollabPanel"); - + // Update reactive status color based on connection state const bool connected = collaboration_state_.active; - collaboration_status_color_ = connected ? ImVec4(0.133f, 0.545f, 0.133f, 1.0f) : ImVec4(0.6f, 0.6f, 0.6f, 1.0f); - + collaboration_status_color_ = connected ? ImVec4(0.133f, 0.545f, 0.133f, 1.0f) + : ImVec4(0.6f, 0.6f, 0.6f, 1.0f); + if (!ImGui::CollapsingHeader(ICON_MD_PEOPLE " Collaboration & Network")) { ImGui::PopID(); return; } // Mode selector (compact inline) - ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_SETTINGS_ETHERNET " Mode:"); + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), + ICON_MD_SETTINGS_ETHERNET " Mode:"); ImGui::SameLine(); - ImGui::RadioButton(ICON_MD_FOLDER " Local##collab_mode_local", + ImGui::RadioButton(ICON_MD_FOLDER " Local##collab_mode_local", reinterpret_cast(&collaboration_state_.mode), static_cast(CollaborationMode::kLocal)); ImGui::SameLine(); - ImGui::RadioButton(ICON_MD_WIFI " Network##collab_mode_network", + ImGui::RadioButton(ICON_MD_WIFI " Network##collab_mode_network", reinterpret_cast(&collaboration_state_.mode), static_cast(CollaborationMode::kNetwork)); - + ImGui::Spacing(); // Main content in table layout (fixed size to prevent auto-resize) if (ImGui::BeginTable("Collab_MainTable", 2, ImGuiTableFlags_BordersInnerV)) { ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 180); - ImGui::TableSetupColumn("Controls", ImGuiTableColumnFlags_WidthFixed, - ImGui::GetContentRegionAvail().x - 180); + ImGui::TableSetupColumn("Controls", ImGuiTableColumnFlags_WidthFixed, + ImGui::GetContentRegionAvail().x - 180); ImGui::TableNextRow(); // LEFT COLUMN: Session Details ImGui::TableSetColumnIndex(0); ImGui::BeginGroup(); ImGui::PushID("StatusColumn"); - + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.15f, 0.2f, 0.18f, 0.4f)); ImGui::BeginChild("Collab_SessionDetails", ImVec2(0, 80), true); - ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_INFO " Session Status:"); + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), + ICON_MD_INFO " Session Status:"); ImGui::Spacing(); if (connected) { - ImGui::TextColored(collaboration_status_color_, ICON_MD_CHECK_CIRCLE " Connected"); + ImGui::TextColored(collaboration_status_color_, + ICON_MD_CHECK_CIRCLE " Connected"); } else { ImGui::TextDisabled(ICON_MD_CANCEL " Not connected"); } if (collaboration_state_.mode == CollaborationMode::kNetwork) { ImGui::Spacing(); - ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f), ICON_MD_CLOUD " Server:"); + ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f), + ICON_MD_CLOUD " Server:"); ImGui::TextWrapped("%s", collaboration_state_.server_url.c_str()); } if (!collaboration_state_.session_name.empty()) { ImGui::Spacing(); - ImGui::TextColored(collaboration_status_color_, ICON_MD_LABEL " Session:"); + ImGui::TextColored(collaboration_status_color_, + ICON_MD_LABEL " Session:"); ImGui::TextWrapped("%s", collaboration_state_.session_name.c_str()); } if (!collaboration_state_.session_id.empty()) { ImGui::Spacing(); - ImGui::TextColored(collaboration_status_color_, ICON_MD_KEY " Session Code:"); + ImGui::TextColored(collaboration_status_color_, + ICON_MD_KEY " Session Code:"); ImGui::TextWrapped("%s", collaboration_state_.session_id.c_str()); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.4f, 0.6f, 0.6f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.416f, 0.353f, 0.804f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(0.416f, 0.353f, 0.804f, 1.0f)); if (ImGui::Button(ICON_MD_CONTENT_COPY " Copy##copy_session_id")) { ImGui::SetClipboardText(collaboration_state_.session_id.c_str()); if (toast_manager_) { - toast_manager_->Show("Session code copied!", ToastType::kSuccess, 2.0f); + toast_manager_->Show("Session code copied!", ToastType::kSuccess, + 2.0f); } } ImGui::PopStyleColor(2); @@ -1069,12 +1140,14 @@ void AgentChatWidget::RenderCollaborationPanel() { if (collaboration_state_.last_synced != absl::InfinitePast()) { ImGui::Spacing(); - ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), ICON_MD_ACCESS_TIME " Last sync:"); + ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f), + ICON_MD_ACCESS_TIME " Last sync:"); ImGui::SameLine(); - ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), - "%s", absl::FormatTime("%H:%M:%S", collaboration_state_.last_synced, - absl::LocalTimeZone()) - .c_str()); + ImGui::TextColored( + ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "%s", + absl::FormatTime("%H:%M:%S", collaboration_state_.last_synced, + absl::LocalTimeZone()) + .c_str()); } ImGui::EndChild(); ImGui::PopStyleColor(); @@ -1085,23 +1158,25 @@ void AgentChatWidget::RenderCollaborationPanel() { ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.12f, 0.16f, 0.14f, 0.4f)); ImGui::BeginChild("Collab_ParticipantsList", ImVec2(0, 0), true); { - if (collaboration_state_.participants.empty()) { + if (collaboration_state_.participants.empty()) { ImGui::TextDisabled(ICON_MD_PEOPLE " No participants yet"); - } else { - ImGui::TextColored(collaboration_status_color_, ICON_MD_PEOPLE " Participants (%zu):", - collaboration_state_.participants.size()); - ImGui::Separator(); + } else { + ImGui::TextColored(collaboration_status_color_, + ICON_MD_PEOPLE " Participants (%zu):", + collaboration_state_.participants.size()); + ImGui::Separator(); for (size_t i = 0; i < collaboration_state_.participants.size(); ++i) { ImGui::PushID(static_cast(i)); - ImGui::BulletText(ICON_MD_PERSON " %s", collaboration_state_.participants[i].c_str()); + ImGui::BulletText(ICON_MD_PERSON " %s", + collaboration_state_.participants[i].c_str()); ImGui::PopID(); } } } ImGui::EndChild(); ImGui::PopStyleColor(); - - ImGui::PopID(); // StatusColumn + + ImGui::PopID(); // StatusColumn ImGui::EndGroup(); // RIGHT COLUMN: Controls @@ -1123,17 +1198,20 @@ void AgentChatWidget::RenderCollaborationPanel() { // Network mode: Show server URL input with styling if (collaboration_state_.mode == CollaborationMode::kNetwork) { - ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f), ICON_MD_CLOUD " Server URL:"); + ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f), + ICON_MD_CLOUD " Server URL:"); ImGui::SetNextItemWidth(-80); ImGui::InputText("##collab_server_url", server_url_buffer_, IM_ARRAYSIZE(server_url_buffer_)); ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.5f, 0.7f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.196f, 0.6f, 0.8f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(0.196f, 0.6f, 0.8f, 1.0f)); if (ImGui::Button(ICON_MD_LINK "##connect_server_btn")) { collaboration_state_.server_url = server_url_buffer_; if (toast_manager_) { - toast_manager_->Show("Connecting to server...", ToastType::kInfo, 3.0f); + toast_manager_->Show("Connecting to server...", ToastType::kInfo, + 3.0f); } } ImGui::PopStyleColor(2); @@ -1143,7 +1221,8 @@ void AgentChatWidget::RenderCollaborationPanel() { ImGui::Separator(); } - ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_ADD_CIRCLE " Host New Session:"); + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), + ICON_MD_ADD_CIRCLE " Host New Session:"); ImGui::SetNextItemWidth(-70); ImGui::InputTextWithHint("##collab_session_name", "Enter session name...", session_name_buffer_, @@ -1152,7 +1231,8 @@ void AgentChatWidget::RenderCollaborationPanel() { if (!can_host) ImGui::BeginDisabled(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.5f, 0.0f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 0.843f, 0.0f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(1.0f, 0.843f, 0.0f, 1.0f)); if (ImGui::Button(ICON_MD_ROCKET_LAUNCH "##host_session_btn")) { std::string name = session_name_buffer_; if (name.empty()) { @@ -1193,15 +1273,18 @@ void AgentChatWidget::RenderCollaborationPanel() { } ImGui::Spacing(); - ImGui::TextColored(ImVec4(0.133f, 0.545f, 0.133f, 1.0f), ICON_MD_LOGIN " Join Existing Session:"); + ImGui::TextColored(ImVec4(0.133f, 0.545f, 0.133f, 1.0f), + ICON_MD_LOGIN " Join Existing Session:"); ImGui::SetNextItemWidth(-70); - ImGui::InputTextWithHint("##collab_join_code", "Enter session code...", join_code_buffer_, + ImGui::InputTextWithHint("##collab_join_code", "Enter session code...", + join_code_buffer_, IM_ARRAYSIZE(join_code_buffer_)); ImGui::SameLine(); if (!can_join) ImGui::BeginDisabled(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.1f, 0.4f, 0.1f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.133f, 0.545f, 0.133f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(0.133f, 0.545f, 0.133f, 1.0f)); if (ImGui::Button(ICON_MD_MEETING_ROOM "##join_session_btn")) { std::string code = join_code_buffer_; if (code.empty()) { @@ -1243,12 +1326,14 @@ void AgentChatWidget::RenderCollaborationPanel() { if (collaboration_state_.active) { ImGui::Separator(); ImGui::Spacing(); - + if (!can_leave) ImGui::BeginDisabled(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.7f, 0.2f, 0.2f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.863f, 0.078f, 0.235f, 1.0f)); - if (ImGui::Button(ICON_MD_LOGOUT " Leave Session##leave_session_btn", ImVec2(-1, 0))) { + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(0.863f, 0.078f, 0.235f, 1.0f)); + if (ImGui::Button(ICON_MD_LOGOUT " Leave Session##leave_session_btn", + ImVec2(-1, 0))) { absl::Status status = collaboration_callbacks_.leave_session ? collaboration_callbacks_.leave_session() : absl::OkStatus(); @@ -1275,34 +1360,38 @@ void AgentChatWidget::RenderCollaborationPanel() { if (!can_refresh) ImGui::BeginDisabled(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.4f, 0.6f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.416f, 0.353f, 0.804f, 1.0f)); - if (ImGui::Button(ICON_MD_REFRESH " Refresh Session##refresh_collab_btn", ImVec2(-1, 0))) { + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(0.416f, 0.353f, 0.804f, 1.0f)); + if (ImGui::Button(ICON_MD_REFRESH " Refresh Session##refresh_collab_btn", + ImVec2(-1, 0))) { RefreshCollaboration(); } ImGui::PopStyleColor(2); - if (!can_refresh && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { + if (!can_refresh && + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { ImGui::SetTooltip("Provide refresh_session callback to enable"); } if (!can_refresh) ImGui::EndDisabled(); } else { ImGui::Spacing(); - ImGui::TextDisabled(ICON_MD_INFO " Start or join a session to collaborate."); + ImGui::TextDisabled(ICON_MD_INFO + " Start or join a session to collaborate."); } ImGui::EndChild(); // Collab_Controls - ImGui::PopID(); // ControlsColumn + ImGui::PopID(); // ControlsColumn ImGui::EndGroup(); ImGui::EndTable(); } - - ImGui::PopID(); // CollabPanel + + ImGui::PopID(); // CollabPanel } void AgentChatWidget::RenderMultimodalPanel() { ImGui::PushID("MultimodalPanel"); ImVec4 gemini_color = ImVec4(0.196f, 0.6f, 0.8f, 1.0f); - + // Dense header (no collapsing for small panel) ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.12f, 0.16f, 0.20f, 0.95f)); ImGui::BeginChild("Multimodal_Panel", ImVec2(0, 100), true); @@ -1361,8 +1450,10 @@ void AgentChatWidget::RenderMultimodalPanel() { } else { ImGui::TextDisabled(ICON_MD_CAMERA_ALT); } - if (ImGui::IsItemHovered() && multimodal_state_.last_capture_path.has_value()) { - ImGui::SetTooltip("%s", multimodal_state_.last_capture_path->filename().string().c_str()); + if (ImGui::IsItemHovered() && + multimodal_state_.last_capture_path.has_value()) { + ImGui::SetTooltip( + "%s", multimodal_state_.last_capture_path->filename().string().c_str()); } if (!can_send) @@ -1399,7 +1490,7 @@ void AgentChatWidget::RenderMultimodalPanel() { } if (!can_send) ImGui::EndDisabled(); - + ImGui::EndChild(); ImGui::PopStyleColor(); ImGui::PopID(); @@ -1537,18 +1628,19 @@ void AgentChatWidget::PollSharedHistory() { void AgentChatWidget::UpdateAgentConfig(const AgentConfigState& config) { agent_config_ = config; - + // Apply configuration to the agent service cli::agent::AgentConfig service_config; service_config.verbose = config.verbose; service_config.show_reasoning = config.show_reasoning; service_config.max_tool_iterations = config.max_tool_iterations; service_config.max_retry_attempts = config.max_retry_attempts; - + agent_service_.SetConfig(service_config); - + if (toast_manager_) { - toast_manager_->Show("Agent configuration updated", ToastType::kSuccess, 2.5f); + toast_manager_->Show("Agent configuration updated", ToastType::kSuccess, + 2.5f); } } @@ -1556,54 +1648,64 @@ void AgentChatWidget::RenderAgentConfigPanel() { // Dense header (no collapsing) ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.12f, 0.14f, 0.18f, 0.95f)); ImGui::BeginChild("AgentConfig", ImVec2(0, 140), true); // Reduced from 350 - ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_SETTINGS " Config"); + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), + ICON_MD_SETTINGS " Config"); ImGui::Separator(); - + // Compact provider selection int provider_idx = 0; - if (agent_config_.ai_provider == "ollama") provider_idx = 1; - else if (agent_config_.ai_provider == "gemini") provider_idx = 2; - + if (agent_config_.ai_provider == "ollama") + provider_idx = 1; + else if (agent_config_.ai_provider == "gemini") + provider_idx = 2; + if (ImGui::RadioButton("Mock", &provider_idx, 0)) { agent_config_.ai_provider = "mock"; - std::snprintf(agent_config_.provider_buffer, sizeof(agent_config_.provider_buffer), "mock"); + std::snprintf(agent_config_.provider_buffer, + sizeof(agent_config_.provider_buffer), "mock"); } ImGui::SameLine(); if (ImGui::RadioButton("Ollama", &provider_idx, 1)) { agent_config_.ai_provider = "ollama"; - std::snprintf(agent_config_.provider_buffer, sizeof(agent_config_.provider_buffer), "ollama"); + std::snprintf(agent_config_.provider_buffer, + sizeof(agent_config_.provider_buffer), "ollama"); } ImGui::SameLine(); if (ImGui::RadioButton("Gemini", &provider_idx, 2)) { agent_config_.ai_provider = "gemini"; - std::snprintf(agent_config_.provider_buffer, sizeof(agent_config_.provider_buffer), "gemini"); + std::snprintf(agent_config_.provider_buffer, + sizeof(agent_config_.provider_buffer), "gemini"); } - + // Dense provider settings if (agent_config_.ai_provider == "ollama") { - ImGui::InputText("##ollama_model", agent_config_.model_buffer, IM_ARRAYSIZE(agent_config_.model_buffer)); - ImGui::InputText("##ollama_host", agent_config_.ollama_host_buffer, IM_ARRAYSIZE(agent_config_.ollama_host_buffer)); + ImGui::InputText("##ollama_model", agent_config_.model_buffer, + IM_ARRAYSIZE(agent_config_.model_buffer)); + ImGui::InputText("##ollama_host", agent_config_.ollama_host_buffer, + IM_ARRAYSIZE(agent_config_.ollama_host_buffer)); } else if (agent_config_.ai_provider == "gemini") { - ImGui::InputText("##gemini_model", agent_config_.model_buffer, IM_ARRAYSIZE(agent_config_.model_buffer)); - ImGui::InputText("##gemini_key", agent_config_.gemini_key_buffer, - IM_ARRAYSIZE(agent_config_.gemini_key_buffer), + ImGui::InputText("##gemini_model", agent_config_.model_buffer, + IM_ARRAYSIZE(agent_config_.model_buffer)); + ImGui::InputText("##gemini_key", agent_config_.gemini_key_buffer, + IM_ARRAYSIZE(agent_config_.gemini_key_buffer), ImGuiInputTextFlags_Password); } - + ImGui::Separator(); ImGui::Checkbox("Verbose", &agent_config_.verbose); ImGui::SameLine(); ImGui::Checkbox("Reasoning", &agent_config_.show_reasoning); ImGui::SetNextItemWidth(-1); - ImGui::SliderInt("##max_iter", &agent_config_.max_tool_iterations, 1, 10, "Iter: %d"); - + ImGui::SliderInt("##max_iter", &agent_config_.max_tool_iterations, 1, 10, + "Iter: %d"); + if (ImGui::Button(ICON_MD_CHECK " Apply", ImVec2(-1, 0))) { agent_config_.ai_model = agent_config_.model_buffer; agent_config_.ollama_host = agent_config_.ollama_host_buffer; agent_config_.gemini_api_key = agent_config_.gemini_key_buffer; UpdateAgentConfig(agent_config_); } - + ImGui::EndChild(); ImGui::PopStyleColor(); } @@ -1611,18 +1713,19 @@ void AgentChatWidget::RenderAgentConfigPanel() { void AgentChatWidget::RenderZ3EDCommandPanel() { ImGui::PushID("Z3EDCmdPanel"); ImVec4 command_color = ImVec4(1.0f, 0.647f, 0.0f, 1.0f); - + // Dense header (no collapsing) ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.14f, 0.12f, 0.18f, 0.95f)); - ImGui::BeginChild("Z3ED_CommandsChild", ImVec2(0, 90), true); // Reduced from 120 - + ImGui::BeginChild("Z3ED_CommandsChild", ImVec2(0, 90), + true); // Reduced from 120 + ImGui::TextColored(command_color, ICON_MD_TERMINAL " Commands"); ImGui::Separator(); - + ImGui::SetNextItemWidth(-60); - ImGui::InputTextWithHint("##z3ed_cmd", "Command...", - z3ed_command_state_.command_input_buffer, - IM_ARRAYSIZE(z3ed_command_state_.command_input_buffer)); + ImGui::InputTextWithHint( + "##z3ed_cmd", "Command...", z3ed_command_state_.command_input_buffer, + IM_ARRAYSIZE(z3ed_command_state_.command_input_buffer)); ImGui::SameLine(); ImGui::BeginDisabled(z3ed_command_state_.command_running); if (ImGui::Button(ICON_MD_PLAY_ARROW "##z3ed_run", ImVec2(50, 0))) { @@ -1640,49 +1743,55 @@ void AgentChatWidget::RenderZ3EDCommandPanel() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Run command"); } - + // Compact action buttons (inline) if (ImGui::SmallButton(ICON_MD_PREVIEW)) { - if (z3ed_callbacks_.list_proposals) { - auto result = z3ed_callbacks_.list_proposals(); - if (result.ok()) { - const auto& proposals = *result; + if (z3ed_callbacks_.list_proposals) { + auto result = z3ed_callbacks_.list_proposals(); + if (result.ok()) { + const auto& proposals = *result; z3ed_command_state_.command_output = absl::StrJoin(proposals, "\n"); } } } - if (ImGui::IsItemHovered()) ImGui::SetTooltip("List"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("List"); ImGui::SameLine(); if (ImGui::SmallButton(ICON_MD_DIFFERENCE)) { - if (z3ed_callbacks_.diff_proposal) { - auto result = z3ed_callbacks_.diff_proposal(""); - if (result.ok()) z3ed_command_state_.command_output = *result; + if (z3ed_callbacks_.diff_proposal) { + auto result = z3ed_callbacks_.diff_proposal(""); + if (result.ok()) + z3ed_command_state_.command_output = *result; } } - if (ImGui::IsItemHovered()) ImGui::SetTooltip("Diff"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Diff"); ImGui::SameLine(); if (ImGui::SmallButton(ICON_MD_CHECK)) { if (z3ed_callbacks_.accept_proposal) { z3ed_callbacks_.accept_proposal(""); } } - if (ImGui::IsItemHovered()) ImGui::SetTooltip("Accept"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Accept"); ImGui::SameLine(); if (ImGui::SmallButton(ICON_MD_CLOSE)) { if (z3ed_callbacks_.reject_proposal) { z3ed_callbacks_.reject_proposal(""); } } - if (ImGui::IsItemHovered()) ImGui::SetTooltip("Reject"); - + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Reject"); + if (!z3ed_command_state_.command_output.empty()) { ImGui::Separator(); - ImGui::TextDisabled("%s", z3ed_command_state_.command_output.substr(0, 100).c_str()); + ImGui::TextDisabled( + "%s", z3ed_command_state_.command_output.substr(0, 100).c_str()); } - + ImGui::EndChild(); ImGui::PopStyleColor(); - + ImGui::PopID(); // FIX: Pop the Z3EDCmdPanel ID } @@ -1691,16 +1800,17 @@ void AgentChatWidget::RenderRomSyncPanel() { ImGuiTreeNodeFlags_DefaultOpen)) { return; } - + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.18f, 0.14f, 0.12f, 1.0f)); ImGui::BeginChild("RomSync", ImVec2(0, 200), true); - + ImGui::Text(ICON_MD_STORAGE " ROM State"); ImGui::Separator(); - + // Display current ROM hash if (!rom_sync_state_.current_rom_hash.empty()) { - ImGui::Text("Hash: %s", rom_sync_state_.current_rom_hash.substr(0, 16).c_str()); + ImGui::Text("Hash: %s", + rom_sync_state_.current_rom_hash.substr(0, 16).c_str()); ImGui::SameLine(); if (ImGui::SmallButton(ICON_MD_CONTENT_COPY)) { ImGui::SetClipboardText(rom_sync_state_.current_rom_hash.c_str()); @@ -1711,76 +1821,78 @@ void AgentChatWidget::RenderRomSyncPanel() { } else { ImGui::TextDisabled("No ROM loaded"); } - + if (rom_sync_state_.last_sync_time != absl::InfinitePast()) { ImGui::Text("Last Sync: %s", absl::FormatTime("%H:%M:%S", rom_sync_state_.last_sync_time, - absl::LocalTimeZone()).c_str()); + absl::LocalTimeZone()) + .c_str()); } - + ImGui::Spacing(); ImGui::Checkbox("Auto-sync ROM changes", &rom_sync_state_.auto_sync_enabled); - + if (rom_sync_state_.auto_sync_enabled) { - ImGui::SliderInt("Sync Interval (seconds)", + ImGui::SliderInt("Sync Interval (seconds)", &rom_sync_state_.sync_interval_seconds, 10, 120); } - + ImGui::Spacing(); ImGui::Separator(); - + bool can_sync = static_cast(rom_sync_callbacks_.generate_rom_diff) && collaboration_state_.active && collaboration_state_.mode == CollaborationMode::kNetwork; - - if (!can_sync) ImGui::BeginDisabled(); - + + if (!can_sync) + ImGui::BeginDisabled(); + if (ImGui::Button(ICON_MD_CLOUD_UPLOAD " Send ROM Sync", ImVec2(-1, 0))) { if (rom_sync_callbacks_.generate_rom_diff) { auto diff_result = rom_sync_callbacks_.generate_rom_diff(); if (diff_result.ok()) { - std::string hash = rom_sync_callbacks_.get_rom_hash - ? rom_sync_callbacks_.get_rom_hash() - : ""; - + std::string hash = rom_sync_callbacks_.get_rom_hash + ? rom_sync_callbacks_.get_rom_hash() + : ""; + rom_sync_state_.current_rom_hash = hash; rom_sync_state_.last_sync_time = absl::Now(); - + // TODO: Send via network coordinator if (toast_manager_) { - toast_manager_->Show(ICON_MD_CLOUD_DONE " ROM synced to collaborators", + toast_manager_->Show(ICON_MD_CLOUD_DONE + " ROM synced to collaborators", ToastType::kSuccess, 3.0f); } } else if (toast_manager_) { - toast_manager_->Show( - absl::StrFormat(ICON_MD_ERROR " Sync failed: %s", - diff_result.status().message()), - ToastType::kError, 5.0f); + toast_manager_->Show(absl::StrFormat(ICON_MD_ERROR " Sync failed: %s", + diff_result.status().message()), + ToastType::kError, 5.0f); } } } - + if (!can_sync) { ImGui::EndDisabled(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Connect to a network session to sync ROM"); } } - + // Show pending syncs if (!rom_sync_state_.pending_syncs.empty()) { ImGui::Spacing(); - ImGui::Text(ICON_MD_PENDING " Pending Syncs (%zu)", + ImGui::Text(ICON_MD_PENDING " Pending Syncs (%zu)", rom_sync_state_.pending_syncs.size()); ImGui::Separator(); - + ImGui::BeginChild("PendingSyncs", ImVec2(0, 80), true); for (const auto& sync : rom_sync_state_.pending_syncs) { ImGui::BulletText("%s", sync.substr(0, 40).c_str()); } ImGui::EndChild(); } - + ImGui::EndChild(); ImGui::PopStyleColor(); } @@ -1790,34 +1902,36 @@ void AgentChatWidget::RenderSnapshotPreviewPanel() { ImGuiTreeNodeFlags_DefaultOpen)) { return; } - + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.12f, 0.12f, 0.16f, 1.0f)); ImGui::BeginChild("SnapshotPreview", ImVec2(0, 200), true); - + if (multimodal_state_.last_capture_path.has_value()) { ImGui::Text(ICON_MD_IMAGE " Latest Capture"); ImGui::Separator(); - ImGui::TextWrapped("%s", - multimodal_state_.last_capture_path->filename().string().c_str()); - + ImGui::TextWrapped( + "%s", multimodal_state_.last_capture_path->filename().string().c_str()); + // TODO: Load and display image thumbnail ImGui::TextDisabled("Preview: [Image preview not yet implemented]"); - + ImGui::Spacing(); - - bool can_share = collaboration_state_.active && + + bool can_share = collaboration_state_.active && collaboration_state_.mode == CollaborationMode::kNetwork; - - if (!can_share) ImGui::BeginDisabled(); - - if (ImGui::Button(ICON_MD_SHARE " Share with Collaborators", ImVec2(-1, 0))) { + + if (!can_share) + ImGui::BeginDisabled(); + + if (ImGui::Button(ICON_MD_SHARE " Share with Collaborators", + ImVec2(-1, 0))) { // TODO: Share snapshot via network coordinator if (toast_manager_) { toast_manager_->Show(ICON_MD_CHECK " Snapshot shared", ToastType::kSuccess, 3.0f); } } - + if (!can_share) { ImGui::EndDisabled(); if (ImGui::IsItemHovered()) { @@ -1828,7 +1942,7 @@ void AgentChatWidget::RenderSnapshotPreviewPanel() { ImGui::TextDisabled(ICON_MD_NO_PHOTOGRAPHY " No snapshot captured yet"); ImGui::TextWrapped("Use the Multimodal panel to capture a snapshot"); } - + ImGui::EndChild(); ImGui::PopStyleColor(); } @@ -1836,43 +1950,47 @@ void AgentChatWidget::RenderSnapshotPreviewPanel() { void AgentChatWidget::RenderProposalManagerPanel() { ImGui::Text(ICON_MD_PREVIEW " Proposal Management"); ImGui::Separator(); - + if (z3ed_callbacks_.list_proposals) { auto proposals_result = z3ed_callbacks_.list_proposals(); - + if (proposals_result.ok()) { const auto& proposals = *proposals_result; - + ImGui::Text("Total Proposals: %zu", proposals.size()); ImGui::Spacing(); - + if (proposals.empty()) { - ImGui::TextDisabled("No proposals yet. Use the agent to create proposals."); + ImGui::TextDisabled( + "No proposals yet. Use the agent to create proposals."); } else { if (ImGui::BeginTable("ProposalsTable", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | - ImGuiTableFlags_Resizable)) { - ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 100.0f); - ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 150.0f); + ImGuiTableFlags_Resizable)) { + ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, + 100.0f); + ImGui::TableSetupColumn("Description", + ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, + 150.0f); ImGui::TableHeadersRow(); - + for (const auto& proposal_id : proposals) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextUnformatted(proposal_id.c_str()); - + ImGui::TableNextColumn(); ImGui::TextDisabled("Proposal details..."); - + ImGui::TableNextColumn(); ImGui::PushID(proposal_id.c_str()); - + if (ImGui::SmallButton(ICON_MD_VISIBILITY)) { FocusProposalDrawer(proposal_id); } ImGui::SameLine(); - + if (ImGui::SmallButton(ICON_MD_CHECK)) { if (z3ed_callbacks_.accept_proposal) { auto status = z3ed_callbacks_.accept_proposal(proposal_id); @@ -1880,25 +1998,24 @@ void AgentChatWidget::RenderProposalManagerPanel() { } } ImGui::SameLine(); - + if (ImGui::SmallButton(ICON_MD_CLOSE)) { if (z3ed_callbacks_.reject_proposal) { auto status = z3ed_callbacks_.reject_proposal(proposal_id); (void)status; // Acknowledge result } } - + ImGui::PopID(); } - + ImGui::EndTable(); } } } else { std::string error_msg(proposals_result.status().message()); ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), - "Failed to load proposals: %s", - error_msg.c_str()); + "Failed to load proposals: %s", error_msg.c_str()); } } else { ImGui::TextDisabled("Proposal management not available"); @@ -1910,19 +2027,20 @@ void AgentChatWidget::HandleRomSyncReceived(const std::string& diff_data, const std::string& rom_hash) { if (rom_sync_callbacks_.apply_rom_diff) { auto status = rom_sync_callbacks_.apply_rom_diff(diff_data, rom_hash); - + if (status.ok()) { rom_sync_state_.current_rom_hash = rom_hash; rom_sync_state_.last_sync_time = absl::Now(); - + if (toast_manager_) { - toast_manager_->Show(ICON_MD_CLOUD_DOWNLOAD " ROM sync applied from collaborator", + toast_manager_->Show(ICON_MD_CLOUD_DOWNLOAD + " ROM sync applied from collaborator", ToastType::kInfo, 3.5f); } } else if (toast_manager_) { - toast_manager_->Show( - absl::StrFormat(ICON_MD_ERROR " ROM sync failed: %s", status.message()), - ToastType::kError, 5.0f); + toast_manager_->Show(absl::StrFormat(ICON_MD_ERROR " ROM sync failed: %s", + status.message()), + ToastType::kError, 5.0f); } } } @@ -1942,14 +2060,16 @@ void AgentChatWidget::HandleProposalReceived( [[maybe_unused]] const std::string& proposal_data) { // TODO: Parse and add proposal to local registry if (toast_manager_) { - toast_manager_->Show(ICON_MD_LIGHTBULB " New proposal received from collaborator", + toast_manager_->Show(ICON_MD_LIGHTBULB + " New proposal received from collaborator", ToastType::kInfo, 3.5f); } } void AgentChatWidget::SyncHistoryToPopup() { - if (!chat_history_popup_) return; - + if (!chat_history_popup_) + return; + const auto& history = agent_service_.GetHistory(); chat_history_popup_->UpdateHistory(history); chat_history_popup_->NotifyNewMessage(); @@ -1957,7 +2077,7 @@ void AgentChatWidget::SyncHistoryToPopup() { void AgentChatWidget::RenderSystemPromptEditor() { ImGui::BeginChild("SystemPromptEditor", ImVec2(0, 0), false); - + // Toolbar if (ImGui::Button(ICON_MD_FOLDER_OPEN " Load V1")) { // Load embedded system_prompt.txt (v1) @@ -1974,26 +2094,28 @@ void AgentChatWidget::RenderSystemPromptEditor() { break; } } - + if (!found) { FileEditorTab tab; tab.filename = "system_prompt_v1.txt (built-in)"; tab.filepath = ""; tab.is_system_prompt = true; - tab.editor.SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus()); + tab.editor.SetLanguageDefinition( + TextEditor::LanguageDefinition::CPlusPlus()); tab.editor.SetText(prompt_v1); open_files_.push_back(std::move(tab)); active_file_tab_ = static_cast(open_files_.size()) - 1; } - + if (toast_manager_) { toast_manager_->Show("System prompt V1 loaded", ToastType::kSuccess); } } else if (toast_manager_) { - toast_manager_->Show("Could not load system prompt V1", ToastType::kError); + toast_manager_->Show("Could not load system prompt V1", + ToastType::kError); } } - + ImGui::SameLine(); if (ImGui::Button(ICON_MD_FOLDER_OPEN " Load V2")) { // Load embedded system_prompt_v2.txt @@ -2010,26 +2132,28 @@ void AgentChatWidget::RenderSystemPromptEditor() { break; } } - + if (!found) { FileEditorTab tab; tab.filename = "system_prompt_v2.txt (built-in)"; tab.filepath = ""; tab.is_system_prompt = true; - tab.editor.SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus()); + tab.editor.SetLanguageDefinition( + TextEditor::LanguageDefinition::CPlusPlus()); tab.editor.SetText(prompt_v2); open_files_.push_back(std::move(tab)); active_file_tab_ = static_cast(open_files_.size()) - 1; } - + if (toast_manager_) { toast_manager_->Show("System prompt V2 loaded", ToastType::kSuccess); } } else if (toast_manager_) { - toast_manager_->Show("Could not load system prompt V2", ToastType::kError); + toast_manager_->Show("Could not load system prompt V2", + ToastType::kError); } } - + ImGui::SameLine(); if (ImGui::Button(ICON_MD_SAVE " Save to Project")) { // Save the current system prompt to project directory @@ -2045,18 +2169,20 @@ void AgentChatWidget::RenderSystemPromptEditor() { tab.filename = util::GetFileName(save_path); tab.modified = false; if (toast_manager_) { - toast_manager_->Show(absl::StrFormat("System prompt saved to %s", save_path), - ToastType::kSuccess); + toast_manager_->Show( + absl::StrFormat("System prompt saved to %s", save_path), + ToastType::kSuccess); } } else if (toast_manager_) { - toast_manager_->Show("Failed to save system prompt", ToastType::kError); + toast_manager_->Show("Failed to save system prompt", + ToastType::kError); } } break; } } } - + ImGui::SameLine(); if (ImGui::Button(ICON_MD_NOTE_ADD " Create New")) { FileEditorTab tab; @@ -2064,12 +2190,14 @@ void AgentChatWidget::RenderSystemPromptEditor() { tab.filepath = ""; tab.is_system_prompt = true; tab.modified = true; - tab.editor.SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus()); - tab.editor.SetText("# Custom System Prompt\n\nEnter your custom system prompt here...\n"); + tab.editor.SetLanguageDefinition( + TextEditor::LanguageDefinition::CPlusPlus()); + tab.editor.SetText( + "# Custom System Prompt\n\nEnter your custom system prompt here...\n"); open_files_.push_back(std::move(tab)); active_file_tab_ = static_cast(open_files_.size()) - 1; } - + ImGui::SameLine(); if (ImGui::Button(ICON_MD_FOLDER_OPEN " Load Custom")) { auto filepath = util::FileDialogWrapper::ShowOpenFileDialog(); @@ -2089,31 +2217,33 @@ void AgentChatWidget::RenderSystemPromptEditor() { break; } } - + if (!found) { FileEditorTab tab; tab.filename = util::GetFileName(filepath); tab.filepath = filepath; tab.is_system_prompt = true; - tab.editor.SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus()); + tab.editor.SetLanguageDefinition( + TextEditor::LanguageDefinition::CPlusPlus()); std::stringstream buffer; buffer << file.rdbuf(); tab.editor.SetText(buffer.str()); open_files_.push_back(std::move(tab)); active_file_tab_ = static_cast(open_files_.size()) - 1; } - + if (toast_manager_) { - toast_manager_->Show("Custom system prompt loaded", ToastType::kSuccess); + toast_manager_->Show("Custom system prompt loaded", + ToastType::kSuccess); } } else if (toast_manager_) { toast_manager_->Show("Could not load file", ToastType::kError); } } } - + ImGui::Separator(); - + // Find and render system prompt editor bool found_prompt = false; for (size_t i = 0; i < open_files_.size(); ++i) { @@ -2127,17 +2257,19 @@ void AgentChatWidget::RenderSystemPromptEditor() { break; } } - + if (!found_prompt) { - ImGui::TextWrapped("No system prompt loaded. Click 'Load Default' to edit the system prompt."); + ImGui::TextWrapped( + "No system prompt loaded. Click 'Load Default' to edit the system " + "prompt."); } - + ImGui::EndChild(); } void AgentChatWidget::RenderFileEditorTabs() { ImGui::BeginChild("FileEditorArea", ImVec2(0, 0), false); - + // Toolbar if (ImGui::Button(ICON_MD_NOTE_ADD " New File")) { ImGui::OpenPopup("NewFilePopup"); @@ -2149,13 +2281,14 @@ void AgentChatWidget::RenderFileEditorTabs() { OpenFileInEditor(filepath); } } - + // New file popup static char new_filename_buffer[256] = {}; if (ImGui::BeginPopup("NewFilePopup")) { ImGui::Text("Create New File"); ImGui::Separator(); - ImGui::InputText("Filename", new_filename_buffer, sizeof(new_filename_buffer)); + ImGui::InputText("Filename", new_filename_buffer, + sizeof(new_filename_buffer)); if (ImGui::Button("Create")) { if (strlen(new_filename_buffer) > 0) { CreateNewFileInEditor(new_filename_buffer); @@ -2169,25 +2302,27 @@ void AgentChatWidget::RenderFileEditorTabs() { } ImGui::EndPopup(); } - + ImGui::Separator(); - + // File tabs if (!open_files_.empty()) { - if (ImGui::BeginTabBar("FileTabs", ImGuiTabBarFlags_Reorderable | - ImGuiTabBarFlags_FittingPolicyScroll)) { + if (ImGui::BeginTabBar("FileTabs", + ImGuiTabBarFlags_Reorderable | + ImGuiTabBarFlags_FittingPolicyScroll)) { for (size_t i = 0; i < open_files_.size(); ++i) { - if (open_files_[i].is_system_prompt) continue; // Skip system prompt in file tabs - + if (open_files_[i].is_system_prompt) + continue; // Skip system prompt in file tabs + bool open = true; std::string tab_label = open_files_[i].filename; if (open_files_[i].modified) { tab_label += " *"; } - + if (ImGui::BeginTabItem(tab_label.c_str(), &open)) { active_file_tab_ = static_cast(i); - + // File toolbar if (ImGui::Button(ICON_MD_SAVE " Save")) { if (!open_files_[i].filepath.empty()) { @@ -2217,23 +2352,24 @@ void AgentChatWidget::RenderFileEditorTabs() { } } } - + ImGui::SameLine(); - ImGui::TextDisabled("%s", open_files_[i].filepath.empty() ? - "(unsaved)" : open_files_[i].filepath.c_str()); - + ImGui::TextDisabled("%s", open_files_[i].filepath.empty() + ? "(unsaved)" + : open_files_[i].filepath.c_str()); + ImGui::Separator(); - + // Editor ImVec2 editor_size = ImVec2(0, ImGui::GetContentRegionAvail().y); open_files_[i].editor.Render("##FileEditor", editor_size); if (open_files_[i].editor.IsTextChanged()) { open_files_[i].modified = true; } - + ImGui::EndTabItem(); } - + if (!open) { // Tab was closed open_files_.erase(open_files_.begin() + i); @@ -2246,9 +2382,10 @@ void AgentChatWidget::RenderFileEditorTabs() { ImGui::EndTabBar(); } } else { - ImGui::TextWrapped("No files open. Create a new file or open an existing one."); + ImGui::TextWrapped( + "No files open. Create a new file or open an existing one."); } - + ImGui::EndChild(); } @@ -2260,7 +2397,7 @@ void AgentChatWidget::OpenFileInEditor(const std::string& filepath) { return; } } - + // Load the file std::ifstream file(filepath); if (!file.is_open()) { @@ -2269,32 +2406,34 @@ void AgentChatWidget::OpenFileInEditor(const std::string& filepath) { } return; } - + FileEditorTab tab; tab.filepath = filepath; - + // Extract filename from path size_t last_slash = filepath.find_last_of("/\\"); - tab.filename = (last_slash != std::string::npos) ? - filepath.substr(last_slash + 1) : filepath; - + tab.filename = (last_slash != std::string::npos) + ? filepath.substr(last_slash + 1) + : filepath; + // Set language based on extension std::string ext = util::GetFileExtension(filepath); if (ext == "cpp" || ext == "cc" || ext == "h" || ext == "hpp") { - tab.editor.SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus()); + tab.editor.SetLanguageDefinition( + TextEditor::LanguageDefinition::CPlusPlus()); } else if (ext == "c") { tab.editor.SetLanguageDefinition(TextEditor::LanguageDefinition::C()); } else if (ext == "lua") { tab.editor.SetLanguageDefinition(TextEditor::LanguageDefinition::Lua()); } - + std::stringstream buffer; buffer << file.rdbuf(); tab.editor.SetText(buffer.str()); - + open_files_.push_back(std::move(tab)); active_file_tab_ = static_cast(open_files_.size()) - 1; - + if (toast_manager_) { toast_manager_->Show("File loaded", ToastType::kSuccess); } @@ -2304,22 +2443,24 @@ void AgentChatWidget::CreateNewFileInEditor(const std::string& filename) { FileEditorTab tab; tab.filename = filename; tab.modified = true; - + // Set language based on extension std::string ext = util::GetFileExtension(filename); if (ext == "cpp" || ext == "cc" || ext == "h" || ext == "hpp") { - tab.editor.SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus()); + tab.editor.SetLanguageDefinition( + TextEditor::LanguageDefinition::CPlusPlus()); } else if (ext == "c") { tab.editor.SetLanguageDefinition(TextEditor::LanguageDefinition::C()); } else if (ext == "lua") { tab.editor.SetLanguageDefinition(TextEditor::LanguageDefinition::Lua()); } - + open_files_.push_back(std::move(tab)); active_file_tab_ = static_cast(open_files_.size()) - 1; } -void AgentChatWidget::LoadAgentSettingsFromProject(const core::YazeProject& project) { +void AgentChatWidget::LoadAgentSettingsFromProject( + const core::YazeProject& project) { // Load AI provider settings from project agent_config_.ai_provider = project.agent_settings.ai_provider; agent_config_.ai_model = project.agent_settings.ai_model; @@ -2327,23 +2468,25 @@ void AgentChatWidget::LoadAgentSettingsFromProject(const core::YazeProject& proj agent_config_.gemini_api_key = project.agent_settings.gemini_api_key; agent_config_.show_reasoning = project.agent_settings.show_reasoning; agent_config_.verbose = project.agent_settings.verbose; - agent_config_.max_tool_iterations = project.agent_settings.max_tool_iterations; + agent_config_.max_tool_iterations = + project.agent_settings.max_tool_iterations; agent_config_.max_retry_attempts = project.agent_settings.max_retry_attempts; - + // Copy to buffer for ImGui - strncpy(agent_config_.provider_buffer, agent_config_.ai_provider.c_str(), + strncpy(agent_config_.provider_buffer, agent_config_.ai_provider.c_str(), sizeof(agent_config_.provider_buffer) - 1); - strncpy(agent_config_.model_buffer, agent_config_.ai_model.c_str(), + strncpy(agent_config_.model_buffer, agent_config_.ai_model.c_str(), sizeof(agent_config_.model_buffer) - 1); - strncpy(agent_config_.ollama_host_buffer, agent_config_.ollama_host.c_str(), + strncpy(agent_config_.ollama_host_buffer, agent_config_.ollama_host.c_str(), sizeof(agent_config_.ollama_host_buffer) - 1); - strncpy(agent_config_.gemini_key_buffer, agent_config_.gemini_api_key.c_str(), + strncpy(agent_config_.gemini_key_buffer, agent_config_.gemini_api_key.c_str(), sizeof(agent_config_.gemini_key_buffer) - 1); - + // Load custom system prompt if specified - if (project.agent_settings.use_custom_prompt && + if (project.agent_settings.use_custom_prompt && !project.agent_settings.custom_system_prompt.empty()) { - std::string prompt_path = project.GetAbsolutePath(project.agent_settings.custom_system_prompt); + std::string prompt_path = + project.GetAbsolutePath(project.agent_settings.custom_system_prompt); std::ifstream file(prompt_path); if (file.is_open()) { // Load into system prompt tab @@ -2359,13 +2502,14 @@ void AgentChatWidget::LoadAgentSettingsFromProject(const core::YazeProject& proj break; } } - + if (!found) { FileEditorTab tab; tab.filename = util::GetFileName(prompt_path); tab.filepath = prompt_path; tab.is_system_prompt = true; - tab.editor.SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus()); + tab.editor.SetLanguageDefinition( + TextEditor::LanguageDefinition::CPlusPlus()); std::stringstream buffer; buffer << file.rdbuf(); tab.editor.SetText(buffer.str()); @@ -2383,13 +2527,14 @@ void AgentChatWidget::SaveAgentSettingsToProject(core::YazeProject& project) { project.agent_settings.gemini_api_key = agent_config_.gemini_api_key; project.agent_settings.show_reasoning = agent_config_.show_reasoning; project.agent_settings.verbose = agent_config_.verbose; - project.agent_settings.max_tool_iterations = agent_config_.max_tool_iterations; + project.agent_settings.max_tool_iterations = + agent_config_.max_tool_iterations; project.agent_settings.max_retry_attempts = agent_config_.max_retry_attempts; - + // Check if a custom system prompt is loaded for (const auto& tab : open_files_) { if (tab.is_system_prompt && !tab.filepath.empty()) { - project.agent_settings.custom_system_prompt = + project.agent_settings.custom_system_prompt = project.GetRelativePath(tab.filepath); project.agent_settings.use_custom_prompt = true; break; diff --git a/src/app/editor/agent/agent_chat_widget.h b/src/app/editor/agent/agent_chat_widget.h index 845e6321..af3ce9ca 100644 --- a/src/app/editor/agent/agent_chat_widget.h +++ b/src/app/editor/agent/agent_chat_widget.h @@ -235,6 +235,11 @@ public: // History synchronization void SyncHistoryToPopup(); + // AI response state + bool waiting_for_response_ = false; + float thinking_animation_ = 0.0f; + std::string pending_message_; + // Chat session management struct ChatSession { std::string id; diff --git a/src/cli/handlers/agent/tool_commands.cc b/src/cli/handlers/agent/tool_commands.cc index 80642aa8..a48fa90b 100644 --- a/src/cli/handlers/agent/tool_commands.cc +++ b/src/cli/handlers/agent/tool_commands.cc @@ -112,14 +112,19 @@ absl::Status HandleResourceListCommand( rom = &rom_storage; } - // Initialize embedded labels if not already loaded - if (rom->resource_label() && !rom->resource_label()->labels_loaded_) { - core::YazeProject project; - auto labels_status = project.InitializeEmbeddedLabels(); - if (labels_status.ok()) { - rom->resource_label()->labels_ = project.resource_labels; - rom->resource_label()->labels_loaded_ = true; + // Initialize embedded labels if needed + if (rom->resource_label()) { + if (!rom->resource_label()->labels_loaded_ || rom->resource_label()->labels_.empty()) { + core::YazeProject project; + project.use_embedded_labels = true; + auto labels_status = project.InitializeEmbeddedLabels(); + if (labels_status.ok()) { + rom->resource_label()->labels_ = project.resource_labels; + rom->resource_label()->labels_loaded_ = true; + } } + } else { + return absl::FailedPreconditionError("ROM has no resource label manager"); } ResourceContextBuilder context_builder(rom); diff --git a/src/cli/service/resources/resource_context_builder.cc b/src/cli/service/resources/resource_context_builder.cc index 7072f348..10973e05 100644 --- a/src/cli/service/resources/resource_context_builder.cc +++ b/src/cli/service/resources/resource_context_builder.cc @@ -205,7 +205,11 @@ ResourceContextBuilder::GetLabels(const std::string& category) { } auto* label_mgr = rom_->resource_label(); - if (!label_mgr || !label_mgr->labels_loaded_) { + if (!label_mgr) { + return absl::FailedPreconditionError("No resource label manager"); + } + + if (!label_mgr->labels_loaded_) { return absl::FailedPreconditionError("No labels file loaded"); }