161 lines
7.0 KiB
C++
161 lines
7.0 KiB
C++
// 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 <hex> | frame | text | resize R C | timers | flush | sources
|
|
#include "TuiChatScene.h"
|
|
#include "TuiComplete.h"
|
|
#include <cstdio>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <sstream>
|
|
using namespace detz::tui;
|
|
|
|
struct FakeStore {
|
|
std::map<std::string, std::vector<TuiChatMessage>> sessions;
|
|
std::vector<std::string> bots{"main", "alpha", "beta"};
|
|
};
|
|
|
|
class FakeAgent : public TuiChatAgent {
|
|
public:
|
|
FakeAgent(std::shared_ptr<FakeStore> 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<TuiChatMessage> messages() const override { return msgs_; }
|
|
std::string provider_pin() const override { return ""; }
|
|
std::string model_pin() const override { return ""; }
|
|
std::vector<std::string> list_bots() const override { return store_->bots; }
|
|
std::vector<TuiChatMessage> 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<void(const std::string&, const std::string&)>) override {}
|
|
void set_prompter(std::function<TuiValue(const std::string&, const std::string&, const TuiValue&)>) override {}
|
|
void set_stream_factory(std::function<std::shared_ptr<TuiStreamSink>(const std::string&)>) override {}
|
|
std::shared_ptr<TuiHistoryBackend> history_backend() override { return nullptr; }
|
|
int checkpointed{0};
|
|
private:
|
|
std::shared_ptr<FakeStore> store_;
|
|
std::string bot_;
|
|
std::vector<TuiChatMessage> msgs_;
|
|
};
|
|
|
|
class FakeHost : public TuiChatHost {
|
|
public:
|
|
explicit FakeHost(std::shared_ptr<FakeStore> store) : store_(std::move(store)) {}
|
|
std::shared_ptr<TuiChatAgent> spawn_agent(TuiChatAgent&, const std::string& name) override { return std::make_shared<FakeAgent>(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<void()> start_scheduler() override { return [] {}; }
|
|
private:
|
|
std::shared_ptr<FakeStore> 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<char>(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<std::time_t>(1741944413); }; // 2025-03-14 09:26:53 UTC
|
|
auto store = std::make_shared<FakeStore>();
|
|
{
|
|
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<FakeAgent>(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<std::string>{"model-a", "model-b"}; };
|
|
s.complete_files = [](const std::string&, const std::string&) { return std::vector<std::string>{"src/a.txt"}; };
|
|
s.dir_key = [] { return std::string("/tmp"); };
|
|
TuiComplete::reset_command_help();
|
|
}
|
|
}
|
|
return 0;
|
|
}
|