From 85c58bbcd098bdf8e0e92c697591a2e2fbd262b7 Mon Sep 17 00:00:00 2001 From: Xuan Son Nguyen Date: Tue, 23 Jun 2026 16:19:28 +0200 Subject: [PATCH] remote server ok --- common/arg.cpp | 12 +++- common/common.h | 2 +- tools/cli/cli-view.h | 129 ++++++++++++++++++++++++++++++++++++++++-- tools/cli/cli.cpp | 124 ---------------------------------------- tools/server/main.cpp | 14 ----- 5 files changed, 134 insertions(+), 147 deletions(-) diff --git a/common/arg.cpp b/common/arg.cpp index 276dbec8ba..2a20d6ae4f 100644 --- a/common/arg.cpp +++ b/common/arg.cpp @@ -603,9 +603,8 @@ static bool common_params_parse_ex(int argc, char ** argv, common_params_context // model is required (except for server) // TODO @ngxson : maybe show a list of available models in CLI in this case - if (params.model.path.empty() - && !params.usage - && !params.completion) { + bool can_skip_model = params.usage || params.completion || !params.server_base.empty(); + if (!can_skip_model && params.model.path.empty()) { throw std::invalid_argument("error: --model is required\n"); } } @@ -1119,6 +1118,13 @@ common_params_context common_params_parser_init(common_params & params, llama_ex params.completion = true; } )); + add_opt(common_arg( + {"--server-base"}, "URL", + string_format("connect to this server instead of starting a new one, example: 'http://localhost:8080' (default: none)"), + [](common_params & params, const std::string & value) { + params.server_base = value; + } + ).set_examples({LLAMA_EXAMPLE_CLI})); add_opt(common_arg( {"--verbose-prompt"}, string_format("print a verbose prompt before generation (default: %s)", params.verbose_prompt ? "true" : "false"), diff --git a/common/common.h b/common/common.h index 381c0306c3..203de5dcb5 100644 --- a/common/common.h +++ b/common/common.h @@ -632,7 +632,7 @@ struct common_params { std::map default_template_kwargs; // CLI params - std::string server_base; + std::string server_base; // if set, connect to this server instead of starting a new one // UI configs bool ui = true; diff --git a/tools/cli/cli-view.h b/tools/cli/cli-view.h index a44a0ba240..852ae7e733 100644 --- a/tools/cli/cli-view.h +++ b/tools/cli/cli-view.h @@ -3,18 +3,137 @@ #include "common.h" #include "console.h" -// note: make this view implementation generic, so that we can move to TUI in the future if we want to -namespace view { - using completion_callback = std::function>(std::string_view, size_t)>; +#include +#include +#include +#include - static void set_completion_callback(completion_callback cb) { - console::set_completion_callback(std::move(cb)); +// TODO?: Make this reusable, enums, docs +static const std::array cmds = { + "/audio ", + "/clear", + "/exit", + "/glob ", + "/image ", + "/read ", + "/regen", + "/video ", +}; + +static std::vector> auto_completion_callback(std::string_view line, size_t cursor_byte_pos) { + std::vector> matches; + std::string cmd; + + if (line.length() > 1 && line.front() == '/' && !std::any_of(cmds.begin(), cmds.end(), [line](std::string_view prefix) { + return string_starts_with(line, prefix); + })) { + auto it = cmds.begin(); + + while ((it = std::find_if(it, cmds.end(), [line](std::string_view cmd_line) { + return string_starts_with(cmd_line, line); + })) != cmds.end()) { + matches.emplace_back(*it, it->length()); + ++it; + } + } else { + auto it = std::find_if(cmds.begin(), cmds.end(), [line](std::string_view prefix) { + return prefix.back() == ' ' && string_starts_with(line, prefix); + }); + + if (it != cmds.end()) { + cmd = *it; + } } + if (!cmd.empty() && cmd != "/glob " && line.length() >= cmd.length() && cursor_byte_pos >= cmd.length()) { + const std::string path_prefix = std::string(line.substr(cmd.length(), cursor_byte_pos - cmd.length())); + const std::string path_postfix = std::string(line.substr(cursor_byte_pos)); + auto cur_dir = std::filesystem::current_path(); + std::string cur_dir_str = cur_dir.string(); + std::string expanded_prefix = path_prefix; + +#if !defined(_WIN32) + if (string_starts_with(path_prefix, '~')) { + const char * home = std::getenv("HOME"); + if (home && home[0]) { + expanded_prefix = home + path_prefix.substr(1); + } + } + if (string_starts_with(expanded_prefix, '/')) { +#else + if (std::isalpha(expanded_prefix[0]) && expanded_prefix.find(':') == 1) { +#endif + cur_dir = std::filesystem::path(expanded_prefix).parent_path(); + cur_dir_str.clear(); + } else if (!path_prefix.empty()) { + cur_dir /= std::filesystem::path(path_prefix).parent_path(); + } + + std::error_code ec; + for (const auto & entry : std::filesystem::directory_iterator(cur_dir, ec)) { + if (ec) { + break; + } + if (!entry.exists(ec)) { + ec.clear(); + continue; + } + + const std::string path_full = entry.path().string(); + std::string path_entry = !cur_dir_str.empty() && string_starts_with(path_full, cur_dir_str) ? path_full.substr(cur_dir_str.length() + 1) : path_full; + + if (entry.is_directory(ec)) { + path_entry.push_back(std::filesystem::path::preferred_separator); + } + + if (expanded_prefix.empty() || string_starts_with(path_entry, expanded_prefix)) { + const std::string updated_line = cmd + path_entry; + matches.emplace_back(updated_line + path_postfix, updated_line.length()); + } + + if (ec) { + ec.clear(); + } + } + + if (matches.empty()) { + const std::string updated_line = cmd + path_prefix; + matches.emplace_back(updated_line + path_postfix, updated_line.length()); + } + + // Add the longest common prefix + if (!expanded_prefix.empty() && matches.size() > 1) { + const std::string_view match0(matches[0].first); + const std::string_view match1(matches[1].first); + auto it = std::mismatch(match0.begin(), match0.end(), match1.begin(), match1.end()); + size_t len = it.first - match0.begin(); + + for (size_t i = 2; i < matches.size(); ++i) { + const std::string_view matchi(matches[i].first); + auto cmp = std::mismatch(match0.begin(), match0.end(), matchi.begin(), matchi.end()); + len = std::min(len, static_cast(cmp.first - match0.begin())); + } + + const std::string updated_line = std::string(match0.substr(0, len)); + matches.emplace_back(updated_line + path_postfix, updated_line.length()); + } + + std::sort(matches.begin(), matches.end(), [](const auto & a, const auto & b) { + return a.first.compare(0, a.second, b.first, 0, b.second) < 0; + }); + } + + return matches; +} + +// note: make this view implementation generic, so that we can move to TUI in the future if we want to +namespace view { static void init(const common_params & params) { // TODO: avoid using atexit() here by making `console` a singleton console::init(params.simple_io, params.use_color); atexit([]() { console::cleanup(); }); + + console::set_completion_callback(auto_completion_callback); } struct spinner { diff --git a/tools/cli/cli.cpp b/tools/cli/cli.cpp index 2778b98c0b..e832f903e5 100644 --- a/tools/cli/cli.cpp +++ b/tools/cli/cli.cpp @@ -5,10 +5,6 @@ #include "cli-context.h" #include "cli-view.h" -#include -#include -#include -#include #include #if defined(_WIN32) @@ -32,124 +28,6 @@ static void signal_handler(int) { } #endif -// TODO?: Make this reusable, enums, docs -static const std::array cmds = { - "/audio ", - "/clear", - "/exit", - "/glob ", - "/image ", - "/read ", - "/regen", - "/video ", -}; - -static std::vector> auto_completion_callback(std::string_view line, size_t cursor_byte_pos) { - std::vector> matches; - std::string cmd; - - if (line.length() > 1 && line.front() == '/' && !std::any_of(cmds.begin(), cmds.end(), [line](std::string_view prefix) { - return string_starts_with(line, prefix); - })) { - auto it = cmds.begin(); - - while ((it = std::find_if(it, cmds.end(), [line](std::string_view cmd_line) { - return string_starts_with(cmd_line, line); - })) != cmds.end()) { - matches.emplace_back(*it, it->length()); - ++it; - } - } else { - auto it = std::find_if(cmds.begin(), cmds.end(), [line](std::string_view prefix) { - return prefix.back() == ' ' && string_starts_with(line, prefix); - }); - - if (it != cmds.end()) { - cmd = *it; - } - } - - if (!cmd.empty() && cmd != "/glob " && line.length() >= cmd.length() && cursor_byte_pos >= cmd.length()) { - const std::string path_prefix = std::string(line.substr(cmd.length(), cursor_byte_pos - cmd.length())); - const std::string path_postfix = std::string(line.substr(cursor_byte_pos)); - auto cur_dir = std::filesystem::current_path(); - std::string cur_dir_str = cur_dir.string(); - std::string expanded_prefix = path_prefix; - -#if !defined(_WIN32) - if (string_starts_with(path_prefix, '~')) { - const char * home = std::getenv("HOME"); - if (home && home[0]) { - expanded_prefix = home + path_prefix.substr(1); - } - } - if (string_starts_with(expanded_prefix, '/')) { -#else - if (std::isalpha(expanded_prefix[0]) && expanded_prefix.find(':') == 1) { -#endif - cur_dir = std::filesystem::path(expanded_prefix).parent_path(); - cur_dir_str.clear(); - } else if (!path_prefix.empty()) { - cur_dir /= std::filesystem::path(path_prefix).parent_path(); - } - - std::error_code ec; - for (const auto & entry : std::filesystem::directory_iterator(cur_dir, ec)) { - if (ec) { - break; - } - if (!entry.exists(ec)) { - ec.clear(); - continue; - } - - const std::string path_full = entry.path().string(); - std::string path_entry = !cur_dir_str.empty() && string_starts_with(path_full, cur_dir_str) ? path_full.substr(cur_dir_str.length() + 1) : path_full; - - if (entry.is_directory(ec)) { - path_entry.push_back(std::filesystem::path::preferred_separator); - } - - if (expanded_prefix.empty() || string_starts_with(path_entry, expanded_prefix)) { - const std::string updated_line = cmd + path_entry; - matches.emplace_back(updated_line + path_postfix, updated_line.length()); - } - - if (ec) { - ec.clear(); - } - } - - if (matches.empty()) { - const std::string updated_line = cmd + path_prefix; - matches.emplace_back(updated_line + path_postfix, updated_line.length()); - } - - // Add the longest common prefix - if (!expanded_prefix.empty() && matches.size() > 1) { - const std::string_view match0(matches[0].first); - const std::string_view match1(matches[1].first); - auto it = std::mismatch(match0.begin(), match0.end(), match1.begin(), match1.end()); - size_t len = it.first - match0.begin(); - - for (size_t i = 2; i < matches.size(); ++i) { - const std::string_view matchi(matches[i].first); - auto cmp = std::mismatch(match0.begin(), match0.end(), matchi.begin(), matchi.end()); - len = std::min(len, static_cast(cmp.first - match0.begin())); - } - - const std::string updated_line = std::string(match0.substr(0, len)); - matches.emplace_back(updated_line + path_postfix, updated_line.length()); - } - - std::sort(matches.begin(), matches.end(), [](const auto & a, const auto & b) { - return a.first.compare(0, a.second, b.first, 0, b.second) < 0; - }); - } - - return matches; -} - // satisfies -Wmissing-declarations int llama_cli(int argc, char ** argv); @@ -164,8 +42,6 @@ int llama_cli(int argc, char ** argv) { return 1; } - view::set_completion_callback(auto_completion_callback); - #if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) struct sigaction sigint_action; sigint_action.sa_handler = signal_handler; diff --git a/tools/server/main.cpp b/tools/server/main.cpp index b8d14e3111..7f17c56a8c 100644 --- a/tools/server/main.cpp +++ b/tools/server/main.cpp @@ -3,17 +3,3 @@ int llama_server(int argc, char ** argv); int main(int argc, char ** argv) { return llama_server(argc, argv); } - -// satisfies -Wmissing-declarations -void server_signal_handler(int signal); - -void server_signal_handler(int signal) { - if (is_terminating.test_and_set()) { - // in case it hangs, we can force terminate the server by hitting Ctrl+C twice - // this is for better developer experience, we can remove when the server is stable enough - fprintf(stderr, "Received second interrupt, terminating immediately.\n"); - exit(1); - } - - shutdown_handler(signal); -}