diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 534427b..5bf8dc3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,7 +35,9 @@ add_library(solanaceae_rpbot ./solanaceae/rpbot/message_prompt_builder.cpp ./solanaceae/rpbot/rpbot.hpp + ./solanaceae/rpbot/rpbot_states.hpp ./solanaceae/rpbot/rpbot.cpp + ./solanaceae/rpbot/rpbot_commands.cpp ) target_include_directories(solanaceae_rpbot PUBLIC .) diff --git a/src/solanaceae/rpbot/rpbot.cpp b/src/solanaceae/rpbot/rpbot.cpp index ed9e47d..c71d1c3 100644 --- a/src/solanaceae/rpbot/rpbot.cpp +++ b/src/solanaceae/rpbot/rpbot.cpp @@ -1,7 +1,8 @@ #include "./rpbot.hpp" #include "./message_prompt_builder.hpp" -#include "solanaceae/contact/contact_model3.hpp" + +#include "./rpbot_states.hpp" #include #include @@ -10,46 +11,10 @@ #include #include #include -#include #include #include #include -// sleeps until onMsg or onTimer -struct StateIdle { - static constexpr const char* name {"StateIdle"}; - float timeout {0.f}; -}; - -// determines if self should generate a message -struct StateNext { - static constexpr const char* name {"StateNext"}; - - std::string prompt; - std::vector possible_names; - std::vector possible_contacts; - - std::future future; -}; - -// generate message -struct StateGenerateMsg { - static constexpr const char* name {"StateGenerateMsg"}; - - std::string prompt; - - // returns new line (single message) - std::future future; -}; - -// look if it took too long/too many new messages came in -// while also optionally sleeping to make message appear not too fast -// HACK: skip, just send for now -struct StateTimingCheck { - static constexpr const char* name {"StateTimingCheck"}; - int tmp; -}; - template<> void RPBot::stateTransition(const Contact3 c, const StateIdle& from, StateNext& to) { // collect promp @@ -71,23 +36,25 @@ void RPBot::stateTransition(const Contact3 c, const StateIdle& from, StateNext& std::cout << "prompt for next: '" << to.prompt << "'\n"; - { // get set of possible usernames + int64_t self {-1}; + { // get set of possible usernames (even if forced, just to make sure) // copy mpb.names (contains string views, needs copies) for (const auto& [name_c, name] : mpb.names) { - if (_cr.all_of(name_c)) { + if (_cr.all_of(name_c)) { + self = to.possible_contacts.size(); + to.possible_names.push_back(std::string{name}); + to.possible_contacts.push_back(name_c); + } else if (_cr.all_of(name_c)) { if (_cr.get(name_c).state != Contact::Components::ConnectionState::disconnected) { // online to.possible_names.push_back(std::string{name}); to.possible_contacts.push_back(name_c); } - } else if (_cr.all_of(name_c)) { - to.possible_names.push_back(std::string{name}); - to.possible_contacts.push_back(name_c); } } } - { // launch async + if (!from.force) { // launch async // copy names for string view param (lol) std::vector pnames; for (const auto& n : to.possible_names) { @@ -97,6 +64,11 @@ void RPBot::stateTransition(const Contact3 c, const StateIdle& from, StateNext& to.future = std::async(std::launch::async, [pnames, &to, this]() -> int64_t { return _completion.completeSelect(to.prompt, pnames); }); + } else { + std::cout << "next forced with self " << self << "\n"; + // forced, we predict ourselfs + // TODO: set without future? + to.future = std::async(std::launch::async, [self]() -> int64_t { return self; }); } } @@ -135,13 +107,25 @@ RPBot::RPBot( ) : _completion(completion), _conf(conf), _cr(cr), _rmm(rmm), _mcd(mcd) { //system_prompt = R"sys(Transcript of a group chat, where Bob talks to online strangers. //)sys"; - system_prompt = "Transcript of a group chat, where "; + // TODO: name is chat specific, leave system prompt a template + std::string self_name {"Bob"}; if (_conf.has_string("tox", "name")) { - system_prompt += _conf.get_string("tox", "name").value(); - } else { - system_prompt += std::string{"Bob"}; + self_name = _conf.get_string("tox", "name").value(); } - system_prompt += std::string{" talks to online strangers.\n"}; + +#if 1 + system_prompt = "Transcript of a group chat, where "; + system_prompt += self_name; + system_prompt += " talks to online strangers. "; + system_prompt += self_name; + system_prompt += " is creative and curious. "; + system_prompt += self_name; + system_prompt += " is precise in its writing, but with occasional typos."; +#else +#include "./test_system_prompt1.inl" +#endif + + system_prompt += "\n"; // last entry registerCommands(); } @@ -157,96 +141,19 @@ float RPBot::tick(float time_delta) { return min_tick_interval; } -void RPBot::registerCommands(void) { - if (_mcd == nullptr) { - return; - } - - _mcd->registerCommand( - "RPBot", "rpbot", - "start", - [this](std::string_view params, Message3Handle m) -> bool { - const auto contact_from = m.get().c; - const auto contact_to = m.get().c; - - if (params.empty()) { - // contact_to should be the contact this is for - if (_cr.any_of(contact_to)) { - _rmm.sendText( - contact_from, - "error: already running" - ); - return true; - } - if (_cr.any_of(contact_from)) { - _rmm.sendText( - contact_from, - "error: already running" - ); - return true; - } - - if (_cr.all_of(contact_to)) { - // group - auto& new_state = _cr.emplace(contact_to); - new_state.timeout = 10.f; - } else { - // pm - auto& new_state = _cr.emplace(contact_from); - new_state.timeout = 10.f; - } - - _rmm.sendText( - contact_from, - "RPBot started" - ); - return true; - } else { - // id in params - if (params.size() % 2 != 0) { - _rmm.sendText( - contact_from, - "malformed hex id" - ); - return true; - } - - auto id_bin = hex2bin(params); - - auto view = _cr.view(); - for (auto it = view.begin(), it_end = view.end(); it != it_end; it++) { - if (view.get(*it).data == id_bin) { - auto& new_state = _cr.emplace(*it); - new_state.timeout = 10.f; - - _rmm.sendText( - contact_from, - "RPBot started" - ); - return true; - } - } - - _rmm.sendText( - contact_from, - "no contact found for id" - ); - return true; - } - }, - "Start RPBot in current contact.", - MessageCommandDispatcher::Perms::ADMIN // TODO: should proably be MODERATOR - ); - - std::cout << "RPBot: registered commands\n"; -} - float RPBot::doAllIdle(float time_delta) { float min_tick_interval = std::numeric_limits::max(); std::vector to_remove_stateidle; auto view = _cr.view(); view.each([this, time_delta, &to_remove_stateidle, &min_tick_interval](const Contact3 c, StateIdle& state) { + if (_cr.all_of(c)) { + // marked for deletion, in idle (here) we remove them without adding next state + to_remove_stateidle.push_back(c); + _cr.remove(c); + return; + } + state.timeout -= time_delta; if (state.timeout <= 0.f) { std::cout << "RPBot: idle timed out\n"; diff --git a/src/solanaceae/rpbot/rpbot_commands.cpp b/src/solanaceae/rpbot/rpbot_commands.cpp new file mode 100644 index 0000000..4ca76b2 --- /dev/null +++ b/src/solanaceae/rpbot/rpbot_commands.cpp @@ -0,0 +1,249 @@ +#include "./rpbot.hpp" + +#include "./rpbot_states.hpp" + +#include +#include +#include + +void RPBot::registerCommands(void) { + if (_mcd == nullptr) { + return; + } + + _mcd->registerCommand( + "RPBot", "rpbot", + "start", + [this](std::string_view params, Message3Handle m) -> bool { + const auto contact_from = m.get().c; + const auto contact_to = m.get().c; + + if (params.empty()) { + // contact_to should be the contact this is for + if (_cr.any_of(contact_to)) { + _rmm.sendText( + contact_from, + "error: already running" + ); + return true; + } + if (_cr.any_of(contact_from)) { + _rmm.sendText( + contact_from, + "error: already running" + ); + return true; + } + + if (_cr.all_of(contact_to)) { + // group + auto& new_state = _cr.emplace(contact_to); + new_state.timeout = 10.f; + } else { + // pm + auto& new_state = _cr.emplace(contact_from); + new_state.timeout = 10.f; + } + + _rmm.sendText( + contact_from, + "RPBot started" + ); + return true; + } else { + // id in params + if (params.size() % 2 != 0) { + _rmm.sendText( + contact_from, + "malformed hex id" + ); + return true; + } + + auto id_bin = hex2bin(params); + + auto view = _cr.view(); + for (auto it = view.begin(), it_end = view.end(); it != it_end; it++) { + if (view.get(*it).data == id_bin) { + auto& new_state = _cr.emplace(*it); + new_state.timeout = 10.f; + + _rmm.sendText( + contact_from, + "RPBot started" + ); + return true; + } + } + + _rmm.sendText( + contact_from, + "no contact found for id" + ); + return true; + } + }, + "Start RPBot in current contact.", + MessageCommandDispatcher::Perms::ADMIN // TODO: should proably be MODERATOR + ); + + _mcd->registerCommand( + "RPBot", "rpbot", + "stop", + [this](std::string_view params, Message3Handle m) -> bool { + const auto contact_from = m.get().c; + const auto contact_to = m.get().c; + + if (params.empty()) { + // contact_to should be the contact this is for + if (_cr.any_of(contact_to)) { + _cr.emplace_or_replace(contact_to); + _rmm.sendText( + contact_from, + "stopped" + ); + return true; + } + if (_cr.any_of(contact_from)) { + _cr.emplace_or_replace(contact_from); + _rmm.sendText( + contact_from, + "stopped" + ); + return true; + } + + _rmm.sendText( + contact_from, + "error: not running" + ); + return true; + } else { + // id in params + if (params.size() % 2 != 0) { + _rmm.sendText( + contact_from, + "malformed hex id" + ); + return true; + } + + auto id_bin = hex2bin(params); + + auto view = _cr.view(); + for (auto it = view.begin(), it_end = view.end(); it != it_end; it++) { + if (view.get(*it).data == id_bin) { + if (_cr.any_of(*it)) { + _cr.emplace_or_replace(*it); + _rmm.sendText( + contact_from, + "stopped" + ); + return true; + } else { + _rmm.sendText( + contact_from, + "error: not running" + ); + return true; + } + } + } + + _rmm.sendText( + contact_from, + "no contact found for id" + ); + return true; + } + }, + "Stop RPBot in current or id contact.", + MessageCommandDispatcher::Perms::ADMIN // TODO: should proably be MODERATOR + ); + + _mcd->registerCommand( + "RPBot", "rpbot", + "force", + [this](std::string_view params, Message3Handle m) -> bool { + const auto contact_from = m.get().c; + const auto contact_to = m.get().c; + + if (params.empty()) { + // contact_to should be the contact this is for + if (_cr.any_of(contact_to)) { + if (_cr.all_of(contact_to)) { + _cr.get(contact_to).force = true; + _cr.get(contact_to).timeout = 2.f; + _rmm.sendText( + contact_from, + "forced its hand" + ); + } + return true; + } + if (_cr.any_of(contact_from)) { + if (_cr.all_of(contact_from)) { + _cr.get(contact_from).force = true; + _cr.get(contact_from).timeout = 2.f; + _rmm.sendText( + contact_from, + "forced its hand" + ); + } + return true; + } + + _rmm.sendText( + contact_from, + "error: not running" + ); + return true; + } else { + // id in params + if (params.size() % 2 != 0) { + _rmm.sendText( + contact_from, + "malformed hex id" + ); + return true; + } + + auto id_bin = hex2bin(params); + + auto view = _cr.view(); + for (auto it = view.begin(), it_end = view.end(); it != it_end; it++) { + if (view.get(*it).data == id_bin) { + if (_cr.any_of(*it)) { + if (_cr.all_of(*it)) { + _cr.get(*it).force = true; + _cr.get(*it).timeout = 2.f; + _rmm.sendText( + contact_from, + "forced its hand" + ); + } + return true; + } else { + _rmm.sendText( + contact_from, + "error: not running" + ); + return true; + } + } + } + + _rmm.sendText( + contact_from, + "no contact found for id" + ); + return true; + } + }, + "force it to generate a message", + MessageCommandDispatcher::Perms::ADMIN // TODO: should proably be MODERATOR + ); + + std::cout << "RPBot: registered commands\n"; +} + diff --git a/src/solanaceae/rpbot/rpbot_states.hpp b/src/solanaceae/rpbot/rpbot_states.hpp new file mode 100644 index 0000000..64dabe6 --- /dev/null +++ b/src/solanaceae/rpbot/rpbot_states.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "./rpbot.hpp" + +#include +#include +#include +#include + +struct TagStopRPBot {}; + +// sleeps until onMsg or onTimer +struct StateIdle { + static constexpr const char* name {"StateIdle"}; + float timeout {0.f}; + bool force {false}; +}; + +// determines if self should generate a message +struct StateNext { + static constexpr const char* name {"StateNext"}; + + std::string prompt; + std::vector possible_names; + std::vector possible_contacts; + + std::future future; +}; + +// generate message +struct StateGenerateMsg { + static constexpr const char* name {"StateGenerateMsg"}; + + std::string prompt; + + // returns new line (single message) + std::future future; +}; + +// look if it took too long/too many new messages came in +// while also optionally sleeping to make message appear not too fast +// HACK: skip, just send for now +struct StateTimingCheck { + static constexpr const char* name {"StateTimingCheck"}; + int tmp; +}; +