1#define IMGUI_DEFINE_MATH_OPERATORS
9#include "absl/strings/str_format.h"
10#include "absl/time/clock.h"
11#include "absl/time/time.h"
16#include "imgui/imgui.h"
24 if (timestamp == absl::InfinitePast()) {
27 absl::Duration delta = absl::Now() - timestamp;
28 if (delta < absl::Seconds(60)) {
31 if (delta < absl::Minutes(60)) {
32 return absl::StrFormat(
"%dm ago",
33 static_cast<int>(delta / absl::Minutes(1)));
35 if (delta < absl::Hours(24)) {
36 return absl::StrFormat(
"%dh ago",
static_cast<int>(delta / absl::Hours(1)));
38 return absl::FormatTime(
"%b %d", timestamp, absl::LocalTimeZone());
42 std::ifstream file(path);
43 if (!file.is_open()) {
47 std::ostringstream content;
50 while (std::getline(file, line) && line_count < max_lines) {
51 content << line <<
"\n";
55 if (line_count >= max_lines) {
56 content <<
"\n... (truncated)\n";
97 float list_height = available_height > 0
98 ? available_height - ImGui::GetCursorPosY() - detail_height
99 : ImGui::GetContentRegionAvail().y - detail_height;
102 ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.panel_bg_darker);
103 ImGui::BeginChild(
"ProposalList", ImVec2(0, list_height),
false);
106 ImGui::PopStyleColor();
116 ImGui::OpenPopup(
"Confirm Action");
119 if (ImGui::BeginPopupModal(
"Confirm Action",
nullptr,
120 ImGuiWindowFlags_AlwaysAutoResize)) {
121 ImGui::Text(
"Are you sure you want to %s proposal %s?",
125 if (ImGui::Button(
"Yes", ImVec2(80, 0))) {
134 ImGui::CloseCurrentPopup();
137 if (ImGui::Button(
"No", ImVec2(80, 0))) {
139 ImGui::CloseCurrentPopup();
149 const char* filter_labels[] = {
"All",
"Pending",
"Accepted",
"Rejected"};
150 int current_filter =
static_cast<int>(proposal_state.filter_mode);
163 for (
int i = 0; i < 4; ++i) {
164 if (i > 0) ImGui::SameLine();
165 bool selected = (current_filter == i);
167 ImGui::PushStyleColor(ImGuiCol_Button,
168 ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]);
170 if (ImGui::SmallButton(filter_labels[i])) {
171 proposal_state.filter_mode =
176 ImGui::PopStyleColor();
185 ImGui::TextDisabled(
"No proposals found");
198 bool matches =
false;
199 switch (filter_mode) {
201 matches = (proposal.status ==
205 matches = (proposal.status ==
209 matches = (proposal.status ==
215 if (!matches)
continue;
227 ImGui::PushID(proposal.
id.c_str());
230 if (ImGui::Selectable(
"##row", is_selected,
231 ImGuiSelectableFlags_SpanAllColumns |
232 ImGuiSelectableFlags_AllowOverlap,
246 ImGui::Text(
"%s", proposal.
id.c_str());
251 ImGui::Text(
"%s", proposal.
id.c_str());
252 ImGui::TextDisabled(
"%s", proposal.
description.empty()
257 ImGui::SameLine(ImGui::GetContentRegionAvail().x - 150.0f);
261 ImGui::TextDisabled(
"%s", FormatRelativeTime(proposal.
created_at).c_str());
262 ImGui::TextDisabled(
"%d bytes, %d cmds", proposal.
bytes_changed,
269 ImGui::SameLine(ImGui::GetContentRegionAvail().x - (
compact_mode_ ? 60.0f : 100.0f));
280 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
284 ImGui::PushStyleColor(ImGuiCol_Text, theme.status_success);
290 ImGui::PopStyleColor();
294 ImGui::PushStyleColor(ImGuiCol_Text, theme.status_error);
300 ImGui::PopStyleColor();
318 ImGui::PopStyleColor();
326 ImGui::BeginChild(
"ProposalDetail", ImVec2(0, 0),
true);
333 ImGui::TextColored(status_color,
"(%s)",
344 if (ImGui::BeginTabBar(
"DetailTabs")) {
345 if (ImGui::BeginTabItem(
"Diff")) {
349 if (ImGui::BeginTabItem(
"Log")) {
367 ImGui::TextDisabled(
"No diff available");
371 ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.code_bg_color);
372 ImGui::BeginChild(
"DiffContent", ImVec2(0, 0),
false,
373 ImGuiWindowFlags_HorizontalScrollbar);
378 while (std::getline(stream, line)) {
383 if (line[0] ==
'+') {
384 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.8f, 0.4f, 1.0f));
385 }
else if (line[0] ==
'-') {
386 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.8f, 0.4f, 0.4f, 1.0f));
387 }
else if (line[0] ==
'@') {
388 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.6f, 0.8f, 1.0f));
390 ImGui::PushStyleColor(ImGuiCol_Text, theme.text_secondary_color);
392 ImGui::TextUnformatted(line.c_str());
393 ImGui::PopStyleColor();
397 ImGui::PopStyleColor();
408 ImGui::TextDisabled(
"No log available");
412 ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.code_bg_color);
413 ImGui::BeginChild(
"LogContent", ImVec2(0, 0),
false,
414 ImGuiWindowFlags_HorizontalScrollbar);
417 ImGui::PopStyleColor();
435 proposal_state.pending_proposals = 0;
436 proposal_state.accepted_proposals = 0;
437 proposal_state.rejected_proposals = 0;
442 ++proposal_state.pending_proposals;
445 ++proposal_state.accepted_proposals;
448 ++proposal_state.rejected_proposals;
494 if (p.id == proposal_id) {
520 return theme.status_warning;
522 return theme.status_success;
524 return theme.status_error;
526 return theme.text_secondary_color;
531 const std::string& proposal_id) {
550 absl::StrFormat(
"Failed to accept proposal: %s", status.message()),
558 const std::string& proposal_id) {
577 absl::StrFormat(
"Failed to reject proposal: %s", status.message()),
585 const std::string& proposal_id) {
606 absl::StrFormat(
"Failed to delete proposal: %s", status.message()),
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
static ProposalRegistry & Instance()
absl::Status UpdateStatus(const std::string &proposal_id, ProposalStatus status)
absl::Status RemoveProposal(const std::string &proposal_id)
std::vector< ProposalMetadata > ListProposals(std::optional< ProposalStatus > filter_status=std::nullopt) const
std::string selected_proposal_id_
std::string confirm_action_
void SetToastManager(ToastManager *toast_manager)
Set toast manager for notifications.
int GetPendingCount() const
Get count of pending proposals.
void SelectProposal(const std::string &proposal_id)
const char * GetStatusIcon(cli::ProposalRegistry::ProposalStatus status) const
void DrawProposalRow(const cli::ProposalRegistry::ProposalMetadata &proposal)
ProposalCallbacks proposal_callbacks_
void DrawProposalList()
Draw just the proposal list (for custom layouts)
bool show_confirm_dialog_
void SetRom(Rom *rom)
Set ROM reference for proposal merging.
void Draw(float available_height=0.0f)
Draw the complete proposals panel.
absl::Status DeleteProposal(const std::string &proposal_id)
std::string diff_content_
ImVec4 GetStatusColor(cli::ProposalRegistry::ProposalStatus status) const
void SetContext(AgentUIContext *context)
Set the shared UI context.
std::vector< cli::ProposalRegistry::ProposalMetadata > proposals_
void FocusProposal(const std::string &proposal_id)
Focus a specific proposal by ID.
void DrawProposalDetail()
Draw the detail view for selected proposal.
const cli::ProposalRegistry::ProposalMetadata * selected_proposal_
void DrawQuickActions(const cli::ProposalRegistry::ProposalMetadata &proposal)
absl::Status AcceptProposal(const std::string &proposal_id)
AgentUIContext * context_
std::string confirm_proposal_id_
ToastManager * toast_manager_
absl::Status RejectProposal(const std::string &proposal_id)
void SetProposalCallbacks(const ProposalCallbacks &callbacks)
Set proposal callbacks.
void RefreshProposals()
Refresh the proposal list from registry.
Unified context for agent UI components.
ProposalState & proposal_state()
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
#define ICON_MD_CHECK_CIRCLE
bool StyledButton(const char *label, const ImVec4 &color, const ImVec2 &size)
const AgentUITheme & GetTheme()
void StatusBadge(const char *text, ButtonColor color)
std::string FormatRelativeTime(absl::Time timestamp)
std::string ReadFileContents(const std::filesystem::path &path, int max_lines=100)
Callbacks for proposal operations.
std::function< absl::Status(const std::string &) reject_proposal)
std::function< absl::Status(const std::string &) accept_proposal)
std::string focused_proposal_id