29 auto parent = path.parent_path();
31 return absl::OkStatus();
39 return absl::InvalidArgumentError(
"prefs is null");
44 return absl::OkStatus();
47 std::istringstream ss(data);
49 while (std::getline(ss, line)) {
50 size_t eq_pos = line.find(
'=');
51 if (eq_pos == std::string::npos) {
55 std::string key = line.substr(0, eq_pos);
56 std::string val = line.substr(eq_pos + 1);
59 if (key ==
"font_global_scale") {
61 }
else if (key ==
"backup_rom") {
63 }
else if (key ==
"save_new_auto") {
65 }
else if (key ==
"autosave_enabled") {
67 }
else if (key ==
"autosave_interval") {
69 }
else if (key ==
"recent_files_limit") {
71 }
else if (key ==
"last_rom_path") {
73 }
else if (key ==
"last_project_path") {
75 }
else if (key ==
"show_welcome_on_startup") {
77 }
else if (key ==
"restore_last_session") {
79 }
else if (key ==
"prefer_hmagic_sprite_names") {
81 }
else if (key ==
"reduced_motion") {
83 }
else if (key ==
"switch_motion_profile") {
87 else if (key ==
"backup_before_save") {
89 }
else if (key ==
"default_editor") {
93 else if (key ==
"vsync") {
94 prefs->
vsync = (val ==
"1");
95 }
else if (key ==
"target_fps") {
97 }
else if (key ==
"cache_size_mb") {
99 }
else if (key ==
"undo_history_size") {
103 else if (key ==
"ai_provider") {
105 }
else if (key ==
"ai_model") {
107 }
else if (key ==
"ollama_url") {
109 }
else if (key ==
"gemini_api_key") {
111 }
else if (key ==
"openai_api_key") {
113 }
else if (key ==
"anthropic_api_key") {
115 }
else if (key ==
"ai_temperature") {
117 }
else if (key ==
"ai_max_tokens") {
119 }
else if (key ==
"ai_proactive") {
121 }
else if (key ==
"ai_auto_learn") {
123 }
else if (key ==
"ai_multimodal") {
127 else if (key ==
"log_level") {
129 }
else if (key ==
"log_to_file") {
131 }
else if (key ==
"log_file_path") {
133 }
else if (key ==
"log_ai_requests") {
135 }
else if (key ==
"log_rom_operations") {
137 }
else if (key ==
"log_gui_automation") {
139 }
else if (key ==
"log_proposals") {
143 else if (key.substr(0, 15) ==
"panel_shortcut.") {
144 std::string panel_id = key.substr(15);
148 else if (key.substr(0, 14) ==
"card_shortcut.") {
149 std::string panel_id = key.substr(14);
153 else if (key ==
"sidebar_visible") {
155 }
else if (key ==
"sidebar_panel_expanded") {
157 }
else if (key ==
"sidebar_panel_width") {
159 }
else if (key ==
"panel_browser_category_width") {
161 }
else if (key ==
"panel_layout_defaults_revision") {
163 }
else if (key ==
"sidebar_active_category") {
167 else if (key ==
"show_status_bar") {
171 else if (key.substr(0, 17) ==
"panel_visibility.") {
172 std::string rest = key.substr(17);
173 size_t dot_pos = rest.find(
'.');
174 if (dot_pos != std::string::npos) {
175 std::string editor_type = rest.substr(0, dot_pos);
176 std::string panel_id = rest.substr(dot_pos + 1);
181 else if (key.substr(0, 13) ==
"pinned_panel.") {
182 std::string panel_id = key.substr(13);
186 else if (key.substr(0, 18) ==
"right_panel_width.") {
187 std::string panel_key = key.substr(18);
191 else if (key.substr(0, 13) ==
"saved_layout.") {
192 std::string rest = key.substr(13);
193 size_t dot_pos = rest.find(
'.');
194 if (dot_pos != std::string::npos) {
195 std::string layout_name = rest.substr(0, dot_pos);
196 std::string panel_id = rest.substr(dot_pos + 1);
202 return absl::OkStatus();
208 if (!ensure_status.ok()) {
209 return ensure_status;
212 std::ostringstream ss;
215 ss <<
"backup_rom=" << (prefs.
backup_rom ? 1 : 0) <<
"\n";
216 ss <<
"save_new_auto=" << (prefs.
save_new_auto ? 1 : 0) <<
"\n";
225 ss <<
"prefer_hmagic_sprite_names="
227 ss <<
"reduced_motion=" << (prefs.
reduced_motion ? 1 : 0) <<
"\n";
235 ss <<
"vsync=" << (prefs.
vsync ? 1 : 0) <<
"\n";
236 ss <<
"target_fps=" << prefs.
target_fps <<
"\n";
242 ss <<
"ai_model=" << prefs.
ai_model <<
"\n";
243 ss <<
"ollama_url=" << prefs.
ollama_url <<
"\n";
249 ss <<
"ai_proactive=" << (prefs.
ai_proactive ? 1 : 0) <<
"\n";
250 ss <<
"ai_auto_learn=" << (prefs.
ai_auto_learn ? 1 : 0) <<
"\n";
251 ss <<
"ai_multimodal=" << (prefs.
ai_multimodal ? 1 : 0) <<
"\n";
254 ss <<
"log_level=" << prefs.
log_level <<
"\n";
255 ss <<
"log_to_file=" << (prefs.
log_to_file ? 1 : 0) <<
"\n";
260 ss <<
"log_proposals=" << (prefs.
log_proposals ? 1 : 0) <<
"\n";
264 ss <<
"panel_shortcut." << panel_id <<
"=" << shortcut <<
"\n";
272 ss <<
"panel_browser_category_width="
274 ss <<
"panel_layout_defaults_revision="
283 for (
const auto& [panel_id, visible] : panel_state) {
284 ss <<
"panel_visibility." << editor_type <<
"." << panel_id <<
"="
285 << (visible ? 1 : 0) <<
"\n";
291 ss <<
"pinned_panel." << panel_id <<
"=" << (pinned ? 1 : 0) <<
"\n";
295 ss <<
"right_panel_width." << panel_key <<
"=" << width <<
"\n";
299 for (
const auto& [layout_name, panel_state] : prefs.
saved_layouts) {
300 for (
const auto& [panel_id, visible] : panel_state) {
301 ss <<
"saved_layout." << layout_name <<
"." << panel_id <<
"="
302 << (visible ? 1 : 0) <<
"\n";
306 std::ofstream file(path);
307 if (!file.is_open()) {
308 return absl::InternalError(
309 absl::StrFormat(
"Failed to open settings file: %s", path.string()));
312 return absl::OkStatus();
329 UserSettings::Preferences::AiHost host;
330 host.id =
"ollama-local";
331 host.label =
"Ollama (local)";
333 host.api_type =
"ollama";
334 host.supports_tools =
true;
335 host.supports_streaming =
true;
340 UserSettings::Preferences::AiHost lmstudio;
341 lmstudio.id =
"lmstudio-local";
342 lmstudio.label =
"LM Studio (local)";
343 lmstudio.base_url =
"http://localhost:1234";
344 lmstudio.api_type =
"lmstudio";
345 lmstudio.supports_tools =
true;
346 lmstudio.supports_streaming =
true;
347 prefs->
ai_hosts.push_back(lmstudio);
354void EnsureDefaultAiProfiles(UserSettings::Preferences* prefs) {
358 if (!prefs->ai_profiles.empty()) {
359 if (prefs->active_ai_profile.empty()) {
360 prefs->active_ai_profile = prefs->ai_profiles.front().name;
364 if (!prefs->ai_model.empty()) {
365 UserSettings::Preferences::AiModelProfile profile;
366 profile.name =
"default";
367 profile.model = prefs->ai_model;
368 profile.temperature = prefs->ai_temperature;
369 profile.top_p = 0.95f;
370 profile.max_output_tokens = prefs->ai_max_tokens;
371 profile.supports_tools =
true;
372 prefs->ai_profiles.push_back(profile);
373 prefs->active_ai_profile = profile.name;
377void EnsureDefaultFilesystemRoots(UserSettings::Preferences* prefs) {
382 auto add_unique_root = [&](
const std::filesystem::path& path) {
386 const std::string path_str = path.string();
387 auto it = std::find(prefs->project_root_paths.begin(),
388 prefs->project_root_paths.end(), path_str);
389 if (it == prefs->project_root_paths.end()) {
390 prefs->project_root_paths.push_back(path_str);
396 add_unique_root(*docs_dir);
399 if (prefs->use_icloud_sync) {
402 if (icloud_dir.ok()) {
403 add_unique_root(*icloud_dir);
404 if (prefs->default_project_root.empty()) {
405 prefs->default_project_root = icloud_dir->string();
410 if (prefs->default_project_root.empty() &&
411 !prefs->project_root_paths.empty()) {
412 prefs->default_project_root = prefs->project_root_paths.front();
416void EnsureDefaultModelPaths(UserSettings::Preferences* prefs) {
420 if (!prefs->ai_model_paths.empty()) {
424 auto add_unique_path = [&](
const std::filesystem::path& path) {
428 const std::string path_str = path.string();
429 auto it = std::find(prefs->ai_model_paths.begin(),
430 prefs->ai_model_paths.end(), path_str);
431 if (it == prefs->ai_model_paths.end()) {
432 prefs->ai_model_paths.push_back(path_str);
437 if (!home_dir.empty() && home_dir !=
".") {
438 add_unique_path(home_dir /
"models");
439 add_unique_path(home_dir /
".lmstudio" /
"models");
440 add_unique_path(home_dir /
".ollama" /
"models");
444void LoadStringMap(
const json& src,
445 std::unordered_map<std::string, std::string>* target) {
446 if (!target || !src.is_object()) {
450 for (
const auto& [key, value] : src.items()) {
451 if (value.is_string()) {
452 (*target)[
key] = value.get<std::string>();
457void LoadBoolMap(
const json& src,
458 std::unordered_map<std::string, bool>* target) {
459 if (!target || !src.is_object()) {
463 for (
const auto& [key, value] : src.items()) {
464 if (value.is_boolean()) {
465 (*target)[
key] = value.get<
bool>();
470void LoadFloatMap(
const json& src,
471 std::unordered_map<std::string, float>* target) {
472 if (!target || !src.is_object()) {
476 for (
const auto& [key, value] : src.items()) {
477 if (value.is_number()) {
478 (*target)[
key] = value.get<
float>();
483void LoadNestedBoolMap(
485 std::unordered_map<std::string, std::unordered_map<std::string, bool>>*
487 if (!target || !src.is_object()) {
491 for (
const auto& [outer_key, outer_val] : src.items()) {
492 if (!outer_val.is_object()) {
495 auto& inner = (*target)[outer_key];
497 for (
const auto& [inner_key, inner_val] : outer_val.items()) {
498 if (inner_val.is_boolean()) {
499 inner[inner_key] = inner_val.get<
bool>();
505json ToStringMap(
const std::unordered_map<std::string, std::string>& map) {
506 json obj = json::object();
507 for (
const auto& [key, value] : map) {
513json ToBoolMap(
const std::unordered_map<std::string, bool>& map) {
514 json obj = json::object();
515 for (
const auto& [key, value] : map) {
521json ToFloatMap(
const std::unordered_map<std::string, float>& map) {
522 json obj = json::object();
523 for (
const auto& [key, value] : map) {
529json ToNestedBoolMap(
const std::unordered_map<
530 std::string, std::unordered_map<std::string, bool>>& map) {
531 json obj = json::object();
532 for (
const auto& [outer_key, inner] : map) {
533 obj[outer_key] = ToBoolMap(inner);
538absl::Status LoadPreferencesFromJson(
const std::filesystem::path& path,
539 UserSettings::Preferences* prefs) {
541 return absl::InvalidArgumentError(
"prefs is null");
544 std::ifstream file(path);
545 if (!file.is_open()) {
546 return absl::NotFoundError(
547 absl::StrFormat(
"Settings file not found: %s", path.string()));
553 }
catch (
const std::exception& e) {
554 return absl::InternalError(
555 absl::StrFormat(
"Failed to parse settings.json: %s", e.what()));
558 if (root.contains(
"general")) {
559 const auto& g = root[
"general"];
560 prefs->font_global_scale =
561 g.value(
"font_global_scale", prefs->font_global_scale);
562 prefs->backup_rom = g.value(
"backup_rom", prefs->backup_rom);
563 prefs->save_new_auto = g.value(
"save_new_auto", prefs->save_new_auto);
564 prefs->autosave_enabled =
565 g.value(
"autosave_enabled", prefs->autosave_enabled);
566 prefs->autosave_interval =
567 g.value(
"autosave_interval", prefs->autosave_interval);
568 prefs->recent_files_limit =
569 g.value(
"recent_files_limit", prefs->recent_files_limit);
570 prefs->last_rom_path = g.value(
"last_rom_path", prefs->last_rom_path);
571 prefs->last_project_path =
572 g.value(
"last_project_path", prefs->last_project_path);
573 prefs->show_welcome_on_startup =
574 g.value(
"show_welcome_on_startup", prefs->show_welcome_on_startup);
575 prefs->restore_last_session =
576 g.value(
"restore_last_session", prefs->restore_last_session);
577 prefs->prefer_hmagic_sprite_names = g.value(
578 "prefer_hmagic_sprite_names", prefs->prefer_hmagic_sprite_names);
581 if (root.contains(
"appearance")) {
582 const auto& appearance = root[
"appearance"];
583 prefs->reduced_motion =
584 appearance.value(
"reduced_motion", prefs->reduced_motion);
585 prefs->switch_motion_profile = appearance.value(
586 "switch_motion_profile", prefs->switch_motion_profile);
589 if (root.contains(
"editor")) {
590 const auto& e = root[
"editor"];
591 prefs->backup_before_save =
592 e.value(
"backup_before_save", prefs->backup_before_save);
593 prefs->default_editor = e.value(
"default_editor", prefs->default_editor);
596 if (root.contains(
"performance")) {
597 const auto& p = root[
"performance"];
598 prefs->vsync = p.value(
"vsync", prefs->vsync);
599 prefs->target_fps = p.value(
"target_fps", prefs->target_fps);
600 prefs->cache_size_mb = p.value(
"cache_size_mb", prefs->cache_size_mb);
601 prefs->undo_history_size =
602 p.value(
"undo_history_size", prefs->undo_history_size);
605 if (root.contains(
"ai")) {
606 const auto& ai = root[
"ai"];
607 prefs->ai_provider = ai.value(
"provider", prefs->ai_provider);
608 prefs->ai_model = ai.value(
"model", prefs->ai_model);
609 prefs->ollama_url = ai.value(
"ollama_url", prefs->ollama_url);
610 prefs->gemini_api_key = ai.value(
"gemini_api_key", prefs->gemini_api_key);
611 prefs->openai_api_key = ai.value(
"openai_api_key", prefs->openai_api_key);
612 prefs->anthropic_api_key =
613 ai.value(
"anthropic_api_key", prefs->anthropic_api_key);
614 std::string google_key = ai.value(
"google_api_key", std::string());
615 if (prefs->gemini_api_key.empty() && !google_key.empty()) {
616 prefs->gemini_api_key = google_key;
618 prefs->ai_temperature = ai.value(
"temperature", prefs->ai_temperature);
619 prefs->ai_max_tokens = ai.value(
"max_tokens", prefs->ai_max_tokens);
620 prefs->ai_proactive = ai.value(
"proactive", prefs->ai_proactive);
621 prefs->ai_auto_learn = ai.value(
"auto_learn", prefs->ai_auto_learn);
622 prefs->ai_multimodal = ai.value(
"multimodal", prefs->ai_multimodal);
623 prefs->active_ai_host_id =
624 ai.value(
"active_host_id", prefs->active_ai_host_id);
625 prefs->active_ai_profile =
626 ai.value(
"active_profile", prefs->active_ai_profile);
627 prefs->remote_build_host_id =
628 ai.value(
"remote_build_host_id", prefs->remote_build_host_id);
629 if (ai.contains(
"model_paths") && ai[
"model_paths"].is_array()) {
630 prefs->ai_model_paths.clear();
631 for (
const auto& item : ai[
"model_paths"]) {
632 if (item.is_string()) {
633 prefs->ai_model_paths.push_back(item.get<std::string>());
638 if (ai.contains(
"hosts") && ai[
"hosts"].is_array()) {
639 prefs->ai_hosts.clear();
640 for (
const auto& host : ai[
"hosts"]) {
641 if (!host.is_object()) {
644 UserSettings::Preferences::AiHost entry;
645 entry.id = host.value(
"id",
"");
646 entry.label = host.value(
"label",
"");
647 entry.base_url = host.value(
"base_url",
"");
648 entry.api_type = host.value(
"api_type",
"");
649 entry.supports_vision =
650 host.value(
"supports_vision", entry.supports_vision);
651 entry.supports_tools =
652 host.value(
"supports_tools", entry.supports_tools);
653 entry.supports_streaming =
654 host.value(
"supports_streaming", entry.supports_streaming);
655 entry.allow_insecure =
656 host.value(
"allow_insecure", entry.allow_insecure);
657 entry.api_key = host.value(
"api_key",
"");
658 entry.credential_id = host.value(
"credential_id",
"");
659 prefs->ai_hosts.push_back(entry);
663 if (ai.contains(
"profiles") && ai[
"profiles"].is_array()) {
664 prefs->ai_profiles.clear();
665 for (
const auto& profile : ai[
"profiles"]) {
666 if (!profile.is_object()) {
669 UserSettings::Preferences::AiModelProfile entry;
670 entry.name = profile.value(
"name",
"");
671 entry.model = profile.value(
"model",
"");
672 entry.temperature = profile.value(
"temperature", entry.temperature);
673 entry.top_p = profile.value(
"top_p", entry.top_p);
674 entry.max_output_tokens =
675 profile.value(
"max_output_tokens", entry.max_output_tokens);
676 entry.supports_vision =
677 profile.value(
"supports_vision", entry.supports_vision);
678 entry.supports_tools =
679 profile.value(
"supports_tools", entry.supports_tools);
680 prefs->ai_profiles.push_back(entry);
685 if (root.contains(
"logging")) {
686 const auto& log = root[
"logging"];
687 prefs->log_level = log.value(
"level", prefs->log_level);
688 prefs->log_to_file = log.value(
"to_file", prefs->log_to_file);
689 prefs->log_file_path = log.value(
"file_path", prefs->log_file_path);
690 prefs->log_ai_requests = log.value(
"ai_requests", prefs->log_ai_requests);
691 prefs->log_rom_operations =
692 log.value(
"rom_operations", prefs->log_rom_operations);
693 prefs->log_gui_automation =
694 log.value(
"gui_automation", prefs->log_gui_automation);
695 prefs->log_proposals = log.value(
"proposals", prefs->log_proposals);
698 if (root.contains(
"shortcuts")) {
699 const auto& shortcuts = root[
"shortcuts"];
700 if (shortcuts.contains(
"panel")) {
701 LoadStringMap(shortcuts[
"panel"], &prefs->panel_shortcuts);
703 if (shortcuts.contains(
"global")) {
704 LoadStringMap(shortcuts[
"global"], &prefs->global_shortcuts);
706 if (shortcuts.contains(
"editor")) {
707 LoadStringMap(shortcuts[
"editor"], &prefs->editor_shortcuts);
711 if (root.contains(
"sidebar")) {
712 const auto& sidebar = root[
"sidebar"];
713 prefs->sidebar_visible = sidebar.value(
"visible", prefs->sidebar_visible);
714 prefs->sidebar_panel_expanded =
715 sidebar.value(
"panel_expanded", prefs->sidebar_panel_expanded);
716 prefs->sidebar_panel_width =
717 sidebar.value(
"panel_width", prefs->sidebar_panel_width);
718 prefs->panel_browser_category_width = sidebar.value(
719 "panel_browser_category_width", prefs->panel_browser_category_width);
720 prefs->sidebar_active_category =
721 sidebar.value(
"active_category", prefs->sidebar_active_category);
724 if (root.contains(
"status_bar")) {
725 const auto& status_bar = root[
"status_bar"];
726 prefs->show_status_bar =
727 status_bar.value(
"visible", prefs->show_status_bar);
730 if (root.contains(
"layouts")) {
731 const auto& layouts = root[
"layouts"];
732 prefs->panel_layout_defaults_revision =
733 layouts.value(
"defaults_revision", prefs->panel_layout_defaults_revision);
734 if (layouts.contains(
"panel_visibility")) {
735 LoadNestedBoolMap(layouts[
"panel_visibility"],
736 &prefs->panel_visibility_state);
738 if (layouts.contains(
"pinned_panels")) {
739 LoadBoolMap(layouts[
"pinned_panels"], &prefs->pinned_panels);
741 if (layouts.contains(
"right_panel_widths")) {
742 LoadFloatMap(layouts[
"right_panel_widths"], &prefs->right_panel_widths);
744 if (layouts.contains(
"saved_layouts")) {
745 LoadNestedBoolMap(layouts[
"saved_layouts"], &prefs->saved_layouts);
749 if (root.contains(
"filesystem")) {
750 const auto& fs = root[
"filesystem"];
751 if (fs.contains(
"project_root_paths") &&
752 fs[
"project_root_paths"].is_array()) {
753 prefs->project_root_paths.clear();
754 for (
const auto& item : fs[
"project_root_paths"]) {
755 if (item.is_string()) {
756 prefs->project_root_paths.push_back(item.get<std::string>());
760 prefs->default_project_root =
761 fs.value(
"default_project_root", prefs->default_project_root);
762 prefs->use_files_app = fs.value(
"use_files_app", prefs->use_files_app);
763 prefs->use_icloud_sync =
764 fs.value(
"use_icloud_sync", prefs->use_icloud_sync);
767 EnsureDefaultAiHosts(prefs);
768 EnsureDefaultAiProfiles(prefs);
769 EnsureDefaultFilesystemRoots(prefs);
771 return absl::OkStatus();
774absl::Status SavePreferencesToJson(
const std::filesystem::path& path,
775 const UserSettings::Preferences& prefs) {
777 if (!ensure_status.ok()) {
778 return ensure_status;
784 {
"font_global_scale", prefs.font_global_scale},
785 {
"backup_rom", prefs.backup_rom},
786 {
"save_new_auto", prefs.save_new_auto},
787 {
"autosave_enabled", prefs.autosave_enabled},
788 {
"autosave_interval", prefs.autosave_interval},
789 {
"recent_files_limit", prefs.recent_files_limit},
790 {
"last_rom_path", prefs.last_rom_path},
791 {
"last_project_path", prefs.last_project_path},
792 {
"show_welcome_on_startup", prefs.show_welcome_on_startup},
793 {
"restore_last_session", prefs.restore_last_session},
794 {
"prefer_hmagic_sprite_names", prefs.prefer_hmagic_sprite_names},
797 root[
"appearance"] = {
798 {
"reduced_motion", prefs.reduced_motion},
799 {
"switch_motion_profile", prefs.switch_motion_profile},
803 {
"backup_before_save", prefs.backup_before_save},
804 {
"default_editor", prefs.default_editor},
807 root[
"performance"] = {
808 {
"vsync", prefs.vsync},
809 {
"target_fps", prefs.target_fps},
810 {
"cache_size_mb", prefs.cache_size_mb},
811 {
"undo_history_size", prefs.undo_history_size},
814 json ai_hosts = json::array();
815 for (
const auto& host : prefs.ai_hosts) {
818 {
"label", host.label},
819 {
"base_url", host.base_url},
820 {
"api_type", host.api_type},
821 {
"supports_vision", host.supports_vision},
822 {
"supports_tools", host.supports_tools},
823 {
"supports_streaming", host.supports_streaming},
824 {
"allow_insecure", host.allow_insecure},
825 {
"api_key", host.api_key},
826 {
"credential_id", host.credential_id},
830 json ai_profiles = json::array();
831 for (
const auto& profile : prefs.ai_profiles) {
832 ai_profiles.push_back({
833 {
"name", profile.name},
834 {
"model", profile.model},
835 {
"temperature", profile.temperature},
836 {
"top_p", profile.top_p},
837 {
"max_output_tokens", profile.max_output_tokens},
838 {
"supports_vision", profile.supports_vision},
839 {
"supports_tools", profile.supports_tools},
844 {
"provider", prefs.ai_provider},
845 {
"model", prefs.ai_model},
846 {
"ollama_url", prefs.ollama_url},
847 {
"gemini_api_key", prefs.gemini_api_key},
848 {
"google_api_key", prefs.gemini_api_key},
849 {
"openai_api_key", prefs.openai_api_key},
850 {
"anthropic_api_key", prefs.anthropic_api_key},
851 {
"temperature", prefs.ai_temperature},
852 {
"max_tokens", prefs.ai_max_tokens},
853 {
"proactive", prefs.ai_proactive},
854 {
"auto_learn", prefs.ai_auto_learn},
855 {
"multimodal", prefs.ai_multimodal},
857 {
"active_host_id", prefs.active_ai_host_id},
858 {
"profiles", ai_profiles},
859 {
"active_profile", prefs.active_ai_profile},
860 {
"remote_build_host_id", prefs.remote_build_host_id},
861 {
"model_paths", prefs.ai_model_paths},
865 {
"level", prefs.log_level},
866 {
"to_file", prefs.log_to_file},
867 {
"file_path", prefs.log_file_path},
868 {
"ai_requests", prefs.log_ai_requests},
869 {
"rom_operations", prefs.log_rom_operations},
870 {
"gui_automation", prefs.log_gui_automation},
871 {
"proposals", prefs.log_proposals},
874 root[
"shortcuts"] = {
875 {
"panel", ToStringMap(prefs.panel_shortcuts)},
876 {
"global", ToStringMap(prefs.global_shortcuts)},
877 {
"editor", ToStringMap(prefs.editor_shortcuts)},
881 {
"visible", prefs.sidebar_visible},
882 {
"panel_expanded", prefs.sidebar_panel_expanded},
883 {
"panel_width", prefs.sidebar_panel_width},
884 {
"panel_browser_category_width", prefs.panel_browser_category_width},
885 {
"active_category", prefs.sidebar_active_category},
888 root[
"status_bar"] = {
889 {
"visible", prefs.show_status_bar},
893 {
"defaults_revision", prefs.panel_layout_defaults_revision},
894 {
"panel_visibility", ToNestedBoolMap(prefs.panel_visibility_state)},
895 {
"pinned_panels", ToBoolMap(prefs.pinned_panels)},
896 {
"right_panel_widths", ToFloatMap(prefs.right_panel_widths)},
897 {
"saved_layouts", ToNestedBoolMap(prefs.saved_layouts)},
900 root[
"filesystem"] = {
901 {
"project_root_paths", prefs.project_root_paths},
902 {
"default_project_root", prefs.default_project_root},
903 {
"use_files_app", prefs.use_files_app},
904 {
"use_icloud_sync", prefs.use_icloud_sync},
907 std::ofstream file(path);
908 if (!file.is_open()) {
909 return absl::InternalError(
910 absl::StrFormat(
"Failed to open settings file: %s", path.string()));
913 file << root.dump(2) <<
"\n";
914 return absl::OkStatus();