/* * 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. */ #include #include #include #define CPPHTTPLIB_OPENSSL_SUPPORT #include "httplib.h" #include "log.hpp" #include "ai_service.hpp" // ---- Utility: simple JSON value extractor (very naive) ---- char *extract_json_value(const char *json, const char *key, char *out, size_t out_size) { char pattern[128]; snprintf(pattern, sizeof(pattern), "\"%s\"", key); const char *pos = strstr(json, pattern); if (!pos) return NULL; pos = strchr(pos, ':'); if (!pos) return NULL; pos++; // Skip whitespace and quotes while (*pos == ' ' || *pos == '\"') pos++; size_t i = 0; while (*pos && *pos != '\"' && *pos != ',' && *pos != '}' && i < out_size - 1) { out[i++] = *pos++; } out[i] = '\0'; return out; } // ---- Read file chunk ---- size_t read_chunk(FILE *fp, char *buffer, size_t chunk_size) { return fread(buffer, 1, chunk_size, fp); } const char* get_filename(const char* path) { const char* filename = strrchr(path, '/'); // for Unix-style paths if (filename) return filename + 1; // skip the '/' filename = strrchr(path, '\\'); // for Windows-style paths if (filename) return filename + 1; return path; // no slashes found, path itself is filename } ai_request* ai_document_to_invoice(char* file_path) { const char *api_key = administration_get_ai_service().api_key_public; const char *filename = get_filename(file_path); FILE* orig_file = fopen(file_path, "rb"); if (orig_file == NULL) { log_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]; snprintf(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) { log_error("ERROR Failed to create upload."); fclose(orig_file); return 0; } char upload_id[128]; extract_json_value(res->body.c_str(), "id", upload_id, sizeof(upload_id)); size_t part_size = 64000000; // 64mb log_info("Created upload %s with part size %zu.", upload_id, part_size); char *buffer = (char*)malloc(part_size); int part_number = 0; while (1) { size_t read_bytes = read_chunk(orig_file, buffer, part_size); if (read_bytes == 0) break; httplib::Headers part_headers; part_headers.insert(std::make_pair("Authorization", std::string("Bearer ") + api_key)); part_headers.insert(std::make_pair("Content-Type", "multipart/form-data")); std::string chunk(buffer, read_bytes); httplib::UploadFormDataItems items = { {"data", chunk, filename, "application/pdf"} }; char path[256]; snprintf(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) { log_error("Failed to upload part %d.", part_number); free(buffer); fclose(orig_file); return 0; } log_info("Uploaded part %d\n", part_number); part_number++; } free(buffer); fclose(orig_file); // ---------- Step 3: Complete upload ---------- httplib::Result complete_res = cli.Post((std::string("/v1/uploads/") + upload_id + "/complete").c_str(), headers, "", "application/json"); if (!complete_res || complete_res->status != 200) { log_error("ERROR Failed to complete upload."); return 0; } return 0; }