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"
32#include "imgui/misc/cpp/imgui_stdlib.h"
47 ImGuiIO& imgui_io = ImGui::GetIO();
59 ImGui::TextDisabled(
"Agent configuration unavailable.");
60 ImGui::TextWrapped(
"Initialize the Agent UI to edit provider settings.");
68 if (!prefs.ai_hosts.empty()) {
71 const auto& hosts = prefs.ai_hosts;
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);
82 const char* preview = (active_index >= 0)
83 ? hosts[active_index].label.c_str()
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)) {
95 ImGui::SetItemDefaultFocus();
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());
109 "Host presets come from settings.json (Documents/Yaze).");
140 if (ImGui::BeginChild(
"AgentStatusCard", ImVec2(0, 150),
true)) {
145 if (ImGui::BeginTable(
"AgentStatusTable", 2,
146 ImGuiTableFlags_SizingStretchProp)) {
147 ImGui::TableNextRow();
148 ImGui::TableSetColumnIndex(0);
149 ImGui::TextDisabled(
"Chat");
150 ImGui::TableSetColumnIndex(1);
155 if (ImGui::SmallButton(
"Open")) {
160 ImGui::TableNextRow();
161 ImGui::TableSetColumnIndex(0);
162 ImGui::TextDisabled(
"Provider");
163 ImGui::TableSetColumnIndex(1);
170 ImGui::TableNextRow();
171 ImGui::TableSetColumnIndex(0);
172 ImGui::TextDisabled(
"ROM");
173 ImGui::TableSetColumnIndex(1);
175 ImGui::TextColored(theme.status_success,
178 ImGui::TextDisabled(
"Tools ready");
180 ImGui::TextColored(theme.status_warning,
ICON_MD_WARNING " Not Loaded");
189 if (ImGui::BeginChild(
"AgentMetricsCard", ImVec2(0, 170),
true)) {
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);
204 std::vector<double> latencies;
205 for (
const auto& msg :
agent_chat_->GetAgentService()->GetHistory()) {
207 msg.model_metadata.has_value() &&
208 msg.model_metadata->latency_seconds > 0.0) {
209 latencies.push_back(msg.model_metadata->latency_seconds);
212 if (latencies.size() > 30) {
213 latencies.erase(latencies.begin(),
214 latencies.end() -
static_cast<long>(30));
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);
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);
228 *std::max_element(latencies.begin(), latencies.end());
229 ImPlot::SetupAxisLimits(ImAxis_Y1, 0, max_latency * 1.2,
231 ImPlot::PlotLine(
"Latency", xs.data(), latencies.data(),
232 static_cast<int>(latencies.size()));
237 ImGui::TextDisabled(
"Initialize the chat system to see metrics.");
247 ImGui::TextDisabled(
"View detailed metrics in the Metrics tab");
256 ImGui::Text(
"File:");
257 ImGui::SetNextItemWidth(-45);
259 const char* options[] = {
"system_prompt.txt",
"system_prompt_v2.txt",
260 "system_prompt_v3.txt"};
261 for (
const char* option : options) {
263 if (ImGui::Selectable(option, selected)) {
275 if (ImGui::IsItemHovered()) {
276 ImGui::SetTooltip(
"Reload from disk");
282 if (content_result.ok()) {
292 std::string placeholder = absl::StrFormat(
293 "# System prompt file not found: %s\n"
295 "# Ensure the file exists in assets/agent/%s\n",
305 ImVec2 editor_size(ImGui::GetContentRegionAvail().x,
306 ImGui::GetContentRegionAvail().y - 60);
310 if (ImGui::Button(
ICON_MD_SAVE " Save Prompt to Profile", ImVec2(-1, 0))) {
321 "Edit the system prompt that guides the agent's behavior. Changes are "
322 "stored on the active bot profile.");
331 ImGui::BeginChild(
"CurrentProfile", ImVec2(0, 150),
true);
339 ImGui::TextWrapped(
"Description: %s",
347 if (ImGui::Button(
ICON_MD_ADD " Create New Profile", ImVec2(-1, 0))) {
349 new_profile.
name =
"New Profile";
364 ImGui::BeginChild(
"ProfilesList", ImVec2(0, 0),
true);
367 "No saved profiles. Create and save a profile to see it here.");
371 ImGui::PushID(
static_cast<int>(i));
374 ImVec2 button_size(ImGui::GetContentRegionAvail().x - 80, 0);
375 ImVec4 button_color =
376 is_current ? theme.accent_color : theme.panel_bg_darker;
382 absl::StrFormat(
"Loaded profile: %s", profile.name),
398 absl::StrFormat(
"Deleted profile: %s", profile.name),
408 ImGui::TextDisabled(
" %s | %s", profile.provider.c_str(),
409 profile.description.empty()
411 : profile.description.c_str());
443 ImGui::BeginChild(
"HistoryList", ImVec2(0, 0),
true);
446 "No chat history. Start a conversation in the chat window.");
451 from_user ? theme.user_message_color : theme.agent_message_color;
456 ImGui::TextDisabled(
"%s", absl::FormatTime(
"%H:%M:%S", msg.timestamp,
457 absl::LocalTimeZone())
460 ImGui::TextWrapped(
"%s", msg.message.c_str());
475 auto metrics =
agent_chat_->GetAgentService()->GetMetrics();
476 if (ImGui::BeginTable(
"MetricsTable", 2,
477 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
478 ImGui::TableSetupColumn(
"Metric", ImGuiTableColumnFlags_WidthFixed,
480 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthStretch);
481 ImGui::TableHeadersRow();
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());
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));
500 absl::StrFormat(
"%.2f", metrics.total_elapsed_seconds));
505 ImGui::TextDisabled(
"Initialize the chat system to see metrics.");
516 "Customize the tile reference file that AI uses for tile placement. "
517 "Organize tiles by category and provide hex IDs with descriptions.");
534 if (ImGui::Button(
ICON_MD_SAVE " Save", ImVec2(100, 0))) {
537 " Save to project directory (coming soon)",
546 if (ImGui::IsItemHovered()) {
547 ImGui::SetTooltip(
"Reload from disk");
555 std::string default_tiles =
556 "# Common Tile16 Reference\n"
557 "# Format: 0xHEX = Description\n\n"
559 "0x020 = Grass (standard)\n\n"
561 "0x02E = Tree (oak)\n"
564 "0x14C = Water (top edge)\n"
565 "0x14D = Water (middle)\n";
575 ImVec2 editor_size(ImGui::GetContentRegionAvail().x,
576 ImGui::GetContentRegionAvail().y);
588 "Create a custom system prompt from scratch or start from a template.");
591 ImGui::Text(
"Prompt Name:");
592 ImGui::SetNextItemWidth(-1);
593 ImGui::InputTextWithHint(
"##new_prompt_name",
"e.g., custom_prompt.txt",
597 ImGui::Text(
"Start from template:");
599 auto LoadTemplate = [&](
const char* path,
const char* label) {
600 if (ImGui::Button(label, ImVec2(-1, 0))) {
612 LoadTemplate(
"agent/system_prompt_v2.txt",
614 LoadTemplate(
"agent/system_prompt_v3.txt",
619 std::string blank_template =
620 "# Custom System Prompt\n\n"
621 "You are an AI assistant for ROM hacking.\n\n"
623 "- Help users understand ROM data\n"
624 "- Provide accurate information\n"
625 "- Use tools when needed\n\n"
627 "1. Always provide text_response after tool calls\n"
628 "2. Be helpful and accurate\n"
629 "3. Explain your reasoning\n";
643 if (ImGui::Button(
ICON_MD_SAVE " Save New Prompt", ImVec2(-1, 40))) {
646 if (!absl::EndsWith(filename,
".txt")) {
651 absl::StrFormat(
ICON_MD_SAVE " Prompt saved as %s", filename),
664 "Note: New prompts are saved to your project. Use the Prompt Editor to "
665 "edit existing prompts.");
674 ImGui::TextDisabled(
"Chat system not initialized.");
678 ImGui::BeginChild(
"AgentBuilderPanel", ImVec2(0, 0),
false);
683 int completed_stages = 0;
685 if (stage.completed) {
689 float completion_ratio =
692 :
static_cast<float>(completed_stages) /
694 auto truncate_summary = [](
const std::string& text) {
695 constexpr size_t kMaxLen = 64;
696 if (text.size() <= kMaxLen) {
699 return text.substr(0, kMaxLen - 3) +
"...";
702 const float left_width =
703 std::min(260.0f, ImGui::GetContentRegionAvail().x * 0.32f);
705 ImGui::BeginChild(
"BuilderStages", ImVec2(left_width, 0),
true);
707 ImGui::TextDisabled(
"%d/%zu complete", completed_stages,
709 ImGui::ProgressBar(completion_ratio, ImVec2(-1, 0));
714 ImGui::PushID(
static_cast<int>(i));
716 if (ImGui::Selectable(stage.name.c_str(), selected)) {
718 stage_index =
static_cast<int>(i);
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());
730 ImGui::BeginChild(
"BuilderDetails", ImVec2(0, 0),
false);
733 if (stage_index >= 0 &&
735 ImGui::TextColored(theme.text_secondary_color,
"%s",
740 switch (stage_index) {
742 static std::string new_goal;
743 ImGui::Text(
"Persona + Goals");
745 "Define the agent's voice, boundaries, and success criteria. Keep "
746 "goals short and action-focused.");
747 ImGui::InputTextMultiline(
"##persona_notes",
750 ImGui::TextDisabled(
"Add Goal");
751 ImGui::InputTextWithHint(
"##goal_input",
752 "e.g. Review collision edge cases", &new_goal);
754 if (ImGui::Button(
ICON_MD_ADD) && !new_goal.empty()) {
761 ImGui::PushID(
static_cast<int>(i));
772 ImGui::Text(
"Tool Stack");
774 "Enable only what the plan needs. Fewer tools = clearer responses.");
775 auto tool_checkbox = [&](
const char* label,
bool* value,
777 ImGui::Checkbox(label, value);
779 ImGui::TextDisabled(
"%s", hint);
782 "Project files, docs, refs");
784 "Rooms, objects, entrances");
786 "Maps, tile16, entities");
788 "NPC text + scripts");
790 "Test harness + screenshots");
793 "Sprites + palettes");
797 "RAM/SRAM watch + inspection");
801 ImGui::Text(
"Automation");
803 "Use automation to validate fixes quickly. Pair with gRPC harness "
804 "for repeatable checks.");
807 ImGui::Checkbox(
"Auto-focus proposal drawer",
812 ImGui::Text(
"Validation Criteria");
814 "Capture the acceptance criteria and what a passing run looks like.");
815 ImGui::InputTextMultiline(
"##validation_notes",
821 ImGui::Text(
"E2E Checklist");
822 ImGui::ProgressBar(completion_ratio, ImVec2(-1, 0),
823 absl::StrFormat(
"%d/%zu complete", completed_stages,
826 ImGui::Checkbox(
"Ready for automation handoff",
828 ImGui::TextDisabled(
"Auto-sync ROM: %s",
830 ImGui::TextDisabled(
"Auto-focus proposals: %s",
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");
845 if (ImGui::Button(
ICON_MD_LINK " Apply to Chat", ImVec2(-1, 0))) {
860 service->SetToolPreferences(prefs);
862 auto agent_cfg = service->GetConfig();
867 service->SetConfig(agent_cfg);
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")
921 ImGui::TextDisabled(
"Mesen2 debug panel unavailable.");
929 ImGui::TextDisabled(
"Mesen2 screenshot panel unavailable.");
941 ImGui::TextDisabled(
"Oracle state panel unavailable.");
951 ImGui::TextDisabled(
"Feature flag panel unavailable.");
960 ImGui::TextDisabled(
"Manifest panel unavailable.");
969 ImGui::TextDisabled(
"SRAM viewer panel unavailable.");
static absl::StatusOr< std::string > LoadTextFile(const std::string &relative_path)
void RefreshModelCache(bool force)
absl::Status SaveBuilderBlueprint(const std::filesystem::path &path)
void DrawAdvancedMetricsPanel()
std::unique_ptr< MesenScreenshotPanel > mesen_screenshot_panel_
void SyncContextFromProfile()
void ApplyToolPreferencesFromContext()
std::unique_ptr< OracleStateLibraryPanel > oracle_state_panel_
void ApplyModelPreset(const ModelPreset &preset)
bool prompt_editor_initialized_
std::vector< cli::agent::ChatMessage > cached_history_
AgentBuilderState builder_state_
bool common_tiles_initialized_
void DrawPromptEditorPanel()
void DrawFeatureFlagPanel()
std::unique_ptr< FeatureFlagEditorPanel > feature_flag_panel_
std::unique_ptr< AgentConfigPanel > config_panel_
std::unique_ptr< MesenDebugPanel > mesen_debug_panel_
void DrawSramViewerPanel()
AgentUIContext * context_
absl::Status LoadBuilderBlueprint(const std::filesystem::path &path)
bool history_needs_refresh_
void DrawNewPromptCreator()
void DrawOracleStatePanel()
std::string active_prompt_file_
absl::Status DeleteBotProfile(const std::string &name)
ToastManager * toast_manager_
std::unique_ptr< TextEditor > common_tiles_editor_
void DrawCommonTilesEditor()
void DrawMesenScreenshotPanel()
char new_prompt_name_[128]
std::unique_ptr< TextEditor > prompt_editor_
void DrawMesenDebugPanel()
std::unique_ptr< SramViewerPanel > sram_viewer_panel_
BotProfile current_profile_
absl::Status LoadBotProfile(const std::string &name)
void DrawChatHistoryViewer()
void DrawBotProfilesPanel()
void ApplyConfigFromContext(const AgentConfigState &config)
void MarkProfileUiDirty()
std::unique_ptr< AgentChat > agent_chat_
void DrawConfigurationPanel()
std::unique_ptr< ManifestPanel > manifest_panel_
std::vector< BotProfile > loaded_profiles_
void DrawAgentBuilderPanel()
EditorDependencies dependencies_
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
RAII guard for ImGui style colors.
#define ICON_MD_FOLDER_OPEN
#define ICON_MD_AUTO_FIX_HIGH
#define ICON_MD_FILE_COPY
#define ICON_MD_CHECK_CIRCLE
#define ICON_MD_DELETE_FOREVER
#define ICON_MD_ANALYTICS
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()> apply_tool_preferences
std::function< void(const ModelPreset &) apply_preset)
Agent configuration state.
struct yaze::editor::AgentEditor::AgentBuilderState::ToolPlan tools
std::string persona_notes
std::vector< std::string > goals
std::vector< Stage > stages
bool auto_focus_proposals
std::string blueprint_path
std::string system_prompt
project::YazeProject * project
UserSettings * user_settings
Model preset for quick switching.