FEATURE: Less friction for starting a conversation with an AI bot. (#63)
* FEATURE: Less friction for starting a conversation with an AI bot. This PR adds a new header icon as a shortcut to start a conversation with one of our AI Bots. After clicking and selecting one from the dropdown menu, we'll open the composer with some fields already filled (recipients and title). If you leave the title as is, we'll queue a job after five minutes to update it using a bot suggestion. * Update assets/javascripts/initializers/ai-bot-replies.js Co-authored-by: Rafael dos Santos Silva <xfalcox@gmail.com> * Update assets/javascripts/initializers/ai-bot-replies.js Co-authored-by: Rafael dos Santos Silva <xfalcox@gmail.com> --------- Co-authored-by: Rafael dos Santos Silva <xfalcox@gmail.com>
This commit is contained in:
parent
2ed1f874c2
commit
362f6167d1
|
@ -14,6 +14,15 @@ module DiscourseAi
|
||||||
|
|
||||||
render json: {}, status: 200
|
render json: {}, status: 200
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def show_bot_username
|
||||||
|
bot_user_id = DiscourseAi::AiBot::EntryPoint.map_bot_model_to_user_id(params[:username])
|
||||||
|
raise Discourse::InvalidParameters.new(:username) if !bot_user_id
|
||||||
|
|
||||||
|
bot_username_lower = User.find(bot_user_id).username_lower
|
||||||
|
|
||||||
|
render json: { bot_username: bot_username_lower }, status: 200
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
{{#if this.singleBotEnabled}}
|
||||||
|
<DButton
|
||||||
|
@class="icon btn-flat"
|
||||||
|
@action={{this.singleComposeAiBotMessage}}
|
||||||
|
@icon="robot"
|
||||||
|
/>
|
||||||
|
{{else}}
|
||||||
|
<DButton
|
||||||
|
@class="icon btn-flat ai-bot-toggle-available-bots"
|
||||||
|
@action={{this.toggleBotOptions}}
|
||||||
|
@icon="robot"
|
||||||
|
/>
|
||||||
|
{{#if this.open}}
|
||||||
|
<div class="ai-bot-available-bot-options">
|
||||||
|
<div
|
||||||
|
class="ai-bot-available-bot-options-wrapper"
|
||||||
|
{{did-insert this.registerClickListener}}
|
||||||
|
{{will-destroy this.unregisterClickListener}}
|
||||||
|
>
|
||||||
|
{{#each this.enabledBotOptions as |modelName|}}
|
||||||
|
<DButton
|
||||||
|
@class="btn-flat ai-bot-available-bot-content"
|
||||||
|
@translatedTitle={{modelName}}
|
||||||
|
@translatedLabel={{modelName}}
|
||||||
|
@action={{action "composeMessageWithTargetBot" modelName}}
|
||||||
|
/>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
|
@ -0,0 +1,98 @@
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
import Component from "@ember/component";
|
||||||
|
import Composer from "discourse/models/composer";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
|
import I18n from "I18n";
|
||||||
|
|
||||||
|
export default class AiBotHeaderIcon extends Component {
|
||||||
|
@service siteSettings;
|
||||||
|
@service composer;
|
||||||
|
|
||||||
|
@tracked open = false;
|
||||||
|
|
||||||
|
@action
|
||||||
|
async toggleBotOptions() {
|
||||||
|
this.open = !this.open;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
async composeMessageWithTargetBot(target) {
|
||||||
|
this._composeAiBotMessage(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
async singleComposeAiBotMessage() {
|
||||||
|
this._composeAiBotMessage(
|
||||||
|
this.siteSettings.ai_bot_enabled_chat_bots.split("|")[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
registerClickListener() {
|
||||||
|
this.#addClickEventListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
unregisterClickListener() {
|
||||||
|
this.#removeClickEventListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
closeDetails(event) {
|
||||||
|
if (this.open) {
|
||||||
|
const isLinkClick = event.target.className.includes(
|
||||||
|
"ai-bot-toggle-available-bots"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isLinkClick || this.#isOutsideDetailsClick(event)) {
|
||||||
|
this.open = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#isOutsideDetailsClick(event) {
|
||||||
|
return !event.composedPath().some((element) => {
|
||||||
|
return element.className === "ai-bot-available-bot-options";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#removeClickEventListener() {
|
||||||
|
document.removeEventListener("click", this.closeDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
#addClickEventListener() {
|
||||||
|
document.addEventListener("click", this.closeDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
get enabledBotOptions() {
|
||||||
|
return this.siteSettings.ai_bot_enabled_chat_bots.split("|");
|
||||||
|
}
|
||||||
|
|
||||||
|
get singleBotEnabled() {
|
||||||
|
return this.enabledBotOptions.length === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _composeAiBotMessage(targetBot) {
|
||||||
|
let botUsername = await ajax("/discourse-ai/ai-bot/bot-username", {
|
||||||
|
data: { username: targetBot },
|
||||||
|
}).then((data) => {
|
||||||
|
return data.bot_username;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.composer.open({
|
||||||
|
action: Composer.PRIVATE_MESSAGE,
|
||||||
|
recipients: botUsername,
|
||||||
|
topicTitle: `${I18n.t(
|
||||||
|
"discourse_ai.ai_bot.default_pm_prefix"
|
||||||
|
)} ${botUsername}`,
|
||||||
|
archetypeId: "private_message",
|
||||||
|
draftKey: Composer.NEW_PRIVATE_MESSAGE_KEY,
|
||||||
|
hasGroups: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.open = false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { createWidget } from "discourse/widgets/widget";
|
||||||
|
import RenderGlimmer from "discourse/widgets/render-glimmer";
|
||||||
|
import { hbs } from "ember-cli-htmlbars";
|
||||||
|
|
||||||
|
export default createWidget("ai-bot-header-icon", {
|
||||||
|
tagName: "li.header-dropdown-toggle.ai-bot-header-icon",
|
||||||
|
title: "discourse_ai.ai_bot.shortcut_title",
|
||||||
|
|
||||||
|
services: ["siteSettings"],
|
||||||
|
|
||||||
|
html() {
|
||||||
|
const enabledBots = this.siteSettings.ai_bot_enabled_chat_bots
|
||||||
|
.split("|")
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (!enabledBots || enabledBots.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
new RenderGlimmer(
|
||||||
|
this,
|
||||||
|
"div.widget-component-connector",
|
||||||
|
hbs`<AiBotHeaderIcon />`
|
||||||
|
),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
});
|
|
@ -8,6 +8,14 @@ function isGPTBot(user) {
|
||||||
return user && [-110, -111, -112].includes(user.id);
|
return user && [-110, -111, -112].includes(user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function attachHeaderIcon(api) {
|
||||||
|
const settings = api.container.lookup("service:site-settings");
|
||||||
|
|
||||||
|
if (settings.ai_helper_add_ai_pm_to_header) {
|
||||||
|
api.addToHeaderIcons("ai-bot-header-icon");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function initializeAIBotReplies(api) {
|
function initializeAIBotReplies(api) {
|
||||||
api.addPostMenuButton("cancel-gpt", (post) => {
|
api.addPostMenuButton("cancel-gpt", (post) => {
|
||||||
if (isGPTBot(post.user)) {
|
if (isGPTBot(post.user)) {
|
||||||
|
@ -94,10 +102,19 @@ export default {
|
||||||
|
|
||||||
initialize(container) {
|
initialize(container) {
|
||||||
const settings = container.lookup("service:site-settings");
|
const settings = container.lookup("service:site-settings");
|
||||||
|
const user = container.lookup("service:current-user");
|
||||||
const aiBotEnaled =
|
const aiBotEnaled =
|
||||||
settings.discourse_ai_enabled && settings.ai_bot_enabled;
|
settings.discourse_ai_enabled && settings.ai_bot_enabled;
|
||||||
|
|
||||||
if (aiBotEnaled) {
|
const aiBotsAllowedGroups = settings.ai_bot_allowed_groups
|
||||||
|
.split("|")
|
||||||
|
.map(parseInt);
|
||||||
|
const canInteractWithAIBots = user?.groups.some((g) =>
|
||||||
|
aiBotsAllowedGroups.includes(g.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (aiBotEnaled && canInteractWithAIBots) {
|
||||||
|
withPluginApi("1.6.0", attachHeaderIcon);
|
||||||
withPluginApi("1.6.0", initializeAIBotReplies);
|
withPluginApi("1.6.0", initializeAIBotReplies);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,3 +5,21 @@ nav.post-controls .actions button.cancel-streaming {
|
||||||
article.streaming nav.post-controls .actions button.cancel-streaming {
|
article.streaming nav.post-controls .actions button.cancel-streaming {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ai-bot-available-bot-options {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
z-index: z("modal", "content") + 1;
|
||||||
|
transition: background-color 0.25s;
|
||||||
|
background-color: var(--secondary);
|
||||||
|
min-width: 150px;
|
||||||
|
|
||||||
|
.ai-bot-available-bot-content {
|
||||||
|
color: var(--primary-high);
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--primary-low);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ en:
|
||||||
|
|
||||||
ai_bot:
|
ai_bot:
|
||||||
cancel_streaming: Stop reply
|
cancel_streaming: Stop reply
|
||||||
|
default_pm_prefix: "[Untitled AI bot PM]"
|
||||||
|
|
||||||
|
|
||||||
review:
|
review:
|
||||||
|
|
|
@ -50,6 +50,7 @@ en:
|
||||||
ai_embeddings_pg_connection_string: "PostgreSQL connection string for the embeddings module. Needs pgvector extension enabled and a series of tables created. See docs for more info."
|
ai_embeddings_pg_connection_string: "PostgreSQL connection string for the embeddings module. Needs pgvector extension enabled and a series of tables created. See docs for more info."
|
||||||
ai_embeddings_semantic_search_model: "Model to use for semantic search."
|
ai_embeddings_semantic_search_model: "Model to use for semantic search."
|
||||||
ai_embeddings_semantic_search_enabled: "Enable full-page semantic search."
|
ai_embeddings_semantic_search_enabled: "Enable full-page semantic search."
|
||||||
|
ai_embeddings_semantic_related_include_closed_topics: "Include closed topics in semantic search results"
|
||||||
|
|
||||||
ai_summarization_enabled: "Enable the summarization module."
|
ai_summarization_enabled: "Enable the summarization module."
|
||||||
ai_summarization_discourse_service_api_endpoint: "URL where the Discourse summarization API is running."
|
ai_summarization_discourse_service_api_endpoint: "URL where the Discourse summarization API is running."
|
||||||
|
@ -60,6 +61,7 @@ en:
|
||||||
ai_bot_enabled: "Enable the AI Bot module."
|
ai_bot_enabled: "Enable the AI Bot module."
|
||||||
ai_bot_allowed_groups: "When the GPT Bot has access to the PM, it will reply to members of these groups."
|
ai_bot_allowed_groups: "When the GPT Bot has access to the PM, it will reply to members of these groups."
|
||||||
ai_bot_enabled_chat_bots: "Available models to act as an AI Bot"
|
ai_bot_enabled_chat_bots: "Available models to act as an AI Bot"
|
||||||
|
ai_helper_add_ai_pm_to_header: "Display a button in the header to start a PM with a AI Bot"
|
||||||
|
|
||||||
|
|
||||||
reviewables:
|
reviewables:
|
||||||
|
@ -80,3 +82,6 @@ en:
|
||||||
generate_titles: Suggest topic titles
|
generate_titles: Suggest topic titles
|
||||||
proofread: Proofread text
|
proofread: Proofread text
|
||||||
markdown_table: Generate Markdown table
|
markdown_table: Generate Markdown table
|
||||||
|
|
||||||
|
ai_bot:
|
||||||
|
default_pm_prefix: "[Untitled AI bot PM]"
|
||||||
|
|
|
@ -16,6 +16,7 @@ DiscourseAi::Engine.routes.draw do
|
||||||
|
|
||||||
scope module: :ai_bot, path: "/ai-bot", defaults: { format: :json } do
|
scope module: :ai_bot, path: "/ai-bot", defaults: { format: :json } do
|
||||||
post "post/:post_id/stop-streaming" => "bot#stop_streaming_response"
|
post "post/:post_id/stop-streaming" => "bot#stop_streaming_response"
|
||||||
|
get "bot-username" => "bot#show_bot_username"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -203,3 +203,6 @@ plugins:
|
||||||
- gpt-3.5-turbo
|
- gpt-3.5-turbo
|
||||||
- gpt-4
|
- gpt-4
|
||||||
- claude-v1
|
- claude-v1
|
||||||
|
ai_helper_add_ai_pm_to_header:
|
||||||
|
default: true
|
||||||
|
client: true
|
||||||
|
|
|
@ -31,6 +31,15 @@ module DiscourseAi
|
||||||
partial[:completion]
|
partial[:completion]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_updated_title(prompt)
|
||||||
|
DiscourseAi::Inference::AnthropicCompletions.perform!(
|
||||||
|
prompt,
|
||||||
|
model_for,
|
||||||
|
temperature: 0.7,
|
||||||
|
max_tokens: 40,
|
||||||
|
).dig(:completion)
|
||||||
|
end
|
||||||
|
|
||||||
def submit_prompt_and_stream_reply(prompt, &blk)
|
def submit_prompt_and_stream_reply(prompt, &blk)
|
||||||
DiscourseAi::Inference::AnthropicCompletions.perform!(
|
DiscourseAi::Inference::AnthropicCompletions.perform!(
|
||||||
prompt,
|
prompt,
|
||||||
|
|
|
@ -20,6 +20,17 @@ module DiscourseAi
|
||||||
@bot_user = bot_user
|
@bot_user = bot_user
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_pm_title(post)
|
||||||
|
prompt = [title_prompt(post)]
|
||||||
|
|
||||||
|
new_title = get_updated_title(prompt)
|
||||||
|
|
||||||
|
PostRevisor.new(post.topic.first_post, post.topic).revise!(
|
||||||
|
bot_user,
|
||||||
|
title: new_title.sub(/\A"/, "").sub(/"\Z/, ""),
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def reply_to(post)
|
def reply_to(post)
|
||||||
prompt = bot_prompt_with_topic_context(post)
|
prompt = bot_prompt_with_topic_context(post)
|
||||||
|
|
||||||
|
@ -72,7 +83,7 @@ module DiscourseAi
|
||||||
Discourse.warn_exception(e, message: "ai-bot: Reply failed")
|
Discourse.warn_exception(e, message: "ai-bot: Reply failed")
|
||||||
end
|
end
|
||||||
|
|
||||||
def bot_prompt_with_topic_context(post)
|
def bot_prompt_with_topic_context(post, prompt: "topic")
|
||||||
messages = []
|
messages = []
|
||||||
conversation = conversation_context(post)
|
conversation = conversation_context(post)
|
||||||
|
|
||||||
|
@ -106,10 +117,22 @@ module DiscourseAi
|
||||||
raise NotImplemented
|
raise NotImplemented
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def title_prompt(post)
|
||||||
|
build_message(bot_user.username, <<~TEXT)
|
||||||
|
Suggest a 7 word title for the following topic without quoting any of it:
|
||||||
|
|
||||||
|
#{post.topic.posts[1..-1].map(&:raw).join("\n\n")[0..prompt_limit]}
|
||||||
|
TEXT
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
attr_reader :bot_user
|
attr_reader :bot_user
|
||||||
|
|
||||||
|
def get_updated_title(prompt)
|
||||||
|
raise NotImplemented
|
||||||
|
end
|
||||||
|
|
||||||
def model_for(bot)
|
def model_for(bot)
|
||||||
raise NotImplemented
|
raise NotImplemented
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,8 +12,22 @@ module DiscourseAi
|
||||||
[CLAUDE_V1_ID, "claude_v1_bot"],
|
[CLAUDE_V1_ID, "claude_v1_bot"],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def self.map_bot_model_to_user_id(model_name)
|
||||||
|
case model_name
|
||||||
|
in "gpt-3.5-turbo"
|
||||||
|
GPT3_5_TURBO_ID
|
||||||
|
in "gpt-4"
|
||||||
|
GPT4_ID
|
||||||
|
in "claude-v1"
|
||||||
|
CLAUDE_V1_ID
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def load_files
|
def load_files
|
||||||
require_relative "jobs/regular/create_ai_reply"
|
require_relative "jobs/regular/create_ai_reply"
|
||||||
|
require_relative "jobs/regular/update_ai_bot_pm_title"
|
||||||
require_relative "bot"
|
require_relative "bot"
|
||||||
require_relative "anthropic_bot"
|
require_relative "anthropic_bot"
|
||||||
require_relative "open_ai_bot"
|
require_relative "open_ai_bot"
|
||||||
|
@ -24,6 +38,8 @@ module DiscourseAi
|
||||||
Rails.root.join("plugins", "discourse-ai", "db", "fixtures", "ai_bot"),
|
Rails.root.join("plugins", "discourse-ai", "db", "fixtures", "ai_bot"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
plugin.register_svg_icon("robot")
|
||||||
|
|
||||||
plugin.on(:post_created) do |post|
|
plugin.on(:post_created) do |post|
|
||||||
bot_ids = BOTS.map(&:first)
|
bot_ids = BOTS.map(&:first)
|
||||||
|
|
||||||
|
@ -31,7 +47,15 @@ module DiscourseAi
|
||||||
if (SiteSetting.ai_bot_allowed_groups_map & post.user.group_ids).present?
|
if (SiteSetting.ai_bot_allowed_groups_map & post.user.group_ids).present?
|
||||||
bot_id = post.topic.topic_allowed_users.where(user_id: bot_ids).first&.user_id
|
bot_id = post.topic.topic_allowed_users.where(user_id: bot_ids).first&.user_id
|
||||||
|
|
||||||
Jobs.enqueue(:create_ai_reply, post_id: post.id, bot_user_id: bot_id) if bot_id
|
if bot_id
|
||||||
|
Jobs.enqueue(:create_ai_reply, post_id: post.id, bot_user_id: bot_id)
|
||||||
|
Jobs.enqueue_in(
|
||||||
|
5.minutes,
|
||||||
|
:update_ai_bot_pm_title,
|
||||||
|
post_id: post.id,
|
||||||
|
bot_user_id: bot_id,
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module ::Jobs
|
||||||
|
class UpdateAiBotPmTitle < ::Jobs::Base
|
||||||
|
sidekiq_options retry: false
|
||||||
|
|
||||||
|
def execute(args)
|
||||||
|
return unless bot_user = User.find_by(id: args[:bot_user_id])
|
||||||
|
return unless bot = DiscourseAi::AiBot::Bot.as(bot_user)
|
||||||
|
return unless post = Post.includes(:topic).find_by(id: args[:post_id])
|
||||||
|
|
||||||
|
return unless post.topic.title.start_with?(I18n.t("discourse_ai.ai_bot.default_pm_prefix"))
|
||||||
|
|
||||||
|
bot.update_pm_title(post)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -33,6 +33,16 @@ module DiscourseAi
|
||||||
current_delta + partial.dig(:choices, 0, :delta, :content).to_s
|
current_delta + partial.dig(:choices, 0, :delta, :content).to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_updated_title(prompt)
|
||||||
|
DiscourseAi::Inference::OpenAiCompletions.perform!(
|
||||||
|
prompt,
|
||||||
|
model_for,
|
||||||
|
temperature: 0.7,
|
||||||
|
top_p: 0.9,
|
||||||
|
max_tokens: 40,
|
||||||
|
).dig(:choices, 0, :message, :content)
|
||||||
|
end
|
||||||
|
|
||||||
def submit_prompt_and_stream_reply(prompt, &blk)
|
def submit_prompt_and_stream_reply(prompt, &blk)
|
||||||
DiscourseAi::Inference::OpenAiCompletions.perform!(
|
DiscourseAi::Inference::OpenAiCompletions.perform!(
|
||||||
prompt,
|
prompt,
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative "../../../support/openai_completions_inference_stubs"
|
||||||
|
|
||||||
|
RSpec.describe DiscourseAi::AiBot::Bot do
|
||||||
|
describe "#update_pm_title" do
|
||||||
|
fab!(:topic) { Fabricate(:topic) }
|
||||||
|
fab!(:post) { Fabricate(:post, topic: topic) }
|
||||||
|
|
||||||
|
let(:expected_response) { "This is a suggested title" }
|
||||||
|
|
||||||
|
before { SiteSetting.min_personal_message_post_length = 5 }
|
||||||
|
|
||||||
|
before { SiteSetting.min_personal_message_post_length = 5 }
|
||||||
|
|
||||||
|
it "updates the title using bot suggestions" do
|
||||||
|
bot_user = User.find(DiscourseAi::AiBot::EntryPoint::GPT3_5_TURBO_ID)
|
||||||
|
OpenAiCompletionsInferenceStubs.stub_response(
|
||||||
|
DiscourseAi::AiBot::OpenAiBot.new(bot_user).title_prompt(post),
|
||||||
|
expected_response,
|
||||||
|
req_opts: {
|
||||||
|
temperature: 0.7,
|
||||||
|
top_p: 0.9,
|
||||||
|
max_tokens: 40,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
described_class.as(bot_user).update_pm_title(post)
|
||||||
|
|
||||||
|
expect(topic.reload.title).to eq(expected_response)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,9 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
RSpec.describe DiscourseAi::AiBot::BotController do
|
RSpec.describe DiscourseAi::AiBot::BotController do
|
||||||
|
fab!(:user) { Fabricate(:user) }
|
||||||
|
before { sign_in(user) }
|
||||||
|
|
||||||
describe "#stop_streaming_response" do
|
describe "#stop_streaming_response" do
|
||||||
fab!(:pm_topic) { Fabricate(:private_message_topic) }
|
fab!(:pm_topic) { Fabricate(:private_message_topic) }
|
||||||
fab!(:pm_post) { Fabricate(:post, topic: pm_topic) }
|
fab!(:pm_post) { Fabricate(:post, topic: pm_topic) }
|
||||||
|
@ -10,8 +13,6 @@ RSpec.describe DiscourseAi::AiBot::BotController do
|
||||||
before { Discourse.redis.setex(redis_stream_key, 60, 1) }
|
before { Discourse.redis.setex(redis_stream_key, 60, 1) }
|
||||||
|
|
||||||
it "returns a 403 when the user cannot see the PM" do
|
it "returns a 403 when the user cannot see the PM" do
|
||||||
sign_in(Fabricate(:user))
|
|
||||||
|
|
||||||
post "/discourse-ai/ai-bot/post/#{pm_post.id}/stop-streaming"
|
post "/discourse-ai/ai-bot/post/#{pm_post.id}/stop-streaming"
|
||||||
|
|
||||||
expect(response.status).to eq(403)
|
expect(response.status).to eq(403)
|
||||||
|
@ -26,4 +27,16 @@ RSpec.describe DiscourseAi::AiBot::BotController do
|
||||||
expect(Discourse.redis.get(redis_stream_key)).to be_nil
|
expect(Discourse.redis.get(redis_stream_key)).to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#show_bot_username" do
|
||||||
|
it "returns the username_lower of the selected bot" do
|
||||||
|
gpt_3_5_bot = "gpt-3.5-turbo"
|
||||||
|
expected_username = User.find(DiscourseAi::AiBot::EntryPoint::GPT3_5_TURBO_ID).username_lower
|
||||||
|
|
||||||
|
get "/discourse-ai/ai-bot/bot-username", params: { username: gpt_3_5_bot }
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
expect(response.parsed_body["bot_username"]).to eq(expected_username)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue