mirror of
https://github.com/discourse/discourse-ai.git
synced 2025-07-08 15:22:47 +00:00
WIP: migrate persona to agent
This commit is contained in:
parent
ad5c48d9ae
commit
399feafc4f
@ -0,0 +1,19 @@
|
|||||||
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
|
|
||||||
|
export default class AdminPluginsShowDiscourseAiAgentsEdit extends DiscourseRoute {
|
||||||
|
async model(params) {
|
||||||
|
const allAgents = this.modelFor(
|
||||||
|
"adminPlugins.show.discourse-ai-agents"
|
||||||
|
);
|
||||||
|
const id = parseInt(params.id, 10);
|
||||||
|
return allAgents.findBy("id", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupController(controller, model) {
|
||||||
|
super.setupController(controller, model);
|
||||||
|
controller.set(
|
||||||
|
"allAgents",
|
||||||
|
this.modelFor("adminPlugins.show.discourse-ai-agents")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,16 @@
|
|||||||
import { AUTO_GROUPS } from "discourse/lib/constants";
|
import { AUTO_GROUPS } from "discourse/lib/constants";
|
||||||
import DiscourseRoute from "discourse/routes/discourse";
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
|
|
||||||
export default class AdminPluginsShowDiscourseAiPersonasNew extends DiscourseRoute {
|
export default class AdminPluginsShowDiscourseAiAgentsNew extends DiscourseRoute {
|
||||||
async model() {
|
async model() {
|
||||||
const record = this.store.createRecord("ai-persona");
|
const record = this.store.createRecord("ai-agent");
|
||||||
record.set("allowed_group_ids", [AUTO_GROUPS.trust_level_0.id]);
|
record.set("allowed_group_ids", [AUTO_GROUPS.trust_level_0.id]);
|
||||||
record.set("rag_uploads", []);
|
record.set("rag_uploads", []);
|
||||||
// these match the defaults on the table
|
// these match the defaults on the table
|
||||||
record.set("rag_chunk_tokens", 374);
|
record.set("rag_chunk_tokens", 374);
|
||||||
record.set("rag_chunk_overlap_tokens", 10);
|
record.set("rag_chunk_overlap_tokens", 10);
|
||||||
record.set("rag_conversation_chunks", 10);
|
record.set("rag_conversation_chunks", 10);
|
||||||
record.set("allow_personal_messages", true);
|
record.set("allow_agentl_messages", true);
|
||||||
record.set("tool_details", false);
|
record.set("tool_details", false);
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
@ -18,8 +18,8 @@ export default class AdminPluginsShowDiscourseAiPersonasNew extends DiscourseRou
|
|||||||
setupController(controller, model) {
|
setupController(controller, model) {
|
||||||
super.setupController(controller, model);
|
super.setupController(controller, model);
|
||||||
controller.set(
|
controller.set(
|
||||||
"allPersonas",
|
"allAgents",
|
||||||
this.modelFor("adminPlugins.show.discourse-ai-personas")
|
this.modelFor("adminPlugins.show.discourse-ai-agents")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
import DiscourseRoute from "discourse/routes/discourse";
|
||||||
|
|
||||||
|
export default class DiscourseAiAiAgentsRoute extends DiscourseRoute {
|
||||||
|
model() {
|
||||||
|
return this.store.findAll("ai-agent");
|
||||||
|
}
|
||||||
|
}
|
@ -1,19 +0,0 @@
|
|||||||
import DiscourseRoute from "discourse/routes/discourse";
|
|
||||||
|
|
||||||
export default class AdminPluginsShowDiscourseAiPersonasEdit extends DiscourseRoute {
|
|
||||||
async model(params) {
|
|
||||||
const allPersonas = this.modelFor(
|
|
||||||
"adminPlugins.show.discourse-ai-personas"
|
|
||||||
);
|
|
||||||
const id = parseInt(params.id, 10);
|
|
||||||
return allPersonas.findBy("id", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
setupController(controller, model) {
|
|
||||||
super.setupController(controller, model);
|
|
||||||
controller.set(
|
|
||||||
"allPersonas",
|
|
||||||
this.modelFor("adminPlugins.show.discourse-ai-personas")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
import DiscourseRoute from "discourse/routes/discourse";
|
|
||||||
|
|
||||||
export default class DiscourseAiAiPersonasRoute extends DiscourseRoute {
|
|
||||||
model() {
|
|
||||||
return this.store.findAll("ai-persona");
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,4 @@
|
|||||||
|
<AiAgentListEditor
|
||||||
|
@agents={{this.allAgents}}
|
||||||
|
@currentAgent={{this.model}}
|
||||||
|
/>
|
@ -0,0 +1 @@
|
|||||||
|
<AiAgentListEditor @agents={{this.model}} />
|
@ -0,0 +1,4 @@
|
|||||||
|
<AiAgentListEditor
|
||||||
|
@agents={{this.allAgents}}
|
||||||
|
@currentAgent={{this.model}}
|
||||||
|
/>
|
@ -15,7 +15,7 @@ export default RouteTemplate(
|
|||||||
const prefix = "discourse_ai.features.list.header";
|
const prefix = "discourse_ai.features.list.header";
|
||||||
return [
|
return [
|
||||||
i18n(`${prefix}.name`),
|
i18n(`${prefix}.name`),
|
||||||
i18n(`${prefix}.persona`),
|
i18n(`${prefix}.agent`),
|
||||||
i18n(`${prefix}.groups`),
|
i18n(`${prefix}.groups`),
|
||||||
"",
|
"",
|
||||||
];
|
];
|
||||||
@ -75,21 +75,21 @@ export default RouteTemplate(
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
class="d-admin-row__detail ai-feature-list__row-item ai-feature-list__persona"
|
class="d-admin-row__detail ai-feature-list__row-item ai-feature-list__agent"
|
||||||
>
|
>
|
||||||
<DButton
|
<DButton
|
||||||
class="btn-flat btn-small ai-feature-list__row-item-persona"
|
class="btn-flat btn-small ai-feature-list__row-item-agent"
|
||||||
@translatedLabel={{feature.persona.name}}
|
@translatedLabel={{feature.agent.name}}
|
||||||
@route="adminPlugins.show.discourse-ai-personas.edit"
|
@route="adminPlugins.show.discourse-ai-agents.edit"
|
||||||
@routeModels={{feature.persona.id}}
|
@routeModels={{feature.agent.id}}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td
|
<td
|
||||||
class="d-admin-row__detail ai-feature-list__row-item ai-feature-list__groups"
|
class="d-admin-row__detail ai-feature-list__row-item ai-feature-list__groups"
|
||||||
>
|
>
|
||||||
{{#if (gt feature.persona.allowed_groups.length 0)}}
|
{{#if (gt feature.agent.allowed_groups.length 0)}}
|
||||||
<ul class="ai-feature-list__row-item-groups">
|
<ul class="ai-feature-list__row-item-groups">
|
||||||
{{#each feature.persona.allowed_groups as |group|}}
|
{{#each feature.agent.allowed_groups as |group|}}
|
||||||
<li>{{group.name}}</li>
|
<li>{{group.name}}</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
<AiPersonaListEditor
|
|
||||||
@personas={{this.allPersonas}}
|
|
||||||
@currentPersona={{this.model}}
|
|
||||||
/>
|
|
@ -1 +0,0 @@
|
|||||||
<AiPersonaListEditor @personas={{this.model}} />
|
|
@ -1,4 +0,0 @@
|
|||||||
<AiPersonaListEditor
|
|
||||||
@personas={{this.allPersonas}}
|
|
||||||
@currentPersona={{this.model}}
|
|
||||||
/>
|
|
@ -1,4 +1,4 @@
|
|||||||
<section class="ai-persona-tool-editor__current admin-detail pull-left">
|
<section class="ai-agent-tool-editor__current admin-detail pull-left">
|
||||||
<AiToolEditor
|
<AiToolEditor
|
||||||
@tools={{this.allTools}}
|
@tools={{this.allTools}}
|
||||||
@model={{this.model}}
|
@model={{this.model}}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<section class="ai-persona-tool-editor__current admin-detail pull-left">
|
<section class="ai-agent-tool-editor__current admin-detail pull-left">
|
||||||
<AiToolEditor
|
<AiToolEditor
|
||||||
@tools={{this.allTools}}
|
@tools={{this.allTools}}
|
||||||
@model={{this.model}}
|
@model={{this.model}}
|
||||||
|
@ -2,20 +2,20 @@
|
|||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Admin
|
module Admin
|
||||||
class AiPersonasController < ::Admin::AdminController
|
class AiAgentsController < ::Admin::AdminController
|
||||||
requires_plugin ::DiscourseAi::PLUGIN_NAME
|
requires_plugin ::DiscourseAi::PLUGIN_NAME
|
||||||
|
|
||||||
before_action :find_ai_persona, only: %i[edit update destroy create_user]
|
before_action :find_ai_agent, only: %i[edit update destroy create_user]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
ai_personas =
|
ai_agents =
|
||||||
AiPersona.ordered.map do |persona|
|
AiAgent.ordered.map do |agent|
|
||||||
# we use a special serializer here cause names and descriptions are
|
# we use a special serializer here cause names and descriptions are
|
||||||
# localized for system personas
|
# localized for system agents
|
||||||
LocalizedAiPersonaSerializer.new(persona, root: false)
|
LocalizedAiAgentSerializer.new(agent, root: false)
|
||||||
end
|
end
|
||||||
tools =
|
tools =
|
||||||
DiscourseAi::Personas::Persona.all_available_tools.map do |tool|
|
DiscourseAi::Agents::Agent.all_available_tools.map do |tool|
|
||||||
AiToolSerializer.new(tool, root: false)
|
AiToolSerializer.new(tool, root: false)
|
||||||
end
|
end
|
||||||
AiTool
|
AiTool
|
||||||
@ -36,7 +36,7 @@ module DiscourseAi
|
|||||||
allowed_seeded_llm_ids: SiteSetting.ai_bot_allowed_seeded_models_map,
|
allowed_seeded_llm_ids: SiteSetting.ai_bot_allowed_seeded_models_map,
|
||||||
)
|
)
|
||||||
render json: {
|
render json: {
|
||||||
ai_personas: ai_personas,
|
ai_agents: ai_agents,
|
||||||
meta: {
|
meta: {
|
||||||
tools: tools,
|
tools: tools,
|
||||||
llms: llms,
|
llms: llms,
|
||||||
@ -51,55 +51,55 @@ module DiscourseAi
|
|||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
render json: LocalizedAiPersonaSerializer.new(@ai_persona)
|
render json: LocalizedAiAgentSerializer.new(@ai_agent)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
ai_persona = AiPersona.new(ai_persona_params.except(:rag_uploads))
|
ai_agent = AiAgent.new(ai_agent_params.except(:rag_uploads))
|
||||||
if ai_persona.save
|
if ai_agent.save
|
||||||
RagDocumentFragment.link_target_and_uploads(ai_persona, attached_upload_ids)
|
RagDocumentFragment.link_target_and_uploads(ai_agent, attached_upload_ids)
|
||||||
|
|
||||||
render json: {
|
render json: {
|
||||||
ai_persona: LocalizedAiPersonaSerializer.new(ai_persona, root: false),
|
ai_agent: LocalizedAiAgentSerializer.new(ai_agent, root: false),
|
||||||
},
|
},
|
||||||
status: :created
|
status: :created
|
||||||
else
|
else
|
||||||
render_json_error ai_persona
|
render_json_error ai_agent
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_user
|
def create_user
|
||||||
user = @ai_persona.create_user!
|
user = @ai_agent.create_user!
|
||||||
render json: BasicUserSerializer.new(user, root: "user")
|
render json: BasicUserSerializer.new(user, root: "user")
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
if @ai_persona.update(ai_persona_params.except(:rag_uploads))
|
if @ai_agent.update(ai_agent_params.except(:rag_uploads))
|
||||||
RagDocumentFragment.update_target_uploads(@ai_persona, attached_upload_ids)
|
RagDocumentFragment.update_target_uploads(@ai_agent, attached_upload_ids)
|
||||||
|
|
||||||
render json: LocalizedAiPersonaSerializer.new(@ai_persona, root: false)
|
render json: LocalizedAiAgentSerializer.new(@ai_agent, root: false)
|
||||||
else
|
else
|
||||||
render_json_error @ai_persona
|
render_json_error @ai_agent
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
if @ai_persona.destroy
|
if @ai_agent.destroy
|
||||||
head :no_content
|
head :no_content
|
||||||
else
|
else
|
||||||
render_json_error @ai_persona
|
render_json_error @ai_agent
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def stream_reply
|
def stream_reply
|
||||||
persona =
|
agent =
|
||||||
AiPersona.find_by(name: params[:persona_name]) ||
|
AiAgent.find_by(name: params[:agent_name]) ||
|
||||||
AiPersona.find_by(id: params[:persona_id])
|
AiAgent.find_by(id: params[:agent_id])
|
||||||
return render_json_error(I18n.t("discourse_ai.errors.persona_not_found")) if persona.nil?
|
return render_json_error(I18n.t("discourse_ai.errors.agent_not_found")) if agent.nil?
|
||||||
|
|
||||||
return render_json_error(I18n.t("discourse_ai.errors.persona_disabled")) if !persona.enabled
|
return render_json_error(I18n.t("discourse_ai.errors.agent_disabled")) if !agent.enabled
|
||||||
|
|
||||||
if persona.default_llm.blank?
|
if agent.default_llm.blank?
|
||||||
return render_json_error(I18n.t("discourse_ai.errors.no_default_llm"))
|
return render_json_error(I18n.t("discourse_ai.errors.no_default_llm"))
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -107,8 +107,8 @@ module DiscourseAi
|
|||||||
return render_json_error(I18n.t("discourse_ai.errors.no_query_specified"))
|
return render_json_error(I18n.t("discourse_ai.errors.no_query_specified"))
|
||||||
end
|
end
|
||||||
|
|
||||||
if !persona.user_id
|
if !agent.user_id
|
||||||
return render_json_error(I18n.t("discourse_ai.errors.no_user_for_persona"))
|
return render_json_error(I18n.t("discourse_ai.errors.no_user_for_agent"))
|
||||||
end
|
end
|
||||||
|
|
||||||
if !params[:username] && !params[:user_unique_id]
|
if !params[:username] && !params[:user_unique_id]
|
||||||
@ -142,7 +142,7 @@ module DiscourseAi
|
|||||||
|
|
||||||
DiscourseAi::AiBot::ResponseHttpStreamer.queue_streamed_reply(
|
DiscourseAi::AiBot::ResponseHttpStreamer.queue_streamed_reply(
|
||||||
io: io,
|
io: io,
|
||||||
persona: persona,
|
agent: agent,
|
||||||
user: user,
|
user: user,
|
||||||
topic: topic,
|
topic: topic,
|
||||||
query: params[:query].to_s,
|
query: params[:query].to_s,
|
||||||
@ -178,17 +178,17 @@ module DiscourseAi
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_ai_persona
|
def find_ai_agent
|
||||||
@ai_persona = AiPersona.find(params[:id])
|
@ai_agent = AiAgent.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def attached_upload_ids
|
def attached_upload_ids
|
||||||
ai_persona_params[:rag_uploads].to_a.map { |h| h[:id] }
|
ai_agent_params[:rag_uploads].to_a.map { |h| h[:id] }
|
||||||
end
|
end
|
||||||
|
|
||||||
def ai_persona_params
|
def ai_agent_params
|
||||||
permitted =
|
permitted =
|
||||||
params.require(:ai_persona).permit(
|
params.require(:ai_agent).permit(
|
||||||
:name,
|
:name,
|
||||||
:description,
|
:description,
|
||||||
:enabled,
|
:enabled,
|
||||||
@ -209,7 +209,7 @@ module DiscourseAi
|
|||||||
:allow_chat_channel_mentions,
|
:allow_chat_channel_mentions,
|
||||||
:allow_chat_direct_messages,
|
:allow_chat_direct_messages,
|
||||||
:allow_topic_mentions,
|
:allow_topic_mentions,
|
||||||
:allow_personal_messages,
|
:allow_agentl_messages,
|
||||||
:tool_details,
|
:tool_details,
|
||||||
:forced_tool_count,
|
:forced_tool_count,
|
||||||
:force_default_llm,
|
:force_default_llm,
|
||||||
@ -217,15 +217,15 @@ module DiscourseAi
|
|||||||
rag_uploads: [:id],
|
rag_uploads: [:id],
|
||||||
)
|
)
|
||||||
|
|
||||||
if tools = params.dig(:ai_persona, :tools)
|
if tools = params.dig(:ai_agent, :tools)
|
||||||
permitted[:tools] = permit_tools(tools)
|
permitted[:tools] = permit_tools(tools)
|
||||||
end
|
end
|
||||||
|
|
||||||
if response_format = params.dig(:ai_persona, :response_format)
|
if response_format = params.dig(:ai_agent, :response_format)
|
||||||
permitted[:response_format] = permit_response_format(response_format)
|
permitted[:response_format] = permit_response_format(response_format)
|
||||||
end
|
end
|
||||||
|
|
||||||
if examples = params.dig(:ai_persona, :examples)
|
if examples = params.dig(:ai_agent, :examples)
|
||||||
permitted[:examples] = permit_examples(examples)
|
permitted[:examples] = permit_examples(examples)
|
||||||
end
|
end
|
||||||
|
|
@ -17,19 +17,19 @@ module DiscourseAi
|
|||||||
private
|
private
|
||||||
|
|
||||||
def serialize_features(features)
|
def serialize_features(features)
|
||||||
features.map { |feature| feature.merge(persona: serialize_persona(feature[:persona])) }
|
features.map { |feature| feature.merge(agent: serialize_agent(feature[:agent])) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def serialize_feature(feature)
|
def serialize_feature(feature)
|
||||||
return nil if feature.blank?
|
return nil if feature.blank?
|
||||||
|
|
||||||
feature.merge(persona: serialize_persona(feature[:persona]))
|
feature.merge(agent: serialize_agent(feature[:agent]))
|
||||||
end
|
end
|
||||||
|
|
||||||
def serialize_persona(persona)
|
def serialize_agent(agent)
|
||||||
return nil if persona.blank?
|
return nil if agent.blank?
|
||||||
|
|
||||||
serialize_data(persona, AiFeaturesPersonaSerializer, root: false)
|
serialize_data(agent, AiFeaturesAgentSerializer, root: false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -6,8 +6,8 @@ module DiscourseAi
|
|||||||
requires_plugin ::DiscourseAi::PLUGIN_NAME
|
requires_plugin ::DiscourseAi::PLUGIN_NAME
|
||||||
|
|
||||||
def indexing_status_check
|
def indexing_status_check
|
||||||
if params[:target_type] == "AiPersona"
|
if params[:target_type] == "AiAgent"
|
||||||
@target = AiPersona.find(params[:target_id])
|
@target = AiAgent.find(params[:target_id])
|
||||||
elsif params[:target_type] == "AiTool"
|
elsif params[:target_type] == "AiTool"
|
||||||
@target = AiTool.find(params[:target_id])
|
@target = AiTool.find(params[:target_id])
|
||||||
else
|
else
|
||||||
|
@ -46,17 +46,17 @@ module DiscourseAi
|
|||||||
end
|
end
|
||||||
|
|
||||||
def discover
|
def discover
|
||||||
ai_persona =
|
ai_agent =
|
||||||
AiPersona
|
AiAgent
|
||||||
.all_personas(enabled_only: false)
|
.all_agents(enabled_only: false)
|
||||||
.find { |persona| persona.id == SiteSetting.ai_bot_discover_persona.to_i }
|
.find { |agent| agent.id == SiteSetting.ai_bot_discover_agent.to_i }
|
||||||
|
|
||||||
if ai_persona.nil? || !current_user.in_any_groups?(ai_persona.allowed_group_ids.to_a)
|
if ai_agent.nil? || !current_user.in_any_groups?(ai_agent.allowed_group_ids.to_a)
|
||||||
raise Discourse::InvalidAccess.new
|
raise Discourse::InvalidAccess.new
|
||||||
end
|
end
|
||||||
|
|
||||||
if ai_persona.default_llm_id.blank?
|
if ai_agent.default_llm_id.blank?
|
||||||
render_json_error "Discover persona is missing a default LLM model.", status: 503
|
render_json_error "Discover agent is missing a default LLM model.", status: 503
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ module DiscourseAi
|
|||||||
|
|
||||||
user = User.find(params[:user_id])
|
user = User.find(params[:user_id])
|
||||||
|
|
||||||
bot_user_id = AiPersona.find_by(id: SiteSetting.ai_bot_discover_persona).user_id
|
bot_user_id = AiAgent.find_by(id: SiteSetting.ai_bot_discover_agent).user_id
|
||||||
bot_username = User.find_by(id: bot_user_id).username
|
bot_username = User.find_by(id: bot_user_id).username
|
||||||
|
|
||||||
query = params[:query]
|
query = params[:query]
|
||||||
|
@ -11,12 +11,12 @@ module ::Jobs
|
|||||||
message = ::Chat::Message.find_by(id: args[:message_id])
|
message = ::Chat::Message.find_by(id: args[:message_id])
|
||||||
return if message.blank?
|
return if message.blank?
|
||||||
|
|
||||||
personaClass =
|
agentClass =
|
||||||
DiscourseAi::Personas::Persona.find_by(id: args[:persona_id], user: message.user)
|
DiscourseAi::Agents::Agent.find_by(id: args[:agent_id], user: message.user)
|
||||||
return if personaClass.blank?
|
return if agentClass.blank?
|
||||||
|
|
||||||
user = User.find_by(id: personaClass.user_id)
|
user = User.find_by(id: agentClass.user_id)
|
||||||
bot = DiscourseAi::Personas::Bot.as(user, persona: personaClass.new)
|
bot = DiscourseAi::Agents::Bot.as(user, agent: agentClass.new)
|
||||||
|
|
||||||
DiscourseAi::AiBot::Playground.new(bot).reply_to_chat_message(
|
DiscourseAi::AiBot::Playground.new(bot).reply_to_chat_message(
|
||||||
message,
|
message,
|
||||||
|
@ -7,18 +7,18 @@ module ::Jobs
|
|||||||
def execute(args)
|
def execute(args)
|
||||||
return unless bot_user = User.find_by(id: args[:bot_user_id])
|
return unless bot_user = User.find_by(id: args[:bot_user_id])
|
||||||
return unless post = Post.includes(:topic).find_by(id: args[:post_id])
|
return unless post = Post.includes(:topic).find_by(id: args[:post_id])
|
||||||
persona_id = args[:persona_id]
|
agent_id = args[:agent_id]
|
||||||
|
|
||||||
begin
|
begin
|
||||||
persona = DiscourseAi::Personas::Persona.find_by(user: post.user, id: persona_id)
|
agent = DiscourseAi::Agents::Agent.find_by(user: post.user, id: agent_id)
|
||||||
raise DiscourseAi::Personas::Bot::BOT_NOT_FOUND if persona.nil?
|
raise DiscourseAi::Agents::Bot::BOT_NOT_FOUND if agent.nil?
|
||||||
|
|
||||||
bot = DiscourseAi::Personas::Bot.as(bot_user, persona: persona.new)
|
bot = DiscourseAi::Agents::Bot.as(bot_user, agent: agent.new)
|
||||||
|
|
||||||
DiscourseAi::AiBot::Playground.new(bot).reply_to(post)
|
DiscourseAi::AiBot::Playground.new(bot).reply_to(post)
|
||||||
rescue DiscourseAi::Personas::Bot::BOT_NOT_FOUND
|
rescue DiscourseAi::Agents::Bot::BOT_NOT_FOUND
|
||||||
Rails.logger.warn(
|
Rails.logger.warn(
|
||||||
"Bot not found for post #{post.id} - perhaps persona was deleted or bot was disabled",
|
"Bot not found for post #{post.id} - perhaps agent was deleted or bot was disabled",
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -9,8 +9,8 @@ module Jobs
|
|||||||
|
|
||||||
return unless SiteSetting.ai_discord_search_enabled
|
return unless SiteSetting.ai_discord_search_enabled
|
||||||
|
|
||||||
if SiteSetting.ai_discord_search_mode == "persona"
|
if SiteSetting.ai_discord_search_mode == "agent"
|
||||||
DiscourseAi::Discord::Bot::PersonaReplier.new(interaction).handle_interaction!
|
DiscourseAi::Discord::Bot::AgentReplier.new(interaction).handle_interaction!
|
||||||
else
|
else
|
||||||
DiscourseAi::Discord::Bot::Search.new(interaction).handle_interaction!
|
DiscourseAi::Discord::Bot::Search.new(interaction).handle_interaction!
|
||||||
end
|
end
|
||||||
|
@ -8,20 +8,20 @@ module Jobs
|
|||||||
return if (user = User.find_by(id: args[:user_id])).nil?
|
return if (user = User.find_by(id: args[:user_id])).nil?
|
||||||
return if (query = args[:query]).blank?
|
return if (query = args[:query]).blank?
|
||||||
|
|
||||||
ai_persona_klass =
|
ai_agent_klass =
|
||||||
AiPersona
|
AiAgent
|
||||||
.all_personas(enabled_only: false)
|
.all_agents(enabled_only: false)
|
||||||
.find { |persona| persona.id == SiteSetting.ai_bot_discover_persona.to_i }
|
.find { |agent| agent.id == SiteSetting.ai_bot_discover_agent.to_i }
|
||||||
|
|
||||||
if ai_persona_klass.nil? || !user.in_any_groups?(ai_persona_klass.allowed_group_ids.to_a)
|
if ai_agent_klass.nil? || !user.in_any_groups?(ai_agent_klass.allowed_group_ids.to_a)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
return if (llm_model = LlmModel.find_by(id: ai_persona_klass.default_llm_id)).nil?
|
return if (llm_model = LlmModel.find_by(id: ai_agent_klass.default_llm_id)).nil?
|
||||||
|
|
||||||
bot =
|
bot =
|
||||||
DiscourseAi::Personas::Bot.as(
|
DiscourseAi::Agents::Bot.as(
|
||||||
Discourse.system_user,
|
Discourse.system_user,
|
||||||
persona: ai_persona_klass.new,
|
agent: ai_agent_klass.new,
|
||||||
model: llm_model,
|
model: llm_model,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ module Jobs
|
|||||||
base = { query: query, model_used: llm_model.display_name }
|
base = { query: query, model_used: llm_model.display_name }
|
||||||
|
|
||||||
context =
|
context =
|
||||||
DiscourseAi::Personas::BotContext.new(
|
DiscourseAi::Agents::BotContext.new(
|
||||||
messages: [{ type: :user, content: query }],
|
messages: [{ type: :user, content: query }],
|
||||||
skip_tool_details: true,
|
skip_tool_details: true,
|
||||||
)
|
)
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class AiPersona < ActiveRecord::Base
|
class AiAgent < ActiveRecord::Base
|
||||||
# TODO remove this line 01-10-2025
|
# TODO remove this line 01-10-2025
|
||||||
self.ignored_columns = %i[default_llm question_consolidator_llm]
|
self.ignored_columns = %i[default_llm question_consolidator_llm]
|
||||||
|
|
||||||
# places a hard limit, so per site we cache a maximum of 500 classes
|
# places a hard limit, so per site we cache a maximum of 500 classes
|
||||||
MAX_PERSONAS_PER_SITE = 500
|
MAX_AGENTS_PER_SITE = 500
|
||||||
|
|
||||||
validates :name, presence: true, uniqueness: true, length: { maximum: 100 }
|
validates :name, presence: true, uniqueness: true, length: { maximum: 100 }
|
||||||
validates :description, presence: true, length: { maximum: 2000 }
|
validates :description, presence: true, length: { maximum: 2000 }
|
||||||
validates :system_prompt, presence: true, length: { maximum: 10_000_000 }
|
validates :system_prompt, presence: true, length: { maximum: 10_000_000 }
|
||||||
validate :system_persona_unchangeable, on: :update, if: :system
|
validate :system_agent_unchangeable, on: :update, if: :system
|
||||||
validate :chat_preconditions
|
validate :chat_preconditions
|
||||||
validate :allowed_seeded_model, if: :default_llm_id
|
validate :allowed_seeded_model, if: :default_llm_id
|
||||||
validate :well_formated_examples
|
validate :well_formated_examples
|
||||||
@ -41,50 +41,50 @@ class AiPersona < ActiveRecord::Base
|
|||||||
before_destroy :ensure_not_system
|
before_destroy :ensure_not_system
|
||||||
before_update :regenerate_rag_fragments
|
before_update :regenerate_rag_fragments
|
||||||
|
|
||||||
def self.persona_cache
|
def self.agent_cache
|
||||||
@persona_cache ||= ::DiscourseAi::MultisiteHash.new("persona_cache")
|
@agent_cache ||= ::DiscourseAi::MultisiteHash.new("agent_cache")
|
||||||
end
|
end
|
||||||
|
|
||||||
scope :ordered, -> { order("priority DESC, lower(name) ASC") }
|
scope :ordered, -> { order("priority DESC, lower(name) ASC") }
|
||||||
|
|
||||||
def self.all_personas(enabled_only: true)
|
def self.all_agents(enabled_only: true)
|
||||||
persona_cache[:value] ||= AiPersona
|
agent_cache[:value] ||= AiAgent
|
||||||
.ordered
|
.ordered
|
||||||
.all
|
.all
|
||||||
.limit(MAX_PERSONAS_PER_SITE)
|
.limit(MAX_AGENTS_PER_SITE)
|
||||||
.map(&:class_instance)
|
.map(&:class_instance)
|
||||||
|
|
||||||
if enabled_only
|
if enabled_only
|
||||||
persona_cache[:value].select { |p| p.enabled }
|
agent_cache[:value].select { |p| p.enabled }
|
||||||
else
|
else
|
||||||
persona_cache[:value]
|
agent_cache[:value]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.persona_users(user: nil)
|
def self.agent_users(user: nil)
|
||||||
persona_users =
|
agent_users =
|
||||||
persona_cache[:persona_users] ||= AiPersona
|
agent_cache[:agent_users] ||= AiAgent
|
||||||
.where(enabled: true)
|
.where(enabled: true)
|
||||||
.joins(:user)
|
.joins(:user)
|
||||||
.map do |persona|
|
.map do |agent|
|
||||||
{
|
{
|
||||||
id: persona.id,
|
id: agent.id,
|
||||||
user_id: persona.user_id,
|
user_id: agent.user_id,
|
||||||
username: persona.user.username_lower,
|
username: agent.user.username_lower,
|
||||||
allowed_group_ids: persona.allowed_group_ids,
|
allowed_group_ids: agent.allowed_group_ids,
|
||||||
default_llm_id: persona.default_llm_id,
|
default_llm_id: agent.default_llm_id,
|
||||||
force_default_llm: persona.force_default_llm,
|
force_default_llm: agent.force_default_llm,
|
||||||
allow_chat_channel_mentions: persona.allow_chat_channel_mentions,
|
allow_chat_channel_mentions: agent.allow_chat_channel_mentions,
|
||||||
allow_chat_direct_messages: persona.allow_chat_direct_messages,
|
allow_chat_direct_messages: agent.allow_chat_direct_messages,
|
||||||
allow_topic_mentions: persona.allow_topic_mentions,
|
allow_topic_mentions: agent.allow_topic_mentions,
|
||||||
allow_personal_messages: persona.allow_personal_messages,
|
allow_agentl_messages: agent.allow_agentl_messages,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
if user
|
if user
|
||||||
persona_users.select { |persona_user| user.in_any_groups?(persona_user[:allowed_group_ids]) }
|
agent_users.select { |agent_user| user.in_any_groups?(agent_user[:allowed_group_ids]) }
|
||||||
else
|
else
|
||||||
persona_users
|
agent_users
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -93,31 +93,31 @@ class AiPersona < ActiveRecord::Base
|
|||||||
allow_chat_channel_mentions: false,
|
allow_chat_channel_mentions: false,
|
||||||
allow_chat_direct_messages: false,
|
allow_chat_direct_messages: false,
|
||||||
allow_topic_mentions: false,
|
allow_topic_mentions: false,
|
||||||
allow_personal_messages: false
|
allow_agentl_messages: false
|
||||||
)
|
)
|
||||||
index =
|
index =
|
||||||
"modality-#{allow_chat_channel_mentions}-#{allow_chat_direct_messages}-#{allow_topic_mentions}-#{allow_personal_messages}"
|
"modality-#{allow_chat_channel_mentions}-#{allow_chat_direct_messages}-#{allow_topic_mentions}-#{allow_agentl_messages}"
|
||||||
|
|
||||||
personas =
|
agents =
|
||||||
persona_cache[index.to_sym] ||= persona_users.select do |persona|
|
agent_cache[index.to_sym] ||= agent_users.select do |agent|
|
||||||
next true if allow_chat_channel_mentions && persona[:allow_chat_channel_mentions]
|
next true if allow_chat_channel_mentions && agent[:allow_chat_channel_mentions]
|
||||||
next true if allow_chat_direct_messages && persona[:allow_chat_direct_messages]
|
next true if allow_chat_direct_messages && agent[:allow_chat_direct_messages]
|
||||||
next true if allow_topic_mentions && persona[:allow_topic_mentions]
|
next true if allow_topic_mentions && agent[:allow_topic_mentions]
|
||||||
next true if allow_personal_messages && persona[:allow_personal_messages]
|
next true if allow_agentl_messages && agent[:allow_agentl_messages]
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
if user
|
if user
|
||||||
personas.select { |u| user.in_any_groups?(u[:allowed_group_ids]) }
|
agents.select { |u| user.in_any_groups?(u[:allowed_group_ids]) }
|
||||||
else
|
else
|
||||||
personas
|
agents
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
after_commit :bump_cache
|
after_commit :bump_cache
|
||||||
|
|
||||||
def bump_cache
|
def bump_cache
|
||||||
self.class.persona_cache.flush!
|
self.class.agent_cache.flush!
|
||||||
end
|
end
|
||||||
|
|
||||||
def tools_can_not_be_duplicated
|
def tools_can_not_be_duplicated
|
||||||
@ -138,7 +138,7 @@ class AiPersona < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
if seen_tools.include?(inner_name)
|
if seen_tools.include?(inner_name)
|
||||||
errors.add(:tools, I18n.t("discourse_ai.ai_bot.personas.cannot_have_duplicate_tools"))
|
errors.add(:tools, I18n.t("discourse_ai.ai_bot.agents.cannot_have_duplicate_tools"))
|
||||||
break
|
break
|
||||||
else
|
else
|
||||||
seen_tools.add(inner_name)
|
seen_tools.add(inner_name)
|
||||||
@ -154,7 +154,7 @@ class AiPersona < ActiveRecord::Base
|
|||||||
.pluck(:tool_name)
|
.pluck(:tool_name)
|
||||||
.each do |tool_name|
|
.each do |tool_name|
|
||||||
if builtin_tool_names.include?(tool_name.downcase)
|
if builtin_tool_names.include?(tool_name.downcase)
|
||||||
errors.add(:tools, I18n.t("discourse_ai.ai_bot.personas.cannot_have_duplicate_tools"))
|
errors.add(:tools, I18n.t("discourse_ai.ai_bot.agents.cannot_have_duplicate_tools"))
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -176,7 +176,7 @@ class AiPersona < ActiveRecord::Base
|
|||||||
allow_chat_channel_mentions
|
allow_chat_channel_mentions
|
||||||
allow_chat_direct_messages
|
allow_chat_direct_messages
|
||||||
allow_topic_mentions
|
allow_topic_mentions
|
||||||
allow_personal_messages
|
allow_agentl_messages
|
||||||
force_default_llm
|
force_default_llm
|
||||||
name
|
name
|
||||||
description
|
description
|
||||||
@ -208,14 +208,14 @@ class AiPersona < ActiveRecord::Base
|
|||||||
if inner_name.start_with?("custom-")
|
if inner_name.start_with?("custom-")
|
||||||
custom_tool_id = inner_name.split("-", 2).last.to_i
|
custom_tool_id = inner_name.split("-", 2).last.to_i
|
||||||
if AiTool.exists?(id: custom_tool_id, enabled: true)
|
if AiTool.exists?(id: custom_tool_id, enabled: true)
|
||||||
klass = DiscourseAi::Personas::Tools::Custom.class_instance(custom_tool_id)
|
klass = DiscourseAi::Agents::Tools::Custom.class_instance(custom_tool_id)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
inner_name = inner_name.gsub("Tool", "")
|
inner_name = inner_name.gsub("Tool", "")
|
||||||
inner_name = "List#{inner_name}" if %w[Categories Tags].include?(inner_name)
|
inner_name = "List#{inner_name}" if %w[Categories Tags].include?(inner_name)
|
||||||
|
|
||||||
begin
|
begin
|
||||||
klass = "DiscourseAi::Personas::Tools::#{inner_name}".constantize
|
klass = "DiscourseAi::Agents::Tools::#{inner_name}".constantize
|
||||||
options[klass] = current_options if current_options
|
options[klass] = current_options if current_options
|
||||||
rescue StandardError
|
rescue StandardError
|
||||||
end
|
end
|
||||||
@ -225,14 +225,14 @@ class AiPersona < ActiveRecord::Base
|
|||||||
klass
|
klass
|
||||||
end
|
end
|
||||||
|
|
||||||
persona_class = DiscourseAi::Personas::Persona.system_personas_by_id[self.id]
|
agent_class = DiscourseAi::Agents::Agent.system_agents_by_id[self.id]
|
||||||
if persona_class
|
if agent_class
|
||||||
return(
|
return(
|
||||||
# we need a new copy so we don't leak information
|
# we need a new copy so we don't leak information
|
||||||
# across sites
|
# across sites
|
||||||
Class.new(persona_class) do
|
Class.new(agent_class) do
|
||||||
# required for localization
|
# required for localization
|
||||||
define_singleton_method(:to_s) { persona_class.to_s }
|
define_singleton_method(:to_s) { agent_class.to_s }
|
||||||
instance_attributes.each do |key, value|
|
instance_attributes.each do |key, value|
|
||||||
# description/name are localized
|
# description/name are localized
|
||||||
define_singleton_method(key) { value } if key != :description && key != :name
|
define_singleton_method(key) { value } if key != :description && key != :name
|
||||||
@ -242,9 +242,9 @@ class AiPersona < ActiveRecord::Base
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
ai_persona_id = self.id
|
ai_agent_id = self.id
|
||||||
|
|
||||||
Class.new(DiscourseAi::Personas::Persona) do
|
Class.new(DiscourseAi::Agents::Agent) do
|
||||||
instance_attributes.each { |key, value| define_singleton_method(key) { value } }
|
instance_attributes.each { |key, value| define_singleton_method(key) { value } }
|
||||||
|
|
||||||
define_singleton_method(:to_s) do
|
define_singleton_method(:to_s) do
|
||||||
@ -254,24 +254,24 @@ class AiPersona < ActiveRecord::Base
|
|||||||
define_singleton_method(:inspect) { to_s }
|
define_singleton_method(:inspect) { to_s }
|
||||||
|
|
||||||
define_method(:initialize) do |*args, **kwargs|
|
define_method(:initialize) do |*args, **kwargs|
|
||||||
@ai_persona = AiPersona.find_by(id: ai_persona_id)
|
@ai_agent = AiAgent.find_by(id: ai_agent_id)
|
||||||
super(*args, **kwargs)
|
super(*args, **kwargs)
|
||||||
end
|
end
|
||||||
|
|
||||||
define_method(:tools) { tools }
|
define_method(:tools) { tools }
|
||||||
define_method(:force_tool_use) { force_tool_use }
|
define_method(:force_tool_use) { force_tool_use }
|
||||||
define_method(:forced_tool_count) { @ai_persona&.forced_tool_count }
|
define_method(:forced_tool_count) { @ai_agent&.forced_tool_count }
|
||||||
define_method(:options) { options }
|
define_method(:options) { options }
|
||||||
define_method(:temperature) { @ai_persona&.temperature }
|
define_method(:temperature) { @ai_agent&.temperature }
|
||||||
define_method(:top_p) { @ai_persona&.top_p }
|
define_method(:top_p) { @ai_agent&.top_p }
|
||||||
define_method(:system_prompt) { @ai_persona&.system_prompt || "You are a helpful bot." }
|
define_method(:system_prompt) { @ai_agent&.system_prompt || "You are a helpful bot." }
|
||||||
define_method(:uploads) { @ai_persona&.uploads }
|
define_method(:uploads) { @ai_agent&.uploads }
|
||||||
define_method(:response_format) { @ai_persona&.response_format }
|
define_method(:response_format) { @ai_agent&.response_format }
|
||||||
define_method(:examples) { @ai_persona&.examples }
|
define_method(:examples) { @ai_agent&.examples }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
FIRST_PERSONA_USER_ID = -1200
|
FIRST_AGENT_USER_ID = -1200
|
||||||
|
|
||||||
def create_user!
|
def create_user!
|
||||||
raise "User already exists" if user_id && User.exists?(user_id)
|
raise "User already exists" if user_id && User.exists?(user_id)
|
||||||
@ -279,7 +279,7 @@ class AiPersona < ActiveRecord::Base
|
|||||||
# find the first id smaller than FIRST_USER_ID that is not taken
|
# find the first id smaller than FIRST_USER_ID that is not taken
|
||||||
id = nil
|
id = nil
|
||||||
|
|
||||||
id = DB.query_single(<<~SQL, FIRST_PERSONA_USER_ID, FIRST_PERSONA_USER_ID - 200).first
|
id = DB.query_single(<<~SQL, FIRST_AGENT_USER_ID, FIRST_AGENT_USER_ID - 200).first
|
||||||
WITH seq AS (
|
WITH seq AS (
|
||||||
SELECT generate_series(?, ?, -1) AS id
|
SELECT generate_series(?, ?, -1) AS id
|
||||||
)
|
)
|
||||||
@ -323,12 +323,12 @@ class AiPersona < ActiveRecord::Base
|
|||||||
allow_chat_channel_mentions || allow_chat_direct_messages || allow_topic_mentions ||
|
allow_chat_channel_mentions || allow_chat_direct_messages || allow_topic_mentions ||
|
||||||
force_default_llm
|
force_default_llm
|
||||||
) && !default_llm_id
|
) && !default_llm_id
|
||||||
errors.add(:default_llm, I18n.t("discourse_ai.ai_bot.personas.default_llm_required"))
|
errors.add(:default_llm, I18n.t("discourse_ai.ai_bot.agents.default_llm_required"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def system_persona_unchangeable
|
def system_agent_unchangeable
|
||||||
error_msg = I18n.t("discourse_ai.ai_bot.personas.cannot_edit_system_persona")
|
error_msg = I18n.t("discourse_ai.ai_bot.agents.cannot_edit_system_agent")
|
||||||
|
|
||||||
if top_p_changed? || temperature_changed? || system_prompt_changed? || name_changed? ||
|
if top_p_changed? || temperature_changed? || system_prompt_changed? || name_changed? ||
|
||||||
description_changed?
|
description_changed?
|
||||||
@ -356,7 +356,7 @@ class AiPersona < ActiveRecord::Base
|
|||||||
|
|
||||||
def ensure_not_system
|
def ensure_not_system
|
||||||
if system
|
if system
|
||||||
errors.add(:base, I18n.t("discourse_ai.ai_bot.personas.cannot_delete_system_persona"))
|
errors.add(:base, I18n.t("discourse_ai.ai_bot.agents.cannot_delete_system_agent"))
|
||||||
throw :abort
|
throw :abort
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -380,13 +380,13 @@ class AiPersona < ActiveRecord::Base
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
errors.add(:examples, I18n.t("discourse_ai.personas.malformed_examples"))
|
errors.add(:examples, I18n.t("discourse_ai.agents.malformed_examples"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
#
|
#
|
||||||
# Table name: ai_personas
|
# Table name: ai_agents
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# name :string(100) not null
|
# name :string(100) not null
|
||||||
@ -414,7 +414,7 @@ end
|
|||||||
# allow_chat_channel_mentions :boolean default(FALSE), not null
|
# allow_chat_channel_mentions :boolean default(FALSE), not null
|
||||||
# allow_chat_direct_messages :boolean default(FALSE), not null
|
# allow_chat_direct_messages :boolean default(FALSE), not null
|
||||||
# allow_topic_mentions :boolean default(FALSE), not null
|
# allow_topic_mentions :boolean default(FALSE), not null
|
||||||
# allow_personal_messages :boolean default(TRUE), not null
|
# allow_agentl_messages :boolean default(TRUE), not null
|
||||||
# force_default_llm :boolean default(FALSE), not null
|
# force_default_llm :boolean default(FALSE), not null
|
||||||
# rag_llm_model_id :bigint
|
# rag_llm_model_id :bigint
|
||||||
# default_llm_id :bigint
|
# default_llm_id :bigint
|
||||||
@ -424,5 +424,5 @@ end
|
|||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# index_ai_personas_on_name (name) UNIQUE
|
# index_ai_agents_on_name (name) UNIQUE
|
||||||
#
|
#
|
@ -36,7 +36,7 @@ class AiTool < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def runner(parameters, llm:, bot_user:, context: nil)
|
def runner(parameters, llm:, bot_user:, context: nil)
|
||||||
DiscourseAi::Personas::ToolRunner.new(
|
DiscourseAi::Agents::ToolRunner.new(
|
||||||
parameters: parameters,
|
parameters: parameters,
|
||||||
llm: llm,
|
llm: llm,
|
||||||
bot_user: bot_user,
|
bot_user: bot_user,
|
||||||
@ -45,10 +45,10 @@ class AiTool < ActiveRecord::Base
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
after_commit :bump_persona_cache
|
after_commit :bump_agent_cache
|
||||||
|
|
||||||
def bump_persona_cache
|
def bump_agent_cache
|
||||||
AiPersona.persona_cache.flush!
|
AiAgent.agent_cache.flush!
|
||||||
end
|
end
|
||||||
|
|
||||||
def regenerate_rag_fragments
|
def regenerate_rag_fragments
|
||||||
@ -176,11 +176,11 @@ class AiTool < ActiveRecord::Base
|
|||||||
* user_id_or_username (number | string): The ID or username of the user.
|
* user_id_or_username (number | string): The ID or username of the user.
|
||||||
* Returns: Object (User details using UserSerializer structure) or null if not found.
|
* Returns: Object (User details using UserSerializer structure) or null if not found.
|
||||||
*
|
*
|
||||||
* discourse.getPersona(name): Gets an object representing another AI Persona configured on the site.
|
* discourse.getAgent(name): Gets an object representing another AI Agent configured on the site.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* name (string): The name of the target persona.
|
* name (string): The name of the target agent.
|
||||||
* Returns: Object { respondTo: function(params) } or null if persona not found.
|
* Returns: Object { respondTo: function(params) } or null if agent not found.
|
||||||
* respondTo(params): Instructs the target persona to generate a response within the current context (e.g., replying to the same post or chat message).
|
* respondTo(params): Instructs the target agent to generate a response within the current context (e.g., replying to the same post or chat message).
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* params (Object, optional): { instructions: string, whisper: boolean }
|
* params (Object, optional): { instructions: string, whisper: boolean }
|
||||||
* Returns: { success: boolean, post_id?: number, post_number?: number, message_id?: number } or { error: string }
|
* Returns: { success: boolean, post_id?: number, post_number?: number, message_id?: number } or { error: string }
|
||||||
@ -201,7 +201,7 @@ class AiTool < ActiveRecord::Base
|
|||||||
* private_message (boolean): Whether the context is a private message (in Post context).
|
* private_message (boolean): Whether the context is a private message (in Post context).
|
||||||
* message_id (number): ID of the chat message triggering the tool (if in Chat context).
|
* message_id (number): ID of the chat message triggering the tool (if in Chat context).
|
||||||
* channel_id (number): ID of the chat channel (if in Chat context).
|
* channel_id (number): ID of the chat channel (if in Chat context).
|
||||||
* user (Object): Details of the user invoking the tool/persona (structure may vary, often null or SystemUser details unless explicitly passed).
|
* user (Object): Details of the user invoking the tool/agent (structure may vary, often null or SystemUser details unless explicitly passed).
|
||||||
* participants (string): Comma-separated list of usernames in a PM (if applicable).
|
* participants (string): Comma-separated list of usernames in a PM (if applicable).
|
||||||
* // ... other potential context-specific properties added by the calling environment.
|
* // ... other potential context-specific properties added by the calling environment.
|
||||||
*
|
*
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class RagDocumentFragment < ActiveRecord::Base
|
class RagDocumentFragment < ActiveRecord::Base
|
||||||
# TODO Jan 2025 - remove
|
# TODO Jan 2025 - remove
|
||||||
self.ignored_columns = %i[ai_persona_id]
|
self.ignored_columns = %i[ai_agent_id]
|
||||||
|
|
||||||
belongs_to :upload
|
belongs_to :upload
|
||||||
belongs_to :target, polymorphic: true
|
belongs_to :target, polymorphic: true
|
||||||
@ -38,7 +38,7 @@ class RagDocumentFragment < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def indexing_status(persona, uploads)
|
def indexing_status(agent, uploads)
|
||||||
embeddings_table = DiscourseAi::Embeddings::Schema.for(self).table
|
embeddings_table = DiscourseAi::Embeddings::Schema.for(self).table
|
||||||
|
|
||||||
results =
|
results =
|
||||||
@ -56,8 +56,8 @@ class RagDocumentFragment < ActiveRecord::Base
|
|||||||
WHERE uploads.id IN (:upload_ids)
|
WHERE uploads.id IN (:upload_ids)
|
||||||
GROUP BY uploads.id
|
GROUP BY uploads.id
|
||||||
SQL
|
SQL
|
||||||
target_id: persona.id,
|
target_id: agent.id,
|
||||||
target_type: persona.class.to_s,
|
target_type: agent.class.to_s,
|
||||||
upload_ids: uploads.map(&:id),
|
upload_ids: uploads.map(&:id),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -51,13 +51,13 @@ class SharedAiConversation < ActiveRecord::Base
|
|||||||
# but this name works
|
# but this name works
|
||||||
class SharedPost
|
class SharedPost
|
||||||
attr_accessor :user
|
attr_accessor :user
|
||||||
attr_reader :id, :user_id, :created_at, :cooked, :persona
|
attr_reader :id, :user_id, :created_at, :cooked, :agent
|
||||||
def initialize(post)
|
def initialize(post)
|
||||||
@id = post[:id]
|
@id = post[:id]
|
||||||
@user_id = post[:user_id]
|
@user_id = post[:user_id]
|
||||||
@created_at = DateTime.parse(post[:created_at])
|
@created_at = DateTime.parse(post[:created_at])
|
||||||
@cooked = post[:cooked]
|
@cooked = post[:cooked]
|
||||||
@persona = post[:persona]
|
@agent = post[:agent]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -140,9 +140,9 @@ class SharedAiConversation < ActiveRecord::Base
|
|||||||
llm_name = ActiveSupport::Inflector.humanize(llm_name) if llm_name
|
llm_name = ActiveSupport::Inflector.humanize(llm_name) if llm_name
|
||||||
llm_name ||= I18n.t("discourse_ai.unknown_model")
|
llm_name ||= I18n.t("discourse_ai.unknown_model")
|
||||||
|
|
||||||
persona = nil
|
agent = nil
|
||||||
if persona_id = topic.custom_fields["ai_persona_id"]
|
if agent_id = topic.custom_fields["ai_agent_id"]
|
||||||
persona = AiPersona.find_by(id: persona_id.to_i)&.name
|
agent = AiAgent.find_by(id: agent_id.to_i)&.name
|
||||||
end
|
end
|
||||||
|
|
||||||
posts =
|
posts =
|
||||||
@ -167,7 +167,7 @@ class SharedAiConversation < ActiveRecord::Base
|
|||||||
cooked: cook_artifacts(post),
|
cooked: cook_artifacts(post),
|
||||||
}
|
}
|
||||||
|
|
||||||
mapped[:persona] = persona if ai_bot_participant&.id == post.user_id
|
mapped[:agent] = agent if ai_bot_participant&.id == post.user_id
|
||||||
mapped[:username] = post.user&.username if include_usernames
|
mapped[:username] = post.user&.username if include_usernames
|
||||||
mapped
|
mapped
|
||||||
end,
|
end,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class AiFeaturesPersonaSerializer < ApplicationSerializer
|
class AiFeaturesAgentSerializer < ApplicationSerializer
|
||||||
attributes :id, :name, :system_prompt, :allowed_groups, :enabled
|
attributes :id, :name, :system_prompt, :allowed_groups, :enabled
|
||||||
|
|
||||||
def allowed_groups
|
def allowed_groups
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class LlmModelSerializer < ApplicationSerializer
|
class LlmModelSerializer < ApplicationSerializer
|
||||||
# TODO: we probably should rename the table LlmModel to AiLlm
|
# TODO: we probably should rename the table LlmModel to AiLlm
|
||||||
# it is consistent with AiPersona and AiTool
|
# it is consistent with AiAgent and AiTool
|
||||||
# LLM model is a bit confusing given that large langauge model model is a confusing
|
# LLM model is a bit confusing given that large langauge model model is a confusing
|
||||||
# name
|
# name
|
||||||
root "ai_llm"
|
root "ai_llm"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class LocalizedAiPersonaSerializer < ApplicationSerializer
|
class LocalizedAiAgentSerializer < ApplicationSerializer
|
||||||
root "ai_persona"
|
root "ai_agent"
|
||||||
|
|
||||||
attributes :id,
|
attributes :id,
|
||||||
:name,
|
:name,
|
||||||
@ -29,7 +29,7 @@ class LocalizedAiPersonaSerializer < ApplicationSerializer
|
|||||||
:allow_chat_channel_mentions,
|
:allow_chat_channel_mentions,
|
||||||
:allow_chat_direct_messages,
|
:allow_chat_direct_messages,
|
||||||
:allow_topic_mentions,
|
:allow_topic_mentions,
|
||||||
:allow_personal_messages,
|
:allow_agentl_messages,
|
||||||
:force_default_llm,
|
:force_default_llm,
|
||||||
:response_format,
|
:response_format,
|
||||||
:examples
|
:examples
|
@ -43,8 +43,8 @@
|
|||||||
<article class="post">
|
<article class="post">
|
||||||
<header class="post__header">
|
<header class="post__header">
|
||||||
<span class="post__user"><%= post.user.username %></span>
|
<span class="post__user"><%= post.user.username %></span>
|
||||||
<%if post.persona.present? %>
|
<%if post.agent.present? %>
|
||||||
<span class="post__persona"><%= post.persona %></span>
|
<span class="post__agent"><%= post.agent %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<time class="post__date" datetime="<%= post.created_at.iso8601 %>"><%= post.created_at.strftime('%Y-%m-%d') %></time>
|
<time class="post__date" datetime="<%= post.created_at.iso8601 %>"><%= post.created_at.strftime('%Y-%m-%d') %></time>
|
||||||
</header>
|
</header>
|
||||||
|
@ -4,7 +4,7 @@ export default {
|
|||||||
path: "/plugins",
|
path: "/plugins",
|
||||||
|
|
||||||
map() {
|
map() {
|
||||||
this.route("discourse-ai-personas", { path: "ai-personas" }, function () {
|
this.route("discourse-ai-agents", { path: "ai-agents" }, function () {
|
||||||
this.route("new");
|
this.route("new");
|
||||||
this.route("edit", { path: "/:id/edit" });
|
this.route("edit", { path: "/:id/edit" });
|
||||||
});
|
});
|
||||||
|
@ -16,6 +16,6 @@ export default class Adapter extends RestAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apiNameFor() {
|
apiNameFor() {
|
||||||
return "ai-persona";
|
return "ai-agent";
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -29,7 +29,7 @@ const CREATE_ATTRIBUTES = [
|
|||||||
"allow_chat",
|
"allow_chat",
|
||||||
"tool_details",
|
"tool_details",
|
||||||
"forced_tool_count",
|
"forced_tool_count",
|
||||||
"allow_personal_messages",
|
"allow_agentl_messages",
|
||||||
"allow_topic_mentions",
|
"allow_topic_mentions",
|
||||||
"allow_chat_channel_mentions",
|
"allow_chat_channel_mentions",
|
||||||
"allow_chat_direct_messages",
|
"allow_chat_direct_messages",
|
||||||
@ -58,16 +58,16 @@ const SYSTEM_ATTRIBUTES = [
|
|||||||
"rag_llm_model_id",
|
"rag_llm_model_id",
|
||||||
"question_consolidator_llm_id",
|
"question_consolidator_llm_id",
|
||||||
"tool_details",
|
"tool_details",
|
||||||
"allow_personal_messages",
|
"allow_agentl_messages",
|
||||||
"allow_topic_mentions",
|
"allow_topic_mentions",
|
||||||
"allow_chat_channel_mentions",
|
"allow_chat_channel_mentions",
|
||||||
"allow_chat_direct_messages",
|
"allow_chat_direct_messages",
|
||||||
];
|
];
|
||||||
|
|
||||||
export default class AiPersona extends RestModel {
|
export default class AiAgent extends RestModel {
|
||||||
async createUser() {
|
async createUser() {
|
||||||
const result = await ajax(
|
const result = await ajax(
|
||||||
`/admin/plugins/discourse-ai/ai-personas/${this.id}/create-user.json`,
|
`/admin/plugins/discourse-ai/ai-agents/${this.id}/create-user.json`,
|
||||||
{
|
{
|
||||||
type: "POST",
|
type: "POST",
|
||||||
}
|
}
|
||||||
@ -143,10 +143,10 @@ export default class AiPersona extends RestModel {
|
|||||||
fromPOJO(data) {
|
fromPOJO(data) {
|
||||||
const dataClone = JSON.parse(JSON.stringify(data));
|
const dataClone = JSON.parse(JSON.stringify(data));
|
||||||
|
|
||||||
const persona = AiPersona.create(dataClone);
|
const agent = AiAgent.create(dataClone);
|
||||||
persona.tools = this.flattenedToolStructure(dataClone);
|
agent.tools = this.flattenedToolStructure(dataClone);
|
||||||
|
|
||||||
return persona;
|
return agent;
|
||||||
}
|
}
|
||||||
|
|
||||||
toPOJO() {
|
toPOJO() {
|
@ -8,8 +8,8 @@ export default class AiFeature extends RestModel {
|
|||||||
"ref",
|
"ref",
|
||||||
"description",
|
"description",
|
||||||
"enable_setting",
|
"enable_setting",
|
||||||
"persona",
|
"agent",
|
||||||
"persona_setting"
|
"agent_setting"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,15 +15,15 @@ import Group from "discourse/models/group";
|
|||||||
import { i18n } from "discourse-i18n";
|
import { i18n } from "discourse-i18n";
|
||||||
import AdminUser from "admin/models/admin-user";
|
import AdminUser from "admin/models/admin-user";
|
||||||
import GroupChooser from "select-kit/components/group-chooser";
|
import GroupChooser from "select-kit/components/group-chooser";
|
||||||
import AiPersonaResponseFormatEditor from "../components/modal/ai-persona-response-format-editor";
|
import AiAgentResponseFormatEditor from "../components/modal/ai-agent-response-format-editor";
|
||||||
import AiLlmSelector from "./ai-llm-selector";
|
import AiLlmSelector from "./ai-llm-selector";
|
||||||
import AiPersonaCollapsableExample from "./ai-persona-example";
|
import AiAgentCollapsableExample from "./ai-agent-example";
|
||||||
import AiPersonaToolOptions from "./ai-persona-tool-options";
|
import AiAgentToolOptions from "./ai-agent-tool-options";
|
||||||
import AiToolSelector from "./ai-tool-selector";
|
import AiToolSelector from "./ai-tool-selector";
|
||||||
import RagOptionsFk from "./rag-options-fk";
|
import RagOptionsFk from "./rag-options-fk";
|
||||||
import RagUploader from "./rag-uploader";
|
import RagUploader from "./rag-uploader";
|
||||||
|
|
||||||
export default class PersonaEditor extends Component {
|
export default class AgentEditor extends Component {
|
||||||
@service router;
|
@service router;
|
||||||
@service store;
|
@service store;
|
||||||
@service dialog;
|
@service dialog;
|
||||||
@ -59,12 +59,12 @@ export default class PersonaEditor extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get allTools() {
|
get allTools() {
|
||||||
return this.args.personas.resultSetMeta.tools;
|
return this.args.agents.resultSetMeta.tools;
|
||||||
}
|
}
|
||||||
|
|
||||||
get maxPixelValues() {
|
get maxPixelValues() {
|
||||||
const l = (key) =>
|
const l = (key) =>
|
||||||
i18n(`discourse_ai.ai_persona.vision_max_pixel_sizes.${key}`);
|
i18n(`discourse_ai.ai_agent.vision_max_pixel_sizes.${key}`);
|
||||||
return [
|
return [
|
||||||
{ name: l("low"), id: 65536 },
|
{ name: l("low"), id: 65536 },
|
||||||
{ name: l("medium"), id: 262144 },
|
{ name: l("medium"), id: 262144 },
|
||||||
@ -76,14 +76,14 @@ export default class PersonaEditor extends Component {
|
|||||||
const content = [
|
const content = [
|
||||||
{
|
{
|
||||||
id: -1,
|
id: -1,
|
||||||
name: i18n("discourse_ai.ai_persona.tool_strategies.all"),
|
name: i18n("discourse_ai.ai_agent.tool_strategies.all"),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
[1, 2, 5].forEach((i) => {
|
[1, 2, 5].forEach((i) => {
|
||||||
content.push({
|
content.push({
|
||||||
id: i,
|
id: i,
|
||||||
name: i18n("discourse_ai.ai_persona.tool_strategies.replies", {
|
name: i18n("discourse_ai.ai_agent.tool_strategies.replies", {
|
||||||
count: i,
|
count: i,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@ -112,23 +112,23 @@ export default class PersonaEditor extends Component {
|
|||||||
this.isSaving = true;
|
this.isSaving = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const personaToSave = Object.assign(
|
const agentToSave = Object.assign(
|
||||||
this.args.model,
|
this.args.model,
|
||||||
this.args.model.fromPOJO(data)
|
this.args.model.fromPOJO(data)
|
||||||
);
|
);
|
||||||
|
|
||||||
await personaToSave.save();
|
await agentToSave.save();
|
||||||
this.#sortPersonas();
|
this.#sortAgents();
|
||||||
|
|
||||||
if (isNew && this.args.model.rag_uploads.length === 0) {
|
if (isNew && this.args.model.rag_uploads.length === 0) {
|
||||||
this.args.personas.addObject(personaToSave);
|
this.args.agents.addObject(agentToSave);
|
||||||
this.router.transitionTo(
|
this.router.transitionTo(
|
||||||
"adminPlugins.show.discourse-ai-personas.edit",
|
"adminPlugins.show.discourse-ai-agents.edit",
|
||||||
personaToSave
|
agentToSave
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.toasts.success({
|
this.toasts.success({
|
||||||
data: { message: i18n("discourse_ai.ai_persona.saved") },
|
data: { message: i18n("discourse_ai.ai_agent.saved") },
|
||||||
duration: 2000,
|
duration: 2000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -151,12 +151,12 @@ export default class PersonaEditor extends Component {
|
|||||||
@action
|
@action
|
||||||
delete() {
|
delete() {
|
||||||
return this.dialog.confirm({
|
return this.dialog.confirm({
|
||||||
message: i18n("discourse_ai.ai_persona.confirm_delete"),
|
message: i18n("discourse_ai.ai_agent.confirm_delete"),
|
||||||
didConfirm: () => {
|
didConfirm: () => {
|
||||||
return this.args.model.destroyRecord().then(() => {
|
return this.args.model.destroyRecord().then(() => {
|
||||||
this.args.personas.removeObject(this.args.model);
|
this.args.agents.removeObject(this.args.model);
|
||||||
this.router.transitionTo(
|
this.router.transitionTo(
|
||||||
"adminPlugins.show.discourse-ai-personas.index"
|
"adminPlugins.show.discourse-ai-agents.index"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -259,7 +259,7 @@ export default class PersonaEditor extends Component {
|
|||||||
return updatedOptions;
|
return updatedOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
async persistField(dirtyData, field, newValue, sortPersonas) {
|
async persistField(dirtyData, field, newValue, sortAgents) {
|
||||||
if (!this.args.model.isNew) {
|
if (!this.args.model.isNew) {
|
||||||
const updatedDirtyData = Object.assign({}, dirtyData);
|
const updatedDirtyData = Object.assign({}, dirtyData);
|
||||||
updatedDirtyData[field] = newValue;
|
updatedDirtyData[field] = newValue;
|
||||||
@ -270,8 +270,8 @@ export default class PersonaEditor extends Component {
|
|||||||
|
|
||||||
this.dirtyFormData = updatedDirtyData;
|
this.dirtyFormData = updatedDirtyData;
|
||||||
await this.args.model.update(args);
|
await this.args.model.update(args);
|
||||||
if (sortPersonas) {
|
if (sortAgents) {
|
||||||
this.#sortPersonas();
|
this.#sortAgents();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
popupAjaxError(e);
|
popupAjaxError(e);
|
||||||
@ -279,8 +279,8 @@ export default class PersonaEditor extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#sortPersonas() {
|
#sortAgents() {
|
||||||
const sorted = this.args.personas.toArray().sort((a, b) => {
|
const sorted = this.args.agents.toArray().sort((a, b) => {
|
||||||
if (a.priority && !b.priority) {
|
if (a.priority && !b.priority) {
|
||||||
return -1;
|
return -1;
|
||||||
} else if (!a.priority && b.priority) {
|
} else if (!a.priority && b.priority) {
|
||||||
@ -289,20 +289,20 @@ export default class PersonaEditor extends Component {
|
|||||||
return a.name.localeCompare(b.name);
|
return a.name.localeCompare(b.name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.args.personas.clear();
|
this.args.agents.clear();
|
||||||
this.args.personas.setObjects(sorted);
|
this.args.agents.setObjects(sorted);
|
||||||
}
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BackButton
|
<BackButton
|
||||||
@route="adminPlugins.show.discourse-ai-personas"
|
@route="adminPlugins.show.discourse-ai-agents"
|
||||||
@label="discourse_ai.ai_persona.back"
|
@label="discourse_ai.ai_agent.back"
|
||||||
/>
|
/>
|
||||||
<div class="ai-persona-editor" {{didInsert this.updateAllGroups @model.id}}>
|
<div class="ai-agent-editor" {{didInsert this.updateAllGroups @model.id}}>
|
||||||
<Form @onSubmit={{this.save}} @data={{this.formData}} as |form data|>
|
<Form @onSubmit={{this.save}} @data={{this.formData}} as |form data|>
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="name"
|
@name="name"
|
||||||
@title={{i18n "discourse_ai.ai_persona.name"}}
|
@title={{i18n "discourse_ai.ai_agent.name"}}
|
||||||
@validation="required|length:1,100"
|
@validation="required|length:1,100"
|
||||||
@disabled={{data.system}}
|
@disabled={{data.system}}
|
||||||
@format="large"
|
@format="large"
|
||||||
@ -313,7 +313,7 @@ export default class PersonaEditor extends Component {
|
|||||||
|
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="description"
|
@name="description"
|
||||||
@title={{i18n "discourse_ai.ai_persona.description"}}
|
@title={{i18n "discourse_ai.ai_agent.description"}}
|
||||||
@validation="required|length:1,100"
|
@validation="required|length:1,100"
|
||||||
@disabled={{data.system}}
|
@disabled={{data.system}}
|
||||||
@format="large"
|
@format="large"
|
||||||
@ -324,7 +324,7 @@ export default class PersonaEditor extends Component {
|
|||||||
|
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="system_prompt"
|
@name="system_prompt"
|
||||||
@title={{i18n "discourse_ai.ai_persona.system_prompt"}}
|
@title={{i18n "discourse_ai.ai_agent.system_prompt"}}
|
||||||
@validation="required|length:1,100000"
|
@validation="required|length:1,100000"
|
||||||
@disabled={{data.system}}
|
@disabled={{data.system}}
|
||||||
@format="large"
|
@format="large"
|
||||||
@ -333,28 +333,28 @@ export default class PersonaEditor extends Component {
|
|||||||
<field.Textarea />
|
<field.Textarea />
|
||||||
</form.Field>
|
</form.Field>
|
||||||
|
|
||||||
<AiPersonaResponseFormatEditor @form={{form}} @data={{data}} />
|
<AiAgentResponseFormatEditor @form={{form}} @data={{data}} />
|
||||||
|
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="default_llm_id"
|
@name="default_llm_id"
|
||||||
@title={{i18n "discourse_ai.ai_persona.default_llm"}}
|
@title={{i18n "discourse_ai.ai_agent.default_llm"}}
|
||||||
@tooltip={{i18n "discourse_ai.ai_persona.default_llm_help"}}
|
@tooltip={{i18n "discourse_ai.ai_agent.default_llm_help"}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
>
|
>
|
||||||
<field.Custom>
|
<field.Custom>
|
||||||
<AiLlmSelector
|
<AiLlmSelector
|
||||||
@value={{field.value}}
|
@value={{field.value}}
|
||||||
@llms={{@personas.resultSetMeta.llms}}
|
@llms={{@agents.resultSetMeta.llms}}
|
||||||
@onChange={{field.set}}
|
@onChange={{field.set}}
|
||||||
@class="ai-persona-editor__llms"
|
@class="ai-agent-editor__llms"
|
||||||
/>
|
/>
|
||||||
</field.Custom>
|
</field.Custom>
|
||||||
</form.Field>
|
</form.Field>
|
||||||
|
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="allowed_group_ids"
|
@name="allowed_group_ids"
|
||||||
@title={{i18n "discourse_ai.ai_persona.allowed_groups"}}
|
@title={{i18n "discourse_ai.ai_agent.allowed_groups"}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
>
|
>
|
||||||
@ -369,8 +369,8 @@ export default class PersonaEditor extends Component {
|
|||||||
|
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="vision_enabled"
|
@name="vision_enabled"
|
||||||
@title={{i18n "discourse_ai.ai_persona.vision_enabled"}}
|
@title={{i18n "discourse_ai.ai_agent.vision_enabled"}}
|
||||||
@tooltip={{i18n "discourse_ai.ai_persona.vision_enabled_help"}}
|
@tooltip={{i18n "discourse_ai.ai_agent.vision_enabled_help"}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
>
|
>
|
||||||
@ -380,7 +380,7 @@ export default class PersonaEditor extends Component {
|
|||||||
{{#if data.vision_enabled}}
|
{{#if data.vision_enabled}}
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="vision_max_pixels"
|
@name="vision_max_pixels"
|
||||||
@title={{i18n "discourse_ai.ai_persona.vision_max_pixels"}}
|
@title={{i18n "discourse_ai.ai_agent.vision_max_pixels"}}
|
||||||
@onSet={{this.onChangeMaxPixels}}
|
@onSet={{this.onChangeMaxPixels}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
@ -397,8 +397,8 @@ export default class PersonaEditor extends Component {
|
|||||||
|
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="max_context_posts"
|
@name="max_context_posts"
|
||||||
@title={{i18n "discourse_ai.ai_persona.max_context_posts"}}
|
@title={{i18n "discourse_ai.ai_agent.max_context_posts"}}
|
||||||
@tooltip={{i18n "discourse_ai.ai_persona.max_context_posts_help"}}
|
@tooltip={{i18n "discourse_ai.ai_agent.max_context_posts_help"}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
>
|
>
|
||||||
@ -408,8 +408,8 @@ export default class PersonaEditor extends Component {
|
|||||||
{{#unless data.system}}
|
{{#unless data.system}}
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="temperature"
|
@name="temperature"
|
||||||
@title={{i18n "discourse_ai.ai_persona.temperature"}}
|
@title={{i18n "discourse_ai.ai_agent.temperature"}}
|
||||||
@tooltip={{i18n "discourse_ai.ai_persona.temperature_help"}}
|
@tooltip={{i18n "discourse_ai.ai_agent.temperature_help"}}
|
||||||
@disabled={{data.system}}
|
@disabled={{data.system}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
@ -419,8 +419,8 @@ export default class PersonaEditor extends Component {
|
|||||||
|
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="top_p"
|
@name="top_p"
|
||||||
@title={{i18n "discourse_ai.ai_persona.top_p"}}
|
@title={{i18n "discourse_ai.ai_agent.top_p"}}
|
||||||
@tooltip={{i18n "discourse_ai.ai_persona.top_p_help"}}
|
@tooltip={{i18n "discourse_ai.ai_agent.top_p_help"}}
|
||||||
@disabled={{data.system}}
|
@disabled={{data.system}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
@ -430,22 +430,22 @@ export default class PersonaEditor extends Component {
|
|||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
|
||||||
<form.Section
|
<form.Section
|
||||||
@title={{i18n "discourse_ai.ai_persona.examples.title"}}
|
@title={{i18n "discourse_ai.ai_agent.examples.title"}}
|
||||||
@subtitle={{i18n "discourse_ai.ai_persona.examples.examples_help"}}
|
@subtitle={{i18n "discourse_ai.ai_agent.examples.examples_help"}}
|
||||||
>
|
>
|
||||||
{{#unless data.system}}
|
{{#unless data.system}}
|
||||||
<form.Container>
|
<form.Container>
|
||||||
<form.Button
|
<form.Button
|
||||||
@action={{fn this.addExamplesPair form data}}
|
@action={{fn this.addExamplesPair form data}}
|
||||||
@label="discourse_ai.ai_persona.examples.new"
|
@label="discourse_ai.ai_agent.examples.new"
|
||||||
class="ai-persona-editor__new_example"
|
class="ai-agent-editor__new_example"
|
||||||
/>
|
/>
|
||||||
</form.Container>
|
</form.Container>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
|
||||||
{{#if (gt data.examples.length 0)}}
|
{{#if (gt data.examples.length 0)}}
|
||||||
<form.Collection @name="examples" as |exCollection exCollectionIdx|>
|
<form.Collection @name="examples" as |exCollection exCollectionIdx|>
|
||||||
<AiPersonaCollapsableExample
|
<AiAgentCollapsableExample
|
||||||
@examplesCollection={{exCollection}}
|
@examplesCollection={{exCollection}}
|
||||||
@exampleNumber={{exCollectionIdx}}
|
@exampleNumber={{exCollectionIdx}}
|
||||||
@system={{data.system}}
|
@system={{data.system}}
|
||||||
@ -455,10 +455,10 @@ export default class PersonaEditor extends Component {
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
</form.Section>
|
</form.Section>
|
||||||
|
|
||||||
<form.Section @title={{i18n "discourse_ai.ai_persona.ai_tools"}}>
|
<form.Section @title={{i18n "discourse_ai.ai_agent.ai_tools"}}>
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="tools"
|
@name="tools"
|
||||||
@title={{i18n "discourse_ai.ai_persona.tools"}}
|
@title={{i18n "discourse_ai.ai_agent.tools"}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
>
|
>
|
||||||
@ -467,7 +467,7 @@ export default class PersonaEditor extends Component {
|
|||||||
@value={{field.value}}
|
@value={{field.value}}
|
||||||
@disabled={{data.system}}
|
@disabled={{data.system}}
|
||||||
@onChange={{fn this.updateToolNames form data}}
|
@onChange={{fn this.updateToolNames form data}}
|
||||||
@content={{@personas.resultSetMeta.tools}}
|
@content={{@agents.resultSetMeta.tools}}
|
||||||
/>
|
/>
|
||||||
</field.Custom>
|
</field.Custom>
|
||||||
</form.Field>
|
</form.Field>
|
||||||
@ -475,7 +475,7 @@ export default class PersonaEditor extends Component {
|
|||||||
{{#if (gt data.tools.length 0)}}
|
{{#if (gt data.tools.length 0)}}
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="forcedTools"
|
@name="forcedTools"
|
||||||
@title={{i18n "discourse_ai.ai_persona.forced_tools"}}
|
@title={{i18n "discourse_ai.ai_agent.forced_tools"}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
>
|
>
|
||||||
@ -493,7 +493,7 @@ export default class PersonaEditor extends Component {
|
|||||||
{{#if (gt data.forcedTools.length 0)}}
|
{{#if (gt data.forcedTools.length 0)}}
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="forced_tool_count"
|
@name="forced_tool_count"
|
||||||
@title={{i18n "discourse_ai.ai_persona.forced_tool_strategy"}}
|
@title={{i18n "discourse_ai.ai_agent.forced_tool_strategy"}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
>
|
>
|
||||||
@ -508,19 +508,19 @@ export default class PersonaEditor extends Component {
|
|||||||
{{#if (gt data.tools.length 0)}}
|
{{#if (gt data.tools.length 0)}}
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="tool_details"
|
@name="tool_details"
|
||||||
@title={{i18n "discourse_ai.ai_persona.tool_details"}}
|
@title={{i18n "discourse_ai.ai_agent.tool_details"}}
|
||||||
@tooltip={{i18n "discourse_ai.ai_persona.tool_details_help"}}
|
@tooltip={{i18n "discourse_ai.ai_agent.tool_details_help"}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
>
|
>
|
||||||
<field.Checkbox />
|
<field.Checkbox />
|
||||||
</form.Field>
|
</form.Field>
|
||||||
|
|
||||||
<AiPersonaToolOptions
|
<AiAgentToolOptions
|
||||||
@form={{form}}
|
@form={{form}}
|
||||||
@data={{data}}
|
@data={{data}}
|
||||||
@llms={{@personas.resultSetMeta.llms}}
|
@llms={{@agents.resultSetMeta.llms}}
|
||||||
@allTools={{@personas.resultSetMeta.tools}}
|
@allTools={{@agents.resultSetMeta.tools}}
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</form.Section>
|
</form.Section>
|
||||||
@ -535,10 +535,10 @@ export default class PersonaEditor extends Component {
|
|||||||
<field.Custom>
|
<field.Custom>
|
||||||
<RagUploader
|
<RagUploader
|
||||||
@target={{data}}
|
@target={{data}}
|
||||||
@targetName="AiPersona"
|
@targetName="AiAgent"
|
||||||
@updateUploads={{fn this.updateUploads form}}
|
@updateUploads={{fn this.updateUploads form}}
|
||||||
@onRemove={{fn this.removeUpload form data field.value}}
|
@onRemove={{fn this.removeUpload form data field.value}}
|
||||||
@allowImages={{@personas.resultSetMeta.settings.rag_images_enabled}}
|
@allowImages={{@agents.resultSetMeta.settings.rag_images_enabled}}
|
||||||
/>
|
/>
|
||||||
</field.Custom>
|
</field.Custom>
|
||||||
</form.Field>
|
</form.Field>
|
||||||
@ -546,16 +546,16 @@ export default class PersonaEditor extends Component {
|
|||||||
<RagOptionsFk
|
<RagOptionsFk
|
||||||
@form={{form}}
|
@form={{form}}
|
||||||
@data={{data}}
|
@data={{data}}
|
||||||
@llms={{@personas.resultSetMeta.llms}}
|
@llms={{@agents.resultSetMeta.llms}}
|
||||||
@allowImages={{@personas.resultSetMeta.settings.rag_images_enabled}}
|
@allowImages={{@agents.resultSetMeta.settings.rag_images_enabled}}
|
||||||
>
|
>
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="rag_conversation_chunks"
|
@name="rag_conversation_chunks"
|
||||||
@title={{i18n
|
@title={{i18n
|
||||||
"discourse_ai.ai_persona.rag_conversation_chunks"
|
"discourse_ai.ai_agent.rag_conversation_chunks"
|
||||||
}}
|
}}
|
||||||
@tooltip={{i18n
|
@tooltip={{i18n
|
||||||
"discourse_ai.ai_persona.rag_conversation_chunks_help"
|
"discourse_ai.ai_agent.rag_conversation_chunks_help"
|
||||||
}}
|
}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
@ -566,10 +566,10 @@ export default class PersonaEditor extends Component {
|
|||||||
<form.Field
|
<form.Field
|
||||||
@name="question_consolidator_llm_id"
|
@name="question_consolidator_llm_id"
|
||||||
@title={{i18n
|
@title={{i18n
|
||||||
"discourse_ai.ai_persona.question_consolidator_llm"
|
"discourse_ai.ai_agent.question_consolidator_llm"
|
||||||
}}
|
}}
|
||||||
@tooltip={{i18n
|
@tooltip={{i18n
|
||||||
"discourse_ai.ai_persona.question_consolidator_llm_help"
|
"discourse_ai.ai_agent.question_consolidator_llm_help"
|
||||||
}}
|
}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
@ -577,9 +577,9 @@ export default class PersonaEditor extends Component {
|
|||||||
<field.Custom>
|
<field.Custom>
|
||||||
<AiLlmSelector
|
<AiLlmSelector
|
||||||
@value={{field.value}}
|
@value={{field.value}}
|
||||||
@llms={{@personas.resultSetMeta.llms}}
|
@llms={{@agents.resultSetMeta.llms}}
|
||||||
@onChange={{field.set}}
|
@onChange={{field.set}}
|
||||||
@class="ai-persona-editor__llms"
|
@class="ai-agent-editor__llms"
|
||||||
/>
|
/>
|
||||||
</field.Custom>
|
</field.Custom>
|
||||||
</form.Field>
|
</form.Field>
|
||||||
@ -587,10 +587,10 @@ export default class PersonaEditor extends Component {
|
|||||||
</form.Section>
|
</form.Section>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<form.Section @title={{i18n "discourse_ai.ai_persona.ai_bot.title"}}>
|
<form.Section @title={{i18n "discourse_ai.ai_agent.ai_bot.title"}}>
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="enabled"
|
@name="enabled"
|
||||||
@title={{i18n "discourse_ai.ai_persona.enabled"}}
|
@title={{i18n "discourse_ai.ai_agent.enabled"}}
|
||||||
@onSet={{fn this.toggleEnabled data}}
|
@onSet={{fn this.toggleEnabled data}}
|
||||||
as |field|
|
as |field|
|
||||||
>
|
>
|
||||||
@ -599,21 +599,21 @@ export default class PersonaEditor extends Component {
|
|||||||
|
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="priority"
|
@name="priority"
|
||||||
@title={{i18n "discourse_ai.ai_persona.priority"}}
|
@title={{i18n "discourse_ai.ai_agent.priority"}}
|
||||||
@onSet={{fn this.togglePriority data}}
|
@onSet={{fn this.togglePriority data}}
|
||||||
@tooltip={{i18n "discourse_ai.ai_persona.priority_help"}}
|
@tooltip={{i18n "discourse_ai.ai_agent.priority_help"}}
|
||||||
as |field|
|
as |field|
|
||||||
>
|
>
|
||||||
<field.Toggle />
|
<field.Toggle />
|
||||||
</form.Field>
|
</form.Field>
|
||||||
|
|
||||||
{{#if @model.isNew}}
|
{{#if @model.isNew}}
|
||||||
<div>{{i18n "discourse_ai.ai_persona.ai_bot.save_first"}}</div>
|
<div>{{i18n "discourse_ai.ai_agent.ai_bot.save_first"}}</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#if data.default_llm_id}}
|
{{#if data.default_llm_id}}
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="force_default_llm"
|
@name="force_default_llm"
|
||||||
@title={{i18n "discourse_ai.ai_persona.force_default_llm"}}
|
@title={{i18n "discourse_ai.ai_agent.force_default_llm"}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
>
|
>
|
||||||
@ -622,12 +622,12 @@ export default class PersonaEditor extends Component {
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<form.Container
|
<form.Container
|
||||||
@title={{i18n "discourse_ai.ai_persona.user"}}
|
@title={{i18n "discourse_ai.ai_agent.user"}}
|
||||||
@tooltip={{unless
|
@tooltip={{unless
|
||||||
data.user
|
data.user
|
||||||
(i18n "discourse_ai.ai_persona.create_user_help")
|
(i18n "discourse_ai.ai_agent.create_user_help")
|
||||||
}}
|
}}
|
||||||
class="ai-persona-editor__ai_bot_user"
|
class="ai-agent-editor__ai_bot_user"
|
||||||
>
|
>
|
||||||
{{#if data.user}}
|
{{#if data.user}}
|
||||||
<a
|
<a
|
||||||
@ -643,20 +643,20 @@ export default class PersonaEditor extends Component {
|
|||||||
{{else}}
|
{{else}}
|
||||||
<form.Button
|
<form.Button
|
||||||
@action={{fn this.createUser form}}
|
@action={{fn this.createUser form}}
|
||||||
@label="discourse_ai.ai_persona.create_user"
|
@label="discourse_ai.ai_agent.create_user"
|
||||||
class="ai-persona-editor__create-user"
|
class="ai-agent-editor__create-user"
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</form.Container>
|
</form.Container>
|
||||||
|
|
||||||
{{#if data.user}}
|
{{#if data.user}}
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="allow_personal_messages"
|
@name="allow_agentl_messages"
|
||||||
@title={{i18n
|
@title={{i18n
|
||||||
"discourse_ai.ai_persona.allow_personal_messages"
|
"discourse_ai.ai_agent.allow_agentl_messages"
|
||||||
}}
|
}}
|
||||||
@tooltip={{i18n
|
@tooltip={{i18n
|
||||||
"discourse_ai.ai_persona.allow_personal_messages_help"
|
"discourse_ai.ai_agent.allow_agentl_messages_help"
|
||||||
}}
|
}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
@ -666,9 +666,9 @@ export default class PersonaEditor extends Component {
|
|||||||
|
|
||||||
<form.Field
|
<form.Field
|
||||||
@name="allow_topic_mentions"
|
@name="allow_topic_mentions"
|
||||||
@title={{i18n "discourse_ai.ai_persona.allow_topic_mentions"}}
|
@title={{i18n "discourse_ai.ai_agent.allow_topic_mentions"}}
|
||||||
@tooltip={{i18n
|
@tooltip={{i18n
|
||||||
"discourse_ai.ai_persona.allow_topic_mentions_help"
|
"discourse_ai.ai_agent.allow_topic_mentions_help"
|
||||||
}}
|
}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
@ -680,10 +680,10 @@ export default class PersonaEditor extends Component {
|
|||||||
<form.Field
|
<form.Field
|
||||||
@name="allow_chat_direct_messages"
|
@name="allow_chat_direct_messages"
|
||||||
@title={{i18n
|
@title={{i18n
|
||||||
"discourse_ai.ai_persona.allow_chat_direct_messages"
|
"discourse_ai.ai_agent.allow_chat_direct_messages"
|
||||||
}}
|
}}
|
||||||
@tooltip={{i18n
|
@tooltip={{i18n
|
||||||
"discourse_ai.ai_persona.allow_chat_direct_messages_help"
|
"discourse_ai.ai_agent.allow_chat_direct_messages_help"
|
||||||
}}
|
}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
@ -694,10 +694,10 @@ export default class PersonaEditor extends Component {
|
|||||||
<form.Field
|
<form.Field
|
||||||
@name="allow_chat_channel_mentions"
|
@name="allow_chat_channel_mentions"
|
||||||
@title={{i18n
|
@title={{i18n
|
||||||
"discourse_ai.ai_persona.allow_chat_channel_mentions"
|
"discourse_ai.ai_agent.allow_chat_channel_mentions"
|
||||||
}}
|
}}
|
||||||
@tooltip={{i18n
|
@tooltip={{i18n
|
||||||
"discourse_ai.ai_persona.allow_chat_channel_mentions_help"
|
"discourse_ai.ai_agent.allow_chat_channel_mentions_help"
|
||||||
}}
|
}}
|
||||||
@format="large"
|
@format="large"
|
||||||
as |field|
|
as |field|
|
||||||
@ -715,7 +715,7 @@ export default class PersonaEditor extends Component {
|
|||||||
{{#unless (or @model.isNew @model.system)}}
|
{{#unless (or @model.isNew @model.system)}}
|
||||||
<form.Button
|
<form.Button
|
||||||
@action={{this.delete}}
|
@action={{this.delete}}
|
||||||
@label="discourse_ai.ai_persona.delete"
|
@label="discourse_ai.ai_agent.delete"
|
||||||
class="btn-danger"
|
class="btn-danger"
|
||||||
/>
|
/>
|
||||||
{{/unless}}
|
{{/unless}}
|
@ -7,7 +7,7 @@ import { eq } from "truth-helpers";
|
|||||||
import icon from "discourse/helpers/d-icon";
|
import icon from "discourse/helpers/d-icon";
|
||||||
import { i18n } from "discourse-i18n";
|
import { i18n } from "discourse-i18n";
|
||||||
|
|
||||||
export default class AiPersonaCollapsableExample extends Component {
|
export default class AiAgentCollapsableExample extends Component {
|
||||||
@tracked collapsed = true;
|
@tracked collapsed = true;
|
||||||
|
|
||||||
get caretIcon() {
|
get caretIcon() {
|
||||||
@ -26,7 +26,7 @@ export default class AiPersonaCollapsableExample extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get exampleTitle() {
|
get exampleTitle() {
|
||||||
return i18n("discourse_ai.ai_persona.examples.collapsable_title", {
|
return i18n("discourse_ai.ai_agent.examples.collapsable_title", {
|
||||||
number: this.args.exampleNumber + 1,
|
number: this.args.exampleNumber + 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ export default class AiPersonaCollapsableExample extends Component {
|
|||||||
<exPair.Field
|
<exPair.Field
|
||||||
@title={{i18n
|
@title={{i18n
|
||||||
(concat
|
(concat
|
||||||
"discourse_ai.ai_persona.examples."
|
"discourse_ai.ai_agent.examples."
|
||||||
(if (eq pairIdx 0) "user" "model")
|
(if (eq pairIdx 0) "user" "model")
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@ -57,8 +57,8 @@ export default class AiPersonaCollapsableExample extends Component {
|
|||||||
<@form.Container>
|
<@form.Container>
|
||||||
<@form.Button
|
<@form.Button
|
||||||
@action={{this.deletePair}}
|
@action={{this.deletePair}}
|
||||||
@label="discourse_ai.ai_persona.examples.remove"
|
@label="discourse_ai.ai_agent.examples.remove"
|
||||||
class="ai-persona-editor__delete_example btn-danger"
|
class="ai-agent-editor__delete_example btn-danger"
|
||||||
/>
|
/>
|
||||||
</@form.Container>
|
</@form.Container>
|
||||||
{{/unless}}
|
{{/unless}}
|
117
assets/javascripts/discourse/components/ai-agent-list-editor.gjs
Normal file
117
assets/javascripts/discourse/components/ai-agent-list-editor.gjs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { fn } from "@ember/helper";
|
||||||
|
import { on } from "@ember/modifier";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { LinkTo } from "@ember/routing";
|
||||||
|
import { service } from "@ember/service";
|
||||||
|
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
|
||||||
|
import DPageSubheader from "discourse/components/d-page-subheader";
|
||||||
|
import DToggleSwitch from "discourse/components/d-toggle-switch";
|
||||||
|
import concatClass from "discourse/helpers/concat-class";
|
||||||
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
import { i18n } from "discourse-i18n";
|
||||||
|
import AdminConfigAreaEmptyList from "admin/components/admin-config-area-empty-list";
|
||||||
|
import AiAgentEditor from "./ai-agent-editor";
|
||||||
|
|
||||||
|
export default class AiAgentListEditor extends Component {
|
||||||
|
@service adminPluginNavManager;
|
||||||
|
|
||||||
|
@action
|
||||||
|
async toggleEnabled(agent) {
|
||||||
|
const oldValue = agent.enabled;
|
||||||
|
const newValue = !oldValue;
|
||||||
|
|
||||||
|
try {
|
||||||
|
agent.set("enabled", newValue);
|
||||||
|
await agent.save();
|
||||||
|
} catch (err) {
|
||||||
|
agent.set("enabled", oldValue);
|
||||||
|
popupAjaxError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DBreadcrumbsItem
|
||||||
|
@path="/admin/plugins/{{this.adminPluginNavManager.currentPlugin.name}}/ai-agents"
|
||||||
|
@label={{i18n "discourse_ai.ai_agent.short_title"}}
|
||||||
|
/>
|
||||||
|
<section class="ai-agent-list-editor__current admin-detail pull-left">
|
||||||
|
{{#if @currentAgent}}
|
||||||
|
<AiAgentEditor @model={{@currentAgent}} @agents={{@agents}} />
|
||||||
|
{{else}}
|
||||||
|
<DPageSubheader
|
||||||
|
@titleLabel={{i18n "discourse_ai.ai_agent.short_title"}}
|
||||||
|
@descriptionLabel={{i18n
|
||||||
|
"discourse_ai.ai_agent.agent_description"
|
||||||
|
}}
|
||||||
|
@learnMoreUrl="https://meta.discourse.org/t/ai-bot-agents/306099"
|
||||||
|
>
|
||||||
|
<:actions as |actions|>
|
||||||
|
<actions.Primary
|
||||||
|
@label="discourse_ai.ai_agent.new"
|
||||||
|
@route="adminPlugins.show.discourse-ai-agents.new"
|
||||||
|
@icon="plus"
|
||||||
|
class="ai-agent-list-editor__new-button"
|
||||||
|
/>
|
||||||
|
</:actions>
|
||||||
|
</DPageSubheader>
|
||||||
|
|
||||||
|
{{#if @agents}}
|
||||||
|
<table class="content-list ai-agent-list-editor d-admin-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{i18n "discourse_ai.ai_agent.name"}}</th>
|
||||||
|
<th>{{i18n "discourse_ai.ai_agent.list.enabled"}}</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#each @agents as |agent|}}
|
||||||
|
<tr
|
||||||
|
data-agent-id={{agent.id}}
|
||||||
|
class={{concatClass
|
||||||
|
"ai-agent-list__row d-admin-row__content"
|
||||||
|
(if agent.priority "priority")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<td class="d-admin-row__overview">
|
||||||
|
<div class="ai-agent-list__name-with-description">
|
||||||
|
<div class="ai-agent-list__name">
|
||||||
|
<strong>
|
||||||
|
{{agent.name}}
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
<div class="ai-agent-list__description">
|
||||||
|
{{agent.description}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="d-admin-row__detail">
|
||||||
|
<DToggleSwitch
|
||||||
|
@state={{agent.enabled}}
|
||||||
|
{{on "click" (fn this.toggleEnabled agent)}}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td class="d-admin-row__controls">
|
||||||
|
<LinkTo
|
||||||
|
@route="adminPlugins.show.discourse-ai-agents.edit"
|
||||||
|
@model={{agent}}
|
||||||
|
class="btn btn-text btn-small"
|
||||||
|
>{{i18n "discourse_ai.ai_agent.edit"}} </LinkTo>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{else}}
|
||||||
|
<AdminConfigAreaEmptyList
|
||||||
|
@ctaLabel="discourse_ai.ai_agent.new"
|
||||||
|
@ctaRoute="adminPlugins.show.discourse-ai-agents.new"
|
||||||
|
@ctaClass="ai-agent-list-editor__empty-new-button"
|
||||||
|
@emptyLabel="discourse_ai.ai_agent.no_agents"
|
||||||
|
/>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
}
|
@ -6,10 +6,10 @@ import { service } from "@ember/service";
|
|||||||
import { i18n } from "discourse-i18n";
|
import { i18n } from "discourse-i18n";
|
||||||
import DropdownSelectBox from "select-kit/components/dropdown-select-box";
|
import DropdownSelectBox from "select-kit/components/dropdown-select-box";
|
||||||
|
|
||||||
const PERSONA_SELECTOR_KEY = "ai_persona_selector_id";
|
const AGENT_SELECTOR_KEY = "ai_agent_selector_id";
|
||||||
const LLM_SELECTOR_KEY = "ai_llm_selector_id";
|
const LLM_SELECTOR_KEY = "ai_llm_selector_id";
|
||||||
|
|
||||||
export default class AiPersonaLlmSelector extends Component {
|
export default class AiAgentLlmSelector extends Component {
|
||||||
@service currentUser;
|
@service currentUser;
|
||||||
@service keyValueStore;
|
@service keyValueStore;
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ export default class AiPersonaLlmSelector extends Component {
|
|||||||
super(...arguments);
|
super(...arguments);
|
||||||
|
|
||||||
if (this.botOptions?.length) {
|
if (this.botOptions?.length) {
|
||||||
this.#loadStoredPersona();
|
this.#loadStoredAgent();
|
||||||
this.#loadStoredLlm();
|
this.#loadStoredLlm();
|
||||||
|
|
||||||
next(() => {
|
next(() => {
|
||||||
@ -34,25 +34,25 @@ export default class AiPersonaLlmSelector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get hasLlmSelector() {
|
get hasLlmSelector() {
|
||||||
return this.currentUser.ai_enabled_chat_bots.any((bot) => !bot.is_persona);
|
return this.currentUser.ai_enabled_chat_bots.any((bot) => !bot.is_agent);
|
||||||
}
|
}
|
||||||
|
|
||||||
get botOptions() {
|
get botOptions() {
|
||||||
if (!this.currentUser.ai_enabled_personas) {
|
if (!this.currentUser.ai_enabled_agents) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let enabledPersonas = this.currentUser.ai_enabled_personas;
|
let enabledAgents = this.currentUser.ai_enabled_agents;
|
||||||
|
|
||||||
if (!this.hasLlmSelector) {
|
if (!this.hasLlmSelector) {
|
||||||
enabledPersonas = enabledPersonas.filter((persona) => persona.username);
|
enabledAgents = enabledAgents.filter((agent) => agent.username);
|
||||||
}
|
}
|
||||||
|
|
||||||
return enabledPersonas.map((persona) => {
|
return enabledAgents.map((agent) => {
|
||||||
return {
|
return {
|
||||||
id: persona.id,
|
id: agent.id,
|
||||||
name: persona.name,
|
name: agent.name,
|
||||||
description: persona.description,
|
description: agent.description,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -67,8 +67,8 @@ export default class AiPersonaLlmSelector extends Component {
|
|||||||
|
|
||||||
set value(newValue) {
|
set value(newValue) {
|
||||||
this._value = newValue;
|
this._value = newValue;
|
||||||
this.keyValueStore.setItem(PERSONA_SELECTOR_KEY, newValue);
|
this.keyValueStore.setItem(AGENT_SELECTOR_KEY, newValue);
|
||||||
this.args.setPersonaId(newValue);
|
this.args.setAgentId(newValue);
|
||||||
this.setAllowLLMSelector();
|
this.setAllowLLMSelector();
|
||||||
this.resetTargetRecipients();
|
this.resetTargetRecipients();
|
||||||
}
|
}
|
||||||
@ -79,11 +79,11 @@ export default class AiPersonaLlmSelector extends Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const persona = this.currentUser.ai_enabled_personas.find(
|
const agent = this.currentUser.ai_enabled_agents.find(
|
||||||
(innerPersona) => innerPersona.id === this._value
|
(innerAgent) => innerAgent.id === this._value
|
||||||
);
|
);
|
||||||
|
|
||||||
this.allowLLMSelector = !persona?.force_default_llm;
|
this.allowLLMSelector = !agent?.force_default_llm;
|
||||||
}
|
}
|
||||||
|
|
||||||
get currentLlm() {
|
get currentLlm() {
|
||||||
@ -104,16 +104,16 @@ export default class AiPersonaLlmSelector extends Component {
|
|||||||
).username;
|
).username;
|
||||||
this.args.setTargetRecipient(botUsername);
|
this.args.setTargetRecipient(botUsername);
|
||||||
} else {
|
} else {
|
||||||
const persona = this.currentUser.ai_enabled_personas.find(
|
const agent = this.currentUser.ai_enabled_agents.find(
|
||||||
(innerPersona) => innerPersona.id === this._value
|
(innerAgent) => innerAgent.id === this._value
|
||||||
);
|
);
|
||||||
this.args.setTargetRecipient(persona.username || "");
|
this.args.setTargetRecipient(agent.username || "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get llmOptions() {
|
get llmOptions() {
|
||||||
const availableBots = this.currentUser.ai_enabled_chat_bots
|
const availableBots = this.currentUser.ai_enabled_chat_bots
|
||||||
.filter((bot) => !bot.is_persona)
|
.filter((bot) => !bot.is_agent)
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
return availableBots
|
return availableBots
|
||||||
@ -130,18 +130,18 @@ export default class AiPersonaLlmSelector extends Component {
|
|||||||
return this.allowLLMSelector && this.llmOptions.length > 1;
|
return this.allowLLMSelector && this.llmOptions.length > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#loadStoredPersona() {
|
#loadStoredAgent() {
|
||||||
let personaId = this.keyValueStore.getItem(PERSONA_SELECTOR_KEY);
|
let agentId = this.keyValueStore.getItem(AGENT_SELECTOR_KEY);
|
||||||
|
|
||||||
this._value = this.botOptions[0].id;
|
this._value = this.botOptions[0].id;
|
||||||
if (personaId) {
|
if (agentId) {
|
||||||
personaId = parseInt(personaId, 10);
|
agentId = parseInt(agentId, 10);
|
||||||
if (this.botOptions.any((bot) => bot.id === personaId)) {
|
if (this.botOptions.any((bot) => bot.id === agentId)) {
|
||||||
this._value = personaId;
|
this._value = agentId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.args.setPersonaId(this._value);
|
this.args.setAgentId(this._value);
|
||||||
}
|
}
|
||||||
|
|
||||||
#loadStoredLlm() {
|
#loadStoredLlm() {
|
||||||
@ -172,13 +172,13 @@ export default class AiPersonaLlmSelector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="persona-llm-selector">
|
<div class="agent-llm-selector">
|
||||||
<div class="persona-llm-selector__selection-wrapper gpt-persona">
|
<div class="agent-llm-selector__selection-wrapper gpt-agent">
|
||||||
{{#if @showLabels}}
|
{{#if @showLabels}}
|
||||||
<label>{{i18n "discourse_ai.ai_bot.persona"}}</label>
|
<label>{{i18n "discourse_ai.ai_bot.agent"}}</label>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<DropdownSelectBox
|
<DropdownSelectBox
|
||||||
class="persona-llm-selector__persona-dropdown"
|
class="agent-llm-selector__agent-dropdown"
|
||||||
@value={{this.value}}
|
@value={{this.value}}
|
||||||
@content={{this.botOptions}}
|
@content={{this.botOptions}}
|
||||||
@options={{hash
|
@options={{hash
|
||||||
@ -188,12 +188,12 @@ export default class AiPersonaLlmSelector extends Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{{#if this.showLLMSelector}}
|
{{#if this.showLLMSelector}}
|
||||||
<div class="persona-llm-selector__selection-wrapper llm-selector">
|
<div class="agent-llm-selector__selection-wrapper llm-selector">
|
||||||
{{#if @showLabels}}
|
{{#if @showLabels}}
|
||||||
<label>{{i18n "discourse_ai.ai_bot.llm"}}</label>
|
<label>{{i18n "discourse_ai.ai_bot.llm"}}</label>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<DropdownSelectBox
|
<DropdownSelectBox
|
||||||
class="persona-llm-selector__llm-dropdown"
|
class="agent-llm-selector__llm-dropdown"
|
||||||
@value={{this.currentLlm}}
|
@value={{this.currentLlm}}
|
||||||
@content={{this.llmOptions}}
|
@content={{this.llmOptions}}
|
||||||
@options={{hash icon=(if @showLabels "angle-down" "globe")}}
|
@options={{hash icon=(if @showLabels "angle-down" "globe")}}
|
@ -4,7 +4,7 @@ import { eq } from "truth-helpers";
|
|||||||
import { i18n } from "discourse-i18n";
|
import { i18n } from "discourse-i18n";
|
||||||
import AiLlmSelector from "./ai-llm-selector";
|
import AiLlmSelector from "./ai-llm-selector";
|
||||||
|
|
||||||
export default class AiPersonaToolOptions extends Component {
|
export default class AiAgentToolOptions extends Component {
|
||||||
get showToolOptions() {
|
get showToolOptions() {
|
||||||
const allTools = this.args.allTools;
|
const allTools = this.args.allTools;
|
||||||
if (!allTools || !this.args.data.tools) {
|
if (!allTools || !this.args.data.tools) {
|
||||||
@ -36,19 +36,19 @@ export default class AiPersonaToolOptions extends Component {
|
|||||||
<template>
|
<template>
|
||||||
{{#if this.showToolOptions}}
|
{{#if this.showToolOptions}}
|
||||||
<@form.Container
|
<@form.Container
|
||||||
@title={{i18n "discourse_ai.ai_persona.tool_options"}}
|
@title={{i18n "discourse_ai.ai_agent.tool_options"}}
|
||||||
@direction="column"
|
@direction="column"
|
||||||
@format="full"
|
@format="full"
|
||||||
>
|
>
|
||||||
<@form.Object
|
<@form.Object
|
||||||
@name="toolOptions"
|
@name="toolOptions"
|
||||||
@title={{i18n "discourse_ai.ai_persona.tool_options"}}
|
@title={{i18n "discourse_ai.ai_agent.tool_options"}}
|
||||||
as |toolObj optsPerTool|
|
as |toolObj optsPerTool|
|
||||||
>
|
>
|
||||||
{{#each (this.formObjectKeys optsPerTool) as |toolId|}}
|
{{#each (this.formObjectKeys optsPerTool) as |toolId|}}
|
||||||
<div class="ai-persona-editor__tool-options">
|
<div class="ai-agent-editor__tool-options">
|
||||||
{{#let (get this.toolsMetadata toolId) as |toolMeta|}}
|
{{#let (get this.toolsMetadata toolId) as |toolMeta|}}
|
||||||
<div class="ai-persona-editor__tool-options-name">
|
<div class="ai-agent-editor__tool-options-name">
|
||||||
{{toolMeta.name}}
|
{{toolMeta.name}}
|
||||||
</div>
|
</div>
|
||||||
<toolObj.Object @name={{toolId}} as |optionsObj optionData|>
|
<toolObj.Object @name={{toolId}} as |optionsObj optionData|>
|
||||||
@ -73,7 +73,7 @@ export default class AiPersonaToolOptions extends Component {
|
|||||||
@value={{field.value}}
|
@value={{field.value}}
|
||||||
@llms={{@llms}}
|
@llms={{@llms}}
|
||||||
@onChange={{field.set}}
|
@onChange={{field.set}}
|
||||||
@class="ai-persona-tool-option-editor__llms"
|
@class="ai-agent-tool-option-editor__llms"
|
||||||
/>
|
/>
|
||||||
</field.Custom>
|
</field.Custom>
|
||||||
{{else if (eq optionMeta.type "boolean")}}
|
{{else if (eq optionMeta.type "boolean")}}
|
@ -23,7 +23,7 @@ import {
|
|||||||
} from "discourse/lib/user-status-on-autocomplete";
|
} from "discourse/lib/user-status-on-autocomplete";
|
||||||
import { clipboardHelpers } from "discourse/lib/utilities";
|
import { clipboardHelpers } from "discourse/lib/utilities";
|
||||||
import { i18n } from "discourse-i18n";
|
import { i18n } from "discourse-i18n";
|
||||||
import AiPersonaLlmSelector from "discourse/plugins/discourse-ai/discourse/components/ai-persona-llm-selector";
|
import AiAgentLlmSelector from "discourse/plugins/discourse-ai/discourse/components/ai-agent-llm-selector";
|
||||||
|
|
||||||
export default class AiBotConversations extends Component {
|
export default class AiBotConversations extends Component {
|
||||||
@service aiBotConversationsHiddenSubmit;
|
@service aiBotConversationsHiddenSubmit;
|
||||||
@ -133,8 +133,8 @@ export default class AiBotConversations extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
setPersonaId(id) {
|
setAgentId(id) {
|
||||||
this.aiBotConversationsHiddenSubmit.personaId = id;
|
this.aiBotConversationsHiddenSubmit.agentId = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@ -279,9 +279,9 @@ export default class AiBotConversations extends Component {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="ai-bot-conversations">
|
<div class="ai-bot-conversations">
|
||||||
<AiPersonaLlmSelector
|
<AiAgentLlmSelector
|
||||||
@showLabels={{true}}
|
@showLabels={{true}}
|
||||||
@setPersonaId={{this.setPersonaId}}
|
@setAgentId={{this.setAgentId}}
|
||||||
@setTargetRecipient={{this.setTargetRecipient}}
|
@setTargetRecipient={{this.setTargetRecipient}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ export default class AiLlmEditorForm extends Component {
|
|||||||
|
|
||||||
const localized = usedBy.map((m) => {
|
const localized = usedBy.map((m) => {
|
||||||
return i18n(`discourse_ai.llms.usage.${m.type}`, {
|
return i18n(`discourse_ai.llms.usage.${m.type}`, {
|
||||||
persona: m.name,
|
agent: m.name,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ const AiLlmSelector = <template>
|
|||||||
@onChange={{@onChange}}
|
@onChange={{@onChange}}
|
||||||
@options={{hash
|
@options={{hash
|
||||||
filterable=true
|
filterable=true
|
||||||
none="discourse_ai.ai_persona.no_llm_selected"
|
none="discourse_ai.ai_agent.no_llm_selected"
|
||||||
}}
|
}}
|
||||||
class={{@class}}
|
class={{@class}}
|
||||||
/>
|
/>
|
||||||
|
@ -112,9 +112,9 @@ export default class AiLlmsListEditor extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
localizeUsage(usage) {
|
localizeUsage(usage) {
|
||||||
if (usage.type === "ai_persona") {
|
if (usage.type === "ai_agent") {
|
||||||
return i18n("discourse_ai.llms.usage.ai_persona", {
|
return i18n("discourse_ai.llms.usage.ai_agent", {
|
||||||
persona: usage.name,
|
agent: usage.name,
|
||||||
});
|
});
|
||||||
} else if (usage.type === "automation") {
|
} else if (usage.type === "automation") {
|
||||||
return i18n("discourse_ai.llms.usage.automation", {
|
return i18n("discourse_ai.llms.usage.automation", {
|
||||||
|
@ -1,117 +0,0 @@
|
|||||||
import Component from "@glimmer/component";
|
|
||||||
import { fn } from "@ember/helper";
|
|
||||||
import { on } from "@ember/modifier";
|
|
||||||
import { action } from "@ember/object";
|
|
||||||
import { LinkTo } from "@ember/routing";
|
|
||||||
import { service } from "@ember/service";
|
|
||||||
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
|
|
||||||
import DPageSubheader from "discourse/components/d-page-subheader";
|
|
||||||
import DToggleSwitch from "discourse/components/d-toggle-switch";
|
|
||||||
import concatClass from "discourse/helpers/concat-class";
|
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
|
||||||
import { i18n } from "discourse-i18n";
|
|
||||||
import AdminConfigAreaEmptyList from "admin/components/admin-config-area-empty-list";
|
|
||||||
import AiPersonaEditor from "./ai-persona-editor";
|
|
||||||
|
|
||||||
export default class AiPersonaListEditor extends Component {
|
|
||||||
@service adminPluginNavManager;
|
|
||||||
|
|
||||||
@action
|
|
||||||
async toggleEnabled(persona) {
|
|
||||||
const oldValue = persona.enabled;
|
|
||||||
const newValue = !oldValue;
|
|
||||||
|
|
||||||
try {
|
|
||||||
persona.set("enabled", newValue);
|
|
||||||
await persona.save();
|
|
||||||
} catch (err) {
|
|
||||||
persona.set("enabled", oldValue);
|
|
||||||
popupAjaxError(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<DBreadcrumbsItem
|
|
||||||
@path="/admin/plugins/{{this.adminPluginNavManager.currentPlugin.name}}/ai-personas"
|
|
||||||
@label={{i18n "discourse_ai.ai_persona.short_title"}}
|
|
||||||
/>
|
|
||||||
<section class="ai-persona-list-editor__current admin-detail pull-left">
|
|
||||||
{{#if @currentPersona}}
|
|
||||||
<AiPersonaEditor @model={{@currentPersona}} @personas={{@personas}} />
|
|
||||||
{{else}}
|
|
||||||
<DPageSubheader
|
|
||||||
@titleLabel={{i18n "discourse_ai.ai_persona.short_title"}}
|
|
||||||
@descriptionLabel={{i18n
|
|
||||||
"discourse_ai.ai_persona.persona_description"
|
|
||||||
}}
|
|
||||||
@learnMoreUrl="https://meta.discourse.org/t/ai-bot-personas/306099"
|
|
||||||
>
|
|
||||||
<:actions as |actions|>
|
|
||||||
<actions.Primary
|
|
||||||
@label="discourse_ai.ai_persona.new"
|
|
||||||
@route="adminPlugins.show.discourse-ai-personas.new"
|
|
||||||
@icon="plus"
|
|
||||||
class="ai-persona-list-editor__new-button"
|
|
||||||
/>
|
|
||||||
</:actions>
|
|
||||||
</DPageSubheader>
|
|
||||||
|
|
||||||
{{#if @personas}}
|
|
||||||
<table class="content-list ai-persona-list-editor d-admin-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{{i18n "discourse_ai.ai_persona.name"}}</th>
|
|
||||||
<th>{{i18n "discourse_ai.ai_persona.list.enabled"}}</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{{#each @personas as |persona|}}
|
|
||||||
<tr
|
|
||||||
data-persona-id={{persona.id}}
|
|
||||||
class={{concatClass
|
|
||||||
"ai-persona-list__row d-admin-row__content"
|
|
||||||
(if persona.priority "priority")
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<td class="d-admin-row__overview">
|
|
||||||
<div class="ai-persona-list__name-with-description">
|
|
||||||
<div class="ai-persona-list__name">
|
|
||||||
<strong>
|
|
||||||
{{persona.name}}
|
|
||||||
</strong>
|
|
||||||
</div>
|
|
||||||
<div class="ai-persona-list__description">
|
|
||||||
{{persona.description}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="d-admin-row__detail">
|
|
||||||
<DToggleSwitch
|
|
||||||
@state={{persona.enabled}}
|
|
||||||
{{on "click" (fn this.toggleEnabled persona)}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td class="d-admin-row__controls">
|
|
||||||
<LinkTo
|
|
||||||
@route="adminPlugins.show.discourse-ai-personas.edit"
|
|
||||||
@model={{persona}}
|
|
||||||
class="btn btn-text btn-small"
|
|
||||||
>{{i18n "discourse_ai.ai_persona.edit"}} </LinkTo>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{/each}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{{else}}
|
|
||||||
<AdminConfigAreaEmptyList
|
|
||||||
@ctaLabel="discourse_ai.ai_persona.new"
|
|
||||||
@ctaRoute="adminPlugins.show.discourse-ai-personas.new"
|
|
||||||
@ctaClass="ai-persona-list-editor__empty-new-button"
|
|
||||||
@emptyLabel="discourse_ai.ai_persona.no_personas"
|
|
||||||
/>
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
}
|
|
@ -154,8 +154,8 @@ export default class AiSearchDiscoveries extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get canContinueConversation() {
|
get canContinueConversation() {
|
||||||
const personas = this.currentUser?.ai_enabled_personas;
|
const agents = this.currentUser?.ai_enabled_agents;
|
||||||
if (!personas) {
|
if (!agents) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,16 +163,16 @@ export default class AiSearchDiscoveries extends Component {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const discoverPersona = personas.find(
|
const discoverAgent = agents.find(
|
||||||
(persona) =>
|
(agent) =>
|
||||||
persona.id === parseInt(this.siteSettings?.ai_bot_discover_persona, 10)
|
agent.id === parseInt(this.siteSettings?.ai_bot_discover_agent, 10)
|
||||||
);
|
);
|
||||||
const discoverPersonaHasBot = discoverPersona?.username;
|
const discoverAgentHasBot = discoverAgent?.username;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
this.discobotDiscoveries.discovery?.length > 0 &&
|
this.discobotDiscoveries.discovery?.length > 0 &&
|
||||||
!this.smoothStreamer.isStreaming &&
|
!this.smoothStreamer.isStreaming &&
|
||||||
discoverPersonaHasBot
|
discoverAgentHasBot
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,16 +7,16 @@ import ModalJsonSchemaEditor from "discourse/components/modal/json-schema-editor
|
|||||||
import { prettyJSON } from "discourse/lib/formatter";
|
import { prettyJSON } from "discourse/lib/formatter";
|
||||||
import { i18n } from "discourse-i18n";
|
import { i18n } from "discourse-i18n";
|
||||||
|
|
||||||
export default class AiPersonaResponseFormatEditor extends Component {
|
export default class AiAgentResponseFormatEditor extends Component {
|
||||||
@tracked showJsonEditorModal = false;
|
@tracked showJsonEditorModal = false;
|
||||||
|
|
||||||
jsonSchema = {
|
jsonSchema = {
|
||||||
type: "array",
|
type: "array",
|
||||||
uniqueItems: true,
|
uniqueItems: true,
|
||||||
title: i18n("discourse_ai.ai_persona.response_format.modal.root_title"),
|
title: i18n("discourse_ai.ai_agent.response_format.modal.root_title"),
|
||||||
items: {
|
items: {
|
||||||
type: "object",
|
type: "object",
|
||||||
title: i18n("discourse_ai.ai_persona.response_format.modal.key_title"),
|
title: i18n("discourse_ai.ai_agent.response_format.modal.key_title"),
|
||||||
properties: {
|
properties: {
|
||||||
key: {
|
key: {
|
||||||
type: "string",
|
type: "string",
|
||||||
@ -30,7 +30,7 @@ export default class AiPersonaResponseFormatEditor extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
get editorTitle() {
|
get editorTitle() {
|
||||||
return i18n("discourse_ai.ai_persona.response_format.title");
|
return i18n("discourse_ai.ai_agent.response_format.title");
|
||||||
}
|
}
|
||||||
|
|
||||||
get responseFormatAsJSON() {
|
get responseFormatAsJSON() {
|
||||||
@ -64,21 +64,21 @@ export default class AiPersonaResponseFormatEditor extends Component {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<@form.Container @title={{this.editorTitle}} @format="large">
|
<@form.Container @title={{this.editorTitle}} @format="large">
|
||||||
<div class="ai-persona-editor__response-format">
|
<div class="ai-agent-editor__response-format">
|
||||||
{{#if (gt @data.response_format.length 0)}}
|
{{#if (gt @data.response_format.length 0)}}
|
||||||
<pre class="ai-persona-editor__response-format-pre">
|
<pre class="ai-agent-editor__response-format-pre">
|
||||||
<code
|
<code
|
||||||
>{{this.displayJSON}}</code>
|
>{{this.displayJSON}}</code>
|
||||||
</pre>
|
</pre>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="ai-persona-editor__response-format-none">
|
<div class="ai-agent-editor__response-format-none">
|
||||||
{{i18n "discourse_ai.ai_persona.response_format.no_format"}}
|
{{i18n "discourse_ai.ai_agent.response_format.no_format"}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<@form.Button
|
<@form.Button
|
||||||
@action={{this.openModal}}
|
@action={{this.openModal}}
|
||||||
@label="discourse_ai.ai_persona.response_format.open_modal"
|
@label="discourse_ai.ai_agent.response_format.open_modal"
|
||||||
@disabled={{@data.system}}
|
@disabled={{@data.system}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
@ -1,14 +1,14 @@
|
|||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import { isGPTBot } from "../../lib/ai-bot-helper";
|
import { isGPTBot } from "../../lib/ai-bot-helper";
|
||||||
|
|
||||||
export default class AiPersonaFlair extends Component {
|
export default class AiAgentFlair extends Component {
|
||||||
static shouldRender(args) {
|
static shouldRender(args) {
|
||||||
return isGPTBot(args.post.user);
|
return isGPTBot(args.post.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<span class="persona-flair">
|
<span class="agent-flair">
|
||||||
{{@outletArgs.post.topic.ai_persona_name}}
|
{{@outletArgs.post.topic.ai_agent_name}}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
}
|
}
|
@ -70,7 +70,7 @@ export default class RagOptionsFk extends Component {
|
|||||||
@value={{field.value}}
|
@value={{field.value}}
|
||||||
@llms={{this.visionLlms}}
|
@llms={{this.visionLlms}}
|
||||||
@onChange={{field.set}}
|
@onChange={{field.set}}
|
||||||
@class="ai-persona-editor__llms"
|
@class="ai-agent-editor__llms"
|
||||||
/>
|
/>
|
||||||
</field.Custom>
|
</field.Custom>
|
||||||
</@form.Field>
|
</@form.Field>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import AiPersonaLlmSelector from "discourse/plugins/discourse-ai/discourse/components/ai-persona-llm-selector";
|
import AiAgentLlmSelector from "discourse/plugins/discourse-ai/discourse/components/ai-agent-llm-selector";
|
||||||
|
|
||||||
function isBotMessage(composer, currentUser) {
|
function isBotMessage(composer, currentUser) {
|
||||||
if (
|
if (
|
||||||
@ -21,7 +21,7 @@ function isBotMessage(composer, currentUser) {
|
|||||||
export default class BotSelector extends Component {
|
export default class BotSelector extends Component {
|
||||||
static shouldRender(args, container) {
|
static shouldRender(args, container) {
|
||||||
return (
|
return (
|
||||||
container?.currentUser?.ai_enabled_personas &&
|
container?.currentUser?.ai_enabled_agents &&
|
||||||
isBotMessage(args.model, container.currentUser)
|
isBotMessage(args.model, container.currentUser)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -29,8 +29,8 @@ export default class BotSelector extends Component {
|
|||||||
@service currentUser;
|
@service currentUser;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
setPersonaIdOnComposer(id) {
|
setAgentIdOnComposer(id) {
|
||||||
this.args.outletArgs.model.metaData = { ai_persona_id: id };
|
this.args.outletArgs.model.metaData = { ai_agent_id: id };
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@ -39,8 +39,8 @@ export default class BotSelector extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AiPersonaLlmSelector
|
<AiAgentLlmSelector
|
||||||
@setPersonaId={{this.setPersonaIdOnComposer}}
|
@setAgentId={{this.setAgentIdOnComposer}}
|
||||||
@setTargetRecipient={{this.setTargetRecipientsOnComposer}}
|
@setTargetRecipient={{this.setTargetRecipientsOnComposer}}
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
@ -9,8 +9,8 @@ import AiSearchDiscoveriesTooltip from "../../components/ai-search-discoveries-t
|
|||||||
export default class AiFullPageDiscobotDiscoveries extends Component {
|
export default class AiFullPageDiscobotDiscoveries extends Component {
|
||||||
static shouldRender(_args, { siteSettings, currentUser }) {
|
static shouldRender(_args, { siteSettings, currentUser }) {
|
||||||
return (
|
return (
|
||||||
siteSettings.ai_bot_discover_persona &&
|
siteSettings.ai_bot_discover_agent &&
|
||||||
currentUser?.can_use_ai_bot_discover_persona &&
|
currentUser?.can_use_ai_bot_discover_agent &&
|
||||||
currentUser?.user_option?.ai_search_discoveries
|
currentUser?.user_option?.ai_search_discoveries
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,8 @@ import AiSearchDiscoveriesTooltip from "../../components/ai-search-discoveries-t
|
|||||||
export default class AiDiscobotDiscoveries extends Component {
|
export default class AiDiscobotDiscoveries extends Component {
|
||||||
static shouldRender(args, { siteSettings, currentUser }) {
|
static shouldRender(args, { siteSettings, currentUser }) {
|
||||||
return (
|
return (
|
||||||
siteSettings.ai_bot_discover_persona &&
|
siteSettings.ai_bot_discover_agent &&
|
||||||
currentUser?.can_use_ai_bot_discover_persona &&
|
currentUser?.can_use_ai_bot_discover_agent &&
|
||||||
currentUser?.user_option?.ai_search_discoveries
|
currentUser?.user_option?.ai_search_discoveries
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -35,8 +35,8 @@ export default class PreferencesAiController extends Controller {
|
|||||||
checked: this.model.user_option.ai_search_discoveries,
|
checked: this.model.user_option.ai_search_discoveries,
|
||||||
isIncluded: (() => {
|
isIncluded: (() => {
|
||||||
return (
|
return (
|
||||||
this.siteSettings.ai_bot_discover_persona &&
|
this.siteSettings.ai_bot_discover_agent &&
|
||||||
this.model?.can_use_ai_bot_discover_persona &&
|
this.model?.can_use_ai_bot_discover_agent &&
|
||||||
this.siteSettings.ai_bot_enabled
|
this.siteSettings.ai_bot_enabled
|
||||||
);
|
);
|
||||||
})(),
|
})(),
|
||||||
|
@ -5,7 +5,7 @@ import Composer from "discourse/models/composer";
|
|||||||
import { i18n } from "discourse-i18n";
|
import { i18n } from "discourse-i18n";
|
||||||
import ShareFullTopicModal from "../components/modal/share-full-topic-modal";
|
import ShareFullTopicModal from "../components/modal/share-full-topic-modal";
|
||||||
|
|
||||||
const MAX_PERSONA_USER_ID = -1200;
|
const MAX_AGENT_USER_ID = -1200;
|
||||||
|
|
||||||
let enabledChatBotMap = null;
|
let enabledChatBotMap = null;
|
||||||
|
|
||||||
@ -40,12 +40,12 @@ export function getBotType(user) {
|
|||||||
if (!bot) {
|
if (!bot) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return bot.is_persona ? "persona" : "llm";
|
return bot.is_agent ? "agent" : "llm";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isPostFromAiBot(post, currentUser) {
|
export function isPostFromAiBot(post, currentUser) {
|
||||||
return (
|
return (
|
||||||
post.user_id <= MAX_PERSONA_USER_ID ||
|
post.user_id <= MAX_AGENT_USER_ID ||
|
||||||
!!currentUser?.ai_enabled_chat_bots?.any(
|
!!currentUser?.ai_enabled_chat_bots?.any(
|
||||||
(bot) => post.username === bot.username
|
(bot) => post.username === bot.username
|
||||||
)
|
)
|
||||||
@ -66,7 +66,7 @@ export async function composeAiBotMessage(
|
|||||||
options = {
|
options = {
|
||||||
skipFocus: false,
|
skipFocus: false,
|
||||||
topicBody: "",
|
topicBody: "",
|
||||||
personaUsername: null,
|
agentUsername: null,
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const currentUser = composer.currentUser;
|
const currentUser = composer.currentUser;
|
||||||
@ -77,8 +77,8 @@ export async function composeAiBotMessage(
|
|||||||
botUsername = currentUser.ai_enabled_chat_bots.find(
|
botUsername = currentUser.ai_enabled_chat_bots.find(
|
||||||
(bot) => bot.model_name === targetBot
|
(bot) => bot.model_name === targetBot
|
||||||
)?.username;
|
)?.username;
|
||||||
} else if (options.personaUsername) {
|
} else if (options.agentUsername) {
|
||||||
botUsername = options.personaUsername;
|
botUsername = options.agentUsername;
|
||||||
} else {
|
} else {
|
||||||
botUsername = currentUser.ai_enabled_chat_bots[0].username;
|
botUsername = currentUser.ai_enabled_chat_bots[0].username;
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ export default class AiBotConversationsHiddenSubmit extends Service {
|
|||||||
|
|
||||||
@tracked loading = false;
|
@tracked loading = false;
|
||||||
|
|
||||||
personaId;
|
agentId;
|
||||||
targetUsername;
|
targetUsername;
|
||||||
uploads = [];
|
uploads = [];
|
||||||
|
|
||||||
@ -35,12 +35,12 @@ export default class AiBotConversationsHiddenSubmit extends Service {
|
|||||||
async submitToBot() {
|
async submitToBot() {
|
||||||
if (
|
if (
|
||||||
this.inputValue.length <
|
this.inputValue.length <
|
||||||
this.siteSettings.min_personal_message_post_length
|
this.siteSettings.min_agentl_message_post_length
|
||||||
) {
|
) {
|
||||||
return this.dialog.alert({
|
return this.dialog.alert({
|
||||||
message: i18n(
|
message: i18n(
|
||||||
"discourse_ai.ai_bot.conversations.min_input_length_message",
|
"discourse_ai.ai_bot.conversations.min_input_length_message",
|
||||||
{ count: this.siteSettings.min_personal_message_post_length }
|
{ count: this.siteSettings.min_agentl_message_post_length }
|
||||||
),
|
),
|
||||||
didConfirm: () => this.focusInput(),
|
didConfirm: () => this.focusInput(),
|
||||||
didCancel: () => this.focusInput(),
|
didCancel: () => this.focusInput(),
|
||||||
@ -78,7 +78,7 @@ export default class AiBotConversationsHiddenSubmit extends Service {
|
|||||||
title,
|
title,
|
||||||
archetype: "private_message",
|
archetype: "private_message",
|
||||||
target_recipients: this.targetUsername,
|
target_recipients: this.targetUsername,
|
||||||
meta_data: { ai_persona_id: this.personaId },
|
meta_data: { ai_agent_id: this.agentId },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -22,9 +22,9 @@ export default {
|
|||||||
description: "discourse_ai.llms.preconfigured.description",
|
description: "discourse_ai.llms.preconfigured.description",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "discourse_ai.ai_persona.short_title",
|
label: "discourse_ai.ai_agent.short_title",
|
||||||
route: "adminPlugins.show.discourse-ai-personas",
|
route: "adminPlugins.show.discourse-ai-agents",
|
||||||
description: "discourse_ai.ai_persona.persona_description",
|
description: "discourse_ai.ai_agent.agent_description",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "discourse_ai.embeddings.short_title",
|
label: "discourse_ai.embeddings.short_title",
|
||||||
|
@ -3,7 +3,7 @@ import { withSilencedDeprecations } from "discourse/lib/deprecated";
|
|||||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
import { registerWidgetShim } from "discourse/widgets/render-glimmer";
|
import { registerWidgetShim } from "discourse/widgets/render-glimmer";
|
||||||
import AiBotHeaderIcon from "../discourse/components/ai-bot-header-icon";
|
import AiBotHeaderIcon from "../discourse/components/ai-bot-header-icon";
|
||||||
import AiPersonaFlair from "../discourse/components/post/ai-persona-flair";
|
import AiAgentFlair from "../discourse/components/post/ai-agent-flair";
|
||||||
import AiCancelStreamingButton from "../discourse/components/post-menu/ai-cancel-streaming-button";
|
import AiCancelStreamingButton from "../discourse/components/post-menu/ai-cancel-streaming-button";
|
||||||
import AiDebugButton from "../discourse/components/post-menu/ai-debug-button";
|
import AiDebugButton from "../discourse/components/post-menu/ai-debug-button";
|
||||||
import AiShareButton from "../discourse/components/post-menu/ai-share-button";
|
import AiShareButton from "../discourse/components/post-menu/ai-share-button";
|
||||||
@ -53,35 +53,35 @@ function initializeAIBotReplies(api) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializePersonaDecorator(api) {
|
function initializeAgentDecorator(api) {
|
||||||
api.renderAfterWrapperOutlet("post-meta-data-poster-name", AiPersonaFlair);
|
api.renderAfterWrapperOutlet("post-meta-data-poster-name", AiAgentFlair);
|
||||||
|
|
||||||
withSilencedDeprecations("discourse.post-stream-widget-overrides", () =>
|
withSilencedDeprecations("discourse.post-stream-widget-overrides", () =>
|
||||||
initializeWidgetPersonaDecorator(api)
|
initializeWidgetAgentDecorator(api)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeWidgetPersonaDecorator(api) {
|
function initializeWidgetAgentDecorator(api) {
|
||||||
api.decorateWidget(`poster-name:after`, (dec) => {
|
api.decorateWidget(`poster-name:after`, (dec) => {
|
||||||
const botType = getBotType(dec.attrs.user);
|
const botType = getBotType(dec.attrs.user);
|
||||||
// we have 2 ways of decorating
|
// we have 2 ways of decorating
|
||||||
// 1. if a bot is a LLM we decorate with persona name
|
// 1. if a bot is a LLM we decorate with agent name
|
||||||
// 2. if bot is a persona we decorate with LLM name
|
// 2. if bot is a agent we decorate with LLM name
|
||||||
if (botType === "llm") {
|
if (botType === "llm") {
|
||||||
return dec.widget.attach("persona-flair", {
|
return dec.widget.attach("agent-flair", {
|
||||||
personaName: dec.model?.topic?.ai_persona_name,
|
agentName: dec.model?.topic?.ai_agent_name,
|
||||||
});
|
});
|
||||||
} else if (botType === "persona") {
|
} else if (botType === "agent") {
|
||||||
return dec.widget.attach("persona-flair", {
|
return dec.widget.attach("agent-flair", {
|
||||||
personaName: dec.model?.llm_name,
|
agentName: dec.model?.llm_name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
registerWidgetShim(
|
registerWidgetShim(
|
||||||
"persona-flair",
|
"agent-flair",
|
||||||
"span.persona-flair",
|
"span.agent-flair",
|
||||||
hbs`{{@data.personaName}}`
|
hbs`{{@data.agentName}}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,11 +149,11 @@ function initializeShareTopicButton(api) {
|
|||||||
showShareConversationModal(modal, this.topic.id);
|
showShareConversationModal(modal, this.topic.id);
|
||||||
},
|
},
|
||||||
classNames: ["share-ai-conversation-button"],
|
classNames: ["share-ai-conversation-button"],
|
||||||
dependentKeys: ["topic.ai_persona_name"],
|
dependentKeys: ["topic.ai_agent_name"],
|
||||||
displayed() {
|
displayed() {
|
||||||
return (
|
return (
|
||||||
currentUser?.can_share_ai_bot_conversations &&
|
currentUser?.can_share_ai_bot_conversations &&
|
||||||
this.topic.ai_persona_name
|
this.topic.ai_agent_name
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -171,7 +171,7 @@ export default {
|
|||||||
withPluginApi((api) => {
|
withPluginApi((api) => {
|
||||||
attachHeaderIcon(api);
|
attachHeaderIcon(api);
|
||||||
initializeAIBotReplies(api);
|
initializeAIBotReplies(api);
|
||||||
initializePersonaDecorator(api);
|
initializeAgentDecorator(api);
|
||||||
initializeDebugButton(api, container);
|
initializeDebugButton(api, container);
|
||||||
initializeShareButton(api, container);
|
initializeShareButton(api, container);
|
||||||
initializeShareTopicButton(api, container);
|
initializeShareTopicButton(api, container);
|
||||||
|
@ -6,7 +6,7 @@ export default apiInitializer((api) => {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
!settings.ai_bot_enabled ||
|
!settings.ai_bot_enabled ||
|
||||||
!currentUser?.can_use_ai_bot_discover_persona
|
!currentUser?.can_use_ai_bot_discover_agent
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__row-item-persona {
|
&__row-item-agent {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ body.has-ai-conversations-sidebar {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: calc(100dvh - var(--header-offset) - 5em);
|
height: calc(100dvh - var(--header-offset) - 5em);
|
||||||
|
|
||||||
.persona-llm-selector {
|
.agent-llm-selector {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
.admin-contents .ai-persona-list-editor {
|
.admin-contents .ai-agent-list-editor {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-persona-list-editor {
|
.ai-agent-list-editor {
|
||||||
&__header {
|
&__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -23,7 +23,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-persona-tool-option-editor {
|
.ai-agent-tool-option-editor {
|
||||||
&__instructions {
|
&__instructions {
|
||||||
color: var(--primary-medium);
|
color: var(--primary-medium);
|
||||||
font-size: var(--font-down-1);
|
font-size: var(--font-down-1);
|
||||||
@ -31,7 +31,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-personas__container {
|
.ai-agents__container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -39,7 +39,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-persona-editor {
|
.ai-agent-editor {
|
||||||
padding-left: 0.5em;
|
padding-left: 0.5em;
|
||||||
|
|
||||||
&__tool-options {
|
&__tool-options {
|
@ -11,7 +11,7 @@ nav.post-controls .actions button.cancel-streaming {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.persona-llm-selector {
|
.agent-llm-selector {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -24,7 +24,7 @@ nav.post-controls .actions button.cancel-streaming {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ai-bot-pm {
|
.ai-bot-pm {
|
||||||
.gpt-persona {
|
.gpt-agent {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ article.streaming nav.post-controls .actions button.cancel-streaming {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.topic-body .persona-flair {
|
.topic-body .agent-flair {
|
||||||
order: 2;
|
order: 2;
|
||||||
font-size: var(--font-down-1);
|
font-size: var(--font-down-1);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
.ai-persona-editor {
|
.ai-agent-editor {
|
||||||
&__system_prompt,
|
&__system_prompt,
|
||||||
&__description,
|
&__description,
|
||||||
.select-kit.multi-select {
|
.select-kit.multi-select {
|
@ -45,7 +45,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ai-tool-list-editor__current,
|
.ai-tool-list-editor__current,
|
||||||
.ai-persona-list-editor__current,
|
.ai-agent-list-editor__current,
|
||||||
.ai-llms-list-editor__configured {
|
.ai-llms-list-editor__configured {
|
||||||
.d-admin-table {
|
.d-admin-table {
|
||||||
tr:hover {
|
tr:hover {
|
||||||
|
@ -6,8 +6,8 @@ en:
|
|||||||
descriptions:
|
descriptions:
|
||||||
discourse_ai:
|
discourse_ai:
|
||||||
search: "Allows AI search"
|
search: "Allows AI search"
|
||||||
stream_completion: "Allows streaming AI persona completions"
|
stream_completion: "Allows streaming AI agent completions"
|
||||||
update_personas: "Allows updating AI personas"
|
update_agents: "Allows updating AI agents"
|
||||||
|
|
||||||
site_settings:
|
site_settings:
|
||||||
categories:
|
categories:
|
||||||
@ -100,17 +100,17 @@ en:
|
|||||||
label: "Tool"
|
label: "Tool"
|
||||||
description: "Tool to use for triage (tool must have no parameters defined)"
|
description: "Tool to use for triage (tool must have no parameters defined)"
|
||||||
|
|
||||||
llm_persona_triage:
|
llm_agent_triage:
|
||||||
fields:
|
fields:
|
||||||
persona:
|
agent:
|
||||||
label: "Persona"
|
label: "Agent"
|
||||||
description: "AI Persona to use for triage (must have default LLM and User set)"
|
description: "AI Agent to use for triage (must have default LLM and User set)"
|
||||||
whisper:
|
whisper:
|
||||||
label: "Reply as Whisper"
|
label: "Reply as Whisper"
|
||||||
description: "Whether the persona's response should be a whisper"
|
description: "Whether the agent's response should be a whisper"
|
||||||
silent_mode:
|
silent_mode:
|
||||||
label: "Silent Mode"
|
label: "Silent Mode"
|
||||||
description: "In silent mode persona will receive the content but will not post anything on the forum - useful when performing triage using tools"
|
description: "In silent mode agent will receive the content but will not post anything on the forum - useful when performing triage using tools"
|
||||||
llm_triage:
|
llm_triage:
|
||||||
fields:
|
fields:
|
||||||
system_prompt:
|
system_prompt:
|
||||||
@ -146,15 +146,15 @@ en:
|
|||||||
flag_post:
|
flag_post:
|
||||||
label: "Flag post"
|
label: "Flag post"
|
||||||
description: "Flags post (either as spam or for review)"
|
description: "Flags post (either as spam or for review)"
|
||||||
include_personal_messages:
|
include_agentl_messages:
|
||||||
label: "Include personal messages"
|
label: "Include agentl messages"
|
||||||
description: "Also scan and triage personal messages"
|
description: "Also scan and triage agentl messages"
|
||||||
whisper:
|
whisper:
|
||||||
label: "Reply as Whisper"
|
label: "Reply as Whisper"
|
||||||
description: "Whether the AI's response should be a whisper"
|
description: "Whether the AI's response should be a whisper"
|
||||||
reply_persona:
|
reply_agent:
|
||||||
label: "Reply Persona"
|
label: "Reply Agent"
|
||||||
description: "AI Persona to use for replies (must have default LLM), will be prioritized over canned reply"
|
description: "AI Agent to use for replies (must have default LLM), will be prioritized over canned reply"
|
||||||
model:
|
model:
|
||||||
label: "Model"
|
label: "Model"
|
||||||
description: "Language model used for triage"
|
description: "Language model used for triage"
|
||||||
@ -167,12 +167,12 @@ en:
|
|||||||
|
|
||||||
features:
|
features:
|
||||||
short_title: "Features"
|
short_title: "Features"
|
||||||
description: "These are the AI features available to visitors on your site. These can be configured to use specific personas and LLMs, and can be access controlled by groups."
|
description: "These are the AI features available to visitors on your site. These can be configured to use specific agents and LLMs, and can be access controlled by groups."
|
||||||
back: "Back"
|
back: "Back"
|
||||||
list:
|
list:
|
||||||
header:
|
header:
|
||||||
name: "Name"
|
name: "Name"
|
||||||
persona: "Persona"
|
agent: "Agent"
|
||||||
groups: "Groups"
|
groups: "Groups"
|
||||||
edit: "Edit"
|
edit: "Edit"
|
||||||
set_up: "Set up"
|
set_up: "Set up"
|
||||||
@ -257,7 +257,7 @@ en:
|
|||||||
last_month: "Last month"
|
last_month: "Last month"
|
||||||
custom: "Custom..."
|
custom: "Custom..."
|
||||||
|
|
||||||
ai_persona:
|
ai_agent:
|
||||||
ai_tools: "Tools"
|
ai_tools: "Tools"
|
||||||
tool_strategies:
|
tool_strategies:
|
||||||
all: "Apply to all replies"
|
all: "Apply to all replies"
|
||||||
@ -269,7 +269,7 @@ en:
|
|||||||
edit: "Edit"
|
edit: "Edit"
|
||||||
description: "Description"
|
description: "Description"
|
||||||
no_llm_selected: "No language model selected"
|
no_llm_selected: "No language model selected"
|
||||||
use_parent_llm: "Use personas language model"
|
use_parent_llm: "Use agents language model"
|
||||||
max_context_posts: "Max context posts"
|
max_context_posts: "Max context posts"
|
||||||
max_context_posts_help: "The maximum number of posts to use as context for the AI when responding to a user. (empty for default)"
|
max_context_posts_help: "The maximum number of posts to use as context for the AI when responding to a user. (empty for default)"
|
||||||
vision_enabled: Vision enabled
|
vision_enabled: Vision enabled
|
||||||
@ -282,47 +282,47 @@ en:
|
|||||||
tool_details: Show tool details
|
tool_details: Show tool details
|
||||||
tool_details_help: Will show end users details on which tools the language model has triggered.
|
tool_details_help: Will show end users details on which tools the language model has triggered.
|
||||||
mentionable: Allow mentions
|
mentionable: Allow mentions
|
||||||
mentionable_help: If enabled, users in allowed groups can mention this user in posts, the AI will respond as this persona.
|
mentionable_help: If enabled, users in allowed groups can mention this user in posts, the AI will respond as this agent.
|
||||||
user: User
|
user: User
|
||||||
create_user: Create user
|
create_user: Create user
|
||||||
create_user_help: You can optionally attach a user to this persona. If you do, the AI will use this user to respond to requests.
|
create_user_help: You can optionally attach a user to this agent. If you do, the AI will use this user to respond to requests.
|
||||||
default_llm: Default language model
|
default_llm: Default language model
|
||||||
default_llm_help: The default language model to use for this persona. Required if you wish to mention persona on public posts.
|
default_llm_help: The default language model to use for this agent. Required if you wish to mention agent on public posts.
|
||||||
question_consolidator_llm: Language Model for Question Consolidator
|
question_consolidator_llm: Language Model for Question Consolidator
|
||||||
question_consolidator_llm_help: The language model to use for the question consolidator, you may choose a less powerful model to save costs.
|
question_consolidator_llm_help: The language model to use for the question consolidator, you may choose a less powerful model to save costs.
|
||||||
system_prompt: System prompt
|
system_prompt: System prompt
|
||||||
forced_tool_strategy: Forced tool strategy
|
forced_tool_strategy: Forced tool strategy
|
||||||
allow_chat_direct_messages: "Allow chat direct messages"
|
allow_chat_direct_messages: "Allow chat direct messages"
|
||||||
allow_chat_direct_messages_help: "If enabled, users in allowed groups can send direct messages to this persona."
|
allow_chat_direct_messages_help: "If enabled, users in allowed groups can send direct messages to this agent."
|
||||||
allow_chat_channel_mentions: "Allow chat channel mentions"
|
allow_chat_channel_mentions: "Allow chat channel mentions"
|
||||||
allow_chat_channel_mentions_help: "If enabled, users in allowed groups can mention this persona in chat channels."
|
allow_chat_channel_mentions_help: "If enabled, users in allowed groups can mention this agent in chat channels."
|
||||||
allow_personal_messages: "Allow personal messages"
|
allow_agentl_messages: "Allow agentl messages"
|
||||||
allow_personal_messages_help: "If enabled, users in allowed groups can send personal messages to this persona."
|
allow_agentl_messages_help: "If enabled, users in allowed groups can send agentl messages to this agent."
|
||||||
allow_topic_mentions: "Allow topic mentions"
|
allow_topic_mentions: "Allow topic mentions"
|
||||||
allow_topic_mentions_help: "If enabled, users in allowed groups can mention this persona in topics."
|
allow_topic_mentions_help: "If enabled, users in allowed groups can mention this agent in topics."
|
||||||
force_default_llm: "Always use default language model"
|
force_default_llm: "Always use default language model"
|
||||||
save: "Save"
|
save: "Save"
|
||||||
saved: "Persona saved"
|
saved: "Agent saved"
|
||||||
enabled: "Enabled?"
|
enabled: "Enabled?"
|
||||||
tools: "Enabled tools"
|
tools: "Enabled tools"
|
||||||
forced_tools: "Forced tools"
|
forced_tools: "Forced tools"
|
||||||
allowed_groups: "Allowed groups"
|
allowed_groups: "Allowed groups"
|
||||||
confirm_delete: "Are you sure you want to delete this persona?"
|
confirm_delete: "Are you sure you want to delete this agent?"
|
||||||
new: "New persona"
|
new: "New agent"
|
||||||
no_personas: "You have not created any personas yet"
|
no_agents: "You have not created any agents yet"
|
||||||
title: "Personas"
|
title: "Agents"
|
||||||
short_title: "Personas"
|
short_title: "Agents"
|
||||||
delete: "Delete"
|
delete: "Delete"
|
||||||
temperature: "Temperature"
|
temperature: "Temperature"
|
||||||
temperature_help: "Temperature to use for the LLM. Increase to increase creativity (leave empty to use model default, generally a value from 0.0 to 2.0)"
|
temperature_help: "Temperature to use for the LLM. Increase to increase creativity (leave empty to use model default, generally a value from 0.0 to 2.0)"
|
||||||
top_p: "Top P"
|
top_p: "Top P"
|
||||||
top_p_help: "Top P to use for the LLM, increase to increase randomness (leave empty to use model default, generally a value from 0.0 to 1.0)"
|
top_p_help: "Top P to use for the LLM, increase to increase randomness (leave empty to use model default, generally a value from 0.0 to 1.0)"
|
||||||
priority: "Priority"
|
priority: "Priority"
|
||||||
priority_help: "Priority personas are displayed to users at the top of the persona list. If multiple personas have priority, they will be sorted alphabetically."
|
priority_help: "Priority agents are displayed to users at the top of the agent list. If multiple agents have priority, they will be sorted alphabetically."
|
||||||
tool_options: "Tool options"
|
tool_options: "Tool options"
|
||||||
rag_conversation_chunks: "Search conversation chunks"
|
rag_conversation_chunks: "Search conversation chunks"
|
||||||
rag_conversation_chunks_help: "The number of chunks to use for the RAG model searches. Increase to increase the amount of context the AI can use."
|
rag_conversation_chunks_help: "The number of chunks to use for the RAG model searches. Increase to increase the amount of context the AI can use."
|
||||||
persona_description: "Personas are a powerful feature that allows you to customize the behavior of the AI engine in your Discourse forum. They act as a 'system message' that guides the AI's responses and interactions, helping to create a more personalized and engaging user experience."
|
agent_description: "Agents are a powerful feature that allows you to customize the behavior of the AI engine in your Discourse forum. They act as a 'system message' that guides the AI's responses and interactions, helping to create a more agentlized and engaging user experience."
|
||||||
response_format:
|
response_format:
|
||||||
title: "JSON response format"
|
title: "JSON response format"
|
||||||
no_format: "No JSON format specified"
|
no_format: "No JSON format specified"
|
||||||
@ -344,7 +344,7 @@ en:
|
|||||||
|
|
||||||
ai_bot:
|
ai_bot:
|
||||||
title: "AI bot options"
|
title: "AI bot options"
|
||||||
save_first: "More AI bot options will become available once you save the persona."
|
save_first: "More AI bot options will become available once you save the agent."
|
||||||
|
|
||||||
rag:
|
rag:
|
||||||
title: "RAG"
|
title: "RAG"
|
||||||
@ -377,7 +377,7 @@ en:
|
|||||||
name_help: "Name will show up in the Discourse UI and is the short identifier you will use to find the tool in various settings, it should be distinct (it is required)"
|
name_help: "Name will show up in the Discourse UI and is the short identifier you will use to find the tool in various settings, it should be distinct (it is required)"
|
||||||
new: "New tool"
|
new: "New tool"
|
||||||
tool_name: "Tool Name"
|
tool_name: "Tool Name"
|
||||||
tool_name_help: "Tool Name is presented to the large language model. It is not distinct, but it is distinct per persona. (persona validates on save)"
|
tool_name_help: "Tool Name is presented to the large language model. It is not distinct, but it is distinct per agent. (agent validates on save)"
|
||||||
description: "Description"
|
description: "Description"
|
||||||
description_help: "A clear description of the tool's purpose for the language model"
|
description_help: "A clear description of the tool's purpose for the language model"
|
||||||
subheader_description: "Tools extend the capabilities of AI bots with user-defined JavaScript functions."
|
subheader_description: "Tools extend the capabilities of AI bots with user-defined JavaScript functions."
|
||||||
@ -455,7 +455,7 @@ en:
|
|||||||
ai_bot: "AI bot"
|
ai_bot: "AI bot"
|
||||||
ai_helper: "Helper"
|
ai_helper: "Helper"
|
||||||
ai_helper_image_caption: "Image caption"
|
ai_helper_image_caption: "Image caption"
|
||||||
ai_persona: "Persona (%{persona})"
|
ai_agent: "Agent (%{agent})"
|
||||||
ai_summarization: "Summarize"
|
ai_summarization: "Summarize"
|
||||||
ai_embeddings_semantic_search: "AI search"
|
ai_embeddings_semantic_search: "AI search"
|
||||||
ai_spam: "Spam"
|
ai_spam: "Spam"
|
||||||
@ -681,7 +681,7 @@ en:
|
|||||||
click_to_run_label: "Run Artifact"
|
click_to_run_label: "Run Artifact"
|
||||||
|
|
||||||
ai_bot:
|
ai_bot:
|
||||||
persona: "Persona"
|
agent: "Agent"
|
||||||
llm: "Model"
|
llm: "Model"
|
||||||
pm_warning: "AI chatbot messages are monitored regularly by moderators."
|
pm_warning: "AI chatbot messages are monitored regularly by moderators."
|
||||||
cancel_streaming: "Stop reply"
|
cancel_streaming: "Stop reply"
|
||||||
|
@ -10,9 +10,9 @@ en:
|
|||||||
llm_tool_triage:
|
llm_tool_triage:
|
||||||
title: Triage posts using AI Tool
|
title: Triage posts using AI Tool
|
||||||
description: "Triage posts using custom logic in an AI tool"
|
description: "Triage posts using custom logic in an AI tool"
|
||||||
llm_persona_triage:
|
llm_agent_triage:
|
||||||
title: Triage posts using AI Persona
|
title: Triage posts using AI Agent
|
||||||
description: "Respond to posts using a specific AI persona"
|
description: "Respond to posts using a specific AI agent"
|
||||||
llm_triage:
|
llm_triage:
|
||||||
title: Triage posts using AI
|
title: Triage posts using AI
|
||||||
description: "Triage posts using a large language model"
|
description: "Triage posts using a large language model"
|
||||||
@ -76,7 +76,7 @@ en:
|
|||||||
ai_auto_image_caption_allowed_groups: "Users on these groups can toggle automatic image captioning."
|
ai_auto_image_caption_allowed_groups: "Users on these groups can toggle automatic image captioning."
|
||||||
|
|
||||||
ai_embeddings_selected_model: "Use the selected model for generating embeddings."
|
ai_embeddings_selected_model: "Use the selected model for generating embeddings."
|
||||||
ai_embeddings_generate_for_pms: "Generate embeddings for personal messages."
|
ai_embeddings_generate_for_pms: "Generate embeddings for agentl messages."
|
||||||
ai_embeddings_semantic_related_topics_enabled: "Use Semantic Search for related topics."
|
ai_embeddings_semantic_related_topics_enabled: "Use Semantic Search for related topics."
|
||||||
ai_embeddings_semantic_related_topics: "Maximum number of topics to show in related topic section."
|
ai_embeddings_semantic_related_topics: "Maximum number of topics to show in related topic section."
|
||||||
ai_embeddings_backfill_batch_size: "Number of embeddings to backfill every 15 minutes."
|
ai_embeddings_backfill_batch_size: "Number of embeddings to backfill every 15 minutes."
|
||||||
@ -88,7 +88,7 @@ en:
|
|||||||
|
|
||||||
ai_summarization_enabled: "Enable the summarize feature"
|
ai_summarization_enabled: "Enable the summarize feature"
|
||||||
ai_summarization_model: "Model to use for summarization"
|
ai_summarization_model: "Model to use for summarization"
|
||||||
ai_summarization_persona: "Persona to use for summarize feature"
|
ai_summarization_agent: "Agent to use for summarize feature"
|
||||||
ai_custom_summarization_allowed_groups: "Groups allowed to use create new summaries."
|
ai_custom_summarization_allowed_groups: "Groups allowed to use create new summaries."
|
||||||
ai_pm_summarization_allowed_groups: "Groups allowed to create and view summaries in PMs."
|
ai_pm_summarization_allowed_groups: "Groups allowed to create and view summaries in PMs."
|
||||||
ai_summary_gists_enabled: "Generate brief summaries of latest replies in topics automatically"
|
ai_summary_gists_enabled: "Generate brief summaries of latest replies in topics automatically"
|
||||||
@ -99,7 +99,7 @@ en:
|
|||||||
ai_bot_enable_chat_warning: "Display a warning when PM chat is initiated. Can be overriden by editing the translation string: discourse_ai.ai_bot.pm_warning"
|
ai_bot_enable_chat_warning: "Display a warning when PM chat is initiated. Can be overriden by editing the translation string: discourse_ai.ai_bot.pm_warning"
|
||||||
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_debugging_allowed_groups: "Allow these groups to see a debug button on posts which displays the raw AI request and response"
|
ai_bot_debugging_allowed_groups: "Allow these groups to see a debug button on posts which displays the raw AI request and response"
|
||||||
ai_bot_public_sharing_allowed_groups: "Allow these groups to share AI personal messages with the public via a unique publicly available link. Note: if your site requires login, shares will also require login."
|
ai_bot_public_sharing_allowed_groups: "Allow these groups to share AI agentl messages with the public via a unique publicly available link. Note: if your site requires login, shares will also require login."
|
||||||
ai_bot_add_to_header: "Display a button in the header to start a PM with a AI Bot"
|
ai_bot_add_to_header: "Display a button in the header to start a PM with a AI Bot"
|
||||||
ai_bot_github_access_token: "GitHub access token for use with GitHub AI tools (required for search support)"
|
ai_bot_github_access_token: "GitHub access token for use with GitHub AI tools (required for search support)"
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ en:
|
|||||||
ai_discord_app_id: "The ID of the Discord application you would like to connect Discord search to"
|
ai_discord_app_id: "The ID of the Discord application you would like to connect Discord search to"
|
||||||
ai_discord_app_public_key: "The public key of the Discord application you would like to connect Discord search to"
|
ai_discord_app_public_key: "The public key of the Discord application you would like to connect Discord search to"
|
||||||
ai_discord_search_mode: "Select the search mode to use for Discord search"
|
ai_discord_search_mode: "Select the search mode to use for Discord search"
|
||||||
ai_discord_search_persona: "The persona to use for Discord search."
|
ai_discord_search_agent: "The agent to use for Discord search."
|
||||||
ai_discord_allowed_guilds: "Discord guilds (servers) where the bot is allowed to search"
|
ai_discord_allowed_guilds: "Discord guilds (servers) where the bot is allowed to search"
|
||||||
ai_bot_enable_dedicated_ux: "Allow for full screen bot interface, instead of a PM"
|
ai_bot_enable_dedicated_ux: "Allow for full screen bot interface, instead of a PM"
|
||||||
|
|
||||||
@ -129,7 +129,7 @@ en:
|
|||||||
description: "This report provides sentiment analysis for posts, grouped by category, with positive, negative, and neutral scores for each post and category."
|
description: "This report provides sentiment analysis for posts, grouped by category, with positive, negative, and neutral scores for each post and category."
|
||||||
overall_sentiment:
|
overall_sentiment:
|
||||||
title: "Overall sentiment"
|
title: "Overall sentiment"
|
||||||
description: 'The chart compares the number of posts classified as either positive or negative. These are calculated when positive or negative scores > the set threshold score. This means neutral posts are not shown. Personal messages (PMs) are also excluded. Classified with "cardiffnlp/twitter-roberta-base-sentiment-latest"'
|
description: 'The chart compares the number of posts classified as either positive or negative. These are calculated when positive or negative scores > the set threshold score. This means neutral posts are not shown. Agentl messages (PMs) are also excluded. Classified with "cardiffnlp/twitter-roberta-base-sentiment-latest"'
|
||||||
xaxis: "Positive(%)"
|
xaxis: "Positive(%)"
|
||||||
yaxis: "Date"
|
yaxis: "Date"
|
||||||
emotion_admiration:
|
emotion_admiration:
|
||||||
@ -267,8 +267,8 @@ en:
|
|||||||
title: "%{title} - AI Conversation - %{site_name}"
|
title: "%{title} - AI Conversation - %{site_name}"
|
||||||
errors:
|
errors:
|
||||||
not_allowed: "You are not allowed to share this topic"
|
not_allowed: "You are not allowed to share this topic"
|
||||||
other_people_in_pm: "Personal messages with other humans cannot be shared publicly"
|
other_people_in_pm: "Agentl messages with other humans cannot be shared publicly"
|
||||||
other_content_in_pm: "Personal messages containing posts from other people cannot be shared publicly"
|
other_content_in_pm: "Agentl messages containing posts from other people cannot be shared publicly"
|
||||||
failed_to_share: "Failed to share the conversation"
|
failed_to_share: "Failed to share the conversation"
|
||||||
conversation_deleted: "Conversation share deleted successfully"
|
conversation_deleted: "Conversation share deleted successfully"
|
||||||
spam_detection:
|
spam_detection:
|
||||||
@ -283,10 +283,10 @@ en:
|
|||||||
reply_error: "Sorry, it looks like our system encountered an unexpected issue while trying to reply.\n\n[details='Error details']\n%{details}\n[/details]"
|
reply_error: "Sorry, it looks like our system encountered an unexpected issue while trying to reply.\n\n[details='Error details']\n%{details}\n[/details]"
|
||||||
default_pm_prefix: "[Untitled AI bot PM]"
|
default_pm_prefix: "[Untitled AI bot PM]"
|
||||||
thinking: "Thinking..."
|
thinking: "Thinking..."
|
||||||
personas:
|
agents:
|
||||||
default_llm_required: "Default LLM model is required prior to enabling Chat"
|
default_llm_required: "Default LLM model is required prior to enabling Chat"
|
||||||
cannot_delete_system_persona: "System personas cannot be deleted, please disable it instead"
|
cannot_delete_system_agent: "System agents cannot be deleted, please disable it instead"
|
||||||
cannot_edit_system_persona: "System personas can only be renamed, you may not edit tools or system prompt, instead disable and make a copy"
|
cannot_edit_system_agent: "System agents can only be renamed, you may not edit tools or system prompt, instead disable and make a copy"
|
||||||
cannot_have_duplicate_tools: "Can not have duplicate tools"
|
cannot_have_duplicate_tools: "Can not have duplicate tools"
|
||||||
github_helper:
|
github_helper:
|
||||||
name: "GitHub Helper"
|
name: "GitHub Helper"
|
||||||
@ -326,10 +326,10 @@ en:
|
|||||||
description: "AI Bot specialized in creating interactive web artifacts"
|
description: "AI Bot specialized in creating interactive web artifacts"
|
||||||
summarizer:
|
summarizer:
|
||||||
name: "Summarizer"
|
name: "Summarizer"
|
||||||
description: "Default persona used to power AI summaries"
|
description: "Default agent used to power AI summaries"
|
||||||
short_summarizer:
|
short_summarizer:
|
||||||
name: "Summarizer (short form)"
|
name: "Summarizer (short form)"
|
||||||
description: "Default persona used to power AI short summaries for topic lists' items"
|
description: "Default agent used to power AI short summaries for topic lists' items"
|
||||||
topic_not_found: "Summary unavailable, topic not found!"
|
topic_not_found: "Summary unavailable, topic not found!"
|
||||||
summarizing: "Summarizing topic"
|
summarizing: "Summarizing topic"
|
||||||
searching: "Searching for: '%{query}'"
|
searching: "Searching for: '%{query}'"
|
||||||
@ -521,7 +521,7 @@ en:
|
|||||||
other: "We couldn't delete this model because %{settings} are using it. Update the settings and try again."
|
other: "We couldn't delete this model because %{settings} are using it. Update the settings and try again."
|
||||||
cannot_edit_builtin: "You can't edit a built-in model."
|
cannot_edit_builtin: "You can't edit a built-in model."
|
||||||
|
|
||||||
personas:
|
agents:
|
||||||
malformed_examples: "The given examples have the wrong format."
|
malformed_examples: "The given examples have the wrong format."
|
||||||
|
|
||||||
embeddings:
|
embeddings:
|
||||||
@ -554,12 +554,12 @@ en:
|
|||||||
quota_exceeded: "You have exceeded the quota for this model. Please try again in %{relative_time}."
|
quota_exceeded: "You have exceeded the quota for this model. Please try again in %{relative_time}."
|
||||||
quota_required: "You must specify maximum tokens or usages for this model"
|
quota_required: "You must specify maximum tokens or usages for this model"
|
||||||
no_query_specified: The query parameter is required, please specify it.
|
no_query_specified: The query parameter is required, please specify it.
|
||||||
no_user_for_persona: The persona specified does not have a user associated with it.
|
no_user_for_agent: The agent specified does not have a user associated with it.
|
||||||
persona_not_found: The persona specified does not exist. Check the persona_name or persona_id params.
|
agent_not_found: The agent specified does not exist. Check the agent_name or agent_id params.
|
||||||
no_user_specified: The username or the user_unique_id parameter is required, please specify it.
|
no_user_specified: The username or the user_unique_id parameter is required, please specify it.
|
||||||
user_not_found: The user specified does not exist. Check the username param.
|
user_not_found: The user specified does not exist. Check the username param.
|
||||||
persona_disabled: The persona specified is disabled. Check the persona_name or persona_id params.
|
agent_disabled: The agent specified is disabled. Check the agent_name or agent_id params.
|
||||||
no_default_llm: The persona must have a default_llm defined.
|
no_default_llm: The agent must have a default_llm defined.
|
||||||
user_not_allowed: The user is not allowed to participate in the topic.
|
user_not_allowed: The user is not allowed to participate in the topic.
|
||||||
prompt_message_length: The message %{idx} is over the 1000 character limit.
|
prompt_message_length: The message %{idx} is over the 1000 character limit.
|
||||||
dashboard:
|
dashboard:
|
||||||
|
@ -63,12 +63,12 @@ Discourse::Application.routes.draw do
|
|||||||
:constraints => StaffConstraint.new
|
:constraints => StaffConstraint.new
|
||||||
|
|
||||||
scope "/admin/plugins/discourse-ai", constraints: AdminConstraint.new do
|
scope "/admin/plugins/discourse-ai", constraints: AdminConstraint.new do
|
||||||
resources :ai_personas,
|
resources :ai_agents,
|
||||||
only: %i[index new create edit update destroy],
|
only: %i[index new create edit update destroy],
|
||||||
path: "ai-personas",
|
path: "ai-agents",
|
||||||
controller: "discourse_ai/admin/ai_personas"
|
controller: "discourse_ai/admin/ai_agents"
|
||||||
|
|
||||||
post "/ai-personas/stream-reply" => "discourse_ai/admin/ai_personas#stream_reply"
|
post "/ai-agents/stream-reply" => "discourse_ai/admin/ai_agents#stream_reply"
|
||||||
|
|
||||||
resources(
|
resources(
|
||||||
:ai_tools,
|
:ai_tools,
|
||||||
@ -79,10 +79,10 @@ Discourse::Application.routes.draw do
|
|||||||
|
|
||||||
post "/ai-tools/:id/test", to: "discourse_ai/admin/ai_tools#test"
|
post "/ai-tools/:id/test", to: "discourse_ai/admin/ai_tools#test"
|
||||||
|
|
||||||
post "/ai-personas/:id/create-user", to: "discourse_ai/admin/ai_personas#create_user"
|
post "/ai-agents/:id/create-user", to: "discourse_ai/admin/ai_agents#create_user"
|
||||||
|
|
||||||
put "/ai-personas/:id/files/remove", to: "discourse_ai/admin/ai_personas#remove_file"
|
put "/ai-agents/:id/files/remove", to: "discourse_ai/admin/ai_agents#remove_file"
|
||||||
get "/ai-personas/:id/files/status", to: "discourse_ai/admin/ai_personas#indexing_status_check"
|
get "/ai-agents/:id/files/status", to: "discourse_ai/admin/ai_agents#indexing_status_check"
|
||||||
|
|
||||||
post "/rag-document-fragments/files/upload",
|
post "/rag-document-fragments/files/upload",
|
||||||
to: "discourse_ai/admin/rag_document_fragments#upload_file"
|
to: "discourse_ai/admin/rag_document_fragments#upload_file"
|
||||||
|
87
db/migrate/20250528154009_copy_persona_tables_to_agent.rb
Normal file
87
db/migrate/20250528154009_copy_persona_tables_to_agent.rb
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CopyPersonaTablesToAgent < ActiveRecord::Migration[7.0]
|
||||||
|
def up
|
||||||
|
# Copy the main table structure and data
|
||||||
|
if table_exists?(:ai_personas) && !table_exists?(:ai_agents)
|
||||||
|
execute <<~SQL
|
||||||
|
CREATE TABLE ai_agents AS
|
||||||
|
SELECT * FROM ai_personas
|
||||||
|
SQL
|
||||||
|
|
||||||
|
# Copy indexes from ai_personas to ai_agents
|
||||||
|
execute <<~SQL
|
||||||
|
CREATE UNIQUE INDEX index_ai_agents_on_id
|
||||||
|
ON ai_agents USING btree (id)
|
||||||
|
SQL
|
||||||
|
|
||||||
|
# Copy any other indexes that exist on ai_personas
|
||||||
|
indexes = execute(<<~SQL).to_a
|
||||||
|
SELECT indexname, indexdef
|
||||||
|
FROM pg_indexes
|
||||||
|
WHERE tablename = 'ai_personas'
|
||||||
|
AND indexname != 'ai_personas_pkey'
|
||||||
|
SQL
|
||||||
|
|
||||||
|
indexes.each do |index|
|
||||||
|
new_index_def = index['indexdef'].gsub('ai_personas', 'ai_agents')
|
||||||
|
new_index_name = index['indexname'].gsub('ai_personas', 'ai_agents')
|
||||||
|
new_index_def = new_index_def.gsub(index['indexname'], new_index_name)
|
||||||
|
execute(new_index_def)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Update polymorphic associations to point to new table
|
||||||
|
execute <<~SQL
|
||||||
|
UPDATE rag_document_fragments
|
||||||
|
SET target_type = 'AiAgent'
|
||||||
|
WHERE target_type = 'AiPersona'
|
||||||
|
SQL
|
||||||
|
|
||||||
|
execute <<~SQL
|
||||||
|
UPDATE upload_references
|
||||||
|
SET target_type = 'AiAgent'
|
||||||
|
WHERE target_type = 'AiPersona'
|
||||||
|
SQL
|
||||||
|
|
||||||
|
# Migrate persona-related site settings to agent equivalents
|
||||||
|
migrate_site_setting('ai_summarization_persona', 'ai_summarization_agent')
|
||||||
|
migrate_site_setting('ai_summary_gists_persona', 'ai_summary_gists_agent')
|
||||||
|
migrate_site_setting('ai_bot_discover_persona', 'ai_bot_discover_agent')
|
||||||
|
migrate_site_setting('ai_discord_search_persona', 'ai_discord_search_agent')
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
drop_table :ai_agents if table_exists?(:ai_agents)
|
||||||
|
|
||||||
|
# Revert polymorphic associations
|
||||||
|
execute <<~SQL
|
||||||
|
UPDATE rag_document_fragments
|
||||||
|
SET target_type = 'AiPersona'
|
||||||
|
WHERE target_type = 'AiAgent'
|
||||||
|
SQL
|
||||||
|
|
||||||
|
execute <<~SQL
|
||||||
|
UPDATE upload_references
|
||||||
|
SET target_type = 'AiPersona'
|
||||||
|
WHERE target_type = 'AiAgent'
|
||||||
|
SQL
|
||||||
|
|
||||||
|
# Remove the new agent settings (keep the old persona ones)
|
||||||
|
['ai_summarization_agent', 'ai_summary_gists_agent', 'ai_bot_discover_agent', 'ai_discord_search_agent'].each do |setting|
|
||||||
|
execute "DELETE FROM site_settings WHERE name = '#{setting}'"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def migrate_site_setting(old_name, new_name)
|
||||||
|
execute <<~SQL
|
||||||
|
INSERT INTO site_settings (name, value, data_type, created_at, updated_at)
|
||||||
|
SELECT '#{new_name}', value, data_type, NOW(), NOW()
|
||||||
|
FROM site_settings
|
||||||
|
WHERE name = '#{old_name}'
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM site_settings WHERE name = '#{new_name}')
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
end
|
24
db/post_migrate/20250528154010_drop_persona_tables.rb
Normal file
24
db/post_migrate/20250528154010_drop_persona_tables.rb
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class DropPersonaTables < ActiveRecord::Migration[7.0]
|
||||||
|
def up
|
||||||
|
# Drop the old table after copying to new one
|
||||||
|
drop_table :ai_personas if table_exists?(:ai_personas)
|
||||||
|
|
||||||
|
# Remove old persona settings after copying to agent settings
|
||||||
|
old_persona_settings = [
|
||||||
|
'ai_summarization_persona',
|
||||||
|
'ai_summary_gists_persona',
|
||||||
|
'ai_bot_discover_persona',
|
||||||
|
'ai_discord_search_persona'
|
||||||
|
]
|
||||||
|
|
||||||
|
old_persona_settings.each do |setting|
|
||||||
|
execute "DELETE FROM site_settings WHERE name = '#{setting}'"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
raise ActiveRecord::IrreversibleMigration, "Cannot recreate dropped persona tables and settings"
|
||||||
|
end
|
||||||
|
end
|
@ -1,17 +1,17 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
if defined?(DiscourseAutomation)
|
if defined?(DiscourseAutomation)
|
||||||
DiscourseAutomation::Scriptable.add("llm_persona_triage") do
|
DiscourseAutomation::Scriptable.add("llm_agent_triage") do
|
||||||
version 1
|
version 1
|
||||||
run_in_background
|
run_in_background
|
||||||
|
|
||||||
triggerables %i[post_created_edited]
|
triggerables %i[post_created_edited]
|
||||||
|
|
||||||
field :persona,
|
field :agent,
|
||||||
component: :choices,
|
component: :choices,
|
||||||
required: true,
|
required: true,
|
||||||
extra: {
|
extra: {
|
||||||
content: DiscourseAi::Automation.available_persona_choices,
|
content: DiscourseAi::Automation.available_agent_choices,
|
||||||
}
|
}
|
||||||
field :whisper, component: :boolean
|
field :whisper, component: :boolean
|
||||||
field :silent_mode, component: :boolean
|
field :silent_mode, component: :boolean
|
||||||
@ -20,28 +20,28 @@ if defined?(DiscourseAutomation)
|
|||||||
post = context["post"]
|
post = context["post"]
|
||||||
next if post&.user&.bot?
|
next if post&.user&.bot?
|
||||||
|
|
||||||
persona_id = fields.dig("persona", "value")
|
agent_id = fields.dig("agent", "value")
|
||||||
whisper = !!fields.dig("whisper", "value")
|
whisper = !!fields.dig("whisper", "value")
|
||||||
silent_mode = !!fields.dig("silent_mode", "value")
|
silent_mode = !!fields.dig("silent_mode", "value")
|
||||||
|
|
||||||
begin
|
begin
|
||||||
RateLimiter.new(
|
RateLimiter.new(
|
||||||
Discourse.system_user,
|
Discourse.system_user,
|
||||||
"llm_persona_triage_#{post.id}",
|
"llm_agent_triage_#{post.id}",
|
||||||
SiteSetting.ai_automation_max_triage_per_post_per_minute,
|
SiteSetting.ai_automation_max_triage_per_post_per_minute,
|
||||||
1.minute,
|
1.minute,
|
||||||
).performed!
|
).performed!
|
||||||
|
|
||||||
RateLimiter.new(
|
RateLimiter.new(
|
||||||
Discourse.system_user,
|
Discourse.system_user,
|
||||||
"llm_persona_triage",
|
"llm_agent_triage",
|
||||||
SiteSetting.ai_automation_max_triage_per_minute,
|
SiteSetting.ai_automation_max_triage_per_minute,
|
||||||
1.minute,
|
1.minute,
|
||||||
).performed!
|
).performed!
|
||||||
|
|
||||||
DiscourseAi::Automation::LlmPersonaTriage.handle(
|
DiscourseAi::Automation::LlmAgentTriage.handle(
|
||||||
post: post,
|
post: post,
|
||||||
persona_id: persona_id,
|
agent_id: agent_id,
|
||||||
whisper: whisper,
|
whisper: whisper,
|
||||||
automation: self.automation,
|
automation: self.automation,
|
||||||
silent_mode: silent_mode,
|
silent_mode: silent_mode,
|
||||||
@ -49,7 +49,7 @@ if defined?(DiscourseAutomation)
|
|||||||
rescue => e
|
rescue => e
|
||||||
Discourse.warn_exception(
|
Discourse.warn_exception(
|
||||||
e,
|
e,
|
||||||
message: "llm_persona_triage: skipped triage on post #{post.id}",
|
message: "llm_agent_triage: skipped triage on post #{post.id}",
|
||||||
)
|
)
|
||||||
raise e if Rails.env.tests?
|
raise e if Rails.env.tests?
|
||||||
end
|
end
|
@ -10,7 +10,7 @@ if defined?(DiscourseAutomation)
|
|||||||
triggerables %i[post_created_edited]
|
triggerables %i[post_created_edited]
|
||||||
|
|
||||||
# TODO move to triggerables
|
# TODO move to triggerables
|
||||||
field :include_personal_messages, component: :boolean
|
field :include_agentl_messages, component: :boolean
|
||||||
|
|
||||||
# Inputs
|
# Inputs
|
||||||
field :model,
|
field :model,
|
||||||
@ -39,11 +39,11 @@ if defined?(DiscourseAutomation)
|
|||||||
default: "review"
|
default: "review"
|
||||||
field :canned_reply_user, component: :user
|
field :canned_reply_user, component: :user
|
||||||
field :canned_reply, component: :message
|
field :canned_reply, component: :message
|
||||||
field :reply_persona,
|
field :reply_agent,
|
||||||
component: :choices,
|
component: :choices,
|
||||||
extra: {
|
extra: {
|
||||||
content:
|
content:
|
||||||
DiscourseAi::Automation.available_persona_choices(
|
DiscourseAi::Automation.available_agent_choices(
|
||||||
require_user: false,
|
require_user: false,
|
||||||
require_default_llm: true,
|
require_default_llm: true,
|
||||||
),
|
),
|
||||||
@ -55,13 +55,13 @@ if defined?(DiscourseAutomation)
|
|||||||
next if post&.user&.bot?
|
next if post&.user&.bot?
|
||||||
|
|
||||||
if post.topic.private_message?
|
if post.topic.private_message?
|
||||||
include_personal_messages = fields.dig("include_personal_messages", "value")
|
include_agentl_messages = fields.dig("include_agentl_messages", "value")
|
||||||
next if !include_personal_messages
|
next if !include_agentl_messages
|
||||||
end
|
end
|
||||||
|
|
||||||
canned_reply = fields.dig("canned_reply", "value")
|
canned_reply = fields.dig("canned_reply", "value")
|
||||||
canned_reply_user = fields.dig("canned_reply_user", "value")
|
canned_reply_user = fields.dig("canned_reply_user", "value")
|
||||||
reply_persona_id = fields.dig("reply_persona", "value")
|
reply_agent_id = fields.dig("reply_agent", "value")
|
||||||
whisper = fields.dig("whisper", "value")
|
whisper = fields.dig("whisper", "value")
|
||||||
|
|
||||||
# nothing to do if we already replied
|
# nothing to do if we already replied
|
||||||
@ -113,7 +113,7 @@ if defined?(DiscourseAutomation)
|
|||||||
tags: tags,
|
tags: tags,
|
||||||
canned_reply: canned_reply,
|
canned_reply: canned_reply,
|
||||||
canned_reply_user: canned_reply_user,
|
canned_reply_user: canned_reply_user,
|
||||||
reply_persona_id: reply_persona_id,
|
reply_agent_id: reply_agent_id,
|
||||||
whisper: whisper,
|
whisper: whisper,
|
||||||
hide_topic: hide_topic,
|
hide_topic: hide_topic,
|
||||||
flag_post: flag_post,
|
flag_post: flag_post,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
module ArtifactUpdateStrategies
|
module ArtifactUpdateStrategies
|
||||||
class InvalidFormatError < StandardError
|
class InvalidFormatError < StandardError
|
||||||
end
|
end
|
@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
module ArtifactUpdateStrategies
|
module ArtifactUpdateStrategies
|
||||||
class Diff < Base
|
class Diff < Base
|
||||||
attr_reader :failed_searches
|
attr_reader :failed_searches
|
@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
module ArtifactUpdateStrategies
|
module ArtifactUpdateStrategies
|
||||||
class Full < Base
|
class Full < Base
|
||||||
private
|
private
|
@ -1,8 +1,8 @@
|
|||||||
#frozen_string_literal: true
|
#frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
class Artist < Persona
|
class Artist < Agent
|
||||||
def tools
|
def tools
|
||||||
[Tools::Image]
|
[Tools::Image]
|
||||||
end
|
end
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
class Bot
|
class Bot
|
||||||
attr_reader :model
|
attr_reader :model
|
||||||
|
|
||||||
@ -13,19 +13,19 @@ module DiscourseAi
|
|||||||
# limit is arbitrary, but 5 which was used in the past was too low
|
# limit is arbitrary, but 5 which was used in the past was too low
|
||||||
MAX_TOOLS = 20
|
MAX_TOOLS = 20
|
||||||
|
|
||||||
def self.as(bot_user, persona: DiscourseAi::Personas::General.new, model: nil)
|
def self.as(bot_user, agent: DiscourseAi::Agents::General.new, model: nil)
|
||||||
new(bot_user, persona, model)
|
new(bot_user, agent, model)
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(bot_user, persona, model = nil)
|
def initialize(bot_user, agent, model = nil)
|
||||||
@bot_user = bot_user
|
@bot_user = bot_user
|
||||||
@persona = persona
|
@agent = agent
|
||||||
@model =
|
@model =
|
||||||
model || self.class.guess_model(bot_user) || LlmModel.find(@persona.class.default_llm_id)
|
model || self.class.guess_model(bot_user) || LlmModel.find(@agent.class.default_llm_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_reader :bot_user
|
attr_reader :bot_user
|
||||||
attr_accessor :persona
|
attr_accessor :agent
|
||||||
|
|
||||||
def llm
|
def llm
|
||||||
DiscourseAi::Completions::Llm.proxy(model)
|
DiscourseAi::Completions::Llm.proxy(model)
|
||||||
@ -35,12 +35,12 @@ module DiscourseAi
|
|||||||
return if prompt.tool_choice == :none
|
return if prompt.tool_choice == :none
|
||||||
|
|
||||||
context.chosen_tools ||= []
|
context.chosen_tools ||= []
|
||||||
forced_tools = persona.force_tool_use.map { |tool| tool.name }
|
forced_tools = agent.force_tool_use.map { |tool| tool.name }
|
||||||
force_tool = forced_tools.find { |name| !context.chosen_tools.include?(name) }
|
force_tool = forced_tools.find { |name| !context.chosen_tools.include?(name) }
|
||||||
|
|
||||||
if force_tool && persona.forced_tool_count > 0
|
if force_tool && agent.forced_tool_count > 0
|
||||||
user_turns = prompt.messages.select { |m| m[:type] == :user }.length
|
user_turns = prompt.messages.select { |m| m[:type] == :user }.length
|
||||||
force_tool = false if user_turns > persona.forced_tool_count
|
force_tool = false if user_turns > agent.forced_tool_count
|
||||||
end
|
end
|
||||||
|
|
||||||
if force_tool
|
if force_tool
|
||||||
@ -57,7 +57,7 @@ module DiscourseAi
|
|||||||
end
|
end
|
||||||
context.cancel_manager ||= DiscourseAi::Completions::CancelManager.new
|
context.cancel_manager ||= DiscourseAi::Completions::CancelManager.new
|
||||||
current_llm = llm
|
current_llm = llm
|
||||||
prompt = persona.craft_prompt(context, llm: current_llm)
|
prompt = agent.craft_prompt(context, llm: current_llm)
|
||||||
|
|
||||||
total_completions = 0
|
total_completions = 0
|
||||||
ongoing_chain = true
|
ongoing_chain = true
|
||||||
@ -67,11 +67,11 @@ module DiscourseAi
|
|||||||
|
|
||||||
llm_kwargs = llm_args.dup
|
llm_kwargs = llm_args.dup
|
||||||
llm_kwargs[:user] = user
|
llm_kwargs[:user] = user
|
||||||
llm_kwargs[:temperature] = persona.temperature if persona.temperature
|
llm_kwargs[:temperature] = agent.temperature if agent.temperature
|
||||||
llm_kwargs[:top_p] = persona.top_p if persona.top_p
|
llm_kwargs[:top_p] = agent.top_p if agent.top_p
|
||||||
llm_kwargs[:response_format] = build_json_schema(
|
llm_kwargs[:response_format] = build_json_schema(
|
||||||
persona.response_format,
|
agent.response_format,
|
||||||
) if persona.response_format.present?
|
) if agent.response_format.present?
|
||||||
|
|
||||||
needs_newlines = false
|
needs_newlines = false
|
||||||
tools_ran = 0
|
tools_ran = 0
|
||||||
@ -82,7 +82,7 @@ module DiscourseAi
|
|||||||
|
|
||||||
tool_halted = false
|
tool_halted = false
|
||||||
|
|
||||||
allow_partial_tool_calls = persona.allow_partial_tool_calls?
|
allow_partial_tool_calls = agent.allow_partial_tool_calls?
|
||||||
existing_tools = Set.new
|
existing_tools = Set.new
|
||||||
current_thinking = []
|
current_thinking = []
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ module DiscourseAi
|
|||||||
**llm_kwargs,
|
**llm_kwargs,
|
||||||
) do |partial|
|
) do |partial|
|
||||||
tool =
|
tool =
|
||||||
persona.find_tool(
|
agent.find_tool(
|
||||||
partial,
|
partial,
|
||||||
bot_user: user,
|
bot_user: user,
|
||||||
llm: current_llm,
|
llm: current_llm,
|
||||||
@ -183,7 +183,7 @@ module DiscourseAi
|
|||||||
end
|
end
|
||||||
|
|
||||||
def returns_json?
|
def returns_json?
|
||||||
persona.response_format.present?
|
agent.response_format.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@ -285,7 +285,7 @@ module DiscourseAi
|
|||||||
def self.guess_model(bot_user)
|
def self.guess_model(bot_user)
|
||||||
associated_llm = LlmModel.find_by(user_id: bot_user.id)
|
associated_llm = LlmModel.find_by(user_id: bot_user.id)
|
||||||
|
|
||||||
return if associated_llm.nil? # Might be a persona user. Handled by constructor.
|
return if associated_llm.nil? # Might be a agent user. Handled by constructor.
|
||||||
|
|
||||||
associated_llm
|
associated_llm
|
||||||
end
|
end
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
class BotContext
|
class BotContext
|
||||||
attr_accessor :messages,
|
attr_accessor :messages,
|
||||||
:topic_id,
|
:topic_id,
|
@ -1,8 +1,8 @@
|
|||||||
#frozen_string_literal: true
|
#frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
class Creative < Persona
|
class Creative < Agent
|
||||||
def tools
|
def tools
|
||||||
[]
|
[]
|
||||||
end
|
end
|
@ -1,8 +1,8 @@
|
|||||||
#frozen_string_literal: true
|
#frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
class DallE3 < Persona
|
class DallE3 < Agent
|
||||||
def tools
|
def tools
|
||||||
[Tools::DallE]
|
[Tools::DallE]
|
||||||
end
|
end
|
@ -1,8 +1,8 @@
|
|||||||
#frozen_string_literal: true
|
#frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
class Designer < Persona
|
class Designer < Agent
|
||||||
def tools
|
def tools
|
||||||
[Tools::CreateImage, Tools::EditImage]
|
[Tools::CreateImage, Tools::EditImage]
|
||||||
end
|
end
|
@ -1,8 +1,8 @@
|
|||||||
#frozen_string_literal: true
|
#frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
class DiscourseHelper < Persona
|
class DiscourseHelper < Agent
|
||||||
def tools
|
def tools
|
||||||
[Tools::DiscourseMetaSearch]
|
[Tools::DiscourseMetaSearch]
|
||||||
end
|
end
|
@ -1,8 +1,8 @@
|
|||||||
#frozen_string_literal: true
|
#frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
class ForumResearcher < Persona
|
class ForumResearcher < Agent
|
||||||
def self.default_enabled
|
def self.default_enabled
|
||||||
false
|
false
|
||||||
end
|
end
|
@ -1,8 +1,8 @@
|
|||||||
#frozen_string_literal: true
|
#frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
class General < Persona
|
class General < Agent
|
||||||
def tools
|
def tools
|
||||||
[
|
[
|
||||||
Tools::Search,
|
Tools::Search,
|
@ -1,8 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
class GithubHelper < Persona
|
class GithubHelper < Agent
|
||||||
def tools
|
def tools
|
||||||
[
|
[
|
||||||
Tools::GithubFileContent,
|
Tools::GithubFileContent,
|
@ -1,8 +1,8 @@
|
|||||||
#frozen_string_literal: true
|
#frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
class Persona
|
class Agent
|
||||||
class << self
|
class << self
|
||||||
def default_enabled
|
def default_enabled
|
||||||
true
|
true
|
||||||
@ -36,8 +36,8 @@ module DiscourseAi
|
|||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
def system_personas
|
def system_agents
|
||||||
@system_personas ||= {
|
@system_agents ||= {
|
||||||
General => -1,
|
General => -1,
|
||||||
SqlHelper => -2,
|
SqlHelper => -2,
|
||||||
Artist => -3,
|
Artist => -3,
|
||||||
@ -55,17 +55,17 @@ module DiscourseAi
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def system_personas_by_id
|
def system_agents_by_id
|
||||||
@system_personas_by_id ||= system_personas.invert
|
@system_agents_by_id ||= system_agents.invert
|
||||||
end
|
end
|
||||||
|
|
||||||
def all(user:)
|
def all(user:)
|
||||||
# listing tools has to be dynamic cause site settings may change
|
# listing tools has to be dynamic cause site settings may change
|
||||||
AiPersona.all_personas.filter do |persona|
|
AiAgent.all_agents.filter do |agent|
|
||||||
next false if !user.in_any_groups?(persona.allowed_group_ids)
|
next false if !user.in_any_groups?(agent.allowed_group_ids)
|
||||||
|
|
||||||
if persona.system
|
if agent.system
|
||||||
instance = persona.new
|
instance = agent.new
|
||||||
(
|
(
|
||||||
instance.required_tools == [] ||
|
instance.required_tools == [] ||
|
||||||
(instance.required_tools - all_available_tools).empty?
|
(instance.required_tools - all_available_tools).empty?
|
||||||
@ -77,15 +77,15 @@ module DiscourseAi
|
|||||||
end
|
end
|
||||||
|
|
||||||
def find_by(id: nil, name: nil, user:)
|
def find_by(id: nil, name: nil, user:)
|
||||||
all(user: user).find { |persona| persona.id == id || persona.name == name }
|
all(user: user).find { |agent| agent.id == id || agent.name == name }
|
||||||
end
|
end
|
||||||
|
|
||||||
def name
|
def name
|
||||||
I18n.t("discourse_ai.ai_bot.personas.#{to_s.demodulize.underscore}.name")
|
I18n.t("discourse_ai.ai_bot.agents.#{to_s.demodulize.underscore}.name")
|
||||||
end
|
end
|
||||||
|
|
||||||
def description
|
def description
|
||||||
I18n.t("discourse_ai.ai_bot.personas.#{to_s.demodulize.underscore}.description")
|
I18n.t("discourse_ai.ai_bot.agents.#{to_s.demodulize.underscore}.description")
|
||||||
end
|
end
|
||||||
|
|
||||||
def all_available_tools
|
def all_available_tools
|
||||||
@ -134,8 +134,8 @@ module DiscourseAi
|
|||||||
end
|
end
|
||||||
|
|
||||||
def id
|
def id
|
||||||
@ai_persona&.id || self.class.system_personas[self.class.superclass] ||
|
@ai_agent&.id || self.class.system_agents[self.class.superclass] ||
|
||||||
self.class.system_personas[self.class]
|
self.class.system_agents[self.class]
|
||||||
end
|
end
|
||||||
|
|
||||||
def tools
|
def tools
|
||||||
@ -234,7 +234,7 @@ module DiscourseAi
|
|||||||
prompt.max_pixels = self.class.vision_max_pixels if self.class.vision_enabled
|
prompt.max_pixels = self.class.vision_max_pixels if self.class.vision_enabled
|
||||||
prompt.tools = available_tools.map(&:signature) if available_tools
|
prompt.tools = available_tools.map(&:signature) if available_tools
|
||||||
available_tools.each do |tool|
|
available_tools.each do |tool|
|
||||||
tool.inject_prompt(prompt: prompt, context: context, persona: self)
|
tool.inject_prompt(prompt: prompt, context: context, agent: self)
|
||||||
end
|
end
|
||||||
prompt
|
prompt
|
||||||
end
|
end
|
||||||
@ -307,7 +307,7 @@ module DiscourseAi
|
|||||||
tool_klass.new(
|
tool_klass.new(
|
||||||
arguments,
|
arguments,
|
||||||
tool_call_id: function_id || function_name,
|
tool_call_id: function_id || function_name,
|
||||||
persona_options: options[tool_klass].to_h,
|
agent_options: options[tool_klass].to_h,
|
||||||
bot_user: bot_user,
|
bot_user: bot_user,
|
||||||
llm: llm,
|
llm: llm,
|
||||||
context: context,
|
context: context,
|
||||||
@ -331,7 +331,7 @@ module DiscourseAi
|
|||||||
|
|
||||||
def rag_fragments_prompt(conversation_context, llm:, user:)
|
def rag_fragments_prompt(conversation_context, llm:, user:)
|
||||||
upload_refs =
|
upload_refs =
|
||||||
UploadReference.where(target_id: id, target_type: "AiPersona").pluck(:upload_id)
|
UploadReference.where(target_id: id, target_type: "AiAgent").pluck(:upload_id)
|
||||||
|
|
||||||
return nil if !DiscourseAi::Embeddings.enabled?
|
return nil if !DiscourseAi::Embeddings.enabled?
|
||||||
return nil if conversation_context.blank? || upload_refs.blank?
|
return nil if conversation_context.blank? || upload_refs.blank?
|
||||||
@ -346,7 +346,7 @@ module DiscourseAi
|
|||||||
consolidated_question = latest_interactions[0][:content]
|
consolidated_question = latest_interactions[0][:content]
|
||||||
else
|
else
|
||||||
consolidated_question =
|
consolidated_question =
|
||||||
DiscourseAi::Personas::QuestionConsolidator.consolidate_question(
|
DiscourseAi::Agents::QuestionConsolidator.consolidate_question(
|
||||||
llm,
|
llm,
|
||||||
latest_interactions,
|
latest_interactions,
|
||||||
user,
|
user,
|
||||||
@ -376,7 +376,7 @@ module DiscourseAi
|
|||||||
interactions_vector,
|
interactions_vector,
|
||||||
limit: search_limit,
|
limit: search_limit,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
) { |builder| builder.join(<<~SQL, target_id: id, target_type: "AiPersona") }
|
) { |builder| builder.join(<<~SQL, target_id: id, target_type: "AiAgent") }
|
||||||
rag_document_fragments ON
|
rag_document_fragments ON
|
||||||
rag_document_fragments.id = rag_document_fragment_id AND
|
rag_document_fragments.id = rag_document_fragment_id AND
|
||||||
rag_document_fragments.target_id = :target_id AND
|
rag_document_fragments.target_id = :target_id AND
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
class QuestionConsolidator
|
class QuestionConsolidator
|
||||||
attr_reader :llm, :messages, :user, :max_tokens
|
attr_reader :llm, :messages, :user, :max_tokens
|
||||||
|
|
@ -1,8 +1,8 @@
|
|||||||
#frozen_string_literal: true
|
#frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
class Researcher < Persona
|
class Researcher < Agent
|
||||||
def tools
|
def tools
|
||||||
[Tools::Google, Tools::WebBrowser]
|
[Tools::Google, Tools::WebBrowser]
|
||||||
end
|
end
|
@ -1,8 +1,8 @@
|
|||||||
#frozen_string_literal: true
|
#frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
class SettingsExplorer < Persona
|
class SettingsExplorer < Agent
|
||||||
def tools
|
def tools
|
||||||
[Tools::SettingContext, Tools::SearchSettings]
|
[Tools::SettingContext, Tools::SearchSettings]
|
||||||
end
|
end
|
@ -1,8 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
class ShortSummarizer < Persona
|
class ShortSummarizer < Agent
|
||||||
def self.default_enabled
|
def self.default_enabled
|
||||||
false
|
false
|
||||||
end
|
end
|
@ -1,8 +1,8 @@
|
|||||||
#frozen_string_literal: true
|
#frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
class SqlHelper < Persona
|
class SqlHelper < Agent
|
||||||
def self.schema
|
def self.schema
|
||||||
return @schema if defined?(@schema)
|
return @schema if defined?(@schema)
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ module DiscourseAi
|
|||||||
```
|
```
|
||||||
|
|
||||||
The user_actions tables stores likes (action_type 1).
|
The user_actions tables stores likes (action_type 1).
|
||||||
The topics table stores private/personal messages it uses archetype private_message for them.
|
The topics table stores private/agentl messages it uses archetype private_message for them.
|
||||||
notification_level can be: {muted: 0, regular: 1, tracking: 2, watching: 3, watching_first_post: 4}.
|
notification_level can be: {muted: 0, regular: 1, tracking: 2, watching: 3, watching_first_post: 4}.
|
||||||
bookmarkable_type can be: Post,Topic,ChatMessage and more
|
bookmarkable_type can be: Post,Topic,ChatMessage and more
|
||||||
|
|
@ -1,8 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
class Summarizer < Persona
|
class Summarizer < Agent
|
||||||
def self.default_enabled
|
def self.default_enabled
|
||||||
false
|
false
|
||||||
end
|
end
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
class ToolRunner
|
class ToolRunner
|
||||||
attr_reader :tool, :parameters, :llm
|
attr_reader :tool, :parameters, :llm
|
||||||
attr_accessor :running_attached_function, :timeout, :custom_raw
|
attr_accessor :running_attached_function, :timeout, :custom_raw
|
||||||
@ -14,11 +14,11 @@ module DiscourseAi
|
|||||||
MAX_HTTP_REQUESTS = 20
|
MAX_HTTP_REQUESTS = 20
|
||||||
|
|
||||||
def initialize(parameters:, llm:, bot_user:, context: nil, tool:, timeout: nil)
|
def initialize(parameters:, llm:, bot_user:, context: nil, tool:, timeout: nil)
|
||||||
if context && !context.is_a?(DiscourseAi::Personas::BotContext)
|
if context && !context.is_a?(DiscourseAi::Agents::BotContext)
|
||||||
raise ArgumentError, "context must be a BotContext object"
|
raise ArgumentError, "context must be a BotContext object"
|
||||||
end
|
end
|
||||||
|
|
||||||
context ||= DiscourseAi::Personas::BotContext.new
|
context ||= DiscourseAi::Agents::BotContext.new
|
||||||
|
|
||||||
@parameters = parameters
|
@parameters = parameters
|
||||||
@llm = llm
|
@llm = llm
|
||||||
@ -82,8 +82,8 @@ module DiscourseAi
|
|||||||
search: function(params) {
|
search: function(params) {
|
||||||
return _discourse_search(params);
|
return _discourse_search(params);
|
||||||
},
|
},
|
||||||
updatePersona: function(persona_id_or_name, updates) {
|
updateAgent: function(agent_id_or_name, updates) {
|
||||||
const result = _discourse_update_persona(persona_id_or_name, updates);
|
const result = _discourse_update_agent(agent_id_or_name, updates);
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
throw new Error(result.error);
|
throw new Error(result.error);
|
||||||
}
|
}
|
||||||
@ -92,29 +92,29 @@ module DiscourseAi
|
|||||||
getPost: _discourse_get_post,
|
getPost: _discourse_get_post,
|
||||||
getTopic: _discourse_get_topic,
|
getTopic: _discourse_get_topic,
|
||||||
getUser: _discourse_get_user,
|
getUser: _discourse_get_user,
|
||||||
getPersona: function(name) {
|
getAgent: function(name) {
|
||||||
const personaDetails = _discourse_get_persona(name);
|
const agentDetails = _discourse_get_agent(name);
|
||||||
if (personaDetails.error) {
|
if (agentDetails.error) {
|
||||||
throw new Error(personaDetails.error);
|
throw new Error(agentDetails.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// merge result.persona with {}..
|
// merge result.agent with {}..
|
||||||
return Object.assign({
|
return Object.assign({
|
||||||
update: function(updates) {
|
update: function(updates) {
|
||||||
const result = _discourse_update_persona(name, updates);
|
const result = _discourse_update_agent(name, updates);
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
throw new Error(result.error);
|
throw new Error(result.error);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
respondTo: function(params) {
|
respondTo: function(params) {
|
||||||
const result = _discourse_respond_to_persona(name, params);
|
const result = _discourse_respond_to_agent(name, params);
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
throw new Error(result.error);
|
throw new Error(result.error);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}, personaDetails.persona);
|
}, agentDetails.agent);
|
||||||
},
|
},
|
||||||
createChatMessage: function(params) {
|
createChatMessage: function(params) {
|
||||||
const result = _discourse_create_chat_message(params);
|
const result = _discourse_create_chat_message(params);
|
||||||
@ -365,15 +365,15 @@ module DiscourseAi
|
|||||||
)
|
)
|
||||||
|
|
||||||
mini_racer_context.attach(
|
mini_racer_context.attach(
|
||||||
"_discourse_respond_to_persona",
|
"_discourse_respond_to_agent",
|
||||||
->(persona_name, params) do
|
->(agent_name, params) do
|
||||||
in_attached_function do
|
in_attached_function do
|
||||||
# if we have 1000s of personas this can be slow ... we may need to optimize
|
# if we have 1000s of agents this can be slow ... we may need to optimize
|
||||||
persona_class = AiPersona.all_personas.find { |persona| persona.name == persona_name }
|
agent_class = AiAgent.all_agents.find { |agent| agent.name == agent_name }
|
||||||
return { error: "Persona not found" } if persona_class.nil?
|
return { error: "Agent not found" } if agent_class.nil?
|
||||||
|
|
||||||
persona = persona_class.new
|
agent = agent_class.new
|
||||||
bot = DiscourseAi::Personas::Bot.as(@bot_user || persona.user, persona: persona)
|
bot = DiscourseAi::Agents::Bot.as(@bot_user || agent.user, agent: agent)
|
||||||
playground = DiscourseAi::AiBot::Playground.new(bot)
|
playground = DiscourseAi::AiBot::Playground.new(bot)
|
||||||
|
|
||||||
if @context.post_id
|
if @context.post_id
|
||||||
@ -479,17 +479,17 @@ module DiscourseAi
|
|||||||
)
|
)
|
||||||
|
|
||||||
mini_racer_context.attach(
|
mini_racer_context.attach(
|
||||||
"_discourse_get_persona",
|
"_discourse_get_agent",
|
||||||
->(persona_name) do
|
->(agent_name) do
|
||||||
in_attached_function do
|
in_attached_function do
|
||||||
persona = AiPersona.find_by(name: persona_name)
|
agent = AiAgent.find_by(name: agent_name)
|
||||||
|
|
||||||
return { error: "Persona not found" } if persona.nil?
|
return { error: "Agent not found" } if agent.nil?
|
||||||
|
|
||||||
# Return a subset of relevant persona attributes
|
# Return a subset of relevant agent attributes
|
||||||
{
|
{
|
||||||
persona:
|
agent:
|
||||||
persona.attributes.slice(
|
agent.attributes.slice(
|
||||||
"id",
|
"id",
|
||||||
"name",
|
"name",
|
||||||
"description",
|
"description",
|
||||||
@ -503,7 +503,7 @@ module DiscourseAi
|
|||||||
"allow_chat_channel_mentions",
|
"allow_chat_channel_mentions",
|
||||||
"allow_chat_direct_messages",
|
"allow_chat_direct_messages",
|
||||||
"allow_topic_mentions",
|
"allow_topic_mentions",
|
||||||
"allow_personal_messages",
|
"allow_agentl_messages",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
@ -511,19 +511,19 @@ module DiscourseAi
|
|||||||
)
|
)
|
||||||
|
|
||||||
mini_racer_context.attach(
|
mini_racer_context.attach(
|
||||||
"_discourse_update_persona",
|
"_discourse_update_agent",
|
||||||
->(persona_id_or_name, updates) do
|
->(agent_id_or_name, updates) do
|
||||||
in_attached_function do
|
in_attached_function do
|
||||||
# Find persona by ID or name
|
# Find agent by ID or name
|
||||||
persona = nil
|
agent = nil
|
||||||
if persona_id_or_name.is_a?(Integer) ||
|
if agent_id_or_name.is_a?(Integer) ||
|
||||||
persona_id_or_name.to_i.to_s == persona_id_or_name
|
agent_id_or_name.to_i.to_s == agent_id_or_name
|
||||||
persona = AiPersona.find_by(id: persona_id_or_name.to_i)
|
agent = AiAgent.find_by(id: agent_id_or_name.to_i)
|
||||||
else
|
else
|
||||||
persona = AiPersona.find_by(name: persona_id_or_name)
|
agent = AiAgent.find_by(name: agent_id_or_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
return { error: "Persona not found" } if persona.nil?
|
return { error: "Agent not found" } if agent.nil?
|
||||||
|
|
||||||
allowed_updates = {}
|
allowed_updates = {}
|
||||||
|
|
||||||
@ -545,12 +545,12 @@ module DiscourseAi
|
|||||||
TrueClass,
|
TrueClass,
|
||||||
) || updates["enabled"].is_a?(FalseClass)
|
) || updates["enabled"].is_a?(FalseClass)
|
||||||
|
|
||||||
if persona.update(allowed_updates)
|
if agent.update(allowed_updates)
|
||||||
return(
|
return(
|
||||||
{
|
{
|
||||||
success: true,
|
success: true,
|
||||||
persona:
|
agent:
|
||||||
persona.attributes.slice(
|
agent.attributes.slice(
|
||||||
"id",
|
"id",
|
||||||
"name",
|
"name",
|
||||||
"description",
|
"description",
|
||||||
@ -562,7 +562,7 @@ module DiscourseAi
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
return { error: persona.errors.full_messages.join(", ") }
|
return { error: agent.errors.full_messages.join(", ") }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
@ -612,7 +612,7 @@ module DiscourseAi
|
|||||||
headers = (options && options["headers"]) || {}
|
headers = (options && options["headers"]) || {}
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
DiscourseAi::Personas::Tools::Tool.send_http_request(
|
DiscourseAi::Agents::Tools::Tool.send_http_request(
|
||||||
url,
|
url,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
) do |response|
|
) do |response|
|
||||||
@ -641,7 +641,7 @@ module DiscourseAi
|
|||||||
body = options && options["body"]
|
body = options && options["body"]
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
DiscourseAi::Personas::Tools::Tool.send_http_request(
|
DiscourseAi::Agents::Tools::Tool.send_http_request(
|
||||||
url,
|
url,
|
||||||
method: method,
|
method: method,
|
||||||
headers: headers,
|
headers: headers,
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
module Tools
|
module Tools
|
||||||
class CreateArtifact < Tool
|
class CreateArtifact < Tool
|
||||||
def self.name
|
def self.name
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
module Tools
|
module Tools
|
||||||
class CreateImage < Tool
|
class CreateImage < Tool
|
||||||
def self.signature
|
def self.signature
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
module Tools
|
module Tools
|
||||||
class Custom < Tool
|
class Custom < Tool
|
||||||
def self.class_instance(tool_id)
|
def self.class_instance(tool_id)
|
||||||
@ -33,7 +33,7 @@ module DiscourseAi
|
|||||||
end
|
end
|
||||||
|
|
||||||
def self.has_custom_context?
|
def self.has_custom_context?
|
||||||
# note on safety, this can be cached safely, we bump the whole persona cache when an ai tool is saved
|
# note on safety, this can be cached safely, we bump the whole agent cache when an ai tool is saved
|
||||||
# which will expire this class
|
# which will expire this class
|
||||||
return @has_custom_context if defined?(@has_custom_context)
|
return @has_custom_context if defined?(@has_custom_context)
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ module DiscourseAi
|
|||||||
@has_custom_context
|
@has_custom_context
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.inject_prompt(prompt:, context:, persona:)
|
def self.inject_prompt(prompt:, context:, agent:)
|
||||||
if has_custom_context?
|
if has_custom_context?
|
||||||
ai_tool = AiTool.find_by(id: tool_id)
|
ai_tool = AiTool.find_by(id: tool_id)
|
||||||
if ai_tool
|
if ai_tool
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
module Tools
|
module Tools
|
||||||
class DallE < Tool
|
class DallE < Tool
|
||||||
def self.signature
|
def self.signature
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
module Tools
|
module Tools
|
||||||
class DbSchema < Tool
|
class DbSchema < Tool
|
||||||
def self.signature
|
def self.signature
|
@ -1,7 +1,7 @@
|
|||||||
#frozen_string_literal: true
|
#frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
module Tools
|
module Tools
|
||||||
class DiscourseMetaSearch < Tool
|
class DiscourseMetaSearch < Tool
|
||||||
class << self
|
class << self
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
module Tools
|
module Tools
|
||||||
class EditImage < Tool
|
class EditImage < Tool
|
||||||
def self.signature
|
def self.signature
|
@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
module Tools
|
module Tools
|
||||||
class GithubFileContent < Tool
|
class GithubFileContent < Tool
|
||||||
def self.signature
|
def self.signature
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module DiscourseAi
|
module DiscourseAi
|
||||||
module Personas
|
module Agents
|
||||||
module Tools
|
module Tools
|
||||||
class GithubPullRequestDiff < Tool
|
class GithubPullRequestDiff < Tool
|
||||||
LARGE_OBJECT_THRESHOLD = 30_000
|
LARGE_OBJECT_THRESHOLD = 30_000
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user