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

Commit 5e0bed1

Browse files
committed
WIP: Settings areas
1 parent aad15b4 commit 5e0bed1

File tree

6 files changed

+168
-92
lines changed

6 files changed

+168
-92
lines changed

app/controllers/discourse_ai/admin/ai_features_controller.rb

Lines changed: 14 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ class AiFeaturesController < ::Admin::AdminController
66
requires_plugin ::DiscourseAi::PLUGIN_NAME
77

88
def index
9-
render json: persona_backed_features
9+
render json: serialize_features(DiscourseAi::Features.features)
1010
end
1111

1212
def edit
1313
raise Discourse::InvalidParameters.new(:id) if params[:id].blank?
14-
render json: find_feature_by_id(params[:id].to_i)
14+
render json: serialize_feature(DiscourseAi::Features.find_feature_by_id(params[:id].to_i))
1515
end
1616

1717
def update
@@ -22,103 +22,32 @@ def update
2222
end
2323
raise Discourse::InvalidParameters.new(:enabled) if params[:ai_feature][:enabled].nil?
2424

25-
feature = find_feature_by_id(params[:id].to_i)
25+
feature = DiscourseAi::Features.find_feature_by_id(params[:id].to_i)
2626
enable_value = params[:ai_feature][:enabled]
2727
persona_id = params[:ai_feature][:persona_id]
2828

2929
SiteSetting.set_and_log(feature[:enable_setting][:name], enable_value, guardian.user)
3030
SiteSetting.set_and_log(feature[:persona_setting][:name], persona_id, guardian.user)
3131

32-
render json: find_feature_by_id(params[:id].to_i)
32+
render json: serialize_feature(DiscourseAi::Features.find_feature_by_id(params[:id].to_i))
3333
end
3434

3535
private
3636

37-
# Eventually we may move this all to an active record model
38-
# but for now we are just using a hash
39-
# to store the features and their corresponding settings
40-
def feature_config
41-
[
42-
{
43-
id: 1,
44-
name_key: "discourse_ai.features.summarization.name",
45-
description_key: "discourse_ai.features.summarization.description",
46-
persona_setting_name: "ai_summarization_persona",
47-
enable_setting_name: "ai_summarization_enabled",
48-
additional_settings: %w[
49-
ai_summary_backfill_topic_max_age_days
50-
ai_summary_backfill_maximum_topics_per_hour
51-
ai_summary_backfill_minimum_word_count
52-
ai_pm_summarization_allowed_groups
53-
],
54-
},
55-
{
56-
id: 2,
57-
name_key: "discourse_ai.features.gists.name",
58-
description_key: "discourse_ai.features.gists.description",
59-
persona_setting_name: "ai_summary_gists_persona",
60-
enable_setting_name: "ai_summary_gists_enabled",
61-
},
62-
{
63-
id: 3,
64-
name_key: "discourse_ai.features.discoveries.name",
65-
description_key: "discourse_ai.features.discoveries.description",
66-
persona_setting_name: "ai_bot_discover_persona",
67-
enable_setting_name: "ai_bot_enabled",
68-
},
69-
{
70-
id: 4,
71-
name_key: "discourse_ai.features.discord_search.name",
72-
description_key: "discourse_ai.features.discord_search.description",
73-
persona_setting_name: "ai_discord_search_persona",
74-
enable_setting_name: "ai_discord_search_enabled",
75-
additional_settings: %w[
76-
ai_discord_app_id
77-
ai_discord_app_public_key
78-
ai_discord_search_mode
79-
ai_discord_allowed_guilds
80-
],
81-
},
82-
]
37+
def serialize_features(features)
38+
features.map { |feature| feature.merge(persona: serialize_persona(feature[:persona])) }
8339
end
8440

85-
def persona_backed_features
86-
feature_config.map do |feature|
87-
{
88-
id: feature[:id],
89-
name: I18n.t(feature[:name_key]),
90-
description: I18n.t(feature[:description_key]),
91-
persona:
92-
serialize_data(
93-
AiPersona.find_by(id: SiteSetting.get(feature[:persona_setting_name])),
94-
AiFeaturesPersonaSerializer,
95-
root: false,
96-
),
97-
persona_setting: {
98-
name: feature[:persona_setting_name],
99-
value: SiteSetting.get(feature[:persona_setting_name]),
100-
type: SiteSetting.type_supervisor.get_type(feature[:persona_setting_name]),
101-
},
102-
enable_setting: {
103-
name: feature[:enable_setting_name],
104-
value: SiteSetting.get(feature[:enable_setting_name]),
105-
type: SiteSetting.type_supervisor.get_type(feature[:enable_setting_name]),
106-
},
107-
additional_settings:
108-
(feature[:additional_settings] || []).map do |setting_name|
109-
{
110-
name: setting_name,
111-
value: SiteSetting.get(setting_name),
112-
type: SiteSetting.type_supervisor.get_type(setting_name),
113-
}
114-
end,
115-
}
116-
end
41+
def serialize_feature(feature)
42+
return nil if feature.blank?
43+
44+
feature.merge(persona: serialize_persona(feature[:persona]))
11745
end
11846

