yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
z3ed_network_client.cc
Go to the documentation of this file.
2
3#include <chrono>
4#include <condition_variable>
5#include <mutex>
6#include <thread>
7
8#include "absl/strings/str_cat.h"
9#include "absl/strings/str_format.h"
10#include "absl/time/clock.h"
11#include "absl/time/time.h"
12
13#ifdef YAZE_WITH_JSON
14#include "nlohmann/json.hpp"
15#define CPPHTTPLIB_OPENSSL_SUPPORT
16#include "httplib.h"
17#endif
18
19// Undefine Windows macros that conflict with our method names
20// Must be outside the #ifdef so it applies to all code below
21#ifdef _WIN32
22#ifdef SendMessage
23#undef SendMessage
24#endif
25#endif
26
27namespace yaze {
28namespace cli {
29namespace net {
30
31#ifdef YAZE_WITH_JSON
32
33// Implementation using httplib for cross-platform WebSocket support
34class Z3edNetworkClient::Impl {
35 public:
36 Impl() : connected_(false), in_session_(false) {}
37
38 ~Impl() { Disconnect(); }
39
40 absl::Status Connect(const std::string& host, int port) {
41 std::lock_guard<std::mutex> lock(mutex_);
42
43 if (connected_) {
44 return absl::AlreadyExistsError("Already connected");
45 }
46
47 host_ = host;
48 port_ = port;
49
50 try {
51 // Create HTTP client for WebSocket fallback
52 client_ = std::make_unique<httplib::Client>(host, port);
53 client_->set_connection_timeout(5, 0);
54 client_->set_read_timeout(30, 0);
55
56 // Test connection
57 auto res = client_->Get("/health");
58 if (!res || res->status != 200) {
59 return absl::UnavailableError("Server not responding");
60 }
61
62 connected_ = true;
63 return absl::OkStatus();
64
65 } catch (const std::exception& e) {
66 return absl::UnavailableError(
67 absl::StrCat("Connection failed: ", e.what()));
68 }
69 }
70
71 void Disconnect() {
72 std::lock_guard<std::mutex> lock(mutex_);
73 connected_ = false;
74 in_session_ = false;
75 client_.reset();
76 }
77
78 absl::Status JoinSession(const std::string& session_code,
79 const std::string& username) {
80 std::lock_guard<std::mutex> lock(mutex_);
81
82 if (!connected_) {
83 return absl::FailedPreconditionError("Not connected");
84 }
85
86 try {
87 nlohmann::json message = {
88 {"type", "join_session"},
89 {"payload",
90 {{"session_code", session_code}, {"username", username}}}};
91
92 auto res = client_->Post("/message", message.dump(), "application/json");
93
94 if (!res || res->status != 200) {
95 return absl::InternalError("Failed to join session");
96 }
97
98 in_session_ = true;
99 session_code_ = session_code;
100 username_ = username;
101
102 return absl::OkStatus();
103
104 } catch (const std::exception& e) {
105 return absl::InternalError(absl::StrCat("Join failed: ", e.what()));
106 }
107 }
108
109 absl::Status SubmitProposal(const std::string& description,
110 const std::string& proposal_json,
111 const std::string& username) {
112 std::lock_guard<std::mutex> lock(mutex_);
113
114 if (!connected_ || !in_session_) {
115 return absl::FailedPreconditionError("Not in a session");
116 }
117
118 try {
119 nlohmann::json proposal_data = nlohmann::json::parse(proposal_json);
120 proposal_data["description"] = description;
121
122 nlohmann::json message = {
123 {"type", "proposal_share"},
124 {"payload",
125 {{"sender", username}, {"proposal_data", proposal_data}}}};
126
127 auto res = client_->Post("/message", message.dump(), "application/json");
128
129 if (!res || res->status != 200) {
130 return absl::InternalError("Failed to submit proposal");
131 }
132
133 // Extract proposal ID from response if available
134 if (!res->body.empty()) {
135 try {
136 auto response_json = nlohmann::json::parse(res->body);
137 if (response_json.contains("proposal_id")) {
138 last_proposal_id_ = response_json["proposal_id"];
139 }
140 } catch (...) {
141 // Response parsing failed, continue
142 }
143 }
144
145 return absl::OkStatus();
146
147 } catch (const std::exception& e) {
148 return absl::InternalError(
149 absl::StrCat("Proposal submission failed: ", e.what()));
150 }
151 }
152
153 absl::StatusOr<std::string> GetProposalStatus(
154 const std::string& proposal_id) {
155 std::lock_guard<std::mutex> lock(mutex_);
156
157 if (!connected_) {
158 return absl::FailedPreconditionError("Not connected");
159 }
160
161 try {
162 // Query server for proposal status
163 auto res = client_->Get(
164 absl::StrFormat("/proposal/%s/status", proposal_id).c_str());
165
166 if (!res || res->status != 200) {
167 return absl::NotFoundError("Proposal not found");
168 }
169
170 auto response = nlohmann::json::parse(res->body);
171 return response["status"].get<std::string>();
172
173 } catch (const std::exception& e) {
174 return absl::InternalError(
175 absl::StrCat("Status check failed: ", e.what()));
176 }
177 }
178
179 absl::StatusOr<bool> WaitForApproval(const std::string& proposal_id,
180 int timeout_seconds) {
181 auto deadline = absl::Now() + absl::Seconds(timeout_seconds);
182
183 while (absl::Now() < deadline) {
184 auto status_result = GetProposalStatus(proposal_id);
185
186 if (!status_result.ok()) {
187 return status_result.status();
188 }
189
190 std::string status = *status_result;
191
192 if (status == "approved" || status == "applied") {
193 return true;
194 } else if (status == "rejected") {
195 return false;
196 }
197
198 // Poll every second
199 absl::SleepFor(absl::Seconds(1));
200 }
201
202 return absl::DeadlineExceededError("Approval timeout");
203 }
204
205 absl::Status SendMessage(const std::string& message,
206 const std::string& sender) {
207 std::lock_guard<std::mutex> lock(mutex_);
208
209 if (!connected_ || !in_session_) {
210 return absl::FailedPreconditionError("Not in a session");
211 }
212
213 try {
214 nlohmann::json msg = {
215 {"type", "chat_message"},
216 {"payload", {{"message", message}, {"sender", sender}}}};
217
218 auto res = client_->Post("/message", msg.dump(), "application/json");
219
220 if (!res || res->status != 200) {
221 return absl::InternalError("Failed to send message");
222 }
223
224 return absl::OkStatus();
225
226 } catch (const std::exception& e) {
227 return absl::InternalError(absl::StrCat("Send failed: ", e.what()));
228 }
229 }
230
231 absl::StatusOr<std::string> QueryAI(const std::string& query,
232 const std::string& username) {
233 std::lock_guard<std::mutex> lock(mutex_);
234
235 if (!connected_ || !in_session_) {
236 return absl::FailedPreconditionError("Not in a session");
237 }
238
239 try {
240 nlohmann::json message = {
241 {"type", "ai_query"},
242 {"payload", {{"query", query}, {"username", username}}}};
243
244 auto res = client_->Post("/message", message.dump(), "application/json");
245
246 if (!res || res->status != 200) {
247 return absl::InternalError("AI query failed");
248 }
249
250 // Wait for response (in a real implementation, this would use callbacks)
251 // For now, return placeholder
252 return std::string("AI agent endpoint not configured");
253
254 } catch (const std::exception& e) {
255 return absl::InternalError(absl::StrCat("AI query failed: ", e.what()));
256 }
257 }
258
259 bool IsConnected() const {
260 std::lock_guard<std::mutex> lock(mutex_);
261 return connected_;
262 }
263
264 std::string GetLastProposalId() const {
265 std::lock_guard<std::mutex> lock(mutex_);
266 return last_proposal_id_;
267 }
268
269 private:
270 mutable std::mutex mutex_;
271 std::unique_ptr<httplib::Client> client_;
272
273 std::string host_;
274 int port_;
275 bool connected_;
276 bool in_session_;
277
278 std::string session_code_;
279 std::string username_;
280 std::string last_proposal_id_;
281};
282
283#else
284
285// Stub implementation when JSON is not available
287 public:
288 absl::Status Connect(const std::string&, int) {
289 return absl::UnimplementedError("Network support requires JSON library");
290 }
291 void Disconnect() {}
292 absl::Status JoinSession(const std::string&, const std::string&) {
293 return absl::UnimplementedError("Network support requires JSON library");
294 }
295 absl::Status SubmitProposal(const std::string&, const std::string&,
296 const std::string&) {
297 return absl::UnimplementedError("Network support requires JSON library");
298 }
299 absl::StatusOr<std::string> GetProposalStatus(const std::string&) {
300 return absl::UnimplementedError("Network support requires JSON library");
301 }
302 absl::StatusOr<bool> WaitForApproval(const std::string&, int) {
303 return absl::UnimplementedError("Network support requires JSON library");
304 }
305 absl::Status SendMessage(const std::string&, const std::string&) {
306 return absl::UnimplementedError("Network support requires JSON library");
307 }
308 absl::StatusOr<std::string> QueryAI(const std::string&, const std::string&) {
309 return absl::UnimplementedError("Network support requires JSON library");
310 }
311 bool IsConnected() const { return false; }
312 std::string GetLastProposalId() const { return ""; }
313};
314
315#endif // YAZE_WITH_JSON
316
317// ============================================================================
318// Z3edNetworkClient Implementation
319// ============================================================================
320
321Z3edNetworkClient::Z3edNetworkClient() : impl_(std::make_unique<Impl>()) {}
322
324
325absl::Status Z3edNetworkClient::Connect(const std::string& host, int port) {
326 return impl_->Connect(host, port);
327}
328
329absl::Status Z3edNetworkClient::JoinSession(const std::string& session_code,
330 const std::string& username) {
331 return impl_->JoinSession(session_code, username);
332}
333
334absl::Status Z3edNetworkClient::SubmitProposal(const std::string& description,
335 const std::string& proposal_json,
336 const std::string& username) {
337 return impl_->SubmitProposal(description, proposal_json, username);
338}
339
340absl::StatusOr<std::string> Z3edNetworkClient::GetProposalStatus(
341 const std::string& proposal_id) {
342 return impl_->GetProposalStatus(proposal_id);
343}
344
346 const std::string& proposal_id, int timeout_seconds) {
347 return impl_->WaitForApproval(proposal_id, timeout_seconds);
348}
349
350absl::Status Z3edNetworkClient::SendMessage(const std::string& message,
351 const std::string& sender) {
352 return impl_->SendMessage(message, sender);
353}
354
355absl::StatusOr<std::string> Z3edNetworkClient::QueryAI(
356 const std::string& query, const std::string& username) {
357 return impl_->QueryAI(query, username);
358}
359
361 impl_->Disconnect();
362}
363
365 return impl_->IsConnected();
366}
367
368} // namespace net
369} // namespace cli
370} // namespace yaze
absl::Status SendMessage(const std::string &, const std::string &)
absl::Status Connect(const std::string &, int)
absl::StatusOr< bool > WaitForApproval(const std::string &, int)
absl::Status JoinSession(const std::string &, const std::string &)
absl::StatusOr< std::string > GetProposalStatus(const std::string &)
absl::Status SubmitProposal(const std::string &, const std::string &, const std::string &)
absl::StatusOr< std::string > QueryAI(const std::string &, const std::string &)
absl::StatusOr< bool > WaitForApproval(const std::string &proposal_id, int timeout_seconds=60)
absl::Status SendMessage(const std::string &message, const std::string &sender)
absl::Status JoinSession(const std::string &session_code, const std::string &username)
absl::StatusOr< std::string > QueryAI(const std::string &query, const std::string &username)
absl::Status Connect(const std::string &host, int port=8765)
absl::StatusOr< std::string > GetProposalStatus(const std::string &proposal_id)
absl::Status SubmitProposal(const std::string &description, const std::string &proposal_json, const std::string &username)