0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-07 13:19:19 +08:00
discourse/plugins/discourse-ai/lib/configuration/module.rb
Natalie Tay 86680f6b0d
FEATURE: Dedicated image caption AI feature and agent validations (#41729)
Follow up to https://github.com/discourse/discourse/pull/41586


1. **Bulk of the PR diff** -- there's a mix of usages for "description"
and "caption", we'll stick to V1 terminology and keep "AI caption"
instead of bringing in "description".

2. Move image caption out of AI helper, into its own dedicated "AI
Feature" for consolidated settings

3. Update default values for the existing image caption agent 
- vision_enabled was default off, now on
- update description

4. Validations and problem checks
- via Site Setting validation - agent / llm
  - when enabling the feature
  - or when selecting invalid agent

- inform admin when they've enabled the setting but the agent is
misconfigured via ProblemCheck
2026-07-16 11:29:39 +08:00

226 lines
6.9 KiB
Ruby
Vendored

# frozen_string_literal: true
module DiscourseAi
module Configuration
class Module
SUMMARIZATION = "summarization"
SEARCH = "search"
DISCORD = "discord"
INFERENCE = "inference"
AI_HELPER = "ai_helper"
IMAGE_CAPTION = "image_caption"
TRANSLATION = "translation"
BOT = "bot"
SPAM = "spam"
EMBEDDINGS = "embeddings"
AUTOMATION_REPORTS = "automation_reports"
AUTOMATION_TRIAGE = "automation_triage"
ADMIN_DASHBOARD = "admin_dashboard"
NAMES = [
SUMMARIZATION,
SEARCH,
DISCORD,
INFERENCE,
AI_HELPER,
IMAGE_CAPTION,
TRANSLATION,
BOT,
SPAM,
EMBEDDINGS,
AUTOMATION_REPORTS,
AUTOMATION_TRIAGE,
ADMIN_DASHBOARD,
].freeze
SUMMARIZATION_ID = 1
SEARCH_ID = 2
DISCORD_ID = 3
INFERENCE_ID = 4
AI_HELPER_ID = 5
TRANSLATION_ID = 6
BOT_ID = 7
SPAM_ID = 8
EMBEDDINGS_ID = 9
AUTOMATION_REPORTS_ID = 10
AUTOMATION_TRIAGE_ID = 11
ADMIN_DASHBOARD_ID = 12
IMAGE_CAPTION_ID = 13
class << self
def external_module_id(module_name)
Digest::SHA1.hexdigest(module_name.to_s).to_i(16) % 100_000 + 1000
end
def all
base_modules = [
new(
SUMMARIZATION_ID,
SUMMARIZATION,
enabled_by_setting: "ai_summarization_enabled",
features: DiscourseAi::Configuration::Feature.summarization_features,
),
new(
SEARCH_ID,
SEARCH,
enabled_by_setting: "ai_discover_enabled",
features: DiscourseAi::Configuration::Feature.search_features,
),
new(
DISCORD_ID,
DISCORD,
enabled_by_setting: "ai_discord_search_enabled",
features: DiscourseAi::Configuration::Feature.discord_features,
),
new(
INFERENCE_ID,
INFERENCE,
enabled_by_setting: "inferred_concepts_enabled",
features: DiscourseAi::Configuration::Feature.inference_features,
),
new(
AI_HELPER_ID,
AI_HELPER,
enabled_by_setting: "ai_helper_enabled",
features: DiscourseAi::Configuration::Feature.ai_helper_features,
),
new(
IMAGE_CAPTION_ID,
IMAGE_CAPTION,
enabled_by_setting: "ai_post_image_captions_enabled",
features: DiscourseAi::Configuration::Feature.image_caption_features,
),
new(
TRANSLATION_ID,
TRANSLATION,
enabled_by_setting: "ai_translation_enabled",
features: DiscourseAi::Configuration::Feature.translation_features,
),
new(
BOT_ID,
BOT,
enabled_by_setting: "ai_bot_enabled",
features: DiscourseAi::Configuration::Feature.bot_features,
),
new(
SPAM_ID,
SPAM,
enabled_by_setting: "ai_spam_detection_enabled",
features: DiscourseAi::Configuration::Feature.spam_features,
),
new(
EMBEDDINGS_ID,
EMBEDDINGS,
enabled_by_setting: "ai_embeddings_enabled",
features: DiscourseAi::Configuration::Feature.embeddings_features,
extra_check: -> { SiteSetting.ai_embeddings_semantic_search_enabled },
),
new(
ADMIN_DASHBOARD_ID,
ADMIN_DASHBOARD,
enabled_by_setting: "ai_admin_dashboard_enabled",
features: DiscourseAi::Configuration::Feature.admin_dashboard_features,
extra_check: -> do
DiscourseAi::Configuration::Feature.admin_dashboard_features.any?(&:enabled?)
end,
visible: false,
),
]
if SiteSetting.discourse_automation_enabled
base_modules << new(
AUTOMATION_REPORTS_ID,
AUTOMATION_REPORTS,
enabled_by_setting: "discourse_automation_enabled",
features: DiscourseAi::Configuration::Feature.ai_automation_report_scripts,
extra_check: -> { has_scripts?(["llm_report"]) },
)
base_modules << new(
AUTOMATION_TRIAGE_ID,
AUTOMATION_TRIAGE,
enabled_by_setting: "discourse_automation_enabled",
features: DiscourseAi::Configuration::Feature.ai_automation_triage_scripts,
extra_check: -> { has_scripts?(%w[llm_triage llm_agent_triage]) },
)
end
# external modules from plugin registry
DiscoursePluginRegistry
.external_ai_features
.group_by { |e| e[:module_name] }
.each do |mod_name, entries|
module_id = external_module_id(mod_name)
features =
entries.map do |e|
setting_name = "#{mod_name}_#{e[:feature]}_agent"
DiscourseAi::Configuration::Feature.new(
e[:feature].to_s,
setting_name,
module_id,
mod_name.to_s,
enabled_by_setting: e[:enabled_by_setting],
)
end
base_modules << new(
module_id,
mod_name,
features:,
extra_check: -> { features.any?(&:enabled?) },
visible: entries.any? { |e| e.fetch(:visible, true) },
)
end
base_modules
end
def has_scripts?(script_names)
DB
.query_single(
"SELECT COUNT(*) FROM discourse_automation_automations WHERE script IN (:names) and enabled",
names: script_names,
)
.first
.to_i > 0
end
def find_by(id:)
all.find { |m| m.id == id }
end
end
def initialize(
id,
name,
enabled_by_setting: nil,
features: [],
extra_check: nil,
visible: true
)
@id = id
@name = name
@enabled_by_setting = enabled_by_setting
@features = features
@extra_check = extra_check
@visible = visible
end
attr_reader :id, :name, :enabled_by_setting, :features
def visible?
@visible
end
def enabled?
return @extra_check.present? ? @extra_check.call : true if enabled_by_setting.blank?
enabled_setting = SiteSetting.get(enabled_by_setting)
if @extra_check
enabled_setting && @extra_check.call
else
enabled_setting
end
end
end
end
end