chromium/components/manta/sparky/sparky_util.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/manta/sparky/sparky_util.h"

#include <memory>
#include <optional>

#include "base/containers/fixed_flat_map.h"
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "components/manta/proto/sparky.pb.h"
#include "components/manta/sparky/sparky_delegate.h"

namespace manta {
namespace {
using SettingType = proto::SettingType;

static constexpr auto pref_to_setting_map =
    base::MakeFixedFlatMap<PrefType, SettingType>({
        {PrefType::kBoolean, SettingType::SETTING_TYPE_BOOL},
        {PrefType::kString, SettingType::SETTING_TYPE_STRING},
        {PrefType::kDouble, SettingType::SETTING_TYPE_DOUBLE},
        {PrefType::kInt, SettingType::SETTING_TYPE_INTEGER},
    });

static constexpr auto setting_to_pref_map =
    base::MakeFixedFlatMap<SettingType, PrefType>({
        {SettingType::SETTING_TYPE_BOOL, PrefType::kBoolean},
        {SettingType::SETTING_TYPE_STRING, PrefType::kString},
        {SettingType::SETTING_TYPE_DOUBLE, PrefType::kDouble},
        {SettingType::SETTING_TYPE_INTEGER, PrefType::kInt},
    });

static constexpr auto role_to_proto_map =
    base::MakeFixedFlatMap<Role, proto::Role>({
        {Role::kAssistant, proto::Role::ROLE_ASSISTANT},
        {Role::kUser, proto::Role::ROLE_USER},
    });

static constexpr auto diagnostic_map =
    base::MakeFixedFlatMap<manta::proto::Diagnostics, Diagnostics>(
        {{manta::proto::Diagnostics::DIAGNOSTICS_BATTERY,
          Diagnostics::kBattery},
         {manta::proto::Diagnostics::DIAGNOSTICS_CPU, Diagnostics::kCpu},
         {manta::proto::Diagnostics::DIAGNOSTICS_STORAGE,
          Diagnostics::kStorage},
         {manta::proto::Diagnostics::DIAGNOSTICS_MEMORY,
          Diagnostics::kMemory}});

// Gets the type of setting into the proto enum. Also verifies that the value is
// of the type specified.
std::optional<SettingType> VerifyValueAndConvertPrefTypeToSettingType(
    PrefType pref_type,
    std::optional<base::Value> value) {
  if (!value) {
    return std::nullopt;
  }
  const auto iter = pref_to_setting_map.find(pref_type);

  auto type = iter != pref_to_setting_map.end()
                  ? std::optional<SettingType>(iter->second)
                  : std::nullopt;
  if (!type.has_value()) {
    return std::nullopt;
  }
  if ((pref_type == PrefType::kBoolean && value->is_bool()) ||
      (pref_type == PrefType::kDouble && value->is_double()) ||
      (pref_type == PrefType::kInt && value->is_int()) ||
      (pref_type == PrefType::kString && value->is_string())) {
    return type;
  }

  return std::nullopt;
}

// Converts the setting proto into the pref type. Also verifies that the value
// is of the type specified.
std::optional<PrefType> VerifyValueAndConvertSettingTypeToPrefType(
    SettingType setting_type,
    const proto::SettingsValue& value) {
  const auto iter = setting_to_pref_map.find(setting_type);

  auto type = iter != setting_to_pref_map.end()
                  ? std::optional<PrefType>(iter->second)
                  : std::nullopt;
  if (!type.has_value()) {
    return std::nullopt;
  }
  if ((type.value() == PrefType::kBoolean && value.has_bool_val()) ||
      (type.value() == PrefType::kDouble && value.has_double_val()) ||
      (type.value() == PrefType::kInt && value.has_int_val()) ||
      (type.value() == PrefType::kString && value.has_text_val())) {
    return type;
  }

  return std::nullopt;
}

std::optional<base::Value> GetSettingsValue(proto::SettingsValue value,
                                            const PrefType& pref_type) {
  if (pref_type == PrefType::kBoolean) {
    return std::make_optional(base::Value(value.bool_val()));
  } else if (pref_type == PrefType::kInt) {
    return std::make_optional(base::Value(value.int_val()));
  } else if (pref_type == PrefType::kDouble) {
    return std::make_optional(base::Value(value.double_val()));
  } else if (pref_type == PrefType::kString) {
    return std::make_optional(base::Value(value.text_val()));
  } else {
    return std::nullopt;
  }
}

}  // namespace

FileAction::FileAction(std::string launch_file_path)
    : launch_file_path(launch_file_path) {}
FileAction::~FileAction() = default;

FileAction::FileAction(const FileAction&) = default;
FileAction& FileAction::operator=(const FileAction&) = default;

ClickAction::ClickAction(int x_pos, int y_pos) : x_pos(x_pos), y_pos(y_pos) {}
ClickAction::~ClickAction() = default;

ClickAction::ClickAction(const ClickAction&) = default;
ClickAction& ClickAction::operator=(const ClickAction&) = default;

Action::Action(SettingsData updated_setting)
    : updated_setting(std::make_optional(updated_setting)),
      type(ActionType::kSetting) {}
Action::Action(bool all_done)
    : type(ActionType::kAllDone), all_done(all_done) {}
Action::Action(ClickAction click)
    : click(std::make_optional(click)), type(ActionType::kClick) {}
Action::Action(ActionType type) : type(type) {}
Action::Action(FileAction file_action, ActionType type)
    : file_action(std::make_optional(file_action)), type(type) {}
Action::~Action() = default;

Action::Action(const Action&) = default;
Action& Action::operator=(const Action&) = default;

DialogTurn::DialogTurn(const std::string& message,
                       Role role,
                       std::vector<Action> actions)
    : message(message), role(role), actions(actions) {}

DialogTurn::DialogTurn(const std::string& message, Role role)
    : message(message), role(role) {}

DialogTurn::~DialogTurn() = default;

DialogTurn::DialogTurn(const DialogTurn&) = default;
DialogTurn& DialogTurn::operator=(const DialogTurn&) = default;

void DialogTurn::AppendAction(Action action) {
  actions.emplace_back(action);
}

proto::Role GetRole(Role role) {
  const auto iter = role_to_proto_map.find(role);
  return iter != role_to_proto_map.end() ? iter->second
                                         : proto::ROLE_UNSPECIFIED;
}

void AddSettingProto(const SettingsData& setting,
                     ::manta::proto::Setting* setting_proto,
                     SettingType setting_type) {
  setting_proto->set_type(setting_type);
  setting_proto->set_settings_id(setting.pref_name);
  auto* settings_value = setting_proto->mutable_value();
  if (setting.pref_type == PrefType::kBoolean) {
    settings_value->set_bool_val(setting.bool_val);
  } else if (setting.pref_type == PrefType::kDouble) {
    settings_value->set_double_val(setting.double_val);
  } else if (setting.pref_type == PrefType::kInt) {
    settings_value->set_int_val(setting.int_val);
  } else if (setting.pref_type == PrefType::kString) {
    settings_value->set_text_val(setting.string_val);
  }
}

void AddSettingsProto(const SparkyDelegate::SettingsDataList& settings_list,
                      ::manta::proto::SettingsData* settings_data) {
  for (auto const& [pref_name, setting] : settings_list) {
    auto setting_type = VerifyValueAndConvertPrefTypeToSettingType(
        setting->pref_type, setting->GetValue());
    if (setting_type == std::nullopt) {
      DVLOG(1) << "Invalid setting type for" << setting->pref_name;
      continue;
    }
    auto* setting_data = settings_data->add_setting();
    AddSettingProto(*setting, setting_data, setting_type.value());
  }
}

std::vector<Diagnostics> COMPONENT_EXPORT(MANTA)
    ObtainDiagnosticsVectorFromProto(
        const ::manta::proto::DiagnosticsRequest& diagnostics_request) {
  int number_of_diagnostics_requested = diagnostics_request.diagnostics_size();
  std::vector<Diagnostics> diagnostics_vector;
  for (int index = 0; index < number_of_diagnostics_requested; index++) {
    const auto iter =
        diagnostic_map.find(diagnostics_request.diagnostics(index));

    auto type = iter != diagnostic_map.end()
                    ? std::optional<Diagnostics>(iter->second)
                    : std::nullopt;
    if (type == std::nullopt) {
      DVLOG(1) << "Invalid diagnostics type";
      continue;
    } else {
      diagnostics_vector.emplace_back(type.value());
    }
  }
  return diagnostics_vector;
}

void COMPONENT_EXPORT(MANTA)
    AddDiagnosticsProto(std::optional<DiagnosticsData> diagnostics_data,
                        proto::DiagnosticsData* diagnostics_proto) {
  if (diagnostics_data) {
    if (diagnostics_data->cpu_data) {
      auto* cpu_proto = diagnostics_proto->mutable_cpu();
      cpu_proto->set_temperature(
          diagnostics_data->cpu_data->average_cpu_temp_celsius);
      cpu_proto->set_clock_speed_ghz(
          diagnostics_data->cpu_data->scaling_current_frequency_ghz);
      cpu_proto->set_cpu_usage_snapshot(
          diagnostics_data->cpu_data->cpu_usage_percentage_snapshot);
    }
    if (diagnostics_data->memory_data) {
      auto* memory_proto = diagnostics_proto->mutable_memory();
      memory_proto->set_free_ram_gb(
          diagnostics_data->memory_data->available_memory_gb);
      memory_proto->set_total_ram_gb(
          diagnostics_data->memory_data->total_memory_gb);
    }
    if (diagnostics_data->battery_data) {
      auto* battery_proto = diagnostics_proto->mutable_battery();
      battery_proto->set_battery_health(
          diagnostics_data->battery_data->battery_wear_percentage);
      battery_proto->set_battery_charge_percentage(
          diagnostics_data->battery_data->battery_percentage);
      battery_proto->set_cycle_count(
          diagnostics_data->battery_data->cycle_count);
      battery_proto->set_battery_time(
          diagnostics_data->battery_data->power_time);
    }
    if (diagnostics_data->storage_data) {
      auto* storage_proto = diagnostics_proto->mutable_storage();
      storage_proto->set_free_storage(
          diagnostics_data->storage_data->free_bytes);
      storage_proto->set_total_storage(
          diagnostics_data->storage_data->total_bytes);
    }
  }
}

void AddAppsData(base::span<const AppsData> apps_data,
                 proto::AppsData* apps_proto) {
  for (const manta::AppsData& app : apps_data) {
    proto::App* app_proto = apps_proto->add_app();
    app_proto->set_id(app.id);
    app_proto->set_name(app.name);
    app_proto->mutable_searchable_term()->Add(app.searchable_text.begin(),
                                              app.searchable_text.end());
  }
}

void AddFilesData(base::span<const FileData> files_data,
                  proto::FilesData* files_proto) {
  for (const manta::FileData& file : files_data) {
    proto::File* file_proto = files_proto->add_files();
    file_proto->set_name(file.name);
    file_proto->set_path(file.path);
    file_proto->set_date_modified(file.date_modified);
    file_proto->set_size_in_bytes(file.size_in_bytes);
    if (file.bytes.has_value()) {
      file_proto->set_serialized_bytes(
          std::string(file.bytes->begin(), file.bytes->end()));
    }
    if (!file.summary.empty()) {
      file_proto->set_summary(file.summary);
    }
  }
}

std::unique_ptr<SettingsData> ObtainSettingFromProto(
    proto::Setting setting_proto) {
  auto pref_type = VerifyValueAndConvertSettingTypeToPrefType(
      setting_proto.type(), setting_proto.value());
  if (pref_type == std::nullopt) {
    return nullptr;
  }
  return std::make_unique<SettingsData>(
      setting_proto.settings_id(), *pref_type,
      GetSettingsValue(setting_proto.value(), *pref_type));
}

DialogTurn ConvertDialogToStruct(proto::Turn* turn_proto) {
  DialogTurn dialog =
      DialogTurn(turn_proto->message(),
                 turn_proto->role() == proto::ROLE_ASSISTANT ? Role::kAssistant
                                                             : Role::kUser);
  if (turn_proto->action_size() == 0) {
    return dialog;
  }

  for (int position = 0; position < turn_proto->action_size(); ++position) {
    auto action_proto = turn_proto->action().at(position);
    if (action_proto.has_launch_app_id()) {
      auto action = Action(ActionType::kLaunchApp);
      action.launched_app = action_proto.launch_app_id();
      dialog.AppendAction(action);
    } else if (action_proto.has_update_setting()) {
      auto setting_proto = action_proto.update_setting();
      std::unique_ptr<SettingsData> setting_data =
          ObtainSettingFromProto(setting_proto);
      if (!setting_data) {
        DVLOG(1) << "Invalid setting type for" << setting_proto.settings_id();
        continue;
      }
      dialog.AppendAction(Action(*setting_data.get()));
    } else if (action_proto.has_all_done()) {
      dialog.AppendAction(Action(action_proto.all_done()));
    } else if (action_proto.has_click() && action_proto.click().has_x_pos() &&
               action_proto.click().has_y_pos()) {
      dialog.AppendAction(Action(ClickAction(action_proto.click().x_pos(),
                                             action_proto.click().y_pos())));
    } else if (action_proto.has_text_entry() &&
               action_proto.text_entry().has_text()) {
      auto action = Action(ActionType::kTextEntry);
      action.text_entry = action_proto.text_entry().text();
      dialog.AppendAction(action);
    } else if (action_proto.has_file_action()) {
      if (action_proto.file_action().has_launch_file_path()) {
        dialog.AppendAction(
            Action(FileAction(action_proto.file_action().launch_file_path()),
                   ActionType::kLaunchFile));
      }
    }
  }
  return dialog;
}

void AddDialogToSparkyContext(const std::vector<DialogTurn>& dialog,
                              proto::SparkyContextData* sparky_context_proto) {
  for (const auto& dialog_turn : dialog) {
    auto* dialog_proto = sparky_context_proto->add_conversation();
    dialog_proto->set_message(dialog_turn.message);
    dialog_proto->set_role(GetRole(dialog_turn.role));
    for (const auto& action : dialog_turn.actions) {
      auto* action_proto = dialog_proto->add_action();
      if (action.type == ActionType::kLaunchApp &&
          !action.launched_app.empty()) {
        action_proto->set_launch_app_id(action.launched_app);
      } else if (action.type == ActionType::kSetting &&
                 action.updated_setting.has_value()) {
        auto setting_type = VerifyValueAndConvertPrefTypeToSettingType(
            action.updated_setting.value().pref_type,
            action.updated_setting.value().GetValue());
        if (setting_type == std::nullopt) {
          DVLOG(1) << "Invalid setting type for"
                   << action.updated_setting.value().pref_name;
          continue;
        }
        auto* setting_proto = action_proto->mutable_update_setting();
        AddSettingProto(action.updated_setting.value(), setting_proto,
                        setting_type.value());
      } else if (action.type == ActionType::kClick &&
                 action.click.has_value()) {
        auto* click_proto = action_proto->mutable_click();
        click_proto->set_x_pos(action.click->x_pos);
        click_proto->set_y_pos(action.click->y_pos);
      } else if (action.type == ActionType::kLaunchFile &&
                 action.file_action.has_value() &&
                 !action.file_action->launch_file_path.empty()) {
        auto* file_action = action_proto->mutable_file_action();
        file_action->set_launch_file_path(action.file_action->launch_file_path);
      } else if (action.type == ActionType::kAllDone) {
        action_proto->set_all_done(action.all_done);
      } else if (action.type == ActionType::kTextEntry &&
                 !action.text_entry.empty()) {
        auto* text_entry = action_proto->mutable_text_entry();
        text_entry->set_text(action.text_entry);
      }
    }
  }
}

std::set<std::string> COMPONENT_EXPORT(MANTA)
    GetSelectedFilePaths(const proto::FileRequest& file_request) {
  std::set<std::string> set_file_paths;
  int file_size = file_request.paths_size();
  for (int index = 0; index < file_size; index++) {
    set_file_paths.emplace(file_request.paths(index));
  }
  return set_file_paths;
}

std::optional<FileData> GetFileFromProto(const proto::File& file_proto) {
  if (!file_proto.has_name() || !file_proto.has_path() ||
      !file_proto.has_date_modified() || !file_proto.has_size_in_bytes() ||
      !file_proto.has_summary()) {
    return std::nullopt;
  }
  auto file = std::make_optional<FileData>(file_proto.path(), file_proto.name(),
                                           file_proto.date_modified());
  file->summary = file_proto.summary();
  file->size_in_bytes = file_proto.size_in_bytes();
  if (file_proto.has_serialized_bytes()) {
    file->bytes = std::vector<uint8_t>(file_proto.serialized_bytes().begin(),
                                       file_proto.serialized_bytes().end());
  }
  return file;
}

std::vector<FileData> GetFileDataFromProto(
    const proto::FilesData& files_proto) {
  std::vector<FileData> files_data;
  int proto_file_count = files_proto.files_size();
  for (int index = 0; index < proto_file_count; ++index) {
    auto file = GetFileFromProto(files_proto.files(index));
    if (file.has_value()) {
      files_data.emplace_back(std::move(file.value()));
    }
  }
  return files_data;
}

}  // namespace manta