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:
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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", ¤t_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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user