Skip to content

FEATURE: Use area key from settings to show related settings by feature #1248

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 9, 2025
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
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import { ajax } from "discourse/lib/ajax";
import DiscourseRoute from "discourse/routes/discourse";
import SiteSetting from "admin/models/site-setting";

export default class AdminPluginsShowDiscourseAiFeaturesEdit extends DiscourseRoute {
async model(params) {
const allFeatures = this.modelFor(
"adminPlugins.show.discourse-ai-features"
);
const id = parseInt(params.id, 10);
const currentFeature = allFeatures.find((feature) => feature.id === id);

return allFeatures.find((feature) => feature.id === id);
const { site_settings } = await ajax("/admin/config/site_settings.json", {
data: {
filter_area: `ai-features/${currentFeature.ref}`,
plugin: "discourse-ai",
category: "discourse_ai",
},
});

currentFeature.feature_settings = site_settings.map((setting) =>
SiteSetting.create(setting)
);

return currentFeature;
}
}
91 changes: 11 additions & 80 deletions app/controllers/discourse_ai/admin/ai_features_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,99 +6,30 @@ class AiFeaturesController < ::Admin::AdminController
requires_plugin ::DiscourseAi::PLUGIN_NAME

def index
render json: persona_backed_features
render json: serialize_features(DiscourseAi::Features.features)
end

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

def update
raise Discourse::InvalidParameters.new(:id) if params[:id].blank?
raise Discourse::InvalidParameters.new(:ai_feature) if params[:ai_feature].blank?
if params[:ai_feature][:persona_id].blank?
raise Discourse::InvalidParameters.new(:persona_id)
end
raise Discourse::InvalidParameters.new(:enabled) if params[:ai_feature][:enabled].nil?

feature = find_feature_by_id(params[:id].to_i)
enable_value = params[:ai_feature][:enabled]
persona_id = params[:ai_feature][:persona_id]

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

render json: find_feature_by_id(params[:id].to_i)
def serialize_features(features)
features.map { |feature| feature.merge(persona: serialize_persona(feature[:persona])) }
end

private
def serialize_feature(feature)
return nil if feature.blank?

# Eventually we may move this all to an active record model
# but for now we are just using a hash
# to store the features and their corresponding settings
def feature_config
[
{
id: 1,
name_key: "discourse_ai.features.summarization.name",
description_key: "discourse_ai.features.summarization.description",
persona_setting_name: "ai_summarization_persona",
enable_setting_name: "ai_summarization_enabled",
},
{
id: 2,
name_key: "discourse_ai.features.gists.name",
description_key: "discourse_ai.features.gists.description",
persona_setting_name: "ai_summary_gists_persona",
enable_setting_name: "ai_summary_gists_enabled",
},
{
id: 3,
name_key: "discourse_ai.features.discoveries.name",
description_key: "discourse_ai.features.discoveries.description",
persona_setting_name: "ai_bot_discover_persona",
enable_setting_name: "ai_bot_enabled",
},
{
id: 4,
name_key: "discourse_ai.features.discord_search.name",
description_key: "discourse_ai.features.discord_search.description",
persona_setting_name: "ai_discord_search_persona",
enable_setting_name: "ai_discord_search_enabled",
},
]
feature.merge(persona: serialize_persona(feature[:persona]))
end

def persona_backed_features
feature_config.map do |feature|
{
id: feature[:id],
name: I18n.t(feature[:name_key]),
description: I18n.t(feature[:description_key]),
persona:
serialize_data(
AiPersona.find_by(id: SiteSetting.get(feature[:persona_setting_name])),
AiFeaturesPersonaSerializer,
root: false,
),
persona_setting: {
name: feature[:persona_setting_name],
value: SiteSetting.get(feature[:persona_setting_name]),
type: SiteSetting.type_supervisor.get_type(feature[:persona_setting_name]),
},
enable_setting: {
name: feature[:enable_setting_name],
value: SiteSetting.get(feature[:enable_setting_name]),
type: SiteSetting.type_supervisor.get_type(feature[:enable_setting_name]),
},
}
end
end
def serialize_persona(persona)
return nil if persona.blank?

