summaryrefslogtreecommitdiff
path: root/src/ai_service.cpp
diff options
context:
space:
mode:
authorAldrik Ramaekers <aldrikboy@gmail.com>2025-09-28 17:41:50 +0200
committerAldrik Ramaekers <aldrikboy@gmail.com>2025-09-28 17:41:50 +0200
commita2299b0bae21c8f05f091732a78fc250cbd5e016 (patch)
treeb1de8319d1088b1209ba377665995b4543180d4b /src/ai_service.cpp
parentd8c4d84dc75300c6d4d8b0adceafa33741960b92 (diff)
openAI invoice importing v0.1
Diffstat (limited to 'src/ai_service.cpp')
-rw-r--r--src/ai_service.cpp135
1 files changed, 43 insertions, 92 deletions
diff --git a/src/ai_service.cpp b/src/ai_service.cpp
index 2553f61..e271fcb 100644
--- a/src/ai_service.cpp
+++ b/src/ai_service.cpp
@@ -22,121 +22,72 @@
#include "httplib.h"
#include "log.hpp"
#include "ai_service.hpp"
+#include "strops.hpp"
+#include "administration_reader.hpp"
+ai_provider_impl _ai_get_impl()
+{
+ ai_provider provider = administration_get_ai_service().provider;
-// ---- 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++;
+ switch(provider)
+ {
+ case AI_PROVIDER_OPENAI: return _chatgpt_api_provider;
+ default: assert(0); break;
}
- 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);
+ return ai_provider_impl {0};
}
-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
-}
+extern const char* peppol_invoice_template;
+extern const char* peppol_invoice_line_template;
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;
- }
+ ai_provider_impl impl = _ai_get_impl();
- 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);
+ char file_id[100];
+ if (!impl.upload_file(file_path, file_id, 100)) {
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);
+ size_t query_buffer_len = 50000;
+ char* template_buffer = (char*)malloc(query_buffer_len);
+ memset(template_buffer, 0, query_buffer_len);
- char *buffer = (char*)malloc(part_size);
+ strncpy(template_buffer, peppol_invoice_template, query_buffer_len);
+ strops_replace(template_buffer, 50000, "{{INVOICE_LINE_LIST}}", peppol_invoice_line_template);
- int part_number = 0;
- while (1) {
- size_t read_bytes = read_chunk(orig_file, buffer, part_size);
- if (read_bytes == 0) break;
+ char* ai_query =
+ "\n\nI have provided a file containing an invoice. Fill in the above Peppol 3.0 template with the information from the invoice."
+ "Do not add any fields to the template. If you can't find data for a given field, leave it empty. Do not make up any information."
+ "Only return the filled out template in valid XML format. Nothing else.\n";
- 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"));
+ size_t query_len = strlen(template_buffer);
+ strncpy(template_buffer + query_len, ai_query, query_buffer_len - query_len);
- std::string chunk(buffer, read_bytes);
+ char* response;
+ if (!impl.query_with_file(template_buffer, query_buffer_len, file_id, &response)) {
+ return 0;
+ }
- httplib::UploadFormDataItems items = {
- {"data", chunk, filename, "application/pdf"}
- };
+ invoice inv;
+ if (!administration_reader_read_invoice_from_xml(&inv, response, strlen(response))) {
+ return false;
+ }
- char path[256];
- snprintf(path, sizeof(path), "/v1/uploads/%s/parts?part_number=%d", upload_id, part_number);
+ invoice tmp = administration_invoice_create_empty();
- httplib::Result part_res = cli.Post(path, part_headers, items);
+ inv.status = invoice_status::INVOICE_RECEIVED;
+ strops_copy(inv.id, tmp.id, MAX_LEN_ID); // TODO next_id is not being incremented
+ strops_copy(inv.customer.id, MY_COMPANY_ID, MAX_LEN_ID); // TODO param for incomming/exporting necessary
- if (!part_res || part_res->status != 200) {
- log_error("Failed to upload part %d.", part_number);
- free(buffer);
- fclose(orig_file);
- return 0;
- }
+ strops_copy(inv.document.original_path, file_path, MAX_LEN_PATH);
+ strops_copy(inv.document.copy_path, "", MAX_LEN_PATH);
- log_info("Uploaded part %d\n", part_number);
- part_number++;
- }
+ a_err result = administration_invoice_import(&inv);
- 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;
- }
+ free(template_buffer);
+ free(response);
return 0;
} \ No newline at end of file