114 auto input_message = std::make_shared<std::string>();
119 auto todo_popup_toggle = [
this] {
122 auto shortcut_palette_toggle = [
this] {
127 input_component = CatchEvent(input_component, [
this, input_message, todo_popup_toggle, shortcut_palette_toggle](
const Event& event) {
128 if (event == Event::Return) {
129 if (input_message->empty())
return true;
131 input_message->clear();
134 if (event == Event::Special({20})) {
138 if (event == Event::Special({11})) {
139 shortcut_palette_toggle();
145 auto send_button = Button(
"Send", [
this, input_message] {
146 if (input_message->empty())
return;
148 input_message->clear();
151 auto quick_pick_index = std::make_shared<int>(0);
152 auto quick_pick_menu = Menu(&
quick_actions_, quick_pick_index.get());
158 auto input_container = Container::Horizontal({
163 input_component->TakeFocus();
165 auto main_renderer = Renderer(input_container, [
this, input_component, send_button, quick_pick_menu, quick_pick_index] {
169 std::vector<Element> history_elements;
171 for (
const auto& msg : history) {
177 if (msg.table_data.has_value()) {
178 std::vector<std::vector<std::string>> table_rows;
179 if (!msg.table_data->headers.empty()) {
180 table_rows.push_back(msg.table_data->headers);
182 for (
const auto& row : msg.table_data->rows) {
183 table_rows.push_back(row);
185 Table table(table_rows);
186 table.SelectAll().Border(LIGHT);
187 if (!msg.table_data->headers.empty()) {
188 table.SelectRow(0).Decorate(bold);
190 body = table.Render();
191 }
else if (msg.json_pretty.has_value()) {
193 body = paragraphAlignLeft(msg.json_pretty.value());
196 body = paragraphAlignLeft(msg.message);
199 auto message_block = vbox({
205 if (msg.metrics.has_value()) {
206 const auto& metrics = msg.metrics.value();
207 message_block = vbox({
210 text(absl::StrFormat(
211 "📊 Turn %d | Elapsed: %.2fs",
212 metrics.turn_index, metrics.total_elapsed_seconds)) | color(Color::Cyan) | dim
216 history_elements.push_back(message_block | border);
219 Element history_view;
220 if (history.empty()) {
221 history_view = vbox({
222 text(
"yaze TUI") | bold | center,
223 text(
"A conversational agent for ROM hacking") | center,
225 text(
"No messages yet. Start chatting!") | dim
228 history_view = vbox(history_elements) | vscroll_indicator | frame | flex;
234 Element header_line = hbox({
238 : text(
"✓") | color(Color::GreenLight)
241 std::vector<Element> info_cards;
242 info_cards.push_back(RenderPanelCard(
245 RenderMetricLabel(
"🕒",
"Turns", absl::StrFormat(
"%d", metrics.turn_index), Color::Cyan),
246 RenderMetricLabel(
"🙋",
"User", absl::StrFormat(
"%d", metrics.total_user_messages), Color::White),
247 RenderMetricLabel(
"🤖",
"Agent", absl::StrFormat(
"%d", metrics.total_agent_messages), Color::GreenLight),
248 RenderMetricLabel(
"🔧",
"Tools", absl::StrFormat(
"%d", metrics.total_tool_calls), Color::YellowLight),
249 }, Color::GrayLight));
251 info_cards.push_back(RenderPanelCard(
255 RenderMetricLabel(
"📈",
"Average", absl::StrFormat(
"%.2fs", metrics.average_latency_seconds), Color::MagentaLight),
259 info_cards.push_back(RenderPanelCard(
262 text(
"⌨ Enter ↵ Send") | dim,
263 text(
"⌨ Shift+Enter ↩ Multiline") | dim,
264 text(
"⌨ /help, /rom_info, /status") | dim,
265 text(
"⌨ Ctrl+T TODO overlay · Ctrl+K shortcuts · f fullscreen") | dim,
266 }, Color::BlueLight));
268 Elements layout_elements;
269 layout_elements.push_back(header_line);
270 layout_elements.push_back(separatorLight());
271 layout_elements.push_back(
274 info_cards[0] | flex,
276 info_cards[1] | flex,
278 info_cards[2] | flex,
282 bgcolor(Color::Black) |
287 layout_elements.push_back(separatorLight());
288 layout_elements.push_back(
290 text(
"Turns: ") | bold,
291 text(absl::StrFormat(
"%d", metrics.turn_index)),
293 text(
"User: ") | bold,
294 text(absl::StrFormat(
"%d", metrics.total_user_messages)),
296 text(
"Agent: ") | bold,
297 text(absl::StrFormat(
"%d", metrics.total_agent_messages)),
299 text(
"Tools: ") | bold,
300 text(absl::StrFormat(
"%d", metrics.total_tool_calls)),
303 }) | color(Color::GrayLight)
308 layout_elements.push_back(separator());
309 layout_elements.push_back(
310 text(absl::StrCat(
"⚠ ERROR: ", *
last_error_)) | color(Color::Red)
315 layout_elements.push_back(separator());
316 layout_elements.push_back(
318 text(
"Quick Actions") | bold,
319 quick_pick_menu->Render() | frame | size(HEIGHT, EQUAL, 5) |
322 input_component->Render() | flex
325 layout_elements.push_back(
327 text(
"Press Enter to send | ") | dim,
328 send_button->Render(),
329 text(
" | Tab quick actions · Ctrl+T TODO overlay · Ctrl+K shortcuts") | dim,
333 Element base = vbox(layout_elements) | borderRounded | bgcolor(Color::Black);
337 std::vector<Element> overlays;
338 overlays.push_back(base);
345 base = dbox(overlays);
511 auto refresh_button = Button(
"Refresh", [
this] {
512 screen_.PostEvent(Event::Custom);
514 auto close_button = Button(
"Close", [
this] {
516 screen_.PostEvent(Event::Custom);
519 auto renderer = Renderer([
this, refresh_button, close_button] {
522 rows.push_back(text(
"TODO manager unavailable") | color(Color::Red) | center);
526 rows.push_back(text(
"No TODOs tracked") | dim | center);
528 for (const auto& item : todos) {
529 rows.push_back(hbox({
530 text(absl::StrFormat(
"[%s]", item.StatusToString())) | color(Color::Yellow),
531 text(
" " + item.description) | flex,
532 text(item.category.empty() ?
"" : absl::StrCat(
" (", item.category,
")")) | dim
539 window(text(
"📝 TODO Overlay") | bold,
542 vbox(rows) | frame | size(HEIGHT, LESS_THAN, 12) | size(WIDTH, LESS_THAN, 70),
544 hbox({refresh_button->Render(), text(
" "), close_button->Render()}) | center
546 | size(WIDTH, LESS_THAN, 72) | size(HEIGHT, LESS_THAN, 18) | center
553ftxui::Component ChatTUI::BuildShortcutPalette() {
554 std::vector<std::pair<std::string, std::string>> shortcuts = {
555 {
"Ctrl+T",
"Toggle TODO overlay"},
556 {
"Ctrl+K",
"Shortcut palette"},
557 {
"Ctrl+L",
"Clear chat history"},
558 {
"Ctrl+Shift+S",
"Save transcript"},
559 {
"Ctrl+G",
"Focus quick actions"},
560 {
"Ctrl+P",
"Command palette"},
561 {
"Ctrl+F",
"Fullscreen chat"},
562 {
"Esc",
"Back to unified layout"},
565 auto close_button = Button(
"Close", [
this] {
566 shortcut_palette_visible_ =
false;
567 screen_.PostEvent(Event::Custom);
570 auto renderer = Renderer([shortcuts, close_button] {
572 for (
const auto& [combo, desc] : shortcuts) {
573 rows.push_back(hbox({text(combo) | bold | color(Color::Cyan), text(
" " + desc)}));
577 window(text(
"⌨ Shortcuts") | bold,
580 vbox(rows) | frame | size(HEIGHT, LESS_THAN, 12) | size(WIDTH, LESS_THAN, 60),
582 close_button->Render() | center
584 | size(WIDTH, LESS_THAN, 64) | size(HEIGHT, LESS_THAN, 16) | center