feat: Add Agent Chat History Popup for Enhanced User Interaction
- Introduced the AgentChatHistoryPopup class to provide a sidebar for displaying recent chat messages, improving accessibility and user experience. - Implemented message filtering options (All, User Only, Agent Only) and auto-scroll functionality for better message management. - Enhanced the AgentChatWidget to synchronize chat history with the new popup, ensuring users have up-to-date information at their fingertips. - Updated CMake configuration to include the new build_cleaner target for improved project maintenance.
This commit is contained in:
@@ -394,3 +394,9 @@ endif()
|
||||
# Packaging configuration
|
||||
include(cmake/packaging.cmake)
|
||||
|
||||
add_custom_target(build_cleaner
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Running scripts/build_cleaner.py --dry-run"
|
||||
COMMAND ${CMAKE_COMMAND} -E chdir ${CMAKE_SOURCE_DIR} ${Python3_EXECUTABLE} scripts/build_cleaner.py --dry-run
|
||||
COMMENT "Validate CMake source lists and includes"
|
||||
)
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "util/file_util.h"
|
||||
#include "app/editor/agent/agent_chat_history_codec.h"
|
||||
#include "app/editor/system/proposal_drawer.h"
|
||||
#include "app/editor/system/agent_chat_history_popup.h"
|
||||
#include "app/editor/system/toast_manager.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/core/project.h"
|
||||
@@ -122,6 +123,20 @@ void AgentChatWidget::SetProposalDrawer(ProposalDrawer* drawer) {
|
||||
}
|
||||
}
|
||||
|
||||
void AgentChatWidget::SetChatHistoryPopup(AgentChatHistoryPopup* popup) {
|
||||
chat_history_popup_ = popup;
|
||||
|
||||
// Set up callback to open this chat window
|
||||
if (chat_history_popup_) {
|
||||
chat_history_popup_->SetOpenChatCallback([this]() {
|
||||
this->set_active(true);
|
||||
});
|
||||
|
||||
// Initial sync
|
||||
SyncHistoryToPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void AgentChatWidget::EnsureHistoryLoaded() {
|
||||
if (history_loaded_) {
|
||||
return;
|
||||
@@ -221,6 +236,9 @@ void AgentChatWidget::PersistHistory() {
|
||||
snapshot.collaboration.active = collaboration_state_.active;
|
||||
snapshot.collaboration.session_id = collaboration_state_.session_id;
|
||||
snapshot.collaboration.session_name = collaboration_state_.session_name;
|
||||
|
||||
// Sync to popup when persisting
|
||||
SyncHistoryToPopup();
|
||||
snapshot.collaboration.participants = collaboration_state_.participants;
|
||||
snapshot.collaboration.last_synced = collaboration_state_.last_synced;
|
||||
snapshot.multimodal.last_capture_path = multimodal_state_.last_capture_path;
|
||||
@@ -321,6 +339,9 @@ void AgentChatWidget::HandleAgentResponse(
|
||||
NotifyProposalCreated(message, total);
|
||||
}
|
||||
last_proposal_count_ = std::max(last_proposal_count_, total);
|
||||
|
||||
// Sync history to popup after response
|
||||
SyncHistoryToPopup();
|
||||
}
|
||||
|
||||
void AgentChatWidget::RenderMessage(const ChatMessage& msg, int index) {
|
||||
@@ -1833,6 +1854,14 @@ void AgentChatWidget::HandleProposalReceived(
|
||||
}
|
||||
}
|
||||
|
||||
void AgentChatWidget::SyncHistoryToPopup() {
|
||||
if (!chat_history_popup_) return;
|
||||
|
||||
const auto& history = agent_service_.GetHistory();
|
||||
chat_history_popup_->UpdateHistory(history);
|
||||
chat_history_popup_->NotifyNewMessage();
|
||||
}
|
||||
|
||||
void AgentChatWidget::RenderSystemPromptEditor() {
|
||||
ImGui::BeginChild("SystemPromptEditor", ImVec2(0, 0), false);
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace editor {
|
||||
|
||||
class ProposalDrawer;
|
||||
class ToastManager;
|
||||
class AgentChatHistoryPopup;
|
||||
|
||||
/**
|
||||
* @class AgentChatWidget
|
||||
@@ -86,6 +87,8 @@ class AgentChatWidget {
|
||||
void SetToastManager(ToastManager* toast_manager);
|
||||
|
||||
void SetProposalDrawer(ProposalDrawer* drawer);
|
||||
|
||||
void SetChatHistoryPopup(AgentChatHistoryPopup* popup);
|
||||
|
||||
void SetCollaborationCallbacks(const CollaborationCallbacks& callbacks) {
|
||||
collaboration_callbacks_ = callbacks;
|
||||
@@ -228,6 +231,9 @@ public:
|
||||
void HandleRomSyncReceived(const std::string& diff_data, const std::string& rom_hash);
|
||||
void HandleSnapshotReceived(const std::string& snapshot_data, const std::string& snapshot_type);
|
||||
void HandleProposalReceived(const std::string& proposal_data);
|
||||
|
||||
// History synchronization
|
||||
void SyncHistoryToPopup();
|
||||
|
||||
// Chat session management
|
||||
struct ChatSession {
|
||||
@@ -261,6 +267,7 @@ public:
|
||||
int last_proposal_count_ = 0;
|
||||
ToastManager* toast_manager_ = nullptr;
|
||||
ProposalDrawer* proposal_drawer_ = nullptr;
|
||||
AgentChatHistoryPopup* chat_history_popup_ = nullptr;
|
||||
std::string pending_focus_proposal_id_;
|
||||
absl::Time last_persist_time_ = absl::InfinitePast();
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ set(
|
||||
app/editor/system/shortcut_manager.cc
|
||||
app/editor/system/popup_manager.cc
|
||||
app/editor/system/proposal_drawer.cc
|
||||
app/editor/system/agent_chat_history_popup.cc
|
||||
app/editor/agent/agent_chat_history_codec.cc
|
||||
)
|
||||
|
||||
|
||||
231
src/app/editor/system/agent_chat_history_popup.cc
Normal file
231
src/app/editor/system/agent_chat_history_popup.cc
Normal file
@@ -0,0 +1,231 @@
|
||||
#include "app/editor/system/agent_chat_history_popup.h"
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "imgui/imgui.h"
|
||||
#include "app/gui/icons.h"
|
||||
#include "app/editor/system/toast_manager.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
|
||||
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);
|
||||
|
||||
} // namespace
|
||||
|
||||
AgentChatHistoryPopup::AgentChatHistoryPopup() {}
|
||||
|
||||
void AgentChatHistoryPopup::Draw() {
|
||||
if (!visible_) return;
|
||||
|
||||
// Set drawer position on the LEFT side
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(drawer_width_, io.DisplaySize.y),
|
||||
ImGuiCond_Always);
|
||||
|
||||
ImGuiWindowFlags flags = ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoCollapse;
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.08f, 0.10f, 0.95f));
|
||||
|
||||
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_();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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_);
|
||||
|
||||
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);
|
||||
DrawMessageList();
|
||||
|
||||
if (needs_scroll_) {
|
||||
ImGui::SetScrollHereY(1.0f);
|
||||
needs_scroll_ = false;
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
|
||||
// Action buttons at bottom
|
||||
ImGui::Separator();
|
||||
DrawActionButtons();
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
|
||||
void AgentChatHistoryPopup::DrawMessageList() {
|
||||
if (messages_.empty()) {
|
||||
ImGui::TextDisabled("No messages yet. Start a conversation in the chat window.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate starting index for display limit
|
||||
int start_index = messages_.size() > display_limit_ ?
|
||||
messages_.size() - display_limit_ : 0;
|
||||
|
||||
for (int i = start_index; i < messages_.size(); ++i) {
|
||||
const auto& msg = messages_[i];
|
||||
|
||||
// Skip internal messages
|
||||
if (msg.is_internal) continue;
|
||||
|
||||
// Apply filter
|
||||
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;
|
||||
|
||||
DrawMessage(msg, i);
|
||||
}
|
||||
}
|
||||
|
||||
void AgentChatHistoryPopup::DrawMessage(const cli::agent::ChatMessage& msg, int index) {
|
||||
ImGui::PushID(index);
|
||||
|
||||
bool from_user = (msg.sender == cli::agent::ChatMessage::Sender::kUser);
|
||||
ImVec4 header_color = from_user ? kUserColor : kAgentColor;
|
||||
const char* sender_label = from_user ? ICON_MD_PERSON " You" : ICON_MD_SMART_TOY " Agent";
|
||||
|
||||
// Message header with sender and timestamp
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, header_color);
|
||||
ImGui::Text("%s", sender_label);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, kTimestampColor);
|
||||
ImGui::Text("%s", absl::FormatTime("%H:%M:%S", msg.timestamp, absl::LocalTimeZone()).c_str());
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
// Message content
|
||||
ImGui::Indent(10.0f);
|
||||
|
||||
if (msg.table_data.has_value()) {
|
||||
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.9f, 1.0f), ICON_MD_TABLE_CHART " [Table Data]");
|
||||
} else if (msg.json_pretty.has_value()) {
|
||||
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.9f, 1.0f), ICON_MD_DATA_OBJECT " [Structured Response]");
|
||||
} else {
|
||||
// Truncate long messages
|
||||
std::string content = msg.message;
|
||||
if (content.length() > 200) {
|
||||
content = content.substr(0, 197) + "...";
|
||||
}
|
||||
ImGui::TextWrapped("%s", content.c_str());
|
||||
}
|
||||
|
||||
// Show proposal indicator if present
|
||||
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());
|
||||
}
|
||||
|
||||
ImGui::Unindent(10.0f);
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
void AgentChatHistoryPopup::DrawActionButtons() {
|
||||
if (ImGui::Button(ICON_MD_DELETE " Clear History", ImVec2(-1, 0))) {
|
||||
ClearHistory();
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Clear all messages from the popup view\n(Full history preserved in chat window)");
|
||||
}
|
||||
}
|
||||
|
||||
void AgentChatHistoryPopup::UpdateHistory(const std::vector<cli::agent::ChatMessage>& history) {
|
||||
bool had_messages = !messages_.empty();
|
||||
int old_size = messages_.size();
|
||||
|
||||
messages_ = history;
|
||||
|
||||
// Auto-scroll if new messages arrived
|
||||
if (auto_scroll_ && messages_.size() > old_size) {
|
||||
needs_scroll_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void AgentChatHistoryPopup::NotifyNewMessage() {
|
||||
if (auto_scroll_) {
|
||||
needs_scroll_ = true;
|
||||
}
|
||||
|
||||
// Flash the window to draw attention
|
||||
if (toast_manager_ && !visible_) {
|
||||
toast_manager_->Show(ICON_MD_CHAT " New message received", ToastType::kInfo, 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void AgentChatHistoryPopup::ClearHistory() {
|
||||
messages_.clear();
|
||||
|
||||
if (toast_manager_) {
|
||||
toast_manager_->Show("Chat history popup cleared", ToastType::kInfo, 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void AgentChatHistoryPopup::ExportHistory() {
|
||||
// TODO: Implement export functionality
|
||||
if (toast_manager_) {
|
||||
toast_manager_->Show("Export feature coming soon", ToastType::kInfo, 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void AgentChatHistoryPopup::ScrollToBottom() {
|
||||
needs_scroll_ = true;
|
||||
}
|
||||
|
||||
} // namespace editor
|
||||
} // namespace yaze
|
||||
98
src/app/editor/system/agent_chat_history_popup.h
Normal file
98
src/app/editor/system/agent_chat_history_popup.h
Normal file
@@ -0,0 +1,98 @@
|
||||
#ifndef YAZE_APP_EDITOR_SYSTEM_AGENT_CHAT_HISTORY_POPUP_H
|
||||
#define YAZE_APP_EDITOR_SYSTEM_AGENT_CHAT_HISTORY_POPUP_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cli/service/agent/conversational_agent_service.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace editor {
|
||||
|
||||
class ToastManager;
|
||||
|
||||
/**
|
||||
* @class AgentChatHistoryPopup
|
||||
* @brief ImGui popup drawer for displaying chat history on the left side
|
||||
*
|
||||
* Provides a quick-access sidebar for viewing recent chat messages,
|
||||
* complementing the ProposalDrawer on the right. Features:
|
||||
* - Recent message list with timestamps
|
||||
* - User/Agent message differentiation
|
||||
* - Scroll to view older messages
|
||||
* - Quick actions (clear, export, open full chat)
|
||||
* - Syncs with AgentChatWidget and AgentEditor
|
||||
*
|
||||
* Positioned on the LEFT side of the screen as a slide-out panel.
|
||||
*/
|
||||
class AgentChatHistoryPopup {
|
||||
public:
|
||||
AgentChatHistoryPopup();
|
||||
~AgentChatHistoryPopup() = default;
|
||||
|
||||
// Set dependencies
|
||||
void SetToastManager(ToastManager* toast_manager) {
|
||||
toast_manager_ = toast_manager;
|
||||
}
|
||||
|
||||
// Render the popup UI
|
||||
void Draw();
|
||||
|
||||
// Show/hide the popup
|
||||
void Show() { visible_ = true; }
|
||||
void Hide() { visible_ = false; }
|
||||
void Toggle() { visible_ = !visible_; }
|
||||
bool IsVisible() const { return visible_; }
|
||||
|
||||
// Update history from service
|
||||
void UpdateHistory(const std::vector<cli::agent::ChatMessage>& history);
|
||||
|
||||
// Notify of new message (triggers auto-scroll)
|
||||
void NotifyNewMessage();
|
||||
|
||||
// Set callback for opening full chat window
|
||||
using OpenChatCallback = std::function<void()>;
|
||||
void SetOpenChatCallback(OpenChatCallback callback) {
|
||||
open_chat_callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
private:
|
||||
void DrawMessageList();
|
||||
void DrawMessage(const cli::agent::ChatMessage& msg, int index);
|
||||
void DrawActionButtons();
|
||||
|
||||
void ClearHistory();
|
||||
void ExportHistory();
|
||||
void ScrollToBottom();
|
||||
|
||||
bool visible_ = false;
|
||||
bool needs_scroll_ = false;
|
||||
bool auto_scroll_ = true;
|
||||
|
||||
// History state
|
||||
std::vector<cli::agent::ChatMessage> messages_;
|
||||
int display_limit_ = 50; // Show last 50 messages
|
||||
|
||||
// UI state
|
||||
float drawer_width_ = 400.0f;
|
||||
float min_drawer_width_ = 300.0f;
|
||||
float max_drawer_width_ = 600.0f;
|
||||
bool is_resizing_ = false;
|
||||
|
||||
// Filter state
|
||||
enum class MessageFilter {
|
||||
kAll,
|
||||
kUserOnly,
|
||||
kAgentOnly
|
||||
};
|
||||
MessageFilter message_filter_ = MessageFilter::kAll;
|
||||
|
||||
// Dependencies
|
||||
ToastManager* toast_manager_ = nullptr;
|
||||
OpenChatCallback open_chat_callback_;
|
||||
};
|
||||
|
||||
} // namespace editor
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_EDITOR_SYSTEM_AGENT_CHAT_HISTORY_POPUP_H
|
||||
Reference in New Issue
Block a user