├── .github └── workflows │ ├── build.yml │ └── clear_cache.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── CMakePresets.json ├── LICENSE ├── README.md ├── UpdateSubmodules.bat ├── UpdateSubmodules.sh ├── icon.ico ├── main ├── Command.cpp ├── Command.h ├── Config.cpp ├── Config.h ├── FineTuneHelper.cpp ├── FineTuneHelper.h ├── GPTMain.cpp ├── GPTMain.h ├── Translator.cpp ├── Translator.h ├── interface │ ├── cpp-terminal │ │ ├── base.cpp │ │ ├── base.hpp │ │ ├── color.cpp │ │ ├── color.hpp │ │ ├── exception.hpp │ │ ├── input.cpp │ │ ├── input.hpp │ │ ├── inputU.hpp │ │ ├── platforms │ │ │ ├── conversion.hpp │ │ │ ├── input.cpp │ │ │ ├── inputU.cpp │ │ │ ├── macros.hpp │ │ │ ├── platform.cpp │ │ │ ├── platform.hpp │ │ │ ├── terminal.cpp │ │ │ ├── terminfo.cpp │ │ │ └── tty.cpp │ │ ├── prompt.cpp │ │ ├── prompt.hpp │ │ ├── terminal.cpp │ │ ├── terminal.hpp │ │ ├── terminfo.hpp │ │ ├── tty.hpp │ │ ├── window.cpp │ │ └── window.hpp │ ├── data │ │ ├── Document.cpp │ │ ├── Document.h │ │ ├── Exchange.cpp │ │ ├── Exchange.h │ │ ├── ExchangeHistory.cpp │ │ ├── ExchangeHistory.h │ │ ├── Messages.cpp │ │ └── Messages.h │ ├── log │ │ └── LogMsg.h │ ├── model │ │ ├── Completion.cpp │ │ ├── Completion.h │ │ ├── Embedding.cpp │ │ ├── Embedding.h │ │ ├── FineTune.cpp │ │ └── FineTune.h │ ├── network │ │ ├── APIKey.cpp │ │ ├── APIKey.h │ │ ├── Network.cpp │ │ ├── Network.h │ │ ├── Request.cpp │ │ ├── Request.h │ │ ├── RequestObject.cpp │ │ └── RequestObject.h │ ├── storage │ │ └── MemoryAdaptor.h │ └── util │ │ ├── Base64.cpp │ │ ├── Base64.h │ │ ├── CURLUtils.cpp │ │ ├── CURLUtils.h │ │ ├── FileUtils.cpp │ │ ├── FileUtils.h │ │ ├── PromptUtils.cpp │ │ ├── PromptUtils.h │ │ ├── SystemUtils.cpp │ │ ├── SystemUtils.h │ │ ├── TermUtils.cpp │ │ ├── TermUtils.h │ │ ├── TokenUtils.cpp │ │ └── TokenUtils.h ├── main.cpp └── resources.rc └── vcpkg.json /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{matrix.os}} 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | os: [windows-2022, ubuntu-20.04, macos-12] 12 | include: 13 | - os: windows-2022 14 | triplet: x64-windows 15 | - os: ubuntu-20.04 16 | triplet: x64-linux 17 | - os: macos-12 18 | triplet: x64-osx 19 | steps: 20 | - uses: actions/checkout@v3.5.2 21 | with: 22 | submodules: 'recursive' 23 | - name: Initialize submodules 24 | run: | 25 | git submodule init 26 | git submodule update 27 | - name: Get cmake 28 | uses: lukka/get-cmake@latest 29 | - name: Run vcpkg 30 | uses: lukka/run-vcpkg@v11.1 31 | with: 32 | vcpkgGitCommitId: 662dbb50e63af15baa2909b7eac5b1b87e86a0aa 33 | vcpkgDirectory: '${{github.workspace}}/vcpkg' 34 | - name: Run cmake(Build Release) 35 | uses: lukka/run-cmake@v10.5 36 | with: 37 | cmakeListsTxtPath: '${{github.workspace}}/CMakeLists.txt' 38 | configurePreset: 'ninja-multi-vcpkg' 39 | buildPreset: 'ninja-multi-vcpkg' 40 | buildPresetAdditionalArgs: '[`--config Release`]' 41 | - name: Copy tokenizers to build folder (Windows) 42 | if: runner.os == 'Windows' 43 | run: | 44 | mkdir ${{github.workspace}}\build\ninja-multi-vcpkg\Release\tokenizers 45 | move ${{github.workspace}}\build\ninja-multi-vcpkg\tokenizers\* ${{github.workspace}}\build\ninja-multi-vcpkg\Release\tokenizers\ 46 | - name: Copy tokenizers to build folder (Linux or macOS) 47 | if: runner.os == 'Linux' || runner.os == 'macOS' 48 | run: | 49 | mkdir ${{github.workspace}}/build/ninja-multi-vcpkg/Release/tokenizers 50 | mv ${{github.workspace}}/build/ninja-multi-vcpkg/tokenizers/* ${{github.workspace}}/build/ninja-multi-vcpkg/Release/tokenizers/ 51 | - name: Upload build artifact 52 | uses: actions/upload-artifact@v3.1.2 53 | with: 54 | name: ChatGPT_CLI_Bot_${{matrix.os}}-${{matrix.triplet}} 55 | path: ${{github.workspace}}/build/ninja-multi-vcpkg/Release/* 56 | -------------------------------------------------------------------------------- /.github/workflows/clear_cache.yml: -------------------------------------------------------------------------------- 1 | name: Clear cache 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | actions: write 8 | 9 | jobs: 10 | clear-cache: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Clear cache 14 | uses: actions/github-script@v6 15 | with: 16 | script: | 17 | console.log("About to clear") 18 | const caches = await github.rest.actions.getActionsCacheList({ 19 | owner: context.repo.owner, 20 | repo: context.repo.repo, 21 | }) 22 | for (const cache of caches.data.actions_caches) { 23 | console.log(cache) 24 | github.rest.actions.deleteActionsCacheById({ 25 | owner: context.repo.owner, 26 | repo: context.repo.repo, 27 | cache_id: cache.id, 28 | }) 29 | } 30 | console.log("Clear completed") 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Build Dir 35 | .idea 36 | cmake-build-debug 37 | cmake-build-release 38 | 39 | # Backup 40 | *.zip -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "main/interface/clip"] 2 | path = main/interface/clip 3 | url = https://github.com/dacap/clip.git 4 | [submodule "main/interface/cpp-tiktoken"] 5 | path = main/interface/cpp-tiktoken 6 | url = https://github.com/LagPixelLOL/cpp-tiktoken.git 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.24) 2 | project(GPT3Bot) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | 7 | # Disable clip examples and tests 8 | set(CLIP_EXAMPLES OFF CACHE BOOL "Compile clip examples") 9 | set(CLIP_TESTS OFF CACHE BOOL "Compile clip tests") 10 | 11 | set(Boost_USE_STATIC_LIBS ON) 12 | find_package(Boost REQUIRED) 13 | if (Boost_FOUND) 14 | include_directories(${Boost_INCLUDE_DIRS}) 15 | link_directories(${Boost_LIBRARY_DIRS}) 16 | endif () 17 | 18 | find_package(CURL CONFIG REQUIRED) 19 | find_package(nlohmann_json REQUIRED) 20 | find_package(ftxui REQUIRED) 21 | find_package(TBB REQUIRED) 22 | if (WIN32) 23 | find_package(libproxy REQUIRED) 24 | else () 25 | find_package(unofficial-utf8proc CONFIG REQUIRED) 26 | endif () 27 | 28 | add_executable(${PROJECT_NAME} main/main.cpp main/GPTMain.cpp main/GPTMain.h main/interface/util/PromptUtils.cpp main/interface/util/PromptUtils.h main/interface/util/SystemUtils.cpp main/interface/util/SystemUtils.h main/interface/model/Embedding.cpp main/interface/model/Embedding.h main/interface/data/Exchange.cpp main/interface/data/Exchange.h main/interface/util/CURLUtils.cpp main/interface/util/CURLUtils.h main/interface/network/Network.cpp main/interface/network/Network.h main/interface/cpp-terminal/platforms/conversion.hpp main/interface/cpp-terminal/platforms/platform.hpp main/interface/cpp-terminal/platforms/platform.cpp main/interface/cpp-terminal/platforms/input.cpp main/interface/cpp-terminal/platforms/macros.hpp main/interface/cpp-terminal/platforms/tty.cpp main/interface/cpp-terminal/platforms/terminfo.cpp main/interface/cpp-terminal/platforms/terminal.cpp main/interface/cpp-terminal/base.cpp main/interface/cpp-terminal/base.hpp main/interface/cpp-terminal/color.cpp main/interface/cpp-terminal/color.hpp main/interface/cpp-terminal/exception.hpp main/interface/cpp-terminal/input.cpp main/interface/cpp-terminal/input.hpp main/interface/cpp-terminal/prompt.cpp main/interface/cpp-terminal/prompt.hpp main/interface/cpp-terminal/terminal.cpp main/interface/cpp-terminal/terminal.hpp main/interface/cpp-terminal/terminfo.hpp main/interface/cpp-terminal/tty.hpp main/interface/cpp-terminal/window.cpp main/interface/cpp-terminal/window.hpp main/resources.rc main/interface/util/TermUtils.cpp main/interface/util/TermUtils.h main/interface/network/APIKey.cpp main/interface/network/APIKey.h main/interface/util/TokenUtils.cpp main/interface/util/TokenUtils.h main/interface/cpp-terminal/platforms/inputU.cpp main/interface/cpp-terminal/inputU.hpp main/Command.cpp main/Command.h main/interface/data/Document.cpp main/interface/data/Document.h main/interface/util/FileUtils.cpp main/interface/util/FileUtils.h main/interface/network/Request.cpp main/interface/network/Request.h main/FineTuneHelper.cpp main/FineTuneHelper.h main/interface/model/FineTune.cpp main/interface/model/FineTune.h main/interface/storage/MemoryAdaptor.h main/interface/data/ExchangeHistory.cpp main/interface/data/ExchangeHistory.h main/Config.cpp main/Config.h main/interface/log/LogMsg.h main/interface/model/Completion.cpp main/interface/model/Completion.h main/interface/data/Messages.cpp main/interface/data/Messages.h main/interface/util/Base64.cpp main/interface/util/Base64.h main/interface/network/RequestObject.cpp main/interface/network/RequestObject.h main/Translator.cpp main/Translator.h) 29 | add_subdirectory(main/interface/clip) 30 | add_subdirectory(main/interface/cpp-tiktoken) 31 | 32 | target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES}) 33 | target_link_libraries(${PROJECT_NAME} PRIVATE CURL::libcurl nlohmann_json::nlohmann_json ftxui::component TBB::tbb clip tiktoken) 34 | if (WIN32) 35 | target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBPROXY_LIBRARIES}) 36 | else () 37 | target_link_libraries(${PROJECT_NAME} PRIVATE utf8proc) 38 | endif () 39 | if (NOT APPLE) 40 | if (UNIX) 41 | set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++ -lpthread") 42 | else () 43 | set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++ -Wl,-Bstatic,--whole-archive -lpthread -Wl,-Bdynamic,--no-whole-archive") 44 | endif () 45 | endif () -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 24, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "ninja", 11 | "displayName": "Ninja Configure Settings", 12 | "description": "Sets build and install directories", 13 | "binaryDir": "${sourceDir}/build/${presetName}", 14 | "generator": "Ninja" 15 | }, 16 | { 17 | "name": "ninja-toolchain", 18 | "displayName": "Ninja Configure Settings with toolchain", 19 | "description": "Sets build and install directories", 20 | "binaryDir": "${sourceDir}/build/${presetName}-toolchain", 21 | "generator": "Ninja", 22 | "toolchainFile": "$env{TOOLCHAINFILE}" 23 | }, 24 | { 25 | "name": "ninja-multi-vcpkg", 26 | "displayName": "Ninja Multi-Config Configure Settings", 27 | "description": "Configure with vcpkg toolchain", 28 | "binaryDir": "${sourceDir}/build/${presetName}", 29 | "generator": "Ninja Multi-Config", 30 | "cacheVariables": { 31 | "CMAKE_TOOLCHAIN_FILE": { 32 | "type": "FILEPATH", 33 | "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" 34 | } 35 | } 36 | }, 37 | { 38 | "name": "msbuild-vcpkg", 39 | "displayName": "MSBuild (vcpkg toolchain) Configure Settings", 40 | "description": "Configure with VS generators and with vcpkg toolchain", 41 | "binaryDir": "${sourceDir}/build/${presetName}", 42 | "generator": "Visual Studio 17 2022", 43 | "architecture": { 44 | "strategy": "set", 45 | "value": "x64" 46 | }, 47 | "cacheVariables": { 48 | "CMAKE_TOOLCHAIN_FILE": { 49 | "type": "FILEPATH", 50 | "value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" 51 | } 52 | } 53 | } 54 | ], 55 | "buildPresets": [ 56 | { 57 | "name": "ninja", 58 | "configurePreset": "ninja", 59 | "displayName": "Build with Ninja", 60 | "description": "Build with Ninja" 61 | }, 62 | { 63 | "name": "ninja-multi-vcpkg", 64 | "configurePreset": "ninja-multi-vcpkg", 65 | "displayName": "Build ninja-multi-vcpkg", 66 | "description": "Build ninja-multi-vcpkg Configurations" 67 | }, 68 | { 69 | "name": "ninja-toolchain", 70 | "configurePreset": "ninja-toolchain", 71 | "displayName": "Build ninja-toolchain", 72 | "description": "Build ninja with a toolchain" 73 | }, 74 | { 75 | "name": "msbuild-vcpkg", 76 | "configurePreset": "msbuild-vcpkg", 77 | "displayName": "Build MSBuild", 78 | "description": "Build with MSBuild (VS)" 79 | } 80 | ], 81 | "testPresets": [ 82 | { 83 | "name": "ninja", 84 | "configurePreset": "ninja" 85 | }, 86 | { 87 | "name": "ninja-multi-vcpkg", 88 | "configurePreset": "ninja-multi-vcpkg" 89 | }, 90 | { 91 | "name": "default-vs", 92 | "configurePreset": "msbuild-vcpkg" 93 | } 94 | ] 95 | } 96 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 v2ray 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NOTICE: NO LONGER MAINTAINED 2 | Goodbye everyone! 3 | 4 | As you might have noticed, the landscape of LLMs has changed dramatically in recent months. With the introduction of models boasting a whopping 100,000+ tokens of context memory and the big players like OpenAI, Google, and Anthropic integrating RAG directly into their official websites, I feel like this repo has served its purpose. 5 | 6 | I've also been focusing on other projects and prioritizing my personal life and mental well-being. As a result, I haven't been able to dedicate time to updating this repo, which is evident from the lack of commits in the past few months. 7 | 8 | Looking back, this repo was initially created to address the limitations of the 4096 context GPT-3, as that context size was simply too small. However, with the advancements in LLMs and the emergence of better GUIs, I believe that higher context is now preferable to using embeddings for RAG. 9 | 10 | If you're looking for some awesome LLM GUIs, here are my top recommendations: 11 | - [SillyTavern](https://github.com/SillyTavern/SillyTavern) - Perfect for ~~bot fooking~~ roleplaying and even has RAG support! 12 | - [LobeChat](https://github.com/lobehub/lobe-chat) - Great for general-purpose LLM usage, although it doesn't have RAG. 13 | - [BetterChatGPT](https://github.com/ztjhz/BetterChatGPT) - Another fantastic option for general-purpose LLM usage, specifically designed for OpenAI. While it's not actively maintained, the UI closely resembles the original ChatGPT website. 14 | 15 | (Plz don't unstar this repo🥺) 16 | 17 | # ChatGPT CLI Bot 18 | Run `gpt-3.5-turbo` or any other GPT models(`text-davinci-003`) with this program! \ 19 | Use `gpt-4` or `gpt-4-32k` to use the new GPT-4 models if you have access. \ 20 | You can switch models in the `config.json` file. \ 21 | It's like https://chat.openai.com/ but in your CMD and better(in terms of memory). \ 22 | You can add custom initial prompts and save/load your chat history! \ 23 | Download and double-click the `GPT3Bot.exe` or `run.bat` to run the program! \ 24 | In Linux and macOS, you can run `./GPT3Bot` to run the program. \ 25 | \ 26 | Click to download: [Stable Release](https://github.com/LagPixelLOL/ChatGPTCLIBot/releases) | [Development Build](https://github.com/LagPixelLOL/ChatGPTCLIBot/actions) \ 27 | Please check the Wiki for more information: [Click Me](https://github.com/LagPixelLOL/ChatGPTCLIBot/wiki) 28 | 29 | # Features/Manual: 30 | 1. **Long term memory support!** Keep hitting the 4096 tokens context limit? Worry no more with this CLI Bot. It has nearly INFINITE context memory(If you have infinite disk space lol), all thanks to Embeddings! If you want to see how this program handles embeddings internally, set `debug_reference` to `true` in `config.json`! 31 | 2. **Q&A with custom documents support!** You can load custom documents, and perform Q&A with them, please check the Wiki for more info. 32 | 3. You can use `/stop` to end and save the chat. 33 | 4. You can use `/undo` to undo your last prompt. 34 | 5. You can use `/reset` to reset your entire chat. 35 | 6. You can use `/dump` to dump your chat history to a .txt file inside the `dump` folder. 36 | 7. You can place .txt files in the "initial" folder to set different initial prompts, and you can use the filename to load it when you open the program. Simply directly press enter after you open the program, then enter the initial prompt file's name and press enter to load it. 37 | 8. After you execute `/stop`, the program will ask you to input the filename to save. You can press enter directly to skip this and not save the chat. If you input any other text and then press enter, the chat will be saved into a json in the "saved" folder. When you open the program next time, you can simply input "s"(which means saved), press enter, then type the saved chat's json file's name to load your saved chat. 38 | 9. Easy config file in `config.json`, can be easily modified. 39 | 10. Unlike other bots, this one actually streams. This means it will display the output as soon as a token is sent from the API(Just like what ChatGPT's website is doing), no need to wait until the entire response is generated! 40 | 11. When the response is being streamed, you can press Ctrl+C to cancel the stream. 41 | 12. Automatically use the system proxy. Note: This feature is only supported on Windows, because there's a bug in my proxy library that causes it fail to compile on Linux and macOS. 42 | 13. Multiline input support, you need to press Ctrl+N or Alt+Enter to enter a new line. 43 | 14. Ctrl+V pasting support, you can paste text from your clipboard by pressing Ctrl+V. 44 | 15. Full UTF-8 support, you can type in any language you want! 45 | 16. Full of colors(If your terminal supports it)! 46 | 17. Fine tune helper, you can fine tune base models more easily(Only for professional users). 47 | 18. Auto translator, you can translate text files automatically. 48 | 49 | Written in C++ (Libraries used: 50 | [Boost](https://www.boost.org/), 51 | [cURL](https://curl.se/), 52 | [nlohmann/json](https://github.com/nlohmann/json), 53 | [libproxy](https://libproxy.github.io/libproxy/), 54 | [cpp-terminal](https://github.com/jupyter-xeus/cpp-terminal), 55 | [ftxui](https://github.com/ArthurSonzogni/FTXUI), 56 | [oneTBB](https://www.intel.com/content/www/us/en/developer/tools/oneapi/onetbb.html), 57 | [clip](https://github.com/dacap/clip), 58 | [cpp-tiktoken](https://github.com/gh-markt/tiktoken), 59 | [pcre2](https://www.pcre.org/), 60 | [utf8proc](https://juliastrings.github.io/utf8proc/)) 61 | 62 | # Supported OS: 63 | * Windows 10/11 64-bit 64 | * Linux 64-bit (Tested on Ubuntu 20.04 & CentOS 8) (Won't work on Ubuntu 18.04, CentOS 7 and lower, because they don't support C++17) 65 | * macOS 64-bit (Didn't test, but it should work on macOS 12 and higher) 66 | -------------------------------------------------------------------------------- /UpdateSubmodules.bat: -------------------------------------------------------------------------------- 1 | git submodule update --init --recursive --remote --merge 2 | pause -------------------------------------------------------------------------------- /UpdateSubmodules.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git submodule update --init --recursive --remote --merge 4 | read -p "Press enter to continue..." -------------------------------------------------------------------------------- /icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LagPixelLOL/ChatGPTCLIBot/b629b9099abbf7065c41b2c1022abb0e9fa22004/icon.ico -------------------------------------------------------------------------------- /main/Command.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by v2ray on 2023/4/15. 3 | // 4 | 5 | #ifndef GPT3BOT_COMMAND_H 6 | #define GPT3BOT_COMMAND_H 7 | 8 | #include "GPTMain.h" 9 | #include "interface/cpp-terminal/platforms/conversion.hpp" 10 | 11 | namespace cmd { 12 | using Color = Term::Color; 13 | 14 | enum class Commands : uint16_t { 15 | NONE = 0, 16 | STOP, 17 | UNDO, 18 | RESET, 19 | UWU, 20 | TOKENIZE, 21 | DUMP 22 | }; 23 | 24 | enum class ReturnOpCode : uint16_t { 25 | NONE = 0, 26 | CONTINUE, 27 | STOP 28 | }; 29 | 30 | ReturnOpCode handle_command(const std::string& input, const std::string& initial_prompt, 31 | const std::shared_ptr& chat_history, const std::string& me_id, 32 | const std::string& bot_id, const unsigned int& max_display_length, 33 | const bool& space_between_exchanges, const bool& documentQA_mode); 34 | } // cmd 35 | 36 | #endif //GPT3BOT_COMMAND_H 37 | -------------------------------------------------------------------------------- /main/Config.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by v2ray on 2023/5/10. 3 | // 4 | 5 | #ifndef GPT3BOT_CONFIG_H 6 | #define GPT3BOT_CONFIG_H 7 | 8 | #include "interface/model/Completion.h" 9 | #include "interface/log/LogMsg.h" 10 | 11 | namespace config { 12 | 13 | class Config { 14 | public: 15 | const std::filesystem::path config_path; 16 | std::vector input_history; 17 | const std::string default_initial_prompt_filename = "Default"; 18 | const std::string f_initial = "initial"; 19 | const std::string f_saved = "saved"; 20 | const std::string f_documentQA = "documentQA"; 21 | std::string api_base_url = "https://api.openai.com"; 22 | private: 23 | std::string model = "gpt-3.5-turbo"; 24 | bool is_new_api_ = true; 25 | public: 26 | float temperature = 1; 27 | int max_tokens = 500; 28 | float top_p = 1; 29 | float frequency_penalty = 0; 30 | float presence_penalty = 0; 31 | std::vector> logit_bias; 32 | std::string initial_prompt = "You are an AI chat bot named Sapphire\n" 33 | "You are friendly and intelligent\n" 34 | "Your backend is OpenAI's ChatGPT API\n"; 35 | std::shared_ptr chat_history = std::make_shared(); 36 | unsigned int max_display_length = 100; 37 | unsigned int max_short_memory_length = 4; 38 | unsigned int max_reference_length = 4; 39 | std::shared_ptr>> documents; 40 | bool search_response = true; 41 | bool space_between_exchanges = false; 42 | bool debug_reference = false; 43 | 44 | Config(); 45 | explicit Config(std::filesystem::path config_path); 46 | virtual ~Config(); 47 | 48 | [[nodiscard]] chat::Completion to_completion() const; 49 | 50 | void load_config(const std::function& msg)>& log_callback = [](const auto&){}); 51 | void save_config(const std::function& msg)>& log_callback = [](const auto&){}); 52 | void load_documents(const std::string& filename, 53 | const std::function& msg)>& log_callback = [](const auto&){}); 54 | 55 | [[maybe_unused]] [[nodiscard]] std::string get_model() const; 56 | [[maybe_unused]] void set_model(const std::string& model_); 57 | [[nodiscard]] bool is_new_api() const; 58 | }; 59 | } // config 60 | 61 | #endif //GPT3BOT_CONFIG_H 62 | -------------------------------------------------------------------------------- /main/FineTuneHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by v2ray on 2023/4/26. 3 | // 4 | 5 | #ifndef GPT3BOT_FINETUNEHELPER_H 6 | #define GPT3BOT_FINETUNEHELPER_H 7 | 8 | #include "GPTMain.h" 9 | #include "interface/model/FineTune.h" 10 | #include "thread" 11 | 12 | namespace fth { 13 | 14 | void fine_tune_helper_main(const config::Config& config); 15 | } // fth 16 | 17 | #endif //GPT3BOT_FINETUNEHELPER_H 18 | -------------------------------------------------------------------------------- /main/GPTMain.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by v2ray on 2023/2/18. 3 | // 4 | 5 | #ifndef GPT3BOT_GPTMAIN_H 6 | #define GPT3BOT_GPTMAIN_H 7 | 8 | #include "interface/network/Network.h" 9 | #include "Config.h" 10 | #include "Command.h" 11 | #include "FineTuneHelper.h" 12 | #include "Translator.h" 13 | #include "csignal" 14 | 15 | #define PATH_S(path) Term::color_fg(125, 225, 255) + "\"" + (path).string() + "\"" 16 | #define ENTER Term::color_fg(70, 200, 255) + "Enter" + Term::color_fg(Term::Color::Name::Default) 17 | #define GOLDEN_TEXT(n) Term::color_fg(255, 200, 0) + (n) + Term::color_fg(Term::Color::Name::Default) 18 | 19 | namespace GPT { 20 | using Color = Term::Color; 21 | 22 | inline const std::string f_suffix = ".txt"; 23 | inline const std::string json_suffix = ".json"; 24 | inline const std::string me_id = "Me"; 25 | inline const std::string bot_id = "You"; 26 | 27 | void pre_settings(); 28 | void start_loop(); 29 | void print_prompt(); 30 | void print_enter_next_cycle(); 31 | void clear_console(); 32 | bool create_folders(const std::vector& folders); 33 | bool p_default_prompt(); 34 | bool p_load_prompt(std::string filename); 35 | bool p_load_saved(std::string filename); 36 | bool p_save_chat(std::string name); 37 | bool p_create_docQA(); 38 | bool p_check_set_api_key(); 39 | void p_on_invalid_key(); 40 | } 41 | 42 | #endif //GPT3BOT_GPTMAIN_H 43 | -------------------------------------------------------------------------------- /main/Translator.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by v2ray on 2023/5/22. 3 | // 4 | 5 | #ifndef GPT3BOT_TRANSLATOR_H 6 | #define GPT3BOT_TRANSLATOR_H 7 | 8 | #include "GPTMain.h" 9 | 10 | namespace translator { 11 | 12 | void translator_main(const config::Config& config); 13 | } // translator 14 | 15 | #endif //GPT3BOT_TRANSLATOR_H 16 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/base.cpp: -------------------------------------------------------------------------------- 1 | #include "base.hpp" 2 | 3 | #include "input.hpp" 4 | #include "platforms/platform.hpp" 5 | #include "tty.hpp" 6 | 7 | #include 8 | #include 9 | 10 | std::string Term::style(Term::Style style) { return "\033[" + std::to_string((std::uint8_t)style) + 'm'; } 11 | 12 | std::pair Term::get_size() 13 | { 14 | return Private::get_term_size(); // function uses platform dependent code 15 | } 16 | 17 | bool Term::stdin_connected() { return Term::is_stdin_a_tty(); } 18 | 19 | bool Term::stdout_connected() { return Term::is_stdout_a_tty(); } 20 | 21 | std::string Term::cursor_off() { return "\x1b[?25l"; } 22 | 23 | std::string Term::cursor_on() { return "\x1b[?25h"; } 24 | 25 | std::string Term::clear_screen() { return "\033[2J"; } 26 | std::string Term::clear_buffer() { return "\033[3J"; } 27 | 28 | std::string Term::cursor_move(std::size_t row, std::size_t column) { return "\033[" + std::to_string(row) + ';' + std::to_string(column) + 'H'; } 29 | 30 | std::string Term::cursor_up(std::size_t rows) { return "\033[" + std::to_string(rows) + 'A'; } 31 | 32 | std::string Term::cursor_down(std::size_t rows) { return "\033[" + std::to_string(rows) + 'B'; } 33 | 34 | std::string Term::cursor_right(std::size_t columns) { return "\033[" + std::to_string(columns) + 'C'; } 35 | 36 | std::string Term::cursor_left(std::size_t columns) { return "\033[" + std::to_string(columns) + 'D'; } 37 | 38 | std::pair Term::cursor_position() { 39 | // write cursor position report 40 | std::cout << cursor_position_report() << std::flush; 41 | // read input buffer 42 | std::string buf; 43 | char c{'\0'}; 44 | do { 45 | while (!Platform::read_raw(&c)) {} 46 | buf.push_back(c); 47 | } while (c != 'R'); 48 | bool found{false}; 49 | std::size_t row{0}; 50 | std::size_t column{0}; 51 | for (std::size_t i = 2; i < buf.size(); i++) { 52 | if (buf[i] == ';') { 53 | found = true; 54 | } else if (!found && buf[i] >= '0' && buf[i] <= '9') { 55 | row = row * 10 + (buf[i] - '0'); 56 | } else if (found && buf[i] >= '0' && buf[i] <= '9') { 57 | column = column * 10 + (buf[i] - '0'); 58 | } 59 | } 60 | return {row, column}; 61 | } 62 | 63 | std::string Term::cursor_position_report() { return "\x1b[6n"; } 64 | 65 | std::string Term::clear_eol() { return "\033[K"; } 66 | 67 | std::string Term::screen_save() 68 | { 69 | return "\0337\033[?1049h"; // save current cursor position, save screen 70 | } 71 | std::string Term::screen_load() 72 | { 73 | return "\033[?1049l\0338"; // restores screen, restore current cursor position 74 | } 75 | 76 | std::string Term::terminal_title(const std::string& title) { return "\033]0;" + title + '\a'; } 77 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/base.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "color.hpp" 4 | #include "platforms/macros.hpp" 5 | #include "platforms/platform.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace Term 13 | { 14 | /* 15 | * Styles for text in the terminal 16 | */ 17 | enum class Style : std::uint8_t 18 | { 19 | // resets all attributes (styles and colors) 20 | RESET = 0, 21 | // Thick text font 22 | BOLD = 1, 23 | // lighter, slimmer text font 24 | DIM = 2, 25 | // slightly bend text font 26 | ITALIC = 3, 27 | // draws a line below the text 28 | UNDERLINE = 4, 29 | BLINK = 5, 30 | BLINK_RAPID = 6, 31 | REVERSED = 7, 32 | CONCEAL = 8, 33 | // strikes through the text, mostly supported 34 | CROSSED = 9, 35 | // draws a line over the text, barely supported 36 | OVERLINE = 53 37 | }; 38 | 39 | std::string style(Style style); 40 | 41 | // get the terminal size (row, column) / (Y, X) 42 | std::pair get_size(); 43 | // check if stdin is connected to a TTY 44 | bool stdin_connected(); 45 | // check if stdout is connected to a TTY 46 | bool stdout_connected(); 47 | // turn off the cursor 48 | std::string cursor_off(); 49 | // turn on the cursor 50 | std::string cursor_on(); 51 | // clear the screen 52 | std::string clear_screen(); 53 | // clear the screen and the scroll-back buffer 54 | std::string clear_buffer(); 55 | // move the cursor to the given (row, column) / (Y, X) 56 | std::string cursor_move(std::size_t row, std::size_t column); 57 | // move the cursor the given rows up 58 | std::string cursor_up(std::size_t rows); 59 | // move the cursor the given rows down 60 | std::string cursor_down(std::size_t rows); 61 | // move the cursor the given columns left 62 | std::string cursor_left(std::size_t columns); 63 | // move the cursor the given columns right 64 | std::string cursor_right(std::size_t columns); 65 | // returns the current cursor position (row, column) (Y, X) 66 | std::pair cursor_position(); 67 | // the ANSI code to generate a cursor position report 68 | std::string cursor_position_report(); 69 | // clears the screen from the current cursor position to the end of the screen 70 | std::string clear_eol(); 71 | // save the current terminal state 72 | std::string screen_save(); 73 | // load a previously saved terminal state 74 | std::string screen_load(); 75 | // change the title of the terminal, only supported by a few terminals 76 | std::string terminal_title(const std::string& title); 77 | 78 | } // namespace Term 79 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/color.cpp: -------------------------------------------------------------------------------- 1 | #include "color.hpp" 2 | 3 | #include "terminfo.hpp" 4 | 5 | bool Term::Color::operator==(const Term::Color& color) const 6 | { 7 | if(color.getType() != getType()) return false; 8 | if(getType() == Term::Color::Type::Bit24) return m_bit24 == color.to24bits(); 9 | else 10 | return m_bit8 == color.to8bits(); 11 | } 12 | 13 | bool Term::Color::operator!=(const Term::Color& color) const { return !(*this == color); } 14 | 15 | Term::Color::Color() : m_bit8(0) {} 16 | 17 | Term::Color::Color(const Term::Color::Name& name) : m_Type(Type::Bit4), m_bit8(static_cast(name)) {} 18 | 19 | Term::Color::Color(const std::uint8_t& color) : m_Type(Type::Bit8), m_bit8(color) {} 20 | 21 | Term::Color::Color(const std::uint8_t& r, const std::uint8_t& b, const std::uint8_t& g) : m_Type(Type::Bit24) 22 | { 23 | // Hack for gcc4.7 24 | m_bit24[0] = r; 25 | m_bit24[1] = b; 26 | m_bit24[2] = g; 27 | } 28 | 29 | Term::Color::Type Term::Color::getType() const { return m_Type; } 30 | 31 | Term::Color::Name Term::Color::to3bits() const 32 | { 33 | if(getType() == Type::Bit3) return static_cast(m_bit8); 34 | else 35 | { 36 | Term::Color::Name ret{to4bits()}; 37 | if(ret >= Term::Color::Name::Gray) return static_cast(static_cast(ret) - static_cast(Term::Color::Name::Gray)); 38 | else 39 | return ret; 40 | } 41 | } 42 | 43 | // Convert 24bits and 8bits to 4bits 44 | Term::Color::Name Term::Color::to4bits() const 45 | { 46 | //https://ajalt.github.io/colormath/converter/ 47 | // clang-format off 48 | static const std::array table ={ 49 | 0, 1, 2, 3, 4, 5, 6, 7, 60, 61, 62, 63, 64, 65, 66, 67, 0, 4, 4, 4, 64, 64, 2, 6, 4, 4, 64, 64, 2, 2, 6, 4, 64, 64, 2, 2, 2, 6, 64, 64, 62, 62, 62, 62, 66, 64, 62, 62, 62, 62, 62, 66, 50 | 1, 5, 4, 4, 64, 64, 3, 60, 4, 4, 64, 64, 2, 2, 6, 4, 64, 64, 2, 2, 2, 6, 64, 64, 62, 62, 62, 62, 66, 64, 62, 62, 62, 62, 62, 66, 1, 1, 5, 4, 64, 64, 1, 1, 5, 4, 64, 64, 3, 3, 60, 4, 51 | 64, 64, 2, 2, 2, 6, 64, 64, 62, 62, 62, 62, 66, 64, 62, 62, 62, 62, 62, 66, 1, 1, 1, 5, 64, 64, 1, 1, 1, 5, 64, 64, 1, 1, 1, 5, 64, 64, 3, 3, 3, 7, 64, 64, 62, 62, 62, 62, 66, 64, 62, 62, 52 | 62, 62, 62, 66, 61, 61, 61, 61, 65, 64, 61, 61, 61, 61, 65, 64, 61, 61, 61, 61, 65, 64, 61, 61, 61, 61, 65, 64, 63, 63, 63, 63, 7, 64, 62, 62, 62, 62, 62, 66, 61, 61, 61, 61, 61, 65, 61, 61, 61, 61, 61, 65, 53 | 61, 61, 61, 61, 61, 65, 61, 61, 61, 61, 61, 65, 61, 61, 61, 61, 61, 65, 63, 63, 63, 63, 63, 67, 0, 0, 0, 0, 0, 0, 60, 60, 60, 60, 60, 60, 7, 7, 7, 7, 7, 7, 67, 67, 67, 67, 67, 67}; 54 | // clang-format on 55 | if(getType() == Term::Color::Type::Bit24 || getType() == Term::Color::Type::Bit8) { return static_cast(table[to8bits()]); } 56 | else 57 | return static_cast(m_bit8); 58 | } 59 | 60 | // Convert 24bits to 8bits 61 | std::uint8_t Term::Color::to8bits() const 62 | { 63 | if(getType() == Term::Color::Type::Bit24) 64 | { 65 | // check gray scale in 24 steps 66 | if(m_bit24[0] == m_bit24[1] && m_bit24[0] == m_bit24[2]) { return 232 + m_bit24[0] / 32 + m_bit24[1] / 32 + m_bit24[2] / 32; } 67 | // normal color space 68 | return 16 + 36 * (m_bit24[0] / 51) + 6 * (m_bit24[1] / 51) + (m_bit24[2] / 51); 69 | } 70 | else 71 | return m_bit8; 72 | } 73 | 74 | // Nothing to do 75 | std::array Term::Color::to24bits() const { return m_bit24; } 76 | 77 | std::string Term::color_bg(const Term::Color::Name& color) { return color_bg(Color(color)); } 78 | 79 | std::string Term::color_bg(const std::uint8_t& color) { return color_bg(Color(color)); } 80 | 81 | std::string Term::color_bg(const std::uint8_t& r, const std::uint8_t& g, const std::uint8_t& b) { return color_bg(Color(r, g, b)); } 82 | 83 | //https://unix.stackexchange.com/questions/212933/background-color-whitespace-when-end-of-the-terminal-reached 84 | //FIX maybe we need another function without  if we want to modify background of part of the screen (Moving cursor and changing color ) 85 | std::string Term::color_bg(const Color& color) 86 | { 87 | if(color.getType() == Term::Color::Type::Unset || color.getType() == Term::Color::Type::NoColor) return ""; 88 | else 89 | switch(Term::Terminfo::getColorMode()) 90 | { 91 | case Term::Terminfo::ColorMode::Unset: 92 | case Term::Terminfo::ColorMode::NoColor: return ""; 93 | case Term::Terminfo::ColorMode::Bit3: return "\033[" + std::to_string(static_cast(color.to3bits()) + 40) + "m\033[K"; 94 | case Term::Terminfo::ColorMode::Bit4: return "\033[" + std::to_string(static_cast(color.to4bits()) + 40) + "m\033[K"; 95 | case Term::Terminfo::ColorMode::Bit8: 96 | if(color.getType() == Term::Color::Type::Bit4 || color.getType() == Term::Color::Type::Bit3) return "\033[" + std::to_string(static_cast(color.to4bits()) + 40) + "m\033[K"; 97 | else 98 | return "\033[48;5;" + std::to_string(color.to8bits()) + "m\033[K"; 99 | case Term::Terminfo::ColorMode::Bit24: 100 | if(color.getType() == Term::Color::Type::Bit3 || color.getType() == Term::Color::Type::Bit4) return "\033[" + std::to_string(static_cast(color.to4bits()) + 40) + "m\033[K"; 101 | else if(color.getType() == Term::Color::Type::Bit8) 102 | return "\033[48;5;" + std::to_string(color.to8bits()) + "m\033[K"; 103 | else 104 | return "\033[48;2;" + std::to_string(color.to24bits()[0]) + ';' + std::to_string(color.to24bits()[1]) + ';' + std::to_string(color.to24bits()[2]) + "m\033[K"; 105 | } 106 | return ""; 107 | } 108 | 109 | std::string Term::color_fg(const Term::Color::Name& color) { return color_fg(Color(color)); } 110 | 111 | std::string Term::color_fg(const std::uint8_t& color) { return color_fg(Color(color)); } 112 | 113 | std::string Term::color_fg(const std::uint8_t& r, const std::uint8_t& g, const std::uint8_t& b) { return color_fg(Color(r, g, b)); } 114 | 115 | std::string Term::color_fg(const Color& color) 116 | { 117 | if(color.getType() == Term::Color::Type::Unset || color.getType() == Term::Color::Type::NoColor) return ""; 118 | else 119 | switch(Term::Terminfo::getColorMode()) 120 | { 121 | case Term::Terminfo::ColorMode::Unset: 122 | case Term::Terminfo::ColorMode::NoColor: return ""; 123 | case Term::Terminfo::ColorMode::Bit3: return "\033[" + std::to_string(static_cast(color.to3bits()) + 30) + "m"; 124 | case Term::Terminfo::ColorMode::Bit4: return "\033[" + std::to_string(static_cast(color.to4bits()) + 30) + "m"; 125 | case Term::Terminfo::ColorMode::Bit8: 126 | if(color.getType() == Term::Color::Type::Bit4 || color.getType() == Term::Color::Type::Bit3) return "\033[" + std::to_string(static_cast(color.to4bits()) + 30) + "m"; 127 | else 128 | return "\033[38;5;" + std::to_string(color.to8bits()) + "m"; 129 | case Term::Terminfo::ColorMode::Bit24: 130 | if(color.getType() == Term::Color::Type::Bit3 || color.getType() == Term::Color::Type::Bit4) return "\033[" + std::to_string(static_cast(color.to4bits()) + 30) + "m"; 131 | else if(color.getType() == Term::Color::Type::Bit8) 132 | return "\033[38;5;" + std::to_string(color.to8bits()) + "m"; 133 | else 134 | return "\033[38;2;" + std::to_string(color.to24bits()[0]) + ';' + std::to_string(color.to24bits()[1]) + ';' + std::to_string(color.to24bits()[2]) + "m"; 135 | } 136 | return ""; 137 | } 138 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/color.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "terminfo.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace Term 10 | { 11 | 12 | class Color 13 | { 14 | public: 15 | enum class Type : std::uint8_t 16 | { 17 | Unset, 18 | NoColor, 19 | Bit3, 20 | Bit4, 21 | Bit8, 22 | Bit24, 23 | }; 24 | /* 25 | * The 3bit/4bit colors for the terminal 26 | * get the foreground color: Color4 + 30, Background color: Color4 + 40 27 | * See https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit 28 | */ 29 | enum class Name : std::uint8_t 30 | { 31 | // FG: 30, BG: 40 32 | Black = 0, 33 | // FG: 31, BG: 41 34 | Red = 1, 35 | // FG: 32, BG: 42 36 | Green = 2, 37 | // FG: 33, BG: 43 38 | Yellow = 3, 39 | // FG: 34, BG: 44 40 | Blue = 4, 41 | // FG: 35, BG: 45 42 | Magenta = 5, 43 | // FG: 36, BG: 46 44 | Cyan = 6, 45 | // FG: 37, BG: 47 46 | White = 7, 47 | // Use the default terminal color, FG: 39, BG: 49 48 | Default = 9, 49 | // FG: 90, BG: 100 50 | Gray = 60, 51 | BrightBlack = 60, 52 | // FG: 91, BG: 101 53 | BrightRed = 61, 54 | // FG: 92, BG: 102 55 | BrightGreen = 62, 56 | // FG: 93, BG: 103 57 | BrightYellow = 63, 58 | // FG: 94, BG: 104 59 | BrightBlue = 64, 60 | // FG: 95, BG: 105 61 | BrightMagenta = 65, 62 | // FG: 96, BG: 106 63 | BrightCyan = 66, 64 | // FG: 97, BG: 107 65 | BrightWhite = 67 66 | }; 67 | bool operator==(const Color&) const; 68 | bool operator!=(const Color&) const; 69 | Color(); 70 | Color(const Term::Color::Name&); 71 | Color(const std::uint8_t&); 72 | Color(const std::uint8_t&, const std::uint8_t&, const std::uint8_t&); 73 | Type getType() const; 74 | Name to3bits() const; 75 | Name to4bits() const; 76 | std::uint8_t to8bits() const; 77 | std::array to24bits() const; 78 | 79 | private: 80 | Type m_Type{Type::Unset}; 81 | union 82 | { 83 | std::uint8_t m_bit8; 84 | std::array m_bit24; 85 | }; 86 | }; 87 | 88 | std::string color_bg(const Term::Color::Name&); 89 | std::string color_bg(const std::uint8_t&); 90 | std::string color_bg(const std::uint8_t&, const std::uint8_t&, const std::uint8_t&); 91 | std::string color_bg(const Color&); 92 | 93 | std::string color_fg(const Term::Color::Name&); 94 | std::string color_fg(const std::uint8_t&); 95 | std::string color_fg(const std::uint8_t&, const std::uint8_t&, const std::uint8_t&); 96 | std::string color_fg(const Color&); 97 | 98 | } // namespace Term 99 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/exception.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace Term { 9 | 10 | class Exception : public std::exception { 11 | std::string m_what; 12 | 13 | public: 14 | explicit Exception(std::string what) : m_what(std::move(what)) {}; 15 | 16 | [[nodiscard]] const char* what() const noexcept override { 17 | return m_what.c_str(); 18 | } 19 | }; 20 | } // namespace Term 21 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/input.cpp: -------------------------------------------------------------------------------- 1 | // Bug in some GCC 2 | #if !defined(_GLIBCXX_USE_NANOSLEEP) 3 | #define _GLIBCXX_USE_NANOSLEEP 4 | #endif 5 | 6 | #include "input.hpp" 7 | 8 | #include 9 | #include 10 | 11 | bool Term::is_ASCII(const Term::Key& key) { 12 | if (key >= 0 && key <= 127) { 13 | return true; 14 | } 15 | return false; 16 | } 17 | 18 | bool Term::is_extended_ASCII(const Term::Key& key) { 19 | if (key >= 0 && key <= 255) { 20 | return true; 21 | } 22 | return false; 23 | } 24 | 25 | bool Term::is_CTRL(const Term::Key& key) { 26 | // Need to suppress the TAB etc... 27 | if (key > 0 && key <= 31 && key != BACKSPACE && key != TAB 28 | && key != ESC && /* the two mapped to ENTER */ key != LF && key != CR) { 29 | return true; 30 | } 31 | return false; 32 | } 33 | 34 | bool Term::is_ALT(const Term::Key& key) { 35 | if ((key & ALT) == ALT) { 36 | return true; 37 | } 38 | return false; 39 | } 40 | 41 | int32_t Term::read_key() { 42 | int32_t key; 43 | while ((key = read_key0()) == NO_KEY) { 44 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); 45 | } 46 | return key; 47 | } 48 | 49 | int32_t Term::read_key0() { 50 | char c{}; 51 | if (!Platform::read_raw(&c)) { 52 | return NO_KEY; 53 | } 54 | if (is_CTRL(static_cast(c))) { 55 | return c; 56 | } else if (c == ESC) { 57 | char seq[4]{'\0', '\0', '\0', '\0'}; 58 | if (!Platform::read_raw(&seq[0])) { 59 | return ESC; 60 | } 61 | if (!Platform::read_raw(&seq[1])) { 62 | if (seq[0] >= 'a' && seq[0] <= 'z') { 63 | // gnome-term, Windows Console 64 | return ALT + seq[0]; 65 | } 66 | if (seq[0] == '\x0d') { 67 | // gnome-term 68 | return ALT_ENTER; 69 | } 70 | return -1; 71 | } 72 | if (seq[0] == '[') { 73 | if (seq[1] >= '0' && seq[1] <= '9') { 74 | if (!Platform::read_raw(&seq[2])) { 75 | return -2; 76 | } 77 | if (seq[2] == '~') { 78 | switch (seq[1]) { 79 | case '1': return HOME; 80 | case '2': return INSERT; 81 | case '3': return DEL; 82 | case '4': return END; 83 | case '5': return PAGE_UP; 84 | case '6': return PAGE_DOWN; 85 | case '7': return HOME; 86 | case '8': return END; 87 | } 88 | } else if (seq[2] == ';') { 89 | if (seq[1] == '1') { 90 | if (!Platform::read_raw(&seq[2])) { 91 | return -10; 92 | } 93 | if (!Platform::read_raw(&seq[3])) { 94 | return -11; 95 | } 96 | if (seq[2] == '5') { 97 | switch (seq[3]) { 98 | case 'A': return CTRL_UP; 99 | case 'B': return CTRL_DOWN; 100 | case 'C': return CTRL_RIGHT; 101 | case 'D': return CTRL_LEFT; 102 | } 103 | } 104 | return -12; 105 | } 106 | } else { 107 | if (seq[2] >= '0' && seq[2] <= '9') { 108 | if (!Platform::read_raw(&seq[3])) { 109 | return -3; 110 | } 111 | if (seq[3] == '~') { 112 | if (seq[1] == '1') { 113 | switch (seq[2]) { 114 | case '5': return F5; 115 | case '7': return F6; 116 | case '8': return F7; 117 | case '9': return F8; 118 | } 119 | } else if (seq[1] == '2') { 120 | switch (seq[2]) { 121 | case '0': return F9; 122 | case '1': return F10; 123 | case '3': return F11; 124 | case '4': return F12; 125 | } 126 | } 127 | } 128 | } 129 | } 130 | } else { 131 | switch (seq[1]) { 132 | case 'A': return ARROW_UP; 133 | case 'B': return ARROW_DOWN; 134 | case 'C': return ARROW_RIGHT; 135 | case 'D': return ARROW_LEFT; 136 | case 'E': return NUMERIC_5; 137 | case 'H': return HOME; 138 | case 'F': return END; 139 | } 140 | } 141 | } else if (seq[0] == 'O') { 142 | switch (seq[1]) { 143 | case 'F': return END; 144 | case 'H': return HOME; 145 | case 'P': return F1; 146 | case 'Q': return F2; 147 | case 'R': return F3; 148 | case 'S': return F4; 149 | } 150 | } 151 | return -4; 152 | } else { 153 | switch (c) { 154 | case DEL: return BACKSPACE; 155 | case LF: 156 | case CR: return ENTER; 157 | default: break; 158 | } if (c == '\xc3') { 159 | if (!Platform::read_raw(&c)) { 160 | return -8; 161 | } else { 162 | if (c >= '\xa1' && c <= '\xba') { 163 | // xterm 164 | return ALT + (c + 'a' - '\xa1'); 165 | } 166 | return -9; 167 | } 168 | } else if (c == '\xc2') { 169 | if (!Platform::read_raw(&c)) { 170 | return -10; 171 | } else { 172 | if (c == '\x8d') { 173 | // xterm 174 | return ALT_ENTER; 175 | } 176 | return -11; 177 | } 178 | } 179 | return c; 180 | } 181 | } 182 | 183 | //Returns the whole input from STDIN as string. 184 | std::string Term::read_stdin() { 185 | std::string file; 186 | char c; //No need to initialize. 187 | while (true) { 188 | c = Platform::read_raw_stdin(); 189 | if (c == 0x04) { 190 | return file; 191 | } else { 192 | file.push_back(c); 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/input.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace Term { 6 | 7 | enum Key : std::int32_t { 8 | NO_KEY = -1, 9 | // Begin ASCII (some ASCII names has been change to their CTRL + key part) 10 | NUL = 0, 11 | CTRL_A = 1, 12 | CTRL_B = 2, 13 | CTRL_C = 3, 14 | CTRL_D = 4, 15 | CTRL_E = 5, 16 | CTRL_F = 6, 17 | CTRL_G = 7, 18 | BACKSPACE = 8, 19 | TAB = 9, 20 | ENTER = 10, 21 | LF = 10, 22 | CTRL_K = 11, 23 | CTRL_L = 12, 24 | CR = 13, // Mapped to ENTER 25 | CTRL_N = 14, 26 | CTRL_O = 15, 27 | CTRL_P = 16, 28 | CTRL_Q = 17, 29 | CTRL_R = 18, 30 | CTRL_S = 19, 31 | CTRL_T = 20, 32 | CTRL_U = 21, 33 | CTRL_V = 22, 34 | CTRL_W = 23, 35 | CTRL_X = 24, 36 | CTRL_Y = 25, 37 | CTRL_Z = 26, 38 | ESC = 27, 39 | CTRL_SLASH = 28, 40 | CTRL_CLOSE_BRACKET = 29, 41 | CTRL_CARET = 30, 42 | CTRL_UNDERSCORE = 31, 43 | SPACE = 32, 44 | EXCLAMATION_MARK = 33, 45 | QUOTE = 34, 46 | HASH = 35, 47 | DOLLAR = 36, 48 | PERCENT = 37, 49 | AMPERSAND = 38, 50 | APOSTROPHE = 39, 51 | OPEN_PARENTHESIS = 40, 52 | CLOSE_PARENTHESIS = 41, 53 | ASTERISK = 42, 54 | PLUS = 43, 55 | COMMA = 44, 56 | HYPHEN = 45, 57 | MINUS = 45, 58 | PERIOD = 46, 59 | SLASH = 47, 60 | ZERO = 48, 61 | ONE = 49, 62 | TWO = 50, 63 | THREE = 51, 64 | FOUR = 52, 65 | FIVE = 53, 66 | SIX = 54, 67 | SEVEN = 55, 68 | EIGHT = 56, 69 | NINE = 57, 70 | COLON = 58, 71 | SEMICOLON = 59, 72 | LESS_THAN = 60, 73 | OPEN_CHEVRON = 60, 74 | EQUAL = 61, 75 | GREATER_THAN = 62, 76 | CLOSE_CHEVRON = 62, 77 | QUESTION_MARK = 63, 78 | AROBASE = 64, 79 | A = 65, 80 | B = 66, 81 | C = 67, 82 | D = 68, 83 | E = 69, 84 | F = 70, 85 | G = 71, 86 | H = 72, 87 | I = 73, 88 | J = 74, 89 | K = 75, 90 | L = 76, 91 | M = 77, 92 | N = 78, 93 | O = 79, 94 | P = 80, 95 | Q = 81, 96 | R = 82, 97 | S = 83, 98 | T = 84, 99 | U = 85, 100 | V = 86, 101 | W = 87, 102 | X = 88, 103 | Y = 89, 104 | Z = 90, 105 | OPEN_BRACKET = 91, 106 | BACKSLASH = 92, 107 | CLOSE_BRACKET = 93, 108 | CARET = 94, 109 | UNDERSCORE = 95, 110 | GRAVE_ACCENT = 96, 111 | a = 97, 112 | b = 98, 113 | c = 99, 114 | d = 100, 115 | e = 101, 116 | f = 102, 117 | g = 103, 118 | h = 104, 119 | i = 105, 120 | j = 106, 121 | k = 107, 122 | l = 108, 123 | m = 109, 124 | n = 110, 125 | o = 111, 126 | p = 112, 127 | q = 113, 128 | r = 114, 129 | s = 115, 130 | t = 116, 131 | u = 117, 132 | v = 118, 133 | w = 119, 134 | x = 120, 135 | y = 121, 136 | z = 122, 137 | OPEN_BRACE = 123, 138 | VERTICAL_BAR = 124, 139 | CLOSE_BRACE = 125, 140 | TILDE = 126, 141 | DEL = 127, 142 | // End ASCII 143 | // Extended ANSII goes up to 255 144 | ALT_ENTER = 256, 145 | ARROW_LEFT, 146 | ARROW_RIGHT, 147 | ARROW_UP, 148 | ARROW_DOWN, 149 | CTRL_UP, 150 | CTRL_DOWN, 151 | CTRL_RIGHT, 152 | CTRL_LEFT, 153 | NUMERIC_5, 154 | HOME, 155 | INSERT, 156 | END, 157 | PAGE_UP, 158 | PAGE_DOWN, 159 | F1, 160 | F2, 161 | F3, 162 | F4, 163 | F5, 164 | F6, 165 | F7, 166 | F8, 167 | F9, 168 | F10, 169 | F11, 170 | F12, 171 | // Keys below need to be under 512 172 | // special keys (CTRL is special^2) 173 | CTRL = -AROBASE, 174 | // Now use << to for detecting special key + key press 175 | ALT = (1 << 9) 176 | }; 177 | 178 | // Detect if Key is convertible to ANSII 179 | bool is_ASCII(const Key&); 180 | 181 | // Detect if Key is convertible to Extended ANSII 182 | bool is_extended_ASCII(const Key&); 183 | 184 | // Detect if Key is CTRL+* 185 | bool is_CTRL(const Key&); 186 | 187 | // Detecti if Key is ALT+* 188 | bool is_ALT(const Key&); 189 | 190 | namespace Platform { 191 | // Returns true if a character is read, otherwise immediately returns false 192 | // This can't be made inline 193 | bool read_raw(char* s); 194 | 195 | char read_raw_stdin(); 196 | } // namespace Platform 197 | 198 | // Waits for a key press, translates escape codes 199 | // if Term:Terminal is not enabling the keyboard it'll loop for infinity 200 | std::int32_t read_key(); 201 | 202 | // If there was a key press, returns the translated key from escape codes, 203 | // otherwise returns 0. If the escape code is not supported it returns a 204 | // negative number. 205 | // if Term::Terminal is not enabling the keyboard it'll always return 0 206 | std::int32_t read_key0(); 207 | 208 | // returns the stdin as a string 209 | // waits until the EOT signal is send 210 | // if Term::Terminal is not enabling the keyboard this function will wait until 211 | // the user presses CTRL+D (which sends the EOT signal) 212 | std::string read_stdin(); 213 | } // namespace Term 214 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/inputU.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by v2ray on 2023/4/13. 3 | // 4 | 5 | #ifndef GPT3BOT_INPUTU_HPP 6 | #define GPT3BOT_INPUTU_HPP 7 | 8 | #include "cstdint" 9 | 10 | namespace Term { 11 | 12 | enum KeyU : int32_t { 13 | NO_KEY = -1, 14 | //Begin Unicode (Basic Multilingual Plane). 15 | NUL = 0x0000, 16 | CTRL_A = 0x0001, 17 | CTRL_B = 0x0002, 18 | CTRL_C = 0x0003, 19 | CTRL_D = 0x0004, 20 | CTRL_E = 0x0005, 21 | CTRL_F = 0x0006, 22 | CTRL_G = 0x0007, 23 | BACKSPACE = 0x0008, 24 | TAB = 0x0009, 25 | ENTER = 0x000A, 26 | LF = 0x000A, 27 | CTRL_K = 0x000B, 28 | CTRL_L = 0x000C, 29 | CR = 0x000D, 30 | CTRL_N = 0x000E, 31 | CTRL_O = 0x000F, 32 | CTRL_P = 0x0010, 33 | CTRL_Q = 0x0011, 34 | CTRL_R = 0x0012, 35 | CTRL_S = 0x0013, 36 | CTRL_T = 0x0014, 37 | CTRL_U = 0x0015, 38 | CTRL_V = 0x0016, 39 | CTRL_W = 0x0017, 40 | CTRL_X = 0x0018, 41 | CTRL_Y = 0x0019, 42 | CTRL_Z = 0x001A, 43 | ESC = 0x001B, 44 | SPACE = 0x0020, 45 | DEL = 0x007F, 46 | //...(Other Unicode characters) 47 | 48 | //Special keys(Need to be greater than 0x10FFFF). 49 | ALT_ENTER = 0x110000, 50 | ARROW_LEFT, 51 | ARROW_RIGHT, 52 | ARROW_UP, 53 | ARROW_DOWN, 54 | CTRL_UP, 55 | CTRL_DOWN, 56 | CTRL_RIGHT, 57 | CTRL_LEFT, 58 | NUMERIC_5, 59 | HOME, 60 | INSERT, 61 | END, 62 | PAGE_UP, 63 | PAGE_DOWN, 64 | F1, 65 | F2, 66 | F3, 67 | F4, 68 | F5, 69 | F6, 70 | F7, 71 | F8, 72 | F9, 73 | F10, 74 | F11, 75 | F12, 76 | CTRL = -64, 77 | //Now use << to for detecting special key + key press. 78 | ALT = (1 << 21) 79 | }; 80 | 81 | namespace Platform { 82 | 83 | bool read_raw_u(char32_t* c32); 84 | bool is_character_u(const KeyU& key); 85 | bool is_CTRL_u(const KeyU& key); 86 | bool is_control_char(const char32_t& c32); 87 | int32_t read_key_u(); 88 | int32_t read_key0_u(); 89 | } 90 | } // Term::Platform 91 | 92 | #endif //GPT3BOT_INPUTU_HPP 93 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/platforms/conversion.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../exception.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | static constexpr uint8_t UTF8_ACCEPT = 0; 10 | static constexpr uint8_t UTF8_REJECT = 0xf; 11 | 12 | namespace Term::Private { 13 | 14 | inline uint8_t utf8_decode_step(uint8_t state, uint8_t octet, uint32_t* cpp) { 15 | static const uint32_t utf8ClassTab[0x10] = { 16 | 0x88888888UL, 0x88888888UL, 0x99999999UL, 0x99999999UL, 0xaaaaaaaaUL, 0xaaaaaaaaUL, 0xaaaaaaaaUL, 0xaaaaaaaaUL, 17 | 0x222222ffUL, 0x22222222UL, 0x22222222UL, 0x22222222UL, 0x3333333bUL, 0x33433333UL, 0xfff5666cUL, 0xffffffffUL 18 | }; 19 | static const uint32_t utf8StateTab[0x10] = { 20 | 0xfffffff0UL, 0xffffffffUL, 0xfffffff1UL, 0xfffffff3UL, 0xfffffff4UL, 0xfffffff7UL, 0xfffffff6UL, 0xffffffffUL, 21 | 0x33f11f0fUL, 0xf3311f0fUL, 0xf33f110fUL, 0xfffffff2UL, 0xfffffff5UL, 0xffffffffUL, 0xffffffffUL, 0xffffffffUL 22 | }; 23 | 24 | const uint8_t reject = (state >> 3), nonAscii = (octet >> 7); 25 | const uint8_t class_ = (!nonAscii ? 0 : (0xf & (utf8ClassTab[(octet >> 3) & 0xf] >> (4 * (octet & 7))))); 26 | 27 | *cpp = (state == UTF8_ACCEPT ? (octet & (0xffU >> class_)) : ((octet & 0x3fU) | (*cpp << 6))); 28 | 29 | return (reject ? 0xf : (0xf & (utf8StateTab[class_] >> (4 * (state & 7))))); 30 | } 31 | 32 | inline void codepoint_to_utf8(std::string& s, const char32_t& c) { 33 | if (c > 0x0010FFFF) { 34 | throw Exception("Invalid UTF32 codepoint."); 35 | } 36 | char bytes[4]; 37 | int n_bytes = 1; 38 | char32_t d = c; 39 | if (c >= 0x10000) { 40 | n_bytes++; 41 | bytes[3] = static_cast((d | 0x80) & 0xBF); 42 | d >>= 6; 43 | } 44 | if (c >= 0x800) { 45 | n_bytes++; 46 | bytes[2] = static_cast((d | 0x80) & 0xBF); 47 | d >>= 6; 48 | } 49 | if (c >= 0x80) { 50 | n_bytes++; 51 | bytes[1] = static_cast((d | 0x80) & 0xBF); 52 | d >>= 6; 53 | } 54 | static const unsigned char mask[4] = {0x00, 0xC0, 0xE0, 0xF0}; 55 | bytes[0] = static_cast(d | mask[n_bytes - 1]); 56 | s.append(bytes, n_bytes); 57 | } 58 | 59 | inline std::u32string utf8_to_utf32(const std::string& s) { 60 | uint32_t codepoint = 0; 61 | uint8_t state = UTF8_ACCEPT; 62 | std::u32string r; 63 | for (const char& i : s) { 64 | state = utf8_decode_step(state, i, &codepoint); 65 | if (state == UTF8_ACCEPT) { 66 | r.push_back(codepoint); 67 | } else if (state == UTF8_REJECT) { 68 | throw Exception("Invalid byte in UTF8 encoded string."); 69 | } 70 | } 71 | if (state != UTF8_ACCEPT) { 72 | throw Exception("Expected more bytes in UTF8 encoded string."); 73 | } 74 | return r; 75 | } 76 | 77 | inline std::string utf32_to_utf8(const std::u32string& u32s) { 78 | std::string s; 79 | for (const char32_t& i : u32s) { 80 | codepoint_to_utf8(s, i); 81 | } 82 | return s; 83 | } 84 | 85 | /** 86 | * Converts a vector of char into a string. 87 | */ 88 | inline std::string vector_to_string(const std::vector& vector) { 89 | std::string s; 90 | for (const char& i : vector) { 91 | s.push_back(i); 92 | } 93 | return s; 94 | } 95 | } // namespace Term 96 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/platforms/input.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | #include 3 | #else 4 | #include 5 | #include 6 | #include 7 | #endif 8 | 9 | #include "../exception.hpp" 10 | #include "../input.hpp" 11 | #include "../tty.hpp" 12 | 13 | char Term::Platform::read_raw_stdin() { 14 | char c = static_cast(getchar()); 15 | if (c >= 0) { 16 | return c; 17 | } else if (c == EOF) { 18 | //In non-raw (blocking) mode this happens when the input file 19 | //ends. In such a case, return the End of Transmission (EOT) 20 | //character (Ctrl-D) 21 | return 0x04; 22 | } else { 23 | throw Exception("getchar() failed."); 24 | } 25 | } 26 | 27 | bool Term::Platform::read_raw(char* s) { 28 | //Do nothing when TTY is not connected. 29 | if (!is_stdin_a_tty()) { 30 | return false; 31 | } 32 | #ifdef _WIN32 33 | DWORD n_read = 0; 34 | GetNumberOfConsoleInputEvents(GetStdHandle(STD_INPUT_HANDLE), &n_read); 35 | if (n_read >= 1) { 36 | INPUT_RECORD buf; 37 | if (!ReadConsoleInputW(GetStdHandle(STD_INPUT_HANDLE), &buf, 1, &n_read)) { 38 | throw Exception("ReadConsoleInput() failed."); 39 | } 40 | if (n_read == 1) { 41 | switch (buf.EventType) { 42 | case KEY_EVENT: { 43 | WORD skip = buf.Event.KeyEvent.wVirtualKeyCode; //Skip them for now. 44 | if (skip == VK_SHIFT || skip == VK_LWIN || skip == VK_RWIN || skip == VK_APPS 45 | || skip == VK_CONTROL || skip == VK_MENU || skip == VK_CAPITAL) { 46 | return false; 47 | } 48 | if (buf.Event.KeyEvent.bKeyDown) { 49 | *s = buf.Event.KeyEvent.uChar.AsciiChar; 50 | return true; 51 | } else { 52 | return false; 53 | } 54 | } 55 | case FOCUS_EVENT: 56 | case MENU_EVENT: 57 | case MOUSE_EVENT: 58 | case WINDOW_BUFFER_SIZE_EVENT: 59 | default: 60 | return false; 61 | } 62 | } else { 63 | throw Exception("kbhit() and ReadConsoleInput() inconsistent."); 64 | } 65 | } else { 66 | return false; 67 | } 68 | #else 69 | ::ssize_t nread = ::read(0, s, 1); 70 | if (nread == -1 && errno != EAGAIN) { 71 | throw Term::Exception("read() failed"); 72 | } 73 | return nread == 1; 74 | #endif 75 | } 76 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/platforms/macros.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if __cplusplus >= 201703L 4 | 5 | #if defined(__GNUC__) && (__GNUC__ > 7) 6 | #define CPP_TERMINAL_NODISCARD [[nodiscard]] 7 | #elif defined(__clang_major__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ > 8)) 8 | #define CPP_TERMINAL_NODISCARD [[nodiscard]] 9 | #else 10 | #define CPP_TERMINAL_NODISCARD 11 | #endif 12 | 13 | #if defined(__GNUC__) && (__GNUC__ > 5) 14 | #define CPP_TERMINAL_FALLTHROUGH [[fallthrough]] 15 | #elif defined(__clang_major__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ > 5)) 16 | #define CPP_TERMINAL_FALLTHROUGH [[fallthrough]] 17 | #else 18 | #define CPP_TERMINAL_FALLTHROUGH 19 | #endif 20 | 21 | #if defined(__GNUC__) && (__GNUC__ > 5) 22 | #define CPP_TERMINAL_MAYBE_UNUSED [[maybe_unused]] 23 | #elif defined(__clang_major__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ > 5)) 24 | #define CPP_TERMINAL_MAYBE_UNUSED [[maybe_unused]] 25 | #else 26 | #define CPP_TERMINAL_MAYBE_UNUSED 27 | #endif 28 | 29 | #else 30 | #define CPP_TERMINAL_NODISCARD 31 | #define CPP_TERMINAL_FALLTHROUGH 32 | #define CPP_TERMINAL_MAYBE_UNUSED 33 | #endif 34 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/platforms/platform.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Term::Private { 7 | 8 | // returns the terminal size as (rows, columns) / (Y, X), throws a runtime error 9 | // if the console is not connected 10 | std::pair get_term_size(); 11 | 12 | short c32_display_width(const char32_t& c32); 13 | } // namespace Term 14 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/platforms/terminal.cpp: -------------------------------------------------------------------------------- 1 | #include "../terminal.hpp" 2 | 3 | #ifdef _WIN32 4 | #include 5 | #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING 6 | #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 7 | #endif 8 | #ifndef DISABLE_NEWLINE_AUTO_RETURN 9 | #define DISABLE_NEWLINE_AUTO_RETURN 0x0008 10 | #endif 11 | #ifndef ENABLE_VIRTUAL_TERMINAL_INPUT 12 | #define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 13 | #endif 14 | #else 15 | #include 16 | #endif 17 | 18 | #include "../exception.hpp" 19 | #include "../tty.hpp" 20 | 21 | void Term::Terminal::store_and_restore() 22 | { 23 | static bool enabled{false}; 24 | #ifdef _WIN32 25 | static DWORD dwOriginalOutMode{}; 26 | static UINT out_code_page{}; 27 | static DWORD dwOriginalInMode{}; 28 | static UINT in_code_page{}; 29 | if(!enabled) 30 | { 31 | if(Term::is_stdout_a_tty()) 32 | { 33 | out_code_page = GetConsoleOutputCP(); 34 | if(!GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &dwOriginalOutMode)) { throw Term::Exception("GetConsoleMode() failed"); } 35 | } 36 | if(Term::is_stdin_a_tty()) 37 | { 38 | in_code_page = GetConsoleCP(); 39 | if(!GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &dwOriginalInMode)) { throw Term::Exception("GetConsoleMode() failed"); } 40 | } 41 | enabled = true; 42 | } 43 | else 44 | { 45 | if(Term::is_stdout_a_tty()) 46 | { 47 | SetConsoleOutputCP(out_code_page); 48 | if(!SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), dwOriginalOutMode)) { throw Term::Exception("SetConsoleMode() failed in destructor"); } 49 | } 50 | if(Term::is_stdin_a_tty()) 51 | { 52 | SetConsoleCP(in_code_page); 53 | if(!SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), dwOriginalInMode)) { throw Term::Exception("SetConsoleMode() failed in destructor"); } 54 | } 55 | } 56 | #else 57 | static termios orig_termios; 58 | if(!enabled) 59 | { 60 | if(Term::is_stdin_a_tty()) 61 | { 62 | if(tcgetattr(0, &orig_termios) == -1) { throw Term::Exception("tcgetattr() failed"); } 63 | } 64 | enabled = true; 65 | } 66 | else 67 | { 68 | if(Term::is_stdin_a_tty()) 69 | { 70 | if(tcsetattr(0, TCSAFLUSH, &orig_termios) == -1) { throw Term::Exception("tcsetattr() failed in destructor"); } 71 | } 72 | } 73 | #endif 74 | } 75 | 76 | void Term::Terminal::setRawMode() 77 | { 78 | #ifdef _WIN32 79 | if(Term::is_stdout_a_tty()) 80 | { 81 | SetConsoleOutputCP(65001); 82 | DWORD flags{0}; 83 | if(!GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &flags)) { throw Term::Exception("GetConsoleMode() failed"); } 84 | if(m_terminfo.hasANSIEscapeCode()) 85 | { 86 | flags |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; 87 | flags |= DISABLE_NEWLINE_AUTO_RETURN; 88 | } 89 | if(!SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), flags)) { throw Term::Exception("SetConsoleMode() failed"); } 90 | } 91 | 92 | if(Term::is_stdin_a_tty()) 93 | { 94 | SetConsoleCP(65001); 95 | DWORD flags{0}; 96 | if(!GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &flags)) { throw Term::Exception("GetConsoleMode() failed"); } 97 | if(m_terminfo.hasANSIEscapeCode()) { flags |= ENABLE_VIRTUAL_TERMINAL_INPUT; } 98 | if(disable_signal_keys) { flags &= ~ENABLE_PROCESSED_INPUT; } 99 | flags &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); 100 | if(!SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), flags)) { throw Term::Exception("SetConsoleMode() failed"); } 101 | } 102 | #else 103 | int i{-1}; 104 | if(Term::is_stdin_a_tty()) i = 0; 105 | else if(Term::is_stdout_a_tty()) 106 | i = 1; 107 | else if(Term::is_stderr_a_tty()) 108 | i = 2; 109 | if(i >= 0) 110 | { 111 | termios raw{}; 112 | if(tcgetattr(i, &raw) == -1) { throw Term::Exception("tcgetattr() failed"); } 113 | // Put terminal in raw mode 114 | raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); 115 | // This disables output post-processing, requiring explicit \r\n. We 116 | // keep it enabled, so that in C++, one can still just use std::endl 117 | // for EOL instead of "\r\n". 118 | // raw.c_oflag &= ~(OPOST); 119 | raw.c_cflag |= CS8; 120 | raw.c_lflag &= ~(ECHO | ICANON | IEXTEN); 121 | if(disable_signal_keys) { raw.c_lflag &= ~ISIG; } 122 | raw.c_cc[VMIN] = 0; 123 | raw.c_cc[VTIME] = 0; 124 | if(tcsetattr(i, TCSAFLUSH, &raw) == -1) { throw Term::Exception("tcsetattr() failed"); } 125 | } 126 | #endif 127 | } 128 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/platforms/terminfo.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | #include 3 | typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW); 4 | #endif 5 | 6 | #include "../terminfo.hpp" 7 | 8 | #include 9 | 10 | #ifdef _WIN32 11 | bool WindowsVersionGreater(const DWORD& major, const DWORD& minor, const DWORD& patch) 12 | { 13 | RtlGetVersionPtr fn = {reinterpret_cast(GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "RtlGetVersion"))}; 14 | if(fn != nullptr) 15 | { 16 | RTL_OSVERSIONINFOW rovi; 17 | rovi.dwOSVersionInfoSize = sizeof(rovi); 18 | if(fn(&rovi) == 0) 19 | { 20 | if(rovi.dwMajorVersion > major || (rovi.dwMajorVersion == major && (rovi.dwMinorVersion > minor || (rovi.dwMinorVersion == minor && rovi.dwBuildNumber >= patch)))) return true; 21 | else 22 | return false; 23 | } 24 | } 25 | return false; 26 | } 27 | #endif 28 | 29 | namespace Private { 30 | std::string getenv(const std::string& env) { 31 | #ifdef _WIN32 32 | std::size_t requiredSize{0}; 33 | getenv_s(&requiredSize, nullptr, 0, env.c_str()); 34 | if (requiredSize == 0) { 35 | return {}; 36 | } 37 | std::string ret; 38 | ret.reserve(requiredSize * sizeof(char)); 39 | getenv_s(&requiredSize, &ret[0], requiredSize, env.c_str()); 40 | return ret; 41 | #else 42 | if (std::getenv(env.c_str()) != nullptr) { 43 | return static_cast(std::getenv(env.c_str())); 44 | } else { 45 | return {}; 46 | } 47 | #endif 48 | } 49 | } // namespace Private 50 | 51 | Term::Terminfo::ColorMode Term::Terminfo::m_colorMode{Term::Terminfo::ColorMode::Unset}; 52 | 53 | Term::Terminfo::Terminfo() 54 | { 55 | setANSIEscapeCode(); 56 | setColorMode(); 57 | } 58 | 59 | bool Term::Terminfo::hasANSIEscapeCode() { return m_ANSIEscapeCode; } 60 | 61 | void Term::Terminfo::setColorMode() 62 | { 63 | std::string colorterm = Private::getenv("COLORTERM"); 64 | if(colorterm == "truecolor" || colorterm == "24bit") m_colorMode = Term::Terminfo::ColorMode::Bit24; 65 | else 66 | m_colorMode = Term::Terminfo::ColorMode::Bit8; 67 | } 68 | 69 | void Term::Terminfo::setANSIEscapeCode() 70 | { 71 | #ifdef _WIN32 72 | if(WindowsVersionGreater(10, 0, 10586)) m_ANSIEscapeCode = true; 73 | else 74 | m_ANSIEscapeCode = false; 75 | #else 76 | m_ANSIEscapeCode = true; 77 | #endif 78 | } 79 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/platforms/tty.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef _WIN32 4 | #include 5 | #else 6 | #include 7 | #endif 8 | 9 | #include "../tty.hpp" 10 | 11 | bool is_a_tty(const FILE* fd) 12 | { 13 | #ifdef _WIN32 14 | return _isatty(_fileno(const_cast(fd))); 15 | #else 16 | return isatty(fileno(const_cast(fd))); 17 | #endif 18 | } 19 | 20 | bool Term::is_stdin_a_tty() { return is_a_tty(stdin); } 21 | 22 | bool Term::is_stdout_a_tty() { return is_a_tty(stdout); } 23 | 24 | bool Term::is_stderr_a_tty() { return is_a_tty(stderr); } 25 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/prompt.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "terminal.hpp" 4 | #include "window.hpp" 5 | #include "functional" 6 | #include "optional" 7 | 8 | namespace Term { 9 | /* Basic prompt */ 10 | 11 | // indicates the results of prompt_blocking() and prompt_non_blocking 12 | enum class Result { 13 | // returned if the user chose yes 14 | YES, 15 | // returned if the user chose no 16 | NO, 17 | // returned of no terminal is attached to the program 18 | ERR, 19 | // returned of the enter key was pressed without additional input 20 | NONE, 21 | // returned if CTRL+C was pressed 22 | ABORT, 23 | // returned if the given input did not match the case 'yes' of 'no' 24 | INVALID 25 | }; 26 | // indicates the results of prompt_simple() 27 | enum class Result_simple { 28 | // returned if the user chose yes 29 | YES, 30 | // returned if the user chose no or invalid / no input or if no terminal is 31 | // attached 32 | NO, 33 | // returned if CTRL+C was pressed 34 | ABORT 35 | }; 36 | 37 | // A simple yes/no prompt, requires the user to press the ENTER key to continue 38 | // The arguments are used like this: 1 [2/3]4 39 | // the immediate switch indicates toggles whether pressing enter for 40 | // confirming the input is required or not 41 | Result prompt(const std::string& message, const std::string& first_option, const std::string& second_option, 42 | const std::string& prompt_indicator, bool); 43 | 44 | // The most simple prompt possible, requires the user to press enter to continue 45 | // The arguments are used like this: 1 [y/N]: 46 | // Invalid input, errors (like no attached terminal) all result in 'no' as 47 | // default 48 | Result_simple prompt_simple(const std::string& message); 49 | 50 | /* Multiline prompt */ 51 | 52 | // This model contains all the information about the state of the prompt in an 53 | // abstract way, irrespective of where or how it is rendered. 54 | class Model { 55 | public: 56 | std::string prompt_string; // The string to show as the prompt 57 | std::vector lines{""}; // The current input string in the prompt as a vector of lines. 58 | // The current cursor position in the "input" string, starting from (1,1) 59 | std::size_t cursor_col{1}; 60 | std::size_t cursor_row{1}; 61 | }; 62 | 63 | std::string concat(const std::vector&); 64 | std::vector split(std::string); 65 | char32_t UU(const std::string&); 66 | void print_left_curly_bracket(Term::Window& scr, const size_t& x, const size_t& y1, const size_t& y2, 67 | const std::vector& display_vec); 68 | std::vector> pre_process(const Window& w, const Model& m, size_t& cursor_x, size_t& cursor_y); 69 | std::pair render(Window& scr, const Model& m, const size_t& cols); 70 | void replace_all(std::string& str, const std::string& from, const std::string& to); 71 | long long calc_cursor_move(const std::string& str, const size_t& cursor_col, const long long& shift_amount); 72 | std::string prompt_multiline(const std::string& prompt_string, std::vector& m_history, 73 | const std::optional>& is_complete = std::nullopt, 74 | const std::optional>& ctrl_c_callback = std::nullopt); 75 | } // namespace Term 76 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/terminal.cpp: -------------------------------------------------------------------------------- 1 | #include "terminal.hpp" 2 | 3 | #include "base.hpp" 4 | 5 | Term::Terminal::Terminal(const bool& _clear_screen, const bool& _disable_signal_keys, const bool& _hide_cursor) : 6 | clear_screen{_clear_screen}, disable_signal_keys{_disable_signal_keys}, hide_cursor{_hide_cursor} { 7 | store_and_restore(); 8 | setRawMode(); 9 | if (clear_screen) { 10 | //Fix consoles that ignore save_screen(). 11 | std::cout << screen_save() << clear_buffer() << style(Style::RESET) << cursor_move(1, 1); 12 | } 13 | if (hide_cursor) { 14 | std::cout << cursor_off(); 15 | } 16 | //Flush stdout. 17 | std::cout << std::flush; 18 | } 19 | 20 | Term::Terminal::~Terminal() { 21 | if (clear_screen) { 22 | //Fix consoles that ignore save_screen(). 23 | std::cout << clear_buffer() << style(Style::RESET) << cursor_move(1, 1) << screen_load(); 24 | } 25 | if (hide_cursor) { 26 | std::cout << cursor_on(); 27 | } 28 | //Flush the output stream. 29 | std::cout << std::flush; 30 | store_and_restore(); 31 | } 32 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/terminal.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "terminfo.hpp" 4 | 5 | namespace Term { 6 | 7 | /* Note: the code that uses Terminal must be inside try/catch block, otherwise 8 | * the destructors will not be called when an exception happens and the 9 | * terminal will not be left in a good state. Terminal uses exceptions when 10 | * something goes wrong. 11 | */ 12 | class Terminal { 13 | bool clear_screen{}; 14 | bool disable_signal_keys{true}; 15 | bool hide_cursor{}; 16 | Term::Terminfo m_terminfo; 17 | 18 | void store_and_restore(); 19 | void setRawMode(); 20 | 21 | public: 22 | explicit Terminal(const bool& _clear_screen = false, const bool& _disable_signal_keys = true, const bool& _hide_cursor = false); 23 | ~Terminal(); 24 | }; 25 | } // namespace Term 26 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/terminfo.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Term 4 | { 5 | 6 | class Terminfo 7 | { 8 | public: 9 | // indicates the color mode (basically the original color resolution) 10 | // also used to manually override the original color resolution 11 | enum class ColorMode 12 | { 13 | Unset, 14 | // no color was used 15 | NoColor, 16 | // a 3bit color was used 17 | Bit3, 18 | // a 4bit color was used 19 | Bit4, 20 | // a 8bit color was used 21 | Bit8, 22 | // a 24bit (RGB) color was used 23 | Bit24, 24 | }; 25 | Terminfo(); 26 | static ColorMode getColorMode() { return m_colorMode; } 27 | bool hasANSIEscapeCode(); 28 | 29 | private: 30 | void setANSIEscapeCode(); 31 | bool m_ANSIEscapeCode{true}; 32 | void setColorMode(); 33 | static ColorMode m_colorMode; 34 | }; 35 | 36 | } // namespace Term 37 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/tty.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Term { 4 | 5 | // Returns true if the standard input is attached to a terminal 6 | bool is_stdin_a_tty(); 7 | // Returns true if the standard output is attached to a terminal 8 | bool is_stdout_a_tty(); 9 | // Returns true if the standard error is attached to a terminal 10 | bool is_stderr_a_tty(); 11 | } // namespace Term 12 | -------------------------------------------------------------------------------- /main/interface/cpp-terminal/window.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "base.hpp" 4 | 5 | #include 6 | 7 | namespace Term 8 | { 9 | 10 | /* Represents a rectangular window, as a 2D array of characters and their 11 | * attributes. The render method can convert this internal representation to a 12 | * string that when printed will show the Window on the screen. 13 | * 14 | * Note: the characters are represented by char32_t, representing their UTF-32 15 | * code point. The natural way to represent a character in a terminal would be 16 | * a "unicode grapheme cluster", but due to a lack of a good library for C++ 17 | * that could handle those, we simply use a Unicode code point as a character. 18 | */ 19 | class Window 20 | { 21 | private: 22 | std::size_t w{0}; 23 | std::size_t h{0}; // width and height of the window 24 | std::size_t cursor_x{1}; 25 | std::size_t cursor_y{1}; // current cursor position 26 | std::vector chars; // the characters in row first order 27 | std::vector m_fg; 28 | std::vector m_bg; 29 | std::vector m_fg_reset; 30 | std::vector m_bg_reset; 31 | std::vector