|
@@ -647,6 +647,9 @@ void HttpServer::SetupRoutes() {
|
|
|
// Setup page routes (page builder)
|
|
// Setup page routes (page builder)
|
|
|
SetupPageRoutes();
|
|
SetupPageRoutes();
|
|
|
|
|
|
|
|
|
|
+ // Setup LLM routes (AI assistant)
|
|
|
|
|
+ SetupLlmRoutes();
|
|
|
|
|
+
|
|
|
// Setup collection routes (before workspace routes since they're more specific)
|
|
// Setup collection routes (before workspace routes since they're more specific)
|
|
|
SetupCollectionRoutes();
|
|
SetupCollectionRoutes();
|
|
|
|
|
|
|
@@ -5566,4 +5569,1409 @@ void HttpServer::HandleDeletePage(const httplib::Request& req, httplib::Response
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// ============================================================================
|
|
|
|
|
+// LLM Routes
|
|
|
|
|
+// ============================================================================
|
|
|
|
|
+
|
|
|
|
|
+void HttpServer::SetupLlmRoutes() {
|
|
|
|
|
+ // Session management
|
|
|
|
|
+ httpServer_->Post("/api/llm/sessions",
|
|
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ HandleLlmCreateSession(req, res);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ httpServer_->Get("/api/llm/sessions",
|
|
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ HandleLlmListSessions(req, res);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ httpServer_->Get(R"(/api/llm/sessions/([^/]+))",
|
|
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ HandleLlmGetSession(req, res);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ httpServer_->Delete(R"(/api/llm/sessions/([^/]+))",
|
|
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ HandleLlmDeleteSession(req, res);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ httpServer_->Post(R"(/api/llm/sessions/([^/]+)/messages)",
|
|
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ HandleLlmSendMessage(req, res);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ httpServer_->Post(R"(/api/llm/sessions/([^/]+)/stream)",
|
|
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ HandleLlmStreamMessage(req, res);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ httpServer_->Post(R"(/api/llm/sessions/([^/]+)/clear)",
|
|
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ HandleLlmClearMessages(req, res);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Models and providers (read-only)
|
|
|
|
|
+ httpServer_->Get("/api/llm/providers",
|
|
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ HandleLlmListProviders(req, res);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ httpServer_->Get("/api/llm/models",
|
|
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ HandleLlmListModels(req, res);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ httpServer_->Post("/api/llm/models/refresh",
|
|
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ HandleLlmRefreshModels(req, res);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ httpServer_->Get(R"(/api/llm/providers/([^/]+)/health)",
|
|
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ HandleLlmGetProviderHealth(req, res);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Admin routes for provider configuration
|
|
|
|
|
+ httpServer_->Get("/api/llm/admin/providers",
|
|
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ HandleLlmAdminListProviders(req, res);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ httpServer_->Post("/api/llm/admin/providers",
|
|
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ HandleLlmAdminCreateProvider(req, res);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ httpServer_->Put(R"(/api/llm/admin/providers/([^/]+))",
|
|
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ HandleLlmAdminUpdateProvider(req, res);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ httpServer_->Delete(R"(/api/llm/admin/providers/([^/]+))",
|
|
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ HandleLlmAdminDeleteProvider(req, res);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Workspace BYOK routes
|
|
|
|
|
+ httpServer_->Put(R"(/api/llm/workspaces/([^/]+)/key)",
|
|
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ HandleLlmSetWorkspaceKey(req, res);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ httpServer_->Get(R"(/api/llm/workspaces/([^/]+)/key)",
|
|
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ HandleLlmGetWorkspaceKey(req, res);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ httpServer_->Delete(R"(/api/llm/workspaces/([^/]+)/key)",
|
|
|
|
|
+ [this](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ HandleLlmDeleteWorkspaceKey(req, res);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ spdlog::info("LLM routes configured");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void HttpServer::HandleLlmCreateSession(const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
|
|
+ if (!auth_user) {
|
|
|
|
|
+ res.status = 401;
|
|
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Check LLM chat permission
|
|
|
|
|
+ if (!authorizationService_->HasPermission(*auth_user, permissions::kLlmChat)) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - no LLM chat access"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!llmClient_ || !llmClient_->IsConnected()) {
|
|
|
|
|
+ res.status = 503;
|
|
|
|
|
+ res.set_content(R"({"error":"LLM service not available"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json body;
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (!req.body.empty()) {
|
|
|
|
|
+ body = nlohmann::json::parse(req.body);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (const nlohmann::json::parse_error&) {
|
|
|
|
|
+ res.status = 400;
|
|
|
|
|
+ res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ ::smartbotic::llm::CreateSessionRequest request;
|
|
|
|
|
+ request.set_user_id(auth_user->user_id);
|
|
|
|
|
+
|
|
|
|
|
+ if (body.contains("workspace_id") && body["workspace_id"].is_string()) {
|
|
|
|
|
+ request.set_workspace_id(body["workspace_id"].get<std::string>());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (body.contains("title") && body["title"].is_string()) {
|
|
|
|
|
+ request.set_title(body["title"].get<std::string>());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (body.contains("model_id") && body["model_id"].is_string()) {
|
|
|
|
|
+ request.set_model_id(body["model_id"].get<std::string>());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (body.contains("system_prompt") && body["system_prompt"].is_string()) {
|
|
|
|
|
+ request.set_system_prompt(body["system_prompt"].get<std::string>());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ auto [status, session] = llmClient_->CreateSession(request);
|
|
|
|
|
+
|
|
|
|
|
+ if (!status.ok()) {
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ nlohmann::json error = {{"error", status.error_message()}};
|
|
|
|
|
+ res.set_content(error.dump(), "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json response;
|
|
|
|
|
+ response["id"] = session.id();
|
|
|
|
|
+ response["user_id"] = session.user_id();
|
|
|
|
|
+ response["workspace_id"] = session.workspace_id();
|
|
|
|
|
+ response["title"] = session.title();
|
|
|
|
|
+ response["model_id"] = session.model_id();
|
|
|
|
|
+ response["system_prompt"] = session.system_prompt();
|
|
|
|
|
+ response["created_at"] = session.created_at().seconds();
|
|
|
|
|
+ response["updated_at"] = session.updated_at().seconds();
|
|
|
|
|
+
|
|
|
|
|
+ res.set_content(response.dump(), "application/json");
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::error("LLM create session error: {}", e.what());
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void HttpServer::HandleLlmListSessions(const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
|
|
+ if (!auth_user) {
|
|
|
|
|
+ res.status = 401;
|
|
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!authorizationService_->HasPermission(*auth_user, permissions::kLlmChat)) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - no LLM chat access"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!llmClient_ || !llmClient_->IsConnected()) {
|
|
|
|
|
+ res.status = 503;
|
|
|
|
|
+ res.set_content(R"({"error":"LLM service not available"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ ::smartbotic::llm::ListSessionsRequest request;
|
|
|
|
|
+ request.set_user_id(auth_user->user_id);
|
|
|
|
|
+
|
|
|
|
|
+ if (req.has_param("workspace_id")) {
|
|
|
|
|
+ request.set_workspace_id(req.get_param_value("workspace_id"));
|
|
|
|
|
+ }
|
|
|
|
|
+ if (req.has_param("limit")) {
|
|
|
|
|
+ request.set_page_size(std::stoi(req.get_param_value("limit")));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ auto [status, list_response] = llmClient_->ListSessions(request);
|
|
|
|
|
+
|
|
|
|
|
+ if (!status.ok()) {
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ nlohmann::json error = {{"error", status.error_message()}};
|
|
|
|
|
+ res.set_content(error.dump(), "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json sessions_array = nlohmann::json::array();
|
|
|
|
|
+ for (const auto& session : list_response.sessions()) {
|
|
|
|
|
+ nlohmann::json s;
|
|
|
|
|
+ s["id"] = session.id();
|
|
|
|
|
+ s["user_id"] = session.user_id();
|
|
|
|
|
+ s["workspace_id"] = session.workspace_id();
|
|
|
|
|
+ s["title"] = session.title();
|
|
|
|
|
+ s["model_id"] = session.model_id();
|
|
|
|
|
+ s["message_count"] = session.message_count();
|
|
|
|
|
+ s["created_at"] = session.created_at().seconds();
|
|
|
|
|
+ s["updated_at"] = session.updated_at().seconds();
|
|
|
|
|
+ sessions_array.push_back(s);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json response;
|
|
|
|
|
+ response["sessions"] = sessions_array;
|
|
|
|
|
+ response["total"] = list_response.total_count();
|
|
|
|
|
+
|
|
|
|
|
+ res.set_content(response.dump(), "application/json");
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::error("LLM list sessions error: {}", e.what());
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void HttpServer::HandleLlmGetSession(const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
|
|
+ if (!auth_user) {
|
|
|
|
|
+ res.status = 401;
|
|
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!authorizationService_->HasPermission(*auth_user, permissions::kLlmChat)) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - no LLM chat access"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!llmClient_ || !llmClient_->IsConnected()) {
|
|
|
|
|
+ res.status = 503;
|
|
|
|
|
+ res.set_content(R"({"error":"LLM service not available"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string session_id = req.matches[1].str();
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ ::smartbotic::llm::GetSessionRequest request;
|
|
|
|
|
+ request.set_session_id(session_id);
|
|
|
|
|
+ request.set_include_messages(true);
|
|
|
|
|
+
|
|
|
|
|
+ auto [status, session] = llmClient_->GetSession(request);
|
|
|
|
|
+
|
|
|
|
|
+ if (!status.ok()) {
|
|
|
|
|
+ if (status.error_code() == grpc::StatusCode::NOT_FOUND) {
|
|
|
|
|
+ res.status = 404;
|
|
|
|
|
+ res.set_content(R"({"error":"Session not found"})", "application/json");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ nlohmann::json error = {{"error", status.error_message()}};
|
|
|
|
|
+ res.set_content(error.dump(), "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Verify ownership
|
|
|
|
|
+ if (session.user_id() != auth_user->user_id) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - not your session"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json response;
|
|
|
|
|
+ response["id"] = session.id();
|
|
|
|
|
+ response["user_id"] = session.user_id();
|
|
|
|
|
+ response["workspace_id"] = session.workspace_id();
|
|
|
|
|
+ response["title"] = session.title();
|
|
|
|
|
+ response["model_id"] = session.model_id();
|
|
|
|
|
+ response["system_prompt"] = session.system_prompt();
|
|
|
|
|
+ response["created_at"] = session.created_at().seconds();
|
|
|
|
|
+ response["updated_at"] = session.updated_at().seconds();
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json messages_array = nlohmann::json::array();
|
|
|
|
|
+ for (const auto& msg : session.messages()) {
|
|
|
|
|
+ nlohmann::json m;
|
|
|
|
|
+ m["id"] = msg.id();
|
|
|
|
|
+ m["role"] = ::smartbotic::llm::MessageRole_Name(msg.role());
|
|
|
|
|
+
|
|
|
|
|
+ // Extract text content from message parts
|
|
|
|
|
+ std::string msg_text;
|
|
|
|
|
+ for (const auto& part : msg.content()) {
|
|
|
|
|
+ if (part.has_text()) {
|
|
|
|
|
+ msg_text += part.text();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ m["content"] = msg_text;
|
|
|
|
|
+ m["created_at"] = msg.created_at().seconds();
|
|
|
|
|
+
|
|
|
|
|
+ if (msg.has_page_context()) {
|
|
|
|
|
+ m["page_context"] = {
|
|
|
|
|
+ {"path", msg.page_context().path()},
|
|
|
|
|
+ {"title", msg.page_context().title()},
|
|
|
|
|
+ {"workspace_id", msg.page_context().workspace_id()},
|
|
|
|
|
+ {"collection", msg.page_context().collection()},
|
|
|
|
|
+ {"view_id", msg.page_context().view_id()}
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ messages_array.push_back(m);
|
|
|
|
|
+ }
|
|
|
|
|
+ response["messages"] = messages_array;
|
|
|
|
|
+
|
|
|
|
|
+ res.set_content(response.dump(), "application/json");
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::error("LLM get session error: {}", e.what());
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void HttpServer::HandleLlmDeleteSession(const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
|
|
+ if (!auth_user) {
|
|
|
|
|
+ res.status = 401;
|
|
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!authorizationService_->HasPermission(*auth_user, permissions::kLlmChat)) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - no LLM chat access"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!llmClient_ || !llmClient_->IsConnected()) {
|
|
|
|
|
+ res.status = 503;
|
|
|
|
|
+ res.set_content(R"({"error":"LLM service not available"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string session_id = req.matches[1].str();
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // First get session to verify ownership
|
|
|
|
|
+ ::smartbotic::llm::GetSessionRequest get_request;
|
|
|
|
|
+ get_request.set_session_id(session_id);
|
|
|
|
|
+
|
|
|
|
|
+ auto [get_status, session] = llmClient_->GetSession(get_request);
|
|
|
|
|
+ if (!get_status.ok()) {
|
|
|
|
|
+ res.status = 404;
|
|
|
|
|
+ res.set_content(R"({"error":"Session not found"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (session.user_id() != auth_user->user_id) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - not your session"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ::smartbotic::llm::DeleteSessionRequest request;
|
|
|
|
|
+ request.set_session_id(session_id);
|
|
|
|
|
+
|
|
|
|
|
+ auto [status, delete_response] = llmClient_->DeleteSession(request);
|
|
|
|
|
+
|
|
|
|
|
+ if (!status.ok()) {
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ nlohmann::json error = {{"error", status.error_message()}};
|
|
|
|
|
+ res.set_content(error.dump(), "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ res.set_content(R"({"message":"Session deleted successfully"})", "application/json");
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::error("LLM delete session error: {}", e.what());
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void HttpServer::HandleLlmSendMessage(const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
|
|
+ if (!auth_user) {
|
|
|
|
|
+ res.status = 401;
|
|
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!authorizationService_->HasPermission(*auth_user, permissions::kLlmChat)) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - no LLM chat access"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!llmClient_ || !llmClient_->IsConnected()) {
|
|
|
|
|
+ res.status = 503;
|
|
|
|
|
+ res.set_content(R"({"error":"LLM service not available"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string session_id = req.matches[1].str();
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json body;
|
|
|
|
|
+ try {
|
|
|
|
|
+ body = nlohmann::json::parse(req.body);
|
|
|
|
|
+ } catch (const nlohmann::json::parse_error&) {
|
|
|
|
|
+ res.status = 400;
|
|
|
|
|
+ res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!body.contains("content") || !body["content"].is_string()) {
|
|
|
|
|
+ res.status = 400;
|
|
|
|
|
+ res.set_content(R"({"error":"content is required"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Verify session ownership first
|
|
|
|
|
+ ::smartbotic::llm::GetSessionRequest get_request;
|
|
|
|
|
+ get_request.set_session_id(session_id);
|
|
|
|
|
+
|
|
|
|
|
+ auto [get_status, session] = llmClient_->GetSession(get_request);
|
|
|
|
|
+ if (!get_status.ok()) {
|
|
|
|
|
+ res.status = 404;
|
|
|
|
|
+ res.set_content(R"({"error":"Session not found"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (session.user_id() != auth_user->user_id) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - not your session"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ::smartbotic::llm::ChatRequest request;
|
|
|
|
|
+ request.set_session_id(session_id);
|
|
|
|
|
+ request.set_user_id(auth_user->user_id);
|
|
|
|
|
+ auto* content_part = request.add_content();
|
|
|
|
|
+ content_part->set_text(body["content"].get<std::string>());
|
|
|
|
|
+
|
|
|
|
|
+ if (body.contains("page_context") && body["page_context"].is_object()) {
|
|
|
|
|
+ auto* ctx = request.mutable_page_context();
|
|
|
|
|
+ const auto& pc = body["page_context"];
|
|
|
|
|
+ if (pc.contains("path")) ctx->set_path(pc["path"].get<std::string>());
|
|
|
|
|
+ if (pc.contains("title")) ctx->set_title(pc["title"].get<std::string>());
|
|
|
|
|
+ if (pc.contains("workspace_id")) ctx->set_workspace_id(pc["workspace_id"].get<std::string>());
|
|
|
|
|
+ if (pc.contains("collection")) ctx->set_collection(pc["collection"].get<std::string>());
|
|
|
|
|
+ if (pc.contains("view_id")) ctx->set_view_id(pc["view_id"].get<std::string>());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ auto [status, chat_response] = llmClient_->Chat(request);
|
|
|
|
|
+
|
|
|
|
|
+ if (!status.ok()) {
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ nlohmann::json error = {{"error", status.error_message()}};
|
|
|
|
|
+ res.set_content(error.dump(), "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json response;
|
|
|
|
|
+ response["session_id"] = chat_response.session_id();
|
|
|
|
|
+ response["message_id"] = chat_response.assistant_message().id();
|
|
|
|
|
+
|
|
|
|
|
+ // Extract text content from assistant message
|
|
|
|
|
+ std::string content_text;
|
|
|
|
|
+ for (const auto& part : chat_response.assistant_message().content()) {
|
|
|
|
|
+ if (part.has_text()) {
|
|
|
|
|
+ content_text += part.text();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ response["content"] = content_text;
|
|
|
|
|
+ response["finish_reason"] = static_cast<int>(chat_response.finish_reason());
|
|
|
|
|
+
|
|
|
|
|
+ if (chat_response.tool_calls_size() > 0) {
|
|
|
|
|
+ nlohmann::json tool_calls = nlohmann::json::array();
|
|
|
|
|
+ for (const auto& tc : chat_response.tool_calls()) {
|
|
|
|
|
+ tool_calls.push_back({
|
|
|
|
|
+ {"id", tc.id()},
|
|
|
|
|
+ {"name", tc.name()},
|
|
|
|
|
+ {"arguments", tc.arguments()}
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ response["tool_calls"] = tool_calls;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ response["usage"] = {
|
|
|
|
|
+ {"prompt_tokens", chat_response.usage().prompt_tokens()},
|
|
|
|
|
+ {"completion_tokens", chat_response.usage().completion_tokens()},
|
|
|
|
|
+ {"total_tokens", chat_response.usage().total_tokens()}
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ res.set_content(response.dump(), "application/json");
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::error("LLM send message error: {}", e.what());
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void HttpServer::HandleLlmStreamMessage(const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
|
|
+ if (!auth_user) {
|
|
|
|
|
+ res.status = 401;
|
|
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!authorizationService_->HasPermission(*auth_user, permissions::kLlmChat)) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - no LLM chat access"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!llmClient_ || !llmClient_->IsConnected()) {
|
|
|
|
|
+ res.status = 503;
|
|
|
|
|
+ res.set_content(R"({"error":"LLM service not available"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string session_id = req.matches[1].str();
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json body;
|
|
|
|
|
+ try {
|
|
|
|
|
+ body = nlohmann::json::parse(req.body);
|
|
|
|
|
+ } catch (const nlohmann::json::parse_error&) {
|
|
|
|
|
+ res.status = 400;
|
|
|
|
|
+ res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!body.contains("content") || !body["content"].is_string()) {
|
|
|
|
|
+ res.status = 400;
|
|
|
|
|
+ res.set_content(R"({"error":"content is required"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Verify session ownership first
|
|
|
|
|
+ ::smartbotic::llm::GetSessionRequest get_request;
|
|
|
|
|
+ get_request.set_session_id(session_id);
|
|
|
|
|
+
|
|
|
|
|
+ auto [get_status, session] = llmClient_->GetSession(get_request);
|
|
|
|
|
+ if (!get_status.ok()) {
|
|
|
|
|
+ res.status = 404;
|
|
|
|
|
+ res.set_content(R"({"error":"Session not found"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (session.user_id() != auth_user->user_id) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - not your session"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ::smartbotic::llm::ChatRequest request;
|
|
|
|
|
+ request.set_session_id(session_id);
|
|
|
|
|
+ request.set_user_id(auth_user->user_id);
|
|
|
|
|
+ auto* stream_content_part = request.add_content();
|
|
|
|
|
+ stream_content_part->set_text(body["content"].get<std::string>());
|
|
|
|
|
+
|
|
|
|
|
+ if (body.contains("page_context") && body["page_context"].is_object()) {
|
|
|
|
|
+ auto* ctx = request.mutable_page_context();
|
|
|
|
|
+ const auto& pc = body["page_context"];
|
|
|
|
|
+ if (pc.contains("path")) ctx->set_path(pc["path"].get<std::string>());
|
|
|
|
|
+ if (pc.contains("title")) ctx->set_title(pc["title"].get<std::string>());
|
|
|
|
|
+ if (pc.contains("workspace_id")) ctx->set_workspace_id(pc["workspace_id"].get<std::string>());
|
|
|
|
|
+ if (pc.contains("collection")) ctx->set_collection(pc["collection"].get<std::string>());
|
|
|
|
|
+ if (pc.contains("view_id")) ctx->set_view_id(pc["view_id"].get<std::string>());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Set up SSE response
|
|
|
|
|
+ res.set_header("Content-Type", "text/event-stream");
|
|
|
|
|
+ res.set_header("Cache-Control", "no-cache");
|
|
|
|
|
+ res.set_header("Connection", "keep-alive");
|
|
|
|
|
+
|
|
|
|
|
+ std::string sse_content;
|
|
|
|
|
+
|
|
|
|
|
+ auto stream_status = llmClient_->ChatStream(request,
|
|
|
|
|
+ [&sse_content](const ::smartbotic::llm::ChatStreamChunk& chunk) -> bool {
|
|
|
|
|
+ nlohmann::json event;
|
|
|
|
|
+ event["session_id"] = chunk.session_id();
|
|
|
|
|
+
|
|
|
|
|
+ // ChatStreamChunk uses oneof for content_delta, tool_call, or metadata
|
|
|
|
|
+ if (chunk.has_content_delta()) {
|
|
|
|
|
+ event["delta"] = chunk.content_delta();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (chunk.has_tool_call()) {
|
|
|
|
|
+ event["tool_call"] = {
|
|
|
|
|
+ {"id", chunk.tool_call().id()},
|
|
|
|
|
+ {"name", chunk.tool_call().name()},
|
|
|
|
|
+ {"arguments", chunk.tool_call().arguments()}
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ if (chunk.has_metadata()) {
|
|
|
|
|
+ const auto& meta = chunk.metadata();
|
|
|
|
|
+ event["finish_reason"] = static_cast<int>(meta.finish_reason());
|
|
|
|
|
+ event["message_id"] = meta.assistant_message().id();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ sse_content += "data: " + event.dump() + "\n\n";
|
|
|
|
|
+ return true;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (!stream_status.ok()) {
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ nlohmann::json error = {{"error", stream_status.error_message()}};
|
|
|
|
|
+ res.set_content(error.dump(), "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Send final done event
|
|
|
|
|
+ sse_content += "data: [DONE]\n\n";
|
|
|
|
|
+ res.set_content(sse_content, "text/event-stream");
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::error("LLM stream message error: {}", e.what());
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void HttpServer::HandleLlmClearMessages(const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
|
|
+ if (!auth_user) {
|
|
|
|
|
+ res.status = 401;
|
|
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!authorizationService_->HasPermission(*auth_user, permissions::kLlmChat)) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - no LLM chat access"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!llmClient_ || !llmClient_->IsConnected()) {
|
|
|
|
|
+ res.status = 503;
|
|
|
|
|
+ res.set_content(R"({"error":"LLM service not available"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string session_id = req.matches[1].str();
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Verify session ownership first
|
|
|
|
|
+ ::smartbotic::llm::GetSessionRequest get_request;
|
|
|
|
|
+ get_request.set_session_id(session_id);
|
|
|
|
|
+
|
|
|
|
|
+ auto [get_status, session] = llmClient_->GetSession(get_request);
|
|
|
|
|
+ if (!get_status.ok()) {
|
|
|
|
|
+ res.status = 404;
|
|
|
|
|
+ res.set_content(R"({"error":"Session not found"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (session.user_id() != auth_user->user_id) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - not your session"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ::smartbotic::llm::ClearSessionMessagesRequest request;
|
|
|
|
|
+ request.set_session_id(session_id);
|
|
|
|
|
+
|
|
|
|
|
+ auto [status, updated_session] = llmClient_->ClearSessionMessages(request);
|
|
|
|
|
+
|
|
|
|
|
+ if (!status.ok()) {
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ nlohmann::json error = {{"error", status.error_message()}};
|
|
|
|
|
+ res.set_content(error.dump(), "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ res.set_content(R"({"message":"Messages cleared successfully"})", "application/json");
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::error("LLM clear messages error: {}", e.what());
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void HttpServer::HandleLlmListProviders(const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
|
|
+ if (!auth_user) {
|
|
|
|
|
+ res.status = 401;
|
|
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!authorizationService_->HasPermission(*auth_user, permissions::kLlmChat)) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - no LLM chat access"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!llmClient_ || !llmClient_->IsConnected()) {
|
|
|
|
|
+ res.status = 503;
|
|
|
|
|
+ res.set_content(R"({"error":"LLM service not available"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ ::smartbotic::llm::ListProvidersRequest request;
|
|
|
|
|
+ request.set_include_disabled(false); // Only show enabled providers
|
|
|
|
|
+
|
|
|
|
|
+ auto [status, list_response] = llmClient_->ListProviders(request);
|
|
|
|
|
+
|
|
|
|
|
+ if (!status.ok()) {
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ nlohmann::json error = {{"error", status.error_message()}};
|
|
|
|
|
+ res.set_content(error.dump(), "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json providers_array = nlohmann::json::array();
|
|
|
|
|
+ for (const auto& provider : list_response.providers()) {
|
|
|
|
|
+ nlohmann::json p;
|
|
|
|
|
+ p["id"] = provider.id();
|
|
|
|
|
+ p["name"] = provider.name();
|
|
|
|
|
+ p["type"] = ::smartbotic::llm::ProviderType_Name(provider.type());
|
|
|
|
|
+ p["base_url"] = provider.base_url();
|
|
|
|
|
+ p["enabled"] = provider.enabled();
|
|
|
|
|
+ p["healthy"] = provider.healthy();
|
|
|
|
|
+ providers_array.push_back(p);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json response;
|
|
|
|
|
+ response["providers"] = providers_array;
|
|
|
|
|
+
|
|
|
|
|
+ res.set_content(response.dump(), "application/json");
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::error("LLM list providers error: {}", e.what());
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void HttpServer::HandleLlmListModels(const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
|
|
+ if (!auth_user) {
|
|
|
|
|
+ res.status = 401;
|
|
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!authorizationService_->HasPermission(*auth_user, permissions::kLlmChat)) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - no LLM chat access"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!llmClient_ || !llmClient_->IsConnected()) {
|
|
|
|
|
+ res.status = 503;
|
|
|
|
|
+ res.set_content(R"({"error":"LLM service not available"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ ::smartbotic::llm::ListModelsRequest request;
|
|
|
|
|
+
|
|
|
|
|
+ if (req.has_param("provider_id")) {
|
|
|
|
|
+ request.set_provider_id(req.get_param_value("provider_id"));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ auto [status, list_response] = llmClient_->ListModels(request);
|
|
|
|
|
+
|
|
|
|
|
+ if (!status.ok()) {
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ nlohmann::json error = {{"error", status.error_message()}};
|
|
|
|
|
+ res.set_content(error.dump(), "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json models_array = nlohmann::json::array();
|
|
|
|
|
+ for (const auto& model : list_response.models()) {
|
|
|
|
|
+ nlohmann::json m;
|
|
|
|
|
+ m["id"] = model.id();
|
|
|
|
|
+ m["name"] = model.name();
|
|
|
|
|
+ m["provider_id"] = model.provider_id();
|
|
|
|
|
+ m["provider_type"] = ::smartbotic::llm::ProviderType_Name(model.provider_type());
|
|
|
|
|
+ m["context_length"] = model.context_length();
|
|
|
|
|
+ m["supports_tools"] = model.supports_tools();
|
|
|
|
|
+ m["supports_vision"] = model.supports_vision();
|
|
|
|
|
+ models_array.push_back(m);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json response;
|
|
|
|
|
+ response["models"] = models_array;
|
|
|
|
|
+
|
|
|
|
|
+ res.set_content(response.dump(), "application/json");
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::error("LLM list models error: {}", e.what());
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void HttpServer::HandleLlmRefreshModels(const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
|
|
+ if (!auth_user) {
|
|
|
|
|
+ res.status = 401;
|
|
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!authorizationService_->HasPermission(*auth_user, permissions::kLlmProvidersRead)) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - no provider access"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!llmClient_ || !llmClient_->IsConnected()) {
|
|
|
|
|
+ res.status = 503;
|
|
|
|
|
+ res.set_content(R"({"error":"LLM service not available"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ ::smartbotic::llm::RefreshModelsRequest request;
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json body;
|
|
|
|
|
+ if (!req.body.empty()) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ body = nlohmann::json::parse(req.body);
|
|
|
|
|
+ if (body.contains("provider_id") && body["provider_id"].is_string()) {
|
|
|
|
|
+ request.set_provider_id(body["provider_id"].get<std::string>());
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (...) {
|
|
|
|
|
+ // Ignore parse errors, proceed without provider_id filter
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ auto [status, refresh_response] = llmClient_->RefreshModels(request);
|
|
|
|
|
+
|
|
|
|
|
+ if (!status.ok()) {
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ nlohmann::json error = {{"error", status.error_message()}};
|
|
|
|
|
+ res.set_content(error.dump(), "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json response;
|
|
|
|
|
+ response["models_found"] = refresh_response.models_found();
|
|
|
|
|
+
|
|
|
|
|
+ if (refresh_response.errors_size() > 0) {
|
|
|
|
|
+ nlohmann::json errors_array = nlohmann::json::array();
|
|
|
|
|
+ for (const auto& err : refresh_response.errors()) {
|
|
|
|
|
+ errors_array.push_back(err);
|
|
|
|
|
+ }
|
|
|
|
|
+ response["errors"] = errors_array;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ res.set_content(response.dump(), "application/json");
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::error("LLM refresh models error: {}", e.what());
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void HttpServer::HandleLlmGetProviderHealth(const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
|
|
+ if (!auth_user) {
|
|
|
|
|
+ res.status = 401;
|
|
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!authorizationService_->HasPermission(*auth_user, permissions::kLlmChat)) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - no LLM chat access"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!llmClient_ || !llmClient_->IsConnected()) {
|
|
|
|
|
+ res.status = 503;
|
|
|
|
|
+ res.set_content(R"({"error":"LLM service not available"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string provider_id = req.matches[1].str();
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ ::smartbotic::llm::GetProviderHealthRequest request;
|
|
|
|
|
+ request.set_provider_id(provider_id);
|
|
|
|
|
+
|
|
|
|
|
+ auto [status, health_response] = llmClient_->GetProviderHealth(request);
|
|
|
|
|
+
|
|
|
|
|
+ if (!status.ok()) {
|
|
|
|
|
+ if (status.error_code() == grpc::StatusCode::NOT_FOUND) {
|
|
|
|
|
+ res.status = 404;
|
|
|
|
|
+ res.set_content(R"({"error":"Provider not found"})", "application/json");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ nlohmann::json error = {{"error", status.error_message()}};
|
|
|
|
|
+ res.set_content(error.dump(), "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json response;
|
|
|
|
|
+ response["provider_id"] = health_response.provider_id();
|
|
|
|
|
+ response["healthy"] = health_response.healthy();
|
|
|
|
|
+ response["latency_ms"] = health_response.latency_ms();
|
|
|
|
|
+ if (!health_response.error().empty()) {
|
|
|
|
|
+ response["error"] = health_response.error();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ res.set_content(response.dump(), "application/json");
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::error("LLM get provider health error: {}", e.what());
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void HttpServer::HandleLlmAdminListProviders(const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
|
|
+ if (!auth_user) {
|
|
|
|
|
+ res.status = 401;
|
|
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!authorizationService_->HasPermission(*auth_user, permissions::kLlmProvidersRead)) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - no provider admin access"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!llmClient_ || !llmClient_->IsConnected()) {
|
|
|
|
|
+ res.status = 503;
|
|
|
|
|
+ res.set_content(R"({"error":"LLM service not available"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ ::smartbotic::llm::ListProviderConfigsRequest request;
|
|
|
|
|
+ request.set_include_disabled(true); // Admin sees all
|
|
|
|
|
+
|
|
|
|
|
+ auto [status, list_response] = llmClient_->ListProviderConfigs(request);
|
|
|
|
|
+
|
|
|
|
|
+ if (!status.ok()) {
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ nlohmann::json error = {{"error", status.error_message()}};
|
|
|
|
|
+ res.set_content(error.dump(), "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json providers_array = nlohmann::json::array();
|
|
|
|
|
+ for (const auto& provider : list_response.providers()) {
|
|
|
|
|
+ nlohmann::json p;
|
|
|
|
|
+ p["id"] = provider.id();
|
|
|
|
|
+ p["name"] = provider.name();
|
|
|
|
|
+ p["type"] = ::smartbotic::llm::ProviderType_Name(provider.type());
|
|
|
|
|
+ p["base_url"] = provider.base_url();
|
|
|
|
|
+ p["enabled"] = provider.enabled();
|
|
|
|
|
+ p["has_api_key"] = provider.has_api_key();
|
|
|
|
|
+ providers_array.push_back(p);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json response;
|
|
|
|
|
+ response["providers"] = providers_array;
|
|
|
|
|
+
|
|
|
|
|
+ res.set_content(response.dump(), "application/json");
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::error("LLM admin list providers error: {}", e.what());
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void HttpServer::HandleLlmAdminCreateProvider(const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
|
|
+ if (!auth_user) {
|
|
|
|
|
+ res.status = 401;
|
|
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!authorizationService_->HasPermission(*auth_user, permissions::kLlmProvidersCreate)) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - no provider create access"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!llmClient_ || !llmClient_->IsConnected()) {
|
|
|
|
|
+ res.status = 503;
|
|
|
|
|
+ res.set_content(R"({"error":"LLM service not available"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json body;
|
|
|
|
|
+ try {
|
|
|
|
|
+ body = nlohmann::json::parse(req.body);
|
|
|
|
|
+ } catch (const nlohmann::json::parse_error&) {
|
|
|
|
|
+ res.status = 400;
|
|
|
|
|
+ res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!body.contains("name") || !body["name"].is_string()) {
|
|
|
|
|
+ res.status = 400;
|
|
|
|
|
+ res.set_content(R"({"error":"name is required"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!body.contains("type") || !body["type"].is_string()) {
|
|
|
|
|
+ res.status = 400;
|
|
|
|
|
+ res.set_content(R"({"error":"type is required"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ ::smartbotic::llm::SetProviderConfigRequest request;
|
|
|
|
|
+ // Leave id empty for new provider
|
|
|
|
|
+ request.set_name(body["name"].get<std::string>());
|
|
|
|
|
+
|
|
|
|
|
+ std::string type_str = body["type"].get<std::string>();
|
|
|
|
|
+ ::smartbotic::llm::ProviderType type;
|
|
|
|
|
+ if (!::smartbotic::llm::ProviderType_Parse(type_str, &type)) {
|
|
|
|
|
+ res.status = 400;
|
|
|
|
|
+ res.set_content(R"({"error":"Invalid provider type"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ request.set_type(type);
|
|
|
|
|
+
|
|
|
|
|
+ if (body.contains("base_url") && body["base_url"].is_string()) {
|
|
|
|
|
+ request.set_base_url(body["base_url"].get<std::string>());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (body.contains("api_key") && body["api_key"].is_string()) {
|
|
|
|
|
+ request.set_api_key(body["api_key"].get<std::string>());
|
|
|
|
|
+ }
|
|
|
|
|
+ request.set_enabled(body.value("enabled", true));
|
|
|
|
|
+
|
|
|
|
|
+ auto [status, provider] = llmClient_->SetProviderConfig(request);
|
|
|
|
|
+
|
|
|
|
|
+ if (!status.ok()) {
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ nlohmann::json error = {{"error", status.error_message()}};
|
|
|
|
|
+ res.set_content(error.dump(), "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json response;
|
|
|
|
|
+ response["id"] = provider.id();
|
|
|
|
|
+ response["name"] = provider.name();
|
|
|
|
|
+ response["type"] = ::smartbotic::llm::ProviderType_Name(provider.type());
|
|
|
|
|
+ response["base_url"] = provider.base_url();
|
|
|
|
|
+ response["enabled"] = provider.enabled();
|
|
|
|
|
+ response["has_api_key"] = provider.has_api_key();
|
|
|
|
|
+
|
|
|
|
|
+ res.status = 201;
|
|
|
|
|
+ res.set_content(response.dump(), "application/json");
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::error("LLM admin create provider error: {}", e.what());
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void HttpServer::HandleLlmAdminUpdateProvider(const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
|
|
+ if (!auth_user) {
|
|
|
|
|
+ res.status = 401;
|
|
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!authorizationService_->HasPermission(*auth_user, permissions::kLlmProvidersUpdate)) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - no provider update access"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!llmClient_ || !llmClient_->IsConnected()) {
|
|
|
|
|
+ res.status = 503;
|
|
|
|
|
+ res.set_content(R"({"error":"LLM service not available"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string provider_id = req.matches[1].str();
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json body;
|
|
|
|
|
+ try {
|
|
|
|
|
+ body = nlohmann::json::parse(req.body);
|
|
|
|
|
+ } catch (const nlohmann::json::parse_error&) {
|
|
|
|
|
+ res.status = 400;
|
|
|
|
|
+ res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ ::smartbotic::llm::SetProviderConfigRequest request;
|
|
|
|
|
+ request.set_id(provider_id);
|
|
|
|
|
+
|
|
|
|
|
+ if (body.contains("name") && body["name"].is_string()) {
|
|
|
|
|
+ request.set_name(body["name"].get<std::string>());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (body.contains("type") && body["type"].is_string()) {
|
|
|
|
|
+ std::string type_str = body["type"].get<std::string>();
|
|
|
|
|
+ ::smartbotic::llm::ProviderType type;
|
|
|
|
|
+ if (::smartbotic::llm::ProviderType_Parse(type_str, &type)) {
|
|
|
|
|
+ request.set_type(type);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (body.contains("base_url") && body["base_url"].is_string()) {
|
|
|
|
|
+ request.set_base_url(body["base_url"].get<std::string>());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (body.contains("api_key") && body["api_key"].is_string()) {
|
|
|
|
|
+ request.set_api_key(body["api_key"].get<std::string>());
|
|
|
|
|
+ }
|
|
|
|
|
+ if (body.contains("enabled") && body["enabled"].is_boolean()) {
|
|
|
|
|
+ request.set_enabled(body["enabled"].get<bool>());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ auto [status, provider] = llmClient_->SetProviderConfig(request);
|
|
|
|
|
+
|
|
|
|
|
+ if (!status.ok()) {
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ nlohmann::json error = {{"error", status.error_message()}};
|
|
|
|
|
+ res.set_content(error.dump(), "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json response;
|
|
|
|
|
+ response["id"] = provider.id();
|
|
|
|
|
+ response["name"] = provider.name();
|
|
|
|
|
+ response["type"] = ::smartbotic::llm::ProviderType_Name(provider.type());
|
|
|
|
|
+ response["base_url"] = provider.base_url();
|
|
|
|
|
+ response["enabled"] = provider.enabled();
|
|
|
|
|
+ response["has_api_key"] = provider.has_api_key();
|
|
|
|
|
+
|
|
|
|
|
+ res.set_content(response.dump(), "application/json");
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::error("LLM admin update provider error: {}", e.what());
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void HttpServer::HandleLlmAdminDeleteProvider(const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
|
|
+ if (!auth_user) {
|
|
|
|
|
+ res.status = 401;
|
|
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!authorizationService_->HasPermission(*auth_user, permissions::kLlmProvidersDelete)) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - no provider delete access"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!llmClient_ || !llmClient_->IsConnected()) {
|
|
|
|
|
+ res.status = 503;
|
|
|
|
|
+ res.set_content(R"({"error":"LLM service not available"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string provider_id = req.matches[1].str();
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ ::smartbotic::llm::DeleteProviderConfigRequest request;
|
|
|
|
|
+ request.set_provider_id(provider_id);
|
|
|
|
|
+
|
|
|
|
|
+ auto [status, delete_response] = llmClient_->DeleteProviderConfig(request);
|
|
|
|
|
+
|
|
|
|
|
+ if (!status.ok()) {
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ nlohmann::json error = {{"error", status.error_message()}};
|
|
|
|
|
+ res.set_content(error.dump(), "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ res.set_content(R"({"message":"Provider deleted successfully"})", "application/json");
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::error("LLM admin delete provider error: {}", e.what());
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void HttpServer::HandleLlmSetWorkspaceKey(const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
|
|
+ if (!auth_user) {
|
|
|
|
|
+ res.status = 401;
|
|
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string workspace_id = req.matches[1].str();
|
|
|
|
|
+
|
|
|
|
|
+ // Check BYOK permission for this workspace
|
|
|
|
|
+ std::string byok_permission = permissions::BuildWorkspacePermission(workspace_id, "llm_byok");
|
|
|
|
|
+ if (!authorizationService_->HasPermission(*auth_user, byok_permission)) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - no BYOK access for this workspace"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!llmClient_ || !llmClient_->IsConnected()) {
|
|
|
|
|
+ res.status = 503;
|
|
|
|
|
+ res.set_content(R"({"error":"LLM service not available"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json body;
|
|
|
|
|
+ try {
|
|
|
|
|
+ body = nlohmann::json::parse(req.body);
|
|
|
|
|
+ } catch (const nlohmann::json::parse_error&) {
|
|
|
|
|
+ res.status = 400;
|
|
|
|
|
+ res.set_content(R"({"error":"Invalid JSON body"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!body.contains("provider_id") || !body["provider_id"].is_string()) {
|
|
|
|
|
+ res.status = 400;
|
|
|
|
|
+ res.set_content(R"({"error":"provider_id is required"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!body.contains("api_key") || !body["api_key"].is_string()) {
|
|
|
|
|
+ res.status = 400;
|
|
|
|
|
+ res.set_content(R"({"error":"api_key is required"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ ::smartbotic::llm::SetWorkspaceApiKeyRequest request;
|
|
|
|
|
+ request.set_workspace_id(workspace_id);
|
|
|
|
|
+ request.set_provider_id(body["provider_id"].get<std::string>());
|
|
|
|
|
+ request.set_api_key(body["api_key"].get<std::string>());
|
|
|
|
|
+
|
|
|
|
|
+ auto status = llmClient_->SetWorkspaceApiKey(request);
|
|
|
|
|
+
|
|
|
|
|
+ if (!status.ok()) {
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ nlohmann::json error = {{"error", status.error_message()}};
|
|
|
|
|
+ res.set_content(error.dump(), "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ res.set_content(R"({"message":"Workspace API key set successfully"})", "application/json");
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::error("LLM set workspace key error: {}", e.what());
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void HttpServer::HandleLlmGetWorkspaceKey(const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
|
|
+ if (!auth_user) {
|
|
|
|
|
+ res.status = 401;
|
|
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string workspace_id = req.matches[1].str();
|
|
|
|
|
+
|
|
|
|
|
+ // Check BYOK permission for this workspace
|
|
|
|
|
+ std::string byok_permission = permissions::BuildWorkspacePermission(workspace_id, "llm_byok");
|
|
|
|
|
+ if (!authorizationService_->HasPermission(*auth_user, byok_permission)) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - no BYOK access for this workspace"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!llmClient_ || !llmClient_->IsConnected()) {
|
|
|
|
|
+ res.status = 503;
|
|
|
|
|
+ res.set_content(R"({"error":"LLM service not available"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string provider_id;
|
|
|
|
|
+ if (req.has_param("provider_id")) {
|
|
|
|
|
+ provider_id = req.get_param_value("provider_id");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ res.status = 400;
|
|
|
|
|
+ res.set_content(R"({"error":"provider_id query parameter is required"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ ::smartbotic::llm::GetWorkspaceApiKeyRequest request;
|
|
|
|
|
+ request.set_workspace_id(workspace_id);
|
|
|
|
|
+ request.set_provider_id(provider_id);
|
|
|
|
|
+
|
|
|
|
|
+ auto [status, key_response] = llmClient_->GetWorkspaceApiKey(request);
|
|
|
|
|
+
|
|
|
|
|
+ if (!status.ok()) {
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ nlohmann::json error = {{"error", status.error_message()}};
|
|
|
|
|
+ res.set_content(error.dump(), "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ nlohmann::json response;
|
|
|
|
|
+ response["workspace_id"] = key_response.workspace_id();
|
|
|
|
|
+ response["provider_id"] = key_response.provider_id();
|
|
|
|
|
+ response["has_api_key"] = key_response.has_api_key();
|
|
|
|
|
+
|
|
|
|
|
+ res.set_content(response.dump(), "application/json");
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::error("LLM get workspace key error: {}", e.what());
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void HttpServer::HandleLlmDeleteWorkspaceKey(const httplib::Request& req, httplib::Response& res) {
|
|
|
|
|
+ auto auth_user = AuthenticateRequest(req);
|
|
|
|
|
+ if (!auth_user) {
|
|
|
|
|
+ res.status = 401;
|
|
|
|
|
+ res.set_content(R"({"error":"Unauthorized"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string workspace_id = req.matches[1].str();
|
|
|
|
|
+
|
|
|
|
|
+ // Check BYOK permission for this workspace
|
|
|
|
|
+ std::string byok_permission = permissions::BuildWorkspacePermission(workspace_id, "llm_byok");
|
|
|
|
|
+ if (!authorizationService_->HasPermission(*auth_user, byok_permission)) {
|
|
|
|
|
+ res.status = 403;
|
|
|
|
|
+ res.set_content(R"({"error":"Forbidden - no BYOK access for this workspace"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!llmClient_ || !llmClient_->IsConnected()) {
|
|
|
|
|
+ res.status = 503;
|
|
|
|
|
+ res.set_content(R"({"error":"LLM service not available"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ std::string provider_id;
|
|
|
|
|
+ if (req.has_param("provider_id")) {
|
|
|
|
|
+ provider_id = req.get_param_value("provider_id");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ res.status = 400;
|
|
|
|
|
+ res.set_content(R"({"error":"provider_id query parameter is required"})", "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ ::smartbotic::llm::DeleteWorkspaceApiKeyRequest request;
|
|
|
|
|
+ request.set_workspace_id(workspace_id);
|
|
|
|
|
+ request.set_provider_id(provider_id);
|
|
|
|
|
+
|
|
|
|
|
+ auto [status, delete_response] = llmClient_->DeleteWorkspaceApiKey(request);
|
|
|
|
|
+
|
|
|
|
|
+ if (!status.ok()) {
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ nlohmann::json error = {{"error", status.error_message()}};
|
|
|
|
|
+ res.set_content(error.dump(), "application/json");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ res.set_content(R"({"message":"Workspace API key deleted successfully"})", "application/json");
|
|
|
|
|
+
|
|
|
|
|
+ } catch (const std::exception& e) {
|
|
|
|
|
+ spdlog::error("LLM delete workspace key error: {}", e.what());
|
|
|
|
|
+ res.status = 500;
|
|
|
|
|
+ res.set_content(R"({"error":"Internal server error"})", "application/json");
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
} // namespace smartbotic::webserver
|
|
} // namespace smartbotic::webserver
|