119-
def find_feature_by_id(id)
120-
lookup = persona_backed_features.index_by { |feature| feature[:id] }
121-
lookup[id]
47+
def serialize_persona(persona)
48+
return nil if persona.blank?
49+
50+
serialize_data(persona, AiFeaturesPersonaSerializer, root: false)
12251
end
12352
end
12453
end

assets/javascripts/discourse/admin/models/ai-feature.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export default class AiFeature extends RestModel {
55
return this.getProperties(
66
"id",
77
"name",
8+
"ref",
89
"description",
910
"enable_setting",
1011
"persona",

assets/javascripts/discourse/components/ai-feature-editor.gjs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,36 @@ import { tracked } from "@glimmer/tracking";
33
import { action } from "@ember/object";
44
import { service } from "@ember/service";
55
import { htmlSafe } from "@ember/template";
6-
import { eq } from "truth-helpers";
6+
import { eq, gt } from "truth-helpers";
77
import BackButton from "discourse/components/back-button";
88
import Form from "discourse/components/form";
9+
import { ajax } from "discourse/lib/ajax";
910
import { popupAjaxError } from "discourse/lib/ajax-error";
1011
import getURL from "discourse/lib/get-url";
1112
import discourseLater from "discourse/lib/later";
1213
import { i18n } from "discourse-i18n";
14+
import SiteSettingComponent from "admin/components/site-setting";
15+
import SiteSetting from "admin/models/site-setting";
1316

1417
export default class AiFeatureEditor extends Component {
1518
@service toasts;
1619
@service currentUser;
1720
@service router;
1821

22+
@tracked settings = null;
23+
@tracked isLoading = false;
1924
@tracked isSaving = false;
2025

26+
constructor() {
27+
super(...arguments);
28+
this.#loadSettings();
29+
}
30+
2131
get formData() {
2232
return {
2333
enabled: this.args.model.enable_setting?.value,
2434
persona_id: this.args.model.persona?.id,
35+
additional_settings: this.args.model.additional_settings,
2536
};
2637
}
2738

@@ -62,6 +73,33 @@ export default class AiFeatureEditor extends Component {
6273
});
6374
}
6475

76+
async #loadSettings() {
77+
this.isLoading = true;
78+
79+
try {
80+
const result = await ajax("/admin/config/site_settings.json", {
81+
data: {
82+
filter_area: `ai-features/${this.args.model.ref}`,
83+
plugin: "discourse-ai",
84+
category: "discourse_ai",
85+
},
86+
});
87+
88+
const settings = result.site_settings;
89+
const settingsMap = settings.map((setting) =>
90+
SiteSetting.create(setting)
91+
);
92+
this.settings = settingsMap;
93+
94+
console.log(this.settings);
95+
} catch (error) {
96+
// eslint-disable-next-line no-console
97+
console.warn(`Failed to load settings with error: ${error}`);
98+
} finally {
99+
this.isLoading = false;
100+
}
101+
}
102+
65103
<template>
66104
<BackButton
67105
@route="adminPlugins.show.discourse-ai-features"
@@ -111,12 +149,23 @@ export default class AiFeatureEditor extends Component {
111149
</field.Select>
112150
</form.Field>
113151

152+
<form.Section
153+
@title={{i18n "discourse_ai.features.editor.advanced_settings"}}
154+
/>
155+
114156
<form.Actions>
115157
<form.Submit
116158
@label="discourse_ai.features.editor.save"
117159
@disabled={{this.isSaving}}
118160
/>
119161
</form.Actions>
120162
</Form>
163+
{{#unless this.isLoading}}
164+
{{#each this.settings as |setting|}}
165+
{{#if setting}}
166+
<SiteSettingComponent @setting={{setting}} />
167+
{{/if}}
168+
{{/each}}
169+
{{/unless}}
121170
</template>
122171
}

config/settings.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -347,32 +347,32 @@ discourse_ai:
347347
ai_discord_search_enabled:
348348
default: false
349349
client: true
350-
hidden: true
350+
area: "ai-features/discord_search"
351351
ai_discord_app_id:
352352
default: ""
353353
client: false
354-
hidden: true
354+
area: "ai-features/discord_search"
355355
ai_discord_app_public_key:
356356
default: ""
357357
client: false
358-
hidden: true
358+
area: "ai-features/discord_search"
359359
ai_discord_search_mode:
360360
default: "search"
361361
type: enum
362362
choices:
363363
- search
364364
- persona
365-
hidden: true
365+
area: "ai-features/discord_search"
366366
ai_discord_search_persona:
367367
default: ""
368368
type: enum
369369
enum: "DiscourseAi::Configuration::PersonaEnumerator"
370-
hidden: true
370+
area: "ai-features/discord_search"
371371
ai_discord_allowed_guilds:
372372
type: list
373373
list_type: compact
374374
default: ""
375-
hidden: true
375+
area: "ai-features/discord_search"
376376

377377
ai_spam_detection_enabled:
378378
default: false

lib/features.rb

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseAi
4+
module Features
5+
def self.feature_config
6+
[
7+
{
8+
id: 1,
9+
name_ref: "summarization",
10+
name_key: "discourse_ai.features.summarization.name",
11+
description_key: "discourse_ai.features.summarization.description",
12+
persona_setting_name: "ai_summarization_persona",
13+
enable_setting_name: "ai_summarization_enabled",
14+
},
15+
{
16+
id: 2,
17+
name_ref: "gists",
18+
name_key: "discourse_ai.features.gists.name",
19+
description_key: "discourse_ai.features.gists.description",
20+
persona_setting_name: "ai_summary_gists_persona",
21+
enable_setting_name: "ai_summary_gists_enabled",
22+
},
23+
{
24+
id: 3,
25+
name_ref: "discoveries",
26+
name_key: "discourse_ai.features.discoveries.name",
27+
description_key: "discourse_ai.features.discoveries.description",
28+
persona_setting_name: "ai_bot_discover_persona",
29+
enable_setting_name: "ai_bot_enabled",
30+
},
31+
{
32+
id: 4,
33+
name_ref: "discord_search",
34+
name_key: "discourse_ai.features.discord_search.name",
35+
description_key: "discourse_ai.features.discord_search.description",
36+
persona_setting_name: "ai_discord_search_persona",
37+
enable_setting_name: "ai_discord_search_enabled",
38+
},
39+
]
40+
end
41+
42+
def self.features
43+
feature_config.map do |feature|
44+
{
45+
id: feature[:id],
46+
ref: feature[:name_ref],
47+
name: I18n.t(feature[:name_key]),
48+
description: I18n.t(feature[:description_key]),
49+
persona: AiPersona.find_by(id: SiteSetting.get(feature[:persona_setting_name])),
50+
persona_setting: {
51+
name: feature[:persona_setting_name],
52+
value: SiteSetting.get(feature[:persona_setting_name]),
53+
type: SiteSetting.type_supervisor.get_type(feature[:persona_setting_name]),
54+
},
55+
enable_setting: {
56+
name: feature[:enable_setting_name],
57+
value: SiteSetting.get(feature[:enable_setting_name]),
58+
type: SiteSetting.type_supervisor.get_type(feature[:enable_setting_name]),
59+
},
60+
additional_settings:
61+
(feature[:additional_settings] || []).map do |setting_name|
62+
{
63+
name: setting_name,
64+
value: SiteSetting.get(setting_name),
65+
type: SiteSetting.type_supervisor.get_type(setting_name),
66+
}
67+
end,
68+
}
69+
end
70+
end
71+
72+
def self.find_feature_by_id(id)
73+
lookup = features.index_by { |f| f[:id] }
74+
lookup[id]
75+
end
76+
77+
def self.find_feature_by_ref(name_ref)
78+
lookup = features.index_by { |f| f[:ref] }
79+
lookup[name_ref]
80+
end
81+
82+
def self.find_feature_id_by_ref(name_ref)
83+
find_feature_by_ref(name_ref)&.dig(:id)
84+
end
85+
86+
def self.feature_area(name_ref)
87+
name_ref = name_ref.to_s if name_ref.is_a?(Symbol)
88+
find_feature_by_ref(name_ref) || raise(ArgumentError, "Feature not found: #{name_ref}")
89+
"ai-features/#{name_ref}"
90+
end
91+
end
92+
end

plugin.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ def self.public_asset_path(name)
7070
Rails.autoloaders.main.push_dir(File.join(__dir__, "lib"), namespace: ::DiscourseAi)
7171

7272
require_relative "lib/engine"
73+
require_relative "lib/features"
74+
75+
DiscourseAi::Features.feature_config.each do |feature|
76+
register_site_setting_area("ai-features/#{feature[:name_ref]}")
77+
end
7378

7479
after_initialize do
7580
if defined?(Rack::MiniProfiler)

0 commit comments

Comments
 (0)