char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Xuan-Son Nguyencli : move to HTTP-based implementation (#24948)c264f65ff

char/aliceai-t5-moe
8.7 KiB251 linesraw
1#pragma once
2
3#include "common.h"
4#include "console.h"
5
6#include <array>
7#include <algorithm>
8#include <cctype>
9#include <filesystem>
10#include <string_view>
11
12// TODO?: Make this reusable, enums, docs
13static const std::array<std::string_view, 8> cmds = {
14    "/audio ",
15    "/clear",
16    "/exit",
17    "/glob ",
18    "/image ",
19    "/read ",
20    "/regen",
21    "/video ",
22};
23
24static std::vector<std::pair<std::string, size_t>> auto_completion_callback(std::string_view line, size_t cursor_byte_pos) {
25    std::vector<std::pair<std::string, size_t>> matches;
26    std::string cmd;
27
28    if (line.length() > 1 && line.front() == '/' && !std::any_of(cmds.begin(), cmds.end(), [line](std::string_view prefix) {
29        return string_starts_with(line, prefix);
30    })) {
31        auto it = cmds.begin();
32
33        while ((it = std::find_if(it, cmds.end(), [line](std::string_view cmd_line) {
34            return string_starts_with(cmd_line, line);
35        })) != cmds.end()) {
36            matches.emplace_back(*it, it->length());
37            ++it;
38        }
39    } else {
40        auto it = std::find_if(cmds.begin(), cmds.end(), [line](std::string_view prefix) {
41            return prefix.back() == ' ' && string_starts_with(line, prefix);
42        });
43
44        if (it != cmds.end()) {
45            cmd = *it;
46        }
47    }
48
49    if (!cmd.empty() && cmd != "/glob " && line.length() >= cmd.length() && cursor_byte_pos >= cmd.length()) {
50        const std::string path_prefix  = std::string(line.substr(cmd.length(), cursor_byte_pos - cmd.length()));
51        const std::string path_postfix = std::string(line.substr(cursor_byte_pos));
52        auto cur_dir = std::filesystem::current_path();
53        std::string cur_dir_str = cur_dir.string();
54        std::string expanded_prefix = path_prefix;
55
56#if !defined(_WIN32)
57        if (string_starts_with(path_prefix, '~')) {
58            const char * home = std::getenv("HOME");
59            if (home && home[0]) {
60                expanded_prefix = home + path_prefix.substr(1);
61            }
62        }
63        if (string_starts_with(expanded_prefix, '/')) {
64#else
65        if (std::isalpha(static_cast<unsigned char>(expanded_prefix[0])) && expanded_prefix.find(':') == 1) {
66#endif
67            cur_dir = std::filesystem::path(expanded_prefix).parent_path();
68            cur_dir_str.clear();
69        } else if (!path_prefix.empty()) {
70            cur_dir /= std::filesystem::path(path_prefix).parent_path();
71        }
72
73        std::error_code ec;
74        for (const auto & entry : std::filesystem::directory_iterator(cur_dir, ec)) {
75            if (ec) {
76                break;
77            }
78            if (!entry.exists(ec)) {
79                ec.clear();
80                continue;
81            }
82
83            const std::string path_full = entry.path().string();
84            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;
85
86            if (entry.is_directory(ec)) {
87                path_entry.push_back(std::filesystem::path::preferred_separator);
88            }
89
90            if (expanded_prefix.empty() || string_starts_with(path_entry, expanded_prefix)) {
91                const std::string updated_line = cmd + path_entry;
92                matches.emplace_back(updated_line + path_postfix, updated_line.length());
93            }
94
95            if (ec) {
96                ec.clear();
97            }
98        }
99
100        if (matches.empty()) {
101            const std::string updated_line = cmd + path_prefix;
102            matches.emplace_back(updated_line + path_postfix, updated_line.length());
103        }
104
105        // Add the longest common prefix
106        if (!expanded_prefix.empty() && matches.size() > 1) {
107            const std::string_view match0(matches[0].first);
108            const std::string_view match1(matches[1].first);
109            auto it = std::mismatch(match0.begin(), match0.end(), match1.begin(), match1.end());
110            size_t len = it.first - match0.begin();
111
112            for (size_t i = 2; i < matches.size(); ++i) {
113                const std::string_view matchi(matches[i].first);
114                auto cmp = std::mismatch(match0.begin(), match0.end(), matchi.begin(), matchi.end());
115                len = std::min(len, static_cast<size_t>(cmp.first - match0.begin()));
116            }
117
118            const std::string updated_line = std::string(match0.substr(0, len));
119            matches.emplace_back(updated_line + path_postfix, updated_line.length());
120        }
121
122        std::sort(matches.begin(), matches.end(), [](const auto & a, const auto & b) {
123            return a.first.compare(0, a.second, b.first, 0, b.second) < 0;
124        });
125    }
126
127    return matches;
128}
129
130// note: make this view implementation generic, so that we can move to TUI in the future if we want to
131namespace ui {
132    static void init(const common_params & params) {
133        // TODO: avoid using atexit() here by making `console` a singleton
134        console::init(params.simple_io, params.use_color);
135        atexit([]() { console::cleanup(); });
136
137        console::set_completion_callback(auto_completion_callback);
138    }
139
140    struct spinner {
141        spinner(const std::string & message) {
142            if (!message.empty()) {
143                console::log("%s ", message.c_str());
144            }
145            console::spinner::start();
146        }
147        ~spinner() {
148            console::spinner::stop();
149        }
150    };
151
152    struct user_turn {
153        user_turn() {
154            console::set_display(DISPLAY_TYPE_USER_INPUT);
155        }
156        ~user_turn() {
157            console::set_display(DISPLAY_TYPE_RESET);
158        }
159        void echo(const std::string & buffer) {
160            if (buffer.size() > 500) {
161                console::log("\n> %s ... (truncated)\n", buffer.substr(0, 500).c_str());
162            } else {
163                console::log("\n> %s\n", buffer.c_str());
164            }
165        }
166        std::string read_input(bool multiline_input, const char * prompt = nullptr) {
167            if (prompt) {
168                console::log("%s", prompt);
169            } else {
170                console::log("\n> ");
171            }
172            std::string buffer;
173            std::string line;
174            bool another_line = true;
175            do {
176                another_line = console::readline(line, multiline_input);
177                buffer += line;
178            } while (another_line);
179            return buffer;
180        }
181    };
182
183    enum assistant_display_mode {
184        ASSISTANT_DISPLAY_MODE_REASONING,
185        ASSISTANT_DISPLAY_MODE_CONTENT,
186    };
187    struct assistant_turn {
188        assistant_display_mode mode = ASSISTANT_DISPLAY_MODE_CONTENT;
189        bool trailing_newline = true;
190        bool is_inside_reasoning = false;
191        assistant_turn() {
192            console::set_display(DISPLAY_TYPE_RESET);
193        }
194        ~assistant_turn() {
195            console::set_display(DISPLAY_TYPE_RESET);
196            add_newline_if_needed();
197        }
198        void push(assistant_display_mode m, const std::string & buffer) {
199            if (m != mode) {
200                add_newline_if_needed();
201                switch (m) {
202                    case ASSISTANT_DISPLAY_MODE_CONTENT:
203                        {
204                            if (is_inside_reasoning) {
205                                console::log("[End thinking]\n\n");
206                                is_inside_reasoning = false;
207                            }
208                            console::set_display(DISPLAY_TYPE_RESET);
209                        } break;
210                    case ASSISTANT_DISPLAY_MODE_REASONING:
211                        {
212                            console::set_display(DISPLAY_TYPE_REASONING);
213                            is_inside_reasoning = true;
214                            console::log("\n[Start thinking]\n\n");
215                        } break;
216                }
217            }
218            mode = m;
219            if (buffer.empty()) {
220                return;
221            }
222            trailing_newline = buffer.back() == '\n';
223            console::log("%s", buffer.c_str());
224            console::flush();
225        }
226        void add_newline_if_needed() {
227            if (!trailing_newline) {
228                console::log("\n");
229                console::flush();
230            }
231        }
232    };
233
234    static void show_error(const std::string & title, const std::string & message = "") {
235        console::spinner::stop();
236        console::error("Error: %s\n", title.c_str());
237        if (!message.empty()) {
238            console::log("%s\n", message.c_str());
239        }
240    }
241
242    static void show_message(const std::string & message) {
243        console::log("%s\n", message.c_str());
244    }
245
246    static void show_info(const std::string & message) {
247        console::set_display(DISPLAY_TYPE_INFO);
248        console::log("%s\n", message.c_str());
249        console::set_display(DISPLAY_TYPE_RESET);
250    }
251}