feat: Implement Z3ED Command Callbacks for Proposal Management in EditorManager

- Added Z3ED command callbacks for managing proposals within the AgentChatWidget, enhancing user interaction with proposal acceptance, rejection, listing, and diff viewing functionalities.
- Updated the AgentChatWidget to initialize default chat sessions and improved the chat history popup with new message sending and snapshot capturing capabilities.
- Enhanced the UI of the AgentEditor with a compact tabbed interface for better organization of bot management and session history, improving overall user experience.
This commit is contained in:
scawful
2025-10-05 05:51:53 -04:00
parent fbcfadbd11
commit 38915a5162
5 changed files with 447 additions and 117 deletions

View File

@@ -105,6 +105,12 @@ AgentChatWidget::AgentChatWidget() {
memset(input_buffer_, 0, sizeof(input_buffer_));
history_path_ = ResolveHistoryPath();
history_supported_ = AgentChatHistoryCodec::Available();
// Initialize default session
if (chat_sessions_.empty()) {
chat_sessions_.emplace_back("default", "Main Session");
active_session_index_ = 0;
}
}
void AgentChatWidget::SetRomContext(Rom* rom) {
@@ -126,15 +132,42 @@ void AgentChatWidget::SetProposalDrawer(ProposalDrawer* drawer) {
void AgentChatWidget::SetChatHistoryPopup(AgentChatHistoryPopup* popup) {
chat_history_popup_ = popup;
if (!chat_history_popup_) return;
// Set up callback to open this chat window
if (chat_history_popup_) {
chat_history_popup_->SetOpenChatCallback([this]() {
this->set_active(true);
});
// Initial sync
SyncHistoryToPopup();
}
chat_history_popup_->SetOpenChatCallback([this]() {
this->set_active(true);
});
// Set up callback to send messages from popup
chat_history_popup_->SetSendMessageCallback([this](const std::string& message) {
// Send message through the agent service
auto response = agent_service_.SendMessage(message);
HandleAgentResponse(response);
PersistHistory();
});
// Set up callback to capture snapshots from popup
chat_history_popup_->SetCaptureSnapshotCallback([this]() {
if (multimodal_callbacks_.capture_snapshot) {
std::filesystem::path output_path;
auto status = multimodal_callbacks_.capture_snapshot(&output_path);
if (status.ok()) {
multimodal_state_.last_capture_path = output_path;
multimodal_state_.last_updated = absl::Now();
if (toast_manager_) {
toast_manager_->Show(ICON_MD_PHOTO " Screenshot captured", ToastType::kSuccess, 2.5f);
}
} else if (toast_manager_) {
toast_manager_->Show(
absl::StrFormat("Capture failed: %s", status.message()),
ToastType::kError, 3.0f);
}
}
});
// Initial sync
SyncHistoryToPopup();
}
void AgentChatWidget::EnsureHistoryLoaded() {
@@ -647,6 +680,7 @@ void AgentChatWidget::Draw() {
if (ImGui::BeginMenu(ICON_MD_MENU " Actions")) {
if (ImGui::MenuItem(ICON_MD_DELETE_FOREVER " Clear History")) {
agent_service_.ResetConversation();
SyncHistoryToPopup();
if (toast_manager_) {
toast_manager_->Show("Chat history cleared", ToastType::kInfo, 2.5f);
}
@@ -654,6 +688,7 @@ void AgentChatWidget::Draw() {
ImGui::Separator();
if (ImGui::MenuItem(ICON_MD_REFRESH " Reset Conversation")) {
agent_service_.ResetConversation();
SyncHistoryToPopup();
if (toast_manager_) {
toast_manager_->Show("Conversation reset", ToastType::kInfo, 2.5f);
}
@@ -664,9 +699,42 @@ void AgentChatWidget::Draw() {
toast_manager_->Show("Export not yet implemented", ToastType::kWarning);
}
}
ImGui::Separator();
if (ImGui::MenuItem(ICON_MD_ADD " New Session Tab")) {
// Create new session
if (!chat_sessions_.empty()) {
ChatSession new_session(
absl::StrFormat("session_%d", chat_sessions_.size()),
absl::StrFormat("Session %d", chat_sessions_.size() + 1));
chat_sessions_.push_back(std::move(new_session));
active_session_index_ = chat_sessions_.size() - 1;
if (toast_manager_) {
toast_manager_->Show("New session created", ToastType::kSuccess);
}
}
}
ImGui::EndMenu();
}
// Session tabs in menu bar (if multiple sessions)
if (!chat_sessions_.empty() && chat_sessions_.size() > 1) {
ImGui::Separator();
for (size_t i = 0; i < chat_sessions_.size(); ++i) {
bool is_active = (i == active_session_index_);
if (is_active) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2f, 0.8f, 0.4f, 1.0f));
}
if (ImGui::MenuItem(chat_sessions_[i].name.c_str(), nullptr, is_active)) {
active_session_index_ = i;
history_loaded_ = false; // Trigger reload
SyncHistoryToPopup();
}
if (is_active) {
ImGui::PopStyleColor();
}
}
}
ImGui::EndMenuBar();
}

View File

@@ -12,6 +12,7 @@
#include "app/editor/system/toast_manager.h"
#include "app/rom.h"
#include "app/gui/icons.h"
#include "app/core/platform/asset_loader.h"
#include "util/file_util.h"
#ifdef YAZE_WITH_GRPC
@@ -170,40 +171,64 @@ void AgentEditor::DrawDashboard() {
ImGui::EndMenuBar();
}
// Tabbed interface for dense organization
// Compact tabbed interface (combined tabs)
if (ImGui::BeginTabBar("AgentEditorTabs", ImGuiTabBarFlags_None)) {
// Configuration Tab
if (ImGui::BeginTabItem(ICON_MD_SETTINGS " Configuration")) {
// Bot Management Tab (combines Config + Profiles + Prompts)
if (ImGui::BeginTabItem(ICON_MD_SMART_TOY " Bot Studio")) {
ImGui::Spacing();
DrawConfigurationPanel();
// Three-column layout with prompt editor in center
if (ImGui::BeginTable("BotStudioLayout", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) {
ImGui::TableSetupColumn("Config", ImGuiTableColumnFlags_WidthFixed, 380.0f);
ImGui::TableSetupColumn("Prompt Editor", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("Profiles", ImGuiTableColumnFlags_WidthFixed, 320.0f);
ImGui::TableNextRow();
// LEFT: Configuration
ImGui::TableSetColumnIndex(0);
ImGui::BeginChild("ConfigSection", ImVec2(0, 0), false);
DrawConfigurationPanel();
ImGui::EndChild();
// CENTER: System Prompt Editor
ImGui::TableSetColumnIndex(1);
ImGui::BeginChild("PromptSection", ImVec2(0, 0), false);
DrawPromptEditorPanel();
ImGui::EndChild();
// RIGHT: Bot Profiles List
ImGui::TableSetColumnIndex(2);
ImGui::BeginChild("ProfilesSection", ImVec2(0, 0), false);
DrawBotProfilesPanel();
ImGui::EndChild();
ImGui::EndTable();
}
ImGui::EndTabItem();
}
// System Prompts Tab
if (ImGui::BeginTabItem(ICON_MD_EDIT " System Prompts")) {
// Session Manager Tab (combines History + Metrics)
if (ImGui::BeginTabItem(ICON_MD_HISTORY " Sessions & History")) {
ImGui::Spacing();
DrawPromptEditorPanel();
ImGui::EndTabItem();
}
// Bot Profiles Tab
if (ImGui::BeginTabItem(ICON_MD_FOLDER " Bot Profiles")) {
ImGui::Spacing();
DrawBotProfilesPanel();
ImGui::EndTabItem();
}
// Chat History Tab
if (ImGui::BeginTabItem(ICON_MD_HISTORY " Chat History")) {
ImGui::Spacing();
DrawChatHistoryViewer();
ImGui::EndTabItem();
}
// Metrics Tab
if (ImGui::BeginTabItem(ICON_MD_ANALYTICS " Metrics")) {
ImGui::Spacing();
DrawAdvancedMetricsPanel();
// Two-column layout
if (ImGui::BeginTable("SessionLayout", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) {
ImGui::TableSetupColumn("History", ImGuiTableColumnFlags_WidthStretch, 0.6f);
ImGui::TableSetupColumn("Metrics", ImGuiTableColumnFlags_WidthStretch, 0.4f);
ImGui::TableNextRow();
// LEFT: Chat History
ImGui::TableSetColumnIndex(0);
DrawChatHistoryViewer();
// RIGHT: Metrics
ImGui::TableSetColumnIndex(1);
DrawAdvancedMetricsPanel();
ImGui::EndTable();
}
ImGui::EndTabItem();
}
@@ -469,13 +494,13 @@ void AgentEditor::DrawMetricsPanel() {
}
void AgentEditor::DrawPromptEditorPanel() {
ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_EDIT " System Prompt Editor");
ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_EDIT " Prompt Editor");
ImGui::Separator();
ImGui::Spacing();
// Prompt file selector
ImGui::Text("Active Prompt:");
ImGui::SameLine();
// Compact prompt file selector
ImGui::Text("File:");
ImGui::SetNextItemWidth(-45);
if (ImGui::BeginCombo("##prompt_file", active_prompt_file_.c_str())) {
if (ImGui::Selectable("system_prompt.txt", active_prompt_file_ == "system_prompt.txt")) {
active_prompt_file_ = "system_prompt.txt";
@@ -493,30 +518,34 @@ void AgentEditor::DrawPromptEditorPanel() {
}
ImGui::SameLine();
if (ImGui::Button(ICON_MD_REFRESH " Reload")) {
if (ImGui::SmallButton(ICON_MD_REFRESH)) {
prompt_editor_initialized_ = false;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Reload from disk");
}
// Load prompt file if not initialized
if (!prompt_editor_initialized_ && prompt_editor_) {
// Build path to assets/agent directory
std::filesystem::path base_path;
#ifdef __APPLE__
base_path = std::filesystem::path(yaze::util::GetBundleResourcePath()) / "Contents" / "Resources" / "agent";
#else
base_path = std::filesystem::path("assets") / "agent";
#endif
std::string asset_path = "agent/" + active_prompt_file_;
auto content_result = core::AssetLoader::LoadTextFile(asset_path);
std::filesystem::path prompt_path = base_path / active_prompt_file_;
if (std::filesystem::exists(prompt_path)) {
std::ifstream file(prompt_path);
if (file.is_open()) {
std::string content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
prompt_editor_->SetText(content);
current_profile_.system_prompt = content;
prompt_editor_initialized_ = true;
if (content_result.ok()) {
prompt_editor_->SetText(*content_result);
current_profile_.system_prompt = *content_result;
prompt_editor_initialized_ = true;
} else {
// Only show error on first attempt (don't spam)
static bool error_shown = false;
if (!error_shown && toast_manager_) {
toast_manager_->Show(
absl::StrFormat("Prompt file not found: %s", active_prompt_file_),
ToastType::kWarning, 3.0f);
error_shown = true;
}
// Set placeholder text
prompt_editor_->SetText("# System prompt file not found\n# Please check assets/agent/ directory");
prompt_editor_initialized_ = true; // Don't retry every frame
}
}

View File

@@ -329,6 +329,47 @@ void EditorManager::Initialize(const std::string& filename) {
return absl::OkStatus();
};
agent_editor_.GetChatWidget()->SetMultimodalCallbacks(multimodal_callbacks);
// Set up Z3ED command callbacks for proposal management
AgentChatWidget::Z3EDCommandCallbacks z3ed_callbacks;
z3ed_callbacks.accept_proposal = [this](const std::string& proposal_id) -> absl::Status {
// Use ProposalDrawer's existing logic
proposal_drawer_.Show();
proposal_drawer_.FocusProposal(proposal_id);
toast_manager_.Show(
absl::StrFormat("%s View proposal %s in drawer to accept", ICON_MD_PREVIEW, proposal_id),
ToastType::kInfo, 3.5f);
return absl::OkStatus();
};
z3ed_callbacks.reject_proposal = [this](const std::string& proposal_id) -> absl::Status {
// Use ProposalDrawer's existing logic
proposal_drawer_.Show();
proposal_drawer_.FocusProposal(proposal_id);
toast_manager_.Show(
absl::StrFormat("%s View proposal %s in drawer to reject", ICON_MD_PREVIEW, proposal_id),
ToastType::kInfo, 3.0f);
return absl::OkStatus();
};
z3ed_callbacks.list_proposals = []() -> absl::StatusOr<std::vector<std::string>> {
// Return empty for now - ProposalDrawer handles the real list
return std::vector<std::string>{};
};
z3ed_callbacks.diff_proposal = [this](const std::string& proposal_id) -> absl::StatusOr<std::string> {
// Open drawer to show diff
proposal_drawer_.Show();
proposal_drawer_.FocusProposal(proposal_id);
return "See diff in proposal drawer";
};
agent_editor_.GetChatWidget()->SetZ3EDCommandCallbacks(z3ed_callbacks);
#endif
// Load critical user settings first

View File

@@ -1,5 +1,7 @@
#include "app/editor/system/agent_chat_history_popup.h"
#include <cstring>
#include "absl/strings/str_format.h"
#include "absl/time/time.h"
#include "imgui/imgui.h"
@@ -14,15 +16,20 @@ namespace {
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 kTimestampColor = ImVec4(0.6f, 0.6f, 0.6f, 1.0f);
const ImVec4 kAccentColor = ImVec4(0.196f, 0.6f, 0.8f, 1.0f);
const ImVec4 kBackgroundColor = ImVec4(0.08f, 0.08f, 0.12f, 0.98f);
const ImVec4 kHeaderColor = ImVec4(0.12f, 0.14f, 0.18f, 1.0f);
} // namespace
AgentChatHistoryPopup::AgentChatHistoryPopup() {}
AgentChatHistoryPopup::AgentChatHistoryPopup() {
std::memset(input_buffer_, 0, sizeof(input_buffer_));
}
void AgentChatHistoryPopup::Draw() {
if (!visible_) return;
// Set drawer position on the LEFT side
// Set drawer position on the LEFT side with beautiful styling
ImGuiIO& io = ImGui::GetIO();
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(drawer_width_, io.DisplaySize.y),
@@ -30,61 +37,30 @@ void AgentChatHistoryPopup::Draw() {
ImGuiWindowFlags flags = ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoCollapse;
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoTitleBar;
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.08f, 0.10f, 0.95f));
// Beautiful gradient background
ImGui::PushStyleColor(ImGuiCol_WindowBg, kBackgroundColor);
ImGui::PushStyleColor(ImGuiCol_Border, kAccentColor);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12, 12));
if (ImGui::Begin(ICON_MD_CHAT " Chat History", &visible_, flags)) {
// Header with controls
if (ImGui::Button(ICON_MD_OPEN_IN_NEW " Open Full Chat")) {
if (open_chat_callback_) {
open_chat_callback_();
}
}
if (ImGui::Begin("##AgentChatPopup", &visible_, flags)) {
// Animated header pulse
header_pulse_ += io.DeltaTime * 2.0f;
float pulse = 0.5f + 0.5f * sinf(header_pulse_);
ImGui::SameLine();
if (ImGui::Button(ICON_MD_REFRESH " Refresh")) {
// Trigger external refresh through callback
if (toast_manager_) {
toast_manager_->Show("Refreshing chat history...", ToastType::kInfo, 1.5f);
}
}
DrawHeader();
ImGui::Separator();
// Filter dropdown
const char* filter_labels[] = {"All Messages", "User Only", "Agent Only"};
int current_filter = static_cast<int>(message_filter_);
ImGui::SetNextItemWidth(-1);
if (ImGui::Combo("##filter", &current_filter, filter_labels, 3)) {
message_filter_ = static_cast<MessageFilter>(current_filter);
}
ImGui::Spacing();
// Auto-scroll checkbox
ImGui::Checkbox("Auto-scroll", &auto_scroll_);
// Message list with gradient background
float list_height = io.DisplaySize.y - 280.0f; // Reserve space for input and actions
ImGui::Separator();
// Message count indicator
int visible_count = 0;
for (const auto& msg : messages_) {
if (msg.is_internal) continue;
if (message_filter_ == MessageFilter::kUserOnly &&
msg.sender != cli::agent::ChatMessage::Sender::kUser) continue;
if (message_filter_ == MessageFilter::kAgentOnly &&
msg.sender != cli::agent::ChatMessage::Sender::kAgent) continue;
visible_count++;
}
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
"%d message%s", visible_count, visible_count == 1 ? "" : "s");
ImGui::Separator();
// Message list
ImGui::BeginChild("MessageList", ImVec2(0, -45), true);
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.05f, 0.05f, 0.08f, 0.95f));
ImGui::BeginChild("MessageList", ImVec2(0, list_height), true, ImGuiWindowFlags_AlwaysVerticalScrollbar);
DrawMessageList();
if (needs_scroll_) {
@@ -93,14 +69,23 @@ void AgentChatHistoryPopup::Draw() {
}
ImGui::EndChild();
ImGui::PopStyleColor();
// Action buttons at bottom
ImGui::Separator();
DrawActionButtons();
ImGui::Spacing();
// Quick actions bar
if (show_quick_actions_) {
DrawQuickActions();
ImGui::Spacing();
}
// Input section at bottom
DrawInputSection();
}
ImGui::End();
ImGui::PopStyleColor();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(2);
}
void AgentChatHistoryPopup::DrawMessageList() {
@@ -175,13 +160,193 @@ void AgentChatHistoryPopup::DrawMessage(const cli::agent::ChatMessage& msg, int
ImGui::PopID();
}
void AgentChatHistoryPopup::DrawActionButtons() {
if (ImGui::Button(ICON_MD_DELETE " Clear History", ImVec2(-1, 0))) {
ClearHistory();
void AgentChatHistoryPopup::DrawHeader() {
// Beautiful header with gradient accent
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImVec2 header_start = ImGui::GetCursorScreenPos();
ImVec2 header_size(ImGui::GetContentRegionAvail().x, 60);
// Gradient background
ImU32 color_top = ImGui::GetColorU32(ImVec4(0.15f, 0.18f, 0.22f, 1.0f));
ImU32 color_bottom = ImGui::GetColorU32(kHeaderColor);
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);
// Accent line with pulse effect
float pulse = 0.7f + 0.3f * sinf(header_pulse_);
ImVec4 accent_pulse = ImVec4(kAccentColor.x, kAccentColor.y, kAccentColor.z, pulse);
ImU32 accent_color = ImGui::GetColorU32(accent_pulse);
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, 3.0f);
ImGui::Dummy(ImVec2(0, 10));
// Title with icon
ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); // Default font (bold if available)
ImGui::TextColored(kAccentColor, "%s AI Chat", ICON_MD_CHAT);
ImGui::PopFont();
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 130);
// Compact mode toggle
if (ImGui::SmallButton(compact_mode_ ? ICON_MD_UNFOLD_MORE : ICON_MD_UNFOLD_LESS)) {
compact_mode_ = !compact_mode_;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(compact_mode_ ? "Expand view" : "Compact view");
}
ImGui::SameLine();
// Full chat button
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(kAccentColor.x, kAccentColor.y, kAccentColor.z, 0.6f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, kAccentColor);
if (ImGui::SmallButton(ICON_MD_OPEN_IN_NEW)) {
if (open_chat_callback_) {
open_chat_callback_();
}
}
ImGui::PopStyleColor(2);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Clear all messages from the popup view\n(Full history preserved in chat window)");
ImGui::SetTooltip("Open full chat window");
}
// Message count with badge
int visible_count = 0;
for (const auto& msg : messages_) {
if (msg.is_internal) continue;
if (message_filter_ == MessageFilter::kUserOnly &&
msg.sender != cli::agent::ChatMessage::Sender::kUser) continue;
if (message_filter_ == MessageFilter::kAgentOnly &&
msg.sender != cli::agent::ChatMessage::Sender::kAgent) continue;
visible_count++;
}
ImGui::Spacing();
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 0.9f),
"%d message%s", visible_count, visible_count == 1 ? "" : "s");
ImGui::Dummy(ImVec2(0, 5));
}
void AgentChatHistoryPopup::DrawQuickActions() {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.15f, 0.2f, 0.8f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.2f, 0.2f, 0.3f, 0.9f));
float button_width = (ImGui::GetContentRegionAvail().x - 8) / 3.0f;
// Multimodal snapshot button
if (ImGui::Button(ICON_MD_CAMERA, ImVec2(button_width, 32))) {
if (capture_snapshot_callback_) {
capture_snapshot_callback_();
}
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Capture screenshot for Gemini analysis");
}
ImGui::SameLine();
// Filter button
const char* filter_icon = ICON_MD_FILTER_LIST;
if (ImGui::Button(filter_icon, ImVec2(button_width, 32))) {
ImGui::OpenPopup("FilterPopup");
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Filter messages");
}
// Filter popup
if (ImGui::BeginPopup("FilterPopup")) {
if (ImGui::Selectable("All Messages", message_filter_ == MessageFilter::kAll)) {
message_filter_ = MessageFilter::kAll;
}
if (ImGui::Selectable("User Only", message_filter_ == MessageFilter::kUserOnly)) {
message_filter_ = MessageFilter::kUserOnly;
}
if (ImGui::Selectable("Agent Only", message_filter_ == MessageFilter::kAgentOnly)) {
message_filter_ = MessageFilter::kAgentOnly;
}
ImGui::EndPopup();
}
ImGui::SameLine();
// Clear button
if (ImGui::Button(ICON_MD_DELETE, ImVec2(button_width, 32))) {
ClearHistory();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Clear popup view");
}
ImGui::PopStyleColor(2);
}
void AgentChatHistoryPopup::DrawInputSection() {
ImGui::Separator();
ImGui::Spacing();
// Input field with beautiful styling
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.12f, 0.14f, 0.18f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.15f, 0.17f, 0.22f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.18f, 0.20f, 0.25f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f);
bool send_message = false;
ImGui::SetNextItemWidth(-1);
if (ImGui::InputTextMultiline("##popup_input", input_buffer_, sizeof(input_buffer_),
ImVec2(-1, 60),
ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CtrlEnterForNewLine)) {
send_message = true;
}
// Focus input on first show
if (focus_input_) {
ImGui::SetKeyboardFocusHere(-1);
focus_input_ = false;
}
ImGui::PopStyleVar();
ImGui::PopStyleColor(3);
// Send button with gradient
ImGui::Spacing();
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(kAccentColor.x, kAccentColor.y, kAccentColor.z, 0.7f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, kAccentColor);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.15f, 0.5f, 0.7f, 1.0f));
if (ImGui::Button(absl::StrFormat("%s Send", ICON_MD_SEND).c_str(), ImVec2(-1, 32)) || send_message) {
if (std::strlen(input_buffer_) > 0) {
SendMessage(input_buffer_);
std::memset(input_buffer_, 0, sizeof(input_buffer_));
}
}
ImGui::PopStyleColor(3);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Send message (Enter) • Ctrl+Enter for newline");
}
ImGui::Spacing();
ImGui::TextDisabled(ICON_MD_INFO " Enter: send • Ctrl+Enter: newline");
}
void AgentChatHistoryPopup::SendMessage(const std::string& message) {
if (send_message_callback_) {
send_message_callback_(message);
if (toast_manager_) {
toast_manager_->Show(ICON_MD_SEND " Message sent", ToastType::kSuccess, 1.5f);
}
// Auto-scroll to see response
needs_scroll_ = true;
}
}

