imgui-frontend-engineer: refine agent editor wiring

This commit is contained in:
scawful
2025-12-28 10:52:01 -06:00
parent 14a3084c7f
commit a65fc1d975
8 changed files with 341 additions and 127 deletions

View File

@@ -7,6 +7,7 @@
#include "app/service/canvas_automation_service.h"
#include "app/service/unified_grpc_server.h"
#include "app/test/test_manager.h"
#include "cli/service/agent/emulator_service_impl.h"
#endif
#ifdef __EMSCRIPTEN__
@@ -26,47 +27,8 @@ void Application::Initialize(const AppConfig& config) {
config_ = config;
LOG_INFO("App", "Initializing Application instance...");
#ifdef YAZE_WITH_GRPC
// Initialize gRPC server if enabled
if (config_.enable_test_harness) {
LOG_INFO("App", "Initializing gRPC automation services...");
canvas_automation_service_ = std::make_unique<CanvasAutomationServiceImpl>();
grpc_server_ = std::make_unique<YazeGRPCServer>();
// Initialize server with all services
// Note: RomService and ProposalApprovalManager will be connected later
// when we have a session context, but we can start the server now.
auto status = grpc_server_->Initialize(
config_.test_harness_port,
&yaze::test::TestManager::Get(),
nullptr, // ROM not loaded yet
nullptr, // Version manager not ready
nullptr, // Approval manager not ready
canvas_automation_service_.get()
);
if (status.ok()) {
status = grpc_server_->StartAsync(); // Start in background thread
if (!status.ok()) {
LOG_ERROR("App", "Failed to start gRPC server: %s", std::string(status.message()).c_str());
} else {
LOG_INFO("App", "gRPC server started on port %d", config_.test_harness_port);
}
} else {
LOG_ERROR("App", "Failed to initialize gRPC server: %s", std::string(status.message()).c_str());
}
}
#endif
controller_ = std::make_unique<Controller>();
#ifdef YAZE_WITH_GRPC
// Connect services to controller/editor manager
if (canvas_automation_service_) {
controller_->SetCanvasAutomationService(canvas_automation_service_.get());
}
#endif
// Process pending ROM load if we have one (from flags/config - non-WASM only)
std::string start_path = config_.rom_file;
@@ -102,6 +64,58 @@ void Application::Initialize(const AppConfig& config) {
if (!start_path.empty() && controller_->editor_manager()) {
RunStartupActions();
}
#ifdef YAZE_WITH_GRPC
// Initialize gRPC server if enabled
if (config_.enable_test_harness) {
LOG_INFO("App", "Initializing gRPC automation services...");
canvas_automation_service_ = std::make_unique<CanvasAutomationServiceImpl>();
grpc_server_ = std::make_unique<YazeGRPCServer>();
auto rom_getter = [this]() { return controller_->GetCurrentRom(); };
if (controller_->editor_manager()) {
auto emulator_service =
std::make_unique<yaze::net::EmulatorServiceImpl>(
&controller_->editor_manager()->emulator(),
rom_getter);
auto add_status =
grpc_server_->AddService(std::move(emulator_service));
if (!add_status.ok()) {
LOG_ERROR("App", "Failed to attach emulator service: %s",
std::string(add_status.message()).c_str());
}
} else {
LOG_WARN("App", "EditorManager not ready; emulator gRPC disabled");
}
// Initialize server with all services
auto status = grpc_server_->Initialize(
config_.test_harness_port,
&yaze::test::TestManager::Get(),
rom_getter,
nullptr, // Version manager not ready
nullptr, // Approval manager not ready
canvas_automation_service_.get()
);
if (status.ok()) {
status = grpc_server_->StartAsync(); // Start in background thread
if (!status.ok()) {
LOG_ERROR("App", "Failed to start gRPC server: %s", std::string(status.message()).c_str());
} else {
LOG_INFO("App", "gRPC server started on port %d", config_.test_harness_port);
}
} else {
LOG_ERROR("App", "Failed to initialize gRPC server: %s", std::string(status.message()).c_str());
}
// Connect services to controller/editor manager
if (canvas_automation_service_) {
controller_->SetCanvasAutomationService(canvas_automation_service_.get());
}
}
#endif
}
#ifdef __EMSCRIPTEN__

View File

@@ -2,6 +2,10 @@
#include "app/platform/sdl_compat.h"
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
#include <string>
#include "absl/status/status.h"
@@ -21,8 +25,17 @@ namespace yaze {
absl::Status Controller::OnEntry(std::string filename) {
// Create window backend using factory (auto-selects SDL2 or SDL3)
window_backend_ = platform::WindowBackendFactory::Create(
platform::WindowBackendFactory::GetDefaultType());
auto backend_type = platform::WindowBackendFactory::GetDefaultType();
auto renderer_type = gfx::RendererFactory::GetDefaultBackendType();
#if defined(__APPLE__) && (TARGET_OS_IPHONE == 1 || TARGET_IPHONE_SIMULATOR == 1)
backend_type = platform::WindowBackendType::IOS;
renderer_type = gfx::RendererBackendType::Metal;
#endif
window_backend_ = platform::WindowBackendFactory::Create(backend_type);
if (!window_backend_) {
return absl::InternalError("Failed to create window backend");
}
platform::WindowConfig config;
config.title = "Yet Another Zelda3 Editor";
@@ -32,7 +45,7 @@ absl::Status Controller::OnEntry(std::string filename) {
RETURN_IF_ERROR(window_backend_->Initialize(config));
// Create renderer via factory (auto-selects SDL2 or SDL3)
renderer_ = gfx::RendererFactory::Create();
renderer_ = gfx::RendererFactory::Create(renderer_type);
if (!window_backend_->InitializeRenderer(renderer_.get())) {
return absl::InternalError("Failed to initialize renderer");
}
@@ -162,6 +175,11 @@ absl::Status Controller::OnLoad() {
if (!show_menu_bar && editor_manager_.ui_coordinator()) {
editor_manager_.ui_coordinator()->DrawMenuBarRestoreButton();
}
#else
if (window_backend_) {
window_backend_->NewImGuiFrame();
ImGui::NewFrame();
}
#endif
gui::WidgetIdRegistry::Instance().BeginFrame();
absl::Status update_status = editor_manager_.Update();
@@ -192,7 +210,9 @@ void Controller::DoRender() const {
// Gentle frame rate cap to prevent excessive CPU usage
// Only delay if we're rendering faster than 144 FPS (< 7ms per frame)
if (delta_time < 0.007f) {
#if TARGET_OS_IPHONE != 1
SDL_Delay(1); // Tiny delay to yield CPU without affecting ImGui timing
#endif
}
}

View File

@@ -1,6 +1,7 @@
#include "app/editor/agent/agent_chat.h"
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
@@ -13,6 +14,7 @@
#include "app/gui/core/theme_manager.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "util/platform_paths.h"
#include "util/log.h"
#ifdef YAZE_WITH_JSON
@@ -22,6 +24,27 @@
namespace yaze {
namespace editor {
namespace {
std::string ResolveAgentChatHistoryPath() {
auto agent_dir = util::PlatformPaths::GetAppDataSubdirectory("agent");
if (agent_dir.ok()) {
return (*agent_dir / "agent_chat_history.json").string();
}
auto docs_dir = util::PlatformPaths::GetUserDocumentsSubdirectory("agent");
if (docs_dir.ok()) {
return (*docs_dir / "agent_chat_history.json").string();
}
auto temp_dir = util::PlatformPaths::GetTempDirectory();
if (temp_dir.ok()) {
return (*temp_dir / "agent_chat_history.json").string();
}
return (std::filesystem::current_path() / "agent_chat_history.json")
.string();
}
} // namespace
AgentChat::AgentChat() {
// Default initialization
}
@@ -119,7 +142,7 @@ void AgentChat::RenderToolbar() {
ImGui::SameLine();
if (ImGui::Button(ICON_MD_SAVE " Save")) {
std::string filepath = ".yaze/agent_chat_history.json";
std::string filepath = ResolveAgentChatHistoryPath();
if (auto status = SaveHistory(filepath); !status.ok()) {
if (toast_manager_) {
toast_manager_->Show("Failed to save history: " + std::string(status.message()), ToastType::kError);
@@ -133,7 +156,7 @@ void AgentChat::RenderToolbar() {
ImGui::SameLine();
if (ImGui::Button(ICON_MD_FOLDER_OPEN " Load")) {
std::string filepath = ".yaze/agent_chat_history.json";
std::string filepath = ResolveAgentChatHistoryPath();
if (auto status = LoadHistory(filepath); !status.ok()) {
if (toast_manager_) {
toast_manager_->Show("Failed to load history: " + std::string(status.message()), ToastType::kError);
@@ -480,7 +503,13 @@ absl::Status AgentChat::SaveHistory(const std::string& filepath) {
// Create directory if needed
std::filesystem::path path(filepath);
if (path.has_parent_path()) {
std::filesystem::create_directories(path.parent_path());
std::error_code ec;
std::filesystem::create_directories(path.parent_path(), ec);
if (ec) {
return absl::InternalError(
absl::StrFormat("Failed to create history directory: %s",
ec.message()));
}
}
std::ofstream file(filepath);

View File

@@ -262,12 +262,19 @@ std::string AgentCollaborationCoordinator::GenerateSessionCode() const {
}
std::filesystem::path AgentCollaborationCoordinator::SessionsDirectory() const {
auto config_dir = util::PlatformPaths::GetConfigDirectory();
if (!config_dir.ok()) {
// Fallback to a local directory if config can't be determined.
return fs::current_path() / ".yaze" / "agent" / "sessions";
auto agent_dir = util::PlatformPaths::GetAppDataSubdirectory("agent");
if (agent_dir.ok()) {
return *agent_dir / "sessions";
}
return *config_dir / "agent" / "sessions";
auto docs_dir = util::PlatformPaths::GetUserDocumentsSubdirectory("agent");
if (docs_dir.ok()) {
return *docs_dir / "sessions";
}
auto temp_dir = util::PlatformPaths::GetTempDirectory();
if (temp_dir.ok()) {
return *temp_dir / "agent" / "sessions";
}
return fs::current_path() / "agent" / "sessions";
}
std::filesystem::path AgentCollaborationCoordinator::SessionFilePath(

View File

@@ -1453,11 +1453,20 @@ absl::Status AgentEditor::ImportProfile(const std::filesystem::path& path) {
}
std::filesystem::path AgentEditor::GetProfilesDirectory() const {
auto config_dir = yaze::util::PlatformPaths::GetConfigDirectory();
if (!config_dir.ok()) {
return std::filesystem::current_path() / ".yaze" / "agent" / "profiles";
auto agent_dir = yaze::util::PlatformPaths::GetAppDataSubdirectory("agent");
if (agent_dir.ok()) {
return *agent_dir / "profiles";
}
return *config_dir / "agent" / "profiles";
auto docs_dir =
yaze::util::PlatformPaths::GetUserDocumentsSubdirectory("agent");
if (docs_dir.ok()) {
return *docs_dir / "profiles";
}
auto temp_dir = yaze::util::PlatformPaths::GetTempDirectory();
if (temp_dir.ok()) {
return *temp_dir / "agent" / "profiles";
}
return std::filesystem::current_path() / "agent" / "profiles";
}
absl::Status AgentEditor::EnsureProfilesDirectory() {

View File

@@ -14,8 +14,8 @@
#include "app/gui/core/input.h"
#include "dungeon_canvas_viewer.h"
#include "dungeon_coordinates.h"
#include "canvas/canvas_menu.h"
#include "core/icons.h"
#include "app/gui/canvas/canvas_menu.h"
#include "app/gui/core/icons.h"
#include "absl/status/status.h"
#include "editor/dungeon/object_selection.h"
#include "imgui/imgui.h"

View File

@@ -17,6 +17,10 @@
#include <utility>
#include <vector>
#ifdef __APPLE__
#include <TargetConditionals.h>
#endif
// Third-party library headers
#define IMGUI_DEFINE_MATH_OPERATORS
#include "absl/status/status.h"
@@ -270,7 +274,8 @@ EditorManager::EditorManager()
project_management_panel_->SetToastManager(&toast_manager_);
project_management_panel_->SetSwapRomCallback([this]() {
// Prompt user to select a new ROM for the project
auto rom_path = util::FileDialogWrapper::ShowOpenFileDialog();
auto rom_path = util::FileDialogWrapper::ShowOpenFileDialog(
util::MakeRomFileDialogOptions(false));
if (!rom_path.empty()) {
current_project_.rom_filename = rom_path;
auto status = current_project_.Save();
@@ -1480,7 +1485,7 @@ void EditorManager::DrawMenuBar() {
* 8. Update UI state and recent files
*/
absl::Status EditorManager::LoadRom() {
auto file_name = util::FileDialogWrapper::ShowOpenFileDialog();
auto load_from_path = [this](const std::string& file_name) -> absl::Status {
if (file_name.empty()) {
return absl::OkStatus();
}
@@ -1535,6 +1540,25 @@ absl::Status EditorManager::LoadRom() {
}
return absl::OkStatus();
};
#if defined(__APPLE__) && TARGET_OS_IOS == 1
util::FileDialogWrapper::ShowOpenFileDialogAsync(
util::MakeRomFileDialogOptions(false),
[this, load_from_path](const std::string& file_name) {
auto status = load_from_path(file_name);
if (!status.ok()) {
toast_manager_.Show(
absl::StrFormat("Failed to load ROM: %s", status.message()),
ToastType::kError);
}
});
return absl::OkStatus();
#else
auto file_name = util::FileDialogWrapper::ShowOpenFileDialog(
util::MakeRomFileDialogOptions(false));
return load_from_path(file_name);
#endif
}
absl::Status EditorManager::LoadAssets(uint64_t passed_handle) {
@@ -1900,16 +1924,19 @@ absl::Status EditorManager::CreateNewProject(const std::string& template_name) {
// Trigger ROM selection dialog - projects need a ROM to be useful
// LoadRom() opens file dialog and shows ROM load options when ROM is loaded
status = LoadRom();
#if !(defined(__APPLE__) && TARGET_OS_IOS == 1)
if (status.ok() && ui_coordinator_) {
ui_coordinator_->SetWelcomeScreenVisible(false);
ui_coordinator_->SetWelcomeScreenManuallyClosed(true);
}
#endif
}
return status;
}
absl::Status EditorManager::OpenProject() {
auto file_path = util::FileDialogWrapper::ShowOpenFileDialog();
auto open_project_from_path =
[this](const std::string& file_path) -> absl::Status {
if (file_path.empty()) {
return absl::OkStatus();
}
@@ -1931,10 +1958,29 @@ absl::Status EditorManager::OpenProject() {
current_project_ = std::move(new_project);
// Initialize VersionManager for the project
version_manager_ = std::make_unique<core::VersionManager>(&current_project_);
version_manager_ =
std::make_unique<core::VersionManager>(&current_project_);
version_manager_->InitializeGit();
return LoadProjectWithRom();
};
#if defined(__APPLE__) && TARGET_OS_IOS == 1
util::FileDialogWrapper::ShowOpenFileDialogAsync(
util::FileDialogOptions{},
[this, open_project_from_path](const std::string& file_path) {
auto status = open_project_from_path(file_path);
if (!status.ok()) {
toast_manager_.Show(
absl::StrFormat("Failed to open project: %s", status.message()),
ToastType::kError);
}
});
return absl::OkStatus();
#else
auto file_path = util::FileDialogWrapper::ShowOpenFileDialog();
return open_project_from_path(file_path);
#endif
}
absl::Status EditorManager::LoadProjectWithRom() {
@@ -1944,13 +1990,41 @@ absl::Status EditorManager::LoadProjectWithRom() {
toast_manager_.Show(
"Project has no ROM file configured. Please select a ROM.",
editor::ToastType::kInfo);
auto rom_path = util::FileDialogWrapper::ShowOpenFileDialog();
#if defined(__APPLE__) && TARGET_OS_IOS == 1
util::FileDialogWrapper::ShowOpenFileDialogAsync(
util::MakeRomFileDialogOptions(false),
[this](const std::string& rom_path) {
if (rom_path.empty()) {
return;
}
current_project_.rom_filename = rom_path;
auto save_status = current_project_.Save();
if (!save_status.ok()) {
toast_manager_.Show(
absl::StrFormat("Failed to update project ROM: %s",
save_status.message()),
ToastType::kError);
return;
}
auto status = LoadProjectWithRom();
if (!status.ok()) {
toast_manager_.Show(
absl::StrFormat("Failed to load project ROM: %s",
status.message()),
ToastType::kError);
}
});
return absl::OkStatus();
#else
auto rom_path = util::FileDialogWrapper::ShowOpenFileDialog(
util::MakeRomFileDialogOptions(false));
if (rom_path.empty()) {
return absl::OkStatus();
}
current_project_.rom_filename = rom_path;
// Save updated project
RETURN_IF_ERROR(current_project_.Save());
#endif
}
// Load ROM from project
@@ -1963,14 +2037,41 @@ absl::Status EditorManager::LoadProjectWithRom() {
absl::StrFormat("Could not load ROM '%s': %s. Please select a new ROM.",
current_project_.rom_filename, load_status.message()),
editor::ToastType::kWarning, 5.0f);
auto rom_path = util::FileDialogWrapper::ShowOpenFileDialog();
#if defined(__APPLE__) && TARGET_OS_IOS == 1
util::FileDialogWrapper::ShowOpenFileDialogAsync(
util::MakeRomFileDialogOptions(false),
[this](const std::string& rom_path) {
if (rom_path.empty()) {
return;
}
current_project_.rom_filename = rom_path;
auto save_status = current_project_.Save();
if (!save_status.ok()) {
toast_manager_.Show(
absl::StrFormat("Failed to update project ROM: %s",
save_status.message()),
ToastType::kError);
return;
}
auto status = LoadProjectWithRom();
if (!status.ok()) {
toast_manager_.Show(
absl::StrFormat("Failed to load project ROM: %s",
status.message()),
ToastType::kError);
}
});
return absl::OkStatus();
#else
auto rom_path = util::FileDialogWrapper::ShowOpenFileDialog(
util::MakeRomFileDialogOptions(false));
if (rom_path.empty()) {
return absl::OkStatus();
}
current_project_.rom_filename = rom_path;
RETURN_IF_ERROR(current_project_.Save());
RETURN_IF_ERROR(rom_file_manager_.LoadRom(&temp_rom, rom_path));
#endif
}
auto session_or = session_coordinator_->CreateSessionFromRom(

View File

@@ -1,6 +1,7 @@
#include "app/gui/app/agent_chat_widget.h"
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
@@ -8,6 +9,7 @@
#include "absl/time/time.h"
#include "imgui/imgui.h"
#include "imgui/misc/cpp/imgui_stdlib.h"
#include "util/platform_paths.h"
#ifdef YAZE_WITH_JSON
#include "nlohmann/json.hpp"
@@ -17,6 +19,27 @@ namespace yaze {
namespace gui {
namespace {
std::string ResolveAgentChatHistoryPath() {
auto agent_dir = util::PlatformPaths::GetAppDataSubdirectory("agent");
if (agent_dir.ok()) {
return (*agent_dir / "agent_chat_history.json").string();
}
auto docs_dir = util::PlatformPaths::GetUserDocumentsSubdirectory("agent");
if (docs_dir.ok()) {
return (*docs_dir / "agent_chat_history.json").string();
}
auto temp_dir = util::PlatformPaths::GetTempDirectory();
if (temp_dir.ok()) {
return (*temp_dir / "agent_chat_history.json").string();
}
return (std::filesystem::current_path() / "agent_chat_history.json")
.string();
}
} // namespace
AgentChatWidget::AgentChatWidget()
: scroll_to_bottom_(false),
auto_scroll_(true),
@@ -99,7 +122,7 @@ void AgentChatWidget::RenderToolbar() {
ImGui::SameLine();
if (ImGui::Button("Save History")) {
std::string filepath = ".yaze/agent_chat_history.json";
std::string filepath = ResolveAgentChatHistoryPath();
if (auto status = SaveHistory(filepath); !status.ok()) {
std::cerr << "Failed to save history: " << status.message() << std::endl;
} else {
@@ -109,7 +132,7 @@ void AgentChatWidget::RenderToolbar() {
ImGui::SameLine();
if (ImGui::Button("Load History")) {
std::string filepath = ".yaze/agent_chat_history.json";
std::string filepath = ResolveAgentChatHistoryPath();
if (auto status = LoadHistory(filepath); !status.ok()) {
std::cerr << "Failed to load history: " << status.message() << std::endl;
}
@@ -297,6 +320,17 @@ absl::Status AgentChatWidget::SaveHistory(const std::string& filepath) {
return absl::FailedPreconditionError("Agent service not initialized");
}
std::filesystem::path path(filepath);
if (path.has_parent_path()) {
std::error_code ec;
std::filesystem::create_directories(path.parent_path(), ec);
if (ec) {
return absl::InternalError(
absl::StrFormat("Failed to create history directory: %s",
ec.message()));
}
}
std::ofstream file(filepath);
if (!file.is_open()) {
return absl::InternalError(