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/core/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 // Pop the TitleBgActive color pushed at the beginning of DrawDashboard
313 ImGui::PopStyleColor();
314}
315
317
318 // AI Provider Configuration
319 if (ImGui::CollapsingHeader(ICON_MD_SETTINGS " AI Provider",
320 ImGuiTreeNodeFlags_DefaultOpen)) {
321 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
322 ICON_MD_SMART_TOY " Provider Selection");
323 ImGui::Spacing();
324
325 // Provider buttons (large, visual)
326 ImVec2 button_size(ImGui::GetContentRegionAvail().x / 3 - 8, 60);
327
328 bool is_mock = (current_profile_.provider == "mock");
329 bool is_ollama = (current_profile_.provider == "ollama");
330 bool is_gemini = (current_profile_.provider == "gemini");
331
332 if (is_mock)
333 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.6f, 0.6f, 0.8f));
334 if (ImGui::Button(ICON_MD_SETTINGS " Mock", button_size)) {
335 current_profile_.provider = "mock";
336 }
337 if (is_mock)
338 ImGui::PopStyleColor();
339
340 ImGui::SameLine();
341 if (is_ollama)
342 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.8f, 0.4f, 0.8f));
343 if (ImGui::Button(ICON_MD_CLOUD " Ollama", button_size)) {
344 current_profile_.provider = "ollama";
345 }
346 if (is_ollama)
347 ImGui::PopStyleColor();
348
349 ImGui::SameLine();
350 if (is_gemini)
351 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.196f, 0.6f, 0.8f, 0.8f));
352 if (ImGui::Button(ICON_MD_SMART_TOY " Gemini", button_size)) {
353 current_profile_.provider = "gemini";
354 }
355 if (is_gemini)
356 ImGui::PopStyleColor();
357
358 ImGui::Separator();
359 ImGui::Spacing();
360
361 // Provider-specific settings
362 if (current_profile_.provider == "ollama") {
363 ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.4f, 1.0f),
364 ICON_MD_SETTINGS " Ollama Settings");
365 ImGui::Text("Model:");
366 ImGui::SetNextItemWidth(-1);
367 static char model_buf[128] = "qwen2.5-coder:7b";
368 if (!current_profile_.model.empty()) {
369 strncpy(model_buf, current_profile_.model.c_str(),
370 sizeof(model_buf) - 1);
371 }
372 if (ImGui::InputTextWithHint("##ollama_model",
373 "e.g., qwen2.5-coder:7b, llama3.2",
374 model_buf, sizeof(model_buf))) {
375 current_profile_.model = model_buf;
376 }
377
378 ImGui::Text("Host URL:");
379 ImGui::SetNextItemWidth(-1);
380 static char host_buf[256] = "http://localhost:11434";
381 strncpy(host_buf, current_profile_.ollama_host.c_str(),
382 sizeof(host_buf) - 1);
383 if (ImGui::InputText("##ollama_host", host_buf, sizeof(host_buf))) {
384 current_profile_.ollama_host = host_buf;
385 }
386 } else if (current_profile_.provider == "gemini") {
387 ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f),
388 ICON_MD_SMART_TOY " Gemini Settings");
389
390 // Load from environment button
391 if (ImGui::Button(ICON_MD_REFRESH " Load from Environment")) {
392 const char* gemini_key = std::getenv("GEMINI_API_KEY");
393 if (gemini_key) {
394 current_profile_.gemini_api_key = gemini_key;
396 if (toast_manager_) {
397 toast_manager_->Show("Gemini API key loaded", ToastType::kSuccess);
398 }
399 } else {
400 if (toast_manager_) {
401 toast_manager_->Show("GEMINI_API_KEY not found",
403 }
404 }
405 }
406
407 ImGui::Spacing();
408
409 ImGui::Text("Model:");
410 ImGui::SetNextItemWidth(-1);
411 static char model_buf[128] = "gemini-1.5-flash";
412 if (!current_profile_.model.empty()) {
413 strncpy(model_buf, current_profile_.model.c_str(),
414 sizeof(model_buf) - 1);
415 }
416 if (ImGui::InputTextWithHint("##gemini_model", "e.g., gemini-1.5-flash",
417 model_buf, sizeof(model_buf))) {
418 current_profile_.model = model_buf;
419 }
420
421 ImGui::Text("API Key:");
422 ImGui::SetNextItemWidth(-1);
423 static char key_buf[256] = "";
424 if (!current_profile_.gemini_api_key.empty() && key_buf[0] == '\0') {
425 strncpy(key_buf, current_profile_.gemini_api_key.c_str(),
426 sizeof(key_buf) - 1);
427 }
428 if (ImGui::InputText("##gemini_key", key_buf, sizeof(key_buf),
429 ImGuiInputTextFlags_Password)) {
431 }
432 if (!current_profile_.gemini_api_key.empty()) {
433 ImGui::TextColored(ImVec4(0.133f, 0.545f, 0.133f, 1.0f),
434 ICON_MD_CHECK_CIRCLE " API key configured");
435 }
436 } else {
437 ImGui::TextDisabled(ICON_MD_INFO " Mock mode - no configuration needed");
438 }
439 }
440
441 // Behavior Settings
442 if (ImGui::CollapsingHeader(ICON_MD_TUNE " Behavior",
443 ImGuiTreeNodeFlags_DefaultOpen)) {
444 ImGui::Checkbox(ICON_MD_VISIBILITY " Show Reasoning",
446 ImGui::Checkbox(ICON_MD_ANALYTICS " Verbose Output",
448 ImGui::SliderInt(ICON_MD_LOOP " Max Tool Iterations",
450 ImGui::SliderInt(ICON_MD_REFRESH " Max Retry Attempts",
452 }
453
454 // Profile Metadata
455 if (ImGui::CollapsingHeader(ICON_MD_INFO " Profile Info")) {
456 ImGui::Text("Name:");
457 static char name_buf[128];
458 strncpy(name_buf, current_profile_.name.c_str(), sizeof(name_buf) - 1);
459 if (ImGui::InputText("##profile_name", name_buf, sizeof(name_buf))) {
460 current_profile_.name = name_buf;
461 }
462
463 ImGui::Text("Description:");
464 static char desc_buf[256];
465 strncpy(desc_buf, current_profile_.description.c_str(),
466 sizeof(desc_buf) - 1);
467 if (ImGui::InputTextMultiline("##profile_desc", desc_buf, sizeof(desc_buf),
468 ImVec2(-1, 60))) {
469 current_profile_.description = desc_buf;
470 }
471
472 ImGui::Text("Tags (comma-separated):");
473 static char tags_buf[256];
474 if (tags_buf[0] == '\0' && !current_profile_.tags.empty()) {
475 std::string tags_str;
476 for (size_t i = 0; i < current_profile_.tags.size(); ++i) {
477 if (i > 0)
478 tags_str += ", ";
479 tags_str += current_profile_.tags[i];
480 }
481 strncpy(tags_buf, tags_str.c_str(), sizeof(tags_buf) - 1);
482 }
483 if (ImGui::InputText("##profile_tags", tags_buf, sizeof(tags_buf))) {
484 // Parse comma-separated tags
485 current_profile_.tags.clear();
486 std::string tags_str(tags_buf);
487 size_t pos = 0;
488 while ((pos = tags_str.find(',')) != std::string::npos) {
489 std::string tag = tags_str.substr(0, pos);
490 // Trim whitespace
491 tag.erase(0, tag.find_first_not_of(" \t"));
492 tag.erase(tag.find_last_not_of(" \t") + 1);
493 if (!tag.empty()) {
494 current_profile_.tags.push_back(tag);
495 }
496 tags_str.erase(0, pos + 1);
497 }
498 if (!tags_str.empty()) {
499 tags_str.erase(0, tags_str.find_first_not_of(" \t"));
500 tags_str.erase(tags_str.find_last_not_of(" \t") + 1);
501 if (!tags_str.empty()) {
502 current_profile_.tags.push_back(tags_str);
503 }
504 }
505 }
506 }
507
508 // Apply button
509 ImGui::Spacing();
510 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.133f, 0.545f, 0.133f, 0.8f));
511 ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
512 ImVec4(0.133f, 0.545f, 0.133f, 1.0f));
513 if (ImGui::Button(ICON_MD_CHECK " Apply & Save Configuration",
514 ImVec2(-1, 40))) {
515 // Update legacy config
523
525 Save();
526
527 if (toast_manager_) {
528 toast_manager_->Show("Configuration applied and saved",
530 }
531 }
532 ImGui::PopStyleColor(2);
533
535}
536
538 // Always visible status cards (no collapsing)
539
540 // Chat Status Card
541 ImGui::BeginChild("ChatStatusCard", ImVec2(0, 100), true);
542 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_CHAT " Chat");
543 ImGui::Separator();
544
545 if (chat_widget_ && chat_widget_->is_active()) {
546 ImGui::TextColored(ImVec4(0.133f, 0.545f, 0.133f, 1.0f),
547 ICON_MD_CHECK_CIRCLE " Active");
548 } else {
549 ImGui::TextDisabled(ICON_MD_CANCEL " Inactive");
550 }
551
552 ImGui::Spacing();
553 if (ImGui::Button(ICON_MD_OPEN_IN_NEW " Open", ImVec2(-1, 0))) {
555 }
556 ImGui::EndChild();
557
558 ImGui::Spacing();
559
560 // ROM Context Card
561 ImGui::BeginChild("RomStatusCard", ImVec2(0, 100), true);
562 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f), ICON_MD_GAMEPAD " ROM");
563 ImGui::Separator();
564
565 if (rom_ && rom_->is_loaded()) {
566 ImGui::TextColored(ImVec4(0.133f, 0.545f, 0.133f, 1.0f),
567 ICON_MD_CHECK_CIRCLE " Loaded");
568 ImGui::TextDisabled("Title: %s", rom_->title().c_str());
569 ImGui::TextDisabled("Tools: Ready");
570 } else {
571 ImGui::TextColored(ImVec4(0.8f, 0.2f, 0.2f, 1.0f),
572 ICON_MD_WARNING " Not Loaded");
573 ImGui::TextDisabled("Load ROM for AI tools");
574 }
575 ImGui::EndChild();
576
577 ImGui::Spacing();
578
579 // Quick Tips Card
580 ImGui::BeginChild("QuickTipsCard", ImVec2(0, 150), true);
581 ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f),
582 ICON_MD_TIPS_AND_UPDATES " Quick Tips");
583 ImGui::Separator();
584 ImGui::Spacing();
585
586 ImGui::BulletText("Ctrl+H: Toggle chat popup");
587 ImGui::BulletText("Ctrl+P: View proposals");
588 ImGui::BulletText("Edit prompts in center");
589 ImGui::BulletText("Create custom bots");
590 ImGui::BulletText("Save/load chat sessions");
591
592 ImGui::EndChild();
593}
594
596 if (ImGui::CollapsingHeader(ICON_MD_ANALYTICS " Quick Metrics")) {
597 if (chat_widget_) {
598 // Get metrics from the chat widget's service
599 ImGui::TextDisabled("View detailed metrics in the Metrics tab");
600 } else {
601 ImGui::TextDisabled("No metrics available");
602 }
603 }
604}
605
607 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
608 ICON_MD_EDIT " Prompt Editor");
609 ImGui::Separator();
610 ImGui::Spacing();
611
612 // Compact prompt file selector
613 ImGui::Text("File:");
614 ImGui::SetNextItemWidth(-45);
615 if (ImGui::BeginCombo("##prompt_file", active_prompt_file_.c_str())) {
616 if (ImGui::Selectable("system_prompt.txt",
617 active_prompt_file_ == "system_prompt.txt")) {
618 active_prompt_file_ = "system_prompt.txt";
620 }
621 if (ImGui::Selectable("system_prompt_v2.txt",
622 active_prompt_file_ == "system_prompt_v2.txt")) {
623 active_prompt_file_ = "system_prompt_v2.txt";
625 }
626 if (ImGui::Selectable("system_prompt_v3.txt",
627 active_prompt_file_ == "system_prompt_v3.txt")) {
628 active_prompt_file_ = "system_prompt_v3.txt";
630 }
631 ImGui::EndCombo();
632 }
633
634 ImGui::SameLine();
635 if (ImGui::SmallButton(ICON_MD_REFRESH)) {
637 }
638 if (ImGui::IsItemHovered()) {
639 ImGui::SetTooltip("Reload from disk");
640 }
641
642 // Load prompt file if not initialized
644 std::string asset_path = "agent/" + active_prompt_file_;
645 auto content_result = AssetLoader::LoadTextFile(asset_path);
646
647 if (content_result.ok()) {
648 prompt_editor_->SetText(*content_result);
649 current_profile_.system_prompt = *content_result;
651
652 if (toast_manager_) {
653 toast_manager_->Show(absl::StrFormat(ICON_MD_CHECK_CIRCLE " Loaded %s",
655 ToastType::kSuccess, 2.0f);
656 }
657 } else {
658 // Show detailed error in console
659 std::cerr << "❌ Failed to load " << active_prompt_file_ << "\n";
660 std::cerr << " Error: " << content_result.status().message() << "\n";
661
662 // Set placeholder with instructions
663 std::string placeholder = absl::StrFormat(
664 "# System prompt file not found: %s\n"
665 "# Error: %s\n\n"
666 "# Please ensure the file exists in:\n"
667 "# - assets/agent/%s\n"
668 "# - Or Contents/Resources/agent/%s (macOS bundle)\n\n"
669 "# You can create a custom prompt here and save it to your bot "
670 "profile.",
671 active_prompt_file_, content_result.status().message(),
673
674 prompt_editor_->SetText(placeholder);
676 }
677 }
678
679 ImGui::Spacing();
680
681 // Text editor
682 if (prompt_editor_) {
683 ImVec2 editor_size = ImVec2(ImGui::GetContentRegionAvail().x,
684 ImGui::GetContentRegionAvail().y - 50);
685 prompt_editor_->Render("##prompt_editor", editor_size, true);
686
687 // Save button
688 ImGui::Spacing();
689 if (ImGui::Button(ICON_MD_SAVE " Save Prompt to Profile", ImVec2(-1, 0))) {
691 if (toast_manager_) {
692 toast_manager_->Show("System prompt saved to profile",
694 }
695 }
696 }
697
698 ImGui::Spacing();
699 ImGui::TextWrapped(
700 "Edit the system prompt that guides the AI agent's behavior. Changes are "
701 "saved to the current bot profile.");
702}
703
705 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
706 ICON_MD_FOLDER " Bot Profile Manager");
707 ImGui::Separator();
708 ImGui::Spacing();
709
710 // Current profile display
711 ImGui::BeginChild("CurrentProfile", ImVec2(0, 150), true);
712 ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f),
713 ICON_MD_STAR " Current Profile");
714 ImGui::Separator();
715 ImGui::Text("Name: %s", current_profile_.name.c_str());
716 ImGui::Text("Provider: %s", current_profile_.provider.c_str());
717 if (!current_profile_.model.empty()) {
718 ImGui::Text("Model: %s", current_profile_.model.c_str());
719 }
720 ImGui::TextWrapped("Description: %s",
722 ? "No description"
723 : current_profile_.description.c_str());
724 ImGui::EndChild();
725
726 ImGui::Spacing();
727
728 // Profile management buttons
729 if (ImGui::Button(ICON_MD_ADD " Create New Profile", ImVec2(-1, 0))) {
730 // Create new profile from current
731 BotProfile new_profile = current_profile_;
732 new_profile.name = "New Profile";
733 new_profile.created_at = absl::Now();
734 new_profile.modified_at = absl::Now();
735 current_profile_ = new_profile;
736 if (toast_manager_) {
737 toast_manager_->Show("New profile created. Configure and save it.",
739 }
740 }
741
742 ImGui::Spacing();
743
744 // Saved profiles list
745 ImGui::TextColored(ImVec4(0.196f, 0.6f, 0.8f, 1.0f),
746 ICON_MD_LIST " Saved Profiles");
747 ImGui::Separator();
748
749 ImGui::BeginChild("ProfilesList", ImVec2(0, 0), true);
750
751 if (loaded_profiles_.empty()) {
752 ImGui::TextDisabled(
753 "No saved profiles. Create and save a profile to see it here.");
754 } else {
755 for (size_t i = 0; i < loaded_profiles_.size(); ++i) {
756 const auto& profile = loaded_profiles_[i];
757 ImGui::PushID(static_cast<int>(i));
758
759 bool is_current = (profile.name == current_profile_.name);
760 if (is_current) {
761 ImGui::PushStyleColor(ImGuiCol_Button,
762 ImVec4(0.196f, 0.6f, 0.8f, 0.6f));
763 }
764
765 if (ImGui::Button(profile.name.c_str(),
766 ImVec2(ImGui::GetContentRegionAvail().x - 80, 0))) {
767 LoadBotProfile(profile.name);
768 if (toast_manager_) {
770 absl::StrFormat("Loaded profile: %s", profile.name),
772 }
773 }
774
775 if (is_current) {
776 ImGui::PopStyleColor();
777 }
778
779 ImGui::SameLine();
780 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.2f, 0.2f, 0.6f));
781 if (ImGui::SmallButton(ICON_MD_DELETE)) {
782 DeleteBotProfile(profile.name);
783 if (toast_manager_) {
785 absl::StrFormat("Deleted profile: %s", profile.name),
787 }
788 }
789 ImGui::PopStyleColor();
790
791 ImGui::TextDisabled(" %s | %s", profile.provider.c_str(),
792 profile.description.empty()
793 ? "No description"
794 : profile.description.c_str());
795 ImGui::Spacing();
796
797 ImGui::PopID();
798 }
799 }
800
801 ImGui::EndChild();
802}
803
805 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
806 ICON_MD_HISTORY " Chat History Viewer");
807 ImGui::Separator();
808 ImGui::Spacing();
809
810 if (ImGui::Button(ICON_MD_REFRESH " Refresh History")) {
812 }
813
814 ImGui::SameLine();
815 if (ImGui::Button(ICON_MD_DELETE " Clear History")) {
816 if (chat_widget_) {
817 // Clear through the chat widget's service
818 if (toast_manager_) {
819 toast_manager_->Show("Chat history cleared", ToastType::kInfo);
820 }
821 }
822 }
823
824 ImGui::Spacing();
825 ImGui::Separator();
826
827 // Get history from chat widget
829 // Access the service's history through the chat widget
830 // For now, show a placeholder
832 }
833
834 ImGui::BeginChild("HistoryList", ImVec2(0, 0), true);
835
836 if (cached_history_.empty()) {
837 ImGui::TextDisabled(
838 "No chat history. Start a conversation in the chat window.");
839 } else {
840 for (const auto& msg : cached_history_) {
841 bool from_user = (msg.sender == cli::agent::ChatMessage::Sender::kUser);
842 ImVec4 color = from_user ? ImVec4(0.6f, 0.8f, 1.0f, 1.0f)
843 : ImVec4(0.4f, 0.8f, 0.4f, 1.0f);
844
845 ImGui::PushStyleColor(ImGuiCol_Text, color);
846 ImGui::Text("%s:", from_user ? "User" : "Agent");
847 ImGui::PopStyleColor();
848
849 ImGui::SameLine();
850 ImGui::TextDisabled("%s", absl::FormatTime("%H:%M:%S", msg.timestamp,
851 absl::LocalTimeZone())
852 .c_str());
853
854 ImGui::TextWrapped("%s", msg.message.c_str());
855 ImGui::Spacing();
856 ImGui::Separator();
857 }
858 }
859
860 ImGui::EndChild();
861}
862
864 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
865 ICON_MD_ANALYTICS " Session Metrics & Analytics");
866 ImGui::Separator();
867 ImGui::Spacing();
868
869 // Get metrics from chat widget service
870 if (chat_widget_) {
871 // For now show placeholder metrics structure
872 if (ImGui::BeginTable("MetricsTable", 2,
873 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
874 ImGui::TableSetupColumn("Metric", ImGuiTableColumnFlags_WidthFixed,
875 200.0f);
876 ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch);
877 ImGui::TableHeadersRow();
878
879 ImGui::TableNextRow();
880 ImGui::TableSetColumnIndex(0);
881 ImGui::Text(ICON_MD_CHAT " Total Messages");
882 ImGui::TableSetColumnIndex(1);
883 ImGui::TextDisabled("Available in chat session");
884
885 ImGui::TableNextRow();
886 ImGui::TableSetColumnIndex(0);
887 ImGui::Text(ICON_MD_BUILD " Tool Calls");
888 ImGui::TableSetColumnIndex(1);
889 ImGui::TextDisabled("Available in chat session");
890
891 ImGui::TableNextRow();
892 ImGui::TableSetColumnIndex(0);
893 ImGui::Text(ICON_MD_PREVIEW " Proposals Created");
894 ImGui::TableSetColumnIndex(1);
895 ImGui::TextDisabled("Available in chat session");
896
897 ImGui::TableNextRow();
898 ImGui::TableSetColumnIndex(0);
899 ImGui::Text(ICON_MD_TIMER " Average Latency");
900 ImGui::TableSetColumnIndex(1);
901 ImGui::TextDisabled("Available in chat session");
902
903 ImGui::EndTable();
904 }
905
906 ImGui::Spacing();
907 ImGui::TextWrapped(
908 "Detailed session metrics are available during active chat sessions. "
909 "Open the chat window to see live statistics.");
910 } else {
911 ImGui::TextDisabled(
912 "No metrics available. Initialize the chat system first.");
913 }
914}
915
917 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
918 ICON_MD_GRID_ON " Common Tiles Reference");
919 ImGui::Separator();
920 ImGui::Spacing();
921
922 ImGui::TextWrapped(
923 "Customize the tile reference file that AI uses for tile placement. "
924 "Organize tiles by category and provide hex IDs with descriptions.");
925
926 ImGui::Spacing();
927
928 // Load/Save buttons
929 if (ImGui::Button(ICON_MD_FOLDER_OPEN " Load", ImVec2(100, 0))) {
930 auto content = AssetLoader::LoadTextFile("agent/common_tiles.txt");
931 if (content.ok()) {
932 common_tiles_editor_->SetText(*content);
934 if (toast_manager_) {
935 toast_manager_->Show(ICON_MD_CHECK_CIRCLE " Common tiles loaded",
936 ToastType::kSuccess, 2.0f);
937 }
938 }
939 }
940
941 ImGui::SameLine();
942 if (ImGui::Button(ICON_MD_SAVE " Save", ImVec2(100, 0))) {
943 // Save to project or assets directory
944 if (toast_manager_) {
946 " Save to project directory (coming soon)",
947 ToastType::kInfo, 2.0f);
948 }
949 }
950
951 ImGui::SameLine();
952 if (ImGui::SmallButton(ICON_MD_REFRESH)) {
954 }
955 if (ImGui::IsItemHovered()) {
956 ImGui::SetTooltip("Reload from disk");
957 }
958
959 // Load if not initialized
961 auto content = AssetLoader::LoadTextFile("agent/common_tiles.txt");
962 if (content.ok()) {
963 common_tiles_editor_->SetText(*content);
964 } else {
965 // Create default template
966 std::string default_tiles =
967 "# Common Tile16 Reference\n"
968 "# Format: 0xHEX = Description\n\n"
969 "[grass_tiles]\n"
970 "0x020 = Grass (standard)\n\n"
971 "[nature_tiles]\n"
972 "0x02E = Tree (oak)\n"
973 "0x003 = Bush\n\n"
974 "[water_tiles]\n"
975 "0x14C = Water (top edge)\n"
976 "0x14D = Water (middle)\n";
977 common_tiles_editor_->SetText(default_tiles);
978 }
980 }
981
982 ImGui::Separator();
983 ImGui::Spacing();
984
985 // Editor
987 ImVec2 editor_size(ImGui::GetContentRegionAvail().x,
988 ImGui::GetContentRegionAvail().y);
989 common_tiles_editor_->Render("##tiles_editor", editor_size, true);
990 }
991}
992
994 ImGui::TextColored(ImVec4(1.0f, 0.843f, 0.0f, 1.0f),
995 ICON_MD_ADD " Create New System Prompt");
996 ImGui::Separator();
997 ImGui::Spacing();
998
999 ImGui::TextWrapped(
1000 "Create a custom system prompt from scratch or use a template.");
1001
1002 ImGui::Spacing();
1003 ImGui::Separator();
1004
1005 // Prompt name input
1006 ImGui::Text("Prompt Name:");
1007 ImGui::SetNextItemWidth(-1);
1008 ImGui::InputTextWithHint("##new_prompt_name", "e.g., custom_prompt.txt",
1010
1011 ImGui::Spacing();
1012
1013 // Template selection
1014 ImGui::Text("Start from template:");
1015
1016 if (ImGui::Button(ICON_MD_FILE_COPY " v1 (Basic)", ImVec2(-1, 0))) {
1017 auto content = AssetLoader::LoadTextFile("agent/system_prompt.txt");
1018 if (content.ok() && prompt_editor_) {
1019 prompt_editor_->SetText(*content);
1020 if (toast_manager_) {
1021 toast_manager_->Show("Template v1 loaded", ToastType::kSuccess, 1.5f);
1022 }
1023 }
1024 }
1025
1026 if (ImGui::Button(ICON_MD_FILE_COPY " v2 (Enhanced)", ImVec2(-1, 0))) {
1027 auto content =
1028 AssetLoader::LoadTextFile("agent/system_prompt_v2.txt");
1029 if (content.ok() && prompt_editor_) {
1030 prompt_editor_->SetText(*content);
1031 if (toast_manager_) {
1032 toast_manager_->Show("Template v2 loaded", ToastType::kSuccess, 1.5f);
1033 }
1034 }
1035 }
1036
1037 if (ImGui::Button(ICON_MD_FILE_COPY " v3 (Proactive)", ImVec2(-1, 0))) {
1038 auto content =
1039 AssetLoader::LoadTextFile("agent/system_prompt_v3.txt");
1040 if (content.ok() && prompt_editor_) {
1041 prompt_editor_->SetText(*content);
1042 if (toast_manager_) {
1043 toast_manager_->Show("Template v3 loaded", ToastType::kSuccess, 1.5f);
1044 }
1045 }
1046 }
1047
1048 if (ImGui::Button(ICON_MD_NOTE_ADD " Blank Template", ImVec2(-1, 0))) {
1049 if (prompt_editor_) {
1050 std::string blank_template =
1051 "# Custom System Prompt\n\n"
1052 "You are an AI assistant for ROM hacking.\n\n"
1053 "## Your Role\n"
1054 "- Help users understand ROM data\n"
1055 "- Provide accurate information\n"
1056 "- Use tools when needed\n\n"
1057 "## Available Tools\n"
1058 "- resource-list: List resources by type\n"
1059 "- dungeon-describe-room: Get room details\n"
1060 "- overworld-find-tile: Find tile locations\n"
1061 "- ... (see function schemas for complete list)\n\n"
1062 "## Guidelines\n"
1063 "1. Always provide text_response after tool calls\n"
1064 "2. Be helpful and accurate\n"
1065 "3. Explain your reasoning\n";
1066 prompt_editor_->SetText(blank_template);
1067 if (toast_manager_) {
1068 toast_manager_->Show("Blank template created", ToastType::kSuccess,
1069 1.5f);
1070 }
1071 }
1072 }
1073
1074 ImGui::Spacing();
1075 ImGui::Separator();
1076
1077 // Save button
1078 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.133f, 0.545f, 0.133f, 0.8f));
1079 if (ImGui::Button(ICON_MD_SAVE " Save New Prompt", ImVec2(-1, 40))) {
1080 if (std::strlen(new_prompt_name_) > 0 && prompt_editor_) {
1081 // Save to assets/agent/ directory
1082 std::string filename = new_prompt_name_;
1083 if (!absl::EndsWith(filename, ".txt")) {
1084 filename += ".txt";
1085 }
1086
1087 // TODO: Actually save the file
1088 if (toast_manager_) {
1090 absl::StrFormat(ICON_MD_SAVE " Prompt saved as %s", filename),
1091 ToastType::kSuccess, 3.0f);
1092 }
1093
1094 // Clear name buffer
1095 std::memset(new_prompt_name_, 0, sizeof(new_prompt_name_));
1096 } else if (toast_manager_) {
1097 toast_manager_->Show(ICON_MD_WARNING " Enter a name for the prompt",
1098 ToastType::kWarning, 2.0f);
1099 }
1100 }
1101 ImGui::PopStyleColor();
1102
1103 ImGui::Spacing();
1104 ImGui::TextWrapped(
1105 "Note: New prompts are saved to your project. Use 'System Prompt' tab to "
1106 "edit existing prompts.");
1107}
1108
1109// Bot Profile Management Implementation
1110absl::Status AgentEditor::SaveBotProfile(const BotProfile& profile) {
1111#if defined(YAZE_WITH_JSON)
1113
1114 std::filesystem::path profile_path =
1115 GetProfilesDirectory() / (profile.name + ".json");
1116 std::ofstream file(profile_path);
1117 if (!file.is_open()) {
1118 return absl::InternalError("Failed to open profile file for writing");
1119 }
1120
1121 file << ProfileToJson(profile);
1122 file.close();
1123
1124 // Reload profiles list
1125 Load();
1126
1127 return absl::OkStatus();
1128#else
1129 return absl::UnimplementedError(
1130 "JSON support required for profile management");
1131#endif
1132}
1133
1134absl::Status AgentEditor::LoadBotProfile(const std::string& name) {
1135#if defined(YAZE_WITH_JSON)
1136 std::filesystem::path profile_path =
1137 GetProfilesDirectory() / (name + ".json");
1138 if (!std::filesystem::exists(profile_path)) {
1139 return absl::NotFoundError(absl::StrFormat("Profile '%s' not found", name));
1140 }
1141
1142 std::ifstream file(profile_path);
1143 if (!file.is_open()) {
1144 return absl::InternalError("Failed to open profile file");
1145 }
1146
1147 std::string json_content((std::istreambuf_iterator<char>(file)),
1148 std::istreambuf_iterator<char>());
1149
1150 ASSIGN_OR_RETURN(auto profile, JsonToProfile(json_content));
1151 current_profile_ = profile;
1152
1153 // Update legacy config
1154 current_config_.provider = profile.provider;
1155 current_config_.model = profile.model;
1156 current_config_.ollama_host = profile.ollama_host;
1157 current_config_.gemini_api_key = profile.gemini_api_key;
1158 current_config_.verbose = profile.verbose;
1159 current_config_.show_reasoning = profile.show_reasoning;
1160 current_config_.max_tool_iterations = profile.max_tool_iterations;
1161
1162 // Apply to chat widget
1164
1165 return absl::OkStatus();
1166#else
1167 return absl::UnimplementedError(
1168 "JSON support required for profile management");
1169#endif
1170}
1171
1172absl::Status AgentEditor::DeleteBotProfile(const std::string& name) {
1173 std::filesystem::path profile_path =
1174 GetProfilesDirectory() / (name + ".json");
1175 if (!std::filesystem::exists(profile_path)) {
1176 return absl::NotFoundError(absl::StrFormat("Profile '%s' not found", name));
1177 }
1178
1179 std::filesystem::remove(profile_path);
1180
1181 // Reload profiles list
1182 Load();
1183
1184 return absl::OkStatus();
1185}
1186
1187std::vector<AgentEditor::BotProfile> AgentEditor::GetAllProfiles() const {
1188 return loaded_profiles_;
1189}
1190
1192 current_profile_ = profile;
1193
1194 // Update legacy config
1196 current_config_.model = profile.model;
1202}
1203
1204absl::Status AgentEditor::ExportProfile(const BotProfile& profile,
1205 const std::filesystem::path& path) {
1206#if defined(YAZE_WITH_JSON)
1207 std::ofstream file(path);
1208 if (!file.is_open()) {
1209 return absl::InternalError("Failed to open export file");
1210 }
1211
1212 file << ProfileToJson(profile);
1213 file.close();
1214
1215 return absl::OkStatus();
1216#else
1217 return absl::UnimplementedError("JSON support required");
1218#endif
1219}
1220
1221absl::Status AgentEditor::ImportProfile(const std::filesystem::path& path) {
1222#if defined(YAZE_WITH_JSON)
1223 if (!std::filesystem::exists(path)) {
1224 return absl::NotFoundError("Import file not found");
1225 }
1226
1227 std::ifstream file(path);
1228 if (!file.is_open()) {
1229 return absl::InternalError("Failed to open import file");
1230 }
1231
1232 std::string json_content((std::istreambuf_iterator<char>(file)),
1233 std::istreambuf_iterator<char>());
1234
1235 ASSIGN_OR_RETURN(auto profile, JsonToProfile(json_content));
1236
1237 // Save as new profile
1238 return SaveBotProfile(profile);
1239#else
1240 return absl::UnimplementedError("JSON support required");
1241#endif
1242}
1243
1244std::filesystem::path AgentEditor::GetProfilesDirectory() const {
1246 if (!config_dir.ok()) {
1247 // Fallback to a local directory if config can't be determined.
1248 return std::filesystem::current_path() / ".yaze" / "agent" / "profiles";
1249 }
1250 return *config_dir / "agent" / "profiles";
1251}
1252
1254 auto dir = GetProfilesDirectory();
1255 std::error_code ec;
1256 std::filesystem::create_directories(dir, ec);
1257 if (ec) {
1258 return absl::InternalError(absl::StrFormat(
1259 "Failed to create profiles directory: %s", ec.message()));
1260 }
1261 return absl::OkStatus();
1262}
1263
1264std::string AgentEditor::ProfileToJson(const BotProfile& profile) const {
1265#if defined(YAZE_WITH_JSON)
1266 nlohmann::json json;
1267 json["name"] = profile.name;
1268 json["description"] = profile.description;
1269 json["provider"] = profile.provider;
1270 json["model"] = profile.model;
1271 json["ollama_host"] = profile.ollama_host;
1272 json["gemini_api_key"] = profile.gemini_api_key;
1273 json["system_prompt"] = profile.system_prompt;
1274 json["verbose"] = profile.verbose;
1275 json["show_reasoning"] = profile.show_reasoning;
1276 json["max_tool_iterations"] = profile.max_tool_iterations;
1277 json["max_retry_attempts"] = profile.max_retry_attempts;
1278 json["tags"] = profile.tags;
1279 json["created_at"] = absl::FormatTime(absl::RFC3339_full, profile.created_at,
1280 absl::UTCTimeZone());
1281 json["modified_at"] = absl::FormatTime(
1282 absl::RFC3339_full, profile.modified_at, absl::UTCTimeZone());
1283
1284 return json.dump(2);
1285#else
1286 return "{}";
1287#endif
1288}
1289
1290absl::StatusOr<AgentEditor::BotProfile> AgentEditor::JsonToProfile(
1291 const std::string& json_str) const {
1292#if defined(YAZE_WITH_JSON)
1293 try {
1294 nlohmann::json json = nlohmann::json::parse(json_str);
1295
1296 BotProfile profile;
1297 profile.name = json.value("name", "Unnamed Profile");
1298 profile.description = json.value("description", "");
1299 profile.provider = json.value("provider", "mock");
1300 profile.model = json.value("model", "");
1301 profile.ollama_host = json.value("ollama_host", "http://localhost:11434");
1302 profile.gemini_api_key = json.value("gemini_api_key", "");
1303 profile.system_prompt = json.value("system_prompt", "");
1304 profile.verbose = json.value("verbose", false);
1305 profile.show_reasoning = json.value("show_reasoning", true);
1306 profile.max_tool_iterations = json.value("max_tool_iterations", 4);
1307 profile.max_retry_attempts = json.value("max_retry_attempts", 3);
1308
1309 if (json.contains("tags") && json["tags"].is_array()) {
1310 for (const auto& tag : json["tags"]) {
1311 profile.tags.push_back(tag.get<std::string>());
1312 }
1313 }
1314
1315 if (json.contains("created_at")) {
1316 absl::Time created;
1317 if (absl::ParseTime(absl::RFC3339_full,
1318 json["created_at"].get<std::string>(), &created,
1319 nullptr)) {
1320 profile.created_at = created;
1321 }
1322 }
1323
1324 if (json.contains("modified_at")) {
1325 absl::Time modified;
1326 if (absl::ParseTime(absl::RFC3339_full,
1327 json["modified_at"].get<std::string>(), &modified,
1328 nullptr)) {
1329 profile.modified_at = modified;
1330 }
1331 }
1332
1333 return profile;
1334 } catch (const std::exception& e) {
1335 return absl::InternalError(
1336 absl::StrFormat("Failed to parse profile JSON: %s", e.what()));
1337 }
1338#else
1339 return absl::UnimplementedError("JSON support required");
1340#endif
1341}
1342
1343// Legacy methods
1347
1349 current_config_ = config;
1350
1351 // Apply to chat widget if available
1352 if (chat_widget_) {
1354 chat_config.ai_provider = config.provider;
1355 chat_config.ai_model = config.model;
1356 chat_config.ollama_host = config.ollama_host;
1357 chat_config.gemini_api_key = config.gemini_api_key;
1358 chat_config.verbose = config.verbose;
1359 chat_config.show_reasoning = config.show_reasoning;
1360 chat_config.max_tool_iterations = config.max_tool_iterations;
1361 chat_widget_->UpdateAgentConfig(chat_config);
1362 }
1363}
1364
1366 return chat_widget_ && chat_widget_->is_active();
1367}
1368
1370 if (chat_widget_) {
1371 chat_widget_->set_active(active);
1372 }
1373}
1374
1378
1380 if (chat_widget_) {
1381 chat_widget_->set_active(true);
1382 }
1383}
1384
1385absl::StatusOr<AgentEditor::SessionInfo> AgentEditor::HostSession(
1386 const std::string& session_name, CollaborationMode mode) {
1387 current_mode_ = mode;
1388
1389 if (mode == CollaborationMode::kLocal) {
1390 ASSIGN_OR_RETURN(auto session,
1391 local_coordinator_->HostSession(session_name));
1392
1393 SessionInfo info;
1394 info.session_id = session.session_id;
1395 info.session_name = session.session_name;
1396 info.participants = session.participants;
1397
1398 in_session_ = true;
1402
1403 // Switch chat to shared history
1404 if (chat_widget_) {
1405 chat_widget_->SwitchToSharedHistory(info.session_id);
1406 }
1407
1408 if (toast_manager_) {
1410 absl::StrFormat("Hosting local session: %s", session_name),
1411 ToastType::kSuccess, 3.5f);
1412 }
1413
1414 return info;
1415 }
1416
1417#ifdef YAZE_WITH_GRPC
1418 if (mode == CollaborationMode::kNetwork) {
1419 if (!network_coordinator_) {
1420 return absl::FailedPreconditionError(
1421 "Network coordinator not initialized. Connect to a server first.");
1422 }
1423
1424 const char* username = std::getenv("USER");
1425 if (!username) {
1426 username = std::getenv("USERNAME");
1427 }
1428 if (!username) {
1429 username = "unknown";
1430 }
1431
1432 ASSIGN_OR_RETURN(auto session,
1433 network_coordinator_->HostSession(session_name, username));
1434
1435 SessionInfo info;
1436 info.session_id = session.session_id;
1437 info.session_name = session.session_name;
1438 info.participants = session.participants;
1439
1440 in_session_ = true;
1444
1445 if (toast_manager_) {
1447 absl::StrFormat("Hosting network session: %s", session_name),
1448 ToastType::kSuccess, 3.5f);
1449 }
1450
1451 return info;
1452 }
1453#endif
1454
1455 return absl::InvalidArgumentError("Unsupported collaboration mode");
1456}
1457
1458absl::StatusOr<AgentEditor::SessionInfo> AgentEditor::JoinSession(
1459 const std::string& session_code, CollaborationMode mode) {
1460 current_mode_ = mode;
1461
1462 if (mode == CollaborationMode::kLocal) {
1463 ASSIGN_OR_RETURN(auto session,
1464 local_coordinator_->JoinSession(session_code));
1465
1466 SessionInfo info;
1467 info.session_id = session.session_id;
1468 info.session_name = session.session_name;
1469 info.participants = session.participants;
1470
1471 in_session_ = true;
1475
1476 // Switch chat to shared history
1477 if (chat_widget_) {
1478 chat_widget_->SwitchToSharedHistory(info.session_id);
1479 }
1480
1481 if (toast_manager_) {
1483 absl::StrFormat("Joined local session: %s", session_code),
1484 ToastType::kSuccess, 3.5f);
1485 }
1486
1487 return info;
1488 }
1489
1490#ifdef YAZE_WITH_GRPC
1491 if (mode == CollaborationMode::kNetwork) {
1492 if (!network_coordinator_) {
1493 return absl::FailedPreconditionError(
1494 "Network coordinator not initialized. Connect to a server first.");
1495 }
1496
1497 const char* username = std::getenv("USER");
1498 if (!username) {
1499 username = std::getenv("USERNAME");
1500 }
1501 if (!username) {
1502 username = "unknown";
1503 }
1504
1505 ASSIGN_OR_RETURN(auto session,
1506 network_coordinator_->JoinSession(session_code, username));
1507
1508 SessionInfo info;
1509 info.session_id = session.session_id;
1510 info.session_name = session.session_name;
1511 info.participants = session.participants;
1512
1513 in_session_ = true;
1517
1518 if (toast_manager_) {
1520 absl::StrFormat("Joined network session: %s", session_code),
1521 ToastType::kSuccess, 3.5f);
1522 }
1523
1524 return info;
1525 }
1526#endif
1527
1528 return absl::InvalidArgumentError("Unsupported collaboration mode");
1529}
1530
1532 if (!in_session_) {
1533 return absl::FailedPreconditionError("Not in a session");
1534 }
1535
1537 RETURN_IF_ERROR(local_coordinator_->LeaveSession());
1538 }
1539#ifdef YAZE_WITH_GRPC
1541 if (network_coordinator_) {
1542 RETURN_IF_ERROR(network_coordinator_->LeaveSession());
1543 }
1544 }
1545#endif
1546
1547 // Switch chat back to local history
1548 if (chat_widget_) {
1549 chat_widget_->SwitchToLocalHistory();
1550 }
1551
1552 in_session_ = false;
1553 current_session_id_.clear();
1554 current_session_name_.clear();
1555 current_participants_.clear();
1556
1557 if (toast_manager_) {
1558 toast_manager_->Show("Left collaboration session", ToastType::kInfo, 3.0f);
1559 }
1560
1561 return absl::OkStatus();
1562}
1563
1564absl::StatusOr<AgentEditor::SessionInfo> AgentEditor::RefreshSession() {
1565 if (!in_session_) {
1566 return absl::FailedPreconditionError("Not in a session");
1567 }
1568
1570 ASSIGN_OR_RETURN(auto session, local_coordinator_->RefreshSession());
1571
1572 SessionInfo info;
1573 info.session_id = session.session_id;
1574 info.session_name = session.session_name;
1575 info.participants = session.participants;
1576
1578
1579 return info;
1580 }
1581
1582 // Network mode doesn't need explicit refresh - it's real-time
1583 SessionInfo info;
1587 return info;
1588}
1589
1591 [[maybe_unused]] std::filesystem::path* output_path,
1592 [[maybe_unused]] const CaptureConfig& config) {
1593 return absl::UnimplementedError(
1594 "CaptureSnapshot should be called through the chat widget UI");
1595}
1596
1598 [[maybe_unused]] const std::filesystem::path& image_path,
1599 [[maybe_unused]] const std::string& prompt) {
1600 return absl::UnimplementedError(
1601 "SendToGemini should be called through the chat widget UI");
1602}
1603
1604#ifdef YAZE_WITH_GRPC
1605absl::Status AgentEditor::ConnectToServer(const std::string& server_url) {
1606 try {
1607 network_coordinator_ =
1608 std::make_unique<NetworkCollaborationCoordinator>(server_url);
1609
1610 if (toast_manager_) {
1612 absl::StrFormat("Connected to server: %s", server_url),
1613 ToastType::kSuccess, 3.0f);
1614 }
1615
1616 return absl::OkStatus();
1617 } catch (const std::exception& e) {
1618 return absl::InternalError(
1619 absl::StrFormat("Failed to connect to server: %s", e.what()));
1620 }
1621}
1622
1623void AgentEditor::DisconnectFromServer() {
1625 LeaveSession();
1626 }
1627 network_coordinator_.reset();
1628
1629 if (toast_manager_) {
1630 toast_manager_->Show("Disconnected from server", ToastType::kInfo, 2.5f);
1631 }
1632}
1633
1634bool AgentEditor::IsConnectedToServer() const {
1635 return network_coordinator_ && network_coordinator_->IsConnected();
1636}
1637#endif
1638
1640 return in_session_;
1641}
1642
1646
1647std::optional<AgentEditor::SessionInfo> AgentEditor::GetCurrentSession() const {
1648 if (!in_session_) {
1649 return std::nullopt;
1650 }
1651
1652 SessionInfo info;
1656 return info;
1657}
1658
1660 if (!chat_widget_) {
1661 return;
1662 }
1663
1665
1666 collab_callbacks.host_session = [this](const std::string& session_name)
1667 -> absl::StatusOr<
1669 ASSIGN_OR_RETURN(auto session,
1670 this->HostSession(session_name, current_mode_));
1671
1673 context.session_id = session.session_id;
1674 context.session_name = session.session_name;
1675 context.participants = session.participants;
1676 return context;
1677 };
1678
1679 collab_callbacks.join_session = [this](const std::string& session_code)
1680 -> absl::StatusOr<
1682 ASSIGN_OR_RETURN(auto session,
1683 this->JoinSession(session_code, current_mode_));
1684
1686 context.session_id = session.session_id;
1687 context.session_name = session.session_name;
1688 context.participants = session.participants;
1689 return context;
1690 };
1691
1692 collab_callbacks.leave_session = [this]() {
1693 return this->LeaveSession();
1694 };
1695
1696 collab_callbacks.refresh_session =
1697 [this]() -> absl::StatusOr<
1699 ASSIGN_OR_RETURN(auto session, this->RefreshSession());
1700
1702 context.session_id = session.session_id;
1703 context.session_name = session.session_name;
1704 context.participants = session.participants;
1705 return context;
1706 };
1707
1708 chat_widget_->SetCollaborationCallbacks(collab_callbacks);
1709}
1710
1712 // Multimodal callbacks are set up by the EditorManager since it has
1713 // access to the screenshot utilities. We just initialize the structure here.
1714}
1715
1716} // namespace editor
1717} // 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:74
bool is_loaded() const
Definition rom.h:200
auto title() const
Definition rom.h:204
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:164
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.
Definition controller.cc:20
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