yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
agent_editor_dashboard.cc
Go to the documentation of this file.
1#include <algorithm>
2#include <cstring>
3#include <filesystem>
4#include <optional>
5#include <string>
6#include <vector>
7
8#include "absl/strings/match.h"
9#include "absl/strings/str_format.h"
10#include "absl/time/clock.h"
11#include "absl/time/time.h"
25#include "app/gui/core/icons.h"
32#include "imgui/misc/cpp/imgui_stdlib.h"
33#include "implot.h"
34#include "rom/rom.h"
35
36namespace yaze {
37namespace editor {
38
39namespace {} // namespace
40
42 if (!active_) {
43 return;
44 }
45
46 // Animate retro effects
47 ImGuiIO& imgui_io = ImGui::GetIO();
48 pulse_animation_ += imgui_io.DeltaTime * 2.0f;
49 scanline_offset_ += imgui_io.DeltaTime * 0.4f;
50 if (scanline_offset_ > 1.0f) {
51 scanline_offset_ -= 1.0f;
52 }
53 glitch_timer_ += imgui_io.DeltaTime * 5.0f;
54 blink_counter_ = static_cast<int>(pulse_animation_ * 2.0f) % 2;
55}
56
58 if (!context_) {
59 ImGui::TextDisabled("Agent configuration unavailable.");
60 ImGui::TextWrapped("Initialize the Agent UI to edit provider settings.");
61 return;
62 }
63
64 const auto& theme = AgentUI::GetTheme();
65
67 const auto& prefs = dependencies_.user_settings->prefs();
68 if (!prefs.ai_hosts.empty()) {
70 theme.accent_color);
71 const auto& hosts = prefs.ai_hosts;
72 std::string active_id = current_profile_.host_id.empty()
73 ? prefs.active_ai_host_id
75 int active_index = -1;
76 for (size_t i = 0; i < hosts.size(); ++i) {
77 if (!active_id.empty() && hosts[i].id == active_id) {
78 active_index = static_cast<int>(i);
79 break;
80 }
81 }
82 const char* preview = (active_index >= 0)
83 ? hosts[active_index].label.c_str()
84 : "Select host";
85 if (ImGui::BeginCombo("##ai_host_preset", preview)) {
86 for (size_t i = 0; i < hosts.size(); ++i) {
87 const bool selected = (static_cast<int>(i) == active_index);
88 if (ImGui::Selectable(hosts[i].label.c_str(), selected)) {
90 &prefs);
93 }
94 if (selected) {
95 ImGui::SetItemDefaultFocus();
96 }
97 }
98 ImGui::EndCombo();
99 }
100 if (active_index >= 0) {
101 const auto& host = hosts[active_index];
102 ImGui::TextDisabled("Active host: %s", host.label.c_str());
103 ImGui::TextDisabled("Endpoint: %s", host.base_url.c_str());
104 ImGui::TextDisabled("API type: %s", host.api_type.empty()
106 : host.api_type.c_str());
107 } else {
108 ImGui::TextDisabled(
109 "Host presets come from settings.json (Documents/Yaze).");
110 }
111 ImGui::Spacing();
112 ImGui::Separator();
113 ImGui::Spacing();
114 }
115 }
116
118 callbacks.update_config = [this](const AgentConfigState& config) {
120 };
121 callbacks.refresh_models = [this](bool force) {
122 RefreshModelCache(force);
123 };
124 callbacks.apply_preset = [this](const ModelPreset& preset) {
125 ApplyModelPreset(preset);
126 };
127 callbacks.apply_tool_preferences = [this]() {
129 };
130
131 if (config_panel_) {
132 config_panel_->Draw(context_, callbacks, toast_manager_);
133 }
134}
135
137 const auto& theme = AgentUI::GetTheme();
138
140 if (ImGui::BeginChild("AgentStatusCard", ImVec2(0, 150), true)) {
142 theme.accent_color);
143
144 bool chat_active = agent_chat_ && *agent_chat_->active();
145 if (ImGui::BeginTable("AgentStatusTable", 2,
146 ImGuiTableFlags_SizingStretchProp)) {
147 ImGui::TableNextRow();
148 ImGui::TableSetColumnIndex(0);
149 ImGui::TextDisabled("Chat");
150 ImGui::TableSetColumnIndex(1);
151 AgentUI::RenderStatusIndicator(chat_active ? "Active" : "Inactive",
152 chat_active);
153 if (!chat_active) {
154 ImGui::SameLine();
155 if (ImGui::SmallButton("Open")) {
157 }
158 }
159
160 ImGui::TableNextRow();
161 ImGui::TableSetColumnIndex(0);
162 ImGui::TextDisabled("Provider");
163 ImGui::TableSetColumnIndex(1);
165 if (!current_profile_.model.empty()) {
166 ImGui::SameLine();
167 ImGui::TextDisabled("%s", current_profile_.model.c_str());
168 }
169
170 ImGui::TableNextRow();
171 ImGui::TableSetColumnIndex(0);
172 ImGui::TextDisabled("ROM");
173 ImGui::TableSetColumnIndex(1);
174 if (rom_ && rom_->is_loaded()) {
175 ImGui::TextColored(theme.status_success,
176 ICON_MD_CHECK_CIRCLE " Loaded");
177 ImGui::SameLine();
178 ImGui::TextDisabled("Tools ready");
179 } else {
180 ImGui::TextColored(theme.status_warning, ICON_MD_WARNING " Not Loaded");
181 }
182 ImGui::EndTable();
183 }
184 }
185 ImGui::EndChild();
186
187 ImGui::Spacing();
188
189 if (ImGui::BeginChild("AgentMetricsCard", ImVec2(0, 170), true)) {
191 theme.accent_color);
192 if (agent_chat_) {
193 auto metrics = agent_chat_->GetAgentService()->GetMetrics();
194 ImGui::TextDisabled("Messages: %d user / %d agent",
195 metrics.total_user_messages,
196 metrics.total_agent_messages);
197 ImGui::TextDisabled("Tool calls: %d Proposals: %d Commands: %d",
198 metrics.total_tool_calls, metrics.total_proposals,
199 metrics.total_commands);
200 ImGui::TextDisabled("Avg latency: %.2fs Elapsed: %.2fs",
201 metrics.average_latency_seconds,
202 metrics.total_elapsed_seconds);
203
204 std::vector<double> latencies;
205 for (const auto& msg : agent_chat_->GetAgentService()->GetHistory()) {
206 if (msg.sender == cli::agent::ChatMessage::Sender::kAgent &&
207 msg.model_metadata.has_value() &&
208 msg.model_metadata->latency_seconds > 0.0) {
209 latencies.push_back(msg.model_metadata->latency_seconds);
210 }
211 }
212 if (latencies.size() > 30) {
213 latencies.erase(latencies.begin(),
214 latencies.end() - static_cast<long>(30));
215 }
216 if (!latencies.empty()) {
217 std::vector<double> xs(latencies.size());
218 for (size_t i = 0; i < xs.size(); ++i) {
219 xs[i] = static_cast<double>(i);
220 }
221 ImPlotFlags plot_flags = ImPlotFlags_NoLegend | ImPlotFlags_NoMenus |
222 ImPlotFlags_NoBoxSelect;
223 if (ImPlot::BeginPlot("##LatencyPlot", ImVec2(-1, 90), plot_flags)) {
224 ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoDecorations,
225 ImPlotAxisFlags_NoDecorations);
226 ImPlot::SetupAxisLimits(ImAxis_X1, 0, xs.back(), ImGuiCond_Always);
227 double max_latency =
228 *std::max_element(latencies.begin(), latencies.end());
229 ImPlot::SetupAxisLimits(ImAxis_Y1, 0, max_latency * 1.2,
230 ImGuiCond_Always);
231 ImPlot::PlotLine("Latency", xs.data(), latencies.data(),
232 static_cast<int>(latencies.size()));
233 ImPlot::EndPlot();
234 }
235 }
236 } else {
237 ImGui::TextDisabled("Initialize the chat system to see metrics.");
238 }
239 }
240 ImGui::EndChild();
241
243}
244
246 if (ImGui::CollapsingHeader(ICON_MD_ANALYTICS " Quick Metrics")) {
247 ImGui::TextDisabled("View detailed metrics in the Metrics tab");
248 }
249}
250
252 const auto& theme = AgentUI::GetTheme();
254 theme.accent_color);
255
256 ImGui::Text("File:");
257 ImGui::SetNextItemWidth(-45);
258 if (ImGui::BeginCombo("##prompt_file", active_prompt_file_.c_str())) {
259 const char* options[] = {"system_prompt.txt", "system_prompt_v2.txt",
260 "system_prompt_v3.txt"};
261 for (const char* option : options) {
262 bool selected = active_prompt_file_ == option;
263 if (ImGui::Selectable(option, selected)) {
264 active_prompt_file_ = option;
266 }
267 }
268 ImGui::EndCombo();
269 }
270
271 ImGui::SameLine();
272 if (ImGui::SmallButton(ICON_MD_REFRESH)) {
274 }
275 if (ImGui::IsItemHovered()) {
276 ImGui::SetTooltip("Reload from disk");
277 }
278
280 std::string asset_path = "agent/" + active_prompt_file_;
281 auto content_result = AssetLoader::LoadTextFile(asset_path);
282 if (content_result.ok()) {
283 prompt_editor_->SetText(*content_result);
284 current_profile_.system_prompt = *content_result;
286 if (toast_manager_) {
287 toast_manager_->Show(absl::StrFormat(ICON_MD_CHECK_CIRCLE " Loaded %s",
289 ToastType::kSuccess, 2.0f);
290 }
291 } else {
292 std::string placeholder = absl::StrFormat(
293 "# System prompt file not found: %s\n"
294 "# Error: %s\n\n"
295 "# Ensure the file exists in assets/agent/%s\n",
296 active_prompt_file_, content_result.status().message(),
298 prompt_editor_->SetText(placeholder);
300 }
301 }
302
303 ImGui::Spacing();
304 if (prompt_editor_) {
305 ImVec2 editor_size(ImGui::GetContentRegionAvail().x,
306 ImGui::GetContentRegionAvail().y - 60);
307 prompt_editor_->Render("##prompt_editor", editor_size, true);
308
309 ImGui::Spacing();
310 if (ImGui::Button(ICON_MD_SAVE " Save Prompt to Profile", ImVec2(-1, 0))) {
312 if (toast_manager_) {
313 toast_manager_->Show("System prompt saved to profile",
315 }
316 }
317 }
318
319 ImGui::Spacing();
320 ImGui::TextWrapped(
321 "Edit the system prompt that guides the agent's behavior. Changes are "
322 "stored on the active bot profile.");
323}
324
326 const auto& theme = AgentUI::GetTheme();
327 AgentUI::RenderSectionHeader(ICON_MD_FOLDER, "Bot Profile Manager",
328 theme.accent_color);
329 ImGui::Spacing();
330
331 ImGui::BeginChild("CurrentProfile", ImVec2(0, 150), true);
333 theme.accent_color);
334 ImGui::Text("Name: %s", current_profile_.name.c_str());
335 ImGui::Text("Provider: %s", current_profile_.provider.c_str());
336 if (!current_profile_.model.empty()) {
337 ImGui::Text("Model: %s", current_profile_.model.c_str());
338 }
339 ImGui::TextWrapped("Description: %s",
341 ? "No description"
342 : current_profile_.description.c_str());
343 ImGui::EndChild();
344
345 ImGui::Spacing();
346
347 if (ImGui::Button(ICON_MD_ADD " Create New Profile", ImVec2(-1, 0))) {
348 BotProfile new_profile = current_profile_;
349 new_profile.name = "New Profile";
350 new_profile.created_at = absl::Now();
351 new_profile.modified_at = absl::Now();
352 current_profile_ = new_profile;
354 if (toast_manager_) {
355 toast_manager_->Show("New profile created. Configure and save it.",
357 }
358 }
359
360 ImGui::Spacing();
362 theme.accent_color);
363
364 ImGui::BeginChild("ProfilesList", ImVec2(0, 0), true);
365 if (loaded_profiles_.empty()) {
366 ImGui::TextDisabled(
367 "No saved profiles. Create and save a profile to see it here.");
368 } else {
369 for (size_t i = 0; i < loaded_profiles_.size(); ++i) {
370 const auto& profile = loaded_profiles_[i];
371 ImGui::PushID(static_cast<int>(i));
372
373 bool is_current = (profile.name == current_profile_.name);
374 ImVec2 button_size(ImGui::GetContentRegionAvail().x - 80, 0);
375 ImVec4 button_color =
376 is_current ? theme.accent_color : theme.panel_bg_darker;
377 if (AgentUI::StyledButton(profile.name.c_str(), button_color,
378 button_size)) {
379 if (auto status = LoadBotProfile(profile.name); status.ok()) {
380 if (toast_manager_) {
382 absl::StrFormat("Loaded profile: %s", profile.name),
384 }
385 } else if (toast_manager_) {
386 toast_manager_->Show(std::string(status.message()),
388 }
389 }
390
391 ImGui::SameLine();
392 {
393 gui::StyleColorGuard del_guard(ImGuiCol_Button, theme.status_warning);
394 if (ImGui::SmallButton(ICON_MD_DELETE)) {
395 if (auto status = DeleteBotProfile(profile.name); status.ok()) {
396 if (toast_manager_) {
398 absl::StrFormat("Deleted profile: %s", profile.name),
400 }
401 } else if (toast_manager_) {
402 toast_manager_->Show(std::string(status.message()),
404 }
405 }
406 }
407
408 ImGui::TextDisabled(" %s | %s", profile.provider.c_str(),
409 profile.description.empty()
410 ? "No description"
411 : profile.description.c_str());
412 ImGui::Spacing();
413 ImGui::PopID();
414 }
415 }
416 ImGui::EndChild();
417}
418
420 const auto& theme = AgentUI::GetTheme();
421 AgentUI::RenderSectionHeader(ICON_MD_HISTORY, "Chat History Viewer",
422 theme.accent_color);
423
424 if (ImGui::Button(ICON_MD_REFRESH " Refresh History")) {
426 }
427 ImGui::SameLine();
428 if (ImGui::Button(ICON_MD_DELETE_FOREVER " Clear History")) {
429 if (agent_chat_) {
430 agent_chat_->ClearHistory();
431 cached_history_.clear();
432 }
433 }
434
436 cached_history_ = agent_chat_->GetAgentService()->GetHistory();
438 }
439
440 ImGui::Spacing();
441 ImGui::Separator();
442
443 ImGui::BeginChild("HistoryList", ImVec2(0, 0), true);
444 if (cached_history_.empty()) {
445 ImGui::TextDisabled(
446 "No chat history. Start a conversation in the chat window.");
447 } else {
448 for (const auto& msg : cached_history_) {
449 bool from_user = (msg.sender == cli::agent::ChatMessage::Sender::kUser);
450 ImVec4 color =
451 from_user ? theme.user_message_color : theme.agent_message_color;
452
453 gui::ColoredTextF(color, "%s:", from_user ? "User" : "Agent");
454
455 ImGui::SameLine();
456 ImGui::TextDisabled("%s", absl::FormatTime("%H:%M:%S", msg.timestamp,
457 absl::LocalTimeZone())
458 .c_str());
459
460 ImGui::TextWrapped("%s", msg.message.c_str());
461 ImGui::Spacing();
462 ImGui::Separator();
463 }
464 }
465 ImGui::EndChild();
466}
467
469 const auto& theme = AgentUI::GetTheme();
471 theme.accent_color);
472 ImGui::Spacing();
473
474 if (agent_chat_) {
475 auto metrics = agent_chat_->GetAgentService()->GetMetrics();
476 if (ImGui::BeginTable("MetricsTable", 2,
477 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
478 ImGui::TableSetupColumn("Metric", ImGuiTableColumnFlags_WidthFixed,
479 200.0f);
480 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
481 ImGui::TableHeadersRow();
482
483 auto Row = [](const char* label, const std::string& value) {
484 ImGui::TableNextRow();
485 ImGui::TableSetColumnIndex(0);
486 ImGui::Text("%s", label);
487 ImGui::TableSetColumnIndex(1);
488 ImGui::TextDisabled("%s", value.c_str());
489 };
490
491 Row("Total Messages",
492 absl::StrFormat("%d user / %d agent", metrics.total_user_messages,
493 metrics.total_agent_messages));
494 Row("Tool Calls", absl::StrFormat("%d", metrics.total_tool_calls));
495 Row("Commands", absl::StrFormat("%d", metrics.total_commands));
496 Row("Proposals", absl::StrFormat("%d", metrics.total_proposals));
497 Row("Average Latency (s)",
498 absl::StrFormat("%.2f", metrics.average_latency_seconds));
499 Row("Elapsed (s)",
500 absl::StrFormat("%.2f", metrics.total_elapsed_seconds));
501
502 ImGui::EndTable();
503 }
504 } else {
505 ImGui::TextDisabled("Initialize the chat system to see metrics.");
506 }
507}
508
510 const auto& theme = AgentUI::GetTheme();
511 AgentUI::RenderSectionHeader(ICON_MD_GRID_ON, "Common Tiles Reference",
512 theme.accent_color);
513 ImGui::Spacing();
514
515 ImGui::TextWrapped(
516 "Customize the tile reference file that AI uses for tile placement. "
517 "Organize tiles by category and provide hex IDs with descriptions.");
518
519 ImGui::Spacing();
520
521 if (ImGui::Button(ICON_MD_FOLDER_OPEN " Load", ImVec2(100, 0))) {
522 auto content = AssetLoader::LoadTextFile("agent/common_tiles.txt");
523 if (content.ok()) {
524 common_tiles_editor_->SetText(*content);
526 if (toast_manager_) {
527 toast_manager_->Show(ICON_MD_CHECK_CIRCLE " Common tiles loaded",
528 ToastType::kSuccess, 2.0f);
529 }
530 }
531 }
532
533 ImGui::SameLine();
534 if (ImGui::Button(ICON_MD_SAVE " Save", ImVec2(100, 0))) {
535 if (toast_manager_) {
537 " Save to project directory (coming soon)",
538 ToastType::kInfo, 2.0f);
539 }
540 }
541
542 ImGui::SameLine();
543 if (ImGui::SmallButton(ICON_MD_REFRESH)) {
545 }
546 if (ImGui::IsItemHovered()) {
547 ImGui::SetTooltip("Reload from disk");
548 }
549
551 auto content = AssetLoader::LoadTextFile("agent/common_tiles.txt");
552 if (content.ok()) {
553 common_tiles_editor_->SetText(*content);
554 } else {
555 std::string default_tiles =
556 "# Common Tile16 Reference\n"
557 "# Format: 0xHEX = Description\n\n"
558 "[grass_tiles]\n"
559 "0x020 = Grass (standard)\n\n"
560 "[nature_tiles]\n"
561 "0x02E = Tree (oak)\n"
562 "0x003 = Bush\n\n"
563 "[water_tiles]\n"
564 "0x14C = Water (top edge)\n"
565 "0x14D = Water (middle)\n";
566 common_tiles_editor_->SetText(default_tiles);
567 }
569 }
570
571 ImGui::Separator();
572 ImGui::Spacing();
573
575 ImVec2 editor_size(ImGui::GetContentRegionAvail().x,
576 ImGui::GetContentRegionAvail().y);
577 common_tiles_editor_->Render("##tiles_editor", editor_size, true);
578 }
579}
580
582 const auto& theme = AgentUI::GetTheme();
583 AgentUI::RenderSectionHeader(ICON_MD_ADD, "Create New System Prompt",
584 theme.accent_color);
585 ImGui::Spacing();
586
587 ImGui::TextWrapped(
588 "Create a custom system prompt from scratch or start from a template.");
589 ImGui::Separator();
590
591 ImGui::Text("Prompt Name:");
592 ImGui::SetNextItemWidth(-1);
593 ImGui::InputTextWithHint("##new_prompt_name", "e.g., custom_prompt.txt",
595
596 ImGui::Spacing();
597 ImGui::Text("Start from template:");
598
599 auto LoadTemplate = [&](const char* path, const char* label) {
600 if (ImGui::Button(label, ImVec2(-1, 0))) {
601 auto content = AssetLoader::LoadTextFile(path);
602 if (content.ok() && prompt_editor_) {
603 prompt_editor_->SetText(*content);
604 if (toast_manager_) {
605 toast_manager_->Show("Template loaded", ToastType::kSuccess, 1.5f);
606 }
607 }
608 }
609 };
610
611 LoadTemplate("agent/system_prompt.txt", ICON_MD_FILE_COPY " v1 (Basic)");
612 LoadTemplate("agent/system_prompt_v2.txt",
613 ICON_MD_FILE_COPY " v2 (Enhanced)");
614 LoadTemplate("agent/system_prompt_v3.txt",
615 ICON_MD_FILE_COPY " v3 (Proactive)");
616
617 if (ImGui::Button(ICON_MD_NOTE_ADD " Blank Template", ImVec2(-1, 0))) {
618 if (prompt_editor_) {
619 std::string blank_template =
620 "# Custom System Prompt\n\n"
621 "You are an AI assistant for ROM hacking.\n\n"
622 "## Your Role\n"
623 "- Help users understand ROM data\n"
624 "- Provide accurate information\n"
625 "- Use tools when needed\n\n"
626 "## Guidelines\n"
627 "1. Always provide text_response after tool calls\n"
628 "2. Be helpful and accurate\n"
629 "3. Explain your reasoning\n";
630 prompt_editor_->SetText(blank_template);
631 if (toast_manager_) {
632 toast_manager_->Show("Blank template created", ToastType::kSuccess,
633 1.5f);
634 }
635 }
636 }
637
638 ImGui::Spacing();
639 ImGui::Separator();
640
641 {
642 gui::StyleColorGuard save_guard(ImGuiCol_Button, theme.status_success);
643 if (ImGui::Button(ICON_MD_SAVE " Save New Prompt", ImVec2(-1, 40))) {
644 if (std::strlen(new_prompt_name_) > 0 && prompt_editor_) {
645 std::string filename = new_prompt_name_;
646 if (!absl::EndsWith(filename, ".txt")) {
647 filename += ".txt";
648 }
649 if (toast_manager_) {
651 absl::StrFormat(ICON_MD_SAVE " Prompt saved as %s", filename),
652 ToastType::kSuccess, 3.0f);
653 }
654 std::memset(new_prompt_name_, 0, sizeof(new_prompt_name_));
655 } else if (toast_manager_) {
656 toast_manager_->Show(ICON_MD_WARNING " Enter a name for the prompt",
657 ToastType::kWarning, 2.0f);
658 }
659 }
660 }
661
662 ImGui::Spacing();
663 ImGui::TextWrapped(
664 "Note: New prompts are saved to your project. Use the Prompt Editor to "
665 "edit existing prompts.");
666}
667
669 const auto& theme = AgentUI::GetTheme();
671 theme.accent_color);
672
673 if (!agent_chat_) {
674 ImGui::TextDisabled("Chat system not initialized.");
675 return;
676 }
677
678 ImGui::BeginChild("AgentBuilderPanel", ImVec2(0, 0), false);
679
680 int stage_index =
681 std::clamp(builder_state_.active_stage, 0,
682 static_cast<int>(builder_state_.stages.size()) - 1);
683 int completed_stages = 0;
684 for (const auto& stage : builder_state_.stages) {
685 if (stage.completed) {
686 ++completed_stages;
687 }
688 }
689 float completion_ratio =
690 builder_state_.stages.empty()
691 ? 0.0f
692 : static_cast<float>(completed_stages) /
693 static_cast<float>(builder_state_.stages.size());
694 auto truncate_summary = [](const std::string& text) {
695 constexpr size_t kMaxLen = 64;
696 if (text.size() <= kMaxLen) {
697 return text;
698 }
699 return text.substr(0, kMaxLen - 3) + "...";
700 };
701
702 const float left_width =
703 std::min(260.0f, ImGui::GetContentRegionAvail().x * 0.32f);
704
705 ImGui::BeginChild("BuilderStages", ImVec2(left_width, 0), true);
706 AgentUI::RenderSectionHeader(ICON_MD_LIST, "Stages", theme.accent_color);
707 ImGui::TextDisabled("%d/%zu complete", completed_stages,
708 builder_state_.stages.size());
709 ImGui::ProgressBar(completion_ratio, ImVec2(-1, 0));
710 ImGui::Spacing();
711
712 for (size_t i = 0; i < builder_state_.stages.size(); ++i) {
713 auto& stage = builder_state_.stages[i];
714 ImGui::PushID(static_cast<int>(i));
715 bool selected = builder_state_.active_stage == static_cast<int>(i);
716 if (ImGui::Selectable(stage.name.c_str(), selected)) {
717 builder_state_.active_stage = static_cast<int>(i);
718 stage_index = static_cast<int>(i);
719 }
720 ImGui::SameLine(ImGui::GetContentRegionAvail().x - 24.0f);
721 ImGui::Checkbox("##stage_done", &stage.completed);
722 ImGui::TextDisabled("%s", truncate_summary(stage.summary).c_str());
723 ImGui::Separator();
724 ImGui::PopID();
725 }
726 ImGui::EndChild();
727
728 ImGui::SameLine();
729
730 ImGui::BeginChild("BuilderDetails", ImVec2(0, 0), false);
732 theme.accent_color);
733 if (stage_index >= 0 &&
734 stage_index < static_cast<int>(builder_state_.stages.size())) {
735 ImGui::TextColored(theme.text_secondary_color, "%s",
736 builder_state_.stages[stage_index].summary.c_str());
737 }
738 ImGui::Spacing();
739
740 switch (stage_index) {
741 case 0: {
742 static std::string new_goal;
743 ImGui::Text("Persona + Goals");
744 ImGui::TextWrapped(
745 "Define the agent's voice, boundaries, and success criteria. Keep "
746 "goals short and action-focused.");
747 ImGui::InputTextMultiline("##persona_notes",
748 &builder_state_.persona_notes, ImVec2(-1, 140));
749 ImGui::Spacing();
750 ImGui::TextDisabled("Add Goal");
751 ImGui::InputTextWithHint("##goal_input",
752 "e.g. Review collision edge cases", &new_goal);
753 ImGui::SameLine();
754 if (ImGui::Button(ICON_MD_ADD) && !new_goal.empty()) {
755 builder_state_.goals.push_back(new_goal);
756 new_goal.clear();
757 }
758 for (size_t i = 0; i < builder_state_.goals.size(); ++i) {
759 ImGui::BulletText("%s", builder_state_.goals[i].c_str());
760 ImGui::SameLine();
761 ImGui::PushID(static_cast<int>(i));
762 if (ImGui::SmallButton(ICON_MD_CLOSE)) {
763 builder_state_.goals.erase(builder_state_.goals.begin() + i);
764 ImGui::PopID();
765 break;
766 }
767 ImGui::PopID();
768 }
769 break;
770 }
771 case 1: {
772 ImGui::Text("Tool Stack");
773 ImGui::TextWrapped(
774 "Enable only what the plan needs. Fewer tools = clearer responses.");
775 auto tool_checkbox = [&](const char* label, bool* value,
776 const char* hint) {
777 ImGui::Checkbox(label, value);
778 ImGui::SameLine();
779 ImGui::TextDisabled("%s", hint);
780 };
781 tool_checkbox("Resources", &builder_state_.tools.resources,
782 "Project files, docs, refs");
783 tool_checkbox("Dungeon", &builder_state_.tools.dungeon,
784 "Rooms, objects, entrances");
785 tool_checkbox("Overworld", &builder_state_.tools.overworld,
786 "Maps, tile16, entities");
787 tool_checkbox("Dialogue", &builder_state_.tools.dialogue,
788 "NPC text + scripts");
789 tool_checkbox("GUI Automation", &builder_state_.tools.gui,
790 "Test harness + screenshots");
791 tool_checkbox("Music", &builder_state_.tools.music, "Trackers + SPC");
792 tool_checkbox("Sprite", &builder_state_.tools.sprite,
793 "Sprites + palettes");
794 tool_checkbox("Emulator", &builder_state_.tools.emulator,
795 "Runtime probes");
796 tool_checkbox("Memory Inspector", &builder_state_.tools.memory_inspector,
797 "RAM/SRAM watch + inspection");
798 break;
799 }
800 case 2: {
801 ImGui::Text("Automation");
802 ImGui::TextWrapped(
803 "Use automation to validate fixes quickly. Pair with gRPC harness "
804 "for repeatable checks.");
805 ImGui::Checkbox("Auto-run harness plan", &builder_state_.auto_run_tests);
806 ImGui::Checkbox("Auto-sync ROM context", &builder_state_.auto_sync_rom);
807 ImGui::Checkbox("Auto-focus proposal drawer",
809 break;
810 }
811 case 3: {
812 ImGui::Text("Validation Criteria");
813 ImGui::TextWrapped(
814 "Capture the acceptance criteria and what a passing run looks like.");
815 ImGui::InputTextMultiline("##validation_notes",
816 &builder_state_.stages[stage_index].summary,
817 ImVec2(-1, 140));
818 break;
819 }
820 case 4: {
821 ImGui::Text("E2E Checklist");
822 ImGui::ProgressBar(completion_ratio, ImVec2(-1, 0),
823 absl::StrFormat("%d/%zu complete", completed_stages,
824 builder_state_.stages.size())
825 .c_str());
826 ImGui::Checkbox("Ready for automation handoff",
828 ImGui::TextDisabled("Auto-sync ROM: %s",
829 builder_state_.auto_sync_rom ? "ON" : "OFF");
830 ImGui::TextDisabled("Auto-focus proposals: %s",
831 builder_state_.auto_focus_proposals ? "ON" : "OFF");
832 break;
833 }
834 }
835
836 ImGui::Spacing();
837 ImGui::Separator();
838 ImGui::TextDisabled("Builder Output");
839 ImGui::BulletText("Persona notes sync to the chat summary");
840 ImGui::BulletText("Tool stack applies to the agent tool preferences");
841 ImGui::BulletText("E2E readiness gates automation handoff");
842
843 ImGui::Spacing();
844 gui::StyleColorGuard apply_guard(ImGuiCol_Button, theme.accent_color);
845 if (ImGui::Button(ICON_MD_LINK " Apply to Chat", ImVec2(-1, 0))) {
846 auto* service = agent_chat_->GetAgentService();
847 if (service) {
853 prefs.gui = builder_state_.tools.gui;
856#ifdef YAZE_WITH_GRPC
858#endif
860 service->SetToolPreferences(prefs);
861
862 auto agent_cfg = service->GetConfig();
863 agent_cfg.max_tool_iterations = current_profile_.max_tool_iterations;
864 agent_cfg.max_retry_attempts = current_profile_.max_retry_attempts;
865 agent_cfg.verbose = current_profile_.verbose;
866 agent_cfg.show_reasoning = current_profile_.show_reasoning;
867 service->SetConfig(agent_cfg);
868 }
869
870 agent_chat_->SetLastPlanSummary(builder_state_.persona_notes);
871
872 if (toast_manager_) {
873 toast_manager_->Show("Builder tool plan synced to chat",
874 ToastType::kSuccess, 2.0f);
875 }
876 }
877
878 ImGui::Spacing();
879 ImGui::InputTextWithHint("##blueprint_path",
880 "Path to blueprint (optional)...",
882 std::filesystem::path blueprint_path =
884 ? (std::filesystem::temp_directory_path() / "agent_builder.json")
885 : std::filesystem::path(builder_state_.blueprint_path);
886
887 if (ImGui::Button(ICON_MD_SAVE " Save Blueprint")) {
888 auto status = SaveBuilderBlueprint(blueprint_path);
889 if (toast_manager_) {
890 if (status.ok()) {
891 toast_manager_->Show("Builder blueprint saved", ToastType::kSuccess,
892 2.0f);
893 } else {
894 toast_manager_->Show(std::string(status.message()), ToastType::kError,
895 3.5f);
896 }
897 }
898 }
899 ImGui::SameLine();
900 if (ImGui::Button(ICON_MD_FOLDER_OPEN " Load Blueprint")) {
901 auto status = LoadBuilderBlueprint(blueprint_path);
902 if (toast_manager_) {
903 if (status.ok()) {
904 toast_manager_->Show("Builder blueprint loaded", ToastType::kSuccess,
905 2.0f);
906 } else {
907 toast_manager_->Show(std::string(status.message()), ToastType::kError,
908 3.5f);
909 }
910 }
911 }
912
913 ImGui::EndChild();
914 ImGui::EndChild();
915}
916
918 if (mesen_debug_panel_) {
919 mesen_debug_panel_->Draw();
920 } else {
921 ImGui::TextDisabled("Mesen2 debug panel unavailable.");
922 }
923}
924
928 } else {
929 ImGui::TextDisabled("Mesen2 screenshot panel unavailable.");
930 }
931}
932
935 // Share the Mesen client if available
936 if (mesen_debug_panel_ && mesen_debug_panel_->IsConnected()) {
937 // The panels can share the client from the registry
938 }
939 oracle_state_panel_->Draw();
940 } else {
941 ImGui::TextDisabled("Oracle state panel unavailable.");
942 }
943}
944
947 // Wire up the project pointer so the panel can access the manifest
949 feature_flag_panel_->Draw();
950 } else {
951 ImGui::TextDisabled("Feature flag panel unavailable.");
952 }
953}
954
956 if (manifest_panel_) {
958 manifest_panel_->Draw();
959 } else {
960 ImGui::TextDisabled("Manifest panel unavailable.");
961 }
962}
963
965 if (sram_viewer_panel_) {
967 sram_viewer_panel_->Draw();
968 } else {
969 ImGui::TextDisabled("SRAM viewer panel unavailable.");
970 }
971}
972
973} // namespace editor
974} // namespace yaze
static absl::StatusOr< std::string > LoadTextFile(const std::string &relative_path)
bool is_loaded() const
Definition rom.h:132
absl::Status SaveBuilderBlueprint(const std::filesystem::path &path)
std::unique_ptr< MesenScreenshotPanel > mesen_screenshot_panel_
std::unique_ptr< OracleStateLibraryPanel > oracle_state_panel_
void ApplyModelPreset(const ModelPreset &preset)
std::vector< cli::agent::ChatMessage > cached_history_
AgentBuilderState builder_state_
std::unique_ptr< FeatureFlagEditorPanel > feature_flag_panel_
std::unique_ptr< AgentConfigPanel > config_panel_
std::unique_ptr< MesenDebugPanel > mesen_debug_panel_
AgentUIContext * context_
absl::Status LoadBuilderBlueprint(const std::filesystem::path &path)
absl::Status DeleteBotProfile(const std::string &name)
ToastManager * toast_manager_
std::unique_ptr< TextEditor > common_tiles_editor_
std::unique_ptr< TextEditor > prompt_editor_
std::unique_ptr< SramViewerPanel > sram_viewer_panel_
absl::Status LoadBotProfile(const std::string &name)
void ApplyConfigFromContext(const AgentConfigState &config)
std::unique_ptr< AgentChat > agent_chat_
std::unique_ptr< ManifestPanel > manifest_panel_
std::vector< BotProfile > loaded_profiles_
EditorDependencies dependencies_
Definition editor.h:316
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
RAII guard for ImGui style colors.
Definition style_guard.h:27
#define ICON_MD_FOLDER_OPEN
Definition icons.h:813
#define ICON_MD_LINK
Definition icons.h:1090
#define ICON_MD_INFO
Definition icons.h:993
#define ICON_MD_CHAT
Definition icons.h:394
#define ICON_MD_STORAGE
Definition icons.h:1865
#define ICON_MD_WARNING
Definition icons.h:2123
#define ICON_MD_NOTE_ADD
Definition icons.h:1330
#define ICON_MD_STAR
Definition icons.h:1848
#define ICON_MD_REFRESH
Definition icons.h:1572
#define ICON_MD_EDIT
Definition icons.h:645
#define ICON_MD_AUTO_FIX_HIGH
Definition icons.h:218
#define ICON_MD_GRID_ON
Definition icons.h:896
#define ICON_MD_LIST
Definition icons.h:1094
#define ICON_MD_ADD
Definition icons.h:86
#define ICON_MD_FILE_COPY
Definition icons.h:743
#define ICON_MD_CHECK_CIRCLE
Definition icons.h:400
#define ICON_MD_SAVE
Definition icons.h:1644
#define ICON_MD_DELETE
Definition icons.h:530
#define ICON_MD_FOLDER
Definition icons.h:809
#define ICON_MD_DELETE_FOREVER
Definition icons.h:531
#define ICON_MD_CLOSE
Definition icons.h:418
#define ICON_MD_ANALYTICS
Definition icons.h:154
#define ICON_MD_HISTORY
Definition icons.h:946
constexpr char kProviderOpenAi[]
void RenderStatusIndicator(const char *label, bool active)
bool StyledButton(const char *label, const ImVec4 &color, const ImVec2 &size)
const AgentUITheme & GetTheme()
void RenderSectionHeader(const char *icon, const char *label, const ImVec4 &color)
void RenderProviderBadge(const char *provider)
void ApplyHostPresetToProfile(AgentEditor::BotProfile *profile, const UserSettings::Preferences::AiHost &host, const UserSettings::Preferences *prefs)
void ColoredTextF(const ImVec4 &color, const char *fmt,...)
std::function< void(const AgentConfigState &) update_config)
std::function< void(bool force)> refresh_models
std::function< void(const ModelPreset &) apply_preset)
Agent configuration state.
struct yaze::editor::AgentEditor::AgentBuilderState::ToolPlan tools
project::YazeProject * project
Definition editor.h:168
Model preset for quick switching.