Skip to content

Commit 19f6c6c

Browse files
authored
FEATURE: Use area key from settings to show related settings by feature (#1248)
This update makes use of the `area` key in `settings.yml` to show all settings related to particular features inside their feature tab editor. This allows admins to easily configure features and parse through a variety of different settings.
1 parent 0963a6a commit 19f6c6c

File tree

13 files changed

+189
-224
lines changed

13 files changed

+189
-224
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
1+
import { ajax } from "discourse/lib/ajax";
12
import DiscourseRoute from "discourse/routes/discourse";
3+
import SiteSetting from "admin/models/site-setting";
24

35
export default class AdminPluginsShowDiscourseAiFeaturesEdit extends DiscourseRoute {
46
async model(params) {
57
const allFeatures = this.modelFor(
68
"adminPlugins.show.discourse-ai-features"
79
);
810
const id = parseInt(params.id, 10);
11+
const currentFeature = allFeatures.find((feature) => feature.id === id);
912

10-
return allFeatures.find((feature) => feature.id === id);
13+
const { site_settings } = await ajax("/admin/config/site_settings.json", {
14+
data: {
15+
filter_area: `ai-features/${currentFeature.ref}`,
16+
plugin: "discourse-ai",
17+
category: "discourse_ai",
18+
},
19+
});
20+
21+
currentFeature.feature_settings = site_settings.map((setting) =>
22+
SiteSetting.create(setting)
23+
);
24+
25+
return currentFeature;
1126
}
1227
}

app/controllers/discourse_ai/admin/ai_features_controller.rb

+11-80
Original file line numberDiff line numberDiff line change
@@ -6,99 +6,30 @@ 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

17-
def update
18-
raise Discourse::InvalidParameters.new(:id) if params[:id].blank?
19-
raise Discourse::InvalidParameters.new(:ai_feature) if params[:ai_feature].blank?
20-
if params[:ai_feature][:persona_id].blank?
21-
raise Discourse::InvalidParameters.new(:persona_id)
22-
end
23-
raise Discourse::InvalidParameters.new(:enabled) if params[:ai_feature][:enabled].nil?
24-
25-
feature = find_feature_by_id(params[:id].to_i)
26-
enable_value = params[:ai_feature][:enabled]
27-
persona_id = params[:ai_feature][:persona_id]
28-
29-
SiteSetting.set_and_log(feature[:enable_setting][:name], enable_value, guardian.user)
30-
SiteSetting.set_and_log(feature[:persona_setting][:name], persona_id, guardian.user)
17+
private
3118

32-
render json: find_feature_by_id(params[:id].to_i)
19+
def serialize_features(features)
20+
features.map { |feature| feature.merge(persona: serialize_persona(feature[:persona])) }
3321
end
3422

35-
private
23+
def serialize_feature(feature)
24+
return nil if feature.blank?
3625

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-
},
49-
{
50-
id: 2,
51-
name_key: "discourse_ai.features.gists.name",
52-
description_key: "discourse_ai.features.gists.description",
53-
persona_setting_name: "ai_summary_gists_persona",
54-
enable_setting_name: "ai_summary_gists_enabled",
55-
},
56-
{
57-
id: 3,
58-
name_key: "discourse_ai.features.discoveries.name",
59-
description_key: "discourse_ai.features.discoveries.description",
60-
persona_setting_name: "ai_bot_discover_persona",
61-
enable_setting_name: "ai_bot_enabled",
62-
},
63-
{
64-
id: 4,
65-
name_key: "discourse_ai.features.discord_search.name",
66-
description_key: "discourse_ai.features.discord_search.description",
67-
persona_setting_name: "ai_discord_search_persona",
68-
enable_setting_name: "ai_discord_search_enabled",
69-
},
70-
]
26+
feature.merge(persona: serialize_persona(feature[:persona]))
7127
end
7228

