feat: Add network collaboration coordinator for real-time collaboration features

- Introduced NetworkCollaborationCoordinator class to manage WebSocket connections for collaborative sessions.
- Implemented session management functions: HostSession, JoinSession, and LeaveSession.
- Added message handling for chat messages and participant updates.
- Updated CMake configuration to include the new network collaboration source files.
- Integrated a stub WebSocket client for initial functionality, with plans for future enhancements.
This commit is contained in:
scawful
2025-10-04 17:18:51 -04:00
parent f182833afe
commit 0b29af5f2b
4 changed files with 485 additions and 0 deletions

View File

@@ -662,6 +662,11 @@ if (YAZE_BUILD_LIB)
target_compile_definitions(yaze_c PRIVATE YAZE_ENABLE_IMGUI_TEST_ENGINE=0)
endif()
# Link with test support library if available (required by editor)
if(TARGET yaze_test_support)
target_link_libraries(yaze_c PRIVATE yaze_test_support)
endif()
if(PNG_FOUND)
target_link_libraries(yaze_c PRIVATE ${PNG_LIBRARIES})
if(NOT YAZE_USE_MODULAR_BUILD)

View File

@@ -40,6 +40,7 @@ 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
)
endif()

View File

@@ -0,0 +1,380 @@
#include "app/editor/system/network_collaboration_coordinator.h"
#ifdef YAZE_WITH_GRPC
#include <iostream>
#include <sstream>
#include "absl/strings/str_format.h"
#include "absl/strings/str_split.h"
#ifdef YAZE_WITH_JSON
#include "httplib.h"
#include "nlohmann/json.hpp"
using Json = nlohmann::json;
#endif
namespace yaze {
namespace editor {
#ifdef YAZE_WITH_JSON
namespace detail {
// Stub WebSocket client implementation
// TODO: Integrate proper WebSocket library (websocketpp, ixwebsocket, or libwebsockets)
// This is a placeholder to allow compilation
class WebSocketClient {
public:
explicit WebSocketClient(const std::string& host, int port)
: host_(host), port_(port) {
std::cerr << "⚠️ WebSocket client stub - not yet implemented" << std::endl;
std::cerr << " To use network collaboration, integrate a WebSocket library" << std::endl;
}
bool Connect(const std::string& path) {
(void)path; // Suppress unused parameter warning
std::cerr << "WebSocket Connect stub called for " << host_ << ":" << port_ << std::endl;
// Return false for now - real implementation needed
return false;
}
void Close() {
// Stub
}
bool Send(const std::string& message) {
(void)message; // Suppress unused parameter warning
if (!connected_) return false;
// Stub - real implementation needed
return false;
}
std::string Receive() {
if (!connected_) return "";
// Stub - real implementation needed
return "";
}
bool IsConnected() const { return connected_; }
private:
std::string host_;
int port_;
bool connected_ = false;
};
} // namespace detail
NetworkCollaborationCoordinator::NetworkCollaborationCoordinator(
const std::string& server_url)
: server_url_(server_url) {
// Parse server URL
// Expected format: ws://hostname:port or wss://hostname:port
if (server_url_.find("ws://") == 0) {
// Extract hostname and port
// For now, use default localhost:8765
ConnectWebSocket();
}
}
NetworkCollaborationCoordinator::~NetworkCollaborationCoordinator() {
should_stop_ = true;
if (receive_thread_ && receive_thread_->joinable()) {
receive_thread_->join();
}
DisconnectWebSocket();
}
void NetworkCollaborationCoordinator::ConnectWebSocket() {
// Parse URL (simple implementation - assumes ws://host:port format)
std::string host = "localhost";
int port = 8765;
// Extract from server_url_ if needed
if (server_url_.find("ws://") == 0) {
std::string url_part = server_url_.substr(5); // Skip "ws://"
std::vector<std::string> parts = absl::StrSplit(url_part, ':');
if (!parts.empty()) {
host = parts[0];
}
if (parts.size() > 1) {
port = std::stoi(parts[1]);
}
}
ws_client_ = std::make_unique<detail::WebSocketClient>(host, port);
if (ws_client_->Connect("/")) {
connected_ = true;
// Start receive thread
should_stop_ = false;
receive_thread_ = std::make_unique<std::thread>(
&NetworkCollaborationCoordinator::WebSocketReceiveLoop, this);
}
}
void NetworkCollaborationCoordinator::DisconnectWebSocket() {
if (ws_client_) {
ws_client_->Close();
ws_client_.reset();
}
connected_ = false;
}
absl::StatusOr<NetworkCollaborationCoordinator::SessionInfo>
NetworkCollaborationCoordinator::HostSession(const std::string& session_name,
const std::string& username) {
if (!connected_) {
return absl::FailedPreconditionError("Not connected to collaboration server");
}
username_ = username;
// Build host_session message
Json message = {
{"type", "host_session"},
{"payload", {
{"session_name", session_name},
{"username", username}
}}
};
SendWebSocketMessage("host_session", message["payload"].dump());
// TODO: Wait for session_hosted response and parse it
// For now, return a placeholder
SessionInfo info;
info.session_name = session_name;
info.session_code = "PENDING"; // Will be updated from server response
info.participants = {username};
in_session_ = true;
session_name_ = session_name;
return info;
}
absl::StatusOr<NetworkCollaborationCoordinator::SessionInfo>
NetworkCollaborationCoordinator::JoinSession(const std::string& session_code,
const std::string& username) {
if (!connected_) {
return absl::FailedPreconditionError("Not connected to collaboration server");
}
username_ = username;
session_code_ = session_code;
// Build join_session message
Json message = {
{"type", "join_session"},
{"payload", {
{"session_code", session_code},
{"username", username}
}}
};
SendWebSocketMessage("join_session", message["payload"].dump());
// TODO: Wait for session_joined response and parse it
SessionInfo info;
info.session_code = session_code;
in_session_ = true;
return info;
}
absl::Status NetworkCollaborationCoordinator::LeaveSession() {
if (!in_session_) {
return absl::FailedPreconditionError("Not in a session");
}
Json message = {{"type", "leave_session"}};
SendWebSocketMessage("leave_session", "{}");
in_session_ = false;
session_id_.clear();
session_code_.clear();
session_name_.clear();
return absl::OkStatus();
}
absl::Status NetworkCollaborationCoordinator::SendMessage(
const std::string& sender, const std::string& message) {
if (!in_session_) {
return absl::FailedPreconditionError("Not in a session");
}
Json msg = {
{"type", "chat_message"},
{"payload", {
{"sender", sender},
{"message", message}
}}
};
SendWebSocketMessage("chat_message", msg["payload"].dump());
return absl::OkStatus();
}
bool NetworkCollaborationCoordinator::IsConnected() const {
return connected_;
}
void NetworkCollaborationCoordinator::SetMessageCallback(MessageCallback callback) {
absl::MutexLock lock(&mutex_);
message_callback_ = std::move(callback);
}
void NetworkCollaborationCoordinator::SetParticipantCallback(
ParticipantCallback callback) {
absl::MutexLock lock(&mutex_);
participant_callback_ = std::move(callback);
}
void NetworkCollaborationCoordinator::SetErrorCallback(ErrorCallback callback) {
absl::MutexLock lock(&mutex_);
error_callback_ = std::move(callback);
}
void NetworkCollaborationCoordinator::SendWebSocketMessage(
const std::string& type, const std::string& payload_json) {
if (!ws_client_ || !connected_) {
return;
}
Json message = {
{"type", type},
{"payload", Json::parse(payload_json)}
};
ws_client_->Send(message.dump());
}
void NetworkCollaborationCoordinator::HandleWebSocketMessage(
const std::string& message_str) {
try {
Json message = Json::parse(message_str);
std::string type = message["type"];
if (type == "session_hosted") {
Json payload = message["payload"];
session_id_ = payload["session_id"];
session_code_ = payload["session_code"];
session_name_ = payload["session_name"];
if (payload.contains("participants")) {
absl::MutexLock lock(&mutex_);
if (participant_callback_) {
std::vector<std::string> participants = payload["participants"];
participant_callback_(participants);
}
}
} else if (type == "session_joined") {
Json payload = message["payload"];
session_id_ = payload["session_id"];
session_code_ = payload["session_code"];
session_name_ = payload["session_name"];
if (payload.contains("participants")) {
absl::MutexLock lock(&mutex_);
if (participant_callback_) {
std::vector<std::string> participants = payload["participants"];
participant_callback_(participants);
}
}
} else if (type == "chat_message") {
Json payload = message["payload"];
ChatMessage msg;
msg.sender = payload["sender"];
msg.message = payload["message"];
msg.timestamp = payload["timestamp"];
absl::MutexLock lock(&mutex_);
if (message_callback_) {
message_callback_(msg);
}
} else if (type == "participant_joined" || type == "participant_left") {
Json payload = message["payload"];
if (payload.contains("participants")) {
absl::MutexLock lock(&mutex_);
if (participant_callback_) {
std::vector<std::string> participants = payload["participants"];
participant_callback_(participants);
}
}
} else if (type == "error") {
Json payload = message["payload"];
std::string error = payload["error"];
absl::MutexLock lock(&mutex_);
if (error_callback_) {
error_callback_(error);
}
}
} catch (const std::exception& e) {
std::cerr << "Error parsing WebSocket message: " << e.what() << std::endl;
}
}
void NetworkCollaborationCoordinator::WebSocketReceiveLoop() {
while (!should_stop_ && connected_) {
if (!ws_client_) break;
std::string message = ws_client_->Receive();
if (!message.empty()) {
HandleWebSocketMessage(message);
}
// Small sleep to avoid busy-waiting
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
#else // !YAZE_WITH_JSON
// Stub implementations when JSON is not available
NetworkCollaborationCoordinator::NetworkCollaborationCoordinator(
const std::string& server_url) : server_url_(server_url) {}
NetworkCollaborationCoordinator::~NetworkCollaborationCoordinator() = default;
absl::StatusOr<NetworkCollaborationCoordinator::SessionInfo>
NetworkCollaborationCoordinator::HostSession(const std::string&, const std::string&) {
return absl::UnimplementedError("Network collaboration requires JSON support");
}
absl::StatusOr<NetworkCollaborationCoordinator::SessionInfo>
NetworkCollaborationCoordinator::JoinSession(const std::string&, const std::string&) {
return absl::UnimplementedError("Network collaboration requires JSON support");
}
absl::Status NetworkCollaborationCoordinator::LeaveSession() {
return absl::UnimplementedError("Network collaboration requires JSON support");
}
absl::Status NetworkCollaborationCoordinator::SendMessage(
const std::string&, const std::string&) {
return absl::UnimplementedError("Network collaboration requires JSON support");
}
bool NetworkCollaborationCoordinator::IsConnected() const { return false; }
void NetworkCollaborationCoordinator::SetMessageCallback(MessageCallback) {}
void NetworkCollaborationCoordinator::SetParticipantCallback(ParticipantCallback) {}
void NetworkCollaborationCoordinator::SetErrorCallback(ErrorCallback) {}
void NetworkCollaborationCoordinator::ConnectWebSocket() {}
void NetworkCollaborationCoordinator::DisconnectWebSocket() {}
void NetworkCollaborationCoordinator::SendWebSocketMessage(const std::string&, const std::string&) {}
void NetworkCollaborationCoordinator::HandleWebSocketMessage(const std::string&) {}
void NetworkCollaborationCoordinator::WebSocketReceiveLoop() {}
#endif // YAZE_WITH_JSON
} // namespace editor
} // namespace yaze
#endif // YAZE_WITH_GRPC

View File

@@ -0,0 +1,99 @@
#ifndef YAZE_APP_EDITOR_SYSTEM_NETWORK_COLLABORATION_COORDINATOR_H_
#define YAZE_APP_EDITOR_SYSTEM_NETWORK_COLLABORATION_COORDINATOR_H_
#ifdef YAZE_WITH_GRPC // Reuse gRPC build flag for network features
#include <atomic>
#include <functional>
#include <memory>
#include <string>
#include <thread>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/synchronization/mutex.h"
namespace yaze {
namespace editor {
// Forward declarations to avoid including httplib in header
namespace detail {
class WebSocketClient;
}
// Coordinates network-based collaboration via WebSocket connections
class NetworkCollaborationCoordinator {
public:
struct SessionInfo {
std::string session_id;
std::string session_code;
std::string session_name;
std::vector<std::string> participants;
};
struct ChatMessage {
std::string sender;
std::string message;
int64_t timestamp;
};
// Callbacks for handling incoming events
using MessageCallback = std::function<void(const ChatMessage&)>;
using ParticipantCallback = std::function<void(const std::vector<std::string>&)>;
using ErrorCallback = std::function<void(const std::string&)>;
explicit NetworkCollaborationCoordinator(const std::string& server_url);
~NetworkCollaborationCoordinator();
// Session management
absl::StatusOr<SessionInfo> HostSession(const std::string& session_name,
const std::string& username);
absl::StatusOr<SessionInfo> JoinSession(const std::string& session_code,
const std::string& username);
absl::Status LeaveSession();
// Send chat message to current session
absl::Status SendMessage(const std::string& sender, const std::string& message);
// Connection status
bool IsConnected() const;
bool InSession() const { return in_session_; }
const std::string& session_code() const { return session_code_; }
const std::string& session_name() const { return session_name_; }
// Event callbacks
void SetMessageCallback(MessageCallback callback);
void SetParticipantCallback(ParticipantCallback callback);
void SetErrorCallback(ErrorCallback callback);
private:
void ConnectWebSocket();
void DisconnectWebSocket();
void SendWebSocketMessage(const std::string& type, const std::string& payload_json);
void HandleWebSocketMessage(const std::string& message);
void WebSocketReceiveLoop();
std::string server_url_;
std::string username_;
std::string session_id_;
std::string session_code_;
std::string session_name_;
bool in_session_ = false;
std::unique_ptr<detail::WebSocketClient> ws_client_;
std::atomic<bool> connected_{false};
std::atomic<bool> should_stop_{false};
std::unique_ptr<std::thread> receive_thread_;
mutable absl::Mutex mutex_;
MessageCallback message_callback_ ABSL_GUARDED_BY(mutex_);
ParticipantCallback participant_callback_ ABSL_GUARDED_BY(mutex_);
ErrorCallback error_callback_ ABSL_GUARDED_BY(mutex_);
};
} // namespace editor
} // namespace yaze
#endif // YAZE_WITH_GRPC
#endif // YAZE_APP_EDITOR_SYSTEM_NETWORK_COLLABORATION_COORDINATOR_H_