feat: Introduce Agent UI Theme Management and Refactor Color Usage

- Added agent_ui_theme.h and agent_ui_theme.cc to centralize theme color definitions for the Agent UI, improving consistency across components.
- Updated AgentChatWidget and AgentChatHistoryPopup to utilize the new theme management system, replacing hardcoded colors with theme-derived values.
- Introduced agent_ui_theme.cc to define color properties for various UI elements, enhancing maintainability and visual coherence.
- Included agent_ui_theme.h in relevant files to streamline access to theme colors, promoting a cleaner codebase.
This commit is contained in:
scawful
2025-10-05 15:38:41 -04:00
parent cd6a6d9478
commit 8d6453df5e
5 changed files with 318 additions and 36 deletions

View File

@@ -20,6 +20,7 @@
#include "absl/time/time.h"
#include "app/core/project.h"
#include "app/editor/agent/agent_chat_history_codec.h"
#include "app/editor/agent/agent_ui_theme.h"
#include "app/editor/system/agent_chat_history_popup.h"
#include "app/editor/system/proposal_drawer.h"
#include "app/editor/system/toast_manager.h"
@@ -37,11 +38,6 @@ namespace {
using yaze::cli::agent::ChatMessage;
const ImVec4 kUserColor = ImVec4(0.88f, 0.76f, 0.36f, 1.0f);
const ImVec4 kAgentColor = ImVec4(0.56f, 0.82f, 0.62f, 1.0f);
const ImVec4 kJsonTextColor = ImVec4(0.78f, 0.83f, 0.90f, 1.0f);
const ImVec4 kProposalPanelColor = ImVec4(0.20f, 0.35f, 0.20f, 0.35f);
std::filesystem::path ExpandUserPath(std::string path) {
if (!path.empty() && path.front() == '~') {
const char* home = nullptr;
@@ -423,9 +419,10 @@ void AgentChatWidget::RenderMessage(const ChatMessage& msg, int index) {
}
ImGui::PushID(index);
const auto& theme = AgentUI::GetTheme();
const bool from_user = (msg.sender == ChatMessage::Sender::kUser);
const ImVec4 header_color = from_user ? kUserColor : kAgentColor;
const ImVec4 header_color = from_user ? theme.user_message_color : theme.agent_message_color;
const char* header_label = from_user ? "You" : "Agent";
ImGui::TextColored(header_color, "%s", header_label);
@@ -437,8 +434,8 @@ void AgentChatWidget::RenderMessage(const ChatMessage& msg, int index) {
// Add copy button for all messages
ImGui::SameLine();
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.3f, 0.4f, 0.6f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.4f, 0.4f, 0.5f, 0.8f));
ImGui::PushStyleColor(ImGuiCol_Button, theme.button_copy);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, theme.button_copy_hover);
if (ImGui::SmallButton(ICON_MD_CONTENT_COPY)) {
std::string copy_text = msg.message;
if (copy_text.empty() && msg.json_pretty.has_value()) {
@@ -460,7 +457,8 @@ void AgentChatWidget::RenderMessage(const ChatMessage& msg, int index) {
RenderTable(*msg.table_data);
} else if (msg.json_pretty.has_value()) {
// Don't show JSON as a message - it's internal structure
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.5f, 0.5f, 0.8f));
const auto& theme = AgentUI::GetTheme();
ImGui::PushStyleColor(ImGuiCol_Text, theme.json_text_color);
ImGui::TextDisabled(ICON_MD_DATA_OBJECT " (Structured response)");
ImGui::PopStyleColor();
} else {
@@ -483,14 +481,15 @@ void AgentChatWidget::RenderProposalQuickActions(const ChatMessage& msg,
return;
}
const auto& theme = AgentUI::GetTheme();
const auto& proposal = *msg.proposal;
ImGui::PushStyleColor(ImGuiCol_ChildBg, kProposalPanelColor);
ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.proposal_panel_bg);
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f);
ImGui::BeginChild(absl::StrFormat("proposal_panel_%d", index).c_str(),
ImVec2(0, ImGui::GetFrameHeight() * 3.2f), true,
ImGuiWindowFlags_None);
ImGui::TextColored(ImVec4(0.8f, 1.0f, 0.8f, 1.0f), "%s Proposal %s",
ImGui::TextColored(theme.proposal_accent, "%s Proposal %s",
ICON_MD_PREVIEW, proposal.id.c_str());
ImGui::Text("Changes: %d", proposal.change_count);
ImGui::Text("Commands: %d", proposal.executed_commands);
@@ -523,12 +522,13 @@ void AgentChatWidget::RenderProposalQuickActions(const ChatMessage& msg,
}
void AgentChatWidget::RenderHistory() {
const auto& theme = AgentUI::GetTheme();
const auto& history = agent_service_.GetHistory();
float reserved_height = ImGui::GetFrameHeightWithSpacing() * 4.0f;
reserved_height += 100.0f; // Reduced to 100 for much taller chat area
// Styled chat history container
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.08f, 0.08f, 0.10f, 0.95f));
ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.code_bg_color);
if (ImGui::BeginChild("History", ImVec2(0, -reserved_height), true,
ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
if (history.empty()) {
@@ -560,9 +560,10 @@ void AgentChatWidget::RenderHistory() {
}
void AgentChatWidget::RenderInputBox() {
const auto& theme = AgentUI::GetTheme();
ImGui::Separator();
ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
ICON_MD_EDIT " Message:");
ImGui::TextColored(theme.command_text_color, ICON_MD_EDIT " Message:");
bool submitted = ImGui::InputTextMultiline(
"##agent_input", input_buffer_, sizeof(input_buffer_), ImVec2(-1, 60.0f),
@@ -578,10 +579,11 @@ void AgentChatWidget::RenderInputBox() {
ImGui::Spacing();
// Send button row
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.5f, 0.7f, 0.8f));
// Send button row
ImGui::PushStyleColor(ImGuiCol_Button, theme.provider_gemini);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
ImVec4(0.196f, 0.6f, 0.8f, 1.0f));
ImVec4(theme.provider_gemini.x * 1.2f, theme.provider_gemini.y * 1.1f,
theme.provider_gemini.z, theme.provider_gemini.w));
if (ImGui::Button(absl::StrFormat("%s Send", ICON_MD_SEND).c_str(),
ImVec2(140, 0)) ||
send) {
@@ -1602,12 +1604,13 @@ void AgentChatWidget::UpdateAgentConfig(const AgentConfigState& config) {
}
void AgentChatWidget::RenderAgentConfigPanel() {
const auto& theme = AgentUI::GetTheme();
// Dense header (no collapsing)
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.12f, 0.14f, 0.18f, 0.95f));
ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.panel_bg_color);
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::Separator();
AgentUI::RenderSectionHeader(ICON_MD_SETTINGS, "Config", theme.command_text_color);
// Compact provider selection
int provider_idx = 0;

