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.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
#include "app/editor/system/agent_chat_history_codec.h"
|
#include "app/editor/agent/agent_chat_history_codec.h"
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#ifndef YAZE_APP_EDITOR_SYSTEM_AGENT_CHAT_HISTORY_CODEC_H_
|
#ifndef YAZE_APP_EDITOR_AGENT_AGENT_CHAT_HISTORY_CODEC_H_
|
||||||
#define YAZE_APP_EDITOR_SYSTEM_AGENT_CHAT_HISTORY_CODEC_H_
|
#define YAZE_APP_EDITOR_AGENT_AGENT_CHAT_HISTORY_CODEC_H_
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@@ -51,4 +51,4 @@ class AgentChatHistoryCodec {
|
|||||||
} // namespace editor
|
} // namespace editor
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|
||||||
#endif // YAZE_APP_EDITOR_SYSTEM_AGENT_CHAT_HISTORY_CODEC_H_
|
#endif // YAZE_APP_EDITOR_AGENT_AGENT_CHAT_HISTORY_CODEC_H_
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
#include "app/editor/system/agent_chat_widget.h"
|
#include "app/editor/agent/agent_chat_widget.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstdio>
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
#include "absl/time/clock.h"
|
#include "absl/time/clock.h"
|
||||||
#include "absl/time/time.h"
|
#include "absl/time/time.h"
|
||||||
#include "app/core/platform/file_dialog.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/proposal_drawer.h"
|
||||||
#include "app/editor/system/toast_manager.h"
|
#include "app/editor/system/toast_manager.h"
|
||||||
#include "app/gui/icons.h"
|
#include "app/gui/icons.h"
|
||||||
@@ -51,13 +51,13 @@ std::filesystem::path ResolveHistoryPath(const std::string& session_id = "") {
|
|||||||
base = ExpandUserPath(".yaze");
|
base = ExpandUserPath(".yaze");
|
||||||
}
|
}
|
||||||
auto directory = base / "agent";
|
auto directory = base / "agent";
|
||||||
|
|
||||||
// If in a collaborative session, use shared history
|
// If in a collaborative session, use shared history
|
||||||
if (!session_id.empty()) {
|
if (!session_id.empty()) {
|
||||||
directory = directory / "sessions";
|
directory = directory / "sessions";
|
||||||
return directory / (session_id + "_history.json");
|
return directory / (session_id + "_history.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
return directory / "chat_history.json";
|
return directory / "chat_history.json";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,9 +131,8 @@ void AgentChatWidget::EnsureHistoryLoaded() {
|
|||||||
std::filesystem::create_directories(directory, ec);
|
std::filesystem::create_directories(directory, ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
if (toast_manager_) {
|
if (toast_manager_) {
|
||||||
toast_manager_->Show(
|
toast_manager_->Show("Unable to prepare chat history directory",
|
||||||
"Unable to prepare chat history directory",
|
ToastType::kError, 5.0f);
|
||||||
ToastType::kError, 5.0f);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -163,10 +162,9 @@ void AgentChatWidget::EnsureHistoryLoaded() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (toast_manager_) {
|
if (toast_manager_) {
|
||||||
toast_manager_->Show(
|
toast_manager_->Show(absl::StrFormat("Failed to load chat history: %s",
|
||||||
absl::StrFormat("Failed to load chat history: %s",
|
result.status().ToString()),
|
||||||
result.status().ToString()),
|
ToastType::kError, 6.0f);
|
||||||
ToastType::kError, 6.0f);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -180,8 +178,7 @@ void AgentChatWidget::EnsureHistoryLoaded() {
|
|||||||
history_dirty_ = false;
|
history_dirty_ = false;
|
||||||
last_persist_time_ = absl::Now();
|
last_persist_time_ = absl::Now();
|
||||||
if (toast_manager_) {
|
if (toast_manager_) {
|
||||||
toast_manager_->Show("Restored chat history",
|
toast_manager_->Show("Restored chat history", ToastType::kInfo, 3.5f);
|
||||||
ToastType::kInfo, 3.5f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,8 +192,7 @@ void AgentChatWidget::EnsureHistoryLoaded() {
|
|||||||
collaboration_state_.session_name = collaboration_state_.session_id;
|
collaboration_state_.session_name = collaboration_state_.session_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
multimodal_state_.last_capture_path =
|
multimodal_state_.last_capture_path = snapshot.multimodal.last_capture_path;
|
||||||
snapshot.multimodal.last_capture_path;
|
|
||||||
multimodal_state_.status_message = snapshot.multimodal.status_message;
|
multimodal_state_.status_message = snapshot.multimodal.status_message;
|
||||||
multimodal_state_.last_updated = snapshot.multimodal.last_updated;
|
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.session_name = collaboration_state_.session_name;
|
||||||
snapshot.collaboration.participants = collaboration_state_.participants;
|
snapshot.collaboration.participants = collaboration_state_.participants;
|
||||||
snapshot.collaboration.last_synced = collaboration_state_.last_synced;
|
snapshot.collaboration.last_synced = collaboration_state_.last_synced;
|
||||||
snapshot.multimodal.last_capture_path =
|
snapshot.multimodal.last_capture_path = multimodal_state_.last_capture_path;
|
||||||
multimodal_state_.last_capture_path;
|
|
||||||
snapshot.multimodal.status_message = multimodal_state_.status_message;
|
snapshot.multimodal.status_message = multimodal_state_.status_message;
|
||||||
snapshot.multimodal.last_updated = multimodal_state_.last_updated;
|
snapshot.multimodal.last_updated = multimodal_state_.last_updated;
|
||||||
|
|
||||||
@@ -244,10 +239,9 @@ void AgentChatWidget::PersistHistory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (toast_manager_) {
|
if (toast_manager_) {
|
||||||
toast_manager_->Show(
|
toast_manager_->Show(absl::StrFormat("Failed to persist chat history: %s",
|
||||||
absl::StrFormat("Failed to persist chat history: %s",
|
status.ToString()),
|
||||||
status.ToString()),
|
ToastType::kError, 6.0f);
|
||||||
ToastType::kError, 6.0f);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -287,13 +281,13 @@ void AgentChatWidget::NotifyProposalCreated(const ChatMessage& msg,
|
|||||||
const auto& proposal = *msg.proposal;
|
const auto& proposal = *msg.proposal;
|
||||||
toast_manager_->Show(
|
toast_manager_->Show(
|
||||||
absl::StrFormat("%s Proposal %s ready (%d change%s)", ICON_MD_PREVIEW,
|
absl::StrFormat("%s Proposal %s ready (%d change%s)", ICON_MD_PREVIEW,
|
||||||
proposal.id, proposal.change_count,
|
proposal.id, proposal.change_count,
|
||||||
proposal.change_count == 1 ? "" : "s"),
|
proposal.change_count == 1 ? "" : "s"),
|
||||||
ToastType::kSuccess, 5.5f);
|
ToastType::kSuccess, 5.5f);
|
||||||
} else {
|
} else {
|
||||||
toast_manager_->Show(
|
toast_manager_->Show(
|
||||||
absl::StrFormat("%s %d new proposal%s queued",
|
absl::StrFormat("%s %d new proposal%s queued", ICON_MD_PREVIEW, delta,
|
||||||
ICON_MD_PREVIEW, delta, delta == 1 ? "" : "s"),
|
delta == 1 ? "" : "s"),
|
||||||
ToastType::kSuccess, 4.5f);
|
ToastType::kSuccess, 4.5f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,9 +330,9 @@ void AgentChatWidget::RenderMessage(const ChatMessage& msg, int index) {
|
|||||||
ImGui::TextColored(header_color, "%s", header_label);
|
ImGui::TextColored(header_color, "%s", header_label);
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::TextDisabled("%s",
|
ImGui::TextDisabled(
|
||||||
absl::FormatTime("%H:%M:%S", msg.timestamp,
|
"%s", absl::FormatTime("%H:%M:%S", msg.timestamp, absl::LocalTimeZone())
|
||||||
absl::LocalTimeZone()).c_str());
|
.c_str());
|
||||||
|
|
||||||
ImGui::Indent();
|
ImGui::Indent();
|
||||||
|
|
||||||
@@ -346,8 +340,8 @@ void AgentChatWidget::RenderMessage(const ChatMessage& msg, int index) {
|
|||||||
if (ImGui::SmallButton("Copy JSON")) {
|
if (ImGui::SmallButton("Copy JSON")) {
|
||||||
ImGui::SetClipboardText(msg.json_pretty->c_str());
|
ImGui::SetClipboardText(msg.json_pretty->c_str());
|
||||||
if (toast_manager_) {
|
if (toast_manager_) {
|
||||||
toast_manager_->Show("Copied JSON to clipboard",
|
toast_manager_->Show("Copied JSON to clipboard", ToastType::kInfo,
|
||||||
ToastType::kInfo, 2.5f);
|
2.5f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
@@ -387,8 +381,8 @@ void AgentChatWidget::RenderProposalQuickActions(const ChatMessage& msg,
|
|||||||
ImVec2(0, ImGui::GetFrameHeight() * 3.2f), true,
|
ImVec2(0, ImGui::GetFrameHeight() * 3.2f), true,
|
||||||
ImGuiWindowFlags_None);
|
ImGuiWindowFlags_None);
|
||||||
|
|
||||||
ImGui::TextColored(ImVec4(0.8f, 1.0f, 0.8f, 1.0f),
|
ImGui::TextColored(ImVec4(0.8f, 1.0f, 0.8f, 1.0f), "%s Proposal %s",
|
||||||
"%s Proposal %s", ICON_MD_PREVIEW, proposal.id.c_str());
|
ICON_MD_PREVIEW, proposal.id.c_str());
|
||||||
ImGui::Text("Changes: %d", proposal.change_count);
|
ImGui::Text("Changes: %d", proposal.change_count);
|
||||||
ImGui::Text("Commands: %d", proposal.executed_commands);
|
ImGui::Text("Commands: %d", proposal.executed_commands);
|
||||||
|
|
||||||
@@ -401,15 +395,16 @@ void AgentChatWidget::RenderProposalQuickActions(const ChatMessage& msg,
|
|||||||
proposal.proposal_json_path.string().c_str());
|
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);
|
FocusProposalDrawer(proposal.id);
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
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());
|
ImGui::SetClipboardText(proposal.id.c_str());
|
||||||
if (toast_manager_) {
|
if (toast_manager_) {
|
||||||
toast_manager_->Show("Proposal ID copied",
|
toast_manager_->Show("Proposal ID copied", ToastType::kInfo, 2.5f);
|
||||||
ToastType::kInfo, 2.5f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,9 +418,7 @@ void AgentChatWidget::RenderHistory() {
|
|||||||
float reserved_height = ImGui::GetFrameHeightWithSpacing() * 4.0f;
|
float reserved_height = ImGui::GetFrameHeightWithSpacing() * 4.0f;
|
||||||
reserved_height += 220.0f;
|
reserved_height += 220.0f;
|
||||||
|
|
||||||
if (ImGui::BeginChild("History",
|
if (ImGui::BeginChild("History", ImVec2(0, -reserved_height), false,
|
||||||
ImVec2(0, -reserved_height),
|
|
||||||
false,
|
|
||||||
ImGuiWindowFlags_AlwaysVerticalScrollbar |
|
ImGuiWindowFlags_AlwaysVerticalScrollbar |
|
||||||
ImGuiWindowFlags_HorizontalScrollbar)) {
|
ImGuiWindowFlags_HorizontalScrollbar)) {
|
||||||
if (history.empty()) {
|
if (history.empty()) {
|
||||||
@@ -449,10 +442,8 @@ void AgentChatWidget::RenderInputBox() {
|
|||||||
ImGui::Text("Message:");
|
ImGui::Text("Message:");
|
||||||
|
|
||||||
bool submitted = ImGui::InputTextMultiline(
|
bool submitted = ImGui::InputTextMultiline(
|
||||||
"##agent_input", input_buffer_, sizeof(input_buffer_),
|
"##agent_input", input_buffer_, sizeof(input_buffer_), ImVec2(-1, 80.0f),
|
||||||
ImVec2(-1, 80.0f),
|
ImGuiInputTextFlags_AllowTabInput | ImGuiInputTextFlags_EnterReturnsTrue);
|
||||||
ImGuiInputTextFlags_AllowTabInput |
|
|
||||||
ImGuiInputTextFlags_EnterReturnsTrue);
|
|
||||||
|
|
||||||
bool send = submitted;
|
bool send = submitted;
|
||||||
if (submitted && ImGui::GetIO().KeyShift) {
|
if (submitted && ImGui::GetIO().KeyShift) {
|
||||||
@@ -467,7 +458,8 @@ void AgentChatWidget::RenderInputBox() {
|
|||||||
|
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
if (ImGui::Button(absl::StrFormat("%s Send", ICON_MD_SEND).c_str(),
|
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) {
|
if (std::strlen(input_buffer_) > 0) {
|
||||||
history_dirty_ = true;
|
history_dirty_ = true;
|
||||||
EnsureHistoryLoaded();
|
EnsureHistoryLoaded();
|
||||||
@@ -489,15 +481,30 @@ void AgentChatWidget::Draw() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EnsureHistoryLoaded();
|
EnsureHistoryLoaded();
|
||||||
|
|
||||||
// Poll for new messages in collaborative sessions
|
// Poll for new messages in collaborative sessions
|
||||||
PollSharedHistory();
|
PollSharedHistory();
|
||||||
|
|
||||||
ImGui::Begin(title_.c_str(), &active_);
|
ImGui::Begin(title_.c_str(), &active_);
|
||||||
RenderHistory();
|
if (ImGui::BeginTable("#agent_chat_table", 2,
|
||||||
RenderCollaborationPanel();
|
ImGuiTableFlags_BordersInnerV |
|
||||||
RenderMultimodalPanel();
|
ImGuiTableFlags_Resizable |
|
||||||
RenderInputBox();
|
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();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,36 +529,40 @@ void AgentChatWidget::RenderCollaborationPanel() {
|
|||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
// Table layout: Left side = Session Details, Right side = Controls
|
// Table layout: Left side = Session Details, Right side = Controls
|
||||||
if (ImGui::BeginTable("collab_table", 2, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingFixedFit)) {
|
if (ImGui::BeginTable(
|
||||||
ImGui::TableSetupColumn("Session Details", ImGuiTableColumnFlags_WidthFixed, 250.0f);
|
"collab_table", 2,
|
||||||
|
ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingFixedFit)) {
|
||||||
|
ImGui::TableSetupColumn("Session Details", ImGuiTableColumnFlags_WidthFixed,
|
||||||
|
250.0f);
|
||||||
ImGui::TableSetupColumn("Controls", ImGuiTableColumnFlags_WidthStretch);
|
ImGui::TableSetupColumn("Controls", ImGuiTableColumnFlags_WidthStretch);
|
||||||
|
|
||||||
ImGui::TableNextRow();
|
ImGui::TableNextRow();
|
||||||
|
|
||||||
// LEFT COLUMN: Session Details
|
// LEFT COLUMN: Session Details
|
||||||
ImGui::TableSetColumnIndex(0);
|
ImGui::TableSetColumnIndex(0);
|
||||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.15f, 0.15f, 0.18f, 1.0f));
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.15f, 0.15f, 0.18f, 1.0f));
|
||||||
ImGui::BeginChild("session_details", ImVec2(0, 180), true);
|
ImGui::BeginChild("session_details", ImVec2(0, 180), true);
|
||||||
|
|
||||||
const bool connected = collaboration_state_.active;
|
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),
|
ImGui::TextColored(connected ? ImVec4(0.4f, 1.0f, 0.4f, 1.0f)
|
||||||
"%s %s", connected ? "●" : "○",
|
: ImVec4(0.7f, 0.7f, 0.7f, 1.0f),
|
||||||
connected ? "Connected" : "Not connected");
|
"%s %s", connected ? "●" : "○",
|
||||||
|
connected ? "Connected" : "Not connected");
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
if (collaboration_state_.mode == CollaborationMode::kNetwork) {
|
if (collaboration_state_.mode == CollaborationMode::kNetwork) {
|
||||||
ImGui::Text("Server:");
|
ImGui::Text("Server:");
|
||||||
ImGui::TextWrapped("%s", collaboration_state_.server_url.c_str());
|
ImGui::TextWrapped("%s", collaboration_state_.server_url.c_str());
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!collaboration_state_.session_name.empty()) {
|
if (!collaboration_state_.session_name.empty()) {
|
||||||
ImGui::Text("Session:");
|
ImGui::Text("Session:");
|
||||||
ImGui::TextWrapped("%s", collaboration_state_.session_name.c_str());
|
ImGui::TextWrapped("%s", collaboration_state_.session_name.c_str());
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!collaboration_state_.session_id.empty()) {
|
if (!collaboration_state_.session_id.empty()) {
|
||||||
ImGui::Text("Code:");
|
ImGui::Text("Code:");
|
||||||
ImGui::TextWrapped("%s", collaboration_state_.session_id.c_str());
|
ImGui::TextWrapped("%s", collaboration_state_.session_id.c_str());
|
||||||
@@ -563,182 +574,193 @@ void AgentChatWidget::RenderCollaborationPanel() {
|
|||||||
}
|
}
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (collaboration_state_.last_synced != absl::InfinitePast()) {
|
if (collaboration_state_.last_synced != absl::InfinitePast()) {
|
||||||
ImGui::TextDisabled("Last sync:");
|
ImGui::TextDisabled("Last sync:");
|
||||||
ImGui::TextDisabled("%s",
|
ImGui::TextDisabled(
|
||||||
absl::FormatTime("%H:%M:%S", collaboration_state_.last_synced,
|
"%s", absl::FormatTime("%H:%M:%S", collaboration_state_.last_synced,
|
||||||
absl::LocalTimeZone()).c_str());
|
absl::LocalTimeZone())
|
||||||
|
.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
|
|
||||||
// Show participants list below session details
|
// Show participants list below session details
|
||||||
ImGui::BeginChild("participants", ImVec2(0, 0), true);
|
ImGui::BeginChild("participants", ImVec2(0, 0), true);
|
||||||
if (collaboration_state_.participants.empty()) {
|
if (collaboration_state_.participants.empty()) {
|
||||||
ImGui::TextDisabled("No participants");
|
ImGui::TextDisabled("No participants");
|
||||||
} else {
|
} else {
|
||||||
ImGui::Text("Participants (%zu):", collaboration_state_.participants.size());
|
ImGui::Text("Participants (%zu):",
|
||||||
|
collaboration_state_.participants.size());
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
for (const auto& participant : collaboration_state_.participants) {
|
for (const auto& participant : collaboration_state_.participants) {
|
||||||
ImGui::BulletText("%s", participant.c_str());
|
ImGui::BulletText("%s", participant.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
|
|
||||||
// RIGHT COLUMN: Controls
|
// RIGHT COLUMN: Controls
|
||||||
ImGui::TableSetColumnIndex(1);
|
ImGui::TableSetColumnIndex(1);
|
||||||
ImGui::BeginChild("controls", ImVec2(0, 0), false);
|
ImGui::BeginChild("controls", ImVec2(0, 0), false);
|
||||||
|
|
||||||
ImGui::Separator();
|
|
||||||
|
|
||||||
const bool can_host = static_cast<bool>(collaboration_callbacks_.host_session);
|
|
||||||
const bool can_join = static_cast<bool>(collaboration_callbacks_.join_session);
|
|
||||||
const bool can_leave = static_cast<bool>(collaboration_callbacks_.leave_session);
|
|
||||||
const bool can_refresh = static_cast<bool>(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::Separator();
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Text("Host New Session:");
|
const bool can_host =
|
||||||
ImGui::InputTextWithHint("##session_name", "Session name",
|
static_cast<bool>(collaboration_callbacks_.host_session);
|
||||||
session_name_buffer_,
|
const bool can_join =
|
||||||
IM_ARRAYSIZE(session_name_buffer_));
|
static_cast<bool>(collaboration_callbacks_.join_session);
|
||||||
ImGui::SameLine();
|
const bool can_leave =
|
||||||
if (!can_host) ImGui::BeginDisabled();
|
static_cast<bool>(collaboration_callbacks_.leave_session);
|
||||||
if (ImGui::Button("Host")) {
|
const bool can_refresh =
|
||||||
std::string name = session_name_buffer_;
|
static_cast<bool>(collaboration_callbacks_.refresh_session);
|
||||||
if (name.empty()) {
|
|
||||||
if (toast_manager_) {
|
// Network mode: Show server URL input
|
||||||
toast_manager_->Show("Enter a session name first",
|
if (collaboration_state_.mode == CollaborationMode::kNetwork) {
|
||||||
ToastType::kWarning, 3.0f);
|
ImGui::Text("Server URL:");
|
||||||
}
|
ImGui::InputText("##server_url", server_url_buffer_,
|
||||||
} else {
|
IM_ARRAYSIZE(server_url_buffer_));
|
||||||
auto session_or = collaboration_callbacks_.host_session(name);
|
if (ImGui::Button("Connect to Server")) {
|
||||||
if (session_or.ok()) {
|
collaboration_state_.server_url = server_url_buffer_;
|
||||||
ApplyCollaborationSession(session_or.value(), /*update_action_timestamp=*/true);
|
// TODO: Trigger network coordinator connection
|
||||||
std::snprintf(join_code_buffer_, sizeof(join_code_buffer_), "%s",
|
|
||||||
collaboration_state_.session_id.c_str());
|
|
||||||
session_name_buffer_[0] = '\0';
|
|
||||||
if (toast_manager_) {
|
if (toast_manager_) {
|
||||||
toast_manager_->Show(
|
toast_manager_->Show("Network mode: connecting...", ToastType::kInfo,
|
||||||
absl::StrFormat("Hosting session %s",
|
3.0f);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
ImGui::Separator();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!can_host) {
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::SetTooltip("Provide host_session callback to enable hosting");
|
|
||||||
}
|
|
||||||
ImGui::EndDisabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Spacing();
|
ImGui::Text("Host New Session:");
|
||||||
ImGui::Text("Join Existing Session:");
|
ImGui::InputTextWithHint("##session_name", "Session name",
|
||||||
ImGui::InputTextWithHint("##join_code", "Session code",
|
session_name_buffer_,
|
||||||
join_code_buffer_,
|
IM_ARRAYSIZE(session_name_buffer_));
|
||||||
IM_ARRAYSIZE(join_code_buffer_));
|
ImGui::SameLine();
|
||||||
ImGui::SameLine();
|
if (!can_host)
|
||||||
if (!can_join) ImGui::BeginDisabled();
|
ImGui::BeginDisabled();
|
||||||
if (ImGui::Button("Join")) {
|
if (ImGui::Button("Host")) {
|
||||||
std::string code = join_code_buffer_;
|
std::string name = session_name_buffer_;
|
||||||
if (code.empty()) {
|
if (name.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_) {
|
if (toast_manager_) {
|
||||||
toast_manager_->Show(
|
toast_manager_->Show("Enter a session name first",
|
||||||
absl::StrFormat("Joined session %s",
|
ToastType::kWarning, 3.0f);
|
||||||
collaboration_state_.session_id.c_str()),
|
}
|
||||||
ToastType::kSuccess, 3.5f);
|
} 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_host) {
|
||||||
if (!can_join) {
|
if (ImGui::IsItemHovered()) {
|
||||||
if (ImGui::IsItemHovered()) {
|
ImGui::SetTooltip("Provide host_session callback to enable hosting");
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
}
|
}
|
||||||
if (!can_leave) ImGui::EndDisabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connected) {
|
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
ImGui::Separator();
|
ImGui::Text("Join Existing Session:");
|
||||||
if (!can_refresh) ImGui::BeginDisabled();
|
ImGui::InputTextWithHint("##join_code", "Session code", join_code_buffer_,
|
||||||
if (ImGui::Button("Refresh Session")) {
|
IM_ARRAYSIZE(join_code_buffer_));
|
||||||
RefreshCollaboration();
|
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()) {
|
if (!can_join) {
|
||||||
ImGui::SetTooltip("Provide refresh_session callback to enable");
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip("Provide join_session callback to enable joining");
|
||||||
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
}
|
}
|
||||||
if (!can_refresh) ImGui::EndDisabled();
|
|
||||||
} else {
|
if (connected) {
|
||||||
ImGui::Spacing();
|
if (!can_leave)
|
||||||
ImGui::TextDisabled("Start or join a session to collaborate.");
|
ImGui::BeginDisabled();
|
||||||
}
|
if (ImGui::Button("Leave Session")) {
|
||||||
|
absl::Status status = collaboration_callbacks_.leave_session
|
||||||
ImGui::EndChild(); // controls
|
? collaboration_callbacks_.leave_session()
|
||||||
ImGui::EndTable();
|
: 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
|
// Capture mode selection
|
||||||
ImGui::Text("Capture Mode:");
|
ImGui::Text("Capture Mode:");
|
||||||
ImGui::RadioButton("Full Window",
|
ImGui::RadioButton("Full Window",
|
||||||
reinterpret_cast<int*>(&multimodal_state_.capture_mode),
|
reinterpret_cast<int*>(&multimodal_state_.capture_mode),
|
||||||
static_cast<int>(CaptureMode::kFullWindow));
|
static_cast<int>(CaptureMode::kFullWindow));
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::RadioButton("Active Editor",
|
ImGui::RadioButton("Active Editor",
|
||||||
reinterpret_cast<int*>(&multimodal_state_.capture_mode),
|
reinterpret_cast<int*>(&multimodal_state_.capture_mode),
|
||||||
static_cast<int>(CaptureMode::kActiveEditor));
|
static_cast<int>(CaptureMode::kActiveEditor));
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::RadioButton("Specific Window",
|
ImGui::RadioButton("Specific Window",
|
||||||
reinterpret_cast<int*>(&multimodal_state_.capture_mode),
|
reinterpret_cast<int*>(&multimodal_state_.capture_mode),
|
||||||
static_cast<int>(CaptureMode::kSpecificWindow));
|
static_cast<int>(CaptureMode::kSpecificWindow));
|
||||||
|
|
||||||
// If specific window mode, show input for window name
|
// If specific window mode, show input for window name
|
||||||
if (multimodal_state_.capture_mode == CaptureMode::kSpecificWindow) {
|
if (multimodal_state_.capture_mode == CaptureMode::kSpecificWindow) {
|
||||||
ImGui::InputText("Window Name", multimodal_state_.specific_window_buffer,
|
ImGui::InputText("Window Name", multimodal_state_.specific_window_buffer,
|
||||||
IM_ARRAYSIZE(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();
|
ImGui::Separator();
|
||||||
|
|
||||||
if (!can_capture) ImGui::BeginDisabled();
|
if (!can_capture)
|
||||||
|
ImGui::BeginDisabled();
|
||||||
if (ImGui::Button("Capture Snapshot")) {
|
if (ImGui::Button("Capture Snapshot")) {
|
||||||
if (multimodal_callbacks_.capture_snapshot) {
|
if (multimodal_callbacks_.capture_snapshot) {
|
||||||
std::filesystem::path captured_path;
|
std::filesystem::path captured_path;
|
||||||
@@ -786,8 +810,7 @@ void AgentChatWidget::RenderMultimodalPanel() {
|
|||||||
absl::StrFormat("Captured %s", captured_path.string());
|
absl::StrFormat("Captured %s", captured_path.string());
|
||||||
multimodal_state_.last_updated = absl::Now();
|
multimodal_state_.last_updated = absl::Now();
|
||||||
if (toast_manager_) {
|
if (toast_manager_) {
|
||||||
toast_manager_->Show("Snapshot captured",
|
toast_manager_->Show("Snapshot captured", ToastType::kSuccess, 3.0f);
|
||||||
ToastType::kSuccess, 3.0f);
|
|
||||||
}
|
}
|
||||||
MarkHistoryDirty();
|
MarkHistoryDirty();
|
||||||
} else {
|
} else {
|
||||||
@@ -818,24 +841,24 @@ void AgentChatWidget::RenderMultimodalPanel() {
|
|||||||
ImGui::InputTextMultiline("##gemini_prompt", multimodal_prompt_buffer_,
|
ImGui::InputTextMultiline("##gemini_prompt", multimodal_prompt_buffer_,
|
||||||
IM_ARRAYSIZE(multimodal_prompt_buffer_),
|
IM_ARRAYSIZE(multimodal_prompt_buffer_),
|
||||||
ImVec2(-1, 60.0f));
|
ImVec2(-1, 60.0f));
|
||||||
if (!can_send) ImGui::BeginDisabled();
|
if (!can_send)
|
||||||
|
ImGui::BeginDisabled();
|
||||||
if (ImGui::Button("Send to Gemini")) {
|
if (ImGui::Button("Send to Gemini")) {
|
||||||
if (!multimodal_state_.last_capture_path.has_value()) {
|
if (!multimodal_state_.last_capture_path.has_value()) {
|
||||||
if (toast_manager_) {
|
if (toast_manager_) {
|
||||||
toast_manager_->Show("Capture a snapshot first",
|
toast_manager_->Show("Capture a snapshot first", ToastType::kWarning,
|
||||||
ToastType::kWarning, 3.0f);
|
3.0f);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
std::string prompt = multimodal_prompt_buffer_;
|
std::string prompt = multimodal_prompt_buffer_;
|
||||||
absl::Status status = multimodal_callbacks_.send_to_gemini(
|
absl::Status status = multimodal_callbacks_.send_to_gemini(
|
||||||
*multimodal_state_.last_capture_path, prompt);
|
*multimodal_state_.last_capture_path, prompt);
|
||||||
if (status.ok()) {
|
if (status.ok()) {
|
||||||
multimodal_state_.status_message =
|
multimodal_state_.status_message = "Submitted image to Gemini";
|
||||||
"Submitted image to Gemini";
|
|
||||||
multimodal_state_.last_updated = absl::Now();
|
multimodal_state_.last_updated = absl::Now();
|
||||||
if (toast_manager_) {
|
if (toast_manager_) {
|
||||||
toast_manager_->Show("Gemini request sent",
|
toast_manager_->Show("Gemini request sent", ToastType::kSuccess,
|
||||||
ToastType::kSuccess, 3.0f);
|
3.0f);
|
||||||
}
|
}
|
||||||
MarkHistoryDirty();
|
MarkHistoryDirty();
|
||||||
} else {
|
} else {
|
||||||
@@ -862,7 +885,8 @@ void AgentChatWidget::RenderMultimodalPanel() {
|
|||||||
ImGui::TextDisabled(
|
ImGui::TextDisabled(
|
||||||
"Updated: %s",
|
"Updated: %s",
|
||||||
absl::FormatTime("%H:%M:%S", multimodal_state_.last_updated,
|
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();
|
MarkHistoryDirty();
|
||||||
}
|
}
|
||||||
if (toast_manager_) {
|
if (toast_manager_) {
|
||||||
toast_manager_->Show(
|
toast_manager_->Show(absl::StrFormat("Failed to refresh participants: %s",
|
||||||
absl::StrFormat("Failed to refresh participants: %s",
|
session_or.status().message()),
|
||||||
session_or.status().message()),
|
ToastType::kError, 5.0f);
|
||||||
ToastType::kError, 5.0f);
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplyCollaborationSession(session_or.value(), /*update_action_timestamp=*/false);
|
ApplyCollaborationSession(session_or.value(),
|
||||||
|
/*update_action_timestamp=*/false);
|
||||||
MarkHistoryDirty();
|
MarkHistoryDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -896,9 +920,8 @@ void AgentChatWidget::ApplyCollaborationSession(
|
|||||||
bool update_action_timestamp) {
|
bool update_action_timestamp) {
|
||||||
collaboration_state_.active = true;
|
collaboration_state_.active = true;
|
||||||
collaboration_state_.session_id = context.session_id;
|
collaboration_state_.session_id = context.session_id;
|
||||||
collaboration_state_.session_name = context.session_name.empty()
|
collaboration_state_.session_name =
|
||||||
? context.session_id
|
context.session_name.empty() ? context.session_id : context.session_name;
|
||||||
: context.session_name;
|
|
||||||
collaboration_state_.participants = context.participants;
|
collaboration_state_.participants = context.participants;
|
||||||
collaboration_state_.last_synced = absl::Now();
|
collaboration_state_.last_synced = absl::Now();
|
||||||
if (update_action_timestamp) {
|
if (update_action_timestamp) {
|
||||||
@@ -924,14 +947,14 @@ void AgentChatWidget::SwitchToSharedHistory(const std::string& session_id) {
|
|||||||
// Switch to shared history path
|
// Switch to shared history path
|
||||||
history_path_ = ResolveHistoryPath(session_id);
|
history_path_ = ResolveHistoryPath(session_id);
|
||||||
history_loaded_ = false;
|
history_loaded_ = false;
|
||||||
|
|
||||||
// Load shared history
|
// Load shared history
|
||||||
EnsureHistoryLoaded();
|
EnsureHistoryLoaded();
|
||||||
|
|
||||||
// Initialize polling state
|
// Initialize polling state
|
||||||
last_known_history_size_ = agent_service_.GetHistory().size();
|
last_known_history_size_ = agent_service_.GetHistory().size();
|
||||||
last_shared_history_poll_ = absl::Now();
|
last_shared_history_poll_ = absl::Now();
|
||||||
|
|
||||||
if (toast_manager_) {
|
if (toast_manager_) {
|
||||||
toast_manager_->Show(
|
toast_manager_->Show(
|
||||||
absl::StrFormat("Switched to shared chat history for session %s",
|
absl::StrFormat("Switched to shared chat history for session %s",
|
||||||
@@ -949,13 +972,13 @@ void AgentChatWidget::SwitchToLocalHistory() {
|
|||||||
// Switch back to local history
|
// Switch back to local history
|
||||||
history_path_ = ResolveHistoryPath("");
|
history_path_ = ResolveHistoryPath("");
|
||||||
history_loaded_ = false;
|
history_loaded_ = false;
|
||||||
|
|
||||||
// Load local history
|
// Load local history
|
||||||
EnsureHistoryLoaded();
|
EnsureHistoryLoaded();
|
||||||
|
|
||||||
if (toast_manager_) {
|
if (toast_manager_) {
|
||||||
toast_manager_->Show("Switched to local chat history",
|
toast_manager_->Show("Switched to local chat history", ToastType::kInfo,
|
||||||
ToastType::kInfo, 3.0f);
|
3.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -965,12 +988,12 @@ void AgentChatWidget::PollSharedHistory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const absl::Time now = absl::Now();
|
const absl::Time now = absl::Now();
|
||||||
|
|
||||||
// Poll every 2 seconds
|
// Poll every 2 seconds
|
||||||
if (now - last_shared_history_poll_ < absl::Seconds(2)) {
|
if (now - last_shared_history_poll_ < absl::Seconds(2)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
last_shared_history_poll_ = now;
|
last_shared_history_poll_ = now;
|
||||||
|
|
||||||
// Check if the shared history file has been updated
|
// Check if the shared history file has been updated
|
||||||
@@ -980,15 +1003,15 @@ void AgentChatWidget::PollSharedHistory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const size_t new_size = result->history.size();
|
const size_t new_size = result->history.size();
|
||||||
|
|
||||||
// If history has grown, reload it
|
// If history has grown, reload it
|
||||||
if (new_size > last_known_history_size_) {
|
if (new_size > last_known_history_size_) {
|
||||||
const size_t new_messages = new_size - last_known_history_size_;
|
const size_t new_messages = new_size - last_known_history_size_;
|
||||||
|
|
||||||
agent_service_.ReplaceHistory(std::move(result->history));
|
agent_service_.ReplaceHistory(std::move(result->history));
|
||||||
last_history_size_ = new_size;
|
last_history_size_ = new_size;
|
||||||
last_known_history_size_ = new_size;
|
last_known_history_size_ = new_size;
|
||||||
|
|
||||||
if (toast_manager_) {
|
if (toast_manager_) {
|
||||||
toast_manager_->Show(
|
toast_manager_->Show(
|
||||||
absl::StrFormat("📬 %zu new message%s from collaborators",
|
absl::StrFormat("📬 %zu new message%s from collaborators",
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#ifndef YAZE_SRC_APP_EDITOR_SYSTEM_AGENT_CHAT_WIDGET_H_
|
#ifndef YAZE_APP_EDITOR_AGENT_AGENT_CHAT_WIDGET_H_
|
||||||
#define YAZE_SRC_APP_EDITOR_SYSTEM_AGENT_CHAT_WIDGET_H_
|
#define YAZE_APP_EDITOR_AGENT_AGENT_CHAT_WIDGET_H_
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@@ -158,4 +158,4 @@ public:
|
|||||||
} // namespace editor
|
} // namespace editor
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|
||||||
#endif // YAZE_SRC_APP_EDITOR_SYSTEM_AGENT_CHAT_WIDGET_H_
|
#endif // YAZE_APP_EDITOR_AGENT_AGENT_CHAT_WIDGET_H_
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "app/editor/system/agent_collaboration_coordinator.h"
|
#include "app/editor/agent/agent_collaboration_coordinator.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#ifndef YAZE_APP_EDITOR_SYSTEM_AGENT_COLLABORATION_COORDINATOR_H_
|
#ifndef YAZE_APP_EDITOR_AGENT_AGENT_COLLABORATION_COORDINATOR_H_
|
||||||
#define YAZE_APP_EDITOR_SYSTEM_AGENT_COLLABORATION_COORDINATOR_H_
|
#define YAZE_APP_EDITOR_AGENT_AGENT_COLLABORATION_COORDINATOR_H_
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -63,4 +63,4 @@ class AgentCollaborationCoordinator {
|
|||||||
} // namespace editor
|
} // namespace editor
|
||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|
||||||
#endif // YAZE_APP_EDITOR_SYSTEM_AGENT_COLLABORATION_COORDINATOR_H_
|
#endif // YAZE_APP_EDITOR_AGENT_AGENT_COLLABORATION_COORDINATOR_H_
|
||||||
392
src/app/editor/agent/agent_editor.cc
Normal file
392
src/app/editor/agent/agent_editor.cc
Normal file
@@ -0,0 +1,392 @@
|
|||||||
|
#include "app/editor/agent/agent_editor.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#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<AgentChatWidget>();
|
||||||
|
local_coordinator_ = std::make_unique<AgentCollaborationCoordinator>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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::SessionInfo> 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::SessionInfo> 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::SessionInfo> 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<NetworkCollaborationCoordinator>(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::SessionInfo> 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<AgentChatWidget::CollaborationCallbacks::SessionContext> {
|
||||||
|
// 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<AgentChatWidget::CollaborationCallbacks::SessionContext> {
|
||||||
|
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<AgentChatWidget::CollaborationCallbacks::SessionContext> {
|
||||||
|
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
|
||||||
141
src/app/editor/agent/agent_editor.h
Normal file
141
src/app/editor/agent/agent_editor.h
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
#ifndef YAZE_APP_EDITOR_AGENT_AGENT_EDITOR_H_
|
||||||
|
#define YAZE_APP_EDITOR_AGENT_AGENT_EDITOR_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#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<std::string> participants;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Host a new collaboration session
|
||||||
|
absl::StatusOr<SessionInfo> HostSession(const std::string& session_name,
|
||||||
|
CollaborationMode mode = CollaborationMode::kLocal);
|
||||||
|
|
||||||
|
// Join an existing collaboration session
|
||||||
|
absl::StatusOr<SessionInfo> JoinSession(const std::string& session_code,
|
||||||
|
CollaborationMode mode = CollaborationMode::kLocal);
|
||||||
|
|
||||||
|
// Leave the current collaboration session
|
||||||
|
absl::Status LeaveSession();
|
||||||
|
|
||||||
|
// Refresh session information
|
||||||
|
absl::StatusOr<SessionInfo> 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<SessionInfo> 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<AgentChatWidget> chat_widget_;
|
||||||
|
std::unique_ptr<AgentCollaborationCoordinator> local_coordinator_;
|
||||||
|
#ifdef YAZE_WITH_GRPC
|
||||||
|
std::unique_ptr<NetworkCollaborationCoordinator> 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<std::string> current_participants_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace editor
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_EDITOR_AGENT_AGENT_EDITOR_H_
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "app/editor/system/network_collaboration_coordinator.h"
|
#include "app/editor/agent/network_collaboration_coordinator.h"
|
||||||
|
|
||||||
#ifdef YAZE_WITH_GRPC
|
#ifdef YAZE_WITH_GRPC
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#ifndef YAZE_APP_EDITOR_SYSTEM_NETWORK_COLLABORATION_COORDINATOR_H_
|
#ifndef YAZE_APP_EDITOR_AGENT_NETWORK_COLLABORATION_COORDINATOR_H_
|
||||||
#define YAZE_APP_EDITOR_SYSTEM_NETWORK_COLLABORATION_COORDINATOR_H_
|
#define YAZE_APP_EDITOR_AGENT_NETWORK_COLLABORATION_COORDINATOR_H_
|
||||||
|
|
||||||
#ifdef YAZE_WITH_GRPC // Reuse gRPC build flag for network features
|
#ifdef YAZE_WITH_GRPC // Reuse gRPC build flag for network features
|
||||||
|
|
||||||
@@ -96,4 +96,4 @@ class NetworkCollaborationCoordinator {
|
|||||||
} // namespace yaze
|
} // namespace yaze
|
||||||
|
|
||||||
#endif // YAZE_WITH_GRPC
|
#endif // YAZE_WITH_GRPC
|
||||||
#endif // YAZE_APP_EDITOR_SYSTEM_NETWORK_COLLABORATION_COORDINATOR_H_
|
#endif // YAZE_APP_EDITOR_AGENT_NETWORK_COLLABORATION_COORDINATOR_H_
|
||||||
@@ -32,15 +32,16 @@ set(
|
|||||||
app/editor/system/extension_manager.cc
|
app/editor/system/extension_manager.cc
|
||||||
app/editor/system/shortcut_manager.cc
|
app/editor/system/shortcut_manager.cc
|
||||||
app/editor/system/popup_manager.cc
|
app/editor/system/popup_manager.cc
|
||||||
app/editor/system/agent_chat_history_codec.cc
|
|
||||||
app/editor/system/proposal_drawer.cc
|
app/editor/system/proposal_drawer.cc
|
||||||
|
app/editor/agent/agent_chat_history_codec.cc
|
||||||
)
|
)
|
||||||
|
|
||||||
if(YAZE_WITH_GRPC)
|
if(YAZE_WITH_GRPC)
|
||||||
list(APPEND YAZE_APP_EDITOR_SRC
|
list(APPEND YAZE_APP_EDITOR_SRC
|
||||||
app/editor/system/agent_chat_widget.cc
|
app/editor/agent/agent_editor.cc
|
||||||
app/editor/system/agent_collaboration_coordinator.cc
|
app/editor/agent/agent_chat_widget.cc
|
||||||
app/editor/system/network_collaboration_coordinator.cc
|
app/editor/agent/agent_collaboration_coordinator.cc
|
||||||
|
app/editor/agent/network_collaboration_coordinator.cc
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -231,58 +231,8 @@ void EditorManager::Initialize(const std::string& filename) {
|
|||||||
context_.popup_manager = popup_manager_.get();
|
context_.popup_manager = popup_manager_.get();
|
||||||
|
|
||||||
#ifdef YAZE_WITH_GRPC
|
#ifdef YAZE_WITH_GRPC
|
||||||
agent_chat_widget_.SetToastManager(&toast_manager_);
|
// Initialize the agent editor
|
||||||
agent_chat_widget_.SetProposalDrawer(&proposal_drawer_);
|
agent_editor_.Initialize(&toast_manager_, &proposal_drawer_);
|
||||||
AgentChatWidget::CollaborationCallbacks collab_callbacks;
|
|
||||||
collab_callbacks.host_session =
|
|
||||||
[this](const std::string& session_name)
|
|
||||||
-> absl::StatusOr<AgentChatWidget::CollaborationCallbacks::SessionContext> {
|
|
||||||
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<AgentChatWidget::CollaborationCallbacks::SessionContext> {
|
|
||||||
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<AgentChatWidget::CollaborationCallbacks::SessionContext> {
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Set up multimodal (vision) callbacks for Gemini
|
// Set up multimodal (vision) callbacks for Gemini
|
||||||
AgentChatWidget::MultimodalCallbacks multimodal_callbacks;
|
AgentChatWidget::MultimodalCallbacks multimodal_callbacks;
|
||||||
@@ -293,7 +243,7 @@ void EditorManager::Initialize(const std::string& filename) {
|
|||||||
absl::StatusOr<yaze::test::ScreenshotArtifact> result;
|
absl::StatusOr<yaze::test::ScreenshotArtifact> result;
|
||||||
|
|
||||||
// Capture based on selected mode
|
// Capture based on selected mode
|
||||||
switch (agent_chat_widget_.capture_mode()) {
|
switch (agent_editor_.GetChatWidget()->capture_mode()) {
|
||||||
case CaptureMode::kFullWindow:
|
case CaptureMode::kFullWindow:
|
||||||
result = yaze::test::CaptureHarnessScreenshot("");
|
result = yaze::test::CaptureHarnessScreenshot("");
|
||||||
break;
|
break;
|
||||||
@@ -307,7 +257,7 @@ void EditorManager::Initialize(const std::string& filename) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case CaptureMode::kSpecificWindow: {
|
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) {
|
if (window_name && std::strlen(window_name) > 0) {
|
||||||
result = yaze::test::CaptureWindowByName(window_name, "");
|
result = yaze::test::CaptureWindowByName(window_name, "");
|
||||||
if (!result.ok()) {
|
if (!result.ok()) {
|
||||||
@@ -360,11 +310,11 @@ void EditorManager::Initialize(const std::string& filename) {
|
|||||||
agent_msg.sender = cli::agent::ChatMessage::Sender::kAgent;
|
agent_msg.sender = cli::agent::ChatMessage::Sender::kAgent;
|
||||||
agent_msg.message = response->text_response;
|
agent_msg.message = response->text_response;
|
||||||
agent_msg.timestamp = absl::Now();
|
agent_msg.timestamp = absl::Now();
|
||||||
agent_chat_widget_.SetRomContext(current_rom_);
|
agent_editor_.GetChatWidget()->SetRomContext(current_rom_);
|
||||||
|
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
};
|
};
|
||||||
agent_chat_widget_.SetMultimodalCallbacks(multimodal_callbacks);
|
agent_editor_.GetChatWidget()->SetMultimodalCallbacks(multimodal_callbacks);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Load critical user settings first
|
// Load critical user settings first
|
||||||
@@ -858,9 +808,9 @@ void EditorManager::Initialize(const std::string& filename) {
|
|||||||
#ifdef YAZE_WITH_GRPC
|
#ifdef YAZE_WITH_GRPC
|
||||||
{absl::StrCat(ICON_MD_CHAT, " Agent Chat"), "",
|
{absl::StrCat(ICON_MD_CHAT, " Agent Chat"), "",
|
||||||
[this]() {
|
[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
|
#endif
|
||||||
|
|
||||||
{gui::kSeparator, "", nullptr, []() { return true; }},
|
{gui::kSeparator, "", nullptr, []() { return true; }},
|
||||||
@@ -1083,8 +1033,8 @@ absl::Status EditorManager::Update() {
|
|||||||
Rom* rom_context =
|
Rom* rom_context =
|
||||||
(current_rom_ != nullptr && current_rom_->is_loaded()) ? current_rom_
|
(current_rom_ != nullptr && current_rom_->is_loaded()) ? current_rom_
|
||||||
: nullptr;
|
: nullptr;
|
||||||
agent_chat_widget_.SetRomContext(rom_context);
|
agent_editor_.SetRomContext(rom_context);
|
||||||
agent_chat_widget_.Draw();
|
agent_editor_.Draw();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return absl::OkStatus();
|
return absl::OkStatus();
|
||||||
|
|||||||
@@ -24,8 +24,7 @@
|
|||||||
#include "app/editor/system/popup_manager.h"
|
#include "app/editor/system/popup_manager.h"
|
||||||
#include "app/editor/system/proposal_drawer.h"
|
#include "app/editor/system/proposal_drawer.h"
|
||||||
#ifdef YAZE_WITH_GRPC
|
#ifdef YAZE_WITH_GRPC
|
||||||
#include "app/editor/system/agent_collaboration_coordinator.h"
|
#include "app/editor/agent/agent_editor.h"
|
||||||
#include "app/editor/system/agent_chat_widget.h"
|
|
||||||
#endif
|
#endif
|
||||||
#include "app/editor/system/settings_editor.h"
|
#include "app/editor/system/settings_editor.h"
|
||||||
#include "app/editor/system/toast_manager.h"
|
#include "app/editor/system/toast_manager.h"
|
||||||
@@ -186,9 +185,8 @@ class EditorManager {
|
|||||||
bool show_proposal_drawer_ = false;
|
bool show_proposal_drawer_ = false;
|
||||||
|
|
||||||
#ifdef YAZE_WITH_GRPC
|
#ifdef YAZE_WITH_GRPC
|
||||||
// Agent chat widget
|
// Agent editor - manages chat, collaboration, and network coordination
|
||||||
AgentCollaborationCoordinator collaboration_coordinator_;
|
AgentEditor agent_editor_;
|
||||||
AgentChatWidget agent_chat_widget_;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::string version_ = "";
|
std::string version_ = "";
|
||||||
|
|||||||
Reference in New Issue
Block a user