4#include <condition_variable>
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"
14#include "nlohmann/json.hpp"
15#if !defined(_WIN32) && !defined(YAZE_IOS) && !defined(__EMSCRIPTEN__)
16#define CPPHTTPLIB_OPENSSL_SUPPORT
20#if defined(YAZE_WITH_JSON) && !defined(__EMSCRIPTEN__)
36#if defined(YAZE_WITH_JSON) && !defined(__EMSCRIPTEN__)
39class Z3edNetworkClient::Impl {
41 Impl() : connected_(false), in_session_(false) {}
45 absl::Status
Connect(
const std::string& host,
int port) {
46 std::lock_guard<std::mutex> lock(mutex_);
49 return absl::AlreadyExistsError(
"Already connected");
57 client_ = std::make_unique<httplib::Client>(host, port);
58 client_->set_connection_timeout(5, 0);
59 client_->set_read_timeout(30, 0);
62 auto res = client_->Get(
"/health");
63 if (!res || res->status != 200) {
64 return absl::UnavailableError(
"Server not responding");
68 return absl::OkStatus();
70 }
catch (
const std::exception& e) {
71 return absl::UnavailableError(
72 absl::StrCat(
"Connection failed: ", e.what()));
77 std::lock_guard<std::mutex> lock(mutex_);
83 absl::Status
JoinSession(
const std::string& session_code,
84 const std::string& username) {
85 std::lock_guard<std::mutex> lock(mutex_);
88 return absl::FailedPreconditionError(
"Not connected");
92 nlohmann::json message = {
93 {
"type",
"join_session"},
95 {{
"session_code", session_code}, {
"username", username}}}};
97 auto res = client_->Post(
"/message", message.dump(),
"application/json");
99 if (!res || res->status != 200) {
100 return absl::InternalError(
"Failed to join session");
104 session_code_ = session_code;
105 username_ = username;
107 return absl::OkStatus();
109 }
catch (
const std::exception& e) {
110 return absl::InternalError(absl::StrCat(
"Join failed: ", e.what()));
115 const std::string& proposal_json,
116 const std::string& username) {
117 std::lock_guard<std::mutex> lock(mutex_);
119 if (!connected_ || !in_session_) {
120 return absl::FailedPreconditionError(
"Not in a session");
124 nlohmann::json proposal_data = nlohmann::json::parse(proposal_json);
127 nlohmann::json message = {
128 {
"type",
"proposal_share"},
130 {{
"sender", username}, {
"proposal_data", proposal_data}}}};
132 auto res = client_->Post(
"/message", message.dump(),
"application/json");
134 if (!res || res->status != 200) {
135 return absl::InternalError(
"Failed to submit proposal");
139 if (!res->body.empty()) {
141 auto response_json = nlohmann::json::parse(res->body);
142 if (response_json.contains(
"proposal_id")) {
143 last_proposal_id_ = response_json[
"proposal_id"];
150 return absl::OkStatus();
152 }
catch (
const std::exception& e) {
153 return absl::InternalError(
154 absl::StrCat(
"Proposal submission failed: ", e.what()));
159 const std::string& proposal_id) {
160 std::lock_guard<std::mutex> lock(mutex_);
163 return absl::FailedPreconditionError(
"Not connected");
168 auto res = client_->Get(
169 absl::StrFormat(
"/proposal/%s/status", proposal_id).c_str());
171 if (!res || res->status != 200) {
172 return absl::NotFoundError(
"Proposal not found");
175 auto response = nlohmann::json::parse(res->body);
176 return response[
"status"].get<std::string>();
178 }
catch (
const std::exception& e) {
179 return absl::InternalError(
180 absl::StrCat(
"Status check failed: ", e.what()));
185 int timeout_seconds) {
186 auto deadline = absl::Now() + absl::Seconds(timeout_seconds);
188 while (absl::Now() < deadline) {
191 if (!status_result.ok()) {
192 return status_result.status();
195 std::string status = *status_result;
197 if (status ==
"approved" || status ==
"applied") {
199 }
else if (status ==
"rejected") {
204 absl::SleepFor(absl::Seconds(1));
207 return absl::DeadlineExceededError(
"Approval timeout");
210 absl::Status
SendMessage(
const std::string& message,
211 const std::string& sender) {
212 std::lock_guard<std::mutex> lock(mutex_);
214 if (!connected_ || !in_session_) {
215 return absl::FailedPreconditionError(
"Not in a session");
219 nlohmann::json msg = {
220 {
"type",
"chat_message"},
221 {
"payload", {{
"message", message}, {
"sender", sender}}}};
223 auto res = client_->Post(
"/message", msg.dump(),
"application/json");
225 if (!res || res->status != 200) {
226 return absl::InternalError(
"Failed to send message");
229 return absl::OkStatus();
231 }
catch (
const std::exception& e) {
232 return absl::InternalError(absl::StrCat(
"Send failed: ", e.what()));
236 absl::StatusOr<std::string>
QueryAI(
const std::string& query,
237 const std::string& username) {
238 std::lock_guard<std::mutex> lock(mutex_);
240 if (!connected_ || !in_session_) {
241 return absl::FailedPreconditionError(
"Not in a session");
245 nlohmann::json message = {
246 {
"type",
"ai_query"},
247 {
"payload", {{
"query", query}, {
"username", username}}}};
249 auto res = client_->Post(
"/message", message.dump(),
"application/json");
251 if (!res || res->status != 200) {
252 return absl::InternalError(
"AI query failed");
257 return std::string(
"AI agent endpoint not configured");
259 }
catch (
const std::exception& e) {
260 return absl::InternalError(absl::StrCat(
"AI query failed: ", e.what()));
265 std::lock_guard<std::mutex> lock(mutex_);
270 std::lock_guard<std::mutex> lock(mutex_);
271 return last_proposal_id_;
275 mutable std::mutex mutex_;
276 std::unique_ptr<httplib::Client> client_;
283 std::string session_code_;
284 std::string username_;
285 std::string last_proposal_id_;
294#if defined(__EMSCRIPTEN__)
295 "Network support is not available in WASM builds";
297 "Network support requires JSON library";
300 absl::Status
Connect(
const std::string&,
int) {
304 absl::Status
JoinSession(
const std::string&,
const std::string&) {
308 const std::string&) {
317 absl::Status
SendMessage(
const std::string&,
const std::string&) {
320 absl::StatusOr<std::string>
QueryAI(
const std::string&,
const std::string&) {
338 return impl_->Connect(host, port);
342 const std::string& username) {
343 return impl_->JoinSession(session_code, username);
347 const std::string& proposal_json,
348 const std::string& username) {
349 return impl_->SubmitProposal(description, proposal_json, username);
353 const std::string& proposal_id) {
354 return impl_->GetProposalStatus(proposal_id);
358 const std::string& proposal_id,
int timeout_seconds) {
359 return impl_->WaitForApproval(proposal_id, timeout_seconds);
363 const std::string& sender) {
364 return impl_->SendMessage(message, sender);
368 const std::string& query,
const std::string& username) {
369 return impl_->QueryAI(query, username);
377 return impl_->IsConnected();
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 &)
static constexpr const char * kUnavailableMessage
absl::Status SubmitProposal(const std::string &, const std::string &, const std::string &)
absl::StatusOr< std::string > QueryAI(const std::string &, const std::string &)
std::string GetLastProposalId() const
absl::StatusOr< bool > WaitForApproval(const std::string &proposal_id, int timeout_seconds=60)
absl::Status SendMessage(const std::string &message, const std::string &sender)
std::unique_ptr< Impl > impl_
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)