View File

@@ -0,0 +1,188 @@
#include "app/editor/agent/agent_ui_theme.h"
#include "app/gui/theme_manager.h"
#include "app/gui/color.h"
#include "imgui/imgui.h"
namespace yaze {
namespace editor {
// Global theme instance
static AgentUITheme g_agent_theme;
static bool g_theme_initialized = false;
AgentUITheme AgentUITheme::FromCurrentTheme() {
AgentUITheme theme;
const auto& current = gui::ThemeManager::Get().GetCurrentTheme();
// Message colors - derived from theme primary/secondary
theme.user_message_color = ImVec4(
current.primary.red * 1.1f,
current.primary.green * 0.95f,
current.primary.blue * 0.6f,
1.0f
);
theme.agent_message_color = ImVec4(
current.secondary.red * 0.9f,
current.secondary.green * 1.3f,
current.secondary.blue * 1.0f,
1.0f
);
theme.system_message_color = ImVec4(
current.info.red,
current.info.green,
current.info.blue,
1.0f
);
// Content colors
theme.json_text_color = ImVec4(0.78f, 0.83f, 0.90f, 1.0f);
theme.command_text_color = ImVec4(1.0f, 0.647f, 0.0f, 1.0f);
theme.code_bg_color = ImVec4(0.08f, 0.08f, 0.10f, 0.95f);
// UI element colors
theme.panel_bg_color = ImVec4(0.12f, 0.14f, 0.18f, 0.95f);
theme.panel_bg_darker = ImVec4(0.08f, 0.10f, 0.14f, 0.95f);
theme.panel_border_color = ConvertColorToImVec4(current.border);
theme.accent_color = ConvertColorToImVec4(current.accent);
// Status colors
theme.status_active = ConvertColorToImVec4(current.success);
theme.status_inactive = ImVec4(0.6f, 0.6f, 0.6f, 1.0f);
theme.status_success = ConvertColorToImVec4(current.success);
theme.status_warning = ConvertColorToImVec4(current.warning);
theme.status_error = ConvertColorToImVec4(current.error);
// Provider-specific colors
theme.provider_ollama = ImVec4(0.2f, 0.8f, 0.4f, 1.0f); // Green
theme.provider_gemini = ImVec4(0.196f, 0.6f, 0.8f, 1.0f); // Blue
theme.provider_mock = ImVec4(0.6f, 0.6f, 0.6f, 1.0f); // Gray
// Collaboration colors
theme.collaboration_active = ImVec4(0.133f, 0.545f, 0.133f, 1.0f); // Forest green
theme.collaboration_inactive = ImVec4(0.6f, 0.6f, 0.6f, 1.0f);
// Proposal colors
theme.proposal_panel_bg = ImVec4(0.20f, 0.35f, 0.20f, 0.35f);
theme.proposal_accent = ImVec4(0.8f, 1.0f, 0.8f, 1.0f);
// Button colors
theme.button_copy = ImVec4(0.3f, 0.3f, 0.4f, 0.6f);
theme.button_copy_hover = ImVec4(0.4f, 0.4f, 0.5f, 0.8f);
// Gradient colors
theme.gradient_top = ImVec4(0.18f, 0.22f, 0.28f, 1.0f);
theme.gradient_bottom = ImVec4(0.12f, 0.16f, 0.22f, 1.0f);
return theme;
}
namespace AgentUI {
const AgentUITheme& GetTheme() {
if (!g_theme_initialized) {
RefreshTheme();
}
return g_agent_theme;
}
void RefreshTheme() {
g_agent_theme = AgentUITheme::FromCurrentTheme();
g_theme_initialized = true;
}
void PushPanelStyle() {
const auto& theme = GetTheme();
ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.panel_bg_color);
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 6.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12, 12));
}
void PopPanelStyle() {
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
}
void RenderSectionHeader(const char* icon, const char* label, const ImVec4& color) {
ImGui::TextColored(color, "%s %s", icon, label);
ImGui::Separator();
}
void RenderStatusIndicator(const char* label, bool active) {
const auto& theme = GetTheme();
ImVec4 color = active ? theme.status_active : theme.status_inactive;
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImVec2 pos = ImGui::GetCursorScreenPos();
float radius = 4.0f;
pos.x += radius + 2;
pos.y += ImGui::GetTextLineHeight() * 0.5f;
draw_list->AddCircleFilled(pos, radius, ImGui::GetColorU32(color));
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + radius * 2 + 8);
ImGui::Text("%s", label);
}
void RenderProviderBadge(const char* provider) {
const auto& theme = GetTheme();
ImVec4 badge_color;
if (strcmp(provider, "ollama") == 0) {
badge_color = theme.provider_ollama;
} else if (strcmp(provider, "gemini") == 0) {
badge_color = theme.provider_gemini;
} else {
badge_color = theme.provider_mock;
}
ImGui::PushStyleColor(ImGuiCol_Button, badge_color);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 12.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 4));
ImGui::SmallButton(provider);
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
}
void VerticalSpacing(float amount) {
ImGui::Dummy(ImVec2(0, amount));
}
void HorizontalSpacing(float amount) {
ImGui::Dummy(ImVec2(amount, 0));
ImGui::SameLine();
}
bool StyledButton(const char* label, const ImVec4& color, const ImVec2& size) {
ImGui::PushStyleColor(ImGuiCol_Button, color);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
ImVec4(color.x * 1.2f, color.y * 1.2f, color.z * 1.2f, color.w));
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
ImVec4(color.x * 0.8f, color.y * 0.8f, color.z * 0.8f, color.w));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f);
bool result = ImGui::Button(label, size);
ImGui::PopStyleVar();
ImGui::PopStyleColor(3);
return result;
}
bool IconButton(const char* icon, const char* tooltip) {
bool result = ImGui::SmallButton(icon);
if (tooltip && ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", tooltip);
}
return result;
}
} // namespace AgentUI
} // namespace editor
} // namespace yaze

