Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,8 @@ core

# environment
.env

# ignore all files except `*.cpp` and `*.hpp` in the source directory
src/*
!src/*.cpp
!src/*.hpp
7 changes: 5 additions & 2 deletions .vscode/setting.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@
"shellcheck.executablePath": "shellcheck",
"shellcheck.ignorePatterns": {
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/node_modules/*/**": true
"**/.git/subtree-cache/**": true
},
"shellcheck.run": "onType",
"shellcheck.useWorkspaceRootAsCwd": true,
Expand All @@ -46,6 +45,10 @@
"clang-format.fallbackStyle": "LLVM",
"clang-format.language.cpp.enable": true,
"clang-format.style": "file",
"C_Cpp.default.cppStandard": "gnu++20",
"C_Cpp.default.cStandard": "gnu23",
"C_Cpp.default.compileCommands": "${workspaceFolder}/build/Release/compile_commands.json",
"C_Cpp.intelliSenseEngine": "Tag Parser",
"files.defaultLanguage": "cpp",
"files.hotExit": "onExit",
"yaml.format.enable": true,
Expand Down
14 changes: 12 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,24 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

add_executable(cpp-workspace-app
src/main.cpp
src/ollama_client.cpp
)

find_package(fmt CONFIG REQUIRED)
target_link_libraries(cpp-workspace-app PRIVATE fmt::fmt)
find_package(fmt REQUIRED)
find_package(CURL REQUIRED)
find_package(nlohmann_json REQUIRED)
target_link_libraries(cpp-workspace-app
PRIVATE
fmt::fmt
CURL::libcurl
nlohmann_json::nlohmann_json
)

# enable_testing()
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,23 @@ or
```bash
cz c
```

## FAQ

### Manual build

```bash
rm -rf ./build/*
conan install . --build=missing
cmake --preset conan-release
cmake --build --preset conan-release
```

or

```bash
rm -rf ./build/* ./CMakeUserPresets.json
conan install . --build=missing
cmake --preset conan-release
cmake --build --preset conan-release
```
2 changes: 2 additions & 0 deletions conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class AppConan(ConanFile):

def requirements(self):
self.requires("fmt/11.2.0")
self.requires("libcurl/8.5.0")
self.requires("nlohmann_json/3.11.2")

def layout(self):
cmake_layout(self)
46 changes: 33 additions & 13 deletions src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
#include <format>
#include "ollama_client.hpp"
#include <iostream>

