diff --git a/src/app/application.cc b/src/app/application.cc index 34cb8512..f3c38038 100644 --- a/src/app/application.cc +++ b/src/app/application.cc @@ -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(); - grpc_server_ = std::make_unique(); - - // 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(); -#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(); + grpc_server_ = std::make_unique(); + + auto rom_getter = [this]() { return controller_->GetCurrentRom(); }; + + if (controller_->editor_manager()) { + auto emulator_service = + std::make_unique( + &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__ diff --git a/src/app/controller.cc b/src/app/controller.cc index 51efe904..588fa178 100644 --- a/src/app/controller.cc +++ b/src/app/controller.cc @@ -2,6 +2,10 @@ #include "app/platform/sdl_compat.h" +#if defined(__APPLE__) +#include +#endif + #include #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 } } diff --git a/src/app/editor/agent/agent_chat.cc b/src/app/editor/agent/agent_chat.cc index 6285463d..1288ad93 100644 --- a/src/app/editor/agent/agent_chat.cc +++ b/src/app/editor/agent/agent_chat.cc @@ -1,6 +1,7 @@ #include "app/editor/agent/agent_chat.h" #include +#include #include #include @@ -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); diff --git a/src/app/editor/agent/agent_collaboration_coordinator.cc b/src/app/editor/agent/agent_collaboration_coordinator.cc index b2098f5a..b5e695d9 100644 --- a/src/app/editor/agent/agent_collaboration_coordinator.cc +++ b/src/app/editor/agent/agent_collaboration_coordinator.cc @@ -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( diff --git a/src/app/editor/agent/agent_editor.cc b/src/app/editor/agent/agent_editor.cc index d10c1ba8..45b844a1 100644 --- a/src/app/editor/agent/agent_editor.cc +++ b/src/app/editor/agent/agent_editor.cc @@ -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() { diff --git a/src/app/editor/dungeon/dungeon_canvas_viewer.cc b/src/app/editor/dungeon/dungeon_canvas_viewer.cc index 5fa8ca92..4fe660a5 100644 --- a/src/app/editor/dungeon/dungeon_canvas_viewer.cc +++ b/src/app/editor/dungeon/dungeon_canvas_viewer.cc @@ -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" diff --git a/src/app/editor/editor_manager.cc b/src/app/editor/editor_manager.cc index 2b8a4f24..9cd608e4 100644 --- a/src/app/editor/editor_manager.cc +++ b/src/app/editor/editor_manager.cc @@ -17,6 +17,10 @@ #include #include +#ifdef __APPLE__ +#include +#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,61 +1485,80 @@ void EditorManager::DrawMenuBar() { * 8. Update UI state and recent files */ absl::Status EditorManager::LoadRom() { - auto file_name = util::FileDialogWrapper::ShowOpenFileDialog(); - if (file_name.empty()) { - return absl::OkStatus(); - } + auto load_from_path = [this](const std::string& file_name) -> absl::Status { + if (file_name.empty()) { + return absl::OkStatus(); + } - // Check if this is a project file - route to project loading - if (absl::StrContains(file_name, ".yaze")) { - return OpenRomOrProject(file_name); - } + // Check if this is a project file - route to project loading + if (absl::StrContains(file_name, ".yaze")) { + return OpenRomOrProject(file_name); + } - if (session_coordinator_->HasDuplicateSession(file_name)) { - toast_manager_.Show("ROM already open in another session", - editor::ToastType::kWarning); - return absl::OkStatus(); - } + if (session_coordinator_->HasDuplicateSession(file_name)) { + toast_manager_.Show("ROM already open in another session", + editor::ToastType::kWarning); + return absl::OkStatus(); + } - // Delegate ROM loading to RomFileManager - Rom temp_rom; - RETURN_IF_ERROR(rom_file_manager_.LoadRom(&temp_rom, file_name)); + // Delegate ROM loading to RomFileManager + Rom temp_rom; + RETURN_IF_ERROR(rom_file_manager_.LoadRom(&temp_rom, file_name)); - auto session_or = session_coordinator_->CreateSessionFromRom( - std::move(temp_rom), file_name); - if (!session_or.ok()) { - return session_or.status(); - } + auto session_or = session_coordinator_->CreateSessionFromRom( + std::move(temp_rom), file_name); + if (!session_or.ok()) { + return session_or.status(); + } - ConfigureEditorDependencies(GetCurrentEditorSet(), GetCurrentRom(), - GetCurrentSessionId()); + ConfigureEditorDependencies(GetCurrentEditorSet(), GetCurrentRom(), + GetCurrentSessionId()); - // Initialize resource labels for LoadRom() - use defaults with current project settings - auto& label_provider = zelda3::GetResourceLabels(); - label_provider.SetProjectLabels(¤t_project_.resource_labels); - label_provider.SetPreferHMagicNames( - current_project_.workspace_settings.prefer_hmagic_names); - LOG_INFO("EditorManager", "Initialized ResourceLabelProvider for LoadRom"); + // Initialize resource labels for LoadRom() - use defaults with current project settings + auto& label_provider = zelda3::GetResourceLabels(); + label_provider.SetProjectLabels(¤t_project_.resource_labels); + label_provider.SetPreferHMagicNames( + current_project_.workspace_settings.prefer_hmagic_names); + LOG_INFO("EditorManager", "Initialized ResourceLabelProvider for LoadRom"); #ifdef YAZE_ENABLE_TESTING - test::TestManager::Get().SetCurrentRom(GetCurrentRom()); + test::TestManager::Get().SetCurrentRom(GetCurrentRom()); #endif - auto& manager = project::RecentFilesManager::GetInstance(); - manager.AddFile(file_name); - manager.Save(); + auto& manager = project::RecentFilesManager::GetInstance(); + manager.AddFile(file_name); + manager.Save(); - RETURN_IF_ERROR(LoadAssets()); + RETURN_IF_ERROR(LoadAssets()); - if (ui_coordinator_) { - ui_coordinator_->SetWelcomeScreenVisible(false); + if (ui_coordinator_) { + ui_coordinator_->SetWelcomeScreenVisible(false); - // Show ROM load options dialog for ZSCustomOverworld and feature settings - rom_load_options_dialog_.Open(GetCurrentRom()); - show_rom_load_options_ = true; - } + // Show ROM load options dialog for ZSCustomOverworld and feature settings + rom_load_options_dialog_.Open(GetCurrentRom()); + show_rom_load_options_ = true; + } + 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,41 +1924,63 @@ 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 open_project_from_path = + [this](const std::string& file_path) -> absl::Status { + if (file_path.empty()) { + return absl::OkStatus(); + } + + project::YazeProject new_project; + RETURN_IF_ERROR(new_project.Open(file_path)); + + // Validate project + auto validation_status = new_project.Validate(); + if (!validation_status.ok()) { + toast_manager_.Show(absl::StrFormat("Project validation failed: %s", + validation_status.message()), + editor::ToastType::kWarning, 5.0f); + + // Ask user if they want to repair + popup_manager_->Show("Project Repair"); + } + + current_project_ = std::move(new_project); + + // Initialize VersionManager for the project + version_manager_ = + std::make_unique(¤t_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(); - if (file_path.empty()) { - return absl::OkStatus(); - } - - project::YazeProject new_project; - RETURN_IF_ERROR(new_project.Open(file_path)); - - // Validate project - auto validation_status = new_project.Validate(); - if (!validation_status.ok()) { - toast_manager_.Show(absl::StrFormat("Project validation failed: %s", - validation_status.message()), - editor::ToastType::kWarning, 5.0f); - - // Ask user if they want to repair - popup_manager_->Show("Project Repair"); - } - - current_project_ = std::move(new_project); - - // Initialize VersionManager for the project - version_manager_ = std::make_unique(¤t_project_); - version_manager_->InitializeGit(); - - return LoadProjectWithRom(); + 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( diff --git a/src/app/gui/app/agent_chat_widget.cc b/src/app/gui/app/agent_chat_widget.cc index 528832a7..d32bffc1 100644 --- a/src/app/gui/app/agent_chat_widget.cc +++ b/src/app/gui/app/agent_chat_widget.cc @@ -1,6 +1,7 @@ #include "app/gui/app/agent_chat_widget.h" #include +#include #include #include @@ -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(