// tui_chat: scripted headless session over the local chat scene with a deterministic // fake agent, to diff against tai.py (tui_build_chat with the same fake). // Script lines (stdin): feed | frame | text | resize R C | timers | flush | sources #include "TuiChatScene.h" #include "TuiComplete.h" #include #include #include #include using namespace detz::tui; struct FakeStore { std::map> sessions; std::vector bots{"main", "alpha", "beta"}; }; class FakeAgent : public TuiChatAgent { public: FakeAgent(std::shared_ptr store, std::string bot) : store_(std::move(store)), bot_(std::move(bot)) { msgs_ = store_->sessions[bot_]; } std::string bot() const override { return bot_; } std::string profile() const override { return "default"; } std::vector messages() const override { return msgs_; } std::string provider_pin() const override { return ""; } std::string model_pin() const override { return ""; } std::vector list_bots() const override { return store_->bots; } std::vector load_session(const std::string& bot) override { return store_->sessions[bot]; } void save_session() override { store_->sessions[bot_] = msgs_; } void switch_bot(const std::string& name) override { bot_ = name; msgs_ = store_->sessions[name]; } std::string run_turn(const std::string& text) override { if (text == "boom") throw TuiChatBackendError("backend down"); if (text == "int") throw TuiChatInterrupted(); std::string reply = text.rfind("long", 0) == 0 ? "# Heading\n\nSome **bold** and *italic* with `code`.\n\n- first item\n- second item that is long enough to wrap around the bubble\n\n```\ncode block line\n```\n> quoted words here\n\ntail paragraph with many words to wrap across the available width of the transcript okay" : "echo " + text; TuiChatMessage u; u.role = "user"; u.content = text; TuiChatMessage a; a.role = "assistant"; a.content = reply; msgs_.push_back(u); msgs_.push_back(a); return reply; } void checkpoint() override { checkpointed++; } void set_quiet(bool) override {} bool handle_command(const std::string& text, std::string& output) override { if (text == "/help") { output = "\x1b[1mhelp line 1\x1b[0m\nhelp line 2\n"; return true; } if (text == "/quit") return false; if (text == "/bot alpha") { switch_bot("alpha"); output = "switched\n"; return true; } if (text == "/fail") throw std::runtime_error("boom"); output = "unknown command\n"; return true; } void set_on_phase(std::function) override {} void set_prompter(std::function) override {} void set_stream_factory(std::function(const std::string&)>) override {} std::shared_ptr history_backend() override { return nullptr; } int checkpointed{0}; private: std::shared_ptr store_; std::string bot_; std::vector msgs_; }; class FakeHost : public TuiChatHost { public: explicit FakeHost(std::shared_ptr store) : store_(std::move(store)) {} std::shared_ptr spawn_agent(TuiChatAgent&, const std::string& name) override { return std::make_shared(store_, name); } int running_agents() override { return 0; } std::string version() override { return "9.9.9"; } std::string short_error(const std::string& message) override { return message.substr(0, 300); } std::function start_scheduler() override { return [] {}; } private: std::shared_ptr store_; }; static std::string unhex(const std::string& h) { std::string out; for (size_t i = 0; i + 1 < h.size(); i += 2) out.push_back(static_cast(std::stoi(h.substr(i, 2), nullptr, 16))); return out; } static std::string hex(const std::string& s) { static const char* d = "0123456789abcdef"; std::string out; for (unsigned char c : s) { out.push_back(d[c >> 4]); out.push_back(d[c & 15]); } return out; } int main(int argc, char** argv) { int rows = argc > 1 ? std::atoi(argv[1]) : 30; int cols = argc > 2 ? std::atoi(argv[2]) : 100; TuiChatScene::clock = [] { return static_cast(1741944413); }; // 2025-03-14 09:26:53 UTC auto store = std::make_shared(); { TuiChatMessage u; u.role = "user"; u.content = "hi there"; TuiChatMessage a; a.role = "assistant"; a.content = "Hello! **How** can I help?"; a.tool_calls.push_back({"shell_run", "{\"cmd\": \"ls\"}"}); store->sessions["main"] = {u, a}; TuiChatMessage b; b.role = "assistant"; b.content = "alpha says `code`"; store->sessions["alpha"] = {b}; } FakeHost host(store); auto agent = std::make_shared(store, "main"); TuiApp app; app.rows = rows; app.cols = cols; TuiChatScene scene(app, agent, host); PanelP root = scene.build(); app.mount(root); scene.replay(); scene.box->focus(); std::string line; while (std::getline(std::cin, line)) { std::istringstream in(line); std::string cmd; in >> cmd; if (cmd == "feed") { std::string h; in >> h; for (auto& ev : app.input.feed(unhex(h))) app.dispatch(*ev); } else if (cmd == "flush") { for (auto& ev : app.input.flush()) app.dispatch(*ev); } else if (cmd == "frame") { std::printf("%s\n", hex(app.frame_bytes()).c_str()); } else if (cmd == "text") { std::printf("%s\n--\n", app.render_text().c_str()); } else if (cmd == "resize") { int r, c; in >> r >> c; app.rows = r; app.cols = c; app.compositor.invalidate(); app.dirty_layout(); TuiResize ev; ev.panel = app.root.get(); ev.rows = r; ev.cols = c; app.root->emit(ev); } else if (cmd == "timers") { app.fire_timers(TuiApp::now() + 100.0); } else if (cmd == "sources") { auto& s = TuiComplete::sources(); s.commands = {"help", "profile", "bots", "quit"}; s.help_text = " /help show help Show this help\n /profile NAME switch Switch profile\n /bots list bots\n"; s.provider_labels = {"Muse", "Grok"}; s.default_models = [] { return std::vector{"model-a", "model-b"}; }; s.complete_files = [](const std::string&, const std::string&) { return std::vector{"src/a.txt"}; }; s.dir_key = [] { return std::string("/tmp"); }; TuiComplete::reset_command_help(); } } return 0; }