UX: Remove multi llm selector from header and move to composer (#619)
LLM selector control had no memory and was awkward to click. Instead we now: - Clearly display which llm you are talking to - Allow you to change llm direct from composer
This commit is contained in:
parent
8eee6893d6
commit
cb23ae614f
|
@ -4,9 +4,7 @@ import { service } from "@ember/service";
|
||||||
import { gt } from "truth-helpers";
|
import { gt } from "truth-helpers";
|
||||||
import DButton from "discourse/components/d-button";
|
import DButton from "discourse/components/d-button";
|
||||||
import i18n from "discourse-common/helpers/i18n";
|
import i18n from "discourse-common/helpers/i18n";
|
||||||
import DMenu from "float-kit/components/d-menu";
|
|
||||||
import { composeAiBotMessage } from "../lib/ai-bot-helper";
|
import { composeAiBotMessage } from "../lib/ai-bot-helper";
|
||||||
import AiBotHeaderPanel from "./ai-bot-header-panel";
|
|
||||||
|
|
||||||
export default class AiBotHeaderIcon extends Component {
|
export default class AiBotHeaderIcon extends Component {
|
||||||
@service siteSettings;
|
@service siteSettings;
|
||||||
|
@ -26,24 +24,12 @@ export default class AiBotHeaderIcon extends Component {
|
||||||
<template>
|
<template>
|
||||||
{{#if (gt this.bots.length 0)}}
|
{{#if (gt this.bots.length 0)}}
|
||||||
<li>
|
<li>
|
||||||
{{#if (gt this.bots.length 1)}}
|
<DButton
|
||||||
<DMenu
|
@icon="robot"
|
||||||
@icon="robot"
|
@title={{i18n "discourse_ai.ai_bot.shortcut_title"}}
|
||||||
@title={{i18n "discourse_ai.ai_bot.shortcut_title"}}
|
class="ai-bot-button icon btn-flat"
|
||||||
class="ai-bot-button icon btn-flat"
|
@action={{this.compose}}
|
||||||
>
|
/>
|
||||||
<:content as |args|>
|
|
||||||
<AiBotHeaderPanel @closePanel={{args.close}} />
|
|
||||||
</:content>
|
|
||||||
</DMenu>
|
|
||||||
{{else}}
|
|
||||||
<DButton
|
|
||||||
@icon="robot"
|
|
||||||
@title={{i18n "discourse_ai.ai_bot.shortcut_title"}}
|
|
||||||
class="ai-bot-button icon btn-flat"
|
|
||||||
@action={{this.compose}}
|
|
||||||
/>
|
|
||||||
{{/if}}
|
|
||||||
</li>
|
</li>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
<div class="bot-panel ai-bot-available-bot-options">
|
|
||||||
{{#each this.botNames as |bot|}}
|
|
||||||
<DButton
|
|
||||||
@translatedTitle={{bot.humanized}}
|
|
||||||
@translatedLabel={{bot.humanized}}
|
|
||||||
@action={{action "composeMessageWithTargetBot" bot.modelName}}
|
|
||||||
class="btn-flat ai-bot-available-bot-content"
|
|
||||||
/>
|
|
||||||
{{/each}}
|
|
||||||
</div>
|
|
|
@ -1,33 +0,0 @@
|
||||||
import Component from "@glimmer/component";
|
|
||||||
import { action } from "@ember/object";
|
|
||||||
import { inject as service } from "@ember/service";
|
|
||||||
import I18n from "I18n";
|
|
||||||
import { composeAiBotMessage } from "discourse/plugins/discourse-ai/discourse/lib/ai-bot-helper";
|
|
||||||
|
|
||||||
export default class AiBotHeaderPanel extends Component {
|
|
||||||
@service siteSettings;
|
|
||||||
@service composer;
|
|
||||||
|
|
||||||
@action
|
|
||||||
composeMessageWithTargetBot(target) {
|
|
||||||
this.#composeAiBotMessage(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
get botNames() {
|
|
||||||
return this.enabledBotOptions.map((bot) => {
|
|
||||||
return {
|
|
||||||
humanized: I18n.t(`discourse_ai.ai_bot.bot_names.${bot}`),
|
|
||||||
modelName: bot,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get enabledBotOptions() {
|
|
||||||
return this.siteSettings.ai_bot_enabled_chat_bots.split("|");
|
|
||||||
}
|
|
||||||
|
|
||||||
#composeAiBotMessage(targetBot) {
|
|
||||||
this.args.closePanel();
|
|
||||||
composeAiBotMessage(targetBot, this.composer);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,10 @@
|
||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
import { hash } from "@ember/helper";
|
import { hash } from "@ember/helper";
|
||||||
|
import { next } from "@ember/runloop";
|
||||||
import { inject as service } from "@ember/service";
|
import { inject as service } from "@ember/service";
|
||||||
import KeyValueStore from "discourse/lib/key-value-store";
|
import KeyValueStore from "discourse/lib/key-value-store";
|
||||||
|
import I18n from "I18n";
|
||||||
import DropdownSelectBox from "select-kit/components/dropdown-select-box";
|
import DropdownSelectBox from "select-kit/components/dropdown-select-box";
|
||||||
|
|
||||||
function isBotMessage(composer, currentUser) {
|
function isBotMessage(composer, currentUser) {
|
||||||
|
@ -28,9 +31,14 @@ export default class BotSelector extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
@service currentUser;
|
@service currentUser;
|
||||||
|
@service siteSettings;
|
||||||
|
@tracked llm;
|
||||||
|
|
||||||
STORE_NAMESPACE = "discourse_ai_persona_selector_";
|
STORE_NAMESPACE = "discourse_ai_persona_selector_";
|
||||||
|
LLM_STORE_NAMESPACE = "discourse_ai_llm_selector_";
|
||||||
|
|
||||||
preferredPersonaStore = new KeyValueStore(this.STORE_NAMESPACE);
|
preferredPersonaStore = new KeyValueStore(this.STORE_NAMESPACE);
|
||||||
|
preferredLlmStore = new KeyValueStore(this.LLM_STORE_NAMESPACE);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
|
@ -47,6 +55,14 @@ export default class BotSelector extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.composer.metaData = { ai_persona_id: this._value };
|
this.composer.metaData = { ai_persona_id: this._value };
|
||||||
|
|
||||||
|
let llm = this.preferredLlmStore.getObject("id");
|
||||||
|
llm = llm || this.llmOptions[0].id;
|
||||||
|
if (llm) {
|
||||||
|
next(() => {
|
||||||
|
this.currentLlm = llm;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,14 +96,49 @@ export default class BotSelector extends Component {
|
||||||
this.composer.metaData = { ai_persona_id: newValue };
|
this.composer.metaData = { ai_persona_id: newValue };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get currentLlm() {
|
||||||
|
return this.llm;
|
||||||
|
}
|
||||||
|
|
||||||
|
set currentLlm(newValue) {
|
||||||
|
this.llm = newValue;
|
||||||
|
const botUsername = this.currentUser.ai_enabled_chat_bots.find(
|
||||||
|
(bot) => bot.model_name === this.llm
|
||||||
|
).username;
|
||||||
|
this.preferredLlmStore.setObject({ key: "id", value: newValue });
|
||||||
|
this.composer.set("targetRecipients", botUsername);
|
||||||
|
}
|
||||||
|
|
||||||
|
get llmOptions() {
|
||||||
|
return this.siteSettings.ai_bot_enabled_chat_bots
|
||||||
|
.split("|")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((bot) => {
|
||||||
|
return {
|
||||||
|
id: bot,
|
||||||
|
name: I18n.t(`discourse_ai.ai_bot.bot_names.${bot}`),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="gpt-persona">
|
<div class="persona-llm-selector">
|
||||||
<DropdownSelectBox
|
<div class="gpt-persona">
|
||||||
class="persona-selector__dropdown"
|
<DropdownSelectBox
|
||||||
@value={{this.value}}
|
class="persona-llm-selector__persona-dropdown"
|
||||||
@content={{this.botOptions}}
|
@value={{this.value}}
|
||||||
@options={{hash icon="robot" filterable=this.filterable}}
|
@content={{this.botOptions}}
|
||||||
/>
|
@options={{hash icon="robot" filterable=this.filterable}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="llm-selector">
|
||||||
|
<DropdownSelectBox
|
||||||
|
class="persona-llm-selector__llm-dropdown"
|
||||||
|
@value={{this.currentLlm}}
|
||||||
|
@content={{this.llmOptions}}
|
||||||
|
@options={{hash icon="globe"}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
}
|
}
|
|
@ -9,12 +9,15 @@ nav.post-controls .actions button.cancel-streaming {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.gpt-persona {
|
.persona-llm-selector {
|
||||||
margin-bottom: 5px;
|
display: flex;
|
||||||
margin-top: -10px;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.select-kit.single-select.dropdown-select-box ul.select-kit-collection {
|
.select-kit.single-select.dropdown-select-box ul.select-kit-collection {
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
}
|
}
|
||||||
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,9 +23,6 @@ RSpec.describe "AI chat channel summarization", type: :system, js: true do
|
||||||
expect(page).to have_selector(".ai-bot-button")
|
expect(page).to have_selector(".ai-bot-button")
|
||||||
find(".ai-bot-button").click
|
find(".ai-bot-button").click
|
||||||
|
|
||||||
expect(page).to have_selector(".ai-bot-available-bot-content")
|
|
||||||
find("button.ai-bot-available-bot-content:first-child").click
|
|
||||||
|
|
||||||
# composer is open
|
# composer is open
|
||||||
expect(page).to have_selector(".d-editor-container")
|
expect(page).to have_selector(".d-editor-container")
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,8 @@ RSpec.describe "AI personas", type: :system, js: true do
|
||||||
it "remembers the last selected persona" do
|
it "remembers the last selected persona" do
|
||||||
visit "/"
|
visit "/"
|
||||||
find(".d-header .ai-bot-button").click()
|
find(".d-header .ai-bot-button").click()
|
||||||
persona_selector = PageObjects::Components::SelectKit.new(".persona-selector__dropdown")
|
persona_selector =
|
||||||
|
PageObjects::Components::SelectKit.new(".persona-llm-selector__persona-dropdown")
|
||||||
|
|
||||||
id = DiscourseAi::AiBot::Personas::Persona.all(user: admin).first.id
|
id = DiscourseAi::AiBot::Personas::Persona.all(user: admin).first.id
|
||||||
|
|
||||||
|
@ -24,7 +25,8 @@ RSpec.describe "AI personas", type: :system, js: true do
|
||||||
|
|
||||||
visit "/"
|
visit "/"
|
||||||
find(".d-header .ai-bot-button").click()
|
find(".d-header .ai-bot-button").click()
|
||||||
persona_selector = PageObjects::Components::SelectKit.new(".persona-selector__dropdown")
|
persona_selector =
|
||||||
|
PageObjects::Components::SelectKit.new(".persona-llm-selector__persona-dropdown")
|
||||||
persona_selector.expand
|
persona_selector.expand
|
||||||
expect(persona_selector).to have_selected_value(-2)
|
expect(persona_selector).to have_selected_value(-2)
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue