Skip to content
This repository was archived by the owner on Jul 4, 2025. It is now read-only.

Commit cb6c3d8

Browse files
authored
Merge pull request #360 from janhq/303-feat-nitro-chat-completion-with-image-supporting-local-image-path
feat: Support for LVM with openai compatible API with local image path
2 parents 92b5a5c + 236e3ba commit cb6c3d8

File tree

3 files changed

+108
-6
lines changed

3 files changed

+108
-6
lines changed

controllers/llamaCPP.cc

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -222,18 +222,33 @@ void llamaCPP::chatCompletion(
222222
for (auto content_piece : message["content"]) {
223223
role = user_prompt;
224224

225+
json content_piece_image_data;
226+
content_piece_image_data["data"] = "";
227+
225228
auto content_piece_type = content_piece["type"].asString();
226229
if (content_piece_type == "text") {
227230
auto text = content_piece["text"].asString();
228231
formatted_output += text;
229232
} else if (content_piece_type == "image_url") {
230233
auto image_url = content_piece["image_url"]["url"].asString();
231-
auto base64_image_data = nitro_utils::extractBase64(image_url);
232-
LOG_INFO << base64_image_data;
233-
formatted_output += "[img-" + std::to_string(no_images) + "]";
234-
235-
json content_piece_image_data;
234+
std::string base64_image_data;
235+
if (image_url.find("http") != std::string::npos) {
236+
LOG_INFO << "Remote image detected but not supported yet";
237+
} else if (image_url.find("data:image") != std::string::npos) {
238+
LOG_INFO << "Base64 image detected";
239+
base64_image_data = nitro_utils::extractBase64(image_url);
240+
LOG_INFO << base64_image_data;
241+
} else {
242+
LOG_INFO << "Local image detected";
243+
nitro_utils::processLocalImage(
244+
image_url, [&](const std::string &base64Image) {
245+
base64_image_data = base64Image;
246+
});
247+
LOG_INFO << base64_image_data;
248+
}
236249
content_piece_image_data["data"] = base64_image_data;
250+
251+
formatted_output += "[img-" + std::to_string(no_images) + "]";
237252
content_piece_image_data["id"] = no_images;
238253
data["image_data"].push_back(content_piece_image_data);
239254
no_images++;

llama.cpp

utils/nitro_utils.h

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
#include "random"
44
#include "string"
55
#include <algorithm>
6+
#include <drogon/HttpClient.h>
67
#include <drogon/HttpResponse.h>
8+
#include <fstream>
79
#include <iostream>
810
#include <ostream>
911
#include <regex>
12+
#include <vector>
1013
// Include platform-specific headers
1114
#ifdef _WIN32
1215
#include <winsock2.h>
@@ -32,6 +35,90 @@ inline std::string extractBase64(const std::string &input) {
3235
return "";
3336
}
3437

38+
// Helper function to encode data to Base64
39+
inline std::string base64Encode(const std::vector<unsigned char> &data) {
40+
static const char encodingTable[] =
41+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
42+
std::string encodedData;
43+
int i = 0;
44+
int j = 0;
45+
unsigned char array3[3];
46+
unsigned char array4[4];
47+
48+
for (unsigned char c : data) {
49+
array3[i++] = c;
50+
if (i == 3) {
51+
array4[0] = (array3[0] & 0xfc) >> 2;
52+
array4[1] = ((array3[0] & 0x03) << 4) + ((array3[1] & 0xf0) >> 4);
53+
array4[2] = ((array3[1] & 0x0f) << 2) + ((array3[2] & 0xc0) >> 6);
54+
array4[3] = array3[2] & 0x3f;
55+
56+
for (i = 0; i < 4; i++)
57+
encodedData += encodingTable[array4[i]];
58+
i = 0;
59+
}
60+
}
61+
62+
if (i) {
63+
for (j = i; j < 3; j++)
64+
array3[j] = '\0';
65+
66+
array4[0] = (array3[0] & 0xfc) >> 2;
67+
array4[1] = ((array3[0] & 0x03) << 4) + ((array3[1] & 0xf0) >> 4);
68+
array4[2] = ((array3[1] & 0x0f) << 2) + ((array3[2] & 0xc0) >> 6);
69+
70+
for (j = 0; j < i + 1; j++)
71+
encodedData += encodingTable[array4[j]];
72+
73+
while (i++ < 3)
74+
encodedData += '=';
75+
}
76+
77+
return encodedData;
78+
}
79+
80+
// Function to load an image and convert it to Base64
81+
inline std::string imageToBase64(const std::string &imagePath) {
82+
std::ifstream imageFile(imagePath, std::ios::binary);
83+
if (!imageFile.is_open()) {
84+
throw std::runtime_error("Could not open the image file.");
85+
}
86+
87+
std::vector<unsigned char> buffer(std::istreambuf_iterator<char>(imageFile),
88+
{});
89+
return base64Encode(buffer);
90+
}
91+
92+
// Helper function to generate a unique filename
93+
inline std::string generateUniqueFilename(const std::string &prefix,
94+
const std::string &extension) {
95+
// Get current time as a timestamp
96+
auto now = std::chrono::system_clock::now();
97+
auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now);
98+
auto epoch = now_ms.time_since_epoch();
99+
auto value = std::chrono::duration_cast<std::chrono::milliseconds>(epoch);
100+
101+
// Generate a random number
102+
std::random_device rd;
103+
std::mt19937 gen(rd());
104+
std::uniform_int_distribution<> dis(1000, 9999);
105+
106+
std::stringstream ss;
107+
ss << prefix << value.count() << "_" << dis(gen) << extension;
108+
return ss.str();
109+
}
110+
111+
inline void
112+
processLocalImage(const std::string &localPath,
113+
std::function<void(const std::string &)> callback) {
114+
try {
115+
std::string base64Image = imageToBase64(localPath);
116+
callback(base64Image); // Invoke the callback with the Base64 string
117+
} catch (const std::exception &e) {
118+
std::cerr << "Error during processing: " << e.what() << std::endl;
119+
}
120+
}
121+
35122
inline std::vector<std::string> listFilesInDir(const std::string &path) {
36123
std::vector<std::string> files;
37124

0 commit comments

Comments
 (0)