using std::cout;
using std::format;
int main(int argc, const char* argv[]) {
std::string model_arg = "gemma3n:latest";
std::string prompt_arg = "Hey! What's up!";
bool stream_arg = false;

int main() {
const char string[]{"this is a string"};

for (auto* p = string; *p; p++) {
cout << format("char = {}\n", *p);
for (int i = 1; i < argc; i += 1) {
std::string arg = argv[i];
if (arg.starts_with("--model=") == 0) {
model_arg = arg.substr(8);
} else if (arg.starts_with("--prompt=") == 0) {
prompt_arg = arg.substr(9);
} else if (arg.starts_with("--stream=") == 0) {
stream_arg = arg.substr(9) == "true" ? true : false;
}
}

for (int i{0}; string[i]; i++) {
cout << format("{} char = {}\n", i, string[i]);
}
printf("\n");
printf("model_arg = %s\n", model_arg.c_str());
printf("prompt_arg = %s\n", prompt_arg.c_str());
printf("stream_arg = %s\n", stream_arg ? "true" : "false");
printf("\n");

for (const auto& el : string) {
cout << format("element = {}\n", el);
if (prompt_arg.length() == 0) {
printf("Enter your prompt: ");
getline(std::cin, prompt_arg);
}

OllamaClient client = OllamaClient();

struct OllamaPromptConfig config;
config.model = model_arg;
config.prompt = prompt_arg;
config.stream = stream_arg;

client.curl_request(config);

return 0;
}
120 changes: 120 additions & 0 deletions src/ollama_client.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#include "ollama_client.hpp"
#include <curl/curl.h>
#include <iostream>
#include <nlohmann/json.hpp>
#include <sstream>

using nlohmann::json_abi_v3_11_2::json;

static size_t WriteCallback(char* contents, size_t size, size_t nmemb, std::string* userp) {
userp->append(contents, size * nmemb);
return size * nmemb;
}

// Create an instance of OllamaClient.
OllamaClient::OllamaClient() {
this->url = "http://localhost:11434/api/generate";
this->headers = nullptr;

this->check_ollama();

this->init_curl();

printf("\n");
}

// Checks whether Ollama binary exists on the file system.
void OllamaClient::check_ollama() {
if (system("which ollama > /dev/null 2>&1")) {
fprintf(stderr, "Ollama binary does not exist.\nInstallation instructions: "
"https://docs.ollama.com/linux\n");
exit(0);
} else {
printf("[ok] Ollama binary exists.\n");
}
};

// Initialize CURL.
void OllamaClient::init_curl() {
this->curl = curl_easy_init();
if (!this->curl) {
fprintf(stderr, "Failed to initialize CURL.\n");
exit(1);
} else {
printf("[ok] CURL initialized.\n");
}
}

// Process a response.
void OllamaClient::process_response(std::string response) {
try {
json response_json = json::parse(response);
std::cout << "Response: " << response_json["response"].get<std::string>() << std::endl;
} catch (const json::exception& err) {
fprintf(stderr, "JSON parse error: %s\n", err.what());
}
}

// Process a streaming response.
void OllamaClient::process_response_stream(std::string response) {
std::istringstream stream(response);
std::string line;

std::cout << "Response: ";
std::cout.flush();

while (std::getline(stream, line)) {
if (!line.empty()) {
try {
json chunk = json::parse(line);
std::string text = chunk["response"].get<std::string>();
std::cout << text;
std::cout.flush();

if (chunk["done"].get<bool>()) {
break;
}
} catch (const json::exception& err) {
fprintf(stderr, "JSON parse error: %s\n", err.what());
}
}
}

std::cout << std::endl;
}

// Send a request to the Ollama endpoint using CURL.
void OllamaClient::curl_request(OllamaPromptConfig config) {
json request_payload = {
{"model", config.model}, {"prompt", config.prompt}, {"stream", config.stream}};

std::string post_data = request_payload.dump();

std::string response;

curl_easy_setopt(this->curl, CURLOPT_URL, this->url.c_str());
curl_easy_setopt(this->curl, CURLOPT_POSTFIELDS, post_data.c_str());
curl_easy_setopt(this->curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(this->curl, CURLOPT_WRITEDATA, &response);

this->headers = curl_slist_append(this->headers, "Content-Type: application/json");
curl_easy_setopt(this->curl, CURLOPT_HTTPHEADER, this->headers);

CURLcode res = curl_easy_perform(curl);

if (res != CURLE_OK) {
fprintf(stderr, "CURL error: %s\n", curl_easy_strerror(res));
curl_slist_free_all(this->headers);
curl_easy_cleanup(this->curl);
exit(1);
}

if (config.stream) {
this->process_response_stream(response);
} else {
this->process_response(response);
}

curl_slist_free_all(this->headers);
curl_easy_cleanup(this->curl);
}
25 changes: 25 additions & 0 deletions src/ollama_client.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include <curl/curl.h>
#include <iostream>

struct OllamaPromptConfig {
std::string model;
std::string prompt;
bool stream;
};

class OllamaClient {
private:
CURL* curl;
std::string url;
struct curl_slist* headers;

void check_ollama();
void init_curl();
void process_response(std::string response);
void process_response_stream(std::string response);

public:
OllamaClient();

void curl_request(OllamaPromptConfig config);
};
14 changes: 7 additions & 7 deletions tools/shell/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ log_error() {

# Extract version from CMakeLists.txt
get_version() {
grep -oP 'project\(\s*\w+\s+VERSION\s+\K[0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt 2>/dev/null || echo "0.0.0"
grep -oP 'project\(\s*[\w-]+\s+VERSION\s+\K[0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt 2>/dev/null || echo "0.0.0"
}

# Main build script
Expand Down Expand Up @@ -133,15 +133,15 @@ main() {
fi

log_info "Building project..."
if cmake --build "$BUILD_DIR" --config "$BUILD_TYPE"; then
if cmake --build "$BUILD_DIR/$BUILD_TYPE" --config "$BUILD_TYPE"; then
log_success "Build completed"
else
log_error "Build failed"
exit 1
fi

log_info "Running tests..."
if ctest --test-dir "$BUILD_DIR" --output-on-failure; then
if ctest --test-dir "$BUILD_DIR/$BUILD_TYPE" --output-on-failure; then
log_success "All tests passed"
else
log_warning "Some tests failed (continuing)"
Expand All @@ -150,9 +150,9 @@ main() {
local BIN_NAME
BIN_NAME=cpp-workspace-app

if [ -f "$BUILD_DIR/bin/$BIN_NAME" ]; then
if [ -f "$BUILD_DIR/$BUILD_TYPE/bin/$BIN_NAME" ]; then
log_info "Running executable..."
"$BUILD_DIR/bin/$BIN_NAME"
"$BUILD_DIR/$BUILD_TYPE/bin/$BIN_NAME"
log_success "Executable ran successfully"
else
log_warning "Executable not found at $BUILD_DIR/bin/$BIN_NAME"
Expand All @@ -179,8 +179,8 @@ EOF
mkdir -p "$RELEASE_DIR"

# Copy executable
if [ -f "$BUILD_DIR/bin/$BIN_NAME" ]; then
cp "$BUILD_DIR/bin/$BIN_NAME" "$RELEASE_DIR/"
if [ -f "$BUILD_DIR/$BUILD_TYPE/bin/$BIN_NAME" ]; then
cp "$BUILD_DIR/$BUILD_TYPE/bin/$BIN_NAME" "$RELEASE_DIR/"
fi

# Copy documentation
Expand Down
Loading