def find_feature_by_id(id)
lookup = persona_backed_features.index_by { |feature| feature[:id] }
lookup[id]
serialize_data(persona, AiFeaturesPersonaSerializer, root: false)
end
end
end
Expand Down
1 change: 1 addition & 0 deletions assets/javascripts/discourse/admin/models/ai-feature.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export default class AiFeature extends RestModel {
return this.getProperties(
"id",
"name",
"ref",
"description",
"enable_setting",
"persona",
Expand Down
109 changes: 8 additions & 101 deletions assets/javascripts/discourse/components/ai-feature-editor.gjs
Original file line number Diff line number Diff line change
@@ -1,67 +1,13 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { service } from "@ember/service";
import { htmlSafe } from "@ember/template";
import { eq } from "truth-helpers";
import BackButton from "discourse/components/back-button";
import Form from "discourse/components/form";
import { popupAjaxError } from "discourse/lib/ajax-error";
import getURL from "discourse/lib/get-url";
import discourseLater from "discourse/lib/later";
import { i18n } from "discourse-i18n";
import SiteSettingComponent from "admin/components/site-setting";

export default class AiFeatureEditor extends Component {
@service toasts;
@service currentUser;
@service router;

@tracked isSaving = false;

get formData() {
return {
enabled: this.args.model.enable_setting?.value,
persona_id: this.args.model.persona?.id,
};
}

@action
async save(formData) {
this.isSaving = true;

try {
this.args.model.save({
enabled: formData.enabled,
persona_id: parseInt(formData.persona_id, 10),
});

this.toasts.success({
data: {
message: i18n("discourse_ai.features.editor.saved", {
feature_name: this.args.model.name,
}),
},
duration: 2000,
});

discourseLater(() => {
this.router.transitionTo(
"adminPlugins.show.discourse-ai-features.index"
);
}, 500);
} catch (error) {
popupAjaxError(error);
} finally {
this.isSaving = false;
}
}

get personasHint() {
return i18n("discourse_ai.features.editor.persona_help", {
config_url: getURL("/admin/plugins/discourse-ai/ai-personas"),
});
}

<template>
<BackButton
@route="adminPlugins.show.discourse-ai-features"
Expand All @@ -72,51 +18,12 @@ export default class AiFeatureEditor extends Component {
<p>{{@model.description}}</p>
</section>

<Form
@onSubmit={{this.save}}
@data={{this.formData}}
class="form-horizontal ai-feature-editor"
as |form|
>
{{#if (eq @model.enable_setting.type "bool")}}
<form.Field
@name="enabled"
@title={{i18n "discourse_ai.features.editor.enable_setting"}}
@tooltip={{i18n
"discourse_ai.features.editor.enable_setting_help"
[email protected]_setting.name
}}
@validation="required"
@type="boolean"
as |field|
>
<field.Toggle />
</form.Field>
{{/if}}

<form.Field
@name="persona_id"
@title={{i18n "discourse_ai.features.editor.persona"}}
@format="large"
@helpText={{htmlSafe this.personasHint}}
@validation="required"
as |field|
>
<field.Select @includeNone={{false}} as |select|>
{{#each this.currentUser.ai_enabled_personas as |persona|}}
<select.Option @value={{persona.id}}>
{{persona.name}}
</select.Option>
{{/each}}
</field.Select>
</form.Field>

<form.Actions>
<form.Submit
@label="discourse_ai.features.editor.save"
@disabled={{this.isSaving}}
/>
</form.Actions>
</Form>
<section class="ai-feature-editor">
{{#each @model.feature_settings as |setting|}}
<div>
<SiteSettingComponent @setting={{setting}} />
</div>
{{/each}}
</section>
</template>
}
33 changes: 33 additions & 0 deletions assets/stylesheets/common/ai-features.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,36 @@
}
}
}

.ai-feature-editor {
&__header {
border-bottom: 1px solid var(--primary-low);
}

.setting {
margin-block: 1.5rem;
}

.setting-label {
font-size: var(--font-down-1-rem);
color: var(--primary-high);

a[title="View change history"],
.history-icon {
display: none;
}
}

.setting-value {
.desc {
font-size: var(--font-down-1-rem);
color: var(--primary-high-or-secondary-low);
}
}

.setting-controls,
.setting-controls__undo {
font-size: var(--font-down-1-rem);
margin-top: 0.5rem;
}
}
1 change: 1 addition & 0 deletions config/locales/client.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ en:
enable_setting_help: "Toggles '%{setting}' setting"
persona: "Persona"
persona_help: "To create/edit personas go to the <a href='%{config_url}'>persona configuration page</a>"
advanced_settings: "Advanced settings"
save: "Save"
saved: "%{feature_name} feature saved"

Expand Down
14 changes: 11 additions & 3 deletions config/locales/server.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,12 @@ en:
ai_embeddings_semantic_search_hyde_model: "Model used to expand keywords to get better results during a semantic search"
ai_embeddings_per_post_enabled: Generate embeddings for each post

ai_summarization_enabled: "Enable the topic summarization module."
ai_summarization_model: "Model to use for summarization."
ai_summarization_enabled: "Enable the summarize feature"
ai_summarization_model: "Model to use for summarization"
ai_summarization_persona: "Persona to use for summarize feature"
ai_custom_summarization_allowed_groups: "Groups allowed to use create new summaries."
ai_pm_summarization_allowed_groups: "Groups allowed to create and view summaries in PMs."
ai_summary_gists_enabled: "Generate brief summaries of latest replies in topics automatically."
ai_summary_gists_enabled: "Generate brief summaries of latest replies in topics automatically"
ai_summary_gists_allowed_groups: "Groups allowed to see gists in the hot topics list."
ai_summary_backfill_maximum_topics_per_hour: "Number of topic summaries to backfill per hour."

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

ai_discord_search_enabled: "Enables the Discord search feature"
ai_discord_app_id: "The ID of the Discord application you would like to connect Discord search to"
ai_discord_app_public_key: "The public key of the Discord application you would like to connect Discord search to"
ai_discord_search_mode: "Select the search mode to use for Discord search"
ai_discord_search_persona: "The persona to use for Discord search."
ai_discord_allowed_guilds: "Discord guilds (servers) where the bot is allowed to search"

reviewables:
reasons:
flagged_by_toxicity: The AI plugin flagged this after classifying it as toxic.
Expand Down
2 changes: 1 addition & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
end

resources :ai_features,
only: %i[index edit update],
only: %i[index edit],
path: "ai-features",
controller: "discourse_ai/admin/ai_features"
end
Expand Down
Loading