Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 32 additions & 7 deletions app/controllers/discourse_ai/admin/ai_features_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,59 @@ class AiFeaturesController < ::Admin::AdminController
requires_plugin ::DiscourseAi::PLUGIN_NAME

def index
render json: serialize_features(DiscourseAi::Features.features)
render json: serialize_modules(DiscourseAi::Configuration::Module.all)
end

def edit
raise Discourse::InvalidParameters.new(:id) if params[:id].blank?
render json: serialize_module(DiscourseAi::Features.find_module_by_id(params[:id].to_i))

a_module = DiscourseAi::Configuration::Module.find_by(id: params[:id].to_i)

render json: serialize_module(a_module)
end

private

def serialize_features(modules)
def serialize_modules(modules)
modules.map { |a_module| serialize_module(a_module) }
end

def serialize_module(a_module)
return nil if a_module.blank?

a_module.merge(
features:
a_module[:features].map { |f| f.merge(persona: serialize_persona(f[:persona])) },
)
{
id: a_module.id,
module_name: a_module.name,
module_enabled: a_module.enabled?,
features: a_module.features.map { |f| serialize_feature(f) },
}
end

def serialize_feature(feature)
{
name: feature.name,
persona: serialize_persona(persona_id_obj_hash[feature.persona_id]),
enabled: feature.enabled?,
}
end

def serialize_persona(persona)
return nil if persona.blank?

serialize_data(persona, AiFeaturesPersonaSerializer, root: false)
end

private

def persona_id_obj_hash
@persona_id_obj_hash ||=
begin
setting_names = DiscourseAi::Configuration::Feature.all_persona_setting_names
ids = setting_names.map { |sn| SiteSetting.public_send(sn) }

AiPersona.where(id: ids).index_by(&:id)
end
end
end
end
end
4 changes: 4 additions & 0 deletions app/models/ai_persona.rb
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,10 @@ def regenerate_rag_fragments
end
end

def features
DiscourseAi::Configuration::Feature.find_features_using(persona_id: id)
end

private

def chat_preconditions
Expand Down
2 changes: 1 addition & 1 deletion app/serializers/ai_features_persona_serializer.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

class AiFeaturesPersonaSerializer < ApplicationSerializer
attributes :id, :name, :system_prompt, :allowed_groups, :enabled
attributes :id, :name, :allowed_groups

def allowed_groups
Group
Expand Down
5 changes: 5 additions & 0 deletions app/serializers/basic_llm_model_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class BasicLlmModelSerializer < ApplicationSerializer
attributes :id, :display_name
end
5 changes: 5 additions & 0 deletions app/serializers/localized_ai_persona_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class LocalizedAiPersonaSerializer < ApplicationSerializer

has_one :user, serializer: BasicUserSerializer, embed: :object
has_many :rag_uploads, serializer: UploadSerializer, embed: :object
has_one :default_llm, serializer: BasicLlmModelSerializer, embed: :object

def rag_uploads
object.uploads
Expand All @@ -48,4 +49,8 @@ def name
def description
object.class_instance.description
end

