/* * Copyright (c) 2025 Aldrik Ramaekers * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #define CPPHTTPLIB_OPENSSL_SUPPORT #include "httplib.h" #include "memops.hpp" #include "strops.hpp" #include "logger.hpp" #include "importer.hpp" static bool _openAI_query_with_file(char* query, size_t query_length, char* file_id, char** response) { #if 1 const char *api_key = administration::get_ai_service().api_key_public; httplib::SSLClient cli("api.openai.com", 443); //cli.enable_server_certificate_verification(false); char* query_escaped = strops::prep_str_for_json(query, query_length); memops::unalloc(query); size_t body_size = query_length + 200; char* body = (char*)memops::alloc(body_size); strops::format(body, body_size, "{\"model\":\"%s\", \"input\": [ { \"role\": \"user\", \"content\": [ { \"type\": \"input_file\", \"file_id\": \"%s\" }, " "{ \"type\": \"input_text\", \"text\": \"%s\" } ] } ] }", administration::get_ai_service().model_name, file_id, query_escaped); httplib::Headers headers; headers.insert(std::make_pair("Authorization", std::string("Bearer ") + api_key)); httplib::Result res = cli.Post("/v1/responses", headers, body, "application/json"); memops::unalloc(body); if (!res || res->status != 200) { logger::error("ERROR Failed to query API."); logger::error(res->body.c_str()); return 0; } char* response_body = (char*)res->body.c_str(); *response = (char*)memops::alloc(100000); memops::zero(*response, 100000); strops::copy(*response, response_body, 100000); strops::get_json_value(*response, "text", *response, 100000); *response = strops::unprep_str_from_json(*response); #else *response = (char*)memops::alloc(100000); memops::zero(*response, 100000); strops::copy(*response, " urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0 urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 492043632 2024-09-01 2024-09-01 380 USD Final invoice do:team:67840ecb-44e2-472e-bc45-801bd4e1f1fe DigitalOcean LLC 101 Avenue of the Americas 2nd Floor New York 10013 NY US EU528002224 VAT DigitalOcean LLC My Team Keerderstraat 81 Maastricht 6226 XW LI NL VAT aldrikboy@gmail.com 492043632 3.49 15.60 3.28 VAT 1.00 0.21 VAT 16.60 16.60 20.09 20.09 1 16.60 false Discount Product Usage Charges Internal Tax Rate ID VAT ", 100000); #endif return 1; } static bool _openAI_upload_file(char* file_path, char* file_id, size_t file_id_len) { const char *api_key = administration::get_ai_service().api_key_public; const char *filename = strops::get_filename(file_path); FILE* orig_file = fopen(file_path, "rb"); if (orig_file == NULL) { logger::error("ERROR: file to upload could not be opened."); return 0; } fseek(orig_file, 0L, SEEK_END); long sz = ftell(orig_file); fseek(orig_file, 0, SEEK_SET); httplib::SSLClient cli("api.openai.com", 443); //cli.enable_server_certificate_verification(false); char body[512]; strops::format(body, sizeof(body), "{\"filename\":\"%s\",\"purpose\":\"user_data\", \"bytes\": %d, \"mime_type\": \"application/pdf\", \"expires_after\": { \"anchor\": \"created_at\", \"seconds\": 3600 } }", filename, sz); httplib::Headers headers; headers.insert(std::make_pair("Authorization", std::string("Bearer ") + api_key)); httplib::Result res = cli.Post("/v1/uploads", headers, body, "application/json"); if (!res || res->status != 200) { logger::error("ERROR Failed to create upload."); logger::error(res->body.c_str()); fclose(orig_file); return 0; } char upload_id[128]; strops::get_json_value(res->body.c_str(), "id", upload_id, sizeof(upload_id)); size_t part_size = 64000000; // 64mb logger::info("Created upload %s with part size %zu.", upload_id, part_size); char *buffer = (char*)memops::alloc(part_size); char completion_body[1048]; strops::format(completion_body, sizeof(completion_body), "{\"part_ids\": ["); int part_number = 0; while (1) { size_t read_bytes = fread(buffer, 1, part_size, orig_file); if (read_bytes == 0) break; httplib::Headers part_headers; part_headers.insert(std::make_pair("Authorization", std::string("Bearer ") + api_key)); std::string chunk(buffer, read_bytes); httplib::UploadFormDataItems items = { {"data", chunk, filename, "application/octet-stream"} }; char path[256]; strops::format(path, sizeof(path), "/v1/uploads/%s/parts?part_number=%d", upload_id, part_number); httplib::Result part_res = cli.Post(path, part_headers, items); if (!part_res || part_res->status != 200) { logger::error("Failed to upload part %d.", part_number); logger::error(part_res->body.c_str()); memops::unalloc(buffer); fclose(orig_file); return 0; } else { char part_id[128]; strops::get_json_value(part_res->body.c_str(), "id", part_id, sizeof(part_id)); if (part_number == 0) strops::format(completion_body+strops::length(completion_body), sizeof(completion_body)-strops::length(completion_body), "\"%s\"", part_id); if (part_number != 0) strops::format(completion_body+strops::length(completion_body), sizeof(completion_body)-strops::length(completion_body), ", \"%s\"", part_id); } logger::info("Uploaded part %d\n", part_number); part_number++; } strops::format(completion_body+strops::length(completion_body), sizeof(completion_body)-strops::length(completion_body), "]}"); memops::unalloc(buffer); fclose(orig_file); // ---------- Step 3: Complete upload ---------- httplib::Result complete_res = cli.Post((std::string("/v1/uploads/") + upload_id + "/complete").c_str(), headers, completion_body, "application/json"); if (!complete_res || complete_res->status != 200) { logger::error("ERROR Failed to complete upload."); logger::error(complete_res->body.c_str()); return 0; } char* completion_body_response = (char*)complete_res->body.c_str(); strops::get_json_value(completion_body_response, "id", file_id, file_id_len, 1); return 1; } static bool _openAI_get_available_models(importer::model_list_request* buffer) { const char *api_key = administration::get_ai_service().api_key_public; httplib::SSLClient cli("api.openai.com", 443); httplib::Headers headers; headers.insert(std::make_pair("Authorization", std::string("Bearer ") + api_key)); httplib::Result res = cli.Get("/v1/models", headers); if (!res || res->status != 200) { logger::error("ERROR Failed to get models list."); logger::error(res->body.c_str()); return 0; } char* completion_body_response = (char*)res->body.c_str(); u32 count = 0; char model_name[MAX_LEN_SHORT_DESC]; while(1) { if (!strops::get_json_value(completion_body_response, "id", model_name, MAX_LEN_SHORT_DESC, count++)) break; if (count == MAX_MODEL_LIST_RESULT_COUNT) break; strops::copy(buffer->result[buffer->result_count++], model_name, MAX_LEN_SHORT_DESC); } return 1; } importer::ai_provider_impl _chatgpt_api_provider = { "OpenAI", "gpt-5-nano", _openAI_upload_file, _openAI_query_with_file, _openAI_get_available_models, };