yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
agent_chat_widget.cc
Go to the documentation of this file.
1#define IMGUI_DEFINE_MATH_OPERATORS
2
4
5#include <algorithm>
6#include <cstdio>
7#include <cstdlib>
8#include <cstring>
9#include <fstream>
10#include <optional>
11#include <string>
12#include <utility>
13#include <vector>
14
15#include "absl/status/status.h"
16#include "absl/strings/match.h"
17#include "absl/strings/str_format.h"
18#include "absl/strings/str_join.h"
19#include "absl/time/clock.h"
20#include "absl/time/time.h"
21#include "app/core/project.h"
27#include "app/gui/icons.h"
28#include "app/rom.h"
29#include "imgui/imgui.h"
30#include "util/file_util.h"
31#include "util/platform_paths.h"
32
33#include <SDL.h>
34#include <filesystem>
35
36#if defined(YAZE_WITH_GRPC)
38#endif
39
40namespace {
41
42namespace fs = std::filesystem;
44
45std::filesystem::path ExpandUserPath(std::string path) {
46 if (!path.empty() && path.front() == '~') {
47 const char* home = nullptr;
48#ifdef _WIN32
49 home = std::getenv("USERPROFILE");
50#else
51 home = std::getenv("HOME");
52#endif
53 if (home != nullptr) {
54 path.replace(0, 1, home);
55 }
56 }
57 return std::filesystem::path(path);
58}
59
60std::filesystem::path ResolveHistoryPath(const std::string& session_id = "") {
62 if (!config_dir.ok()) {
63 // Fallback to a local directory if config can't be determined.
64 return fs::current_path() / ".yaze" / "agent" / "history" / (session_id.empty() ? "default.json" : session_id + ".json");
65 }
66
67 fs::path base = *config_dir;
68 if (base.empty()) {
69 base = ExpandUserPath(".yaze");
70 }
71 auto directory = base / "agent";
72
73 // If in a collaborative session, use shared history
74 if (!session_id.empty()) {
75 directory = directory / "sessions";
76 return directory / (session_id + "_history.json");
77 }
78
79 return directory / "chat_history.json";
80}
81
82void RenderTable(const ChatMessage::TableData& table_data) {
83 const int column_count = static_cast<int>(table_data.headers.size());
84 if (column_count <= 0) {
85 ImGui::TextDisabled("(empty)");
86 return;
87 }
88
89 if (ImGui::BeginTable("structured_table", column_count,
90 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
91 ImGuiTableFlags_SizingStretchProp)) {
92 for (const auto& header : table_data.headers) {
93 ImGui::TableSetupColumn(header.c_str());
94 }
95 ImGui::TableHeadersRow();
96
97 for (const auto& row : table_data.rows) {
98 ImGui::TableNextRow();
99 for (int col = 0; col < column_count; ++col) {
100 ImGui::TableSetColumnIndex(col);
101 if (col < static_cast<int>(row.size())) {
102 ImGui::TextWrapped("%s", row[col].c_str());
103 } else {
104 ImGui::TextUnformatted("-");
105 }
106 }
107 }
108 ImGui::EndTable();
109 }
110}
111
112} // namespace
113
114namespace yaze {
115namespace editor {
116
118 title_ = "Agent Chat";
119 memset(input_buffer_, 0, sizeof(input_buffer_));
120 history_path_ = ResolveHistoryPath();
123
124 // Initialize default session
125 if (chat_sessions_.empty()) {
126 chat_sessions_.emplace_back("default", "Main Session");
128 }
129}
130
132 // Track if we've already initialized labels for this ROM instance
133 static Rom* last_rom_initialized = nullptr;
134
136
137 // Only initialize labels ONCE per ROM instance
138 if (rom && rom->is_loaded() && rom->resource_label() && last_rom_initialized != rom) {
139 core::YazeProject project;
140 project.use_embedded_labels = true;
141
142 auto labels_status = project.InitializeEmbeddedLabels();
143
144 if (labels_status.ok()) {
145 rom->resource_label()->labels_ = project.resource_labels;
146 rom->resource_label()->labels_loaded_ = true;
147 last_rom_initialized = rom; // Mark as initialized
148
149 int total_count = 0;
150 for (const auto& [category, labels] : project.resource_labels) {
151 total_count += labels.size();
152 }
153
154 if (toast_manager_) {
156 absl::StrFormat(ICON_MD_CHECK_CIRCLE " %d labels ready for AI", total_count),
157 ToastType::kSuccess, 2.0f);
158 }
159 }
160 }
161}
162
164 toast_manager_ = toast_manager;
165}
166
174
176 chat_history_popup_ = popup;
177
179 return;
180
181 // Set up callback to open this chat window
183 [this]() { this->set_active(true); });
184
185 // Set up callback to send messages from popup
187 [this](const std::string& message) {
188 // Send message through the agent service
189 auto response = agent_service_.SendMessage(message);
190 HandleAgentResponse(response);
192 });
193
194 // Set up callback to capture snapshots from popup
197 std::filesystem::path output_path;
198 auto status = multimodal_callbacks_.capture_snapshot(&output_path);
199 if (status.ok()) {
201 multimodal_state_.last_updated = absl::Now();
202 if (toast_manager_) {
203 toast_manager_->Show(ICON_MD_PHOTO " Screenshot captured",
204 ToastType::kSuccess, 2.5f);
205 }
206 } else if (toast_manager_) {
208 absl::StrFormat("Capture failed: %s", status.message()),
209 ToastType::kError, 3.0f);
210 }
211 }
212 });
213
214 // Initial sync
216}
217
219 if (history_loaded_) {
220 return;
221 }
222 history_loaded_ = true;
223
224 std::error_code ec;
225 auto directory = history_path_.parent_path();
226 if (!directory.empty()) {
227 std::filesystem::create_directories(directory, ec);
228 if (ec) {
229 if (toast_manager_) {
230 toast_manager_->Show("Unable to prepare chat history directory",
231 ToastType::kError, 5.0f);
232 }
233 return;
234 }
235 }
236 if (!history_supported_) {
239 "Chat history requires gRPC/JSON support and is disabled",
240 ToastType::kWarning, 5.0f);
242 }
243 return;
244 }
245
246 absl::StatusOr<AgentChatHistoryCodec::Snapshot> result =
248 if (!result.ok()) {
249 if (result.status().code() == absl::StatusCode::kUnimplemented) {
250 history_supported_ = false;
253 "Chat history requires gRPC/JSON support and is disabled",
254 ToastType::kWarning, 5.0f);
256 }
257 return;
258 }
259
260 if (toast_manager_) {
261 toast_manager_->Show(absl::StrFormat("Failed to load chat history: %s",
262 result.status().ToString()),
263 ToastType::kError, 6.0f);
264 }
265 return;
266 }
267
268 AgentChatHistoryCodec::Snapshot snapshot = std::move(result.value());
269
270 if (!snapshot.history.empty()) {
271 agent_service_.ReplaceHistory(std::move(snapshot.history));
274 history_dirty_ = false;
275 last_persist_time_ = absl::Now();
276 if (toast_manager_) {
277 toast_manager_->Show("Restored chat history", ToastType::kInfo, 3.5f);
278 }
279 }
280
289 }
290
294}
295
298 return;
299 }
300
301 if (!history_supported_) {
302 history_dirty_ = false;
305 "Chat history requires gRPC/JSON support and is disabled",
306 ToastType::kWarning, 5.0f);
308 }
309 return;
310 }
311
313 snapshot.history = agent_service_.GetHistory();
317
318 // Sync to popup when persisting
325
326 absl::Status status = AgentChatHistoryCodec::Save(history_path_, snapshot);
327 if (!status.ok()) {
328 if (status.code() == absl::StatusCode::kUnimplemented) {
329 history_supported_ = false;
332 "Chat history requires gRPC/JSON support and is disabled",
333 ToastType::kWarning, 5.0f);
335 }
336 history_dirty_ = false;
337 return;
338 }
339
340 if (toast_manager_) {
341 toast_manager_->Show(absl::StrFormat("Failed to persist chat history: %s",
342 status.ToString()),
343 ToastType::kError, 6.0f);
344 }
345 return;
346 }
347
348 history_dirty_ = false;
349 last_persist_time_ = absl::Now();
350}
351
353 int total = 0;
354 const auto& history = agent_service_.GetHistory();
355 for (const auto& message : history) {
356 if (message.metrics.has_value()) {
357 total = std::max(total, message.metrics->total_proposals);
358 } else if (message.proposal.has_value()) {
359 ++total;
360 }
361 }
362 return total;
363}
364
365void AgentChatWidget::FocusProposalDrawer(const std::string& proposal_id) {
366 if (proposal_id.empty()) {
367 return;
368 }
369 if (proposal_drawer_) {
370 proposal_drawer_->FocusProposal(proposal_id);
371 }
372 pending_focus_proposal_id_ = proposal_id;
373}
374
375void AgentChatWidget::NotifyProposalCreated(const ChatMessage& msg,
376 int new_total_proposals) {
377 int delta = std::max(1, new_total_proposals - last_proposal_count_);
378 if (toast_manager_) {
379 if (msg.proposal.has_value()) {
380 const auto& proposal = *msg.proposal;
382 absl::StrFormat("%s Proposal %s ready (%d change%s)", ICON_MD_PREVIEW,
383 proposal.id, proposal.change_count,
384 proposal.change_count == 1 ? "" : "s"),
385 ToastType::kSuccess, 5.5f);
386 } else {
388 absl::StrFormat("%s %d new proposal%s queued", ICON_MD_PREVIEW, delta,
389 delta == 1 ? "" : "s"),
390 ToastType::kSuccess, 4.5f);
391 }
392 }
393
394 if (msg.proposal.has_value()) {
395 FocusProposalDrawer(msg.proposal->id);
396 }
397}
398
400 const absl::StatusOr<ChatMessage>& response) {
401 if (!response.ok()) {
402 if (toast_manager_) {
404 absl::StrFormat("Agent error: %s", response.status().message()),
405 ToastType::kError, 5.0f);
406 }
407 return;
408 }
409
410 const ChatMessage& message = response.value();
411 int total = CountKnownProposals();
412 if (message.metrics.has_value()) {
413 total = std::max(total, message.metrics->total_proposals);
414 }
415
416 if (total > last_proposal_count_) {
417 NotifyProposalCreated(message, total);
418 }
420
421 // Sync history to popup after response
423}
424
425void AgentChatWidget::RenderMessage(const ChatMessage& msg, int index) {
426 // Skip internal messages (tool results meant only for the LLM)
427 if (msg.is_internal) {
428 return;
429 }
430
431 ImGui::PushID(index);
432 const auto& theme = AgentUI::GetTheme();
433
434 const bool from_user = (msg.sender == ChatMessage::Sender::kUser);
435 const ImVec4 header_color = from_user ? theme.user_message_color : theme.agent_message_color;
436 const char* header_label = from_user ? "You" : "Agent";
437
438 ImGui::TextColored(header_color, "%s", header_label);
439
440 ImGui::SameLine();
441 ImGui::TextDisabled(
442 "%s", absl::FormatTime("%H:%M:%S", msg.timestamp, absl::LocalTimeZone())
443 .c_str());
444
445 // Add copy button for all messages
446 ImGui::SameLine();
447 ImGui::PushStyleColor(ImGuiCol_Button, theme.button_copy);
448 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, theme.button_copy_hover);
449 if (ImGui::SmallButton(ICON_MD_CONTENT_COPY)) {
450 std::string copy_text = msg.message;
451 if (copy_text.empty() && msg.json_pretty.has_value()) {
452 copy_text = *msg.json_pretty;
453 }
454 ImGui::SetClipboardText(copy_text.c_str());
455 if (toast_manager_) {
456 toast_manager_->Show("Message copied", ToastType::kSuccess, 2.0f);
457 }
458 }
459 ImGui::PopStyleColor(2);
460 if (ImGui::IsItemHovered()) {
461 ImGui::SetTooltip("Copy to clipboard");
462 }
463
464 ImGui::Indent();
465
466 if (msg.table_data.has_value()) {
467 RenderTable(*msg.table_data);
468 } else if (msg.json_pretty.has_value()) {
469 // Don't show JSON as a message - it's internal structure
470 const auto& theme = AgentUI::GetTheme();
471 ImGui::PushStyleColor(ImGuiCol_Text, theme.json_text_color);
472 ImGui::TextDisabled(ICON_MD_DATA_OBJECT " (Structured response)");
473 ImGui::PopStyleColor();
474 } else {
475 ImGui::TextWrapped("%s", msg.message.c_str());
476 }
477
478 if (msg.proposal.has_value()) {
479 RenderProposalQuickActions(msg, index);
480 }
481
482 ImGui::Unindent();
483 ImGui::Spacing();
484 ImGui::Separator();
485 ImGui::PopID();
486}
487
489 int index) {
490 if (!msg.proposal.has_value()) {
491 return;
492 }
493
494 const auto& theme = AgentUI::GetTheme();
495 const auto& proposal = *msg.proposal;
496 ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.proposal_panel_bg);
497 ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 4.0f);
498 ImGui::BeginChild(absl::StrFormat("proposal_panel_%d", index).c_str(),
499 ImVec2(0, ImGui::GetFrameHeight() * 3.2f), true,
500 ImGuiWindowFlags_None);
501
502 ImGui::TextColored(theme.proposal_accent, "%s Proposal %s",
503 ICON_MD_PREVIEW, proposal.id.c_str());
504 ImGui::Text("Changes: %d", proposal.change_count);
505 ImGui::Text("Commands: %d", proposal.executed_commands);
506
507 if (!proposal.sandbox_rom_path.empty()) {
508 ImGui::TextDisabled("Sandbox: %s",
509 proposal.sandbox_rom_path.string().c_str());
510 }
511 if (!proposal.proposal_json_path.empty()) {
512 ImGui::TextDisabled("Manifest: %s",
513 proposal.proposal_json_path.string().c_str());
514 }
515
516 if (ImGui::SmallButton(
517 absl::StrFormat("%s Review", ICON_MD_VISIBILITY).c_str())) {
518 FocusProposalDrawer(proposal.id);
519 }
520 ImGui::SameLine();
521 if (ImGui::SmallButton(
522 absl::StrFormat("%s Copy ID", ICON_MD_CONTENT_COPY).c_str())) {
523 ImGui::SetClipboardText(proposal.id.c_str());
524 if (toast_manager_) {
525 toast_manager_->Show("Proposal ID copied", ToastType::kInfo, 2.5f);
526 }
527 }
528
529 ImGui::EndChild();
530 ImGui::PopStyleVar();
531 ImGui::PopStyleColor();
532}
533
535 const auto& theme = AgentUI::GetTheme();
536 const auto& history = agent_service_.GetHistory();
537 float reserved_height = ImGui::GetFrameHeightWithSpacing() * 4.0f;
538 reserved_height += 100.0f; // Reduced to 100 for much taller chat area
539
540 // Styled chat history container
541 ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.code_bg_color);
542 if (ImGui::BeginChild("History", ImVec2(0, -reserved_height), true,
543 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
544 if (history.empty()) {
545 // Centered empty state
546 ImVec2 text_size = ImGui::CalcTextSize("No messages yet");
547 ImVec2 avail = ImGui::GetContentRegionAvail();
548 ImGui::SetCursorPosX((avail.x - text_size.x) / 2);
549 ImGui::SetCursorPosY((avail.y - text_size.y) / 2);
550 ImGui::TextDisabled(ICON_MD_CHAT " No messages yet");
551 ImGui::SetCursorPosX(
552 (avail.x - ImGui::CalcTextSize("Start typing below to begin").x) / 2);
553 ImGui::TextDisabled("Start typing below to begin");
554 } else {
555 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
556 ImVec2(8, 12)); // More spacing between messages
557 for (size_t index = 0; index < history.size(); ++index) {
558 RenderMessage(history[index], static_cast<int>(index));
559 }
560 ImGui::PopStyleVar();
561 }
562
563 if (history.size() > last_history_size_) {
564 ImGui::SetScrollHereY(1.0f);
565 }
566 }
567 ImGui::EndChild();
568 ImGui::PopStyleColor(); // Pop the color we pushed at line 531
569 last_history_size_ = history.size();
570}
571
573 const auto& theme = AgentUI::GetTheme();
574
575 ImGui::Separator();
576 ImGui::TextColored(theme.command_text_color, ICON_MD_EDIT " Message:");
577
578 bool submitted = ImGui::InputTextMultiline(
579 "##agent_input", input_buffer_, sizeof(input_buffer_), ImVec2(-1, 60.0f),
580 ImGuiInputTextFlags_None);
581
582 // Check for Ctrl+Enter to send (Enter alone adds newline)
583 bool send = false;
584 if (ImGui::IsItemFocused()) {
585 if (ImGui::IsKeyPressed(ImGuiKey_Enter) && ImGui::GetIO().KeyCtrl) {
586 send = true;
587 }
588 }
589
590 ImGui::Spacing();
591
592 // Send button row
593 ImGui::PushStyleColor(ImGuiCol_Button, theme.provider_gemini);
594 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
595 ImVec4(theme.provider_gemini.x * 1.2f, theme.provider_gemini.y * 1.1f,
596 theme.provider_gemini.z, theme.provider_gemini.w));
597 if (ImGui::Button(absl::StrFormat("%s Send", ICON_MD_SEND).c_str(),
598 ImVec2(140, 0)) ||
599 send) {
600 if (std::strlen(input_buffer_) > 0 && !waiting_for_response_) {
601 history_dirty_ = true;
605 memset(input_buffer_, 0, sizeof(input_buffer_));
606
607 // Send in next frame to avoid blocking
608 // For now, send synchronously but show thinking indicator
610 HandleAgentResponse(response);
612 waiting_for_response_ = false;
613 ImGui::SetKeyboardFocusHere(-1);
614 }
615 }
616 ImGui::PopStyleColor(2);
617 if (ImGui::IsItemHovered()) {
618 ImGui::SetTooltip("Send message (Ctrl+Enter)");
619 }
620
621 ImGui::SameLine();
622 ImGui::TextDisabled(ICON_MD_INFO " Ctrl+Enter: send • Enter: newline");
623
624 // Action buttons row below
625 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.5f, 0.0f, 0.7f));
626 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
627 ImVec4(1.0f, 0.843f, 0.0f, 0.9f));
628 if (ImGui::SmallButton(ICON_MD_DELETE_FOREVER " Clear")) {
630 if (toast_manager_) {
631 toast_manager_->Show("Conversation cleared", ToastType::kSuccess);
632 }
633 }
634 ImGui::PopStyleColor(2);
635 if (ImGui::IsItemHovered()) {
636 ImGui::SetTooltip("Clear all messages from conversation");
637 }
638
639 ImGui::SameLine();
640 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.35f, 0.6f, 0.7f));
641 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
642 ImVec4(0.502f, 0.0f, 0.502f, 0.9f));
643 if (ImGui::SmallButton(ICON_MD_PREVIEW " Proposals")) {
644 if (proposal_drawer_) {
645 // Focus proposal drawer
646 }
647 }
648 ImGui::PopStyleColor(2);
649 if (ImGui::IsItemHovered()) {
650 ImGui::SetTooltip("View code proposals");
651 }
652
653 // Multimodal Vision controls integrated
654 ImGui::SameLine();
655 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.5f, 0.7f, 0.7f));
656 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
657 ImVec4(0.196f, 0.6f, 0.8f, 0.9f));
658 if (ImGui::SmallButton(ICON_MD_PHOTO_CAMERA " Capture")) {
659 // Quick capture with current mode
661 std::filesystem::path captured_path;
662 auto status = multimodal_callbacks_.capture_snapshot(&captured_path);
663 if (status.ok()) {
664 multimodal_state_.last_capture_path = captured_path;
665 if (toast_manager_) {
666 toast_manager_->Show("Screenshot captured", ToastType::kSuccess);
667 }
668 } else if (toast_manager_) {
670 absl::StrFormat("Capture failed: %s", status.message()),
672 }
673 }
674 }
675 ImGui::PopStyleColor(2);
676 if (ImGui::IsItemHovered()) {
677 ImGui::SetTooltip("Capture screenshot for vision analysis");
678 }
679
680 ImGui::SameLine();
681 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.5f, 0.3f, 0.3f, 0.7f));
682 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
683 ImVec4(0.863f, 0.078f, 0.235f, 0.9f));
684 if (ImGui::SmallButton(ICON_MD_STOP " Stop")) {
685 // Stop generation (if implemented)
686 if (toast_manager_) {
687 toast_manager_->Show("Stop not yet implemented", ToastType::kWarning);
688 }
689 }
690 ImGui::PopStyleColor(2);
691 if (ImGui::IsItemHovered()) {
692 ImGui::SetTooltip("Stop current generation");
693 }
694
695 ImGui::SameLine();
696 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.5f, 0.3f, 0.7f));
697 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
698 ImVec4(0.133f, 0.545f, 0.133f, 0.9f));
699 if (ImGui::SmallButton(ICON_MD_SAVE " Export")) {
700 // Export conversation
701 if (toast_manager_) {
702 toast_manager_->Show("Export not yet implemented", ToastType::kWarning);
703 }
704 }
705 ImGui::PopStyleColor(2);
706 if (ImGui::IsItemHovered()) {
707 ImGui::SetTooltip("Export conversation history");
708 }
709
710 // Vision prompt (inline when image captured)
711 if (multimodal_state_.last_capture_path.has_value()) {
712 ImGui::Spacing();
713 ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f),
714 ICON_MD_IMAGE " Vision prompt:");
715 ImGui::SetNextItemWidth(-200);
716 ImGui::InputTextWithHint(
717 "##quick_vision_prompt", "Ask about the screenshot...",
719 ImGui::SameLine();
720 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.5f, 0.7f, 0.8f));
721 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
722 ImVec4(0.196f, 0.6f, 0.8f, 1.0f));
723 if (ImGui::Button(ICON_MD_SEND " Analyze##vision_send", ImVec2(180, 0))) {
726 std::string prompt = multimodal_prompt_buffer_;
729 if (status.ok() && toast_manager_) {
730 toast_manager_->Show("Vision analysis requested",
732 }
733 }
734 }
735 ImGui::PopStyleColor(2);
736 }
737}
738
740 if (!active_) {
741 return;
742 }
743
745
746 // Poll for new messages in collaborative sessions
748
749 ImGui::SetNextWindowSize(ImVec2(1400, 1000), ImGuiCond_FirstUseEver);
750 ImGui::Begin(title_.c_str(), &active_, ImGuiWindowFlags_MenuBar);
751
752 // Simplified menu bar
753 if (ImGui::BeginMenuBar()) {
754 if (ImGui::BeginMenu(ICON_MD_MENU " Actions")) {
755 if (ImGui::MenuItem(ICON_MD_DELETE_FOREVER " Clear History")) {
758 if (toast_manager_) {
759 toast_manager_->Show("Chat history cleared", ToastType::kInfo, 2.5f);
760 }
761 }
762 ImGui::Separator();
763 if (ImGui::MenuItem(ICON_MD_REFRESH " Reset Conversation")) {
766 if (toast_manager_) {
767 toast_manager_->Show("Conversation reset", ToastType::kInfo, 2.5f);
768 }
769 }
770 ImGui::Separator();
771 if (ImGui::MenuItem(ICON_MD_SAVE " Export History")) {
772 if (toast_manager_) {
773 toast_manager_->Show("Export not yet implemented",
775 }
776 }
777 ImGui::Separator();
778 if (ImGui::MenuItem(ICON_MD_ADD " New Session Tab")) {
779 // Create new session
780 if (!chat_sessions_.empty()) {
781 ChatSession new_session(
782 absl::StrFormat("session_%d", chat_sessions_.size()),
783 absl::StrFormat("Session %d", chat_sessions_.size() + 1));
784 chat_sessions_.push_back(std::move(new_session));
786 if (toast_manager_) {
787 toast_manager_->Show("New session created", ToastType::kSuccess);
788 }
789 }
790 }
791 ImGui::EndMenu();
792 }
793
794 // Session tabs in menu bar (if multiple sessions)
795 if (!chat_sessions_.empty() && chat_sessions_.size() > 1) {
796 ImGui::Separator();
797 for (size_t i = 0; i < chat_sessions_.size(); ++i) {
798 bool is_active = (i == active_session_index_);
799 if (is_active) {
800 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2f, 0.8f, 0.4f, 1.0f));
801 }
802 if (ImGui::MenuItem(chat_sessions_[i].name.c_str(), nullptr,
803 is_active)) {
805 history_loaded_ = false; // Trigger reload
807 }
808 if (is_active) {
809 ImGui::PopStyleColor();
810 }
811 }
812 }
813
814 ImGui::EndMenuBar();
815 }
816
817 // Update reactive status color
819 ? ImVec4(0.133f, 0.545f, 0.133f, 1.0f)
820 : ImVec4(0.6f, 0.6f, 0.6f, 1.0f);
821
822 // Connection status bar at top (taller for better visibility)
823 ImDrawList* draw_list = ImGui::GetWindowDrawList();
824 ImVec2 bar_start = ImGui::GetCursorScreenPos();
825 ImVec2 bar_size(ImGui::GetContentRegionAvail().x, 60); // Increased from 55
826
827 // Gradient background
828 ImU32 color_top = ImGui::GetColorU32(ImVec4(0.18f, 0.22f, 0.28f, 1.0f));
829 ImU32 color_bottom = ImGui::GetColorU32(ImVec4(0.12f, 0.16f, 0.22f, 1.0f));
830 draw_list->AddRectFilledMultiColor(
831 bar_start, ImVec2(bar_start.x + bar_size.x, bar_start.y + bar_size.y),
832 color_top, color_top, color_bottom, color_bottom);
833
834 // Colored accent bar based on provider
835 ImVec4 accent_color = (agent_config_.ai_provider == "ollama")
836 ? ImVec4(0.2f, 0.8f, 0.4f, 1.0f)
837 : (agent_config_.ai_provider == "gemini")
838 ? ImVec4(0.196f, 0.6f, 0.8f, 1.0f)
839 : ImVec4(0.6f, 0.6f, 0.6f, 1.0f);
840 draw_list->AddRectFilled(bar_start,
841 ImVec2(bar_start.x + bar_size.x, bar_start.y + 3),
842 ImGui::GetColorU32(accent_color));
843
844 ImGui::BeginChild("AgentChat_ConnectionBar", bar_size, false,
845 ImGuiWindowFlags_NoScrollbar);
846 ImGui::PushID("ConnectionBar");
847 {
848 // Center content vertically in the 55px bar
849 float content_height = ImGui::GetFrameHeight();
850 float vertical_padding = (55.0f - content_height) / 2.0f;
851 ImGui::SetCursorPosY(ImGui::GetCursorPosY() + vertical_padding);
852
853 // Compact single row layout (restored)
854 ImGui::TextColored(accent_color, ICON_MD_SMART_TOY);
855 ImGui::SameLine();
856 ImGui::SetNextItemWidth(95);
857 const char* providers[] = {"Mock", "Ollama", "Gemini"};
858 int current_provider = (agent_config_.ai_provider == "mock") ? 0
859 : (agent_config_.ai_provider == "ollama") ? 1
860 : 2;
861 if (ImGui::Combo("##main_provider", &current_provider, providers, 3)) {
862 agent_config_.ai_provider = (current_provider == 0) ? "mock"
863 : (current_provider == 1) ? "ollama"
864 : "gemini";
865 // Auto-populate default models
866 if (agent_config_.ai_provider == "ollama") {
867 strncpy(agent_config_.model_buffer, "qwen2.5-coder:7b",
868 sizeof(agent_config_.model_buffer) - 1);
870 } else if (agent_config_.ai_provider == "gemini") {
871 strncpy(agent_config_.model_buffer, "gemini-2.5-flash",
872 sizeof(agent_config_.model_buffer) - 1);
874 }
875 }
876
877 ImGui::SameLine();
878 if (agent_config_.ai_provider != "mock") {
879 ImGui::SetNextItemWidth(150);
880 ImGui::InputTextWithHint("##main_model", "Model name...",
883 if (ImGui::IsItemHovered()) {
884 ImGui::SetTooltip("AI model name");
885 }
886 }
887
888 // Gemini API key input
889 ImGui::SameLine();
890 if (agent_config_.ai_provider == "gemini") {
891 ImGui::SetNextItemWidth(200);
892 if (ImGui::InputTextWithHint("##main_api_key",
893 "API Key (or load from env)...",
896 ImGuiInputTextFlags_Password)) {
898 }
899 if (ImGui::IsItemHovered()) {
900 ImGui::SetTooltip("Gemini API Key (hidden)");
901 }
902
903 ImGui::SameLine();
904 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.5f, 0.7f, 0.7f));
905 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
906 ImVec4(0.196f, 0.6f, 0.8f, 1.0f));
907 if (ImGui::SmallButton(ICON_MD_REFRESH)) {
908 const char* gemini_key = nullptr;
909#ifdef _WIN32
910 char* env_key = nullptr;
911 size_t len = 0;
912 if (_dupenv_s(&env_key, &len, "GEMINI_API_KEY") == 0 &&
913 env_key != nullptr) {
914 strncpy(agent_config_.gemini_key_buffer, env_key,
915 sizeof(agent_config_.gemini_key_buffer) - 1);
917 free(env_key);
918 }
919#else
920 gemini_key = std::getenv("GEMINI_API_KEY");
921 if (gemini_key) {
922 strncpy(agent_config_.gemini_key_buffer, gemini_key,
923 sizeof(agent_config_.gemini_key_buffer) - 1);
924 agent_config_.gemini_api_key = gemini_key;
925 }
926#endif
928 toast_manager_->Show("Key loaded", ToastType::kSuccess, 1.5f);
929 }
930 }
931 ImGui::PopStyleColor(2);
932 if (ImGui::IsItemHovered()) {
933 ImGui::SetTooltip("Load from GEMINI_API_KEY");
934 }
935 }
936
937 ImGui::SameLine();
939 if (ImGui::IsItemHovered()) {
940 ImGui::SetTooltip("Show reasoning");
941 }
942
943 // Session management button
944 ImGui::SameLine();
945 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.4f, 0.6f, 0.7f));
946 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
947 ImVec4(0.5f, 0.5f, 0.7f, 0.9f));
948 if (ImGui::SmallButton(
949 absl::StrFormat("%s %d", ICON_MD_TAB,
950 static_cast<int>(chat_sessions_.size()))
951 .c_str())) {
952 ImGui::OpenPopup("SessionsPopup");
953 }
954 ImGui::PopStyleColor(2);
955 if (ImGui::IsItemHovered()) {
956 ImGui::SetTooltip("Manage chat sessions");
957 }
958
959 // Sessions popup
960 if (ImGui::BeginPopup("SessionsPopup")) {
961 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
962 ICON_MD_TAB " Chat Sessions");
963 ImGui::Separator();
964
965 if (ImGui::Button(ICON_MD_ADD " New Session", ImVec2(200, 0))) {
966 std::string session_id = absl::StrFormat(
967 "session_%d", static_cast<int>(chat_sessions_.size() + 1));
968 std::string session_name = absl::StrFormat(
969 "Chat %d", static_cast<int>(chat_sessions_.size() + 1));
970 chat_sessions_.emplace_back(session_id, session_name);
971 active_session_index_ = static_cast<int>(chat_sessions_.size() - 1);
972 if (toast_manager_) {
973 toast_manager_->Show("New session created", ToastType::kSuccess);
974 }
975 ImGui::CloseCurrentPopup();
976 }
977
978 if (!chat_sessions_.empty()) {
979 ImGui::Spacing();
980 ImGui::TextDisabled("Active Sessions:");
981 ImGui::Separator();
982 for (size_t i = 0; i < chat_sessions_.size(); ++i) {
983 ImGui::PushID(static_cast<int>(i));
984 bool is_active = (active_session_index_ == static_cast<int>(i));
985 if (ImGui::Selectable(absl::StrFormat("%s %s%s", ICON_MD_CHAT,
986 chat_sessions_[i].name,
987 is_active ? " (active)" : "")
988 .c_str(),
989 is_active)) {
990 active_session_index_ = static_cast<int>(i);
991 ImGui::CloseCurrentPopup();
992 }
993 ImGui::PopID();
994 }
995 }
996 ImGui::EndPopup();
997 }
998
999 // Session status (right side)
1001 ImGui::SameLine(ImGui::GetContentRegionAvail().x - 25);
1003 }
1004 }
1005 ImGui::PopID();
1006 ImGui::EndChild();
1007
1008 ImGui::Spacing();
1009
1010 // Main layout: Chat area (left, 70%) + Control panels (right, 30%)
1011 if (ImGui::BeginTable("AgentChat_MainLayout", 2,
1012 ImGuiTableFlags_Resizable |
1013 ImGuiTableFlags_Reorderable |
1014 ImGuiTableFlags_BordersInnerV |
1015 ImGuiTableFlags_ContextMenuInBody)) {
1016 ImGui::TableSetupColumn("Chat", ImGuiTableColumnFlags_WidthStretch, 0.7f);
1017 ImGui::TableSetupColumn("Controls", ImGuiTableColumnFlags_WidthStretch,
1018 0.3f);
1019 ImGui::TableHeadersRow();
1020 ImGui::TableNextRow();
1021
1022 // LEFT: Chat area with ROM sync below
1023 ImGui::TableSetColumnIndex(0);
1024 ImGui::PushID("ChatColumn");
1025
1026 // Chat history and input (main area)
1027 RenderHistory();
1029
1030 // ROM Sync inline below chat (when active)
1033 ImGui::Spacing();
1034 ImGui::Separator();
1035 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 4));
1036 ImGui::TextColored(ImVec4(1.0f, 0.647f, 0.0f, 1.0f),
1037 ICON_MD_SYNC " ROM Sync");
1038 ImGui::SameLine();
1039 if (!rom_sync_state_.current_rom_hash.empty()) {
1040 ImGui::TextColored(
1041 ImVec4(0.6f, 0.6f, 0.6f, 1.0f), "%s",
1042 rom_sync_state_.current_rom_hash.substr(0, 12).c_str());
1043 }
1044 ImGui::PopStyleVar();
1045 }
1046
1047 ImGui::PopID();
1048
1049 // RIGHT: Control panels (collapsible sections)
1050 ImGui::TableSetColumnIndex(1);
1051 ImGui::PushID("ControlsColumn");
1052 ImGui::BeginChild("AgentChat_ControlPanels", ImVec2(0, 0), false);
1053
1054 // All panels always visible (dense layout)
1055 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
1056 ImVec2(6, 6)); // Tighter spacing
1057 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
1058 ImVec2(4, 3)); // Compact padding
1059
1060 if (ImGui::BeginTable("##commands_and_multimodal", 2)) {
1061 ImGui::TableSetupColumn("Commands", ImGuiTableColumnFlags_WidthFixed, 180);
1062 ImGui::TableSetupColumn("Multimodal", ImGuiTableColumnFlags_WidthFixed, ImGui::GetContentRegionAvail().x - 180);
1063 ImGui::TableNextRow();
1064 ImGui::TableSetColumnIndex(0);
1066 ImGui::TableSetColumnIndex(1);
1068 ImGui::EndTable();
1069 }
1070
1077
1078 ImGui::PopStyleVar(2);
1079
1080 ImGui::EndChild();
1081 ImGui::PopID();
1082
1083 ImGui::EndTable();
1084 }
1085
1086 ImGui::End();
1087}
1088
1090 ImGui::PushID("CollabPanel");
1091
1092 // Tighter style for more content
1093 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 3));
1094 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3, 2));
1095
1096 // Update reactive status color
1097 const bool connected = collaboration_state_.active;
1098 collaboration_status_color_ = connected ? ImVec4(0.133f, 0.545f, 0.133f, 1.0f)
1099 : ImVec4(0.6f, 0.6f, 0.6f, 1.0f);
1100
1101 // Always visible (no collapsing header)
1102 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.12f, 0.14f, 0.18f, 0.95f));
1103 ImGui::BeginChild("CollabPanel", ImVec2(0, 140), true); // reduced height
1104 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_PEOPLE " Collaboration");
1105 ImGui::SameLine();
1106 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_SETTINGS_ETHERNET " Mode:");
1107 ImGui::SameLine();
1108 ImGui::RadioButton(ICON_MD_FOLDER " Local##collab_mode_local",
1109 reinterpret_cast<int*>(&collaboration_state_.mode),
1110 static_cast<int>(CollaborationMode::kLocal));
1111 ImGui::SameLine();
1112 ImGui::RadioButton(ICON_MD_WIFI " Network##collab_mode_network",
1113 reinterpret_cast<int*>(&collaboration_state_.mode),
1114 static_cast<int>(CollaborationMode::kNetwork));
1115
1116 // Main content in table layout (fixed size to prevent auto-resize)
1117 if (ImGui::BeginTable("Collab_MainTable", 2, ImGuiTableFlags_BordersInnerV)) {
1118 ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 150);
1119 ImGui::TableSetupColumn("Controls", ImGuiTableColumnFlags_WidthFixed,
1120 ImGui::GetContentRegionAvail().x - 150);
1121 ImGui::TableNextRow();
1122
1123 // LEFT COLUMN: Session Details
1124 ImGui::TableSetColumnIndex(0);
1125 ImGui::BeginGroup();
1126 ImGui::PushID("StatusColumn");
1127
1128 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.15f, 0.2f, 0.18f, 0.4f));
1129 ImGui::BeginChild("Collab_SessionDetails", ImVec2(0, 60), true); // reduced height
1130 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
1131 ICON_MD_INFO " Session:");
1132 if (connected) {
1133 ImGui::TextColored(collaboration_status_color_,
1134 ICON_MD_CHECK_CIRCLE " Connected");
1135 } else {
1136 ImGui::TextDisabled(ICON_MD_CANCEL " Not connected");
1137 }
1138
1140 ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f),
1141 ICON_MD_CLOUD " Server:");
1142 ImGui::TextUnformatted(collaboration_state_.server_url.c_str());
1143 }
1144
1145 if (!collaboration_state_.session_name.empty()) {
1146 ImGui::TextColored(collaboration_status_color_,
1148 }
1149
1150 if (!collaboration_state_.session_id.empty()) {
1151 ImGui::TextColored(collaboration_status_color_,
1153 ImGui::SameLine();
1154 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.4f, 0.6f, 0.6f));
1155 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
1156 ImVec4(0.416f, 0.353f, 0.804f, 1.0f));
1157 if (ImGui::SmallButton(ICON_MD_CONTENT_COPY "##copy_session_id")) {
1158 ImGui::SetClipboardText(collaboration_state_.session_id.c_str());
1159 if (toast_manager_) {
1160 toast_manager_->Show("Session code copied!", ToastType::kSuccess, 2.0f);
1161 }
1162 }
1163 ImGui::PopStyleColor(2);
1164 }
1165
1166 if (collaboration_state_.last_synced != absl::InfinitePast()) {
1167 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
1168 ICON_MD_ACCESS_TIME " %s",
1169 absl::FormatTime("%H:%M:%S", collaboration_state_.last_synced,
1170 absl::LocalTimeZone()).c_str());
1171 }
1172 ImGui::EndChild();
1173 ImGui::PopStyleColor();
1174
1175 // Participants list below session details
1176 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.12f, 0.16f, 0.14f, 0.4f));
1177 ImGui::BeginChild("Collab_ParticipantsList", ImVec2(0, 0), true);
1178 if (collaboration_state_.participants.empty()) {
1179 ImGui::TextDisabled(ICON_MD_PEOPLE " No participants");
1180 } else {
1181 ImGui::TextColored(collaboration_status_color_,
1183 for (size_t i = 0; i < collaboration_state_.participants.size(); ++i) {
1184 ImGui::PushID(static_cast<int>(i));
1185 ImGui::BulletText("%s", collaboration_state_.participants[i].c_str());
1186 ImGui::PopID();
1187 }
1188 }
1189 ImGui::EndChild();
1190 ImGui::PopStyleColor();
1191
1192 ImGui::PopID(); // StatusColumn
1193 ImGui::EndGroup();
1194
1195 // RIGHT COLUMN: Controls
1196 ImGui::TableSetColumnIndex(1);
1197 ImGui::BeginGroup();
1198 ImGui::PushID("ControlsColumn");
1199 ImGui::BeginChild("Collab_Controls", ImVec2(0, 0), false);
1200
1201 const bool can_host =
1202 static_cast<bool>(collaboration_callbacks_.host_session);
1203 const bool can_join =
1204 static_cast<bool>(collaboration_callbacks_.join_session);
1205 const bool can_leave =
1206 static_cast<bool>(collaboration_callbacks_.leave_session);
1207 const bool can_refresh =
1208 static_cast<bool>(collaboration_callbacks_.refresh_session);
1209
1210 // Network mode: Show server URL input with styling
1212 ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f), ICON_MD_CLOUD);
1213 ImGui::SameLine();
1214 ImGui::SetNextItemWidth(100);
1215 ImGui::InputText("##collab_server_url", server_url_buffer_, IM_ARRAYSIZE(server_url_buffer_));
1216 ImGui::SameLine();
1217 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.15f, 0.5f, 0.7f, 0.8f));
1218 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.196f, 0.6f, 0.8f, 1.0f));
1219 if (ImGui::SmallButton(ICON_MD_LINK "##connect_server_btn")) {
1221 if (toast_manager_) {
1222 toast_manager_->Show("Connecting to server...", ToastType::kInfo, 3.0f);
1223 }
1224 }
1225 ImGui::PopStyleColor(2);
1226 if (ImGui::IsItemHovered()) {
1227 ImGui::SetTooltip("Connect to collaboration server");
1228 }
1229 }
1230
1231 // Host session
1232 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_ADD_CIRCLE);
1233 ImGui::SameLine();
1234 ImGui::SetNextItemWidth(100);
1235 ImGui::InputTextWithHint("##collab_session_name", "Session name...", session_name_buffer_, IM_ARRAYSIZE(session_name_buffer_));
1236 ImGui::SameLine();
1237 if (!can_host) ImGui::BeginDisabled();
1238 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.5f, 0.0f, 0.8f));
1239 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 0.843f, 0.0f, 1.0f));
1240 if (ImGui::SmallButton(ICON_MD_ROCKET_LAUNCH "##host_session_btn")) {
1241 std::string name = session_name_buffer_;
1242 if (name.empty()) {
1243 if (toast_manager_) {
1244 toast_manager_->Show("Enter a session name first", ToastType::kWarning, 3.0f);
1245 }
1246 } else {
1247 auto session_or = collaboration_callbacks_.host_session(name);
1248 if (session_or.ok()) {
1249 ApplyCollaborationSession(session_or.value(), /*update_action_timestamp=*/true);
1250 std::snprintf(join_code_buffer_, sizeof(join_code_buffer_), "%s", collaboration_state_.session_id.c_str());
1251 session_name_buffer_[0] = '\0';
1252 if (toast_manager_) {
1253 toast_manager_->Show(absl::StrFormat("Hosting session %s", collaboration_state_.session_id.c_str()), ToastType::kSuccess, 3.5f);
1254 }
1256 } else if (toast_manager_) {
1257 toast_manager_->Show(absl::StrFormat("Failed to host: %s", session_or.status().message()), ToastType::kError, 5.0f);
1258 }
1259 }
1260 }
1261 ImGui::PopStyleColor(2);
1262 if (!can_host) {
1263 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1264 ImGui::SetTooltip("Provide host_session callback to enable hosting");
1265 }
1266 ImGui::EndDisabled();
1267 } else if (ImGui::IsItemHovered()) {
1268 ImGui::SetTooltip("Host a new collaboration session");
1269 }
1270
1271 // Join session
1272 ImGui::TextColored(ImVec4(0.133f, 0.545f, 0.133f, 1.0f), ICON_MD_LOGIN);
1273 ImGui::SameLine();
1274 ImGui::SetNextItemWidth(100);
1275 ImGui::InputTextWithHint("##collab_join_code", "Session code...", join_code_buffer_, IM_ARRAYSIZE(join_code_buffer_));
1276 ImGui::SameLine();
1277 if (!can_join) ImGui::BeginDisabled();
1278 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.1f, 0.4f, 0.1f, 0.8f));
1279 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.133f, 0.545f, 0.133f, 1.0f));
1280 if (ImGui::SmallButton(ICON_MD_MEETING_ROOM "##join_session_btn")) {
1281 std::string code = join_code_buffer_;
1282 if (code.empty()) {
1283 if (toast_manager_) {
1284 toast_manager_->Show("Enter a session code first", ToastType::kWarning, 3.0f);
1285 }
1286 } else {
1287 auto session_or = collaboration_callbacks_.join_session(code);
1288 if (session_or.ok()) {
1289 ApplyCollaborationSession(session_or.value(), /*update_action_timestamp=*/true);
1290 std::snprintf(join_code_buffer_, sizeof(join_code_buffer_), "%s", collaboration_state_.session_id.c_str());
1291 if (toast_manager_) {
1292 toast_manager_->Show(absl::StrFormat("Joined session %s", collaboration_state_.session_id.c_str()), ToastType::kSuccess, 3.5f);
1293 }
1295 } else if (toast_manager_) {
1296 toast_manager_->Show(absl::StrFormat("Failed to join: %s", session_or.status().message()), ToastType::kError, 5.0f);
1297 }
1298 }
1299 }
1300 ImGui::PopStyleColor(2);
1301 if (!can_join) {
1302 if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1303 ImGui::SetTooltip("Provide join_session callback to enable joining");
1304 }
1305 ImGui::EndDisabled();
1306 } else if (ImGui::IsItemHovered()) {
1307 ImGui::SetTooltip("Join an existing collaboration session");
1308 }
1309
1310 // Leave/Refresh
1312 if (!can_leave) ImGui::BeginDisabled();
1313 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.7f, 0.2f, 0.2f, 0.8f));
1314 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.863f, 0.078f, 0.235f, 1.0f));
1315 if (ImGui::SmallButton(ICON_MD_LOGOUT "##leave_session_btn")) {
1316 absl::Status status = collaboration_callbacks_.leave_session
1318 : absl::OkStatus();
1319 if (status.ok()) {
1321 join_code_buffer_[0] = '\0';
1322 if (toast_manager_) {
1323 toast_manager_->Show("Left collaborative session", ToastType::kInfo, 3.0f);
1324 }
1326 } else if (toast_manager_) {
1327 toast_manager_->Show(absl::StrFormat("Failed to leave: %s", status.message()), ToastType::kError, 5.0f);
1328 }
1329 }
1330 ImGui::PopStyleColor(2);
1331 if (!can_leave) ImGui::EndDisabled();
1332
1333 ImGui::SameLine();
1334 if (!can_refresh) ImGui::BeginDisabled();
1335 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.4f, 0.4f, 0.6f, 0.8f));
1336 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.416f, 0.353f, 0.804f, 1.0f));
1337 if (ImGui::SmallButton(ICON_MD_REFRESH "##refresh_collab_btn")) {
1339 }
1340 ImGui::PopStyleColor(2);
1341 if (!can_refresh && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
1342 ImGui::SetTooltip("Provide refresh_session callback to enable");
1343 }
1344 if (!can_refresh) ImGui::EndDisabled();
1345 } else {
1346 ImGui::TextDisabled(ICON_MD_INFO " Start or join a session to collaborate.");
1347 }
1348
1349 ImGui::EndChild(); // Collab_Controls
1350 ImGui::PopID(); // ControlsColumn
1351 ImGui::EndGroup();
1352 ImGui::EndTable();
1353 }
1354
1355 ImGui::EndChild();
1356 ImGui::PopStyleColor(); // Pop the ChildBg color from line 1091
1357 ImGui::PopStyleVar(2); // Pop the 2 StyleVars from lines 1082-1083
1358 ImGui::PopID(); // CollabPanel
1359}
1360
1362 const auto& theme = AgentUI::GetTheme();
1363 ImGui::PushID("MultimodalPanel");
1364
1365 // Dense header (no collapsing for small panel)
1367 ImGui::BeginChild("Multimodal_Panel", ImVec2(0, 120), true); // Slightly taller
1368 AgentUI::RenderSectionHeader(ICON_MD_CAMERA, "Vision", theme.provider_gemini);
1369
1370 bool can_capture = static_cast<bool>(multimodal_callbacks_.capture_snapshot);
1371 bool can_send = static_cast<bool>(multimodal_callbacks_.send_to_gemini);
1372
1373 // Ultra-compact mode selector
1374 ImGui::RadioButton("Full##mm_full",
1375 reinterpret_cast<int*>(&multimodal_state_.capture_mode),
1376 static_cast<int>(CaptureMode::kFullWindow));
1377 ImGui::SameLine();
1378 ImGui::RadioButton("Active##mm_active",
1379 reinterpret_cast<int*>(&multimodal_state_.capture_mode),
1380 static_cast<int>(CaptureMode::kActiveEditor));
1381 ImGui::SameLine();
1382 ImGui::RadioButton("Window##mm_window",
1383 reinterpret_cast<int*>(&multimodal_state_.capture_mode),
1384 static_cast<int>(CaptureMode::kSpecificWindow));
1385 ImGui::SameLine();
1386 ImGui::RadioButton("Region##mm_region",
1387 reinterpret_cast<int*>(&multimodal_state_.capture_mode),
1388 static_cast<int>(CaptureMode::kRegionSelect));
1389
1390 if (!can_capture)
1391 ImGui::BeginDisabled();
1392 if (ImGui::SmallButton(ICON_MD_PHOTO_CAMERA " Capture##mm_cap")) {
1394 // Begin region selection mode
1397 std::filesystem::path captured_path;
1398 absl::Status status =
1400 if (status.ok()) {
1401 multimodal_state_.last_capture_path = captured_path;
1403 absl::StrFormat("Captured %s", captured_path.string());
1404 multimodal_state_.last_updated = absl::Now();
1405 LoadScreenshotPreview(captured_path);
1406 if (toast_manager_) {
1407 toast_manager_->Show("Snapshot captured", ToastType::kSuccess, 3.0f);
1408 }
1410 } else {
1411 multimodal_state_.status_message = status.message();
1412 multimodal_state_.last_updated = absl::Now();
1413 if (toast_manager_) {
1415 absl::StrFormat("Snapshot failed: %s", status.message()),
1416 ToastType::kError, 5.0f);
1417 }
1418 }
1419 }
1420 }
1421 if (!can_capture)
1422 ImGui::EndDisabled();
1423
1424 ImGui::SameLine();
1425 if (multimodal_state_.last_capture_path.has_value()) {
1426 ImGui::TextColored(theme.status_success, ICON_MD_CHECK_CIRCLE);
1427 } else {
1428 ImGui::TextDisabled(ICON_MD_CAMERA_ALT);
1429 }
1430 if (ImGui::IsItemHovered() &&
1432 ImGui::SetTooltip(
1433 "%s", multimodal_state_.last_capture_path->filename().string().c_str());
1434 }
1435
1436 if (!can_send)
1437 ImGui::BeginDisabled();
1438 ImGui::SameLine();
1439 if (ImGui::SmallButton(ICON_MD_SEND " Analyze##mm_send")) {
1440 if (!multimodal_state_.last_capture_path.has_value()) {
1441 if (toast_manager_) {
1442 toast_manager_->Show("Capture a snapshot first", ToastType::kWarning,
1443 3.0f);
1444 }
1445 } else {
1446 std::string prompt = multimodal_prompt_buffer_;
1447 absl::Status status = multimodal_callbacks_.send_to_gemini(
1449 if (status.ok()) {
1450 multimodal_state_.status_message = "Submitted image to Gemini";
1451 multimodal_state_.last_updated = absl::Now();
1452 if (toast_manager_) {
1453 toast_manager_->Show("Gemini request sent", ToastType::kSuccess,
1454 3.0f);
1455 }
1457 } else {
1458 multimodal_state_.status_message = status.message();
1459 multimodal_state_.last_updated = absl::Now();
1460 if (toast_manager_) {
1462 absl::StrFormat("Gemini request failed: %s", status.message()),
1463 ToastType::kError, 5.0f);
1464 }
1465 }
1466 }
1467 }
1468 if (!can_send)
1469 ImGui::EndDisabled();
1470
1471 // Screenshot preview section
1473 ImGui::Spacing();
1474 ImGui::Separator();
1475 ImGui::Text(ICON_MD_IMAGE " Preview:");
1477 }
1478
1479 // Region selection active indicator
1481 ImGui::Spacing();
1482 ImGui::Separator();
1483 ImGui::TextColored(theme.provider_ollama, ICON_MD_CROP " Drag to select region");
1484 if (ImGui::SmallButton("Cancel##region_cancel")) {
1486 }
1487 }
1488
1489 ImGui::EndChild();
1491 ImGui::PopID();
1492
1493 // Handle region selection (overlay)
1496 }
1497}
1498
1500 const auto& theme = AgentUI::GetTheme();
1501 ImGui::PushID("AutomationPanel");
1502
1503 // Auto-poll for status updates
1505
1506 // Animate pulse and scanlines for retro effect
1507 automation_state_.pulse_animation += ImGui::GetIO().DeltaTime * 2.0f;
1508 automation_state_.scanline_offset += ImGui::GetIO().DeltaTime * 0.5f;
1511 }
1512
1514 if (ImGui::BeginChild("Automation_Panel", ImVec2(0, 240), true)) {
1515 // === HEADER WITH RETRO GLITCH EFFECT ===
1516 float pulse = 0.5f + 0.5f * std::sin(automation_state_.pulse_animation);
1517 ImVec4 header_glow = ImVec4(
1518 theme.provider_ollama.x + 0.3f * pulse,
1519 theme.provider_ollama.y + 0.2f * pulse,
1520 theme.provider_ollama.z + 0.4f * pulse,
1521 1.0f
1522 );
1523
1524 ImGui::PushStyleColor(ImGuiCol_Text, header_glow);
1525 ImGui::TextWrapped("%s %s", ICON_MD_SMART_TOY, "GUI AUTOMATION");
1526 ImGui::PopStyleColor();
1527
1528 ImGui::SameLine();
1529 ImGui::TextDisabled("[v0.4.x]");
1530
1531 // === CONNECTION STATUS WITH VISUAL EFFECTS ===
1532 bool connected = automation_state_.harness_connected;
1533 ImVec4 status_color;
1534 const char* status_text;
1535 const char* status_icon;
1536
1537 if (connected) {
1538 // Pulsing green for connected
1539 float green_pulse = 0.7f + 0.3f * std::sin(automation_state_.pulse_animation * 0.5f);
1540 status_color = ImVec4(0.1f, green_pulse, 0.3f, 1.0f);
1541 status_text = "ONLINE";
1542 status_icon = ICON_MD_CHECK_CIRCLE;
1543 } else {
1544 // Pulsing red for disconnected
1545 float red_pulse = 0.6f + 0.4f * std::sin(automation_state_.pulse_animation * 1.5f);
1546 status_color = ImVec4(red_pulse, 0.2f, 0.2f, 1.0f);
1547 status_text = "OFFLINE";
1548 status_icon = ICON_MD_ERROR;
1549 }
1550
1551 ImGui::Separator();
1552 ImGui::TextColored(status_color, "%s %s", status_icon, status_text);
1553 ImGui::SameLine();
1554 ImGui::TextDisabled("| %s", automation_state_.grpc_server_address.c_str());
1555
1556 // === CONTROL BAR ===
1557 ImGui::Spacing();
1558
1559 // Refresh button with pulse effect when auto-refresh is on
1560 bool auto_ref_pulse = automation_state_.auto_refresh_enabled &&
1561 (static_cast<int>(automation_state_.pulse_animation * 2.0f) % 2 == 0);
1562 if (auto_ref_pulse) {
1563 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.5f, 0.7f, 0.8f));
1564 }
1565
1566 if (ImGui::SmallButton(ICON_MD_REFRESH " Refresh")) {
1570 }
1571 }
1572
1573 if (auto_ref_pulse) {
1574 ImGui::PopStyleColor();
1575 }
1576
1577 if (ImGui::IsItemHovered()) {
1578 ImGui::SetTooltip("Refresh automation status\nAuto-refresh: %s (%.1fs)",
1581 }
1582
1583 // Auto-refresh toggle
1584 ImGui::SameLine();
1585 ImGui::Checkbox("##auto_refresh", &automation_state_.auto_refresh_enabled);
1586 if (ImGui::IsItemHovered()) {
1587 ImGui::SetTooltip("Auto-refresh connection status");
1588 }
1589
1590 // Quick action buttons
1591 ImGui::SameLine();
1592 if (ImGui::SmallButton(ICON_MD_DASHBOARD " Dashboard")) {
1595 }
1596 }
1597 if (ImGui::IsItemHovered()) {
1598 ImGui::SetTooltip("Open automation dashboard");
1599 }
1600
1601 ImGui::SameLine();
1602 if (ImGui::SmallButton(ICON_MD_REPLAY " Replay")) {
1605 }
1606 }
1607 if (ImGui::IsItemHovered()) {
1608 ImGui::SetTooltip("Replay last automation plan");
1609 }
1610
1611 // === SETTINGS ROW ===
1612 ImGui::Spacing();
1613 ImGui::SetNextItemWidth(80.0f);
1614 ImGui::SliderFloat("##refresh_interval", &automation_state_.refresh_interval_seconds,
1615 0.5f, 10.0f, "%.1fs");
1616 if (ImGui::IsItemHovered()) {
1617 ImGui::SetTooltip("Auto-refresh interval");
1618 }
1619
1620 // === RECENT AUTOMATION ACTIONS WITH SCROLLING ===
1621 ImGui::Spacing();
1622 ImGui::Separator();
1623
1624 // Header with retro styling
1625 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "%s RECENT ACTIONS", ICON_MD_LIST);
1626 ImGui::SameLine();
1627 ImGui::TextDisabled("[%zu]", automation_state_.recent_tests.size());
1628
1629 if (automation_state_.recent_tests.empty()) {
1630 ImGui::Spacing();
1631 ImGui::TextDisabled(" > No recent actions");
1632 ImGui::TextDisabled(" > Waiting for automation tasks...");
1633
1634 // Add animated dots
1635 int dots = static_cast<int>(automation_state_.pulse_animation) % 4;
1636 std::string dot_string(dots, '.');
1637 ImGui::TextDisabled(" > %s", dot_string.c_str());
1638 } else {
1639 // Scrollable action list with retro styling
1640 ImGui::BeginChild("ActionQueue", ImVec2(0, 100), true,
1641 ImGuiWindowFlags_AlwaysVerticalScrollbar);
1642
1643 // Add scanline effect (visual only)
1644 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1645 ImVec2 win_pos = ImGui::GetWindowPos();
1646 ImVec2 win_size = ImGui::GetWindowSize();
1647
1648 // Draw scanlines
1649 for (float y = 0; y < win_size.y; y += 4.0f) {
1650 float offset_y = y + automation_state_.scanline_offset * 4.0f;
1651 if (offset_y < win_size.y) {
1652 draw_list->AddLine(
1653 ImVec2(win_pos.x, win_pos.y + offset_y),
1654 ImVec2(win_pos.x + win_size.x, win_pos.y + offset_y),
1655 IM_COL32(0, 0, 0, 20));
1656 }
1657 }
1658
1659 for (const auto& test : automation_state_.recent_tests) {
1660 ImGui::PushID(test.test_id.c_str());
1661
1662 // Status icon with animation for running tests
1663 ImVec4 action_color;
1664 const char* status_icon;
1665 bool is_running = false;
1666
1667 if (test.status == "success" || test.status == "completed" || test.status == "passed") {
1668 action_color = theme.status_success;
1669 status_icon = ICON_MD_CHECK_CIRCLE;
1670 } else if (test.status == "running" || test.status == "in_progress") {
1671 is_running = true;
1672 float running_pulse = 0.5f + 0.5f * std::sin(automation_state_.pulse_animation * 3.0f);
1673 action_color = ImVec4(
1674 theme.provider_ollama.x * running_pulse,
1675 theme.provider_ollama.y * (0.8f + 0.2f * running_pulse),
1676 theme.provider_ollama.z * running_pulse,
1677 1.0f
1678 );
1679 status_icon = ICON_MD_PENDING;
1680 } else if (test.status == "failed" || test.status == "error") {
1681 action_color = theme.status_error;
1682 status_icon = ICON_MD_ERROR;
1683 } else {
1684 action_color = theme.text_secondary_color;
1685 status_icon = ICON_MD_HELP;
1686 }
1687
1688 // Icon with pulse
1689 ImGui::TextColored(action_color, "%s", status_icon);
1690 ImGui::SameLine();
1691
1692 // Action name with monospace font
1693 ImGui::Text("> %s", test.name.c_str());
1694
1695 // Timestamp
1696 if (test.updated_at != absl::InfinitePast()) {
1697 ImGui::SameLine();
1698 auto elapsed = absl::Now() - test.updated_at;
1699 if (elapsed < absl::Seconds(60)) {
1700 ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
1701 "[%ds]", static_cast<int>(absl::ToInt64Seconds(elapsed)));
1702 } else if (elapsed < absl::Minutes(60)) {
1703 ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
1704 "[%dm]", static_cast<int>(absl::ToInt64Minutes(elapsed)));
1705 } else {
1706 ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
1707 "[%dh]", static_cast<int>(absl::ToInt64Hours(elapsed)));
1708 }
1709 }
1710
1711 // Message (if any) with indentation
1712 if (!test.message.empty()) {
1713 ImGui::Indent(20.0f);
1714 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.0f));
1715 ImGui::TextWrapped(" %s %s", ICON_MD_MESSAGE, test.message.c_str());
1716 ImGui::PopStyleColor();
1717 ImGui::Unindent(20.0f);
1718 }
1719
1720 ImGui::PopID();
1721 }
1722
1723 ImGui::EndChild();
1724 }
1725 }
1726 ImGui::EndChild();
1728 ImGui::PopID();
1729}
1730
1733 return;
1734 }
1735 auto session_or = collaboration_callbacks_.refresh_session();
1736 if (!session_or.ok()) {
1737 if (session_or.status().code() == absl::StatusCode::kNotFound) {
1739 join_code_buffer_[0] = '\0';
1741 }
1742 if (toast_manager_) {
1743 toast_manager_->Show(absl::StrFormat("Failed to refresh participants: %s",
1744 session_or.status().message()),
1745 ToastType::kError, 5.0f);
1746 }
1747 return;
1748 }
1749
1750 ApplyCollaborationSession(session_or.value(),
1751 /*update_action_timestamp=*/false);
1753}
1754
1757 bool update_action_timestamp) {
1761 context.session_name.empty() ? context.session_id : context.session_name;
1763 collaboration_state_.last_synced = absl::Now();
1764 if (update_action_timestamp) {
1765 last_collaboration_action_ = absl::Now();
1766 }
1767}
1768
1770 history_dirty_ = true;
1771 const absl::Time now = absl::Now();
1772 if (last_persist_time_ == absl::InfinitePast() ||
1773 now - last_persist_time_ > absl::Seconds(2)) {
1775 }
1776}
1777
1778void AgentChatWidget::SwitchToSharedHistory(const std::string& session_id) {
1779 // Save current local history before switching
1782 }
1783
1784 // Switch to shared history path
1785 history_path_ = ResolveHistoryPath(session_id);
1786 history_loaded_ = false;
1787
1788 // Load shared history
1790
1791 // Initialize polling state
1793 last_shared_history_poll_ = absl::Now();
1794
1795 if (toast_manager_) {
1797 absl::StrFormat("Switched to shared chat history for session %s",
1798 session_id),
1799 ToastType::kInfo, 3.0f);
1800 }
1801}
1802
1804 // Save shared history before switching
1807 }
1808
1809 // Switch back to local history
1810 history_path_ = ResolveHistoryPath("");
1811 history_loaded_ = false;
1812
1813 // Load local history
1815
1816 if (toast_manager_) {
1817 toast_manager_->Show("Switched to local chat history", ToastType::kInfo,
1818 3.0f);
1819 }
1820}
1821
1824 return; // Not in a collaborative session
1825 }
1826
1827 const absl::Time now = absl::Now();
1828
1829 // Poll every 2 seconds
1830 if (now - last_shared_history_poll_ < absl::Seconds(2)) {
1831 return;
1832 }
1833
1835
1836 // Check if the shared history file has been updated
1838 if (!result.ok()) {
1839 return; // File might not exist yet or be temporarily locked
1840 }
1841
1842 const size_t new_size = result->history.size();
1843
1844 // If history has grown, reload it
1845 if (new_size > last_known_history_size_) {
1846 const size_t new_messages = new_size - last_known_history_size_;
1847
1848 agent_service_.ReplaceHistory(std::move(result->history));
1849 last_history_size_ = new_size;
1850 last_known_history_size_ = new_size;
1851
1852 if (toast_manager_) {
1854 absl::StrFormat("📬 %zu new message%s from collaborators",
1855 new_messages, new_messages == 1 ? "" : "s"),
1856 ToastType::kInfo, 3.0f);
1857 }
1858 }
1859}
1860
1862 agent_config_ = config;
1863
1864 // Apply configuration to the agent service
1865 cli::agent::AgentConfig service_config;
1866 service_config.verbose = config.verbose;
1867 service_config.show_reasoning = config.show_reasoning;
1868 service_config.max_tool_iterations = config.max_tool_iterations;
1869 service_config.max_retry_attempts = config.max_retry_attempts;
1870
1871 agent_service_.SetConfig(service_config);
1872
1873 if (toast_manager_) {
1874 toast_manager_->Show("Agent configuration updated", ToastType::kSuccess,
1875 2.5f);
1876 }
1877}
1878
1880 const auto& theme = AgentUI::GetTheme();
1881
1882 // Dense header (no collapsing)
1883 ImGui::PushStyleColor(ImGuiCol_ChildBg, theme.panel_bg_color);
1884 ImGui::BeginChild("AgentConfig", ImVec2(0, 140), true); // Reduced from 350
1885 AgentUI::RenderSectionHeader(ICON_MD_SETTINGS, "Config", theme.command_text_color);
1886
1887
1888 // Compact provider selection
1889 int provider_idx = 0;
1890 if (agent_config_.ai_provider == "ollama")
1891 provider_idx = 1;
1892 else if (agent_config_.ai_provider == "gemini")
1893 provider_idx = 2;
1894
1895 if (ImGui::RadioButton("Mock", &provider_idx, 0)) {
1896 agent_config_.ai_provider = "mock";
1897 std::snprintf(agent_config_.provider_buffer,
1898 sizeof(agent_config_.provider_buffer), "mock");
1899 }
1900 ImGui::SameLine();
1901 if (ImGui::RadioButton("Ollama", &provider_idx, 1)) {
1902 agent_config_.ai_provider = "ollama";
1903 std::snprintf(agent_config_.provider_buffer,
1904 sizeof(agent_config_.provider_buffer), "ollama");
1905 }
1906 ImGui::SameLine();
1907 if (ImGui::RadioButton("Gemini", &provider_idx, 2)) {
1908 agent_config_.ai_provider = "gemini";
1909 std::snprintf(agent_config_.provider_buffer,
1910 sizeof(agent_config_.provider_buffer), "gemini");
1911 }
1912
1913 // Dense provider settings
1914 if (agent_config_.ai_provider == "ollama") {
1915 ImGui::InputText("##ollama_model", agent_config_.model_buffer,
1916 IM_ARRAYSIZE(agent_config_.model_buffer));
1917 ImGui::InputText("##ollama_host", agent_config_.ollama_host_buffer,
1918 IM_ARRAYSIZE(agent_config_.ollama_host_buffer));
1919 } else if (agent_config_.ai_provider == "gemini") {
1920 ImGui::InputText("##gemini_model", agent_config_.model_buffer,
1921 IM_ARRAYSIZE(agent_config_.model_buffer));
1922 ImGui::InputText("##gemini_key", agent_config_.gemini_key_buffer,
1923 IM_ARRAYSIZE(agent_config_.gemini_key_buffer),
1924 ImGuiInputTextFlags_Password);
1925 }
1926
1927 ImGui::Separator();
1928 ImGui::Checkbox("Verbose", &agent_config_.verbose);
1929 ImGui::SameLine();
1930 ImGui::Checkbox("Reasoning", &agent_config_.show_reasoning);
1931 ImGui::SetNextItemWidth(-1);
1932 ImGui::SliderInt("##max_iter", &agent_config_.max_tool_iterations, 1, 10,
1933 "Iter: %d");
1934
1935 if (ImGui::Button(ICON_MD_CHECK " Apply", ImVec2(-1, 0))) {
1940 }
1941
1942 ImGui::EndChild();
1943 ImGui::PopStyleColor(); // Pop the ChildBg color from line 1609
1944}
1945
1947 ImGui::PushID("Z3EDCmdPanel");
1948 ImVec4 command_color = ImVec4(1.0f, 0.647f, 0.0f, 1.0f);
1949
1950 // Dense header (no collapsing)
1951 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.14f, 0.12f, 0.18f, 0.95f));
1952 ImGui::BeginChild("Z3ED_CommandsChild", ImVec2(0, 100),
1953 true);
1954
1955 ImGui::TextColored(command_color, ICON_MD_TERMINAL " Commands");
1956 ImGui::Separator();
1957
1958 ImGui::SetNextItemWidth(-60);
1959 ImGui::InputTextWithHint(
1960 "##z3ed_cmd", "Command...", z3ed_command_state_.command_input_buffer,
1962 ImGui::SameLine();
1963 ImGui::BeginDisabled(z3ed_command_state_.command_running);
1964 if (ImGui::Button(ICON_MD_PLAY_ARROW "##z3ed_run", ImVec2(50, 0))) {
1966 std::string command = z3ed_command_state_.command_input_buffer;
1968 auto status = z3ed_callbacks_.run_agent_task(command);
1970 if (status.ok() && toast_manager_) {
1971 toast_manager_->Show("Task started", ToastType::kSuccess, 2.0f);
1972 }
1973 }
1974 }
1975 ImGui::EndDisabled();
1976 if (ImGui::IsItemHovered()) {
1977 ImGui::SetTooltip("Run command");
1978 }
1979
1980 // Compact action buttons (inline)
1981 if (ImGui::SmallButton(ICON_MD_PREVIEW)) {
1983 auto result = z3ed_callbacks_.list_proposals();
1984 if (result.ok()) {
1985 const auto& proposals = *result;
1986 z3ed_command_state_.command_output = absl::StrJoin(proposals, "\n");
1987 }
1988 }
1989 }
1990 if (ImGui::IsItemHovered())
1991 ImGui::SetTooltip("List");
1992 ImGui::SameLine();
1993 if (ImGui::SmallButton(ICON_MD_DIFFERENCE)) {
1995 auto result = z3ed_callbacks_.diff_proposal("");
1996 if (result.ok())
1998 }
1999 }
2000 if (ImGui::IsItemHovered())
2001 ImGui::SetTooltip("Diff");
2002 ImGui::SameLine();
2003 if (ImGui::SmallButton(ICON_MD_CHECK)) {
2006 }
2007 }
2008 if (ImGui::IsItemHovered())
2009 ImGui::SetTooltip("Accept");
2010 ImGui::SameLine();
2011 if (ImGui::SmallButton(ICON_MD_CLOSE)) {
2014 }
2015 }
2016 if (ImGui::IsItemHovered())
2017 ImGui::SetTooltip("Reject");
2018
2019 if (!z3ed_command_state_.command_output.empty()) {
2020 ImGui::Separator();
2021 ImGui::TextDisabled(
2022 "%s", z3ed_command_state_.command_output.substr(0, 100).c_str());
2023 }
2024
2025 ImGui::EndChild();
2026 ImGui::PopStyleColor(); // Pop the ChildBg color from line 1677
2027
2028 ImGui::PopID(); // Pop the Z3EDCmdPanel ID
2029}
2030
2032 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.18f, 0.14f, 0.12f, 1.0f));
2033 ImGui::BeginChild("RomSync", ImVec2(0, 130), true);
2034
2035 ImGui::Text(ICON_MD_STORAGE " ROM State");
2036 ImGui::Separator();
2037
2038 // Display current ROM hash
2039 if (!rom_sync_state_.current_rom_hash.empty()) {
2040 ImGui::Text("Hash: %s",
2041 rom_sync_state_.current_rom_hash.substr(0, 16).c_str());
2042 ImGui::SameLine();
2043 if (ImGui::SmallButton(ICON_MD_CONTENT_COPY)) {
2044 ImGui::SetClipboardText(rom_sync_state_.current_rom_hash.c_str());
2045 if (toast_manager_) {
2046 toast_manager_->Show("ROM hash copied", ToastType::kInfo, 2.0f);
2047 }
2048 }
2049 } else {
2050 ImGui::TextDisabled("No ROM loaded");
2051 }
2052
2053 if (rom_sync_state_.last_sync_time != absl::InfinitePast()) {
2054 ImGui::Text("Last Sync: %s",
2055 absl::FormatTime("%H:%M:%S", rom_sync_state_.last_sync_time,
2056 absl::LocalTimeZone())
2057 .c_str());
2058 }
2059
2060 ImGui::Spacing();
2061 ImGui::Checkbox("Auto-sync ROM changes", &rom_sync_state_.auto_sync_enabled);
2062
2064 ImGui::SliderInt("Sync Interval (seconds)",
2066 }
2067
2068 ImGui::Spacing();
2069 ImGui::Separator();
2070
2071 bool can_sync = static_cast<bool>(rom_sync_callbacks_.generate_rom_diff) &&
2074
2075 if (!can_sync)
2076 ImGui::BeginDisabled();
2077
2078 if (ImGui::Button(ICON_MD_CLOUD_UPLOAD " Send ROM Sync", ImVec2(-1, 0))) {
2080 auto diff_result = rom_sync_callbacks_.generate_rom_diff();
2081 if (diff_result.ok()) {
2082 std::string hash = rom_sync_callbacks_.get_rom_hash
2084 : "";
2085
2087 rom_sync_state_.last_sync_time = absl::Now();
2088
2089 // TODO: Send via network coordinator
2090 if (toast_manager_) {
2092 " ROM synced to collaborators",
2093 ToastType::kSuccess, 3.0f);
2094 }
2095 } else if (toast_manager_) {
2096 toast_manager_->Show(absl::StrFormat(ICON_MD_ERROR " Sync failed: %s",
2097 diff_result.status().message()),
2098 ToastType::kError, 5.0f);
2099 }
2100 }
2101 }
2102
2103 if (!can_sync) {
2104 ImGui::EndDisabled();
2105 if (ImGui::IsItemHovered()) {
2106 ImGui::SetTooltip("Connect to a network session to sync ROM");
2107 }
2108 }
2109
2110 // Show pending syncs
2111 if (!rom_sync_state_.pending_syncs.empty()) {
2112 ImGui::Spacing();
2113 ImGui::Text(ICON_MD_PENDING " Pending Syncs (%zu)",
2115 ImGui::Separator();
2116
2117 ImGui::BeginChild("PendingSyncs", ImVec2(0, 80), true);
2118 for (const auto& sync : rom_sync_state_.pending_syncs) {
2119 ImGui::BulletText("%s", sync.substr(0, 40).c_str());
2120 }
2121 ImGui::EndChild();
2122 }
2123
2124 ImGui::EndChild();
2125 ImGui::PopStyleColor(); // Pop the ChildBg color from line 1758
2126}
2127
2129 if (!ImGui::CollapsingHeader(ICON_MD_PHOTO_CAMERA " Snapshot Preview",
2130 ImGuiTreeNodeFlags_DefaultOpen)) {
2131 return;
2132 }
2133
2134 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.12f, 0.12f, 0.16f, 1.0f));
2135 ImGui::BeginChild("SnapshotPreview", ImVec2(0, 200), true);
2136
2137 if (multimodal_state_.last_capture_path.has_value()) {
2138 ImGui::Text(ICON_MD_IMAGE " Latest Capture");
2139 ImGui::Separator();
2140 ImGui::TextWrapped(
2141 "%s", multimodal_state_.last_capture_path->filename().string().c_str());
2142
2143 // TODO: Load and display image thumbnail
2144 ImGui::TextDisabled("Preview: [Image preview not yet implemented]");
2145
2146 ImGui::Spacing();
2147
2148 bool can_share = collaboration_state_.active &&
2150
2151 if (!can_share)
2152 ImGui::BeginDisabled();
2153
2154 if (ImGui::Button(ICON_MD_SHARE " Share with Collaborators",
2155 ImVec2(-1, 0))) {
2156 // TODO: Share snapshot via network coordinator
2157 if (toast_manager_) {
2158 toast_manager_->Show(ICON_MD_CHECK " Snapshot shared",
2159 ToastType::kSuccess, 3.0f);
2160 }
2161 }
2162
2163 if (!can_share) {
2164 ImGui::EndDisabled();
2165 if (ImGui::IsItemHovered()) {
2166 ImGui::SetTooltip("Connect to a network session to share snapshots");
2167 }
2168 }
2169 } else {
2170 ImGui::TextDisabled(ICON_MD_NO_PHOTOGRAPHY " No snapshot captured yet");
2171 ImGui::TextWrapped("Use the Multimodal panel to capture a snapshot");
2172 }
2173
2174 ImGui::EndChild();
2175 ImGui::PopStyleColor(); // Pop the ChildBg color from line 1860
2176}
2177
2179 ImGui::Text(ICON_MD_PREVIEW " Proposal Management");
2180 ImGui::Separator();
2181
2183 auto proposals_result = z3ed_callbacks_.list_proposals();
2184
2185 if (proposals_result.ok()) {
2186 const auto& proposals = *proposals_result;
2187
2188 ImGui::Text("Total Proposals: %zu", proposals.size());
2189 ImGui::Spacing();
2190
2191 if (proposals.empty()) {
2192 ImGui::TextDisabled(
2193 "No proposals yet. Use the agent to create proposals.");
2194 } else {
2195 if (ImGui::BeginTable("ProposalsTable", 3,
2196 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
2197 ImGuiTableFlags_Resizable)) {
2198 ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed,
2199 100.0f);
2200 ImGui::TableSetupColumn("Description",
2201 ImGuiTableColumnFlags_WidthStretch);
2202 ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed,
2203 150.0f);
2204 ImGui::TableHeadersRow();
2205
2206 for (const auto& proposal_id : proposals) {
2207 ImGui::TableNextRow();
2208 ImGui::TableNextColumn();
2209 ImGui::TextUnformatted(proposal_id.c_str());
2210
2211 ImGui::TableNextColumn();
2212 ImGui::TextDisabled("Proposal details...");
2213
2214 ImGui::TableNextColumn();
2215 ImGui::PushID(proposal_id.c_str());
2216
2217 if (ImGui::SmallButton(ICON_MD_VISIBILITY)) {
2218 FocusProposalDrawer(proposal_id);
2219 }
2220 ImGui::SameLine();
2221
2222 if (ImGui::SmallButton(ICON_MD_CHECK)) {
2224 auto status = z3ed_callbacks_.accept_proposal(proposal_id);
2225 (void)status; // Acknowledge result
2226 }
2227 }
2228 ImGui::SameLine();
2229
2230 if (ImGui::SmallButton(ICON_MD_CLOSE)) {
2232 auto status = z3ed_callbacks_.reject_proposal(proposal_id);
2233 (void)status; // Acknowledge result
2234 }
2235 }
2236
2237 ImGui::PopID();
2238 }
2239
2240 ImGui::EndTable();
2241 }
2242 }
2243 } else {
2244 std::string error_msg(proposals_result.status().message());
2245 ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f),
2246 "Failed to load proposals: %s", error_msg.c_str());
2247 }
2248 } else {
2249 ImGui::TextDisabled("Proposal management not available");
2250 ImGui::TextWrapped("Set up Z3ED command callbacks to enable this feature");
2251 }
2252}
2253
2255 ImGui::PushID("HarnessPanel");
2256 ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0.12f, 0.16f, 0.22f, 0.95f));
2257 ImGui::BeginChild("HarnessPanel", ImVec2(0, 220), true);
2258
2259 ImGui::TextColored(ImVec4(0.392f, 0.863f, 1.0f, 1.0f), ICON_MD_PLAY_CIRCLE " Harness Automation");
2260 ImGui::Separator();
2261
2262 ImGui::TextDisabled("Shared automation pipeline between CLI + Agent Chat");
2263 ImGui::Spacing();
2264
2265 if (ImGui::BeginTable("HarnessActions", 2, ImGuiTableFlags_BordersInnerV)) {
2266 ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed, 170.0f);
2267 ImGui::TableSetupColumn("Telemetry", ImGuiTableColumnFlags_WidthStretch);
2268 ImGui::TableNextRow();
2269
2270 // Actions column
2271 ImGui::TableSetColumnIndex(0);
2272 ImGui::BeginGroup();
2273
2274 const bool has_callbacks = automation_callbacks_.open_harness_dashboard ||
2277
2278 if (!has_callbacks) {
2279 ImGui::TextDisabled("Automation bridge not available");
2280 ImGui::TextWrapped("Hook up AutomationCallbacks via EditorManager to enable controls.");
2281 } else {
2283 ImGui::Button(ICON_MD_DASHBOARD " Dashboard", ImVec2(-FLT_MIN, 0))) {
2285 }
2286
2288 ImGui::Button(ICON_MD_REPLAY " Replay Last Plan", ImVec2(-FLT_MIN, 0))) {
2290 }
2291
2293 ImGui::Button(ICON_MD_LIST " Active Tests", ImVec2(-FLT_MIN, 0))) {
2295 }
2296
2298 ImGui::Spacing();
2299 ImGui::TextDisabled("Proposal tools");
2300 if (!pending_focus_proposal_id_.empty()) {
2301 ImGui::TextWrapped("Proposal %s active", pending_focus_proposal_id_.c_str());
2302 if (ImGui::SmallButton(ICON_MD_VISIBILITY " View Proposal")) {
2304 }
2305 } else {
2306 ImGui::TextDisabled("No proposal selected");
2307 }
2308 }
2309 }
2310
2311 ImGui::EndGroup();
2312
2313 // Telemetry column
2314 ImGui::TableSetColumnIndex(1);
2315 ImGui::BeginGroup();
2316
2317 ImGui::TextColored(ImVec4(0.6f, 0.78f, 1.0f, 1.0f), ICON_MD_QUERY_STATS " Live Telemetry");
2318 ImGui::Spacing();
2319
2320 if (!automation_state_.recent_tests.empty()) {
2321 const float row_height = ImGui::GetTextLineHeightWithSpacing() * 2.0f + 6.0f;
2322 if (ImGui::BeginTable("HarnessTelemetryRows", 4,
2323 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg |
2324 ImGuiTableFlags_SizingStretchProp)) {
2325 ImGui::TableSetupColumn("Test", ImGuiTableColumnFlags_WidthStretch, 0.3f);
2326 ImGui::TableSetupColumn("Status", ImGuiTableColumnFlags_WidthFixed, 90.0f);
2327 ImGui::TableSetupColumn("Message", ImGuiTableColumnFlags_WidthStretch, 0.5f);
2328 ImGui::TableSetupColumn("Updated", ImGuiTableColumnFlags_WidthFixed, 120.0f);
2329 ImGui::TableHeadersRow();
2330
2331 for (const auto& entry : automation_state_.recent_tests) {
2332 ImGui::TableNextRow(ImGuiTableRowFlags_None, row_height);
2333 ImGui::TableSetColumnIndex(0);
2334 ImGui::TextWrapped("%s", entry.name.empty() ? entry.test_id.c_str()
2335 : entry.name.c_str());
2336 ImGui::TableSetColumnIndex(1);
2337 const char* status = entry.status.c_str();
2338 ImVec4 status_color = ImVec4(0.8f, 0.8f, 0.8f, 1.0f);
2339 if (absl::EqualsIgnoreCase(status, "passed")) {
2340 status_color = ImVec4(0.2f, 0.8f, 0.4f, 1.0f);
2341 } else if (absl::EqualsIgnoreCase(status, "failed") ||
2342 absl::EqualsIgnoreCase(status, "timeout")) {
2343 status_color = ImVec4(0.95f, 0.4f, 0.4f, 1.0f);
2344 } else if (absl::EqualsIgnoreCase(status, "running")) {
2345 status_color = ImVec4(0.95f, 0.75f, 0.3f, 1.0f);
2346 }
2347 ImGui::TextColored(status_color, "%s", status);
2348
2349 ImGui::TableSetColumnIndex(2);
2350 ImGui::TextWrapped("%s", entry.message.c_str());
2351
2352 ImGui::TableSetColumnIndex(3);
2353 if (entry.updated_at == absl::InfinitePast()) {
2354 ImGui::TextDisabled("-");
2355 } else {
2356 const double seconds_ago = absl::ToDoubleSeconds(absl::Now() - entry.updated_at);
2357 ImGui::Text("%.0fs ago", seconds_ago);
2358 }
2359 }
2360 ImGui::EndTable();
2361 }
2362 } else {
2363 ImGui::TextDisabled("No harness activity recorded yet");
2364 }
2365
2366 ImGui::EndGroup();
2367 ImGui::EndTable();
2368 }
2369
2370 ImGui::EndChild();
2371 ImGui::PopStyleColor(); // Pop the ChildBg color from line 1982
2372 ImGui::PopID();
2373}
2374
2376 ImGui::BeginChild("SystemPromptEditor", ImVec2(0, 0), false);
2377
2378 // Toolbar
2379 if (ImGui::Button(ICON_MD_FOLDER_OPEN " Load V1")) {
2380 // Load embedded system_prompt.txt (v1)
2381 std::string prompt_v1 = util::LoadFile("assets/agent/system_prompt.txt");
2382 if (!prompt_v1.empty()) {
2383 // Find or create system prompt tab
2384 bool found = false;
2385 for (auto& tab : open_files_) {
2386 if (tab.is_system_prompt) {
2387 tab.editor.SetText(prompt_v1);
2388 tab.filepath = ""; // Not saved to disk
2389 tab.filename = "system_prompt_v1.txt (built-in)";
2390 found = true;
2391 break;
2392 }
2393 }
2394
2395 if (!found) {
2396 FileEditorTab tab;
2397 tab.filename = "system_prompt_v1.txt (built-in)";
2398 tab.filepath = "";
2399 tab.is_system_prompt = true;
2402 tab.editor.SetText(prompt_v1);
2403 open_files_.push_back(std::move(tab));
2404 active_file_tab_ = static_cast<int>(open_files_.size()) - 1;
2405 }
2406
2407 if (toast_manager_) {
2408 toast_manager_->Show("System prompt V1 loaded", ToastType::kSuccess);
2409 }
2410 } else if (toast_manager_) {
2411 toast_manager_->Show("Could not load system prompt V1",
2413 }
2414 }
2415
2416 ImGui::SameLine();
2417 if (ImGui::Button(ICON_MD_FOLDER_OPEN " Load V2")) {
2418 // Load embedded system_prompt_v2.txt
2419 std::string prompt_v2 = util::LoadFile("assets/agent/system_prompt_v2.txt");
2420 if (!prompt_v2.empty()) {
2421 // Find or create system prompt tab
2422 bool found = false;
2423 for (auto& tab : open_files_) {
2424 if (tab.is_system_prompt) {
2425 tab.editor.SetText(prompt_v2);
2426 tab.filepath = ""; // Not saved to disk
2427 tab.filename = "system_prompt_v2.txt (built-in)";
2428 found = true;
2429 break;
2430 }
2431 }
2432
2433 if (!found) {
2434 FileEditorTab tab;
2435 tab.filename = "system_prompt_v2.txt (built-in)";
2436 tab.filepath = "";
2437 tab.is_system_prompt = true;
2440 tab.editor.SetText(prompt_v2);
2441 open_files_.push_back(std::move(tab));
2442 active_file_tab_ = static_cast<int>(open_files_.size()) - 1;
2443 }
2444
2445 if (toast_manager_) {
2446 toast_manager_->Show("System prompt V2 loaded", ToastType::kSuccess);
2447 }
2448 } else if (toast_manager_) {
2449 toast_manager_->Show("Could not load system prompt V2",
2451 }
2452 }
2453
2454 ImGui::SameLine();
2455 if (ImGui::Button(ICON_MD_SAVE " Save to Project")) {
2456 // Save the current system prompt to project directory
2457 for (auto& tab : open_files_) {
2458 if (tab.is_system_prompt) {
2460 "custom_system_prompt", "txt");
2461 if (!save_path.empty()) {
2462 std::ofstream file(save_path);
2463 if (file.is_open()) {
2464 file << tab.editor.GetText();
2465 tab.filepath = save_path;
2466 tab.filename = util::GetFileName(save_path);
2467 tab.modified = false;
2468 if (toast_manager_) {
2470 absl::StrFormat("System prompt saved to %s", save_path),
2472 }
2473 } else if (toast_manager_) {
2474 toast_manager_->Show("Failed to save system prompt",
2476 }
2477 }
2478 break;
2479 }
2480 }
2481 }
2482
2483 ImGui::SameLine();
2484 if (ImGui::Button(ICON_MD_NOTE_ADD " Create New")) {
2485 FileEditorTab tab;
2486 tab.filename = "custom_system_prompt.txt (unsaved)";
2487 tab.filepath = "";
2488 tab.is_system_prompt = true;
2489 tab.modified = true;
2492 tab.editor.SetText(
2493 "# Custom System Prompt\n\nEnter your custom system prompt here...\n");
2494 open_files_.push_back(std::move(tab));
2495 active_file_tab_ = static_cast<int>(open_files_.size()) - 1;
2496 }
2497
2498 ImGui::SameLine();
2499 if (ImGui::Button(ICON_MD_FOLDER_OPEN " Load Custom")) {
2501 if (!filepath.empty()) {
2502 std::ifstream file(filepath);
2503 if (file.is_open()) {
2504 bool found = false;
2505 for (auto& tab : open_files_) {
2506 if (tab.is_system_prompt) {
2507 std::stringstream buffer;
2508 buffer << file.rdbuf();
2509 tab.editor.SetText(buffer.str());
2510 tab.filepath = filepath;
2511 tab.filename = util::GetFileName(filepath);
2512 tab.modified = false;
2513 found = true;
2514 break;
2515 }
2516 }
2517
2518 if (!found) {
2519 FileEditorTab tab;
2520 tab.filename = util::GetFileName(filepath);
2521 tab.filepath = filepath;
2522 tab.is_system_prompt = true;
2525 std::stringstream buffer;
2526 buffer << file.rdbuf();
2527 tab.editor.SetText(buffer.str());
2528 open_files_.push_back(std::move(tab));
2529 active_file_tab_ = static_cast<int>(open_files_.size()) - 1;
2530 }
2531
2532 if (toast_manager_) {
2533 toast_manager_->Show("Custom system prompt loaded",
2535 }
2536 } else if (toast_manager_) {
2537 toast_manager_->Show("Could not load file", ToastType::kError);
2538 }
2539 }
2540 }
2541
2542 ImGui::Separator();
2543
2544 // Find and render system prompt editor
2545 bool found_prompt = false;
2546 for (size_t i = 0; i < open_files_.size(); ++i) {
2547 if (open_files_[i].is_system_prompt) {
2548 found_prompt = true;
2549 ImVec2 editor_size = ImVec2(0, ImGui::GetContentRegionAvail().y);
2550 open_files_[i].editor.Render("##SystemPromptEditor", editor_size);
2551 if (open_files_[i].editor.IsTextChanged()) {
2552 open_files_[i].modified = true;
2553 }
2554 break;
2555 }
2556 }
2557
2558 if (!found_prompt) {
2559 ImGui::TextWrapped(
2560 "No system prompt loaded. Click 'Load Default' to edit the system "
2561 "prompt.");
2562 }
2563
2564 ImGui::EndChild();
2565}
2566
2568 ImGui::BeginChild("FileEditorArea", ImVec2(0, 0), false);
2569
2570 // Toolbar
2571 if (ImGui::Button(ICON_MD_NOTE_ADD " New File")) {
2572 ImGui::OpenPopup("NewFilePopup");
2573 }
2574 ImGui::SameLine();
2575 if (ImGui::Button(ICON_MD_FOLDER_OPEN " Open File")) {
2577 if (!filepath.empty()) {
2578 OpenFileInEditor(filepath);
2579 }
2580 }
2581
2582 // New file popup
2583 static char new_filename_buffer[256] = {};
2584 if (ImGui::BeginPopup("NewFilePopup")) {
2585 ImGui::Text("Create New File");
2586 ImGui::Separator();
2587 ImGui::InputText("Filename", new_filename_buffer,
2588 sizeof(new_filename_buffer));
2589 if (ImGui::Button("Create")) {
2590 if (strlen(new_filename_buffer) > 0) {
2591 CreateNewFileInEditor(new_filename_buffer);
2592 memset(new_filename_buffer, 0, sizeof(new_filename_buffer));
2593 ImGui::CloseCurrentPopup();
2594 }
2595 }
2596 ImGui::SameLine();
2597 if (ImGui::Button("Cancel")) {
2598 ImGui::CloseCurrentPopup();
2599 }
2600 ImGui::EndPopup();
2601 }
2602
2603 ImGui::Separator();
2604
2605 // File tabs
2606 if (!open_files_.empty()) {
2607 if (ImGui::BeginTabBar("FileTabs",
2608 ImGuiTabBarFlags_Reorderable |
2609 ImGuiTabBarFlags_FittingPolicyScroll)) {
2610 for (size_t i = 0; i < open_files_.size(); ++i) {
2611 if (open_files_[i].is_system_prompt)
2612 continue; // Skip system prompt in file tabs
2613
2614 bool open = true;
2615 std::string tab_label = open_files_[i].filename;
2616 if (open_files_[i].modified) {
2617 tab_label += " *";
2618 }
2619
2620 if (ImGui::BeginTabItem(tab_label.c_str(), &open)) {
2621 active_file_tab_ = static_cast<int>(i);
2622
2623 // File toolbar
2624 if (ImGui::Button(ICON_MD_SAVE " Save")) {
2625 if (!open_files_[i].filepath.empty()) {
2626 std::ofstream file(open_files_[i].filepath);
2627 if (file.is_open()) {
2628 file << open_files_[i].editor.GetText();
2629 open_files_[i].modified = false;
2630 if (toast_manager_) {
2631 toast_manager_->Show("File saved", ToastType::kSuccess);
2632 }
2633 } else if (toast_manager_) {
2634 toast_manager_->Show("Failed to save file", ToastType::kError);
2635 }
2636 } else {
2638 open_files_[i].filename, "");
2639 if (!save_path.empty()) {
2640 std::ofstream file(save_path);
2641 if (file.is_open()) {
2642 file << open_files_[i].editor.GetText();
2643 open_files_[i].filepath = save_path;
2644 open_files_[i].modified = false;
2645 if (toast_manager_) {
2646 toast_manager_->Show("File saved", ToastType::kSuccess);
2647 }
2648 }
2649 }
2650 }
2651 }
2652
2653 ImGui::SameLine();
2654 ImGui::TextDisabled("%s", open_files_[i].filepath.empty()
2655 ? "(unsaved)"
2656 : open_files_[i].filepath.c_str());
2657
2658 ImGui::Separator();
2659
2660 // Editor
2661 ImVec2 editor_size = ImVec2(0, ImGui::GetContentRegionAvail().y);
2662 open_files_[i].editor.Render("##FileEditor", editor_size);
2663 if (open_files_[i].editor.IsTextChanged()) {
2664 open_files_[i].modified = true;
2665 }
2666
2667 ImGui::EndTabItem();
2668 }
2669
2670 if (!open) {
2671 // Tab was closed
2672 open_files_.erase(open_files_.begin() + i);
2673 if (active_file_tab_ >= static_cast<int>(i)) {
2675 }
2676 break;
2677 }
2678 }
2679 ImGui::EndTabBar();
2680 }
2681 } else {
2682 ImGui::TextWrapped(
2683 "No files open. Create a new file or open an existing one.");
2684 }
2685
2686 ImGui::EndChild();
2687}
2688
2689void AgentChatWidget::OpenFileInEditor(const std::string& filepath) {
2690 // Check if file is already open
2691 for (size_t i = 0; i < open_files_.size(); ++i) {
2692 if (open_files_[i].filepath == filepath) {
2693 active_file_tab_ = static_cast<int>(i);
2694 return;
2695 }
2696 }
2697
2698 // Load the file
2699 std::ifstream file(filepath);
2700 if (!file.is_open()) {
2701 if (toast_manager_) {
2702 toast_manager_->Show("Could not open file", ToastType::kError);
2703 }
2704 return;
2705 }
2706
2707 FileEditorTab tab;
2708 tab.filepath = filepath;
2709
2710 // Extract filename from path
2711 size_t last_slash = filepath.find_last_of("/\\");
2712 tab.filename = (last_slash != std::string::npos)
2713 ? filepath.substr(last_slash + 1)
2714 : filepath;
2715
2716 // Set language based on extension
2717 std::string ext = util::GetFileExtension(filepath);
2718 if (ext == "cpp" || ext == "cc" || ext == "h" || ext == "hpp") {
2721 } else if (ext == "c") {
2723 } else if (ext == "lua") {
2725 }
2726
2727 std::stringstream buffer;
2728 buffer << file.rdbuf();
2729 tab.editor.SetText(buffer.str());
2730
2731 open_files_.push_back(std::move(tab));
2732 active_file_tab_ = static_cast<int>(open_files_.size()) - 1;
2733
2734 if (toast_manager_) {
2735 toast_manager_->Show("File loaded", ToastType::kSuccess);
2736 }
2737}
2738
2739void AgentChatWidget::CreateNewFileInEditor(const std::string& filename) {
2740 FileEditorTab tab;
2741 tab.filename = filename;
2742 tab.modified = true;
2743
2744 // Set language based on extension
2745 std::string ext = util::GetFileExtension(filename);
2746 if (ext == "cpp" || ext == "cc" || ext == "h" || ext == "hpp") {
2749 } else if (ext == "c") {
2751 } else if (ext == "lua") {
2753 }
2754
2755 open_files_.push_back(std::move(tab));
2756 active_file_tab_ = static_cast<int>(open_files_.size()) - 1;
2757}
2758
2760 const core::YazeProject& project) {
2761 // Load AI provider settings from project
2771
2772 // Copy to buffer for ImGui
2774 sizeof(agent_config_.provider_buffer) - 1);
2776 sizeof(agent_config_.model_buffer) - 1);
2778 sizeof(agent_config_.ollama_host_buffer) - 1);
2780 sizeof(agent_config_.gemini_key_buffer) - 1);
2781
2782 // Load custom system prompt if specified
2783 if (project.agent_settings.use_custom_prompt &&
2784 !project.agent_settings.custom_system_prompt.empty()) {
2785 std::string prompt_path =
2787 std::ifstream file(prompt_path);
2788 if (file.is_open()) {
2789 // Load into system prompt tab
2790 bool found = false;
2791 for (auto& tab : open_files_) {
2792 if (tab.is_system_prompt) {
2793 std::stringstream buffer;
2794 buffer << file.rdbuf();
2795 tab.editor.SetText(buffer.str());
2796 tab.filepath = prompt_path;
2797 tab.filename = util::GetFileName(prompt_path);
2798 found = true;
2799 break;
2800 }
2801 }
2802
2803 if (!found) {
2804 FileEditorTab tab;
2805 tab.filename = util::GetFileName(prompt_path);
2806 tab.filepath = prompt_path;
2807 tab.is_system_prompt = true;
2810 std::stringstream buffer;
2811 buffer << file.rdbuf();
2812 tab.editor.SetText(buffer.str());
2813 open_files_.push_back(std::move(tab));
2814 }
2815 }
2816 }
2817}
2818
2820 // Save AI provider settings to project
2830
2831 // Check if a custom system prompt is loaded
2832 for (const auto& tab : open_files_) {
2833 if (tab.is_system_prompt && !tab.filepath.empty()) {
2835 project.GetRelativePath(tab.filepath);
2836 project.agent_settings.use_custom_prompt = true;
2837 break;
2838 }
2839 }
2840}
2841
2843 const MultimodalCallbacks& callbacks) {
2844 multimodal_callbacks_ = callbacks;
2845}
2846
2848 const AutomationCallbacks& callbacks) {
2849 automation_callbacks_ = callbacks;
2850}
2851
2853 const AutomationTelemetry& telemetry) {
2854 auto predicate = [&](const AutomationTelemetry& entry) {
2855 return entry.test_id == telemetry.test_id;
2856 };
2857
2858 auto it = std::find_if(automation_state_.recent_tests.begin(),
2859 automation_state_.recent_tests.end(), predicate);
2860 if (it != automation_state_.recent_tests.end()) {
2861 *it = telemetry;
2862 } else {
2863 if (automation_state_.recent_tests.size() >= 16) {
2865 }
2866 automation_state_.recent_tests.push_back(telemetry);
2867 }
2868}
2869
2870void AgentChatWidget::SetLastPlanSummary(const std::string& /* summary */) {
2871 // Store the plan summary for display in the automation panel
2872 // TODO: Implement plan summary storage and display
2873 // This could be shown in the harness panel or logged
2874 if (toast_manager_) {
2875 toast_manager_->Show("Plan summary received", ToastType::kInfo, 2.0f);
2876 }
2877}
2878
2880 // Check if we should poll based on interval and auto-refresh setting
2882 return;
2883 }
2884
2885 absl::Time now = absl::Now();
2886 absl::Duration elapsed = now - automation_state_.last_poll;
2887
2888 if (elapsed < absl::Seconds(automation_state_.refresh_interval_seconds)) {
2889 return;
2890 }
2891
2892 // Update last poll time
2894
2895 // Check connection status
2896 bool was_connected = automation_state_.harness_connected;
2898
2899 // Notify on status change
2900 if (was_connected != automation_state_.harness_connected && toast_manager_) {
2902 toast_manager_->Show(ICON_MD_CHECK_CIRCLE " Automation harness connected",
2903 ToastType::kSuccess, 2.0f);
2904 } else {
2905 toast_manager_->Show(ICON_MD_WARNING " Automation harness disconnected",
2906 ToastType::kWarning, 2.0f);
2907 }
2908 }
2909}
2910
2912#if defined(YAZE_WITH_GRPC)
2913 try {
2914 // Attempt to get harness summaries from TestManager
2915 // If this succeeds, the harness infrastructure is working
2916 auto summaries = test::TestManager::Get().ListHarnessTestSummaries();
2917
2918 // If we get here, the test manager is operational
2919 // In a real implementation, you might want to ping the gRPC server
2921 return true;
2922 } catch (const std::exception& e) {
2924 return false;
2925 }
2926#else
2927 return false;
2928#endif
2929}
2930
2932 if (!chat_history_popup_) {
2933 return;
2934 }
2935
2936 // Get the current chat history from the agent service
2937 const auto& history = agent_service_.GetHistory();
2938
2939 // Update the popup with the latest history
2941}
2942
2943// Screenshot Preview Implementation
2944void AgentChatWidget::LoadScreenshotPreview(const std::filesystem::path& image_path) {
2945 // Unload any existing preview first
2947
2948 // Load the image using SDL
2949 SDL_Surface* surface = SDL_LoadBMP(image_path.string().c_str());
2950 if (!surface) {
2951 if (toast_manager_) {
2952 toast_manager_->Show(absl::StrFormat("Failed to load image: %s", SDL_GetError()),
2953 ToastType::kError, 3.0f);
2954 }
2955 return;
2956 }
2957
2958 // Get the renderer from ImGui backend
2959 ImGuiIO& io = ImGui::GetIO();
2960 auto* backend_data = static_cast<void**>(io.BackendRendererUserData);
2961 SDL_Renderer* renderer = nullptr;
2962
2963 if (backend_data) {
2964 // Assuming SDL renderer backend
2965 // The backend data structure has renderer as first member
2966 renderer = *reinterpret_cast<SDL_Renderer**>(backend_data);
2967 }
2968
2969 if (!renderer) {
2970 SDL_FreeSurface(surface);
2971 if (toast_manager_) {
2972 toast_manager_->Show("Failed to get SDL renderer", ToastType::kError, 3.0f);
2973 }
2974 return;
2975 }
2976
2977 // Create texture from surface
2978 SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
2979 if (!texture) {
2980 SDL_FreeSurface(surface);
2981 if (toast_manager_) {
2982 toast_manager_->Show(absl::StrFormat("Failed to create texture: %s", SDL_GetError()),
2983 ToastType::kError, 3.0f);
2984 }
2985 return;
2986 }
2987
2988 // Store texture info
2989 multimodal_state_.preview.texture_id = reinterpret_cast<void*>(texture);
2990 multimodal_state_.preview.width = surface->w;
2991 multimodal_state_.preview.height = surface->h;
2994
2995 SDL_FreeSurface(surface);
2996
2997 if (toast_manager_) {
2998 toast_manager_->Show(absl::StrFormat("Screenshot preview loaded (%dx%d)",
2999 surface->w, surface->h),
3000 ToastType::kSuccess, 2.0f);
3001 }
3002}
3003
3005 if (multimodal_state_.preview.texture_id != nullptr) {
3006 // Destroy the SDL texture
3007 SDL_Texture* texture = reinterpret_cast<SDL_Texture*>(multimodal_state_.preview.texture_id);
3008 SDL_DestroyTexture(texture);
3010 }
3014}
3015
3017 if (!multimodal_state_.last_capture_path.has_value()) {
3018 ImGui::TextDisabled("No screenshot to preview");
3019 return;
3020 }
3021
3022 const auto& theme = AgentUI::GetTheme();
3023
3024 // Display filename
3025 std::string filename = multimodal_state_.last_capture_path->filename().string();
3026 ImGui::TextColored(theme.text_secondary_color, "%s", filename.c_str());
3027
3028 // Preview controls
3029 if (ImGui::SmallButton(ICON_MD_CLOSE " Hide")) {
3031 }
3032 ImGui::SameLine();
3033
3035 // Display the actual texture
3036 ImVec2 preview_size(
3039 );
3040 ImGui::Image(multimodal_state_.preview.texture_id, preview_size);
3041
3042 // Scale slider
3043 ImGui::SetNextItemWidth(150);
3044 ImGui::SliderFloat("##preview_scale", &multimodal_state_.preview.preview_scale,
3045 0.1f, 2.0f, "Scale: %.1fx");
3046 } else {
3047 // Placeholder when texture not loaded
3048 ImGui::BeginChild("PreviewPlaceholder", ImVec2(200, 150), true);
3049 ImGui::SetCursorPos(ImVec2(60, 60));
3050 ImGui::TextColored(theme.text_secondary_color, ICON_MD_IMAGE);
3051 ImGui::SetCursorPosX(40);
3052 ImGui::TextWrapped("Preview placeholder");
3053 ImGui::TextDisabled("(Texture loading not yet implemented)");
3054 ImGui::EndChild();
3055 }
3056}
3057
3058// Region Selection Implementation
3062
3063 if (toast_manager_) {
3064 toast_manager_->Show(ICON_MD_CROP " Drag to select region",
3065 ToastType::kInfo, 3.0f);
3066 }
3067}
3068
3071 return;
3072 }
3073
3074 // Get the full window viewport
3075 ImGuiViewport* viewport = ImGui::GetMainViewport();
3076 ImVec2 viewport_pos = viewport->Pos;
3077 ImVec2 viewport_size = viewport->Size;
3078
3079 // Draw semi-transparent overlay
3080 ImDrawList* draw_list = ImGui::GetForegroundDrawList();
3081 ImVec2 overlay_min = viewport_pos;
3082 ImVec2 overlay_max = ImVec2(viewport_pos.x + viewport_size.x,
3083 viewport_pos.y + viewport_size.y);
3084
3085 draw_list->AddRectFilled(overlay_min, overlay_max,
3086 IM_COL32(0, 0, 0, 100));
3087
3088 // Handle mouse input for region selection
3089 ImGuiIO& io = ImGui::GetIO();
3090 ImVec2 mouse_pos = io.MousePos;
3091
3092 // Start dragging
3093 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
3098 }
3099
3100 // Update drag
3102 ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
3104
3105 // Calculate selection rectangle
3108
3110 std::min(start.x, end.x),
3111 std::min(start.y, end.y)
3112 );
3113
3115 std::max(start.x, end.x),
3116 std::max(start.y, end.y)
3117 );
3118
3119 // Draw selection rectangle
3120 draw_list->AddRect(
3123 IM_COL32(100, 180, 255, 255), 0.0f, 0, 2.0f
3124 );
3125
3126 // Draw dimensions label
3131
3132 std::string dimensions = absl::StrFormat("%.0f x %.0f", width, height);
3133 ImVec2 label_pos = ImVec2(
3136 );
3137
3138 draw_list->AddText(label_pos, IM_COL32(255, 255, 255, 255),
3139 dimensions.c_str());
3140 }
3141
3142 // End dragging
3144 ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
3148 }
3149
3150 // Cancel on Escape
3151 if (ImGui::IsKeyPressed(ImGuiKey_Escape)) {
3154 if (toast_manager_) {
3155 toast_manager_->Show("Region selection cancelled", ToastType::kInfo);
3156 }
3157 }
3158
3159 // Instructions overlay
3160 ImVec2 text_pos = ImVec2(viewport_pos.x + 20, viewport_pos.y + 20);
3161 draw_list->AddText(text_pos, IM_COL32(255, 255, 255, 255),
3162 "Drag to select region (ESC to cancel)");
3163}
3164
3166 // Calculate region bounds
3169
3170 float width = max.x - min.x;
3171 float height = max.y - min.y;
3172
3173 // Validate selection
3174 if (width < 10 || height < 10) {
3175 if (toast_manager_) {
3176 toast_manager_->Show("Region too small", ToastType::kWarning);
3177 }
3178 return;
3179 }
3180
3181 // Get the renderer from ImGui backend
3182 ImGuiIO& io = ImGui::GetIO();
3183 auto* backend_data = static_cast<void**>(io.BackendRendererUserData);
3184 SDL_Renderer* renderer = nullptr;
3185
3186 if (backend_data) {
3187 renderer = *reinterpret_cast<SDL_Renderer**>(backend_data);
3188 }
3189
3190 if (!renderer) {
3191 if (toast_manager_) {
3192 toast_manager_->Show("Failed to get SDL renderer", ToastType::kError, 3.0f);
3193 }
3194 return;
3195 }
3196
3197 // Get renderer size
3198 int full_width = 0;
3199 int full_height = 0;
3200 if (SDL_GetRendererOutputSize(renderer, &full_width, &full_height) != 0) {
3201 if (toast_manager_) {
3202 toast_manager_->Show(absl::StrFormat("Failed to get renderer size: %s", SDL_GetError()),
3203 ToastType::kError, 3.0f);
3204 }
3205 return;
3206 }
3207
3208 // Clamp region to renderer bounds
3209 int capture_x = std::max(0, static_cast<int>(min.x));
3210 int capture_y = std::max(0, static_cast<int>(min.y));
3211 int capture_width = std::min(static_cast<int>(width), full_width - capture_x);
3212 int capture_height = std::min(static_cast<int>(height), full_height - capture_y);
3213
3214 if (capture_width <= 0 || capture_height <= 0) {
3215 if (toast_manager_) {
3216 toast_manager_->Show("Invalid capture region", ToastType::kError);
3217 }
3218 return;
3219 }
3220
3221 // Create surface for the capture region
3222 SDL_Surface* surface = SDL_CreateRGBSurface(0, capture_width, capture_height,
3223 32, 0x00FF0000, 0x0000FF00,
3224 0x000000FF, 0xFF000000);
3225 if (!surface) {
3226 if (toast_manager_) {
3227 toast_manager_->Show(absl::StrFormat("Failed to create surface: %s", SDL_GetError()),
3228 ToastType::kError, 3.0f);
3229 }
3230 return;
3231 }
3232
3233 // Read pixels from the selected region
3234 SDL_Rect region_rect = {capture_x, capture_y, capture_width, capture_height};
3235 if (SDL_RenderReadPixels(renderer, &region_rect, SDL_PIXELFORMAT_ARGB8888,
3236 surface->pixels, surface->pitch) != 0) {
3237 SDL_FreeSurface(surface);
3238 if (toast_manager_) {
3239 toast_manager_->Show(absl::StrFormat("Failed to read pixels: %s", SDL_GetError()),
3240 ToastType::kError, 3.0f);
3241 }
3242 return;
3243 }
3244
3245 // Generate output path
3246 std::filesystem::path screenshot_dir = std::filesystem::temp_directory_path() / "yaze" / "screenshots";
3247 std::error_code ec;
3248 std::filesystem::create_directories(screenshot_dir, ec);
3249
3250 const int64_t timestamp_ms = absl::ToUnixMillis(absl::Now());
3251 std::filesystem::path output_path = screenshot_dir /
3252 std::filesystem::path(absl::StrFormat("region_%lld.bmp", static_cast<long long>(timestamp_ms)));
3253
3254 // Save the cropped image
3255 if (SDL_SaveBMP(surface, output_path.string().c_str()) != 0) {
3256 SDL_FreeSurface(surface);
3257 if (toast_manager_) {
3258 toast_manager_->Show(absl::StrFormat("Failed to save screenshot: %s", SDL_GetError()),
3259 ToastType::kError, 3.0f);
3260 }
3261 return;
3262 }
3263
3264 SDL_FreeSurface(surface);
3265
3266 // Store the capture path and load preview
3268 LoadScreenshotPreview(output_path);
3269
3270 if (toast_manager_) {
3272 absl::StrFormat("Region captured: %dx%d", capture_width, capture_height),
3274 );
3275 }
3276
3277 // Call the Gemini callback if available
3279 std::filesystem::path captured_path;
3280 auto status = multimodal_callbacks_.capture_snapshot(&captured_path);
3281 if (status.ok()) {
3282 multimodal_state_.last_capture_path = captured_path;
3283 multimodal_state_.status_message = "Region captured";
3284 multimodal_state_.last_updated = absl::Now();
3285 LoadScreenshotPreview(captured_path);
3287 }
3288 }
3289}
3290
3291} // namespace editor
3292} // namespace yaze
void SetText(const std::string &aText)
void SetLanguageDefinition(const LanguageDefinition &aLanguageDef)
The Rom class is used to load, save, and modify Rom data.
Definition rom.h:71
auto size() const
Definition rom.h:202
core::ResourceLabelManager * resource_label()
Definition rom.h:220
bool is_loaded() const
Definition rom.h:197
absl::StatusOr< ChatMessage > SendMessage(const std::string &message)
const std::vector< ChatMessage > & GetHistory() const
void ReplaceHistory(std::vector< ChatMessage > history)
static absl::Status Save(const std::filesystem::path &path, const Snapshot &snapshot)
static absl::StatusOr< Snapshot > Load(const std::filesystem::path &path)
ImGui popup drawer for displaying chat history on the left side.
void SetCaptureSnapshotCallback(CaptureSnapshotCallback callback)
void SetSendMessageCallback(SendMessageCallback callback)
void UpdateHistory(const std::vector< cli::agent::ChatMessage > &history)
void SetOpenChatCallback(OpenChatCallback callback)
Z3EDCommandCallbacks z3ed_callbacks_
void RenderProposalQuickActions(const cli::agent::ChatMessage &msg, int index)
void SetLastPlanSummary(const std::string &summary)
void RenderMessage(const cli::agent::ChatMessage &msg, int index)
void SaveAgentSettingsToProject(core::YazeProject &project)
void SetMultimodalCallbacks(const MultimodalCallbacks &callbacks)
void SetAutomationCallbacks(const AutomationCallbacks &callbacks)
void LoadAgentSettingsFromProject(const core::YazeProject &project)
void CreateNewFileInEditor(const std::string &filename)
void NotifyProposalCreated(const cli::agent::ChatMessage &msg, int new_total_proposals)
std::vector< ChatSession > chat_sessions_
void HandleAgentResponse(const absl::StatusOr< cli::agent::ChatMessage > &response)
void SetProposalDrawer(ProposalDrawer *drawer)
MultimodalCallbacks multimodal_callbacks_
cli::agent::ConversationalAgentService agent_service_
void FocusProposalDrawer(const std::string &proposal_id)
CollaborationCallbacks collaboration_callbacks_
void LoadScreenshotPreview(const std::filesystem::path &image_path)
void UpdateAgentConfig(const AgentConfigState &config)
CollaborationState collaboration_state_
void SwitchToSharedHistory(const std::string &session_id)
std::vector< FileEditorTab > open_files_
AutomationCallbacks automation_callbacks_
AgentChatHistoryPopup * chat_history_popup_
std::filesystem::path history_path_
void OpenFileInEditor(const std::string &filepath)
void UpdateHarnessTelemetry(const AutomationTelemetry &telemetry)
void SetToastManager(ToastManager *toast_manager)
void ApplyCollaborationSession(const CollaborationCallbacks::SessionContext &context, bool update_action_timestamp)
void SetChatHistoryPopup(AgentChatHistoryPopup *popup)
ImGui drawer for displaying and managing agent proposals.
void FocusProposal(const std::string &proposal_id)
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
static TestManager & Get()
static std::string ShowSaveFileDialog(const std::string &default_name="", const std::string &default_extension="")
ShowSaveFileDialog opens a save file dialog and returns the selected filepath. Uses global feature fl...
static std::string ShowOpenFileDialog()
ShowOpenFileDialog opens a file dialog and returns the selected filepath. Uses global feature flag to...
static absl::StatusOr< std::filesystem::path > GetConfigDirectory()
Get the user-specific configuration directory for YAZE.
#define ICON_MD_ROCKET_LAUNCH
Definition icons.h:1610
#define ICON_MD_MEETING_ROOM
Definition icons.h:1192
#define ICON_MD_FOLDER_OPEN
Definition icons.h:811
#define ICON_MD_SETTINGS
Definition icons.h:1697
#define ICON_MD_DATA_OBJECT
Definition icons.h:519
#define ICON_MD_CAMERA
Definition icons.h:352
#define ICON_MD_LINK
Definition icons.h:1088
#define ICON_MD_INFO
Definition icons.h:991
#define ICON_MD_CHAT
Definition icons.h:392
#define ICON_MD_CANCEL
Definition icons.h:362
#define ICON_MD_STORAGE
Definition icons.h:1863
#define ICON_MD_WARNING
Definition icons.h:2121
#define ICON_MD_NO_PHOTOGRAPHY
Definition icons.h:1313
#define ICON_MD_CAMERA_ALT
Definition icons.h:353
#define ICON_MD_NOTE_ADD
Definition icons.h:1328
#define ICON_MD_PLAY_ARROW
Definition icons.h:1477
#define ICON_MD_CHECK
Definition icons.h:395
#define ICON_MD_LOGOUT
Definition icons.h:1146
#define ICON_MD_CLOUD_DONE
Definition icons.h:423
#define ICON_MD_REFRESH
Definition icons.h:1570
#define ICON_MD_DIFFERENCE
Definition icons.h:557
#define ICON_MD_STOP
Definition icons.h:1860
#define ICON_MD_LABEL
Definition icons.h:1051
#define ICON_MD_MESSAGE
Definition icons.h:1199
#define ICON_MD_VISIBILITY
Definition icons.h:2099
#define ICON_MD_EDIT
Definition icons.h:643
#define ICON_MD_LOGIN
Definition icons.h:1144
#define ICON_MD_SETTINGS_ETHERNET
Definition icons.h:1705
#define ICON_MD_REPLAY
Definition icons.h:1586
#define ICON_MD_ERROR
Definition icons.h:684
#define ICON_MD_LIST
Definition icons.h:1092
#define ICON_MD_ADD
Definition icons.h:84
#define ICON_MD_PENDING
Definition icons.h:1396
#define ICON_MD_SEND
Definition icons.h:1681
#define ICON_MD_WIFI
Definition icons.h:2158
#define ICON_MD_SHARE
Definition icons.h:1719
#define ICON_MD_PLAY_CIRCLE
Definition icons.h:1478
#define ICON_MD_IMAGE
Definition icons.h:980
#define ICON_MD_CHECK_CIRCLE
Definition icons.h:398
#define ICON_MD_TERMINAL
Definition icons.h:1949
#define ICON_MD_PREVIEW
Definition icons.h:1510
#define ICON_MD_ACCESS_TIME
Definition icons.h:71
#define ICON_MD_DASHBOARD
Definition icons.h:515
#define ICON_MD_SAVE
Definition icons.h:1642
#define ICON_MD_TAB
Definition icons.h:1928
#define ICON_MD_PEOPLE
Definition icons.h:1399
#define ICON_MD_MENU
Definition icons.h:1194
#define ICON_MD_FOLDER
Definition icons.h:807
#define ICON_MD_KEY
Definition icons.h:1024
#define ICON_MD_CONTENT_COPY
Definition icons.h:463
#define ICON_MD_DELETE_FOREVER
Definition icons.h:529
#define ICON_MD_SYNC
Definition icons.h:1917
#define ICON_MD_CLOUD
Definition icons.h:421
#define ICON_MD_PHOTO
Definition icons.h:1449
#define ICON_MD_CLOSE
Definition icons.h:416
#define ICON_MD_HELP
Definition icons.h:931
#define ICON_MD_CROP
Definition icons.h:487
#define ICON_MD_QUERY_STATS
Definition icons.h:1532
#define ICON_MD_CLOUD_UPLOAD
Definition icons.h:428
#define ICON_MD_PHOTO_CAMERA
Definition icons.h:1451
#define ICON_MD_ADD_CIRCLE
Definition icons.h:93
#define ICON_MD_SMART_TOY
Definition icons.h:1779
std::filesystem::path ExpandUserPath(std::string path)
void RenderTable(const ChatMessage::TableData &table_data)
std::filesystem::path ResolveHistoryPath(const std::string &session_id="")
const AgentUITheme & GetTheme()
void RenderSectionHeader(const char *icon, const char *label, const ImVec4 &color)
std::string GetFileName(const std::string &filename)
Gets the filename from a full path.
Definition file_util.cc:19
std::string GetFileExtension(const std::string &filename)
Gets the file extension from a filename.
Definition file_util.cc:15
std::string LoadFile(const std::string &filename)
Loads the entire contents of a file into a string.
Definition file_util.cc:23
Main namespace for the application.
static const LanguageDefinition & Lua()
static const LanguageDefinition & C()
static const LanguageDefinition & CPlusPlus()
std::vector< std::vector< std::string > > rows
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > labels_
Definition project.h:235
Modern project structure with comprehensive settings consolidation.
Definition project.h:78
std::unordered_map< std::string, std::unordered_map< std::string, std::string > > resource_labels
Definition project.h:100
struct yaze::core::YazeProject::AgentSettings agent_settings
std::string GetRelativePath(const std::string &absolute_path) const
Definition project.cc:539
absl::Status InitializeEmbeddedLabels()
Definition project.cc:887
std::string GetAbsolutePath(const std::string &relative_path) const
Definition project.cc:553
std::vector< cli::agent::ChatMessage > history
std::function< void(const std::string &)> focus_proposal
std::vector< AutomationTelemetry > recent_tests
std::function< absl::StatusOr< SessionContext >()> refresh_session
std::function< absl::StatusOr< SessionContext >(const std::string &)> join_session
std::function< absl::StatusOr< SessionContext >(const std::string &)> host_session
std::function< absl::Status(const std::filesystem::path &, const std::string &)> send_to_gemini
std::function< absl::Status(std::filesystem::path *)> capture_snapshot
std::optional< std::filesystem::path > last_capture_path
std::function< absl::StatusOr< std::string >()> generate_rom_diff
std::function< absl::StatusOr< std::string >(const std::string &)> diff_proposal
std::function< absl::StatusOr< std::vector< std::string > >()> list_proposals
std::function< absl::Status(const std::string &)> run_agent_task
std::function< absl::Status(const std::string &)> accept_proposal
std::function< absl::Status(const std::string &)> reject_proposal