feat: Enhance AgentChatWidget with Chat Session Management and UI Improvements
- Implemented functionality to save, load, and delete chat sessions, allowing users to manage their chat history effectively. - Introduced a new layout for the connection status bar and improved the AI provider selection interface for better visibility. - Enhanced the UI of the AgentEditor with a modular 3-column layout, including dedicated tabs for system prompts and common tiles, improving user experience and organization.
This commit is contained in:
@@ -799,11 +799,10 @@ void AgentChatWidget::Draw() {
|
||||
? ImVec4(0.133f, 0.545f, 0.133f, 1.0f)
|
||||
: ImVec4(0.6f, 0.6f, 0.6f, 1.0f);
|
||||
|
||||
// Connection status bar at top (compact)
|
||||
// Connection status bar at top (taller for better visibility)
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
ImVec2 bar_start = ImGui::GetCursorScreenPos();
|
||||
ImVec2 bar_size(ImGui::GetContentRegionAvail().x,
|
||||
55); // Reduced from 75 to 55
|
||||
ImVec2 bar_size(ImGui::GetContentRegionAvail().x, 90); // Increased from 55
|
||||
|
||||
// Gradient background
|
||||
ImU32 color_top = ImGui::GetColorU32(ImVec4(0.18f, 0.22f, 0.28f, 1.0f));
|
||||
@@ -831,15 +830,14 @@ void AgentChatWidget::Draw() {
|
||||
float vertical_padding = (55.0f - content_height) / 2.0f;
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + vertical_padding);
|
||||
|
||||
// Compact single row layout
|
||||
ImGui::TextColored(accent_color, ICON_MD_SMART_TOY);
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(95);
|
||||
const char* providers[] = {"Mock", "Ollama", "Gemini"};
|
||||
int current_provider = (agent_config_.ai_provider == "mock") ? 0
|
||||
: (agent_config_.ai_provider == "ollama") ? 1
|
||||
: 2;
|
||||
if (ImGui::Combo("##main_provider", ¤t_provider, providers, 3)) {
|
||||
// Two-row layout for better visibility
|
||||
ImGui::Text(ICON_MD_SMART_TOY " AI Provider:");
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
const char* providers[] = {"Mock", "Ollama", "Gemini"};
|
||||
int current_provider = (agent_config_.ai_provider == "mock") ? 0
|
||||
: (agent_config_.ai_provider == "ollama") ? 1
|
||||
: 2;
|
||||
if (ImGui::Combo("##main_provider", ¤t_provider, providers, 3)) {
|
||||
agent_config_.ai_provider = (current_provider == 0) ? "mock"
|
||||
: (current_provider == 1) ? "ollama"
|
||||
: "gemini";
|
||||
@@ -2075,6 +2073,99 @@ void AgentChatWidget::SyncHistoryToPopup() {
|
||||
chat_history_popup_->NotifyNewMessage();
|
||||
}
|
||||
|
||||
std::filesystem::path AgentChatWidget::GetSessionsDirectory() {
|
||||
std::filesystem::path config_dir(yaze::util::GetConfigDirectory());
|
||||
if (config_dir.empty()) {
|
||||
#ifdef _WIN32
|
||||
const char* appdata = std::getenv("APPDATA");
|
||||
if (appdata) {
|
||||
config_dir = std::filesystem::path(appdata) / "yaze";
|
||||
}
|
||||
#else
|
||||
const char* home = std::getenv("HOME");
|
||||
if (home) {
|
||||
config_dir = std::filesystem::path(home) / ".yaze";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return config_dir / "chats";
|
||||
}
|
||||
|
||||
void AgentChatWidget::SaveChatSession(const ChatSession& session) {
|
||||
auto sessions_dir = GetSessionsDirectory();
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories(sessions_dir, ec);
|
||||
|
||||
std::filesystem::path save_path = sessions_dir / (session.id + ".json");
|
||||
|
||||
// Save using existing history codec
|
||||
AgentChatHistoryCodec::Snapshot snapshot;
|
||||
snapshot.history = session.agent_service.GetHistory();
|
||||
|
||||
auto status = AgentChatHistoryCodec::Save(save_path, snapshot);
|
||||
if (status.ok() && toast_manager_) {
|
||||
toast_manager_->Show(
|
||||
absl::StrFormat(ICON_MD_SAVE " Chat '%s' saved", session.name),
|
||||
ToastType::kSuccess, 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void AgentChatWidget::LoadChatSession(const std::string& session_id) {
|
||||
auto sessions_dir = GetSessionsDirectory();
|
||||
std::filesystem::path load_path = sessions_dir / (session_id + ".json");
|
||||
|
||||
if (!std::filesystem::exists(load_path)) {
|
||||
if (toast_manager_) {
|
||||
toast_manager_->Show(ICON_MD_WARNING " Session file not found", ToastType::kWarning, 2.5f);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto snapshot_result = AgentChatHistoryCodec::Load(load_path);
|
||||
if (snapshot_result.ok()) {
|
||||
// Create new session with loaded history
|
||||
ChatSession session(session_id, session_id);
|
||||
session.agent_service.ReplaceHistory(snapshot_result->history);
|
||||
session.history_loaded = true;
|
||||
chat_sessions_.push_back(std::move(session));
|
||||
active_session_index_ = static_cast<int>(chat_sessions_.size() - 1);
|
||||
|
||||
if (toast_manager_) {
|
||||
toast_manager_->Show(ICON_MD_CHECK_CIRCLE " Chat session loaded", ToastType::kSuccess, 2.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AgentChatWidget::DeleteChatSession(const std::string& session_id) {
|
||||
auto sessions_dir = GetSessionsDirectory();
|
||||
std::filesystem::path session_path = sessions_dir / (session_id + ".json");
|
||||
|
||||
if (std::filesystem::exists(session_path)) {
|
||||
std::filesystem::remove(session_path);
|
||||
if (toast_manager_) {
|
||||
toast_manager_->Show(ICON_MD_DELETE " Chat deleted", ToastType::kInfo, 2.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> AgentChatWidget::GetSavedSessions() {
|
||||
std::vector<std::string> sessions;
|
||||
auto sessions_dir = GetSessionsDirectory();
|
||||
|
||||
if (!std::filesystem::exists(sessions_dir)) {
|
||||
return sessions;
|
||||
}
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(sessions_dir)) {
|
||||
if (entry.path().extension() == ".json") {
|
||||
sessions.push_back(entry.path().stem().string());
|
||||
}
|
||||
}
|
||||
|
||||
return sessions;
|
||||
}
|
||||
|
||||
void AgentChatWidget::RenderSystemPromptEditor() {
|
||||
ImGui::BeginChild("SystemPromptEditor", ImVec2(0, 0), false);
|
||||
|
||||
|
||||
@@ -244,17 +244,25 @@ public:
|
||||
struct ChatSession {
|
||||
std::string id;
|
||||
std::string name;
|
||||
std::filesystem::path save_path;
|
||||
cli::agent::ConversationalAgentService agent_service;
|
||||
size_t last_history_size = 0;
|
||||
bool history_loaded = false;
|
||||
bool history_dirty = false;
|
||||
std::filesystem::path history_path;
|
||||
absl::Time created_at = absl::Now();
|
||||
absl::Time last_persist_time = absl::InfinitePast();
|
||||
|
||||
ChatSession(const std::string& session_id, const std::string& session_name)
|
||||
: id(session_id), name(session_name) {}
|
||||
};
|
||||
|
||||
void SaveChatSession(const ChatSession& session);
|
||||
void LoadChatSession(const std::string& session_id);
|
||||
void DeleteChatSession(const std::string& session_id);
|
||||
std::vector<std::string> GetSavedSessions();
|
||||
std::filesystem::path GetSessionsDirectory();
|
||||
|
||||
std::vector<ChatSession> chat_sessions_;
|
||||
int active_session_index_ = 0;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <memory>
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "absl/strings/match.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "app/editor/agent/agent_chat_widget.h"
|
||||
#include "app/editor/agent/agent_collaboration_coordinator.h"
|
||||
@@ -31,6 +32,7 @@ AgentEditor::AgentEditor() {
|
||||
chat_widget_ = std::make_unique<AgentChatWidget>();
|
||||
local_coordinator_ = std::make_unique<AgentCollaborationCoordinator>();
|
||||
prompt_editor_ = std::make_unique<TextEditor>();
|
||||
common_tiles_editor_ = std::make_unique<TextEditor>();
|
||||
|
||||
// Initialize default configuration (legacy)
|
||||
current_config_.provider = "mock";
|
||||
@@ -46,11 +48,15 @@ AgentEditor::AgentEditor() {
|
||||
current_profile_.max_retry_attempts = 3;
|
||||
current_profile_.tags = {"default", "z3ed"};
|
||||
|
||||
// Setup text editor for system prompts
|
||||
// Setup text editors
|
||||
prompt_editor_->SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus());
|
||||
prompt_editor_->SetReadOnly(false);
|
||||
prompt_editor_->SetShowWhitespaces(false);
|
||||
|
||||
common_tiles_editor_->SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus());
|
||||
common_tiles_editor_->SetReadOnly(false);
|
||||
common_tiles_editor_->SetShowWhitespaces(false);
|
||||
|
||||
// Ensure profiles directory exists
|
||||
EnsureProfilesDirectory();
|
||||
}
|
||||
@@ -173,34 +179,58 @@ void AgentEditor::DrawDashboard() {
|
||||
|
||||
// Compact tabbed interface (combined tabs)
|
||||
if (ImGui::BeginTabBar("AgentEditorTabs", ImGuiTabBarFlags_None)) {
|
||||
// Bot Management Tab (combines Config + Profiles + Prompts)
|
||||
// Bot Studio Tab - Modular 3-column layout
|
||||
if (ImGui::BeginTabItem(ICON_MD_SMART_TOY " Bot Studio")) {
|
||||
ImGui::Spacing();
|
||||
|
||||
// Three-column layout with prompt editor in center
|
||||
if (ImGui::BeginTable("BotStudioLayout", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) {
|
||||
// Use ImGui table for clean 3-column resizable layout
|
||||
ImGuiTableFlags table_flags = ImGuiTableFlags_Resizable |
|
||||
ImGuiTableFlags_BordersInnerV |
|
||||
ImGuiTableFlags_SizingStretchProp;
|
||||
|
||||
if (ImGui::BeginTable("BotStudioLayout", 3, table_flags)) {
|
||||
ImGui::TableSetupColumn("Config", ImGuiTableColumnFlags_WidthFixed, 380.0f);
|
||||
ImGui::TableSetupColumn("Prompt Editor", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("Editors", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("Profiles", ImGuiTableColumnFlags_WidthFixed, 320.0f);
|
||||
ImGui::TableNextRow();
|
||||
|
||||
// LEFT: Configuration
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::BeginChild("ConfigSection", ImVec2(0, 0), false);
|
||||
// Column 1: Configuration
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::PushID("ConfigColumn");
|
||||
DrawConfigurationPanel();
|
||||
ImGui::EndChild();
|
||||
ImGui::PopID();
|
||||
|
||||
// CENTER: System Prompt Editor
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::BeginChild("PromptSection", ImVec2(0, 0), false);
|
||||
DrawPromptEditorPanel();
|
||||
ImGui::EndChild();
|
||||
// Column 2: Editors (Prompt + Tiles + New)
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::PushID("EditorsColumn");
|
||||
|
||||
// RIGHT: Bot Profiles List
|
||||
ImGui::TableSetColumnIndex(2);
|
||||
ImGui::BeginChild("ProfilesSection", ImVec2(0, 0), false);
|
||||
// Tabbed editors for better organization
|
||||
if (ImGui::BeginTabBar("EditorTabs", ImGuiTabBarFlags_None)) {
|
||||
if (ImGui::BeginTabItem(ICON_MD_EDIT " System Prompt")) {
|
||||
DrawPromptEditorPanel();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabItem(ICON_MD_GRID_ON " Common Tiles")) {
|
||||
DrawCommonTilesEditor();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabItem(ICON_MD_ADD " New Prompt")) {
|
||||
DrawNewPromptCreator();
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
|
||||
// Column 3: Bot Profiles
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::PushID("ProfilesColumn");
|
||||
DrawBotProfilesPanel();
|
||||
ImGui::EndChild();
|
||||
ImGui::PopID();
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
@@ -771,6 +801,185 @@ void AgentEditor::DrawAdvancedMetricsPanel() {
|
||||
}
|
||||
}
|
||||
|
||||
void AgentEditor::DrawCommonTilesEditor() {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_GRID_ON " Common Tiles Reference");
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::TextWrapped("Customize the tile reference file that AI uses for tile placement. "
|
||||
"Organize tiles by category and provide hex IDs with descriptions.");
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Load/Save buttons
|
||||
if (ImGui::Button(ICON_MD_FOLDER_OPEN " Load", ImVec2(100, 0))) {
|
||||
auto content = core::AssetLoader::LoadTextFile("agent/common_tiles.txt");
|
||||
if (content.ok()) {
|
||||
common_tiles_editor_->SetText(*content);
|
||||
common_tiles_initialized_ = true;
|
||||
if (toast_manager_) {
|
||||
toast_manager_->Show(ICON_MD_CHECK_CIRCLE " Common tiles loaded", ToastType::kSuccess, 2.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_MD_SAVE " Save", ImVec2(100, 0))) {
|
||||
// Save to project or assets directory
|
||||
if (toast_manager_) {
|
||||
toast_manager_->Show(ICON_MD_INFO " Save to project directory (coming soon)", ToastType::kInfo, 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton(ICON_MD_REFRESH)) {
|
||||
common_tiles_initialized_ = false;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Reload from disk");
|
||||
}
|
||||
|
||||
// Load if not initialized
|
||||
if (!common_tiles_initialized_ && common_tiles_editor_) {
|
||||
auto content = core::AssetLoader::LoadTextFile("agent/common_tiles.txt");
|
||||
if (content.ok()) {
|
||||
common_tiles_editor_->SetText(*content);
|
||||
} else {
|
||||
// Create default template
|
||||
std::string default_tiles =
|
||||
"# Common Tile16 Reference\n"
|
||||
"# Format: 0xHEX = Description\n\n"
|
||||
"[grass_tiles]\n"
|
||||
"0x020 = Grass (standard)\n\n"
|
||||
"[nature_tiles]\n"
|
||||
"0x02E = Tree (oak)\n"
|
||||
"0x003 = Bush\n\n"
|
||||
"[water_tiles]\n"
|
||||
"0x14C = Water (top edge)\n"
|
||||
"0x14D = Water (middle)\n";
|
||||
common_tiles_editor_->SetText(default_tiles);
|
||||
}
|
||||
common_tiles_initialized_ = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Editor
|
||||
if (common_tiles_editor_) {
|
||||
ImVec2 editor_size(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y);
|
||||
common_tiles_editor_->Render("##tiles_editor", editor_size, true);
|
||||
}
|
||||
}
|
||||
|
||||
void AgentEditor::DrawNewPromptCreator() {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_ADD " Create New System Prompt");
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::TextWrapped("Create a custom system prompt from scratch or use a template.");
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
|
||||
// Prompt name input
|
||||
ImGui::Text("Prompt Name:");
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::InputTextWithHint("##new_prompt_name", "e.g., custom_prompt.txt",
|
||||
new_prompt_name_, sizeof(new_prompt_name_));
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
// Template selection
|
||||
ImGui::Text("Start from template:");
|
||||
|
||||
if (ImGui::Button(ICON_MD_FILE_COPY " v1 (Basic)", ImVec2(-1, 0))) {
|
||||
auto content = core::AssetLoader::LoadTextFile("agent/system_prompt.txt");
|
||||
if (content.ok() && prompt_editor_) {
|
||||
prompt_editor_->SetText(*content);
|
||||
if (toast_manager_) {
|
||||
toast_manager_->Show("Template v1 loaded", ToastType::kSuccess, 1.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::Button(ICON_MD_FILE_COPY " v2 (Enhanced)", ImVec2(-1, 0))) {
|
||||
auto content = core::AssetLoader::LoadTextFile("agent/system_prompt_v2.txt");
|
||||
if (content.ok() && prompt_editor_) {
|
||||
prompt_editor_->SetText(*content);
|
||||
if (toast_manager_) {
|
||||
toast_manager_->Show("Template v2 loaded", ToastType::kSuccess, 1.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::Button(ICON_MD_FILE_COPY " v3 (Proactive)", ImVec2(-1, 0))) {
|
||||
auto content = core::AssetLoader::LoadTextFile("agent/system_prompt_v3.txt");
|
||||
if (content.ok() && prompt_editor_) {
|
||||
prompt_editor_->SetText(*content);
|
||||
if (toast_manager_) {
|
||||
toast_manager_->Show("Template v3 loaded", ToastType::kSuccess, 1.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::Button(ICON_MD_NOTE_ADD " Blank Template", ImVec2(-1, 0))) {
|
||||
if (prompt_editor_) {
|
||||
std::string blank_template =
|
||||
"# Custom System Prompt\n\n"
|
||||
"You are an AI assistant for ROM hacking.\n\n"
|
||||
"## Your Role\n"
|
||||
"- Help users understand ROM data\n"
|
||||
"- Provide accurate information\n"
|
||||
"- Use tools when needed\n\n"
|
||||
"## Available Tools\n"
|
||||
"- resource-list: List resources by type\n"
|
||||
"- dungeon-describe-room: Get room details\n"
|
||||
"- overworld-find-tile: Find tile locations\n"
|
||||
"- ... (see function schemas for complete list)\n\n"
|
||||
"## Guidelines\n"
|
||||
"1. Always provide text_response after tool calls\n"
|
||||
"2. Be helpful and accurate\n"
|
||||
"3. Explain your reasoning\n";
|
||||
prompt_editor_->SetText(blank_template);
|
||||
if (toast_manager_) {
|
||||
toast_manager_->Show("Blank template created", ToastType::kSuccess, 1.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
|
||||
// Save button
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.133f, 0.545f, 0.133f, 0.8f));
|
||||
if (ImGui::Button(ICON_MD_SAVE " Save New Prompt", ImVec2(-1, 40))) {
|
||||
if (std::strlen(new_prompt_name_) > 0 && prompt_editor_) {
|
||||
// Save to assets/agent/ directory
|
||||
std::string filename = new_prompt_name_;
|
||||
if (!absl::EndsWith(filename, ".txt")) {
|
||||
filename += ".txt";
|
||||
}
|
||||
|
||||
// TODO: Actually save the file
|
||||
if (toast_manager_) {
|
||||
toast_manager_->Show(
|
||||
absl::StrFormat(ICON_MD_SAVE " Prompt saved as %s", filename),
|
||||
ToastType::kSuccess, 3.0f);
|
||||
}
|
||||
|
||||
// Clear name buffer
|
||||
std::memset(new_prompt_name_, 0, sizeof(new_prompt_name_));
|
||||
} else if (toast_manager_) {
|
||||
toast_manager_->Show(ICON_MD_WARNING " Enter a name for the prompt", ToastType::kWarning, 2.0f);
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::TextWrapped("Note: New prompts are saved to your project. Use 'System Prompt' tab to edit existing prompts.");
|
||||
}
|
||||
|
||||
// Bot Profile Management Implementation
|
||||
absl::Status AgentEditor::SaveBotProfile(const BotProfile& profile) {
|
||||
#if defined(YAZE_WITH_JSON)
|
||||
|
||||
@@ -182,6 +182,8 @@ class AgentEditor : public Editor {
|
||||
void DrawBotProfilesPanel();
|
||||
void DrawChatHistoryViewer();
|
||||
void DrawAdvancedMetricsPanel();
|
||||
void DrawCommonTilesEditor();
|
||||
void DrawNewPromptCreator();
|
||||
|
||||
// Setup callbacks
|
||||
void SetupChatWidgetCallbacks();
|
||||
@@ -213,8 +215,11 @@ class AgentEditor : public Editor {
|
||||
|
||||
// System Prompt Editor
|
||||
std::unique_ptr<TextEditor> prompt_editor_;
|
||||
std::unique_ptr<TextEditor> common_tiles_editor_;
|
||||
bool prompt_editor_initialized_ = false;
|
||||
bool common_tiles_initialized_ = false;
|
||||
std::string active_prompt_file_ = "system_prompt_v3.txt";
|
||||
char new_prompt_name_[128] = {};
|
||||
|
||||
// Collaboration state
|
||||
CollaborationMode current_mode_ = CollaborationMode::kLocal;
|
||||
|
||||
@@ -30,33 +30,29 @@ AgentChatHistoryPopup::AgentChatHistoryPopup() {
|
||||
void AgentChatHistoryPopup::Draw() {
|
||||
if (!visible_) return;
|
||||
|
||||
// Set drawer position on the LEFT side with beautiful styling
|
||||
// Set drawer position on the LEFT side (full height)
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(drawer_width_, io.DisplaySize.y),
|
||||
ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(drawer_width_, io.DisplaySize.y), ImGuiCond_Always);
|
||||
|
||||
ImGuiWindowFlags flags = ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoTitleBar;
|
||||
|
||||
// Theme-matched styling
|
||||
// Use current theme colors
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10, 10));
|
||||
|
||||
if (ImGui::Begin("##AgentChatPopup", &visible_, flags)) {
|
||||
// Animated header pulse
|
||||
header_pulse_ += io.DeltaTime * 2.0f;
|
||||
float pulse = 0.5f + 0.5f * sinf(header_pulse_);
|
||||
|
||||
DrawHeader();
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Spacing();
|
||||
|
||||
// Message list with gradient background
|
||||
float list_height = io.DisplaySize.y - 280.0f; // Reserve space for input and actions
|
||||
// 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::BeginChild("MessageList", ImVec2(0, list_height), true, ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
||||
@@ -116,18 +112,20 @@ void AgentChatHistoryPopup::DrawMessage(const cli::agent::ChatMessage& msg, int
|
||||
ImGui::PushID(index);
|
||||
|
||||
bool from_user = (msg.sender == cli::agent::ChatMessage::Sender::kUser);
|
||||
ImVec4 header_color = from_user ? kUserColor : kAgentColor;
|
||||
|
||||
// Use theme colors with slight tint
|
||||
ImVec4 text_color = ImGui::GetStyleColorVec4(ImGuiCol_Text);
|
||||
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
|
||||
|
||||
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();
|
||||
// Message header
|
||||
ImGui::TextColored(header_color, "%s", sender_label);
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, kTimestampColor);
|
||||
ImGui::Text("%s", absl::FormatTime("%H:%M:%S", msg.timestamp, absl::LocalTimeZone()).c_str());
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::TextDisabled("%s", absl::FormatTime("%H:%M:%S", msg.timestamp, absl::LocalTimeZone()).c_str());
|
||||
|
||||
// Message content
|
||||
ImGui::Indent(10.0f);
|
||||
@@ -181,10 +179,10 @@ void AgentChatHistoryPopup::DrawHeader() {
|
||||
|
||||
ImGui::Dummy(ImVec2(0, 8));
|
||||
|
||||
// Title with theme colors
|
||||
ImGui::TextColored(ImGui::GetStyleColorVec4(ImGuiCol_Text), "%s AI Chat", ICON_MD_CHAT);
|
||||
// Title
|
||||
ImGui::Text("%s AI Chat", ICON_MD_CHAT);
|
||||
|
||||
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 130);
|
||||
ImGui::SameLine(ImGui::GetContentRegionAvail().x - 95);
|
||||
|
||||
// Compact mode toggle
|
||||
if (ImGui::SmallButton(compact_mode_ ? ICON_MD_UNFOLD_MORE : ICON_MD_UNFOLD_LESS)) {
|
||||
@@ -197,18 +195,25 @@ void AgentChatHistoryPopup::DrawHeader() {
|
||||
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("Open full chat window");
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
// Close button
|
||||
if (ImGui::SmallButton(ICON_MD_CLOSE)) {
|
||||
visible_ = false;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Close (Ctrl+H)");
|
||||
}
|
||||
|
||||
// Message count with badge
|
||||
int visible_count = 0;
|
||||
for (const auto& msg : messages_) {
|
||||
@@ -228,9 +233,6 @@ void AgentChatHistoryPopup::DrawHeader() {
|
||||
}
|
||||
|
||||
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
|
||||
@@ -277,20 +279,13 @@ void AgentChatHistoryPopup::DrawQuickActions() {
|
||||
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);
|
||||
|
||||
// Input field using theme colors
|
||||
bool send_message = false;
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
if (ImGui::InputTextMultiline("##popup_input", input_buffer_, sizeof(input_buffer_),
|
||||
@@ -305,30 +300,18 @@ void AgentChatHistoryPopup::DrawInputSection() {
|
||||
focus_input_ = false;
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
// Send button with gradient
|
||||
// Send button
|
||||
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 (ImGui::Button(absl::StrFormat("%s Send", ICON_MD_SEND).c_str(), ImVec2(-1, 30)) || 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) {
|
||||
|
||||
Reference in New Issue
Block a user