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"
15#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 int max_lines = 100) {
43 std::ifstream file(path);
44 if (!file.is_open()) {
48 std::ostringstream content;
51 while (std::getline(file, line) && line_count < max_lines) {
52 content << line <<
"\n";
56 if (line_count >= max_lines) {
57 content <<
"\n... (truncated)\n";
102 ? available_height - ImGui::GetCursorPosY() - detail_height
103 : ImGui::GetContentRegionAvail().y - detail_height;
106 ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.panel_bg_darker);
107 ImGui::BeginChild(
"ProposalList", ImVec2(0, list_height),
false);
110 ImGui::PopStyleColor();
120 ImGui::OpenPopup(
"Confirm Action");
123 if (ImGui::BeginPopupModal(
"Confirm Action",
nullptr,
124 ImGuiWindowFlags_AlwaysAutoResize)) {
125 ImGui::Text(
"Are you sure you want to %s proposal %s?",
129 if (ImGui::Button(
"Yes", ImVec2(80, 0))) {
138 ImGui::CloseCurrentPopup();
141 if (ImGui::Button(
"No", ImVec2(80, 0))) {
143 ImGui::CloseCurrentPopup();
154 const char* filter_labels[] = {
"All",
"Pending",
"Accepted",
"Rejected"};
155 int current_filter =
static_cast<int>(proposal_state.filter_mode);
168 for (
int i = 0; i < 4; ++i) {
171 bool selected = (current_filter == i);
173 ImGui::PushStyleColor(ImGuiCol_Button,
174 ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]);
176 if (ImGui::SmallButton(filter_labels[i])) {
181 ImGui::PopStyleColor();
190 ImGui::TextDisabled(
"No proposals found");
203 bool matches =
false;
204 switch (filter_mode) {
206 matches = (proposal.status ==
210 matches = (proposal.status ==
214 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 -
287 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
291 ImGui::PushStyleColor(ImGuiCol_Text, theme.status_success);
297 ImGui::PopStyleColor();
301 ImGui::PushStyleColor(ImGuiCol_Text, theme.status_error);
307 ImGui::PopStyleColor();
325 ImGui::PopStyleColor();
334 ImGui::BeginChild(
"ProposalDetail", ImVec2(0, 0),
true);
341 ImGui::TextColored(status_color,
"(%s)",
352 if (ImGui::BeginTabBar(
"DetailTabs")) {
353 if (ImGui::BeginTabItem(
"Diff")) {
357 if (ImGui::BeginTabItem(
"Log")) {
375 ImGui::TextDisabled(
"No diff available");
379 ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.code_bg_color);
380 ImGui::BeginChild(
"DiffContent", ImVec2(0, 0),
false,
381 ImGuiWindowFlags_HorizontalScrollbar);
386 while (std::getline(stream, line)) {
391 if (line[0] ==
'+') {
392 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.8f, 0.4f, 1.0f));
393 }
else if (line[0] ==
'-') {
394 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.8f, 0.4f, 0.4f, 1.0f));
395 }
else if (line[0] ==
'@') {
396 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.6f, 0.8f, 1.0f));
398 ImGui::PushStyleColor(ImGuiCol_Text, theme.text_secondary_color);
400 ImGui::TextUnformatted(line.c_str());
401 ImGui::PopStyleColor();
405 ImGui::PopStyleColor();
416 ImGui::TextDisabled(
"No log available");
420 ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.code_bg_color);
421 ImGui::BeginChild(
"LogContent", ImVec2(0, 0),
false,
422 ImGuiWindowFlags_HorizontalScrollbar);
425 ImGui::PopStyleColor();
443 proposal_state.pending_proposals = 0;
444 proposal_state.accepted_proposals = 0;
445 proposal_state.rejected_proposals = 0;
450 ++proposal_state.pending_proposals;
453 ++proposal_state.accepted_proposals;
456 ++proposal_state.rejected_proposals;
502 if (p.id == proposal_id) {
528 return theme.status_warning;
530 return theme.status_success;
532 return theme.status_error;
534 return theme.text_secondary_color;
539 const std::string& proposal_id) {
557 absl::StrFormat(
"Failed to accept proposal: %s", status.message()),
565 const std::string& proposal_id) {
583 absl::StrFormat(
"Failed to reject proposal: %s", status.message()),
591 const std::string& proposal_id) {
610 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