diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e9a432..c2b9971 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ else() set(CMAKE_CXX_FLAGS_RELEASE "-O3 -ffast-math -funroll-loops") endif() + add_compile_options(-Wall -Wextra -Wno-unused-parameter) set(SOURCES @@ -29,6 +30,9 @@ set(SOURCES xenith/ollama_api/handler.cpp xenith/ollama_api/handler.h xenith/ollama_api/server.h + xenith/ollama_api/server.cpp + xenith/ui/tui.cpp + xenith/ui/tui.h ) # Заголовочные файлы (для удобства в IDE) @@ -38,9 +42,9 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) add_executable(Xenith ${SOURCES} ${HEADER_FILES}) -# Настройка include директорий +target_include_directories(Xenith SYSTEM PRIVATE ./xenith/libs/cpp-httplib-0.57.1) + target_include_directories(Xenith PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) -# Потоки (pthread) find_package(Threads REQUIRED) target_link_libraries(Xenith PRIVATE Threads::Threads) \ No newline at end of file diff --git a/README.md b/README.md index 050776f..46f92ac 100644 --- a/README.md +++ b/README.md @@ -26,16 +26,30 @@ ## Быстрый старт ```bash -# 1) собрать модель из своего текста -./bin/Xenith new models/my.xnh --corpus data.txt \ +# 1) активация окружения +source activate.sh + +# 2) собрать модель из своего текста +Xenith new models/my.xnh --corpus data.txt \ --vocab 512 --embd 128 --layers 6 --heads 8 --ctx 256 -# 2) обучить -./bin/Xenith train models/my.xnh --corpus data.txt \ +# 3) обучить +Xenith train models/my.xnh --corpus data.txt \ --out models/my_trained.xnh --steps 5000 --batch 16 --lr 3e-4 -# 3) сгенерировать -./bin/Xenith gen models/my_trained.xnh --prompt "Привет" --n 200 --temp 0.8 +# 4) сгенерировать +Xenith gen models/my_trained.xnh --prompt "Привет" --n 200 --temp 0.8 +``` + +## Перед использованием ***Xenith*** желательно активировать окружение + +### Команда активации окружения +```bash +# для Zsh/bash +source activate.sh + +# для fish +source activate.fish ``` ## Команды @@ -114,9 +128,9 @@ ### info / gradcheck / bench ```bash -./bin/Xenith info models/my.xnh # конфигурация и словарь -./bin/Xenith gradcheck # градиенты против численных -./bin/Xenith bench models/my.xnh # скорость +Xenith info models/my.xnh # конфигурация и словарь +Xenith gradcheck # градиенты против численных +Xenith bench models/my.xnh # скорость ``` `gradcheck` стоит запускать после любой правки в `model.cpp` / `backward.cpp` — diff --git a/activate.fish b/activate.fish new file mode 100644 index 0000000..46b14be --- /dev/null +++ b/activate.fish @@ -0,0 +1,85 @@ +set -gx VIRTUAL_ENV_PROMPT "Xenith" +complete -c Xenith -erase +complete -c ./bin/Xenith -erase +complete -c Xenith -f +complete -c ./bin/Xenith -f +set -l main_commands "new:создать новую модель из корпуса" \ + "train:обучить существующую модель" \ + "gen:сгенерировать текст" \ + "info:показать конфигурацию модели" \ + "gradcheck:проверить градиенты численно" \ + "bench:замерить скорость форварда и шага обучения" \ + "update:Автомотическое обновление с гита" +for cmd in $main_commands + set -l parts (string split ":" $cmd) + complete -c Xenith -n "__fish_use_subcommand" -a $parts[1] -d $parts[2] + complete -c ./bin/Xenith -n "__fish_use_subcommand" -a $parts[1] -d $parts[2] +end +set -l new_cond "__fish_seen_subcommand_from new" +complete -c Xenith -n $new_cond -l corpus -r -F -d "текст для построения словаря (обязательно)" +complete -c Xenith -n $new_cond -l out -r -F -d "файл модели (по умолчанию model.xnh)" +complete -c Xenith -n $new_cond -l vocab -r -d "размер словаря (по умолчанию 512)" +complete -c Xenith -n $new_cond -l embd -r -d "размер эмбеддинга (по умолчанию 128)" +complete -c Xenith -n $new_cond -l layers -r -d "число слоёв (по умолчанию 4)" +complete -c Xenith -n $new_cond -l heads -r -d "число голов (по умолчанию 4)" +complete -c Xenith -n $new_cond -l ctx -r -d "максимальный контекст (по умолчанию 128)" +complete -c Xenith -n $new_cond -l ffn -r -d "размер FFN (по умолчанию 4*embd)" +complete -c Xenith -n $new_cond -l rope -r -d "база RoPE (по умолчанию 10000)" +complete -c Xenith -n $new_cond -l seed -r -d "зерно инициализации (по умолчанию 1337)" +complete -c Xenith -n $new_cond -l untied -d "отдельная матрица выхода вместо привязанной к wte" +complete -c Xenith -n $new_cond -l view-vocab -d "выводит словарь нейросети" +set -l train_cond "__fish_seen_subcommand_from train" +complete -c Xenith -n $train_cond -l corpus -r -F -d "обучающий текст (обязательно)" +complete -c Xenith -n $train_cond -l out -r -F -d "куда сохранить (по умолчанию перезапись входного файла)" +complete -c Xenith -n $train_cond -l steps -r -d "шагов (по умолчанию 1000)" +complete -c Xenith -n $train_cond -l batch -r -d "батч (по умолчанию 8)" +complete -c Xenith -n $train_cond -l block -r -d "длина окна (по умолчанию 64)" +complete -c Xenith -n $train_cond -l lr -r -d "скорость (по умолчанию 3e-4)" +complete -c Xenith -n $train_cond -l wd -r -d "weight decay (по умолчанию 0.01)" +complete -c Xenith -n $train_cond -l clip -r -d "клип нормы градиента (по умолчанию 1.0)" +complete -c Xenith -n $train_cond -l warmup -r -d "прогрев (по умолчанию 100)" +complete -c Xenith -n $train_cond -l seed -r -d "зерно (по умолчанию 1337)" +complete -c Xenith -n $train_cond -l ckpt-every -r -d "промежуточное сохранение каждые N шагов" +complete -c Xenith -n $train_cond -l val-every -r -d "валидация каждые N шагов" +complete -c Xenith -n $train_cond -l val-tokens -r -d "размер валидации (по умолчанию 20000)" +complete -c Xenith -n $train_cond -l threads -r -d "потоков (0 = все ядра)" +complete -c Xenith -n $train_cond -l resume -d "продолжить с сохранённого step (Adam-state из файла)" +set -l gen_cond "__fish_seen_subcommand_from gen" +complete -c Xenith -n $gen_cond -l prompt -r -d "стартовый текст" +complete -c Xenith -n $gen_cond -l n -r -d "сколько токенов (по умолчанию 200)" +complete -c Xenith -n $gen_cond -l temp -r -d "температура, 0 = жадный выбор (по умолчанию 0.8)" +complete -c Xenith -n $gen_cond -l top-k -r -d "top-k (0 = выкл)" +complete -c Xenith -n $gen_cond -l top-p -r -d "top-p (1.0 = выкл)" +complete -c Xenith -n $gen_cond -l seed -r -d "зерно" +complete -c Xenith -n $gen_cond -l no-stream -d "не печатать в процессе" +complete -c Xenith -n $gen_cond -l batch -r -d "несколько промптов через ;" +complete -c Xenith -n $gen_cond -l show-tokens -d "показать id токенов" +complete -c ./bin/Xenith -n "__fish_seen_subcommand_from new" -a "(complete -C'Xenith new ')" +complete -c ./bin/Xenith -n "__fish_seen_subcommand_from train" -a "(complete -C'Xenith train ')" +complete -c ./bin/Xenith -n "__fish_seen_subcommand_from gen" -a "(complete -C'Xenith gen ')" +function Xenith + ./bin/Xenith $argv +end +if not set -q _OLD_VIRTUAL_PS1 + functions -c fish_prompt _old_fish_prompt + function fish_prompt + echo -n "($VIRTUAL_ENV_PROMPT) " + _old_fish_prompt + end + set -g _OLD_VIRTUAL_PS1 1 +end +echo "Xenith activate" +function deactivate + functions -e Xenith + if functions -q _old_fish_prompt + functions -e fish_prompt + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + end + set -e VIRTUAL_ENV_PROMPT + set -e _OLD_VIRTUAL_PS1 + complete -c Xenith -erase + complete -c ./bin/Xenith -erase + functions -e deactivate + echo "Xenith deactivated" +end diff --git a/activate.sh b/activate.sh new file mode 100644 index 0000000..479f604 --- /dev/null +++ b/activate.sh @@ -0,0 +1,26 @@ +export VIRTUAL_ENV_PROMPT="Xenith" +alias Xenith='./bin/Xenith' +if [ -z "${_OLD_VIRTUAL_PS1+x}" ]; then + _OLD_VIRTUAL_PS1="$PS1" +fi +PS1="($VIRTUAL_ENV_PROMPT) $_OLD_VIRTUAL_PS1" +echo "Xenith activate (zsh)" +deactivate_custom() { + unalias Xenith 2>/dev/null + unalias deactivate 2>/dev/null + + if [ -n "${_OLD_VIRTUAL_PS1+x}" ]; then + PS1="$_OLD_VIRTUAL_PS1" + unset _OLD_VIRTUAL_PS1 + fi + + if (( $+functions[_old_virtual_deactivate] )); then + _old_virtual_deactivate + elif (( $+functions[deactivate] )); then + builtin deactivate 2>/dev/null || true + fi + + unset -f deactivate_custom + echo "Xenith deactivated" +} +alias deactivate='deactivate_custom' \ No newline at end of file diff --git a/bin/Xenith b/bin/Xenith index d47dfb3..3509bc2 100755 Binary files a/bin/Xenith and b/bin/Xenith differ diff --git a/config.yaml b/config.yaml index 6c2217f..bf6b4f3 100644 --- a/config.yaml +++ b/config.yaml @@ -1,2 +1,9 @@ + models: - BiPy: models/bipy.bif + BiPy: + file: "models/my.xnh" + details: + parent_model: "" + family: "Bfr" + families: + - Bfr \ No newline at end of file diff --git a/help.h b/help.h index 6454467..18d80d9 100644 --- a/help.h +++ b/help.h @@ -1,71 +1,138 @@ #ifndef XENITH_HELP_H #define XENITH_HELP_H -inline const std::string HELP_TEXT = R"(ИСПОЛЬЗОВАНИЕ - xenith <команда> [опции] +#define NEW +#define TRAIN +#define GEN +#define INFO +#define GRADCHECK +#define BENCH +//#define OLLAMA +#define UPDATE +//#define TUI -КОМАНДЫ - new создать новую модель из корпуса - train обучить существующую модель - gen сгенерировать текст - info показать конфигурацию модели - gradcheck проверить градиенты численно - bench замерить скорость форварда и шага обучения - ollama запустьтить сервер с api ollama - update Автомотическое обновление с гита +#ifdef NEW +#define CMD_NEW " new создать новую модель из корпуса\n" +#else +#define CMD_NEW "" +#endif +#ifdef TRAIN +#define CMD_TRAIN " train обучить существующую модель\n" +#else +#define CMD_TRAIN "" +#endif +#ifdef GEN +#define CMD_GEN " gen сгенерировать текст\n" +#else +#define CMD_GEN "" +#endif +#ifdef INFO +#define CMD_INFO " info показать конфигурацию модели\n" +#else +#define CMD_INFO "" +#endif +#ifdef GRADCHECK +#define CMD_GRADCHECK " gradcheck проверить градиенты численно\n" +#else +#define CMD_GRADCHECK "" +#endif +#ifdef BENCH +#define CMD_BENCH " bench замерить скорость форварда и шага обучения\n" +#else +#define CMD_BENCH "" +#endif +#ifdef OLLAMA +#define CMD_OLLAMA " ollama запустьтить сервер с api ollama\n" +#else +#define CMD_OLLAMA "" +#endif +#ifdef UPDATE +#define CMD_UPDATE " update Автомотическое обновление с гита\n" +#else +#define CMD_UPDATE "" +#endif +#ifdef TUI +#define CMD_TUI " tui Графическо-консольный режим (находится в тестировании)\n" +#else +#define CMD_TUI "" +#endif -ПРИМЕРЫ - xenith new model.xnh --corpus data.txt --vocab 512 --embd 64 --layers 4 --heads 4 - xenith train model.xnh --corpus data.txt --out model_trained.xnh --steps 3000 - xenith train model_trained.xnh --corpus data.txt --out model_trained.xnh --steps 6000 - xenith gen model_trained.xnh --prompt "Привет" --n 200 --temp 0.8 -ОПЦИИ new - --corpus PATH текст для построения словаря (обязательно) - --out PATH файл модели (по умолчанию model.xnh) - --vocab N размер словаря (по умолчанию 512) - --embd N размер эмбеддинга (по умолчанию 128) - --layers N число слоёв (по умолчанию 4) - --heads N число голов (по умолчанию 4) - --ctx N максимальный контекст (по умолчанию 128) - --ffn N размер FFN (по умолчанию 4*embd) - --rope N база RoPE (по умолчанию 10000) - --seed N зерно инициализации (по умолчанию 1337) - --untied отдельная матрица выхода вместо привязанной к wte - --view-vocab выводит словарь нейросети +#ifdef NEW +#define OPT_NEW "\nОПЦИИ new\n" \ +" --corpus PATH текст для построения словаря (обязательно)\n" \ +" --out PATH файл модели (по умолчанию model.xnh)\n" \ +" --vocab N размер словаря (по умолчанию 512)\n" \ +" --embd N размер эмбеддинга (по умолчанию 128)\n" \ +" --layers N число слоёв (по умолчанию 4)\n" \ +" --heads N число голов (по умолчанию 4)\n" \ +" --ctx N максимальный контекст (по умолчанию 128)\n" \ +" --ffn N размер FFN (по умолчанию 4*embd)\n" \ +" --rope N база RoPE (по умолчанию 10000)\n" \ +" --seed N зерно инициализации (по умолчанию 1337)\n" \ +" --untied отдельная матрица выхода вместо привязанной к wte\n" \ +" --view-vocab выводит словарь нейросети\n" +#else +#define OPT_NEW "" +#endif +#ifdef TRAIN +#define OPT_TRAIN "\nОПЦИИ train\n" \ +" --corpus PATH обучающий текст (обязательно)\n" \ +" --out PATH куда сохранить (по умолчанию перезапись входного файла)\n" \ +" --steps N шагов (по умолчанию 1000)\n" \ +" --batch N батч (по умолчанию 8)\n" \ +" --block N длина окна (по умолчанию 64)\n" \ +" --lr F скорость (по умолчанию 3e-4)\n" \ +" --wd F weight decay (по умолчанию 0.01)\n" \ +" --clip F клип нормы градиента (по умолчанию 1.0)\n" \ +" --warmup N прогрев (по умолчанию 100)\n" \ +" --seed N зерно (по умолчанию 1337)\n" \ +" --ckpt-every N промежуточное сохранение каждые N шагов (0 = нет)\n" \ +" --val-every N валидация каждые N шагов (0 = нет)\n" \ +" --val-tokens N размер валидации (по умолчанию 20000)\n" \ +" --threads N потоков (0 = все ядра)\n" \ +" --resume продолжить с сохранённого step (Adam-state из файла)\n" +#else +#define OPT_TRAIN "" +#endif +#ifdef GEN +#define OPT_GEN "\nОПЦИИ gen\n" \ +" --prompt STR стартовый текст\n" \ +" --n N сколько токенов (по умолчанию 200)\n" \ +" --temp F температура, 0 = жадный выбор (по умолчанию 0.8)\n" \ +" --top-k N top-k (0 = выкл)\n" \ +" --top-p F top-p (1.0 = выкл)\n" \ +" --seed N зерно\n" \ +" --no-stream не печатать в процессе\n" \ +" --batch \"a;b;c\" несколько промптов OPTчерез ;\n" \ +" --show-tokens показать id токенов\n" +#else +#define OPT_GEN "" +#endif +#ifdef OLLAMA +#define OPT_OLLAMA "\nОПЦИИ ollama\n" \ +" --port PORT порт на котором будет открыт сервер\n" \ +" --conf NAME.conf конфиг с настройками и списком моделей\n" +#else +#define OPT_OLLAMA "" +#endif -ОПЦИИ train - --corpus PATH обучающий текст (обязательно) - --out PATH куда сохранить (по умолчанию перезапись входного файла) - --steps N шагов (по умолчанию 1000) - --batch N батч (по умолчанию 8) - --block N длина окна (по умолчанию 64) - --lr F скорость (по умолчанию 3e-4) - --wd F weight decay (по умолчанию 0.01) - --clip F клип нормы градиента (по умолчанию 1.0) - --warmup N прогрев (по умолчанию 100) - --seed N зерно (по умолчанию 1337) - --log-every N как часто печатать (по умолчанию 50) - --ckpt-every N промежуточное сохранение каждые N шагов (0 = нет) - --val-every N валидация каждые N шагов (0 = нет) - --val-tokens N размер валидации (по умолчанию 20000) - --threads N потоков (0 = все ядра) - --resume продолжить с сохранённого step (Adam-state из файла) - -ОПЦИИ gen - --prompt STR стартовый текст - --n N сколько токенов (по умолчанию 200) - --temp F температура, 0 = жадный выбор (по умолчанию 0.8) - --top-k N top-k (0 = выкл) - --top-p F top-p (1.0 = выкл) - --seed N зерно - --no-stream не печатать в процессе - --batch "a;b;c" несколько промптов через ; - --show-tokens показать id токенов - -ОПЦИИ ollama - --port PORT порт на котором будет открыт сервер - --conf NAME.conf конфиг с настройками и списком моделей -)"; +#define HELP_TEXT \ + "ИСПОЛЬЗОВАНИЕ\n" \ + " ./bin/Xenith <команда> [опции]\n\n" \ + "КОМАНДЫ\n" \ + CMD_NEW \ + CMD_TRAIN \ + CMD_GEN \ + CMD_INFO \ + CMD_GRADCHECK \ + CMD_BENCH \ + CMD_OLLAMA \ + CMD_UPDATE \ + CMD_TUI \ + OPT_NEW \ + OPT_TRAIN \ + OPT_GEN \ + OPT_OLLAMA #endif \ No newline at end of file diff --git a/main.cpp b/main.cpp index c6d73c7..1cc3fd8 100644 --- a/main.cpp +++ b/main.cpp @@ -2,6 +2,7 @@ #include "xenith/core/generate.h" #include "xenith/core/train.h" #include "xenith/core/gradcheck.h" +#include "xenith/ui/tui.h" #include #include "help.h" #include @@ -263,7 +264,6 @@ Version parse_version(const std::string& str) { return v; } -// Правильное сравнение версий bool is_newer(const Version& remote, const Version& current) { if (remote.major != current.major) return remote.major > current.major; if (remote.minor != current.minor) return remote.minor > current.minor; @@ -356,10 +356,20 @@ static int cmd_update(const Args& a) { return 0; } +static int cmd_tui(const Args& a) +{ + ui::Tui tui = ui::Tui(); + while (tui.is_running()) + { + tui.update(); + } + return 0; +} + int main(int argc, char** argv) { - if (argc < 2) { printf(HELP_TEXT.c_str()); return 1; } + if (argc < 2) { printf(HELP_TEXT); return 1; } const std::string cmd = argv[1]; - if (cmd == "-h" || cmd == "--help" || cmd == "help") { printf(HELP_TEXT.c_str()); return 0; } + if (cmd == "-h" || cmd == "--help" || cmd == "help") { printf(HELP_TEXT); return 0; } Args a(argc, argv, 2); if (cmd == "new") return cmd_new(a); @@ -370,8 +380,9 @@ int main(int argc, char** argv) { if (cmd == "bench") return cmd_bench(a); if (cmd == "ollama") return cmd_ollama(a); if (cmd == "update") return cmd_update(a); + if (cmd == "tui") return cmd_tui(a); fprintf(stderr, "неизвестная команда: %s\n\n", cmd.c_str()); - printf(HELP_TEXT.c_str()); + printf(HELP_TEXT); return 1; } \ No newline at end of file diff --git a/xenith/ollama_api/handler.cpp b/xenith/ollama_api/handler.cpp index 05af4c0..0080569 100644 --- a/xenith/ollama_api/handler.cpp +++ b/xenith/ollama_api/handler.cpp @@ -1,5 +1,2 @@ -// -// Created by koder on 30.09.2026. -// - #include "handler.h" + diff --git a/xenith/ollama_api/handler.h b/xenith/ollama_api/handler.h index c4e6b76..02040a6 100644 --- a/xenith/ollama_api/handler.h +++ b/xenith/ollama_api/handler.h @@ -1,13 +1,69 @@ #ifndef XENITH_HANDLER_H #define XENITH_HANDLER_H +#include +#include +#include namespace api { - class handler + enum class QuantLevel { + BF16, + FP16, + Q8, + Q6, + Q5, + Q4, + Q2, + Q1, + }; + class QuantizationLevel + { + private: + std::string value; + static std::string enum_to_string(QuantLevel quant) + { + switch (quant) { + case QuantLevel::BF16: return "BF16"; + case QuantLevel::FP16: return "FP16"; + case QuantLevel::Q8: return "Q8_0"; + case QuantLevel::Q6: return "Q6_K"; + case QuantLevel::Q5: return "Q5_K_M"; + case QuantLevel::Q4: return "Q4_K_M"; + case QuantLevel::Q2: return "Q2_K"; + case QuantLevel::Q1: return "Q1"; + default: return "UNKNOWN"; + } + } + public: + QuantizationLevel() : value("UNKNOWN") {} + QuantizationLevel(QuantLevel quant) : value(enum_to_string(quant)) {} + QuantizationLevel(const std::string& str) : value(str) {} + operator std::string() const { return value; } + }; + + struct models_details + { + std::string parent_model; + std::string format; + std::string family; + std::pmr::vector families; + uint32_t parameter_size; + QuantizationLevel quantization_level; + std::string expires_at; + std::string size_vram; + }; + + struct models { + std::string name; + std::string model; + std::string modified_at; + uint64_t size; + std::string digest; + models_details details; }; } -#endif //XENITH_HANDLER_H +#endif diff --git a/xenith/ollama_api/server.cpp b/xenith/ollama_api/server.cpp new file mode 100644 index 0000000..7f4c27f --- /dev/null +++ b/xenith/ollama_api/server.cpp @@ -0,0 +1,9 @@ +#include "server.h" + +namespace api +{ + server::server() + { + + } +} diff --git a/xenith/ollama_api/server.h b/xenith/ollama_api/server.h index 3494217..27f7c4c 100644 --- a/xenith/ollama_api/server.h +++ b/xenith/ollama_api/server.h @@ -1,6 +1,14 @@ #ifndef XENITH_SERVER_H #define XENITH_SERVER_H +#include "handler.h" - +namespace api +{ + class server + { + server(); + ~server(); + }; +} #endif //XENITH_SERVER_H diff --git a/xenith/ollama_api/test.cpp b/xenith/ollama_api/test.cpp index 4765483..0174e29 100644 --- a/xenith/ollama_api/test.cpp +++ b/xenith/ollama_api/test.cpp @@ -3,8 +3,8 @@ #include #include #include -#include "libs/cpp-httplib-0.57.1/httplib.h" -#include "libs/json-3.12.0/single_include/nlohmann/json.hpp" +#include "./xenith/libs/cpp-httplib-0.57.1/httplib.h" +#include "./xenith/libs/json-3.12.0/nlohmann/json.hpp" using json = nlohmann::json; @@ -29,7 +29,7 @@ void log_ollama_options(const std::string& request_body) { } } -int main() { +int main_() { httplib::Server svr; svr.set_logger([](const httplib::Request& req, const httplib::Response& res) { diff --git a/xenith/ui/tui.cpp b/xenith/ui/tui.cpp new file mode 100644 index 0000000..33c411d --- /dev/null +++ b/xenith/ui/tui.cpp @@ -0,0 +1,149 @@ +#include "tui.h" + +#include + +namespace ui +{ + Tui::Tui() + { + ConsoleSize cs = get_console_size(); + int panel_w = (cs.width / 100) * 27; + std::cout << "Ширина (колонок): " << cs.width << "\n"; + std::cout << "Высота (строк): " << cs.height << "\n"; + clear(); + hide_cursor(); + set_foreground(Color::White); + apply_color(); + draw_border((cs.width / 100) * 27, cs.height, border_clip); + draw_border(cs.width - ((cs.width / 100) * 27), cs.height, border_clip, (cs.width / 100) * 27); + draw_button(2, 2, panel_w - 2, 3, "Chat", border_clip); + draw_button(2, 2 + 3, panel_w - 2, 3, "Models", border_clip); + draw_button(2, 2 + 6, panel_w - 2, 3, "Settings", border_clip); + draw_button(2, 2 + 9, panel_w - 2, 3, "Server", border_clip); + std::cout << "\033[?1000h\033[?1002h\033[?1006h" << std::flush; + } + + Tui::~Tui() + = default; + + void Tui::update() + { + + } + + bool Tui::is_running() const + { + return running; + } + + Tui::ConsoleSize Tui::get_console_size() + { + ConsoleSize size; + winsize w; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0) { + size.width = w.ws_col; + size.height = w.ws_row; + } + + return size; + } + + void Tui::apply_color() const + { + std::cout << "\033["<< foreground << ";" << background <<"m" << std::flush; + } + + void Tui::move_cursor(int x, int y) + { + std::cout << "\033[" << y << ";" << x << "H" << std::flush; + } + + void Tui::clear() + { + std::cout << "\033c" << std::flush; + } + + void Tui::draw_border(int w, int h, border_charset charset, int x, int y) + { + move_cursor(x, y); + std::cout << charset.top_left; + for (int q = 0; q < w - 2; q++) std::cout << charset.top; + std::cout << charset.top_right <<"\n"; + move_cursor_x(x); + for (int i = 0; i < h - 2; i++) + { + std::cout << charset.vertical; + for (int q = 0; q < w - 2; q++) std::cout << charset.bg; + std::cout << charset.vertical; + std::cout << "\n"; + move_cursor_x(x); + } + std::cout << charset.bottom_left; + for (int q = 0; q < w - 2; q++) std::cout << charset.bottom; + std::cout << charset.bottom_right << std::flush; + } + + void Tui::hide_cursor() + { + std::cout << "\033[?25l" << std::flush; + } + + void Tui::show_cursor() + { + std::cout << "\033[?25h" << std::flush; + } + + void Tui::set_background(Color clr) + { + background = static_cast(clr); + } + + void Tui::set_foreground(Color clr) + { + foreground = static_cast(clr); + } + + void Tui::move_cursor_right(int n) + { + if (n > 0) { + std::cout << "\033[" << n << "C" << std::flush; + } + } + + void Tui::move_cursor_x(int col) + { + if (col > 0) { + std::cout << "\033[" << col << "G" << std::flush; + } + } + + void Tui::move_cursor_y(int row) + { + if (row > 0) { + std::cout << "\033[" << row << "d" << std::flush; + } + } + + void Tui::invert_color() + { + std::cout << "\033[7m" << std::flush; + } + + void Tui::draw_button(int x, int y, int w, int h, std::string_view label, border_charset charset) + { + draw_border(w, h, charset, x, y); + int text_y = y + (h / 2); + int text_x = x + ((w - static_cast(label.length())) / 2); + move_cursor(text_x, text_y); + std::cout << label; + std::cout << std::flush; + } + + void Tui::invert_color_off() + { + std::cout << "\033[27m" << std::flush; + } + + +} diff --git a/xenith/ui/tui.h b/xenith/ui/tui.h new file mode 100644 index 0000000..75539f2 --- /dev/null +++ b/xenith/ui/tui.h @@ -0,0 +1,107 @@ +#ifndef XENITH_TUI_H +#define XENITH_TUI_H + +#include +#include +#include + +namespace ui +{ + class Tui + { + public: + enum class Color : int { + DefaultText = 39, + DefaultBg = 49, + Black = 30, + Red = 31, + Green = 32, + Yellow = 33, + Blue = 34, + Magenta = 35, + Cyan = 36, + White = 37 + }; + Tui(); + ~Tui(); + static void update(); + bool is_running() const; + private: + int background = 49; + int foreground = 39; + bool running = true; + struct ConsoleSize { + int width = 80; + int height = 24; + }; + struct border_charset + { + std::string_view top_left; + std::string_view top_right; + std::string_view bottom_left; + std::string_view bottom_right; + std::string_view bottom; + std::string_view top; + std::string_view vertical; + std::string_view bg; + }; + inline static const border_charset border_clip { + .top_left = "╭", + .top_right = "╮", + .bottom_left = "╰", + .bottom_right = "╯", + .bottom = "─", + .top = "─", + .vertical = "│", + .bg = " ", + }; + inline static const border_charset border_bold { + .top_left = "┏", + .top_right = "┓", + .bottom_left = "┗", + .bottom_right = "┛", + .bottom = "━", + .top = "━", + .vertical = "┃", + .bg = " ", + }; + inline static const border_charset border_solid { + .top_left = "▟", + .top_right = "▙", + .bottom_left = "▜", + .bottom_right = "▛", + .bottom = "█", + .top = "█", + .vertical = "█", + .bg = " ", + }; + inline static const border_charset border_btn { + .top_left = "▟", + .top_right = "▙", + .bottom_left = "▜", + .bottom_right = "▛", + .bottom = "█", + .top = "█", + .vertical = "█", + .bg = "█", + }; + static ConsoleSize get_console_size(); + void apply_color() const; + static void move_cursor(int x, int y); + static void clear(); + static void draw_border(int w, int h, border_charset charset = border_clip, int x = 1, int y = 1); + static void hide_cursor(); + static void show_cursor(); + void set_background(Color clr); + void set_foreground(Color clr); + static void move_cursor_right(int n); + static void move_cursor_x(int col); + static void move_cursor_y(int row); + static void invert_color(); + static void draw_button(int x, int y, int w, int h, std::string_view label, border_charset charset = border_solid); + void invert_color_off(); + }; +} + + +#endif \ No newline at end of file