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 ==
"welcome_triforce_alpha") {
83 }
else if (key ==
"welcome_triforce_speed") {
85 }
else if (key ==
"welcome_triforce_size") {
87 }
else if (key ==
"welcome_particles_enabled") {
89 }
else if (key ==
"welcome_mouse_repel_enabled") {
91 }
else if (key ==
"reduced_motion") {
93 }
else if (key ==
"switch_motion_profile") {
95 }
else if (key ==
"last_theme_name") {
99 else if (key ==
"backup_before_save") {
101 }
else if (key ==
"default_editor") {
105 else if (key ==
"vsync") {
106 prefs->
vsync = (val ==
"1");
107 }
else if (key ==
"target_fps") {
109 }
else if (key ==
"cache_size_mb") {
111 }
else if (key ==
"undo_history_size") {
115 else if (key ==
"ai_provider") {
117 }
else if (key ==
"ai_model") {
119 }
else if (key ==
"ollama_url") {
121 }
else if (key ==
"gemini_api_key") {
123 }
else if (key ==
"openai_api_key") {
125 }
else if (key ==
"anthropic_api_key") {
127 }
else if (key ==
"ai_temperature") {
129 }
else if (key ==
"ai_max_tokens") {
131 }
else if (key ==
"ai_proactive") {
133 }
else if (key ==
"ai_auto_learn") {
135 }
else if (key ==
"ai_multimodal") {
139 else if (key ==
"log_level") {
141 }
else if (key ==
"log_to_file") {
143 }
else if (key ==
"log_file_path") {
145 }
else if (key ==
"log_ai_requests") {
147 }
else if (key ==
"log_rom_operations") {
149 }
else if (key ==
"log_gui_automation") {
151 }
else if (key ==
"log_proposals") {
155 else if (key.substr(0, 15) ==
"panel_shortcut.") {
156 std::string panel_id = key.substr(15);
160 else if (key.substr(0, 14) ==
"card_shortcut.") {
161 std::string panel_id = key.substr(14);
165 else if (key ==
"sidebar_visible") {
167 }
else if (key ==
"sidebar_panel_expanded") {
169 }
else if (key ==
"sidebar_panel_width") {
171 }
else if (key ==
"panel_browser_category_width") {
173 }
else if (key ==
"panel_layout_defaults_revision") {
175 }
else if (key ==
"sidebar_active_category") {
179 else if (key ==
"show_status_bar") {
183 else if (key.substr(0, 17) ==
"panel_visibility.") {
184 std::string rest = key.substr(17);
185 size_t dot_pos = rest.find(
'.');
186 if (dot_pos != std::string::npos) {
187 std::string editor_type = rest.substr(0, dot_pos);
188 std::string panel_id = rest.substr(dot_pos + 1);
193 else if (key.substr(0, 13) ==
"pinned_panel.") {
194 std::string panel_id = key.substr(13);
198 else if (key.substr(0, 18) ==
"right_panel_width.") {
199 std::string panel_key = key.substr(18);
203 else if (key.substr(0, 13) ==
"saved_layout.") {
204 std::string rest = key.substr(13);
205 size_t dot_pos = rest.find(
'.');
206 if (dot_pos != std::string::npos) {
207 std::string layout_name = rest.substr(0, dot_pos);
208 std::string panel_id = rest.substr(dot_pos + 1);
214 return absl::OkStatus();
220 if (!ensure_status.ok()) {
221 return ensure_status;
224 std::ostringstream ss;
227 ss <<
"backup_rom=" << (prefs.
backup_rom ? 1 : 0) <<
"\n";
228 ss <<
"save_new_auto=" << (prefs.
save_new_auto ? 1 : 0) <<
"\n";
237 ss <<
"prefer_hmagic_sprite_names="
242 ss <<
"welcome_particles_enabled="
244 ss <<
"welcome_mouse_repel_enabled="
246 ss <<
"reduced_motion=" << (prefs.
reduced_motion ? 1 : 0) <<
"\n";
255 ss <<
"vsync=" << (prefs.
vsync ? 1 : 0) <<
"\n";
256 ss <<
"target_fps=" << prefs.
target_fps <<
"\n";
262 ss <<
"ai_model=" << prefs.
ai_model <<
"\n";
263 ss <<
"ollama_url=" << prefs.
ollama_url <<
"\n";
269 ss <<
"ai_proactive=" << (prefs.
ai_proactive ? 1 : 0) <<
"\n";
270 ss <<
"ai_auto_learn=" << (prefs.
ai_auto_learn ? 1 : 0) <<
"\n";
271 ss <<
"ai_multimodal=" << (prefs.
ai_multimodal ? 1 : 0) <<
"\n";
274 ss <<
"log_level=" << prefs.
log_level <<
"\n";
275 ss <<
"log_to_file=" << (prefs.
log_to_file ? 1 : 0) <<
"\n";
280 ss <<
"log_proposals=" << (prefs.
log_proposals ? 1 : 0) <<
"\n";
284 ss <<
"panel_shortcut." << panel_id <<
"=" << shortcut <<
"\n";
294 ss <<
"panel_layout_defaults_revision="
303 for (
const auto& [panel_id, visible] : panel_state) {
304 ss <<
"panel_visibility." << editor_type <<
"." << panel_id <<
"="
305 << (visible ? 1 : 0) <<
"\n";
311 ss <<
"pinned_panel." << panel_id <<
"=" << (pinned ? 1 : 0) <<
"\n";
315 ss <<
"right_panel_width." << panel_key <<
"=" << width <<
"\n";
319 for (
const auto& [layout_name, panel_state] : prefs.
saved_layouts) {
320 for (
const auto& [panel_id, visible] : panel_state) {
321 ss <<
"saved_layout." << layout_name <<
"." << panel_id <<
"="
322 << (visible ? 1 : 0) <<
"\n";
326 std::ofstream file(path);
327 if (!file.is_open()) {
328 return absl::InternalError(
329 absl::StrFormat(
"Failed to open settings file: %s", path.string()));
332 return absl::OkStatus();
349 UserSettings::Preferences::AiHost host;
350 host.id =
"ollama-local";
351 host.label =
"Ollama (local)";
353 host.api_type =
"ollama";
354 host.supports_tools =
true;
355 host.supports_streaming =
true;
360 UserSettings::Preferences::AiHost lmstudio;
361 lmstudio.id =
"lmstudio-local";
362 lmstudio.label =
"LM Studio (local)";
363 lmstudio.base_url =
"http://localhost:1234";
364 lmstudio.api_type =
"lmstudio";
365 lmstudio.supports_tools =
true;
366 lmstudio.supports_streaming =
true;
367 prefs->
ai_hosts.push_back(lmstudio);
374void EnsureDefaultAiProfiles(UserSettings::Preferences* prefs) {
378 if (!prefs->ai_profiles.empty()) {
379 if (prefs->active_ai_profile.empty()) {
380 prefs->active_ai_profile = prefs->ai_profiles.front().name;
384 if (!prefs->ai_model.empty()) {
385 UserSettings::Preferences::AiModelProfile profile;
386 profile.name =
"default";
387 profile.model = prefs->ai_model;
388 profile.temperature = prefs->ai_temperature;
389 profile.top_p = 0.95f;
390 profile.max_output_tokens = prefs->ai_max_tokens;
391 profile.supports_tools =
true;
392 prefs->ai_profiles.push_back(profile);
393 prefs->active_ai_profile = profile.name;
397void EnsureDefaultFilesystemRoots(UserSettings::Preferences* prefs) {
402 auto add_unique_root = [&](
const std::filesystem::path& path) {
406 const std::string path_str = path.string();
407 auto it = std::find(prefs->project_root_paths.begin(),
408 prefs->project_root_paths.end(), path_str);
409 if (it == prefs->project_root_paths.end()) {
410 prefs->project_root_paths.push_back(path_str);
416 add_unique_root(*docs_dir);
419 if (prefs->use_icloud_sync) {
422 if (icloud_dir.ok()) {
423 add_unique_root(*icloud_dir);
424 if (prefs->default_project_root.empty()) {
425 prefs->default_project_root = icloud_dir->string();
430 if (prefs->default_project_root.empty() &&
431 !prefs->project_root_paths.empty()) {
432 prefs->default_project_root = prefs->project_root_paths.front();
436void EnsureDefaultModelPaths(UserSettings::Preferences* prefs) {
440 if (!prefs->ai_model_paths.empty()) {
444 auto add_unique_path = [&](
const std::filesystem::path& path) {
448 const std::string path_str = path.string();
449 auto it = std::find(prefs->ai_model_paths.begin(),
450 prefs->ai_model_paths.end(), path_str);
451 if (it == prefs->ai_model_paths.end()) {
452 prefs->ai_model_paths.push_back(path_str);
457 if (!home_dir.empty() && home_dir !=
".") {
458 add_unique_path(home_dir /
"models");
459 add_unique_path(home_dir /
".lmstudio" /
"models");
460 add_unique_path(home_dir /
".ollama" /
"models");
464void LoadStringMap(
const json& src,
465 std::unordered_map<std::string, std::string>* target) {
466 if (!target || !src.is_object()) {
470 for (
const auto& [key, value] : src.items()) {
471 if (value.is_string()) {
472 (*target)[
key] = value.get<std::string>();
477void LoadBoolMap(
const json& src,
478 std::unordered_map<std::string, bool>* target) {
479 if (!target || !src.is_object()) {
483 for (
const auto& [key, value] : src.items()) {
484 if (value.is_boolean()) {
485 (*target)[
key] = value.get<
bool>();
490void LoadFloatMap(
const json& src,
491 std::unordered_map<std::string, float>* target) {
492 if (!target || !src.is_object()) {
496 for (
const auto& [key, value] : src.items()) {
497 if (value.is_number()) {
498 (*target)[
key] = value.get<
float>();
503void LoadNestedBoolMap(
505 std::unordered_map<std::string, std::unordered_map<std::string, bool>>*
507 if (!target || !src.is_object()) {
511 for (
const auto& [outer_key, outer_val] : src.items()) {
512 if (!outer_val.is_object()) {
515 auto& inner = (*target)[outer_key];
517 for (
const auto& [inner_key, inner_val] : outer_val.items()) {
518 if (inner_val.is_boolean()) {
519 inner[inner_key] = inner_val.get<
bool>();
525json ToStringMap(
const std::unordered_map<std::string, std::string>& map) {
526 json obj = json::object();
527 for (
const auto& [key, value] : map) {
533json ToBoolMap(
const std::unordered_map<std::string, bool>& map) {
534 json obj = json::object();
535 for (
const auto& [key, value] : map) {
541json ToFloatMap(
const std::unordered_map<std::string, float>& map) {
542 json obj = json::object();
543 for (
const auto& [key, value] : map) {
549json ToNestedBoolMap(
const std::unordered_map<
550 std::string, std::unordered_map<std::string, bool>>& map) {
551 json obj = json::object();
552 for (
const auto& [outer_key, inner] : map) {
553 obj[outer_key] = ToBoolMap(inner);
558absl::Status LoadPreferencesFromJson(
const std::filesystem::path& path,
559 UserSettings::Preferences* prefs) {
561 return absl::InvalidArgumentError(
"prefs is null");
564 std::ifstream file(path);
565 if (!file.is_open()) {
566 return absl::NotFoundError(
567 absl::StrFormat(
"Settings file not found: %s", path.string()));
573 }
catch (
const std::exception& e) {
574 return absl::InternalError(
575 absl::StrFormat(
"Failed to parse settings.json: %s", e.what()));
578 if (root.contains(
"general")) {
579 const auto& g = root[
"general"];
580 prefs->font_global_scale =
581 g.value(
"font_global_scale", prefs->font_global_scale);
582 prefs->backup_rom = g.value(
"backup_rom", prefs->backup_rom);
583 prefs->save_new_auto = g.value(
"save_new_auto", prefs->save_new_auto);
584 prefs->autosave_enabled =
585 g.value(
"autosave_enabled", prefs->autosave_enabled);
586 prefs->autosave_interval =
587 g.value(
"autosave_interval", prefs->autosave_interval);
588 prefs->recent_files_limit =
589 g.value(
"recent_files_limit", prefs->recent_files_limit);
590 prefs->last_rom_path = g.value(
"last_rom_path", prefs->last_rom_path);
591 prefs->last_project_path =
592 g.value(
"last_project_path", prefs->last_project_path);
593 prefs->show_welcome_on_startup =
594 g.value(
"show_welcome_on_startup", prefs->show_welcome_on_startup);
595 prefs->restore_last_session =
596 g.value(
"restore_last_session", prefs->restore_last_session);
597 prefs->prefer_hmagic_sprite_names = g.value(
598 "prefer_hmagic_sprite_names", prefs->prefer_hmagic_sprite_names);
599 prefs->welcome_triforce_alpha =
600 g.value(
"welcome_triforce_alpha", prefs->welcome_triforce_alpha);
601 prefs->welcome_triforce_speed =
602 g.value(
"welcome_triforce_speed", prefs->welcome_triforce_speed);
603 prefs->welcome_triforce_size =
604 g.value(
"welcome_triforce_size", prefs->welcome_triforce_size);
605 prefs->welcome_particles_enabled =
606 g.value(
"welcome_particles_enabled", prefs->welcome_particles_enabled);
607 prefs->welcome_mouse_repel_enabled = g.value(
608 "welcome_mouse_repel_enabled", prefs->welcome_mouse_repel_enabled);
611 if (root.contains(
"appearance")) {
612 const auto& appearance = root[
"appearance"];
613 prefs->reduced_motion =
614 appearance.value(
"reduced_motion", prefs->reduced_motion);
615 prefs->switch_motion_profile =
616 appearance.value(
"switch_motion_profile", prefs->switch_motion_profile);
617 prefs->last_theme_name =
618 appearance.value(
"last_theme_name", prefs->last_theme_name);
621 if (root.contains(
"editor")) {
622 const auto& e = root[
"editor"];
623 prefs->backup_before_save =
624 e.value(
"backup_before_save", prefs->backup_before_save);
625 prefs->default_editor = e.value(
"default_editor", prefs->default_editor);
628 if (root.contains(
"performance")) {
629 const auto& p = root[
"performance"];
630 prefs->vsync = p.value(
"vsync", prefs->vsync);
631 prefs->target_fps = p.value(
"target_fps", prefs->target_fps);
632 prefs->cache_size_mb = p.value(
"cache_size_mb", prefs->cache_size_mb);
633 prefs->undo_history_size =
634 p.value(
"undo_history_size", prefs->undo_history_size);
637 if (root.contains(
"ai")) {
638 const auto& ai = root[
"ai"];
639 prefs->ai_provider = ai.value(
"provider", prefs->ai_provider);
640 prefs->ai_model = ai.value(
"model", prefs->ai_model);
641 prefs->ollama_url = ai.value(
"ollama_url", prefs->ollama_url);
642 prefs->gemini_api_key = ai.value(
"gemini_api_key", prefs->gemini_api_key);
643 prefs->openai_api_key = ai.value(
"openai_api_key", prefs->openai_api_key);
644 prefs->anthropic_api_key =
645 ai.value(
"anthropic_api_key", prefs->anthropic_api_key);
646 std::string google_key = ai.value(
"google_api_key", std::string());
647 if (prefs->gemini_api_key.empty() && !google_key.empty()) {
648 prefs->gemini_api_key = google_key;
650 prefs->ai_temperature = ai.value(
"temperature", prefs->ai_temperature);
651 prefs->ai_max_tokens = ai.value(
"max_tokens", prefs->ai_max_tokens);
652 prefs->ai_proactive = ai.value(
"proactive", prefs->ai_proactive);
653 prefs->ai_auto_learn = ai.value(
"auto_learn", prefs->ai_auto_learn);
654 prefs->ai_multimodal = ai.value(
"multimodal", prefs->ai_multimodal);
655 prefs->active_ai_host_id =
656 ai.value(
"active_host_id", prefs->active_ai_host_id);
657 prefs->active_ai_profile =
658 ai.value(
"active_profile", prefs->active_ai_profile);
659 prefs->remote_build_host_id =
660 ai.value(
"remote_build_host_id", prefs->remote_build_host_id);
661 if (ai.contains(
"model_paths") && ai[
"model_paths"].is_array()) {
662 prefs->ai_model_paths.clear();
663 for (
const auto& item : ai[
"model_paths"]) {
664 if (item.is_string()) {
665 prefs->ai_model_paths.push_back(item.get<std::string>());
670 if (ai.contains(
"hosts") && ai[
"hosts"].is_array()) {
671 prefs->ai_hosts.clear();
672 for (
const auto& host : ai[
"hosts"]) {
673 if (!host.is_object()) {
676 UserSettings::Preferences::AiHost entry;
677 entry.id = host.value(
"id",
"");
678 entry.label = host.value(
"label",
"");
679 entry.base_url = host.value(
"base_url",
"");
680 entry.api_type = host.value(
"api_type",
"");
681 entry.supports_vision =
682 host.value(
"supports_vision", entry.supports_vision);
683 entry.supports_tools =
684 host.value(
"supports_tools", entry.supports_tools);
685 entry.supports_streaming =
686 host.value(
"supports_streaming", entry.supports_streaming);
687 entry.allow_insecure =
688 host.value(
"allow_insecure", entry.allow_insecure);
689 entry.api_key = host.value(
"api_key",
"");
690 entry.credential_id = host.value(
"credential_id",
"");
691 prefs->ai_hosts.push_back(entry);
695 if (ai.contains(
"profiles") && ai[
"profiles"].is_array()) {
696 prefs->ai_profiles.clear();
697 for (
const auto& profile : ai[
"profiles"]) {
698 if (!profile.is_object()) {
701 UserSettings::Preferences::AiModelProfile entry;
702 entry.name = profile.value(
"name",
"");
703 entry.model = profile.value(
"model",
"");
704 entry.temperature = profile.value(
"temperature", entry.temperature);
705 entry.top_p = profile.value(
"top_p", entry.top_p);
706 entry.max_output_tokens =
707 profile.value(
"max_output_tokens", entry.max_output_tokens);
708 entry.supports_vision =
709 profile.value(
"supports_vision", entry.supports_vision);
710 entry.supports_tools =
711 profile.value(
"supports_tools", entry.supports_tools);
712 prefs->ai_profiles.push_back(entry);
717 if (root.contains(
"logging")) {
718 const auto& log = root[
"logging"];
719 prefs->log_level = log.value(
"level", prefs->log_level);
720 prefs->log_to_file = log.value(
"to_file", prefs->log_to_file);
721 prefs->log_file_path = log.value(
"file_path", prefs->log_file_path);
722 prefs->log_ai_requests = log.value(
"ai_requests", prefs->log_ai_requests);
723 prefs->log_rom_operations =
724 log.value(
"rom_operations", prefs->log_rom_operations);
725 prefs->log_gui_automation =
726 log.value(
"gui_automation", prefs->log_gui_automation);
727 prefs->log_proposals = log.value(
"proposals", prefs->log_proposals);
730 if (root.contains(
"shortcuts")) {
731 const auto& shortcuts = root[
"shortcuts"];
732 if (shortcuts.contains(
"panel")) {
733 LoadStringMap(shortcuts[
"panel"], &prefs->panel_shortcuts);
735 if (shortcuts.contains(
"global")) {
736 LoadStringMap(shortcuts[
"global"], &prefs->global_shortcuts);
738 if (shortcuts.contains(
"editor")) {
739 LoadStringMap(shortcuts[
"editor"], &prefs->editor_shortcuts);
743 if (root.contains(
"sidebar")) {
744 const auto& sidebar = root[
"sidebar"];
745 prefs->sidebar_visible = sidebar.value(
"visible", prefs->sidebar_visible);
746 prefs->sidebar_panel_expanded =
747 sidebar.value(
"panel_expanded", prefs->sidebar_panel_expanded);
748 prefs->sidebar_panel_width =
749 sidebar.value(
"panel_width", prefs->sidebar_panel_width);
750 prefs->panel_browser_category_width = sidebar.value(
751 "panel_browser_category_width", prefs->panel_browser_category_width);
752 prefs->sidebar_active_category =
753 sidebar.value(
"active_category", prefs->sidebar_active_category);
755 if (sidebar.contains(
"order") && sidebar[
"order"].is_array()) {
756 prefs->sidebar_order.clear();
757 for (
const auto& item : sidebar[
"order"]) {
758 if (item.is_string()) {
759 prefs->sidebar_order.push_back(item.get<std::string>());
763 if (sidebar.contains(
"hidden") && sidebar[
"hidden"].is_array()) {
764 prefs->sidebar_hidden.clear();
765 for (
const auto& item : sidebar[
"hidden"]) {
766 if (item.is_string()) {
767 prefs->sidebar_hidden.insert(item.get<std::string>());
771 if (sidebar.contains(
"pinned") && sidebar[
"pinned"].is_array()) {
772 prefs->sidebar_pinned.clear();
773 for (
const auto& item : sidebar[
"pinned"]) {
774 if (item.is_string()) {
775 prefs->sidebar_pinned.insert(item.get<std::string>());
781 if (root.contains(
"status_bar")) {
782 const auto& status_bar = root[
"status_bar"];
783 prefs->show_status_bar =
784 status_bar.value(
"visible", prefs->show_status_bar);
787 if (root.contains(
"layouts")) {
788 const auto& layouts = root[
"layouts"];
789 prefs->panel_layout_defaults_revision = layouts.value(
790 "defaults_revision", prefs->panel_layout_defaults_revision);
791 if (layouts.contains(
"panel_visibility")) {
792 LoadNestedBoolMap(layouts[
"panel_visibility"],
793 &prefs->panel_visibility_state);
795 if (layouts.contains(
"pinned_panels")) {
796 LoadBoolMap(layouts[
"pinned_panels"], &prefs->pinned_panels);
798 if (layouts.contains(
"right_panel_widths")) {
799 LoadFloatMap(layouts[
"right_panel_widths"], &prefs->right_panel_widths);
801 if (layouts.contains(
"saved_layouts")) {
802 LoadNestedBoolMap(layouts[
"saved_layouts"], &prefs->saved_layouts);
806 if (root.contains(
"filesystem")) {
807 const auto& fs = root[
"filesystem"];
808 if (fs.contains(
"project_root_paths") &&
809 fs[
"project_root_paths"].is_array()) {
810 prefs->project_root_paths.clear();
811 for (
const auto& item : fs[
"project_root_paths"]) {
812 if (item.is_string()) {
813 prefs->project_root_paths.push_back(item.get<std::string>());
817 prefs->default_project_root =
818 fs.value(
"default_project_root", prefs->default_project_root);
819 prefs->use_files_app = fs.value(
"use_files_app", prefs->use_files_app);
820 prefs->use_icloud_sync =
821 fs.value(
"use_icloud_sync", prefs->use_icloud_sync);
824 EnsureDefaultAiHosts(prefs);
825 EnsureDefaultAiProfiles(prefs);
826 EnsureDefaultFilesystemRoots(prefs);
828 return absl::OkStatus();
831absl::Status SavePreferencesToJson(
const std::filesystem::path& path,
832 const UserSettings::Preferences& prefs) {
834 if (!ensure_status.ok()) {
835 return ensure_status;
841 {
"font_global_scale", prefs.font_global_scale},
842 {
"backup_rom", prefs.backup_rom},
843 {
"save_new_auto", prefs.save_new_auto},
844 {
"autosave_enabled", prefs.autosave_enabled},
845 {
"autosave_interval", prefs.autosave_interval},
846 {
"recent_files_limit", prefs.recent_files_limit},
847 {
"last_rom_path", prefs.last_rom_path},
848 {
"last_project_path", prefs.last_project_path},
849 {
"show_welcome_on_startup", prefs.show_welcome_on_startup},
850 {
"restore_last_session", prefs.restore_last_session},
851 {
"prefer_hmagic_sprite_names", prefs.prefer_hmagic_sprite_names},
852 {
"welcome_triforce_alpha", prefs.welcome_triforce_alpha},
853 {
"welcome_triforce_speed", prefs.welcome_triforce_speed},
854 {
"welcome_triforce_size", prefs.welcome_triforce_size},
855 {
"welcome_particles_enabled", prefs.welcome_particles_enabled},
856 {
"welcome_mouse_repel_enabled", prefs.welcome_mouse_repel_enabled},
859 root[
"appearance"] = {
860 {
"reduced_motion", prefs.reduced_motion},
861 {
"switch_motion_profile", prefs.switch_motion_profile},
862 {
"last_theme_name", prefs.last_theme_name},
866 {
"backup_before_save", prefs.backup_before_save},
867 {
"default_editor", prefs.default_editor},
870 root[
"performance"] = {
871 {
"vsync", prefs.vsync},
872 {
"target_fps", prefs.target_fps},
873 {
"cache_size_mb", prefs.cache_size_mb},
874 {
"undo_history_size", prefs.undo_history_size},
877 json ai_hosts = json::array();
878 for (
const auto& host : prefs.ai_hosts) {
881 {
"label", host.label},
882 {
"base_url", host.base_url},
883 {
"api_type", host.api_type},
884 {
"supports_vision", host.supports_vision},
885 {
"supports_tools", host.supports_tools},
886 {
"supports_streaming", host.supports_streaming},
887 {
"allow_insecure", host.allow_insecure},
888 {
"api_key", host.api_key},
889 {
"credential_id", host.credential_id},
893 json ai_profiles = json::array();
894 for (
const auto& profile : prefs.ai_profiles) {
895 ai_profiles.push_back({
896 {
"name", profile.name},
897 {
"model", profile.model},
898 {
"temperature", profile.temperature},
899 {
"top_p", profile.top_p},
900 {
"max_output_tokens", profile.max_output_tokens},
901 {
"supports_vision", profile.supports_vision},
902 {
"supports_tools", profile.supports_tools},
907 {
"provider", prefs.ai_provider},
908 {
"model", prefs.ai_model},
909 {
"ollama_url", prefs.ollama_url},
910 {
"gemini_api_key", prefs.gemini_api_key},
911 {
"google_api_key", prefs.gemini_api_key},
912 {
"openai_api_key", prefs.openai_api_key},
913 {
"anthropic_api_key", prefs.anthropic_api_key},
914 {
"temperature", prefs.ai_temperature},
915 {
"max_tokens", prefs.ai_max_tokens},
916 {
"proactive", prefs.ai_proactive},
917 {
"auto_learn", prefs.ai_auto_learn},
918 {
"multimodal", prefs.ai_multimodal},
920 {
"active_host_id", prefs.active_ai_host_id},
921 {
"profiles", ai_profiles},
922 {
"active_profile", prefs.active_ai_profile},
923 {
"remote_build_host_id", prefs.remote_build_host_id},
924 {
"model_paths", prefs.ai_model_paths},
928 {
"level", prefs.log_level},
929 {
"to_file", prefs.log_to_file},
930 {
"file_path", prefs.log_file_path},
931 {
"ai_requests", prefs.log_ai_requests},
932 {
"rom_operations", prefs.log_rom_operations},
933 {
"gui_automation", prefs.log_gui_automation},
934 {
"proposals", prefs.log_proposals},
937 root[
"shortcuts"] = {
938 {
"panel", ToStringMap(prefs.panel_shortcuts)},
939 {
"global", ToStringMap(prefs.global_shortcuts)},
940 {
"editor", ToStringMap(prefs.editor_shortcuts)},
943 auto set_to_sorted_vec =
944 [](
const std::unordered_set<std::string>& s) -> std::vector<std::string> {
945 std::vector<std::string> v(s.begin(), s.end());
946 std::sort(v.begin(), v.end());
951 {
"visible", prefs.sidebar_visible},
952 {
"panel_expanded", prefs.sidebar_panel_expanded},
953 {
"panel_width", prefs.sidebar_panel_width},
954 {
"panel_browser_category_width", prefs.panel_browser_category_width},
955 {
"active_category", prefs.sidebar_active_category},
956 {
"order", prefs.sidebar_order},
957 {
"hidden", set_to_sorted_vec(prefs.sidebar_hidden)},
958 {
"pinned", set_to_sorted_vec(prefs.sidebar_pinned)},
961 root[
"status_bar"] = {
962 {
"visible", prefs.show_status_bar},
966 {
"defaults_revision", prefs.panel_layout_defaults_revision},
967 {
"panel_visibility", ToNestedBoolMap(prefs.panel_visibility_state)},
968 {
"pinned_panels", ToBoolMap(prefs.pinned_panels)},
969 {
"right_panel_widths", ToFloatMap(prefs.right_panel_widths)},
970 {
"saved_layouts", ToNestedBoolMap(prefs.saved_layouts)},
973 root[
"filesystem"] = {
974 {
"project_root_paths", prefs.project_root_paths},
975 {
"default_project_root", prefs.default_project_root},
976 {
"use_files_app", prefs.use_files_app},
977 {
"use_icloud_sync", prefs.use_icloud_sync},
980 std::ofstream file(path);
981 if (!file.is_open()) {
982 return absl::InternalError(
983 absl::StrFormat(
"Failed to open settings file: %s", path.string()));
986 file << root.dump(2) <<
"\n";
987 return absl::OkStatus();