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.
This commit is contained in:
scawful
2025-10-06 01:33:08 -04:00
parent 7ba8d5b443
commit 8688f0c502
7 changed files with 368 additions and 71 deletions

View File

@@ -23,9 +23,15 @@ void AgentChatHistoryPopup::Draw() {
const auto& theme = AgentUI::GetTheme(); const auto& theme = AgentUI::GetTheme();
// Set drawer position on the LEFT side (full height) // Animate retro effects
ImGuiIO& io = ImGui::GetIO(); 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<int>(pulse_animation_ * 2.0f) % 2;
// Set drawer position on the LEFT side (full height)
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(drawer_width_, io.DisplaySize.y), ImGuiCond_Always); ImGui::SetNextWindowSize(ImVec2(drawer_width_, io.DisplaySize.y), ImGuiCond_Always);
@@ -34,10 +40,19 @@ void AgentChatHistoryPopup::Draw() {
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoTitleBar; ImGuiWindowFlags_NoTitleBar;
// Use current theme colors // Use current theme colors with slight glow
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10, 10)); 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)) { if (ImGui::Begin("##AgentChatPopup", &visible_, flags)) {
DrawHeader(); DrawHeader();
@@ -47,8 +62,30 @@ void AgentChatHistoryPopup::Draw() {
// Calculate proper list height // Calculate proper list height
float list_height = ImGui::GetContentRegionAvail().y - 220.0f; 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); 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(); DrawMessageList();
if (needs_scroll_) { if (needs_scroll_) {
@@ -72,6 +109,7 @@ void AgentChatHistoryPopup::Draw() {
} }
ImGui::End(); ImGui::End();
ImGui::PopStyleColor(); // Border color
ImGui::PopStyleVar(2); 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); bool from_user = (msg.sender == cli::agent::ChatMessage::Sender::kUser);
// Use theme colors with slight tint // Retro terminal colors
ImVec4 text_color = ImGui::GetStyleColorVec4(ImGuiCol_Text);
ImVec4 header_color = from_user 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(1.0f, 0.85f, 0.0f, 1.0f) // Amber/Gold for user
: ImVec4(text_color.x * 0.7f, text_color.y * 1.0f, text_color.z * 0.9f, 1.0f); // Teal tint : 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::TextColored(header_color, "%s", sender_label);
ImGui::SameLine(); 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 // Message content with terminal styling
ImGui::Indent(10.0f); ImGui::Indent(15.0f);
if (msg.table_data.has_value()) { 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()) { } 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 { } else {
// Truncate long messages // Truncate long messages with ellipsis
std::string content = msg.message; std::string content = msg.message;
if (content.length() > 200) { if (content.length() > 200) {
content = content.substr(0, 197) + "..."; 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()) { if (msg.proposal.has_value()) {
ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.4f, 1.0f), float proposal_pulse = 0.7f + 0.3f * std::sin(pulse_animation_ * 2.0f);
ICON_MD_PREVIEW " Proposal: %s", msg.proposal->id.c_str()); 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::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(); ImGui::PopID();
} }
void AgentChatHistoryPopup::DrawHeader() { void AgentChatHistoryPopup::DrawHeader() {
// Theme-matched header with subtle gradient const auto& theme = AgentUI::GetTheme();
ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImVec2 header_start = ImGui::GetCursorScreenPos(); ImVec2 header_start = ImGui::GetCursorScreenPos();
ImVec2 header_size(ImGui::GetContentRegionAvail().x, 55); ImVec2 header_size(ImGui::GetContentRegionAvail().x, 55);
// Subtle gradient matching theme // Retro gradient with pulse
ImU32 color_top = ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_WindowBg)); float pulse = 0.5f + 0.5f * std::sin(pulse_animation_);
ImU32 color_bottom = ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_ChildBg)); 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( draw_list->AddRectFilledMultiColor(
header_start, header_start,
ImVec2(header_start.x + header_size.x, header_start.y + header_size.y), ImVec2(header_start.x + header_size.x, header_start.y + header_size.y),
color_top, color_top, color_bottom, color_bottom); color_top, color_top, color_bottom, color_bottom);
// Thin accent line (no pulse - matches theme better) // Pulsing accent line with glow
ImU32 accent_color = ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_Separator)); float line_pulse = 0.6f + 0.4f * std::sin(pulse_animation_ * 0.7f);
ImU32 accent_color = IM_COL32(
static_cast<int>(theme.provider_ollama.x * 255 * line_pulse),
static_cast<int>(theme.provider_ollama.y * 255 * line_pulse),
static_cast<int>(theme.provider_ollama.z * 255 * line_pulse + 50),
200
);
draw_list->AddLine( draw_list->AddLine(
ImVec2(header_start.x, header_start.y + header_size.y), ImVec2(header_start.x, header_start.y + header_size.y),
ImVec2(header_start.x + header_size.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)); ImGui::Dummy(ImVec2(0, 8));
// Title and provider dropdown (like connection header) // Title with pulsing glow
ImGui::Text(ICON_MD_CHAT); ImVec4 title_color = ImVec4(
ImGui::SameLine(); 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::SameLine();
ImGui::SetNextItemWidth(120); ImGui::TextDisabled("[v0.4.x]");
static int provider_idx = 0;
const char* providers[] = {"Mock", "Ollama", "Gemini"};
ImGui::Combo("##popup_provider", &provider_idx, providers, 3);
// Buttons properly spaced from right edge // Buttons properly spaced from right edge
ImGui::SameLine(ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x - 75.0f); 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)) { if (ImGui::SmallButton(compact_mode_ ? ICON_MD_UNFOLD_MORE : ICON_MD_UNFOLD_LESS)) {
compact_mode_ = !compact_mode_; compact_mode_ = !compact_mode_;
} }
if (blink_counter_ == 0 && compact_mode_) {
ImGui::PopStyleColor();
}
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(compact_mode_ ? "Expand view" : "Compact view"); ImGui::SetTooltip(compact_mode_ ? "Expand view" : "Compact view");
} }
ImGui::SameLine(); ImGui::SameLine();
// Full chat button (closes popup when opened) // Full chat button
if (ImGui::SmallButton(ICON_MD_OPEN_IN_NEW)) { if (ImGui::SmallButton(ICON_MD_OPEN_IN_NEW)) {
if (open_chat_callback_) { if (open_chat_callback_) {
open_chat_callback_(); open_chat_callback_();
visible_ = false; // Close popup when opening main chat visible_ = false;
} }
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
@@ -216,7 +294,7 @@ void AgentChatHistoryPopup::DrawHeader() {
ImGui::SetTooltip("Close (Ctrl+H)"); ImGui::SetTooltip("Close (Ctrl+H)");
} }
// Message count with badge // Message count with retro styling
int visible_count = 0; int visible_count = 0;
for (const auto& msg : messages_) { for (const auto& msg : messages_) {
if (msg.is_internal) continue; if (msg.is_internal) continue;
@@ -228,8 +306,16 @@ void AgentChatHistoryPopup::DrawHeader() {
} }
ImGui::Spacing(); ImGui::Spacing();
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 0.9f), ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f),
"%d message%s", visible_count, visible_count == 1 ? "" : "s"); "> 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)); ImGui::Dummy(ImVec2(0, 5));
} }