def default_llm
LlmModel.find_by(id: object.default_llm_id)
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ export default {

withPluginApi("1.1.0", (api) => {
api.addAdminPluginConfigurationNav("discourse-ai", [
{
label: "discourse_ai.features.short_title",
route: "adminPlugins.show.discourse-ai-features",
description: "discourse_ai.features.description",
},
{
label: "discourse_ai.usage.short_title",
route: "adminPlugins.show.discourse-ai-usage",
Expand Down Expand Up @@ -41,11 +46,6 @@ export default {
route: "adminPlugins.show.discourse-ai-spam",
description: "discourse_ai.spam.spam_description",
},
{
label: "discourse_ai.features.short_title",
route: "adminPlugins.show.discourse-ai-features",
description: "discourse_ai.features.description",
},
]);
});
},
Expand Down
159 changes: 159 additions & 0 deletions lib/configuration/feature.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# frozen_string_literal: true

module DiscourseAi
module Configuration
class Feature
class << self
def feature_cache
@feature_cache ||= ::DiscourseAi::MultisiteHash.new("feature_cache")
end

def summarization_features
feature_cache[:summarization] ||= [
new(
"topic_summaries",
"ai_summarization_persona",
DiscourseAi::Configuration::Module::SUMMARIZATION_ID,
DiscourseAi::Configuration::Module::SUMMARIZATION,
),
new(
"gists",
"ai_summary_gists_persona",
DiscourseAi::Configuration::Module::SUMMARIZATION_ID,
DiscourseAi::Configuration::Module::SUMMARIZATION,
enabled_by_setting: "ai_summary_gists_enabled",
),
]
end

def search_features
feature_cache[:search] ||= [
new(
"discoveries",
"ai_bot_discover_persona",
DiscourseAi::Configuration::Module::SEARCH_ID,
DiscourseAi::Configuration::Module::SEARCH,
),
]
end

def discord_features
feature_cache[:discord] ||= [
new(
"search",
"ai_discord_search_persona",
DiscourseAi::Configuration::Module::DISCORD_ID,
DiscourseAi::Configuration::Module::DISCORD,
),
]
end

def inference_features
feature_cache[:inference] ||= [
new(
"generate_concepts",
"inferred_concepts_generate_persona",
DiscourseAi::Configuration::Module::INFERENCE_ID,
DiscourseAi::Configuration::Module::INFERENCE,
),
new(
"match_concepts",
"inferred_concepts_match_persona",
DiscourseAi::Configuration::Module::INFERENCE_ID,
DiscourseAi::Configuration::Module::INFERENCE,
),
new(
"deduplicate_concepts",
"inferred_concepts_deduplicate_persona",
DiscourseAi::Configuration::Module::INFERENCE_ID,
DiscourseAi::Configuration::Module::INFERENCE,
),
]
end

def ai_helper_features
feature_cache[:ai_helper] ||= [
new(
"proofread",
"ai_helper_proofreader_persona",
DiscourseAi::Configuration::Module::AI_HELPER_ID,
DiscourseAi::Configuration::Module::AI_HELPER,
),
new(
"title_suggestions",
"ai_helper_title_suggestions_persona",
DiscourseAi::Configuration::Module::AI_HELPER_ID,
DiscourseAi::Configuration::Module::AI_HELPER,
),
new(
"explain",
"ai_helper_explain_persona",
DiscourseAi::Configuration::Module::AI_HELPER_ID,
DiscourseAi::Configuration::Module::AI_HELPER,
),
new(
"smart_dates",
"ai_helper_smart_dates_persona",
DiscourseAi::Configuration::Module::AI_HELPER_ID,
DiscourseAi::Configuration::Module::AI_HELPER,
),
new(
"markdown_tables",
"ai_helper_markdown_tables_persona",
DiscourseAi::Configuration::Module::AI_HELPER_ID,
DiscourseAi::Configuration::Module::AI_HELPER,
),
new(
"custom_prompt",
"ai_helper_custom_prompt_persona",
DiscourseAi::Configuration::Module::AI_HELPER_ID,
DiscourseAi::Configuration::Module::AI_HELPER,
),
new(
"image_caption",
"ai_helper_image_caption_persona",
DiscourseAi::Configuration::Module::AI_HELPER_ID,
DiscourseAi::Configuration::Module::AI_HELPER,
),
]
end

def all
[
summarization_features,
search_features,
discord_features,
inference_features,
ai_helper_features,
].flatten
end

def all_persona_setting_names
all.map(&:persona_setting)
end

def find_features_using(persona_id:)
all.select { |feature| feature.persona_id == persona_id }
end
end

def initialize(name, persona_setting, module_id, module_name, enabled_by_setting: "")
@name = name
@persona_setting = persona_setting
@module_id = module_id
@module_name = module_name
@enabled_by_setting = enabled_by_setting
end

attr_reader :name, :persona_setting, :module_id, :module_name

def enabled?
@enabled_by_setting.blank? || SiteSetting.get(@enabled_by_setting)
end

def persona_id
SiteSetting.get(persona_setting).to_i
end
end
end
end
75 changes: 75 additions & 0 deletions lib/configuration/module.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# frozen_string_literal: true

module DiscourseAi
module Configuration
class Module
SUMMARIZATION = "summarization"
SEARCH = "search"
DISCORD = "discord"
INFERENCE = "inference"
AI_HELPER = "ai_helper"

NAMES = [SUMMARIZATION, SEARCH, DISCORD, INFERENCE, AI_HELPER]

SUMMARIZATION_ID = 1
SEARCH_ID = 2
DISCORD_ID = 3
INFERENCE_ID = 4
AI_HELPER_ID = 5

class << self
def all
[
new(
SUMMARIZATION_ID,
SUMMARIZATION,
"ai_summarization_enabled",
features: DiscourseAi::Configuration::Feature.summarization_features,
),
new(
SEARCH_ID,
SEARCH,
"ai_bot_enabled",
features: DiscourseAi::Configuration::Feature.search_features,
),
new(
DISCORD_ID,
DISCORD,
"ai_discord_search_enabled",
features: DiscourseAi::Configuration::Feature.discord_features,
),
new(
INFERENCE_ID,
INFERENCE,
"inferred_concepts_enabled",
features: DiscourseAi::Configuration::Feature.inference_features,
),
new(
AI_HELPER_ID,
AI_HELPER,
"ai_helper_enabled",
features: DiscourseAi::Configuration::Feature.ai_helper_features,
),
]
end

def find_by(id:)
all.find { |m| m.id == id }
end
end

def initialize(id, name, enabled_by_setting, features: [])
@id = id
@name = name
@enabled_by_setting = enabled_by_setting
@features = features
end

attr_reader :id, :name, :enabled_by_setting, :features

def enabled?
SiteSetting.get(enabled_by_setting)
end
end
end
end
Loading
Loading