From 8688f0c5029066f07d897b41ec9b3e96af830bcb Mon Sep 17 00:00:00 2001 From: scawful Date: Mon, 6 Oct 2025 01:33:08 -0400 Subject: [PATCH] feat: Enhance Agent Chat History Popup with Retro Animations - Introduced retro-style animations including pulsing borders, scanline effects, and glitch animations to the Agent Chat History Popup, enhancing visual appeal. - Updated message display with terminal-inspired styling, including color-coded sender labels and retro separators. - Improved header design with a pulsing glow effect and animated status indicators for unread messages, enriching user interaction. - Refactored drawing methods to accommodate new visual effects while maintaining existing functionality. --- .../editor/agent/agent_chat_history_popup.cc | 172 ++++++++++---- .../editor/agent/agent_chat_history_popup.h | 6 + src/app/editor/agent/agent_chat_widget.cc | 222 +++++++++++++++--- src/app/editor/agent/agent_chat_widget.h | 11 + src/app/editor/agent/agent_editor.cc | 19 +- src/app/editor/agent/agent_editor.h | 6 + src/cli/agent.cmake | 3 + 7 files changed, 368 insertions(+), 71 deletions(-) diff --git a/src/app/editor/agent/agent_chat_history_popup.cc b/src/app/editor/agent/agent_chat_history_popup.cc index b8aabcb7..85353c06 100644 --- a/src/app/editor/agent/agent_chat_history_popup.cc +++ b/src/app/editor/agent/agent_chat_history_popup.cc @@ -23,9 +23,15 @@ void AgentChatHistoryPopup::Draw() { const auto& theme = AgentUI::GetTheme(); - // Set drawer position on the LEFT side (full height) + // Animate retro effects ImGuiIO& io = ImGui::GetIO(); + pulse_animation_ += io.DeltaTime * 2.0f; + scanline_offset_ += io.DeltaTime * 0.3f; + if (scanline_offset_ > 1.0f) scanline_offset_ -= 1.0f; + glitch_animation_ += io.DeltaTime * 5.0f; + blink_counter_ = static_cast(pulse_animation_ * 2.0f) % 2; + // Set drawer position on the LEFT side (full height) ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); ImGui::SetNextWindowSize(ImVec2(drawer_width_, io.DisplaySize.y), ImGuiCond_Always); @@ -34,10 +40,19 @@ void AgentChatHistoryPopup::Draw() { ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar; - // Use current theme colors - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f); + // Use current theme colors with slight glow + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10, 10)); + // Pulsing border color + float border_pulse = 0.7f + 0.3f * std::sin(pulse_animation_ * 0.5f); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4( + theme.provider_ollama.x * border_pulse, + theme.provider_ollama.y * border_pulse, + theme.provider_ollama.z * border_pulse + 0.2f, + 0.8f + )); + if (ImGui::Begin("##AgentChatPopup", &visible_, flags)) { DrawHeader(); @@ -47,8 +62,30 @@ void AgentChatHistoryPopup::Draw() { // Calculate proper list height float list_height = ImGui::GetContentRegionAvail().y - 220.0f; - ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.code_bg_color); + // Dark terminal background + ImVec4 terminal_bg = theme.code_bg_color; + terminal_bg.x *= 0.9f; + terminal_bg.y *= 0.9f; + terminal_bg.z *= 0.95f; + + ImGui::PushStyleColor(ImGuiCol_ChildBg, terminal_bg); ImGui::BeginChild("MessageList", ImVec2(0, list_height), true, ImGuiWindowFlags_AlwaysVerticalScrollbar); + + // Draw scanline effect + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 win_pos = ImGui::GetWindowPos(); + ImVec2 win_size = ImGui::GetWindowSize(); + + for (float y = 0; y < win_size.y; y += 3.0f) { + float offset_y = y + scanline_offset_ * 3.0f; + if (offset_y < win_size.y) { + draw_list->AddLine( + ImVec2(win_pos.x, win_pos.y + offset_y), + ImVec2(win_pos.x + win_size.x, win_pos.y + offset_y), + IM_COL32(0, 0, 0, 15)); + } + } + DrawMessageList(); if (needs_scroll_) { @@ -72,6 +109,7 @@ void AgentChatHistoryPopup::Draw() { } ImGui::End(); + ImGui::PopStyleColor(); // Border color ImGui::PopStyleVar(2); } @@ -106,100 +144,140 @@ void AgentChatHistoryPopup::DrawMessage(const cli::agent::ChatMessage& msg, int bool from_user = (msg.sender == cli::agent::ChatMessage::Sender::kUser); - // Use theme colors with slight tint - ImVec4 text_color = ImGui::GetStyleColorVec4(ImGuiCol_Text); + // Retro terminal colors ImVec4 header_color = from_user - ? ImVec4(text_color.x * 1.2f, text_color.y * 0.9f, text_color.z * 0.5f, 1.0f) // Gold tint - : ImVec4(text_color.x * 0.7f, text_color.y * 1.0f, text_color.z * 0.9f, 1.0f); // Teal tint + ? ImVec4(1.0f, 0.85f, 0.0f, 1.0f) // Amber/Gold for user + : ImVec4(0.0f, 1.0f, 0.7f, 1.0f); // Cyan/Green for agent - const char* sender_label = from_user ? ICON_MD_PERSON " You" : ICON_MD_SMART_TOY " Agent"; + const char* sender_label = from_user ? "> USER:" : "> AGENT:"; - // Message header + // Message header with terminal prefix ImGui::TextColored(header_color, "%s", sender_label); ImGui::SameLine(); - ImGui::TextDisabled("%s", absl::FormatTime("%H:%M:%S", msg.timestamp, absl::LocalTimeZone()).c_str()); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), + "[%s]", absl::FormatTime("%H:%M:%S", msg.timestamp, absl::LocalTimeZone()).c_str()); - // Message content - ImGui::Indent(10.0f); + // Message content with terminal styling + ImGui::Indent(15.0f); if (msg.table_data.has_value()) { - ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.9f, 1.0f), ICON_MD_TABLE_CHART " [Table Data]"); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.9f, 1.0f), + " %s [Table Data]", ICON_MD_TABLE_CHART); } else if (msg.json_pretty.has_value()) { - ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.9f, 1.0f), ICON_MD_DATA_OBJECT " [Structured Response]"); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.9f, 1.0f), + " %s [Structured Response]", ICON_MD_DATA_OBJECT); } else { - // Truncate long messages + // Truncate long messages with ellipsis std::string content = msg.message; if (content.length() > 200) { content = content.substr(0, 197) + "..."; } - ImGui::TextWrapped("%s", content.c_str()); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.85f, 0.85f, 0.85f, 1.0f)); + ImGui::TextWrapped(" %s", content.c_str()); + ImGui::PopStyleColor(); } - // Show proposal indicator if present + // Show proposal indicator with pulse if (msg.proposal.has_value()) { - ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.4f, 1.0f), - ICON_MD_PREVIEW " Proposal: %s", msg.proposal->id.c_str()); + float proposal_pulse = 0.7f + 0.3f * std::sin(pulse_animation_ * 2.0f); + ImGui::TextColored(ImVec4(0.2f, proposal_pulse, 0.4f, 1.0f), + " %s Proposal: [%s]", ICON_MD_PREVIEW, msg.proposal->id.c_str()); } - ImGui::Unindent(10.0f); + ImGui::Unindent(15.0f); ImGui::Spacing(); - ImGui::Separator(); + + // Retro separator line + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 line_start = ImGui::GetCursorScreenPos(); + float line_width = ImGui::GetContentRegionAvail().x; + draw_list->AddLine( + line_start, + ImVec2(line_start.x + line_width, line_start.y), + IM_COL32(60, 60, 70, 100), + 1.0f + ); + + ImGui::Dummy(ImVec2(0, 2)); ImGui::PopID(); } void AgentChatHistoryPopup::DrawHeader() { - // Theme-matched header with subtle gradient + const auto& theme = AgentUI::GetTheme(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImVec2 header_start = ImGui::GetCursorScreenPos(); ImVec2 header_size(ImGui::GetContentRegionAvail().x, 55); - // Subtle gradient matching theme - ImU32 color_top = ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_WindowBg)); - ImU32 color_bottom = ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_ChildBg)); + // Retro gradient with pulse + float pulse = 0.5f + 0.5f * std::sin(pulse_animation_); + ImVec4 bg_top = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); + ImVec4 bg_bottom = ImGui::GetStyleColorVec4(ImGuiCol_ChildBg); + bg_top.x += 0.1f * pulse; + bg_top.y += 0.1f * pulse; + bg_top.z += 0.15f * pulse; + + ImU32 color_top = ImGui::GetColorU32(bg_top); + ImU32 color_bottom = ImGui::GetColorU32(bg_bottom); 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); - // Thin accent line (no pulse - matches theme better) - ImU32 accent_color = ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_Separator)); + // Pulsing accent line with glow + float line_pulse = 0.6f + 0.4f * std::sin(pulse_animation_ * 0.7f); + ImU32 accent_color = IM_COL32( + static_cast(theme.provider_ollama.x * 255 * line_pulse), + static_cast(theme.provider_ollama.y * 255 * line_pulse), + static_cast(theme.provider_ollama.z * 255 * line_pulse + 50), + 200 + ); 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, 1.5f); + accent_color, 2.0f); ImGui::Dummy(ImVec2(0, 8)); - // Title and provider dropdown (like connection header) - ImGui::Text(ICON_MD_CHAT); - ImGui::SameLine(); + // Title with pulsing glow + ImVec4 title_color = ImVec4( + 0.4f + 0.3f * pulse, + 0.8f + 0.2f * pulse, + 1.0f, + 1.0f + ); + ImGui::PushStyleColor(ImGuiCol_Text, title_color); + ImGui::Text("%s CHAT HISTORY", ICON_MD_CHAT); + ImGui::PopStyleColor(); - // Model dropdown (compact) - ImGui::SetNextItemWidth(120); - static int provider_idx = 0; - const char* providers[] = {"Mock", "Ollama", "Gemini"}; - ImGui::Combo("##popup_provider", &provider_idx, providers, 3); + ImGui::SameLine(); + ImGui::TextDisabled("[v0.4.x]"); // Buttons properly spaced from right edge ImGui::SameLine(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 75.0f); - // Compact mode toggle + // Compact mode toggle with pulse + if (blink_counter_ == 0 && compact_mode_) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.4f, 0.6f, 0.7f)); + } if (ImGui::SmallButton(compact_mode_ ? ICON_MD_UNFOLD_MORE : ICON_MD_UNFOLD_LESS)) { compact_mode_ = !compact_mode_; } + if (blink_counter_ == 0 && compact_mode_) { + ImGui::PopStyleColor(); + } if (ImGui::IsItemHovered()) { ImGui::SetTooltip(compact_mode_ ? "Expand view" : "Compact view"); } ImGui::SameLine(); - // Full chat button (closes popup when opened) + // Full chat button if (ImGui::SmallButton(ICON_MD_OPEN_IN_NEW)) { if (open_chat_callback_) { open_chat_callback_(); - visible_ = false; // Close popup when opening main chat + visible_ = false; } } if (ImGui::IsItemHovered()) { @@ -216,7 +294,7 @@ void AgentChatHistoryPopup::DrawHeader() { ImGui::SetTooltip("Close (Ctrl+H)"); } - // Message count with badge + // Message count with retro styling int visible_count = 0; for (const auto& msg : messages_) { if (msg.is_internal) continue; @@ -228,8 +306,16 @@ void AgentChatHistoryPopup::DrawHeader() { } ImGui::Spacing(); - ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 0.9f), - "%d message%s", visible_count, visible_count == 1 ? "" : "s"); + ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), + "> MESSAGES: [%d]", visible_count); + + // Animated status indicator + if (unread_count_ > 0) { + ImGui::SameLine(); + float unread_pulse = 0.5f + 0.5f * std::sin(pulse_animation_ * 3.0f); + ImGui::TextColored(ImVec4(1.0f, unread_pulse * 0.5f, 0.0f, 1.0f), + "%s %d NEW", ICON_MD_NOTIFICATION_IMPORTANT, unread_count_); + } ImGui::Dummy(ImVec2(0, 5)); } diff --git a/src/app/editor/agent/agent_chat_history_popup.h b/src/app/editor/agent/agent_chat_history_popup.h index 48f624b0..8468d7e6 100644 --- a/src/app/editor/agent/agent_chat_history_popup.h +++ b/src/app/editor/agent/agent_chat_history_popup.h @@ -112,6 +112,12 @@ class AgentChatHistoryPopup { float header_pulse_ = 0.0f; int unread_count_ = 0; + // Retro hacker aesthetic animations + float pulse_animation_ = 0.0f; + float scanline_offset_ = 0.0f; + float glitch_animation_ = 0.0f; + int blink_counter_ = 0; + // Dependencies ToastManager* toast_manager_ = nullptr; OpenChatCallback open_chat_callback_; diff --git a/src/app/editor/agent/agent_chat_widget.cc b/src/app/editor/agent/agent_chat_widget.cc index ee578d0b..ac073a54 100644 --- a/src/app/editor/agent/agent_chat_widget.cc +++ b/src/app/editor/agent/agent_chat_widget.cc @@ -1491,29 +1491,91 @@ void AgentChatWidget::RenderAutomationPanel() { const auto& theme = AgentUI::GetTheme(); ImGui::PushID("AutomationPanel"); - AgentUI::PushPanelStyle(); - if (ImGui::BeginChild("Automation_Panel", ImVec2(0, 180), true)) { - AgentUI::RenderSectionHeader(ICON_MD_SMART_TOY, "GUI Automation", - theme.provider_ollama); + // Auto-poll for status updates + PollAutomationStatus(); - // Connection status - bool connected = automation_state_.harness_connected; - ImVec4 status_color = connected ? theme.status_success : theme.status_warning; - const char* status_text = connected ? "Connected" : "Disconnected"; + // Animate pulse and scanlines for retro effect + automation_state_.pulse_animation += ImGui::GetIO().DeltaTime * 2.0f; + automation_state_.scanline_offset += ImGui::GetIO().DeltaTime * 0.5f; + if (automation_state_.scanline_offset > 1.0f) { + automation_state_.scanline_offset -= 1.0f; + } + + AgentUI::PushPanelStyle(); + if (ImGui::BeginChild("Automation_Panel", ImVec2(0, 240), true)) { + // === HEADER WITH RETRO GLITCH EFFECT === + float pulse = 0.5f + 0.5f * std::sin(automation_state_.pulse_animation); + ImVec4 header_glow = ImVec4( + theme.provider_ollama.x + 0.3f * pulse, + theme.provider_ollama.y + 0.2f * pulse, + theme.provider_ollama.z + 0.4f * pulse, + 1.0f + ); + + ImGui::PushStyleColor(ImGuiCol_Text, header_glow); + ImGui::TextWrapped("%s %s", ICON_MD_SMART_TOY, "GUI AUTOMATION"); + ImGui::PopStyleColor(); - ImGui::TextColored(status_color, "%s %s", - connected ? ICON_MD_CHECK_CIRCLE : ICON_MD_WARNING, - status_text); ImGui::SameLine(); + ImGui::TextDisabled("[v0.4.x]"); + + // === CONNECTION STATUS WITH VISUAL EFFECTS === + bool connected = automation_state_.harness_connected; + ImVec4 status_color; + const char* status_text; + const char* status_icon; + + if (connected) { + // Pulsing green for connected + float green_pulse = 0.7f + 0.3f * std::sin(automation_state_.pulse_animation * 0.5f); + status_color = ImVec4(0.1f, green_pulse, 0.3f, 1.0f); + status_text = "ONLINE"; + status_icon = ICON_MD_CHECK_CIRCLE; + } else { + // Pulsing red for disconnected + float red_pulse = 0.6f + 0.4f * std::sin(automation_state_.pulse_animation * 1.5f); + status_color = ImVec4(red_pulse, 0.2f, 0.2f, 1.0f); + status_text = "OFFLINE"; + status_icon = ICON_MD_ERROR; + } + + ImGui::Separator(); + ImGui::TextColored(status_color, "%s %s", status_icon, status_text); + ImGui::SameLine(); + ImGui::TextDisabled("| %s", automation_state_.grpc_server_address.c_str()); + + // === CONTROL BAR === + ImGui::Spacing(); + + // Refresh button with pulse effect when auto-refresh is on + bool auto_ref_pulse = automation_state_.auto_refresh_enabled && + (static_cast(automation_state_.pulse_animation * 2.0f) % 2 == 0); + if (auto_ref_pulse) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.5f, 0.7f, 0.8f)); + } if (ImGui::SmallButton(ICON_MD_REFRESH " Refresh")) { + PollAutomationStatus(); if (automation_callbacks_.show_active_tests) { automation_callbacks_.show_active_tests(); } } + if (auto_ref_pulse) { + ImGui::PopStyleColor(); + } + if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Refresh automation status"); + ImGui::SetTooltip("Refresh automation status\nAuto-refresh: %s (%.1fs)", + automation_state_.auto_refresh_enabled ? "ON" : "OFF", + automation_state_.refresh_interval_seconds); + } + + // Auto-refresh toggle + ImGui::SameLine(); + ImGui::Checkbox("##auto_refresh", &automation_state_.auto_refresh_enabled); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Auto-refresh connection status"); } // Quick action buttons @@ -1523,6 +1585,9 @@ void AgentChatWidget::RenderAutomationPanel() { automation_callbacks_.open_harness_dashboard(); } } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Open automation dashboard"); + } ImGui::SameLine(); if (ImGui::SmallButton(ICON_MD_REPLAY " Replay")) { @@ -1530,33 +1595,78 @@ void AgentChatWidget::RenderAutomationPanel() { automation_callbacks_.replay_last_plan(); } } - if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Replay last automation plan"); } - // Recent automation actions + // === SETTINGS ROW === + ImGui::Spacing(); + ImGui::SetNextItemWidth(80.0f); + ImGui::SliderFloat("##refresh_interval", &automation_state_.refresh_interval_seconds, + 0.5f, 10.0f, "%.1fs"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Auto-refresh interval"); + } + + // === RECENT AUTOMATION ACTIONS WITH SCROLLING === ImGui::Spacing(); ImGui::Separator(); - ImGui::Text(ICON_MD_LIST " Recent Actions:"); + + // Header with retro styling + ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "%s RECENT ACTIONS", ICON_MD_LIST); + ImGui::SameLine(); + ImGui::TextDisabled("[%zu]", automation_state_.recent_tests.size()); if (automation_state_.recent_tests.empty()) { - ImGui::TextDisabled(" No recent actions"); + ImGui::Spacing(); + ImGui::TextDisabled(" > No recent actions"); + ImGui::TextDisabled(" > Waiting for automation tasks..."); + + // Add animated dots + int dots = static_cast(automation_state_.pulse_animation) % 4; + std::string dot_string(dots, '.'); + ImGui::TextDisabled(" > %s", dot_string.c_str()); } else { - ImGui::BeginChild("ActionQueue", ImVec2(0, 80), false); + // Scrollable action list with retro styling + ImGui::BeginChild("ActionQueue", ImVec2(0, 100), true, + ImGuiWindowFlags_AlwaysVerticalScrollbar); + + // Add scanline effect (visual only) + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + ImVec2 win_pos = ImGui::GetWindowPos(); + ImVec2 win_size = ImGui::GetWindowSize(); + + // Draw scanlines + for (float y = 0; y < win_size.y; y += 4.0f) { + float offset_y = y + automation_state_.scanline_offset * 4.0f; + if (offset_y < win_size.y) { + draw_list->AddLine( + ImVec2(win_pos.x, win_pos.y + offset_y), + ImVec2(win_pos.x + win_size.x, win_pos.y + offset_y), + IM_COL32(0, 0, 0, 20)); + } + } for (const auto& test : automation_state_.recent_tests) { ImGui::PushID(test.test_id.c_str()); - // Status icon + // Status icon with animation for running tests ImVec4 action_color; const char* status_icon; + bool is_running = false; - if (test.status == "success" || test.status == "completed") { + if (test.status == "success" || test.status == "completed" || test.status == "passed") { action_color = theme.status_success; status_icon = ICON_MD_CHECK_CIRCLE; } else if (test.status == "running" || test.status == "in_progress") { - action_color = theme.provider_ollama; + is_running = true; + float running_pulse = 0.5f + 0.5f * std::sin(automation_state_.pulse_animation * 3.0f); + action_color = ImVec4( + theme.provider_ollama.x * running_pulse, + theme.provider_ollama.y * (0.8f + 0.2f * running_pulse), + theme.provider_ollama.z * running_pulse, + 1.0f + ); status_icon = ICON_MD_PENDING; } else if (test.status == "failed" || test.status == "error") { action_color = theme.status_error; @@ -1566,29 +1676,35 @@ void AgentChatWidget::RenderAutomationPanel() { status_icon = ICON_MD_HELP; } + // Icon with pulse ImGui::TextColored(action_color, "%s", status_icon); ImGui::SameLine(); - // Action name - ImGui::Text("%s", test.name.c_str()); + // Action name with monospace font + ImGui::Text("> %s", test.name.c_str()); // Timestamp if (test.updated_at != absl::InfinitePast()) { ImGui::SameLine(); auto elapsed = absl::Now() - test.updated_at; if (elapsed < absl::Seconds(60)) { - ImGui::TextDisabled("(%ds ago)", static_cast(absl::ToInt64Seconds(elapsed))); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), + "[%ds]", static_cast(absl::ToInt64Seconds(elapsed))); } else if (elapsed < absl::Minutes(60)) { - ImGui::TextDisabled("(%dm ago)", static_cast(absl::ToInt64Minutes(elapsed))); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), + "[%dm]", static_cast(absl::ToInt64Minutes(elapsed))); } else { - ImGui::TextDisabled("(%dh ago)", static_cast(absl::ToInt64Hours(elapsed))); + ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), + "[%dh]", static_cast(absl::ToInt64Hours(elapsed))); } } - // Message (if any) + // Message (if any) with indentation if (!test.message.empty()) { ImGui::Indent(20.0f); - ImGui::TextWrapped(ICON_MD_MESSAGE " %s", test.message.c_str()); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f)); + ImGui::TextWrapped(" %s %s", ICON_MD_MESSAGE, test.message.c_str()); + ImGui::PopStyleColor(); ImGui::Unindent(20.0f); } @@ -2751,6 +2867,58 @@ void AgentChatWidget::SetLastPlanSummary(const std::string& /* summary */) { } } +void AgentChatWidget::PollAutomationStatus() { + // Check if we should poll based on interval and auto-refresh setting + if (!automation_state_.auto_refresh_enabled) { + return; + } + + absl::Time now = absl::Now(); + absl::Duration elapsed = now - automation_state_.last_poll; + + if (elapsed < absl::Seconds(automation_state_.refresh_interval_seconds)) { + return; + } + + // Update last poll time + automation_state_.last_poll = now; + + // Check connection status + bool was_connected = automation_state_.harness_connected; + automation_state_.harness_connected = CheckHarnessConnection(); + + // Notify on status change + if (was_connected != automation_state_.harness_connected && toast_manager_) { + if (automation_state_.harness_connected) { + toast_manager_->Show(ICON_MD_CHECK_CIRCLE " Automation harness connected", + ToastType::kSuccess, 2.0f); + } else { + toast_manager_->Show(ICON_MD_WARNING " Automation harness disconnected", + ToastType::kWarning, 2.0f); + } + } +} + +bool AgentChatWidget::CheckHarnessConnection() { +#if defined(YAZE_WITH_GRPC) + try { + // Attempt to get harness summaries from TestManager + // If this succeeds, the harness infrastructure is working + auto summaries = test::TestManager::Get().ListHarnessTestSummaries(); + + // If we get here, the test manager is operational + // In a real implementation, you might want to ping the gRPC server + automation_state_.connection_attempts = 0; + return true; + } catch (const std::exception& e) { + automation_state_.connection_attempts++; + return false; + } +#else + return false; +#endif +} + void AgentChatWidget::SyncHistoryToPopup() { if (!chat_history_popup_) { return; diff --git a/src/app/editor/agent/agent_chat_widget.h b/src/app/editor/agent/agent_chat_widget.h index 60d16470..faae8e0d 100644 --- a/src/app/editor/agent/agent_chat_widget.h +++ b/src/app/editor/agent/agent_chat_widget.h @@ -125,6 +125,10 @@ class AgentChatWidget { void UpdateHarnessTelemetry(const AutomationTelemetry& telemetry); void SetLastPlanSummary(const std::string& summary); + + // Automation status polling + void PollAutomationStatus(); + bool CheckHarnessConnection(); void SetZ3EDCommandCallbacks(const Z3EDCommandCallbacks& callbacks) { z3ed_callbacks_ = callbacks; @@ -194,6 +198,13 @@ public: std::vector recent_tests; bool harness_connected = false; absl::Time last_poll = absl::InfinitePast(); + bool auto_refresh_enabled = true; + float refresh_interval_seconds = 2.0f; + float pulse_animation = 0.0f; + float scanline_offset = 0.0f; + int connection_attempts = 0; + absl::Time last_connection_attempt = absl::InfinitePast(); + std::string grpc_server_address = "localhost:50052"; }; // Agent Configuration State diff --git a/src/app/editor/agent/agent_editor.cc b/src/app/editor/agent/agent_editor.cc index cfebee09..3f5d3c50 100644 --- a/src/app/editor/agent/agent_editor.cc +++ b/src/app/editor/agent/agent_editor.cc @@ -140,8 +140,25 @@ void AgentEditor::DrawDashboard() { if (!active_) return; + // Animate retro effects + ImGuiIO& io = ImGui::GetIO(); + pulse_animation_ += io.DeltaTime * 2.0f; + scanline_offset_ += io.DeltaTime * 0.4f; + if (scanline_offset_ > 1.0f) scanline_offset_ -= 1.0f; + glitch_timer_ += io.DeltaTime * 5.0f; + blink_counter_ = static_cast(pulse_animation_ * 2.0f) % 2; + + // Pulsing glow for window + float pulse = 0.5f + 0.5f * std::sin(pulse_animation_); + ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ImVec4( + 0.1f + 0.1f * pulse, + 0.2f + 0.15f * pulse, + 0.3f + 0.2f * pulse, + 1.0f + )); + ImGui::SetNextWindowSize(ImVec2(1200, 800), ImGuiCond_FirstUseEver); - ImGui::Begin(ICON_MD_SMART_TOY " AI Agent Platform & Bot Creator", &active_, + ImGui::Begin(ICON_MD_SMART_TOY " AI AGENT PLATFORM [v0.4.x]", &active_, ImGuiWindowFlags_MenuBar); // Menu bar diff --git a/src/app/editor/agent/agent_editor.h b/src/app/editor/agent/agent_editor.h index 5f10cfbd..185c435e 100644 --- a/src/app/editor/agent/agent_editor.h +++ b/src/app/editor/agent/agent_editor.h @@ -103,6 +103,12 @@ class AgentEditor : public Editor { int max_tool_iterations = 4; }; + // Retro hacker animation state + float pulse_animation_ = 0.0f; + float scanline_offset_ = 0.0f; + float glitch_timer_ = 0.0f; + int blink_counter_ = 0; + AgentConfig GetCurrentConfig() const; void ApplyConfig(const AgentConfig& config); diff --git a/src/cli/agent.cmake b/src/cli/agent.cmake index d911cf62..fcc97057 100644 --- a/src/cli/agent.cmake +++ b/src/cli/agent.cmake @@ -68,6 +68,9 @@ set(YAZE_AGENT_SOURCES cli/service/agent/proposal_executor.cc cli/handlers/agent/tool_commands.cc cli/handlers/agent/gui_tool_commands.cc + cli/handlers/agent/dialogue_tool_commands.cc + cli/handlers/agent/music_tool_commands.cc + cli/handlers/agent/sprite_tool_commands.cc cli/handlers/agent/todo_commands.cc cli/handlers/agent/hex_commands.cc cli/handlers/agent/palette_commands.cc