From b54f4b99dd01ef4c4a36fc0a294f5cb043757f58 Mon Sep 17 00:00:00 2001 From: scawful Date: Sat, 4 Oct 2025 17:47:23 -0400 Subject: [PATCH] feat: Refactor agent-related components for improved collaboration and chat management - Moved agent chat history codec and chat widget to a dedicated agent directory for better organization. - Introduced AgentEditor class to manage chat, collaboration, and network coordination. - Updated EditorManager to utilize the new AgentEditor for handling chat and collaboration features. - Enhanced collaboration capabilities with local and network session management. - Integrated new agent collaboration coordinator for managing collaborative sessions. - Improved CMake configuration to include new agent source files and dependencies. --- .../agent_chat_history_codec.cc | 2 +- .../agent_chat_history_codec.h | 6 +- .../{system => agent}/agent_chat_widget.cc | 507 +++++++++--------- .../{system => agent}/agent_chat_widget.h | 6 +- .../agent_collaboration_coordinator.cc | 2 +- .../agent_collaboration_coordinator.h | 6 +- src/app/editor/agent/agent_editor.cc | 392 ++++++++++++++ src/app/editor/agent/agent_editor.h | 141 +++++ .../network_collaboration_coordinator.cc | 2 +- .../network_collaboration_coordinator.h | 6 +- src/app/editor/editor_library.cmake | 9 +- src/app/editor/editor_manager.cc | 70 +-- src/app/editor/editor_manager.h | 8 +- 13 files changed, 831 insertions(+), 326 deletions(-) rename src/app/editor/{system => agent}/agent_chat_history_codec.cc (99%) rename src/app/editor/{system => agent}/agent_chat_history_codec.h (88%) rename src/app/editor/{system => agent}/agent_chat_widget.cc (68%) rename src/app/editor/{system => agent}/agent_chat_widget.h (96%) rename src/app/editor/{system => agent}/agent_collaboration_coordinator.cc (99%) rename src/app/editor/{system => agent}/agent_collaboration_coordinator.h (90%) create mode 100644 src/app/editor/agent/agent_editor.cc create mode 100644 src/app/editor/agent/agent_editor.h rename src/app/editor/{system => agent}/network_collaboration_coordinator.cc (99%) rename src/app/editor/{system => agent}/network_collaboration_coordinator.h (93%) diff --git a/src/app/editor/system/agent_chat_history_codec.cc b/src/app/editor/agent/agent_chat_history_codec.cc similarity index 99% rename from src/app/editor/system/agent_chat_history_codec.cc rename to src/app/editor/agent/agent_chat_history_codec.cc index 05dc306f..70167676 100644 --- a/src/app/editor/system/agent_chat_history_codec.cc +++ b/src/app/editor/agent/agent_chat_history_codec.cc @@ -1,4 +1,4 @@ -#include "app/editor/system/agent_chat_history_codec.h" +#include "app/editor/agent/agent_chat_history_codec.h" #include #include diff --git a/src/app/editor/system/agent_chat_history_codec.h b/src/app/editor/agent/agent_chat_history_codec.h similarity index 88% rename from src/app/editor/system/agent_chat_history_codec.h rename to src/app/editor/agent/agent_chat_history_codec.h index 368e31c4..1d9131b1 100644 --- a/src/app/editor/system/agent_chat_history_codec.h +++ b/src/app/editor/agent/agent_chat_history_codec.h @@ -1,5 +1,5 @@ -#ifndef YAZE_APP_EDITOR_SYSTEM_AGENT_CHAT_HISTORY_CODEC_H_ -#define YAZE_APP_EDITOR_SYSTEM_AGENT_CHAT_HISTORY_CODEC_H_ +#ifndef YAZE_APP_EDITOR_AGENT_AGENT_CHAT_HISTORY_CODEC_H_ +#define YAZE_APP_EDITOR_AGENT_AGENT_CHAT_HISTORY_CODEC_H_ #include #include @@ -51,4 +51,4 @@ class AgentChatHistoryCodec { } // namespace editor } // namespace yaze -#endif // YAZE_APP_EDITOR_SYSTEM_AGENT_CHAT_HISTORY_CODEC_H_ +#endif // YAZE_APP_EDITOR_AGENT_AGENT_CHAT_HISTORY_CODEC_H_ diff --git a/src/app/editor/system/agent_chat_widget.cc b/src/app/editor/agent/agent_chat_widget.cc similarity index 68% rename from src/app/editor/system/agent_chat_widget.cc rename to src/app/editor/agent/agent_chat_widget.cc index 3f7a2e91..312179ec 100644 --- a/src/app/editor/system/agent_chat_widget.cc +++ b/src/app/editor/agent/agent_chat_widget.cc @@ -1,9 +1,9 @@ -#include "app/editor/system/agent_chat_widget.h" +#include "app/editor/agent/agent_chat_widget.h" #include +#include #include #include -#include #include #include #include @@ -14,7 +14,7 @@ #include "absl/time/clock.h" #include "absl/time/time.h" #include "app/core/platform/file_dialog.h" -#include "app/editor/system/agent_chat_history_codec.h" +#include "app/editor/agent/agent_chat_history_codec.h" #include "app/editor/system/proposal_drawer.h" #include "app/editor/system/toast_manager.h" #include "app/gui/icons.h" @@ -51,13 +51,13 @@ std::filesystem::path ResolveHistoryPath(const std::string& session_id = "") { base = ExpandUserPath(".yaze"); } auto directory = base / "agent"; - + // If in a collaborative session, use shared history if (!session_id.empty()) { directory = directory / "sessions"; return directory / (session_id + "_history.json"); } - + return directory / "chat_history.json"; } @@ -131,9 +131,8 @@ void AgentChatWidget::EnsureHistoryLoaded() { std::filesystem::create_directories(directory, ec); if (ec) { if (toast_manager_) { - toast_manager_->Show( - "Unable to prepare chat history directory", - ToastType::kError, 5.0f); + toast_manager_->Show("Unable to prepare chat history directory", + ToastType::kError, 5.0f); } return; } @@ -163,10 +162,9 @@ void AgentChatWidget::EnsureHistoryLoaded() { } if (toast_manager_) { - toast_manager_->Show( - absl::StrFormat("Failed to load chat history: %s", - result.status().ToString()), - ToastType::kError, 6.0f); + toast_manager_->Show(absl::StrFormat("Failed to load chat history: %s", + result.status().ToString()), + ToastType::kError, 6.0f); } return; } @@ -180,8 +178,7 @@ void AgentChatWidget::EnsureHistoryLoaded() { history_dirty_ = false; last_persist_time_ = absl::Now(); if (toast_manager_) { - toast_manager_->Show("Restored chat history", - ToastType::kInfo, 3.5f); + toast_manager_->Show("Restored chat history", ToastType::kInfo, 3.5f); } } @@ -195,8 +192,7 @@ void AgentChatWidget::EnsureHistoryLoaded() { collaboration_state_.session_name = collaboration_state_.session_id; } - multimodal_state_.last_capture_path = - snapshot.multimodal.last_capture_path; + multimodal_state_.last_capture_path = snapshot.multimodal.last_capture_path; multimodal_state_.status_message = snapshot.multimodal.status_message; multimodal_state_.last_updated = snapshot.multimodal.last_updated; } @@ -224,8 +220,7 @@ void AgentChatWidget::PersistHistory() { snapshot.collaboration.session_name = collaboration_state_.session_name; snapshot.collaboration.participants = collaboration_state_.participants; snapshot.collaboration.last_synced = collaboration_state_.last_synced; - snapshot.multimodal.last_capture_path = - multimodal_state_.last_capture_path; + snapshot.multimodal.last_capture_path = multimodal_state_.last_capture_path; snapshot.multimodal.status_message = multimodal_state_.status_message; snapshot.multimodal.last_updated = multimodal_state_.last_updated; @@ -244,10 +239,9 @@ void AgentChatWidget::PersistHistory() { } if (toast_manager_) { - toast_manager_->Show( - absl::StrFormat("Failed to persist chat history: %s", - status.ToString()), - ToastType::kError, 6.0f); + toast_manager_->Show(absl::StrFormat("Failed to persist chat history: %s", + status.ToString()), + ToastType::kError, 6.0f); } return; } @@ -287,13 +281,13 @@ void AgentChatWidget::NotifyProposalCreated(const ChatMessage& msg, const auto& proposal = *msg.proposal; toast_manager_->Show( absl::StrFormat("%s Proposal %s ready (%d change%s)", ICON_MD_PREVIEW, - proposal.id, proposal.change_count, - proposal.change_count == 1 ? "" : "s"), + proposal.id, proposal.change_count, + proposal.change_count == 1 ? "" : "s"), ToastType::kSuccess, 5.5f); } else { toast_manager_->Show( - absl::StrFormat("%s %d new proposal%s queued", - ICON_MD_PREVIEW, delta, delta == 1 ? "" : "s"), + absl::StrFormat("%s %d new proposal%s queued", ICON_MD_PREVIEW, delta, + delta == 1 ? "" : "s"), ToastType::kSuccess, 4.5f); } } @@ -336,9 +330,9 @@ void AgentChatWidget::RenderMessage(const ChatMessage& msg, int index) { ImGui::TextColored(header_color, "%s", header_label); ImGui::SameLine(); - ImGui::TextDisabled("%s", - absl::FormatTime("%H:%M:%S", msg.timestamp, - absl::LocalTimeZone()).c_str()); + ImGui::TextDisabled( + "%s", absl::FormatTime("%H:%M:%S", msg.timestamp, absl::LocalTimeZone()) + .c_str()); ImGui::Indent(); @@ -346,8 +340,8 @@ void AgentChatWidget::RenderMessage(const ChatMessage& msg, int index) { if (ImGui::SmallButton("Copy JSON")) { ImGui::SetClipboardText(msg.json_pretty->c_str()); if (toast_manager_) { - toast_manager_->Show("Copied JSON to clipboard", - ToastType::kInfo, 2.5f); + toast_manager_->Show("Copied JSON to clipboard", ToastType::kInfo, + 2.5f); } } ImGui::SameLine(); @@ -387,8 +381,8 @@ void AgentChatWidget::RenderProposalQuickActions(const ChatMessage& msg, ImVec2(0, ImGui::GetFrameHeight() * 3.2f), true, ImGuiWindowFlags_None); - ImGui::TextColored(ImVec4(0.8f, 1.0f, 0.8f, 1.0f), - "%s Proposal %s", ICON_MD_PREVIEW, proposal.id.c_str()); + ImGui::TextColored(ImVec4(0.8f, 1.0f, 0.8f, 1.0f), "%s Proposal %s", + ICON_MD_PREVIEW, proposal.id.c_str()); ImGui::Text("Changes: %d", proposal.change_count); ImGui::Text("Commands: %d", proposal.executed_commands); @@ -401,15 +395,16 @@ void AgentChatWidget::RenderProposalQuickActions(const ChatMessage& msg, proposal.proposal_json_path.string().c_str()); } - if (ImGui::SmallButton(absl::StrFormat("%s Review", ICON_MD_VISIBILITY).c_str())) { + if (ImGui::SmallButton( + absl::StrFormat("%s Review", ICON_MD_VISIBILITY).c_str())) { FocusProposalDrawer(proposal.id); } ImGui::SameLine(); - if (ImGui::SmallButton(absl::StrFormat("%s Copy ID", ICON_MD_CONTENT_COPY).c_str())) { + if (ImGui::SmallButton( + absl::StrFormat("%s Copy ID", ICON_MD_CONTENT_COPY).c_str())) { ImGui::SetClipboardText(proposal.id.c_str()); if (toast_manager_) { - toast_manager_->Show("Proposal ID copied", - ToastType::kInfo, 2.5f); + toast_manager_->Show("Proposal ID copied", ToastType::kInfo, 2.5f); } } @@ -423,9 +418,7 @@ void AgentChatWidget::RenderHistory() { float reserved_height = ImGui::GetFrameHeightWithSpacing() * 4.0f; reserved_height += 220.0f; - if (ImGui::BeginChild("History", - ImVec2(0, -reserved_height), - false, + if (ImGui::BeginChild("History", ImVec2(0, -reserved_height), false, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_HorizontalScrollbar)) { if (history.empty()) { @@ -449,10 +442,8 @@ void AgentChatWidget::RenderInputBox() { ImGui::Text("Message:"); bool submitted = ImGui::InputTextMultiline( - "##agent_input", input_buffer_, sizeof(input_buffer_), - ImVec2(-1, 80.0f), - ImGuiInputTextFlags_AllowTabInput | - ImGuiInputTextFlags_EnterReturnsTrue); + "##agent_input", input_buffer_, sizeof(input_buffer_), ImVec2(-1, 80.0f), + ImGuiInputTextFlags_AllowTabInput | ImGuiInputTextFlags_EnterReturnsTrue); bool send = submitted; if (submitted && ImGui::GetIO().KeyShift) { @@ -467,7 +458,8 @@ void AgentChatWidget::RenderInputBox() { ImGui::Spacing(); if (ImGui::Button(absl::StrFormat("%s Send", ICON_MD_SEND).c_str(), - ImVec2(120, 0)) || send) { + ImVec2(120, 0)) || + send) { if (std::strlen(input_buffer_) > 0) { history_dirty_ = true; EnsureHistoryLoaded(); @@ -489,15 +481,30 @@ void AgentChatWidget::Draw() { } EnsureHistoryLoaded(); - + // Poll for new messages in collaborative sessions PollSharedHistory(); ImGui::Begin(title_.c_str(), &active_); - RenderHistory(); - RenderCollaborationPanel(); - RenderMultimodalPanel(); - RenderInputBox(); + if (ImGui::BeginTable("#agent_chat_table", 2, + ImGuiTableFlags_BordersInnerV | + ImGuiTableFlags_Resizable | + ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("Session Details", ImGuiTableColumnFlags_WidthFixed, + 450.0f); + ImGui::TableSetupColumn("Chat History", ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + + ImGui::TableSetColumnIndex(0); + RenderMultimodalPanel(); + RenderCollaborationPanel(); + + ImGui::TableSetColumnIndex(1); + RenderHistory(); + RenderInputBox(); + ImGui::EndTable(); + } ImGui::End(); } @@ -522,36 +529,40 @@ void AgentChatWidget::RenderCollaborationPanel() { ImGui::Separator(); // Table layout: Left side = Session Details, Right side = Controls - if (ImGui::BeginTable("collab_table", 2, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingFixedFit)) { - ImGui::TableSetupColumn("Session Details", ImGuiTableColumnFlags_WidthFixed, 250.0f); + if (ImGui::BeginTable( + "collab_table", 2, + ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableSetupColumn("Session Details", ImGuiTableColumnFlags_WidthFixed, + 250.0f); ImGui::TableSetupColumn("Controls", ImGuiTableColumnFlags_WidthStretch); - + ImGui::TableNextRow(); - + // LEFT COLUMN: Session Details ImGui::TableSetColumnIndex(0); ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.15f, 0.15f, 0.18f, 1.0f)); ImGui::BeginChild("session_details", ImVec2(0, 180), true); - + const bool connected = collaboration_state_.active; - ImGui::TextColored(connected ? ImVec4(0.4f, 1.0f, 0.4f, 1.0f) : ImVec4(0.7f, 0.7f, 0.7f, 1.0f), - "%s %s", connected ? "●" : "○", - connected ? "Connected" : "Not connected"); - + ImGui::TextColored(connected ? ImVec4(0.4f, 1.0f, 0.4f, 1.0f) + : ImVec4(0.7f, 0.7f, 0.7f, 1.0f), + "%s %s", connected ? "●" : "○", + connected ? "Connected" : "Not connected"); + ImGui::Separator(); - + if (collaboration_state_.mode == CollaborationMode::kNetwork) { ImGui::Text("Server:"); ImGui::TextWrapped("%s", collaboration_state_.server_url.c_str()); ImGui::Spacing(); } - + if (!collaboration_state_.session_name.empty()) { ImGui::Text("Session:"); ImGui::TextWrapped("%s", collaboration_state_.session_name.c_str()); ImGui::Spacing(); } - + if (!collaboration_state_.session_id.empty()) { ImGui::Text("Code:"); ImGui::TextWrapped("%s", collaboration_state_.session_id.c_str()); @@ -563,182 +574,193 @@ void AgentChatWidget::RenderCollaborationPanel() { } ImGui::Spacing(); } - + if (collaboration_state_.last_synced != absl::InfinitePast()) { ImGui::TextDisabled("Last sync:"); - ImGui::TextDisabled("%s", - absl::FormatTime("%H:%M:%S", collaboration_state_.last_synced, - absl::LocalTimeZone()).c_str()); + ImGui::TextDisabled( + "%s", absl::FormatTime("%H:%M:%S", collaboration_state_.last_synced, + absl::LocalTimeZone()) + .c_str()); } - + ImGui::EndChild(); ImGui::PopStyleColor(); - + // Show participants list below session details ImGui::BeginChild("participants", ImVec2(0, 0), true); if (collaboration_state_.participants.empty()) { ImGui::TextDisabled("No participants"); } else { - ImGui::Text("Participants (%zu):", collaboration_state_.participants.size()); + ImGui::Text("Participants (%zu):", + collaboration_state_.participants.size()); ImGui::Separator(); for (const auto& participant : collaboration_state_.participants) { ImGui::BulletText("%s", participant.c_str()); } } ImGui::EndChild(); - + // RIGHT COLUMN: Controls ImGui::TableSetColumnIndex(1); ImGui::BeginChild("controls", ImVec2(0, 0), false); - ImGui::Separator(); - - const bool can_host = static_cast(collaboration_callbacks_.host_session); - const bool can_join = static_cast(collaboration_callbacks_.join_session); - const bool can_leave = static_cast(collaboration_callbacks_.leave_session); - const bool can_refresh = static_cast(collaboration_callbacks_.refresh_session); - - // Network mode: Show server URL input - if (collaboration_state_.mode == CollaborationMode::kNetwork) { - ImGui::Text("Server URL:"); - ImGui::InputText("##server_url", server_url_buffer_, - IM_ARRAYSIZE(server_url_buffer_)); - if (ImGui::Button("Connect to Server")) { - collaboration_state_.server_url = server_url_buffer_; - // TODO: Trigger network coordinator connection - if (toast_manager_) { - toast_manager_->Show("Network mode: connecting...", - ToastType::kInfo, 3.0f); - } - } ImGui::Separator(); - } - ImGui::Text("Host New Session:"); - ImGui::InputTextWithHint("##session_name", "Session name", - session_name_buffer_, - IM_ARRAYSIZE(session_name_buffer_)); - ImGui::SameLine(); - if (!can_host) ImGui::BeginDisabled(); - if (ImGui::Button("Host")) { - std::string name = session_name_buffer_; - if (name.empty()) { - if (toast_manager_) { - toast_manager_->Show("Enter a session name first", - ToastType::kWarning, 3.0f); - } - } else { - auto session_or = collaboration_callbacks_.host_session(name); - if (session_or.ok()) { - ApplyCollaborationSession(session_or.value(), /*update_action_timestamp=*/true); - std::snprintf(join_code_buffer_, sizeof(join_code_buffer_), "%s", - collaboration_state_.session_id.c_str()); - session_name_buffer_[0] = '\0'; + const bool can_host = + static_cast(collaboration_callbacks_.host_session); + const bool can_join = + static_cast(collaboration_callbacks_.join_session); + const bool can_leave = + static_cast(collaboration_callbacks_.leave_session); + const bool can_refresh = + static_cast(collaboration_callbacks_.refresh_session); + + // Network mode: Show server URL input + if (collaboration_state_.mode == CollaborationMode::kNetwork) { + ImGui::Text("Server URL:"); + ImGui::InputText("##server_url", server_url_buffer_, + IM_ARRAYSIZE(server_url_buffer_)); + if (ImGui::Button("Connect to Server")) { + collaboration_state_.server_url = server_url_buffer_; + // TODO: Trigger network coordinator connection if (toast_manager_) { - toast_manager_->Show( - absl::StrFormat("Hosting session %s", - collaboration_state_.session_id.c_str()), - ToastType::kSuccess, 3.5f); + toast_manager_->Show("Network mode: connecting...", ToastType::kInfo, + 3.0f); } - MarkHistoryDirty(); - } else if (toast_manager_) { - toast_manager_->Show( - absl::StrFormat("Failed to host: %s", - session_or.status().message()), - ToastType::kError, 5.0f); } + ImGui::Separator(); } - } - if (!can_host) { - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Provide host_session callback to enable hosting"); - } - ImGui::EndDisabled(); - } - ImGui::Spacing(); - ImGui::Text("Join Existing Session:"); - ImGui::InputTextWithHint("##join_code", "Session code", - join_code_buffer_, - IM_ARRAYSIZE(join_code_buffer_)); - ImGui::SameLine(); - if (!can_join) ImGui::BeginDisabled(); - if (ImGui::Button("Join")) { - std::string code = join_code_buffer_; - if (code.empty()) { - if (toast_manager_) { - toast_manager_->Show("Enter a session code first", - ToastType::kWarning, 3.0f); - } - } else { - auto session_or = collaboration_callbacks_.join_session(code); - if (session_or.ok()) { - ApplyCollaborationSession(session_or.value(), /*update_action_timestamp=*/true); - std::snprintf(join_code_buffer_, sizeof(join_code_buffer_), "%s", - collaboration_state_.session_id.c_str()); + ImGui::Text("Host New Session:"); + ImGui::InputTextWithHint("##session_name", "Session name", + session_name_buffer_, + IM_ARRAYSIZE(session_name_buffer_)); + ImGui::SameLine(); + if (!can_host) + ImGui::BeginDisabled(); + if (ImGui::Button("Host")) { + std::string name = session_name_buffer_; + if (name.empty()) { if (toast_manager_) { - toast_manager_->Show( - absl::StrFormat("Joined session %s", - collaboration_state_.session_id.c_str()), - ToastType::kSuccess, 3.5f); + toast_manager_->Show("Enter a session name first", + ToastType::kWarning, 3.0f); + } + } else { + auto session_or = collaboration_callbacks_.host_session(name); + if (session_or.ok()) { + ApplyCollaborationSession(session_or.value(), + /*update_action_timestamp=*/true); + std::snprintf(join_code_buffer_, sizeof(join_code_buffer_), "%s", + collaboration_state_.session_id.c_str()); + session_name_buffer_[0] = '\0'; + if (toast_manager_) { + toast_manager_->Show( + absl::StrFormat("Hosting session %s", + collaboration_state_.session_id.c_str()), + ToastType::kSuccess, 3.5f); + } + MarkHistoryDirty(); + } else if (toast_manager_) { + toast_manager_->Show(absl::StrFormat("Failed to host: %s", + session_or.status().message()), + ToastType::kError, 5.0f); } - MarkHistoryDirty(); - } else if (toast_manager_) { - toast_manager_->Show( - absl::StrFormat("Failed to join: %s", - session_or.status().message()), - ToastType::kError, 5.0f); } } - } - if (!can_join) { - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Provide join_session callback to enable joining"); - } - ImGui::EndDisabled(); - } - - if (connected) { - if (!can_leave) ImGui::BeginDisabled(); - if (ImGui::Button("Leave Session")) { - absl::Status status = collaboration_callbacks_.leave_session - ? collaboration_callbacks_.leave_session() - : absl::OkStatus(); - if (status.ok()) { - collaboration_state_ = CollaborationState{}; - join_code_buffer_[0] = '\0'; - if (toast_manager_) { - toast_manager_->Show("Left collaborative session", - ToastType::kInfo, 3.0f); - } - MarkHistoryDirty(); - } else if (toast_manager_) { - toast_manager_->Show( - absl::StrFormat("Failed to leave: %s", status.message()), - ToastType::kError, 5.0f); + if (!can_host) { + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Provide host_session callback to enable hosting"); } + ImGui::EndDisabled(); } - if (!can_leave) ImGui::EndDisabled(); - } - if (connected) { ImGui::Spacing(); - ImGui::Separator(); - if (!can_refresh) ImGui::BeginDisabled(); - if (ImGui::Button("Refresh Session")) { - RefreshCollaboration(); + ImGui::Text("Join Existing Session:"); + ImGui::InputTextWithHint("##join_code", "Session code", join_code_buffer_, + IM_ARRAYSIZE(join_code_buffer_)); + ImGui::SameLine(); + if (!can_join) + ImGui::BeginDisabled(); + if (ImGui::Button("Join")) { + std::string code = join_code_buffer_; + if (code.empty()) { + if (toast_manager_) { + toast_manager_->Show("Enter a session code first", + ToastType::kWarning, 3.0f); + } + } else { + auto session_or = collaboration_callbacks_.join_session(code); + if (session_or.ok()) { + ApplyCollaborationSession(session_or.value(), + /*update_action_timestamp=*/true); + std::snprintf(join_code_buffer_, sizeof(join_code_buffer_), "%s", + collaboration_state_.session_id.c_str()); + if (toast_manager_) { + toast_manager_->Show( + absl::StrFormat("Joined session %s", + collaboration_state_.session_id.c_str()), + ToastType::kSuccess, 3.5f); + } + MarkHistoryDirty(); + } else if (toast_manager_) { + toast_manager_->Show(absl::StrFormat("Failed to join: %s", + session_or.status().message()), + ToastType::kError, 5.0f); + } + } } - if (!can_refresh && ImGui::IsItemHovered()) { - ImGui::SetTooltip("Provide refresh_session callback to enable"); + if (!can_join) { + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Provide join_session callback to enable joining"); + } + ImGui::EndDisabled(); } - if (!can_refresh) ImGui::EndDisabled(); - } else { - ImGui::Spacing(); - ImGui::TextDisabled("Start or join a session to collaborate."); - } - - ImGui::EndChild(); // controls - ImGui::EndTable(); + + if (connected) { + if (!can_leave) + ImGui::BeginDisabled(); + if (ImGui::Button("Leave Session")) { + absl::Status status = collaboration_callbacks_.leave_session + ? collaboration_callbacks_.leave_session() + : absl::OkStatus(); + if (status.ok()) { + collaboration_state_ = CollaborationState{}; + join_code_buffer_[0] = '\0'; + if (toast_manager_) { + toast_manager_->Show("Left collaborative session", ToastType::kInfo, + 3.0f); + } + MarkHistoryDirty(); + } else if (toast_manager_) { + toast_manager_->Show( + absl::StrFormat("Failed to leave: %s", status.message()), + ToastType::kError, 5.0f); + } + } + if (!can_leave) + ImGui::EndDisabled(); + } + + if (connected) { + ImGui::Spacing(); + ImGui::Separator(); + if (!can_refresh) + ImGui::BeginDisabled(); + if (ImGui::Button("Refresh Session")) { + RefreshCollaboration(); + } + if (!can_refresh && ImGui::IsItemHovered()) { + ImGui::SetTooltip("Provide refresh_session callback to enable"); + } + if (!can_refresh) + ImGui::EndDisabled(); + } else { + ImGui::Spacing(); + ImGui::TextDisabled("Start or join a session to collaborate."); + } + + ImGui::EndChild(); // controls + ImGui::EndTable(); } } @@ -753,28 +775,30 @@ void AgentChatWidget::RenderMultimodalPanel() { // Capture mode selection ImGui::Text("Capture Mode:"); - ImGui::RadioButton("Full Window", - reinterpret_cast(&multimodal_state_.capture_mode), - static_cast(CaptureMode::kFullWindow)); + ImGui::RadioButton("Full Window", + reinterpret_cast(&multimodal_state_.capture_mode), + static_cast(CaptureMode::kFullWindow)); ImGui::SameLine(); - ImGui::RadioButton("Active Editor", - reinterpret_cast(&multimodal_state_.capture_mode), - static_cast(CaptureMode::kActiveEditor)); + ImGui::RadioButton("Active Editor", + reinterpret_cast(&multimodal_state_.capture_mode), + static_cast(CaptureMode::kActiveEditor)); ImGui::SameLine(); - ImGui::RadioButton("Specific Window", - reinterpret_cast(&multimodal_state_.capture_mode), - static_cast(CaptureMode::kSpecificWindow)); + ImGui::RadioButton("Specific Window", + reinterpret_cast(&multimodal_state_.capture_mode), + static_cast(CaptureMode::kSpecificWindow)); // If specific window mode, show input for window name if (multimodal_state_.capture_mode == CaptureMode::kSpecificWindow) { ImGui::InputText("Window Name", multimodal_state_.specific_window_buffer, IM_ARRAYSIZE(multimodal_state_.specific_window_buffer)); - ImGui::TextDisabled("Examples: Overworld Editor, Dungeon Editor, Sprite Editor"); + ImGui::TextDisabled( + "Examples: Overworld Editor, Dungeon Editor, Sprite Editor"); } ImGui::Separator(); - if (!can_capture) ImGui::BeginDisabled(); + if (!can_capture) + ImGui::BeginDisabled(); if (ImGui::Button("Capture Snapshot")) { if (multimodal_callbacks_.capture_snapshot) { std::filesystem::path captured_path; @@ -786,8 +810,7 @@ void AgentChatWidget::RenderMultimodalPanel() { absl::StrFormat("Captured %s", captured_path.string()); multimodal_state_.last_updated = absl::Now(); if (toast_manager_) { - toast_manager_->Show("Snapshot captured", - ToastType::kSuccess, 3.0f); + toast_manager_->Show("Snapshot captured", ToastType::kSuccess, 3.0f); } MarkHistoryDirty(); } else { @@ -818,24 +841,24 @@ void AgentChatWidget::RenderMultimodalPanel() { ImGui::InputTextMultiline("##gemini_prompt", multimodal_prompt_buffer_, IM_ARRAYSIZE(multimodal_prompt_buffer_), ImVec2(-1, 60.0f)); - if (!can_send) ImGui::BeginDisabled(); + if (!can_send) + ImGui::BeginDisabled(); if (ImGui::Button("Send to Gemini")) { if (!multimodal_state_.last_capture_path.has_value()) { if (toast_manager_) { - toast_manager_->Show("Capture a snapshot first", - ToastType::kWarning, 3.0f); + toast_manager_->Show("Capture a snapshot first", ToastType::kWarning, + 3.0f); } } else { std::string prompt = multimodal_prompt_buffer_; absl::Status status = multimodal_callbacks_.send_to_gemini( *multimodal_state_.last_capture_path, prompt); if (status.ok()) { - multimodal_state_.status_message = - "Submitted image to Gemini"; + multimodal_state_.status_message = "Submitted image to Gemini"; multimodal_state_.last_updated = absl::Now(); if (toast_manager_) { - toast_manager_->Show("Gemini request sent", - ToastType::kSuccess, 3.0f); + toast_manager_->Show("Gemini request sent", ToastType::kSuccess, + 3.0f); } MarkHistoryDirty(); } else { @@ -862,7 +885,8 @@ void AgentChatWidget::RenderMultimodalPanel() { ImGui::TextDisabled( "Updated: %s", absl::FormatTime("%H:%M:%S", multimodal_state_.last_updated, - absl::LocalTimeZone()).c_str()); + absl::LocalTimeZone()) + .c_str()); } } } @@ -879,15 +903,15 @@ void AgentChatWidget::RefreshCollaboration() { MarkHistoryDirty(); } if (toast_manager_) { - toast_manager_->Show( - absl::StrFormat("Failed to refresh participants: %s", - session_or.status().message()), - ToastType::kError, 5.0f); + toast_manager_->Show(absl::StrFormat("Failed to refresh participants: %s", + session_or.status().message()), + ToastType::kError, 5.0f); } return; } - ApplyCollaborationSession(session_or.value(), /*update_action_timestamp=*/false); + ApplyCollaborationSession(session_or.value(), + /*update_action_timestamp=*/false); MarkHistoryDirty(); } @@ -896,9 +920,8 @@ void AgentChatWidget::ApplyCollaborationSession( bool update_action_timestamp) { collaboration_state_.active = true; collaboration_state_.session_id = context.session_id; - collaboration_state_.session_name = context.session_name.empty() - ? context.session_id - : context.session_name; + collaboration_state_.session_name = + context.session_name.empty() ? context.session_id : context.session_name; collaboration_state_.participants = context.participants; collaboration_state_.last_synced = absl::Now(); if (update_action_timestamp) { @@ -924,14 +947,14 @@ void AgentChatWidget::SwitchToSharedHistory(const std::string& session_id) { // Switch to shared history path history_path_ = ResolveHistoryPath(session_id); history_loaded_ = false; - + // Load shared history EnsureHistoryLoaded(); - + // Initialize polling state last_known_history_size_ = agent_service_.GetHistory().size(); last_shared_history_poll_ = absl::Now(); - + if (toast_manager_) { toast_manager_->Show( absl::StrFormat("Switched to shared chat history for session %s", @@ -949,13 +972,13 @@ void AgentChatWidget::SwitchToLocalHistory() { // Switch back to local history history_path_ = ResolveHistoryPath(""); history_loaded_ = false; - + // Load local history EnsureHistoryLoaded(); - + if (toast_manager_) { - toast_manager_->Show("Switched to local chat history", - ToastType::kInfo, 3.0f); + toast_manager_->Show("Switched to local chat history", ToastType::kInfo, + 3.0f); } } @@ -965,12 +988,12 @@ void AgentChatWidget::PollSharedHistory() { } const absl::Time now = absl::Now(); - + // Poll every 2 seconds if (now - last_shared_history_poll_ < absl::Seconds(2)) { return; } - + last_shared_history_poll_ = now; // Check if the shared history file has been updated @@ -980,15 +1003,15 @@ void AgentChatWidget::PollSharedHistory() { } const size_t new_size = result->history.size(); - + // If history has grown, reload it if (new_size > last_known_history_size_) { const size_t new_messages = new_size - last_known_history_size_; - + agent_service_.ReplaceHistory(std::move(result->history)); last_history_size_ = new_size; last_known_history_size_ = new_size; - + if (toast_manager_) { toast_manager_->Show( absl::StrFormat("📬 %zu new message%s from collaborators", diff --git a/src/app/editor/system/agent_chat_widget.h b/src/app/editor/agent/agent_chat_widget.h similarity index 96% rename from src/app/editor/system/agent_chat_widget.h rename to src/app/editor/agent/agent_chat_widget.h index abfdfe26..7f4ce4b7 100644 --- a/src/app/editor/system/agent_chat_widget.h +++ b/src/app/editor/agent/agent_chat_widget.h @@ -1,5 +1,5 @@ -#ifndef YAZE_SRC_APP_EDITOR_SYSTEM_AGENT_CHAT_WIDGET_H_ -#define YAZE_SRC_APP_EDITOR_SYSTEM_AGENT_CHAT_WIDGET_H_ +#ifndef YAZE_APP_EDITOR_AGENT_AGENT_CHAT_WIDGET_H_ +#define YAZE_APP_EDITOR_AGENT_AGENT_CHAT_WIDGET_H_ #include #include @@ -158,4 +158,4 @@ public: } // namespace editor } // namespace yaze -#endif // YAZE_SRC_APP_EDITOR_SYSTEM_AGENT_CHAT_WIDGET_H_ +#endif // YAZE_APP_EDITOR_AGENT_AGENT_CHAT_WIDGET_H_ diff --git a/src/app/editor/system/agent_collaboration_coordinator.cc b/src/app/editor/agent/agent_collaboration_coordinator.cc similarity index 99% rename from src/app/editor/system/agent_collaboration_coordinator.cc rename to src/app/editor/agent/agent_collaboration_coordinator.cc index 6827c6b6..0b3c636d 100644 --- a/src/app/editor/system/agent_collaboration_coordinator.cc +++ b/src/app/editor/agent/agent_collaboration_coordinator.cc @@ -1,4 +1,4 @@ -#include "app/editor/system/agent_collaboration_coordinator.h" +#include "app/editor/agent/agent_collaboration_coordinator.h" #include #include diff --git a/src/app/editor/system/agent_collaboration_coordinator.h b/src/app/editor/agent/agent_collaboration_coordinator.h similarity index 90% rename from src/app/editor/system/agent_collaboration_coordinator.h rename to src/app/editor/agent/agent_collaboration_coordinator.h index cc443c04..5bd43681 100644 --- a/src/app/editor/system/agent_collaboration_coordinator.h +++ b/src/app/editor/agent/agent_collaboration_coordinator.h @@ -1,5 +1,5 @@ -#ifndef YAZE_APP_EDITOR_SYSTEM_AGENT_COLLABORATION_COORDINATOR_H_ -#define YAZE_APP_EDITOR_SYSTEM_AGENT_COLLABORATION_COORDINATOR_H_ +#ifndef YAZE_APP_EDITOR_AGENT_AGENT_COLLABORATION_COORDINATOR_H_ +#define YAZE_APP_EDITOR_AGENT_AGENT_COLLABORATION_COORDINATOR_H_ #include #include @@ -63,4 +63,4 @@ class AgentCollaborationCoordinator { } // namespace editor } // namespace yaze -#endif // YAZE_APP_EDITOR_SYSTEM_AGENT_COLLABORATION_COORDINATOR_H_ +#endif // YAZE_APP_EDITOR_AGENT_AGENT_COLLABORATION_COORDINATOR_H_ diff --git a/src/app/editor/agent/agent_editor.cc b/src/app/editor/agent/agent_editor.cc new file mode 100644 index 00000000..79242728 --- /dev/null +++ b/src/app/editor/agent/agent_editor.cc @@ -0,0 +1,392 @@ +#include "app/editor/agent/agent_editor.h" + +#include +#include + +#include "absl/strings/str_format.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" + +#ifdef YAZE_WITH_GRPC +#include "app/editor/agent/network_collaboration_coordinator.h" +#endif + +namespace yaze { +namespace editor { + +AgentEditor::AgentEditor() { + chat_widget_ = std::make_unique(); + local_coordinator_ = std::make_unique(); +} + +AgentEditor::~AgentEditor() = default; + +void AgentEditor::Initialize(ToastManager* toast_manager, + ProposalDrawer* proposal_drawer) { + toast_manager_ = toast_manager; + proposal_drawer_ = proposal_drawer; + + chat_widget_->SetToastManager(toast_manager_); + chat_widget_->SetProposalDrawer(proposal_drawer_); + + SetupChatWidgetCallbacks(); + SetupMultimodalCallbacks(); +} + +void AgentEditor::SetRomContext(Rom* rom) { + rom_ = rom; + if (chat_widget_) { + chat_widget_->SetRomContext(rom); + } +} + +void AgentEditor::Draw() { + if (chat_widget_) { + chat_widget_->Draw(); + } +} + +bool AgentEditor::IsChatActive() const { + return chat_widget_ && chat_widget_->is_active(); +} + +void AgentEditor::SetChatActive(bool active) { + if (chat_widget_) { + chat_widget_->set_active(active); + } +} + +void AgentEditor::ToggleChat() { + SetChatActive(!IsChatActive()); +} + +absl::StatusOr AgentEditor::HostSession( + const std::string& session_name, CollaborationMode mode) { + current_mode_ = mode; + + if (mode == CollaborationMode::kLocal) { + ASSIGN_OR_RETURN(auto session, local_coordinator_->HostSession(session_name)); + + SessionInfo info; + info.session_id = session.session_id; + info.session_name = session.session_name; + info.participants = session.participants; + + in_session_ = true; + current_session_id_ = info.session_id; + current_session_name_ = info.session_name; + current_participants_ = info.participants; + + // Switch chat to shared history + if (chat_widget_) { + chat_widget_->SwitchToSharedHistory(info.session_id); + } + + if (toast_manager_) { + toast_manager_->Show( + absl::StrFormat("Hosting local session: %s", session_name), + ToastType::kSuccess, 3.5f); + } + + return info; + } + +#ifdef YAZE_WITH_GRPC + if (mode == CollaborationMode::kNetwork) { + if (!network_coordinator_) { + return absl::FailedPreconditionError( + "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 = "unknown"; + } + + ASSIGN_OR_RETURN(auto session, + network_coordinator_->HostSession(session_name, username)); + + SessionInfo info; + info.session_id = session.session_id; + info.session_name = session.session_name; + info.participants = session.participants; + + in_session_ = true; + current_session_id_ = info.session_id; + current_session_name_ = info.session_name; + current_participants_ = info.participants; + + if (toast_manager_) { + toast_manager_->Show( + absl::StrFormat("Hosting network session: %s", session_name), + ToastType::kSuccess, 3.5f); + } + + return info; + } +#endif + + return absl::InvalidArgumentError("Unsupported collaboration mode"); +} + +absl::StatusOr AgentEditor::JoinSession( + const std::string& session_code, CollaborationMode mode) { + current_mode_ = mode; + + if (mode == CollaborationMode::kLocal) { + ASSIGN_OR_RETURN(auto session, local_coordinator_->JoinSession(session_code)); + + SessionInfo info; + info.session_id = session.session_id; + info.session_name = session.session_name; + info.participants = session.participants; + + in_session_ = true; + current_session_id_ = info.session_id; + current_session_name_ = info.session_name; + current_participants_ = info.participants; + + // Switch chat to shared history + if (chat_widget_) { + chat_widget_->SwitchToSharedHistory(info.session_id); + } + + if (toast_manager_) { + toast_manager_->Show( + absl::StrFormat("Joined local session: %s", session_code), + ToastType::kSuccess, 3.5f); + } + + return info; + } + +#ifdef YAZE_WITH_GRPC + if (mode == CollaborationMode::kNetwork) { + if (!network_coordinator_) { + return absl::FailedPreconditionError( + "Network coordinator not initialized. Connect to a server first."); + } + + const char* username = std::getenv("USER"); + if (!username) { + username = "unknown"; + } + + ASSIGN_OR_RETURN(auto session, + network_coordinator_->JoinSession(session_code, username)); + + SessionInfo info; + info.session_id = session.session_id; + info.session_name = session.session_name; + info.participants = session.participants; + + in_session_ = true; + current_session_id_ = info.session_id; + current_session_name_ = info.session_name; + current_participants_ = info.participants; + + if (toast_manager_) { + toast_manager_->Show( + absl::StrFormat("Joined network session: %s", session_code), + ToastType::kSuccess, 3.5f); + } + + return info; + } +#endif + + return absl::InvalidArgumentError("Unsupported collaboration mode"); +} + +absl::Status AgentEditor::LeaveSession() { + if (!in_session_) { + return absl::FailedPreconditionError("Not in a session"); + } + + if (current_mode_ == CollaborationMode::kLocal) { + RETURN_IF_ERROR(local_coordinator_->LeaveSession()); + } +#ifdef YAZE_WITH_GRPC + else if (current_mode_ == CollaborationMode::kNetwork) { + if (network_coordinator_) { + RETURN_IF_ERROR(network_coordinator_->LeaveSession()); + } + } +#endif + + // Switch chat back to local history + if (chat_widget_) { + chat_widget_->SwitchToLocalHistory(); + } + + in_session_ = false; + current_session_id_.clear(); + current_session_name_.clear(); + current_participants_.clear(); + + if (toast_manager_) { + toast_manager_->Show("Left collaboration session", ToastType::kInfo, 3.0f); + } + + return absl::OkStatus(); +} + +absl::StatusOr AgentEditor::RefreshSession() { + if (!in_session_) { + return absl::FailedPreconditionError("Not in a session"); + } + + if (current_mode_ == CollaborationMode::kLocal) { + ASSIGN_OR_RETURN(auto session, local_coordinator_->RefreshSession()); + + SessionInfo info; + info.session_id = session.session_id; + info.session_name = session.session_name; + info.participants = session.participants; + + current_participants_ = info.participants; + + return info; + } + + // Network mode doesn't need explicit refresh - it's real-time + SessionInfo info; + info.session_id = current_session_id_; + info.session_name = current_session_name_; + info.participants = current_participants_; + return info; +} + +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"); +} + +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"); +} + +#ifdef YAZE_WITH_GRPC +absl::Status AgentEditor::ConnectToServer(const std::string& server_url) { + try { + network_coordinator_ = + std::make_unique(server_url); + + if (toast_manager_) { + toast_manager_->Show( + absl::StrFormat("Connected to server: %s", server_url), + ToastType::kSuccess, 3.0f); + } + + return absl::OkStatus(); + } catch (const std::exception& e) { + return absl::InternalError( + absl::StrFormat("Failed to connect to server: %s", e.what())); + } +} + +void AgentEditor::DisconnectFromServer() { + if (in_session_ && current_mode_ == CollaborationMode::kNetwork) { + LeaveSession(); + } + network_coordinator_.reset(); + + if (toast_manager_) { + toast_manager_->Show("Disconnected from server", ToastType::kInfo, 2.5f); + } +} + +bool AgentEditor::IsConnectedToServer() const { + return network_coordinator_ && network_coordinator_->IsConnected(); +} +#endif + +bool AgentEditor::IsInSession() const { + return in_session_; +} + +AgentEditor::CollaborationMode AgentEditor::GetCurrentMode() const { + return current_mode_; +} + +std::optional AgentEditor::GetCurrentSession() const { + if (!in_session_) { + return std::nullopt; + } + + SessionInfo info; + info.session_id = current_session_id_; + info.session_name = current_session_name_; + info.participants = current_participants_; + return info; +} + +void AgentEditor::SetupChatWidgetCallbacks() { + if (!chat_widget_) { + return; + } + + AgentChatWidget::CollaborationCallbacks collab_callbacks; + + 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; + context.session_id = session.session_id; + context.session_name = session.session_name; + context.participants = session.participants; + return context; + }; + + collab_callbacks.join_session = + [this](const std::string& session_code) + -> absl::StatusOr { + ASSIGN_OR_RETURN(auto session, this->JoinSession(session_code, current_mode_)); + + AgentChatWidget::CollaborationCallbacks::SessionContext context; + context.session_id = session.session_id; + context.session_name = session.session_name; + context.participants = session.participants; + return context; + }; + + collab_callbacks.leave_session = [this]() { + return this->LeaveSession(); + }; + + collab_callbacks.refresh_session = + [this]() -> absl::StatusOr { + ASSIGN_OR_RETURN(auto session, this->RefreshSession()); + + AgentChatWidget::CollaborationCallbacks::SessionContext context; + context.session_id = session.session_id; + context.session_name = session.session_name; + context.participants = session.participants; + return context; + }; + + chat_widget_->SetCollaborationCallbacks(collab_callbacks); +} + +void AgentEditor::SetupMultimodalCallbacks() { + // Multimodal callbacks are set up by the EditorManager since it has + // access to the screenshot utilities. We just initialize the structure here. +} + +} // namespace editor +} // namespace yaze diff --git a/src/app/editor/agent/agent_editor.h b/src/app/editor/agent/agent_editor.h new file mode 100644 index 00000000..41bfcbb6 --- /dev/null +++ b/src/app/editor/agent/agent_editor.h @@ -0,0 +1,141 @@ +#ifndef YAZE_APP_EDITOR_AGENT_AGENT_EDITOR_H_ +#define YAZE_APP_EDITOR_AGENT_AGENT_EDITOR_H_ + +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/status/statusor.h" + +namespace yaze { + +class Rom; + +namespace editor { + +class ToastManager; +class ProposalDrawer; +class AgentChatWidget; +class AgentCollaborationCoordinator; + +#ifdef YAZE_WITH_GRPC +class NetworkCollaborationCoordinator; +#endif + +/** + * @class AgentEditor + * @brief Manages all agent-related functionality including chat, collaboration, + * and network coordination for the Z3ED editor. + * + * This class serves as a high-level manager for: + * - Agent chat widget and conversations + * - Local filesystem-based collaboration + * - Network-based (WebSocket) collaboration + * - Coordination between multiple collaboration modes + */ +class AgentEditor { + public: + AgentEditor(); + ~AgentEditor(); + + // Initialization + void Initialize(ToastManager* toast_manager, ProposalDrawer* proposal_drawer); + void SetRomContext(Rom* rom); + + // Main rendering + void Draw(); + + // Chat widget access + bool IsChatActive() const; + void SetChatActive(bool active); + void ToggleChat(); + + // Collaboration management + enum class CollaborationMode { + kLocal, // Filesystem-based collaboration + kNetwork // WebSocket-based collaboration + }; + + struct SessionInfo { + std::string session_id; + std::string session_name; + std::vector participants; + }; + + // Host a new collaboration session + absl::StatusOr HostSession(const std::string& session_name, + CollaborationMode mode = CollaborationMode::kLocal); + + // Join an existing collaboration session + absl::StatusOr JoinSession(const std::string& session_code, + CollaborationMode mode = CollaborationMode::kLocal); + + // Leave the current collaboration session + absl::Status LeaveSession(); + + // Refresh session information + absl::StatusOr RefreshSession(); + + // Multimodal (vision/screenshot) support + struct CaptureConfig { + enum class CaptureMode { + kFullWindow, + kActiveEditor, + kSpecificWindow + }; + CaptureMode mode = CaptureMode::kActiveEditor; + std::string specific_window_name; + }; + + absl::Status CaptureSnapshot(std::filesystem::path* output_path, + const CaptureConfig& config); + absl::Status SendToGemini(const std::filesystem::path& image_path, + const std::string& prompt); + + // Server management for network mode +#ifdef YAZE_WITH_GRPC + absl::Status ConnectToServer(const std::string& server_url); + void DisconnectFromServer(); + bool IsConnectedToServer() const; +#endif + + // State queries + bool IsInSession() const; + CollaborationMode GetCurrentMode() const; + std::optional GetCurrentSession() const; + + // Access to underlying components (for advanced use) + AgentChatWidget* GetChatWidget() { return chat_widget_.get(); } + AgentCollaborationCoordinator* GetLocalCoordinator() { return local_coordinator_.get(); } +#ifdef YAZE_WITH_GRPC + NetworkCollaborationCoordinator* GetNetworkCoordinator() { return network_coordinator_.get(); } +#endif + + private: + // Setup callbacks for the chat widget + void SetupChatWidgetCallbacks(); + void SetupMultimodalCallbacks(); + + // Internal state + std::unique_ptr chat_widget_; + std::unique_ptr local_coordinator_; +#ifdef YAZE_WITH_GRPC + std::unique_ptr network_coordinator_; +#endif + + ToastManager* toast_manager_ = nullptr; + ProposalDrawer* proposal_drawer_ = nullptr; + Rom* rom_ = nullptr; + + CollaborationMode current_mode_ = CollaborationMode::kLocal; + bool in_session_ = false; + std::string current_session_id_; + std::string current_session_name_; + std::vector current_participants_; +}; + +} // namespace editor +} // namespace yaze + +#endif // YAZE_APP_EDITOR_AGENT_AGENT_EDITOR_H_ diff --git a/src/app/editor/system/network_collaboration_coordinator.cc b/src/app/editor/agent/network_collaboration_coordinator.cc similarity index 99% rename from src/app/editor/system/network_collaboration_coordinator.cc rename to src/app/editor/agent/network_collaboration_coordinator.cc index 390a89de..905d9d51 100644 --- a/src/app/editor/system/network_collaboration_coordinator.cc +++ b/src/app/editor/agent/network_collaboration_coordinator.cc @@ -1,4 +1,4 @@ -#include "app/editor/system/network_collaboration_coordinator.h" +#include "app/editor/agent/network_collaboration_coordinator.h" #ifdef YAZE_WITH_GRPC diff --git a/src/app/editor/system/network_collaboration_coordinator.h b/src/app/editor/agent/network_collaboration_coordinator.h similarity index 93% rename from src/app/editor/system/network_collaboration_coordinator.h rename to src/app/editor/agent/network_collaboration_coordinator.h index d62d52c4..70bc5a27 100644 --- a/src/app/editor/system/network_collaboration_coordinator.h +++ b/src/app/editor/agent/network_collaboration_coordinator.h @@ -1,5 +1,5 @@ -#ifndef YAZE_APP_EDITOR_SYSTEM_NETWORK_COLLABORATION_COORDINATOR_H_ -#define YAZE_APP_EDITOR_SYSTEM_NETWORK_COLLABORATION_COORDINATOR_H_ +#ifndef YAZE_APP_EDITOR_AGENT_NETWORK_COLLABORATION_COORDINATOR_H_ +#define YAZE_APP_EDITOR_AGENT_NETWORK_COLLABORATION_COORDINATOR_H_ #ifdef YAZE_WITH_GRPC // Reuse gRPC build flag for network features @@ -96,4 +96,4 @@ class NetworkCollaborationCoordinator { } // namespace yaze #endif // YAZE_WITH_GRPC -#endif // YAZE_APP_EDITOR_SYSTEM_NETWORK_COLLABORATION_COORDINATOR_H_ +#endif // YAZE_APP_EDITOR_AGENT_NETWORK_COLLABORATION_COORDINATOR_H_ diff --git a/src/app/editor/editor_library.cmake b/src/app/editor/editor_library.cmake index 852cccd7..4e888a12 100644 --- a/src/app/editor/editor_library.cmake +++ b/src/app/editor/editor_library.cmake @@ -32,15 +32,16 @@ set( app/editor/system/extension_manager.cc app/editor/system/shortcut_manager.cc app/editor/system/popup_manager.cc - app/editor/system/agent_chat_history_codec.cc app/editor/system/proposal_drawer.cc + app/editor/agent/agent_chat_history_codec.cc ) if(YAZE_WITH_GRPC) list(APPEND YAZE_APP_EDITOR_SRC - app/editor/system/agent_chat_widget.cc - app/editor/system/agent_collaboration_coordinator.cc - app/editor/system/network_collaboration_coordinator.cc + app/editor/agent/agent_editor.cc + app/editor/agent/agent_chat_widget.cc + app/editor/agent/agent_collaboration_coordinator.cc + app/editor/agent/network_collaboration_coordinator.cc ) endif() diff --git a/src/app/editor/editor_manager.cc b/src/app/editor/editor_manager.cc index ad2a58c4..b95782d7 100644 --- a/src/app/editor/editor_manager.cc +++ b/src/app/editor/editor_manager.cc @@ -231,58 +231,8 @@ void EditorManager::Initialize(const std::string& filename) { context_.popup_manager = popup_manager_.get(); #ifdef YAZE_WITH_GRPC - agent_chat_widget_.SetToastManager(&toast_manager_); - agent_chat_widget_.SetProposalDrawer(&proposal_drawer_); - AgentChatWidget::CollaborationCallbacks collab_callbacks; - collab_callbacks.host_session = - [this](const std::string& session_name) - -> absl::StatusOr { - ASSIGN_OR_RETURN(auto session, - collaboration_coordinator_.HostSession(session_name)); - AgentChatWidget::CollaborationCallbacks::SessionContext context; - context.session_id = session.session_id; - context.session_name = session.session_name; - context.participants = session.participants; - - // Switch to shared chat history for this session - agent_chat_widget_.SwitchToSharedHistory(session.session_id); - - return context; - }; - collab_callbacks.join_session = - [this](const std::string& session_code) - -> absl::StatusOr { - ASSIGN_OR_RETURN(auto session, - collaboration_coordinator_.JoinSession(session_code)); - AgentChatWidget::CollaborationCallbacks::SessionContext context; - context.session_id = session.session_id; - context.session_name = session.session_name; - context.participants = session.participants; - - // Switch to shared chat history for this session - agent_chat_widget_.SwitchToSharedHistory(session.session_id); - - return context; - }; - collab_callbacks.leave_session = - [this]() { - absl::Status status = collaboration_coordinator_.LeaveSession(); - if (status.ok()) { - // Switch back to local chat history - agent_chat_widget_.SwitchToLocalHistory(); - } - return status; - }; - collab_callbacks.refresh_session = - [this]() -> absl::StatusOr { - ASSIGN_OR_RETURN(auto session, collaboration_coordinator_.RefreshSession()); - AgentChatWidget::CollaborationCallbacks::SessionContext context; - context.session_id = session.session_id; - context.session_name = session.session_name; - context.participants = session.participants; - return context; - }; - agent_chat_widget_.SetCollaborationCallbacks(collab_callbacks); + // Initialize the agent editor + agent_editor_.Initialize(&toast_manager_, &proposal_drawer_); // Set up multimodal (vision) callbacks for Gemini AgentChatWidget::MultimodalCallbacks multimodal_callbacks; @@ -293,7 +243,7 @@ void EditorManager::Initialize(const std::string& filename) { absl::StatusOr result; // Capture based on selected mode - switch (agent_chat_widget_.capture_mode()) { + switch (agent_editor_.GetChatWidget()->capture_mode()) { case CaptureMode::kFullWindow: result = yaze::test::CaptureHarnessScreenshot(""); break; @@ -307,7 +257,7 @@ void EditorManager::Initialize(const std::string& filename) { break; case CaptureMode::kSpecificWindow: { - const char* window_name = agent_chat_widget_.specific_window_name(); + const char* window_name = agent_editor_.GetChatWidget()->specific_window_name(); if (window_name && std::strlen(window_name) > 0) { result = yaze::test::CaptureWindowByName(window_name, ""); if (!result.ok()) { @@ -360,11 +310,11 @@ void EditorManager::Initialize(const std::string& filename) { agent_msg.sender = cli::agent::ChatMessage::Sender::kAgent; agent_msg.message = response->text_response; agent_msg.timestamp = absl::Now(); - agent_chat_widget_.SetRomContext(current_rom_); + agent_editor_.GetChatWidget()->SetRomContext(current_rom_); return absl::OkStatus(); }; - agent_chat_widget_.SetMultimodalCallbacks(multimodal_callbacks); + agent_editor_.GetChatWidget()->SetMultimodalCallbacks(multimodal_callbacks); #endif // Load critical user settings first @@ -858,9 +808,9 @@ void EditorManager::Initialize(const std::string& filename) { #ifdef YAZE_WITH_GRPC {absl::StrCat(ICON_MD_CHAT, " Agent Chat"), "", [this]() { - agent_chat_widget_.set_active(!agent_chat_widget_.is_active()); + agent_editor_.ToggleChat(); }, - [this]() { return agent_chat_widget_.is_active(); }}, + [this]() { return agent_editor_.IsChatActive(); }}, #endif {gui::kSeparator, "", nullptr, []() { return true; }}, @@ -1083,8 +1033,8 @@ absl::Status EditorManager::Update() { Rom* rom_context = (current_rom_ != nullptr && current_rom_->is_loaded()) ? current_rom_ : nullptr; - agent_chat_widget_.SetRomContext(rom_context); - agent_chat_widget_.Draw(); + agent_editor_.SetRomContext(rom_context); + agent_editor_.Draw(); #endif return absl::OkStatus(); diff --git a/src/app/editor/editor_manager.h b/src/app/editor/editor_manager.h index ccd96249..199ba88f 100644 --- a/src/app/editor/editor_manager.h +++ b/src/app/editor/editor_manager.h @@ -24,8 +24,7 @@ #include "app/editor/system/popup_manager.h" #include "app/editor/system/proposal_drawer.h" #ifdef YAZE_WITH_GRPC -#include "app/editor/system/agent_collaboration_coordinator.h" -#include "app/editor/system/agent_chat_widget.h" +#include "app/editor/agent/agent_editor.h" #endif #include "app/editor/system/settings_editor.h" #include "app/editor/system/toast_manager.h" @@ -186,9 +185,8 @@ class EditorManager { bool show_proposal_drawer_ = false; #ifdef YAZE_WITH_GRPC - // Agent chat widget - AgentCollaborationCoordinator collaboration_coordinator_; - AgentChatWidget agent_chat_widget_; + // Agent editor - manages chat, collaboration, and network coordination + AgentEditor agent_editor_; #endif std::string version_ = "";