View File

@@ -112,6 +112,12 @@ class AgentChatHistoryPopup {
float header_pulse_ = 0.0f; float header_pulse_ = 0.0f;
int unread_count_ = 0; 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 // Dependencies
ToastManager* toast_manager_ = nullptr; ToastManager* toast_manager_ = nullptr;
OpenChatCallback open_chat_callback_; OpenChatCallback open_chat_callback_;

View File

@@ -1491,29 +1491,91 @@ void AgentChatWidget::RenderAutomationPanel() {
const auto& theme = AgentUI::GetTheme(); const auto& theme = AgentUI::GetTheme();
ImGui::PushID("AutomationPanel"); ImGui::PushID("AutomationPanel");
AgentUI::PushPanelStyle(); // Auto-poll for status updates
if (ImGui::BeginChild("Automation_Panel", ImVec2(0, 180), true)) { PollAutomationStatus();
AgentUI::RenderSectionHeader(ICON_MD_SMART_TOY, "GUI Automation",
theme.provider_ollama);
// Connection status // Animate pulse and scanlines for retro effect
bool connected = automation_state_.harness_connected; automation_state_.pulse_animation += ImGui::GetIO().DeltaTime * 2.0f;
ImVec4 status_color = connected ? theme.status_success : theme.status_warning; automation_state_.scanline_offset += ImGui::GetIO().DeltaTime * 0.5f;
const char* status_text = connected ? "Connected" : "Disconnected"; 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::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<int>(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")) { if (ImGui::SmallButton(ICON_MD_REFRESH " Refresh")) {
PollAutomationStatus();
if (automation_callbacks_.show_active_tests) { if (automation_callbacks_.show_active_tests) {
automation_callbacks_.show_active_tests(); automation_callbacks_.show_active_tests();
} }
} }
if (auto_ref_pulse) {
ImGui::PopStyleColor();
}
if (ImGui::IsItemHovered()) { 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 // Quick action buttons
@@ -1523,6 +1585,9 @@ void AgentChatWidget::RenderAutomationPanel() {
automation_callbacks_.open_harness_dashboard(); automation_callbacks_.open_harness_dashboard();
} }
} }
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Open automation dashboard");
}
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::SmallButton(ICON_MD_REPLAY " Replay")) { if (ImGui::SmallButton(ICON_MD_REPLAY " Replay")) {
@@ -1530,33 +1595,78 @@ void AgentChatWidget::RenderAutomationPanel() {
automation_callbacks_.replay_last_plan(); automation_callbacks_.replay_last_plan();
} }
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Replay last automation plan"); 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::Spacing();
ImGui::Separator(); 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()) { 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<int>(automation_state_.pulse_animation) % 4;
std::string dot_string(dots, '.');
ImGui::TextDisabled(" > %s", dot_string.c_str());
} else { } 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) { for (const auto& test : automation_state_.recent_tests) {
ImGui::PushID(test.test_id.c_str()); ImGui::PushID(test.test_id.c_str());
// Status icon // Status icon with animation for running tests
ImVec4 action_color; ImVec4 action_color;
const char* status_icon; 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; action_color = theme.status_success;
status_icon = ICON_MD_CHECK_CIRCLE; status_icon = ICON_MD_CHECK_CIRCLE;
} else if (test.status == "running" || test.status == "in_progress") { } 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; status_icon = ICON_MD_PENDING;
} else if (test.status == "failed" || test.status == "error") { } else if (test.status == "failed" || test.status == "error") {
action_color = theme.status_error; action_color = theme.status_error;
@@ -1566,29 +1676,35 @@ void AgentChatWidget::RenderAutomationPanel() {
status_icon = ICON_MD_HELP; status_icon = ICON_MD_HELP;
} }
// Icon with pulse
ImGui::TextColored(action_color, "%s", status_icon); ImGui::TextColored(action_color, "%s", status_icon);
ImGui::SameLine(); ImGui::SameLine();
// Action name // Action name with monospace font
ImGui::Text("%s", test.name.c_str()); ImGui::Text("> %s", test.name.c_str());
// Timestamp // Timestamp
if (test.updated_at != absl::InfinitePast()) { if (test.updated_at != absl::InfinitePast()) {
ImGui::SameLine(); ImGui::SameLine();
auto elapsed = absl::Now() - test.updated_at; auto elapsed = absl::Now() - test.updated_at;
if (elapsed < absl::Seconds(60)) { if (elapsed < absl::Seconds(60)) {
ImGui::TextDisabled("(%ds ago)", static_cast<int>(absl::ToInt64Seconds(elapsed))); ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
"[%ds]", static_cast<int>(absl::ToInt64Seconds(elapsed)));
} else if (elapsed < absl::Minutes(60)) { } else if (elapsed < absl::Minutes(60)) {
ImGui::TextDisabled("(%dm ago)", static_cast<int>(absl::ToInt64Minutes(elapsed))); ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
"[%dm]", static_cast<int>(absl::ToInt64Minutes(elapsed)));
} else { } else {
ImGui::TextDisabled("(%dh ago)", static_cast<int>(absl::ToInt64Hours(elapsed))); ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
"[%dh]", static_cast<int>(absl::ToInt64Hours(elapsed)));
} }
} }
// Message (if any) // Message (if any) with indentation
if (!test.message.empty()) { if (!test.message.empty()) {
ImGui::Indent(20.0f); 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); 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() { void AgentChatWidget::SyncHistoryToPopup() {
if (!chat_history_popup_) { if (!chat_history_popup_) {
return; return;

View File

@@ -125,6 +125,10 @@ class AgentChatWidget {
void UpdateHarnessTelemetry(const AutomationTelemetry& telemetry); void UpdateHarnessTelemetry(const AutomationTelemetry& telemetry);
void SetLastPlanSummary(const std::string& summary); void SetLastPlanSummary(const std::string& summary);
// Automation status polling
void PollAutomationStatus();
bool CheckHarnessConnection();
void SetZ3EDCommandCallbacks(const Z3EDCommandCallbacks& callbacks) { void SetZ3EDCommandCallbacks(const Z3EDCommandCallbacks& callbacks) {
z3ed_callbacks_ = callbacks; z3ed_callbacks_ = callbacks;
@@ -194,6 +198,13 @@ public:
std::vector<AutomationTelemetry> recent_tests; std::vector<AutomationTelemetry> recent_tests;
bool harness_connected = false; bool harness_connected = false;
absl::Time last_poll = absl::InfinitePast(); 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 // Agent Configuration State

View File

@@ -140,8 +140,25 @@ void AgentEditor::DrawDashboard() {
if (!active_) if (!active_)
return; 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<int>(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::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); ImGuiWindowFlags_MenuBar);
// Menu bar // Menu bar

View File

@@ -103,6 +103,12 @@ class AgentEditor : public Editor {
int max_tool_iterations = 4; 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; AgentConfig GetCurrentConfig() const;
void ApplyConfig(const AgentConfig& config); void ApplyConfig(const AgentConfig& config);

View File

@@ -68,6 +68,9 @@ set(YAZE_AGENT_SOURCES
cli/service/agent/proposal_executor.cc cli/service/agent/proposal_executor.cc
cli/handlers/agent/tool_commands.cc cli/handlers/agent/tool_commands.cc
cli/handlers/agent/gui_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/todo_commands.cc
cli/handlers/agent/hex_commands.cc cli/handlers/agent/hex_commands.cc
cli/handlers/agent/palette_commands.cc cli/handlers/agent/palette_commands.cc