Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.

Commit f7e0ea8

Browse files
authored
DEV: Use a PORO to represent modules/features. (#1421)
Additional changes: Adds a "#features" method in AiPersona to find which features are using that persona. Serializes a basic version of a LlmModel in the persona's "#default_llm" serializer attribute.
1 parent b54db13 commit f7e0ea8

File tree

11 files changed

+289
-126
lines changed

11 files changed

+289
-126
lines changed

app/controllers/discourse_ai/admin/ai_features_controller.rb

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,59 @@ class AiFeaturesController < ::Admin::AdminController
66
requires_plugin ::DiscourseAi::PLUGIN_NAME
77

88
def index
9-
render json: serialize_features(DiscourseAi::Features.features)
9+
render json: serialize_modules(DiscourseAi::Configuration::Module.all)
1010
end
1111

1212
def edit
1313
raise Discourse::InvalidParameters.new(:id) if params[:id].blank?
14-
render json: serialize_module(DiscourseAi::Features.find_module_by_id(params[:id].to_i))
14+
15+
a_module = DiscourseAi::Configuration::Module.find_by(id: params[:id].to_i)
16+
17+
render json: serialize_module(a_module)
1518
end
1619

1720
private
1821

19-
def serialize_features(modules)
22+
def serialize_modules(modules)
2023
modules.map { |a_module| serialize_module(a_module) }
2124
end
2225

2326
def serialize_module(a_module)
2427
return nil if a_module.blank?
2528

26-
a_module.merge(
27-
features:
28-
a_module[:features].map { |f| f.merge(persona: serialize_persona(f[:persona])) },
29-
)
29+
{
30+
id: a_module.id,
31+
module_name: a_module.name,
32+
module_enabled: a_module.enabled?,
33+
features: a_module.features.map { |f| serialize_feature(f) },
34+
}
35+
end
36+
37+
def serialize_feature(feature)
38+
{
39+
name: feature.name,
40+
persona: serialize_persona(persona_id_obj_hash[feature.persona_id]),
41+
enabled: feature.enabled?,
42+
}
3043
end
3144

3245
def serialize_persona(persona)
3346
return nil if persona.blank?
3447

3548
serialize_data(persona, AiFeaturesPersonaSerializer, root: false)
3649
end
50+
51+
private
52+
53+
def persona_id_obj_hash
54+
@persona_id_obj_hash ||=
55+
begin
56+
setting_names = DiscourseAi::Configuration::Feature.all_persona_setting_names
57+
ids = setting_names.map { |sn| SiteSetting.public_send(sn) }
58+
59+
AiPersona.where(id: ids).index_by(&:id)
60+
end
61+
end
3762
end
3863
end
3964
end

app/models/ai_persona.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,10 @@ def regenerate_rag_fragments
316316
end
317317
end
318318

319+
def features
320+
DiscourseAi::Configuration::Feature.find_features_using(persona_id: id)
321+
end
322+
319323
private
320324

321325
def chat_preconditions

app/serializers/ai_features_persona_serializer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

33
class AiFeaturesPersonaSerializer < ApplicationSerializer
4-
attributes :id, :name, :system_prompt, :allowed_groups, :enabled
4+
attributes :id, :name, :allowed_groups
55

66
def allowed_groups
77
Group
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# frozen_string_literal: true
2+
3+
class BasicLlmModelSerializer < ApplicationSerializer
4+
attributes :id, :display_name
5+
end

app/serializers/localized_ai_persona_serializer.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class LocalizedAiPersonaSerializer < ApplicationSerializer
3636

3737
has_one :user, serializer: BasicUserSerializer, embed: :object
3838
has_many :rag_uploads, serializer: UploadSerializer, embed: :object
39+
has_one :default_llm, serializer: BasicLlmModelSerializer, embed: :object
3940

4041
def rag_uploads
4142
object.uploads
@@ -48,4 +49,8 @@ def name
4849
def description
4950
object.class_instance.description
5051
end
52+
53+
def default_llm
54+
LlmModel.find_by(id: object.default_llm_id)
55+
end
5156
end

assets/javascripts/initializers/admin-plugin-configuration-nav.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ export default {
1111

1212
withPluginApi("1.1.0", (api) => {
1313
api.addAdminPluginConfigurationNav("discourse-ai", [
14+
{
15+
label: "discourse_ai.features.short_title",
16+
route: "adminPlugins.show.discourse-ai-features",
17+
description: "discourse_ai.features.description",
18+
},
1419
{
1520
label: "discourse_ai.usage.short_title",
1621
route: "adminPlugins.show.discourse-ai-usage",
@@ -41,11 +46,6 @@ export default {
4146
route: "adminPlugins.show.discourse-ai-spam",
4247
description: "discourse_ai.spam.spam_description",
4348
},
44-
{
45-
label: "discourse_ai.features.short_title",
46-
route: "adminPlugins.show.discourse-ai-features",
47-
description: "discourse_ai.features.description",
48-
},
4949
]);
5050
});
5151
},

lib/configuration/feature.rb

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseAi
4+
module Configuration
5+
class Feature
6+
class << self
7+
def feature_cache
8+
@feature_cache ||= ::DiscourseAi::MultisiteHash.new("feature_cache")
9+
end
10+
11+
def summarization_features
12+
feature_cache[:summarization] ||= [
13+
new(
14+
"topic_summaries",
15+
"ai_summarization_persona",
16+
DiscourseAi::Configuration::Module::SUMMARIZATION_ID,
17+
DiscourseAi::Configuration::Module::SUMMARIZATION,
18+
),
19+
new(
20+
"gists",
21+
"ai_summary_gists_persona",
22+
DiscourseAi::Configuration::Module::SUMMARIZATION_ID,
23+
DiscourseAi::Configuration::Module::SUMMARIZATION,
24+
enabled_by_setting: "ai_summary_gists_enabled",
25+
),
26+
]
27+
end
28+
29+
def search_features
30+
feature_cache[:search] ||= [
31+
new(
32+
"discoveries",
33+
"ai_bot_discover_persona",
34+
DiscourseAi::Configuration::Module::SEARCH_ID,
35+
DiscourseAi::Configuration::Module::SEARCH,
36+
),
37+
]
38+
end
39+
40+
def discord_features
41+
feature_cache[:discord] ||= [
42+
new(
43+
"search",
44+
"ai_discord_search_persona",
45+
DiscourseAi::Configuration::Module::DISCORD_ID,
46+
DiscourseAi::Configuration::Module::DISCORD,
47+
),
48+
]
49+
end
50+
51+
def inference_features
52+
feature_cache[:inference] ||= [
53+
new(
54+
"generate_concepts",
55+
"inferred_concepts_generate_persona",
56+
DiscourseAi::Configuration::Module::INFERENCE_ID,
57+
DiscourseAi::Configuration::Module::INFERENCE,
58+
),
59+
new(
60+
"match_concepts",
61+
"inferred_concepts_match_persona",
62+
DiscourseAi::Configuration::Module::INFERENCE_ID,
63+
DiscourseAi::Configuration::Module::INFERENCE,
64+
),
65+
new(
66+
"deduplicate_concepts",
67+
"inferred_concepts_deduplicate_persona",
68+
DiscourseAi::Configuration::Module::INFERENCE_ID,
69+
DiscourseAi::Configuration::Module::INFERENCE,
70+
),
71+
]
72+
end
73+
74+
def ai_helper_features
75+
feature_cache[:ai_helper] ||= [
76+
new(
77+
"proofread",
78+
"ai_helper_proofreader_persona",
79+
DiscourseAi::Configuration::Module::AI_HELPER_ID,
80+
DiscourseAi::Configuration::Module::AI_HELPER,
81+
),
82+
new(
83+
"title_suggestions",
84+
"ai_helper_title_suggestions_persona",
85+
DiscourseAi::Configuration::Module::AI_HELPER_ID,
86+
DiscourseAi::Configuration::Module::AI_HELPER,
87+
),
88+
new(
89+
"explain",
90+
"ai_helper_explain_persona",
91+
DiscourseAi::Configuration::Module::AI_HELPER_ID,
92+
DiscourseAi::Configuration::Module::AI_HELPER,
93+
),
94+
new(
95+
"smart_dates",
96+
"ai_helper_smart_dates_persona",
97+
DiscourseAi::Configuration::Module::AI_HELPER_ID,
98+
DiscourseAi::Configuration::Module::AI_HELPER,
99+
),
100+
new(
101+
"markdown_tables",
102+
"ai_helper_markdown_tables_persona",
103+
DiscourseAi::Configuration::Module::AI_HELPER_ID,
104+
DiscourseAi::Configuration::Module::AI_HELPER,
105+
),
106+
new(
107+
"custom_prompt",
108+
"ai_helper_custom_prompt_persona",
109+
DiscourseAi::Configuration::Module::AI_HELPER_ID,
110+
DiscourseAi::Configuration::Module::AI_HELPER,
111+
),
112+
new(
113+
"image_caption",
114+
"ai_helper_image_caption_persona",
115+
DiscourseAi::Configuration::Module::AI_HELPER_ID,
116+
DiscourseAi::Configuration::Module::AI_HELPER,
117+
),
118+
]
119+
end
120+
121+
def all
122+
[
123+
summarization_features,
124+
search_features,
125+
discord_features,
126+
inference_features,
127+
ai_helper_features,
128+
].flatten
129+
end
130+
131+
def all_persona_setting_names
132+
all.map(&:persona_setting)
133+
end
134+
135+
def find_features_using(persona_id:)
136+
all.select { |feature| feature.persona_id == persona_id }
137+
end
138+
end
139+
140+
def initialize(name, persona_setting, module_id, module_name, enabled_by_setting: "")
141+
@name = name
142+
@persona_setting = persona_setting
143+
@module_id = module_id
144+
@module_name = module_name
145+
@enabled_by_setting = enabled_by_setting
146+
end
147+
148+
attr_reader :name, :persona_setting, :module_id, :module_name
149+
150+
def enabled?
151+
@enabled_by_setting.blank? || SiteSetting.get(@enabled_by_setting)
152+
end
153+
154+
def persona_id
155+
SiteSetting.get(persona_setting).to_i
156+
end
157+
end
158+
end
159+
end

lib/configuration/module.rb

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseAi
4+
module Configuration
5+
class Module
6+
SUMMARIZATION = "summarization"
7+
SEARCH = "search"
8+
DISCORD = "discord"
9+
INFERENCE = "inference"
10+
AI_HELPER = "ai_helper"
11+
12+
NAMES = [SUMMARIZATION, SEARCH, DISCORD, INFERENCE, AI_HELPER]
13+
14+
SUMMARIZATION_ID = 1
15+
SEARCH_ID = 2
16+
DISCORD_ID = 3
17+
INFERENCE_ID = 4
18+
AI_HELPER_ID = 5
19+
20+
class << self
21+
def all
22+
[
23+
new(
24+
SUMMARIZATION_ID,
25+
SUMMARIZATION,
26+
"ai_summarization_enabled",
27+
features: DiscourseAi::Configuration::Feature.summarization_features,
28+
),
29+
new(
30+
SEARCH_ID,
31+
SEARCH,
32+
"ai_bot_enabled",
33+
features: DiscourseAi::Configuration::Feature.search_features,
34+
),
35+
new(
36+
DISCORD_ID,
37+
DISCORD,
38+
"ai_discord_search_enabled",
39+
features: DiscourseAi::Configuration::Feature.discord_features,
40+
),
41+
new(
42+
INFERENCE_ID,
43+
INFERENCE,
44+
"inferred_concepts_enabled",
45+
features: DiscourseAi::Configuration::Feature.inference_features,
46+
),
47+
new(
48+
AI_HELPER_ID,
49+
AI_HELPER,
50+
"ai_helper_enabled",
51+
features: DiscourseAi::Configuration::Feature.ai_helper_features,
52+
),
53+
]
54+
end
55+
56+
def find_by(id:)
57+
all.find { |m| m.id == id }
58+
end
59+
end
60+
61+
def initialize(id, name, enabled_by_setting, features: [])
62+
@id = id
63+
@name = name
64+
@enabled_by_setting = enabled_by_setting
65+
@features = features
66+
end
67+
68+
attr_reader :id, :name, :enabled_by_setting, :features
69+
70+
def enabled?
71+
SiteSetting.get(enabled_by_setting)
72+
end
73+
end
74+
end
75+
end

0 commit comments

Comments
 (0)