From f67d3a80fbe9ee1b76ec591477540b23a8202782 Mon Sep 17 00:00:00 2001 From: rfprod Date: Wed, 14 Oct 2026 13:02:20 +0400 Subject: [PATCH] feat(program): separate interactive and non-interactive output handling - [x] move string declarations before control flow in `check_ollama` and `init_curl`; - [x] extract response stream output to `UI` methods instead of direct `stdout`; - [x] add `print_message_noninteractive` method for non-interactive output mode; - [x] implement text buffering and batched output in response stream processing; - [x] add documentation comments for `UserInterface` print methods; - [x] format streaming output inline with the non-streaming; - [x] document methods declared in `user-interface.hpp`; --- src/ollama_client.cpp | 43 +++++++--- src/user-interface.cpp | 20 +++++ src/user-interface.hpp | 187 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+), 11 deletions(-) diff --git a/src/ollama_client.cpp b/src/ollama_client.cpp index bf04d1f..c5716aa 100644 --- a/src/ollama_client.cpp +++ b/src/ollama_client.cpp @@ -64,9 +64,10 @@ OllamaClient::OllamaClient(UserInterface* ui) { * Output is routed through the UserInterface (if available) or stdout/stderr. */ void OllamaClient::check_ollama() { + std::string message{}; if (system("which ollama > /dev/null 2>&1")) { - std::string message = "Ollama binary does not exist.\nInstallation instructions: " - "https://docs.ollama.com/linux\n"; + message = "Ollama binary does not exist.\nInstallation instructions: " + "https://docs.ollama.com/linux\n"; if (this->ui->is_running() == false) { fprintf(stderr, "❌ %s", message.c_str()); exit(0); @@ -75,7 +76,7 @@ void OllamaClient::check_ollama() { this->ui->end_session(EXIT_FAILURE); } } else { - std::string message = "Ollama binary exists.\n"; + message = "Ollama binary exists.\n"; if (this->ui->is_running() == false) { printf("✅ %s", message.c_str()); } else { @@ -92,8 +93,9 @@ void OllamaClient::check_ollama() { */ void OllamaClient::init_curl() { this->curl = curl_easy_init(); + std::string message{}; if (!this->curl) { - std::string message = "Failed to initialize CURL.\n"; + message = "Failed to initialize CURL.\n"; if (this->ui->is_running() == false) { fprintf(stderr, "❌ %s", message.c_str()); exit(1); @@ -102,7 +104,7 @@ void OllamaClient::init_curl() { this->ui->end_session(EXIT_FAILURE); } } else { - std::string message = "CURL initialized.\n"; + message = "CURL initialized.\n"; if (this->ui->is_running() == false) { printf("✅ %s", message.c_str()); } else { @@ -203,16 +205,30 @@ void OllamaClient::process_response_stream(std::string response) { std::istringstream stream(response); std::string line; - std::cout << "Response: "; - std::cout.flush(); + std::string accumulated{}; + + std::string text = "🦙 llama:\n"; + if (this->ui->is_running() == false) { + this->ui->print_message_noninteractive(text); + } else { + accumulated += text; + } while (std::getline(stream, line)) { if (!line.empty()) { try { json chunk = json::parse(line); - std::string text = chunk["response"].get(); - std::cout << text; - std::cout.flush(); + text = chunk["response"].get(); + if (this->ui->is_running() == false) { + this->ui->print_message_noninteractive(text); + } else { + accumulated += text; + + if (text.find('\n') != std::string::npos || accumulated.length() > 256) { + this->ui->print_message(accumulated); + accumulated.clear(); + } + } if (chunk["done"].get()) { break; @@ -230,7 +246,12 @@ void OllamaClient::process_response_stream(std::string response) { } } - std::cout << std::endl; + text = "\n---\n"; + if (this->ui->is_running() == false) { + this->ui->print_message_noninteractive(text); + } else { + this->ui->print_message(text); + } } /** diff --git a/src/user-interface.cpp b/src/user-interface.cpp index 3d4b496..21d688d 100644 --- a/src/user-interface.cpp +++ b/src/user-interface.cpp @@ -252,6 +252,9 @@ bool UserInterface::next_prompt_noninteractive(std::string* prompt) { return done; } +/** + * Prints a message using ncurses. + */ void UserInterface::print(const std::string& text, int type) { { std::lock_guard lock(this->ui_mutex); @@ -260,18 +263,35 @@ void UserInterface::print(const std::string& text, int type) { this->request_redraw(); } +/** + * Prints an error message using ncurses. + */ void UserInterface::print_error(const std::string& text) { this->print(text, UserInterface::MESSAGE_TYPE_ERROR); } +/** + * Prints a success message using ncurses. + */ void UserInterface::print_success(const std::string& text) { this->print(text, UserInterface::MESSAGE_TYPE_SUCCESS); } +/** + * Prints a normal message using ncurses. + */ void UserInterface::print_message(const std::string& text) { this->print(text, UserInterface::MESSAGE_TYPE_NORMAL); } +/** + * Prints a message using `std::cout`. + */ +void UserInterface::print_message_noninteractive(const std::string& text) { + std::cout << text; + std::cout.flush(); +} + void UserInterface::draw_output_area() { // Clear the output area for (int i = 0; i < layout.output_height; i++) { diff --git a/src/user-interface.hpp b/src/user-interface.hpp index 541f42f..805dc53 100644 --- a/src/user-interface.hpp +++ b/src/user-interface.hpp @@ -56,59 +56,246 @@ class UserInterface { std::atomic progress_percent{0}; // Initialization methods. + + /** + * Initializes the ncurses library for terminal UI rendering. + * Sets up color pairs, configures terminal mode (raw mode, no echo), + * hides cursor, enables keypad input, and initializes screen dimensions. + */ void init_ncurses(void); + + /** + * Cleans up and restores the terminal to its original state. + * Disables raw mode, re-enables echo, restores cursor visibility, + * and deallocates ncurses resources. + */ void cleanup_ncurses(void); // Renderer methods. + + /** + * Spawns a dedicated background thread that runs render_loop(). + * The thread handles periodic UI redraws without blocking input handling. + */ void start_render_thread(void); + + /** + * Signals the render thread to stop and waits for it to join. + * Sets thread_running to false and notifies the condition variable. + */ void stop_render_thread(void); + + /** + * Main loop executed by the render thread. + * Waits for redraw requests (via condition_variable), then redraws + * the entire UI if the terminal wasn't resized. Exits when thread_running is false. + * Thread-safe: uses ui_mutex to protect rendering operations. + */ void render_loop(void); // Layout manpulation methods. + + /** + * Redraws the entire user interface. + * Clears the screen and calls draw_input_area(), draw_output_area(), + * draw_spinner(), and refreshes the display. + */ void draw_ui(void); + + /** + * Draws the progress spinner at the designated position. + * Displays an animated character based on the current spinner frame. + */ void draw_spinner(void); + + /** + * Recalculates layout positions and dimensions based on current terminal size. + * Called when SIGWINCH (terminal resize) is detected. + * Updates the Layout struct fields (input_y, output_y, status_y, etc.). + */ void update_layout(); // Input area methods. + + /** + * Renders the input prompt and user input buffer. + * Displays the prompt text and the current contents of input_buffer, + * with cursor positioned at input_cursor_x. + */ void draw_input_area(); + + /** + * Clears the input area of the screen. + * Used before redrawing to prevent display artifacts. + */ void clear_input_area(); // Output area methods. + + /** + * Renders all messages in the output buffer. + * Iterates through output_buffer and draws each message with its + * associated type (normal, error, success) which may affect coloring. + */ void draw_output_area(); public: + /** + * Constructor: Initializes the UserInterface object. + * Sets up initial state, layout, and registers signal handlers + * but does NOT start the render thread (that happens in run()). + */ UserInterface(); + + /** + * Destructor: Cleans up resources. + * Stops the render thread and restores the terminal via cleanup_ncurses(). + */ ~UserInterface(); + /** + * Returns whether the UI is currently running. + * Reads the volatile 'running' flag in a thread-safe manner. + * @return true if the UI is running, false otherwise. + */ bool is_running(); + /** + * Static signal handler for SIGWINCH (terminal resize). + * Sets resize_flag to 1 to trigger layout recalculation. + * Must be static to comply with C signal API. + * @param sig The signal number (unused, present for signal handler compliance). + */ static void handle_resize(int sig); + + /** + * Static signal handler for SIGINT (Ctrl+C). + * Sets running to 0 to gracefully shut down the UI. + * Must be static to comply with C signal API. + * @param sig The signal number (unused, present for signal handler compliance). + */ static void handle_interrupt(int sig); + /** + * Starts the interactive user interface. + * Registers signal handlers, initializes ncurses, starts the render thread, + * and enters the main input loop. Blocks until running becomes false. + */ void run(void); + + /** + * Ends the current session and exits. + * Stops the render thread, cleans up ncurses, and terminates the application. + * @param exit_code The program exit code to return to the operating system. + */ void end_session(int exit_code); + /** + * Displays configuration/arguments at the start of the session (interactive mode). + * Outputs model, prompt, and mode information to the UI. + * @param model The model being used. + * @param prompt The initial prompt text. + * @param stream Whether streaming is enabled. + * @param interactive Whether interactive mode is enabled. + */ void print_args(const std::string& model, const std::string& prompt, bool stream, bool interactive); + /** + * Static version of print_args() for non-interactive mode. + * Outputs arguments to stdout instead of the UI. + * @param model The model being used. + * @param prompt The initial prompt text. + * @param stream Whether streaming is enabled. + * @param interactive Whether interactive mode is enabled. + */ static void print_args_noninteractive(const std::string& model, const std::string& prompt, bool stream, bool interactive); + /** + * Waits for and retrieves the next user input prompt (interactive mode). + * Blocks until the user presses Enter and a prompt is available. + * @param prompt Pointer to a std::string to receive the user input. + * @return true if a prompt was successfully read, false if the session ended. + */ bool next_prompt(std::string* prompt); + + /** + * Static version of next_prompt() for non-interactive mode. + * Reads from stdin instead of the interactive UI. + * @param prompt Pointer to a std::string to receive the user input. + * @return true if input was read, false on EOF or error. + */ static bool next_prompt_noninteractive(std::string* prompt); + /** + * Prints a message to the output area with a specified type. + * Appends the message to output_buffer and triggers a redraw. + * @param text The message to display. + * @param type The message type (MESSAGE_TYPE_NORMAL, MESSAGE_TYPE_ERROR, or + * MESSAGE_TYPE_SUCCESS). Defaults to MESSAGE_TYPE_NORMAL if not specified. + */ void print(const std::string& text, int type = UserInterface::MESSAGE_TYPE_NORMAL); + + /** + * Convenience method: prints an error message (MESSAGE_TYPE_ERROR). + * @param text The error message to display. + */ void print_error(const std::string& text); + + /** + * Convenience method: prints a success message (MESSAGE_TYPE_SUCCESS). + * @param text The success message to display. + */ void print_success(const std::string& text); + + /** + * Convenience method: prints a normal message (MESSAGE_TYPE_NORMAL). + * @param text The message to display. + */ void print_message(const std::string& text); + void print_message_noninteractive(const std::string& text); + + /** + * Requests that the UI be redrawn on the next render cycle. + * Sets should_redraw to true and notifies the render thread via condition variable. + * Safe to call from multiple threads. + */ void request_redraw(void); // Spinner methods. + + /** + * Activates the progress spinner animation. + * Sets spinner_active to true. + */ void start_spinner(void); + + /** + * Deactivates the progress spinner. + * Sets spinner_active to false. + */ void stop_spinner(void); + + /** + * Advances the spinner animation to the next frame. + * Increments spinner_frame (wraps around after reaching max). + * Called periodically during render_loop(). + */ void advance_spinner_frame(void); + + /** + * Returns the current state of the spinner. + * @return true if the spinner is currently active, false otherwise. + */ bool is_spinner_active(void) const; + + /** + * Sets the progress percentage displayed with the spinner. + * @param percent Progress as a percentage (0–100). + */ void set_progress(int percent); + // Grant friend access to ProgressSpinner friend class ProgressSpinner; };