| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromeos/components/quick_answers/quick_answers_model.h" |
| |
| #include <cmath> |
| #include <compare> |
| |
| #include "base/notreached.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chromeos/components/quick_answers/utils/quick_answers_utils.h" |
| #include "chromeos/strings/grit/chromeos_strings.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| namespace { |
| |
| int GetFormulaMessageId(bool is_multiply, bool is_approximate) { |
| if (is_multiply) { |
| if (is_approximate) { |
| return IDS_RICH_ANSWERS_VIEW_UNIT_CONVERSION_APPROXIMATE_MULTIPLICATION_FORMULA_TEXT; |
| } |
| |
| return IDS_RICH_ANSWERS_VIEW_UNIT_CONVERSION_EXACT_MULTIPLICATION_FORMULA_TEXT; |
| } |
| |
| if (is_approximate) { |
| return IDS_RICH_ANSWERS_VIEW_UNIT_CONVERSION_APPROXIMATE_DIVISION_FORMULA_TEXT; |
| } |
| |
| return IDS_RICH_ANSWERS_VIEW_UNIT_CONVERSION_EXACT_DIVISION_FORMULA_TEXT; |
| } |
| |
| } // namespace |
| |
| namespace quick_answers { |
| |
| std::optional<quick_answers::Intent> ToIntent(IntentType intent_type) { |
| switch (intent_type) { |
| case IntentType::kDictionary: |
| return quick_answers::Intent::kDefinition; |
| case IntentType::kTranslation: |
| return quick_answers::Intent::kTranslation; |
| case IntentType::kUnit: |
| return quick_answers::Intent::kUnitConversion; |
| case IntentType::kUnknown: |
| return std::nullopt; |
| } |
| |
| NOTREACHED() << "Invalid intent type enum value provided"; |
| } |
| |
| PhoneticsInfo::PhoneticsInfo() = default; |
| PhoneticsInfo::PhoneticsInfo(const PhoneticsInfo&) = default; |
| PhoneticsInfo::~PhoneticsInfo() = default; |
| |
| bool PhoneticsInfo::PhoneticsInfoAvailable() const { |
| return AudioUrlAvailable() || TtsAudioAvailable(); |
| } |
| |
| bool PhoneticsInfo::AudioUrlAvailable() const { |
| return !phonetics_audio.is_empty(); |
| } |
| |
| bool PhoneticsInfo::TtsAudioAvailable() const { |
| return !query_text.empty() && !locale.empty(); |
| } |
| |
| QuickAnswer::QuickAnswer() = default; |
| QuickAnswer::~QuickAnswer() = default; |
| |
| IntentInfo::IntentInfo() = default; |
| IntentInfo::IntentInfo(const IntentInfo& other) = default; |
| IntentInfo::IntentInfo(const std::string& intent_text, |
| IntentType intent_type, |
| std::string_view device_language, |
| std::string_view source_language) { |
| this->intent_text = intent_text; |
| this->intent_type = intent_type; |
| this->device_language = std::string(device_language); |
| this->source_language = std::string(source_language); |
| } |
| IntentInfo::~IntentInfo() = default; |
| |
| PreprocessedOutput::PreprocessedOutput() = default; |
| PreprocessedOutput::PreprocessedOutput(const PreprocessedOutput& other) = |
| default; |
| PreprocessedOutput::~PreprocessedOutput() = default; |
| |
| QuickAnswersRequest::QuickAnswersRequest() = default; |
| QuickAnswersRequest::QuickAnswersRequest(const QuickAnswersRequest& other) = |
| default; |
| QuickAnswersRequest::~QuickAnswersRequest() = default; |
| |
| Sense::Sense() = default; |
| Sense::Sense(const Sense& other) = default; |
| Sense& Sense::Sense::operator=(const Sense& other) = default; |
| Sense::~Sense() = default; |
| |
| DefinitionResult::DefinitionResult() = default; |
| DefinitionResult::DefinitionResult(const DefinitionResult& other) = default; |
| DefinitionResult& DefinitionResult::DefinitionResult::operator=( |
| const DefinitionResult& other) = default; |
| DefinitionResult::~DefinitionResult() = default; |
| |
| TranslationResult::TranslationResult() = default; |
| TranslationResult::TranslationResult(const TranslationResult& other) = default; |
| TranslationResult& TranslationResult::TranslationResult::operator=( |
| const TranslationResult& other) = default; |
| TranslationResult::~TranslationResult() = default; |
| |
| ConversionRule::ConversionRule(const std::string& category, |
| const std::string& unit_name, |
| double term_a, |
| double term_b, |
| double term_c) |
| : category_(category), |
| unit_name_(unit_name), |
| term_a_(term_a), |
| term_b_(term_b), |
| term_c_(term_c) {} |
| ConversionRule::ConversionRule(const ConversionRule& other) = default; |
| ConversionRule& ConversionRule::ConversionRule::operator=( |
| const ConversionRule& other) = default; |
| ConversionRule::~ConversionRule() = default; |
| std::optional<ConversionRule> ConversionRule::Create( |
| const std::string& category, |
| const std::string& unit_name, |
| const std::optional<double>& term_a, |
| const std::optional<double>& term_b, |
| const std::optional<double>& term_c) { |
| if (category.empty() || unit_name.empty()) { |
| return std::nullopt; |
| } |
| |
| // If neither |term_a| nor |term_c| is valid, there is no valid conversion |
| // rule. |
| if ((!term_a || term_a.value() == kInvalidRateTermValue) && |
| (!term_c || term_c.value() == kInvalidRateTermValue)) { |
| return std::nullopt; |
| } |
| |
| // Neither |term_a| nor |term_c| should be negative. Return nullopt for this |
| // unexpected case. |
| if ((term_a && term_a.value() < 0) || (term_c && term_c.value() < 0)) { |
| return std::nullopt; |
| } |
| |
| double term_a_value = term_a.value_or(kInvalidRateTermValue); |
| double term_b_value = term_b.value_or(kInvalidRateTermValue); |
| double term_c_value = term_c.value_or(kInvalidRateTermValue); |
| return ConversionRule(category, unit_name, term_a_value, term_b_value, |
| term_c_value); |
| } |
| double ConversionRule::ConvertAmountToSi(double unit_amount) const { |
| return (term_a_ != kInvalidRateTermValue) ? (term_a_ * unit_amount + term_b_) |
| : (term_c_ / unit_amount); |
| } |
| double ConversionRule::ConvertAmountFromSi(double si_amount) const { |
| return (term_a_ != kInvalidRateTermValue) ? ((si_amount - term_b_) / term_a_) |
| : (term_c_ / si_amount); |
| } |
| bool ConversionRule::IsSingleVariableLinearConversion() const { |
| return (term_a_ != kInvalidRateTermValue) && |
| (term_b_ == kInvalidRateTermValue) && |
| (term_c_ == kInvalidRateTermValue); |
| } |
| |
| UnitConversion::UnitConversion(const ConversionRule& source_rule, |
| const ConversionRule& dest_rule) |
| : source_rule_(source_rule), dest_rule_(dest_rule) {} |
| UnitConversion::UnitConversion(const UnitConversion& other) = default; |
| UnitConversion& UnitConversion::UnitConversion::operator=( |
| const UnitConversion& other) = default; |
| UnitConversion::~UnitConversion() = default; |
| std::optional<UnitConversion> UnitConversion::Create( |
| const ConversionRule& source_rule, |
| const ConversionRule& dest_rule) { |
| if (source_rule.category() != dest_rule.category()) { |
| return std::nullopt; |
| } |
| |
| return UnitConversion(source_rule, dest_rule); |
| } |
| double UnitConversion::ConvertSourceAmountToDestAmount( |
| double source_amount) const { |
| return dest_rule_.ConvertAmountFromSi( |
| source_rule_.ConvertAmountToSi(source_amount)); |
| } |
| std::optional<std::string> UnitConversion::GetConversionFormulaText() const { |
| // We only return formula description texts for conversions between two units |
| // whose `ConversionRule` only involves |term_a_| values i.e. formula form is: |
| // a * source = target. |
| if (!source_rule_.IsSingleVariableLinearConversion() || |
| !dest_rule_.IsSingleVariableLinearConversion()) { |
| return std::nullopt; |
| } |
| |
| // Don't return a formula description text if the conversion rate is |
| // exactly 1. |
| if (source_rule_.term_a() == dest_rule_.term_a()) { |
| return std::nullopt; |
| } |
| |
| // Get the greater ratio (i.e. >= 1) of the two linear |term_a_| values. |
| // The conversion formula will be in the form: source * (a1/a2) = target |
| // The actual ratio will determine whether the conversion operator used for |
| // the formula description is multiplication (when the source unit |term_a| |
| // value is the numerator) or division (when the target unit |term_a| value |
| // is the numerator). |
| double conversion_term_a = |
| MaybeGetRatio(source_rule_.term_a(), dest_rule_.term_a()); |
| |
| if (conversion_term_a == kInvalidRateTermValue) { |
| return std::nullopt; |
| } |
| |
| // Check if the conversion term is a decimal number. If it is, an |
| // approximation qualifier (i.e. "for an approximate result, ...") will be |
| // appended at the beginning of the formula text. |
| double int_part = 0.0; |
| bool is_approximate_formula = std::modf(conversion_term_a, &int_part) != 0.0; |
| |
| bool is_multiply_formula = source_rule_.term_a() > dest_rule_.term_a(); |
| int formula_message_id = |
| GetFormulaMessageId(is_multiply_formula, is_approximate_formula); |
| |
| return l10n_util::GetStringFUTF8( |
| formula_message_id, |
| base::UTF8ToUTF16(base::ToLowerASCII(source_rule_.category())), |
| base::UTF8ToUTF16(BuildRoundedUnitAmountDisplayText(conversion_term_a))); |
| } |
| |
| // static |
| double UnitConversion::MaybeGetRatio(double value1, double value2) { |
| if (value1 == quick_answers::kInvalidRateTermValue || |
| value2 == quick_answers::kInvalidRateTermValue) { |
| return quick_answers::kInvalidRateTermValue; |
| } |
| |
| return std::max(value1, value2) / std::min(value1, value2); |
| } |
| |
| std::weak_ordering operator<=>(const UnitConversion& a, |
| const UnitConversion& b) { |
| double a_linear_term_ratio = UnitConversion::MaybeGetRatio( |
| a.source_rule_.term_a(), a.dest_rule_.term_a()); |
| CHECK(std::isfinite(a_linear_term_ratio)); |
| if (a_linear_term_ratio == kInvalidRateTermValue) { |
| return std::weak_ordering::greater; |
| } |
| |
| double b_linear_term_ratio = UnitConversion::MaybeGetRatio( |
| b.source_rule_.term_a(), b.dest_rule_.term_a()); |
| CHECK(std::isfinite(b_linear_term_ratio)); |
| if (b_linear_term_ratio == kInvalidRateTermValue || |
| a_linear_term_ratio < b_linear_term_ratio) { |
| return std::weak_ordering::less; |
| } |
| |
| // Cannot use `<=>` on the doubles directly, since that returns a partial |
| // ordering, not a weak ordering. |
| return a_linear_term_ratio == b_linear_term_ratio |
| ? std::weak_ordering::equivalent |
| : std::weak_ordering::greater; |
| } |
| |
| UnitConversionResult::UnitConversionResult() = default; |
| UnitConversionResult::UnitConversionResult(const UnitConversionResult& other) = |
| default; |
| UnitConversionResult& UnitConversionResult::UnitConversionResult::operator=( |
| const UnitConversionResult& other) = default; |
| UnitConversionResult::~UnitConversionResult() = default; |
| |
| StructuredResult::StructuredResult() = default; |
| StructuredResult::~StructuredResult() = default; |
| ResultType StructuredResult::GetResultType() const { |
| if (translation_result) { |
| return ResultType::kTranslationResult; |
| } |
| if (definition_result) { |
| return ResultType::kDefinitionResult; |
| } |
| if (unit_conversion_result) { |
| return ResultType::kUnitConversionResult; |
| } |
| return ResultType::kNoResult; |
| } |
| |
| QuickAnswersSession::QuickAnswersSession() = default; |
| QuickAnswersSession::~QuickAnswersSession() = default; |
| |
| } // namespace quick_answers |