yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
proposal_drawer.cc
Go to the documentation of this file.
2#include "util/i18n/tr.h"
3
4#include <algorithm>
5#include <filesystem>
6#include <fstream>
7#include <sstream>
8
9#include "absl/strings/str_format.h"
10#include "absl/time/time.h"
11#include "app/gui/core/icons.h"
13#include "imgui/imgui.h"
14
15// Policy evaluation support (optional, only in main yaze build)
16#ifdef YAZE_ENABLE_POLICY_FRAMEWORK
18#endif
19
20namespace yaze {
21namespace editor {
22
26
28 // Draw content without window wrapper (for embedding in RightDrawerManager)
29 if (needs_refresh_) {
31 needs_refresh_ = false;
34 }
35 }
36
37 // Header with refresh button
38 if (ImGui::Button(ICON_MD_REFRESH " Refresh")) {
40 }
41 ImGui::SameLine();
43 if (!action_status_.ok()) {
44 ImGui::PushStyleColor(ImGuiCol_Text, gui::GetErrorColor());
45 ImGui::TextWrapped("%s", std::string(action_status_.message()).c_str());
46 ImGui::PopStyleColor();
47 } else if (!action_feedback_.empty()) {
48 ImGui::TextWrapped("%s", tr(action_feedback_.c_str()));
49 }
50
51 ImGui::Separator();
52
53 // Split view: proposal list on top, details on bottom
54 const float avail_y = std::max(0.0f, ImGui::GetContentRegionAvail().y);
55 const float detail_reserve =
56 selected_proposal_ ? std::min(120.0f, avail_y * 0.4f) : 0.0f;
57 const float list_budget = std::max(0.0f, avail_y - detail_reserve);
58 const float preferred_list = std::clamp(avail_y * 0.45f, 180.0f, 320.0f);
59 const float list_height = std::min(preferred_list, list_budget);
60
61 ImGui::BeginChild("ProposalListEmbed", ImVec2(0, list_height), true);
63 ImGui::EndChild();
64
66 ImGui::Separator();
67 ImGui::BeginChild("ProposalDetailEmbed", ImVec2(0, 0), true);
69 ImGui::EndChild();
70 }
72}
73
75 if (!visible_)
76 return;
77
78 ImGuiIO& io = ImGui::GetIO();
79 ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x - drawer_width_, 0),
80 ImGuiCond_Always);
81 ImGui::SetNextWindowSize(ImVec2(drawer_width_, io.DisplaySize.y),
82 ImGuiCond_Always);
83 const ImGuiWindowFlags flags = ImGuiWindowFlags_NoMove |
84 ImGuiWindowFlags_NoResize |
85 ImGuiWindowFlags_NoCollapse;
86 if (ImGui::Begin("Agent Proposals", &visible_, flags)) {
88 }
89 ImGui::End();
90}
91
93 if (rom_ == rom)
94 return;
95 rom_ = rom;
97}
98
101 selected_proposal_id_.clear();
102 selected_proposal_ = nullptr;
103 diff_content_.clear();
104 log_content_.clear();
105 action_status_ = absl::OkStatus();
106 action_feedback_.clear();
107 needs_refresh_ = true;
108}
109
115
118 ImGui::OpenPopup("Confirm Proposal Action");
119 show_confirm_dialog_ = false;
120 }
121 if (!ImGui::BeginPopupModal("Confirm Proposal Action", nullptr,
122 ImGuiWindowFlags_AlwaysAutoResize)) {
123 return;
124 }
125 if (confirm_action_.empty() || confirm_proposal_id_.empty()) {
126 ImGui::CloseCurrentPopup();
127 ImGui::EndPopup();
128 return;
129 }
130
131 ImGui::Text(tr("Are you sure you want to %s this proposal?"),
132 confirm_action_.c_str());
133 ImGui::Separator();
134 if (ImGui::Button(tr("Yes"), ImVec2(120, 0))) {
135 if (confirm_action_ == "reject") {
137 action_feedback_ = "Proposal rejected.";
138 } else if (confirm_action_ == "delete") {
140 action_feedback_ = "Proposal deleted.";
141 } else {
143 action_feedback_.clear();
144 }
146 ImGui::CloseCurrentPopup();
148 }
149 ImGui::SameLine();
150 if (ImGui::Button(tr("No"), ImVec2(120, 0))) {
152 ImGui::CloseCurrentPopup();
153 }
154 ImGui::EndPopup();
155}
156
158 if (proposals_.empty()) {
159 ImGui::TextWrapped(tr("No proposals found."));
160 ImGui::TextWrapped(tr("Run CLI command: z3ed agent run --prompt \"...\""));
161 return;
162 }
163
164 ImGuiTableFlags flags =
165 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY;
166
167 if (ImGui::BeginTable("ProposalsTable", 3, flags)) {
168 ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 60.0f);
169 ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 80.0f);
170 ImGui::TableSetupColumn("Prompt", ImGuiTableColumnFlags_WidthStretch);
171 ImGui::TableSetupScrollFreeze(0, 1);
172 ImGui::TableHeadersRow();
173
174 for (const auto& proposal : proposals_) {
175 ImGui::TableNextRow();
176
177 // ID column
178 ImGui::TableSetColumnIndex(0);
179 bool is_selected = (proposal.id == selected_proposal_id_);
180 if (ImGui::Selectable(proposal.id.c_str(), is_selected,
181 ImGuiSelectableFlags_SpanAllColumns)) {
182 SelectProposal(proposal.id);
183 }
184
185 // Status column
186 ImGui::TableSetColumnIndex(1);
187 switch (proposal.status) {
189 ImGui::TextColored(gui::GetWarningColor(),
190 ICON_MD_HOURGLASS_TOP " Pending");
191 break;
193 ImGui::TextColored(gui::GetSuccessColor(),
194 ICON_MD_CHECK_CIRCLE " Accepted");
195 break;
197 ImGui::TextColored(gui::GetErrorColor(), ICON_MD_CANCEL " Rejected");
198 break;
199 }
200
201 // Prompt column (truncated)
202 ImGui::TableSetColumnIndex(2);
203 std::string truncated = proposal.prompt;
204 if (truncated.length() > 30) {
205 truncated = truncated.substr(0, 27) + "...";
206 }
207 ImGui::TextWrapped("%s", truncated.c_str());
208 }
209
210 ImGui::EndTable();
211 }
212}
213
216 return;
217
218 const auto& p = *selected_proposal_;
219
220 // Metadata section
221 if (ImGui::CollapsingHeader(tr("Metadata"), ImGuiTreeNodeFlags_DefaultOpen)) {
222 ImGui::Text(tr("ID: %s"), p.id.c_str());
223 ImGui::Text(tr("Sandbox: %s"), p.sandbox_id.c_str());
224 ImGui::Text(tr("Created: %s"), absl::FormatTime(p.created_at).c_str());
225 if (p.reviewed_at.has_value()) {
226 ImGui::Text(tr("Reviewed: %s"), absl::FormatTime(*p.reviewed_at).c_str());
227 }
228 ImGui::Text(tr("Commands: %d"), p.commands_executed);
229 ImGui::Text(tr("Bytes Changed: %d"), p.bytes_changed);
230 ImGui::Separator();
231 ImGui::TextWrapped(tr("Prompt: %s"), p.prompt.c_str());
232 ImGui::TextWrapped(tr("Description: %s"), p.description.c_str());
233 }
234
235 // Diff section
236 if (ImGui::CollapsingHeader(tr("Diff"), ImGuiTreeNodeFlags_DefaultOpen)) {
237 if (diff_content_.empty() && std::filesystem::exists(p.diff_path)) {
238 std::ifstream diff_file(p.diff_path);
239 if (diff_file.is_open()) {
240 std::stringstream buffer;
241 buffer << diff_file.rdbuf();
242 diff_content_ = buffer.str();
243 }
244 }
245
246 if (!diff_content_.empty()) {
247 ImGui::BeginChild("DiffContent", ImVec2(0, 150), true,
248 ImGuiWindowFlags_HorizontalScrollbar);
249 ImGui::TextUnformatted(diff_content_.c_str());
250 ImGui::EndChild();
251 } else {
252 ImGui::TextWrapped(tr("No diff available"));
253 }
254 }
255
256 // Log section
257 if (ImGui::CollapsingHeader(tr("Execution Log"))) {
258 if (log_content_.empty() && std::filesystem::exists(p.log_path)) {
259 std::ifstream log_file(p.log_path);
260 if (log_file.is_open()) {
261 std::stringstream buffer;
262 std::string line;
263 int line_count = 0;
264 while (std::getline(log_file, line) &&
265 line_count < log_display_lines_) {
266 buffer << line << "\n";
267 line_count++;
268 }
269 if (line_count >= log_display_lines_) {
270 buffer << "... (truncated, see " << p.log_path.string() << ")\n";
271 }
272 log_content_ = buffer.str();
273 }
274 }
275
276 if (!log_content_.empty()) {
277 ImGui::BeginChild("LogContent", ImVec2(0, 150), true,
278 ImGuiWindowFlags_HorizontalScrollbar);
279 ImGui::TextUnformatted(log_content_.c_str());
280 ImGui::EndChild();
281 } else {
282 ImGui::TextWrapped(tr("No log available"));
283 }
284 }
285
286 // Policy Status section (NEW)
287#ifdef YAZE_ENABLE_POLICY_FRAMEWORK
289#endif
290
291 // Action buttons
292 ImGui::Separator();
294}
295
297 const char* filter_labels[] = {"All", "Pending", "Accepted", "Rejected"};
298 int current_filter = static_cast<int>(status_filter_);
299
300 ImGui::SetNextItemWidth(120.0f);
301 if (ImGui::Combo(tr("Filter"), &current_filter, filter_labels, 4)) {
302 status_filter_ = static_cast<StatusFilter>(current_filter);
304 }
305}
306
308#ifdef YAZE_ENABLE_POLICY_FRAMEWORK
310 return;
311
312 const auto& p = *selected_proposal_;
313
314 // Only evaluate policies for pending proposals
316 return;
317 }
318
319 if (ImGui::CollapsingHeader(tr("Policy Status"),
320 ImGuiTreeNodeFlags_DefaultOpen)) {
321 auto& policy_eval = cli::PolicyEvaluator::GetInstance();
322
323 if (!policy_eval.IsEnabled()) {
324 ImGui::TextColored(gui::GetDisabledColor(),
325 ICON_MD_INFO " No policies configured");
326 ImGui::TextWrapped(
327 tr("Create .yaze/policies/agent.yaml to enable policy evaluation"));
328 return;
329 }
330
331 // Evaluate proposal against policies
332 auto policy_result = policy_eval.EvaluateProposal(p.id);
333
334 if (!policy_result.ok()) {
335 ImGui::TextColored(gui::GetErrorColor(),
336 ICON_MD_ERROR " Policy evaluation failed");
337 ImGui::TextWrapped("%s", policy_result.status().message().data());
338 return;
339 }
340
341 const auto& result = policy_result.value();
342
343 // Overall status
344 if (result.is_clean()) {
345 ImGui::TextColored(gui::GetSuccessColor(),
346 ICON_MD_VERIFIED " All policies passed");
347 } else if (result.passed) {
348 ImGui::TextColored(gui::GetWarningColor(),
349 ICON_MD_WARNING " Passed with warnings");
350 } else {
351 ImGui::TextColored(gui::GetErrorColor(),
352 ICON_MD_CANCEL " Critical violations found");
353 }
354
355 ImGui::Separator();
356
357 // Show critical violations
358 if (!result.critical_violations.empty()) {
359 ImGui::TextColored(gui::GetErrorColor(),
360 ICON_MD_BLOCK " Critical Violations:");
361 for (const auto& violation : result.critical_violations) {
362 ImGui::Bullet();
363 ImGui::TextWrapped("%s: %s", violation.policy_name.c_str(),
364 violation.message.c_str());
365 if (!violation.details.empty()) {
366 ImGui::Indent();
367 ImGui::TextColored(gui::GetDisabledColor(), "%s",
368 violation.details.c_str());
369 ImGui::Unindent();
370 }
371 }
372 ImGui::Separator();
373 }
374
375 // Show warnings
376 if (!result.warnings.empty()) {
377 ImGui::TextColored(gui::GetWarningColor(), ICON_MD_WARNING " Warnings:");
378 for (const auto& violation : result.warnings) {
379 ImGui::Bullet();
380 ImGui::TextWrapped("%s: %s", violation.policy_name.c_str(),
381 violation.message.c_str());
382 if (!violation.details.empty()) {
383 ImGui::Indent();
384 ImGui::TextColored(gui::GetDisabledColor(), "%s",
385 violation.details.c_str());
386 ImGui::Unindent();
387 }
388 }
389 ImGui::Separator();
390 }
391
392 // Show info messages
393 if (!result.info.empty()) {
394 ImGui::TextColored(gui::GetInfoColor(), ICON_MD_INFO " Information:");
395 for (const auto& violation : result.info) {
396 ImGui::Bullet();
397 ImGui::TextWrapped("%s: %s", violation.policy_name.c_str(),
398 violation.message.c_str());
399 }
400 }
401 }
402#endif // YAZE_ENABLE_POLICY_FRAMEWORK
403}
404
407 return;
408
409 const auto& p = *selected_proposal_;
411
412 if (is_pending) {
413 if (ImGui::Button(ICON_MD_CHECK " Accept", ImVec2(-1, 0))) {
415 action_feedback_.clear();
416 }
417
418 // Reject button (only for pending proposals)
419 if (ImGui::Button(ICON_MD_CLOSE " Reject", ImVec2(-1, 0))) {
420 confirm_action_ = "reject";
423 }
424 }
425
426 // Delete button (for all proposals)
427 if (ImGui::Button(ICON_MD_DELETE " Delete", ImVec2(-1, 0))) {
428 confirm_action_ = "delete";
431 }
432}
433
434void ProposalDrawer::FocusProposal(const std::string& proposal_id) {
435 visible_ = true;
436 selected_proposal_id_ = proposal_id;
437 selected_proposal_ = nullptr;
438 needs_refresh_ = true;
439}
440
442#ifdef Z3ED_AI
443 auto& registry = cli::ProposalRegistry::Instance();
444
445 std::optional<cli::ProposalRegistry::ProposalStatus> filter;
446 switch (status_filter_) {
449 break;
452 break;
455 break;
457 filter = std::nullopt;
458 break;
459 }
460
461 // Replacing the list invalidates pointers into the previous vector.
462 const std::string selected_id = selected_proposal_id_;
463 selected_proposal_ = nullptr;
464 proposals_ = registry.ListProposals(filter);
465 SelectProposal(selected_id);
467 selected_proposal_id_.clear();
468#endif
469}
470
471void ProposalDrawer::SelectProposal(const std::string& proposal_id) {
472 selected_proposal_id_ = proposal_id;
473 selected_proposal_ = nullptr;
474 diff_content_.clear();
475 log_content_.clear();
476
477 // Find the proposal in our list
478 for (auto& p : proposals_) {
479 if (p.id == proposal_id) {
481 break;
482 }
483 }
484}
485
486absl::Status ProposalDrawer::AcceptProposal(const std::string&) {
487 return absl::UnimplementedError(
488 "Proposal apply is unavailable. Preview the changes and apply them "
489 "through the normal editor.");
490}
491
492absl::Status ProposalDrawer::RejectProposal(const std::string& proposal_id) {
493#ifdef Z3ED_AI
494 auto& registry = cli::ProposalRegistry::Instance();
495 auto status = registry.UpdateStatus(
497
498 needs_refresh_ = true;
499 return status;
500#else
501 return absl::UnimplementedError("AI features disabled");
502#endif
503}
504
505absl::Status ProposalDrawer::DeleteProposal(const std::string& proposal_id) {
506#ifdef Z3ED_AI
507 auto& registry = cli::ProposalRegistry::Instance();
508 auto status = registry.RemoveProposal(proposal_id);
509
510 if (proposal_id == selected_proposal_id_) {
511 selected_proposal_id_.clear();
512 selected_proposal_ = nullptr;
513 diff_content_.clear();
514 log_content_.clear();
515 }
516
517 needs_refresh_ = true;
518 return status;
519#else
520 return absl::UnimplementedError("AI features disabled");
521#endif
522}
523
524} // namespace editor
525} // namespace yaze
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
Definition rom.h:52
static PolicyEvaluator & GetInstance()
static ProposalRegistry & Instance()
absl::Status AcceptProposal(const std::string &proposal_id)
absl::Status RejectProposal(const std::string &proposal_id)
cli::ProposalRegistry::ProposalMetadata * selected_proposal_
absl::Status DeleteProposal(const std::string &proposal_id)
void FocusProposal(const std::string &proposal_id)
std::vector< cli::ProposalRegistry::ProposalMetadata > proposals_
void SelectProposal(const std::string &proposal_id)
#define ICON_MD_BLOCK
Definition icons.h:269
#define ICON_MD_INFO
Definition icons.h:993
#define ICON_MD_CANCEL
Definition icons.h:364
#define ICON_MD_WARNING
Definition icons.h:2123
#define ICON_MD_CHECK
Definition icons.h:397
#define ICON_MD_REFRESH
Definition icons.h:1572
#define ICON_MD_VERIFIED
Definition icons.h:2055
#define ICON_MD_ERROR
Definition icons.h:686
#define ICON_MD_HOURGLASS_TOP
Definition icons.h:969
#define ICON_MD_CHECK_CIRCLE
Definition icons.h:400
#define ICON_MD_DELETE
Definition icons.h:530
#define ICON_MD_CLOSE
Definition icons.h:418
ImVec4 GetSuccessColor()
Definition ui_helpers.cc:49
ImVec4 GetDisabledColor()
Definition ui_helpers.cc:74
ImVec4 GetErrorColor()
Definition ui_helpers.cc:59
ImVec4 GetWarningColor()
Definition ui_helpers.cc:54
ImVec4 GetInfoColor()
Definition ui_helpers.cc:64