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

Commit db08849

Browse files
committed
Merge branch 'main' into dev/glimmer-post-stream
2 parents b5d09a4 + d2002f8 commit db08849

File tree

296 files changed

+7793
-3248
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

296 files changed

+7793
-3248
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ node_modules
55
evals/log
66
evals/cases
77
config/eval-llms.local.yml
8+
# this gets rid of search results from ag, ripgrep, etc
9+
tokenizers/
10+
public/ai-share/highlight.min.js
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { ajax } from "discourse/lib/ajax";
2+
import DiscourseRoute from "discourse/routes/discourse";
3+
import SiteSetting from "admin/models/site-setting";
4+
5+
export default class AdminPluginsShowDiscourseAiFeaturesEdit extends DiscourseRoute {
6+
async model(params) {
7+
const allFeatures = this.modelFor(
8+
"adminPlugins.show.discourse-ai-features"
9+
);
10+
const id = parseInt(params.id, 10);
11+
const currentFeature = allFeatures.find((feature) => feature.id === id);
12+
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;
26+
}
27+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { service } from "@ember/service";
2+
import DiscourseRoute from "discourse/routes/discourse";
3+
4+
export default class AdminPluginsShowDiscourseAiFeatures extends DiscourseRoute {
5+
@service store;
6+
7+
async model() {
8+
return this.store.findAll("ai-feature");
9+
}
10+
}

admin/assets/javascripts/discourse/routes/admin-plugins-show-discourse-ai-llms-edit.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,17 @@ import DiscourseRoute from "discourse/routes/discourse";
22

33
export default class AdminPluginsShowDiscourseAiLlmsEdit extends DiscourseRoute {
44
async model(params) {
5-
const allLlms = this.modelFor("adminPlugins.show.discourse-ai-llms");
65
const id = parseInt(params.id, 10);
6+
7+
if (id < 0) {
8+
// You shouldn't be able to access the edit page
9+
// if the model is seeded
10+
return this.router.transitionTo(
11+
"adminPlugins.show.discourse-ai-llms.index"
12+
);
13+
}
14+
15+
const allLlms = this.modelFor("adminPlugins.show.discourse-ai-llms");
716
const record = allLlms.findBy("id", id);
817
record.provider_params = record.provider_params || {};
918
return record;
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import DiscourseRoute from "discourse/routes/discourse";
22

33
export default class DiscourseAiToolsNewRoute extends DiscourseRoute {
4+
beforeModel(transition) {
5+
this.preset = transition.to.queryParams.presetId || "empty_tool";
6+
}
7+
48
async model() {
59
return this.store.createRecord("ai-tool");
610
}
711

812
setupController(controller) {
913
super.setupController(...arguments);
1014
const toolsModel = this.modelFor("adminPlugins.show.discourse-ai-tools");
11-
1215
controller.set("allTools", toolsModel);
1316
controller.set("presets", toolsModel.resultSetMeta.presets);
1417
controller.set("llms", toolsModel.resultSetMeta.llms);
1518
controller.set("settings", toolsModel.resultSetMeta.settings);
19+
controller.set("selectedPreset", this.preset);
1620
}
1721
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import RouteTemplate from "ember-route-template";
2+
import BackButton from "discourse/components/back-button";
3+
import SiteSettingComponent from "admin/components/site-setting";
4+
5+
export default RouteTemplate(
6+
<template>
7+
<BackButton
8+
@route="adminPlugins.show.discourse-ai-features"
9+
@label="discourse_ai.features.back"
10+
/>
11+
<section class="ai-feature-editor__header">
12+
<h2>{{@model.name}}</h2>
13+
<p>{{@model.description}}</p>
14+
</section>
15+
16+
<section class="ai-feature-editor">
17+
{{#each @model.feature_settings as |setting|}}
18+
<div>
19+
<SiteSettingComponent @setting={{setting}} />
20+
</div>
21+
{{/each}}
22+
</section>
23+
</template>
24+
);
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import Component from "@glimmer/component";
2+
import { service } from "@ember/service";
3+
import RouteTemplate from "ember-route-template";
4+
import { gt } from "truth-helpers";
5+
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
6+
import DButton from "discourse/components/d-button";
7+
import DPageSubheader from "discourse/components/d-page-subheader";
8+
import { i18n } from "discourse-i18n";
9+
10+
export default RouteTemplate(
11+
class extends Component {
12+
@service adminPluginNavManager;
13+
14+
get tableHeaders() {
15+
const prefix = "discourse_ai.features.list.header";
16+
return [
17+
i18n(`${prefix}.name`),
18+
i18n(`${prefix}.persona`),
19+
i18n(`${prefix}.groups`),
20+
"",
21+
];
22+
}
23+
24+
get configuredFeatures() {
25+
return this.args.model.filter(
26+
(feature) => feature.enable_setting.value === true
27+
);
28+
}
29+
30+
get unconfiguredFeatures() {
31+
return this.args.model.filter(
32+
(feature) => feature.enable_setting.value === false
33+
);
34+
}
35+
36+
<template>
37+
<DBreadcrumbsItem
38+
@path="/admin/plugins/{{this.adminPluginNavManager.currentPlugin.name}}/ai-features"
39+
@label={{i18n "discourse_ai.features.short_title"}}
40+
/>
41+
<section class="ai-feature-list admin-detail">
42+
<DPageSubheader
43+
@titleLabel={{i18n "discourse_ai.features.short_title"}}
44+
@descriptionLabel={{i18n "discourse_ai.features.description"}}
45+
@learnMoreUrl="todo"
46+
/>
47+
48+
{{#if (gt this.configuredFeatures.length 0)}}
49+
<div class="ai-feature-list__configured-features">
50+
<h3>{{i18n "discourse_ai.features.list.configured_features"}}</h3>
51+
52+
<table class="d-admin-table">
53+
<thead>
54+
<tr>
55+
{{#each this.tableHeaders as |header|}}
56+
<th>{{header}}</th>
57+
{{/each}}
58+
</tr>
59+
</thead>
60+
61+
<tbody>
62+
{{#each this.configuredFeatures as |feature|}}
63+
<tr
64+
class="ai-feature-list__row d-admin-row__content"
65+
data-feature-name={{feature.name}}
66+
>
67+
<td class="d-admin-row__overview ai-feature-list__row-item">
68+
<span class="ai-feature-list__row-item-name">
69+
<strong>
70+
{{feature.name}}
71+
</strong>
72+
</span>
73+
<span class="ai-feature-list__row-item-description">
74+
{{feature.description}}
75+
</span>
76+
</td>
77+
<td
78+
class="d-admin-row__detail ai-feature-list__row-item ai-feature-list__persona"
79+
>
80+
<DButton
81+
class="btn-flat btn-small ai-feature-list__row-item-persona"
82+
@translatedLabel={{feature.persona.name}}
83+
@route="adminPlugins.show.discourse-ai-personas.edit"
84+
@routeModels={{feature.persona.id}}
85+
/>
86+
</td>
87+
<td
88+
class="d-admin-row__detail ai-feature-list__row-item ai-feature-list__groups"
89+
>
90+
{{#if (gt feature.persona.allowed_groups.length 0)}}
91+
<ul class="ai-feature-list__row-item-groups">
92+
{{#each feature.persona.allowed_groups as |group|}}
93+
<li>{{group.name}}</li>
94+
{{/each}}
95+
</ul>
96+
{{/if}}
97+
</td>
98+
<td class="d-admin-row_controls">
99+
<DButton
100+
class="btn-small edit"
101+
@label="discourse_ai.features.list.edit"
102+
@route="adminPlugins.show.discourse-ai-features.edit"
103+
@routeModels={{feature.id}}
104+
/>
105+
</td>
106+
</tr>
107+
{{/each}}
108+
</tbody>
109+
</table>
110+
</div>
111+
{{/if}}
112+
113+
{{#if (gt this.unconfiguredFeatures.length 0)}}
114+
<div class="ai-feature-list__unconfigured-features">
115+
<h3>{{i18n "discourse_ai.features.list.unconfigured_features"}}</h3>
116+
117+
<table class="d-admin-table">
118+
<thead>
119+
<tr>
120+
<th>{{i18n "discourse_ai.features.list.header.name"}}</th>
121+
<th></th>
122+
</tr>
123+
</thead>
124+
125+
<tbody>
126+
{{#each this.unconfiguredFeatures as |feature|}}
127+
<tr class="ai-feature-list__row d-admin-row__content">
128+
<td class="d-admin-row__overview ai-feature-list__row-item">
129+
<span class="ai-feature-list__row-item-name">
130+
<strong>
131+
{{feature.name}}
132+
</strong>
133+
</span>
134+
<span class="ai-feature-list__row-item-description">
135+
{{feature.description}}
136+
</span>
137+
</td>
138+
139+
<td class="d-admin-row_controls">
140+
<DButton
141+
class="btn-small"
142+
@label="discourse_ai.features.list.set_up"
143+
@route="adminPlugins.show.discourse-ai-features.edit"
144+
@routeModels={{feature.id}}
145+
/>
146+
</td>
147+
</tr>
148+
{{/each}}
149+
</tbody>
150+
</table>
151+
</div>
152+
{{/if}}
153+
</section>
154+
</template>
155+
}
156+
);

admin/assets/javascripts/discourse/templates/admin-plugins/show/discourse-ai-tools/new.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
@presets={{this.presets}}
66
@llms={{this.llms}}
77
@settings={{this.settings}}
8+
@selectedPreset={{this.selectedPreset}}
89
/>
910
</section>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseAi
4+
module Admin
5+
class AiFeaturesController < ::Admin::AdminController
6+
requires_plugin ::DiscourseAi::PLUGIN_NAME
7+
8+
def index
9+
render json: serialize_features(DiscourseAi::Features.features)
10+
end
11+
12+
def edit
13+
raise Discourse::InvalidParameters.new(:id) if params[:id].blank?
14+
render json: serialize_feature(DiscourseAi::Features.find_feature_by_id(params[:id].to_i))
15+
end
16+
17+
private
18+
19+
def serialize_features(features)
20+
features.map { |feature| feature.merge(persona: serialize_persona(feature[:persona])) }
21+
end
22+
23+
def serialize_feature(feature)
24+
return nil if feature.blank?
25+
26+
feature.merge(persona: serialize_persona(feature[:persona]))
27+
end
28+
29+
def serialize_persona(persona)
30+
return nil if persona.blank?
31+
32+
serialize_data(persona, AiFeaturesPersonaSerializer, root: false)
33+
end
34+
end
35+
end
36+
end

app/controllers/discourse_ai/admin/ai_llms_controller.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,13 @@ def ai_llm_params(updating: nil)
157157
:provider,
158158
:tokenizer,
159159
:max_prompt_tokens,
160+
:max_output_tokens,
160161
:api_key,
161162
:enabled_chat_bot,
162163
:vision_enabled,
164+
:input_cost,
165+
:cached_input_cost,
166+
:output_cost,
163167
)
164168

165169
provider = updating ? updating.provider : permitted[:provider]

0 commit comments

Comments
 (0)