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:
@@ -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<int>(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<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(
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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<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")) {
|
||||
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<int>(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<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)) {
|
||||
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 {
|
||||
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()) {
|
||||
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;
|
||||
|
||||
@@ -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<AutomationTelemetry> 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
|
||||
|
||||
@@ -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<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::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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user