yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
agent_editor.cc
Go to the documentation of this file.
2
3#include <filesystem>
4#include <fstream>
5#include <memory>
6
7#include "absl/strings/match.h"
8#include "absl/strings/str_format.h"
9#include "absl/time/clock.h"
15#include "app/gui/icons.h"
16#include "app/rom.h"
17#include "util/file_util.h"
18#include "util/platform_paths.h"
19
20#ifdef YAZE_WITH_GRPC
22#endif
23
24#if defined(YAZE_WITH_JSON)
25#include "nlohmann/json.hpp"
26#endif
27
28namespace yaze {
29namespace editor {
30
33 chat_widget_ = std::make_unique<AgentChatWidget>();
34 local_coordinator_ = std::make_unique<AgentCollaborationCoordinator>();
35 prompt_editor_ = std::make_unique<TextEditor>();
36 common_tiles_editor_ = std::make_unique<TextEditor>();
37
38 // Initialize default configuration (legacy)
42
43 // Initialize default bot profile
44 current_profile_.name = "Default Z3ED Bot";
45 current_profile_.description = "Default bot for Zelda 3 ROM editing";
50 current_profile_.tags = {"default", "z3ed"};
51
52 // Setup text editors
53 prompt_editor_->SetLanguageDefinition(
55 prompt_editor_->SetReadOnly(false);
56 prompt_editor_->SetShowWhitespaces(false);
57
58 common_tiles_editor_->SetLanguageDefinition(
60 common_tiles_editor_->SetReadOnly(false);
61 common_tiles_editor_->SetShowWhitespaces(false);
62
63 // Ensure profiles directory exists
65}
66
68
70 // Base initialization
72}
73
74absl::Status AgentEditor::Load() {
75 // Load agent configuration from project/settings
76 // Try to load all bot profiles
77 auto profiles_dir = GetProfilesDirectory();
78 if (std::filesystem::exists(profiles_dir)) {
79 for (const auto& entry :
80 std::filesystem::directory_iterator(profiles_dir)) {
81 if (entry.path().extension() == ".json") {
82 std::ifstream file(entry.path());
83 if (file.is_open()) {
84 std::string json_content((std::istreambuf_iterator<char>(file)),
85 std::istreambuf_iterator<char>());
86 auto profile_or = JsonToProfile(json_content);
87 if (profile_or.ok()) {
88 loaded_profiles_.push_back(profile_or.value());
89 }
90 }
91 }
92 }
93 }
94 return absl::OkStatus();
95}
96
97absl::Status AgentEditor::Save() {
98 // Save current profile
100}
101
102absl::Status AgentEditor::Update() {
103 if (!active_)
104 return absl::OkStatus();
105
106 // Draw configuration dashboard
108
109 // Chat widget is drawn separately (not here)
110
111 return absl::OkStatus();
112}
113
115 ProposalDrawer* proposal_drawer,
116 Rom* rom) {
117 toast_manager_ = toast_manager;
118 proposal_drawer_ = proposal_drawer;
119 rom_ = rom;
120
121 if (chat_widget_) {
122 chat_widget_->SetToastManager(toast_manager);
123 chat_widget_->SetProposalDrawer(proposal_drawer);
124 if (rom) {
125 chat_widget_->SetRomContext(rom);
126 }
127 }
128
131}
132
134 rom_ = rom;
135 if (chat_widget_) {
136 chat_widget_->SetRomContext(rom);
137 }
138}
139
141 if (!active_)
142 return;
143
144 // Animate retro effects
145 ImGuiIO& io = ImGui::GetIO();
146 pulse_animation_ += io.DeltaTime * 2.0f;
147 scanline_offset_ += io.DeltaTime * 0.4f;
148 if (scanline_offset_ > 1.0f) scanline_offset_ -= 1.0f;
149 glitch_timer_ += io.DeltaTime * 5.0f;
150 blink_counter_ = static_cast<int>(pulse_animation_ * 2.0f) % 2;
151
152 // Pulsing glow for window
153 float pulse = 0.5f + 0.5f * std::sin(pulse_animation_);
154 ImGui::PushStyleColor(ImGuiCol_TitleBgActive, ImVec4(
155 0.1f + 0.1f * pulse,
156 0.2f + 0.15f * pulse,
157 0.3f + 0.2f * pulse,
158 1.0f
159 ));
160
161 ImGui::SetNextWindowSize(ImVec2(1200, 800), ImGuiCond_FirstUseEver);
162 ImGui::Begin(ICON_MD_SMART_TOY " AI AGENT PLATFORM [v0.4.x]", &active_,
163 ImGuiWindowFlags_MenuBar);
164
165 // Menu bar
166 if (ImGui::BeginMenuBar()) {
167 if (ImGui::BeginMenu(ICON_MD_MENU " File")) {
168 if (ImGui::MenuItem(ICON_MD_SAVE " Save Profile")) {
169 Save();
170 if (toast_manager_) {
171 toast_manager_->Show("Bot profile saved", ToastType::kSuccess);
172 }
173 }
174 if (ImGui::MenuItem(ICON_MD_FILE_UPLOAD " Export Profile...")) {
175 // TODO: Open file dialog for export
176 if (toast_manager_) {
177 toast_manager_->Show("Export functionality coming soon",
179 }
180 }
181 if (ImGui::MenuItem(ICON_MD_FILE_DOWNLOAD " Import Profile...")) {
182 // TODO: Open file dialog for import
183 if (toast_manager_) {
184 toast_manager_->Show("Import functionality coming soon",
186 }
187 }
188 ImGui::EndMenu();
189 }
190
191 if (ImGui::BeginMenu(ICON_MD_VIEW_LIST " View")) {
192 if (ImGui::MenuItem(ICON_MD_CHAT " Open Chat Window", "Ctrl+Shift+A")) {
194 }
195 ImGui::Separator();
196 ImGui::MenuItem(ICON_MD_EDIT " Show Prompt Editor", nullptr,
198 ImGui::MenuItem(ICON_MD_FOLDER " Show Bot Profiles", nullptr,
200 ImGui::MenuItem(ICON_MD_HISTORY " Show Chat History", nullptr,
202 ImGui::MenuItem(ICON_MD_ANALYTICS " Show Metrics Dashboard", nullptr,
204 ImGui::EndMenu();
205 }
206
207 ImGui::EndMenuBar();
208 }
209
210 // Compact tabbed interface (combined tabs)
211 if (ImGui::BeginTabBar("AgentEditorTabs", ImGuiTabBarFlags_None)) {
212 // Bot Studio Tab - Modular 3-column layout
213 if (ImGui::BeginTabItem(ICON_MD_SMART_TOY " Bot Studio")) {
214 ImGui::Spacing();
215
216 // Three-column layout: Config+Status | Editors | Profiles
217 ImGuiTableFlags table_flags = ImGuiTableFlags_Resizable |
218 ImGuiTableFlags_BordersInnerV |
219 ImGuiTableFlags_SizingStretchProp;
220
221 if (ImGui::BeginTable("BotStudioLayout", 3, table_flags)) {
222 ImGui::TableSetupColumn("Settings", ImGuiTableColumnFlags_WidthFixed,
223 320.0f);
224 ImGui::TableSetupColumn("Editors", ImGuiTableColumnFlags_WidthStretch);
225 ImGui::TableSetupColumn("Profiles", ImGuiTableColumnFlags_WidthFixed,
226 280.0f);
227 ImGui::TableNextRow();
228
229 // Column 1: AI Provider, Behavior, ROM, Tips, Metrics (merged!)
230 ImGui::TableNextColumn();
231 ImGui::PushID("SettingsColumn");
232
233 // Provider settings (always visible)
235 ImGui::Spacing();
236
237 // Status cards (always visible)
239
240 ImGui::PopID();
241
242 // Column 2: Tabbed Editors
243 ImGui::TableNextColumn();
244 ImGui::PushID("EditorsColumn");
245
246 if (ImGui::BeginTabBar("EditorTabs", ImGuiTabBarFlags_None)) {
247 if (ImGui::BeginTabItem(ICON_MD_EDIT " System Prompt")) {
249 ImGui::EndTabItem();
250 }
251
252 if (ImGui::BeginTabItem(ICON_MD_GRID_ON " Common Tiles")) {
254 ImGui::EndTabItem();
255 }
256
257 if (ImGui::BeginTabItem(ICON_MD_ADD " New Prompt")) {
259 ImGui::EndTabItem();
260 }
261
262 ImGui::EndTabBar();
263 }
264
265 ImGui::PopID();
266
267 // Column 3: Bot Profiles
268 ImGui::TableNextColumn();
269 ImGui::PushID("ProfilesColumn");
271 ImGui::PopID();
272
273 ImGui::EndTable();
274 }
275
276 ImGui::EndTabItem();
277 }
278
279 // Session Manager Tab (combines History + Metrics)
280 if (ImGui::BeginTabItem(ICON_MD_HISTORY " Sessions & History")) {
281 ImGui::Spacing();
282
283 // Two-column layout
284 if (ImGui::BeginTable(
285 "SessionLayout", 2,
286 ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV)) {
287 ImGui::TableSetupColumn("History", ImGuiTableColumnFlags_WidthStretch,
288 0.6f);
289 ImGui::TableSetupColumn("Metrics", ImGuiTableColumnFlags_WidthStretch,
290 0.4f);
291 ImGui::TableNextRow();
292
293 // LEFT: Chat History
294 ImGui::TableSetColumnIndex(0);
296
297 // RIGHT: Metrics
298 ImGui::TableSetColumnIndex(1);
300
301 ImGui::EndTable();
302 }
303
304 ImGui::EndTabItem();
305 }
306
307 ImGui::EndTabBar();
308 }
309
310 ImGui::End();
311}
312
314
315 // AI Provider Configuration
316 if (ImGui::CollapsingHeader(ICON_MD_SETTINGS " AI Provider",
317 ImGuiTreeNodeFlags_DefaultOpen)) {
318 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
319 ICON_MD_SMART_TOY " Provider Selection");
320 ImGui::Spacing();
321
322 // Provider buttons (large, visual)
323 ImVec2 button_size(ImGui::GetContentRegionAvail().x / 3 - 8, 60);
324
325 bool is_mock = (current_profile_.provider == "mock");
326 bool is_ollama = (current_profile_.provider == "ollama");
327 bool is_gemini = (current_profile_.provider == "gemini");
328
329 if (is_mock)
330 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.6f, 0.6f, 0.8f));
331 if (ImGui::Button(ICON_MD_SETTINGS " Mock", button_size)) {
332 current_profile_.provider = "mock";
333 }
334 if (is_mock)
335 ImGui::PopStyleColor();
336
337 ImGui::SameLine();
338 if (is_ollama)
339 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.8f, 0.4f, 0.8f));
340 if (ImGui::Button(ICON_MD_CLOUD " Ollama", button_size)) {
341 current_profile_.provider = "ollama";
342 }
343 if (is_ollama)
344 ImGui::PopStyleColor();
345
346 ImGui::SameLine();
347 if (is_gemini)
348 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.196f, 0.6f, 0.8f, 0.8f));
349 if (ImGui::Button(ICON_MD_SMART_TOY " Gemini", button_size)) {
350 current_profile_.provider = "gemini";
351 }
352 if (is_gemini)
353 ImGui::PopStyleColor();
354
355 ImGui::Separator();
356 ImGui::Spacing();
357
358 // Provider-specific settings
359 if (current_profile_.provider == "ollama") {
360 ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.4f, 1.0f),
361 ICON_MD_SETTINGS " Ollama Settings");
362 ImGui::Text("Model:");
363 ImGui::SetNextItemWidth(-1);
364 static char model_buf[128] = "qwen2.5-coder:7b";
365 if (!current_profile_.model.empty()) {
366 strncpy(model_buf, current_profile_.model.c_str(),
367 sizeof(model_buf) - 1);
368 }
369 if (ImGui::InputTextWithHint("##ollama_model",
370 "e.g., qwen2.5-coder:7b, llama3.2",
371 model_buf, sizeof(model_buf))) {
372 current_profile_.model = model_buf;
373 }
374
375 ImGui::Text("Host URL:");
376 ImGui::SetNextItemWidth(-1);
377 static char host_buf[256] = "http://localhost:11434";
378 strncpy(host_buf, current_profile_.ollama_host.c_str(),
379 sizeof(host_buf) - 1);
380 if (ImGui::InputText("##ollama_host", host_buf, sizeof(host_buf))) {
381 current_profile_.ollama_host = host_buf;
382 }
383 } else if (current_profile_.provider == "gemini") {
384 ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f),
385 ICON_MD_SMART_TOY " Gemini Settings");
386
387 // Load from environment button
388 if (ImGui::Button(ICON_MD_REFRESH " Load from Environment")) {
389 const char* gemini_key = std::getenv("GEMINI_API_KEY");
390 if (gemini_key) {
391 current_profile_.gemini_api_key = gemini_key;
393 if (toast_manager_) {
394 toast_manager_->Show("Gemini API key loaded", ToastType::kSuccess);
395 }
396 } else {
397 if (toast_manager_) {
398 toast_manager_->Show("GEMINI_API_KEY not found",
400 }
401 }
402 }
403
404 ImGui::Spacing();
405
406 ImGui::Text("Model:");
407 ImGui::SetNextItemWidth(-1);
408 static char model_buf[128] = "gemini-1.5-flash";
409 if (!current_profile_.model.empty()) {
410 strncpy(model_buf, current_profile_.model.c_str(),
411 sizeof(model_buf) - 1);
412 }
413 if (ImGui::InputTextWithHint("##gemini_model", "e.g., gemini-1.5-flash",
414 model_buf, sizeof(model_buf))) {
415 current_profile_.model = model_buf;
416 }
417
418 ImGui::Text("API Key:");
419 ImGui::SetNextItemWidth(-1);
420 static char key_buf[256] = "";
421 if (!current_profile_.gemini_api_key.empty() && key_buf[0] == '\0') {
422 strncpy(key_buf, current_profile_.gemini_api_key.c_str(),
423 sizeof(key_buf) - 1);
424 }
425 if (ImGui::InputText("##gemini_key", key_buf, sizeof(key_buf),
426 ImGuiInputTextFlags_Password)) {
428 }
429 if (!current_profile_.gemini_api_key.empty()) {
430 ImGui::TextColored(ImVec4(0.133f, 0.545f, 0.133f, 1.0f),
431 ICON_MD_CHECK_CIRCLE " API key configured");
432 }
433 } else {
434 ImGui::TextDisabled(ICON_MD_INFO " Mock mode - no configuration needed");
435 }
436 }
437
438 // Behavior Settings
439 if (ImGui::CollapsingHeader(ICON_MD_TUNE " Behavior",
440 ImGuiTreeNodeFlags_DefaultOpen)) {
441 ImGui::Checkbox(ICON_MD_VISIBILITY " Show Reasoning",
443 ImGui::Checkbox(ICON_MD_ANALYTICS " Verbose Output",
445 ImGui::SliderInt(ICON_MD_LOOP " Max Tool Iterations",
447 ImGui::SliderInt(ICON_MD_REFRESH " Max Retry Attempts",
449 }
450
451 // Profile Metadata
452 if (ImGui::CollapsingHeader(ICON_MD_INFO " Profile Info")) {
453 ImGui::Text("Name:");
454 static char name_buf[128];
455 strncpy(name_buf, current_profile_.name.c_str(), sizeof(name_buf) - 1);
456 if (ImGui::InputText("##profile_name", name_buf, sizeof(name_buf))) {
457 current_profile_.name = name_buf;
458 }
459
460 ImGui::Text("Description:");
461 static char desc_buf[256];
462 strncpy(desc_buf, current_profile_.description.c_str(),
463 sizeof(desc_buf) - 1);
464 if (ImGui::InputTextMultiline("##profile_desc", desc_buf, sizeof(desc_buf),
465 ImVec2(-1, 60))) {
466 current_profile_.description = desc_buf;
467 }
468
469 ImGui::Text("Tags (comma-separated):");
470 static char tags_buf[256];
471 if (tags_buf[0] == '\0' && !current_profile_.tags.empty()) {
472 std::string tags_str;
473 for (size_t i = 0; i < current_profile_.tags.size(); ++i) {
474 if (i > 0)
475 tags_str += ", ";
476 tags_str += current_profile_.tags[i];
477 }
478 strncpy(tags_buf, tags_str.c_str(), sizeof(tags_buf) - 1);
479 }
480 if (ImGui::InputText("##profile_tags", tags_buf, sizeof(tags_buf))) {
481 // Parse comma-separated tags
482 current_profile_.tags.clear();
483 std::string tags_str(tags_buf);
484 size_t pos = 0;
485 while ((pos = tags_str.find(',')) != std::string::npos) {
486 std::string tag = tags_str.substr(0, pos);
487 // Trim whitespace
488 tag.erase(0, tag.find_first_not_of(" \t"));
489 tag.erase(tag.find_last_not_of(" \t") + 1);
490 if (!tag.empty()) {
491 current_profile_.tags.push_back(tag);
492 }
493 tags_str.erase(0, pos + 1);
494 }
495 if (!tags_str.empty()) {
496 tags_str.erase(0, tags_str.find_first_not_of(" \t"));
497 tags_str.erase(tags_str.find_last_not_of(" \t") + 1);
498 if (!tags_str.empty()) {
499 current_profile_.tags.push_back(tags_str);
500 }
501 }
502 }
503 }
504
505 // Apply button
506 ImGui::Spacing();
507 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.133f, 0.545f, 0.133f, 0.8f));
508 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
509 ImVec4(0.133f, 0.545f, 0.133f, 1.0f));
510 if (ImGui::Button(ICON_MD_CHECK " Apply & Save Configuration",
511 ImVec2(-1, 40))) {
512 // Update legacy config
520
522 Save();
523
524 if (toast_manager_) {
525 toast_manager_->Show("Configuration applied and saved",
527 }
528 }
529 ImGui::PopStyleColor(2);
530
532}
533
535 // Always visible status cards (no collapsing)
536
537 // Chat Status Card
538 ImGui::BeginChild("ChatStatusCard", ImVec2(0, 100), true);
539 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_CHAT " Chat");
540 ImGui::Separator();
541
542 if (chat_widget_ && chat_widget_->is_active()) {
543 ImGui::TextColored(ImVec4(0.133f, 0.545f, 0.133f, 1.0f),
544 ICON_MD_CHECK_CIRCLE " Active");
545 } else {
546 ImGui::TextDisabled(ICON_MD_CANCEL " Inactive");
547 }
548
549 ImGui::Spacing();
550 if (ImGui::Button(ICON_MD_OPEN_IN_NEW " Open", ImVec2(-1, 0))) {
552 }
553 ImGui::EndChild();
554
555 ImGui::Spacing();
556
557 // ROM Context Card
558 ImGui::BeginChild("RomStatusCard", ImVec2(0, 100), true);
559 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_GAMEPAD " ROM");
560 ImGui::Separator();
561
562 if (rom_ && rom_->is_loaded()) {
563 ImGui::TextColored(ImVec4(0.133f, 0.545f, 0.133f, 1.0f),
564 ICON_MD_CHECK_CIRCLE " Loaded");
565 ImGui::TextDisabled("Title: %s", rom_->title().c_str());
566 ImGui::TextDisabled("Tools: Ready");
567 } else {
568 ImGui::TextColored(ImVec4(0.8f, 0.2f, 0.2f, 1.0f),
569 ICON_MD_WARNING " Not Loaded");
570 ImGui::TextDisabled("Load ROM for AI tools");
571 }
572 ImGui::EndChild();
573
574 ImGui::Spacing();
575
576 // Quick Tips Card
577 ImGui::BeginChild("QuickTipsCard", ImVec2(0, 150), true);
578 ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f),
579 ICON_MD_TIPS_AND_UPDATES " Quick Tips");
580 ImGui::Separator();
581 ImGui::Spacing();
582
583 ImGui::BulletText("Ctrl+H: Toggle chat popup");
584 ImGui::BulletText("Ctrl+P: View proposals");
585 ImGui::BulletText("Edit prompts in center");
586 ImGui::BulletText("Create custom bots");
587 ImGui::BulletText("Save/load chat sessions");
588
589 ImGui::EndChild();
590}
591
593 if (ImGui::CollapsingHeader(ICON_MD_ANALYTICS " Quick Metrics")) {
594 if (chat_widget_) {
595 // Get metrics from the chat widget's service
596 ImGui::TextDisabled("View detailed metrics in the Metrics tab");
597 } else {
598 ImGui::TextDisabled("No metrics available");
599 }
600 }
601}
602
604 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
605 ICON_MD_EDIT " Prompt Editor");
606 ImGui::Separator();
607 ImGui::Spacing();
608
609 // Compact prompt file selector
610 ImGui::Text("File:");
611 ImGui::SetNextItemWidth(-45);
612 if (ImGui::BeginCombo("##prompt_file", active_prompt_file_.c_str())) {
613 if (ImGui::Selectable("system_prompt.txt",
614 active_prompt_file_ == "system_prompt.txt")) {
615 active_prompt_file_ = "system_prompt.txt";
617 }
618 if (ImGui::Selectable("system_prompt_v2.txt",
619 active_prompt_file_ == "system_prompt_v2.txt")) {
620 active_prompt_file_ = "system_prompt_v2.txt";
622 }
623 if (ImGui::Selectable("system_prompt_v3.txt",
624 active_prompt_file_ == "system_prompt_v3.txt")) {
625 active_prompt_file_ = "system_prompt_v3.txt";
627 }
628 ImGui::EndCombo();
629 }
630
631 ImGui::SameLine();
632 if (ImGui::SmallButton(ICON_MD_REFRESH)) {
634 }
635 if (ImGui::IsItemHovered()) {
636 ImGui::SetTooltip("Reload from disk");
637 }
638
639 // Load prompt file if not initialized
641 std::string asset_path = "agent/" + active_prompt_file_;
642 auto content_result = AssetLoader::LoadTextFile(asset_path);
643
644 if (content_result.ok()) {
645 prompt_editor_->SetText(*content_result);
646 current_profile_.system_prompt = *content_result;
648
649 if (toast_manager_) {
650 toast_manager_->Show(absl::StrFormat(ICON_MD_CHECK_CIRCLE " Loaded %s",
652 ToastType::kSuccess, 2.0f);
653 }
654 } else {
655 // Show detailed error in console
656 std::cerr << "❌ Failed to load " << active_prompt_file_ << "\n";
657 std::cerr << " Error: " << content_result.status().message() << "\n";
658
659 // Set placeholder with instructions
660 std::string placeholder = absl::StrFormat(
661 "# System prompt file not found: %s\n"
662 "# Error: %s\n\n"
663 "# Please ensure the file exists in:\n"
664 "# - assets/agent/%s\n"
665 "# - Or Contents/Resources/agent/%s (macOS bundle)\n\n"
666 "# You can create a custom prompt here and save it to your bot "
667 "profile.",
668 active_prompt_file_, content_result.status().message(),
670
671 prompt_editor_->SetText(placeholder);
673 }
674 }
675
676 ImGui::Spacing();
677
678 // Text editor
679 if (prompt_editor_) {
680 ImVec2 editor_size = ImVec2(ImGui::GetContentRegionAvail().x,
681 ImGui::GetContentRegionAvail().y - 50);
682 prompt_editor_->Render("##prompt_editor", editor_size, true);
683
684 // Save button
685 ImGui::Spacing();
686 if (ImGui::Button(ICON_MD_SAVE " Save Prompt to Profile", ImVec2(-1, 0))) {
688 if (toast_manager_) {
689 toast_manager_->Show("System prompt saved to profile",
691 }
692 }
693 }
694
695 ImGui::Spacing();
696 ImGui::TextWrapped(
697 "Edit the system prompt that guides the AI agent's behavior. Changes are "
698 "saved to the current bot profile.");
699}
700
702 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
703 ICON_MD_FOLDER " Bot Profile Manager");
704 ImGui::Separator();
705 ImGui::Spacing();
706
707 // Current profile display
708 ImGui::BeginChild("CurrentProfile", ImVec2(0, 150), true);
709 ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f),
710 ICON_MD_STAR " Current Profile");
711 ImGui::Separator();
712 ImGui::Text("Name: %s", current_profile_.name.c_str());
713 ImGui::Text("Provider: %s", current_profile_.provider.c_str());
714 if (!current_profile_.model.empty()) {
715 ImGui::Text("Model: %s", current_profile_.model.c_str());
716 }
717 ImGui::TextWrapped("Description: %s",
719 ? "No description"
720 : current_profile_.description.c_str());
721 ImGui::EndChild();
722
723 ImGui::Spacing();
724
725 // Profile management buttons
726 if (ImGui::Button(ICON_MD_ADD " Create New Profile", ImVec2(-1, 0))) {
727 // Create new profile from current
728 BotProfile new_profile = current_profile_;
729 new_profile.name = "New Profile";
730 new_profile.created_at = absl::Now();
731 new_profile.modified_at = absl::Now();
732 current_profile_ = new_profile;
733 if (toast_manager_) {
734 toast_manager_->Show("New profile created. Configure and save it.",
736 }
737 }
738
739 ImGui::Spacing();
740
741 // Saved profiles list
742 ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f),
743 ICON_MD_LIST " Saved Profiles");
744 ImGui::Separator();
745
746 ImGui::BeginChild("ProfilesList", ImVec2(0, 0), true);
747
748 if (loaded_profiles_.empty()) {
749 ImGui::TextDisabled(
750 "No saved profiles. Create and save a profile to see it here.");
751 } else {
752 for (size_t i = 0; i < loaded_profiles_.size(); ++i) {
753 const auto& profile = loaded_profiles_[i];
754 ImGui::PushID(static_cast<int>(i));
755
756 bool is_current = (profile.name == current_profile_.name);
757 if (is_current) {
758 ImGui::PushStyleColor(ImGuiCol_Button,
759 ImVec4(0.196f, 0.6f, 0.8f, 0.6f));
760 }
761
762 if (ImGui::Button(profile.name.c_str(),
763 ImVec2(ImGui::GetContentRegionAvail().x - 80, 0))) {
764 LoadBotProfile(profile.name);
765 if (toast_manager_) {
767 absl::StrFormat("Loaded profile: %s", profile.name),
769 }
770 }
771
772 if (is_current) {
773 ImGui::PopStyleColor();
774 }
775
776 ImGui::SameLine();
777 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.2f, 0.2f, 0.6f));
778 if (ImGui::SmallButton(ICON_MD_DELETE)) {
779 DeleteBotProfile(profile.name);
780 if (toast_manager_) {
782 absl::StrFormat("Deleted profile: %s", profile.name),
784 }
785 }
786 ImGui::PopStyleColor();
787
788 ImGui::TextDisabled(" %s | %s", profile.provider.c_str(),
789 profile.description.empty()
790 ? "No description"
791 : profile.description.c_str());
792 ImGui::Spacing();
793
794 ImGui::PopID();
795 }
796 }
797
798 ImGui::EndChild();
799}
800
802 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
803 ICON_MD_HISTORY " Chat History Viewer");
804 ImGui::Separator();
805 ImGui::Spacing();
806
807 if (ImGui::Button(ICON_MD_REFRESH " Refresh History")) {
809 }
810
811 ImGui::SameLine();
812 if (ImGui::Button(ICON_MD_DELETE " Clear History")) {
813 if (chat_widget_) {
814 // Clear through the chat widget's service
815 if (toast_manager_) {
816 toast_manager_->Show("Chat history cleared", ToastType::kInfo);
817 }
818 }
819 }
820
821 ImGui::Spacing();
822 ImGui::Separator();
823
824 // Get history from chat widget
826 // Access the service's history through the chat widget
827 // For now, show a placeholder
829 }
830
831 ImGui::BeginChild("HistoryList", ImVec2(0, 0), true);
832
833 if (cached_history_.empty()) {
834 ImGui::TextDisabled(
835 "No chat history. Start a conversation in the chat window.");
836 } else {
837 for (const auto& msg : cached_history_) {
838 bool from_user = (msg.sender == cli::agent::ChatMessage::Sender::kUser);
839 ImVec4 color = from_user ? ImVec4(0.6f, 0.8f, 1.0f, 1.0f)
840 : ImVec4(0.4f, 0.8f, 0.4f, 1.0f);
841
842 ImGui::PushStyleColor(ImGuiCol_Text, color);
843 ImGui::Text("%s:", from_user ? "User" : "Agent");
844 ImGui::PopStyleColor();
845
846 ImGui::SameLine();
847 ImGui::TextDisabled("%s", absl::FormatTime("%H:%M:%S", msg.timestamp,
848 absl::LocalTimeZone())
849 .c_str());
850
851 ImGui::TextWrapped("%s", msg.message.c_str());
852 ImGui::Spacing();
853 ImGui::Separator();
854 }
855 }
856
857 ImGui::EndChild();
858}
859
861 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
862 ICON_MD_ANALYTICS " Session Metrics & Analytics");
863 ImGui::Separator();
864 ImGui::Spacing();
865
866 // Get metrics from chat widget service
867 if (chat_widget_) {
868 // For now show placeholder metrics structure
869 if (ImGui::BeginTable("MetricsTable", 2,
870 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
871 ImGui::TableSetupColumn("Metric", ImGuiTableColumnFlags_WidthFixed,
872 200.0f);
873 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
874 ImGui::TableHeadersRow();
875
876 ImGui::TableNextRow();
877 ImGui::TableSetColumnIndex(0);
878 ImGui::Text(ICON_MD_CHAT " Total Messages");
879 ImGui::TableSetColumnIndex(1);
880 ImGui::TextDisabled("Available in chat session");
881
882 ImGui::TableNextRow();
883 ImGui::TableSetColumnIndex(0);
884 ImGui::Text(ICON_MD_BUILD " Tool Calls");
885 ImGui::TableSetColumnIndex(1);
886 ImGui::TextDisabled("Available in chat session");
887
888 ImGui::TableNextRow();
889 ImGui::TableSetColumnIndex(0);
890 ImGui::Text(ICON_MD_PREVIEW " Proposals Created");
891 ImGui::TableSetColumnIndex(1);
892 ImGui::TextDisabled("Available in chat session");
893
894 ImGui::TableNextRow();
895 ImGui::TableSetColumnIndex(0);
896 ImGui::Text(ICON_MD_TIMER " Average Latency");
897 ImGui::TableSetColumnIndex(1);
898 ImGui::TextDisabled("Available in chat session");
899
900 ImGui::EndTable();
901 }
902
903 ImGui::Spacing();
904 ImGui::TextWrapped(
905 "Detailed session metrics are available during active chat sessions. "
906 "Open the chat window to see live statistics.");
907 } else {
908 ImGui::TextDisabled(
909 "No metrics available. Initialize the chat system first.");
910 }
911}
912
914 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
915 ICON_MD_GRID_ON " Common Tiles Reference");
916 ImGui::Separator();
917 ImGui::Spacing();
918
919 ImGui::TextWrapped(
920 "Customize the tile reference file that AI uses for tile placement. "
921 "Organize tiles by category and provide hex IDs with descriptions.");
922
923 ImGui::Spacing();
924
925 // Load/Save buttons
926 if (ImGui::Button(ICON_MD_FOLDER_OPEN " Load", ImVec2(100, 0))) {
927 auto content = AssetLoader::LoadTextFile("agent/common_tiles.txt");
928 if (content.ok()) {
929 common_tiles_editor_->SetText(*content);
931 if (toast_manager_) {
932 toast_manager_->Show(ICON_MD_CHECK_CIRCLE " Common tiles loaded",
933 ToastType::kSuccess, 2.0f);
934 }
935 }
936 }
937
938 ImGui::SameLine();
939 if (ImGui::Button(ICON_MD_SAVE " Save", ImVec2(100, 0))) {
940 // Save to project or assets directory
941 if (toast_manager_) {
943 " Save to project directory (coming soon)",
944 ToastType::kInfo, 2.0f);
945 }
946 }
947
948 ImGui::SameLine();
949 if (ImGui::SmallButton(ICON_MD_REFRESH)) {
951 }
952 if (ImGui::IsItemHovered()) {
953 ImGui::SetTooltip("Reload from disk");
954 }
955
956 // Load if not initialized
958 auto content = AssetLoader::LoadTextFile("agent/common_tiles.txt");
959 if (content.ok()) {
960 common_tiles_editor_->SetText(*content);
961 } else {
962 // Create default template
963 std::string default_tiles =
964 "# Common Tile16 Reference\n"
965 "# Format: 0xHEX = Description\n\n"
966 "[grass_tiles]\n"
967 "0x020 = Grass (standard)\n\n"
968 "[nature_tiles]\n"
969 "0x02E = Tree (oak)\n"
970 "0x003 = Bush\n\n"
971 "[water_tiles]\n"
972 "0x14C = Water (top edge)\n"
973 "0x14D = Water (middle)\n";
974 common_tiles_editor_->SetText(default_tiles);
975 }
977 }
978
979 ImGui::Separator();
980 ImGui::Spacing();
981
982 // Editor
984 ImVec2 editor_size(ImGui::GetContentRegionAvail().x,
985 ImGui::GetContentRegionAvail().y);
986 common_tiles_editor_->Render("##tiles_editor", editor_size, true);
987 }
988}
989
991 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
992 ICON_MD_ADD " Create New System Prompt");
993 ImGui::Separator();
994 ImGui::Spacing();
995
996 ImGui::TextWrapped(
997 "Create a custom system prompt from scratch or use a template.");
998
999 ImGui::Spacing();
1000 ImGui::Separator();
1001
1002 // Prompt name input
1003 ImGui::Text("Prompt Name:");
1004 ImGui::SetNextItemWidth(-1);
1005 ImGui::InputTextWithHint("##new_prompt_name", "e.g., custom_prompt.txt",
1007
1008 ImGui::Spacing();
1009
1010 // Template selection
1011 ImGui::Text("Start from template:");
1012
1013 if (ImGui::Button(ICON_MD_FILE_COPY " v1 (Basic)", ImVec2(-1, 0))) {
1014 auto content = AssetLoader::LoadTextFile("agent/system_prompt.txt");
1015 if (content.ok() && prompt_editor_) {
1016 prompt_editor_->SetText(*content);
1017 if (toast_manager_) {
1018 toast_manager_->Show("Template v1 loaded", ToastType::kSuccess, 1.5f);
1019 }
1020 }
1021 }
1022
1023 if (ImGui::Button(ICON_MD_FILE_COPY " v2 (Enhanced)", ImVec2(-1, 0))) {
1024 auto content =
1025 AssetLoader::LoadTextFile("agent/system_prompt_v2.txt");
1026 if (content.ok() && prompt_editor_) {
1027 prompt_editor_->SetText(*content);
1028 if (toast_manager_) {
1029 toast_manager_->Show("Template v2 loaded", ToastType::kSuccess, 1.5f);
1030 }
1031 }
1032 }
1033
1034 if (ImGui::Button(ICON_MD_FILE_COPY " v3 (Proactive)", ImVec2(-1, 0))) {
1035 auto content =
1036 AssetLoader::LoadTextFile("agent/system_prompt_v3.txt");
1037 if (content.ok() && prompt_editor_) {
1038 prompt_editor_->SetText(*content);
1039 if (toast_manager_) {
1040 toast_manager_->Show("Template v3 loaded", ToastType::kSuccess, 1.5f);
1041 }
1042 }
1043 }
1044
1045 if (ImGui::Button(ICON_MD_NOTE_ADD " Blank Template", ImVec2(-1, 0))) {
1046 if (prompt_editor_) {
1047 std::string blank_template =
1048 "# Custom System Prompt\n\n"
1049 "You are an AI assistant for ROM hacking.\n\n"
1050 "## Your Role\n"
1051 "- Help users understand ROM data\n"
1052 "- Provide accurate information\n"
1053 "- Use tools when needed\n\n"
1054 "## Available Tools\n"
1055 "- resource-list: List resources by type\n"
1056 "- dungeon-describe-room: Get room details\n"
1057 "- overworld-find-tile: Find tile locations\n"
1058 "- ... (see function schemas for complete list)\n\n"
1059 "## Guidelines\n"
1060 "1. Always provide text_response after tool calls\n"
1061 "2. Be helpful and accurate\n"
1062 "3. Explain your reasoning\n";
1063 prompt_editor_->SetText(blank_template);
1064 if (toast_manager_) {
1065 toast_manager_->Show("Blank template created", ToastType::kSuccess,
1066 1.5f);
1067 }
1068 }
1069 }
1070
1071 ImGui::Spacing();
1072 ImGui::Separator();
1073
1074 // Save button
1075 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.133f, 0.545f, 0.133f, 0.8f));
1076 if (ImGui::Button(ICON_MD_SAVE " Save New Prompt", ImVec2(-1, 40))) {
1077 if (std::strlen(new_prompt_name_) > 0 && prompt_editor_) {
1078 // Save to assets/agent/ directory
1079 std::string filename = new_prompt_name_;
1080 if (!absl::EndsWith(filename, ".txt")) {
1081 filename += ".txt";
1082 }
1083
1084 // TODO: Actually save the file
1085 if (toast_manager_) {
1087 absl::StrFormat(ICON_MD_SAVE " Prompt saved as %s", filename),
1088 ToastType::kSuccess, 3.0f);
1089 }
1090
1091 // Clear name buffer
1092 std::memset(new_prompt_name_, 0, sizeof(new_prompt_name_));
1093 } else if (toast_manager_) {
1094 toast_manager_->Show(ICON_MD_WARNING " Enter a name for the prompt",
1095 ToastType::kWarning, 2.0f);
1096 }
1097 }
1098 ImGui::PopStyleColor();
1099
1100 ImGui::Spacing();
1101 ImGui::TextWrapped(
1102 "Note: New prompts are saved to your project. Use 'System Prompt' tab to "
1103 "edit existing prompts.");
1104}
1105
1106// Bot Profile Management Implementation
1107absl::Status AgentEditor::SaveBotProfile(const BotProfile& profile) {
1108#if defined(YAZE_WITH_JSON)
1110
1111 std::filesystem::path profile_path =
1112 GetProfilesDirectory() / (profile.name + ".json");
1113 std::ofstream file(profile_path);
1114 if (!file.is_open()) {
1115 return absl::InternalError("Failed to open profile file for writing");
1116 }
1117
1118 file << ProfileToJson(profile);
1119 file.close();
1120
1121 // Reload profiles list
1122 Load();
1123
1124 return absl::OkStatus();
1125#else
1126 return absl::UnimplementedError(
1127 "JSON support required for profile management");
1128#endif
1129}
1130
1131absl::Status AgentEditor::LoadBotProfile(const std::string& name) {
1132#if defined(YAZE_WITH_JSON)
1133 std::filesystem::path profile_path =
1134 GetProfilesDirectory() / (name + ".json");
1135 if (!std::filesystem::exists(profile_path)) {
1136 return absl::NotFoundError(absl::StrFormat("Profile '%s' not found", name));
1137 }
1138
1139 std::ifstream file(profile_path);
1140 if (!file.is_open()) {
1141 return absl::InternalError("Failed to open profile file");
1142 }
1143
1144 std::string json_content((std::istreambuf_iterator<char>(file)),
1145 std::istreambuf_iterator<char>());
1146
1147 ASSIGN_OR_RETURN(auto profile, JsonToProfile(json_content));
1148 current_profile_ = profile;
1149
1150 // Update legacy config
1151 current_config_.provider = profile.provider;
1152 current_config_.model = profile.model;
1153 current_config_.ollama_host = profile.ollama_host;
1154 current_config_.gemini_api_key = profile.gemini_api_key;
1155 current_config_.verbose = profile.verbose;
1156 current_config_.show_reasoning = profile.show_reasoning;
1157 current_config_.max_tool_iterations = profile.max_tool_iterations;
1158
1159 // Apply to chat widget
1161
1162 return absl::OkStatus();
1163#else
1164 return absl::UnimplementedError(
1165 "JSON support required for profile management");
1166#endif
1167}
1168
1169absl::Status AgentEditor::DeleteBotProfile(const std::string& name) {
1170 std::filesystem::path profile_path =
1171 GetProfilesDirectory() / (name + ".json");
1172 if (!std::filesystem::exists(profile_path)) {
1173 return absl::NotFoundError(absl::StrFormat("Profile '%s' not found", name));
1174 }
1175
1176 std::filesystem::remove(profile_path);
1177
1178 // Reload profiles list
1179 Load();
1180
1181 return absl::OkStatus();
1182}
1183
1184std::vector<AgentEditor::BotProfile> AgentEditor::GetAllProfiles() const {
1185 return loaded_profiles_;
1186}
1187
1189 current_profile_ = profile;
1190
1191 // Update legacy config
1193 current_config_.model = profile.model;
1199}
1200
1201absl::Status AgentEditor::ExportProfile(const BotProfile& profile,
1202 const std::filesystem::path& path) {
1203#if defined(YAZE_WITH_JSON)
1204 std::ofstream file(path);
1205 if (!file.is_open()) {
1206 return absl::InternalError("Failed to open export file");
1207 }
1208
1209 file << ProfileToJson(profile);
1210 file.close();
1211
1212 return absl::OkStatus();
1213#else
1214 return absl::UnimplementedError("JSON support required");
1215#endif
1216}
1217
1218absl::Status AgentEditor::ImportProfile(const std::filesystem::path& path) {
1219#if defined(YAZE_WITH_JSON)
1220 if (!std::filesystem::exists(path)) {
1221 return absl::NotFoundError("Import file not found");
1222 }
1223
1224 std::ifstream file(path);
1225 if (!file.is_open()) {
1226 return absl::InternalError("Failed to open import file");
1227 }
1228
1229 std::string json_content((std::istreambuf_iterator<char>(file)),
1230 std::istreambuf_iterator<char>());
1231
1232 ASSIGN_OR_RETURN(auto profile, JsonToProfile(json_content));
1233
1234 // Save as new profile
1235 return SaveBotProfile(profile);
1236#else
1237 return absl::UnimplementedError("JSON support required");
1238#endif
1239}
1240
1241std::filesystem::path AgentEditor::GetProfilesDirectory() const {
1243 if (!config_dir.ok()) {
1244 // Fallback to a local directory if config can't be determined.
1245 return std::filesystem::current_path() / ".yaze" / "agent" / "profiles";
1246 }
1247 return *config_dir / "agent" / "profiles";
1248}
1249
1251 auto dir = GetProfilesDirectory();
1252 std::error_code ec;
1253 std::filesystem::create_directories(dir, ec);
1254 if (ec) {
1255 return absl::InternalError(absl::StrFormat(
1256 "Failed to create profiles directory: %s", ec.message()));
1257 }
1258 return absl::OkStatus();
1259}
1260
1261std::string AgentEditor::ProfileToJson(const BotProfile& profile) const {
1262#if defined(YAZE_WITH_JSON)
1263 nlohmann::json json;
1264 json["name"] = profile.name;
1265 json["description"] = profile.description;
1266 json["provider"] = profile.provider;
1267 json["model"] = profile.model;
1268 json["ollama_host"] = profile.ollama_host;
1269 json["gemini_api_key"] = profile.gemini_api_key;
1270 json["system_prompt"] = profile.system_prompt;
1271 json["verbose"] = profile.verbose;
1272 json["show_reasoning"] = profile.show_reasoning;
1273 json["max_tool_iterations"] = profile.max_tool_iterations;
1274 json["max_retry_attempts"] = profile.max_retry_attempts;
1275 json["tags"] = profile.tags;
1276 json["created_at"] = absl::FormatTime(absl::RFC3339_full, profile.created_at,
1277 absl::UTCTimeZone());
1278 json["modified_at"] = absl::FormatTime(
1279 absl::RFC3339_full, profile.modified_at, absl::UTCTimeZone());
1280
1281 return json.dump(2);
1282#else
1283 return "{}";
1284#endif
1285}
1286
1287absl::StatusOr<AgentEditor::BotProfile> AgentEditor::JsonToProfile(
1288 const std::string& json_str) const {
1289#if defined(YAZE_WITH_JSON)
1290 try {
1291 nlohmann::json json = nlohmann::json::parse(json_str);
1292
1293 BotProfile profile;
1294 profile.name = json.value("name", "Unnamed Profile");
1295 profile.description = json.value("description", "");
1296 profile.provider = json.value("provider", "mock");
1297 profile.model = json.value("model", "");
1298 profile.ollama_host = json.value("ollama_host", "http://localhost:11434");
1299 profile.gemini_api_key = json.value("gemini_api_key", "");
1300 profile.system_prompt = json.value("system_prompt", "");
1301 profile.verbose = json.value("verbose", false);
1302 profile.show_reasoning = json.value("show_reasoning", true);
1303 profile.max_tool_iterations = json.value("max_tool_iterations", 4);
1304 profile.max_retry_attempts = json.value("max_retry_attempts", 3);
1305
1306 if (json.contains("tags") && json["tags"].is_array()) {
1307 for (const auto& tag : json["tags"]) {
1308 profile.tags.push_back(tag.get<std::string>());
1309 }
1310 }
1311
1312 if (json.contains("created_at")) {
1313 absl::Time created;
1314 if (absl::ParseTime(absl::RFC3339_full,
1315 json["created_at"].get<std::string>(), &created,
1316 nullptr)) {
1317 profile.created_at = created;
1318 }
1319 }
1320
1321 if (json.contains("modified_at")) {
1322 absl::Time modified;
1323 if (absl::ParseTime(absl::RFC3339_full,
1324 json["modified_at"].get<std::string>(), &modified,
1325 nullptr)) {
1326 profile.modified_at = modified;
1327 }
1328 }
1329
1330 return profile;
1331 } catch (const std::exception& e) {
1332 return absl::InternalError(
1333 absl::StrFormat("Failed to parse profile JSON: %s", e.what()));
1334 }
1335#else
1336 return absl::UnimplementedError("JSON support required");
1337#endif
1338}
1339
1340// Legacy methods
1344
1346 current_config_ = config;
1347
1348 // Apply to chat widget if available
1349 if (chat_widget_) {
1351 chat_config.ai_provider = config.provider;
1352 chat_config.ai_model = config.model;
1353 chat_config.ollama_host = config.ollama_host;
1354 chat_config.gemini_api_key = config.gemini_api_key;
1355 chat_config.verbose = config.verbose;
1356 chat_config.show_reasoning = config.show_reasoning;
1357 chat_config.max_tool_iterations = config.max_tool_iterations;
1358 chat_widget_->UpdateAgentConfig(chat_config);
1359 }
1360}
1361
1363 return chat_widget_ && chat_widget_->is_active();
1364}
1365
1367 if (chat_widget_) {
1368 chat_widget_->set_active(active);
1369 }
1370}
1371
1375
1377 if (chat_widget_) {
1378 chat_widget_->set_active(true);
1379 }
1380}
1381
1382absl::StatusOr<AgentEditor::SessionInfo> AgentEditor::HostSession(
1383 const std::string& session_name, CollaborationMode mode) {
1384 current_mode_ = mode;
1385
1386 if (mode == CollaborationMode::kLocal) {
1387 ASSIGN_OR_RETURN(auto session,
1388 local_coordinator_->HostSession(session_name));
1389
1390 SessionInfo info;
1391 info.session_id = session.session_id;
1392 info.session_name = session.session_name;
1393 info.participants = session.participants;
1394
1395 in_session_ = true;
1399
1400 // Switch chat to shared history
1401 if (chat_widget_) {
1402 chat_widget_->SwitchToSharedHistory(info.session_id);
1403 }
1404
1405 if (toast_manager_) {
1407 absl::StrFormat("Hosting local session: %s", session_name),
1408 ToastType::kSuccess, 3.5f);
1409 }
1410
1411 return info;
1412 }
1413
1414#ifdef YAZE_WITH_GRPC
1415 if (mode == CollaborationMode::kNetwork) {
1416 if (!network_coordinator_) {
1417 return absl::FailedPreconditionError(
1418 "Network coordinator not initialized. Connect to a server first.");
1419 }
1420
1421 const char* username = std::getenv("USER");
1422 if (!username) {
1423 username = std::getenv("USERNAME");
1424 }
1425 if (!username) {
1426 username = "unknown";
1427 }
1428
1429 ASSIGN_OR_RETURN(auto session,
1430 network_coordinator_->HostSession(session_name, username));
1431
1432 SessionInfo info;
1433 info.session_id = session.session_id;
1434 info.session_name = session.session_name;
1435 info.participants = session.participants;
1436
1437 in_session_ = true;
1441
1442 if (toast_manager_) {
1444 absl::StrFormat("Hosting network session: %s", session_name),
1445 ToastType::kSuccess, 3.5f);
1446 }
1447
1448 return info;
1449 }
1450#endif
1451
1452 return absl::InvalidArgumentError("Unsupported collaboration mode");
1453}
1454
1455absl::StatusOr<AgentEditor::SessionInfo> AgentEditor::JoinSession(
1456 const std::string& session_code, CollaborationMode mode) {
1457 current_mode_ = mode;
1458
1459 if (mode == CollaborationMode::kLocal) {
1460 ASSIGN_OR_RETURN(auto session,
1461 local_coordinator_->JoinSession(session_code));
1462
1463 SessionInfo info;
1464 info.session_id = session.session_id;
1465 info.session_name = session.session_name;
1466 info.participants = session.participants;
1467
1468 in_session_ = true;
1472
1473 // Switch chat to shared history
1474 if (chat_widget_) {
1475 chat_widget_->SwitchToSharedHistory(info.session_id);
1476 }
1477
1478 if (toast_manager_) {
1480 absl::StrFormat("Joined local session: %s", session_code),
1481 ToastType::kSuccess, 3.5f);
1482 }
1483
1484 return info;
1485 }
1486
1487#ifdef YAZE_WITH_GRPC
1488 if (mode == CollaborationMode::kNetwork) {
1489 if (!network_coordinator_) {
1490 return absl::FailedPreconditionError(
1491 "Network coordinator not initialized. Connect to a server first.");
1492 }
1493
1494 const char* username = std::getenv("USER");
1495 if (!username) {
1496 username = std::getenv("USERNAME");
1497 }
1498 if (!username) {
1499 username = "unknown";
1500 }
1501
1502 ASSIGN_OR_RETURN(auto session,
1503 network_coordinator_->JoinSession(session_code, username));
1504
1505 SessionInfo info;
1506 info.session_id = session.session_id;
1507 info.session_name = session.session_name;
1508 info.participants = session.participants;
1509
1510 in_session_ = true;
1514
1515 if (toast_manager_) {
1517 absl::StrFormat("Joined network session: %s", session_code),
1518 ToastType::kSuccess, 3.5f);
1519 }
1520
1521 return info;
1522 }
1523#endif
1524
1525 return absl::InvalidArgumentError("Unsupported collaboration mode");
1526}
1527
1529 if (!in_session_) {
1530 return absl::FailedPreconditionError("Not in a session");
1531 }
1532
1534 RETURN_IF_ERROR(local_coordinator_->LeaveSession());
1535 }
1536#ifdef YAZE_WITH_GRPC
1538 if (network_coordinator_) {
1539 RETURN_IF_ERROR(network_coordinator_->LeaveSession());
1540 }
1541 }
1542#endif
1543
1544 // Switch chat back to local history
1545 if (chat_widget_) {
1546 chat_widget_->SwitchToLocalHistory();
1547 }
1548
1549 in_session_ = false;
1550 current_session_id_.clear();
1551 current_session_name_.clear();
1552 current_participants_.clear();
1553
1554 if (toast_manager_) {
1555 toast_manager_->Show("Left collaboration session", ToastType::kInfo, 3.0f);
1556 }
1557
1558 return absl::OkStatus();
1559}
1560
1561absl::StatusOr<AgentEditor::SessionInfo> AgentEditor::RefreshSession() {
1562 if (!in_session_) {
1563 return absl::FailedPreconditionError("Not in a session");
1564 }
1565
1567 ASSIGN_OR_RETURN(auto session, local_coordinator_->RefreshSession());
1568
1569 SessionInfo info;
1570 info.session_id = session.session_id;
1571 info.session_name = session.session_name;
1572 info.participants = session.participants;
1573
1575
1576 return info;
1577 }
1578
1579 // Network mode doesn't need explicit refresh - it's real-time
1580 SessionInfo info;
1584 return info;
1585}
1586
1588 [[maybe_unused]] std::filesystem::path* output_path,
1589 [[maybe_unused]] const CaptureConfig& config) {
1590 return absl::UnimplementedError(
1591 "CaptureSnapshot should be called through the chat widget UI");
1592}
1593
1595 [[maybe_unused]] const std::filesystem::path& image_path,
1596 [[maybe_unused]] const std::string& prompt) {
1597 return absl::UnimplementedError(
1598 "SendToGemini should be called through the chat widget UI");
1599}
1600
1601#ifdef YAZE_WITH_GRPC
1602absl::Status AgentEditor::ConnectToServer(const std::string& server_url) {
1603 try {
1604 network_coordinator_ =
1605 std::make_unique<NetworkCollaborationCoordinator>(server_url);
1606
1607 if (toast_manager_) {
1609 absl::StrFormat("Connected to server: %s", server_url),
1610 ToastType::kSuccess, 3.0f);
1611 }
1612
1613 return absl::OkStatus();
1614 } catch (const std::exception& e) {
1615 return absl::InternalError(
1616 absl::StrFormat("Failed to connect to server: %s", e.what()));
1617 }
1618}
1619
1620void AgentEditor::DisconnectFromServer() {
1622 LeaveSession();
1623 }
1624 network_coordinator_.reset();
1625
1626 if (toast_manager_) {
1627 toast_manager_->Show("Disconnected from server", ToastType::kInfo, 2.5f);
1628 }
1629}
1630
1631bool AgentEditor::IsConnectedToServer() const {
1632 return network_coordinator_ && network_coordinator_->IsConnected();
1633}
1634#endif
1635
1637 return in_session_;
1638}
1639
1643
1644std::optional<AgentEditor::SessionInfo> AgentEditor::GetCurrentSession() const {
1645 if (!in_session_) {
1646 return std::nullopt;
1647 }
1648
1649 SessionInfo info;
1653 return info;
1654}
1655
1657 if (!chat_widget_) {
1658 return;
1659 }
1660
1662
1663 collab_callbacks.host_session = [this](const std::string& session_name)
1664 -> absl::StatusOr<
1666 ASSIGN_OR_RETURN(auto session,
1667 this->HostSession(session_name, current_mode_));
1668
1670 context.session_id = session.session_id;
1671 context.session_name = session.session_name;
1672 context.participants = session.participants;
1673 return context;
1674 };
1675
1676 collab_callbacks.join_session = [this](const std::string& session_code)
1677 -> absl::StatusOr<
1679 ASSIGN_OR_RETURN(auto session,
1680 this->JoinSession(session_code, current_mode_));
1681
1683 context.session_id = session.session_id;
1684 context.session_name = session.session_name;
1685 context.participants = session.participants;
1686 return context;
1687 };
1688
1689 collab_callbacks.leave_session = [this]() {
1690 return this->LeaveSession();
1691 };
1692
1693 collab_callbacks.refresh_session =
1694 [this]() -> absl::StatusOr<
1696 ASSIGN_OR_RETURN(auto session, this->RefreshSession());
1697
1699 context.session_id = session.session_id;
1700 context.session_name = session.session_name;
1701 context.participants = session.participants;
1702 return context;
1703 };
1704
1705 chat_widget_->SetCollaborationCallbacks(collab_callbacks);
1706}
1707
1709 // Multimodal callbacks are set up by the EditorManager since it has
1710 // access to the screenshot utilities. We just initialize the structure here.
1711}
1712
1713} // namespace editor
1714} // namespace yaze
static absl::StatusOr< std::string > LoadTextFile(const std::string &relative_path)
The Rom class is used to load, save, and modify Rom data.
Definition rom.h:71
bool is_loaded() const
Definition rom.h:197
auto title() const
Definition rom.h:201
std::unique_ptr< AgentChatWidget > chat_widget_
void SetCurrentProfile(const BotProfile &profile)
void ApplyConfig(const AgentConfig &config)
void InitializeWithDependencies(ToastManager *toast_manager, ProposalDrawer *proposal_drawer, Rom *rom)
absl::StatusOr< SessionInfo > JoinSession(const std::string &session_code, CollaborationMode mode=CollaborationMode::kLocal)
void SetChatActive(bool active)
absl::StatusOr< BotProfile > JsonToProfile(const std::string &json) const
absl::StatusOr< SessionInfo > RefreshSession()
absl::Status ImportProfile(const std::filesystem::path &path)
void Initialize() override
ProposalDrawer * proposal_drawer_
std::vector< cli::agent::ChatMessage > cached_history_
absl::Status Save() override
std::string current_session_name_
CollaborationMode GetCurrentMode() const
absl::Status SendToGemini(const std::filesystem::path &image_path, const std::string &prompt)
absl::Status EnsureProfilesDirectory()
absl::Status ExportProfile(const BotProfile &profile, const std::filesystem::path &path)
CollaborationMode current_mode_
absl::Status SaveBotProfile(const BotProfile &profile)
absl::Status DeleteBotProfile(const std::string &name)
std::filesystem::path GetProfilesDirectory() const
ToastManager * toast_manager_
AgentConfig GetCurrentConfig() const
std::unique_ptr< TextEditor > common_tiles_editor_
absl::StatusOr< SessionInfo > HostSession(const std::string &session_name, CollaborationMode mode=CollaborationMode::kLocal)
std::string ProfileToJson(const BotProfile &profile) const
std::unique_ptr< TextEditor > prompt_editor_
std::unique_ptr< AgentCollaborationCoordinator > local_coordinator_
absl::Status CaptureSnapshot(std::filesystem::path *output_path, const CaptureConfig &config)
absl::Status Load() override
absl::Status LoadBotProfile(const std::string &name)
std::vector< BotProfile > GetAllProfiles() const
std::vector< std::string > current_participants_
absl::Status Update() override
std::vector< BotProfile > loaded_profiles_
std::optional< SessionInfo > GetCurrentSession() const
EditorType type_
Definition editor.h:123
ImGui drawer for displaying and managing agent proposals.
void Show(const std::string &message, ToastType type=ToastType::kInfo, float ttl_seconds=3.0f)
static absl::StatusOr< std::filesystem::path > GetConfigDirectory()
Get the user-specific configuration directory for YAZE.
#define ICON_MD_FOLDER_OPEN
Definition icons.h:811
#define ICON_MD_SETTINGS
Definition icons.h:1697
#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_WARNING
Definition icons.h:2121
#define ICON_MD_VIEW_LIST
Definition icons.h:2090
#define ICON_MD_NOTE_ADD
Definition icons.h:1328
#define ICON_MD_STAR
Definition icons.h:1846
#define ICON_MD_CHECK
Definition icons.h:395
#define ICON_MD_FILE_DOWNLOAD
Definition icons.h:742
#define ICON_MD_TUNE
Definition icons.h:2020
#define ICON_MD_REFRESH
Definition icons.h:1570
#define ICON_MD_VISIBILITY
Definition icons.h:2099
#define ICON_MD_LOOP
Definition icons.h:1154
#define ICON_MD_TIPS_AND_UPDATES
Definition icons.h:1986
#define ICON_MD_EDIT
Definition icons.h:643
#define ICON_MD_FILE_UPLOAD
Definition icons.h:747
#define ICON_MD_GRID_ON
Definition icons.h:894
#define ICON_MD_LIST
Definition icons.h:1092
#define ICON_MD_ADD
Definition icons.h:84
#define ICON_MD_TIMER
Definition icons.h:1980
#define ICON_MD_FILE_COPY
Definition icons.h:741
#define ICON_MD_CHECK_CIRCLE
Definition icons.h:398
#define ICON_MD_PREVIEW
Definition icons.h:1510
#define ICON_MD_BUILD
Definition icons.h:326
#define ICON_MD_SAVE
Definition icons.h:1642
#define ICON_MD_DELETE
Definition icons.h:528
#define ICON_MD_MENU
Definition icons.h:1194
#define ICON_MD_FOLDER
Definition icons.h:807
#define ICON_MD_OPEN_IN_NEW
Definition icons.h:1352
#define ICON_MD_CLOUD
Definition icons.h:421
#define ICON_MD_ANALYTICS
Definition icons.h:152
#define ICON_MD_GAMEPAD
Definition icons.h:864
#define ICON_MD_SMART_TOY
Definition icons.h:1779
#define ICON_MD_HISTORY
Definition icons.h:944
#define RETURN_IF_ERROR(expression)
Definition macro.h:53
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:61
Main namespace for the application.
static const LanguageDefinition & CPlusPlus()
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::vector< std::string > tags
std::vector< std::string > participants