| |
| |
| |
| |
| |
| |
|
|
| #include <curl/curl.h> |
|
|
| #include <iostream> |
| #include <limits> |
| #include <sstream> |
| #include <stdexcept> |
| #include <string> |
|
|
| struct LLMModel { |
| const char* name; |
| const char* id; |
| }; |
|
|
| const LLMModel MODELS[] = { |
| {"Laguna S 2.1 (Free)", "poolside/laguna-s-2.1:free"}, |
| {"Gemini 3.6 Flash (Paid)", "google/gemini-3.6-flash"}, |
| {"Claude Opus 5 (Paid)", "anthropic/claude-opus-5"}, |
| {"Claude Fable 5 (Paid)", "anthropic/claude-fable-5"}, |
| {"GPT-5.6 Sol (Paid)", "openai/gpt-5.6-sol"}, |
| {"GPT-5.6 Luna (Paid)", "openai/gpt-5.6-luna"}, |
| {"Nemotron 3 Ultra (Free)", "nvidia/nemotron-3-ultra-550b-a55b:free"} |
| }; |
|
|
| const std::size_t MODEL_COUNT = sizeof(MODELS) / sizeof(MODELS[0]); |
|
|
| class CurlGlobal { |
| public: |
| CurlGlobal() { |
| const CURLcode result = curl_global_init(CURL_GLOBAL_DEFAULT); |
| if (result != CURLE_OK) { |
| throw std::runtime_error(std::string("Failed to initialize libcurl: ") + |
| curl_easy_strerror(result)); |
| } |
| } |
|
|
| ~CurlGlobal() { curl_global_cleanup(); } |
|
|
| CurlGlobal(const CurlGlobal&) = delete; |
| CurlGlobal& operator=(const CurlGlobal&) = delete; |
| }; |
|
|
| class CurlHandle { |
| public: |
| CurlHandle() : handle_(curl_easy_init()) { |
| if (!handle_) { |
| throw std::runtime_error("Failed to create libcurl handle"); |
| } |
| } |
|
|
| ~CurlHandle() { curl_easy_cleanup(handle_); } |
|
|
| CurlHandle(const CurlHandle&) = delete; |
| CurlHandle& operator=(const CurlHandle&) = delete; |
|
|
| CURL* get() const { return handle_; } |
|
|
| private: |
| CURL* handle_; |
| }; |
|
|
| class CurlHeaders { |
| public: |
| ~CurlHeaders() { curl_slist_free_all(headers_); } |
|
|
| void append(const std::string& header) { |
| curl_slist* updated = curl_slist_append(headers_, header.c_str()); |
| if (!updated) { |
| throw std::runtime_error("Failed to allocate HTTP headers"); |
| } |
| headers_ = updated; |
| } |
|
|
| curl_slist* get() const { return headers_; } |
|
|
| CurlHeaders(const CurlHeaders&) = delete; |
| CurlHeaders& operator=(const CurlHeaders&) = delete; |
| CurlHeaders() = default; |
|
|
| private: |
| curl_slist* headers_ = nullptr; |
| }; |
|
|
| std::size_t writeResponse(char* data, std::size_t size, std::size_t count, void* output) { |
| const std::size_t bytes = size * count; |
| static_cast<std::string*>(output)->append(data, bytes); |
| return bytes; |
| } |
|
|
| void requireCurlOption(CURLcode result, const char* option) { |
| if (result != CURLE_OK) { |
| throw std::runtime_error(std::string("Failed to set ") + option + ": " + |
| curl_easy_strerror(result)); |
| } |
| } |
|
|
| std::string askLLM(const std::string& prompt, const LLMModel& model) { |
| CurlHandle curl; |
| CurlHeaders headers; |
| std::string response; |
|
|
| headers.append("Content-Type: text/plain; charset=utf-8"); |
| headers.append(std::string("X-Model: ") + model.id); |
|
|
| requireCurlOption(curl_easy_setopt(curl.get(), CURLOPT_URL, |
| "https://ttl0110-llm-proxy.hf.space/ask"), |
| "request URL"); |
| requireCurlOption(curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, headers.get()), |
| "request headers"); |
| requireCurlOption(curl_easy_setopt(curl.get(), CURLOPT_POST, 1L), "POST method"); |
| requireCurlOption(curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, prompt.c_str()), |
| "request body"); |
| requireCurlOption(curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDSIZE_LARGE, |
| static_cast<curl_off_t>(prompt.size())), |
| "request body size"); |
| requireCurlOption(curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, writeResponse), |
| "response callback"); |
| requireCurlOption(curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response), |
| "response destination"); |
| requireCurlOption(curl_easy_setopt(curl.get(), CURLOPT_CONNECTTIMEOUT, 30L), |
| "connection timeout"); |
| requireCurlOption(curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT, 120L), |
| "request timeout"); |
| requireCurlOption(curl_easy_setopt(curl.get(), CURLOPT_USERAGENT, |
| "LLM-Proxy-Cpp-Client/1.0"), |
| "user agent"); |
|
|
| const CURLcode result = curl_easy_perform(curl.get()); |
| if (result != CURLE_OK) { |
| throw std::runtime_error(std::string("Request failed: ") + curl_easy_strerror(result)); |
| } |
|
|
| long statusCode = 0; |
| const CURLcode statusResult = curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &statusCode); |
| if (statusResult != CURLE_OK) { |
| throw std::runtime_error(std::string("Failed to read HTTP status: ") + |
| curl_easy_strerror(statusResult)); |
| } |
| if (statusCode < 200 || statusCode >= 300) { |
| throw std::runtime_error("Server returned HTTP " + std::to_string(statusCode) + |
| (response.empty() ? "" : ": " + response)); |
| } |
|
|
| return response; |
| } |
|
|
| std::size_t selectModel() { |
| std::cout << "Chon Model LLM:\n"; |
| for (std::size_t i = 0; i < MODEL_COUNT; ++i) { |
| std::cout << i + 1 << ". " << MODELS[i].name << '\n'; |
| } |
|
|
| int choice = 0; |
| while (choice < 1 || choice > static_cast<int>(MODEL_COUNT)) { |
| std::cout << "Nhap lua chon (1-" << MODEL_COUNT << "): "; |
| if (!(std::cin >> choice)) { |
| std::cin.clear(); |
| } |
| std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); |
| } |
| return static_cast<std::size_t>(choice - 1); |
| } |
|
|
| int main() { |
| try { |
| CurlGlobal curlGlobal; |
|
|
| std::cout << "=== C++ LLM Assistant (HuggingFace Proxy) ===\n\n"; |
| const LLMModel& selectedModel = MODELS[selectModel()]; |
| std::cout << "Ban da chon: " << selectedModel.name << "\n\n"; |
|
|
| std::cout << "Nhap prompt prefix (bo trong neu khong dung): "; |
| std::string promptPrefix; |
| std::getline(std::cin, promptPrefix); |
|
|
| std::cout << "Huong dan: Nhap prompt cua ban. Nhap 'SUBMIT' o dong cuoi de gui.\n"; |
| std::cout << "Nhap 'EXIT' o mot dong rieng de thoat.\n"; |
|
|
| while (true) { |
| std::cout << "\nBan [" << selectedModel.name << "]:\n"; |
|
|
| std::ostringstream prompt; |
| std::string line; |
| bool exitRequested = false; |
| while (std::getline(std::cin, line)) { |
| if (line == "EXIT") { |
| exitRequested = true; |
| break; |
| } |
| if (line == "SUBMIT") { |
| break; |
| } |
| if (prompt.tellp() > 0) { |
| prompt << '\n'; |
| } |
| prompt << line; |
| } |
|
|
| if (exitRequested || !std::cin) { |
| break; |
| } |
|
|
| const std::string promptText = prompt.str(); |
| if (promptText.empty()) { |
| std::cout << "Prompt trong, vui long nhap lai.\n"; |
| continue; |
| } |
|
|
| const std::string fullPrompt = promptPrefix.empty() |
| ? promptText |
| : promptPrefix + "\n\n" + promptText; |
|
|
| std::cout << "Thinking...\n"; |
| try { |
| std::cout << "\n[LLM]: " << askLLM(fullPrompt, selectedModel) << '\n'; |
| } catch (const std::exception& error) { |
| std::cout << "\nError: " << error.what() << '\n'; |
| } |
| std::cout << "-------------------------------------------\n"; |
| } |
|
|
| std::cout << "Tam biet!\n"; |
| return 0; |
| } catch (const std::exception& error) { |
| std::cerr << "Fatal error: " << error.what() << '\n'; |
| return 1; |
| } |
| } |
|
|