View File

@@ -0,0 +1,97 @@
#ifndef YAZE_APP_EDITOR_AGENT_AGENT_UI_THEME_H
#define YAZE_APP_EDITOR_AGENT_AGENT_UI_THEME_H
#include "imgui/imgui.h"
#include "app/gui/theme_manager.h"
#include "app/gui/color.h"
namespace yaze {
namespace editor {
/**
* @struct AgentUITheme
* @brief Centralized theme colors for Agent UI components
*
* All hardcoded colors from AgentChatWidget, AgentEditor, and AgentChatHistoryPopup
* are consolidated here and derived from the current theme.
*/
struct AgentUITheme {
// Message colors
ImVec4 user_message_color;
ImVec4 agent_message_color;
ImVec4 system_message_color;
// Content colors
ImVec4 json_text_color;
ImVec4 command_text_color;
ImVec4 code_bg_color;
// UI element colors
ImVec4 panel_bg_color;
ImVec4 panel_bg_darker;
ImVec4 panel_border_color;
ImVec4 accent_color;
// Status colors
ImVec4 status_active;
ImVec4 status_inactive;
ImVec4 status_success;
ImVec4 status_warning;
ImVec4 status_error;
// Provider colors
ImVec4 provider_ollama;
ImVec4 provider_gemini;
ImVec4 provider_mock;
// Collaboration colors
ImVec4 collaboration_active;
ImVec4 collaboration_inactive;
// Proposal colors
ImVec4 proposal_panel_bg;
ImVec4 proposal_accent;
// Button colors
ImVec4 button_copy;
ImVec4 button_copy_hover;
// Gradient colors
ImVec4 gradient_top;
ImVec4 gradient_bottom;
// Initialize from current theme
static AgentUITheme FromCurrentTheme();
};
// Helper functions for common UI patterns
namespace AgentUI {
// Get current theme colors
const AgentUITheme& GetTheme();
// Refresh theme from ThemeManager
void RefreshTheme();
// Common UI components
void PushPanelStyle();
void PopPanelStyle();
void RenderSectionHeader(const char* icon, const char* label, const ImVec4& color);
void RenderStatusIndicator(const char* label, bool active);
void RenderProviderBadge(const char* provider);
// Spacing helpers
void VerticalSpacing(float amount = 8.0f);
void HorizontalSpacing(float amount = 8.0f);
// Common button styles
bool StyledButton(const char* label, const ImVec4& color, const ImVec2& size = ImVec2(0, 0));
bool IconButton(const char* icon, const char* tooltip = nullptr);
} // namespace AgentUI
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_AGENT_AGENT_UI_THEME_H

View File

@@ -49,6 +49,7 @@ if(YAZE_WITH_GRPC)
list(APPEND YAZE_APP_EDITOR_SRC
app/editor/agent/agent_editor.cc
app/editor/agent/agent_chat_widget.cc
app/editor/agent/agent_ui_theme.cc
app/editor/agent/agent_collaboration_coordinator.cc
app/editor/agent/network_collaboration_coordinator.cc
app/editor/agent/automation_bridge.cc

View File

@@ -4,25 +4,16 @@
#include "absl/strings/str_format.h"
#include "absl/time/time.h"
#include "imgui/imgui.h"
#include "app/gui/icons.h"
#include "app/editor/agent/agent_ui_theme.h"
#include "app/editor/system/toast_manager.h"
#include "app/gui/icons.h"
#include "app/gui/style.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
namespace yaze {
namespace editor {
namespace {
// Theme-matched colors
const ImVec4 kUserColor = ImVec4(0.90f, 0.70f, 0.00f, 1.0f); // Gold
const ImVec4 kAgentColor = ImVec4(0.40f, 0.76f, 0.64f, 1.0f); // Teal
const ImVec4 kTimestampColor = ImVec4(0.6f, 0.6f, 0.6f, 0.9f);
const ImVec4 kAccentColor = ImVec4(0.26f, 0.59f, 0.98f, 1.0f); // Theme blue
const ImVec4 kBackgroundColor = ImVec4(0.10f, 0.10f, 0.13f, 0.98f); // Darker
const ImVec4 kHeaderColor = ImVec4(0.14f, 0.14f, 0.16f, 1.0f);
} // namespace
AgentChatHistoryPopup::AgentChatHistoryPopup() {
std::memset(input_buffer_, 0, sizeof(input_buffer_));
}
@@ -30,6 +21,8 @@ AgentChatHistoryPopup::AgentChatHistoryPopup() {
void AgentChatHistoryPopup::Draw() {
if (!visible_) return;
const auto& theme = AgentUI::GetTheme();
// Set drawer position on the LEFT side (full height)
ImGuiIO& io = ImGui::GetIO();
@@ -54,7 +47,7 @@ void AgentChatHistoryPopup::Draw() {
// Calculate proper list height
float list_height = ImGui::GetContentRegionAvail().y - 220.0f;
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.05f, 0.05f, 0.08f, 0.95f));
ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.code_bg_color);
ImGui::BeginChild("MessageList", ImVec2(0, list_height), true, ImGuiWindowFlags_AlwaysVerticalScrollbar);
DrawMessageList();