73-
def persona_backed_features
74-
feature_config.map do |feature|
75-
{
76-
id: feature[:id],
77-
name: I18n.t(feature[:name_key]),
78-
description: I18n.t(feature[:description_key]),
79-
persona:
80-
serialize_data(
81-
AiPersona.find_by(id: SiteSetting.get(feature[:persona_setting_name])),
82-
AiFeaturesPersonaSerializer,
83-
root: false,
84-
),
85-
persona_setting: {
86-
name: feature[:persona_setting_name],
87-
value: SiteSetting.get(feature[:persona_setting_name]),
88-
type: SiteSetting.type_supervisor.get_type(feature[:persona_setting_name]),
89-
},
90-
enable_setting: {
91-
name: feature[:enable_setting_name],
92-
value: SiteSetting.get(feature[:enable_setting_name]),
93-
type: SiteSetting.type_supervisor.get_type(feature[:enable_setting_name]),
94-
},
95-
}
96-
end
97-
end
29+
def serialize_persona(persona)
30+
return nil if persona.blank?
9831

99-
def find_feature_by_id(id)
100-
lookup = persona_backed_features.index_by { |feature| feature[:id] }
101-
lookup[id]
32+
serialize_data(persona, AiFeaturesPersonaSerializer, root: false)
10233
end
10334
end
10435
end

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

+1
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",
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,13 @@
11
import Component from "@glimmer/component";
2-
import { tracked } from "@glimmer/tracking";
3-
import { action } from "@ember/object";
42
import { service } from "@ember/service";
5-
import { htmlSafe } from "@ember/template";
6-
import { eq } from "truth-helpers";
73
import BackButton from "discourse/components/back-button";
8-
import Form from "discourse/components/form";
9-
import { popupAjaxError } from "discourse/lib/ajax-error";
10-
import getURL from "discourse/lib/get-url";
11-
import discourseLater from "discourse/lib/later";
12-
import { i18n } from "discourse-i18n";
4+
import SiteSettingComponent from "admin/components/site-setting";
135

