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 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
80 changes: 42 additions & 38 deletions assets/javascripts/discourse/components/ai-llm-editor-form.gjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { Input } from "@ember/component";
import { concat, get } from "@ember/helper";
import { concat, get, hash } from "@ember/helper";
import { on } from "@ember/modifier";
import { action, computed } from "@ember/object";
import { LinkTo } from "@ember/routing";
Expand Down Expand Up @@ -189,6 +189,7 @@ export default class AiLlmEditorForm extends Component {
class="ai-llm-editor-input ai-llm-editor__display-name"
@type="text"
@value={{@model.display_name}}
disabled={{this.seeded}}
/>
</div>
<div class="control-group">
Expand All @@ -197,24 +198,26 @@ export default class AiLlmEditorForm extends Component {
class="ai-llm-editor-input ai-llm-editor__name"
@type="text"
@value={{@model.name}}
disabled={{this.seeded}}
/>
<DTooltip
@icon="question-circle"
@content={{I18n.t "discourse_ai.llms.hints.name"}}
@content={{i18n "discourse_ai.llms.hints.name"}}
/>
</div>
<div class="control-group">
<label>{{I18n.t "discourse_ai.llms.provider"}}</label>
<label>{{i18n "discourse_ai.llms.provider"}}</label>
<ComboBox
@value={{@model.provider}}
@content={{this.selectedProviders}}
@class="ai-llm-editor__provider"
@options={{hash disabled=this.seeded}}
/>
</div>
{{#unless this.seeded}}
{{#if this.canEditURL}}
<div class="control-group">
<label>{{I18n.t "discourse_ai.llms.url"}}</label>
<label>{{i18n "discourse_ai.llms.url"}}</label>
<Input
class="ai-llm-editor-input ai-llm-editor__url"
@type="text"
Expand All @@ -224,7 +227,7 @@ export default class AiLlmEditorForm extends Component {
</div>
{{/if}}
<div class="control-group">
<label>{{I18n.t "discourse_ai.llms.api_key"}}</label>
<label>{{i18n "discourse_ai.llms.api_key"}}</label>
<div class="ai-llm-editor__secret-api-key-group">
<Input
@value={{@model.api_key}}
Expand All @@ -241,7 +244,7 @@ export default class AiLlmEditorForm extends Component {
</div>
{{#each-in this.metaProviderParams as |field type|}}
<div class="control-group ai-llm-editor-provider-param__{{type}}">
<label>{{I18n.t
<label>{{i18n
(concat "discourse_ai.llms.provider_fields." field)
}}</label>
{{#if (eq type "checkbox")}}
Expand All @@ -258,7 +261,7 @@ export default class AiLlmEditorForm extends Component {
</div>
{{/each-in}}
<div class="control-group">
<label>{{I18n.t "discourse_ai.llms.tokenizer"}}</label>
<label>{{i18n "discourse_ai.llms.tokenizer"}}</label>
<ComboBox
@value={{@model.tokenizer}}
@content={{@llms.resultSetMeta.tokenizers}}
Expand All @@ -278,63 +281,64 @@ export default class AiLlmEditorForm extends Component {
/>
<DTooltip
@icon="question-circle"
@content={{I18n.t "discourse_ai.llms.hints.max_prompt_tokens"}}
@content={{i18n "discourse_ai.llms.hints.max_prompt_tokens"}}
/>
</div>
<div class="control-group ai-llm-editor__vision-enabled">
<Input @type="checkbox" @checked={{@model.vision_enabled}} />
<label>{{I18n.t "discourse_ai.llms.vision_enabled"}}</label>
<label>{{i18n "discourse_ai.llms.vision_enabled"}}</label>
<DTooltip
@icon="question-circle"
@content={{I18n.t "discourse_ai.llms.hints.vision_enabled"}}
@content={{i18n "discourse_ai.llms.hints.vision_enabled"}}
/>
</div>
<div class="control-group ai-llm-editor__enabled-chat-bot">
<Input @type="checkbox" @checked={{@model.enabled_chat_bot}} />
<label>{{I18n.t "discourse_ai.llms.enabled_chat_bot"}}</label>
<label>{{i18n "discourse_ai.llms.enabled_chat_bot"}}</label>
<DTooltip
@icon="question-circle"
@content={{I18n.t "discourse_ai.llms.hints.enabled_chat_bot"}}
@content={{i18n "discourse_ai.llms.hints.enabled_chat_bot"}}
/>
</div>
{{#if @model.user}}
<div class="control-group">
<label>{{i18n "discourse_ai.llms.ai_bot_user"}}</label>
<a
class="avatar"
href={{@model.user.path}}
data-user-card={{@model.user.username}}
>
{{Avatar @model.user.avatar_template "small"}}
</a>
<LinkTo @route="adminUser" @model={{this.adminUser}}>
{{@model.user.username}}
</LinkTo>
</div>
{{/if}}
{{/unless}}

{{#if @model.user}}
<div class="control-group">
<label>{{i18n "discourse_ai.llms.ai_bot_user"}}</label>
<a
class="avatar"
href={{@model.user.path}}
data-user-card={{@model.user.username}}
>
{{Avatar @model.user.avatar_template "small"}}
</a>
<LinkTo @route="adminUser" @model={{this.adminUser}}>
{{@model.user.username}}
</LinkTo>
</div>
{{/if}}

{{#unless this.seeded}}
<div class="control-group ai-llm-editor__action_panel">
<DButton
class="ai-llm-editor__test"
@action={{this.test}}
@disabled={{this.testRunning}}
>
{{I18n.t "discourse_ai.llms.tests.title"}}
</DButton>
@label="discourse_ai.llms.tests.title"
/>

<DButton
class="btn-primary ai-llm-editor__save"
@action={{this.save}}
@disabled={{this.isSaving}}
>
{{I18n.t "discourse_ai.llms.save"}}
</DButton>
@label="discourse_ai.llms.save"
/>
{{#unless @model.isNew}}
<DButton
@action={{this.delete}}
class="btn-danger ai-llm-editor__delete"
>
{{I18n.t "discourse_ai.llms.delete"}}
</DButton>
@label="discourse_ai.llms.delete"
/>
{{/unless}}
</div>
{{/unless}}
Expand All @@ -343,12 +347,12 @@ export default class AiLlmEditorForm extends Component {
{{#if this.displayTestResult}}
{{#if this.testRunning}}
<div class="spinner small"></div>
{{I18n.t "discourse_ai.llms.tests.running"}}
{{i18n "discourse_ai.llms.tests.running"}}
{{else}}
{{#if this.testResult}}
<div class="ai-llm-editor-tests__success">
{{icon "check"}}
{{I18n.t "discourse_ai.llms.tests.success"}}
{{i18n "discourse_ai.llms.tests.success"}}
</div>
{{else}}
<div class="ai-llm-editor-tests__failure">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export default class AiLlmsListEditor extends Component {
</thead>
<tbody>
{{#each @llms as |llm|}}
<tr data-persona-id={{llm.id}} class="ai-llm-list__row">
<tr data-llm-id={{llm.name}} class="ai-llm-list__row">
<td class="column-name">
<h3>{{llm.display_name}}</h3>
<p>
Expand All @@ -149,7 +149,7 @@ export default class AiLlmsListEditor extends Component {
</ul>
{{/if}}
</td>
<td>
<td class="column-provider">
{{i18n
(concat "discourse_ai.llms.providers." llm.provider)
}}
Expand Down
11 changes: 11 additions & 0 deletions spec/fabricators/llm_model_fabricator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,14 @@
url "http://api.ollama.ai/api/chat"
provider_params { { enable_native_tool: true } }
end

Fabricator(:seeded_model, from: :llm_model) do
id "-2"
display_name "CDCK Hosted Model"
name "cdck-hosted"
provider "fake"
api_key "DSC"
tokenizer "DiscourseAi::Tokenizer::OpenAiTokenizer"
url "https://cdck.test/"
enabled_chat_bot true
end
31 changes: 30 additions & 1 deletion spec/system/llms/ai_llm_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

RSpec.describe "Managing LLM configurations", type: :system do
RSpec.describe "Managing LLM configurations", type: :system, js: true do
fab!(:admin)

before do
Expand Down Expand Up @@ -69,4 +69,33 @@
expect(llm.vision_enabled).to eq(true)
expect(llm.user_id).not_to be_nil
end

context "when seeded LLM is present" do
fab!(:llm_model) { Fabricate(:seeded_model) }

it "shows the provider as CDCK in the UI" do
visit "/admin/plugins/discourse-ai/ai-llms"
expect(page).to have_css(
"[data-llm-id='cdck-hosted'] .column-provider",
text: I18n.t("js.discourse_ai.llms.providers.CDCK"),
)
end

it "shows an info alert to the user about the seeded LLM" do
visit "/admin/plugins/discourse-ai/ai-llms"
find("[data-llm-id='#{llm_model.name}'] .column-edit .btn").click()
expect(page).to have_css(
".alert.alert-info",
text: I18n.t("js.discourse_ai.llms.seeded_warning"),
)
end

it "limits and shows disabled inputs for the seeded LLM" do
visit "/admin/plugins/discourse-ai/ai-llms"
find("[data-llm-id='cdck-hosted'] .column-edit .btn").click()
expect(page).to have_css(".ai-llm-editor__display-name[disabled]")
expect(page).to have_css(".ai-llm-editor__name[disabled]")
expect(page).to have_css(".ai-llm-editor__provider.is-disabled")
end
end
end