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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
add_executable(cpp-workspace-app
src/main.cpp
src/ollama_client.cpp
src/progress_spinner.cpp
)

find_package(fmt REQUIRED)
Expand Down
28 changes: 14 additions & 14 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,38 @@
#include <iostream>

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;
std::string model = "gemma3n:latest";
std::string prompt = "Hey! What's up!";
bool stream = false;

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

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("🛈 model = %s\n", model.c_str());
printf("🛈 prompt = %s\n", prompt.c_str());
printf("🛈 stream = %s\n", stream ? "true" : "false");
printf("\n");

if (prompt_arg.length() == 0) {
if (prompt.length() == 0) {
printf("Enter your prompt: ");
getline(std::cin, prompt_arg);
getline(std::cin, prompt);
}

OllamaClient client = OllamaClient();

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

client.curl_request(config);

Expand Down
27 changes: 24 additions & 3 deletions src/ollama_client.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#include "ollama_client.hpp"
#include <algorithm>
#include <curl/curl.h>
#include <iostream>
#include <nlohmann/json.hpp>
#include <sstream>
#include <string>

using nlohmann::json_abi_v3_11_2::json;

Expand Down Expand Up @@ -30,7 +32,7 @@ void OllamaClient::check_ollama() {
"https://docs.ollama.com/linux\n");
exit(0);
} else {
printf("[ok] Ollama binary exists.\n");
printf("✅ Ollama binary exists.\n");
}
};

Expand All @@ -41,15 +43,31 @@ void OllamaClient::init_curl() {
fprintf(stderr, "Failed to initialize CURL.\n");
exit(1);
} else {
printf("[ok] CURL initialized.\n");
printf("✅ CURL initialized.\n");
}
}

// Trims leading whitespace and trailing whitespace from a json response.
std::string OllamaClient::trim_response(std::string response) {
response.erase(response.begin(),
std::find_if(response.begin(), response.end(),
[](unsigned char ch) { return !std::isspace(ch); }));

response.erase(std::find_if(response.rbegin(), response.rend(),
[](unsigned char ch) { return !std::isspace(ch); })
.base(),
response.end());
return response;
}

// 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;

std::cout << "🦙 llama:\n"
<< this->trim_response(response_json["response"].get<std::string>()) << "\n---\n"
<< std::endl;
} catch (const json::exception& err) {
fprintf(stderr, "JSON parse error: %s\n", err.what());
}
Expand Down Expand Up @@ -97,6 +115,9 @@ void OllamaClient::curl_request(OllamaPromptConfig config) {
curl_easy_setopt(this->curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(this->curl, CURLOPT_WRITEDATA, &response);

curl_easy_setopt(this->curl, CURLOPT_XFERINFOFUNCTION, ProgressSpinner::progress_callback);
curl_easy_setopt(this->curl, CURLOPT_NOPROGRESS, 0L);

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

Expand Down
5 changes: 5 additions & 0 deletions src/ollama_client.hpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "progress_spinner.hpp"
#include <curl/curl.h>
#include <iostream>

Expand All @@ -12,9 +13,13 @@ class OllamaClient {
CURL* curl;
std::string url;
struct curl_slist* headers;
ProgressSpinner spinner{ProgressSpinner()};

void check_ollama();
void init_curl();

std::string trim_response(std::string response);

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

Expand Down
73 changes: 73 additions & 0 deletions src/progress_spinner.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include "progress_spinner.hpp"
#include <atomic>
#include <chrono>
#include <curl/curl.h>
#include <iostream>
#include <thread>

ProgressSpinner::ProgressSpinner() {
this->active = false;
}

ProgressSpinner::ProgressSpinner(bool activate) {
if (activate == true) {
this->active = activate;
start();
}
}

ProgressSpinner::~ProgressSpinner() {
stop();
}

// Starts the progress spinner.
void ProgressSpinner::start() {
active = true;
spinner_thread = std::thread([this]() {
const char spinner_chars[] = {'|', '/', '-', '\\'};
int i = 0;
while (active) {
std::cout << "\r" << spinner_chars[i++ % 4] << " Loading..." << std::flush;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::cout << "\r✓ Complete\n" << std::endl;
});
}

// Stops the progress spinner.
void ProgressSpinner::stop() {
active = false;
if (spinner_thread.joinable()) {
spinner_thread.join();
}
}

/**
* CURL progress callback.
*
* @example
* ```cpp
* CURL* curl = curl_easy_init();
* curl_easy_setopt(this->curl, CURLOPT_XFERINFOFUNCTION, ProgressSpinner::progress_callback);
* curl_easy_setopt(this->curl, CURLOPT_NOPROGRESS, 0L);
* curl_easy_perform(curl);
* ```
*/
int ProgressSpinner::progress_callback(void* clientp, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow) {
if (dltotal > 0) {
// If size is known, show progress bar
int progress = (dlnow * 100) / dltotal;
if (progress == 100) {
std::cout << "\r✓ Complete " << std::flush;
} else {
std::cout << "\rProgress: " << progress << "% " << std::flush;
}
} else {
// Indeterminate - show spinner
const char spinner_chars[] = {'|', '/', '-', '\\'};
static int i = 0;
std::cout << "\r" << spinner_chars[i++ % 4] << " Loading..." << std::flush;
}
return 0;
}
39 changes: 39 additions & 0 deletions src/progress_spinner.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#pragma once

#ifndef PROGRESS_SPINNER_HPP
#define PROGRESS_SPINNER_HPP

#include <atomic>
#include <curl/curl.h>
#include <iostream>
#include <thread>

/**
* Progress spinner for asynchronous operations.
*
* @example manual mode
* ```cpp
* ProgressSpinner spinner = ProgressSpinner();
* spinner.start();
* curl_easy_perform(curl_handle);
* spinner.stop();
* ```
*/
class ProgressSpinner {
private:
std::atomic<bool> active{false};
std::thread spinner_thread;

public:
ProgressSpinner();
explicit ProgressSpinner(bool activate);
~ProgressSpinner();

void start();
void stop();

static int progress_callback(void* clientp, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow);
};

#endif
Loading