summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/assets.cpp86
-rw-r--r--src/main_linux.cpp3
-rw-r--r--src/providers/DeepSeek.cpp121
-rw-r--r--src/providers/MailerSend.cpp1
-rw-r--r--src/providers/openAI.cpp3
-rw-r--r--src/ui/imgui_extensions.cpp2
-rw-r--r--src/ui/ui_invoices.cpp4
-rw-r--r--src/ui/ui_settings.cpp60
-rw-r--r--src/ui/ui_setup.cpp37
9 files changed, 147 insertions, 170 deletions
diff --git a/src/assets.cpp b/src/assets.cpp
new file mode 100644
index 0000000..7ea3cdb
--- /dev/null
+++ b/src/assets.cpp
@@ -0,0 +1,86 @@
+/*
+* 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 <GLFW/glfw3.h>
+#include <stb/stb_image.h>
+
+#include "assets.hpp"
+#include "strops.hpp"
+
+#include "assets/splash.h"
+
+#include "assets/logos/MailSender.h"
+#include "assets/logos/Perplexity.h"
+#include "assets/logos/OpenAI.h"
+#include "assets/logos/Gemini.h"
+
+#define MAX_IMAGE_COUNT 5
+
+image _image_dict[MAX_IMAGE_COUNT] = {
+ (image){-1, "img/splash", (const void*)img_splash, img_splash_length},
+ (image){-1, "img/logos/mailsender", (const void*)img_mailsender, img_mailsender_length},
+ (image){-1, "img/logos/openai", (const void*)img_openai, img_openai_length},
+ (image){-1, "img/logos/perplexity", (const void*)img_perplexity, img_perplexity_length},
+ (image){-1, "img/logos/gemini", (const void*)img_gemini, img_gemini_length},
+};
+
+static int _load_image(image img)
+{
+ int width, height, channels;
+ unsigned char* data = stbi_load_from_memory((const stbi_uc*)img.data_start, img.data_length, &width, &height, &channels, 0);
+
+ if (!data)
+ {
+ printf("%s\n", stbi_failure_reason());
+ return 0;
+ }
+
+ GLuint textureID;
+ glGenTextures(1, &textureID);
+ glBindTexture(GL_TEXTURE_2D, textureID);
+
+ // Set texture parameters
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ // Upload texture data
+ GLenum format = (channels == 4) ? GL_RGBA : GL_RGB;
+ glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
+ //glGenerateMipmap(GL_TEXTURE_2D);
+
+ // Free image memory
+ stbi_image_free(data);
+
+ return textureID;
+}
+
+s32 assets::load_image(const char* name)
+{
+ for (u32 i = 0; i < MAX_IMAGE_COUNT; i++)
+ {
+ if (strops::equals(_image_dict[i].name, name)) {
+
+ if (_image_dict[i].img_id == -1) {
+ _image_dict[i].img_id = _load_image(_image_dict[i]);
+ }
+ return _image_dict[i].img_id;
+ }
+ }
+
+ return 0;
+} \ No newline at end of file
diff --git a/src/main_linux.cpp b/src/main_linux.cpp
index b317ab1..95177ef 100644
--- a/src/main_linux.cpp
+++ b/src/main_linux.cpp
@@ -74,7 +74,8 @@ static void _create_window(bool is_setup_window)
if (window == nullptr)
return;
glfwMakeContextCurrent(window);
- glfwSwapInterval(1); // Enable vsync
+ glfwSwapInterval(1);
+ glfwWindowHint(GLFW_SAMPLES, 4);
IMGUI_CHECKVERSION();
ImGui::CreateContext();
diff --git a/src/providers/DeepSeek.cpp b/src/providers/DeepSeek.cpp
deleted file mode 100644
index 7c695fb..0000000
--- a/src/providers/DeepSeek.cpp
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
-* 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.
-*/
-
-#define CPPHTTPLIB_OPENSSL_SUPPORT
-#include <cpp-httplib/httplib.h>
-
-#include "strops.hpp"
-#include "memops.hpp"
-#include "logger.hpp"
-#include "importer.hpp"
-
-#define QUERY_BUFFER_SIZE 1000000
-
-char* query_buffer = 0;
-static bool _DeepSeek_query_with_file(const char* query, size_t query_length, const char* file_id, char** response)
-{
- (void)file_id;
- (void)query_length;
- assert(query_buffer);
-
- const char *api_key = administration::get_active_ai_service().api_key_public;
-
- httplib::SSLClient cli("api.deepseek.com");
- //cli.enable_server_certificate_verification(false);
-
- //char* query_escaped = strops::prep_str_for_json(query, query_length);
- //memops::unalloc(query); // TODO why??
-
- size_t file_size = strops::length(query_buffer);
- sprintf(query_buffer + file_size, "%s", query);
-
- char* query_escaped = strops::prep_str_for_json(query_buffer, strops::length(query_buffer));
-
- size_t body_size = file_size + QUERY_BUFFER_SIZE;
- char* body = (char*)memops::alloc(body_size);
- strops::format(body, body_size,
- "{\"model\":\"%s\", \"messages\": [ { \"role\": \"user\", \"content\": \"%s\" } ] }", administration::get_active_ai_service().model_name, query_escaped);
-
- httplib::Headers headers;
- headers.insert(std::make_pair("Authorization", std::string("Bearer ") + api_key));
- headers.insert(std::make_pair("Content-Type", "application/json"));
- headers.insert(std::make_pair("Accept", "application/json"));
-
- httplib::Result res = cli.Post("/chat/completions", 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, "content", *response, 100000);
- *response = strops::unprep_str_from_json(*response);
-
- return 1;
-}
-
-static bool _DeepSeek_upload_file(const char* file_path, char* file_id, size_t file_id_len)
-{
- (void)file_id;
- (void)file_id_len;
- const char *filename = strops::get_filename(file_path);
-
- FILE* orig_file = fopen(file_path, "r");
- 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);
-
- size_t buffer_size = sz + QUERY_BUFFER_SIZE;
- char* file_content_buffer = (char*)memops::alloc(buffer_size);
- memops::zero(file_content_buffer, buffer_size);
-
- query_buffer = file_content_buffer;
-
- file_content_buffer += sprintf(file_content_buffer, "[file name]: %s\n", filename);
- file_content_buffer += sprintf(file_content_buffer, "[file content begin]\n");
-
- fread(file_content_buffer, sz, 1, orig_file);
- file_content_buffer += sz;
-
- for (int i = 0; i < file_content_buffer-query_buffer; i++) if (query_buffer[i] <= 0x1f) query_buffer[i] = ' ';
-
- file_content_buffer += sprintf(file_content_buffer, "\n[file content end]\n");
- file_content_buffer[0] = 0;
-
- fclose(orig_file);
-
- return 1;
-}
-
-importer::ai_provider_impl _deepseek_api_provider = {
- "DeekSeek",
- "deepseek-reasoner",
- _DeepSeek_upload_file,
- _DeepSeek_query_with_file,
- 0,
-}; \ No newline at end of file
diff --git a/src/providers/MailerSend.cpp b/src/providers/MailerSend.cpp
index 1758ac8..dbae4c6 100644
--- a/src/providers/MailerSend.cpp
+++ b/src/providers/MailerSend.cpp
@@ -72,5 +72,6 @@ bool _MailerSend_send_email(const char* sender, const char* recipient, const cha
exporter::email_provider_impl _mailersend_api_provider = {
"MailerSend",
+ "img/logos/mailsender",
_MailerSend_send_email,
}; \ No newline at end of file
diff --git a/src/providers/openAI.cpp b/src/providers/openAI.cpp
index c743711..5659465 100644
--- a/src/providers/openAI.cpp
+++ b/src/providers/openAI.cpp
@@ -269,6 +269,7 @@ static bool _openAI_get_available_models(importer::model_list_request* buffer)
importer::ai_provider_impl _chatgpt_api_provider = {
"OpenAI",
"gpt-5-nano",
+ "img/logos/openai",
_openAI_upload_file,
_openAI_query_with_file,
_openAI_batch_query_with_file,
@@ -278,6 +279,7 @@ importer::ai_provider_impl _chatgpt_api_provider = {
importer::ai_provider_impl _gemini_api_provider = {
"Gemini",
"",
+ "img/logos/gemini",
0,
0,
0,
@@ -287,6 +289,7 @@ importer::ai_provider_impl _gemini_api_provider = {
importer::ai_provider_impl _perplexity_api_provider = {
"Perplexity",
"",
+ "img/logos/perplexity",
0,
0,
0,
diff --git a/src/ui/imgui_extensions.cpp b/src/ui/imgui_extensions.cpp
index 4112568..e3e917c 100644
--- a/src/ui/imgui_extensions.cpp
+++ b/src/ui/imgui_extensions.cpp
@@ -881,7 +881,7 @@ namespace ImGui
//style->WindowMinSize = ImVec2( 160, 20 );
style->FramePadding = ImVec2( 10, 5 );
- style->ItemSpacing = ImVec2( 6, 2 );
+ style->ItemSpacing = ImVec2( 6, 5 );
style->ItemInnerSpacing = ImVec2( 6, 4 );
style->Alpha = 1.0f;
style->DisabledAlpha = 0.45f;
diff --git a/src/ui/ui_invoices.cpp b/src/ui/ui_invoices.cpp
index c222050..e6ee7d3 100644
--- a/src/ui/ui_invoices.cpp
+++ b/src/ui/ui_invoices.cpp
@@ -75,10 +75,10 @@ void draw_invoice_items_form(invoice* invoice, bool outgoing, bool viewing_only
if (ImGui::BeginTable("TableBillingItems", 9, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
ImGui::TableSetupColumn("##actions", ImGuiTableColumnFlags_WidthFixed, 30);
- ImGui::TableSetupColumn(locale::get("invoice.table.amount"), ImGuiTableColumnFlags_WidthFixed, 110);
+ ImGui::TableSetupColumn(locale::get("invoice.table.amount"), ImGuiTableColumnFlags_WidthFixed, 90);
ImGui::TableSetupColumn(locale::get("invoice.table.description"));
ImGui::TableSetupColumn(locale::get("invoice.table.price"), ImGuiTableColumnFlags_WidthFixed, 100);
- ImGui::TableSetupColumn(locale::get("invoice.table.discount"), ImGuiTableColumnFlags_WidthFixed, 130);
+ ImGui::TableSetupColumn(locale::get("invoice.table.discount"), ImGuiTableColumnFlags_WidthFixed, 110);
ImGui::TableSetupColumn(locale::get("invoice.table.net"), ImGuiTableColumnFlags_WidthFixed, 100);
ImGui::TableSetupColumn(locale::get("invoice.table.tax%"), ImGuiTableColumnFlags_WidthFixed, 120);
ImGui::TableSetupColumn(locale::get("invoice.table.tax"), ImGuiTableColumnFlags_WidthFixed, 100);
diff --git a/src/ui/ui_settings.cpp b/src/ui/ui_settings.cpp
index f0a0a53..95f24e3 100644
--- a/src/ui/ui_settings.cpp
+++ b/src/ui/ui_settings.cpp
@@ -16,6 +16,7 @@
#include "ui.hpp"
+#include "assets.hpp"
#include "strops.hpp"
#include "memops.hpp"
#include "locales.hpp"
@@ -264,18 +265,20 @@ static void draw_ai_service_ui()
if (ImGui::CollapsingHeader(locale::get("settings.services.ai_service")))
{
+ const char* ai_service_icons[AI_PROVIDER_END];
const char* ai_service_names[AI_PROVIDER_END];
for (u32 i = 0; i < AI_PROVIDER_END; i++) {
- ai_service_names[i] = importer::get_ai_provider_implementation((ai_provider)i).provider_name;
+ importer::ai_provider_impl impl = importer::get_ai_provider_implementation((ai_provider)i);
+ ai_service_names[i] = impl.provider_name;
+ ai_service_icons[i] = impl.icon_name;
}
- if (ImGui::BeginCombo(locale::get("settings.services.ai_service.provider"), ai_service_names[new_ai_service.provider]))
- {
+ if (ImGui::BeginCombo(locale::get("settings.services.ai_service.provider"), NULL, 1 << 20)) {
for (u32 n = 0; n < AI_PROVIDER_END; n++)
{
+ ImGui::PushID(n);
bool is_selected = n == (uint32_t)new_ai_service.provider;
- if (ImGui::Selectable(ai_service_names[n], is_selected)) {
-
+ if (ImGui::Selectable("##emailProviderOption", is_selected)) {
ai_service service = administration::get_ai_service((ai_provider)n);
new_ai_service.provider = (ai_provider)n;
strops::copy(new_ai_service.model_name, service.model_name, MAX_LEN_SHORT_DESC);
@@ -284,9 +287,25 @@ static void draw_ai_service_ui()
model_request = 0;
set_model_on_load = strops::length(new_ai_service.model_name) == 0;
}
+
+ ImGui::SameLine();
+ ImGui::Image(assets::load_image(ai_service_icons[n]), ImVec2(16, 16));
+
+ ImGui::SameLine();
+ ImGui::Text(ai_service_names[n]);
+
+ ImGui::PopID();
}
ImGui::EndCombo();
}
+
+ ImGui::BeginComboPreview();
+ {
+ ImGui::Image(assets::load_image(ai_service_icons[new_ai_service.provider]), ImVec2(16, 16));
+ ImGui::Text(ai_service_names[new_ai_service.provider]);
+
+ ImGui::EndComboPreview();
+ }
ImGui::InputTextWithHint(locale::get("settings.services.ai_service.pubkey"), locale::get("settings.services.ai_service.pubkey"),
new_ai_service.api_key_public, sizeof(new_ai_service.api_key_public));
@@ -348,6 +367,7 @@ static void draw_ai_service_ui()
administration_writer::set_write_completed_event_callback(0);
administration::set_active_ai_service(new_ai_service);
}
+ ImGui::Spacing();
}
}
@@ -356,21 +376,40 @@ static void draw_email_service_ui()
if (ImGui::CollapsingHeader(locale::get("settings.services.email_service")))
{
const char* email_service_names[EMAIL_PROVIDER_END];
+ const char* email_service_images[EMAIL_PROVIDER_END];
for (u32 i = 0; i < EMAIL_PROVIDER_END; i++) {
- email_service_names[i] = exporter::get_email_provider_implementation((email_provider)i).provider_name;
+ exporter::email_provider_impl impl = exporter::get_email_provider_implementation((email_provider)i);
+ email_service_names[i] = impl.provider_name;
+ email_service_images[i] = impl.icon_name;
}
-
- if (ImGui::BeginCombo(locale::get("settings.services.email_service.provider"), email_service_names[new_ai_service.provider]))
- {
+
+ if (ImGui::BeginCombo("##emailProviderDropdown", NULL, 1 << 20)) {
for (u32 n = 0; n < EMAIL_PROVIDER_END; n++)
{
+ ImGui::PushID(n);
bool is_selected = n == (uint32_t)new_email_service.provider;
- if (ImGui::Selectable(email_service_names[n], is_selected)) {
+ if (ImGui::Selectable("##emailProviderOption", is_selected)) {
new_email_service.provider = (email_provider)n;
}
+
+ ImGui::SameLine();
+ ImGui::Image(assets::load_image(email_service_images[n]), ImVec2(16, 16));
+
+ ImGui::SameLine();
+ ImGui::Text(email_service_names[n]);
+
+ ImGui::PopID();
}
ImGui::EndCombo();
}
+
+ ImGui::BeginComboPreview();
+ {
+ ImGui::Image(assets::load_image(email_service_images[new_email_service.provider]), ImVec2(16, 16));
+ ImGui::Text(email_service_names[new_email_service.provider]);
+
+ ImGui::EndComboPreview();
+ }
ImGui::InputTextWithHint(locale::get("settings.services.email_service.pubkey"),
locale::get("settings.services.email_service.pubkey"), new_email_service.api_key, sizeof(new_email_service.api_key));
@@ -381,6 +420,7 @@ static void draw_email_service_ui()
administration_writer::set_write_completed_event_callback(0);
administration::set_email_service(new_email_service);
}
+ ImGui::Spacing();
}
}
diff --git a/src/ui/ui_setup.cpp b/src/ui/ui_setup.cpp
index 6140d5a..99d6d9f 100644
--- a/src/ui/ui_setup.cpp
+++ b/src/ui/ui_setup.cpp
@@ -14,53 +14,20 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#include <GLFW/glfw3.h>
#include <imgui/imgui.h>
#include <IconFontCppHeaders/IconsFontAwesome5.h>
#include "ui.hpp"
+#include "assets.hpp"
#include "strops.hpp"
#include "config.hpp"
#include "locales.hpp"
#include "administration_writer.hpp"
#include "administration_reader.hpp"
-#include <stb/stb_image.h>
-
-int ui::load_image(const char* filename)
-{
- int width, height, channels;
- unsigned char* data = stbi_load(filename, &width, &height, &channels, 0);
-
- if (!data)
- {
- return 0;
- }
-
- GLuint textureID;
- glGenTextures(1, &textureID);
- glBindTexture(GL_TEXTURE_2D, textureID);
-
- // Set texture parameters
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
- // Upload texture data
- GLenum format = (channels == 4) ? GL_RGBA : GL_RGB;
- glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
- //glGenerateMipmap(GL_TEXTURE_2D);
-
- // Free image memory
- stbi_image_free(data);
-
- return textureID;
-}
-
void ui::draw_setup()
{
- static int img = load_image("/home/aldrik/Projects/open-books/build/splash.png");
+ static int img = assets::load_image("img/splash");
ImVec2 area = ImGui::GetContentRegionAvail();
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0f, 0.5f));