UX: Improve seeded LLM edit page (#856)

This commit is contained in:
Keegan George 2024-10-24 05:58:27 +09:00 committed by GitHub
parent 0aa2789437
commit 9af0c2e719
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 85 additions and 41 deletions

View File

@ -1,7 +1,7 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking"; import { tracked } from "@glimmer/tracking";
import { Input } from "@ember/component"; import { Input } from "@ember/component";
import { concat, get } from "@ember/helper"; import { concat, get, hash } from "@ember/helper";
import { on } from "@ember/modifier"; import { on } from "@ember/modifier";
import { action, computed } from "@ember/object"; import { action, computed } from "@ember/object";
import { LinkTo } from "@ember/routing"; import { LinkTo } from "@ember/routing";
@ -189,6 +189,7 @@ export default class AiLlmEditorForm extends Component {
class="ai-llm-editor-input ai-llm-editor__display-name" class="ai-llm-editor-input ai-llm-editor__display-name"
@type="text" @type="text"
@value={{@model.display_name}} @value={{@model.display_name}}
disabled={{this.seeded}}
/> />
</div> </div>
<div class="control-group"> <div class="control-group">
@ -197,24 +198,26 @@ export default class AiLlmEditorForm extends Component {
class="ai-llm-editor-input ai-llm-editor__name" class="ai-llm-editor-input ai-llm-editor__name"
@type="text" @type="text"
@value={{@model.name}} @value={{@model.name}}
disabled={{this.seeded}}
/> />
<DTooltip <DTooltip
@icon="question-circle" @icon="question-circle"
@content={{I18n.t "discourse_ai.llms.hints.name"}} @content={{i18n "discourse_ai.llms.hints.name"}}
/> />
</div> </div>
<div class="control-group"> <div class="control-group">
<label>{{I18n.t "discourse_ai.llms.provider"}}</label> <label>{{i18n "discourse_ai.llms.provider"}}</label>
<ComboBox <ComboBox
@value={{@model.provider}} @value={{@model.provider}}
@content={{this.selectedProviders}} @content={{this.selectedProviders}}
@class="ai-llm-editor__provider" @class="ai-llm-editor__provider"
@options={{hash disabled=this.seeded}}
/> />
</div> </div>
{{#unless this.seeded}} {{#unless this.seeded}}
{{#if this.canEditURL}} {{#if this.canEditURL}}
<div class="control-group"> <div class="control-group">
<label>{{I18n.t "discourse_ai.llms.url"}}</label> <label>{{i18n "discourse_ai.llms.url"}}</label>
<Input <Input
class="ai-llm-editor-input ai-llm-editor__url" class="ai-llm-editor-input ai-llm-editor__url"
@type="text" @type="text"
@ -224,7 +227,7 @@ export default class AiLlmEditorForm extends Component {
</div> </div>
{{/if}} {{/if}}
<div class="control-group"> <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"> <div class="ai-llm-editor__secret-api-key-group">
<Input <Input
@value={{@model.api_key}} @value={{@model.api_key}}
@ -241,7 +244,7 @@ export default class AiLlmEditorForm extends Component {
</div> </div>
{{#each-in this.metaProviderParams as |field type|}} {{#each-in this.metaProviderParams as |field type|}}
<div class="control-group ai-llm-editor-provider-param__{{type}}"> <div class="control-group ai-llm-editor-provider-param__{{type}}">
<label>{{I18n.t <label>{{i18n
(concat "discourse_ai.llms.provider_fields." field) (concat "discourse_ai.llms.provider_fields." field)
}}</label> }}</label>
{{#if (eq type "checkbox")}} {{#if (eq type "checkbox")}}
@ -258,7 +261,7 @@ export default class AiLlmEditorForm extends Component {
</div> </div>
{{/each-in}} {{/each-in}}
<div class="control-group"> <div class="control-group">
<label>{{I18n.t "discourse_ai.llms.tokenizer"}}</label> <label>{{i18n "discourse_ai.llms.tokenizer"}}</label>
<ComboBox <ComboBox
@value={{@model.tokenizer}} @value={{@model.tokenizer}}
@content={{@llms.resultSetMeta.tokenizers}} @content={{@llms.resultSetMeta.tokenizers}}
@ -278,63 +281,64 @@ export default class AiLlmEditorForm extends Component {
/> />
<DTooltip <DTooltip
@icon="question-circle" @icon="question-circle"
@content={{I18n.t "discourse_ai.llms.hints.max_prompt_tokens"}} @content={{i18n "discourse_ai.llms.hints.max_prompt_tokens"}}
/> />
</div> </div>
<div class="control-group ai-llm-editor__vision-enabled"> <div class="control-group ai-llm-editor__vision-enabled">
<Input @type="checkbox" @checked={{@model.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 <DTooltip
@icon="question-circle" @icon="question-circle"
@content={{I18n.t "discourse_ai.llms.hints.vision_enabled"}} @content={{i18n "discourse_ai.llms.hints.vision_enabled"}}
/> />
</div> </div>
<div class="control-group ai-llm-editor__enabled-chat-bot"> <div class="control-group ai-llm-editor__enabled-chat-bot">
<Input @type="checkbox" @checked={{@model.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 <DTooltip
@icon="question-circle" @icon="question-circle"
@content={{I18n.t "discourse_ai.llms.hints.enabled_chat_bot"}} @content={{i18n "discourse_ai.llms.hints.enabled_chat_bot"}}
/> />
</div> </div>
{{#if @model.user}} {{/unless}}
<div class="control-group">
<label>{{i18n "discourse_ai.llms.ai_bot_user"}}</label> {{#if @model.user}}
<a <div class="control-group">
class="avatar" <label>{{i18n "discourse_ai.llms.ai_bot_user"}}</label>
href={{@model.user.path}} <a
data-user-card={{@model.user.username}} class="avatar"
> href={{@model.user.path}}
{{Avatar @model.user.avatar_template "small"}} data-user-card={{@model.user.username}}
</a> >
<LinkTo @route="adminUser" @model={{this.adminUser}}> {{Avatar @model.user.avatar_template "small"}}
{{@model.user.username}} </a>
</LinkTo> <LinkTo @route="adminUser" @model={{this.adminUser}}>
</div> {{@model.user.username}}
{{/if}} </LinkTo>
</div>
{{/if}}
{{#unless this.seeded}}
<div class="control-group ai-llm-editor__action_panel"> <div class="control-group ai-llm-editor__action_panel">
<DButton <DButton
class="ai-llm-editor__test" class="ai-llm-editor__test"
@action={{this.test}} @action={{this.test}}
@disabled={{this.testRunning}} @disabled={{this.testRunning}}
> @label="discourse_ai.llms.tests.title"
{{I18n.t "discourse_ai.llms.tests.title"}} />
</DButton>
<DButton <DButton
class="btn-primary ai-llm-editor__save" class="btn-primary ai-llm-editor__save"
@action={{this.save}} @action={{this.save}}
@disabled={{this.isSaving}} @disabled={{this.isSaving}}
> @label="discourse_ai.llms.save"
{{I18n.t "discourse_ai.llms.save"}} />
</DButton>
{{#unless @model.isNew}} {{#unless @model.isNew}}
<DButton <DButton
@action={{this.delete}} @action={{this.delete}}
class="btn-danger ai-llm-editor__delete" class="btn-danger ai-llm-editor__delete"
> @label="discourse_ai.llms.delete"
{{I18n.t "discourse_ai.llms.delete"}} />
</DButton>
{{/unless}} {{/unless}}
</div> </div>
{{/unless}} {{/unless}}
@ -343,12 +347,12 @@ export default class AiLlmEditorForm extends Component {
{{#if this.displayTestResult}} {{#if this.displayTestResult}}
{{#if this.testRunning}} {{#if this.testRunning}}
<div class="spinner small"></div> <div class="spinner small"></div>
{{I18n.t "discourse_ai.llms.tests.running"}} {{i18n "discourse_ai.llms.tests.running"}}
{{else}} {{else}}
{{#if this.testResult}} {{#if this.testResult}}
<div class="ai-llm-editor-tests__success"> <div class="ai-llm-editor-tests__success">
{{icon "check"}} {{icon "check"}}
{{I18n.t "discourse_ai.llms.tests.success"}} {{i18n "discourse_ai.llms.tests.success"}}
</div> </div>
{{else}} {{else}}
<div class="ai-llm-editor-tests__failure"> <div class="ai-llm-editor-tests__failure">

View File

@ -135,7 +135,7 @@ export default class AiLlmsListEditor extends Component {
</thead> </thead>
<tbody> <tbody>
{{#each @llms as |llm|}} {{#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"> <td class="column-name">
<h3>{{llm.display_name}}</h3> <h3>{{llm.display_name}}</h3>
<p> <p>
@ -149,7 +149,7 @@ export default class AiLlmsListEditor extends Component {
</ul> </ul>
{{/if}} {{/if}}
</td> </td>
<td> <td class="column-provider">
{{i18n {{i18n
(concat "discourse_ai.llms.providers." llm.provider) (concat "discourse_ai.llms.providers." llm.provider)
}} }}

View File

@ -89,3 +89,14 @@ Fabricator(:ollama_model, from: :llm_model) do
url "http://api.ollama.ai/api/chat" url "http://api.ollama.ai/api/chat"
provider_params { { enable_native_tool: true } } provider_params { { enable_native_tool: true } }
end 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

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe "Managing LLM configurations", type: :system do RSpec.describe "Managing LLM configurations", type: :system, js: true do
fab!(:admin) fab!(:admin)
before do before do
@ -69,4 +69,33 @@ RSpec.describe "Managing LLM configurations", type: :system do
expect(llm.vision_enabled).to eq(true) expect(llm.vision_enabled).to eq(true)
expect(llm.user_id).not_to be_nil expect(llm.user_id).not_to be_nil
end 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 end