146
export default class AiFeatureEditor extends Component {
157
@service toasts;
168
@service currentUser;
179
@service router;
1810

19-
@tracked isSaving = false;
20-
21-
get formData() {
22-
return {
23-
enabled: this.args.model.enable_setting?.value,
24-
persona_id: this.args.model.persona?.id,
25-
};
26-
}
27-
28-
@action
29-
async save(formData) {
30-
this.isSaving = true;
31-
32-
try {
33-
this.args.model.save({
34-
enabled: formData.enabled,
35-
persona_id: parseInt(formData.persona_id, 10),
36-
});
37-
38-
this.toasts.success({
39-
data: {
40-
message: i18n("discourse_ai.features.editor.saved", {
41-
feature_name: this.args.model.name,
42-
}),
43-
},
44-
duration: 2000,
45-
});
46-
47-
discourseLater(() => {
48-
this.router.transitionTo(
49-
"adminPlugins.show.discourse-ai-features.index"
50-
);
51-
}, 500);
52-
} catch (error) {
53-
popupAjaxError(error);
54-
} finally {
55-
this.isSaving = false;
56-
}
57-
}
58-
59-
get personasHint() {
60-
return i18n("discourse_ai.features.editor.persona_help", {
61-
config_url: getURL("/admin/plugins/discourse-ai/ai-personas"),
62-
});
63-
}
64-
6511
<template>
6612
<BackButton
6713
@route="adminPlugins.show.discourse-ai-features"
@@ -72,51 +18,12 @@ export default class AiFeatureEditor extends Component {
7218
<p>{{@model.description}}</p>
7319
</section>
7420

75-
<Form
76-
@onSubmit={{this.save}}
77-
@data={{this.formData}}
78-
class="form-horizontal ai-feature-editor"
79-
as |form|
80-
>
81-
{{#if (eq @model.enable_setting.type "bool")}}
82-
<form.Field
83-
@name="enabled"
84-
@title={{i18n "discourse_ai.features.editor.enable_setting"}}
85-
@tooltip={{i18n
86-
"discourse_ai.features.editor.enable_setting_help"
87-
setting=@model.enable_setting.name
88-
}}
89-
@validation="required"
90-
@type="boolean"
91-
as |field|
92-
>
93-
<field.Toggle />
94-
</form.Field>
95-
{{/if}}
96-
97-
<form.Field
98-
@name="persona_id"
99-
@title={{i18n "discourse_ai.features.editor.persona"}}
100-
@format="large"
101-
@helpText={{htmlSafe this.personasHint}}
102-
@validation="required"
103-
as |field|
104-
>
105-
<field.Select @includeNone={{false}} as |select|>
106-
{{#each this.currentUser.ai_enabled_personas as |persona|}}
107-
<select.Option @value={{persona.id}}>
108-
{{persona.name}}
109-
</select.Option>
110-
{{/each}}
111-
</field.Select>
112-
</form.Field>
113-
114-
<form.Actions>
115-
<form.Submit
116-
@label="discourse_ai.features.editor.save"
117-
@disabled={{this.isSaving}}
118-
/>
119-
</form.Actions>
120-
</Form>
21+
<section class="ai-feature-editor">
22+
{{#each @model.feature_settings as |setting|}}
23+
<div>
24+
<SiteSettingComponent @setting={{setting}} />
25+
</div>
26+
{{/each}}
27+
</section>
12128
</template>
12229
}

assets/stylesheets/common/ai-features.scss

+33
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,36 @@
3030
}
3131
}
3232
}
33+
34+
.ai-feature-editor {
35+
&__header {
36+
border-bottom: 1px solid var(--primary-low);
37+
}
38+
39+
.setting {
40+
margin-block: 1.5rem;
41+
}
42+
43+
.setting-label {
44+
font-size: var(--font-down-1-rem);
45+
color: var(--primary-high);
46+
47+
a[title="View change history"],
48+
.history-icon {
49+
display: none;
50+
}
51+
}
52+
53+
.setting-value {
54+
.desc {
55+
font-size: var(--font-down-1-rem);
56+
color: var(--primary-high-or-secondary-low);
57+
}
58+
}
59+
60+
.setting-controls,
61+
.setting-controls__undo {
62+
font-size: var(--font-down-1-rem);
63+
margin-top: 0.5rem;
64+
}
65+
}

config/locales/client.en.yml

+1
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ en:
182182
enable_setting_help: "Toggles '%{setting}' setting"
183183
persona: "Persona"
184184
persona_help: "To create/edit personas go to the <a href='%{config_url}'>persona configuration page</a>"
185+
advanced_settings: "Advanced settings"
185186
save: "Save"
186187
saved: "%{feature_name} feature saved"
187188

config/locales/server.en.yml

+11-3
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,12 @@ en:
8181
ai_embeddings_semantic_search_hyde_model: "Model used to expand keywords to get better results during a semantic search"
8282
ai_embeddings_per_post_enabled: Generate embeddings for each post
8383

84-
ai_summarization_enabled: "Enable the topic summarization module."
85-
ai_summarization_model: "Model to use for summarization."
84+
ai_summarization_enabled: "Enable the summarize feature"
85+
ai_summarization_model: "Model to use for summarization"
86+
ai_summarization_persona: "Persona to use for summarize feature"
8687
ai_custom_summarization_allowed_groups: "Groups allowed to use create new summaries."
8788
ai_pm_summarization_allowed_groups: "Groups allowed to create and view summaries in PMs."
88-
ai_summary_gists_enabled: "Generate brief summaries of latest replies in topics automatically."
89+
ai_summary_gists_enabled: "Generate brief summaries of latest replies in topics automatically"
8990
ai_summary_gists_allowed_groups: "Groups allowed to see gists in the hot topics list."
9091
ai_summary_backfill_maximum_topics_per_hour: "Number of topic summaries to backfill per hour."
9192

@@ -104,6 +105,13 @@ en:
104105
ai_google_custom_search_api_key: "API key for the Google Custom Search API see: https://developers.google.com/custom-search"
105106
ai_google_custom_search_cx: "CX for Google Custom Search API"
106107

108+
ai_discord_search_enabled: "Enables the Discord search feature"
109+
ai_discord_app_id: "The ID of the Discord application you would like to connect Discord search to"
110+
ai_discord_app_public_key: "The public key of the Discord application you would like to connect Discord search to"
111+
ai_discord_search_mode: "Select the search mode to use for Discord search"
112+
ai_discord_search_persona: "The persona to use for Discord search."
113+
ai_discord_allowed_guilds: "Discord guilds (servers) where the bot is allowed to search"
114+
107115
reviewables:
108116
reasons:
109117
flagged_by_toxicity: The AI plugin flagged this after classifying it as toxic.

config/routes.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
end
113113

114114
resources :ai_features,
115-
only: %i[index edit update],
115+
only: %i[index edit],
116116
path: "ai-features",
117117
controller: "discourse_ai/admin/ai_features"
118118
end

0 commit comments

Comments
 (0)