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"
18#include "imgui/imgui.h"
27 if (timestamp == absl::InfinitePast()) {
30 absl::Duration delta = absl::Now() - timestamp;
31 if (delta < absl::Seconds(60)) {
34 if (delta < absl::Minutes(60)) {
35 return absl::StrFormat(
"%dm ago",
36 static_cast<int>(delta / absl::Minutes(1)));
38 if (delta < absl::Hours(24)) {
39 return absl::StrFormat(
"%dh ago",
static_cast<int>(delta / absl::Hours(1)));
41 return absl::FormatTime(
"%b %d", timestamp, absl::LocalTimeZone());
45 int max_lines = 100) {
46 std::ifstream file(path);
47 if (!file.is_open()) {
51 std::ostringstream content;
54 while (std::getline(file, line) && line_count < max_lines) {
55 content << line <<
"\n";
59 if (line_count >= max_lines) {
60 content <<
"\n... (truncated)\n";
105 ? available_height - ImGui::GetCursorPosY() - detail_height
106 : ImGui::GetContentRegionAvail().y - detail_height;
111 {.bg = theme.panel_bg_darker});
123 ImGui::OpenPopup(
"Confirm Action");
126 if (ImGui::BeginPopupModal(
"Confirm Action",
nullptr,
127 ImGuiWindowFlags_AlwaysAutoResize)) {
128 ImGui::Text(
"Are you sure you want to %s proposal %s?",
132 if (ImGui::Button(
"Yes", ImVec2(80, 0))) {
141 ImGui::CloseCurrentPopup();
144 if (ImGui::Button(
"No", ImVec2(80, 0))) {
146 ImGui::CloseCurrentPopup();
157 const char* filter_labels[] = {
"All",
"Pending",
"Accepted",
"Rejected"};
158 int current_filter =
static_cast<int>(proposal_state.filter_mode);
171 for (
int i = 0; i < 4; ++i) {
174 bool selected = (current_filter == i);
175 std::optional<gui::StyleColorGuard> filter_guard;
177 filter_guard.emplace(ImGuiCol_Button,
178 ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]);
180 if (ImGui::SmallButton(filter_labels[i])) {
191 ImGui::TextDisabled(
"No proposals found");
204 bool matches =
false;
205 switch (filter_mode) {
207 matches = (proposal.status ==
211 matches = (proposal.status ==
215 matches = (proposal.status ==
233 ImGui::PushID(proposal.
id.c_str());
236 if (ImGui::Selectable(
"##row", is_selected,
237 ImGuiSelectableFlags_SpanAllColumns |
238 ImGuiSelectableFlags_AllowOverlap,
252 ImGui::Text(
"%s", proposal.
id.c_str());
257 ImGui::Text(
"%s", proposal.
id.c_str());
258 ImGui::TextDisabled(
"%s", proposal.
description.empty()
263 ImGui::SameLine(ImGui::GetContentRegionAvail().x - 150.0f);
267 ImGui::TextDisabled(
"%s", FormatRelativeTime(proposal.
created_at).c_str());
268 ImGui::TextDisabled(
"%d bytes, %d cmds", proposal.
bytes_changed,
275 ImGui::SameLine(ImGui::GetContentRegionAvail().x -
334 ImGui::BeginChild(
"ProposalDetail", ImVec2(0, 0),
true);
341 ImGui::TextColored(status_color,
"(%s)",
353 if (ImGui::BeginTabItem(
"Diff")) {
357 if (ImGui::BeginTabItem(
"Log")) {
375 ImGui::TextDisabled(
"No diff available");
380 {.bg = theme.code_bg_color},
false,
381 ImGuiWindowFlags_HorizontalScrollbar);
386 while (std::getline(stream, line)) {
392 if (line[0] ==
'+') {
393 line_color = ImVec4(0.4f, 0.8f, 0.4f, 1.0f);
394 }
else if (line[0] ==
'-') {
395 line_color = ImVec4(0.8f, 0.4f, 0.4f, 1.0f);
396 }
else if (line[0] ==
'@') {
397 line_color = ImVec4(0.4f, 0.6f, 0.8f, 1.0f);
399 line_color = theme.text_secondary_color;
413 ImGui::TextDisabled(
"No log available");
418 {.bg = theme.code_bg_color},
false,
419 ImGuiWindowFlags_HorizontalScrollbar);
440 proposal_state.pending_proposals = 0;
441 proposal_state.accepted_proposals = 0;
442 proposal_state.rejected_proposals = 0;
447 ++proposal_state.pending_proposals;
450 ++proposal_state.accepted_proposals;
453 ++proposal_state.rejected_proposals;
499 if (p.id == proposal_id) {
525 return theme.status_warning;
527 return theme.status_success;
529 return theme.status_error;
531 return theme.text_secondary_color;
536 const std::string& proposal_id) {
554 absl::StrFormat(
"Failed to accept proposal: %s", status.message()),
562 const std::string& proposal_id) {
580 absl::StrFormat(
"Failed to reject proposal: %s", status.message()),
588 const std::string& proposal_id) {
607 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)
RAII guard for ImGui style colors.
RAII guard for ImGui child windows with optional styling.
#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)
void ColoredText(const char *text, const ImVec4 &color)
bool BeginThemedTabBar(const char *id, ImGuiTabBarFlags flags)
A stylized tab bar with "Mission Control" branding.
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