0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-05 18:09:26 +08:00
discourse/plugins/discourse-ai/lib/summarization.rb
Natalie Tay cff8f6ea4d
FEATURE: Allow selection of multiple content languages when viewing topics and posts (#42128)
Related:
https://meta.discourse.org/t/multi-language-preferences-for-displaying-localized-content/381116

Allow anon and logged in users to set more than one language that they
understand, when content localization is enabled.


### User preferences

In user preferences, we now include a Content Languages section when the
site has Content Localization enabled.

### Topic menu

In topics where there are any languages that's not the user's language,
the option will appear to set their preferred content languages. This
appears for anon and logged in users, but anon users will be asked to
logged in to set the languages.
__________

These preferences apply only to topic titles and posts. They do not
affect navigational features like categories, tags, or sidebar content.
The per-post option to show the original remains independent of the
user's preferences.

This PR temporarily retains `show_original_content` as a compatibility
field computed from `automatically_translate`, keeping cached clients
and rolling deployments safe. A follow-up PR will remove the legacy API
and frontend compatibility code, then retire the old database column
through the staged column-removal process.
2026-07-30 12:19:44 +08:00

152 lines
4.9 KiB
Ruby
Vendored

# frozen_string_literal: true
module DiscourseAi
module Summarization
class << self
def topic_summary(topic, locale: nil, llm_model: nil)
return nil if !SiteSetting.ai_summarization_enabled
if (ai_agent = AiAgent.find_by_id_from_cache(SiteSetting.ai_summarization_agent)).blank?
return nil
end
locale ||= source_locale(topic)
agent_klass = ai_agent.class_instance
llm_model ||= find_summarization_model(agent_klass)
return nil if llm_model.blank?
DiscourseAi::Summarization::FoldContent.new(
build_bot(agent_klass, llm_model),
DiscourseAi::Summarization::Strategies::TopicSummary.new(topic, locale:),
)
end
def topic_gist(topic, locale: nil, llm_model: nil)
return nil if !SiteSetting.ai_summarization_enabled
if (ai_agent = AiAgent.find_by_id_from_cache(SiteSetting.ai_summary_gists_agent)).blank?
return nil
end
agent_klass = ai_agent.class_instance
llm_model ||= find_summarization_model(agent_klass)
return nil if llm_model.blank?
locale ||= gist_source_locale(topic)
strategy = DiscourseAi::Summarization::Strategies::HotTopicGists.new(topic, locale:)
DiscourseAi::Summarization::FoldContent.new(
build_bot(agent_klass, llm_model, output_tool: strategy.output_tool),
strategy,
)
end
def chat_channel_summary(channel, time_window_in_hours, llm_model: nil)
return nil if !SiteSetting.ai_summarization_enabled
if (ai_agent = AiAgent.find_by_id_from_cache(SiteSetting.ai_summarization_agent)).blank?
return nil
end
agent_klass = ai_agent.class_instance
llm_model ||= find_summarization_model(agent_klass)
return nil if llm_model.blank?
DiscourseAi::Summarization::FoldContent.new(
build_bot(agent_klass, llm_model),
DiscourseAi::Summarization::Strategies::ChatMessages.new(channel, time_window_in_hours),
persist_summaries: false,
)
end
def gist_locales(topic)
topic_source_locale = source_locale(topic)
locales =
if SiteSetting.content_localization_enabled
[*SiteSetting.content_localization_locales, topic_source_locale]
else
[topic_source_locale]
end
locales.each_with_object([]) do |locale, result|
normalized_locale = LocaleNormalizer.normalize_to_i18n(locale)&.to_s
next if normalized_locale.blank?
next if result.any? { |existing| LocaleNormalizer.is_same?(existing, normalized_locale) }
result << normalized_locale
end
end
def source_locale(topic)
LocaleNormalizer.normalize_to_i18n(raw_source_locale(topic))&.to_s
end
def display_locale(topic, scope:)
source_locale = raw_source_locale(topic)
locale =
if ContentLocalization.show_translated_topic?(topic, scope)
SiteSetting.content_localization_locales.find do |supported_locale|
LocaleNormalizer.is_same?(supported_locale, I18n.locale)
end || source_locale
else
source_locale
end
LocaleNormalizer.normalize_to_i18n(locale)&.to_s
end
def gist_source_locale(topic)
normalized_source_locale = source_locale(topic)
return if normalized_source_locale.blank?
gist_locales(topic).find do |locale|
LocaleNormalizer.is_same?(locale, normalized_source_locale)
end || normalized_source_locale
end
def gist_for(topic, scope:)
locale = display_locale(topic, scope:)
return if locale.blank?
topic.ai_gist_summaries.to_a.find do |summary|
summary.locale.present? && LocaleNormalizer.is_same?(summary.locale, locale)
end
end
# Priorities are:
# 1. Agent's default LLM
# 2. SiteSetting.ai_default_llm_model (or newest LLM if not set)
def find_summarization_model(agent_klass)
model_id = agent_klass.default_llm_id || SiteSetting.ai_default_llm_model
if model_id.present?
LlmModel.find_by(id: model_id)
else
LlmModel.last
end
end
### Private
def build_bot(agent_klass, llm_model, output_tool: nil)
agent = agent_klass.new
if output_tool
agent.define_singleton_method(:available_tools) { [output_tool] }
agent.define_singleton_method(:force_tool_use) { [output_tool] }
agent.define_singleton_method(:forced_tool_count) { 1 }
agent.define_singleton_method(:response_format) { nil }
end
user = User.find_by(id: agent_klass.user_id) || Discourse.system_user
DiscourseAi::Agents::Bot.as(user, agent:, model: llm_model)
end
private
def raw_source_locale(topic)
topic.locale.presence || SiteSetting.default_locale
end
end
end
end