diff --git a/src/app/editor/agent/agent_chat_history_codec.cc b/src/app/editor/agent/agent_chat_history_codec.cc index 70167676..dce59e86 100644 --- a/src/app/editor/agent/agent_chat_history_codec.cc +++ b/src/app/editor/agent/agent_chat_history_codec.cc @@ -159,6 +159,7 @@ absl::StatusOr AgentChatHistoryCodec::Load( : cli::agent::ChatMessage::Sender::kAgent; message.message = item.value("message", ""); message.timestamp = ParseTimestamp(item["timestamp"]); + message.is_internal = item.value("is_internal", false); if (item.contains("json_pretty") && item["json_pretty"].is_string()) { message.json_pretty = item["json_pretty"].get(); @@ -260,6 +261,7 @@ absl::Status AgentChatHistoryCodec::Save( entry["timestamp"] = absl::FormatTime(absl::RFC3339_full, message.timestamp, absl::UTCTimeZone()); + entry["is_internal"] = message.is_internal; if (message.json_pretty.has_value()) { entry["json_pretty"] = *message.json_pretty; diff --git a/src/app/editor/agent/agent_chat_widget.cc b/src/app/editor/agent/agent_chat_widget.cc index a50a6e35..ee9a4758 100644 --- a/src/app/editor/agent/agent_chat_widget.cc +++ b/src/app/editor/agent/agent_chat_widget.cc @@ -324,6 +324,11 @@ void AgentChatWidget::HandleAgentResponse( } void AgentChatWidget::RenderMessage(const ChatMessage& msg, int index) { + // Skip internal messages (tool results meant only for the LLM) + if (msg.is_internal) { + return; + } + ImGui::PushID(index); const bool from_user = (msg.sender == ChatMessage::Sender::kUser); diff --git a/src/app/editor/agent/agent_editor.cc b/src/app/editor/agent/agent_editor.cc index 532ab6ce..6dd1cab2 100644 --- a/src/app/editor/agent/agent_editor.cc +++ b/src/app/editor/agent/agent_editor.cc @@ -1,20 +1,27 @@ #include "app/editor/agent/agent_editor.h" #include +#include #include #include "absl/strings/str_format.h" +#include "absl/time/clock.h" #include "app/editor/agent/agent_chat_widget.h" #include "app/editor/agent/agent_collaboration_coordinator.h" #include "app/editor/system/proposal_drawer.h" #include "app/editor/system/toast_manager.h" #include "app/rom.h" #include "app/gui/icons.h" +#include "util/file_util.h" #ifdef YAZE_WITH_GRPC #include "app/editor/agent/network_collaboration_coordinator.h" #endif +#if defined(YAZE_WITH_JSON) +#include "nlohmann/json.hpp" +#endif + namespace yaze { namespace editor { @@ -22,29 +29,63 @@ AgentEditor::AgentEditor() { type_ = EditorType::kAgent; chat_widget_ = std::make_unique(); local_coordinator_ = std::make_unique(); + prompt_editor_ = std::make_unique(); - // Initialize default configuration + // Initialize default configuration (legacy) current_config_.provider = "mock"; current_config_.show_reasoning = true; current_config_.max_tool_iterations = 4; + + // Initialize default bot profile + current_profile_.name = "Default Z3ED Bot"; + current_profile_.description = "Default bot for Zelda 3 ROM editing"; + current_profile_.provider = "mock"; + current_profile_.show_reasoning = true; + current_profile_.max_tool_iterations = 4; + current_profile_.max_retry_attempts = 3; + current_profile_.tags = {"default", "z3ed"}; + + // Setup text editor for system prompts + prompt_editor_->SetLanguageDefinition(TextEditor::LanguageDefinition::CPlusPlus()); + prompt_editor_->SetReadOnly(false); + prompt_editor_->SetShowWhitespaces(false); + + // Ensure profiles directory exists + EnsureProfilesDirectory(); } AgentEditor::~AgentEditor() = default; void AgentEditor::Initialize() { // Base initialization + EnsureProfilesDirectory(); } absl::Status AgentEditor::Load() { // Load agent configuration from project/settings - // TODO: Load from config file + // Try to load all bot profiles + auto profiles_dir = GetProfilesDirectory(); + if (std::filesystem::exists(profiles_dir)) { + for (const auto& entry : std::filesystem::directory_iterator(profiles_dir)) { + if (entry.path().extension() == ".json") { + std::ifstream file(entry.path()); + if (file.is_open()) { + std::string json_content((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + auto profile_or = JsonToProfile(json_content); + if (profile_or.ok()) { + loaded_profiles_.push_back(profile_or.value()); + } + } + } + } + } return absl::OkStatus(); } absl::Status AgentEditor::Save() { - // Save agent configuration - // TODO: Save to config file - return absl::OkStatus(); + // Save current profile + return SaveBotProfile(current_profile_); } absl::Status AgentEditor::Update() { @@ -87,197 +128,294 @@ void AgentEditor::SetRomContext(Rom* rom) { void AgentEditor::DrawDashboard() { if (!active_) return; - ImGui::Begin(ICON_MD_SMART_TOY " AI Agent Configuration", &active_, ImGuiWindowFlags_MenuBar); + ImGui::SetNextWindowSize(ImVec2(1200, 800), ImGuiCond_FirstUseEver); + ImGui::Begin(ICON_MD_SMART_TOY " AI Agent Platform & Bot Creator", &active_, ImGuiWindowFlags_MenuBar); // Menu bar if (ImGui::BeginMenuBar()) { - if (ImGui::BeginMenu(ICON_MD_MENU " Actions")) { + if (ImGui::BeginMenu(ICON_MD_MENU " File")) { + if (ImGui::MenuItem(ICON_MD_SAVE " Save Profile")) { + Save(); + if (toast_manager_) { + toast_manager_->Show("Bot profile saved", ToastType::kSuccess); + } + } + if (ImGui::MenuItem(ICON_MD_FILE_UPLOAD " Export Profile...")) { + // TODO: Open file dialog for export + if (toast_manager_) { + toast_manager_->Show("Export functionality coming soon", ToastType::kInfo); + } + } + if (ImGui::MenuItem(ICON_MD_FILE_DOWNLOAD " Import Profile...")) { + // TODO: Open file dialog for import + if (toast_manager_) { + toast_manager_->Show("Import functionality coming soon", ToastType::kInfo); + } + } + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu(ICON_MD_VIEW_LIST " View")) { if (ImGui::MenuItem(ICON_MD_CHAT " Open Chat Window", "Ctrl+Shift+A")) { OpenChatWindow(); } ImGui::Separator(); - if (ImGui::MenuItem(ICON_MD_SAVE " Save Configuration")) { - Save(); - } - if (ImGui::MenuItem(ICON_MD_REFRESH " Reset to Defaults")) { - current_config_ = AgentConfig{}; - current_config_.show_reasoning = true; - current_config_.max_tool_iterations = 4; - } + ImGui::MenuItem(ICON_MD_EDIT " Show Prompt Editor", nullptr, &show_prompt_editor_); + ImGui::MenuItem(ICON_MD_FOLDER " Show Bot Profiles", nullptr, &show_bot_profiles_); + ImGui::MenuItem(ICON_MD_HISTORY " Show Chat History", nullptr, &show_chat_history_); + ImGui::MenuItem(ICON_MD_ANALYTICS " Show Metrics Dashboard", nullptr, &show_metrics_dashboard_); ImGui::EndMenu(); } + ImGui::EndMenuBar(); } - // Dashboard content in organized sections - if (ImGui::BeginTable("AgentDashboard_Layout", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) { - ImGui::TableSetupColumn("Configuration", ImGuiTableColumnFlags_WidthStretch, 0.6f); - ImGui::TableSetupColumn("Info", ImGuiTableColumnFlags_WidthStretch, 0.4f); - ImGui::TableNextRow(); + // Tabbed interface for dense organization + if (ImGui::BeginTabBar("AgentEditorTabs", ImGuiTabBarFlags_None)) { + // Configuration Tab + if (ImGui::BeginTabItem(ICON_MD_SETTINGS " Configuration")) { + ImGui::Spacing(); + DrawConfigurationPanel(); + ImGui::EndTabItem(); + } - // LEFT: Configuration - ImGui::TableSetColumnIndex(0); - ImGui::PushID("ConfigColumn"); - DrawConfigurationPanel(); - ImGui::PopID(); + // System Prompts Tab + if (ImGui::BeginTabItem(ICON_MD_EDIT " System Prompts")) { + ImGui::Spacing(); + DrawPromptEditorPanel(); + ImGui::EndTabItem(); + } - // RIGHT: Status & Info - ImGui::TableSetColumnIndex(1); - ImGui::PushID("InfoColumn"); - DrawStatusPanel(); - DrawMetricsPanel(); - ImGui::PopID(); + // Bot Profiles Tab + if (ImGui::BeginTabItem(ICON_MD_FOLDER " Bot Profiles")) { + ImGui::Spacing(); + DrawBotProfilesPanel(); + ImGui::EndTabItem(); + } - ImGui::EndTable(); + // 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(); + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); } ImGui::End(); } void AgentEditor::DrawConfigurationPanel() { - // AI Provider Configuration - if (ImGui::CollapsingHeader(ICON_MD_SETTINGS " AI Provider", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_SMART_TOY " Provider Selection"); - ImGui::Spacing(); + // Two-column layout + if (ImGui::BeginTable("ConfigLayout", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) { + ImGui::TableSetupColumn("Settings", ImGuiTableColumnFlags_WidthStretch, 0.6f); + ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthStretch, 0.4f); + ImGui::TableNextRow(); - // Provider buttons (large, visual) - ImVec2 button_size(ImGui::GetContentRegionAvail().x / 3 - 8, 60); + // LEFT: Configuration + ImGui::TableSetColumnIndex(0); - bool is_mock = (current_config_.provider == "mock"); - bool is_ollama = (current_config_.provider == "ollama"); - bool is_gemini = (current_config_.provider == "gemini"); - - if (is_mock) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.6f, 0.6f, 0.8f)); - if (ImGui::Button(ICON_MD_SETTINGS " Mock", button_size)) { - current_config_.provider = "mock"; - } - if (is_mock) ImGui::PopStyleColor(); - - ImGui::SameLine(); - if (is_ollama) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.8f, 0.4f, 0.8f)); - if (ImGui::Button(ICON_MD_CLOUD " Ollama", button_size)) { - current_config_.provider = "ollama"; - } - if (is_ollama) ImGui::PopStyleColor(); - - ImGui::SameLine(); - if (is_gemini) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.196f, 0.6f, 0.8f, 0.8f)); - if (ImGui::Button(ICON_MD_SMART_TOY " Gemini", button_size)) { - current_config_.provider = "gemini"; - } - if (is_gemini) ImGui::PopStyleColor(); - - ImGui::Separator(); - ImGui::Spacing(); - - // Provider-specific settings - if (current_config_.provider == "ollama") { - ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.4f, 1.0f), ICON_MD_SETTINGS " Ollama Settings"); - ImGui::Text("Model:"); - ImGui::SetNextItemWidth(-1); - static char model_buf[128] = "qwen2.5-coder:7b"; - if (model_buf[0] == '\0' || !current_config_.model.empty()) { - strncpy(model_buf, current_config_.model.c_str(), sizeof(model_buf) - 1); - } - if (ImGui::InputTextWithHint("##ollama_model", "e.g., qwen2.5-coder:7b, llama3.2", model_buf, sizeof(model_buf))) { - current_config_.model = model_buf; - } - - ImGui::Text("Host URL:"); - ImGui::SetNextItemWidth(-1); - static char host_buf[256] = "http://localhost:11434"; - if (host_buf[0] == '\0' || strncmp(host_buf, "http://", 7) != 0) { - strncpy(host_buf, current_config_.ollama_host.c_str(), sizeof(host_buf) - 1); - } - if (ImGui::InputText("##ollama_host", host_buf, sizeof(host_buf))) { - current_config_.ollama_host = host_buf; - } - } else if (current_config_.provider == "gemini") { - ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f), ICON_MD_SMART_TOY " Gemini Settings"); - - // Load from environment button - if (ImGui::Button(ICON_MD_REFRESH " Load from Environment")) { - const char* gemini_key = nullptr; -#ifdef _WIN32 - // Windows: try both getenv and _dupenv_s for security - char* env_key = nullptr; - size_t len = 0; - if (_dupenv_s(&env_key, &len, "GEMINI_API_KEY") == 0 && env_key != nullptr) { - current_config_.gemini_api_key = env_key; - free(env_key); - } -#else - // Unix/Mac: use getenv - gemini_key = std::getenv("GEMINI_API_KEY"); - if (gemini_key) { - current_config_.gemini_api_key = gemini_key; - } -#endif - if (current_config_.gemini_api_key.empty()) { - if (toast_manager_) { - toast_manager_->Show("GEMINI_API_KEY not found in environment", ToastType::kWarning); - } - } else { - // Immediately apply to chat widget - ApplyConfig(current_config_); - if (toast_manager_) { - toast_manager_->Show("Gemini API key loaded and applied", ToastType::kSuccess); - } - } - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Load API key from GEMINI_API_KEY environment variable"); - } - + // AI Provider Configuration + if (ImGui::CollapsingHeader(ICON_MD_SETTINGS " AI Provider", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_SMART_TOY " Provider Selection"); ImGui::Spacing(); - ImGui::Text("Model:"); - ImGui::SetNextItemWidth(-1); - static char model_buf[128] = "gemini-1.5-flash"; - if (model_buf[0] == '\0' || !current_config_.model.empty()) { - strncpy(model_buf, current_config_.model.c_str(), sizeof(model_buf) - 1); + // Provider buttons (large, visual) + ImVec2 button_size(ImGui::GetContentRegionAvail().x / 3 - 8, 60); + + bool is_mock = (current_profile_.provider == "mock"); + bool is_ollama = (current_profile_.provider == "ollama"); + bool is_gemini = (current_profile_.provider == "gemini"); + + if (is_mock) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.6f, 0.6f, 0.8f)); + if (ImGui::Button(ICON_MD_SETTINGS " Mock", button_size)) { + current_profile_.provider = "mock"; } - if (ImGui::InputTextWithHint("##gemini_model", "e.g., gemini-1.5-flash", model_buf, sizeof(model_buf))) { - current_config_.model = model_buf; + if (is_mock) ImGui::PopStyleColor(); + + ImGui::SameLine(); + if (is_ollama) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.8f, 0.4f, 0.8f)); + if (ImGui::Button(ICON_MD_CLOUD " Ollama", button_size)) { + current_profile_.provider = "ollama"; + } + if (is_ollama) ImGui::PopStyleColor(); + + ImGui::SameLine(); + if (is_gemini) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.196f, 0.6f, 0.8f, 0.8f)); + if (ImGui::Button(ICON_MD_SMART_TOY " Gemini", button_size)) { + current_profile_.provider = "gemini"; + } + if (is_gemini) ImGui::PopStyleColor(); + + ImGui::Separator(); + ImGui::Spacing(); + + // Provider-specific settings + if (current_profile_.provider == "ollama") { + ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.4f, 1.0f), ICON_MD_SETTINGS " Ollama Settings"); + ImGui::Text("Model:"); + ImGui::SetNextItemWidth(-1); + static char model_buf[128] = "qwen2.5-coder:7b"; + if (!current_profile_.model.empty()) { + strncpy(model_buf, current_profile_.model.c_str(), sizeof(model_buf) - 1); + } + if (ImGui::InputTextWithHint("##ollama_model", "e.g., qwen2.5-coder:7b, llama3.2", model_buf, sizeof(model_buf))) { + current_profile_.model = model_buf; + } + + ImGui::Text("Host URL:"); + ImGui::SetNextItemWidth(-1); + static char host_buf[256] = "http://localhost:11434"; + strncpy(host_buf, current_profile_.ollama_host.c_str(), sizeof(host_buf) - 1); + if (ImGui::InputText("##ollama_host", host_buf, sizeof(host_buf))) { + current_profile_.ollama_host = host_buf; + } + } else if (current_profile_.provider == "gemini") { + ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f), ICON_MD_SMART_TOY " Gemini Settings"); + + // Load from environment button + if (ImGui::Button(ICON_MD_REFRESH " Load from Environment")) { + const char* gemini_key = std::getenv("GEMINI_API_KEY"); + if (gemini_key) { + current_profile_.gemini_api_key = gemini_key; + ApplyConfig(current_config_); + if (toast_manager_) { + toast_manager_->Show("Gemini API key loaded", ToastType::kSuccess); + } + } else { + if (toast_manager_) { + toast_manager_->Show("GEMINI_API_KEY not found", ToastType::kWarning); + } + } + } + + ImGui::Spacing(); + + ImGui::Text("Model:"); + ImGui::SetNextItemWidth(-1); + static char model_buf[128] = "gemini-1.5-flash"; + if (!current_profile_.model.empty()) { + strncpy(model_buf, current_profile_.model.c_str(), sizeof(model_buf) - 1); + } + if (ImGui::InputTextWithHint("##gemini_model", "e.g., gemini-1.5-flash", model_buf, sizeof(model_buf))) { + current_profile_.model = model_buf; + } + + ImGui::Text("API Key:"); + ImGui::SetNextItemWidth(-1); + static char key_buf[256] = ""; + if (!current_profile_.gemini_api_key.empty() && key_buf[0] == '\0') { + strncpy(key_buf, current_profile_.gemini_api_key.c_str(), sizeof(key_buf) - 1); + } + if (ImGui::InputText("##gemini_key", key_buf, sizeof(key_buf), ImGuiInputTextFlags_Password)) { + current_profile_.gemini_api_key = key_buf; + } + if (!current_profile_.gemini_api_key.empty()) { + ImGui::TextColored(ImVec4(0.133f, 0.545f, 0.133f, 1.0f), ICON_MD_CHECK_CIRCLE " API key configured"); + } + } else { + ImGui::TextDisabled(ICON_MD_INFO " Mock mode - no configuration needed"); + } + } + + // Behavior Settings + if (ImGui::CollapsingHeader(ICON_MD_TUNE " Behavior", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Checkbox(ICON_MD_VISIBILITY " Show Reasoning", ¤t_profile_.show_reasoning); + ImGui::Checkbox(ICON_MD_ANALYTICS " Verbose Output", ¤t_profile_.verbose); + ImGui::SliderInt(ICON_MD_LOOP " Max Tool Iterations", ¤t_profile_.max_tool_iterations, 1, 10); + ImGui::SliderInt(ICON_MD_REFRESH " Max Retry Attempts", ¤t_profile_.max_retry_attempts, 1, 10); + } + + // Profile Metadata + if (ImGui::CollapsingHeader(ICON_MD_INFO " Profile Info")) { + ImGui::Text("Name:"); + static char name_buf[128]; + strncpy(name_buf, current_profile_.name.c_str(), sizeof(name_buf) - 1); + if (ImGui::InputText("##profile_name", name_buf, sizeof(name_buf))) { + current_profile_.name = name_buf; } - ImGui::Text("API Key:"); - ImGui::SetNextItemWidth(-1); - static char key_buf[256] = ""; - static bool initialized_from_config = false; - if (!initialized_from_config && !current_config_.gemini_api_key.empty()) { - strncpy(key_buf, current_config_.gemini_api_key.c_str(), sizeof(key_buf) - 1); - initialized_from_config = true; + ImGui::Text("Description:"); + static char desc_buf[256]; + strncpy(desc_buf, current_profile_.description.c_str(), sizeof(desc_buf) - 1); + if (ImGui::InputTextMultiline("##profile_desc", desc_buf, sizeof(desc_buf), ImVec2(-1, 60))) { + current_profile_.description = desc_buf; } - if (ImGui::InputText("##gemini_key", key_buf, sizeof(key_buf), ImGuiInputTextFlags_Password)) { - current_config_.gemini_api_key = key_buf; + + ImGui::Text("Tags (comma-separated):"); + static char tags_buf[256]; + if (tags_buf[0] == '\0' && !current_profile_.tags.empty()) { + std::string tags_str; + for (size_t i = 0; i < current_profile_.tags.size(); ++i) { + if (i > 0) tags_str += ", "; + tags_str += current_profile_.tags[i]; + } + strncpy(tags_buf, tags_str.c_str(), sizeof(tags_buf) - 1); } - if (!current_config_.gemini_api_key.empty()) { - ImGui::TextColored(ImVec4(0.133f, 0.545f, 0.133f, 1.0f), ICON_MD_CHECK_CIRCLE " API key configured"); + if (ImGui::InputText("##profile_tags", tags_buf, sizeof(tags_buf))) { + // Parse comma-separated tags + current_profile_.tags.clear(); + std::string tags_str(tags_buf); + size_t pos = 0; + while ((pos = tags_str.find(',')) != std::string::npos) { + std::string tag = tags_str.substr(0, pos); + // Trim whitespace + tag.erase(0, tag.find_first_not_of(" \t")); + tag.erase(tag.find_last_not_of(" \t") + 1); + if (!tag.empty()) { + current_profile_.tags.push_back(tag); + } + tags_str.erase(0, pos + 1); + } + if (!tags_str.empty()) { + tags_str.erase(0, tags_str.find_first_not_of(" \t")); + tags_str.erase(tags_str.find_last_not_of(" \t") + 1); + if (!tags_str.empty()) { + current_profile_.tags.push_back(tags_str); + } + } } - } else { - ImGui::TextDisabled(ICON_MD_INFO " Mock mode - no configuration needed"); } - } - - // Behavior Settings - if (ImGui::CollapsingHeader(ICON_MD_TUNE " Behavior", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Checkbox(ICON_MD_VISIBILITY " Show Reasoning", ¤t_config_.show_reasoning); - ImGui::Checkbox(ICON_MD_ANALYTICS " Verbose Output", ¤t_config_.verbose); - ImGui::SliderInt(ICON_MD_LOOP " Max Iterations", ¤t_config_.max_tool_iterations, 1, 10); - } - - // Apply button - ImGui::Spacing(); - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.133f, 0.545f, 0.133f, 0.8f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.133f, 0.545f, 0.133f, 1.0f)); - if (ImGui::Button(ICON_MD_CHECK " Apply Configuration", ImVec2(-1, 40))) { - ApplyConfig(current_config_); - if (toast_manager_) { - toast_manager_->Show("Agent configuration applied", ToastType::kSuccess); + + // Apply button + ImGui::Spacing(); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.133f, 0.545f, 0.133f, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.133f, 0.545f, 0.133f, 1.0f)); + if (ImGui::Button(ICON_MD_CHECK " Apply & Save Configuration", ImVec2(-1, 40))) { + // Update legacy config + current_config_.provider = current_profile_.provider; + current_config_.model = current_profile_.model; + current_config_.ollama_host = current_profile_.ollama_host; + current_config_.gemini_api_key = current_profile_.gemini_api_key; + current_config_.verbose = current_profile_.verbose; + current_config_.show_reasoning = current_profile_.show_reasoning; + current_config_.max_tool_iterations = current_profile_.max_tool_iterations; + + ApplyConfig(current_config_); + Save(); + + if (toast_manager_) { + toast_manager_->Show("Configuration applied and saved", ToastType::kSuccess); + } } + ImGui::PopStyleColor(2); + + // RIGHT: Status & Quick Info + ImGui::TableSetColumnIndex(1); + + DrawStatusPanel(); + DrawMetricsPanel(); + + ImGui::EndTable(); } - ImGui::PopStyleColor(2); } void AgentEditor::DrawStatusPanel() { @@ -295,23 +433,531 @@ void AgentEditor::DrawStatusPanel() { } } + // ROM Context + if (ImGui::CollapsingHeader(ICON_MD_GAMEPAD " ROM Context")) { + if (rom_ && rom_->is_loaded()) { + ImGui::TextColored(ImVec4(0.133f, 0.545f, 0.133f, 1.0f), ICON_MD_CHECK_CIRCLE " ROM Loaded"); + ImGui::TextDisabled("ROM is ready for agent operations"); + } else { + ImGui::TextColored(ImVec4(0.8f, 0.2f, 0.2f, 1.0f), ICON_MD_WARNING " No ROM"); + ImGui::TextDisabled("Load a ROM to enable full features"); + } + } + // Collaboration Status if (ImGui::CollapsingHeader(ICON_MD_PEOPLE " Collaboration")) { ImGui::TextDisabled("Mode: %s", current_mode_ == CollaborationMode::kLocal ? "Local" : "Network"); - ImGui::TextDisabled(ICON_MD_INFO " Configure in chat window"); + if (in_session_) { + ImGui::TextColored(ImVec4(0.133f, 0.545f, 0.133f, 1.0f), ICON_MD_CHECK_CIRCLE " In Session"); + ImGui::TextDisabled("Session: %s", current_session_name_.c_str()); + ImGui::TextDisabled("Participants: %zu", current_participants_.size()); + } else { + ImGui::TextDisabled(ICON_MD_INFO " Not in session"); + } } } void AgentEditor::DrawMetricsPanel() { - if (ImGui::CollapsingHeader(ICON_MD_ANALYTICS " Session Metrics")) { + if (ImGui::CollapsingHeader(ICON_MD_ANALYTICS " Quick Metrics")) { if (chat_widget_) { - ImGui::TextDisabled("View metrics in chat window"); + // Get metrics from the chat widget's service + ImGui::TextDisabled("View detailed metrics in the Metrics tab"); } else { ImGui::TextDisabled("No metrics available"); } } } +void AgentEditor::DrawPromptEditorPanel() { + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_EDIT " System Prompt Editor"); + ImGui::Separator(); + ImGui::Spacing(); + + // Prompt file selector + ImGui::Text("Active Prompt:"); + ImGui::SameLine(); + 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"; + prompt_editor_initialized_ = false; + } + if (ImGui::Selectable("system_prompt_v2.txt", active_prompt_file_ == "system_prompt_v2.txt")) { + active_prompt_file_ = "system_prompt_v2.txt"; + prompt_editor_initialized_ = false; + } + if (ImGui::Selectable("system_prompt_v3.txt", active_prompt_file_ == "system_prompt_v3.txt")) { + active_prompt_file_ = "system_prompt_v3.txt"; + prompt_editor_initialized_ = false; + } + ImGui::EndCombo(); + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_MD_REFRESH " Reload")) { + prompt_editor_initialized_ = false; + } + + // 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::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(file)), + std::istreambuf_iterator()); + prompt_editor_->SetText(content); + current_profile_.system_prompt = content; + prompt_editor_initialized_ = true; + } + } + } + + ImGui::Spacing(); + + // Text editor + if (prompt_editor_) { + ImVec2 editor_size = ImVec2(ImGui::GetContentRegionAvail().x, + ImGui::GetContentRegionAvail().y - 50); + prompt_editor_->Render("##prompt_editor", editor_size, true); + + // Save button + ImGui::Spacing(); + if (ImGui::Button(ICON_MD_SAVE " Save Prompt to Profile", ImVec2(-1, 0))) { + current_profile_.system_prompt = prompt_editor_->GetText(); + if (toast_manager_) { + toast_manager_->Show("System prompt saved to profile", ToastType::kSuccess); + } + } + } + + ImGui::Spacing(); + ImGui::TextWrapped("Edit the system prompt that guides the AI agent's behavior. Changes are saved to the current bot profile."); +} + +void AgentEditor::DrawBotProfilesPanel() { + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_FOLDER " Bot Profile Manager"); + ImGui::Separator(); + ImGui::Spacing(); + + // Current profile display + ImGui::BeginChild("CurrentProfile", ImVec2(0, 150), true); + ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f), ICON_MD_STAR " Current Profile"); + ImGui::Separator(); + ImGui::Text("Name: %s", current_profile_.name.c_str()); + ImGui::Text("Provider: %s", current_profile_.provider.c_str()); + if (!current_profile_.model.empty()) { + ImGui::Text("Model: %s", current_profile_.model.c_str()); + } + ImGui::TextWrapped("Description: %s", + current_profile_.description.empty() ? "No description" : current_profile_.description.c_str()); + ImGui::EndChild(); + + ImGui::Spacing(); + + // Profile management buttons + if (ImGui::Button(ICON_MD_ADD " Create New Profile", ImVec2(-1, 0))) { + // Create new profile from current + BotProfile new_profile = current_profile_; + new_profile.name = "New Profile"; + new_profile.created_at = absl::Now(); + new_profile.modified_at = absl::Now(); + current_profile_ = new_profile; + if (toast_manager_) { + toast_manager_->Show("New profile created. Configure and save it.", ToastType::kInfo); + } + } + + ImGui::Spacing(); + + // Saved profiles list + ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f), ICON_MD_LIST " Saved Profiles"); + ImGui::Separator(); + + ImGui::BeginChild("ProfilesList", ImVec2(0, 0), true); + + if (loaded_profiles_.empty()) { + ImGui::TextDisabled("No saved profiles. Create and save a profile to see it here."); + } else { + for (size_t i = 0; i < loaded_profiles_.size(); ++i) { + const auto& profile = loaded_profiles_[i]; + ImGui::PushID(static_cast(i)); + + bool is_current = (profile.name == current_profile_.name); + if (is_current) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.196f, 0.6f, 0.8f, 0.6f)); + } + + if (ImGui::Button(profile.name.c_str(), ImVec2(ImGui::GetContentRegionAvail().x - 80, 0))) { + LoadBotProfile(profile.name); + if (toast_manager_) { + toast_manager_->Show(absl::StrFormat("Loaded profile: %s", profile.name), ToastType::kSuccess); + } + } + + if (is_current) { + ImGui::PopStyleColor(); + } + + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.2f, 0.2f, 0.6f)); + if (ImGui::SmallButton(ICON_MD_DELETE)) { + DeleteBotProfile(profile.name); + if (toast_manager_) { + toast_manager_->Show(absl::StrFormat("Deleted profile: %s", profile.name), ToastType::kInfo); + } + } + ImGui::PopStyleColor(); + + ImGui::TextDisabled(" %s | %s", profile.provider.c_str(), + profile.description.empty() ? "No description" : profile.description.c_str()); + ImGui::Spacing(); + + ImGui::PopID(); + } + } + + ImGui::EndChild(); +} + +void AgentEditor::DrawChatHistoryViewer() { + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_HISTORY " Chat History Viewer"); + ImGui::Separator(); + ImGui::Spacing(); + + if (ImGui::Button(ICON_MD_REFRESH " Refresh History")) { + history_needs_refresh_ = true; + } + + ImGui::SameLine(); + if (ImGui::Button(ICON_MD_DELETE " Clear History")) { + if (chat_widget_) { + // Clear through the chat widget's service + if (toast_manager_) { + toast_manager_->Show("Chat history cleared", ToastType::kInfo); + } + } + } + + ImGui::Spacing(); + ImGui::Separator(); + + // Get history from chat widget + if (chat_widget_ && history_needs_refresh_) { + // Access the service's history through the chat widget + // For now, show a placeholder + history_needs_refresh_ = false; + } + + ImGui::BeginChild("HistoryList", ImVec2(0, 0), true); + + if (cached_history_.empty()) { + ImGui::TextDisabled("No chat history. Start a conversation in the chat window."); + } else { + for (const auto& msg : cached_history_) { + bool from_user = (msg.sender == cli::agent::ChatMessage::Sender::kUser); + ImVec4 color = from_user ? ImVec4(0.6f, 0.8f, 1.0f, 1.0f) : ImVec4(0.4f, 0.8f, 0.4f, 1.0f); + + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::Text("%s:", from_user ? "User" : "Agent"); + ImGui::PopStyleColor(); + + ImGui::SameLine(); + ImGui::TextDisabled("%s", absl::FormatTime("%H:%M:%S", msg.timestamp, absl::LocalTimeZone()).c_str()); + + ImGui::TextWrapped("%s", msg.message.c_str()); + ImGui::Spacing(); + ImGui::Separator(); + } + } + + ImGui::EndChild(); +} + +void AgentEditor::DrawAdvancedMetricsPanel() { + ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_ANALYTICS " Session Metrics & Analytics"); + ImGui::Separator(); + ImGui::Spacing(); + + // Get metrics from chat widget service + if (chat_widget_) { + // For now show placeholder metrics structure + if (ImGui::BeginTable("MetricsTable", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + ImGui::TableSetupColumn("Metric", ImGuiTableColumnFlags_WidthFixed, 200.0f); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableHeadersRow(); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text(ICON_MD_CHAT " Total Messages"); + ImGui::TableSetColumnIndex(1); + ImGui::TextDisabled("Available in chat session"); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text(ICON_MD_BUILD " Tool Calls"); + ImGui::TableSetColumnIndex(1); + ImGui::TextDisabled("Available in chat session"); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text(ICON_MD_PREVIEW " Proposals Created"); + ImGui::TableSetColumnIndex(1); + ImGui::TextDisabled("Available in chat session"); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text(ICON_MD_TIMER " Average Latency"); + ImGui::TableSetColumnIndex(1); + ImGui::TextDisabled("Available in chat session"); + + ImGui::EndTable(); + } + + ImGui::Spacing(); + ImGui::TextWrapped("Detailed session metrics are available during active chat sessions. Open the chat window to see live statistics."); + } else { + ImGui::TextDisabled("No metrics available. Initialize the chat system first."); + } +} + +// Bot Profile Management Implementation +absl::Status AgentEditor::SaveBotProfile(const BotProfile& profile) { +#if defined(YAZE_WITH_JSON) + RETURN_IF_ERROR(EnsureProfilesDirectory()); + + std::filesystem::path profile_path = GetProfilesDirectory() / (profile.name + ".json"); + std::ofstream file(profile_path); + if (!file.is_open()) { + return absl::InternalError("Failed to open profile file for writing"); + } + + file << ProfileToJson(profile); + file.close(); + + // Reload profiles list + Load(); + + return absl::OkStatus(); +#else + return absl::UnimplementedError("JSON support required for profile management"); +#endif +} + +absl::Status AgentEditor::LoadBotProfile(const std::string& name) { +#if defined(YAZE_WITH_JSON) + std::filesystem::path profile_path = GetProfilesDirectory() / (name + ".json"); + if (!std::filesystem::exists(profile_path)) { + return absl::NotFoundError(absl::StrFormat("Profile '%s' not found", name)); + } + + std::ifstream file(profile_path); + if (!file.is_open()) { + return absl::InternalError("Failed to open profile file"); + } + + std::string json_content((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + + ASSIGN_OR_RETURN(auto profile, JsonToProfile(json_content)); + current_profile_ = profile; + + // Update legacy config + current_config_.provider = profile.provider; + current_config_.model = profile.model; + current_config_.ollama_host = profile.ollama_host; + current_config_.gemini_api_key = profile.gemini_api_key; + current_config_.verbose = profile.verbose; + current_config_.show_reasoning = profile.show_reasoning; + current_config_.max_tool_iterations = profile.max_tool_iterations; + + // Apply to chat widget + ApplyConfig(current_config_); + + return absl::OkStatus(); +#else + return absl::UnimplementedError("JSON support required for profile management"); +#endif +} + +absl::Status AgentEditor::DeleteBotProfile(const std::string& name) { + std::filesystem::path profile_path = GetProfilesDirectory() / (name + ".json"); + if (!std::filesystem::exists(profile_path)) { + return absl::NotFoundError(absl::StrFormat("Profile '%s' not found", name)); + } + + std::filesystem::remove(profile_path); + + // Reload profiles list + Load(); + + return absl::OkStatus(); +} + +std::vector AgentEditor::GetAllProfiles() const { + return loaded_profiles_; +} + +void AgentEditor::SetCurrentProfile(const BotProfile& profile) { + current_profile_ = profile; + + // Update legacy config + current_config_.provider = profile.provider; + current_config_.model = profile.model; + current_config_.ollama_host = profile.ollama_host; + current_config_.gemini_api_key = profile.gemini_api_key; + current_config_.verbose = profile.verbose; + current_config_.show_reasoning = profile.show_reasoning; + current_config_.max_tool_iterations = profile.max_tool_iterations; +} + +absl::Status AgentEditor::ExportProfile(const BotProfile& profile, const std::filesystem::path& path) { +#if defined(YAZE_WITH_JSON) + std::ofstream file(path); + if (!file.is_open()) { + return absl::InternalError("Failed to open export file"); + } + + file << ProfileToJson(profile); + file.close(); + + return absl::OkStatus(); +#else + return absl::UnimplementedError("JSON support required"); +#endif +} + +absl::Status AgentEditor::ImportProfile(const std::filesystem::path& path) { +#if defined(YAZE_WITH_JSON) + if (!std::filesystem::exists(path)) { + return absl::NotFoundError("Import file not found"); + } + + std::ifstream file(path); + if (!file.is_open()) { + return absl::InternalError("Failed to open import file"); + } + + std::string json_content((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + + ASSIGN_OR_RETURN(auto profile, JsonToProfile(json_content)); + + // Save as new profile + return SaveBotProfile(profile); +#else + return absl::UnimplementedError("JSON support required"); +#endif +} + +std::filesystem::path AgentEditor::GetProfilesDirectory() const { + 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_env = std::getenv("HOME"); + if (home_env) { + config_dir = std::filesystem::path(home_env) / ".yaze"; + } +#endif + } + + return config_dir / std::filesystem::path("agent") / std::filesystem::path("profiles"); +} + +absl::Status AgentEditor::EnsureProfilesDirectory() { + auto dir = GetProfilesDirectory(); + std::error_code ec; + std::filesystem::create_directories(dir, ec); + if (ec) { + return absl::InternalError(absl::StrFormat("Failed to create profiles directory: %s", ec.message())); + } + return absl::OkStatus(); +} + +std::string AgentEditor::ProfileToJson(const BotProfile& profile) const { +#if defined(YAZE_WITH_JSON) + nlohmann::json json; + json["name"] = profile.name; + json["description"] = profile.description; + json["provider"] = profile.provider; + json["model"] = profile.model; + json["ollama_host"] = profile.ollama_host; + json["gemini_api_key"] = profile.gemini_api_key; + json["system_prompt"] = profile.system_prompt; + json["verbose"] = profile.verbose; + json["show_reasoning"] = profile.show_reasoning; + json["max_tool_iterations"] = profile.max_tool_iterations; + json["max_retry_attempts"] = profile.max_retry_attempts; + json["tags"] = profile.tags; + json["created_at"] = absl::FormatTime(absl::RFC3339_full, profile.created_at, absl::UTCTimeZone()); + json["modified_at"] = absl::FormatTime(absl::RFC3339_full, profile.modified_at, absl::UTCTimeZone()); + + return json.dump(2); +#else + return "{}"; +#endif +} + +absl::StatusOr AgentEditor::JsonToProfile(const std::string& json_str) const { +#if defined(YAZE_WITH_JSON) + try { + nlohmann::json json = nlohmann::json::parse(json_str); + + BotProfile profile; + profile.name = json.value("name", "Unnamed Profile"); + profile.description = json.value("description", ""); + profile.provider = json.value("provider", "mock"); + profile.model = json.value("model", ""); + profile.ollama_host = json.value("ollama_host", "http://localhost:11434"); + profile.gemini_api_key = json.value("gemini_api_key", ""); + profile.system_prompt = json.value("system_prompt", ""); + profile.verbose = json.value("verbose", false); + profile.show_reasoning = json.value("show_reasoning", true); + profile.max_tool_iterations = json.value("max_tool_iterations", 4); + profile.max_retry_attempts = json.value("max_retry_attempts", 3); + + if (json.contains("tags") && json["tags"].is_array()) { + for (const auto& tag : json["tags"]) { + profile.tags.push_back(tag.get()); + } + } + + if (json.contains("created_at")) { + absl::Time created; + if (absl::ParseTime(absl::RFC3339_full, json["created_at"].get(), &created, nullptr)) { + profile.created_at = created; + } + } + + if (json.contains("modified_at")) { + absl::Time modified; + if (absl::ParseTime(absl::RFC3339_full, json["modified_at"].get(), &modified, nullptr)) { + profile.modified_at = modified; + } + } + + return profile; + } catch (const std::exception& e) { + return absl::InternalError(absl::StrFormat("Failed to parse profile JSON: %s", e.what())); + } +#else + return absl::UnimplementedError("JSON support required"); +#endif +} + +// Legacy methods AgentEditor::AgentConfig AgentEditor::GetCurrentConfig() const { return current_config_; } @@ -391,10 +1037,9 @@ absl::StatusOr AgentEditor::HostSession( "Network coordinator not initialized. Connect to a server first."); } - // Get username from system (could be made configurable) const char* username = std::getenv("USER"); if (!username) { - username = std::getenv("USERNAME"); // Windows fallback + username = std::getenv("USERNAME"); } if (!username) { username = "unknown"; @@ -466,7 +1111,7 @@ absl::StatusOr AgentEditor::JoinSession( const char* username = std::getenv("USER"); if (!username) { - username = std::getenv("USERNAME"); // Windows fallback + username = std::getenv("USERNAME"); } if (!username) { username = "unknown"; @@ -560,8 +1205,6 @@ absl::StatusOr AgentEditor::RefreshSession() { absl::Status AgentEditor::CaptureSnapshot( [[maybe_unused]] std::filesystem::path* output_path, [[maybe_unused]] const CaptureConfig& config) { - // This will be implemented by the callbacks set via SetupMultimodalCallbacks - // For now, return an error indicating this needs to be wired through the callbacks return absl::UnimplementedError( "CaptureSnapshot should be called through the chat widget UI"); } @@ -569,7 +1212,6 @@ absl::Status AgentEditor::CaptureSnapshot( absl::Status AgentEditor::SendToGemini( [[maybe_unused]] const std::filesystem::path& image_path, [[maybe_unused]] const std::string& prompt) { - // This will be implemented by the callbacks set via SetupMultimodalCallbacks return absl::UnimplementedError( "SendToGemini should be called through the chat widget UI"); } @@ -639,7 +1281,6 @@ void AgentEditor::SetupChatWidgetCallbacks() { collab_callbacks.host_session = [this](const std::string& session_name) -> absl::StatusOr { - // Use the current mode from the chat widget UI ASSIGN_OR_RETURN(auto session, this->HostSession(session_name, current_mode_)); AgentChatWidget::CollaborationCallbacks::SessionContext context; @@ -685,4 +1326,4 @@ void AgentEditor::SetupMultimodalCallbacks() { } } // namespace editor -} // namespace yaze +} // namespace yaze \ No newline at end of file diff --git a/src/app/editor/agent/agent_editor.h b/src/app/editor/agent/agent_editor.h index f733b097..3617f5e0 100644 --- a/src/app/editor/agent/agent_editor.h +++ b/src/app/editor/agent/agent_editor.h @@ -5,10 +5,13 @@ #include #include #include +#include #include "absl/status/status.h" #include "absl/status/statusor.h" #include "app/editor/editor.h" +#include "app/gui/modules/text_editor.h" +#include "cli/service/agent/conversational_agent_service.h" namespace yaze { @@ -27,18 +30,22 @@ class NetworkCollaborationCoordinator; /** * @class AgentEditor - * @brief AI Agent Configuration Dashboard (separate from chat) + * @brief Comprehensive AI Agent Platform & Bot Creator * - * A comprehensive configuration editor for the AI agent: + * A full-featured bot creation and management platform: * - Agent provider configuration (Ollama, Gemini, Mock) * - Model selection and parameters + * - System prompt editing with live syntax highlighting + * - Bot profile management (create, save, load custom bots) + * - Chat history viewer and management + * - Session metrics and analytics dashboard * - Collaboration settings (Local/Network) * - Z3ED command automation presets * - Multimodal/vision configuration - * - Session metrics and monitoring - * - System prompt customization + * - Export/Import bot configurations * - * The chat widget is separate and managed by EditorManager. + * The chat widget is separate and managed by EditorManager, with + * a dense/compact mode for focused conversations. */ class AgentEditor : public Editor { public: @@ -67,7 +74,25 @@ class AgentEditor : public Editor { // Main rendering (called by Update()) void DrawDashboard(); - // Get current agent configuration (for chat to use) + // Bot Configuration & Profile Management + struct BotProfile { + std::string name = "Default Bot"; + std::string description; + std::string provider = "mock"; + std::string model; + std::string ollama_host = "http://localhost:11434"; + std::string gemini_api_key; + std::string system_prompt; + bool verbose = false; + bool show_reasoning = true; + int max_tool_iterations = 4; + int max_retry_attempts = 3; + std::vector tags; + absl::Time created_at = absl::Now(); + absl::Time modified_at = absl::Now(); + }; + + // Legacy support struct AgentConfig { std::string provider = "mock"; std::string model; @@ -81,6 +106,16 @@ class AgentEditor : public Editor { AgentConfig GetCurrentConfig() const; void ApplyConfig(const AgentConfig& config); + // Bot Profile Management + absl::Status SaveBotProfile(const BotProfile& profile); + absl::Status LoadBotProfile(const std::string& name); + absl::Status DeleteBotProfile(const std::string& name); + std::vector GetAllProfiles() const; + BotProfile GetCurrentProfile() const { return current_profile_; } + void SetCurrentProfile(const BotProfile& profile); + absl::Status ExportProfile(const BotProfile& profile, const std::filesystem::path& path); + absl::Status ImportProfile(const std::filesystem::path& path); + // Chat widget access (for EditorManager) AgentChatWidget* GetChatWidget() { return chat_widget_.get(); } bool IsChatActive() const; @@ -143,11 +178,21 @@ class AgentEditor : public Editor { void DrawConfigurationPanel(); void DrawStatusPanel(); void DrawMetricsPanel(); + void DrawPromptEditorPanel(); + void DrawBotProfilesPanel(); + void DrawChatHistoryViewer(); + void DrawAdvancedMetricsPanel(); // Setup callbacks void SetupChatWidgetCallbacks(); void SetupMultimodalCallbacks(); + // Bot profile helpers + std::filesystem::path GetProfilesDirectory() const; + absl::Status EnsureProfilesDirectory(); + std::string ProfileToJson(const BotProfile& profile) const; + absl::StatusOr JsonToProfile(const std::string& json) const; + // Internal state std::unique_ptr chat_widget_; // Owned by AgentEditor std::unique_ptr local_coordinator_; @@ -159,8 +204,19 @@ class AgentEditor : public Editor { ProposalDrawer* proposal_drawer_ = nullptr; Rom* rom_ = nullptr; - // Configuration state + // Configuration state (legacy) AgentConfig current_config_; + + // Bot Profile System + BotProfile current_profile_; + std::vector loaded_profiles_; + + // System Prompt Editor + std::unique_ptr prompt_editor_; + bool prompt_editor_initialized_ = false; + std::string active_prompt_file_ = "system_prompt_v3.txt"; + + // Collaboration state CollaborationMode current_mode_ = CollaborationMode::kLocal; bool in_session_ = false; std::string current_session_id_; @@ -169,6 +225,15 @@ class AgentEditor : public Editor { // UI state bool show_advanced_settings_ = false; + bool show_prompt_editor_ = false; + bool show_bot_profiles_ = false; + bool show_chat_history_ = false; + bool show_metrics_dashboard_ = false; + int selected_tab_ = 0; // 0=Config, 1=Prompts, 2=Bots, 3=History, 4=Metrics + + // Chat history viewer state + std::vector cached_history_; + bool history_needs_refresh_ = true; }; } // namespace editor diff --git a/src/cli/service/agent/conversational_agent_service.cc b/src/cli/service/agent/conversational_agent_service.cc index 161bcfe7..e9bd2637 100644 --- a/src/cli/service/agent/conversational_agent_service.cc +++ b/src/cli/service/agent/conversational_agent_service.cc @@ -337,8 +337,9 @@ absl::StatusOr ConversationalAgentService::SendMessage( "The tool returned the following data:\n", tool_output, "\n\n", "Please provide a text_response field in your JSON to summarize this information for the user."); - history_.push_back( - CreateMessage(ChatMessage::Sender::kUser, marked_output)); + auto tool_result_msg = CreateMessage(ChatMessage::Sender::kUser, marked_output); + tool_result_msg.is_internal = true; // Don't show this to the human user + history_.push_back(tool_result_msg); } executed_tool = true; } diff --git a/src/cli/service/agent/conversational_agent_service.h b/src/cli/service/agent/conversational_agent_service.h index cb4ec2de..6f2a8d8b 100644 --- a/src/cli/service/agent/conversational_agent_service.h +++ b/src/cli/service/agent/conversational_agent_service.h @@ -37,6 +37,7 @@ struct ChatMessage { absl::Time timestamp; std::optional json_pretty; std::optional table_data; + bool is_internal = false; // True for tool results and other messages not meant for user display struct SessionMetrics { int turn_index = 0; int total_user_messages = 0;