summaryrefslogtreecommitdiff
path: root/src/ai_service.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ai_service.cpp')
-rw-r--r--src/ai_service.cpp142
1 files changed, 142 insertions, 0 deletions
diff --git a/src/ai_service.cpp b/src/ai_service.cpp
new file mode 100644
index 0000000..2553f61
--- /dev/null
+++ b/src/ai_service.cpp
@@ -0,0 +1,142 @@
+/*
+* Copyright (c) 2025 Aldrik Ramaekers <aldrik.ramaekers@gmail.com>
+*
+* 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 <fstream>
+#include <iostream>
+#include <string>
+
+#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;
+} \ No newline at end of file