View File

@@ -55,12 +55,27 @@ class AgentChatHistoryPopup {
void SetOpenChatCallback(OpenChatCallback callback) {
open_chat_callback_ = std::move(callback);
}
// Set callback for sending messages
using SendMessageCallback = std::function<void(const std::string&)>;
void SetSendMessageCallback(SendMessageCallback callback) {
send_message_callback_ = std::move(callback);
}
// Set callback for capturing snapshots
using CaptureSnapshotCallback = std::function<void()>;
void SetCaptureSnapshotCallback(CaptureSnapshotCallback callback) {
capture_snapshot_callback_ = std::move(callback);
}
private:
void DrawHeader();
void DrawMessageList();
void DrawMessage(const cli::agent::ChatMessage& msg, int index);
void DrawActionButtons();
void DrawInputSection();
void DrawQuickActions();
void SendMessage(const std::string& message);
void ClearHistory();
void ExportHistory();
void ScrollToBottom();
@@ -68,15 +83,21 @@ class AgentChatHistoryPopup {
bool visible_ = false;
bool needs_scroll_ = false;
bool auto_scroll_ = true;
bool compact_mode_ = true;
bool show_quick_actions_ = true;
// History state
std::vector<cli::agent::ChatMessage> messages_;
int display_limit_ = 50; // Show last 50 messages
// Input state
char input_buffer_[512] = {};
bool focus_input_ = false;
// UI state
float drawer_width_ = 400.0f;
float drawer_width_ = 420.0f;
float min_drawer_width_ = 300.0f;
float max_drawer_width_ = 600.0f;
float max_drawer_width_ = 700.0f;
bool is_resizing_ = false;
// Filter state
@@ -87,9 +108,15 @@ class AgentChatHistoryPopup {
};
MessageFilter message_filter_ = MessageFilter::kAll;
// Visual state
float header_pulse_ = 0.0f;
int unread_count_ = 0;
// Dependencies
ToastManager* toast_manager_ = nullptr;
OpenChatCallback open_chat_callback_;
SendMessageCallback send_message_callback_;
CaptureSnapshotCallback capture_snapshot_callback_;
};
} // namespace editor