Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 5 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,6 +1,10 @@
import DiscourseRoute from "discourse/routes/discourse";

export default DiscourseRoute.extend({
queryParams: {
llmTemplate: { refreshModel: true },
},

async model() {
const record = this.store.createRecord("ai-llm");
record.provider_params = {};
Expand All @@ -13,5 +17,9 @@ export default DiscourseRoute.extend({
"allLlms",
this.modelFor("adminPlugins.show.discourse-ai-llms")
);
controller.set(
"llmTemplate",
this.paramsFor(this.routeName).llmTemplate || null
);
},
});
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
<AiLlmsListEditor @llms={{this.allLlms}} @currentLlm={{this.model}} />
<AiLlmsListEditor
@llms={{this.allLlms}}
@currentLlm={{this.model}}
@llmTemplate={{this.llmTemplate}}
/>
63 changes: 7 additions & 56 deletions assets/javascripts/discourse/components/ai-llm-editor.gjs
Original file line number Diff line number Diff line change
@@ -1,49 +1,19 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import BackButton from "discourse/components/back-button";
import DButton from "discourse/components/d-button";
import I18n from "discourse-i18n";
import ComboBox from "select-kit/components/combo-box";
import AiLlmEditorForm from "./ai-llm-editor-form";

export default class AiLlmEditor extends Component {
@tracked presetConfigured = false;
presetId = "none";

get showPresets() {
return (
this.args.model.isNew && !this.presetConfigured && !this.args.model.url
);
}

get preConfiguredLlms() {
let options = [
{
id: "none",
name: I18n.t(`discourse_ai.llms.preconfigured.none`),
},
];

this.args.llms.resultSetMeta.presets.forEach((llm) => {
if (llm.models) {
llm.models.forEach((model) => {
options.push({
id: `${llm.id}-${model.name}`,
name: model.display_name,
});
});
}
});

return options;
constructor() {
super(...arguments);
if (this.args.llmTemplate) {
this.configurePreset();
}
}

@action
configurePreset() {
this.presetConfigured = true;

let [id, model] = this.presetId.split(/-(.*)/);
let [id, model] = this.args.llmTemplate.split(/-(.*)/);
if (id === "none") {
return;
}
Expand All @@ -66,25 +36,6 @@ export default class AiLlmEditor extends Component {
@route="adminPlugins.show.discourse-ai-llms"
@label="discourse_ai.llms.back"
/>
{{#if this.showPresets}}
<form class="form-horizontal ai-llm-editor">
<div class="control-group">
<label>{{I18n.t "discourse_ai.llms.preconfigured_llms"}}</label>
<ComboBox
@value={{this.presetId}}
@content={{this.preConfiguredLlms}}
class="ai-llm-editor__presets"
/>
</div>

<div class="control-group ai-llm-editor__action_panel">
<DButton class="ai-llm-editor__next" @action={{this.configurePreset}}>
{{I18n.t "discourse_ai.llms.next.title"}}
</DButton>
</div>
</form>
{{else}}
<AiLlmEditorForm @model={{@model}} @llms={{@llms}} />
{{/if}}
<AiLlmEditorForm @model={{@model}} @llms={{@llms}} />
</template>
}
207 changes: 158 additions & 49 deletions assets/javascripts/discourse/components/ai-llms-list-editor.gjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { LinkTo } from "@ember/routing";
import { inject as service } from "@ember/service";
import { or } from "truth-helpers";
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
import DButton from "discourse/components/d-button";
import DToggleSwitch from "discourse/components/d-toggle-switch";
import { popupAjaxError } from "discourse/lib/ajax-error";
import icon from "discourse-common/helpers/d-icon";
Expand All @@ -14,11 +16,60 @@ import AiLlmEditor from "./ai-llm-editor";

export default class AiLlmsListEditor extends Component {
@service adminPluginNavManager;
@service router;

sanitizedTranslationKey(id) {
return id.replace(/\./g, "-");
}

get hasLLMElements() {
return this.args.llms.length !== 0;
}

get preConfiguredLlms() {
let options = [
{
id: "none",
name: I18n.t(`discourse_ai.llms.preconfigured.fake`),
provider: "fake",
},
];

const llmsContent = this.args.llms.content.map((llm) => ({
provider: llm.provider,
name: llm.name,
}));

this.args.llms.resultSetMeta.presets.forEach((llm) => {
if (llm.models) {
llm.models.forEach((model) => {
const id = `${llm.id}-${model.name}`;
const isConfigured = llmsContent.some(
(content) =>
content.provider === llm.provider && content.name === model.name
);

if (!isConfigured) {
options.push({
id,
name: model.display_name,
provider: llm.provider,
});
}
});
}
});

return options;
}

@action
transitionToLlmEditor(llm) {
this.router.transitionTo("adminPlugins.show.discourse-ai-llms.new", {
queryParams: { llmTemplate: llm },
});
}

@action
async toggleEnabledChatBot(llm) {
const oldValue = llm.enabled_chat_bot;
Expand All @@ -39,60 +90,118 @@ export default class AiLlmsListEditor extends Component {
@path="/admin/plugins/{{this.adminPluginNavManager.currentPlugin.name}}/ai-llms"
@label={{i18n "discourse_ai.llms.short_title"}}
/>
<section class="ai-llms-list-editor admin-detail pull-left">

<section class="ai-llm-list-editor admin-detail">
{{#if @currentLlm}}
<AiLlmEditor @model={{@currentLlm}} @llms={{@llms}} />
<AiLlmEditor
@model={{@currentLlm}}
@llms={{@llms}}
@llmTemplate={{@llmTemplate}}
/>
{{else}}
<div class="ai-llms-list-editor__header">
<h3>{{i18n "discourse_ai.llms.short_title"}}</h3>
{{#unless @currentLlm.isNew}}
<LinkTo
@route="adminPlugins.show.discourse-ai-llms.new"
class="btn btn-small btn-primary ai-llms-list-editor__new"
>
{{icon "plus"}}
<span>{{I18n.t "discourse_ai.llms.new"}}</span>
</LinkTo>
{{/unless}}
</div>

{{#if this.hasLLMElements}}
<table class="content-list ai-persona-list-editor">
<thead>
<tr>
<th>{{i18n "discourse_ai.llms.display_name"}}</th>
<th>{{i18n "discourse_ai.llms.provider"}}</th>
<th>{{i18n "discourse_ai.llms.enabled_chat_bot"}}</th>
<th></th>
</tr>
</thead>
<tbody>
{{#each @llms as |llm|}}
<tr data-persona-id={{llm.id}} class="ai-llm-list__row">
<td><strong>{{llm.display_name}}</strong></td>
<td>{{i18n
(concat "discourse_ai.llms.providers." llm.provider)
}}</td>
<td>
<DToggleSwitch
@state={{llm.enabled_chat_bot}}
{{on "click" (fn this.toggleEnabledChatBot llm)}}
/>
</td>
<td>
<LinkTo
@route="adminPlugins.show.discourse-ai-llms.show"
current-when="true"
class="btn btn-text btn-small"
@model={{llm}}
>{{i18n "discourse_ai.llms.edit"}}</LinkTo>
</td>
<section class="ai-llms-list-editor__configured">
<div class="admin-page-subheader">
<div class="admin-page-subheader__title-row">
<h2 class="admin-page-subheader__title">
{{i18n "discourse_ai.llms.configured.title"}}
</h2>
</div>
</div>
<table>
<thead>
<tr>
<th>{{i18n "discourse_ai.llms.display_name"}}</th>
<th>{{i18n "discourse_ai.llms.provider"}}</th>
<th>{{i18n "discourse_ai.llms.enabled_chat_bot"}}</th>
<th></th>
</tr>
{{/each}}
</tbody>
</table>
</thead>
<tbody>
{{#each @llms as |llm|}}
<tr data-persona-id={{llm.id}} class="ai-llm-list__row">
<td class="column-name">
<h3>{{llm.display_name}}</h3>
<p>
{{i18n
(concat
"discourse_ai.llms.model_description."
llm.provider
"-"
llm.name
)
}}
</p>
</td>
<td>
{{i18n
(concat "discourse_ai.llms.providers." llm.provider)
}}
</td>
<td>
<DToggleSwitch
@state={{llm.enabled_chat_bot}}
{{on "click" (fn this.toggleEnabledChatBot llm)}}
/>
</td>
<td class="column-edit">
<LinkTo
@route="adminPlugins.show.discourse-ai-llms.show"
class="btn btn-default"
@model={{or llm.id llm.llm.id}}
Copy link
Member Author

@awesomerobot awesomerobot Sep 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After first configuring an LLM, when you save and it returns you to this list page the model is structured llm.llm.id and not llm.id so this avoids the error (which existed before this PR)

this fix doesn't seem ideal though, so if anyone has a suggestion I'd appreciate it!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't check code locally for now, but I looked at the code quickly.

Can you show me what console.log(this.args.model) gives at this line https://github.com/discourse/discourse-ai/blob/main/assets/javascripts/discourse/components/ai-llm-editor-form.gjs#L95 please?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I borked the routing a bit when I separated that LLM route? Here is what the console.log gives:

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed this in my commit ... honestly I think it may be an issue with llms hallucinating code cause it had ai_persona there ... which is totally unrelated.

>
{{icon "wrench"}}
<div class="d-button-label">
{{i18n "discourse_ai.llms.edit"}}
</div>
</LinkTo>
</td>
</tr>
{{/each}}
</tbody>
</table>
</section>
{{/if}}
<section class="ai-llms-list-editor__templates">
<div class="admin-page-subheader">
Copy link
Contributor

@martin-brennan martin-brennan Sep 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<div class="admin-page-subheader__title-row">
<h2 class="admin-page-subheader__title">
{{#if this.hasLLMElements}}
{{i18n "discourse_ai.llms.preconfigured.title"}}
{{else}}
{{i18n "discourse_ai.llms.preconfigured.title_no_llms"}}
{{/if}}
</h2>
</div>
</div>
<div class="ai-llms-list-editor__templates-list">
{{#each this.preConfiguredLlms as |llm|}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would really prefer these "section landing" tiles are reusable components, this is why I opened this draft PR https://github.com/discourse/discourse/pull/28477/files . At the least could you please extract this to a component in this PR then we can replace with a core version later?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets do this in a followup, really want to land this. I addressed the rest of the feedback.

<div
data-llm-id={{llm.id}}
class="ai-llms-list-editor__templates-list-item"
>
<h4>
{{i18n (concat "discourse_ai.llms.providers." llm.provider)}}
</h4>
<h3>
{{llm.name}}
</h3>
<p>
{{i18n
(concat
"discourse_ai.llms.model_description."
(this.sanitizedTranslationKey llm.id)
)
}}
</p>
<DButton
@action={{fn this.transitionToLlmEditor llm.id}}
@icon="gear"
@label="discourse_ai.llms.preconfigured.button"
/>
</div>
{{/each}}
</div>
</section>
{{/if}}
</section>
</template>
Expand Down
Loading